+
+Note, this is __not__ a full SOCKS5 implementation due to missing GSSAPI
+authentication (but it's unlikely you're going to miss it anyway).
+
+By default, the `Client` communicates via SOCKS4a with the SOCKS server
+– unless you enable [authentication](#authentication), in which case it will
+default to SOCKS5.
+This is done because SOCKS4a incurs less overhead than SOCKS5 (see above) and
+is equivalent with SOCKS4 if you use [local DNS resolution](#dns-resolution).
+
+If want to explicitly set the protocol version, use the supported values URI
+schemes `socks4`, `socks4a` or `socks5` as part of the SOCKS URI:
+
+```php
+$client = new Client('socks5://127.0.0.1', $connector);
+```
+
+As seen above, both SOCKS5 and SOCKS4a support remote and local DNS resolution.
+If you've explicitly set this to SOCKS4, then you may want to check the following
+chapter about local DNS resolution or you may only connect to IPv4 addresses.
+
+#### DNS resolution
+
+By default, the `Client` does not perform any DNS resolution at all and simply
+forwards any hostname you're trying to connect to to the SOCKS server.
+The remote SOCKS server is thus responsible for looking up any hostnames via DNS
+(this default mode is thus called *remote DNS resolution*).
+As seen above, this mode is supported by the SOCKS5 and SOCKS4a protocols, but
+not the SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
+
+On the other hand, all SOCKS protocol versions support sending destination IP
+addresses to the SOCKS server.
+In this mode you either have to stick to using IPs only (which is ofen unfeasable)
+or perform any DNS lookups locally and only transmit the resolved destination IPs
+(this mode is thus called *local DNS resolution*).
+
+The default *remote DNS resolution* is useful if your local `Client` either can
+not resolve target hostnames because it has no direct access to the internet or
+if it should not resolve target hostnames because its outgoing DNS traffic might
+be intercepted (in particular when using the
+[Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections)).
+
+As noted above, the `Client` defaults to using remote DNS resolution.
+However, wrapping the `Client` in React's
+[`Connector`](https://github.com/reactphp/socket#connector) actually
+performs local DNS resolution unless explicitly defined otherwise.
+Given that remote DNS resolution is assumed to be the preferred mode, all
+other examples explicitly disable DNS resoltion like this:
+
+```php
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false
+));
+```
+
+If you want to explicitly use *local DNS resolution* (such as when explicitly
+using SOCKS4), you can use the following code:
+
+```php
+// set up Connector which uses Google's public DNS (8.8.8.8)
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => '8.8.8.8'
+));
+```
+
+See also the [fourth example](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+> Also note how local DNS resolution is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+If you've explicitly set the client to SOCKS4 and stick to the default
+*remote DNS resolution*, then you may only connect to IPv4 addresses because
+the protocol lacks a way to communicate hostnames.
+If you try to connect to a hostname despite, the resulting promise will be
+rejected right away.
+
+#### Authentication
+
+This library supports username/password authentication for SOCKS5 servers as
+defined in [RFC 1929](http://tools.ietf.org/html/rfc1929).
+
+On the client side, simply pass your username and password to use for
+authentication (see below).
+For each further connection the client will merely send a flag to the server
+indicating authentication information is available.
+Only if the server requests authentication during the initial handshake,
+the actual authentication credentials will be transmitted to the server.
+
+Note that the password is transmitted in cleartext to the SOCKS proxy server,
+so this methods should not be used on a network where you have to worry about eavesdropping.
+
+You can simply pass the authentication information as part of the SOCKS URI:
+
+```php
+$client = new Client('username:password@127.0.0.1', $connector);
+```
+
+Note that both the username and password must be percent-encoded if they contain
+special characters:
+
+```php
+$user = 'he:llo';
+$pass = 'p@ss';
+
+$client = new Client(
+ rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1',
+ $connector
+);
+```
+
+> The authentication details will be transmitted in cleartext to the SOCKS proxy
+ server only if it requires username/password authentication.
+ If the authentication details are missing or not accepted by the remote SOCKS
+ proxy server, it is expected to reject each connection attempt with an
+ exception error code of `SOCKET_EACCES` (13).
+
+Authentication is only supported by protocol version 5 (SOCKS5),
+so passing authentication to the `Client` enforces communication with protocol
+version 5 and complains if you have explicitly set anything else:
+
+```php
+// throws InvalidArgumentException
+new Client('socks4://user:pass@127.0.0.1', $connector);
+```
+
+#### Proxy chaining
+
+The `Client` is responsible for creating connections to the SOCKS server which
+then connects to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if you want to conceal your origin address.
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Client` uses any instance of the `ConnectorInterface` to establish
+outgoing connections.
+In order to connect through another SOCKS server, you can simply use another
+SOCKS connector from another SOCKS client like this:
+
+```php
+// https via the proxy chain "MiddlemanSocksServer -> TargetSocksServer -> TargetHost"
+// please note how the client uses TargetSocksServer (not MiddlemanSocksServer!),
+// which in turn then uses MiddlemanSocksServer.
+// this creates a TCP/IP connection to MiddlemanSocksServer, which then connects
+// to TargetSocksServer, which then connects to the TargetHost
+$middle = new Client('127.0.0.1:1080', new Connector($loop));
+$target = new Client('example.com:1080', $middle);
+
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $target,
+ 'dns' => false
+));
+
+$connector->connect('tls://www.google.com:443')->then(function ($stream) {
+ // …
+});
+```
+
+See also the [third example](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and not part of this class.
+ For example, you can find this in the below [`Server`](#server-proxy-chaining)
+ class or somewhat similar when you're using the
+ [Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections).
+
+#### Connection timeout
+
+By default, the `Client` does not implement any timeouts for establishing remote
+connections.
+Your underlying operating system may impose limits on pending and/or idle TCP/IP
+connections, anywhere in a range of a few minutes to several hours.
+
+Many use cases require more control over the timeout and likely values much
+smaller, usually in the range of a few seconds only.
+
+You can use React's [`Connector`](https://github.com/reactphp/socket#connector)
+or the low-level
+[`TimeoutConnector`](https://github.com/reactphp/socket#timeoutconnector)
+to decorate any given `ConnectorInterface` instance.
+It provides the same `connect()` method, but will automatically reject the
+underlying connection attempt if it takes too long:
+
+```php
+$connector = new Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false,
+ 'timeout' => 3.0
+));
+
+$connector->connect('tcp://google.com:80')->then(function ($stream) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+> Also note how connection timeout is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+#### SOCKS over TLS
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* you connect to and
+may also be able to see your [SOCKS authentication](#authentication) details
+in cleartext.
+
+As an alternative, you may establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+you want to connect to or your [SOCKS authentication](#authentication) details.
+
+You can use the `sockss://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$client = new Client('sockss://127.0.0.1:1080', new Connector($loop));
+
+$client = new Client('socks5s://127.0.0.1:1080', new Connector($loop));
+```
+
+See also [example 32](examples).
+
+Simiarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$client = new Client('sockss://user:pass@127.0.0.1:1080', new Connector($loop));
+```
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+ In particular, the SOCKS server has to accept secure TLS connections, see
+ also [Server SOCKS over TLS](#server-socks-over-tls) for more details.
+ Also, PHP does not support "double encryption" over a single connection.
+ This means that enabling [secure TLS connections](#secure-tls-connections)
+ over a communication channel that has been opened with SOCKS over TLS
+ may not be supported.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this may also have only limited support for
+ [proxy chaining](#proxy-chaining) over multiple TLS paths.
+
+#### Unix domain sockets
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS server listen on a
+Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#authentication).
+
+You can use the `socks+unix://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$client = new Client('socks+unix:///tmp/proxy.sock', new Connector($loop));
+
+$client = new Client('socks5+unix:///tmp/proxy.sock', new Connector($loop));
+```
+
+Simiarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$client = new Client('socks+unix://user:pass@/tmp/proxy.sock', new Connector($loop));
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
+ has limited support for this.
+ In particular, enabling [secure TLS](#secure-tls-connections) may not be
+ supported.
+
+> Note that SOCKS protocol does not support the notion of UDS paths. The above
+ works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does also not support [proxy chaining](#proxy-chaining)
+ over multiple UDS paths.
+
+### Server
+
+The `Server` is responsible for accepting incoming communication from SOCKS clients
+and forwarding the requested connection to the target host.
+It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
+and an underlying TCP/IP socket server like this:
+
+```php
+$loop = \React\EventLoop\Factory::create();
+
+// listen on localhost:$port
+$socket = new Socket($port, $loop);
+
+$server = new Server($loop, $socket);
+```
+
+#### Server connector
+
+The `Server` uses an instance of the [`ConnectorInterface`](#connectorinterface)
+to establish outgoing connections for each incoming connection request.
+
+If you need custom connector settings (DNS resolution, timeouts etc.), you can explicitly pass a
+custom instance of the [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
+
+```php
+// use local DNS server
+$dnsResolverFactory = new DnsFactory();
+$resolver = $dnsResolverFactory->createCached('127.0.0.1', $loop);
+
+// outgoing connections to target host via interface 192.168.10.1
+$connector = new DnsConnector(
+ new TcpConnector($loop, array('bindto' => '192.168.10.1:0')),
+ $resolver
+);
+
+$server = new Server($loop, $socket, $connector);
+```
+
+If you want to forward the outgoing connection through another SOCKS proxy, you
+may also pass a [`Client`](#client) instance as a connector, see also
+[server proxy chaining](#server-proxy-chaining) for more details.
+
+Internally, the `Server` uses the normal [`connect()`](#connect) method, but
+it also passes the original client IP as the `?source={remote}` parameter.
+The `source` parameter contains the full remote URI, including the protocol
+and any authentication details, for example `socks5://user:pass@1.2.3.4:5678`.
+You can use this parameter for logging purposes or to restrict connection
+requests for certain clients by providing a custom implementation of the
+[`ConnectorInterface`](#connectorinterface).
+
+#### Server protocol version
+
+The `Server` supports all protocol versions (SOCKS4, SOCKS4a and SOCKS5) by default.
+
+If want to explicitly set the protocol version, use the supported values `4`, `4a` or `5`:
+
+```PHP
+$server->setProtocolVersion(5);
+```
+
+In order to reset the protocol version to its default (i.e. automatic detection),
+use `null` as protocol version.
+
+```PHP
+$server->setProtocolVersion(null);
+```
+
+#### Server authentication
+
+By default, the `Server` does not require any authentication from the clients.
+You can enable authentication support so that clients need to pass a valid
+username and password before forwarding any connections.
+
+Setting authentication on the `Server` enforces each further connected client
+to use protocol version 5 (SOCKS5).
+If a client tries to use any other protocol version, does not send along
+authentication details or if authentication details can not be verified,
+the connection will be rejected.
+
+Because your authentication mechanism might take some time to actually check
+the provided authentication credentials (like querying a remote database or webservice),
+the server side uses a [Promise](https://github.com/reactphp/promise) based interface.
+While this might seem complex at first, it actually provides a very simple way
+to handle simultanous connections in a non-blocking fashion and increases overall performance.
+
+```PHP
+$server->setAuth(function ($username, $password, $remote) {
+ // either return a boolean success value right away
+ // or use promises for delayed authentication
+
+ // $remote is a full URI à la socks5://user:pass@192.168.1.1:1234
+ // or socks5s://user:pass@192.168.1.1:1234 for SOCKS over TLS
+ // useful for logging or extracting parts, such as the remote IP
+ $ip = parse_url($remote, PHP_URL_HOST);
+
+ return ($username === 'root' && $ip === '127.0.0.1');
+});
+```
+
+Or if you only accept static authentication details, you can use the simple
+array-based authentication method as a shortcut:
+
+```PHP
+$server->setAuthArray(array(
+ 'tom' => 'password',
+ 'admin' => 'root'
+));
+```
+
+See also [example #12](examples).
+
+If you do not want to use authentication anymore:
+
+```PHP
+$server->unsetAuth();
+```
+
+#### Server proxy chaining
+
+The `Server` is responsible for creating connections to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if your target SOCKS server requires
+authentication, but your client does not support sending authentication
+information (e.g. like most webbrowser).
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Server` uses any instance of the `ConnectorInterface` to establish outgoing
+connections.
+In order to connect through another SOCKS server, you can simply use the
+[`Client`](#client) SOCKS connector from above.
+You can create a SOCKS `Client` instance like this:
+
+```php
+// set next SOCKS server example.com:1080 as target
+$connector = new React\Socket\Connector($loop);
+$client = new Client('user:pass@example.com:1080', $connector);
+
+// listen on localhost:1080
+$socket = new Socket('127.0.0.1:1080', $loop);
+
+// start a new server which forwards all connections to the other SOCKS server
+$server = new Server($loop, $socket, $client);
+```
+
+See also [example #21](examples).
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property and not part of this class.
+ For example, you can find this in the above [`Client`](#proxy-chaining) class.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and can be implemented as above.
+
+#### Server SOCKS over TLS
+
+All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* the client connects
+to and may also be able to see the [SOCKS authentication](#authentication)
+details in cleartext.
+
+As an alternative, you may listen for SOCKS over TLS connections so
+that the client has to establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+the client wants to connect to or their [SOCKS authentication](#authentication)
+details.
+
+You can simply start your listening socket on the `tls://` URI scheme like this:
+
+```php
+$loop = \React\EventLoop\Factory::create();
+
+// listen on tls://127.0.0.1:1080 with the given server certificate
+$socket = new React\Socket\Server('tls://127.0.0.1:1080', $loop, array(
+ 'tls' => array(
+ 'local_cert' => __DIR__ . '/localhost.pem',
+ )
+));
+$server = new Server($loop, $socket);
+```
+
+See also [example 31](examples).
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this does also not support [proxy chaining](#server-proxy-chaining)
+ over multiple TLS paths.
+
+#### Server Unix domain sockets
+
+All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS server listen on a
+Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#server-authentication).
+
+You can simply start your listening socket on the `unix://` URI scheme like this:
+
+```php
+$loop = \React\EventLoop\Factory::create();
+
+// listen on /tmp/proxy.sock
+$socket = new React\Socket\Server('unix:///tmp/proxy.sock', $loop);
+$server = new Server($loop, $socket);
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and that
+ the SOCKS protocol does not support the notion of UDS paths. The above
+ works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does also not support [proxy chaining](#server-proxy-chaining)
+ over multiple UDS paths.
+
+## Servers
+
+### Using a PHP SOCKS server
+
+* If you're looking for an end-user SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
+* If you're looking for a SOCKS server implementation, consider using
+ the above [`Server`](#server) class.
+
+### Using SSH as a SOCKS server
+
+If you already have an SSH server set up, you can easily use it as a SOCKS
+tunnel end point. On your client, simply start your SSH client and use
+the `-D ` option to start a local SOCKS server (quoting the man page:
+a `local "dynamic" application-level port forwarding`).
+
+You can start a local SOCKS server by creating a loopback connection to your
+local system if you already run an SSH daemon:
+
+```bash
+$ ssh -D 1080 localhost
+```
+
+Alternatively, you can start a local SOCKS server tunneling through a given
+remote host that runs an SSH daemon:
+
+```bash
+$ ssh -D 1080 example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$client = new Client('127.0.0.1:1080', $connector);
+```
+
+Note that the above will allow all users on the local system to connect over
+your SOCKS server without authentication which may or may not be what you need.
+As an alternative, recent OpenSSH client versions also support
+[Unix domain sockets](#unix-domain-sockets) (UDS) paths so that you can rely
+on Unix file system permissions instead:
+
+```bash
+$ ssh -D/tmp/proxy.sock example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$client = new Client('socks+unix:///tmp/proxy.sock', $connector);
+```
+
+### Using the Tor (anonymity network) to tunnel SOCKS connections
+
+The [Tor anonymity network](http://www.torproject.org) client software is designed
+to encrypt your traffic and route it over a network of several nodes to conceal its origin.
+It presents a SOCKS4 and SOCKS5 interface on TCP port 9050 by default
+which allows you to tunnel any traffic through the anonymity network.
+In most scenarios you probably don't want your client to resolve the target hostnames,
+because you would leak DNS information to anybody observing your local traffic.
+Also, Tor provides hidden services through an `.onion` pseudo top-level domain
+which have to be resolved by Tor.
+
+```PHP
+$client = new Client('127.0.0.1:9050', $connector);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require clue/socks-react:^0.8.7
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite contains a number of tests that rely on a working internet
+connection, alternatively you can also run it like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see LICENSE
+
+## More
+
+* If you want to learn more about processing streams of data, refer to the
+ documentation of the underlying
+ [react/stream](https://github.com/reactphp/stream) component.
+* If you want to learn more about how the
+ [`ConnectorInterface`](#connectorinterface) and its usual implementations look
+ like, refer to the documentation of the underlying
+ [react/socket component](https://github.com/reactphp/socket).
+* As an alternative to a SOCKS (SOCKS4/SOCKS5) proxy, you may also want to look into
+ using an HTTP CONNECT proxy instead.
+ You may want to use [clue/http-proxy-react](https://github.com/clue/php-http-proxy-react)
+ which also provides an implementation of the
+ [`ConnectorInterface`](#connectorinterface) so that supporting either proxy
+ protocol should be fairly trivial.
+* If you're dealing with public proxies, you'll likely have to work with mixed
+ quality and unreliable proxies. You may want to look into using
+ [clue/connection-manager-extra](https://github.com/clue/php-connection-manager-extra)
+ which allows retrying unreliable ones, implying connection timeouts,
+ concurrently working with multiple connectors and more.
+* If you're looking for an end-user SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
diff --git a/instafeed/vendor/clue/socks-react/composer.json b/instafeed/vendor/clue/socks-react/composer.json
new file mode 100755
index 0000000..50ef83a
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "clue/socks-react",
+ "description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
+ "keywords": ["socks client", "socks server", "proxy", "tcp tunnel", "socks protocol", "async", "ReactPHP"],
+ "homepage": "https://github.com/clue/php-socks-react",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "autoload": {
+ "psr-4": {"Clue\\React\\Socks\\": "src/"}
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/socket": "^1.0 || ^0.8.6",
+ "react/promise": "^2.1 || ^1.2",
+ "evenement/evenement": "~3.0|~1.0|~2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
+ "clue/connection-manager-extra": "^1.0 || ^0.7",
+ "clue/block-react": "^1.1"
+ }
+}
diff --git a/instafeed/vendor/clue/socks-react/examples/01-http.php b/instafeed/vendor/clue/socks-react/examples/01-http.php
new file mode 100755
index 0000000..584b6c4
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/01-http.php
@@ -0,0 +1,30 @@
+ $client,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/02-https.php b/instafeed/vendor/clue/socks-react/examples/02-https.php
new file mode 100755
index 0000000..f763fc2
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/02-https.php
@@ -0,0 +1,30 @@
+ $client,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/03-proxy-chaining.php b/instafeed/vendor/clue/socks-react/examples/03-proxy-chaining.php
new file mode 100755
index 0000000..5278d2c
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/03-proxy-chaining.php
@@ -0,0 +1,46 @@
+ [...]' . PHP_EOL;
+ echo 'You can add 1..n proxies in the path' . PHP_EOL;
+ exit(1);
+}
+
+$path = array_slice($argv, 1);
+
+// Alternatively, you can also hard-code this value like this:
+//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
+
+$loop = React\EventLoop\Factory::create();
+
+// set next SOCKS server chain via p1 -> p2 -> p3 -> destination
+$connector = new Connector($loop);
+foreach ($path as $proxy) {
+ $connector = new Client($proxy, $connector);
+}
+
+// please note how the client uses p3 (not p1!), which in turn then uses the complete chain
+// this creates a TCP/IP connection to p1, which then connects to p2, then to p3, which then connects to the target
+$connector = new Connector($loop, array(
+ 'tcp' => $connector,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS client connecting to SOCKS proxy server chain ' . implode(' -> ', $path) . PHP_EOL;
+
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/04-local-dns.php b/instafeed/vendor/clue/socks-react/examples/04-local-dns.php
new file mode 100755
index 0000000..2ea39dd
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/04-local-dns.php
@@ -0,0 +1,31 @@
+ $client,
+ 'timeout' => 3.0,
+ 'dns' => '8.8.8.8'
+));
+
+echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/11-server.php b/instafeed/vendor/clue/socks-react/examples/11-server.php
new file mode 100755
index 0000000..da0a1a1
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/11-server.php
@@ -0,0 +1,19 @@
+getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/12-server-with-password.php b/instafeed/vendor/clue/socks-react/examples/12-server-with-password.php
new file mode 100755
index 0000000..55cc30b
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/12-server-with-password.php
@@ -0,0 +1,24 @@
+setAuthArray(array(
+ 'tom' => 'god',
+ 'user' => 'p@ssw0rd'
+));
+
+echo 'SOCKS5 server requiring authentication listening on ' . $socket->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/13-server-blacklist.php b/instafeed/vendor/clue/socks-react/examples/13-server-blacklist.php
new file mode 100755
index 0000000..c153049
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/13-server-blacklist.php
@@ -0,0 +1,40 @@
+ $reject,
+ 'www.google.com:80' => $reject,
+ '*' => $permit
+));
+
+// listen on 127.0.0.1:1080 or first argument
+$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
+$socket = new Socket($listen, $loop);
+
+// start the actual socks server on the given server socket and using our connection manager for outgoing connections
+$server = new Server($loop, $socket, $connector);
+
+echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/21-server-proxy-chaining.php b/instafeed/vendor/clue/socks-react/examples/21-server-proxy-chaining.php
new file mode 100755
index 0000000..0d0dee2
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/21-server-proxy-chaining.php
@@ -0,0 +1,42 @@
+ [...]' . PHP_EOL;
+ echo 'You can add 1..n proxies in the path' . PHP_EOL;
+ exit(1);
+}
+
+$listen = $argv[1];
+$path = array_slice($argv, 2);
+
+// Alternatively, you can also hard-code these values like this:
+//$listen = '127.0.0.1:9050';
+//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
+
+$loop = React\EventLoop\Factory::create();
+
+// set next SOCKS server chain -> p1 -> p2 -> p3 -> destination
+$connector = new Connector($loop);
+foreach ($path as $proxy) {
+ $connector = new Client($proxy, $connector);
+}
+
+// listen on 127.0.0.1:1080 or first argument
+$socket = new Socket($listen, $loop);
+
+// start a new server which forwards all connections to the other SOCKS server
+$server = new Server($loop, $socket, $connector);
+
+echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
+echo 'Forwarding via: ' . implode(' -> ', $path) . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php b/instafeed/vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
new file mode 100755
index 0000000..39245c9
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
@@ -0,0 +1,46 @@
+ ...' . PHP_EOL;
+ echo 'You can add 2..n proxies in the pool' . PHP_EOL;
+ exit(1);
+}
+
+$listen = $argv[1];
+$pool = array_slice($argv, 2);
+
+// Alternatively, you can also hard-code these values like this:
+//$listen = '127.0.0.1:9050';
+//$pool = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
+
+$loop = LoopFactory::create();
+
+// forward to socks server listening on 127.0.0.1:9051-9053
+// this connector randomly picks one of the the attached connectors from the pool
+$connector = new Connector($loop);
+$clients = array();
+foreach ($pool as $proxy) {
+ $clients []= new Client($proxy, $connector);
+}
+$connector = new ConnectionManagerRandom($clients);
+
+$socket = new Socket($listen, $loop);
+
+// start the actual socks server on the given server socket and using our connection manager for outgoing connections
+$server = new Server($loop, $socket, $connector);
+
+echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
+echo 'Randomly picking from: ' . implode(', ', $pool) . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/31-server-secure.php b/instafeed/vendor/clue/socks-react/examples/31-server-secure.php
new file mode 100755
index 0000000..b4b2109
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/31-server-secure.php
@@ -0,0 +1,21 @@
+ array(
+ 'local_cert' => __DIR__ . '/localhost.pem',
+)));
+
+// start a new server listening for incoming connection on the given socket
+$server = new Server($loop, $socket);
+
+echo 'SOCKS over TLS server listening on ' . str_replace('tls:', 'sockss:', $socket->getAddress()) . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/32-http-secure.php b/instafeed/vendor/clue/socks-react/examples/32-http-secure.php
new file mode 100755
index 0000000..d304bd4
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/32-http-secure.php
@@ -0,0 +1,33 @@
+ array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+))));
+$connector = new Connector($loop, array(
+ 'tcp' => $client,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS over TLS client connecting to secure SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/clue/socks-react/examples/localhost.pem b/instafeed/vendor/clue/socks-react/examples/localhost.pem
new file mode 100755
index 0000000..be69279
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/examples/localhost.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
+MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
+BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
+0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
+7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
+824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
+V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
+IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
+ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
+g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
+tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
+LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
+tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
+9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
+43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
+pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
+OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
+2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
+li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
+b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
+vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
+XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
+Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
+iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
++EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
+y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
+81oh1uCH1YPLM29hPyaohxL8
+-----END PRIVATE KEY-----
diff --git a/instafeed/vendor/clue/socks-react/phpunit.xml.dist b/instafeed/vendor/clue/socks-react/phpunit.xml.dist
new file mode 100755
index 0000000..d451dff
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
diff --git a/instafeed/vendor/clue/socks-react/src/Client.php b/instafeed/vendor/clue/socks-react/src/Client.php
new file mode 100755
index 0000000..ee643b6
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/src/Client.php
@@ -0,0 +1,382 @@
+ SOCKS5 authentication
+ if (isset($parts['user']) || isset($parts['pass'])) {
+ if ($parts['scheme'] === 'socks') {
+ // default to using SOCKS5 if not given explicitly
+ $parts['scheme'] = 'socks5';
+ } elseif ($parts['scheme'] !== 'socks5') {
+ // fail if any other protocol version given explicitly
+ throw new InvalidArgumentException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
+ }
+ $parts += array('user' => '', 'pass' => '');
+ $this->setAuth(rawurldecode($parts['user']), rawurldecode($parts['pass']));
+ }
+
+ // check for valid protocol version from URI scheme
+ $this->setProtocolVersionFromScheme($parts['scheme']);
+
+ $this->socksUri = $parts['host'] . ':' . $parts['port'];
+ $this->connector = $connector;
+ }
+
+ private function setProtocolVersionFromScheme($scheme)
+ {
+ if ($scheme === 'socks' || $scheme === 'socks4a') {
+ $this->protocolVersion = '4a';
+ } elseif ($scheme === 'socks5') {
+ $this->protocolVersion = '5';
+ } elseif ($scheme === 'socks4') {
+ $this->protocolVersion = '4';
+ } else {
+ throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
+ }
+ }
+
+ /**
+ * set login data for username/password authentication method (RFC1929)
+ *
+ * @param string $username
+ * @param string $password
+ * @link http://tools.ietf.org/html/rfc1929
+ */
+ private function setAuth($username, $password)
+ {
+ if (strlen($username) > 255 || strlen($password) > 255) {
+ throw new InvalidArgumentException('Both username and password MUST NOT exceed a length of 255 bytes each');
+ }
+ $this->auth = pack('C2', 0x01, strlen($username)) . $username . pack('C', strlen($password)) . $password;
+ }
+
+ /**
+ * Establish a TCP/IP connection to the given target URI through the SOCKS server
+ *
+ * Many higher-level networking protocols build on top of TCP. It you're dealing
+ * with one such client implementation, it probably uses/accepts an instance
+ * implementing React's `ConnectorInterface` (and usually its default `Connector`
+ * instance). In this case you can also pass this `Connector` instance instead
+ * to make this client implementation SOCKS-aware. That's it.
+ *
+ * @param string $uri
+ * @return PromiseInterface Promise
+ */
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $port = $parts['port'];
+
+ if ($this->protocolVersion === '4' && false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ return Promise\reject(new InvalidArgumentException('Requires an IPv4 address for SOCKS4'));
+ }
+
+ if (strlen($host) > 255 || $port > 65535 || $port < 0 || (string)$port !== (string)(int)$port) {
+ return Promise\reject(new InvalidArgumentException('Invalid target specified'));
+ }
+
+ // construct URI to SOCKS server to connect to
+ $socksUri = $this->socksUri;
+
+ // append path from URI if given
+ if (isset($parts['path'])) {
+ $socksUri .= $parts['path'];
+ }
+
+ // parse query args
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // append hostname from URI to query string unless explicitly given
+ if (!isset($args['hostname'])) {
+ $args['hostname'] = $host;
+ }
+
+ // append query string
+ $socksUri .= '?' . http_build_query($args, '', '&');
+
+ // append fragment from URI if given
+ if (isset($parts['fragment'])) {
+ $socksUri .= '#' . $parts['fragment'];
+ }
+
+ $that = $this;
+
+ // start TCP/IP connection to SOCKS server and then
+ // handle SOCKS protocol once connection is ready
+ // resolve plain connection once SOCKS protocol is completed
+ return $this->connector->connect($socksUri)->then(
+ function (ConnectionInterface $stream) use ($that, $host, $port) {
+ return $that->handleConnectedSocks($stream, $host, $port);
+ },
+ function (Exception $e) {
+ throw new RuntimeException('Unable to connect to proxy (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $e);
+ }
+ );
+ }
+
+ /**
+ * Internal helper used to handle the communication with the SOCKS server
+ *
+ * @param ConnectionInterface $stream
+ * @param string $host
+ * @param int $port
+ * @return Promise Promise
+ * @internal
+ */
+ public function handleConnectedSocks(ConnectionInterface $stream, $host, $port)
+ {
+ $deferred = new Deferred(function ($_, $reject) {
+ $reject(new RuntimeException('Connection canceled while establishing SOCKS session (ECONNABORTED)', defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103));
+ });
+
+ $reader = new StreamReader();
+ $stream->on('data', array($reader, 'write'));
+
+ $stream->on('error', $onError = function (Exception $e) use ($deferred) {
+ $deferred->reject(new RuntimeException('Stream error while waiting for response from proxy (EIO)', defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e));
+ });
+
+ $stream->on('close', $onClose = function () use ($deferred) {
+ $deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
+ });
+
+ if ($this->protocolVersion === '5') {
+ $promise = $this->handleSocks5($stream, $host, $port, $reader);
+ } else {
+ $promise = $this->handleSocks4($stream, $host, $port, $reader);
+ }
+ $promise->then(function () use ($deferred, $stream) {
+ $deferred->resolve($stream);
+ }, function (Exception $error) use ($deferred) {
+ // pass custom RuntimeException through as-is, otherwise wrap in protocol error
+ if (!$error instanceof RuntimeException) {
+ $error = new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $error);
+ }
+
+ $deferred->reject($error);
+ });
+
+ return $deferred->promise()->then(
+ function (ConnectionInterface $stream) use ($reader, $onError, $onClose) {
+ $stream->removeListener('data', array($reader, 'write'));
+ $stream->removeListener('error', $onError);
+ $stream->removeListener('close', $onClose);
+
+ return $stream;
+ },
+ function ($error) use ($stream, $onClose) {
+ $stream->removeListener('close', $onClose);
+ $stream->close();
+
+ throw $error;
+ }
+ );
+ }
+
+ private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader)
+ {
+ // do not resolve hostname. only try to convert to IP
+ $ip = ip2long($host);
+
+ // send IP or (0.0.0.1) if invalid
+ $data = pack('C2nNC', 0x04, 0x01, $port, $ip === false ? 1 : $ip, 0x00);
+
+ if ($ip === false) {
+ // host is not a valid IP => send along hostname (SOCKS4a)
+ $data .= $host . pack('C', 0x00);
+ }
+
+ $stream->write($data);
+
+ return $reader->readBinary(array(
+ 'null' => 'C',
+ 'status' => 'C',
+ 'port' => 'n',
+ 'ip' => 'N'
+ ))->then(function ($data) {
+ if ($data['null'] !== 0x00) {
+ throw new Exception('Invalid SOCKS response');
+ }
+ if ($data['status'] !== 0x5a) {
+ throw new RuntimeException('Proxy refused connection with SOCKS error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ }
+ });
+ }
+
+ private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader)
+ {
+ // protocol version 5
+ $data = pack('C', 0x05);
+
+ $auth = $this->auth;
+ if ($auth === null) {
+ // one method, no authentication
+ $data .= pack('C2', 0x01, 0x00);
+ } else {
+ // two methods, username/password and no authentication
+ $data .= pack('C3', 0x02, 0x02, 0x00);
+ }
+ $stream->write($data);
+
+ $that = $this;
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'method' => 'C'
+ ))->then(function ($data) use ($auth, $stream, $reader) {
+ if ($data['version'] !== 0x05) {
+ throw new Exception('Version/Protocol mismatch');
+ }
+
+ if ($data['method'] === 0x02 && $auth !== null) {
+ // username/password authentication requested and provided
+ $stream->write($auth);
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'status' => 'C'
+ ))->then(function ($data) {
+ if ($data['version'] !== 0x01 || $data['status'] !== 0x00) {
+ throw new RuntimeException('Username/Password authentication failed (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ }
+ });
+ } else if ($data['method'] !== 0x00) {
+ // any other method than "no authentication"
+ throw new RuntimeException('No acceptable authentication method found (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ }
+ })->then(function () use ($stream, $reader, $host, $port) {
+ // do not resolve hostname. only try to convert to (binary/packed) IP
+ $ip = @inet_pton($host);
+
+ $data = pack('C3', 0x05, 0x01, 0x00);
+ if ($ip === false) {
+ // not an IP, send as hostname
+ $data .= pack('C2', 0x03, strlen($host)) . $host;
+ } else {
+ // send as IPv4 / IPv6
+ $data .= pack('C', (strpos($host, ':') === false) ? 0x01 : 0x04) . $ip;
+ }
+ $data .= pack('n', $port);
+
+ $stream->write($data);
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'status' => 'C',
+ 'null' => 'C',
+ 'type' => 'C'
+ ));
+ })->then(function ($data) use ($reader) {
+ if ($data['version'] !== 0x05 || $data['null'] !== 0x00) {
+ throw new Exception('Invalid SOCKS response');
+ }
+ if ($data['status'] !== 0x00) {
+ // map limited list of SOCKS error codes to common socket error conditions
+ // @link https://tools.ietf.org/html/rfc1928#section-6
+ if ($data['status'] === Server::ERROR_GENERAL) {
+ throw new RuntimeException('SOCKS server reported a general server failure (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ } elseif ($data['status'] === Server::ERROR_NOT_ALLOWED_BY_RULESET) {
+ throw new RuntimeException('SOCKS server reported connection is not allowed by ruleset (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ } elseif ($data['status'] === Server::ERROR_NETWORK_UNREACHABLE) {
+ throw new RuntimeException('SOCKS server reported network unreachable (ENETUNREACH)', defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101);
+ } elseif ($data['status'] === Server::ERROR_HOST_UNREACHABLE) {
+ throw new RuntimeException('SOCKS server reported host unreachable (EHOSTUNREACH)', defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113);
+ } elseif ($data['status'] === Server::ERROR_CONNECTION_REFUSED) {
+ throw new RuntimeException('SOCKS server reported connection refused (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ } elseif ($data['status'] === Server::ERROR_TTL) {
+ throw new RuntimeException('SOCKS server reported TTL/timeout expired (ETIMEDOUT)', defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110);
+ } elseif ($data['status'] === Server::ERROR_COMMAND_UNSUPPORTED) {
+ throw new RuntimeException('SOCKS server does not support the CONNECT command (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
+ } elseif ($data['status'] === Server::ERROR_ADDRESS_UNSUPPORTED) {
+ throw new RuntimeException('SOCKS server does not support this address type (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
+ }
+
+ throw new RuntimeException('SOCKS server reported an unassigned error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ }
+ if ($data['type'] === 0x01) {
+ // IPv4 address => skip IP and port
+ return $reader->readLength(6);
+ } elseif ($data['type'] === 0x03) {
+ // domain name => read domain name length
+ return $reader->readBinary(array(
+ 'length' => 'C'
+ ))->then(function ($data) use ($reader) {
+ // skip domain name and port
+ return $reader->readLength($data['length'] + 2);
+ });
+ } elseif ($data['type'] === 0x04) {
+ // IPv6 address => skip IP and port
+ return $reader->readLength(18);
+ } else {
+ throw new Exception('Invalid SOCKS reponse: Invalid address type');
+ }
+ });
+ }
+}
diff --git a/instafeed/vendor/clue/socks-react/src/Server.php b/instafeed/vendor/clue/socks-react/src/Server.php
new file mode 100755
index 0000000..0a069e2
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/src/Server.php
@@ -0,0 +1,422 @@
+loop = $loop;
+ $this->connector = $connector;
+
+ $that = $this;
+ $serverInterface->on('connection', function ($connection) use ($that) {
+ $that->emit('connection', array($connection));
+ $that->onConnection($connection);
+ });
+ }
+
+ public function setProtocolVersion($version)
+ {
+ if ($version !== null) {
+ $version = (string)$version;
+ if (!in_array($version, array('4', '4a', '5'), true)) {
+ throw new InvalidArgumentException('Invalid protocol version given');
+ }
+ if ($version !== '5' && $this->auth !== null){
+ throw new UnexpectedValueException('Unable to change protocol version to anything but SOCKS5 while authentication is used. Consider removing authentication info or sticking to SOCKS5');
+ }
+ }
+ $this->protocolVersion = $version;
+ }
+
+ public function setAuth($auth)
+ {
+ if (!is_callable($auth)) {
+ throw new InvalidArgumentException('Given authenticator is not a valid callable');
+ }
+ if ($this->protocolVersion !== null && $this->protocolVersion !== '5') {
+ throw new UnexpectedValueException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
+ }
+ // wrap authentication callback in order to cast its return value to a promise
+ $this->auth = function($username, $password, $remote) use ($auth) {
+ $ret = call_user_func($auth, $username, $password, $remote);
+ if ($ret instanceof PromiseInterface) {
+ return $ret;
+ }
+ $deferred = new Deferred();
+ $ret ? $deferred->resolve() : $deferred->reject();
+ return $deferred->promise();
+ };
+ }
+
+ public function setAuthArray(array $login)
+ {
+ $this->setAuth(function ($username, $password) use ($login) {
+ return (isset($login[$username]) && (string)$login[$username] === $password);
+ });
+ }
+
+ public function unsetAuth()
+ {
+ $this->auth = null;
+ }
+
+ public function onConnection(ConnectionInterface $connection)
+ {
+ $that = $this;
+ $handling = $this->handleSocks($connection)->then(function($remote) use ($connection){
+ $connection->emit('ready',array($remote));
+ }, function ($error) use ($connection, $that) {
+ if (!($error instanceof \Exception)) {
+ $error = new \Exception($error);
+ }
+ $connection->emit('error', array($error));
+ $that->endConnection($connection);
+ });
+
+ $connection->on('close', function () use ($handling) {
+ $handling->cancel();
+ });
+ }
+
+ /**
+ * gracefully shutdown connection by flushing all remaining data and closing stream
+ */
+ public function endConnection(ConnectionInterface $stream)
+ {
+ $tid = true;
+ $loop = $this->loop;
+
+ // cancel below timer in case connection is closed in time
+ $stream->once('close', function () use (&$tid, $loop) {
+ // close event called before the timer was set up, so everything is okay
+ if ($tid === true) {
+ // make sure to not start a useless timer
+ $tid = false;
+ } else {
+ $loop->cancelTimer($tid);
+ }
+ });
+
+ // shut down connection by pausing input data, flushing outgoing buffer and then exit
+ $stream->pause();
+ $stream->end();
+
+ // check if connection is not already closed
+ if ($tid === true) {
+ // fall back to forcefully close connection in 3 seconds if buffer can not be flushed
+ $tid = $loop->addTimer(3.0, array($stream,'close'));
+ }
+ }
+
+ private function handleSocks(ConnectionInterface $stream)
+ {
+ $reader = new StreamReader();
+ $stream->on('data', array($reader, 'write'));
+
+ $that = $this;
+ $that = $this;
+
+ $auth = $this->auth;
+ $protocolVersion = $this->protocolVersion;
+
+ // authentication requires SOCKS5
+ if ($auth !== null) {
+ $protocolVersion = '5';
+ }
+
+ return $reader->readByte()->then(function ($version) use ($stream, $that, $protocolVersion, $auth, $reader){
+ if ($version === 0x04) {
+ if ($protocolVersion === '5') {
+ throw new UnexpectedValueException('SOCKS4 not allowed due to configuration');
+ }
+ return $that->handleSocks4($stream, $protocolVersion, $reader);
+ } else if ($version === 0x05) {
+ if ($protocolVersion !== null && $protocolVersion !== '5') {
+ throw new UnexpectedValueException('SOCKS5 not allowed due to configuration');
+ }
+ return $that->handleSocks5($stream, $auth, $reader);
+ }
+ throw new UnexpectedValueException('Unexpected/unknown version number');
+ });
+ }
+
+ public function handleSocks4(ConnectionInterface $stream, $protocolVersion, StreamReader $reader)
+ {
+ // suppliying hostnames is only allowed for SOCKS4a (or automatically detected version)
+ $supportsHostname = ($protocolVersion === null || $protocolVersion === '4a');
+
+ $remote = $stream->getRemoteAddress();
+ if ($remote !== null) {
+ // remove transport scheme and prefix socks4:// instead
+ $secure = strpos($remote, 'tls://') === 0;
+ if (($pos = strpos($remote, '://')) !== false) {
+ $remote = substr($remote, $pos + 3);
+ }
+ $remote = 'socks4' . ($secure ? 's' : '') . '://' . $remote;
+ }
+
+ $that = $this;
+ return $reader->readByteAssert(0x01)->then(function () use ($reader) {
+ return $reader->readBinary(array(
+ 'port' => 'n',
+ 'ipLong' => 'N',
+ 'null' => 'C'
+ ));
+ })->then(function ($data) use ($reader, $supportsHostname, $remote) {
+ if ($data['null'] !== 0x00) {
+ throw new Exception('Not a null byte');
+ }
+ if ($data['ipLong'] === 0) {
+ throw new Exception('Invalid IP');
+ }
+ if ($data['port'] === 0) {
+ throw new Exception('Invalid port');
+ }
+ if ($data['ipLong'] < 256 && $supportsHostname) {
+ // invalid IP => probably a SOCKS4a request which appends the hostname
+ return $reader->readStringNull()->then(function ($string) use ($data, $remote){
+ return array($string, $data['port'], $remote);
+ });
+ } else {
+ $ip = long2ip($data['ipLong']);
+ return array($ip, $data['port'], $remote);
+ }
+ })->then(function ($target) use ($stream, $that) {
+ return $that->connectTarget($stream, $target)->then(function (ConnectionInterface $remote) use ($stream){
+ $stream->write(pack('C8', 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+
+ return $remote;
+ }, function($error) use ($stream){
+ $stream->end(pack('C8', 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+
+ throw $error;
+ });
+ }, function($error) {
+ throw new UnexpectedValueException('SOCKS4 protocol error',0,$error);
+ });
+ }
+
+ public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamReader $reader)
+ {
+ $remote = $stream->getRemoteAddress();
+ if ($remote !== null) {
+ // remove transport scheme and prefix socks5:// instead
+ $secure = strpos($remote, 'tls://') === 0;
+ if (($pos = strpos($remote, '://')) !== false) {
+ $remote = substr($remote, $pos + 3);
+ }
+ $remote = 'socks5' . ($secure ? 's' : '') . '://' . $remote;
+ }
+
+ $that = $this;
+ return $reader->readByte()->then(function ($num) use ($reader) {
+ // $num different authentication mechanisms offered
+ return $reader->readLength($num);
+ })->then(function ($methods) use ($reader, $stream, $auth, &$remote) {
+ if ($auth === null && strpos($methods,"\x00") !== false) {
+ // accept "no authentication"
+ $stream->write(pack('C2', 0x05, 0x00));
+
+ return 0x00;
+ } else if ($auth !== null && strpos($methods,"\x02") !== false) {
+ // username/password authentication (RFC 1929) sub negotiation
+ $stream->write(pack('C2', 0x05, 0x02));
+ return $reader->readByteAssert(0x01)->then(function () use ($reader) {
+ return $reader->readByte();
+ })->then(function ($length) use ($reader) {
+ return $reader->readLength($length);
+ })->then(function ($username) use ($reader, $auth, $stream, &$remote) {
+ return $reader->readByte()->then(function ($length) use ($reader) {
+ return $reader->readLength($length);
+ })->then(function ($password) use ($username, $auth, $stream, &$remote) {
+ // username and password given => authenticate
+
+ // prefix username/password to remote URI
+ if ($remote !== null) {
+ $remote = str_replace('://', '://' . rawurlencode($username) . ':' . rawurlencode($password) . '@', $remote);
+ }
+
+ return $auth($username, $password, $remote)->then(function () use ($stream, $username) {
+ // accept
+ $stream->emit('auth', array($username));
+ $stream->write(pack('C2', 0x01, 0x00));
+ }, function() use ($stream) {
+ // reject => send any code but 0x00
+ $stream->end(pack('C2', 0x01, 0xFF));
+ throw new UnexpectedValueException('Unable to authenticate');
+ });
+ });
+ });
+ } else {
+ // reject all offered authentication methods
+ $stream->write(pack('C2', 0x05, 0xFF));
+ throw new UnexpectedValueException('No acceptable authentication mechanism found');
+ }
+ })->then(function ($method) use ($reader, $stream) {
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'command' => 'C',
+ 'null' => 'C',
+ 'type' => 'C'
+ ));
+ })->then(function ($data) use ($reader) {
+ if ($data['version'] !== 0x05) {
+ throw new UnexpectedValueException('Invalid SOCKS version');
+ }
+ if ($data['command'] !== 0x01) {
+ throw new UnexpectedValueException('Only CONNECT requests supported', Server::ERROR_COMMAND_UNSUPPORTED);
+ }
+// if ($data['null'] !== 0x00) {
+// throw new UnexpectedValueException('Reserved byte has to be NULL');
+// }
+ if ($data['type'] === 0x03) {
+ // target hostname string
+ return $reader->readByte()->then(function ($len) use ($reader) {
+ return $reader->readLength($len);
+ });
+ } else if ($data['type'] === 0x01) {
+ // target IPv4
+ return $reader->readLength(4)->then(function ($addr) {
+ return inet_ntop($addr);
+ });
+ } else if ($data['type'] === 0x04) {
+ // target IPv6
+ return $reader->readLength(16)->then(function ($addr) {
+ return inet_ntop($addr);
+ });
+ } else {
+ throw new UnexpectedValueException('Invalid address type', Server::ERROR_ADDRESS_UNSUPPORTED);
+ }
+ })->then(function ($host) use ($reader, &$remote) {
+ return $reader->readBinary(array('port'=>'n'))->then(function ($data) use ($host, &$remote) {
+ return array($host, $data['port'], $remote);
+ });
+ })->then(function ($target) use ($that, $stream) {
+ return $that->connectTarget($stream, $target);
+ }, function($error) use ($stream) {
+ throw new UnexpectedValueException('SOCKS5 protocol error', $error->getCode(), $error);
+ })->then(function (ConnectionInterface $remote) use ($stream) {
+ $stream->write(pack('C4Nn', 0x05, 0x00, 0x00, 0x01, 0, 0));
+
+ return $remote;
+ }, function(Exception $error) use ($stream){
+ $stream->write(pack('C4Nn', 0x05, $error->getCode() === 0 ? Server::ERROR_GENERAL : $error->getCode(), 0x00, 0x01, 0, 0));
+
+ throw $error;
+ });
+ }
+
+ public function connectTarget(ConnectionInterface $stream, array $target)
+ {
+ $uri = $target[0];
+ if (strpos($uri, ':') !== false) {
+ $uri = '[' . $uri . ']';
+ }
+ $uri .= ':' . $target[1];
+
+ // validate URI so a string hostname can not pass excessive URI parts
+ $parts = parse_url('tcp://' . $uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || count($parts) !== 3) {
+ return Promise\reject(new InvalidArgumentException('Invalid target URI given'));
+ }
+
+ if (isset($target[2])) {
+ $uri .= '?source=' . rawurlencode($target[2]);
+ }
+
+ $stream->emit('target', $target);
+ $that = $this;
+ $connecting = $this->connector->connect($uri);
+
+ $stream->on('close', function () use ($connecting) {
+ $connecting->cancel();
+ });
+
+ return $connecting->then(function (ConnectionInterface $remote) use ($stream, $that) {
+ $stream->pipe($remote, array('end'=>false));
+ $remote->pipe($stream, array('end'=>false));
+
+ // remote end closes connection => stop reading from local end, try to flush buffer to local and disconnect local
+ $remote->on('end', function() use ($stream, $that) {
+ $stream->emit('shutdown', array('remote', null));
+ $that->endConnection($stream);
+ });
+
+ // local end closes connection => stop reading from remote end, try to flush buffer to remote and disconnect remote
+ $stream->on('end', function() use ($remote, $that) {
+ $that->endConnection($remote);
+ });
+
+ // set bigger buffer size of 100k to improve performance
+ $stream->bufferSize = $remote->bufferSize = 100 * 1024 * 1024;
+
+ return $remote;
+ }, function(Exception $error) {
+ // default to general/unknown error
+ $code = Server::ERROR_GENERAL;
+
+ // map common socket error conditions to limited list of SOCKS error codes
+ if ((defined('SOCKET_EACCES') && $error->getCode() === SOCKET_EACCES) || $error->getCode() === 13) {
+ $code = Server::ERROR_NOT_ALLOWED_BY_RULESET;
+ } elseif ((defined('SOCKET_EHOSTUNREACH') && $error->getCode() === SOCKET_EHOSTUNREACH) || $error->getCode() === 113) {
+ $code = Server::ERROR_HOST_UNREACHABLE;
+ } elseif ((defined('SOCKET_ENETUNREACH') && $error->getCode() === SOCKET_ENETUNREACH) || $error->getCode() === 101) {
+ $code = Server::ERROR_NETWORK_UNREACHABLE;
+ } elseif ((defined('SOCKET_ECONNREFUSED') && $error->getCode() === SOCKET_ECONNREFUSED) || $error->getCode() === 111 || $error->getMessage() === 'Connection refused') {
+ // Socket component does not currently assign an error code for this, so we have to resort to checking the exception message
+ $code = Server::ERROR_CONNECTION_REFUSED;
+ } elseif ((defined('SOCKET_ETIMEDOUT') && $error->getCode() === SOCKET_ETIMEDOUT) || $error->getCode() === 110 || $error instanceof TimeoutException) {
+ // Socket component does not currently assign an error code for this, but we can rely on the TimeoutException
+ $code = Server::ERROR_TTL;
+ }
+
+ throw new UnexpectedValueException('Unable to connect to remote target', $code, $error);
+ });
+ }
+}
diff --git a/instafeed/vendor/clue/socks-react/src/StreamReader.php b/instafeed/vendor/clue/socks-react/src/StreamReader.php
new file mode 100755
index 0000000..6dbf51c
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/src/StreamReader.php
@@ -0,0 +1,149 @@
+buffer .= $data;
+
+ do {
+ $current = reset($this->queue);
+
+ if ($current === false) {
+ break;
+ }
+
+ /* @var $current Closure */
+
+ $ret = $current($this->buffer);
+
+ if ($ret === self::RET_INCOMPLETE) {
+ // current is incomplete, so wait for further data to arrive
+ break;
+ } else {
+ // current is done, remove from list and continue with next
+ array_shift($this->queue);
+ }
+ } while (true);
+ }
+
+ public function readBinary($structure)
+ {
+ $length = 0;
+ $unpack = '';
+ foreach ($structure as $name=>$format) {
+ if ($length !== 0) {
+ $unpack .= '/';
+ }
+ $unpack .= $format . $name;
+
+ if ($format === 'C') {
+ ++$length;
+ } else if ($format === 'n') {
+ $length += 2;
+ } else if ($format === 'N') {
+ $length += 4;
+ } else {
+ throw new InvalidArgumentException('Invalid format given');
+ }
+ }
+
+ return $this->readLength($length)->then(function ($response) use ($unpack) {
+ return unpack($unpack, $response);
+ });
+ }
+
+ public function readLength($bytes)
+ {
+ $deferred = new Deferred();
+
+ $this->readBufferCallback(function (&$buffer) use ($bytes, $deferred) {
+ if (strlen($buffer) >= $bytes) {
+ $deferred->resolve((string)substr($buffer, 0, $bytes));
+ $buffer = (string)substr($buffer, $bytes);
+
+ return StreamReader::RET_DONE;
+ }
+ });
+
+ return $deferred->promise();
+ }
+
+ public function readByte()
+ {
+ return $this->readBinary(array(
+ 'byte' => 'C'
+ ))->then(function ($data) {
+ return $data['byte'];
+ });
+ }
+
+ public function readByteAssert($expect)
+ {
+ return $this->readByte()->then(function ($byte) use ($expect) {
+ if ($byte !== $expect) {
+ throw new UnexpectedValueException('Unexpected byte encountered');
+ }
+ return $byte;
+ });
+ }
+
+ public function readStringNull()
+ {
+ $deferred = new Deferred();
+ $string = '';
+
+ $that = $this;
+ $readOne = function () use (&$readOne, $that, $deferred, &$string) {
+ $that->readByte()->then(function ($byte) use ($deferred, &$string, $readOne) {
+ if ($byte === 0x00) {
+ $deferred->resolve($string);
+ } else {
+ $string .= chr($byte);
+ $readOne();
+ }
+ });
+ };
+ $readOne();
+
+ return $deferred->promise();
+ }
+
+ public function readBufferCallback(/* callable */ $callable)
+ {
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException('Given function must be callable');
+ }
+
+ if ($this->queue) {
+ $this->queue []= $callable;
+ } else {
+ $this->queue = array($callable);
+
+ if ($this->buffer !== '') {
+ // this is the first element in the queue and the buffer is filled => trigger write procedure
+ $this->write('');
+ }
+ }
+ }
+
+ public function getBuffer()
+ {
+ return $this->buffer;
+ }
+}
diff --git a/instafeed/vendor/clue/socks-react/tests/ClientTest.php b/instafeed/vendor/clue/socks-react/tests/ClientTest.php
new file mode 100755
index 0000000..e304901
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/tests/ClientTest.php
@@ -0,0 +1,403 @@
+loop = React\EventLoop\Factory::create();
+ $this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->client = new Client('127.0.0.1:1080', $this->connector);
+ }
+
+ public function testCtorAcceptsUriWithHostAndPort()
+ {
+ $client = new Client('127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithScheme()
+ {
+ $client = new Client('socks://127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithHostOnlyAssumesDefaultPort()
+ {
+ $client = new Client('127.0.0.1', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSecureScheme()
+ {
+ $client = new Client('sockss://127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSecureVersionScheme()
+ {
+ $client = new Client('socks5s://127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSocksUnixScheme()
+ {
+ $client = new Client('socks+unix:///tmp/socks.socket', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSocks5UnixScheme()
+ {
+ $client = new Client('socks5+unix:///tmp/socks.socket', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testCtorThrowsForInvalidUri()
+ {
+ new Client('////', $this->connector);
+ }
+
+ public function testValidAuthFromUri()
+ {
+ $this->client = new Client('username:password@127.0.0.1', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidAuthInformation()
+ {
+ new Client(str_repeat('a', 256) . ':test@127.0.0.1', $this->connector);
+ }
+
+ public function testValidAuthAndVersionFromUri()
+ {
+ $this->client = new Client('socks5://username:password@127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidCanNotSetAuthenticationForSocks4Uri()
+ {
+ $this->client = new Client('socks4://username:password@127.0.0.1:9050', $this->connector);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidProtocolVersion()
+ {
+ $this->client = new Client('socks3://127.0.0.1:9050', $this->connector);
+ }
+
+ public function testCreateWillConnectToProxy()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=localhost')->willReturn($promise);
+
+ $promise = $this->client->connect('localhost:80');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testCreateWillConnectToProxyWithFullUri()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080/?hostname=test#fragment')->willReturn($promise);
+
+ $promise = $this->client->connect('localhost:80/?hostname=test#fragment');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testCreateWithInvalidHostDoesNotConnect()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $promise = $this->client->connect(str_repeat('a', '256') . ':80');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testCreateWithInvalidPortDoesNotConnect()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $promise = $this->client->connect('some-random-site:some-random-port');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectorRejectsWillRejectConnection()
+ {
+ $promise = \React\Promise\reject(new RuntimeException());
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
+ }
+
+ public function testCancelConnectionDuringConnectionWillCancelConnection()
+ {
+ $promise = new Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+ $promise->cancel();
+
+ $this->expectPromiseReject($promise);
+ }
+
+ public function testCancelConnectionDuringSessionWillCloseStream()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = new Promise(function ($resolve) use ($stream) { $resolve($stream); });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNABORTED));
+ }
+
+ public function testEmitConnectionCloseDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('close');
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNRESET));
+ }
+
+ public function testEmitConnectionErrorDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('error', array(new RuntimeException()));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EIO));
+ }
+
+ public function testEmitInvalidSocks4DataDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testEmitInvalidSocks5DataDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testEmitSocks5DataErrorDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x01\x00\x00"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
+ }
+
+ public function testEmitSocks5DataInvalidAddressTypeWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x00"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testEmitSocks5DataIpv6AddressWillResolveConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->never())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=%3A%3A1')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('[::1]:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x04" . inet_pton('::1') . "\x00\x50"));
+
+ $promise->then($this->expectCallableOnce());
+ }
+
+ public function testEmitSocks5DataHostnameAddressWillResolveConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->never())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x03\x0Agoogle.com\x00\x50"));
+
+ $promise->then($this->expectCallableOnce());
+ }
+
+ public function provideConnectionErrors()
+ {
+ return array(
+ array(
+ Server::ERROR_GENERAL,
+ SOCKET_ECONNREFUSED
+ ),
+ array(
+ Server::ERROR_NOT_ALLOWED_BY_RULESET,
+ SOCKET_EACCES
+ ),
+ array(
+ Server::ERROR_NETWORK_UNREACHABLE,
+ SOCKET_ENETUNREACH
+ ),
+ array(
+ Server::ERROR_HOST_UNREACHABLE,
+ SOCKET_EHOSTUNREACH
+ ),
+ array(
+ Server::ERROR_CONNECTION_REFUSED,
+ SOCKET_ECONNREFUSED
+ ),
+ array(
+ Server::ERROR_TTL,
+ SOCKET_ETIMEDOUT
+ ),
+ array(
+ Server::ERROR_COMMAND_UNSUPPORTED,
+ SOCKET_EPROTO
+ ),
+ array(
+ Server::ERROR_ADDRESS_UNSUPPORTED,
+ SOCKET_EPROTO
+ ),
+ array(
+ 200,
+ SOCKET_ECONNREFUSED
+ )
+ );
+ }
+
+ /**
+ * @dataProvider provideConnectionErrors
+ * @param int $error
+ * @param int $expectedCode
+ */
+ public function testEmitSocks5DataErrorMapsToExceptionCode($error, $expectedCode)
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05" . chr($error) . "\x00\x00"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode($expectedCode));
+ }
+}
diff --git a/instafeed/vendor/clue/socks-react/tests/FunctionalTest.php b/instafeed/vendor/clue/socks-react/tests/FunctionalTest.php
new file mode 100755
index 0000000..2f1190e
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/tests/FunctionalTest.php
@@ -0,0 +1,437 @@
+loop = React\EventLoop\Factory::create();
+
+ $socket = new React\Socket\Server(0, $this->loop);
+ $address = $socket->getAddress();
+ if (strpos($address, '://') === false) {
+ $address = 'tcp://' . $address;
+ }
+ $this->port = parse_url($address, PHP_URL_PORT);
+ $this->assertNotEquals(0, $this->port);
+
+ $this->server = new Server($this->loop, $socket);
+ $this->connector = new TcpConnector($this->loop);
+ $this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
+ }
+
+ /** @group internet */
+ public function testConnection()
+ {
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionInvalid()
+ {
+ $this->assertRejectPromise($this->client->connect('www.google.com.invalid:80'));
+ }
+
+ public function testConnectionWithIpViaSocks4()
+ {
+ $this->server->setProtocolVersion('4');
+
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('127.0.0.1:' . $this->port));
+ }
+
+ /** @group internet */
+ public function testConnectionWithHostnameViaSocks4Fails()
+ {
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionWithInvalidPortFails()
+ {
+ $this->assertRejectPromise($this->client->connect('www.google.com:100000'));
+ }
+
+ public function testConnectionWithIpv6ViaSocks4Fails()
+ {
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('[::1]:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocks4a()
+ {
+ $this->server->setProtocolVersion('4a');
+ $this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocks5()
+ {
+ $this->server->setProtocolVersion(5);
+ $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocksOverTls()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ )));
+ $this->server = new Server($this->loop, $socket);
+
+ $this->connector = new Connector($this->loop, array('tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ )));
+ $this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /**
+ * @group internet
+ * @requires PHP 5.6
+ */
+ public function testConnectionSocksOverTlsUsesPeerNameFromSocksUri()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ )));
+ $this->server = new Server($this->loop, $socket);
+
+ $this->connector = new Connector($this->loop, array('tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => true
+ )));
+ $this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocksOverUnix()
+ {
+ if (!in_array('unix', stream_get_transports())) {
+ $this->markTestSkipped('System does not support unix:// scheme');
+ }
+
+ $path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
+ $socket = new UnixServer($path, $this->loop);
+ $this->server = new Server($this->loop, $socket);
+
+ $this->connector = new Connector($this->loop);
+ $this->client = new Client('socks+unix://' . $path, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+
+ unlink($path);
+ }
+
+ /** @group internet */
+ public function testConnectionSocks5OverUnix()
+ {
+ if (!in_array('unix', stream_get_transports())) {
+ $this->markTestSkipped('System does not support unix:// scheme');
+ }
+
+ $path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
+ $socket = new UnixServer($path, $this->loop);
+ $this->server = new Server($this->loop, $socket);
+ $this->server->setProtocolVersion(5);
+
+ $this->connector = new Connector($this->loop);
+ $this->client = new Client('socks5+unix://' . $path, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+
+ unlink($path);
+ }
+
+ /** @group internet */
+ public function testConnectionSocksWithAuthenticationOverUnix()
+ {
+ if (!in_array('unix', stream_get_transports())) {
+ $this->markTestSkipped('System does not support unix:// scheme');
+ }
+
+ $path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
+ $socket = new UnixServer($path, $this->loop);
+ $this->server = new Server($this->loop, $socket);
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->connector = new Connector($this->loop);
+ $this->client = new Client('socks+unix://name:pass@' . $path, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+
+ unlink($path);
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationFromUri()
+ {
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationCallback()
+ {
+ $called = 0;
+ $that = $this;
+ $this->server->setAuth(function ($name, $pass, $remote) use ($that, &$called) {
+ ++$called;
+ $that->assertEquals('name', $name);
+ $that->assertEquals('pass', $pass);
+ $that->assertStringStartsWith('socks5://name:pass@127.0.0.1:', $remote);
+
+ return true;
+ });
+
+ $this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ $this->assertEquals(1, $called);
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationCallbackWillNotBeInvokedIfClientsSendsNoAuth()
+ {
+ $called = 0;
+ $this->server->setAuth(function () use (&$called) {
+ ++$called;
+
+ return true;
+ });
+
+ $this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'));
+ $this->assertEquals(0, $called);
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationFromUriEncoded()
+ {
+ $this->server->setAuthArray(array('name' => 'p@ss:w0rd'));
+
+ $this->client = new Client(rawurlencode('name') . ':' . rawurlencode('p@ss:w0rd') . '@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationFromUriWithOnlyUserAndNoPassword()
+ {
+ $this->server->setAuthArray(array('empty' => ''));
+
+ $this->client = new Client('empty@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationEmptyPassword()
+ {
+ $this->server->setAuthArray(array('user' => ''));
+ $this->client = new Client('user@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationUnused()
+ {
+ $this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ public function testConnectionInvalidProtocolDoesNotMatchSocks5()
+ {
+ $this->server->setProtocolVersion(5);
+ $this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
+ }
+
+ public function testConnectionInvalidProtocolDoesNotMatchSocks4()
+ {
+ $this->server->setProtocolVersion(4);
+ $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
+ }
+
+ public function testConnectionInvalidNoAuthentication()
+ {
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
+ }
+
+ public function testConnectionInvalidAuthenticationMismatch()
+ {
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
+ }
+
+ /** @group internet */
+ public function testConnectorOkay()
+ {
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectorInvalidDomain()
+ {
+ $this->assertRejectPromise($this->client->connect('www.google.commm:80'));
+ }
+
+ /** @group internet */
+ public function testConnectorCancelConnection()
+ {
+ $promise = $this->client->connect('www.google.com:80');
+ $promise->cancel();
+
+ $this->assertRejectPromise($promise);
+ }
+
+ /** @group internet */
+ public function testConnectorInvalidUnboundPortTimeout()
+ {
+ // time out the connection attempt in 0.1s (as expected)
+ $tcp = new TimeoutConnector($this->client, 0.1, $this->loop);
+
+ $this->assertRejectPromise($tcp->connect('www.google.com:8080'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorOkay()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop);
+
+ $this->assertResolveStream($ssl->connect('www.google.com:443'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorToBadSslWithVerifyFails()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => true));
+
+ $this->assertRejectPromise($ssl->connect('self-signed.badssl.com:443'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorToBadSslWithoutVerifyWorks()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => false));
+
+ $this->assertResolveStream($ssl->connect('self-signed.badssl.com:443'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorInvalidPlaintextIsNotSsl()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop);
+
+ $this->assertRejectPromise($ssl->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorInvalidUnboundPortTimeout()
+ {
+ $ssl = new SecureConnector($this->client, $this->loop);
+
+ // time out the connection attempt in 0.1s (as expected)
+ $ssl = new TimeoutConnector($ssl, 0.1, $this->loop);
+
+ $this->assertRejectPromise($ssl->connect('www.google.com:8080'));
+ }
+
+ private function assertResolveStream($promise)
+ {
+ $this->expectPromiseResolve($promise);
+
+ $promise->then(function ($stream) {
+ $stream->close();
+ });
+
+ Block\await($promise, $this->loop, 2.0);
+ }
+
+ private function assertRejectPromise($promise, $message = null, $code = null)
+ {
+ $this->expectPromiseReject($promise);
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Exception');
+ if ($message !== null) {
+ $this->expectExceptionMessage($message);
+ }
+ if ($code !== null) {
+ $this->expectExceptionCode($code);
+ }
+ } else {
+ $this->setExpectedException('Exception', $message, $code);
+ }
+
+ Block\await($promise, $this->loop, 2.0);
+ }
+}
diff --git a/instafeed/vendor/clue/socks-react/tests/ServerTest.php b/instafeed/vendor/clue/socks-react/tests/ServerTest.php
new file mode 100755
index 0000000..5c73845
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/tests/ServerTest.php
@@ -0,0 +1,428 @@
+getMockBuilder('React\Socket\ServerInterface')
+ ->getMock();
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')
+ ->getMock();
+
+ $this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')
+ ->getMock();
+
+ $this->server = new Server($loop, $socket, $this->connector);
+ }
+
+ public function testSetProtocolVersion()
+ {
+ $this->server->setProtocolVersion(4);
+ $this->server->setProtocolVersion('4a');
+ $this->server->setProtocolVersion(5);
+ $this->server->setProtocolVersion(null);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetInvalidProtocolVersion()
+ {
+ $this->server->setProtocolVersion(6);
+ }
+
+ public function testSetAuthArray()
+ {
+ $this->server->setAuthArray(array());
+
+ $this->server->setAuthArray(array(
+ 'name1' => 'password1',
+ 'name2' => 'password2'
+ ));
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetAuthInvalid()
+ {
+ $this->server->setAuth(true);
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testUnableToSetAuthIfProtocolDoesNotSupportAuth()
+ {
+ $this->server->setProtocolVersion(4);
+
+ $this->server->setAuthArray(array());
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testUnableToSetProtocolWhichDoesNotSupportAuth()
+ {
+ $this->server->setAuthArray(array());
+
+ // this is okay
+ $this->server->setProtocolVersion(5);
+
+ $this->server->setProtocolVersion(4);
+ }
+
+ public function testConnectWillCreateConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectWillCreateConnectionWithSourceUri()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80, 'socks5://10.20.30.40:5060'));
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectWillRejectIfConnectionFails()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function ($_, $reject) { $reject(new \RuntimeException()); });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectWillCancelConnectionIfStreamCloses()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close'))->getMock();
+
+ $promise = new Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $stream->emit('close');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectWillAbortIfPromiseIsCanceled()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function provideConnectionErrors()
+ {
+ return array(
+ array(
+ new RuntimeException('', SOCKET_EACCES),
+ Server::ERROR_NOT_ALLOWED_BY_RULESET
+ ),
+ array(
+ new RuntimeException('', SOCKET_ENETUNREACH),
+ Server::ERROR_NETWORK_UNREACHABLE
+ ),
+ array(
+ new RuntimeException('', SOCKET_EHOSTUNREACH),
+ Server::ERROR_HOST_UNREACHABLE,
+ ),
+ array(
+ new RuntimeException('', SOCKET_ECONNREFUSED),
+ Server::ERROR_CONNECTION_REFUSED
+ ),
+ array(
+ new RuntimeException('Connection refused'),
+ Server::ERROR_CONNECTION_REFUSED
+ ),
+ array(
+ new RuntimeException('', SOCKET_ETIMEDOUT),
+ Server::ERROR_TTL
+ ),
+ array(
+ new TimeoutException(1.0),
+ Server::ERROR_TTL
+ ),
+ array(
+ new RuntimeException(),
+ Server::ERROR_GENERAL
+ )
+ );
+ }
+
+ /**
+ * @dataProvider provideConnectionErrors
+ * @param Exception $error
+ * @param int $expectedCode
+ */
+ public function testConnectWillReturnMappedSocks5ErrorCodeFromConnector($error, $expectedCode)
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = \React\Promise\reject($error);
+
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $code = null;
+ $promise->then(null, function ($error) use (&$code) {
+ $code = $error->getCode();
+ });
+
+ $this->assertEquals($expectedCode, $code);
+ }
+
+ public function testHandleSocksConnectionWillEndOnInvalidData()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+ $connection->expects($this->once())->method('pause');
+ $connection->expects($this->once())->method('end');
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array('asdasdasdasdasd'));
+ }
+
+ public function testHandleSocks4ConnectionWithIpv4WillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithHostnameWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithHostnameAndSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithSecureTlsSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithInvalidHostnameWillNotEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "tls://example.com:80?" . "\x00"));
+ }
+
+ public function testHandleSocks5ConnectionWithIpv4WillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithIpv4AndSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithSecureTlsIpv4AndSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithIpv6WillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('[::1]:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x04" . inet_pton('::1') . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithHostnameWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithConnectorRefusedWillReturnReturnRefusedError()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = \React\Promise\reject(new RuntimeException('Connection refused'));
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x05" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
+ }
+
+ public function testHandleSocks5UdpCommandWillNotEstablishOutgoingConnectionAndReturnCommandError()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $this->server->onConnection($connection);
+
+ $connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x07" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x03\x00\x03\x0B" . "example.com" . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithInvalidHostnameWillNotEstablishOutgoingConnectionAndReturnGeneralError()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $this->server->onConnection($connection);
+
+ $connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x01" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x15" . "tls://example.com:80?" . "\x00\x50"));
+ }
+
+ public function testHandleSocksConnectionWillCancelOutputConnectionIfIncomingCloses()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
+ $connection->emit('close');
+ }
+
+ public function testUnsetAuth()
+ {
+ $this->server->unsetAuth();
+ $this->server->unsetAuth();
+
+ $this->assertTrue(true);
+ }
+}
diff --git a/instafeed/vendor/clue/socks-react/tests/StreamReaderTest.php b/instafeed/vendor/clue/socks-react/tests/StreamReaderTest.php
new file mode 100755
index 0000000..a2a0d95
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/tests/StreamReaderTest.php
@@ -0,0 +1,82 @@
+reader = new StreamReader();
+ }
+
+ public function testReadByteAssertCorrect()
+ {
+ $this->reader->readByteAssert(0x01)->then($this->expectCallableOnce(0x01));
+
+ $this->reader->write("\x01");
+ }
+
+ public function testReadByteAssertInvalid()
+ {
+ $this->reader->readByteAssert(0x02)->then(null, $this->expectCallableOnce());
+
+ $this->reader->write("\x03");
+ }
+
+ public function testReadStringNull()
+ {
+ $this->reader->readStringNull()->then($this->expectCallableOnce('hello'));
+
+ $this->reader->write("hello\x00");
+ }
+
+ public function testReadStringLength()
+ {
+ $this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
+
+ $this->reader->write('he');
+ $this->reader->write('ll');
+ $this->reader->write('o ');
+
+ $this->assertEquals(' ', $this->reader->getBuffer());
+ }
+
+ public function testReadBuffered()
+ {
+ $this->reader->write('hello');
+
+ $this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
+
+ $this->assertEquals('', $this->reader->getBuffer());
+ }
+
+ public function testSequence()
+ {
+ $this->reader->readByte()->then($this->expectCallableOnce(ord('h')));
+ $this->reader->readByteAssert(ord('e'))->then($this->expectCallableOnce(ord('e')));
+ $this->reader->readLength(4)->then($this->expectCallableOnce('llo '));
+ $this->reader->readBinary(array('w'=>'C', 'o' => 'C'))->then($this->expectCallableOnce(array('w' => ord('w'), 'o' => ord('o'))));
+
+ $this->reader->write('hello world');
+
+ $this->assertEquals('rld', $this->reader->getBuffer());
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidStructure()
+ {
+ $this->reader->readBinary(array('invalid' => 'y'));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidCallback()
+ {
+ $this->reader->readBufferCallback(array());
+ }
+}
diff --git a/instafeed/vendor/clue/socks-react/tests/bootstrap.php b/instafeed/vendor/clue/socks-react/tests/bootstrap.php
new file mode 100755
index 0000000..029c6b9
--- /dev/null
+++ b/instafeed/vendor/clue/socks-react/tests/bootstrap.php
@@ -0,0 +1,103 @@
+createCallableMock();
+
+
+ if (func_num_args() > 0) {
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->equalTo(func_get_arg(0)));
+ } else {
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+ }
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWithExceptionCode($code)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->callback(function ($e) use ($code) {
+ return $e->getCode() === $code;
+ }));
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceParameter($type)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf($type));
+
+ return $mock;
+ }
+
+ /**
+ * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
+ */
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('CallableStub')->getMock();
+ }
+
+ protected function expectPromiseResolve($promise)
+ {
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $that = $this;
+ $promise->then(null, function($error) use ($that) {
+ $that->assertNull($error);
+ $that->fail('promise rejected');
+ });
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ return $promise;
+ }
+
+ protected function expectPromiseReject($promise)
+ {
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $that = $this;
+ $promise->then(function($value) use ($that) {
+ $that->assertNull($value);
+ $that->fail('promise resolved');
+ });
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ return $promise;
+ }
+}
+
+class CallableStub
+{
+ public function __invoke()
+ {
+ }
+}
diff --git a/instafeed/vendor/composer/ClassLoader.php b/instafeed/vendor/composer/ClassLoader.php
new file mode 100755
index 0000000..fce8549
--- /dev/null
+++ b/instafeed/vendor/composer/ClassLoader.php
@@ -0,0 +1,445 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+ private $apcuPrefix;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/instafeed/vendor/composer/LICENSE b/instafeed/vendor/composer/LICENSE
new file mode 100755
index 0000000..4b615a3
--- /dev/null
+++ b/instafeed/vendor/composer/LICENSE
@@ -0,0 +1,56 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: Composer
+Upstream-Contact: Jordi Boggiano
+Source: https://github.com/composer/composer
+
+Files: *
+Copyright: 2016, Nils Adermann
+ 2016, Jordi Boggiano
+License: Expat
+
+Files: src/Composer/Util/TlsHelper.php
+Copyright: 2016, Nils Adermann
+ 2016, Jordi Boggiano
+ 2013, Evan Coury
+License: Expat and BSD-2-Clause
+
+License: BSD-2-Clause
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+ .
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ .
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is furnished
+ to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
diff --git a/instafeed/vendor/composer/autoload_classmap.php b/instafeed/vendor/composer/autoload_classmap.php
new file mode 100755
index 0000000..7a91153
--- /dev/null
+++ b/instafeed/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+ $vendorDir . '/react/promise/src/functions_include.php',
+ '972fda704d680a3a53c68e34e193cb22' => $vendorDir . '/react/promise-timer/src/functions_include.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
+ 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
+ 'ebf8799635f67b5d7248946fe2154f4a' => $vendorDir . '/ringcentral/psr7/src/functions_include.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
+);
diff --git a/instafeed/vendor/composer/autoload_namespaces.php b/instafeed/vendor/composer/autoload_namespaces.php
new file mode 100755
index 0000000..02066fb
--- /dev/null
+++ b/instafeed/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,10 @@
+ array($vendorDir . '/evenement/evenement/src'),
+);
diff --git a/instafeed/vendor/composer/autoload_psr4.php b/instafeed/vendor/composer/autoload_psr4.php
new file mode 100755
index 0000000..5d4f0c7
--- /dev/null
+++ b/instafeed/vendor/composer/autoload_psr4.php
@@ -0,0 +1,32 @@
+ array($vendorDir . '/winbox/args/src'),
+ 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
+ 'RingCentral\\Psr7\\' => array($vendorDir . '/ringcentral/psr7/src'),
+ 'React\\Stream\\' => array($vendorDir . '/react/stream/src'),
+ 'React\\Socket\\' => array($vendorDir . '/react/socket/src'),
+ 'React\\Promise\\Timer\\' => array($vendorDir . '/react/promise-timer/src'),
+ 'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
+ 'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
+ 'React\\Dns\\' => array($vendorDir . '/react/dns/src'),
+ 'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
+ 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
+ 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
+ 'LazyJsonMapper\\' => array($vendorDir . '/lazyjsonmapper/lazyjsonmapper/src'),
+ 'InstagramAPI\\' => array($vendorDir . '/mgp25/instagram-php/src'),
+ 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
+ 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
+ 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
+ 'GetOptionKit\\' => array($vendorDir . '/corneltek/getoptionkit/src'),
+ 'Fbns\\Client\\' => array($vendorDir . '/valga/fbns-react/src'),
+ 'Clue\\React\\Socks\\' => array($vendorDir . '/clue/socks-react/src'),
+ 'Clue\\React\\HttpProxy\\' => array($vendorDir . '/clue/http-proxy-react/src'),
+ 'BinSoul\\Net\\Mqtt\\Client\\React\\' => array($vendorDir . '/binsoul/net-mqtt-client-react/src'),
+ 'BinSoul\\Net\\Mqtt\\' => array($vendorDir . '/binsoul/net-mqtt/src'),
+);
diff --git a/instafeed/vendor/composer/autoload_real.php b/instafeed/vendor/composer/autoload_real.php
new file mode 100755
index 0000000..6a318f4
--- /dev/null
+++ b/instafeed/vendor/composer/autoload_real.php
@@ -0,0 +1,70 @@
+= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require_once __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInit331ae81537359437b02a96bc74f11b80::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ if ($useStaticLoader) {
+ $includeFiles = Composer\Autoload\ComposerStaticInit331ae81537359437b02a96bc74f11b80::$files;
+ } else {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ }
+ foreach ($includeFiles as $fileIdentifier => $file) {
+ composerRequire331ae81537359437b02a96bc74f11b80($fileIdentifier, $file);
+ }
+
+ return $loader;
+ }
+}
+
+function composerRequire331ae81537359437b02a96bc74f11b80($fileIdentifier, $file)
+{
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ require $file;
+
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+ }
+}
diff --git a/instafeed/vendor/composer/autoload_static.php b/instafeed/vendor/composer/autoload_static.php
new file mode 100755
index 0000000..5c67b20
--- /dev/null
+++ b/instafeed/vendor/composer/autoload_static.php
@@ -0,0 +1,189 @@
+ __DIR__ . '/..' . '/react/promise/src/functions_include.php',
+ '972fda704d680a3a53c68e34e193cb22' => __DIR__ . '/..' . '/react/promise-timer/src/functions_include.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
+ 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
+ 'ebf8799635f67b5d7248946fe2154f4a' => __DIR__ . '/..' . '/ringcentral/psr7/src/functions_include.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
+ );
+
+ public static $prefixLengthsPsr4 = array (
+ 'W' =>
+ array (
+ 'Winbox\\' => 7,
+ ),
+ 'S' =>
+ array (
+ 'Symfony\\Component\\Process\\' => 26,
+ ),
+ 'R' =>
+ array (
+ 'RingCentral\\Psr7\\' => 17,
+ 'React\\Stream\\' => 13,
+ 'React\\Socket\\' => 13,
+ 'React\\Promise\\Timer\\' => 20,
+ 'React\\Promise\\' => 14,
+ 'React\\EventLoop\\' => 16,
+ 'React\\Dns\\' => 10,
+ 'React\\Cache\\' => 12,
+ ),
+ 'P' =>
+ array (
+ 'Psr\\Log\\' => 8,
+ 'Psr\\Http\\Message\\' => 17,
+ ),
+ 'L' =>
+ array (
+ 'LazyJsonMapper\\' => 15,
+ ),
+ 'I' =>
+ array (
+ 'InstagramAPI\\' => 13,
+ ),
+ 'G' =>
+ array (
+ 'GuzzleHttp\\Psr7\\' => 16,
+ 'GuzzleHttp\\Promise\\' => 19,
+ 'GuzzleHttp\\' => 11,
+ 'GetOptionKit\\' => 13,
+ ),
+ 'F' =>
+ array (
+ 'Fbns\\Client\\' => 12,
+ ),
+ 'C' =>
+ array (
+ 'Clue\\React\\Socks\\' => 17,
+ 'Clue\\React\\HttpProxy\\' => 21,
+ ),
+ 'B' =>
+ array (
+ 'BinSoul\\Net\\Mqtt\\Client\\React\\' => 30,
+ 'BinSoul\\Net\\Mqtt\\' => 17,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Winbox\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/winbox/args/src',
+ ),
+ 'Symfony\\Component\\Process\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/process',
+ ),
+ 'RingCentral\\Psr7\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ringcentral/psr7/src',
+ ),
+ 'React\\Stream\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/stream/src',
+ ),
+ 'React\\Socket\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/socket/src',
+ ),
+ 'React\\Promise\\Timer\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/promise-timer/src',
+ ),
+ 'React\\Promise\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/promise/src',
+ ),
+ 'React\\EventLoop\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/event-loop/src',
+ ),
+ 'React\\Dns\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/dns/src',
+ ),
+ 'React\\Cache\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/cache/src',
+ ),
+ 'Psr\\Log\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
+ ),
+ 'Psr\\Http\\Message\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/http-message/src',
+ ),
+ 'LazyJsonMapper\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/lazyjsonmapper/lazyjsonmapper/src',
+ ),
+ 'InstagramAPI\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/mgp25/instagram-php/src',
+ ),
+ 'GuzzleHttp\\Psr7\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
+ ),
+ 'GuzzleHttp\\Promise\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
+ ),
+ 'GuzzleHttp\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
+ ),
+ 'GetOptionKit\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/corneltek/getoptionkit/src',
+ ),
+ 'Fbns\\Client\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/valga/fbns-react/src',
+ ),
+ 'Clue\\React\\Socks\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/clue/socks-react/src',
+ ),
+ 'Clue\\React\\HttpProxy\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/clue/http-proxy-react/src',
+ ),
+ 'BinSoul\\Net\\Mqtt\\Client\\React\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/binsoul/net-mqtt-client-react/src',
+ ),
+ 'BinSoul\\Net\\Mqtt\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/binsoul/net-mqtt/src',
+ ),
+ );
+
+ public static $prefixesPsr0 = array (
+ 'E' =>
+ array (
+ 'Evenement' =>
+ array (
+ 0 => __DIR__ . '/..' . '/evenement/evenement/src',
+ ),
+ ),
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit331ae81537359437b02a96bc74f11b80::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit331ae81537359437b02a96bc74f11b80::$prefixDirsPsr4;
+ $loader->prefixesPsr0 = ComposerStaticInit331ae81537359437b02a96bc74f11b80::$prefixesPsr0;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/instafeed/vendor/composer/installed.json b/instafeed/vendor/composer/installed.json
new file mode 100755
index 0000000..46699b3
--- /dev/null
+++ b/instafeed/vendor/composer/installed.json
@@ -0,0 +1,1328 @@
+[
+ {
+ "name": "binsoul/net-mqtt",
+ "version": "0.2.1",
+ "version_normalized": "0.2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/binsoul/net-mqtt.git",
+ "reference": "286b28e6014739b19e0e7ce0cd5871cdd0cef9b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/binsoul/net-mqtt/zipball/286b28e6014739b19e0e7ce0cd5871cdd0cef9b3",
+ "reference": "286b28e6014739b19e0e7ce0cd5871cdd0cef9b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~5.6|~7.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~1.0",
+ "phpunit/phpunit": "~4.0||~5.0"
+ },
+ "time": "2017-04-03T20:17:02+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "description": "MQTT protocol implementation",
+ "homepage": "https://github.com/binsoul/net-mqtt",
+ "keywords": [
+ "mqtt",
+ "net"
+ ]
+ },
+ {
+ "name": "binsoul/net-mqtt-client-react",
+ "version": "0.3.2",
+ "version_normalized": "0.3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/binsoul/net-mqtt-client-react.git",
+ "reference": "6a80fea50e927ebb8bb8a631ea7903c22742ded5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/binsoul/net-mqtt-client-react/zipball/6a80fea50e927ebb8bb8a631ea7903c22742ded5",
+ "reference": "6a80fea50e927ebb8bb8a631ea7903c22742ded5",
+ "shasum": ""
+ },
+ "require": {
+ "binsoul/net-mqtt": "~0.2",
+ "php": "~5.6|~7.0",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~1.0",
+ "phpunit/phpunit": "~4.0||~5.0"
+ },
+ "time": "2017-08-20T08:06:53+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\Client\\React\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "description": "Asynchronous MQTT client built on React",
+ "homepage": "https://github.com/binsoul/net-mqtt-client-react",
+ "keywords": [
+ "client",
+ "mqtt",
+ "net"
+ ]
+ },
+ {
+ "name": "clue/http-proxy-react",
+ "version": "v1.3.0",
+ "version_normalized": "1.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/clue/php-http-proxy-react.git",
+ "reference": "eeff725640ed53386a6adb05ffdbfc2837404fdf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/clue/php-http-proxy-react/zipball/eeff725640ed53386a6adb05ffdbfc2837404fdf",
+ "reference": "eeff725640ed53386a6adb05ffdbfc2837404fdf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/promise": " ^2.1 || ^1.2.1",
+ "react/socket": "^1.0 || ^0.8.4",
+ "ringcentral/psr7": "^1.2"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.1",
+ "phpunit/phpunit": "^5.0 || ^4.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
+ },
+ "time": "2018-02-13T16:31:32+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Clue\\React\\HttpProxy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "description": "Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
+ "homepage": "https://github.com/clue/php-http-proxy-react",
+ "keywords": [
+ "async",
+ "connect",
+ "http",
+ "proxy",
+ "reactphp"
+ ]
+ },
+ {
+ "name": "clue/socks-react",
+ "version": "v0.8.7",
+ "version_normalized": "0.8.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/clue/php-socks-react.git",
+ "reference": "0fcd6f2f506918ff003f1b995c6e78443f26e8ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/clue/php-socks-react/zipball/0fcd6f2f506918ff003f1b995c6e78443f26e8ea",
+ "reference": "0fcd6f2f506918ff003f1b995c6e78443f26e8ea",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "~3.0|~1.0|~2.0",
+ "php": ">=5.3",
+ "react/promise": "^2.1 || ^1.2",
+ "react/socket": "^1.0 || ^0.8.6"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.1",
+ "clue/connection-manager-extra": "^1.0 || ^0.7",
+ "phpunit/phpunit": "^6.0 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
+ },
+ "time": "2017-12-17T14:47:58+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Clue\\React\\Socks\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
+ "homepage": "https://github.com/clue/php-socks-react",
+ "keywords": [
+ "async",
+ "proxy",
+ "reactphp",
+ "socks client",
+ "socks protocol",
+ "socks server",
+ "tcp tunnel"
+ ]
+ },
+ {
+ "name": "corneltek/getoptionkit",
+ "version": "2.6.0",
+ "version_normalized": "2.6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/c9s/GetOptionKit.git",
+ "reference": "995607ddf4fc90ebdb4a7d58fe972d581ad8495f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/c9s/GetOptionKit/zipball/995607ddf4fc90ebdb4a7d58fe972d581ad8495f",
+ "reference": "995607ddf4fc90ebdb4a7d58fe972d581ad8495f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2017-06-30T14:54:48+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GetOptionKit\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Yo-An Lin",
+ "email": "yoanlin93@gmail.com"
+ }
+ ],
+ "description": "Powerful command-line option toolkit",
+ "homepage": "http://github.com/c9s/GetOptionKit"
+ },
+ {
+ "name": "evenement/evenement",
+ "version": "v3.0.1",
+ "version_normalized": "3.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/igorw/evenement.git",
+ "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
+ "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "time": "2017-07-23T21:35:13+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Evenement": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": [
+ "event-dispatcher",
+ "event-emitter"
+ ]
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "6.4.1",
+ "version_normalized": "6.4.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "0895c932405407fd3a7368b6910c09a24d26db11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11",
+ "reference": "0895c932405407fd3a7368b6910c09a24d26db11",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.6.1",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "time": "2019-10-23T15:58:00+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.3-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ]
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "v1.3.1",
+ "version_normalized": "1.3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "time": "2016-12-20T10:07:11+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ]
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.6.1",
+ "version_normalized": "1.6.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "ext-zlib": "*",
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+ },
+ "suggest": {
+ "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "time": "2019-07-01T23:21:34+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ]
+ },
+ {
+ "name": "lazyjsonmapper/lazyjsonmapper",
+ "version": "v1.6.3",
+ "version_normalized": "1.6.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/lazyjsonmapper/lazyjsonmapper.git",
+ "reference": "51e093b50f4de15d2d64548b3ca743713eed6ee9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/lazyjsonmapper/lazyjsonmapper/zipball/51e093b50f4de15d2d64548b3ca743713eed6ee9",
+ "reference": "51e093b50f4de15d2d64548b3ca743713eed6ee9",
+ "shasum": ""
+ },
+ "require": {
+ "corneltek/getoptionkit": "2.*",
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.7.1",
+ "phpunit/phpunit": "6.*"
+ },
+ "time": "2018-05-02T16:57:09+00:00",
+ "bin": [
+ "bin/lazydoctor"
+ ],
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "LazyJsonMapper\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "SteveJobzniak",
+ "role": "Developer",
+ "homepage": "https://github.com/SteveJobzniak"
+ }
+ ],
+ "description": "Advanced, intelligent & automatic object-oriented JSON containers for PHP.",
+ "homepage": "https://github.com/SteveJobzniak/LazyJsonMapper",
+ "keywords": [
+ "development",
+ "json"
+ ]
+ },
+ {
+ "name": "mgp25/instagram-php",
+ "version": "v7.0.1",
+ "version_normalized": "7.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mgp25/Instagram-API.git",
+ "reference": "53421f90b9ef7743f1c6221c4963f2b9f7a592e8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mgp25/Instagram-API/zipball/53421f90b9ef7743f1c6221c4963f2b9f7a592e8",
+ "reference": "53421f90b9ef7743f1c6221c4963f2b9f7a592e8",
+ "shasum": ""
+ },
+ "require": {
+ "binsoul/net-mqtt-client-react": "^0.3.2",
+ "clue/http-proxy-react": "^1.1.0",
+ "clue/socks-react": "^0.8.2",
+ "ext-bcmath": "*",
+ "ext-curl": "*",
+ "ext-exif": "*",
+ "ext-gd": "*",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "guzzlehttp/guzzle": "^6.2",
+ "lazyjsonmapper/lazyjsonmapper": "^1.6.1",
+ "php": ">=5.6",
+ "psr/log": "^1.0",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "^2.5",
+ "react/socket": "^0.8",
+ "symfony/process": "^3.4|^4.0",
+ "valga/fbns-react": "^0.1.8",
+ "winbox/args": "1.0.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.11.0",
+ "monolog/monolog": "^1.23",
+ "phpunit/phpunit": "^5.7 || ^6.2",
+ "react/http": "^0.7.2"
+ },
+ "suggest": {
+ "ext-event": "Installing PHP's native Event extension enables faster Realtime class event handling."
+ },
+ "time": "2019-09-17T00:56:42+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "InstagramAPI\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "RPL-1.5",
+ "proprietary"
+ ],
+ "authors": [
+ {
+ "name": "mgp25",
+ "email": "me@mgp25.com",
+ "role": "Founder"
+ },
+ {
+ "name": "SteveJobzniak",
+ "homepage": "https://github.com/SteveJobzniak",
+ "role": "Developer"
+ }
+ ],
+ "description": "Instagram's private API for PHP",
+ "keywords": [
+ "api",
+ "instagram",
+ "php",
+ "private"
+ ]
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2016-08-06T14:39:51+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ]
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.1",
+ "version_normalized": "1.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
+ "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2019-10-25T08:06:51+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ]
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "version_normalized": "3.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "time": "2019-03-08T08:55:37+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders."
+ },
+ {
+ "name": "react/cache",
+ "version": "v1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/cache.git",
+ "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/cache/zipball/aa10d63a1b40a36a486bdf527f28bac607ee6466",
+ "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "~2.0|~1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-07-11T13:45:28+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Cache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": [
+ "cache",
+ "caching",
+ "promise",
+ "reactphp"
+ ]
+ },
+ {
+ "name": "react/dns",
+ "version": "v0.4.19",
+ "version_normalized": "0.4.19.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/dns.git",
+ "reference": "6852fb98e22d2e5bb35fe5aeeaa96551b120e7c9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/dns/zipball/6852fb98e22d2e5bb35fe5aeeaa96551b120e7c9",
+ "reference": "6852fb98e22d2e5bb35fe5aeeaa96551b120e7c9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.1 || ^1.2.1",
+ "react/promise-timer": "^1.2",
+ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-07-10T21:00:53+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Dns\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": [
+ "async",
+ "dns",
+ "dns-resolver",
+ "reactphp"
+ ]
+ },
+ {
+ "name": "react/event-loop",
+ "version": "v0.4.3",
+ "version_normalized": "0.4.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/event-loop.git",
+ "reference": "8bde03488ee897dc6bb3d91e4e17c353f9c5252f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/event-loop/zipball/8bde03488ee897dc6bb3d91e4e17c353f9c5252f",
+ "reference": "8bde03488ee897dc6bb3d91e4e17c353f9c5252f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "suggest": {
+ "ext-event": "~1.0",
+ "ext-libev": "*",
+ "ext-libevent": ">=0.1.0"
+ },
+ "time": "2017-04-27T10:56:23+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Event loop abstraction layer that libraries can use for evented I/O.",
+ "keywords": [
+ "asynchronous",
+ "event-loop"
+ ]
+ },
+ {
+ "name": "react/promise",
+ "version": "v2.7.1",
+ "version_normalized": "2.7.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise.git",
+ "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
+ "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "time": "2019-01-07T21:25:54+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com"
+ }
+ ],
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "keywords": [
+ "promise",
+ "promises"
+ ]
+ },
+ {
+ "name": "react/promise-timer",
+ "version": "v1.5.1",
+ "version_normalized": "1.5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise-timer.git",
+ "reference": "35fb910604fd86b00023fc5cda477c8074ad0abc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/35fb910604fd86b00023fc5cda477c8074ad0abc",
+ "reference": "35fb910604fd86b00023fc5cda477c8074ad0abc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.7.0 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-03-27T18:10:32+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\Timer\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
+ "homepage": "https://github.com/reactphp/promise-timer",
+ "keywords": [
+ "async",
+ "event-loop",
+ "promise",
+ "reactphp",
+ "timeout",
+ "timer"
+ ]
+ },
+ {
+ "name": "react/socket",
+ "version": "v0.8.12",
+ "version_normalized": "0.8.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/socket.git",
+ "reference": "7f7e6c56ccda7418a1a264892a625f38a5bdee0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/socket/zipball/7f7e6c56ccda7418a1a264892a625f38a5bdee0c",
+ "reference": "7f7e6c56ccda7418a1a264892a625f38a5bdee0c",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.0",
+ "react/dns": "^0.4.13",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.6.0 || ^1.2.1",
+ "react/promise-timer": "^1.4.0",
+ "react/stream": "^1.0 || ^0.7.1"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2018-06-11T14:33:43+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": [
+ "Connection",
+ "Socket",
+ "async",
+ "reactphp",
+ "stream"
+ ]
+ },
+ {
+ "name": "react/stream",
+ "version": "v1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/stream.git",
+ "reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/stream/zipball/50426855f7a77ddf43b9266c22320df5bf6c6ce6",
+ "reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5"
+ },
+ "require-dev": {
+ "clue/stream-filter": "~1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-01-01T16:15:09+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": [
+ "event-driven",
+ "io",
+ "non-blocking",
+ "pipe",
+ "reactphp",
+ "readable",
+ "stream",
+ "writable"
+ ]
+ },
+ {
+ "name": "ringcentral/psr7",
+ "version": "1.2.2",
+ "version_normalized": "1.2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ringcentral/psr7.git",
+ "reference": "dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ringcentral/psr7/zipball/dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c",
+ "reference": "dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "psr/http-message": "~1.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "time": "2018-01-15T21:00:49+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "RingCentral\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "PSR-7 message implementation",
+ "keywords": [
+ "http",
+ "message",
+ "stream",
+ "uri"
+ ]
+ },
+ {
+ "name": "symfony/process",
+ "version": "v4.3.5",
+ "version_normalized": "4.3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/50556892f3cc47d4200bfd1075314139c4c9ff4b",
+ "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "time": "2019-09-26T21:17:10+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com"
+ },
+ {
+ "name": "valga/fbns-react",
+ "version": "0.1.8",
+ "version_normalized": "0.1.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/valga/fbns-react.git",
+ "reference": "4bbf513a8ffed7e0c9ca10776033d34515bb8b37"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/valga/fbns-react/zipball/4bbf513a8ffed7e0c9ca10776033d34515bb8b37",
+ "reference": "4bbf513a8ffed7e0c9ca10776033d34515bb8b37",
+ "shasum": ""
+ },
+ "require": {
+ "binsoul/net-mqtt": "~0.2",
+ "evenement/evenement": "~2.0|~3.0",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "php": "~5.6|~7.0",
+ "psr/log": "~1.0",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.4",
+ "monolog/monolog": "~1.23"
+ },
+ "suggest": {
+ "ext-event": "For more efficient event loop implementation.",
+ "ext-gmp": "To be able to run this code on x86 PHP builds."
+ },
+ "time": "2017-10-09T07:54:13+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Fbns\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Abyr Valg",
+ "email": "valga.github@abyrga.ru"
+ }
+ ],
+ "description": "A PHP client for the FBNS built on top of ReactPHP",
+ "keywords": [
+ "FBNS",
+ "client",
+ "php"
+ ]
+ },
+ {
+ "name": "winbox/args",
+ "version": "v1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/johnstevenson/winbox-args.git",
+ "reference": "389a9ed9410e6f422b1031b3e55a402ace716296"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/johnstevenson/winbox-args/zipball/389a9ed9410e6f422b1031b3e55a402ace716296",
+ "reference": "389a9ed9410e6f422b1031b3e55a402ace716296",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "time": "2016-08-04T14:30:27+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Winbox\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Windows command-line formatter",
+ "homepage": "http://github.com/johnstevenson/winbox-args",
+ "keywords": [
+ "Escape",
+ "command",
+ "windows"
+ ]
+ }
+]
diff --git a/instafeed/vendor/corneltek/getoptionkit/.gitignore b/instafeed/vendor/corneltek/getoptionkit/.gitignore
new file mode 100755
index 0000000..f32e02a
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/.gitignore
@@ -0,0 +1,3 @@
+/build
+/vendor
+*.tgz
diff --git a/instafeed/vendor/corneltek/getoptionkit/.travis.yml b/instafeed/vendor/corneltek/getoptionkit/.travis.yml
new file mode 100755
index 0000000..147e77c
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/.travis.yml
@@ -0,0 +1,29 @@
+language: php
+php:
+- 5.6
+- 7.0
+- 7.1
+- hhvm
+install:
+- phpenv rehash
+- composer require "satooshi/php-coveralls" "^1" --dev --no-update
+- composer install --no-interaction
+script:
+- phpunit -c phpunit.xml.dist
+after_success:
+- php vendor/bin/coveralls -v
+matrix:
+ fast_finish: true
+ allow_failures:
+ - php: hhvm
+ - php: '5.6'
+cache:
+ apt: true
+ directories:
+ - vendor
+notifications:
+ email:
+ on_success: change
+ on_failure: change
+ slack:
+ secure: dKH3qw9myjwDO+OIz6qBqn5vJJqoFyD2frS4eH68jI5gtxHX2PJVwaP9waXTSu+FFq9wILsHGQezrawWHcMkbEo4gxbri2Ne7gFT4CJD0DAOyf02JgQ1A/cJaqQ5XrLO9CwjP0/8PKaMCHiND4SLEWgmtlFRoB7gr33QjbQjBvc=
diff --git a/instafeed/vendor/corneltek/getoptionkit/CHANGELOG.md b/instafeed/vendor/corneltek/getoptionkit/CHANGELOG.md
new file mode 100755
index 0000000..2446ddb
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/CHANGELOG.md
@@ -0,0 +1,56 @@
+CHANGELOG
+==================
+
+## v2.3.0 - Thu May 12 10:29:19 2016
+
+- Fixed bugs for multiple value parsing with arguments.
+- OptionParser::parse(argv) now expects the first argument to be the program name,
+ so you can pass argv directly to the parser.
+
+## v2.2.5-6 - Wed May 11 2016
+
+- Fixed bugs for ContinuousOptionParser.
+
+## v2.2.4 - Fri Oct 2 15:53:33 2015
+
+- ContinuousOptionParser improvements.
+
+## v2.2.2 - Tue Jul 14 00:15:26 2015
+
+- Added PathType.
+
+## v2.2.1 - Tue Jul 14 00:17:19 2015
+
+Merged PRs:
+
+- Commit 7bbb91b: Merge pull request #34 from 1Franck/master
+
+ added value type option(s) support, added class RegexType
+
+- Commit 3722992: Merge pull request #33 from 1Franck/patch-1
+
+ Clarified InvalidOptionValue exception message
+
+
+## v2.2.0 - Thu Jun 4 13:51:47 2015
+
+- Added more types for type constraint option. url, ip, ipv4, ipv6, email by @1Franck++
+- Several bug fixes by @1Franck++
+
+
+
+## v2.1.0 - Fri Apr 24 16:43:00 2015
+
+- Added incremental option value support.
+- Fixed #21 for negative value.
+- Used autoloading with PSR-4
+
+## v2.0.12 - Tue Apr 21 18:51:12 2015
+
+- Improved hinting text for default value
+- Some coding style fix
+- Added default value support
+- Updated default value support for ContinuousOptionParser
+- Added getValue() accessor on OptionSpec class
+- Merged pull request #22 from Gasol/zero-option. @Gasol++
+ - Fix option that can't not be 0
diff --git a/instafeed/vendor/corneltek/getoptionkit/CONTRIBUTORS.txt b/instafeed/vendor/corneltek/getoptionkit/CONTRIBUTORS.txt
new file mode 100755
index 0000000..59cb935
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/CONTRIBUTORS.txt
@@ -0,0 +1,11 @@
+Bitdeli Chef
+Dlussky Kirill
+Francois Lajoie
+Gasol Wu
+Igor Santos
+Jevon Wright
+MartyIX
+Michał Kniotek
+Robbert Klarenbeek
+Yo-An Lin
+Yo-An Lin
diff --git a/instafeed/vendor/corneltek/getoptionkit/LICENSE b/instafeed/vendor/corneltek/getoptionkit/LICENSE
new file mode 100755
index 0000000..30a0180
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Yo-An Lin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/instafeed/vendor/corneltek/getoptionkit/README.md b/instafeed/vendor/corneltek/getoptionkit/README.md
new file mode 100755
index 0000000..1e5794b
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/README.md
@@ -0,0 +1,370 @@
+GetOptionKit
+============
+
+Code Quality
+
+[](https://travis-ci.org/c9s/GetOptionKit)
+[](https://coveralls.io/github/c9s/GetOptionKit?branch=master)
+
+Versions & Stats
+
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+
+A powerful option parser toolkit for PHP, supporting type constraints,
+flag, multiple flag, multiple values and required value checking.
+
+GetOptionKit supports PHP5.3, with fine unit testing with PHPUnit testing
+framework.
+
+GetOptionKit is object-oriented, it's flexible and extendable.
+
+Powering PHPBrew , CLIFramework and AssetKit
+
+
+
+## Features
+
+- Simple format.
+- Type constrant.
+- Multiple value, requried value, optional value checking.
+- Auto-generated help text from defined options.
+- Support app/subcommand option parsing.
+- Option Value Validator
+- Option Suggestions
+- SPL library.
+- HHVM support.
+
+## Requirements
+
+* PHP 5.3+
+
+## Install From Composer
+
+```sh
+composer require corneltek/getoptionkit
+```
+
+
+## Supported Option Formats
+
+simple flags:
+
+```sh
+program.php -a -b -c
+program.php -abc
+program.php -vvv # incremental flag v=3
+program.php -a -bc
+```
+
+with multiple values:
+
+```sh
+program.php -a foo -a bar -a zoo -b -b -b
+```
+
+specify value with equal sign:
+
+```sh
+program.php -a=foo
+program.php --long=foo
+```
+
+with normal arguments:
+
+```
+program.php -a=foo -b=bar arg1 arg2 arg3
+program.php arg1 arg2 arg3 -a=foo -b=bar
+```
+
+## Option SPEC
+
+ v|verbose flag option (with boolean value true)
+ d|dir: option require a value (MUST require)
+ d|dir+ option with multiple values.
+ d|dir? option with optional value
+ dir:=string option with type constraint of string
+ dir:=number option with type constraint of number
+ dir:=file option with type constraint of file
+ dir:=date option with type constraint of date
+ dir:=boolean option with type constraint of boolean
+ d single character only option
+ dir long option name
+
+## Command Line Forms
+
+ app [app-opts] [app arguments]
+
+ app [app-opts] subcommand [subcommand-opts] [subcommand-args]
+
+ app [app-opts] subcmd1 [subcmd-opts1] subcmd2 [subcmd-opts] subcmd3 [subcmd-opts3] [subcommand arguments....]
+
+
+## Documentation
+
+See more details in the [documentation](https://github.com/c9s/GetOptionKit/wiki)
+
+## Demo
+
+Please check `examples/demo.php`.
+
+Run:
+
+```sh
+% php examples/demo.php -f test -b 123 -b 333
+```
+
+Print:
+
+ * Available options:
+ -f, --foo option requires a value.
+ -b, --bar + option with multiple value.
+ -z, --zoo [] option with optional value.
+ -v, --verbose verbose message.
+ -d, --debug debug message.
+ --long long option name only.
+ -s short option name only.
+ Enabled options:
+ * key:foo spec:-f, --foo desc:option requires a value.
+ value => test
+
+ * key:bar spec:-b, --bar + desc:option with multiple value.
+ Array
+ (
+ [0] => 123
+ [1] => 333
+ )
+
+## Synopsis
+
+```php
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
+
+$specs = new OptionCollection;
+$specs->add('f|foo:', 'option requires a value.' )
+ ->isa('String');
+
+$specs->add('b|bar+', 'option with multiple value.' )
+ ->isa('Number');
+
+$specs->add('ip+', 'Ip constraint' )
+ ->isa('Ip');
+
+$specs->add('email+', 'Email address constraint' )
+ ->isa('Email');
+
+$specs->add('z|zoo?', 'option with optional value.' )
+ ->isa('Boolean');
+
+$specs->add('file:', 'option value should be a file.' )
+ ->isa('File');
+
+$specs->add('v|verbose', 'verbose message.' );
+$specs->add('d|debug', 'debug message.' );
+$specs->add('long', 'long option name only.' );
+$specs->add('s', 'short option name only.' );
+
+$printer = new ConsoleOptionPrinter();
+echo $printer->render($specs);
+
+$parser = new OptionParser($specs);
+
+echo "Enabled options: \n";
+try {
+ $result = $parser->parse( $argv );
+ foreach ($result->keys as $key => $spec) {
+ print_r($spec);
+ }
+
+ $opt = $result->keys['foo']; // return the option object.
+ $str = $result->keys['foo']->value; // return the option value
+
+ print_r($opt);
+ var_dump($str);
+
+} catch( Exception $e ) {
+ echo $e->getMessage();
+}
+```
+
+
+## Documentation
+
+See for more details.
+
+### Option Value Type
+
+The option value type help you validate the input,
+the following list is the current supported types:
+
+- `string`
+- `number`
+- `boolean`
+- `file`
+- `date`
+- `url`
+- `email`
+- `ip`
+- `ipv4`
+- `ipv6`
+- `regex`
+
+And here is the related sample code:
+
+```php
+$opt->add( 'f|foo:' , 'with string type value' )
+ ->isa('string');
+
+$opt->add( 'b|bar+' , 'with number type value' )
+ ->isa('number');
+
+$opt->add( 'z|zoo?' , 'with boolean type value' )
+ ->isa('boolean');
+
+$opt->add( 'file:' , 'with file type value' )
+ ->isa('file');
+
+$opt->add( 'date:' , 'with date type value' )
+ ->isa('date');
+
+$opt->add( 'url:' , 'with url type value' )
+ ->isa('url');
+
+$opt->add( 'email:' , 'with email type value' )
+ ->isa('email');
+
+$opt->add( 'ip:' , 'with ip(v4/v6) type value' )
+ ->isa('ip');
+
+$opt->add( 'ipv4:' , 'with ipv4 type value' )
+ ->isa('ipv4');
+
+$opt->add( 'ipv6:' , 'with ipv6 type value' )
+ ->isa('ipv6');
+
+$specs->add('r|regex:', 'with custom regex type value')
+ ->isa('Regex', '/^([a-z]+)$/');
+```
+
+> Please note that currently only `string`, `number`, `boolean` types can be validated.
+
+### ContinuousOptionParser
+
+```php
+$specs = new OptionCollection;
+$spec_verbose = $specs->add('v|verbose');
+$spec_color = $specs->add('c|color');
+$spec_debug = $specs->add('d|debug');
+$spec_verbose->description = 'verbose flag';
+
+// ContinuousOptionParser
+$parser = new ContinuousOptionParser( $specs );
+$result = $parser->parse(explode(' ','program -v -d test -a -b -c subcommand -e -f -g subcommand2'));
+$result2 = $parser->continueParse();
+```
+
+### OptionPrinter
+
+GetOptionKit\OptionPrinter can print options for you:
+
+ * Available options:
+ -f, --foo option requires a value.
+ -b, --bar option with multiple value.
+ -z, --zoo option with optional value.
+ -v, --verbose verbose message.
+ -d, --debug debug message.
+ --long long option name only.
+ -s short option name only.
+
+
+## Command-line app with subcommands
+
+For application with subcommands is designed by following form:
+
+
+ [app name] [app opts]
+ [subcommand1] [subcommand-opts]
+ [subcommand2] [subcommand-opts]
+ [subcommand3] [subcommand-opts]
+ [arguments]
+
+You can check the `tests/GetOptionKit/ContinuousOptionParserTest.php` unit test file:
+
+```php
+// subcommand stack
+$subcommands = array('subcommand1','subcommand2','subcommand3');
+
+// different command has its own options
+$subcommandSpecs = array(
+ 'subcommand1' => $cmdspecs,
+ 'subcommand2' => $cmdspecs,
+ 'subcommand3' => $cmdspecs,
+);
+
+// for saved options
+$subcommandOptions = array();
+
+// command arguments
+$arguments = array();
+
+$argv = explode(' ','program -v -d -c subcommand1 -a -b -c subcommand2 -c subcommand3 arg1 arg2 arg3');
+
+// parse application options first
+$parser = new ContinuousOptionParser( $appspecs );
+$app_options = $parser->parse( $argv );
+while (! $parser->isEnd()) {
+ if (@$subcommands[0] && $parser->getCurrentArgument() == $subcommands[0]) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs( $subcommandSpecs[$subcommand] );
+ $subcommandOptions[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+}
+```
+
+## Todo
+
+* Option Spec group.
+* option valid value checking.
+* custom command mapping.
+
+## Command Line Utility Design Concept
+
+* main program name should be easy to type, easy to remember.
+* subcommand should be easy to type, easy to remember. length should be shorter than 7 characters.
+* options should always have long descriptive name
+* a program should be easy to check usage.
+
+## General command interface
+
+To list usage of all subcommands or the program itself:
+
+ $ prog help
+
+To list the subcommand usage
+
+ $ prog help subcommand subcommand2 subcommand3
+
+## Hacking
+
+Fork this repository and clone it:
+
+ $ git clone git://github.com/c9s/GetOptionKit.git
+ $ cd GetOptionKit
+ $ composer install
+
+Run PHPUnit to test:
+
+ $ phpunit
+
+## License
+
+This project is released under MIT License.
diff --git a/instafeed/vendor/corneltek/getoptionkit/build.xml b/instafeed/vendor/corneltek/getoptionkit/build.xml
new file mode 100755
index 0000000..f53f06d
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/build.xml
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/composer.json b/instafeed/vendor/corneltek/getoptionkit/composer.json
new file mode 100755
index 0000000..e6577d5
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/composer.json
@@ -0,0 +1,16 @@
+{
+ "name": "corneltek/getoptionkit",
+ "homepage": "http://github.com/c9s/GetOptionKit",
+ "description": "Powerful command-line option toolkit",
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "license": "MIT",
+ "authors": [ { "name": "Yo-An Lin", "email": "yoanlin93@gmail.com" } ],
+ "autoload": {
+ "psr-4": {
+ "GetOptionKit\\": "src/"
+ }
+ },
+ "extra": { "branch-alias": { "dev-master": "2.6.x-dev" } }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/composer.lock b/instafeed/vendor/corneltek/getoptionkit/composer.lock
new file mode 100755
index 0000000..1dd27fe
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/composer.lock
@@ -0,0 +1,19 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "5a9b6e9fccaf89d121650d35313e842b",
+ "packages": [],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.3.0"
+ },
+ "platform-dev": []
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/examples/demo.php b/instafeed/vendor/corneltek/getoptionkit/examples/demo.php
new file mode 100755
index 0000000..905f5c3
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/examples/demo.php
@@ -0,0 +1,76 @@
+#!/usr/bin/env php
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+require 'vendor/autoload.php';
+
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
+
+$specs = new OptionCollection;
+$specs->add('f|foo:', 'option requires a value.' )
+ ->isa('String');
+
+$specs->add('b|bar+', 'option with multiple value.' )
+ ->isa('Number');
+
+$specs->add('z|zoo?', 'option with optional value.' )
+ ->isa('Boolean')
+ ;
+
+$specs->add('o|output?', 'option with optional value.' )
+ ->isa('File')
+ ->defaultValue('output.txt')
+ ;
+
+// works for -vvv => verbose = 3
+$specs->add('v|verbose', 'verbose')
+ ->isa('Number')
+ ->incremental();
+
+$specs->add('file:', 'option value should be a file.' )
+ ->trigger(function($value) {
+ echo "Set value to :";
+ var_dump($value);
+ })
+ ->isa('File');
+
+$specs->add('r|regex:', 'with custom regex type value')
+ ->isa('Regex', '/^([a-z]+)$/');
+
+$specs->add('d|debug', 'debug message.' );
+$specs->add('long', 'long option name only.' );
+$specs->add('s', 'short option name only.' );
+$specs->add('m', 'short option m');
+$specs->add('4', 'short option with digit');
+
+$printer = new ConsoleOptionPrinter;
+echo $printer->render($specs);
+
+$parser = new OptionParser($specs);
+
+echo "Enabled options: \n";
+try {
+ $result = $parser->parse( $argv );
+ foreach ($result->keys as $key => $spec) {
+ print_r($spec);
+ }
+
+ $opt = $result->keys['foo']; // return the option object.
+ $str = $result->keys['foo']->value; // return the option value
+
+ print_r($opt);
+ var_dump($str);
+
+} catch( Exception $e ) {
+ echo $e->getMessage();
+}
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/package.ini b/instafeed/vendor/corneltek/getoptionkit/package.ini
new file mode 100755
index 0000000..4d71095
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/package.ini
@@ -0,0 +1,15 @@
+[package]
+name = GetOptionKit
+version = 1.2.2
+desc = "A powerful GetOpt toolkit for PHP, which supports type constraints, flag,
+multiple flag, multiple values, required value checking."
+
+author = "Yo-An Lin (c9s) "
+channel = pear.corneltek.com
+stability = stable
+
+[require]
+php = 5.3
+pearinstaller = 1.4.1
+pear.corneltek.com/Universal =
+pear.corneltek.com/PHPUnit_TestMore =
diff --git a/instafeed/vendor/corneltek/getoptionkit/phpdox.xml b/instafeed/vendor/corneltek/getoptionkit/phpdox.xml
new file mode 100755
index 0000000..c727483
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/phpdox.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/phprelease.ini b/instafeed/vendor/corneltek/getoptionkit/phprelease.ini
new file mode 100755
index 0000000..a5c5cc9
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/phprelease.ini
@@ -0,0 +1,2 @@
+Steps = PHPUnit, BumpVersion, GitCommit, GitTag, GitPush, GitPushTags
+; VersionFrom = src/PHPRelease/Console.php
\ No newline at end of file
diff --git a/instafeed/vendor/corneltek/getoptionkit/phpunit-ci.xml b/instafeed/vendor/corneltek/getoptionkit/phpunit-ci.xml
new file mode 100755
index 0000000..0fed2fa
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/phpunit-ci.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/phpunit.xml.dist b/instafeed/vendor/corneltek/getoptionkit/phpunit.xml.dist
new file mode 100755
index 0000000..87bb483
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/phpunit.xml.dist
@@ -0,0 +1,29 @@
+
+
+
+
+
+ src
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
+
+
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/Argument.php b/instafeed/vendor/corneltek/getoptionkit/src/Argument.php
new file mode 100755
index 0000000..2a92717
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/Argument.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+class Argument
+{
+ public $arg;
+
+ public function __construct($arg)
+ {
+ $this->arg = $arg;
+ }
+
+ public function isLongOption()
+ {
+ return substr($this->arg, 0, 2) === '--';
+ }
+
+ public function isShortOption()
+ {
+ return (substr($this->arg, 0, 1) === '-')
+ && (substr($this->arg, 1, 1) !== '-');
+ }
+
+ public function isEmpty()
+ {
+ return $this->arg === null || empty($this->arg) && ('0' !== $this->arg);
+ }
+
+ /**
+ * Check if an option is one of the option in the collection.
+ */
+ public function anyOfOptions(OptionCollection $options)
+ {
+ $name = $this->getOptionName();
+ $keys = $options->keys();
+
+ return in_array($name, $keys);
+ }
+
+ /**
+ * Check current argument is an option by the preceding dash.
+ * note this method does not work for string with negative value.
+ *
+ * -a
+ * --foo
+ */
+ public function isOption()
+ {
+ return $this->isShortOption() || $this->isLongOption();
+ }
+
+ /**
+ * Parse option and return the name after dash. e.g.,
+ * '--foo' returns 'foo'
+ * '-f' returns 'f'.
+ *
+ * @return string
+ */
+ public function getOptionName()
+ {
+ if (preg_match('/^[-]+([a-zA-Z0-9-]+)/', $this->arg, $regs)) {
+ return $regs[1];
+ }
+ }
+
+ public function splitAsOption()
+ {
+ return explode('=', $this->arg, 2);
+ }
+
+ public function containsOptionValue()
+ {
+ return preg_match('/=.+/', $this->arg);
+ }
+
+ public function getOptionValue()
+ {
+ if (preg_match('/=(.+)/', $this->arg, $regs)) {
+ return $regs[1];
+ }
+
+ return;
+ }
+
+ /**
+ * Check combined short flags for "-abc" or "-vvv".
+ *
+ * like: -abc
+ */
+ public function withExtraFlagOptions()
+ {
+ return preg_match('/^-[a-zA-Z0-9]{2,}/', $this->arg);
+ }
+
+ public function extractExtraFlagOptions()
+ {
+ $args = array();
+ for ($i = 2;$i < strlen($this->arg); ++$i) {
+ $args[] = '-'.$this->arg[$i];
+ }
+ $this->arg = substr($this->arg, 0, 2); # -[a-z]
+ return $args;
+ }
+
+ public function __toString()
+ {
+ return $this->arg;
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/ContinuousOptionParser.php b/instafeed/vendor/corneltek/getoptionkit/src/ContinuousOptionParser.php
new file mode 100755
index 0000000..fa592fd
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/ContinuousOptionParser.php
@@ -0,0 +1,201 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+use Exception;
+use LogicException;
+use GetOptionKit\Exception\InvalidOptionException;
+use GetOptionKit\Exception\RequireValueException;
+
+/**
+ * A common command line argument format:.
+ *
+ * app.php
+ * [--app-options]
+ *
+ * [subcommand
+ * --subcommand-options]
+ * [subcommand
+ * --subcommand-options]
+ * [subcommand
+ * --subcommand-options]
+ *
+ * [arguments]
+ *
+ * ContinuousOptionParser is for the process flow:
+ *
+ * init app options,
+ * parse app options
+ *
+ *
+ *
+ * while not end
+ * if stop at command
+ * shift command
+ * parse command options
+ * else if stop at arguments
+ * shift arguments
+ * execute current command with the arguments.
+ *
+ * Example code:
+ *
+ *
+ * // subcommand stack
+ * $subcommands = array('subcommand1','subcommand2','subcommand3');
+ *
+ * // different command has its own options
+ * $subcommand_specs = array(
+ * 'subcommand1' => $cmdspecs,
+ * 'subcommand2' => $cmdspecs,
+ * 'subcommand3' => $cmdspecs,
+ * );
+ *
+ * // for saved options
+ * $subcommand_options = array();
+ *
+ * // command arguments
+ * $arguments = array();
+ *
+ * $argv = explode(' ','-v -d -c subcommand1 -a -b -c subcommand2 -c subcommand3 arg1 arg2 arg3');
+ *
+ * // parse application options first
+ * $parser = new ContinuousOptionParser( $appspecs );
+ * $app_options = $parser->parse( $argv );
+ * while (! $parser->isEnd()) {
+ * if( $parser->getCurrentArgument() == $subcommands[0] ) {
+ * $parser->advance();
+ * $subcommand = array_shift( $subcommands );
+ * $parser->setSpecs( $subcommand_specs[$subcommand] );
+ * $subcommand_options[ $subcommand ] = $parser->continueParse();
+ * } else {
+ * $arguments[] = $parser->advance();
+ * }
+ * }
+ **/
+class ContinuousOptionParser extends OptionParser
+{
+ public $index;
+ public $length;
+ public $argv;
+
+ /* for the constructor , the option specs is application options */
+ public function __construct(OptionCollection $specs)
+ {
+ parent::__construct($specs);
+ $this->index = 1;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ */
+ public function startFrom($index)
+ {
+ $this->index = $index;
+ }
+
+ public function isEnd()
+ {
+ # echo "!! {$this->index} >= {$this->length}\n";
+ return $this->index >= $this->length;
+ }
+
+ /**
+ * Return the current argument and advance to the next position.
+ *
+ * @return string
+ */
+ public function advance()
+ {
+ if ($this->index >= $this->length) {
+ throw new LogicException("Argument index out of bounds.");
+ }
+ return $this->argv[$this->index++];
+ }
+
+ /**
+ * Return the current argument that the index pointed to.
+ *
+ * @return string
+ */
+ public function getCurrentArgument()
+ {
+ return $this->argv[$this->index];
+ }
+
+ public function continueParse()
+ {
+ return $this->parse($this->argv);
+ }
+
+
+ protected function fillDefaultValues(OptionCollection $opts, OptionResult $result)
+ {
+ // register option result from options with default value
+ foreach ($opts as $opt) {
+ if ($opt->value === null && $opt->defaultValue !== null) {
+ $opt->setValue($opt->getDefaultValue());
+ $result->set($opt->getId(), $opt);
+ }
+ }
+ }
+
+
+ public function parse(array $argv)
+ {
+ // create new Result object.
+ $result = new OptionResult();
+ list($this->argv, $extra) = $this->preprocessingArguments($argv);
+ $this->length = count($this->argv);
+
+ // from last parse index
+ for (; $this->index < $this->length; ++$this->index) {
+ $arg = new Argument($this->argv[$this->index]);
+
+ /* let the application decide for: command or arguments */
+ if (!$arg->isOption()) {
+ # echo "stop at {$this->index}\n";
+ $this->fillDefaultValues($this->specs, $result);
+ return $result;
+ }
+
+ // if the option is with extra flags,
+ // split it out, and insert into the argv array
+ //
+ // like -abc
+ if ($arg->withExtraFlagOptions()) {
+ $extra = $arg->extractExtraFlagOptions();
+ array_splice($this->argv, $this->index + 1, 0, $extra);
+ $this->argv[$this->index] = $arg->arg; // update argument to current argv list.
+ $this->length = count($this->argv); // update argv list length
+ }
+
+ $next = null;
+ if ($this->index + 1 < count($this->argv)) {
+ $next = new Argument($this->argv[$this->index + 1]);
+ }
+
+ $spec = $this->specs->get($arg->getOptionName());
+ if (!$spec) {
+ throw new InvalidOptionException('Invalid option: '.$arg);
+ }
+
+ // This if block is unnecessary
+ // if ($spec->isRequired() || $spec->isMultiple() || $spec->isOptional() || $spec->isFlag()) {
+ $this->index += $this->consumeOptionToken($spec, $arg, $next);
+ $result->set($spec->getId(), $spec);
+ }
+
+ $this->fillDefaultValues($this->specs, $result);
+
+ return $result;
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionException.php b/instafeed/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionException.php
new file mode 100755
index 0000000..dea01de
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class InvalidOptionException extends Exception
+{
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionValueException.php b/instafeed/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionValueException.php
new file mode 100755
index 0000000..fa4b163
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionValueException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class InvalidOptionValueException extends Exception
+{
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/Exception/NonNumericException.php b/instafeed/vendor/corneltek/getoptionkit/src/Exception/NonNumericException.php
new file mode 100755
index 0000000..757d214
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/Exception/NonNumericException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class NonNumericException extends Exception
+{
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/Exception/OptionConflictException.php b/instafeed/vendor/corneltek/getoptionkit/src/Exception/OptionConflictException.php
new file mode 100755
index 0000000..6a3c9e1
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/Exception/OptionConflictException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class OptionConflictException extends Exception
+{
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/Exception/RequireValueException.php b/instafeed/vendor/corneltek/getoptionkit/src/Exception/RequireValueException.php
new file mode 100755
index 0000000..c9e69ef
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/Exception/RequireValueException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class RequireValueException extends Exception
+{
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/Option.php b/instafeed/vendor/corneltek/getoptionkit/src/Option.php
new file mode 100755
index 0000000..f11a93a
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/Option.php
@@ -0,0 +1,557 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+
+use Exception;
+use LogicException;
+use InvalidArgumentException;
+use GetOptionKit\Exception\InvalidOptionValueException;
+
+class Option
+{
+ public $short;
+
+ public $long;
+
+ /**
+ * @var string the description of this option
+ */
+ public $desc;
+
+ /**
+ * @var string The option key
+ */
+ public $key; /* key to store values */
+
+ public $value;
+
+ public $type;
+
+ public $valueName; /* name for the value place holder, for printing */
+
+ public $isa;
+
+ public $isaOption;
+
+ public $validValues;
+
+ public $suggestions;
+
+ public $defaultValue;
+
+ public $incremental = false;
+
+ /**
+ * @var Closure The filter closure of the option value.
+ */
+ public $filter;
+
+ public $validator;
+
+ public $multiple = false;
+
+ public $optional = false;
+
+ public $required = false;
+
+ public $flag = false;
+
+ /**
+ * @var callable trigger callback after value is set.
+ */
+ protected $trigger;
+
+ public function __construct($spec)
+ {
+ $this->initFromSpecString($spec);
+ }
+
+ /**
+ * Build spec attributes from spec string.
+ *
+ * @param string $specString
+ */
+ protected function initFromSpecString($specString)
+ {
+ $pattern = '/
+ (
+ (?:[a-zA-Z0-9-]+)
+ (?:
+ \|
+ (?:[a-zA-Z0-9-]+)
+ )?
+ )
+
+ # option attribute operators
+ ([:+?])?
+
+ # value types
+ (?:=(boolean|string|number|date|file|dir|url|email|ip|ipv6|ipv4))?
+ /x';
+ $ret = preg_match($pattern, $specString, $regs);
+ if ($ret === false || $ret === 0) {
+ throw new Exception('Incorrect spec string');
+ }
+
+ $orig = $regs[0];
+ $name = $regs[1];
+ $attributes = isset($regs[2]) ? $regs[2] : null;
+ $type = isset($regs[3]) ? $regs[3] : null;
+
+ $short = null;
+ $long = null;
+
+ // check long,short option name.
+ if (strpos($name, '|') !== false) {
+ list($short, $long) = explode('|', $name);
+ } else if (strlen($name) === 1) {
+ $short = $name;
+ } else if (strlen($name) > 1) {
+ $long = $name;
+ }
+
+ $this->short = $short;
+ $this->long = $long;
+
+ // option is required.
+ if (strpos($attributes, ':') !== false) {
+ $this->required();
+ } else if (strpos($attributes, '+') !== false) {
+ // option with multiple value
+ $this->multiple();
+ } else if (strpos($attributes, '?') !== false) {
+ // option is optional.(zero or one value)
+ $this->optional();
+ } else {
+ $this->flag();
+ }
+ if ($type) {
+ $this->isa($type);
+ }
+ }
+
+ /*
+ * get the option key for result key mapping.
+ */
+ public function getId()
+ {
+ return $this->key ?: $this->long ?: $this->short;
+ }
+
+ /**
+ * To make -v, -vv, -vvv works.
+ */
+ public function incremental()
+ {
+ $this->incremental = true;
+
+ return $this;
+ }
+
+ public function required()
+ {
+ $this->required = true;
+
+ return $this;
+ }
+
+ /**
+ * Set default value
+ *
+ * @param mixed|Closure $value
+ */
+ public function defaultValue($value)
+ {
+ $this->defaultValue = $value;
+
+ return $this;
+ }
+
+ public function multiple()
+ {
+ $this->multiple = true;
+ $this->value = array(); # for value pushing
+ return $this;
+ }
+
+ public function optional()
+ {
+ $this->optional = true;
+
+ return $this;
+ }
+
+ public function flag()
+ {
+ $this->flag = true;
+
+ return $this;
+ }
+
+ public function trigger(callable $trigger)
+ {
+ $this->trigger = $trigger;
+
+ return $this;
+ }
+
+ public function isIncremental()
+ {
+ return $this->incremental;
+ }
+
+ public function isFlag()
+ {
+ return $this->flag;
+ }
+
+ public function isMultiple()
+ {
+ return $this->multiple;
+ }
+
+ public function isRequired()
+ {
+ return $this->required;
+ }
+
+ public function isOptional()
+ {
+ return $this->optional;
+ }
+
+ public function isTypeNumber()
+ {
+ return $this->isa == 'number';
+ }
+
+ public function isType($type)
+ {
+ return $this->isa === $type;
+ }
+
+ public function getTypeClass()
+ {
+ $class = 'GetOptionKit\\ValueType\\'.ucfirst($this->isa).'Type';
+ if (class_exists($class, true)) {
+ return new $class($this->isaOption);
+ }
+ throw new Exception("Type class '$class' not found.");
+ }
+
+ public function testValue($value)
+ {
+ $type = $this->getTypeClass();
+ return $type->test($value);
+ }
+
+ protected function _preprocessValue($value)
+ {
+ $val = $value;
+
+ if ($isa = ucfirst($this->isa)) {
+ $type = $this->getTypeClass();
+ if ($type->test($value)) {
+ $val = $type->parse($value);
+ } else {
+ if (strtolower($isa) === 'regex') {
+ $isa .= '('.$this->isaOption.')';
+ }
+ throw new InvalidOptionValueException("Invalid value for {$this->renderReadableSpec(false)}. Requires a type $isa.");
+ }
+ }
+
+ // check pre-filter for option value
+ if ($this->filter) {
+ $val = call_user_func($this->filter, $val);
+ }
+
+ // check validValues
+ if ($validValues = $this->getValidValues()) {
+ if (!in_array($value, $validValues)) {
+ throw new InvalidOptionValueException('valid values are: '.implode(', ', $validValues));
+ }
+ }
+
+ if (!$this->validate($value)[0]) {
+ throw new InvalidOptionValueException('option is invalid');
+ }
+
+ return $val;
+ }
+
+ protected function callTrigger()
+ {
+ if ($this->trigger) {
+ if ($ret = call_user_func($this->trigger, $this->value)) {
+ $this->value = $ret;
+ }
+ }
+ }
+
+ /*
+ * set option value
+ */
+ public function setValue($value)
+ {
+ $this->value = $this->_preprocessValue($value);
+ $this->callTrigger();
+ }
+
+ /**
+ * This method is for incremental option.
+ */
+ public function increaseValue()
+ {
+ if (!$this->value) {
+ $this->value = 1;
+ } else {
+ ++$this->value;
+ }
+ $this->callTrigger();
+ }
+
+ /**
+ * push option value, when the option accept multiple values.
+ *
+ * @param mixed
+ */
+ public function pushValue($value)
+ {
+ $value = $this->_preprocessValue($value);
+ $this->value[] = $value;
+ $this->callTrigger();
+ }
+
+ public function desc($desc)
+ {
+ $this->desc = $desc;
+ }
+
+ /**
+ * valueName is for option value hinting:.
+ *
+ * --name=
+ */
+ public function valueName($name)
+ {
+ $this->valueName = $name;
+
+ return $this;
+ }
+
+ public function renderValueHint()
+ {
+ $n = null;
+ if ($this->valueName) {
+ $n = $this->valueName;
+ } else if ($values = $this->getValidValues()) {
+ $n = '('.implode(',', $values).')';
+ } else if ($values = $this->getSuggestions()) {
+ $n = '['.implode(',', $values).']';
+ } else if ($val = $this->getDefaultValue()) {
+ // This allows for `0` and `false` values to be displayed also.
+ if ((is_scalar($val) && strlen((string) $val)) || is_bool($val)) {
+ if (is_bool($val)) {
+ $n = ($val ? 'true' : 'false');
+ } else {
+ $n = $val;
+ }
+ }
+ }
+
+ if (!$n && $this->isa !== null) {
+ $n = '<'.$this->isa.'>';
+ }
+ if ($this->isRequired()) {
+ return sprintf('=%s', $n);
+ } else if ($this->isOptional() || $this->defaultValue) {
+ return sprintf('[=%s]', $n);
+ } else if ($n) {
+ return '='.$n;
+ }
+
+ return '';
+ }
+
+ public function getDefaultValue()
+ {
+ if (is_callable($this->defaultValue)) {
+ return $this->defaultValue;
+ }
+
+ return $this->defaultValue;
+ }
+
+ public function getValue()
+ {
+ if (null !== $this->value) {
+ if (is_callable($this->value)) {
+ return call_user_func($this->value);
+ }
+ return $this->value;
+ }
+
+ return $this->getDefaultValue();
+ }
+
+ /**
+ * get readable spec for printing.
+ *
+ * @param string $renderHint render also value hint
+ */
+ public function renderReadableSpec($renderHint = true)
+ {
+ $c1 = '';
+ if ($this->short && $this->long) {
+ $c1 = sprintf('-%s, --%s', $this->short, $this->long);
+ } else if ($this->short) {
+ $c1 = sprintf('-%s', $this->short);
+ } else if ($this->long) {
+ $c1 = sprintf('--%s', $this->long);
+ }
+ if ($renderHint) {
+ return $c1.$this->renderValueHint();
+ }
+
+ return $c1;
+ }
+
+ public function __toString()
+ {
+ $c1 = $this->renderReadableSpec();
+ $return = '';
+ $return .= sprintf('* key:%-8s spec:%s desc:%s', $this->getId(), $c1, $this->desc)."\n";
+ $val = $this->getValue();
+ if (is_array($val)) {
+ $return .= ' value => ' . join(',', array_map(function($v) { return var_export($v, true); }, $val))."\n";
+ } else {
+ $return .= sprintf(' value => %s', $val)."\n";
+ }
+
+ return $return;
+ }
+
+ /**
+ * Value Type Setters.
+ *
+ * @param string $type the value type, valid values are 'number', 'string',
+ * 'file', 'boolean', you can also use your own value type name.
+ * @param mixed $option option(s) for value type class (optionnal)
+ */
+ public function isa($type, $option = null)
+ {
+ // "bool" was kept for backward compatibility
+ if ($type === 'bool') {
+ $type = 'boolean';
+ }
+ $this->isa = $type;
+ $this->isaOption = $option;
+
+ return $this;
+ }
+
+ /**
+ * Assign validValues to member value.
+ */
+ public function validValues($values)
+ {
+ $this->validValues = $values;
+
+ return $this;
+ }
+
+ /**
+ * Assign suggestions.
+ *
+ * @param Closure|array
+ */
+ public function suggestions($suggestions)
+ {
+ $this->suggestions = $suggestions;
+
+ return $this;
+ }
+
+ /**
+ * Return valud values array.
+ *
+ * @return string[] or nil
+ */
+ public function getValidValues()
+ {
+ if ($this->validValues) {
+ if (is_callable($this->validValues)) {
+ return call_user_func($this->validValues);
+ }
+
+ return $this->validValues;
+ }
+
+ return;
+ }
+
+ /**
+ * Return suggestions.
+ *
+ * @return string[] or nil
+ */
+ public function getSuggestions()
+ {
+ if ($this->suggestions) {
+ if (is_callable($this->suggestions)) {
+ return call_user_func($this->suggestions);
+ }
+
+ return $this->suggestions;
+ }
+
+ return;
+ }
+
+ public function validate($value)
+ {
+ if ($this->validator) {
+ $ret = call_user_func($this->validator, $value);
+ if (is_array($ret)) {
+ return $ret;
+ } else if ($ret === false) {
+ return array(false, "Invalid value: $value");
+ } else if ($ret === true) {
+ return array(true, 'Successfully validated.');
+ }
+ throw new InvalidArgumentException('Invalid return value from the validator.');
+ }
+
+ return array(true);
+ }
+
+ public function validator($cb)
+ {
+ $this->validator = $cb;
+
+ return $this;
+ }
+
+ /**
+ * Set up a filter function for the option value.
+ *
+ * todo: add "callable" type hint later.
+ */
+ public function filter($cb)
+ {
+ $this->filter = $cb;
+
+ return $this;
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/OptionCollection.php b/instafeed/vendor/corneltek/getoptionkit/src/OptionCollection.php
new file mode 100755
index 0000000..22cb638
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/OptionCollection.php
@@ -0,0 +1,207 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+
+use ArrayIterator;
+use IteratorAggregate;
+use Countable;
+use Exception;
+use LogicException;
+use GetOptionKit\Exception\OptionConflictException;
+
+class OptionCollection
+ implements IteratorAggregate, Countable
+{
+ public $data = array();
+
+ /**
+ * @var Option[string]
+ *
+ * read-only property
+ */
+ public $longOptions = array();
+
+ /**
+ * @var Option[string]
+ *
+ * read-only property
+ */
+ public $shortOptions = array();
+
+ /**
+ * @var Option[]
+ *
+ * read-only property
+ */
+ public $options = array();
+
+ public function __construct()
+ {
+ $this->data = array();
+ }
+
+ public function __clone()
+ {
+ foreach ($this->data as $k => $v) {
+ $this->data[ $k ] = clone $v;
+ }
+ foreach ($this->longOptions as $k => $v) {
+ $this->longOptions[ $k ] = clone $v;
+ }
+ foreach ($this->shortOptions as $k => $v) {
+ $this->shortOptions[ $k ] = clone $v;
+ }
+ foreach ($this->options as $k => $v) {
+ $this->options[ $k ] = clone $v;
+ }
+ }
+
+ /**
+ * add( [spec string], [desc string] ).
+ *
+ * add( [option object] )
+ */
+ public function add()
+ {
+ $num = func_num_args();
+ $args = func_get_args();
+ $first = $args[0];
+
+ if ($first instanceof Option) {
+
+ $this->addOption($first);
+
+ } else if (is_string($first)) {
+
+ $specString = $args[0];
+ $desc = isset($args[1]) ? $args[1] : null;
+ $key = isset($args[2]) ? $args[2] : null;
+
+ // parse spec string
+ $spec = new Option($specString);
+ if ($desc) {
+ $spec->desc($desc);
+ }
+ if ($key) {
+ $spec->key = $key;
+ }
+ $this->addOption($spec);
+ return $spec;
+
+ } else {
+
+ throw new LogicException('Unknown Spec Type');
+
+ }
+ }
+
+ /**
+ * Add option object.
+ *
+ * @param object $spec the option object.
+ */
+ public function addOption(Option $spec)
+ {
+ $this->data[$spec->getId()] = $spec;
+ if ($spec->long) {
+ if (isset($this->longOptions[$spec->long])) {
+ throw new OptionConflictException('Option conflict: --'.$spec->long.' is already defined.');
+ }
+ $this->longOptions[$spec->long] = $spec;
+ }
+ if ($spec->short) {
+ if (isset($this->shortOptions[$spec->short])) {
+ throw new OptionConflictException('Option conflict: -'.$spec->short.' is already defined.');
+ }
+ $this->shortOptions[$spec->short] = $spec;
+ }
+ $this->options[] = $spec;
+ if (!$spec->long && !$spec->short) {
+ throw new Exception('Neither long option name nor short name is not given.');
+ }
+ }
+
+ public function getLongOption($name)
+ {
+ return isset($this->longOptions[ $name ]) ? $this->longOptions[ $name ] : null;
+ }
+
+ public function getShortOption($name)
+ {
+ return isset($this->shortOptions[ $name ]) ? $this->shortOptions[ $name ] : null;
+ }
+
+ /* Get spec by spec id */
+ public function get($id)
+ {
+ if (isset($this->data[$id])) {
+ return $this->data[$id];
+ } else if (isset($this->longOptions[$id])) {
+ return $this->longOptions[$id];
+ } else if (isset($this->shortOptions[$id])) {
+ return $this->shortOptions[$id];
+ }
+ }
+
+ public function find($name)
+ {
+ foreach ($this->options as $option) {
+ if ($option->short === $name || $option->long === $name) {
+ return $option;
+ }
+ }
+ }
+
+ public function size()
+ {
+ return count($this->data);
+ }
+
+ public function all()
+ {
+ return $this->data;
+ }
+
+ public function toArray()
+ {
+ $array = array();
+ foreach ($this->data as $k => $spec) {
+ $item = array();
+ if ($spec->long) {
+ $item['long'] = $spec->long;
+ }
+ if ($spec->short) {
+ $item['short'] = $spec->short;
+ }
+ $item['desc'] = $spec->desc;
+ $array[] = $item;
+ }
+
+ return $array;
+ }
+
+ public function keys()
+ {
+ return array_merge(array_keys($this->longOptions), array_keys($this->shortOptions));
+ }
+
+ public function count()
+ {
+ return count($this->data);
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->data);
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/OptionParser.php b/instafeed/vendor/corneltek/getoptionkit/src/OptionParser.php
new file mode 100755
index 0000000..da9cba5
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/OptionParser.php
@@ -0,0 +1,193 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+use Exception;
+use GetOptionKit\Exception\InvalidOptionException;
+use GetOptionKit\Exception\RequireValueException;
+
+class OptionParser
+{
+ public $specs;
+ public $longOptions;
+ public $shortOptions;
+
+ public function __construct(OptionCollection $specs)
+ {
+ $this->specs = $specs;
+ }
+
+ public function setSpecs(OptionCollection $specs)
+ {
+ $this->specs = $specs;
+ }
+
+ /**
+ * consume option value from current argument or from the next argument
+ *
+ * @return boolean next token consumed?
+ */
+ protected function consumeOptionToken(Option $spec, $arg, $next, & $success = false)
+ {
+ // Check options doesn't require next token before
+ // all options that require values.
+ if ($spec->isFlag()) {
+
+ if ($spec->isIncremental()) {
+ $spec->increaseValue();
+ } else {
+ $spec->setValue(true);
+ }
+ return 0;
+
+ } else if ($spec->isRequired()) {
+
+ if ($next && !$next->isEmpty() && !$next->anyOfOptions($this->specs)) {
+ $spec->setValue($next->arg);
+ return 1;
+ } else {
+ throw new RequireValueException("Option '{$arg->getOptionName()}' requires a value.");
+ }
+
+ } else if ($spec->isMultiple()) {
+
+ if ($next && !$next->isEmpty() && !$next->anyOfOptions($this->specs)) {
+ $this->pushOptionValue($spec, $arg, $next);
+ return 1;
+ }
+
+ } else if ($spec->isOptional() && $next && !$next->isEmpty() && !$next->anyOfOptions($this->specs)) {
+
+ $spec->setValue($next->arg);
+ return 1;
+
+ }
+ return 0;
+ }
+
+ /*
+ * push value to multipl value option
+ */
+ protected function pushOptionValue(Option $spec, $arg, $next)
+ {
+ if ($next && !$next->anyOfOptions($this->specs)) {
+ $spec->pushValue($next->arg);
+ }
+ }
+
+ /**
+ * preprocess the argv array
+ *
+ * - split option and option value
+ * - separate arguments after "--"
+ */
+ protected function preprocessingArguments(array $argv)
+ {
+ // preprocessing arguments
+ $newArgv = array();
+ $extra = array();
+ $afterDash = false;
+ foreach ($argv as $arg) {
+ if ($arg === '--') {
+ $afterDash = true;
+ continue;
+ }
+ if ($afterDash) {
+ $extra[] = $arg;
+ continue;
+ }
+
+ $a = new Argument($arg);
+ if ($a->anyOfOptions($this->specs) && $a->containsOptionValue()) {
+ list($opt, $val) = $a->splitAsOption();
+ array_push($newArgv, $opt, $val);
+ } else {
+ $newArgv[] = $arg;
+ }
+ }
+ return array($newArgv, $extra);
+ }
+
+ protected function fillDefaultValues(OptionCollection $opts, OptionResult $result)
+ {
+ // register option result from options with default value
+ foreach ($opts as $opt) {
+ if ($opt->value === null && $opt->defaultValue !== null) {
+ $opt->setValue($opt->getDefaultValue());
+ $result->set($opt->getId(), $opt);
+ }
+ }
+ }
+
+ /**
+ * @param array $argv
+ *
+ * @return OptionResult|Option[]
+ *
+ * @throws Exception\RequireValueException
+ * @throws Exception\InvalidOptionException
+ * @throws \Exception
+ */
+ public function parse(array $argv)
+ {
+ $result = new OptionResult();
+
+ list($argv, $extra) = $this->preprocessingArguments($argv);
+
+ $len = count($argv);
+
+ // some people might still pass only the option names here.
+ $first = new Argument($argv[0]);
+ if ($first->isOption()) {
+ throw new Exception('parse(argv) expects the first argument to be the program name.');
+ }
+
+ for ($i = 1; $i < $len; ++$i) {
+ $arg = new Argument($argv[$i]);
+
+ // if looks like not an option, push it to argument list.
+ // TODO: we might want to support argument with preceding dash (?)
+ if (!$arg->isOption()) {
+ $result->addArgument($arg);
+ continue;
+ }
+
+ // if the option is with extra flags,
+ // split the string, and insert into the argv array
+ if ($arg->withExtraFlagOptions()) {
+ $extra = $arg->extractExtraFlagOptions();
+ array_splice($argv, $i + 1, 0, $extra);
+ $argv[$i] = $arg->arg; // update argument to current argv list.
+ $len = count($argv); // update argv list length
+ }
+
+ $next = null;
+ if ($i + 1 < count($argv)) {
+ $next = new Argument($argv[$i + 1]);
+ }
+
+ $spec = $this->specs->get($arg->getOptionName());
+ if (!$spec) {
+ throw new InvalidOptionException('Invalid option: '.$arg);
+ }
+
+ // This if expr might be unnecessary, becase we have default mode - flag
+ // if ($spec->isRequired() || $spec->isMultiple() || $spec->isOptional() || $spec->isFlag()) {
+ $i += $this->consumeOptionToken($spec, $arg, $next);
+ $result->set($spec->getId(), $spec);
+ }
+
+ $this->fillDefaultValues($this->specs, $result);
+
+ return $result;
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/OptionPrinter/ConsoleOptionPrinter.php b/instafeed/vendor/corneltek/getoptionkit/src/OptionPrinter/ConsoleOptionPrinter.php
new file mode 100755
index 0000000..dba0a50
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/OptionPrinter/ConsoleOptionPrinter.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace GetOptionKit\OptionPrinter;
+
+use GetOptionKit\OptionCollection;
+use GetOptionKit\Option;
+
+class ConsoleOptionPrinter implements OptionPrinter
+{
+ public $screenWidth = 78;
+
+ /**
+ * Render readable spec.
+ */
+ public function renderOption(Option $opt)
+ {
+ $c1 = '';
+ if ($opt->short && $opt->long) {
+ $c1 = sprintf('-%s, --%s', $opt->short, $opt->long);
+ } else if ($opt->short) {
+ $c1 = sprintf('-%s', $opt->short);
+ } else if ($opt->long) {
+ $c1 = sprintf('--%s', $opt->long);
+ }
+ $c1 .= $opt->renderValueHint();
+
+ return $c1;
+ }
+
+ /**
+ * render option descriptions.
+ *
+ * @return string output
+ */
+ public function render(OptionCollection $options)
+ {
+ # echo "* Available options:\n";
+ $lines = array();
+ foreach ($options as $option) {
+ $c1 = $this->renderOption($option);
+ $lines[] = "\t".$c1;
+ $lines[] = wordwrap("\t\t".$option->desc, $this->screenWidth, "\n\t\t"); # wrap text
+ $lines[] = '';
+ }
+
+ return implode("\n", $lines);
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/OptionPrinter/OptionPrinter.php b/instafeed/vendor/corneltek/getoptionkit/src/OptionPrinter/OptionPrinter.php
new file mode 100755
index 0000000..4ed0c70
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/OptionPrinter/OptionPrinter.php
@@ -0,0 +1,12 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+use ArrayIterator;
+use ArrayAccess;
+use IteratorAggregate;
+use Countable;
+
+/**
+ * Define the getopt parsing result.
+ *
+ * create option result from array()
+ *
+ * OptionResult::create($spec, array(
+ * 'key' => 'value'
+ * ), array( ... arguments ... ) );
+ */
+class OptionResult
+ implements IteratorAggregate, ArrayAccess, Countable
+{
+ /**
+ * @var array option specs, key => Option object
+ * */
+ public $keys = array();
+
+ private $currentKey;
+
+ /* arguments */
+ public $arguments = array();
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->keys);
+ }
+
+ public function count()
+ {
+ return count($this->keys);
+ }
+
+ public function merge(OptionResult $a)
+ {
+ $this->keys = array_merge($this->keys, $a->keys);
+ $this->arguments = array_merge($this->arguments, $a->arguments);
+ }
+
+ public function __isset($key)
+ {
+ return isset($this->keys[$key]);
+ }
+
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ public function get($key)
+ {
+ if (isset($this->keys[$key])) {
+ return $this->keys[$key]->getValue();
+ }
+
+ // verifying if we got a camelCased key: http://stackoverflow.com/a/7599674/102960
+ // get $options->baseDir as $option->{'base-dir'}
+ $parts = preg_split('/(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/', $key);
+ if (sizeof($parts) > 1) {
+ $key = implode('-', array_map('strtolower', $parts));
+ }
+ if (isset($this->keys[$key])) {
+ return $this->keys[$key]->getValue();
+ }
+ }
+
+ public function __set($key, $value)
+ {
+ $this->keys[ $key ] = $value;
+ }
+
+ public function has($key)
+ {
+ return isset($this->keys[ $key ]);
+ }
+
+ public function set($key, Option $value)
+ {
+ $this->keys[ $key ] = $value;
+ }
+
+ public function addArgument(Argument $arg)
+ {
+ $this->arguments[] = $arg;
+ }
+
+ public function getArguments()
+ {
+ return array_map(function ($e) { return $e->__toString(); }, $this->arguments);
+ }
+
+ public function offsetSet($name, $value)
+ {
+ $this->keys[ $name ] = $value;
+ }
+
+ public function offsetExists($name)
+ {
+ return isset($this->keys[ $name ]);
+ }
+
+ public function offsetGet($name)
+ {
+ return $this->keys[ $name ];
+ }
+
+ public function offsetUnset($name)
+ {
+ unset($this->keys[$name]);
+ }
+
+ public function toArray()
+ {
+ $array = array();
+ foreach ($this->keys as $key => $option) {
+ $array[ $key ] = $option->getValue();
+ }
+
+ return $array;
+ }
+
+ public static function create($specs, array $values = array(), array $arguments = null)
+ {
+ $new = new self();
+ foreach ($specs as $spec) {
+ $id = $spec->getId();
+ if (isset($values[$id])) {
+ $new->$id = $spec;
+ $spec->setValue($values[$id]);
+ }
+ if ($arguments) {
+ foreach ($arguments as $arg) {
+ $new->addArgument(new Argument($arg));
+ }
+ }
+ }
+
+ return $new;
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/ValueType/BaseType.php b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/BaseType.php
new file mode 100755
index 0000000..98477bf
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/BaseType.php
@@ -0,0 +1,34 @@
+option = $option;
+ }
+ }
+
+ /**
+ * Test a value to see if it fit the type.
+ *
+ * @param mixed $value
+ */
+ abstract public function test($value);
+
+ /**
+ * Parse a string value into it's type value.
+ *
+ * @param mixed $value
+ */
+ abstract public function parse($value);
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/ValueType/BoolType.php b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/BoolType.php
new file mode 100755
index 0000000..e2922d8
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/BoolType.php
@@ -0,0 +1,10 @@
+ DateTime::ATOM,
+ );
+
+ public function test($value)
+ {
+ return DateTime::createFromFormat($this->option['format'], $value) !== false;
+ }
+
+ public function parse($value)
+ {
+ return DateTime::createFromFormat($this->option['format'], $value);
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/ValueType/DateType.php b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/DateType.php
new file mode 100755
index 0000000..d168ef4
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/DateType.php
@@ -0,0 +1,20 @@
+ 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public function parse($value)
+ {
+ return date_parse($value);
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/ValueType/DirType.php b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/DirType.php
new file mode 100755
index 0000000..de28279
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/DirType.php
@@ -0,0 +1,18 @@
+option = $option;
+ }
+
+ public function test($value)
+ {
+ return preg_match($this->option, $value) !== 0;
+ }
+
+ public function parse($value)
+ {
+ preg_match($this->option, $value, $this->matches);
+ return strval($value);
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/src/ValueType/StringType.php b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/StringType.php
new file mode 100755
index 0000000..cf41b7b
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/src/ValueType/StringType.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+use GetOptionKit\Argument;
+class ArgumentTest extends \PHPUnit\Framework\TestCase
+{
+ function test()
+ {
+ $arg = new Argument( '--option' );
+ $this->assertTrue( $arg->isLongOption() );
+ $this->assertFalse( $arg->isShortOption() );
+ $this->assertEquals('option' , $arg->getOptionName());
+
+ $this->assertEquals(null, $arg->getOptionValue());
+ }
+
+ function test2()
+ {
+ $arg = new Argument('--option=value');
+ $this->assertNotNull( $arg->containsOptionValue() );
+ $this->assertEquals('value' , $arg->getOptionValue());
+ $this->assertEquals('option' , $arg->getOptionName());
+ }
+
+ function test3()
+ {
+ $arg = new Argument( '-abc' );
+ $this->assertNotNull( $arg->withExtraFlagOptions() );
+
+ $args = $arg->extractExtraFlagOptions();
+ $this->assertNotNull( $args );
+ $this->assertCount( 2, $args );
+
+ $this->assertEquals( '-b', $args[0] );
+ $this->assertEquals( '-c', $args[1] );
+ $this->assertEquals( '-a', $arg->arg);
+ }
+
+ function testZeroValue()
+ {
+ $arg = new Argument( '0' );
+ $this->assertFalse( $arg->isShortOption() );
+ $this->assertFalse( $arg->isLongOption() );
+ $this->assertFalse( $arg->isEmpty() );
+ }
+}
+
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/tests/ContinuousOptionParserTest.php b/instafeed/vendor/corneltek/getoptionkit/tests/ContinuousOptionParserTest.php
new file mode 100755
index 0000000..b1c2ad1
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/tests/ContinuousOptionParserTest.php
@@ -0,0 +1,335 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace tests\GetOptionKit;
+use GetOptionKit\ContinuousOptionParser;
+use GetOptionKit\OptionCollection;
+
+class ContinuousOptionParserTest extends \PHPUnit\Framework\TestCase
+{
+
+ public function testOptionCollection()
+ {
+ $specs = new OptionCollection;
+ $specVerbose = $specs->add('v|verbose');
+ $specColor = $specs->add('c|color');
+ $specDebug = $specs->add('d|debug');
+ }
+
+
+
+ public function argumentProvider()
+ {
+ return [
+ [
+ ['program','subcommand1', 'arg1', 'arg2', 'arg3', 'subcommand2', '-b', 1, 'subcommand3', '-b', 2],
+ [
+ 'args' => ['arg1', 'arg2', 'arg3']
+ ],
+ ],
+ [
+ ['program','-v', '-c', 'subcommand1', '--as', 99, 'arg1', 'arg2', 'arg3'],
+ [
+ 'app' => ['verbose' => true ],
+ 'args' => ['arg1', 'arg2', 'arg3']
+ ],
+ ],
+ [
+ ['program','-v', '-c', 'subcommand1', '--as', 99, 'arg1', 'arg2', 'arg3', '--','zz','xx','vv'],
+ [
+ 'app' => ['verbose' => true],
+ 'args' => ['arg1', 'arg2', 'arg3']
+ ],
+ ],
+ ];
+ }
+
+
+
+ /**
+ * @dataProvider argumentProvider
+ */
+ public function testParseSubCommandOptions($argv, $expected)
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('as:');
+ $cmdspecs->add('b:');
+ $cmdspecs->add('c:');
+ $cmdspecs->add('def:')->isa('number')->defaultValue(3);
+
+ $parser = new ContinuousOptionParser( $appspecs );
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => clone $cmdspecs,
+ 'subcommand2' => clone $cmdspecs,
+ 'subcommand3' => clone $cmdspecs,
+ );
+ $subcommand_options = array();
+
+ // $argv = explode(' ','program -v -c subcommand1 --as 99 arg1 arg2 arg3 -- zz xx vv');
+ // $argv = explode(' ','program subcommand1 -a 1 subcommand2 -a 2 subcommand3 -a 3 arg1 arg2 arg3');
+ $app_options = $parser->parse( $argv );
+ $arguments = array();
+ while (! $parser->isEnd()) {
+ if (!empty($subcommands) && $parser->getCurrentArgument() == $subcommands[0]) {
+ $parser->advance();
+ $subcommand = array_shift($subcommands);
+ $parser->setSpecs($subcommand_specs[$subcommand]);
+ $subcommand_options[$subcommand] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+ $this->assertSame($expected['args'], $arguments);
+ if (isset($expected['app'])) {
+ foreach ($expected['app'] as $k => $v) {
+ $this->assertEquals($v, $app_options->get($k));
+ }
+ }
+
+ // $this->assertEquals(99, $subcommand_options['subcommand1']->as);
+ }
+
+
+
+ public function testParser3()
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('n|name:=string');
+ $cmdspecs->add('p|phone:=string');
+ $cmdspecs->add('a|address:=string');
+
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => $cmdspecs,
+ 'subcommand2' => $cmdspecs,
+ 'subcommand3' => $cmdspecs,
+ );
+ $subcommand_options = array();
+ $arguments = array();
+
+ $argv = explode(' ','program -v -d -c subcommand1 --name=c9s --phone=123123123 --address=somewhere arg1 arg2 arg3');
+ $parser = new ContinuousOptionParser( $appspecs );
+ $app_options = $parser->parse( $argv );
+ while (! $parser->isEnd()) {
+ if (@$subcommands[0] && $parser->getCurrentArgument() == $subcommands[0]) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs( $subcommand_specs[$subcommand] );
+ $subcommand_options[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+
+ $this->assertCount(3, $arguments);
+ $this->assertEquals('arg1', $arguments[0]);
+ $this->assertEquals('arg2', $arguments[1]);
+ $this->assertEquals('arg3', $arguments[2]);
+
+ $this->assertNotNull($subcommand_options['subcommand1']);
+ $this->assertEquals('c9s', $subcommand_options['subcommand1']->name );
+ $this->assertEquals('123123123', $subcommand_options['subcommand1']->phone );
+ $this->assertEquals('somewhere', $subcommand_options['subcommand1']->address );
+ }
+
+
+ /* test parser without options */
+ function testParser4()
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('a:'); // required
+ $cmdspecs->add('b?'); // optional
+ $cmdspecs->add('c+'); // multiple (required)
+
+
+
+ $parser = new ContinuousOptionParser( $appspecs );
+ $this->assertNotNull( $parser );
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => clone $cmdspecs,
+ 'subcommand2' => clone $cmdspecs,
+ 'subcommand3' => clone $cmdspecs,
+ );
+ $subcommand_options = array();
+
+ $argv = explode(' ','program subcommand1 subcommand2 subcommand3 -a a -b b -c c');
+ $app_options = $parser->parse( $argv );
+ $arguments = array();
+ while( ! $parser->isEnd() ) {
+ if( @$subcommands[0] && $parser->getCurrentArgument() == $subcommands[0] ) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs( $subcommand_specs[$subcommand] );
+ $subcommand_options[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+
+ $this->assertNotNull( $subcommand_options );
+ $this->assertNotNull( $subcommand_options['subcommand1'] );
+ $this->assertNotNull( $subcommand_options['subcommand2'] );
+ $this->assertNotNull( $subcommand_options['subcommand3'] );
+
+ $r = $subcommand_options['subcommand3'];
+ $this->assertNotNull( $r );
+
+
+
+ $this->assertNotNull( $r->a , 'option a' );
+ $this->assertNotNull( $r->b , 'option b' );
+ $this->assertNotNull( $r->c , 'option c' );
+
+ $this->assertEquals( 'a', $r->a );
+ $this->assertEquals( 'b', $r->b );
+ $this->assertEquals( 'c', $r->c[0] );
+ }
+
+ /* test parser without options */
+ function testParser5()
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('a:');
+ $cmdspecs->add('b');
+ $cmdspecs->add('c');
+
+ $parser = new ContinuousOptionParser( $appspecs );
+ $this->assertNotNull( $parser );
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => clone $cmdspecs,
+ 'subcommand2' => clone $cmdspecs,
+ 'subcommand3' => clone $cmdspecs,
+ );
+ $subcommand_options = array();
+
+ $argv = explode(' ','program subcommand1 -a 1 subcommand2 -a 2 subcommand3 -a 3 arg1 arg2 arg3');
+ $app_options = $parser->parse( $argv );
+ $arguments = array();
+ while (! $parser->isEnd()) {
+ if (!empty($subcommands) && $parser->getCurrentArgument() == $subcommands[0] ) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs($subcommand_specs[$subcommand]);
+ $subcommand_options[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+
+ $this->assertEquals( 'arg1', $arguments[0] );
+ $this->assertEquals( 'arg2', $arguments[1] );
+ $this->assertEquals( 'arg3', $arguments[2] );
+ $this->assertNotNull( $subcommand_options );
+
+ $this->assertEquals(1, $subcommand_options['subcommand1']->a);
+ $this->assertNotNull( 2, $subcommand_options['subcommand2']->a );
+ $this->assertNotNull( 3, $subcommand_options['subcommand3']->a );
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionException
+ */
+ public function testParseInvalidOptionException()
+ {
+ $parser = new ContinuousOptionParser(new OptionCollection);
+ $parser->parse(array('app','--foo'));
+ $arguments = array();
+ while (!$parser->isEnd())
+ {
+ $arguments[] = $parser->getCurrentArgument();
+ $parser->advance();
+ }
+ }
+
+
+
+ public function testMultipleShortOption()
+ {
+ $options = new OptionCollection;
+ $options->add("a");
+ $options->add("b");
+ $options->add("c");
+
+ $parser = new ContinuousOptionParser($options);
+
+ $result = $parser->parse(array('app', '-ab', 'foo', 'bar'));
+ while (!$parser->isEnd())
+ {
+ $arguments[] = $parser->getCurrentArgument();
+ $parser->advance();
+ }
+
+ $this->assertTrue($result->keys["a"]->value);
+ $this->assertTrue($result->keys["b"]->value);
+ }
+
+ public function testIncrementalValue()
+ {
+ $options = new OptionCollection;
+ $options->add("v|verbose")->incremental();
+ $parser = new ContinuousOptionParser($options);
+ $result = $parser->parse(array('app', '-vvv'));
+ $this->assertEquals(3, $result->keys["verbose"]->value);
+ }
+
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionException
+ */
+ public function testUnknownOption()
+ {
+ $options = new OptionCollection;
+ $options->add("v|verbose");
+ $parser = new ContinuousOptionParser($options);
+ $result = $parser->parse(array('app', '-b'));
+ }
+
+ /**
+ * @expectedException LogicException
+ */
+ public function testAdvancedOutOfBounds()
+ {
+ $options = new OptionCollection;
+ $options->add("v|verbose");
+ $parser = new ContinuousOptionParser($options);
+ $result = $parser->parse(array('app', '-v'));
+ $parser->advance();
+ }
+
+}
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/tests/OptionCollectionTest.php b/instafeed/vendor/corneltek/getoptionkit/tests/OptionCollectionTest.php
new file mode 100755
index 0000000..542b293
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/tests/OptionCollectionTest.php
@@ -0,0 +1,46 @@
+add($o = new Option('v|verbose'));
+ $this->assertSame($o, $opts->getLongOption('verbose'));
+ $this->assertSame($o, $opts->getShortOption('v'));
+ }
+
+
+ /**
+ * @expectedException LogicException
+ */
+ public function testAddInvalidOption()
+ {
+ $opts = new OptionCollection;
+ $opts->add(123);
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\OptionConflictException
+ */
+ public function testOptionConflictShort()
+ {
+ $opts = new OptionCollection;
+ $opts->add('r|repeat');
+ $opts->add('t|time');
+ $opts->add('r|regex');
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\OptionConflictException
+ */
+ public function testOptionConflictLong()
+ {
+ $opts = new OptionCollection;
+ $opts->add('r|repeat');
+ $opts->add('t|time');
+ $opts->add('c|repeat');
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/tests/OptionParserTest.php b/instafeed/vendor/corneltek/getoptionkit/tests/OptionParserTest.php
new file mode 100755
index 0000000..5d5a9b9
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/tests/OptionParserTest.php
@@ -0,0 +1,477 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+use GetOptionKit\InvalidOptionValue;
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\Option;
+
+class OptionParserTest extends \PHPUnit\Framework\TestCase
+{
+ public $parser;
+ public $specs;
+
+ public function setUp()
+ {
+ $this->specs = new OptionCollection;
+ $this->parser = new OptionParser($this->specs);
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testInvalidOption()
+ {
+ $options = new OptionCollection;
+ $options->addOption(new Option(0));
+ }
+
+
+ public function testResultArrayAccessor()
+ {
+ $options = new OptionCollection;
+ $options->add('n|nice:' , 'I take negative value');
+ $parser = new OptionParser($options);
+ $result = $parser->parse(array('a', '-n', '-1', '--', '......'));
+
+ $this->assertTrue(isset($result->nice));
+ $this->assertTrue($result->has('nice'));
+ $this->assertTrue(isset($result['nice']));
+ $this->assertEquals(-1, $result['nice']->value);
+
+ $res = clone $result['nice'];
+ $res->value = 10;
+ $result['nice'] = $res;
+ $this->assertEquals(10, $result['nice']->value);
+
+ unset($result['nice']);
+ }
+
+ public function testCamelCaseOptionName()
+ {
+ $this->specs->add('base-dir:=dir' , 'I take path');
+ $result = $this->parser->parse(array('a', '--base-dir', 'src'));
+ $this->assertInstanceOf('SplFileInfo', $result->baseDir);
+ }
+
+ public function testOptionWithNegativeValue()
+ {
+ $this->specs->add('n|nice:' , 'I take negative value');
+ $result = $this->parser->parse(array('a', '-n', '-1'));
+ $this->assertEquals(-1, $result->nice);
+ }
+
+ public function testShortOptionName()
+ {
+ $this->specs->add('f:' , 'file');
+ $result = $this->parser->parse(array('a', '-f', 'aaa'));
+ $this->assertEquals('aaa',$result['f']->getValue());
+ }
+
+ public function testOptionWithShortNameAndLongName()
+ {
+ $this->specs->add( 'f|foo' , 'flag' );
+ $result = $this->parser->parse(array('a', '-f'));
+ $this->assertTrue($result->foo);
+
+ $result = $this->parser->parse(array('a', '--foo'));
+ $this->assertTrue($result->foo);
+ }
+
+ public function testSpec()
+ {
+ $options = new OptionCollection;
+ $options->add( 'f|foo:' , 'option require value' );
+ $options->add( 'b|bar+' , 'option with multiple value' );
+ $options->add( 'z|zoo?' , 'option with optional value' );
+ $options->add( 'v|verbose' , 'verbose message' );
+ $options->add( 'd|debug' , 'debug message' );
+ $this->assertEquals(5, $options->size());
+ $this->assertEquals(5, count($options));
+
+
+ $opt = $options->get('foo');
+ $this->assertTrue($opt->isRequired());
+
+ $opt = $options->get('bar');
+ $this->assertTrue( $opt->isMultiple() );
+
+ $opt = $options->get('zoo');
+ $this->assertTrue( $opt->isOptional() );
+
+ $opt = $options->get( 'debug' );
+ $this->assertNotNull( $opt );
+ $this->assertInstanceOf('GetOptionKit\\Option', $opt);
+ $this->assertEquals('debug', $opt->long);
+ $this->assertEquals('d', $opt->short);
+ $this->assertTrue($opt->isFlag());
+
+ return $options;
+ }
+
+ /**
+ * @depends testSpec
+ */
+ public function testOptionFinder($options)
+ {
+ $this->assertNotNull($options->find('f'));
+ $this->assertNotNull($options->find('foo'));
+ $this->assertNull($options->find('xyz'));
+ }
+
+ public function testRequire()
+ {
+ $this->specs->add( 'f|foo:' , 'option require value' );
+ $this->specs->add( 'b|bar+' , 'option with multiple value' );
+ $this->specs->add( 'z|zoo?' , 'option with optional value' );
+ $this->specs->add( 'v|verbose' , 'verbose message' );
+ $this->specs->add( 'd|debug' , 'debug message' );
+
+ $firstExceptionRaised = false;
+ $secondExceptionRaised = false;
+
+ // option required a value should throw an exception
+ try {
+ $result = $this->parser->parse( array('a', '-f' , '-v' , '-d' ) );
+ }
+ catch (Exception $e) {
+ $firstExceptionRaised = true;
+ }
+
+ // even if only one option presented in args array
+ try {
+ $result = $this->parser->parse(array('a','-f'));
+ } catch (Exception $e) {
+ $secondExceptionRaised = true;
+ }
+ if ($firstExceptionRaised && $secondExceptionRaised) {
+ return;
+ }
+ $this->fail('An expected exception has not been raised.');
+ }
+
+ public function testMultiple()
+ {
+ $opt = new OptionCollection;
+ $opt->add( 'b|bar+' , 'option with multiple value' );
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app -b 1 -b 2 --bar 3'));
+ $this->assertNotNull($result->bar);
+ $this->assertCount(3,$result->bar);
+ }
+
+
+ public function testMultipleNumber()
+ {
+ $opt = new OptionCollection;
+ $opt->add('b|bar+=number' , 'option with multiple value');
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app --bar 1 --bar 2 --bar 3'));
+ $this->assertNotNull($result->bar);
+ $this->assertCount(3,$result->bar);
+ $this->assertSame(array(1,2,3),$result->bar);
+ }
+
+ public function testSimpleOptionWithDefaultValue()
+ {
+ $opts = new OptionCollection;
+ $opts->add('p|proc=number' , 'option with required value')
+ ->defaultValue(10)
+ ;
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app'));
+ $this->assertEquals(10, $result['proc']->value);
+ }
+
+ public function testOptionalOptionWithDefaultValue()
+ {
+ $opts = new OptionCollection;
+ $opts->add('p|proc?=number' , 'option with required value')
+ ->defaultValue(10)
+ ;
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app --proc'));
+ $this->assertEquals(10, $result['proc']->value);
+ }
+
+ public function testMultipleString()
+ {
+ $opts = new OptionCollection;
+ $opts->add('b|bar+=string' , 'option with multiple value');
+ $bar = $opts->get('bar');
+ $this->assertNotNull($bar);
+ $this->assertTrue($bar->isMultiple());
+ $this->assertTrue($bar->isType('string'));
+ $this->assertFalse($bar->isType('number'));
+
+
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app --bar lisa --bar mary --bar john a b c'));
+ $this->assertNotNull($result->bar);
+ $this->assertCount(3,$result->bar);
+ $this->assertSame(array('lisa', 'mary', 'john'),$result->bar);
+ $this->assertSame(array('a','b','c'), $result->getArguments());
+ }
+
+ public function testParseIncrementalOption()
+ {
+ $opts = new OptionCollection;
+ $opts->add('v|verbose' , 'verbose')
+ ->isa("number")
+ ->incremental();
+
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app -vvv arg1 arg2'));
+ $this->assertInstanceOf('GetOptionKit\Option',$result['verbose']);
+ $this->assertNotNull($result['verbose']);
+ $this->assertEquals(3, $result['verbose']->value);
+ }
+
+
+ /**
+ * @expectedException Exception
+ */
+ public function testIntegerTypeNonNumeric()
+ {
+ $opt = new OptionCollection;
+ $opt->add( 'b|bar:=number' , 'option with integer type' );
+
+ $parser = new OptionParser($opt);
+ $spec = $opt->get('bar');
+ $this->assertTrue($spec->isTypeNumber());
+
+ // test non numeric
+ $result = $parser->parse(explode(' ','app -b test'));
+ $this->assertNotNull($result->bar);
+ }
+
+
+ public function testIntegerTypeNumericWithoutEqualSign()
+ {
+ $opt = new OptionCollection;
+ $opt->add('b|bar:=number', 'option with integer type');
+
+ $spec = $opt->get('bar');
+ $this->assertTrue($spec->isTypeNumber());
+
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app -b 123123'));
+ $this->assertNotNull($result);
+ $this->assertEquals(123123, $result->bar);
+ }
+
+ public function testIntegerTypeNumericWithEqualSign()
+ {
+ $opt = new OptionCollection;
+ $opt->add('b|bar:=number' , 'option with integer type');
+
+ $spec = $opt->get('bar');
+ $this->assertTrue($spec->isTypeNumber());
+
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app -b=123123'));
+ $this->assertNotNull($result);
+ $this->assertNotNull($result->bar);
+ $this->assertEquals(123123, $result->bar);
+ }
+
+ public function testStringType()
+ {
+ $this->specs->add( 'b|bar:=string' , 'option with type' );
+
+ $spec = $this->specs->get('bar');
+
+ $result = $this->parser->parse(explode(' ','app -b text arg1 arg2 arg3'));
+ $this->assertNotNull($result->bar);
+
+ $result = $this->parser->parse(explode(' ','app -b=text arg1 arg2 arg3'));
+ $this->assertNotNull($result->bar);
+
+ $args = $result->getArguments();
+ $this->assertNotEmpty($args);
+ $this->assertCount(3,$args);
+ $this->assertEquals('arg1', $args[0]);
+ $this->assertEquals('arg2', $args[1]);
+ $this->assertEquals('arg3', $args[2]);
+ }
+
+ public function testStringQuoteOptionValue()
+ {
+ $opts = new OptionCollection();
+ $opts->add('f|foo:' , 'option requires a value.');
+ $parser = new OptionParser($opts);
+ $res = $parser->parse(['app','--foo=aa bb cc']);
+ $this->assertEquals('aa bb cc', $res->get('foo'));
+ }
+
+ public function testSpec2()
+ {
+ $this->specs->add('long' , 'long option name only.');
+ $this->specs->add('a' , 'short option name only.');
+ $this->specs->add('b' , 'short option name only.');
+ $this->assertNotNull($this->specs->all());
+ $this->assertNotNull($this->specs);
+ $this->assertNotNull($result = $this->parser->parse(explode(' ','app -a -b --long')) );
+ $this->assertNotNull($result->a);
+ $this->assertNotNull($result->b);
+ }
+
+
+ public function testSpecCollection()
+ {
+ $this->specs->add( 'f|foo:' , 'option requires a value.' );
+ $this->specs->add( 'b|bar+' , 'option with multiple value.' );
+ $this->specs->add( 'z|zoo?' , 'option with optional value.' );
+ $this->specs->add( 'v|verbose' , 'verbose message.' );
+ $this->specs->add( 'd|debug' , 'debug message.' );
+ $this->specs->add( 'long' , 'long option name only.' );
+ $this->specs->add( 's' , 'short option name only.' );
+
+ $this->assertNotNull( $this->specs->all() );
+ $this->assertNotNull( $this->specs );
+
+ $this->assertCount( 7 , $array = $this->specs->toArray() );
+ $this->assertNotEmpty( isset($array[0]['long'] ));
+ $this->assertNotEmpty( isset($array[0]['short'] ));
+ $this->assertNotEmpty( isset($array[0]['desc'] ));
+ }
+
+ public function optionTestProvider()
+ {
+ return array(
+ array( 'foo', 'simple boolean option', 'foo', true,
+ [['a','--foo','a', 'b', 'c']]
+ ),
+ array( 'f|foo', 'simple boolean option', 'foo', true,
+ [['a','--foo'], ['a','-f']]
+ ),
+ array( 'f|foo:=string', 'string option', 'foo', 'xxx',
+ [['a','--foo','xxx'], ['a','-f', 'xxx']]
+ ),
+ array( 'f|foo:=string', 'string option', 'foo', 'xxx',
+ [['a','b', 'c', '--foo','xxx'], ['a', 'a', 'b', 'c', '-f', 'xxx']]
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider optionTestProvider
+ */
+ public function test($specString, $desc, $key, $expectedValue, array $argvList)
+ {
+ $opts = new OptionCollection();
+ $opts->add($specString, $desc);
+ $parser = new OptionParser($opts);
+ foreach ($argvList as $argv) {
+ $res = $parser->parse($argv);
+ $this->assertSame($expectedValue, $res->get($key));
+ }
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testParseWithoutProgramName()
+ {
+ $parser = new OptionParser(new OptionCollection);
+ $parser->parse(array('--foo'));
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionException
+ */
+ public function testParseInvalidOptionException()
+ {
+ $parser = new OptionParser(new OptionCollection);
+ $parser->parse(array('app','--foo'));
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\RequireValueException
+ */
+ public function testParseOptionRequireValueException()
+ {
+ $options = new OptionCollection;
+ $options->add('name:=string', 'name');
+
+ $parser = new OptionParser($options);
+ $parser->parse(array('app','--name'));
+ }
+
+
+
+ public function testMore()
+ {
+ $this->specs->add('f|foo:' , 'option require value' );
+ $this->specs->add('b|bar+' , 'option with multiple value' );
+ $this->specs->add('z|zoo?' , 'option with optional value' );
+ $this->specs->add('v|verbose' , 'verbose message' );
+ $this->specs->add('d|debug' , 'debug message' );
+
+ $result = $this->parser->parse( array('a', '-f' , 'foo value' , '-v' , '-d' ) );
+ $this->assertNotNull($result->foo);
+ $this->assertNotNull($result->verbose);
+ $this->assertNotNull($result->debug);
+ $this->assertEquals( 'foo value', $result->foo );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+
+ foreach ($result as $k => $v) {
+ $this->assertTrue(in_array($k, ['foo','bar','zoo','verbose', 'debug']));
+ $this->assertInstanceOf('GetOptionKit\\Option', $v);
+ }
+ $this->assertSame([
+ 'foo' => 'foo value',
+ 'verbose' => true,
+ 'debug' => true
+ ], $result->toArray());
+
+ $result = $this->parser->parse( array('a', '-f=foo value' , '-v' , '-d' ) );
+ $this->assertNotNull( $result );
+ $this->assertNotNull( $result->foo );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+
+ $this->assertEquals( 'foo value', $result->foo );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+
+ $result = $this->parser->parse( array('a', '-vd' ) );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+ }
+
+ public function testParseAcceptsValidOption()
+ {
+ $this->specs
+ ->add('f:foo', 'test option')
+ ->validator(function($value) {
+ return $value === 'valid-option';
+ });
+
+ $result = $this->parser->parse(array('a', '-f' , 'valid-option'));
+
+ $this->assertArrayHasKey('f', $result);
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionValueException
+ */
+ public function testParseThrowsExceptionOnInvalidOption()
+ {
+ $this->specs
+ ->add('f:foo', 'test option')
+ ->validator(function($value) {
+ return $value === 'valid-option';
+ });
+
+ $this->parser->parse(array('a', '-f' , 'not-a-valid-option'));
+ }
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/tests/OptionPrinter/ConsoleOptionPrinterTest.php b/instafeed/vendor/corneltek/getoptionkit/tests/OptionPrinter/ConsoleOptionPrinterTest.php
new file mode 100755
index 0000000..2f06767
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/tests/OptionPrinter/ConsoleOptionPrinterTest.php
@@ -0,0 +1,32 @@
+add('f|foo:', 'option requires a value.' )
+ ->isa('String');
+
+ $options->add('b|bar+', 'option with multiple value.' )
+ ->isa('Number');
+
+ $options->add('z|zoo?', 'option with optional value.' )
+ ->isa('Boolean')
+ ;
+
+ $options->add('n', 'n flag' );
+
+ $options->add('verbose', 'verbose');
+
+ $options->add('o|output?', 'option with optional value.' )
+ ->isa('File')
+ ->defaultValue('output.txt')
+ ;
+ $printer = new ConsoleOptionPrinter;
+ $output = $printer->render($options);
+ }
+
+}
diff --git a/instafeed/vendor/corneltek/getoptionkit/tests/OptionResultTest.php b/instafeed/vendor/corneltek/getoptionkit/tests/OptionResultTest.php
new file mode 100755
index 0000000..01be96d
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/tests/OptionResultTest.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+class OptionResultTest extends \PHPUnit\Framework\TestCase
+{
+
+ function testOption()
+ {
+ $option = new \GetOptionKit\OptionResult;
+ $this->assertNotNull( $option );
+
+ $specs = new \GetOptionKit\OptionCollection;
+ $specs->add('name:','name');
+ $result = \GetOptionKit\OptionResult::create($specs,array( 'name' => 'c9s' ),array( 'arg1' ));
+ $this->assertNotNull( $result );
+ $this->assertNotNull( $result->arguments );
+ $this->assertNotNull( $result->name );
+ $this->assertEquals( 'c9s', $result->name );
+ $this->assertEquals( $result->arguments[0] , 'arg1' );
+ }
+
+}
+
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/tests/OptionTest.php b/instafeed/vendor/corneltek/getoptionkit/tests/OptionTest.php
new file mode 100755
index 0000000..7b06f1d
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/tests/OptionTest.php
@@ -0,0 +1,227 @@
+assertNotNull($opt);
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testInvalidOptionSpec()
+ {
+ new Option('....');
+ }
+
+ public function testValueName()
+ {
+ $opt = new Option('z');
+ $opt->defaultValue(10);
+ $opt->valueName('priority');
+ $this->assertEquals('[=priority]', $opt->renderValueHint());
+ $this->assertEquals('-z[=priority]', $opt->renderReadableSpec());
+ }
+
+
+ public function testDefaultValue()
+ {
+ $opt = new Option('z');
+ $opt->defaultValue(10);
+ $this->assertEquals(10, $opt->getValue());
+ $this->assertEquals('-z[=10]',$opt->renderReadableSpec(true));
+ }
+
+ public function testBackwardCompatibleBoolean()
+ {
+ $opt = new Option('scope');
+ $opt->isa('bool');
+ $this->assertEquals('boolean', $opt->isa);
+ $this->assertEquals('--scope=',$opt->renderReadableSpec(true));
+ }
+
+
+
+ public function validatorProvider()
+ {
+ return [
+ [function($a) { return in_array($a, ['public', 'private']); }],
+ [function($a) { return [in_array($a, ['public', 'private']), "message"]; }]
+ ];
+ }
+
+ /**
+ * @dataProvider validatorProvider
+ */
+ public function testValidator($cb)
+ {
+ $opt = new Option('scope');
+ $opt->validator($cb);
+ $ret = $opt->validate('public');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('private');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('foo');
+ $this->assertFalse($ret[0]);
+ $this->assertEquals('--scope', $opt->renderReadableSpec(true));
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testInvalidTypeClass()
+ {
+ $opt = new Option('scope');
+ $opt->isa('SomethingElse');
+ $class = $opt->getTypeClass();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testValidatorReturnValue()
+ {
+ $opt = new Option('scope');
+ $opt->validator(function($val) {
+ return 123454;
+ });
+ $ret = $opt->validate('public');
+ }
+
+ public function testOptionWithoutValidator()
+ {
+ $opt = new Option('scope');
+ $ret = $opt->validate('public');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('private');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('foo');
+ $this->assertTrue($ret[0]);
+ $this->assertEquals('--scope',$opt->renderReadableSpec(true));
+ }
+
+
+
+
+ public function testSuggestionsCallback()
+ {
+ $opt = new Option('scope');
+ $this->assertEmpty($opt->getSuggestions());
+
+ $opt->suggestions(function() {
+ return ['public', 'private'];
+ });
+ $this->assertNotEmpty($opt->getSuggestions());
+ $this->assertSame(['public', 'private'],$opt->getSuggestions());
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+
+ $this->assertEquals('--scope=[public,private]',$opt->renderReadableSpec(true));
+ }
+
+ public function testSuggestions()
+ {
+ $opt = new Option('scope');
+ $opt->suggestions(['public', 'private']);
+ $this->assertNotEmpty($opt->getSuggestions());
+ $this->assertSame(['public', 'private'],$opt->getSuggestions());
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+
+ $this->assertEquals('--scope=[public,private]',$opt->renderReadableSpec(true));
+ }
+
+ public function testValidValuesCallback() {
+ $opt = new Option('scope');
+ $opt->validValues(function() {
+ return ['public', 'private'];
+ });
+ $this->assertNotNull($opt->getValidValues());
+ $this->assertNotEmpty($opt->getValidValues());
+
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+ $this->assertEquals('--scope=(public,private)',$opt->renderReadableSpec(true));
+ }
+
+ public function testTrigger()
+ {
+ $opt = new Option('scope');
+ $opt->validValues([ 'public', 'private' ]);
+
+ $state = 0;
+ $opt->trigger(function($val) use(& $state) {
+ $state++;
+ });
+ $this->assertNotEmpty($opt->getValidValues());
+ $opt->setValue('public');
+
+ $this->assertEquals(1, $state);
+ $opt->setValue('private');
+ $this->assertEquals(2, $state);
+
+ }
+
+
+
+ public function testArrayValueToString()
+ {
+ $opt = new Option('uid');
+ $opt->setValue([1,2,3,4]);
+ $toString = '* key:uid spec:--uid desc:
+ value => 1,2,3,4
+';
+ $this->assertEquals($toString,$opt->__toString());
+ }
+
+ public function testValidValues()
+ {
+ $opt = new Option('scope');
+ $opt->validValues([ 'public', 'private' ])
+ ;
+ $this->assertNotEmpty($opt->getValidValues());
+ $this->assertTrue(is_array($opt->getValidValues()));
+
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+ $this->assertEquals('--scope=(public,private)',$opt->renderReadableSpec(true));
+ $this->assertNotEmpty($opt->__toString());
+ }
+
+
+ public function testFilter() {
+ $opt = new Option('scope');
+ $opt->filter(function($val) {
+ return preg_replace('#a#', 'x', $val);
+ })
+ ;
+ $opt->setValue('aa');
+ $this->assertEquals('xx', $opt->value);
+ }
+}
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/tests/RegexValueTypeTest.php b/instafeed/vendor/corneltek/getoptionkit/tests/RegexValueTypeTest.php
new file mode 100755
index 0000000..6917918
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/tests/RegexValueTypeTest.php
@@ -0,0 +1,26 @@
+assertEquals($regex->option, '#^Test$#');
+ }
+
+ public function testValidation()
+ {
+ $regex = new RegexType('#^Test$#');
+ $this->assertTrue($regex->test('Test'));
+ $this->assertFalse($regex->test('test'));
+
+ $regex->option = '/^([a-z]+)$/';
+ $this->assertTrue($regex->test('barfoo'));
+ $this->assertFalse($regex->test('foobar234'));
+ $ret = $regex->parse('foobar234');
+ $this->assertNotNull($ret);
+ }
+}
+
diff --git a/instafeed/vendor/corneltek/getoptionkit/tests/ValueTypeTest.php b/instafeed/vendor/corneltek/getoptionkit/tests/ValueTypeTest.php
new file mode 100755
index 0000000..08472c5
--- /dev/null
+++ b/instafeed/vendor/corneltek/getoptionkit/tests/ValueTypeTest.php
@@ -0,0 +1,200 @@
+assertNotNull( new BooleanType );
+ $this->assertNotNull( new StringType );
+ $this->assertNotNull( new FileType );
+ $this->assertNotNull( new DateType );
+ $this->assertNotNull( new DateTimeType );
+ $this->assertNotNull( new NumberType );
+ $this->assertNotNull( new UrlType );
+ $this->assertNotNull( new IpType );
+ $this->assertNotNull( new Ipv4Type );
+ $this->assertNotNull( new Ipv6Type );
+ $this->assertNotNull( new EmailType );
+ $this->assertNotNull( new PathType );
+ $this->assertNotNull( new RegexType("/[a-z]/"));
+ }
+
+
+ public function testDateTimeType()
+ {
+ $type = new DateTimeType([ 'format' => 'Y-m-d' ]);
+ $this->assertTrue($type->test('2016-12-30'));
+ $a = $type->parse('2016-12-30');
+ $this->assertEquals(2016, $a->format('Y'));
+ $this->assertEquals(12, $a->format('m'));
+ $this->assertEquals(30, $a->format('d'));
+ $this->assertFalse($type->test('foo'));
+ }
+
+ public function testDateType()
+ {
+ $type = new DateType;
+ $this->assertTrue($type->test('2016-12-30'));
+ $a = $type->parse('2016-12-30');
+ $this->assertEquals(2016, $a['year']);
+ $this->assertEquals(12, $a['month']);
+ $this->assertEquals(30, $a['day']);
+ $this->assertFalse($type->test('foo'));
+ }
+
+
+
+ public function booleanTestProvider()
+ {
+ return [
+ [true , true, true],
+ [false , true, false],
+ ['true' , true, true],
+ ['false' , true, false],
+ ['0' , true, false],
+ ['1' , true, true],
+ ['foo' , false, null],
+ ['123' , false, null],
+ ];
+ }
+
+ /**
+ * @dataProvider booleanTestProvider
+ */
+ public function testBooleanType($a, $test, $expected)
+ {
+ $bool = new BooleanType;
+ $this->assertEquals($test, $bool->test($a));
+ if ($bool->test($a)) {
+ $this->assertEquals($expected, $bool->parse($a));
+ }
+ }
+
+ public function testDirType()
+ {
+ $type = new DirType;
+ $this->assertTrue($type->test('tests'));
+ $this->assertFalse($type->test('composer.json'));
+ $this->assertFalse($type->test('foo/bar'));
+ $this->assertInstanceOf('SplFileInfo',$type->parse('tests'));
+ }
+
+ public function testFileType()
+ {
+ $type = new FileType;
+ $this->assertFalse($type->test('tests'));
+ $this->assertTrue($type->test('composer.json'));
+ $this->assertFalse($type->test('foo/bar'));
+ $this->assertInstanceOf('SplFileInfo', $type->parse('composer.json'));
+ }
+
+ public function testPathType()
+ {
+ $type = new PathType;
+ $this->assertTrue($type->test('tests'));
+ $this->assertTrue($type->test('composer.json'));
+ $this->assertFalse($type->test('foo/bar'));
+ $this->assertInstanceOf('SplFileInfo', $type->parse('composer.json'));
+ }
+
+ public function testUrlType()
+ {
+ $url = new UrlType;
+ $this->assertTrue($url->test('http://t'));
+ $this->assertTrue($url->test('http://t.c'));
+ $this->assertFalse($url->test('t.c'));
+ $this->assertEquals('http://t.c', $url->parse('http://t.c'));
+ }
+
+ public function ipV4Provider()
+ {
+ return [
+ ['192.168.25.58', true],
+ ['8.8.8.8', true],
+ ['github.com', false],
+ ];
+ }
+
+ public function ipV6Provider()
+ {
+ return [
+ ['192.168.25.58', false],
+ ['2607:f0d0:1002:51::4', true],
+ ['2607:f0d0:1002:0051:0000:0000:0000:0004', true],
+ ['::1', true],
+ ['10.10.15.10/16', false],
+ ['github.com', false],
+ ];
+ }
+
+ public function ipProvider()
+ {
+ return [
+ ['192.168.25.58', true],
+ ['2607:f0d0:1002:51::4', true],
+ ['::1', true],
+ ['10.10.15.10/16', false],
+ ['github.com', false],
+ ];
+ }
+
+ /**
+ * @dataProvider ipProvider
+ */
+ public function testIpType($ipstr, $pass = true)
+ {
+ $ip = new IpType;
+ $this->assertEquals($pass, $ip->test($ipstr));
+ if ($pass) {
+ $this->assertNotNull($ip->parse($ipstr));
+ }
+ }
+
+ /**
+ * @dataProvider ipV4Provider
+ */
+ public function testIpv4Type($ipstr, $pass = true)
+ {
+ $ipv4 = new Ipv4Type;
+ $this->assertEquals($pass, $ipv4->test($ipstr));
+ if ($pass) {
+ $this->assertNotNull($ipv4->parse($ipstr));
+ }
+ }
+
+ /**
+ * @dataProvider ipV6Provider
+ */
+ public function testIpv6Type($ipstr, $pass = true)
+ {
+ $ipv6 = new Ipv6Type;
+ $this->assertEquals($pass, $ipv6->test($ipstr));
+ if ($pass) {
+ $this->assertNotNull($ipv6->parse($ipstr));
+ }
+ }
+
+ public function testEmailType()
+ {
+ $email = new EmailType;
+ $this->assertTrue($email->test('test@gmail.com'));
+ $this->assertFalse($email->test('test@test'));
+ $email->parse('test@gmail.com');
+ }
+}
+
diff --git a/instafeed/vendor/evenement/evenement/.gitignore b/instafeed/vendor/evenement/evenement/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/instafeed/vendor/evenement/evenement/.travis.yml b/instafeed/vendor/evenement/evenement/.travis.yml
new file mode 100755
index 0000000..65ba0ce
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/.travis.yml
@@ -0,0 +1,24 @@
+language: php
+
+php:
+ - 7.0
+ - 7.1
+ - hhvm
+ - nightly
+
+matrix:
+ allow_failures:
+ - php: hhvm
+ - php: nightly
+
+before_script:
+ - wget http://getcomposer.org/composer.phar
+ - php composer.phar install
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
+ - php -n examples/benchmark-emit-no-arguments.php
+ - php -n examples/benchmark-emit-one-argument.php
+ - php -n examples/benchmark-emit.php
+ - php -n examples/benchmark-emit-once.php
+ - php -n examples/benchmark-remove-listener-once.php
diff --git a/instafeed/vendor/evenement/evenement/CHANGELOG.md b/instafeed/vendor/evenement/evenement/CHANGELOG.md
new file mode 100755
index 0000000..568f229
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/CHANGELOG.md
@@ -0,0 +1,35 @@
+CHANGELOG
+=========
+
+
+* v3.0.1 (2017-07-23)
+
+ * Resolved regression introduced in once listeners in v3.0.0 [#49](https://github.com/igorw/evenement/pull/49)
+
+* v3.0.0 (2017-07-23)
+
+ * Passing null as event name throw exception [#46](https://github.com/igorw/evenement/pull/46), and [#47](https://github.com/igorw/evenement/pull/47)
+ * Performance improvements [#39](https://github.com/igorw/evenement/pull/39), and [#45](https://github.com/igorw/evenement/pull/45)
+ * Remove once listeners [#44](https://github.com/igorw/evenement/pull/44), [#45](https://github.com/igorw/evenement/pull/45)
+
+* v2.1.0 (2017-07-17)
+
+ * Chaining for "on" method [#30](https://github.com/igorw/evenement/pull/30)
+ * Unit tests (on Travis) improvements [#33](https://github.com/igorw/evenement/pull/33), [#36](https://github.com/igorw/evenement/pull/36), and [#37](https://github.com/igorw/evenement/pull/37)
+ * Benchmarks added [#35](https://github.com/igorw/evenement/pull/35), and [#40](https://github.com/igorw/evenement/pull/40)
+ * Minor performance improvements [#42](https://github.com/igorw/evenement/pull/42), and [#38](https://github.com/igorw/evenement/pull/38)
+
+* v2.0.0 (2012-11-02)
+
+ * Require PHP >=5.4.0
+ * Added EventEmitterTrait
+ * Removed EventEmitter2
+
+* v1.1.0 (2017-07-17)
+
+ * Chaining for "on" method [#29](https://github.com/igorw/evenement/pull/29)
+ * Minor performance improvements [#43](https://github.com/igorw/evenement/pull/43)
+
+* v1.0.0 (2012-05-30)
+
+ * Inital stable release
diff --git a/instafeed/vendor/evenement/evenement/LICENSE b/instafeed/vendor/evenement/evenement/LICENSE
new file mode 100755
index 0000000..d9a37d0
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/evenement/evenement/README.md b/instafeed/vendor/evenement/evenement/README.md
new file mode 100755
index 0000000..9443011
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/README.md
@@ -0,0 +1,83 @@
+# Événement
+
+Événement is a very simple event dispatching library for PHP.
+
+It has the same design goals as [Silex](http://silex-project.org) and
+[Pimple](http://pimple-project.org), to empower the user while staying concise
+and simple.
+
+It is very strongly inspired by the EventEmitter API found in
+[node.js](http://nodejs.org).
+
+[](http://travis-ci.org/igorw/evenement)
+
+## Fetch
+
+The recommended way to install Événement is [through composer](http://getcomposer.org).
+
+Just create a composer.json file for your project:
+
+```JSON
+{
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0"
+ }
+}
+```
+
+**Note:** The `3.x` version of Événement requires PHP 7 and the `2.x` version requires PHP 5.4. If you are
+using PHP 5.3, please use the `1.x` version:
+
+```JSON
+{
+ "require": {
+ "evenement/evenement": "^1.0"
+ }
+}
+```
+
+And run these two commands to install it:
+
+ $ curl -s http://getcomposer.org/installer | php
+ $ php composer.phar install
+
+Now you can add the autoloader, and you will have access to the library:
+
+```php
+on('user.created', function (User $user) use ($logger) {
+ $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+});
+```
+
+### Emitting Events
+
+```php
+emit('user.created', [$user]);
+```
+
+Tests
+-----
+
+ $ ./vendor/bin/phpunit
+
+License
+-------
+MIT, see LICENSE.
diff --git a/instafeed/vendor/evenement/evenement/composer.json b/instafeed/vendor/evenement/evenement/composer.json
new file mode 100755
index 0000000..cbb4827
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "evenement/evenement",
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": ["event-dispatcher", "event-emitter"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "autoload": {
+ "psr-0": {
+ "Evenement": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-0": {
+ "Evenement": "tests"
+ },
+ "files": ["tests/Evenement/Tests/functions.php"]
+ }
+}
diff --git a/instafeed/vendor/evenement/evenement/doc/00-intro.md b/instafeed/vendor/evenement/evenement/doc/00-intro.md
new file mode 100755
index 0000000..6c28a2a
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/doc/00-intro.md
@@ -0,0 +1,28 @@
+# Introduction
+
+Événement is is French and means "event". The événement library aims to
+provide a simple way of subscribing to events and notifying those subscribers
+whenever an event occurs.
+
+The API that it exposes is almost a direct port of the EventEmitter API found
+in node.js. It also includes an "EventEmitter". There are some minor
+differences however.
+
+The EventEmitter is an implementation of the publish-subscribe pattern, which
+is a generalized version of the observer pattern. The observer pattern
+specifies an observable subject, which observers can register themselves to.
+Once something interesting happens, the subject notifies its observers.
+
+Pub/sub takes the same idea but encapsulates the observation logic inside a
+separate object which manages all of its subscribers or listeners. Subscribers
+are bound to an event name, and will only receive notifications of the events
+they subscribed to.
+
+**TLDR: What does evenement do, in short? It provides a mapping from event
+names to a list of listener functions and triggers each listener for a given
+event when it is emitted.**
+
+Why do we do this, you ask? To achieve decoupling.
+
+It allows you to design a system where the core will emit events, and modules
+are able to subscribe to these events. And respond to them.
diff --git a/instafeed/vendor/evenement/evenement/doc/01-api.md b/instafeed/vendor/evenement/evenement/doc/01-api.md
new file mode 100755
index 0000000..17ba333
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/doc/01-api.md
@@ -0,0 +1,91 @@
+# API
+
+The API that événement exposes is defined by the
+`Evenement\EventEmitterInterface`. The interface is useful if you want to
+define an interface that extends the emitter and implicitly defines certain
+events to be emitted, or if you want to type hint an `EventEmitter` to be
+passed to a method without coupling to the specific implementation.
+
+## on($event, callable $listener)
+
+Allows you to subscribe to an event.
+
+Example:
+
+```php
+$emitter->on('user.created', function (User $user) use ($logger) {
+ $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+});
+```
+
+Since the listener can be any callable, you could also use an instance method
+instead of the anonymous function:
+
+```php
+$loggerSubscriber = new LoggerSubscriber($logger);
+$emitter->on('user.created', array($loggerSubscriber, 'onUserCreated'));
+```
+
+This has the benefit that listener does not even need to know that the emitter
+exists.
+
+You can also accept more than one parameter for the listener:
+
+```php
+$emitter->on('numbers_added', function ($result, $a, $b) {});
+```
+
+## once($event, callable $listener)
+
+Convenience method that adds a listener which is guaranteed to only be called
+once.
+
+Example:
+
+```php
+$conn->once('connected', function () use ($conn, $data) {
+ $conn->send($data);
+});
+```
+
+## emit($event, array $arguments = [])
+
+Emit an event, which will call all listeners.
+
+Example:
+
+```php
+$conn->emit('data', [$data]);
+```
+
+The second argument to emit is an array of listener arguments. This is how you
+specify more args:
+
+```php
+$result = $a + $b;
+$emitter->emit('numbers_added', [$result, $a, $b]);
+```
+
+## listeners($event)
+
+Allows you to inspect the listeners attached to an event. Particularly useful
+to check if there are any listeners at all.
+
+Example:
+
+```php
+$e = new \RuntimeException('Everything is broken!');
+if (0 === count($emitter->listeners('error'))) {
+ throw $e;
+}
+```
+
+## removeListener($event, callable $listener)
+
+Remove a specific listener for a specific event.
+
+## removeAllListeners($event = null)
+
+Remove all listeners for a specific event or all listeners all together. This
+is useful for long-running processes, where you want to remove listeners in
+order to allow them to get garbage collected.
diff --git a/instafeed/vendor/evenement/evenement/doc/02-plugin-system.md b/instafeed/vendor/evenement/evenement/doc/02-plugin-system.md
new file mode 100755
index 0000000..6a08371
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/doc/02-plugin-system.md
@@ -0,0 +1,155 @@
+# Example: Plugin system
+
+In this example I will show you how to create a generic plugin system with
+événement where plugins can alter the behaviour of the app. The app is a blog.
+Boring, I know. By using the EventEmitter it will be easy to extend this blog
+with additional functionality without modifying the core system.
+
+The blog is quite basic. Users are able to create blog posts when they log in.
+The users are stored in a static config file, so there is no sign up process.
+Once logged in they get a "new post" link which gives them a form where they
+can create a new blog post with plain HTML. That will store the post in a
+document database. The index lists all blog post titles by date descending.
+Clicking on the post title will take you to the full post.
+
+## Plugin structure
+
+The goal of the plugin system is to allow features to be added to the blog
+without modifying any core files of the blog.
+
+The plugins are managed through a config file, `plugins.json`. This JSON file
+contains a JSON-encoded list of class-names for plugin classes. This allows
+you to enable and disable plugins in a central location. The initial
+`plugins.json` is just an empty array:
+```json
+[]
+```
+
+A plugin class must implement the `PluginInterface`:
+```php
+interface PluginInterface
+{
+ function attachEvents(EventEmitterInterface $emitter);
+}
+```
+
+The `attachEvents` method allows the plugin to attach any events to the
+emitter. For example:
+```php
+class FooPlugin implements PluginInterface
+{
+ public function attachEvents(EventEmitterInterface $emitter)
+ {
+ $emitter->on('foo', function () {
+ echo 'bar!';
+ });
+ }
+}
+```
+
+The blog system creates an emitter instance and loads the plugins:
+```php
+$emitter = new EventEmitter();
+
+$pluginClasses = json_decode(file_get_contents('plugins.json'), true);
+foreach ($pluginClasses as $pluginClass) {
+ $plugin = new $pluginClass();
+ $pluginClass->attachEvents($emitter);
+}
+```
+
+This is the base system. There are no plugins yet, and there are no events yet
+either. That's because I don't know which extension points will be needed. I
+will add them on demand.
+
+## Feature: Markdown
+
+Writing blog posts in HTML sucks! Wouldn't it be great if I could write them
+in a nice format such as markdown, and have that be converted to HTML for me?
+
+This feature will need two extension points. I need to be able to mark posts
+as markdown, and I need to be able to hook into the rendering of the post body
+and convert it from markdown to HTML. So the blog needs two new events:
+`post.create` and `post.render`.
+
+In the code that creates the post, I'll insert the `post.create` event:
+```php
+class PostEvent
+{
+ public $post;
+
+ public function __construct(array $post)
+ {
+ $this->post = $post;
+ }
+}
+
+$post = createPostFromRequest($_POST);
+
+$event = new PostEvent($post);
+$emitter->emit('post.create', [$event]);
+$post = $event->post;
+
+$db->save('post', $post);
+```
+
+This shows that you can wrap a value in an event object to make it mutable,
+allowing listeners to change it.
+
+The same thing for the `post.render` event:
+```php
+public function renderPostBody(array $post)
+{
+ $emitter = $this->emitter;
+
+ $event = new PostEvent($post);
+ $emitter->emit('post.render', [$event]);
+ $post = $event->post;
+
+ return $post['body'];
+}
+
+
= $post['title'] %>
+
= renderPostBody($post) %>
+```
+
+Ok, the events are in place. It's time to create the first plugin, woohoo! I
+will call this the `MarkdownPlugin`, so here's `plugins.json`:
+```json
+[
+ "MarkdownPlugin"
+]
+```
+
+The `MarkdownPlugin` class will be autoloaded, so I don't have to worry about
+including any files. I just have to worry about implementing the plugin class.
+The `markdown` function represents a markdown to HTML converter.
+```php
+class MarkdownPlugin implements PluginInterface
+{
+ public function attachEvents(EventEmitterInterface $emitter)
+ {
+ $emitter->on('post.create', function (PostEvent $event) {
+ $event->post['format'] = 'markdown';
+ });
+
+ $emitter->on('post.render', function (PostEvent $event) {
+ if (isset($event->post['format']) && 'markdown' === $event->post['format']) {
+ $event->post['body'] = markdown($event->post['body']);
+ }
+ });
+ }
+}
+```
+
+There you go, the blog now renders posts as markdown. But all of the previous
+posts before the addition of the markdown plugin are still rendered correctly
+as raw HTML.
+
+## Feature: Comments
+
+TODO
+
+## Feature: Comment spam control
+
+TODO
diff --git a/instafeed/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php b/instafeed/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php
new file mode 100755
index 0000000..53d7f4b
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function () {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->emit('event');
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/instafeed/vendor/evenement/evenement/examples/benchmark-emit-once.php b/instafeed/vendor/evenement/evenement/examples/benchmark-emit-once.php
new file mode 100755
index 0000000..74f4d17
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/examples/benchmark-emit-once.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+ini_set('memory_limit', '512M');
+
+const ITERATIONS = 100000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->once('event', function ($a, $b, $c) {});
+}
+
+$start = microtime(true);
+$emitter->emit('event', [1, 2, 3]);
+$time = microtime(true) - $start;
+
+echo 'Emitting one event to ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/instafeed/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php b/instafeed/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php
new file mode 100755
index 0000000..39fc4ba
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function ($a) {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->emit('event', [1]);
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/instafeed/vendor/evenement/evenement/examples/benchmark-emit.php b/instafeed/vendor/evenement/evenement/examples/benchmark-emit.php
new file mode 100755
index 0000000..3ab639e
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/examples/benchmark-emit.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function ($a, $b, $c) {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->emit('event', [1, 2, 3]);
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/instafeed/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php b/instafeed/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php
new file mode 100755
index 0000000..414be3b
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+ini_set('memory_limit', '512M');
+
+const ITERATIONS = 100000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$listeners = [];
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $listeners[] = function ($a, $b, $c) {};
+}
+
+$start = microtime(true);
+foreach ($listeners as $listener) {
+ $emitter->once('event', $listener);
+}
+$time = microtime(true) - $start;
+echo 'Adding ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
+
+$start = microtime(true);
+foreach ($listeners as $listener) {
+ $emitter->removeListener('event', $listener);
+}
+$time = microtime(true) - $start;
+echo 'Removing ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/instafeed/vendor/evenement/evenement/phpunit.xml.dist b/instafeed/vendor/evenement/evenement/phpunit.xml.dist
new file mode 100755
index 0000000..70bc693
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/phpunit.xml.dist
@@ -0,0 +1,24 @@
+
+
+
+
+
+ ./tests/Evenement/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/instafeed/vendor/evenement/evenement/src/Evenement/EventEmitter.php b/instafeed/vendor/evenement/evenement/src/Evenement/EventEmitter.php
new file mode 100755
index 0000000..db189b9
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/src/Evenement/EventEmitter.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+class EventEmitter implements EventEmitterInterface
+{
+ use EventEmitterTrait;
+}
diff --git a/instafeed/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php b/instafeed/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
new file mode 100755
index 0000000..310631a
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+interface EventEmitterInterface
+{
+ public function on($event, callable $listener);
+ public function once($event, callable $listener);
+ public function removeListener($event, callable $listener);
+ public function removeAllListeners($event = null);
+ public function listeners($event = null);
+ public function emit($event, array $arguments = []);
+}
diff --git a/instafeed/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php b/instafeed/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
new file mode 100755
index 0000000..a78e65c
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
@@ -0,0 +1,135 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+use InvalidArgumentException;
+
+trait EventEmitterTrait
+{
+ protected $listeners = [];
+ protected $onceListeners = [];
+
+ public function on($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->listeners[$event])) {
+ $this->listeners[$event] = [];
+ }
+
+ $this->listeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function once($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->onceListeners[$event])) {
+ $this->onceListeners[$event] = [];
+ }
+
+ $this->onceListeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function removeListener($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (isset($this->listeners[$event])) {
+ $index = \array_search($listener, $this->listeners[$event], true);
+ if (false !== $index) {
+ unset($this->listeners[$event][$index]);
+ if (\count($this->listeners[$event]) === 0) {
+ unset($this->listeners[$event]);
+ }
+ }
+ }
+
+ if (isset($this->onceListeners[$event])) {
+ $index = \array_search($listener, $this->onceListeners[$event], true);
+ if (false !== $index) {
+ unset($this->onceListeners[$event][$index]);
+ if (\count($this->onceListeners[$event]) === 0) {
+ unset($this->onceListeners[$event]);
+ }
+ }
+ }
+ }
+
+ public function removeAllListeners($event = null)
+ {
+ if ($event !== null) {
+ unset($this->listeners[$event]);
+ } else {
+ $this->listeners = [];
+ }
+
+ if ($event !== null) {
+ unset($this->onceListeners[$event]);
+ } else {
+ $this->onceListeners = [];
+ }
+ }
+
+ public function listeners($event = null): array
+ {
+ if ($event === null) {
+ $events = [];
+ $eventNames = \array_unique(
+ \array_merge(\array_keys($this->listeners), \array_keys($this->onceListeners))
+ );
+ foreach ($eventNames as $eventName) {
+ $events[$eventName] = \array_merge(
+ isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [],
+ isset($this->onceListeners[$eventName]) ? $this->onceListeners[$eventName] : []
+ );
+ }
+ return $events;
+ }
+
+ return \array_merge(
+ isset($this->listeners[$event]) ? $this->listeners[$event] : [],
+ isset($this->onceListeners[$event]) ? $this->onceListeners[$event] : []
+ );
+ }
+
+ public function emit($event, array $arguments = [])
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (isset($this->listeners[$event])) {
+ foreach ($this->listeners[$event] as $listener) {
+ $listener(...$arguments);
+ }
+ }
+
+ if (isset($this->onceListeners[$event])) {
+ $listeners = $this->onceListeners[$event];
+ unset($this->onceListeners[$event]);
+ foreach ($listeners as $listener) {
+ $listener(...$arguments);
+ }
+ }
+ }
+}
diff --git a/instafeed/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php b/instafeed/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php
new file mode 100755
index 0000000..28f3011
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php
@@ -0,0 +1,438 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+use Evenement\EventEmitter;
+use InvalidArgumentException;
+use PHPUnit\Framework\TestCase;
+
+class EventEmitterTest extends TestCase
+{
+ private $emitter;
+
+ public function setUp()
+ {
+ $this->emitter = new EventEmitter();
+ }
+
+ public function testAddListenerWithLambda()
+ {
+ $this->emitter->on('foo', function () {});
+ }
+
+ public function testAddListenerWithMethod()
+ {
+ $listener = new Listener();
+ $this->emitter->on('foo', [$listener, 'onFoo']);
+ }
+
+ public function testAddListenerWithStaticMethod()
+ {
+ $this->emitter->on('bar', ['Evenement\Tests\Listener', 'onBar']);
+ }
+
+ public function testAddListenerWithInvalidListener()
+ {
+ try {
+ $this->emitter->on('foo', 'not a callable');
+ $this->fail();
+ } catch (\Exception $e) {
+ } catch (\TypeError $e) {
+ }
+ }
+
+ public function testOnce()
+ {
+ $listenerCalled = 0;
+
+ $this->emitter->once('foo', function () use (&$listenerCalled) {
+ $listenerCalled++;
+ });
+
+ $this->assertSame(0, $listenerCalled);
+
+ $this->emitter->emit('foo');
+
+ $this->assertSame(1, $listenerCalled);
+
+ $this->emitter->emit('foo');
+
+ $this->assertSame(1, $listenerCalled);
+ }
+
+ public function testOnceWithArguments()
+ {
+ $capturedArgs = [];
+
+ $this->emitter->once('foo', function ($a, $b) use (&$capturedArgs) {
+ $capturedArgs = array($a, $b);
+ });
+
+ $this->emitter->emit('foo', array('a', 'b'));
+
+ $this->assertSame(array('a', 'b'), $capturedArgs);
+ }
+
+ public function testEmitWithoutArguments()
+ {
+ $listenerCalled = false;
+
+ $this->emitter->on('foo', function () use (&$listenerCalled) {
+ $listenerCalled = true;
+ });
+
+ $this->assertSame(false, $listenerCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(true, $listenerCalled);
+ }
+
+ public function testEmitWithOneArgument()
+ {
+ $test = $this;
+
+ $listenerCalled = false;
+
+ $this->emitter->on('foo', function ($value) use (&$listenerCalled, $test) {
+ $listenerCalled = true;
+
+ $test->assertSame('bar', $value);
+ });
+
+ $this->assertSame(false, $listenerCalled);
+ $this->emitter->emit('foo', ['bar']);
+ $this->assertSame(true, $listenerCalled);
+ }
+
+ public function testEmitWithTwoArguments()
+ {
+ $test = $this;
+
+ $listenerCalled = false;
+
+ $this->emitter->on('foo', function ($arg1, $arg2) use (&$listenerCalled, $test) {
+ $listenerCalled = true;
+
+ $test->assertSame('bar', $arg1);
+ $test->assertSame('baz', $arg2);
+ });
+
+ $this->assertSame(false, $listenerCalled);
+ $this->emitter->emit('foo', ['bar', 'baz']);
+ $this->assertSame(true, $listenerCalled);
+ }
+
+ public function testEmitWithNoListeners()
+ {
+ $this->emitter->emit('foo');
+ $this->emitter->emit('foo', ['bar']);
+ $this->emitter->emit('foo', ['bar', 'baz']);
+ }
+
+ public function testEmitWithTwoListeners()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(2, $listenersCalled);
+ }
+
+ public function testRemoveListenerMatching()
+ {
+ $listenersCalled = 0;
+
+ $listener = function () use (&$listenersCalled) {
+ $listenersCalled++;
+ };
+
+ $this->emitter->on('foo', $listener);
+ $this->emitter->removeListener('foo', $listener);
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(0, $listenersCalled);
+ }
+
+ public function testRemoveListenerNotMatching()
+ {
+ $listenersCalled = 0;
+
+ $listener = function () use (&$listenersCalled) {
+ $listenersCalled++;
+ };
+
+ $this->emitter->on('foo', $listener);
+ $this->emitter->removeListener('bar', $listener);
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(1, $listenersCalled);
+ }
+
+ public function testRemoveAllListenersMatching()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->removeAllListeners('foo');
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(0, $listenersCalled);
+ }
+
+ public function testRemoveAllListenersNotMatching()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->removeAllListeners('bar');
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(1, $listenersCalled);
+ }
+
+ public function testRemoveAllListenersWithoutArguments()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->on('bar', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->removeAllListeners();
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->emitter->emit('bar');
+ $this->assertSame(0, $listenersCalled);
+ }
+
+ public function testCallablesClosure()
+ {
+ $calledWith = null;
+
+ $this->emitter->on('foo', function ($data) use (&$calledWith) {
+ $calledWith = $data;
+ });
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame('bar', $calledWith);
+ }
+
+ public function testCallablesClass()
+ {
+ $listener = new Listener();
+ $this->emitter->on('foo', [$listener, 'onFoo']);
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame(['bar'], $listener->getData());
+ }
+
+
+ public function testCallablesClassInvoke()
+ {
+ $listener = new Listener();
+ $this->emitter->on('foo', $listener);
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame(['bar'], $listener->getMagicData());
+ }
+
+ public function testCallablesStaticClass()
+ {
+ $this->emitter->on('foo', '\Evenement\Tests\Listener::onBar');
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame(['bar'], Listener::getStaticData());
+ }
+
+ public function testCallablesFunction()
+ {
+ $this->emitter->on('foo', '\Evenement\Tests\setGlobalTestData');
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame('bar', $GLOBALS['evenement-evenement-test-data']);
+
+ unset($GLOBALS['evenement-evenement-test-data']);
+ }
+
+ public function testListeners()
+ {
+ $onA = function () {};
+ $onB = function () {};
+ $onC = function () {};
+ $onceA = function () {};
+ $onceB = function () {};
+ $onceC = function () {};
+
+ self::assertCount(0, $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onA);
+ self::assertCount(1, $this->emitter->listeners('event'));
+ self::assertSame([$onA], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceA);
+ self::assertCount(2, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceB);
+ self::assertCount(3, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onceA, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onB);
+ self::assertCount(4, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceA, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->removeListener('event', $onceA);
+ self::assertCount(3, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceC);
+ self::assertCount(4, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceB, $onceC], $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onC);
+ self::assertCount(5, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onC, $onceB, $onceC], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceA);
+ self::assertCount(6, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->removeListener('event', $onB);
+ self::assertCount(5, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->emit('event');
+ self::assertCount(2, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onC], $this->emitter->listeners('event'));
+ }
+
+ public function testOnceCallIsNotRemovedWhenWorkingOverOnceListeners()
+ {
+ $aCalled = false;
+ $aCallable = function () use (&$aCalled) {
+ $aCalled = true;
+ };
+ $bCalled = false;
+ $bCallable = function () use (&$bCalled, $aCallable) {
+ $bCalled = true;
+ $this->emitter->once('event', $aCallable);
+ };
+ $this->emitter->once('event', $bCallable);
+
+ self::assertFalse($aCalled);
+ self::assertFalse($bCalled);
+ $this->emitter->emit('event');
+
+ self::assertFalse($aCalled);
+ self::assertTrue($bCalled);
+ $this->emitter->emit('event');
+
+ self::assertTrue($aCalled);
+ self::assertTrue($bCalled);
+ }
+
+ public function testEventNameMustBeStringOn()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->on(null, function () {});
+ }
+
+ public function testEventNameMustBeStringOnce()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->once(null, function () {});
+ }
+
+ public function testEventNameMustBeStringRemoveListener()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->removeListener(null, function () {});
+ }
+
+ public function testEventNameMustBeStringEmit()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->emit(null);
+ }
+
+ public function testListenersGetAll()
+ {
+ $a = function () {};
+ $b = function () {};
+ $c = function () {};
+ $d = function () {};
+
+ $this->emitter->once('event2', $c);
+ $this->emitter->on('event', $a);
+ $this->emitter->once('event', $b);
+ $this->emitter->on('event', $c);
+ $this->emitter->once('event', $d);
+
+ self::assertSame(
+ [
+ 'event' => [
+ $a,
+ $c,
+ $b,
+ $d,
+ ],
+ 'event2' => [
+ $c,
+ ],
+ ],
+ $this->emitter->listeners()
+ );
+ }
+
+ public function testOnceNestedCallRegression()
+ {
+ $first = 0;
+ $second = 0;
+
+ $this->emitter->once('event', function () use (&$first, &$second) {
+ $first++;
+ $this->emitter->once('event', function () use (&$second) {
+ $second++;
+ });
+ $this->emitter->emit('event');
+ });
+ $this->emitter->emit('event');
+
+ self::assertSame(1, $first);
+ self::assertSame(1, $second);
+ }
+}
diff --git a/instafeed/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php b/instafeed/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php
new file mode 100755
index 0000000..df17424
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+class Listener
+{
+ private $data = [];
+
+ private $magicData = [];
+
+ private static $staticData = [];
+
+ public function onFoo($data)
+ {
+ $this->data[] = $data;
+ }
+
+ public function __invoke($data)
+ {
+ $this->magicData[] = $data;
+ }
+
+ public static function onBar($data)
+ {
+ self::$staticData[] = $data;
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function getMagicData()
+ {
+ return $this->magicData;
+ }
+
+ public static function getStaticData()
+ {
+ return self::$staticData;
+ }
+}
diff --git a/instafeed/vendor/evenement/evenement/tests/Evenement/Tests/functions.php b/instafeed/vendor/evenement/evenement/tests/Evenement/Tests/functions.php
new file mode 100755
index 0000000..7f11f5b
--- /dev/null
+++ b/instafeed/vendor/evenement/evenement/tests/Evenement/Tests/functions.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+function setGlobalTestData($data)
+{
+ $GLOBALS['evenement-evenement-test-data'] = $data;
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/.php_cs b/instafeed/vendor/guzzlehttp/guzzle/.php_cs
new file mode 100755
index 0000000..a8ace8a
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/.php_cs
@@ -0,0 +1,21 @@
+setRiskyAllowed(true)
+ ->setRules([
+ '@PSR2' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'declare_strict_types' => false,
+ 'concat_space' => ['spacing'=>'one'],
+ // 'ordered_imports' => true,
+ // 'phpdoc_align' => ['align'=>'vertical'],
+ // 'native_function_invocation' => true,
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__.'/src')
+ ->name('*.php')
+ )
+;
+
+return $config;
diff --git a/instafeed/vendor/guzzlehttp/guzzle/CHANGELOG.md b/instafeed/vendor/guzzlehttp/guzzle/CHANGELOG.md
new file mode 100755
index 0000000..6555749
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/CHANGELOG.md
@@ -0,0 +1,1304 @@
+# Change Log
+
+## 6.4.1 - 2019-10-23
+
+* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that
+* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar`
+
+## 6.4.0 - 2019-10-23
+
+* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108)
+* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081)
+* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161)
+* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163)
+* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242)
+* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284)
+* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273)
+* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335)
+* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362)
+
+## 6.3.3 - 2018-04-22
+
+* Fix: Default headers when decode_content is specified
+
+
+## 6.3.2 - 2018-03-26
+
+* Fix: Release process
+
+
+## 6.3.1 - 2018-03-26
+
+* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014)
+* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012)
+* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999)
+* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998)
+* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953)
+* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915)
+* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916)
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+
+## 6.3.0 - 2017-06-22
+
+* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
+* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
+* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
+* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
+* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
+* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
+* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
+* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
+* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
+* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
+* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
+* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
+* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
+* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
+* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)
+
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+## 6.2.3 - 2017-02-28
+
+* Fix deprecations with guzzle/psr7 version 1.4
+
+## 6.2.2 - 2016-10-08
+
+* Allow to pass nullable Response to delay callable
+* Only add scheme when host is present
+* Fix drain case where content-length is the literal string zero
+* Obfuscate in-URL credentials in exceptions
+
+## 6.2.1 - 2016-07-18
+
+* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
+ https://httpoxy.org/
+* Fixing timeout bug with StreamHandler:
+ https://github.com/guzzle/guzzle/pull/1488
+* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
+ a server does not honor `Connection: close`.
+* Ignore URI fragment when sending requests.
+
+## 6.2.0 - 2016-03-21
+
+* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
+ https://github.com/guzzle/guzzle/pull/1389
+* Bug fix: Fix sleep calculation when waiting for delayed requests.
+ https://github.com/guzzle/guzzle/pull/1324
+* Feature: More flexible history containers.
+ https://github.com/guzzle/guzzle/pull/1373
+* Bug fix: defer sink stream opening in StreamHandler.
+ https://github.com/guzzle/guzzle/pull/1377
+* Bug fix: do not attempt to escape cookie values.
+ https://github.com/guzzle/guzzle/pull/1406
+* Feature: report original content encoding and length on decoded responses.
+ https://github.com/guzzle/guzzle/pull/1409
+* Bug fix: rewind seekable request bodies before dispatching to cURL.
+ https://github.com/guzzle/guzzle/pull/1422
+* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
+ https://github.com/guzzle/guzzle/pull/1367
+
+## 6.1.1 - 2015-11-22
+
+* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
+ https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
+* Feature: HandlerStack is now more generic.
+ https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
+* Bug fix: setting verify to false in the StreamHandler now disables peer
+ verification. https://github.com/guzzle/guzzle/issues/1256
+* Feature: Middleware now uses an exception factory, including more error
+ context. https://github.com/guzzle/guzzle/pull/1282
+* Feature: better support for disabled functions.
+ https://github.com/guzzle/guzzle/pull/1287
+* Bug fix: fixed regression where MockHandler was not using `sink`.
+ https://github.com/guzzle/guzzle/pull/1292
+
+## 6.1.0 - 2015-09-08
+
+* Feature: Added the `on_stats` request option to provide access to transfer
+ statistics for requests. https://github.com/guzzle/guzzle/pull/1202
+* Feature: Added the ability to persist session cookies in CookieJars.
+ https://github.com/guzzle/guzzle/pull/1195
+* Feature: Some compatibility updates for Google APP Engine
+ https://github.com/guzzle/guzzle/pull/1216
+* Feature: Added support for NO_PROXY to prevent the use of a proxy based on
+ a simple set of rules. https://github.com/guzzle/guzzle/pull/1197
+* Feature: Cookies can now contain square brackets.
+ https://github.com/guzzle/guzzle/pull/1237
+* Bug fix: Now correctly parsing `=` inside of quotes in Cookies.
+ https://github.com/guzzle/guzzle/pull/1232
+* Bug fix: Cusotm cURL options now correctly override curl options of the
+ same name. https://github.com/guzzle/guzzle/pull/1221
+* Bug fix: Content-Type header is now added when using an explicitly provided
+ multipart body. https://github.com/guzzle/guzzle/pull/1218
+* Bug fix: Now ignoring Set-Cookie headers that have no name.
+* Bug fix: Reason phrase is no longer cast to an int in some cases in the
+ cURL handler. https://github.com/guzzle/guzzle/pull/1187
+* Bug fix: Remove the Authorization header when redirecting if the Host
+ header changes. https://github.com/guzzle/guzzle/pull/1207
+* Bug fix: Cookie path matching fixes
+ https://github.com/guzzle/guzzle/issues/1129
+* Bug fix: Fixing the cURL `body_as_string` setting
+ https://github.com/guzzle/guzzle/pull/1201
+* Bug fix: quotes are no longer stripped when parsing cookies.
+ https://github.com/guzzle/guzzle/issues/1172
+* Bug fix: `form_params` and `query` now always uses the `&` separator.
+ https://github.com/guzzle/guzzle/pull/1163
+* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set.
+ https://github.com/guzzle/guzzle/pull/1189
+
+## 6.0.2 - 2015-07-04
+
+* Fixed a memory leak in the curl handlers in which references to callbacks
+ were not being removed by `curl_reset`.
+* Cookies are now extracted properly before redirects.
+* Cookies now allow more character ranges.
+* Decoded Content-Encoding responses are now modified to correctly reflect
+ their state if the encoding was automatically removed by a handler. This
+ means that the `Content-Encoding` header may be removed an the
+ `Content-Length` modified to reflect the message size after removing the
+ encoding.
+* Added a more explicit error message when trying to use `form_params` and
+ `multipart` in the same request.
+* Several fixes for HHVM support.
+* Functions are now conditionally required using an additional level of
+ indirection to help with global Composer installations.
+
+## 6.0.1 - 2015-05-27
+
+* Fixed a bug with serializing the `query` request option where the `&`
+ separator was missing.
+* Added a better error message for when `body` is provided as an array. Please
+ use `form_params` or `multipart` instead.
+* Various doc fixes.
+
+## 6.0.0 - 2015-05-26
+
+* See the UPGRADING.md document for more information.
+* Added `multipart` and `form_params` request options.
+* Added `synchronous` request option.
+* Added the `on_headers` request option.
+* Fixed `expect` handling.
+* No longer adding default middlewares in the client ctor. These need to be
+ present on the provided handler in order to work.
+* Requests are no longer initiated when sending async requests with the
+ CurlMultiHandler. This prevents unexpected recursion from requests completing
+ while ticking the cURL loop.
+* Removed the semantics of setting `default` to `true`. This is no longer
+ required now that the cURL loop is not ticked for async requests.
+* Added request and response logging middleware.
+* No longer allowing self signed certificates when using the StreamHandler.
+* Ensuring that `sink` is valid if saving to a file.
+* Request exceptions now include a "handler context" which provides handler
+ specific contextual information.
+* Added `GuzzleHttp\RequestOptions` to allow request options to be applied
+ using constants.
+* `$maxHandles` has been removed from CurlMultiHandler.
+* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package.
+
+## 5.3.0 - 2015-05-19
+
+* Mock now supports `save_to`
+* Marked `AbstractRequestEvent::getTransaction()` as public.
+* Fixed a bug in which multiple headers using different casing would overwrite
+ previous headers in the associative array.
+* Added `Utils::getDefaultHandler()`
+* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated.
+* URL scheme is now always lowercased.
+
+## 6.0.0-beta.1
+
+* Requires PHP >= 5.5
+* Updated to use PSR-7
+ * Requires immutable messages, which basically means an event based system
+ owned by a request instance is no longer possible.
+ * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7).
+ * Removed the dependency on `guzzlehttp/streams`. These stream abstractions
+ are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7`
+ namespace.
+* Added middleware and handler system
+ * Replaced the Guzzle event and subscriber system with a middleware system.
+ * No longer depends on RingPHP, but rather places the HTTP handlers directly
+ in Guzzle, operating on PSR-7 messages.
+ * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which
+ means the `guzzlehttp/retry-subscriber` is now obsolete.
+ * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`.
+* Asynchronous responses
+ * No longer supports the `future` request option to send an async request.
+ Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`,
+ `getAsync`, etc.).
+ * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid
+ recursion required by chaining and forwarding react promises. See
+ https://github.com/guzzle/promises
+ * Added `requestAsync` and `sendAsync` to send request asynchronously.
+ * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests
+ asynchronously.
+* Request options
+ * POST and form updates
+ * Added the `form_fields` and `form_files` request options.
+ * Removed the `GuzzleHttp\Post` namespace.
+ * The `body` request option no longer accepts an array for POST requests.
+ * The `exceptions` request option has been deprecated in favor of the
+ `http_errors` request options.
+ * The `save_to` request option has been deprecated in favor of `sink` request
+ option.
+* Clients no longer accept an array of URI template string and variables for
+ URI variables. You will need to expand URI templates before passing them
+ into a client constructor or request method.
+* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are
+ now magic methods that will send synchronous requests.
+* Replaced `Utils.php` with plain functions in `functions.php`.
+* Removed `GuzzleHttp\Collection`.
+* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as
+ an array.
+* Removed `GuzzleHttp\Query`. Query string handling is now handled using an
+ associative array passed into the `query` request option. The query string
+ is serialized using PHP's `http_build_query`. If you need more control, you
+ can pass the query string in as a string.
+* `GuzzleHttp\QueryParser` has been replaced with the
+ `GuzzleHttp\Psr7\parse_query`.
+
+## 5.2.0 - 2015-01-27
+
+* Added `AppliesHeadersInterface` to make applying headers to a request based
+ on the body more generic and not specific to `PostBodyInterface`.
+* Reduced the number of stack frames needed to send requests.
+* Nested futures are now resolved in the client rather than the RequestFsm
+* Finishing state transitions is now handled in the RequestFsm rather than the
+ RingBridge.
+* Added a guard in the Pool class to not use recursion for request retries.
+
+## 5.1.0 - 2014-12-19
+
+* Pool class no longer uses recursion when a request is intercepted.
+* The size of a Pool can now be dynamically adjusted using a callback.
+ See https://github.com/guzzle/guzzle/pull/943.
+* Setting a request option to `null` when creating a request with a client will
+ ensure that the option is not set. This allows you to overwrite default
+ request options on a per-request basis.
+ See https://github.com/guzzle/guzzle/pull/937.
+* Added the ability to limit which protocols are allowed for redirects by
+ specifying a `protocols` array in the `allow_redirects` request option.
+* Nested futures due to retries are now resolved when waiting for synchronous
+ responses. See https://github.com/guzzle/guzzle/pull/947.
+* `"0"` is now an allowed URI path. See
+ https://github.com/guzzle/guzzle/pull/935.
+* `Query` no longer typehints on the `$query` argument in the constructor,
+ allowing for strings and arrays.
+* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle
+ specific exceptions if necessary.
+
+## 5.0.3 - 2014-11-03
+
+This change updates query strings so that they are treated as un-encoded values
+by default where the value represents an un-encoded value to send over the
+wire. A Query object then encodes the value before sending over the wire. This
+means that even value query string values (e.g., ":") are url encoded. This
+makes the Query class match PHP's http_build_query function. However, if you
+want to send requests over the wire using valid query string characters that do
+not need to be encoded, then you can provide a string to Url::setQuery() and
+pass true as the second argument to specify that the query string is a raw
+string that should not be parsed or encoded (unless a call to getQuery() is
+subsequently made, forcing the query-string to be converted into a Query
+object).
+
+## 5.0.2 - 2014-10-30
+
+* Added a trailing `\r\n` to multipart/form-data payloads. See
+ https://github.com/guzzle/guzzle/pull/871
+* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs.
+* Status codes are now returned as integers. See
+ https://github.com/guzzle/guzzle/issues/881
+* No longer overwriting an existing `application/x-www-form-urlencoded` header
+ when sending POST requests, allowing for customized headers. See
+ https://github.com/guzzle/guzzle/issues/877
+* Improved path URL serialization.
+
+ * No longer double percent-encoding characters in the path or query string if
+ they are already encoded.
+ * Now properly encoding the supplied path to a URL object, instead of only
+ encoding ' ' and '?'.
+ * Note: This has been changed in 5.0.3 to now encode query string values by
+ default unless the `rawString` argument is provided when setting the query
+ string on a URL: Now allowing many more characters to be present in the
+ query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A
+
+## 5.0.1 - 2014-10-16
+
+Bugfix release.
+
+* Fixed an issue where connection errors still returned response object in
+ error and end events event though the response is unusable. This has been
+ corrected so that a response is not returned in the `getResponse` method of
+ these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867
+* Fixed an issue where transfer statistics were not being populated in the
+ RingBridge. https://github.com/guzzle/guzzle/issues/866
+
+## 5.0.0 - 2014-10-12
+
+Adding support for non-blocking responses and some minor API cleanup.
+
+### New Features
+
+* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`.
+* Added a public API for creating a default HTTP adapter.
+* Updated the redirect plugin to be non-blocking so that redirects are sent
+ concurrently. Other plugins like this can now be updated to be non-blocking.
+* Added a "progress" event so that you can get upload and download progress
+ events.
+* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers
+ requests concurrently using a capped pool size as efficiently as possible.
+* Added `hasListeners()` to EmitterInterface.
+* Removed `GuzzleHttp\ClientInterface::sendAll` and marked
+ `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the
+ recommended way).
+
+### Breaking changes
+
+The breaking changes in this release are relatively minor. The biggest thing to
+look out for is that request and response objects no longer implement fluent
+interfaces.
+
+* Removed the fluent interfaces (i.e., `return $this`) from requests,
+ responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`,
+ `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and
+ `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of
+ why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/.
+ This also makes the Guzzle message interfaces compatible with the current
+ PSR-7 message proposal.
+* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except
+ for the HTTP request functions from function.php, these functions are now
+ implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode`
+ moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to
+ `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to
+ `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be
+ `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php
+ caused problems for many users: they aren't PSR-4 compliant, require an
+ explicit include, and needed an if-guard to ensure that the functions are not
+ declared multiple times.
+* Rewrote adapter layer.
+ * Removing all classes from `GuzzleHttp\Adapter`, these are now
+ implemented as callables that are stored in `GuzzleHttp\Ring\Client`.
+ * Removed the concept of "parallel adapters". Sending requests serially or
+ concurrently is now handled using a single adapter.
+ * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The
+ Transaction object now exposes the request, response, and client as public
+ properties. The getters and setters have been removed.
+* Removed the "headers" event. This event was only useful for changing the
+ body a response once the headers of the response were known. You can implement
+ a similar behavior in a number of ways. One example might be to use a
+ FnStream that has access to the transaction being sent. For example, when the
+ first byte is written, you could check if the response headers match your
+ expectations, and if so, change the actual stream body that is being
+ written to.
+* Removed the `asArray` parameter from
+ `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+ value as an array, then use the newly added `getHeaderAsArray()` method of
+ `MessageInterface`. This change makes the Guzzle interfaces compatible with
+ the PSR-7 interfaces.
+* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add
+ custom request options using double-dispatch (this was an implementation
+ detail). Instead, you should now provide an associative array to the
+ constructor which is a mapping of the request option name mapping to a
+ function that applies the option value to a request.
+* Removed the concept of "throwImmediately" from exceptions and error events.
+ This control mechanism was used to stop a transfer of concurrent requests
+ from completing. This can now be handled by throwing the exception or by
+ cancelling a pool of requests or each outstanding future request individually.
+* Updated to "GuzzleHttp\Streams" 3.0.
+ * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a
+ `maxLen` parameter. This update makes the Guzzle streams project
+ compatible with the current PSR-7 proposal.
+ * `GuzzleHttp\Stream\Stream::__construct`,
+ `GuzzleHttp\Stream\Stream::factory`, and
+ `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second
+ argument. They now accept an associative array of options, including the
+ "size" key and "metadata" key which can be used to provide custom metadata.
+
+## 4.2.2 - 2014-09-08
+
+* Fixed a memory leak in the CurlAdapter when reusing cURL handles.
+* No longer using `request_fulluri` in stream adapter proxies.
+* Relative redirects are now based on the last response, not the first response.
+
+## 4.2.1 - 2014-08-19
+
+* Ensuring that the StreamAdapter does not always add a Content-Type header
+* Adding automated github releases with a phar and zip
+
+## 4.2.0 - 2014-08-17
+
+* Now merging in default options using a case-insensitive comparison.
+ Closes https://github.com/guzzle/guzzle/issues/767
+* Added the ability to automatically decode `Content-Encoding` response bodies
+ using the `decode_content` request option. This is set to `true` by default
+ to decode the response body if it comes over the wire with a
+ `Content-Encoding`. Set this value to `false` to disable decoding the
+ response content, and pass a string to provide a request `Accept-Encoding`
+ header and turn on automatic response decoding. This feature now allows you
+ to pass an `Accept-Encoding` header in the headers of a request but still
+ disable automatic response decoding.
+ Closes https://github.com/guzzle/guzzle/issues/764
+* Added the ability to throw an exception immediately when transferring
+ requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
+* Updating guzzlehttp/streams dependency to ~2.1
+* No longer utilizing the now deprecated namespaced methods from the stream
+ package.
+
+## 4.1.8 - 2014-08-14
+
+* Fixed an issue in the CurlFactory that caused setting the `stream=false`
+ request option to throw an exception.
+ See: https://github.com/guzzle/guzzle/issues/769
+* TransactionIterator now calls rewind on the inner iterator.
+ See: https://github.com/guzzle/guzzle/pull/765
+* You can now set the `Content-Type` header to `multipart/form-data`
+ when creating POST requests to force multipart bodies.
+ See https://github.com/guzzle/guzzle/issues/768
+
+## 4.1.7 - 2014-08-07
+
+* Fixed an error in the HistoryPlugin that caused the same request and response
+ to be logged multiple times when an HTTP protocol error occurs.
+* Ensuring that cURL does not add a default Content-Type when no Content-Type
+ has been supplied by the user. This prevents the adapter layer from modifying
+ the request that is sent over the wire after any listeners may have already
+ put the request in a desired state (e.g., signed the request).
+* Throwing an exception when you attempt to send requests that have the
+ "stream" set to true in parallel using the MultiAdapter.
+* Only calling curl_multi_select when there are active cURL handles. This was
+ previously changed and caused performance problems on some systems due to PHP
+ always selecting until the maximum select timeout.
+* Fixed a bug where multipart/form-data POST fields were not correctly
+ aggregated (e.g., values with "&").
+
+## 4.1.6 - 2014-08-03
+
+* Added helper methods to make it easier to represent messages as strings,
+ including getting the start line and getting headers as a string.
+
+## 4.1.5 - 2014-08-02
+
+* Automatically retrying cURL "Connection died, retrying a fresh connect"
+ errors when possible.
+* cURL implementation cleanup
+* Allowing multiple event subscriber listeners to be registered per event by
+ passing an array of arrays of listener configuration.
+
+## 4.1.4 - 2014-07-22
+
+* Fixed a bug that caused multi-part POST requests with more than one field to
+ serialize incorrectly.
+* Paths can now be set to "0"
+* `ResponseInterface::xml` now accepts a `libxml_options` option and added a
+ missing default argument that was required when parsing XML response bodies.
+* A `save_to` stream is now created lazily, which means that files are not
+ created on disk unless a request succeeds.
+
+## 4.1.3 - 2014-07-15
+
+* Various fixes to multipart/form-data POST uploads
+* Wrapping function.php in an if-statement to ensure Guzzle can be used
+ globally and in a Composer install
+* Fixed an issue with generating and merging in events to an event array
+* POST headers are only applied before sending a request to allow you to change
+ the query aggregator used before uploading
+* Added much more robust query string parsing
+* Fixed various parsing and normalization issues with URLs
+* Fixing an issue where multi-valued headers were not being utilized correctly
+ in the StreamAdapter
+
+## 4.1.2 - 2014-06-18
+
+* Added support for sending payloads with GET requests
+
+## 4.1.1 - 2014-06-08
+
+* Fixed an issue related to using custom message factory options in subclasses
+* Fixed an issue with nested form fields in a multi-part POST
+* Fixed an issue with using the `json` request option for POST requests
+* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar`
+
+## 4.1.0 - 2014-05-27
+
+* Added a `json` request option to easily serialize JSON payloads.
+* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON.
+* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`.
+* Added the ability to provide an emitter to a client in the client constructor.
+* Added the ability to persist a cookie session using $_SESSION.
+* Added a trait that can be used to add event listeners to an iterator.
+* Removed request method constants from RequestInterface.
+* Fixed warning when invalid request start-lines are received.
+* Updated MessageFactory to work with custom request option methods.
+* Updated cacert bundle to latest build.
+
+4.0.2 (2014-04-16)
+------------------
+
+* Proxy requests using the StreamAdapter now properly use request_fulluri (#632)
+* Added the ability to set scalars as POST fields (#628)
+
+## 4.0.1 - 2014-04-04
+
+* The HTTP status code of a response is now set as the exception code of
+ RequestException objects.
+* 303 redirects will now correctly switch from POST to GET requests.
+* The default parallel adapter of a client now correctly uses the MultiAdapter.
+* HasDataTrait now initializes the internal data array as an empty array so
+ that the toArray() method always returns an array.
+
+## 4.0.0 - 2014-03-29
+
+* For more information on the 4.0 transition, see:
+ http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/
+* For information on changes and upgrading, see:
+ https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+* Added `GuzzleHttp\batch()` as a convenience function for sending requests in
+ parallel without needing to write asynchronous code.
+* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`.
+ You can now pass a callable or an array of associative arrays where each
+ associative array contains the "fn", "priority", and "once" keys.
+
+## 4.0.0.rc-2 - 2014-03-25
+
+* Removed `getConfig()` and `setConfig()` from clients to avoid confusion
+ around whether things like base_url, message_factory, etc. should be able to
+ be retrieved or modified.
+* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface
+* functions.php functions were renamed using snake_case to match PHP idioms
+* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and
+ `GUZZLE_CURL_SELECT_TIMEOUT` environment variables
+* Added the ability to specify custom `sendAll()` event priorities
+* Added the ability to specify custom stream context options to the stream
+ adapter.
+* Added a functions.php function for `get_path()` and `set_path()`
+* CurlAdapter and MultiAdapter now use a callable to generate curl resources
+* MockAdapter now properly reads a body and emits a `headers` event
+* Updated Url class to check if a scheme and host are set before adding ":"
+ and "//". This allows empty Url (e.g., "") to be serialized as "".
+* Parsing invalid XML no longer emits warnings
+* Curl classes now properly throw AdapterExceptions
+* Various performance optimizations
+* Streams are created with the faster `Stream\create()` function
+* Marked deprecation_proxy() as internal
+* Test server is now a collection of static methods on a class
+
+## 4.0.0-rc.1 - 2014-03-15
+
+* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+
+## 3.8.1 - 2014-01-28
+
+* Bug: Always using GET requests when redirecting from a 303 response
+* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
+ `Guzzle\Http\ClientInterface::setSslVerification()`
+* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
+* Bug: The body of a request can now be set to `"0"`
+* Sending PHP stream requests no longer forces `HTTP/1.0`
+* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
+ each sub-exception
+* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
+ clobbering everything).
+* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
+* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
+ For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
+* Now properly escaping the regular expression delimiter when matching Cookie domains.
+* Network access is now disabled when loading XML documents
+
+## 3.8.0 - 2013-12-05
+
+* Added the ability to define a POST name for a file
+* JSON response parsing now properly walks additionalProperties
+* cURL error code 18 is now retried automatically in the BackoffPlugin
+* Fixed a cURL error when URLs contain fragments
+* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
+ CurlExceptions
+* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
+* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
+* Fixed a bug that was encountered when parsing empty header parameters
+* UriTemplate now has a `setRegex()` method to match the docs
+* The `debug` request parameter now checks if it is truthy rather than if it exists
+* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
+* Added the ability to combine URLs using strict RFC 3986 compliance
+* Command objects can now return the validation errors encountered by the command
+* Various fixes to cache revalidation (#437 and 29797e5)
+* Various fixes to the AsyncPlugin
+* Cleaned up build scripts
+
+## 3.7.4 - 2013-10-02
+
+* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
+* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
+ (see https://github.com/aws/aws-sdk-php/issues/147)
+* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
+* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
+* Updated the bundled cacert.pem (#419)
+* OauthPlugin now supports adding authentication to headers or query string (#425)
+
+## 3.7.3 - 2013-09-08
+
+* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
+ `CommandTransferException`.
+* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
+* Schemas are only injected into response models when explicitly configured.
+* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
+ an EntityBody.
+* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
+* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
+* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
+* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
+* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests
+* Bug fix: Properly parsing headers that contain commas contained in quotes
+* Bug fix: mimetype guessing based on a filename is now case-insensitive
+
+## 3.7.2 - 2013-08-02
+
+* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
+ See https://github.com/guzzle/guzzle/issues/371
+* Bug fix: Cookie domains are now matched correctly according to RFC 6265
+ See https://github.com/guzzle/guzzle/issues/377
+* Bug fix: GET parameters are now used when calculating an OAuth signature
+* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
+* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
+* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
+ See https://github.com/guzzle/guzzle/issues/379
+* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
+ https://github.com/guzzle/guzzle/pull/380
+* cURL multi cleanup and optimizations
+
+## 3.7.1 - 2013-07-05
+
+* Bug fix: Setting default options on a client now works
+* Bug fix: Setting options on HEAD requests now works. See #352
+* Bug fix: Moving stream factory before send event to before building the stream. See #353
+* Bug fix: Cookies no longer match on IP addresses per RFC 6265
+* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
+* Added `cert` and `ssl_key` as request options
+* `Host` header can now diverge from the host part of a URL if the header is set manually
+* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
+* OAuth parameters are only added via the plugin if they aren't already set
+* Exceptions are now thrown when a URL cannot be parsed
+* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
+* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
+
+## 3.7.0 - 2013-06-10
+
+* See UPGRADING.md for more information on how to upgrade.
+* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
+ request. You can pass a 'request.options' configuration setting to a client to apply default request options to
+ every request created by a client (e.g. default query string variables, headers, curl options, etc.).
+* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
+ See `Guzzle\Http\StaticClient::mount`.
+* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
+ created by a command (e.g. custom headers, query string variables, timeout settings, etc.).
+* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
+ headers of a response
+* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
+ (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
+* ServiceBuilders now support storing and retrieving arbitrary data
+* CachePlugin can now purge all resources for a given URI
+* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
+* CachePlugin now uses the Vary header to determine if a resource is a cache hit
+* `Guzzle\Http\Message\Response` now implements `\Serializable`
+* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
+* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
+* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
+* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
+* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
+* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
+ Symfony users can still use the old version of Monolog.
+* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
+ Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
+* Several performance improvements to `Guzzle\Common\Collection`
+* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+* Added `Guzzle\Stream\StreamInterface::isRepeatable`
+* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
+* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
+* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
+* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
+* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
+* Removed `Guzzle\Http\Message\RequestInterface::canCache`
+* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
+* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
+* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
+ `Guzzle\Common\Version::$emitWarnings` to true.
+* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
+ `$request->getResponseBody()->isRepeatable()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
+ These will work through Guzzle 4.0
+* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
+* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
+* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
+* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+* Marked `Guzzle\Common\Collection::inject()` as deprecated.
+* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
+* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+* Always setting X-cache headers on cached responses
+* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+* Added `CacheStorageInterface::purge($url)`
+* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+## 3.6.0 - 2013-05-29
+
+* ServiceDescription now implements ToArrayInterface
+* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
+* Guzzle can now correctly parse incomplete URLs
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+* Added the ability to cast Model objects to a string to view debug information.
+
+## 3.5.0 - 2013-05-13
+
+* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
+* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove
+ itself from the EventDispatcher)
+* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
+* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
+* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
+ non-existent key
+* Bug: All __call() method arguments are now required (helps with mocking frameworks)
+* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
+ to help with refcount based garbage collection of resources created by sending a request
+* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
+* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
+ HistoryPlugin for a history.
+* Added a `responseBody` alias for the `response_body` location
+* Refactored internals to no longer rely on Response::getRequest()
+* HistoryPlugin can now be cast to a string
+* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
+ and responses that are sent over the wire
+* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
+
+## 3.4.3 - 2013-04-30
+
+* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
+* Added a check to re-extract the temp cacert bundle from the phar before sending each request
+
+## 3.4.2 - 2013-04-29
+
+* Bug fix: Stream objects now work correctly with "a" and "a+" modes
+* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
+* Bug fix: AsyncPlugin no longer forces HEAD requests
+* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
+* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
+* Setting a response on a request will write to the custom request body from the response body if one is specified
+* LogPlugin now writes to php://output when STDERR is undefined
+* Added the ability to set multiple POST files for the same key in a single call
+* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
+* Added the ability to queue CurlExceptions to the MockPlugin
+* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
+* Configuration loading now allows remote files
+
+## 3.4.1 - 2013-04-16
+
+* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
+ handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
+* Exceptions are now properly grouped when sending requests in parallel
+* Redirects are now properly aggregated when a multi transaction fails
+* Redirects now set the response on the original object even in the event of a failure
+* Bug fix: Model names are now properly set even when using $refs
+* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
+* Added support for oauth_callback in OAuth signatures
+* Added support for oauth_verifier in OAuth signatures
+* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
+
+## 3.4.0 - 2013-04-11
+
+* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
+* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
+* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
+* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
+* Bug fix: Added `number` type to service descriptions.
+* Bug fix: empty parameters are removed from an OAuth signature
+* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
+* Bug fix: Fixed "array to string" error when validating a union of types in a service description
+* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
+* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
+* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
+* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
+* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
+ the Content-Type can be determined based on the entity body or the path of the request.
+* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
+* Added support for a PSR-3 LogAdapter.
+* Added a `command.after_prepare` event
+* Added `oauth_callback` parameter to the OauthPlugin
+* Added the ability to create a custom stream class when using a stream factory
+* Added a CachingEntityBody decorator
+* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
+* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
+* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
+* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
+ means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
+ POST fields or files (the latter is only used when emulating a form POST in the browser).
+* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
+
+## 3.3.1 - 2013-03-10
+
+* Added the ability to create PHP streaming responses from HTTP requests
+* Bug fix: Running any filters when parsing response headers with service descriptions
+* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
+* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
+ response location visitors.
+* Bug fix: Removed the possibility of creating configuration files with circular dependencies
+* RequestFactory::create() now uses the key of a POST file when setting the POST file name
+* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
+
+## 3.3.0 - 2013-03-03
+
+* A large number of performance optimizations have been made
+* Bug fix: Added 'wb' as a valid write mode for streams
+* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
+* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
+* BC: Removed `Guzzle\Http\Utils` class
+* BC: Setting a service description on a client will no longer modify the client's command factories.
+* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
+ the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
+ lowercase
+* Operation parameter objects are now lazy loaded internally
+* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
+* Added support for instantiating responseType=class responseClass classes. Classes must implement
+ `Guzzle\Service\Command\ResponseClassInterface`
+* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
+ additional properties also support locations and can be used to parse JSON responses where the outermost part of the
+ JSON is an array
+* Added support for nested renaming of JSON models (rename sentAs to name)
+* CachePlugin
+ * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
+ * Debug headers can now added to cached response in the CachePlugin
+
+## 3.2.0 - 2013-02-14
+
+* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
+* URLs with no path no longer contain a "/" by default
+* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
+* BadResponseException no longer includes the full request and response message
+* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
+* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
+* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
+* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
+* xmlEncoding can now be customized for the XML declaration of a XML service description operation
+* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
+ aggregation and no longer uses callbacks
+* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
+* Bug fix: Filters were not always invoked for array service description parameters
+* Bug fix: Redirects now use a target response body rather than a temporary response body
+* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
+* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
+
+## 3.1.2 - 2013-01-27
+
+* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
+ response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
+* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
+* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
+* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
+* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
+
+## 3.1.1 - 2013-01-20
+
+* Adding wildcard support to Guzzle\Common\Collection::getPath()
+* Adding alias support to ServiceBuilder configs
+* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
+
+## 3.1.0 - 2013-01-12
+
+* BC: CurlException now extends from RequestException rather than BadResponseException
+* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
+* Added getData to ServiceDescriptionInterface
+* Added context array to RequestInterface::setState()
+* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
+* Bug: Adding required content-type when JSON request visitor adds JSON to a command
+* Bug: Fixing the serialization of a service description with custom data
+* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
+ an array of successful and failed responses
+* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
+* Added Guzzle\Http\IoEmittingEntityBody
+* Moved command filtration from validators to location visitors
+* Added `extends` attributes to service description parameters
+* Added getModels to ServiceDescriptionInterface
+
+## 3.0.7 - 2012-12-19
+
+* Fixing phar detection when forcing a cacert to system if null or true
+* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
+* Cleaning up `Guzzle\Common\Collection::inject` method
+* Adding a response_body location to service descriptions
+
+## 3.0.6 - 2012-12-09
+
+* CurlMulti performance improvements
+* Adding setErrorResponses() to Operation
+* composer.json tweaks
+
+## 3.0.5 - 2012-11-18
+
+* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
+* Bug: Response body can now be a string containing "0"
+* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
+* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
+* Added support for XML attributes in service description responses
+* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
+* Added better mimetype guessing to requests and post files
+
+## 3.0.4 - 2012-11-11
+
+* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
+* Bug: Cookies can now be added that have a name, domain, or value set to "0"
+* Bug: Using the system cacert bundle when using the Phar
+* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
+* Enhanced cookie jar de-duplication
+* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
+* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
+* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
+
+## 3.0.3 - 2012-11-04
+
+* Implementing redirects in PHP rather than cURL
+* Added PECL URI template extension and using as default parser if available
+* Bug: Fixed Content-Length parsing of Response factory
+* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
+* Adding ToArrayInterface throughout library
+* Fixing OauthPlugin to create unique nonce values per request
+
+## 3.0.2 - 2012-10-25
+
+* Magic methods are enabled by default on clients
+* Magic methods return the result of a command
+* Service clients no longer require a base_url option in the factory
+* Bug: Fixed an issue with URI templates where null template variables were being expanded
+
+## 3.0.1 - 2012-10-22
+
+* Models can now be used like regular collection objects by calling filter, map, etc.
+* Models no longer require a Parameter structure or initial data in the constructor
+* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
+
+## 3.0.0 - 2012-10-15
+
+* Rewrote service description format to be based on Swagger
+ * Now based on JSON schema
+ * Added nested input structures and nested response models
+ * Support for JSON and XML input and output models
+ * Renamed `commands` to `operations`
+ * Removed dot class notation
+ * Removed custom types
+* Broke the project into smaller top-level namespaces to be more component friendly
+* Removed support for XML configs and descriptions. Use arrays or JSON files.
+* Removed the Validation component and Inspector
+* Moved all cookie code to Guzzle\Plugin\Cookie
+* Magic methods on a Guzzle\Service\Client now return the command un-executed.
+* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
+* Now shipping with cURL's CA certs and using it by default
+* Added previousResponse() method to response objects
+* No longer sending Accept and Accept-Encoding headers on every request
+* Only sending an Expect header by default when a payload is greater than 1MB
+* Added/moved client options:
+ * curl.blacklist to curl.option.blacklist
+ * Added ssl.certificate_authority
+* Added a Guzzle\Iterator component
+* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
+* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
+* Added a more robust caching plugin
+* Added setBody to response objects
+* Updating LogPlugin to use a more flexible MessageFormatter
+* Added a completely revamped build process
+* Cleaning up Collection class and removing default values from the get method
+* Fixed ZF2 cache adapters
+
+## 2.8.8 - 2012-10-15
+
+* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
+
+## 2.8.7 - 2012-09-30
+
+* Bug: Fixed config file aliases for JSON includes
+* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
+* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
+* Bug: Hardening request and response parsing to account for missing parts
+* Bug: Fixed PEAR packaging
+* Bug: Fixed Request::getInfo
+* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
+* Adding the ability for the namespace Iterator factory to look in multiple directories
+* Added more getters/setters/removers from service descriptions
+* Added the ability to remove POST fields from OAuth signatures
+* OAuth plugin now supports 2-legged OAuth
+
+## 2.8.6 - 2012-09-05
+
+* Added the ability to modify and build service descriptions
+* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
+* Added a `json` parameter location
+* Now allowing dot notation for classes in the CacheAdapterFactory
+* Using the union of two arrays rather than an array_merge when extending service builder services and service params
+* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
+ in service builder config files.
+* Services defined in two different config files that include one another will by default replace the previously
+ defined service, but you can now create services that extend themselves and merge their settings over the previous
+* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
+ '_default' with a default JSON configuration file.
+
+## 2.8.5 - 2012-08-29
+
+* Bug: Suppressed empty arrays from URI templates
+* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
+* Added support for HTTP responses that do not contain a reason phrase in the start-line
+* AbstractCommand commands are now invokable
+* Added a way to get the data used when signing an Oauth request before a request is sent
+
+## 2.8.4 - 2012-08-15
+
+* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
+* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
+* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
+* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
+* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
+* Added additional response status codes
+* Removed SSL information from the default User-Agent header
+* DELETE requests can now send an entity body
+* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
+* Added the ability of the MockPlugin to consume mocked request bodies
+* LogPlugin now exposes request and response objects in the extras array
+
+## 2.8.3 - 2012-07-30
+
+* Bug: Fixed a case where empty POST requests were sent as GET requests
+* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
+* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
+* Added multiple inheritance to service description commands
+* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()`
+* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
+* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
+
+## 2.8.2 - 2012-07-24
+
+* Bug: Query string values set to 0 are no longer dropped from the query string
+* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`
+* Bug: `+` is now treated as an encoded space when parsing query strings
+* QueryString and Collection performance improvements
+* Allowing dot notation for class paths in filters attribute of a service descriptions
+
+## 2.8.1 - 2012-07-16
+
+* Loosening Event Dispatcher dependency
+* POST redirects can now be customized using CURLOPT_POSTREDIR
+
+## 2.8.0 - 2012-07-15
+
+* BC: Guzzle\Http\Query
+ * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
+ * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
+ * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
+ * Changed the aggregation functions of QueryString to be static methods
+ * Can now use fromString() with querystrings that have a leading ?
+* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters
+* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
+* Cookies are no longer URL decoded by default
+* Bug: URI template variables set to null are no longer expanded
+
+## 2.7.2 - 2012-07-02
+
+* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
+* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
+* CachePlugin now allows for a custom request parameter function to check if a request can be cached
+* Bug fix: CachePlugin now only caches GET and HEAD requests by default
+* Bug fix: Using header glue when transferring headers over the wire
+* Allowing deeply nested arrays for composite variables in URI templates
+* Batch divisors can now return iterators or arrays
+
+## 2.7.1 - 2012-06-26
+
+* Minor patch to update version number in UA string
+* Updating build process
+
+## 2.7.0 - 2012-06-25
+
+* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
+* BC: Removed magic setX methods from commands
+* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
+* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
+* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
+* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
+* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
+* Added the ability to set POST fields and files in a service description
+* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
+* Adding a command.before_prepare event to clients
+* Added BatchClosureTransfer and BatchClosureDivisor
+* BatchTransferException now includes references to the batch divisor and transfer strategies
+* Fixed some tests so that they pass more reliably
+* Added Guzzle\Common\Log\ArrayLogAdapter
+
+## 2.6.6 - 2012-06-10
+
+* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
+* BC: Removing Guzzle\Service\Command\CommandSet
+* Adding generic batching system (replaces the batch queue plugin and command set)
+* Updating ZF cache and log adapters and now using ZF's composer repository
+* Bug: Setting the name of each ApiParam when creating through an ApiCommand
+* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
+* Bug: Changed the default cookie header casing back to 'Cookie'
+
+## 2.6.5 - 2012-06-03
+
+* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
+* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
+* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
+* BC: Renaming methods in the CookieJarInterface
+* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
+* Making the default glue for HTTP headers ';' instead of ','
+* Adding a removeValue to Guzzle\Http\Message\Header
+* Adding getCookies() to request interface.
+* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
+
+## 2.6.4 - 2012-05-30
+
+* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
+* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
+* Bug: Fixing magic method command calls on clients
+* Bug: Email constraint only validates strings
+* Bug: Aggregate POST fields when POST files are present in curl handle
+* Bug: Fixing default User-Agent header
+* Bug: Only appending or prepending parameters in commands if they are specified
+* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
+* Allowing the use of dot notation for class namespaces when using instance_of constraint
+* Added any_match validation constraint
+* Added an AsyncPlugin
+* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
+* Allowing the result of a command object to be changed
+* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
+
+## 2.6.3 - 2012-05-23
+
+* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
+* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
+* You can now use an array of data when creating PUT request bodies in the request factory.
+* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
+* [Http] Adding support for Content-Type in multipart POST uploads per upload
+* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
+* Adding more POST data operations for easier manipulation of POST data.
+* You can now set empty POST fields.
+* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
+* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
+* CS updates
+
+## 2.6.2 - 2012-05-19
+
+* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
+
+## 2.6.1 - 2012-05-19
+
+* [BC] Removing 'path' support in service descriptions. Use 'uri'.
+* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
+* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it.
+* [BC] Removing Guzzle\Common\XmlElement.
+* All commands, both dynamic and concrete, have ApiCommand objects.
+* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
+* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
+* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
+
+## 2.6.0 - 2012-05-15
+
+* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
+* [BC] Executing a Command returns the result of the command rather than the command
+* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
+* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
+* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
+* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
+* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
+* [BC] Guzzle\Guzzle is now deprecated
+* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
+* Adding Guzzle\Version class to give version information about Guzzle
+* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
+* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
+* ServiceDescription and ServiceBuilder are now cacheable using similar configs
+* Changing the format of XML and JSON service builder configs. Backwards compatible.
+* Cleaned up Cookie parsing
+* Trimming the default Guzzle User-Agent header
+* Adding a setOnComplete() method to Commands that is called when a command completes
+* Keeping track of requests that were mocked in the MockPlugin
+* Fixed a caching bug in the CacheAdapterFactory
+* Inspector objects can be injected into a Command object
+* Refactoring a lot of code and tests to be case insensitive when dealing with headers
+* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
+* Adding the ability to set global option overrides to service builder configs
+* Adding the ability to include other service builder config files from within XML and JSON files
+* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
+
+## 2.5.0 - 2012-05-08
+
+* Major performance improvements
+* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.
+* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
+* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}"
+* Added the ability to passed parameters to all requests created by a client
+* Added callback functionality to the ExponentialBackoffPlugin
+* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
+* Rewinding request stream bodies when retrying requests
+* Exception is thrown when JSON response body cannot be decoded
+* Added configurable magic method calls to clients and commands. This is off by default.
+* Fixed a defect that added a hash to every parsed URL part
+* Fixed duplicate none generation for OauthPlugin.
+* Emitting an event each time a client is generated by a ServiceBuilder
+* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
+* cache.* request parameters should be renamed to params.cache.*
+* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle.
+* Added the ability to disable type validation of service descriptions
+* ServiceDescriptions and ServiceBuilders are now Serializable
diff --git a/instafeed/vendor/guzzlehttp/guzzle/Dockerfile b/instafeed/vendor/guzzlehttp/guzzle/Dockerfile
new file mode 100755
index 0000000..f6a0952
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/Dockerfile
@@ -0,0 +1,18 @@
+FROM composer:latest as setup
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+RUN set -xe \
+ && composer init --name=guzzlehttp/test --description="Simple project for testing Guzzle scripts" --author="Márk Sági-Kazár " --no-interaction \
+ && composer require guzzlehttp/guzzle
+
+
+FROM php:7.3
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+COPY --from=setup /guzzle /guzzle
diff --git a/instafeed/vendor/guzzlehttp/guzzle/LICENSE b/instafeed/vendor/guzzlehttp/guzzle/LICENSE
new file mode 100755
index 0000000..50a177b
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/guzzlehttp/guzzle/README.md b/instafeed/vendor/guzzlehttp/guzzle/README.md
new file mode 100755
index 0000000..a5ef18a
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/README.md
@@ -0,0 +1,90 @@
+Guzzle, PHP HTTP client
+=======================
+
+[](https://github.com/guzzle/guzzle/releases)
+[](https://travis-ci.org/guzzle/guzzle)
+[](https://packagist.org/packages/guzzlehttp/guzzle)
+
+Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
+trivial to integrate with web services.
+
+- Simple interface for building query strings, POST requests, streaming large
+ uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
+ etc...
+- Can send both synchronous and asynchronous requests using the same interface.
+- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
+ to utilize other PSR-7 compatible libraries with Guzzle.
+- Abstracts away the underlying HTTP transport, allowing you to write
+ environment and transport agnostic code; i.e., no hard dependency on cURL,
+ PHP streams, sockets, or non-blocking event loops.
+- Middleware system allows you to augment and compose client behavior.
+
+```php
+$client = new \GuzzleHttp\Client();
+$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
+
+echo $response->getStatusCode(); # 200
+echo $response->getHeaderLine('content-type'); # 'application/json; charset=utf8'
+echo $response->getBody(); # '{"id": 1420053, "name": "guzzle", ...}'
+
+# Send an asynchronous request.
+$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
+$promise = $client->sendAsync($request)->then(function ($response) {
+ echo 'I completed! ' . $response->getBody();
+});
+
+$promise->wait();
+```
+
+## Help and docs
+
+- [Documentation](http://guzzlephp.org/)
+- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle)
+- [Gitter](https://gitter.im/guzzle/guzzle)
+
+
+## Installing Guzzle
+
+The recommended way to install Guzzle is through
+[Composer](http://getcomposer.org).
+
+```bash
+# Install Composer
+curl -sS https://getcomposer.org/installer | php
+```
+
+Next, run the Composer command to install the latest stable version of Guzzle:
+
+```bash
+composer require guzzlehttp/guzzle
+```
+
+After installing, you need to require Composer's autoloader:
+
+```php
+require 'vendor/autoload.php';
+```
+
+You can then later update Guzzle using composer:
+
+ ```bash
+composer update
+ ```
+
+
+## Version Guidance
+
+| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
+|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------|
+| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 |
+| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 |
+| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 |
+| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 |
+
+[guzzle-3-repo]: https://github.com/guzzle/guzzle3
+[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
+[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
+[guzzle-6-repo]: https://github.com/guzzle/guzzle
+[guzzle-3-docs]: http://guzzle3.readthedocs.org
+[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/
+[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/
diff --git a/instafeed/vendor/guzzlehttp/guzzle/UPGRADING.md b/instafeed/vendor/guzzlehttp/guzzle/UPGRADING.md
new file mode 100755
index 0000000..91d1dcc
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/UPGRADING.md
@@ -0,0 +1,1203 @@
+Guzzle Upgrade Guide
+====================
+
+5.0 to 6.0
+----------
+
+Guzzle now uses [PSR-7](http://www.php-fig.org/psr/psr-7/) for HTTP messages.
+Due to the fact that these messages are immutable, this prompted a refactoring
+of Guzzle to use a middleware based system rather than an event system. Any
+HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be
+updated to work with the new immutable PSR-7 request and response objects. Any
+event listeners or subscribers need to be updated to become middleware
+functions that wrap handlers (or are injected into a
+`GuzzleHttp\HandlerStack`).
+
+- Removed `GuzzleHttp\BatchResults`
+- Removed `GuzzleHttp\Collection`
+- Removed `GuzzleHttp\HasDataTrait`
+- Removed `GuzzleHttp\ToArrayInterface`
+- The `guzzlehttp/streams` dependency has been removed. Stream functionality
+ is now present in the `GuzzleHttp\Psr7` namespace provided by the
+ `guzzlehttp/psr7` package.
+- Guzzle no longer uses ReactPHP promises and now uses the
+ `guzzlehttp/promises` library. We use a custom promise library for three
+ significant reasons:
+ 1. React promises (at the time of writing this) are recursive. Promise
+ chaining and promise resolution will eventually blow the stack. Guzzle
+ promises are not recursive as they use a sort of trampolining technique.
+ Note: there has been movement in the React project to modify promises to
+ no longer utilize recursion.
+ 2. Guzzle needs to have the ability to synchronously block on a promise to
+ wait for a result. Guzzle promises allows this functionality (and does
+ not require the use of recursion).
+ 3. Because we need to be able to wait on a result, doing so using React
+ promises requires wrapping react promises with RingPHP futures. This
+ overhead is no longer needed, reducing stack sizes, reducing complexity,
+ and improving performance.
+- `GuzzleHttp\Mimetypes` has been moved to a function in
+ `GuzzleHttp\Psr7\mimetype_from_extension` and
+ `GuzzleHttp\Psr7\mimetype_from_filename`.
+- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
+ strings must now be passed into request objects as strings, or provided to
+ the `query` request option when creating requests with clients. The `query`
+ option uses PHP's `http_build_query` to convert an array to a string. If you
+ need a different serialization technique, you will need to pass the query
+ string in as a string. There are a couple helper functions that will make
+ working with query strings easier: `GuzzleHttp\Psr7\parse_query` and
+ `GuzzleHttp\Psr7\build_query`.
+- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
+ system based on PSR-7, using RingPHP and it's middleware system as well adds
+ more complexity than the benefits it provides. All HTTP handlers that were
+ present in RingPHP have been modified to work directly with PSR-7 messages
+ and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces
+ complexity in Guzzle, removes a dependency, and improves performance. RingPHP
+ will be maintained for Guzzle 5 support, but will no longer be a part of
+ Guzzle 6.
+- As Guzzle now uses a middleware based systems the event system and RingPHP
+ integration has been removed. Note: while the event system has been removed,
+ it is possible to add your own type of event system that is powered by the
+ middleware system.
+ - Removed the `Event` namespace.
+ - Removed the `Subscriber` namespace.
+ - Removed `Transaction` class
+ - Removed `RequestFsm`
+ - Removed `RingBridge`
+ - `GuzzleHttp\Subscriber\Cookie` is now provided by
+ `GuzzleHttp\Middleware::cookies`
+ - `GuzzleHttp\Subscriber\HttpError` is now provided by
+ `GuzzleHttp\Middleware::httpError`
+ - `GuzzleHttp\Subscriber\History` is now provided by
+ `GuzzleHttp\Middleware::history`
+ - `GuzzleHttp\Subscriber\Mock` is now provided by
+ `GuzzleHttp\Handler\MockHandler`
+ - `GuzzleHttp\Subscriber\Prepare` is now provided by
+ `GuzzleHttp\PrepareBodyMiddleware`
+ - `GuzzleHttp\Subscriber\Redirect` is now provided by
+ `GuzzleHttp\RedirectMiddleware`
+- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
+ `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
+- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
+ functions under the `GuzzleHttp` namespace. This requires either a Composer
+ based autoloader or you to include functions.php.
+- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
+ `GuzzleHttp\ClientInterface::getConfig`.
+- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
+- The `json` and `xml` methods of response objects has been removed. With the
+ migration to strictly adhering to PSR-7 as the interface for Guzzle messages,
+ adding methods to message interfaces would actually require Guzzle messages
+ to extend from PSR-7 messages rather then work with them directly.
+
+## Migrating to middleware
+
+The change to PSR-7 unfortunately required significant refactoring to Guzzle
+due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event
+system from plugins. The event system relied on mutability of HTTP messages and
+side effects in order to work. With immutable messages, you have to change your
+workflow to become more about either returning a value (e.g., functional
+middlewares) or setting a value on an object. Guzzle v6 has chosen the
+functional middleware approach.
+
+Instead of using the event system to listen for things like the `before` event,
+you now create a stack based middleware function that intercepts a request on
+the way in and the promise of the response on the way out. This is a much
+simpler and more predictable approach than the event system and works nicely
+with PSR-7 middleware. Due to the use of promises, the middleware system is
+also asynchronous.
+
+v5:
+
+```php
+use GuzzleHttp\Event\BeforeEvent;
+$client = new GuzzleHttp\Client();
+// Get the emitter and listen to the before event.
+$client->getEmitter()->on('before', function (BeforeEvent $e) {
+ // Guzzle v5 events relied on mutation
+ $e->getRequest()->setHeader('X-Foo', 'Bar');
+});
+```
+
+v6:
+
+In v6, you can modify the request before it is sent using the `mapRequest`
+middleware. The idiomatic way in v6 to modify the request/response lifecycle is
+to setup a handler middleware stack up front and inject the handler into a
+client.
+
+```php
+use GuzzleHttp\Middleware;
+// Create a handler stack that has all of the default middlewares attached
+$handler = GuzzleHttp\HandlerStack::create();
+// Push the handler onto the handler stack
+$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
+ // Notice that we have to return a request object
+ return $request->withHeader('X-Foo', 'Bar');
+}));
+// Inject the handler into the client
+$client = new GuzzleHttp\Client(['handler' => $handler]);
+```
+
+## POST Requests
+
+This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params)
+and `multipart` request options. `form_params` is an associative array of
+strings or array of strings and is used to serialize an
+`application/x-www-form-urlencoded` POST request. The
+[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart)
+option is now used to send a multipart/form-data POST request.
+
+`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
+POST files to a multipart/form-data request.
+
+The `body` option no longer accepts an array to send POST requests. Please use
+`multipart` or `form_params` instead.
+
+The `base_url` option has been renamed to `base_uri`.
+
+4.x to 5.0
+----------
+
+## Rewritten Adapter Layer
+
+Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send
+HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
+is still supported, but it has now been renamed to `handler`. Instead of
+passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
+`callable` that follows the RingPHP specification.
+
+## Removed Fluent Interfaces
+
+[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil)
+from the following classes:
+
+- `GuzzleHttp\Collection`
+- `GuzzleHttp\Url`
+- `GuzzleHttp\Query`
+- `GuzzleHttp\Post\PostBody`
+- `GuzzleHttp\Cookie\SetCookie`
+
+## Removed functions.php
+
+Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following
+functions can be used as replacements.
+
+- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode`
+- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
+- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
+- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
+ deprecated in favor of using `GuzzleHttp\Pool::batch()`.
+
+The "procedural" global client has been removed with no replacement (e.g.,
+`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client`
+object as a replacement.
+
+## `throwImmediately` has been removed
+
+The concept of "throwImmediately" has been removed from exceptions and error
+events. This control mechanism was used to stop a transfer of concurrent
+requests from completing. This can now be handled by throwing the exception or
+by cancelling a pool of requests or each outstanding future request
+individually.
+
+## headers event has been removed
+
+Removed the "headers" event. This event was only useful for changing the
+body a response once the headers of the response were known. You can implement
+a similar behavior in a number of ways. One example might be to use a
+FnStream that has access to the transaction being sent. For example, when the
+first byte is written, you could check if the response headers match your
+expectations, and if so, change the actual stream body that is being
+written to.
+
+## Updates to HTTP Messages
+
+Removed the `asArray` parameter from
+`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+value as an array, then use the newly added `getHeaderAsArray()` method of
+`MessageInterface`. This change makes the Guzzle interfaces compatible with
+the PSR-7 interfaces.
+
+3.x to 4.0
+----------
+
+## Overarching changes:
+
+- Now requires PHP 5.4 or greater.
+- No longer requires cURL to send requests.
+- Guzzle no longer wraps every exception it throws. Only exceptions that are
+ recoverable are now wrapped by Guzzle.
+- Various namespaces have been removed or renamed.
+- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
+ based on the Symfony EventDispatcher is
+ now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant
+ speed and functionality improvements).
+
+Changes per Guzzle 3.x namespace are described below.
+
+## Batch
+
+The `Guzzle\Batch` namespace has been removed. This is best left to
+third-parties to implement on top of Guzzle's core HTTP library.
+
+## Cache
+
+The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement
+has been implemented yet, but hoping to utilize a PSR cache interface).
+
+## Common
+
+- Removed all of the wrapped exceptions. It's better to use the standard PHP
+ library for unrecoverable exceptions.
+- `FromConfigInterface` has been removed.
+- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
+ at `GuzzleHttp\ClientInterface::VERSION`.
+
+### Collection
+
+- `getAll` has been removed. Use `toArray` to convert a collection to an array.
+- `inject` has been removed.
+- `keySearch` has been removed.
+- `getPath` no longer supports wildcard expressions. Use something better like
+ JMESPath for this.
+- `setPath` now supports appending to an existing array via the `[]` notation.
+
+### Events
+
+Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
+`GuzzleHttp\Event\Emitter`.
+
+- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by
+ `GuzzleHttp\Event\EmitterInterface`.
+- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
+ `GuzzleHttp\Event\Emitter`.
+- `Symfony\Component\EventDispatcher\Event` is replaced by
+ `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
+ `GuzzleHttp\Event\EventInterface`.
+- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
+ `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
+ event emitter of a request, client, etc. now uses the `getEmitter` method
+ rather than the `getDispatcher` method.
+
+#### Emitter
+
+- Use the `once()` method to add a listener that automatically removes itself
+ the first time it is invoked.
+- Use the `listeners()` method to retrieve a list of event listeners rather than
+ the `getListeners()` method.
+- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
+- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
+ `removeSubscriber()`.
+
+```php
+$mock = new Mock();
+// 3.x
+$request->getEventDispatcher()->addSubscriber($mock);
+$request->getEventDispatcher()->removeSubscriber($mock);
+// 4.x
+$request->getEmitter()->attach($mock);
+$request->getEmitter()->detach($mock);
+```
+
+Use the `on()` method to add a listener rather than the `addListener()` method.
+
+```php
+// 3.x
+$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
+// 4.x
+$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
+```
+
+## Http
+
+### General changes
+
+- The cacert.pem certificate has been moved to `src/cacert.pem`.
+- Added the concept of adapters that are used to transfer requests over the
+ wire.
+- Simplified the event system.
+- Sending requests in parallel is still possible, but batching is no longer a
+ concept of the HTTP layer. Instead, you must use the `complete` and `error`
+ events to asynchronously manage parallel request transfers.
+- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
+- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
+- QueryAggregators have been rewritten so that they are simply callable
+ functions.
+- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
+ `functions.php` for an easy to use static client instance.
+- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
+ `GuzzleHttp\Exception\TransferException`.
+
+### Client
+
+Calling methods like `get()`, `post()`, `head()`, etc. no longer create and
+return a request, but rather creates a request, sends the request, and returns
+the response.
+
+```php
+// 3.0
+$request = $client->get('/');
+$response = $request->send();
+
+// 4.0
+$response = $client->get('/');
+
+// or, to mirror the previous behavior
+$request = $client->createRequest('GET', '/');
+$response = $client->send($request);
+```
+
+`GuzzleHttp\ClientInterface` has changed.
+
+- The `send` method no longer accepts more than one request. Use `sendAll` to
+ send multiple requests in parallel.
+- `setUserAgent()` has been removed. Use a default request option instead. You
+ could, for example, do something like:
+ `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`.
+- `setSslVerification()` has been removed. Use default request options instead,
+ like `$client->setConfig('defaults/verify', true)`.
+
+`GuzzleHttp\Client` has changed.
+
+- The constructor now accepts only an associative array. You can include a
+ `base_url` string or array to use a URI template as the base URL of a client.
+ You can also specify a `defaults` key that is an associative array of default
+ request options. You can pass an `adapter` to use a custom adapter,
+ `batch_adapter` to use a custom adapter for sending requests in parallel, or
+ a `message_factory` to change the factory used to create HTTP requests and
+ responses.
+- The client no longer emits a `client.create_request` event.
+- Creating requests with a client no longer automatically utilize a URI
+ template. You must pass an array into a creational method (e.g.,
+ `createRequest`, `get`, `put`, etc.) in order to expand a URI template.
+
+### Messages
+
+Messages no longer have references to their counterparts (i.e., a request no
+longer has a reference to it's response, and a response no loger has a
+reference to its request). This association is now managed through a
+`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to
+these transaction objects using request events that are emitted over the
+lifecycle of a request.
+
+#### Requests with a body
+
+- `GuzzleHttp\Message\EntityEnclosingRequest` and
+ `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
+ separation between requests that contain a body and requests that do not
+ contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
+ handles both use cases.
+- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
+ `GuzzleHttp\Message\ResponseInterface`.
+- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
+ `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
+ both requests and responses and is implemented in
+ `GuzzleHttp\Message\MessageFactory`.
+- POST field and file methods have been removed from the request object. You
+ must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface`
+ to control the format of a POST body. Requests that are created using a
+ standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
+ a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
+ the method is POST and no body is provided.
+
+```php
+$request = $client->createRequest('POST', '/');
+$request->getBody()->setField('foo', 'bar');
+$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
+```
+
+#### Headers
+
+- `GuzzleHttp\Message\Header` has been removed. Header values are now simply
+ represented by an array of values or as a string. Header values are returned
+ as a string by default when retrieving a header value from a message. You can
+ pass an optional argument of `true` to retrieve a header value as an array
+ of strings instead of a single concatenated string.
+- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
+ `GuzzleHttp\Post`. This interface has been simplified and now allows the
+ addition of arbitrary headers.
+- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
+ of the custom headers are now handled separately in specific
+ subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
+ been updated to properly handle headers that contain parameters (like the
+ `Link` header).
+
+#### Responses
+
+- `GuzzleHttp\Message\Response::getInfo()` and
+ `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event
+ system to retrieve this type of information.
+- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed.
+- `GuzzleHttp\Message\Response::getMessage()` has been removed.
+- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific
+ methods have moved to the CacheSubscriber.
+- Header specific helper functions like `getContentMd5()` have been removed.
+ Just use `getHeader('Content-MD5')` instead.
+- `GuzzleHttp\Message\Response::setRequest()` and
+ `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event
+ system to work with request and response objects as a transaction.
+- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the
+ Redirect subscriber instead.
+- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have
+ been removed. Use `getStatusCode()` instead.
+
+#### Streaming responses
+
+Streaming requests can now be created by a client directly, returning a
+`GuzzleHttp\Message\ResponseInterface` object that contains a body stream
+referencing an open PHP HTTP stream.
+
+```php
+// 3.0
+use Guzzle\Stream\PhpStreamRequestFactory;
+$request = $client->get('/');
+$factory = new PhpStreamRequestFactory();
+$stream = $factory->fromRequest($request);
+$data = $stream->read(1024);
+
+// 4.0
+$response = $client->get('/', ['stream' => true]);
+// Read some data off of the stream in the response body
+$data = $response->getBody()->read(1024);
+```
+
+#### Redirects
+
+The `configureRedirects()` method has been removed in favor of a
+`allow_redirects` request option.
+
+```php
+// Standard redirects with a default of a max of 5 redirects
+$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);
+
+// Strict redirects with a custom number of redirects
+$request = $client->createRequest('GET', '/', [
+ 'allow_redirects' => ['max' => 5, 'strict' => true]
+]);
+```
+
+#### EntityBody
+
+EntityBody interfaces and classes have been removed or moved to
+`GuzzleHttp\Stream`. All classes and interfaces that once required
+`GuzzleHttp\EntityBodyInterface` now require
+`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no
+longer uses `GuzzleHttp\EntityBody::factory` but now uses
+`GuzzleHttp\Stream\Stream::factory` or even better:
+`GuzzleHttp\Stream\create()`.
+
+- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
+- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
+- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
+- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
+- `Guzzle\Http\IoEmittyinEntityBody` has been removed.
+
+#### Request lifecycle events
+
+Requests previously submitted a large number of requests. The number of events
+emitted over the lifecycle of a request has been significantly reduced to make
+it easier to understand how to extend the behavior of a request. All events
+emitted during the lifecycle of a request now emit a custom
+`GuzzleHttp\Event\EventInterface` object that contains context providing
+methods and a way in which to modify the transaction at that specific point in
+time (e.g., intercept the request and set a response on the transaction).
+
+- `request.before_send` has been renamed to `before` and now emits a
+ `GuzzleHttp\Event\BeforeEvent`
+- `request.complete` has been renamed to `complete` and now emits a
+ `GuzzleHttp\Event\CompleteEvent`.
+- `request.sent` has been removed. Use `complete`.
+- `request.success` has been removed. Use `complete`.
+- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
+- `request.exception` has been removed. Use `error`.
+- `request.receive.status_line` has been removed.
+- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
+ maintain a status update.
+- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
+ intercept writes.
+- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
+ intercept reads.
+
+`headers` is a new event that is emitted after the response headers of a
+request have been received before the body of the response is downloaded. This
+event emits a `GuzzleHttp\Event\HeadersEvent`.
+
+You can intercept a request and inject a response using the `intercept()` event
+of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
+`GuzzleHttp\Event\ErrorEvent` event.
+
+See: http://docs.guzzlephp.org/en/latest/events.html
+
+## Inflection
+
+The `Guzzle\Inflection` namespace has been removed. This is not a core concern
+of Guzzle.
+
+## Iterator
+
+The `Guzzle\Iterator` namespace has been removed.
+
+- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and
+ `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
+ Guzzle itself.
+- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
+ class is shipped with PHP 5.4.
+- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because
+ it's easier to just wrap an iterator in a generator that maps values.
+
+For a replacement of these iterators, see https://github.com/nikic/iter
+
+## Log
+
+The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
+`Guzzle\Log` namespace has been removed. Guzzle now relies on
+`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been
+moved to `GuzzleHttp\Subscriber\Log\Formatter`.
+
+## Parser
+
+The `Guzzle\Parser` namespace has been removed. This was previously used to
+make it possible to plug in custom parsers for cookies, messages, URI
+templates, and URLs; however, this level of complexity is not needed in Guzzle
+so it has been removed.
+
+- Cookie: Cookie parsing logic has been moved to
+ `GuzzleHttp\Cookie\SetCookie::fromString`.
+- Message: Message parsing logic for both requests and responses has been moved
+ to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only
+ used in debugging or deserializing messages, so it doesn't make sense for
+ Guzzle as a library to add this level of complexity to parsing messages.
+- UriTemplate: URI template parsing has been moved to
+ `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL
+ URI template library if it is installed.
+- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously
+ it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
+ then developers are free to subclass `GuzzleHttp\Url`.
+
+## Plugin
+
+The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`.
+Several plugins are shipping with the core Guzzle library under this namespace.
+
+- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar
+ code has moved to `GuzzleHttp\Cookie`.
+- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin.
+- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is
+ received.
+- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin.
+- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before
+ sending. This subscriber is attached to all requests by default.
+- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin.
+
+The following plugins have been removed (third-parties are free to re-implement
+these if needed):
+
+- `GuzzleHttp\Plugin\Async` has been removed.
+- `GuzzleHttp\Plugin\CurlAuth` has been removed.
+- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This
+ functionality should instead be implemented with event listeners that occur
+ after normal response parsing occurs in the guzzle/command package.
+
+The following plugins are not part of the core Guzzle package, but are provided
+in separate repositories:
+
+- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
+ to build custom retry policies using simple functions rather than various
+ chained classes. See: https://github.com/guzzle/retry-subscriber
+- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
+ https://github.com/guzzle/cache-subscriber
+- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
+ https://github.com/guzzle/log-subscriber
+- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
+ https://github.com/guzzle/message-integrity-subscriber
+- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
+ `GuzzleHttp\Subscriber\MockSubscriber`.
+- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to
+ https://github.com/guzzle/oauth-subscriber
+
+## Service
+
+The service description layer of Guzzle has moved into two separate packages:
+
+- http://github.com/guzzle/command Provides a high level abstraction over web
+ services by representing web service operations using commands.
+- http://github.com/guzzle/guzzle-services Provides an implementation of
+ guzzle/command that provides request serialization and response parsing using
+ Guzzle service descriptions.
+
+## Stream
+
+Stream have moved to a separate package available at
+https://github.com/guzzle/streams.
+
+`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take
+on the responsibilities of `Guzzle\Http\EntityBody` and
+`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number
+of methods implemented by the `StreamInterface` has been drastically reduced to
+allow developers to more easily extend and decorate stream behavior.
+
+## Removed methods from StreamInterface
+
+- `getStream` and `setStream` have been removed to better encapsulate streams.
+- `getMetadata` and `setMetadata` have been removed in favor of
+ `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
+ removed. This data is accessible when
+ using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `rewind` has been removed. Use `seek(0)` for a similar behavior.
+
+## Renamed methods
+
+- `detachStream` has been renamed to `detach`.
+- `feof` has been renamed to `eof`.
+- `ftell` has been renamed to `tell`.
+- `readLine` has moved from an instance method to a static class method of
+ `GuzzleHttp\Stream\Stream`.
+
+## Metadata streams
+
+`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
+that contain additional metadata accessible via `getMetadata()`.
+`GuzzleHttp\Stream\StreamInterface::getMetadata` and
+`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.
+
+## StreamRequestFactory
+
+The entire concept of the StreamRequestFactory has been removed. The way this
+was used in Guzzle 3 broke the actual interface of sending streaming requests
+(instead of getting back a Response, you got a StreamInterface). Streaming
+PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.
+
+3.6 to 3.7
+----------
+
+### Deprecations
+
+- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
+
+```php
+\Guzzle\Common\Version::$emitWarnings = true;
+```
+
+The following APIs and options have been marked as deprecated:
+
+- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+- Marked `Guzzle\Common\Collection::inject()` as deprecated.
+- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
+ `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
+ `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
+
+3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
+request methods. When paired with a client's configuration settings, these options allow you to specify default settings
+for various aspects of a request. Because these options make other previous configuration options redundant, several
+configuration options and methods of a client and AbstractCommand have been deprecated.
+
+- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
+- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
+- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
+- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
+
+ $command = $client->getCommand('foo', array(
+ 'command.headers' => array('Test' => '123'),
+ 'command.response_body' => '/path/to/file'
+ ));
+
+ // Should be changed to:
+
+ $command = $client->getCommand('foo', array(
+ 'command.request_options' => array(
+ 'headers' => array('Test' => '123'),
+ 'save_as' => '/path/to/file'
+ )
+ ));
+
+### Interface changes
+
+Additions and changes (you will need to update any implementations or subclasses you may have created):
+
+- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+- Added `Guzzle\Stream\StreamInterface::isRepeatable`
+- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+
+The following methods were removed from interfaces. All of these methods are still available in the concrete classes
+that implement them, but you should update your code to use alternative methods:
+
+- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
+ `$client->setDefaultOption('headers/{header_name}', 'value')`. or
+ `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
+- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
+- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
+- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
+
+### Cache plugin breaking changes
+
+- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+- Always setting X-cache headers on cached responses
+- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+- Added `CacheStorageInterface::purge($url)`
+- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+3.5 to 3.6
+----------
+
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+ For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
+ Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Moved getLinks() from Response to just be used on a Link header object.
+
+If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
+HeaderInterface (e.g. toArray(), getAll(), etc.).
+
+### Interface changes
+
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+
+### Removed deprecated functions
+
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+
+### Deprecations
+
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+
+### Other changes
+
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+
+3.3 to 3.4
+----------
+
+Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
+
+3.2 to 3.3
+----------
+
+### Response::getEtag() quote stripping removed
+
+`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
+
+### Removed `Guzzle\Http\Utils`
+
+The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
+
+### Stream wrapper and type
+
+`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase.
+
+### curl.emit_io became emit_io
+
+Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
+'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+
+3.1 to 3.2
+----------
+
+### CurlMulti is no longer reused globally
+
+Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
+to a single client can pollute requests dispatched from other clients.
+
+If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
+ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
+created.
+
+```php
+$multi = new Guzzle\Http\Curl\CurlMulti();
+$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
+$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
+ $event['client']->setCurlMulti($multi);
+}
+});
+```
+
+### No default path
+
+URLs no longer have a default path value of '/' if no path was specified.
+
+Before:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com/
+```
+
+After:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com
+```
+
+### Less verbose BadResponseException
+
+The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
+response information. You can, however, get access to the request and response object by calling `getRequest()` or
+`getResponse()` on the exception object.
+
+### Query parameter aggregation
+
+Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
+setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
+responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
+
+2.8 to 3.x
+----------
+
+### Guzzle\Service\Inspector
+
+Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
+
+**Before**
+
+```php
+use Guzzle\Service\Inspector;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Inspector::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+**After**
+
+```php
+use Guzzle\Common\Collection;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Collection::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+### Convert XML Service Descriptions to JSON
+
+**Before**
+
+```xml
+
+
+
+
+
+ Get a list of groups
+
+
+ Uses a search query to get a list of groups
+
+
+
+ Create a group
+
+
+
+
+ Delete a group by ID
+
+
+
+
+
+
+ Update a group
+
+
+
+
+
+
+```
+
+**After**
+
+```json
+{
+ "name": "Zendesk REST API v2",
+ "apiVersion": "2012-12-31",
+ "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
+ "operations": {
+ "list_groups": {
+ "httpMethod":"GET",
+ "uri": "groups.json",
+ "summary": "Get a list of groups"
+ },
+ "search_groups":{
+ "httpMethod":"GET",
+ "uri": "search.json?query=\"{query} type:group\"",
+ "summary": "Uses a search query to get a list of groups",
+ "parameters":{
+ "query":{
+ "location": "uri",
+ "description":"Zendesk Search Query",
+ "type": "string",
+ "required": true
+ }
+ }
+ },
+ "create_group": {
+ "httpMethod":"POST",
+ "uri": "groups.json",
+ "summary": "Create a group",
+ "parameters":{
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ },
+ "delete_group": {
+ "httpMethod":"DELETE",
+ "uri": "groups/{id}.json",
+ "summary": "Delete a group",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to delete by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "get_group": {
+ "httpMethod":"GET",
+ "uri": "groups/{id}.json",
+ "summary": "Get a ticket",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to get by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "update_group": {
+ "httpMethod":"PUT",
+ "uri": "groups/{id}.json",
+ "summary": "Update a group",
+ "parameters":{
+ "id": {
+ "location": "uri",
+ "description":"Group to update by ID",
+ "type": "integer",
+ "required": true
+ },
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ }
+}
+```
+
+### Guzzle\Service\Description\ServiceDescription
+
+Commands are now called Operations
+
+**Before**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getCommands(); // @returns ApiCommandInterface[]
+$sd->hasCommand($name);
+$sd->getCommand($name); // @returns ApiCommandInterface|null
+$sd->addCommand($command); // @param ApiCommandInterface $command
+```
+
+**After**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getOperations(); // @returns OperationInterface[]
+$sd->hasOperation($name);
+$sd->getOperation($name); // @returns OperationInterface|null
+$sd->addOperation($operation); // @param OperationInterface $operation
+```
+
+### Guzzle\Common\Inflection\Inflector
+
+Namespace is now `Guzzle\Inflection\Inflector`
+
+### Guzzle\Http\Plugin
+
+Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
+
+### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
+
+Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
+
+**Before**
+
+```php
+use Guzzle\Common\Log\ClosureLogAdapter;
+use Guzzle\Http\Plugin\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $verbosity is an integer indicating desired message verbosity level
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
+```
+
+**After**
+
+```php
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Log\MessageFormatter;
+use Guzzle\Plugin\Log\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $format is a string indicating desired message format -- @see MessageFormatter
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
+```
+
+### Guzzle\Http\Plugin\CurlAuthPlugin
+
+Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
+
+### Guzzle\Http\Plugin\ExponentialBackoffPlugin
+
+Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
+
+**Before**
+
+```php
+use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
+
+$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
+ ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
+ ));
+
+$client->addSubscriber($backoffPlugin);
+```
+
+**After**
+
+```php
+use Guzzle\Plugin\Backoff\BackoffPlugin;
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+
+// Use convenient factory method instead -- see implementation for ideas of what
+// you can do with chaining backoff strategies
+$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
+ HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
+ ));
+$client->addSubscriber($backoffPlugin);
+```
+
+### Known Issues
+
+#### [BUG] Accept-Encoding header behavior changed unintentionally.
+
+(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
+
+In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
+properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
+See issue #217 for a workaround, or use a version containing the fix.
diff --git a/instafeed/vendor/guzzlehttp/guzzle/composer.json b/instafeed/vendor/guzzlehttp/guzzle/composer.json
new file mode 100755
index 0000000..c553257
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/composer.json
@@ -0,0 +1,58 @@
+{
+ "name": "guzzlehttp/guzzle",
+ "type": "library",
+ "description": "Guzzle is a PHP HTTP client library",
+ "keywords": [
+ "framework",
+ "http",
+ "rest",
+ "web service",
+ "curl",
+ "client",
+ "HTTP client"
+ ],
+ "homepage": "http://guzzlephp.org/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.5",
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.6.1"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/phpstan.neon.dist b/instafeed/vendor/guzzlehttp/guzzle/phpstan.neon.dist
new file mode 100755
index 0000000..4ef4192
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/phpstan.neon.dist
@@ -0,0 +1,9 @@
+parameters:
+ level: 1
+ paths:
+ - src
+
+ ignoreErrors:
+ -
+ message: '#Function uri_template not found#'
+ path: %currentWorkingDirectory%/src/functions.php
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Client.php b/instafeed/vendor/guzzlehttp/guzzle/src/Client.php
new file mode 100755
index 0000000..0f43c71
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Client.php
@@ -0,0 +1,422 @@
+ 'http://www.foo.com/1.0/',
+ * 'timeout' => 0,
+ * 'allow_redirects' => false,
+ * 'proxy' => '192.168.16.1:10'
+ * ]);
+ *
+ * Client configuration settings include the following options:
+ *
+ * - handler: (callable) Function that transfers HTTP requests over the
+ * wire. The function is called with a Psr7\Http\Message\RequestInterface
+ * and array of transfer options, and must return a
+ * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
+ * Psr7\Http\Message\ResponseInterface on success. "handler" is a
+ * constructor only option that cannot be overridden in per/request
+ * options. If no handler is provided, a default handler will be created
+ * that enables all of the request options below by attaching all of the
+ * default middleware to the handler.
+ * - base_uri: (string|UriInterface) Base URI of the client that is merged
+ * into relative URIs. Can be a string or instance of UriInterface.
+ * - **: any request option
+ *
+ * @param array $config Client configuration settings.
+ *
+ * @see \GuzzleHttp\RequestOptions for a list of available request options.
+ */
+ public function __construct(array $config = [])
+ {
+ if (!isset($config['handler'])) {
+ $config['handler'] = HandlerStack::create();
+ } elseif (!is_callable($config['handler'])) {
+ throw new \InvalidArgumentException('handler must be a callable');
+ }
+
+ // Convert the base_uri to a UriInterface
+ if (isset($config['base_uri'])) {
+ $config['base_uri'] = Psr7\uri_for($config['base_uri']);
+ }
+
+ $this->configureDefaults($config);
+ }
+
+ public function __call($method, $args)
+ {
+ if (count($args) < 1) {
+ throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
+ }
+
+ $uri = $args[0];
+ $opts = isset($args[1]) ? $args[1] : [];
+
+ return substr($method, -5) === 'Async'
+ ? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
+ : $this->request($method, $uri, $opts);
+ }
+
+ public function sendAsync(RequestInterface $request, array $options = [])
+ {
+ // Merge the base URI into the request URI if needed.
+ $options = $this->prepareDefaults($options);
+
+ return $this->transfer(
+ $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
+ $options
+ );
+ }
+
+ public function send(RequestInterface $request, array $options = [])
+ {
+ $options[RequestOptions::SYNCHRONOUS] = true;
+ return $this->sendAsync($request, $options)->wait();
+ }
+
+ public function requestAsync($method, $uri = '', array $options = [])
+ {
+ $options = $this->prepareDefaults($options);
+ // Remove request modifying parameter because it can be done up-front.
+ $headers = isset($options['headers']) ? $options['headers'] : [];
+ $body = isset($options['body']) ? $options['body'] : null;
+ $version = isset($options['version']) ? $options['version'] : '1.1';
+ // Merge the URI into the base URI.
+ $uri = $this->buildUri($uri, $options);
+ if (is_array($body)) {
+ $this->invalidBody();
+ }
+ $request = new Psr7\Request($method, $uri, $headers, $body, $version);
+ // Remove the option so that they are not doubly-applied.
+ unset($options['headers'], $options['body'], $options['version']);
+
+ return $this->transfer($request, $options);
+ }
+
+ public function request($method, $uri = '', array $options = [])
+ {
+ $options[RequestOptions::SYNCHRONOUS] = true;
+ return $this->requestAsync($method, $uri, $options)->wait();
+ }
+
+ public function getConfig($option = null)
+ {
+ return $option === null
+ ? $this->config
+ : (isset($this->config[$option]) ? $this->config[$option] : null);
+ }
+
+ private function buildUri($uri, array $config)
+ {
+ // for BC we accept null which would otherwise fail in uri_for
+ $uri = Psr7\uri_for($uri === null ? '' : $uri);
+
+ if (isset($config['base_uri'])) {
+ $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
+ }
+
+ return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
+ }
+
+ /**
+ * Configures the default options for a client.
+ *
+ * @param array $config
+ */
+ private function configureDefaults(array $config)
+ {
+ $defaults = [
+ 'allow_redirects' => RedirectMiddleware::$defaultSettings,
+ 'http_errors' => true,
+ 'decode_content' => true,
+ 'verify' => true,
+ 'cookies' => false
+ ];
+
+ // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
+
+ // We can only trust the HTTP_PROXY environment variable in a CLI
+ // process due to the fact that PHP has no reliable mechanism to
+ // get environment variables that start with "HTTP_".
+ if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
+ $defaults['proxy']['http'] = getenv('HTTP_PROXY');
+ }
+
+ if ($proxy = getenv('HTTPS_PROXY')) {
+ $defaults['proxy']['https'] = $proxy;
+ }
+
+ if ($noProxy = getenv('NO_PROXY')) {
+ $cleanedNoProxy = str_replace(' ', '', $noProxy);
+ $defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
+ }
+
+ $this->config = $config + $defaults;
+
+ if (!empty($config['cookies']) && $config['cookies'] === true) {
+ $this->config['cookies'] = new CookieJar();
+ }
+
+ // Add the default user-agent header.
+ if (!isset($this->config['headers'])) {
+ $this->config['headers'] = ['User-Agent' => default_user_agent()];
+ } else {
+ // Add the User-Agent header if one was not already set.
+ foreach (array_keys($this->config['headers']) as $name) {
+ if (strtolower($name) === 'user-agent') {
+ return;
+ }
+ }
+ $this->config['headers']['User-Agent'] = default_user_agent();
+ }
+ }
+
+ /**
+ * Merges default options into the array.
+ *
+ * @param array $options Options to modify by reference
+ *
+ * @return array
+ */
+ private function prepareDefaults(array $options)
+ {
+ $defaults = $this->config;
+
+ if (!empty($defaults['headers'])) {
+ // Default headers are only added if they are not present.
+ $defaults['_conditional'] = $defaults['headers'];
+ unset($defaults['headers']);
+ }
+
+ // Special handling for headers is required as they are added as
+ // conditional headers and as headers passed to a request ctor.
+ if (array_key_exists('headers', $options)) {
+ // Allows default headers to be unset.
+ if ($options['headers'] === null) {
+ $defaults['_conditional'] = null;
+ unset($options['headers']);
+ } elseif (!is_array($options['headers'])) {
+ throw new \InvalidArgumentException('headers must be an array');
+ }
+ }
+
+ // Shallow merge defaults underneath options.
+ $result = $options + $defaults;
+
+ // Remove null values.
+ foreach ($result as $k => $v) {
+ if ($v === null) {
+ unset($result[$k]);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Transfers the given request and applies request options.
+ *
+ * The URI of the request is not modified and the request options are used
+ * as-is without merging in default options.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return Promise\PromiseInterface
+ */
+ private function transfer(RequestInterface $request, array $options)
+ {
+ // save_to -> sink
+ if (isset($options['save_to'])) {
+ $options['sink'] = $options['save_to'];
+ unset($options['save_to']);
+ }
+
+ // exceptions -> http_errors
+ if (isset($options['exceptions'])) {
+ $options['http_errors'] = $options['exceptions'];
+ unset($options['exceptions']);
+ }
+
+ $request = $this->applyOptions($request, $options);
+ $handler = $options['handler'];
+
+ try {
+ return Promise\promise_for($handler($request, $options));
+ } catch (\Exception $e) {
+ return Promise\rejection_for($e);
+ }
+ }
+
+ /**
+ * Applies the array of request options to a request.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return RequestInterface
+ */
+ private function applyOptions(RequestInterface $request, array &$options)
+ {
+ $modify = [
+ 'set_headers' => [],
+ ];
+
+ if (isset($options['headers'])) {
+ $modify['set_headers'] = $options['headers'];
+ unset($options['headers']);
+ }
+
+ if (isset($options['form_params'])) {
+ if (isset($options['multipart'])) {
+ throw new \InvalidArgumentException('You cannot use '
+ . 'form_params and multipart at the same time. Use the '
+ . 'form_params option if you want to send application/'
+ . 'x-www-form-urlencoded requests, and the multipart '
+ . 'option to send multipart/form-data requests.');
+ }
+ $options['body'] = http_build_query($options['form_params'], '', '&');
+ unset($options['form_params']);
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
+ }
+
+ if (isset($options['multipart'])) {
+ $options['body'] = new Psr7\MultipartStream($options['multipart']);
+ unset($options['multipart']);
+ }
+
+ if (isset($options['json'])) {
+ $options['body'] = \GuzzleHttp\json_encode($options['json']);
+ unset($options['json']);
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'application/json';
+ }
+
+ if (!empty($options['decode_content'])
+ && $options['decode_content'] !== true
+ ) {
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']);
+ $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
+ }
+
+ if (isset($options['body'])) {
+ if (is_array($options['body'])) {
+ $this->invalidBody();
+ }
+ $modify['body'] = Psr7\stream_for($options['body']);
+ unset($options['body']);
+ }
+
+ if (!empty($options['auth']) && is_array($options['auth'])) {
+ $value = $options['auth'];
+ $type = isset($value[2]) ? strtolower($value[2]) : 'basic';
+ switch ($type) {
+ case 'basic':
+ // Ensure that we don't have the header in different case and set the new value.
+ $modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']);
+ $modify['set_headers']['Authorization'] = 'Basic '
+ . base64_encode("$value[0]:$value[1]");
+ break;
+ case 'digest':
+ // @todo: Do not rely on curl
+ $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
+ $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
+ break;
+ case 'ntlm':
+ $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
+ $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
+ break;
+ }
+ }
+
+ if (isset($options['query'])) {
+ $value = $options['query'];
+ if (is_array($value)) {
+ $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
+ }
+ if (!is_string($value)) {
+ throw new \InvalidArgumentException('query must be a string or array');
+ }
+ $modify['query'] = $value;
+ unset($options['query']);
+ }
+
+ // Ensure that sink is not an invalid value.
+ if (isset($options['sink'])) {
+ // TODO: Add more sink validation?
+ if (is_bool($options['sink'])) {
+ throw new \InvalidArgumentException('sink must not be a boolean');
+ }
+ }
+
+ $request = Psr7\modify_request($request, $modify);
+ if ($request->getBody() instanceof Psr7\MultipartStream) {
+ // Use a multipart/form-data POST if a Content-Type is not set.
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
+ . $request->getBody()->getBoundary();
+ }
+
+ // Merge in conditional headers if they are not present.
+ if (isset($options['_conditional'])) {
+ // Build up the changes so it's in a single clone of the message.
+ $modify = [];
+ foreach ($options['_conditional'] as $k => $v) {
+ if (!$request->hasHeader($k)) {
+ $modify['set_headers'][$k] = $v;
+ }
+ }
+ $request = Psr7\modify_request($request, $modify);
+ // Don't pass this internal value along to middleware/handlers.
+ unset($options['_conditional']);
+ }
+
+ return $request;
+ }
+
+ private function invalidBody()
+ {
+ throw new \InvalidArgumentException('Passing in the "body" request '
+ . 'option as an array to send a POST request has been deprecated. '
+ . 'Please use the "form_params" request option to send a '
+ . 'application/x-www-form-urlencoded request, or the "multipart" '
+ . 'request option to send a multipart/form-data request.');
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/instafeed/vendor/guzzlehttp/guzzle/src/ClientInterface.php
new file mode 100755
index 0000000..5b37085
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/ClientInterface.php
@@ -0,0 +1,84 @@
+strictMode = $strictMode;
+
+ foreach ($cookieArray as $cookie) {
+ if (!($cookie instanceof SetCookie)) {
+ $cookie = new SetCookie($cookie);
+ }
+ $this->setCookie($cookie);
+ }
+ }
+
+ /**
+ * Create a new Cookie jar from an associative array and domain.
+ *
+ * @param array $cookies Cookies to create the jar from
+ * @param string $domain Domain to set the cookies to
+ *
+ * @return self
+ */
+ public static function fromArray(array $cookies, $domain)
+ {
+ $cookieJar = new self();
+ foreach ($cookies as $name => $value) {
+ $cookieJar->setCookie(new SetCookie([
+ 'Domain' => $domain,
+ 'Name' => $name,
+ 'Value' => $value,
+ 'Discard' => true
+ ]));
+ }
+
+ return $cookieJar;
+ }
+
+ /**
+ * @deprecated
+ */
+ public static function getCookieValue($value)
+ {
+ return $value;
+ }
+
+ /**
+ * Evaluate if this cookie should be persisted to storage
+ * that survives between requests.
+ *
+ * @param SetCookie $cookie Being evaluated.
+ * @param bool $allowSessionCookies If we should persist session cookies
+ * @return bool
+ */
+ public static function shouldPersist(
+ SetCookie $cookie,
+ $allowSessionCookies = false
+ ) {
+ if ($cookie->getExpires() || $allowSessionCookies) {
+ if (!$cookie->getDiscard()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Finds and returns the cookie based on the name
+ *
+ * @param string $name cookie name to search for
+ * @return SetCookie|null cookie that was found or null if not found
+ */
+ public function getCookieByName($name)
+ {
+ // don't allow a null name
+ if ($name === null) {
+ return null;
+ }
+ foreach ($this->cookies as $cookie) {
+ if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) {
+ return $cookie;
+ }
+ }
+ }
+
+ public function toArray()
+ {
+ return array_map(function (SetCookie $cookie) {
+ return $cookie->toArray();
+ }, $this->getIterator()->getArrayCopy());
+ }
+
+ public function clear($domain = null, $path = null, $name = null)
+ {
+ if (!$domain) {
+ $this->cookies = [];
+ return;
+ } elseif (!$path) {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($domain) {
+ return !$cookie->matchesDomain($domain);
+ }
+ );
+ } elseif (!$name) {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($path, $domain) {
+ return !($cookie->matchesPath($path) &&
+ $cookie->matchesDomain($domain));
+ }
+ );
+ } else {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($path, $domain, $name) {
+ return !($cookie->getName() == $name &&
+ $cookie->matchesPath($path) &&
+ $cookie->matchesDomain($domain));
+ }
+ );
+ }
+ }
+
+ public function clearSessionCookies()
+ {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) {
+ return !$cookie->getDiscard() && $cookie->getExpires();
+ }
+ );
+ }
+
+ public function setCookie(SetCookie $cookie)
+ {
+ // If the name string is empty (but not 0), ignore the set-cookie
+ // string entirely.
+ $name = $cookie->getName();
+ if (!$name && $name !== '0') {
+ return false;
+ }
+
+ // Only allow cookies with set and valid domain, name, value
+ $result = $cookie->validate();
+ if ($result !== true) {
+ if ($this->strictMode) {
+ throw new \RuntimeException('Invalid cookie: ' . $result);
+ } else {
+ $this->removeCookieIfEmpty($cookie);
+ return false;
+ }
+ }
+
+ // Resolve conflicts with previously set cookies
+ foreach ($this->cookies as $i => $c) {
+
+ // Two cookies are identical, when their path, and domain are
+ // identical.
+ if ($c->getPath() != $cookie->getPath() ||
+ $c->getDomain() != $cookie->getDomain() ||
+ $c->getName() != $cookie->getName()
+ ) {
+ continue;
+ }
+
+ // The previously set cookie is a discard cookie and this one is
+ // not so allow the new cookie to be set
+ if (!$cookie->getDiscard() && $c->getDiscard()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the new cookie's expiration is further into the future, then
+ // replace the old cookie
+ if ($cookie->getExpires() > $c->getExpires()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the value has changed, we better change it
+ if ($cookie->getValue() !== $c->getValue()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // The cookie exists, so no need to continue
+ return false;
+ }
+
+ $this->cookies[] = $cookie;
+
+ return true;
+ }
+
+ public function count()
+ {
+ return count($this->cookies);
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator(array_values($this->cookies));
+ }
+
+ public function extractCookies(
+ RequestInterface $request,
+ ResponseInterface $response
+ ) {
+ if ($cookieHeader = $response->getHeader('Set-Cookie')) {
+ foreach ($cookieHeader as $cookie) {
+ $sc = SetCookie::fromString($cookie);
+ if (!$sc->getDomain()) {
+ $sc->setDomain($request->getUri()->getHost());
+ }
+ if (0 !== strpos($sc->getPath(), '/')) {
+ $sc->setPath($this->getCookiePathFromRequest($request));
+ }
+ $this->setCookie($sc);
+ }
+ }
+ }
+
+ /**
+ * Computes cookie path following RFC 6265 section 5.1.4
+ *
+ * @link https://tools.ietf.org/html/rfc6265#section-5.1.4
+ *
+ * @param RequestInterface $request
+ * @return string
+ */
+ private function getCookiePathFromRequest(RequestInterface $request)
+ {
+ $uriPath = $request->getUri()->getPath();
+ if ('' === $uriPath) {
+ return '/';
+ }
+ if (0 !== strpos($uriPath, '/')) {
+ return '/';
+ }
+ if ('/' === $uriPath) {
+ return '/';
+ }
+ if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
+ return '/';
+ }
+
+ return substr($uriPath, 0, $lastSlashPos);
+ }
+
+ public function withCookieHeader(RequestInterface $request)
+ {
+ $values = [];
+ $uri = $request->getUri();
+ $scheme = $uri->getScheme();
+ $host = $uri->getHost();
+ $path = $uri->getPath() ?: '/';
+
+ foreach ($this->cookies as $cookie) {
+ if ($cookie->matchesPath($path) &&
+ $cookie->matchesDomain($host) &&
+ !$cookie->isExpired() &&
+ (!$cookie->getSecure() || $scheme === 'https')
+ ) {
+ $values[] = $cookie->getName() . '='
+ . $cookie->getValue();
+ }
+ }
+
+ return $values
+ ? $request->withHeader('Cookie', implode('; ', $values))
+ : $request;
+ }
+
+ /**
+ * If a cookie already exists and the server asks to set it again with a
+ * null value, the cookie must be deleted.
+ *
+ * @param SetCookie $cookie
+ */
+ private function removeCookieIfEmpty(SetCookie $cookie)
+ {
+ $cookieValue = $cookie->getValue();
+ if ($cookieValue === null || $cookieValue === '') {
+ $this->clear(
+ $cookie->getDomain(),
+ $cookie->getPath(),
+ $cookie->getName()
+ );
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/instafeed/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
new file mode 100755
index 0000000..2cf298a
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
@@ -0,0 +1,84 @@
+filename = $cookieFile;
+ $this->storeSessionCookies = $storeSessionCookies;
+
+ if (file_exists($cookieFile)) {
+ $this->load($cookieFile);
+ }
+ }
+
+ /**
+ * Saves the file when shutting down
+ */
+ public function __destruct()
+ {
+ $this->save($this->filename);
+ }
+
+ /**
+ * Saves the cookies to a file.
+ *
+ * @param string $filename File to save
+ * @throws \RuntimeException if the file cannot be found or created
+ */
+ public function save($filename)
+ {
+ $json = [];
+ foreach ($this as $cookie) {
+ /** @var SetCookie $cookie */
+ if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
+ $json[] = $cookie->toArray();
+ }
+ }
+
+ $jsonStr = \GuzzleHttp\json_encode($json);
+ if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) {
+ throw new \RuntimeException("Unable to save file {$filename}");
+ }
+ }
+
+ /**
+ * Load cookies from a JSON formatted file.
+ *
+ * Old cookies are kept unless overwritten by newly loaded ones.
+ *
+ * @param string $filename Cookie file to load.
+ * @throws \RuntimeException if the file cannot be loaded.
+ */
+ public function load($filename)
+ {
+ $json = file_get_contents($filename);
+ if (false === $json) {
+ throw new \RuntimeException("Unable to load file {$filename}");
+ } elseif ($json === '') {
+ return;
+ }
+
+ $data = \GuzzleHttp\json_decode($json, true);
+ if (is_array($data)) {
+ foreach (json_decode($json, true) as $cookie) {
+ $this->setCookie(new SetCookie($cookie));
+ }
+ } elseif (strlen($data)) {
+ throw new \RuntimeException("Invalid cookie file: {$filename}");
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/instafeed/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php
new file mode 100755
index 0000000..0224a24
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php
@@ -0,0 +1,72 @@
+sessionKey = $sessionKey;
+ $this->storeSessionCookies = $storeSessionCookies;
+ $this->load();
+ }
+
+ /**
+ * Saves cookies to session when shutting down
+ */
+ public function __destruct()
+ {
+ $this->save();
+ }
+
+ /**
+ * Save cookies to the client session
+ */
+ public function save()
+ {
+ $json = [];
+ foreach ($this as $cookie) {
+ /** @var SetCookie $cookie */
+ if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
+ $json[] = $cookie->toArray();
+ }
+ }
+
+ $_SESSION[$this->sessionKey] = json_encode($json);
+ }
+
+ /**
+ * Load the contents of the client session into the data array
+ */
+ protected function load()
+ {
+ if (!isset($_SESSION[$this->sessionKey])) {
+ return;
+ }
+ $data = json_decode($_SESSION[$this->sessionKey], true);
+ if (is_array($data)) {
+ foreach ($data as $cookie) {
+ $this->setCookie(new SetCookie($cookie));
+ }
+ } elseif (strlen($data)) {
+ throw new \RuntimeException("Invalid cookie data");
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/instafeed/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php
new file mode 100755
index 0000000..3d776a7
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php
@@ -0,0 +1,403 @@
+ null,
+ 'Value' => null,
+ 'Domain' => null,
+ 'Path' => '/',
+ 'Max-Age' => null,
+ 'Expires' => null,
+ 'Secure' => false,
+ 'Discard' => false,
+ 'HttpOnly' => false
+ ];
+
+ /** @var array Cookie data */
+ private $data;
+
+ /**
+ * Create a new SetCookie object from a string
+ *
+ * @param string $cookie Set-Cookie header string
+ *
+ * @return self
+ */
+ public static function fromString($cookie)
+ {
+ // Create the default return array
+ $data = self::$defaults;
+ // Explode the cookie string using a series of semicolons
+ $pieces = array_filter(array_map('trim', explode(';', $cookie)));
+ // The name of the cookie (first kvp) must exist and include an equal sign.
+ if (empty($pieces[0]) || !strpos($pieces[0], '=')) {
+ return new self($data);
+ }
+
+ // Add the cookie pieces into the parsed data array
+ foreach ($pieces as $part) {
+ $cookieParts = explode('=', $part, 2);
+ $key = trim($cookieParts[0]);
+ $value = isset($cookieParts[1])
+ ? trim($cookieParts[1], " \n\r\t\0\x0B")
+ : true;
+
+ // Only check for non-cookies when cookies have been found
+ if (empty($data['Name'])) {
+ $data['Name'] = $key;
+ $data['Value'] = $value;
+ } else {
+ foreach (array_keys(self::$defaults) as $search) {
+ if (!strcasecmp($search, $key)) {
+ $data[$search] = $value;
+ continue 2;
+ }
+ }
+ $data[$key] = $value;
+ }
+ }
+
+ return new self($data);
+ }
+
+ /**
+ * @param array $data Array of cookie data provided by a Cookie parser
+ */
+ public function __construct(array $data = [])
+ {
+ $this->data = array_replace(self::$defaults, $data);
+ // Extract the Expires value and turn it into a UNIX timestamp if needed
+ if (!$this->getExpires() && $this->getMaxAge()) {
+ // Calculate the Expires date
+ $this->setExpires(time() + $this->getMaxAge());
+ } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
+ $this->setExpires($this->getExpires());
+ }
+ }
+
+ public function __toString()
+ {
+ $str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
+ foreach ($this->data as $k => $v) {
+ if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
+ if ($k === 'Expires') {
+ $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
+ } else {
+ $str .= ($v === true ? $k : "{$k}={$v}") . '; ';
+ }
+ }
+ }
+
+ return rtrim($str, '; ');
+ }
+
+ public function toArray()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Get the cookie name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->data['Name'];
+ }
+
+ /**
+ * Set the cookie name
+ *
+ * @param string $name Cookie name
+ */
+ public function setName($name)
+ {
+ $this->data['Name'] = $name;
+ }
+
+ /**
+ * Get the cookie value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->data['Value'];
+ }
+
+ /**
+ * Set the cookie value
+ *
+ * @param string $value Cookie value
+ */
+ public function setValue($value)
+ {
+ $this->data['Value'] = $value;
+ }
+
+ /**
+ * Get the domain
+ *
+ * @return string|null
+ */
+ public function getDomain()
+ {
+ return $this->data['Domain'];
+ }
+
+ /**
+ * Set the domain of the cookie
+ *
+ * @param string $domain
+ */
+ public function setDomain($domain)
+ {
+ $this->data['Domain'] = $domain;
+ }
+
+ /**
+ * Get the path
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->data['Path'];
+ }
+
+ /**
+ * Set the path of the cookie
+ *
+ * @param string $path Path of the cookie
+ */
+ public function setPath($path)
+ {
+ $this->data['Path'] = $path;
+ }
+
+ /**
+ * Maximum lifetime of the cookie in seconds
+ *
+ * @return int|null
+ */
+ public function getMaxAge()
+ {
+ return $this->data['Max-Age'];
+ }
+
+ /**
+ * Set the max-age of the cookie
+ *
+ * @param int $maxAge Max age of the cookie in seconds
+ */
+ public function setMaxAge($maxAge)
+ {
+ $this->data['Max-Age'] = $maxAge;
+ }
+
+ /**
+ * The UNIX timestamp when the cookie Expires
+ *
+ * @return mixed
+ */
+ public function getExpires()
+ {
+ return $this->data['Expires'];
+ }
+
+ /**
+ * Set the unix timestamp for which the cookie will expire
+ *
+ * @param int $timestamp Unix timestamp
+ */
+ public function setExpires($timestamp)
+ {
+ $this->data['Expires'] = is_numeric($timestamp)
+ ? (int) $timestamp
+ : strtotime($timestamp);
+ }
+
+ /**
+ * Get whether or not this is a secure cookie
+ *
+ * @return bool|null
+ */
+ public function getSecure()
+ {
+ return $this->data['Secure'];
+ }
+
+ /**
+ * Set whether or not the cookie is secure
+ *
+ * @param bool $secure Set to true or false if secure
+ */
+ public function setSecure($secure)
+ {
+ $this->data['Secure'] = $secure;
+ }
+
+ /**
+ * Get whether or not this is a session cookie
+ *
+ * @return bool|null
+ */
+ public function getDiscard()
+ {
+ return $this->data['Discard'];
+ }
+
+ /**
+ * Set whether or not this is a session cookie
+ *
+ * @param bool $discard Set to true or false if this is a session cookie
+ */
+ public function setDiscard($discard)
+ {
+ $this->data['Discard'] = $discard;
+ }
+
+ /**
+ * Get whether or not this is an HTTP only cookie
+ *
+ * @return bool
+ */
+ public function getHttpOnly()
+ {
+ return $this->data['HttpOnly'];
+ }
+
+ /**
+ * Set whether or not this is an HTTP only cookie
+ *
+ * @param bool $httpOnly Set to true or false if this is HTTP only
+ */
+ public function setHttpOnly($httpOnly)
+ {
+ $this->data['HttpOnly'] = $httpOnly;
+ }
+
+ /**
+ * Check if the cookie matches a path value.
+ *
+ * A request-path path-matches a given cookie-path if at least one of
+ * the following conditions holds:
+ *
+ * - The cookie-path and the request-path are identical.
+ * - The cookie-path is a prefix of the request-path, and the last
+ * character of the cookie-path is %x2F ("/").
+ * - The cookie-path is a prefix of the request-path, and the first
+ * character of the request-path that is not included in the cookie-
+ * path is a %x2F ("/") character.
+ *
+ * @param string $requestPath Path to check against
+ *
+ * @return bool
+ */
+ public function matchesPath($requestPath)
+ {
+ $cookiePath = $this->getPath();
+
+ // Match on exact matches or when path is the default empty "/"
+ if ($cookiePath === '/' || $cookiePath == $requestPath) {
+ return true;
+ }
+
+ // Ensure that the cookie-path is a prefix of the request path.
+ if (0 !== strpos($requestPath, $cookiePath)) {
+ return false;
+ }
+
+ // Match if the last character of the cookie-path is "/"
+ if (substr($cookiePath, -1, 1) === '/') {
+ return true;
+ }
+
+ // Match if the first character not included in cookie path is "/"
+ return substr($requestPath, strlen($cookiePath), 1) === '/';
+ }
+
+ /**
+ * Check if the cookie matches a domain value
+ *
+ * @param string $domain Domain to check against
+ *
+ * @return bool
+ */
+ public function matchesDomain($domain)
+ {
+ // Remove the leading '.' as per spec in RFC 6265.
+ // http://tools.ietf.org/html/rfc6265#section-5.2.3
+ $cookieDomain = ltrim($this->getDomain(), '.');
+
+ // Domain not set or exact match.
+ if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
+ return true;
+ }
+
+ // Matching the subdomain according to RFC 6265.
+ // http://tools.ietf.org/html/rfc6265#section-5.1.3
+ if (filter_var($domain, FILTER_VALIDATE_IP)) {
+ return false;
+ }
+
+ return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/', $domain);
+ }
+
+ /**
+ * Check if the cookie is expired
+ *
+ * @return bool
+ */
+ public function isExpired()
+ {
+ return $this->getExpires() !== null && time() > $this->getExpires();
+ }
+
+ /**
+ * Check if the cookie is valid according to RFC 6265
+ *
+ * @return bool|string Returns true if valid or an error message if invalid
+ */
+ public function validate()
+ {
+ // Names must not be empty, but can be 0
+ $name = $this->getName();
+ if (empty($name) && !is_numeric($name)) {
+ return 'The cookie name must not be empty';
+ }
+
+ // Check if any of the invalid characters are present in the cookie name
+ if (preg_match(
+ '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
+ $name
+ )) {
+ return 'Cookie name must not contain invalid characters: ASCII '
+ . 'Control characters (0-31;127), space, tab and the '
+ . 'following characters: ()<>@,;:\"/?={}';
+ }
+
+ // Value must not be empty, but can be 0
+ $value = $this->getValue();
+ if (empty($value) && !is_numeric($value)) {
+ return 'The cookie value must not be empty';
+ }
+
+ // Domains must not be empty, but can be 0
+ // A "0" is not a valid internet domain, but may be used as server name
+ // in a private network.
+ $domain = $this->getDomain();
+ if (empty($domain) && !is_numeric($domain)) {
+ return 'The cookie domain must not be empty';
+ }
+
+ return true;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/instafeed/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php
new file mode 100755
index 0000000..427d896
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php
@@ -0,0 +1,27 @@
+getStatusCode()
+ : 0;
+ parent::__construct($message, $code, $previous);
+ $this->request = $request;
+ $this->response = $response;
+ $this->handlerContext = $handlerContext;
+ }
+
+ /**
+ * Wrap non-RequestExceptions with a RequestException
+ *
+ * @param RequestInterface $request
+ * @param \Exception $e
+ *
+ * @return RequestException
+ */
+ public static function wrapException(RequestInterface $request, \Exception $e)
+ {
+ return $e instanceof RequestException
+ ? $e
+ : new RequestException($e->getMessage(), $request, null, $e);
+ }
+
+ /**
+ * Factory method to create a new exception with a normalized error message
+ *
+ * @param RequestInterface $request Request
+ * @param ResponseInterface $response Response received
+ * @param \Exception $previous Previous exception
+ * @param array $ctx Optional handler context.
+ *
+ * @return self
+ */
+ public static function create(
+ RequestInterface $request,
+ ResponseInterface $response = null,
+ \Exception $previous = null,
+ array $ctx = []
+ ) {
+ if (!$response) {
+ return new self(
+ 'Error completing request',
+ $request,
+ null,
+ $previous,
+ $ctx
+ );
+ }
+
+ $level = (int) floor($response->getStatusCode() / 100);
+ if ($level === 4) {
+ $label = 'Client error';
+ $className = ClientException::class;
+ } elseif ($level === 5) {
+ $label = 'Server error';
+ $className = ServerException::class;
+ } else {
+ $label = 'Unsuccessful request';
+ $className = __CLASS__;
+ }
+
+ $uri = $request->getUri();
+ $uri = static::obfuscateUri($uri);
+
+ // Client Error: `GET /` resulted in a `404 Not Found` response:
+ // ... (truncated)
+ $message = sprintf(
+ '%s: `%s %s` resulted in a `%s %s` response',
+ $label,
+ $request->getMethod(),
+ $uri,
+ $response->getStatusCode(),
+ $response->getReasonPhrase()
+ );
+
+ $summary = static::getResponseBodySummary($response);
+
+ if ($summary !== null) {
+ $message .= ":\n{$summary}\n";
+ }
+
+ return new $className($message, $request, $response, $previous, $ctx);
+ }
+
+ /**
+ * Get a short summary of the response
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param ResponseInterface $response
+ *
+ * @return string|null
+ */
+ public static function getResponseBodySummary(ResponseInterface $response)
+ {
+ $body = $response->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $summary = $body->read(120);
+ $body->rewind();
+
+ if ($size > 120) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
+ return null;
+ }
+
+ return $summary;
+ }
+
+ /**
+ * Obfuscates URI if there is an username and a password present
+ *
+ * @param UriInterface $uri
+ *
+ * @return UriInterface
+ */
+ private static function obfuscateUri($uri)
+ {
+ $userInfo = $uri->getUserInfo();
+
+ if (false !== ($pos = strpos($userInfo, ':'))) {
+ return $uri->withUserInfo(substr($userInfo, 0, $pos), '***');
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Get the request that caused the exception
+ *
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Get the associated response
+ *
+ * @return ResponseInterface|null
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Check if a response was received
+ *
+ * @return bool
+ */
+ public function hasResponse()
+ {
+ return $this->response !== null;
+ }
+
+ /**
+ * Get contextual information about the error from the underlying handler.
+ *
+ * The contents of this array will vary depending on which handler you are
+ * using. It may also be just an empty array. Relying on this data will
+ * couple you to a specific handler, but can give more debug information
+ * when needed.
+ *
+ * @return array
+ */
+ public function getHandlerContext()
+ {
+ return $this->handlerContext;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php b/instafeed/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php
new file mode 100755
index 0000000..a77c289
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php
@@ -0,0 +1,27 @@
+stream = $stream;
+ $msg = $msg ?: 'Could not seek the stream to position ' . $pos;
+ parent::__construct($msg);
+ }
+
+ /**
+ * @return StreamInterface
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/instafeed/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php
new file mode 100755
index 0000000..127094c
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php
@@ -0,0 +1,9 @@
+maxHandles = $maxHandles;
+ }
+
+ public function create(RequestInterface $request, array $options)
+ {
+ if (isset($options['curl']['body_as_string'])) {
+ $options['_body_as_string'] = $options['curl']['body_as_string'];
+ unset($options['curl']['body_as_string']);
+ }
+
+ $easy = new EasyHandle;
+ $easy->request = $request;
+ $easy->options = $options;
+ $conf = $this->getDefaultConf($easy);
+ $this->applyMethod($easy, $conf);
+ $this->applyHandlerOptions($easy, $conf);
+ $this->applyHeaders($easy, $conf);
+ unset($conf['_headers']);
+
+ // Add handler options from the request configuration options
+ if (isset($options['curl'])) {
+ $conf = array_replace($conf, $options['curl']);
+ }
+
+ $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
+ $easy->handle = $this->handles
+ ? array_pop($this->handles)
+ : curl_init();
+ curl_setopt_array($easy->handle, $conf);
+
+ return $easy;
+ }
+
+ public function release(EasyHandle $easy)
+ {
+ $resource = $easy->handle;
+ unset($easy->handle);
+
+ if (count($this->handles) >= $this->maxHandles) {
+ curl_close($resource);
+ } else {
+ // Remove all callback functions as they can hold onto references
+ // and are not cleaned up by curl_reset. Using curl_setopt_array
+ // does not work for some reason, so removing each one
+ // individually.
+ curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
+ curl_setopt($resource, CURLOPT_READFUNCTION, null);
+ curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
+ curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
+ curl_reset($resource);
+ $this->handles[] = $resource;
+ }
+ }
+
+ /**
+ * Completes a cURL transaction, either returning a response promise or a
+ * rejected promise.
+ *
+ * @param callable $handler
+ * @param EasyHandle $easy
+ * @param CurlFactoryInterface $factory Dictates how the handle is released
+ *
+ * @return \GuzzleHttp\Promise\PromiseInterface
+ */
+ public static function finish(
+ callable $handler,
+ EasyHandle $easy,
+ CurlFactoryInterface $factory
+ ) {
+ if (isset($easy->options['on_stats'])) {
+ self::invokeStats($easy);
+ }
+
+ if (!$easy->response || $easy->errno) {
+ return self::finishError($handler, $easy, $factory);
+ }
+
+ // Return the response if it is present and there is no error.
+ $factory->release($easy);
+
+ // Rewind the body of the response if possible.
+ $body = $easy->response->getBody();
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+
+ return new FulfilledPromise($easy->response);
+ }
+
+ private static function invokeStats(EasyHandle $easy)
+ {
+ $curlStats = curl_getinfo($easy->handle);
+ $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME);
+ $stats = new TransferStats(
+ $easy->request,
+ $easy->response,
+ $curlStats['total_time'],
+ $easy->errno,
+ $curlStats
+ );
+ call_user_func($easy->options['on_stats'], $stats);
+ }
+
+ private static function finishError(
+ callable $handler,
+ EasyHandle $easy,
+ CurlFactoryInterface $factory
+ ) {
+ // Get error information and release the handle to the factory.
+ $ctx = [
+ 'errno' => $easy->errno,
+ 'error' => curl_error($easy->handle),
+ 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME),
+ ] + curl_getinfo($easy->handle);
+ $ctx[self::CURL_VERSION_STR] = curl_version()['version'];
+ $factory->release($easy);
+
+ // Retry when nothing is present or when curl failed to rewind.
+ if (empty($easy->options['_err_message'])
+ && (!$easy->errno || $easy->errno == 65)
+ ) {
+ return self::retryFailedRewind($handler, $easy, $ctx);
+ }
+
+ return self::createRejection($easy, $ctx);
+ }
+
+ private static function createRejection(EasyHandle $easy, array $ctx)
+ {
+ static $connectionErrors = [
+ CURLE_OPERATION_TIMEOUTED => true,
+ CURLE_COULDNT_RESOLVE_HOST => true,
+ CURLE_COULDNT_CONNECT => true,
+ CURLE_SSL_CONNECT_ERROR => true,
+ CURLE_GOT_NOTHING => true,
+ ];
+
+ // If an exception was encountered during the onHeaders event, then
+ // return a rejected promise that wraps that exception.
+ if ($easy->onHeadersException) {
+ return \GuzzleHttp\Promise\rejection_for(
+ new RequestException(
+ 'An error was encountered during the on_headers event',
+ $easy->request,
+ $easy->response,
+ $easy->onHeadersException,
+ $ctx
+ )
+ );
+ }
+ if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) {
+ $message = sprintf(
+ 'cURL error %s: %s (%s)',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
+ );
+ } else {
+ $message = sprintf(
+ 'cURL error %s: %s (%s) for %s',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html',
+ $easy->request->getUri()
+ );
+ }
+
+ // Create a connection exception if it was a specific error code.
+ $error = isset($connectionErrors[$easy->errno])
+ ? new ConnectException($message, $easy->request, null, $ctx)
+ : new RequestException($message, $easy->request, $easy->response, null, $ctx);
+
+ return \GuzzleHttp\Promise\rejection_for($error);
+ }
+
+ private function getDefaultConf(EasyHandle $easy)
+ {
+ $conf = [
+ '_headers' => $easy->request->getHeaders(),
+ CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
+ CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
+ CURLOPT_RETURNTRANSFER => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ ];
+
+ if (defined('CURLOPT_PROTOCOLS')) {
+ $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
+ }
+
+ $version = $easy->request->getProtocolVersion();
+ if ($version == 1.1) {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
+ } elseif ($version == 2.0) {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
+ } else {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
+ }
+
+ return $conf;
+ }
+
+ private function applyMethod(EasyHandle $easy, array &$conf)
+ {
+ $body = $easy->request->getBody();
+ $size = $body->getSize();
+
+ if ($size === null || $size > 0) {
+ $this->applyBody($easy->request, $easy->options, $conf);
+ return;
+ }
+
+ $method = $easy->request->getMethod();
+ if ($method === 'PUT' || $method === 'POST') {
+ // See http://tools.ietf.org/html/rfc7230#section-3.3.2
+ if (!$easy->request->hasHeader('Content-Length')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
+ }
+ } elseif ($method === 'HEAD') {
+ $conf[CURLOPT_NOBODY] = true;
+ unset(
+ $conf[CURLOPT_WRITEFUNCTION],
+ $conf[CURLOPT_READFUNCTION],
+ $conf[CURLOPT_FILE],
+ $conf[CURLOPT_INFILE]
+ );
+ }
+ }
+
+ private function applyBody(RequestInterface $request, array $options, array &$conf)
+ {
+ $size = $request->hasHeader('Content-Length')
+ ? (int) $request->getHeaderLine('Content-Length')
+ : null;
+
+ // Send the body as a string if the size is less than 1MB OR if the
+ // [curl][body_as_string] request value is set.
+ if (($size !== null && $size < 1000000) ||
+ !empty($options['_body_as_string'])
+ ) {
+ $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
+ // Don't duplicate the Content-Length header
+ $this->removeHeader('Content-Length', $conf);
+ $this->removeHeader('Transfer-Encoding', $conf);
+ } else {
+ $conf[CURLOPT_UPLOAD] = true;
+ if ($size !== null) {
+ $conf[CURLOPT_INFILESIZE] = $size;
+ $this->removeHeader('Content-Length', $conf);
+ }
+ $body = $request->getBody();
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+ $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
+ return $body->read($length);
+ };
+ }
+
+ // If the Expect header is not present, prevent curl from adding it
+ if (!$request->hasHeader('Expect')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
+ }
+
+ // cURL sometimes adds a content-type by default. Prevent this.
+ if (!$request->hasHeader('Content-Type')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
+ }
+ }
+
+ private function applyHeaders(EasyHandle $easy, array &$conf)
+ {
+ foreach ($conf['_headers'] as $name => $values) {
+ foreach ($values as $value) {
+ $value = (string) $value;
+ if ($value === '') {
+ // cURL requires a special format for empty headers.
+ // See https://github.com/guzzle/guzzle/issues/1882 for more details.
+ $conf[CURLOPT_HTTPHEADER][] = "$name;";
+ } else {
+ $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
+ }
+ }
+ }
+
+ // Remove the Accept header if one was not set
+ if (!$easy->request->hasHeader('Accept')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
+ }
+ }
+
+ /**
+ * Remove a header from the options array.
+ *
+ * @param string $name Case-insensitive header to remove
+ * @param array $options Array of options to modify
+ */
+ private function removeHeader($name, array &$options)
+ {
+ foreach (array_keys($options['_headers']) as $key) {
+ if (!strcasecmp($key, $name)) {
+ unset($options['_headers'][$key]);
+ return;
+ }
+ }
+ }
+
+ private function applyHandlerOptions(EasyHandle $easy, array &$conf)
+ {
+ $options = $easy->options;
+ if (isset($options['verify'])) {
+ if ($options['verify'] === false) {
+ unset($conf[CURLOPT_CAINFO]);
+ $conf[CURLOPT_SSL_VERIFYHOST] = 0;
+ $conf[CURLOPT_SSL_VERIFYPEER] = false;
+ } else {
+ $conf[CURLOPT_SSL_VERIFYHOST] = 2;
+ $conf[CURLOPT_SSL_VERIFYPEER] = true;
+ if (is_string($options['verify'])) {
+ // Throw an error if the file/folder/link path is not valid or doesn't exist.
+ if (!file_exists($options['verify'])) {
+ throw new \InvalidArgumentException(
+ "SSL CA bundle not found: {$options['verify']}"
+ );
+ }
+ // If it's a directory or a link to a directory use CURLOPT_CAPATH.
+ // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
+ if (is_dir($options['verify']) ||
+ (is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
+ $conf[CURLOPT_CAPATH] = $options['verify'];
+ } else {
+ $conf[CURLOPT_CAINFO] = $options['verify'];
+ }
+ }
+ }
+ }
+
+ if (!empty($options['decode_content'])) {
+ $accept = $easy->request->getHeaderLine('Accept-Encoding');
+ if ($accept) {
+ $conf[CURLOPT_ENCODING] = $accept;
+ } else {
+ $conf[CURLOPT_ENCODING] = '';
+ // Don't let curl send the header over the wire
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
+ }
+ }
+
+ if (isset($options['sink'])) {
+ $sink = $options['sink'];
+ if (!is_string($sink)) {
+ $sink = \GuzzleHttp\Psr7\stream_for($sink);
+ } elseif (!is_dir(dirname($sink))) {
+ // Ensure that the directory exists before failing in curl.
+ throw new \RuntimeException(sprintf(
+ 'Directory %s does not exist for sink value of %s',
+ dirname($sink),
+ $sink
+ ));
+ } else {
+ $sink = new LazyOpenStream($sink, 'w+');
+ }
+ $easy->sink = $sink;
+ $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
+ return $sink->write($write);
+ };
+ } else {
+ // Use a default temp stream if no sink was set.
+ $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
+ $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
+ }
+ $timeoutRequiresNoSignal = false;
+ if (isset($options['timeout'])) {
+ $timeoutRequiresNoSignal |= $options['timeout'] < 1;
+ $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
+ }
+
+ // CURL default value is CURL_IPRESOLVE_WHATEVER
+ if (isset($options['force_ip_resolve'])) {
+ if ('v4' === $options['force_ip_resolve']) {
+ $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
+ } elseif ('v6' === $options['force_ip_resolve']) {
+ $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
+ }
+ }
+
+ if (isset($options['connect_timeout'])) {
+ $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
+ $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
+ }
+
+ if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
+ $conf[CURLOPT_NOSIGNAL] = true;
+ }
+
+ if (isset($options['proxy'])) {
+ if (!is_array($options['proxy'])) {
+ $conf[CURLOPT_PROXY] = $options['proxy'];
+ } else {
+ $scheme = $easy->request->getUri()->getScheme();
+ if (isset($options['proxy'][$scheme])) {
+ $host = $easy->request->getUri()->getHost();
+ if (!isset($options['proxy']['no']) ||
+ !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
+ ) {
+ $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
+ }
+ }
+ }
+ }
+
+ if (isset($options['cert'])) {
+ $cert = $options['cert'];
+ if (is_array($cert)) {
+ $conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
+ $cert = $cert[0];
+ }
+ if (!file_exists($cert)) {
+ throw new \InvalidArgumentException(
+ "SSL certificate not found: {$cert}"
+ );
+ }
+ $conf[CURLOPT_SSLCERT] = $cert;
+ }
+
+ if (isset($options['ssl_key'])) {
+ $sslKey = $options['ssl_key'];
+ if (is_array($sslKey)) {
+ $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1];
+ $sslKey = $sslKey[0];
+ }
+ if (!file_exists($sslKey)) {
+ throw new \InvalidArgumentException(
+ "SSL private key not found: {$sslKey}"
+ );
+ }
+ $conf[CURLOPT_SSLKEY] = $sslKey;
+ }
+
+ if (isset($options['progress'])) {
+ $progress = $options['progress'];
+ if (!is_callable($progress)) {
+ throw new \InvalidArgumentException(
+ 'progress client option must be callable'
+ );
+ }
+ $conf[CURLOPT_NOPROGRESS] = false;
+ $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
+ $args = func_get_args();
+ // PHP 5.5 pushed the handle onto the start of the args
+ if (is_resource($args[0])) {
+ array_shift($args);
+ }
+ call_user_func_array($progress, $args);
+ };
+ }
+
+ if (!empty($options['debug'])) {
+ $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
+ $conf[CURLOPT_VERBOSE] = true;
+ }
+ }
+
+ /**
+ * This function ensures that a response was set on a transaction. If one
+ * was not set, then the request is retried if possible. This error
+ * typically means you are sending a payload, curl encountered a
+ * "Connection died, retrying a fresh connect" error, tried to rewind the
+ * stream, and then encountered a "necessary data rewind wasn't possible"
+ * error, causing the request to be sent through curl_multi_info_read()
+ * without an error status.
+ */
+ private static function retryFailedRewind(
+ callable $handler,
+ EasyHandle $easy,
+ array $ctx
+ ) {
+ try {
+ // Only rewind if the body has been read from.
+ $body = $easy->request->getBody();
+ if ($body->tell() > 0) {
+ $body->rewind();
+ }
+ } catch (\RuntimeException $e) {
+ $ctx['error'] = 'The connection unexpectedly failed without '
+ . 'providing an error. The request would have been retried, '
+ . 'but attempting to rewind the request body failed. '
+ . 'Exception: ' . $e;
+ return self::createRejection($easy, $ctx);
+ }
+
+ // Retry no more than 3 times before giving up.
+ if (!isset($easy->options['_curl_retries'])) {
+ $easy->options['_curl_retries'] = 1;
+ } elseif ($easy->options['_curl_retries'] == 2) {
+ $ctx['error'] = 'The cURL request was retried 3 times '
+ . 'and did not succeed. The most likely reason for the failure '
+ . 'is that cURL was unable to rewind the body of the request '
+ . 'and subsequent retries resulted in the same error. Turn on '
+ . 'the debug option to see what went wrong. See '
+ . 'https://bugs.php.net/bug.php?id=47204 for more information.';
+ return self::createRejection($easy, $ctx);
+ } else {
+ $easy->options['_curl_retries']++;
+ }
+
+ return $handler($easy->request, $easy->options);
+ }
+
+ private function createHeaderFn(EasyHandle $easy)
+ {
+ if (isset($easy->options['on_headers'])) {
+ $onHeaders = $easy->options['on_headers'];
+
+ if (!is_callable($onHeaders)) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+ } else {
+ $onHeaders = null;
+ }
+
+ return function ($ch, $h) use (
+ $onHeaders,
+ $easy,
+ &$startingResponse
+ ) {
+ $value = trim($h);
+ if ($value === '') {
+ $startingResponse = true;
+ $easy->createResponse();
+ if ($onHeaders !== null) {
+ try {
+ $onHeaders($easy->response);
+ } catch (\Exception $e) {
+ // Associate the exception with the handle and trigger
+ // a curl header write error by returning 0.
+ $easy->onHeadersException = $e;
+ return -1;
+ }
+ }
+ } elseif ($startingResponse) {
+ $startingResponse = false;
+ $easy->headers = [$value];
+ } else {
+ $easy->headers[] = $value;
+ }
+ return strlen($h);
+ };
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
new file mode 100755
index 0000000..b0fc236
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
@@ -0,0 +1,27 @@
+factory = isset($options['handle_factory'])
+ ? $options['handle_factory']
+ : new CurlFactory(3);
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (isset($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ $easy = $this->factory->create($request, $options);
+ curl_exec($easy->handle);
+ $easy->errno = curl_errno($easy->handle);
+
+ return CurlFactory::finish($this, $easy, $this->factory);
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
new file mode 100755
index 0000000..d829762
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
@@ -0,0 +1,205 @@
+factory = isset($options['handle_factory'])
+ ? $options['handle_factory'] : new CurlFactory(50);
+
+ if (isset($options['select_timeout'])) {
+ $this->selectTimeout = $options['select_timeout'];
+ } elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
+ $this->selectTimeout = $selectTimeout;
+ } else {
+ $this->selectTimeout = 1;
+ }
+ }
+
+ public function __get($name)
+ {
+ if ($name === '_mh') {
+ return $this->_mh = curl_multi_init();
+ }
+
+ throw new \BadMethodCallException();
+ }
+
+ public function __destruct()
+ {
+ if (isset($this->_mh)) {
+ curl_multi_close($this->_mh);
+ unset($this->_mh);
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $easy = $this->factory->create($request, $options);
+ $id = (int) $easy->handle;
+
+ $promise = new Promise(
+ [$this, 'execute'],
+ function () use ($id) {
+ return $this->cancel($id);
+ }
+ );
+
+ $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
+
+ return $promise;
+ }
+
+ /**
+ * Ticks the curl event loop.
+ */
+ public function tick()
+ {
+ // Add any delayed handles if needed.
+ if ($this->delays) {
+ $currentTime = \GuzzleHttp\_current_time();
+ foreach ($this->delays as $id => $delay) {
+ if ($currentTime >= $delay) {
+ unset($this->delays[$id]);
+ curl_multi_add_handle(
+ $this->_mh,
+ $this->handles[$id]['easy']->handle
+ );
+ }
+ }
+ }
+
+ // Step through the task queue which may add additional requests.
+ P\queue()->run();
+
+ if ($this->active &&
+ curl_multi_select($this->_mh, $this->selectTimeout) === -1
+ ) {
+ // Perform a usleep if a select returns -1.
+ // See: https://bugs.php.net/bug.php?id=61141
+ usleep(250);
+ }
+
+ while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);
+
+ $this->processMessages();
+ }
+
+ /**
+ * Runs until all outstanding connections have completed.
+ */
+ public function execute()
+ {
+ $queue = P\queue();
+
+ while ($this->handles || !$queue->isEmpty()) {
+ // If there are no transfers, then sleep for the next delay
+ if (!$this->active && $this->delays) {
+ usleep($this->timeToNext());
+ }
+ $this->tick();
+ }
+ }
+
+ private function addRequest(array $entry)
+ {
+ $easy = $entry['easy'];
+ $id = (int) $easy->handle;
+ $this->handles[$id] = $entry;
+ if (empty($easy->options['delay'])) {
+ curl_multi_add_handle($this->_mh, $easy->handle);
+ } else {
+ $this->delays[$id] = \GuzzleHttp\_current_time() + ($easy->options['delay'] / 1000);
+ }
+ }
+
+ /**
+ * Cancels a handle from sending and removes references to it.
+ *
+ * @param int $id Handle ID to cancel and remove.
+ *
+ * @return bool True on success, false on failure.
+ */
+ private function cancel($id)
+ {
+ // Cannot cancel if it has been processed.
+ if (!isset($this->handles[$id])) {
+ return false;
+ }
+
+ $handle = $this->handles[$id]['easy']->handle;
+ unset($this->delays[$id], $this->handles[$id]);
+ curl_multi_remove_handle($this->_mh, $handle);
+ curl_close($handle);
+
+ return true;
+ }
+
+ private function processMessages()
+ {
+ while ($done = curl_multi_info_read($this->_mh)) {
+ $id = (int) $done['handle'];
+ curl_multi_remove_handle($this->_mh, $done['handle']);
+
+ if (!isset($this->handles[$id])) {
+ // Probably was cancelled.
+ continue;
+ }
+
+ $entry = $this->handles[$id];
+ unset($this->handles[$id], $this->delays[$id]);
+ $entry['easy']->errno = $done['result'];
+ $entry['deferred']->resolve(
+ CurlFactory::finish(
+ $this,
+ $entry['easy'],
+ $this->factory
+ )
+ );
+ }
+ }
+
+ private function timeToNext()
+ {
+ $currentTime = \GuzzleHttp\_current_time();
+ $nextTime = PHP_INT_MAX;
+ foreach ($this->delays as $time) {
+ if ($time < $nextTime) {
+ $nextTime = $time;
+ }
+ }
+
+ return max(0, $nextTime - $currentTime) * 1000000;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
new file mode 100755
index 0000000..7754e91
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
@@ -0,0 +1,92 @@
+headers)) {
+ throw new \RuntimeException('No headers have been received');
+ }
+
+ // HTTP-version SP status-code SP reason-phrase
+ $startLine = explode(' ', array_shift($this->headers), 3);
+ $headers = \GuzzleHttp\headers_from_lines($this->headers);
+ $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
+
+ if (!empty($this->options['decode_content'])
+ && isset($normalizedKeys['content-encoding'])
+ ) {
+ $headers['x-encoded-content-encoding']
+ = $headers[$normalizedKeys['content-encoding']];
+ unset($headers[$normalizedKeys['content-encoding']]);
+ if (isset($normalizedKeys['content-length'])) {
+ $headers['x-encoded-content-length']
+ = $headers[$normalizedKeys['content-length']];
+
+ $bodyLength = (int) $this->sink->getSize();
+ if ($bodyLength) {
+ $headers[$normalizedKeys['content-length']] = $bodyLength;
+ } else {
+ unset($headers[$normalizedKeys['content-length']]);
+ }
+ }
+ }
+
+ // Attach a response to the easy handle with the parsed headers.
+ $this->response = new Response(
+ $startLine[1],
+ $headers,
+ $this->sink,
+ substr($startLine[0], 5),
+ isset($startLine[2]) ? (string) $startLine[2] : null
+ );
+ }
+
+ public function __get($name)
+ {
+ $msg = $name === 'handle'
+ ? 'The EasyHandle has been released'
+ : 'Invalid property: ' . $name;
+ throw new \BadMethodCallException($msg);
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
new file mode 100755
index 0000000..d5c449c
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
@@ -0,0 +1,190 @@
+onFulfilled = $onFulfilled;
+ $this->onRejected = $onRejected;
+
+ if ($queue) {
+ call_user_func_array([$this, 'append'], $queue);
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (!$this->queue) {
+ throw new \OutOfBoundsException('Mock queue is empty');
+ }
+
+ if (isset($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ $this->lastRequest = $request;
+ $this->lastOptions = $options;
+ $response = array_shift($this->queue);
+
+ if (isset($options['on_headers'])) {
+ if (!is_callable($options['on_headers'])) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+ try {
+ $options['on_headers']($response);
+ } catch (\Exception $e) {
+ $msg = 'An error was encountered during the on_headers event';
+ $response = new RequestException($msg, $request, $response, $e);
+ }
+ }
+
+ if (is_callable($response)) {
+ $response = call_user_func($response, $request, $options);
+ }
+
+ $response = $response instanceof \Exception
+ ? \GuzzleHttp\Promise\rejection_for($response)
+ : \GuzzleHttp\Promise\promise_for($response);
+
+ return $response->then(
+ function ($value) use ($request, $options) {
+ $this->invokeStats($request, $options, $value);
+ if ($this->onFulfilled) {
+ call_user_func($this->onFulfilled, $value);
+ }
+ if (isset($options['sink'])) {
+ $contents = (string) $value->getBody();
+ $sink = $options['sink'];
+
+ if (is_resource($sink)) {
+ fwrite($sink, $contents);
+ } elseif (is_string($sink)) {
+ file_put_contents($sink, $contents);
+ } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) {
+ $sink->write($contents);
+ }
+ }
+
+ return $value;
+ },
+ function ($reason) use ($request, $options) {
+ $this->invokeStats($request, $options, null, $reason);
+ if ($this->onRejected) {
+ call_user_func($this->onRejected, $reason);
+ }
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ }
+
+ /**
+ * Adds one or more variadic requests, exceptions, callables, or promises
+ * to the queue.
+ */
+ public function append()
+ {
+ foreach (func_get_args() as $value) {
+ if ($value instanceof ResponseInterface
+ || $value instanceof \Exception
+ || $value instanceof PromiseInterface
+ || is_callable($value)
+ ) {
+ $this->queue[] = $value;
+ } else {
+ throw new \InvalidArgumentException('Expected a response or '
+ . 'exception. Found ' . \GuzzleHttp\describe_type($value));
+ }
+ }
+ }
+
+ /**
+ * Get the last received request.
+ *
+ * @return RequestInterface
+ */
+ public function getLastRequest()
+ {
+ return $this->lastRequest;
+ }
+
+ /**
+ * Get the last received request options.
+ *
+ * @return array
+ */
+ public function getLastOptions()
+ {
+ return $this->lastOptions;
+ }
+
+ /**
+ * Returns the number of remaining items in the queue.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->queue);
+ }
+
+ private function invokeStats(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response = null,
+ $reason = null
+ ) {
+ if (isset($options['on_stats'])) {
+ $transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0;
+ $stats = new TransferStats($request, $response, $transferTime, $reason);
+ call_user_func($options['on_stats'], $stats);
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
new file mode 100755
index 0000000..f8b00be
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
@@ -0,0 +1,55 @@
+withoutHeader('Expect');
+
+ // Append a content-length header if body size is zero to match
+ // cURL's behavior.
+ if (0 === $request->getBody()->getSize()) {
+ $request = $request->withHeader('Content-Length', '0');
+ }
+
+ return $this->createResponse(
+ $request,
+ $options,
+ $this->createStream($request, $options),
+ $startTime
+ );
+ } catch (\InvalidArgumentException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Determine if the error was a networking error.
+ $message = $e->getMessage();
+ // This list can probably get more comprehensive.
+ if (strpos($message, 'getaddrinfo') // DNS lookup failed
+ || strpos($message, 'Connection refused')
+ || strpos($message, "couldn't connect to host") // error on HHVM
+ || strpos($message, "connection attempt failed")
+ ) {
+ $e = new ConnectException($e->getMessage(), $request, $e);
+ }
+ $e = RequestException::wrapException($request, $e);
+ $this->invokeStats($options, $request, $startTime, null, $e);
+
+ return \GuzzleHttp\Promise\rejection_for($e);
+ }
+ }
+
+ private function invokeStats(
+ array $options,
+ RequestInterface $request,
+ $startTime,
+ ResponseInterface $response = null,
+ $error = null
+ ) {
+ if (isset($options['on_stats'])) {
+ $stats = new TransferStats(
+ $request,
+ $response,
+ \GuzzleHttp\_current_time() - $startTime,
+ $error,
+ []
+ );
+ call_user_func($options['on_stats'], $stats);
+ }
+ }
+
+ private function createResponse(
+ RequestInterface $request,
+ array $options,
+ $stream,
+ $startTime
+ ) {
+ $hdrs = $this->lastHeaders;
+ $this->lastHeaders = [];
+ $parts = explode(' ', array_shift($hdrs), 3);
+ $ver = explode('/', $parts[0])[1];
+ $status = $parts[1];
+ $reason = isset($parts[2]) ? $parts[2] : null;
+ $headers = \GuzzleHttp\headers_from_lines($hdrs);
+ list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
+ $stream = Psr7\stream_for($stream);
+ $sink = $stream;
+
+ if (strcasecmp('HEAD', $request->getMethod())) {
+ $sink = $this->createSink($stream, $options);
+ }
+
+ $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
+
+ if (isset($options['on_headers'])) {
+ try {
+ $options['on_headers']($response);
+ } catch (\Exception $e) {
+ $msg = 'An error was encountered during the on_headers event';
+ $ex = new RequestException($msg, $request, $response, $e);
+ return \GuzzleHttp\Promise\rejection_for($ex);
+ }
+ }
+
+ // Do not drain when the request is a HEAD request because they have
+ // no body.
+ if ($sink !== $stream) {
+ $this->drain(
+ $stream,
+ $sink,
+ $response->getHeaderLine('Content-Length')
+ );
+ }
+
+ $this->invokeStats($options, $request, $startTime, $response, null);
+
+ return new FulfilledPromise($response);
+ }
+
+ private function createSink(StreamInterface $stream, array $options)
+ {
+ if (!empty($options['stream'])) {
+ return $stream;
+ }
+
+ $sink = isset($options['sink'])
+ ? $options['sink']
+ : fopen('php://temp', 'r+');
+
+ return is_string($sink)
+ ? new Psr7\LazyOpenStream($sink, 'w+')
+ : Psr7\stream_for($sink);
+ }
+
+ private function checkDecode(array $options, array $headers, $stream)
+ {
+ // Automatically decode responses when instructed.
+ if (!empty($options['decode_content'])) {
+ $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
+ if (isset($normalizedKeys['content-encoding'])) {
+ $encoding = $headers[$normalizedKeys['content-encoding']];
+ if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
+ $stream = new Psr7\InflateStream(
+ Psr7\stream_for($stream)
+ );
+ $headers['x-encoded-content-encoding']
+ = $headers[$normalizedKeys['content-encoding']];
+ // Remove content-encoding header
+ unset($headers[$normalizedKeys['content-encoding']]);
+ // Fix content-length header
+ if (isset($normalizedKeys['content-length'])) {
+ $headers['x-encoded-content-length']
+ = $headers[$normalizedKeys['content-length']];
+
+ $length = (int) $stream->getSize();
+ if ($length === 0) {
+ unset($headers[$normalizedKeys['content-length']]);
+ } else {
+ $headers[$normalizedKeys['content-length']] = [$length];
+ }
+ }
+ }
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ /**
+ * Drains the source stream into the "sink" client option.
+ *
+ * @param StreamInterface $source
+ * @param StreamInterface $sink
+ * @param string $contentLength Header specifying the amount of
+ * data to read.
+ *
+ * @return StreamInterface
+ * @throws \RuntimeException when the sink option is invalid.
+ */
+ private function drain(
+ StreamInterface $source,
+ StreamInterface $sink,
+ $contentLength
+ ) {
+ // If a content-length header is provided, then stop reading once
+ // that number of bytes has been read. This can prevent infinitely
+ // reading from a stream when dealing with servers that do not honor
+ // Connection: Close headers.
+ Psr7\copy_to_stream(
+ $source,
+ $sink,
+ (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
+ );
+
+ $sink->seek(0);
+ $source->close();
+
+ return $sink;
+ }
+
+ /**
+ * Create a resource and check to ensure it was created successfully
+ *
+ * @param callable $callback Callable that returns stream resource
+ *
+ * @return resource
+ * @throws \RuntimeException on error
+ */
+ private function createResource(callable $callback)
+ {
+ $errors = null;
+ set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
+ $errors[] = [
+ 'message' => $msg,
+ 'file' => $file,
+ 'line' => $line
+ ];
+ return true;
+ });
+
+ $resource = $callback();
+ restore_error_handler();
+
+ if (!$resource) {
+ $message = 'Error creating resource: ';
+ foreach ($errors as $err) {
+ foreach ($err as $key => $value) {
+ $message .= "[$key] $value" . PHP_EOL;
+ }
+ }
+ throw new \RuntimeException(trim($message));
+ }
+
+ return $resource;
+ }
+
+ private function createStream(RequestInterface $request, array $options)
+ {
+ static $methods;
+ if (!$methods) {
+ $methods = array_flip(get_class_methods(__CLASS__));
+ }
+
+ // HTTP/1.1 streams using the PHP stream wrapper require a
+ // Connection: close header
+ if ($request->getProtocolVersion() == '1.1'
+ && !$request->hasHeader('Connection')
+ ) {
+ $request = $request->withHeader('Connection', 'close');
+ }
+
+ // Ensure SSL is verified by default
+ if (!isset($options['verify'])) {
+ $options['verify'] = true;
+ }
+
+ $params = [];
+ $context = $this->getDefaultContext($request);
+
+ if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+
+ if (!empty($options)) {
+ foreach ($options as $key => $value) {
+ $method = "add_{$key}";
+ if (isset($methods[$method])) {
+ $this->{$method}($request, $context, $value, $params);
+ }
+ }
+ }
+
+ if (isset($options['stream_context'])) {
+ if (!is_array($options['stream_context'])) {
+ throw new \InvalidArgumentException('stream_context must be an array');
+ }
+ $context = array_replace_recursive(
+ $context,
+ $options['stream_context']
+ );
+ }
+
+ // Microsoft NTLM authentication only supported with curl handler
+ if (isset($options['auth'])
+ && is_array($options['auth'])
+ && isset($options['auth'][2])
+ && 'ntlm' == $options['auth'][2]
+ ) {
+ throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
+ }
+
+ $uri = $this->resolveHost($request, $options);
+
+ $context = $this->createResource(
+ function () use ($context, $params) {
+ return stream_context_create($context, $params);
+ }
+ );
+
+ return $this->createResource(
+ function () use ($uri, &$http_response_header, $context, $options) {
+ $resource = fopen((string) $uri, 'r', null, $context);
+ $this->lastHeaders = $http_response_header;
+
+ if (isset($options['read_timeout'])) {
+ $readTimeout = $options['read_timeout'];
+ $sec = (int) $readTimeout;
+ $usec = ($readTimeout - $sec) * 100000;
+ stream_set_timeout($resource, $sec, $usec);
+ }
+
+ return $resource;
+ }
+ );
+ }
+
+ private function resolveHost(RequestInterface $request, array $options)
+ {
+ $uri = $request->getUri();
+
+ if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
+ if ('v4' === $options['force_ip_resolve']) {
+ $records = dns_get_record($uri->getHost(), DNS_A);
+ if (!isset($records[0]['ip'])) {
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv4 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
+ }
+ $uri = $uri->withHost($records[0]['ip']);
+ } elseif ('v6' === $options['force_ip_resolve']) {
+ $records = dns_get_record($uri->getHost(), DNS_AAAA);
+ if (!isset($records[0]['ipv6'])) {
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv6 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
+ }
+ $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
+ }
+ }
+
+ return $uri;
+ }
+
+ private function getDefaultContext(RequestInterface $request)
+ {
+ $headers = '';
+ foreach ($request->getHeaders() as $name => $value) {
+ foreach ($value as $val) {
+ $headers .= "$name: $val\r\n";
+ }
+ }
+
+ $context = [
+ 'http' => [
+ 'method' => $request->getMethod(),
+ 'header' => $headers,
+ 'protocol_version' => $request->getProtocolVersion(),
+ 'ignore_errors' => true,
+ 'follow_location' => 0,
+ ],
+ ];
+
+ $body = (string) $request->getBody();
+
+ if (!empty($body)) {
+ $context['http']['content'] = $body;
+ // Prevent the HTTP handler from adding a Content-Type header.
+ if (!$request->hasHeader('Content-Type')) {
+ $context['http']['header'] .= "Content-Type:\r\n";
+ }
+ }
+
+ $context['http']['header'] = rtrim($context['http']['header']);
+
+ return $context;
+ }
+
+ private function add_proxy(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (!is_array($value)) {
+ $options['http']['proxy'] = $value;
+ } else {
+ $scheme = $request->getUri()->getScheme();
+ if (isset($value[$scheme])) {
+ if (!isset($value['no'])
+ || !\GuzzleHttp\is_host_in_noproxy(
+ $request->getUri()->getHost(),
+ $value['no']
+ )
+ ) {
+ $options['http']['proxy'] = $value[$scheme];
+ }
+ }
+ }
+ }
+
+ private function add_timeout(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value > 0) {
+ $options['http']['timeout'] = $value;
+ }
+ }
+
+ private function add_verify(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === true) {
+ // PHP 5.6 or greater will find the system cert by default. When
+ // < 5.6, use the Guzzle bundled cacert.
+ if (PHP_VERSION_ID < 50600) {
+ $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
+ }
+ } elseif (is_string($value)) {
+ $options['ssl']['cafile'] = $value;
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL CA bundle not found: $value");
+ }
+ } elseif ($value === false) {
+ $options['ssl']['verify_peer'] = false;
+ $options['ssl']['verify_peer_name'] = false;
+ return;
+ } else {
+ throw new \InvalidArgumentException('Invalid verify request option');
+ }
+
+ $options['ssl']['verify_peer'] = true;
+ $options['ssl']['verify_peer_name'] = true;
+ $options['ssl']['allow_self_signed'] = false;
+ }
+
+ private function add_cert(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (is_array($value)) {
+ $options['ssl']['passphrase'] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL certificate not found: {$value}");
+ }
+
+ $options['ssl']['local_cert'] = $value;
+ }
+
+ private function add_progress(RequestInterface $request, &$options, $value, &$params)
+ {
+ $this->addNotification(
+ $params,
+ function ($code, $a, $b, $c, $transferred, $total) use ($value) {
+ if ($code == STREAM_NOTIFY_PROGRESS) {
+ $value($total, $transferred, null, null);
+ }
+ }
+ );
+ }
+
+ private function add_debug(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === false) {
+ return;
+ }
+
+ static $map = [
+ STREAM_NOTIFY_CONNECT => 'CONNECT',
+ STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
+ STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
+ STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
+ STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
+ STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
+ STREAM_NOTIFY_PROGRESS => 'PROGRESS',
+ STREAM_NOTIFY_FAILURE => 'FAILURE',
+ STREAM_NOTIFY_COMPLETED => 'COMPLETED',
+ STREAM_NOTIFY_RESOLVE => 'RESOLVE',
+ ];
+ static $args = ['severity', 'message', 'message_code',
+ 'bytes_transferred', 'bytes_max'];
+
+ $value = \GuzzleHttp\debug_resource($value);
+ $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
+ $this->addNotification(
+ $params,
+ function () use ($ident, $value, $map, $args) {
+ $passed = func_get_args();
+ $code = array_shift($passed);
+ fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
+ foreach (array_filter($passed) as $i => $v) {
+ fwrite($value, $args[$i] . ': "' . $v . '" ');
+ }
+ fwrite($value, "\n");
+ }
+ );
+ }
+
+ private function addNotification(array &$params, callable $notify)
+ {
+ // Wrap the existing function if needed.
+ if (!isset($params['notification'])) {
+ $params['notification'] = $notify;
+ } else {
+ $params['notification'] = $this->callArray([
+ $params['notification'],
+ $notify
+ ]);
+ }
+ }
+
+ private function callArray(array $functions)
+ {
+ return function () use ($functions) {
+ $args = func_get_args();
+ foreach ($functions as $fn) {
+ call_user_func_array($fn, $args);
+ }
+ };
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/instafeed/vendor/guzzlehttp/guzzle/src/HandlerStack.php
new file mode 100755
index 0000000..f001686
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/HandlerStack.php
@@ -0,0 +1,273 @@
+push(Middleware::httpErrors(), 'http_errors');
+ $stack->push(Middleware::redirect(), 'allow_redirects');
+ $stack->push(Middleware::cookies(), 'cookies');
+ $stack->push(Middleware::prepareBody(), 'prepare_body');
+
+ return $stack;
+ }
+
+ /**
+ * @param callable $handler Underlying HTTP handler.
+ */
+ public function __construct(callable $handler = null)
+ {
+ $this->handler = $handler;
+ }
+
+ /**
+ * Invokes the handler stack as a composed handler
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $handler = $this->resolve();
+
+ return $handler($request, $options);
+ }
+
+ /**
+ * Dumps a string representation of the stack.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $depth = 0;
+ $stack = [];
+ if ($this->handler) {
+ $stack[] = "0) Handler: " . $this->debugCallable($this->handler);
+ }
+
+ $result = '';
+ foreach (array_reverse($this->stack) as $tuple) {
+ $depth++;
+ $str = "{$depth}) Name: '{$tuple[1]}', ";
+ $str .= "Function: " . $this->debugCallable($tuple[0]);
+ $result = "> {$str}\n{$result}";
+ $stack[] = $str;
+ }
+
+ foreach (array_keys($stack) as $k) {
+ $result .= "< {$stack[$k]}\n";
+ }
+
+ return $result;
+ }
+
+ /**
+ * Set the HTTP handler that actually returns a promise.
+ *
+ * @param callable $handler Accepts a request and array of options and
+ * returns a Promise.
+ */
+ public function setHandler(callable $handler)
+ {
+ $this->handler = $handler;
+ $this->cached = null;
+ }
+
+ /**
+ * Returns true if the builder has a handler.
+ *
+ * @return bool
+ */
+ public function hasHandler()
+ {
+ return (bool) $this->handler;
+ }
+
+ /**
+ * Unshift a middleware to the bottom of the stack.
+ *
+ * @param callable $middleware Middleware function
+ * @param string $name Name to register for this middleware.
+ */
+ public function unshift(callable $middleware, $name = null)
+ {
+ array_unshift($this->stack, [$middleware, $name]);
+ $this->cached = null;
+ }
+
+ /**
+ * Push a middleware to the top of the stack.
+ *
+ * @param callable $middleware Middleware function
+ * @param string $name Name to register for this middleware.
+ */
+ public function push(callable $middleware, $name = '')
+ {
+ $this->stack[] = [$middleware, $name];
+ $this->cached = null;
+ }
+
+ /**
+ * Add a middleware before another middleware by name.
+ *
+ * @param string $findName Middleware to find
+ * @param callable $middleware Middleware function
+ * @param string $withName Name to register for this middleware.
+ */
+ public function before($findName, callable $middleware, $withName = '')
+ {
+ $this->splice($findName, $withName, $middleware, true);
+ }
+
+ /**
+ * Add a middleware after another middleware by name.
+ *
+ * @param string $findName Middleware to find
+ * @param callable $middleware Middleware function
+ * @param string $withName Name to register for this middleware.
+ */
+ public function after($findName, callable $middleware, $withName = '')
+ {
+ $this->splice($findName, $withName, $middleware, false);
+ }
+
+ /**
+ * Remove a middleware by instance or name from the stack.
+ *
+ * @param callable|string $remove Middleware to remove by instance or name.
+ */
+ public function remove($remove)
+ {
+ $this->cached = null;
+ $idx = is_callable($remove) ? 0 : 1;
+ $this->stack = array_values(array_filter(
+ $this->stack,
+ function ($tuple) use ($idx, $remove) {
+ return $tuple[$idx] !== $remove;
+ }
+ ));
+ }
+
+ /**
+ * Compose the middleware and handler into a single callable function.
+ *
+ * @return callable
+ */
+ public function resolve()
+ {
+ if (!$this->cached) {
+ if (!($prev = $this->handler)) {
+ throw new \LogicException('No handler has been specified');
+ }
+
+ foreach (array_reverse($this->stack) as $fn) {
+ $prev = $fn[0]($prev);
+ }
+
+ $this->cached = $prev;
+ }
+
+ return $this->cached;
+ }
+
+ /**
+ * @param string $name
+ * @return int
+ */
+ private function findByName($name)
+ {
+ foreach ($this->stack as $k => $v) {
+ if ($v[1] === $name) {
+ return $k;
+ }
+ }
+
+ throw new \InvalidArgumentException("Middleware not found: $name");
+ }
+
+ /**
+ * Splices a function into the middleware list at a specific position.
+ *
+ * @param string $findName
+ * @param string $withName
+ * @param callable $middleware
+ * @param bool $before
+ */
+ private function splice($findName, $withName, callable $middleware, $before)
+ {
+ $this->cached = null;
+ $idx = $this->findByName($findName);
+ $tuple = [$middleware, $withName];
+
+ if ($before) {
+ if ($idx === 0) {
+ array_unshift($this->stack, $tuple);
+ } else {
+ $replacement = [$tuple, $this->stack[$idx]];
+ array_splice($this->stack, $idx, 1, $replacement);
+ }
+ } elseif ($idx === count($this->stack) - 1) {
+ $this->stack[] = $tuple;
+ } else {
+ $replacement = [$this->stack[$idx], $tuple];
+ array_splice($this->stack, $idx, 1, $replacement);
+ }
+ }
+
+ /**
+ * Provides a debug string for a given callable.
+ *
+ * @param array|callable $fn Function to write as a string.
+ *
+ * @return string
+ */
+ private function debugCallable($fn)
+ {
+ if (is_string($fn)) {
+ return "callable({$fn})";
+ }
+
+ if (is_array($fn)) {
+ return is_string($fn[0])
+ ? "callable({$fn[0]}::{$fn[1]})"
+ : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])";
+ }
+
+ return 'callable(' . spl_object_hash($fn) . ')';
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/instafeed/vendor/guzzlehttp/guzzle/src/MessageFormatter.php
new file mode 100755
index 0000000..663ac73
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/MessageFormatter.php
@@ -0,0 +1,180 @@
+>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
+ const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';
+
+ /** @var string Template used to format log messages */
+ private $template;
+
+ /**
+ * @param string $template Log message template
+ */
+ public function __construct($template = self::CLF)
+ {
+ $this->template = $template ?: self::CLF;
+ }
+
+ /**
+ * Returns a formatted message string.
+ *
+ * @param RequestInterface $request Request that was sent
+ * @param ResponseInterface $response Response that was received
+ * @param \Exception $error Exception that was received
+ *
+ * @return string
+ */
+ public function format(
+ RequestInterface $request,
+ ResponseInterface $response = null,
+ \Exception $error = null
+ ) {
+ $cache = [];
+
+ return preg_replace_callback(
+ '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
+ function (array $matches) use ($request, $response, $error, &$cache) {
+ if (isset($cache[$matches[1]])) {
+ return $cache[$matches[1]];
+ }
+
+ $result = '';
+ switch ($matches[1]) {
+ case 'request':
+ $result = Psr7\str($request);
+ break;
+ case 'response':
+ $result = $response ? Psr7\str($response) : '';
+ break;
+ case 'req_headers':
+ $result = trim($request->getMethod()
+ . ' ' . $request->getRequestTarget())
+ . ' HTTP/' . $request->getProtocolVersion() . "\r\n"
+ . $this->headers($request);
+ break;
+ case 'res_headers':
+ $result = $response ?
+ sprintf(
+ 'HTTP/%s %d %s',
+ $response->getProtocolVersion(),
+ $response->getStatusCode(),
+ $response->getReasonPhrase()
+ ) . "\r\n" . $this->headers($response)
+ : 'NULL';
+ break;
+ case 'req_body':
+ $result = $request->getBody();
+ break;
+ case 'res_body':
+ $result = $response ? $response->getBody() : 'NULL';
+ break;
+ case 'ts':
+ case 'date_iso_8601':
+ $result = gmdate('c');
+ break;
+ case 'date_common_log':
+ $result = date('d/M/Y:H:i:s O');
+ break;
+ case 'method':
+ $result = $request->getMethod();
+ break;
+ case 'version':
+ $result = $request->getProtocolVersion();
+ break;
+ case 'uri':
+ case 'url':
+ $result = $request->getUri();
+ break;
+ case 'target':
+ $result = $request->getRequestTarget();
+ break;
+ case 'req_version':
+ $result = $request->getProtocolVersion();
+ break;
+ case 'res_version':
+ $result = $response
+ ? $response->getProtocolVersion()
+ : 'NULL';
+ break;
+ case 'host':
+ $result = $request->getHeaderLine('Host');
+ break;
+ case 'hostname':
+ $result = gethostname();
+ break;
+ case 'code':
+ $result = $response ? $response->getStatusCode() : 'NULL';
+ break;
+ case 'phrase':
+ $result = $response ? $response->getReasonPhrase() : 'NULL';
+ break;
+ case 'error':
+ $result = $error ? $error->getMessage() : 'NULL';
+ break;
+ default:
+ // handle prefixed dynamic headers
+ if (strpos($matches[1], 'req_header_') === 0) {
+ $result = $request->getHeaderLine(substr($matches[1], 11));
+ } elseif (strpos($matches[1], 'res_header_') === 0) {
+ $result = $response
+ ? $response->getHeaderLine(substr($matches[1], 11))
+ : 'NULL';
+ }
+ }
+
+ $cache[$matches[1]] = $result;
+ return $result;
+ },
+ $this->template
+ );
+ }
+
+ private function headers(MessageInterface $message)
+ {
+ $result = '';
+ foreach ($message->getHeaders() as $name => $values) {
+ $result .= $name . ': ' . implode(', ', $values) . "\r\n";
+ }
+
+ return trim($result);
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Middleware.php b/instafeed/vendor/guzzlehttp/guzzle/src/Middleware.php
new file mode 100755
index 0000000..bffc197
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Middleware.php
@@ -0,0 +1,254 @@
+withCookieHeader($request);
+ return $handler($request, $options)
+ ->then(
+ function ($response) use ($cookieJar, $request) {
+ $cookieJar->extractCookies($request, $response);
+ return $response;
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that throws exceptions for 4xx or 5xx responses when the
+ * "http_error" request option is set to true.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function httpErrors()
+ {
+ return function (callable $handler) {
+ return function ($request, array $options) use ($handler) {
+ if (empty($options['http_errors'])) {
+ return $handler($request, $options);
+ }
+ return $handler($request, $options)->then(
+ function (ResponseInterface $response) use ($request) {
+ $code = $response->getStatusCode();
+ if ($code < 400) {
+ return $response;
+ }
+ throw RequestException::create($request, $response);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that pushes history data to an ArrayAccess container.
+ *
+ * @param array|\ArrayAccess $container Container to hold the history (by reference).
+ *
+ * @return callable Returns a function that accepts the next handler.
+ * @throws \InvalidArgumentException if container is not an array or ArrayAccess.
+ */
+ public static function history(&$container)
+ {
+ if (!is_array($container) && !$container instanceof \ArrayAccess) {
+ throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
+ }
+
+ return function (callable $handler) use (&$container) {
+ return function ($request, array $options) use ($handler, &$container) {
+ return $handler($request, $options)->then(
+ function ($value) use ($request, &$container, $options) {
+ $container[] = [
+ 'request' => $request,
+ 'response' => $value,
+ 'error' => null,
+ 'options' => $options
+ ];
+ return $value;
+ },
+ function ($reason) use ($request, &$container, $options) {
+ $container[] = [
+ 'request' => $request,
+ 'response' => null,
+ 'error' => $reason,
+ 'options' => $options
+ ];
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that invokes a callback before and after sending a request.
+ *
+ * The provided listener cannot modify or alter the response. It simply
+ * "taps" into the chain to be notified before returning the promise. The
+ * before listener accepts a request and options array, and the after
+ * listener accepts a request, options array, and response promise.
+ *
+ * @param callable $before Function to invoke before forwarding the request.
+ * @param callable $after Function invoked after forwarding.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function tap(callable $before = null, callable $after = null)
+ {
+ return function (callable $handler) use ($before, $after) {
+ return function ($request, array $options) use ($handler, $before, $after) {
+ if ($before) {
+ $before($request, $options);
+ }
+ $response = $handler($request, $options);
+ if ($after) {
+ $after($request, $options, $response);
+ }
+ return $response;
+ };
+ };
+ }
+
+ /**
+ * Middleware that handles request redirects.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function redirect()
+ {
+ return function (callable $handler) {
+ return new RedirectMiddleware($handler);
+ };
+ }
+
+ /**
+ * Middleware that retries requests based on the boolean result of
+ * invoking the provided "decider" function.
+ *
+ * If no delay function is provided, a simple implementation of exponential
+ * backoff will be utilized.
+ *
+ * @param callable $decider Function that accepts the number of retries,
+ * a request, [response], and [exception] and
+ * returns true if the request is to be retried.
+ * @param callable $delay Function that accepts the number of retries and
+ * returns the number of milliseconds to delay.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function retry(callable $decider, callable $delay = null)
+ {
+ return function (callable $handler) use ($decider, $delay) {
+ return new RetryMiddleware($decider, $handler, $delay);
+ };
+ }
+
+ /**
+ * Middleware that logs requests, responses, and errors using a message
+ * formatter.
+ *
+ * @param LoggerInterface $logger Logs messages.
+ * @param MessageFormatter $formatter Formatter used to create message strings.
+ * @param string $logLevel Level at which to log requests.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = 'info' /* \Psr\Log\LogLevel::INFO */)
+ {
+ return function (callable $handler) use ($logger, $formatter, $logLevel) {
+ return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) {
+ return $handler($request, $options)->then(
+ function ($response) use ($logger, $request, $formatter, $logLevel) {
+ $message = $formatter->format($request, $response);
+ $logger->log($logLevel, $message);
+ return $response;
+ },
+ function ($reason) use ($logger, $request, $formatter) {
+ $response = $reason instanceof RequestException
+ ? $reason->getResponse()
+ : null;
+ $message = $formatter->format($request, $response, $reason);
+ $logger->notice($message);
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * This middleware adds a default content-type if possible, a default
+ * content-length or transfer-encoding header, and the expect header.
+ *
+ * @return callable
+ */
+ public static function prepareBody()
+ {
+ return function (callable $handler) {
+ return new PrepareBodyMiddleware($handler);
+ };
+ }
+
+ /**
+ * Middleware that applies a map function to the request before passing to
+ * the next handler.
+ *
+ * @param callable $fn Function that accepts a RequestInterface and returns
+ * a RequestInterface.
+ * @return callable
+ */
+ public static function mapRequest(callable $fn)
+ {
+ return function (callable $handler) use ($fn) {
+ return function ($request, array $options) use ($handler, $fn) {
+ return $handler($fn($request), $options);
+ };
+ };
+ }
+
+ /**
+ * Middleware that applies a map function to the resolved promise's
+ * response.
+ *
+ * @param callable $fn Function that accepts a ResponseInterface and
+ * returns a ResponseInterface.
+ * @return callable
+ */
+ public static function mapResponse(callable $fn)
+ {
+ return function (callable $handler) use ($fn) {
+ return function ($request, array $options) use ($handler, $fn) {
+ return $handler($request, $options)->then($fn);
+ };
+ };
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/Pool.php b/instafeed/vendor/guzzlehttp/guzzle/src/Pool.php
new file mode 100755
index 0000000..05c854a
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/Pool.php
@@ -0,0 +1,123 @@
+ $rfn) {
+ if ($rfn instanceof RequestInterface) {
+ yield $key => $client->sendAsync($rfn, $opts);
+ } elseif (is_callable($rfn)) {
+ yield $key => $rfn($opts);
+ } else {
+ throw new \InvalidArgumentException('Each value yielded by '
+ . 'the iterator must be a Psr7\Http\Message\RequestInterface '
+ . 'or a callable that returns a promise that fulfills '
+ . 'with a Psr7\Message\Http\ResponseInterface object.');
+ }
+ }
+ };
+
+ $this->each = new EachPromise($requests(), $config);
+ }
+
+ public function promise()
+ {
+ return $this->each->promise();
+ }
+
+ /**
+ * Sends multiple requests concurrently and returns an array of responses
+ * and exceptions that uses the same ordering as the provided requests.
+ *
+ * IMPORTANT: This method keeps every request and response in memory, and
+ * as such, is NOT recommended when sending a large number or an
+ * indeterminate number of requests concurrently.
+ *
+ * @param ClientInterface $client Client used to send the requests
+ * @param array|\Iterator $requests Requests to send concurrently.
+ * @param array $options Passes through the options available in
+ * {@see GuzzleHttp\Pool::__construct}
+ *
+ * @return array Returns an array containing the response or an exception
+ * in the same order that the requests were sent.
+ * @throws \InvalidArgumentException if the event format is incorrect.
+ */
+ public static function batch(
+ ClientInterface $client,
+ $requests,
+ array $options = []
+ ) {
+ $res = [];
+ self::cmpCallback($options, 'fulfilled', $res);
+ self::cmpCallback($options, 'rejected', $res);
+ $pool = new static($client, $requests, $options);
+ $pool->promise()->wait();
+ ksort($res);
+
+ return $res;
+ }
+
+ private static function cmpCallback(array &$options, $name, array &$results)
+ {
+ if (!isset($options[$name])) {
+ $options[$name] = function ($v, $k) use (&$results) {
+ $results[$k] = $v;
+ };
+ } else {
+ $currentFn = $options[$name];
+ $options[$name] = function ($v, $k) use (&$results, $currentFn) {
+ $currentFn($v, $k);
+ $results[$k] = $v;
+ };
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/instafeed/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php
new file mode 100755
index 0000000..2eb95f9
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php
@@ -0,0 +1,106 @@
+nextHandler = $nextHandler;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $fn = $this->nextHandler;
+
+ // Don't do anything if the request has no body.
+ if ($request->getBody()->getSize() === 0) {
+ return $fn($request, $options);
+ }
+
+ $modify = [];
+
+ // Add a default content-type if possible.
+ if (!$request->hasHeader('Content-Type')) {
+ if ($uri = $request->getBody()->getMetadata('uri')) {
+ if ($type = Psr7\mimetype_from_filename($uri)) {
+ $modify['set_headers']['Content-Type'] = $type;
+ }
+ }
+ }
+
+ // Add a default content-length or transfer-encoding header.
+ if (!$request->hasHeader('Content-Length')
+ && !$request->hasHeader('Transfer-Encoding')
+ ) {
+ $size = $request->getBody()->getSize();
+ if ($size !== null) {
+ $modify['set_headers']['Content-Length'] = $size;
+ } else {
+ $modify['set_headers']['Transfer-Encoding'] = 'chunked';
+ }
+ }
+
+ // Add the expect header if needed.
+ $this->addExpectHeader($request, $options, $modify);
+
+ return $fn(Psr7\modify_request($request, $modify), $options);
+ }
+
+ private function addExpectHeader(
+ RequestInterface $request,
+ array $options,
+ array &$modify
+ ) {
+ // Determine if the Expect header should be used
+ if ($request->hasHeader('Expect')) {
+ return;
+ }
+
+ $expect = isset($options['expect']) ? $options['expect'] : null;
+
+ // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
+ if ($expect === false || $request->getProtocolVersion() < 1.1) {
+ return;
+ }
+
+ // The expect header is unconditionally enabled
+ if ($expect === true) {
+ $modify['set_headers']['Expect'] = '100-Continue';
+ return;
+ }
+
+ // By default, send the expect header when the payload is > 1mb
+ if ($expect === null) {
+ $expect = 1048576;
+ }
+
+ // Always add if the body cannot be rewound, the size cannot be
+ // determined, or the size is greater than the cutoff threshold
+ $body = $request->getBody();
+ $size = $body->getSize();
+
+ if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
+ $modify['set_headers']['Expect'] = '100-Continue';
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/instafeed/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php
new file mode 100755
index 0000000..bff4e4e
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php
@@ -0,0 +1,237 @@
+ 5,
+ 'protocols' => ['http', 'https'],
+ 'strict' => false,
+ 'referer' => false,
+ 'track_redirects' => false,
+ ];
+
+ /** @var callable */
+ private $nextHandler;
+
+ /**
+ * @param callable $nextHandler Next handler to invoke.
+ */
+ public function __construct(callable $nextHandler)
+ {
+ $this->nextHandler = $nextHandler;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $fn = $this->nextHandler;
+
+ if (empty($options['allow_redirects'])) {
+ return $fn($request, $options);
+ }
+
+ if ($options['allow_redirects'] === true) {
+ $options['allow_redirects'] = self::$defaultSettings;
+ } elseif (!is_array($options['allow_redirects'])) {
+ throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
+ } else {
+ // Merge the default settings with the provided settings
+ $options['allow_redirects'] += self::$defaultSettings;
+ }
+
+ if (empty($options['allow_redirects']['max'])) {
+ return $fn($request, $options);
+ }
+
+ return $fn($request, $options)
+ ->then(function (ResponseInterface $response) use ($request, $options) {
+ return $this->checkRedirect($request, $options, $response);
+ });
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface|PromiseInterface $response
+ *
+ * @return ResponseInterface|PromiseInterface
+ */
+ public function checkRedirect(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ if (substr($response->getStatusCode(), 0, 1) != '3'
+ || !$response->hasHeader('Location')
+ ) {
+ return $response;
+ }
+
+ $this->guardMax($request, $options);
+ $nextRequest = $this->modifyRequest($request, $options, $response);
+
+ if (isset($options['allow_redirects']['on_redirect'])) {
+ call_user_func(
+ $options['allow_redirects']['on_redirect'],
+ $request,
+ $response,
+ $nextRequest->getUri()
+ );
+ }
+
+ /** @var PromiseInterface|ResponseInterface $promise */
+ $promise = $this($nextRequest, $options);
+
+ // Add headers to be able to track history of redirects.
+ if (!empty($options['allow_redirects']['track_redirects'])) {
+ return $this->withTracking(
+ $promise,
+ (string) $nextRequest->getUri(),
+ $response->getStatusCode()
+ );
+ }
+
+ return $promise;
+ }
+
+ private function withTracking(PromiseInterface $promise, $uri, $statusCode)
+ {
+ return $promise->then(
+ function (ResponseInterface $response) use ($uri, $statusCode) {
+ // Note that we are pushing to the front of the list as this
+ // would be an earlier response than what is currently present
+ // in the history header.
+ $historyHeader = $response->getHeader(self::HISTORY_HEADER);
+ $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
+ array_unshift($historyHeader, $uri);
+ array_unshift($statusHeader, $statusCode);
+ return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
+ ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
+ }
+ );
+ }
+
+ private function guardMax(RequestInterface $request, array &$options)
+ {
+ $current = isset($options['__redirect_count'])
+ ? $options['__redirect_count']
+ : 0;
+ $options['__redirect_count'] = $current + 1;
+ $max = $options['allow_redirects']['max'];
+
+ if ($options['__redirect_count'] > $max) {
+ throw new TooManyRedirectsException(
+ "Will not follow more than {$max} redirects",
+ $request
+ );
+ }
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface $response
+ *
+ * @return RequestInterface
+ */
+ public function modifyRequest(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ // Request modifications to apply.
+ $modify = [];
+ $protocols = $options['allow_redirects']['protocols'];
+
+ // Use a GET request if this is an entity enclosing request and we are
+ // not forcing RFC compliance, but rather emulating what all browsers
+ // would do.
+ $statusCode = $response->getStatusCode();
+ if ($statusCode == 303 ||
+ ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict'])
+ ) {
+ $modify['method'] = 'GET';
+ $modify['body'] = '';
+ }
+
+ $modify['uri'] = $this->redirectUri($request, $response, $protocols);
+ Psr7\rewind_body($request);
+
+ // Add the Referer header if it is told to do so and only
+ // add the header if we are not redirecting from https to http.
+ if ($options['allow_redirects']['referer']
+ && $modify['uri']->getScheme() === $request->getUri()->getScheme()
+ ) {
+ $uri = $request->getUri()->withUserInfo('');
+ $modify['set_headers']['Referer'] = (string) $uri;
+ } else {
+ $modify['remove_headers'][] = 'Referer';
+ }
+
+ // Remove Authorization header if host is different.
+ if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
+ $modify['remove_headers'][] = 'Authorization';
+ }
+
+ return Psr7\modify_request($request, $modify);
+ }
+
+ /**
+ * Set the appropriate URL on the request based on the location header
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param array $protocols
+ *
+ * @return UriInterface
+ */
+ private function redirectUri(
+ RequestInterface $request,
+ ResponseInterface $response,
+ array $protocols
+ ) {
+ $location = Psr7\UriResolver::resolve(
+ $request->getUri(),
+ new Psr7\Uri($response->getHeaderLine('Location'))
+ );
+
+ // Ensure that the redirect URI is allowed based on the protocols.
+ if (!in_array($location->getScheme(), $protocols)) {
+ throw new BadResponseException(
+ sprintf(
+ 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
+ $location,
+ implode(', ', $protocols)
+ ),
+ $request,
+ $response
+ );
+ }
+
+ return $location;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/instafeed/vendor/guzzlehttp/guzzle/src/RequestOptions.php
new file mode 100755
index 0000000..5c0fd19
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/RequestOptions.php
@@ -0,0 +1,255 @@
+decider = $decider;
+ $this->nextHandler = $nextHandler;
+ $this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
+ }
+
+ /**
+ * Default exponential backoff delay function.
+ *
+ * @param int $retries
+ *
+ * @return int
+ */
+ public static function exponentialDelay($retries)
+ {
+ return (int) pow(2, $retries - 1);
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (!isset($options['retries'])) {
+ $options['retries'] = 0;
+ }
+
+ $fn = $this->nextHandler;
+ return $fn($request, $options)
+ ->then(
+ $this->onFulfilled($request, $options),
+ $this->onRejected($request, $options)
+ );
+ }
+
+ private function onFulfilled(RequestInterface $req, array $options)
+ {
+ return function ($value) use ($req, $options) {
+ if (!call_user_func(
+ $this->decider,
+ $options['retries'],
+ $req,
+ $value,
+ null
+ )) {
+ return $value;
+ }
+ return $this->doRetry($req, $options, $value);
+ };
+ }
+
+ private function onRejected(RequestInterface $req, array $options)
+ {
+ return function ($reason) use ($req, $options) {
+ if (!call_user_func(
+ $this->decider,
+ $options['retries'],
+ $req,
+ null,
+ $reason
+ )) {
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ return $this->doRetry($req, $options);
+ };
+ }
+
+ private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null)
+ {
+ $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response);
+
+ return $this($request, $options);
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/TransferStats.php b/instafeed/vendor/guzzlehttp/guzzle/src/TransferStats.php
new file mode 100755
index 0000000..23a22a3
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/TransferStats.php
@@ -0,0 +1,126 @@
+request = $request;
+ $this->response = $response;
+ $this->transferTime = $transferTime;
+ $this->handlerErrorData = $handlerErrorData;
+ $this->handlerStats = $handlerStats;
+ }
+
+ /**
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Returns the response that was received (if any).
+ *
+ * @return ResponseInterface|null
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Returns true if a response was received.
+ *
+ * @return bool
+ */
+ public function hasResponse()
+ {
+ return $this->response !== null;
+ }
+
+ /**
+ * Gets handler specific error data.
+ *
+ * This might be an exception, a integer representing an error code, or
+ * anything else. Relying on this value assumes that you know what handler
+ * you are using.
+ *
+ * @return mixed
+ */
+ public function getHandlerErrorData()
+ {
+ return $this->handlerErrorData;
+ }
+
+ /**
+ * Get the effective URI the request was sent to.
+ *
+ * @return UriInterface
+ */
+ public function getEffectiveUri()
+ {
+ return $this->request->getUri();
+ }
+
+ /**
+ * Get the estimated time the request was being transferred by the handler.
+ *
+ * @return float Time in seconds.
+ */
+ public function getTransferTime()
+ {
+ return $this->transferTime;
+ }
+
+ /**
+ * Gets an array of all of the handler specific transfer data.
+ *
+ * @return array
+ */
+ public function getHandlerStats()
+ {
+ return $this->handlerStats;
+ }
+
+ /**
+ * Get a specific handler statistic from the handler by name.
+ *
+ * @param string $stat Handler specific transfer stat to retrieve.
+ *
+ * @return mixed|null
+ */
+ public function getHandlerStat($stat)
+ {
+ return isset($this->handlerStats[$stat])
+ ? $this->handlerStats[$stat]
+ : null;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/UriTemplate.php b/instafeed/vendor/guzzlehttp/guzzle/src/UriTemplate.php
new file mode 100755
index 0000000..96dcfd0
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/UriTemplate.php
@@ -0,0 +1,237 @@
+ ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
+ '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
+ '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
+ ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
+ '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
+ '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true]
+ ];
+
+ /** @var array Delimiters */
+ private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$',
+ '&', '\'', '(', ')', '*', '+', ',', ';', '='];
+
+ /** @var array Percent encoded delimiters */
+ private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D',
+ '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
+ '%3B', '%3D'];
+
+ public function expand($template, array $variables)
+ {
+ if (false === strpos($template, '{')) {
+ return $template;
+ }
+
+ $this->template = $template;
+ $this->variables = $variables;
+
+ return preg_replace_callback(
+ '/\{([^\}]+)\}/',
+ [$this, 'expandMatch'],
+ $this->template
+ );
+ }
+
+ /**
+ * Parse an expression into parts
+ *
+ * @param string $expression Expression to parse
+ *
+ * @return array Returns an associative array of parts
+ */
+ private function parseExpression($expression)
+ {
+ $result = [];
+
+ if (isset(self::$operatorHash[$expression[0]])) {
+ $result['operator'] = $expression[0];
+ $expression = substr($expression, 1);
+ } else {
+ $result['operator'] = '';
+ }
+
+ foreach (explode(',', $expression) as $value) {
+ $value = trim($value);
+ $varspec = [];
+ if ($colonPos = strpos($value, ':')) {
+ $varspec['value'] = substr($value, 0, $colonPos);
+ $varspec['modifier'] = ':';
+ $varspec['position'] = (int) substr($value, $colonPos + 1);
+ } elseif (substr($value, -1) === '*') {
+ $varspec['modifier'] = '*';
+ $varspec['value'] = substr($value, 0, -1);
+ } else {
+ $varspec['value'] = (string) $value;
+ $varspec['modifier'] = '';
+ }
+ $result['values'][] = $varspec;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Process an expansion
+ *
+ * @param array $matches Matches met in the preg_replace_callback
+ *
+ * @return string Returns the replacement string
+ */
+ private function expandMatch(array $matches)
+ {
+ static $rfc1738to3986 = ['+' => '%20', '%7e' => '~'];
+
+ $replacements = [];
+ $parsed = self::parseExpression($matches[1]);
+ $prefix = self::$operatorHash[$parsed['operator']]['prefix'];
+ $joiner = self::$operatorHash[$parsed['operator']]['joiner'];
+ $useQuery = self::$operatorHash[$parsed['operator']]['query'];
+
+ foreach ($parsed['values'] as $value) {
+ if (!isset($this->variables[$value['value']])) {
+ continue;
+ }
+
+ $variable = $this->variables[$value['value']];
+ $actuallyUseQuery = $useQuery;
+ $expanded = '';
+
+ if (is_array($variable)) {
+ $isAssoc = $this->isAssoc($variable);
+ $kvp = [];
+ foreach ($variable as $key => $var) {
+ if ($isAssoc) {
+ $key = rawurlencode($key);
+ $isNestedArray = is_array($var);
+ } else {
+ $isNestedArray = false;
+ }
+
+ if (!$isNestedArray) {
+ $var = rawurlencode($var);
+ if ($parsed['operator'] === '+' ||
+ $parsed['operator'] === '#'
+ ) {
+ $var = $this->decodeReserved($var);
+ }
+ }
+
+ if ($value['modifier'] === '*') {
+ if ($isAssoc) {
+ if ($isNestedArray) {
+ // Nested arrays must allow for deeply nested
+ // structures.
+ $var = strtr(
+ http_build_query([$key => $var]),
+ $rfc1738to3986
+ );
+ } else {
+ $var = $key . '=' . $var;
+ }
+ } elseif ($key > 0 && $actuallyUseQuery) {
+ $var = $value['value'] . '=' . $var;
+ }
+ }
+
+ $kvp[$key] = $var;
+ }
+
+ if (empty($variable)) {
+ $actuallyUseQuery = false;
+ } elseif ($value['modifier'] === '*') {
+ $expanded = implode($joiner, $kvp);
+ if ($isAssoc) {
+ // Don't prepend the value name when using the explode
+ // modifier with an associative array.
+ $actuallyUseQuery = false;
+ }
+ } else {
+ if ($isAssoc) {
+ // When an associative array is encountered and the
+ // explode modifier is not set, then the result must be
+ // a comma separated list of keys followed by their
+ // respective values.
+ foreach ($kvp as $k => &$v) {
+ $v = $k . ',' . $v;
+ }
+ }
+ $expanded = implode(',', $kvp);
+ }
+ } else {
+ if ($value['modifier'] === ':') {
+ $variable = substr($variable, 0, $value['position']);
+ }
+ $expanded = rawurlencode($variable);
+ if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
+ $expanded = $this->decodeReserved($expanded);
+ }
+ }
+
+ if ($actuallyUseQuery) {
+ if (!$expanded && $joiner !== '&') {
+ $expanded = $value['value'];
+ } else {
+ $expanded = $value['value'] . '=' . $expanded;
+ }
+ }
+
+ $replacements[] = $expanded;
+ }
+
+ $ret = implode($joiner, $replacements);
+ if ($ret && $prefix) {
+ return $prefix . $ret;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Determines if an array is associative.
+ *
+ * This makes the assumption that input arrays are sequences or hashes.
+ * This assumption is a tradeoff for accuracy in favor of speed, but it
+ * should work in almost every case where input is supplied for a URI
+ * template.
+ *
+ * @param array $array Array to check
+ *
+ * @return bool
+ */
+ private function isAssoc(array $array)
+ {
+ return $array && array_keys($array)[0] !== 0;
+ }
+
+ /**
+ * Removes percent encoding on reserved characters (used with + and #
+ * modifiers).
+ *
+ * @param string $string String to fix
+ *
+ * @return string
+ */
+ private function decodeReserved($string)
+ {
+ return str_replace(self::$delimsPct, self::$delims, $string);
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/functions.php b/instafeed/vendor/guzzlehttp/guzzle/src/functions.php
new file mode 100755
index 0000000..51d736d
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/functions.php
@@ -0,0 +1,346 @@
+expand($template, $variables);
+}
+
+/**
+ * Debug function used to describe the provided value type and class.
+ *
+ * @param mixed $input
+ *
+ * @return string Returns a string containing the type of the variable and
+ * if a class is provided, the class name.
+ */
+function describe_type($input)
+{
+ switch (gettype($input)) {
+ case 'object':
+ return 'object(' . get_class($input) . ')';
+ case 'array':
+ return 'array(' . count($input) . ')';
+ default:
+ ob_start();
+ var_dump($input);
+ // normalize float vs double
+ return str_replace('double(', 'float(', rtrim(ob_get_clean()));
+ }
+}
+
+/**
+ * Parses an array of header lines into an associative array of headers.
+ *
+ * @param array $lines Header lines array of strings in the following
+ * format: "Name: Value"
+ * @return array
+ */
+function headers_from_lines($lines)
+{
+ $headers = [];
+
+ foreach ($lines as $line) {
+ $parts = explode(':', $line, 2);
+ $headers[trim($parts[0])][] = isset($parts[1])
+ ? trim($parts[1])
+ : null;
+ }
+
+ return $headers;
+}
+
+/**
+ * Returns a debug stream based on the provided variable.
+ *
+ * @param mixed $value Optional value
+ *
+ * @return resource
+ */
+function debug_resource($value = null)
+{
+ if (is_resource($value)) {
+ return $value;
+ } elseif (defined('STDOUT')) {
+ return STDOUT;
+ }
+
+ return fopen('php://output', 'w');
+}
+
+/**
+ * Chooses and creates a default handler to use based on the environment.
+ *
+ * The returned handler is not wrapped by any default middlewares.
+ *
+ * @throws \RuntimeException if no viable Handler is available.
+ * @return callable Returns the best handler for the given system.
+ */
+function choose_handler()
+{
+ $handler = null;
+ if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
+ $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
+ } elseif (function_exists('curl_exec')) {
+ $handler = new CurlHandler();
+ } elseif (function_exists('curl_multi_exec')) {
+ $handler = new CurlMultiHandler();
+ }
+
+ if (ini_get('allow_url_fopen')) {
+ $handler = $handler
+ ? Proxy::wrapStreaming($handler, new StreamHandler())
+ : new StreamHandler();
+ } elseif (!$handler) {
+ throw new \RuntimeException('GuzzleHttp requires cURL, the '
+ . 'allow_url_fopen ini setting, or a custom HTTP handler.');
+ }
+
+ return $handler;
+}
+
+/**
+ * Get the default User-Agent string to use with Guzzle
+ *
+ * @return string
+ */
+function default_user_agent()
+{
+ static $defaultAgent = '';
+
+ if (!$defaultAgent) {
+ $defaultAgent = 'GuzzleHttp/' . Client::VERSION;
+ if (extension_loaded('curl') && function_exists('curl_version')) {
+ $defaultAgent .= ' curl/' . \curl_version()['version'];
+ }
+ $defaultAgent .= ' PHP/' . PHP_VERSION;
+ }
+
+ return $defaultAgent;
+}
+
+/**
+ * Returns the default cacert bundle for the current system.
+ *
+ * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
+ * If those settings are not configured, then the common locations for
+ * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
+ * and Windows are checked. If any of these file locations are found on
+ * disk, they will be utilized.
+ *
+ * Note: the result of this function is cached for subsequent calls.
+ *
+ * @return string
+ * @throws \RuntimeException if no bundle can be found.
+ */
+function default_ca_bundle()
+{
+ static $cached = null;
+ static $cafiles = [
+ // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
+ '/etc/pki/tls/certs/ca-bundle.crt',
+ // Ubuntu, Debian (provided by the ca-certificates package)
+ '/etc/ssl/certs/ca-certificates.crt',
+ // FreeBSD (provided by the ca_root_nss package)
+ '/usr/local/share/certs/ca-root-nss.crt',
+ // SLES 12 (provided by the ca-certificates package)
+ '/var/lib/ca-certificates/ca-bundle.pem',
+ // OS X provided by homebrew (using the default path)
+ '/usr/local/etc/openssl/cert.pem',
+ // Google app engine
+ '/etc/ca-certificates.crt',
+ // Windows?
+ 'C:\\windows\\system32\\curl-ca-bundle.crt',
+ 'C:\\windows\\curl-ca-bundle.crt',
+ ];
+
+ if ($cached) {
+ return $cached;
+ }
+
+ if ($ca = ini_get('openssl.cafile')) {
+ return $cached = $ca;
+ }
+
+ if ($ca = ini_get('curl.cainfo')) {
+ return $cached = $ca;
+ }
+
+ foreach ($cafiles as $filename) {
+ if (file_exists($filename)) {
+ return $cached = $filename;
+ }
+ }
+
+ throw new \RuntimeException(
+ <<< EOT
+No system CA bundle could be found in any of the the common system locations.
+PHP versions earlier than 5.6 are not properly configured to use the system's
+CA bundle by default. In order to verify peer certificates, you will need to
+supply the path on disk to a certificate bundle to the 'verify' request
+option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
+need a specific certificate bundle, then Mozilla provides a commonly used CA
+bundle which can be downloaded here (provided by the maintainer of cURL):
+https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
+you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
+ini setting to point to the path to the file, allowing you to omit the 'verify'
+request option. See http://curl.haxx.se/docs/sslcerts.html for more
+information.
+EOT
+ );
+}
+
+/**
+ * Creates an associative array of lowercase header names to the actual
+ * header casing.
+ *
+ * @param array $headers
+ *
+ * @return array
+ */
+function normalize_header_keys(array $headers)
+{
+ $result = [];
+ foreach (array_keys($headers) as $key) {
+ $result[strtolower($key)] = $key;
+ }
+
+ return $result;
+}
+
+/**
+ * Returns true if the provided host matches any of the no proxy areas.
+ *
+ * This method will strip a port from the host if it is present. Each pattern
+ * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
+ * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
+ * "baz.foo.com", but ".foo.com" != "foo.com").
+ *
+ * Areas are matched in the following cases:
+ * 1. "*" (without quotes) always matches any hosts.
+ * 2. An exact match.
+ * 3. The area starts with "." and the area is the last part of the host. e.g.
+ * '.mit.edu' will match any host that ends with '.mit.edu'.
+ *
+ * @param string $host Host to check against the patterns.
+ * @param array $noProxyArray An array of host patterns.
+ *
+ * @return bool
+ */
+function is_host_in_noproxy($host, array $noProxyArray)
+{
+ if (strlen($host) === 0) {
+ throw new \InvalidArgumentException('Empty host provided');
+ }
+
+ // Strip port if present.
+ if (strpos($host, ':')) {
+ $host = explode($host, ':', 2)[0];
+ }
+
+ foreach ($noProxyArray as $area) {
+ // Always match on wildcards.
+ if ($area === '*') {
+ return true;
+ } elseif (empty($area)) {
+ // Don't match on empty values.
+ continue;
+ } elseif ($area === $host) {
+ // Exact matches.
+ return true;
+ } else {
+ // Special match if the area when prefixed with ".". Remove any
+ // existing leading "." and add a new leading ".".
+ $area = '.' . ltrim($area, '.');
+ if (substr($host, -(strlen($area))) === $area) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Wrapper for json_decode that throws when an error occurs.
+ *
+ * @param string $json JSON data to parse
+ * @param bool $assoc When true, returned objects will be converted
+ * into associative arrays.
+ * @param int $depth User specified recursion depth.
+ * @param int $options Bitmask of JSON decode options.
+ *
+ * @return mixed
+ * @throws Exception\InvalidArgumentException if the JSON cannot be decoded.
+ * @link http://www.php.net/manual/en/function.json-decode.php
+ */
+function json_decode($json, $assoc = false, $depth = 512, $options = 0)
+{
+ $data = \json_decode($json, $assoc, $depth, $options);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new Exception\InvalidArgumentException(
+ 'json_decode error: ' . json_last_error_msg()
+ );
+ }
+
+ return $data;
+}
+
+/**
+ * Wrapper for JSON encoding that throws when an error occurs.
+ *
+ * @param mixed $value The value being encoded
+ * @param int $options JSON encode option bitmask
+ * @param int $depth Set the maximum depth. Must be greater than zero.
+ *
+ * @return string
+ * @throws Exception\InvalidArgumentException if the JSON cannot be encoded.
+ * @link http://www.php.net/manual/en/function.json-encode.php
+ */
+function json_encode($value, $options = 0, $depth = 512)
+{
+ $json = \json_encode($value, $options, $depth);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new Exception\InvalidArgumentException(
+ 'json_encode error: ' . json_last_error_msg()
+ );
+ }
+
+ return $json;
+}
+
+/**
+ * Wrapper for the hrtime() or microtime() functions
+ * (depending on the PHP version, one of the two is used)
+ *
+ * @return float|mixed UNIX timestamp
+ * @internal
+ */
+function _current_time()
+{
+ return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true);
+}
diff --git a/instafeed/vendor/guzzlehttp/guzzle/src/functions_include.php b/instafeed/vendor/guzzlehttp/guzzle/src/functions_include.php
new file mode 100755
index 0000000..a93393a
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/guzzle/src/functions_include.php
@@ -0,0 +1,6 @@
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/guzzlehttp/promises/Makefile b/instafeed/vendor/guzzlehttp/promises/Makefile
new file mode 100755
index 0000000..8d5b3ef
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/Makefile
@@ -0,0 +1,13 @@
+all: clean test
+
+test:
+ vendor/bin/phpunit
+
+coverage:
+ vendor/bin/phpunit --coverage-html=artifacts/coverage
+
+view-coverage:
+ open artifacts/coverage/index.html
+
+clean:
+ rm -rf artifacts/*
diff --git a/instafeed/vendor/guzzlehttp/promises/README.md b/instafeed/vendor/guzzlehttp/promises/README.md
new file mode 100755
index 0000000..7b607e2
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/README.md
@@ -0,0 +1,504 @@
+# Guzzle Promises
+
+[Promises/A+](https://promisesaplus.com/) implementation that handles promise
+chaining and resolution iteratively, allowing for "infinite" promise chaining
+while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/)
+for a general introduction to promises.
+
+- [Features](#features)
+- [Quick start](#quick-start)
+- [Synchronous wait](#synchronous-wait)
+- [Cancellation](#cancellation)
+- [API](#api)
+ - [Promise](#promise)
+ - [FulfilledPromise](#fulfilledpromise)
+ - [RejectedPromise](#rejectedpromise)
+- [Promise interop](#promise-interop)
+- [Implementation notes](#implementation-notes)
+
+
+# Features
+
+- [Promises/A+](https://promisesaplus.com/) implementation.
+- Promise resolution and chaining is handled iteratively, allowing for
+ "infinite" promise chaining.
+- Promises have a synchronous `wait` method.
+- Promises can be cancelled.
+- Works with any object that has a `then` function.
+- C# style async/await coroutine promises using
+ `GuzzleHttp\Promise\coroutine()`.
+
+
+# Quick start
+
+A *promise* represents the eventual result of an asynchronous operation. The
+primary way of interacting with a promise is through its `then` method, which
+registers callbacks to receive either a promise's eventual value or the reason
+why the promise cannot be fulfilled.
+
+
+## Callbacks
+
+Callbacks are registered with the `then` method by providing an optional
+`$onFulfilled` followed by an optional `$onRejected` function.
+
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(
+ // $onFulfilled
+ function ($value) {
+ echo 'The promise was fulfilled.';
+ },
+ // $onRejected
+ function ($reason) {
+ echo 'The promise was rejected.';
+ }
+);
+```
+
+*Resolving* a promise means that you either fulfill a promise with a *value* or
+reject a promise with a *reason*. Resolving a promises triggers callbacks
+registered with the promises's `then` method. These callbacks are triggered
+only once and in the order in which they were added.
+
+
+## Resolving a promise
+
+Promises are fulfilled using the `resolve($value)` method. Resolving a promise
+with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
+all of the onFulfilled callbacks (resolving a promise with a rejected promise
+will reject the promise and trigger the `$onRejected` callbacks).
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise
+ ->then(function ($value) {
+ // Return a value and don't break the chain
+ return "Hello, " . $value;
+ })
+ // This then is executed after the first then and receives the value
+ // returned from the first then.
+ ->then(function ($value) {
+ echo $value;
+ });
+
+// Resolving the promise triggers the $onFulfilled callbacks and outputs
+// "Hello, reader".
+$promise->resolve('reader.');
+```
+
+
+## Promise forwarding
+
+Promises can be chained one after the other. Each then in the chain is a new
+promise. The return value of a promise is what's forwarded to the next
+promise in the chain. Returning a promise in a `then` callback will cause the
+subsequent promises in the chain to only be fulfilled when the returned promise
+has been fulfilled. The next promise in the chain will be invoked with the
+resolved value of the promise.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$nextPromise = new Promise();
+
+$promise
+ ->then(function ($value) use ($nextPromise) {
+ echo $value;
+ return $nextPromise;
+ })
+ ->then(function ($value) {
+ echo $value;
+ });
+
+// Triggers the first callback and outputs "A"
+$promise->resolve('A');
+// Triggers the second callback and outputs "B"
+$nextPromise->resolve('B');
+```
+
+## Promise rejection
+
+When a promise is rejected, the `$onRejected` callbacks are invoked with the
+rejection reason.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ echo $reason;
+});
+
+$promise->reject('Error!');
+// Outputs "Error!"
+```
+
+## Rejection forwarding
+
+If an exception is thrown in an `$onRejected` callback, subsequent
+`$onRejected` callbacks are invoked with the thrown exception as the reason.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ throw new \Exception($reason);
+})->then(null, function ($reason) {
+ assert($reason->getMessage() === 'Error!');
+});
+
+$promise->reject('Error!');
+```
+
+You can also forward a rejection down the promise chain by returning a
+`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or
+`$onRejected` callback.
+
+```php
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ return new RejectedPromise($reason);
+})->then(null, function ($reason) {
+ assert($reason === 'Error!');
+});
+
+$promise->reject('Error!');
+```
+
+If an exception is not thrown in a `$onRejected` callback and the callback
+does not return a rejected promise, downstream `$onFulfilled` callbacks are
+invoked using the value returned from the `$onRejected` callback.
+
+```php
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new Promise();
+$promise
+ ->then(null, function ($reason) {
+ return "It's ok";
+ })
+ ->then(function ($value) {
+ assert($value === "It's ok");
+ });
+
+$promise->reject('Error!');
+```
+
+# Synchronous wait
+
+You can synchronously force promises to complete using a promise's `wait`
+method. When creating a promise, you can provide a wait function that is used
+to synchronously force a promise to complete. When a wait function is invoked
+it is expected to deliver a value to the promise or reject the promise. If the
+wait function does not deliver a value, then an exception is thrown. The wait
+function provided to a promise constructor is invoked when the `wait` function
+of the promise is called.
+
+```php
+$promise = new Promise(function () use (&$promise) {
+ $promise->resolve('foo');
+});
+
+// Calling wait will return the value of the promise.
+echo $promise->wait(); // outputs "foo"
+```
+
+If an exception is encountered while invoking the wait function of a promise,
+the promise is rejected with the exception and the exception is thrown.
+
+```php
+$promise = new Promise(function () use (&$promise) {
+ throw new \Exception('foo');
+});
+
+$promise->wait(); // throws the exception.
+```
+
+Calling `wait` on a promise that has been fulfilled will not trigger the wait
+function. It will simply return the previously resolved value.
+
+```php
+$promise = new Promise(function () { die('this is not called!'); });
+$promise->resolve('foo');
+echo $promise->wait(); // outputs "foo"
+```
+
+Calling `wait` on a promise that has been rejected will throw an exception. If
+the rejection reason is an instance of `\Exception` the reason is thrown.
+Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
+can be obtained by calling the `getReason` method of the exception.
+
+```php
+$promise = new Promise();
+$promise->reject('foo');
+$promise->wait();
+```
+
+> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'
+
+
+## Unwrapping a promise
+
+When synchronously waiting on a promise, you are joining the state of the
+promise into the current state of execution (i.e., return the value of the
+promise if it was fulfilled or throw an exception if it was rejected). This is
+called "unwrapping" the promise. Waiting on a promise will by default unwrap
+the promise state.
+
+You can force a promise to resolve and *not* unwrap the state of the promise
+by passing `false` to the first argument of the `wait` function:
+
+```php
+$promise = new Promise();
+$promise->reject('foo');
+// This will not throw an exception. It simply ensures the promise has
+// been resolved.
+$promise->wait(false);
+```
+
+When unwrapping a promise, the resolved value of the promise will be waited
+upon until the unwrapped value is not a promise. This means that if you resolve
+promise A with a promise B and unwrap promise A, the value returned by the
+wait function will be the value delivered to promise B.
+
+**Note**: when you do not unwrap the promise, no value is returned.
+
+
+# Cancellation
+
+You can cancel a promise that has not yet been fulfilled using the `cancel()`
+method of a promise. When creating a promise you can provide an optional
+cancel function that when invoked cancels the action of computing a resolution
+of the promise.
+
+
+# API
+
+
+## Promise
+
+When creating a promise object, you can provide an optional `$waitFn` and
+`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
+expected to resolve the promise. `$cancelFn` is a function with no arguments
+that is expected to cancel the computation of a promise. It is invoked when the
+`cancel()` method of a promise is called.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise(
+ function () use (&$promise) {
+ $promise->resolve('waited');
+ },
+ function () {
+ // do something that will cancel the promise computation (e.g., close
+ // a socket, cancel a database query, etc...)
+ }
+);
+
+assert('waited' === $promise->wait());
+```
+
+A promise has the following methods:
+
+- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
+
+ Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.
+
+- `otherwise(callable $onRejected) : PromiseInterface`
+
+ Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
+
+- `wait($unwrap = true) : mixed`
+
+ Synchronously waits on the promise to complete.
+
+ `$unwrap` controls whether or not the value of the promise is returned for a
+ fulfilled promise or if an exception is thrown if the promise is rejected.
+ This is set to `true` by default.
+
+- `cancel()`
+
+ Attempts to cancel the promise if possible. The promise being cancelled and
+ the parent most ancestor that has not yet been resolved will also be
+ cancelled. Any promises waiting on the cancelled promise to resolve will also
+ be cancelled.
+
+- `getState() : string`
+
+ Returns the state of the promise. One of `pending`, `fulfilled`, or
+ `rejected`.
+
+- `resolve($value)`
+
+ Fulfills the promise with the given `$value`.
+
+- `reject($reason)`
+
+ Rejects the promise with the given `$reason`.
+
+
+## FulfilledPromise
+
+A fulfilled promise can be created to represent a promise that has been
+fulfilled.
+
+```php
+use GuzzleHttp\Promise\FulfilledPromise;
+
+$promise = new FulfilledPromise('value');
+
+// Fulfilled callbacks are immediately invoked.
+$promise->then(function ($value) {
+ echo $value;
+});
+```
+
+
+## RejectedPromise
+
+A rejected promise can be created to represent a promise that has been
+rejected.
+
+```php
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new RejectedPromise('Error');
+
+// Rejected callbacks are immediately invoked.
+$promise->then(null, function ($reason) {
+ echo $reason;
+});
+```
+
+
+# Promise interop
+
+This library works with foreign promises that have a `then` method. This means
+you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
+for example. When a foreign promise is returned inside of a then method
+callback, promise resolution will occur recursively.
+
+```php
+// Create a React promise
+$deferred = new React\Promise\Deferred();
+$reactPromise = $deferred->promise();
+
+// Create a Guzzle promise that is fulfilled with a React promise.
+$guzzlePromise = new \GuzzleHttp\Promise\Promise();
+$guzzlePromise->then(function ($value) use ($reactPromise) {
+ // Do something something with the value...
+ // Return the React promise
+ return $reactPromise;
+});
+```
+
+Please note that wait and cancel chaining is no longer possible when forwarding
+a foreign promise. You will need to wrap a third-party promise with a Guzzle
+promise in order to utilize wait and cancel functions with foreign promises.
+
+
+## Event Loop Integration
+
+In order to keep the stack size constant, Guzzle promises are resolved
+asynchronously using a task queue. When waiting on promises synchronously, the
+task queue will be automatically run to ensure that the blocking promise and
+any forwarded promises are resolved. When using promises asynchronously in an
+event loop, you will need to run the task queue on each tick of the loop. If
+you do not run the task queue, then promises will not be resolved.
+
+You can run the task queue using the `run()` method of the global task queue
+instance.
+
+```php
+// Get the global task queue
+$queue = \GuzzleHttp\Promise\queue();
+$queue->run();
+```
+
+For example, you could use Guzzle promises with React using a periodic timer:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$loop->addPeriodicTimer(0, [$queue, 'run']);
+```
+
+*TODO*: Perhaps adding a `futureTick()` on each tick would be faster?
+
+
+# Implementation notes
+
+
+## Promise resolution and chaining is handled iteratively
+
+By shuffling pending handlers from one owner to another, promises are
+resolved iteratively, allowing for "infinite" then chaining.
+
+```php
+then(function ($v) {
+ // The stack size remains constant (a good thing)
+ echo xdebug_get_stack_depth() . ', ';
+ return $v + 1;
+ });
+}
+
+$parent->resolve(0);
+var_dump($p->wait()); // int(1000)
+
+```
+
+When a promise is fulfilled or rejected with a non-promise value, the promise
+then takes ownership of the handlers of each child promise and delivers values
+down the chain without using recursion.
+
+When a promise is resolved with another promise, the original promise transfers
+all of its pending handlers to the new promise. When the new promise is
+eventually resolved, all of the pending handlers are delivered the forwarded
+value.
+
+
+## A promise is the deferred.
+
+Some promise libraries implement promises using a deferred object to represent
+a computation and a promise object to represent the delivery of the result of
+the computation. This is a nice separation of computation and delivery because
+consumers of the promise cannot modify the value that will be eventually
+delivered.
+
+One side effect of being able to implement promise resolution and chaining
+iteratively is that you need to be able for one promise to reach into the state
+of another promise to shuffle around ownership of handlers. In order to achieve
+this without making the handlers of a promise publicly mutable, a promise is
+also the deferred value, allowing promises of the same parent class to reach
+into and modify the private properties of promises of the same type. While this
+does allow consumers of the value to modify the resolution or rejection of the
+deferred, it is a small price to pay for keeping the stack size constant.
+
+```php
+$promise = new Promise();
+$promise->then(function ($value) { echo $value; });
+// The promise is the deferred value, so you can deliver a value to it.
+$promise->resolve('foo');
+// prints "foo"
+```
diff --git a/instafeed/vendor/guzzlehttp/promises/composer.json b/instafeed/vendor/guzzlehttp/promises/composer.json
new file mode 100755
index 0000000..ec41a61
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "guzzlehttp/promises",
+ "description": "Guzzle promises library",
+ "keywords": ["promise"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "test-ci": "vendor/bin/phpunit --coverage-text"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/promises/src/AggregateException.php b/instafeed/vendor/guzzlehttp/promises/src/AggregateException.php
new file mode 100755
index 0000000..6a5690c
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/src/AggregateException.php
@@ -0,0 +1,16 @@
+then(function ($v) { echo $v; });
+ *
+ * @param callable $generatorFn Generator function to wrap into a promise.
+ *
+ * @return Promise
+ * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
+ */
+final class Coroutine implements PromiseInterface
+{
+ /**
+ * @var PromiseInterface|null
+ */
+ private $currentPromise;
+
+ /**
+ * @var Generator
+ */
+ private $generator;
+
+ /**
+ * @var Promise
+ */
+ private $result;
+
+ public function __construct(callable $generatorFn)
+ {
+ $this->generator = $generatorFn();
+ $this->result = new Promise(function () {
+ while (isset($this->currentPromise)) {
+ $this->currentPromise->wait();
+ }
+ });
+ $this->nextCoroutine($this->generator->current());
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ return $this->result->then($onFulfilled, $onRejected);
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->result->otherwise($onRejected);
+ }
+
+ public function wait($unwrap = true)
+ {
+ return $this->result->wait($unwrap);
+ }
+
+ public function getState()
+ {
+ return $this->result->getState();
+ }
+
+ public function resolve($value)
+ {
+ $this->result->resolve($value);
+ }
+
+ public function reject($reason)
+ {
+ $this->result->reject($reason);
+ }
+
+ public function cancel()
+ {
+ $this->currentPromise->cancel();
+ $this->result->cancel();
+ }
+
+ private function nextCoroutine($yielded)
+ {
+ $this->currentPromise = promise_for($yielded)
+ ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
+ }
+
+ /**
+ * @internal
+ */
+ public function _handleSuccess($value)
+ {
+ unset($this->currentPromise);
+ try {
+ $next = $this->generator->send($value);
+ if ($this->generator->valid()) {
+ $this->nextCoroutine($next);
+ } else {
+ $this->result->resolve($value);
+ }
+ } catch (Exception $exception) {
+ $this->result->reject($exception);
+ } catch (Throwable $throwable) {
+ $this->result->reject($throwable);
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function _handleFailure($reason)
+ {
+ unset($this->currentPromise);
+ try {
+ $nextYield = $this->generator->throw(exception_for($reason));
+ // The throw was caught, so keep iterating on the coroutine
+ $this->nextCoroutine($nextYield);
+ } catch (Exception $exception) {
+ $this->result->reject($exception);
+ } catch (Throwable $throwable) {
+ $this->result->reject($throwable);
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/promises/src/EachPromise.php b/instafeed/vendor/guzzlehttp/promises/src/EachPromise.php
new file mode 100755
index 0000000..d0ddf60
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/src/EachPromise.php
@@ -0,0 +1,229 @@
+iterable = iter_for($iterable);
+
+ if (isset($config['concurrency'])) {
+ $this->concurrency = $config['concurrency'];
+ }
+
+ if (isset($config['fulfilled'])) {
+ $this->onFulfilled = $config['fulfilled'];
+ }
+
+ if (isset($config['rejected'])) {
+ $this->onRejected = $config['rejected'];
+ }
+ }
+
+ public function promise()
+ {
+ if ($this->aggregate) {
+ return $this->aggregate;
+ }
+
+ try {
+ $this->createPromise();
+ $this->iterable->rewind();
+ $this->refillPending();
+ } catch (\Throwable $e) {
+ $this->aggregate->reject($e);
+ } catch (\Exception $e) {
+ $this->aggregate->reject($e);
+ }
+
+ return $this->aggregate;
+ }
+
+ private function createPromise()
+ {
+ $this->mutex = false;
+ $this->aggregate = new Promise(function () {
+ reset($this->pending);
+ if (empty($this->pending) && !$this->iterable->valid()) {
+ $this->aggregate->resolve(null);
+ return;
+ }
+
+ // Consume a potentially fluctuating list of promises while
+ // ensuring that indexes are maintained (precluding array_shift).
+ while ($promise = current($this->pending)) {
+ next($this->pending);
+ $promise->wait();
+ if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+ }
+ });
+
+ // Clear the references when the promise is resolved.
+ $clearFn = function () {
+ $this->iterable = $this->concurrency = $this->pending = null;
+ $this->onFulfilled = $this->onRejected = null;
+ };
+
+ $this->aggregate->then($clearFn, $clearFn);
+ }
+
+ private function refillPending()
+ {
+ if (!$this->concurrency) {
+ // Add all pending promises.
+ while ($this->addPending() && $this->advanceIterator());
+ return;
+ }
+
+ // Add only up to N pending promises.
+ $concurrency = is_callable($this->concurrency)
+ ? call_user_func($this->concurrency, count($this->pending))
+ : $this->concurrency;
+ $concurrency = max($concurrency - count($this->pending), 0);
+ // Concurrency may be set to 0 to disallow new promises.
+ if (!$concurrency) {
+ return;
+ }
+ // Add the first pending promise.
+ $this->addPending();
+ // Note this is special handling for concurrency=1 so that we do
+ // not advance the iterator after adding the first promise. This
+ // helps work around issues with generators that might not have the
+ // next value to yield until promise callbacks are called.
+ while (--$concurrency
+ && $this->advanceIterator()
+ && $this->addPending());
+ }
+
+ private function addPending()
+ {
+ if (!$this->iterable || !$this->iterable->valid()) {
+ return false;
+ }
+
+ $promise = promise_for($this->iterable->current());
+ $idx = $this->iterable->key();
+
+ $this->pending[$idx] = $promise->then(
+ function ($value) use ($idx) {
+ if ($this->onFulfilled) {
+ call_user_func(
+ $this->onFulfilled, $value, $idx, $this->aggregate
+ );
+ }
+ $this->step($idx);
+ },
+ function ($reason) use ($idx) {
+ if ($this->onRejected) {
+ call_user_func(
+ $this->onRejected, $reason, $idx, $this->aggregate
+ );
+ }
+ $this->step($idx);
+ }
+ );
+
+ return true;
+ }
+
+ private function advanceIterator()
+ {
+ // Place a lock on the iterator so that we ensure to not recurse,
+ // preventing fatal generator errors.
+ if ($this->mutex) {
+ return false;
+ }
+
+ $this->mutex = true;
+
+ try {
+ $this->iterable->next();
+ $this->mutex = false;
+ return true;
+ } catch (\Throwable $e) {
+ $this->aggregate->reject($e);
+ $this->mutex = false;
+ return false;
+ } catch (\Exception $e) {
+ $this->aggregate->reject($e);
+ $this->mutex = false;
+ return false;
+ }
+ }
+
+ private function step($idx)
+ {
+ // If the promise was already resolved, then ignore this step.
+ if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+
+ unset($this->pending[$idx]);
+
+ // Only refill pending promises if we are not locked, preventing the
+ // EachPromise to recursively invoke the provided iterator, which
+ // cause a fatal error: "Cannot resume an already running generator"
+ if ($this->advanceIterator() && !$this->checkIfFinished()) {
+ // Add more pending promises if possible.
+ $this->refillPending();
+ }
+ }
+
+ private function checkIfFinished()
+ {
+ if (!$this->pending && !$this->iterable->valid()) {
+ // Resolve the promise if there's nothing left to do.
+ $this->aggregate->resolve(null);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/instafeed/vendor/guzzlehttp/promises/src/FulfilledPromise.php
new file mode 100755
index 0000000..dbbeeb9
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/src/FulfilledPromise.php
@@ -0,0 +1,82 @@
+value = $value;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ // Return itself if there is no onFulfilled function.
+ if (!$onFulfilled) {
+ return $this;
+ }
+
+ $queue = queue();
+ $p = new Promise([$queue, 'run']);
+ $value = $this->value;
+ $queue->add(static function () use ($p, $value, $onFulfilled) {
+ if ($p->getState() === self::PENDING) {
+ try {
+ $p->resolve($onFulfilled($value));
+ } catch (\Throwable $e) {
+ $p->reject($e);
+ } catch (\Exception $e) {
+ $p->reject($e);
+ }
+ }
+ });
+
+ return $p;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true, $defaultDelivery = null)
+ {
+ return $unwrap ? $this->value : null;
+ }
+
+ public function getState()
+ {
+ return self::FULFILLED;
+ }
+
+ public function resolve($value)
+ {
+ if ($value !== $this->value) {
+ throw new \LogicException("Cannot resolve a fulfilled promise");
+ }
+ }
+
+ public function reject($reason)
+ {
+ throw new \LogicException("Cannot reject a fulfilled promise");
+ }
+
+ public function cancel()
+ {
+ // pass
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/promises/src/Promise.php b/instafeed/vendor/guzzlehttp/promises/src/Promise.php
new file mode 100755
index 0000000..844ada0
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/src/Promise.php
@@ -0,0 +1,280 @@
+waitFn = $waitFn;
+ $this->cancelFn = $cancelFn;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ if ($this->state === self::PENDING) {
+ $p = new Promise(null, [$this, 'cancel']);
+ $this->handlers[] = [$p, $onFulfilled, $onRejected];
+ $p->waitList = $this->waitList;
+ $p->waitList[] = $this;
+ return $p;
+ }
+
+ // Return a fulfilled promise and immediately invoke any callbacks.
+ if ($this->state === self::FULFILLED) {
+ return $onFulfilled
+ ? promise_for($this->result)->then($onFulfilled)
+ : promise_for($this->result);
+ }
+
+ // It's either cancelled or rejected, so return a rejected promise
+ // and immediately invoke any callbacks.
+ $rejection = rejection_for($this->result);
+ return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true)
+ {
+ $this->waitIfPending();
+
+ $inner = $this->result instanceof PromiseInterface
+ ? $this->result->wait($unwrap)
+ : $this->result;
+
+ if ($unwrap) {
+ if ($this->result instanceof PromiseInterface
+ || $this->state === self::FULFILLED
+ ) {
+ return $inner;
+ } else {
+ // It's rejected so "unwrap" and throw an exception.
+ throw exception_for($inner);
+ }
+ }
+ }
+
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ public function cancel()
+ {
+ if ($this->state !== self::PENDING) {
+ return;
+ }
+
+ $this->waitFn = $this->waitList = null;
+
+ if ($this->cancelFn) {
+ $fn = $this->cancelFn;
+ $this->cancelFn = null;
+ try {
+ $fn();
+ } catch (\Throwable $e) {
+ $this->reject($e);
+ } catch (\Exception $e) {
+ $this->reject($e);
+ }
+ }
+
+ // Reject the promise only if it wasn't rejected in a then callback.
+ if ($this->state === self::PENDING) {
+ $this->reject(new CancellationException('Promise has been cancelled'));
+ }
+ }
+
+ public function resolve($value)
+ {
+ $this->settle(self::FULFILLED, $value);
+ }
+
+ public function reject($reason)
+ {
+ $this->settle(self::REJECTED, $reason);
+ }
+
+ private function settle($state, $value)
+ {
+ if ($this->state !== self::PENDING) {
+ // Ignore calls with the same resolution.
+ if ($state === $this->state && $value === $this->result) {
+ return;
+ }
+ throw $this->state === $state
+ ? new \LogicException("The promise is already {$state}.")
+ : new \LogicException("Cannot change a {$this->state} promise to {$state}");
+ }
+
+ if ($value === $this) {
+ throw new \LogicException('Cannot fulfill or reject a promise with itself');
+ }
+
+ // Clear out the state of the promise but stash the handlers.
+ $this->state = $state;
+ $this->result = $value;
+ $handlers = $this->handlers;
+ $this->handlers = null;
+ $this->waitList = $this->waitFn = null;
+ $this->cancelFn = null;
+
+ if (!$handlers) {
+ return;
+ }
+
+ // If the value was not a settled promise or a thenable, then resolve
+ // it in the task queue using the correct ID.
+ if (!method_exists($value, 'then')) {
+ $id = $state === self::FULFILLED ? 1 : 2;
+ // It's a success, so resolve the handlers in the queue.
+ queue()->add(static function () use ($id, $value, $handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler($id, $value, $handler);
+ }
+ });
+ } elseif ($value instanceof Promise
+ && $value->getState() === self::PENDING
+ ) {
+ // We can just merge our handlers onto the next promise.
+ $value->handlers = array_merge($value->handlers, $handlers);
+ } else {
+ // Resolve the handlers when the forwarded promise is resolved.
+ $value->then(
+ static function ($value) use ($handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler(1, $value, $handler);
+ }
+ },
+ static function ($reason) use ($handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler(2, $reason, $handler);
+ }
+ }
+ );
+ }
+ }
+
+ /**
+ * Call a stack of handlers using a specific callback index and value.
+ *
+ * @param int $index 1 (resolve) or 2 (reject).
+ * @param mixed $value Value to pass to the callback.
+ * @param array $handler Array of handler data (promise and callbacks).
+ *
+ * @return array Returns the next group to resolve.
+ */
+ private static function callHandler($index, $value, array $handler)
+ {
+ /** @var PromiseInterface $promise */
+ $promise = $handler[0];
+
+ // The promise may have been cancelled or resolved before placing
+ // this thunk in the queue.
+ if ($promise->getState() !== self::PENDING) {
+ return;
+ }
+
+ try {
+ if (isset($handler[$index])) {
+ $promise->resolve($handler[$index]($value));
+ } elseif ($index === 1) {
+ // Forward resolution values as-is.
+ $promise->resolve($value);
+ } else {
+ // Forward rejections down the chain.
+ $promise->reject($value);
+ }
+ } catch (\Throwable $reason) {
+ $promise->reject($reason);
+ } catch (\Exception $reason) {
+ $promise->reject($reason);
+ }
+ }
+
+ private function waitIfPending()
+ {
+ if ($this->state !== self::PENDING) {
+ return;
+ } elseif ($this->waitFn) {
+ $this->invokeWaitFn();
+ } elseif ($this->waitList) {
+ $this->invokeWaitList();
+ } else {
+ // If there's not wait function, then reject the promise.
+ $this->reject('Cannot wait on a promise that has '
+ . 'no internal wait function. You must provide a wait '
+ . 'function when constructing the promise to be able to '
+ . 'wait on a promise.');
+ }
+
+ queue()->run();
+
+ if ($this->state === self::PENDING) {
+ $this->reject('Invoking the wait callback did not resolve the promise');
+ }
+ }
+
+ private function invokeWaitFn()
+ {
+ try {
+ $wfn = $this->waitFn;
+ $this->waitFn = null;
+ $wfn(true);
+ } catch (\Exception $reason) {
+ if ($this->state === self::PENDING) {
+ // The promise has not been resolved yet, so reject the promise
+ // with the exception.
+ $this->reject($reason);
+ } else {
+ // The promise was already resolved, so there's a problem in
+ // the application.
+ throw $reason;
+ }
+ }
+ }
+
+ private function invokeWaitList()
+ {
+ $waitList = $this->waitList;
+ $this->waitList = null;
+
+ foreach ($waitList as $result) {
+ while (true) {
+ $result->waitIfPending();
+
+ if ($result->result instanceof Promise) {
+ $result = $result->result;
+ } else {
+ if ($result->result instanceof PromiseInterface) {
+ $result->result->wait(false);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/promises/src/PromiseInterface.php b/instafeed/vendor/guzzlehttp/promises/src/PromiseInterface.php
new file mode 100755
index 0000000..8f5f4b9
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/src/PromiseInterface.php
@@ -0,0 +1,93 @@
+reason = $reason;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ // If there's no onRejected callback then just return self.
+ if (!$onRejected) {
+ return $this;
+ }
+
+ $queue = queue();
+ $reason = $this->reason;
+ $p = new Promise([$queue, 'run']);
+ $queue->add(static function () use ($p, $reason, $onRejected) {
+ if ($p->getState() === self::PENDING) {
+ try {
+ // Return a resolved promise if onRejected does not throw.
+ $p->resolve($onRejected($reason));
+ } catch (\Throwable $e) {
+ // onRejected threw, so return a rejected promise.
+ $p->reject($e);
+ } catch (\Exception $e) {
+ // onRejected threw, so return a rejected promise.
+ $p->reject($e);
+ }
+ }
+ });
+
+ return $p;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true, $defaultDelivery = null)
+ {
+ if ($unwrap) {
+ throw exception_for($this->reason);
+ }
+ }
+
+ public function getState()
+ {
+ return self::REJECTED;
+ }
+
+ public function resolve($value)
+ {
+ throw new \LogicException("Cannot resolve a rejected promise");
+ }
+
+ public function reject($reason)
+ {
+ if ($reason !== $this->reason) {
+ throw new \LogicException("Cannot reject a rejected promise");
+ }
+ }
+
+ public function cancel()
+ {
+ // pass
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/promises/src/RejectionException.php b/instafeed/vendor/guzzlehttp/promises/src/RejectionException.php
new file mode 100755
index 0000000..07c1136
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/src/RejectionException.php
@@ -0,0 +1,47 @@
+reason = $reason;
+
+ $message = 'The promise was rejected';
+
+ if ($description) {
+ $message .= ' with reason: ' . $description;
+ } elseif (is_string($reason)
+ || (is_object($reason) && method_exists($reason, '__toString'))
+ ) {
+ $message .= ' with reason: ' . $this->reason;
+ } elseif ($reason instanceof \JsonSerializable) {
+ $message .= ' with reason: '
+ . json_encode($this->reason, JSON_PRETTY_PRINT);
+ }
+
+ parent::__construct($message);
+ }
+
+ /**
+ * Returns the rejection reason.
+ *
+ * @return mixed
+ */
+ public function getReason()
+ {
+ return $this->reason;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/promises/src/TaskQueue.php b/instafeed/vendor/guzzlehttp/promises/src/TaskQueue.php
new file mode 100755
index 0000000..6e8a2a0
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/src/TaskQueue.php
@@ -0,0 +1,66 @@
+run();
+ */
+class TaskQueue implements TaskQueueInterface
+{
+ private $enableShutdown = true;
+ private $queue = [];
+
+ public function __construct($withShutdown = true)
+ {
+ if ($withShutdown) {
+ register_shutdown_function(function () {
+ if ($this->enableShutdown) {
+ // Only run the tasks if an E_ERROR didn't occur.
+ $err = error_get_last();
+ if (!$err || ($err['type'] ^ E_ERROR)) {
+ $this->run();
+ }
+ }
+ });
+ }
+ }
+
+ public function isEmpty()
+ {
+ return !$this->queue;
+ }
+
+ public function add(callable $task)
+ {
+ $this->queue[] = $task;
+ }
+
+ public function run()
+ {
+ /** @var callable $task */
+ while ($task = array_shift($this->queue)) {
+ $task();
+ }
+ }
+
+ /**
+ * The task queue will be run and exhausted by default when the process
+ * exits IFF the exit is not the result of a PHP E_ERROR error.
+ *
+ * You can disable running the automatic shutdown of the queue by calling
+ * this function. If you disable the task queue shutdown process, then you
+ * MUST either run the task queue (as a result of running your event loop
+ * or manually using the run() method) or wait on each outstanding promise.
+ *
+ * Note: This shutdown will occur before any destructors are triggered.
+ */
+ public function disableShutdown()
+ {
+ $this->enableShutdown = false;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/instafeed/vendor/guzzlehttp/promises/src/TaskQueueInterface.php
new file mode 100755
index 0000000..ac8306e
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/src/TaskQueueInterface.php
@@ -0,0 +1,25 @@
+
+ * while ($eventLoop->isRunning()) {
+ * GuzzleHttp\Promise\queue()->run();
+ * }
+ *
+ *
+ * @param TaskQueueInterface $assign Optionally specify a new queue instance.
+ *
+ * @return TaskQueueInterface
+ */
+function queue(TaskQueueInterface $assign = null)
+{
+ static $queue;
+
+ if ($assign) {
+ $queue = $assign;
+ } elseif (!$queue) {
+ $queue = new TaskQueue();
+ }
+
+ return $queue;
+}
+
+/**
+ * Adds a function to run in the task queue when it is next `run()` and returns
+ * a promise that is fulfilled or rejected with the result.
+ *
+ * @param callable $task Task function to run.
+ *
+ * @return PromiseInterface
+ */
+function task(callable $task)
+{
+ $queue = queue();
+ $promise = new Promise([$queue, 'run']);
+ $queue->add(function () use ($task, $promise) {
+ try {
+ $promise->resolve($task());
+ } catch (\Throwable $e) {
+ $promise->reject($e);
+ } catch (\Exception $e) {
+ $promise->reject($e);
+ }
+ });
+
+ return $promise;
+}
+
+/**
+ * Creates a promise for a value if the value is not a promise.
+ *
+ * @param mixed $value Promise or value.
+ *
+ * @return PromiseInterface
+ */
+function promise_for($value)
+{
+ if ($value instanceof PromiseInterface) {
+ return $value;
+ }
+
+ // Return a Guzzle promise that shadows the given promise.
+ if (method_exists($value, 'then')) {
+ $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
+ $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
+ $promise = new Promise($wfn, $cfn);
+ $value->then([$promise, 'resolve'], [$promise, 'reject']);
+ return $promise;
+ }
+
+ return new FulfilledPromise($value);
+}
+
+/**
+ * Creates a rejected promise for a reason if the reason is not a promise. If
+ * the provided reason is a promise, then it is returned as-is.
+ *
+ * @param mixed $reason Promise or reason.
+ *
+ * @return PromiseInterface
+ */
+function rejection_for($reason)
+{
+ if ($reason instanceof PromiseInterface) {
+ return $reason;
+ }
+
+ return new RejectedPromise($reason);
+}
+
+/**
+ * Create an exception for a rejected promise value.
+ *
+ * @param mixed $reason
+ *
+ * @return \Exception|\Throwable
+ */
+function exception_for($reason)
+{
+ return $reason instanceof \Exception || $reason instanceof \Throwable
+ ? $reason
+ : new RejectionException($reason);
+}
+
+/**
+ * Returns an iterator for the given value.
+ *
+ * @param mixed $value
+ *
+ * @return \Iterator
+ */
+function iter_for($value)
+{
+ if ($value instanceof \Iterator) {
+ return $value;
+ } elseif (is_array($value)) {
+ return new \ArrayIterator($value);
+ } else {
+ return new \ArrayIterator([$value]);
+ }
+}
+
+/**
+ * Synchronously waits on a promise to resolve and returns an inspection state
+ * array.
+ *
+ * Returns a state associative array containing a "state" key mapping to a
+ * valid promise state. If the state of the promise is "fulfilled", the array
+ * will contain a "value" key mapping to the fulfilled value of the promise. If
+ * the promise is rejected, the array will contain a "reason" key mapping to
+ * the rejection reason of the promise.
+ *
+ * @param PromiseInterface $promise Promise or value.
+ *
+ * @return array
+ */
+function inspect(PromiseInterface $promise)
+{
+ try {
+ return [
+ 'state' => PromiseInterface::FULFILLED,
+ 'value' => $promise->wait()
+ ];
+ } catch (RejectionException $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
+ } catch (\Throwable $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ } catch (\Exception $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ }
+}
+
+/**
+ * Waits on all of the provided promises, but does not unwrap rejected promises
+ * as thrown exception.
+ *
+ * Returns an array of inspection state arrays.
+ *
+ * @param PromiseInterface[] $promises Traversable of promises to wait upon.
+ *
+ * @return array
+ * @see GuzzleHttp\Promise\inspect for the inspection state array format.
+ */
+function inspect_all($promises)
+{
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = inspect($promise);
+ }
+
+ return $results;
+}
+
+/**
+ * Waits on all of the provided promises and returns the fulfilled values.
+ *
+ * Returns an array that contains the value of each promise (in the same order
+ * the promises were provided). An exception is thrown if any of the promises
+ * are rejected.
+ *
+ * @param mixed $promises Iterable of PromiseInterface objects to wait on.
+ *
+ * @return array
+ * @throws \Exception on error
+ * @throws \Throwable on error in PHP >=7
+ */
+function unwrap($promises)
+{
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = $promise->wait();
+ }
+
+ return $results;
+}
+
+/**
+ * Given an array of promises, return a promise that is fulfilled when all the
+ * items in the array are fulfilled.
+ *
+ * The promise's fulfillment value is an array with fulfillment values at
+ * respective positions to the original array. If any promise in the array
+ * rejects, the returned promise is rejected with the rejection reason.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function all($promises)
+{
+ $results = [];
+ return each(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = $value;
+ },
+ function ($reason, $idx, Promise $aggregate) {
+ $aggregate->reject($reason);
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+}
+
+/**
+ * Initiate a competitive race between multiple promises or values (values will
+ * become immediately fulfilled promises).
+ *
+ * When count amount of promises have been fulfilled, the returned promise is
+ * fulfilled with an array that contains the fulfillment values of the winners
+ * in order of resolution.
+ *
+ * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException}
+ * if the number of fulfilled promises is less than the desired $count.
+ *
+ * @param int $count Total number of promises.
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function some($count, $promises)
+{
+ $results = [];
+ $rejections = [];
+
+ return each(
+ $promises,
+ function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
+ if ($p->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+ $results[$idx] = $value;
+ if (count($results) >= $count) {
+ $p->resolve(null);
+ }
+ },
+ function ($reason) use (&$rejections) {
+ $rejections[] = $reason;
+ }
+ )->then(
+ function () use (&$results, &$rejections, $count) {
+ if (count($results) !== $count) {
+ throw new AggregateException(
+ 'Not enough promises to fulfill count',
+ $rejections
+ );
+ }
+ ksort($results);
+ return array_values($results);
+ }
+ );
+}
+
+/**
+ * Like some(), with 1 as count. However, if the promise fulfills, the
+ * fulfillment value is not an array of 1 but the value directly.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function any($promises)
+{
+ return some(1, $promises)->then(function ($values) { return $values[0]; });
+}
+
+/**
+ * Returns a promise that is fulfilled when all of the provided promises have
+ * been fulfilled or rejected.
+ *
+ * The returned promise is fulfilled with an array of inspection state arrays.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ * @see GuzzleHttp\Promise\inspect for the inspection state array format.
+ */
+function settle($promises)
+{
+ $results = [];
+
+ return each(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
+ },
+ function ($reason, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+}
+
+/**
+ * Given an iterator that yields promises or values, returns a promise that is
+ * fulfilled with a null value when the iterator has been consumed or the
+ * aggregate promise has been fulfilled or rejected.
+ *
+ * $onFulfilled is a function that accepts the fulfilled value, iterator
+ * index, and the aggregate promise. The callback can invoke any necessary side
+ * effects and choose to resolve or reject the aggregate promise if needed.
+ *
+ * $onRejected is a function that accepts the rejection reason, iterator
+ * index, and the aggregate promise. The callback can invoke any necessary side
+ * effects and choose to resolve or reject the aggregate promise if needed.
+ *
+ * @param mixed $iterable Iterator or array to iterate over.
+ * @param callable $onFulfilled
+ * @param callable $onRejected
+ *
+ * @return PromiseInterface
+ */
+function each(
+ $iterable,
+ callable $onFulfilled = null,
+ callable $onRejected = null
+) {
+ return (new EachPromise($iterable, [
+ 'fulfilled' => $onFulfilled,
+ 'rejected' => $onRejected
+ ]))->promise();
+}
+
+/**
+ * Like each, but only allows a certain number of outstanding promises at any
+ * given time.
+ *
+ * $concurrency may be an integer or a function that accepts the number of
+ * pending promises and returns a numeric concurrency limit value to allow for
+ * dynamic a concurrency size.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ * @param callable $onRejected
+ *
+ * @return PromiseInterface
+ */
+function each_limit(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null,
+ callable $onRejected = null
+) {
+ return (new EachPromise($iterable, [
+ 'fulfilled' => $onFulfilled,
+ 'rejected' => $onRejected,
+ 'concurrency' => $concurrency
+ ]))->promise();
+}
+
+/**
+ * Like each_limit, but ensures that no promise in the given $iterable argument
+ * is rejected. If any promise is rejected, then the aggregate promise is
+ * rejected with the encountered rejection.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ *
+ * @return PromiseInterface
+ */
+function each_limit_all(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null
+) {
+ return each_limit(
+ $iterable,
+ $concurrency,
+ $onFulfilled,
+ function ($reason, $idx, PromiseInterface $aggregate) {
+ $aggregate->reject($reason);
+ }
+ );
+}
+
+/**
+ * Returns true if a promise is fulfilled.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_fulfilled(PromiseInterface $promise)
+{
+ return $promise->getState() === PromiseInterface::FULFILLED;
+}
+
+/**
+ * Returns true if a promise is rejected.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_rejected(PromiseInterface $promise)
+{
+ return $promise->getState() === PromiseInterface::REJECTED;
+}
+
+/**
+ * Returns true if a promise is fulfilled or rejected.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_settled(PromiseInterface $promise)
+{
+ return $promise->getState() !== PromiseInterface::PENDING;
+}
+
+/**
+ * @see Coroutine
+ *
+ * @param callable $generatorFn
+ *
+ * @return PromiseInterface
+ */
+function coroutine(callable $generatorFn)
+{
+ return new Coroutine($generatorFn);
+}
diff --git a/instafeed/vendor/guzzlehttp/promises/src/functions_include.php b/instafeed/vendor/guzzlehttp/promises/src/functions_include.php
new file mode 100755
index 0000000..34cd171
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/promises/src/functions_include.php
@@ -0,0 +1,6 @@
+withPath('foo')->withHost('example.com')` will throw an exception
+ because the path of a URI with an authority must start with a slash "/" or be empty
+ - `(new Uri())->withScheme('http')` will return `'http://localhost'`
+
+### Deprecated
+
+- `Uri::resolve` in favor of `UriResolver::resolve`
+- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`
+
+### Fixed
+
+- `Stream::read` when length parameter <= 0.
+- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
+- `ServerRequest::getUriFromGlobals` when `Host` header contains port.
+- Compatibility of URIs with `file` scheme and empty host.
+
+
+## [1.3.1] - 2016-06-25
+
+### Fixed
+
+- `Uri::__toString` for network path references, e.g. `//example.org`.
+- Missing lowercase normalization for host.
+- Handling of URI components in case they are `'0'` in a lot of places,
+ e.g. as a user info password.
+- `Uri::withAddedHeader` to correctly merge headers with different case.
+- Trimming of header values in `Uri::withAddedHeader`. Header values may
+ be surrounded by whitespace which should be ignored according to RFC 7230
+ Section 3.2.4. This does not apply to header names.
+- `Uri::withAddedHeader` with an array of header values.
+- `Uri::resolve` when base path has no slash and handling of fragment.
+- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the
+ key/value both in encoded as well as decoded form to those methods. This is
+ consistent with withPath, withQuery etc.
+- `ServerRequest::withoutAttribute` when attribute value is null.
+
+
+## [1.3.0] - 2016-04-13
+
+### Added
+
+- Remaining interfaces needed for full PSR7 compatibility
+ (ServerRequestInterface, UploadedFileInterface, etc.).
+- Support for stream_for from scalars.
+
+### Changed
+
+- Can now extend Uri.
+
+### Fixed
+- A bug in validating request methods by making it more permissive.
+
+
+## [1.2.3] - 2016-02-18
+
+### Fixed
+
+- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
+ streams, which can sometimes return fewer bytes than requested with `fread`.
+- Handling of gzipped responses with FNAME headers.
+
+
+## [1.2.2] - 2016-01-22
+
+### Added
+
+- Support for URIs without any authority.
+- Support for HTTP 451 'Unavailable For Legal Reasons.'
+- Support for using '0' as a filename.
+- Support for including non-standard ports in Host headers.
+
+
+## [1.2.1] - 2015-11-02
+
+### Changes
+
+- Now supporting negative offsets when seeking to SEEK_END.
+
+
+## [1.2.0] - 2015-08-15
+
+### Changed
+
+- Body as `"0"` is now properly added to a response.
+- Now allowing forward seeking in CachingStream.
+- Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+- functions.php is now conditionally required.
+- user-info is no longer dropped when resolving URIs.
+
+
+## [1.1.0] - 2015-06-24
+
+### Changed
+
+- URIs can now be relative.
+- `multipart/form-data` headers are now overridden case-insensitively.
+- URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+- A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
+
+
+
+[Unreleased]: https://github.com/guzzle/psr7/compare/1.6.0...HEAD
+[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0
+[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2
+[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1
+[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0
+[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2
+[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1
+[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0
+[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1
+[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0
+[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3
+[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2
+[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1
+[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0
+[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0
diff --git a/instafeed/vendor/guzzlehttp/psr7/LICENSE b/instafeed/vendor/guzzlehttp/psr7/LICENSE
new file mode 100755
index 0000000..581d95f
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/guzzlehttp/psr7/README.md b/instafeed/vendor/guzzlehttp/psr7/README.md
new file mode 100755
index 0000000..c60a6a3
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/README.md
@@ -0,0 +1,745 @@
+# PSR-7 Message Implementation
+
+This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing.
+
+
+[](https://travis-ci.org/guzzle/psr7)
+
+
+# Stream implementation
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## AppendStream
+
+`GuzzleHttp\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use GuzzleHttp\Psr7;
+
+$a = Psr7\stream_for('abc, ');
+$b = Psr7\stream_for('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\stream_for(' Above all listen to me'));
+
+echo $composed; // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`GuzzleHttp\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use GuzzleHttp\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`GuzzleHttp\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use GuzzleHttp\Psr7;
+
+// Create an empty stream
+$stream = Psr7\stream_for();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$dropping->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`GuzzleHttp\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing
+to create a concrete class for a simple extension point.
+
+```php
+
+use GuzzleHttp\Psr7;
+
+$stream = Psr7\stream_for('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`GuzzleHttp\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
+
+This stream decorator skips the first 10 bytes of the given stream to remove
+the gzip header, converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`GuzzleHttp\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use GuzzleHttp\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`GuzzleHttp\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`GuzzleHttp\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`GuzzleHttp\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`GuzzleHttp\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use GuzzleHttp\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ call_user_func($this->callback);
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use GuzzleHttp\Psr7\StreamWrapper;
+
+$stream = GuzzleHttp\Psr7\stream_for('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Function API
+
+There are various functions available under the `GuzzleHttp\Psr7` namespace.
+
+
+## `function str`
+
+`function str(MessageInterface $message)`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com');
+echo GuzzleHttp\Psr7\str($request);
+```
+
+
+## `function uri_for`
+
+`function uri_for($uri)`
+
+This function accepts a string or `Psr\Http\Message\UriInterface` and returns a
+UriInterface for the given value. If the value is already a `UriInterface`, it
+is returned as-is.
+
+```php
+$uri = GuzzleHttp\Psr7\uri_for('http://example.com');
+assert($uri === GuzzleHttp\Psr7\uri_for($uri));
+```
+
+
+## `function stream_for`
+
+`function stream_for($resource = '', array $options = [])`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+* - metadata: Array of custom metadata.
+* - size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = GuzzleHttp\Psr7\stream_for('foo');
+$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r'));
+
+$generator = function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = GuzzleHttp\Psr7\stream_for($generator(100));
+```
+
+
+## `function parse_header`
+
+`function parse_header($header)`
+
+Parse an array of header values containing ";" separated data into an array of
+associative arrays representing the header key value pair data of the header.
+When a parameter does not contain a value, but just contains a key, this
+function will inject a key with a '' string value.
+
+
+## `function normalize_header`
+
+`function normalize_header($header)`
+
+Converts an array of header values that may contain comma separated headers
+into an array of headers with no comma separated values.
+
+
+## `function modify_request`
+
+`function modify_request(RequestInterface $request, array $changes)`
+
+Clone and modify a request with the given changes. This method is useful for
+reducing the number of clones needed to mutate a message.
+
+The changes can be one of:
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `function rewind_body`
+
+`function rewind_body(MessageInterface $message)`
+
+Attempts to rewind a message body and throws an exception on failure. The body
+of the message will only be rewound if a call to `tell()` returns a value other
+than `0`.
+
+
+## `function try_fopen`
+
+`function try_fopen($filename, $mode)`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an error
+handler that checks for errors and throws an exception instead.
+
+
+## `function copy_to_string`
+
+`function copy_to_string(StreamInterface $stream, $maxLen = -1)`
+
+Copy the contents of a stream into a string until the given number of bytes
+have been read.
+
+
+## `function copy_to_stream`
+
+`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)`
+
+Copy the contents of a stream into another stream until the given number of
+bytes have been read.
+
+
+## `function hash`
+
+`function hash(StreamInterface $stream, $algo, $rawOutput = false)`
+
+Calculate a hash of a Stream. This method reads the entire stream to calculate
+a rolling hash (based on PHP's hash_init functions).
+
+
+## `function readline`
+
+`function readline(StreamInterface $stream, $maxLength = null)`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `function parse_request`
+
+`function parse_request($message)`
+
+Parses a request message string into a request object.
+
+
+## `function parse_response`
+
+`function parse_response($message)`
+
+Parses a response message string into a response object.
+
+
+## `function parse_query`
+
+`function parse_query($str, $urlEncoding = true)`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key value pair
+will become an array. This function does not parse nested PHP style arrays into
+an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
+`['foo[a]' => '1', 'foo[b]' => '2']`).
+
+
+## `function build_query`
+
+`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of parse_query() to build a query string.
+This function does not modify the provided keys when an array is encountered
+(like http_build_query would).
+
+
+## `function mimetype_from_filename`
+
+`function mimetype_from_filename($filename)`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `function mimetype_from_extension`
+
+`function mimetype_from_extension($extension)`
+
+Maps a file extensions to a mimetype.
+
+
+# Additional URI Methods
+
+Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
+this library also provides additional functionality when working with URIs as static methods.
+
+## URI Types
+
+An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
+An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
+the base URI. Relative references can be divided into several forms according to
+[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2):
+
+- network-path references, e.g. `//example.com/path`
+- absolute-path references, e.g. `/path`
+- relative-path references, e.g. `subpath`
+
+The following methods can be used to identify the type of the URI.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolute`
+
+`public static function isAbsolute(UriInterface $uri): bool`
+
+Whether the URI is absolute, i.e. it has a scheme.
+
+### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
+
+`public static function isNetworkPathReference(UriInterface $uri): bool`
+
+Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
+termed an network-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
+
+`public static function isAbsolutePathReference(UriInterface $uri): bool`
+
+Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
+termed an absolute-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
+
+`public static function isRelativePathReference(UriInterface $uri): bool`
+
+Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
+termed a relative-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
+
+`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
+
+Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
+fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
+(apart from its fragment) is considered a same-document reference.
+
+## URI Components
+
+Additional methods to work with URI components.
+
+### `GuzzleHttp\Psr7\Uri::isDefaultPort`
+
+`public static function isDefaultPort(UriInterface $uri): bool`
+
+Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
+or the standard port. This method can be used independently of the implementation.
+
+### `GuzzleHttp\Psr7\Uri::composeComponents`
+
+`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
+
+Composes a URI reference string from its various components according to
+[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called
+manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
+
+### `GuzzleHttp\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts): UriInterface`
+
+Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components.
+
+
+### `GuzzleHttp\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
+
+Creates a new URI with a specific query string value. Any existing query string values that exactly match the
+provided key are removed and replaced with the given key value pair. A value of null will set the query string
+key without a value, e.g. "key" instead of "key=value".
+
+### `GuzzleHttp\Psr7\Uri::withQueryValues`
+
+`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface`
+
+Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an
+associative array of key => value.
+
+### `GuzzleHttp\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
+
+Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
+provided key are removed.
+
+## Reference Resolution
+
+`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
+to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers
+do when resolving a link in a website based on the current request URI.
+
+### `GuzzleHttp\Psr7\UriResolver::resolve`
+
+`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
+
+Converts the relative URI into a new URI that is resolved against the base URI.
+
+### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
+
+`public static function removeDotSegments(string $path): string`
+
+Removes dot segments from a path and returns the new path according to
+[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4).
+
+### `GuzzleHttp\Psr7\UriResolver::relativize`
+
+`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
+
+Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
+
+```php
+(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+```
+
+One use-case is to use the current request URI as base URI and then generate relative links in your documents
+to reduce the document size or offer self-contained downloadable document archives.
+
+```php
+$base = new Uri('http://example.com/a/b/');
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+```
+
+## Normalization and Comparison
+
+`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
+[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6).
+
+### `GuzzleHttp\Psr7\UriNormalizer::normalize`
+
+`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
+
+Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
+This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
+of normalizations to apply. The following normalizations are available:
+
+- `UriNormalizer::PRESERVING_NORMALIZATIONS`
+
+ Default normalizations which only include the ones that preserve semantics.
+
+- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
+
+ All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
+
+ Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b`
+
+- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
+
+ Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
+ ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
+ not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
+ characters by URI normalizers.
+
+ Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/`
+
+- `UriNormalizer::CONVERT_EMPTY_PATH`
+
+ Converts the empty path to "/" for http and https URIs.
+
+ Example: `http://example.org` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DEFAULT_HOST`
+
+ Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
+ "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
+ RFC 3986.
+
+ Example: `file://localhost/myfile` → `file:///myfile`
+
+- `UriNormalizer::REMOVE_DEFAULT_PORT`
+
+ Removes the default port of the given URI scheme from the URI.
+
+ Example: `http://example.org:80/` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DOT_SEGMENTS`
+
+ Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
+ change the semantics of the URI reference.
+
+ Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html`
+
+- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
+
+ Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
+ and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
+ may change the semantics. Encoded slashes (%2F) are not removed.
+
+ Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html`
+
+- `UriNormalizer::SORT_QUERY_PARAMETERS`
+
+ Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
+ significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
+ of the URI.
+
+ Example: `?lang=en&article=fred` → `?article=fred&lang=en`
+
+### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
+
+`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
+
+Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
+`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
+This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
+equivalence or difference of relative references does not mean anything.
diff --git a/instafeed/vendor/guzzlehttp/psr7/composer.json b/instafeed/vendor/guzzlehttp/psr7/composer.json
new file mode 100755
index 0000000..168a055
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/composer.json
@@ -0,0 +1,49 @@
+{
+ "name": "guzzlehttp/psr7",
+ "type": "library",
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": ["request", "response", "message", "stream", "http", "uri", "url", "psr-7"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8",
+ "ext-zlib": "*"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "suggest": {
+ "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\Psr7\\": "tests/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6-dev"
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/AppendStream.php b/instafeed/vendor/guzzlehttp/psr7/src/AppendStream.php
new file mode 100755
index 0000000..472a0d6
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/AppendStream.php
@@ -0,0 +1,241 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream)
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ *
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = [];
+ }
+
+ /**
+ * Detaches each attached stream.
+ *
+ * Returns null as it's not clear which underlying stream resource to return.
+ *
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->detach();
+ }
+
+ $this->streams = [];
+ }
+
+ public function tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ *
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof()
+ {
+ return !$this->streams ||
+ ($this->current >= count($this->streams) - 1 &&
+ $this->streams[$this->current]->eof());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ *
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ . $i . ' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ *
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ $this->current++;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ // Using a loose comparison here to match on '', false, and null
+ if ($result == null) {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key ? null : [];
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/BufferStream.php b/instafeed/vendor/guzzlehttp/psr7/src/BufferStream.php
new file mode 100755
index 0000000..af4d4c2
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/BufferStream.php
@@ -0,0 +1,137 @@
+hwm = $hwm;
+ }
+
+ public function __toString()
+ {
+ return $this->getContents();
+ }
+
+ public function getContents()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+ }
+
+ public function getSize()
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return true;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof()
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell()
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length)
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string)
+ {
+ $this->buffer .= $string;
+
+ // TODO: What should happen here?
+ if (strlen($this->buffer) >= $this->hwm) {
+ return false;
+ }
+
+ return strlen($string);
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key == 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : [];
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/CachingStream.php b/instafeed/vendor/guzzlehttp/psr7/src/CachingStream.php
new file mode 100755
index 0000000..ed68f08
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/CachingStream.php
@@ -0,0 +1,138 @@
+remoteStream = $stream;
+ $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
+ }
+
+ public function getSize()
+ {
+ return max($this->stream->getSize(), $this->remoteStream->getSize());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence == SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence == SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence == SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ $byte = $size + $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // Read the remoteStream until we have read in at least the amount
+ // of bytes requested, or we reach the end of the file.
+ while ($diff > 0 && !$this->remoteStream->eof()) {
+ $this->read($diff);
+ $diff = $byte - $this->stream->getSize();
+ }
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string)
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ $this->remoteStream->close() && $this->stream->close();
+ }
+
+ private function cacheEntireStream()
+ {
+ $target = new FnStream(['write' => 'strlen']);
+ copy_to_stream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/DroppingStream.php b/instafeed/vendor/guzzlehttp/psr7/src/DroppingStream.php
new file mode 100755
index 0000000..8935c80
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/DroppingStream.php
@@ -0,0 +1,42 @@
+stream = $stream;
+ $this->maxLength = $maxLength;
+ }
+
+ public function write($string)
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/FnStream.php b/instafeed/vendor/guzzlehttp/psr7/src/FnStream.php
new file mode 100755
index 0000000..73daea6
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/FnStream.php
@@ -0,0 +1,158 @@
+methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_' . $name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ * @throws \BadMethodCallException
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ . '() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ call_user_func($this->_fn_close);
+ }
+ }
+
+ /**
+ * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
+ * @throws \LogicException
+ */
+ public function __wakeup()
+ {
+ throw new \LogicException('FnStream should never be unserialized');
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
+ $methods[$diff] = [$stream, $diff];
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString()
+ {
+ return call_user_func($this->_fn___toString);
+ }
+
+ public function close()
+ {
+ return call_user_func($this->_fn_close);
+ }
+
+ public function detach()
+ {
+ return call_user_func($this->_fn_detach);
+ }
+
+ public function getSize()
+ {
+ return call_user_func($this->_fn_getSize);
+ }
+
+ public function tell()
+ {
+ return call_user_func($this->_fn_tell);
+ }
+
+ public function eof()
+ {
+ return call_user_func($this->_fn_eof);
+ }
+
+ public function isSeekable()
+ {
+ return call_user_func($this->_fn_isSeekable);
+ }
+
+ public function rewind()
+ {
+ call_user_func($this->_fn_rewind);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ call_user_func($this->_fn_seek, $offset, $whence);
+ }
+
+ public function isWritable()
+ {
+ return call_user_func($this->_fn_isWritable);
+ }
+
+ public function write($string)
+ {
+ return call_user_func($this->_fn_write, $string);
+ }
+
+ public function isReadable()
+ {
+ return call_user_func($this->_fn_isReadable);
+ }
+
+ public function read($length)
+ {
+ return call_user_func($this->_fn_read, $length);
+ }
+
+ public function getContents()
+ {
+ return call_user_func($this->_fn_getContents);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return call_user_func($this->_fn_getMetadata, $key);
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/InflateStream.php b/instafeed/vendor/guzzlehttp/psr7/src/InflateStream.php
new file mode 100755
index 0000000..5e4f602
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/InflateStream.php
@@ -0,0 +1,52 @@
+read(10);
+ $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header);
+ // Skip the header, that is 10 + length of filename + 1 (nil) bytes
+ $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
+ $resource = StreamWrapper::getResource($stream);
+ stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
+ $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
+ }
+
+ /**
+ * @param StreamInterface $stream
+ * @param $header
+ * @return int
+ */
+ private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
+ {
+ $filename_header_length = 0;
+
+ if (substr(bin2hex($header), 6, 2) === '08') {
+ // we have a filename, read until nil
+ $filename_header_length = 1;
+ while ($stream->read(1) !== chr(0)) {
+ $filename_header_length++;
+ }
+ }
+
+ return $filename_header_length;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/instafeed/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
new file mode 100755
index 0000000..02cec3a
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
@@ -0,0 +1,39 @@
+filename = $filename;
+ $this->mode = $mode;
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream()
+ {
+ return stream_for(try_fopen($this->filename, $this->mode));
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/LimitStream.php b/instafeed/vendor/guzzlehttp/psr7/src/LimitStream.php
new file mode 100755
index 0000000..e4f239e
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/LimitStream.php
@@ -0,0 +1,155 @@
+stream = $stream;
+ $this->setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof()
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit == -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit == -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset %s with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset($offset)
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length)
+ {
+ if ($this->limit == -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/MessageTrait.php b/instafeed/vendor/guzzlehttp/psr7/src/MessageTrait.php
new file mode 100755
index 0000000..a7966d1
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/MessageTrait.php
@@ -0,0 +1,213 @@
+ array of values */
+ private $headers = [];
+
+ /** @var array Map of lowercase header name => original name at registration */
+ private $headerNames = [];
+
+ /** @var string */
+ private $protocol = '1.1';
+
+ /** @var StreamInterface */
+ private $stream;
+
+ public function getProtocolVersion()
+ {
+ return $this->protocol;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+ return $new;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ public function hasHeader($header)
+ {
+ return isset($this->headerNames[strtolower($header)]);
+ }
+
+ public function getHeader($header)
+ {
+ $header = strtolower($header);
+
+ if (!isset($this->headerNames[$header])) {
+ return [];
+ }
+
+ $header = $this->headerNames[$header];
+
+ return $this->headers[$header];
+ }
+
+ public function getHeaderLine($header)
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value)
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ unset($new->headers[$new->headerNames[$normalized]]);
+ }
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value)
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $new->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+ }
+
+ return $new;
+ }
+
+ public function withoutHeader($header)
+ {
+ $normalized = strtolower($header);
+
+ if (!isset($this->headerNames[$normalized])) {
+ return $this;
+ }
+
+ $header = $this->headerNames[$normalized];
+
+ $new = clone $this;
+ unset($new->headers[$header], $new->headerNames[$normalized]);
+
+ return $new;
+ }
+
+ public function getBody()
+ {
+ if (!$this->stream) {
+ $this->stream = stream_for('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body)
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+ return $new;
+ }
+
+ private function setHeaders(array $headers)
+ {
+ $this->headerNames = $this->headers = [];
+ foreach ($headers as $header => $value) {
+ if (is_int($header)) {
+ // Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec
+ // and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass.
+ $header = (string) $header;
+ }
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+ if (isset($this->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $this->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $this->headerNames[$normalized] = $header;
+ $this->headers[$header] = $value;
+ }
+ }
+ }
+
+ private function normalizeHeaderValue($value)
+ {
+ if (!is_array($value)) {
+ return $this->trimHeaderValues([$value]);
+ }
+
+ if (count($value) === 0) {
+ throw new \InvalidArgumentException('Header value can not be an empty array.');
+ }
+
+ return $this->trimHeaderValues($value);
+ }
+
+ /**
+ * Trims whitespace from the header values.
+ *
+ * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
+ *
+ * header-field = field-name ":" OWS field-value OWS
+ * OWS = *( SP / HTAB )
+ *
+ * @param string[] $values Header values
+ *
+ * @return string[] Trimmed header values
+ *
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ */
+ private function trimHeaderValues(array $values)
+ {
+ return array_map(function ($value) {
+ if (!is_scalar($value) && null !== $value) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header value must be scalar or null but %s provided.',
+ is_object($value) ? get_class($value) : gettype($value)
+ ));
+ }
+
+ return trim((string) $value, " \t");
+ }, $values);
+ }
+
+ private function assertHeader($header)
+ {
+ if (!is_string($header)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header name must be a string but %s provided.',
+ is_object($header) ? get_class($header) : gettype($header)
+ ));
+ }
+
+ if ($header === '') {
+ throw new \InvalidArgumentException('Header name can not be empty.');
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/MultipartStream.php b/instafeed/vendor/guzzlehttp/psr7/src/MultipartStream.php
new file mode 100755
index 0000000..c0fd584
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/MultipartStream.php
@@ -0,0 +1,153 @@
+boundary = $boundary ?: sha1(uniqid('', true));
+ $this->stream = $this->createStream($elements);
+ }
+
+ /**
+ * Get the boundary
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ */
+ private function getHeaders(array $headers)
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements)
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(stream_for("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element)
+ {
+ foreach (['contents', 'name'] as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = stream_for($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if (substr($uri, 0, 6) !== 'php://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ list($body, $headers) = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ isset($element['filename']) ? $element['filename'] : null,
+ isset($element['headers']) ? $element['headers'] : []
+ );
+
+ $stream->addStream(stream_for($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(stream_for("\r\n"));
+ }
+
+ /**
+ * @return array
+ */
+ private function createElement($name, StreamInterface $stream, $filename, array $headers)
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = $this->getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = ($filename === '0' || $filename)
+ ? sprintf('form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename))
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = $this->getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = $this->getHeader($headers, 'content-type');
+ if (!$type && ($filename === '0' || $filename)) {
+ if ($type = mimetype_from_filename($filename)) {
+ $headers['Content-Type'] = $type;
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ private function getHeader(array $headers, $key)
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower($k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/instafeed/vendor/guzzlehttp/psr7/src/NoSeekStream.php
new file mode 100755
index 0000000..2332218
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/NoSeekStream.php
@@ -0,0 +1,22 @@
+source = $source;
+ $this->size = isset($options['size']) ? $options['size'] : null;
+ $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString()
+ {
+ try {
+ return copy_to_string($this);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function close()
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = false;
+ $this->source = null;
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ return $this->tellPos;
+ }
+
+ public function eof()
+ {
+ return !$this->source;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents()
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
+ }
+
+ private function pump($length)
+ {
+ if ($this->source) {
+ do {
+ $data = call_user_func($this->source, $length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/Request.php b/instafeed/vendor/guzzlehttp/psr7/src/Request.php
new file mode 100755
index 0000000..59f337d
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/Request.php
@@ -0,0 +1,151 @@
+assertMethod($method);
+ if (!($uri instanceof UriInterface)) {
+ $uri = new Uri($uri);
+ }
+
+ $this->method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $version;
+
+ if (!isset($this->headerNames['host'])) {
+ $this->updateHostFromUri();
+ }
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+ }
+
+ public function getRequestTarget()
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target == '') {
+ $target = '/';
+ }
+ if ($this->uri->getQuery() != '') {
+ $target .= '?' . $this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget)
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+ return $new;
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method)
+ {
+ $this->assertMethod($method);
+ $new = clone $this;
+ $new->method = strtoupper($method);
+ return $new;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false)
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost || !isset($this->headerNames['host'])) {
+ $new->updateHostFromUri();
+ }
+
+ return $new;
+ }
+
+ private function updateHostFromUri()
+ {
+ $host = $this->uri->getHost();
+
+ if ($host == '') {
+ return;
+ }
+
+ if (($port = $this->uri->getPort()) !== null) {
+ $host .= ':' . $port;
+ }
+
+ if (isset($this->headerNames['host'])) {
+ $header = $this->headerNames['host'];
+ } else {
+ $header = 'Host';
+ $this->headerNames['host'] = 'Host';
+ }
+ // Ensure Host is the first header.
+ // See: http://tools.ietf.org/html/rfc7230#section-5.4
+ $this->headers = [$header => [$host]] + $this->headers;
+ }
+
+ private function assertMethod($method)
+ {
+ if (!is_string($method) || $method === '') {
+ throw new \InvalidArgumentException('Method must be a non-empty string.');
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/Response.php b/instafeed/vendor/guzzlehttp/psr7/src/Response.php
new file mode 100755
index 0000000..e7e04d8
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/Response.php
@@ -0,0 +1,154 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 511 => 'Network Authentication Required',
+ ];
+
+ /** @var string */
+ private $reasonPhrase = '';
+
+ /** @var int */
+ private $statusCode = 200;
+
+ /**
+ * @param int $status Status code
+ * @param array $headers Response headers
+ * @param string|null|resource|StreamInterface $body Response body
+ * @param string $version Protocol version
+ * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
+ */
+ public function __construct(
+ $status = 200,
+ array $headers = [],
+ $body = null,
+ $version = '1.1',
+ $reason = null
+ ) {
+ $this->assertStatusCodeIsInteger($status);
+ $status = (int) $status;
+ $this->assertStatusCodeRange($status);
+
+ $this->statusCode = $status;
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+
+ $this->setHeaders($headers);
+ if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
+ $this->reasonPhrase = self::$phrases[$this->statusCode];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ $this->assertStatusCodeIsInteger($code);
+ $code = (int) $code;
+ $this->assertStatusCodeRange($code);
+
+ $new = clone $this;
+ $new->statusCode = $code;
+ if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
+ $reasonPhrase = self::$phrases[$new->statusCode];
+ }
+ $new->reasonPhrase = $reasonPhrase;
+ return $new;
+ }
+
+ private function assertStatusCodeIsInteger($statusCode)
+ {
+ if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) {
+ throw new \InvalidArgumentException('Status code must be an integer value.');
+ }
+ }
+
+ private function assertStatusCodeRange($statusCode)
+ {
+ if ($statusCode < 100 || $statusCode >= 600) {
+ throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.');
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/Rfc7230.php b/instafeed/vendor/guzzlehttp/psr7/src/Rfc7230.php
new file mode 100755
index 0000000..505e474
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/Rfc7230.php
@@ -0,0 +1,18 @@
+@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
+ const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/ServerRequest.php b/instafeed/vendor/guzzlehttp/psr7/src/ServerRequest.php
new file mode 100755
index 0000000..1a09a6c
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/ServerRequest.php
@@ -0,0 +1,376 @@
+serverParams = $serverParams;
+
+ parent::__construct($method, $uri, $headers, $body, $version);
+ }
+
+ /**
+ * Return an UploadedFile instance array.
+ *
+ * @param array $files A array which respect $_FILES structure
+ * @throws InvalidArgumentException for unrecognized values
+ * @return array
+ */
+ public static function normalizeFiles(array $files)
+ {
+ $normalized = [];
+
+ foreach ($files as $key => $value) {
+ if ($value instanceof UploadedFileInterface) {
+ $normalized[$key] = $value;
+ } elseif (is_array($value) && isset($value['tmp_name'])) {
+ $normalized[$key] = self::createUploadedFileFromSpec($value);
+ } elseif (is_array($value)) {
+ $normalized[$key] = self::normalizeFiles($value);
+ continue;
+ } else {
+ throw new InvalidArgumentException('Invalid value in files specification');
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Create and return an UploadedFile instance from a $_FILES specification.
+ *
+ * If the specification represents an array of values, this method will
+ * delegate to normalizeNestedFileSpec() and return that return value.
+ *
+ * @param array $value $_FILES struct
+ * @return array|UploadedFileInterface
+ */
+ private static function createUploadedFileFromSpec(array $value)
+ {
+ if (is_array($value['tmp_name'])) {
+ return self::normalizeNestedFileSpec($value);
+ }
+
+ return new UploadedFile(
+ $value['tmp_name'],
+ (int) $value['size'],
+ (int) $value['error'],
+ $value['name'],
+ $value['type']
+ );
+ }
+
+ /**
+ * Normalize an array of file specifications.
+ *
+ * Loops through all nested files and returns a normalized array of
+ * UploadedFileInterface instances.
+ *
+ * @param array $files
+ * @return UploadedFileInterface[]
+ */
+ private static function normalizeNestedFileSpec(array $files = [])
+ {
+ $normalizedFiles = [];
+
+ foreach (array_keys($files['tmp_name']) as $key) {
+ $spec = [
+ 'tmp_name' => $files['tmp_name'][$key],
+ 'size' => $files['size'][$key],
+ 'error' => $files['error'][$key],
+ 'name' => $files['name'][$key],
+ 'type' => $files['type'][$key],
+ ];
+ $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
+ }
+
+ return $normalizedFiles;
+ }
+
+ /**
+ * Return a ServerRequest populated with superglobals:
+ * $_GET
+ * $_POST
+ * $_COOKIE
+ * $_FILES
+ * $_SERVER
+ *
+ * @return ServerRequestInterface
+ */
+ public static function fromGlobals()
+ {
+ $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
+ $headers = getallheaders();
+ $uri = self::getUriFromGlobals();
+ $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+ $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
+
+ $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
+
+ return $serverRequest
+ ->withCookieParams($_COOKIE)
+ ->withQueryParams($_GET)
+ ->withParsedBody($_POST)
+ ->withUploadedFiles(self::normalizeFiles($_FILES));
+ }
+
+ private static function extractHostAndPortFromAuthority($authority)
+ {
+ $uri = 'http://'.$authority;
+ $parts = parse_url($uri);
+ if (false === $parts) {
+ return [null, null];
+ }
+
+ $host = isset($parts['host']) ? $parts['host'] : null;
+ $port = isset($parts['port']) ? $parts['port'] : null;
+
+ return [$host, $port];
+ }
+
+ /**
+ * Get a Uri populated with values from $_SERVER.
+ *
+ * @return UriInterface
+ */
+ public static function getUriFromGlobals()
+ {
+ $uri = new Uri('');
+
+ $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
+
+ $hasPort = false;
+ if (isset($_SERVER['HTTP_HOST'])) {
+ list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
+ if ($host !== null) {
+ $uri = $uri->withHost($host);
+ }
+
+ if ($port !== null) {
+ $hasPort = true;
+ $uri = $uri->withPort($port);
+ }
+ } elseif (isset($_SERVER['SERVER_NAME'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_NAME']);
+ } elseif (isset($_SERVER['SERVER_ADDR'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
+ }
+
+ if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
+ $uri = $uri->withPort($_SERVER['SERVER_PORT']);
+ }
+
+ $hasQuery = false;
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
+ $uri = $uri->withPath($requestUriParts[0]);
+ if (isset($requestUriParts[1])) {
+ $hasQuery = true;
+ $uri = $uri->withQuery($requestUriParts[1]);
+ }
+ }
+
+ if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
+ $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
+ }
+
+ return $uri;
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getServerParams()
+ {
+ return $this->serverParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getUploadedFiles()
+ {
+ return $this->uploadedFiles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->uploadedFiles = $uploadedFiles;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCookieParams()
+ {
+ return $this->cookieParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->cookieParams = $cookies;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttribute($attribute, $default = null)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $default;
+ }
+
+ return $this->attributes[$attribute];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withAttribute($attribute, $value)
+ {
+ $new = clone $this;
+ $new->attributes[$attribute] = $value;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withoutAttribute($attribute)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ unset($new->attributes[$attribute]);
+
+ return $new;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/Stream.php b/instafeed/vendor/guzzlehttp/psr7/src/Stream.php
new file mode 100755
index 0000000..d9e7409
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/Stream.php
@@ -0,0 +1,267 @@
+size = $options['size'];
+ }
+
+ $this->customMetadata = isset($options['metadata'])
+ ? $options['metadata']
+ : [];
+
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']);
+ $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return (string) stream_get_contents($this->stream);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $contents = stream_get_contents($this->stream);
+
+ if ($contents === false) {
+ throw new \RuntimeException('Unable to read stream contents');
+ }
+
+ return $contents;
+ }
+
+ public function close()
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function eof()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ return feof($this->stream);
+ }
+
+ public function tell()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $whence = (int) $whence;
+
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ }
+ if (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ . $offset . ' with whence ' . var_export($whence, true));
+ }
+ }
+
+ public function read($length)
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+ if ($length < 0) {
+ throw new \RuntimeException('Length parameter cannot be negative');
+ }
+
+ if (0 === $length) {
+ return '';
+ }
+
+ $string = fread($this->stream, $length);
+ if (false === $string) {
+ throw new \RuntimeException('Unable to read from stream');
+ }
+
+ return $string;
+ }
+
+ public function write($string)
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : [];
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/instafeed/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
new file mode 100755
index 0000000..daec6f5
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,149 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @param string $name Name of the property (allows "stream" only).
+ *
+ * @return StreamInterface
+ */
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ $this->stream = $this->createStream();
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString()
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+ return $this->getContents();
+ } catch (\Exception $e) {
+ // Really, PHP? https://bugs.php.net/bug.php?id=53648
+ trigger_error('StreamDecorator::__toString exception: '
+ . (string) $e, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ $result = call_user_func_array([$this->stream, $method], $args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close()
+ {
+ $this->stream->close();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize()
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable()
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->stream->write($string);
+ }
+
+ /**
+ * Implement in subclasses to dynamically create streams when requested.
+ *
+ * @return StreamInterface
+ * @throws \BadMethodCallException
+ */
+ protected function createStream()
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/instafeed/vendor/guzzlehttp/psr7/src/StreamWrapper.php
new file mode 100755
index 0000000..0f3a285
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/StreamWrapper.php
@@ -0,0 +1,161 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ . 'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, null, self::createStreamContext($stream));
+ }
+
+ /**
+ * Creates a stream context that can be used to open a stream as a php stream resource.
+ *
+ * @param StreamInterface $stream
+ *
+ * @return resource
+ */
+ public static function createStreamContext(StreamInterface $stream)
+ {
+ return stream_context_create([
+ 'guzzle' => ['stream' => $stream]
+ ]);
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register()
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read($count)
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write($data)
+ {
+ return (int) $this->stream->write($data);
+ }
+
+ public function stream_tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ public function stream_cast($cast_as)
+ {
+ $stream = clone($this->stream);
+
+ return $stream->detach();
+ }
+
+ public function stream_stat()
+ {
+ static $modeMap = [
+ 'r' => 33060,
+ 'rb' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188,
+ 'wb' => 33188
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+
+ public function url_stat($path, $flags)
+ {
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/UploadedFile.php b/instafeed/vendor/guzzlehttp/psr7/src/UploadedFile.php
new file mode 100755
index 0000000..e62bd5c
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/UploadedFile.php
@@ -0,0 +1,316 @@
+setError($errorStatus);
+ $this->setSize($size);
+ $this->setClientFilename($clientFilename);
+ $this->setClientMediaType($clientMediaType);
+
+ if ($this->isOk()) {
+ $this->setStreamOrFile($streamOrFile);
+ }
+ }
+
+ /**
+ * Depending on the value set file or stream variable
+ *
+ * @param mixed $streamOrFile
+ * @throws InvalidArgumentException
+ */
+ private function setStreamOrFile($streamOrFile)
+ {
+ if (is_string($streamOrFile)) {
+ $this->file = $streamOrFile;
+ } elseif (is_resource($streamOrFile)) {
+ $this->stream = new Stream($streamOrFile);
+ } elseif ($streamOrFile instanceof StreamInterface) {
+ $this->stream = $streamOrFile;
+ } else {
+ throw new InvalidArgumentException(
+ 'Invalid stream or file provided for UploadedFile'
+ );
+ }
+ }
+
+ /**
+ * @param int $error
+ * @throws InvalidArgumentException
+ */
+ private function setError($error)
+ {
+ if (false === is_int($error)) {
+ throw new InvalidArgumentException(
+ 'Upload file error status must be an integer'
+ );
+ }
+
+ if (false === in_array($error, UploadedFile::$errors)) {
+ throw new InvalidArgumentException(
+ 'Invalid error status for UploadedFile'
+ );
+ }
+
+ $this->error = $error;
+ }
+
+ /**
+ * @param int $size
+ * @throws InvalidArgumentException
+ */
+ private function setSize($size)
+ {
+ if (false === is_int($size)) {
+ throw new InvalidArgumentException(
+ 'Upload file size must be an integer'
+ );
+ }
+
+ $this->size = $size;
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringOrNull($param)
+ {
+ return in_array(gettype($param), ['string', 'NULL']);
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringNotEmpty($param)
+ {
+ return is_string($param) && false === empty($param);
+ }
+
+ /**
+ * @param string|null $clientFilename
+ * @throws InvalidArgumentException
+ */
+ private function setClientFilename($clientFilename)
+ {
+ if (false === $this->isStringOrNull($clientFilename)) {
+ throw new InvalidArgumentException(
+ 'Upload file client filename must be a string or null'
+ );
+ }
+
+ $this->clientFilename = $clientFilename;
+ }
+
+ /**
+ * @param string|null $clientMediaType
+ * @throws InvalidArgumentException
+ */
+ private function setClientMediaType($clientMediaType)
+ {
+ if (false === $this->isStringOrNull($clientMediaType)) {
+ throw new InvalidArgumentException(
+ 'Upload file client media type must be a string or null'
+ );
+ }
+
+ $this->clientMediaType = $clientMediaType;
+ }
+
+ /**
+ * Return true if there is no upload error
+ *
+ * @return boolean
+ */
+ private function isOk()
+ {
+ return $this->error === UPLOAD_ERR_OK;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isMoved()
+ {
+ return $this->moved;
+ }
+
+ /**
+ * @throws RuntimeException if is moved or not ok
+ */
+ private function validateActive()
+ {
+ if (false === $this->isOk()) {
+ throw new RuntimeException('Cannot retrieve stream due to upload error');
+ }
+
+ if ($this->isMoved()) {
+ throw new RuntimeException('Cannot retrieve stream after it has already been moved');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws RuntimeException if the upload was not successful.
+ */
+ public function getStream()
+ {
+ $this->validateActive();
+
+ if ($this->stream instanceof StreamInterface) {
+ return $this->stream;
+ }
+
+ return new LazyOpenStream($this->file, 'r+');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/is_uploaded_file
+ * @see http://php.net/move_uploaded_file
+ * @param string $targetPath Path to which to move the uploaded file.
+ * @throws RuntimeException if the upload was not successful.
+ * @throws InvalidArgumentException if the $path specified is invalid.
+ * @throws RuntimeException on any error during the move operation, or on
+ * the second or subsequent call to the method.
+ */
+ public function moveTo($targetPath)
+ {
+ $this->validateActive();
+
+ if (false === $this->isStringNotEmpty($targetPath)) {
+ throw new InvalidArgumentException(
+ 'Invalid path provided for move operation; must be a non-empty string'
+ );
+ }
+
+ if ($this->file) {
+ $this->moved = php_sapi_name() == 'cli'
+ ? rename($this->file, $targetPath)
+ : move_uploaded_file($this->file, $targetPath);
+ } else {
+ copy_to_stream(
+ $this->getStream(),
+ new LazyOpenStream($targetPath, 'w')
+ );
+
+ $this->moved = true;
+ }
+
+ if (false === $this->moved) {
+ throw new RuntimeException(
+ sprintf('Uploaded file could not be moved to %s', $targetPath)
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return int|null The file size in bytes or null if unknown.
+ */
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/manual/en/features.file-upload.errors.php
+ * @return int One of PHP's UPLOAD_ERR_XXX constants.
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return string|null The filename sent by the client or null if none
+ * was provided.
+ */
+ public function getClientFilename()
+ {
+ return $this->clientFilename;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getClientMediaType()
+ {
+ return $this->clientMediaType;
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/Uri.php b/instafeed/vendor/guzzlehttp/psr7/src/Uri.php
new file mode 100755
index 0000000..825a25e
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/Uri.php
@@ -0,0 +1,760 @@
+ 80,
+ 'https' => 443,
+ 'ftp' => 21,
+ 'gopher' => 70,
+ 'nntp' => 119,
+ 'news' => 119,
+ 'telnet' => 23,
+ 'tn3270' => 23,
+ 'imap' => 143,
+ 'pop' => 110,
+ 'ldap' => 389,
+ ];
+
+ private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
+ private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
+ private static $replaceQuery = ['=' => '%3D', '&' => '%26'];
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /**
+ * @param string $uri URI to parse
+ */
+ public function __construct($uri = '')
+ {
+ // weak type check to also accept null until we can add scalar type hints
+ if ($uri != '') {
+ $parts = parse_url($uri);
+ if ($parts === false) {
+ throw new \InvalidArgumentException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ public function __toString()
+ {
+ return self::composeComponents(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->path,
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ /**
+ * Composes a URI reference string from its various components.
+ *
+ * Usually this method does not need to be called manually but instead is used indirectly via
+ * `Psr\Http\Message\UriInterface::__toString`.
+ *
+ * PSR-7 UriInterface treats an empty component the same as a missing component as
+ * getQuery(), getFragment() etc. always return a string. This explains the slight
+ * difference to RFC 3986 Section 5.3.
+ *
+ * Another adjustment is that the authority separator is added even when the authority is missing/empty
+ * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
+ * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
+ * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
+ * that format).
+ *
+ * @param string $scheme
+ * @param string $authority
+ * @param string $path
+ * @param string $query
+ * @param string $fragment
+ *
+ * @return string
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-5.3
+ */
+ public static function composeComponents($scheme, $authority, $path, $query, $fragment)
+ {
+ $uri = '';
+
+ // weak type checks to also accept null until we can add scalar type hints
+ if ($scheme != '') {
+ $uri .= $scheme . ':';
+ }
+
+ if ($authority != ''|| $scheme === 'file') {
+ $uri .= '//' . $authority;
+ }
+
+ $uri .= $path;
+
+ if ($query != '') {
+ $uri .= '?' . $query;
+ }
+
+ if ($fragment != '') {
+ $uri .= '#' . $fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether the URI has the default port of the current scheme.
+ *
+ * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
+ * independently of the implementation.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ */
+ public static function isDefaultPort(UriInterface $uri)
+ {
+ return $uri->getPort() === null
+ || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]);
+ }
+
+ /**
+ * Whether the URI is absolute, i.e. it has a scheme.
+ *
+ * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
+ * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
+ * to another URI, the base URI. Relative references can be divided into several forms:
+ * - network-path references, e.g. '//example.com/path'
+ * - absolute-path references, e.g. '/path'
+ * - relative-path references, e.g. 'subpath'
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @see Uri::isNetworkPathReference
+ * @see Uri::isAbsolutePathReference
+ * @see Uri::isRelativePathReference
+ * @link https://tools.ietf.org/html/rfc3986#section-4
+ */
+ public static function isAbsolute(UriInterface $uri)
+ {
+ return $uri->getScheme() !== '';
+ }
+
+ /**
+ * Whether the URI is a network-path reference.
+ *
+ * A relative reference that begins with two slash characters is termed an network-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isNetworkPathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === '' && $uri->getAuthority() !== '';
+ }
+
+ /**
+ * Whether the URI is a absolute-path reference.
+ *
+ * A relative reference that begins with a single slash character is termed an absolute-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isAbsolutePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && isset($uri->getPath()[0])
+ && $uri->getPath()[0] === '/';
+ }
+
+ /**
+ * Whether the URI is a relative-path reference.
+ *
+ * A relative reference that does not begin with a slash character is termed a relative-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isRelativePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
+ }
+
+ /**
+ * Whether the URI is a same-document reference.
+ *
+ * A same-document reference refers to a URI that is, aside from its fragment
+ * component, identical to the base URI. When no base URI is given, only an empty
+ * URI reference (apart from its fragment) is considered a same-document reference.
+ *
+ * @param UriInterface $uri The URI to check
+ * @param UriInterface|null $base An optional base URI to compare against
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.4
+ */
+ public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
+ {
+ if ($base !== null) {
+ $uri = UriResolver::resolve($base, $uri);
+
+ return ($uri->getScheme() === $base->getScheme())
+ && ($uri->getAuthority() === $base->getAuthority())
+ && ($uri->getPath() === $base->getPath())
+ && ($uri->getQuery() === $base->getQuery());
+ }
+
+ return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
+ }
+
+ /**
+ * Removes dot segments from a path and returns the new path.
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
+ * @see UriResolver::removeDotSegments
+ */
+ public static function removeDotSegments($path)
+ {
+ return UriResolver::removeDotSegments($path);
+ }
+
+ /**
+ * Converts the relative URI into a new URI that is resolved against the base URI.
+ *
+ * @param UriInterface $base Base URI
+ * @param string|UriInterface $rel Relative URI
+ *
+ * @return UriInterface
+ *
+ * @deprecated since version 1.4. Use UriResolver::resolve instead.
+ * @see UriResolver::resolve
+ */
+ public static function resolve(UriInterface $base, $rel)
+ {
+ if (!($rel instanceof UriInterface)) {
+ $rel = new self($rel);
+ }
+
+ return UriResolver::resolve($base, $rel);
+ }
+
+ /**
+ * Creates a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key to remove.
+ *
+ * @return UriInterface
+ */
+ public static function withoutQueryValue(UriInterface $uri, $key)
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * A value of null will set the query string key without a value, e.g. "key"
+ * instead of "key=value".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string|null $value Value to set
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValue(UriInterface $uri, $key, $value)
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ $result[] = self::generateQueryString($key, $value);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with multiple specific query string values.
+ *
+ * It has the same behavior as withQueryValue() but for an associative array of key => value.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param array $keyValueArray Associative array of key and values
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValues(UriInterface $uri, array $keyValueArray)
+ {
+ $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
+
+ foreach ($keyValueArray as $key => $value) {
+ $result[] = self::generateQueryString($key, $value);
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a URI from a hash of `parse_url` components.
+ *
+ * @param array $parts
+ *
+ * @return UriInterface
+ * @link http://php.net/manual/en/function.parse-url.php
+ *
+ * @throws \InvalidArgumentException If the components do not form a valid URI.
+ */
+ public static function fromParts(array $parts)
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ $uri->validateState();
+
+ return $uri;
+ }
+
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority()
+ {
+ $authority = $this->host;
+ if ($this->userInfo !== '') {
+ $authority = $this->userInfo . '@' . $authority;
+ }
+
+ if ($this->port !== null) {
+ $authority .= ':' . $this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo()
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme)
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null)
+ {
+ $info = $this->filterUserInfoComponent($user);
+ if ($password !== null) {
+ $info .= ':' . $this->filterUserInfoComponent($password);
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withHost($host)
+ {
+ $host = $this->filterHost($host);
+
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPort($port)
+ {
+ $port = $this->filterPort($port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPath($path)
+ {
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withQuery($query)
+ {
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+
+ return $new;
+ }
+
+ public function withFragment($fragment)
+ {
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+
+ return $new;
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param array $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts)
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user'])
+ ? $this->filterUserInfoComponent($parts['user'])
+ : '';
+ $this->host = isset($parts['host'])
+ ? $this->filterHost($parts['host'])
+ : '';
+ $this->port = isset($parts['port'])
+ ? $this->filterPort($parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
+ }
+
+ $this->removeDefaultPort();
+ }
+
+ /**
+ * @param string $scheme
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the scheme is invalid.
+ */
+ private function filterScheme($scheme)
+ {
+ if (!is_string($scheme)) {
+ throw new \InvalidArgumentException('Scheme must be a string');
+ }
+
+ return strtolower($scheme);
+ }
+
+ /**
+ * @param string $component
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the user info is invalid.
+ */
+ private function filterUserInfoComponent($component)
+ {
+ if (!is_string($component)) {
+ throw new \InvalidArgumentException('User info must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $component
+ );
+ }
+
+ /**
+ * @param string $host
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the host is invalid.
+ */
+ private function filterHost($host)
+ {
+ if (!is_string($host)) {
+ throw new \InvalidArgumentException('Host must be a string');
+ }
+
+ return strtolower($host);
+ }
+
+ /**
+ * @param int|null $port
+ *
+ * @return int|null
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($port)
+ {
+ if ($port === null) {
+ return null;
+ }
+
+ $port = (int) $port;
+ if (0 > $port || 0xffff < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
+ );
+ }
+
+ return $port;
+ }
+
+ /**
+ * @param UriInterface $uri
+ * @param array $keys
+ *
+ * @return array
+ */
+ private static function getFilteredQueryString(UriInterface $uri, array $keys)
+ {
+ $current = $uri->getQuery();
+
+ if ($current === '') {
+ return [];
+ }
+
+ $decodedKeys = array_map('rawurldecode', $keys);
+
+ return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
+ return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
+ });
+ }
+
+ /**
+ * @param string $key
+ * @param string|null $value
+ *
+ * @return string
+ */
+ private static function generateQueryString($key, $value)
+ {
+ // Query string separators ("=", "&") within the key or value need to be encoded
+ // (while preventing double-encoding) before setting the query string. All other
+ // chars that need percent-encoding will be encoded by withQuery().
+ $queryString = strtr($key, self::$replaceQuery);
+
+ if ($value !== null) {
+ $queryString .= '=' . strtr($value, self::$replaceQuery);
+ }
+
+ return $queryString;
+ }
+
+ private function removeDefaultPort()
+ {
+ if ($this->port !== null && self::isDefaultPort($this)) {
+ $this->port = null;
+ }
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the path is invalid.
+ */
+ private function filterPath($path)
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException('Path must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param string $str
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the query or fragment is invalid.
+ */
+ private function filterQueryAndFragment($str)
+ {
+ if (!is_string($str)) {
+ throw new \InvalidArgumentException('Query and fragment must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match)
+ {
+ return rawurlencode($match[0]);
+ }
+
+ private function validateState()
+ {
+ if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
+ $this->host = self::HTTP_DEFAULT_HOST;
+ }
+
+ if ($this->getAuthority() === '') {
+ if (0 === strpos($this->path, '//')) {
+ throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
+ }
+ if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
+ throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
+ }
+ } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
+ @trigger_error(
+ 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
+ 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
+ E_USER_DEPRECATED
+ );
+ $this->path = '/'. $this->path;
+ //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
+ }
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/instafeed/vendor/guzzlehttp/psr7/src/UriNormalizer.php
new file mode 100755
index 0000000..384c29e
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/UriNormalizer.php
@@ -0,0 +1,216 @@
+getPath() === '' &&
+ ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
+ ) {
+ $uri = $uri->withPath('/');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
+ $uri = $uri->withHost('');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
+ $uri = $uri->withPort(null);
+ }
+
+ if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
+ $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
+ }
+
+ if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
+ $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
+ }
+
+ if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
+ $queryKeyValues = explode('&', $uri->getQuery());
+ sort($queryKeyValues);
+ $uri = $uri->withQuery(implode('&', $queryKeyValues));
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether two URIs can be considered equivalent.
+ *
+ * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
+ * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
+ * resolved against the same base URI. If this is not the case, determination of equivalence or difference of
+ * relative references does not mean anything.
+ *
+ * @param UriInterface $uri1 An URI to compare
+ * @param UriInterface $uri2 An URI to compare
+ * @param int $normalizations A bitmask of normalizations to apply, see constants
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-6.1
+ */
+ public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS)
+ {
+ return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
+ }
+
+ private static function capitalizePercentEncoding(UriInterface $uri)
+ {
+ $regex = '/(?:%[A-Fa-f0-9]{2})++/';
+
+ $callback = function (array $match) {
+ return strtoupper($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private static function decodeUnreservedCharacters(UriInterface $uri)
+ {
+ $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
+
+ $callback = function (array $match) {
+ return rawurldecode($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/UriResolver.php b/instafeed/vendor/guzzlehttp/psr7/src/UriResolver.php
new file mode 100755
index 0000000..c1cb8a2
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/UriResolver.php
@@ -0,0 +1,219 @@
+getScheme() != '') {
+ return $rel->withPath(self::removeDotSegments($rel->getPath()));
+ }
+
+ if ($rel->getAuthority() != '') {
+ $targetAuthority = $rel->getAuthority();
+ $targetPath = self::removeDotSegments($rel->getPath());
+ $targetQuery = $rel->getQuery();
+ } else {
+ $targetAuthority = $base->getAuthority();
+ if ($rel->getPath() === '') {
+ $targetPath = $base->getPath();
+ $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
+ } else {
+ if ($rel->getPath()[0] === '/') {
+ $targetPath = $rel->getPath();
+ } else {
+ if ($targetAuthority != '' && $base->getPath() === '') {
+ $targetPath = '/' . $rel->getPath();
+ } else {
+ $lastSlashPos = strrpos($base->getPath(), '/');
+ if ($lastSlashPos === false) {
+ $targetPath = $rel->getPath();
+ } else {
+ $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
+ }
+ }
+ }
+ $targetPath = self::removeDotSegments($targetPath);
+ $targetQuery = $rel->getQuery();
+ }
+ }
+
+ return new Uri(Uri::composeComponents(
+ $base->getScheme(),
+ $targetAuthority,
+ $targetPath,
+ $targetQuery,
+ $rel->getFragment()
+ ));
+ }
+
+ /**
+ * Returns the target URI as a relative reference from the base URI.
+ *
+ * This method is the counterpart to resolve():
+ *
+ * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+ *
+ * One use-case is to use the current request URI as base URI and then generate relative links in your documents
+ * to reduce the document size or offer self-contained downloadable document archives.
+ *
+ * $base = new Uri('http://example.com/a/b/');
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+ * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+ *
+ * This method also accepts a target that is already relative and will try to relativize it further. Only a
+ * relative-path reference will be returned as-is.
+ *
+ * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
+ *
+ * @param UriInterface $base Base URI
+ * @param UriInterface $target Target URI
+ *
+ * @return UriInterface The relative URI reference
+ */
+ public static function relativize(UriInterface $base, UriInterface $target)
+ {
+ if ($target->getScheme() !== '' &&
+ ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
+ ) {
+ return $target;
+ }
+
+ if (Uri::isRelativePathReference($target)) {
+ // As the target is already highly relative we return it as-is. It would be possible to resolve
+ // the target with `$target = self::resolve($base, $target);` and then try make it more relative
+ // by removing a duplicate query. But let's not do that automatically.
+ return $target;
+ }
+
+ if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
+ return $target->withScheme('');
+ }
+
+ // We must remove the path before removing the authority because if the path starts with two slashes, the URI
+ // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
+ // invalid.
+ $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
+
+ if ($base->getPath() !== $target->getPath()) {
+ return $emptyPathUri->withPath(self::getRelativePath($base, $target));
+ }
+
+ if ($base->getQuery() === $target->getQuery()) {
+ // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
+ return $emptyPathUri->withQuery('');
+ }
+
+ // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
+ // inherit the base query component when resolving.
+ if ($target->getQuery() === '') {
+ $segments = explode('/', $target->getPath());
+ $lastSegment = end($segments);
+
+ return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
+ }
+
+ return $emptyPathUri;
+ }
+
+ private static function getRelativePath(UriInterface $base, UriInterface $target)
+ {
+ $sourceSegments = explode('/', $base->getPath());
+ $targetSegments = explode('/', $target->getPath());
+ array_pop($sourceSegments);
+ $targetLastSegment = array_pop($targetSegments);
+ foreach ($sourceSegments as $i => $segment) {
+ if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
+ unset($sourceSegments[$i], $targetSegments[$i]);
+ } else {
+ break;
+ }
+ }
+ $targetSegments[] = $targetLastSegment;
+ $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);
+
+ // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
+ if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
+ $relativePath = "./$relativePath";
+ } elseif ('/' === $relativePath[0]) {
+ if ($base->getAuthority() != '' && $base->getPath() === '') {
+ // In this case an extra slash is added by resolve() automatically. So we must not add one here.
+ $relativePath = ".$relativePath";
+ } else {
+ $relativePath = "./$relativePath";
+ }
+ }
+
+ return $relativePath;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/functions.php b/instafeed/vendor/guzzlehttp/psr7/src/functions.php
new file mode 100755
index 0000000..8e6dafe
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/functions.php
@@ -0,0 +1,899 @@
+getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+}
+
+/**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
+ * returns a UriInterface for the given value. If the value is already a
+ * `UriInterface`, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @return UriInterface
+ * @throws \InvalidArgumentException
+ */
+function uri_for($uri)
+{
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ } elseif (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+}
+
+/**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return StreamInterface
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+function stream_for($resource = '', array $options = [])
+{
+ if (is_scalar($resource)) {
+ $stream = fopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new Stream($stream, $options);
+ }
+
+ switch (gettype($resource)) {
+ case 'resource':
+ return new Stream($resource, $options);
+ case 'object':
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return stream_for((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(fopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
+}
+
+/**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair
+ * data of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ *
+ * @return array Returns the parsed header values.
+ */
+function parse_header($header)
+{
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = [];
+
+ foreach (normalize_header($header) as $val) {
+ $part = [];
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+function normalize_header($header)
+{
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = [];
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Clone and modify a request with the given changes.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+function modify_request(RequestInterface $request, array $changes)
+{
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+
+ if ($port = $changes['uri']->getPort()) {
+ $standardPorts = ['http' => 80, 'https' => 443];
+ $scheme = $changes['uri']->getScheme();
+ if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
+ $changes['set_headers']['Host'] .= ':'.$port;
+ }
+ }
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = _caseless_remove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ if ($request instanceof ServerRequestInterface) {
+ return (new ServerRequest(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion(),
+ $request->getServerParams()
+ ))
+ ->withParsedBody($request->getParsedBody())
+ ->withQueryParams($request->getQueryParams())
+ ->withCookieParams($request->getCookieParams())
+ ->withUploadedFiles($request->getUploadedFiles());
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+}
+
+/**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()` returns a
+ * value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+function rewind_body(MessageInterface $message)
+{
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+}
+
+/**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ * @throws \RuntimeException if the file cannot be opened
+ */
+function try_fopen($filename, $mode)
+{
+ $ex = null;
+ set_error_handler(function () use ($filename, $mode, &$ex) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open %s using mode %s: %s',
+ $filename,
+ $mode,
+ func_get_args()[1]
+ ));
+ });
+
+ $handle = fopen($filename, $mode);
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+}
+
+/**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ * @return string
+ * @throws \RuntimeException on error.
+ */
+function copy_to_string(StreamInterface $stream, $maxLen = -1)
+{
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+}
+
+/**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+function copy_to_stream(
+ StreamInterface $source,
+ StreamInterface $dest,
+ $maxLen = -1
+) {
+ $bufferSize = 8192;
+
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read($bufferSize))) {
+ break;
+ }
+ }
+ } else {
+ $remaining = $maxLen;
+ while ($remaining > 0 && !$source->eof()) {
+ $buf = $source->read(min($bufferSize, $remaining));
+ $len = strlen($buf);
+ if (!$len) {
+ break;
+ }
+ $remaining -= $len;
+ $dest->write($buf);
+ }
+ }
+}
+
+/**
+ * Calculate a hash of a Stream
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ * @throws \RuntimeException on error.
+ */
+function hash(
+ StreamInterface $stream,
+ $algo,
+ $rawOutput = false
+) {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+}
+
+/**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string
+ */
+function readline(StreamInterface $stream, $maxLength = null)
+{
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ // Using a loose equality here to match on '' and false.
+ if (null == ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte === "\n" || ++$size === $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+}
+
+/**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ *
+ * @return Request
+ */
+function parse_request($message)
+{
+ $data = _parse_message($message);
+ $matches = [];
+ if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+}
+
+/**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ *
+ * @return Response
+ */
+function parse_response($message)
+{
+ $data = _parse_message($message);
+ // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
+ // between status-code and reason-phrase is required. But browsers accept
+ // responses without space and reason as well.
+ if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+
+ return new Response(
+ $parts[1],
+ $data['headers'],
+ $data['body'],
+ explode('/', $parts[0])[1],
+ isset($parts[2]) ? $parts[2] : null
+ );
+}
+
+/**
+ * Parse a query string into an associative array.
+ *
+ * If multiple values are found for the same key, the value of that key
+ * value pair will become an array. This function does not parse nested
+ * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
+ * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
+ *
+ * @param string $str Query string to parse
+ * @param int|bool $urlEncoding How the query string is encoded
+ *
+ * @return array
+ */
+function parse_query($str, $urlEncoding = true)
+{
+ $result = [];
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', $value));
+ };
+ } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) { return $str; };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!isset($result[$key])) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = [$result[$key]];
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of parse_query() to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like http_build_query would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
+ * to encode using RFC3986, or PHP_QUERY_RFC1738
+ * to encode using RFC1738.
+ * @return string
+ */
+function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
+{
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function ($str) { return $str; };
+ } elseif ($encoding === PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding === PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder($k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ if ($v !== null) {
+ $qs .= '=' . $encoder($v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ if ($vv !== null) {
+ $qs .= '=' . $encoder($vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+}
+
+/**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @param $filename
+ *
+ * @return null|string
+ */
+function mimetype_from_filename($filename)
+{
+ return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
+}
+
+/**
+ * Maps a file extensions to a mimetype.
+ *
+ * @param $extension string The file extension.
+ *
+ * @return string|null
+ * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
+ */
+function mimetype_from_extension($extension)
+{
+ static $mimetypes = [
+ '3gp' => 'video/3gpp',
+ '7z' => 'application/x-7z-compressed',
+ 'aac' => 'audio/x-aac',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'asc' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'atom' => 'application/atom+xml',
+ 'avi' => 'video/x-msvideo',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip2',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'deb' => 'application/x-debian-package',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dvi' => 'application/x-dvi',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'etx' => 'text/x-setext',
+ 'flac' => 'audio/flac',
+ 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif',
+ 'gz' => 'application/gzip',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ini' => 'text/plain',
+ 'iso' => 'application/x-iso9660-image',
+ 'jar' => 'application/java-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'latex' => 'application/x-latex',
+ 'log' => 'text/plain',
+ 'm4a' => 'audio/mp4',
+ 'm4v' => 'video/mp4',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mkv' => 'video/x-matroska',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'qt' => 'video/quicktime',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'torrent' => 'application/x-bittorrent',
+ 'ttf' => 'application/x-font-ttf',
+ 'txt' => 'text/plain',
+ 'wav' => 'audio/x-wav',
+ 'webm' => 'video/webm',
+ 'webp' => 'image/webp',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'video/x-ms-wmv',
+ 'woff' => 'application/x-font-woff',
+ 'wsdl' => 'application/wsdl+xml',
+ 'xbm' => 'image/x-xbitmap',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'zip' => 'application/zip',
+ ];
+
+ $extension = strtolower($extension);
+
+ return isset($mimetypes[$extension])
+ ? $mimetypes[$extension]
+ : null;
+}
+
+/**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ *
+ * @return array
+ * @internal
+ */
+function _parse_message($message)
+{
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ $message = ltrim($message, "\r\n");
+
+ $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
+
+ if ($messageParts === false || count($messageParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
+ }
+
+ list($rawHeaders, $body) = $messageParts;
+ $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
+ $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
+
+ if ($headerParts === false || count($headerParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing status line');
+ }
+
+ list($startLine, $rawHeaders) = $headerParts;
+
+ if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
+ // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
+ $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
+ }
+
+ /** @var array[] $headerLines */
+ $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
+
+ // If these aren't the same, then one line didn't match and there's an invalid header.
+ if ($count !== substr_count($rawHeaders, "\n")) {
+ // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
+ throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
+ }
+
+ throw new \InvalidArgumentException('Invalid header syntax');
+ }
+
+ $headers = [];
+
+ foreach ($headerLines as $headerLine) {
+ $headers[$headerLine[1]][] = $headerLine[2];
+ }
+
+ return [
+ 'start-line' => $startLine,
+ 'headers' => $headers,
+ 'body' => $body,
+ ];
+}
+
+/**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ *
+ * @return string
+ * @internal
+ */
+function _parse_request_uri($path, array $headers)
+{
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme . '://' . $host . '/' . ltrim($path, '/');
+}
+
+/**
+ * Get a short summary of the message body
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param MessageInterface $message The message to get the body summary
+ * @param int $truncateAt The maximum allowed size of the summary
+ *
+ * @return null|string
+ */
+function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
+{
+ $body = $message->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $summary = $body->read($truncateAt);
+ $body->rewind();
+
+ if ($size > $truncateAt) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
+ return null;
+ }
+
+ return $summary;
+}
+
+/** @internal */
+function _caseless_remove($keys, array $data)
+{
+ $result = [];
+
+ foreach ($keys as &$key) {
+ $key = strtolower($key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower($k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+}
diff --git a/instafeed/vendor/guzzlehttp/psr7/src/functions_include.php b/instafeed/vendor/guzzlehttp/psr7/src/functions_include.php
new file mode 100755
index 0000000..96a4a83
--- /dev/null
+++ b/instafeed/vendor/guzzlehttp/psr7/src/functions_include.php
@@ -0,0 +1,6 @@
+setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__)
+ ->name('/(?:^lazydoctor$|\.php$)/')
+ )
+ ->setIndent(' ')
+ ->setLineEnding("\n")
+ ->setRules([
+ '@Symfony' => true,
+ // Override @Symfony rules
+ 'pre_increment' => false,
+ 'blank_line_before_statement' => ['statements' => ['return', 'try', 'throw']],
+ 'phpdoc_align' => ['tags' => ['param', 'throws']],
+ 'method_argument_space' => ['ensure_fully_multiline' => false],
+ 'binary_operator_spaces' => [
+ 'align_double_arrow' => true,
+ 'align_equals' => false,
+ ],
+ 'phpdoc_annotation_without_dot' => false,
+ 'yoda_style' => [
+ // Symfony writes their conditions backwards; we use normal order.
+ 'equal' => false,
+ 'identical' => false,
+ 'less_and_greater' => false,
+ ],
+ 'is_null' => [
+ // Replaces all is_null() with === null.
+ 'use_yoda_style' => false,
+ ],
+ // Custom rules
+ 'align_multiline_comment' => true,
+ 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
+ 'ordered_imports' => true,
+ 'phpdoc_order' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ]);
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/.pre-commit.hook b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/.pre-commit.hook
new file mode 100755
index 0000000..4a87bd2
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/.pre-commit.hook
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright 2017 The LazyJsonMapper Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ----------------------------------------------------------------------------
+#
+# Verifies that all files in the worktree follow our codestyle standards.
+#
+# Note that this script can't check that they're actually committing the nicely
+# formatted code. It just checks that the worktree is clean. So if they've fixed
+# all files but haven't added the codestyle fixes to their commit, they'll still
+# pass this check. But it's still a great protection against most mistakes.
+#
+# To install this hook, just run the following in the project's root folder:
+# ln -fs "../../.pre-commit.hook" .git/hooks/pre-commit
+#
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Git ensures that CWD is always the root of the project folder, so we can just
+# run all tests without verifying what folder we are in...
+
+failed="no"
+echo "[pre-commit] Checking work-tree codestyle..."
+
+# Check the general codestyle format.
+echo "> Verifying php-cs-fixer..."
+vendor/bin/php-cs-fixer fix --config=.php_cs.dist --allow-risky yes --dry-run
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Look for specific problems with the style, related to our project.
+echo "> Verifying checkStyle..."
+/usr/bin/env php devtools/checkStyle.php x
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Refuse to commit if there were problems. Instruct the user about solving it.
+if [ "${failed}" = "yes" ]; then
+ # Yes there are lots of "echo" commands, because "\n" is not cross-platform.
+ echo "[commit failed] There are problems with your code..."
+ echo ""
+ echo "Run 'composer codestyle' to fix the code in your worktree."
+ echo ""
+ echo "But beware that the process is automatic, and that the result"
+ echo "isn't always perfect and won't be automatically staged."
+ echo ""
+ echo "So remember to manually read through the changes, then further"
+ echo "fix them if necessary, and finally stage the updated code"
+ echo "afterwards so that the fixed code gets committed."
+ exit 1
+fi
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/LICENSE b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/NOTICE b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/NOTICE
new file mode 100755
index 0000000..27c6183
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/NOTICE
@@ -0,0 +1,5 @@
+LazyJsonMapper
+Copyright 2017 The LazyJsonMapper Project
+
+This product includes software developed at
+The LazyJsonMapper Project (https://github.com/SteveJobzniak/LazyJsonMapper).
\ No newline at end of file
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/README.md b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/README.md
new file mode 100755
index 0000000..7d3a997
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/README.md
@@ -0,0 +1,211 @@
+# LazyJsonMapper
+
+## Advanced, intelligent & automatic object-oriented JSON containers for PHP.
+
+Implements a highly efficient, automatic, object-oriented and lightweight
+(memory-wise) JSON data container. It provides intelligent data conversion
+and parsing, to give you a nice, reliable interface to your JSON data,
+without having to worry about doing any of the tedious parsing yourself.
+
+### Features:
+
+- Provides a completely object-oriented interface to all of your JSON data.
+
+- Automatically maps complex, nested JSON data structures onto real PHP
+ objects, with total support for nested objects and multi-level arrays.
+
+- Extremely optimized for very high performance and very low memory usage.
+ Much lower than other PHP JSON mappers that people have used in the past.
+
+ For example, normal PHP objects with manually defined `$properties`, which
+ is what's used by _other_ JSON mappers, will consume memory for every
+ property even if that property wasn't in the JSON data (is a `NULL`). Our
+ system on the other hand takes up ZERO bytes of RAM for any properties
+ that don't exist in the current object's JSON data!
+
+- Automatically provides "direct virtual properties", which lets you
+ interact with the JSON data as if it were regular object properties,
+ such as `echo $item->some_value` and `$item->some_value = 'foo'`.
+
+ The virtual properties can be disabled via an option.
+
+- Automatically provides object-oriented "virtual functions", which let you
+ interact with the data in a fully object-oriented way via functions such
+ as `$item->getSomeValue()` and `$item->setSomeValue('foo')`. We support a
+ large range of different functions for manipulating the JSON data, and you
+ can see a list of all available function names for all of your properties
+ by simply running `$item->printPropertyDescriptions()`.
+
+ The virtual functions can be disabled via an option.
+
+- Includes the `LazyDoctor` tool, which _automatically_ documents all of
+ your `LazyJsonMapper`-based classes so that their virtual properties and
+ functions become _fully_ visible to your IDE and to various intelligent
+ code analysis tools. It also performs class diagnostics by compiling all
+ of your class property maps, which means that you can be 100% sure that
+ all of your maps are valid (compilable) if this tool runs successfully.
+
+- We provide a complete, internal API which your subclasses can use to
+ interact with the data inside of the JSON container. This allows you to
+ easily override the automatic functions or create additional functions
+ for your objects. To override core functions, just define a function with
+ the exact same name on your object and make it do whatever you want to.
+
+ Here are some examples of function overriding:
+
+ ```php
+ public function getFoo()
+ {
+ // try to read property, and handle a special "current_time" value.
+ $value = $this->_getProperty('foo');
+ if ($value === 'current_time') { return time(); }
+ return $value;
+ }
+ public function setFoo(
+ $value)
+ {
+ // if they try to set value to "md5", we use a special value instead
+ if ($value === 'md5') { $value = md5(time()); }
+ return $this->_setProperty('foo', $value);
+ }
+ ```
+
+- All mapping/data conversion is done "lazily", on a per-property basis.
+ When you access a property, that specific property is mapped/converted to
+ the proper type as defined by your class property map. No time or memory
+ is wasted converting properties that you never touch.
+
+- Strong type-system. The class property map controls the exact types and
+ array depths. You can fully trust that the data you get/set will match
+ your specifications. Invalid data that mismatches the spec is impossible.
+
+- Advanced settings system. Everything is easily configured via PHP class
+ constants, which means that your class-settings are stateless (there's no
+ need for any special "settings/mapper object" to keep track of settings),
+ and that all settings are immutable constants (which means that they are
+ reliable and can never mutate at runtime, so that you can fully trust that
+ classes will always behave as-defined in their code).
+
+ If you want to override multiple core settings identically for all of your
+ classes, then simply create a subclass of `LazyJsonMapper` and configure
+ all of your settings on that, and then derive all of your other classes
+ from your re-configured subclass!
+
+- The world's most advanced mapper definition system. Your class property
+ maps are defined in an easy PHPdoc-style format, and support multilevel
+ arrays (such as `int[][]` for "an array of arrays of ints"), relative
+ types (so you can map properties to classes/objects that are relative to
+ the namespace of the class property map), parent inheritance (all of your
+ parent `extends`-hierarchy's maps will be included in your final property
+ map) and even multiple inheritance (you can literally "import" an infinite
+ number of other maps into your class, which don't come from your own
+ parent `extends`-hierarchy).
+
+- Inheriting properties from parent classes or importing properties from
+ other classes is a zero-cost operation thanks to how efficient our
+ property map compiler is. So feel free to import everything you need.
+ You can even use this system to create importable classes that just hold
+ "collections" of shared properties, which you import into other classes.
+
+- The class property maps are compiled a single time per-class at runtime,
+ the first time a class is used. The compilation process fully verifies
+ and compiles all property definitions, all parent maps, all inherited
+ maps, and all maps of all classes you link properties to.
+
+ If there are any compilation problems due to a badly written map anywhere
+ in your hierarchy, you will be shown the exact problem in great detail.
+
+ In case of success, the compiled and verified maps are all stored in an
+ incredibly memory-efficient format in a global cache which is shared by
+ your whole PHP runtime, which means that anything in your code or in any
+ other libraries which accesses the same classes will all share the cached
+ compilations of those classes, for maximum memory efficiency.
+
+- You are also able to access JSON properties that haven't been defined in
+ the class property map. In that case, they are treated as undefined and
+ untyped (`mixed`) and there won't be any automatic type-conversion of such
+ properties, but it can still be handy in a pinch.
+
+- There are lots of data export/output options for your object's JSON data,
+ to get it back out of the object again: As a multi-level array, as nested
+ stdClass objects, or as a JSON string representation of your object.
+
+- We include a whole assortment of incredibly advanced debugging features:
+
+ You can run the constructor with `$requireAnalysis` to ensure that all
+ of your JSON data is successfully mapped according to your class property
+ map, and that you haven't missed defining any properties that exist in the
+ data. In case of any problems, the analysis message will give you a full
+ list of all problems encountered in your entire JSON data hierarchy.
+
+ For your class property maps themselves, you can run functions such as
+ `printPropertyDescriptions()` to see a complete list of all properties and
+ how they are defined. This helps debug your class inheritance and imports
+ to visually see what your final class map looks like, and it also helps
+ users see all available properties and all of their virtual functions.
+
+ And for the JSON data, you can use functions such as `printJson()` to get
+ a beautiful view of all internal JSON data, which is incredibly helpful
+ when you (or your users) need to figure out what's available inside the
+ current object instance's data storage.
+
+- A fine-grained and logical exception-system which ensures that you can
+ always trust the behavior of your objects and can catch problems easily.
+ And everything we throw is _always_ based on `LazyJsonMapperException`,
+ which means that you can simply catch that single "root" exception
+ whenever you don't care about fine-grained differentiation.
+
+- Clean and modular code ensures stability and future extensibility.
+
+- Deep code documentation explains everything you could ever wonder about.
+
+- Lastly, we implement super-efficient object serialization. Everything is
+ stored in a tightly packed format which minimizes data size when you need
+ to transfer your objects between runtimes.
+
+### Installation
+
+You need at least PHP 5.6 or higher. PHP 7+ is also fully supported and is recommended.
+
+Run the following [Composer](https://getcomposer.org/download/) installation command:
+
+```
+composer require lazyjsonmapper/lazyjsonmapper
+```
+
+### Examples
+
+View the contents of the [`examples/`](https://github.com/SteveJobzniak/LazyJsonMapper/tree/master/examples) folder.
+
+### Documentation
+
+Everything is fully documented directly within the source code of this library.
+
+You can also [read the same documentation online](https://mgp25.github.io/lazyjsonmapper-docs/namespaces/LazyJsonMapper.html) as nicely formatted HTML pages.
+
+### LazyDoctor
+
+Our automatic class-documentation and diagnostic utility will be placed within
+your project's `./vendor/bin/` folder. Simply run it without any parameters to
+see a list of all available options. You can also open that file in a regular
+text editor to read some general usage tips and tricks at the top of the
+utility's source code.
+
+### Copyright
+
+Copyright 2017 The LazyJsonMapper Project
+
+### License
+
+[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+
+### Author
+
+SteveJobzniak
+
+### Contributing
+
+If you would like to contribute to this project, please feel free to submit a
+pull request, or perhaps even sending a donation to a team member as a token of
+your appreciation.
+
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/bin/lazydoctor b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/bin/lazydoctor
new file mode 100755
index 0000000..c142b53
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/bin/lazydoctor
@@ -0,0 +1,738 @@
+#!/usr/bin/env php
+/dev/null" to send STDOUT to the void...
+ * That way you'll ONLY see critical status messages during the processing.
+ */
+
+set_time_limit(0);
+date_default_timezone_set('UTC');
+
+// Verify minimum PHP version.
+if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50600) {
+ fwrite(STDERR, 'LazyDoctor requires PHP 5.6 or higher.'.PHP_EOL);
+ exit(1);
+}
+
+// Register a simple GetOptionKit autoloader. This is fine because
+// GetOptionKit has no 3rd party library dependencies.
+spl_autoload_register(function ($class) {
+ // Check if this is a "GetOptionKit" load-request.
+ static $prefix = 'GetOptionKit\\';
+ static $len = 13; // strlen($prefix)
+ if (strncmp($prefix, $class, $len) !== 0) {
+ return;
+ }
+
+ // Find the "GetOptionKit" source folder.
+ static $dirs = [
+ __DIR__.'/../../../corneltek/getoptionkit/src',
+ __DIR__.'/../vendor/corneltek/getoptionkit/src',
+ ];
+ $baseDir = null;
+ foreach ($dirs as $dir) {
+ if (is_dir($dir) && ($dir = realpath($dir)) !== false) {
+ $baseDir = $dir;
+ break;
+ }
+ }
+ if ($baseDir === null) {
+ return;
+ }
+
+ // Get the relative class name.
+ $relativeClass = substr($class, $len);
+
+ // Generate PSR-4 file path to the class.
+ $file = sprintf('%s/%s.php', $baseDir, str_replace('\\', '/', $relativeClass));
+ if (is_file($file)) {
+ require $file;
+ }
+});
+
+// Parse command line options...
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
+
+$specs = new OptionCollection();
+$specs->add('c|composer:=file', 'Path to your project\'s composer.json file.');
+$specs->add('p|properties?=boolean', 'Document virtual properties (if enabled for the classes).');
+$specs->add('f|functions?=boolean', 'Document virtual functions (if enabled for the classes).');
+$specs->add('o|document-overridden?=boolean', 'Always document virtual functions/properties even when they have been manually overridden by the class (or its parents).');
+$specs->add('w|windows?=boolean', 'Generate Windows-style ("\r\n") documentation line endings instead of the default Unix-style ("\n").');
+$specs->add('validate-only?=boolean', 'Validate current docs for all classes but don\'t write anything to disk.');
+$specs->add('h|help?=boolean', 'Show all available options.');
+
+try {
+ $parser = new OptionParser($specs);
+ $result = $parser->parse($argv);
+ $options = [
+ 'composer' => isset($result->keys['composer']) ? $result->keys['composer']->value : null,
+ 'properties' => isset($result->keys['properties']) && $result->keys['properties']->value !== false,
+ 'functions' => isset($result->keys['functions']) && $result->keys['functions']->value !== false,
+ 'document-overridden' => isset($result->keys['document-overridden']) && $result->keys['document-overridden']->value !== false,
+ 'windows' => isset($result->keys['windows']) && $result->keys['windows']->value !== false,
+ 'validate-only' => isset($result->keys['validate-only']) && $result->keys['validate-only']->value !== false,
+ 'help' => isset($result->keys['help']) && $result->keys['help']->value !== false,
+ ];
+} catch (Exception $e) {
+ // Warns in case of invalid option values.
+ fwrite(STDERR, $e->getMessage().PHP_EOL);
+ exit(1);
+}
+
+// Verify options...
+echo '[ LazyDoctor ]'.PHP_EOL.PHP_EOL;
+if ($options['composer'] === null || $options['help']) {
+ if ($options['composer'] === null) {
+ fwrite(STDERR, 'You must provide the --composer option.'.PHP_EOL.PHP_EOL);
+ }
+ $printer = new ConsoleOptionPrinter();
+ echo 'Available options:'.PHP_EOL.PHP_EOL;
+ echo $printer->render($specs);
+ exit($options['composer'] === null && !$options['help'] ? 1 : 0);
+}
+
+if ($options['composer']->getBasename() !== 'composer.json') {
+ fwrite(STDERR, 'You must point to your project\'s composer.json file.'.PHP_EOL.'You used: "'.$options['composer']->getRealPath().'".'.PHP_EOL);
+ exit(1);
+}
+
+// Decode the composer.json file...
+$json = @json_decode(file_get_contents($options['composer']->getRealPath()), true);
+if ($json === null) {
+ fwrite(STDERR, 'Unable to decode composer.json.'.PHP_EOL);
+ exit(1);
+}
+
+// Determine the project folder's real root path...
+$projectRoot = $options['composer']->getPathInfo()->getRealPath();
+
+// Determine their namespace PSR-4 paths via their project's composer.json...
+$namespaces = [];
+foreach (['autoload', 'autoload-dev'] as $type) {
+ if (!isset($json[$type]['psr-4']) || !is_array($json[$type]['psr-4'])) {
+ continue;
+ }
+
+ foreach ($json[$type]['psr-4'] as $namespace => $dir) {
+ // We don't support composer's empty "fallback" namespaces.
+ if ($namespace === '') {
+ fwrite(STDERR, 'Encountered illegal unnamed PSR-4 autoload namespace in composer.json.'.PHP_EOL);
+ exit(1);
+ }
+
+ // Ensure that the namespace ends in backslash.
+ if (substr_compare($namespace, '\\', strlen($namespace) - 1, 1) !== 0) {
+ fwrite(STDERR, 'Encountered illegal namespace "'.$namespace.'" (does not end in backslash) in composer.json.'.PHP_EOL);
+ exit(1);
+ }
+
+ // Ensure that the value is a string.
+ // NOTE: We allow empty strings, which corresponds to root folder.
+ if (!is_string($dir)) {
+ fwrite(STDERR, 'Encountered illegal non-string value for namespace "'.$namespace.'".'.PHP_EOL);
+ exit(1);
+ }
+
+ // Now resolve the path name...
+ $path = sprintf('%s/%s', $projectRoot, $dir);
+ $realpath = realpath($path);
+ if ($realpath === false) {
+ fwrite(STDERR, 'Unable to resolve real path for "'.$path.'".'.PHP_EOL);
+ exit(1);
+ }
+
+ // We don't allow the same directory to be defined multiple times.
+ if (isset($namespaces[$realpath])) {
+ fwrite(STDERR, 'Encountered duplicate namespace directory "'.$realpath.'" in composer.json.'.PHP_EOL);
+ exit(1);
+ }
+
+ // And we're done! The namespace and its path have been resolved.
+ $namespaces[$realpath] = $namespace;
+ }
+}
+
+// Verify that we found some namespaces...
+if (empty($namespaces)) {
+ fwrite(STDERR, 'There are no PSR-4 autoload namespaces in your composer.json.'.PHP_EOL);
+ exit(1);
+}
+
+// Now load the project's autoload.php file.
+// NOTE: This is necessary so that we can autoload their classes...
+$autoload = sprintf('%s/vendor/autoload.php', $projectRoot);
+$realautoload = realpath($autoload);
+if ($realautoload === false) {
+ fwrite(STDERR, 'Unable to find the project\'s Composer autoloader ("'.$autoload.'").'.PHP_EOL);
+ exit(1);
+}
+require $realautoload;
+
+// Verify that their project's autoloader contains LazyJsonMapper...
+if (!class_exists('\LazyJsonMapper\LazyJsonMapper', true)) { // TRUE = Autoload.
+ fwrite(STDERR, 'Target project doesn\'t contain the LazyJsonMapper library.'.PHP_EOL);
+ exit(1);
+}
+
+// Alright, display the current options...
+echo 'Project: "'.$projectRoot.'".'.PHP_EOL
+ .'- Documentation Line Endings: '.($options['windows'] ? 'Windows ("\r\n")' : 'Unix ("\n")').'.'.PHP_EOL
+ .'- ['.($options['properties'] ? 'X' : ' ').'] Document Virtual Properties ("@property").'.PHP_EOL
+ .'- ['.($options['functions'] ? 'X' : ' ').'] Document Virtual Functions ("@method").'.PHP_EOL
+ .'- ['.($options['document-overridden'] ? 'X' : ' ').'] Document Overridden Properties/Functions.'.PHP_EOL;
+if ($options['validate-only']) {
+ echo '- This is a validation run. Nothing will be written to disk.'.PHP_EOL;
+}
+
+// We can now use our custom classes, since the autoloader has been imported...
+use LazyJsonMapper\Exception\LazyJsonMapperException;
+use LazyJsonMapper\Export\PropertyDescription;
+use LazyJsonMapper\Property\PropertyMapCache;
+use LazyJsonMapper\Property\PropertyMapCompiler;
+use LazyJsonMapper\Utilities;
+
+/**
+ * Automatic LazyJsonMapper-class documentation generator.
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class LazyClassDocumentor
+{
+ /** @var PropertyMapCache */
+ private static $_propertyMapCache;
+
+ /** @var array */
+ private $_compiledPropertyMapLink;
+
+ /** @var ReflectionClass */
+ private $_reflector;
+
+ /** @var array */
+ private $_options;
+
+ /** @var string Newline sequence. */
+ private $_nl;
+
+ /**
+ * Constructor.
+ *
+ * @param string $class
+ * @param array $options
+ *
+ * @throws ReflectionException
+ */
+ public function __construct(
+ $class,
+ array $options)
+ {
+ if (self::$_propertyMapCache === null) {
+ self::$_propertyMapCache = new PropertyMapCache();
+ }
+ $this->_reflector = new ReflectionClass($class);
+ $this->_options = $options;
+ $this->_nl = $options['windows'] ? "\r\n" : "\n";
+ }
+
+ /**
+ * Process the current class.
+ *
+ * @throws ReflectionException
+ * @throws LazyJsonMapperException
+ *
+ * @return bool `TRUE` if on-disk file has correct docs, otherwise `FALSE`.
+ */
+ public function process()
+ {
+ // Only process user-defined classes (never any built-in PHP classes).
+ if (!$this->_reflector->isUserDefined()) {
+ return true;
+ }
+
+ // There's nothing to do if this isn't a LazyJsonMapper subclass.
+ // NOTE: This properly skips "\LazyJsonMapper\LazyJsonMapper" itself.
+ if (!$this->_reflector->isSubclassOf('\LazyJsonMapper\LazyJsonMapper')) {
+ return true;
+ }
+
+ // Compile this class property map if not yet built and cached.
+ $thisClassName = $this->_reflector->getName();
+ if (!isset(self::$_propertyMapCache->classMaps[$thisClassName])) {
+ try {
+ PropertyMapCompiler::compileClassPropertyMap( // Throws.
+ self::$_propertyMapCache,
+ $thisClassName
+ );
+ } catch (Exception $e) {
+ fwrite(STDERR, '> Unable to compile the class property map for "'.$thisClassName.'". Reason: '.$e->getMessage().PHP_EOL);
+
+ return false;
+ }
+ }
+
+ // Now link to the property map cache for our current class.
+ $this->_compiledPropertyMapLink = &self::$_propertyMapCache->classMaps[$thisClassName];
+
+ // Get the current class comment (string if ok, FALSE if none exists).
+ $currentDocComment = $this->_reflector->getDocComment();
+ if (is_string($currentDocComment)) {
+ $currentDocComment = trim($currentDocComment);
+ }
+
+ // Extract all relevant lines from the current comment.
+ $finalDocLines = $this->_extractRelevantLines($currentDocComment);
+
+ // Generate the automatic summary line (classname followed by period).
+ $autoSummaryLine = $this->_reflector->getShortName().'.';
+
+ // If the 1st line is a classname followed by a period, update the name.
+ // NOTE: This ensures that we update all outdated auto-added classnames,
+ // and the risk of false positives is very low since we only document
+ // `LazyJsonMapper`-based classes with a `OneWord.`-style summary line.
+ // NOTE: Regex is from http://php.net/manual/en/language.oop5.basic.php,
+ // and yes we must run it in NON-UNICODE MODE, so that it parses on a
+ // byte by byte basis exactly like the real PHP classname interpreter.
+ if (
+ isset($finalDocLines[0]) // The 1st line MUST exist to proceed.
+ && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\.$/', $finalDocLines[0])
+ ) {
+ $finalDocLines[0] = $autoSummaryLine;
+ }
+
+ // Generate the magic documentation lines for the current class.
+ $magicDocLines = $this->_generateMagicDocs();
+ if (!empty($magicDocLines)) {
+ // If there are no lines already... add the automatic summary line.
+ if (empty($finalDocLines)) {
+ $finalDocLines[] = $autoSummaryLine;
+ }
+
+ // Check the 1st char of the 1st line. If it's an @tag of any kind,
+ // insert automatic summary line at top and empty line after that.
+ elseif ($finalDocLines[0][0] === '@') {
+ array_unshift(
+ $finalDocLines,
+ $autoSummaryLine,
+ ''
+ );
+ }
+
+ $finalDocLines[] = ''; // Add empty line before our magic docs.
+ $finalDocLines = array_merge($finalDocLines, array_values($magicDocLines));
+ }
+ unset($magicDocLines);
+
+ // Generate the final doc-comment that this class is supposed to have.
+ if (!empty($finalDocLines)) {
+ // This will generate even if the class only contained an existing
+ // summary/tags and nothing was added by our magic handler.
+ foreach ($finalDocLines as &$line) {
+ $line = ($line === '' ? ' *' : " * {$line}");
+ }
+ unset($line);
+ $finalDocComment = sprintf(
+ '/**%s%s%s */',
+ $this->_nl,
+ implode($this->_nl, $finalDocLines),
+ $this->_nl
+ );
+ } else {
+ // The FALSE signifies that we want no class doc-block at all...
+ $finalDocComment = false;
+ }
+ unset($finalDocLines);
+
+ // There's nothing to do if the doc-comment is already correct.
+ // NOTE: Both values are FALSE if no doc-comment exists and none wanted.
+ if ($currentDocComment === $finalDocComment) {
+ return true;
+ }
+
+ // The docs mismatch. If this is a validate-run, just return false now.
+ if ($this->_options['validate-only']) {
+ fwrite(STDERR, '> Outdated class docs encountered in "'.$thisClassName.'". Aborting scan...'.PHP_EOL);
+
+ return false;
+ }
+
+ // Load the contents of the file...
+ $classFileName = $this->_reflector->getFileName();
+ $fileLines = @file($classFileName);
+ if ($fileLines === false) {
+ fwrite(STDERR, '> Unable to read class file from disk: "'.$classFileName.'".'.PHP_EOL);
+
+ return false;
+ }
+
+ // Split the file into lines BEFORE the class and lines AFTER the class.
+ $classLine = $this->_reflector->getStartLine();
+ $startLines = array_slice($fileLines, 0, $classLine - 1);
+ $endLines = array_slice($fileLines, $classLine - 1);
+ unset($fileLines);
+
+ // Insert the new class documentation using a very careful algorithm.
+ if ($currentDocComment !== false) {
+ // Since the class already had PHPdoc, remove it and insert new doc.
+ // NOTE: A valid PHPdoc (getDocComment()) always starts with
+ // "/**[whitespace]". If it's just a "/*" or something like
+ // "/**Foo", then it's not detected by getDocComment(). However, the
+ // comment may be several lines above the class. So we'll have to do
+ // an intelligent search to find the old class-comment. As for the
+ // ending tag "*/", PHP doesn't care about whitespace around that.
+ // And it also doesn't let the user escape the "*/", which means
+ // that if we see that sequence we KNOW it's the end of a comment!
+ // NOTE: We'll search for the latest "/**[whitespace]" block and
+ // remove all lines from that until its closest "*/".
+ $deleteFrom = null;
+ $deleteTo = null;
+ for ($i = count($startLines) - 1; $i >= 0; --$i) {
+ if (strpos($startLines[$i], '*/') !== false) {
+ $deleteTo = $i;
+ }
+ if (preg_match('/^\s*\/\*\*\s/u', $startLines[$i])) {
+ $deleteFrom = $i;
+ break;
+ }
+ }
+
+ // Ensure that we have found valid comment-offsets.
+ if ($deleteFrom === null || $deleteTo === null || $deleteTo < $deleteFrom) {
+ fwrite(STDERR, '> Unable to parse current class comment on disk: "'.$classFileName.'".'.PHP_EOL);
+
+ return false;
+ }
+
+ // Now update the startLines array to replace the doc-comment...
+ foreach ($startLines as $k => $v) {
+ if ($k === $deleteFrom && $finalDocComment !== false) {
+ // We've found the first line of the old comment, and we
+ // have a new comment. So replace that array entry.
+ $startLines[$k] = $finalDocComment.$this->_nl;
+ } elseif ($k >= $deleteFrom && $k <= $deleteTo) {
+ // Delete all other comment lines, including the first line
+ // if we had no new doc-comment.
+ unset($startLines[$k]);
+ }
+
+ // Break if we've reached the final line to delete.
+ if ($k >= $deleteTo) {
+ break;
+ }
+ }
+ } elseif ($finalDocComment !== false) {
+ // There's no existing doc-comment. Just add ours above the class.
+ // NOTE: This only does something if we had a new comment to insert,
+ // which we SHOULD have since we came this far in this scenario...
+ $startLines[] = $finalDocComment.$this->_nl;
+ }
+
+ // Generate the new file contents.
+ $newFileContent = implode($startLines).implode($endLines);
+ unset($startLines);
+ unset($endLines);
+
+ // Perform an atomic file-write to disk, which ensures that we will
+ // never be able to corrupt the class-files on disk via partial writes.
+ $written = Utilities::atomicWrite($classFileName, $newFileContent);
+ if ($written !== false) {
+ echo '> Wrote updated class documentation to disk: "'.$classFileName.'".'.PHP_EOL;
+
+ return true;
+ } else {
+ fwrite(STDERR, '> Unable to write new class documentation to disk: "'.$classFileName.'".'.PHP_EOL);
+
+ return false;
+ }
+ }
+
+ /**
+ * Extracts all relevant lines from a doc-comment.
+ *
+ * @param string $currentDocComment
+ *
+ * @return array
+ */
+ private function _extractRelevantLines(
+ $currentDocComment)
+ {
+ if (!is_string($currentDocComment)) {
+ return [];
+ }
+
+ // Remove the leading and trailing doc-comment tags (/** and */).
+ $currentDocComment = preg_replace('/(^\s*\/\*\*\s*|\s*\*\/$)/u', '', $currentDocComment);
+
+ // Process all lines. Skip all @method and @property lines.
+ $relevantLines = [];
+ $lines = preg_split('/\r?\n|\r/u', $currentDocComment);
+ foreach ($lines as $line) {
+ // Remove leading and trailing whitespace, and leading asterisks.
+ $line = trim(preg_replace('/^\s*\*+/u', '', $line));
+
+ // Skip this line if it's a @method or @property line.
+ // NOTE: Removing them is totally safe, because the LazyJsonMapper
+ // class has marked all of its magic property/function handlers as
+ // final, which means that people's subclasses CANNOT override them
+ // to add their own magic methods/properties. So therefore we KNOW
+ // that ALL existing @method/@property class doc lines belong to us!
+ if (preg_match('/^@(?:method|property)/u', $line)) {
+ continue;
+ }
+
+ $relevantLines[] = $line;
+ }
+
+ // Remove trailing empty lines from the relevant lines.
+ for ($i = count($relevantLines) - 1; $i >= 0; --$i) {
+ if ($relevantLines[$i] === '') {
+ unset($relevantLines[$i]);
+ } else {
+ break;
+ }
+ }
+
+ // Remove leading empty lines from the relevant lines.
+ foreach ($relevantLines as $k => $v) {
+ if ($v !== '') {
+ break;
+ }
+
+ unset($relevantLines[$k]);
+ }
+
+ // Return a re-indexed (properly 0-indexed) array.
+ return array_values($relevantLines);
+ }
+
+ /**
+ * Generate PHPdoc lines for all magic properties and functions.
+ *
+ * @throws ReflectionException
+ * @throws LazyJsonMapperException
+ *
+ * @return array
+ */
+ private function _generateMagicDocs()
+ {
+ // Check whether we should (and can) document properties and functions.
+ $documentProperties = $this->_options['properties'] && $this->_reflector->getConstant('ALLOW_VIRTUAL_PROPERTIES');
+ $documentFunctions = $this->_options['functions'] && $this->_reflector->getConstant('ALLOW_VIRTUAL_FUNCTIONS');
+ if (!$documentProperties && !$documentFunctions) {
+ return [];
+ }
+
+ // Export all JSON properties, with RELATIVE class-paths when possible.
+ // NOTE: We will document ALL properties. Even ones inherited from
+ // parents/imported maps. This ensures that users who are manually
+ // reading the source code can see EVERYTHING without needing an IDE.
+ $properties = [];
+ $ownerClassName = $this->_reflector->getName();
+ foreach ($this->_compiledPropertyMapLink as $propName => $propDef) {
+ $properties[$propName] = new PropertyDescription( // Throws.
+ $ownerClassName,
+ $propName,
+ $propDef,
+ true // Use relative class-paths when possible.
+ );
+ }
+
+ // Build the magic documentation...
+ $magicDocLines = [];
+ foreach (['functions', 'properties'] as $docType) {
+ if (($docType === 'functions' && !$documentFunctions)
+ || ($docType === 'properties' && !$documentProperties)) {
+ continue;
+ }
+
+ // Generate all lines for the current magic tag type...
+ $lineStorage = [];
+ foreach ($properties as $property) {
+ if ($docType === 'functions') {
+ // We will only document useful functions (not the "has",
+ // since those are useless for properties that are fully
+ // defined in the class map).
+ foreach (['get', 'set', 'is', 'unset'] as $funcType) {
+ // Generate the function name, ie "getSomething", and
+ // skip this function if it's already defined as a REAL
+ // (overridden) function in this class or its parents.
+ $functionName = $funcType.$property->func_case;
+ if (!$this->_options['document-overridden'] && $this->_reflector->hasMethod($functionName)) {
+ continue;
+ }
+
+ // Alright, the function doesn't exist as a real class
+ // function, or the user wants to document it anyway...
+ // Document it via its calculated signature.
+ // NOTE: Classtypes use paths relative to current class!
+ $functionSignature = $property->{'function_'.$funcType};
+ $lineStorage[$functionName] = sprintf('@method %s', $functionSignature);
+ }
+ } elseif ($docType === 'properties') {
+ // Skip this property if it's already defined as a REAL
+ // (overridden) property in this class or its parents.
+ if (!$this->_options['document-overridden'] && $this->_reflector->hasProperty($property->name)) {
+ continue;
+ }
+
+ // Alright, the property doesn't exist as a real class
+ // property, or the user wants to document it anyway...
+ // Document it via its calculated signature.
+ // NOTE: Classtypes use paths relative to current class!
+ $lineStorage[$property->name] = sprintf(
+ '@property %s $%s',
+ $property->type,
+ $property->name
+ );
+ }
+ }
+
+ // Skip this tag type if there was nothing to document...
+ if (empty($lineStorage)) {
+ continue;
+ }
+
+ // Insert empty line separators between different magic tag types.
+ if (!empty($magicDocLines)) {
+ $magicDocLines[] = '';
+ }
+
+ // Reorder lines by name and add them to the magic doc lines.
+ // NOTE: We use case sensitivity so that "getComments" and
+ // "getCommentThreads" etc aren't placed next to each other.
+ ksort($lineStorage, SORT_NATURAL); // Case-sensitive natural order.
+ $magicDocLines = array_merge($magicDocLines, array_values($lineStorage));
+ }
+
+ return $magicDocLines;
+ }
+}
+
+// Now process all PHP files under all of the project's namespace folders.
+foreach ($namespaces as $realpath => $namespace) {
+ echo PHP_EOL.'Processing namespace "'.$namespace.'".'.PHP_EOL.'- Path: "'.$realpath.'".'.PHP_EOL;
+ $realpathlen = strlen($realpath);
+
+ $iterator = new RegexIterator(
+ new RecursiveIteratorIterator(new RecursiveDirectoryIterator($realpath)),
+ '/\.php$/i', RecursiveRegexIterator::GET_MATCH
+ );
+ foreach ($iterator as $file => $ext) {
+ // Determine the real path to the file (compatible with $realpath).
+ $realfile = realpath($file);
+ if ($realfile === false) {
+ fwrite(STDERR, 'Unable to determine real path to file "'.$file.'".'.PHP_EOL);
+ exit(1);
+ }
+
+ // Now ensure that the file starts with the expected path...
+ if (strncmp($realpath, $realfile, $realpathlen) !== 0) {
+ fwrite(STDERR, 'Unexpected path to file "'.$realfile.'". Does not match project path.'.PHP_EOL);
+ exit(1);
+ }
+ $class = substr($realfile, $realpathlen);
+
+ // Remove the leading slash for the folder...
+ if ($class[0] !== '/' && $class[0] !== '\\') {
+ fwrite(STDERR, 'Unexpected path to file "'.$realfile.'". Does not match project path.'.PHP_EOL);
+ exit(1);
+ }
+ $class = substr($class, 1);
+
+ // And now just generate the final class name...
+ $class = sprintf(
+ '%s%s',
+ $namespace,
+ str_replace('/', '\\', preg_replace('/\.php$/ui', '', $class))
+ );
+
+ // Some files may not contain classes. For example, some people have
+ // functions.php files with functions, etc. So before we proceed, just
+ // ensure that the generated class name actually exists.
+ // NOTE: class_exists() ignores interfaces. Only finds classes. Good.
+ if (!class_exists($class, true)) { // TRUE = Autoload.
+ continue;
+ }
+
+ // Now process the current class.
+ $documentor = new LazyClassDocumentor($class, $options);
+ $result = $documentor->process();
+ if (!$result) {
+ if ($options['validate-only']) {
+ fwrite(STDERR, '> One or more files need updated class documentation or contain other errors.'.PHP_EOL);
+ } else {
+ fwrite(STDERR, '> Error while processing class "'.$class.'". Aborting...'.PHP_EOL);
+ }
+ exit(1);
+ }
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/composer.json b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/composer.json
new file mode 100755
index 0000000..3b21d54
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/composer.json
@@ -0,0 +1,48 @@
+{
+ "name": "lazyjsonmapper/lazyjsonmapper",
+ "type": "library",
+ "description": "Advanced, intelligent & automatic object-oriented JSON containers for PHP.",
+ "keywords": ["json", "development"],
+ "homepage": "https://github.com/SteveJobzniak/LazyJsonMapper",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "SteveJobzniak",
+ "homepage": "https://github.com/SteveJobzniak",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": ">=5.6",
+ "corneltek/getoptionkit": "2.*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "6.*",
+ "friendsofphp/php-cs-fixer": "^2.7.1"
+ },
+ "bin": [
+ "bin/lazydoctor"
+ ],
+ "autoload": {
+ "psr-4": {
+ "LazyJsonMapper\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "LazyJsonMapper\\Tests\\": "tests/"
+ }
+ },
+ "scripts": {
+ "codestyle": [
+ "php-cs-fixer fix --config=.php_cs.dist --allow-risky yes",
+ "php devtools/checkStyle.php x"
+ ],
+ "generatedocs": [
+ "rm -rf docs/output/ && phpdoc"
+ ],
+ "generatefreshdocs": [
+ "rm -rf docs/ && phpdoc"
+ ]
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/checkStyle.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/checkStyle.php
new file mode 100755
index 0000000..5a0fc6c
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/checkStyle.php
@@ -0,0 +1,225 @@
+run();
+if (!empty($badFiles)) {
+ // Exit with non-zero code to signal that there are problems.
+ exit(1);
+}
+
+/**
+ * Code style checker.
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class styleChecker
+{
+ /**
+ * @var string
+ */
+ private $_baseDir;
+
+ /**
+ * @var string[]
+ */
+ private $_inspectFolders;
+
+ /**
+ * @var bool
+ */
+ private $_onlyShowInvalidFiles;
+
+ /**
+ * Constructor.
+ *
+ * @param string $baseDir
+ * @param string[] $inspectFolders
+ * @param bool $onlyShowInvalidFiles
+ */
+ public function __construct(
+ $baseDir,
+ array $inspectFolders,
+ $onlyShowInvalidFiles)
+ {
+ $this->_baseDir = realpath($baseDir);
+ if ($this->_baseDir === false) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid path.', $baseDir));
+ }
+ $this->_inspectFolders = $inspectFolders;
+ $this->_onlyShowInvalidFiles = $onlyShowInvalidFiles;
+ }
+
+ /**
+ * Process single file.
+ *
+ * @param string $filePath
+ *
+ * @return bool TRUE if the file has codestyle problems, otherwise FALSE.
+ */
+ private function _processFile(
+ $filePath)
+ {
+ $hasProblems = false;
+ $hasVisibilityProblems = false;
+ $fileName = basename($filePath);
+ $inputLines = @file($filePath);
+ $outputLines = [];
+
+ if ($inputLines === false) {
+ // We were unable to read the input file. Ignore if broken symlink.
+ if (is_link($filePath)) {
+ return false; // File is okay, since the symlink is invalid.
+ } else {
+ echo "- {$filePath}: UNABLE TO READ FILE!".PHP_EOL;
+
+ return true; // File has problems...
+ }
+ }
+
+ foreach ($inputLines as $line) {
+ // Function arguments on separate lines.
+ if (preg_match('/^(.*?(?:(?:final|static)\s+)*(?:public|private|protected)(?:\s+(?:final|static))*\s+function\s+.+?)\((.+)\)(.*)$/', $line, $matches)) {
+ $hasProblems = true;
+
+ $funcstart = $matches[1];
+ $params = $matches[2];
+ $trail = $matches[3];
+ $params = explode(', ', $params);
+
+ $outputLines[] = $funcstart.'('.PHP_EOL;
+ for ($i = 0, $len = count($params); $i < $len; ++$i) {
+ $newline = ' '.$params[$i];
+ if ($i == ($len - 1)) {
+ $newline .= ')'.PHP_EOL;
+ } else {
+ $newline .= ','.PHP_EOL;
+ }
+ $outputLines[] = $newline;
+ }
+ // } else {
+ // $outputLines[] = $line;
+ }
+
+ // Appropriate public, private and protected member prefixes.
+ if (preg_match('/^\s*(?:(?:final|static)\s+)*(public|private|protected)(?:\s+(?:final|static))*\s+(function|\$)\s*&?([^;\(\s]+)/', $line, $matches)) {
+ $visibility = &$matches[1]; // public, private, protected
+ $type = &$matches[2]; // $, function
+ $name = &$matches[3]; // Member name
+
+ if ($visibility == 'public') {
+ if ($name[0] == '_' && (
+ $name != '__construct'
+ && $name != '__destruct'
+ && $name != '__call'
+ && $name != '__get'
+ && $name != '__set'
+ && $name != '__isset'
+ && $name != '__unset'
+ && $name != '__invoke'
+ && $name != '__toString'
+ )) {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PUBLIC NAME:".trim($matches[0]).PHP_EOL;
+ }
+ } else { // private, protected
+ if ($name[0] != '_') {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PRIVATE/PROTECTED NAME:".trim($matches[0]).PHP_EOL;
+ }
+ }
+ }
+ }
+
+ $newFile = implode('', $outputLines);
+ if (!$hasProblems) {
+ if (!$this->_onlyShowInvalidFiles) {
+ echo " {$filePath}: Already formatted correctly.\n";
+ }
+ } elseif (!$hasVisibilityProblems) {
+ // Has problems, but no visibility problems. Output fixed file.
+ echo "- {$filePath}: Has function parameter problems:\n";
+ echo $newFile;
+ } else {
+ echo "- {$filePath}: Had member visibility problems.\n";
+ }
+
+ return $hasProblems;
+ }
+
+ /**
+ * Process all *.php files in given path.
+ *
+ * @return string[] An array with all files that have codestyle problems.
+ */
+ public function run()
+ {
+ $filesWithProblems = [];
+ foreach ($this->_inspectFolders as $inspectFolder) {
+ $directoryIterator = new RecursiveDirectoryIterator($this->_baseDir.'/'.$inspectFolder);
+ $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
+ $phpIterator = new RegexIterator($recursiveIterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
+
+ foreach ($phpIterator as $filePath => $dummy) {
+ $hasProblems = $this->_processFile($filePath);
+ if ($hasProblems) {
+ $filesWithProblems[] = $filePath;
+ }
+ }
+ }
+
+ return $filesWithProblems;
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/funcListData.serialized b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/funcListData.serialized
new file mode 100755
index 0000000..589e5be
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/funcListData.serialized
@@ -0,0 +1 @@
+a:10000:{i:0;s:3:"Pol";i:1;s:3:"Unp";i:2;s:8:"DeDiveSt";i:3;s:2:"Un";i:4;s:5:"Oyste";i:5;s:5:"Postm";i:6;s:2:"Sa";i:7;s:10:"SorroWaddl";i:8;s:6:"ApoCys";i:9;s:10:"GenRevisTe";i:10;s:11:"FarcGenoGoy";i:11;s:3:"Cro";i:12;s:12:"BasDefoHorse";i:13;s:6:"PsycSu";i:14;s:8:"DemPresa";i:15;s:4:"PeSc";i:16;s:2:"Va";i:17;s:6:"AnsKaz";i:18;s:3:"Ove";i:19;s:10:"HanovRaoul";i:20;s:3:"Twi";i:21;s:13:"NonsTortWhapu";i:22;s:5:"Areco";i:23;s:13:"BacilMaladUna";i:24;s:5:"Unflu";i:25;s:13:"CheyHarmSands";i:26;s:11:"OtheOvUltra";i:27;s:2:"Lo";i:28;s:4:"Shos";i:29;s:5:"Scler";i:30;s:5:"Predi";i:31;s:4:"Unre";i:32;s:4:"Punj";i:33;s:9:"HosanUnig";i:34;s:7:"BetaWis";i:35;s:13:"ShalVerteWasp";i:36;s:7:"InhSuWi";i:37;s:5:"Pluto";i:38;s:2:"Ph";i:39;s:9:"IrPhResur";i:40;s:10:"UnconVisco";i:41;s:12:"DrMessaSizzl";i:42;s:6:"LaPrea";i:43;s:2:"Zo";i:44;s:4:"Suck";i:45;s:5:"Monop";i:46;s:5:"Pinci";i:47;s:8:"PatroPha";i:48;s:9:"HogPicrTh";i:49;s:4:"Picn";i:50;s:8:"DisasGib";i:51;s:5:"Popul";i:52;s:10:"ExordPseSh";i:53;s:4:"Unpo";i:54;s:8:"HabiTaci";i:55;s:11:"SalsSanStep";i:56;s:11:"ProaeRepUnd";i:57;s:8:"InflLaSu";i:58;s:6:"ReTens";i:59;s:7:"CarnaHy";i:60;s:3:"Tur";i:61;s:6:"NympRe";i:62;s:5:"PacUp";i:63;s:12:"SpangWritZul";i:64;s:5:"LonPa";i:65;s:9:"SanxScrew";i:66;s:4:"KePo";i:67;s:3:"Eno";i:68;s:4:"Rich";i:69;s:9:"PlushThUn";i:70;s:4:"Seid";i:71;s:4:"GaPi";i:72;s:8:"SupeUpwh";i:73;s:4:"Sest";i:74;s:9:"ResoScler";i:75;s:5:"QuRep";i:76;s:8:"SquSuspe";i:77;s:12:"ExtRefunUnwi";i:78;s:4:"FlUn";i:79;s:6:"RenSar";i:80;s:8:"PierrRet";i:81;s:7:"HarNona";i:82;s:8:"PhanPian";i:83;s:11:"MiasPoachPs";i:84;s:5:"Refor";i:85;s:5:"PrUnf";i:86;s:11:"HedeProarRe";i:87;s:13:"SpieTurnUnesc";i:88;s:13:"EmboOgallSpin";i:89;s:11:"MelanUnstWi";i:90;s:10:"FroPutatUn";i:91;s:4:"PaVa";i:92;s:8:"TanisYot";i:93;s:12:"ConDaddFulgu";i:94;s:2:"Pl";i:95;s:11:"HaKerauProt";i:96;s:12:"DecoHinWhigg";i:97;s:4:"OvTh";i:98;s:5:"Gunat";i:99;s:3:"Pot";i:100;s:5:"Virgi";i:101;s:10:"CopoDiGast";i:102;s:10:"DefHexitUn";i:103;s:14:"InertLeckeNaut";i:104;s:13:"IodomUnreeYac";i:105;s:3:"Tul";i:106;s:9:"BailaPrZe";i:107;s:12:"LaminRecoSpi";i:108;s:6:"CoPoly";i:109;s:5:"Irida";i:110;s:3:"Syl";i:111;s:4:"SuVe";i:112;s:13:"GravMiswiTher";i:113;s:4:"Shem";i:114;s:6:"SolToo";i:115;s:5:"Unsta";i:116;s:10:"ForeReTicx";i:117;s:5:"Outra";i:118;s:12:"InteRoripSph";i:119;s:8:"FroTapst";i:120;s:13:"DislMultWhore";i:121;s:9:"CirInPili";i:122;s:3:"Woo";i:123;s:12:"MustePreceRa";i:124;s:12:"PeavPolRootl";i:125;s:6:"KnopSc";i:126;s:10:"TaTormiTub";i:127;s:8:"OvPrefSu";i:128;s:9:"MetTymVen";i:129;s:6:"PreTri";i:130;s:9:"NemSatUni";i:131;s:4:"DiTe";i:132;s:7:"ManSuUn";i:133;s:10:"CoaxFurRes";i:134;s:3:"Yua";i:135;s:4:"Pygo";i:136;s:4:"Indi";i:137;s:4:"Spon";i:138;s:10:"CrumePurch";i:139;s:8:"HemTerri";i:140;s:5:"ProRe";i:141;s:10:"DispuRoyal";i:142;s:6:"SympUn";i:143;s:4:"Nons";i:144;s:5:"Trime";i:145;s:3:"Non";i:146;s:8:"ReproUnd";i:147;s:10:"ExaPhlPrec";i:148;s:4:"Prot";i:149;s:8:"CurasDev";i:150;s:9:"EchiPlica";i:151;s:11:"FairOrbiUna";i:152;s:15:"GriffPutorRepro";i:153;s:11:"CalmOtolSpo";i:154;s:8:"FigPurUn";i:155;s:9:"GlIgInhum";i:156;s:2:"Tr";i:157;s:8:"HauMetaz";i:158;s:2:"On";i:159;s:11:"PluSnapWoor";i:160;s:11:"EuShosUnbre";i:161;s:15:"FilteRocheSemim";i:162;s:9:"ReviRoUns";i:163;s:2:"Ov";i:164;s:10:"HydrJesUnd";i:165;s:10:"SipSortTur";i:166;s:5:"LixSe";i:167;s:5:"NuUra";i:168;s:8:"MelWhisp";i:169;s:9:"DiJumpyTr";i:170;s:10:"LaMurUpyok";i:171;s:3:"Pty";i:172;s:4:"Ithy";i:173;s:4:"Bloo";i:174;s:4:"UnUn";i:175;s:5:"EsSwa";i:176;s:9:"NeobOvPar";i:177;s:5:"DemLa";i:178;s:8:"NonvoUnm";i:179;s:9:"MutiRimux";i:180;s:2:"Pr";i:181;s:11:"UninVillaWh";i:182;s:3:"Unl";i:183;s:10:"ProscSownx";i:184;s:11:"HiRescUnint";i:185;s:3:"Cau";i:186;s:13:"HittoHustlSub";i:187;s:8:"JeremSer";i:188;s:10:"DeMetagPet";i:189;s:8:"MisexRig";i:190;s:10:"ChildCogRe";i:191;s:11:"HyPennoRech";i:192;s:3:"Lig";i:193;s:7:"OuUnmem";i:194;s:2:"Re";i:195;s:4:"VoWa";i:196;s:8:"UnicUnju";i:197;s:5:"SecSn";i:198;s:9:"NumSaiUnc";i:199;s:13:"DoubtHeteUnse";i:200;s:4:"Smee";i:201;s:4:"PrTh";i:202;s:7:"MoVindh";i:203;s:8:"GasZodia";i:204;s:2:"Do";i:205;s:12:"UllUnfatWord";i:206;s:7:"IconNov";i:207;s:4:"Juda";i:208;s:9:"GrReptSei";i:209;s:14:"OutpPenelPolan";i:210;s:4:"Poly";i:211;s:2:"No";i:212;s:11:"SchiUnaVera";i:213;s:4:"Ther";i:214;s:9:"EbFlooPes";i:215;s:6:"MoOchi";i:216;s:5:"Triol";i:217;s:2:"Ra";i:218;s:5:"ProTe";i:219;s:10:"EuryoGrade";i:220;s:12:"EntOligPulpy";i:221;s:8:"RepRepSa";i:222;s:7:"NeskPap";i:223;s:3:"Unf";i:224;s:9:"FreMeUntr";i:225;s:7:"PeriUlt";i:226;s:5:"Varyi";i:227;s:8:"CorrPhTo";i:228;s:8:"ProfeSmo";i:229;s:6:"SuUncr";i:230;s:4:"Rurb";i:231;s:3:"Lan";i:232;s:9:"HearSuper";i:233;s:10:"AngulTsuga";i:234;s:10:"PertUnhYer";i:235;s:11:"GrandNitNoc";i:236;s:2:"Le";i:237;s:11:"HypeOverWei";i:238;s:11:"MentSoSumle";i:239;s:6:"LaShip";i:240;s:9:"PrenUnawa";i:241;s:13:"ExceKotaPonti";i:242;s:7:"HaMelod";i:243;s:6:"PaTyUn";i:244;s:2:"Fo";i:245;s:5:"Verse";i:246;s:10:"OverProTuk";i:247;s:9:"SubvTitan";i:248;s:10:"PondeSight";i:249;s:4:"Thar";i:250;s:7:"InRedho";i:251;s:10:"ReSynuThou";i:252;s:5:"Unpaw";i:253;s:2:"We";i:254;s:4:"Peco";i:255;s:8:"PlacaRoz";i:256;s:9:"RecoUnfre";i:257;s:9:"GyHaloHyp";i:258;s:3:"Lit";i:259;s:12:"NapOverUnfor";i:260;s:13:"CrosIntraNumi";i:261;s:11:"CreHandeSup";i:262;s:2:"Sc";i:263;s:10:"MucOzVerte";i:264;s:4:"Prim";i:265;s:9:"HomMonPhi";i:266;s:5:"Unene";i:267;s:2:"Sl";i:268;s:6:"LimRec";i:269;s:5:"Trebl";i:270;s:13:"ForehNetmResp";i:271;s:9:"NonprUnbe";i:272;s:9:"CarbuRaRe";i:273;s:10:"LihyOverSt";i:274;s:10:"CrotaPurpu";i:275;s:4:"Inel";i:276;s:7:"CenMast";i:277;s:9:"SlaxTaTit";i:278;s:11:"SuTrimUncha";i:279;s:2:"Dw";i:280;s:7:"EcaudSy";i:281;s:11:"FormHeteVog";i:282;s:4:"Stra";i:283;s:13:"IncuLushePelv";i:284;s:13:"LuthUnmaWiste";i:285;s:8:"ApnSivve";i:286;s:7:"CouUnme";i:287;s:7:"PsTrepa";i:288;s:4:"Wiss";i:289;s:10:"BorHomelRi";i:290;s:7:"ThUtrVe";i:291;s:2:"Te";i:292;s:8:"SaTeaUnd";i:293;s:6:"CoHuSo";i:294;s:2:"Th";i:295;s:9:"FlObSubco";i:296;s:4:"FlPa";i:297;s:9:"OverSeagh";i:298;s:7:"LympYaw";i:299;s:7:"IrreJac";i:300;s:10:"TholUnhUnw";i:301;s:4:"DeEl";i:302;s:9:"MervePrTi";i:303;s:8:"NatPrefa";i:304;s:7:"HumInco";i:305;s:7:"RiRibby";i:306;s:8:"BalosPer";i:307;s:8:"BepluDet";i:308;s:8:"EulogRed";i:309;s:8:"HydroVar";i:310;s:6:"OvePse";i:311;s:4:"FuPa";i:312;s:2:"Da";i:313;s:6:"MaSeSh";i:314;s:7:"PanisPr";i:315;s:4:"Pudi";i:316;s:2:"Su";i:317;s:7:"HyMeSqu";i:318;s:7:"MacPlPu";i:319;s:5:"PtiWo";i:320;s:2:"Ja";i:321;s:9:"FoShutTar";i:322;s:15:"HypoiRoughTrilo";i:323;s:10:"MePhylProc";i:324;s:13:"ExpIncreSugge";i:325;s:7:"PhyllTh";i:326;s:4:"Knoc";i:327;s:12:"CrTilloUnder";i:328;s:5:"CaPen";i:329;s:8:"ItaRequi";i:330;s:10:"NitReaTrop";i:331;s:4:"Kelp";i:332;s:7:"TelepVa";i:333;s:10:"MultiTriph";i:334;s:4:"Synu";i:335;s:13:"PremRobenUnre";i:336;s:6:"OveUnp";i:337;s:5:"DraSu";i:338;s:8:"FluProar";i:339;s:5:"UnjWi";i:340;s:4:"Tume";i:341;s:5:"ApEat";i:342;s:12:"BerEsopHippo";i:343;s:4:"Weig";i:344;s:7:"GloSupe";i:345;s:5:"Morib";i:346;s:7:"HaOzoRu";i:347;s:5:"EfMys";i:348;s:7:"BuoSpot";i:349;s:5:"Unhop";i:350;s:8:"IntNaiXa";i:351;s:8:"ReSerpVi";i:352;s:7:"RaUnVer";i:353;s:6:"CuEndl";i:354;s:8:"PopiWood";i:355;s:8:"LionPrSa";i:356;s:10:"SuppUneWre";i:357;s:5:"Vaing";i:358;s:4:"Pale";i:359;s:7:"OveSpha";i:360;s:7:"MiRossx";i:361;s:4:"High";i:362;s:9:"PlaWhZama";i:363;s:11:"GalacMicPan";i:364;s:5:"Tonop";i:365;s:4:"Hera";i:366;s:8:"DermPrUn";i:367;s:5:"Branc";i:368;s:10:"StchUpVulg";i:369;s:12:"OverPecSerol";i:370;s:5:"QuaRe";i:371;s:13:"SubiSyllaUltr";i:372;s:3:"Tar";i:373;s:8:"RigwiSqu";i:374;s:4:"Wabb";i:375;s:12:"LotifSaZooto";i:376;s:5:"Strat";i:377;s:7:"DisDiLa";i:378;s:3:"Una";i:379;s:10:"LandfMesSu";i:380;s:7:"PeavRol";i:381;s:13:"GrudgPerTeuto";i:382;s:12:"ChiCrypUnflo";i:383;s:5:"Front";i:384;s:4:"Erud";i:385;s:7:"PenSqua";i:386;s:11:"BeyonCheMal";i:387;s:3:"Sli";i:388;s:4:"Uter";i:389;s:8:"CloaPenc";i:390;s:8:"PoShiUnp";i:391;s:5:"Deami";i:392;s:9:"LeucMelli";i:393;s:6:"PresQu";i:394;s:6:"HepUnr";i:395;s:9:"VioloVisc";i:396;s:10:"SeTolyWate";i:397;s:5:"Lestr";i:398;s:4:"Wood";i:399;s:10:"IntOddmeTr";i:400;s:10:"PerioRotul";i:401;s:4:"Stol";i:402;s:11:"EntomSouSyn";i:403;s:4:"Semi";i:404;s:6:"DurrUn";i:405;s:7:"DiscoOv";i:406;s:9:"PangiProl";i:407;s:9:"NapRecSqu";i:408;s:3:"Unr";i:409;s:13:"GamesTetanUns";i:410;s:8:"FalMetRa";i:411;s:14:"BataDenouSaxon";i:412;s:5:"Solvo";i:413;s:14:"ScytaTighUnjoy";i:414;s:4:"Glut";i:415;s:4:"EkFi";i:416;s:6:"ErNoPa";i:417;s:6:"LaSpal";i:418;s:11:"JoRackRepat";i:419;s:10:"ReSorreUnd";i:420;s:12:"MisenRiTextu";i:421;s:4:"Wayb";i:422;s:6:"CampQu";i:423;s:11:"BrNapolSulp";i:424;s:4:"Snow";i:425;s:2:"Mo";i:426;s:5:"Tehua";i:427;s:13:"PastPressUnwi";i:428;s:5:"Entom";i:429;s:4:"SoTr";i:430;s:4:"Soft";i:431;s:10:"ChElephUnd";i:432;s:3:"Whi";i:433;s:6:"UpkZoo";i:434;s:5:"PaPre";i:435;s:7:"LobxVal";i:436;s:2:"Di";i:437;s:7:"NarNonp";i:438;s:9:"HorUnexVe";i:439;s:5:"Summe";i:440;s:10:"CouLocoPli";i:441;s:11:"ReSnortUnas";i:442;s:7:"HeKnPre";i:443;s:5:"Shavi";i:444;s:2:"Fi";i:445;s:7:"KiOstra";i:446;s:6:"HeliJu";i:447;s:7:"MasdeNo";i:448;s:5:"Tapst";i:449;s:5:"NazVe";i:450;s:4:"Sque";i:451;s:6:"FrIcht";i:452;s:3:"Hyd";i:453;s:8:"InirLevi";i:454;s:7:"ElasIna";i:455;s:3:"Inc";i:456;s:8:"UnfonUns";i:457;s:10:"PeuceProma";i:458;s:10:"ParomPeSom";i:459;s:7:"ImpOuUn";i:460;s:7:"PeiseUn";i:461;s:3:"Tri";i:462;s:6:"IntrWa";i:463;s:13:"CapryRemigStr";i:464;s:5:"FulPr";i:465;s:9:"MuncRhomb";i:466;s:14:"LiteNaphtSarco";i:467;s:7:"TelecTr";i:468;s:8:"EngaMePr";i:469;s:5:"Minco";i:470;s:4:"HaMa";i:471;s:5:"Skitt";i:472;s:12:"MidWharWonde";i:473;s:5:"ArSto";i:474;s:5:"OrbPi";i:475;s:5:"Sulfo";i:476;s:11:"PeVoluWhalp";i:477;s:7:"MusToas";i:478;s:3:"Spi";i:479;s:6:"PhilSu";i:480;s:3:"Foc";i:481;s:11:"MansNardxPo";i:482;s:11:"RongaTheUnc";i:483;s:12:"ParsSemiSynt";i:484;s:5:"SuToc";i:485;s:7:"OsiSnib";i:486;s:3:"Dee";i:487;s:5:"Throa";i:488;s:12:"HydroReZealo";i:489;s:9:"MetapUnha";i:490;s:10:"HeHoastPen";i:491;s:3:"Het";i:492;s:4:"CaCo";i:493;s:7:"LimitPr";i:494;s:2:"Ho";i:495;s:8:"RodlVive";i:496;s:8:"UpsWatWe";i:497;s:6:"PerSin";i:498;s:4:"Righ";i:499;s:11:"OstraReUnde";i:500;s:5:"ScSko";i:501;s:3:"End";i:502;s:6:"NimPle";i:503;s:12:"QuadrUndoVet";i:504;s:6:"EkMeMu";i:505;s:12:"DefLacquTrad";i:506;s:7:"PrUndWa";i:507;s:12:"ProtoStroUnf";i:508;s:6:"ReTheo";i:509;s:3:"Pre";i:510;s:6:"OvUnfo";i:511;s:3:"Loi";i:512;s:5:"Cultu";i:513;s:5:"Fabul";i:514;s:4:"Succ";i:515;s:8:"MegaRegr";i:516;s:3:"Rur";i:517;s:3:"Ter";i:518;s:10:"ResisUnire";i:519;s:10:"HeloOveSem";i:520;s:12:"NatrSavoScaw";i:521;s:9:"MusoNeuri";i:522;s:6:"ChClia";i:523;s:5:"ParPs";i:524;s:11:"LabxMetaUnd";i:525;s:10:"SilkStuVux";i:526;s:12:"BroaScoSpouc";i:527;s:6:"PaUnde";i:528;s:10:"HovePanoRe";i:529;s:9:"InsulTric";i:530;s:9:"ExtForUnm";i:531;s:6:"FlatUn";i:532;s:3:"Var";i:533;s:11:"HypoeRevSup";i:534;s:14:"StasSubitUncry";i:535;s:13:"BattoStewSupe";i:536;s:6:"SneThe";i:537;s:6:"ConnSi";i:538;s:2:"Po";i:539;s:7:"BraQuid";i:540;s:11:"StomaUnVana";i:541;s:4:"TrUn";i:542;s:15:"SicyoStinkUnspe";i:543;s:10:"InManOcean";i:544;s:8:"IndLymPr";i:545;s:7:"OsseoVa";i:546;s:10:"HeteHyUnsu";i:547;s:6:"TricUn";i:548;s:7:"PlSeTal";i:549;s:7:"DyEpsom";i:550;s:3:"Stu";i:551;s:3:"Nem";i:552;s:5:"Spina";i:553;s:13:"EusFowlfOleag";i:554;s:8:"HeRadiSt";i:555;s:7:"NonjPar";i:556;s:8:"ChelaDai";i:557;s:10:"CommePeSem";i:558;s:9:"EhxMisOve";i:559;s:6:"FoUret";i:560;s:2:"Pi";i:561;s:4:"Inte";i:562;s:4:"GoUn";i:563;s:7:"PhilaSe";i:564;s:10:"ReaUnprVas";i:565;s:14:"ClapHuzzaWalli";i:566;s:7:"LaconTy";i:567;s:8:"EagThioc";i:568;s:3:"Uns";i:569;s:7:"DingaSe";i:570;s:2:"Ve";i:571;s:11:"ParaPyophTu";i:572;s:10:"KasbaOsteo";i:573;s:4:"Tang";i:574;s:8:"StiStrTo";i:575;s:12:"MiSubdrTurbu";i:576;s:11:"PostPrPreal";i:577;s:9:"EquHypoNi";i:578;s:9:"HiIndZeug";i:579;s:4:"Scat";i:580;s:14:"MesoSloomSpasm";i:581;s:10:"ProcoUnyie";i:582;s:4:"Nonr";i:583;s:6:"CompCo";i:584;s:4:"Kymo";i:585;s:12:"NuanPolygSel";i:586;s:11:"LeafaLegZin";i:587;s:8:"RhomVent";i:588;s:9:"PeteRaSul";i:589;s:12:"DesSemblUret";i:590;s:4:"Zoog";i:591;s:8:"ExamiPre";i:592;s:10:"AmpManoRet";i:593;s:4:"Same";i:594;s:2:"Ty";i:595;s:3:"Spr";i:596;s:4:"Eels";i:597;s:5:"Motor";i:598;s:9:"ScyphUnli";i:599;s:12:"DefGanoUnalo";i:600;s:7:"EpEstRo";i:601;s:4:"Pana";i:602;s:10:"CanaEnlSca";i:603;s:9:"HomRoamRo";i:604;s:7:"HanKeWh";i:605;s:5:"DeIch";i:606;s:8:"CapInSto";i:607;s:7:"PenneVo";i:608;s:11:"ElatTardiVi";i:609;s:7:"ManUnvi";i:610;s:10:"OutbPhPrem";i:611;s:11:"AttrBarDros";i:612;s:8:"InofUrod";i:613;s:3:"Oli";i:614;s:11:"EncImpSchai";i:615;s:4:"Palm";i:616;s:3:"Vit";i:617;s:10:"OphthPyret";i:618;s:2:"So";i:619;s:6:"ExacPr";i:620;s:7:"MonPrTr";i:621;s:12:"MaraMystUnam";i:622;s:4:"Vela";i:623;s:6:"PoSupe";i:624;s:10:"CathCoaThu";i:625;s:5:"ToUpc";i:626;s:3:"Toc";i:627;s:9:"GrePraSli";i:628;s:4:"Sams";i:629;s:8:"ChaHoMon";i:630;s:2:"My";i:631;s:7:"HypItin";i:632;s:11:"GloKoxScrib";i:633;s:9:"SalaUnpro";i:634;s:8:"ImpacSan";i:635;s:3:"Rep";i:636;s:9:"ParaPuggi";i:637;s:8:"RebSheUn";i:638;s:9:"MaObRedis";i:639;s:9:"GuStomTir";i:640;s:4:"Isos";i:641;s:9:"SopoUnUnr";i:642;s:8:"PedroUnd";i:643;s:4:"Ward";i:644;s:3:"Val";i:645;s:4:"Plat";i:646;s:6:"RotUns";i:647;s:7:"PermiRe";i:648;s:6:"ConvSc";i:649;s:4:"Knic";i:650;s:7:"HemImme";i:651;s:10:"HandOutSti";i:652;s:12:"DioEpirrNesi";i:653;s:5:"Yodel";i:654;s:11:"BrangOdUnem";i:655;s:4:"Rebr";i:656;s:3:"Sys";i:657;s:10:"PeRegrTong";i:658;s:8:"GlResiUn";i:659;s:10:"NeapxOsteo";i:660;s:9:"LappPsRev";i:661;s:12:"PrePromTrica";i:662;s:5:"Prota";i:663;s:6:"TablUr";i:664;s:9:"CimbMenta";i:665;s:12:"DesHoaxxUnca";i:666;s:10:"LithoSteat";i:667;s:4:"Goyx";i:668;s:13:"PignoPrehSigx";i:669;s:2:"St";i:670;s:13:"BacDigreMontr";i:671;s:7:"TraUnro";i:672;s:7:"HikRiva";i:673;s:7:"CoPeUnp";i:674;s:5:"Thoro";i:675;s:5:"Vibro";i:676;s:7:"HoRudim";i:677;s:5:"Satis";i:678;s:14:"SulkiSyndTugri";i:679;s:8:"AnEpSius";i:680;s:7:"MyPomWe";i:681;s:11:"PathPepsiPl";i:682;s:7:"PerPlut";i:683;s:8:"MorPrete";i:684;s:8:"PiProUnf";i:685;s:7:"SleUnem";i:686;s:10:"BimPolypVa";i:687;s:6:"DzeThi";i:688;s:6:"DolEmp";i:689;s:5:"Skibb";i:690;s:3:"Pen";i:691;s:12:"SpinSymWaysi";i:692;s:9:"CamHomoTe";i:693;s:7:"HemopRa";i:694;s:13:"ConsParsoReri";i:695;s:7:"HumorKh";i:696;s:3:"Meg";i:697;s:9:"HuckInval";i:698;s:5:"CoSto";i:699;s:5:"Wykeh";i:700;s:8:"PeUnscUn";i:701;s:13:"LaminLeisOvov";i:702;s:4:"Unco";i:703;s:5:"HiUnd";i:704;s:9:"ForMeUnri";i:705;s:6:"RhymUn";i:706;s:8:"UnderUrn";i:707;s:13:"QuadrRuntTort";i:708;s:9:"HemiRepur";i:709;s:9:"EuPiSemin";i:710;s:6:"SubcSu";i:711;s:9:"MorNitcSl";i:712;s:10:"PreteVitel";i:713;s:7:"ReducTr";i:714;s:7:"NonSkew";i:715;s:5:"Rejec";i:716;s:6:"ImNuTe";i:717;s:10:"FlaForeHyp";i:718;s:5:"Stech";i:719;s:11:"CypHasteSpo";i:720;s:5:"Ocell";i:721;s:10:"ObtaiThrim";i:722;s:14:"DihyPhenaSmili";i:723;s:14:"ForwaPeripSius";i:724;s:15:"DropcOlivaPippe";i:725;s:10:"SubbeWhite";i:726;s:5:"Pneum";i:727;s:8:"OpprRaga";i:728;s:5:"DesDi";i:729;s:8:"HelioSup";i:730;s:10:"ParaPiSauc";i:731;s:3:"Sor";i:732;s:6:"SeptSu";i:733;s:5:"DrPot";i:734;s:8:"CoOutWau";i:735;s:6:"CeWold";i:736;s:8:"VivenWhi";i:737;s:10:"BandiQuadr";i:738;s:13:"MesiPlanSunni";i:739;s:3:"Urs";i:740;s:10:"KiUnchUnes";i:741;s:12:"MynaxSeTroch";i:742;s:4:"Shut";i:743;s:7:"ObSpurg";i:744;s:3:"Boy";i:745;s:10:"GuitaUnsub";i:746;s:4:"GrPr";i:747;s:11:"MonStrumSus";i:748;s:9:"DiSenTurr";i:749;s:4:"Unop";i:750;s:10:"CansoPrion";i:751;s:12:"HieMesaParoa";i:752;s:3:"Sil";i:753;s:8:"OpPoScio";i:754;s:9:"TrumpTwUn";i:755;s:14:"DoomsEtymoUnch";i:756;s:5:"HypUn";i:757;s:11:"InterMetOoz";i:758;s:9:"NondOcSta";i:759;s:9:"OinOtorRe";i:760;s:11:"IndiJapaUnd";i:761;s:8:"SnufUnsc";i:762;s:2:"Gr";i:763;s:10:"OpePupilUn";i:764;s:12:"IrreLiqueTra";i:765;s:5:"Uncol";i:766;s:4:"MeVe";i:767;s:6:"BlepVa";i:768;s:8:"PreSubva";i:769;s:6:"MaPrep";i:770;s:7:"PuUnnat";i:771;s:6:"JuriKh";i:772;s:9:"CoprGastr";i:773;s:9:"EuergOvSi";i:774;s:5:"Hyper";i:775;s:9:"StoSulpUn";i:776;s:5:"Unfop";i:777;s:4:"Retr";i:778;s:8:"BunchPos";i:779;s:5:"NorSu";i:780;s:12:"HordRunnUnea";i:781;s:7:"InterLi";i:782;s:11:"EnvOdonStew";i:783;s:10:"DiaMesNonc";i:784;s:8:"HematThe";i:785;s:3:"Ski";i:786;s:5:"Kodak";i:787;s:9:"DoRedouRe";i:788;s:2:"Sp";i:789;s:5:"Unfil";i:790;s:9:"RubicWarb";i:791;s:9:"HemiUinta";i:792;s:7:"OulSupr";i:793;s:10:"HieMuPelop";i:794;s:3:"Dis";i:795;s:5:"Telot";i:796;s:3:"Cza";i:797;s:5:"KidLa";i:798;s:4:"Nase";i:799;s:3:"Sul";i:800;s:8:"CubbyRed";i:801;s:8:"OrPectTo";i:802;s:7:"SeSvaUp";i:803;s:6:"SerWit";i:804;s:3:"Spo";i:805;s:10:"MeProbSmyr";i:806;s:5:"Tunab";i:807;s:9:"HolInVene";i:808;s:10:"ClaInlieRe";i:809;s:4:"Carb";i:810;s:13:"ReseSteeUncon";i:811;s:9:"UnbarWhit";i:812;s:11:"OstPhthiStu";i:813;s:5:"Whenn";i:814;s:4:"Ungl";i:815;s:3:"Sch";i:816;s:9:"PictuReco";i:817;s:10:"LyPremoTyp";i:818;s:7:"PagStal";i:819;s:5:"Sogdi";i:820;s:5:"UnUno";i:821;s:5:"Anast";i:822;s:11:"IsocMasOtos";i:823;s:7:"RayReli";i:824;s:4:"Umbe";i:825;s:7:"CortMil";i:826;s:8:"ProSaddl";i:827;s:6:"LigWob";i:828;s:6:"ReSawi";i:829;s:3:"Inw";i:830;s:5:"Recre";i:831;s:7:"PantaPs";i:832;s:7:"LuNoUna";i:833;s:9:"FeliPoTar";i:834;s:13:"RajaxSoweWhor";i:835;s:6:"HeHorn";i:836;s:2:"Vi";i:837;s:5:"Sulph";i:838;s:7:"ScalSco";i:839;s:6:"SuSwin";i:840;s:12:"PleTempUnspl";i:841;s:4:"Disl";i:842;s:6:"PuTach";i:843;s:5:"Methy";i:844;s:9:"StThreeWa";i:845;s:8:"MetaPeri";i:846;s:12:"KoMuttoPseud";i:847;s:3:"Yot";i:848;s:8:"UnleaUnt";i:849;s:12:"NoTammyZoosp";i:850;s:8:"TendUnre";i:851;s:11:"ChunSiSlate";i:852;s:11:"ReverTheUni";i:853;s:14:"SynoWhipsYokel";i:854;s:4:"Undi";i:855;s:10:"HoLodgeTum";i:856;s:10:"GoralLaWul";i:857;s:7:"OnPrede";i:858;s:6:"LieSac";i:859;s:9:"BemuGyrin";i:860;s:3:"Ung";i:861;s:4:"Retu";i:862;s:9:"DesipSavi";i:863;s:8:"RouSandi";i:864;s:4:"Prec";i:865;s:10:"GastPrStra";i:866;s:4:"Muli";i:867;s:6:"DeFebr";i:868;s:11:"IcacPoltrUn";i:869;s:5:"Trach";i:870;s:7:"MisadUn";i:871;s:15:"GaywiOverdScorp";i:872;s:11:"ChiliIsSono";i:873;s:4:"Than";i:874;s:4:"Tube";i:875;s:5:"Mermi";i:876;s:10:"KuskUbUnwo";i:877;s:9:"GeHeMicro";i:878;s:2:"Py";i:879;s:2:"Vo";i:880;s:9:"FrThTrans";i:881;s:13:"RipiSubuWorme";i:882;s:11:"AssigCoenOu";i:883;s:4:"DiUn";i:884;s:12:"NonsRotiWhat";i:885;s:5:"NoUni";i:886;s:6:"OmPhle";i:887;s:4:"Para";i:888;s:3:"Rev";i:889;s:4:"ShSt";i:890;s:8:"MahaPunn";i:891;s:7:"StatoUn";i:892;s:10:"GalliNecta";i:893;s:12:"JuMintmWouch";i:894;s:9:"SempeSpir";i:895;s:13:"OphthSemidSty";i:896;s:8:"CenTripe";i:897;s:8:"SipunUne";i:898;s:5:"Uvulo";i:899;s:4:"Verb";i:900;s:8:"CuprMaPa";i:901;s:4:"PhUn";i:902;s:7:"PulTher";i:903;s:5:"Suppo";i:904;s:8:"BombCeGl";i:905;s:11:"CoccPaSerot";i:906;s:6:"CoExTr";i:907;s:9:"PaPatPedi";i:908;s:5:"Sillo";i:909;s:10:"DrysPyreSy";i:910;s:7:"NoVermi";i:911;s:8:"SaddVuln";i:912;s:10:"NonTessUns";i:913;s:8:"HeSemiUn";i:914;s:5:"ReUns";i:915;s:10:"IntePiSiki";i:916;s:6:"BoleCo";i:917;s:6:"DisgSa";i:918;s:3:"Rem";i:919;s:9:"ChroOverd";i:920;s:3:"Unt";i:921;s:7:"HeSands";i:922;s:6:"PrShTi";i:923;s:10:"NecroPrere";i:924;s:10:"OoeciPhUns";i:925;s:3:"Ham";i:926;s:7:"GaReful";i:927;s:7:"BunyPed";i:928;s:13:"IntoParalZigz";i:929;s:5:"Jacks";i:930;s:2:"Wr";i:931;s:6:"SalUnc";i:932;s:9:"GeSlovTic";i:933;s:9:"NeighPain";i:934;s:3:"Und";i:935;s:6:"OutTri";i:936;s:4:"Teii";i:937;s:5:"Taint";i:938;s:5:"Guill";i:939;s:10:"AzonxFaMoa";i:940;s:10:"InterSeUnc";i:941;s:7:"OveSobr";i:942;s:8:"ReTrihUn";i:943;s:7:"CyWordi";i:944;s:9:"TurnaWash";i:945;s:8:"RevoUngy";i:946;s:6:"PhoRer";i:947;s:5:"Whizz";i:948;s:13:"ProUndesUnsal";i:949;s:11:"StitcUpWitx";i:950;s:4:"Pert";i:951;s:3:"Tac";i:952;s:9:"EmulsProu";i:953;s:6:"UnwaUs";i:954;s:8:"FelTaxia";i:955;s:8:"ReResScl";i:956;s:6:"SemUnp";i:957;s:8:"OvTopWhi";i:958;s:8:"IntReaWo";i:959;s:4:"PlTo";i:960;s:14:"OveraPositUnpl";i:961;s:5:"Trest";i:962;s:10:"BixaChloVi";i:963;s:6:"CharGr";i:964;s:7:"ImSemUn";i:965;s:5:"Leadw";i:966;s:4:"Ples";i:967;s:11:"PsStepVirul";i:968;s:8:"NoOoSpir";i:969;s:10:"HeraImWhee";i:970;s:6:"SaTrag";i:971;s:10:"ForweSubre";i:972;s:11:"BegloEffHyd";i:973;s:8:"ChatTido";i:974;s:5:"RiSup";i:975;s:5:"Nonex";i:976;s:10:"JonMaParap";i:977;s:10:"ChapeDovEn";i:978;s:5:"Twizz";i:979;s:7:"MuckPra";i:980;s:8:"HurklLus";i:981;s:10:"MortProVal";i:982;s:9:"FroLimiRe";i:983;s:9:"HoliMimTr";i:984;s:5:"Besca";i:985;s:7:"MetroRu";i:986;s:13:"SawfSemibUngu";i:987;s:9:"ForePicco";i:988;s:9:"ParSysTee";i:989;s:4:"Pres";i:990;s:7:"HeJaile";i:991;s:3:"Ref";i:992;s:6:"MusgRe";i:993;s:3:"Her";i:994;s:4:"Hyet";i:995;s:7:"HelioPo";i:996;s:6:"BasiYa";i:997;s:8:"ProtVell";i:998;s:7:"NonoWea";i:999;s:7:"SubrUnm";i:1000;s:11:"ConEboniVer";i:1001;s:3:"Uni";i:1002;s:9:"IdePlasUn";i:1003;s:9:"PronStabi";i:1004;s:14:"QuinSquabUnrig";i:1005;s:2:"Ob";i:1006;s:7:"NatchOx";i:1007;s:5:"IsXan";i:1008;s:6:"JaReel";i:1009;s:8:"ProRhota";i:1010;s:4:"Vell";i:1011;s:5:"Hemop";i:1012;s:5:"Searc";i:1013;s:8:"EntaUnst";i:1014;s:12:"MonolNoRajax";i:1015;s:13:"DanPolynStere";i:1016;s:13:"TubUnattZogan";i:1017;s:5:"Raini";i:1018;s:5:"SpiSp";i:1019;s:8:"PosolPro";i:1020;s:6:"AlysEc";i:1021;s:10:"PoRedetThe";i:1022;s:10:"RecoiUglil";i:1023;s:10:"AntifCrIns";i:1024;s:3:"Pse";i:1025;s:12:"NonuSemSeria";i:1026;s:12:"PanSourUncom";i:1027;s:7:"FiMatan";i:1028;s:6:"RattVe";i:1029;s:6:"SpaTom";i:1030;s:12:"GebbiPasTess";i:1031;s:9:"PhotUncUn";i:1032;s:6:"RicTar";i:1033;s:10:"PlagiSeque";i:1034;s:3:"Zer";i:1035;s:5:"Denom";i:1036;s:14:"LentiPrecaUnpr";i:1037;s:6:"PathSu";i:1038;s:4:"PuSh";i:1039;s:10:"LaLinoPrem";i:1040;s:11:"IsPteryViru";i:1041;s:6:"HerbLo";i:1042;s:10:"FebKobusOv";i:1043;s:8:"ChiloWai";i:1044;s:12:"PosTetraTong";i:1045;s:5:"StUni";i:1046;s:11:"SuccTriliUn";i:1047;s:12:"PertSymmUnsu";i:1048;s:8:"UnshrZea";i:1049;s:6:"KonMar";i:1050;s:8:"LimTidbi";i:1051;s:6:"IsMePe";i:1052;s:7:"MarWher";i:1053;s:8:"OssifPen";i:1054;s:3:"Nig";i:1055;s:9:"NoRevuWar";i:1056;s:14:"CatalNympUnfal";i:1057;s:4:"Prep";i:1058;s:2:"Pe";i:1059;s:13:"InsciProSulph";i:1060;s:5:"DysHa";i:1061;s:2:"Wa";i:1062;s:5:"Goldb";i:1063;s:8:"DiHaWhey";i:1064;s:5:"SpThr";i:1065;s:8:"MesoUncu";i:1066;s:8:"SaUncoVi";i:1067;s:5:"SemUn";i:1068;s:13:"EmplHumanPitt";i:1069;s:5:"AmpHa";i:1070;s:6:"LoPrec";i:1071;s:9:"OnRegleSn";i:1072;s:8:"CurDesUn";i:1073;s:7:"NasUnex";i:1074;s:9:"ChulPrudy";i:1075;s:7:"SacUnUp";i:1076;s:10:"InMesMisca";i:1077;s:5:"StrVe";i:1078;s:10:"ForHymeTap";i:1079;s:4:"Ruin";i:1080;s:9:"KurbaOxhe";i:1081;s:9:"DamScyTet";i:1082;s:3:"Int";i:1083;s:9:"ClypeHair";i:1084;s:10:"DoNicolSpe";i:1085;s:6:"PraUne";i:1086;s:2:"He";i:1087;s:9:"SigniUnbr";i:1088;s:10:"IntePlaTor";i:1089;s:3:"Kol";i:1090;s:11:"MesRomUnshu";i:1091;s:5:"Calfl";i:1092;s:5:"Pyrol";i:1093;s:5:"Trous";i:1094;s:7:"LanSpew";i:1095;s:12:"SylTyranUnre";i:1096;s:8:"PodRetra";i:1097;s:2:"Ox";i:1098;s:6:"PosTel";i:1099;s:5:"SynWa";i:1100;s:6:"DauUnb";i:1101;s:13:"PseuThorUropt";i:1102;s:13:"ScleThermVirt";i:1103;s:3:"Imp";i:1104;s:4:"Repr";i:1105;s:8:"SacriSub";i:1106;s:6:"GrSolo";i:1107;s:8:"OvPoShad";i:1108;s:11:"ConfParisRe";i:1109;s:8:"CnidoUna";i:1110;s:2:"Gl";i:1111;s:3:"Inf";i:1112;s:11:"OxyneRehaWi";i:1113;s:3:"Cou";i:1114;s:4:"PhSh";i:1115;s:7:"ExcSubt";i:1116;s:7:"PlProto";i:1117;s:8:"HelleWor";i:1118;s:10:"MyOvertThu";i:1119;s:4:"Unsu";i:1120;s:10:"UnUnindWor";i:1121;s:4:"Tabo";i:1122;s:8:"SoSphTuz";i:1123;s:7:"SeptSpi";i:1124;s:10:"IsoOcRedic";i:1125;s:5:"Shane";i:1126;s:8:"HydroSem";i:1127;s:14:"MattyNeossSemi";i:1128;s:9:"MetaPeSac";i:1129;s:5:"Under";i:1130;s:11:"MonopNodiSk";i:1131;s:5:"RecSu";i:1132;s:10:"PensiSnipt";i:1133;s:7:"EnchoKe";i:1134;s:8:"DisDrTae";i:1135;s:5:"Indem";i:1136;s:5:"DefUp";i:1137;s:5:"EpiIn";i:1138;s:12:"GastrSephSub";i:1139;s:8:"FosUnlie";i:1140;s:5:"Tekya";i:1141;s:8:"NonfeZib";i:1142;s:12:"StyloSubmTha";i:1143;s:7:"UnhUnst";i:1144;s:12:"DispNontPern";i:1145;s:4:"Cary";i:1146;s:2:"Fr";i:1147;s:2:"Cr";i:1148;s:7:"PhlebTs";i:1149;s:8:"FletcUne";i:1150;s:8:"HypeMaUn";i:1151;s:12:"ImprMalSuper";i:1152;s:5:"SacUn";i:1153;s:11:"RubSchatSup";i:1154;s:7:"HeSurfb";i:1155;s:13:"MagTaipoTript";i:1156;s:7:"LesSubs";i:1157;s:7:"PrTreTr";i:1158;s:7:"IvUnUns";i:1159;s:10:"HidagIntRe";i:1160;s:11:"NoSaleSpinu";i:1161;s:5:"Myalg";i:1162;s:10:"DeLenSubla";i:1163;s:7:"SurgyTr";i:1164;s:5:"OuPes";i:1165;s:7:"MarMuWe";i:1166;s:6:"FestFl";i:1167;s:7:"PhQuiUn";i:1168;s:5:"Scabr";i:1169;s:5:"SpTra";i:1170;s:4:"Dact";i:1171;s:5:"Reorg";i:1172;s:8:"LuPapSab";i:1173;s:3:"Epi";i:1174;s:11:"HairSpStrat";i:1175;s:13:"IlleiSemicSha";i:1176;s:14:"DespPropoRepla";i:1177;s:5:"Chymi";i:1178;s:7:"FoUnwre";i:1179;s:5:"Unper";i:1180;s:3:"Jel";i:1181;s:3:"Ego";i:1182;s:6:"CryPre";i:1183;s:5:"PrUnt";i:1184;s:2:"Rh";i:1185;s:6:"FaVamp";i:1186;s:8:"WesteYel";i:1187;s:4:"Limn";i:1188;s:9:"ClamHeUra";i:1189;s:10:"CrooEupShe";i:1190;s:11:"ChDandiMort";i:1191;s:7:"EsEveNe";i:1192;s:14:"LatitToniVoidl";i:1193;s:10:"HadLitNong";i:1194;s:4:"Psyc";i:1195;s:3:"Oct";i:1196;s:3:"Une";i:1197;s:9:"RaScuseSp";i:1198;s:8:"InherRor";i:1199;s:6:"ParStr";i:1200;s:7:"MetaPau";i:1201;s:9:"SeiUnUnva";i:1202;s:9:"TaUntVerj";i:1203;s:8:"CheTelot";i:1204;s:10:"InceKiMurz";i:1205;s:15:"DomicImoliLiter";i:1206;s:5:"CopDe";i:1207;s:3:"Amo";i:1208;s:10:"ParalUnent";i:1209;s:2:"Ea";i:1210;s:4:"Unbi";i:1211;s:13:"PachPlagiTrep";i:1212;s:4:"LyUn";i:1213;s:2:"Wo";i:1214;s:7:"PlatRom";i:1215;s:12:"ChiQuitSulph";i:1216;s:11:"AuPennoRoll";i:1217;s:13:"LarriPallPron";i:1218;s:8:"MailpPyg";i:1219;s:4:"PiTa";i:1220;s:4:"Unam";i:1221;s:9:"HedgeStra";i:1222;s:6:"GynKit";i:1223;s:2:"Sh";i:1224;s:10:"CubInStati";i:1225;s:2:"Mi";i:1226;s:8:"PeRefSou";i:1227;s:5:"InInf";i:1228;s:5:"OveVi";i:1229;s:10:"ReacUnsVal";i:1230;s:5:"Silve";i:1231;s:5:"GooUn";i:1232;s:6:"ThVict";i:1233;s:10:"MalPerShin";i:1234;s:4:"Chuc";i:1235;s:7:"RiaSerr";i:1236;s:11:"OverPhyUlli";i:1237;s:8:"CepEnvie";i:1238;s:8:"ManSubmi";i:1239;s:10:"CarErysPyr";i:1240;s:8:"PlaSpecu";i:1241;s:9:"DraOveOve";i:1242;s:9:"GiddyHyWe";i:1243;s:4:"Cala";i:1244;s:5:"ChHyr";i:1245;s:10:"HeterPoSyn";i:1246;s:7:"FrMaPer";i:1247;s:7:"SeeUnbe";i:1248;s:5:"Redto";i:1249;s:13:"FonsxIllPreli";i:1250;s:7:"InOvers";i:1251;s:5:"Thril";i:1252;s:10:"DisplMicVo";i:1253;s:9:"ImpeInSem";i:1254;s:4:"Osch";i:1255;s:4:"Duog";i:1256;s:9:"NonPuShre";i:1257;s:14:"OstreSergToolm";i:1258;s:9:"HyakMahPa";i:1259;s:7:"PalRefr";i:1260;s:7:"PoTroch";i:1261;s:12:"GiddyInWhipp";i:1262;s:7:"BroDrys";i:1263;s:6:"IllRef";i:1264;s:10:"CommaLabSt";i:1265;s:5:"Apoll";i:1266;s:2:"Ne";i:1267;s:3:"Kat";i:1268;s:7:"PoTinct";i:1269;s:10:"NasSynosUn";i:1270;s:4:"Pant";i:1271;s:5:"Unmis";i:1272;s:5:"UnVin";i:1273;s:4:"Ordi";i:1274;s:10:"MazocPrebe";i:1275;s:3:"Unw";i:1276;s:4:"TyUn";i:1277;s:6:"MetaUn";i:1278;s:4:"Thri";i:1279;s:11:"OokiSilkwSt";i:1280;s:5:"OvPed";i:1281;s:6:"MesoSq";i:1282;s:3:"Per";i:1283;s:9:"ImTraWrit";i:1284;s:2:"In";i:1285;s:9:"NaSliceWh";i:1286;s:8:"PolTherm";i:1287;s:4:"Topm";i:1288;s:9:"CasPnTita";i:1289;s:5:"Unsur";i:1290;s:6:"HeInPr";i:1291;s:8:"AtGiMisp";i:1292;s:14:"BathuEpicyPhor";i:1293;s:5:"Windb";i:1294;s:7:"DoDomUn";i:1295;s:7:"LetPrer";i:1296;s:4:"Subu";i:1297;s:12:"MacroOrtSwee";i:1298;s:11:"DarapEelHoo";i:1299;s:7:"UnaWeWu";i:1300;s:10:"HydrJoiOve";i:1301;s:4:"Rein";i:1302;s:9:"GuarPedia";i:1303;s:5:"Primo";i:1304;s:5:"Pregn";i:1305;s:2:"Tw";i:1306;s:11:"KinMemorTou";i:1307;s:14:"LandbMantSuper";i:1308;s:4:"Pers";i:1309;s:5:"DiRho";i:1310;s:9:"KabaRinne";i:1311;s:2:"To";i:1312;s:4:"Stom";i:1313;s:5:"Movie";i:1314;s:12:"ProtoSpoUnsy";i:1315;s:12:"LiceSharUpso";i:1316;s:9:"SlStereWo";i:1317;s:4:"Tubb";i:1318;s:7:"EradiPe";i:1319;s:10:"OrthoVolum";i:1320;s:10:"LeaMattNon";i:1321;s:11:"PeSemiThrom";i:1322;s:3:"Yar";i:1323;s:12:"DetraGalvSki";i:1324;s:6:"ThyUna";i:1325;s:8:"DiRainRo";i:1326;s:12:"HarakHerVide";i:1327;s:5:"Plata";i:1328;s:4:"Occi";i:1329;s:7:"PaQuadr";i:1330;s:7:"SanToot";i:1331;s:10:"DecExploIn";i:1332;s:2:"Cl";i:1333;s:7:"UnbeUns";i:1334;s:6:"OphRel";i:1335;s:13:"SangSphenUnco";i:1336;s:4:"Unli";i:1337;s:15:"HorneNonopSnake";i:1338;s:11:"PhiSubjUnim";i:1339;s:6:"SaniSu";i:1340;s:3:"Man";i:1341;s:7:"UnUntre";i:1342;s:10:"DihRecapUn";i:1343;s:9:"IntNonrTr";i:1344;s:10:"CoryLerrWh";i:1345;s:10:"GimMonRetr";i:1346;s:4:"Phry";i:1347;s:6:"MyoWea";i:1348;s:3:"Was";i:1349;s:6:"GrHemi";i:1350;s:9:"InInfePre";i:1351;s:4:"NuPn";i:1352;s:14:"ChlorHeteProca";i:1353;s:7:"FroImpu";i:1354;s:6:"DiStee";i:1355;s:7:"CarnUnd";i:1356;s:11:"SentiTimeUn";i:1357;s:11:"ScrawVeYach";i:1358;s:5:"Utero";i:1359;s:8:"InfOfPho";i:1360;s:10:"MolSulphUn";i:1361;s:6:"CaDePr";i:1362;s:5:"InRea";i:1363;s:4:"Scra";i:1364;s:12:"JaPosteUnder";i:1365;s:7:"MuscVer";i:1366;s:12:"DimerEupaMeg";i:1367;s:4:"Grap";i:1368;s:11:"CadeHectoHo";i:1369;s:6:"SulThi";i:1370;s:4:"Hama";i:1371;s:9:"LymOuRemn";i:1372;s:4:"Mart";i:1373;s:6:"KaTrum";i:1374;s:3:"Way";i:1375;s:11:"KitLatPomon";i:1376;s:14:"IsotLeiomTrich";i:1377;s:9:"SteTarrUn";i:1378;s:10:"DeFoxTurnc";i:1379;s:8:"LeucNoPe";i:1380;s:9:"PeriRelSi";i:1381;s:9:"NoParTien";i:1382;s:6:"PupUna";i:1383;s:5:"PanSa";i:1384;s:9:"DeoKerxTu";i:1385;s:5:"Unwor";i:1386;s:2:"Mu";i:1387;s:14:"IodosOverWrith";i:1388;s:4:"HuPa";i:1389;s:7:"JocLame";i:1390;s:4:"Mame";i:1391;s:5:"Unspi";i:1392;s:11:"IthyLeisPos";i:1393;s:7:"ParapWa";i:1394;s:7:"TriliUn";i:1395;s:8:"TarTheer";i:1396;s:5:"Frust";i:1397;s:6:"GuTrun";i:1398;s:9:"MembrReco";i:1399;s:10:"KwartRheUr";i:1400;s:3:"Sem";i:1401;s:4:"Naph";i:1402;s:11:"ParRecUsuar";i:1403;s:12:"LegaMallPowd";i:1404;s:8:"OveProet";i:1405;s:11:"FinesOvXeno";i:1406;s:10:"PresuRattl";i:1407;s:3:"Dem";i:1408;s:13:"FrienRevSeque";i:1409;s:12:"BriHyperKrem";i:1410;s:13:"HomoMoribTouc";i:1411;s:4:"Wauk";i:1412;s:12:"SupeTonsiUns";i:1413;s:8:"KikuScyt";i:1414;s:5:"Scare";i:1415;s:12:"HippoPlPneum";i:1416;s:8:"GraNonfo";i:1417;s:7:"EuphVel";i:1418;s:5:"Undar";i:1419;s:12:"GampShriSurr";i:1420;s:2:"Wi";i:1421;s:7:"OsmPrUn";i:1422;s:10:"TarahUnabi";i:1423;s:7:"CaDawTy";i:1424;s:10:"GeomeLater";i:1425;s:8:"GelPaSup";i:1426;s:8:"RegreVol";i:1427;s:7:"LepMeio";i:1428;s:12:"HeMesolSpeec";i:1429;s:4:"Pred";i:1430;s:12:"EpParagRedex";i:1431;s:10:"MiPhilTyro";i:1432;s:8:"ParaSaga";i:1433;s:6:"EstNih";i:1434;s:11:"OrdiRevolSe";i:1435;s:3:"Pla";i:1436;s:12:"LepiRetSuper";i:1437;s:12:"HoloOperWild";i:1438;s:6:"InteVe";i:1439;s:4:"Whit";i:1440;s:11:"JuMinkoXant";i:1441;s:10:"BowmHelMal";i:1442;s:11:"KetazPrVeld";i:1443;s:4:"Intr";i:1444;s:7:"LeucSub";i:1445;s:7:"OveUnUn";i:1446;s:7:"MyelPos";i:1447;s:6:"PrSche";i:1448;s:2:"Ze";i:1449;s:5:"Pyrrh";i:1450;s:5:"ChaPh";i:1451;s:8:"RelUltra";i:1452;s:5:"Prere";i:1453;s:7:"FoProSe";i:1454;s:11:"MyoUnyiWeir";i:1455;s:8:"InLiTaro";i:1456;s:8:"QuintSty";i:1457;s:4:"Euch";i:1458;s:5:"Consu";i:1459;s:5:"Incon";i:1460;s:11:"HypoMagnPyc";i:1461;s:5:"PeUbi";i:1462;s:13:"HistoIlokaRas";i:1463;s:8:"ManoMiPl";i:1464;s:11:"LeathRedeSu";i:1465;s:11:"PalStucToba";i:1466;s:13:"HabitPriSaber";i:1467;s:5:"Table";i:1468;s:11:"SeneSubcToo";i:1469;s:11:"DiOffeSuper";i:1470;s:4:"Ruff";i:1471;s:12:"HyperPoiStor";i:1472;s:13:"BigwDirtUnasp";i:1473;s:3:"Pro";i:1474;s:6:"EpiExe";i:1475;s:10:"HypMetNahu";i:1476;s:10:"DimiValeWi";i:1477;s:4:"FiRa";i:1478;s:11:"DoSomnaUnta";i:1479;s:8:"BupMazam";i:1480;s:13:"CurtaOntSuppl";i:1481;s:5:"Waben";i:1482;s:10:"EpicrLymph";i:1483;s:7:"CasUnca";i:1484;s:8:"HatchWhe";i:1485;s:8:"HarmMole";i:1486;s:3:"Thi";i:1487;s:3:"Hal";i:1488;s:10:"CisSpuddVe";i:1489;s:11:"LiMismiPsyc";i:1490;s:11:"PolliSiVign";i:1491;s:10:"SeducShSta";i:1492;s:8:"SaugScab";i:1493;s:6:"IslaTr";i:1494;s:10:"ComImParat";i:1495;s:4:"Peri";i:1496;s:11:"GritLaRambe";i:1497;s:7:"EddZygo";i:1498;s:12:"BumInteMidma";i:1499;s:15:"InterOrnitPreme";i:1500;s:9:"PrSugSulf";i:1501;s:8:"IdePsych";i:1502;s:4:"Proc";i:1503;s:7:"CosmTri";i:1504;s:6:"CommUn";i:1505;s:5:"Sawfl";i:1506;s:5:"CusSe";i:1507;s:7:"TrUlste";i:1508;s:4:"Thyr";i:1509;s:5:"Persp";i:1510;s:9:"PedulSiVi";i:1511;s:6:"AmMyrm";i:1512;s:5:"Physi";i:1513;s:8:"SurcTolx";i:1514;s:5:"Cytop";i:1515;s:5:"GaRav";i:1516;s:13:"CarMelanUnuni";i:1517;s:6:"EdSubt";i:1518;s:7:"DotGeph";i:1519;s:7:"InNativ";i:1520;s:5:"Ozoty";i:1521;s:7:"PriScal";i:1522;s:4:"SeUn";i:1523;s:13:"FlexuIntYoudi";i:1524;s:3:"Tra";i:1525;s:8:"SubbiTap";i:1526;s:11:"ChloDrucGra";i:1527;s:8:"MarrMimi";i:1528;s:9:"DooSeSpec";i:1529;s:7:"GauzySe";i:1530;s:4:"Chio";i:1531;s:10:"RatafThVip";i:1532;s:6:"FruiGy";i:1533;s:15:"FoddeSulfoZeuct";i:1534;s:7:"BromiRe";i:1535;s:8:"OvPhrUlt";i:1536;s:3:"Unv";i:1537;s:5:"Lidfl";i:1538;s:5:"Uncon";i:1539;s:12:"SapodUnblUno";i:1540;s:13:"DomiHakimMola";i:1541;s:4:"Unth";i:1542;s:5:"FoOve";i:1543;s:9:"LendePoUn";i:1544;s:14:"ConfScrofTelle";i:1545;s:11:"MonoOutboTe";i:1546;s:11:"FeckTeUnfea";i:1547;s:5:"Wopsx";i:1548;s:8:"StaSulfa";i:1549;s:9:"CoalInapa";i:1550;s:6:"RisSqu";i:1551;s:12:"SubtWacexZyg";i:1552;s:11:"MonoMorSqua";i:1553;s:7:"DisFrTr";i:1554;s:9:"BonaIllit";i:1555;s:4:"Pron";i:1556;s:13:"LuculThaThutt";i:1557;s:3:"Unm";i:1558;s:9:"FelsOpine";i:1559;s:13:"LumpiStaiUnhu";i:1560;s:12:"HoIneliUnlat";i:1561;s:2:"Or";i:1562;s:11:"GoatrNoUnes";i:1563;s:12:"AlBenthPsych";i:1564;s:4:"Insu";i:1565;s:5:"Unver";i:1566;s:4:"Eulo";i:1567;s:7:"OmThroa";i:1568;s:9:"HyUnsUnwo";i:1569;s:5:"PeRes";i:1570;s:5:"DecTe";i:1571;s:6:"SpoZym";i:1572;s:5:"Satyr";i:1573;s:4:"Subc";i:1574;s:2:"As";i:1575;s:3:"Ret";i:1576;s:7:"JarblMy";i:1577;s:4:"Noni";i:1578;s:10:"BalusCacon";i:1579;s:10:"HazReiWard";i:1580;s:7:"QuisThe";i:1581;s:12:"SawflStrumTr";i:1582;s:4:"Witc";i:1583;s:12:"BenzoCheOver";i:1584;s:4:"Preo";i:1585;s:11:"DisseGaName";i:1586;s:8:"EleReadm";i:1587;s:8:"UnaUnZig";i:1588;s:7:"EskNeur";i:1589;s:9:"DisFanSco";i:1590;s:8:"FesJolRe";i:1591;s:10:"ConfuDoPre";i:1592;s:12:"CaptPseuRush";i:1593;s:12:"ScreXenomYar";i:1594;s:9:"RidiTarVa";i:1595;s:10:"HippMbaSaf";i:1596;s:14:"ElateNonreOver";i:1597;s:6:"MiPara";i:1598;s:10:"PterPuSear";i:1599;s:5:"Marsh";i:1600;s:5:"Stram";i:1601;s:8:"SheWippe";i:1602;s:9:"StreWench";i:1603;s:13:"SubauSycopZoo";i:1604;s:14:"LarunNonaPhyto";i:1605;s:8:"OrdeVigi";i:1606;s:11:"KumOphthPar";i:1607;s:4:"Depe";i:1608;s:10:"DecumPerio";i:1609;s:8:"ArchClMe";i:1610;s:7:"TawieWy";i:1611;s:5:"SupWr";i:1612;s:10:"ReshSaTydi";i:1613;s:8:"BeverDis";i:1614;s:11:"HypNeuroTru";i:1615;s:15:"DrinkFulmiSaper";i:1616;s:14:"GlycMachiNonre";i:1617;s:8:"TapiUrbi";i:1618;s:5:"InNun";i:1619;s:11:"ProTalegTot";i:1620;s:5:"GarUn";i:1621;s:5:"Signa";i:1622;s:10:"ProofUltra";i:1623;s:5:"Parti";i:1624;s:10:"HoWanlWitc";i:1625;s:5:"Ossua";i:1626;s:11:"ImpPreSuper";i:1627;s:8:"CurIsoRi";i:1628;s:4:"Play";i:1629;s:4:"Fore";i:1630;s:11:"DecuExtrInd";i:1631;s:12:"DysanHydPost";i:1632;s:7:"ReTelev";i:1633;s:9:"CionxPrSh";i:1634;s:6:"NatiUn";i:1635;s:4:"Grav";i:1636;s:4:"Tran";i:1637;s:3:"Kan";i:1638;s:12:"LichMakiUnne";i:1639;s:9:"LacMesPer";i:1640;s:7:"CoHidJi";i:1641;s:11:"MahSurUnlam";i:1642;s:10:"DeOutsWauk";i:1643;s:13:"MarPylorRoots";i:1644;s:6:"ExPehu";i:1645;s:5:"PaTak";i:1646;s:5:"Rubas";i:1647;s:8:"PhaseWit";i:1648;s:5:"Inhar";i:1649;s:12:"OsteRhyScapu";i:1650;s:13:"MonotRedeSupe";i:1651;s:10:"BlephDaPer";i:1652;s:8:"SylvTrUn";i:1653;s:6:"MePuSa";i:1654;s:4:"DuUn";i:1655;s:7:"DistSem";i:1656;s:5:"HuPer";i:1657;s:11:"PlPolyUnpea";i:1658;s:5:"FeeNe";i:1659;s:12:"ElegGrinZygo";i:1660;s:13:"PanteUnbloUns";i:1661;s:6:"LifTur";i:1662;s:4:"Unsi";i:1663;s:7:"PeoplUr";i:1664;s:5:"CerMa";i:1665;s:8:"TechnUns";i:1666;s:7:"ScleWhe";i:1667;s:11:"MazanNecPro";i:1668;s:10:"RecovSergi";i:1669;s:5:"Unidi";i:1670;s:8:"SunUninf";i:1671;s:9:"MoSciUnfa";i:1672;s:10:"FelsOstTri";i:1673;s:7:"ConSang";i:1674;s:3:"Unh";i:1675;s:7:"CrHyTri";i:1676;s:3:"Kne";i:1677;s:10:"EpiNonPhoe";i:1678;s:7:"HydNeig";i:1679;s:6:"BoOnse";i:1680;s:7:"UnhorVe";i:1681;s:13:"OtherSirTobex";i:1682;s:8:"LaPrebUn";i:1683;s:12:"MyeNitidRace";i:1684;s:11:"DisDorsPseu";i:1685;s:4:"Unne";i:1686;s:10:"ComatTashr";i:1687;s:5:"Chrom";i:1688;s:8:"ScolTene";i:1689;s:6:"PholTi";i:1690;s:5:"EnoUn";i:1691;s:8:"MaxNonsp";i:1692;s:11:"MisStaphTal";i:1693;s:6:"PsSlUn";i:1694;s:7:"SeUlste";i:1695;s:10:"PreThrepUn";i:1696;s:3:"Min";i:1697;s:6:"SplZoo";i:1698;s:4:"Obit";i:1699;s:9:"LiRicoSar";i:1700;s:9:"PsReReind";i:1701;s:2:"Gu";i:1702;s:11:"PioSemUncha";i:1703;s:10:"DebamInsPl";i:1704;s:4:"CoNo";i:1705;s:3:"Pat";i:1706;s:5:"Rhino";i:1707;s:12:"GladdInUnscr";i:1708;s:5:"Gargo";i:1709;s:3:"Hog";i:1710;s:4:"Tany";i:1711;s:9:"FacepUngr";i:1712;s:6:"DesUnd";i:1713;s:3:"Tap";i:1714;s:6:"ReUnde";i:1715;s:2:"Qu";i:1716;s:7:"IntuPar";i:1717;s:5:"LopPr";i:1718;s:4:"Unta";i:1719;s:3:"Tin";i:1720;s:3:"Mot";i:1721;s:10:"MemorRemic";i:1722;s:11:"FistlPanTyi";i:1723;s:12:"GhoGuarRelbu";i:1724;s:3:"Coo";i:1725;s:6:"NotPig";i:1726;s:4:"Orna";i:1727;s:9:"InNonalTh";i:1728;s:7:"AlConen";i:1729;s:8:"QuiSyncx";i:1730;s:12:"HematLarTear";i:1731;s:4:"Link";i:1732;s:7:"PolarVa";i:1733;s:14:"MacroParvRuina";i:1734;s:7:"KeelRep";i:1735;s:4:"UnWo";i:1736;s:13:"PolygRemTucun";i:1737;s:9:"LoxoReSin";i:1738;s:12:"OcPassaSciss";i:1739;s:9:"CopeCorre";i:1740;s:12:"GawLaborTeta";i:1741;s:8:"CoHeSupe";i:1742;s:7:"ProtTel";i:1743;s:7:"RaUnplu";i:1744;s:3:"Try";i:1745;s:4:"Tols";i:1746;s:2:"Sn";i:1747;s:8:"CincUnco";i:1748;s:11:"CondPytUnre";i:1749;s:7:"CeMePro";i:1750;s:5:"Myrme";i:1751;s:4:"Skew";i:1752;s:3:"Usa";i:1753;s:8:"SabbaSla";i:1754;s:9:"GlePrThyr";i:1755;s:7:"FlRhina";i:1756;s:11:"ReaSilicUnb";i:1757;s:9:"InPahTeer";i:1758;s:4:"Gude";i:1759;s:9:"KaxPyosUn";i:1760;s:5:"Bianc";i:1761;s:10:"SelenSquas";i:1762;s:9:"RhinUntuc";i:1763;s:8:"PesSupUn";i:1764;s:7:"SyTeneb";i:1765;s:3:"Gly";i:1766;s:12:"MethaMucPara";i:1767;s:14:"DemaIcelaWhome";i:1768;s:3:"Pri";i:1769;s:10:"ChapaLiMet";i:1770;s:11:"EhaNonseVot";i:1771;s:3:"Xan";i:1772;s:12:"MerocTuppWal";i:1773;s:3:"Dyn";i:1774;s:11:"MystaPseSem";i:1775;s:3:"Org";i:1776;s:13:"MelanParaWear";i:1777;s:9:"ReissUnVi";i:1778;s:2:"Me";i:1779;s:9:"JokisPrTh";i:1780;s:13:"CraEluviFlood";i:1781;s:7:"NavStee";i:1782;s:7:"PasqRep";i:1783;s:7:"FeMildh";i:1784;s:3:"Sub";i:1785;s:12:"IntMeisTetra";i:1786;s:8:"CherPrec";i:1787;s:7:"MegaSex";i:1788;s:4:"GrSp";i:1789;s:8:"PePinfSc";i:1790;s:9:"FluxTripp";i:1791;s:8:"GaliWeak";i:1792;s:3:"Req";i:1793;s:9:"InOvereRo";i:1794;s:8:"NicRamen";i:1795;s:8:"AlfMaUnd";i:1796;s:7:"NonabPi";i:1797;s:8:"ResiTing";i:1798;s:8:"GoParTub";i:1799;s:4:"Sili";i:1800;s:5:"PhPre";i:1801;s:10:"ApartTuske";i:1802;s:6:"HereRe";i:1803;s:4:"ElPr";i:1804;s:13:"HiruMonoTownw";i:1805;s:11:"ChalGuaWord";i:1806;s:11:"ChrNonpuSub";i:1807;s:11:"LazyxQuUndi";i:1808;s:4:"QuRe";i:1809;s:8:"DiGulfWi";i:1810;s:7:"PunUroc";i:1811;s:9:"CulFurHyp";i:1812;s:5:"Salty";i:1813;s:9:"PitcPoStu";i:1814;s:2:"La";i:1815;s:7:"UropZoo";i:1816;s:11:"HermiKaRoup";i:1817;s:4:"Cons";i:1818;s:10:"RapiSprTri";i:1819;s:8:"DenoMiPi";i:1820;s:8:"NairyPhy";i:1821;s:2:"Um";i:1822;s:10:"JuScopUnwi";i:1823;s:4:"Radi";i:1824;s:8:"CribExtr";i:1825;s:6:"SalThr";i:1826;s:3:"Dil";i:1827;s:7:"DefEwde";i:1828;s:9:"PalliUnac";i:1829;s:9:"SafraUnst";i:1830;s:13:"PrefSidaUnder";i:1831;s:11:"SatSporUnin";i:1832;s:5:"MoSch";i:1833;s:8:"CrRepSym";i:1834;s:2:"Pt";i:1835;s:4:"OvPr";i:1836;s:6:"DeUnab";i:1837;s:4:"Unde";i:1838;s:5:"Negro";i:1839;s:5:"MarPu";i:1840;s:5:"Ortho";i:1841;s:12:"MatroThesTra";i:1842;s:2:"Ma";i:1843;s:8:"ActFaKee";i:1844;s:12:"SalacThecUnd";i:1845;s:10:"MuMurPrein";i:1846;s:9:"PtiloTall";i:1847;s:2:"Oc";i:1848;s:10:"ScattSphen";i:1849;s:5:"MotUn";i:1850;s:7:"CuStuUn";i:1851;s:4:"Unqu";i:1852;s:4:"Decr";i:1853;s:5:"TalTr";i:1854;s:4:"Sexd";i:1855;s:7:"ShaUnmo";i:1856;s:6:"EuoNat";i:1857;s:8:"MahiSemi";i:1858;s:9:"GiglTacho";i:1859;s:10:"GneisMaulx";i:1860;s:11:"HemaLaPrors";i:1861;s:10:"FlubPacTyp";i:1862;s:9:"AlNonjTel";i:1863;s:9:"RecSuiUnc";i:1864;s:8:"ConsiSar";i:1865;s:4:"Prei";i:1866;s:4:"Lami";i:1867;s:11:"ManPelecSau";i:1868;s:5:"Ducat";i:1869;s:7:"PsSauci";i:1870;s:7:"HolTerp";i:1871;s:3:"She";i:1872;s:10:"DisDowerSt";i:1873;s:5:"Timan";i:1874;s:3:"Pal";i:1875;s:8:"QuinSuTo";i:1876;s:12:"HeathNonsuSo";i:1877;s:11:"GenMicrTeno";i:1878;s:5:"RecUn";i:1879;s:4:"Unpr";i:1880;s:7:"FlamIrr";i:1881;s:13:"DonarManaPero";i:1882;s:6:"ShTopi";i:1883;s:4:"Scit";i:1884;s:10:"OmmatScour";i:1885;s:5:"Ethen";i:1886;s:4:"Xylo";i:1887;s:5:"EryTr";i:1888;s:6:"BraHol";i:1889;s:13:"ImpalRummaVar";i:1890;s:4:"Trav";i:1891;s:11:"JotLoxUnfun";i:1892;s:8:"MuscSeme";i:1893;s:8:"GiLiPara";i:1894;s:9:"ViscoVouc";i:1895;s:3:"Scr";i:1896;s:6:"HaMaNo";i:1897;s:6:"StaSwe";i:1898;s:9:"InteRadio";i:1899;s:12:"PrThynnUnvol";i:1900;s:8:"PhilRecr";i:1901;s:9:"OmelTecpa";i:1902;s:9:"PettyUnwe";i:1903;s:5:"Melan";i:1904;s:14:"GasteQuinqTric";i:1905;s:12:"CommuDaInter";i:1906;s:5:"Megam";i:1907;s:12:"MiPickpTakam";i:1908;s:4:"Hydr";i:1909;s:4:"Torv";i:1910;s:7:"ExtiInt";i:1911;s:13:"EuphoFruOutdw";i:1912;s:5:"Tinni";i:1913;s:4:"Subs";i:1914;s:5:"OvPho";i:1915;s:11:"GlyphMonRig";i:1916;s:4:"Pion";i:1917;s:10:"IndagPieTi";i:1918;s:11:"TakiUnprUpa";i:1919;s:5:"LoVar";i:1920;s:5:"FaPla";i:1921;s:7:"MiscOns";i:1922;s:7:"DusStee";i:1923;s:9:"BeetlHang";i:1924;s:12:"FigMarmUnend";i:1925;s:5:"Fordo";i:1926;s:4:"Soci";i:1927;s:12:"HygrHypShopk";i:1928;s:11:"HabPlowWenc";i:1929;s:8:"PerSpent";i:1930;s:4:"Camp";i:1931;s:5:"PatRu";i:1932;s:8:"GhaSuthe";i:1933;s:10:"OuttPaUnim";i:1934;s:9:"StockTacc";i:1935;s:7:"InneUns";i:1936;s:4:"Trac";i:1937;s:4:"Unin";i:1938;s:8:"PolUninv";i:1939;s:9:"PheSterUn";i:1940;s:8:"DysYukia";i:1941;s:5:"PenSc";i:1942;s:7:"DaInShi";i:1943;s:14:"HepaNonshOrbic";i:1944;s:7:"PluPrPr";i:1945;s:4:"Semp";i:1946;s:3:"Unb";i:1947;s:9:"LancNonco";i:1948;s:10:"FiKlockNon";i:1949;s:4:"InSw";i:1950;s:8:"RoUnsuWi";i:1951;s:6:"ObscSe";i:1952;s:8:"ScythSub";i:1953;s:7:"HistMot";i:1954;s:5:"GoUtt";i:1955;s:5:"Inter";i:1956;s:7:"MiThUnp";i:1957;s:8:"BreComus";i:1958;s:8:"HurMemTu";i:1959;s:6:"OdylSn";i:1960;s:5:"Misec";i:1961;s:10:"PolypScler";i:1962;s:10:"OverSintTh";i:1963;s:8:"OverSexu";i:1964;s:12:"DruMagneSant";i:1965;s:10:"ParoPawPri";i:1966;s:7:"MaPrVar";i:1967;s:12:"LancNeolShas";i:1968;s:7:"SelUnsh";i:1969;s:5:"Nephr";i:1970;s:11:"NaSeismSept";i:1971;s:9:"DrivaNySi";i:1972;s:8:"PePheUnt";i:1973;s:9:"BusElSilp";i:1974;s:10:"SarclSever";i:1975;s:10:"ChirpDeEde";i:1976;s:10:"SarSigUndi";i:1977;s:8:"PyxidSup";i:1978;s:4:"Nubb";i:1979;s:8:"HederPne";i:1980;s:10:"PhthiTeneb";i:1981;s:7:"FrFreon";i:1982;s:12:"QuareResiSke";i:1983;s:6:"GangNo";i:1984;s:6:"NoonSp";i:1985;s:5:"StrUn";i:1986;s:11:"CrDarogYera";i:1987;s:9:"PhPogamPr";i:1988;s:11:"EneugInceOs";i:1989;s:5:"Carac";i:1990;s:11:"NaysaOvSupe";i:1991;s:5:"BlaHu";i:1992;s:10:"RoselUnWoo";i:1993;s:10:"CorinGraTe";i:1994;s:7:"InUnobv";i:1995;s:9:"BaChuroIn";i:1996;s:8:"MulSerol";i:1997;s:4:"Cast";i:1998;s:6:"DeQuXy";i:1999;s:8:"MiPnVeri";i:2000;s:7:"MyrStor";i:2001;s:8:"IncavZin";i:2002;s:9:"KayRulaTr";i:2003;s:3:"Ser";i:2004;s:7:"PaucRus";i:2005;s:2:"Tu";i:2006;s:9:"OddmWindb";i:2007;s:8:"UnpWhang";i:2008;s:4:"GaMe";i:2009;s:6:"MaPost";i:2010;s:7:"PomRach";i:2011;s:2:"Wy";i:2012;s:11:"MaQueeTapst";i:2013;s:5:"Denar";i:2014;s:10:"MethoSneer";i:2015;s:9:"CovaSpell";i:2016;s:8:"SlanTuis";i:2017;s:12:"ChiReprTaunt";i:2018;s:9:"LaMeasUne";i:2019;s:5:"PoPre";i:2020;s:5:"SpaSs";i:2021;s:4:"Salm";i:2022;s:5:"Endop";i:2023;s:3:"Tut";i:2024;s:6:"PeriPh";i:2025;s:13:"IrreSodaVeget";i:2026;s:5:"Locri";i:2027;s:7:"UnZooma";i:2028;s:4:"Prou";i:2029;s:5:"Outdw";i:2030;s:12:"MesneReThrou";i:2031;s:2:"Se";i:2032;s:4:"Sate";i:2033;s:4:"AtDi";i:2034;s:5:"OrPhe";i:2035;s:5:"Quond";i:2036;s:5:"Samar";i:2037;s:2:"Ke";i:2038;s:5:"Nonmu";i:2039;s:12:"ForcTaggyTea";i:2040;s:4:"Uncr";i:2041;s:5:"PlaRa";i:2042;s:7:"FeFemPr";i:2043;s:5:"ClDis";i:2044;s:14:"BackbIlluvPseu";i:2045;s:10:"BrDemiLuxu";i:2046;s:10:"GaMyoplOut";i:2047;s:14:"MeteOutwoPeriu";i:2048;s:8:"GaMycSha";i:2049;s:5:"Unapp";i:2050;s:4:"Gibb";i:2051;s:3:"Hem";i:2052;s:5:"Syngn";i:2053;s:5:"Lophi";i:2054;s:7:"OvReUnl";i:2055;s:6:"ProfWo";i:2056;s:11:"BaElasmElop";i:2057;s:2:"Eq";i:2058;s:5:"Workm";i:2059;s:9:"AppelUnic";i:2060;s:4:"Rabb";i:2061;s:8:"DovInspi";i:2062;s:5:"Katip";i:2063;s:12:"MiPortmTecto";i:2064;s:8:"ScSiVola";i:2065;s:3:"Cra";i:2066;s:14:"HexaPassaPhila";i:2067;s:9:"FrHoUndet";i:2068;s:7:"DipLint";i:2069;s:4:"IsPa";i:2070;s:3:"New";i:2071;s:3:"Ton";i:2072;s:11:"ImshMacrScr";i:2073;s:5:"Pampr";i:2074;s:9:"InnocUnch";i:2075;s:13:"LithxUnrZipsx";i:2076;s:13:"MingyUngeUnor";i:2077;s:10:"NonnoUngUn";i:2078;s:4:"Pict";i:2079;s:8:"RemeSpon";i:2080;s:4:"Plas";i:2081;s:5:"IntRu";i:2082;s:3:"Hyb";i:2083;s:3:"Tec";i:2084;s:3:"Dia";i:2085;s:4:"Unme";i:2086;s:11:"CarlDeliVam";i:2087;s:8:"NeUncrUn";i:2088;s:3:"Ker";i:2089;s:10:"ObjuProlTw";i:2090;s:8:"DeOverSu";i:2091;s:2:"An";i:2092;s:7:"GraduUn";i:2093;s:9:"PalaeSyTr";i:2094;s:3:"Sen";i:2095;s:6:"SubTet";i:2096;s:9:"MadMultSu";i:2097;s:10:"GermaUnpre";i:2098;s:10:"GibbNonsVo";i:2099;s:3:"Phr";i:2100;s:4:"Macr";i:2101;s:4:"ExUn";i:2102;s:11:"KaibMeUnpar";i:2103;s:5:"Mutil";i:2104;s:12:"PentaPoVentr";i:2105;s:3:"For";i:2106;s:10:"PedeUnciUp";i:2107;s:9:"FranTheop";i:2108;s:10:"InterJerUn";i:2109;s:6:"CyaUnt";i:2110;s:4:"Chic";i:2111;s:10:"RemTanThri";i:2112;s:4:"BeSa";i:2113;s:3:"Fec";i:2114;s:10:"CozilMoUht";i:2115;s:2:"Pa";i:2116;s:5:"Mispr";i:2117;s:4:"PrSt";i:2118;s:4:"DiHe";i:2119;s:10:"FakoMoQuix";i:2120;s:11:"PtePyrUnarr";i:2121;s:6:"PhRoSt";i:2122;s:4:"BeEn";i:2123;s:8:"GuiHypha";i:2124;s:8:"GuInoPre";i:2125;s:9:"PatRhaUnt";i:2126;s:4:"Holo";i:2127;s:8:"PrioUnme";i:2128;s:11:"GradRubytUn";i:2129;s:10:"CoeCredeSi";i:2130;s:9:"ReifUndis";i:2131;s:6:"LuVaWi";i:2132;s:7:"AnBuDia";i:2133;s:4:"Trad";i:2134;s:11:"CardOvRocks";i:2135;s:7:"UnhaWas";i:2136;s:13:"MaggeOthWilli";i:2137;s:8:"MycNoPol";i:2138;s:9:"FasGriNig";i:2139;s:6:"LanPar";i:2140;s:9:"FeoffPrSu";i:2141;s:11:"GonoGreUnco";i:2142;s:8:"SmotStom";i:2143;s:4:"Tute";i:2144;s:6:"MellRa";i:2145;s:4:"Pter";i:2146;s:9:"ThrThroTu";i:2147;s:5:"InTap";i:2148;s:7:"CollTub";i:2149;s:2:"Fa";i:2150;s:9:"PiRibbiSu";i:2151;s:7:"ExuPaSe";i:2152;s:9:"DownHoriz";i:2153;s:12:"JudicMockUnb";i:2154;s:9:"MeloSemia";i:2155;s:6:"CoFeRa";i:2156;s:5:"Macaw";i:2157;s:6:"NeurSe";i:2158;s:8:"LattSouc";i:2159;s:8:"UninWart";i:2160;s:9:"GiSubTown";i:2161;s:12:"PaveTitheUnl";i:2162;s:10:"DragsSubUn";i:2163;s:9:"SlopUncha";i:2164;s:5:"OrgTh";i:2165;s:8:"MixScole";i:2166;s:2:"Em";i:2167;s:5:"Remon";i:2168;s:2:"Bo";i:2169;s:7:"SabSupe";i:2170;s:4:"Neri";i:2171;s:5:"TuUnd";i:2172;s:9:"FlagSciss";i:2173;s:3:"Mas";i:2174;s:9:"InIsaThar";i:2175;s:11:"SemiStiThio";i:2176;s:3:"Oth";i:2177;s:10:"NeePoUnhel";i:2178;s:9:"CarInMate";i:2179;s:9:"DenuRefRi";i:2180;s:11:"ChiwColleMo";i:2181;s:12:"EquidPrecUnv";i:2182;s:6:"KrSpha";i:2183;s:10:"ShTeeTotte";i:2184;s:3:"Tou";i:2185;s:8:"IncRoUpb";i:2186;s:7:"IlMengx";i:2187;s:6:"CephSa";i:2188;s:11:"InkPhthaTet";i:2189;s:8:"IncuMoco";i:2190;s:7:"OveSeis";i:2191;s:3:"Rub";i:2192;s:12:"ObUnctiVeloc";i:2193;s:9:"OpalPodli";i:2194;s:3:"Dec";i:2195;s:8:"CymMoPyo";i:2196;s:9:"PhSpicaTr";i:2197;s:5:"Nondy";i:2198;s:4:"Roma";i:2199;s:9:"InsitSuTr";i:2200;s:3:"Poi";i:2201;s:8:"ImInhRos";i:2202;s:9:"HemoInsPr";i:2203;s:7:"RecoSan";i:2204;s:9:"IntraLepr";i:2205;s:7:"PartiRo";i:2206;s:7:"PlacPla";i:2207;s:9:"MedieSeSy";i:2208;s:4:"BaSc";i:2209;s:2:"Sk";i:2210;s:5:"UnfUn";i:2211;s:8:"CadavEpi";i:2212;s:5:"LagPh";i:2213;s:5:"MalMo";i:2214;s:8:"SpSupeUn";i:2215;s:8:"HiOgrePo";i:2216;s:2:"Ca";i:2217;s:5:"Infol";i:2218;s:10:"DyassUninu";i:2219;s:6:"UnUnsa";i:2220;s:12:"ChoSchwZarni";i:2221;s:9:"InReUnpay";i:2222;s:9:"UntrYello";i:2223;s:12:"OraOverSland";i:2224;s:6:"HePrSt";i:2225;s:10:"OpPropiQuo";i:2226;s:9:"PeriTheUn";i:2227;s:12:"AbuPhysiRheg";i:2228;s:7:"DisShad";i:2229;s:12:"CheGalyaTeax";i:2230;s:11:"CorrKaUncol";i:2231;s:9:"HypLandLu";i:2232;s:4:"Yegu";i:2233;s:4:"Prig";i:2234;s:12:"MultiTubbaVi";i:2235;s:14:"RehaSchnoStrin";i:2236;s:8:"PelSquat";i:2237;s:8:"HexRacTu";i:2238;s:12:"SemblSomeUpb";i:2239;s:11:"GymnImplOve";i:2240;s:6:"NondUn";i:2241;s:11:"HelIneStasi";i:2242;s:7:"EstelTi";i:2243;s:10:"InterPoThe";i:2244;s:5:"Degge";i:2245;s:3:"Rel";i:2246;s:4:"Rock";i:2247;s:9:"FlaGaSpas";i:2248;s:7:"MayaTel";i:2249;s:7:"EnteFic";i:2250;s:4:"Penn";i:2251;s:7:"OvUndaz";i:2252;s:5:"Spinu";i:2253;s:11:"CoPhotoRake";i:2254;s:13:"HomocUnioUnli";i:2255;s:7:"FidGirl";i:2256;s:6:"FaceSt";i:2257;s:9:"HardePyre";i:2258;s:5:"Brand";i:2259;s:5:"Enqui";i:2260;s:5:"Workl";i:2261;s:7:"HoIncom";i:2262;s:10:"SittUloUnk";i:2263;s:2:"Ps";i:2264;s:8:"PertiTra";i:2265;s:11:"EngOverTins";i:2266;s:5:"Uncom";i:2267;s:3:"Jaw";i:2268;s:7:"PiStrSy";i:2269;s:5:"Sotti";i:2270;s:14:"AnthrNonsSiusl";i:2271;s:5:"PseQu";i:2272;s:5:"Reedl";i:2273;s:8:"DikerPea";i:2274;s:6:"NoSwar";i:2275;s:10:"PonTiTrans";i:2276;s:6:"StomWh";i:2277;s:5:"Prepa";i:2278;s:11:"EctoTraWeap";i:2279;s:7:"CeJuKet";i:2280;s:7:"CaStiTo";i:2281;s:11:"CholaScalSl";i:2282;s:5:"LeSpu";i:2283;s:7:"NonexPa";i:2284;s:5:"CuUnd";i:2285;s:5:"CoHas";i:2286;s:7:"PresVet";i:2287;s:10:"EfEndotSph";i:2288;s:4:"GeHo";i:2289;s:10:"ConEnkraEx";i:2290;s:9:"SeakUrano";i:2291;s:10:"RebScutSta";i:2292;s:11:"AmetFoStaph";i:2293;s:4:"LoPu";i:2294;s:4:"Unmo";i:2295;s:12:"ChamaCufRetr";i:2296;s:9:"CuttiPoun";i:2297;s:7:"OxToote";i:2298;s:4:"Hali";i:2299;s:14:"OverSuperTickt";i:2300;s:9:"DefeElong";i:2301;s:10:"RozumTypif";i:2302;s:11:"OnomReinhUn";i:2303;s:5:"HePat";i:2304;s:6:"PrRaff";i:2305;s:7:"UrWorld";i:2306;s:12:"MacTanyoVoli";i:2307;s:6:"KeUnbe";i:2308;s:11:"FooliPeSoun";i:2309;s:8:"FiMaStra";i:2310;s:10:"PrRevuSubc";i:2311;s:3:"Kri";i:2312;s:6:"EnneMe";i:2313;s:3:"Mar";i:2314;s:10:"PaSubmeUna";i:2315;s:14:"MitiNoncaPelta";i:2316;s:7:"TroTylo";i:2317;s:4:"Curv";i:2318;s:5:"QuaTy";i:2319;s:7:"NonsaPr";i:2320;s:5:"Urete";i:2321;s:5:"Hynex";i:2322;s:5:"Tillo";i:2323;s:4:"EnMa";i:2324;s:10:"IonPresuPr";i:2325;s:9:"OverwTale";i:2326;s:9:"EnmaSenda";i:2327;s:13:"HiodTrihUnvis";i:2328;s:3:"Viz";i:2329;s:9:"NayarSuVa";i:2330;s:9:"VasoWallo";i:2331;s:8:"CrypMans";i:2332;s:9:"CondyEpip";i:2333;s:7:"GlMalle";i:2334;s:3:"Pag";i:2335;s:7:"ScUltra";i:2336;s:11:"ScholTaUnba";i:2337;s:13:"DisarGestNonc";i:2338;s:3:"Sir";i:2339;s:9:"PugmiUltr";i:2340;s:5:"Pangl";i:2341;s:8:"MetMyoge";i:2342;s:7:"HydroSp";i:2343;s:5:"Anaca";i:2344;s:6:"OrthPs";i:2345;s:5:"Nicco";i:2346;s:12:"DactTonnUnbo";i:2347;s:3:"Tox";i:2348;s:14:"ChorExcitPhaco";i:2349;s:3:"Tab";i:2350;s:10:"BasUnUnrui";i:2351;s:6:"RefUne";i:2352;s:3:"Ope";i:2353;s:9:"OverQuWar";i:2354;s:5:"Antid";i:2355;s:9:"ExFinRust";i:2356;s:14:"PuggiSecedStri";i:2357;s:7:"IriRaWa";i:2358;s:11:"MormProrPse";i:2359;s:5:"StUno";i:2360;s:7:"GastrPh";i:2361;s:2:"Li";i:2362;s:12:"NonOndiRocam";i:2363;s:7:"MollUnt";i:2364;s:2:"Ti";i:2365;s:4:"Podu";i:2366;s:5:"SanTe";i:2367;s:13:"ChieDanaiFell";i:2368;s:7:"CentFar";i:2369;s:6:"OutTab";i:2370;s:14:"MitigNoncoPiro";i:2371;s:13:"HondMarsObnun";i:2372;s:4:"Rest";i:2373;s:7:"NostrOb";i:2374;s:7:"IconLob";i:2375;s:5:"Virag";i:2376;s:9:"ImmePhane";i:2377;s:5:"Scran";i:2378;s:10:"LeNonalUnv";i:2379;s:7:"NiParUn";i:2380;s:8:"PhyTosUn";i:2381;s:12:"HydMarinSpec";i:2382;s:8:"LocSuUnv";i:2383;s:11:"LympTreUnse";i:2384;s:10:"CondxTenta";i:2385;s:8:"LerUnmis";i:2386;s:14:"GhanKeratNekto";i:2387;s:9:"InMalPrav";i:2388;s:3:"Mul";i:2389;s:8:"PilRever";i:2390;s:13:"PrepSialaTrum";i:2391;s:10:"DammKatKir";i:2392;s:11:"KnobProloRu";i:2393;s:4:"Thie";i:2394;s:3:"Win";i:2395;s:3:"Def";i:2396;s:8:"StrWanch";i:2397;s:7:"LatriRa";i:2398;s:2:"Si";i:2399;s:6:"DiSlSm";i:2400;s:9:"HoStelaUm";i:2401;s:4:"SwTe";i:2402;s:10:"LanToVaira";i:2403;s:6:"OzonTo";i:2404;s:6:"MirSci";i:2405;s:14:"DaisiMyeloUret";i:2406;s:12:"DigitMounUnt";i:2407;s:6:"UniUnm";i:2408;s:6:"HallUn";i:2409;s:3:"Rea";i:2410;s:13:"FrushLitRecry";i:2411;s:4:"JuNa";i:2412;s:6:"SeSuev";i:2413;s:7:"RunTetr";i:2414;s:12:"NoneRoenThio";i:2415;s:4:"Wolf";i:2416;s:9:"HetaJaPyl";i:2417;s:10:"DiapaUnder";i:2418;s:3:"Wra";i:2419;s:4:"Subp";i:2420;s:3:"Sal";i:2421;s:5:"Unmea";i:2422;s:12:"FittiQuinaSe";i:2423;s:8:"PseWicke";i:2424;s:12:"BlNondePlate";i:2425;s:8:"SigTyUnt";i:2426;s:3:"San";i:2427;s:8:"LocPerRx";i:2428;s:8:"SupUnsup";i:2429;s:6:"GraTri";i:2430;s:7:"BaldnLo";i:2431;s:9:"CruroUnej";i:2432;s:10:"LovesRehoo";i:2433;s:12:"HemeNomaOuth";i:2434;s:5:"Innox";i:2435;s:11:"KierRenTele";i:2436;s:9:"SolitStaw";i:2437;s:8:"CeMamUns";i:2438;s:11:"MysoiNeotQu";i:2439;s:10:"KatakTurns";i:2440;s:10:"DisprHymno";i:2441;s:4:"Regr";i:2442;s:9:"GlobaSynt";i:2443;s:3:"Lut";i:2444;s:3:"The";i:2445;s:3:"Rhi";i:2446;s:3:"Tym";i:2447;s:7:"MeUnman";i:2448;s:9:"ExiOmnTor";i:2449;s:12:"PerfeStiSton";i:2450;s:6:"HoPrUn";i:2451;s:10:"IrSeptaTam";i:2452;s:10:"FoParTechn";i:2453;s:4:"Spha";i:2454;s:12:"StupUnflaVie";i:2455;s:7:"LoneVan";i:2456;s:8:"MonRadio";i:2457;s:10:"HomImpSynd";i:2458;s:2:"Ch";i:2459;s:9:"EnOrPoeta";i:2460;s:10:"CoSeminSen";i:2461;s:7:"ProidVe";i:2462;s:6:"MeShor";i:2463;s:4:"Hois";i:2464;s:9:"EyeMePoli";i:2465;s:3:"Rei";i:2466;s:5:"Zoste";i:2467;s:8:"SithUnwa";i:2468;s:5:"Corre";i:2469;s:9:"SilTriUnv";i:2470;s:9:"CompMysta";i:2471;s:7:"HepMuSu";i:2472;s:6:"OpeQua";i:2473;s:4:"Rema";i:2474;s:11:"DeclMassiUn";i:2475;s:3:"Lep";i:2476;s:8:"NecrPhth";i:2477;s:4:"Trir";i:2478;s:8:"MesoWelc";i:2479;s:4:"Vulp";i:2480;s:7:"LindPhr";i:2481;s:7:"EntirMo";i:2482;s:9:"RiSlaTaun";i:2483;s:8:"PreToufi";i:2484;s:9:"PalaSovra";i:2485;s:5:"Penni";i:2486;s:12:"NonprProcRad";i:2487;s:8:"PantRogu";i:2488;s:8:"ForeScom";i:2489;s:2:"Yo";i:2490;s:9:"LiOutUnti";i:2491;s:6:"PostSt";i:2492;s:10:"SpiSyUntuc";i:2493;s:7:"AlquiRh";i:2494;s:12:"PaloSkeldTir";i:2495;s:8:"BiflaUnr";i:2496;s:9:"LacRhuWin";i:2497;s:5:"Unres";i:2498;s:7:"PoPsilo";i:2499;s:3:"Obe";i:2500;s:5:"Nonin";i:2501;s:7:"UncaUnh";i:2502;s:5:"Undiv";i:2503;s:9:"InIzMorin";i:2504;s:7:"NoPicPi";i:2505;s:13:"PediPoltThiof";i:2506;s:4:"Scap";i:2507;s:11:"CoMesorTact";i:2508;s:10:"DeepPikePr";i:2509;s:8:"OmbroQui";i:2510;s:5:"InePo";i:2511;s:10:"PaPolyzWea";i:2512;s:10:"GhiziPosne";i:2513;s:5:"Nonde";i:2514;s:10:"RachyStaph";i:2515;s:9:"NorProtRh";i:2516;s:11:"ConflElusSt";i:2517;s:9:"OliSaraTa";i:2518;s:5:"Spath";i:2519;s:9:"HetIreUnr";i:2520;s:9:"GigeHoUro";i:2521;s:6:"NonSex";i:2522;s:9:"DrawIreni";i:2523;s:7:"HacMars";i:2524;s:10:"DenRaSoros";i:2525;s:10:"HolHypSupe";i:2526;s:4:"Cred";i:2527;s:5:"StUns";i:2528;s:8:"FictWagw";i:2529;s:3:"Unc";i:2530;s:9:"ShoreWhit";i:2531;s:11:"ProRehaTras";i:2532;s:5:"UnUph";i:2533;s:10:"MoPneumPsi";i:2534;s:12:"RabbSomnUnti";i:2535;s:11:"SplenTheUnb";i:2536;s:3:"Par";i:2537;s:10:"DyaFisQuin";i:2538;s:6:"LoUncl";i:2539;s:11:"DicOvariVar";i:2540;s:8:"PreSciSc";i:2541;s:8:"HarHiMor";i:2542;s:11:"QuadrSawaSi";i:2543;s:6:"SuThim";i:2544;s:5:"Vulva";i:2545;s:3:"Hou";i:2546;s:3:"Mer";i:2547;s:8:"GiInPaga";i:2548;s:2:"Co";i:2549;s:10:"PseudRenun";i:2550;s:13:"MiskMortaTana";i:2551;s:7:"OmTerro";i:2552;s:7:"ExUnbex";i:2553;s:5:"Micro";i:2554;s:11:"DenuObsSemi";i:2555;s:11:"BursDiRenea";i:2556;s:3:"Zym";i:2557;s:9:"BunNoWitt";i:2558;s:5:"Misqu";i:2559;s:4:"LaPl";i:2560;s:6:"IndKin";i:2561;s:10:"TriUnmVent";i:2562;s:10:"MoOvePlant";i:2563;s:5:"Verbe";i:2564;s:5:"CutSc";i:2565;s:9:"UnbeVigor";i:2566;s:9:"ListUnder";i:2567;s:10:"PtilSegrSt";i:2568;s:9:"GruelNaso";i:2569;s:5:"Nudis";i:2570;s:13:"LithPhrasRest";i:2571;s:10:"HyPonUngra";i:2572;s:8:"ParPrese";i:2573;s:2:"Pu";i:2574;s:7:"SeSweUn";i:2575;s:4:"Stop";i:2576;s:11:"EntSynTechn";i:2577;s:14:"OverpTrachUneq";i:2578;s:10:"SteenUtter";i:2579;s:12:"FlorMediUnco";i:2580;s:11:"CoCyancNigg";i:2581;s:4:"Sexu";i:2582;s:11:"BignoErStom";i:2583;s:12:"BiggeMyrSupe";i:2584;s:5:"Tinti";i:2585;s:9:"PhilaSubc";i:2586;s:8:"CouPoeti";i:2587;s:13:"DockeIriMisti";i:2588;s:11:"GramOlPityp";i:2589;s:13:"DisEvoluMycod";i:2590;s:5:"Morph";i:2591;s:2:"Ou";i:2592;s:5:"ProUn";i:2593;s:4:"Dulc";i:2594;s:7:"LongaSp";i:2595;s:8:"PolPulUn";i:2596;s:8:"BuDiInco";i:2597;s:6:"ChaiDe";i:2598;s:11:"PacabUnUnsh";i:2599;s:10:"ArthrUninv";i:2600;s:6:"PyroRo";i:2601;s:7:"VulgaYi";i:2602;s:5:"EcLio";i:2603;s:11:"EranIncouOr";i:2604;s:12:"LunuModulOrd";i:2605;s:5:"Patri";i:2606;s:2:"Dr";i:2607;s:7:"RaTrans";i:2608;s:12:"CompoLipScot";i:2609;s:10:"SardoSensa";i:2610;s:12:"OverlSupWrin";i:2611;s:9:"PhotSperm";i:2612;s:7:"TimekUn";i:2613;s:5:"Pusey";i:2614;s:7:"PoQuSou";i:2615;s:6:"FreeGr";i:2616;s:4:"Nona";i:2617;s:6:"PsilUn";i:2618;s:7:"DiseSta";i:2619;s:5:"Eluso";i:2620;s:7:"EndabFr";i:2621;s:7:"EntMaTe";i:2622;s:5:"UndUn";i:2623;s:8:"FrStavUn";i:2624;s:6:"DisSyn";i:2625;s:3:"Tub";i:2626;s:11:"BescDeRenas";i:2627;s:12:"RabbiReSuper";i:2628;s:4:"Bolo";i:2629;s:10:"NonaPectPh";i:2630;s:12:"PolymRoomtUn";i:2631;s:11:"KolTypWirem";i:2632;s:7:"CroPrae";i:2633;s:7:"SubWarr";i:2634;s:6:"SteTro";i:2635;s:7:"BaForel";i:2636;s:6:"OppiWh";i:2637;s:9:"GleanSacc";i:2638;s:10:"MisliQueru";i:2639;s:8:"ScaWoodb";i:2640;s:3:"Equ";i:2641;s:10:"NeocyUnwov";i:2642;s:13:"CoacElectUnsp";i:2643;s:4:"Unch";i:2644;s:7:"BrUncom";i:2645;s:5:"Tanni";i:2646;s:10:"CuLeiOcclu";i:2647;s:3:"Soa";i:2648;s:11:"MyxoPoUnbeg";i:2649;s:12:"PhysiTrUnexi";i:2650;s:6:"InSice";i:2651;s:7:"NonPauc";i:2652;s:6:"SecuUl";i:2653;s:10:"HornOutOut";i:2654;s:6:"OpprOv";i:2655;s:10:"BaHypStich";i:2656;s:9:"HemoaIcTi";i:2657;s:4:"Zeug";i:2658;s:4:"Sphy";i:2659;s:5:"Shaka";i:2660;s:6:"PaPrSc";i:2661;s:5:"Unrel";i:2662;s:9:"ReSmintTw";i:2663;s:2:"Ku";i:2664;s:4:"Botu";i:2665;s:9:"TrulUnext";i:2666;s:9:"CoglWhite";i:2667;s:5:"Curle";i:2668;s:10:"InMinSince";i:2669;s:11:"NoRachTrans";i:2670;s:11:"RolexUnflZo";i:2671;s:7:"KoSwiUm";i:2672;s:9:"SemiUncUn";i:2673;s:7:"AdBosVe";i:2674;s:12:"CamphPePrein";i:2675;s:7:"PygmoSe";i:2676;s:7:"InMorco";i:2677;s:7:"OcOffen";i:2678;s:5:"Speck";i:2679;s:4:"Till";i:2680;s:11:"BestrUnWorc";i:2681;s:8:"OlOveSyn";i:2682;s:10:"UnsocWound";i:2683;s:6:"ReprSu";i:2684;s:7:"RemigUn";i:2685;s:12:"GeorOutmServ";i:2686;s:3:"Pac";i:2687;s:12:"LePerseSerop";i:2688;s:11:"FeverNonWav";i:2689;s:5:"BaBac";i:2690;s:9:"DragGaSan";i:2691;s:8:"UnforYar";i:2692;s:4:"Vent";i:2693;s:7:"BluSlug";i:2694;s:6:"OverSp";i:2695;s:12:"DiuHypoProto";i:2696;s:7:"ReUndes";i:2697;s:8:"PolUnimp";i:2698;s:5:"Unsmo";i:2699;s:9:"CuriaSulf";i:2700;s:11:"SyntToTreas";i:2701;s:13:"SaproSyceeTen";i:2702;s:5:"Thuoc";i:2703;s:6:"BroHol";i:2704;s:13:"MetaMothwVili";i:2705;s:10:"LeStuUnioc";i:2706;s:12:"PitheTeTrigo";i:2707;s:5:"LowMe";i:2708;s:12:"SoStahlTomme";i:2709;s:8:"PothSpTe";i:2710;s:11:"MultPhytoUn";i:2711;s:4:"Note";i:2712;s:13:"SuluSynanViol";i:2713;s:9:"SeapUnsta";i:2714;s:3:"Nin";i:2715;s:3:"Lat";i:2716;s:11:"BiopDespeEt";i:2717;s:9:"FiSuperVo";i:2718;s:5:"Cereb";i:2719;s:10:"BoNonbUnde";i:2720;s:2:"Lu";i:2721;s:10:"PrerPsyWhe";i:2722;s:11:"MeReferShee";i:2723;s:12:"GrosInvitUna";i:2724;s:8:"SarrSeri";i:2725;s:4:"Rags";i:2726;s:2:"Sm";i:2727;s:5:"Unint";i:2728;s:3:"Gut";i:2729;s:9:"InteMyPyr";i:2730;s:7:"IsoloUn";i:2731;s:5:"SiUnb";i:2732;s:10:"BlCanniEqu";i:2733;s:5:"Konar";i:2734;s:4:"Step";i:2735;s:6:"InteNa";i:2736;s:7:"KnaSubc";i:2737;s:9:"ChaUnaUnc";i:2738;s:6:"LaNeUp";i:2739;s:3:"Reb";i:2740;s:9:"DiscPePot";i:2741;s:8:"NonNonVi";i:2742;s:4:"Tata";i:2743;s:4:"Cent";i:2744;s:7:"HandPho";i:2745;s:11:"GazetMenWoa";i:2746;s:9:"PolyPredi";i:2747;s:12:"LaboPreScase";i:2748;s:7:"PaTheTh";i:2749;s:14:"DeafDiaclSides";i:2750;s:4:"Dixi";i:2751;s:8:"MiscUnpo";i:2752;s:9:"RepShaUpt";i:2753;s:13:"GaberJewdPont";i:2754;s:6:"DyoInc";i:2755;s:11:"FanaOeQuinq";i:2756;s:6:"ChThre";i:2757;s:4:"Orth";i:2758;s:9:"LaborMiSp";i:2759;s:6:"DeNavi";i:2760;s:10:"RioShSwain";i:2761;s:3:"Men";i:2762;s:4:"Ghib";i:2763;s:9:"SunpTrust";i:2764;s:5:"Feloi";i:2765;s:9:"NotorOiko";i:2766;s:5:"Laryn";i:2767;s:5:"Wishf";i:2768;s:10:"DoMultiUne";i:2769;s:4:"Unow";i:2770;s:10:"DumpiEvePy";i:2771;s:14:"OctonTwelUnman";i:2772;s:11:"NasSterXylo";i:2773;s:8:"CompOppu";i:2774;s:9:"CoalEnure";i:2775;s:4:"Suba";i:2776;s:5:"Histo";i:2777;s:12:"PaPsidiTooth";i:2778;s:5:"Tubul";i:2779;s:4:"Outg";i:2780;s:5:"HolNi";i:2781;s:2:"El";i:2782;s:11:"SpurnSuUnst";i:2783;s:3:"Soc";i:2784;s:7:"MachSyn";i:2785;s:12:"LoriPraePren";i:2786;s:7:"ProTric";i:2787;s:5:"Disre";i:2788;s:11:"IntePseuXen";i:2789;s:3:"Sei";i:2790;s:4:"Refl";i:2791;s:11:"MisomSperUn";i:2792;s:9:"RetraVaca";i:2793;s:6:"PolyRe";i:2794;s:12:"GrizzImpSnow";i:2795;s:8:"NumUroxa";i:2796;s:4:"LePa";i:2797;s:3:"Qua";i:2798;s:14:"InvolSearSemio";i:2799;s:6:"LeStan";i:2800;s:10:"SantaStaph";i:2801;s:4:"Imag";i:2802;s:9:"PreRakiSu";i:2803;s:13:"DegeTrigoUnch";i:2804;s:8:"FracUngr";i:2805;s:12:"EzrFlukShast";i:2806;s:10:"PlaneSteth";i:2807;s:8:"PresiUnd";i:2808;s:9:"BroDrivIn";i:2809;s:4:"Vice";i:2810;s:3:"Mah";i:2811;s:4:"AcXy";i:2812;s:10:"IsrMegaUst";i:2813;s:9:"PeriReaTr";i:2814;s:10:"CoOswaZami";i:2815;s:5:"JacUn";i:2816;s:9:"FaLeanPar";i:2817;s:14:"EngauInerrSpha";i:2818;s:5:"Sager";i:2819;s:13:"SteeSubwUnpry";i:2820;s:9:"RhagoStag";i:2821;s:4:"Plag";i:2822;s:4:"Sens";i:2823;s:11:"MonTylosWoo";i:2824;s:5:"Toxop";i:2825;s:4:"MiMn";i:2826;s:6:"PhlePy";i:2827;s:14:"LatoPeramUpval";i:2828;s:10:"PerStTarox";i:2829;s:5:"Thero";i:2830;s:9:"ForHaPras";i:2831;s:6:"PeTubi";i:2832;s:13:"DiffeIneVesic";i:2833;s:10:"NonveTeneb";i:2834;s:6:"TauUna";i:2835;s:14:"JeronUncuUntha";i:2836;s:5:"Telem";i:2837;s:9:"BargHaSul";i:2838;s:11:"InartMelaSt";i:2839;s:5:"Confe";i:2840;s:9:"MobPseTen";i:2841;s:4:"Obst";i:2842;s:9:"IvanPedan";i:2843;s:5:"Noctu";i:2844;s:8:"MythTiUn";i:2845;s:13:"ParlaQuadRash";i:2846;s:10:"CyOpiloPre";i:2847;s:13:"OsteUngyvUnsp";i:2848;s:5:"StZoo";i:2849;s:4:"Slid";i:2850;s:9:"ParRuVibr";i:2851;s:14:"EndoImposSubmi";i:2852;s:7:"SpiUnUn";i:2853;s:7:"PreRais";i:2854;s:9:"EpigMyxVo";i:2855;s:5:"ReUnd";i:2856;s:9:"RusSheUnm";i:2857;s:6:"PretWr";i:2858;s:3:"Hom";i:2859;s:6:"GrSini";i:2860;s:11:"ColeNonvPos";i:2861;s:3:"Tor";i:2862;s:8:"CraMonoc";i:2863;s:15:"MisfoPrestSaliv";i:2864;s:7:"NonWaho";i:2865;s:12:"GlanReliSeax";i:2866;s:10:"BrNonpUnse";i:2867;s:7:"TarocTh";i:2868;s:3:"Ora";i:2869;s:8:"GrudgPra";i:2870;s:6:"PeSnUn";i:2871;s:13:"LigniMicroUnc";i:2872;s:7:"ClogHor";i:2873;s:10:"MiserRebUn";i:2874;s:10:"EntSpeSple";i:2875;s:10:"ColleGuard";i:2876;s:5:"Totte";i:2877;s:8:"IsataTax";i:2878;s:8:"NonWorra";i:2879;s:5:"Unsha";i:2880;s:10:"EuStanxUne";i:2881;s:3:"Pin";i:2882;s:4:"MoSe";i:2883;s:5:"WeaWh";i:2884;s:7:"ProviRa";i:2885;s:11:"PreTibiUnin";i:2886;s:11:"MyeloUnsUnt";i:2887;s:9:"InspiStee";i:2888;s:5:"Sensu";i:2889;s:9:"StufSwerv";i:2890;s:2:"Sy";i:2891;s:11:"GunnHaiTrun";i:2892;s:7:"NeopPre";i:2893;s:11:"BitMooncTip";i:2894;s:4:"Unso";i:2895;s:12:"MajPhlebSpin";i:2896;s:5:"Furil";i:2897;s:5:"Sledf";i:2898;s:10:"LadylMaSac";i:2899;s:10:"ChMylioSen";i:2900;s:9:"IxParaUre";i:2901;s:9:"DoMedalUn";i:2902;s:6:"OuUnsa";i:2903;s:8:"DouNonax";i:2904;s:4:"Univ";i:2905;s:4:"Moly";i:2906;s:9:"MonitPeda";i:2907;s:9:"CelaLunul";i:2908;s:8:"AnDenaMe";i:2909;s:6:"IrTerr";i:2910;s:3:"Usu";i:2911;s:7:"UnfoUnv";i:2912;s:7:"JudMono";i:2913;s:14:"PentRagsoUnimb";i:2914;s:9:"ReTransWh";i:2915;s:14:"EquisHornsInco";i:2916;s:12:"HukxOligiSup";i:2917;s:9:"PhSpSuper";i:2918;s:11:"SemUndaUnpr";i:2919;s:4:"Self";i:2920;s:11:"FleeMatePro";i:2921;s:6:"SupWor";i:2922;s:5:"Unben";i:2923;s:9:"MemorTach";i:2924;s:6:"SegUns";i:2925;s:10:"CoDisplSod";i:2926;s:7:"EleSple";i:2927;s:8:"PolyUror";i:2928;s:3:"Yal";i:2929;s:5:"JiTri";i:2930;s:10:"TaiThyroUn";i:2931;s:4:"Wath";i:2932;s:7:"EquiRep";i:2933;s:10:"ShimSubUnd";i:2934;s:6:"HasSan";i:2935;s:5:"HemUt";i:2936;s:4:"Spiv";i:2937;s:10:"MunycPhary";i:2938;s:8:"LargOver";i:2939;s:6:"OveSab";i:2940;s:14:"AulosLarxTraga";i:2941;s:9:"DePubesZa";i:2942;s:9:"LutSeYaki";i:2943;s:8:"PoSphaTh";i:2944;s:3:"Igl";i:2945;s:5:"Contr";i:2946;s:10:"PolysRamSu";i:2947;s:5:"Unack";i:2948;s:10:"AnCapsHyda";i:2949;s:6:"PoeSli";i:2950;s:6:"SausUn";i:2951;s:12:"PostgStaghWi";i:2952;s:8:"TetrTita";i:2953;s:14:"MendaPostScran";i:2954;s:9:"CharaSeag";i:2955;s:8:"HexSpodi";i:2956;s:5:"Quadr";i:2957;s:8:"SubpSwea";i:2958;s:9:"DoNicoTer";i:2959;s:7:"MilliSl";i:2960;s:13:"HelpPneumSlim";i:2961;s:6:"ToeUrb";i:2962;s:10:"DiMaionVen";i:2963;s:7:"PestePo";i:2964;s:7:"LiyPura";i:2965;s:10:"SparlTrian";i:2966;s:5:"BraGa";i:2967;s:7:"FrontRe";i:2968;s:5:"Unall";i:2969;s:5:"Diape";i:2970;s:8:"EscIdiot";i:2971;s:5:"Salam";i:2972;s:13:"CommHypeInest";i:2973;s:4:"Wors";i:2974;s:7:"ReinfSa";i:2975;s:8:"ResciTre";i:2976;s:10:"PiStaurSup";i:2977;s:8:"PorraUpl";i:2978;s:8:"StocUnki";i:2979;s:4:"IsSe";i:2980;s:11:"SattTaTrans";i:2981;s:3:"Mel";i:2982;s:11:"UnanUnUnexp";i:2983;s:5:"Green";i:2984;s:13:"SokemStulWape";i:2985;s:6:"TacoVe";i:2986;s:5:"Coral";i:2987;s:13:"ChoroDipicShr";i:2988;s:11:"PseSterTepe";i:2989;s:6:"OverRe";i:2990;s:10:"FreehMonPo";i:2991;s:12:"KreiLocaPlas";i:2992;s:9:"HurMorPty";i:2993;s:10:"DaSeqTirre";i:2994;s:13:"HypsiReproUre";i:2995;s:5:"Subno";i:2996;s:5:"Outpo";i:2997;s:10:"AndSevenVo";i:2998;s:5:"Vehmi";i:2999;s:3:"Vet";i:3000;s:5:"Proto";i:3001;s:5:"Unbri";i:3002;s:4:"Spie";i:3003;s:11:"ClaColobWhe";i:3004;s:4:"MeSy";i:3005;s:7:"ForSuXy";i:3006;s:11:"ChaKukPrior";i:3007;s:5:"Draco";i:3008;s:5:"Stair";i:3009;s:3:"Len";i:3010;s:4:"Saga";i:3011;s:9:"PyrroReTy";i:3012;s:4:"Nigg";i:3013;s:6:"ProSki";i:3014;s:8:"AutoPeri";i:3015;s:12:"NitroPieTric";i:3016;s:5:"StaTu";i:3017;s:3:"Rec";i:3018;s:4:"Pall";i:3019;s:12:"PreceShamUpl";i:3020;s:7:"CoviWag";i:3021;s:6:"ImTran";i:3022;s:13:"MandaStageUnf";i:3023;s:9:"SemisXero";i:3024;s:3:"Eso";i:3025;s:7:"FumatTr";i:3026;s:4:"Weev";i:3027;s:8:"IntraOst";i:3028;s:11:"GaNeurUngel";i:3029;s:7:"UnUnUnp";i:3030;s:5:"SiTan";i:3031;s:5:"FlPhy";i:3032;s:10:"GmeRubWarl";i:3033;s:9:"CayapTerm";i:3034;s:10:"MoRefinVau";i:3035;s:5:"Phosp";i:3036;s:12:"EligiHexUnkn";i:3037;s:6:"OvUnor";i:3038;s:5:"Verbi";i:3039;s:4:"Wind";i:3040;s:6:"BoroPh";i:3041;s:12:"MesoRudSinec";i:3042;s:10:"DisHemaJam";i:3043;s:8:"ReSiphSt";i:3044;s:4:"Pret";i:3045;s:4:"Pros";i:3046;s:8:"GlaObstu";i:3047;s:8:"JouPaPip";i:3048;s:8:"PunUndef";i:3049;s:5:"Ultra";i:3050;s:3:"Pur";i:3051;s:12:"MisMockxNomo";i:3052;s:12:"TactThacUnhe";i:3053;s:8:"PostSund";i:3054;s:9:"QuadrSavi";i:3055;s:7:"RecTepi";i:3056;s:12:"RutteTeUnplu";i:3057;s:6:"EncPar";i:3058;s:6:"ElSexu";i:3059;s:5:"MonSe";i:3060;s:4:"Velo";i:3061;s:10:"IthaTropUn";i:3062;s:9:"DuffTaute";i:3063;s:9:"ThesaTrUn";i:3064;s:5:"Unson";i:3065;s:14:"HumisKensiPanp";i:3066;s:9:"NectShTil";i:3067;s:4:"Pais";i:3068;s:8:"FeatSemi";i:3069;s:3:"Scu";i:3070;s:11:"DispePhWavy";i:3071;s:9:"ReSexivSp";i:3072;s:12:"NonfeNonlaPh";i:3073;s:9:"CuForYipx";i:3074;s:5:"DiHap";i:3075;s:11:"OversPhytSp";i:3076;s:8:"OrthScor";i:3077;s:3:"Ofo";i:3078;s:6:"MyoPic";i:3079;s:9:"CarLeNaos";i:3080;s:8:"PalmSati";i:3081;s:9:"SpokUnimo";i:3082;s:5:"Gorin";i:3083;s:9:"DyOligUnc";i:3084;s:9:"PhorSpaeb";i:3085;s:9:"ParaSumpt";i:3086;s:11:"CloConDidym";i:3087;s:11:"MenUnaWanto";i:3088;s:5:"Tetra";i:3089;s:7:"PreTurn";i:3090;s:8:"SliTitiv";i:3091;s:8:"HowisUns";i:3092;s:12:"MemPlantUnce";i:3093;s:11:"OstePindSuc";i:3094;s:12:"NonPhycSpect";i:3095;s:4:"Pogo";i:3096;s:2:"Ex";i:3097;s:4:"SaSt";i:3098;s:14:"SeedxSheexSupe";i:3099;s:5:"Fring";i:3100;s:13:"ExcoUndefUnev";i:3101;s:6:"IrRamx";i:3102;s:3:"Rab";i:3103;s:2:"Oe";i:3104;s:8:"UnevZhmu";i:3105;s:5:"OutPr";i:3106;s:3:"Ger";i:3107;s:6:"MesVer";i:3108;s:9:"FuchTidel";i:3109;s:10:"ChRetUnter";i:3110;s:4:"Mall";i:3111;s:10:"ReddySanda";i:3112;s:3:"Ido";i:3113;s:12:"FonSylviUnha";i:3114;s:10:"SamsoTrans";i:3115;s:6:"OrchTe";i:3116;s:10:"NotSemUnse";i:3117;s:4:"Ultr";i:3118;s:4:"PoUn";i:3119;s:8:"TransVal";i:3120;s:8:"FuciGlob";i:3121;s:7:"ScotTet";i:3122;s:15:"RicheRonsdWindo";i:3123;s:5:"Steel";i:3124;s:5:"Semif";i:3125;s:6:"SupUpw";i:3126;s:4:"Bedp";i:3127;s:13:"DagDispiUnsen";i:3128;s:3:"Coa";i:3129;s:12:"JetPedalSego";i:3130;s:4:"Lion";i:3131;s:11:"CymSnipjTeu";i:3132;s:4:"Prop";i:3133;s:7:"NeuroYi";i:3134;s:9:"MasMeTrom";i:3135;s:10:"EthylLoRef";i:3136;s:10:"CombDogSpi";i:3137;s:8:"FrLoMidm";i:3138;s:4:"Myog";i:3139;s:9:"PrScleSub";i:3140;s:13:"RefTuboaUnsor";i:3141;s:9:"EvasiStei";i:3142;s:9:"SpUnValix";i:3143;s:5:"Spira";i:3144;s:4:"EuPs";i:3145;s:9:"HardPenna";i:3146;s:5:"Wonde";i:3147;s:12:"NeonVocXipho";i:3148;s:10:"ChalJuckRe";i:3149;s:8:"DeopMadd";i:3150;s:4:"Grea";i:3151;s:3:"Tik";i:3152;s:5:"SuTar";i:3153;s:9:"LimacScTo";i:3154;s:13:"OverpPenxUnci";i:3155;s:5:"Rimpi";i:3156;s:4:"PySa";i:3157;s:10:"HydLamaiSq";i:3158;s:12:"HyReserSpiro";i:3159;s:11:"BeaBothDrif";i:3160;s:10:"PlRhiSikar";i:3161;s:4:"Olid";i:3162;s:7:"DiSperm";i:3163;s:4:"Prel";i:3164;s:5:"Mioce";i:3165;s:5:"Nonim";i:3166;s:5:"Prize";i:3167;s:7:"StasVul";i:3168;s:10:"PreliWoods";i:3169;s:3:"Rif";i:3170;s:3:"Bur";i:3171;s:13:"PleurUncluVes";i:3172;s:6:"RefSam";i:3173;s:8:"TickeXer";i:3174;s:4:"Unla";i:3175;s:7:"HyPelec";i:3176;s:12:"ForHumbuMalm";i:3177;s:9:"FuOrbitPh";i:3178;s:7:"FerFoRe";i:3179;s:4:"CoVe";i:3180;s:6:"NuncSu";i:3181;s:7:"CuUnfor";i:3182;s:11:"DemoPerStru";i:3183;s:6:"ReevUn";i:3184;s:9:"UndeWhelk";i:3185;s:4:"FuTu";i:3186;s:5:"Ithom";i:3187;s:5:"Sopex";i:3188;s:7:"InSpeci";i:3189;s:5:"Nontu";i:3190;s:6:"HomPre";i:3191;s:10:"InteMyceSu";i:3192;s:9:"CelLoSlac";i:3193;s:4:"Piny";i:3194;s:13:"HeterSpionTur";i:3195;s:4:"Slot";i:3196;s:8:"MetPresh";i:3197;s:13:"DisiNonnuVert";i:3198;s:9:"ArOdonWhi";i:3199;s:8:"InInteMe";i:3200;s:4:"Weat";i:3201;s:9:"GaUnsupUn";i:3202;s:7:"PrisoSh";i:3203;s:10:"IndiLycMol";i:3204;s:7:"PaRehUn";i:3205;s:7:"PollPro";i:3206;s:3:"Gro";i:3207;s:4:"Past";i:3208;s:10:"LambsPanty";i:3209;s:9:"ImpliProc";i:3210;s:5:"Cried";i:3211;s:6:"CycaUn";i:3212;s:14:"KeaxScreeUnlet";i:3213;s:8:"NundRevo";i:3214;s:12:"MadSherWeebl";i:3215;s:2:"Hi";i:3216;s:11:"RajaUnWokex";i:3217;s:12:"PseudRiviSte";i:3218;s:7:"FlaPaUn";i:3219;s:3:"Tax";i:3220;s:4:"PhPu";i:3221;s:11:"GraPsammStr";i:3222;s:14:"CelluEquiRailb";i:3223;s:7:"CongKer";i:3224;s:7:"MagnePs";i:3225;s:13:"NovelSeqSignl";i:3226;s:10:"DauncFoute";i:3227;s:11:"ArianPucRig";i:3228;s:10:"AlaCapThir";i:3229;s:7:"ConteDe";i:3230;s:12:"RupRuskySpri";i:3231;s:8:"FreUncon";i:3232;s:8:"HxLonYvo";i:3233;s:9:"PeriaStag";i:3234;s:2:"Op";i:3235;s:6:"LibiVa";i:3236;s:7:"PouTumu";i:3237;s:5:"Unerr";i:3238;s:7:"OblOrch";i:3239;s:8:"CountUng";i:3240;s:10:"NordiUneas";i:3241;s:9:"CtOrtSail";i:3242;s:5:"Roomm";i:3243;s:4:"Fant";i:3244;s:3:"Gan";i:3245;s:6:"PeUnle";i:3246;s:8:"MuPolRok";i:3247;s:9:"DrawHyper";i:3248;s:13:"EpiMaywiNotho";i:3249;s:7:"LySaxTe";i:3250;s:6:"NoncRh";i:3251;s:6:"FoUnsh";i:3252;s:3:"Sym";i:3253;s:11:"DiaOveThraw";i:3254;s:8:"CycloSem";i:3255;s:8:"PanjaThe";i:3256;s:2:"Ri";i:3257;s:5:"SuUnm";i:3258;s:9:"ArCoPirop";i:3259;s:9:"GoffLibMa";i:3260;s:10:"InsepTalpa";i:3261;s:4:"Steg";i:3262;s:5:"Renai";i:3263;s:9:"SubUnUnwi";i:3264;s:9:"PerPersQu";i:3265;s:5:"DisHo";i:3266;s:4:"Test";i:3267;s:8:"OpenhShi";i:3268;s:4:"SaTe";i:3269;s:5:"SarVe";i:3270;s:8:"PrSpVisi";i:3271;s:9:"KeLoloxRe";i:3272;s:3:"Exe";i:3273;s:4:"Plet";i:3274;s:3:"Uno";i:3275;s:5:"Overr";i:3276;s:7:"MyQuadr";i:3277;s:8:"MacSemSo";i:3278;s:11:"EgghoFeThur";i:3279;s:9:"GaNeTickl";i:3280;s:10:"CafEnlUnpr";i:3281;s:7:"CroScap";i:3282;s:12:"EspaNonmValv";i:3283;s:7:"ParaUnb";i:3284;s:3:"Tal";i:3285;s:9:"CysDitNon";i:3286;s:5:"Unsan";i:3287;s:5:"GlyRe";i:3288;s:4:"Tent";i:3289;s:3:"Bio";i:3290;s:9:"MeSpielUn";i:3291;s:12:"GrufRewoUnch";i:3292;s:5:"EumSe";i:3293;s:3:"Mog";i:3294;s:7:"SoUnswa";i:3295;s:9:"RidSpXero";i:3296;s:4:"Pock";i:3297;s:5:"Steno";i:3298;s:9:"EndMiliVi";i:3299;s:6:"EvocRh";i:3300;s:8:"JoQuisUn";i:3301;s:4:"Gemm";i:3302;s:7:"PinSoma";i:3303;s:6:"JubMul";i:3304;s:10:"BecivBrent";i:3305;s:9:"JaunSynco";i:3306;s:10:"KenMalSure";i:3307;s:8:"SeTriaWo";i:3308;s:9:"ScTriUpal";i:3309;s:10:"FeMycoZucc";i:3310;s:7:"FeUntru";i:3311;s:10:"ScathWeath";i:3312;s:5:"Knubb";i:3313;s:5:"OvUnu";i:3314;s:11:"DoNonliUnre";i:3315;s:5:"Relie";i:3316;s:4:"Must";i:3317;s:4:"PaRe";i:3318;s:2:"Il";i:3319;s:9:"DartPaTat";i:3320;s:5:"KilPr";i:3321;s:4:"DoMi";i:3322;s:7:"ScoSuff";i:3323;s:5:"PirSi";i:3324;s:4:"Rota";i:3325;s:3:"Wad";i:3326;s:14:"InfaMesolMonoh";i:3327;s:7:"RigidUn";i:3328;s:10:"RosSimilUn";i:3329;s:3:"Thr";i:3330;s:9:"DecolDupl";i:3331;s:11:"DialGlazePr";i:3332;s:6:"FunUnt";i:3333;s:6:"ScSemi";i:3334;s:4:"Elec";i:3335;s:14:"HeauLargeUpsta";i:3336;s:7:"SpUptra";i:3337;s:3:"Sno";i:3338;s:7:"NoNontr";i:3339;s:7:"DubTurb";i:3340;s:9:"MilkSupra";i:3341;s:7:"PhiPySu";i:3342;s:14:"NightPerioRect";i:3343;s:4:"EuMe";i:3344;s:12:"FuliLutePros";i:3345;s:12:"AutDeroEyoty";i:3346;s:10:"HulPrusSpu";i:3347;s:7:"PatWeez";i:3348;s:5:"Fragm";i:3349;s:6:"NuTigx";i:3350;s:6:"InfUlt";i:3351;s:6:"CoSubj";i:3352;s:8:"GreHyLea";i:3353;s:6:"CypsEm";i:3354;s:4:"Unsa";i:3355;s:5:"GuPro";i:3356;s:4:"Rotu";i:3357;s:5:"DoEmb";i:3358;s:3:"Pia";i:3359;s:7:"DrMoroc";i:3360;s:7:"LeOpsim";i:3361;s:5:"Sextu";i:3362;s:13:"EpipRecaShoop";i:3363;s:9:"ExaraSiVe";i:3364;s:12:"PreasSooUndi";i:3365;s:6:"FroInv";i:3366;s:7:"MorthPa";i:3367;s:3:"Wri";i:3368;s:7:"OverTet";i:3369;s:3:"Yea";i:3370;s:4:"Sand";i:3371;s:11:"DemonDiManq";i:3372;s:5:"MoiTi";i:3373;s:8:"ForsHype";i:3374;s:8:"HaloMont";i:3375;s:11:"InvPacUnexh";i:3376;s:12:"SilvUnaggWir";i:3377;s:8:"MuSemSen";i:3378;s:4:"Disp";i:3379;s:6:"ProUnq";i:3380;s:10:"ImploVenet";i:3381;s:4:"Hors";i:3382;s:5:"LucTh";i:3383;s:9:"ObliTibio";i:3384;s:4:"Homo";i:3385;s:12:"MonoSabSelen";i:3386;s:7:"NonSubm";i:3387;s:4:"Seri";i:3388;s:8:"CypriTeh";i:3389;s:8:"LocSewer";i:3390;s:12:"PiliTubUnmor";i:3391;s:5:"Beami";i:3392;s:8:"HyRegRep";i:3393;s:7:"SmUnrou";i:3394;s:7:"HeNoUnd";i:3395;s:8:"CyResSob";i:3396;s:9:"ElecaMocm";i:3397;s:5:"Quini";i:3398;s:10:"TegeaUnsca";i:3399;s:10:"ScorSeTrip";i:3400;s:8:"OligSeTh";i:3401;s:9:"HuRooUnde";i:3402;s:5:"Razor";i:3403;s:4:"Uncl";i:3404;s:6:"ScriSu";i:3405;s:4:"Unpu";i:3406;s:8:"RomneSin";i:3407;s:4:"Supe";i:3408;s:5:"Subor";i:3409;s:10:"PhotoProSt";i:3410;s:5:"MaVer";i:3411;s:8:"GrImToba";i:3412;s:10:"PaRetUnfor";i:3413;s:9:"PanPriaRe";i:3414;s:8:"FlokiPht";i:3415;s:6:"PreSol";i:3416;s:8:"QuiniVar";i:3417;s:12:"BurnRetSphae";i:3418;s:7:"GondSpe";i:3419;s:5:"Pseud";i:3420;s:7:"MeRespe";i:3421;s:5:"OtTur";i:3422;s:7:"OuPedRe";i:3423;s:10:"CeraNoSara";i:3424;s:7:"EpiFiRe";i:3425;s:14:"SulphThallTwee";i:3426;s:7:"OuRhema";i:3427;s:4:"Fanc";i:3428;s:8:"OoscoWay";i:3429;s:14:"ComfoHomodHypo";i:3430;s:12:"ImTapisUnfit";i:3431;s:4:"Laix";i:3432;s:5:"Unfes";i:3433;s:2:"Bu";i:3434;s:7:"EtReoTo";i:3435;s:9:"SnippYard";i:3436;s:5:"SaUnd";i:3437;s:5:"Osteo";i:3438;s:8:"PageaUnd";i:3439;s:8:"InhNonUn";i:3440;s:14:"MiscPilasSilic";i:3441;s:9:"OvePnStyc";i:3442;s:8:"BaNoncTo";i:3443;s:5:"Super";i:3444;s:6:"IrSeUn";i:3445;s:9:"CountEchi";i:3446;s:5:"BliRo";i:3447;s:11:"KeMoveaUnha";i:3448;s:15:"CratcSeamrSuper";i:3449;s:4:"Mist";i:3450;s:4:"Wrec";i:3451;s:7:"CenNons";i:3452;s:5:"Confr";i:3453;s:9:"IndecUnal";i:3454;s:5:"Groov";i:3455;s:5:"Nonre";i:3456;s:12:"OuthiSoSuper";i:3457;s:6:"HeSubc";i:3458;s:15:"MetriScuftTable";i:3459;s:5:"Satie";i:3460;s:12:"IncoOrphaSqu";i:3461;s:4:"HyTh";i:3462;s:10:"DirepGiMoi";i:3463;s:3:"Mor";i:3464;s:5:"PaPho";i:3465;s:6:"TavTol";i:3466;s:9:"IsthmNapp";i:3467;s:6:"CopSpl";i:3468;s:6:"BuTren";i:3469;s:11:"PrepUncrUnp";i:3470;s:3:"Mis";i:3471;s:5:"Twitt";i:3472;s:5:"Koiar";i:3473;s:10:"BuHirTetra";i:3474;s:7:"DipiWis";i:3475;s:5:"BasGh";i:3476;s:11:"DeziPreRune";i:3477;s:5:"PerPr";i:3478;s:5:"Scrol";i:3479;s:9:"CenSapSna";i:3480;s:12:"ObraOchleTyk";i:3481;s:6:"AreLan";i:3482;s:10:"MeniNoProa";i:3483;s:4:"Wadm";i:3484;s:10:"LaMotleUnd";i:3485;s:7:"CoSurfe";i:3486;s:5:"KaUnm";i:3487;s:5:"IlSim";i:3488;s:5:"Plero";i:3489;s:9:"LanceSele";i:3490;s:2:"Ro";i:3491;s:11:"DrawsSecuVe";i:3492;s:4:"Vine";i:3493;s:5:"Mythm";i:3494;s:8:"DiJesuKo";i:3495;s:9:"CorLiScut";i:3496;s:5:"ObeOr";i:3497;s:9:"PreStouTe";i:3498;s:8:"SubUnfor";i:3499;s:11:"DiEtonThala";i:3500;s:13:"FamOrallSuper";i:3501;s:5:"Unrib";i:3502;s:7:"PrReUno";i:3503;s:11:"MichTricWai";i:3504;s:8:"OveUtric";i:3505;s:5:"Propa";i:3506;s:10:"PolycVikin";i:3507;s:9:"DivMeduTh";i:3508;s:13:"LuxuTranViper";i:3509;s:8:"DiFrHypo";i:3510;s:13:"MagniMontWres";i:3511;s:10:"CantDeUnci";i:3512;s:10:"PeScripUnf";i:3513;s:10:"ClaitSpUnc";i:3514;s:10:"DecoPhoSup";i:3515;s:5:"InSys";i:3516;s:13:"RhetoUdoUnder";i:3517;s:4:"Marr";i:3518;s:5:"LaRed";i:3519;s:10:"KilolPiUns";i:3520;s:5:"Monoc";i:3521;s:13:"GalliUreViato";i:3522;s:3:"Mac";i:3523;s:9:"SadhScamm";i:3524;s:11:"PhoenSinUns";i:3525;s:8:"EpiUnjud";i:3526;s:8:"JasMonTu";i:3527;s:8:"ParWangx";i:3528;s:9:"AverCaste";i:3529;s:11:"BindMonoUnm";i:3530;s:3:"Sph";i:3531;s:5:"Postf";i:3532;s:5:"Oligo";i:3533;s:10:"ComexRodin";i:3534;s:4:"ThWe";i:3535;s:5:"Unfra";i:3536;s:10:"PleurUnaug";i:3537;s:8:"DepeNeSe";i:3538;s:10:"DiProRaddl";i:3539;s:7:"FeThere";i:3540;s:10:"HenOverpSi";i:3541;s:5:"Epido";i:3542;s:5:"GarTr";i:3543;s:7:"HypThZi";i:3544;s:4:"ReTw";i:3545;s:5:"Trans";i:3546;s:8:"AfReSchi";i:3547;s:5:"Dacry";i:3548;s:12:"QuartTruncWi";i:3549;s:10:"DishoFooti";i:3550;s:12:"ChasInvLutec";i:3551;s:4:"Whip";i:3552;s:5:"MaWin";i:3553;s:5:"HyUns";i:3554;s:11:"EmEvangPoly";i:3555;s:5:"Submo";i:3556;s:4:"PaSw";i:3557;s:6:"DipLac";i:3558;s:7:"MegRemo";i:3559;s:12:"ExtraHymeSec";i:3560;s:7:"OutlePl";i:3561;s:12:"KhasRanjUnle";i:3562;s:10:"RhRounxSan";i:3563;s:4:"Thea";i:3564;s:12:"GraRelatUnri";i:3565;s:4:"Snar";i:3566;s:4:"SoTh";i:3567;s:5:"Undel";i:3568;s:9:"GazHydSem";i:3569;s:11:"CatCopiUnho";i:3570;s:5:"Cerol";i:3571;s:6:"MisrTa";i:3572;s:11:"IntJouLiqui";i:3573;s:3:"Sec";i:3574;s:8:"UnrowVil";i:3575;s:8:"LocoThTh";i:3576;s:10:"HandmUngue";i:3577;s:2:"Is";i:3578;s:11:"RefUterXant";i:3579;s:5:"Canid";i:3580;s:7:"InIsote";i:3581;s:7:"PeUncor";i:3582;s:5:"HoWin";i:3583;s:9:"DisadNoti";i:3584;s:13:"GuttMaeaRamma";i:3585;s:4:"UnWa";i:3586;s:13:"EpiMerrySexua";i:3587;s:7:"FlunkSu";i:3588;s:4:"Solv";i:3589;s:8:"MiNeOnag";i:3590;s:4:"Nibo";i:3591;s:11:"FlocHydMuri";i:3592;s:9:"CaJozyOle";i:3593;s:9:"FemeResti";i:3594;s:7:"PhRocce";i:3595;s:4:"Urun";i:3596;s:9:"DrakeFrum";i:3597;s:11:"InsaMaOctin";i:3598;s:8:"PertuTea";i:3599;s:9:"MisOuriPh";i:3600;s:12:"HeSeaweWretc";i:3601;s:4:"MaPo";i:3602;s:9:"PancQuidd";i:3603;s:2:"Wh";i:3604;s:7:"SmugSol";i:3605;s:4:"Tris";i:3606;s:5:"CeInf";i:3607;s:7:"OphiTeg";i:3608;s:6:"FoFrem";i:3609;s:10:"HabilStore";i:3610;s:3:"Ste";i:3611;s:3:"Vei";i:3612;s:6:"HetmSy";i:3613;s:13:"RivoSpinoZoop";i:3614;s:7:"GyrMiSo";i:3615;s:11:"MeNoncPathi";i:3616;s:10:"DerOrPobsx";i:3617;s:5:"Volti";i:3618;s:9:"PoacUnrep";i:3619;s:9:"ExacSnaUn";i:3620;s:3:"Sce";i:3621;s:7:"DisSple";i:3622;s:11:"KiMonoUnaro";i:3623;s:4:"Tall";i:3624;s:12:"MyelOldSheet";i:3625;s:3:"Con";i:3626;s:14:"CollExpedPreci";i:3627;s:11:"InfecNevYol";i:3628;s:8:"ExHyTomf";i:3629;s:3:"Shi";i:3630;s:2:"Na";i:3631;s:11:"ErGownsMous";i:3632;s:7:"ScUtsuk";i:3633;s:9:"IsoQuRecr";i:3634;s:6:"ChaHon";i:3635;s:11:"EquIconoNep";i:3636;s:10:"TuitiUnsin";i:3637;s:11:"FoxMooUnpor";i:3638;s:4:"Synt";i:3639;s:7:"HemNerv";i:3640;s:5:"Wroth";i:3641;s:7:"UnZirba";i:3642;s:10:"HeterThUnm";i:3643;s:8:"ReUnraVo";i:3644;s:7:"PissaRo";i:3645;s:9:"DiFretSix";i:3646;s:8:"RabUnpro";i:3647;s:12:"InteIrksLipo";i:3648;s:10:"ByrSumpUnp";i:3649;s:2:"Oo";i:3650;s:13:"AntimDaggPseu";i:3651;s:12:"SuVaginVerbe";i:3652;s:5:"InLar";i:3653;s:5:"Fibro";i:3654;s:10:"FoldMeUnab";i:3655;s:12:"BiPrediProar";i:3656;s:11:"SyphThrasTr";i:3657;s:11:"ExNitroPala";i:3658;s:7:"DegrPre";i:3659;s:13:"ExormFeudxTer";i:3660;s:7:"SuTraUn";i:3661;s:7:"NyaSche";i:3662;s:7:"EntoGer";i:3663;s:9:"MacrTrans";i:3664;s:6:"DenoGn";i:3665;s:12:"MultiUnbarUn";i:3666;s:10:"InsLipPenc";i:3667;s:10:"JanTyroWhi";i:3668;s:10:"HalHydruMo";i:3669;s:3:"Irr";i:3670;s:13:"SkaffSubTriad";i:3671;s:5:"Windr";i:3672;s:5:"Unebb";i:3673;s:5:"RevWi";i:3674;s:7:"MajTend";i:3675;s:7:"PreUnbe";i:3676;s:12:"MyoOutscPala";i:3677;s:10:"DrainIlUnm";i:3678;s:13:"RepeSaxxUndis";i:3679;s:11:"ImidMuPsila";i:3680;s:7:"DopLuci";i:3681;s:7:"DecTezx";i:3682;s:9:"ToothViri";i:3683;s:14:"MagneRaspiTach";i:3684;s:10:"SluSwYalix";i:3685;s:10:"PhanSyUnel";i:3686;s:8:"HuReSpec";i:3687;s:4:"Pitc";i:3688;s:8:"HiHyShor";i:3689;s:12:"LieNonacPseu";i:3690;s:13:"KnaOinomSerri";i:3691;s:6:"EntUph";i:3692;s:9:"OdoSaraSu";i:3693;s:5:"Varis";i:3694;s:6:"CorOrp";i:3695;s:9:"BlNocSpli";i:3696;s:7:"SwomWis";i:3697;s:12:"PanscResWigw";i:3698;s:6:"RepTin";i:3699;s:5:"Chitc";i:3700;s:6:"InquMi";i:3701;s:6:"InWhin";i:3702;s:7:"HerTabo";i:3703;s:7:"RackfTu";i:3704;s:10:"MediRabbUn";i:3705;s:12:"CauloExospIn";i:3706;s:11:"HydroSiUnch";i:3707;s:8:"CyPrRect";i:3708;s:7:"AllBand";i:3709;s:8:"HystMail";i:3710;s:12:"HypLooseSole";i:3711;s:10:"CoFreckNon";i:3712;s:10:"AnEffeTali";i:3713;s:10:"DoctrPerit";i:3714;s:5:"SiUna";i:3715;s:4:"Rhiz";i:3716;s:4:"OuRe";i:3717;s:4:"Hobb";i:3718;s:3:"Cer";i:3719;s:9:"AnOrangRa";i:3720;s:9:"SoulfSpZo";i:3721;s:11:"UnreUnsVeto";i:3722;s:5:"HyTec";i:3723;s:9:"AtPhotUns";i:3724;s:4:"Lune";i:3725;s:3:"Tro";i:3726;s:8:"PhagoPim";i:3727;s:9:"NagNoUnde";i:3728;s:9:"FezzeMike";i:3729;s:10:"RiffxVersa";i:3730;s:8:"SigSusan";i:3731;s:7:"OverRei";i:3732;s:10:"NonOverPer";i:3733;s:11:"CoundSpriTc";i:3734;s:5:"Bismu";i:3735;s:9:"ClefThrea";i:3736;s:9:"PinkTetra";i:3737;s:5:"Therm";i:3738;s:5:"PeXyl";i:3739;s:5:"SaSma";i:3740;s:15:"PerinTransUnart";i:3741;s:5:"Pytha";i:3742;s:7:"SyUngen";i:3743;s:9:"BeMonotPa";i:3744;s:11:"DiDictyMoto";i:3745;s:11:"PolyTrWarbl";i:3746;s:9:"FaForeHor";i:3747;s:4:"Inkl";i:3748;s:5:"Perfr";i:3749;s:8:"FunMeRep";i:3750;s:10:"PrePrSkunk";i:3751;s:6:"DoHepa";i:3752;s:12:"NonParoPhilo";i:3753;s:7:"GroUnre";i:3754;s:9:"PillaUnwa";i:3755;s:10:"CixoxSocTh";i:3756;s:6:"HypPat";i:3757;s:15:"TautoTreacUnhar";i:3758;s:4:"Cant";i:3759;s:7:"DisUnsa";i:3760;s:15:"InconMilleWenon";i:3761;s:13:"EmbryNonacPre";i:3762;s:5:"Nonfl";i:3763;s:12:"RegaTripUngr";i:3764;s:9:"ScratWarn";i:3765;s:6:"BiGluc";i:3766;s:7:"BaPolyp";i:3767;s:11:"NovaReskSto";i:3768;s:4:"Utte";i:3769;s:6:"PlaPle";i:3770;s:9:"HematMona";i:3771;s:8:"OphZoono";i:3772;s:10:"StaidUnpre";i:3773;s:9:"DispoIntr";i:3774;s:7:"JeluJet";i:3775;s:4:"Four";i:3776;s:10:"IntMycoUpl";i:3777;s:5:"Skulk";i:3778;s:11:"LepPoodlSer";i:3779;s:13:"OxyrhSpiSquar";i:3780;s:7:"ExHulPa";i:3781;s:5:"SaSod";i:3782;s:15:"PretySweepSweet";i:3783;s:11:"GryMystSter";i:3784;s:5:"RuSmo";i:3785;s:7:"SuperUn";i:3786;s:8:"PolStorm";i:3787;s:8:"IncliPac";i:3788;s:5:"Prepr";i:3789;s:8:"MeckeMeg";i:3790;s:5:"Unobj";i:3791;s:3:"Gra";i:3792;s:9:"OwerlPrTe";i:3793;s:9:"BrShaVesi";i:3794;s:5:"Thaum";i:3795;s:2:"Ki";i:3796;s:7:"HeUngui";i:3797;s:4:"Sout";i:3798;s:4:"Ross";i:3799;s:8:"EyeWhips";i:3800;s:10:"HeIatroUnp";i:3801;s:5:"MePro";i:3802;s:5:"Fault";i:3803;s:9:"MutaSabba";i:3804;s:3:"Swa";i:3805;s:8:"ReSoWive";i:3806;s:5:"FlIsl";i:3807;s:4:"HiPh";i:3808;s:8:"ChelMerc";i:3809;s:6:"BlaReq";i:3810;s:4:"Codi";i:3811;s:9:"SpTriWote";i:3812;s:9:"LegiPyrSt";i:3813;s:6:"EnLida";i:3814;s:4:"Ungr";i:3815;s:11:"PuSchoSnudg";i:3816;s:8:"HyOsmUnh";i:3817;s:7:"UndeZos";i:3818;s:12:"PhilProvSnap";i:3819;s:11:"OoPeriSangu";i:3820;s:5:"Smutt";i:3821;s:5:"MePac";i:3822;s:5:"Thuri";i:3823;s:14:"ArsenHerseSuba";i:3824;s:4:"Macc";i:3825;s:5:"Untra";i:3826;s:8:"CrNonPil";i:3827;s:4:"CaMe";i:3828;s:8:"PanTherm";i:3829;s:12:"InMesolPatac";i:3830;s:8:"MatriNon";i:3831;s:6:"GeoSex";i:3832;s:9:"NurseShit";i:3833;s:4:"Stro";i:3834;s:11:"BavPreinTug";i:3835;s:11:"KarsMuUnwit";i:3836;s:4:"Meta";i:3837;s:7:"OvPrill";i:3838;s:7:"MiMucos";i:3839;s:10:"CouMobsSha";i:3840;s:4:"Visi";i:3841;s:7:"SmooSub";i:3842;s:6:"CyPaSi";i:3843;s:9:"MyPacUntu";i:3844;s:5:"Oricy";i:3845;s:3:"Sup";i:3846;s:6:"TerUnd";i:3847;s:8:"IsMonoSa";i:3848;s:9:"HidSunrWo";i:3849;s:12:"MolaxSanUnst";i:3850;s:5:"MoPha";i:3851;s:8:"PieSubri";i:3852;s:5:"Watch";i:3853;s:4:"Team";i:3854;s:10:"PrSesaVind";i:3855;s:10:"HexadNaVac";i:3856;s:9:"MelanNoRa";i:3857;s:7:"PrSepSt";i:3858;s:5:"Sridh";i:3859;s:5:"MasPo";i:3860;s:7:"LiStatu";i:3861;s:10:"SprawTrans";i:3862;s:8:"LooPhoto";i:3863;s:9:"CaTaZingi";i:3864;s:3:"Oro";i:3865;s:5:"Dermi";i:3866;s:7:"DrierRe";i:3867;s:10:"MiskPerSup";i:3868;s:3:"Wat";i:3869;s:6:"CoWelc";i:3870;s:11:"NonPediScho";i:3871;s:10:"JemmOuRubo";i:3872;s:5:"Urtic";i:3873;s:5:"FrGal";i:3874;s:6:"LaceVa";i:3875;s:8:"RecSubch";i:3876;s:5:"SaSph";i:3877;s:12:"MarylMnemoOn";i:3878;s:3:"Lie";i:3879;s:8:"FluPrSuf";i:3880;s:5:"Pangx";i:3881;s:13:"MediReoccUnda";i:3882;s:9:"CelFatGou";i:3883;s:4:"Xant";i:3884;s:4:"Moil";i:3885;s:11:"DeltForIned";i:3886;s:7:"RegiUnp";i:3887;s:7:"KabylTh";i:3888;s:8:"ShaTrade";i:3889;s:7:"VilWaWi";i:3890;s:9:"MappiOsph";i:3891;s:5:"DriSl";i:3892;s:5:"Tauto";i:3893;s:5:"Sinia";i:3894;s:5:"PrUnr";i:3895;s:7:"UnsVerb";i:3896;s:13:"DesExploTorsi";i:3897;s:5:"PaUna";i:3898;s:8:"PhotUpis";i:3899;s:12:"FemHalfSubur";i:3900;s:10:"BesoDolMem";i:3901;s:9:"WordZebra";i:3902;s:5:"Sweep";i:3903;s:7:"TrUnpro";i:3904;s:11:"ResSerorWid";i:3905;s:8:"SafrSouv";i:3906;s:4:"Ridg";i:3907;s:11:"CancMessUnd";i:3908;s:6:"OvUnUn";i:3909;s:9:"ShSqueUne";i:3910;s:7:"OratoRo";i:3911;s:5:"GaSpe";i:3912;s:7:"CoOolog";i:3913;s:5:"NonPr";i:3914;s:9:"VanguWaho";i:3915;s:7:"ConseLa";i:3916;s:4:"Zimb";i:3917;s:2:"Im";i:3918;s:11:"AzDiasFonsx";i:3919;s:11:"CrEmbleGale";i:3920;s:7:"DiHonUn";i:3921;s:8:"PerjSphy";i:3922;s:12:"PomonStUnvol";i:3923;s:9:"MiPokoTik";i:3924;s:10:"BikhxConic";i:3925;s:4:"BiFi";i:3926;s:6:"CruFig";i:3927;s:4:"Requ";i:3928;s:5:"Amoeb";i:3929;s:9:"FeathHusb";i:3930;s:8:"ExcrLave";i:3931;s:3:"Obl";i:3932;s:10:"HyperMonSh";i:3933;s:7:"UnerrXe";i:3934;s:8:"HypJibNe";i:3935;s:11:"HousSbUnent";i:3936;s:4:"Pyra";i:3937;s:10:"BacParaSub";i:3938;s:5:"Occam";i:3939;s:12:"MichiRumblTu";i:3940;s:4:"Supr";i:3941;s:7:"FeUnsuf";i:3942;s:7:"DowieSp";i:3943;s:10:"FaceOppoUn";i:3944;s:12:"MetamTopoUbi";i:3945;s:7:"PeriPre";i:3946;s:7:"PsycSep";i:3947;s:8:"MeloUncl";i:3948;s:8:"UndanWau";i:3949;s:11:"AutoChoResy";i:3950;s:7:"OvPegUn";i:3951;s:10:"ArseSoyUns";i:3952;s:5:"SpeUn";i:3953;s:4:"Pinn";i:3954;s:5:"Unspr";i:3955;s:3:"Glo";i:3956;s:5:"BraLe";i:3957;s:10:"IdylNonUnm";i:3958;s:9:"ChilChimp";i:3959;s:2:"Hy";i:3960;s:11:"KharNicomPi";i:3961;s:14:"HemipPariUnpre";i:3962;s:8:"TexUnciv";i:3963;s:12:"MitNaphtRevi";i:3964;s:12:"IraqiKoreTat";i:3965;s:12:"HephInfuThix";i:3966;s:13:"ButtFratcPrer";i:3967;s:11:"BanExtraPro";i:3968;s:10:"PsPulpVomi";i:3969;s:9:"DraffTutw";i:3970;s:10:"NoSoliTheo";i:3971;s:6:"NonvSo";i:3972;s:10:"BeSpurSuba";i:3973;s:3:"Rey";i:3974;s:8:"FadGuTel";i:3975;s:12:"FluGeocPascu";i:3976;s:5:"Punty";i:3977;s:5:"Alcid";i:3978;s:5:"Soggy";i:3979;s:7:"PurpRes";i:3980;s:3:"Por";i:3981;s:7:"MultThw";i:3982;s:2:"Of";i:3983;s:7:"GalicOd";i:3984;s:13:"HyperMilUndis";i:3985;s:7:"ElegiUn";i:3986;s:4:"Inst";i:3987;s:8:"MonkcPan";i:3988;s:4:"Jawx";i:3989;s:12:"CoDonneRewar";i:3990;s:11:"MerkSemidUn";i:3991;s:14:"ErectRomerSero";i:3992;s:4:"Jett";i:3993;s:10:"DeJohRahul";i:3994;s:2:"Ar";i:3995;s:5:"Silic";i:3996;s:10:"PrearPrSem";i:3997;s:4:"Meso";i:3998;s:10:"CysLicheRe";i:3999;s:4:"Snag";i:4000;s:9:"GenecPres";i:4001;s:7:"PropSha";i:4002;s:9:"HamaTraUn";i:4003;s:9:"GyroMatUn";i:4004;s:12:"MisuNonParas";i:4005;s:13:"KindeOvePrein";i:4006;s:13:"IconUnentUnpl";i:4007;s:10:"MyrmeNotel";i:4008;s:10:"KrobPoUnsa";i:4009;s:10:"IndusPalau";i:4010;s:7:"HostPar";i:4011;s:9:"HelPreiSu";i:4012;s:5:"Kacha";i:4013;s:3:"Nac";i:4014;s:7:"SafeWan";i:4015;s:8:"ScrViost";i:4016;s:9:"LoSinnSub";i:4017;s:5:"Vedai";i:4018;s:12:"IdeoPanVaunt";i:4019;s:9:"DeErgxOcy";i:4020;s:8:"HanMePal";i:4021;s:7:"ShValky";i:4022;s:11:"DingIncuTel";i:4023;s:8:"PePillSp";i:4024;s:6:"FocPos";i:4025;s:5:"KnaTr";i:4026;s:4:"Wint";i:4027;s:12:"InsiTheUnmis";i:4028;s:7:"AtmogLe";i:4029;s:8:"MePalRec";i:4030;s:8:"ComKwann";i:4031;s:4:"Over";i:4032;s:12:"NiobiNoneqTo";i:4033;s:11:"KiScytStrop";i:4034;s:4:"Wast";i:4035;s:10:"CasuInteMo";i:4036;s:5:"ShWhi";i:4037;s:3:"Wro";i:4038;s:9:"SwasThTot";i:4039;s:5:"AndSc";i:4040;s:6:"GrapSc";i:4041;s:7:"FirepUn";i:4042;s:13:"BoerBuffaPass";i:4043;s:7:"CoSpTin";i:4044;s:3:"Gor";i:4045;s:7:"DisbeHi";i:4046;s:5:"EleSi";i:4047;s:12:"LympUninkYer";i:4048;s:5:"ShaSl";i:4049;s:9:"IlLiPlagi";i:4050;s:7:"CeOrtho";i:4051;s:7:"AntShel";i:4052;s:7:"DiPeris";i:4053;s:3:"Flo";i:4054;s:10:"GladiNonfa";i:4055;s:4:"Unen";i:4056;s:5:"Myosa";i:4057;s:8:"ReqUnder";i:4058;s:8:"IdyTortr";i:4059;s:10:"CanPrTheri";i:4060;s:2:"Ur";i:4061;s:10:"AtollCorti";i:4062;s:8:"DexGestx";i:4063;s:6:"MerQua";i:4064;s:4:"Usat";i:4065;s:14:"BromLicenNaseb";i:4066;s:11:"GoOrthoSull";i:4067;s:11:"QuinSuperUn";i:4068;s:12:"ConfKoftPass";i:4069;s:8:"OrgaSkTr";i:4070;s:6:"CheaOu";i:4071;s:11:"BafGynRamul";i:4072;s:9:"OpPerZepp";i:4073;s:9:"HypMerTom";i:4074;s:7:"RarelUn";i:4075;s:13:"EpidoPostcSta";i:4076;s:7:"VandyWh";i:4077;s:10:"OutwaRapSo";i:4078;s:11:"OverSouWall";i:4079;s:11:"BeclaChorCo";i:4080;s:3:"Sin";i:4081;s:6:"RaSnak";i:4082;s:10:"FiHomLumbr";i:4083;s:12:"PapaProaThyr";i:4084;s:7:"IntTric";i:4085;s:10:"DisInfrOrl";i:4086;s:10:"GoldeIntVo";i:4087;s:8:"DainOoUl";i:4088;s:9:"FullMulti";i:4089;s:9:"MolRizWoo";i:4090;s:5:"Mimos";i:4091;s:5:"Unfun";i:4092;s:7:"HiSuber";i:4093;s:9:"OpistTrac";i:4094;s:9:"MourPreho";i:4095;s:10:"ProtoRebra";i:4096;s:7:"DiManif";i:4097;s:9:"RepuSubin";i:4098;s:5:"Khald";i:4099;s:5:"SkiSu";i:4100;s:8:"BecloNon";i:4101;s:5:"Mesol";i:4102;s:12:"InnaSmeltTur";i:4103;s:9:"ExtooSidt";i:4104;s:12:"InduTitTrywo";i:4105;s:8:"CoMarsMa";i:4106;s:3:"Wei";i:4107;s:6:"GrSiph";i:4108;s:9:"MelOvPyro";i:4109;s:9:"JovinUnin";i:4110;s:6:"OlPsyc";i:4111;s:10:"GiggeWinly";i:4112;s:5:"Shoem";i:4113;s:8:"CoSyncUn";i:4114;s:10:"NomOverhSp";i:4115;s:10:"TrembUncon";i:4116;s:8:"ReinScSm";i:4117;s:5:"Gager";i:4118;s:11:"TiUnprWorke";i:4119;s:9:"MaParanRe";i:4120;s:9:"StruUnche";i:4121;s:3:"Div";i:4122;s:13:"IndiNeomWoodb";i:4123;s:7:"FiFlaUn";i:4124;s:6:"BryCru";i:4125;s:9:"SubdoSubs";i:4126;s:9:"ReveUnWin";i:4127;s:2:"Ir";i:4128;s:7:"GlSkill";i:4129;s:8:"MagySent";i:4130;s:6:"SarsUn";i:4131;s:14:"SquirVaricVola";i:4132;s:6:"GallWa";i:4133;s:7:"PlumbPr";i:4134;s:10:"EmersHaStr";i:4135;s:10:"EffForSupe";i:4136;s:7:"ReiSpin";i:4137;s:12:"PeacPleTinny";i:4138;s:4:"Tara";i:4139;s:11:"DoghInterSi";i:4140;s:9:"KetPoSpik";i:4141;s:7:"LiMalle";i:4142;s:4:"Unbo";i:4143;s:12:"SharUnfVersa";i:4144;s:7:"UndeWea";i:4145;s:3:"Myx";i:4146;s:7:"DetPlut";i:4147;s:7:"IroqRec";i:4148;s:7:"IntPsha";i:4149;s:5:"PosSe";i:4150;s:10:"SubTimarUn";i:4151;s:5:"MetPa";i:4152;s:9:"HoSnarStr";i:4153;s:5:"Phall";i:4154;s:10:"NecrThUnte";i:4155;s:12:"GemaHortSwam";i:4156;s:4:"Nong";i:4157;s:7:"CerSole";i:4158;s:10:"LatinPenma";i:4159;s:11:"PieriRejSem";i:4160;s:5:"PrPro";i:4161;s:8:"MultiPri";i:4162;s:4:"Twol";i:4163;s:7:"TraTurb";i:4164;s:7:"NaNodRe";i:4165;s:5:"Ouaba";i:4166;s:6:"KerPlu";i:4167;s:4:"Pont";i:4168;s:3:"Gur";i:4169;s:11:"PersSpoliUn";i:4170;s:7:"PlantSl";i:4171;s:2:"Fl";i:4172;s:8:"DiSuUnbo";i:4173;s:8:"IseReque";i:4174;s:11:"GenePtVentr";i:4175;s:4:"Rain";i:4176;s:7:"TrUnUns";i:4177;s:7:"DisUnmi";i:4178;s:10:"OcuReVorti";i:4179;s:8:"AzComSiz";i:4180;s:11:"InterStaVes";i:4181;s:6:"FoPanc";i:4182;s:12:"DeutLifNocta";i:4183;s:4:"HyVo";i:4184;s:7:"OvePres";i:4185;s:6:"SaUnpr";i:4186;s:13:"IzduPostePros";i:4187;s:11:"PePiscoUnri";i:4188;s:9:"FlakePist";i:4189;s:10:"GrappOratr";i:4190;s:9:"OilmaPoly";i:4191;s:4:"Soro";i:4192;s:11:"BellUncloUn";i:4193;s:4:"Ribb";i:4194;s:5:"Encri";i:4195;s:11:"NocuShopSia";i:4196;s:8:"GloTraum";i:4197;s:10:"NondParaPh";i:4198;s:12:"GooxPanWeapo";i:4199;s:8:"ScleWhor";i:4200;s:6:"LokaRa";i:4201;s:9:"SaWeaseWi";i:4202;s:8:"ConUnglo";i:4203;s:6:"TafiWo";i:4204;s:13:"EmpPrayfSubal";i:4205;s:11:"CallCamComp";i:4206;s:8:"CorPaleo";i:4207;s:9:"FrOpiuPal";i:4208;s:7:"CardoHe";i:4209;s:10:"LapMaQuebr";i:4210;s:11:"EncycKupUnh";i:4211;s:3:"Opi";i:4212;s:12:"HydroMeRecti";i:4213;s:4:"Nomo";i:4214;s:5:"Unsub";i:4215;s:7:"HomesRa";i:4216;s:12:"ForksMoStepg";i:4217;s:8:"OctoPopp";i:4218;s:12:"FrazQuernSna";i:4219;s:5:"Serol";i:4220;s:7:"NonelSc";i:4221;s:11:"PepPlaUncom";i:4222;s:11:"NonRetrUnha";i:4223;s:5:"Moril";i:4224;s:4:"CoTo";i:4225;s:7:"EnduLin";i:4226;s:8:"PhoebUng";i:4227;s:11:"HetTalkWrec";i:4228;s:12:"JadisMuUnfor";i:4229;s:5:"BitPu";i:4230;s:5:"EuOle";i:4231;s:10:"UnpUnrarUp";i:4232;s:3:"Vis";i:4233;s:7:"OilskPe";i:4234;s:9:"MaSuVicel";i:4235;s:12:"HypoiIlocaWa";i:4236;s:5:"Figle";i:4237;s:5:"Level";i:4238;s:7:"EntalIn";i:4239;s:7:"PrePrun";i:4240;s:7:"ScolUnf";i:4241;s:3:"Gna";i:4242;s:8:"SepSymph";i:4243;s:5:"LeiSp";i:4244;s:5:"Onrus";i:4245;s:12:"NonOxidoStee";i:4246;s:9:"AsymReiRe";i:4247;s:11:"OvePluRelin";i:4248;s:9:"MePedVege";i:4249;s:12:"HemMarkMasse";i:4250;s:10:"OrogeUnpea";i:4251;s:9:"ForPilThy";i:4252;s:7:"MaPtery";i:4253;s:10:"OraShrUndi";i:4254;s:5:"Bacte";i:4255;s:6:"RefoUn";i:4256;s:13:"ParabPhysaTwa";i:4257;s:13:"ClaDandlItera";i:4258;s:9:"LeucMicOp";i:4259;s:3:"Vip";i:4260;s:7:"TrunkWr";i:4261;s:5:"Vermi";i:4262;s:10:"DupHirUnde";i:4263;s:14:"HoardSalteTaur";i:4264;s:3:"Vio";i:4265;s:5:"Goatb";i:4266;s:12:"DressInMaryx";i:4267;s:4:"Neph";i:4268;s:12:"TanstTrivaWe";i:4269;s:6:"LupeSu";i:4270;s:2:"Us";i:4271;s:6:"PerTab";i:4272;s:9:"FiMerYeas";i:4273;s:4:"OnOt";i:4274;s:10:"AntihSmich";i:4275;s:7:"ObiRoSa";i:4276;s:9:"UnscWelsh";i:4277;s:7:"RhombUn";i:4278;s:5:"Think";i:4279;s:6:"SinWes";i:4280;s:5:"LesSu";i:4281;s:5:"NoObs";i:4282;s:8:"ScarWusp";i:4283;s:8:"HoNaPhot";i:4284;s:5:"Vulca";i:4285;s:12:"MarrOwsYeast";i:4286;s:4:"Togu";i:4287;s:13:"FiloPilgrRuth";i:4288;s:8:"CouPyrTe";i:4289;s:7:"ConUnpa";i:4290;s:4:"Unag";i:4291;s:9:"SaUncUroc";i:4292;s:6:"UnrWhe";i:4293;s:9:"PolyShatt";i:4294;s:5:"ReaSi";i:4295;s:7:"NonOuac";i:4296;s:10:"OlidPsUnex";i:4297;s:6:"SwunUn";i:4298;s:7:"TorvVac";i:4299;s:9:"SoariVall";i:4300;s:9:"EreGaShea";i:4301;s:10:"PospoSemip";i:4302;s:8:"NoSuscUn";i:4303;s:3:"Ost";i:4304;s:9:"ErytMolSa";i:4305;s:5:"SaTre";i:4306;s:7:"PaShaZe";i:4307;s:11:"EnlTaarxTra";i:4308;s:5:"Puppi";i:4309;s:3:"War";i:4310;s:4:"Fera";i:4311;s:13:"CircHandkStal";i:4312;s:6:"PiSubc";i:4313;s:5:"Morrh";i:4314;s:10:"JoiPhytoTe";i:4315;s:8:"DevExcur";i:4316;s:7:"NunsPig";i:4317;s:10:"MarsPiePri";i:4318;s:6:"DuraSa";i:4319;s:5:"FluGe";i:4320;s:4:"RoWi";i:4321;s:5:"Ovine";i:4322;s:10:"PepsiPhVir";i:4323;s:8:"MiSpSupe";i:4324;s:3:"Sel";i:4325;s:5:"Sympt";i:4326;s:11:"IsUndrWhatt";i:4327;s:6:"AnOstr";i:4328;s:4:"IrSa";i:4329;s:4:"Spal";i:4330;s:4:"Sear";i:4331;s:3:"Fiq";i:4332;s:7:"FaOutro";i:4333;s:11:"DamoMePolyd";i:4334;s:13:"HarmfOreTushx";i:4335;s:5:"MaUnt";i:4336;s:7:"DesmoTa";i:4337;s:9:"CossaPhPh";i:4338;s:5:"Notho";i:4339;s:5:"Sowli";i:4340;s:7:"MisesPo";i:4341;s:4:"Uplo";i:4342;s:5:"Recti";i:4343;s:7:"GloHolo";i:4344;s:9:"TyppUnnob";i:4345;s:3:"Euc";i:4346;s:8:"MonNotPr";i:4347;s:7:"LogiSoo";i:4348;s:6:"NoUnde";i:4349;s:4:"Mosa";i:4350;s:3:"Cor";i:4351;s:9:"CinquSupr";i:4352;s:7:"PunkVra";i:4353;s:10:"CarSlWebwo";i:4354;s:5:"CarDe";i:4355;s:7:"TetraUl";i:4356;s:2:"Jo";i:4357;s:4:"OvPo";i:4358;s:4:"Spoi";i:4359;s:8:"IrresTyp";i:4360;s:6:"OvRySo";i:4361;s:6:"MicrPa";i:4362;s:3:"Ral";i:4363;s:5:"PrSem";i:4364;s:8:"OuttrSol";i:4365;s:12:"DisInduSmoke";i:4366;s:7:"PneuSag";i:4367;s:4:"Pstx";i:4368;s:8:"MeOveUnr";i:4369;s:5:"Diaer";i:4370;s:8:"FrorGiUn";i:4371;s:6:"SciSea";i:4372;s:6:"ImOver";i:4373;s:7:"CuLoath";i:4374;s:13:"MatriSorTawgi";i:4375;s:5:"SulWa";i:4376;s:11:"DemiHoodxMu";i:4377;s:9:"GladGuiMu";i:4378;s:10:"MaiMazPeri";i:4379;s:9:"ReedTetra";i:4380;s:8:"MeUnUnpe";i:4381;s:11:"HistoHouTro";i:4382;s:7:"DumGoUn";i:4383;s:3:"Ses";i:4384;s:12:"ReceScincUpg";i:4385;s:9:"ChaDiNonm";i:4386;s:9:"HyraxUnsc";i:4387;s:7:"MiNephr";i:4388;s:5:"PlePr";i:4389;s:5:"OpPre";i:4390;s:8:"AwnlBitt";i:4391;s:12:"JuraMucUnrue";i:4392;s:4:"NoRu";i:4393;s:4:"Rita";i:4394;s:10:"DyStenTetr";i:4395;s:6:"MoPoUn";i:4396;s:11:"ConKokoPapi";i:4397;s:8:"HoUnUnde";i:4398;s:13:"RhiUnsweWapac";i:4399;s:10:"BecLadRema";i:4400;s:13:"JordaSeleWitn";i:4401;s:8:"TramUnwh";i:4402;s:13:"GladePanzoZym";i:4403;s:7:"EvilOli";i:4404;s:11:"LeoUnconWea";i:4405;s:5:"ThToo";i:4406;s:8:"SubvVisi";i:4407;s:11:"IloInaTrogo";i:4408;s:5:"Troch";i:4409;s:5:"TriWo";i:4410;s:10:"IsoJuvVanq";i:4411;s:7:"NaTalon";i:4412;s:11:"CauRenSemin";i:4413;s:11:"HectoMancPa";i:4414;s:5:"HySwa";i:4415;s:6:"CuprLe";i:4416;s:4:"Tain";i:4417;s:4:"Watc";i:4418;s:13:"NegriPosWollo";i:4419;s:10:"DetInterLy";i:4420;s:11:"HamiManSpoa";i:4421;s:6:"FuUran";i:4422;s:11:"EpitLegitSi";i:4423;s:4:"Resi";i:4424;s:9:"FlobIctPh";i:4425;s:13:"QopSpaniTroch";i:4426;s:12:"ReediThirUna";i:4427;s:14:"ItaliNondeRuck";i:4428;s:8:"UntUpsho";i:4429;s:11:"InquStanTri";i:4430;s:10:"PeSebiTarg";i:4431;s:5:"ReUnp";i:4432;s:9:"FlocHecRe";i:4433;s:7:"MogrSum";i:4434;s:5:"ForHi";i:4435;s:6:"NicePe";i:4436;s:4:"Unob";i:4437;s:8:"MnemSupe";i:4438;s:5:"StUnc";i:4439;s:5:"Basop";i:4440;s:4:"Rust";i:4441;s:10:"ChGonVerme";i:4442;s:6:"SigYet";i:4443;s:4:"Trap";i:4444;s:5:"Uproo";i:4445;s:7:"BrSpecu";i:4446;s:5:"DePro";i:4447;s:11:"PrPterUness";i:4448;s:7:"LipPeTi";i:4449;s:8:"RoostTru";i:4450;s:12:"EngarIndeSub";i:4451;s:9:"SipeTaint";i:4452;s:13:"HutxLibelUnem";i:4453;s:10:"DuEtheUnor";i:4454;s:9:"SmStockSw";i:4455;s:7:"TarTeYu";i:4456;s:5:"LoUnr";i:4457;s:4:"Yesx";i:4458;s:9:"SleeStone";i:4459;s:10:"FraGemmPer";i:4460;s:9:"IsocThesm";i:4461;s:10:"DerogEritr";i:4462;s:13:"ConHarasLippy";i:4463;s:5:"UnUnr";i:4464;s:6:"GastSt";i:4465;s:4:"Unsh";i:4466;s:9:"GalHypUnt";i:4467;s:2:"Fe";i:4468;s:13:"HypotIntReali";i:4469;s:4:"Uner";i:4470;s:6:"MoUnid";i:4471;s:11:"HomodMymVil";i:4472;s:9:"FibPrepUn";i:4473;s:9:"SpracWart";i:4474;s:5:"EdxSo";i:4475;s:8:"AxeOverf";i:4476;s:12:"EunucFanhoMo";i:4477;s:4:"DeFl";i:4478;s:8:"ErGnSulp";i:4479;s:9:"MoOvePeri";i:4480;s:11:"ScUnanUnpow";i:4481;s:11:"NautiTaYawn";i:4482;s:6:"QuScSu";i:4483;s:3:"Urf";i:4484;s:4:"Phyt";i:4485;s:7:"GhMyelo";i:4486;s:5:"ClEle";i:4487;s:12:"MetePyrotSne";i:4488;s:7:"MonSpoi";i:4489;s:5:"Operc";i:4490;s:5:"HaHex";i:4491;s:10:"BivFirefPh";i:4492;s:7:"TrabUpt";i:4493;s:5:"Maund";i:4494;s:10:"MalebPrere";i:4495;s:4:"Quar";i:4496;s:9:"MaStarbUn";i:4497;s:11:"PythUnVello";i:4498;s:4:"Domi";i:4499;s:8:"PoiSprew";i:4500;s:9:"MeagrUnwi";i:4501;s:8:"NapRevea";i:4502;s:5:"Unact";i:4503;s:15:"CorniJumboSubur";i:4504;s:6:"JudUnd";i:4505;s:10:"InteMisRow";i:4506;s:9:"OleanSyba";i:4507;s:10:"ElaRedlSup";i:4508;s:4:"Farm";i:4509;s:4:"Hipp";i:4510;s:9:"PangPedPh";i:4511;s:5:"RhiTi";i:4512;s:9:"HomisPaTs";i:4513;s:4:"Reas";i:4514;s:7:"AnaboDi";i:4515;s:9:"JarlxRaTh";i:4516;s:11:"GravSunsVer";i:4517;s:4:"Real";i:4518;s:7:"EfPulta";i:4519;s:9:"GunlxMaRe";i:4520;s:7:"PhaPhre";i:4521;s:5:"PlUnr";i:4522;s:5:"Skipp";i:4523;s:4:"Mura";i:4524;s:14:"SerosUnentWork";i:4525;s:3:"Ske";i:4526;s:11:"AttrFourLac";i:4527;s:8:"PreShrin";i:4528;s:7:"InsProf";i:4529;s:11:"ClSwottWebm";i:4530;s:11:"EndPhylaPle";i:4531;s:8:"MenyaThe";i:4532;s:8:"FissVaul";i:4533;s:7:"HastiOv";i:4534;s:3:"Met";i:4535;s:2:"Ta";i:4536;s:8:"HatbaPan";i:4537;s:6:"SyndTe";i:4538;s:5:"Hoppl";i:4539;s:8:"FerNecro";i:4540;s:4:"Rese";i:4541;s:8:"EnoHiUna";i:4542;s:7:"MaOverh";i:4543;s:11:"ItzNonsPecu";i:4544;s:5:"PhiTh";i:4545;s:3:"Jus";i:4546;s:9:"CoLevoTra";i:4547;s:4:"Sent";i:4548;s:7:"PeUrami";i:4549;s:7:"MuWoman";i:4550;s:4:"Ooph";i:4551;s:10:"InterRocUn";i:4552;s:7:"SaShadd";i:4553;s:14:"PaniTwiteUnirh";i:4554;s:7:"PaPhaRe";i:4555;s:4:"SaSy";i:4556;s:11:"ExarcTestTr";i:4557;s:3:"Vex";i:4558;s:3:"Yuz";i:4559;s:5:"UnWea";i:4560;s:11:"HylPanPapil";i:4561;s:9:"WineWulfe";i:4562;s:7:"PenSpar";i:4563;s:8:"WaveWove";i:4564;s:10:"DisFlStenc";i:4565;s:14:"DelegImpreMult";i:4566;s:7:"DiOpina";i:4567;s:5:"PenPr";i:4568;s:5:"Pinna";i:4569;s:14:"HieroIdylYoick";i:4570;s:6:"CougSt";i:4571;s:5:"RevSt";i:4572;s:10:"IndiMusPro";i:4573;s:4:"SkSu";i:4574;s:4:"Becl";i:4575;s:7:"IndiIns";i:4576;s:9:"NickeOuri";i:4577;s:9:"DeDiHundr";i:4578;s:9:"PerfeSeUn";i:4579;s:5:"MerTh";i:4580;s:7:"LanMann";i:4581;s:13:"FileOtheUndev";i:4582;s:9:"JupaSymme";i:4583;s:5:"Overb";i:4584;s:9:"RelocSnoo";i:4585;s:13:"MckayPetiQuee";i:4586;s:12:"SantaSophUnt";i:4587;s:10:"ConInvinSa";i:4588;s:4:"SyZo";i:4589;s:7:"DessSal";i:4590;s:5:"Septu";i:4591;s:10:"EchenOuTel";i:4592;s:8:"AsPeWith";i:4593;s:5:"Rubli";i:4594;s:11:"DonInspiMor";i:4595;s:12:"CypriDeHorto";i:4596;s:14:"ShravSoureSqua";i:4597;s:11:"IsothUncoYu";i:4598;s:5:"Softe";i:4599;s:8:"FiliTegx";i:4600;s:11:"PreRotteTan";i:4601;s:12:"FulgPitcVulc";i:4602;s:5:"Raphi";i:4603;s:4:"Silk";i:4604;s:7:"IntMilk";i:4605;s:7:"EthExor";i:4606;s:4:"InTe";i:4607;s:12:"HousePetrPre";i:4608;s:5:"Techn";i:4609;s:4:"Spir";i:4610;s:6:"MaThul";i:4611;s:4:"Phae";i:4612;s:9:"MythPipRe";i:4613;s:7:"PottlUn";i:4614;s:10:"FarOverStu";i:4615;s:12:"CranDhawxMis";i:4616;s:14:"ImprOverlTrach";i:4617;s:14:"DoohiGrimiNond";i:4618;s:7:"RedfiSe";i:4619;s:8:"EnteUpra";i:4620;s:13:"IrreKoalPicud";i:4621;s:9:"SaSenSiva";i:4622;s:4:"Quad";i:4623;s:5:"Japon";i:4624;s:9:"SeveSodSt";i:4625;s:13:"CyanhExtMicro";i:4626;s:6:"PavRig";i:4627;s:9:"NaphtProv";i:4628;s:12:"HypovIrreTet";i:4629;s:10:"GiloxLapsi";i:4630;s:5:"ToTri";i:4631;s:8:"GroggPre";i:4632;s:5:"Perco";i:4633;s:5:"Upclo";i:4634;s:8:"GenitLab";i:4635;s:6:"UnXylo";i:4636;s:5:"Devir";i:4637;s:8:"FleNassa";i:4638;s:4:"Salt";i:4639;s:13:"DeliManipUnen";i:4640;s:7:"ShinSho";i:4641;s:6:"IliaOu";i:4642;s:10:"EleImponPu";i:4643;s:9:"ProSuWild";i:4644;s:8:"AnNymRum";i:4645;s:14:"HexamInteUnrou";i:4646;s:11:"CopiHealTem";i:4647;s:14:"PieceSometUnre";i:4648;s:5:"HadRa";i:4649;s:10:"CamacJohan";i:4650;s:9:"OverZeugo";i:4651;s:3:"Sha";i:4652;s:6:"PrReZo";i:4653;s:10:"CoeffPhoUn";i:4654;s:9:"MycoObjec";i:4655;s:4:"MiTa";i:4656;s:9:"BiQuadrSe";i:4657;s:13:"OverPhantSanc";i:4658;s:4:"Poni";i:4659;s:7:"MyeTerr";i:4660;s:4:"Inri";i:4661;s:5:"SemZy";i:4662;s:8:"AuFruKer";i:4663;s:8:"GrPeVasu";i:4664;s:8:"NooQuant";i:4665;s:10:"PlodPtarTa";i:4666;s:8:"SubWistx";i:4667;s:14:"CoccSuperTundx";i:4668;s:5:"Spass";i:4669;s:8:"PuzzSapl";i:4670;s:4:"Snon";i:4671;s:8:"InstPega";i:4672;s:13:"HomoOrthRadia";i:4673;s:5:"Phyll";i:4674;s:8:"ChondRev";i:4675;s:9:"MoroPessi";i:4676;s:5:"Fores";i:4677;s:11:"NeOligPtole";i:4678;s:4:"Serf";i:4679;s:10:"InSymUnacu";i:4680;s:8:"BigemMis";i:4681;s:8:"OvicRaSc";i:4682;s:9:"IsmxUnder";i:4683;s:13:"HaeShoweTesta";i:4684;s:4:"Gram";i:4685;s:7:"TipsUnc";i:4686;s:6:"EuPerm";i:4687;s:9:"EyLoPhone";i:4688;s:7:"RepScat";i:4689;s:4:"MiSu";i:4690;s:11:"ChaucRoriUn";i:4691;s:5:"Slowh";i:4692;s:3:"Osp";i:4693;s:5:"Windo";i:4694;s:3:"Izt";i:4695;s:4:"Hoga";i:4696;s:7:"DrUndis";i:4697;s:9:"NucPyraTh";i:4698;s:5:"Unrum";i:4699;s:10:"BotchGrGue";i:4700;s:9:"LaPreaSem";i:4701;s:7:"OdaxSho";i:4702;s:4:"Outb";i:4703;s:9:"CreeUniun";i:4704;s:12:"JeaniMandSun";i:4705;s:6:"DeDigr";i:4706;s:10:"RotSciurSe";i:4707;s:7:"ConfiUn";i:4708;s:4:"Regi";i:4709;s:11:"DoSterThimb";i:4710;s:4:"Vamp";i:4711;s:3:"Hae";i:4712;s:6:"HyRete";i:4713;s:2:"Up";i:4714;s:9:"FumarRece";i:4715;s:3:"Loc";i:4716;s:5:"Unatt";i:4717;s:7:"OstrVea";i:4718;s:11:"InNettlPres";i:4719;s:7:"BuMilks";i:4720;s:4:"Bone";i:4721;s:10:"MyopPorpSy";i:4722;s:12:"EmmenMeUndis";i:4723;s:9:"DaggeDiGi";i:4724;s:7:"UnVarie";i:4725;s:5:"Perfe";i:4726;s:9:"AzoDaffTa";i:4727;s:10:"IneffWheal";i:4728;s:10:"DeconPinSe";i:4729;s:10:"TeUnmVolut";i:4730;s:10:"SaproTrach";i:4731;s:10:"PlaRhiUrba";i:4732;s:9:"StropSubv";i:4733;s:7:"IcNatur";i:4734;s:8:"FluxaRei";i:4735;s:6:"ChEcTo";i:4736;s:12:"QuadrUnovZoo";i:4737;s:12:"NecroThakuWa";i:4738;s:5:"SeSer";i:4739;s:2:"Go";i:4740;s:12:"FrogxPredSar";i:4741;s:12:"CaFrumpTortr";i:4742;s:7:"LePrakr";i:4743;s:6:"KurtWa";i:4744;s:13:"NymphOthVowma";i:4745;s:14:"InduMurgeTrabe";i:4746;s:10:"CytogUrodi";i:4747;s:12:"CarMarkwPrep";i:4748;s:9:"DyPalatRi";i:4749;s:15:"LazarMimusRemov";i:4750;s:9:"OfPterRef";i:4751;s:10:"PapawTherm";i:4752;s:9:"MoProTrea";i:4753;s:9:"PushfScut";i:4754;s:6:"NonSub";i:4755;s:9:"OcSubWido";i:4756;s:8:"UnchaUng";i:4757;s:9:"IcIntePer";i:4758;s:12:"PurgaReiRepl";i:4759;s:13:"CryLoxiaPutri";i:4760;s:12:"HomolMeaslTa";i:4761;s:4:"Pent";i:4762;s:7:"MisMoRu";i:4763;s:9:"ColorCyMa";i:4764;s:7:"HaemoTr";i:4765;s:5:"ParTa";i:4766;s:8:"MesOrxUn";i:4767;s:5:"Kavix";i:4768;s:3:"Pha";i:4769;s:13:"CaliCatsPenba";i:4770;s:10:"OverRaUnsm";i:4771;s:5:"Psych";i:4772;s:9:"SphaeUnfa";i:4773;s:4:"Wyli";i:4774;s:6:"SuSwan";i:4775;s:10:"DichoElkho";i:4776;s:6:"OsteSt";i:4777;s:5:"Overa";i:4778;s:10:"ReprTeUnpa";i:4779;s:9:"PaTonnZei";i:4780;s:4:"Ostr";i:4781;s:5:"Octob";i:4782;s:3:"Exo";i:4783;s:3:"Res";i:4784;s:3:"But";i:4785;s:8:"RhoTreVi";i:4786;s:6:"PrReda";i:4787;s:4:"IlUn";i:4788;s:7:"MeMonNo";i:4789;s:12:"FluMyrmePaho";i:4790;s:6:"ImprRe";i:4791;s:8:"EpichGla";i:4792;s:12:"RepaRolliUnr";i:4793;s:8:"MininOve";i:4794;s:7:"ProsaUn";i:4795;s:12:"HypNonapYoke";i:4796;s:7:"LalopSc";i:4797;s:8:"HoUneVis";i:4798;s:12:"DorKenmMicro";i:4799;s:15:"ProclResubZoops";i:4800;s:15:"SemirSerraSuper";i:4801;s:7:"SaceUna";i:4802;s:5:"Monoa";i:4803;s:5:"NoUns";i:4804;s:9:"DiEmTauto";i:4805;s:10:"BoehmSupUn";i:4806;s:10:"SuperUnres";i:4807;s:14:"BattDemonVitam";i:4808;s:9:"FilInxRap";i:4809;s:10:"PremiStStr";i:4810;s:10:"DesyGooPer";i:4811;s:4:"CoSo";i:4812;s:8:"HypeTric";i:4813;s:4:"Writ";i:4814;s:8:"PolSangu";i:4815;s:7:"PreUnig";i:4816;s:10:"RubServuTo";i:4817;s:5:"Sider";i:4818;s:9:"UnhauUnpe";i:4819;s:9:"HerniVine";i:4820;s:5:"MaMud";i:4821;s:5:"Nonan";i:4822;s:9:"CurraEnte";i:4823;s:8:"RaTenuTe";i:4824;s:2:"Ba";i:4825;s:5:"CuNeu";i:4826;s:8:"SartShat";i:4827;s:5:"Polyp";i:4828;s:7:"UnreUnv";i:4829;s:12:"InteKarakRhy";i:4830;s:3:"Ven";i:4831;s:3:"Tan";i:4832;s:8:"MeNecrNo";i:4833;s:12:"DioResoUnpre";i:4834;s:4:"Fell";i:4835;s:9:"ExGrafMai";i:4836;s:14:"SanifSuffrUnpr";i:4837;s:12:"GrimmKerUnma";i:4838;s:5:"Subsc";i:4839;s:6:"GilLaw";i:4840;s:10:"PerinPsilo";i:4841;s:8:"RenilRiv";i:4842;s:12:"FuThymoXenop";i:4843;s:7:"CassMen";i:4844;s:6:"LuMoro";i:4845;s:10:"HobPlasmSk";i:4846;s:5:"Rehum";i:4847;s:4:"Piaz";i:4848;s:5:"Defen";i:4849;s:4:"Prea";i:4850;s:9:"RedSaccSu";i:4851;s:9:"StrouUrox";i:4852;s:9:"MakObelSe";i:4853;s:5:"ReoTa";i:4854;s:7:"MiNonvi";i:4855;s:7:"AutZieg";i:4856;s:5:"Oscil";i:4857;s:9:"SuSuperUn";i:4858;s:4:"Slee";i:4859;s:5:"CaaJa";i:4860;s:8:"OvePosit";i:4861;s:7:"LianWel";i:4862;s:11:"FibOvoelTub";i:4863;s:4:"Crou";i:4864;s:5:"Wheed";i:4865;s:5:"Heter";i:4866;s:5:"MaPol";i:4867;s:8:"HundMaUr";i:4868;s:5:"ChGyr";i:4869;s:7:"CrFrPhr";i:4870;s:7:"KiPrima";i:4871;s:13:"DemibSinUnint";i:4872;s:5:"PuTyp";i:4873;s:8:"HooPhysi";i:4874;s:7:"CubitSt";i:4875;s:12:"PaletProWhos";i:4876;s:7:"RaTorme";i:4877;s:15:"LeiotSnortWally";i:4878;s:10:"LymPlaniSc";i:4879;s:5:"SuUni";i:4880;s:10:"BefrGlobRh";i:4881;s:11:"LibeNowaTro";i:4882;s:8:"ArEnerRh";i:4883;s:4:"Scar";i:4884;s:10:"CorpLumSha";i:4885;s:14:"HereaTraiTruan";i:4886;s:5:"Syrin";i:4887;s:6:"ChaInn";i:4888;s:3:"Use";i:4889;s:12:"EkahLusiUnfo";i:4890;s:10:"HelpiPhala";i:4891;s:10:"InterUnpro";i:4892;s:5:"Unred";i:4893;s:9:"ConfPePle";i:4894;s:9:"HoPreReam";i:4895;s:11:"PotTedTimer";i:4896;s:5:"Shiel";i:4897;s:10:"GoOverResc";i:4898;s:6:"NaTyle";i:4899;s:10:"FibGommeLa";i:4900;s:7:"PremoSt";i:4901;s:9:"HydroTale";i:4902;s:7:"PerThig";i:4903;s:8:"AcoelHal";i:4904;s:7:"BocNonr";i:4905;s:8:"PsySnaTe";i:4906;s:5:"Stint";i:4907;s:9:"SectSeeUn";i:4908;s:4:"FlMu";i:4909;s:4:"Reve";i:4910;s:7:"QuestUn";i:4911;s:11:"LibUnsVictu";i:4912;s:8:"SellaTra";i:4913;s:10:"CoMultParu";i:4914;s:3:"See";i:4915;s:7:"MaReTea";i:4916;s:10:"DucTowaTri";i:4917;s:7:"KeeProb";i:4918;s:10:"CitywPenta";i:4919;s:5:"Sprad";i:4920;s:5:"Fugit";i:4921;s:5:"TrVas";i:4922;s:12:"FoozlKlysSta";i:4923;s:10:"HomaMisTur";i:4924;s:8:"MensuVar";i:4925;s:13:"ForLinteNidic";i:4926;s:10:"SteToUpspl";i:4927;s:11:"JoshSuVisco";i:4928;s:5:"Semis";i:4929;s:7:"NovoxTr";i:4930;s:9:"GushxSchi";i:4931;s:10:"CitrPhthSu";i:4932;s:6:"ProUnc";i:4933;s:5:"PaVol";i:4934;s:3:"Lam";i:4935;s:7:"MatPlSo";i:4936;s:2:"Ru";i:4937;s:9:"PerRereTh";i:4938;s:11:"DokPetrSube";i:4939;s:6:"ProtSt";i:4940;s:15:"EidetParroScyph";i:4941;s:9:"OverRechi";i:4942;s:5:"Elsew";i:4943;s:12:"HomoSpecZizz";i:4944;s:5:"PrUnw";i:4945;s:8:"RattlRig";i:4946;s:5:"Helic";i:4947;s:9:"MicrSimSq";i:4948;s:8:"PreUnjil";i:4949;s:7:"InfrRep";i:4950;s:10:"GuasPalRub";i:4951;s:8:"OrgSubWa";i:4952;s:4:"Sept";i:4953;s:8:"BrEyeTri";i:4954;s:7:"NagOver";i:4955;s:12:"ButNonadSill";i:4956;s:11:"LavStympVea";i:4957;s:11:"DispoExRaph";i:4958;s:11:"FlexuMaleSa";i:4959;s:10:"IncraVihar";i:4960;s:6:"ScSkew";i:4961;s:4:"Irre";i:4962;s:8:"PeRecUna";i:4963;s:11:"CoPulviSchi";i:4964;s:10:"InconLyasx";i:4965;s:10:"MonodSparg";i:4966;s:6:"KickPi";i:4967;s:11:"TrabUnrWary";i:4968;s:14:"MuleResorUnsna";i:4969;s:13:"MetapNiggSupe";i:4970;s:9:"GraSterUv";i:4971;s:7:"LasquQu";i:4972;s:6:"UnaUnp";i:4973;s:10:"BipheConWr";i:4974;s:6:"CirUni";i:4975;s:3:"Pon";i:4976;s:5:"ExtRe";i:4977;s:5:"Inact";i:4978;s:7:"TrVendi";i:4979;s:5:"Santa";i:4980;s:9:"OsPhlPoro";i:4981;s:14:"HolidHydroTome";i:4982;s:4:"Incr";i:4983;s:3:"Hyp";i:4984;s:10:"CoOverrSug";i:4985;s:4:"Soap";i:4986;s:4:"Sole";i:4987;s:11:"PompProteSq";i:4988;s:6:"OutySu";i:4989;s:8:"FraFrPha";i:4990;s:7:"ParaPat";i:4991;s:12:"MarcOrobScot";i:4992;s:9:"PhysiTuto";i:4993;s:7:"AzygCit";i:4994;s:11:"UnharUnpUns";i:4995;s:3:"Neg";i:4996;s:6:"PrSupe";i:4997;s:9:"RosehUnso";i:4998;s:8:"NonRodom";i:4999;s:9:"IsothOpto";i:5000;s:8:"ForeFrac";i:5001;s:11:"ShoneTeZymo";i:5002;s:10:"BrKoloUnif";i:5003;s:12:"ImprPremiSup";i:5004;s:9:"EpeisGiIn";i:5005;s:8:"SnatcTes";i:5006;s:7:"GerTrav";i:5007;s:11:"ElSubsuUnsu";i:5008;s:4:"ApPr";i:5009;s:5:"Seapo";i:5010;s:7:"PoTetra";i:5011;s:11:"DeceFraOver";i:5012;s:4:"NoPa";i:5013;s:10:"BaraBiMoul";i:5014;s:8:"SchooZam";i:5015;s:3:"Omp";i:5016;s:12:"NuttiStVotar";i:5017;s:9:"DiSoarSto";i:5018;s:3:"Ges";i:5019;s:12:"OeninSubTher";i:5020;s:5:"DumJi";i:5021;s:9:"ClareDias";i:5022;s:9:"OutglPuUn";i:5023;s:5:"Salel";i:5024;s:10:"HyperOePro";i:5025;s:6:"BegNon";i:5026;s:6:"DemeLa";i:5027;s:11:"AuletCoNonr";i:5028;s:10:"JaNonRegul";i:5029;s:3:"Coc";i:5030;s:3:"Fun";i:5031;s:3:"Sti";i:5032;s:9:"MaineMile";i:5033;s:10:"CahokLikab";i:5034;s:6:"SponSt";i:5035;s:3:"Tai";i:5036;s:11:"MicrOverRef";i:5037;s:9:"SlumSphin";i:5038;s:8:"RivalSho";i:5039;s:13:"ContPredeTurb";i:5040;s:7:"TopUndi";i:5041;s:7:"PluStar";i:5042;s:2:"Gy";i:5043;s:13:"PantPiccSemid";i:5044;s:11:"NoTetraTumm";i:5045;s:7:"DaTheot";i:5046;s:13:"PiaTheorUnawa";i:5047;s:5:"Zilla";i:5048;s:12:"LikenProWhat";i:5049;s:5:"Demod";i:5050;s:2:"Ul";i:5051;s:5:"Sexlo";i:5052;s:4:"Unab";i:5053;s:5:"Tobac";i:5054;s:7:"UnVenti";i:5055;s:14:"HugeoSchuSucci";i:5056;s:4:"Octa";i:5057;s:12:"EcoFritxUncu";i:5058;s:7:"MadhvVa";i:5059;s:10:"UnbewUnive";i:5060;s:8:"PsTreUni";i:5061;s:5:"Overc";i:5062;s:7:"DarryLe";i:5063;s:8:"LateRuin";i:5064;s:8:"ParThYea";i:5065;s:5:"Reapp";i:5066;s:12:"NarProoeRhin";i:5067;s:10:"ThiUnconUn";i:5068;s:7:"OrtUnpe";i:5069;s:5:"Reliq";i:5070;s:4:"Uret";i:5071;s:6:"PsyTer";i:5072;s:3:"Gal";i:5073;s:5:"Rando";i:5074;s:6:"UnnWis";i:5075;s:12:"IndInteOutth";i:5076;s:3:"Fla";i:5077;s:7:"AntiSem";i:5078;s:7:"ConMeRh";i:5079;s:6:"SupTaf";i:5080;s:9:"MaPosScen";i:5081;s:3:"Kae";i:5082;s:8:"EquStrTi";i:5083;s:8:"GawmxOve";i:5084;s:10:"GinglUncap";i:5085;s:14:"DemodHakxSongb";i:5086;s:6:"IracSe";i:5087;s:4:"Squa";i:5088;s:7:"MicrUnp";i:5089;s:6:"LoSzla";i:5090;s:11:"JurorUnhaVe";i:5091;s:8:"ThioaZen";i:5092;s:12:"HerbaHeteIso";i:5093;s:8:"RhizUnve";i:5094;s:3:"Twe";i:5095;s:13:"InnSpraySunbe";i:5096;s:9:"KoLoRecop";i:5097;s:7:"LegaSta";i:5098;s:5:"Uncou";i:5099;s:9:"SemiSkiTh";i:5100;s:6:"BecoUn";i:5101;s:2:"Av";i:5102;s:8:"AssyCoRe";i:5103;s:7:"PrWauch";i:5104;s:8:"RhapStyl";i:5105;s:5:"Parap";i:5106;s:5:"Thrif";i:5107;s:10:"InteLophVi";i:5108;s:11:"GradIntLeas";i:5109;s:8:"AurRandy";i:5110;s:7:"HyOutdw";i:5111;s:2:"Sw";i:5112;s:4:"Pede";i:5113;s:12:"HomoeIllYame";i:5114;s:12:"NoninSclStro";i:5115;s:5:"Nonco";i:5116;s:7:"GeSpeck";i:5117;s:6:"StoUnp";i:5118;s:5:"ExpSk";i:5119;s:10:"DiEthnoPro";i:5120;s:11:"RhynUnconUn";i:5121;s:3:"Tha";i:5122;s:4:"Unst";i:5123;s:10:"MicroUncit";i:5124;s:7:"IrrTymp";i:5125;s:10:"SemUnimaWa";i:5126;s:10:"OverPontSn";i:5127;s:10:"AreLaugLau";i:5128;s:7:"DesTran";i:5129;s:7:"IrMinue";i:5130;s:11:"NoRenteVexx";i:5131;s:8:"GousPySc";i:5132;s:7:"NeOxylx";i:5133;s:5:"Unpen";i:5134;s:10:"KatexUnpat";i:5135;s:5:"PoTra";i:5136;s:4:"Nonf";i:5137;s:5:"Expos";i:5138;s:4:"Repe";i:5139;s:11:"DePisoUnide";i:5140;s:5:"EquZa";i:5141;s:8:"UnUnUnwi";i:5142;s:11:"OffcParaXyl";i:5143;s:10:"RaSemiSurv";i:5144;s:9:"PhacoVesi";i:5145;s:3:"Inv";i:5146;s:14:"EpitrOsteXanth";i:5147;s:5:"Wellb";i:5148;s:4:"CaSu";i:5149;s:11:"SincTrogUna";i:5150;s:7:"InParam";i:5151;s:13:"GabioOverbUnc";i:5152;s:6:"PlowRo";i:5153;s:13:"InsecJeremTel";i:5154;s:3:"Rat";i:5155;s:7:"IncPrYa";i:5156;s:8:"JarTwist";i:5157;s:6:"ElaPre";i:5158;s:9:"MiRopTurk";i:5159;s:11:"PrPremoSept";i:5160;s:13:"EssOverfWycli";i:5161;s:7:"TragiUn";i:5162;s:9:"InOuRatch";i:5163;s:5:"Bolar";i:5164;s:2:"Br";i:5165;s:12:"QuadrRentVol";i:5166;s:11:"OuphUnsleWh";i:5167;s:11:"GalvRachiSt";i:5168;s:12:"DeliFazPutri";i:5169;s:9:"HiHomeRet";i:5170;s:8:"SloSuper";i:5171;s:4:"Mime";i:5172;s:4:"Turb";i:5173;s:10:"MagiOrUnsu";i:5174;s:11:"BlaOrthOutg";i:5175;s:4:"Inal";i:5176;s:8:"SkeVolva";i:5177;s:12:"HexatMunicOn";i:5178;s:8:"OmnQuUnc";i:5179;s:3:"Qui";i:5180;s:8:"BiFacMan";i:5181;s:5:"Uncle";i:5182;s:10:"PalaSpUnco";i:5183;s:5:"Sabba";i:5184;s:5:"NonSe";i:5185;s:14:"JuggeTetryUnbo";i:5186;s:10:"KerniPalRe";i:5187;s:4:"Unma";i:5188;s:4:"Unmu";i:5189;s:5:"Incom";i:5190;s:7:"PinkSca";i:5191;s:11:"NaSletSperm";i:5192;s:3:"Mil";i:5193;s:10:"AnthrBuOve";i:5194;s:11:"NemaPerUnre";i:5195;s:10:"RevisSanto";i:5196;s:7:"GaGunRa";i:5197;s:13:"PhotoPreUnrec";i:5198;s:6:"ReZoop";i:5199;s:12:"DermaMaThorn";i:5200;s:4:"Scor";i:5201;s:3:"Tet";i:5202;s:10:"MesocSeric";i:5203;s:12:"OmenSomnaSub";i:5204;s:10:"FiordJochx";i:5205;s:9:"DissGaQua";i:5206;s:7:"TeTorme";i:5207;s:7:"LarrUnf";i:5208;s:6:"DiesTa";i:5209;s:7:"NonfStr";i:5210;s:7:"DisEpil";i:5211;s:5:"Smoke";i:5212;s:9:"JimSaShut";i:5213;s:11:"BandOxamPsy";i:5214;s:3:"Pul";i:5215;s:4:"Hype";i:5216;s:4:"Scon";i:5217;s:11:"FourpMilUnd";i:5218;s:12:"ChuThundTopo";i:5219;s:9:"ExMuRelev";i:5220;s:4:"Resp";i:5221;s:8:"CorMilks";i:5222;s:11:"HalfInPyrul";i:5223;s:12:"PhotoProsaUn";i:5224;s:4:"Maie";i:5225;s:7:"MaOchUb";i:5226;s:5:"SpWar";i:5227;s:8:"CalPhaTe";i:5228;s:7:"HePrYam";i:5229;s:4:"Yama";i:5230;s:4:"Trib";i:5231;s:6:"InPoUn";i:5232;s:5:"Sciss";i:5233;s:3:"Teg";i:5234;s:12:"CinctJesModi";i:5235;s:14:"FoghToxigWolfh";i:5236;s:10:"LowSuWould";i:5237;s:7:"SpittTe";i:5238;s:8:"KathUnde";i:5239;s:11:"EnanReRight";i:5240;s:4:"Unpe";i:5241;s:8:"ToppVaci";i:5242;s:11:"LymphSwirWh";i:5243;s:9:"MarkPseud";i:5244;s:10:"ElegaTlasc";i:5245;s:10:"NonmTheoWa";i:5246;s:8:"MenSynar";i:5247;s:3:"Pil";i:5248;s:5:"Thala";i:5249;s:11:"OlePreUnado";i:5250;s:2:"Ni";i:5251;s:4:"Unge";i:5252;s:8:"ProSolTo";i:5253;s:4:"Rece";i:5254;s:8:"IsoLitho";i:5255;s:3:"Col";i:5256;s:5:"Intra";i:5257;s:5:"RoUnl";i:5258;s:7:"HeronIn";i:5259;s:4:"Undu";i:5260;s:7:"LimSaVo";i:5261;s:10:"CenPentSto";i:5262;s:8:"IsraTher";i:5263;s:9:"DeperUpge";i:5264;s:9:"DispLeWoo";i:5265;s:4:"Puru";i:5266;s:3:"Rig";i:5267;s:9:"CoGolPred";i:5268;s:3:"Lyr";i:5269;s:9:"PoRediUnl";i:5270;s:12:"SnotTibiaUnt";i:5271;s:5:"Mirac";i:5272;s:4:"HyHy";i:5273;s:5:"Senti";i:5274;s:6:"RadeSe";i:5275;s:12:"LattSpheUnne";i:5276;s:5:"Sperm";i:5277;s:7:"PrStabl";i:5278;s:9:"KieyUnpam";i:5279;s:9:"NeThiocVi";i:5280;s:6:"MicrRe";i:5281;s:4:"Hint";i:5282;s:6:"OxycVi";i:5283;s:12:"TrimUndeUnex";i:5284;s:7:"EncTcUr";i:5285;s:4:"Tomb";i:5286;s:12:"RoritSxTartw";i:5287;s:9:"GametPret";i:5288;s:8:"CoNodPri";i:5289;s:13:"PhosPogrPseud";i:5290;s:6:"PalTab";i:5291;s:7:"DiscGla";i:5292;s:8:"GrHibVen";i:5293;s:14:"LeaveMassUncor";i:5294;s:9:"ReSmiUnin";i:5295;s:5:"Sacch";i:5296;s:7:"MisceSt";i:5297;s:14:"PrissRepoTauch";i:5298;s:9:"CornPeScr";i:5299;s:8:"HepSuper";i:5300;s:11:"HiroSeriUnd";i:5301;s:12:"SuTripaUnpin";i:5302;s:10:"IrPuerViti";i:5303;s:7:"SubVint";i:5304;s:12:"ScenaThWinna";i:5305;s:8:"FlorSupr";i:5306;s:8:"ProUndes";i:5307;s:12:"TranVagiVier";i:5308;s:7:"KerriQu";i:5309;s:12:"PrunQuinSter";i:5310;s:8:"HoUnWeit";i:5311;s:6:"UnUnte";i:5312;s:9:"OthinUnWe";i:5313;s:9:"PlanRouUn";i:5314;s:3:"Sex";i:5315;s:7:"KangPia";i:5316;s:5:"Uncau";i:5317;s:5:"GeJox";i:5318;s:7:"GamalPs";i:5319;s:4:"Mono";i:5320;s:7:"PaleoPa";i:5321;s:6:"KeysMa";i:5322;s:6:"SeUngo";i:5323;s:5:"StWre";i:5324;s:12:"CamelDatiFre";i:5325;s:5:"ParUn";i:5326;s:5:"Phoby";i:5327;s:12:"BrocNotorUnt";i:5328;s:5:"Repan";i:5329;s:7:"EczWeed";i:5330;s:9:"FatKornOv";i:5331;s:8:"PaReSyno";i:5332;s:10:"LeoraPolyc";i:5333;s:8:"PolSalmo";i:5334;s:11:"LeaLillSpea";i:5335;s:7:"PasPrim";i:5336;s:10:"GyPalTachy";i:5337;s:6:"ProTra";i:5338;s:10:"OdontReamy";i:5339;s:9:"PasixZoog";i:5340;s:5:"SleTo";i:5341;s:10:"IcedxPaSom";i:5342;s:3:"Nep";i:5343;s:7:"OofyShe";i:5344;s:3:"Bic";i:5345;s:11:"ExHoovRodne";i:5346;s:5:"IsQui";i:5347;s:10:"MolSkiesUn";i:5348;s:4:"Trop";i:5349;s:8:"EnterTch";i:5350;s:5:"OveSu";i:5351;s:5:"Uncre";i:5352;s:9:"InquPhren";i:5353;s:10:"StaSwUnrun";i:5354;s:9:"MoNontaSt";i:5355;s:12:"LaryUninUnme";i:5356;s:5:"Spiro";i:5357;s:8:"PhylTett";i:5358;s:4:"SaSe";i:5359;s:11:"MultiPerPsy";i:5360;s:8:"DaRemSub";i:5361;s:4:"Trip";i:5362;s:11:"HemLummoUna";i:5363;s:13:"DepigGinIntre";i:5364;s:5:"MiOog";i:5365;s:3:"Mic";i:5366;s:6:"ConExt";i:5367;s:10:"SaSturdSur";i:5368;s:8:"KinsmUle";i:5369;s:9:"ItenMilde";i:5370;s:5:"Urome";i:5371;s:10:"SolUnexVar";i:5372;s:9:"SulphTask";i:5373;s:9:"CasMetaVa";i:5374;s:10:"KyuxTriUnm";i:5375;s:13:"MyocePectuPor";i:5376;s:14:"LardiMiscuToda";i:5377;s:7:"UnfXant";i:5378;s:12:"PhaRackUnind";i:5379;s:3:"Mes";i:5380;s:8:"HepatMed";i:5381;s:8:"EarSomet";i:5382;s:6:"ExemUn";i:5383;s:8:"KeelPuQu";i:5384;s:5:"Raung";i:5385;s:13:"HyogQuadrSili";i:5386;s:8:"AmpLiPat";i:5387;s:8:"ImpTradi";i:5388;s:8:"SergeSph";i:5389;s:8:"CirUnblo";i:5390;s:9:"EvMortZin";i:5391;s:10:"MammaThioz";i:5392;s:5:"PeiPr";i:5393;s:13:"MicrNotoRadic";i:5394;s:10:"PhtPicumPs";i:5395;s:11:"GeocNonbOst";i:5396;s:8:"AvaJagVy";i:5397;s:5:"MaVen";i:5398;s:8:"ParaUnbl";i:5399;s:7:"OdonRen";i:5400;s:5:"Locus";i:5401;s:12:"DecoOrthRach";i:5402;s:9:"BlFolSyna";i:5403;s:6:"TeUnde";i:5404;s:8:"MouUntVe";i:5405;s:8:"CedarIso";i:5406;s:9:"StonTenct";i:5407;s:11:"ReinsRepSil";i:5408;s:8:"QuaSneak";i:5409;s:8:"CakeCont";i:5410;s:5:"Eucon";i:5411;s:4:"Tele";i:5412;s:4:"Nonc";i:5413;s:8:"AeMapSub";i:5414;s:10:"GigmaRebri";i:5415;s:4:"Wany";i:5416;s:5:"UnpUn";i:5417;s:5:"PorRe";i:5418;s:13:"HomotMoonPost";i:5419;s:14:"CommDecorImmed";i:5420;s:4:"LiLu";i:5421;s:11:"ConcHibZygo";i:5422;s:12:"InteSubeUnch";i:5423;s:5:"Bulim";i:5424;s:9:"RecoUnhoi";i:5425;s:11:"ProteSaloVe";i:5426;s:10:"MaMegPrair";i:5427;s:12:"OvereSeralTo";i:5428;s:4:"Saca";i:5429;s:11:"ChipLactaSl";i:5430;s:3:"Nos";i:5431;s:11:"SickSponWhi";i:5432;s:7:"MouVisi";i:5433;s:8:"OvQxUnsh";i:5434;s:12:"GlabeLePromy";i:5435;s:4:"SpUs";i:5436;s:5:"Uncan";i:5437;s:10:"SculpSuTru";i:5438;s:9:"OuStronWa";i:5439;s:9:"ChioMaste";i:5440;s:11:"DownlPrUnfe";i:5441;s:5:"Tinca";i:5442;s:10:"HidPrRoare";i:5443;s:6:"ChrEri";i:5444;s:9:"OlfaSalpi";i:5445;s:6:"PleoPr";i:5446;s:10:"UnmenWalle";i:5447;s:4:"Osop";i:5448;s:10:"SpaSubtUnt";i:5449;s:7:"EmitRou";i:5450;s:8:"GriPatSa";i:5451;s:5:"FoPhe";i:5452;s:12:"RectoSteUnre";i:5453;s:7:"NoUnequ";i:5454;s:7:"EntitRa";i:5455;s:4:"GaMa";i:5456;s:7:"PrStere";i:5457;s:14:"PalisRevieSeac";i:5458;s:10:"KeratScUnc";i:5459;s:5:"Gorra";i:5460;s:4:"Oste";i:5461;s:5:"Snipx";i:5462;s:5:"SwaTe";i:5463;s:13:"SikeUnutVolit";i:5464;s:8:"SeptTaff";i:5465;s:8:"MartPrRu";i:5466;s:12:"CeChopsRever";i:5467;s:12:"InProveVagin";i:5468;s:5:"InTri";i:5469;s:13:"OligaPalVesse";i:5470;s:6:"PhysPr";i:5471;s:14:"GlycoNeurVulva";i:5472;s:7:"BeDrScl";i:5473;s:7:"BraUnsa";i:5474;s:3:"Ory";i:5475;s:5:"Phary";i:5476;s:3:"Ini";i:5477;s:10:"MaResuWitt";i:5478;s:8:"DeMaddMa";i:5479;s:12:"HereSteZygom";i:5480;s:9:"FluHashUn";i:5481;s:5:"ChIni";i:5482;s:5:"SoUng";i:5483;s:6:"MaSpir";i:5484;s:10:"GawnxRaUns";i:5485;s:7:"ScToxic";i:5486;s:6:"LabPla";i:5487;s:4:"RoVo";i:5488;s:12:"CircuDicProt";i:5489;s:14:"MariPhiloThank";i:5490;s:3:"Wid";i:5491;s:12:"PlatTheokTou";i:5492;s:6:"ScTiVi";i:5493;s:5:"Clubm";i:5494;s:9:"PondUnvul";i:5495;s:5:"Salte";i:5496;s:11:"RotTubicUnh";i:5497;s:10:"TheoTitlUn";i:5498;s:8:"NatifRen";i:5499;s:4:"Zygn";i:5500;s:3:"Eut";i:5501;s:4:"Gene";i:5502;s:12:"ProReactSemi";i:5503;s:11:"CartPoPyrex";i:5504;s:5:"Shock";i:5505;s:4:"Zygo";i:5506;s:5:"MiTen";i:5507;s:7:"PhrTorr";i:5508;s:8:"GruSeTau";i:5509;s:7:"SmaSoUr";i:5510;s:12:"HoweMegOverw";i:5511;s:7:"NumPrUn";i:5512;s:7:"CyQuebr";i:5513;s:8:"IzzarSup";i:5514;s:8:"CoDihDor";i:5515;s:9:"MowRosSee";i:5516;s:14:"PerseSabeSalpi";i:5517;s:4:"Hear";i:5518;s:7:"StrTrep";i:5519;s:10:"NumerUnrWe";i:5520;s:8:"SleiUnch";i:5521;s:4:"NoPr";i:5522;s:4:"Buzz";i:5523;s:5:"ChMae";i:5524;s:5:"DiMer";i:5525;s:7:"RebetVe";i:5526;s:8:"RevTonsi";i:5527;s:11:"PilSquirSui";i:5528;s:8:"PaVoluYo";i:5529;s:10:"CalaCalcCr";i:5530;s:12:"HoverImpPerf";i:5531;s:7:"EmbHeZa";i:5532;s:5:"Paran";i:5533;s:8:"ElianWat";i:5534;s:13:"LumbMugfuUnsu";i:5535;s:9:"CitOvUnso";i:5536;s:11:"PreSunZerma";i:5537;s:4:"Plan";i:5538;s:10:"LaugMoPopu";i:5539;s:8:"PerSuppl";i:5540;s:11:"CapMeroThre";i:5541;s:7:"DerUndi";i:5542;s:9:"MuddiPuUn";i:5543;s:6:"PoSnai";i:5544;s:4:"Trun";i:5545;s:13:"ConfiExhauSou";i:5546;s:5:"Idiom";i:5547;s:5:"ChSpa";i:5548;s:12:"PheRectTousc";i:5549;s:11:"HemJokSelen";i:5550;s:10:"MilliPeatm";i:5551;s:8:"InacPseu";i:5552;s:7:"VegeXen";i:5553;s:6:"BarGre";i:5554;s:5:"InWhi";i:5555;s:9:"CounReove";i:5556;s:13:"PalmePlaSwang";i:5557;s:9:"DiPreiRee";i:5558;s:10:"CappaHyper";i:5559;s:9:"PatsTymUn";i:5560;s:4:"PrUn";i:5561;s:8:"BongHeat";i:5562;s:4:"Derm";i:5563;s:7:"HomoeSt";i:5564;s:13:"SignaSpeSquif";i:5565;s:9:"OlofPlaco";i:5566;s:7:"CaStoUn";i:5567;s:7:"GlMossi";i:5568;s:7:"TrUnclo";i:5569;s:14:"GlycPolycTapet";i:5570;s:15:"OchroParasVilit";i:5571;s:6:"BairDi";i:5572;s:9:"AssorJuSp";i:5573;s:10:"LooPhPluto";i:5574;s:8:"CorCroni";i:5575;s:7:"LaTerml";i:5576;s:11:"SponsTraTub";i:5577;s:6:"ConOst";i:5578;s:8:"SambuUnl";i:5579;s:8:"HormOrga";i:5580;s:7:"CytRecl";i:5581;s:6:"PySupe";i:5582;s:3:"Foo";i:5583;s:9:"GeoMatPho";i:5584;s:7:"CattPar";i:5585;s:11:"CacheHomUng";i:5586;s:13:"RefTrucuUnrip";i:5587;s:5:"HusWx";i:5588;s:13:"DumbSollyUnla";i:5589;s:7:"CoIroni";i:5590;s:11:"FiberLeniTc";i:5591;s:11:"ReanaUrduZe";i:5592;s:8:"TwiddVir";i:5593;s:5:"Possi";i:5594;s:5:"ArEus";i:5595;s:8:"NeogZono";i:5596;s:8:"ProdPsTe";i:5597;s:6:"AttSte";i:5598;s:4:"Epiz";i:5599;s:5:"Impot";i:5600;s:8:"PotWerec";i:5601;s:13:"DavidPsePseud";i:5602;s:5:"Semic";i:5603;s:4:"Hell";i:5604;s:8:"EncyRepe";i:5605;s:6:"TorUng";i:5606;s:12:"GlSauroUnlea";i:5607;s:7:"RecoThy";i:5608;s:7:"GiQuiSu";i:5609;s:5:"CrVel";i:5610;s:9:"PeSeUnthr";i:5611;s:12:"SchUnleaWitc";i:5612;s:14:"BiannGenitLevi";i:5613;s:11:"EndoMisNong";i:5614;s:3:"Slu";i:5615;s:11:"FruOordxSyn";i:5616;s:7:"RondVes";i:5617;s:4:"Upmo";i:5618;s:9:"MemorShou";i:5619;s:5:"Preli";i:5620;s:4:"Midd";i:5621;s:6:"PilTom";i:5622;s:5:"Wager";i:5623;s:12:"CollaCosSpor";i:5624;s:12:"SleuTaffyTet";i:5625;s:8:"NaceVagi";i:5626;s:5:"Ondag";i:5627;s:3:"Anc";i:5628;s:5:"Unafi";i:5629;s:5:"Parda";i:5630;s:10:"CrossIndig";i:5631;s:4:"ChCo";i:5632;s:6:"JaSeba";i:5633;s:6:"ForPen";i:5634;s:6:"PlunTr";i:5635;s:9:"OverTrUni";i:5636;s:7:"SupUnmo";i:5637;s:13:"SeeaSemilThos";i:5638;s:4:"Clad";i:5639;s:5:"MiSal";i:5640;s:5:"IntRe";i:5641;s:10:"CopGaSpeec";i:5642;s:7:"MaOaRiz";i:5643;s:9:"ElePoseUn";i:5644;s:5:"Trapf";i:5645;s:4:"Syll";i:5646;s:4:"Unof";i:5647;s:7:"SpSupTr";i:5648;s:7:"CrassOv";i:5649;s:9:"SupeTinke";i:5650;s:8:"NoneYamx";i:5651;s:4:"Jour";i:5652;s:7:"SummeSy";i:5653;s:7:"SwaTwin";i:5654;s:5:"Outse";i:5655;s:5:"Unlic";i:5656;s:12:"HospPlastQua";i:5657;s:7:"UnaYabb";i:5658;s:5:"Holly";i:5659;s:7:"PlaUnre";i:5660;s:11:"DisEngMonum";i:5661;s:3:"Opu";i:5662;s:10:"LogyxPrefe";i:5663;s:5:"OccPr";i:5664;s:7:"DrafPro";i:5665;s:8:"SeleTypi";i:5666;s:4:"SuUr";i:5667;s:4:"Sper";i:5668;s:7:"IrUnret";i:5669;s:7:"GoloSem";i:5670;s:12:"QuadrRevToly";i:5671;s:3:"Ura";i:5672;s:6:"ShopVa";i:5673;s:11:"ChucGentlVi";i:5674;s:8:"HaulmHic";i:5675;s:8:"OversOxe";i:5676;s:15:"IllumTritoUnwoo";i:5677;s:10:"ReThunUnpe";i:5678;s:3:"Phe";i:5679;s:5:"OdySe";i:5680;s:10:"DisIrrMans";i:5681;s:9:"DoEpYttri";i:5682;s:12:"OveOxazSpoof";i:5683;s:5:"Recom";i:5684;s:3:"Spy";i:5685;s:9:"ChiFooUns";i:5686;s:8:"NoSuUnov";i:5687;s:7:"LeTiUnr";i:5688;s:14:"ChromIncusSulp";i:5689;s:12:"EschaGastrRe";i:5690;s:12:"OpaquTechnUn";i:5691;s:9:"CuGasSnee";i:5692;s:9:"BrillShoo";i:5693;s:8:"NotUrete";i:5694;s:13:"SpiroSteYaupo";i:5695;s:8:"IntScyph";i:5696;s:5:"HoUnr";i:5697;s:5:"Silkg";i:5698;s:3:"Khw";i:5699;s:8:"MalSemip";i:5700;s:9:"ChrisNonp";i:5701;s:8:"PosPrefl";i:5702;s:6:"RuffSu";i:5703;s:3:"Sax";i:5704;s:8:"MulTermi";i:5705;s:10:"CooMonsUns";i:5706;s:7:"JiPaUnb";i:5707;s:5:"Thymo";i:5708;s:12:"CurstMimePos";i:5709;s:7:"UnrWood";i:5710;s:5:"Favon";i:5711;s:6:"LeSupp";i:5712;s:2:"Sq";i:5713;s:8:"FireInse";i:5714;s:10:"MonopRaffa";i:5715;s:3:"Pho";i:5716;s:14:"ConvNotwiSuper";i:5717;s:5:"OchSt";i:5718;s:10:"DeDialEogh";i:5719;s:9:"MisoUnwra";i:5720;s:8:"ProvUnad";i:5721;s:2:"Cy";i:5722;s:7:"MisenWo";i:5723;s:7:"FiPansc";i:5724;s:13:"AnomaHianPear";i:5725;s:5:"HySat";i:5726;s:4:"PrVa";i:5727;s:12:"QuicUnwWoneg";i:5728;s:9:"DefraHygr";i:5729;s:8:"CherHoMy";i:5730;s:9:"ChanVined";i:5731;s:7:"RaRavis";i:5732;s:15:"LiquiOverpSuper";i:5733;s:11:"CubPhiloUns";i:5734;s:7:"SleiSpa";i:5735;s:6:"SwaUnp";i:5736;s:10:"CoRefUnrig";i:5737;s:15:"SarciThundWantl";i:5738;s:10:"BubonIncOn";i:5739;s:7:"PlScrup";i:5740;s:7:"DeDiObv";i:5741;s:11:"RaisUnderVe";i:5742;s:12:"ConteHaIndec";i:5743;s:11:"MusPolycSul";i:5744;s:7:"SagaSub";i:5745;s:5:"OcUnr";i:5746;s:4:"Reca";i:5747;s:10:"ImmenWitti";i:5748;s:7:"PhraUnc";i:5749;s:5:"Prein";i:5750;s:10:"LievMetPre";i:5751;s:8:"OggSigil";i:5752;s:12:"ThTripyUnbro";i:5753;s:10:"KraTerebTr";i:5754;s:6:"InteSe";i:5755;s:10:"FoSheThora";i:5756;s:9:"ExiNoniUn";i:5757;s:12:"QuRoentSamsa";i:5758;s:6:"PhTyro";i:5759;s:10:"LazybParap";i:5760;s:8:"CaFissMo";i:5761;s:5:"Ataxi";i:5762;s:14:"JaileMolePrear";i:5763;s:10:"BarytSixha";i:5764;s:4:"SiSo";i:5765;s:12:"DiscoInterRa";i:5766;s:11:"HyposSyVici";i:5767;s:4:"InLu";i:5768;s:10:"FlPolysTot";i:5769;s:10:"PrSlaugThe";i:5770;s:9:"MegThTort";i:5771;s:7:"NodReSy";i:5772;s:9:"ConPoliVi";i:5773;s:5:"Snipp";i:5774;s:13:"CuticGunbSulf";i:5775;s:5:"Parag";i:5776;s:3:"Ili";i:5777;s:4:"Call";i:5778;s:14:"MarmSairvSuper";i:5779;s:8:"MollSeam";i:5780;s:10:"InvalSesba";i:5781;s:5:"EqInd";i:5782;s:6:"CoPodo";i:5783;s:7:"AntiWis";i:5784;s:14:"GestiHomePetra";i:5785;s:3:"Phy";i:5786;s:7:"CaimiIs";i:5787;s:8:"OstUrson";i:5788;s:6:"ReSpha";i:5789;s:10:"DiDisParas";i:5790;s:10:"OtalOvSpha";i:5791;s:7:"SaSpavi";i:5792;s:8:"UndwiUns";i:5793;s:5:"Linse";i:5794;s:7:"StUnadu";i:5795;s:4:"SaWi";i:5796;s:13:"InterPsyTempt";i:5797;s:10:"CrosDiSecl";i:5798;s:6:"CyLeMu";i:5799;s:5:"CryNu";i:5800;s:5:"Yiddi";i:5801;s:5:"StoUn";i:5802;s:9:"EggeOutwa";i:5803;s:10:"MoravVisuo";i:5804;s:5:"Ghurr";i:5805;s:14:"CompHolidToxic";i:5806;s:7:"MyoSoci";i:5807;s:9:"LiPosthUn";i:5808;s:6:"SpUnto";i:5809;s:3:"Sep";i:5810;s:11:"FumatIndiTr";i:5811;s:4:"Nege";i:5812;s:10:"PeResuScra";i:5813;s:12:"ConvLinParas";i:5814;s:6:"HooPal";i:5815;s:6:"SporSt";i:5816;s:11:"DyeForsUnem";i:5817;s:10:"PredrPrStu";i:5818;s:11:"DivHegLutem";i:5819;s:10:"ScornUnhig";i:5820;s:9:"BeneTubWa";i:5821;s:5:"SanSt";i:5822;s:7:"LaSaUnc";i:5823;s:7:"MisaPol";i:5824;s:14:"MungPenetRudol";i:5825;s:8:"NonclSur";i:5826;s:10:"HydLigPres";i:5827;s:6:"QuirSa";i:5828;s:7:"SquirTa";i:5829;s:8:"EnExpPro";i:5830;s:4:"HeUn";i:5831;s:9:"UnstUnthr";i:5832;s:5:"LiZoo";i:5833;s:5:"Somet";i:5834;s:7:"UnZucch";i:5835;s:6:"InSpUn";i:5836;s:12:"GawNeptOutbo";i:5837;s:4:"Weed";i:5838;s:9:"BoPolySig";i:5839;s:8:"HatcNucl";i:5840;s:6:"HypInn";i:5841;s:6:"UlUngr";i:5842;s:10:"MesScUnatt";i:5843;s:5:"UncYo";i:5844;s:12:"BimilFasciMe";i:5845;s:8:"PubiTass";i:5846;s:15:"GaragLouchNonlo";i:5847;s:8:"PhilhTah";i:5848;s:6:"SornWa";i:5849;s:10:"TamilUnhor";i:5850;s:3:"Bra";i:5851;s:12:"HeteParPreco";i:5852;s:15:"DazemTotanUncri";i:5853;s:9:"FahlMilly";i:5854;s:5:"ImOpp";i:5855;s:5:"Sahar";i:5856;s:14:"MelopReceTrira";i:5857;s:5:"Mimly";i:5858;s:13:"LunaSupraUnil";i:5859;s:12:"ResiVerWhipp";i:5860;s:10:"NucTolUnou";i:5861;s:6:"SuTele";i:5862;s:6:"PinShu";i:5863;s:8:"HyphYuzl";i:5864;s:7:"SplinUn";i:5865;s:5:"Stack";i:5866;s:5:"Odont";i:5867;s:11:"MoParamPreo";i:5868;s:7:"LiMonst";i:5869;s:6:"StroUn";i:5870;s:4:"Tica";i:5871;s:7:"CouElec";i:5872;s:8:"EnrKusim";i:5873;s:3:"Sic";i:5874;s:9:"IncoUnali";i:5875;s:11:"RedeSelexUp";i:5876;s:5:"Parat";i:5877;s:9:"NonacWhif";i:5878;s:12:"RoSangaWistl";i:5879;s:8:"RatTranq";i:5880;s:9:"JentLogUp";i:5881;s:3:"Mus";i:5882;s:14:"ServaSparkSper";i:5883;s:5:"Sylvi";i:5884;s:10:"SnackSurge";i:5885;s:11:"AztecHomPou";i:5886;s:4:"PrPs";i:5887;s:4:"Unex";i:5888;s:7:"CeorGon";i:5889;s:8:"DisesWin";i:5890;s:8:"EglesIsl";i:5891;s:5:"Remix";i:5892;s:5:"BuRes";i:5893;s:11:"HemogOvSupe";i:5894;s:14:"MeasMenacSpora";i:5895;s:8:"OsphyTal";i:5896;s:4:"Blan";i:5897;s:6:"EfflHo";i:5898;s:9:"PrSpeceUn";i:5899;s:6:"IscUnt";i:5900;s:4:"InTa";i:5901;s:9:"PrehuTene";i:5902;s:9:"MatRuShak";i:5903;s:11:"NervePetRed";i:5904;s:8:"EpiMoOut";i:5905;s:7:"CuOsUnp";i:5906;s:6:"InfiSu";i:5907;s:8:"DeMnSkid";i:5908;s:5:"Secti";i:5909;s:5:"CurEc";i:5910;s:11:"FlankMiShri";i:5911;s:12:"ReeliRuRusty";i:5912;s:7:"PluviTu";i:5913;s:12:"MonoNonasStr";i:5914;s:5:"PreTo";i:5915;s:7:"StuUnif";i:5916;s:5:"Unord";i:5917;s:7:"DichrNa";i:5918;s:9:"NimmProlo";i:5919;s:3:"Jol";i:5920;s:9:"HadScoSub";i:5921;s:5:"Stoma";i:5922;s:8:"PrSignTe";i:5923;s:10:"RebTubeVul";i:5924;s:8:"DiNoWood";i:5925;s:5:"OrSwa";i:5926;s:9:"GaIntelOr";i:5927;s:5:"Scuti";i:5928;s:4:"Gent";i:5929;s:7:"NoniPte";i:5930;s:6:"CaHeat";i:5931;s:10:"PhalaWarve";i:5932;s:8:"PrPygUra";i:5933;s:5:"Nutcr";i:5934;s:11:"LepidMonUnr";i:5935;s:7:"PalPatr";i:5936;s:9:"ReiSixVes";i:5937;s:6:"MesUnf";i:5938;s:3:"Tsu";i:5939;s:5:"Ununi";i:5940;s:6:"ReSpie";i:5941;s:4:"SuUn";i:5942;s:9:"OrdinRabb";i:5943;s:11:"MiNapuPlebx";i:5944;s:8:"PodogSta";i:5945;s:9:"KuskuNigg";i:5946;s:4:"Capr";i:5947;s:5:"Chlam";i:5948;s:5:"CoSch";i:5949;s:13:"IndisMyseTubx";i:5950;s:10:"MazRhiTran";i:5951;s:4:"Prer";i:5952;s:7:"DoMecRu";i:5953;s:11:"RamaTopeZen";i:5954;s:13:"OverSpicUnmel";i:5955;s:5:"Prata";i:5956;s:6:"NebaSt";i:5957;s:12:"BudmaCritDio";i:5958;s:11:"GyroPaleoUn";i:5959;s:8:"EsMaRede";i:5960;s:8:"SizTetra";i:5961;s:7:"AsStaTa";i:5962;s:12:"NurseSmyrUna";i:5963;s:9:"CoUnmWrea";i:5964;s:10:"SisUnaltUn";i:5965;s:11:"GigarGranPr";i:5966;s:9:"MacabMyop";i:5967;s:7:"CrediGu";i:5968;s:10:"OncRinSlug";i:5969;s:4:"Rose";i:5970;s:5:"MisMo";i:5971;s:11:"ShoeSlUnmem";i:5972;s:10:"PasquPreci";i:5973;s:9:"DedicInIr";i:5974;s:12:"GlovePyjamSa";i:5975;s:9:"CockThroa";i:5976;s:3:"Jau";i:5977;s:7:"LibTrip";i:5978;s:13:"MegaRaceTobac";i:5979;s:5:"Atwix";i:5980;s:12:"PlatyReUnben";i:5981;s:11:"MoPropSulph";i:5982;s:5:"Twere";i:5983;s:4:"Timi";i:5984;s:8:"CoOophUn";i:5985;s:7:"KentiRe";i:5986;s:11:"AgrolKaPter";i:5987;s:5:"Xeres";i:5988;s:14:"ChoriLochiMaga";i:5989;s:12:"FucoxGaoNutt";i:5990;s:9:"PrStrTerp";i:5991;s:7:"ItSciss";i:5992;s:11:"GassiNatUnb";i:5993;s:4:"Unri";i:5994;s:8:"PrSarSir";i:5995;s:4:"HeUs";i:5996;s:11:"CyMatamStan";i:5997;s:9:"MarcSuTub";i:5998;s:7:"UnrVall";i:5999;s:7:"PentaPh";i:6000;s:5:"Knobs";i:6001;s:5:"Poste";i:6002;s:4:"InPr";i:6003;s:12:"DoldrRecUros";i:6004;s:12:"OctodParkeTh";i:6005;s:6:"PlaThe";i:6006;s:2:"En";i:6007;s:13:"PseudReheSpri";i:6008;s:8:"NitPraTo";i:6009;s:6:"PrTetr";i:6010;s:8:"DiscKary";i:6011;s:5:"Rattl";i:6012;s:6:"SpicSu";i:6013;s:7:"NonfTet";i:6014;s:7:"UnUteri";i:6015;s:14:"LesbiNawxPrere";i:6016;s:4:"Thra";i:6017;s:7:"MercThu";i:6018;s:6:"PiSini";i:6019;s:3:"Ten";i:6020;s:10:"ExecuXylos";i:6021;s:6:"IaKoUn";i:6022;s:4:"Coin";i:6023;s:6:"HagsLo";i:6024;s:8:"ProsScha";i:6025;s:5:"Weepx";i:6026;s:7:"MirdaPo";i:6027;s:4:"Pegm";i:6028;s:8:"DemForgo";i:6029;s:5:"Meado";i:6030;s:10:"ArchLimbSk";i:6031;s:8:"NegOrSem";i:6032;s:10:"KonRusseUn";i:6033;s:4:"PrSm";i:6034;s:7:"MuRenUn";i:6035;s:3:"Ras";i:6036;s:10:"InterKaTep";i:6037;s:13:"RedoToseTropi";i:6038;s:6:"LibeTa";i:6039;s:8:"ChQuinRe";i:6040;s:4:"Zoop";i:6041;s:10:"MorSpanUne";i:6042;s:10:"IndicRoUnr";i:6043;s:5:"SarTi";i:6044;s:3:"Mou";i:6045;s:7:"CaSatra";i:6046;s:12:"ForamMoaMura";i:6047;s:8:"MaMoSulp";i:6048;s:13:"PiscaTonnVisi";i:6049;s:14:"IndusMuskPodic";i:6050;s:9:"ResprSeni";i:6051;s:9:"AftHypeOa";i:6052;s:9:"DeDigEuco";i:6053;s:8:"TriUndes";i:6054;s:7:"MoskSik";i:6055;s:9:"FifthLuSu";i:6056;s:5:"Proli";i:6057;s:9:"PolyeQuin";i:6058;s:9:"InIsUnmat";i:6059;s:7:"CrPondo";i:6060;s:9:"LantStrid";i:6061;s:13:"HaveSubpTechn";i:6062;s:8:"MaMusaSc";i:6063;s:7:"MoSeSup";i:6064;s:5:"Timef";i:6065;s:9:"LoPyvuSyn";i:6066;s:3:"Spl";i:6067;s:9:"MouReseUm";i:6068;s:4:"ToWa";i:6069;s:9:"MonocSept";i:6070;s:13:"KanepTransTri";i:6071;s:4:"Enhy";i:6072;s:13:"SpareSubTaran";i:6073;s:8:"PagaSupe";i:6074;s:13:"MaesNaturSero";i:6075;s:6:"ClDere";i:6076;s:6:"BuruSn";i:6077;s:5:"PerPs";i:6078;s:7:"PanmUpw";i:6079;s:6:"GreeRe";i:6080;s:7:"ErUnpro";i:6081;s:7:"CycliHe";i:6082;s:2:"Eu";i:6083;s:7:"HoPandr";i:6084;s:11:"MesNonOscil";i:6085;s:10:"ExtPoTetra";i:6086;s:13:"MastoMonoPyro";i:6087;s:6:"HiNoSp";i:6088;s:10:"GlPraUnhed";i:6089;s:6:"RewTho";i:6090;s:7:"UnaWoor";i:6091;s:14:"ElfhPicknUntes";i:6092;s:4:"Tinw";i:6093;s:6:"CrucEx";i:6094;s:8:"FloInoge";i:6095;s:11:"HadexPremTh";i:6096;s:3:"Mys";i:6097;s:6:"TiUnau";i:6098;s:12:"EurNonsuNonv";i:6099;s:5:"Woonx";i:6100;s:5:"DiUnr";i:6101;s:7:"PoRaUlt";i:6102;s:5:"Ethno";i:6103;s:4:"Heat";i:6104;s:6:"ShacSm";i:6105;s:11:"NonPhantSyn";i:6106;s:4:"Quin";i:6107;s:14:"FewnKieseOptio";i:6108;s:11:"ClypImpaUns";i:6109;s:9:"ParalUnbe";i:6110;s:7:"ParSing";i:6111;s:10:"CleSexuaUn";i:6112;s:10:"SacchSolid";i:6113;s:9:"HorsIncor";i:6114;s:13:"FoodlHusNight";i:6115;s:8:"MonSubUn";i:6116;s:5:"Inkho";i:6117;s:4:"Panz";i:6118;s:4:"NoPe";i:6119;s:7:"PonTatu";i:6120;s:8:"CotVangl";i:6121;s:4:"Xero";i:6122;s:5:"CurPi";i:6123;s:3:"Str";i:6124;s:4:"Sacr";i:6125;s:4:"Quai";i:6126;s:5:"Uncoa";i:6127;s:9:"ChitProsu";i:6128;s:6:"PenuUn";i:6129;s:4:"BrLa";i:6130;s:7:"EnterJu";i:6131;s:11:"CoOutsTroph";i:6132;s:11:"ComNondRele";i:6133;s:12:"PreleScenUnp";i:6134;s:12:"ExHogwaPerip";i:6135;s:10:"UndWaZibet";i:6136;s:12:"GinkxQuinqRa";i:6137;s:6:"EmmVer";i:6138;s:10:"PalReTeasa";i:6139;s:12:"MePrepoSurge";i:6140;s:12:"HomSommUncti";i:6141;s:9:"TheoTinin";i:6142;s:12:"IrrMonoPetio";i:6143;s:13:"MultOxysuThes";i:6144;s:4:"Natc";i:6145;s:11:"DisiEnStoma";i:6146;s:11:"HegarPolTan";i:6147;s:5:"StTra";i:6148;s:5:"Unexc";i:6149;s:12:"AweSyllaUnpr";i:6150;s:6:"CaReci";i:6151;s:12:"SatirUnmiVac";i:6152;s:5:"SeUnc";i:6153;s:8:"ProtTint";i:6154;s:5:"Waikl";i:6155;s:10:"MaMegasSpr";i:6156;s:3:"Gam";i:6157;s:7:"CoVagar";i:6158;s:7:"ToppUre";i:6159;s:7:"ArcImpa";i:6160;s:15:"MokoxOcherSubar";i:6161;s:10:"DeeEtiolTh";i:6162;s:10:"TrottUnUnf";i:6163;s:5:"Bronz";i:6164;s:4:"Tutr";i:6165;s:6:"PisaRi";i:6166;s:6:"PuSuTe";i:6167;s:5:"PoSpi";i:6168;s:7:"LiReefe";i:6169;s:9:"QuadWimex";i:6170;s:11:"GoniSizalYa";i:6171;s:3:"Ins";i:6172;s:15:"GinglSemigSerpe";i:6173;s:13:"GadinSemUnfei";i:6174;s:7:"TinUnne";i:6175;s:5:"Grapt";i:6176;s:7:"PhScaTh";i:6177;s:5:"Preaf";i:6178;s:3:"Kap";i:6179;s:10:"MaywMohaWe";i:6180;s:4:"TaTe";i:6181;s:5:"FiTig";i:6182;s:3:"Jer";i:6183;s:11:"OdobPawneSp";i:6184;s:15:"NonbaTorchUnext";i:6185;s:4:"SlSt";i:6186;s:5:"Weelx";i:6187;s:5:"ExTem";i:6188;s:4:"EnUn";i:6189;s:12:"HurlyPythoTr";i:6190;s:4:"Glos";i:6191;s:13:"SpisuTriceUne";i:6192;s:11:"EpacImpoPro";i:6193;s:12:"MetOakeOuttr";i:6194;s:9:"OrScWoodn";i:6195;s:7:"HoMazum";i:6196;s:9:"MesenSemi";i:6197;s:9:"RegulSand";i:6198;s:5:"Stasi";i:6199;s:6:"UnZoog";i:6200;s:4:"Paro";i:6201;s:11:"SinapStaSup";i:6202;s:9:"TaeTeTheo";i:6203;s:10:"KrapiProrh";i:6204;s:9:"GuarMarxi";i:6205;s:13:"SybarTanUnwed";i:6206;s:4:"JaUn";i:6207;s:6:"SemiSo";i:6208;s:8:"UniUnind";i:6209;s:12:"DulTriuTubul";i:6210;s:5:"Engra";i:6211;s:12:"UnpreZygonZy";i:6212;s:5:"Recko";i:6213;s:9:"GrinnInUn";i:6214;s:7:"EleGeLi";i:6215;s:12:"MolSyconUnst";i:6216;s:4:"Scum";i:6217;s:10:"FlaFostUnd";i:6218;s:5:"Peris";i:6219;s:8:"CophLava";i:6220;s:12:"SmetSpriTran";i:6221;s:8:"NephUnwa";i:6222;s:7:"QueetSc";i:6223;s:10:"FeifGueTab";i:6224;s:10:"OliveUnciv";i:6225;s:10:"BuilCompTa";i:6226;s:7:"RecliRe";i:6227;s:4:"OrUn";i:6228;s:7:"ExRadSa";i:6229;s:3:"Jux";i:6230;s:12:"ShikiSupeUns";i:6231;s:4:"DeUn";i:6232;s:9:"MiNuReine";i:6233;s:8:"TheTrime";i:6234;s:6:"SemWot";i:6235;s:3:"Pyr";i:6236;s:9:"NerthOuts";i:6237;s:6:"DyscGl";i:6238;s:3:"Mag";i:6239;s:8:"LucraPer";i:6240;s:7:"ParUlVi";i:6241;s:3:"Ken";i:6242;s:8:"ScrUnsca";i:6243;s:10:"PewmaToUnp";i:6244;s:13:"MangNeglPolys";i:6245;s:9:"FoInhiSor";i:6246;s:11:"DrunSporTir";i:6247;s:4:"HaLo";i:6248;s:11:"PharyQuUnch";i:6249;s:12:"MiamMilPhary";i:6250;s:4:"Stuc";i:6251;s:10:"DomElectPs";i:6252;s:4:"Kumn";i:6253;s:9:"PreteVaga";i:6254;s:4:"MiPr";i:6255;s:9:"GimGrooPr";i:6256;s:5:"ForSc";i:6257;s:3:"Opa";i:6258;s:5:"Hirud";i:6259;s:6:"OverPo";i:6260;s:9:"MarNomaSt";i:6261;s:4:"Saxo";i:6262;s:6:"HyLept";i:6263;s:7:"MyxaOcy";i:6264;s:3:"Iso";i:6265;s:9:"NonchPeri";i:6266;s:9:"ElytrTric";i:6267;s:6:"MePurb";i:6268;s:6:"ChaOut";i:6269;s:8:"NonTrave";i:6270;s:8:"InsiPent";i:6271;s:6:"PrTern";i:6272;s:4:"Subg";i:6273;s:7:"ImprUnv";i:6274;s:4:"Unha";i:6275;s:8:"ProgUngl";i:6276;s:12:"LaMammiToros";i:6277;s:5:"SeaSu";i:6278;s:5:"Vigon";i:6279;s:9:"AriIdiLog";i:6280;s:8:"NovPsyTh";i:6281;s:8:"KrausTro";i:6282;s:5:"Undup";i:6283;s:10:"ContrMysTe";i:6284;s:4:"Harl";i:6285;s:3:"Upk";i:6286;s:6:"InfThi";i:6287;s:7:"GriMyct";i:6288;s:9:"BleaCoccy";i:6289;s:12:"NeuroSoldiSw";i:6290;s:8:"HeKathRa";i:6291;s:10:"NackeNitro";i:6292;s:14:"CapacPestPsych";i:6293;s:8:"OutlaUnl";i:6294;s:7:"PhrenVe";i:6295;s:10:"PluQuicYum";i:6296;s:5:"Tesse";i:6297;s:5:"Poiki";i:6298;s:6:"AppaWr";i:6299;s:8:"CattChPo";i:6300;s:5:"Unsuc";i:6301;s:10:"JotnOtoUns";i:6302;s:10:"ArgNubiPre";i:6303;s:9:"GastLagga";i:6304;s:3:"Yom";i:6305;s:8:"SpSteVet";i:6306;s:12:"ColPleoSheri";i:6307;s:10:"FrankNeSes";i:6308;s:3:"Kon";i:6309;s:14:"CraigMayorOpul";i:6310;s:8:"UnwUnVer";i:6311;s:7:"NonpeRe";i:6312;s:3:"Ort";i:6313;s:7:"HirsQui";i:6314;s:7:"HurOver";i:6315;s:5:"PrSlo";i:6316;s:5:"Cerem";i:6317;s:10:"PleaToXero";i:6318;s:9:"ProUnemUn";i:6319;s:4:"UnWi";i:6320;s:5:"PrePr";i:6321;s:11:"MigSerUnref";i:6322;s:5:"Progu";i:6323;s:8:"HaMultSe";i:6324;s:6:"RiRoll";i:6325;s:5:"Unthi";i:6326;s:14:"ExtolMisnSperm";i:6327;s:7:"PreTimb";i:6328;s:7:"RickeSh";i:6329;s:10:"PericPyrrh";i:6330;s:7:"TiUnder";i:6331;s:13:"HeaVocalWhewt";i:6332;s:4:"Misw";i:6333;s:3:"Bol";i:6334;s:14:"PlatiPunnSphac";i:6335;s:3:"Vag";i:6336;s:14:"SainxSlowSteep";i:6337;s:10:"MacrPaguTr";i:6338;s:4:"Scut";i:6339;s:8:"ShooUnin";i:6340;s:10:"OenanSayax";i:6341;s:14:"EpigLegioTiara";i:6342;s:11:"MenogStopVa";i:6343;s:6:"PlUniq";i:6344;s:10:"EmOverZepp";i:6345;s:11:"SeTelenWork";i:6346;s:6:"MoReco";i:6347;s:4:"Reda";i:6348;s:8:"PelRucTh";i:6349;s:10:"UngriWitho";i:6350;s:4:"MuOr";i:6351;s:14:"PrincThiopVivi";i:6352;s:6:"NonaPh";i:6353;s:5:"NeZoo";i:6354;s:8:"PuffySup";i:6355;s:4:"Bair";i:6356;s:14:"ManwaSinaiUroc";i:6357;s:6:"AntLux";i:6358;s:9:"FungKokTo";i:6359;s:10:"AquipPhyto";i:6360;s:8:"OrtTuVie";i:6361;s:11:"CaloGudgPie";i:6362;s:4:"Palt";i:6363;s:7:"HubXeno";i:6364;s:9:"KnobSyTra";i:6365;s:10:"MirzaObiUn";i:6366;s:10:"CongeStali";i:6367;s:5:"Pomox";i:6368;s:3:"Vol";i:6369;s:7:"InepMel";i:6370;s:10:"ResiRhynSe";i:6371;s:8:"PrTychUn";i:6372;s:9:"DactyTrut";i:6373;s:13:"BradaCarpHera";i:6374;s:9:"OctRebiSu";i:6375;s:10:"ShTruerVer";i:6376;s:9:"HeMeTellu";i:6377;s:12:"FairyGraveSt";i:6378;s:8:"PergTalp";i:6379;s:3:"Sty";i:6380;s:6:"IntSol";i:6381;s:9:"SpeciToUn";i:6382;s:8:"NutaSkir";i:6383;s:5:"Rhomb";i:6384;s:9:"SharUnsni";i:6385;s:5:"DiInt";i:6386;s:5:"Splen";i:6387;s:6:"OraTer";i:6388;s:5:"Dahli";i:6389;s:8:"ClaOliSo";i:6390;s:8:"PeSangSa";i:6391;s:4:"Tors";i:6392;s:9:"HypopPari";i:6393;s:14:"ThiosTonoUnise";i:6394;s:7:"CatPara";i:6395;s:4:"Sumb";i:6396;s:7:"OsPreSw";i:6397;s:8:"HattUrsi";i:6398;s:6:"DiNotc";i:6399;s:5:"Disso";i:6400;s:8:"ReRenaWh";i:6401;s:12:"MonocSeroWha";i:6402;s:5:"Toade";i:6403;s:4:"CnMo";i:6404;s:8:"CardiSca";i:6405;s:4:"Corn";i:6406;s:14:"ExtrPotorPreve";i:6407;s:13:"HomalRepreSub";i:6408;s:4:"Vers";i:6409;s:5:"Unbla";i:6410;s:12:"PalaPneStodg";i:6411;s:5:"PyTri";i:6412;s:10:"DeyshPlPul";i:6413;s:9:"IvorUndis";i:6414;s:7:"PellTid";i:6415;s:5:"Thatx";i:6416;s:8:"DecaEleu";i:6417;s:4:"Maea";i:6418;s:9:"AuObUnadj";i:6419;s:11:"EntInoJumps";i:6420;s:12:"TreUnnotUnsw";i:6421;s:9:"AtechGeOv";i:6422;s:9:"LecUndeUn";i:6423;s:9:"DimnStrin";i:6424;s:13:"MonopNonsuRep";i:6425;s:3:"Ver";i:6426;s:6:"PesTho";i:6427;s:11:"InPredSemis";i:6428;s:7:"PoPrint";i:6429;s:4:"Coch";i:6430;s:4:"Mopp";i:6431;s:3:"Dra";i:6432;s:7:"DrygoPr";i:6433;s:9:"SulciUnco";i:6434;s:8:"SaboUnUn";i:6435;s:10:"TanqTreUns";i:6436;s:7:"UnjVesp";i:6437;s:4:"Pror";i:6438;s:4:"Love";i:6439;s:10:"EmeHyPlaty";i:6440;s:6:"JucRec";i:6441;s:11:"ConfePhoTra";i:6442;s:9:"ConcoPoss";i:6443;s:12:"GalacIrSemif";i:6444;s:9:"ParaUnawa";i:6445;s:7:"UnUnZam";i:6446;s:7:"PinSabr";i:6447;s:9:"MicOverSu";i:6448;s:8:"PoilViny";i:6449;s:8:"SentiSup";i:6450;s:10:"ParlaProSu";i:6451;s:11:"BipBrancObu";i:6452;s:5:"Uncri";i:6453;s:5:"Suran";i:6454;s:12:"BloClaviTitu";i:6455;s:4:"TwUn";i:6456;s:6:"PiPost";i:6457;s:7:"ElaFeHy";i:6458;s:5:"Unnav";i:6459;s:13:"OphidPenthUni";i:6460;s:3:"Lar";i:6461;s:15:"LongiRisibTopog";i:6462;s:7:"ThamuUn";i:6463;s:11:"LotProUnbla";i:6464;s:7:"TaUnmer";i:6465;s:7:"MilPhyl";i:6466;s:5:"IncNi";i:6467;s:3:"Fix";i:6468;s:6:"ExSwip";i:6469;s:5:"OraUn";i:6470;s:4:"Scle";i:6471;s:9:"HoReparSu";i:6472;s:9:"OvangUnpr";i:6473;s:12:"DoorSnowbUni";i:6474;s:9:"OverPolUn";i:6475;s:3:"Mit";i:6476;s:5:"Unsop";i:6477;s:14:"HeavyHeterJump";i:6478;s:9:"PostpWall";i:6479;s:11:"RepRicTetra";i:6480;s:12:"LiqPalaUnipu";i:6481;s:7:"ClMesPe";i:6482;s:13:"HeterLirParda";i:6483;s:5:"Trieq";i:6484;s:9:"SamarTigr";i:6485;s:5:"Diver";i:6486;s:4:"LiUn";i:6487;s:4:"Beta";i:6488;s:6:"DivuOp";i:6489;s:9:"NoniScruf";i:6490;s:9:"LoafOnchi";i:6491;s:3:"Ele";i:6492;s:7:"TeraUnm";i:6493;s:5:"Palee";i:6494;s:5:"Solid";i:6495;s:4:"Mynp";i:6496;s:10:"FairyPulWo";i:6497;s:4:"Wate";i:6498;s:4:"Rodd";i:6499;s:10:"OffPlUngui";i:6500;s:9:"OvPlayUrd";i:6501;s:7:"SocioUp";i:6502;s:5:"EtPim";i:6503;s:10:"ParonPrVir";i:6504;s:2:"Ju";i:6505;s:10:"EchiSaThuy";i:6506;s:12:"NepNodulRefi";i:6507;s:11:"IncSinecUni";i:6508;s:9:"GarbStThe";i:6509;s:13:"GluttHeadsRun";i:6510;s:8:"EvaKafir";i:6511;s:9:"CoInNonen";i:6512;s:7:"InuOtho";i:6513;s:14:"DeutParenPrere";i:6514;s:10:"EnvMetamSh";i:6515;s:8:"ButadDis";i:6516;s:9:"MendPseud";i:6517;s:6:"PaProv";i:6518;s:3:"Ina";i:6519;s:10:"EcclInfPol";i:6520;s:6:"PrUnde";i:6521;s:14:"IneffPudgPursu";i:6522;s:9:"SchizToVo";i:6523;s:3:"Imb";i:6524;s:4:"ChSt";i:6525;s:9:"LarLecoSt";i:6526;s:5:"UnUnd";i:6527;s:7:"IntReoc";i:6528;s:7:"FixedIm";i:6529;s:9:"CogTabWoo";i:6530;s:8:"SpaciUnp";i:6531;s:6:"BaraBe";i:6532;s:5:"Unbar";i:6533;s:12:"CometDeIntra";i:6534;s:12:"ErePeracWhee";i:6535;s:7:"CounUnw";i:6536;s:8:"NonTormo";i:6537;s:10:"PreiSpeTri";i:6538;s:11:"CerLankWeve";i:6539;s:12:"TractUngelVe";i:6540;s:10:"DepDrUnsol";i:6541;s:4:"Mult";i:6542;s:6:"RewVol";i:6543;s:5:"MorSy";i:6544;s:8:"UndWilkx";i:6545;s:4:"BeUn";i:6546;s:7:"ImpasUn";i:6547;s:10:"IndiaMerca";i:6548;s:4:"Unfo";i:6549;s:8:"ImMiMuti";i:6550;s:13:"NaviPreaUnspa";i:6551;s:5:"Extra";i:6552;s:11:"RenuWhWicke";i:6553;s:7:"TergUnp";i:6554;s:8:"MydTabid";i:6555;s:3:"Lod";i:6556;s:4:"Cruc";i:6557;s:5:"DiaSo";i:6558;s:8:"HetInter";i:6559;s:7:"ClMisap";i:6560;s:3:"Spe";i:6561;s:9:"IllecPant";i:6562;s:5:"Rhabd";i:6563;s:13:"BrushWireYell";i:6564;s:7:"DilaOve";i:6565;s:12:"CyprDegPotma";i:6566;s:8:"TicUtero";i:6567;s:6:"InPtyc";i:6568;s:12:"DelaMaraScul";i:6569;s:7:"AlpCrSt";i:6570;s:12:"HeUnproVagra";i:6571;s:4:"Theo";i:6572;s:12:"RecSplanVenu";i:6573;s:4:"Groc";i:6574;s:14:"BasilNoncoZami";i:6575;s:10:"LifexPasto";i:6576;s:14:"PsychSubdWhipp";i:6577;s:5:"MosTe";i:6578;s:14:"CoplaObsiXyloq";i:6579;s:10:"FatbrStrat";i:6580;s:7:"ToUnpit";i:6581;s:5:"PitSh";i:6582;s:8:"JapRatch";i:6583;s:8:"VarniYod";i:6584;s:11:"HancoRemTen";i:6585;s:10:"KoreiOverf";i:6586;s:9:"TineVimfu";i:6587;s:7:"NoruUne";i:6588;s:3:"Ure";i:6589;s:11:"CultrPlaSun";i:6590;s:10:"ExogaTrowe";i:6591;s:5:"Upcom";i:6592;s:10:"CuneiFePre";i:6593;s:10:"MatPlSnaff";i:6594;s:8:"PhaTarTy";i:6595;s:9:"BetHenrUn";i:6596;s:6:"BruMet";i:6597;s:4:"Peck";i:6598;s:5:"Makes";i:6599;s:5:"Unpit";i:6600;s:8:"ScThewTo";i:6601;s:4:"Spec";i:6602;s:9:"UnplUnres";i:6603;s:11:"OxytoSaftUp";i:6604;s:5:"CiHoo";i:6605;s:9:"InverMoTi";i:6606;s:3:"Upt";i:6607;s:15:"OverfPolymPrein";i:6608;s:9:"CotePhoto";i:6609;s:4:"Crew";i:6610;s:8:"PredPrim";i:6611;s:10:"CephaShrub";i:6612;s:3:"Nuc";i:6613;s:3:"Ouz";i:6614;s:9:"ThereVena";i:6615;s:3:"Sug";i:6616;s:3:"Nam";i:6617;s:7:"QuinoTr";i:6618;s:5:"Reove";i:6619;s:10:"GliomIntSc";i:6620;s:9:"DuchReban";i:6621;s:6:"DisHot";i:6622;s:10:"PhoroRatio";i:6623;s:4:"BrTi";i:6624;s:10:"HyInterTae";i:6625;s:4:"Plic";i:6626;s:6:"IsoSar";i:6627;s:8:"CyaniPos";i:6628;s:7:"JossaTh";i:6629;s:7:"MoTease";i:6630;s:9:"PenciSynt";i:6631;s:12:"TobUnjelUrti";i:6632;s:7:"StorTri";i:6633;s:4:"Squi";i:6634;s:10:"IsoThUnsof";i:6635;s:9:"EpilNonbu";i:6636;s:4:"Recr";i:6637;s:12:"GlossMinTown";i:6638;s:3:"Sid";i:6639;s:10:"MoireNoTri";i:6640;s:9:"FungSkUna";i:6641;s:10:"LewPemmUne";i:6642;s:9:"MetaOveSc";i:6643;s:11:"FlaMiscaOwl";i:6644;s:6:"PrWhis";i:6645;s:7:"SpTesUn";i:6646;s:12:"BroSubjuUnmo";i:6647;s:6:"SupTch";i:6648;s:10:"HeromLodPo";i:6649;s:12:"BeniCheesSur";i:6650;s:9:"HypeOverl";i:6651;s:4:"Sela";i:6652;s:10:"StiTurtlVe";i:6653;s:11:"GreMacrMarr";i:6654;s:8:"AsHanImp";i:6655;s:6:"FaSher";i:6656;s:12:"MicPonerTran";i:6657;s:11:"GigarHyMudd";i:6658;s:11:"AmErytFemin";i:6659;s:5:"Sacer";i:6660;s:9:"OutmUneph";i:6661;s:7:"DissWar";i:6662;s:9:"ObPlScabr";i:6663;s:5:"Inten";i:6664;s:3:"Raj";i:6665;s:8:"LaPipeUn";i:6666;s:8:"PreReUne";i:6667;s:11:"InpMonobPle";i:6668;s:4:"Here";i:6669;s:8:"GasPicWe";i:6670;s:12:"GamoInfiMyos";i:6671;s:8:"MinoPlai";i:6672;s:4:"Unbe";i:6673;s:5:"Outla";i:6674;s:7:"PseuVer";i:6675;s:4:"Wing";i:6676;s:5:"Unrav";i:6677;s:6:"GreTur";i:6678;s:11:"OysStaTrith";i:6679;s:8:"SaSymTur";i:6680;s:13:"MisliOdontPha";i:6681;s:11:"HaysNanomRe";i:6682;s:8:"PasPhySe";i:6683;s:11:"ColoLutOpta";i:6684;s:7:"LyUrtic";i:6685;s:6:"TimbTo";i:6686;s:9:"ShotTeete";i:6687;s:13:"BrushCafCompu";i:6688;s:9:"UncrUnhum";i:6689;s:6:"FibrTa";i:6690;s:7:"GameInc";i:6691;s:5:"Weakh";i:6692;s:10:"ObfusQuave";i:6693;s:13:"FurlIrreUngen";i:6694;s:8:"FoulPant";i:6695;s:11:"DisEuryQuar";i:6696;s:4:"Fati";i:6697;s:5:"Shalt";i:6698;s:9:"GhosMiThe";i:6699;s:12:"BandlKowtoTe";i:6700;s:10:"ImponTabUn";i:6701;s:10:"HexOnsUnpe";i:6702;s:8:"ElegiTem";i:6703;s:7:"PenhRep";i:6704;s:9:"OdonPhosp";i:6705;s:6:"PlWenl";i:6706;s:7:"ReprUns";i:6707;s:4:"Smit";i:6708;s:5:"Terro";i:6709;s:7:"ResusTe";i:6710;s:10:"HoppiPrSop";i:6711;s:5:"Shini";i:6712;s:12:"HeterRoamSal";i:6713;s:6:"OverWo";i:6714;s:5:"ScUnp";i:6715;s:4:"Mead";i:6716;s:5:"Upstr";i:6717;s:5:"Unrep";i:6718;s:4:"Rumi";i:6719;s:7:"SuperTe";i:6720;s:5:"ThVer";i:6721;s:12:"SupeThrTomop";i:6722;s:12:"OugPedanStib";i:6723;s:6:"CiCoun";i:6724;s:9:"HeHobThug";i:6725;s:6:"CohGno";i:6726;s:4:"Styr";i:6727;s:9:"MePresRes";i:6728;s:9:"ExPhThumb";i:6729;s:8:"SheSkipp";i:6730;s:10:"CaFaunaUnq";i:6731;s:4:"Meri";i:6732;s:10:"FerReWaste";i:6733;s:5:"Ninet";i:6734;s:9:"KmetxUnar";i:6735;s:8:"PulsSaxt";i:6736;s:6:"PrepUp";i:6737;s:5:"Marxi";i:6738;s:10:"EpiscSubpr";i:6739;s:8:"KochProh";i:6740;s:5:"GemOr";i:6741;s:15:"HeroiSubcoUnbra";i:6742;s:4:"Scot";i:6743;s:8:"MisSnort";i:6744;s:6:"PhaPha";i:6745;s:8:"RevUnpre";i:6746;s:8:"AnoSupVe";i:6747;s:4:"Stam";i:6748;s:3:"Pru";i:6749;s:7:"BeCatap";i:6750;s:5:"DevSc";i:6751;s:11:"SexiSocUnil";i:6752;s:6:"InNons";i:6753;s:11:"RecaTecnTre";i:6754;s:5:"CiCoa";i:6755;s:11:"CoDiscPachy";i:6756;s:4:"Pleu";i:6757;s:10:"ExtrHesiMu";i:6758;s:6:"TilbTo";i:6759;s:5:"Vacuo";i:6760;s:13:"PlaTheopTrica";i:6761;s:12:"OsirProtRhoe";i:6762;s:8:"MulWeava";i:6763;s:4:"Mort";i:6764;s:10:"QuSchilTan";i:6765;s:7:"SqueTar";i:6766;s:12:"OrthPhaRoent";i:6767;s:9:"MoPifTrib";i:6768;s:15:"SubcaSuperUnbra";i:6769;s:7:"LilaNan";i:6770;s:10:"OrnisPresu";i:6771;s:7:"AndreSo";i:6772;s:5:"Sloug";i:6773;s:9:"HoOxyheSc";i:6774;s:13:"DermLawlMetac";i:6775;s:13:"DirTwelvVirgi";i:6776;s:11:"NuttRatapVi";i:6777;s:4:"Teet";i:6778;s:9:"GardeScWr";i:6779;s:6:"PaPlUn";i:6780;s:8:"PhrUrrad";i:6781;s:4:"OrTh";i:6782;s:10:"LenUnUtopi";i:6783;s:7:"FaOssZi";i:6784;s:9:"NonmTexas";i:6785;s:8:"SuTricUn";i:6786;s:11:"NaOrchiRive";i:6787;s:6:"KleUnp";i:6788;s:6:"PhilUn";i:6789;s:4:"Cade";i:6790;s:13:"MacanOvercUpc";i:6791;s:8:"PinnSpha";i:6792;s:11:"IcMegalPara";i:6793;s:5:"NonTi";i:6794;s:10:"PienxVaria";i:6795;s:9:"EncoNoYal";i:6796;s:4:"Uptr";i:6797;s:8:"AnconSyc";i:6798;s:9:"FeIntoSta";i:6799;s:7:"RelShif";i:6800;s:7:"ExonePr";i:6801;s:6:"PuThio";i:6802;s:6:"HesiKa";i:6803;s:5:"PanTy";i:6804;s:11:"MacuMiRomag";i:6805;s:12:"LearnNotaSti";i:6806;s:4:"Raki";i:6807;s:8:"OrthUnfl";i:6808;s:10:"KurvePhySp";i:6809;s:7:"HisMump";i:6810;s:9:"PanxTreen";i:6811;s:8:"IndifUnc";i:6812;s:5:"ByMus";i:6813;s:11:"OuSuspUnplu";i:6814;s:12:"IsoLoundOutp";i:6815;s:15:"HexaxSinuaUntro";i:6816;s:8:"SiccUnde";i:6817;s:7:"CaprSep";i:6818;s:8:"RetroTet";i:6819;s:9:"InteQuSem";i:6820;s:5:"Imper";i:6821;s:3:"Psy";i:6822;s:12:"GnarLogTogsx";i:6823;s:4:"Unpa";i:6824;s:7:"HorseRe";i:6825;s:12:"PaUndisUndre";i:6826;s:3:"Lus";i:6827;s:2:"Cu";i:6828;s:3:"Sip";i:6829;s:3:"Tom";i:6830;s:7:"MicRask";i:6831;s:4:"Pect";i:6832;s:12:"ThesTrichVir";i:6833;s:5:"Unwas";i:6834;s:5:"Naple";i:6835;s:9:"MegPrTumo";i:6836;s:11:"HastaPeteSu";i:6837;s:4:"Unrh";i:6838;s:7:"PolyTol";i:6839;s:13:"DisaOrnitPlat";i:6840;s:3:"Oes";i:6841;s:5:"Puboi";i:6842;s:5:"Polyt";i:6843;s:11:"NonmPsTetra";i:6844;s:4:"Uniu";i:6845;s:11:"FasPreRamis";i:6846;s:5:"Stylo";i:6847;s:13:"PonciScyUnloc";i:6848;s:9:"MiaoRecol";i:6849;s:11:"SleSuperVig";i:6850;s:6:"RampRe";i:6851;s:5:"Hongx";i:6852;s:9:"ReticUnpo";i:6853;s:9:"LactiTrip";i:6854;s:7:"PolStTr";i:6855;s:10:"AxonoGraci";i:6856;s:12:"LinSuddeVerm";i:6857;s:9:"CataSuste";i:6858;s:7:"GiRussi";i:6859;s:8:"SextoSla";i:6860;s:5:"Suthe";i:6861;s:6:"PreSha";i:6862;s:10:"MoisRaSpir";i:6863;s:5:"Herco";i:6864;s:9:"TepaWalac";i:6865;s:5:"Tempe";i:6866;s:7:"RaisiUn";i:6867;s:9:"PremUnwif";i:6868;s:6:"MacSta";i:6869;s:12:"EphahPaurUnd";i:6870;s:9:"ParafSurv";i:6871;s:10:"FiRelatUns";i:6872;s:10:"OverdSinto";i:6873;s:5:"NoPup";i:6874;s:10:"CounForPty";i:6875;s:6:"MethVi";i:6876;s:11:"DecayKnocPu";i:6877;s:4:"Xena";i:6878;s:8:"ReRetrUn";i:6879;s:5:"KlTwi";i:6880;s:14:"BeechSubpUnmea";i:6881;s:5:"Unpro";i:6882;s:4:"PrSe";i:6883;s:6:"ReStUn";i:6884;s:8:"FeruOcra";i:6885;s:8:"HomTetry";i:6886;s:5:"IneNa";i:6887;s:7:"IndiPro";i:6888;s:11:"GinGuppScle";i:6889;s:9:"FellLight";i:6890;s:6:"LymPro";i:6891;s:5:"HaUnd";i:6892;s:12:"DaboiIntPant";i:6893;s:11:"CyaJangPann";i:6894;s:8:"PulStink";i:6895;s:12:"BolDevicOpis";i:6896;s:8:"HyTempWh";i:6897;s:7:"CheerSe";i:6898;s:5:"OmPyo";i:6899;s:3:"Tit";i:6900;s:7:"RetiSow";i:6901;s:8:"SemShall";i:6902;s:9:"SecStroVa";i:6903;s:10:"PieSemiSub";i:6904;s:4:"Conv";i:6905;s:10:"PiemaUndex";i:6906;s:11:"BreFrankUng";i:6907;s:9:"OkiTenUnw";i:6908;s:6:"PoStav";i:6909;s:2:"Zi";i:6910;s:11:"DiarrDoWind";i:6911;s:8:"AlicPara";i:6912;s:8:"SarcSnuf";i:6913;s:5:"Poetr";i:6914;s:5:"Nonma";i:6915;s:12:"BathMegUnple";i:6916;s:8:"PoSatUnd";i:6917;s:4:"Exor";i:6918;s:5:"Megar";i:6919;s:7:"HyIndMe";i:6920;s:8:"DuPhUnsp";i:6921;s:7:"SugViki";i:6922;s:9:"PreSquUns";i:6923;s:5:"Tiger";i:6924;s:2:"Dy";i:6925;s:8:"DebaRoun";i:6926;s:10:"PrecTicTra";i:6927;s:5:"Myoca";i:6928;s:9:"PinaSundr";i:6929;s:12:"NonioSonoTes";i:6930;s:8:"PersTone";i:6931;s:7:"PloPrel";i:6932;s:13:"EyepNorthVisc";i:6933;s:7:"RatiRou";i:6934;s:5:"Place";i:6935;s:5:"Proch";i:6936;s:9:"AppRepSto";i:6937;s:6:"OnlySe";i:6938;s:8:"DieguOsk";i:6939;s:11:"MatteOptaRa";i:6940;s:12:"SqTricuUncon";i:6941;s:12:"BreaDolpTrea";i:6942;s:10:"SerabUnlec";i:6943;s:4:"TaUn";i:6944;s:5:"HyPro";i:6945;s:12:"HeteIrraLupi";i:6946;s:6:"OverPa";i:6947;s:4:"Pili";i:6948;s:7:"MediVen";i:6949;s:7:"StUnple";i:6950;s:4:"Toxi";i:6951;s:4:"Medi";i:6952;s:11:"RadiiSoTouc";i:6953;s:8:"PeriPhan";i:6954;s:11:"EpopHeadsSh";i:6955;s:2:"Ka";i:6956;s:6:"IchSpe";i:6957;s:6:"ChTeta";i:6958;s:9:"LateSemih";i:6959;s:4:"Lutr";i:6960;s:10:"BedebHysMa";i:6961;s:8:"SardShru";i:6962;s:10:"CoreDePain";i:6963;s:3:"Tsa";i:6964;s:2:"De";i:6965;s:12:"MotRhinoVoll";i:6966;s:5:"Prefa";i:6967;s:3:"Est";i:6968;s:10:"FitOverdUn";i:6969;s:11:"PontaQuaSem";i:6970;s:8:"ForMaMis";i:6971;s:11:"PlumlPraeZi";i:6972;s:4:"Pseu";i:6973;s:8:"PuritTru";i:6974;s:12:"EquSpirWokex";i:6975;s:8:"SleepUnt";i:6976;s:5:"MidPo";i:6977;s:6:"ProtRu";i:6978;s:6:"ProgRe";i:6979;s:12:"PleniRunoUnu";i:6980;s:8:"BorJeann";i:6981;s:4:"Unwo";i:6982;s:8:"PamphSho";i:6983;s:8:"IneSkirt";i:6984;s:10:"PeRyukTrag";i:6985;s:9:"EnnoRonVa";i:6986;s:14:"MastySikarTrir";i:6987;s:3:"Tem";i:6988;s:15:"MozamRepliUncon";i:6989;s:8:"HecKoUnd";i:6990;s:8:"TellxThi";i:6991;s:7:"PseRadi";i:6992;s:8:"MissTrac";i:6993;s:7:"SlTrust";i:6994;s:8:"MacrSpUn";i:6995;s:9:"DaisyGoWh";i:6996;s:7:"PeriSor";i:6997;s:13:"HyposInteOver";i:6998;s:4:"Maza";i:6999;s:11:"QuSubscWeez";i:7000;s:6:"TruUre";i:7001;s:9:"PamlRecus";i:7002;s:10:"CycliStren";i:7003;s:5:"Serpi";i:7004;s:3:"Top";i:7005;s:3:"Moo";i:7006;s:7:"KneMaSe";i:7007;s:7:"IsatPol";i:7008;s:11:"ErytFattPeu";i:7009;s:7:"OsteUnd";i:7010;s:5:"Bulba";i:7011;s:8:"PaSnVerb";i:7012;s:5:"Unsin";i:7013;s:7:"PseuRub";i:7014;s:5:"Exsan";i:7015;s:10:"InterMiPen";i:7016;s:5:"Pleur";i:7017;s:13:"HydrInterOver";i:7018;s:4:"Coag";i:7019;s:7:"SaSicar";i:7020;s:6:"MonPre";i:7021;s:8:"SaSumbWi";i:7022;s:7:"GePreci";i:7023;s:4:"Tymp";i:7024;s:9:"SinuoUbii";i:7025;s:5:"Overw";i:7026;s:7:"HeorUnp";i:7027;s:6:"NeisUn";i:7028;s:10:"FrKikaPrec";i:7029;s:3:"Obt";i:7030;s:11:"BowmCroMedi";i:7031;s:12:"GreenMasUnde";i:7032;s:10:"CathoComed";i:7033;s:8:"SmeStUna";i:7034;s:5:"Undig";i:7035;s:8:"MicProba";i:7036;s:5:"Unvai";i:7037;s:8:"PreStoTo";i:7038;s:6:"LibWar";i:7039;s:9:"SaccVampi";i:7040;s:9:"ScoSqWanl";i:7041;s:13:"IndiKiloZephy";i:7042;s:3:"Rah";i:7043;s:8:"UncoUnUn";i:7044;s:7:"SwartUn";i:7045;s:4:"Sord";i:7046;s:4:"Nasi";i:7047;s:4:"Phil";i:7048;s:4:"Suss";i:7049;s:9:"HomopRiSp";i:7050;s:3:"Sca";i:7051;s:2:"Bi";i:7052;s:5:"Unret";i:7053;s:5:"LocTo";i:7054;s:6:"RubUnd";i:7055;s:4:"Foca";i:7056;s:7:"PePreRu";i:7057;s:9:"BaptiUnpa";i:7058;s:5:"ScSig";i:7059;s:11:"EndGenevUns";i:7060;s:6:"LepSpi";i:7061;s:4:"Vorl";i:7062;s:4:"Thro";i:7063;s:9:"FugHaHete";i:7064;s:8:"InteNote";i:7065;s:6:"PapUnd";i:7066;s:5:"Lands";i:7067;s:11:"FluPseuThel";i:7068;s:5:"Letha";i:7069;s:8:"GomaRidi";i:7070;s:9:"CourtMadd";i:7071;s:9:"NovePenSh";i:7072;s:2:"Ut";i:7073;s:4:"Vatm";i:7074;s:4:"Taxi";i:7075;s:7:"QuaveSp";i:7076;s:9:"MonstPred";i:7077;s:11:"PedTriUntoi";i:7078;s:11:"PlaUpteVivi";i:7079;s:8:"SurrTabl";i:7080;s:4:"Imid";i:7081;s:6:"NonZoo";i:7082;s:9:"PalaTrich";i:7083;s:5:"Thecl";i:7084;s:13:"OphPaleoUnout";i:7085;s:12:"DrumNebQuila";i:7086;s:11:"EpMiniProag";i:7087;s:13:"MuleRotuTriac";i:7088;s:5:"Ostra";i:7089;s:5:"Endoc";i:7090;s:12:"HydrVaiYouwa";i:7091;s:5:"RevSe";i:7092;s:8:"DermSigh";i:7093;s:5:"Splat";i:7094;s:5:"MerUn";i:7095;s:8:"KrisPaPr";i:7096;s:9:"LodgMaPol";i:7097;s:9:"FlMenoVul";i:7098;s:8:"MyelaRab";i:7099;s:5:"Commo";i:7100;s:10:"HaPhyTorcx";i:7101;s:10:"NomiReUnfo";i:7102;s:5:"Unwal";i:7103;s:10:"MucSiniTea";i:7104;s:9:"PsSolUnha";i:7105;s:7:"CraHyPr";i:7106;s:12:"IntRheuSerop";i:7107;s:12:"ManWindYouth";i:7108;s:8:"NotoSemi";i:7109;s:11:"MaPhytPosts";i:7110;s:8:"DeaPeWal";i:7111;s:12:"NotaPrimaSou";i:7112;s:4:"Sera";i:7113;s:7:"PaSaUnt";i:7114;s:6:"SimSup";i:7115;s:9:"MalpPhoto";i:7116;s:11:"AsbConiPred";i:7117;s:10:"MetatSpoon";i:7118;s:3:"Hol";i:7119;s:8:"DilLoTap";i:7120;s:4:"OuSe";i:7121;s:4:"SeYe";i:7122;s:7:"SulpUns";i:7123;s:7:"FliQuak";i:7124;s:8:"OphtRecu";i:7125;s:6:"CheNon";i:7126;s:12:"PandSarcoUns";i:7127;s:9:"MeMuleOxy";i:7128;s:10:"CerGarisMe";i:7129;s:9:"BoDuskPer";i:7130;s:4:"Moos";i:7131;s:13:"LiberPeaRolle";i:7132;s:4:"Unhi";i:7133;s:9:"ReadUnroo";i:7134;s:6:"BeaUnu";i:7135;s:5:"Taget";i:7136;s:8:"CellExPr";i:7137;s:7:"PaoScSu";i:7138;s:11:"DonHaeSubro";i:7139;s:3:"Gun";i:7140;s:8:"HePyraSu";i:7141;s:13:"AscaErythLett";i:7142;s:10:"HypocZoonu";i:7143;s:7:"SpewiUn";i:7144;s:5:"Sylva";i:7145;s:8:"MesSteno";i:7146;s:9:"RhomTiger";i:7147;s:4:"Rumm";i:7148;s:2:"Hu";i:7149;s:9:"DosGeopRe";i:7150;s:5:"GlWot";i:7151;s:7:"PaUnUno";i:7152;s:8:"CapeYenx";i:7153;s:10:"TouchTrUnf";i:7154;s:11:"EnaPreRigwi";i:7155;s:9:"TyphlUnUn";i:7156;s:12:"MecoMuddSpea";i:7157;s:4:"Myel";i:7158;s:10:"ExcoReiSej";i:7159;s:11:"ConjMotorZy";i:7160;s:6:"JoinUn";i:7161;s:10:"ExudSympUn";i:7162;s:10:"DisHorseSp";i:7163;s:8:"PreTherm";i:7164;s:7:"SupeTuc";i:7165;s:9:"CaDemSame";i:7166;s:5:"Zenag";i:7167;s:3:"Rug";i:7168;s:7:"RatioZo";i:7169;s:12:"InconPrQuidd";i:7170;s:8:"DevoiPal";i:7171;s:11:"CaIndaTourn";i:7172;s:5:"Infer";i:7173;s:9:"HydroPrec";i:7174;s:4:"Yout";i:7175;s:3:"Hak";i:7176;s:7:"UntZeph";i:7177;s:4:"Peed";i:7178;s:8:"MetPiTuc";i:7179;s:11:"LiNonsaOsci";i:7180;s:3:"Hea";i:7181;s:11:"PiprPostSen";i:7182;s:10:"MiSniUnmet";i:7183;s:6:"CaiExt";i:7184;s:6:"UnfWom";i:7185;s:7:"NonfUnw";i:7186;s:6:"NajNon";i:7187;s:10:"PaProTruck";i:7188;s:10:"RemoSemiSt";i:7189;s:8:"PerUnsym";i:7190;s:5:"Xyrid";i:7191;s:7:"AnaChun";i:7192;s:7:"HaLepro";i:7193;s:5:"Veuve";i:7194;s:14:"NoaxOverpWanto";i:7195;s:4:"Lith";i:7196;s:7:"FlaTara";i:7197;s:7:"PsychTe";i:7198;s:3:"Fra";i:7199;s:5:"Sitti";i:7200;s:13:"SnottTroTwitt";i:7201;s:9:"LampWreck";i:7202;s:8:"SuperVar";i:7203;s:7:"NondSym";i:7204;s:7:"FeetaSo";i:7205;s:9:"GrafNonfi";i:7206;s:2:"Od";i:7207;s:5:"Butan";i:7208;s:9:"LippLiPhy";i:7209;s:5:"ParPr";i:7210;s:7:"ScuUnUs";i:7211;s:6:"InPsUn";i:7212;s:7:"PipSaye";i:7213;s:9:"MatcStirr";i:7214;s:13:"ParrTenuUnpla";i:7215;s:7:"DurEnMe";i:7216;s:5:"PoPor";i:7217;s:11:"CriCutSauce";i:7218;s:10:"StViceWeax";i:7219;s:9:"FingNeuri";i:7220;s:6:"SubVir";i:7221;s:7:"TrWildi";i:7222;s:6:"ReSeUn";i:7223;s:10:"IsoPuSandl";i:7224;s:6:"SenSpl";i:7225;s:12:"LabryPrUnfel";i:7226;s:10:"LingaOpist";i:7227;s:10:"HydPeUninq";i:7228;s:15:"CloudObeyeUnder";i:7229;s:4:"Post";i:7230;s:7:"CrPhoto";i:7231;s:10:"MoPredePre";i:7232;s:12:"CardSchisWhi";i:7233;s:11:"JapKolVenom";i:7234;s:4:"Unfl";i:7235;s:8:"PhacVers";i:7236;s:3:"Lab";i:7237;s:3:"Jig";i:7238;s:10:"CorybIrrem";i:7239;s:9:"TaguUnimp";i:7240;s:5:"Whipp";i:7241;s:4:"Stoc";i:7242;s:13:"ConveOwregShi";i:7243;s:10:"BromoCaFor";i:7244;s:9:"MoStahlTy";i:7245;s:14:"IndigLoriStaye";i:7246;s:10:"ElProeUnsu";i:7247;s:8:"PanUnrid";i:7248;s:5:"Prehe";i:7249;s:3:"Leg";i:7250;s:9:"BotryPrem";i:7251;s:5:"UnWed";i:7252;s:7:"CraPudd";i:7253;s:12:"HainSnonSulf";i:7254;s:12:"PerSouthUnpo";i:7255;s:8:"ElGrafTh";i:7256;s:8:"CrassWil";i:7257;s:9:"ConNonOly";i:7258;s:5:"Schni";i:7259;s:9:"TertZygom";i:7260;s:11:"ScrVacYawwe";i:7261;s:10:"ButDisceTr";i:7262;s:11:"ChoriEcarVe";i:7263;s:4:"Femi";i:7264;s:7:"NeUnpos";i:7265;s:13:"MurPerveRunol";i:7266;s:5:"PrSig";i:7267;s:10:"EmbMidmTro";i:7268;s:8:"CestInca";i:7269;s:5:"CorPy";i:7270;s:12:"BylSpoonTymp";i:7271;s:9:"NotiSlunk";i:7272;s:5:"Whitr";i:7273;s:4:"Unba";i:7274;s:10:"MonatTheor";i:7275;s:14:"IdioShoalTamac";i:7276;s:7:"PartiTo";i:7277;s:11:"CounCraMill";i:7278;s:4:"Uppe";i:7279;s:9:"PatavPewt";i:7280;s:4:"CoSa";i:7281;s:4:"Vase";i:7282;s:6:"ColcNo";i:7283;s:14:"ThrifTransWate";i:7284;s:10:"NoTacTartu";i:7285;s:12:"ReiUnforUngl";i:7286;s:8:"CrExpaEx";i:7287;s:5:"ParPh";i:7288;s:6:"SkylUn";i:7289;s:5:"StaUn";i:7290;s:10:"FibuSlTric";i:7291;s:10:"KartvMagne";i:7292;s:13:"BotcCatstSenc";i:7293;s:7:"EnduHem";i:7294;s:12:"MammaStrUnfo";i:7295;s:9:"SlidSpUro";i:7296;s:13:"PatSnabbVolat";i:7297;s:5:"Mysti";i:7298;s:4:"OsSc";i:7299;s:5:"Trick";i:7300;s:5:"Cohen";i:7301;s:12:"HypMoralTinh";i:7302;s:10:"PettySacri";i:7303;s:8:"StrowUlt";i:7304;s:5:"Circu";i:7305;s:4:"Rifl";i:7306;s:13:"FlocxLaxUnacc";i:7307;s:10:"HirsMyodPe";i:7308;s:5:"LiMic";i:7309;s:9:"MesRainWa";i:7310;s:8:"BuntiSyp";i:7311;s:12:"AgroConveOrt";i:7312;s:4:"Rais";i:7313;s:8:"NeSemiTr";i:7314;s:7:"CrGenPe";i:7315;s:7:"RecUnch";i:7316;s:10:"LatchPosse";i:7317;s:9:"BavenWeal";i:7318;s:5:"NonSp";i:7319;s:6:"MontVo";i:7320;s:15:"MisfaNarcaNonto";i:7321;s:5:"DeTuc";i:7322;s:8:"CountEuc";i:7323;s:10:"HaiIliocUn";i:7324;s:10:"CoaptLumPa";i:7325;s:5:"Thoms";i:7326;s:5:"TaTop";i:7327;s:8:"PhalaVib";i:7328;s:6:"BoraKa";i:7329;s:12:"TassTempeVir";i:7330;s:8:"MonosNam";i:7331;s:10:"SpiTethThe";i:7332;s:13:"QuerUnchiVare";i:7333;s:6:"BipNon";i:7334;s:9:"JaPolyoSi";i:7335;s:12:"AmbiEntGloam";i:7336;s:13:"MetalRegalSup";i:7337;s:10:"ErotiRetun";i:7338;s:12:"PyraSteprSyn";i:7339;s:9:"StabUngui";i:7340;s:5:"Pates";i:7341;s:12:"CaEnseeRunbo";i:7342;s:13:"NeurPteroSoma";i:7343;s:10:"RitarSquTa";i:7344;s:3:"Hum";i:7345;s:7:"PangePo";i:7346;s:5:"InOut";i:7347;s:11:"CoExtoUncan";i:7348;s:11:"HomoeNoctUn";i:7349;s:7:"BuPoRes";i:7350;s:8:"PhalRect";i:7351;s:3:"Mea";i:7352;s:13:"JoyleNotReplu";i:7353;s:4:"Nonh";i:7354;s:4:"Reco";i:7355;s:2:"Fu";i:7356;s:9:"OctSaSpic";i:7357;s:8:"NinUntes";i:7358;s:5:"NoSpl";i:7359;s:5:"OuSte";i:7360;s:9:"ResurStau";i:7361;s:14:"RevokSclerThan";i:7362;s:5:"Tombl";i:7363;s:10:"IsMarooNaz";i:7364;s:11:"OtotoSeriSo";i:7365;s:8:"CerePleu";i:7366;s:7:"ForaPse";i:7367;s:8:"CentTube";i:7368;s:14:"CebuEkamaSauce";i:7369;s:10:"GlenxHypog";i:7370;s:6:"HyOafx";i:7371;s:9:"RackWillo";i:7372;s:5:"LoOgh";i:7373;s:9:"WigfuWint";i:7374;s:14:"IndiOrthoProdu";i:7375;s:12:"ClCoconUnpre";i:7376;s:12:"ExpFlunkMega";i:7377;s:9:"ChorInsWo";i:7378;s:4:"Hair";i:7379;s:6:"UncWin";i:7380;s:7:"PaPsoro";i:7381;s:9:"TradUndit";i:7382;s:9:"MakeUnlik";i:7383;s:10:"AntiApMeta";i:7384;s:7:"PhoPhyt";i:7385;s:7:"ColMust";i:7386;s:10:"CathaConPh";i:7387;s:8:"NoTromVe";i:7388;s:3:"Cos";i:7389;s:10:"CapDunPres";i:7390;s:14:"HypanIncuLeopa";i:7391;s:6:"UnasWe";i:7392;s:5:"ExpUn";i:7393;s:5:"IntUn";i:7394;s:5:"Overf";i:7395;s:5:"Rameq";i:7396;s:9:"ImmLoxoUn";i:7397;s:5:"EmaGi";i:7398;s:13:"EsoEurasUnlab";i:7399;s:8:"LobaPrZo";i:7400;s:9:"ProaUnrYa";i:7401;s:8:"SubUnleg";i:7402;s:9:"ComaExNeg";i:7403;s:10:"HandwPrUnc";i:7404;s:3:"Bre";i:7405;s:7:"QuatrVo";i:7406;s:3:"Erm";i:7407;s:8:"ParThioc";i:7408;s:7:"NoctiTr";i:7409;s:7:"ElUncha";i:7410;s:11:"PitiRecStri";i:7411;s:4:"OtPa";i:7412;s:9:"GanoMyrrh";i:7413;s:4:"Redu";i:7414;s:8:"HuPifTon";i:7415;s:6:"NeUnmu";i:7416;s:5:"GeHus";i:7417;s:14:"DisafProvQuino";i:7418;s:11:"LoydQuReman";i:7419;s:3:"Sta";i:7420;s:12:"RefTeasUnchi";i:7421;s:6:"ClaTam";i:7422;s:5:"Disho";i:7423;s:11:"BaNowneRein";i:7424;s:7:"CoFocus";i:7425;s:6:"SemiWa";i:7426;s:14:"BottoObsceUltr";i:7427;s:4:"Shoa";i:7428;s:7:"MonoTri";i:7429;s:10:"LackQuadUn";i:7430;s:13:"SarcTetaTrump";i:7431;s:11:"UmbUntawVin";i:7432;s:14:"NewlSkewnUnpar";i:7433;s:4:"Podo";i:7434;s:3:"Wor";i:7435;s:12:"InterKeratMy";i:7436;s:11:"ResStrTesta";i:7437;s:5:"Unfat";i:7438;s:4:"Prof";i:7439;s:5:"Misco";i:7440;s:10:"ChurLimbUn";i:7441;s:5:"Rammi";i:7442;s:10:"CarCeleoSn";i:7443;s:9:"DysaSeven";i:7444;s:4:"Inno";i:7445;s:5:"PhoUn";i:7446;s:7:"QuiReUn";i:7447;s:14:"AlgarBrumEquiv";i:7448;s:4:"FiWo";i:7449;s:5:"Wuthe";i:7450;s:11:"MoguePorSpr";i:7451;s:7:"HiMarRe";i:7452;s:9:"UndeUnder";i:7453;s:13:"PinUndelUpspe";i:7454;s:9:"SomitSwSy";i:7455;s:7:"FeGaMed";i:7456;s:10:"ImmPowSore";i:7457;s:7:"DisUnop";i:7458;s:9:"LactOliUn";i:7459;s:5:"GroVe";i:7460;s:6:"StTele";i:7461;s:3:"Exc";i:7462;s:4:"MePa";i:7463;s:8:"SubcZaca";i:7464;s:9:"SyUnsWast";i:7465;s:2:"Gh";i:7466;s:9:"RecRobTum";i:7467;s:11:"DistPreUnre";i:7468;s:12:"EbHoerxMatro";i:7469;s:13:"BeeCorreGetae";i:7470;s:12:"LariOutflPro";i:7471;s:9:"InSmVocab";i:7472;s:5:"Hecta";i:7473;s:6:"IgnaTw";i:7474;s:5:"Seric";i:7475;s:8:"SaeSeiTi";i:7476;s:6:"OppPat";i:7477;s:5:"MoTha";i:7478;s:6:"NonvSk";i:7479;s:4:"Sarc";i:7480;s:5:"Autho";i:7481;s:13:"BestoStiUncla";i:7482;s:9:"CanToolUn";i:7483;s:9:"FornModio";i:7484;s:9:"RhapWartx";i:7485;s:10:"GynodImKra";i:7486;s:13:"FirepPreShinz";i:7487;s:12:"KroLucuMetem";i:7488;s:6:"ReUnsu";i:7489;s:5:"Rooib";i:7490;s:9:"ExpFiGout";i:7491;s:8:"EuTeYess";i:7492;s:11:"PluRecReins";i:7493;s:3:"Com";i:7494;s:5:"FooUn";i:7495;s:4:"Tort";i:7496;s:4:"Brac";i:7497;s:5:"EquTo";i:7498;s:9:"ExTrUnsmi";i:7499;s:4:"Lobe";i:7500;s:5:"GynSu";i:7501;s:10:"PondSxTric";i:7502;s:9:"FocaUnreq";i:7503;s:6:"IrrePr";i:7504;s:4:"Styt";i:7505;s:4:"FoPi";i:7506;s:6:"NaTenn";i:7507;s:5:"Bookb";i:7508;s:3:"Yet";i:7509;s:10:"MyxedSpinn";i:7510;s:9:"AweeNonco";i:7511;s:8:"PrenRecr";i:7512;s:7:"CoImmod";i:7513;s:8:"ApocEeMe";i:7514;s:11:"GiNilsxVisa";i:7515;s:4:"Taxe";i:7516;s:4:"Epip";i:7517;s:10:"ConduMeSub";i:7518;s:10:"CrouReVerm";i:7519;s:10:"OversWeath";i:7520;s:6:"ChMeso";i:7521;s:4:"Dipl";i:7522;s:6:"PlinTh";i:7523;s:7:"OrPreSu";i:7524;s:10:"HipPrRevis";i:7525;s:7:"SchSter";i:7526;s:8:"PlasPlum";i:7527;s:5:"UnWol";i:7528;s:7:"MightSp";i:7529;s:9:"PseuSynca";i:7530;s:7:"PsUnpur";i:7531;s:7:"LynxxMa";i:7532;s:4:"Clun";i:7533;s:12:"OverPhlResil";i:7534;s:12:"MetSatiStabl";i:7535;s:5:"Scien";i:7536;s:3:"Tee";i:7537;s:6:"NoVago";i:7538;s:8:"HomMortg";i:7539;s:4:"Stic";i:7540;s:9:"CaCongrUn";i:7541;s:4:"Whar";i:7542;s:9:"OverQuadr";i:7543;s:8:"MisPlata";i:7544;s:9:"CaiSquUnh";i:7545;s:10:"TenUncouUn";i:7546;s:7:"OutTrom";i:7547;s:3:"Lec";i:7548;s:7:"MoSpili";i:7549;s:6:"CaUnab";i:7550;s:10:"MuddPsUnwa";i:7551;s:12:"CoseExpUncon";i:7552;s:5:"Verde";i:7553;s:7:"CoMoWes";i:7554;s:7:"SimuSli";i:7555;s:9:"MylodTran";i:7556;s:7:"CymbSub";i:7557;s:10:"ImThyUnfor";i:7558;s:4:"Stev";i:7559;s:7:"HoploTa";i:7560;s:7:"CoRabix";i:7561;s:7:"OdoTurk";i:7562;s:3:"Fre";i:7563;s:7:"JoKorex";i:7564;s:10:"CofNoneUnc";i:7565;s:3:"Ery";i:7566;s:7:"MusResu";i:7567;s:5:"Valid";i:7568;s:7:"OncoShi";i:7569;s:11:"FoKibbUndim";i:7570;s:3:"Tim";i:7571;s:8:"MortThWi";i:7572;s:13:"ReaalUnsedUnt";i:7573;s:4:"Degl";i:7574;s:9:"MiniOutPe";i:7575;s:13:"HelioHyetMemo";i:7576;s:11:"InvePrediSl";i:7577;s:7:"GhostHe";i:7578;s:10:"RevUncUntr";i:7579;s:5:"Bemai";i:7580;s:7:"TrikeWh";i:7581;s:8:"TelTikix";i:7582;s:9:"MaliUncir";i:7583;s:12:"ForksHepTher";i:7584;s:7:"HypNond";i:7585;s:5:"Wintr";i:7586;s:7:"JeeSubm";i:7587;s:9:"InadeOrUn";i:7588;s:11:"MilnOversTr";i:7589;s:4:"Thal";i:7590;s:3:"Duk";i:7591;s:7:"SleUpla";i:7592;s:2:"Ha";i:7593;s:5:"PeUnm";i:7594;s:9:"DeHipOryz";i:7595;s:4:"PhTe";i:7596;s:8:"UnpZygos";i:7597;s:11:"LovRefiUnco";i:7598;s:6:"DwHodd";i:7599;s:3:"Phi";i:7600;s:7:"DonatSu";i:7601;s:4:"Gran";i:7602;s:8:"LaboTurr";i:7603;s:5:"Geode";i:7604;s:3:"Gle";i:7605;s:5:"Hebra";i:7606;s:4:"Crot";i:7607;s:10:"BloPahSupe";i:7608;s:8:"PostmSer";i:7609;s:6:"IncoUn";i:7610;s:4:"Rere";i:7611;s:11:"ObstTetraUn";i:7612;s:13:"ForehSanieUnc";i:7613;s:6:"SpToma";i:7614;s:5:"Triha";i:7615;s:13:"GallProceWeis";i:7616;s:10:"NonpePolys";i:7617;s:8:"PartrPol";i:7618;s:5:"Volle";i:7619;s:7:"BockPle";i:7620;s:9:"DeDichWel";i:7621;s:10:"IncIndTher";i:7622;s:5:"Minor";i:7623;s:5:"Shado";i:7624;s:6:"ShSlav";i:7625;s:12:"MazalVelVera";i:7626;s:12:"EmargSaUnapp";i:7627;s:11:"OveTussoUnc";i:7628;s:13:"NovaToniUnfor";i:7629;s:12:"FormOnaOutba";i:7630;s:7:"LouvReg";i:7631;s:8:"SerShZau";i:7632;s:10:"EbullIncav";i:7633;s:12:"SmirkStryUna";i:7634;s:7:"StraiUn";i:7635;s:4:"Besn";i:7636;s:6:"DuchIm";i:7637;s:4:"Cowp";i:7638;s:8:"GermJuMi";i:7639;s:11:"BurdHydProm";i:7640;s:9:"PeSeTimbe";i:7641;s:3:"Cac";i:7642;s:6:"MortUn";i:7643;s:5:"Straw";i:7644;s:10:"ExterProph";i:7645;s:11:"RetroSwTele";i:7646;s:6:"ThiUnf";i:7647;s:4:"Unsk";i:7648;s:9:"AnnodHoIl";i:7649;s:5:"ErgEt";i:7650;s:13:"JoviPreabSnee";i:7651;s:5:"Gentl";i:7652;s:3:"Bet";i:7653;s:12:"NuPebaxTitan";i:7654;s:4:"Tato";i:7655;s:7:"SulphUn";i:7656;s:5:"DubSt";i:7657;s:9:"PlowwUnmi";i:7658;s:9:"PaPhotPis";i:7659;s:5:"Subex";i:7660;s:11:"OptoProSubc";i:7661;s:4:"Rout";i:7662;s:4:"Vapo";i:7663;s:9:"HyMidriSy";i:7664;s:7:"NulliWi";i:7665;s:9:"FerStoUnt";i:7666;s:7:"LusSaun";i:7667;s:7:"SterUnc";i:7668;s:5:"BlOst";i:7669;s:7:"InMicro";i:7670;s:5:"Induc";i:7671;s:4:"Tope";i:7672;s:4:"Disi";i:7673;s:5:"SuUte";i:7674;s:7:"CohMiRe";i:7675;s:11:"AmpuScroWin";i:7676;s:10:"MilToUncit";i:7677;s:12:"CenTerrVesic";i:7678;s:8:"QuixUnhu";i:7679;s:10:"SupeTifTub";i:7680;s:4:"Tens";i:7681;s:4:"SeSy";i:7682;s:5:"Precu";i:7683;s:4:"LiSe";i:7684;s:5:"HerLe";i:7685;s:8:"PapyPhot";i:7686;s:9:"ShantSooh";i:7687;s:10:"TradeVicto";i:7688;s:10:"GuardHiYex";i:7689;s:7:"AlkSpra";i:7690;s:4:"EmSt";i:7691;s:8:"HalosPor";i:7692;s:12:"GrotReasVerb";i:7693;s:13:"InchoNonprPor";i:7694;s:8:"LievLubr";i:7695;s:8:"PhalWith";i:7696;s:7:"HemerLu";i:7697;s:9:"HaMoldUnr";i:7698;s:8:"SpanUnwi";i:7699;s:9:"ConnStTri";i:7700;s:7:"HypSuki";i:7701;s:4:"None";i:7702;s:14:"OverPartuUncal";i:7703;s:13:"JessSperSpide";i:7704;s:11:"ReSynodUpba";i:7705;s:4:"Unsq";i:7706;s:10:"MystaReint";i:7707;s:8:"SubcTeno";i:7708;s:7:"ImpaSom";i:7709;s:10:"PsychUncom";i:7710;s:8:"OverProc";i:7711;s:8:"OvePrRec";i:7712;s:6:"DaiPri";i:7713;s:9:"HeavThUnp";i:7714;s:9:"MesolUntr";i:7715;s:11:"SulUncUncoq";i:7716;s:7:"OvileUn";i:7717;s:10:"EnthuSiale";i:7718;s:10:"ErythOrYen";i:7719;s:6:"DeUnVa";i:7720;s:14:"SemidUnpitVege";i:7721;s:8:"CannWhee";i:7722;s:8:"FratPhRe";i:7723;s:7:"MuSulph";i:7724;s:9:"GhaNudiSl";i:7725;s:7:"SomSuTr";i:7726;s:5:"Nomen";i:7727;s:2:"Be";i:7728;s:7:"LabPreb";i:7729;s:6:"UnqUns";i:7730;s:10:"IntraVespe";i:7731;s:8:"HoUncVot";i:7732;s:8:"GroinSto";i:7733;s:5:"Cooke";i:7734;s:12:"OligoScarVol";i:7735;s:12:"CarGoldUndel";i:7736;s:9:"ReceSqUnd";i:7737;s:7:"GaliJam";i:7738;s:3:"Ole";i:7739;s:7:"LaSilve";i:7740;s:3:"Pne";i:7741;s:3:"Sig";i:7742;s:12:"HugPseudVert";i:7743;s:12:"IleocPolVera";i:7744;s:6:"PrShak";i:7745;s:5:"HyRie";i:7746;s:4:"Swee";i:7747;s:12:"PetThelWinet";i:7748;s:11:"DongoHeReci";i:7749;s:9:"MonoPaZyg";i:7750;s:8:"FrowzGap";i:7751;s:6:"GuInte";i:7752;s:4:"File";i:7753;s:6:"AsinHy";i:7754;s:8:"StotUnfi";i:7755;s:11:"RetTetUname";i:7756;s:13:"BiriCedarQuir";i:7757;s:6:"LuPlWe";i:7758;s:6:"IndSty";i:7759;s:7:"ForePlo";i:7760;s:8:"IntOvipo";i:7761;s:13:"TubuUncomUpdr";i:7762;s:9:"BrLilOver";i:7763;s:3:"Kow";i:7764;s:3:"Nut";i:7765;s:3:"Yuk";i:7766;s:11:"PePhoenVege";i:7767;s:8:"PerceUns";i:7768;s:13:"TalloUnqVesic";i:7769;s:4:"Elen";i:7770;s:13:"GriSymmeUsuri";i:7771;s:4:"Esqu";i:7772;s:5:"PaPea";i:7773;s:6:"EtheTo";i:7774;s:7:"PolyPse";i:7775;s:5:"FibUr";i:7776;s:9:"PassiReSo";i:7777;s:14:"EspiGnomoPerch";i:7778;s:9:"InePoUnsp";i:7779;s:10:"SmitSquaTe";i:7780;s:5:"Trunc";i:7781;s:6:"ChClum";i:7782;s:4:"Pear";i:7783;s:8:"BromoCic";i:7784;s:8:"ExItPayr";i:7785;s:6:"GaSulb";i:7786;s:15:"DeindDiscoWoodr";i:7787;s:4:"Sple";i:7788;s:7:"UndreVi";i:7789;s:12:"ImpPerobStur";i:7790;s:5:"Moder";i:7791;s:3:"Det";i:7792;s:8:"SubUndec";i:7793;s:9:"ProphTris";i:7794;s:10:"PlPreSedul";i:7795;s:9:"PedaUlotr";i:7796;s:6:"GeGing";i:7797;s:9:"BugfChrom";i:7798;s:8:"IntPerso";i:7799;s:10:"SicexSplay";i:7800;s:3:"Ces";i:7801;s:12:"PusSelenSten";i:7802;s:11:"NurseReWars";i:7803;s:9:"CoNaUnbir";i:7804;s:5:"Sospi";i:7805;s:7:"CirGeol";i:7806;s:9:"SesquUnpr";i:7807;s:13:"BioLaticPsora";i:7808;s:10:"IntOstarPe";i:7809;s:5:"PiTwa";i:7810;s:5:"Pleom";i:7811;s:13:"PerinProSasse";i:7812;s:10:"GuanUnsWat";i:7813;s:7:"RekiUnt";i:7814;s:7:"CarPoly";i:7815;s:13:"BiangImprLeti";i:7816;s:9:"ObUnapVeg";i:7817;s:13:"OxanaRasSulph";i:7818;s:11:"InidoShuVis";i:7819;s:7:"SciUnha";i:7820;s:5:"Nonpr";i:7821;s:5:"Sandm";i:7822;s:10:"PolygTwUnc";i:7823;s:7:"GenScTa";i:7824;s:8:"PreSuVin";i:7825;s:11:"MeShekUpren";i:7826;s:6:"PaPicr";i:7827;s:3:"Oph";i:7828;s:9:"CracPeria";i:7829;s:9:"DiscDownl";i:7830;s:5:"Trias";i:7831;s:13:"SecSkullVeris";i:7832;s:9:"FructTher";i:7833;s:6:"DiaZea";i:7834;s:4:"Tren";i:7835;s:14:"LudgaMastProge";i:7836;s:8:"ExteMock";i:7837;s:3:"Vir";i:7838;s:5:"RegSt";i:7839;s:7:"OccuUdx";i:7840;s:10:"GrNeriUnha";i:7841;s:10:"SpiraTopic";i:7842;s:5:"Oreas";i:7843;s:5:"Unmou";i:7844;s:3:"Wes";i:7845;s:2:"Ec";i:7846;s:5:"Probo";i:7847;s:9:"DiscoGano";i:7848;s:8:"NonaSmok";i:7849;s:8:"RamTraUn";i:7850;s:9:"LameMusic";i:7851;s:8:"CowPopes";i:7852;s:12:"PorcStrUnbio";i:7853;s:4:"Rash";i:7854;s:11:"FielInteWes";i:7855;s:4:"Anth";i:7856;s:9:"CeCubiSaf";i:7857;s:7:"PeaPoRo";i:7858;s:7:"PrPyram";i:7859;s:7:"DiseaDo";i:7860;s:10:"HeterPeSac";i:7861;s:9:"FixiLaSla";i:7862;s:8:"HuaProSa";i:7863;s:13:"JuglJustTrivi";i:7864;s:5:"Selac";i:7865;s:7:"HaWacke";i:7866;s:3:"Pas";i:7867;s:12:"DeInaniPolyt";i:7868;s:9:"UncomUnim";i:7869;s:5:"Slaug";i:7870;s:8:"UnbloUnc";i:7871;s:6:"NonPho";i:7872;s:6:"ProfTh";i:7873;s:11:"EpictJuriPe";i:7874;s:7:"MerPhyt";i:7875;s:12:"PhiloRoVirgi";i:7876;s:12:"SpVasolWhiff";i:7877;s:7:"NonloPa";i:7878;s:7:"AbFaUnm";i:7879;s:5:"Tetar";i:7880;s:8:"ReideUre";i:7881;s:9:"EsoPrevRe";i:7882;s:5:"Plymo";i:7883;s:10:"HandlNaTra";i:7884;s:12:"DisbGlumVipe";i:7885;s:9:"OppreWhea";i:7886;s:5:"GueKo";i:7887;s:8:"MonuNond";i:7888;s:7:"SalUnpi";i:7889;s:4:"Syne";i:7890;s:14:"SluitWeisWhatk";i:7891;s:10:"GinwOaPerg";i:7892;s:11:"PleurRaUnde";i:7893;s:10:"MetMetPtil";i:7894;s:11:"OchPenSuper";i:7895;s:3:"Jar";i:7896;s:9:"PreciSond";i:7897;s:3:"Des";i:7898;s:9:"HeKenoNon";i:7899;s:13:"ConneNonspOdy";i:7900;s:9:"AspBliIna";i:7901;s:9:"JansePhPi";i:7902;s:6:"RhTamm";i:7903;s:9:"PoetTucka";i:7904;s:6:"TricZo";i:7905;s:4:"Furd";i:7906;s:5:"Squee";i:7907;s:13:"HippoSteTrigo";i:7908;s:14:"CombiInseLoine";i:7909;s:12:"HaemImprQuav";i:7910;s:4:"Unad";i:7911;s:4:"Uros";i:7912;s:4:"Palp";i:7913;s:4:"ReUl";i:7914;s:6:"ExcPea";i:7915;s:6:"PodTri";i:7916;s:14:"QuibbRobiUntem";i:7917;s:5:"Mothe";i:7918;s:12:"MansThorWald";i:7919;s:6:"HearUr";i:7920;s:13:"DarkPalaeUnar";i:7921;s:10:"SpeTeUntra";i:7922;s:5:"Ineff";i:7923;s:9:"LazexMisi";i:7924;s:7:"HarPseu";i:7925;s:13:"IntSalesVeloc";i:7926;s:5:"Halop";i:7927;s:7:"HyPyroc";i:7928;s:11:"HymeJeProdu";i:7929;s:8:"PseSinga";i:7930;s:7:"UtopXan";i:7931;s:5:"OvPro";i:7932;s:11:"SalUnsWoman";i:7933;s:10:"PyrTesToxo";i:7934;s:5:"GunPr";i:7935;s:7:"OveraUn";i:7936;s:12:"LaPacktWease";i:7937;s:5:"Lepto";i:7938;s:6:"MeStTr";i:7939;s:7:"OwlVerm";i:7940;s:4:"Spad";i:7941;s:4:"Unap";i:7942;s:12:"CulgeDideMan";i:7943;s:8:"PoPreaPr";i:7944;s:10:"TelauUnhus";i:7945;s:9:"CalInJuba";i:7946;s:9:"MicUnfVol";i:7947;s:7:"EumeFle";i:7948;s:10:"NumiRuWide";i:7949;s:6:"TritTu";i:7950;s:5:"Sanuk";i:7951;s:7:"HazarQu";i:7952;s:8:"LeroQuUg";i:7953;s:3:"Who";i:7954;s:12:"MakefMetScul";i:7955;s:9:"FamiPluri";i:7956;s:5:"Purse";i:7957;s:9:"PhTissuUn";i:7958;s:12:"AloDemopSens";i:7959;s:9:"InteSluic";i:7960;s:4:"Mesi";i:7961;s:8:"ReTuliUn";i:7962;s:14:"CyanLycanSpenc";i:7963;s:4:"NeRo";i:7964;s:7:"AmmocCe";i:7965;s:6:"MiSika";i:7966;s:8:"LongiNoa";i:7967;s:8:"ColMiPla";i:7968;s:11:"CoincMillPn";i:7969;s:11:"MoraePrUmpx";i:7970;s:7:"PromaSh";i:7971;s:7:"TaTorre";i:7972;s:7:"PresPro";i:7973;s:7:"GayVomi";i:7974;s:7:"EncVagi";i:7975;s:3:"Geo";i:7976;s:12:"GroupOverUnh";i:7977;s:8:"CaltMeNo";i:7978;s:10:"IsochTogVi";i:7979;s:5:"Sphae";i:7980;s:6:"HeHoIn";i:7981;s:4:"Plis";i:7982;s:9:"MisjoPhar";i:7983;s:10:"AscCoUnthi";i:7984;s:12:"HemiPannaSha";i:7985;s:9:"RosiScrol";i:7986;s:8:"RepSepia";i:7987;s:9:"RaReagRev";i:7988;s:5:"SpThe";i:7989;s:8:"FilaPiez";i:7990;s:10:"GeoboHeNec";i:7991;s:7:"OverSen";i:7992;s:3:"Har";i:7993;s:4:"Torp";i:7994;s:9:"OpilUnder";i:7995;s:7:"KaSupra";i:7996;s:4:"Ikat";i:7997;s:5:"Forea";i:7998;s:6:"FoRere";i:7999;s:13:"PeridSecluTru";i:8000;s:10:"JeewhPigeo";i:8001;s:5:"DiPer";i:8002;s:8:"IndTheUn";i:8003;s:13:"ChlorHesSacri";i:8004;s:7:"UncoUnk";i:8005;s:6:"ComInc";i:8006;s:10:"HelLimPlet";i:8007;s:3:"Tau";i:8008;s:13:"HexaRepreSidd";i:8009;s:11:"LeptoReliUt";i:8010;s:3:"Din";i:8011;s:10:"ChronCuSay";i:8012;s:15:"CaphtHymenPsych";i:8013;s:4:"Saim";i:8014;s:5:"RenSh";i:8015;s:6:"AntiFo";i:8016;s:9:"RecadUnad";i:8017;s:3:"Ben";i:8018;s:5:"Forma";i:8019;s:4:"OrQu";i:8020;s:11:"AutGuesInnk";i:8021;s:10:"DacryUltZa";i:8022;s:8:"SubcoTes";i:8023;s:4:"Sapp";i:8024;s:14:"EuchPostaRipco";i:8025;s:7:"PatbRej";i:8026;s:5:"Maxil";i:8027;s:10:"HeMalaProv";i:8028;s:14:"RakexStagVomer";i:8029;s:9:"MonosPrRa";i:8030;s:4:"Oket";i:8031;s:7:"PaTrack";i:8032;s:9:"CausUnYaw";i:8033;s:9:"IntraUncl";i:8034;s:5:"Staro";i:8035;s:4:"Walk";i:8036;s:4:"Pitt";i:8037;s:10:"SappSmaTan";i:8038;s:6:"ReUnhu";i:8039;s:5:"Measl";i:8040;s:9:"UrbaWhimb";i:8041;s:7:"JaSucci";i:8042;s:10:"CoPyrTeles";i:8043;s:9:"MelilScle";i:8044;s:8:"IntPrStr";i:8045;s:11:"CatFrogSwif";i:8046;s:7:"DiscPar";i:8047;s:10:"PeRecoSpon";i:8048;s:10:"EpiOvePont";i:8049;s:14:"PrefeScatTrans";i:8050;s:7:"OutTele";i:8051;s:8:"HenKluxe";i:8052;s:14:"GametGrubrMono";i:8053;s:3:"Sho";i:8054;s:8:"EschaIso";i:8055;s:11:"EcliPropUnd";i:8056;s:6:"DePros";i:8057;s:13:"BetocCaecaInt";i:8058;s:7:"PrescPr";i:8059;s:12:"DeGeniiSkiam";i:8060;s:5:"SlTre";i:8061;s:11:"SeSpooTrich";i:8062;s:8:"PteroZor";i:8063;s:3:"Tru";i:8064;s:6:"FlSter";i:8065;s:12:"ReptiTrUndec";i:8066;s:14:"HyperProphWood";i:8067;s:10:"TimbaUnfis";i:8068;s:8:"CaSenUni";i:8069;s:8:"RathUrrh";i:8070;s:5:"ReSte";i:8071;s:8:"AutoTheg";i:8072;s:5:"Unrus";i:8073;s:10:"CorLePeaco";i:8074;s:10:"DepaMeRewh";i:8075;s:5:"HomUp";i:8076;s:7:"DeguFis";i:8077;s:10:"DreyfRefal";i:8078;s:4:"IlIn";i:8079;s:8:"OutsOvPr";i:8080;s:9:"RoteSulfa";i:8081;s:12:"CladuPolyStu";i:8082;s:5:"Susce";i:8083;s:8:"CoHexaHu";i:8084;s:12:"KnaggPsychUg";i:8085;s:5:"Pytho";i:8086;s:5:"FloTa";i:8087;s:6:"OuSple";i:8088;s:8:"CliPiWee";i:8089;s:4:"Unar";i:8090;s:10:"InvoMarPar";i:8091;s:5:"Homoo";i:8092;s:7:"SaSceni";i:8093;s:6:"FreQua";i:8094;s:13:"ArchsArtilCoc";i:8095;s:7:"MalMult";i:8096;s:5:"Scree";i:8097;s:10:"PhaPoisThy";i:8098;s:7:"GeLemTo";i:8099;s:5:"Perip";i:8100;s:9:"MartWineb";i:8101;s:5:"Trise";i:8102;s:10:"HyRatTehue";i:8103;s:9:"ExImsonUn";i:8104;s:4:"Warf";i:8105;s:11:"LarriMinWit";i:8106;s:6:"TriUps";i:8107;s:11:"MangTaTouch";i:8108;s:10:"ReddiTange";i:8109;s:7:"SwirVal";i:8110;s:11:"IliaInfiNab";i:8111;s:10:"BackFrieUn";i:8112;s:8:"ChaThrom";i:8113;s:5:"JeOve";i:8114;s:5:"Royet";i:8115;s:5:"Plian";i:8116;s:12:"ImprePeUnspi";i:8117;s:5:"Kaffi";i:8118;s:4:"Skim";i:8119;s:6:"MuRewa";i:8120;s:5:"Wicex";i:8121;s:8:"PruReSce";i:8122;s:10:"ConEpiUnli";i:8123;s:9:"MoPresaSc";i:8124;s:3:"Wel";i:8125;s:5:"PyrPy";i:8126;s:9:"CordoStTi";i:8127;s:12:"PetrSteUnsmo";i:8128;s:9:"HissOtocr";i:8129;s:8:"EuMeSlas";i:8130;s:4:"Leth";i:8131;s:5:"IrLon";i:8132;s:9:"AuliIsUin";i:8133;s:6:"GameYe";i:8134;s:9:"NoncNonsa";i:8135;s:12:"FamisPoStrew";i:8136;s:4:"Stan";i:8137;s:8:"ShiSulph";i:8138;s:10:"MyogrResUn";i:8139;s:13:"MultUncheUnco";i:8140;s:7:"KasOpUn";i:8141;s:10:"DiploPaTyl";i:8142;s:4:"Mann";i:8143;s:11:"LuffxUnbUne";i:8144;s:6:"OutsSu";i:8145;s:5:"Shape";i:8146;s:9:"ScreTripe";i:8147;s:13:"CrimeNapPreim";i:8148;s:6:"MiZest";i:8149;s:5:"Suito";i:8150;s:5:"Learx";i:8151;s:14:"CursoOracPenna";i:8152;s:6:"EnstRa";i:8153;s:9:"ChowdPref";i:8154;s:9:"ClansThre";i:8155;s:7:"BlCurub";i:8156;s:6:"SaffUn";i:8157;s:8:"OuScUndr";i:8158;s:7:"DeWista";i:8159;s:7:"AnImmMe";i:8160;s:8:"InscUnra";i:8161;s:9:"RanceUnco";i:8162;s:3:"Lob";i:8163;s:8:"HumorPoo";i:8164;s:4:"GoSu";i:8165;s:6:"TalaTe";i:8166;s:10:"ChoirPoVet";i:8167;s:12:"MinNecrNonra";i:8168;s:4:"Unes";i:8169;s:11:"ForOvePrele";i:8170;s:4:"GaPe";i:8171;s:5:"IntTh";i:8172;s:5:"Prece";i:8173;s:7:"PoikiSc";i:8174;s:13:"CloudMedSuper";i:8175;s:5:"PreQu";i:8176;s:7:"SwagSwi";i:8177;s:10:"MorOutliPr";i:8178;s:13:"NaissNuclePre";i:8179;s:4:"Chla";i:8180;s:4:"Zapt";i:8181;s:10:"VentrWeany";i:8182;s:7:"ParabSa";i:8183;s:9:"CenoFillx";i:8184;s:5:"Undul";i:8185;s:5:"Trade";i:8186;s:8:"BizeColc";i:8187;s:6:"PhPugh";i:8188;s:12:"PreSporSport";i:8189;s:8:"GyneZarz";i:8190;s:6:"CalcIp";i:8191;s:8:"UnqVakia";i:8192;s:9:"SeUnViola";i:8193;s:7:"HySnoop";i:8194;s:5:"MaiTo";i:8195;s:12:"GaviaMeliNic";i:8196;s:4:"Prae";i:8197;s:6:"HorrMo";i:8198;s:8:"TauroWag";i:8199;s:5:"Punic";i:8200;s:4:"Matr";i:8201;s:5:"Jimba";i:8202;s:2:"Ya";i:8203;s:10:"JapRemaScr";i:8204;s:12:"PrThiasUnten";i:8205;s:11:"MisdPhanPsy";i:8206;s:5:"Store";i:8207;s:6:"StTigh";i:8208;s:9:"ApChoreSl";i:8209;s:4:"Refo";i:8210;s:14:"BlowlHydraPump";i:8211;s:11:"PalaeUnpWha";i:8212;s:8:"ArBiUnco";i:8213;s:12:"ConvPrevSuet";i:8214;s:9:"HemocStag";i:8215;s:9:"TutuVario";i:8216;s:6:"HeiUns";i:8217;s:5:"InTen";i:8218;s:8:"SeroSphe";i:8219;s:4:"Tarr";i:8220;s:5:"ExKro";i:8221;s:8:"MukdPoly";i:8222;s:10:"SpliVotWas";i:8223;s:8:"RecomSca";i:8224;s:11:"ManaPneumSo";i:8225;s:3:"Cha";i:8226;s:9:"MicrOvRet";i:8227;s:8:"PreyTwad";i:8228;s:7:"MoNoRes";i:8229;s:7:"EpUnboi";i:8230;s:7:"DodgiUn";i:8231;s:8:"NovSotti";i:8232;s:8:"RenSubSu";i:8233;s:10:"JurisUncVe";i:8234;s:12:"SteatSutToxo";i:8235;s:11:"TucUncUnpro";i:8236;s:8:"EpiSciss";i:8237;s:8:"OleUnave";i:8238;s:5:"CaFor";i:8239;s:9:"PostRedSc";i:8240;s:7:"ImpeSav";i:8241;s:7:"LocSupr";i:8242;s:8:"HaddInSu";i:8243;s:13:"PolitVerWorth";i:8244;s:9:"MaxilSymp";i:8245;s:3:"Wha";i:8246;s:5:"Oroba";i:8247;s:10:"HolocPurSe";i:8248;s:12:"ChEnsmaMonia";i:8249;s:14:"SaponToxeUnven";i:8250;s:9:"CriDosPre";i:8251;s:5:"Turbo";i:8252;s:13:"FisnHyponVine";i:8253;s:3:"Sol";i:8254;s:10:"PunctSuspe";i:8255;s:12:"BedeCaninPer";i:8256;s:3:"Uro";i:8257;s:7:"StiUnsu";i:8258;s:9:"PaleShore";i:8259;s:7:"LeQuadx";i:8260;s:8:"CryExcTa";i:8261;s:8:"PaProUnp";i:8262;s:9:"PinelScol";i:8263;s:8:"CoHyLayl";i:8264;s:8:"HeOptPri";i:8265;s:8:"PaSliTry";i:8266;s:6:"HaPumi";i:8267;s:6:"SofTra";i:8268;s:4:"Redh";i:8269;s:5:"Round";i:8270;s:10:"PhyUltrVel";i:8271;s:9:"TyrtaZami";i:8272;s:12:"ProbRumeTins";i:8273;s:6:"FlooLa";i:8274;s:9:"RaroSolei";i:8275;s:11:"GorgeNavSma";i:8276;s:4:"Dicy";i:8277;s:5:"ReSpi";i:8278;s:9:"InOcPastu";i:8279;s:11:"CounMyStell";i:8280;s:3:"Sau";i:8281;s:4:"InUn";i:8282;s:11:"NonPredSpin";i:8283;s:5:"Outpr";i:8284;s:11:"ComMilRebuf";i:8285;s:10:"HyStraTell";i:8286;s:6:"PiTris";i:8287;s:6:"RoTect";i:8288;s:6:"SloSpe";i:8289;s:10:"MuttoUnpre";i:8290;s:8:"ChUnwoVe";i:8291;s:5:"Thirt";i:8292;s:9:"PredUnres";i:8293;s:12:"FreelHydroIn";i:8294;s:4:"SaUn";i:8295;s:8:"ErraSage";i:8296;s:10:"QuadrSeaYa";i:8297;s:5:"PerSo";i:8298;s:12:"AvenNonNookl";i:8299;s:4:"DaHe";i:8300;s:8:"ChiDemUk";i:8301;s:8:"MaUnWind";i:8302;s:6:"CaInWe";i:8303;s:11:"EndeOsseoUn";i:8304;s:8:"MyriPeri";i:8305;s:10:"PestpSonat";i:8306;s:4:"PhRa";i:8307;s:9:"HawxPemph";i:8308;s:6:"ReTrem";i:8309;s:9:"NonciScot";i:8310;s:10:"MicPerSpon";i:8311;s:3:"Chr";i:8312;s:9:"GuttTrico";i:8313;s:7:"HagiScr";i:8314;s:6:"PlesPo";i:8315;s:10:"MesorMinNi";i:8316;s:8:"DisPlano";i:8317;s:8:"HyaInOut";i:8318;s:6:"OmTosh";i:8319;s:6:"SuTrVa";i:8320;s:12:"CombiPyrenUn";i:8321;s:5:"PhaPo";i:8322;s:8:"ImpInInt";i:8323;s:8:"ParWandx";i:8324;s:4:"Robo";i:8325;s:9:"MulQuiSpa";i:8326;s:6:"MicOsm";i:8327;s:6:"OvZoog";i:8328;s:11:"OveRemanSel";i:8329;s:10:"BrickGuLac";i:8330;s:12:"PapaSakSensi";i:8331;s:11:"HistPondTel";i:8332;s:4:"Vert";i:8333;s:7:"OestrRe";i:8334;s:10:"MagStrTetr";i:8335;s:8:"BridChry";i:8336;s:7:"ResubUn";i:8337;s:10:"EqPolyTabl";i:8338;s:5:"Matri";i:8339;s:10:"ElectWater";i:8340;s:7:"AmnesUn";i:8341;s:7:"GeRetro";i:8342;s:10:"OstPicValu";i:8343;s:10:"HolPrSpatt";i:8344;s:7:"BrighSe";i:8345;s:11:"MusiNonUniv";i:8346;s:9:"NonSittVo";i:8347;s:4:"Rewo";i:8348;s:7:"ResoUra";i:8349;s:9:"MaPaTolfr";i:8350;s:8:"BunWicke";i:8351;s:13:"HobParasPindy";i:8352;s:8:"IllfMiOu";i:8353;s:11:"DisrTabiUnd";i:8354;s:10:"KoffxPaRua";i:8355;s:13:"GogoxSunkeTom";i:8356;s:9:"UnbeWafer";i:8357;s:9:"SecreSpUp";i:8358;s:4:"Mend";i:8359;s:11:"HallmHydrWa";i:8360;s:7:"ChInPer";i:8361;s:10:"IsoQuinSte";i:8362;s:7:"RelUnta";i:8363;s:7:"GigbaLo";i:8364;s:13:"DrumPipeVentr";i:8365;s:4:"Unil";i:8366;s:10:"PneProviSh";i:8367;s:6:"InInte";i:8368;s:8:"LiUnhuUn";i:8369;s:12:"MismaNeWrith";i:8370;s:5:"Preor";i:8371;s:8:"GeiPrUnp";i:8372;s:9:"NonsuPoly";i:8373;s:5:"Ivory";i:8374;s:9:"TaleYowlr";i:8375;s:13:"MitosNotTrisi";i:8376;s:11:"MeniNeuTatu";i:8377;s:14:"ImprMetalUncon";i:8378;s:12:"PessReaSilic";i:8379;s:12:"RooUnmatVina";i:8380;s:5:"Pedan";i:8381;s:5:"Hattx";i:8382;s:4:"Sham";i:8383;s:3:"Bum";i:8384;s:5:"Polyn";i:8385;s:8:"BaeSerfd";i:8386;s:11:"InureUnleWe";i:8387;s:5:"Outmi";i:8388;s:10:"PaleoTaVer";i:8389;s:11:"HexasLaddSt";i:8390;s:7:"ChevrNo";i:8391;s:7:"LanMeMe";i:8392;s:6:"PhoUps";i:8393;s:6:"ResWri";i:8394;s:8:"PlatTerr";i:8395;s:15:"BilgyDisedUgand";i:8396;s:4:"CrDe";i:8397;s:12:"CholeSaTommy";i:8398;s:9:"OmniSheep";i:8399;s:9:"PeaPtQuat";i:8400;s:8:"IsocNilx";i:8401;s:6:"CeHete";i:8402;s:4:"Voci";i:8403;s:6:"SamSca";i:8404;s:4:"Lapa";i:8405;s:10:"DeuSimUnfr";i:8406;s:9:"HucMarsSc";i:8407;s:11:"PerPlatSpor";i:8408;s:12:"GratuHeKaemp";i:8409;s:13:"MastiShipTemp";i:8410;s:7:"PsePycn";i:8411;s:9:"NubecPipp";i:8412;s:12:"FrencManiuUn";i:8413;s:3:"Gas";i:8414;s:9:"MesoScSub";i:8415;s:12:"FomenMerobUn";i:8416;s:8:"OverPhre";i:8417;s:7:"IcostMi";i:8418;s:10:"BioscMisPa";i:8419;s:10:"ArcExtWham";i:8420;s:9:"TickTitex";i:8421;s:9:"ForfNiSpe";i:8422;s:5:"Unbig";i:8423;s:10:"IrredLiftm";i:8424;s:12:"AugeDebbyUnr";i:8425;s:11:"BleekImmPax";i:8426;s:5:"Dowdi";i:8427;s:8:"SilThimb";i:8428;s:14:"NonrePantiStre";i:8429;s:13:"IncesLentoMar";i:8430;s:12:"CytoPonSludx";i:8431;s:7:"CoSuper";i:8432;s:4:"RoTh";i:8433;s:6:"NiSupe";i:8434;s:7:"ExpPhlo";i:8435;s:3:"Via";i:8436;s:9:"PurbYiddi";i:8437;s:10:"OrPedaRush";i:8438;s:5:"LaTec";i:8439;s:3:"Sod";i:8440;s:9:"CorvDreIm";i:8441;s:9:"NoProUngr";i:8442;s:8:"ContInIn";i:8443;s:5:"Stupe";i:8444;s:4:"Sync";i:8445;s:4:"Sloo";i:8446;s:3:"Hep";i:8447;s:7:"DesFePh";i:8448;s:4:"Toxo";i:8449;s:11:"ConjHeLater";i:8450;s:5:"Mistu";i:8451;s:8:"KnapPsSi";i:8452;s:7:"ArFacQu";i:8453;s:13:"PneuSemiWoode";i:8454;s:7:"KnNisha";i:8455;s:9:"PoQueStak";i:8456;s:13:"LegitRelicRom";i:8457;s:7:"HaIchno";i:8458;s:10:"EleutScept";i:8459;s:7:"ProSter";i:8460;s:11:"HatbSequUna";i:8461;s:4:"Micr";i:8462;s:6:"GlGlut";i:8463;s:12:"ChannMicrPre";i:8464;s:4:"Tyri";i:8465;s:4:"Supp";i:8466;s:5:"Unpre";i:8467;s:9:"JiggeSong";i:8468;s:5:"Wards";i:8469;s:5:"FeInd";i:8470;s:3:"Spa";i:8471;s:12:"BuoyDecuRedu";i:8472;s:5:"Wolfh";i:8473;s:7:"PolonRe";i:8474;s:5:"Astro";i:8475;s:14:"ClareUnlaUntom";i:8476;s:4:"Tria";i:8477;s:11:"UnjarUnUnva";i:8478;s:10:"LampMyeUnb";i:8479;s:4:"Unho";i:8480;s:11:"FraiRiXebec";i:8481;s:9:"EndoThUnr";i:8482;s:9:"FulgMaMin";i:8483;s:13:"HydrPosolProf";i:8484;s:10:"EcholSaumo";i:8485;s:13:"CopeDozexMona";i:8486;s:7:"FlePort";i:8487;s:5:"Usnin";i:8488;s:4:"PaUn";i:8489;s:6:"MourSa";i:8490;s:9:"ChiLarNon";i:8491;s:3:"Sna";i:8492;s:11:"KioPhotPrep";i:8493;s:9:"OligSubco";i:8494;s:8:"InflPiUn";i:8495;s:9:"GlaHetTre";i:8496;s:10:"ChronEucPh";i:8497;s:6:"RuiUne";i:8498;s:10:"ResumUngla";i:8499;s:4:"Corp";i:8500;s:7:"IndPape";i:8501;s:8:"PalProje";i:8502;s:4:"Sepi";i:8503;s:9:"OvariSpin";i:8504;s:10:"QuiveUncor";i:8505;s:5:"Picra";i:8506;s:14:"MythiOutbWedan";i:8507;s:6:"ShowUl";i:8508;s:11:"ParasRecRev";i:8509;s:7:"EugGall";i:8510;s:3:"Coe";i:8511;s:6:"SurrWr";i:8512;s:12:"FlabHetePsyc";i:8513;s:5:"ChDev";i:8514;s:9:"NoneUnbek";i:8515;s:12:"DiagrMesPopu";i:8516;s:8:"ShSupeUn";i:8517;s:11:"KeysMeVicto";i:8518;s:13:"ShriTirriWhos";i:8519;s:15:"PhilaPrickRutil";i:8520;s:13:"EnfesRortyThe";i:8521;s:3:"Ket";i:8522;s:12:"GreePerniPur";i:8523;s:7:"OoSorce";i:8524;s:7:"SonioUn";i:8525;s:8:"QuadSaum";i:8526;s:7:"CorCrGr";i:8527;s:11:"PistShaThou";i:8528;s:6:"DisePh";i:8529;s:8:"SatinWit";i:8530;s:7:"SchisSp";i:8531;s:4:"Upcu";i:8532;s:13:"MadeUnconWitc";i:8533;s:4:"Antu";i:8534;s:10:"SecatTaqua";i:8535;s:5:"Proco";i:8536;s:7:"UntwiWr";i:8537;s:8:"PiProRep";i:8538;s:13:"OstScrimVadim";i:8539;s:5:"Subsu";i:8540;s:10:"CruttLappa";i:8541;s:6:"ShUnli";i:8542;s:7:"EpitRes";i:8543;s:4:"Kuld";i:8544;s:4:"Stru";i:8545;s:6:"UncUne";i:8546;s:3:"Tel";i:8547;s:4:"MuTu";i:8548;s:12:"FreckRippRoc";i:8549;s:10:"UnbefVilay";i:8550;s:9:"HydrInflo";i:8551;s:9:"NotidPred";i:8552;s:12:"GoniLustfUnd";i:8553;s:12:"ImbosMaOverb";i:8554;s:7:"ShThymo";i:8555;s:6:"KaMuPl";i:8556;s:10:"PyogRecoRe";i:8557;s:10:"NestoTrUnt";i:8558;s:12:"MissMonodNon";i:8559;s:6:"UncoUn";i:8560;s:6:"UnUned";i:8561;s:3:"Nes";i:8562;s:6:"DumHem";i:8563;s:4:"Yawn";i:8564;s:5:"Medie";i:8565;s:7:"MetSubi";i:8566;s:9:"SeltSteSt";i:8567;s:8:"LivOlivi";i:8568;s:4:"LiPa";i:8569;s:4:"Sarr";i:8570;s:5:"Porit";i:8571;s:8:"NebbyRam";i:8572;s:6:"PloTim";i:8573;s:9:"MilliOrri";i:8574;s:9:"GessHomTh";i:8575;s:6:"HighLi";i:8576;s:13:"HetHypogSupra";i:8577;s:5:"Stave";i:8578;s:8:"LingTass";i:8579;s:4:"StTh";i:8580;s:6:"SeSong";i:8581;s:4:"Gyra";i:8582;s:9:"OnaxUnhos";i:8583;s:4:"Noto";i:8584;s:6:"PeVerb";i:8585;s:5:"Opera";i:8586;s:7:"SaSoUni";i:8587;s:10:"OphrPeUrea";i:8588;s:5:"InWin";i:8589;s:13:"NecesNoweQues";i:8590;s:7:"SauteSu";i:8591;s:7:"BloBrac";i:8592;s:9:"ExHebriLu";i:8593;s:7:"MpangRa";i:8594;s:12:"DemagEllipMo";i:8595;s:10:"ImprSkiWat";i:8596;s:8:"LocTende";i:8597;s:7:"PaUnadv";i:8598;s:11:"SavSlagStra";i:8599;s:8:"BlTasUnd";i:8600;s:4:"Riss";i:8601;s:5:"MoTah";i:8602;s:5:"Hypot";i:8603;s:5:"Prali";i:8604;s:11:"ExsOverSola";i:8605;s:4:"Omni";i:8606;s:10:"MacrSeSupe";i:8607;s:7:"NautWin";i:8608;s:7:"UnswWhe";i:8609;s:5:"Untoo";i:8610;s:5:"Mimmo";i:8611;s:10:"NotoRegrSu";i:8612;s:7:"MonaPse";i:8613;s:9:"ChiRetSqu";i:8614;s:11:"CatarEpiIde";i:8615;s:8:"SerUnrev";i:8616;s:7:"UnoWrit";i:8617;s:9:"MisSuTeas";i:8618;s:8:"MonWhare";i:8619;s:14:"ElzevRoughUtri";i:8620;s:5:"Uninf";i:8621;s:4:"Volu";i:8622;s:9:"ExpOdsRet";i:8623;s:10:"DiaFendPhy";i:8624;s:7:"OversPh";i:8625;s:3:"Van";i:8626;s:3:"Pic";i:8627;s:3:"Hoo";i:8628;s:7:"MetPneu";i:8629;s:11:"GranuTeleTe";i:8630;s:2:"Ep";i:8631;s:5:"Verti";i:8632;s:8:"BerdaRei";i:8633;s:11:"PolStylWasa";i:8634;s:10:"TemUnbowUn";i:8635;s:10:"HiSeldToli";i:8636;s:8:"PlaRaTre";i:8637;s:7:"SalUnro";i:8638;s:6:"LipoQu";i:8639;s:5:"Refle";i:8640;s:9:"OutloScUn";i:8641;s:7:"GrMaVat";i:8642;s:7:"ShardTr";i:8643;s:6:"PolStr";i:8644;s:9:"StacTiTub";i:8645;s:10:"GangGrooZi";i:8646;s:4:"Titi";i:8647;s:7:"LocPala";i:8648;s:4:"Skin";i:8649;s:11:"DiaphRapSpu";i:8650;s:8:"GlarMeta";i:8651;s:7:"InOffic";i:8652;s:4:"Hete";i:8653;s:7:"RefeSuc";i:8654;s:5:"MeSie";i:8655;s:4:"DaMe";i:8656;s:4:"Yard";i:8657;s:4:"Vene";i:8658;s:8:"MelaShUn";i:8659;s:9:"EjPiUnnab";i:8660;s:9:"OrnitPayn";i:8661;s:12:"CotarPhohUnd";i:8662;s:9:"HaitiUnUn";i:8663;s:8:"NealxSup";i:8664;s:4:"Tetr";i:8665;s:8:"UnlitZeu";i:8666;s:9:"GammaGuid";i:8667;s:9:"TaTraUndi";i:8668;s:10:"SizSuWahah";i:8669;s:7:"PoliPun";i:8670;s:3:"Sum";i:8671;s:13:"PenthRegrTasi";i:8672;s:9:"RetroUnat";i:8673;s:11:"ElInveNickx";i:8674;s:14:"EbionParmeSpid";i:8675;s:4:"Gurg";i:8676;s:4:"Ente";i:8677;s:9:"IntMilTer";i:8678;s:11:"HomoPiRetar";i:8679;s:8:"InspReco";i:8680;s:14:"RealSanctSepar";i:8681;s:12:"CyclOuttSlag";i:8682;s:12:"ExogNadiSupe";i:8683;s:4:"LeTe";i:8684;s:10:"BenefInter";i:8685;s:10:"HarmRotUpp";i:8686;s:8:"PseTunna";i:8687;s:10:"PreResSati";i:8688;s:6:"FloTra";i:8689;s:5:"Troph";i:8690;s:4:"InMo";i:8691;s:15:"PlaceRaciaRuntx";i:8692;s:9:"CauSrTolu";i:8693;s:12:"IncomXenopXe";i:8694;s:12:"CaeciCaPaleo";i:8695;s:4:"LoPh";i:8696;s:12:"GamIodyXerop";i:8697;s:5:"Chond";i:8698;s:5:"MeTel";i:8699;s:8:"ObediPou";i:8700;s:4:"PhVa";i:8701;s:5:"UndUr";i:8702;s:5:"Rachi";i:8703;s:11:"BrickGreeLe";i:8704;s:5:"Unreb";i:8705;s:7:"EnwiTew";i:8706;s:13:"EleonHearScha";i:8707;s:4:"PrRe";i:8708;s:8:"SchUnabr";i:8709;s:6:"CoEmMi";i:8710;s:6:"HypeSh";i:8711;s:5:"FuUna";i:8712;s:10:"RabiSanjTr";i:8713;s:13:"ExamiPredUnsh";i:8714;s:12:"InflaNaSchop";i:8715;s:13:"BoldDoliUnpla";i:8716;s:6:"PaUntw";i:8717;s:4:"Tasi";i:8718;s:3:"Cat";i:8719;s:15:"CountMerisSpott";i:8720;s:3:"Sco";i:8721;s:9:"ManPuWhau";i:8722;s:9:"BlocCapri";i:8723;s:9:"BakinLaLi";i:8724;s:9:"ScreStran";i:8725;s:5:"Thick";i:8726;s:10:"DunkPePost";i:8727;s:9:"SkippStim";i:8728;s:4:"Subr";i:8729;s:5:"CaDer";i:8730;s:5:"MarTe";i:8731;s:5:"Terna";i:8732;s:10:"TelThUnver";i:8733;s:6:"NuclPn";i:8734;s:3:"Vim";i:8735;s:4:"Teac";i:8736;s:5:"PaUno";i:8737;s:11:"CompoDiShru";i:8738;s:6:"MonUnr";i:8739;s:6:"SeSwee";i:8740;s:7:"ErgaPol";i:8741;s:11:"EmbOveStrid";i:8742;s:12:"ArsonPrRepud";i:8743;s:9:"PaliProvi";i:8744;s:9:"MopinOxyp";i:8745;s:9:"GobKentUn";i:8746;s:12:"HypeOdontSpi";i:8747;s:12:"ChiRefoUnthr";i:8748;s:7:"PePress";i:8749;s:11:"HiPeriUndep";i:8750;s:12:"LieutSeToywo";i:8751;s:4:"Gunp";i:8752;s:9:"GuSchTele";i:8753;s:9:"DrumMulti";i:8754;s:8:"InflPray";i:8755;s:9:"PrebTubVi";i:8756;s:9:"DeturEnfi";i:8757;s:7:"KishaWe";i:8758;s:12:"MicPapuThala";i:8759;s:5:"Univa";i:8760;s:6:"PruUrx";i:8761;s:5:"Meato";i:8762;s:5:"SiUro";i:8763;s:11:"PterSnarTeb";i:8764;s:9:"MiscoMySy";i:8765;s:10:"ScotcSubVe";i:8766;s:4:"Unwe";i:8767;s:6:"MasPol";i:8768;s:3:"Pra";i:8769;s:5:"SheWe";i:8770;s:12:"NandSeasoWat";i:8771;s:8:"ImpSnake";i:8772;s:9:"HaubeOver";i:8773;s:11:"NaRetroUnma";i:8774;s:7:"SlSombr";i:8775;s:4:"ExTe";i:8776;s:10:"ReducSancy";i:8777;s:6:"CrVent";i:8778;s:5:"Death";i:8779;s:5:"EmTri";i:8780;s:4:"Bric";i:8781;s:5:"Paron";i:8782;s:10:"MyzOmmiaRe";i:8783;s:14:"ChirThereWallo";i:8784;s:10:"ProceUnUnt";i:8785;s:10:"ConSchapSu";i:8786;s:7:"GymPrUn";i:8787;s:7:"PrimiTr";i:8788;s:9:"OitiTraff";i:8789;s:4:"Mini";i:8790;s:10:"RegeStoiZy";i:8791;s:15:"CategInhabResth";i:8792;s:10:"DipEnsHexo";i:8793;s:9:"PlaPsTana";i:8794;s:10:"NontrTiUnd";i:8795;s:7:"CutirMe";i:8796;s:9:"JamdSubtu";i:8797;s:7:"PredoUn";i:8798;s:11:"BenzoHyVoca";i:8799;s:7:"ChDuHus";i:8800;s:12:"TsarUntuVuls";i:8801;s:9:"PrStTrich";i:8802;s:8:"UnbudVer";i:8803;s:6:"SanaUn";i:8804;s:7:"MoScint";i:8805;s:7:"CaDaFac";i:8806;s:8:"AmbCarpo";i:8807;s:10:"KukSipinUn";i:8808;s:7:"SaUnVen";i:8809;s:13:"PentSubbUnthi";i:8810;s:5:"Unbea";i:8811;s:6:"OrgiPe";i:8812;s:7:"ReScSle";i:8813;s:4:"Whis";i:8814;s:11:"DexPalUnher";i:8815;s:4:"Vaga";i:8816;s:4:"Sins";i:8817;s:4:"PhRe";i:8818;s:7:"KatUnsw";i:8819;s:8:"PenSynov";i:8820;s:9:"QuerRxUnl";i:8821;s:3:"Rac";i:8822;s:6:"OstrUb";i:8823;s:7:"GiMoMon";i:8824;s:5:"ReUnb";i:8825;s:5:"Moral";i:8826;s:14:"PhantUrostVajr";i:8827;s:13:"NoncUnfriUnto";i:8828;s:6:"HarPin";i:8829;s:9:"PeSpaTrim";i:8830;s:10:"CollIrToby";i:8831;s:10:"StyTroUnsl";i:8832;s:6:"LuckPh";i:8833;s:5:"Resou";i:8834;s:8:"MatriPap";i:8835;s:4:"Sugg";i:8836;s:3:"Pos";i:8837;s:10:"ReSquamUnh";i:8838;s:8:"HonInves";i:8839;s:6:"ScreSu";i:8840;s:4:"Whel";i:8841;s:5:"InWag";i:8842;s:9:"MembUnfil";i:8843;s:6:"PrUnth";i:8844;s:14:"HierSubnuUnent";i:8845;s:10:"PogRetinUn";i:8846;s:8:"TidedWit";i:8847;s:9:"EphorSnob";i:8848;s:12:"KinetLopeMit";i:8849;s:10:"NoRaspaSub";i:8850;s:11:"AmaMisSarga";i:8851;s:7:"SuThUns";i:8852;s:13:"DefleThreYeme";i:8853;s:3:"Fas";i:8854;s:4:"Viha";i:8855;s:2:"Kh";i:8856;s:10:"GirJudiOxy";i:8857;s:10:"NeurNonPro";i:8858;s:9:"HydroUnco";i:8859;s:5:"Forec";i:8860;s:7:"EmbExub";i:8861;s:5:"Unpho";i:8862;s:13:"AristPreseTra";i:8863;s:8:"PaSyntUn";i:8864;s:7:"HiPledg";i:8865;s:8:"PostUnze";i:8866;s:10:"MallaUngui";i:8867;s:12:"DrOceanUncor";i:8868;s:6:"CalTur";i:8869;s:3:"Vul";i:8870;s:3:"Ult";i:8871;s:8:"RecTitul";i:8872;s:6:"DilaEa";i:8873;s:7:"OnesWhe";i:8874;s:10:"PlSeroTolu";i:8875;s:7:"RecoSoc";i:8876;s:4:"Leuc";i:8877;s:10:"CaninEnPro";i:8878;s:11:"DisStrTrans";i:8879;s:4:"Pani";i:8880;s:6:"PrSten";i:8881;s:4:"Misc";i:8882;s:6:"NoSemi";i:8883;s:13:"GannPodoSlipo";i:8884;s:5:"MyrUn";i:8885;s:5:"Helio";i:8886;s:10:"MonVerniWa";i:8887;s:4:"Gyno";i:8888;s:7:"InnWint";i:8889;s:11:"CeroShoUnav";i:8890;s:8:"PushSiUn";i:8891;s:8:"DioNeSup";i:8892;s:4:"AuTh";i:8893;s:7:"FeHyPyr";i:8894;s:9:"MePamiUnl";i:8895;s:7:"MePikey";i:8896;s:5:"MacUn";i:8897;s:9:"PoleUnexc";i:8898;s:9:"GeocViper";i:8899;s:9:"JointUnUr";i:8900;s:9:"BuCheTigx";i:8901;s:5:"Ochra";i:8902;s:9:"NjaNoStro";i:8903;s:12:"CnemiGrUnreq";i:8904;s:9:"MellRamSc";i:8905;s:11:"InPistoSubr";i:8906;s:6:"ElamKe";i:8907;s:9:"CounDiagr";i:8908;s:8:"CorRevil";i:8909;s:11:"PurSuoUrini";i:8910;s:3:"Sma";i:8911;s:8:"GralHudd";i:8912;s:10:"BurgaGossi";i:8913;s:8:"FimbIdeo";i:8914;s:5:"Viril";i:8915;s:13:"JuverPredeRep";i:8916;s:7:"PhysiUn";i:8917;s:7:"NiTrans";i:8918;s:11:"IsPrickTrol";i:8919;s:7:"GoOment";i:8920;s:9:"TectUnfus";i:8921;s:10:"OvProtUnen";i:8922;s:5:"LeTre";i:8923;s:4:"Vaul";i:8924;s:8:"PhenRecu";i:8925;s:7:"SaTwelv";i:8926;s:5:"Trypt";i:8927;s:5:"Schiz";i:8928;s:6:"FeNonc";i:8929;s:12:"PolarScirSer";i:8930;s:8:"PraseTin";i:8931;s:10:"OfTattlWis";i:8932;s:4:"ShUn";i:8933;s:11:"GiNemeVicar";i:8934;s:7:"FotUnab";i:8935;s:6:"HeMiOr";i:8936;s:13:"FanxLegatUnid";i:8937;s:3:"Med";i:8938;s:14:"EarnScoroSlaye";i:8939;s:7:"GujrSci";i:8940;s:5:"VeViv";i:8941;s:5:"MeaUn";i:8942;s:6:"PtSpor";i:8943;s:6:"MoisSp";i:8944;s:10:"MopinOtViv";i:8945;s:12:"ExognPanoSpo";i:8946;s:7:"HedSuff";i:8947;s:7:"FifteRe";i:8948;s:4:"Sack";i:8949;s:9:"PiRasSerr";i:8950;s:3:"Vam";i:8951;s:11:"EnFlinPertu";i:8952;s:5:"Vexed";i:8953;s:5:"HenSu";i:8954;s:5:"Picad";i:8955;s:10:"CuirReTusc";i:8956;s:11:"PinPreaSeco";i:8957;s:14:"LaddePapaiUrey";i:8958;s:11:"GirlMoPhoen";i:8959;s:5:"Subli";i:8960;s:9:"UnjaVerti";i:8961;s:9:"ReUnUndes";i:8962;s:4:"PsTe";i:8963;s:7:"ParaWhi";i:8964;s:9:"LetxRetou";i:8965;s:9:"PinfeRass";i:8966;s:12:"RacReadStyle";i:8967;s:7:"TriclUn";i:8968;s:7:"SauSpre";i:8969;s:10:"IncoRhyUnc";i:8970;s:9:"HeftiUtra";i:8971;s:10:"ForSeasoUn";i:8972;s:15:"KorunPlateSynus";i:8973;s:10:"ForHappePo";i:8974;s:9:"SeSquZoon";i:8975;s:12:"HypocOriPent";i:8976;s:11:"SheTanUltra";i:8977;s:8:"OctaUpca";i:8978;s:13:"MuzhiPleniTip";i:8979;s:10:"MiscPhoeSa";i:8980;s:4:"Quan";i:8981;s:9:"NariTricl";i:8982;s:6:"InsTro";i:8983;s:13:"CantoPontoSub";i:8984;s:12:"MirReciSavan";i:8985;s:9:"LeNoVivif";i:8986;s:4:"Paid";i:8987;s:9:"OversTagu";i:8988;s:7:"MyVaunt";i:8989;s:8:"OversStr";i:8990;s:8:"ThyUndup";i:8991;s:10:"LaOuthoRou";i:8992;s:4:"Lepi";i:8993;s:12:"NoUnkinUnmut";i:8994;s:5:"Sermo";i:8995;s:5:"StiSt";i:8996;s:4:"Neos";i:8997;s:8:"IsothNon";i:8998;s:8:"SluiWind";i:8999;s:7:"FelicPa";i:9000;s:7:"UndisUn";i:9001;s:8:"NeoSupra";i:9002;s:10:"JaLontaTus";i:9003;s:5:"Outpa";i:9004;s:12:"HeteSubSupra";i:9005;s:5:"Nephe";i:9006;s:9:"LeProSqua";i:9007;s:10:"CountLofti";i:9008;s:12:"DiagSpeciTiw";i:9009;s:4:"Wien";i:9010;s:8:"PhoniSup";i:9011;s:10:"HardePolyo";i:9012;s:7:"RaRecha";i:9013;s:11:"CydGalInobs";i:9014;s:3:"Yah";i:9015;s:8:"SiStTorn";i:9016;s:4:"Anab";i:9017;s:7:"HolaWen";i:9018;s:7:"CaGusSt";i:9019;s:8:"BinPhoPl";i:9020;s:12:"TracUnaffUnc";i:9021;s:12:"GuHeremOvige";i:9022;s:12:"StimyUnVirus";i:9023;s:8:"MultUnfa";i:9024;s:8:"ChoComCo";i:9025;s:8:"ErotPres";i:9026;s:6:"HypSul";i:9027;s:8:"PrSwUnen";i:9028;s:9:"PeThamViv";i:9029;s:9:"ThyrUnder";i:9030;s:10:"PorphPrRiv";i:9031;s:10:"SchooSubju";i:9032;s:5:"SnaUn";i:9033;s:10:"LamasOvers";i:9034;s:7:"HyUnwis";i:9035;s:3:"Rho";i:9036;s:12:"FrigPawnSani";i:9037;s:9:"SnUnUntar";i:9038;s:11:"BundEnigWhe";i:9039;s:9:"PrusRebUn";i:9040;s:10:"CuOverPutt";i:9041;s:6:"EphyNo";i:9042;s:4:"Stap";i:9043;s:5:"PasPo";i:9044;s:5:"EnwGa";i:9045;s:5:"ExtZy";i:9046;s:7:"RoofwSm";i:9047;s:6:"InfMal";i:9048;s:9:"BriDeVale";i:9049;s:7:"PhasSna";i:9050;s:7:"CaExSpr";i:9051;s:8:"ObiRiSla";i:9052;s:8:"ThuUnpre";i:9053;s:6:"PleUri";i:9054;s:7:"FoSemei";i:9055;s:5:"ImpPh";i:9056;s:13:"RootShemuSpec";i:9057;s:5:"KoSem";i:9058;s:11:"MetaNaphPer";i:9059;s:10:"PolReTrans";i:9060;s:7:"MitPres";i:9061;s:7:"NonspOn";i:9062;s:12:"PancPoinUnre";i:9063;s:11:"GerLactTrin";i:9064;s:4:"ObQu";i:9065;s:5:"Saliv";i:9066;s:4:"Symb";i:9067;s:7:"LopSlee";i:9068;s:8:"PropTaip";i:9069;s:12:"LacteMediSwa";i:9070;s:13:"PeacPreteStyl";i:9071;s:6:"RaWorr";i:9072;s:10:"InspiSacro";i:9073;s:11:"CouFactoSmo";i:9074;s:7:"CoelFat";i:9075;s:8:"StagnSym";i:9076;s:5:"SchTr";i:9077;s:4:"Myth";i:9078;s:3:"Pap";i:9079;s:11:"ReSubcThumb";i:9080;s:4:"InWa";i:9081;s:4:"Coen";i:9082;s:6:"RecThy";i:9083;s:10:"KumbiPhPos";i:9084;s:10:"StradVedan";i:9085;s:10:"LiturSubTi";i:9086;s:12:"PediSubaSupp";i:9087;s:3:"Eff";i:9088;s:14:"InterNeogPreab";i:9089;s:6:"CoPrUn";i:9090;s:8:"InPacTro";i:9091;s:5:"AlUnd";i:9092;s:6:"IsoPen";i:9093;s:6:"PyWave";i:9094;s:9:"OculPithi";i:9095;s:9:"RecSithUr";i:9096;s:6:"ReTant";i:9097;s:7:"TheraTr";i:9098;s:9:"ReRespeWh";i:9099;s:6:"DvaTri";i:9100;s:7:"MiVolca";i:9101;s:11:"PaniPelvUne";i:9102;s:12:"CoutDonSpoon";i:9103;s:4:"DaIr";i:9104;s:12:"OvervScUnder";i:9105;s:7:"OuPolys";i:9106;s:13:"GratiPhyTitan";i:9107;s:12:"ScStaliTipma";i:9108;s:11:"RoVeheVelif";i:9109;s:12:"NiccoPostWas";i:9110;s:7:"MendiPr";i:9111;s:9:"MareUnpVe";i:9112;s:6:"OliSal";i:9113;s:6:"AmpOct";i:9114;s:6:"HeToad";i:9115;s:10:"NapOutpWyc";i:9116;s:10:"CaMerchSup";i:9117;s:5:"Telod";i:9118;s:3:"Cyc";i:9119;s:8:"PraepVol";i:9120;s:4:"Syre";i:9121;s:3:"Ble";i:9122;s:6:"FlSubj";i:9123;s:13:"DelMaledUpait";i:9124;s:7:"PopinUn";i:9125;s:9:"DemiEntSe";i:9126;s:12:"FiltRamaRheo";i:9127;s:9:"RacUntWit";i:9128;s:2:"Pf";i:9129;s:5:"FloPi";i:9130;s:14:"ProinSalaWinge";i:9131;s:11:"GodSmashUnl";i:9132;s:14:"MartMicroNeuro";i:9133;s:6:"PaikUn";i:9134;s:4:"ErUs";i:9135;s:6:"UnUnch";i:9136;s:7:"ConUred";i:9137;s:7:"TaysUng";i:9138;s:7:"LacMyxa";i:9139;s:10:"GouSerXylo";i:9140;s:11:"DeIpomeUnre";i:9141;s:11:"HomoJuTatsa";i:9142;s:8:"TetraUnd";i:9143;s:8:"BragPici";i:9144;s:13:"CompDissQuart";i:9145;s:10:"FuNonvStra";i:9146;s:5:"RaRid";i:9147;s:9:"RisTheUnd";i:9148;s:11:"DendrHySand";i:9149;s:8:"ChrJuWor";i:9150;s:10:"CalDemicSn";i:9151;s:5:"UnaUn";i:9152;s:8:"OppPolTu";i:9153;s:4:"PrTr";i:9154;s:8:"NoncoWor";i:9155;s:7:"BookHyp";i:9156;s:12:"BakesCrEpaul";i:9157;s:5:"Tarum";i:9158;s:5:"Helli";i:9159;s:7:"ImpSpon";i:9160;s:5:"Unpri";i:9161;s:13:"OversPasVerst";i:9162;s:7:"CaPaUnr";i:9163;s:10:"LuMonNeckm";i:9164;s:9:"InSerdaSt";i:9165;s:11:"SemicSucVej";i:9166;s:5:"Portu";i:9167;s:5:"Whiti";i:9168;s:8:"PyopSann";i:9169;s:4:"GyMa";i:9170;s:13:"InduInterMega";i:9171;s:5:"Mykis";i:9172;s:4:"Phot";i:9173;s:12:"InamOssuaSca";i:9174;s:12:"SuTweenZoeal";i:9175;s:5:"SqWor";i:9176;s:8:"RepTarge";i:9177;s:7:"OpUntri";i:9178;s:8:"QueeStSw";i:9179;s:8:"DiopThul";i:9180;s:7:"MyitStr";i:9181;s:8:"SemiTrap";i:9182;s:9:"SluStaSty";i:9183;s:5:"Neorn";i:9184;s:5:"Socia";i:9185;s:6:"LameUn";i:9186;s:5:"Weigh";i:9187;s:5:"ByMor";i:9188;s:9:"MinceSaun";i:9189;s:11:"OvUnceUnper";i:9190;s:11:"ForecHaemPh";i:9191;s:10:"IndusLoSpi";i:9192;s:5:"PanSc";i:9193;s:5:"Vomit";i:9194;s:9:"MazucPoly";i:9195;s:8:"JeProVil";i:9196;s:5:"Swith";i:9197;s:4:"Wave";i:9198;s:9:"OrgaTramf";i:9199;s:12:"InsePrelSuwa";i:9200;s:8:"LoggiOve";i:9201;s:13:"PetPleurSolpu";i:9202;s:9:"SclerTrif";i:9203;s:4:"Wrea";i:9204;s:4:"Dysc";i:9205;s:13:"LapidNonThack";i:9206;s:10:"CumKinsmSh";i:9207;s:8:"GaIdeKas";i:9208;s:12:"BrokaGuiltSy";i:9209;s:11:"BrothFaLoca";i:9210;s:10:"LabPlUncon";i:9211;s:7:"EquimMa";i:9212;s:11:"DiHomoPinak";i:9213;s:5:"Holop";i:9214;s:7:"OliOxyt";i:9215;s:6:"GoWenr";i:9216;s:11:"BaCompTerna";i:9217;s:6:"LymMyt";i:9218;s:8:"CountMon";i:9219;s:9:"ShramTack";i:9220;s:10:"PreScToher";i:9221;s:4:"Wool";i:9222;s:6:"HaPeSo";i:9223;s:10:"EnHypeMarq";i:9224;s:9:"NonPaResu";i:9225;s:10:"SupThyUntu";i:9226;s:9:"FetteFoPa";i:9227;s:12:"MayanThrUnte";i:9228;s:7:"PeerlUn";i:9229;s:10:"ChrysFruTi";i:9230;s:8:"OverbVej";i:9231;s:6:"CirCri";i:9232;s:10:"PuinaTabet";i:9233;s:5:"Pulkx";i:9234;s:7:"UnpreUn";i:9235;s:4:"JaRa";i:9236;s:4:"Disa";i:9237;s:11:"ItinOculoOp";i:9238;s:8:"SophTeet";i:9239;s:4:"Text";i:9240;s:12:"PrehPunnRivu";i:9241;s:11:"ImpSupSyner";i:9242;s:10:"DiscoKensp";i:9243;s:4:"Nond";i:9244;s:6:"MariSe";i:9245;s:7:"BalafHa";i:9246;s:8:"PoPreTea";i:9247;s:8:"LuxNapSc";i:9248;s:5:"Bookk";i:9249;s:5:"AspVa";i:9250;s:10:"MonoNoSkew";i:9251;s:11:"BuDirgUnenv";i:9252;s:12:"LumbTrochVir";i:9253;s:5:"Escha";i:9254;s:7:"NarPeti";i:9255;s:9:"ThorUnmas";i:9256;s:13:"CollFarleUnqu";i:9257;s:8:"FerroPol";i:9258;s:6:"ReimSa";i:9259;s:5:"Robus";i:9260;s:10:"DorSpSpong";i:9261;s:9:"SubdSuper";i:9262;s:7:"PhSalic";i:9263;s:9:"PaulRecor";i:9264;s:7:"IliacUn";i:9265;s:8:"ExPeSupe";i:9266;s:10:"ForehIntRo";i:9267;s:12:"CoHeterNoset";i:9268;s:9:"ScumlStan";i:9269;s:5:"Bulbo";i:9270;s:9:"PateQuVis";i:9271;s:11:"FaradGhoMes";i:9272;s:12:"MurraNidOste";i:9273;s:10:"NeyanPinke";i:9274;s:10:"BewilDoOve";i:9275;s:5:"Prefe";i:9276;s:10:"GasManaPhr";i:9277;s:7:"DiGaPho";i:9278;s:5:"Saltw";i:9279;s:12:"HypeLaiLeuca";i:9280;s:13:"CenEgomiReocc";i:9281;s:8:"HeOmiRet";i:9282;s:14:"CottoGaumPrima";i:9283;s:4:"Flec";i:9284;s:9:"PowelToUn";i:9285;s:3:"Net";i:9286;s:4:"MoPi";i:9287;s:7:"DidHowa";i:9288;s:4:"View";i:9289;s:7:"HadLuUn";i:9290;s:6:"ConvIr";i:9291;s:4:"Xyri";i:9292;s:5:"MalPe";i:9293;s:11:"ScSperTetra";i:9294;s:7:"PreSigh";i:9295;s:4:"Suan";i:9296;s:6:"EquSta";i:9297;s:11:"CoptMitSubc";i:9298;s:10:"EuhModifRo";i:9299;s:9:"RepinStab";i:9300;s:9:"SpleUnipo";i:9301;s:5:"PhoPh";i:9302;s:8:"SlTriWha";i:9303;s:4:"HoSp";i:9304;s:5:"Bystr";i:9305;s:14:"NonsuOxycScorb";i:9306;s:11:"LympSkaffWa";i:9307;s:12:"MonosMudProt";i:9308;s:10:"KinMenViro";i:9309;s:3:"Enz";i:9310;s:8:"SupUnfis";i:9311;s:10:"MorpUnUret";i:9312;s:12:"OveRevisUnre";i:9313;s:9:"NoPauldRe";i:9314;s:5:"ObOld";i:9315;s:12:"FlyflPrUphol";i:9316;s:9:"PetrTouri";i:9317;s:5:"Vesti";i:9318;s:4:"Xiph";i:9319;s:11:"FlouSuzThia";i:9320;s:5:"Tzapo";i:9321;s:9:"PercyWate";i:9322;s:7:"PiSeSpa";i:9323;s:9:"BlizzSupe";i:9324;s:10:"MyopProPse";i:9325;s:9:"TotUnUnre";i:9326;s:8:"LepSubTh";i:9327;s:5:"NonRe";i:9328;s:9:"SatScSpon";i:9329;s:5:"FlVir";i:9330;s:6:"GoIgPu";i:9331;s:10:"PuiSuUnive";i:9332;s:5:"DyMye";i:9333;s:9:"CarHuKhok";i:9334;s:7:"OxoniWe";i:9335;s:7:"PaPerXa";i:9336;s:12:"AteloChPheno";i:9337;s:4:"Jagr";i:9338;s:12:"NewTheaUndro";i:9339;s:7:"DisInfe";i:9340;s:6:"HemMas";i:9341;s:6:"KhOcel";i:9342;s:4:"TaTw";i:9343;s:12:"RedawStUngro";i:9344;s:14:"BuffwImmeOctog";i:9345;s:12:"PardoPhReste";i:9346;s:9:"OrnitSynd";i:9347;s:5:"Suspi";i:9348;s:9:"PancaProv";i:9349;s:6:"PhylVi";i:9350;s:4:"HoIn";i:9351;s:8:"CheNonbe";i:9352;s:8:"MicPraUn";i:9353;s:6:"SuUnes";i:9354;s:5:"StiTy";i:9355;s:7:"SlUnska";i:9356;s:4:"VeWh";i:9357;s:6:"LupiPr";i:9358;s:10:"KaRufVoidl";i:9359;s:7:"RaRhina";i:9360;s:8:"FungSubm";i:9361;s:10:"PatmiPerdu";i:9362;s:3:"Tog";i:9363;s:12:"CanCoalSlips";i:9364;s:10:"DrDubioTri";i:9365;s:3:"Spu";i:9366;s:5:"There";i:9367;s:10:"HomeLecWoo";i:9368;s:5:"BloFo";i:9369;s:7:"ParsPho";i:9370;s:13:"BlizCeroQuitr";i:9371;s:4:"Surr";i:9372;s:10:"QuakReorSu";i:9373;s:13:"DressRhySteno";i:9374;s:5:"OveTo";i:9375;s:5:"PnPol";i:9376;s:10:"MicroThecl";i:9377;s:11:"EchelRuScan";i:9378;s:7:"FluorPr";i:9379;s:4:"Some";i:9380;s:4:"Rhyn";i:9381;s:6:"ChEuro";i:9382;s:13:"PrenuRoariTri";i:9383;s:8:"GuiPosse";i:9384;s:10:"EnsInlySer";i:9385;s:9:"EffUnUnsp";i:9386;s:10:"DoHaluTang";i:9387;s:12:"LeNomadRootw";i:9388;s:10:"HorSincStr";i:9389;s:8:"IntKePre";i:9390;s:10:"CastrFubLo";i:9391;s:5:"Theur";i:9392;s:10:"LubOmeReto";i:9393;s:11:"PreSubopUss";i:9394;s:7:"OrOtWai";i:9395;s:7:"GoverIn";i:9396;s:6:"DrHowk";i:9397;s:6:"ExigPo";i:9398;s:5:"Teler";i:9399;s:9:"MarSpUnbr";i:9400;s:5:"Unrec";i:9401;s:6:"MuNeot";i:9402;s:8:"LeSeaSom";i:9403;s:6:"GaJaco";i:9404;s:6:"CoDisp";i:9405;s:6:"OceTub";i:9406;s:7:"StomUnl";i:9407;s:4:"Stea";i:9408;s:8:"PamShore";i:9409;s:8:"PolysSof";i:9410;s:9:"OpporPres";i:9411;s:5:"ConUn";i:9412;s:4:"Imbo";i:9413;s:11:"PoliStTelev";i:9414;s:11:"ChrysHemiRa";i:9415;s:9:"DivPopPse";i:9416;s:6:"PhUnid";i:9417;s:12:"MiniSomThlas";i:9418;s:5:"ShaSo";i:9419;s:8:"SataTryp";i:9420;s:5:"Visce";i:9421;s:4:"Decl";i:9422;s:6:"DiRoga";i:9423;s:5:"Leafl";i:9424;s:8:"BalStagn";i:9425;s:6:"PrefSc";i:9426;s:6:"LithUm";i:9427;s:8:"IntTruss";i:9428;s:7:"ChlamEx";i:9429;s:5:"Shedh";i:9430;s:8:"CoelOvan";i:9431;s:8:"ProUnder";i:9432;s:8:"EmbryUnk";i:9433;s:4:"Pedo";i:9434;s:7:"MurksWi";i:9435;s:9:"PhiliShSl";i:9436;s:5:"SupTa";i:9437;s:10:"DropoHaLou";i:9438;s:8:"BrChylUn";i:9439;s:10:"PrTetUnpre";i:9440;s:8:"PreScowb";i:9441;s:13:"LimiTortZygon";i:9442;s:9:"HansSpiWa";i:9443;s:7:"DiNonUl";i:9444;s:7:"MalUnco";i:9445;s:9:"IncoStave";i:9446;s:4:"Muni";i:9447;s:10:"PsychTurbi";i:9448;s:5:"Nonig";i:9449;s:9:"CorParPos";i:9450;s:7:"GrUrrho";i:9451;s:5:"Priva";i:9452;s:3:"Gue";i:9453;s:5:"TreUn";i:9454;s:8:"GirnyNom";i:9455;s:9:"GeInPavag";i:9456;s:5:"Poeta";i:9457;s:7:"PrerUro";i:9458;s:8:"PalSacch";i:9459;s:9:"CutliSesq";i:9460;s:4:"Sine";i:9461;s:8:"EasPedun";i:9462;s:11:"DotaNonpSic";i:9463;s:13:"CotyStudiTher";i:9464;s:7:"InTripu";i:9465;s:5:"SteUr";i:9466;s:8:"ConvSeab";i:9467;s:3:"Yor";i:9468;s:4:"Prov";i:9469;s:5:"ChiHe";i:9470;s:7:"NePaPre";i:9471;s:5:"Seque";i:9472;s:10:"EmeHaUpswa";i:9473;s:9:"SorceUnde";i:9474;s:11:"StrTacklUnw";i:9475;s:5:"Johan";i:9476;s:7:"PaSpira";i:9477;s:11:"UncaUncoUnd";i:9478;s:5:"CoGor";i:9479;s:8:"BarrMaWi";i:9480;s:4:"TuXy";i:9481;s:6:"MinPro";i:9482;s:6:"MetUnm";i:9483;s:5:"PrWei";i:9484;s:14:"TatoUnbedWovex";i:9485;s:15:"HuronOpeidPetas";i:9486;s:8:"ThesTool";i:9487;s:8:"MalUnbro";i:9488;s:4:"Tars";i:9489;s:7:"DiGrain";i:9490;s:9:"OgOvRiver";i:9491;s:10:"NonclVexer";i:9492;s:4:"Morg";i:9493;s:10:"BushiParTo";i:9494;s:5:"CanWh";i:9495;s:14:"KerriLampMetap";i:9496;s:11:"MayhaUnpWil";i:9497;s:8:"CtenoNin";i:9498;s:10:"CoDisepSta";i:9499;s:5:"Vitri";i:9500;s:5:"Lecit";i:9501;s:12:"HaqPatriSulp";i:9502;s:5:"Loyal";i:9503;s:12:"KeraRuniTene";i:9504;s:3:"Sne";i:9505;s:13:"HemaShadZeall";i:9506;s:5:"Undif";i:9507;s:8:"DisfrSqu";i:9508;s:6:"SeTrip";i:9509;s:14:"EumitNetbrSche";i:9510;s:7:"MoWentl";i:9511;s:11:"OveProfUnme";i:9512;s:9:"DetesMyZi";i:9513;s:4:"Napa";i:9514;s:10:"DeprMetUnt";i:9515;s:12:"InduParUnrec";i:9516;s:10:"PeachUnthe";i:9517;s:5:"PssTu";i:9518;s:5:"DoTom";i:9519;s:5:"Gavia";i:9520;s:8:"DifDumEp";i:9521;s:9:"NonwaShor";i:9522;s:9:"NeStaUnde";i:9523;s:6:"TestUn";i:9524;s:8:"DemZonoc";i:9525;s:6:"InmUnl";i:9526;s:8:"RedUrino";i:9527;s:9:"StenUsurp";i:9528;s:4:"TaZi";i:9529;s:4:"Phys";i:9530;s:12:"HemoIxoSulph";i:9531;s:6:"HoonSh";i:9532;s:8:"RendeVet";i:9533;s:7:"ScrStun";i:9534;s:6:"NonPat";i:9535;s:5:"Rundi";i:9536;s:9:"GyOmTankm";i:9537;s:5:"Ravel";i:9538;s:4:"MeSe";i:9539;s:10:"LiSeymShel";i:9540;s:14:"CeltEnsnaJesti";i:9541;s:3:"Wal";i:9542;s:14:"InwrTressUnsap";i:9543;s:5:"Remen";i:9544;s:8:"DaUndWit";i:9545;s:8:"PhalaRec";i:9546;s:4:"Vicx";i:9547;s:3:"Pel";i:9548;s:4:"MoPo";i:9549;s:5:"Profr";i:9550;s:8:"JustiRed";i:9551;s:5:"IdThr";i:9552;s:5:"ExMet";i:9553;s:5:"Gleno";i:9554;s:13:"IndeParaTerte";i:9555;s:4:"Uniq";i:9556;s:15:"FilipSourhUnhur";i:9557;s:12:"EuhyoRopeUnr";i:9558;s:12:"FervScapuTec";i:9559;s:7:"IrrKisl";i:9560;s:6:"DefNon";i:9561;s:7:"OmniShe";i:9562;s:6:"OidSup";i:9563;s:10:"MetanSpWin";i:9564;s:5:"Torso";i:9565;s:7:"PrRoShi";i:9566;s:9:"OutpRicke";i:9567;s:7:"InWarmu";i:9568;s:9:"EpaPosPro";i:9569;s:11:"CroInviOvof";i:9570;s:5:"DemRa";i:9571;s:5:"Sacka";i:9572;s:4:"SiSy";i:9573;s:4:"Movi";i:9574;s:5:"Neore";i:9575;s:6:"AmWatc";i:9576;s:6:"ScleSm";i:9577;s:11:"BipaFreWalk";i:9578;s:5:"ArtSh";i:9579;s:8:"PaPikRoo";i:9580;s:13:"ImporTodeVari";i:9581;s:5:"Upste";i:9582;s:10:"MesVolWugg";i:9583;s:7:"CharmPa";i:9584;s:12:"PennWashwWur";i:9585;s:7:"UnWeath";i:9586;s:4:"TrUr";i:9587;s:9:"NervResti";i:9588;s:7:"CruSeSp";i:9589;s:7:"NonnaTe";i:9590;s:5:"Uncal";i:9591;s:11:"HyKatciUnin";i:9592;s:5:"Katun";i:9593;s:11:"ThemsUnUnde";i:9594;s:5:"UndVa";i:9595;s:14:"GapyxIliaMesen";i:9596;s:5:"StUnj";i:9597;s:4:"TiUn";i:9598;s:12:"CreatDecUnfa";i:9599;s:8:"DrumxIrr";i:9600;s:12:"HawPennSceno";i:9601;s:7:"InLitPr";i:9602;s:11:"MisbRumpxTh";i:9603;s:12:"ForesIaJamni";i:9604;s:6:"QuaTal";i:9605;s:13:"HenhPerrUnsto";i:9606;s:7:"ShStere";i:9607;s:7:"SterVan";i:9608;s:10:"NeotPlSwar";i:9609;s:5:"UnWac";i:9610;s:10:"BrFrostInd";i:9611;s:8:"BarbNoNu";i:9612;s:9:"FleKnoPho";i:9613;s:8:"MayPaSub";i:9614;s:8:"ScrawUns";i:9615;s:6:"SeThev";i:9616;s:11:"FiresFoWood";i:9617;s:10:"OvigePaSub";i:9618;s:13:"HolidIntelLuc";i:9619;s:13:"MonotOximaSen";i:9620;s:14:"GangSawhoWoman";i:9621;s:10:"HoImpTepeh";i:9622;s:5:"ImpMe";i:9623;s:8:"LuvaSpee";i:9624;s:9:"DeFolGoye";i:9625;s:12:"EboniMoXiphi";i:9626;s:5:"Milts";i:9627;s:5:"QuSte";i:9628;s:10:"QuisShepSu";i:9629;s:7:"ScatoSp";i:9630;s:9:"RadiSigge";i:9631;s:9:"LollRampa";i:9632;s:10:"JuluRehTri";i:9633;s:8:"CeliObPa";i:9634;s:3:"Scl";i:9635;s:6:"PhoPro";i:9636;s:7:"IrUnWhi";i:9637;s:4:"Whee";i:9638;s:12:"HepOutgrPost";i:9639;s:5:"TacWa";i:9640;s:9:"DulaPleas";i:9641;s:10:"PrPunkeTet";i:9642;s:5:"Presu";i:9643;s:7:"ReSepte";i:9644;s:11:"MicMorphRet";i:9645;s:6:"IcTurn";i:9646;s:7:"CoSlumb";i:9647;s:3:"Kho";i:9648;s:9:"MajPosSti";i:9649;s:6:"OveTyr";i:9650;s:9:"JuraUnali";i:9651;s:10:"HeterUpbur";i:9652;s:4:"Invo";i:9653;s:7:"ChaRuma";i:9654;s:5:"Otoco";i:9655;s:10:"FoHouUnrab";i:9656;s:7:"FumaRom";i:9657;s:10:"BriOnaxUnj";i:9658;s:7:"QuSecUn";i:9659;s:11:"IodaNaumUnc";i:9660;s:9:"PoRiTonsu";i:9661;s:9:"BridHespe";i:9662;s:7:"HeliSup";i:9663;s:11:"LefPoroReso";i:9664;s:5:"NinUn";i:9665;s:14:"LitasPrebPresb";i:9666;s:6:"SaUnUn";i:9667;s:10:"GrHidPsych";i:9668;s:9:"PreceRaRo";i:9669;s:13:"OverPittSadrx";i:9670;s:5:"Ignif";i:9671;s:8:"LighUnsa";i:9672;s:9:"OvUnYeast";i:9673;s:7:"DumFiMi";i:9674;s:4:"Scou";i:9675;s:7:"SexteWr";i:9676;s:5:"HieNo";i:9677;s:6:"CaMala";i:9678;s:4:"StUn";i:9679;s:6:"GrotUn";i:9680;s:5:"InSpi";i:9681;s:10:"PolycShama";i:9682;s:12:"ConEncepRest";i:9683;s:5:"PlaSm";i:9684;s:5:"Blatt";i:9685;s:14:"BilipCensuCont";i:9686;s:7:"DiscOve";i:9687;s:8:"InsLeaUn";i:9688;s:2:"Id";i:9689;s:6:"ShSqui";i:9690;s:10:"StatuTaxed";i:9691;s:7:"StUnale";i:9692;s:7:"PyVampi";i:9693;s:12:"PsyTranqUnco";i:9694;s:4:"Mise";i:9695;s:4:"Wayw";i:9696;s:7:"FleLoSp";i:9697;s:10:"InLixOvert";i:9698;s:9:"IsfahOrPu";i:9699;s:11:"BeniCacoUlt";i:9700;s:8:"FleTarra";i:9701;s:4:"DiOp";i:9702;s:4:"HyPo";i:9703;s:13:"NaumaOveRolle";i:9704;s:10:"TeartUncon";i:9705;s:5:"CirDa";i:9706;s:13:"ImploNondTurb";i:9707;s:10:"DyaSexTric";i:9708;s:10:"GaImpWeapo";i:9709;s:4:"Myas";i:9710;s:11:"OpSpadVolse";i:9711;s:5:"Olent";i:9712;s:11:"BiopLaevoSn";i:9713;s:8:"TagalWai";i:9714;s:8:"HeOxyVal";i:9715;s:4:"Infr";i:9716;s:9:"MicrUmbel";i:9717;s:9:"FaNeedlPe";i:9718;s:11:"LysisPseuUn";i:9719;s:10:"CadChroSib";i:9720;s:12:"GooSyncaTuft";i:9721;s:10:"QuaShodeUn";i:9722;s:5:"OveSt";i:9723;s:4:"Perl";i:9724;s:4:"HuPo";i:9725;s:13:"IntoParaSedim";i:9726;s:5:"Carce";i:9727;s:9:"KlMegaPyr";i:9728;s:12:"OrthSeeaWind";i:9729;s:9:"InsulRhin";i:9730;s:6:"MicRis";i:9731;s:15:"IsaacMediaUnmed";i:9732;s:8:"DeStSwim";i:9733;s:14:"KadayPhospPrea";i:9734;s:10:"MesaPrProb";i:9735;s:11:"PediPrPromo";i:9736;s:4:"Dawt";i:9737;s:11:"NariOrThean";i:9738;s:4:"Word";i:9739;s:10:"ExcitPassu";i:9740;s:7:"HoPoPre";i:9741;s:5:"Sampl";i:9742;s:9:"LePhilaUn";i:9743;s:8:"MalebOve";i:9744;s:9:"BoyaTrigi";i:9745;s:8:"DaDiGuai";i:9746;s:9:"DeerhLiQu";i:9747;s:9:"EpicSquee";i:9748;s:6:"PiSupp";i:9749;s:6:"SenSub";i:9750;s:9:"RemuSubba";i:9751;s:12:"HighlSophiSu";i:9752;s:9:"OptimSpor";i:9753;s:4:"Sech";i:9754;s:9:"AmCartCys";i:9755;s:9:"MushmOsPa";i:9756;s:5:"Decon";i:9757;s:4:"Sexh";i:9758;s:10:"ResisSaThu";i:9759;s:3:"Zec";i:9760;s:4:"Vill";i:9761;s:3:"Pod";i:9762;s:9:"OweSisWai";i:9763;s:11:"EmWassaWeig";i:9764;s:9:"HaPostTub";i:9765;s:4:"Stel";i:9766;s:8:"PePrScru";i:9767;s:6:"GastPr";i:9768;s:10:"TeretTrich";i:9769;s:7:"ResiSpu";i:9770;s:3:"Gre";i:9771;s:14:"CunjOversPneum";i:9772;s:8:"OdOzoUmi";i:9773;s:10:"ConfuCover";i:9774;s:9:"PaSubuUnb";i:9775;s:3:"Och";i:9776;s:6:"OvPorc";i:9777;s:11:"ReweSubvWin";i:9778;s:7:"FouUnfo";i:9779;s:12:"FluMicrRecre";i:9780;s:14:"RecruUnivUnspr";i:9781;s:10:"UnsavUnsea";i:9782;s:9:"HormoPiSe";i:9783;s:8:"SpheTwis";i:9784;s:5:"Thema";i:9785;s:13:"DidelTailUnwh";i:9786;s:5:"Marsi";i:9787;s:14:"InfrPleurPurre";i:9788;s:12:"MekomPreSync";i:9789;s:8:"EartNota";i:9790;s:6:"SoddWa";i:9791;s:3:"Obv";i:9792;s:14:"MarmNaphtPunis";i:9793;s:9:"PhillTaut";i:9794;s:12:"InomaNaPremi";i:9795;s:6:"PagaSa";i:9796;s:9:"LoMidleRe";i:9797;s:14:"MonotPsalTetar";i:9798;s:3:"Mum";i:9799;s:13:"PopuTheorWone";i:9800;s:5:"Worth";i:9801;s:6:"SkWhiz";i:9802;s:8:"DemEroHo";i:9803;s:5:"Soili";i:9804;s:7:"StoutTa";i:9805;s:11:"InteManShaf";i:9806;s:11:"DeyxSpTouri";i:9807;s:7:"GlagLac";i:9808;s:8:"RedaSauc";i:9809;s:11:"PseuTintiTo";i:9810;s:7:"NoachOa";i:9811;s:11:"HyPacinSnor";i:9812;s:4:"Ghee";i:9813;s:5:"Shive";i:9814;s:3:"Urv";i:9815;s:15:"SupraUnaptUnhis";i:9816;s:11:"BullwCrUnge";i:9817;s:11:"GuitaPredVa";i:9818;s:5:"Tarqu";i:9819;s:10:"InsecQuaTh";i:9820;s:11:"EnOverTrans";i:9821;s:11:"DelPeloPeti";i:9822;s:7:"DiFicLo";i:9823;s:4:"FoMa";i:9824;s:11:"InexObjPara";i:9825;s:5:"Stere";i:9826;s:7:"LaiSail";i:9827;s:10:"PrRiddScir";i:9828;s:7:"UnVinat";i:9829;s:7:"OvPileo";i:9830;s:7:"BliCuTr";i:9831;s:12:"ArchsHormoTo";i:9832;s:7:"ForMaTh";i:9833;s:5:"ConDi";i:9834;s:7:"HabblSy";i:9835;s:11:"BioSetulTra";i:9836;s:5:"Twigl";i:9837;s:12:"DarnHesteMed";i:9838;s:9:"ElFrShagb";i:9839;s:9:"DeSticUnc";i:9840;s:5:"Repug";i:9841;s:12:"PamSacroValo";i:9842;s:10:"ThrasUnmod";i:9843;s:6:"DidEco";i:9844;s:11:"AntiDolefRh";i:9845;s:8:"EucomSer";i:9846;s:6:"OxyhPo";i:9847;s:10:"MononPulmo";i:9848;s:4:"Urba";i:9849;s:11:"HolPhoUnshi";i:9850;s:11:"TiamUnguaUn";i:9851;s:6:"EnRoUn";i:9852;s:5:"Semia";i:9853;s:9:"DisbPyrTe";i:9854;s:4:"Geob";i:9855;s:8:"BeInSupe";i:9856;s:8:"DepreNon";i:9857;s:7:"PoWhoms";i:9858;s:4:"Pori";i:9859;s:12:"MisprPhUnarg";i:9860;s:3:"Oxy";i:9861;s:12:"CephaMaPanti";i:9862;s:9:"HyotPhala";i:9863;s:12:"InexUntVoidl";i:9864;s:13:"HendePolysVan";i:9865;s:11:"FusilInTast";i:9866;s:6:"UngUnv";i:9867;s:13:"CaleInterMors";i:9868;s:5:"HeTri";i:9869;s:8:"ElHaPrer";i:9870;s:9:"EcoUnUnwa";i:9871;s:10:"RefinRhTub";i:9872;s:6:"MallPl";i:9873;s:5:"Overm";i:9874;s:14:"EntraProfSynge";i:9875;s:8:"MonUnemb";i:9876;s:4:"Osci";i:9877;s:8:"SwoThink";i:9878;s:14:"OsiaOveriWimpl";i:9879;s:7:"BuHinny";i:9880;s:7:"SulUpgr";i:9881;s:5:"PlSma";i:9882;s:8:"SacchSir";i:9883;s:9:"GristRift";i:9884;s:8:"UncUppar";i:9885;s:10:"SnSubUntho";i:9886;s:4:"Loaf";i:9887;s:13:"PyrocSteekTax";i:9888;s:3:"Zac";i:9889;s:10:"OyeUnVioli";i:9890;s:12:"GimMyeliPyro";i:9891;s:10:"RhodSeliTe";i:9892;s:12:"FisUnadUnsub";i:9893;s:8:"WudgXant";i:9894;s:10:"NoOinPyrop";i:9895;s:6:"ExItal";i:9896;s:5:"Totty";i:9897;s:5:"Octam";i:9898;s:11:"OverReticUl";i:9899;s:9:"ElabrFenc";i:9900;s:13:"EnantUnnUnoxi";i:9901;s:5:"PeSyr";i:9902;s:9:"EtymPhoto";i:9903;s:5:"LilMi";i:9904;s:12:"GlypMedUnfet";i:9905;s:8:"LiaThrWi";i:9906;s:10:"OrShoaThur";i:9907;s:8:"UnthUpru";i:9908;s:7:"LepNihi";i:9909;s:12:"FiliMesoUpst";i:9910;s:9:"PetaTenni";i:9911;s:5:"PedRe";i:9912;s:12:"ColoProfTher";i:9913;s:10:"KatabOvSyr";i:9914;s:5:"Cequi";i:9915;s:7:"DisDiEn";i:9916;s:9:"DorMyzoWi";i:9917;s:8:"NiphaPol";i:9918;s:4:"BiCo";i:9919;s:12:"CompPolypUls";i:9920;s:8:"HeaveStr";i:9921;s:5:"MaMos";i:9922;s:13:"EudoMacarSore";i:9923;s:4:"OlVa";i:9924;s:4:"Cosw";i:9925;s:11:"MalOverdSub";i:9926;s:10:"DiKilUnder";i:9927;s:4:"MaPe";i:9928;s:10:"MetrPeProm";i:9929;s:6:"TekUnd";i:9930;s:7:"PlotiSu";i:9931;s:11:"PaPetalTrac";i:9932;s:11:"MoneyUndUnv";i:9933;s:7:"InsurYo";i:9934;s:11:"FoosMicTact";i:9935;s:7:"OlOther";i:9936;s:7:"MonMyOn";i:9937;s:11:"EmexPySymbo";i:9938;s:11:"HypaxSpiTar";i:9939;s:8:"AntUmbVi";i:9940;s:7:"OcclSqu";i:9941;s:8:"ExaPlush";i:9942;s:12:"PreSuccuVerv";i:9943;s:3:"Tre";i:9944;s:5:"MerTo";i:9945;s:11:"GlossSeptSo";i:9946;s:4:"Magn";i:9947;s:13:"QuintSourwThu";i:9948;s:5:"Kinet";i:9949;s:4:"NoUn";i:9950;s:4:"MaUn";i:9951;s:12:"MatriSpirSwe";i:9952;s:8:"OnlooUnl";i:9953;s:13:"MorwoSubarUne";i:9954;s:4:"Impo";i:9955;s:4:"Phac";i:9956;s:8:"DeprePri";i:9957;s:10:"CatLakarPr";i:9958;s:14:"GuttLophoMildr";i:9959;s:12:"OnychSmeUnwe";i:9960;s:5:"GroWo";i:9961;s:8:"LiRadTru";i:9962;s:13:"IncomTitVidui";i:9963;s:12:"ImpMerePrein";i:9964;s:7:"MaProto";i:9965;s:12:"RamusShrTibi";i:9966;s:11:"BurwFalciSn";i:9967;s:5:"RodTh";i:9968;s:8:"DoxaOlch";i:9969;s:4:"Tere";i:9970;s:4:"DiPo";i:9971;s:8:"DenucLit";i:9972;s:11:"CountInKusk";i:9973;s:5:"HeKok";i:9974;s:5:"PrSta";i:9975;s:7:"PhrenPr";i:9976;s:8:"MyoneNet";i:9977;s:14:"GeasMisfeRepre";i:9978;s:13:"OphidProveSub";i:9979;s:11:"PolProrSien";i:9980;s:5:"Grudg";i:9981;s:10:"LiReglUnde";i:9982;s:7:"DiMyoSu";i:9983;s:7:"SymTyVu";i:9984;s:11:"FiguHebSpon";i:9985;s:8:"CeDoTele";i:9986;s:10:"OkiTipTyig";i:9987;s:9:"PySphTetr";i:9988;s:3:"Pep";i:9989;s:11:"PiuUncUnson";i:9990;s:9:"OchiPrWor";i:9991;s:8:"RequiRyn";i:9992;s:13:"JimbeReceRegi";i:9993;s:4:"HeMe";i:9994;s:5:"Toffy";i:9995;s:7:"EriodSu";i:9996;s:9:"UnexViola";i:9997;s:11:"OrgaUnUnhos";i:9998;s:3:"Dav";i:9999;s:6:"HydRev";}
\ No newline at end of file
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testLazyJsonMapper.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testLazyJsonMapper.php
new file mode 100755
index 0000000..f47480d
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testLazyJsonMapper.php
@@ -0,0 +1,1897 @@
+= 0;
+
+$json = << 'int',
+ 'just_a_string' => 'float[]',
+ 'test_pure_lazymapper_object' => '\LazyJsonMapper\LazyJsonMapper',
+ // test the shortcut to avoid having to write the whole path:
+ 'test_pure_lazymapper_object_shortcut' => 'LazyJsonMapper',
+ 'test_pure_lazymapper_object_shortarr' => 'LazyJsonMapper[][]',
+ // test without strict case-sensitive checking: (MUST fail)
+ // 'test_pure_lazymapper_object_shortcut' => 'lazyJsonMapper',
+ ];
+}
+// var_dump(new TestDeep()); // look at the LazyJsonMapper shortcut success
+class TestMid extends TestDeep
+{
+}
+
+class Test extends TestMid
+{
+ const JSON_PROPERTY_MAP = [
+ 'just_a_string' => 'string',
+ 'camelCaseProp' => 'int',
+ // full namespace path, with case-sensitivity typos on purpose (php
+ // allows it, but LazyJsonMapper compiles this to the proper name
+ // instead so that we have strict names internally):
+ 'self_object' => '\foo\Test',
+ 'string_array' => 'string[]',
+ // relative notation instead of full "\namespace\path":
+ // when relative mode is used, it looks in the defining class' own namespace.
+ 'self_array' => 'Test[]',
+ ];
+}
+
+$jsonData = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+
+var_dump($jsonData);
+
+$x = new Test($jsonData, true);
+
+// begin with basic tests...
+var_dump($x);
+$sub = $x->getSelfObject();
+$sub->setJustAString('modifying nested object and propagating the change to root object $x');
+var_dump($sub);
+var_dump($x->getSelfObject());
+var_dump($x);
+$multi = $x->getSelfArray(); // resolves all objects in array, but avoids doing
+ // it recursively. sub-properties are lazy-converted
+ // when they are actually requested.
+var_dump($multi); // array of objects, with no resolved sub-objects yet
+var_dump($x); // now has array of objects
+$deepsub = $multi[1]->getSelfObject(); // causes nested sub to be resolved
+var_dump($multi);
+var_dump($x);
+$deepsub->setJustAString('wow, propagating change of very deep object!');
+var_dump($multi);
+var_dump($x);
+var_dump($x->getCamelCaseProp());
+var_dump($x->getJustAString());
+var_dump($x->isJustAString());
+var_dump($x->getJustAString());
+var_dump($x);
+var_dump($x->getSelfObject());
+var_dump($x->getSelfObject()->getJustAString());
+var_dump($x->self_object->just_a_string);
+var_dump($x->getStringArray());
+var_dump($x->getSelfArray());
+
+try {
+ echo $x->a_missing_property_not_in_data_or_def;
+} catch (LazyJsonMapperException $e) {
+ printf("Test missing property via property access Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->getAMissingPropertyNotInDataOrDef();
+} catch (LazyJsonMapperException $e) {
+ printf("Test missing property via magic getter Exception: %s\n", $e->getMessage());
+}
+
+$x = new Test($jsonData, true);
+var_dump($x); // no data is resolved yet
+// test deeply nested chain of getters and setters.
+$x->getSelfArray()[1]->getSelfObject()->setJustAString('chained command for deep modification')->setCamelCaseProp(9944);
+var_dump($x); // chain has been resolved and change has propagated
+var_dump($x->getSelfArray()[1]->getSelfObject()->getCamelCaseProp()); // int(9944)
+
+class SubClassOfTest extends Test
+{
+}
+$foo = new SubClassOfTest(); // Test acceptance of subclasses of required class.
+$x->setSelfObject($foo);
+var_dump($x->getSelfObject());
+var_dump($x->getSelfObject()->getJustAString());
+
+try {
+ $x->setSelfObject('x'); // trying to set non-object value for object property
+} catch (LazyJsonMapperException $e) {
+ printf("Test non-object assignment Exception: %s\n", $e->getMessage());
+}
+
+class Bleh
+{
+}
+
+try {
+ $x->setSelfObject(new Bleh()); // trying wrong class for property
+} catch (LazyJsonMapperException $e) {
+ printf("Test wrong object assignment Exception: %s\n", $e->getMessage());
+}
+
+$foo = new Test(['just_a_string' => 'example']);
+$x->setSelfObject($foo);
+var_dump($x->getSelfObject());
+var_dump($x->getSelfObject()->getJustAString());
+$x->printJson();
+var_dump($x->just_a_string);
+var_dump(isset($x->just_a_string));
+unset($x->just_a_string);
+var_dump($x->just_a_string);
+var_dump(isset($x->just_a_string));
+unset($x->self_array);
+unset($x->camelCaseProp);
+$x->printJson();
+
+var_dump('---------------------');
+
+// test creation of objects from empty object-arrays "{}" in JSON
+class EmptyObjTest extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'self' => '\foo\EmptyObjTest',
+ ];
+}
+$x = new EmptyObjTest(json_decode('{"self":null}', true)); // allow null object
+var_dump($x->getSelf());
+// NOTE: the empty-array test is because empty arrays are indistinguishable from
+// objects when decoded from JSON. but if it had been an actual JSON array
+// (which is always non-associative), then we detect that it's non-object data.
+$x = new EmptyObjTest(json_decode('{"self":{}}', true)); // allow empty object
+var_dump($x->getSelf());
+$x = new EmptyObjTest(json_decode('{"self":[]}', true)); // allow empty array
+var_dump($x->getSelf());
+$x = new EmptyObjTest(json_decode('{"self":[1,2]}', true)); // forbid non-object
+try {
+ var_dump($x->getSelf());
+} catch (\Exception $e) {
+ printf("Test converting invalid regular JSON array to object Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestUndefinedProps extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'self' => '\foo\TestUndefinedProps',
+ 'selfArray' => '\foo\TestUndefinedProps[]',
+ 'foo_bar' => 'int[][]',
+ 'property' => 'string',
+ ];
+}
+
+$json = <<getMessage());
+}
+
+// now create the class without analysis enabled... which enables regular
+// operation where the user can access the undefined properties too.
+$y = new TestUndefinedProps($data, false);
+var_dump($y); // look at the internal data and the compiled class map
+
+// now verify what the exported property map says.
+// the only defined properties are foo_bar, self, selfArray and property.
+// the undefined ones are missing_property and another_missing.
+$allowRelativeTypes = true;
+$includeUndefined = true;
+$descriptions = $y->exportPropertyDescriptions($allowRelativeTypes, $includeUndefined);
+var_dump($descriptions);
+foreach ($descriptions as $property) {
+ printf("* Property: '%s'. Defined: %s\n", $property->name,
+ $property->is_defined ? 'Yes!' : 'No.');
+}
+
+// Now just test the automatic printing function too...
+$showFunctions = true;
+$y->printPropertyDescriptions($showFunctions, $allowRelativeTypes, $includeUndefined);
+$showFunctions = true;
+$allowRelativeTypes = false;
+$includeUndefined = false;
+$y->printPropertyDescriptions($showFunctions, $allowRelativeTypes, $includeUndefined);
+$showFunctions = false;
+$y->printPropertyDescriptions($showFunctions, $allowRelativeTypes, $includeUndefined);
+
+// And test it on the main class too:
+$y = new Test();
+$y->printPropertyDescriptions();
+$y->printPropertyDescriptions(false, true); // without functions, with relative
+
+var_dump('---------------------');
+
+// Test the hasX() functions, which are useful when verifying that non-defined
+// (not in class definition) fields exist in data before trying to read, to
+// avoid causing any exceptions in the getter.
+$x = new Test($data);
+var_dump($x->hasReallyMissing()); // false, since it's not in class def or data.
+var_dump($x->hasAnotherMissing()); // true, since it's in data (but not in class def)
+var_dump($x->hasJustAString()); // true, since it's in class def (but not in data)
+var_dump($x->getJustAString()); // null, since it's not in data (but is in class def)
+try {
+ $x->getReallyMissing(); // exception, since it's not in class def or data.
+ // var_dump($x->really_missing); // also exception, "no such object property".
+} catch (LazyJsonMapperException $e) {
+ printf("Test getReallyMissing() Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setReallyMissing('a'); // exception, since it's not in class def or data.
+ // $x->really_missing = 'a'; // also exception, "no such object property".
+} catch (LazyJsonMapperException $e) {
+ printf("Test setReallyMissing() Exception: %s\n", $e->getMessage());
+}
+// intended usage by end-users when accessing undefined values:
+if ($x->hasReallyMissing()) {
+ // this won't run, since ReallyMissing didn't exist. but if it HAD existed
+ // in the JSON data, this function call would now be safe without exceptions:
+ var_dump($x->getReallyMissing());
+} else {
+ var_dump('not running getReallyMissing() since the property is missing');
+}
+
+var_dump('---------------------');
+
+class TestNotSubClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'not_subclass' => '\foo\NotSubClass',
+ ];
+}
+class NotSubClass
+{
+} // Not instance of LazyJsonMapper
+
+$json = <<getNotSubclass();
+} catch (LazyJsonMapperException $e) {
+ printf("TestNotSubClass Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestMissingClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'a_missing_class' => '\foo\Missing',
+ ];
+}
+
+$json = <<getMessage());
+}
+
+var_dump('---------------------');
+
+class TestMissingPropAndMissingClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'a_missing_class' => '\foo\Missing',
+ ];
+}
+
+$json = <<getMessage());
+}
+
+var_dump('---------------------');
+
+// this test checks two things:
+// definitions that do not match the data.
+// properties whose classes cannot be constructed (due to custom _init() fail).
+class TestUnmappableAndFailConstructor extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'bad_definition' => 'int[][][]', // too deep arrays for the data
+ // this one will not be able to construct during the getting of this property...
+ 'impossible_constructor' => '\foo\WithBadConstructor',
+ ];
+}
+
+class WithBadConstructor extends LazyJsonMapper
+{
+ protected function _init()
+ {
+ // Uncomment this other exception to test the "Invalid exception thrown
+ // by _init(). Must use LazyUserException." error when users throw the
+ // wrong exception:
+ // throw new \Exception('test');
+
+ throw new \LazyJsonMapper\Exception\LazyUserException('Hello world! Thrown by a failing constructor.');
+ }
+}
+
+$json = <<getMessage());
+}
+
+var_dump('---------------------');
+
+class TestImpossibleSubPropertyCompile extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ // this one links to a class that exists but isn't compiled yet and
+ // therefore must be sub-compiled. but that particular one actually
+ // failed its own compilation earlier (above) - because the
+ // "TestMissingClass" class CANNOT be compiled... so OUR map compilation
+ // HERE will succeed (it points at TestMissingClass which is a
+ // LazyJsonMapper class which exists), but then WE will fail with a
+ // sub-property class error when we try to ALSO compile OUR property's
+ // uncompiled class ("TestMissingClass") at the end of its own
+ // compilation process.
+ 'impossible_subcompilation' => '\foo\TestMissingClass',
+ ];
+}
+
+try {
+ $x = new TestImpossibleSubPropertyCompile();
+} catch (\Exception $e) {
+ printf("Test impossible sub-property class compilation: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// this test is very similar to the previous test, but it ensures that the
+// validation works for deep hierarchies too, of classes with properties that
+// refer to classes with properties that refer to classes that refer to
+// uncompilable classes.
+class TopFailChainClass extends LazyJsonMapper
+{
+ // successfully compiles since MiddleFailChainClass exists and is based on LazyJsonMapper
+ const JSON_PROPERTY_MAP = [
+ 'middle_fail_chain_class' => '\Foo\MiddleFailChainClass',
+ ];
+}
+
+class MiddleFailChainClass extends LazyJsonMapper
+{
+ // successfully compiles since DeepFailChainBadClass exists and is based on LazyJsonMapper
+ const JSON_PROPERTY_MAP = [
+ 'deep_fail_chain_class' => '\Foo\DeepFailChainBadClass',
+ ];
+}
+
+class DeepFailChainBadClass extends LazyJsonMapper
+{
+ // this map will fail to compile, which should stop the compilation of
+ // whichever class began the compilation process that pointed at us...
+ const JSON_PROPERTY_MAP = [
+ 'not_a_valid_class' => '/What/ever/...',
+ ];
+}
+
+// try starting the compilation with each of the 3 classes:
+// it doesn't matter which one we start with, since the compiler cache will
+// notice the failures in them all and will auto-rollback their compilations.
+// which means that the other classes won't incorrectly see anything in the
+// cache. so each attempt to create any of these classes will be as if it was
+// the first-ever call for compiling the classes in its hierarchy.
+
+try {
+ // fails immediately since this class map cannot be compiled
+ $x = new DeepFailChainBadClass();
+} catch (\Exception $e) {
+ printf("Test compiling DeepFailChainBadClass Exception: %s\n", $e->getMessage());
+}
+
+try {
+ // succeeds at compiling its own map, but then fails when trying to compile
+ // the property classes (DeepFailChainBadClass) it found in the hierarchy.
+ $x = new MiddleFailChainClass();
+} catch (\Exception $e) {
+ printf("Test compiling MiddleFailChainClass Exception: %s\n", $e->getMessage());
+}
+
+try {
+ // succeeds at compiling its own map, then looks at its properties and
+ // succeeds at compiling MiddleFailChainClass, and then looks at that one's
+ // properties and fails at compiling the DeepFailChainBadClass it refers to.
+ $x = new TopFailChainClass();
+} catch (\Exception $e) {
+ printf("Test compiling TopFailChainClass Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestNullValue extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'this_is_null' => '\foo\TestNullValue',
+ ];
+}
+
+$json = <<getThisIsNull());
+} catch (LazyJsonMapperException $e) {
+ printf("TestNullValue Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestNoCastValue extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'no_cast1' => '',
+ 'no_cast2' => 'mixed', // same as ''
+ 'no_cast3' => '',
+ ];
+}
+
+$json = <<getNoCast1());
+ var_dump($x->getNoCast2());
+ var_dump($x->getNoCast3());
+ $x->setNoCast1('should succeed without type-forcing');
+ var_dump($x->getNoCast1());
+} catch (LazyJsonMapperException $e) {
+ printf("TestNoCastValue Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestDepth extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'array_of_arrays_of_arrays_of_int' => 'int[][][]',
+ ];
+}
+$x = new TestDepth([]); // Init with no data.
+try {
+ $x->setArrayOfArraysOfArraysOfInt([[new Test()]]);
+} catch (LazyJsonMapperException $e) {
+ printf("Test non-array value at depth 2 of 3 Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setArrayOfArraysOfArraysOfInt([[[new Test()]]]);
+} catch (LazyJsonMapperException $e) {
+ printf("Test invalid value at depth 3 of 3 Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setArrayOfArraysOfArraysOfInt([[[[]]]]);
+} catch (LazyJsonMapperException $e) {
+ printf("Test invalid array-value at depth 3 of 3 Exception: %s\n", $e->getMessage());
+}
+$x->setArrayOfArraysOfArraysOfInt([[[1, '456', 100, 5.5]], [], null, [[20]]]);
+var_dump($x); // 1, 456, 100, 5, 20
+
+var_dump('---------------------');
+
+// test working with raw properties not defined in the class property map.
+class UndefinedPropertyAccess extends LazyJsonMapper
+{
+}
+$json = '{"some_undefined_prop":null}';
+$data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+
+try {
+ // if we run with VALIDATION (TRUE), this will throw an exception
+ // since the data's "some_undefined_prop" is not in the class property map.
+ // NOTE: We already did this test as TestUndefinedProps earlier...
+ $x = new UndefinedPropertyAccess($data, true);
+} catch (\Exception $e) {
+ printf("Test creating class instance with validation detecting undefined properties Exception: %s\n", $e->getMessage());
+}
+
+$x = new UndefinedPropertyAccess($data);
+var_dump($x);
+var_dump($x->hasSomeUndefinedProp()); // true
+var_dump($x->isSomeUndefinedProp()); // false (the null evaluates to false)
+var_dump($x->getSomeUndefinedProp()); // null
+$x->setSomeUndefinedProp(['no data validation since it is undefined']);
+var_dump($x->isSomeUndefinedProp()); // true (the array evaluates to true)
+var_dump($x->getSomeUndefinedProp()); // array with a string in it
+$x->setSomeUndefinedProp('xyz');
+var_dump($x->hasSomeUndefinedProp()); // true
+var_dump($x->getSomeUndefinedProp()); // "xyz"
+var_dump($x->isSomeUndefinedProp()); // true (the string evaluates to true)
+$x->setSomeUndefinedProp(null);
+var_dump($x->hasSomeUndefinedProp()); // true
+var_dump($x->getSomeUndefinedProp()); // null
+
+var_dump('---------------------');
+
+// test of advanced multi-class inheritance:
+// OurTree* is a set of classes inheriting (extending) each other.
+// Unrelated* are two other classes extending each other.
+// FarClass is a single class without any other parents except LazyJsonMapper.
+//
+// OurTreeThree compiles its own inherited hierarchy, which then imports
+// UnrelatedTwo, which compiles its own hierarchy, which then finally imports
+// FarClass. The result is a final, compiled map which includes all classes.
+//
+// (and as noted in the main source code, memory cost of inheritance is 0 since
+// all classes inherit each other's PropertyDefinition objects; the only cost is
+// the amount of RAM it takes for an array["key"] to link to the borrowed object)
+class FarClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'farclass' => '\Foo\FarClass',
+ ];
+}
+class UnrelatedOne extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ FarClass::class,
+ 'unrelated_one' => 'int',
+ ];
+}
+class UnrelatedTwo extends UnrelatedOne
+{
+ const JSON_PROPERTY_MAP = [
+ 'unrelated_two' => 'float',
+ 'conflicting_prop1' => '\Foo\UnrelatedOne',
+ 'conflicting_prop2' => '\Foo\UnrelatedOne',
+ ];
+}
+class OurTreeOne extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'ourtree_one' => 'string',
+ ];
+}
+class OurTreeTwo extends OurTreeOne
+{
+ const JSON_PROPERTY_MAP = [
+ 'ourtree_two' => 'int',
+ 'conflicting_prop1' => '\Foo\OurTreeThree', // will be overwritten
+ UnrelatedTwo::class, // ... by this import
+ 'conflicting_prop2' => '\Foo\OurTreeThree', // will overwrite the import
+ ];
+}
+class OurTreeThree extends OurTreeTwo
+{
+ const JSON_PROPERTY_MAP = [
+ 'ourtree_three' => 'bool[]',
+ ];
+
+ protected function _init()
+ {
+ echo "Hello world from the init function!\n";
+ }
+}
+
+$x = new OurTreeThree();
+var_dump($x);
+
+var_dump('---------------------');
+
+// LOTS OF TESTS OF DIRECT BY-REFERENCE ACCESS, BOTH INTERNALLY AND EXTERNALLY.
+// INTERNALLY: &_getProperty(), EXTERNALLY: &__get()
+class TestGetProperty extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'foo' => '\Foo\TestGetProperty[]',
+ 'bar' => 'int',
+ ];
+
+ protected function _init()
+ {
+ // always set "bar" to a good value after construction (this is just to
+ // avoid code repetition during the testing... and to test the _init function)
+ $this->_setProperty('bar', 1234);
+
+ // uncommenting this will test the "must be LazyUserException" error:
+ // throw new \Exception('x'); // is rejected and replaced with generic
+ // throw new LazyUserException('What'); // okay, message propagates
+ }
+
+ public function runTest()
+ {
+ // just show the current internal data (nothing exists)
+ var_dump($this); // foo is empty
+
+ // test retrieving prop, but not saving missing NULL to _objectData
+ var_dump($this->_getProperty('foo')); // NULL
+ var_dump($this); // foo still empty
+
+ // test saving reference to return-value, but not saving default NULL to _objectData
+ $val = &$this->_getProperty('foo'); // missing important createMissingValue param
+ $val = 'hi'; // does NOT modify the real property, modifies some temp var
+ var_dump($this); // foo still empty
+
+ // test saving reference to return-value, and creating + filling default
+ // inner NULL value so that we can properly trust our references to always
+ // return to real inner data. this is the correct way to save the return
+ // by reference.
+ $val = &$this->_getProperty('foo', true);
+ var_dump($this); // foo now has a NULL value in its object data
+ $val = 'hi, this worked because we are linked to real internal data!';
+ var_dump($this); // overwritten internal value thanks to proper link
+
+ // notice how we have set an invalid STRING value to the internal data?
+ // the "foo" property was specified to need to be an array of object
+ // instances (or NULL is ok too). well, we will detect that the next
+ // time we try to retrieve that property!
+ try {
+ $this->_getProperty('foo');
+ } catch (\Exception $e) {
+ printf("Test _getProperty() with invalid data inserted by reference Exception: %s\n", $e->getMessage());
+ }
+
+ // let's try satisfying its array requirements (but still fail its type req)
+ $val = ['string inside array now fits array req but not type req'];
+
+ try {
+ $this->_getProperty('foo');
+ } catch (\Exception $e) {
+ printf("Test _getProperty() with valid array but invalid type inserted by reference Exception: %s\n", $e->getMessage());
+ }
+
+ // now let's fix the property (set it back to NULL, or we could have
+ // made an array of this class to satisfy the requirement).
+ $val = null;
+ var_dump($this); // foo is now NULL again
+
+ // lastly, let's show that the value is always copy-on-write if the &
+ // operator is omitted from the function call. because then PHP is told
+ // to make $val into copy-on-write.
+ unset($val); // important: break its current reference to avoid assigning to it
+ $val = $this->_getProperty('foo');
+ $val = 'not modified!';
+ var_dump($this); // foo still NULL, since $val is not a reference
+
+ // for completeness sake, also test the "bar" property which has a value
+ // and therefore ignores the "createMissingValue"
+ $bar = &$this->_getProperty('bar', true);
+ var_dump($bar); // int(1234), since a value already existed
+ var_dump($this); // bar still 1234
+ $bar = 456;
+ var_dump($this); // bar is now 456
+ }
+}
+
+// run the internal $this->_getProperty call tests:
+$x = new TestGetProperty();
+$x->runTest();
+
+// run the external __get() call tests used for "virtual property access":
+$x = new TestGetProperty(); // reset the internal data
+var_dump($x); // has no "foo" data, only "bar"
+// accessing ->foo calls __get(), which creates "foo" since virtual prop access
+// requires true internal data references, otherwise they would misbehave. so it
+// creates the missing property and gives it the default NULL value.
+var_dump($x->foo); // null
+var_dump($x); // also has "foo" now
+// accessing ->bar calls __get() which sees that it exists, and gives us its value.
+var_dump($x->bar); // 1234
+// trying to set varibles via equals causes __set() to run, which validates all data:
+try {
+ $x->bar = ['invalid']; // int is expected
+} catch (\Exception $e) {
+ printf("Test __set() with invalid value Exception: %s\n", $e->getMessage());
+}
+$x->bar = '932'; // this is okay, __set() sees it is valid and casts it to int
+var_dump($x); // "bar" is now int(932)
+// now let's do some evil special cases! we will steal a direct reference to the
+// internal _objectData['bar'], and then modify it, thus bypassing all validation.
+$evilRef = &$x->bar; // now holds reference to bar
+$evilRef = ['invalid']; // works, since we're literally modifying internal data
+var_dump($x); // "bar" now has an invalid value (an array with a string)
+try {
+ // luckily, every call to _getProperty() (which __get() uses) will validate
+ // the data to ensure that its internal state is valid and fits the class map.
+ var_dump($x->bar);
+} catch (\Exception $e) {
+ printf("Test detection of injected invalid data during next __get() Exception: %s\n", $e->getMessage());
+}
+$x->bar = 789; // call __set() and give it a new, valid value again.
+var_dump($x->bar); // int(789), it is now fixed!
+// lastly, let's play with direct access to internal arrays. anytime you access
+// an array, it will call __get() to get the array, and then PHP resolves your
+// [] brackets on the returned array. which means that we can modify arrays by
+// reference automatically!
+$x->foo = []; // runs __set(): create empty array for this "\Foo\TestGetProperty[]" property
+var_dump($x->foo); // runs __get() which sees valid empty array and returns it
+$x->foo[] = new TestGetProperty(); // okay data written by ref to "foo" array
+var_dump($x->foo); // runs __get(), which sees valid array of 1 item of right type
+$x->foo[] = 'invalid'; // this is allowed because __get() gets "foo" and then
+ // PHP just directly modifies the array...
+var_dump($x); // the "foo" prop now has invalid data in it
+// but luckily, anything that calls _getProperty() again, such as __get(), will
+// cause validation of the data:
+try {
+ // var_dump($x->foo); // calls __get(), would also throw the error.
+ $x->foo[] = 'x'; // calls __get() again to resolve "->foo" and throws error
+} catch (\Exception $e) {
+ printf("Test detection of invalid injected data via array access Exception: %s\n", $e->getMessage());
+}
+$x->foo = [new TestGetProperty(), new TestGetProperty()]; // run __set() to give okay array again, with 2 entries
+var_dump($x->foo); // shows the array with 2 objects in it
+$x->foo[0] = null; // runs __get(), gets "foo" by reference, and directly modifies element
+var_dump($x->foo); // shows array with 1 NULL in it (this array is valid hence no error)
+// we can also __get() the internal array, then loop over the
+// values-by-reference, to directly modify them without any validation:
+foreach ($x->foo as $k => &$fooVal) {
+ $fooVal = 'invalid';
+}
+var_dump($x); // array with string "invalid".
+try {
+ // if this had been unset($x->foo) it would work, but we try to unset a
+ // sub-element which means it actually calls __get() instead of __unset()
+ unset($x->foo[0]); // calls __get(), sees that the data is now invalid
+} catch (\Exception $e) {
+ printf("Test detection of invalid injected data via array by-reference value loop Exception: %s\n", $e->getMessage());
+}
+var_dump($x); // two "invalid" remains
+$x->foo = [null, new TestGetProperty()]; // let's make it a valid 2-element
+var_dump($x->foo); // array of [null,obj];
+unset($x->foo[0]); // runs __get() on "foo", then unsets the 0th element
+
+// these tests were commented out after adding strict sequence valiation:
+// var_dump($x->foo); // array of [obj];, with the 0 array key missing
+// unset($x->foo[1]->bar); // runs__get() on "foo", gets array, finds 1st elem,
+// // sees object, runs __unset() on that ones "bar"
+// var_dump($x); // reveals that the inner object no longer has any "bar" value
+
+// let's test accessing an object inside an array. and for fun add in regular getters
+// NOTE: there is a subtle difference. getFoo() returns copy-on-write array, but
+// any objects within it are of course objects and can be modified and will propagate.
+$x->foo = [new TestGetProperty()];
+var_dump($x->foo[0]->bar); // int(1234)
+var_dump($x->foo[0]->getBar()); // int(1234)
+var_dump($x->getFoo()[0]->getBar()); // int(1234)
+var_dump($x->getFoo()[0]->bar); // int(1234)
+$x->getFoo()[0]->setBar(10);
+var_dump($x); // the 0th "foo" array element has a bar of int(10) now
+$x->getFoo()[0] = 'xyz'; // this does nothing, since getFoo() is always copy-on-write.
+var_dump($x); // still intact, statement above had no effect, which is as intended
+// now let's modify the array by reference to avoid constant __get() calls...
+$arr = &$x->foo;
+$arr = [1, 2, 'f'=>'bar', [['very invalid data']]];
+$arr[] = 'more invalid stuff...';
+var_dump($x); // very invalid stuff...
+try {
+ var_dump($x->foo);
+} catch (\Exception $e) {
+ printf("Test __get() after lots of bad array edits by reference Exception: %s\n", $e->getMessage());
+}
+$arr = null;
+var_dump($x->foo); // now it is fine again (NULL)
+// let's call a normal array-command on the returned array-by-ref
+$x->foo = []; // first make it into an array
+array_push($x->foo, 'zzz'); // now __get() "foo" and then directly push (same as $x->foo[] = 'bar';)
+var_dump($x); // we have directly added invalid data "zzz" into the array.
+$x = null; // release the object...
+
+var_dump('---------------------');
+
+// Test PropertyDefinition equality:
+$a = new PropertyDefinition('int[]');
+$b = new PropertyDefinition('int');
+$c = new PropertyDefinition('int[]');
+var_dump($a->equals($b)); // false
+var_dump($a->equals($c)); // true
+var_dump($b->equals($a)); // false
+var_dump($b->equals($c)); // false
+var_dump($c->equals($a)); // true
+var_dump($c->equals($b)); // false
+
+var_dump('---------------------');
+
+// Test inheriting from base-class and also importing from other-class
+class OtherBase extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'otherbase' => 'string[]',
+ // ImportMapTest::class, // triggers circular map error IF ImportMapTest
+ // // itself refers to our class hierarchy.
+ ];
+}
+class Other extends OtherBase
+{
+ const JSON_PROPERTY_MAP = [
+ 'identical_key' => 'float',
+ 'other' => 'int',
+ ];
+}
+class Base extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'base' => 'float',
+ ];
+}
+class ImportMapTest extends Base
+{
+ const JSON_PROPERTY_MAP = [
+ // Base::class, // self-hierarchy reference (not allowed)
+ // ImportMapTest::class, // self-class reference (not allowed)
+ C::class, // reference to deeper version of self (not allowed)
+ Other::class, // okay, since it's another class.. but only ok if that
+ // other class doesn't have its circular reference enabled!
+ 'identical_key' => 'string', // should be string, since we add it after
+ // importing Other. but the one in Other should
+ // remain as its own one (float).
+ ];
+}
+class C extends ImportMapTest
+{
+}
+
+try {
+ $x = new C(); // comment in/out various class references above to test
+ // various arrangements of bad circular references.
+ var_dump($x); // if successful inheritance, print the
+ // _compiledPropertyMapLink so we can verify that all values
+ // are properly merged.
+} catch (\Exception $e) {
+ printf("Test resolved-shared-ancestor circular map Exception: %s\n", $e->getMessage());
+}
+
+class AA extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ BB::class,
+ ];
+}
+class BB extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ AA::class,
+ ];
+}
+
+try {
+ $x = new AA();
+} catch (\Exception $e) {
+ printf("Test resolved-shared-ancestor circular map Exception: %s\n", $e->getMessage());
+}
+
+// ensure that the locks are empty after all the construction failures above
+$x = new LazyJsonMapper();
+$reflect = new \ReflectionProperty($x, '_propertyMapCache');
+$reflect->setAccessible(true);
+var_dump($reflect->getValue()->compilerLocks); // should be empty array
+
+var_dump('---------------------');
+
+// this was written to test PropertyDefinition re-use (RAM saving) when a class
+// re-defines a property to the exact same settings that it already inherited.
+// properties in LazyJsonMapper will keep their parent's/imported value if their
+// new value is identical, thus avoiding needless creation of useless objects
+// that just describe the exact same settings. it makes identical re-definitions
+// into a zero-cost operation!
+class HasFoo extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'foo' => 'string[]',
+ ];
+}
+class RedefinesFoo extends HasFoo
+{
+ const JSON_PROPERTY_MAP = [
+ // 'foo' => 'float', // tests non-identical settings (should USE NEW obj)
+ 'foo' => 'string[]', // tests identical settings (should KEEP parent
+ // obj). memory usage should be same as if 'foo'
+ // wasn't re-defined on THIS object at all.
+ // 'foo' => 'string[][]', // tests non-identical settings (should USE NEW obj)
+ 'extra' => '\LazyJsonMapper\LazyJsonMapper',
+ ];
+}
+$mem = memory_get_usage();
+$x = new RedefinesFoo();
+unset($x); // free the object itself, so we only keep the compiled map cache
+printf("Memory increased by %d bytes.\n", memory_get_usage() - $mem);
+
+var_dump('---------------------');
+
+// test function overriding:
+class OverridesFunction extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'foo' => 'string',
+ ];
+
+ public function getFoo()
+ {
+ $value = $this->_getProperty('foo');
+
+ return 'Custom getter: '.var_export($value, true);
+ }
+
+ public function setFoo(
+ $value)
+ {
+ $value = sprintf('Tried "%s" but we will write "%s" instead.', $value, md5(time()));
+ $this->_setProperty('foo', $value);
+
+ return $this;
+ }
+}
+
+$x = new OverridesFunction();
+var_dump($x->getFoo());
+$x->setFoo('ignored');
+var_dump($x->getFoo());
+
+var_dump('---------------------');
+
+// Test rejection of associative array keys in "array of" JSON definition, since
+// those are illegal JSON.
+
+class TestArrayKeyValidation extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'obj_arr' => '\Foo\TestArrayKeyValidation[]',
+ ];
+}
+
+// normal sequence, okay:
+$x = new TestArrayKeyValidation(['obj_arr' => [null, null, null, new TestArrayKeyValidation()]]);
+var_dump($x->getObjArr());
+
+// gap in sequence, not okay:
+$x = new TestArrayKeyValidation(['obj_arr' => [1 => new TestArrayKeyValidation()]]);
+
+try {
+ var_dump($x->getObjArr());
+} catch (\Exception $e) {
+ printf("* Test numeric gap in typed 'array of' sequence: %s\n", $e->getMessage());
+}
+
+// This is ok because of a PHP quirk which converts '0' to 0 if used as array
+// key in certain cases such as this one. The key here is literally int(0):
+$x = new TestArrayKeyValidation(['obj_arr' => ['0' => new TestArrayKeyValidation()]]);
+var_dump($x->getObjArr());
+
+// string key in numerically indexed "array of", not okay:
+$x = new TestArrayKeyValidation(['obj_arr' => ['not_allowed_to_have_key' => new TestArrayKeyValidation()]]);
+
+try {
+ var_dump($x->getObjArr());
+} catch (\Exception $e) {
+ printf("* Test illegal string-based key in typed 'array of' sequence: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// Test validation of mixed data (only allows NULL, int, float, string, bool, or
+// numerically indexed arrays of any of those types). Untyped arrays always do
+// array key validation.
+
+class TestUntypedValidation extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'unt' => '', // behavior would be the same if this property is not
+ // defined in class map but then it would have to exist in
+ // the original input data, so I defined it here as untyped.
+ 'strict_depth' => 'mixed[][]', // enforces mixed non-array data at 2
+ // levels deep within an array
+ ];
+}
+
+$x = new TestUntypedValidation();
+
+try {
+ $x->setUnt(new \stdClass());
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of object \stdClass: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setUnt([new \stdClass()]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of object [\stdClass]: %s\n", $e->getMessage());
+}
+
+$fh = null;
+
+try {
+ $fh = fopen(__DIR__.'/../funcListData.serialized', 'r');
+
+ try {
+ $x->setUnt($fh);
+ } catch (\Exception $e) {
+ printf("* Test set-untyped rejection of Resource: %s\n", $e->getMessage());
+ }
+
+ try {
+ $x->setUnt([$fh]);
+ } catch (\Exception $e) {
+ printf("* Test set-untyped rejection of [Resource]: %s\n", $e->getMessage());
+ }
+} finally {
+ if (is_resource($fh)) {
+ fclose($fh);
+ }
+}
+
+// all other types are allowed in this untyped field:
+$x->setUnt(null);
+$x->setUnt(1);
+$x->setUnt(1.5);
+$x->setUnt('2');
+$x->setUnt(true);
+$x->setUnt([null, 1, [1.5], '2', true]);
+var_dump($x->getUnt());
+
+// we also allow associative keys in untyped fields (which will become JSON
+// objects), since that allows people to access "objects" in json data (arrays)
+// without needing to manually map the properties to actual LazyJsonMapper
+// NOTE: mixing key types can create weird JSON objects. so if people want
+// strict validation they need to use typed fields instead, as seen above.
+$x->setUnt([null, 1, ['foo' => 1.5], '2', true]);
+var_dump($x->getUnt());
+
+// lastly, test the "mixed[]" strict_depth which specified untyped data but
+// exactly 1 array level deep.
+var_dump($x->getStrictDepth());
+$x->setStrictDepth([[null, 1, null, '2', true], null, []]);
+var_dump($x->getStrictDepth());
+
+try {
+ $x->setStrictDepth([[null, 1, null, '2', true], 'not_array', []]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of non-array at array-depth: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setStrictDepth([[null, 1, null, '2', true], 'foo' => 'bar', []]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of associative key in strict array depth: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setStrictDepth([[null, 1, null, '2', true], [['too_deep']], []]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of array deeper than max depth: %s\n", $e->getMessage());
+}
+var_dump($x->getStrictDepth());
+$x->setStrictDepth([]); // okay, since we never reach maxdepth
+var_dump($x->getStrictDepth()); //accepted
+$x->setStrictDepth(null); // null is always okay
+var_dump($x->getStrictDepth()); //accepted
+try {
+ $x->setStrictDepth('foo'); // rejected since the value is not at specified depth
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of value at not enough depth: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// Test FunctionCase name translations into property names:
+
+$x = new FunctionTranslation('ExportProp');
+var_dump($x);
+
+try {
+ // Invalid single-word lowercase FuncCase name.
+ $x = new FunctionTranslation('somelowercase');
+} catch (\Exception $e) {
+ printf("Test invalid single-word lowercase FuncCase name 'somelowercase' Exception: %s\n", $e->getMessage());
+}
+$x = new FunctionTranslation('_MessageList');
+var_dump($x);
+$x = new FunctionTranslation('Nocamelcase'); // Single uppercase = no camel.
+var_dump($x);
+$x = new FunctionTranslation('WithCamelCase'); // Multiple Ucwords = camel.
+var_dump($x);
+
+var_dump('---------------------');
+
+// Test property name translations into FunctionCase, and back...
+// They must be 100% identical in both directions!
+
+// Test function names to property names, and then ensure that both snake_case
+// and camelCase variants translate back to the same function name via PropertyTranslation.
+$funcList = [
+ 'getSome0XThing',
+ 'getSome0xThing',
+ 'getSomeThing',
+ 'get_Messages',
+ 'get__MessageList',
+ 'get0m__AnUn0x',
+ 'get__Foo_Bar__XBaz__',
+ 'get__Foo_Bar_',
+ 'get___M',
+ 'get_M',
+ 'get_0',
+ 'get_',
+ 'get___',
+ 'get123',
+ 'get123prop',
+ 'get123Prop',
+];
+foreach ($funcList as $f) {
+ echo "---\n";
+ list($functionType, $funcCase) = FunctionTranslation::splitFunctionName($f);
+
+ $x = new FunctionTranslation($funcCase);
+ printf("* Function: '%s'\n- Type: '%s'\n- FuncCase: '%s'\n > snake: '%s',\n > camel: '%s'\n", $f, $functionType, $funcCase, $x->snakePropName, $x->camelPropName);
+
+ $y = new PropertyTranslation($x->snakePropName);
+ $getter = 'get'.$y->propFuncCase;
+ printf("* Property: '%s' (snake)\n > func: '%s' (%s)\n", $x->snakePropName, $getter, $getter === $f ? 'ok' : 'fail');
+ if ($x->camelPropName === null) {
+ echo "* Property: No Camel Property, skipping...\n";
+ } else {
+ $y = new PropertyTranslation($x->camelPropName);
+ $getter = 'get'.$y->propFuncCase;
+ printf("* Property: '%s' (camel)\n > func: '%s' (%s)\n", $x->camelPropName, $getter, $getter === $f ? 'ok' : 'fail');
+ }
+ echo "---\n";
+}
+
+var_dump('---------------------');
+
+// test the special operator translator
+$result = 'A + and - and * and / and finally % symbol... And some ++--**//%% close ones...';
+var_dump($result);
+$result = \LazyJsonMapper\Magic\SpecialOperators::encodeOperators($result);
+var_dump($result);
+$result = \LazyJsonMapper\Magic\SpecialOperators::decodeOperators($result);
+var_dump($result);
+
+class OperatorTest extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'en+US' => 'string',
+ 'en-US' => 'string',
+ 'en/US' => 'string',
+ 'en%US' => 'string',
+ 'en*US' => 'string',
+ 'with;semicolon_and@at' => 'string',
+ ];
+}
+
+$optest = new OperatorTest([
+ 'en+US' => 'plus',
+ 'en-US' => 'minus',
+ 'en/US' => 'divide',
+ 'en%US' => 'modulo',
+ 'en*US' => 'multiply',
+ 'with;semicolon_and@at' => 'complex characters here!',
+]);
+
+$optest->printPropertyDescriptions();
+var_dump($optest->getEn_x2B_US()); // plus
+var_dump($optest->getEn_x2D_US()); // minus
+var_dump($optest->getEn_x2F_US()); // divide
+var_dump($optest->getEn_x25_US()); // modulo
+var_dump($optest->getEn_x2A_US()); // multiply
+var_dump($optest->getWith_x3B_semicolonAnd_x40_at());
+
+var_dump('---------------------');
+
+// Test the property description system (the parameters are so strict that there
+// isn't really anything to test, apart from the relative property param...)
+$ownerClassName = get_class(new Test());
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ new PropertyDefinition('\Foo\Test[][]'),
+ false // do not allow relative paths
+);
+var_dump($desc);
+
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ new PropertyDefinition('\Foo\Test[][]'),
+ true // allow relative paths
+);
+var_dump($desc);
+
+// and now test the is_defined detection of UndefinedProperty:
+
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ \LazyJsonMapper\Property\UndefinedProperty::getInstance(),
+ false // do not allow relative paths
+);
+var_dump($desc);
+
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ \LazyJsonMapper\Property\UndefinedProperty::getInstance(),
+ true // allow relative paths
+);
+var_dump($desc);
+
+var_dump('---------------------');
+
+// calculate the memsize of a propertydefinition under various circumstances
+
+// first... force autoloading to find each class if not already loaded, to avoid
+// messing up the measurement.
+$x = new PropertyDefinition();
+$x = new LazyJsonMapper();
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('\LazyJsonMapper\LazyJsonMapper');
+printf("Memory size of a PropertyDefinition object referring to '\\LazyJsonMapper\\LazyJsonMapper': %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition();
+printf("Memory size of a PropertyDefinition object referring to NULL ('mixed'/untyped): %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('int');
+printf("Memory size of a PropertyDefinition object referring to 'int': %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('int[]');
+printf("Memory size of a PropertyDefinition object referring to 'int[]': %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('float[][][]');
+printf("Memory size of a PropertyDefinition object referring to 'float[][][]': %d bytes.\n", memory_get_usage() - $mem);
+
+var_dump('---------------------');
+
+// Test detection of undefined properties:
+$undef = UndefinedProperty::getInstance();
+$def = new PropertyDefinition();
+var_dump($undef instanceof UndefinedProperty); // true
+var_dump($def instanceof UndefinedProperty); // false
+
+var_dump('---------------------');
+
+// the following test analyzes memory usage of FunctionTranslation objects vs
+// storing the cache values as a regular array (without objects). since objects
+// are specialized arrays in PHP, with lower memory needs, we see some pretty
+// great space savings by using objects.
+//
+// these tests will determine the memory needs for the runtime function call
+// translation cache. I'd say normally people use around 100 different
+// properties. this code generates very realistic function names that mimic
+// real-world data.
+
+// if true, re-use pre-built list of 10000 function names and ignore the other
+// params below. that's useful because building the list of names is slow on PHP5.
+// and especially because it guarantees comparable runs between PHP binaries.
+$usePrebuiltFuncList = true;
+
+// how many function names to generate. large sample sizes = accurate averages.
+// this only happens if "useprebuilt" is disabled.
+// $funcCacheCount = 10000; // recommended
+$funcCacheCount = 100; // fast for testing but gives inaccurate memory averages
+
+/*
+ * Here are results for PHP7 and PHP5 with 10000x entries to really demonstrate
+ * the correct averages by having a large enough sample size.
+ *
+ * PHP7: Array of 10000x FunctionTranslation objects: 2485704 bytes total, ~248.6 bytes per entry.
+ * PHP7: Array of 10000x numerically indexed arrays: 5394584 bytes total, ~539.5 bytes per entry.
+ * PHP5: Array of 10000x FunctionTranslation objects: 5104024 bytes total, ~510.4 bytes per entry.
+ * PHP5: Array of 10000x numerically indexed arrays: 6640864 bytes total, ~664.4 bytes per entry.
+ *
+ * Those numbers include the object AND the overhead of their associatively
+ * named string key in the parent (cache) array. The array key is the FuncCase
+ * portion of the name (meaning it lacks the functionType prefix like "get" or
+ * "unset").
+ *
+ * The actual LazyJsonMapper project uses FunctionTranslation objects, and
+ * normal users can be expected to need around 100 cache entries to cover every
+ * property they use in their project.
+ *
+ * That's ~25kb of RAM on PHP7 or ~51kb on PHP5. ;-)
+ */
+
+function randWords(
+ array $allWords)
+{
+ global $allWords;
+
+ // pick 1-3 words
+ $keys = array_rand($allWords, mt_rand(2, 4));
+ array_shift($keys);
+
+ // ensure that they are all lowercase, with an uppercase first letter
+ $words = [];
+ foreach ($keys as $k) {
+ $w = ucfirst(preg_replace('/[^a-z]+/', 'x', strtolower($allWords[$k])));
+ // The wordlist has many insanely long words...
+ // Limit the length to 2-5 chars per word (JSON programmers are terse)
+ $w = substr($w, 0, mt_rand(2, 5));
+ $words[] = $w;
+ }
+
+ return $words;
+}
+function randFunctionName(
+ array $allWords)
+{ // Generates a valid "realistic" function name
+ // Commented out because we no longer use the function type as part of the
+ // parsing of FuncCase names. So we don't need it in the test-data.
+ // $functionType = ['has', 'get', 'set', 'is', 'unset'][mt_rand(0, 4)];
+ // return $functionType.implode(randWords($allWords));
+
+ return implode(randWords($allWords));
+}
+function buildFuncList(
+ array $allWords,
+ $count = 500)
+{ // Generates a list of unique functions.
+ if (count($allWords) < 100) {
+ die('Not enough words...');
+ }
+ $funcList = [];
+ while (count($funcList) < $count) {
+ $funcList[randFunctionName($allWords)] = true;
+ }
+
+ return array_keys($funcList);
+}
+
+if (!$usePrebuiltFuncList) {
+ if (!is_file('/usr/share/dict/words')) {
+ die('Dictionary file missing.');
+ }
+ $allWords = @file('/usr/share/dict/words');
+
+ // build a list of $funcCacheCount amount functions that we'll put in a lookup cache
+ echo "- creating a list of {$funcCacheCount} random function names...\n";
+ $funcList = buildFuncList($allWords, $funcCacheCount);
+
+ // debug/list generation:
+ // var_dump($funcList); // uncomment to see quality of name generation
+ // file_put_contents(__DIR__.'/../funcListData.serialized', serialize($funcList));
+
+ echo "- function list built... running cache test...\n";
+} else {
+ if (!is_file(__DIR__.'/../funcListData.serialized')) {
+ die('No serialized function list.');
+ }
+ $funcList = unserialize(file_get_contents(__DIR__.'/../funcListData.serialized'));
+}
+
+// force autoloading of the class to prevent counting the class itself in mem
+$x = new FunctionTranslation('Example');
+
+// try storing them as FunctionTranslation objects
+$holder = [];
+foreach ($funcList as $funcCase) {
+ $newFuncCase = $funcCase.'x'; // Avoid variable re-use of incoming string.
+ $holder[$newFuncCase] = new FunctionTranslation($newFuncCase);
+ unset($newFuncCase);
+}
+// var_dump($holder); // warning: don't uncomment while testing; increases mem
+$mem = memory_get_usage();
+unset($holder);
+$totalmem = $mem - memory_get_usage();
+$indivmem = $totalmem / count($funcList); // includes the parent array overhead
+printf("PHP%d: Array of %dx FunctionTranslation objects: %d bytes total, ~%.1f bytes per entry.\n", $hasSeven ? 7 : 5, count($funcList), $totalmem, $indivmem);
+
+// try storing them as a regular non-associative array
+$holder = [];
+foreach ($funcList as $funcCase) {
+ $newFuncCase = $funcCase.'y'; // Avoid variable re-use of incoming string.
+ $translation = new FunctionTranslation($newFuncCase);
+ $y = [
+ // paranoid about PHP re-using the object's value, so let's tweak all:
+ substr($translation->snakePropName, 0, -1).'y',
+ $translation->camelPropName === null ? null : substr($translation->camelPropName, 0, -1).'y',
+ ];
+ $holder[$newFuncCase] = $y;
+ unset($translation);
+ unset($newFuncCase);
+ unset($y);
+}
+// var_dump($holder); // warning: don't uncomment while testing; increases mem
+$mem = memory_get_usage();
+unset($holder);
+$totalmem = $mem - memory_get_usage();
+$indivmem = $totalmem / count($funcList); // includes the parent array overhead
+printf("PHP%d: Array of %dx numerically indexed arrays: %d bytes total, ~%.1f bytes per entry.\n", $hasSeven ? 7 : 5, count($funcList), $totalmem, $indivmem);
+
+var_dump('---------------------');
+
+// test cache clearing and the memory usage of each cache from this test-file.
+$mem = memory_get_usage();
+$lookupCount = LazyJsonMapper::clearGlobalMagicLookupCache();
+printf("Saved %d bytes by clearing the magic function lookup cache, which contained %d function name translations.\n", $mem - memory_get_usage(), $lookupCount);
+
+$mem = memory_get_usage();
+$classCount = LazyJsonMapper::clearGlobalPropertyMapCache();
+printf("Saved %d bytes by clearing %d compiled class maps. But not all may have been freed from memory by PHP yet, if any class instance variables are still in scope.\n", $mem - memory_get_usage(), $classCount);
+
+var_dump('---------------------');
+
+// perform lots of tests of the array converter:
+
+// assign the normal json data array, but do not recursively validate (convert)
+// it since we want a mix of converted and unconverted data during this test...
+$x = new Test($jsonData);
+
+//
+// the magic: asArray() CLONES the internal data, then recursively validates all
+// of it and then converts it back to a plain array. the result is therefore
+// fully validated/type-converted as a side-effect of the conversion process.
+//
+// it does not touch the contents of the original object:
+//
+$x->getSelfObject(); // force self_object to evaluate and parse
+var_dump($x); // look at raw data... nothing is parsed except self_object
+
+$asArray = $x->asArray();
+
+// look at the original object... still nothing is parsed except self_object,
+// which remains obj, with the exact same instance number. this verifies that
+// asArray did not manipulate/destroy data in our object.
+var_dump($x);
+
+// validate the asArray result for correctness:
+// $asArray[] = 'x'; // uncomment this to trigger a mismatch below
+// var_dump($asArray); // look at asarray contents
+printf("The asArray() result matches original input array? %s\n",
+ // NOTE: Array === operator checks all keys, keytypes, key order, values,
+ // valuetypes and counts recursively. If true, arrays contain IDENTICAL.
+ ($asArray === $jsonData ? 'YES!' : 'No...'));
+
+// try tweaking the input data so that the class definition no longer matches:
+$jsonData['self_array'] = [$jsonData['self_array']]; // wrap in extra array depth
+$x = new Test($jsonData);
+
+try {
+ $asArray = $x->asArray();
+} catch (\Exception $e) {
+ printf("Trying asArray() with data that mismatches class map Exception: %s\n", $e->getMessage());
+}
+$jsonData['self_array'] = $jsonData['self_array'][0]; // fix data again
+
+// try undefined/untyped (missing) field with acceptable basic non-object data:
+// acceptable basic data is: "int, float, string, bool, NULL" (and arrays of those).
+$jsonData['untyped_field_with_non_object'] = '123456foo';
+$x = new Test($jsonData);
+$asArray = $x->asArray();
+printf("As array with untyped/undefined missing but ok data: %s\n",
+ ($asArray === $jsonData ? 'YES!' : 'No...'));
+
+// try undefined/untyped (missing) field with a LazyJsonMapper object. this will
+// NOT be okay because untyped fields only allow basic PHP types mentioned above.
+// NOTE: This can NEVER happen via real json_decode() data. It is a test against
+// user's custom data arrays with bad values...
+$jsonData['untyped_field_with_lazy_object'] = new LazyJsonMapper(['inner_val' => '123foo']);
+
+try {
+ $x = new Test($jsonData, true); // true = run with validation
+} catch (\Exception $e) {
+ printf("Test construction with validation enabled, and having an illegal value (object) in an undefined property Exception: %s\n", $e->getMessage());
+}
+// try with non-LazyJsonMapper object too in a different property (will fail too
+// since ALL OBJECTS are forbidden in undefined/untyped properties):
+$jsonData['untyped_field_with_bad_object'] = new \stdClass();
+$x = new Test($jsonData); // now construct it WITHOUT validation, so the illegal
+ // value is undetected...
+try {
+ $asArray = $x->asArray();
+} catch (\Exception $e) {
+ // should warn about BOTH the lazy and the "bad" object:
+ printf("Test asArray() on previously unvalidated object containing illegal values in data array Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->getUntypedFieldWithBadObject();
+} catch (\Exception $e) {
+ // should warn about BOTH the lazy and the "bad" object:
+ printf("Test getUntypedFieldWithBadObject() on previously unvalidated object with illegal value in that field Exception: %s\n", $e->getMessage());
+}
+
+// now remove the fake data value again to restore the original jsonData...
+unset($jsonData['untyped_field_with_lazy_object']);
+unset($jsonData['untyped_field_with_bad_object']);
+
+$x = new Test($jsonData);
+$asArray = $x->asArray(); // works again since all bad data is gone!
+var_dump($asArray);
+
+// now try type-conversion to ensure that the type-map is followed:
+class ForceType extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'arr' => 'float[]',
+ ];
+}
+$x = new ForceType(['arr' => ['1', '232', '94.2', 123.42]]);
+var_dump($x->asArray()); // all are floats, exactly as the class-map requested
+var_dump($x); // and as usual... internal _objectData remains untouched.
+
+// try non-integer arguments
+try {
+ $x->asJson(false);
+} catch (\Exception $e) {
+ printf("Test asJson() with non-int arg1: %s\n", $e->getMessage());
+}
+
+try {
+ $x->asJson(0, false);
+} catch (\Exception $e) {
+ printf("Test asJson() with non-int arg2: %s\n", $e->getMessage());
+}
+
+// try requesting a json data depth that is way too low for the data:
+try {
+ $x->asJson(0, 1);
+} catch (\Exception $e) {
+ printf("Test asJson() with too low depth parameter Exception: %s\n", $e->getMessage());
+}
+
+// and for fun...
+var_dump($x->asArray());
+var_dump($x->asJson());
+var_dump($x->asJson(JSON_PRETTY_PRINT));
+$x->printJson();
+
+var_dump('---------------------');
+
+$x = new Test($jsonData);
+
+// ensure that "convert object to string" works and properly outputs JSON...
+echo '['.$x."]\n\n";
+echo $x;
+echo PHP_EOL;
+
+// and test invalid data being output as with <> brackets as intended:
+$bad = new Test(['self_object' => 1]);
+echo PHP_EOL.'Test of clearly bad input data error handling as message string (since __toString cannot throw): '.$bad.PHP_EOL;
+
+var_dump('---------------------');
+
+// try unsetting properties from the internal JSON data tree:
+$x = new Test($jsonData);
+$x->printJson();
+$x->unsetSelfArray() // NOTE: This tests the "chained unsetter" feature too.
+ ->unsetCamelCaseProp()
+ ->setSelfObject(new Test(['just_a_string' => '123 new object!'])); // Tests chained setter together with unsetters.
+$x->printJson();
+$x->unsetSelfObject();
+$x->printJson();
+
+// now try reading a property and then unsetting and reading it again:
+var_dump($x->getStringArray());
+$x->unsetStringArray();
+var_dump($x->getStringArray());
+$x->printJson();
+
+// also try using the direct unset() on the remaining values
+unset($x->just_a_string);
+unset($x->untyped_field_with_non_object);
+$x->printJson();
+
+var_dump('---------------------');
+
+// Let's do some serialization tests:
+
+// First, run a recursive analysis to force all unparsed properties to evaluate
+// into creating inner LazyJsonMapper objects.
+$x = new Test($jsonData);
+$x->exportClassAnalysis();
+var_dump($x); // tree of objects
+
+// Now test the secret, internal "tight packing" serialization method which
+// returns the internal data as a plain array instead of as a serialized string:
+$secretArr = $x->serialize($x);
+var_dump($secretArr);
+
+// Now serialize the object into an actual, serialized string. The same way an
+// end-user would do it.
+// NOTE: This resolves all nested objects and serializes the root object with a
+// single serialized, plain array within it.
+$str = serialize($x);
+var_dump($str); // no nested serialized objects
+
+// test the ability to fake "unserialize" into re-constructing objects from any
+// serialized array. NOTE: this is just for testing proper re-building /
+// unserialization in a different way. users should never do this. it's dumb.
+// they should just create their "new TheClass([...])" instead.
+$fakeunserialize = new Test();
+var_dump($fakeunserialize); // empty _objectData
+$fakeunserialize->unserialize(serialize(['my_data' => 'hehe']));
+var_dump($fakeunserialize); // objectdata now has my_data
+
+// test exception when calling the function directly with bad params
+try {
+ $fakeunserialize->unserialize();
+ $fakeunserialize->unserialize(null);
+} catch (\Exception $e) {
+ printf("Test unserialize manual call with bad params Exception: %s\n", $e->getMessage());
+}
+
+// lastly, let's test real unserialization as a new object instance.
+// this creates a brand new object with the data array, and has no links to the
+// original object (except using the same shared, compiled classmap since we are
+// still in the same runtime and have a shared classmap cache entry available).
+$new = unserialize($str);
+// verify that all _objectData is there in the new object, and that unlike the
+// original object (which had exportClassAnalysis() to create inner objects),
+// this unserialized copy just has a plain data array:
+var_dump($new);
+// get a random property to cause it to convert it to its destination format:
+$new->getSelfArray();
+var_dump($new); // self_array is now an array of actual objects
+
+var_dump('---------------------');
+
+// test asArray/asStdClass which are aliases to exportObjectDataCopy
+var_dump($x->exportObjectDataCopy('array'));
+var_dump($x->asArray());
+// var_dump($x->exportObjectDataCopy('Array')); // test invalid type
+var_dump($x->exportObjectDataCopy('stdClass'));
+var_dump($x->asStdClass());
+
+var_dump('---------------------');
+
+// test new data assignment at a later time via assignObjectData():
+$foo = new Test(['just_a_string' => '123']);
+$foo->printPropertyDescriptions();
+$foo->printJson();
+$foo->assignObjectData(['camelCaseProp' => 999, 'string_array' => ['a', 'b', 'c']]);
+$foo->printJson();
+
+var_dump('---------------------');
+
+class TestJSONArrayKeys extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'array_of_strings' => 'string[]',
+ ];
+}
+
+// first, test the fact that objects must always be output as {} notation.
+$test = new TestJSONArrayKeys();
+var_dump($test->asJson());
+$test->printJson(); // {}
+$test->setArrayOfStrings(['a', 'b']);
+var_dump($test->asJson());
+$test->printJson(); // {"array_of_strings":["a","b"]}
+$test = new TestJSONArrayKeys(['a', 'b']);
+var_dump($test->asJson());
+$test->printJson(); // {"0":"a","1":"b"}
+
+// now do a test of the fact that properties defined as "array of" only allow
+// sequential, numerical keys.
+$test->setArrayOfStrings(['a', 'b']);
+$test->setArrayOfStrings(['0' => 'a', 'b']); // works because PHP converts "0" to int(0)
+$test->setArrayOfStrings([0 => 'a', 1 => 'b']); // correct order
+try {
+ $test->setArrayOfStrings([1 => 'a', 0 => 'b']); // bad order
+} catch (\Exception $e) {
+ printf("Test wrong order array keys, Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $test->setArrayOfStrings([4 => 'a', 5 => 'b']); // not starting at 0
+} catch (\Exception $e) {
+ printf("Test array keys not starting at 0, Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $test->setArrayOfStrings(['a', 'b', 'foo' => 'b']); // string-key
+} catch (\Exception $e) {
+ printf("Test non-numeric array key in numeric array, Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// test that our forced-object-notation {} JSON output works in all cases (even
+// when strictly numeric keys or empty arrays).
+// the correct, intended format is: The outer container (the object) is always
+// {}, but any inner arrays in properties are [].
+
+$foo = new Test(); // no internal array assigned, so uses empty default array
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([1, [11, 22, 33], 3]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([0=>1, 1=>[11, 22, 33], 2=>3]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([0=>1, '1'=>[11, 22, 33], 2=>3]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([0=>1, 2=>3, 1=>[11, 22, 33]]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([1, [11, 22, 33], 3, 'x'=>1]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test(['x'=>1, 1, [11, 22, 33], 3]);
+var_dump($foo->asJson());
+$foo->printJson();
+
+var_dump('---------------------');
+
+// now end with some nice data dumping tests on the final, large data object...
+// and let's use the newly unserialized object instance for fun..
+var_dump($new->asJson());
+var_dump($new->asJson(JSON_PRETTY_PRINT)); // manually controlling JSON output options
+$new->printPropertyDescriptions();
+echo str_repeat(PHP_EOL, 5);
+$new->printJson(false); // automatic printing but without pretty-print enabled
+echo str_repeat(PHP_EOL, 5);
+$new->getSelfObject()->printJson(); // printing a sub-object (only safe if obj is non-NULL)
+echo str_repeat(PHP_EOL, 5);
+// $new->getSelfArray()->printJson(); // would not work on PHP arrays, obviously
+$new->getSelfArray()[0]->printJson(); // works, since that array entry is an obj
+echo str_repeat(PHP_EOL, 5);
+$new->printJson(); // <-- Debug heaven! ;-)
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testPropertyDefinitionNamespaces.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testPropertyDefinitionNamespaces.php
new file mode 100755
index 0000000..7fc1a39
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testPropertyDefinitionNamespaces.php
@@ -0,0 +1,173 @@
+ 'string',
+ ];
+ }
+}
+
+namespace Foo\Deeper {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+
+ class NoExtendsClass
+ {
+ }
+
+ class MyClass extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = ['foo' => 'string'];
+ }
+}
+
+namespace Other\Space\VerySpace {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+
+ class VeryDeepInSpace extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = [
+ 'deepspacenine' => 'string',
+ ];
+ }
+}
+
+namespace Other\Space {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+ use LazyJsonMapper\Property\PropertyDefinition;
+
+ class OtherClass extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = [
+ 'from_other_class' => 'string',
+ ];
+ }
+
+ class MyClass extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = [
+ 'foo' => 'int',
+ // tests handling of missing relative classes (the warning will
+ // display the namespace of this class which defined the property):
+ // 'missing_relative' => 'NoSuchClass', // uncomment to test
+ // tests support for relative classes within same namespace:
+ 'relative_class_path' => 'OtherClass',
+ 'relative_sub_class_path' => 'VerySpace\VeryDeepInSpace',
+ // and global overrides (the "\" prefix makes PropertyDefinition
+ // use the global namespace instead):
+ 'global_class_path' => '\Foo\Deeper\MyClass',
+ // just for fun, let's import a class map too, via relative:
+ // (can be done via global or relative paths)
+ VerySpace\VeryDeepInSpace::class,
+ ];
+ }
+
+ $resolved = new MyClass();
+ var_dump($resolved);
+}
+
+namespace Foo\Other\Space {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+
+ // This class is here to show that new $x->propType() construction technique
+ // is a bad idea since it may lead to relative resolving like this one, if
+ // the global path cannot be found.
+ class MyClass extends LazyJsonMapper
+ {
+ }
+}
+
+namespace Foo {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+ use LazyJsonMapper\Property\PropertyDefinition;
+
+ var_dump(\Other\Space\MyClass::class);
+ var_dump(class_exists('\Other\Space\MyClass'));
+ var_dump(class_exists('\Other\Space\\\MyClass'));
+ var_dump(Deeper\MyClass::class);
+ var_dump(__NAMESPACE__);
+
+ echo "-----\n";
+
+ // test various combinations of namespaces and class prefixes:
+ // $x = new PropertyDefinition('\MyClass', __NAMESPACE__);
+ // $x = new PropertyDefinition('\MyClass\Deeper', __NAMESPACE__);
+ // $x = new PropertyDefinition('MyClass', __NAMESPACE__);
+ // $x = new PropertyDefinition('MyClass\Deeper', __NAMESPACE__);
+ // $x = new PropertyDefinition('MyClass');
+ // $x = new PropertyDefinition('\MyClass');
+ // $x = new PropertyDefinition('MyClass\Deeper');
+ // var_dump($x);
+
+ // test a valid relative path (and the cleanup/normalization of a bad name).
+ $x = new PropertyDefinition('deePER\MYClass[][]', __NAMESPACE__);
+ var_dump($x);
+ var_dump($x->asString());
+ $y = new $x->propType(); // BAD! WE ALWAYS THE GLOBAL PATH, DO NOT USE THIS
+ // always use getStrictClassPath() instead!
+ var_dump($y); // \Foo\Deeper\MyClass instance
+
+ // test a valid path in other space (via global path)
+ $x = new PropertyDefinition('\Other\SPACe\MYCLASS[][]', __NAMESPACE__);
+ var_dump($x);
+ var_dump($x->asString());
+
+ $type = "Deeper\MyClass"; // PHP would resolve this locally due to no \
+ $y = new $type();
+ var_dump($y);
+
+ $type = "\Deeper\MyClass";
+ $y = new $type();
+ var_dump($y);
+
+ echo "------\n";
+ var_dump($x);
+ $y = new $x->propType(); // BAD IDEA! This field has no "\" prefix and may not
+ // resolve to the intended class in all situations
+ // correct way for extra safety is always:
+ $strictClassPath = $x->getStrictClassPath();
+ var_dump($strictClassPath);
+ $y = new $strictClassPath();
+
+ var_dump($y); // \Other\Space\MyClass instance
+
+ // test bad class warning (no extends)
+ // $x = new PropertyDefinition('deePER\noextendsCLASS', __NAMESPACE__);
+
+ // test bad class warning via mistyped basic typename:
+ // $x = new PropertyDefinition('ints', __NAMESPACE__);
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testUserFeatureToggling.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testUserFeatureToggling.php
new file mode 100755
index 0000000..e011523
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testUserFeatureToggling.php
@@ -0,0 +1,205 @@
+ 'string',
+ 'bar' => 'string',
+ ];
+}
+
+// --------------------------------------------------------
+
+// Test class that disallows functions but allows properties.
+
+class DisallowsFunctions extends CoreMap
+{
+ const ALLOW_VIRTUAL_FUNCTIONS = false;
+
+ public function getBar()
+ {
+ return $this->_getProperty('bar');
+ }
+}
+
+$jsonData = ['foo' => 'hello', 'bar' => 'world'];
+
+$x = new DisallowsFunctions($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+$x->printJson();
+
+// works since we have overridden that function manually
+printf("getBar(): \"%s\"\n", $x->getBar());
+
+// works since we allow direct property access in the class above
+$x->bar = 'changed via direct virtual property access';
+
+// look at the new value
+printf("getBar(): \"%s\"\n", $x->getBar());
+
+// does not work since we have no setter "setBar()" in the class above
+try {
+ $x->setBar('xyzzy');
+} catch (\Exception $e) {
+ printf("setBar(): %s\n", $e->getMessage());
+}
+
+// try all function variations of the "foo" property. none should work.
+try {
+ $x->hasFoo();
+} catch (\Exception $e) {
+ printf("hasFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->isFoo();
+} catch (\Exception $e) {
+ printf("isFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->getFoo();
+} catch (\Exception $e) {
+ printf("getFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->setFoo();
+} catch (\Exception $e) {
+ printf("setFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->unsetFoo();
+} catch (\Exception $e) {
+ printf("unsetFoo(): %s\n", $e->getMessage());
+}
+
+// --------------------------------------------------------
+
+// Test class that disallows properties but allows functions.
+
+class DisallowsProperties extends CoreMap
+{
+ const ALLOW_VIRTUAL_PROPERTIES = false;
+}
+
+$jsonData = ['foo' => 'hello', 'bar' => 'world'];
+
+$x = new DisallowsProperties($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+$x->printJson();
+
+// works since we allow functions
+printf("getBar(): \"%s\"\n", $x->getBar());
+$x->setBar('changed via virtual setBar() function acccess');
+
+// look at the new value
+printf("getBar(): \"%s\"\n", $x->getBar());
+
+// try all property acccess variations of the "foo" property. none should work.
+try {
+ $test = $x->foo;
+} catch (\Exception $e) {
+ printf("__get() via x->foo: %s\n", $e->getMessage());
+}
+
+try {
+ $x->foo[] = 'test'; // this __get()-trigger will fail too
+} catch (\Exception $e) {
+ printf("__get() via x->foo[]: %s\n", $e->getMessage());
+}
+
+try {
+ $x->foo = 'xyz';
+} catch (\Exception $e) {
+ printf("__set() via x->foo = ...: %s\n", $e->getMessage());
+}
+
+try {
+ isset($x->foo);
+} catch (\Exception $e) {
+ printf("__isset() via isset(x->foo): %s\n", $e->getMessage());
+}
+
+try {
+ empty($x->foo);
+} catch (\Exception $e) {
+ printf("__isset() via empty(x->foo): %s\n", $e->getMessage());
+}
+
+try {
+ unset($x->foo);
+} catch (\Exception $e) {
+ printf("__unset() via unset(x->foo): %s\n", $e->getMessage());
+}
+
+// --------------------------------------------------------
+
+// Test class that disallows both.
+
+class DisallowsBoth extends CoreMap
+{
+ const ALLOW_VIRTUAL_PROPERTIES = false;
+ const ALLOW_VIRTUAL_FUNCTIONS = false;
+}
+
+$x = new DisallowsBoth($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+
+try {
+ $test = $x->foo;
+} catch (\Exception $e) {
+ printf("__get() via x->foo: %s\n", $e->getMessage());
+}
+
+try {
+ $x->getFoo();
+} catch (\Exception $e) {
+ printf("getFoo(): %s\n", $e->getMessage());
+}
+
+// --------------------------------------------------------
+
+// Test class that extends "DisallowsBoth" and re-allows both.
+
+class ReallowsBoth extends DisallowsBoth
+{
+ const ALLOW_VIRTUAL_PROPERTIES = true;
+ const ALLOW_VIRTUAL_FUNCTIONS = true;
+}
+
+$x = new ReallowsBoth($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+
+printf("getFoo(): \"%s\"\n", $x->getFoo());
+printf("x->bar: \"%s\"\n", $x->bar);
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/nonRecursiveArrays.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/nonRecursiveArrays.php
new file mode 100755
index 0000000..0b07092
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/nonRecursiveArrays.php
@@ -0,0 +1,402 @@
+current());
+ }
+}
+
+// Algorithm v1: The initial idea I had...
+function array_flat_topdown_traverse(
+ array &$input)
+{
+ // Traverse top-down, processing level by level (going deeper and deeper).
+ $workStack = [&$input]; // The stack processes one array level at a time.
+ $nextStack = []; // Next stack with all deeper arrays found on this level.
+ $currentDepth = 1; // First level of input array should count from 1.
+ while (!empty($workStack)) {
+ // Pop a direct reference off the start of our FIFO stack.
+ reset($workStack);
+ $firstKey = key($workStack);
+ $pointer = &$workStack[$firstKey];
+ unset($workStack[$firstKey]);
+
+ // Now we're ready to act on the popped stack element...
+ foreach ($pointer as $k => &$v) {
+ // printf(
+ // "[D] %d %s\"%s\":%s\n",
+ // $currentDepth,
+ // str_repeat('-', $currentDepth),
+ // $k,
+ // is_array($v) ? '[]' : var_export($v, true)
+ // );
+
+ // Analyze the current array-child...
+ if (is_array($v)) {
+ // Add the discovered child-array to the end of the next-stack.
+ $nextStack[] = &$v;
+ } else {
+ // The child is a non-array element... Send it to the callback!
+ // TODO: Give callback key + value ref + array depth
+ }
+ }
+
+ // If the work-stack is finished, switch to the next (deeper) stack.
+ if (empty($workStack)) {
+ $workStack = $nextStack;
+ $nextStack = [];
+ $currentDepth++;
+ }
+ }
+}
+
+// Algorithm v2: Avoids two count() calls per stack-element iteration.
+function array_flat_topdown_traverse2(
+ array &$input)
+{
+ // Traverse top-down, processing level by level (going deeper and deeper).
+ $workStack = [&$input]; // The stack processes one array level at a time.
+ $workStackSize = 1; // Hardcoded result of count($workStack).
+ $nextStack = []; // Next stack with all deeper arrays found on this level.
+ $currentDepth = 1; // First level of input array should count from 1.
+ while ($workStackSize > 0) {
+ // Pop a direct reference off the start of our FIFO stack.
+ reset($workStack);
+ $firstKey = key($workStack);
+ $pointer = &$workStack[$firstKey];
+ unset($workStack[$firstKey]);
+ $workStackSize--;
+
+ // Now we're ready to act on the popped stack element...
+ foreach ($pointer as $k => &$v) {
+ // printf(
+ // "[D] %d %s\"%s\":%s\n",
+ // $currentDepth,
+ // str_repeat('-', $currentDepth),
+ // $k,
+ // is_array($v) ? '[]' : var_export($v, true)
+ // );
+
+ // Analyze the current array-child...
+ if (is_array($v)) {
+ // Add the discovered child-array to the end of the next-stack.
+ $nextStack[] = &$v;
+ } else {
+ // The child is a non-array element... Send it to the callback!
+ // TODO: Give callback key + value ref + array depth
+ }
+ }
+
+ // If the work-stack is finished, switch to the next (deeper) stack.
+ if ($workStackSize <= 0) {
+ // NOTE: There's no need to assign to workStack by reference to
+ // avoid copy-on-write. Because when we set nextStack to a new
+ // value, PHP will realize that workStack is the only instance.
+ // In fact, by-ref is slower it also needs an unset($nextStack)
+ // call to break its own reference before doing $nextStack = [].
+ $workStack = $nextStack;
+ $workStackSize = count($workStack);
+ $nextStack = [];
+ $currentDepth++;
+ }
+ }
+}
+
+// Regular, old-school recursive function calls.
+function array_recursive_traverse(
+ array &$input,
+ $currentDepth = 1)
+{
+ // Recursion adds 1 level to the function call stack
+ // per depth-level of the array:
+ // debug_print_backtrace();
+
+ $nextDepth = $currentDepth + 1;
+ foreach ($input as $k => &$v) {
+ // printf(
+ // "[D] %d %s\"%s\":%s\n",
+ // $currentDepth,
+ // str_repeat('-', $currentDepth),
+ // $k,
+ // is_array($v) ? '[]' : var_export($v, true)
+ // );
+
+ if (is_array($v)) {
+ array_recursive_traverse($v, $nextDepth);
+ }
+ }
+}
+
+// Build an array data tree.
+function generateData(
+ $depth)
+{
+ $data = [];
+ $pointer = &$data;
+ for ($d = 0; $d < $depth; ++$d) {
+ // $subArr = ['x', 'y', ['z'], ['foo'], [['xxyyzzyy']]]; // Harder data.
+ $subArr = ['x', 'y', 'z', ['foo'], 'xxyyzzyy'];
+ $pointer[] = &$subArr;
+ $pointer = &$subArr;
+ unset($subArr); // Unlink, otherwise next assignment overwrites pointer.
+ }
+
+ return $data;
+}
+
+// Run a single test.
+function runTest(
+ $description,
+ $data,
+ $algorithm,
+ $iterations)
+{
+ $start = microtime(true);
+
+ switch ($algorithm) {
+ case 'array_flat_topdown_traverse':
+ for ($i = 0; $i < $iterations; ++$i) {
+ array_flat_topdown_traverse($data);
+ }
+ break;
+ case 'array_flat_topdown_traverse2':
+ for ($i = 0; $i < $iterations; ++$i) {
+ array_flat_topdown_traverse2($data);
+ }
+ break;
+ case 'array_recursive_traverse':
+ for ($i = 0; $i < $iterations; ++$i) {
+ array_recursive_traverse($data);
+ }
+ break;
+ case 'RecursiveIteratorIterator':
+ for ($i = 0; $i < $iterations; ++$i) {
+ $iterator = new \RecursiveIteratorIterator(
+ new RecursiveArrayOnlyIterator($data),
+ \RecursiveIteratorIterator::SELF_FIRST
+ );
+ // foreach ($iterator as $key => $value) {
+ // // echo "$key => $value\n";
+ // }
+ // This iteration method takes 15% longer than foreach,
+ // but it's the only way to get the depth, which we
+ // absolutely need to know in this project.
+ for (; $iterator->valid(); $iterator->next()) {
+ $key = $iterator->key();
+ $value = $iterator->current();
+ $depth = $iterator->getDepth();
+ }
+ }
+ break;
+ }
+
+ printf(
+ "%dx %s %s: %.0f milliseconds.\n",
+ $iterations, $description, $algorithm,
+ 1000 * (microtime(true) - $start)
+ );
+}
+
+// Run all algorithm tests at once.
+function runTestMulti(
+ $description,
+ $data,
+ $iterations,
+ $iteratorTestMode) // Time-saver: -1 off, 0 divide by ten, 1 normal
+{
+ if ($iteratorTestMode > -1) {
+ runTest($description, $data, 'RecursiveIteratorIterator',
+ $iteratorTestMode > 0 ? $iterations : (int) floor($iterations / 10));
+ }
+ runTest($description, $data, 'array_flat_topdown_traverse', $iterations);
+ runTest($description, $data, 'array_flat_topdown_traverse2', $iterations);
+ runTest($description, $data, 'array_recursive_traverse', $iterations);
+}
+
+// Special data test-tree for use together with debug-output (uncomment it in
+// the algorithms), to verify that each algorithm detects the current depth.
+$data = [
+ '1one' => [
+ '1two' => [
+ '1three-nonarr1' => '1',
+ '1three' => [
+ '1four-nonarr1' => '2',
+ '1four' => [
+ '1five' => '3',
+ ],
+ ],
+ ],
+ ],
+ '2one-nonarr1' => null,
+ '3one' => [
+ '3two-1' => [
+ '3three-nonarr1' => '4',
+ '3three-1' => [
+ '3four-1' => [
+ '3five-1' => [
+ '3six-nonarr1' => '5',
+ ],
+ ],
+ ],
+ '3three-nonarr2' => '6',
+ ],
+ '3two-nonarr1' => '7',
+ '3two-2' => [
+ '3three-nonarr3' => '8',
+ ],
+ '3two-nonarr2' => '9',
+ ],
+];
+
+// The "RecursiveIteratorIterator" is ~10x slower, so this setting saves time.
+// Values: -1 off, 0 divide by ten, 1 normal.
+$iteratorTestMode = -1;
+
+// Globally extend/shorten the amount of test iterations, or "1" for no scaling.
+$testScale = 1;
+
+// Output PHP version details.
+printf("[Running %dx tests on PHP version %s]\n", $testScale, PHP_VERSION);
+printf("[RecursiveIteratorIterator Tests: %s]\n", ['Disabled', 'Shortened by /10', 'Enabled'][$iteratorTestMode + 1]);
+
+// Test with normal data (6 levels deep).
+runTestMulti('normal-6', generateData(6), $testScale * 500000, $iteratorTestMode);
+
+// Test unusual data (50 levels deep).
+runTestMulti('rare-50', generateData(50), $testScale * 100000, $iteratorTestMode);
+
+// Now test with insanely deeply nested data.
+runTestMulti('insane-500', generateData(500), $testScale * 10000, $iteratorTestMode);
+
+// Let's do one final test with even more disgustingly deep arrays.
+runTestMulti('hellish-5000', generateData(5000), $testScale * 100, $iteratorTestMode);
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/prefixSplitAlgorithms.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/prefixSplitAlgorithms.php
new file mode 100755
index 0000000..8c0acfc
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/devtools/prefixSplitAlgorithms.php
@@ -0,0 +1,270 @@
+ 'string',
+ 'users' => 'User[]',
+ ];
+}
+
+class User extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'name' => 'string',
+ 'details' => 'UserDetails',
+ ];
+}
+
+class UserDetails extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'age' => 'int',
+ 'hired_date' => 'string',
+ ];
+}
+
+/*
+ * Now simply create the root object (a Section), and give it the section JSON:
+ */
+
+$section = new Section(json_decode($section_json, true));
+
+/*
+ * Here's how you would look at the available properties and their functions:
+ */
+
+$section->printPropertyDescriptions();
+
+/*
+ * Now let's access some data, via the functions shown by the previous command.
+ */
+
+printf("\n\nData Output Example:\n\nSection Title: %s\n", $section->getSectionTitle());
+foreach ($section->getUsers() as $user) {
+ // $user->printPropertyDescriptions(); // Uncomment to see User-functions.
+ // $user->printJson(); // Uncomment to look at the JSON data for that user.
+ printf(
+ "- User: %s\n Age: %s\n Hired Date: %s\n",
+ $user->getName(),
+ $user->getDetails()->getAge(),
+ $user->getDetails()->getHiredDate()
+ );
+}
+echo "\n\n";
+
+/*
+ * Lastly, let's demonstrate looking at the actual internal JSON data:
+ */
+
+// var_dump($section->asJson()); // Uncomment to get a JSON data string instead.
+// var_dump($section->getUsers()[0]->asJson()); // Property sub-object encoding.
+// var_dump(json_encode($section->getUsers())); // Property non-object values
+// // solvable via `json_encode()`.
+$section->printJson();
+
+/*
+ * There are a million other functions and features. Have fun exploring!
+ * Simply read the main src/LazyJsonMapper.php file for all documentation!
+ */
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/examples/import_example.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/examples/import_example.php
new file mode 100755
index 0000000..50d7014
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/examples/import_example.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'age' => 'int',
+ ];
+}
+
+class AdvancedUser extends User
+{
+ const JSON_PROPERTY_MAP = [
+ 'advanced' => 'string',
+ ];
+}
+
+class SomethingElse extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ AdvancedUser::class, // This is an "import class map"-command.
+ 'otherprop' => 'float[][]',
+ ];
+}
+
+/*
+ * This demonstrates that SomethingElse contains all fields from AdvancedUser
+ * (which in turn inherited User's map), as well as having its own "otherprop".
+ */
+
+$somethingelse = new SomethingElse();
+$somethingelse->printPropertyDescriptions();
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/examples/inheritance_example.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/examples/inheritance_example.php
new file mode 100755
index 0000000..899c187
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/examples/inheritance_example.php
@@ -0,0 +1,51 @@
+ 'string',
+ 'age' => 'int',
+ ];
+}
+
+class AdvancedUser extends User
+{
+ const JSON_PROPERTY_MAP = [
+ 'advanced' => 'string',
+ ];
+}
+
+/*
+ * This demonstrates that AdvancedUser contains all fields from User.
+ */
+
+$advanceduser = new AdvancedUser(json_decode($advanceduser_json, true));
+
+$advanceduser->printPropertyDescriptions();
+printf(
+ "\n\nName: %s\nAge: %s\nAdvanced: %s\n",
+ $advanceduser->getName(),
+ $advanceduser->getAge(),
+ $advanceduser->getAdvanced()
+);
+$advanceduser->printJson();
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/examples/unpredictable_data.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/examples/unpredictable_data.php
new file mode 100755
index 0000000..2d366a5
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/examples/unpredictable_data.php
@@ -0,0 +1,705 @@
+ 'string',
+ ];
+}
+
+/**
+ * This container supports unpredictable keys.
+ *
+ * Note that it doesn't define any properties in its JSON_PROPERTY_MAP. Instead,
+ * we use a custom getter which reads _all_ JSON properties and processes them!
+ *
+ * In other words, this _entire_ container will hold nothing but unpredictable
+ * data keys. (If you have a mixture, there are other examples further down.)
+ */
+class UnpredictableContainer extends LazyJsonMapper
+{
+ /**
+ * Cached key-value object translations.
+ *
+ * This is optional, but speeds up repeated calls to `getUserList()`, since
+ * it will store all previously converted objects for future re-use.
+ *
+ * @var array
+ */
+ protected $_userList;
+
+ /**
+ * Get the list of users.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return array Associative array of key-value pairs, keyed by userID.
+ */
+ public function getUserList()
+ {
+ // Tell LazyJsonMapper to give us all of our internal data as an array.
+ // NOTE: This creates a COPY of the array. It's not attached to the main
+ // storage, which means that any changes we make to the new array aren't
+ // going to affect the actual LazyJsonMapper object's own data. That
+ // won't matter for pure getter-based objects like this one (which is
+ // all you'll need in 99.9999999% of cases where you'll want to read
+ // unpredictable data). So if the user serializes this LazyJsonMapper
+ // object, or gets it "asJson()", etc, then they'd still be serializing
+ // the original, untouched internal data, which is exactly what we are
+ // retrieving here. So everything works out perfectly as long as we
+ // don't have any setters! (But setters will be demonstrated later.)
+ if ($this->_userList === null) {
+ // Get a copy of the internal data as an array, and cache it.
+ // NOTE: This only throws if there is unmappable data internally.
+ $this->_userList = $this->asArray(); // Throws.
+ }
+
+ // Loop through the list of JSON properties and convert every "array"
+ // value to a User object instead. That's because all of JSON's nested,
+ // not-yet-converted "JSON object values" are associative sub-arrays.
+ foreach ($this->_userList as &$value) {
+ if (is_array($value)) {
+ // NOTE: The User constructor can only throw if a custom _init()
+ // fails or if its class property map can't be compiled.
+ $value = new User($value); // Throws.
+ }
+ }
+
+ // Now just return our key-value array. The inner array values have
+ // been converted to User objects, which makes them easy to work with.
+ return $this->_userList;
+ }
+}
+
+/*
+ * Let's try it out with two sets of data that use unpredictable (numeric) keys!
+ */
+
+/*
+ * This JSON data consists of various numeric keys pointing at "User"-objects.
+ */
+$unpredictable_json1 = <<printJson();
+
+/*
+ * Now let's call our custom getter which retrieves all values and gives them to
+ * us as User objects. As you can see, their contents perfectly match the
+ * printJson() results, since our custom getter doesn't manipulate any data.
+ */
+foreach ($unpredictable1->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Let's do the same for another set of data with other keys...
+ */
+$unpredictable_json2 = <<printJson();
+
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Alright, that's all great... but what if we want to manipulate the data?
+ *
+ * Well, the loop above still has $userId and $userInfo variables that point at
+ * the last element it looped to... so let's try using those to set data! What
+ * can possibly go wrong!? ;-)
+ */
+printf("*** Changing the name of user #%s to 'FOO'.\n", $userId);
+$userInfo->setName('FOO');
+
+/*
+ * Now let's look at the contents of the "getUserList()" call...
+ *
+ * Because the user-list is cached internally in our custom object, it already
+ * refers to the exact same object instances... So it will indeed have updated.
+ */
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * But wait... what about the actual internal object data? Let's look at that!
+ *
+ * The name... is not updated...
+ */
+$unpredictable2->printJson();
+
+/*
+ * Uh oh... since we're using a custom getter which fetches the data totally
+ * detached from the core LazyJsonMapper storage, we will need another solution!
+ *
+ * These extra steps are ONLY needed if you want setters that actually work...
+ */
+
+/**
+ * Extends UnpredictableContainer with a custom setter.
+ *
+ * We could have put these functions on the main UnpredictableContainer, but
+ * this example is clearer by only explaining it here as a separate step for
+ * those who need setters...
+ */
+class UnpredictableContainerWithSetter extends UnpredictableContainer
+{
+ /**
+ * Syncs the user list cache with the LazyJsonMapper core storage.
+ *
+ * Note that we could build these steps into `setUserList()`. But by having
+ * it as a separate function, you are able to just update specific User
+ * objects and then call `syncUserList()` to write them back to the core.
+ *
+ * @throws LazyJsonMapperException
+ */
+ public function syncUserList()
+ {
+ // If no internal cache exists yet, get its value from LazyJsonMapper.
+ if ($this->_userList === null) {
+ $this->getUserList(); // Builds our "_userList" variable. Throws.
+ }
+
+ // Now, we need to create a new, internal LazyJsonMapper data array for
+ // our object. In undefined (unmapped) properties, you are ONLY allowed
+ // to store basic data types. Not objects. So we will need to convert
+ // all User objects to real array data. Otherwise OTHER calls would fail
+ // due to invalid internal data when we try things like `printJson()`.
+ $newObjectData = [];
+ foreach ($this->_userList as $k => $v) {
+ $newObjectData[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ // Now give the new object data to LazyJsonMapper, which ensures that it
+ // contains all of the same values as our updated cache! This replaces
+ // the ENTIRE internal JSON property storage, so be aware of that!
+ $this->assignObjectData($newObjectData); // Throws.
+ }
+
+ /**
+ * Replace the entire user list with another list.
+ *
+ * @param array $userList Associative array of User objects keyed by userID.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setUserList(
+ array $userList)
+ {
+ // First of all, let's instantly save their new value to our own
+ // internal cache, since our cache supports User objects and doesn't
+ // have to do any special transformations...
+ $this->_userList = $userList;
+
+ // Now sync our internal cache with the LazyJsonMapper object...! :-)
+ $this->syncUserList(); // Throws.
+
+ // Setters should always return "$this" to make them chainable!
+ return $this;
+ }
+}
+
+/*
+ * Alright, let's try the example again, with the new container that has proper
+ * support for setters!
+ */
+echo "\n\nUnpredictable data #2, with setter:\n";
+$unpredictable2 = new UnpredictableContainerWithSetter(json_decode($unpredictable_json2, true));
+
+$unpredictable2->printJson();
+
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Now let's try manipulating the data again!
+ */
+printf("*** Changing the name of user #%s to 'FOO'.\n", $userId);
+$userInfo->setName('FOO');
+
+/*
+ * Now let's look at the contents of the "getUserList()" call...
+ *
+ * Just as before, it has been updated since we use an internal cache which
+ * already refers to the exact same object instances... so our update is there!
+ */
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Now let's look at the internal LazyJsonMapper data storage...
+ *
+ * It is NOT updated. As expected...
+ */
+$unpredictable2->printJson();
+
+/*
+ * Now let's SYNC our cache back to the internal LazyJsonMapper data storage!
+ *
+ * And voila...!
+ */
+$unpredictable2->syncUserList();
+$unpredictable2->printJson();
+
+/*
+ * Let's also try our custom setter, which takes a whole array of User objects
+ * and does the syncing automatically!
+ */
+$users = $unpredictable2->getUserList();
+foreach ($users as $userId => $userInfo) {
+ $userInfo->setName('Updated...'.$userId.'!');
+}
+$unpredictable2->setUserList($users); // Replaces entire cache and syncs!
+
+/*
+ * Now let's look at the contents of our cache AND the LazyJsonMapper data!
+ *
+ * Everything has been updated, since our "setUserList()" call replaced the
+ * entire cache contents AND synced the new cache contents to the core storage.
+ */
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+$unpredictable2->printJson();
+
+/**
+ * This class handles a mixture of knowable and unknowable data.
+ *
+ * Let's end this with one more example... What if you DON'T want to define a
+ * whole custom container? What if you only want a SPECIFIC value within your
+ * map to be handling unpredictable keys? You could achieve that as follows!
+ *
+ * This class will contain less comments, for brevity. You hopefully understand
+ * all of the major workflow concepts by now! We will only explain new concepts.
+ * And this class won't show any `syncEmployees()` function, since you should
+ * understand how to do that now if you want that feature.
+ */
+class UnpredictableMixtureContainer extends LazyJsonMapper
+{
+ protected $_employees;
+
+ const JSON_PROPERTY_MAP = [
+ // This is a statically named "manager" property, which is a User.
+ 'manager' => 'User',
+ // This is a statically named "employees" property, which consists of
+ // unpredictable key-value pairs, keyed by userID.
+ 'employees' => 'mixed', // Must be 'mixed' (aka '') to allow sub-arrays.
+ ];
+
+ /**
+ * Get the list of employees.
+ *
+ * NOTE: This overrides the normal, automatic LazyJsonMapper getter! By
+ * naming our function identically, we override its behavior!
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return array
+ */
+ public function getEmployees()
+ {
+ if ($this->_employees === null) {
+ // Use the internal _getProperty() API to read the actual property.
+ // NOTE: This function only throws if "employees" contains any
+ // invalid non-basic data. Only basic PHP types are accepted.
+ $this->_employees = $this->_getProperty('employees'); // Throws.
+ }
+
+ foreach ($this->_employees as &$value) {
+ if (is_array($value)) {
+ $value = new User($value); // Throws.
+ }
+ }
+
+ return $this->_employees;
+ }
+
+ /**
+ * Set the list of employees.
+ *
+ * NOTE: This overrides the normal, automatic LazyJsonMapper setter! By
+ * naming our function identically, we override its behavior!
+ *
+ * @param array $employees
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setEmployees(
+ array $employees)
+ {
+ $this->_employees = $employees;
+
+ // We now need to construct a new, inner value for the property. Since
+ // it's a "mixed" property, it only accepts basic PHP types.
+ $newInnerValue = [];
+ foreach ($this->_employees as $k => $v) {
+ $newInnerValue[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ // Now use the internal _setProperty() API to set the new inner value.
+ // NOTE: This function only throws if the new value contains any
+ // invalid non-basic data. Only basic PHP types are accepted.
+ $this->_setProperty('employees', $newInnerValue);
+
+ return $this;
+ }
+}
+
+/*
+ * Let's try it out!
+ */
+$unpredictable_json3 = <<printJson();
+printf("The manager's name is: %s\n", $unpredictable3->getManager()->getName());
+foreach ($unpredictable3->getEmployees() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * Now let's set some new employee data... This time, just for fun, let's give
+ * it a virtual JSON array which we construct manually.
+ */
+$unpredictable3->setEmployees([
+ // Let's provide one value as a User object, since our setter supports
+ // the conversion of User objects back into plain arrays.
+ '10' => new User(['name' => 'Employee Ten']),
+ // However, we could also just provide the data as an array, since that's
+ // the goal that our setter performs... this is for really advanced users.
+ // Don't blame us if you break things by using this shortcut! ;-)
+ '11' => ['name' => 'Employee Eleven'],
+]);
+
+/*
+ * Let's also update the manager, which is done via a normal, automatic
+ * LazyJsonMapper setter, since that property is completely defined. And since
+ * the "manager" object is owned by the LazyJsonMapper core, we're able to chain
+ * its getters and setters to select the manager's User object and set its name!
+ */
+$unpredictable3->getManager()->setName('New Manager!');
+
+/*
+ * Now let's look at all current data again!
+ */
+$unpredictable3->printJson();
+printf("The manager's name is: %s\n", $unpredictable3->getManager()->getName());
+foreach ($unpredictable3->getEmployees() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * But wait... there's yet another way to solve this!
+ *
+ * Since we know that our "mixture container"'s `employees` value is a key-value
+ * storage of User objects, in other words it's "an unpredictable key-value
+ * container", then we CAN just tell LazyJsonMapper to map that property to a
+ * `UnpredictableContainer[WithSetter]` which we defined earlier. That way, the
+ * "employees" values are handled automatically by a neat sub-container.
+ *
+ * In fact, we could do something even better! We could define a basic
+ * "unpredictable keys" container, and then define subclasses of it for various
+ * types of unpredictable containers. Let's do that instead!
+ */
+
+/**
+ * This class defines a core "untyped" container of unpredictable data-keys.
+ *
+ * Unpredictable data is data with keys that cannot be known ahead of time, such
+ * as objects whose values are keyed by things like user IDs.
+ *
+ * Here's an example of such unpredictable data: `{"9323":{"name":"foo"}}`
+ *
+ * The `getData()` function retrieves all key-value pairs, converted to the
+ * optional `$_type` (if one is set via a subclass). And `setData()` writes
+ * the new data back into the core `LazyJsonMapper` container. Most people will
+ * not need to use the setter. It's just provided as an extra feature.
+ *
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class CoreUnpredictableContainer extends LazyJsonMapper
+{
+ // Let's disable direct access to this container via anything other than
+ // the functions that WE define ourselves! That way, people cannot use
+ // virtual properties/functions to manipulate the core data storage.
+ const ALLOW_VIRTUAL_PROPERTIES = false;
+ const ALLOW_VIRTUAL_FUNCTIONS = false;
+
+ /**
+ * Data cache to avoid constant processing every time the getter is used.
+ *
+ * @var array
+ */
+ protected $_cache;
+
+ /**
+ * What class-type to convert all sub-object values into.
+ *
+ * Defaults to no conversion. Override this value via a subclass!
+ *
+ * Always use the FULL path to the target class, with a leading backslash!
+ * The leading backslash ensures that it's found via a strict, global path.
+ *
+ * Example: `\Foo\BarClass`.
+ *
+ * @var string
+ */
+ protected $_type;
+
+ /**
+ * Get the data array of this unpredictable container.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ if ($this->_cache === null) {
+ $this->_cache = $this->asArray(); // Throws.
+ }
+
+ if ($this->_type !== null) {
+ foreach ($this->_cache as &$value) {
+ if (is_array($value)) {
+ $value = new $this->_type($value); // Throws.
+ }
+ }
+ }
+
+ return $this->_cache;
+ }
+
+ /**
+ * Set the data array of this unpredictable container.
+ *
+ * @param array $value The new data array.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setData(
+ array $value)
+ {
+ $this->_cache = $value;
+
+ $newObjectData = [];
+ foreach ($this->_cache as $k => $v) {
+ $newObjectData[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ $this->assignObjectData($newObjectData); // Throws.
+
+ return $this;
+ }
+}
+
+/**
+ * This class defines an "unpredictable container of User objects".
+ *
+ * It's very easy to define other containers. Simply create them like this and
+ * override their `$_type` property to any other valid LazyJsonMapper class.
+ */
+class UserUnpredictableContainer extends CoreUnpredictableContainer
+{
+ // The FULL path to the target class, with leading backslash!
+ // NOTE: The leading backslash ensures it's found via a strict, global path.
+ protected $_type = '\User';
+}
+
+/**
+ * This is our new and improved, final class!
+ *
+ * Here is our final object for mapping our unpredictable "employees" data...
+ * As you can see, it is much easier to create this class now that we have
+ * defined a core, re-usable "unpredictable container" above.
+ */
+class UnpredictableMixtureContainerTwo extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ // This holds a regular User object.
+ 'manager' => 'User',
+ // This property is an unpredictable container of User objets.
+ 'employees' => 'UserUnpredictableContainer',
+ ];
+}
+
+/*
+ * Let's try it out!
+ */
+$unpredictable_json4 = <<printJson();
+printf("The manager's name is: %s\n", $unpredictable4->getManager()->getName());
+foreach ($unpredictable4->getEmployees()->getData() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * And let's update the value of the inner, unpredictable container!
+ *
+ * The container itself takes care of updating the LazyJsonMapper data storage!
+ */
+$unpredictable4->getEmployees()->setData([
+ '123' => ['name' => 'Final Employee 123'],
+ '456' => ['name' => 'Final Employee 456'],
+]);
+
+/*
+ * Now finish by looking at all of the data again, via the LazyJsonMapper core
+ * object and via the `getEmployees()` object's cache... They are identical!
+ */
+$unpredictable4->printJson();
+printf("The manager's name is: %s\n", $unpredictable4->getManager()->getName());
+foreach ($unpredictable4->getEmployees()->getData() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * And that's it! Hopefully you NEVER have to work with nasty, unpredictable
+ * data like this. If you're able to control the JSON format, you should always
+ * design it properly with known keys instead. But at least you now know about
+ * multiple great methods for working with objects that have unpredictable keys!
+ *
+ * There are other methods too, such as if your class contains a blend of known
+ * (defined) keys and unpredictable keys, in which case you'd need to fetch via
+ * `asArray()` as in the `UnpredictableContainer` example, and then you'd simply
+ * filter out all known keys from your cache, to get _just_ the unpredictable
+ * keys. And if you need setters in that scenario, your setter functions would
+ * be a bit more complex since they would need to use `assignObjectData()` AND
+ * would have to provide BOTH the known data AND the unknown data. One way of
+ * doing that would be to use `_getProperty()` to merge in the CURRENT values of
+ * each core property into your NEW object-data array BEFORE you assign it. But
+ * I leave that extremely rare scenario as an excercise for you, dear reader!
+ *
+ * You should go out and adapt all of this code to fit your own needs! ;-)
+ *
+ * For example, if you don't need the unpredictable values to be converted
+ * to/from a specific object type, then simply skip the conversion code.
+ *
+ * If you don't need caching for performance, then skip the caching code.
+ *
+ * If you don't need setters/syncing, then skip all of that code.
+ *
+ * You may also want to disable the user-options `ALLOW_VIRTUAL_PROPERTIES` and
+ * `ALLOW_VIRTUAL_FUNCTIONS` on your unpredictable containers, so users cannot
+ * manipulate the unpredictable data via LazyJsonMapper's automatic functions!
+ * That's what we did for CoreUnpredictableContainer, to ensure that nobody can
+ * destroy its internal data by touching it directly. They can only manipulate
+ * the data via its safe, public `getData()` and `setData()` functions!
+ *
+ * And you may perhaps prefer to write a custom base-class which has a few other
+ * helper-functions for doing these kinds of data translations, caching and
+ * syncing, to make your own work easier (such as the CoreUnpredictableContainer
+ * example above). That way, your various sub-classes could just call your
+ * internal helper functions to do the required processing automatically! :-)
+ *
+ * The possibilities are endless! Have fun!
+ */
+echo "\n\nHave fun!\n";
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/phpdoc.dist.xml b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/phpdoc.dist.xml
new file mode 100755
index 0000000..acd326c
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/phpdoc.dist.xml
@@ -0,0 +1,24 @@
+
+
+ LazyJsonMapper
+
+ docs/cache
+ utf8
+
+ TODO
+ FIXME
+
+
+ php
+
+
+
+ docs/output
+
+
+
+
+
+ src
+
+
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/phpunit.xml.dist b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/phpunit.xml.dist
new file mode 100755
index 0000000..2fa7a92
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/phpunit.xml.dist
@@ -0,0 +1,9 @@
+
+
+
+
+ tests
+
+
+
\ No newline at end of file
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/BadPropertyDefinitionException.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/BadPropertyDefinitionException.php
new file mode 100755
index 0000000..24fb2f8
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/BadPropertyDefinitionException.php
@@ -0,0 +1,29 @@
+_badClassNameA = $badClassNameA;
+ if (!is_string($badClassNameB)) {
+ $badClassNameB = null;
+ }
+ $this->_badClassNameB = $badClassNameB;
+
+ if ($badClassNameA !== null && $badClassNameB !== null) {
+ parent::__construct(sprintf(
+ 'Circular reference between classes "%s" and "%s" in JSON property map import instruction.',
+ $badClassNameA, $badClassNameB
+ ));
+ } else {
+ parent::__construct('Circular reference in JSON property map import instruction.');
+ }
+ }
+
+ /**
+ * Get the name of the first class involved in the circular map.
+ *
+ * @return string|null
+ */
+ public function getBadClassNameA()
+ {
+ return $this->_badClassNameA;
+ }
+
+ /**
+ * Get the name of the second class involved in the circular map.
+ *
+ * @return string|null
+ */
+ public function getBadClassNameB()
+ {
+ return $this->_badClassNameB;
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/LazyJsonMapperException.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/LazyJsonMapperException.php
new file mode 100755
index 0000000..f9b6e51
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/LazyJsonMapperException.php
@@ -0,0 +1,29 @@
+bad_definitions = array_merge_recursive(
+ $this->bad_definitions,
+ $other->bad_definitions
+ );
+ $this->missing_definitions = array_merge_recursive(
+ $this->missing_definitions,
+ $other->missing_definitions
+ );
+ }
+
+ /**
+ * Adds a problem description to the internal state.
+ *
+ * @param string $definitionSource The class which has the problem.
+ * @param string $problemType Type of problem. Either `bad_definitions`
+ * or `missing_definitions`.
+ * @param string $problemMessage A message describing the actual problem.
+ *
+ * @throws LazyJsonMapperException If any of the parameters are invalid.
+ *
+ * @see ClassAnalysis::hasProblems()
+ */
+ public function addProblem(
+ $definitionSource,
+ $problemType,
+ $problemMessage)
+ {
+ if (!is_string($definitionSource) || !is_string($problemMessage)) {
+ throw new LazyJsonMapperException('The definitionSource and problemMessage parameters must be strings.');
+ }
+ if ($problemType !== 'bad_definitions' && $problemType !== 'missing_definitions') {
+ throw new LazyJsonMapperException('The problemType parameter must be either "bad_definitions" or "missing_definitions".');
+ }
+
+ if (!isset($this->{$problemType}[$definitionSource])) {
+ $this->{$problemType}[$definitionSource] = [];
+ }
+
+ $this->{$problemType}[$definitionSource][] = $problemMessage;
+ }
+
+ /**
+ * Convert the per-class arrays to sorted lists of missing/bad properties.
+ *
+ * Removes all duplicate messages and sorts everything nicely. It is
+ * recommended to only call this function a single time, on the final
+ * `ClassAnalysis` object (after all other steps are finished).
+ */
+ public function sortProblemLists()
+ {
+ foreach (['bad_definitions', 'missing_definitions'] as $problemType) {
+ // Sort the problem messages within each class.
+ foreach ($this->{$problemType} as $definitionSource => $messages) {
+ $this->{$problemType}[$definitionSource] = array_unique($messages);
+ natcasesort($this->{$problemType}[$definitionSource]);
+ }
+
+ // Sort the outer array (the class names).
+ ksort($this->{$problemType}, SORT_NATURAL | SORT_FLAG_CASE);
+ }
+ }
+
+ /**
+ * Check whether any problems were discovered.
+ *
+ * In that case, it's recommended to use `generateNiceSummaries()` to format
+ * user-readable messages about the problems.
+ *
+ * @return bool
+ *
+ * @see ClassAnalysis::generateNiceSummaries()
+ */
+ public function hasProblems()
+ {
+ return !empty($this->bad_definitions) || !empty($this->missing_definitions);
+ }
+
+ /**
+ * Generates nicely formatted problem summaries for this class analysis.
+ *
+ * @return array An array with formatted messages for every type of analysis
+ * which actually had errors, keyed by the problem type. If no
+ * errors, the returned array will be empty.
+ *
+ * @see ClassAnalysis::hasProblems()
+ * @see ClassAnalysis::generateNiceSummariesAsString()
+ */
+ public function generateNiceSummaries()
+ {
+ $problemSummaries = [];
+ if (!empty($this->bad_definitions)) {
+ // Build a nice string containing all encountered bad definitions.
+ $strSubChunks = [];
+ foreach ($this->bad_definitions as $className => $messages) {
+ $strSubChunks[] = sprintf(
+ '"%s": ([\'%s\'])',
+ $className, implode('\'], and [\'', $messages)
+ );
+ }
+ $problemSummaries['bad_definitions'] = sprintf(
+ 'Bad JSON property definitions in %s.',
+ implode(', and in ', $strSubChunks)
+ );
+ }
+ if (!empty($this->missing_definitions)) {
+ // Build a nice string containing all missing class properties.
+ $strSubChunks = [];
+ foreach ($this->missing_definitions as $className => $messages) {
+ $strSubChunks[] = sprintf(
+ '"%s": ("%s")',
+ $className, implode('", "', $messages)
+ );
+ }
+ $problemSummaries['missing_definitions'] = sprintf(
+ 'Missing JSON property definitions in %s.',
+ implode(', and in ', $strSubChunks)
+ );
+ }
+
+ return $problemSummaries;
+ }
+
+ /**
+ * Generates a nicely formatted problem summary string for this class analysis.
+ *
+ * This helper combines all summaries and returns them as a single string
+ * (rather than as an array), which is very useful when displaying ALL
+ * errors to a user as a message.
+ *
+ * @return string The final string. Is an empty string if no errors exist.
+ *
+ * @see ClassAnalysis::hasProblems()
+ * @see ClassAnalysis::generateNiceSummaries()
+ */
+ public function generateNiceSummariesAsString()
+ {
+ return implode(' ', $this->generateNiceSummaries());
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Export/PropertyDescription.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Export/PropertyDescription.php
new file mode 100755
index 0000000..8212140
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Export/PropertyDescription.php
@@ -0,0 +1,250 @@
+asString();
+ $isRelativeTypePath = false;
+ if ($allowRelativeTypePath && $propDef->isObjectType) {
+ $relativeType = Utilities::createRelativeClassPath(
+ Utilities::splitStrictClassPath($strictOwnerClassPath),
+ // NOTE: This is safe because the asString() value is always a
+ // strict class path with optional [] suffixes (which will not
+ // interfere with the splitting process).
+ Utilities::splitStrictClassPath($finalType)
+ );
+ if ($finalType !== $relativeType) {
+ $isRelativeTypePath = true;
+ $finalType = $relativeType;
+ }
+ }
+
+ // Perform the translation from the property name to its FunctionCase.
+ $translation = new PropertyTranslation($propName); // Throws.
+
+ // Now just store all of the user-friendly descriptions.
+ $this->owner = $strictOwnerClassPath;
+ $this->is_defined = $isDefined;
+ $this->name = $propName;
+ $this->type = $finalType;
+ $this->is_basic_type = !$propDef->isObjectType;
+ $this->is_relative_type_path = $isRelativeTypePath;
+ $this->function_has = sprintf('bool has%s()', $translation->propFuncCase);
+ $this->function_is = sprintf('bool is%s()', $translation->propFuncCase);
+ $this->function_get = sprintf('%s get%s()', $finalType, $translation->propFuncCase);
+ $this->function_set = sprintf('$this set%s(%s $value)', $translation->propFuncCase, $finalType);
+ $this->function_unset = sprintf('$this unset%s()', $translation->propFuncCase);
+ $this->func_case = $translation->propFuncCase;
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/LazyJsonMapper.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/LazyJsonMapper.php
new file mode 100755
index 0000000..6f837a6
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/LazyJsonMapper.php
@@ -0,0 +1,2692 @@
+some_value` and `$item->some_value = 'foo'`.
+ *
+ * The virtual properties can be disabled via an option.
+ *
+ * - Automatically provides object-oriented "virtual functions", which let you
+ * interact with the data in a fully object-oriented way via functions such
+ * as `$item->getSomeValue()` and `$item->setSomeValue('foo')`. We support a
+ * large range of different functions for manipulating the JSON data, and you
+ * can see a list of all available function names for all of your properties
+ * by simply running `$item->printPropertyDescriptions()`.
+ *
+ * The virtual functions can be disabled via an option.
+ *
+ * - Includes the `LazyDoctor` tool, which _automatically_ documents all of
+ * your `LazyJsonMapper`-based classes so that their virtual properties and
+ * functions become _fully_ visible to your IDE and to various intelligent
+ * code analysis tools. It also performs class diagnostics by compiling all
+ * of your class property maps, which means that you can be 100% sure that
+ * all of your maps are valid (compilable) if this tool runs successfully.
+ *
+ * - We provide a complete, internal API which your subclasses can use to
+ * interact with the data inside of the JSON container. This allows you to
+ * easily override the automatic functions or create additional functions
+ * for your objects. To override core functions, just define a function with
+ * the exact same name on your object and make it do whatever you want to.
+ *
+ * Here are some examples of function overriding:
+ *
+ * ```php
+ * public function getFoo()
+ * {
+ * // try to read property, and handle a special "current_time" value.
+ * $value = $this->_getProperty('foo');
+ * if ($value === 'current_time') { return time(); }
+ * return $value;
+ * }
+ * public function setFoo(
+ * $value)
+ * {
+ * // if they try to set value to "md5", use a special value instead.
+ * if ($value === 'md5') { $value = md5(time()); }
+ * return $this->_setProperty('foo', $value);
+ * }
+ * ```
+ *
+ * - All mapping/data conversion is done "lazily", on a per-property basis.
+ * When you access a property, that specific property is mapped/converted to
+ * the proper type as defined by your class property map. No time or memory
+ * is wasted converting properties that you never touch.
+ *
+ * - Strong type-system. The class property map controls the exact types and
+ * array depths. You can fully trust that the data you get/set will match
+ * your specifications. Invalid data that mismatches the spec is impossible.
+ *
+ * - Advanced settings system. Everything is easily configured via PHP class
+ * constants, which means that your class-settings are stateless (there's no
+ * need for any special "settings/mapper object" to keep track of settings),
+ * and that all settings are immutable constants (which means that they are
+ * reliable and can never mutate at runtime, so that you can fully trust that
+ * classes will always behave as-defined in their code).
+ *
+ * If you want to override multiple core settings identically for all of your
+ * classes, then simply create a subclass of `LazyJsonMapper` and configure
+ * all of your settings on that, and then derive all of your other classes
+ * from your re-configured subclass!
+ *
+ * - The world's most advanced mapper definition system. Your class property
+ * maps are defined in an easy PHPdoc-style format, and support multilevel
+ * arrays (such as `int[][]` for "an array of arrays of ints"), relative
+ * types (so you can map properties to classes/objects that are relative to
+ * the namespace of the class property map), parent inheritance (all of your
+ * parent `extends`-hierarchy's maps will be included in your final property
+ * map) and even multiple inheritance (you can literally "import" an infinite
+ * number of other maps into your class, which don't come from your own
+ * parent `extends`-hierarchy).
+ *
+ * - Inheriting properties from parent classes or importing properties from
+ * other classes is a zero-cost operation thanks to how efficient our
+ * property map compiler is. So feel free to import everything you need.
+ * You can even use this system to create importable classes that just hold
+ * "collections" of shared properties, which you import into other classes.
+ *
+ * - The class property maps are compiled a single time per-class at runtime,
+ * the first time a class is used. The compilation process fully verifies
+ * and compiles all property definitions, all parent maps, all inherited
+ * maps, and all maps of all classes you link properties to.
+ *
+ * If there are any compilation problems due to a badly written map anywhere
+ * in your hierarchy, you will be shown the exact problem in great detail.
+ *
+ * In case of success, the compiled and verified maps are all stored in an
+ * incredibly memory-efficient format in a global cache which is shared by
+ * your whole PHP runtime, which means that anything in your code or in any
+ * other libraries which accesses the same classes will all share the cached
+ * compilations of those classes, for maximum memory efficiency.
+ *
+ * - You are also able to access JSON properties that haven't been defined in
+ * the class property map. In that case, they are treated as undefined and
+ * untyped (`mixed`) and there won't be any automatic type-conversion of such
+ * properties, but it can still be handy in a pinch.
+ *
+ * - There are lots of data export/output options for your object's JSON data,
+ * to get it back out of the object again: As a multi-level array, as nested
+ * stdClass objects, or as a JSON string representation of your object.
+ *
+ * - We include a whole assortment of incredibly advanced debugging features:
+ *
+ * You can run the constructor with `$requireAnalysis` to ensure that all
+ * of your JSON data is successfully mapped according to your class property
+ * map, and that you haven't missed defining any properties that exist in the
+ * data. In case of any problems, the analysis message will give you a full
+ * list of all problems encountered in your entire JSON data hierarchy.
+ *
+ * For your class property maps themselves, you can run functions such as
+ * `printPropertyDescriptions()` to see a complete list of all properties and
+ * how they are defined. This helps debug your class inheritance and imports
+ * to visually see what your final class map looks like, and it also helps
+ * users see all available properties and all of their virtual functions.
+ *
+ * And for the JSON data, you can use functions such as `printJson()` to get
+ * a beautiful view of all internal JSON data, which is incredibly helpful
+ * when you (or your users) need to figure out what's available inside the
+ * current object instance's data storage.
+ *
+ * - A fine-grained and logical exception-system which ensures that you can
+ * always trust the behavior of your objects and can catch problems easily.
+ * And everything we throw is _always_ based on `LazyJsonMapperException`,
+ * which means that you can simply catch that single "root" exception
+ * whenever you don't care about fine-grained differentiation.
+ *
+ * - Clean and modular code ensures stability and future extensibility.
+ *
+ * - Deep code documentation explains everything you could ever wonder about.
+ *
+ * - Lastly, we implement super-efficient object serialization. Everything is
+ * stored in a tightly packed format which minimizes data size when you need
+ * to transfer your objects between runtimes.
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class LazyJsonMapper implements Serializable, JsonSerializable
+{
+ /**
+ * Whether "direct virtual properties" access is enabled.
+ *
+ * This constant can be overridden in your subclasses to toggle the option.
+ *
+ * It is recommended that all of your brand new projects create a subclass
+ * of `LazyJsonMapper` which disables this option, and then you simply
+ * derive all of your other classes from that subclass so that nothing in
+ * your project has direct virtual property access enabled.
+ *
+ * That's because there are many quirks with how PHP implements virtual
+ * properties, which can lead to performance slowdowns and lack of data
+ * validation if you use direct access in CERTAIN scenarios (without writing
+ * proper code). And if users are going to be interacting with YOUR library,
+ * then you SHOULD disable virtual properties so that your users don't fail
+ * to implement their proper usage. PHP's virtual property quirks are all
+ * described in the documentation for `__get()`. Please read it and decide
+ * whether you want to allow "direct virtual properties".
+ *
+ * The only reason why this feature is enabled by default is that it helps
+ * people easily migrate from legacy projects.
+ *
+ * @var bool
+ *
+ * @see LazyJsonMapper::__get() More details about virtual properties.
+ */
+ const ALLOW_VIRTUAL_PROPERTIES = true;
+
+ /**
+ * Whether "virtual functions" access is enabled.
+ *
+ * This constant can be overridden in your subclasses to toggle the option.
+ *
+ * It's the recommended access method. It always ensures complete data
+ * validation and the highest performance. However, some people may want to
+ * disable it in favor of manually defining your object's functions instead.
+ * (This library provides a complete internal API for manipulating the
+ * object's JSON data from your own custom class-functions.)
+ *
+ * @var bool
+ *
+ * @see LazyJsonMapper::__call() More details about virtual functions.
+ */
+ const ALLOW_VIRTUAL_FUNCTIONS = true;
+
+ /**
+ * Whether we should cache all magic virtual function translations.
+ *
+ * This constant can be overridden in your subclasses to toggle caching.
+ *
+ * The cache stores all of the current runtime's encountered magic/virtual
+ * function name translations, such as `SomeProperty` (via `getSomeProperty`
+ * and `setSomeProperty`, etc) and the fact that it refers to a JSON
+ * property that will be named either `some_property` (if snake style)
+ * or `someProperty` (if camel style).
+ *
+ * Entries are added to the cache one-by-one every time you call a function
+ * on an uncached property name for the first time. The next time you call a
+ * function which accesses the same property name, then there's no need to
+ * analyze the function name again (to figure out what JSON data property it
+ * refers to), since the translation is already cached. It is cached on a
+ * per-property name basis. So a SINGLE cached translation of `SomeProperty`
+ * will be shared by ALL function calls related to that property (such as
+ * all of the core functions; `hasSomeProperty`, `isSomeProperty`,
+ * `getSomeProperty`, `setSomeProperty`, and `unsetSomeProperty`).
+ *
+ * At the cost of a very tiny bit of RAM, this caching greatly speeds up
+ * magic function calls so that they only take about 16% as long as they
+ * would without the cache. However, they're still very fast even without a
+ * cache, and you may be accessing so many different properties that you'd
+ * prefer to avoid a cache for memory reasons (if you prefer lower memory
+ * needs over pure speed). The choice is yours.
+ *
+ * To calculate the memory needs of your own project's virtual function
+ * cache, you simply need to know that an average cache entry uses ~248.6
+ * bytes of RAM on PHP 7 and ~510.4 bytes on PHP 5. Note that the size per
+ * name translation is small enough to be measured in BYTES (NOT kilobytes)!
+ *
+ * Those averages were calculated from a huge sample-size of 10000 function
+ * names, and are very accurate for real-world JSON data.
+ *
+ * To calculate your own cache memory size, think about how many different
+ * JSON properties you'll need to access in your project. Most normal
+ * projects would probably only access at most 100 different property names
+ * (such as `getComments`, `setTime`, etc), since you most likely won't
+ * access every property in the JSON data. So only count the properties
+ * you'll access. And also remember that identical property names used
+ * across different classes, and all the different functions for accessing
+ * the same property, will still only require a SINGLE global cache entry
+ * per property name.
+ *
+ * The storage needs for 100 different property name translations would be
+ * as follows. As you can see, the cache is very memory-efficient:
+ *
+ * - PHP 7: 100x ~248.6 bytes = 24 860 bytes (~24.9 kilobytes).
+ * - PHP 5: 100x ~510.4 bytes = 51 040 bytes (~51.1 kilobytes).
+ *
+ * (If you're still using PHP 5, you should really consider upgrading to
+ * PHP 7 with its better memory efficiency and its much faster engine!)
+ *
+ * It is highly recommended to use caching, and it is enabled by default
+ * unless you override the constant in your class, as follows:
+ *
+ * ```php
+ * class MyUncachedLazyJsonMapper extends LazyJsonMapper
+ * {
+ * const USE_MAGIC_LOOKUP_CACHE = false;
+ * }
+ * ```
+ *
+ * With the above code, `MyUncachedLazyJsonMapper` (and anything extending
+ * from it) would run without using the magic function translation cache,
+ * which means that all magic "virtual function" calls on instances of that
+ * class would have to redo their property translations every time.
+ *
+ * @var bool
+ */
+ const USE_MAGIC_LOOKUP_CACHE = true;
+
+ /**
+ * Tells us how to map JSON properties to internal PHP types or objects.
+ *
+ * This constant can be overridden in your subclasses, to add custom JSON
+ * mapping definitions to your class. It is recommended to always do so.
+ *
+ * The value must be an array of key-value pairs, where the key is the name
+ * of a JSON property and the value is a string with the definition for that
+ * specific property in PHPdoc-style. We will then perform automatic, strict
+ * lazy-conversion of the value to the target type whenever you access that
+ * property. (However, be aware that we always allow `NULL` values in any
+ * property, and will never enforce/convert those to the target type.)
+ *
+ * The following built-in types are supported: `bool`, `int`, `float`,
+ * `string`. The JSON data will be type-converted to match that type.
+ *
+ * Note that if the type is set to `mixed` or an empty string `""` instead,
+ * then the property will allow any of the built-in types (`bool`, `int`,
+ * `float`, `string`, `NULL` (as always) and multi-level arrays of any of
+ * those types). There simply won't be any "forced" type-conversion to any
+ * specific basic type for that property since you haven't defined any
+ * strict type.
+ *
+ * Setting up untyped properties is still useful even if you don't know its
+ * type, since defining the property in the class map ensures that you can
+ * always get/set/access/use that property even if it's unavailable in the
+ * current object instance's internal JSON data.
+ *
+ * You can also map values to objects. In the case of objects (classes), the
+ * classes MUST inherit from `LazyJsonMapper` so they support all necessary
+ * mapping features. To assign a class to a property, you can either write a
+ * FULL (global) class path starting with a leading backslash `\`, such as
+ * `\Foo\Bar\Baz`. Alternatively, you can use a RELATIVE path related to the
+ * namespace of the CURRENT MAP'S class, such as `Bar\Baz` (if your current
+ * class is `\Foo\Whatever`, then it'd be interpreted as `\Foo\Bar\Baz`).
+ * Anything that DOESN'T start with a leading backslash is interpreted as a
+ * relative class path! Just be aware that your class `use`-statements are
+ * completely ignored since PHP has no mechanism to let us detect those.
+ *
+ * It's also possible to map properties to the core `LazyJsonMapper` object
+ * if you simply want an object-oriented container for its data without
+ * writing a custom class. You can do that by defining the property type as
+ * either `\LazyJsonMapper\LazyJsonMapper`, or as `LazyJsonMapper` (which is
+ * a special shortcut that ALWAYS resolves to the core class). But it's
+ * always best to write a proper class, so that its properties are reliable.
+ *
+ * Lastly, you can map "arrays of TYPE" as well. Simply add one or more `[]`
+ * brackets to the end of the type. For example, `int[]` means "array of
+ * ints", and `\Foo[][][]` means "array of arrays of arrays of `\Foo`
+ * objects". It may be easier to understand those mentally if you read them
+ * backwards and say the words "array of" every time you see a `[]` bracket.
+ * (Note: You can also use the array notation with `mixed[][]`, which would
+ * then strictly define that the value MUST be mixed data at an exact depth
+ * of 2 arrays, and that no further arrays are allowed deeper than that.)
+ *
+ * The assigned types and array-depths are STRICTLY validated. That's an
+ * integral part of the `LazyJsonMapper` container, since it guarantees your
+ * class property map interface will be strictly followed, and that you can
+ * fully TRUST the data you're interacting with. If your map says that the
+ * array data is at depth 8 and consists of `YourObject` objects, then we'll
+ * make sure the data is indeed at depth 8 and their value type is correct!
+ *
+ * That goes for arrays too. If you define an `int[]` array of ints, then
+ * we'll ensure that the array has sequential numeric keys starting at 0 and
+ * going up without any gaps, exactly as a JSON array is supposed to be. And
+ * if you define an object `YourObject`, then we'll ensure that the input
+ * JSON data consists of an array with associative keys there (which is used
+ * as the object properties), exactly as a JSON object is supposed to be.
+ *
+ * In other words, you can TOTALLY trust your data if you've assigned types!
+ *
+ * Example property map:
+ *
+ * ```php
+ * const JSON_PROPERTY_MAP = [
+ * 'some_string' => 'string',
+ * 'an_object' => '\YourProject\YourObject',
+ * 'array_of_numbers' => 'int[]',
+ * 'array_of_objects' => 'RelativeObject[]',
+ * 'untyped_value' => '', // shorthand for 'mixed' below:
+ * 'another_untyped_value' => 'mixed', // allows multilevel arrays
+ * 'deep_arr_of_arrs_of_int' => 'int[][]',
+ * 'array_of_mixed' => 'mixed[]', // enforces 1-level depth
+ * 'array_of_generic_objects' => 'LazyJsonMapper[]',
+ * ];
+ * ```
+ *
+ * Also note that we automatically inherit all of the class maps from all
+ * parents higher up in your object-inheritance chain. In case of a property
+ * name clash, the deepest child class definition of it takes precedence.
+ *
+ * It is also worth knowing that we support a special "multiple inheritance"
+ * instruction which allows you to "import the map" from one or more other
+ * classes. The imported maps will be merged onto your current class, as if
+ * the entire hierarchy of properties from the target class (including the
+ * target class' inherited parents and own imports) had been "pasted inside"
+ * your class' property map at that point. And you don't have to worry
+ * about carefully writing your inheritance relationships, because we have
+ * full protection against circular references (when two objects try to
+ * import each other) and will safely detect that issue at runtime whenever
+ * we try to compile the maps of either of those bad classes with their
+ * badly written imports.
+ *
+ * The instruction for importing other maps is very simple: You just have to
+ * add an unkeyed array element with a reference to the other class. The
+ * other class must simply refer to the relative or full path of the class,
+ * followed by `::class`.
+ *
+ * Example of importing one or more maps from other classes:
+ *
+ * ```php
+ * const JSON_PROPERTY_MAP = [
+ * 'my_own_prop' => 'string',
+ * OtherClass::class, // relative class path
+ * 'redefined_prop' => float,
+ * \OtherNamespace\SomeClass::class, // full class path
+ * ];
+ * ```
+ *
+ * The imports are resolved in the exact order they're listed in the array.
+ * Any property name clashes will always choose the version from the latest
+ * statement in the list. So in this example, our class would first inherit
+ * all of its own parent (`extends`) class maps. Then it would add/overwrite
+ * `my_own_prop`. Then it imports everything from `OtherClass` (and its
+ * parents/imports). Then it adds/overwrites `redefined_prop` (useful if you
+ * want to re-define some property that was inherited from the other class).
+ * And lastly, it imports everything from `\OtherNamespace\SomeClass` (and
+ * its parents/imports). As long as there are no circular references,
+ * everything will compile successfully and you will end up with a very
+ * advanced final map! And any other class which later inherits from
+ * (extends) or imports YOUR class will inherit the same advanced map from
+ * you! This is REAL "multiple inheritance"! ;-)
+ *
+ * Also note that "relative class path" properties are properly inherited as
+ * pointing to whatever was relative to the original inherited/imported
+ * class where the property was defined. You don't have to worry about that.
+ *
+ * The only thing you should keep in mind is that we ONLY import the maps.
+ * We do NOT import any functions from the imported classes (such as its
+ * overridden functions), since there's no way for us to affect how PHP
+ * resolves function calls to "copy" functions from one class to another. If
+ * you need their functions, then you should simply `extends` from the class
+ * to get true PHP inheritance instead of only importing its map. Or you
+ * could just simply put your functions in PHP Traits and Interfaces so
+ * that they can be re-used by various classes without needing inheritance!
+ *
+ * Lastly, here's an important general note about imports and inheritance:
+ * You DON'T have to worry about "increased memory usage" when importing or
+ * inheriting tons of other classes. The runtime "map compiler" is extremely
+ * efficient and re-uses the already-compiled properties inherited from its
+ * parents/imports, meaning that property inheritance is a ZERO-COST memory
+ * operation! In fact, re-definitions are also zero-cost as long as your
+ * class re-defines a property to the exact same type that it had already
+ * inherited/imported. Such as `Base: foo:string, Child: foo:string`. In
+ * that case, we detect that the definitions are identical and just re-use
+ * the compiled property from `Base` instead. It's only when a class adds
+ * NEW/MODIFIED properties that memory usage increases a little bit! In
+ * other words, inheritance/imports are a very good thing, and are always a
+ * better idea than manually writing similar lists of properties in various
+ * unrelated (not extending each other) classes. In fact, if you have a
+ * situation where many classes need similar properties, then it's a GREAT
+ * idea to create special re-usable "property collection" classes and then
+ * simply importing THOSE into ALL of the different classes that need those
+ * sets of properties! You can even use that technique to get around PHP's
+ * `use`-statement limitations (simply place a "property collection" class
+ * container in the namespace that had all of your `use`-classes, and define
+ * its properties via easy, relative paths there, and then simply make your
+ * other classes import THAT container to get all of its properties).
+ *
+ * As you can see, the mapping and inheritance system is extremely powerful!
+ *
+ * Note that the property maps are analyzed and compiled at runtime, the
+ * first time we encounter each class. After compilation, the compiled maps
+ * become immutable (cannot be modified). So there's no point trying to
+ * modify this variable at runtime. That's also partially the reason why
+ * this is defined through a constant, since they prevent you from modifying
+ * the array at runtime and you believing that it would update your map. The
+ * compiled maps are immutable at runtime for performance & safety reasons!
+ *
+ * Have fun!
+ *
+ * @var array
+ *
+ * @see LazyJsonMapper::printPropertyDescriptions() Easily looking at and
+ * debugging your final
+ * class property map.
+ * @see LazyJsonMapper::printJson() Looking at the JSON data
+ * contents of the current
+ * object instance.
+ * @see LazyJsonMapper::exportClassAnalysis() Looking for problems
+ * with your class map.
+ * However, the same test
+ * can also be achieved
+ * via `$requireAnalysis`
+ * as a constructor flag.
+ */
+ const JSON_PROPERTY_MAP = [];
+
+ /**
+ * Magic virtual function lookup cache.
+ *
+ * Globally shared across all instances of `LazyJsonMapper` classes. That
+ * saves tons of memory, since multiple different components/libraries in
+ * your project can use any functions they need, such as `getComments()`,
+ * and then all OTHER code that uses a `comments` property would share that
+ * cached translation too, which ensures maximum memory efficiency!
+ *
+ * It is private to prevent subclasses from modifying it.
+ *
+ * @var array
+ *
+ * @see LazyJsonMapper::clearGlobalMagicLookupCache()
+ */
+ private static $_magicLookupCache = [];
+
+ /**
+ * Storage container for compiled class property maps.
+ *
+ * Class maps are built at runtime the FIRST time we encounter each class.
+ * This cache is necessary so that we instantly have fully-validated maps
+ * without needing to constantly re-build/validate the class property maps.
+ *
+ * Compilation only happens the first time that we encounter the class
+ * during the current runtime (unless you manually decide to clear the
+ * cache at any point). And the compiled map format is extremely optimized
+ * and very memory-efficient (for example, subclasses and inherited classes
+ * all share the same compiled PropertyDefinition objects, and those objects
+ * themselves are extremely optimized). So you don't have to worry about
+ * memory usage at all.
+ *
+ * You should think about this exactly like PHP's own class loader/compiler.
+ * Whenever you request a class for the first time, PHP reads its .php file
+ * and parses and compiles its code and then keeps that class in memory for
+ * instant re-use. That's exactly like what this "property map cache" does.
+ * It compiles the maps the first time the class is used, and then it just
+ * stays in memory for instant re-use. So again, don't worry about it! :-)
+ *
+ * Globally shared across all instances of `LazyJsonMapper` classes. Which
+ * ensures that classes are truly only compiled ONCE during PHP's runtime,
+ * even when multiple parts of your project or other libraries use the same
+ * classes. And it also means that there's no need for you to manually
+ * maintain any kind of personal "cache-storage class instance". The global
+ * storage takes care of that for you effortlessly!
+ *
+ * It is private to prevent subclasses from modifying it.
+ *
+ * @var PropertyMapCache
+ *
+ * @see LazyJsonMapper::clearGlobalPropertyMapCache()
+ */
+ private static $_propertyMapCache;
+
+ /**
+ * Direct reference to the current object's property definition cache.
+ *
+ * This is a reference. It isn't a copy. So the memory usage of each class
+ * instance stays identical since the same cache is shared among them all.
+ * Therefore, you don't have to worry about seeing this in `var_dump()`.
+ *
+ * It is private to prevent subclasses from modifying it.
+ *
+ * @var array
+ *
+ * @see LazyJsonMapper::printPropertyDescriptions() Easily looking at and
+ * debugging your final
+ * class property map.
+ */
+ private $_compiledPropertyMapLink;
+
+ /**
+ * Container for this specific object instance's JSON data.
+ *
+ * Due to the lazy-conversion of objects, some parts of the tree can be
+ * objects and others can be JSON sub-arrays that have not yet been turned
+ * into objects. That system ensures maximum memory and CPU efficiency.
+ *
+ * This container is private to prevent subclasses from modifying it. Use
+ * the protected functions to indirectly check/access/modify values instead.
+ *
+ * @var array
+ */
+ private $_objectData;
+
+ /**
+ * Constructor.
+ *
+ * You CANNOT override this constructor. That's because the constructor and
+ * its arguments and exact algorithm are too important to allow subclasses
+ * to risk breaking it (especially since you must then perfectly maintain an
+ * identical constructor argument list and types of thrown exceptions, and
+ * would always have to remember to call our constructor first, and so on).
+ *
+ * Furthermore, trying to have custom constructors on "JSON data container
+ * objects" MAKES NO SENSE AT ALL, since your JSON data objects can be
+ * created recursively and automatically from nested JSON object data. So
+ * there's no way you could run your object's custom constructors then!
+ *
+ * Instead, there is a separate `_init()` function which you can override in
+ * your subclasses, for safe custom initialization. And if you NEED to use
+ * some external variables in certain functions in your JSON data classes,
+ * then simply add those variables as arguments to those class-functions!
+ *
+ * Also note that there are many reasons why the JSON data must be provided
+ * as an array instead of as an object. Most importantly, the memory usage
+ * is much lower if you decode to an array (because a \stdClass requires as
+ * much RAM as an array WITH object overhead ON TOP of that), and also
+ * because the processing we do is more efficient when we have an array.
+ *
+ * Lastly, this seems like a prominent enough place to mention something
+ * that's VERY important if you are handling JSON data on 32-bit systems:
+ * PHP running as a 32-bit process does NOT support 64-bit JSON integers
+ * natively. It will cast them to floats instead, which may lose precision,
+ * and fails completely if you then cast those numbers to strings, since you
+ * will get float notation or `INT_MAX` capping depending on what you are
+ * doing with the data (ie `printf('%d')` would give `INT_MAX`, and `%s`
+ * would give scientific notation like `8.472E+22`). All of those situations
+ * can be avoided by simply telling PHP to decode big numbers to strings:
+ *
+ * ```php
+ * json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+ * ```
+ *
+ * If you are ever going to handle 64-bit integers on 32-bit systems, then
+ * you will NEED to do the above, as well as ensuring that all of your class
+ * `JSON_PROPERTY_MAP` definitions never use any `int` field type (since
+ * that would convert the safe strings back into truncated 32-bit integers).
+ * Instead, you should define those fields as `string`-type in your map.
+ *
+ * @param array $objectData Decoded JSON data as an array (NOT object).
+ * @param bool $requireAnalysis Whether to throw an exception if any of the
+ * raw JSON properties aren't defined in the
+ * class property map (are missing), or if any
+ * of the encountered property-classes are bad
+ * (fail to construct with the JSON data;
+ * usually only possible due to a custom
+ * `_init()` or when its raw JSON data value
+ * wasn't actually an object at all), or any
+ * problems with array-depth or type coercion.
+ * This option is very useful for debugging
+ * when creating/updating your custom classes.
+ * But BEWARE that it causes the WHOLE
+ * `$objectData` tree to be recursively parsed
+ * and analyzed, which is really TERRIBLE for
+ * performance. So DON'T use this permanently!
+ *
+ * @throws LazyJsonMapperException If the class hierarchy contains any
+ * invalid JSON property map/definition
+ * which prevents successful class map
+ * compilation, or if JSON data analysis
+ * requested and any of the map's property
+ * definitions are bad/missing. Also if a
+ * custom class `_init()` threw any kind of
+ * exception.
+ *
+ * @see LazyJsonMapper::_init()
+ * @see LazyJsonMapper::assignObjectData()
+ */
+ final public function __construct(
+ array $objectData = [],
+ $requireAnalysis = false)
+ {
+ // Create the global property map cache object if not yet initialized.
+ if (self::$_propertyMapCache === null) {
+ self::$_propertyMapCache = new PropertyMapCache();
+ }
+
+ // Compile this class property map if not yet built and cached.
+ // NOTE: Validates all definitions the first time and throws if invalid
+ // definitions, invalid map, or if there are any circular map imports.
+ // NOTE: This aborts compilation, automatically rolls back the failed
+ // compilation attempt, AND throws - at the FIRST-discovered problem
+ // during map compilation! It will only show the single, specific
+ // problem which caused compilation to fail. So it won't inundate the
+ // user's screen with all individual error message in case there are
+ // more problems. If their class maps have multiple problems that
+ // prevent compilation, they'll have to see and fix them one by one.
+ // But the user will only have to fix their maps once, since compilation
+ // issues are a core problem with their map and aren't a runtime issue!
+ $thisClassName = get_class($this);
+ if (!isset(self::$_propertyMapCache->classMaps[$thisClassName])) {
+ PropertyMapCompiler::compileClassPropertyMap( // Throws.
+ self::$_propertyMapCache,
+ $thisClassName
+ );
+ }
+
+ // Now link this class instance directly to its own property-cache, via
+ // direct REFERENCE for high performance (to avoid map array lookups).
+ // The fact that it's a link also avoids the risk of copy-on-write.
+ $this->_compiledPropertyMapLink = &self::$_propertyMapCache->classMaps[$thisClassName];
+
+ // Assign the JSON data, run optional analysis, and then _init().
+ $this->assignObjectData($objectData, $requireAnalysis); // Throws.
+ }
+
+ /**
+ * Assign a new internal JSON data array for this object.
+ *
+ * This is used by the constructor for assigning the initial internal data
+ * state, but can also be very useful for users who want to manually replace
+ * the contents of their object at a later time.
+ *
+ * For example, it might suit your project design better to first construct
+ * an empty object, and *then* pass it to some other function which actually
+ * fills it with the JSON data. This function allows you to achieve that.
+ *
+ * The entire internal data storage will be replaced with the new data.
+ *
+ * @param array $objectData Decoded JSON data as an array (NOT object).
+ * @param bool $requireAnalysis Whether to analyze the JSON data and throw
+ * if there are problems with mapping. See
+ * `__construct()` for more details.
+ *
+ * @throws LazyJsonMapperException If JSON data analysis requested and any
+ * of the map's property definitions are
+ * bad/missing. Also if a custom class
+ * `_init()` threw any kind of exception.
+ *
+ * @see LazyJsonMapper::__construct()
+ * @see LazyJsonMapper::_init()
+ */
+ final public function assignObjectData(
+ array $objectData = [],
+ $requireAnalysis = false)
+ {
+ // Save the provided JSON data array.
+ $this->_objectData = $objectData;
+
+ // Recursively look for missing/bad JSON properties, if scan requested.
+ // NOTE: "Bad" in this case includes things like fatal mismatches
+ // between the definition of a property and the actual JSON data for it.
+ // NOTE: This analysis includes ALL problems with the class and its
+ // entire hierarchy (recursively), which means that it can produce
+ // really long error messages. It will warn about ALL missing properties
+ // that exist in the data but are not defined in the class, and it will
+ // warn about ALL bad properties that existed in the data but cannot be
+ // mapped in the way that the class map claims.
+ if ($requireAnalysis) {
+ $analysis = $this->exportClassAnalysis(); // Never throws.
+ if ($analysis->hasProblems()) {
+ // Since there were problems, throw with all combined summaries.
+ throw new LazyJsonMapperException(
+ $analysis->generateNiceSummariesAsString()
+ );
+ }
+ }
+
+ // Call the custom initializer, where the subclass can do its own init.
+ // NOTE: This is necessary for safely encapsulating the subclass' code.
+ try {
+ $this->_init();
+ } catch (LazyUserException $e) {
+ throw $e; // Re-throw user-error as is, since it's a proper exception.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY other exception!
+ // Ensure that they didn't throw something dumb from their code.
+ // We'll even swallow the message, to truly discourage misuse.
+ throw new LazyUserException(
+ 'Invalid exception thrown by _init(). Must use LazyUserException.'
+ );
+ }
+ }
+
+ /**
+ * Initializer for custom subclass construction / data updates.
+ *
+ * This is where you can perform your custom subclass initialization, since
+ * you are unable to override the main constructor.
+ *
+ * We automatically run this function at the end of the normal constructor,
+ * as well as every time that you manually use `assignObjectData()` to
+ * replace the object's data. (When new data is assigned, you should treat
+ * yourself as a new object, which is why `_init()` will run again.)
+ *
+ * `WARNING:` Please RESIST the urge to touch ANY of the internal JSON data
+ * during this initialization. All data will always be automatically
+ * validated during actual retrieval and setting, so you can always trust
+ * that the final types WILL match your class property map definitions.
+ *
+ * Remember that any extra work you do in `_init()` will run EVERY time an
+ * instance of your object is created, even when those objects live within a
+ * main object. So if your function is heavy, it will slow down operation of
+ * the class; and your parsing of the properties would be counter-productive
+ * against the goal of "lazy parsing" of JSON data on a when-accessed basis!
+ *
+ * Instead, it's preferable to just override specific property getter and
+ * setter functions, to make your class return different default values if a
+ * certain internal data field is missing an expected value. But in general,
+ * your ultimate USER CODE should be responsible for most of THAT checking,
+ * by simply looking for `NULL` (denoting a missing value in the JSON data
+ * container), and then deciding what to do with that in your final project.
+ *
+ * Lastly, there are a few rules that MUST be followed by your `_init()`:
+ *
+ * 1. As explained above, but worth repeating again: Your `LazyJsonMapper`
+ * classes are supposed to be LIGHT-weight "JSON data containers". So
+ * please DO NOT treat this as a "standard class constructor". The more
+ * work you do in `_init()`, the slower your object creation is. And since
+ * your object is a JSON container which is automatically created from
+ * data, you can expect LOTS of instances of your class to be created
+ * during normal runtime! That's also why your `_init()` function does not
+ * get any parameters! That is to discourage misuse as a normal class.
+ *
+ * Also remember that you may not even need `_init()`, since you can simply
+ * give your properties default values instead of using `_init()`, such as
+ * by writing `public $myproperty = true;`. All instances of that object
+ * would then start with that value set to `TRUE` by default.
+ *
+ * 2. You can ONLY throw `LazyUserException`. All other exceptions will be
+ * blocked and turned into a generic `LazyUserException`. This is done to
+ * ensure that your custom init function cannot break the contract that the
+ * `LazyJsonMapper` constructor guarantees in its listed exceptions.
+ *
+ * (That's also the reason why all built-in functions except `_init()` are
+ * marked `final`: So that subclasses cannot break the `LazyJsonMapper`
+ * API/interface contract. If something IS a `LazyJsonMapper`, it MUST
+ * behave as a `LazyJsonMapper`, and the `final` functions guarantee that!)
+ *
+ * 3. If your class extends from `LazyJsonMapper`, then there's no need to
+ * call `parent::_init()` (since our core `_init()` does nothing). But if
+ * you extend from ANY OTHER CLASS, you MUST call your parent's init BEFORE
+ * doing any work, just to guarantee that your WHOLE parent-class hierarchy
+ * is fully initialized first.
+ *
+ * 4. Understand and accept the fact that your personal class properties
+ * will NOT be serialized if you serialize an object. Only the JSON data
+ * array will be kept. The `_init()` function will be called again when you
+ * unserialize the object data, as if your object had just been created for
+ * the first time. This is done to save space and to discourage misuse
+ * of your `LazyJsonMapper` containers.
+ *
+ * Remember that your classes are supposed to be lightweight JSON data
+ * containers, which give you a strongly typed, automatic, object-oriented
+ * interface to your data. Classes "with advanced constructors and tons of
+ * properties" are NOT suitable as JSON containers and belong ELSEWHERE in
+ * the rest of your project!
+ *
+ * @throws LazyUserException If there is any fatal error which prevents
+ * initialization. This stops object construction
+ * if this is the initial construction. However,
+ * it doesn't affect data-assignment if you throw
+ * during a later `assignObjectData()` call.
+ *
+ * @see LazyJsonMapper::assignObjectData()
+ */
+ protected function _init()
+ {
+ // This standard _init does nothing by default...
+
+ // Always call your parent's init FIRST, to avoid breaking your class
+ // hierarchy's necessary initializers. But that can be skipped if your
+ // direct parent is LazyJsonMapper, since we do nothing in our _init().
+
+ // parent::_init();
+
+ // After your parent hierarchy is initialized, you're welcome to perform
+ // YOUR OWN class initialization, such as setting up state variables...
+
+ // $this->someFlag = true;
+
+ // However, that kind of usage is highly discouraged, since your objects
+ // will lose their state again when serialized and then unserialized.
+ // Remember: Your class is meant to be a "light-weight JSON container",
+ // and NOT some ultra-advanced normal class. Always think about that!
+ }
+
+ /**
+ * Export human-readable descriptions of class/object instance properties.
+ *
+ * The defined (class property map) properties are always included. You can
+ * optionally choose to also include undefined properties that only exist in
+ * the current object instance's JSON data, but which aren't in the class.
+ * Such properties are dangerous, since they only exist in the current data.
+ *
+ * Furthermore, you can choose whether any class-types for properties should
+ * use absolute/global paths (ie `\Foo\Bar\Baz`), or whether they should use
+ * paths relative to the class they are owned by (ie `Baz`) when possible.
+ * And that's ONLY possible whenever the target type lives within the same
+ * namespace as the class. Any properties from other namespaces will still
+ * use absolute paths.
+ *
+ * Note that if relative types are used, they are ALWAYS relative to the
+ * class that the current object IS AN INSTANCE OF. This is true even when
+ * those properties were inherited/imported from another class!
+ *
+ * Relative mode is mostly meant for class-documentation, to be placed
+ * inside each of your class files. That way, the relative paths will be
+ * perfectly understood by your IDE. The absolute, non-relative format is
+ * preferable in other, more general "runtime usage" since it is totally
+ * clear about exactly which final object each class path refers to.
+ *
+ * @param bool $allowRelativeTypes If `TRUE`, object types will use relative
+ * paths (compared to this class) whenever
+ * possible.
+ * @param bool $includeUndefined Whether to also include properties that
+ * only exist in the current object
+ * instance's JSON data, but aren't defined
+ * in the actual class property map.
+ *
+ * @throws LazyJsonMapperException If any properties cannot be described.
+ * But that should never be able to happen.
+ *
+ * @return PropertyDescription[] Associative array of property descriptions,
+ * sorted by property name in case-insensitive
+ * natural order.
+ *
+ * @see LazyJsonMapper::printPropertyDescriptions()
+ */
+ final public function exportPropertyDescriptions(
+ $allowRelativeTypes = false,
+ $includeUndefined = false)
+ {
+ if (!is_bool($allowRelativeTypes) || !is_bool($includeUndefined)) {
+ throw new LazyJsonMapperException('The function arguments must be booleans.');
+ }
+
+ // First include all of the defined properties for the current class.
+ $descriptions = [];
+ $ownerClassName = get_class($this);
+ foreach ($this->_compiledPropertyMapLink as $propName => $propDef) {
+ $descriptions[$propName] = new PropertyDescription( // Throws.
+ $ownerClassName,
+ $propName,
+ $propDef,
+ $allowRelativeTypes
+ );
+ }
+
+ // Also include all undefined, JSON-only data properties if desired.
+ if ($includeUndefined) {
+ $undefinedProperty = UndefinedProperty::getInstance();
+ foreach ($this->_objectData as $propName => $v) {
+ if (!isset($descriptions[$propName])) {
+ $descriptions[$propName] = new PropertyDescription( // Throws.
+ $ownerClassName,
+ $propName,
+ $undefinedProperty,
+ $allowRelativeTypes
+ );
+ }
+ }
+ }
+
+ // Sort the descriptions by the case-insensitive name of each property.
+ ksort($descriptions, SORT_NATURAL | SORT_FLAG_CASE); // Natural order.
+
+ return $descriptions;
+ }
+
+ /**
+ * Print human-readable descriptions of class/object instance properties.
+ *
+ * This helper is provided as a quick and easy debug feature, and helps you
+ * look at your compiled class property maps, or to quickly look up how a
+ * certain property needs to be accessed in its function-name form.
+ *
+ * Please read the description of `exportPropertyDescriptions()` if you want
+ * more information about the various options.
+ *
+ * @param bool $showFunctions Whether to show the list of functions for
+ * each property. Which is very helpful, but
+ * also very long. So you may want to
+ * disable this option.
+ * @param bool $allowRelativeTypes If `TRUE`, object types will use relative
+ * paths (compared to this class) whenever
+ * possible.
+ * @param bool $includeUndefined Whether to also include properties that
+ * only exist in the current object
+ * instance's JSON data, but aren't defined
+ * in the actual class property map.
+ *
+ * @throws LazyJsonMapperException If any properties cannot be described.
+ * But that should never be able to happen.
+ *
+ * @see LazyJsonMapper::exportPropertyDescriptions()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function printPropertyDescriptions(
+ $showFunctions = true,
+ $allowRelativeTypes = false,
+ $includeUndefined = false)
+ {
+ if (!is_bool($showFunctions) || !is_bool($allowRelativeTypes) || !is_bool($includeUndefined)) {
+ throw new LazyJsonMapperException('The function arguments must be booleans.');
+ }
+
+ // Generate the descriptions.
+ $descriptions = $this->exportPropertyDescriptions( // Throws.
+ $allowRelativeTypes,
+ $includeUndefined
+ );
+
+ // Create some bars for output formatting.
+ $equals_bar = str_repeat('=', 60);
+ $dash_bar = str_repeat('-', 60);
+
+ // Header.
+ printf(
+ '%s%s> Class: "%s"%s Supports: [%s] Virtual Functions [%s] Virtual Properties%s%s%s Show Functions: %s.%s Allow Relative Types: %s.%s Include Undefined Properties: %s.%s%s%s',
+ $equals_bar,
+ PHP_EOL,
+ Utilities::createStrictClassPath(get_class($this)),
+ PHP_EOL,
+ static::ALLOW_VIRTUAL_FUNCTIONS ? 'X' : ' ',
+ static::ALLOW_VIRTUAL_PROPERTIES ? 'X' : ' ',
+ PHP_EOL,
+ $dash_bar,
+ PHP_EOL,
+ $showFunctions ? 'Yes' : 'No',
+ PHP_EOL,
+ $allowRelativeTypes ? 'Yes' : 'No',
+ PHP_EOL,
+ $includeUndefined ? 'Yes' : 'No',
+ PHP_EOL,
+ $equals_bar,
+ PHP_EOL
+ );
+
+ // Properties.
+ $lastPropertyNum = count($descriptions);
+ $padNumDigitsTo = strlen($lastPropertyNum);
+ if ($padNumDigitsTo < 2) {
+ $padNumDigitsTo = 2; // Minimum 2-digit padding: "09".
+ }
+ $alignPadding = 4 + (2 * $padNumDigitsTo); // " #/" plus the digits.
+ $thisPropertyNum = 0;
+ foreach ($descriptions as $property) {
+ $thisPropertyNum++;
+
+ // Output core information about the property.
+ printf(
+ ' #%s/%s: "%s"%s%s%s: "%s"%s%s',
+ str_pad($thisPropertyNum, $padNumDigitsTo, '0', STR_PAD_LEFT),
+ str_pad($lastPropertyNum, $padNumDigitsTo, '0', STR_PAD_LEFT),
+ $property->name,
+ !$property->is_defined ? ' (Not in class property map!)' : '',
+ PHP_EOL,
+ str_pad('* Type', $alignPadding, ' ', STR_PAD_LEFT),
+ $property->type,
+ $property->is_basic_type ? ' (Basic PHP type)' : '',
+ PHP_EOL
+ );
+
+ // Optionally output the function list as well.
+ if ($showFunctions) {
+ foreach (['has', 'is', 'get', 'set', 'unset'] as $function) {
+ printf(
+ '%s: %s%s',
+ str_pad($function, $alignPadding, ' ', STR_PAD_LEFT),
+ $property->{"function_{$function}"},
+ PHP_EOL
+ );
+ }
+ }
+
+ // Dividers between properties.
+ if ($thisPropertyNum !== $lastPropertyNum) {
+ echo $dash_bar.PHP_EOL;
+ }
+ }
+
+ // Handle empty property lists.
+ if (empty($descriptions)) {
+ echo '- No properties.'.PHP_EOL;
+ }
+
+ // Footer.
+ echo $equals_bar.PHP_EOL;
+ }
+
+ /**
+ * Get a processed copy of this object instance's internal data contents.
+ *
+ * It is recommended that you save the result if you intend to re-use it
+ * multiple times, since each call to this function will need to perform
+ * copying and data conversion from our internal object representation.
+ *
+ * Note that the conversion process will recursively validate and convert
+ * all properties in the entire internal data hierarchy. You can trust that
+ * the returned result will be perfectly accurate and follow your class map
+ * property type-rules. This does however mean that the data may not be the
+ * same as the input array you gave to `__construct()`. For example, if your
+ * input contained an array `["1", "2"]` and your type definition map says
+ * that you want `int` for that property, then you'd get `[1, 2]` as output.
+ *
+ * The conversion is always done on a temporary COPY of the internal data,
+ * which means that you're welcome to run this function as much as you want
+ * without causing your object to grow from resolving all its sub-objects.
+ *
+ * It also means the returned copy belongs to YOU, and that you're able to
+ * do ANYTHING with it, without risk of affecting any of OUR internal data.
+ *
+ * `WARNING:` If you intend to use the result to `json_encode()` a new JSON
+ * object then please DON'T do that. Look at `asJson()` instead, which gives
+ * you full control over all JSON output parameters and properly handles the
+ * conversion in all scenarios, without you needing to do any manual work.
+ *
+ * Also look at the separate `asStdClass()` and `asArray()` functions, for
+ * handy shortcuts instead of manually having to call this longer function.
+ *
+ * @param string $objectRepresentation What container to use to represent
+ * `LazyJsonMapper` objects. Can be
+ * either `array` (useful if you require
+ * that the result is compatible with
+ * `__construct()`), or `stdClass` (the
+ * best choice if you want to be able to
+ * identify subobjects via
+ * `is_object()`).
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return stdClass|array A processed copy of the internal data, using the
+ * desired container type to represent all objects.
+ *
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function exportObjectDataCopy(
+ $objectRepresentation = 'array')
+ {
+ if (!in_array($objectRepresentation, ['stdClass', 'array'], true)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Invalid object representation type "%s". Must be either "stdClass" or "array".',
+ $objectRepresentation
+ ));
+ }
+
+ // Make a shallow copy (copy-on-write) so that we will avoid affecting
+ // our actual internal object data during the parsing below. Otherwise
+ // we would turn all of OUR internal, still-unconverted sub-object
+ // arrays into real objects on our REAL instance, and permanently cause
+ // our object instance's memory usage to go up after an export call.
+ $copy = clone $this;
+
+ // Perform a NON-RECURSIVE analysis of the copy's top-level (own)
+ // properties. This will CONVERT all of their values to the proper type,
+ // and perform further sub-object creation (convert sub-object arrays
+ // into real objects), etc. It also ensures no illegal values exist.
+ // NOTE: For performance, we DON'T want recursive analysis, since we'll
+ // soon be asking any sub-objects to analyze themselves one-by-one too!
+ $analysis = $copy->exportClassAnalysis(false); // Never throws.
+
+ // Abort if there are any BAD definitions (conversion failure due to
+ // mismatches between defined class map and the actual data).
+ if (!empty($analysis->bad_definitions)) { // Ignore missing_definitions
+ $problemSummaries = $analysis->generateNiceSummaries();
+
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert data to %s: %s',
+ $objectRepresentation,
+ $problemSummaries['bad_definitions'] // Tells them exact error.
+ ));
+ }
+
+ // Now recursively process every other sub-object within the copy's data
+ // via exportObjectDataCopy(), thus forcing all nested sub-objects
+ // (which are still DIRECT REFERENCES to OUR "$this" original versions
+ // of the objects, since objects are always by reference) to copy and
+ // validate/convert THEMSELVES and their data and then return it
+ // within the type of container we need. For a perfect final result.
+ // NOTE: We iterate by &$value reference, but that WON'T affect the
+ // objects they point to. It's a reference to the "_objectData" entry.
+ array_walk_recursive($copy->_objectData, function (&$value, $key) use ($objectRepresentation) {
+ // Only process objects. Everything else is already perfect (either
+ // converted to the target-type if mapped or is valid "mixed" data).
+ if (is_object($value)) {
+ // Verify that the object is an instance of LazyJsonMapper.
+ // NOTE: It SHOULDN'T be able to be anything else, since the
+ // object analysis above has already verified that any "mixed"
+ // (non-LazyJsonMapper) properties only contain basic PHP types.
+ // But this check is a cheap safeguard against FUTURE bugs.
+ if (!$value instanceof self) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert data to %s: Unexpected "%s" object in property/key "%s", but we expected an instance of a LazyJsonMapper object.',
+ $objectRepresentation,
+ Utilities::createStrictClassPath(get_class($value)),
+ $key
+ ));
+ }
+
+ // Now just ask the sub-object to give us a copy of ITS verified
+ // + converted data wrapped in the desired container type.
+ // NOTE: This will be resolved recursively as-necessary for all
+ // objects in the entire data tree.
+ $value = $value->exportObjectDataCopy($objectRepresentation); // Throws.
+ }
+ });
+
+ // Convert the outer data-holder to the object-representation type.
+ if ($objectRepresentation === 'stdClass') {
+ // When they want a stdClass, we MUST create it ourselves and assign
+ // all of the internal data key-value pairs on it, with string-keys.
+ $outputContainer = new stdClass();
+ foreach ($copy->_objectData as $k => $v) {
+ $outputContainer->{(string) $k} = $v;
+ }
+ } else { // 'array'
+ // For an array-representation, we'll simply steal the copy's array.
+ $outputContainer = $copy->_objectData;
+ }
+
+ // Voila, we now have their desired container, with cleaned-up and fully
+ // validated copies of all of our internal data. And we haven't affected
+ // any of our real $this data at all. Clones rock!
+ // NOTE: And since $copy goes out of scope now, there will be no other
+ // references to any of the data inside the container. It is therefore
+ // fully encapsulated, standalone data which is safe to manipulate.
+ return $outputContainer;
+ }
+
+ /**
+ * Get a processed copy of this object instance's data wrapped in stdClass.
+ *
+ * This function is just a shortcut/alias for `exportObjectDataCopy()`.
+ * Please read its documentation to understand the full implications and
+ * explanation for the behavior of this function.
+ *
+ * All objects in the returned value will be represented as `stdClass`
+ * objects, which is important if you need to be able to identify nested
+ * internal sub-objects via `is_object()`.
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return stdClass A processed copy of the internal data, with all objects
+ * represented as stdClass.
+ *
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function asStdClass()
+ {
+ return $this->exportObjectDataCopy('stdClass'); // Throws.
+ }
+
+ /**
+ * Get a processed copy of this object instance's data as an array.
+ *
+ * This function is just a shortcut/alias for `exportObjectDataCopy()`.
+ * Please read its documentation to understand the full implications and
+ * explanation for the behavior of this function.
+ *
+ * All objects in the returned value will be represented as nested arrays,
+ * which is good if you require that the result is compatible with
+ * `__construct()` again.
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return array A processed copy of the internal data, with all objects
+ * represented as arrays.
+ *
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function asArray()
+ {
+ return $this->exportObjectDataCopy('array'); // Throws.
+ }
+
+ /**
+ * Get a processed representation of this object instance's data as JSON.
+ *
+ * This helper gives you a JSON string representation of the object's
+ * internal data, and provides the necessary interface to fully control
+ * the JSON encoding process.
+ *
+ * It handles all steps for you and wraps everything in nice exceptions,
+ * and will even clearly explain any `json_encode()` errors in plain English
+ * (although the default settings will never cause any encoding failures).
+ * And most importantly, this function also guarantees that your data is
+ * ALWAYS properly encoded as a valid JSON object `{}` (rather than risking
+ * getting the result as a JSON array such as `[]` or `["a","b","c"]`).
+ * We accept all of the same parameters as the `json_encode()` function!
+ *
+ * Note that we only encode properties that exist in the actual internal
+ * data. Anything that merely exists in the class property map is omitted.
+ *
+ * `WARNING:` It is worth saving the output of this function if you intend
+ * to use the result multiple times, since each call to this function will
+ * internally use `exportObjectDataCopy()`, which performs quite intensive
+ * work to recursively validate and convert values while creating the final
+ * representation of the object's internal JSON data. Please read the
+ * description of `exportObjectDataCopy()` for more information.
+ *
+ * @param int $options Bitmask to control `json_encode()` behavior.
+ * @param int $depth Maximum JSON depth. Encoding fails if set too low.
+ * Can almost always safely be left at `512` (default).
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return string
+ *
+ * @see http://php.net/json_encode
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::printJson()
+ * @see LazyJsonMapper::jsonSerialize() The native json_encode()
+ * serializer instead.
+ */
+ final public function asJson(
+ $options = 0,
+ $depth = 512)
+ {
+ if (!is_int($options) || !is_int($depth)) {
+ throw new LazyJsonMapperException('Invalid non-integer function argument.');
+ }
+
+ // Create a fully-validated, fully-converted final object-tree.
+ // NOTE: See `jsonSerialize()` for details about why we MUST do this.
+ $objectData = $this->exportObjectDataCopy('stdClass'); // Throws.
+
+ // Gracefully handle JSON encoding and validation.
+ $jsonString = @json_encode($objectData, $options, $depth);
+ if ($jsonString === false) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Failed to encode JSON string (error %d: "%s").',
+ json_last_error(), json_last_error_msg()
+ ));
+ }
+
+ return $jsonString;
+ }
+
+ /**
+ * Print a processed representation of this object instance's data as JSON.
+ *
+ * This helper is provided as a quick and easy debug feature, instead of
+ * having to manually write something like `var_dump($item->asJson())`.
+ *
+ * And it's also a much better alternative than PHP's common but totally
+ * unreadable `var_dump($item->asArray())` or `var_dump($item)` techniques.
+ *
+ * You can even take advantage of chained operations, if you are 100% sure
+ * that NONE of the properties along the way are `NULL`. An example of doing
+ * that would be `$container->getItems()[0]->getUser()->printJson()`.
+ * However, such code is ONLY recommended for quick debug tests, but not
+ * for actual production use, since you should always be handling any `NULL`
+ * properties along the way (unless you enjoy random errors whenever a
+ * particular property along the chain happens to be missing from the
+ * object's internal JSON data value storage).
+ *
+ * Please read the description of `asJson()` if you want more information
+ * about how the internal JSON conversion process works.
+ *
+ * @param bool $prettyPrint Use whitespace to nicely format the data.
+ * Defaults to `TRUE` since we assume that you want
+ * human readable output while debug-printing.
+ * @param int $depth Maximum JSON depth. Encoding fails if set too
+ * low. Can almost always safely be left at `512`
+ * (default).
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printPropertyDescriptions()
+ */
+ final public function printJson(
+ $prettyPrint = true,
+ $depth = 512)
+ {
+ // NOTE: These options are important. For display purposes, we don't
+ // want escaped slashes or `\uXXXX` hex versions of UTF-8 characters.
+ $options = ($prettyPrint ? JSON_PRETTY_PRINT : 0) | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
+ $json = $this->asJson($options, $depth); // Throws.
+ if ($prettyPrint && PHP_EOL !== "\n") {
+ // PHP's JSON pretty-printing uses "\n" line endings, which must be
+ // translated for proper display if that isn't the system's style.
+ $json = str_replace("\n", PHP_EOL, $json);
+ }
+ echo $json.PHP_EOL;
+ }
+
+ /**
+ * Serialize this object to a value that's supported by json_encode().
+ *
+ * You are not supposed to call this directly. It is _automatically_ called
+ * by PHP whenever you attempt to `json_encode()` a `LazyJsonMapper` object
+ * or any data structures (such as arrays) which contain such objects. This
+ * function is then called and provides a proper "object representation"
+ * which can be natively encoded by PHP.
+ *
+ * Having this helper ensures that you can _easily_ encode very advanced
+ * structures (such as a regular PHP array which contains several nested
+ * `LazyJsonMapper`-based objects), _without_ needing to manually fiddle
+ * around with `asJson()` on every individual object within your array.
+ *
+ * You are instead able to simply `json_encode($mainObj->getSubArray())`,
+ * which will properly encode every array-element in that array, regardless
+ * of whether they're pure PHP types or nested `LazyJsonMapper` objects.
+ *
+ * Note that we only export properties that exist in the actual internal
+ * data. Anything that merely exists in the class property map is omitted.
+ *
+ * `WARNING:` It is worth saving the output of `json_encode()` if you intend
+ * to use the result multiple times, since each call to this function will
+ * internally use `exportObjectDataCopy()`, which performs quite intensive
+ * work to recursively validate and convert values while creating the final
+ * representation of the object's internal JSON data. Please read the
+ * description of `exportObjectDataCopy()` for more information.
+ *
+ * `WARNING:` In _most_ cases, you should be using `asJson()` (instead of
+ * `json_encode()`), since it's _far more_ convenient and completely wraps
+ * PHP's JSON encoding problems as exceptions. If you truly want to manually
+ * `json_encode()` the data, you'll still get complete data conversion and
+ * validation, but you _won't_ get any _automatic exceptions_ if PHP fails
+ * to encode the JSON, which means that _you'll_ have to manually check if
+ * the final value was successfully encoded by PHP. Just be aware of that!
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return stdClass A processed copy of the internal data, with all objects
+ * represented as stdClass. Usable by `json_encode()`.
+ *
+ * @see http://php.net/json_encode
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function jsonSerialize()
+ {
+ // Create a fully-validated, fully-converted final object-tree.
+ // NOTE: It is VERY important that we export the data-copy with objects
+ // represented as stdClass objects. Otherwise `json_encode()` will be
+ // unable to understand which parts are JSON objects (whose keys must
+ // always be encoded as `{}` or `{"0":"a","1":"b"}`) and which parts are
+ // JSON arrays (whose keys must always be encoded as `[]` or
+ // `["a","b"]`).
+ // IMPORTANT NOTE: Any "untyped" objects from the original JSON data
+ // input ("mixed"/undefined data NOT mapped to `LazyJsonMapper` classes)
+ // will be encoded using PHP's own auto-detection for arrays, which
+ // GUESSES based on the keys of the array. It'll be guessing perfectly
+ // in almost 100% of all cases and will encode even those "untyped"
+ // arrays as JSON objects again, EXCEPT if an object from the original
+ // data used SEQUENTIAL numerical keys STARTING AT 0, such as:
+ // `json_encode(json_decode('{"0":"a","1":"b"}', true))` which will
+ // result in `["a","b"]`. But that's incredibly rare (if ever), since it
+ // requires weird objects with numerical keys that START AT 0 and then
+ // sequentially go up from there WITHOUT ANY GAPS or ANY NON-NUMERIC
+ // keys. That should NEVER exist in any user's real JSON data, and it
+ // doesn't warrant internally representing all JSON object data as the
+ // incredibly inefficient stdClass type instead. If users REALLY want to
+ // perfectly encode such data as objects when they export as JSON, EVEN
+ // in the insanely-rare/impossible situation where their objects use
+ // sequential numeric keys, then they should at the very least map them
+ // to "LazyJsonMapper", which will ensure that they'll be preserved as
+ // objects in the final JSON output too.
+ return $this->exportObjectDataCopy('stdClass'); // Throws.
+ }
+
+ /**
+ * Handles automatic string conversion when the object is used as a string.
+ *
+ * Internally runs `asJson()`.
+ *
+ * `WARNING:` It's **dangerous** to rely on the automatic string conversion,
+ * since PHP doesn't allow this handler to throw any error/exceptions (if we
+ * do, PHP would die with a Fatal Error). So we cannot notify about errors.
+ *
+ * Therefore, any warnings and exceptions will silently be placed directly
+ * in the string output instead, enclosed in `<>` brackets to guarantee that
+ * they won't be interpreted as valid JSON.
+ *
+ * Smart programmers will instead call `$item->asJson()` manually. It is
+ * only a few more characters, and it guarantees that you can catch
+ * conversion errors and react to them appropriately.
+ *
+ * There is only ONE scenario where `__toString()` is reliable: That's if
+ * your JSON data was constructed with the `$requireAnalysis` constructor
+ * argument, which recursively ensures that all data type-conversions and
+ * all nested sub-object constructions are successful before your object is
+ * allowed to be created. Furthermore, you must not have manipulated
+ * anything via "direct property access by reference" AFTER that (as
+ * explained in `__get()`), so that you're sure that all of your internal
+ * data is truly valid.
+ *
+ * But that is all moot anyway, because always using the debug-only option
+ * `$requireAnalysis` defeats the purpose of memory-efficient lazy-loading
+ * and slows down the creation of all of your class instances. Don't do it.
+ *
+ * In short: `__toString()` is here as a nice bonus for completeness sake,
+ * but you should never use it in production. Just use `asJson()` instead!
+ *
+ * @var string
+ *
+ * @see LazyJsonMapper::asJson()
+ */
+ final public function __toString()
+ {
+ try {
+ return $this->asJson(); // Throws.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ // __toString() is not allowed to throw, so give generic info in a
+ // way that's definitely going to be invalid JSON data, so that the
+ // user will notice the problem if they use this method to manually
+ // generate JSON data, such as if they're doing '["i":'.$item.']'.
+ // NOTE: "" was chosen since it's illegal JSON.
+ return sprintf('<%s>', $e->getMessage());
+ }
+ }
+
+ /**
+ * Analyze the entire object and check for undefined or bad JSON properties.
+ *
+ * This lets you analyze the object data & map to look for the following:
+ *
+ * 1. Undefined JSON properties that need to be defined in your classes
+ * so that their existence becomes permanent and therefore safer to use
+ * (since defined properties are always retrievable even if they don't
+ * exist in the current object instance's data). And actually defining
+ * the properties is also the ONLY way that you can enforce a specific
+ * data-type, since undefined properties default to untyped (`mixed`).
+ *
+ * 2. Bad class map definitions that don't match the actual JSON data.
+ * It does this by checking all of the encountered classes within the JSON
+ * data to ensure that they construct successfully (which they ALWAYS
+ * will, unless the user has overridden `_init()` and throws from it), as
+ * well as verifying that all basic PHP type coercions work, and that the
+ * data is formatted as-described in the definition (such as having the
+ * correct array-depth, or data defined as objects actually being objects).
+ *
+ * The scan is performed by looking at EVERY item in the object's internal
+ * JSON data array, and checking for a corresponding class map definition
+ * (if nothing exists, it's treated as "missing from the class map"), and
+ * then attempting conversion to its specified type (if that fails, it's
+ * treated as "bad class map definition"). All undefined values (which lack
+ * any class map definition) are ALSO validated as if they were `mixed`,
+ * to ensure that NOTHING can contain illegal values.
+ *
+ * If a recursive scan is requested, then all sub-objects (`LazyJsonMapper`
+ * data containers) will ALSO recursively perform the same analysis on their
+ * own internal data, so that the whole JSON data tree is recursively
+ * verified. Their analysis results will be merged with our return value.
+ *
+ * Note that calling this function will cause the WHOLE object tree to be
+ * constructed and ALL of its mapped properties to be converted/parsed to
+ * verify the class map, which takes some time and permanently increases the
+ * object's memory usage (since conversion causes any internal sub-objects
+ * to be stored as actual objects instead of merely as plain sub-arrays)!
+ *
+ * Therefore, you should ONLY use this function when necessary: When you
+ * need advanced and powerful debugging of your class maps!
+ *
+ * @param bool $recursiveScan Whether to also verify missing/bad properties
+ * in sub-objects in the analysis. Recommended.
+ *
+ * @return ClassAnalysis An object describing the problems with this class.
+ */
+ final public function exportClassAnalysis(
+ $recursiveScan = true)
+ {
+ $result = new ClassAnalysis();
+
+ // All problems with OUR class will get filed under our class name.
+ $definitionSource = get_class($this);
+
+ // Ensure that all object-data properties exist in our class definition,
+ // and that their values can be converted as described in the class map.
+ foreach ($this->_objectData as $propName => $value) {
+ // Check if property exists in the class map and get its definition.
+ // NOTE: Normally, this function can throw, but not with the way we
+ // are calling it, because we KNOW that the property exists in at
+ // least the object data, so we DON'T have to catch any errors!
+ $propDef = $this->_getPropertyDefinition($propName);
+
+ // Regardless of whether it's defined or not, we MUST now "get" it
+ // to validate/convert the property's contents, to ensure it's safe.
+ try {
+ // Trying to "get" the property forces complete validation of
+ // the internal property data and non-recursively creates all
+ // sub-objects (if any). In case of any errors, this call will
+ // throw an exception with an error description.
+ // NOTE: Calling this getter is EXTREMELY important even for
+ // UNDEFINED (untyped) properties, because it ensures that no
+ // illegal data values (such as Resources or non-LazyJsonMapper
+ // objects) exist within the data even in undefined properties.
+ // NOTE: In the case of arrays, it validates the whole
+ // arrayDepth and ensures that the array is well-formed and
+ // contains the exact type at the exact depth specified (but it
+ // accepts NULL values anywhere in the chain of arrays, and it
+ // accepts empty arrays at non-max depth). It also verifies
+ // type-coercion to built-in PHP types. And in the case of
+ // objects, it verifies that they are successfully constructed.
+ $value = $this->_getProperty($propName); // Throws.
+
+ // Recursively check all internal objects to make sure they also
+ // have all properties from their own raw JSON data.
+ // NOTE: Nothing in this sub-block throws any exceptions, since
+ // everything was validated by _getProperty(). And the deeper
+ // exportClassAnalysis() calls don't throw anything either.
+ if ($recursiveScan && $value !== null && $propDef->isObjectType) {
+ // NOTE: We don't have to validate array-depth/type
+ // correctness, since _getProperty() already did that for
+ // us. Nothing will be isObjectType unless it's really a
+ // LazyJsonMapper class. Also, since the $value is not NULL
+ // (see above) and it's an "array of [type]" property, then
+ // we KNOW the $value is_array(), since it validated as OK.
+ if ($propDef->arrayDepth > 0) { // Array of objects.
+ array_walk_recursive($value, function (&$obj) use (&$result) {
+ if (is_object($obj)) { // Could be inner NULL too.
+ $result->mergeAnalysis($obj->exportClassAnalysis());
+ }
+ });
+ } else { // Non-"array of" object property.
+ $result->mergeAnalysis($value->exportClassAnalysis());
+ }
+ }
+ } catch (LazyJsonMapperException $e) {
+ // Unable to get the value of this property... which usually
+ // means that the property cannot be type-coerced as requested,
+ // or that the JSON data doesn't match the definition (such as
+ // having a deeper JSON array than what the definition says), or
+ // that invalid data was encountered (such as encountering an
+ // internal non-Lazy object/resource instead of the expected
+ // data), or that the property's class could not be constructed
+ // (which may really only happen due to a user's custom _init()
+ // function failing, since our own classmap's compilation has
+ // already taken care of successfully verifying the classmap
+ // compilation of ALL classes referred to by our ENTIRE property
+ // hierarchy). All of those are user-caused errors...
+
+ // We'll save the exception details message as-is...
+ // TODO: This also means that undefined properties containing
+ // weird data (objects or resources) would throw above and get
+ // logged as "bad definition" despite NOT having any definition.
+ // We may want to add a check here for UndefinedProperty and
+ // prepend something to the exception message in that case, to
+ // tell the user that their UNDEFINED property contained the bad
+ // data. But seriously, they can NEVER have such invalid data
+ // inside of a real json_decode() input array, so nobody sane is
+ // going to be warned of "bad definition" for their undefined
+ // properties! And we can't just log it as "missing" since we
+ // NEED all "bad data" to be logged as bad_definitions, since
+ // that's what our various data validators look at to detect
+ // SERIOUS data errors! Besides, their missing definition WILL
+ // also get logged under "missing_definitions" in the next step.
+ $result->addProblem(
+ $definitionSource,
+ 'bad_definitions',
+ $e->getMessage()
+ );
+ }
+
+ // Now check if we lacked a user-definition for this JSON property.
+ if ($propDef instanceof UndefinedProperty) {
+ // NOTE: We need the (string) casting in case of int-keys. Which
+ // can happen if the user gives us a manually created, non-JSON
+ // constructor array containing non-associative integer keys.
+ $result->addProblem(
+ $definitionSource,
+ 'missing_definitions',
+ (string) $propName
+ );
+ }
+ }
+
+ // Nicely sort the problems and remove any duplicates.
+ $result->sortProblemLists();
+
+ return $result;
+ }
+
+ /**
+ * Check if a property definition exists.
+ *
+ * These are properties defined in the JSON property map for the class.
+ *
+ * `NOTE:` This is the STRICTEST function, which checks if a property is
+ * defined in the class. It rejects properties that only exist in the data.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ */
+ final protected function _hasPropertyDefinition(
+ $propName)
+ {
+ return isset($this->_compiledPropertyMapLink[$propName]);
+ }
+
+ /**
+ * Check if a property definition or an object instance data value exists.
+ *
+ * These are properties that are either defined in the JSON property map
+ * for the class OR that exist in the object instance's data.
+ *
+ * `NOTE:` This is the RECOMMENDED function for checking if a property is
+ * valid, since properties ARE VALID if they exist in the class definition
+ * OR in the object instance's data.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ */
+ final protected function _hasPropertyDefinitionOrData(
+ $propName)
+ {
+ return isset($this->_compiledPropertyMapLink[$propName])
+ || array_key_exists($propName, $this->_objectData);
+ }
+
+ /**
+ * Check if an object instance data value exists.
+ *
+ * These are properties that currently exist in the object instance's data.
+ *
+ * `NOTE:` This function ISN'T RECOMMENDED unless you know what you're doing.
+ * Because any property that exists in the class definition is valid as
+ * well, and can be retrieved even if it isn't in the current object data.
+ * So `_hasPropertyDefinitionOrData()` is recommended instead, if your goal
+ * is to figure out if a property name is valid. Alternatively, you can use
+ * `_hasPropertyDefinition()` if you want to be even stricter by requiring
+ * that the property is defined in the class map.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ *
+ * @see LazyJsonMapper::_hasPropertyDefinition()
+ * @see LazyJsonMapper::_hasPropertyDefinitionOrData()
+ */
+ final protected function _hasPropertyData(
+ $propName)
+ {
+ return array_key_exists($propName, $this->_objectData);
+ }
+
+ /**
+ * Get the property definition for an object data property.
+ *
+ * If the property doesn't exist in the class definition but exists in the
+ * object instance's data, then it will be treated as an undefined & untyped
+ * property. This ensures that the user can access undefined properties too!
+ *
+ * However, you should always strive to keep your classes up-to-date so that
+ * all properties are defined, otherwise they'll be totally inaccessible
+ * (instead of returning `NULL`) anytime they're missing from the JSON data.
+ *
+ * @param string $propName The property name.
+ * @param bool $allowUndefined Whether to allow default definitions when
+ * the property exists in the data but not in
+ * our actual class property map.
+ *
+ * @throws LazyJsonMapperException If property isn't defined in the class
+ * map and also doesn't exist in the object
+ * instance's data. Note that if undefined
+ * definitions are disallowed, we will ONLY
+ * check in the class definition map.
+ *
+ * @return PropertyDefinition An object describing the property.
+ */
+ final protected function _getPropertyDefinition(
+ $propName,
+ $allowUndefined = true)
+ {
+ if (isset($this->_compiledPropertyMapLink[$propName])) {
+ // Custom class property definition exists, so use that. Properties
+ // defined in the class map are always valid even if not in data!
+ return $this->_compiledPropertyMapLink[$propName];
+ } elseif ($allowUndefined && array_key_exists($propName, $this->_objectData)) {
+ // No property definition exists, but the property exists in the
+ // object instance's data, so treat it as undefined & untyped.
+ return UndefinedProperty::getInstance();
+ } else {
+ // There is no definition & no object instance data (or disallowed)!
+ throw new LazyJsonMapperException(sprintf(
+ 'No such object property "%s".',
+ $propName
+ ));
+ }
+ }
+
+ /**
+ * Get the value of an object data property.
+ *
+ * This function automatically reads the object instance's data and converts
+ * the value to the correct type on-the-fly. If that part of the data-array
+ * is an object, it will be lazy-created the first time it's requested.
+ *
+ * `NOTE:` If the object instance's internal data doesn't contain a value, but
+ * it's listed in the class property definition, then it will be treated as
+ * missing and `NULL` will automatically be returned instead. However, if
+ * it's missing from the class definition AND the object instance's data,
+ * then it's treated as an invalid property and an exception is thrown.
+ *
+ * Because of the fact that the default return value for internally-missing
+ * but "class-valid" properties is `NULL`, it means that you cannot discern
+ * whether the `NULL` came from actual JSON data or from the default return
+ * value. However, if that distinction matters to you, you should simply
+ * call `_hasPropertyData()` before retrieving the value.
+ *
+ * `IMPORTANT:` This function performs return-by-reference, which was done
+ * in order to allow certain ADVANCED programming tricks by the caller. If
+ * you save our return value by reference, you'll then have a direct link to
+ * the internal data for that property and can write directly to it
+ * (including writing invalid data if you want to, since we cannot verify
+ * what you do with your reference). However, you A) don't have to worry
+ * about invalid data, because it will all get validated at the next
+ * `_getProperty()` call again, and B) you have to explicitly bind the
+ * returned value by reference to actually risk affecting the internal
+ * data, as explained below.
+ *
+ * Method 1 (recommended for almost 100% of all usages, safest):
+ *
+ * ```php
+ * $val = $this->_getProperty('foo'); // Copy-on-write assignment to $val.
+ * $val = 'bar'; // Does NOT modify the internal data "foo" property.
+ * ```
+ *
+ * Method 2 (dangerous, and this example even contains an intentional bug):
+ *
+ * ```php
+ * $val = &$this->_getProperty('foo'); // Reference assignment to $val.
+ * $val = 'bar'; // Direct link, thus modifies the internal "foo" property.
+ * ```
+ *
+ * `SERIOUS WARNING:` If you use Method 2, do NOT use the code above. It was
+ * just explained that way to demonstrate a SERIOUS MISTAKE. If you assign
+ * by reference, you obviously intend to link directly to internal data. But
+ * the code above fails if no internal data for that property exists yet. In
+ * that case, the code above will write to a temporary `NULL` variable which
+ * is not linked at all to the object. Instead, you MUST ALWAYS use
+ * `$createMissingValue = true` if you want TRUSTABLE data references.
+ *
+ * Method 2 (the CORRECT way of writing it):
+ *
+ * ```php
+ * $val = &$this->_getProperty('foo', true);
+ * $val = 'bar';
+ * ```
+ *
+ * That FIXED code will create the property and put `NULL` in it if it's
+ * missing from the internal data, and then returns the reference to the
+ * created internal data entry. It's the ONLY way to ensure REAL references.
+ *
+ * Note that there is ZERO PERFORMANCE BENEFIT of assigning our return-value
+ * by reference. ONLY use references if you have an ADVANCED reason for it!
+ * Internally, PHP treats both assignment types identically until the moment
+ * you try to write to/modify the contents of the variable. So if you are
+ * only reading, which is most of the time, then there's no reason to use a
+ * reference. And never forget that modifying via direct reference lets you
+ * write invalid data (which won't be detected until a `_getProperty()`
+ * call attempts to read that bad data again), as mentioned earlier.
+ *
+ * There is one final warning: If YOU have a "return-by-reference" function,
+ * then you will AUTOMATICALLY return a direct link to the inner data if you
+ * use us directly in your return statement. Here's an example of that:
+ *
+ * ```php
+ * public function &myFunction() { // <-- Note the "&" return-by-ref here!
+ * // returns reference to real internal data OR temp var (badly coded)
+ * return $this->_getProperty('foo'); // <-- VERY DANGEROUS BUG!
+ *
+ * // returns reference to real internal data (correctly coded)
+ * return $this->_getProperty('foo', true); // <-- CORRECT!
+ *
+ * // you can also intentionally break the link to the internal data,
+ * // by putting it in a copy-on-write variable and returning THAT:
+ * $copyOnWrite = $this->_getProperty('foo'); // copy-on-write (no ref)
+ * return $copyOnWrite; // ref to $copyOnWrite but not to real internal
+ * }
+ * ```
+ *
+ * @param string $propName The property name.
+ * @param bool $createMissingValue If `TRUE` then we will create missing
+ * internal object data entries (for
+ * class-defined properties) and store a
+ * `NULL` in them. This will "pollute" the
+ * internal data with default `NULL`s for
+ * every missing property that you attempt
+ * to retrieve, but it's totally NECESSARY
+ * if you intend to use the return-value
+ * by-reference, since we MUST store the
+ * value internally when you want the
+ * returned reference to link to our
+ * actual object's internal data storage!
+ *
+ * @throws LazyJsonMapperException If the value can't be turned into its
+ * assigned class or built-in PHP type, or
+ * if the property doesn't exist in either
+ * the class property definition or the
+ * object instance's data.
+ *
+ * @return mixed The value as the correct type, or `NULL` if it's either a
+ * literal `NULL` in the data or if no value currently existed
+ * in the internal data storage at all. Note that this
+ * function returns the value by-reference.
+ */
+ final protected function &_getProperty(
+ $propName,
+ $createMissingValue = false)
+ {
+ // Check if the property exists in class/data and get its definition.
+ $propDef = $this->_getPropertyDefinition($propName); // Throws.
+
+ // Assign the appropriate reference to the "$value" variable.
+ if (array_key_exists($propName, $this->_objectData)) {
+ // The value exists in the data, so refer directly to it.
+ $value = &$this->_objectData[$propName]; // IMPORTANT: By reference!
+ } elseif ($createMissingValue) {
+ // No value exists in data yet, AND the caller wants us to create
+ // a default NULL value for the data property in that situation.
+ // NOTE: This is IMPORTANT so that the returned "default NULL"
+ // by-reference value will refer to the correct internal data
+ // array entry instead of to an unrelated, temporary variable.
+ // NOTE: The downside to this is that we'll fill the object's
+ // internal data storage with a bunch of default "NULL" values,
+ // which increases memory needs and messes with the "to JSON"
+ // output functions later. But it's unavoidable. We obviously
+ // CANNOT return a real reference to an internal data entry
+ // unless we CREATE the entry. So we MUST unfortunately do it!
+ // NOTE: This "pollution" is only a problem if the caller intends
+ // to use the property by reference, which isn't the case for
+ // default __call() (the virtual functions), but IS necessary
+ // in the default __get() (direct property access by reference).
+ $this->_objectData[$propName] = null;
+
+ // Now just link to the data array entry we just added.
+ $value = &$this->_objectData[$propName]; // IMPORTANT: By reference!
+ return $value; // OPTIMIZATION: Skip convert() for missing data.
+ } else {
+ // No value exists in data yet, but the caller didn't want us to set
+ // the missing value. So we'll simply use a default NULL variable.
+ // NOTE: If the caller tries to write to this one by reference,
+ // they'll just modify this temporary variable instead of the
+ // internal data. To link to real they MUST $createMissingValue.
+ // NOTE: We MUST return a variable, since "return null" is illegal.
+ $value = null; // Copy-on-write. Temporary variable to be returned.
+ return $value; // OPTIMIZATION: Skip convert() for missing data.
+ }
+
+ // Map the value to the appropriate type and validate it.
+ ValueConverter::convert( // Throws.
+ ValueConverter::CONVERT_FROM_INTERNAL,
+ $value, $propDef->arrayDepth, $propName, $propDef
+ );
+
+ // Whatever $value points at will now be returned by-reference.
+ return $value; // By-reference due to &function signature.
+ }
+
+ /**
+ * Check if an object data property exists and its value evaluates to TRUE.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ */
+ final protected function _isProperty(
+ $propName)
+ {
+ // Object instance's data has the property and it evaluates to true?
+ return array_key_exists($propName, $this->_objectData)
+ && (bool) $this->_objectData[$propName];
+ }
+
+ /**
+ * Set an object data property to a new value.
+ *
+ * @param string $propName The property name.
+ * @param mixed $value The new value for the property. `NULL` is always
+ * allowed as the new value, regardless of type.
+ *
+ * @throws LazyJsonMapperException If the new value isn't legal for that
+ * property, or if the property doesn't
+ * exist in either the class property
+ * definition or the object instance's data.
+ *
+ * @return $this The current object instance, to allow chaining setters.
+ */
+ final protected function _setProperty(
+ $propName,
+ $value)
+ {
+ // Check if the property exists in class/data and get its definition.
+ $propDef = $this->_getPropertyDefinition($propName); // Throws.
+
+ // Map the value to the appropriate type and validate it.
+ ValueConverter::convert( // Throws.
+ ValueConverter::CONVERT_TO_INTERNAL,
+ $value, $propDef->arrayDepth, $propName, $propDef
+ );
+
+ // Assign the new value for the property.
+ $this->_objectData[$propName] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Erase the internal value of an object data property.
+ *
+ * This is useful for things like erasing certain parts of the JSON data
+ * tree before you're going to output the object as JSON. You can still
+ * continue to access and modify an "erased" property after unsetting it,
+ * but ONLY if that property is defined in your class property map.
+ *
+ * The current value is simply removed from the internal object data array,
+ * as if the object had been constructed without that part of the array.
+ *
+ * Also note that we act like the real unset() function, meaning that we
+ * don't care whether that property key exists in the object instance's
+ * data array. We'll simply unset it if it exists. Or otherwise gracefully
+ * do absolutely nothing.
+ *
+ * @param string $propName The property name.
+ *
+ * @return $this The current object instance, to allow chaining unsetters.
+ */
+ final protected function _unsetProperty(
+ $propName)
+ {
+ unset($this->_objectData[$propName]);
+
+ return $this;
+ }
+
+ /**
+ * __CALL is invoked when attempting to access missing functions.
+ *
+ * This magic handler auto-maps "virtual function" has-ers, is-ers, getters,
+ * setters and unsetters for all of the object's JSON data properties.
+ *
+ * - `bool hasX()` checks if "x" exists in the class definition and/or the
+ * object instance data. The `has`-functions are the only ones that never
+ * throw even if the property is invalid. This function is totally useless
+ * if you've defined the property in the class map, and will always return
+ * `TRUE` in that case. Its ONLY purpose is to allow you to look for
+ * UNDEFINED properties that may/may not exist in the current data, before
+ * you decide to call any of the other "throwy" functions on that
+ * property. In other words, it's used for working with UNMAPPED
+ * (undefined) properties!
+ * - `bool isX()` checks if "x" exists in the object instance's data and its
+ * current value evaluates to `TRUE`.
+ * - `mixed getX()` retrieves the value of "x". Uses copy-on-write (not a
+ * reference).
+ * - `$this setX(mixed $value)` sets the value of "x" to `$value`. The
+ * setters can be chained.
+ * - `$this unsetX()` erases the internal value from the object instance's
+ * data. The unsetters can be chained.
+ *
+ * @param string $functionName Name of the function being called.
+ * @param array $arguments Array of arguments passed to the function.
+ *
+ * @throws LazyUserOptionException If virtual functions are disabled.
+ * @throws LazyJsonMapperException If the function type or property name is
+ * invalid, or if there's any problem with
+ * the conversion to/from the object
+ * instance's internal data. As well as if
+ * the setter doesn't get exactly 1 arg.
+ *
+ * @return mixed The return value depends on which function is used.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php
+ * @see LazyJsonMapper::_hasPropertyDefinitionOrData()
+ * @see LazyJsonMapper::_isProperty()
+ * @see LazyJsonMapper::_getProperty()
+ * @see LazyJsonMapper::_setProperty()
+ * @see LazyJsonMapper::_unsetProperty()
+ */
+ final public function __call(
+ $functionName,
+ $arguments)
+ {
+ if (!static::ALLOW_VIRTUAL_FUNCTIONS) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_FUNCTIONS_DISABLED
+ );
+ }
+
+ // Split the function name into its function-type and FuncCase parts.
+ list($functionType, $funcCase) = FunctionTranslation::splitFunctionName(
+ $functionName
+ );
+
+ // If the function didn't follow the [lower prefix][other] format, such
+ // as "getSomething", then this was an invalid function (type = NULL).
+ if ($functionType === null) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".',
+ $functionName
+ ));
+ }
+
+ // Resolve the FuncCase component into its equivalent property names.
+ if (static::USE_MAGIC_LOOKUP_CACHE && isset(self::$_magicLookupCache[$funcCase])) {
+ // Read the previously processed result from the lookup cache.
+ $translation = self::$_magicLookupCache[$funcCase];
+ } else {
+ // Attempt to parse the newly called function name.
+ try {
+ $translation = new FunctionTranslation($funcCase); // Throws.
+ } catch (MagicTranslationException $e) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".', $functionName
+ ));
+ }
+
+ // Store the processed result in the global lookup cache, if caching
+ // is allowed by the current class instance.
+ // NOTE: We'll store it even if the property doesn't exist on this
+ // particular object, because the user may be querying undefined
+ // data that WILL exist, and we don't want to waste time re-parsing.
+ if (static::USE_MAGIC_LOOKUP_CACHE) {
+ self::$_magicLookupCache[$funcCase] = $translation;
+ }
+ }
+
+ // Check for the existence of the "snake_case" property variant first,
+ // and if that fails then look for a "camelCase" property instead.
+ // NOTE: Checks both the class definition & the object instance's data.
+ if ($this->_hasPropertyDefinitionOrData($translation->snakePropName)) {
+ $propName = $translation->snakePropName; // We found a snake prop!
+ } elseif ($translation->camelPropName !== null
+ && $this->_hasPropertyDefinitionOrData($translation->camelPropName)) {
+ $propName = $translation->camelPropName; // We found camel instead.
+ } else {
+ // This object doesn't have the requested property! If this is a
+ // hasX() call, simply return false. In all other cases, throw!
+ if ($functionType === 'has') {
+ return false; // We don't have the property.
+ } else {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".',
+ $functionName
+ ));
+ }
+ }
+
+ // Return the kind of response expected by their desired function.
+ switch ($functionType) {
+ case 'has':
+ return true; // If we've come this far, we have the property.
+ break;
+ case 'is':
+ return $this->_isProperty($propName);
+ break;
+ case 'get':
+ // NOTE: This will NOT return a reference, since __call() itself
+ // does not support return-by-reference. We return a copy-on-write.
+ return $this->_getProperty($propName); // Throws.
+ break;
+ case 'set':
+ // They must provide exactly 1 argument for a setter call.
+ if (count($arguments) !== 1) {
+ // We know property exists; get its def from class or "undef".
+ $propDef = $this->_getPropertyDefinition($propName); // Throws.
+
+ throw new LazyJsonMapperException(sprintf(
+ 'Property setter requires exactly 1 argument: "%s(%s $value)".',
+ $functionName, $propDef->asString()
+ ));
+ }
+
+ // Returns $this so that the user can chain the setters.
+ return $this->_setProperty($propName, $arguments[0]); // Throws.
+ break;
+ case 'unset':
+ // NOTE: Normal PHP unset() calls would have a VOID return type. But
+ // ours actually returns $this so that the user can chain them.
+ return $this->_unsetProperty($propName);
+ break;
+ default:
+ // Unknown function type prefix...
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".',
+ $functionName
+ ));
+ }
+ }
+
+ /**
+ * __GET is invoked when reading data from inaccessible properties.
+ *
+ * This magic handler takes care of "virtual property" access to the
+ * object's JSON data properties.
+ *
+ * `WARNING:` Note that the `__get()` "virtual property" handling creates
+ * `NULL` values in any missing (but valid in class-map) properties that you
+ * try to access! That is NECESSARY because PHP EXPECTS the `__get()` return
+ * value to be a REFERENCE to real internal data, so we MUST create a value
+ * if no value exists, so that we can link PHP to a true reference to the
+ * internal data. Obviously we can't link to something that doesn't exist,
+ * which is why we MUST create `NULL` values. Unfortunately that means that
+ * "virtual property access" will lead to increased memory usage and worse
+ * JSON output (due to all the added values) if you want to export the
+ * internal data as JSON later.
+ *
+ * The recommended access method, `$obj->getFoo()` ("virtual functions")
+ * doesn't have that problem. It ONLY happens when you decide to use
+ * `$obj->foo` for "direct virtual property" access.
+ *
+ * There are several other quirks with "direct virtual properties", due to
+ * how PHP works. If you don't write your code carefully, you may cause
+ * serious issues with performance or unexpected results.
+ *
+ * All of the important quirks are as follows:
+ *
+ * 1. The aforementioned necessary `NULL` value insertion increases memory
+ * usage and may lead to unexpected problems if you convert back to JSON,
+ * such as if the server does not expect those values to exist with `NULL`.
+ *
+ * 2. Anytime you use the array `[]` access operator on the outer property
+ * name, PHP will trigger a `__get()` call, which of course does its whole
+ * data-validation again. The following would therefore be EXTREMELY SLOW,
+ * and possibly even DANGEROUS (since you're manipulating the array while
+ * looping over its keys while constantly triggering `__get()` re-parsing):
+ *
+ * ```php
+ * foreach ($obj->something as $k => $v) {
+ * // Every assignment here will do __get('something') which parses
+ * // the array over and over again before each element is modified:
+ * $obj->something[$k] = 'foo';
+ * }
+ * ```
+ *
+ * The proper way to solve THAT is to either save a reference to
+ * `$obj->something` as follows:
+ *
+ * ```php
+ * $ref = &$obj->something; // Calls __get('something') a single time.
+ * foreach ($ref as $k => $v) {
+ * $ref[$k] = 'foo'; // Changes array directly via stored reference.
+ * }
+ * ```
+ *
+ * Or to loop through the input values as follows:
+ *
+ * ```php
+ * foreach ($obj->something as &$v) { // Note the "&" symbol.
+ * $v = 'foo'; // Changes array value directly via reference.
+ * }
+ * ```
+ *
+ * 3. Anytime you use a reference, there will be NO DATA VALIDATION of the
+ * new value you are inputting. It will not be checked again UNTIL the next
+ * internal `_getProperty()` call for that property (ie. by the next
+ * `__get()`) so you may therefore unintentionally insert bad data that
+ * doesn't match the class definition map:
+ *
+ * ```php
+ * // Saving a reference to internal data and then changing that variable
+ * // will directly edit the value without letting us validate it:
+ *
+ * $ref = &$obj->some_string; // Make "$ref" into a reference to data.
+ * $ref = new InvalidObject(); // Writes a bad value directly to memory.
+ * var_dump($obj->some_string); // Throws error because of bad data.
+ *
+ * // The same is true about array access. In this case, PHP does
+ * // __get('some_int_array') and finds an array, and then it directly
+ * // manipulates that array's memory without letting us validate it:
+ *
+ * $obj->some_int_array[] = new InvalidObject();
+ * $obj->some_int_array[15] = new InvalidObject();
+ * var_dump($obj->some_int_array); // Throws error because of bad data.
+ * ```
+ *
+ * 4. You can always trust that `__set()` assignments WILL be validated.
+ * But a `__set()` ONLY happens when you assign with the equals operator
+ * (`=`) to a pure property name WITHOUT any array access (`[]`) operators.
+ *
+ * ```php
+ * $obj->some_property = '123'; // Calls __set() and validates new value.
+ * $obj->an_array[] = '123' // Calls __get() and won't validate changes.
+ * ```
+ *
+ * These quirks of "virtual direct property access" are quite easy to deal
+ * with when you know about them, since almost all of them are about array
+ * access, and the rest are about intentional misuse by binding directly to
+ * references. Just avoid making those mistakes.
+ *
+ * However, in general, you should REALLY be using the "virtual function"
+ * access method instead, which allows you to do great things such as
+ * overriding certain class property-functions (ie `getSomething()`) with
+ * your own custom behaviors, so that you can do extra validation, get/set
+ * value transformations, and other fantastic things!
+ *
+ * It is possible (and recommended) to disable virtual properties via the
+ * `ALLOW_VIRTUAL_PROPERTIES` class constant. The feature is only enabled by
+ * default because it helps people easily migrate from legacy projects.
+ *
+ * @param string $propName The property name.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ * @throws LazyJsonMapperException If the value can't be turned into its
+ * assigned class or built-in PHP type, or
+ * if the property doesn't exist in either
+ * the class property definition or the
+ * object instance's data.
+ *
+ * @return mixed The value as the correct type, or `NULL` if it's either a
+ * literal `NULL` in the data or if no value currently existed
+ * in the internal data storage at all. Note that this
+ * function returns the value by-reference.
+ *
+ * @see LazyJsonMapper::_getProperty()
+ * @see LazyJsonMapper::__set()
+ */
+ final public function &__get(
+ $propName)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ // This does the usual validation/parsing of the value to ensure that it
+ // is a valid property. It then creates the property with a default NULL
+ // if it doesn't exist, and finally returns a direct reference to the
+ // internal data entry. That's NECESSARY to allow the user treat the
+ // "virtual property" like a real property, so that they can do things
+ // like array-modification, or binding to it by reference, and so on.
+ // NOTE: Because _getProperty() AND __get() are "return-by-reference"
+ // functions, this return-value is automatically a propagated reference.
+ return $this->_getProperty($propName, true); // Throws.
+ }
+
+ /**
+ * __SET is invoked when writing data to inaccessible properties.
+ *
+ * @param string $propName The property name.
+ * @param mixed $value The new value for the property. `NULL` is always
+ * allowed as the new value, regardless of type.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ * @throws LazyJsonMapperException If the new value isn't legal for that
+ * property, or if the property doesn't
+ * exist in either the class property
+ * definition or the object instance's data.
+ *
+ * @see LazyJsonMapper::_setProperty()
+ * @see LazyJsonMapper::__get()
+ */
+ final public function __set(
+ $propName,
+ $value)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ // NOTE: PHP ignores the return value of __set().
+ $this->_setProperty($propName, $value); // Throws.
+ }
+
+ /**
+ * __ISSET is invoked by calling isset() or empty() on inaccessible properties.
+ *
+ * `NOTE:` When the user calls `empty()`, PHP first calls `__isset()`, and if
+ * that's true it calls `__get()` and ensures the value is really non-empty.
+ *
+ * @param string $propName The property name.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ *
+ * @return bool `TRUE` if the property exists in the object instance's data
+ * and is non-`NULL`.
+ */
+ final public function __isset(
+ $propName)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ return isset($this->_objectData[$propName]);
+ }
+
+ /**
+ * __UNSET is invoked by calling unset() on inaccessible properties.
+ *
+ * @param string $propName The property name.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ *
+ * @see LazyJsonMapper::_unsetProperty()
+ */
+ final public function __unset(
+ $propName)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ // NOTE: PHP ignores the return value of __unset().
+ $this->_unsetProperty($propName);
+ }
+
+ /**
+ * Called during serialization of the object.
+ *
+ * You are not supposed to call this directly. Instead, use PHP's global
+ * `serialize()` call:
+ *
+ * ```php
+ * $savedStr = serialize($obj);
+ * ```
+ *
+ * This serializer is thin and efficient. It simply recursively packs all
+ * nested, internal `LazyJsonMapper` objects as a single, plain data array,
+ * which guarantees the lowest possible serialized data size and the fastest
+ * possible unserialization later (since there will only be a SINGLE parent
+ * object that needs to wake up, rather than a whole tree).
+ *
+ * There is no data conversion/validation of properties (ie to make them
+ * match their "class property map"), since serialization is intended to
+ * quickly save the internal contents of an instance to let you restore it
+ * later, regardless of what was in your internal data property storage.
+ *
+ * Also note that only the internal JSON data is serialized. We will not
+ * serialize any subclass `$properties`, since that would be a misuse of how
+ * your `LazyJsonMapper` subclasses are supposed to be designed. If they
+ * need custom properties, then you can handle those in `_init()` as usual.
+ *
+ * Lastly, you should know that calling `serialize()` will not disrupt any
+ * internal data of the current object instance that you're serializing.
+ * You can therefore continue to work with the object afterwards, or even
+ * `serialize()` the same instance multiple times.
+ *
+ * @throws LazySerializationException If the internal data array cannot be
+ * serialized. But this problem can
+ * literally NEVER happen unless YOU have
+ * intentionally put totally-invalid,
+ * non-serializable sub-objects within
+ * your data array AND those objects in
+ * turn throw exceptions when trying to
+ * `serialize()`. That's NEVER going to
+ * happen with real `json_decode()` data;
+ * so if you constructed our object with
+ * real JSON data then you never have to
+ * look for `serialize()` exceptions.
+ *
+ * @return string The object's internal data as a string representation.
+ * Note that serialization produces strings containing binary
+ * data which cannot be handled as text. It is intended for
+ * storage in binary format (ie a `BLOB` database field).
+ */
+ final public function serialize()
+ {
+ // Tell all of our LJM-properties to pack themselves as plain arrays.
+ // NOTE: We don't do any value-conversion or validation of properties,
+ // since that's not our job. The user wants to SERIALIZE our data, so
+ // translation of array entries to "class-map types" doesn't matter.
+ $objectData = $this->_objectData; // Copy-on-write.
+ array_walk_recursive($objectData, function (&$value) {
+ if (is_object($value) && $value instanceof self) {
+ // This call will recursively detect and take care of nested
+ // objects and return ALL of their data as plain sub-arrays.
+ $value = $value->serialize($value); // Throws.
+ }
+ });
+
+ // If this is not the root object of a nested hierarchy, return raw arr.
+ // NOTE: This efficiently packs all inner, nested LazyJsonMapper objects
+ // by ensuring that their data joins the main root-level array again.
+ $args = func_get_args(); // Check secret argument.
+ $isRootObject = !isset($args[0]) || $args[0] !== $this;
+ if (!$isRootObject) {
+ return $objectData; // Secret, undocumented array return value.
+ }
+
+ // This is the root object that was asked to serialize itself, so finish
+ // the process by serializing the data and validating its success.
+ $serialized = null;
+
+ try {
+ // NOTE: This will ALWAYS succeed if the JSON data array is pure.
+ $serialized = serialize($objectData); // Throws.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ // This can literally ONLY happen if the user has given us (and now
+ // wants to serialize) non-JSON data containing other objects that
+ // attempt (and fail) serialization and throw an exception instead.
+ throw new LazySerializationException(sprintf(
+ 'Unexpected exception encountered while serializing a sub-object. Error: %s',
+ $e->getMessage()
+ ));
+ }
+
+ if (!is_string($serialized)) {
+ // Anything other than a string means that serialize() failed.
+ // NOTE: This should NEVER be able to happen!
+ throw new LazySerializationException(
+ 'The object data could not be serialized.'
+ );
+ }
+
+ // The data is fine. Now just return the string.
+ return $serialized;
+ }
+
+ /**
+ * Called during unserialization of the object.
+ *
+ * You are not supposed to call this directly. Instead, use PHP's global
+ * `unserialize()` call:
+ *
+ * ```php
+ * $restoredObj = unserialize($savedStr);
+ * ```
+ *
+ * This unserializer is thin and efficient. It simply unpacks the serialized
+ * raw data array and uses it as the new object's data, without validating
+ * any of the actual values within the serialized data array. It's intended
+ * to get the new object into the same JSON data state as the serialized
+ * object, which is why we don't re-analyze the data after unserialization.
+ *
+ * Note that the unserialization will call the constructor, with a single
+ * argument (your unserialized JSON data array). Everything that the default
+ * constructor performs will happen during unserialization, EXACTLY as if
+ * you had created a brand new object and given it the data array directly.
+ *
+ * You can therefore fully trust your unserialized objects as much as you
+ * already trust your manually created ones! And you can even store them
+ * somewhere and then unserialize them at any time in the future, during a
+ * completely separate PHP process, and even years from now as long as your
+ * project still contains the specific (sub)-class that you originally
+ * serialized. Have fun!
+ *
+ * @param string $serialized The string representation of the object.
+ *
+ * @throws LazySerializationException If the raw, serialized data cannot be
+ * unserialized at all.
+ * @throws LazyJsonMapperException If there are any problems creating the
+ * class that you are unserializing. See
+ * the regular constructor for error
+ * reasons, but disregard all of its data
+ * validation reasons, since unserialized
+ * JSON data is not validated (analyzed)
+ * when it reaches the constructor. The
+ * most likely reasons why this exception
+ * would be thrown during `unserialize()`
+ * is that your class property map is
+ * invalid and could not be compiled, and
+ * thus the object couldn't be recreated,
+ * OR that a custom `_init()` threw.
+ *
+ * @see LazyJsonMapper::__construct()
+ */
+ final public function unserialize(
+ $serialized = null)
+ {
+ $objectData = null;
+
+ try {
+ // Attempt to unpack the serialized data. Do not @suppress any
+ // syntax errors, since the user needs to know if they've provided
+ // a bad serialized string (or even a non-string value).
+ // NOTE: If the original object only contained perfect JSON data,
+ // then there are no sub-objects. But if any sub-objects existed
+ // within the data, this will recursively unserialize those too.
+ $objectData = unserialize($serialized); // Throws.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ // This can literally ONLY happen if the user had given us (and then
+ // serialized) non-JSON data containing other objects that attempt
+ // (and fail) unserialization now and throw an exception instead.
+ throw new LazySerializationException(sprintf(
+ 'Unexpected exception encountered while unserializing a sub-object. Error: %s',
+ $e->getMessage()
+ ));
+ }
+
+ if (!is_array($objectData)) {
+ // Anything other than an array means that $serialized was invalid.
+ throw new LazySerializationException(
+ 'The serialized object data that you provided could not be unserialized.'
+ );
+ }
+
+ // The data is fine. Now just construct this new object instance.
+ // NOTE: Important since ctor builds/links its necessary property map.
+ // NOTE: The unserialized object (or its data) has no links to the
+ // originally serialized object anymore, apart from the fact that (just
+ // like ANY two identical classes) they would both be linked to the
+ // same compiled class property map ("_compiledPropertyMapLink").
+ $this->__construct($objectData); // Throws.
+ }
+
+ /**
+ * Advanced function: Clear the global "magic function lookup cache".
+ *
+ * This command is NOT RECOMMENDED unless you know exactly what you are
+ * doing and WHY you are doing it. This clears the globally shared cache
+ * of magic function-name to property-name translations.
+ *
+ * It is always safe to clear that cache, since any future magic function
+ * calls (even to existing object instances) will simply re-calculate those
+ * translations and put them back in the global cache again.
+ *
+ * However, it's not recommended to clear the cache if you're sure that
+ * you'll constantly be calling the same functions over and over again.
+ * It's obviously faster to keep cached translations for instant lookups!
+ *
+ * @return int How many unique function names were cached before clearing.
+ */
+ final public static function clearGlobalMagicLookupCache()
+ {
+ $lookupCount = count(self::$_magicLookupCache);
+ self::$_magicLookupCache = [];
+
+ return $lookupCount;
+ }
+
+ /**
+ * Advanced function: Clear the global "compiled property map cache".
+ *
+ * This command is NOT RECOMMENDED unless you know exactly what you are
+ * doing and WHY you are doing it. This clears the globally shared cache of
+ * compiled property maps, which may be useful if you've created tons of
+ * different class objects from lots of different classes, and you no longer
+ * have any of those objects in memory and will never create any of them
+ * again. In that case, clearing the cache would get rid of the memory
+ * consumed by the compiled maps from each class.
+ *
+ * All currently existing object instances will continue to work, since they
+ * retain their own personal links to their own compiled property maps.
+ * Therefore, the memory you free by calling this function may not be the
+ * full amount (until ALL of the object instances of all classes are freed).
+ * PHP will only be able to free their unused parent classes, and their
+ * unused subclasses, and any other unused classes in general. But PHP will
+ * retain memory for the SPECIFIC, per-class maps for all living objects.
+ *
+ * Calling this function is totally harmless, since all future class
+ * instance constructors will simply re-compile their whole map hierarchies
+ * again from scratch, and all class maps will be re-built IDENTICALLY the
+ * next time since all of their map definitions themselves are immutable
+ * class constants and can NEVER be modified at runtime (not even via PHP
+ * 7.1's advanced `ReflectionClassConstant` class).
+ *
+ * However, because of the fact that all existing object instances retain
+ * private links to their own maps from the previous compilation, it means
+ * that you may actually end up using MORE memory after clearing the global
+ * cache, IF you still have a LOT of different living object instances AND
+ * also begin to create new instances of various classes again (and thus
+ * begin re-compiling new, global instances of each of their "cleared" class
+ * property maps).
+ *
+ * In short: Do NOT call this function unless you know why you are doing it!
+ *
+ * @return int How many unique classes were cached before clearing.
+ */
+ final public static function clearGlobalPropertyMapCache()
+ {
+ $classCount = count(self::$_propertyMapCache->classMaps);
+ self::$_propertyMapCache->clearCache();
+
+ return $classCount;
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/FunctionTranslation.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/FunctionTranslation.php
new file mode 100755
index 0000000..89cb92a
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/FunctionTranslation.php
@@ -0,0 +1,314 @@
+ `__foo__bar___x_baz__` (snake)
+ * `__foo_Bar__XBaz__` (camel)
+ * - `0m__AnUn0x` => `0m___an_un0x` (snake) & `0m__AnUn0x` (camel)
+ * - `Some0XThing` => `some0_x_thing` (snake) & `some0XThing` (camel)
+ * - `Some0xThing` => `some0x_thing` (snake) & `some0xThing` (camel)
+ * - `SomeThing` => `some_thing` (snake) & `someThing` (camel)
+ * - `Something` => `something` (snake) & `NULL` (camel)
+ * - `___` => `___` (snake) & `NULL` (camel)
+ * - `_0` => `_0` (snake) & `NULL` (camel)
+ * - `_Messages` => `_messages` (snake) & `NULL` (camel)
+ * - `__MessageList` => `__message_list` (snake) & `__messageList` (camel)
+ * - `123` => `123` (snake) & `NULL` (camel)
+ * - `123prop` => `123prop` (snake) & `NULL` (camel)
+ * - `123Prop` => `123_prop` (snake) & `123Prop` (camel)
+ *
+ * ---------------------------------------------------------------------
+ *
+ * `NOTE:` The class validates all parameters, but provides public properties to
+ * avoid needless function calls. It's therefore your responsibility to never
+ * assign any bad values to the public properties after this object's creation!
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ *
+ * @see PropertyTranslation
+ */
+class FunctionTranslation
+{
+ /**
+ * The property name in underscore "snake_case" style.
+ *
+ * For example `some_example_property`.
+ *
+ * @var string
+ */
+ public $snakePropName;
+
+ /**
+ * The property name in "camelCase" style.
+ *
+ * For example `someExampleProperty`.
+ *
+ * `NOTE:` This is `NULL` if the property consists only of a single word.
+ *
+ * @var string|null
+ */
+ public $camelPropName;
+
+ /**
+ * Constructor.
+ *
+ * @param string $funcCase The "property" portion of the function name to
+ * translate, in "FunctionCase" style, such as
+ * `SomeExampleProperty`.
+ *
+ * @throws MagicTranslationException If the `$funcCase` name is unparseable.
+ *
+ * @see FunctionTranslation::splitFunctionName() A helper to split your
+ * function names into the
+ * necessary parts.
+ */
+ public function __construct(
+ $funcCase)
+ {
+ if (!is_string($funcCase) || $funcCase === '') {
+ throw new MagicTranslationException('The function name must be a non-empty string value.');
+ }
+
+ // Convert the FuncCase name to its snake and camel properties.
+ $result = $this->_funcCaseToProperties($funcCase);
+ if ($result === false) {
+ throw new MagicTranslationException(sprintf(
+ 'The provided input value "%s" is not a valid FunctionCase name.',
+ $funcCase
+ ));
+ }
+
+ // We are done with the conversions.
+ $this->snakePropName = $result['snake'];
+ $this->camelPropName = $result['camel'];
+ }
+
+ /**
+ * Split a function name into its function-type and FuncCase components.
+ *
+ * This helper function takes care of splitting a full function name, such
+ * as `getSomeVariable`, into `get` (its function type) and `SomeVariable`
+ * (the valid input format for the FunctionTranslation constructor).
+ *
+ * Call it from your own code before constructing your `FunctionTranslation`
+ * objects. Don't worry about the extra function call for this splitter.
+ * It can perform its job 2.5 million times per second on a 2010 Core i7
+ * dual-core laptop on PHP7, and 0.64 million times per second on PHP5.
+ * And directly embedding these same steps instead of calling this function
+ * will only gain 5% more speed in PHP7 and 16% more speed in PHP5. But the
+ * numbers are already so astronomically fast that it doesn't matter!
+ *
+ * This splitting into `get` and `SomeVariable` is easy and super efficient.
+ * It is this class' final translation of the FunctionCase `SomeVariable`
+ * part into `some_variable` and `someVariable` properties that's the HARD
+ * step which should be cached.
+ *
+ * Recommended usage:
+ *
+ * ```php
+ * list($functionType, $funcCase) = FunctionTranslation::splitFunctionName($name);
+ * // if $functionType is now NULL, the input was invalid. otherwise it was ok.
+ * ```
+ *
+ * @param string $functionName The function name to split. It's your job to
+ * make sure this is a string-type variable! We
+ * will not validate its type. Empty strings ok.
+ *
+ * @return string[]|null[] Two-element array of `functionType` (element 0) &
+ * `funcCase` (element 1). If the input name wasn't
+ * valid a `doSomething` (function-camelCase), then
+ * both elements are `NULL` instead of strings.
+ */
+ public static function splitFunctionName(
+ $functionName)
+ {
+ // Split the input on the FIRST encountered NON-LOWERCAZE ("a-z")
+ // character. And allow empty splits (that's important). Because of the
+ // fact that it splits on non-lowercase character types, it means that
+ // if there are 2 chunks then we KNOW that there was a non-lowercase
+ // character (such as _, 0-9, A-Z, etc) in the input. And if there are
+ // two chunks but the first chunk is an empty string then we know that
+ // there was no lowercase a-z PREFIX in the input. And if there is only
+ // 1 chunk then we know the entire input was all-lowercase a-z.
+ //
+ // Examples of this preg_split()'s behavior:
+ //
+ // "get_" => 2 chunks: "get" & "_"
+ // "getgetX" => 2 chunks: "getget" & "X"
+ // "eraseSomething" => 2 chunks: "erase" & "Something"
+ // "getgetget" (only lowercase a-z) => 1 chunk: "getgetget"
+ // "GetSomething" (no lowercase prefix) => 2 chunks: "" & "GetSomething"
+ // "G" => 2 chunks: "" & "G"
+ // "GX" => 2 chunks: "" & "GX"
+ // "Gx" => 2 chunks: "" & "Gx"
+ // "0" => 2 chunks: "" & "0"
+ // "gx" => 1 chunk: "gx"
+ // "gX" => 2 chunks: "g" & "X"
+ // "g0" => 2 chunks: "g" & "0"
+ // "" (empty string input) => 1 chunk: ""
+ //
+ // Therefore, we know that the input was valid (a lowercase a-z prefix
+ // followed by at least one non-lowercase a-z after that) if we have two
+ // chunks and the first chunk is non-empty!
+ $chunks = preg_split('/(?=[^a-z])/', $functionName, 2);
+ if (count($chunks) === 2 && $chunks[0] !== '') {
+ // [0] = prefix (functionType), [1] = suffix (FuncCase).
+ return $chunks;
+ }
+
+ // Invalid input. Return NULL prefix and NULL suffix values.
+ static $invalidChunks = [null, null]; // Static=Only created first call.
+
+ return $invalidChunks;
+ }
+
+ /**
+ * Converts a FunctionCase name to snake and camel properties.
+ *
+ * See input/output examples in class documentation above.
+ *
+ * @param string $funcCase
+ *
+ * @return array|bool Associative array with `snake` & `camel` elements if
+ * successful, otherwise `FALSE`.
+ */
+ protected function _funcCaseToProperties(
+ $funcCase)
+ {
+ // This algorithm is the exact inverse of PropertyTranslation.
+ // Read that class for more information.
+
+ // There's nothing to do if the input is empty...
+ if (!strlen($funcCase)) {
+ return false;
+ }
+
+ // First, we must decode our encoded representation of any special PHP
+ // operators, just in case their property name had illegal chars.
+ $funcCase = SpecialOperators::decodeOperators($funcCase);
+
+ // Now remove and count all leading underscores (important!).
+ // Example: "__MessageList" => "MessageList".
+ $result = ltrim($funcCase, '_');
+ $leadingUnderscores = strlen($funcCase) - strlen($result);
+
+ // Verify that the REMAINING input result doesn't contain lowercase a-z
+ // as its FIRST character. In that case, we were given invalid input,
+ // because the FuncCase style REQUIRES that the first character is a
+ // NON-LOWERCASE. Anything else is fine, such as UpperCase, numbers or
+ // special characters, etc, but never lowercase, since our splitter
+ // splitFunctionName() would NEVER give us a FuncName part with a
+ // leading lowercase letter! However, the splitter COULD give us
+ // something like "__m" from "get__m". But our PropertyTranslation would
+ // NEVER create output like "__m". It would have created "__M". So
+ // anything that now remains at the start of the string after stripping
+ // leading underscores MUST be non-lowercase.
+ if (preg_match('/^[a-z]/', $result)) {
+ return false;
+ }
+
+ // Split the input into chunks on all camelcase boundaries.
+ // NOTE: Since all chunks are split on camelcase boundaries below, it
+ // means that each chunk ONLY holds a SINGLE fragment which can ONLY
+ // contain at most a SINGLE capital letter (the chunk's first letter).
+ // NOTE: The "PREG_SPLIT_NO_EMPTY" ensures that we don't get an empty
+ // leading array entry when the input begins with an "Upper" character.
+ // NOTE: If this doesn't match anything (meaning there are no uppercase
+ // characters to split on), the input is returned as-is. Such as "123".
+ // NOTE: If $result is an empty string, this returns an empty array.
+ // Example: "MessageList" => "Message" & "List"
+ $chunks = preg_split('/(?=[A-Z])/', $result, -1, PREG_SPLIT_NO_EMPTY);
+ if ($chunks === false) {
+ return false; // Only happens on regex engine failure, NOT mismatch!
+ }
+ $chunkCount = count($chunks);
+
+ // Handle the scenario where there are no chunks ($result was empty).
+ // NOTE: Thanks to all of the validation above, this can ONLY happen
+ // when input consisted entirely of underscores with nothing after that.
+ if ($chunkCount === 0) {
+ // Insert a fake, empty element to act as the first chunk, to ensure
+ // that we have something to insert the underscores into.
+ $chunks[] = '';
+ $chunkCount++;
+ }
+
+ // Lowercase the leading uppercase of 1st chunk (whatever is in there),
+ // since that chunk needs lowercase in both snake_case and camelCase.
+ // Example: "Message" & "List" => "message" & "List"
+ $chunks[0] = lcfirst($chunks[0]);
+
+ // If there were any leading underscores, prepend all of them to the 1st
+ // chunk, which ensures that they become part of the first chunk.
+ // Example: "__message" & "List"
+ if ($leadingUnderscores > 0) {
+ $chunks[0] = str_repeat('_', $leadingUnderscores).$chunks[0];
+ }
+
+ // Now let's create the snake_case and camelCase variable names.
+
+ // The property name chunks are already in perfect format for camelCase.
+ // The first chunk starts with a lowercase letter, and all other chunks
+ // start with an uppercase letter. So generate the camelCase property
+ // name version first. But only if there are 2+ chunks. Otherwise NULL.
+ // NOTE: Turns "i,Tunes,Item" into "iTunesItem", and "foo" into NULL.
+ $camelPropName = $chunkCount >= 2 ? implode('', $chunks) : null;
+
+ // Now make the second property name chunk and onwards into lowercase,
+ // and then generate the all-lowercase "snake_case" property name.
+ // NOTE: Turns "some,Property,Name" into "some_property_name".
+ for ($i = 1; $i < $chunkCount; ++$i) {
+ $chunks[$i] = lcfirst($chunks[$i]); // Only first letter can be UC.
+ }
+ $snakePropName = implode('_', $chunks);
+
+ // Return the final snake_case and camelCase property names.
+ return [
+ 'snake' => $snakePropName,
+ 'camel' => $camelPropName,
+ ];
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/PropertyTranslation.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/PropertyTranslation.php
new file mode 100755
index 0000000..a6e02e3
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/PropertyTranslation.php
@@ -0,0 +1,191 @@
+ `__foo__bar___x_baz__` (snake)
+ * `__foo_Bar__XBaz__` (camel)
+ * - `0m__AnUn0x` => `0m___an_un0x` (snake) & `0m__AnUn0x` (camel)
+ * - `Some0XThing` => `some0_x_thing` (snake) & `some0XThing` (camel)
+ * - `Some0xThing` => `some0x_thing` (snake) & `some0xThing` (camel)
+ * - `SomeThing` => `some_thing` (snake) & `someThing` (camel)
+ * - `Something` => `something` (snake & camel identical; no ucwords)
+ * - `___` => `___` (snake & camel identical; no ucwords)
+ * - `_0` => `_0` (snake & camel identical; no ucwords)
+ * - `_Messages` => `_messages` (snake & camel identical; no ucwords)
+ * - `__MessageList` => `__message_list` (snake) & `__messageList` (camel)
+ * - `123` => `123` (snake & camel identical; no ucwords)
+ * - `123prop` => `123prop` (snake & camel identical; no ucwords)
+ * - `123Prop` => `123_prop` (snake) & `123Prop` (camel)
+ *
+ * ---------------------------------------------------------------------
+ *
+ * `NOTE:` The class validates all parameters, but provides public properties to
+ * avoid needless function calls. It's therefore your responsibility to never
+ * assign any bad values to the public properties after this object's creation!
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ *
+ * @see FunctionTranslation
+ */
+class PropertyTranslation
+{
+ /**
+ * The property name in "FunctionCase" style.
+ *
+ * For example `AwesomeProperty`.
+ *
+ * @var string
+ */
+ public $propFuncCase;
+
+ /**
+ * Constructor.
+ *
+ * @param string $propertyName The name of the property to translate, in
+ * either `snake_case` or `camelCase` style,
+ * such as `awesome_property` (snake)
+ * or `awesomeProperty` (camel). The
+ * translations will differ based on
+ * which style is used. (That's intentional.)
+ *
+ * @throws MagicTranslationException If the property name is unparseable.
+ */
+ public function __construct(
+ $propertyName)
+ {
+ if (!is_string($propertyName) || $propertyName === '') {
+ throw new MagicTranslationException('The property name must be a non-empty string value.');
+ }
+
+ $this->propFuncCase = $this->_propToFunctionCase($propertyName);
+ }
+
+ /**
+ * Converts a property name to FunctionCase.
+ *
+ * See input/output examples in class documentation above.
+ *
+ * @param string $propName The property name, as either `snake_case` or
+ * `camelCase`. The translations will differ based
+ * on which style is used. (That's intentional.)
+ *
+ * @return string
+ */
+ protected function _propToFunctionCase(
+ $propName)
+ {
+ // To translate a property name to FunctionCase, we must simply convert
+ // any leading lowercase (a-z) character to uppercase, as well as any
+ // (a-z) that comes after an underscore. In the latter case, the
+ // underscore must be removed during the conversion.
+ // For example: "example_property" = "ExampleProperty".
+ // (If multiple underscores, still just remove ONE: "a__b"="A_B").
+ //
+ // However, there is a special case: All LEADING underscores must be
+ // preserved exactly as-is: "__messages" = "__Messages" (still 2).
+ //
+ // As for camelCasePropertyNames? They're already perfect, except that
+ // the first letter must be made uppercase. And IF they have any inner
+ // "_[a-z]" (lowercase) chunks, those should be translated as they would
+ // for a snake_case string. But any "_[A-Z]" should not be touched.
+ // In other words, the algorithm is exactly the same for camelCase.
+
+ // Begin by removing and counting all leading underscores (important!).
+ // NOTE: The reason why we have to do this is because otherwise a
+ // property named "_messages" would be translated to just "Messages",
+ // which then becomes incredibly hard to guess whether it means
+ // "messages" or "_messages". So by preserving any leading underscores,
+ // we remove that ambiguity. It also make the function names more
+ // logical (they match the property's amount of leading underscores) and
+ // it removes clashes. For example if the user defines both "messages"
+ // and "_messages", they can safely use their "getMessages()" and
+ // "get_Messages()" without any ambiguity about what they refer to.
+ $result = ltrim($propName, '_');
+ $leadingUnderscores = strlen($propName) - strlen($result);
+
+ // Now simply uppercase any lowercase (a-z) character that is either at
+ // the start of the string or appears immediately after an underscore.
+ //
+ // ----------------------------------------
+ // TODO: If someone is using Unicode in their JSON data, we should
+ // simply extend this (and also the FunctionTranslation class) to run
+ // with PHP's slower mb_* multibyte functions and "//u" UTF-8 flags,
+ // so that we can support functions like "getÅngbåt()", for a property
+ // named '{"ångbåt":1}'. But honestly, I doubt that even international
+ // users name their JSON data in anything but pure, highly-compressible
+ // ASCII, such as "angbat" and "getAngbat()" in the example above. So
+ // for now, we can have the efficient ASCII algorithms here. In the
+ // future we may want to provide a class-constant to override the
+ // parsers to enable UTF-8 mode, so that the user can have that slower
+ // parsing behavior in certain classes only. And in that case, the magic
+ // function translations can still be stored in the same global cache
+ // together with all the ASCII entries, since they'd use their UTF-8
+ // names as key and thus never clash with anything from this algorithm.
+ // ----------------------------------------
+ $result = preg_replace_callback('/(?:^|_)([a-z])/', function ($matches) {
+ return ucfirst($matches[1]); // Always contains just 1 character.
+ }, $result);
+
+ // Now just prepend any leading underscores that we removed earlier.
+ if ($leadingUnderscores > 0) {
+ $result = str_repeat('_', $leadingUnderscores).$result;
+ }
+
+ // Lastly, we must now translate special PHP operators to an encoded
+ // representation, just in case their property name has illegal chars.
+ $result = SpecialOperators::encodeOperators($result);
+
+ return $result;
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/SpecialOperators.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/SpecialOperators.php
new file mode 100755
index 0000000..1659137
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/SpecialOperators.php
@@ -0,0 +1,169 @@
+',
+ ];
+
+ $translations = ['encode' => [], 'decode' => []];
+ foreach ($operators as $chr) {
+ $hex = str_pad(strtoupper(dechex(ord($chr))), 2, '0', STR_PAD_LEFT);
+ $encoded = sprintf('_x%s_', $hex);
+ $translations['encode'][$chr] = $encoded;
+ $translations['decode'][$encoded] = $chr;
+ }
+
+ self::$_translations = $translations;
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyDefinition.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyDefinition.php
new file mode 100755
index 0000000..24bed79
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyDefinition.php
@@ -0,0 +1,325 @@
+arrayDepth = 0;
+ $this->propType = null;
+ $this->isObjectType = false;
+
+ return; // Skip the rest of the code.
+ }
+
+ if (!is_string($definitionStr)) {
+ throw new BadPropertyDefinitionException(
+ 'The property definition must be a string value.'
+ );
+ }
+
+ // Clean up and validate any provided base namespace or make it global.
+ // IMPORTANT NOTE: Any provided classnames "relative to a certain
+ // namespace" will NOT know about any "use"-statements in the files
+ // where those classes are defined. Even Reflection cannot detect "use".
+ if (is_string($baseNamespace)) { // Custom namespace.
+ if (strlen($baseNamespace) > 0
+ && ($baseNamespace[0] === '\\' || substr($baseNamespace, -1) === '\\')) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Invalid namespace "%s". The namespace is not allowed to start or end with a backslash. Use __NAMESPACE__ format.',
+ $baseNamespace
+ ));
+ }
+ } else {
+ $baseNamespace = ''; // Global namespace.
+ }
+
+ // Set arrayDepth: Count well-formed array-brackets at end of type.
+ // Example: "int[][][]" or "[][]" (yes, array of untyped is possible.)
+ $this->arrayDepth = 0;
+ if (preg_match('/(?:\[\])+$/', $definitionStr, $matches)) {
+ // $matches[0] is the sequence of valid pairs, ie "[][][]".
+ $this->arrayDepth = strlen($matches[0]) / 2;
+ // Get rid of the pairs from the end of the definition.
+ $definitionStr = substr($definitionStr, 0, -($this->arrayDepth * 2));
+ }
+
+ // Set propType: It's what remains of our definition string.
+ // Example: "" or "mixed" or "int" or "\Foo\Bar" or "Bar"
+ $this->propType = $definitionStr;
+
+ // Always store "" or "mixed" as NULL, to make it easy to check.
+ if ($this->propType === '' || $this->propType === 'mixed') {
+ $this->propType = null;
+ }
+
+ // If there's no type, or if it refers to a basic type, then we're done.
+ // This ensures that our basic non-empty type value is a real PHP type.
+ // NOTE: This is intentionally cAsE-sensitive.
+ // NOTE: The basic types are reserved names in PHP, so there's no risk
+ // they refer to classes, since PHP doesn't allow such class names.
+ if ($this->propType === null || in_array($this->propType, self::BASIC_TYPES)) {
+ $this->isObjectType = false;
+
+ return; // Skip the rest of the code.
+ }
+
+ // They are trying to refer to a class (or they've mistyped a basic
+ // type). Validate the target to ensure that it's fully trustable
+ // to be a reachable LazyJsonMapper-based class.
+ $this->isObjectType = true;
+
+ // Check if they've used the special shortcut for the core class.
+ if ($this->propType === 'LazyJsonMapper') {
+ $this->propType = '\\'.LazyJsonMapper::class;
+ }
+
+ // Begin by copying whatever remaining type value they've provided...
+ $classPath = $this->propType;
+
+ // First check if they want the global namespace instead.
+ if ($classPath[0] === '\\') {
+ // Their class refers to a global path.
+ $baseNamespace = ''; // Set global namespace as base.
+ $classPath = substr($classPath, 1); // Strip leading "\".
+ }
+
+ // Construct the full class-path from their base namespace and class.
+ // NOTE: The leading backslash is super important to ensure that PHP
+ // actually looks in the target namespace instead of a sub-namespace
+ // of its current namespace.
+ $fullClassPath = sprintf(
+ '\\%s%s%s',
+ $baseNamespace,
+ $baseNamespace !== '' ? '\\' : '',
+ $classPath
+ );
+
+ // Ensure that the target class actually exists (via autoloader).
+ if (!class_exists($fullClassPath)) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Class "%s" not found.',
+ $fullClassPath
+ ));
+ }
+
+ // We'll use a reflector for analysis, to ensure the class is valid
+ // for use as a target type. It must be based on LazyJsonMapper.
+ try {
+ // First clean up the case-insensitive class name to become the
+ // EXACT name for the class. So we can trust "propType" in ===.
+ // Example: "\fOO\bAr" to "Foo\Bar". (Without any leading "\".)
+ // NOTE: getName() gets the "NameSpace\Class" without leading "\",
+ // which is PHP's preferred notation. And that is exactly how we
+ // will store the final path internally, so that we can always
+ // trust comparisons of these typenames vs full paths to other
+ // class names retrieved via other methods such as get_class().
+ // It does however mean that propType is NOT the right value for
+ // actually constructing the class safely.
+ $reflector = new ReflectionClass($fullClassPath);
+ $fullClassPath = $reflector->getName();
+
+ // The target class or its parents MUST inherit LazyJsonMapper,
+ // so that it implements the necessary behaviors and can be
+ // trusted to accept our standardized constructor parameters.
+ // NOTE: As you can see, we also allow users to map directly to
+ // plain "LazyJsonMapper" objects. It's a very bad idea, since
+ // they don't get any property definitions, and therefore their
+ // object would be unreliable. But that's the user's choice.
+ if ($fullClassPath !== LazyJsonMapper::class
+ && !$reflector->isSubClassOf('\\'.LazyJsonMapper::class)) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Class "\\%s" must inherit from LazyJsonMapper.',
+ $fullClassPath
+ ));
+ }
+
+ // Alright, the class path has been fully resolved, validated
+ // to be a LazyJsonMapper, and normalized into its correct name.
+ // ... Rock on! ;-)
+ $this->propType = $fullClassPath;
+ } catch (ReflectionException $e) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Reflection failed for class "%s". Reason: "%s".',
+ $fullClassPath, $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Get the strict, global path to the target class.
+ *
+ * Always use this function when creating objects or in any other way using
+ * the "property type" class path as argument for PHP's class checking
+ * functions. The strict path that it provides ensures that PHP will find
+ * the global path instead of resolving to a local object.
+ *
+ * @return string|null Strict path if this is an object, otherwise `NULL`.
+ */
+ public function getStrictClassPath()
+ {
+ return $this->isObjectType ? '\\'.$this->propType : null;
+ }
+
+ /**
+ * Check if all values of this property match another property object.
+ *
+ * @param PropertyDefinition $otherObject The object to compare with.
+ *
+ * @return bool `TRUE` if all property values are identical, otherwise `FALSE`.
+ */
+ public function equals(
+ PropertyDefinition $otherObject)
+ {
+ // The "==" operator checks for same class and matching property values.
+ return $this == $otherObject;
+ }
+
+ /**
+ * Get the property definition as its string representation.
+ *
+ * The string perfectly represents the property definition, and can
+ * therefore even be used when constructing other object instances.
+ *
+ * @return string
+ */
+ public function asString()
+ {
+ return sprintf(
+ '%s%s%s',
+ $this->isObjectType ? '\\' : '',
+ $this->propType !== null ? $this->propType : 'mixed',
+ str_repeat('[]', $this->arrayDepth)
+ );
+ }
+
+ /**
+ * Get the property definition as its string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->asString();
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCache.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCache.php
new file mode 100755
index 0000000..3f88c0d
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCache.php
@@ -0,0 +1,73 @@
+classMaps = [];
+ $this->compilerLocks = [];
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCompiler.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCompiler.php
new file mode 100755
index 0000000..6872e80
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCompiler.php
@@ -0,0 +1,1274 @@
+compile(); // Throws.
+ }
+
+ /**
+ * Private Constructor.
+ *
+ * @param bool $isRootCompiler Whether this is the root-level
+ * compiler in a compile-stack.
+ * @param PropertyMapCache $propertyMapCache The class/lock cache to use.
+ * @param string $solveClassName The full path of the class to
+ * compile, but without any
+ * leading `\` global prefix. To
+ * save time, we assume the caller
+ * has already verified that it is
+ * a valid `LazyJsonMapper` class.
+ *
+ * @throws BadPropertyMapException
+ */
+ private function __construct(
+ $isRootCompiler,
+ PropertyMapCache $propertyMapCache,
+ $solveClassName)
+ {
+ // Sanity-check the untyped arguments to protect against accidents.
+ if (!is_bool($isRootCompiler)) {
+ throw new BadPropertyMapException('The isRootCompiler argument must be a boolean.');
+ }
+ if (!is_string($solveClassName) || $solveClassName === '') {
+ throw new BadPropertyMapException('The class name to solve must be a non-empty string.');
+ }
+
+ // Save user-properties.
+ // NOTE: The compiler uses a concept of "root compiler" and recursive
+ // "sub-compilers". The compilers automatically spawn sub-compilers for
+ // any resources they need. And the ROOT compiler is responsible for
+ // coordinating everything and doing any final post-processing work
+ // AFTER the main hierarchy (extends) and "import" compilation steps.
+ $this->_isRootCompiler = $isRootCompiler;
+ $this->_propertyMapCache = $propertyMapCache;
+ $this->_solveClassName = $solveClassName;
+
+ // Generate a strict name (with a leading "\") to tell PHP to search for
+ // it from the global namespace, in commands where we need that.
+ // NOTE: We index the cache by "Foo\Bar" for easy lookups, since that is
+ // PHP's internal get_class()-style representation. But we use strict
+ // "\Foo\Bar" when we actually interact with a class or throw errors.
+ // That way, both PHP and the user understands that it's a global path.
+ $this->_strictSolveClassName = Utilities::createStrictClassPath($this->_solveClassName);
+
+ // We will keep track of ALL classes compiled by ourselves and our
+ // recursive sub-compilers. In case of errors ANYWHERE in the process,
+ // we MUST ensure that all of those classes are erased from the cache
+ // again, since they may have serious errors. This list of compiled
+ // classes was specifically added to handle the fact that the class map
+ // and its hierarchy/imports themselves often fully compile themselves
+ // successfully, but then THEIR property class compilations (the root-
+ // POST-PROCESSING step which compiles all classes pointed to by the
+ // maps) MAY fail. In that case, or if there are ANY other compilation
+ // problems in any of the class hierarchies, then we will be sure to
+ // erase ALL of our compiled classes from the cache; otherwise it WOULD
+ // look like those classes are fully compiled and reliable, despite the
+ // bad and invalid classes some of their properties point to!
+ $this->compiledClasses = [];
+
+ // During the main compilation phase, we'll ONLY compile the actual
+ // class that we've been told to compile, as well as its parent
+ // hierarchy and any imports that any of them contain. But any of those
+ // compiled maps MAY in turn contain PROPERTIES that point at OTHER
+ // uncompiled classes. The PropertyDefinition construction will always
+ // verify that those properties are pointing at reachable (having a
+ // valid class-path) LazyJsonMapper classes. However, the actual CLASS
+ // MAPS of THOSE classes may contain problems too and may be impossible
+ // to compile. We obviously want to figure that out for the user RIGHT
+ // NOW so that they don't run into issues later when trying to access
+ // such a property someday (which would THEN trigger its map compilation
+ // and throw a compiler failure deep into their runtime environment when
+ // they least expected it). The truth is that normal people will
+ // sometimes write bad classes, and then point properties at those bad
+ // classes. And without us proactively pre-compiling all classes pointed
+ // to by their properties, they would NEVER know about the problem until
+ // someday later when their program suddenly accesses that bad property
+ //
+ // So we SHOULD analyze everything for the user's safety. But... we
+ // unfortunately CANNOT compile them during the main compilation stage.
+ // In fact, we cannot even compile them afterwards EITHER, UNLESS we are
+ // the ABSOLUTE ROOT COMPILE-CALL which STARTED the current compilation
+ // process. Only the root function call is allowed to resolve PROPERTY
+ // references to other classes and ensure that THOSE maps are
+ // successfully are compiled too. Otherwise we'd run into circular
+ // compilation issues where properties fail to compile because they may
+ // be pointing at things which are not compiled yet (such as a class
+ // containing a property that points at itself, or "A" having a property
+ // that points to class "B" which has a property that points to class
+ // "C" which has a property that points back at class "A" and therefore
+ // would cause a circular reference if we'd tried to compile those
+ // property-classes instantly as-they're encountered). We must therefore
+ // defer property-class compilation until ALL core classes in the
+ // current inheritance/import chain are resolved. THEN it's safe to
+ // begin compiling any other encountered classes from the properties...
+ //
+ // The solution is "simple": Build a list of the class-paths of all
+ // encountered UNCOMPILED property classes within our own map tree and
+ // within any imports (which are done via recursive compile-calls). To
+ // handle imports, we simply ensure that our compile-function (this one)
+ // lets the parent retrieve our list of "encountered uncompiled classes
+ // from properties" so that we'll let it bubble up all the way to the
+ // root class/call that began the whole compile-chain. And then just let
+ // that root-level call resolve ALL uncompiled properties from the
+ // ENTIRE tree by simply compiling the list of property-classes one by
+ // one if they're still missing. That way, we'll get full protection
+ // against uncompilable class-properties ANYWHERE in our tree, and we'll
+ // do it at a totally safe time (at the end of the main compilation call
+ // that kicked everything off)!
+ //
+ // NOTE: As an optimization, to avoid constant array-writes and the
+ // creation of huge and rapidly growing "encountered classes" arrays, we
+ // will attempt to ONLY track the encountered classes that are MISSING
+ // from the cache (not yet compiled). And this list will ALSO be checked
+ // one more time at the end, to truly clear everything that's already
+ // done. That's intended to make the list as light-weight as possible
+ // so that our parent-compiler doesn't have to do heavy lifting.
+ //
+ // NOTE: The fact is that most classes that properties point to will
+ // already be pre-compiled, or at least large parts of their
+ // inheritance/import hierarchy will already be compiled, so this
+ // recursive "property-class" compilation isn't very heavy at all. It's
+ // as optimized as it can be. Classes we encounter will ONLY compile the
+ // specific aspects of their class maps which HAVEN'T been compiled yet,
+ // such as a class that is a sub-tree (extends) of an already-compiled
+ // class, which means that the extra class would be very fast to
+ // compile. Either way, we basically spend a tiny bit of extra effort
+ // here during compilation to save users from a TON of pain from THEIR
+ // bad classes later. ;-)
+ $this->uncompiledPropertyClasses = [];
+
+ // Initialize the remaining class properties.
+ $this->_classHierarchy = [];
+ $this->_ourCompilerClassLocks = [];
+ $this->_previousMapConstantValue = null;
+ $this->_currentClassInfo = null;
+ $this->_currentClassPropertyMap = [];
+ }
+
+ /**
+ * Compile the solve-class, and its parent/import hierarchy (as-necessary).
+ *
+ * Intelligently watches for compilation errors, and if so it performs an
+ * auto-rollback of ALL classes compiled by this `compile()`-call AND by all
+ * of its recursive sub-compilations (if any took place).
+ *
+ * It performs the rollback if there were ANY issues with the solve-class or
+ * ANY part of its inheritance hierarchy (extends/imports) OR with the final
+ * post-processing compilation of all of the classes that THEIR properties
+ * were pointing at. And note that the post-processing is ALSO recursive and
+ * will FULLY validate the entire hierarchies and property trees of ALL
+ * classes that IT compiles, and so on... until no more work remains.
+ *
+ * So if this function DOESN'T throw, then you can TRUST that the ENTIRE
+ * hierarchy of property maps related to the requested solve-class has been
+ * compiled. However, note that their final PROPERTY CLASS compilation step
+ * and final success-validation doesn't happen until the top-level
+ * rootCompiler is reached again, since it's the job of the root to handle
+ * the recursive compilation and resolving of all classes encountered in
+ * properties. So it's only the rootCompiler's `compile()`-call that TRULY
+ * matters and will determine whether EVERYTHING was successful or not.
+ *
+ * Basically: If we fail ANY aspect of the request to compile, then we'll
+ * FULLY roll back everything that we've changed during our processing.
+ * Which safely guarantees that the FINAL map compilation cache ONLY
+ * contains fully verified and trustable classes and their COMPLETE class
+ * inheritance and property hierarchies!
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ *
+ * @see PropertyMapCompiler::compileClassPropertyMap() The public, static
+ * class entry point.
+ * @see PropertyMapCompiler::_compile() The internal compiler
+ * core.
+ */
+ public function compile()
+ {
+ try {
+ $this->_compile();
+ } catch (\Exception $e) { // NOTE: Could target exact type, but meh.
+ // Our compilation or one of its sub-compilations has failed... We
+ // MUST now perform a rollback and unset everything in our list of
+ // compiled classes (which also includes everything that our
+ // sub-compilers have compiled).
+ // NOTE: Every compile()-call is responsible for unsetting ITS OWN
+ // list of (sub-)compiled classes. Because the parent-handler that
+ // reads our "compiledClasses" property may not run when an
+ // exception happens. So it's OUR job to clear OUR changes to the
+ // cache before we let the exception bubble up the call-stack.
+ foreach ($this->compiledClasses as $className => $x) {
+ unset($this->_propertyMapCache->classMaps[$className]);
+ unset($this->compiledClasses[$className]);
+ }
+
+ throw $e; // Re-throw. (Keeps its stack trace & line number.)
+ }
+ }
+
+ /**
+ * The real, internal compiler algorithm entry point.
+ *
+ * MUST be wrapped in another function which handles compilation failures!
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ *
+ * @see PropertyMapCompiler::compile() The wrapper entry point.
+ */
+ private function _compile()
+ {
+ // There's nothing to do if the class is already in the cache.
+ if (isset($this->_propertyMapCache->classMaps[$this->_solveClassName])) {
+ return; // Abort.
+ }
+
+ // Let's compile the desired "solve-class", since it wasn't cached.
+ // NOTE: This entire algorithm is EXCESSIVELY commented so that everyone
+ // will understand it, since it's very complex thanks to its support for
+ // inheritance and "multiple inheritance" (imports), which in turn uses
+ // advanced anti-circular dependency protection to detect bad imports.
+ // As well as its automatic end-of-run compilation of any encountered,
+ // current uncompiled classes from any properties in the tree.
+
+ // Prepare the class hierarchy for compilation.
+ $this->_reflectSolveClassHierarchy(); // Throws.
+ $this->_checkCurrentLocks(); // Throws.
+ $this->_lockUncompiledClasses();
+
+ // Traverse the class hierarchy and compile and merge their property
+ // maps, giving precedence to later classes in case of name clashes.
+ //
+ // And because we build the list top-down through the chain of class
+ // inheritance (starting at base and going to the deepest child), and
+ // thanks to the fact that PHP classes can only extend from 1 parent
+ // class, it also means that we're actually able to save the per-class
+ // lists at every step of the way, for each class we encounter along the
+ // way. To avoid needing to process those later.
+ //
+ // This builds a properly inherited class property map and constructs
+ // and validates all property definitions so that we don't get any nasty
+ // surprises during data-parsing later.
+ //
+ // The top-down order also ensures that we re-use our parent's inherited
+ // PropertyDefinition objects, so that the compiled classes all share
+ // memory whenever they inherit from the same parent classes & imports.
+ try {
+ foreach ($this->_classHierarchy as $classInfo) {
+ $this->_currentClassInfo = $classInfo;
+ $this->_processCurrentClass(); // Throws.
+ }
+ } finally {
+ // IMPORTANT: If we've compiled all classes, or if uncaught
+ // exceptions were thrown during processing, then we must simply
+ // ensure that we unlock all of OUR remaining locks before we allow
+ // the exception to keep bubbling upwards OR processing to continue.
+ // NOTE: We AREN'T allowed to unlock classes we didn't lock.
+ foreach ($this->_ourCompilerClassLocks as $lockedClassName => $x) {
+ unset($this->_ourCompilerClassLocks[$lockedClassName]);
+ unset($this->_propertyMapCache->compilerLocks[$lockedClassName]);
+ }
+ }
+
+ // If we've reached this point, it means that our solve-class SHOULD be
+ // in the cache since its entire compilation ran successfully above.
+ // Just verify it to be extra safe against any random future mistakes.
+ // NOTE: This step should never go wrong.
+ if (!isset($this->_propertyMapCache->classMaps[$this->_solveClassName])) {
+ throw new BadPropertyMapException(sprintf(
+ 'Error while compiling class "%s". Could not find the class in the cache afterwards.',
+ $this->_strictSolveClassName
+ ));
+ }
+
+ // If we came this far, it means that NOTHING above threw, which means
+ // that our class and its hierarchy of inheritance (and map-imports), as
+ // well as all PropertyDefinitions, have succeeded for the whole tree...
+ //
+ // Now the only remaining question is whether it's safe for us to check
+ // the list of encountered classes in properties and ensure that those
+ // can all be compiled too... And the answer is NO, unless we are the
+ // absolute ROOT CALL which began the whole compilation process. If not,
+ // then we should merely finish cleaning up our list of encountered
+ // "uncompiled" classes and then let our parent-caller deal with it.
+
+ // If we are a subcall (sub-compilation within a main compilation),
+ // simply let our parent compiler bubble our data to the root call...
+ if (!$this->_isRootCompiler) {
+ // Some already-processed classes may have slipped onto the list due
+ // to being encountered as properties before those classes became
+ // compiled. So before returning to the parent, we'll just ensure
+ // that we remove all keys that refer to classes that have already
+ // been compiled. That saves RAM and processing power during the
+ // array merging in the parent. Basically, we want this list to
+ // always be as short as possible so there's less need for any HUGE
+ // array manipulation at a higher stage while it's bubbling up.
+ foreach ($this->uncompiledPropertyClasses as $uncompiledClassName => $x) {
+ if (isset($this->_propertyMapCache->classMaps[$uncompiledClassName])) {
+ unset($this->uncompiledPropertyClasses[$uncompiledClassName]);
+ }
+ }
+
+ return; // Skip the rest of the code.
+ }
+
+ // We're the root call! So it is now safe (and IMPORTANT!) to compile
+ // all of the not-yet-compiled classes from the inheritance tree, to
+ // ensure that EVERY part of the tree is ready as far as classmaps go.
+ // NOTE: We have no information about how deeply the encountered classes
+ // existed. But it doesn't matter, since they may exist in multiple
+ // places. If there's a problem we'll simply say that "the class (this
+ // initial root/non-subcall one) or one of its parents or imports has a
+ // property which is linked to bad class X". Especially since there may
+ // also be problems within properties of THESE classes that we're going
+ // to compile. So trying to pinpoint exactly which class had the bad
+ // reference to an uncompilable class is overkill. We'll just show the
+ // uncompilable class name plus its regular high-quality compilation
+ // error message which describes why that class failed to compile.
+ // The user should simply fix their code in THAT particular class.
+ while (!empty($this->uncompiledPropertyClasses)) {
+ // IMPORTANT: Create a COPY-ON-WRITE version of the current contents
+ // of the "uncompiledPropertyClasses" array. Because that array will
+ // be changing whenever we sub-compile, so we'll use a stable COPY.
+ $workQueue = $this->uncompiledPropertyClasses;
+
+ // Process all entries (classes to compile) in the current queue.
+ foreach ($workQueue as $uncompiledClassName => $x) {
+ // Skip this class if it's already been successfully compiled.
+ if (isset($this->_propertyMapCache->classMaps[$uncompiledClassName])) {
+ unset($this->uncompiledPropertyClasses[$uncompiledClassName]);
+ continue;
+ }
+
+ // Attempt to compile the missing class. We do this one by one
+ // in isolation, so that each extra class we compile gets its
+ // entirely own competition-free sub-compiler with its own
+ // class-locks (since our own top-level rootCompiler's hierarchy
+ // is already fully compiled by this point). Which means that
+ // the sub-classes are welcome to refer to anything we've
+ // already compiled, exactly as if each of these extra classes
+ // were new top-level compiles "running in isolation". The only
+ // difference is that they aren't marked as the root compiler,
+ // since we still want to be the one to resolve all of THEIR
+ // uncompiled property-classes here during THIS loop. Mainly so
+ // that we can be the one to throw exception messages, referring
+ // to the correct root-level class as the one that failed.
+ try {
+ // NOTE: If this subcompile is successful and encounters any
+ // more uncompiled property-classes within the classes it
+ // compiles, they'll be automatically added to OUR OWN list
+ // of "uncompiledPropertyClasses" and WILL be resolved too.
+ //
+ // This will carry on until ALL of the linked classes are
+ // compiled and no more work remains. In other words, we
+ // will resolve the COMPLETE hierarchies of every linked
+ // class and ALL of their properties too. However, this
+ // subcompilation is still very fast since most end-stage
+ // jobs refer to classes that are already mostly-compiled
+ // due to having most of their own dependencies already
+ // pre-compiled at that point.
+ $this->_subcompile($uncompiledClassName); // Throws.
+ } catch (\Exception $e) { // NOTE: Could target exact type, but meh.
+ // Failed to compile the class we discovered in a property.
+ // It can be due to all kinds of problems, such as circular
+ // property maps or bad imports or bad definitions or
+ // corrupt map variables or anything else. We'll wrap the
+ // error message in a slightly prefixed message just to hint
+ // that this problem is with a property. Because the
+ // compilation error message itself is already very clear
+ // about which class failed to compile and why.
+ // NOTE: This "prefixed" message handling is unlike parents
+ // and imports, where we simply let their original exception
+ // message bubble up as if they were part of the core class,
+ // since imports are a more "integral" part of a class (a
+ // class literally CANNOT be built without its parents and
+ // its imports compiling). But CLASSES IN PROPERTIES are
+ // different and are more numerous and are capable of being
+ // resolved by _getProperty() LATER without having been
+ // pre-compiled when the main class map itself was compiled
+ // (although we just DID that pre-compilation above; we'll
+ // NEVER let them wait to get resolved later).
+ // NOTE: This WILL cause us to lose the specific class-type
+ // of the exception, such as CircularPropertyMapException,
+ // etc. That's intentional, since THIS error is about a bad
+ // map in the PARENT-hierarchy (IT pointing at a bad class).
+ throw new BadPropertyMapException(sprintf(
+ 'Compilation of sub-property hierarchy failed for class "%s". Reason: %s',
+ $this->_strictSolveClassName, $e->getMessage()
+ ));
+ } // End of _subcompile() try-catch.
+
+ // The sub-compile was successful (nothing was thrown), which
+ // means that it's now in the compiled property map cache. Let's
+ // unset its entry from our list of uncompiled classes.
+ unset($this->uncompiledPropertyClasses[$uncompiledClassName]);
+ } // End of work-queue loop.
+ } // End of "handle uncompiled property classes" loop.
+ }
+
+ /**
+ * Reflect all classes in the solve-class hierarchy.
+ *
+ * Builds a list of all classes in the inheritance chain, with the
+ * base-level class as the first element, and the solve-class as the
+ * last element. (The order is important!)
+ *
+ * @throws BadPropertyMapException
+ */
+ private function _reflectSolveClassHierarchy()
+ {
+ if (!empty($this->_classHierarchy)) {
+ throw new BadPropertyMapException('Detected multiple calls to _reflectSolveClassHierarchy().');
+ }
+
+ try {
+ // Begin reflecting the "solve-class". And completely refuse to
+ // proceed if the class name we were asked to solve doesn't match
+ // its EXACT real name. It's just a nice bonus check for safety,
+ // to ensure that our caller will be able to find their expected
+ // result in the correct cache key later.
+ $reflector = new ReflectionClass($this->_strictSolveClassName);
+ if ($this->_solveClassName !== $reflector->getName()) {
+ throw new BadPropertyMapException(sprintf(
+ 'Unable to compile class "%s" due to mismatched class name parameter value (the real class name is: "%s").',
+ $this->_strictSolveClassName, Utilities::createStrictClassPath($reflector->getName())
+ ));
+ }
+
+ // Now resolve all classes in its inheritance ("extends") chain.
+ do {
+ // Store the class in the hierarchy.
+ // NOTE: The key is VERY important because it's used later when
+ // checking if a specific class exists in the current hierarchy.
+ $this->_classHierarchy[$reflector->getName()] = [
+ 'reflector' => $reflector,
+ 'namespace' => $reflector->getNamespaceName(),
+ 'className' => $reflector->getName(), // Includes namespace.
+ 'strictClassName' => Utilities::createStrictClassPath($reflector->getName()),
+ ];
+
+ // Update the reflector variable to point at the next parent
+ // class in its hierarchy (or false if no more exists).
+ $reflector = $reflector->getParentClass();
+ } while ($reflector !== false);
+
+ // Reverse the list to fix the order, since we built it "bottom-up".
+ $this->_classHierarchy = array_reverse($this->_classHierarchy, true);
+ } catch (ReflectionException $e) {
+ // This should only be able to fail if the classname was invalid.
+ throw new BadPropertyMapException(sprintf(
+ 'Reflection of class hierarchy failed for class "%s". Reason: "%s".',
+ $this->_strictSolveClassName, $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Check for already-locked classes in the solve-class hierarchy.
+ *
+ * Analyzes the solve-class hierarchy and verifies that we don't violate
+ * any current anti-circular map locks.
+ *
+ * @throws CircularPropertyMapException
+ */
+ private function _checkCurrentLocks()
+ {
+ foreach ($this->_classHierarchy as $classInfo) {
+ // FATAL: If any part of our class hierarchy is already locked ("is
+ // being compiled right now", higher up the call stack), then it's a
+ // bad map with circular "import"-statements.
+ // NOTE: This specifically protects against the dangerous scenario
+ // of importing classes derived from our hierarchy but whose
+ // classname isn't in the current class hierarchy, so they weren't
+ // blocked by the "import-target sanity checks". So when they get
+ // to their own sub-compilation, their class hierarchy is checked
+ // here and we'll discover their circular inheritance.
+ if (isset($this->_propertyMapCache->compilerLocks[$classInfo['className']])) {
+ // NOTE: strictClassName goes in "arg1" because it's being
+ // compiled earlier than us, so we're definitely the "arg2".
+ throw new CircularPropertyMapException(
+ $classInfo['strictClassName'],
+ $this->_strictSolveClassName
+ );
+ }
+ }
+ }
+
+ /**
+ * Lock all uncompiled classes in the solve-class hierarchy.
+ *
+ * @throws BadPropertyMapException
+ */
+ private function _lockUncompiledClasses()
+ {
+ if (!empty($this->_ourCompilerClassLocks)) {
+ throw new BadPropertyMapException('Detected multiple calls to _lockUncompiledClasses().');
+ }
+
+ foreach ($this->_classHierarchy as $classInfo) {
+ // If the class isn't already compiled, we'll lock it so nothing
+ // else can refer to it. We'll lock every unresolved class, and then
+ // we'll be unlocking them one by one as we resolve them during THIS
+ // exact compile-call. No other sub-compilers are allowed to use
+ // them while we're resolving them!
+ if (!isset($this->_propertyMapCache->classMaps[$classInfo['className']])) {
+ // NOTE: We now know that NONE of these unresolved classes were
+ // in the lock-list before WE added them to it. That means that
+ // we can safely clear ANY of those added locks if there's a
+ // problem. But WE are NOT allowed to clear ANY OTHER LOCKS!
+ $this->_ourCompilerClassLocks[$classInfo['className']] = true;
+ $this->_propertyMapCache->compilerLocks[$classInfo['className']] = true;
+ }
+ }
+ }
+
+ /**
+ * Process the current class in the solve-class hierarchy.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _processCurrentClass()
+ {
+ // We must always begin by reflecting its "JSON_PROPERTY_MAP" class
+ // constant to determine if this particular class in the hierarchy
+ // re-defines its value (compared to inheriting it from its "extends"
+ // parent). This protects against accidentally re-parsing inherited
+ // constants, which would both waste time AND would be VERY dangerous,
+ // since that would re-interpret all inherited relative properties (ones
+ // pointing at classes relative to the class which ACTUALLY declared
+ // that constant) as if they were relative to the INHERITING class
+ // instead, which is VERY wrong (and could lead to classes being either
+ // mismapped or "not found"). Luckily the code below fully protects us
+ // against that scenario, by detecting if our value is "ours" or not.
+ try {
+ // Use different techniques based on what their PHP supports.
+ $foundConstant = false;
+ if (version_compare(PHP_VERSION, '7.1.0') >= 0) {
+ // In PHP 7.1.0 and higher, they've finally added "reflection
+ // constants" which allow us to get accurate extra information.
+ $reflectionConstant = $this->_currentClassInfo['reflector']
+ ->getReflectionConstant('JSON_PROPERTY_MAP');
+ if ($reflectionConstant !== false) {
+ $foundConstant = true;
+
+ // Just read its value. We don't have to care about its
+ // isPrivate() flags etc. It lets us read the value anyway.
+ $rawClassPropertyMap = $reflectionConstant->getValue();
+
+ // Use PHP7.1's ReflectionClassConstant's ability to tell us
+ // EXACTLY which class declared the current (inherited or
+ // new) value for the constant. If OUR class didn't declare
+ // it, then we know that it was inherited and should NOT be
+ // parsed again. But if we DID declare its value, then we
+ // know that we MUST parse it ("has different constant").
+ // NOTE: This method is 100% accurate even when re-declared
+ // to the exact same value as the inherited (parent) value!
+ $hasDifferentConstant = ($reflectionConstant
+ ->getDeclaringClass()->getName()
+ === $this->_currentClassInfo['className']);
+ }
+ } else {
+ // In older PHP versions, we're pretty limited... we don't get
+ // ANY extra information about the constants. We just get their
+ // values. And if we try to query a specific constant, we get
+ // FALSE if it doesn't exist (which is indistinguishable from it
+ // actually having FALSE values). So we MUST get an array of all
+ // constants to be able to check whether it TRULY exists or not.
+ $classConstants = $this->_currentClassInfo['reflector']
+ ->getConstants();
+ if (array_key_exists('JSON_PROPERTY_MAP', $classConstants)) {
+ $foundConstant = true;
+
+ // Read its value. Unfortunately old versions of PHP don't
+ // give us ANY information about which class declared it
+ // (meaning whether it was inherited or re-declared here).
+ $rawClassPropertyMap = $classConstants['JSON_PROPERTY_MAP'];
+
+ // MAGIC: This is the best we can do on old PHP versions...
+ // The "!==" ensures that this constant really differs from
+ // its parent. In case of arrays, they are only treated as
+ // identical if both arrays have the same key & value types
+ // and values in the exact same order and same count(). And
+ // it checks recursively!
+ //
+ // NOTE: This method is great, but it's not as accurate as
+ // the perfect PHP 7.1 method. It actually has a VERY TINY
+ // issue where it will fail to detect a new constant: If you
+ // manually re-declare a constant to EXACTLY the same value
+ // as what you would have inherited from your parent
+ // ("extends") hierarchy, then we won't be able to detect
+ // that you have a new value. Instead, you will inherit the
+ // compiled parent class map as if you hadn't declared any
+ // value of your own at all. In almost all cases, that won't
+ // matter, and will give you the same result. It only
+ // matters in a very tiny case which probably won't happen
+ // to anybody in real-world usage:
+ //
+ // namespace A { class A { "foo":"B[]" } class B {} }
+ // namespace Z { class Z extends \A\A { "foo":"B[]" } class B {} }
+ //
+ // In that situation, Z\Z would inherit the constant from
+ // A\A, which compiled the relative-class "foo" property as
+ // "\A\B". And when we look at Z's own constant, we would
+ // see a 100% identical JSON_PROPERTY_MAP constant compared
+ // to A\A, so we would assume that since all the array keys
+ // and values match exactly, its constant "was inherited".
+ // Therefore the "identical" constant of Z will not be
+ // parsed. And Z's foo will therefore also link to "\A\B",
+ // instead of "\Z\B".
+ //
+ // In reality, I doubt that ANY user would EVER have such a
+ // weird inheritance structure, with "extends" across
+ // namespaces and relative paths to classes that exist in
+ // both namespaces, etc. And the problem above would not be
+ // able to happen as long as their "Z\Z" map has declared
+ // ANY other value too (or even just changed the order of
+ // the declarations), so that we detect that their constant
+ // differs in "Z\Z". So 99.9999% of users will be safe.
+ //
+ // And no... we CAN'T simply always re-interpret it, because
+ // then we'd get a FAR more dangerous bug, which means that
+ // ALL relative properties would re-compile every time they
+ // are inherited, "as if they had been declared by us".
+ //
+ // So no, this method really is the BEST that PHP < 7.1 has.
+ $hasDifferentConstant = ($rawClassPropertyMap
+ !== $this->_previousMapConstantValue);
+
+ // Update the "previous constant value" since we will be the
+ // new "previous" value after this iteration.
+ $this->_previousMapConstantValue = $rawClassPropertyMap;
+ }
+ }
+ if (!$foundConstant) {
+ // The constant doesn't exist. Should never be able to happen
+ // since the class inherits from LazyJsonMapper.
+ throw new ReflectionException(
+ // NOTE: This exception message mimics PHP's own reflection
+ // error message style.
+ 'Constant JSON_PROPERTY_MAP does not exist'
+ );
+ }
+ } catch (ReflectionException $e) {
+ // Unable to read the map constant from the class...
+ throw new BadPropertyMapException(sprintf(
+ 'Reflection of JSON_PROPERTY_MAP constant failed for class "%s". Reason: "%s".',
+ $this->_currentClassInfo['strictClassName'], $e->getMessage()
+ ));
+ }
+
+ // If we've already parsed that class before, it means that we've
+ // already fully parsed it AND its parents. If so, just use the cache.
+ if (isset($this->_propertyMapCache->classMaps[$this->_currentClassInfo['className']])) {
+ // Overwrite our whole "_currentClassPropertyMap" with the one from
+ // our parent instead. That's faster than merging the arrays, and
+ // guarantees PERFECT PropertyDefinition instance re-usage.
+ // NOTE: To explain it, imagine class B extending from A. A has
+ // already been parsed and cached. We then instantiate B for the
+ // first time and we need to build B's definitions. By copying the
+ // cache from A (its parent), we'll re-use all of A's finished
+ // PropertyDefinition objects in B and throughout any other chain
+ // that derives from the same base object (A). This holds true for
+ // ALL objects that inherit (or "import") ANYTHING (such as
+ // something that later refers to "B"), since we ALWAYS resolve the
+ // chains top-down from the base class to the deepest child class,
+ // and therefore cache the base (hierarchically shared) definitions
+ // FIRST.
+ $this->_currentClassPropertyMap = $this->_propertyMapCache->classMaps[
+ $this->_currentClassInfo['className']
+ ];
+ } else {
+ // We have not parsed/encountered this class before...
+
+ // Only parse its class constant if it differs from its inherited
+ // parent's constant value. Otherwise we'll keep the parent's map
+ // (the value that's currently in "_currentClassPropertyMap").
+ if ($hasDifferentConstant) {
+ // Process and validate the JSON_PROPERTY_MAP of the class.
+ if (!is_array($rawClassPropertyMap)) {
+ throw new BadPropertyMapException(sprintf(
+ 'Invalid JSON property map in class "%s". The map must be an array.',
+ $this->_currentClassInfo['strictClassName']
+ ));
+ }
+ foreach ($rawClassPropertyMap as $propName => $propDefStr) {
+ // Process the current entry and add it to the current
+ // class' compiled property map if the entry is new/diff.
+ $this->_processPropertyMapEntry( // Throws.
+ $propName,
+ $propDefStr
+ );
+ }
+ }
+
+ // Mark the fact that we have compiled this class, so that we'll be
+ // able to know which classes we have compiled later. This list will
+ // bubble through the compile-call-hierarchy as-needed to keep track
+ // of EVERY compiled class during the current root compile()-run.
+ $this->compiledClasses[$this->_currentClassInfo['className']] = true;
+
+ // Now cache the final property-map for this particular class in the
+ // inheritance chain... Note that if it had no new/different map
+ // declarations of its own (despite having a different constant),
+ // then we're still simply re-using the exact property-map objects
+ // of its parent class here again.
+ $this->_propertyMapCache->classMaps[
+ $this->_currentClassInfo['className']
+ ] = $this->_currentClassPropertyMap;
+ } // End of class-map "lookup-or-compilation".
+
+ // IMPORTANT: We've finished processing a class. If this was one of the
+ // classes that WE locked, we MUST NOW unlock it, but ONLY if we
+ // successfully processed the class (added it to the cache). Otherwise
+ // we'll unlock it at the end (where we unlock any stragglers) instead.
+ // NOTE: We AREN'T allowed to unlock classes we didn't lock.
+ // NOTE: Unlocking is IMPORTANT, since it's necessary for being able to
+ // import classes from diverging branches with shared inheritance
+ // ancestors. If we don't release the parent-classes one by one as we
+ // resolve them, then we would never be able to import classes from
+ // other branches of our parent/class hierarchy, since the imports would
+ // see that one of their parents (from our hierarchy) is still locked
+ // and would refuse to compile themselves.
+ if (isset($this->_ourCompilerClassLocks[$this->_currentClassInfo['className']])) {
+ unset($this->_ourCompilerClassLocks[$this->_currentClassInfo['className']]);
+ unset($this->_propertyMapCache->compilerLocks[$this->_currentClassInfo['className']]);
+ }
+ }
+
+ /**
+ * Process a property map entry for the current class.
+ *
+ * Validates and compiles a map entry, and merges it with the current class
+ * property map if everything went okay and the entry was new/different.
+ *
+ * @param string|int $propName The property name, or a numeric array key.
+ * @param mixed $propDefStr Should be a string describing the property
+ * or the class to import, but may be
+ * something else if the user has written an
+ * invalid class property map.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _processPropertyMapEntry(
+ $propName,
+ $propDefStr)
+ {
+ if (is_string($propName) && is_string($propDefStr)) {
+ // This is a string key -> string value pair, so let's attempt to
+ // compile it as a regular property definition and then add it to
+ // our current class map if the entry is new/different.
+ $this->_processPropertyDefinitionString( // Throws.
+ $propName,
+ $propDefStr
+ );
+ } else {
+ // It cannot be a regular property definition. Check if this is an
+ // "import class map" command. They can exist in the map and tell us
+ // to import other classes, and their instructions are written as
+ // [OtherClass::class, 'ownfield'=>'string'].
+ $isImportCommand = false;
+ if (is_int($propName) && is_string($propDefStr)) {
+ // This is an int key -> string value pair, so we should treat
+ // it as a potential "import class map" command. We must first
+ // ensure that the class has a strictly global "\" prefix.
+ $strictImportClassName = Utilities::createStrictClassPath($propDefStr);
+
+ // Now check if the target class fits the "import class"
+ // requirements.
+ if (class_exists($strictImportClassName)
+ && is_subclass_of($strictImportClassName, '\\'.LazyJsonMapper::class)) {
+ // This is an "import other class" command! Thanks to the
+ // lack of an associative key, we saw a numeric array key
+ // (non-associative). And we've verified (above) that its
+ // value points to another valid class which is a sub-class
+ // of LazyJsonMapper (we don't bother allowing to import
+ // LazyJsonMapper itself, since it is the lowest possible
+ // base class and has 0 values).
+ $isImportCommand = true;
+
+ // Perform the import, which will compile the target (if
+ // necessary) and then merge its map with our current map.
+ // NOTE: There is no need for us to prevent the user from
+ // doing multiple imports of the same class, because the way
+ // the importing works ensures that we always re-use the
+ // same PropertyDefinition object instances from the other
+ // class every time we import the other class.
+ $this->_importClassMap( // Throws.
+ $strictImportClassName
+ );
+ }
+ }
+ if (!$isImportCommand) {
+ // This map-array value is definitely NOT okay.
+ throw new BadPropertyMapException(sprintf(
+ 'Invalid JSON property map entry "%s" in class "%s".',
+ $propName, $this->_currentClassInfo['strictClassName']
+ ));
+ }
+ }
+ }
+
+ /**
+ * Compile a property definition string and add it to the current class.
+ *
+ * Attempts to compile the definition, and then appends it to the current
+ * class' final compiled map, IF the definition is valid and describes a
+ * new/different value compared to what's already in the current class map.
+ *
+ * @param string $propName The property name.
+ * @param string $propDefStr A string describing the property.
+ *
+ * @throws BadPropertyDefinitionException
+ */
+ private function _processPropertyDefinitionString(
+ $propName,
+ $propDefStr)
+ {
+ try {
+ // Validates the definition and throws if bad.
+ // NOTE: The namespace argument here is INCREDIBLY important. It's
+ // what allows each class to refer to classes relative to its own
+ // namespace, rather than needing to type the full, global class
+ // path. Without that parameter, the PropertyDefinition would only
+ // search in the global namespace.
+ // NOTE: "use" statements are ignored since PHP has no mechanism
+ // for inspecting those. But the user will quickly notice such a
+ // problem, since such classnames will warn as "not found" here.
+ $propDefObj = new PropertyDefinition( // Throws.
+ $propDefStr,
+ $this->_currentClassInfo['namespace']
+ );
+
+ // MEMORY OPTIMIZATION TRICK: If we wanted to be "naive", we could
+ // simply assign this new PropertyDefinition directly to our current
+ // class property map, regardless of whether the property-name key
+ // already exists. It would certainly give us the "right" result...
+ //
+ // But imagine if one of our parent objects or previous imports have
+ // already created a property as "foo":"string[]", and imagine that
+ // we ALSO define a "foo":"string[]", even though we've already
+ // inherited that EXACT compiled instruction. What would happen?
+ //
+ // Well, if we instantly assign our own, newly created object, even
+ // though it's equal to an identically named and identically defined
+ // property that we've already inherited/imported, then we waste RAM
+ // since we've now got two INDEPENDENT "compiled property" object
+ // instances that describe the EXACT same property-settings.
+ //
+ // Therefore, we can save RAM by first checking if the property name
+ // already exists in our currently compiled map; and if so, whether
+ // its pre-existing PropertyDefinition already describes the EXACT
+ // same settings. If so, we can keep the existing object (which we
+ // KNOW is owned by a parent/import and will therefore always remain
+ // in memory). Thus saving us the RAM-size of a PropertyDefinition
+ // object. In fact, the re-use means the RAM is the same as if the
+ // sub-class hadn't even overwritten the inherited/imported property
+ // AT ALL! So this protective algorithm makes class JSON property
+ // re-definitions to identical settings a ZERO-cost act.
+ //
+ // IMPORTANT: This only works because compiled properties are
+ // immutable, meaning we can trust that the borrowed object will
+ // remain the same. We are NEVER going to allow any runtime
+ // modifications of the compiled maps. So re-use is totally ok.
+ if (!isset($this->_currentClassPropertyMap[$propName])
+ || !$propDefObj->equals($this->_currentClassPropertyMap[$propName])) {
+ // Add the unique (new/different) property to our class map.
+ $this->_currentClassPropertyMap[$propName] = $propDefObj;
+
+ // Alright, we've encountered a brand new property, and we know
+ // it's pointing at a LazyJsonMapper if it's an object. But we
+ // DON'T know if the TARGET class map can actually COMPILE. We
+ // therefore need to add the class to the list of encountered
+ // property classes. But only if we haven't already compiled it.
+ if ($propDefObj->isObjectType
+ && !isset($this->_propertyMapCache->classMaps[$propDefObj->propType])) {
+ // NOTE: During early compilations, at the startup of a PHP
+ // runtime, this will pretty much always add classes it sees
+ // since NOTHING is compiled while the first class is being
+ // compiled. Which means that this list will contain classes
+ // that are currently BEING SOLVED as the class hierarchy
+ // keeps resolving/compiling itself. But we'll take care of
+ // that by double-checking the list later before we're done.
+ // TODO: PERHAPS we can safely optimize this HERE by also
+ // checking for the target class in _propertyMapCache's list
+ // of locked classes... But planning would be necessary.
+ $this->uncompiledPropertyClasses[$propDefObj->propType] = true;
+ }
+ }
+ } catch (BadPropertyDefinitionException $e) {
+ // Add details and throw from here instead.
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Bad property definition for "%s" in class "%s" (Error: "%s").',
+ $propName, $this->_currentClassInfo['strictClassName'], $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Import another class map into the current class.
+ *
+ * @param string $strictImportClassName The strict, global path to the
+ * class, with leading backslash `\`.
+ * To save time, we assume the caller
+ * has already verified that it is a
+ * valid `LazyJsonMapper` class.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _importClassMap(
+ $strictImportClassName)
+ {
+ // Begin via reflection to resolve the EXACT name of the class they want
+ // to import, so that we can trust its name completely.
+ try {
+ // NOTE: The strict, global "\"-name is necessary here, to guarantee
+ // that we find the correct target class via the global namespace.
+ $reflector = new ReflectionClass($strictImportClassName);
+ $importClassName = $reflector->getName(); // The clean, real name.
+ $strictImportClassName = Utilities::createStrictClassPath($importClassName);
+ } catch (ReflectionException $e) {
+ // This should never be able to fail (since our caller already
+ // verified that the class exists), but treat failure as a bad map.
+ throw new BadPropertyMapException(sprintf(
+ 'Reflection failed for class "%s", when trying to import it into class "%s". Reason: "%s".',
+ $strictImportClassName, $this->_currentClassInfo['strictClassName'], $e->getMessage()
+ ));
+ }
+
+ // FATAL: If the encountered import-statement literally refers to itself
+ // (the class we're currently compiling in the hierarchy), then it's a
+ // totally ridiculous circular self-reference of the most insane kind.
+ if ($importClassName === $this->_currentClassInfo['className']) {
+ throw new CircularPropertyMapException(
+ $strictImportClassName,
+ $strictImportClassName
+ );
+ }
+
+ // FATAL: If the import-statement refers to ANY class in OUR OWN
+ // current class' hierarchy, then it's a circular self-reference. We
+ // forbid those because they make ZERO sense. For example: "ABCD", in
+ // that case, "D" COULD definitely import "B" because "D" is later in
+ // the chain and "B" is therefore already resolved. But WHY? We already
+ // inherit from it! And we could NEVER do the reverse; "B" could never
+ // import "D", because "D" depends on "B" being fully compiled already.
+ // Likewise, we can't allow stupid things like "B imports B". Doesn't
+ // make sense. Therefore, we disallow ALL self-imports.
+ //
+ // NOTE: In the example of "B" importing "D", that's NOT caught here
+ // since the classname "D" wouldn't be part of B's hierarchy. But it
+ // will be caught during our upcoming attempt to sub-compile "D" to
+ // parse ITS hierarchy, since "D" will begin to compile and check its
+ // own class hierarchy, and will arrive at the "B" class and will then
+ // detect that "B" is locked as "already being resolved", since the
+ // ongoing resolving of "B" was what triggered the compilation of "D".
+ if (isset($this->_classHierarchy[$importClassName])) {
+ // NOTE: strictSolveClassName goes in "arg1" because we're the ones
+ // trying to import an invalid target class that's already part of
+ // our own hierarchy.
+ throw new CircularPropertyMapException(
+ $this->_strictSolveClassName,
+ $strictImportClassName
+ );
+ }
+
+ // FATAL: Also prevent users from importing any class that's in the list
+ // of "locked and currently being resolved", since that means that the
+ // class they're trying to import is ITSELF part of a compilation-chain
+ // that's CURRENTLY trying to resolve itself right now.
+ //
+ // NOTE: This catches scenarios where class-chains have common ancestors
+ // but then diverge and go into a circular reference to each other. For
+ // example, "A extends LazyJsonMapper, imports B", "B extends
+ // LazyJsonMapper, imports A". Imagine that you construct "A", which
+ // locks "A" during its compilation process. The parent "LazyJsonMapper"
+ // class is successfully compiled since it has no dependencies. Then "A"
+ // sees "import B" and sub-compiles B. (Remember that "A" remains locked
+ // when that happens, since "A" is waiting for "B" to resolve itself.)
+ // The compilation code of "B" begins running. It first checks its
+ // inheritance hierarchy and sees nothing locked (LazyJsonMapper is
+ // unlocked because it's already done, and B is unlocked because B has
+ // not locked itself yet). Next, "B" sees an "import A" statement. It
+ // succeeds the "block import of A if it's part of our own hierarchy"
+ // check above, since B is NOT "extended from A".
+ //
+ // So then we're at a crossroads... there IS a circular reference, but
+ // we don't know it yet. We COULD let it proceed to "resolve A by
+ // sub-compiling the map for A", which would give us a call-stack of
+ // "compile A -> compile B -> compile A", but then the 2nd "A" compiler
+ // would run, and would do its early lock-check and notice that itself
+ // (A) is locked, since the initial compilation of "A" is not yet done.
+ // It would then throw a non-sensical error saying that there's a
+ // circular reference between "A and A", which is technically true, but
+ // makes no sense. That's because we waited too long!
+ //
+ // So, what the code BELOW does instead, is that when "B" is trying to
+ // resolve itself (during the "compile A -> import B -> compile B"
+ // phase), the "B" compiler will come across its "import A" statement,
+ // and will now detect that "A" is already locked, and therefore throws
+ // a proper exception now instead of letting "A" attempt to compile
+ // itself a second time as described above. The result of this early
+ // sanity-check is a better exception message (correct class names).
+ foreach ($this->_propertyMapCache->compilerLocks as $lockedClassName => $isLocked) {
+ if ($isLocked && $lockedClassName === $importClassName) {
+ // NOTE: strictSolveClassName goes in "arg2" because we're
+ // trying to import something unresolved that's therefore higher
+ // than us in the call-stack.
+ throw new CircularPropertyMapException(
+ $strictImportClassName,
+ $this->_strictSolveClassName
+ );
+ }
+ }
+
+ // Now check if the target class is missing from our cache, and if so
+ // then we must attempt to compile (and cache) the map of the target
+ // class that we have been told to import.
+ //
+ // NOTE: This sub-compilation will perform the necessary recursive
+ // compilation and validation of the import's WHOLE map hierarchy. When
+ // the compiler runs, it will ensure that nothing in the target class
+ // hierarchy is currently locked (that would be a circular reference),
+ // and then it will lock its own hierarchy too until it has resolved
+ // itself. This will go on recursively if necessary, until ALL parents
+ // and ALL further imports have been resolved. And if there are ANY
+ // circular reference ANYWHERE, or ANY other problems with its
+ // map/definitions, then the sub-compilation(s) will throw appropriate
+ // exceptions.
+ //
+ // We won't catch them; we'll just let them bubble up to abort the
+ // entire chain of compilation, since a single error anywhere in the
+ // whole chain means that whatever root-level compile-call initially
+ // began this compilation process _cannot_ be resolved either. So we'll
+ // let the error bubble up all the way to the original call.
+ if (!isset($this->_propertyMapCache->classMaps[$importClassName])) {
+ // Compile the target class to import.
+ // NOTE: We will not catch the error. So if the import fails to
+ // compile, it'll simply output its own message saying that
+ // something was wrong in THAT class. We don't bother including any
+ // info about our class (the one which imported it). That's still
+ // very clear in this case, since the top-level class compiler and
+ // its hierarchy will be in the stack trace as well as always having
+ // a clearly visible import-command in their class property map.
+ $this->_subcompile($importClassName); // Throws.
+ }
+
+ // Now simply loop through the compiled property map of the imported
+ // class... and add every property to our own compiled property map. In
+ // case of clashes, we use the imported one.
+ // NOTE: We'll directly assign its PD objects as-is, which will assign
+ // the objects by shallow "shared instance", so that we re-use the
+ // PropertyDefinition object instances from the compiled class that
+ // we're importing. That will save memory.
+ // NOTE: There's no point doing an equals() clash-check here, since
+ // we're importing an EXISTING, necessary definition from another class,
+ // so we won't save any memory by avoiding their version even if the
+ // definitions are equal.
+ foreach ($this->_propertyMapCache->classMaps[$importClassName] as $importedPropName => $importedPropDefObj) {
+ $this->_currentClassPropertyMap[$importedPropName] = $importedPropDefObj;
+ }
+ }
+
+ /**
+ * Used internally when this compiler needs to run a sub-compilation.
+ *
+ * Performs the sub-compile. And if it was successful (meaning it had no
+ * auto-rollbacks due to ITS `compile()` call failing), then we'll merge its
+ * compiler state with our own so that we preserve important state details.
+ *
+ * @param string $className The full path of the class to sub-compile, but
+ * without any leading `\` global prefix. To save
+ * time, we assume the caller has already verified
+ * that it is a valid `LazyJsonMapper` class.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _subcompile(
+ $className)
+ {
+ // Sub-compile the target class. If this DOESN'T throw, we know that the
+ // target class successfully exists in the compilation cache afterwards.
+ // NOTE: If it throws, we know that IT has already rolled back all its
+ // changes itself via its own compile()-catch, so WE don't have to worry
+ // about reading its state on error. We ONLY care when it succeeds.
+ $subCompiler = new self( // Throws.
+ false, // Is NOT the root call!
+ $this->_propertyMapCache, // Use the same cache to share locks.
+ $className
+ );
+ $subCompiler->compile(); // Throws.
+
+ // Add its state of successfully compiled classes to our own list of
+ // compiled classes, so that the info is preserved throughout the stack.
+ // NOTE: This is EXTREMELY important, so that we can clear those classes
+ // from the cache in case ANY other step of the compilation chain fails
+ // unexpectedly. Because in that case, we'll NEED to be able to roll
+ // back ALL of our entire compile-chain's property map cache changes!
+ // NOTE: The merge function de-duplicates keys!
+ $this->compiledClasses = array_merge(
+ $this->compiledClasses,
+ $subCompiler->compiledClasses
+ );
+
+ // Add the sub-compiled class hierarchy's own encountered property-
+ // classes to our list of encountered classes. It gives us everything
+ // from their extends-hierarchy and everything from their own imports.
+ // And in case they had multi-level chained imports (classes that import
+ // classes that import classes), it'll actually be containing a FULLY
+ // recursively resolved and merged sub-import list. We will get them ALL
+ // from their WHOLE sub-hierarchy! Exactly as intended.
+ // NOTE: The merge function de-duplicates keys!
+ $this->uncompiledPropertyClasses = array_merge(
+ $this->uncompiledPropertyClasses,
+ $subCompiler->uncompiledPropertyClasses
+ );
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/UndefinedProperty.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/UndefinedProperty.php
new file mode 100755
index 0000000..5ff159c
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/UndefinedProperty.php
@@ -0,0 +1,79 @@
+arrayDepth`.
+ * @param string $propName The name of the property. For
+ * exception messages.
+ * @param PropertyDefinition $propDef An object describing the property.
+ *
+ * @throws LazyJsonMapperException If the value can't be turned into its
+ * assigned class or built-in PHP type.
+ */
+ public static function convert(
+ $direction,
+ &$value,
+ $remArrayDepth,
+ $propName,
+ PropertyDefinition $propDef)
+ {
+ // Do nothing if this particular value is NULL.
+ if ($value === null) {
+ return; // Skip the rest of the code.
+ }
+
+ // ------------
+ // TODO: Improve all error messages below to make them more
+ // human-readable. Perhaps even display $propDef->asString() as a great
+ // hint about what type the property requires, even indicating what array
+ // depth it must be at. However, the current messages about problems at
+ // "at array-depth X of Y" should be kept, since that level of detail is
+ // very helpful for figuring out which part of the array is bad.
+ // ------------
+
+ // Handle "arrays of [type]" by recursively processing all layers down
+ // until the array-depth, and verifying all keys and values.
+ // NOTE: If array depth remains, we ONLY allow arrays (or NULLs above),
+ // and ALL of the arrays until the depth MUST be numerically indexed in
+ // a sequential order starting from element 0, without any gaps. Because
+ // the ONLY way that a value can be a true JSON ARRAY is if the keys are
+ // numeric and sequential. Otherwise the "array" we are looking at was
+ // originally a JSON OBJECT in the original, pre-json_decode()-d string.
+ // NOTE: We even support "arrays of mixed", and in that case will verify
+ // that the mixed data is at the expected depth and has key integrity.
+ // So specifying "mixed[]" requires data like "[1,null,true]", whereas
+ // specifying "mixed" avoids doing any depth validation.
+ if ($remArrayDepth > 0) {
+ if (!is_array($value)) {
+ if ($direction === self::CONVERT_FROM_INTERNAL) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected non-array value for array-property "%s" at array-depth %d of %d.',
+ $propName, $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ } else {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to assign new non-array value for array-property "%s" at array-depth %d of %d.',
+ $propName, $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ }
+ }
+
+ // Subtract 1 from the remaining array-depth and process current layer.
+ $newRemArrayDepth = $remArrayDepth - 1;
+ $nextValidKey = 0; // Must start at 0.
+ foreach ($value as $k => &$v) { // IMPORTANT: By reference!
+ // OPTIMIZATION: We MUST only allow sequential int keys, but we
+ // avoid the is_int() call by using an int counter instead:
+ if ($k !== $nextValidKey++) { // ++ post increment...
+ // We're in an "array of"-typed JSON property structure, but
+ // encountered either an associative key or a gap in the
+ // normal numeric key sequence. The JSON data is invalid!
+ // Display the appropriate error.
+ if (is_int($k)) {
+ // It is numeric, so there was a gap...
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected out-of-sequence numeric array key (expected: %s, found: %s) for array-property "%s" at array-depth %d of %d.',
+ $nextValidKey - 1, $k, $propName,
+ $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ } else {
+ // Invalid associative key in a numeric array.
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected non-numeric array key ("%s") for array-property "%s" at array-depth %d of %d.',
+ $k, $propName, $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ }
+ }
+
+ // The key was valid, so convert() any sub-values within this
+ // value. Its next depth is either 0 (values) or 1+ (more arrays).
+ if ($v !== null) { // OPTIMIZATION: Avoid useless call if null.
+ if ($direction === self::CONVERT_FROM_INTERNAL) {
+ self::convert($direction, $v, $newRemArrayDepth, $propName, $propDef);
+ } else {
+ self::convert($direction, $v, $newRemArrayDepth, $propName, $propDef);
+ }
+ }
+ }
+
+ // Skip rest of the code, since array depth remained in this call.
+ return;
+ } // End of "remaining array depth" handler.
+
+ // Alright, we now know that we're at the "data depth". However, the
+ // property itself may be untyped ("mixed"). Handle that first.
+ if ($propDef->propType === null) {
+ // This is a non-NULL but "untyped" property, which means that we
+ // only accept three things: NULL, Scalars (int, float, string,
+ // bool) and arrays of those. We will NOT allow any Objects or
+ // external Resources. And arrays at this level will only be
+ // allowed if the "mixed" type didn't specify any depth.
+
+ // We've already checked NULL earlier. Check the most common data
+ // types now. Almost all untyped values will be one of these,
+ // meaning even untyped properties are blazingly fast to process.
+ if (is_scalar($value)) { // int, float, string, bool
+ return; // Scalar accepted. Skip the rest of the code.
+ }
+
+ // Alright... then it's either an array, Object or Resource.
+ try {
+ if (is_array($value)) {
+ // Forbid arrays at this "value-level" IF and only if this
+ // was a "mixed[]" notation untyped property, since the "[]"
+ // specifies the maximum mixed data depth in that case.
+ // ------------------------------------------
+ // TODO: We do not have any PropertyDefinition notation
+ // for specifying "mixed non-array property". Perhaps add
+ // that feature someday, maybe "mixed[-]", since "mixed[]"
+ // is already taken by "1-level deep mixed" and "mixed" is
+ // already taken by "do not check depth of this mixed data".
+ // It would be nice to be able to say that a mixed property
+ // "can contain any basic type but not arrays".
+ // A simple implementation would be that arrayDepth "-1"
+ // for mixed denotes "do not check array depth" ("mixed")
+ // and "0" denotes "check array depth" ("mixed[-]").
+ // Either way, the syntax also needs to be valid PHPdoc so
+ // that the automatic property signatures are valid, so we
+ // actually cannot use "mixed[-]". Perhaps I'm overthinking
+ // all of this, though. Because if the user cares about the
+ // depth of untyped (mixed) properties, they should know
+ // enough to just strongly type-assign something instead.
+ // Although, perhaps this IS a job for PropertyDefinition:
+ // If the user specifies "mixed[-]" we can store it as
+ // untyped with arrayDepth -1, but output it as "mixed" when
+ // converting that PropertyDefinition to signature hints.
+ // ------------------------------------------
+ if ($propDef->arrayDepth > 0) {
+ // This "mixed" type specifies a max-depth, which means
+ // that we've reached it. We cannot allow more arrays.
+ throw new LazyJsonMapperException(sprintf(
+ // Let's try to be REALLY clear so the user understands...
+ // Since I anticipate lots of untyped user properties.
+ '%s non-array inner untyped property of "%s". This untyped property specifies a maximum array depth of %d.',
+ ($direction === self::CONVERT_FROM_INTERNAL
+ ? 'Unexpected inner array value in'
+ : 'Unable to assign new inner array value for'),
+ $propName, $propDef->arrayDepth
+ ));
+ }
+
+ // This mixed property has no max depth. Just verify the
+ // contents recursively to ensure it has no invalid data.
+ array_walk_recursive($value, function ($v) {
+ // NOTE: Mixed properties without max-depth can be
+ // either JSON objects or JSON arrays, and we don't know
+ // which, so we cannot verify their array key-type. If
+ // people want validation of keys, they should set a max
+ // depth for their mixed property OR switch to typed.
+ if ($v !== null && !is_scalar($v)) {
+ // Found bad (non-NULL, non-scalar) inner value.
+ throw new LazyJsonMapperException('bad_inner_type');
+ }
+ });
+ } else {
+ // Their value is an Object or Resource.
+ throw new LazyJsonMapperException('bad_inner_type');
+ }
+ } catch (LazyJsonMapperException $e) {
+ // Automatically select appropriate exception message.
+ if ($e->getMessage() !== 'bad_inner_type') {
+ throw $e; // Re-throw since it already had a message.
+ }
+
+ throw new LazyJsonMapperException(sprintf(
+ // Let's try to be REALLY clear so the user understands...
+ // Since I anticipate lots of untyped user properties.
+ '%s untyped property "%s". Untyped properties can only contain NULL or scalar values (int, float, string, bool), or arrays holding any mixture of those types.',
+ ($direction === self::CONVERT_FROM_INTERNAL
+ ? 'Unexpected value in'
+ : 'Unable to assign invalid new value for'),
+ $propName
+ ));
+ }
+
+ // If we've come this far, their untyped property contained a valid
+ // array with only NULL/scalars (or nothing at all) inside. Done!
+ return; // Skip the rest of the code.
+ }
+
+ // We need the strict classpath for all object comparisons, to ensure
+ // that we always compare against the right class via global namespace.
+ $strictClassPath = $propDef->isObjectType ? $propDef->getStrictClassPath() : null;
+
+ // Alright... we know that we're at the "data depth" and that $value
+ // refers to a single non-NULL, strongly typed value...
+ if ($direction === self::CONVERT_TO_INTERNAL) {
+ // No incoming value is allowed to be array anymore at this depth.
+ if (is_array($value)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to assign new inner array value for non-array inner property of "%s", which must be of type "%s".',
+ $propName, $propDef->isObjectType ? $strictClassPath : $propDef->propType
+ ));
+ }
+
+ // Now convert the provided individual value, as necessary...
+ if (!$propDef->isObjectType) {
+ // Cast the value to the target built-in PHP type. We cannot cast objects.
+ // NOTE: For performance, we don't check is_resource(), because
+ // those should never be able to appear in somebody's data. And
+ // they can actually be cast to any basic PHP type without any
+ // problems at all. It's only objects that are a problem and
+ // cannot have any "settype()" applied to them by PHP.
+ // Furthermore, is_resource() isn't even reliable anyway, since
+ // it returns false if it is a closed resource. So whatever,
+ // just rely on the fact that PHP can convert it to basic types.
+ if (is_object($value) || !@settype($value, $propDef->propType)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to cast new inner value for property "%s" to built-in PHP type "%s".',
+ $propName, $propDef->propType
+ ));
+ }
+ } else {
+ // Check that the new value is an object and that it's an instance
+ // of the exact required class (or at least a subclass of it).
+ // NOTE: Since all PropertyDefinition types are validated to derive
+ // from LazyJsonMapper, we don't need to check "instanceof".
+ if (!is_object($value) || !is_a($value, $strictClassPath)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'The new inner value for property "%s" must be an instance of class "%s".',
+ $propName, $strictClassPath
+ ));
+ }
+ }
+ } else { // CONVERT_FROM_INTERNAL
+ // Now convert the individual internal value, as necessary...
+ // NOTE: We validate and convert all values EVERY time, to protect
+ // against things like being constructed with bad non-JSON input-arrays
+ // with bad objects within it, and (even more importantly) to avoid
+ // problems whenever the user modifies internal data by reference via
+ // _getProperty() or __get() (particularly the latter; direct property
+ // access makes it INCREDIBLY easy to directly modify internal data,
+ // especially if they are arrays and the user does $x->items[] = 'foo').
+ if (!$propDef->isObjectType) {
+ // Basic PHP types are not allowed to have an array as their value.
+ // NOTE: If arr, then the PropertyDefinition doesn't match the JSON!
+ if (is_array($value)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected inner array value in non-array inner property for "%s", where we expect value type "%s".',
+ $propName, $propDef->propType
+ ));
+ }
+
+ // Cast the value to the target built-in PHP type. We cannot cast objects.
+ // NOTE: If resources appear in the data, they'll be castable by settype().
+ if (is_object($value) || !@settype($value, $propDef->propType)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to cast inner value for property "%s" to built-in PHP type "%s".',
+ $propName, $propDef->propType
+ ));
+ }
+ } else {
+ // Only convert the value to object if it isn't already an object.
+ if (!is_object($value)) {
+ // Unconverted JSON objects MUST have an array as their inner
+ // value, which contains their object data property list.
+ if (!is_array($value)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert non-array inner value for property "%s" into class "%s".',
+ $propName, $strictClassPath
+ ));
+ }
+
+ // The encountered array MUST have string-keys, otherwise it
+ // CANNOT be a JSON object. If the array has numerical keys
+ // instead, it means that our array-depth is wrong and that
+ // we're still looking at normal JSON ["foo"] arrays, instead
+ // of associative key-value object {"foo":"bar"} property pairs.
+ // NOTE: We only need to check the first key of the array,
+ // because JSON data CANNOT mix associative and numerical keys.
+ // In fact, if you try something like '{"a":{"a","foo":"bar"}}'
+ // then PHP actually refuses to decode such invalid JSON.
+ // NOTE: If first key is NULL, it means the array is empty. We
+ // must allow empty arrays, since '{"obj":{}}' is valid JSON.
+ // NOTE: We'll only detect non-object inner arrays if their
+ // first key is a numeric int(0). Because objects allow
+ // random numeric keys, but they don't allow sequential ones.
+ reset($value); // Rewind array pointer to its first element.
+ $firstArrKey = key($value); // Get key without moving pointer.
+ if ($firstArrKey !== null && is_int($firstArrKey) && $firstArrKey === 0) {
+ // Determine whether this is a regular array... If it
+ // consists entirely of numeric keys starting at 0 and
+ // going up sequentially without gaps, it's an array...
+ $isRegularArray = true;
+ $nextValidKey = 0; // Must start at 0.
+ foreach ($value as $k => $x) {
+ if ($k !== $nextValidKey++) { // ++ post increment.
+ $isRegularArray = false;
+ break;
+ }
+ }
+
+ // Only throw if it was a totally plain array. This
+ // check ensures that we still allow objects with
+ // numeric keys as long as they aren't 100%
+ // indistinguishable from a regular array.
+ if ($isRegularArray) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert non-object-array inner value for property "%s" into class "%s".',
+ $propName, $strictClassPath
+ ));
+ }
+ }
+
+ // Convert the raw JSON array value to its assigned class object.
+ try {
+ // Attempt creation and catch any construction issues.
+ // NOTE: This won't modify $value if construction fails.
+ $value = new $strictClassPath($value); // Constructs the classname in var.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ throw new LazyJsonMapperException(sprintf(
+ 'Failed to create an instance of class "%s" for property "%s": %s',
+ // NOTE: No need to format the exception message
+ // since it already has perfect grammar.
+ $strictClassPath, $propName, $e->getMessage()
+ ));
+ }
+ }
+
+ // Validate that the class matches the defined property class type.
+ // NOTE: Since all PropertyDefinition types are validated to derive
+ // from LazyJsonMapper, we don't need to verify "instanceof".
+ if (!is_a($value, $strictClassPath)) { // Exact same class or a subclass of it.
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected "%s" object in property "%s", but we expected an instance of "%s".',
+ Utilities::createStrictClassPath(get_class($value)),
+ $propName, $strictClassPath
+ ));
+ }
+ } // End of "Get object from internal".
+ } // End of CONVERT_FROM_INTERNAL.
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Utilities.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Utilities.php
new file mode 100755
index 0000000..1a31708
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/src/Utilities.php
@@ -0,0 +1,180 @@
+ 0) {
+ // Prepend "\" if missing, to force PHP to use the global namespace.
+ if ($className[0] !== '\\') {
+ $className = '\\'.$className;
+ }
+
+ return $className;
+ }
+
+ return null;
+ }
+
+ /**
+ * Splits a strict class-path into its namespace and class name components.
+ *
+ * To rejoin them later, just use: `'namespace' + '\\' + 'class'`.
+ *
+ * The class path should be in `get_class()` aka `TheClass::class` format.
+ *
+ * @param string $strictClassPath Class output of `createStrictClassPath()`.
+ *
+ * @return array Associative array with keys for `namespace` and `class`.
+ *
+ * @see Utilities::createStrictClassPath()
+ */
+ public static function splitStrictClassPath(
+ $strictClassPath = '')
+ {
+ // Split on the rightmost backslash. In a strict path there's always at
+ // least one backslash, for the leading "global namespace" backslash.
+ $lastDelimPos = strrpos($strictClassPath, '\\');
+ // Global: "". Other: "\Foo" or "\Foo\Bar" (if nested namespaces).
+ $namespace = substr($strictClassPath, 0, $lastDelimPos);
+ // Always: "TheClass".
+ $class = substr($strictClassPath, $lastDelimPos + 1);
+
+ return [
+ 'namespace' => $namespace,
+ 'class' => $class,
+ ];
+ }
+
+ /**
+ * Compare two class paths and generate the shortest path between them.
+ *
+ * @param array $sourceComponents Source class as `splitStrictClassPath()`.
+ * @param array $targetComponents Target class as `splitStrictClassPath()`.
+ *
+ * @return string The final path to reach from the source to the target.
+ *
+ * @see Utilities::splitStrictClassPath()
+ */
+ public static function createRelativeClassPath(
+ array $sourceComponents,
+ array $targetComponents)
+ {
+ $sourceNs = &$sourceComponents['namespace'];
+ $targetNs = &$targetComponents['namespace'];
+ $finalType = null;
+
+ // If either the source or the target lives in the global namespace,
+ // we won't do this processing. (Those need a strict, global path.)
+ if ($sourceNs !== '' && $targetNs !== '') {
+ // Check if the source-class namespace is at pos 0 of target space.
+ $pos = strpos($targetNs, $sourceNs);
+ if ($pos === 0) {
+ // Look at the character after the source-class namespace in the
+ // target namespace. Check for "" (str end) or "\\" (subspace).
+ $sourceNsLen = strlen($sourceNs);
+ $chr = substr($targetNs, $sourceNsLen, 1);
+ if ($chr === '') { // Exact same space, without any subspace.
+ $finalType = $targetComponents['class'];
+ } elseif ($chr === '\\') { // Same space, followed by subspace.
+ $finalType = sprintf(
+ '%s\\%s',
+ substr($targetNs, $sourceNsLen + 1),
+ $targetComponents['class']
+ );
+ } // Else: Was false positive, not in same namespace.
+ }
+ }
+
+ // In case of totally different spaces, or if any of the classes are in
+ // the global namespace, then just use the strict, global target path.
+ if ($finalType === null) {
+ $finalType = sprintf(
+ '%s\\%s',
+ $targetNs,
+ $targetComponents['class']
+ );
+ }
+
+ return $finalType;
+ }
+
+ /**
+ * Atomic filewriter.
+ *
+ * Safely writes new contents to a file using an atomic two-step process.
+ * If the script is killed before the write is complete, only the temporary
+ * trash file will be corrupted.
+ *
+ * The algorithm also ensures that 100% of the bytes were written to disk.
+ *
+ * @param string $filename Filename to write the data to.
+ * @param string $data Data to write to file.
+ * @param string $atomicSuffix Lets you optionally provide a different
+ * suffix for the temporary file.
+ *
+ * @return int|bool Number of bytes written on success, otherwise `FALSE`.
+ */
+ public static function atomicWrite(
+ $filename,
+ $data,
+ $atomicSuffix = 'atomictmp')
+ {
+ // Perform an exclusive (locked) overwrite to a temporary file.
+ $filenameTmp = sprintf('%s.%s', $filename, $atomicSuffix);
+ $writeResult = @file_put_contents($filenameTmp, $data, LOCK_EX);
+
+ // Only proceed if we wrote 100% of the data bytes to disk.
+ if ($writeResult !== false && $writeResult === strlen($data)) {
+ // Now move the file to its real destination (replaces if exists).
+ $moveResult = @rename($filenameTmp, $filename);
+ if ($moveResult === true) {
+ // Successful write and move. Return number of bytes written.
+ return $writeResult;
+ }
+ }
+
+ // We've failed. Remove the temporary file if it exists.
+ if (is_file($filenameTmp)) {
+ @unlink($filenameTmp);
+ }
+
+ return false; // Failed.
+ }
+}
diff --git a/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/tests/bootstrap.php b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/tests/bootstrap.php
new file mode 100755
index 0000000..8676102
--- /dev/null
+++ b/instafeed/vendor/lazyjsonmapper/lazyjsonmapper/tests/bootstrap.php
@@ -0,0 +1,18 @@
+login('yourusername', 'yourpassword');
+ $result = $ig->media->comment('14123451234567890_1234567890', 'Hello World');
+ var_dump($result);
+} catch (\Exception $e) {
+ echo $e->getMessage()."\n";
+}
+```
+
+Error Log/var_dump:
+
+```php
+// Please provide your error log/dump here, for example:
+
+RESPONSE: {"status": "fail", "message": "Sorry, the comment data may have been corrupted."}
+
+InstagramAPI\Response\CommentResponse: Sorry, the comment data may have been corrupted.
+```
+
+---
+
+### For a new endpoint *feature request*, you should include the *capture of the request and response*.
+
+Request:
+
+```http
+# Please provide your capture below, for example:
+
+GET /api/v1/si/fetch_headers/?guid=123456abcdeff19cc2f123456&challenge_type=signup HTTP/1.1
+Host: i.instagram.com
+Connection: keep-alive
+X-IG-Connection-Type: mobile(UMTS)
+X-IG-Capabilities: 3ToAAA==
+Accept-Language: en-US
+Cookie: csrftoken=g79dofABCDEFGII3LI7YdHei1234567; mid=WFI52QABAAGrbKL-ABCDEFGHIJK
+User-Agent: Instagram 10.3.0 Android (18/4.3; 320dpi; 720x1280; Xiaomi; HM 1SW; armani; qcom; en_US)
+Accept-Encoding: gzip, deflate, sdch
+```
+
+Response:
+
+```http
+# Please provide your capture below, for example:
+
+HTTP/1.1 200 OK
+Content-Language: en
+Expires: Sat, 01 Jan 2000 00:00:00 GMT
+Vary: Cookie, Accept-Language
+Pragma: no-cache
+Cache-Control: private, no-cache, no-store, must-revalidate
+Date: Thu, 15 Dec 2016 08:50:19 GMT
+Content-Type: application/json
+Set-Cookie: csrftoken=g79dofABCDEFGII3LI7YdHei1234567; expires=Thu, 14-Dec-2017 08:50:19 GMT; Max-Age=31449600; Path=/; secure
+Connection: keep-alive
+Content-Length: 16
+
+{"status": "ok"}
+```
+---
+
+### Describe your issue
+
+Explanation of your issue goes here.
+
+Please make sure the description is worded well enough to be understood, and with as much context and examples as possible.
+
+We reserve the right to close your ticket without answer if you can't bother spending a few minutes to write a helpful report for us.
diff --git a/instafeed/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/bug-report.md b/instafeed/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100755
index 0000000..6a3bf88
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,42 @@
+---
+name: Bug Report
+about: Report a bug or an unexpected behavior with the API
+labels: bug
+---
+# Bug Report
+
+---
+
+### Notes:
+We will close your issue, sometimes without answering, if any of the following are met:
+* You have not included your code or your debug log
+* You are asking about `challenge_required`, `checkpoint_required`, `feedback_required` or `sentry_block`. They've already been answered in the Wiki and *countless* closed tickets in the past!
+* You have used the wrong issue template
+* You are posting screenshots, which are too painful to help you with
+
+Please make sure you have done the following before submitting your issue:
+* You have [Searched](https://github.com/mgp25/Instagram-API/search?type=Issues) the bug-tracker for similar issues including **closed** ones
+* You have [Read the FAQ](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+* You have [Read the wiki](https://github.com/mgp25/Instagram-API/wiki)
+* You have [Reviewed the examples](https://github.com/mgp25/Instagram-API/tree/master/examples)
+* You have [Installed the api using ``composer``](https://github.com/mgp25/Instagram-API#installation)
+* You are [Using latest API release](https://github.com/mgp25/Instagram-API/releases)
+
+---
+
+### Description
+Please include a well-worded description of your issue below; We will close your issue if we have to guess what you're talking about, please be as specific as possible:
+
+__INSERT YOUR DESCRIPTION HERE__
+
+### Code
+Please post your code relevant to the bug in the section below:
+```php
+INSERT YOUR CODE HERE
+```
+
+### Debug Log
+Please post your debug log in the section below; You can enable the debug log by using `new Instagram(true)` instead of `new Instagram()`:
+```php
+INSERT YOUR DEBUG LOG HERE
+```
\ No newline at end of file
diff --git a/instafeed/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/feature-request.md b/instafeed/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100755
index 0000000..07c3ecc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/feature-request.md
@@ -0,0 +1,26 @@
+---
+name: Feature Request
+about: Suggest a new feature/endpoint or an update to a function
+labels: request
+---
+# Feature Request
+
+---
+
+### Notes:
+We will close your feature request, sometimes without answering, if any of the following are met:
+* You requested a endpoint/function for `challenge_required`, `checkpoint_required`, `feedback_required` or `sentry_block` errors. These are decidedly unsupported, and will not be added
+* You requested a endpoint/function for an iOS feature. This API emulates the Android API, therefore we cannot, *safely*, add endpoints/functions which are not in the Android app.
+* You are using the wrong issue template
+
+Please make sure you have done the following before submitting your feature request:
+* You have [Searched](https://github.com/mgp25/Instagram-API/search?type=Issues) the bug-tracker for the same feature request
+* You have [Read the FAQ](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+* You have [Read the wiki](https://github.com/mgp25/Instagram-API/wiki)
+
+---
+
+### Description
+Please include a well-worded description of your feature request. Please make sure to include where on the app this feature is acceptable so we can reproduce it. Additionally, it would make our lives easier if you are able to post the endpoint, its associated parameters, and response.
+
+__INSERT DESCRIPTION HERE__
\ No newline at end of file
diff --git a/instafeed/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/question.md b/instafeed/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/question.md
new file mode 100755
index 0000000..ecddf74
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,42 @@
+---
+name: Question
+about: Ask a question about the API
+labels: question
+---
+# Question
+
+---
+
+### Notes:
+We will close your question, sometimes without answering, if any of the following are met:
+* You have not included context about your question
+* You are asking about `challenge_required`, `checkpoint_required`, `feedback_required` or `sentry_block`. They've already been answered in the Wiki and *countless* closed tickets in the past!
+* You have used the wrong issue template
+* You are posting screenshots, which are too painful to help you with
+
+Please make sure you have done the following before submitting your question:
+* You have [Searched](https://github.com/mgp25/Instagram-API/search?type=Issues) the bug-tracker for similar questions including **closed** ones
+* You have [Read the FAQ](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+* You have [Read the wiki](https://github.com/mgp25/Instagram-API/wiki)
+* You have [Reviewed the examples](https://github.com/mgp25/Instagram-API/tree/master/examples)
+* You have [Installed the api using ``composer``](https://github.com/mgp25/Instagram-API#installation)
+* You are [Using latest API release](https://github.com/mgp25/Instagram-API/releases)
+
+---
+
+### Description
+Please include a well-worded description of your question below; We will close your issue if we have to guess what you're talking about, please be as specific as possible:
+
+__INSERT YOUR DESCRIPTION HERE__
+
+### Code (Optional)
+Please post your code relevant to the question in the section below:
+```php
+INSERT YOUR CODE HERE
+```
+
+### Debug Log (Optional)
+Please post your debug log in the section below; You can enable the debug log by using `new Instagram(true)` instead of `new Instagram()`:
+```php
+INSERT YOUR DEBUG LOG HERE
+```
\ No newline at end of file
diff --git a/instafeed/vendor/mgp25/instagram-php/.gitignore b/instafeed/vendor/mgp25/instagram-php/.gitignore
new file mode 100755
index 0000000..39fc46d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/.gitignore
@@ -0,0 +1,49 @@
+# miscellaneous
+sigs/sigs
+sigs/sigKeys
+
+# ignore the lockfile for this library
+composer.lock
+
+# runtime files
+*.dat
+*.jpg
+*.lock
+backups/
+sessions/
+
+# third party libraries
+vendor/
+.php_cs
+.php_cs.cache
+devtools/exif-orientation-examples/
+
+# temporary editor files
+*.swp
+.\#*
+*.sublime-project
+*.sublime-workspace
+.idea
+
+# operating system cache files
+.DS_Store
+Desktop.ini
+Thumbs.db
+
+# tags created by etags, ctags, gtags (GNU global), cscope and ac-php
+TAGS
+.TAGS
+!TAGS/
+tags
+.tags
+!tags/
+gtags.files
+GTAGS
+GRTAGS
+GPATH
+GSYMS
+cscope.files
+cscope.out
+cscope.in.out
+cscope.po.out
+ac-php-tags/
diff --git a/instafeed/vendor/mgp25/instagram-php/.php_cs.dist b/instafeed/vendor/mgp25/instagram-php/.php_cs.dist
new file mode 100755
index 0000000..c01fac1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/.php_cs.dist
@@ -0,0 +1,38 @@
+setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__)
+ )
+ ->setIndent(' ')
+ ->setLineEnding("\n")
+ ->setRules([
+ '@Symfony' => true,
+ // Override @Symfony rules
+ 'pre_increment' => false,
+ 'blank_line_before_statement' => ['statements' => ['return', 'try', 'throw']],
+ 'phpdoc_align' => ['tags' => ['param', 'throws']],
+ 'method_argument_space' => ['ensure_fully_multiline' => false],
+ 'binary_operator_spaces' => [
+ 'align_double_arrow' => true,
+ 'align_equals' => false,
+ ],
+ 'phpdoc_annotation_without_dot' => false,
+ 'yoda_style' => [
+ // Symfony writes their conditions backwards; we use normal order.
+ 'equal' => false,
+ 'identical' => false,
+ 'less_and_greater' => false,
+ ],
+ 'is_null' => [
+ // Replaces all is_null() with === null.
+ 'use_yoda_style' => false,
+ ],
+ // Custom rules
+ 'align_multiline_comment' => true,
+ 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
+ 'ordered_imports' => ['sort_algorithm' => 'alpha'],
+ 'phpdoc_order' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ]);
diff --git a/instafeed/vendor/mgp25/instagram-php/.pre-commit.hook b/instafeed/vendor/mgp25/instagram-php/.pre-commit.hook
new file mode 100755
index 0000000..d091646
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/.pre-commit.hook
@@ -0,0 +1,58 @@
+#!/bin/sh
+#
+# Verifies that all files in the worktree follow our codestyle standards.
+#
+# Note that this script can't check that they're actually committing the nicely
+# formatted code. It just checks that the worktree is clean. So if they've fixed
+# all files but haven't added the codestyle fixes to their commit, they'll still
+# pass this check. But it's still a great protection against most mistakes.
+#
+# To install this hook, just run the following in the project's root folder:
+# ln -fs "../../.pre-commit.hook" .git/hooks/pre-commit
+#
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Git ensures that CWD is always the root of the project folder, so we can just
+# run all tests without verifying what folder we are in...
+
+failed="no"
+echo "[pre-commit] Checking work-tree codestyle..."
+
+# Check if we need updated LazyJsonMapper class documentation.
+echo "> Verifying class documentation..."
+./vendor/bin/lazydoctor -c composer.json -pfo --validate-only >/dev/null
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Check the general codestyle format.
+echo "> Verifying php-cs-fixer..."
+./vendor/bin/php-cs-fixer fix --config=.php_cs.dist --allow-risky yes --dry-run
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Look for specific problems with the style, related to our project.
+echo "> Verifying checkStyle..."
+/usr/bin/env php devtools/checkStyle.php x
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Refuse to commit if there were problems. Instruct the user about solving it.
+if [ "${failed}" = "yes" ]; then
+ # Yes there are lots of "echo" commands, because "\n" is not cross-platform.
+ echo "[commit failed] There are problems with your code..."
+ echo ""
+ echo "Run 'composer codestyle' to fix the code in your worktree."
+ echo ""
+ echo "But beware that the process is automatic, and that the result"
+ echo "isn't always perfect and won't be automatically staged."
+ echo ""
+ echo "So remember to manually read through the changes, then further"
+ echo "fix them if necessary, and finally stage the updated code"
+ echo "afterwards so that the fixed code gets committed."
+ exit 1
+fi
diff --git a/instafeed/vendor/mgp25/instagram-php/CODE_OF_CONDUCT.md b/instafeed/vendor/mgp25/instagram-php/CODE_OF_CONDUCT.md
new file mode 100755
index 0000000..4754ab5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/instafeed/vendor/mgp25/instagram-php/CONTRIBUTING.md b/instafeed/vendor/mgp25/instagram-php/CONTRIBUTING.md
new file mode 100755
index 0000000..4173a1d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/CONTRIBUTING.md
@@ -0,0 +1,488 @@
+# Contributing to Instagram API
+
+:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
+
+The following is a set of guidelines for contributing to the Instagram API, which is hosted in the [Instagram API repository](https://github.com/mgp25/Instagram-API) on GitHub.
+
+These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
+
+- [What should I know before I get started?](#what-should-i-know-before-i-get-started)
+ * [Code of Conduct](#code-of-conduct)
+
+- [Basic rules](#basic-rules)
+ * [Git Setup](#git-setup)
+ * [Codestyle Command](#codestyle-command)
+ * [Available Tasks](#available-tasks)
+ * [Commits](#commits)
+ * [Modifying anything in the existing code](#modifying-anything-in-the-existing-code)
+
+- [Styleguides](#styleguide)
+ * [Namespaces](#namespaces)
+ * [Functions and Variables](#functions-and-variables)
+ * [Function Documentation](#function-documentation)
+ * [Exceptions](#exceptions)
+
+- [Contributing-new-endpoints](#contributing-new-endpoints)
+
+
+## What should I know before I get started?
+
+### Code of Conduct
+
+This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
+By participating, you are expected to uphold this code.
+Please report any unacceptable behavior.
+
+## Basic rules
+
+Important! Your contributions must follow all of these rules for a consistent and bug-free project.
+
+This is a document which, among other things, describes PSR-4 class autoloading, clean commits, how to handle/document exceptions, how to structure function arguments, naming clear variables, always adding/updating PHPdoc blocks in all affected functions, and how to verify that your changes don't _break everything_ when performing big changes to existing functions or adding brand new functions and classes.
+
+
+### Git Setup
+
+If you are using a Linux/Mac/Unix system, you **MUST** install our git-hook in your local repository. It will help prevent you from accidentally committing badly formatted code. Please run the following command in the project's root folder (the folder which has files like `README.md` etc), to install the hook in your repository:
+
+```
+ln -fs "../../.pre-commit.hook" .git/hooks/pre-commit
+```
+
+
+### Codestyle Command
+
+Before committing any work, you **MUST** always run the codestyle fixer command in the root folder of the repository:
+
+```
+composer codestyle
+```
+
+That command will automatically fix all codestyle issues as well as generate various documentation, such as the class docs for all of the Responses and Model items.
+
+
+### Available Tasks
+
+Please visit our [Project Task Tracker](https://github.com/mgp25/Instagram-API/projects), where contributors can look at the list of available work. High-quality code contributions are greatly appreciated.
+
+
+### Commits
+
+- You **MUST** try to **separate** work into smaller commits. So if you `"changed Utils.php to fix getSeconds and changed Request.php to fix Request()"`, then make **TWO SEPARATE COMMITS**, so that we can easily find your changes in the project history, and can easily revert any individual broken changes without losing tons of other code too! So remember: 1 commit per task. :smile:
+
+- Use the detailed description field of your commit, to document why you did certain changes if it's not an obvious change (such as a typo fix). Your description gets added to the history of the project so that we can know what you were thinking, if we need to check the change again later.
+
+- **Name** your single-file commits `"AffectedClassName: Change description"` and keep the total length of the summary line at 50 characters or less in total (the Git/GitHub standard).
+
+- If your change NEEDS to affect multiple files, then it is SOMETIMES okay to leave out the classname part from the summary, but in that case you _must_ write a very clear and descriptive summary to explain it. _But_ usually you _should_ still include the classname even for multi-file edits. For example, renaming a function in Utils and having to change the name of it in other files is still a `"Utils: Renamed bleh() to bloop()"` commit.
+
+Examples of BAD commit summaries:
+
+```
+Edit something
+Fix this
+Utils+request changes
+```
+
+Examples of GOOD commit summaries:
+
+```
+Utils: Fix formatting of getSeconds
+Request: Send cropping information
+Changed all number_format() to round() everywhere
+Response: Parse timestamps as floats
+```
+
+
+### Modifying anything in the existing code
+
+- If you want to change a public API function's parameters (particularly in `src/Instagram.php`), think EXTREMELY hard about NOT doing it. And if you absolutely MUST do it (such as adding an important new parameter), then you MUST add it in a BACKWARDS-COMPATIBLE WAY, by adding it as the LAST parameter of the argument list AND providing a sensible DEFAULT VALUE for it so that people's CURRENT projects based on this library continue working and DON'T BREAK due to your function definition changes!
+
+- Do NOT look at your changes in isolation. Look at the BIG PICTURE of what your change now does to the REST of the codebase.
+
+- For example, if you change the returned variable type from a function (such as from an `"int"` to a `"string"`), then you MUST update the function's PHPdoc block to document the new return value (with `@return string`), and you MUST also slowly and carefully check ALL OTHER CODE that relied on the OLD behavior of that function. There's a command-line tool called "grep" (or an even better one especially made for programmers, called ["ag" aka "the silver searcher"](https://github.com/ggreer/the_silver_searcher)) to search for text in files. USE IT to check ALL other code locations that called your modified function, and make sure that you didn't break ANY of them AT ALL!
+
+- In fact, ANY TIME that you change a function's ARGUMENTS, RETURN VALUE or THROWN EXCEPTIONS (new/deleted ones), then you MUST update the function's PHPdoc block to match the new truth. And you MUST check ALL other functions that CALL your function, and update those too. For example let's say they DON'T handle a new exception you're now throwing, in which case you MUST now update THEIR `@throws` documentation to document the fact that THEY now let yet another exception bubble up. And then you MUST search for the functions that called THOSE updated functions and update THEIR documentation TOO, all the way until you've reached the top and have FULLY documented what exceptions are being thrown in the whole chain of function calls.
+
+- You MUST ALWAYS update the PHPdoc blocks EVERYWHERE that's affected by your changes (whenever you've changed a functions ARGUMENTS or RETURN type or what it THROWS (any new/deleted exceptions)), because imagine if we NEVER update the blocks EVERYWHERE that's affected by your change. We would then have something like `/** @param $a, $b, $c */ function foo ($x)` and sudden, critical exceptions that aren't getting handled and thus KILL the program.
+
+Here's a checklist for what you MUST do to ENSURE totally correct documentation EVERY TIME that you make a CHANGE to a function's ARGUMENTS or RETURN TYPE or EXCEPTIONS of an existing function, OR when you introduce a NEW function whose use is being added to any existing functions.
+
+1. You MUST ALWAYS use grep/ag. Find EVERY other code location that uses your new/modified function.
+
+2. Check: Did your changes just BREAK EVERYTHING somewhere else, which now has to be updated to match? Almost GUARANTEED the answer is YES, and you MUST update the other locations that expected the old function behavior/return type/parameters/exceptions.
+
+3. Check: Do you need to also UPDATE the PHPdoc for those OTHER functions too? OFTEN the answer is YES. For example, if you've changed the exceptions that a deep, internal function throws, then you MUST either catch it in all higher functions and do something with it, OR let it pass through them upwards. And if you do let it pass through and bubble up then you MUST ALSO update the PHPdoc for THAT higher function to say `"@throws TheNewException"` (or delete something in case you changed a subfunction to no longer throw).
+
+4. If your updates to the affected higher-level functions in steps 2/3 means that THOSE other functions now ALSO behave differently (meaning THEY have new return values/exceptions/parameters), then you MUST also do ANOTHER grep/ag to check for anything that uses THOSE functions, and update all of those code locations TOO. And continue that way up the entire chain of functions that call each other, until you reach the top of the project, so that the entire chain of function calls is caught/documented properly. Otherwise, those unexpected (undocumented) exceptions will terminate PHP, so this is very important!
+
+
+
+## Styleguides
+
+### Namespaces
+
+- Organize all classes into logical namespaces.
+
+- We follow the PSR-4 Autoloading standard, which means that you MUST only have ONE class per source code `.php` file. And the namespace AND classname MUST match BOTH its disk path AND its `.php` filename. In our project, the `src/` folder is the `InstagramAPI` namespace, and everything under that is for its subnamespaces (folders) and our classes (PHP files).
+
+Example of a proper class in our top-level namespace:
+
+```php
+src/Something.php:
+_protectedProperty;
+ }
+
+ public function setProtectedProperty(
+ $protectedProperty)
+ {
+ $this->_protectedProperty = $protectedProperty;
+ }
+
+ public function getPublicProperty()
+ {
+ return $this->_publicProperty;
+ }
+
+ public function setPublicProperty(
+ $publicProperty)
+ {
+ $this->publicProperty = $publicProperty;
+ }
+
+ protected function _somethingInternal()
+ {
+ ...
+ }
+
+ ...
+}
+```
+
+- All functions and variables MUST have descriptive names that document their purpose automatically. Use names like `$videoFilename` and `$deviceInfo` and so on, so that the code documents itself instead of needing tons of comments to explain what each step is doing.
+
+Examples of BAD variable names:
+
+```php
+$x = $po + $py;
+$w = floor($h * $ar);
+```
+
+Examples of GOOD variable names:
+
+```php
+$endpoint = $this->url.'?'.http_build_query($this->params);
+$this->_aspectRatio = (float) ($this->_width / $this->_height);
+$width = floor($this->_height * $this->_maxAspectRatio);
+```
+
+- All functions MUST have occasional comments that explain what they're doing in their various substeps. Look at our codebase and follow our commenting style.
+
+- Our comments start with a capital letter and end in punctuation, and describe the purpose in as few words as possible, such as: `// Default request options (immutable after client creation).`
+
+- All functions MUST do as little work as possible, so that they are easy to maintain and bugfix.
+
+Example of a GOOD function layout:
+
+```php
+function requestVideoURL(...);
+
+function uploadVideoChunks(...);
+
+function configureVideo(...);
+
+function uploadVideo(...)
+{
+ $url = $this->requestVideoURL();
+ if (...handle any errors from the previous function)
+
+ $uploadResult = $this->uploadVideoChunks($url, ...);
+
+ $this->configureVideo($uploadResult, ...);
+}
+```
+
+Example of a BAD function layout:
+
+```php
+function uploadVideo(...)
+{
+ // Request upload URL.
+ // Upload video data to URL.
+ // Configure its location property.
+ // Post it to a timeline.
+ // Call your grandmother.
+ // Make some tea.
+ // ...and 500 other lines of code.
+}
+```
+
+- All function parameter lists MUST be well-thought out so that they list the most important arguments FIRST and so that they are as SIMPLE as possible to EXTEND in the FUTURE, since Instagram's API changes occasionally.
+
+Avoid this kind of function template:
+
+```php
+function uploadVideo($videoFilename, $filter, $url, $caption, $usertags, $hashtags);
+```
+
+Make such multi-argument functions take future-extensible option-arrays instead, especially if you expect that more properties may be added in the future.
+
+So the above would instead be PROPERLY designed as follows:
+
+```php
+function uploadVideo($videoFilename, array $metadata);
+```
+
+Now users can just say `uploadVideo($videoFilename, ['hashtags'=>$hashtags]);`, and we can easily add more metadata fields in the future without ever breaking backwards-compatibility with projects that are using our function!
+
+### Function Documentation
+
+- All functions MUST have _COMPLETE_ PHPdoc doc-blocks. The critically important information is the single-sentence `summary-line` (ALWAYS), then the `detailed description` (if necessary), then the `@param` descriptions (if any), then the `@throws` (one for EVERY type of exception that it throws, even uncaught ones thrown from DEEPER functions called within this function), then the `@return` (if the function returns something), and lastly one or more `@see` if there's any need for a documentation reference to a URL or another function or class.
+
+Example of a properly documented function:
+
+```php
+ /**
+ * Generates a User Agent string from a Device (<< that is the REQUIRED ONE-SENTENCE summary-line).
+ *
+ * [All lines after that are the optional description. This function didn't need any,
+ * but you CAN use this area to provide extra information describing things worth knowing.]
+ *
+ * @param \InstagramAPI\Devices\Device $device The Android device.
+ * @param string[]|null $names (optional) Array of name-strings.
+ *
+ * @throws \InvalidArgumentException If the device parameter is invalid.
+ * @throws \InstagramAPI\Exception\InstagramException In case of invalid or failed API response.
+ *
+ * @return string
+ *
+ * @see otherFunction()
+ * @see http://some-url...
+ */
+ public static function buildUserAgent(
+ Device $device,
+ $names = null)
+ {
+ ...
+ }
+```
+
+- You MUST take EXTREMELY GOOD CARE to ALWAYS _perfectly_ document ALL parameters, the EXACT return-type, and ALL thrown exceptions. All other project developers RELY on the function-documentation ALWAYS being CORRECT! With incorrect documentation, other developers would make incorrect assumptions and _severe_ bugs would be introduced!
+
+### Exceptions
+
+- ALL thrown exceptions that can happen inside a function or in ANY of its SUB-FUNCTION calls MUST be documented as `@throws`, so that we get a COMPLETE OVERVIEW of ALL exceptions that may be thrown when we call the function. YES, that EVEN means exceptions that come from deeper function calls, whose exceptions are NOT being caught by your function and which will therefore bubble up if they're thrown by those deeper sub-functions!
+- Always remember that Exceptions WILL CRITICALLY BREAK ALL OTHER CODE AND STOP PHP'S EXECUTION if not handled or documented properly! They are a LOT of responsibility! So you MUST put a LOT OF TIME AND EFFORT into PROPERLY handling (_catching and doing something_) for ALL exceptions that your function should handle, AND adding PHPdoc _documentation_ about the ones that your function DOESN'T catch/handle internally and which WILL therefore bubble upwards and would possibly BREAK other code (which is EXACTLY what would happen if an exception ISN'T documented by you and someone then uses your bad function and doesn't "catch" your exception since YOU didn't tell them that it can be thrown)!
+- All of our internal exceptions derive from `\InstagramAPI\Exception\InstagramException`, so it's always safe to declare that one as a `@throws \InstagramAPI\Exception\InstagramException` if you're calling anything that throws exceptions based on our internal `src/Exception/*.php` system. But it's even better if you can pinpoint which exact exceptions are thrown, by looking at the functions you're calling and seeing their `@throws` documentation, WHICH OF COURSE DEPENDS ON PEOPLE HAVING WRITTEN PROPER `@throws` FOR THOSE OTHER FUNCTIONS SO THAT _YOU_ KNOW WHAT THE FUNCTIONS YOU'RE CALLING WILL THROW. DO YOU SEE _NOW_ HOW IMPORTANT IT IS TO DECLARE EXCEPTIONS PROPERLY AND TO _ALWAYS_ KEEP THAT LIST UP TO DATE
+- Whenever you are using an EXTERNAL LIBRARY that throws its own custom exceptions (meaning NOT one of the standard PHP ones such as `\Exception` or `\InvalidArgumentException`, etc), then you MUST ALWAYS re-wrap the exception into some appropriate exception from our own library instead, otherwise users will not be able to say `catch (\InstagramAPI\Exception\InstagramException $e)`, since the 3rd party exceptions wouldn't be derived from our base exception and wouldn't be caught, thus breaking the user's program. To solve that, look at the design of our `src/Exception/NetworkException.php`, which we use in `src/Client.php` to re-wrap all Guzzle exceptions into our own exception type instead. Read the source-code of our NetworkException and it will explain how to properly re-wrap 3rd party exceptions and how to ensure that your re-wrapped exception will give users helpful messages and helpful stack traces.
+
+
+# Contributing new endpoints
+
+In order to add endpoints to the API you will need to capture the requests first. For that, you can use any HTTPS proxy you want. You can find a lot of information about this on the internet. Remember that you need to install a root CA (Certificate Authority) in your device so that the proxy can decrypt the requests and show them to you.
+
+Also be aware that you cannot capture Instagram for iPhone's requests. The iPhone API parameters are totally different and they are NOT compatible with this Android API library! You MUST use a real Android APK when capturing requests!
+
+Once you have the endpoint and necessary parameters, how do you add them to this library? Easy, you can follow this example:
+
+```php
+ public function getAwesome(
+ array $userList)
+ {
+ return $this->ig->request('awesome/endpoint/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('user_ids', implode(',', $userList))
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\AwesomeResponse());
+ }
+```
+
+In the example above you can see `('awesome/endpoint/')` which is the endpoint you captured. We are simulating a POST request, so you can add POST parameters easily by doing `->addPost('_uuid', $this->uuid)`.
+
+Which is basically:
+
+```php
+->addPost(key, value)
+```
+
+Where key is the name of the POST param, and value is whatever value the server requires for that parameter.
+
+Some of the requests are signed. This means there is a hash concatenated with the JSON. In order to make a signed request, we can enable or disable signing with the following line:
+
+```php
+->setSignedPost($isSigned)
+```
+
+`$isSigned` is boolean, so if you want a signed request, you simply set it to `true`.
+
+If the request is a GET request, you can add the GET query parameters like this (instead of using `addPost`):
+
+```php
+->addParam(key, value)
+```
+
+And finally, we always end with the `getResponse` function call, which will read the response and return an object with all of the server response values:
+
+```php
+->getResponse(new Response\AwesomeResponse());
+```
+
+Now you might be wondering how to create that response class? But there is nothing to worry about... it's very simple!
+
+Imagine that you have the following response:
+
+```json
+{"items": [{"user": {"is_verified": false, "has_anonymous_profile_picture": false, "is_private": false, "full_name": "awesome", "username": "awesome", "pk": "uid", "profile_pic_url": "profilepic"}, "large_urls": [], "caption": "", "thumbnail_urls": ["thumb1", "thumb2", "thumb3", "thumb4"]}], "status": "ok"}
+```
+
+You can use [http://jsoneditoronline.org](http://jsoneditoronline.org/) for a better visualization:
+
+
+
+(Alternatively, if you're an advanced user, you can create an empty response-class (with no defined properties yet), and then use its `->printJson()` function to look at its contents in a beautifully readable way.)
+
+So your new `src/Response/AwesomeResponse.php` class should contain one `JSON_PROPERTY_MAP` property named `items`. Our magical [LazyJsonMapper](https://github.com/lazyjsonmapper/lazyjsonmapper) object mapping system needs a PHPdoc-style type-definition to tell us if the property is another class, a float, an int, a string, a string array, etc. By default, if you don't specify any type (if you set its value to `''`), it will treat the JSON value as whatever type PHP detected it as internally during JSON decoding (such as a string, int, float, bool, etc).
+
+In this scenario, your `src/Response/AwesomeResponse.php` file and its property definitions should look as follows, since we want to map `items` to an array of `Suggestion` objects:
+
+```php
+ 'Model\Suggestion[]',
+ ];
+}
+```
+
+The `items` property will now expect an array of Suggestion model objects. And `src/Response/Model/Suggestion.php` should look like this:
+
+```php
+ '',
+ 'social_context' => '',
+ 'algorithm' => '',
+ 'thumbnail_urls' => 'string[]',
+ 'value' => '',
+ 'caption' => '',
+ 'user' => 'User',
+ 'large_urls' => 'string[]',
+ 'media_ids' => '',
+ 'icon' => '',
+ ];
+}
+```
+
+Here in this `Suggestion` class you can see many variables that didn't appear in our example endpoint's response, but that's because many other requests _re-use_ the same object, and depending the request, their response contents may differ a bit. Also note that unlike the AwesomeResponse class, the actual Model objects (the files in in `src/Response/Model/`) _don't_ use the "Model\" prefix when referring to other model objects, since they are in the same namespace already.
+
+Also note that many of the properties above are defined as `''` (no type), which means "mixed/untyped". It simply means that the value is not forcibly converted to anything. That's how you should define most properties unless you have a specific reason to force anything to a specific type.
+
+It is also _extremely important_ that any properties relating to Media IDs, PKs, User PKs, etc, _must_ be declared as a `string`, otherwise they may be handled as a float/int which won't fit on 32-bit CPUs and will truncate the number, leading to the wrong data. Just look at all other Model objects that are already in this project, and be sure that any ID/PK fields in your own new Model object are properly tagged as `string` type too!
+
+Now you can test your new endpoint, in order to look at its response object:
+
+```
+$awesome = $i->getAwesome(['123']);
+$awesome->printJson(); // Look at the object data.
+$awesome->printPropertyDescriptions(); // Look at all defined properties.
+```
+
+And finally, how do you access the object's data? Via the magical [LazyJsonMapper](https://github.com/lazyjsonmapper/lazyjsonmapper), which your Response and Model objects inherit from! It automatically creates getters and setters for all properties, and has a million other features!
+
+```php
+$items = $awesome->getItems();
+$user = $items[0]->getUser();
+
+// LazyJsonMapper even lets you look at the JSON of specific sub-objects:
+$user->printJson();
+```
+
+Lastly, you may sometimes be implementing an endpoint which uses a nearly empty response with just the standard `status` and/or `message` (and `_messages`) fields, and no other fields. In that case, there's already a pre-made response which you should use: `GenericResponse`. Search the source code for that word and you'll find many endpoints where we use that basic response. Use it to easily add new endpoint implementations whenever there's no extra data to extract!
+
+We hope that you found this tutorial useful! :smile:
diff --git a/instafeed/vendor/mgp25/instagram-php/Dockerfile b/instafeed/vendor/mgp25/instagram-php/Dockerfile
new file mode 100755
index 0000000..2b30c43
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/Dockerfile
@@ -0,0 +1,37 @@
+# The php:7.0-apache Docker image is based on debian:jessie.
+# See: https://github.com/docker-library/php/blob/20b89e64d16dc9310ba6493a38385e36304dded7/7.0/Dockerfile
+
+FROM php:7.1-apache-jessie
+RUN echo "deb http://deb.debian.org/debian jessie main" > /etc/apt/sources.list \
+ && echo "deb http://security.debian.org jessie/updates main" >> /etc/apt/sources.list \
+ && apt-get update \
+ && apt-get install -y \
+ libfreetype6-dev \
+ libjpeg62-turbo-dev \
+ libmcrypt-dev \
+ libpng12-dev \
+ git \
+ libav-tools \
+ unzip \
+ wget \
+ xz-utils \
+ && docker-php-ext-install -j$(nproc) iconv mcrypt \
+ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
+ && docker-php-ext-install -j$(nproc) gd \
+ && docker-php-ext-install -j$(nproc) bcmath \
+ && docker-php-ext-install -j$(nproc) exif
+
+RUN wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz \
+ && tar Jxvf ./ffmpeg-release-amd64-static.tar.xz \
+ && cp ./ffmpeg*amd64-static/ffmpeg /usr/local/bin/
+
+# Install Composer and make it available in the PATH
+RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer
+
+# Set the WORKDIR to /app so all following commands run in /app
+WORKDIR /var/www/html
+
+COPY . ./
+
+# Install dependencies with Composer.
+RUN composer install --prefer-source --no-interaction
diff --git a/instafeed/vendor/mgp25/instagram-php/LICENSE b/instafeed/vendor/mgp25/instagram-php/LICENSE
new file mode 100755
index 0000000..b1ba59a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/LICENSE
@@ -0,0 +1,546 @@
+Reciprocal Public License (RPL-1.5)
+
+Version 1.5, July 15, 2007
+
+Copyright (C) 2001-2007
+Technical Pursuit Inc.,
+All Rights Reserved.
+
+
+PREAMBLE
+
+The Reciprocal Public License (RPL) is based on the concept of reciprocity or,
+if you prefer, fairness.
+
+In short, this license grew out of a desire to close loopholes in previous open
+source licenses, loopholes that allowed parties to acquire open source software
+and derive financial benefit from it without having to release their
+improvements or derivatives to the community which enabled them. This occurred
+any time an entity did not release their application to a "third party".
+
+While there is a certain freedom in this model of licensing, it struck the
+authors of the RPL as being unfair to the open source community at large and to
+the original authors of the works in particular. After all, bug fixes,
+extensions, and meaningful and valuable derivatives were not consistently
+finding their way back into the community where they could fuel further, and
+faster, growth and expansion of the overall open source software base.
+
+While you should clearly read and understand the entire license, the essence of
+the RPL is found in two definitions: "Deploy" and "Required Components".
+
+Regarding deployment, under the RPL your changes, bug fixes, extensions, etc.
+must be made available to the open source community at large when you Deploy in
+any form -- either internally or to an outside party. Once you start running
+the software you have to start sharing the software.
+
+Further, under the RPL all components you author including schemas, scripts,
+source code, etc. -- regardless of whether they're compiled into a single
+binary or used as two halves of client/server application -- must be shared.
+You have to share the whole pie, not an isolated slice of it.
+
+In addition to these goals, the RPL was authored to meet the requirements of
+the Open Source Definition as maintained by the Open Source Initiative (OSI).
+
+The specific terms and conditions of the license are defined in the remainder
+of this document.
+
+
+LICENSE TERMS
+
+1.0 General; Applicability & Definitions. This Reciprocal Public License
+Version 1.5 ("License") applies to any programs or other works as well as any
+and all updates or maintenance releases of said programs or works ("Software")
+not already covered by this License which the Software copyright holder
+("Licensor") makes available containing a License Notice (hereinafter defined)
+from the Licensor specifying or allowing use or distribution under the terms of
+this License. As used in this License:
+
+1.1 "Contributor" means any person or entity who created or contributed to the
+creation of an Extension.
+
+1.2 "Deploy" means to use, Serve, sublicense or distribute Licensed Software
+other than for Your internal Research and/or Personal Use, and includes
+without limitation, any and all internal use or distribution of Licensed
+Software within Your business or organization other than for Research and/or
+Personal Use, as well as direct or indirect sublicensing or distribution of
+Licensed Software by You to any third party in any form or manner.
+
+1.3 "Derivative Works" as used in this License is defined under U.S. copyright
+law.
+
+1.4 "Electronic Distribution Mechanism" means a mechanism generally accepted
+in the software development community for the electronic transfer of data such
+as download from an FTP server or web site, where such mechanism is publicly
+accessible.
+
+1.5 "Extensions" means any Modifications, Derivative Works, or Required
+Components as those terms are defined in this License.
+
+1.6 "License" means this Reciprocal Public License.
+
+1.7 "License Notice" means any notice contained in EXHIBIT A.
+
+1.8 "Licensed Software" means any Software licensed pursuant to this License.
+Licensed Software also includes all previous Extensions from any Contributor
+that You receive.
+
+1.9 "Licensor" means the copyright holder of any Software previously not
+covered by this License who releases the Software under the terms of this
+License.
+
+1.10 "Modifications" means any additions to or deletions from the substance or
+structure of (i) a file or other storage containing Licensed Software, or (ii)
+any new file or storage that contains any part of Licensed Software, or (iii)
+any file or storage which replaces or otherwise alters the original
+functionality of Licensed Software at runtime.
+
+1.11 "Personal Use" means use of Licensed Software by an individual solely for
+his or her personal, private and non-commercial purposes. An individual's use
+of Licensed Software in his or her capacity as an officer, employee, member,
+independent contractor or agent of a corporation, business or organization
+(commercial or non-commercial) does not qualify as Personal Use.
+
+1.12 "Required Components" means any text, programs, scripts, schema,
+interface definitions, control files, or other works created by You which are
+required by a third party of average skill to successfully install and run
+Licensed Software containing Your Modifications, or to install and run Your
+Derivative Works.
+
+1.13 "Research" means investigation or experimentation for the purpose of
+understanding the nature and limits of the Licensed Software and its potential
+uses.
+
+1.14 "Serve" means to deliver Licensed Software and/or Your Extensions by
+means of a computer network to one or more computers for purposes of execution
+of Licensed Software and/or Your Extensions.
+
+1.15 "Software" means any computer programs or other works as well as any
+updates or maintenance releases of those programs or works which are
+distributed publicly by Licensor.
+
+1.16 "Source Code" means the preferred form for making modifications to the
+Licensed Software and/or Your Extensions, including all modules contained
+therein, plus any associated text, interface definition files, scripts used to
+control compilation and installation of an executable program or other
+components required by a third party of average skill to build a running
+version of the Licensed Software or Your Extensions.
+
+1.17 "User-Visible Attribution Notice" means any notice contained in EXHIBIT B.
+
+1.18 "You" or "Your" means an individual or a legal entity exercising rights
+under this License. For legal entities, "You" or "Your" includes any entity
+which controls, is controlled by, or is under common control with, You, where
+"control" means (a) the power, direct or indirect, to cause the direction or
+management of such entity, whether by contract or otherwise, or (b) ownership
+of fifty percent (50%) or more of the outstanding shares or beneficial
+ownership of such entity.
+
+2.0 Acceptance Of License. You are not required to accept this License since
+you have not signed it, however nothing else grants you permission to use,
+copy, distribute, modify, or create derivatives of either the Software or any
+Extensions created by a Contributor. These actions are prohibited by law if
+you do not accept this License. Therefore, by performing any of these actions
+You indicate Your acceptance of this License and Your agreement to be bound by
+all its terms and conditions. IF YOU DO NOT AGREE WITH ALL THE TERMS AND
+CONDITIONS OF THIS LICENSE DO NOT USE, MODIFY, CREATE DERIVATIVES, OR
+DISTRIBUTE THE SOFTWARE. IF IT IS IMPOSSIBLE FOR YOU TO COMPLY WITH ALL THE
+TERMS AND CONDITIONS OF THIS LICENSE THEN YOU CAN NOT USE, MODIFY, CREATE
+DERIVATIVES, OR DISTRIBUTE THE SOFTWARE.
+
+3.0 Grant of License From Licensor. Subject to the terms and conditions of
+this License, Licensor hereby grants You a world-wide, royalty-free, non-
+exclusive license, subject to Licensor's intellectual property rights, and any
+third party intellectual property claims derived from the Licensed Software
+under this License, to do the following:
+
+3.1 Use, reproduce, modify, display, perform, sublicense and distribute
+Licensed Software and Your Extensions in both Source Code form or as an
+executable program.
+
+3.2 Create Derivative Works (as that term is defined under U.S. copyright law)
+of Licensed Software by adding to or deleting from the substance or structure
+of said Licensed Software.
+
+3.3 Under claims of patents now or hereafter owned or controlled by Licensor,
+to make, use, have made, and/or otherwise dispose of Licensed Software or
+portions thereof, but solely to the extent that any such claim is necessary to
+enable You to make, use, have made, and/or otherwise dispose of Licensed
+Software or portions thereof.
+
+3.4 Licensor reserves the right to release new versions of the Software with
+different features, specifications, capabilities, functions, licensing terms,
+general availability or other characteristics. Title, ownership rights, and
+intellectual property rights in and to the Licensed Software shall remain in
+Licensor and/or its Contributors.
+
+4.0 Grant of License From Contributor. By application of the provisions in
+Section 6 below, each Contributor hereby grants You a world-wide, royalty-
+free, non-exclusive license, subject to said Contributor's intellectual
+property rights, and any third party intellectual property claims derived from
+the Licensed Software under this License, to do the following:
+
+4.1 Use, reproduce, modify, display, perform, sublicense and distribute any
+Extensions Deployed by such Contributor or portions thereof, in both Source
+Code form or as an executable program, either on an unmodified basis or as
+part of Derivative Works.
+
+4.2 Under claims of patents now or hereafter owned or controlled by
+Contributor, to make, use, have made, and/or otherwise dispose of Extensions
+or portions thereof, but solely to the extent that any such claim is necessary
+to enable You to make, use, have made, and/or otherwise dispose of
+Licensed Software or portions thereof.
+
+5.0 Exclusions From License Grant. Nothing in this License shall be deemed to
+grant any rights to trademarks, copyrights, patents, trade secrets or any
+other intellectual property of Licensor or any Contributor except as expressly
+stated herein. Except as expressly stated in Sections 3 and 4, no other patent
+rights, express or implied, are granted herein. Your Extensions may require
+additional patent licenses from Licensor or Contributors which each may grant
+in its sole discretion. No right is granted to the trademarks of Licensor or
+any Contributor even if such marks are included in the Licensed Software.
+Nothing in this License shall be interpreted to prohibit Licensor from
+licensing under different terms from this License any code that Licensor
+otherwise would have a right to license.
+
+5.1 You expressly acknowledge and agree that although Licensor and each
+Contributor grants the licenses to their respective portions of the Licensed
+Software set forth herein, no assurances are provided by Licensor or any
+Contributor that the Licensed Software does not infringe the patent or other
+intellectual property rights of any other entity. Licensor and each
+Contributor disclaim any liability to You for claims brought by any other
+entity based on infringement of intellectual property rights or otherwise. As
+a condition to exercising the rights and licenses granted hereunder, You
+hereby assume sole responsibility to secure any other intellectual property
+rights needed, if any. For example, if a third party patent license is
+required to allow You to distribute the Licensed Software, it is Your
+responsibility to acquire that license before distributing the Licensed
+Software.
+
+6.0 Your Obligations And Grants. In consideration of, and as an express
+condition to, the licenses granted to You under this License You hereby agree
+that any Modifications, Derivative Works, or Required Components (collectively
+Extensions) that You create or to which You contribute are governed by the
+terms of this License including, without limitation, Section 4. Any Extensions
+that You create or to which You contribute must be Deployed under the terms of
+this License or a future version of this License released under Section 7. You
+hereby grant to Licensor and all third parties a world-wide, non-exclusive,
+royalty-free license under those intellectual property rights You own or
+control to use, reproduce, display, perform, modify, create derivatives,
+sublicense, and distribute Licensed Software, in any form. Any Extensions You
+make and Deploy must have a distinct title so as to readily tell any
+subsequent user or Contributor that the Extensions are by You. You must
+include a copy of this License or directions on how to obtain a copy with
+every copy of the Extensions You distribute. You agree not to offer or impose
+any terms on any Source Code or executable version of the Licensed Software,
+or its Extensions that alter or restrict the applicable version of this
+License or the recipients' rights hereunder.
+
+6.1 Availability of Source Code. You must make available, under the terms of
+this License, the Source Code of any Extensions that You Deploy, via an
+Electronic Distribution Mechanism. The Source Code for any version that You
+Deploy must be made available within one (1) month of when you Deploy and must
+remain available for no less than twelve (12) months after the date You cease
+to Deploy. You are responsible for ensuring that the Source Code to each
+version You Deploy remains available even if the Electronic Distribution
+Mechanism is maintained by a third party. You may not charge a fee for any
+copy of the Source Code distributed under this Section in excess of Your
+actual cost of duplication and distribution of said copy.
+
+6.2 Description of Modifications. You must cause any Modifications that You
+create or to which You contribute to be documented in the Source Code, clearly
+describing the additions, changes or deletions You made. You must include a
+prominent statement that the Modifications are derived, directly or indirectly,
+from the Licensed Software and include the names of the Licensor and any
+Contributor to the Licensed Software in (i) the Source Code and (ii) in any
+notice displayed by the Licensed Software You distribute or in related
+documentation in which You describe the origin or ownership of the Licensed
+Software. You may not modify or delete any pre-existing copyright notices,
+change notices or License text in the Licensed Software without written
+permission of the respective Licensor or Contributor.
+
+6.3 Intellectual Property Matters.
+
+a. Third Party Claims. If You have knowledge that a license to a third party's
+intellectual property right is required to exercise the rights granted by this
+License, You must include a human-readable file with Your distribution that
+describes the claim and the party making the claim in sufficient detail that a
+recipient will know whom to contact.
+
+b. Contributor APIs. If Your Extensions include an application programming
+interface ("API") and You have knowledge of patent licenses that are
+reasonably necessary to implement that API, You must also include this
+information in a human-readable file supplied with Your distribution.
+
+c. Representations. You represent that, except as disclosed pursuant to 6.3(a)
+above, You believe that any Extensions You distribute are Your original
+creations and that You have sufficient rights to grant the rights conveyed by
+this License.
+
+6.4 Required Notices.
+
+a. License Text. You must duplicate this License or instructions on how to
+acquire a copy in any documentation You provide along with the Source Code of
+any Extensions You create or to which You contribute, wherever You describe
+recipients' rights relating to Licensed Software.
+
+b. License Notice. You must duplicate any notice contained in EXHIBIT A (the
+"License Notice") in each file of the Source Code of any copy You distribute
+of the Licensed Software and Your Extensions. If You create an Extension, You
+may add Your name as a Contributor to the Source Code and accompanying
+documentation along with a description of the contribution. If it is not
+possible to put the License Notice in a particular Source Code file due to its
+structure, then You must include such License Notice in a location where a
+user would be likely to look for such a notice.
+
+c. Source Code Availability. You must notify the software community of the
+availability of Source Code to Your Extensions within one (1) month of the date
+You initially Deploy and include in such notification a description of the
+Extensions, and instructions on how to acquire the Source Code. Should such
+instructions change you must notify the software community of revised
+instructions within one (1) month of the date of change. You must provide
+notification by posting to appropriate news groups, mailing lists, weblogs, or
+other sites where a publicly accessible search engine would reasonably be
+expected to index your post in relationship to queries regarding the Licensed
+Software and/or Your Extensions.
+
+d. User-Visible Attribution. You must duplicate any notice contained in
+EXHIBIT B (the "User-Visible Attribution Notice") in each user-visible display
+of the Licensed Software and Your Extensions which delineates copyright,
+ownership, or similar attribution information. If You create an Extension,
+You may add Your name as a Contributor, and add Your attribution notice, as an
+equally visible and functional element of any User-Visible Attribution Notice
+content. To ensure proper attribution, You must also include such User-Visible
+Attribution Notice in at least one location in the Software documentation
+where a user would be likely to look for such notice.
+
+6.5 Additional Terms. You may choose to offer, and charge a fee for, warranty,
+support, indemnity or liability obligations to one or more recipients of
+Licensed Software. However, You may do so only on Your own behalf, and not on
+behalf of the Licensor or any Contributor except as permitted under other
+agreements between you and Licensor or Contributor. You must make it clear that
+any such warranty, support, indemnity or liability obligation is offered by You
+alone, and You hereby agree to indemnify the Licensor and every Contributor for
+any liability plus attorney fees, costs, and related expenses due to any such
+action or claim incurred by the Licensor or such Contributor as a result of
+warranty, support, indemnity or liability terms You offer.
+
+6.6 Conflicts With Other Licenses. Where any portion of Your Extensions, by
+virtue of being Derivative Works of another product or similar circumstance,
+fall under the terms of another license, the terms of that license should be
+honored however You must also make Your Extensions available under this
+License. If the terms of this License continue to conflict with the terms of
+the other license you may write the Licensor for permission to resolve the
+conflict in a fashion that remains consistent with the intent of this License.
+Such permission will be granted at the sole discretion of the Licensor.
+
+7.0 Versions of This License. Licensor may publish from time to time revised
+versions of the License. Once Licensed Software has been published under a
+particular version of the License, You may always continue to use it under the
+terms of that version. You may also choose to use such Licensed Software under
+the terms of any subsequent version of the License published by Licensor. No
+one other than Licensor has the right to modify the terms applicable to
+Licensed Software created under this License.
+
+7.1 If You create or use a modified version of this License, which You may do
+only in order to apply it to software that is not already Licensed Software
+under this License, You must rename Your license so that it is not confusingly
+similar to this License, and must make it clear that Your license contains
+terms that differ from this License. In so naming Your license, You may not
+use any trademark of Licensor or of any Contributor. Should Your modifications
+to this License be limited to alteration of a) Section 13.8 solely to modify
+the legal Jurisdiction or Venue for disputes, b) EXHIBIT A solely to define
+License Notice text, or c) to EXHIBIT B solely to define a User-Visible
+Attribution Notice, You may continue to refer to Your License as the
+Reciprocal Public License or simply the RPL.
+
+8.0 Disclaimer of Warranty. LICENSED SOFTWARE IS PROVIDED UNDER THIS LICENSE
+ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
+INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE LICENSED SOFTWARE IS FREE
+OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+FURTHER THERE IS NO WARRANTY MADE AND ALL IMPLIED WARRANTIES ARE DISCLAIMED
+THAT THE LICENSED SOFTWARE MEETS OR COMPLIES WITH ANY DESCRIPTION OF
+PERFORMANCE OR OPERATION, SAID COMPATIBILITY AND SUITABILITY BEING YOUR
+RESPONSIBILITY. LICENSOR DISCLAIMS ANY WARRANTY, IMPLIED OR EXPRESSED, THAT
+ANY CONTRIBUTOR'S EXTENSIONS MEET ANY STANDARD OF COMPATIBILITY OR DESCRIPTION
+OF PERFORMANCE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LICENSED SOFTWARE IS WITH YOU. SHOULD LICENSED SOFTWARE PROVE DEFECTIVE IN ANY
+RESPECT, YOU (AND NOT THE LICENSOR OR ANY OTHER CONTRIBUTOR) ASSUME THE COST
+OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. UNDER THE TERMS OF THIS
+LICENSOR WILL NOT SUPPORT THIS SOFTWARE AND IS UNDER NO OBLIGATION TO ISSUE
+UPDATES TO THIS SOFTWARE. LICENSOR HAS NO KNOWLEDGE OF ERRANT CODE OR VIRUS IN
+THIS SOFTWARE, BUT DOES NOT WARRANT THAT THE SOFTWARE IS FREE FROM SUCH ERRORS
+OR VIRUSES. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS
+LICENSE. NO USE OF LICENSED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
+DISCLAIMER.
+
+9.0 Limitation of Liability. UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+LICENSOR, ANY CONTRIBUTOR, OR ANY DISTRIBUTOR OF LICENSED SOFTWARE, OR ANY
+SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT,
+SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING,
+WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER
+FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES,
+EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH
+DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH
+OR PERSONAL INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
+APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS
+EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10.0 High Risk Activities. THE LICENSED SOFTWARE IS NOT FAULT-TOLERANT AND IS
+NOT DESIGNED, MANUFACTURED, OR INTENDED FOR USE OR DISTRIBUTION AS ON-LINE
+CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE PERFORMANCE,
+SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT NAVIGATION OR
+COMMUNICATIONS SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE SUPPORT MACHINES, OR
+WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE LICENSED SOFTWARE COULD LEAD
+DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+("HIGH RISK ACTIVITIES"). LICENSOR AND CONTRIBUTORS SPECIFICALLY DISCLAIM ANY
+EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.
+
+11.0 Responsibility for Claims. As between Licensor and Contributors, each
+party is responsible for claims and damages arising, directly or indirectly,
+out of its utilization of rights under this License which specifically
+disclaims warranties and limits any liability of the Licensor. This paragraph
+is to be used in conjunction with and controlled by the Disclaimer Of
+Warranties of Section 8, the Limitation Of Damages in Section 9, and the
+disclaimer against use for High Risk Activities in Section 10. The Licensor
+has thereby disclaimed all warranties and limited any damages that it is or
+may be liable for. You agree to work with Licensor and Contributors to
+distribute such responsibility on an equitable basis consistent with the terms
+of this License including Sections 8, 9, and 10. Nothing herein is intended or
+shall be deemed to constitute any admission of liability.
+
+12.0 Termination. This License and all rights granted hereunder will terminate
+immediately in the event of the circumstances described in Section 13.6 or if
+applicable law prohibits or restricts You from fully and or specifically
+complying with Sections 3, 4 and/or 6, or prevents the enforceability of any
+of those Sections, and You must immediately discontinue any use of Licensed
+Software.
+
+12.1 Automatic Termination Upon Breach. This License and the rights granted
+hereunder will terminate automatically if You fail to comply with the terms
+herein and fail to cure such breach within thirty (30) days of becoming aware
+of the breach. All sublicenses to the Licensed Software that are properly
+granted shall survive any termination of this License. Provisions that, by
+their nature, must remain in effect beyond the termination of this License,
+shall survive.
+
+12.2 Termination Upon Assertion of Patent Infringement. If You initiate
+litigation by asserting a patent infringement claim (excluding declaratory
+judgment actions) against Licensor or a Contributor (Licensor or Contributor
+against whom You file such an action is referred to herein as "Respondent")
+alleging that Licensed Software directly or indirectly infringes any patent,
+then any and all rights granted by such Respondent to You under Sections 3 or
+4 of this License shall terminate prospectively upon sixty (60) days notice
+from Respondent (the "Notice Period") unless within that Notice Period You
+either agree in writing (i) to pay Respondent a mutually agreeable reasonably
+royalty for Your past or future use of Licensed Software made by such
+Respondent, or (ii) withdraw Your litigation claim with respect to Licensed
+Software against such Respondent. If within said Notice Period a reasonable
+royalty and payment arrangement are not mutually agreed upon in writing by the
+parties or the litigation claim is not withdrawn, the rights granted by
+Licensor to You under Sections 3 and 4 automatically terminate at the
+expiration of said Notice Period.
+
+12.3 Reasonable Value of This License. If You assert a patent infringement
+claim against Respondent alleging that Licensed Software directly or
+indirectly infringes any patent where such claim is resolved (such as by
+license or settlement) prior to the initiation of patent infringement
+litigation, then the reasonable value of the licenses granted by said
+Respondent under Sections 3 and 4 shall be taken into account in determining
+the amount or value of any payment or license.
+
+12.4 No Retroactive Effect of Termination. In the event of termination under
+this Section all end user license agreements (excluding licenses to
+distributors and resellers) that have been validly granted by You or any
+distributor hereunder prior to termination shall survive termination.
+
+13.0 Miscellaneous.
+
+13.1 U.S. Government End Users. The Licensed Software is a "commercial item,"
+as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of
+"commercial computer software" and "commercial computer software
+documentation," as such terms are used in 48 C.F.R. 12.212 (Sept. 1995).
+Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4
+(June 1995), all U.S. Government End Users acquire Licensed Software with only
+those rights set forth herein.
+
+13.2 Relationship of Parties. This License will not be construed as creating
+an agency, partnership, joint venture, or any other form of legal association
+between or among You, Licensor, or any Contributor, and You will not represent
+to the contrary, whether expressly, by implication, appearance, or otherwise.
+
+13.3 Independent Development. Nothing in this License will impair Licensor's
+right to acquire, license, develop, subcontract, market, or distribute
+technology or products that perform the same or similar functions as, or
+otherwise compete with, Extensions that You may develop, produce, market, or
+distribute.
+
+13.4 Consent To Breach Not Waiver. Failure by Licensor or Contributor to
+enforce any provision of this License will not be deemed a waiver of future enforcement
+of that or any other provision.
+
+13.5 Severability. This License represents the complete agreement concerning
+the subject matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent necessary
+to make it enforceable.
+
+13.6 Inability to Comply Due to Statute or Regulation. If it is impossible for
+You to comply with any of the terms of this License with respect to some or
+all of the Licensed Software due to statute, judicial order, or regulation,
+then You cannot use, modify, or distribute the software.
+
+13.7 Export Restrictions. You may be restricted with respect to downloading or
+otherwise acquiring, exporting, or reexporting the Licensed Software or any
+underlying information or technology by United States and other applicable
+laws and regulations. By downloading or by otherwise obtaining the Licensed
+Software, You are agreeing to be responsible for compliance with all
+applicable laws and regulations.
+
+13.8 Arbitration, Jurisdiction & Venue. This License shall be governed by
+Colorado law provisions (except to the extent applicable law, if any, provides
+otherwise), excluding its conflict-of-law provisions. You expressly agree that
+any dispute relating to this License shall be submitted to binding arbitration
+under the rules then prevailing of the American Arbitration Association. You
+further agree that Adams County, Colorado USA is proper venue and grant such
+arbitration proceeding jurisdiction as may be appropriate for purposes of
+resolving any dispute under this License. Judgement upon any award made in
+arbitration may be entered and enforced in any court of competent
+jurisdiction. The arbitrator shall award attorney's fees and costs of
+arbitration to the prevailing party. Should either party find it necessary to
+enforce its arbitration award or seek specific performance of such award in a
+civil court of competent jurisdiction, the prevailing party shall be entitled
+to reasonable attorney's fees and costs. The application of the United Nations
+Convention on Contracts for the International Sale of Goods is expressly
+excluded. You and Licensor expressly waive any rights to a jury trial in any
+litigation concerning Licensed Software or this License. Any law or regulation
+that provides that the language of a contract shall be construed against the
+drafter shall not apply to this License.
+
+13.9 Entire Agreement. This License constitutes the entire agreement between
+the parties with respect to the subject matter hereof.
+
+EXHIBIT A
+
+The License Notice below must appear in each file of the Source Code of any
+copy You distribute of the Licensed Software or any Extensions thereto:
+
+ Unless explicitly acquired and licensed from Licensor under another
+ license, the contents of this file are subject to the Reciprocal Public
+ License ("RPL") Version 1.5, or subsequent versions as allowed by the RPL,
+ and You may not copy or use this file in either source code or executable
+ form, except in compliance with the terms and conditions of the RPL.
+
+ All software distributed under the RPL is provided strictly on an "AS
+ IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND
+ LICENSOR HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
+ LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific
+ language governing rights and limitations under the RPL.
+
+
+
+EXHIBIT B
+
+The User-Visible Attribution Notice below, when provided, must appear in each
+user-visible display as defined in Section 6.4 (d):
\ No newline at end of file
diff --git a/instafeed/vendor/mgp25/instagram-php/LICENSE_PREMIUM b/instafeed/vendor/mgp25/instagram-php/LICENSE_PREMIUM
new file mode 100755
index 0000000..ed45a9e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/LICENSE_PREMIUM
@@ -0,0 +1,16 @@
+The Premium license applies to all entities which have been granted "premium"
+status, and lasts for the duration of their premium status:
+
+All terms of the Reciprocal Public License 1.5 (RPL-1.5) still apply,
+but with the following modifications:
+
+- You are NOT required to disclose your own source code publicly, which
+ means that this premium license is *perfect* for website/app builders.
+
+- You are NOT required to credit this library anywhere in your own projects.
+ (But if you are re-distributing OUR source code, you must of course still
+ retain all of our copyright notices, and not claim our code as your own.)
+
+- You are encouraged (but NOT required) to contribute your library
+ improvements/modifications back to us. We would appreciate it, and may
+ even grant you premium status if the contributions are high-quality!
\ No newline at end of file
diff --git a/instafeed/vendor/mgp25/instagram-php/README.md b/instafeed/vendor/mgp25/instagram-php/README.md
new file mode 100755
index 0000000..16ab30b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/README.md
@@ -0,0 +1,135 @@
+#  Instagram-API [](https://packagist.org/packages/mgp25/instagram-php) [](https://packagist.org/packages/mgp25/instagram-php)  [](https://packagist.org/packages/mgp25/instagram-php)
+
+This is a PHP library which emulates Instagram's Private API. This library is packed full with almost all the features from the Instagram Android App. This includes media uploads, direct messaging, stories and more.
+
+**Read the [wiki](https://github.com/mgp25/Instagram-API/wiki)** and previous issues before opening a new one! Maybe your issue has already been answered.
+
+**Frequently Asked Questions:** [F.A.Q.](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+
+**Do you like this project? Support it by donating**
+
+**mgp25**
+
+-  Paypal: [Donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5ATYY8H9MC96E)
+-  Bitcoin: 1DCEpC9wYXeUGXS58qSsqKzyy7HLTTXNYe
+
+**stevejobzniak**
+
+-  Paypal: [Donate](https://www.paypal.me/Armindale/0usd)
+-  Bitcoin: 18XF1EmrkpYi4fqkR2XcHkcJxuTMYG4bcv
+
+**jroy**
+
+-  Paypal: [Donate](https://www.paypal.me/JoshuaRoy1/0usd)
+-  Bitcoin: 32J2AqJBDY1VLq6wfZcLrTYS8fCcHHVDKD
+
+----------
+## Installation
+
+### Dependencies
+
+Install/enable the required php extensions and dependencies. You can learn how to do so [here](https://github.com/mgp25/Instagram-API/wiki/Dependencies).
+
+### Install this library
+We use composer to distribute our code effectively and easily. If you do not already have composer installed, you can download and install it here [here](https://getcomposer.org/download/).
+
+Once you have composer installed, you can do the following:
+```sh
+composer require mgp25/instagram-php
+```
+
+```php
+require __DIR__.'/../vendor/autoload.php';
+
+$ig = new \InstagramAPI\Instagram();
+```
+
+If you want to test new and possibly unstable code that is in the master branch, and which hasn't yet been released, then you can do the following (at your own risk):
+
+```sh
+composer require mgp25/instagram-php dev-master
+```
+
+#### _Warning about moving data to a different server_
+
+_Composer checks your system's capabilities and selects libraries based on your **current** machine (where you are running the `composer` command). So if you run Composer on machine `A` to install this library, it will check machine `A`'s capabilities and will install libraries appropriate for that machine (such as installing the PHP 7+ versions of various libraries). If you then move your whole installation to machine `B` instead, it **will not work** unless machine `B` has the **exact** same capabilities (same or higher PHP version and PHP extensions)! Therefore, you should **always** run the Composer-command on your intended target machine instead of your local machine._
+
+## Examples
+
+All examples can be found [here](https://github.com/mgp25/Instagram-API/tree/master/examples).
+
+## Code of Conduct
+
+This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
+By participating, you are expected to uphold this code.
+Please report any unacceptable behavior.
+
+## How do I contribute
+
+If you would like to contribute to this project, please feel free to submit a pull request.
+
+Before you do, take a look at the [contributing guide](https://github.com/mgp25/Instagram-API/blob/master/CONTRIBUTING.md).
+
+## Why did I make this API?
+
+After legal measures, Facebook, WhatsApp and Instagram blocked my accounts.
+In order to use Instagram on my phone I needed a new phone, as they banned my UDID, so that is basically why I made this API.
+
+### What is Instagram?
+According to [the company](https://instagram.com/about/faq/):
+
+> "Instagram is a fun and quirky way to share your life with friends through a series of pictures. Snap a photo with your mobile phone, then choose a filter to transform the image into a memory to keep around forever. We're building Instagram to allow you to experience moments in your friends' lives through pictures as they happen. We imagine a world more connected through photos."
+
+# License
+
+In order help ensure fairness and sharing, this library is dual-licensed. Be
+aware that _all_ usage, unless otherwise specified, is under the **RPL-1.5**
+license!
+
+- Reciprocal Public License 1.5 (RPL-1.5): https://opensource.org/licenses/RPL-1.5
+
+You should read the _entire_ license; especially the `PREAMBLE` at the
+beginning. In short, the word `reciprocal` means "giving something back in
+return for what you are getting". It is _**not** a freeware license_. This
+license _requires_ that you open-source _all_ of your own source code for _any_
+project which uses this library! Creating and maintaining this library is
+endless hard work for us. That's why there is _one_ simple requirement for you:
+Give _something_ back to the world. Whether that's code _or_ financial support
+for this project is entirely up to you, but _nothing else_ grants you _any_
+right to use this library.
+
+Furthermore, the library is _also_ available _to certain entities_ under a
+modified version of the RPL-1.5, which has been modified to allow you to use the
+library _without_ open-sourcing your own project. The modified license
+(see [LICENSE_PREMIUM](https://github.com/mgp25/Instagram-API/blob/master/LICENSE_PREMIUM))
+is granted to certain entities, at _our_ discretion, and for a _limited_ period
+of time (unless otherwise agreed), pursuant to our terms. Currently, we are
+granting this license to all
+"[premium subscribers](https://github.com/mgp25/Instagram-API/issues/2655)" for
+the duration of their subscriptions. You can become a premium subscriber by
+either contributing substantial amounts of high-quality code, or by subscribing
+for a fee. This licensing ensures fairness and stimulates the continued growth
+of this library through both code contributions and the financial support it
+needs.
+
+You are not required to accept this License since you have not signed it,
+however _nothing else_ grants you permission to _use_, copy, distribute, modify,
+or create derivatives of either the Software (this library) or any Extensions
+created by a Contributor. These actions are prohibited by law if you do not
+accept this License. Therefore, by performing any of these actions You indicate
+Your acceptance of this License and Your agreement to be bound by all its terms
+and conditions. IF YOU DO NOT AGREE WITH ALL THE TERMS AND CONDITIONS OF THIS
+LICENSE DO NOT USE, MODIFY, CREATE DERIVATIVES, OR DISTRIBUTE THE SOFTWARE. IF
+IT IS IMPOSSIBLE FOR YOU TO COMPLY WITH ALL THE TERMS AND CONDITIONS OF THIS
+LICENSE THEN YOU CAN NOT USE, MODIFY, CREATE DERIVATIVES, OR DISTRIBUTE THE
+SOFTWARE.
+
+# Terms and conditions
+
+- You will NOT use this API for marketing purposes (spam, botting, harassment, massive bulk messaging...).
+- We do NOT give support to anyone who wants to use this API to send spam or commit other crimes.
+- We reserve the right to block any user of this repository that does not meet these conditions.
+
+## Legal
+
+This code is in no way affiliated with, authorized, maintained, sponsored or endorsed by Instagram or any of its affiliates or subsidiaries. This is an independent and unofficial API. Use at your own risk.
diff --git a/instafeed/vendor/mgp25/instagram-php/composer.json b/instafeed/vendor/mgp25/instagram-php/composer.json
new file mode 100755
index 0000000..166032e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/composer.json
@@ -0,0 +1,81 @@
+{
+ "name": "mgp25/instagram-api",
+ "description": "Instagram's private API for PHP",
+ "license": [
+ "RPL-1.5",
+ "proprietary"
+ ],
+ "keywords": [
+ "Instagram",
+ "Private",
+ "API",
+ "PHP"
+ ],
+ "support": {
+ "issues": "https://github.com/mgp25/Instagram-API/issues",
+ "wiki": "https://github.com/mgp25/Instagram-API/wiki",
+ "source": "https://github.com/mgp25/Instagram-API/"
+ },
+ "authors": [
+ {
+ "name": "mgp25",
+ "email": "me@mgp25.com",
+ "role": "Founder"
+ },
+ {
+ "name": "SteveJobzniak",
+ "homepage": "https://github.com/SteveJobzniak",
+ "role": "Developer"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "InstagramAPI\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "InstagramAPI\\Tests\\": "tests/"
+ }
+ },
+ "require": {
+ "php": ">=5.6",
+ "lazyjsonmapper/lazyjsonmapper": "^1.6.1",
+ "guzzlehttp/guzzle": "^6.2",
+ "ext-curl": "*",
+ "ext-mbstring": "*",
+ "ext-gd": "*",
+ "ext-exif": "*",
+ "ext-zlib": "*",
+ "ext-bcmath": "*",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "^2.5",
+ "react/socket": "^0.8",
+ "binsoul/net-mqtt-client-react": "^0.3.2",
+ "clue/socks-react": "^0.8.2",
+ "clue/http-proxy-react": "^1.1.0",
+ "psr/log": "^1.0",
+ "valga/fbns-react": "^0.1.8",
+ "symfony/process": "^3.4|^4.0",
+ "winbox/args": "1.0.0"
+ },
+ "suggest": {
+ "ext-event": "Installing PHP's native Event extension enables faster Realtime class event handling."
+ },
+ "require-dev": {
+ "react/http": "^0.7.2",
+ "friendsofphp/php-cs-fixer": "^2.11.0",
+ "monolog/monolog": "^1.23",
+ "phpunit/phpunit": "^5.7 || ^6.2"
+ },
+ "scripts": {
+ "codestyle": [
+ "lazydoctor -c composer.json -pfo",
+ "php-cs-fixer fix --config=.php_cs.dist --allow-risky yes",
+ "php devtools/checkStyle.php x"
+ ],
+ "test": [
+ "phpunit tests"
+ ]
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/devtools/README.md b/instafeed/vendor/mgp25/instagram-php/devtools/README.md
new file mode 100755
index 0000000..00449d3
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/devtools/README.md
@@ -0,0 +1,7 @@
+These resources are *ONLY for INTERNAL usage* by the Instagram-API library developer team.
+
+Do *NOT* ask us about them. Do *NOT* make support tickets about them. We will *ban* you.
+
+Using this library is a *privilege* and we do *not* have to explain everything to every newbie who stumbles upon our repository.
+
+If you want to join the team, then read for yourself what they do and try to figure it out on your own.
diff --git a/instafeed/vendor/mgp25/instagram-php/devtools/checkDevices.php b/instafeed/vendor/mgp25/instagram-php/devtools/checkDevices.php
new file mode 100755
index 0000000..c1a64d1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/devtools/checkDevices.php
@@ -0,0 +1,229 @@
+login($username, $password);
+
+// Code for building video lists (uncomment both lines).
+// $userPk = $ig->people->getInfoByName('selenagomez')->getUser()->getPk();
+// buildVideoList($ig, $userPk); exit;
+
+// List of good videos and resolutions that we MUST see with a GOOD user agent.
+// Manually created via buildVideoList() and the code above, by selecting some
+// high resolution videos from random profiles. If these are deleted in the
+// future or if Instagram adds support for wider than 640px videos, we'll need
+// to rebuild this test array with other test videos.
+$highResVideoIDs = [
+ '1451670806714625231_460563723' => [ // Selena Gomez portrait video.
+ ['width'=>640, 'height'=>799, 'url'=>'https://instagram.com/p/BQlXuhNB6zP/'],
+ ['width'=> 480, 'height'=>599, 'url'=>'https://instagram.com/p/BQlXuhNB6zP/'],
+ ['width'=> 480, 'height'=>599, 'url'=>'https://instagram.com/p/BQlXuhNB6zP/'],
+ ],
+ '1264771449117881030_460563723' => [ // Selena Gomez 16:9 landscape video.
+ ['width'=>640, 'height'=>360, 'url'=>'https://instagram.com/p/BGNXu6SujLG/'],
+ ['width'=> 480, 'height'=>270, 'url'=>'https://instagram.com/p/BGNXu6SujLG/'],
+ ['width'=> 480, 'height'=>270, 'url'=>'https://instagram.com/p/BGNXu6SujLG/'],
+ ],
+ '1435631390916900349_460563723' => [ // Selena Gomez max res square video.
+ ['width'=>640, 'height'=>640, 'url'=>'https://instagram.com/p/BPsYyTMAHX9/'],
+ ['width'=> 480, 'height'=>480, 'url'=>'https://instagram.com/p/BPsYyTMAHX9/'],
+ ['width'=> 480, 'height'=>480, 'url'=>'https://instagram.com/p/BPsYyTMAHX9/'],
+ ],
+];
+
+$testDevices = [];
+
+// Test all of our current, "good" devices too? We must do that periodically.
+$testGoodDevices = true;
+if ($testGoodDevices) {
+ foreach (GoodDevices::getAllGoodDevices() as $deviceString) {
+ $testDevices[] = $deviceString;
+ }
+}
+
+// Additional devices to test. Add these new devices in devicestring format.
+$testDevices = array_merge(
+ $testDevices,
+ [
+ // DEMO STRING: This is a low-res device which is NOT capable of
+ // getting HD video URLs in API replies. It's just here for developer
+ // demo purposes. Also note that it's actually caught by the Device()
+ // class' REFUSAL to construct from anything lower than 1920x1080
+ // devices, so we had to FAKE its resolution HERE just to get it to
+ // even be testable! Its real User Agent string is supposed to be:
+ //'17/4.2.2; 240dpi; 480x800; samsung; SM-G350; cs02; hawaii_ss_cs02',
+ // However, Instagram only checks the model identifier, in this case
+ // "SM-G350", and the test shows that this bad device lacks HD videos.
+ '17/4.2.2; 240dpi; 1080x1920; samsung; SM-G350; cs02; hawaii_ss_cs02',
+ ]
+);
+
+// Build all device objects before we even run the tests, just to catch
+// any "bad devicestring" construction errors before wasting time.
+foreach ($testDevices as $key => $deviceString) {
+ // Create the new Device object, without automatic fallbacks.
+ $testDevices[$key] = new Device(Constants::IG_VERSION, Constants::VERSION_CODE, Constants::USER_AGENT_LOCALE, $deviceString, false);
+}
+
+// Test all devices in our list!
+foreach ($testDevices as $thisDevice) {
+ switchDevice($ig, $thisDevice);
+
+ $currentAgent = $ig->device->getUserAgent();
+ echo "\n[{$currentAgent}]\n";
+
+ $isBadDevice = false;
+ foreach ($highResVideoIDs as $videoId => $expectedResolutions) {
+ $bestWidth = $expectedResolutions[0]['width'];
+ $bestHeight = $expectedResolutions[0]['height'];
+ echo "* Checking {$videoId} (should be: {$bestWidth}x{$bestHeight})...";
+
+ // Retrieve video info (with 4 total attempts in case of throttling).
+ for ($attempt = 1; $attempt <= 4; ++$attempt) {
+ try {
+ $mediaInfo = $ig->media->getInfo($videoId)->getItems()[0];
+ break;
+ } catch (\InstagramAPI\Exception\ThrottledException $e) {
+ if ($attempt < 4) {
+ sleep(10);
+ } else {
+ throw $e;
+ }
+ }
+ }
+
+ // Verify all video candidates.
+ $videoVersions = $mediaInfo->getVideoVersions();
+ if (count($videoVersions) !== count($expectedResolutions)) {
+ die('Wrong number of video candidate URLs for media item: '.$videoId);
+ }
+ $isBadVersions = false;
+ foreach ($videoVersions as $idx => $version) {
+ $thisWidth = $version->getWidth();
+ $thisHeight = $version->getHeight();
+ $expectedWidth = $expectedResolutions[$idx]['width'];
+ $expectedHeight = $expectedResolutions[$idx]['height'];
+ if ($thisWidth != $expectedWidth || $thisHeight != $expectedHeight) {
+ $isBadVersions = true;
+ break;
+ }
+ }
+ echo $isBadVersions ? "Bad response: {$thisWidth}x{$thisHeight}.\n" : "OK.\n";
+ if ($isBadVersions) {
+ $isBadDevice = true;
+ }
+ }
+
+ echo $isBadDevice ? "THIS DEVICE IS BAD AND DOES NOT SUPPORT HD VIDEOS!\n" : "DEVICE OK.\n";
+}
+
+// INTERNAL FUNCTIONS...
+
+/**
+ * Changes the user-agent sent by the InstagramAPI library.
+ *
+ * @param \InstagramAPI\Instagram $ig
+ * @param DeviceInterface|string $value
+ */
+function switchDevice(
+ $ig,
+ $value)
+{
+ // Create the new Device object, without automatic fallbacks.
+ $device = ($value instanceof DeviceInterface ? $value : new Device(Constants::IG_VERSION, Constants::VERSION_CODE, Constants::USER_AGENT_LOCALE, $value, false));
+
+ // Update the Instagram Client's User-Agent to the new Device.
+ $ig->device = $device;
+ $ig->client->updateFromCurrentSettings();
+}
+
+/**
+ * Checks a timeline and builds a list of all videos.
+ *
+ * Used for building the internal $highResVideoIDs test array.
+ *
+ * Instagram forces videos (timelines and stories) to be no wider than 640px.
+ *
+ * Max square video: 640x640. Nothing can ever be bigger than that if square.
+ *
+ * Max landscape video: 640xDEPENDS ON PHONE (for example 640x360 (16:9 videos)).
+ * In landscape videos, height is always < 640.
+ *
+ * Max portrait video: 640xDEPENDS ON PHONE (for example 640x799, 640x1136 (16:9)).
+ * In portrait, height IS > 640 (anything less would be a square/landscape).
+ *
+ * So Instagram lets people post HD resolutions in PORTRAIT, but only SD videos
+ * in landscape... This actually leads to a funny realization: To post HD videos
+ * with a high HD resolution, you must rotate them so they're always portrait.
+ *
+ * Also note that Instagram allows UPLOADING videos up to 1080 pixels WIDE. But
+ * they will RESIZE them to no more than 640 pixels WIDE. Perhaps they will
+ * someday allow 1080-width playback, in which case we will have to test and
+ * revise all device identifiers again to make sure they all see the best URLs.
+ *
+ * @param \InstagramAPI\Instagram $ig
+ * @param string $userPk
+ */
+function buildVideoList(
+ $ig,
+ $userPk)
+{
+ // We must use a good device to get answers when scanning for HD videos.
+ switchDevice($ig, GoodDevices::getRandomGoodDevice());
+
+ echo "[\n";
+ $maxId = null;
+ do {
+ $feed = $ig->timeline->getUserFeed($userPk, $maxId);
+ foreach ($feed->getItems() as $item) {
+ if ($item->getMediaType() != \InstagramAPI\Response\Model\Item::VIDEO) {
+ continue;
+ }
+
+ $code = \InstagramAPI\InstagramID::toCode($item->getPk());
+ echo sprintf(" '%s' => [\n", $item->getId());
+ $videoVersions = $item->getVideoVersions();
+ foreach ($videoVersions as $version) {
+ echo sprintf(
+ " ['width'=>%d, 'height'=>%d, 'url'=>'https://instagram.com/p/%s/'],\n",
+ $version->getWidth(), $version->getHeight(), $code
+ );
+ }
+ echo " ],\n";
+ }
+ $maxId = $feed->getNextMaxId();
+ } while ($maxId !== null);
+ echo "]\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/devtools/checkExperiments.php b/instafeed/vendor/mgp25/instagram-php/devtools/checkExperiments.php
new file mode 100755
index 0000000..b3b8f67
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/devtools/checkExperiments.php
@@ -0,0 +1,150 @@
+run();
+if (!empty($problemFiles)) {
+ // Exit with non-zero code to signal that there are problems.
+ exit(1);
+}
+
+class checkExperiments
+{
+ /**
+ * @var string
+ */
+ private $_baseDir;
+
+ /**
+ * @var string[]
+ */
+ private $_inspectFolders;
+
+ /**
+ * @var bool
+ */
+ private $_onlyShowInvalidFiles;
+
+ /**
+ * @var array
+ */
+ private $_experimentWhitelist;
+
+ /**
+ * Constructor.
+ *
+ * @param string $baseDir
+ * @param string[] $inspectFolders
+ * @param bool $onlyShowInvalidFiles
+ */
+ public function __construct(
+ $baseDir,
+ array $inspectFolders,
+ $onlyShowInvalidFiles)
+ {
+ $this->_baseDir = realpath($baseDir);
+ if ($this->_baseDir === false) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid path.', $baseDir));
+ }
+ $this->_inspectFolders = $inspectFolders;
+ $this->_onlyShowInvalidFiles = $onlyShowInvalidFiles;
+ $this->_experimentWhitelist = \InstagramAPI\Settings\StorageHandler::EXPERIMENT_KEYS;
+ }
+
+ /**
+ * Process single file.
+ *
+ * @param string $filePath
+ *
+ * @return bool TRUE if the file uses an un-whitelisted experiment, otherwise FALSE.
+ */
+ private function _processFile(
+ $filePath)
+ {
+ $hasProblems = false;
+ $processedExperiments = [];
+ $inputLines = @file($filePath);
+
+ if ($inputLines === false) {
+ // We were unable to read the input file. Ignore if broken symlink.
+ if (is_link($filePath)) {
+ return false; // File is okay, since the symlink is invalid.
+ } else {
+ echo "- {$filePath}: UNABLE TO READ FILE!".PHP_EOL;
+
+ return true; // File has problems...
+ }
+ }
+
+ if (preg_match_all('/->(?:getExperimentParam|isExperimentEnabled)\((?:\n {1,})?(?:\'|")(\w{1,})(?:\'|")/', implode('', $inputLines), $matches)) {
+ foreach ($matches[1] as $match) {
+ $experimentName = $match;
+ if (!strpos($experimentName, 'Experiment') !== false && !in_array($experimentName, $processedExperiments)) {
+ array_push($processedExperiments, $match);
+ if (in_array($experimentName, $this->_experimentWhitelist)) {
+ if (!$this->_onlyShowInvalidFiles) {
+ echo " {$filePath}: Uses whitelisted experiment: {$experimentName}.\n";
+ }
+ } else {
+ $hasProblems = true;
+ echo "- {$filePath}: Uses un-whitelisted experiment: {$experimentName}.\n";
+ }
+ }
+ }
+ }
+
+ return $hasProblems;
+ }
+
+ /**
+ * Process all *.php files in given path.
+ *
+ * @return string[] An array with all files that have un-whitelisted experiment usage.
+ */
+ public function run()
+ {
+ $filesWithProblems = [];
+ foreach ($this->_inspectFolders as $inspectFolder) {
+ $directoryIterator = new RecursiveDirectoryIterator($this->_baseDir.'/'.$inspectFolder);
+ $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
+ $phpIterator = new RegexIterator($recursiveIterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
+
+ foreach ($phpIterator as $filePath => $dummy) {
+ $hasProblems = $this->_processFile($filePath);
+ if ($hasProblems) {
+ $filesWithProblems[] = $filePath;
+ }
+ }
+ }
+
+ return $filesWithProblems;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/devtools/checkStyle.php b/instafeed/vendor/mgp25/instagram-php/devtools/checkStyle.php
new file mode 100755
index 0000000..aa97ca6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/devtools/checkStyle.php
@@ -0,0 +1,203 @@
+run();
+if (!empty($badFiles)) {
+ // Exit with non-zero code to signal that there are problems.
+ exit(1);
+}
+
+class styleChecker
+{
+ /**
+ * @var string
+ */
+ private $_baseDir;
+
+ /**
+ * @var string[]
+ */
+ private $_inspectFolders;
+
+ /**
+ * @var bool
+ */
+ private $_onlyShowInvalidFiles;
+
+ /**
+ * Constructor.
+ *
+ * @param string $baseDir
+ * @param string[] $inspectFolders
+ * @param bool $onlyShowInvalidFiles
+ */
+ public function __construct(
+ $baseDir,
+ array $inspectFolders,
+ $onlyShowInvalidFiles)
+ {
+ $this->_baseDir = realpath($baseDir);
+ if ($this->_baseDir === false) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid path.', $baseDir));
+ }
+ $this->_inspectFolders = $inspectFolders;
+ $this->_onlyShowInvalidFiles = $onlyShowInvalidFiles;
+ }
+
+ /**
+ * Process single file.
+ *
+ * @param string $filePath
+ *
+ * @return bool TRUE if the file has codestyle problems, otherwise FALSE.
+ */
+ private function _processFile(
+ $filePath)
+ {
+ $hasProblems = false;
+ $hasVisibilityProblems = false;
+ $fileName = basename($filePath);
+ $inputLines = @file($filePath);
+ $outputLines = [];
+
+ if ($inputLines === false) {
+ // We were unable to read the input file. Ignore if broken symlink.
+ if (is_link($filePath)) {
+ return false; // File is okay, since the symlink is invalid.
+ } else {
+ echo "- {$filePath}: UNABLE TO READ FILE!".PHP_EOL;
+
+ return true; // File has problems...
+ }
+ }
+
+ foreach ($inputLines as $line) {
+ // Function arguments on separate lines.
+ if (preg_match('/^(.*?(?:(?:final|static)\s+)*(?:public|private|protected)(?:\s+(?:final|static))*\s+function\s+.+?)\((.+)\)(.*)$/', $line, $matches)) {
+ $hasProblems = true;
+
+ $funcstart = $matches[1];
+ $params = $matches[2];
+ $trail = $matches[3];
+ $params = explode(', ', $params);
+
+ $outputLines[] = $funcstart.'('.PHP_EOL;
+ for ($i = 0, $len = count($params); $i < $len; ++$i) {
+ $newline = ' '.$params[$i];
+ if ($i == ($len - 1)) {
+ $newline .= ')'.PHP_EOL;
+ } else {
+ $newline .= ','.PHP_EOL;
+ }
+ $outputLines[] = $newline;
+ }
+ // } else {
+ // $outputLines[] = $line;
+ }
+
+ // Appropriate public, private and protected member prefixes.
+ if (preg_match('/^\s*(?:(?:final|static)\s+)*(public|private|protected)(?:\s+(?:final|static))*\s+(function|\$)\s*&?([^;\(\s]+)/', $line, $matches)) {
+ $visibility = &$matches[1]; // public, private, protected
+ $type = &$matches[2]; // $, function
+ $name = &$matches[3]; // Member name
+
+ if ($visibility == 'public') {
+ if ($name[0] == '_' && (
+ $name != '__construct'
+ && $name != '__destruct'
+ && $name != '__call'
+ && $name != '__get'
+ && $name != '__set'
+ && $name != '__isset'
+ && $name != '__unset'
+ && $name != '__invoke'
+ && $name != '__toString'
+ )) {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PUBLIC NAME:".trim($matches[0]).PHP_EOL;
+ }
+ } else { // private, protected
+ if ($name[0] != '_') {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PRIVATE/PROTECTED NAME:".trim($matches[0]).PHP_EOL;
+ }
+ }
+ }
+ }
+
+ $newFile = implode('', $outputLines);
+ if (!$hasProblems) {
+ if (!$this->_onlyShowInvalidFiles) {
+ echo " {$filePath}: Already formatted correctly.\n";
+ }
+ } elseif (!$hasVisibilityProblems) {
+ // Has problems, but no visibility problems. Output fixed file.
+ echo "- {$filePath}: Has function parameter problems:\n";
+ echo $newFile;
+ } else {
+ echo "- {$filePath}: Had member visibility problems.\n";
+ }
+
+ return $hasProblems;
+ }
+
+ /**
+ * Process all *.php files in given path.
+ *
+ * @return string[] An array with all files that have codestyle problems.
+ */
+ public function run()
+ {
+ $filesWithProblems = [];
+ foreach ($this->_inspectFolders as $inspectFolder) {
+ $directoryIterator = new RecursiveDirectoryIterator($this->_baseDir.'/'.$inspectFolder);
+ $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
+ $phpIterator = new RegexIterator($recursiveIterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
+
+ foreach ($phpIterator as $filePath => $dummy) {
+ $hasProblems = $this->_processFile($filePath);
+ if ($hasProblems) {
+ $filesWithProblems[] = $filePath;
+ }
+ }
+ }
+
+ return $filesWithProblems;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/devtools/testInstagramPhoto.php b/instafeed/vendor/mgp25/instagram-php/devtools/testInstagramPhoto.php
new file mode 100755
index 0000000..c0e9bb0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/devtools/testInstagramPhoto.php
@@ -0,0 +1,66 @@
+getFile(), $outputFile)) {
+ throw new \RuntimeException(sprintf('Failed to write to "%s".', $outputFile));
+ }
+}
+
+// Ensure that we have the JPEG orientation test images.
+$testImageFolder = __DIR__.'/exif-orientation-examples/';
+if (!is_dir($testImageFolder)) {
+ echo "* Downloading test images:\n";
+ exec('git clone https://github.com/recurser/exif-orientation-examples.git '.escapeshellarg($testImageFolder));
+}
+
+// Process all of the test images.
+$files = [];
+foreach (['Landscape', 'Portrait'] as $orientation) {
+ for ($imgNum = 1; $imgNum <= 8; ++$imgNum) {
+ $fileName = sprintf('%s_%d.jpg', $orientation, $imgNum);
+ $inputFile = sprintf('%s/%s', $testImageFolder, $fileName);
+ if (!is_file($inputFile)) {
+ die('Error: Missing "'.$inputFile.'".');
+ }
+
+ // Run the cropping test...
+ $outputFile = sprintf('%s/result_crop_%s', $testImageFolder, $fileName);
+ runTest($inputFile, $outputFile, [
+ 'forceAspectRatio' => 1.0, // Square.
+ 'horCropFocus' => -35, // Always use the same focus, to look for orientation errors.
+ 'verCropFocus' => -35, // This combo aims at the upper left corner.
+ 'operation' => InstagramMedia::CROP,
+ ]);
+
+ // Run the expansion test...
+ $outputFile = sprintf('%s/result_expand_%s', $testImageFolder, $fileName);
+ runTest($inputFile, $outputFile, [
+ 'forceAspectRatio' => 1.0, // Square.
+ 'operation' => InstagramMedia::EXPAND,
+ ]);
+ }
+}
+
+echo "\n\nAll images have been processed. Manually review the results to ensure they're all correctly processed.\n";
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/README.md b/instafeed/vendor/mgp25/instagram-php/examples/README.md
new file mode 100755
index 0000000..ce3d447
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/README.md
@@ -0,0 +1,17 @@
+## Examples
+
+These examples demonstrate various common tasks and are meant to get you started
+with this library.
+
+Many of these files contain a configuration section at the top, which you must
+edit to provide your own account and media details before running the example.
+
+## WARNING, PLEASE READ!
+
+If you are viewing this examples-folder via the web, they may not work on the
+code version that you have downloaded, since development may evolve quickly in
+the development version of the code tree. Especially if you've installed the
+default, stable version of the library, which can often be severely out of date.
+
+Look in _your own_ local disk's `vendor/mgp25/instagram-php/examples` folder for
+the examples that shipped with the exact library version you have installed!
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/accessingValues.php b/instafeed/vendor/mgp25/instagram-php/examples/accessingValues.php
new file mode 100755
index 0000000..170bf58
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/accessingValues.php
@@ -0,0 +1,74 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ $feed = $ig->discover->getExploreFeed();
+
+ // Let's begin by looking at a beautiful debug output of what's available in
+ // the response! This is very helpful for figuring out what a response has!
+ $feed->printJson();
+
+ // Now let's look at what properties are supported on the $feed object. This
+ // works on ANY object from our library, and will show what functions and
+ // properties are supported, as well as how to call the functions! :-)
+ $feed->printPropertyDescriptions();
+
+ // The getExploreFeed() has an "items" property, which we need. As we saw
+ // above, we should get it via "getItems()". The property list above told us
+ // that it will return an array of "Item" objects. Therefore it's an ARRAY!
+ $items = $feed->getItems();
+
+ // Let's get the media item from the first item of the explore-items array...!
+ $firstItem = $items[0]->getMedia();
+
+ // We can look at that item too, if we want to... Let's do it! Note that
+ // when we list supported properties, it shows everything supported by an
+ // "Item" object. But that DOESN'T mean that every property IS available!
+ // That's why you should always check the JSON to be sure that data exists!
+ $firstItem->printJson(); // Shows its actual JSON contents (available data).
+ $firstItem->printPropertyDescriptions(); // List of supported properties.
+
+ // Let's look specifically at its User object!
+ $firstItem->getUser()->printJson();
+
+ // Okay, so the username of the person who posted the media is easy... And
+ // as you can see, you can even chain multiple function calls in a row here
+ // to get to the data. However, be aware that sometimes Instagram responses
+ // have NULL values, so chaining is sometimes risky. But not in this case,
+ // since we know that "user" and its "username" are always available! :-)
+ $firstItem_username = $firstItem->getUser()->getUsername();
+
+ // Now let's get the "id" of the item too!
+ $firstItem_mediaId = $firstItem->getId();
+
+ // Finally, let's get the highest-quality image URL for the media item!
+ $firstItem_imageUrl = $firstItem->getImageVersions2()->getCandidates()[0]->getUrl();
+
+ // Output some statistics. Well done! :-)
+ echo 'There are '.count($items)." items.\n";
+ echo "The first item has media id: {$firstItem_mediaId}.\n";
+ echo "The first item was uploaded by: {$firstItem_username}.\n";
+ echo "The highest quality image URL is: {$firstItem_imageUrl}.\n";
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/assets/instagram.png b/instafeed/vendor/mgp25/instagram-php/examples/assets/instagram.png
new file mode 100755
index 0000000..fc311c0
Binary files /dev/null and b/instafeed/vendor/mgp25/instagram-php/examples/assets/instagram.png differ
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/customSettings.php b/instafeed/vendor/mgp25/instagram-php/examples/customSettings.php
new file mode 100755
index 0000000..fdb6f19
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/customSettings.php
@@ -0,0 +1,147 @@
+ 'mysql',
+]);
+
+// 2. You can read src/Settings/Factory.php for valid settings for each backend.
+// Here's an example of how to change the default storage location for "file":
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug, [
+ 'storage' => 'file',
+ 'basefolder' => 'some/path/',
+]);
+
+// 3. And here's an example of how to change the default database filename and
+// the default database table name for the "sqlite" backend:
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug, [
+ 'storage' => 'sqlite',
+ 'dbfilename' => 'some/path/foo.db',
+ 'dbtablename' => 'mysettings',
+]);
+
+// 4. If you read src/Settings/Factory.php, you'll notice that you can choose
+// the storage backends and most of their parameters via the command line or
+// environment variables instead. For example: "SETTINGS_STORAGE=mysql php
+// yourscript.php" would set the "storage" parameter via the environment, and
+// typing "php yourscript.php --settings_storage=mysql" would set it via the
+// command line. The command-line arguments have the highest precedence, then
+// the environment variables, and lastly the code within your script. This
+// precedence order is so that you can easily override your script's code to
+// test other backends or change their parameters without modifying your code.
+
+// 5. Very advanced users can look in src/Settings/StorageHandler.php to read
+// about hasUser(), moveUser() and deleteUser(). Three very, VERY DANGEROUS
+// commands which let you rename or delete account settings in your storage.
+// Carefully read through their descriptions and use them wisely. If you're sure
+// that you dare to use them, then you can access them via $ig->settings->...
+
+// 6. Yet another super advanced topic is the ability to copy data between
+// backends. For example if you want to move all of your data from a File
+// backend to a database, or vice versa. That kind of action is not supported
+// natively by us, but you CAN do it by directly interfacing with the storages!
+
+// First, you MUST manually build a list of all users you want to migrate.
+// You can either hardcode this list. Or get it via something like a directory
+// scan (to look at existing folders in a File backend storage path), or a
+// database query to get all "username" values from the old database. If you're
+// using a database, you will have to connect to it manually and query it
+// yourself! There's no way to do it automatically! Just build this array any
+// way you want to do it!
+$migrateUsers = [
+ 'someuser',
+ 'another.user',
+ 'very_awesome_user123',
+];
+
+// Secondly, you must connect to your old and new storages. These are just
+// example values. The array format is the exact same as what's given to the
+// `Instagram()` constructor! And if you give an empty array, you'll use the
+// same default File backend that the main class uses! So if you want to migrate
+// from that, you should just set oldStorage to `createHandler([])`!
+$oldStorage = \InstagramAPI\Settings\Factory::createHandler([
+ 'storage' => 'sqlite',
+ 'dbfilename' => 'app/instagram.sqlite',
+ 'dbtablename' => 'instagram_sessions',
+]);
+$newStorage = \InstagramAPI\Settings\Factory::createHandler([
+ 'storage' => 'file',
+ 'basefolder' => 'some/path/',
+]);
+
+// Now just run the migration process. This will copy all cookies and settings
+// from the old storage to the new storage, for all of the "migrateUsers".
+foreach ($migrateUsers as $user) {
+ if (!$oldStorage->hasUser($user)) {
+ die("Unable to migrate '{$user}' from old storage (user doesn't exist).\n");
+ }
+
+ echo "Migrating '{$user}'.\n";
+
+ $oldStorage->setActiveUser($user);
+ $newStorage->setActiveUser($user);
+
+ $newStorage->setCookies((string) $oldStorage->getCookies());
+ foreach (\InstagramAPI\Settings\StorageHandler::PERSISTENT_KEYS as $key) {
+ $newStorage->set($key, (string) $oldStorage->get($key));
+ }
+}
+
+// 7. Lastly... if you want to implement your own completely CUSTOM STORAGE,
+// then you simply have to do one thing: Implement the StorageInterface class
+// interface. But be very sure to STRICTLY follow ALL rules for storage backends
+// described in that interface's docs, otherwise your custom backend WON'T work.
+//
+// See the overview in src/Settings/StorageInterface.php, and then read through
+// the various built-in storage backends in src/Settings/Storage/ to see perfect
+// implementations that completely follow the required interface specification.
+//
+// Also note that PDO-based backends should be derived from our "PDOStorage"
+// storage sub-component, so that the logic is perfectly implemented and not
+// duplicated. That's exactly how our "sqlite" and "mysql" PDO backends work!
+//
+// To use your custom storage backend, you would simply create your own class
+// similar to the built-in backends. But do NOT put your own class in our
+// src/Settings/Storage/ folder. Store your class inside your own project.
+//
+// Then simply provide your custom storage class instance as the storage class:
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug, [
+ 'storage' => 'custom',
+ 'class' => new MyCustomStorage(), // Whatever you've named your class.
+]);
+
+// That's it! This should get you started on your journey. :-)
+
+// And please think about contributing your WELL-WRITTEN storage backends to
+// this project! If you had a reason to write your own, then there's probably
+// someone else out there with the same need. Remember to SHARE with the open
+// source community! ;-)
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/exceptionDetailsExample.php b/instafeed/vendor/mgp25/instagram-php/examples/exceptionDetailsExample.php
new file mode 100755
index 0000000..57ae4a2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/exceptionDetailsExample.php
@@ -0,0 +1,83 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+/*
+ * The code below demonstrates how to get the full server response from an
+ * InstagramException object (that's the ONLY type of exception which can
+ * have an Instagram server response attached to it).
+ *
+ * All exceptions thrown by our library are derived from InstagramException.
+ * And MOST of them (but NOT ALL) will have a server response object. So it's
+ * important that you check hasResponse() before trying to use the response.
+ */
+
+// Example 1: Catching EVERYTHING derived from InstagramException so that you
+// can look at the server response (IF one is available).
+
+try {
+ $ig->media->delete('123456'); // Invalid media ID, to trigger a server error.
+} catch (\InstagramAPI\Exception\InstagramException $e) {
+ echo 'Something went wrong (InstagramException): '.$e->getMessage()."\n";
+
+ if ($e->hasResponse()) { // <-- VERY IMPORTANT TO CHECK FIRST!
+ echo "The current exception contains a full server response:\n";
+ $e->getResponse()->printJson();
+ echo "Showing the 'did_delete' and 'message' values of the response:\n";
+ var_dump($e->getResponse()->getDidDelete());
+ var_dump($e->getResponse()->getMessage());
+ }
+}
+
+// Example 2: Catching the SPECIFIC exception you want (which must be derived
+// from InstagramException, otherwise it won't have hasResponse()/getResponse()).
+
+try {
+ $ig->media->delete('123456'); // Invalid media ID, to trigger a server error.
+} catch (\InstagramAPI\Exception\EndpointException $e) {
+ echo 'Something went wrong (EndpointException): '.$e->getMessage()."\n";
+
+ if ($e->hasResponse()) { // <-- VERY IMPORTANT TO CHECK FIRST!
+ echo "The current exception contains a full server response:\n";
+ $e->getResponse()->printJson();
+ echo "Showing the 'did_delete' and 'message' values of the response:\n";
+ var_dump($e->getResponse()->getDidDelete());
+ var_dump($e->getResponse()->getMessage());
+ }
+
+ // Bonus: This shows how to look for specific "error reason" messages in an
+ // EndpointException. There are hundreds or even thousands of different
+ // error messages and we won't add exceptions for them (that would be a
+ // nightmare to maintain). Instead, it's up to the library users to check
+ // for the per-endpoint error messages they care about. Such as this one:
+ if (strpos($e->getMessage(), 'Could not delete') !== false) {
+ echo "* The problem in this generic EndpointException was that Instagram could not delete the media!\n";
+ }
+}
+
+// With this knowledge, you can now go out and catch the exact exceptions you
+// want, and look at specific server details in the reply whenever necessary!
+// Note that the easiest way to handle the generic EndpointException (which is
+// triggered when any of the generic functions fail) is to do a string search in
+// $e->getMessage() to look for a specific error message to know what type of
+// error it was. And if you need more details about the error, you can THEN look
+// at $e->getResponse() to see the contents of the actual server reply.
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/liveBroadcast.php b/instafeed/vendor/mgp25/instagram-php/examples/liveBroadcast.php
new file mode 100755
index 0000000..446c1d8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/liveBroadcast.php
@@ -0,0 +1,137 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // NOTE: This code will create a broadcast, which will give us an RTMP url
+ // where we are supposed to stream-upload the media we want to broadcast.
+ //
+ // The following code is using FFMPEG to broadcast, although other
+ // alternatives are valid too, like OBS (Open Broadcaster Software,
+ // https://obsproject.com).
+ //
+ // For more information on FFMPEG, see:
+ // https://github.com/mgp25/Instagram-API/issues/1488#issuecomment-324271177
+ // and for OBS, see:
+ // https://github.com/mgp25/Instagram-API/issues/1488#issuecomment-333365636
+
+ // Get FFmpeg handler and ensure that the application exists on this system.
+ // NOTE: You can supply custom path to the ffmpeg binary, or just leave NULL
+ // to autodetect it.
+ $ffmpegPath = null;
+ $ffmpeg = \InstagramAPI\Media\Video\FFmpeg::factory($ffmpegPath);
+
+ // Tell Instagram that we want to perform a livestream.
+ $stream = $ig->live->create();
+ $broadcastId = $stream->getBroadcastId();
+ $ig->live->start($broadcastId);
+
+ $streamUploadUrl = $stream->getUploadUrl();
+
+ // Broadcast the entire video file.
+ // NOTE: The video is broadcasted asynchronously (in the background).
+ $broadcastProcess = $ffmpeg->runAsync(sprintf(
+ '-rtbufsize 256M -re -i %s -acodec libmp3lame -ar 44100 -b:a 128k -pix_fmt yuv420p -profile:v baseline -s 720x1280 -bufsize 6000k -vb 400k -maxrate 1500k -deinterlace -vcodec libx264 -preset veryfast -g 30 -r 30 -f flv %s',
+ \Winbox\Args::escape($videoFilename),
+ \Winbox\Args::escape($streamUploadUrl)
+ ));
+
+ // The following loop performs important requests to obtain information
+ // about the broadcast while it is ongoing.
+ // NOTE: This is REQUIRED if you want the comments and likes to appear
+ // in your saved post-live feed.
+ // NOTE: These requests are sent *while* the video is being broadcasted.
+ $lastCommentTs = 0;
+ $lastLikeTs = 0;
+ do {
+ // Get broadcast comments.
+ // - The latest comment timestamp will be required for the next
+ // getComments() request.
+ // - There are two types of comments: System comments and user comments.
+ // We compare both and keep the newest (most recent) timestamp.
+ $commentsResponse = $ig->live->getComments($broadcastId, $lastCommentTs);
+ $systemComments = $commentsResponse->getSystemComments();
+ $comments = $commentsResponse->getComments();
+ if (!empty($systemComments)) {
+ $lastCommentTs = $systemComments[0]->getCreatedAt();
+ }
+ if (!empty($comments) && $comments[0]->getCreatedAt() > $lastCommentTs) {
+ $lastCommentTs = $comments[0]->getCreatedAt();
+ }
+
+ // Get broadcast heartbeat and viewer count.
+ $heartbeatResponse = $ig->live->getHeartbeatAndViewerCount($broadcastId);
+
+ // Check to see if the livestream has been flagged for a policy violation.
+ if ($heartbeatResponse->isIsPolicyViolation() && (int) $heartbeatResponse->getIsPolicyViolation() === 1) {
+ echo 'Instagram has flagged your content as a policy violation with the following reason: '.($heartbeatResponse->getPolicyViolationReason() == null ? 'Unknown' : $heartbeatResponse->getPolicyViolationReason())."\n";
+ // Change this to false if disagree with the policy violation and would would like to continue streaming.
+ // - Note: In this example, the violation is always accepted.
+ // In your use case, you may want to prompt the user if
+ // they would like to accept or refute the policy violation.
+ if (true) {
+ // Get the final viewer list of the broadcast.
+ $ig->live->getFinalViewerList($broadcastId);
+ // End the broadcast stream while acknowledging the copyright warning given.
+ $ig->live->end($broadcastId, true);
+ exit(0);
+ }
+ // Acknowledges the copyright warning and allows you to continue streaming.
+ // - Note: This may allow the copyright holder to view your livestream
+ // regardless of your account privacy or if you archive it.
+ $ig->live->resumeBroadcastAfterContentMatch($broadcastId);
+ }
+
+ // Get broadcast like count.
+ // - The latest like timestamp will be required for the next
+ // getLikeCount() request.
+ $likeCountResponse = $ig->live->getLikeCount($broadcastId, $lastLikeTs);
+ $lastLikeTs = $likeCountResponse->getLikeTs();
+
+ // Get the join request counts.
+ // - This doesn't add support for live-with-friends. Rather,
+ // this is only here to emulate the app's livestream flow.
+ $ig->live->getJoinRequestCounts($broadcastId);
+
+ sleep(2);
+ } while ($broadcastProcess->isRunning());
+
+ // Get the final viewer list of the broadcast.
+ // NOTE: You should only use this after the broadcast has stopped uploading.
+ $ig->live->getFinalViewerList($broadcastId);
+
+ // End the broadcast stream.
+ // NOTE: Instagram will ALSO end the stream if your broadcasting software
+ // itself sends a RTMP signal to end the stream. FFmpeg doesn't do that
+ // (without patching), but OBS sends such a packet. So be aware of that.
+ $ig->live->end($broadcastId);
+
+ // Once the broadcast has ended, you can optionally add the finished
+ // broadcast to your post-live feed (saved replay).
+ $ig->live->addToPostLive($broadcastId);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/paginateWithExclusion.php b/instafeed/vendor/mgp25/instagram-php/examples/paginateWithExclusion.php
new file mode 100755
index 0000000..7bf76d2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/paginateWithExclusion.php
@@ -0,0 +1,57 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Let's list locations that match "milan".
+ $query = 'milan';
+
+ // Initialize the state.
+ $rankToken = null;
+ $excludeList = [];
+ do {
+ // Request the page.
+ $response = $ig->location->findPlaces($query, $excludeList, $rankToken);
+
+ // In this example we're simply printing the IDs of this page's items.
+ foreach ($response->getItems() as $item) {
+ $location = $item->getLocation();
+ // Add the item ID to the exclusion list, to tell Instagram's server
+ // to skip that item on the next pagination request.
+ $excludeList[] = $location->getFacebookPlacesId();
+ // Let's print some details about the item.
+ printf("%s (%.3f, %.3f)\n", $item->getTitle(), $location->getLat(), $location->getLng());
+ }
+
+ // Now we must update the rankToken variable.
+ $rankToken = $response->getRankToken();
+
+ // Sleep for 5 seconds before requesting the next page. This is just an
+ // example of an okay sleep time. It is very important that your scripts
+ // always pause between requests that may run very rapidly, otherwise
+ // Instagram will throttle you temporarily for abusing their API!
+ echo "Sleeping for 5s...\n";
+ sleep(5);
+ } while ($response->getHasMore());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/paginationExample.php b/instafeed/vendor/mgp25/instagram-php/examples/paginationExample.php
new file mode 100755
index 0000000..0dd3f56
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/paginationExample.php
@@ -0,0 +1,53 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Get the UserPK ID for "natgeo" (National Geographic).
+ $userId = $ig->people->getUserIdForName('natgeo');
+
+ // Starting at "null" means starting at the first page.
+ $maxId = null;
+ do {
+ // Request the page corresponding to maxId.
+ $response = $ig->timeline->getUserFeed($userId, $maxId);
+
+ // In this example we're simply printing the IDs of this page's items.
+ foreach ($response->getItems() as $item) {
+ printf("[%s] https://instagram.com/p/%s/\n", $item->getId(), $item->getCode());
+ }
+
+ // Now we must update the maxId variable to the "next page".
+ // This will be a null value again when we've reached the last page!
+ // And we will stop looping through pages as soon as maxId becomes null.
+ $maxId = $response->getNextMaxId();
+
+ // Sleep for 5 seconds before requesting the next page. This is just an
+ // example of an okay sleep time. It is very important that your scripts
+ // always pause between requests that may run very rapidly, otherwise
+ // Instagram will throttle you temporarily for abusing their API!
+ echo "Sleeping for 5s...\n";
+ sleep(5);
+ } while ($maxId !== null); // Must use "!==" for comparison instead of "!=".
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/pushReceiver.php b/instafeed/vendor/mgp25/instagram-php/examples/pushReceiver.php
new file mode 100755
index 0000000..4bf8980
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/pushReceiver.php
@@ -0,0 +1,80 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+$loop = \React\EventLoop\Factory::create();
+if ($debug) {
+ $logger = new \Monolog\Logger('push');
+ $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+} else {
+ $logger = null;
+}
+$push = new \InstagramAPI\Push($loop, $ig, $logger);
+$push->on('incoming', function (\InstagramAPI\Push\Notification $push) {
+ printf('%s%s', $push->getMessage(), PHP_EOL);
+});
+$push->on('like', function (\InstagramAPI\Push\Notification $push) {
+ printf('Media ID: %s%s', $push->getActionParam('id'), PHP_EOL);
+});
+$push->on('comment', function (\InstagramAPI\Push\Notification $push) {
+ switch ($push->getActionPath()) {
+ case 'comments_v2':
+ $mediaId = $push->getActionParam('media_id');
+ $commentId = $push->getActionParam('target_comment_id');
+ break;
+ case 'media':
+ default:
+ $mediaId = $push->getActionParam('id');
+ $commentId = $push->getActionParam('forced_preview_comment_id');
+ }
+ printf(
+ 'Media ID: %s. Comment ID: %s.%s',
+ $mediaId,
+ $commentId,
+ PHP_EOL
+ );
+});
+$push->on('direct_v2_message', function (\InstagramAPI\Push\Notification $push) {
+ printf(
+ 'Thread ID: %s. Thread item ID: %s.%s',
+ $push->getActionParam('id'),
+ $push->getActionParam('x'),
+ PHP_EOL
+ );
+});
+$push->on('error', function (\Exception $e) use ($push, $loop) {
+ printf('[!!!] Got fatal error from FBNS: %s%s', $e->getMessage(), PHP_EOL);
+ $push->stop();
+ $loop->stop();
+});
+$push->start();
+
+$loop->run();
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/rankTokenUsage.php b/instafeed/vendor/mgp25/instagram-php/examples/rankTokenUsage.php
new file mode 100755
index 0000000..5321eee
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/rankTokenUsage.php
@@ -0,0 +1,57 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Let's list media for the "nature" hashtag.
+ $tag = 'nature';
+
+ // Generate a random rank token.
+ $rankToken = \InstagramAPI\Signatures::generateUUID();
+
+ // Starting at "null" means starting at the first page.
+ $maxId = null;
+ do {
+ // Request the page corresponding to maxId.
+ // Note that we are using the same rank token for all pages.
+ $response = $ig->hashtag->getFeed($tag, $rankToken, $maxId);
+
+ // In this example we're simply printing the IDs of this page's items.
+ foreach ($response->getItems() as $item) {
+ printf("[%s] https://instagram.com/p/%s/\n", $item->getId(), $item->getCode());
+ }
+
+ // Now we must update the maxId variable to the "next page".
+ // This will be a null value again when we've reached the last page!
+ // And we will stop looping through pages as soon as maxId becomes null.
+ $maxId = $response->getNextMaxId();
+
+ // Sleep for 5 seconds before requesting the next page. This is just an
+ // example of an okay sleep time. It is very important that your scripts
+ // always pause between requests that may run very rapidly, otherwise
+ // Instagram will throttle you temporarily for abusing their API!
+ echo "Sleeping for 5s...\n";
+ sleep(5);
+ } while ($maxId !== null); // Must use "!==" for comparison instead of "!=".
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/realtimeClient.php b/instafeed/vendor/mgp25/instagram-php/examples/realtimeClient.php
new file mode 100755
index 0000000..5abdf06
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/realtimeClient.php
@@ -0,0 +1,100 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+$loop = \React\EventLoop\Factory::create();
+if ($debug) {
+ $logger = new \Monolog\Logger('rtc');
+ $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+} else {
+ $logger = null;
+}
+$rtc = new \InstagramAPI\Realtime($ig, $loop, $logger);
+$rtc->on('live-started', function (\InstagramAPI\Realtime\Payload\LiveBroadcast $live) {
+ printf('[RTC] Live broadcast %s has been started%s', $live->getBroadcastId(), PHP_EOL);
+});
+$rtc->on('live-stopped', function (\InstagramAPI\Realtime\Payload\LiveBroadcast $live) {
+ printf('[RTC] Live broadcast %s has been stopped%s', $live->getBroadcastId(), PHP_EOL);
+});
+$rtc->on('direct-story-created', function (\InstagramAPI\Response\Model\DirectThread $thread) {
+ printf('[RTC] Story %s has been created%s', $thread->getThreadId(), PHP_EOL);
+});
+$rtc->on('direct-story-updated', function ($threadId, $threadItemId, \InstagramAPI\Response\Model\DirectThreadItem $threadItem) {
+ printf('[RTC] Item %s has been created in story %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('direct-story-screenshot', function ($threadId, \InstagramAPI\Realtime\Payload\StoryScreenshot $screenshot) {
+ printf('[RTC] %s has taken screenshot of story %s%s', $screenshot->getActionUserDict()->getUsername(), $threadId, PHP_EOL);
+});
+$rtc->on('direct-story-action', function ($threadId, \InstagramAPI\Response\Model\ActionBadge $storyAction) {
+ printf('[RTC] Story in thread %s has badge %s now%s', $threadId, $storyAction->getActionType(), PHP_EOL);
+});
+$rtc->on('thread-created', function ($threadId, \InstagramAPI\Response\Model\DirectThread $thread) {
+ printf('[RTC] Thread %s has been created%s', $threadId, PHP_EOL);
+});
+$rtc->on('thread-updated', function ($threadId, \InstagramAPI\Response\Model\DirectThread $thread) {
+ printf('[RTC] Thread %s has been updated%s', $threadId, PHP_EOL);
+});
+$rtc->on('thread-notify', function ($threadId, $threadItemId, \InstagramAPI\Realtime\Payload\ThreadAction $notify) {
+ printf('[RTC] Thread %s has notification from %s%s', $threadId, $notify->getUserId(), PHP_EOL);
+});
+$rtc->on('thread-seen', function ($threadId, $userId, \InstagramAPI\Response\Model\DirectThreadLastSeenAt $seenAt) {
+ printf('[RTC] Thread %s has been checked by %s%s', $threadId, $userId, PHP_EOL);
+});
+$rtc->on('thread-activity', function ($threadId, \InstagramAPI\Realtime\Payload\ThreadActivity $activity) {
+ printf('[RTC] Thread %s has some activity made by %s%s', $threadId, $activity->getSenderId(), PHP_EOL);
+});
+$rtc->on('thread-item-created', function ($threadId, $threadItemId, \InstagramAPI\Response\Model\DirectThreadItem $threadItem) {
+ printf('[RTC] Item %s has been created in thread %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('thread-item-updated', function ($threadId, $threadItemId, \InstagramAPI\Response\Model\DirectThreadItem $threadItem) {
+ printf('[RTC] Item %s has been updated in thread %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('thread-item-removed', function ($threadId, $threadItemId) {
+ printf('[RTC] Item %s has been removed from thread %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('client-context-ack', function (\InstagramAPI\Realtime\Payload\Action\AckAction $ack) {
+ printf('[RTC] Received ACK for %s with status %s%s', $ack->getPayload()->getClientContext(), $ack->getStatus(), PHP_EOL);
+});
+$rtc->on('unseen-count-update', function ($inbox, \InstagramAPI\Response\Model\DirectSeenItemPayload $payload) {
+ printf('[RTC] Updating unseen count in %s to %d%s', $inbox, $payload->getCount(), PHP_EOL);
+});
+$rtc->on('presence', function (\InstagramAPI\Response\Model\UserPresence $presence) {
+ $action = $presence->getIsActive() ? 'is now using' : 'just closed';
+ printf('[RTC] User %s %s one of Instagram apps%s', $presence->getUserId(), $action, PHP_EOL);
+});
+$rtc->on('error', function (\Exception $e) use ($rtc, $loop) {
+ printf('[!!!] Got fatal error from Realtime: %s%s', $e->getMessage(), PHP_EOL);
+ $rtc->stop();
+ $loop->stop();
+});
+$rtc->start();
+
+$loop->run();
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/realtimeHttp.php b/instafeed/vendor/mgp25/instagram-php/examples/realtimeHttp.php
new file mode 100755
index 0000000..2478262
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/realtimeHttp.php
@@ -0,0 +1,287 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// Create main event loop.
+$loop = \React\EventLoop\Factory::create();
+if ($debug) {
+ $logger = new \Monolog\Logger('rtc');
+ $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+} else {
+ $logger = null;
+}
+// Create HTTP server along with Realtime client.
+$httpServer = new RealtimeHttpServer($loop, $ig, $logger);
+// Run main loop.
+$loop->run();
+
+class RealtimeHttpServer
+{
+ const HOST = '127.0.0.1';
+ const PORT = 1307;
+
+ const TIMEOUT = 5;
+
+ /** @var \React\Promise\Deferred[] */
+ protected $_contexts;
+
+ /** @var \React\EventLoop\LoopInterface */
+ protected $_loop;
+
+ /** @var \InstagramAPI\Instagram */
+ protected $_instagram;
+
+ /** @var \InstagramAPI\Realtime */
+ protected $_rtc;
+
+ /** @var \React\Http\Server */
+ protected $_server;
+
+ /** @var \Psr\Log\LoggerInterface */
+ protected $_logger;
+
+ /**
+ * Constructor.
+ *
+ * @param \React\EventLoop\LoopInterface $loop
+ * @param \InstagramAPI\Instagram $instagram
+ * @param \Psr\Log\LoggerInterface|null $logger
+ */
+ public function __construct(
+ \React\EventLoop\LoopInterface $loop,
+ \InstagramAPI\Instagram $instagram,
+ \Psr\Log\LoggerInterface $logger = null)
+ {
+ $this->_loop = $loop;
+ $this->_instagram = $instagram;
+ if ($logger === null) {
+ $logger = new \Psr\Log\NullLogger();
+ }
+ $this->_logger = $logger;
+ $this->_contexts = [];
+ $this->_rtc = new \InstagramAPI\Realtime($this->_instagram, $this->_loop, $this->_logger);
+ $this->_rtc->on('client-context-ack', [$this, 'onClientContextAck']);
+ $this->_rtc->on('error', [$this, 'onRealtimeFail']);
+ $this->_rtc->start();
+ $this->_startHttpServer();
+ }
+
+ /**
+ * Gracefully stop everything.
+ */
+ protected function _stop()
+ {
+ // Initiate shutdown sequence.
+ $this->_rtc->stop();
+ // Wait 2 seconds for Realtime to shutdown.
+ $this->_loop->addTimer(2, function () {
+ // Stop main loop.
+ $this->_loop->stop();
+ });
+ }
+
+ /**
+ * Called when fatal error has been received from Realtime.
+ *
+ * @param \Exception $e
+ */
+ public function onRealtimeFail(
+ \Exception $e)
+ {
+ $this->_logger->error((string) $e);
+ $this->_stop();
+ }
+
+ /**
+ * Called when ACK has been received.
+ *
+ * @param \InstagramAPI\Realtime\Payload\Action\AckAction $ack
+ */
+ public function onClientContextAck(
+ \InstagramAPI\Realtime\Payload\Action\AckAction $ack)
+ {
+ $context = $ack->getPayload()->getClientContext();
+ $this->_logger->info(sprintf('Received ACK for %s with status %s', $context, $ack->getStatus()));
+ // Check if we have deferred object for this client_context.
+ if (!isset($this->_contexts[$context])) {
+ return;
+ }
+ // Resolve deferred object with $ack.
+ $deferred = $this->_contexts[$context];
+ $deferred->resolve($ack);
+ // Clean up.
+ unset($this->_contexts[$context]);
+ }
+
+ /**
+ * @param string|bool $context
+ *
+ * @return \React\Http\Response|\React\Promise\PromiseInterface
+ */
+ protected function _handleClientContext(
+ $context)
+ {
+ // Reply with 503 Service Unavailable.
+ if ($context === false) {
+ return new \React\Http\Response(503);
+ }
+ // Set up deferred object.
+ $deferred = new \React\Promise\Deferred();
+ $this->_contexts[$context] = $deferred;
+ // Reject deferred after given timeout.
+ $timeout = $this->_loop->addTimer(self::TIMEOUT, function () use ($deferred, $context) {
+ $deferred->reject();
+ unset($this->_contexts[$context]);
+ });
+ // Set up promise.
+ return $deferred->promise()
+ ->then(function (\InstagramAPI\Realtime\Payload\Action\AckAction $ack) use ($timeout) {
+ // Cancel reject timer.
+ $timeout->cancel();
+ // Reply with info from $ack.
+ return new \React\Http\Response($ack->getStatusCode(), ['Content-Type' => 'text/json'], $ack->getPayload()->asJson());
+ })
+ ->otherwise(function () {
+ // Called by reject timer. Reply with 504 Gateway Time-out.
+ return new \React\Http\Response(504);
+ });
+ }
+
+ /**
+ * Handler for incoming HTTP requests.
+ *
+ * @param \Psr\Http\Message\ServerRequestInterface $request
+ *
+ * @return \React\Http\Response|\React\Promise\PromiseInterface
+ */
+ public function onHttpRequest(
+ \Psr\Http\Message\ServerRequestInterface $request)
+ {
+ // Treat request path as command.
+ $command = $request->getUri()->getPath();
+ // Params validation is up to you.
+ $params = $request->getQueryParams();
+ // Log command with its params.
+ $this->_logger->info(sprintf('Received command %s', $command), $params);
+ switch ($command) {
+ case '/ping':
+ return new \React\Http\Response(200, [], 'pong');
+ case '/stop':
+ $this->_stop();
+
+ return new \React\Http\Response(200);
+ case '/seen':
+ $context = $this->_rtc->markDirectItemSeen($params['threadId'], $params['threadItemId']);
+
+ return new \React\Http\Response($context !== false ? 200 : 503);
+ case '/activity':
+ return $this->_handleClientContext($this->_rtc->indicateActivityInDirectThread($params['threadId'], (bool) $params['flag']));
+ case '/message':
+ return $this->_handleClientContext($this->_rtc->sendTextToDirect($params['threadId'], $params['text']));
+ case '/post':
+ return $this->_handleClientContext($this->_rtc->sendPostToDirect($params['threadId'], $params['mediaId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/story':
+ return $this->_handleClientContext($this->_rtc->sendStoryToDirect($params['threadId'], $params['storyId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/profile':
+ return $this->_handleClientContext($this->_rtc->sendProfileToDirect($params['threadId'], $params['userId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/location':
+ return $this->_handleClientContext($this->_rtc->sendLocationToDirect($params['threadId'], $params['locationId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/hashtag':
+ return $this->_handleClientContext($this->_rtc->sendHashtagToDirect($params['threadId'], $params['hashtag'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/like':
+ return $this->_handleClientContext($this->_rtc->sendLikeToDirect($params['threadId']));
+ case '/likeItem':
+ return $this->_handleClientContext($this->_rtc->sendReactionToDirect($params['threadId'], $params['threadItemId'], 'like'));
+ case '/unlikeItem':
+ return $this->_handleClientContext($this->_rtc->deleteReactionFromDirect($params['threadId'], $params['threadItemId'], 'like'));
+ default:
+ $this->_logger->warning(sprintf('Unknown command %s', $command), $params);
+ // If command is unknown, reply with 404 Not Found.
+ return new \React\Http\Response(404);
+ }
+ }
+
+ /**
+ * Init and start HTTP server.
+ */
+ protected function _startHttpServer()
+ {
+ // Create server socket.
+ $socket = new \React\Socket\Server(self::HOST.':'.self::PORT, $this->_loop);
+ $this->_logger->info(sprintf('Listening on http://%s', $socket->getAddress()));
+ // Bind HTTP server on server socket.
+ $this->_server = new \React\Http\Server([$this, 'onHttpRequest']);
+ $this->_server->listen($socket);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/sharePhotoToStoryFeed.php b/instafeed/vendor/mgp25/instagram-php/examples/sharePhotoToStoryFeed.php
new file mode 100755
index 0000000..6ace2a4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/sharePhotoToStoryFeed.php
@@ -0,0 +1,71 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// NOTE: This code will make the credits of the media area 'clickable', but YOU need to
+// manually draw the credit to the user or a sticker-image on top of your image yourself
+// before uploading, if you want the credit to actually be visible on-screen!
+
+// If we want to attach a media, we must find a valid media_id first.
+try {
+ $mediaInfo = $ig->media->getInfo($mediaId)->getItems()[0];
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
+
+// Now create the metadata array:
+$metadata = [
+ 'attached_media' => [
+ [
+ 'media_id' => $mediaId,
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'width' => 0.8, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.6224662, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+$client = new GuzzleHttp\Client();
+$outputFile = Utils::createTempFile(sys_get_temp_dir(), 'IMG');
+
+try {
+ $response = $client->request('GET', $mediaInfo->getImageVersions2()->getCandidates()[0]->getUrl(), ['sink' => $outputFile]);
+
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories.
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($outputFile, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+} finally {
+ @unlink($outputFile);
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/shortcodeConverter.php b/instafeed/vendor/mgp25/instagram-php/examples/shortcodeConverter.php
new file mode 100755
index 0000000..b700109
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/shortcodeConverter.php
@@ -0,0 +1,29 @@
+ code({$code})\n\n";
+
+// From Shortcode to Media ID.
+$id = InstagramID::fromCode($code);
+echo "Instagram code({$code}) -> id({$id})\n\n";
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/twoFactorLogin.php b/instafeed/vendor/mgp25/instagram-php/examples/twoFactorLogin.php
new file mode 100755
index 0000000..6055ec7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/twoFactorLogin.php
@@ -0,0 +1,31 @@
+login($username, $password);
+
+ if ($loginResponse !== null && $loginResponse->isTwoFactorRequired()) {
+ $twoFactorIdentifier = $loginResponse->getTwoFactorInfo()->getTwoFactorIdentifier();
+
+ // The "STDIN" lets you paste the code via terminal for testing.
+ // You should replace this line with the logic you want.
+ // The verification code will be sent by Instagram via SMS.
+ $verificationCode = trim(fgets(STDIN));
+ $ig->finishTwoFactorLogin($username, $password, $twoFactorIdentifier, $verificationCode);
+ }
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/uploadAlbum.php b/instafeed/vendor/mgp25/instagram-php/examples/uploadAlbum.php
new file mode 100755
index 0000000..12872d2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/uploadAlbum.php
@@ -0,0 +1,109 @@
+ 'photo',
+ 'file' => '', // Path to the photo file.
+ ],
+ [
+ 'type' => 'photo',
+ 'file' => '', // Path to the photo file.
+ /* TAGS COMMENTED OUT UNTIL YOU READ THE BELOW:
+ 'usertags' => [ // Optional, lets you tag one or more users in a PHOTO.
+ [
+ 'position' => [0.5, 0.5],
+
+ // WARNING: THE USER ID MUST BE VALID. INSTAGRAM WILL VERIFY IT
+ // AND IF IT'S WRONG THEY WILL SAY "media configure error".
+ 'user_id' => '123456789', // Must be a numerical UserPK ID.
+ ],
+ ],
+ */
+ ],
+ [
+ 'type' => 'video',
+ 'file' => '', // Path to the video file.
+ 'thumbnail_timestamp' => '', // Timestamp of thumbnail
+ ],
+];
+$captionText = ''; // Caption to use for the album.
+//////////////////////
+
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug);
+
+try {
+ $ig->login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+////// NORMALIZE MEDIA //////
+// All album files must have the same aspect ratio.
+// We copy the app's behavior by using the first file
+// as template for all subsequent ones.
+$mediaOptions = [
+ 'targetFeed' => \InstagramAPI\Constants::FEED_TIMELINE_ALBUM,
+ // Uncomment to expand media instead of cropping it.
+ //'operation' => \InstagramAPI\Media\InstagramMedia::EXPAND,
+];
+foreach ($media as &$item) {
+ /** @var \InstagramAPI\Media\InstagramMedia|null $validMedia */
+ $validMedia = null;
+ switch ($item['type']) {
+ case 'photo':
+ $validMedia = new \InstagramAPI\Media\Photo\InstagramPhoto($item['file'], $mediaOptions);
+ break;
+ case 'video':
+ $validMedia = new \InstagramAPI\Media\Video\InstagramVideo($item['file'], $mediaOptions);
+ break;
+ default:
+ // Ignore unknown media type.
+ }
+ if ($validMedia === null) {
+ continue;
+ }
+
+ try {
+ $item['file'] = $validMedia->getFile();
+ // We must prevent the InstagramMedia object from destructing too early,
+ // because the media class auto-deletes the processed file during their
+ // destructor's cleanup (so we wouldn't be able to upload those files).
+ $item['__media'] = $validMedia; // Save object in an unused array key.
+ } catch (\Exception $e) {
+ continue;
+ }
+ if (!isset($mediaOptions['forceAspectRatio'])) {
+ // Use the first media file's aspect ratio for all subsequent files.
+ /** @var \InstagramAPI\Media\MediaDetails $mediaDetails */
+ $mediaDetails = $validMedia instanceof \InstagramAPI\Media\Photo\InstagramPhoto
+ ? new \InstagramAPI\Media\Photo\PhotoDetails($item['file'])
+ : new \InstagramAPI\Media\Video\VideoDetails($item['file']);
+ $mediaOptions['forceAspectRatio'] = $mediaDetails->getAspectRatio();
+ }
+}
+unset($item);
+/////////////////////////////
+
+try {
+ $ig->timeline->uploadAlbum($media, ['caption' => $captionText]);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/uploadPhoto.php b/instafeed/vendor/mgp25/instagram-php/examples/uploadPhoto.php
new file mode 100755
index 0000000..954d960
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/uploadPhoto.php
@@ -0,0 +1,62 @@
+setProxy("http://$proxyUser:$proxyPassword@$proxyIP:$proxyPort"); // SOCKS5 Proxy needing authentication
+try {
+ $ig->login($username, $password);
+} catch (\Exception $e) {
+ echo 'Error: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // The most basic upload command, if you're sure that your photo file is
+ // valid on Instagram (that it fits all requirements), is the following:
+ // $ig->timeline->uploadPhoto($photoFilename, ['caption' => $captionText]);
+
+ // However, if you want to guarantee that the file is valid (correct format,
+ // width, height and aspect ratio), then you can run it through our
+ // automatic photo processing class. It is pretty fast, and only does any
+ // work when the input file is invalid, so you may want to always use it.
+ // You have nothing to worry about, since the class uses temporary files if
+ // the input needs processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename);
+ $ig->timeline->uploadPhoto($photo->getFile(), ['caption' => $captionText]);
+} catch (\Exception $e) {
+ echo 'Error: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/uploadStory.php b/instafeed/vendor/mgp25/instagram-php/examples/uploadStory.php
new file mode 100755
index 0000000..4eb24a6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/uploadStory.php
@@ -0,0 +1,102 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// You don't have to provide hashtags or locations for your story. It is
+// optional! But we will show you how to do both...
+
+// NOTE: This code will make the hashtag area 'clickable', but YOU need to
+// manually draw the hashtag or a sticker-image on top of your image yourself
+// before uploading, if you want the tag to actually be visible on-screen!
+
+// NOTE: The same thing happens when a location sticker is added. And the
+// "location_sticker" WILL ONLY work if you also add the "location" as shown
+// below.
+
+// NOTE: And "caption" will NOT be visible either! Like all the other story
+// metadata described above, YOU must manually draw the caption on your image.
+
+// If we want to attach a location, we must find a valid Location object first:
+try {
+ $location = $ig->location->search('40.7439862', '-73.998511')->getVenues()[0];
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
+
+// Now create the metadata array:
+$metadata = [
+ // (optional) Captions can always be used, like this:
+ 'caption' => '#test This is a great API!',
+
+ // (optional) To add a hashtag, do this:
+ 'hashtags' => [
+ // Note that you can add more than one hashtag in this array.
+ [
+ 'tag_name' => 'test', // Hashtag WITHOUT the '#'! NOTE: This hashtag MUST appear in the caption.
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'width' => 0.24305555, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.07347973, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => false, // Don't change this value.
+ 'use_custom_title' => false, // Don't change this value.
+ ],
+ // ...
+ ],
+
+ // (optional) To add a location, do BOTH of these:
+ 'location_sticker' => [
+ 'width' => 0.89333333333333331,
+ 'height' => 0.071281859070464776,
+ 'x' => 0.5,
+ 'y' => 0.2,
+ 'rotation' => 0.0,
+ 'is_sticker' => true,
+ 'location_id' => $location->getExternalId(),
+ ],
+ 'location' => $location,
+
+ // (optional) You can use story links ONLY if you have a business account with >= 10k followers.
+ // 'link' => 'https://github.com/mgp25/Instagram-API',
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryCountdown.php b/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryCountdown.php
new file mode 100755
index 0000000..251a70f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryCountdown.php
@@ -0,0 +1,67 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// Now create the metadata array:
+$metadata = [
+ 'story_countdowns' => [
+ [
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'z' => 0, // Don't change this value.
+ 'width' => 0.6564815, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.22630993, // ...
+ 'rotation' => 0.0,
+ 'text' => 'NEXT API RELEASE', // Name of the countdown. Make sure it's in all caps!
+ 'text_color' => '#ffffff',
+ 'start_background_color' => '#ca2ee1',
+ 'end_background_color' => '#5eb1ff',
+ 'digit_color' => '#7e0091',
+ 'digit_card_color' => '#ffffff',
+ 'end_ts' => time() + strtotime('1 day', 0), // UNIX Epoch of when the countdown expires.
+ 'following_enabled' => true, // If true, viewers can subscribe to a notification when the countdown expires.
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryFundraiser.php b/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryFundraiser.php
new file mode 100755
index 0000000..2562453
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryFundraiser.php
@@ -0,0 +1,54 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+//Get the first recommended charity.
+$charityUser = $ig->story->getCharities()->getSearchedCharities()[0];
+
+// Now create the metadata array:
+$metadata = [
+ 'story_fundraisers' => $ig->story->createDonateSticker($charityUser),
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryPoll.php b/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryPoll.php
new file mode 100755
index 0000000..eb348dd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryPoll.php
@@ -0,0 +1,78 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// NOTE: This code will make a story poll sticker with the two answers provided,
+// but YOU need to manually draw the question on top of your image yourself
+// before uploading if you want the question to be visible.
+
+// Now create the metadata array:
+$metadata = [
+ 'story_polls' => [
+ // Note that you can only do one story poll in this array.
+ [
+ 'question' => 'Is this API great?', // Story poll question. You need to manually to draw it on top of your image.
+ 'viewer_vote' => 0, // Don't change this value.
+ 'viewer_can_vote' => true, // Don't change this value.
+ 'tallies' => [
+ [
+ 'text' => 'Best API!', // Answer 1.
+ 'count' => 0, // Don't change this value.
+ 'font_size' => 35.0, // Range: 17.5 - 35.0.
+ ],
+ [
+ 'text' => 'The doubt offends', // Answer 2.
+ 'count' => 0, // Don't change this value.
+ 'font_size' => 27.5, // Range: 17.5 - 35.0.
+ ],
+ ],
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'width' => 0.5661107, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.10647108, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryQuestion.php b/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryQuestion.php
new file mode 100755
index 0000000..ac84854
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/uploadStoryQuestion.php
@@ -0,0 +1,69 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// Get the profile picture pic url to display on the question.
+$profilePicUrl = $ig->account->getCurrentUser()->getUser()->getProfilePicUrl();
+
+// Now create the metadata array:
+$metadata = [
+ 'story_questions' => [
+ // Note that you can only do one story question in this array.
+ [
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5004223, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'z' => 0, // Don't change this value.
+ 'width' => 0.63118356, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.22212838, // ...
+ 'rotation' => 0.0,
+ 'viewer_can_interact' => false, // Don't change this value.
+ 'background_color' => '#ffffff',
+ 'profile_pic_url' => $profilePicUrl, // Must be the profile pic url of the account you are posting from!
+ 'question_type' => 'text', // Don't change this value.
+ 'question' => 'What do you want to see in the API?', // Story question.
+ 'text_color' => '#000000',
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/uploadStorySlider.php b/instafeed/vendor/mgp25/instagram-php/examples/uploadStorySlider.php
new file mode 100755
index 0000000..8465b58
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/uploadStorySlider.php
@@ -0,0 +1,71 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// NOTE: This code will make a story poll sticker with the two answers provided,
+// but YOU need to manually draw the question on top of your image yourself
+// before uploading if you want the question to be visible.
+
+// Now create the metadata array:
+$metadata = [
+ 'story_sliders' => [
+ // Note that you can only do one story poll in this array.
+ [
+ 'question' => 'Is this API great?', // Story poll question. You need to manually to draw it on top of your image.
+ 'viewer_vote' => 0, // Don't change this value.
+ 'viewer_can_vote' => false, // Don't change this value.
+ 'slider_vote_count' => 0, // Don't change this value.
+ 'slider_vote_average' => 0, // Don't change this value.
+ 'background_color' => '#ffffff',
+ 'text_color' => '#000000',
+ 'emoji' => '😍',
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5004223, // Also note that X/Y is setting the position of the CENTER of the clickable area
+ 'width' => 0.7777778, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.22212838, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/examples/uploadVideo.php b/instafeed/vendor/mgp25/instagram-php/examples/uploadVideo.php
new file mode 100755
index 0000000..b25fc75
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/examples/uploadVideo.php
@@ -0,0 +1,46 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Note that all video upload functions perform some automatic chunk upload
+ // retries, in case of failing to upload all video chunks to Instagram's
+ // server! Uploads therefore take longer when their server is overloaded.
+
+ // If you want to guarantee that the file is valid (correct format, codecs,
+ // width, height and aspect ratio), then you can run it through our
+ // automatic video processing class. It only does any work when the input
+ // video file is invalid, so you may want to always use it. You have nothing
+ // to worry about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $video = new \InstagramAPI\Media\Video\InstagramVideo($videoFilename);
+ $ig->timeline->uploadVideo($video->getFile(), ['caption' => $captionText]);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/extradocs/Push_setPreferences.txt b/instafeed/vendor/mgp25/instagram-php/extradocs/Push_setPreferences.txt
new file mode 100755
index 0000000..3a14ed4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/extradocs/Push_setPreferences.txt
@@ -0,0 +1,90 @@
+===================
+Push::setPreferences
+===================
+
+Description
+------------
+Set push preferences.
+
+Params
+------------
+param array $preferences.
+
+Extra Documentation
+--------------------
+
+Each preference can be set either to 'Off', 'Followings' or 'Everyone'/'Enabled'.
+In this context:
+
+'Off' is 1.
+'Followings' is 2.
+'Everyone' is 3.
+
+** Warning **
+
+You can only set preferences if you are eligible for them. See Push::getPreferences()
+to obtain the values for all of your available preferences.
+
+Preferences and possible options
+---------------------------------
+
+Name: likes.
+Options: 1, 2, 3.
+
+Name: comments.
+Options: 1, 2, 3.
+
+Name: comment_likes.
+Options: 1, 2.
+
+Name: like_and_comment_on_photo_user_tagged.
+Options: 1, 2, 3.
+
+Name: live_broadcast.
+Options: 1, 3.
+
+Name: new_follower.
+Options: 1, 3.
+
+Name: follow_request_accepted.
+Options: 1, 3.
+
+Name: contact_joined.
+Options: 1, 3.
+
+Name: pending_direct_share.
+Options: 1, 3.
+
+Name: direct_share_activity.
+Options: 1, 3.
+
+Name: user_tagged.
+Options: 1, 2, 3.
+
+Name: notification_reminders.
+Options: 1, 3.
+
+Name: first_post.
+Options: 1, 2, 3.
+
+Name: announcements.
+Options: 1, 3.
+
+Name: ads.
+Options: 1, 3.
+
+Name: view_count.
+Options: 1, 3.
+
+Name: report_updated.
+Options: 1, 3.
+
+Example
+---------
+
+$preferences = [
+ 'likes' => 3,
+ 'comment_likes' => 2
+ ];
+
+$ig->push->setPreferences($preferences);
diff --git a/instafeed/vendor/mgp25/instagram-php/src/AutoPropertyMapper.php b/instafeed/vendor/mgp25/instagram-php/src/AutoPropertyMapper.php
new file mode 100755
index 0000000..9461f25
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/AutoPropertyMapper.php
@@ -0,0 +1,22 @@
+_parent = $parent;
+
+ // Defaults.
+ $this->_verifySSL = true;
+ $this->_proxy = null;
+
+ // Create a default handler stack with Guzzle's auto-selected "best
+ // possible transfer handler for the user's system", and with all of
+ // Guzzle's default middleware (cookie jar support, etc).
+ $stack = HandlerStack::create();
+
+ // Create our cookies middleware and add it to the stack.
+ $this->_fakeCookies = new FakeCookies();
+ $stack->push($this->_fakeCookies, 'fake_cookies');
+
+ $this->_zeroRating = new ZeroRating();
+ $stack->push($this->_zeroRating, 'zero_rewrite');
+
+ // Default request options (immutable after client creation).
+ $this->_guzzleClient = new GuzzleClient([
+ 'handler' => $stack, // Our middleware is now injected.
+ 'allow_redirects' => [
+ 'max' => 8, // Allow up to eight redirects (that's plenty).
+ ],
+ 'connect_timeout' => 30.0, // Give up trying to connect after 30s.
+ 'decode_content' => true, // Decode gzip/deflate/etc HTTP responses.
+ 'timeout' => 240.0, // Maximum per-request time (seconds).
+ // Tells Guzzle to stop throwing exceptions on non-"2xx" HTTP codes,
+ // thus ensuring that it only triggers exceptions on socket errors!
+ // We'll instead MANUALLY be throwing on certain other HTTP codes.
+ 'http_errors' => false,
+ ]);
+
+ $this->_resetConnection = false;
+ }
+
+ /**
+ * Resets certain Client settings via the current Settings storage.
+ *
+ * Used whenever we switch active user, to configure our internal state.
+ *
+ * @param bool $resetCookieJar (optional) Whether to clear current cookies.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function updateFromCurrentSettings(
+ $resetCookieJar = false)
+ {
+ // Update our internal client state from the new user's settings.
+ $this->_userAgent = $this->_parent->device->getUserAgent();
+ $this->loadCookieJar($resetCookieJar);
+
+ // Verify that the jar contains a non-expired csrftoken for the API
+ // domain. Instagram gives us a 1-year csrftoken whenever we log in.
+ // If it's missing, we're definitely NOT logged in! But even if all of
+ // these checks succeed, the cookie may still not be valid. It's just a
+ // preliminary check to detect definitely-invalid session cookies!
+ if ($this->getToken() === null) {
+ $this->_parent->isMaybeLoggedIn = false;
+ }
+
+ // Load rewrite rules (if any).
+ $this->zeroRating()->update($this->_parent->settings->getRewriteRules());
+ }
+
+ /**
+ * Loads all cookies via the current Settings storage.
+ *
+ * @param bool $resetCookieJar (optional) Whether to clear current cookies.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function loadCookieJar(
+ $resetCookieJar = false)
+ {
+ // Mark any previous cookie jar for garbage collection.
+ $this->_cookieJar = null;
+
+ // Delete all current cookies from the storage if this is a reset.
+ if ($resetCookieJar) {
+ $this->_parent->settings->setCookies('');
+ }
+
+ // Get all cookies for the currently active user.
+ $cookieData = $this->_parent->settings->getCookies();
+
+ // Attempt to restore the cookies, otherwise create a new, empty jar.
+ $restoredCookies = is_string($cookieData) ? @json_decode($cookieData, true) : null;
+ if (!is_array($restoredCookies)) {
+ $restoredCookies = [];
+ }
+
+ // Memory-based cookie jar which must be manually saved later.
+ $this->_cookieJar = new CookieJar(false, $restoredCookies);
+
+ // Reset the "last saved" timestamp to the current time to prevent
+ // auto-saving the cookies again immediately after this jar is loaded.
+ $this->_cookieJarLastSaved = time();
+ }
+
+ /**
+ * Retrieve the CSRF token from the current cookie jar.
+ *
+ * Note that Instagram gives you a 1-year token expiration timestamp when
+ * you log in. But if you log out, they set its timestamp to "0" which means
+ * that the cookie is "expired" and invalid. We ignore token cookies if they
+ * have been logged out, or if they have expired naturally.
+ *
+ * @return string|null The token if found and non-expired, otherwise NULL.
+ */
+ public function getToken()
+ {
+ $cookie = $this->getCookie('csrftoken', 'i.instagram.com');
+ if ($cookie === null || $cookie->getValue() === '') {
+ return null;
+ }
+
+ return $cookie->getValue();
+ }
+
+ /**
+ * Searches for a specific cookie in the current jar.
+ *
+ * @param string $name The name of the cookie.
+ * @param string|null $domain (optional) Require a specific domain match.
+ * @param string|null $path (optional) Require a specific path match.
+ *
+ * @return \GuzzleHttp\Cookie\SetCookie|null A cookie if found and non-expired, otherwise NULL.
+ */
+ public function getCookie(
+ $name,
+ $domain = null,
+ $path = null)
+ {
+ $foundCookie = null;
+ if ($this->_cookieJar instanceof CookieJar) {
+ /** @var SetCookie $cookie */
+ foreach ($this->_cookieJar->getIterator() as $cookie) {
+ if ($cookie->getName() === $name
+ && !$cookie->isExpired()
+ && ($domain === null || $cookie->matchesDomain($domain))
+ && ($path === null || $cookie->matchesPath($path))) {
+ // Loop-"break" is omitted intentionally, because we might
+ // have more than one cookie with the same name, so we will
+ // return the LAST one. This is necessary because Instagram
+ // has changed their cookie domain from `i.instagram.com` to
+ // `.instagram.com` and we want the *most recent* cookie.
+ // Guzzle's `CookieJar::setCookie()` always places the most
+ // recently added/modified cookies at the *end* of array.
+ $foundCookie = $cookie;
+ }
+ }
+ }
+
+ return $foundCookie;
+ }
+
+ /**
+ * Gives you all cookies in the Jar encoded as a JSON string.
+ *
+ * This allows custom Settings storages to retrieve all cookies for saving.
+ *
+ * @throws \InvalidArgumentException If the JSON cannot be encoded.
+ *
+ * @return string
+ */
+ public function getCookieJarAsJSON()
+ {
+ if (!$this->_cookieJar instanceof CookieJar) {
+ return '[]';
+ }
+
+ // Gets ALL cookies from the jar, even temporary session-based cookies.
+ $cookies = $this->_cookieJar->toArray();
+
+ // Throws if data can't be encoded as JSON (will never happen).
+ $jsonStr = \GuzzleHttp\json_encode($cookies);
+
+ return $jsonStr;
+ }
+
+ /**
+ * Tells current settings storage to store cookies if necessary.
+ *
+ * NOTE: This Client class is NOT responsible for calling this function!
+ * Instead, our parent "Instagram" instance takes care of it and saves the
+ * cookies "onCloseUser", so that cookies are written to storage in a
+ * single, efficient write when the user's session is finished. We also call
+ * it during some important function calls such as login/logout. Client also
+ * automatically calls it when enough time has elapsed since last save.
+ *
+ * @throws \InvalidArgumentException If the JSON cannot be encoded.
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function saveCookieJar()
+ {
+ // Tell the settings storage to persist the latest cookies.
+ $newCookies = $this->getCookieJarAsJSON();
+ $this->_parent->settings->setCookies($newCookies);
+
+ // Reset the "last saved" timestamp to the current time.
+ $this->_cookieJarLastSaved = time();
+ }
+
+ /**
+ * Controls the SSL verification behavior of the Client.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#verify
+ *
+ * @param bool|string $state TRUE to verify using PHP's default CA bundle,
+ * FALSE to disable SSL verification (this is
+ * insecure!), String to verify using this path to
+ * a custom CA bundle file.
+ */
+ public function setVerifySSL(
+ $state)
+ {
+ $this->_verifySSL = $state;
+ }
+
+ /**
+ * Gets the current SSL verification behavior of the Client.
+ *
+ * @return bool|string
+ */
+ public function getVerifySSL()
+ {
+ return $this->_verifySSL;
+ }
+
+ /**
+ * Set the proxy to use for requests.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#proxy
+ *
+ * @param string|array|null $value String or Array specifying a proxy in
+ * Guzzle format, or NULL to disable proxying.
+ */
+ public function setProxy(
+ $value)
+ {
+ $this->_proxy = $value;
+ $this->_resetConnection = true;
+ }
+
+ /**
+ * Gets the current proxy used for requests.
+ *
+ * @return string|array|null
+ */
+ public function getProxy()
+ {
+ return $this->_proxy;
+ }
+
+ /**
+ * Sets the network interface override to use.
+ *
+ * Only works if Guzzle is using the cURL backend. But that's
+ * almost always the case, on most PHP installations.
+ *
+ * @see http://php.net/curl_setopt CURLOPT_INTERFACE
+ *
+ * @param string|null $value Interface name, IP address or hostname, or NULL to
+ * disable override and let Guzzle use any interface.
+ */
+ public function setOutputInterface(
+ $value)
+ {
+ $this->_outputInterface = $value;
+ $this->_resetConnection = true;
+ }
+
+ /**
+ * Gets the current network interface override used for requests.
+ *
+ * @return string|null
+ */
+ public function getOutputInterface()
+ {
+ return $this->_outputInterface;
+ }
+
+ /**
+ * Output debugging information.
+ *
+ * @param string $method "GET" or "POST".
+ * @param string $url The URL or endpoint used for the request.
+ * @param string|null $uploadedBody What was sent to the server. Use NULL to
+ * avoid displaying it.
+ * @param int|null $uploadedBytes How many bytes were uploaded. Use NULL to
+ * avoid displaying it.
+ * @param HttpResponseInterface $response The Guzzle response object from the request.
+ * @param string $responseBody The actual text-body reply from the server.
+ */
+ protected function _printDebug(
+ $method,
+ $url,
+ $uploadedBody,
+ $uploadedBytes,
+ HttpResponseInterface $response,
+ $responseBody)
+ {
+ Debug::printRequest($method, $url);
+
+ // Display the data body that was uploaded, if provided for debugging.
+ // NOTE: Only provide this from functions that submit meaningful BODY data!
+ if (is_string($uploadedBody)) {
+ Debug::printPostData($uploadedBody);
+ }
+
+ // Display the number of bytes uploaded in the data body, if provided for debugging.
+ // NOTE: Only provide this from functions that actually upload files!
+ if ($uploadedBytes !== null) {
+ Debug::printUpload(Utils::formatBytes($uploadedBytes));
+ }
+
+ // Display the number of bytes received from the response, and status code.
+ if ($response->hasHeader('x-encoded-content-length')) {
+ $bytes = Utils::formatBytes((int) $response->getHeaderLine('x-encoded-content-length'));
+ } elseif ($response->hasHeader('Content-Length')) {
+ $bytes = Utils::formatBytes((int) $response->getHeaderLine('Content-Length'));
+ } else {
+ $bytes = 0;
+ }
+ Debug::printHttpCode($response->getStatusCode(), $bytes);
+
+ // Display the actual API response body.
+ Debug::printResponse($responseBody, $this->_parent->truncatedDebug);
+ }
+
+ /**
+ * Maps a server response onto a specific kind of result object.
+ *
+ * The result is placed directly inside `$responseObject`.
+ *
+ * @param Response $responseObject An instance of a class object whose
+ * properties to fill with the response.
+ * @param string $rawResponse A raw JSON response string
+ * from Instagram's server.
+ * @param HttpResponseInterface $httpResponse HTTP response object.
+ *
+ * @throws InstagramException In case of invalid or failed API response.
+ */
+ public function mapServerResponse(
+ Response $responseObject,
+ $rawResponse,
+ HttpResponseInterface $httpResponse)
+ {
+ // Attempt to decode the raw JSON to an array.
+ // Important: Special JSON decoder which handles 64-bit numbers!
+ $jsonArray = $this->api_body_decode($rawResponse, true);
+
+ // If the server response is not an array, it means that JSON decoding
+ // failed or some other bad thing happened. So analyze the HTTP status
+ // code (if available) to see what really happened.
+ if (!is_array($jsonArray)) {
+ $httpStatusCode = $httpResponse !== null ? $httpResponse->getStatusCode() : null;
+ switch ($httpStatusCode) {
+ case 400:
+ throw new \InstagramAPI\Exception\BadRequestException('Invalid request options.');
+ case 404:
+ throw new \InstagramAPI\Exception\NotFoundException('Requested resource does not exist.');
+ default:
+ throw new \InstagramAPI\Exception\EmptyResponseException('No response from server. Either a connection or configuration error.');
+ }
+ }
+
+ // Perform mapping of all response properties.
+ try {
+ // Assign the new object data. Only throws if custom _init() fails.
+ // NOTE: False = assign data without automatic analysis.
+ $responseObject->assignObjectData($jsonArray, false); // Throws.
+
+ // Use API developer debugging? We'll throw if class lacks property
+ // definitions, or if they can't be mapped as defined in the class
+ // property map. But we'll ignore missing properties in our custom
+ // UnpredictableKeys containers, since those ALWAYS lack keys. ;-)
+ if ($this->_parent->apiDeveloperDebug) {
+ // Perform manual analysis (so that we can intercept its analysis result).
+ $analysis = $responseObject->exportClassAnalysis(); // Never throws.
+
+ // Remove all "missing_definitions" errors for UnpredictableKeys containers.
+ // NOTE: We will keep any "bad_definitions" errors for them.
+ foreach ($analysis->missing_definitions as $className => $x) {
+ if (strpos($className, '\\Response\\Model\\UnpredictableKeys\\') !== false) {
+ unset($analysis->missing_definitions[$className]);
+ }
+ }
+
+ // If any problems remain after that, throw with all combined summaries.
+ if ($analysis->hasProblems()) {
+ throw new LazyJsonMapperException(
+ $analysis->generateNiceSummariesAsString()
+ );
+ }
+ }
+ } catch (LazyJsonMapperException $e) {
+ // Since there was a problem, let's help our developers by
+ // displaying the server's JSON data in a human-readable format,
+ // which makes it easy to see the structure and necessary changes
+ // and speeds up the job of updating responses and models.
+ try {
+ // Decode to stdClass to properly preserve empty objects `{}`,
+ // otherwise they would appear as empty `[]` arrays in output.
+ // NOTE: Large >32-bit numbers will be transformed into strings,
+ // which helps us see which numeric values need "string" type.
+ $jsonObject = $this->api_body_decode($rawResponse, false);
+ if (is_object($jsonObject)) {
+ $prettyJson = @json_encode(
+ $jsonObject,
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
+ );
+ if ($prettyJson !== false) {
+ Debug::printResponse(
+ 'Human-Readable Response:'.PHP_EOL.$prettyJson,
+ false // Not truncated.
+ );
+ }
+ }
+ } catch (\Exception $e) {
+ // Ignore errors.
+ }
+
+ // Exceptions will only be thrown if API developer debugging is
+ // enabled and finds a problem. Either way, we should re-wrap the
+ // exception to our native type instead. The message gives enough
+ // details and we don't need to know the exact Lazy sub-exception.
+ throw new InstagramException($e->getMessage());
+ }
+
+ // Save the HTTP response object as the "getHttpResponse()" value.
+ $responseObject->setHttpResponse($httpResponse);
+
+ // Throw an exception if the API response was unsuccessful.
+ // NOTE: It will contain the full server response object too, which
+ // means that the user can look at the full response details via the
+ // exception itself.
+ if (!$responseObject->isOk()) {
+ if ($responseObject instanceof \InstagramAPI\Response\DirectSendItemResponse && $responseObject->getPayload() !== null) {
+ $message = $responseObject->getPayload()->getMessage();
+ } else {
+ $message = $responseObject->getMessage();
+ }
+
+ try {
+ ServerMessageThrower::autoThrow(
+ get_class($responseObject),
+ $message,
+ $responseObject,
+ $httpResponse
+ );
+ } catch (LoginRequiredException $e) {
+ // Instagram told us that our session is invalid (that we are
+ // not logged in). Update our cached "logged in?" state. This
+ // ensures that users with various retry-algorithms won't hammer
+ // their server. When this flag is false, ALL further attempts
+ // at AUTHENTICATED requests will be aborted by our library.
+ $this->_parent->isMaybeLoggedIn = false;
+
+ throw $e; // Re-throw.
+ }
+ }
+ }
+
+ /**
+ * Helper which builds in the most important Guzzle options.
+ *
+ * Takes care of adding all critical options that we need on every request.
+ * Such as cookies and the user's proxy. But don't call this function
+ * manually. It's automatically called by _guzzleRequest()!
+ *
+ * @param array $guzzleOptions The options specific to the current request.
+ *
+ * @return array A guzzle options array.
+ */
+ protected function _buildGuzzleOptions(
+ array $guzzleOptions = [])
+ {
+ $criticalOptions = [
+ 'cookies' => ($this->_cookieJar instanceof CookieJar ? $this->_cookieJar : false),
+ 'verify' => $this->_verifySSL,
+ 'proxy' => ($this->_proxy !== null ? $this->_proxy : null),
+ ];
+
+ // Critical options always overwrite identical keys in regular opts.
+ // This ensures that we can't screw up the proxy/verify/cookies.
+ $finalOptions = array_merge($guzzleOptions, $criticalOptions);
+
+ // Now merge any specific Guzzle cURL-backend overrides. We must do this
+ // separately since it's in an associative array and we can't just
+ // overwrite that whole array in case the caller had curl options.
+ if (!array_key_exists('curl', $finalOptions)) {
+ $finalOptions['curl'] = [];
+ }
+
+ // Add their network interface override if they want it.
+ // This option MUST be non-empty if set, otherwise it breaks cURL.
+ if (is_string($this->_outputInterface) && $this->_outputInterface !== '') {
+ $finalOptions['curl'][CURLOPT_INTERFACE] = $this->_outputInterface;
+ }
+ if ($this->_resetConnection) {
+ $finalOptions['curl'][CURLOPT_FRESH_CONNECT] = true;
+ $this->_resetConnection = false;
+ }
+
+ return $finalOptions;
+ }
+
+ /**
+ * Wraps Guzzle's request and adds special error handling and options.
+ *
+ * Automatically throws exceptions on certain very serious HTTP errors. And
+ * re-wraps all Guzzle errors to our own internal exceptions instead. You
+ * must ALWAYS use this (or _apiRequest()) instead of the raw Guzzle Client!
+ * However, you can never assume the server response contains what you
+ * wanted. Be sure to validate the API reply too, since Instagram's API
+ * calls themselves may fail with a JSON message explaining what went wrong.
+ *
+ * WARNING: This is a semi-lowlevel handler which only applies critical
+ * options and HTTP connection handling! Most functions will want to call
+ * _apiRequest() instead. An even higher-level handler which takes care of
+ * debugging, server response checking and response decoding!
+ *
+ * @param HttpRequestInterface $request HTTP request to send.
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ *
+ * @throws \InstagramAPI\Exception\NetworkException For any network/socket related errors.
+ * @throws \InstagramAPI\Exception\ThrottledException When we're throttled by server.
+ * @throws \InstagramAPI\Exception\RequestHeadersTooLargeException When request is too large.
+ *
+ * @return HttpResponseInterface
+ */
+ protected function _guzzleRequest(
+ HttpRequestInterface $request,
+ array $guzzleOptions = [])
+ {
+ // Add critically important options for authenticating the request.
+ $guzzleOptions = $this->_buildGuzzleOptions($guzzleOptions);
+
+ // Attempt the request. Will throw in case of socket errors!
+ try {
+ $response = $this->_guzzleClient->send($request, $guzzleOptions);
+ } catch (\Exception $e) {
+ // Re-wrap Guzzle's exception using our own NetworkException.
+ throw new \InstagramAPI\Exception\NetworkException($e);
+ }
+
+ // Detect very serious HTTP status codes in the response.
+ $httpCode = $response->getStatusCode();
+ switch ($httpCode) {
+ case 429: // "429 Too Many Requests"
+ throw new \InstagramAPI\Exception\ThrottledException('Throttled by Instagram because of too many API requests.');
+ break;
+ case 431: // "431 Request Header Fields Too Large"
+ throw new \InstagramAPI\Exception\RequestHeadersTooLargeException('The request start-line and/or headers are too large to process.');
+ break;
+ // WARNING: Do NOT detect 404 and other higher-level HTTP errors here,
+ // since we catch those later during steps like mapServerResponse()
+ // and autoThrow. This is a warning to future contributors!
+ }
+
+ // We'll periodically auto-save our cookies at certain intervals. This
+ // complements the "onCloseUser" and "login()/logout()" force-saving.
+ if ((time() - $this->_cookieJarLastSaved) > self::COOKIE_AUTOSAVE_INTERVAL) {
+ $this->saveCookieJar();
+ }
+
+ // The response may still have serious but "valid response" errors, such
+ // as "400 Bad Request". But it's up to the CALLER to handle those!
+ return $response;
+ }
+
+ /**
+ * Internal wrapper around _guzzleRequest().
+ *
+ * This takes care of many common additional tasks needed by our library,
+ * so you should try to always use this instead of the raw _guzzleRequest()!
+ *
+ * Available library options are:
+ * - 'noDebug': Can be set to TRUE to forcibly hide debugging output for
+ * this request. The user controls debugging globally, but this is an
+ * override that prevents them from seeing certain requests that you may
+ * not want to trigger debugging (such as perhaps individual steps of a
+ * file upload process). However, debugging SHOULD be allowed in MOST cases!
+ * So only use this feature if you have a very good reason.
+ * - 'debugUploadedBody': Set to TRUE to make debugging display the data that
+ * was uploaded in the body of the request. DO NOT use this if your function
+ * uploaded binary data, since printing those bytes would kill the terminal!
+ * - 'debugUploadedBytes': Set to TRUE to make debugging display the size of
+ * the uploaded body data. Should ALWAYS be TRUE when uploading binary data.
+ *
+ * @param HttpRequestInterface $request HTTP request to send.
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ * @param array $libraryOptions Additional options for controlling Library features
+ * such as the debugging output.
+ *
+ * @throws \InstagramAPI\Exception\NetworkException For any network/socket related errors.
+ * @throws \InstagramAPI\Exception\ThrottledException When we're throttled by server.
+ *
+ * @return HttpResponseInterface
+ */
+ protected function _apiRequest(
+ HttpRequestInterface $request,
+ array $guzzleOptions = [],
+ array $libraryOptions = [])
+ {
+ // Perform the API request and retrieve the raw HTTP response body.
+ $guzzleResponse = $this->_guzzleRequest($request, $guzzleOptions);
+
+ // Debugging (must be shown before possible decoding error).
+ if ($this->_parent->debug && (!isset($libraryOptions['noDebug']) || !$libraryOptions['noDebug'])) {
+ // Determine whether we should display the contents of the UPLOADED body.
+ if (isset($libraryOptions['debugUploadedBody']) && $libraryOptions['debugUploadedBody']) {
+ $uploadedBody = (string) $request->getBody();
+ if (!strlen($uploadedBody)) {
+ $uploadedBody = null;
+ }
+ } else {
+ $uploadedBody = null; // Don't display.
+ }
+
+ // Determine whether we should display the size of the UPLOADED body.
+ if (isset($libraryOptions['debugUploadedBytes']) && $libraryOptions['debugUploadedBytes']) {
+ // Calculate the uploaded bytes by looking at request's body size, if it exists.
+ $uploadedBytes = $request->getBody()->getSize();
+ } else {
+ $uploadedBytes = null; // Don't display.
+ }
+
+ $this->_printDebug(
+ $request->getMethod(),
+ $this->_zeroRating->rewrite((string) $request->getUri()),
+ $uploadedBody,
+ $uploadedBytes,
+ $guzzleResponse,
+ (string) $guzzleResponse->getBody());
+ }
+
+ return $guzzleResponse;
+ }
+
+ /**
+ * Perform an Instagram API call.
+ *
+ * @param HttpRequestInterface $request HTTP request to send.
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ *
+ * @throws InstagramException
+ *
+ * @return HttpResponseInterface
+ */
+ public function api(
+ HttpRequestInterface $request,
+ array $guzzleOptions = [])
+ {
+ // Set up headers that are required for every request.
+ $request = modify_request($request, [
+ 'set_headers' => [
+ 'User-Agent' => $this->_userAgent,
+ // Keep the API's HTTPS connection alive in Guzzle for future
+ // re-use, to greatly speed up all further queries after this.
+ 'Connection' => 'Keep-Alive',
+ 'Accept' => '*/*',
+ 'Accept-Encoding' => Constants::ACCEPT_ENCODING,
+ 'Accept-Language' => Constants::ACCEPT_LANGUAGE,
+ ],
+ ]);
+
+ // Check the Content-Type header for debugging.
+ $contentType = $request->getHeader('Content-Type');
+ $isFormData = count($contentType) && reset($contentType) === Constants::CONTENT_TYPE;
+
+ // Perform the API request.
+ $response = $this->_apiRequest($request, $guzzleOptions, [
+ 'debugUploadedBody' => $isFormData,
+ 'debugUploadedBytes' => !$isFormData,
+ ]);
+
+ return $response;
+ }
+
+ /**
+ * Decode a JSON reply from Instagram's API.
+ *
+ * WARNING: EXTREMELY IMPORTANT! NEVER, *EVER* USE THE BASIC "json_decode"
+ * ON API REPLIES! ALWAYS USE THIS METHOD INSTEAD, TO ENSURE PROPER DECODING
+ * OF BIG NUMBERS! OTHERWISE YOU'LL TRUNCATE VARIOUS INSTAGRAM API FIELDS!
+ *
+ * @param string $json The body (JSON string) of the API response.
+ * @param bool $assoc When FALSE, decode to object instead of associative array.
+ *
+ * @return object|array|null Object if assoc false, Array if assoc true,
+ * or NULL if unable to decode JSON.
+ */
+ public static function api_body_decode(
+ $json,
+ $assoc = true)
+ {
+ return @json_decode($json, $assoc, 512, JSON_BIGINT_AS_STRING);
+ }
+
+ /**
+ * Get the cookies middleware instance.
+ *
+ * @return FakeCookies
+ */
+ public function fakeCookies()
+ {
+ return $this->_fakeCookies;
+ }
+
+ /**
+ * Get the zero rating rewrite middleware instance.
+ *
+ * @return ZeroRating
+ */
+ public function zeroRating()
+ {
+ return $this->_zeroRating;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Constants.php b/instafeed/vendor/mgp25/instagram-php/src/Constants.php
new file mode 100755
index 0000000..2795e82
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Constants.php
@@ -0,0 +1,103 @@
+ 'https://i.instagram.com/api/v1/',
+ 2 => 'https://i.instagram.com/api/v2/',
+ ];
+ const IG_VERSION = '107.0.0.27.121';
+ const VERSION_CODE = '168361634';
+ const IG_SIG_KEY = 'c36436a942ea1dbb40d7f2d7d45280a620d991ce8c62fb4ce600f0a048c32c11';
+ const EXPERIMENTS = 'ig_android_push_notifications_settings_redesign_universe,ig_hashtag_display_universe,ig_android_video_ssim_fix_pts_universe,coupon_price_test_ad4ad_instagram_resurrection_universe,ig_android_live_rendering_looper_universe,ig_shopping_checkout_improvements_universe,ig_android_mqtt_cookie_auth_memcache_universe,ig_android_optional_contact_and_preset_universe,ig_android_video_player_memory_leaks,ig_android_stories_seen_state_serialization,ig_stories_photo_time_duration_universe,ig_android_bitmap_cache_executor_size,ig_android_stories_music_search_typeahead,android_ard_ig_use_brotli_effect_universe,ig_android_remove_fb_nux_universe,ig_android_delayed_comments,ig_android_direct_mutation_manager_media_3,ig_smb_ads_holdout_2019_h1_universe,ig_fb_graph_differentiation,ig_android_stories_share_extension_video_segmentation,ig_android_igtv_crop_top,ig_android_stories_create_flow_favorites_tooltip,ig_android_direct_reshare_chaining,ig_android_stories_no_inflation_on_app_start,ig_android_stories_viewer_viewpoint_universe,ig_android_separate_empty_feed_su_universe,ig_android_zero_rating_carrier_signal,ig_direct_holdout_h1_2019,ig_explore_2019_h1_destination_cover,ig_android_direct_stories_in_direct_inbox,ig_android_explore_recyclerview_universe,ig_android_show_muted_accounts_page,ig_android_vc_service_crash_fix_universe,ig_camera_android_subtle_filter_universe,ig_android_lazy_init_live_composer_controller,ig_fb_graph_differentiation_no_fb_data,ig_android_viewpoint_stories_public_testing,ig_camera_android_api_rewrite_universe,ig_android_growth_fci_team_holdout_universe,android_camera_core_cpu_frames_sync,ig_android_video_source_sponsor_fix,ig_android_save_all,ig_android_ttcp_improvements,ig_android_camera_ar_platform_profile_universe,ig_android_separate_sms_n_email_invites_setting_universe,ig_shopping_bag_universe,ig_ar_shopping_camera_android_universe,ig_android_recyclerview_binder_group_enabled_universe,ig_android_stories_viewer_tall_android_cap_media_universe,ig_android_video_exoplayer_2,native_contact_invites_universe,ig_android_stories_seen_state_processing_universe,ig_android_dash_script,ig_android_insights_media_hashtag_insight_universe,ig_android_search_qpl_switch,ig_camera_fast_tti_universe,ig_android_igtv_improved_search,ig_android_stories_music_filters,ig_android_render_thread_memory_leak_holdout,ig_android_automated_logging,ig_android_viewmaster_post_capture_universe,ig_android_2018_h1_hashtag_report_universe,ig_android_camera_effect_gallery_prefetch,ig_share_to_story_toggle_include_shopping_product,ig_android_interactions_verified_badge_on_comment_details,ig_android_fix_ppr_thumbnail_url,ig_android_camera_reduce_file_exif_reads,ig_interactions_project_daisy_creators_universe,ig_payments_billing_address,ig_android_fs_new_gallery_hashtag_prompts,ig_camera_android_gyro_senser_sampling_period_universe,ig_android_xposting_feed_to_stories_reshares_universe,ig_android_combined_consumption,ig_camera_remove_display_rotation_cb_universe,ig_android_interactions_migrate_inline_composer_to_viewpoint_universe,ig_android_ufiv3_holdout,ig_android_neue_igtv_profile_tab_rollout,ig_android_enable_zero_rating,ig_android_story_ads_carousel_performance_universe_1,ig_android_direct_leave_from_group_message_requests,ig_android_import_page_post_after_biz_conversion,ig_camera_ar_effect_attribution_position,ig_promote_add_payment_navigation_universe,ig_android_story_ads_carousel_performance_universe_2,ig_android_main_feed_refresh_style_universe,ig_stories_engagement_holdout_2019_h1_universe,ig_android_story_ads_performance_universe_1,ig_android_stories_viewer_modal_activity,ig_android_story_ads_performance_universe_2,ig_android_publisher_stories_migration,ig_android_story_ads_performance_universe_3,ig_android_quick_conversion_universe,ig_android_story_import_intent,ig_android_story_ads_performance_universe_4,instagram_android_profile_follow_cta_context_feed,ig_biz_graph_connection_universe,ig_android_stories_boomerang_v2_universe,ig_android_ads_profile_cta_feed_universe,ig_android_vc_cowatch_universe,ig_android_nametag,ig_hashtag_creation_universe,ig_android_igtv_chaining,ig_android_live_qa_viewer_v1_universe,ig_shopping_insights_wc_copy_update_android,ig_android_stories_music_lyrics_pre_capture,android_cameracore_fbaudio_integration_ig_universe,ig_android_camera_stopmotion,ig_android_igtv_reshare,ig_android_wellbeing_timeinapp_v1_universe,ig_android_profile_cta_v3,ig_end_of_feed_universe,ig_android_mainfeed_generate_prefetch_background,ig_android_vc_shareable_moments_universe,ig_camera_text_overlay_controller_opt_universe,ig_android_shopping_product_metadata_on_product_tiles_universe,ig_android_video_qp_logger_universe,ig_android_shopping_pdp_cache,ig_android_follow_request_button_improvements_universe,ig_android_vc_start_from_direct_inbox_universe,ig_android_separate_network_executor,ig_perf_android_holdout,ig_fb_graph_differentiation_only_fb_candidates,ig_android_media_streaming_sdk_universe,ig_android_direct_reshares_from_thread,ig_android_stories_video_prefetch_kb,ig_android_wellbeing_timeinapp_v1_migration,ig_android_camera_post_smile_face_first_universe,ig_android_maintabfragment,ig_android_cookie_injection_retry_universe,ig_inventory_connections,ig_stories_injection_tool_enabled_universe,ig_android_canvas_cookie_universe,ig_android_stories_disable_highlights_media_preloading,ig_android_effect_gallery_post_capture_universe,ig_android_shopping_variant_selector_redesign,ig_android_branded_content_ads_universe,ig_promote_lotus_universe,ig_android_video_streaming_upload_universe,ig_camera_android_attribution_bottomsheet_universe,ig_android_product_tag_hint_dots,ig_interactions_h1_2019_team_holdout_universe,ig_camera_android_release_drawing_view_universe,ig_android_music_story_fb_crosspost_universe,ig_android_disable_scroll_listeners,ig_carousel_bumped_organic_impression_client_universe,ig_android_ad_async_ads_universe,ig_biz_post_approval_nux_universe,ig_android_vc_participants_grid_refactor_universe,android_ard_ig_modelmanager_cache_hit,ig_android_persistent_nux,ig_android_crash_fix_detach_from_gl_context,ig_android_branded_content_upsell_keywords_extension,ig_android_vc_ringscreen_timeout_universe,ig_android_edit_location_page_info,ig_android_stories_project_eclipse,ig_camera_android_segmentation_v106_igdjango_universe,ig_android_camera_recents_gallery_modal,ig_promote_are_you_sure_universe,ig_android_li_session_chaining,ig_android_camera_platform_effect_share_universe,ig_android_rate_limit_mediafeedviewablehelper,ig_android_search_empty_state,ig_android_camera_ar_platform_logging,ig_stories_engagement_holdout_2019_h2_universe,ig_android_search_remove_null_state_sections,ig_direct_android_mentions_receiver,ig_camera_android_device_capabilities_experiment,ig_android_stories_viewer_drawable_cache_universe,ig_camera_android_qcc_constructor_opt_universe,ig_android_stories_alignment_guides_universe,ig_android_rn_ads_manager_universe,ig_android_video_visual_quality_score_based_abr,ig_explore_2018_post_chaining_account_recs_dedupe_universe,ig_android_stories_video_seeking_audio_bug_fix,ig_android_insights_holdout,ig_android_do_not_show_social_context_for_likes_page_universe,ig_android_context_feed_recycler_view,ig_fb_notification_universe,ig_android_report_website_universe,ig_android_feed_post_sticker,ig_android_inline_editing_local_prefill,ig_android_commerce_platform_bloks_universe,ig_android_stories_unregister_decor_listener_universe,ig_android_search_condensed_search_icons,ig_android_video_abr_universe,ig_android_blended_inbox_split_button_v2_universe,ig_android_nelson_v0_universe,ig_android_scroll_audio_priority,ig_android_own_profile_sharing_universe,ig_android_vc_cowatch_media_share_universe,ig_biz_graph_unify_assoc_universe,ig_challenge_general_v2,ig_android_place_signature_universe,ig_android_direct_inbox_cache_universe,ig_android_ig_branding_in_fb_universe,ig_android_business_promote_tooltip,ig_android_tap_to_capture_universe,ig_android_follow_requests_ui_improvements,ig_android_video_ssim_fix_compare_frame_index,ig_android_direct_aggregated_media_and_reshares,ig_android_story_camera_share_to_feed_universe,ig_android_fb_follow_server_linkage_universe,ig_android_stories_viewer_reply_box_placeholder_copy,ig_android_biz_reorder_value_props,ig_android_direct_view_more_qe,ig_android_churned_find_friends_redirect_to_discover_people,ig_android_main_feed_new_posts_indicator_universe,ig_vp9_hd_blacklist,ig_camera_android_ar_effect_stories_deeplink,ig_android_client_side_delivery_universe,ig_ios_queue_time_qpl_universe,ig_android_fix_direct_badge_count_universe,ig_android_insights_audience_tab_native_universe,ig_android_stories_send_client_reels_on_tray_fetch_universe,ig_android_felix_prefetch_thumbnail_sprite_sheet,ig_android_live_use_rtc_upload_universe,ig_android_multi_dex_class_loader_v2,ig_android_live_ama_viewer_universe,ig_smb_ads_holdout_2018_h2_universe,ig_android_camera_post_smile_low_end_universe,ig_android_profile_follow_tab_hashtag_row_universe,ig_android_watch_and_more_redesign,igtv_feed_previews,ig_android_live_realtime_comments_universe,ig_android_story_insights_native_universe,ig_smb_ads_holdout_2019_h2_universe,ig_android_purx_native_checkout_universe,ig_camera_android_filter_optmizations,ig_android_integrity_sprint_universe,ig_android_apr_lazy_build_request_infra,ig_android_igds_edit_profile_fields,ig_android_business_transaction_in_stories_creator,ig_android_rounded_corner_framelayout_perf_fix,ig_android_branded_content_appeal_states,android_cameracore_ard_ig_integration,ig_video_experimental_encoding_consumption_universe,ig_android_iab_autofill,ig_android_creator_quick_reply_universe,ig_android_location_page_intent_survey,ig_camera_android_segmentation_async_universe,ig_android_biz_story_to_fb_page_improvement,ig_android_direct_thread_target_queue_universe,ig_android_branded_content_insights_disclosure,ig_camera_android_target_recognition_universe,ig_camera_android_skip_camera_initialization_open_to_post_capture,ig_android_combined_tagging_videos,ig_android_stories_samsung_sharing_integration,ig_android_create_page_on_top_universe,ig_iab_use_default_intent_loading,ig_android_camera_focus_v2,ig_android_biz_conversion_pull_suggest_biz_data,ig_discovery_holdout_2019_h1_universe,ig_android_wellbeing_support_frx_comment_reporting,ig_android_insights_post_dismiss_button,ig_android_user_url_deeplink_fbpage_endpoint,ig_android_ad_holdout_watchandmore_universe,ig_android_follow_request_button_new_ui,ig_iab_dns_prefetch_universe,ig_android_explore_use_shopping_endpoint,ig_android_image_upload_skip_queue_only_on_wifi,ig_android_igtv_pip,ig_android_ad_watchbrowse_carousel_universe,ig_android_camera_new_post_smile_universe,ig_android_shopping_signup_redesign_universe,ig_android_direct_hide_inbox_header,ig_shopping_pdp_more_related_product_section,ig_android_experimental_onetap_dialogs_universe,ig_android_fix_main_feed_su_cards_size_universe,ig_android_direct_multi_upload_universe,ig_camera_text_mode_composer_controller_opt_universe,ig_explore_2019_h1_video_autoplay_resume,ig_android_multi_capture_camera,ig_android_video_upload_quality_qe1,ig_android_follow_requests_copy_improvements,ig_android_save_collaborative_collections,coupon_price_test_boost_instagram_media_acquisition_universe,ig_android_video_outputsurface_handlerthread_universe,ig_android_country_code_fix_universe,ig_perf_android_holdout_2018_h1,ig_android_stories_music_overlay,ig_android_enable_lean_crash_reporting_universe,ig_android_resumable_downloads_logging_universe,ig_android_low_latency_consumption_universe,ig_android_render_output_surface_timeout_universe,ig_android_big_foot_foregroud_reporting,ig_android_unified_iab_logging_universe,ig_threads_app_close_friends_integration,ig_aggregated_quick_reactions,ig_android_shopping_pdp_post_purchase_sharing,ig_android_aggressive_cookie,ig_android_offline_mode_holdout,ig_android_realtime_mqtt_logging,ig_android_rainbow_hashtags,ig_android_no_bg_effect_tray_live_universe,ig_android_direct_block_from_group_message_requests,ig_android_react_native_universe_kill_switch,ig_android_viewpoint_occlusion,ig_android_logged_in_delta_migration,ig_android_push_reliability_universe,ig_android_stories_gallery_video_segmentation,ig_android_direct_business_holdout,ig_android_vc_direct_inbox_ongoing_pill_universe,ig_android_xposting_upsell_directly_after_sharing_to_story,ig_android_direct_sticker_search_upgrade_universe,ig_android_insights_native_post_universe,ig_android_dual_destination_quality_improvement,ig_android_camera_focus_low_end_universe,ig_android_camera_hair_segmentation_universe,ig_android_direct_combine_action_logs,ig_android_leak_detector_upload_universe,ig_android_ads_data_preferences_universe,ig_android_branded_content_access_upsell,ig_android_follow_button_in_story_viewers_list,ig_android_vc_background_call_toast_universe,ig_hashtag_following_holdout_universe,ig_promote_default_destination_universe,ig_android_delay_segmentation_low_end_universe,ig_android_direct_media_latency_optimizations,mi_viewpoint_viewability_universe,android_ard_ig_download_manager_v2,ig_direct_reshare_sharesheet_ranking,ig_music_dash,ig_android_fb_url_universe,ig_android_le_videoautoplay_disabled,ig_android_reel_raven_video_segmented_upload_universe,ig_android_promote_native_migration_universe,invite_friends_by_messenger_in_setting_universe,ig_android_fb_sync_options_universe,ig_android_thread_gesture_refactor,ig_android_stories_skip_seen_state_update_for_direct_stories,ig_android_recommend_accounts_destination_routing_fix,ig_android_fix_prepare_direct_push,ig_android_enable_automated_instruction_text_ar,ig_android_multi_author_story_reshare_universe,ig_android_building_aymf_universe,ig_android_internal_sticker_universe,ig_traffic_routing_universe,ig_android_payments_growth_business_payments_within_payments_universe,ig_camera_async_gallerycontroller_universe,ig_android_direct_state_observer,ig_android_page_claim_deeplink_qe,ig_android_camera_effects_order_universe,ig_android_video_controls_universe,ig_android_video_local_proxy_video_metadata,ig_android_logging_metric_universe_v2,ig_android_network_onbehavior_change_fix,ig_android_xposting_newly_fbc_people,ig_android_visualcomposer_inapp_notification_universe,ig_android_do_not_show_social_context_on_follow_list_universe,ig_android_contact_point_upload_rate_limit_killswitch,ig_android_webrtc_encoder_factory_universe,ig_android_qpl_class_marker,ig_android_fix_profile_pic_from_fb_universe,ig_android_sso_kototoro_app_universe,ig_android_camera_3p_in_post,ig_android_ar_effect_sticker_consumption_universe,ig_android_direct_unread_count_badge,ig_android_profile_thumbnail_impression,ig_android_igtv_autoplay_on_prepare,ig_android_list_adapter_prefetch_infra,ig_file_based_session_handler_2_universe,ig_branded_content_tagging_upsell,ig_android_clear_inflight_image_request,ig_android_main_feed_video_countdown_timer,ig_android_live_ama_universe,ig_android_external_gallery_import_affordance,ig_search_hashtag_content_advisory_remove_snooze,ig_payment_checkout_info,ig_android_optic_new_zoom_controller,ig_android_photos_qpl,ig_stories_ads_delivery_rules,ig_android_downloadable_spark_spot,ig_android_video_upload_iframe_interval,ig_business_new_value_prop_universe,ig_android_power_metrics,ig_android_vio_pipeline_universe,ig_android_show_profile_picture_upsell_in_reel_universe,ig_discovery_holdout_universe,ig_android_direct_import_google_photos2,ig_direct_feed_media_sticker_universe,ig_android_igtv_upload_error_messages,ig_android_stories_collapse_seen_segments,ig_android_self_profile_suggest_business_main,ig_android_suggested_users_background,ig_android_fetch_xpost_setting_in_camera_fully_open,ig_android_hashtag_discover_tab,ig_android_stories_separate_overlay_creation,ig_android_ads_bottom_sheet_report_flow,ig_android_login_onetap_upsell_universe,ig_android_iris_improvements,enable_creator_account_conversion_v0_universe,ig_android_test_not_signing_address_book_unlink_endpoint,ig_android_low_disk_recovery_universe,ig_ei_option_setting_universe,ig_android_account_insights_native_universe,ig_camera_android_ar_platform_universe,ig_android_browser_ads_page_content_width_universe,ig_android_stories_viewer_prefetch_improvements,ig_android_livewith_liveswap_optimization_universe,ig_android_camera_leak,ig_android_feed_core_unified_tags_universe,ig_android_jit,ig_android_optic_camera_warmup,ig_stories_rainbow_ring,ig_android_place_search_profile_image,ig_android_vp8_audio_encoder,android_cameracore_safe_makecurrent_ig,ig_android_analytics_diagnostics_universe,ig_android_ar_effect_sticker_universe,ig_direct_android_mentions_sender,ig_android_whats_app_contact_invite_universe,ig_android_stories_reaction_setup_delay_universe,ig_shopping_visual_product_sticker,ig_android_profile_unified_follow_view,ig_android_video_upload_hevc_encoding_universe,ig_android_mentions_suggestions,ig_android_vc_face_effects_universe,ig_android_fbpage_on_profile_side_tray,ig_android_direct_empty_state,ig_android_shimmering_loading_state,ig_android_igtv_refresh_tv_guide_interval,ig_android_gallery_minimum_video_length,ig_android_notif_improvement_universe,ig_android_hashtag_remove_share_hashtag,ig_android_fb_profile_integration_fbnc_universe,ig_shopping_checkout_2x2_platformization_universe,ig_android_direct_bump_active_threads,ig_fb_graph_differentiation_control,ig_android_show_create_content_pages_universe,ig_android_igsystrace_universe,ig_android_search_register_recent_store,ig_feed_content_universe,ig_android_disk_usage_logging_universe,ig_android_search_without_typed_hashtag_autocomplete,ig_android_video_product_specific_abr,ig_android_vc_interop_latency,ig_android_stories_layout_universe,ig_android_dont_animate_shutter_button_on_open,ig_android_vc_cpu_overuse_universe,ig_android_invite_list_button_redesign_universe,ig_android_react_native_email_sms_settings_universe,ig_hero_player,ag_family_bridges_2018_h2_holdout,ig_promote_net_promoter_score_universe,ig_android_save_auto_sharing_to_fb_option_on_server,aymt_instagram_promote_flow_abandonment_ig_universe,ig_android_whitehat_options_universe,ig_android_keyword_media_serp_page,ig_android_delete_ssim_compare_img_soon,ig_android_felix_video_upload_length,android_cameracore_preview_frame_listener2_ig_universe,ig_android_direct_message_follow_button,ig_android_biz_conversion_suggest_biz_nux,ig_stories_ads_media_based_insertion,ig_android_analytics_background_uploader_schedule,ig_camera_android_boomerang_attribution_universe,ig_android_igtv_browse_long_press,ig_android_profile_neue_infra_rollout_universe,ig_android_profile_ppr_fixes,ig_discovery_2019_h2_holdout_universe,ig_android_stories_weblink_creation,ig_android_blur_image_renderer,ig_profile_company_holdout_h2_2018,ig_android_ads_manager_pause_resume_ads_universe,ig_android_vc_capture_universe,ig_nametag_local_ocr_universe,ig_android_stories_media_seen_batching_universe,ig_android_interactions_nav_to_permalink_followup_universe,ig_camera_discovery_surface_universe,ig_android_save_to_collections_flow,ig_android_direct_segmented_video,instagram_stories_time_fixes,ig_android_le_cold_start_improvements,ig_android_direct_mark_as_read_notif_action,ig_android_stories_async_view_inflation_universe,ig_android_stories_recently_captured_universe,ig_android_direct_inbox_presence_refactor_universe,ig_business_integrity_ipc_universe,ig_android_direct_selfie_stickers,ig_android_vc_missed_call_call_back_action_universe,ig_cameracore_android_new_optic_camera2,ig_fb_graph_differentiation_top_k_fb_coefficients,ig_android_fbc_upsell_on_dp_first_load,ig_android_rename_share_option_in_dialog_menu_universe,ig_android_direct_refactor_inbox_observable_universe,ig_android_business_attribute_sync,ig_camera_android_bg_processor,ig_android_view_and_likes_cta_universe,ig_android_optic_new_focus_controller,ig_android_dropframe_manager,ig_android_direct_default_group_name,ig_android_optic_new_features_implementation,ig_android_search_hashtag_badges,ig_android_stories_reel_interactive_tap_target_size,ig_android_video_live_trace_universe,ig_android_tango_cpu_overuse_universe,ig_android_igtv_browse_with_pip_v2,ig_android_direct_fix_realtime_status,ig_android_unfollow_from_main_feed_v2,ig_android_self_story_setting_option_in_menu,ig_android_story_ads_tap_and_hold_fixes,ig_android_camera_ar_platform_details_view_universe,android_ard_ig_cache_size,ig_android_story_real_time_ad,ig_android_hybrid_bitmap_v4,ig_android_iab_downloadable_strings_universe,ig_android_branded_content_ads_enable_partner_boost,ufi_share,ig_android_direct_remix_visual_messages,ig_quick_story_placement_validation_universe,ig_android_custom_story_import_intent,ig_android_live_qa_broadcaster_v1_universe,ig_android_search_impression_logging_viewpoint,ig_android_downloadable_fonts_universe,ig_android_view_info_universe,ig_android_camera_upsell_dialog,ig_android_business_transaction_in_stories_consumer,ig_android_dead_code_detection,ig_android_promotion_insights_bloks,ig_android_direct_autoplay_videos_automatically,ig_android_ad_watchbrowse_universe,ig_android_pbia_proxy_profile_universe,ig_android_qp_kill_switch,ig_android_new_follower_removal_universe,instagram_android_stories_sticker_tray_redesign,ig_android_branded_content_access_tag,ig_android_gap_rule_enforcer_universe,ig_android_business_cross_post_with_biz_id_infra,ig_android_direct_delete_or_block_from_message_requests,ig_android_photo_invites,ig_interactions_h2_2019_team_holdout_universe,ig_android_reel_tray_item_impression_logging_viewpoint,ig_account_identity_2018_h2_lockdown_phone_global_holdout,ig_android_direct_left_aligned_navigation_bar,ig_android_high_res_gif_stickers,ig_android_feed_load_more_viewpoint_universe,ig_android_stories_reshare_reply_msg,ig_close_friends_v4,ig_android_ads_history_universe,ig_android_pigeon_sampling_runnable_check,ig_promote_media_picker_universe,ig_direct_holdout_h2_2018,ig_android_sidecar_report_ssim,ig_android_pending_media_file_registry,ig_android_wab_adjust_resize_universe,ig_camera_android_facetracker_v12_universe,ig_android_camera_ar_effects_low_storage_universe,ig_android_profile_add_profile_pic_universe,ig_android_ig_to_fb_sync_universe,ig_android_ar_background_effect_universe,ig_android_audience_control,ig_android_fix_recommended_user_impression,ig_android_stories_cross_sharing_to_fb_holdout_universe,shop_home_hscroll_see_all_button_universe,ig_android_refresh_empty_feed_su_universe,ig_android_shopping_parallel_pdp_fetch,ig_android_enable_main_feed_reel_tray_preloading,ig_android_ad_view_ads_native_universe,ig_android_branded_content_tag_redesign_organic,ig_android_profile_neue_universe,ig_android_igtv_whitelisted_for_web,ig_android_viewmaster_dial_ordering_universe,ig_company_profile_holdout,ig_rti_inapp_notifications_universe,ig_android_vc_join_timeout_universe,ig_shop_directory_entrypoint,ig_android_direct_rx_thread_update,ig_android_add_ci_upsell_in_normal_account_chaining_universe,ig_android_feed_core_ads_2019_h1_holdout_universe,ig_close_friends_v4_global,ig_android_share_publish_page_universe,ig_android_new_camera_design_universe,ig_direct_max_participants,ig_promote_hide_local_awareness_universe,ar_engine_audio_service_fba_decoder_ig,ar_engine_audio_fba_integration_instagram,ig_android_igtv_save,ig_android_explore_lru_cache,ig_android_graphql_survey_new_proxy_universe,ig_android_music_browser_redesign,ig_camera_android_try_on_camera_universe,ig_android_follower_following_whatsapp_invite_universe,ig_android_fs_creation_flow_tweaks,ig_direct_blocking_redesign_universe,ig_android_viewmaster_ar_memory_improvements,ig_android_downloadable_vp8_module,ig_android_claim_location_page,ig_android_direct_inbox_recently_active_presence_dot_universe,ig_android_stories_gutter_width_universe,ig_android_story_ads_2019_h1_holdout_universe,ig_android_3pspp,ig_android_cache_timespan_objects,ig_timestamp_public_test,ig_android_fb_profile_integration_universe,ig_android_feed_auto_share_to_facebook_dialog,ig_android_skip_button_content_on_connect_fb_universe,ig_android_network_perf_qpl_ppr,ig_android_post_live,ig_camera_android_focus_attribution_universe,ig_camera_async_space_validation_for_ar,ig_android_core_search_2019_h2,ig_android_prefetch_notification_data,ig_android_stories_music_line_by_line_cube_reveal_lyrics_sticker,ig_android_iab_clickid_universe,ig_android_interactions_hide_keyboard_onscroll,ig_early_friending_holdout_universe,ig_story_camera_reverse_video_experiment,ig_android_profile_lazy_load_carousel_media,ig_android_stories_question_sticker_music_format,ig_android_vpvd_impressions_universe,ig_android_payload_based_scheduling,ig_pacing_overriding_universe,ig_android_ard_ptl_universe,ig_android_q3lc_transparency_control_settings,ig_stories_selfie_sticker,ig_android_sso_use_trustedapp_universe,ig_android_stories_music_lyrics,ig_android_spark_studio_promo,ig_android_stories_music_awareness_universe,ard_ig_broti_effect,ig_android_camera_class_preloading,ig_android_new_fb_page_selection,ig_video_holdout_h2_2017,ig_background_prefetch,ig_camera_android_focus_in_post_universe,ig_android_time_spent_dashboard,ig_android_story_sharing_universe,ig_promote_political_ads_universe,ig_android_camera_effects_initialization_universe,ig_promote_post_insights_entry_universe,ig_android_ad_iab_qpl_kill_switch_universe,ig_android_live_subscribe_user_level_universe,ig_android_igtv_creation_flow,ig_android_vc_sounds_universe,ig_android_video_call_finish_universe,ig_camera_android_cache_format_picker_children,direct_unread_reminder_qe,ig_android_direct_mqtt_send,ig_android_self_story_button_non_fbc_accounts,ig_android_self_profile_suggest_business_gating,ig_feed_video_autoplay_stop_threshold,ig_android_explore_discover_people_entry_point_universe,ig_android_live_webrtc_livewith_params,ig_feed_experience,ig_android_direct_activator_cards,ig_android_vc_codec_settings,ig_promote_prefill_destination_universe,ig_android_appstate_logger,ig_android_profile_leaks_holdouts,ig_android_video_cached_bandwidth_estimate,ig_promote_insights_video_views_universe,ig_android_global_scheduler_offscreen_prefetch,ig_android_discover_interests_universe,ig_android_camera_gallery_upload_we_universe,ig_android_business_category_sticky_header_qe,ig_android_dismiss_recent_searches,ig_android_feed_camera_size_setter,ig_payment_checkout_cvv,ig_android_fb_link_ui_polish_universe,ig_android_tags_unification_universe,ig_android_shopping_lightbox,ig_android_bandwidth_timed_estimator,ig_android_stories_mixed_attribution_universe,ig_iab_tti_holdout_universe,ig_android_ar_button_visibility,ig_android_igtv_crop_top_consumption,ig_android_camera_gyro_universe,ig_android_nametag_effect_deeplink_universe,ig_android_blurred_product_image_previews,ig_android_igtv_ssim_report,ig_android_optic_surface_texture_cleanup,ig_android_business_remove_unowned_fb_pages,ig_android_stories_combined_asset_search,ig_promote_enter_error_screens_universe,ig_stories_allow_camera_actions_while_recording,ig_android_analytics_mark_events_as_offscreen,ig_shopping_checkout_mvp_experiment,ig_android_video_fit_scale_type_igtv,ig_android_direct_pending_media,ig_android_scroll_main_feed,instagram_pcp_activity_feed_following_tab_universe,ig_android_optic_feature_testing,ig_android_igtv_player_follow_button,ig_android_intialization_chunk_410,ig_android_vc_start_call_minimized_universe,ig_android_recognition_tracking_thread_prority_universe,ig_android_stories_music_sticker_position,ig_android_optic_photo_cropping_fixes,ig_camera_regiontracking_use_similarity_tracker_for_scaling,ig_android_interactions_media_breadcrumb,ig_android_vc_cowatch_config_universe,ig_android_nametag_save_experiment_universe,ig_android_refreshable_list_view_check_spring,ig_android_biz_endpoint_switch,ig_android_direct_continuous_capture,ig_android_comments_direct_reply_to_author,ig_android_profile_visits_in_bio,ig_android_fs_new_gallery,ig_android_remove_follow_all_fb_list,ig_android_vc_webrtc_params,ig_android_specific_story_sharing,ig_android_claim_or_connect_page_on_xpost,ig_android_anr,ig_android_story_viewpoint_impression_event_universe,ig_android_image_exif_metadata_ar_effect_id_universe,ig_android_optic_new_architecture,ig_android_stories_viewer_as_modal_high_end_launch,ig_android_local_info_page,ig_new_eof_demarcator_universe';
+ const LOGIN_EXPERIMENTS = 'ig_android_fci_onboarding_friend_search,ig_android_device_detection_info_upload,ig_android_account_linking_upsell_universe,ig_android_direct_main_tab_universe_v2,ig_android_sms_retriever_backtest_universe,ig_android_direct_add_direct_to_android_native_photo_share_sheet,ig_growth_android_profile_pic_prefill_with_fb_pic_2,ig_account_identity_logged_out_signals_global_holdout_universe,ig_android_login_identifier_fuzzy_match,ig_android_video_render_codec_low_memory_gc,ig_android_custom_transitions_universe,ig_android_push_fcm,ig_android_show_login_info_reminder_universe,ig_android_email_fuzzy_matching_universe,ig_android_one_tap_aymh_redesign_universe,ig_android_direct_send_like_from_notification,ig_android_suma_landing_page,ig_android_session_scoped_logger,ig_android_user_session_scoped_class_opt_universe,ig_android_accoun_switch_badge_fix_universe,ig_android_smartlock_hints_universe,ig_android_black_out,ig_activation_global_discretionary_sms_holdout,ig_android_account_switch_infra_universe,ig_android_video_ffmpegutil_pts_fix,ig_android_multi_tap_login_new,ig_android_caption_typeahead_fix_on_o_universe,ig_android_save_pwd_checkbox_reg_universe,ig_android_nux_add_email_device,ig_android_direct_remove_view_mode_stickiness_universe,ig_username_suggestions_on_username_taken,ig_android_ingestion_video_support_hevc_decoding,ig_android_secondary_account_creation_universe,ig_android_account_recovery_auto_login,ig_android_sim_info_upload,ig_android_mobile_http_flow_device_universe,ig_android_hide_fb_button_when_not_installed_universe,ig_android_targeted_one_tap_upsell_universe,ig_android_gmail_oauth_in_reg,ig_android_account_linking_flow_shorten_universe,ig_android_hide_typeahead_for_logged_users,ig_android_vc_interop_use_test_igid_universe,ig_android_log_suggested_users_cache_on_error,ig_android_reg_modularization_universe,ig_android_phone_edit_distance_universe,ig_android_device_verification_separate_endpoint,ig_android_universe_noticiation_channels,ig_smartlock_login,ig_android_igexecutor_sync_optimization_universe,ig_android_account_linking_skip_value_props_universe,ig_android_account_linking_universe,ig_android_hsite_prefill_new_carrier,ig_android_retry_create_account_universe,ig_android_family_apps_user_values_provider_universe,ig_android_reg_nux_headers_cleanup_universe,ig_android_device_info_foreground_reporting,ig_android_shortcuts_2019,ig_android_device_verification_fb_signup,ig_android_onetaplogin_optimization,ig_video_debug_overlay,ig_android_ask_for_permissions_on_reg,ig_assisted_login_universe,ig_android_display_full_country_name_in_reg_universe,ig_android_security_intent_switchoff,ig_android_device_info_job_based_reporting,ig_android_passwordless_auth,ig_android_direct_main_tab_account_switch,ig_android_modularized_dynamic_nux_universe,ig_android_fb_account_linking_sampling_freq_universe,ig_android_fix_sms_read_lollipop,ig_android_access_flow_prefill';
+ const LAUNCHER_CONFIGS = 'ig_android_media_codec_info_collection,stories_gif_sticker,ig_android_felix_release_players,bloks_binding,ig_android_camera_network_activity_logger,ig_android_os_version_blocking_config,ig_android_carrier_signals_killswitch,live_special_codec_size_list,fbns,ig_android_aed,ig_client_config_server_side_retrieval,ig_android_bloks_perf_logging,ig_user_session_operation,ig_user_mismatch_soft_error,ig_android_prerelease_event_counter,fizz_ig_android,ig_android_vc_clear_task_flag_killswitch,ig_android_killswitch_perm_direct_ssim,ig_android_codec_high_profile,ig_android_smart_prefill_killswitch,sonar_prober,action_bar_layout_width,ig_auth_headers_device,always_use_server_recents';
+ const LAUNCHER_LOGIN_CONFIGS = 'ig_camera_ard_use_ig_downloader,ig_android_dogfooding,ig_android_bloks_data_release,ig_donation_sticker_public_thanks,ig_business_profile_donate_cta_android,ig_launcher_ig_android_network_dispatcher_priority_decider_qe2,ig_multi_decode_config,ig_android_improve_segmentation_hint,ig_android_memory_manager_holdout,ig_android_interactions_direct_sharing_comment_launcher,ig_launcher_ig_android_analytics_request_cap_qe,ig_direct_e2e_send_waterfall_sample_rate_config,ig_android_cdn_image_sizes_config,ig_android_critical_path_manager,ig_android_mobileboost_camera,ig_android_pdp_default_sections,ig_android_video_playback,ig_launcher_explore_sfplt_secondary_response_android,ig_android_upload_heap_on_oom,ig_synchronous_account_switch,ig_android_direct_presence_digest_improvements,ig_android_request_compression_launcher,ig_android_feed_attach_report_logs,ig_android_insights_welcome_dialog_tooltip,ig_android_qp_surveys_v1,ig_direct_requests_approval_config,ig_android_react_native_ota_kill_switch,ig_android_video_profiler_loom_traces,video_call_gk,ig_launcher_ig_android_network_stack_cap_video_request_qe,ig_shopping_android_business_new_tagging_flow,ig_android_igtv_bitrate,ig_android_geo_gating,ig_android_explore_startup_prefetch,ig_android_camera_asset_blocker_config,post_user_cache_user_based,ig_android_branded_content_story_partner_promote_rollout,ig_android_quic,ig_android_videolite_uploader,ig_direct_message_type_reporting_config,ig_camera_android_whitelist_all_effects_in_pre,ig_android_shopping_influencer_creator_nux,ig_android_mobileboost_blacklist,ig_android_direct_gifs_killswitch,ig_android_global_scheduler_direct,ig_android_image_display_logging,ig_android_global_scheduler_infra,ig_igtv_branded_content_killswitch,ig_cg_donor_duplicate_sticker,ig_launcher_explore_verified_badge_on_ads,ig_android_cold_start_class_preloading,ig_camera_android_attributed_effects_endpoint_api_query_config,ig_android_highlighted_products_business_option,ig_direct_join_chat_sticker,ig_android_direct_admin_tools_requests,ig_android_rage_shake_whitelist,ig_android_shopping_ads_cta_rollout,ig_android_igtv_segmentation,ig_launcher_force_switch_on_dialog,ig_android_iab_fullscreen_experience_config,ig_android_instacrash,ig_android_specific_story_url_handling_killswitch,ig_mobile_consent_settings_killswitch,ig_android_influencer_monetization_hub_launcher,ig_and roid_scroll_perf_mobile_boost_launcher,ig_android_cx_stories_about_you,ig_android_replay_safe,ig_android_stories_scroll_perf_misc_fixes_h2_2019,ig_android_shopping_django_product_search,ig_direct_giphy_gifs_rating,ig_android_ppr_url_logging_config,ig_canvas_ad_pixel,ig_strongly_referenced_mediacache,ig_android_direct_show_threads_status_in_direct,ig_camera_ard_brotli_model_compression,ig_image_pipeline_skip_disk_config,ig_android_explore_grid_viewpoint,ig_android_iab_persistent_process,ig_android_in_process_iab,ig_android_launcher_value_consistency_checker,ig_launcher_ig_explore_peek_and_sfplt_android,ig_android_skip_photo_finish,ig_biz_android_use_professional_account_term,ig_android_settings_search,ig_android_direct_presence_media_viewer,ig_launcher_explore_navigation_redesign_android,ig_launcher_ig_android_network_stack_cap_api_request_qe,ig_qe_value_consistency_checker,ig_stories_fundraiser_view_payment_address,ig_business_create_donation_android,ig_android_qp_waterfall_logging,ig_android_bloks_demos,ig_redex_dynamic_analysis,ig_android_bug_report_screen_record,ig_shopping_android_carousel_product_ids_fix_killswitch,ig_shopping_android_creators_new_tagging_flow,ig_android_direct_threads_app_dogfooding_flags,ig_shopping_camera_android,ig_android_qp_keep_promotion_during_cooldown,ig_android_qp_slot_cooldown_enabled_universe,ig_android_request_cap_tuning_with_bandwidth,ig_android_client_config_realtime_subscription,ig_launcher_ig_android_network_request_cap_tuning_qe,ig_android_concurrent_coldstart,ig_android_gps_improvements_launcher,ig_android_notification_setting_sync,ig_android_stories_canvas_mode_colour_wheel,ig_android_iab_session_logging_config,ig_android_network_trace_migration,ig_android_extra_native_debugging_info,ig_android_insights_top_account_dialog_tooltip,ig_launcher_ig_android_dispatcher_viewpoint_onscreen_updater_qe,ig_android_disable_browser_multiple_windows,ig_contact_invites_netego_killswitch,ig_android_update_items_header_height_launcher,ig_android_bulk_tag_untag_killswitch,ig_android_employee_options,ig_launcher_ig_android_video_pending_request_store_qe,ig_story_insights_entry,ig_android_creator_multi_select,ig_android_direct_new_media_viewer,ig_android_gps_profile_launcher,ig_android_direct_real_names_launcher,ig_fev_info_launcher,ig_android_remove_request_params_in_network_trace,ig_android_rageshake_redesign,ig_launcher_ig_android_network_stack_queue_undefined_request_qe,ig_cx_promotion_tooltip,ig_text_response_bottom_sheet,ig_android_carrier_signal_timestamp_max_age,ig_android_qp_xshare_to_fb,ig_android_rollout_gating_payment_settings,ig_android_mobile_boost_kill_switch,ig_android_betamap_cold_start,ig_android_media_store,ig_android_async_view_model_launcher,ig_android_newsfeed_recyclerview,ig_android_feed_optimistic_upload,ig_android_fix_render_backtrack_reporting,ig_delink_lasso_accounts,ig_android_feed_report_ranking_issue,ig_android_shopping_insights_events_validator,ig_biz_android_new_logging_architecture,ig_launcher_ig_android_reactnative_realtime_ota,ig_android_boomerang_crash_android_go,ig_android_shopping_influencer_product_sticker_editing,ig_camera_android_max_vertex_texture_launcher,bloks_suggested_hashtag';
+ const SIG_KEY_VERSION = '4';
+
+ // Endpoint Constants.
+ const BLOCK_VERSIONING_ID = 'a4b4b8345a67599efe117ad96b8a9cb357bb51ac3ee00c3a48be37ce10f2bb4c'; // getTimelineFeed()
+ const BATCH_SURFACES = [
+ ['4715', 'instagram_feed_header'],
+ ['5734', 'instagram_feed_prompt'],
+ ['5858', 'instagram_feed_tool_tip'],
+ ];
+ const BATCH_QUERY = 'viewer() {eligible_promotions.trigger_context_v2().ig_parameters().trigger_name().surface_nux_id().external_gating_permitted_qps().supports_client_filters(true).include_holdouts(true) {edges {client_ttl_seconds,log_eligibility_waterfall,is_holdout,priority,time_range {start,end},node {id,promotion_id,logging_data,max_impressions,triggers,contextual_filters {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}},clauses {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}},clauses {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}},clauses {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}}}}}},is_uncancelable,template {name,parameters {name,required,bool_value,string_value,color_value,}},creatives {title {text},content {text},footer {text},social_context {text},social_context_images,primary_action{title {text},url,limit,dismiss_promotion},secondary_action{title {text},url,limit,dismiss_promotion},dismiss_action{title {text},url,limit,dismiss_promotion},image.scale() {uri,width,height}}}}}}';
+ const BATCH_SCALE = 3;
+ const BATCH_VERSION = 1;
+
+ // User-Agent Constants.
+ const USER_AGENT_LOCALE = 'en_US'; // "language_COUNTRY".
+
+ // HTTP Protocol Constants.
+ const ACCEPT_LANGUAGE = 'en-US'; // "language-COUNTRY".
+ const ACCEPT_ENCODING = 'gzip,deflate';
+ const CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=UTF-8';
+ const X_FB_HTTP_Engine = 'Liger';
+ const X_IG_Connection_Type = 'WIFI';
+ const X_IG_Capabilities = '3brTvw==';
+
+ // Supported Capabilities
+ const SUPPORTED_CAPABILITIES = [
+ [
+ 'name' => 'SUPPORTED_SDK_VERSIONS',
+ 'value' => '13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0,25.0,26.0,27.0,28.0,29.0,30.0,31.0,32.0,33.0,34.0,35.0,36.0,37.0,38.0,39.0,40.0,41.0,42.0,43.0,44.0,45.0,46.0,47.0,48.0,49.0,50.0,51.0,52.0,53.0,54.0,55.0,56.0,57.0,58.0,59.0,60.0,61.0,62.0,63.0,64.0,65.0,66.0,67.0,68.0,69.0',
+ ],
+ [
+ 'name' => 'FACE_TRACKER_VERSION',
+ 'value' => '12',
+ ],
+ [
+ 'name' => 'segmentation',
+ 'value' => 'segmentation_enabled',
+ ],
+ [
+ 'name' => 'COMPRESSION',
+ 'value' => 'ETC2_COMPRESSION',
+ ],
+ [
+ 'name' => 'world_tracker',
+ 'value' => 'world_tracker_enabled',
+ ],
+ [
+ 'name' => 'gyroscope',
+ 'value' => 'gyroscope_enabled',
+ ],
+ ];
+
+ // Facebook Constants.
+ const FACEBOOK_OTA_FIELDS = 'update%7Bdownload_uri%2Cdownload_uri_delta_base%2Cversion_code_delta_base%2Cdownload_uri_delta%2Cfallback_to_full_update%2Cfile_size_delta%2Cversion_code%2Cpublished_date%2Cfile_size%2Cota_bundle_type%2Cresources_checksum%2Callowed_networks%2Crelease_id%7D';
+ const FACEBOOK_ORCA_PROTOCOL_VERSION = 20150314;
+ const FACEBOOK_ORCA_APPLICATION_ID = '124024574287414';
+ const FACEBOOK_ANALYTICS_APPLICATION_ID = '567067343352427';
+
+ // MQTT Constants.
+ const PLATFORM = 'android';
+ const FBNS_APPLICATION_NAME = 'MQTT';
+ const INSTAGRAM_APPLICATION_NAME = 'Instagram';
+ const PACKAGE_NAME = 'com.instagram.android';
+
+ // Instagram Quick Promotions.
+ const SURFACE_PARAM = [
+ 4715,
+ 5734,
+ ];
+
+ // Internal Feedtype Constants. CRITICAL: EVERY value here MUST be unique!
+ const FEED_TIMELINE = 1;
+ const FEED_TIMELINE_ALBUM = 2;
+ const FEED_STORY = 3;
+ const FEED_DIRECT = 4;
+ const FEED_DIRECT_STORY = 5;
+ const FEED_TV = 6;
+
+ // General Constants.
+ const SRC_DIR = __DIR__; // Absolute path to the "src" folder.
+
+ // Story view modes.
+ const STORY_VIEW_MODE_ONCE = 'once';
+ const STORY_VIEW_MODE_REPLAYABLE = 'replayable';
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Debug.php b/instafeed/vendor/mgp25/instagram-php/src/Debug.php
new file mode 100755
index 0000000..6d445c7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Debug.php
@@ -0,0 +1,67 @@
+ 1000) {
+ $response = mb_substr($response, 0, 1000, 'utf8').'...';
+ }
+ echo $res.$response."\n\n";
+ }
+
+ public static function printPostData(
+ $post)
+ {
+ $gzip = mb_strpos($post, "\x1f"."\x8b"."\x08", 0, 'US-ASCII') === 0;
+ if (PHP_SAPI === 'cli') {
+ $dat = Utils::colouredString(($gzip ? 'DECODED ' : '').'DATA: ', 'yellow');
+ } else {
+ $dat = 'DATA: ';
+ }
+ echo $dat.urldecode(($gzip ? zlib_decode($post) : $post))."\n";
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Devices/Device.php b/instafeed/vendor/mgp25/instagram-php/src/Devices/Device.php
new file mode 100755
index 0000000..8f4bbf8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Devices/Device.php
@@ -0,0 +1,268 @@
+_appVersion = $appVersion;
+ $this->_versionCode = $versionCode;
+ $this->_userLocale = $userLocale;
+
+ // Use the provided device if a valid good device. Otherwise use random.
+ if ($autoFallback && (!is_string($deviceString) || !GoodDevices::isGoodDevice($deviceString))) {
+ $deviceString = GoodDevices::getRandomGoodDevice();
+ }
+
+ // Initialize ourselves from the device string.
+ $this->_initFromDeviceString($deviceString);
+ }
+
+ /**
+ * Parses a device string into its component parts and sets internal fields.
+ *
+ * Does no validation to make sure the string is one of the good devices.
+ *
+ * @param string $deviceString
+ *
+ * @throws \RuntimeException If the device string is invalid.
+ */
+ protected function _initFromDeviceString(
+ $deviceString)
+ {
+ if (!is_string($deviceString) || empty($deviceString)) {
+ throw new \RuntimeException('Device string is empty.');
+ }
+
+ // Split the device identifier into its components and verify it.
+ $parts = explode('; ', $deviceString);
+ if (count($parts) !== 7) {
+ throw new \RuntimeException(sprintf('Device string "%s" does not conform to the required device format.', $deviceString));
+ }
+
+ // Check the android version.
+ $androidOS = explode('/', $parts[0], 2);
+ if (version_compare($androidOS[1], self::REQUIRED_ANDROID_VERSION, '<')) {
+ throw new \RuntimeException(sprintf('Device string "%s" does not meet the minimum required Android version "%s" for Instagram.', $deviceString, self::REQUIRED_ANDROID_VERSION));
+ }
+
+ // Check the screen resolution.
+ $resolution = explode('x', $parts[2], 2);
+ $pixelCount = (int) $resolution[0] * (int) $resolution[1];
+ if ($pixelCount < 2073600) { // 1920x1080.
+ throw new \RuntimeException(sprintf('Device string "%s" does not meet the minimum resolution requirement of 1920x1080.', $deviceString));
+ }
+
+ // Extract "Manufacturer/Brand" string into separate fields.
+ $manufacturerAndBrand = explode('/', $parts[3], 2);
+
+ // Store all field values.
+ $this->_deviceString = $deviceString;
+ $this->_androidVersion = $androidOS[0]; // "23".
+ $this->_androidRelease = $androidOS[1]; // "6.0.1".
+ $this->_dpi = $parts[1];
+ $this->_resolution = $parts[2];
+ $this->_manufacturer = $manufacturerAndBrand[0];
+ $this->_brand = (isset($manufacturerAndBrand[1])
+ ? $manufacturerAndBrand[1] : null);
+ $this->_model = $parts[4];
+ $this->_device = $parts[5];
+ $this->_cpu = $parts[6];
+
+ // Build our user agent.
+ $this->_userAgent = UserAgent::buildUserAgent($this->_appVersion, $this->_userLocale, $this);
+
+ $this->_fbUserAgents = [];
+ }
+
+ // Getters for all properties...
+
+ /** {@inheritdoc} */
+ public function getDeviceString()
+ {
+ return $this->_deviceString;
+ }
+
+ /** {@inheritdoc} */
+ public function getUserAgent()
+ {
+ return $this->_userAgent;
+ }
+
+ /** {@inheritdoc} */
+ public function getFbUserAgent(
+ $appName)
+ {
+ if (!isset($this->_fbUserAgents[$appName])) {
+ $this->_fbUserAgents[$appName] = UserAgent::buildFbUserAgent(
+ $appName,
+ $this->_appVersion,
+ $this->_versionCode,
+ $this->_userLocale,
+ $this
+ );
+ }
+
+ return $this->_fbUserAgents[$appName];
+ }
+
+ /** {@inheritdoc} */
+ public function getAndroidVersion()
+ {
+ return $this->_androidVersion;
+ }
+
+ /** {@inheritdoc} */
+ public function getAndroidRelease()
+ {
+ return $this->_androidRelease;
+ }
+
+ /** {@inheritdoc} */
+ public function getDPI()
+ {
+ return $this->_dpi;
+ }
+
+ /** {@inheritdoc} */
+ public function getResolution()
+ {
+ return $this->_resolution;
+ }
+
+ /** {@inheritdoc} */
+ public function getManufacturer()
+ {
+ return $this->_manufacturer;
+ }
+
+ /** {@inheritdoc} */
+ public function getBrand()
+ {
+ return $this->_brand;
+ }
+
+ /** {@inheritdoc} */
+ public function getModel()
+ {
+ return $this->_model;
+ }
+
+ /** {@inheritdoc} */
+ public function getDevice()
+ {
+ return $this->_device;
+ }
+
+ /** {@inheritdoc} */
+ public function getCPU()
+ {
+ return $this->_cpu;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Devices/DeviceInterface.php b/instafeed/vendor/mgp25/instagram-php/src/Devices/DeviceInterface.php
new file mode 100755
index 0000000..413946b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Devices/DeviceInterface.php
@@ -0,0 +1,93 @@
+getManufacturer();
+ if ($device->getBrand() !== null) {
+ $manufacturerWithBrand .= '/'.$device->getBrand();
+ }
+
+ // Generate the final User-Agent string.
+ return sprintf(
+ self::USER_AGENT_FORMAT,
+ $appVersion, // App version ("27.0.0.7.97").
+ $device->getAndroidVersion(),
+ $device->getAndroidRelease(),
+ $device->getDPI(),
+ $device->getResolution(),
+ $manufacturerWithBrand,
+ $device->getModel(),
+ $device->getDevice(),
+ $device->getCPU(),
+ $userLocale, // Locale ("en_US").
+ Constants::VERSION_CODE
+ );
+ }
+
+ /**
+ * Escape string for Facebook User-Agent string.
+ *
+ * @param $string
+ *
+ * @return string
+ */
+ protected static function _escapeFbString(
+ $string)
+ {
+ $result = '';
+ for ($i = 0; $i < strlen($string); ++$i) {
+ $char = $string[$i];
+ if ($char === '&') {
+ $result .= '&';
+ } elseif ($char < ' ' || $char > '~') {
+ $result .= sprintf('%d;', ord($char));
+ } else {
+ $result .= $char;
+ }
+ }
+ $result = strtr($result, ['/' => '-', ';' => '-']);
+
+ return $result;
+ }
+
+ /**
+ * Generates a FB User Agent string from a DeviceInterface.
+ *
+ * @param string $appName Application name.
+ * @param string $appVersion Instagram client app version.
+ * @param string $versionCode Instagram client app version code.
+ * @param string $userLocale The user's locale, such as "en_US".
+ * @param DeviceInterface $device
+ *
+ * @throws \InvalidArgumentException If the device parameter is invalid.
+ *
+ * @return string
+ */
+ public static function buildFbUserAgent(
+ $appName,
+ $appVersion,
+ $versionCode,
+ $userLocale,
+ DeviceInterface $device)
+ {
+ list($width, $height) = explode('x', $device->getResolution());
+ $density = round(str_replace('dpi', '', $device->getDPI()) / 160, 1);
+ $result = [
+ 'FBAN' => $appName,
+ 'FBAV' => $appVersion,
+ 'FBBV' => $versionCode,
+ 'FBDM' => sprintf('{density=%.1f,width=%d,height=%d}', $density, $width, $height),
+ 'FBLC' => $userLocale,
+ 'FBCR' => '', // We don't have cellular.
+ 'FBMF' => self::_escapeFbString($device->getManufacturer()),
+ 'FBBD' => self::_escapeFbString($device->getBrand() ? $device->getBrand() : $device->getManufacturer()),
+ 'FBPN' => Constants::PACKAGE_NAME,
+ 'FBDV' => self::_escapeFbString($device->getModel()),
+ 'FBSV' => self::_escapeFbString($device->getAndroidRelease()),
+ 'FBLR' => 0, // android.hardware.ram.low
+ 'FBBK' => 1, // Const (at least in 10.12.0).
+ 'FBCA' => self::_escapeFbString(GoodDevices::CPU_ABI),
+ ];
+ array_walk($result, function (&$value, $key) {
+ $value = sprintf('%s/%s', $key, $value);
+ });
+
+ // Trailing semicolon is essential.
+ return '['.implode(';', $result).';]';
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Exception/AccountDisabledException.php b/instafeed/vendor/mgp25/instagram-php/src/Exception/AccountDisabledException.php
new file mode 100755
index 0000000..c62a056
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Exception/AccountDisabledException.php
@@ -0,0 +1,7 @@
+_response !== null ? true : false;
+ }
+
+ /**
+ * Get the full server response.
+ *
+ * @return Response|null The full response if one exists, otherwise NULL.
+ *
+ * @see InstagramException::hasResponse()
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Internal. Sets the value of the full server response.
+ *
+ * @param Response|null $response The response value.
+ */
+ public function setResponse(
+ Response $response = null)
+ {
+ $this->_response = $response;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Exception/InternalException.php b/instafeed/vendor/mgp25/instagram-php/src/Exception/InternalException.php
new file mode 100755
index 0000000..0c70bdf
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Exception/InternalException.php
@@ -0,0 +1,10 @@
+_guzzleException = $guzzleException;
+
+ // Ensure that the message is nicely formatted and follows our standard.
+ $message = 'Network: '.ServerMessageThrower::prettifyMessage($this->_guzzleException->getMessage());
+
+ // Construct with our custom message.
+ // NOTE: We DON'T assign the guzzleException to "$previous", otherwise
+ // the user would still see something like "Uncaught GuzzleHttp\Exception\
+ // RequestException" and Guzzle's stack trace, instead of "Uncaught
+ // InstagramAPI\Exception\NetworkException" and OUR correct stack trace.
+ parent::__construct($message);
+ }
+
+ /**
+ * Gets the original Guzzle exception, which contains much more details.
+ *
+ * @return \Exception The original Guzzle exception.
+ */
+ public function getGuzzleException()
+ {
+ return $this->_guzzleException;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Exception/NotFoundException.php b/instafeed/vendor/mgp25/instagram-php/src/Exception/NotFoundException.php
new file mode 100755
index 0000000..2bcd379
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Exception/NotFoundException.php
@@ -0,0 +1,11 @@
+ ['login_required'],
+ 'CheckpointRequiredException' => [
+ 'checkpoint_required', // message
+ 'checkpoint_challenge_required', // error_type
+ ],
+ 'ChallengeRequiredException' => ['challenge_required'],
+ 'FeedbackRequiredException' => ['feedback_required'],
+ 'ConsentRequiredException' => ['consent_required'],
+ 'IncorrectPasswordException' => [
+ // "The password you entered is incorrect".
+ '/password(.*?)incorrect/', // message
+ 'bad_password', // error_type
+ ],
+ 'InvalidSmsCodeException' => [
+ // "Please check the security code we sent you and try again".
+ '/check(.*?)security(.*?)code/', // message
+ 'sms_code_validation_code_invalid', // error_type
+ ],
+ 'AccountDisabledException' => [
+ // "Your account has been disabled for violating our terms".
+ '/account(.*?)disabled(.*?)violating/',
+ ],
+ 'SentryBlockException' => ['sentry_block'],
+ 'InvalidUserException' => [
+ // "The username you entered doesn't appear to belong to an account"
+ '/username(.*?)doesn\'t(.*?)belong/', // message
+ 'invalid_user', // error_type
+ ],
+ 'ForcedPasswordResetException' => ['/reset(.*?)password/'],
+ ];
+
+ /**
+ * Parses a server message and throws the appropriate exception.
+ *
+ * Uses the generic EndpointException if no other exceptions match.
+ *
+ * @param string|null $prefixString What prefix to use for
+ * the message in the
+ * final exception. Should
+ * be something helpful
+ * such as the name of the
+ * class or function which
+ * threw. Can be `NULL`.
+ * @param string|null $serverMessage The failure string from
+ * Instagram's API (from
+ * `getMessage()`). Might
+ * be empty in some cases.
+ * @param Response|null $serverResponse The complete server
+ * response object, if one
+ * is available
+ * (optional).
+ * @param HttpResponseInterface|null $httpResponse The HTTP response
+ * object (if available).
+ *
+ * @throws InstagramException The appropriate exception.
+ */
+ public static function autoThrow(
+ $prefixString,
+ $serverMessage,
+ Response $serverResponse = null,
+ HttpResponseInterface $httpResponse = null)
+ {
+ // We will analyze both the `message` AND `error_type` (if available).
+ $messages = [$serverMessage];
+ $serverErrorType = null;
+ if ($serverResponse instanceof Response) {
+ // We are reading a property that isn't defined in the class
+ // property map, so we must use "has" first, to ensure it exists.
+ if ($serverResponse->hasErrorType()
+ && is_string($serverResponse->getErrorType())) {
+ $serverErrorType = $serverResponse->getErrorType();
+ $messages[] = $serverErrorType;
+ }
+ }
+
+ $exceptionClass = null;
+
+ // Check if the server message is in our CRITICAL exception table.
+ foreach ($messages as $message) {
+ foreach (self::EXCEPTION_MAP as $className => $patterns) {
+ foreach ($patterns as $pattern) {
+ if ($pattern[0] == '/') {
+ // Regex check.
+ if (preg_match($pattern, $message)) {
+ $exceptionClass = $className;
+ break 3;
+ }
+ } else {
+ // Regular string search.
+ if (strpos($message, $pattern) !== false) {
+ $exceptionClass = $className;
+ break 3;
+ }
+ }
+ }
+ }
+ }
+
+ // Check the HTTP status code if no critical exception has been found.
+ if ($exceptionClass === null) {
+ // NOTE FOR CONTRIBUTORS: All HTTP status exceptions below MUST be
+ // derived from EndpointException, since all HTTP errors are
+ // endpoint-error-related responses and MUST be easily catchable!
+ $httpStatusCode = $httpResponse !== null ? $httpResponse->getStatusCode() : null;
+ switch ($httpStatusCode) {
+ case 400:
+ $exceptionClass = 'BadRequestException';
+ break;
+ case 404:
+ $exceptionClass = 'NotFoundException';
+ break;
+ default:
+ // No critical exceptions and no HTTP code exceptions have
+ // been found, so use the generic "API function exception"!
+ $exceptionClass = 'EndpointException';
+ }
+ }
+
+ // We need to specify the full namespace path to the exception class.
+ $fullClassPath = '\\'.__NAMESPACE__.'\\'.$exceptionClass;
+
+ // Determine which message to display to the user.
+ $displayMessage = is_string($serverMessage) && strlen($serverMessage)
+ ? $serverMessage : $serverErrorType;
+ if (!is_string($displayMessage) || !strlen($displayMessage)) {
+ $displayMessage = 'Request failed.';
+ }
+
+ // Some Instagram messages already have punctuation, and others need it.
+ $displayMessage = self::prettifyMessage($displayMessage);
+
+ // Create an instance of the final exception class, with the pretty msg.
+ $e = new $fullClassPath(
+ $prefixString !== null
+ ? sprintf('%s: %s', $prefixString, $displayMessage)
+ : $displayMessage
+ );
+
+ // Attach the server response to the exception, IF a response exists.
+ // NOTE: Only possible on exceptions derived from InstagramException.
+ if ($serverResponse instanceof Response
+ && $e instanceof \InstagramAPI\Exception\InstagramException) {
+ $e->setResponse($serverResponse);
+ }
+
+ throw $e;
+ }
+
+ /**
+ * Nicely reformats externally generated exception messages.
+ *
+ * This is used for guaranteeing consistent message formatting with full
+ * English sentences, ready for display to the user.
+ *
+ * @param string $message The original message.
+ *
+ * @return string The cleaned-up message.
+ */
+ public static function prettifyMessage(
+ $message)
+ {
+ // Some messages already have punctuation, and others need it. Prettify
+ // the message by ensuring that it ALWAYS ends in punctuation, for
+ // consistency with all of our internal error messages.
+ $lastChar = substr($message, -1);
+ if ($lastChar !== '' && $lastChar !== '.' && $lastChar !== '!' && $lastChar !== '?') {
+ $message .= '.';
+ }
+
+ // Guarantee that the first letter is uppercase.
+ $message = ucfirst($message);
+
+ // Replace all underscores (ie. "Login_required.") with spaces.
+ $message = str_replace('_', ' ', $message);
+
+ return $message;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Exception/SettingsException.php b/instafeed/vendor/mgp25/instagram-php/src/Exception/SettingsException.php
new file mode 100755
index 0000000..6f2c42c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Exception/SettingsException.php
@@ -0,0 +1,10 @@
+If you truly want to enable incorrect website usage by directly embedding this application emulator library in your page, then you can do that AT YOUR OWN RISK by setting the following flag before you create the Instagram() object:
'.PHP_EOL;
+ exit(0); // Exit without error to avoid triggering Error 500.
+ }
+
+ // Prevent people from running this library on ancient PHP versions, and
+ // verify that people have the most critically important PHP extensions.
+ // NOTE: All of these are marked as requirements in composer.json, but
+ // some people install the library at home and then move it somewhere
+ // else without the requirements, and then blame us for their errors.
+ if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50600) {
+ throw new \InstagramAPI\Exception\InternalException(
+ 'You must have PHP 5.6 or higher to use the Instagram API library.'
+ );
+ }
+ static $extensions = ['curl', 'mbstring', 'gd', 'exif', 'zlib'];
+ foreach ($extensions as $ext) {
+ if (!@extension_loaded($ext)) {
+ throw new \InstagramAPI\Exception\InternalException(sprintf(
+ 'You must have the "%s" PHP extension to use the Instagram API library.',
+ $ext
+ ));
+ }
+ }
+
+ // Debugging options.
+ $this->debug = $debug;
+ $this->truncatedDebug = $truncatedDebug;
+
+ // Load all function collections.
+ $this->account = new Request\Account($this);
+ $this->business = new Request\Business($this);
+ $this->collection = new Request\Collection($this);
+ $this->creative = new Request\Creative($this);
+ $this->direct = new Request\Direct($this);
+ $this->discover = new Request\Discover($this);
+ $this->hashtag = new Request\Hashtag($this);
+ $this->highlight = new Request\Highlight($this);
+ $this->tv = new Request\TV($this);
+ $this->internal = new Request\Internal($this);
+ $this->live = new Request\Live($this);
+ $this->location = new Request\Location($this);
+ $this->media = new Request\Media($this);
+ $this->people = new Request\People($this);
+ $this->push = new Request\Push($this);
+ $this->shopping = new Request\Shopping($this);
+ $this->story = new Request\Story($this);
+ $this->timeline = new Request\Timeline($this);
+ $this->usertag = new Request\Usertag($this);
+
+ // Configure the settings storage and network client.
+ $self = $this;
+ $this->settings = Settings\Factory::createHandler(
+ $storageConfig,
+ [
+ // This saves all user session cookies "in bulk" at script exit
+ // or when switching to a different user, so that it only needs
+ // to write cookies to storage a few times per user session:
+ 'onCloseUser' => function ($storage) use ($self) {
+ if ($self->client instanceof Client) {
+ $self->client->saveCookieJar();
+ }
+ },
+ ]
+ );
+ $this->client = new Client($this);
+ $this->experiments = [];
+ }
+
+ /**
+ * Controls the SSL verification behavior of the Client.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#verify
+ *
+ * @param bool|string $state TRUE to verify using PHP's default CA bundle,
+ * FALSE to disable SSL verification (this is
+ * insecure!), String to verify using this path to
+ * a custom CA bundle file.
+ */
+ public function setVerifySSL(
+ $state)
+ {
+ $this->client->setVerifySSL($state);
+ }
+
+ /**
+ * Gets the current SSL verification behavior of the Client.
+ *
+ * @return bool|string
+ */
+ public function getVerifySSL()
+ {
+ return $this->client->getVerifySSL();
+ }
+
+ /**
+ * Set the proxy to use for requests.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#proxy
+ *
+ * @param string|array|null $value String or Array specifying a proxy in
+ * Guzzle format, or NULL to disable
+ * proxying.
+ */
+ public function setProxy(
+ $value)
+ {
+ $this->client->setProxy($value);
+ }
+
+ /**
+ * Gets the current proxy used for requests.
+ *
+ * @return string|array|null
+ */
+ public function getProxy()
+ {
+ return $this->client->getProxy();
+ }
+
+ /**
+ * Sets the network interface override to use.
+ *
+ * Only works if Guzzle is using the cURL backend. But that's
+ * almost always the case, on most PHP installations.
+ *
+ * @see http://php.net/curl_setopt CURLOPT_INTERFACE
+ *
+ * @param string|null $value Interface name, IP address or hostname, or NULL
+ * to disable override and let Guzzle use any
+ * interface.
+ */
+ public function setOutputInterface(
+ $value)
+ {
+ $this->client->setOutputInterface($value);
+ }
+
+ /**
+ * Gets the current network interface override used for requests.
+ *
+ * @return string|null
+ */
+ public function getOutputInterface()
+ {
+ return $this->client->getOutputInterface();
+ }
+
+ /**
+ * Login to Instagram or automatically resume and refresh previous session.
+ *
+ * Sets the active account for the class instance. You can call this
+ * multiple times to switch between multiple Instagram accounts.
+ *
+ * WARNING: You MUST run this function EVERY time your script runs! It
+ * handles automatic session resume and relogin and app session state
+ * refresh and other absolutely *vital* things that are important if you
+ * don't want to be banned from Instagram!
+ *
+ * WARNING: This function MAY return a CHALLENGE telling you that the
+ * account needs two-factor login before letting you log in! Read the
+ * two-factor login example to see how to handle that.
+ *
+ * @param string $username Your Instagram username.
+ * You can also use your email or phone,
+ * but take in mind that they won't work
+ * when you have two factor auth enabled.
+ * @param string $password Your Instagram password.
+ * @param int $appRefreshInterval How frequently `login()` should act
+ * like an Instagram app that's been
+ * closed and reopened and needs to
+ * "refresh its state", by asking for
+ * extended account state details.
+ * Default: After `1800` seconds, meaning
+ * `30` minutes after the last
+ * state-refreshing `login()` call.
+ * This CANNOT be longer than `6` hours.
+ * Read `_sendLoginFlow()`! The shorter
+ * your delay is the BETTER. You may even
+ * want to set it to an even LOWER value
+ * than the default 30 minutes!
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse|null A login response if a
+ * full (re-)login
+ * happens, otherwise
+ * `NULL` if an existing
+ * session is resumed.
+ */
+ public function login(
+ $username,
+ $password,
+ $appRefreshInterval = 1800)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to login().');
+ }
+
+ return $this->_login($username, $password, false, $appRefreshInterval);
+ }
+
+ /**
+ * Internal login handler.
+ *
+ * @param string $username
+ * @param string $password
+ * @param bool $forceLogin Force login to Instagram instead of
+ * resuming previous session. Used
+ * internally to do a new, full relogin
+ * when we detect an expired/invalid
+ * previous session.
+ * @param int $appRefreshInterval
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse|null
+ *
+ * @see Instagram::login() The public login handler with a full description.
+ */
+ protected function _login(
+ $username,
+ $password,
+ $forceLogin = false,
+ $appRefreshInterval = 1800)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to _login().');
+ }
+
+ // Switch the currently active user/pass if the details are different.
+ if ($this->username !== $username || $this->password !== $password) {
+ $this->_setUser($username, $password);
+ }
+
+ // Perform a full relogin if necessary.
+ if (!$this->isMaybeLoggedIn || $forceLogin) {
+ $this->_sendPreLoginFlow();
+
+ try {
+ $response = $this->request('accounts/login/')
+ ->setNeedsAuth(false)
+ ->addPost('country_codes', '[{"country_code":"1","source":["default"]}]')
+ ->addPost('phone_id', $this->phone_id)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->addPost('username', $this->username)
+ ->addPost('adid', $this->advertising_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('password', $this->password)
+ ->addPost('google_tokens', '[]')
+ ->addPost('login_attempt_count', 0)
+ ->getResponse(new Response\LoginResponse());
+ } catch (\InstagramAPI\Exception\InstagramException $e) {
+ if ($e->hasResponse() && $e->getResponse()->isTwoFactorRequired()) {
+ // Login failed because two-factor login is required.
+ // Return server response to tell user they need 2-factor.
+ return $e->getResponse();
+ } else {
+ // Login failed for some other reason... Re-throw error.
+ throw $e;
+ }
+ }
+
+ $this->_updateLoginState($response);
+
+ $this->_sendLoginFlow(true, $appRefreshInterval);
+
+ // Full (re-)login successfully completed. Return server response.
+ return $response;
+ }
+
+ // Attempt to resume an existing session, or full re-login if necessary.
+ // NOTE: The "return" here gives a LoginResponse in case of re-login.
+ return $this->_sendLoginFlow(false, $appRefreshInterval);
+ }
+
+ /**
+ * Finish a two-factor authenticated login.
+ *
+ * This function finishes a two-factor challenge that was provided by the
+ * regular `login()` function. If you successfully answer their challenge,
+ * you will be logged in after this function call.
+ *
+ * @param string $username Your Instagram username used for logging
+ * @param string $password Your Instagram password.
+ * @param string $twoFactorIdentifier Two factor identifier, obtained in
+ * login() response. Format: `123456`.
+ * @param string $verificationCode Verification code you have received
+ * via SMS.
+ * @param string $verificationMethod The verification method for 2FA. 1 is SMS,
+ * 2 is backup codes and 3 is TOTP.
+ * @param int $appRefreshInterval See `login()` for description of this
+ * parameter.
+ * @param string $usernameHandler Instagram username sent in the login response,
+ * Email and phone aren't allowed here.
+ * Default value is the first argument $username
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse
+ */
+ public function finishTwoFactorLogin(
+ $username,
+ $password,
+ $twoFactorIdentifier,
+ $verificationCode,
+ $verificationMethod = '1',
+ $appRefreshInterval = 1800,
+ $usernameHandler = null)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to finishTwoFactorLogin().');
+ }
+ if (empty($verificationCode) || empty($twoFactorIdentifier)) {
+ throw new \InvalidArgumentException('You must provide a verification code and two-factor identifier to finishTwoFactorLogin().');
+ }
+ if (!in_array($verificationMethod, ['1', '2', '3'], true)) {
+ throw new \InvalidArgumentException('You must provide a valid verification method value.');
+ }
+
+ // Switch the currently active user/pass if the details are different.
+ // NOTE: The username and password AREN'T actually necessary for THIS
+ // endpoint, but this extra step helps people who statelessly embed the
+ // library directly into a webpage, so they can `finishTwoFactorLogin()`
+ // on their second page load without having to begin any new `login()`
+ // call (since they did that in their previous webpage's library calls).
+ if ($this->username !== $username || $this->password !== $password) {
+ $this->_setUser($username, $password);
+ }
+
+ $username = ($usernameHandler !== null) ? $usernameHandler : $username;
+
+ // Remove all whitespace from the verification code.
+ $verificationCode = preg_replace('/\s+/', '', $verificationCode);
+
+ $response = $this->request('accounts/two_factor_login/')
+ ->setNeedsAuth(false)
+ // 1 - SMS, 2 - Backup codes, 3 - TOTP, 0 - ??
+ ->addPost('verification_method', $verificationMethod)
+ ->addPost('verification_code', $verificationCode)
+ ->addPost('trust_this_device', 1)
+ ->addPost('two_factor_identifier', $twoFactorIdentifier)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->addPost('username', $username)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->getResponse(new Response\LoginResponse());
+
+ $this->_updateLoginState($response);
+
+ $this->_sendLoginFlow(true, $appRefreshInterval);
+
+ return $response;
+ }
+
+ /**
+ * Request a new security code SMS for a Two Factor login account.
+ *
+ * NOTE: You should first attempt to `login()` which will automatically send
+ * you a two factor SMS. This function is just for asking for a new SMS if
+ * the old code has expired.
+ *
+ * NOTE: Instagram can only send you a new code every 60 seconds.
+ *
+ * @param string $username Your Instagram username.
+ * @param string $password Your Instagram password.
+ * @param string $twoFactorIdentifier Two factor identifier, obtained in
+ * `login()` response.
+ * @param string $usernameHandler Instagram username sent in the login response,
+ * Email and phone aren't allowed here.
+ * Default value is the first argument $username
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TwoFactorLoginSMSResponse
+ */
+ public function sendTwoFactorLoginSMS(
+ $username,
+ $password,
+ $twoFactorIdentifier,
+ $usernameHandler = null)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to sendTwoFactorLoginSMS().');
+ }
+ if (empty($twoFactorIdentifier)) {
+ throw new \InvalidArgumentException('You must provide a two-factor identifier to sendTwoFactorLoginSMS().');
+ }
+
+ // Switch the currently active user/pass if the details are different.
+ // NOTE: The password IS NOT actually necessary for THIS
+ // endpoint, but this extra step helps people who statelessly embed the
+ // library directly into a webpage, so they can `sendTwoFactorLoginSMS()`
+ // on their second page load without having to begin any new `login()`
+ // call (since they did that in their previous webpage's library calls).
+ if ($this->username !== $username || $this->password !== $password) {
+ $this->_setUser($username, $password);
+ }
+
+ $username = ($usernameHandler !== null) ? $usernameHandler : $username;
+
+ return $this->request('accounts/send_two_factor_login_sms/')
+ ->setNeedsAuth(false)
+ ->addPost('two_factor_identifier', $twoFactorIdentifier)
+ ->addPost('username', $username)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\TwoFactorLoginSMSResponse());
+ }
+
+ /**
+ * Request information about available password recovery methods for an account.
+ *
+ * This will tell you things such as whether SMS or EMAIL-based recovery is
+ * available for the given account name.
+ *
+ * `WARNING:` You can call this function without having called `login()`,
+ * but be aware that a user database entry will be created for every
+ * username you try to look up. This is ONLY meant for recovering your OWN
+ * accounts.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UsersLookupResponse
+ */
+ public function userLookup(
+ $username)
+ {
+ // Set active user (without pwd), and create database entry if new user.
+ $this->_setUserWithoutPassword($username);
+
+ return $this->request('users/lookup/')
+ ->setNeedsAuth(false)
+ ->addPost('q', $username)
+ ->addPost('directly_sign_in', true)
+ ->addPost('username', $username)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\UsersLookupResponse());
+ }
+
+ /**
+ * Request a recovery EMAIL to get back into your account.
+ *
+ * `WARNING:` You can call this function without having called `login()`,
+ * but be aware that a user database entry will be created for every
+ * username you try to look up. This is ONLY meant for recovering your OWN
+ * accounts.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RecoveryResponse
+ */
+ public function sendRecoveryEmail(
+ $username)
+ {
+ // Verify that they can use the recovery email option.
+ $userLookup = $this->userLookup($username);
+ if (!$userLookup->getCanEmailReset()) {
+ throw new \InstagramAPI\Exception\InternalException('Email recovery is not available, since your account lacks a verified email address.');
+ }
+
+ return $this->request('accounts/send_recovery_flow_email/')
+ ->setNeedsAuth(false)
+ ->addPost('query', $username)
+ ->addPost('adid', $this->advertising_id)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\RecoveryResponse());
+ }
+
+ /**
+ * Request a recovery SMS to get back into your account.
+ *
+ * `WARNING:` You can call this function without having called `login()`,
+ * but be aware that a user database entry will be created for every
+ * username you try to look up. This is ONLY meant for recovering your OWN
+ * accounts.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RecoveryResponse
+ */
+ public function sendRecoverySMS(
+ $username)
+ {
+ // Verify that they can use the recovery SMS option.
+ $userLookup = $this->userLookup($username);
+ if (!$userLookup->getHasValidPhone() || !$userLookup->getCanSmsReset()) {
+ throw new \InstagramAPI\Exception\InternalException('SMS recovery is not available, since your account lacks a verified phone number.');
+ }
+
+ return $this->request('users/lookup_phone/')
+ ->setNeedsAuth(false)
+ ->addPost('query', $username)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\RecoveryResponse());
+ }
+
+ /**
+ * Set the active account for the class instance.
+ *
+ * We can call this multiple times to switch between multiple accounts.
+ *
+ * @param string $username Your Instagram username.
+ * @param string $password Your Instagram password.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _setUser(
+ $username,
+ $password)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to _setUser().');
+ }
+
+ // Load all settings from the storage and mark as current user.
+ $this->settings->setActiveUser($username);
+
+ // Generate the user's device instance, which will be created from the
+ // user's last-used device IF they've got a valid, good one stored.
+ // But if they've got a BAD/none, this will create a brand-new device.
+ $savedDeviceString = $this->settings->get('devicestring');
+ $this->device = new Devices\Device(
+ Constants::IG_VERSION,
+ Constants::VERSION_CODE,
+ Constants::USER_AGENT_LOCALE,
+ $savedDeviceString
+ );
+
+ // Get active device string so that we can compare it to any saved one.
+ $deviceString = $this->device->getDeviceString();
+
+ // Generate a brand-new device fingerprint if the device wasn't reused
+ // from settings, OR if any of the stored fingerprints are missing.
+ // NOTE: The regeneration when our device model changes is to avoid
+ // dangerously reusing the "previous phone's" unique hardware IDs.
+ // WARNING TO CONTRIBUTORS: Only add new parameter-checks here if they
+ // are CRITICALLY important to the particular device. We don't want to
+ // frivolously force the users to generate new device IDs constantly.
+ $resetCookieJar = false;
+ if ($deviceString !== $savedDeviceString // Brand new device, or missing
+ || empty($this->settings->get('uuid')) // one of the critically...
+ || empty($this->settings->get('phone_id')) // ...important device...
+ || empty($this->settings->get('device_id'))) { // ...parameters.
+ // Erase all previously stored device-specific settings and cookies.
+ $this->settings->eraseDeviceSettings();
+
+ // Save the chosen device string to settings.
+ $this->settings->set('devicestring', $deviceString);
+
+ // Generate hardware fingerprints for the new device.
+ $this->settings->set('device_id', Signatures::generateDeviceId());
+ $this->settings->set('phone_id', Signatures::generateUUID(true));
+ $this->settings->set('uuid', Signatures::generateUUID(true));
+
+ // Erase any stored account ID, to ensure that we detect ourselves
+ // as logged-out. This will force a new relogin from the new device.
+ $this->settings->set('account_id', '');
+
+ // We'll also need to throw out all previous cookies.
+ $resetCookieJar = true;
+ }
+
+ // Generate other missing values. These are for less critical parameters
+ // that don't need to trigger a complete device reset like above. For
+ // example, this is good for new parameters that Instagram introduces
+ // over time, since those can be added one-by-one over time here without
+ // needing to wipe/reset the whole device.
+ if (empty($this->settings->get('advertising_id'))) {
+ $this->settings->set('advertising_id', Signatures::generateUUID(true));
+ }
+ if (empty($this->settings->get('session_id'))) {
+ $this->settings->set('session_id', Signatures::generateUUID(true));
+ }
+
+ // Store various important parameters for easy access.
+ $this->username = $username;
+ $this->password = $password;
+ $this->uuid = $this->settings->get('uuid');
+ $this->advertising_id = $this->settings->get('advertising_id');
+ $this->device_id = $this->settings->get('device_id');
+ $this->phone_id = $this->settings->get('phone_id');
+ $this->session_id = $this->settings->get('session_id');
+ $this->experiments = $this->settings->getExperiments();
+
+ // Load the previous session details if we're possibly logged in.
+ if (!$resetCookieJar && $this->settings->isMaybeLoggedIn()) {
+ $this->isMaybeLoggedIn = true;
+ $this->account_id = $this->settings->get('account_id');
+ } else {
+ $this->isMaybeLoggedIn = false;
+ $this->account_id = null;
+ }
+
+ // Configures Client for current user AND updates isMaybeLoggedIn state
+ // if it fails to load the expected cookies from the user's jar.
+ // Must be done last here, so that isMaybeLoggedIn is properly updated!
+ // NOTE: If we generated a new device we start a new cookie jar.
+ $this->client->updateFromCurrentSettings($resetCookieJar);
+ }
+
+ /**
+ * Set the active account for the class instance, without knowing password.
+ *
+ * This internal function is used by all unauthenticated pre-login functions
+ * whenever they need to perform unauthenticated requests, such as looking
+ * up a user's account recovery options.
+ *
+ * `WARNING:` A user database entry will be created for every username you
+ * set as the active user, exactly like the normal `_setUser()` function.
+ * This is necessary so that we generate a user-device and data storage for
+ * each given username, which gives us necessary data such as a "device ID"
+ * for the new user's virtual device, to use in various API-call parameters.
+ *
+ * `WARNING:` This function CANNOT be used for performing logins, since
+ * Instagram will validate the password and will reject the missing
+ * password. It is ONLY meant to be used for *RECOVERY* PRE-LOGIN calls that
+ * need device parameters when the user DOESN'T KNOW their password yet.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _setUserWithoutPassword(
+ $username)
+ {
+ if (empty($username) || !is_string($username)) {
+ throw new \InvalidArgumentException('You must provide a username.');
+ }
+
+ // Switch the currently active user/pass if the username is different.
+ // NOTE: Creates a user database (device) for the user if they're new!
+ // NOTE: Because we don't know their password, we'll mark the user as
+ // having "NOPASSWORD" as pwd. The user will fix that when/if they call
+ // `login()` with the ACTUAL password, which will tell us what it is.
+ // We CANNOT use an empty string since `_setUser()` will not allow that!
+ // NOTE: If the user tries to look up themselves WHILE they are logged
+ // in, we'll correctly NOT call `_setUser()` since they're already set.
+ if ($this->username !== $username) {
+ $this->_setUser($username, 'NOPASSWORD');
+ }
+ }
+
+ /**
+ * Updates the internal state after a successful login.
+ *
+ * @param Response\LoginResponse $response The login response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _updateLoginState(
+ Response\LoginResponse $response)
+ {
+ // This check is just protection against accidental bugs. It makes sure
+ // that we always call this function with a *successful* login response!
+ if (!$response instanceof Response\LoginResponse
+ || !$response->isOk()) {
+ throw new \InvalidArgumentException('Invalid login response provided to _updateLoginState().');
+ }
+
+ $this->isMaybeLoggedIn = true;
+ $this->account_id = $response->getLoggedInUser()->getPk();
+ $this->settings->set('account_id', $this->account_id);
+ $this->settings->set('last_login', time());
+ }
+
+ /**
+ * Sends pre-login flow. This is required to emulate real device behavior.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _sendPreLoginFlow()
+ {
+ // Reset zero rating rewrite rules.
+ $this->client->zeroRating()->reset();
+ // Calling this non-token API will put a csrftoken in our cookie
+ // jar. We must do this before any functions that require a token.
+ $this->internal->fetchZeroRatingToken();
+ $this->internal->bootstrapMsisdnHeader();
+ $this->internal->readMsisdnHeader('default');
+ $this->internal->syncDeviceFeatures(true);
+ $this->internal->sendLauncherSync(true);
+ $this->internal->bootstrapMsisdnHeader();
+ $this->internal->logAttribution();
+ $this->account->getPrefillCandidates();
+ $this->internal->readMsisdnHeader('default', true);
+ $this->account->setContactPointPrefill('prefill');
+ $this->internal->sendLauncherSync(true, true, true);
+ $this->internal->syncDeviceFeatures(true, true);
+ }
+
+ /**
+ * Registers available Push channels during the login flow.
+ */
+ protected function _registerPushChannels()
+ {
+ // Forcibly remove the stored token value if >24 hours old.
+ // This prevents us from constantly re-registering the user's
+ // "useless" token if they have stopped using the Push features.
+ try {
+ $lastFbnsToken = (int) $this->settings->get('last_fbns_token');
+ } catch (\Exception $e) {
+ $lastFbnsToken = null;
+ }
+ if (!$lastFbnsToken || $lastFbnsToken < strtotime('-24 hours')) {
+ try {
+ $this->settings->set('fbns_token', '');
+ } catch (\Exception $e) {
+ // Ignore storage errors.
+ }
+
+ return;
+ }
+
+ // Read our token from the storage.
+ try {
+ $fbnsToken = $this->settings->get('fbns_token');
+ } catch (\Exception $e) {
+ $fbnsToken = null;
+ }
+ if ($fbnsToken === null) {
+ return;
+ }
+
+ // Register our last token since we had a fresh (age <24 hours) one,
+ // or clear our stored token if we fail to register it again.
+ try {
+ $this->push->register('mqtt', $fbnsToken);
+ } catch (\Exception $e) {
+ try {
+ $this->settings->set('fbns_token', '');
+ } catch (\Exception $e) {
+ // Ignore storage errors.
+ }
+ }
+ }
+
+ /**
+ * Sends login flow. This is required to emulate real device behavior.
+ *
+ * @param bool $justLoggedIn Whether we have just performed a full
+ * relogin (rather than doing a resume).
+ * @param int $appRefreshInterval See `login()` for description of this
+ * parameter.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse|null A login response if a
+ * full (re-)login is
+ * needed during the login
+ * flow attempt, otherwise
+ * `NULL`.
+ */
+ protected function _sendLoginFlow(
+ $justLoggedIn,
+ $appRefreshInterval = 1800)
+ {
+ if (!is_int($appRefreshInterval) || $appRefreshInterval < 0) {
+ throw new \InvalidArgumentException("Instagram's app state refresh interval must be a positive integer.");
+ }
+ if ($appRefreshInterval > 21600) {
+ throw new \InvalidArgumentException("Instagram's app state refresh interval is NOT allowed to be higher than 6 hours, and the lower the better!");
+ }
+
+ // SUPER IMPORTANT:
+ //
+ // STOP trying to ask us to remove this code section!
+ //
+ // EVERY time the user presses their device's home button to leave the
+ // app and then comes back to the app, Instagram does ALL of these things
+ // to refresh its internal app state. We MUST emulate that perfectly,
+ // otherwise Instagram will silently detect you as a "fake" client
+ // after a while!
+ //
+ // You can configure the login's $appRefreshInterval in the function
+ // parameter above, but you should keep it VERY frequent (definitely
+ // NEVER longer than 6 hours), so that Instagram sees you as a real
+ // client that keeps quitting and opening their app like a REAL user!
+ //
+ // Otherwise they WILL detect you as a bot and silently BLOCK features
+ // or even ban you.
+ //
+ // You have been warned.
+ if ($justLoggedIn) {
+ // Reset zero rating rewrite rules.
+ $this->client->zeroRating()->reset();
+ // Perform the "user has just done a full login" API flow.
+ $this->account->getAccountFamily();
+ $this->internal->sendLauncherSync(false, false, true);
+ $this->internal->fetchZeroRatingToken();
+ $this->internal->syncUserFeatures();
+ $this->timeline->getTimelineFeed();
+ $this->story->getReelsTrayFeed('cold_start');
+ $this->internal->sendLauncherSync(false, false, true, true);
+ $this->story->getReelsMediaFeed($this->account_id);
+ $this->people->getRecentActivityInbox();
+ //TODO: Figure out why this isn't sending...
+// $this->internal->logResurrectAttribution();
+ $this->internal->getLoomFetchConfig();
+ $this->internal->getDeviceCapabilitiesDecisions();
+ $this->people->getBootstrapUsers();
+ $this->people->getInfoById($this->account_id);
+ $this->account->getLinkageStatus();
+ $this->creative->sendSupportedCapabilities();
+ $this->media->getBlockedMedia();
+ $this->internal->storeClientPushPermissions();
+ $this->internal->getQPCooldowns();
+ $this->_registerPushChannels();
+ $this->story->getReelsMediaFeed($this->account_id);
+ $this->discover->getExploreFeed(null, null, true);
+ $this->internal->getQPFetch();
+ $this->account->getProcessContactPointSignals();
+ $this->internal->getArlinkDownloadInfo();
+ $this->_registerPushChannels();
+ $this->people->getSharePrefill();
+ $this->direct->getPresences();
+ $this->direct->getInbox();
+ $this->direct->getInbox(null, 20, 10);
+ $this->_registerPushChannels();
+ $this->internal->getFacebookOTA();
+ } else {
+ $lastLoginTime = $this->settings->get('last_login');
+ $isSessionExpired = $lastLoginTime === null || (time() - $lastLoginTime) > $appRefreshInterval;
+
+ // Act like a real logged in app client refreshing its news timeline.
+ // This also lets us detect if we're still logged in with a valid session.
+ if ($isSessionExpired) {
+ // Act like a real logged in app client refreshing its news timeline.
+ // This also lets us detect if we're still logged in with a valid session.
+ try {
+ $this->story->getReelsTrayFeed('cold_start');
+ } catch (\InstagramAPI\Exception\LoginRequiredException $e) {
+ // If our session cookies are expired, we were now told to login,
+ // so handle that by running a forced relogin in that case!
+ return $this->_login($this->username, $this->password, true, $appRefreshInterval);
+ }
+ $this->timeline->getTimelineFeed(null, [
+ 'is_pull_to_refresh' => $isSessionExpired ? null : mt_rand(1, 3) < 3,
+ ]);
+ $this->people->getSharePrefill();
+ $this->people->getRecentActivityInbox();
+
+ $this->people->getSharePrefill();
+ $this->people->getRecentActivityInbox();
+ $this->people->getInfoById($this->account_id);
+ $this->internal->getDeviceCapabilitiesDecisions();
+ $this->direct->getPresences();
+ $this->discover->getExploreFeed();
+ $this->direct->getInbox();
+
+ $this->settings->set('last_login', time());
+ // Generate and save a new application session ID.
+ $this->session_id = Signatures::generateUUID();
+ $this->settings->set('session_id', $this->session_id);
+ // Do the rest of the "user is re-opening the app" API flow...
+ $this->tv->getTvGuide();
+
+ $this->internal->getLoomFetchConfig();
+ $this->direct->getRankedRecipients('reshare', true);
+ $this->direct->getRankedRecipients('raven', true);
+ $this->_registerPushChannels();
+ }
+
+ // Users normally resume their sessions, meaning that their
+ // experiments never get synced and updated. So sync periodically.
+ $lastExperimentsTime = $this->settings->get('last_experiments');
+ if ($lastExperimentsTime === null || (time() - $lastExperimentsTime) > self::EXPERIMENTS_REFRESH) {
+ $this->internal->syncUserFeatures();
+ $this->internal->syncDeviceFeatures();
+ }
+
+ // Update zero rating token when it has been expired.
+ $expired = time() - (int) $this->settings->get('zr_expires');
+ if ($expired > 0) {
+ $this->client->zeroRating()->reset();
+ $this->internal->fetchZeroRatingToken($expired > 7200 ? 'token_stale' : 'token_expired');
+ }
+ }
+
+ // We've now performed a login or resumed a session. Forcibly write our
+ // cookies to the storage, to ensure that the storage doesn't miss them
+ // in case something bad happens to PHP after this moment.
+ $this->client->saveCookieJar();
+
+ return null;
+ }
+
+ /**
+ * Log out of Instagram.
+ *
+ * WARNING: Most people should NEVER call `logout()`! Our library emulates
+ * the Instagram app for Android, where you are supposed to stay logged in
+ * forever. By calling this function, you will tell Instagram that you are
+ * logging out of the APP. But you SHOULDN'T do that! In almost 100% of all
+ * cases you want to *stay logged in* so that `login()` resumes your session!
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LogoutResponse
+ *
+ * @see Instagram::login()
+ */
+ public function logout()
+ {
+ $response = $this->request('accounts/logout/')
+ ->setSignedPost(false)
+ ->addPost('phone_id', $this->phone_id)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->addPost('guid', $this->uuid)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('_uuid', $this->uuid)
+ ->getResponse(new Response\LogoutResponse());
+
+ // We've now logged out. Forcibly write our cookies to the storage, to
+ // ensure that the storage doesn't miss them in case something bad
+ // happens to PHP after this moment.
+ $this->client->saveCookieJar();
+
+ return $response;
+ }
+
+ /**
+ * Checks if a parameter is enabled in the given experiment.
+ *
+ * @param string $experiment
+ * @param string $param
+ * @param bool $default
+ *
+ * @return bool
+ */
+ public function isExperimentEnabled(
+ $experiment,
+ $param,
+ $default = false)
+ {
+ return isset($this->experiments[$experiment][$param])
+ ? in_array($this->experiments[$experiment][$param], ['enabled', 'true', '1'])
+ : $default;
+ }
+
+ /**
+ * Get a parameter value for the given experiment.
+ *
+ * @param string $experiment
+ * @param string $param
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getExperimentParam(
+ $experiment,
+ $param,
+ $default = null)
+ {
+ return isset($this->experiments[$experiment][$param])
+ ? $this->experiments[$experiment][$param]
+ : $default;
+ }
+
+ /**
+ * Create a custom API request.
+ *
+ * Used internally, but can also be used by end-users if they want
+ * to create completely custom API queries without modifying this library.
+ *
+ * @param string $url
+ *
+ * @return \InstagramAPI\Request
+ */
+ public function request(
+ $url)
+ {
+ return new Request($this, $url);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/InstagramID.php b/instafeed/vendor/mgp25/instagram-php/src/InstagramID.php
new file mode 100755
index 0000000..d26f3c0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/InstagramID.php
@@ -0,0 +1,249 @@
+_width = (int) $width;
+ $this->_height = (int) $height;
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $this->_aspectRatio = (float) ($this->_width / $this->_height);
+ }
+
+ /**
+ * Get stored width for these dimensions.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->_width;
+ }
+
+ /**
+ * Get stored height for these dimensions.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->_height;
+ }
+
+ /**
+ * Get stored aspect ratio for these dimensions.
+ *
+ * @return float
+ */
+ public function getAspectRatio()
+ {
+ return $this->_aspectRatio;
+ }
+
+ /**
+ * Create a new object with swapped axes.
+ *
+ * @return self
+ */
+ public function withSwappedAxes()
+ {
+ return new self($this->_height, $this->_width);
+ }
+
+ /**
+ * Create a new, scale-adjusted object.
+ *
+ * @param float|int $newScale The scale factor to apply.
+ * @param string $roundingFunc One of `round` (default), `floor` or `ceil`.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function withRescaling(
+ $newScale = 1.0,
+ $roundingFunc = 'round')
+ {
+ if (!is_float($newScale) && !is_int($newScale)) {
+ throw new \InvalidArgumentException('The new scale must be a float or integer.');
+ }
+ if ($roundingFunc !== 'round' && $roundingFunc !== 'floor' && $roundingFunc !== 'ceil') {
+ throw new \InvalidArgumentException(sprintf('Invalid rounding function "%s".', $roundingFunc));
+ }
+
+ $newWidth = (int) $roundingFunc($newScale * $this->_width);
+ $newHeight = (int) $roundingFunc($newScale * $this->_height);
+
+ return new self($newWidth, $newHeight);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Media/Geometry/Rectangle.php b/instafeed/vendor/mgp25/instagram-php/src/Media/Geometry/Rectangle.php
new file mode 100755
index 0000000..513a657
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Media/Geometry/Rectangle.php
@@ -0,0 +1,179 @@
+_x = (int) $x;
+ $this->_y = (int) $y;
+ $this->_width = (int) $width;
+ $this->_height = (int) $height;
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $this->_aspectRatio = (float) ($this->_width / $this->_height);
+ }
+
+ /**
+ * Get stored X1 offset for this rectangle.
+ *
+ * @return int
+ */
+ public function getX()
+ {
+ return $this->_x;
+ }
+
+ /**
+ * Get stored Y1 offset for this rectangle.
+ *
+ * @return int
+ */
+ public function getY()
+ {
+ return $this->_y;
+ }
+
+ /**
+ * Get stored X1 offset for this rectangle.
+ *
+ * This does the same thing as `getX()`. It is just a mental
+ * convenience when working in X1/X2 space.
+ *
+ * @return int
+ */
+ public function getX1()
+ {
+ return $this->_x;
+ }
+
+ /**
+ * Get stored Y1 offset for this rectangle.
+ *
+ * This does the same thing as `getY()`. It is just a mental
+ * convenience when working in Y1/Y2 space.
+ *
+ * @return int
+ */
+ public function getY1()
+ {
+ return $this->_y;
+ }
+
+ /**
+ * Get calculated X2 offset (X1+Width) for this rectangle.
+ *
+ * @return int
+ */
+ public function getX2()
+ {
+ return $this->_x + $this->_width;
+ }
+
+ /**
+ * Get calculated Y2 offset (Y1+Height) for this rectangle.
+ *
+ * @return int
+ */
+ public function getY2()
+ {
+ return $this->_y + $this->_height;
+ }
+
+ /**
+ * Get stored width for this rectangle.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->_width;
+ }
+
+ /**
+ * Get stored height for this rectangle.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->_height;
+ }
+
+ /**
+ * Get stored aspect ratio for this rectangle.
+ *
+ * @return float
+ */
+ public function getAspectRatio()
+ {
+ return $this->_aspectRatio;
+ }
+
+ /**
+ * Create a new object with swapped axes.
+ *
+ * @return self
+ */
+ public function withSwappedAxes()
+ {
+ return new self($this->_y, $this->_x, $this->_height, $this->_width);
+ }
+
+ /**
+ * Create a new, scale-adjusted object.
+ *
+ * NOTE: The x1/y1 offsets are not affected. Only the width and height. But
+ * those new dimensions WILL affect the x2/y2 offsets, as you'd expect.
+ *
+ * @param float|int $newScale The scale factor to apply.
+ * @param string $roundingFunc One of `round` (default), `floor` or `ceil`.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function withRescaling(
+ $newScale = 1.0,
+ $roundingFunc = 'round')
+ {
+ if (!is_float($newScale) && !is_int($newScale)) {
+ throw new \InvalidArgumentException('The new scale must be a float or integer.');
+ }
+ if ($roundingFunc !== 'round' && $roundingFunc !== 'floor' && $roundingFunc !== 'ceil') {
+ throw new \InvalidArgumentException(sprintf('Invalid rounding function "%s".', $roundingFunc));
+ }
+
+ $newWidth = (int) $roundingFunc($newScale * $this->_width);
+ $newHeight = (int) $roundingFunc($newScale * $this->_height);
+
+ return new self($this->_x, $this->_y, $newWidth, $newHeight);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Media/InstagramMedia.php b/instafeed/vendor/mgp25/instagram-php/src/Media/InstagramMedia.php
new file mode 100755
index 0000000..02ed801
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Media/InstagramMedia.php
@@ -0,0 +1,1456 @@
+_debug = $debug === true;
+
+ // Input file.
+ if (!is_file($inputFile)) {
+ throw new \InvalidArgumentException(sprintf('Input file "%s" doesn\'t exist.', $inputFile));
+ }
+ $this->_inputFile = $inputFile;
+
+ // Horizontal crop focus.
+ if ($horCropFocus !== null && (!is_int($horCropFocus) || $horCropFocus < -50 || $horCropFocus > 50)) {
+ throw new \InvalidArgumentException('Horizontal crop focus must be between -50 and 50.');
+ }
+ $this->_horCropFocus = $horCropFocus;
+
+ // Vertical crop focus.
+ if ($verCropFocus !== null && (!is_int($verCropFocus) || $verCropFocus < -50 || $verCropFocus > 50)) {
+ throw new \InvalidArgumentException('Vertical crop focus must be between -50 and 50.');
+ }
+ $this->_verCropFocus = $verCropFocus;
+
+ // Minimum and maximum aspect ratio range.
+ if ($minAspectRatio !== null && !is_float($minAspectRatio)) {
+ throw new \InvalidArgumentException('Minimum aspect ratio must be a floating point number.');
+ }
+ if ($maxAspectRatio !== null && !is_float($maxAspectRatio)) {
+ throw new \InvalidArgumentException('Maximum aspect ratio must be a floating point number.');
+ }
+
+ // Does the user want to override (force) the final "target aspect ratio" choice?
+ // NOTE: This will be used to override `$this->_forceTargetAspectRatio`.
+ $this->_hasUserForceTargetAspectRatio = false;
+ if ($userForceTargetAspectRatio !== null) {
+ if (!is_float($userForceTargetAspectRatio) && !is_int($userForceTargetAspectRatio)) {
+ throw new \InvalidArgumentException('Custom target aspect ratio must be a float or integer.');
+ }
+ $userForceTargetAspectRatio = (float) $userForceTargetAspectRatio;
+ $this->_hasUserForceTargetAspectRatio = true;
+ $useRecommendedRatio = false; // We forcibly disable this too, to avoid risk of future bugs.
+ }
+
+ // Create constraints and determine whether to use "recommended target aspect ratio" (if one is available for feed).
+ $this->_constraints = ConstraintsFactory::createFor($targetFeed);
+ if (!$this->_hasUserForceTargetAspectRatio && $useRecommendedRatio === null) {
+ // No value is provided, so let's guess it.
+ if ($minAspectRatio !== null || $maxAspectRatio !== null) {
+ // If we have at least one custom ratio, we must not use recommended ratio.
+ $useRecommendedRatio = false;
+ } else {
+ // Use the recommended value from constraints (either on or off, depending on which target feed).
+ $useRecommendedRatio = $this->_constraints->useRecommendedRatioByDefault();
+ }
+ }
+
+ // Determine the legal min/max aspect ratios for the target feed.
+ if (!$this->_hasUserForceTargetAspectRatio && $useRecommendedRatio === true) {
+ $this->_forceTargetAspectRatio = $this->_constraints->getRecommendedRatio();
+ $deviation = $this->_constraints->getRecommendedRatioDeviation();
+ $minAspectRatio = $this->_forceTargetAspectRatio - $deviation;
+ $maxAspectRatio = $this->_forceTargetAspectRatio + $deviation;
+ } else {
+ // If the user hasn't specified a custom target aspect ratio, this
+ // "force" value will remain NULL (and the target ratio will be
+ // auto-calculated by the canvas generation algorithms instead).
+ $this->_forceTargetAspectRatio = $userForceTargetAspectRatio;
+ $allowedMinRatio = $this->_constraints->getMinAspectRatio();
+ $allowedMaxRatio = $this->_constraints->getMaxAspectRatio();
+
+ // Select allowed aspect ratio range based on defaults and user input.
+ if ($minAspectRatio !== null && ($minAspectRatio < $allowedMinRatio || $minAspectRatio > $allowedMaxRatio)) {
+ throw new \InvalidArgumentException(sprintf('Minimum aspect ratio must be between %.3f and %.3f.',
+ $allowedMinRatio, $allowedMaxRatio));
+ }
+ if ($minAspectRatio === null) {
+ $minAspectRatio = $allowedMinRatio;
+ }
+ if ($maxAspectRatio !== null && ($maxAspectRatio < $allowedMinRatio || $maxAspectRatio > $allowedMaxRatio)) {
+ throw new \InvalidArgumentException(sprintf('Maximum aspect ratio must be between %.3f and %.3f.',
+ $allowedMinRatio, $allowedMaxRatio));
+ }
+ if ($maxAspectRatio === null) {
+ $maxAspectRatio = $allowedMaxRatio;
+ }
+ if ($minAspectRatio !== null && $maxAspectRatio !== null && $minAspectRatio > $maxAspectRatio) {
+ throw new \InvalidArgumentException('Maximum aspect ratio must be greater than or equal to minimum.');
+ }
+
+ // Validate custom target aspect ratio legality if provided by user.
+ if ($this->_hasUserForceTargetAspectRatio) {
+ if ($minAspectRatio !== null && $this->_forceTargetAspectRatio < $minAspectRatio) {
+ throw new \InvalidArgumentException(sprintf('Custom target aspect ratio (%.5f) must be greater than or equal to the minimum aspect ratio (%.5f).',
+ $this->_forceTargetAspectRatio, $minAspectRatio));
+ }
+ if ($maxAspectRatio !== null && $this->_forceTargetAspectRatio > $maxAspectRatio) {
+ throw new \InvalidArgumentException(sprintf('Custom target aspect ratio (%.5f) must be lesser than or equal to the maximum aspect ratio (%.5f).',
+ $this->_forceTargetAspectRatio, $maxAspectRatio));
+ }
+ }
+ }
+ $this->_minAspectRatio = $minAspectRatio;
+ $this->_maxAspectRatio = $maxAspectRatio;
+
+ // Allow the aspect ratio of the final, new canvas to deviate slightly from the min/max range?
+ $this->_allowNewAspectDeviation = $allowNewAspectDeviation;
+
+ // Background color.
+ if ($bgColor !== null && (!is_array($bgColor) || count($bgColor) !== 3 || !isset($bgColor[0]) || !isset($bgColor[1]) || !isset($bgColor[2]))) {
+ throw new \InvalidArgumentException('The background color must be a 3-element array [R, G, B].');
+ } elseif ($bgColor === null) {
+ $bgColor = [255, 255, 255]; // White.
+ }
+ $this->_bgColor = $bgColor;
+
+ //Blurred border
+ $this->_blurredBorder = $blurredBorder;
+
+ // Media operation.
+ if ($operation !== self::CROP && $operation !== self::EXPAND) {
+ throw new \InvalidArgumentException('The operation must be one of the class constants CROP or EXPAND.');
+ }
+ $this->_operation = $operation;
+
+ // Temporary directory path.
+ if ($tmpPath === null) {
+ $tmpPath = self::$defaultTmpPath !== null
+ ? self::$defaultTmpPath
+ : sys_get_temp_dir();
+ }
+ if (!is_dir($tmpPath) || !is_writable($tmpPath)) {
+ throw new \InvalidArgumentException(sprintf('Directory %s does not exist or is not writable.', $tmpPath));
+ }
+ $this->_tmpPath = realpath($tmpPath);
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->deleteFile();
+ }
+
+ /**
+ * Removes the output file if it exists and differs from input file.
+ *
+ * This function is safe and won't delete the original input file.
+ *
+ * Is automatically called when the class instance is destroyed by PHP.
+ * But you can manually call it ahead of time if you want to force cleanup.
+ *
+ * Note that getFile() will still work afterwards, but will have to process
+ * the media again to a new temp file if the input file required processing.
+ *
+ * @return bool
+ */
+ public function deleteFile()
+ {
+ // Only delete if outputfile exists and isn't the same as input file.
+ if ($this->_outputFile !== null && $this->_outputFile !== $this->_inputFile && is_file($this->_outputFile)) {
+ $result = @unlink($this->_outputFile);
+ $this->_outputFile = null; // Reset so getFile() will work again.
+ return $result;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the path to a media file matching the requirements.
+ *
+ * The automatic processing is performed the first time that this function
+ * is called. Which means that no CPU time is wasted if you never call this
+ * function at all.
+ *
+ * Due to the processing, the first call to this function may take a moment.
+ *
+ * If the input file already fits all of the specifications, we simply
+ * return the input path instead, without any need to re-process it.
+ *
+ * @throws \Exception
+ * @throws \RuntimeException
+ *
+ * @return string The path to the media file.
+ *
+ * @see InstagramMedia::_shouldProcess() The criteria that determines processing.
+ */
+ public function getFile()
+ {
+ if ($this->_outputFile === null) {
+ $this->_outputFile = $this->_shouldProcess() ? $this->_process() : $this->_inputFile;
+ }
+
+ return $this->_outputFile;
+ }
+
+ /**
+ * Checks whether we should process the input file.
+ *
+ * @return bool
+ */
+ protected function _shouldProcess()
+ {
+ $inputAspectRatio = $this->_details->getAspectRatio();
+
+ // Process if aspect ratio < minimum allowed.
+ if ($this->_minAspectRatio !== null && $inputAspectRatio < $this->_minAspectRatio) {
+ return true;
+ }
+
+ // Process if aspect ratio > maximum allowed.
+ if ($this->_maxAspectRatio !== null && $inputAspectRatio > $this->_maxAspectRatio) {
+ return true;
+ }
+
+ // Process if USER provided the custom aspect ratio target and input deviates too much.
+ if ($this->_hasUserForceTargetAspectRatio) {
+ if ($this->_forceTargetAspectRatio == 1.0) {
+ // User wants a SQUARE canvas, which can ALWAYS be achieved (by
+ // making both sides equal). Process input if not EXACTLY square.
+ // WARNING: Comparison here and above MUST use `!=` (NOT strict
+ // `!==`) to support both int(1) and float(1.0) values!
+ if ($inputAspectRatio != 1.0) {
+ return true;
+ }
+ } else {
+ // User wants a non-square canvas, which is almost always
+ // IMPOSSIBLE to achieve perfectly. Only process if input
+ // deviates too much from the desired target.
+ $acceptableDeviation = 0.003; // Allow a very narrow range around the user's target.
+ $acceptableMinAspectRatio = $this->_forceTargetAspectRatio - $acceptableDeviation;
+ $acceptableMaxAspectRatio = $this->_forceTargetAspectRatio + $acceptableDeviation;
+ if ($inputAspectRatio < $acceptableMinAspectRatio || $inputAspectRatio > $acceptableMaxAspectRatio) {
+ return true;
+ }
+ }
+ }
+
+ // Process if the media can't be uploaded to Instagram as is.
+ // NOTE: Nobody is allowed to call `isMod2CanvasRequired()` here. That
+ // isn't its purpose. Whether a final Mod2 canvas is required for actual
+ // resizing has NOTHING to do with whether the input file is ok.
+ try {
+ $this->_details->validate($this->_constraints);
+
+ return false;
+ } catch (\Exception $e) {
+ return true;
+ }
+ }
+
+ /**
+ * Whether this processor requires Mod2 width and height canvas dimensions.
+ *
+ * If this returns FALSE, the calculated `InstagramMedia` canvas passed to
+ * this processor _may_ contain uneven width and/or height as the selected
+ * output dimensions.
+ *
+ * Therefore, this function must return TRUE if (and ONLY IF) perfectly even
+ * dimensions are necessary for this particular processor's output format.
+ *
+ * For example, JPEG images accept any dimensions and must therefore return
+ * FALSE. But H264 videos require EVEN dimensions and must return TRUE.
+ *
+ * @return bool
+ */
+ abstract protected function _isMod2CanvasRequired();
+
+ /**
+ * Process the input file and create the new file.
+ *
+ * @throws \RuntimeException
+ *
+ * @return string The path to the new file.
+ */
+ protected function _process()
+ {
+ // Get the dimensions of the original input file.
+ $inputCanvas = new Dimensions($this->_details->getWidth(), $this->_details->getHeight());
+
+ // Create an output canvas with the desired dimensions.
+ // WARNING: This creates a LEGAL canvas which MUST be followed EXACTLY.
+ $canvasInfo = $this->_calculateNewCanvas( // Throws.
+ $this->_operation,
+ $inputCanvas->getWidth(),
+ $inputCanvas->getHeight(),
+ $this->_isMod2CanvasRequired(),
+ $this->_details->getMinAllowedWidth(),
+ $this->_details->getMaxAllowedWidth(),
+ $this->_minAspectRatio,
+ $this->_maxAspectRatio,
+ $this->_forceTargetAspectRatio,
+ $this->_allowNewAspectDeviation
+ );
+ $outputCanvas = $canvasInfo['canvas'];
+
+ // Determine the media operation's resampling parameters and perform it.
+ // NOTE: This section is EXCESSIVELY commented to explain each step. The
+ // algorithm is pretty easy after you understand it. But without the
+ // detailed comments, future contributors may not understand any of it!
+ // "We'd rather have a WaLL oF TeXt for future reference, than bugs due
+ // to future misunderstandings!" - SteveJobzniak ;-)
+ if ($this->_operation === self::CROP) {
+ // Determine the IDEAL canvas dimensions as if Mod2 adjustments were
+ // not applied. That's NECESSARY for calculating an ACCURATE scale-
+ // change compared to the input, so that we can calculate how much
+ // the canvas has rescaled. WARNING: These are 1-dimensional scales,
+ // and only ONE value (the uncropped side) is valid for comparison.
+ $idealCanvas = new Dimensions($outputCanvas->getWidth() - $canvasInfo['mod2WidthDiff'],
+ $outputCanvas->getHeight() - $canvasInfo['mod2HeightDiff']);
+ $idealWidthScale = (float) ($idealCanvas->getWidth() / $inputCanvas->getWidth());
+ $idealHeightScale = (float) ($idealCanvas->getHeight() / $inputCanvas->getHeight());
+ $this->_debugDimensions(
+ $inputCanvas->getWidth(), $inputCanvas->getHeight(),
+ 'CROP: Analyzing Original Input Canvas Size'
+ );
+ $this->_debugDimensions(
+ $idealCanvas->getWidth(), $idealCanvas->getHeight(),
+ 'CROP: Analyzing Ideally Cropped (Non-Mod2-adjusted) Output Canvas Size'
+ );
+ $this->_debugText(
+ 'CROP: Scale of Ideally Cropped Canvas vs Input Canvas',
+ 'width=%.8f, height=%.8f',
+ $idealWidthScale, $idealHeightScale
+ );
+
+ // Now determine HOW the IDEAL canvas has been cropped compared to
+ // the INPUT canvas. But we can't just compare dimensions, since our
+ // algorithms may have cropped and THEN scaled UP the dimensions to
+ // legal values far above the input values, or scaled them DOWN and
+ // then Mod2-cropped at the new scale, etc. There are so many
+ // possibilities. That's also why we couldn't "just keep track of
+ // amount of pixels cropped during main algorithm". We MUST figure
+ // it out ourselves accurately HERE. We can't do it at any earlier
+ // stage, since cumulative rounding errors from width/height
+ // readjustments could drift us away from the target aspect ratio
+ // and could prevent pixel-perfect results UNLESS we calc it HERE.
+ //
+ // There's IS a great way to figure out the cropping. When the WIDTH
+ // of a canvas is reduced (making it more "portraity"), its aspect
+ // ratio number decreases. When the HEIGHT of a canvas is reduced
+ // (making it more "landscapey"), its aspect ratio number increases.
+ //
+ // And our canvas cropping algorithm only crops in ONE DIRECTION
+ // (width or height), so we only need to detect the aspect ratio
+ // change of the IDEAL (non-Mod2-adjusted) canvas, to know what
+ // happened. However, note that this CAN also trigger if the input
+ // had to be up/downscaled (to an imperfect final aspect), but that
+ // doesn't matter since this algorithm will STILL figure out the
+ // proper scale and croppings to use for the canvas. Because uneven,
+ // aspect-affecting scaling basically IS cropping the INPUT canvas!
+ if ($idealCanvas->getAspectRatio() === $inputCanvas->getAspectRatio()) {
+ // No sides have been cropped. So both width and height scales
+ // WILL be IDENTICAL, since NOTHING else would be able to create
+ // an identical aspect ratio again (otherwise the aspect ratio
+ // would have been warped (not equal)). So just pick either one.
+ // NOTE: Identical (uncropped ratio) DOESN'T mean that scale is
+ // going to be 1.0. It MAY be. Or the canvas MAY have been
+ // evenly expanded or evenly shrunk in both dimensions.
+ $hasCropped = 'nothing';
+ $overallRescale = $idealWidthScale; // $idealHeightScale IS identical.
+ } elseif ($idealCanvas->getAspectRatio() < $inputCanvas->getAspectRatio()) {
+ // The horizontal width has been cropped. Grab the height's
+ // scale, since that side is "unaffected" by the main cropping
+ // and should therefore have a scale of 1. Although it may have
+ // had up/down-scaling. In that case, the height scale will
+ // represent the amount of overall rescale change.
+ $hasCropped = 'width';
+ $overallRescale = $idealHeightScale;
+ } else { // Output aspect is > input.
+ // The vertical height has been cropped. Just like above, the
+ // "unaffected" side is what we'll use as our scale reference.
+ $hasCropped = 'height';
+ $overallRescale = $idealWidthScale;
+ }
+ $this->_debugText(
+ 'CROP: Detecting Cropped Direction',
+ 'cropped=%s, overallRescale=%.8f',
+ $hasCropped, $overallRescale
+ );
+
+ // Alright, now calculate the dimensions of the "IDEALLY CROPPED
+ // INPUT canvas", at INPUT canvas scale. These are the scenarios:
+ //
+ // - "hasCropped: nothing, scale is 1.0" = Nothing was cropped, and
+ // nothing was scaled. Treat as "use whole INPUT canvas". This is
+ // pixel-perfect.
+ //
+ // - "hasCropped: nothing, scale NOT 1.0" = Nothing was cropped, but
+ // the whole canvas was up/down-scaled. We don't have to care at
+ // all about that scaling and should treat it as "use whole INPUT
+ // canvas" for crop calculation purposes. The cropped result will
+ // later be scaled/stretched to the canvas size (up or down).
+ //
+ // - "hasCropped: width/height, scale is 1.0" = A single side was
+ // cropped, and nothing was scaled. Treat as "use IDEALLY CROPPED
+ // canvas". This is pixel-perfect.
+ //
+ // - "hasCropped: width/height, scale NOT 1.0" = A single side was
+ // cropped, and then the whole canvas was up/down-scaled. Treat as
+ // "use scale-fixed version of IDEALLY CROPPED canvas". The
+ // cropped result will later be scaled/stretched to the canvas
+ // size (up or down).
+ //
+ // There's an easy way to handle ALL of those scenarios: Just
+ // translate the IDEALLY CROPPED canvas back into INPUT-SCALED
+ // dimensions. Then we'll get a pixel-perfect "input crop" whenever
+ // scale is 1.0, since a scale of 1.0 gives the same result back.
+ // And we'll get a properly re-scaled result in all other cases.
+ //
+ // NOTE: This result CAN deviate from what was "actually cropped"
+ // during the main algorithm. That is TOTALLY INTENTIONAL AND IS THE
+ // INTENDED, PERFECT BEHAVIOR! Do NOT change this code! By always
+ // re-calculating here, we'll actually FIX rounding errors caused by
+ // the main algorithm's multiple steps, and will create better
+ // looking rescaling, and pixel-perfect unscaled croppings and
+ // pixel-perfect unscaled Mod2 adjustments!
+
+ // First calculate the overall IDEAL cropping applied to the INPUT
+ // canvas. If scale is 1.0 it will be used as-is (pixel-perfect).
+ // NOTE: We tell it to use round() so that the rescaled pixels are
+ // as close to the perfect aspect ratio as possible.
+ $croppedInputCanvas = $idealCanvas->withRescaling(1 / $overallRescale, 'round');
+ $this->_debugDimensions(
+ $croppedInputCanvas->getWidth(), $croppedInputCanvas->getHeight(),
+ 'CROP: Rescaled Ideally Cropped Canvas to Input Dimension Space'
+ );
+
+ // Now re-scale the Mod2 adjustments to the INPUT canvas coordinate
+ // space too. If scale is 1.0 they'll be used as-is (pixel-perfect).
+ // If the scale is up/down, they'll be rounded to the next whole
+ // number. The rounding is INTENTIONAL, because if scaling was used
+ // for the IDEAL canvas then it DOESN'T MATTER how many exact pixels
+ // we crop, but round() gives us the BEST APPROXIMATION!
+ $rescaledMod2WidthDiff = (int) round($canvasInfo['mod2WidthDiff'] * (1 / $overallRescale));
+ $rescaledMod2HeightDiff = (int) round($canvasInfo['mod2HeightDiff'] * (1 / $overallRescale));
+ $this->_debugText(
+ 'CROP: Rescaled Mod2 Adjustments to Input Dimension Space',
+ 'width=%s, height=%s, widthRescaled=%s, heightRescaled=%s',
+ $canvasInfo['mod2WidthDiff'], $canvasInfo['mod2HeightDiff'],
+ $rescaledMod2WidthDiff, $rescaledMod2HeightDiff
+ );
+
+ // Apply the Mod2 adjustments to the input cropping that we'll
+ // perform. This ensures that ALL of the Mod2 croppings (in ANY
+ // dimension) will always be pixel-perfect when we're at scale 1.0!
+ $croppedInputCanvas = new Dimensions($croppedInputCanvas->getWidth() + $rescaledMod2WidthDiff,
+ $croppedInputCanvas->getHeight() + $rescaledMod2HeightDiff);
+ $this->_debugDimensions(
+ $croppedInputCanvas->getWidth(), $croppedInputCanvas->getHeight(),
+ 'CROP: Applied Mod2 Adjustments to Final Cropped Input Canvas'
+ );
+
+ // The "CROPPED INPUT canvas" is in the same dimensions/coordinate
+ // space as the "INPUT canvas". So ensure all dimensions are valid
+ // (don't exceed INPUT) and create the final "CROPPED INPUT canvas".
+ // NOTE: This is it... if the media is at scale 1.0, we now have a
+ // pixel-perfect, cropped canvas with ALL of the cropping and Mod2
+ // adjustments applied to it! And if we're at another scale, we have
+ // a perfectly recalculated, cropped canvas which took into account
+ // cropping, scaling and Mod2 adjustments. Advanced stuff! :-)
+ $croppedInputCanvasWidth = $croppedInputCanvas->getWidth() <= $inputCanvas->getWidth()
+ ? $croppedInputCanvas->getWidth() : $inputCanvas->getWidth();
+ $croppedInputCanvasHeight = $croppedInputCanvas->getHeight() <= $inputCanvas->getHeight()
+ ? $croppedInputCanvas->getHeight() : $inputCanvas->getHeight();
+ $croppedInputCanvas = new Dimensions($croppedInputCanvasWidth, $croppedInputCanvasHeight);
+ $this->_debugDimensions(
+ $croppedInputCanvas->getWidth(), $croppedInputCanvas->getHeight(),
+ 'CROP: Clamped to Legal Input Max-Dimensions'
+ );
+
+ // Initialize the crop-shifting variables. They control the range of
+ // X/Y coordinates we'll copy from ORIGINAL INPUT to OUTPUT canvas.
+ // NOTE: This properly selects the entire INPUT media canvas area.
+ $x1 = $y1 = 0;
+ $x2 = $inputCanvas->getWidth();
+ $y2 = $inputCanvas->getHeight();
+ $this->_debugText(
+ 'CROP: Initializing X/Y Variables to Full Input Canvas Size',
+ 'x1=%s, x2=%s, y1=%s, y2=%s',
+ $x1, $x2, $y1, $y2
+ );
+
+ // Calculate the width and height diffs between the original INPUT
+ // canvas and the new CROPPED INPUT canvas. Negative values mean the
+ // output is smaller (which we'll handle by cropping), and larger
+ // values would mean the output is larger (which we'll handle by
+ // letting the OUTPUT canvas stretch the 100% uncropped original
+ // pixels of the INPUT in that direction, to fill the whole canvas).
+ // NOTE: Because of clamping of the CROPPED INPUT canvas above, this
+ // will actually never be a positive ("scale up") number. It will
+ // only be 0 or less. That's good, just be aware of it if editing!
+ $widthDiff = $croppedInputCanvas->getWidth() - $inputCanvas->getWidth();
+ $heightDiff = $croppedInputCanvas->getHeight() - $inputCanvas->getHeight();
+ $this->_debugText(
+ 'CROP: Calculated Input Canvas Crop Amounts',
+ 'width=%s px, height=%s px',
+ $widthDiff, $heightDiff
+ );
+
+ // After ALL of that work... we finally know how to crop the input
+ // canvas! Alright... handle cropping of the INPUT width and height!
+ // NOTE: The main canvas-creation algorithm only crops a single
+ // dimension (width or height), but its Mod2 adjustments may have
+ // caused BOTH to be cropped, which is why we MUST process both.
+ if ($widthDiff < 0) {
+ // Horizontal cropping. Focus on the center by default.
+ $horCropFocus = $this->_horCropFocus !== null ? $this->_horCropFocus : 0;
+ $this->_debugText('CROP: Horizontal Crop Focus', 'focus=%s', $horCropFocus);
+
+ // Invert the focus if this is horizontally flipped media.
+ if ($this->_details->isHorizontallyFlipped()) {
+ $horCropFocus = -$horCropFocus;
+ $this->_debugText(
+ 'CROP: Media is HorFlipped, Flipping Horizontal Crop Focus',
+ 'focus=%s',
+ $horCropFocus
+ );
+ }
+
+ // Calculate amount of pixels to crop and shift them as-focused.
+ // NOTE: Always use floor() to make uneven amounts lean at left.
+ $absWidthDiff = abs($widthDiff);
+ $x1 = (int) floor($absWidthDiff * (50 + $horCropFocus) / 100);
+ $x2 = $x2 - ($absWidthDiff - $x1);
+ $this->_debugText('CROP: Calculated New X Offsets', 'x1=%s, x2=%s', $x1, $x2);
+ }
+ if ($heightDiff < 0) {
+ // Vertical cropping. Focus on top by default (to keep faces).
+ $verCropFocus = $this->_verCropFocus !== null ? $this->_verCropFocus : -50;
+ $this->_debugText('CROP: Vertical Crop Focus', 'focus=%s', $verCropFocus);
+
+ // Invert the focus if this is vertically flipped media.
+ if ($this->_details->isVerticallyFlipped()) {
+ $verCropFocus = -$verCropFocus;
+ $this->_debugText(
+ 'CROP: Media is VerFlipped, Flipping Vertical Crop Focus',
+ 'focus=%s',
+ $verCropFocus
+ );
+ }
+
+ // Calculate amount of pixels to crop and shift them as-focused.
+ // NOTE: Always use floor() to make uneven amounts lean at top.
+ $absHeightDiff = abs($heightDiff);
+ $y1 = (int) floor($absHeightDiff * (50 + $verCropFocus) / 100);
+ $y2 = $y2 - ($absHeightDiff - $y1);
+ $this->_debugText('CROP: Calculated New Y Offsets', 'y1=%s, y2=%s', $y1, $y2);
+ }
+
+ // Create a source rectangle which starts at the start-offsets
+ // (x1/y1) and lasts until the width and height of the desired area.
+ $srcRect = new Rectangle($x1, $y1, $x2 - $x1, $y2 - $y1);
+ $this->_debugText(
+ 'CROP_SRC: Input Canvas Source Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $srcRect->getX1(), $srcRect->getX2(), $srcRect->getY1(), $srcRect->getY2(),
+ $srcRect->getWidth(), $srcRect->getHeight(), $srcRect->getAspectRatio()
+ );
+
+ // Create a destination rectangle which completely fills the entire
+ // output canvas from edge to edge. This ensures that any undersized
+ // or oversized input will be stretched properly in all directions.
+ //
+ // NOTE: Everything about our cropping/canvas algorithms is
+ // optimized so that stretching won't happen unless the media is so
+ // tiny that it's below the minimum width or so wide that it must be
+ // shrunk. Everything else WILL use sharp 1:1 pixels and pure
+ // cropping instead of stretching/shrinking. And when stretch/shrink
+ // is used, the aspect ratio is always perfectly maintained!
+ $dstRect = new Rectangle(0, 0, $outputCanvas->getWidth(), $outputCanvas->getHeight());
+ $this->_debugText(
+ 'CROP_DST: Output Canvas Destination Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $dstRect->getX1(), $dstRect->getX2(), $dstRect->getY1(), $dstRect->getY2(),
+ $dstRect->getWidth(), $dstRect->getHeight(), $dstRect->getAspectRatio()
+ );
+ } elseif ($this->_operation === self::EXPAND) {
+ // We'll copy the entire original input media onto the new canvas.
+ // Always copy from the absolute top left of the original media.
+ $srcRect = new Rectangle(0, 0, $inputCanvas->getWidth(), $inputCanvas->getHeight());
+ $this->_debugText(
+ 'EXPAND_SRC: Input Canvas Source Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $srcRect->getX1(), $srcRect->getX2(), $srcRect->getY1(), $srcRect->getY2(),
+ $srcRect->getWidth(), $srcRect->getHeight(), $srcRect->getAspectRatio()
+ );
+
+ // Determine the target dimensions to fit it on the new canvas,
+ // because the input media's dimensions may have been too large.
+ // This will not scale anything (uses scale=1) if the input fits.
+ $outputWidthScale = (float) ($outputCanvas->getWidth() / $inputCanvas->getWidth());
+ $outputHeightScale = (float) ($outputCanvas->getHeight() / $inputCanvas->getHeight());
+ $scale = min($outputWidthScale, $outputHeightScale);
+ $this->_debugText(
+ 'EXPAND: Calculating Scale to Fit Input on Output Canvas',
+ 'scale=%.8f',
+ $scale
+ );
+
+ // Calculate the scaled destination rectangle. Note that X/Y remain.
+ // NOTE: We tell it to use ceil(), which guarantees that it'll
+ // never scale a side badly and leave a 1px gap between the media
+ // and canvas sides. Also note that ceil will never produce bad
+ // values, since PHP allows the dst_w/dst_h to exceed beyond canvas!
+ $dstRect = $srcRect->withRescaling($scale, 'ceil');
+ $this->_debugDimensions(
+ $dstRect->getWidth(), $dstRect->getHeight(),
+ 'EXPAND: Rescaled Input to Output Dimension Space'
+ );
+
+ // Now calculate the centered destination offset on the canvas.
+ // NOTE: We use floor() to ensure that the result gets left-aligned
+ // perfectly, and prefers to lean towards towards the top as well.
+ $dst_x = (int) floor(($outputCanvas->getWidth() - $dstRect->getWidth()) / 2);
+ $dst_y = (int) floor(($outputCanvas->getHeight() - $dstRect->getHeight()) / 2);
+ $this->_debugText(
+ 'EXPAND: Calculating Centered Destination on Output Canvas',
+ 'dst_x=%s, dst_y=%s',
+ $dst_x, $dst_y
+ );
+
+ // Build the final destination rectangle for the expanded canvas!
+ $dstRect = new Rectangle($dst_x, $dst_y, $dstRect->getWidth(), $dstRect->getHeight());
+ $this->_debugText(
+ 'EXPAND_DST: Output Canvas Destination Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $dstRect->getX1(), $dstRect->getX2(), $dstRect->getY1(), $dstRect->getY2(),
+ $dstRect->getWidth(), $dstRect->getHeight(), $dstRect->getAspectRatio()
+ );
+ } else {
+ throw new \RuntimeException(sprintf('Unsupported operation: %s.', $this->_operation));
+ }
+
+ return $this->_createOutputFile($srcRect, $dstRect, $outputCanvas);
+ }
+
+ /**
+ * Create the new media file.
+ *
+ * @param Rectangle $srcRect Rectangle to copy from the input.
+ * @param Rectangle $dstRect Destination place and scale of copied pixels.
+ * @param Dimensions $canvas The size of the destination canvas.
+ *
+ * @return string The path to the output file.
+ */
+ abstract protected function _createOutputFile(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas);
+
+ /**
+ * Calculate a new canvas based on input size and requested modifications.
+ *
+ * The final canvas will be the same size as the input if everything was
+ * already okay and within the limits. Otherwise it will be a new canvas
+ * representing the _exact_, best-possible size to convert input media to.
+ *
+ * It is up to the caller to perfectly follow these orders, since deviating
+ * by even a SINGLE PIXEL can create illegal media aspect ratios.
+ *
+ * Also note that the resulting canvas can be LARGER than the input in
+ * several cases, such as in EXPAND-mode (obviously), or when the input
+ * isn't wide enough to be legal (and must be scaled up), and whenever Mod2
+ * is requested. In the latter case, the algorithm may have to add a few
+ * pixels to the height to make it valid in a few rare cases. The caller
+ * must be aware of such "enlarged" canvases and should handle them by
+ * stretching the input if necessary.
+ *
+ * @param int $operation
+ * @param int $inputWidth
+ * @param int $inputHeight
+ * @param bool $isMod2CanvasRequired
+ * @param int $minWidth
+ * @param int $maxWidth
+ * @param float|null $minAspectRatio
+ * @param float|null $maxAspectRatio
+ * @param float|null $forceTargetAspectRatio Optional forced aspect ratio
+ * target (ALWAYS applied,
+ * except if input is already
+ * EXACTLY this ratio).
+ * @param bool $allowNewAspectDeviation See constructor arg docs.
+ *
+ * @throws \RuntimeException If requested canvas couldn't be achieved, most
+ * commonly if you have chosen way too narrow
+ * aspect ratio ranges that cannot be perfectly
+ * reached by your input media, and you AREN'T
+ * running with `$allowNewAspectDeviation`.
+ *
+ * @return array An array with `canvas` (`Dimensions`), `mod2WidthDiff` and
+ * `mod2HeightDiff`. The latter are integers representing how
+ * many pixels were cropped (-) or added (+) by the Mod2 step
+ * compared to the ideal canvas.
+ */
+ protected function _calculateNewCanvas(
+ $operation,
+ $inputWidth,
+ $inputHeight,
+ $isMod2CanvasRequired,
+ $minWidth = 1,
+ $maxWidth = 99999,
+ $minAspectRatio = null,
+ $maxAspectRatio = null,
+ $forceTargetAspectRatio = null,
+ $allowNewAspectDeviation = false)
+ {
+ /*
+ * WARNING TO POTENTIAL CONTRIBUTORS:
+ *
+ * THIS right here is the MOST COMPLEX algorithm in the whole project.
+ * Everything is finely tuned to create 100% accurate, pixel-perfect
+ * resizes. A SINGLE PIXEL ERROR in your calculations WILL lead to it
+ * sometimes outputting illegally formatted files that will be rejected
+ * by Instagram. We know this, because we have SEEN IT HAPPEN while we
+ * tweaked and tweaked and tweaked to balance everything perfectly!
+ *
+ * Unfortunately, this file also seems to attract a lot of beginners.
+ * Maybe because a "media processor" seems "fun and easy". But that
+ * would be an incorrect guess. It's the most serious algorithm in the
+ * whole project. If you break it, *YOU* break people's uploads.
+ *
+ * We have had many random, new contributors just jumping in and adding
+ * zero-effort code everywhere in here, and breaking the whole balance,
+ * and then opening pull requests. We have rejected EVERY single one of
+ * those pull requests because they were totally unusable and unsafe.
+ *
+ * We will not accept such pull requests. Ever.
+ *
+ * This warning is here to save your time, and ours.
+ *
+ * If you are interested in helping out with the media algorithms, then
+ * that's GREAT! But in that case we require that you fully read through
+ * the algorithms below and all of its comments about 50 times over a
+ * 3-4 day period - until you understand every single step perfectly.
+ * The comments will help make it clearer the more you read...
+ *
+ * ...and make an effort.
+ *
+ * Then you are ready... and welcome to the team. :-)
+ *
+ * Thank you.
+ */
+
+ if ($forceTargetAspectRatio !== null) {
+ $this->_debugText('SPECIAL_PARAMETERS: Forced Target Aspect Ratio', 'forceTargetAspectRatio=%.5f', $forceTargetAspectRatio);
+ }
+
+ // Initialize target canvas to original input dimensions & aspect ratio.
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $targetWidth = (int) $inputWidth;
+ $targetHeight = (int) $inputHeight;
+ $targetAspectRatio = (float) ($inputWidth / $inputHeight);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_INPUT: Input Canvas Size');
+
+ // Check aspect ratio and crop/expand the canvas to fit aspect if needed.
+ if (
+ ($minAspectRatio !== null && $targetAspectRatio < $minAspectRatio)
+ || ($forceTargetAspectRatio !== null && $targetAspectRatio < $forceTargetAspectRatio)
+ ) {
+ // Determine target ratio; uses forced aspect ratio if set,
+ // otherwise we target the MINIMUM allowed ratio (since we're < it)).
+ $targetAspectRatio = $forceTargetAspectRatio !== null ? $forceTargetAspectRatio : $minAspectRatio;
+
+ if ($operation === self::CROP) {
+ // We need to limit the height, so floor is used intentionally to
+ // AVOID rounding height upwards to a still-too-low aspect ratio.
+ $targetHeight = (int) floor($targetWidth / $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_CROPPED: %s', $forceTargetAspectRatio === null ? 'Aspect Was < MIN' : 'Applying Forced Aspect for INPUT < TARGET'));
+ } elseif ($operation === self::EXPAND) {
+ // We need to expand the width with left/right borders. We use
+ // ceil to guarantee that the final media is wide enough to be
+ // above the minimum allowed aspect ratio.
+ $targetWidth = (int) ceil($targetHeight * $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_EXPANDED: %s', $forceTargetAspectRatio === null ? 'Aspect Was < MIN' : 'Applying Forced Aspect for INPUT < TARGET'));
+ }
+ } elseif (
+ ($maxAspectRatio !== null && $targetAspectRatio > $maxAspectRatio)
+ || ($forceTargetAspectRatio !== null && $targetAspectRatio > $forceTargetAspectRatio)
+ ) {
+ // Determine target ratio; uses forced aspect ratio if set,
+ // otherwise we target the MAXIMUM allowed ratio (since we're > it)).
+ $targetAspectRatio = $forceTargetAspectRatio !== null ? $forceTargetAspectRatio : $maxAspectRatio;
+
+ if ($operation === self::CROP) {
+ // We need to limit the width. We use floor to guarantee cutting
+ // enough pixels, since our width exceeds the maximum allowed ratio.
+ $targetWidth = (int) floor($targetHeight * $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_CROPPED: %s', $forceTargetAspectRatio === null ? 'Aspect Was > MAX' : 'Applying Forced Aspect for INPUT > TARGET'));
+ } elseif ($operation === self::EXPAND) {
+ // We need to expand the height with top/bottom borders. We use
+ // ceil to guarantee that the final media is tall enough to be
+ // below the maximum allowed aspect ratio.
+ $targetHeight = (int) ceil($targetWidth / $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_EXPANDED: %s', $forceTargetAspectRatio === null ? 'Aspect Was > MAX' : 'Applying Forced Aspect for INPUT > TARGET'));
+ }
+ } else {
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS: Aspect Ratio Already Legal');
+ }
+
+ // Determine whether the final target ratio is closest to either the
+ // legal MINIMUM or the legal MAXIMUM aspect ratio limits.
+ // NOTE: The target ratio will actually still be set to the original
+ // input media's ratio in case of no aspect ratio adjustments above.
+ // NOTE: If min and/or max ratios were not provided, we default min to
+ // `0` and max to `9999999` to ensure that we properly detect the "least
+ // distance" direction even when only one (or neither) of the two "range
+ // limit values" were provided.
+ $minAspectDistance = abs(($minAspectRatio !== null
+ ? $minAspectRatio : 0) - $targetAspectRatio);
+ $maxAspectDistance = abs(($maxAspectRatio !== null
+ ? $maxAspectRatio : 9999999) - $targetAspectRatio);
+ $isClosestToMinAspect = ($minAspectDistance <= $maxAspectDistance);
+
+ // We MUST now set up the correct height re-calculation behavior for the
+ // later algorithm steps. This is used whenever our canvas needs to be
+ // re-scaled by any other code below. If our chosen, final target ratio
+ // is closest to the minimum allowed legal ratio, we'll always use
+ // floor() on the height to ensure that the height value becomes as low
+ // as possible (since having LESS height compared to width is what
+ // causes the aspect ratio value to grow), to ensure that the final
+ // result's ratio (after any additional adjustments) will ALWAYS be
+ // ABOVE the minimum legal ratio (minAspectRatio). Otherwise we'll
+ // instead use ceil() on the height (since having more height causes the
+ // aspect ratio value to shrink), to ensure that the result is always
+ // BELOW the maximum ratio (maxAspectRatio).
+ $useFloorHeightRecalc = $isClosestToMinAspect;
+
+ // Verify square target ratios by ensuring canvas is now a square.
+ // NOTE: This is just a sanity check against wrong code above. It will
+ // never execute, since all code above took care of making both
+ // dimensions identical already (if they differed in any way, they had a
+ // non-1 ratio and invoked the aspect ratio cropping/expansion code). It
+ // then made identical thanks to the fact that X / 1 = X, and X * 1 = X.
+ // NOTE: It's worth noting that our squares are always the size of the
+ // shortest side when cropping or the longest side when expanding.
+ // WARNING: Comparison MUST use `==` (NOT strict `===`) to support both
+ // int(1) and float(1.0) values!
+ if ($targetAspectRatio == 1.0 && $targetWidth !== $targetHeight) { // Ratio 1 = Square.
+ $targetWidth = $targetHeight = $operation === self::CROP
+ ? min($targetWidth, $targetHeight)
+ : max($targetWidth, $targetHeight);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_SQUARIFY: Fixed Badly Generated Square');
+ }
+
+ // Lastly, enforce minimum and maximum width limits on our final canvas.
+ // NOTE: Instagram only enforces width & aspect ratio, which in turn
+ // auto-limits height (since we can only use legal height ratios).
+ // NOTE: Yet again, if the target ratio is 1 (square), we'll get
+ // identical width & height, so NO NEED to MANUALLY "fix square" here.
+ if ($targetWidth > $maxWidth) {
+ $targetWidth = $maxWidth;
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Width Was > MAX');
+ $targetHeight = $this->_accurateHeightRecalc($useFloorHeightRecalc, $targetAspectRatio, $targetWidth);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Height Recalc From Width & Aspect');
+ } elseif ($targetWidth < $minWidth) {
+ $targetWidth = $minWidth;
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Width Was < MIN');
+ $targetHeight = $this->_accurateHeightRecalc($useFloorHeightRecalc, $targetAspectRatio, $targetWidth);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Height Recalc From Width & Aspect');
+ }
+
+ // All of the main canvas algorithms are now finished, and we are now
+ // able to check Mod2 compatibility and accurately readjust if needed.
+ $mod2WidthDiff = $mod2HeightDiff = 0;
+ if ($isMod2CanvasRequired
+ && (!$this->_isNumberMod2($targetWidth) || !$this->_isNumberMod2($targetHeight))
+ ) {
+ // Calculate the Mod2-adjusted final canvas size.
+ $mod2Canvas = $this->_calculateAdjustedMod2Canvas(
+ $inputWidth,
+ $inputHeight,
+ $useFloorHeightRecalc,
+ $targetWidth,
+ $targetHeight,
+ $targetAspectRatio,
+ $minWidth,
+ $maxWidth,
+ $minAspectRatio,
+ $maxAspectRatio,
+ $allowNewAspectDeviation
+ );
+
+ // Determine the pixel difference before and after processing.
+ $mod2WidthDiff = $mod2Canvas->getWidth() - $targetWidth;
+ $mod2HeightDiff = $mod2Canvas->getHeight() - $targetHeight;
+ $this->_debugText('CANVAS: Mod2 Difference Stats', 'width=%s, height=%s', $mod2WidthDiff, $mod2HeightDiff);
+
+ // Update the final canvas to the Mod2-adjusted canvas size.
+ // NOTE: If code above failed, the new values are invalid. But so
+ // could our original values have been. We check that further down.
+ $targetWidth = $mod2Canvas->getWidth();
+ $targetHeight = $mod2Canvas->getHeight();
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS: Updated From Mod2 Result');
+ }
+
+ // Create the new canvas Dimensions object.
+ $canvas = new Dimensions($targetWidth, $targetHeight);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_OUTPUT: Final Output Canvas Size');
+
+ // We must now validate the canvas before returning it.
+ // NOTE: Most of these are just strict sanity-checks to protect against
+ // bad code contributions in the future. The canvas won't be able to
+ // pass all of these checks unless the algorithm above remains perfect.
+ $isIllegalRatio = (($minAspectRatio !== null && $canvas->getAspectRatio() < $minAspectRatio)
+ || ($maxAspectRatio !== null && $canvas->getAspectRatio() > $maxAspectRatio));
+ if ($canvas->getWidth() < 1 || $canvas->getHeight() < 1) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Target width (%s) or height (%s) less than one pixel.',
+ $canvas->getWidth(), $canvas->getHeight()
+ ));
+ } elseif ($canvas->getWidth() < $minWidth) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Target width (%s) less than minimum allowed (%s).',
+ $canvas->getWidth(), $minWidth
+ ));
+ } elseif ($canvas->getWidth() > $maxWidth) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Target width (%s) greater than maximum allowed (%s).',
+ $canvas->getWidth(), $maxWidth
+ ));
+ } elseif ($isIllegalRatio) {
+ if (!$allowNewAspectDeviation) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Unable to reach target aspect ratio range during output canvas generation. The range of allowed aspect ratios is too narrow (%.8f - %.8f). We achieved a ratio of %.8f.',
+ $minAspectRatio !== null ? $minAspectRatio : 0.0,
+ $maxAspectRatio !== null ? $maxAspectRatio : INF,
+ $canvas->getAspectRatio()
+ ));
+ } else {
+ // The user wants us to allow "near-misses", so we proceed...
+ $this->_debugDimensions($canvas->getWidth(), $canvas->getHeight(), 'CANVAS_FINAL: Allowing Deviating Aspect Ratio');
+ }
+ }
+
+ return [
+ 'canvas' => $canvas,
+ 'mod2WidthDiff' => $mod2WidthDiff,
+ 'mod2HeightDiff' => $mod2HeightDiff,
+ ];
+ }
+
+ /**
+ * Calculates a new relative height using the target aspect ratio.
+ *
+ * Used internally by `_calculateNewCanvas()`.
+ *
+ * This algorithm aims at the highest-possible or lowest-possible resulting
+ * aspect ratio based on what's needed. It uses either `floor()` or `ceil()`
+ * depending on whether we need the resulting aspect ratio to be >= or <=
+ * the target aspect ratio.
+ *
+ * The principle behind this is the fact that removing height (via floor)
+ * will give us a higher aspect ratio. And adding height (via ceil) will
+ * give us a lower aspect ratio.
+ *
+ * If the target aspect ratio is square (1), height becomes equal to width.
+ *
+ * @param bool $useFloorHeightRecalc
+ * @param float $targetAspectRatio
+ * @param int $targetWidth
+ *
+ * @return int
+ */
+ protected function _accurateHeightRecalc(
+ $useFloorHeightRecalc,
+ $targetAspectRatio,
+ $targetWidth)
+ {
+ // Read the docs above to understand this CRITICALLY IMPORTANT code.
+ $targetHeight = $useFloorHeightRecalc
+ ? (int) floor($targetWidth / $targetAspectRatio) // >=
+ : (int) ceil($targetWidth / $targetAspectRatio); // <=
+
+ return $targetHeight;
+ }
+
+ /**
+ * Adjusts dimensions to create a Mod2-compatible canvas.
+ *
+ * Used internally by `_calculateNewCanvas()`.
+ *
+ * The reason why this function also takes the original input width/height
+ * is because it tries to maximize its usage of the available original pixel
+ * surface area while correcting the dimensions. It uses the extra
+ * information to know when it's safely able to grow the canvas beyond the
+ * given target width/height parameter values.
+ *
+ * @param int $inputWidth
+ * @param int $inputHeight
+ * @param bool $useFloorHeightRecalc
+ * @param int $targetWidth
+ * @param int $targetHeight
+ * @param float $targetAspectRatio
+ * @param int $minWidth
+ * @param int $maxWidth
+ * @param float|null $minAspectRatio
+ * @param float|null $maxAspectRatio
+ * @param bool $allowNewAspectDeviation See constructor arg docs.
+ *
+ * @throws \RuntimeException If requested canvas couldn't be achieved, most
+ * commonly if you have chosen way too narrow
+ * aspect ratio ranges that cannot be perfectly
+ * reached by your input media, and you AREN'T
+ * running with `$allowNewAspectDeviation`.
+ *
+ * @return Dimensions
+ *
+ * @see InstagramMedia::_calculateNewCanvas()
+ */
+ protected function _calculateAdjustedMod2Canvas(
+ $inputWidth,
+ $inputHeight,
+ $useFloorHeightRecalc,
+ $targetWidth,
+ $targetHeight,
+ $targetAspectRatio,
+ $minWidth = 1,
+ $maxWidth = 99999,
+ $minAspectRatio = null,
+ $maxAspectRatio = null,
+ $allowNewAspectDeviation = false)
+ {
+ // Initialize to the calculated canvas size.
+ $mod2Width = $targetWidth;
+ $mod2Height = $targetHeight;
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Current Canvas Size');
+
+ // Determine if we're able to cut an extra pixel from the width if
+ // necessary, or if cutting would take us below the minimum width.
+ $canCutWidth = $mod2Width > $minWidth;
+
+ // To begin, we must correct the width if it's uneven. We'll only do
+ // this once, and then we'll leave the width at its new number. By
+ // keeping it static, we don't risk going over its min/max width
+ // limits. And by only varying one dimension (height) if multiple Mod2
+ // offset adjustments are needed, then we'll properly get a steadily
+ // increasing/decreasing aspect ratio (moving towards the target ratio).
+ if (!$this->_isNumberMod2($mod2Width)) {
+ // Always prefer cutting an extra pixel, rather than stretching
+ // by +1. But use +1 if cutting would take us below minimum width.
+ // NOTE: Another IMPORTANT reason to CUT width rather than extend
+ // is because in narrow cases (canvas close to original input size),
+ // the extra width proportionally increases total area (thus height
+ // too), and gives us less of the original pixels on the height-axis
+ // to play with when attempting to fix the height (and its ratio).
+ $mod2Width += ($canCutWidth ? -1 : 1);
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Width Mod2Fix');
+
+ // Calculate the new relative height based on the new width.
+ $mod2Height = $this->_accurateHeightRecalc($useFloorHeightRecalc, $targetAspectRatio, $mod2Width);
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Height Recalc From Width & Aspect');
+ }
+
+ // Ensure that the calculated height is also Mod2, but totally ignore
+ // the aspect ratio at this moment (we'll fix that later). Instead,
+ // we'll use the same pattern we'd use for width above. That way, if
+ // both width and height were uneven, they both get adjusted equally.
+ if (!$this->_isNumberMod2($mod2Height)) {
+ $mod2Height += ($canCutWidth ? -1 : 1);
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Height Mod2Fix');
+ }
+
+ // We will now analyze multiple different height alternatives to find
+ // which one gives us the best visual quality. This algorithm looks
+ // for the best qualities (with the most pixel area) first. It first
+ // tries the current height (offset 0, which is the closest to the
+ // pre-Mod2 adjusted canvas), then +2 pixels (gives more pixel area if
+ // this is possible), then -2 pixels (cuts but may be our only choice).
+ // After that, it checks 4, -4, 6 and -6 as well.
+ // NOTE: Every increased offset (+/-2, then +/-4, then +/- 6) USUALLY
+ // (but not always) causes more and more deviation from the intended
+ // cropping aspect ratio. So don't add any more steps after 6, since
+ // NOTHING will be THAT far off! Six was chosen as a good balance.
+ // NOTE: Every offset is checked for visual stretching and aspect ratio,
+ // and then rated into one of 3 categories: "perfect" (legal aspect
+ // ratio, no stretching), "stretch" (legal aspect ratio, but stretches),
+ // or "bad" (illegal aspect ratio).
+ $heightAlternatives = ['perfect' => [], 'stretch' => [], 'bad' => []];
+ static $offsetPriorities = [0, 2, -2, 4, -4, 6, -6];
+ foreach ($offsetPriorities as $offset) {
+ // Calculate the new height and its resulting aspect ratio.
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $offsetMod2Height = $mod2Height + $offset;
+ $offsetMod2AspectRatio = (float) ($mod2Width / $offsetMod2Height);
+
+ // Check if the aspect ratio is legal.
+ $isLegalRatio = (($minAspectRatio === null || $offsetMod2AspectRatio >= $minAspectRatio)
+ && ($maxAspectRatio === null || $offsetMod2AspectRatio <= $maxAspectRatio));
+
+ // Detect whether the height would need stretching. Stretching is
+ // defined as "not enough pixels in the input media to reach".
+ // NOTE: If the input media has been upscaled (such as a 64x64 image
+ // being turned into 320x320), then we will ALWAYS detect that media
+ // as needing stretching. That's intentional and correct, because
+ // such media will INDEED need stretching, so there's never going to
+ // be a perfect rating for it (where aspect ratio is legal AND zero
+ // stretching is needed to reach those dimensions).
+ // NOTE: The max() gets rid of negative values (cropping).
+ $stretchAmount = max(0, $offsetMod2Height - $inputHeight);
+
+ // Calculate the deviation from the target aspect ratio. The larger
+ // this number is, the further away from "the ideal canvas". The
+ // "perfect" answers will always deviate by different amount, and
+ // the most perfect one is the one with least deviation.
+ $ratioDeviation = abs($offsetMod2AspectRatio - $targetAspectRatio);
+
+ // Rate this height alternative and store it according to rating.
+ $rating = ($isLegalRatio && !$stretchAmount ? 'perfect' : ($isLegalRatio ? 'stretch' : 'bad'));
+ $heightAlternatives[$rating][] = [
+ 'offset' => $offset,
+ 'height' => $offsetMod2Height,
+ 'ratio' => $offsetMod2AspectRatio,
+ 'isLegalRatio' => $isLegalRatio,
+ 'stretchAmount' => $stretchAmount,
+ 'ratioDeviation' => $ratioDeviation,
+ 'rating' => $rating,
+ ];
+ $this->_debugDimensions($mod2Width, $offsetMod2Height, sprintf(
+ 'MOD2_CANVAS_CHECK: Testing Height Mod2Ratio (h%s%s = %s)',
+ ($offset >= 0 ? '+' : ''), $offset, $rating)
+ );
+ }
+
+ // Now pick the BEST height from our available choices (if any). We will
+ // pick the LEGAL height that has the LEAST amount of deviation from the
+ // ideal aspect ratio. In other words, the BEST-LOOKING aspect ratio!
+ // NOTE: If we find no legal (perfect or stretch) choices, we'll pick
+ // the most accurate (least deviation from ratio) of the bad choices.
+ $bestHeight = null;
+ foreach (['perfect', 'stretch', 'bad'] as $rating) {
+ if (!empty($heightAlternatives[$rating])) {
+ // Sort all alternatives by their amount of ratio deviation.
+ usort($heightAlternatives[$rating], function ($a, $b) {
+ return ($a['ratioDeviation'] < $b['ratioDeviation'])
+ ? -1 : (($a['ratioDeviation'] > $b['ratioDeviation']) ? 1 : 0);
+ });
+
+ // Pick the 1st array element, which has the least deviation!
+ $bestHeight = $heightAlternatives[$rating][0];
+ break;
+ }
+ }
+
+ // Process and apply the best-possible height we found.
+ $mod2Height = $bestHeight['height'];
+ $this->_debugDimensions($mod2Width, $mod2Height, sprintf(
+ 'MOD2_CANVAS: Selected Most Ideal Height Mod2Ratio (h%s%s = %s)',
+ ($bestHeight['offset'] >= 0 ? '+' : ''), $bestHeight['offset'], $bestHeight['rating']
+ ));
+
+ // Decide what to do if there were no legal aspect ratios among our
+ // calculated choices. This can happen if the user gave us an insanely
+ // narrow range (such as "min/max ratio 1.6578" or whatever).
+ if ($bestHeight['rating'] === 'bad') {
+ if (!$allowNewAspectDeviation) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Unable to reach target aspect ratio range during Mod2 canvas conversion. The range of allowed aspect ratios is too narrow (%.8f - %.8f). We achieved a ratio of %.8f.',
+ $minAspectRatio !== null ? $minAspectRatio : 0.0,
+ $maxAspectRatio !== null ? $maxAspectRatio : INF,
+ (float) ($mod2Width / $mod2Height)
+ ));
+ } else {
+ // They WANT us to allow "near-misses", so we'll KEEP our best
+ // possible bad ratio here (the one that was closest to the
+ // target). We didn't find any more ideal aspect ratio (since
+ // all other attempts ALSO FAILED the aspect ratio ranges), so
+ // we have NO idea if they'd prefer any others! ;-)
+ $this->_debugDimensions($mod2Width, $mod2Height, sprintf(
+ 'MOD2_CANVAS: Allowing Deviating Height Mod2Ratio (h%s%s = %s)',
+ ($bestHeight['offset'] >= 0 ? '+' : ''), $bestHeight['offset'], $bestHeight['rating']
+ ));
+ }
+ }
+
+ return new Dimensions($mod2Width, $mod2Height);
+ }
+
+ /**
+ * Checks whether a number is Mod2.
+ *
+ * @param int|float $number
+ *
+ * @return bool
+ */
+ protected function _isNumberMod2(
+ $number)
+ {
+ // NOTE: The modulo operator correctly returns ints even for float input such as 1.999.
+ return $number % 2 === 0;
+ }
+
+ /**
+ * Output debug text.
+ *
+ * @param string $stepDescription
+ * @param string $formatMessage
+ * @param mixed $args,...
+ */
+ protected function _debugText(
+ $stepDescription,
+ $formatMessage,
+ ...$args)
+ {
+ if (!$this->_debug) {
+ return;
+ }
+
+ printf(
+ "[\033[1;33m%s\033[0m] {$formatMessage}\n",
+ $stepDescription,
+ ...$args
+ );
+ }
+
+ /**
+ * Debug current calculation dimensions and their ratio.
+ *
+ * @param int|float $width
+ * @param int|float $height
+ * @param string|null $stepDescription
+ */
+ protected function _debugDimensions(
+ $width,
+ $height,
+ $stepDescription = null)
+ {
+ if (!$this->_debug) {
+ return;
+ }
+
+ printf(
+ // NOTE: This uses 8 decimals for proper debugging, since small
+ // rounding errors can make rejected ratios look valid.
+ "[\033[1;33m%s\033[0m] w=%s h=%s (aspect %.8f)\n",
+ $stepDescription !== null ? $stepDescription : 'DEBUG',
+ $width, $height, (float) ($width / $height)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Media/MediaDetails.php b/instafeed/vendor/mgp25/instagram-php/src/Media/MediaDetails.php
new file mode 100755
index 0000000..f7516de
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Media/MediaDetails.php
@@ -0,0 +1,172 @@
+hasSwappedAxes() ? $this->_height : $this->_width;
+ }
+
+ /**
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->hasSwappedAxes() ? $this->_width : $this->_height;
+ }
+
+ /**
+ * @return float
+ */
+ public function getAspectRatio()
+ {
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ return (float) ($this->getWidth() / $this->getHeight());
+ }
+
+ /**
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->_filename;
+ }
+
+ /**
+ * @return int
+ */
+ public function getFilesize()
+ {
+ return $this->_filesize;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBasename()
+ {
+ // Fix full path disclosure.
+ return basename($this->_filename);
+ }
+
+ /**
+ * Get the minimum allowed media width for this media type.
+ *
+ * @return int
+ */
+ abstract public function getMinAllowedWidth();
+
+ /**
+ * Get the maximum allowed media width for this media type.
+ *
+ * @return int
+ */
+ abstract public function getMaxAllowedWidth();
+
+ /**
+ * Check whether the media has swapped axes.
+ *
+ * @return bool
+ */
+ abstract public function hasSwappedAxes();
+
+ /**
+ * Check whether the media is horizontally flipped.
+ *
+ * ```
+ * ***** *****
+ * * *
+ * *** => ***
+ * * *
+ * * *
+ * ```
+ *
+ * @return bool
+ */
+ abstract public function isHorizontallyFlipped();
+
+ /**
+ * Check whether the media is vertically flipped.
+ *
+ * ```
+ * ***** *
+ * * *
+ * *** => ***
+ * * *
+ * * *****
+ * ```
+ *
+ * @return bool
+ */
+ abstract public function isVerticallyFlipped();
+
+ /**
+ * Constructor.
+ *
+ * @param string $filename
+ * @param int $filesize
+ * @param int $width
+ * @param int $height
+ */
+ public function __construct(
+ $filename,
+ $filesize,
+ $width,
+ $height)
+ {
+ $this->_filename = $filename;
+ $this->_filesize = $filesize;
+ $this->_width = $width;
+ $this->_height = $height;
+ }
+
+ /**
+ * Verifies that a piece of media follows Instagram's rules.
+ *
+ * @param ConstraintsInterface $constraints
+ *
+ * @throws \InvalidArgumentException If Instagram won't allow this file.
+ */
+ public function validate(
+ ConstraintsInterface $constraints)
+ {
+ $mediaFilename = $this->getBasename();
+
+ // Check rotation.
+ if ($this->hasSwappedAxes() || $this->isVerticallyFlipped() || $this->isHorizontallyFlipped()) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts non-rotated media. Your file "%s" is either rotated or flipped or both.',
+ $mediaFilename
+ ));
+ }
+
+ // Check Aspect Ratio.
+ // NOTE: This Instagram rule is the same for both videos and photos.
+ $aspectRatio = $this->getAspectRatio();
+ $minAspectRatio = $constraints->getMinAspectRatio();
+ $maxAspectRatio = $constraints->getMaxAspectRatio();
+ if ($aspectRatio < $minAspectRatio || $aspectRatio > $maxAspectRatio) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts %s media with aspect ratios between %.3f and %.3f. Your file "%s" has a %.4f aspect ratio.',
+ $constraints->getTitle(), $minAspectRatio, $maxAspectRatio, $mediaFilename, $aspectRatio
+ ));
+ }
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Media/Photo/InstagramPhoto.php b/instafeed/vendor/mgp25/instagram-php/src/Media/Photo/InstagramPhoto.php
new file mode 100755
index 0000000..57b202d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Media/Photo/InstagramPhoto.php
@@ -0,0 +1,326 @@
+_details = new PhotoDetails($this->_inputFile);
+ }
+
+ /** {@inheritdoc} */
+ protected function _isMod2CanvasRequired()
+ {
+ return false;
+ }
+
+ /** {@inheritdoc} */
+ protected function _createOutputFile(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas)
+ {
+ $outputFile = null;
+
+ try {
+ // Attempt to process the input file.
+ $resource = $this->_loadImage();
+
+ try {
+ $output = $this->_processResource($resource, $srcRect, $dstRect, $canvas);
+ } finally {
+ @imagedestroy($resource);
+ }
+
+ // Write the result to disk.
+ try {
+ // Prepare output file.
+ $outputFile = Utils::createTempFile($this->_tmpPath, 'IMG');
+
+ if (!imagejpeg($output, $outputFile, self::JPEG_QUALITY)) {
+ throw new \RuntimeException('Failed to create JPEG image file.');
+ }
+ } finally {
+ @imagedestroy($output);
+ }
+ } catch (\Exception $e) {
+ if ($outputFile !== null && is_file($outputFile)) {
+ @unlink($outputFile);
+ }
+
+ throw $e; // Re-throw.
+ }
+
+ return $outputFile;
+ }
+
+ /**
+ * Loads image into a resource.
+ *
+ * @throws \RuntimeException
+ *
+ * @return resource
+ */
+ protected function _loadImage()
+ {
+ // Read the correct input file format.
+ switch ($this->_details->getType()) {
+ case IMAGETYPE_JPEG:
+ $resource = imagecreatefromjpeg($this->_inputFile);
+ break;
+ case IMAGETYPE_PNG:
+ $resource = imagecreatefrompng($this->_inputFile);
+ break;
+ case IMAGETYPE_GIF:
+ $resource = imagecreatefromgif($this->_inputFile);
+ break;
+ default:
+ throw new \RuntimeException('Unsupported image type.');
+ }
+ if ($resource === false) {
+ throw new \RuntimeException('Failed to load image.');
+ }
+
+ return $resource;
+ }
+
+ /**
+ * @param resource $source The original image loaded as a resource.
+ * @param Rectangle $srcRect Rectangle to copy from the input.
+ * @param Rectangle $dstRect Destination place and scale of copied pixels.
+ * @param Dimensions $canvas The size of the destination canvas.
+ *
+ * @throws \Exception
+ * @throws \RuntimeException
+ *
+ * @return resource
+ */
+ protected function _processResource(
+ $source,
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas
+ ) {
+ // If our input image pixels are stored rotated, swap all coordinates.
+ if ($this->_details->hasSwappedAxes()) {
+ $srcRect = $srcRect->withSwappedAxes();
+ $dstRect = $dstRect->withSwappedAxes();
+ $canvas = $canvas->withSwappedAxes();
+ }
+
+ // Create an output canvas with our desired size.
+ $output = imagecreatetruecolor($canvas->getWidth(), $canvas->getHeight());
+ if ($output === false) {
+ throw new \RuntimeException('Failed to create output image.');
+ }
+
+ // Fill the output canvas with our background color.
+ // NOTE: If cropping, this is just to have a nice background in
+ // the resulting JPG if a transparent image was used as input.
+ // If expanding, this will be the color of the border as well.
+ $bgColor = imagecolorallocate($output, $this->_bgColor[0], $this->_bgColor[1], $this->_bgColor[2]);
+ if ($bgColor === false) {
+ throw new \RuntimeException('Failed to allocate background color.');
+ }
+
+ // If expanding and blurredBorder, use the photo as a blurred border.
+ if ($this->_blurredBorder && $this->_operation === self::EXPAND) {
+ // Calculate the rectangle
+ $blurImageRect = $this->_calculateBlurImage($srcRect, $canvas);
+ $scaleDownRect = $blurImageRect->withRescaling(self::BLUR_IMAGE_SCALE, 'ceil');
+
+ // Create a canvas for the scaled image
+ $scaledDownImage = imagecreatetruecolor($scaleDownRect->getWidth(), $scaleDownRect->getHeight());
+ if ($scaledDownImage === false) {
+ throw new \RuntimeException('Failed to create scaled down image.');
+ }
+
+ // Copy the image to the scaled canvas
+ if (imagecopyresampled(
+ $scaledDownImage, $source,
+ 0, 0,
+ $blurImageRect->getX(), $blurImageRect->getY(),
+ $scaleDownRect->getWidth(), $scaleDownRect->getHeight(),
+ $blurImageRect->getWidth(), $blurImageRect->getHeight()) === false) {
+ throw new \RuntimeException('Failed to resample blur image.');
+ }
+
+ //Blur the scaled canvas
+ for ($i = 0; $i < 40; ++$i) {
+ imagefilter($scaledDownImage, IMG_FILTER_GAUSSIAN_BLUR, 999);
+ }
+
+ //Copy the blurred image to the output canvas.
+ if (imagecopyresampled(
+ $output, $scaledDownImage,
+ 0, 0,
+ 0, 0,
+ $canvas->getWidth(), $canvas->getHeight(),
+ $scaleDownRect->getWidth(), $scaleDownRect->getHeight()) === false) {
+ throw new \RuntimeException('Failed to resample blurred image.');
+ }
+ } else {
+ if (imagefilledrectangle($output, 0, 0, $canvas->getWidth() - 1, $canvas->getHeight() - 1, $bgColor) === false) {
+ throw new \RuntimeException('Failed to fill image with background color.');
+ }
+ }
+
+ // Copy the resized (and resampled) image onto the new canvas.
+ if (imagecopyresampled(
+ $output, $source,
+ $dstRect->getX(), $dstRect->getY(),
+ $srcRect->getX(), $srcRect->getY(),
+ $dstRect->getWidth(), $dstRect->getHeight(),
+ $srcRect->getWidth(), $srcRect->getHeight()
+ ) === false) {
+ throw new \RuntimeException('Failed to resample image.');
+ }
+
+ // Handle image rotation.
+ $output = $this->_rotateResource($output, $bgColor);
+
+ return $output;
+ }
+
+ /**
+ * Calculates the rectangle of the blur image source.
+ *
+ * @param Rectangle $srcRect
+ * @param Dimensions $canvas
+ *
+ * @return Rectangle
+ */
+ protected function _calculateBlurImage(
+ Rectangle $srcRect,
+ Dimensions $canvas)
+ {
+ $widthScale = (float) ($canvas->getWidth() / $srcRect->getWidth());
+ $heightScale = (float) ($canvas->getHeight() / $srcRect->getHeight());
+ if ($widthScale > $heightScale) {
+ $resX = $srcRect->getX();
+ $resW = $srcRect->getWidth();
+ $resH = (float) $canvas->getHeight() / $widthScale;
+ $resY = (int) floor(($srcRect->getHeight() - $resH) / 2);
+
+ return new Rectangle($resX, $resY, $resW, $resH);
+ } else {
+ $resY = $srcRect->getY();
+ $resH = $srcRect->getHeight();
+ $resW = (float) $canvas->getWidth() / $heightScale;
+ $resX = (int) floor(($srcRect->getWidth() - $resW) / 2);
+
+ return new Rectangle($resX, $resY, $resW, $resH);
+ }
+ }
+
+ /**
+ * Wrapper for PHP's imagerotate function.
+ *
+ * @param resource $original
+ * @param int $bgColor
+ *
+ * @throws \RuntimeException
+ *
+ * @return resource
+ */
+ protected function _rotateResource(
+ $original,
+ $bgColor)
+ {
+ $angle = 0;
+ $flip = null;
+ // Find out angle and flip.
+ if ($this->_details->hasSwappedAxes()) {
+ if ($this->_details->isHorizontallyFlipped() && $this->_details->isVerticallyFlipped()) {
+ $angle = -90;
+ $flip = IMG_FLIP_HORIZONTAL;
+ } elseif ($this->_details->isHorizontallyFlipped()) {
+ $angle = -90;
+ } elseif ($this->_details->isVerticallyFlipped()) {
+ $angle = 90;
+ } else {
+ $angle = -90;
+ $flip = IMG_FLIP_VERTICAL;
+ }
+ } else {
+ if ($this->_details->isHorizontallyFlipped() && $this->_details->isVerticallyFlipped()) {
+ $flip = IMG_FLIP_BOTH;
+ } elseif ($this->_details->isHorizontallyFlipped()) {
+ $flip = IMG_FLIP_HORIZONTAL;
+ } elseif ($this->_details->isVerticallyFlipped()) {
+ $flip = IMG_FLIP_VERTICAL;
+ } else {
+ // Do nothing.
+ }
+ }
+
+ // Flip the image resource if needed. Does not create a new resource.
+ if ($flip !== null && imageflip($original, $flip) === false) {
+ throw new \RuntimeException('Failed to flip image.');
+ }
+
+ // Return original resource if no rotation is needed.
+ if ($angle === 0) {
+ return $original;
+ }
+
+ // Attempt to create a new, rotated image resource.
+ $result = imagerotate($original, $angle, $bgColor);
+ if ($result === false) {
+ throw new \RuntimeException('Failed to rotate image.');
+ }
+
+ // Destroy the original resource since we'll return the new resource.
+ @imagedestroy($original);
+
+ return $result;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Media/Photo/PhotoDetails.php b/instafeed/vendor/mgp25/instagram-php/src/Media/Photo/PhotoDetails.php
new file mode 100755
index 0000000..497d1ff
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Media/Photo/PhotoDetails.php
@@ -0,0 +1,177 @@
+_type;
+ }
+
+ /** {@inheritdoc} */
+ public function hasSwappedAxes()
+ {
+ return in_array($this->_orientation, [5, 6, 7, 8], true);
+ }
+
+ /** {@inheritdoc} */
+ public function isHorizontallyFlipped()
+ {
+ return in_array($this->_orientation, [2, 3, 6, 7], true);
+ }
+
+ /** {@inheritdoc} */
+ public function isVerticallyFlipped()
+ {
+ return in_array($this->_orientation, [3, 4, 7, 8], true);
+ }
+
+ /** {@inheritdoc} */
+ public function getMinAllowedWidth()
+ {
+ return self::MIN_WIDTH;
+ }
+
+ /** {@inheritdoc} */
+ public function getMaxAllowedWidth()
+ {
+ return self::MAX_WIDTH;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string $filename Path to the photo file.
+ *
+ * @throws \InvalidArgumentException If the photo file is missing or invalid.
+ */
+ public function __construct(
+ $filename)
+ {
+ // Check if input file exists.
+ if (empty($filename) || !is_file($filename)) {
+ throw new \InvalidArgumentException(sprintf('The photo file "%s" does not exist on disk.', $filename));
+ }
+
+ // Determine photo file size and throw when the file is empty.
+ $filesize = filesize($filename);
+ if ($filesize < 1) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The photo file "%s" is empty.',
+ $filename
+ ));
+ }
+
+ // Get image details.
+ $result = @getimagesize($filename);
+ if ($result === false) {
+ throw new \InvalidArgumentException(sprintf('The photo file "%s" is not a valid image.', $filename));
+ }
+ list($width, $height, $this->_type) = $result;
+
+ // Detect JPEG EXIF orientation if it exists.
+ $this->_orientation = $this->_getExifOrientation($filename, $this->_type);
+
+ parent::__construct($filename, $filesize, $width, $height);
+ }
+
+ /** {@inheritdoc} */
+ public function validate(
+ ConstraintsInterface $constraints)
+ {
+ parent::validate($constraints);
+
+ // WARNING TO CONTRIBUTORS: $mediaFilename is for ERROR DISPLAY to
+ // users. Do NOT use it to read from the hard disk!
+ $mediaFilename = $this->getBasename();
+
+ // Validate image type.
+ // NOTE: It is confirmed that Instagram only accepts JPEG files.
+ $type = $this->getType();
+ if ($type !== IMAGETYPE_JPEG) {
+ throw new \InvalidArgumentException(sprintf('The photo file "%s" is not a JPEG file.', $mediaFilename));
+ }
+
+ $width = $this->getWidth();
+ // Validate photo resolution. Instagram allows between 320px-1080px width.
+ if ($width < self::MIN_WIDTH || $width > self::MAX_WIDTH) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts photos that are between %d and %d pixels wide. Your file "%s" is %d pixels wide.',
+ self::MIN_WIDTH, self::MAX_WIDTH, $mediaFilename, $width
+ ));
+ }
+ }
+
+ /**
+ * Get the EXIF orientation from given file.
+ *
+ * @param string $filename
+ * @param int $type
+ *
+ * @return int
+ */
+ protected function _getExifOrientation(
+ $filename,
+ $type)
+ {
+ if ($type !== IMAGETYPE_JPEG || !function_exists('exif_read_data')) {
+ return self::DEFAULT_ORIENTATION;
+ }
+
+ $exif = @exif_read_data($filename);
+ if ($exif === false || !isset($exif['Orientation'])) {
+ return self::DEFAULT_ORIENTATION;
+ }
+
+ return (int) $exif['Orientation'];
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Media/Video/FFmpeg.php b/instafeed/vendor/mgp25/instagram-php/src/Media/Video/FFmpeg.php
new file mode 100755
index 0000000..7bde900
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Media/Video/FFmpeg.php
@@ -0,0 +1,244 @@
+_ffmpegBinary = $ffmpegBinary;
+
+ try {
+ $this->version();
+ } catch (\Exception $e) {
+ throw new \RuntimeException(sprintf('It seems that the path to ffmpeg binary is invalid. Please check your path to ensure that it is correct.'));
+ }
+ }
+
+ /**
+ * Create a new instance or use a cached one.
+ *
+ * @param string|null $ffmpegBinary Path to a ffmpeg binary, or NULL to autodetect.
+ *
+ * @return static
+ */
+ public static function factory(
+ $ffmpegBinary = null)
+ {
+ if ($ffmpegBinary === null) {
+ return static::_autoDetectBinary();
+ }
+
+ if (isset(self::$_instances[$ffmpegBinary])) {
+ return self::$_instances[$ffmpegBinary];
+ }
+
+ $instance = new static($ffmpegBinary);
+ self::$_instances[$ffmpegBinary] = $instance;
+
+ return $instance;
+ }
+
+ /**
+ * Run a command and wrap errors into an Exception (if any).
+ *
+ * @param string $command
+ *
+ * @throws \RuntimeException
+ *
+ * @return string[]
+ */
+ public function run(
+ $command)
+ {
+ $process = $this->runAsync($command);
+
+ try {
+ $exitCode = $process->wait();
+ } catch (\Exception $e) {
+ throw new \RuntimeException(sprintf('Failed to run the ffmpeg binary: %s', $e->getMessage()));
+ }
+ if ($exitCode) {
+ $errors = preg_replace('#[\r\n]+#', '"], ["', trim($process->getErrorOutput()));
+ $errorMsg = sprintf('FFmpeg Errors: ["%s"], Command: "%s".', $errors, $command);
+
+ throw new \RuntimeException($errorMsg, $exitCode);
+ }
+
+ return preg_split('#[\r\n]+#', $process->getOutput(), null, PREG_SPLIT_NO_EMPTY);
+ }
+
+ /**
+ * Run a command asynchronously.
+ *
+ * @param string $command
+ *
+ * @return Process
+ */
+ public function runAsync(
+ $command)
+ {
+ $fullCommand = sprintf('%s -v error %s', Args::escape($this->_ffmpegBinary), $command);
+
+ $process = new Process($fullCommand);
+ if (is_int(self::$defaultTimeout) && self::$defaultTimeout > 60) {
+ $process->setTimeout(self::$defaultTimeout);
+ }
+ $process->start();
+
+ return $process;
+ }
+
+ /**
+ * Get the ffmpeg version.
+ *
+ * @throws \RuntimeException
+ *
+ * @return string
+ */
+ public function version()
+ {
+ return $this->run('-version')[0];
+ }
+
+ /**
+ * Get the path to the ffmpeg binary.
+ *
+ * @return string
+ */
+ public function getFFmpegBinary()
+ {
+ return $this->_ffmpegBinary;
+ }
+
+ /**
+ * Check whether ffmpeg has -noautorotate flag.
+ *
+ * @return bool
+ */
+ public function hasNoAutorotate()
+ {
+ if ($this->_hasNoAutorotate === null) {
+ try {
+ $this->run('-noautorotate -f lavfi -i color=color=red -t 1 -f null -');
+ $this->_hasNoAutorotate = true;
+ } catch (\RuntimeException $e) {
+ $this->_hasNoAutorotate = false;
+ }
+ }
+
+ return $this->_hasNoAutorotate;
+ }
+
+ /**
+ * Check whether ffmpeg has libfdk_aac audio encoder.
+ *
+ * @return bool
+ */
+ public function hasLibFdkAac()
+ {
+ if ($this->_hasLibFdkAac === null) {
+ $this->_hasLibFdkAac = $this->_hasAudioEncoder('libfdk_aac');
+ }
+
+ return $this->_hasLibFdkAac;
+ }
+
+ /**
+ * Check whether ffmpeg has specified audio encoder.
+ *
+ * @param string $encoder
+ *
+ * @return bool
+ */
+ protected function _hasAudioEncoder(
+ $encoder)
+ {
+ try {
+ $this->run(sprintf(
+ '-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -c:a %s -t 1 -f null -',
+ Args::escape($encoder)
+ ));
+
+ return true;
+ } catch (\RuntimeException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @throws \RuntimeException
+ *
+ * @return static
+ */
+ protected static function _autoDetectBinary()
+ {
+ $binaries = defined('PHP_WINDOWS_VERSION_MAJOR') ? self::WINDOWS_BINARIES : self::BINARIES;
+ if (self::$defaultBinary !== null) {
+ array_unshift($binaries, self::$defaultBinary);
+ }
+ /* Backwards compatibility. */
+ if (Utils::$ffmpegBin !== null) {
+ array_unshift($binaries, Utils::$ffmpegBin);
+ }
+
+ $instance = null;
+ foreach ($binaries as $binary) {
+ if (isset(self::$_instances[$binary])) {
+ return self::$_instances[$binary];
+ }
+
+ try {
+ $instance = new static($binary);
+ } catch (\Exception $e) {
+ continue;
+ }
+ self::$defaultBinary = $binary;
+ self::$_instances[$binary] = $instance;
+
+ return $instance;
+ }
+
+ throw new \RuntimeException('You must have FFmpeg to process videos. Ensure that its binary-folder exists in your PATH environment variable, or manually set its full path via "\InstagramAPI\Media\Video\FFmpeg::$defaultBinary = \'/home/exampleuser/ffmpeg/bin/ffmpeg\';" at the start of your script.');
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Media/Video/InstagramThumbnail.php b/instafeed/vendor/mgp25/instagram-php/src/Media/Video/InstagramThumbnail.php
new file mode 100755
index 0000000..882cd3b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Media/Video/InstagramThumbnail.php
@@ -0,0 +1,155 @@
+_thumbnailTimestamp = 1.0; // Default.
+
+ // Handle per-feed timestamps and custom thumbnail timestamps.
+ if (isset($options['targetFeed'])) {
+ switch ($options['targetFeed']) {
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ // Stories always have the thumbnail at "00:00:00.000" instead.
+ $this->_thumbnailTimestamp = 0.0;
+ break;
+ case Constants::FEED_TIMELINE || Constants::FEED_TIMELINE_ALBUM:
+ // Handle custom timestamp (only supported by timeline media).
+ // NOTE: Matches real app which only customizes timeline covers.
+ if (isset($options['thumbnailTimestamp'])) {
+ $customTimestamp = $options['thumbnailTimestamp'];
+ // If custom timestamp is a number, use as-is. Else assume
+ // a "HH:MM:SS[.000]" string and convert it. Throws if bad.
+ $this->_thumbnailTimestamp = is_int($customTimestamp) || is_float($customTimestamp)
+ ? (float) $customTimestamp
+ : Utils::hmsTimeToSeconds($customTimestamp);
+ }
+ break;
+ default:
+ // Keep the default.
+ }
+ }
+
+ // Ensure the timestamp is 0+ and never longer than the video duration.
+ if ($this->_thumbnailTimestamp > $this->_details->getDuration()) {
+ $this->_thumbnailTimestamp = $this->_details->getDuration();
+ }
+ if ($this->_thumbnailTimestamp < 0.0) {
+ throw new \InvalidArgumentException('Thumbnail timestamp must be a positive number.');
+ }
+ }
+
+ /**
+ * Get thumbnail timestamp as a float.
+ *
+ * @return float Thumbnail offset in secs, with milliseconds (decimals).
+ */
+ public function getTimestamp()
+ {
+ return $this->_thumbnailTimestamp;
+ }
+
+ /**
+ * Get thumbnail timestamp as a formatted string.
+ *
+ * @return string The time formatted as `HH:MM:SS.###` (`###` is millis).
+ */
+ public function getTimestampString()
+ {
+ return Utils::hmsTimeFromSeconds($this->_thumbnailTimestamp);
+ }
+
+ /** {@inheritdoc} */
+ protected function _shouldProcess()
+ {
+ // We must always process the video to get its thumbnail.
+ return true;
+ }
+
+ /** {@inheritdoc} */
+ protected function _ffmpegMustRunAgain(
+ $attempt,
+ array $ffmpegOutput)
+ {
+ // If this was the first run, we must look for the "first frame is no
+ // keyframe" error. It is a rare error which can happen when the user
+ // wants to extract a frame from a timestamp that is before the first
+ // keyframe of the video file. Most files can extract frames even at
+ // `00:00:00.000`, but certain files may have garbage at the start of
+ // the file, and thus extracting a garbage / empty / broken frame and
+ // showing this error. The solution is to omit the `-ss` timestamp for
+ // such files to automatically make ffmpeg extract the 1st VALID frame.
+ // NOTE: We'll only need to retry if timestamp is over 0.0. If it was
+ // zero, then we already executed without `-ss` and shouldn't retry.
+ if ($attempt === 1 && $this->_thumbnailTimestamp > 0.0) {
+ foreach ($ffmpegOutput as $line) {
+ // Example: `[flv @ 0x7fc9cc002e00] warning: first frame is no keyframe`.
+ if (strpos($line, ': first frame is no keyframe') !== false) {
+ return true;
+ }
+ }
+ }
+
+ // If this was the 2nd run or there was no error, accept result as-is.
+ return false;
+ }
+
+ /** {@inheritdoc} */
+ protected function _getInputFlags(
+ $attempt)
+ {
+ // The seektime *must* be specified here, before the input file.
+ // Otherwise ffmpeg will do a slow conversion of the whole file
+ // (but discarding converted frames) until it gets to target time.
+ // See: https://trac.ffmpeg.org/wiki/Seeking
+ // IMPORTANT: WE ONLY APPLY THE SEEK-COMMAND ON THE *FIRST* ATTEMPT. SEE
+ // COMMENTS IN `_ffmpegMustRunAgain()` FOR MORE INFORMATION ABOUT WHY.
+ // AND WE'LL OMIT SEEKING COMPLETELY IF IT'S "0.0" ("EARLIEST POSSIBLE"), TO
+ // GUARANTEE SUCCESS AT GRABBING THE "EARLIEST FRAME" W/O NEEDING RETRIES.
+ return $attempt > 1 || $this->_thumbnailTimestamp === 0.0
+ ? []
+ : [
+ sprintf('-ss %s', $this->getTimestampString()),
+ ];
+ }
+
+ /** {@inheritdoc} */
+ protected function _getOutputFlags(
+ $attempt)
+ {
+ return [
+ '-f mjpeg',
+ '-vframes 1',
+ ];
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Media/Video/InstagramVideo.php b/instafeed/vendor/mgp25/instagram-php/src/Media/Video/InstagramVideo.php
new file mode 100755
index 0000000..c6072a1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Media/Video/InstagramVideo.php
@@ -0,0 +1,263 @@
+_details = new VideoDetails($this->_inputFile);
+
+ $this->_ffmpeg = $ffmpeg;
+ if ($this->_ffmpeg === null) {
+ $this->_ffmpeg = FFmpeg::factory();
+ }
+ }
+
+ /** {@inheritdoc} */
+ protected function _isMod2CanvasRequired()
+ {
+ return true;
+ }
+
+ /** {@inheritdoc} */
+ protected function _createOutputFile(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas)
+ {
+ $outputFile = null;
+
+ try {
+ // Prepare output file.
+ $outputFile = Utils::createTempFile($this->_tmpPath, 'VID');
+ // Attempt to process the input file.
+ // --------------------------------------------------------------
+ // WARNING: This calls ffmpeg, which can run for a long time. The
+ // user may be running in a CLI. In that case, if they press Ctrl-C
+ // to abort, PHP won't run ANY of our shutdown/destructor handlers!
+ // Therefore they'll still have the temp file if they abort ffmpeg
+ // conversion with Ctrl-C, since our auto-cleanup won't run. There's
+ // absolutely nothing good we can do about that (except a signal
+ // handler to interrupt their Ctrl-C, which is a terrible idea).
+ // Their OS should clear its temp folder periodically. Or if they
+ // use a custom temp folder, it's THEIR own job to clear it!
+ // --------------------------------------------------------------
+ $this->_processVideo($srcRect, $dstRect, $canvas, $outputFile);
+ } catch (\Exception $e) {
+ if ($outputFile !== null && is_file($outputFile)) {
+ @unlink($outputFile);
+ }
+
+ throw $e; // Re-throw.
+ }
+
+ return $outputFile;
+ }
+
+ /**
+ * @param Rectangle $srcRect Rectangle to copy from the input.
+ * @param Rectangle $dstRect Destination place and scale of copied pixels.
+ * @param Dimensions $canvas The size of the destination canvas.
+ * @param string $outputFile
+ *
+ * @throws \RuntimeException
+ */
+ protected function _processVideo(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas,
+ $outputFile)
+ {
+ // ffmpeg has a bug - https://trac.ffmpeg.org/ticket/6370 - it preserves rotate tag
+ // in the output video when the input video has display matrix with rotate
+ // and rotation was overridden manually.
+ $shouldAutorotate = $this->_ffmpeg->hasNoAutorotate() && $this->_details->hasRotationMatrix();
+
+ // Swap to correct dimensions if the video pixels are stored rotated.
+ if (!$shouldAutorotate && $this->_details->hasSwappedAxes()) {
+ $srcRect = $srcRect->withSwappedAxes();
+ $dstRect = $dstRect->withSwappedAxes();
+ $canvas = $canvas->withSwappedAxes();
+ }
+
+ // Prepare filters.
+ $bgColor = sprintf('0x%02X%02X%02X', ...$this->_bgColor);
+ $filters = [
+ sprintf('crop=w=%d:h=%d:x=%d:y=%d', $srcRect->getWidth(), $srcRect->getHeight(), $srcRect->getX(), $srcRect->getY()),
+ sprintf('scale=w=%d:h=%d', $dstRect->getWidth(), $dstRect->getHeight()),
+ sprintf('pad=w=%d:h=%d:x=%d:y=%d:color=%s', $canvas->getWidth(), $canvas->getHeight(), $dstRect->getX(), $dstRect->getY(), $bgColor),
+ ];
+
+ // Rotate the video (if needed to).
+ $rotationFilters = $this->_getRotationFilters();
+ $noAutorotate = false;
+ if (!empty($rotationFilters) && !$shouldAutorotate) {
+ $filters = array_merge($filters, $rotationFilters);
+ $noAutorotate = $this->_ffmpeg->hasNoAutorotate();
+ }
+
+ $attempt = 0;
+ do {
+ ++$attempt;
+
+ // Get the flags to apply to the input file.
+ $inputFlags = $this->_getInputFlags($attempt);
+ if ($noAutorotate) {
+ $inputFlags[] = '-noautorotate';
+ }
+
+ // Video format can't copy since we always need to re-encode due to video filtering.
+ $ffmpegOutput = $this->_ffmpeg->run(sprintf(
+ '-y %s -i %s -vf %s %s %s',
+ implode(' ', $inputFlags),
+ Args::escape($this->_inputFile),
+ Args::escape(implode(',', $filters)),
+ implode(' ', $this->_getOutputFlags($attempt)),
+ Args::escape($outputFile)
+ ));
+ } while ($this->_ffmpegMustRunAgain($attempt, $ffmpegOutput));
+ }
+
+ /**
+ * Internal function to determine whether ffmpeg needs to run again.
+ *
+ * @param int $attempt Which ffmpeg attempt just executed.
+ * @param string[] $ffmpegOutput Array of error strings from the attempt.
+ *
+ * @throws \RuntimeException If this function wants to give up and determines
+ * that we cannot succeed and should throw completely.
+ *
+ * @return bool TRUE to run again, FALSE to accept the current output.
+ */
+ protected function _ffmpegMustRunAgain(
+ $attempt,
+ array $ffmpegOutput)
+ {
+ return false;
+ }
+
+ /**
+ * Get the input flags (placed before the input filename).
+ *
+ * @param int $attempt The current ffmpeg execution attempt.
+ *
+ * @return string[]
+ */
+ protected function _getInputFlags(
+ $attempt)
+ {
+ return [];
+ }
+
+ /**
+ * Get the output flags (placed before the output filename).
+ *
+ * @param int $attempt The current ffmpeg execution attempt.
+ *
+ * @return string[]
+ */
+ protected function _getOutputFlags(
+ $attempt)
+ {
+ $result = [
+ '-metadata:s:v rotate=""', // Strip rotation from metadata.
+ '-f mp4', // Force output format to MP4.
+ ];
+
+ // Force H.264 for the video.
+ $result[] = '-c:v libx264 -preset fast -crf 24';
+
+ // Force AAC for the audio.
+ if ($this->_details->getAudioCodec() !== 'aac') {
+ if ($this->_ffmpeg->hasLibFdkAac()) {
+ $result[] = '-c:a libfdk_aac -vbr 4';
+ } else {
+ // The encoder 'aac' is experimental but experimental codecs are not enabled,
+ // add '-strict -2' if you want to use it.
+ $result[] = '-strict -2 -c:a aac -b:a 96k';
+ }
+ } else {
+ $result[] = '-c:a copy';
+ }
+
+ // Cut too long videos.
+ // FFmpeg cuts video sticking to a closest frame. As a result we might
+ // end with a video that is longer than desired duration. To prevent this
+ // we must use a duration that is somewhat smaller than its maximum allowed
+ // value. 0.1 sec is 1 frame of 10 FPS video.
+ $maxDuration = $this->_constraints->getMaxDuration() - 0.1;
+ if ($this->_details->getDuration() > $maxDuration) {
+ $result[] = sprintf('-t %.2F', $maxDuration);
+ }
+
+ // TODO Loop too short videos.
+ if ($this->_details->getDuration() < $this->_constraints->getMinDuration()) {
+ $times = ceil($this->_constraints->getMinDuration() / $this->_details->getDuration());
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get an array of filters needed to restore video orientation.
+ *
+ * @return array
+ */
+ protected function _getRotationFilters()
+ {
+ $result = [];
+ if ($this->_details->hasSwappedAxes()) {
+ if ($this->_details->isHorizontallyFlipped() && $this->_details->isVerticallyFlipped()) {
+ $result[] = 'transpose=clock';
+ $result[] = 'hflip';
+ } elseif ($this->_details->isHorizontallyFlipped()) {
+ $result[] = 'transpose=clock';
+ } elseif ($this->_details->isVerticallyFlipped()) {
+ $result[] = 'transpose=cclock';
+ } else {
+ $result[] = 'transpose=cclock';
+ $result[] = 'vflip';
+ }
+ } else {
+ if ($this->_details->isHorizontallyFlipped()) {
+ $result[] = 'hflip';
+ }
+ if ($this->_details->isVerticallyFlipped()) {
+ $result[] = 'vflip';
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Media/Video/VideoDetails.php b/instafeed/vendor/mgp25/instagram-php/src/Media/Video/VideoDetails.php
new file mode 100755
index 0000000..a1edc96
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Media/Video/VideoDetails.php
@@ -0,0 +1,348 @@
+_duration;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDurationInMsec()
+ {
+ // NOTE: ceil() is to round up and get rid of any MS decimals.
+ return (int) ceil($this->getDuration() * 1000);
+ }
+
+ /**
+ * @return string
+ */
+ public function getVideoCodec()
+ {
+ return $this->_videoCodec;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getAudioCodec()
+ {
+ return $this->_audioCodec;
+ }
+
+ /**
+ * @return string
+ */
+ public function getContainer()
+ {
+ return $this->_container;
+ }
+
+ /** {@inheritdoc} */
+ public function hasSwappedAxes()
+ {
+ return $this->_rotation % 180;
+ }
+
+ /** {@inheritdoc} */
+ public function isHorizontallyFlipped()
+ {
+ return $this->_rotation === 90 || $this->_rotation === 180;
+ }
+
+ /** {@inheritdoc} */
+ public function isVerticallyFlipped()
+ {
+ return $this->_rotation === 180 || $this->_rotation === 270;
+ }
+
+ /**
+ * Check whether the video has display matrix with rotate.
+ *
+ * @return bool
+ */
+ public function hasRotationMatrix()
+ {
+ return $this->_hasRotationMatrix;
+ }
+
+ /** {@inheritdoc} */
+ public function getMinAllowedWidth()
+ {
+ return self::MIN_WIDTH;
+ }
+
+ /** {@inheritdoc} */
+ public function getMaxAllowedWidth()
+ {
+ return self::MAX_WIDTH;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string $filename Path to the video file.
+ *
+ * @throws \InvalidArgumentException If the video file is missing or invalid.
+ * @throws \RuntimeException If FFmpeg isn't working properly.
+ */
+ public function __construct(
+ $filename)
+ {
+ // Check if input file exists.
+ if (empty($filename) || !is_file($filename)) {
+ throw new \InvalidArgumentException(sprintf('The video file "%s" does not exist on disk.', $filename));
+ }
+
+ // Determine video file size and throw when the file is empty.
+ $filesize = filesize($filename);
+ if ($filesize < 1) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The video "%s" is empty.',
+ $filename
+ ));
+ }
+
+ // The user must have FFprobe.
+ $ffprobe = Utils::checkFFPROBE();
+ if ($ffprobe === false) {
+ throw new \RuntimeException('You must have FFprobe to analyze video details. Ensure that its binary-folder exists in your PATH environment variable, or manually set its full path via "\InstagramAPI\Utils::$ffprobeBin = \'/home/exampleuser/ffmpeg/bin/ffprobe\';" at the start of your script.');
+ }
+
+ // Load with FFPROBE. Shows details as JSON and exits.
+ $command = sprintf(
+ '%s -v quiet -print_format json -show_format -show_streams %s',
+ Args::escape($ffprobe),
+ Args::escape($filename)
+ );
+ $jsonInfo = @shell_exec($command);
+
+ // Check for processing errors.
+ if ($jsonInfo === null) {
+ throw new \RuntimeException(sprintf('FFprobe failed to analyze your video file "%s".', $filename));
+ }
+
+ // Attempt to decode the JSON.
+ $probeResult = @json_decode($jsonInfo, true);
+ if ($probeResult === null) {
+ throw new \RuntimeException(sprintf('FFprobe gave us invalid JSON for "%s".', $filename));
+ }
+
+ if (!isset($probeResult['streams']) || !is_array($probeResult['streams'])) {
+ throw new \RuntimeException(sprintf('FFprobe failed to detect any stream. Is "%s" a valid media file?', $filename));
+ }
+
+ // Now analyze all streams to find the first video stream.
+ $width = null;
+ $height = null;
+ $this->_hasRotationMatrix = false;
+ foreach ($probeResult['streams'] as $streamIdx => $streamInfo) {
+ if (!isset($streamInfo['codec_type'])) {
+ continue;
+ }
+ switch ($streamInfo['codec_type']) {
+ case 'video':
+ // TODO Mark media as invalid if more than one video stream found (?)
+ $foundVideoStream = true;
+ $this->_videoCodec = (string) $streamInfo['codec_name']; // string
+ $width = (int) $streamInfo['width'];
+ $height = (int) $streamInfo['height'];
+ if (isset($streamInfo['duration'])) {
+ // NOTE: Duration is a float such as "230.138000".
+ $this->_duration = (float) $streamInfo['duration'];
+ }
+
+ // Read rotation angle from tags.
+ $this->_rotation = 0;
+ if (isset($streamInfo['tags']) && is_array($streamInfo['tags'])) {
+ $tags = array_change_key_case($streamInfo['tags'], CASE_LOWER);
+ if (isset($tags['rotate'])) {
+ $this->_rotation = $this->_normalizeRotation((int) $tags['rotate']);
+ }
+ }
+ if (isset($streamInfo['side_data_list']) && is_array($streamInfo['side_data_list'])) {
+ foreach ($streamInfo['side_data_list'] as $sideData) {
+ if (!isset($sideData['side_data_type'])) {
+ continue;
+ }
+ if (!isset($sideData['rotation'])) {
+ continue;
+ }
+
+ $this->_hasRotationMatrix =
+ strcasecmp($sideData['side_data_type'], 'Display Matrix') === 0
+ && abs($sideData['rotation']) > 0.1;
+ }
+ }
+ break;
+ case 'audio':
+ // TODO Mark media as invalid if more than one audio stream found (?)
+ $this->_audioCodec = (string) $streamInfo['codec_name']; // string
+ break;
+ default:
+ // TODO Mark media as invalid if unknown stream found (?)
+ }
+ }
+
+ // Sometimes there is no duration in stream info, so we should check the format.
+ if ($this->_duration === null && isset($probeResult['format']['duration'])) {
+ $this->_duration = (float) $probeResult['format']['duration'];
+ }
+
+ if (isset($probeResult['format']['format_name'])) {
+ $this->_container = (string) $probeResult['format']['format_name'];
+ }
+
+ // Make sure we have detected the video duration.
+ if ($this->_duration === null) {
+ throw new \RuntimeException(sprintf('FFprobe failed to detect video duration. Is "%s" a valid video file?', $filename));
+ }
+
+ parent::__construct($filename, $filesize, $width, $height);
+ }
+
+ /** {@inheritdoc} */
+ public function validate(
+ ConstraintsInterface $constraints)
+ {
+ parent::validate($constraints);
+
+ // WARNING TO CONTRIBUTORS: $mediaFilename is for ERROR DISPLAY to
+ // users. Do NOT use it to read from the hard disk!
+ $mediaFilename = $this->getBasename();
+
+ // Make sure we have found at least one video stream.
+ if ($this->_videoCodec === null) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram requires video with at least one video stream. Your file "%s" doesn\'t have any.',
+ $mediaFilename
+ ));
+ }
+
+ // Check the video stream. We should have at least one.
+ if ($this->_videoCodec !== 'h264') {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos encoded with the H.264 video codec. Your file "%s" has "%s".',
+ $mediaFilename, $this->_videoCodec
+ ));
+ }
+
+ // Check the audio stream (if available).
+ if ($this->_audioCodec !== null && $this->_audioCodec !== 'aac') {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos encoded with the AAC audio codec. Your file "%s" has "%s".',
+ $mediaFilename, $this->_audioCodec
+ ));
+ }
+
+ // Check the container format.
+ if (strpos($this->_container, 'mp4') === false) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos packed into MP4 containers. Your file "%s" has "%s".',
+ $mediaFilename, $this->_container
+ ));
+ }
+
+ // Validate video resolution. Instagram allows between 480px-720px width.
+ // NOTE: They'll resize 720px wide videos on the server, to 640px instead.
+ // NOTE: Their server CAN receive between 320px-1080px width without
+ // rejecting the video, but the official app would NEVER upload such
+ // resolutions. It's controlled by the "ig_android_universe_video_production"
+ // experiment variable, which currently enforces width of min:480, max:720.
+ // If users want to upload bigger videos, they MUST resize locally first!
+ $width = $this->getWidth();
+ if ($width < self::MIN_WIDTH || $width > self::MAX_WIDTH) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos that are between %d and %d pixels wide. Your file "%s" is %d pixels wide.',
+ self::MIN_WIDTH, self::MAX_WIDTH, $mediaFilename, $width
+ ));
+ }
+
+ // Validate video length.
+ // NOTE: Instagram has no disk size limit, but this length validation
+ // also ensures we can only upload small files exactly as intended.
+ $duration = $this->getDuration();
+ $minDuration = $constraints->getMinDuration();
+ $maxDuration = $constraints->getMaxDuration();
+ if ($duration < $minDuration || $duration > $maxDuration) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts %s videos that are between %.3f and %.3f seconds long. Your video "%s" is %.3f seconds long.',
+ $constraints->getTitle(), $minDuration, $maxDuration, $mediaFilename, $duration
+ ));
+ }
+ }
+
+ /**
+ * @param $rotation
+ *
+ * @return int
+ */
+ private function _normalizeRotation(
+ $rotation)
+ {
+ // The angle must be in 0..359 degrees range.
+ $result = $rotation % 360;
+ // Negative angle can be normalized by adding it to 360:
+ // 360 + (-90) = 270.
+ if ($result < 0) {
+ $result = 360 + $result;
+ }
+ // The final angle must be one of 0, 90, 180 or 270 degrees.
+ // So we are rounding it to the closest one.
+ $result = round($result / 90) * 90;
+
+ return (int) $result;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Middleware/FakeCookies.php b/instafeed/vendor/mgp25/instagram-php/src/Middleware/FakeCookies.php
new file mode 100755
index 0000000..dc92df4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Middleware/FakeCookies.php
@@ -0,0 +1,172 @@
+clear();
+ }
+
+ /**
+ * Removes all fake cookies so they won't be added to further requests.
+ */
+ public function clear()
+ {
+ $this->_cookies = [];
+ }
+
+ /**
+ * Get all currently used fake cookies.
+ *
+ * @return array
+ */
+ public function cookies()
+ {
+ return $this->_cookies;
+ }
+
+ /**
+ * Adds a fake cookie which will be injected into all requests.
+ *
+ * Remember to clear your fake cookies when you no longer need them.
+ *
+ * Usually you only need fake cookies for a few requests, and care must be
+ * taken to GUARANTEE clearout after that, via something like the following:
+ * "try{...}finally{ ...->clearFakeCookies(); }").
+ *
+ * Otherwise you would FOREVER pollute all other requests!
+ *
+ * If you only need the cookie once, the best way to guarantee clearout is
+ * to leave the "singleUse" parameter in its enabled state.
+ *
+ * @param string $name The name of the cookie. CASE SENSITIVE!
+ * @param string $value The value of the cookie.
+ * @param bool $singleUse If TRUE, the cookie will be deleted after 1 use.
+ */
+ public function add(
+ $name,
+ $value,
+ $singleUse = true)
+ {
+ // This overwrites any existing fake cookie with the same name, which is
+ // intentional since the names of cookies must be unique.
+ $this->_cookies[$name] = [
+ 'value' => $value,
+ 'singleUse' => $singleUse,
+ ];
+ }
+
+ /**
+ * Delete a single fake cookie.
+ *
+ * Useful for selectively removing some fake cookies but keeping the rest.
+ *
+ * @param string $name The name of the cookie. CASE SENSITIVE!
+ */
+ public function delete(
+ $name)
+ {
+ unset($this->_cookies[$name]);
+ }
+
+ /**
+ * Middleware setup function.
+ *
+ * Called by Guzzle when it needs to add an instance of our middleware to
+ * its stack. We simply return a callable which will process all requests.
+ *
+ * @param callable $handler
+ *
+ * @return callable
+ */
+ public function __invoke(
+ callable $handler)
+ {
+ return function (
+ RequestInterface $request,
+ array $options
+ ) use ($handler) {
+ $fakeCookies = $this->cookies();
+
+ // Pass request through unmodified if no work to do (to save CPU).
+ if (count($fakeCookies) === 0) {
+ return $handler($request, $options);
+ }
+
+ $finalCookies = [];
+
+ // Extract all existing cookies in this request's "Cookie:" header.
+ if ($request->hasHeader('Cookie')) {
+ $cookieHeaders = $request->getHeader('Cookie');
+ foreach ($cookieHeaders as $headerLine) {
+ $theseCookies = explode('; ', $headerLine);
+ foreach ($theseCookies as $cookieEntry) {
+ $cookieParts = explode('=', $cookieEntry, 2);
+ if (count($cookieParts) == 2) {
+ // We have the name and value of the cookie!
+ $finalCookies[$cookieParts[0]] = $cookieParts[1];
+ } else {
+ // Unable to find an equals sign, just re-use this
+ // cookie as-is (TRUE="re-use literally").
+ $finalCookies[$cookieEntry] = true;
+ }
+ }
+ }
+ }
+
+ // Inject all of our fake cookies, overwriting any name clashes.
+ // NOTE: The name matching is CASE SENSITIVE!
+ foreach ($fakeCookies as $name => $cookieInfo) {
+ $finalCookies[$name] = $cookieInfo['value'];
+
+ // Delete the cookie now if it was a single-use cookie.
+ if ($cookieInfo['singleUse']) {
+ $this->delete($name);
+ }
+ }
+
+ // Generate all individual cookie strings for the final cookies.
+ $values = [];
+ foreach ($finalCookies as $name => $value) {
+ if ($value === true) {
+ // Cookies to re-use as-is, due to parsing error above.
+ $values[] = $name;
+ } else {
+ $values[] = $name.'='.$value;
+ }
+ }
+
+ // Generate our new, semicolon-separated "Cookie:" header line.
+ // NOTE: This completely replaces the old header. As intended.
+ $finalCookieHeader = implode('; ', $values);
+ $request = $request->withHeader('Cookie', $finalCookieHeader);
+
+ return $handler($request, $options);
+ };
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Middleware/ZeroRating.php b/instafeed/vendor/mgp25/instagram-php/src/Middleware/ZeroRating.php
new file mode 100755
index 0000000..bcf6620
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Middleware/ZeroRating.php
@@ -0,0 +1,120 @@
+ '$1b.$2$3',
+ ];
+
+ /**
+ * Rewrite rules.
+ *
+ * @var array
+ */
+ private $_rules;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->reset();
+ }
+
+ /**
+ * Reset rules to default ones.
+ */
+ public function reset()
+ {
+ $this->update(self::DEFAULT_REWRITE);
+ }
+
+ /**
+ * Update rules.
+ *
+ * @param array $rules
+ */
+ public function update(
+ array $rules = [])
+ {
+ $this->_rules = [];
+ foreach ($rules as $from => $to) {
+ $regex = "#{$from}#";
+ $test = @preg_match($regex, '');
+ if ($test === false) {
+ continue;
+ }
+ $this->_rules[$regex] = strtr($to, [
+ '\.' => '.',
+ ]);
+ }
+ }
+
+ /**
+ * Middleware setup function.
+ *
+ * Called by Guzzle when it needs to add an instance of our middleware to
+ * its stack. We simply return a callable which will process all requests.
+ *
+ * @param callable $handler
+ *
+ * @return callable
+ */
+ public function __invoke(
+ callable $handler)
+ {
+ return function (
+ RequestInterface $request,
+ array $options
+ ) use ($handler) {
+ if (empty($this->_rules)) {
+ return $handler($request, $options);
+ }
+
+ $oldUri = (string) $request->getUri();
+ $uri = $this->rewrite($oldUri);
+ if ($uri !== $oldUri) {
+ $request = $request->withUri(new Uri($uri));
+ }
+
+ return $handler($request, $options);
+ };
+ }
+
+ /**
+ * Do a rewrite.
+ *
+ * @param string $uri
+ *
+ * @return string
+ */
+ public function rewrite(
+ $uri)
+ {
+ foreach ($this->_rules as $from => $to) {
+ $result = @preg_replace($from, $to, $uri);
+ if (!is_string($result)) {
+ continue;
+ }
+ // We must break at the first succeeded replace.
+ if ($result !== $uri) {
+ return $result;
+ }
+ }
+
+ return $uri;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Push.php b/instafeed/vendor/mgp25/instagram-php/src/Push.php
new file mode 100755
index 0000000..bee6cfd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Push.php
@@ -0,0 +1,286 @@
+_instagram = $instagram;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+ if ($this->_logger === null) {
+ $this->_logger = new NullLogger();
+ }
+
+ $this->_fbnsAuth = new Fbns\Auth($this->_instagram);
+ $this->_fbns = $this->_getFbns();
+ }
+
+ /**
+ * Incoming notification callback.
+ *
+ * @param Notification $notification
+ */
+ protected function _onPush(
+ Notification $notification)
+ {
+ $collapseKey = $notification->getCollapseKey();
+ $this->_logger->info(sprintf('Received a push with collapse key "%s"', $collapseKey), [(string) $notification]);
+ $this->emit('incoming', [$notification]);
+ if (!empty($collapseKey)) {
+ $this->emit($collapseKey, [$notification]);
+ }
+ }
+
+ /**
+ * Create a new FBNS receiver.
+ *
+ * @return Fbns
+ */
+ protected function _getFbns()
+ {
+ $fbns = new Fbns(
+ $this,
+ new Connector($this->_instagram, $this->_loop),
+ $this->_fbnsAuth,
+ $this->_instagram->device,
+ $this->_loop,
+ $this->_logger
+ );
+ $fbns->on('fbns_auth', function ($authJson) {
+ try {
+ $this->_fbnsAuth->update($authJson);
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to update FBNS auth: %s', $e->getMessage()), [$authJson]);
+ }
+ });
+ $fbns->on('fbns_token', function ($token) {
+ // Refresh the "last token activity" timestamp.
+ // The age of this timestamp helps us detect when the user
+ // has stopped using the Push features due to inactivity.
+ try {
+ $this->_instagram->settings->set('last_fbns_token', time());
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to write FBNS token timestamp: %s', $e->getMessage()));
+ }
+ // Read our old token. If an identical value exists, then we know
+ // that we've already registered that token during this session.
+ try {
+ $oldToken = $this->_instagram->settings->get('fbns_token');
+ // Do nothing when the new token is equal to the old one.
+ if ($token === $oldToken) {
+ return;
+ }
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to read FBNS token: %s', $e->getMessage()));
+ }
+ // Register the new token.
+ try {
+ $this->_instagram->push->register('mqtt', $token);
+ } catch (\Exception $e) {
+ $this->emit('error', [$e]);
+ }
+ // Save the newly received token to the storage.
+ // NOTE: We save it even if the registration failed, since we now
+ // got it from the server and assume they've given us a good one.
+ // However, it'll always be re-validated during the general login()
+ // flow, and will be cleared there if it fails to register there.
+ try {
+ $this->_instagram->settings->set('fbns_token', $token);
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to update FBNS token: %s', $e->getMessage()), [$token]);
+ }
+ });
+ $fbns->on('push', function (Notification $notification) {
+ $this->_onPush($notification);
+ });
+
+ return $fbns;
+ }
+
+ /**
+ * Start Push receiver.
+ */
+ public function start()
+ {
+ $this->_fbns->start();
+ }
+
+ /**
+ * Stop Push receiver.
+ */
+ public function stop()
+ {
+ $this->_fbns->stop();
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Push/Fbns.php b/instafeed/vendor/mgp25/instagram-php/src/Push/Fbns.php
new file mode 100755
index 0000000..2ddf1f2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Push/Fbns.php
@@ -0,0 +1,194 @@
+_target = $target;
+ $this->_connector = $connector;
+ $this->_auth = $auth;
+ $this->_device = $device;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+
+ $this->_client = $this->_getClient();
+ }
+
+ /**
+ * Create a new FBNS client instance.
+ *
+ * @return Lite
+ */
+ protected function _getClient()
+ {
+ $client = new Lite($this->_loop, $this->_connector, $this->_logger);
+
+ // Bind events.
+ $client
+ ->on('connect', function (Lite\ConnectResponsePacket $responsePacket) {
+ // Update auth credentials.
+ $authJson = $responsePacket->getAuth();
+ if (strlen($authJson)) {
+ $this->_logger->info('Received a non-empty auth.', [$authJson]);
+ $this->emit('fbns_auth', [$authJson]);
+ }
+
+ // Register an application.
+ $this->_client->register(Constants::PACKAGE_NAME, Constants::FACEBOOK_ANALYTICS_APPLICATION_ID);
+ })
+ ->on('disconnect', function () {
+ // Try to reconnect.
+ if (!$this->_reconnectInterval) {
+ $this->_connect();
+ }
+ })
+ ->on('register', function (Register $message) {
+ if (!empty($message->getError())) {
+ $this->_target->emit('error', [new \RuntimeException($message->getError())]);
+
+ return;
+ }
+ $this->_logger->info('Received a non-empty token.', [$message->getToken()]);
+ $this->emit('fbns_token', [$message->getToken()]);
+ })
+ ->on('push', function (PushMessage $message) {
+ $payload = $message->getPayload();
+
+ try {
+ $notification = new Notification($payload);
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to decode push: %s', $e->getMessage()), [$payload]);
+
+ return;
+ }
+ $this->emit('push', [$notification]);
+ });
+
+ return $client;
+ }
+
+ /**
+ * Try to establish a connection.
+ */
+ protected function _connect()
+ {
+ $this->_setReconnectTimer(function () {
+ $connection = new Connection(
+ $this->_auth,
+ $this->_device->getFbUserAgent(Constants::FBNS_APPLICATION_NAME)
+ );
+
+ return $this->_client->connect(self::DEFAULT_HOST, self::DEFAULT_PORT, $connection, self::CONNECTION_TIMEOUT);
+ });
+ }
+
+ /**
+ * Start Push receiver.
+ */
+ public function start()
+ {
+ $this->_logger->info('Starting FBNS client...');
+ $this->_isActive = true;
+ $this->_reconnectInterval = 0;
+ $this->_connect();
+ }
+
+ /**
+ * Stop Push receiver.
+ */
+ public function stop()
+ {
+ $this->_logger->info('Stopping FBNS client...');
+ $this->_isActive = false;
+ $this->_cancelReconnectTimer();
+ $this->_client->disconnect();
+ }
+
+ /** {@inheritdoc} */
+ public function isActive()
+ {
+ return $this->_isActive;
+ }
+
+ /** {@inheritdoc} */
+ public function getLogger()
+ {
+ return $this->_logger;
+ }
+
+ /** {@inheritdoc} */
+ public function getLoop()
+ {
+ return $this->_loop;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Push/Fbns/Auth.php b/instafeed/vendor/mgp25/instagram-php/src/Push/Fbns/Auth.php
new file mode 100755
index 0000000..e68b2c3
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Push/Fbns/Auth.php
@@ -0,0 +1,89 @@
+_instagram = $instagram;
+ $this->_deviceAuth = $this->_instagram->settings->getFbnsAuth();
+ }
+
+ /** {@inheritdoc} */
+ public function getClientId()
+ {
+ return $this->_deviceAuth->getClientId();
+ }
+
+ /** {@inheritdoc} */
+ public function getClientType()
+ {
+ return $this->_deviceAuth->getClientType();
+ }
+
+ /** {@inheritdoc} */
+ public function getUserId()
+ {
+ return $this->_deviceAuth->getUserId();
+ }
+
+ /** {@inheritdoc} */
+ public function getPassword()
+ {
+ return $this->_deviceAuth->getPassword();
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceId()
+ {
+ return $this->_deviceAuth->getDeviceId();
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceSecret()
+ {
+ return $this->_deviceAuth->getDeviceSecret();
+ }
+
+ /** {@inheritdoc} */
+ public function __toString()
+ {
+ return $this->_deviceAuth->__toString();
+ }
+
+ /**
+ * Update auth data.
+ *
+ * @param string $auth
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function update(
+ $auth)
+ {
+ /* @var DeviceAuth $auth */
+ $this->_deviceAuth->read($auth);
+ $this->_instagram->settings->setFbnsAuth($this->_deviceAuth);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Push/Notification.php b/instafeed/vendor/mgp25/instagram-php/src/Push/Notification.php
new file mode 100755
index 0000000..3709b09
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Push/Notification.php
@@ -0,0 +1,316 @@
+_json = $json;
+
+ if (isset($data->t)) {
+ $this->_title = (string) $data->t;
+ }
+ if (isset($data->m)) {
+ $this->_message = (string) $data->m;
+ }
+ if (isset($data->tt)) {
+ $this->_tickerText = (string) $data->tt;
+ }
+ $this->_actionPath = '';
+ $this->_actionParams = [];
+ if (isset($data->ig)) {
+ $this->_igAction = (string) $data->ig;
+ $parts = parse_url($this->_igAction);
+ if (isset($parts['path'])) {
+ $this->_actionPath = $parts['path'];
+ }
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $this->_actionParams);
+ }
+ }
+ if (isset($data->collapse_key)) {
+ $this->_collapseKey = (string) $data->collapse_key;
+ }
+ if (isset($data->i)) {
+ $this->_optionalImage = (string) $data->i;
+ }
+ if (isset($data->a)) {
+ $this->_optionalAvatarUrl = (string) $data->a;
+ }
+ if (isset($data->sound)) {
+ $this->_sound = (string) $data->sound;
+ }
+ if (isset($data->pi)) {
+ $this->_pushId = (string) $data->pi;
+ }
+ if (isset($data->c)) {
+ $this->_pushCategory = (string) $data->c;
+ }
+ if (isset($data->u)) {
+ $this->_intendedRecipientUserId = (string) $data->u;
+ }
+ if (isset($data->s)) {
+ $this->_sourceUserId = (string) $data->s;
+ }
+ if (isset($data->igo)) {
+ $this->_igActionOverride = (string) $data->igo;
+ }
+ if (isset($data->bc)) {
+ $this->_badgeCount = new BadgeCount((string) $data->bc);
+ }
+ if (isset($data->ia)) {
+ $this->_inAppActors = (string) $data->ia;
+ }
+ }
+
+ /**
+ * Notification constructor.
+ *
+ * @param string $json
+ */
+ public function __construct(
+ $json)
+ {
+ $this->_parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->_json;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->_title;
+ }
+
+ /**
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->_message;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTickerText()
+ {
+ return $this->_tickerText;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIgAction()
+ {
+ return $this->_igAction;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIgActionOverride()
+ {
+ return $this->_igActionOverride;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOptionalImage()
+ {
+ return $this->_optionalImage;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOptionalAvatarUrl()
+ {
+ return $this->_optionalAvatarUrl;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCollapseKey()
+ {
+ return $this->_collapseKey;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSound()
+ {
+ return $this->_sound;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPushId()
+ {
+ return $this->_pushId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPushCategory()
+ {
+ return $this->_pushCategory;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIntendedRecipientUserId()
+ {
+ return $this->_intendedRecipientUserId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSourceUserId()
+ {
+ return $this->_sourceUserId;
+ }
+
+ /**
+ * @return BadgeCount
+ */
+ public function getBadgeCount()
+ {
+ return $this->_badgeCount;
+ }
+
+ /**
+ * @return string
+ */
+ public function getInAppActors()
+ {
+ return $this->_inAppActors;
+ }
+
+ /**
+ * @return string
+ */
+ public function getActionPath()
+ {
+ return $this->_actionPath;
+ }
+
+ /**
+ * @return array
+ */
+ public function getActionParams()
+ {
+ return $this->_actionParams;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return mixed
+ */
+ public function getActionParam(
+ $key)
+ {
+ return isset($this->_actionParams[$key]) ? $this->_actionParams[$key] : null;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Push/Payload/BadgeCount.php b/instafeed/vendor/mgp25/instagram-php/src/Push/Payload/BadgeCount.php
new file mode 100755
index 0000000..005debd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Push/Payload/BadgeCount.php
@@ -0,0 +1,89 @@
+_json = $json;
+
+ if (isset($data->di)) {
+ $this->_direct = (int) $data->di;
+ }
+ if (isset($data->ds)) {
+ $this->_ds = (int) $data->ds;
+ }
+ if (isset($data->ac)) {
+ $this->_activities = (int) $data->ac;
+ }
+ }
+
+ /**
+ * Notification constructor.
+ *
+ * @param string $json
+ */
+ public function __construct(
+ $json)
+ {
+ $this->_parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->_json;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDirect()
+ {
+ return $this->_direct;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDs()
+ {
+ return $this->_ds;
+ }
+
+ /**
+ * @return int
+ */
+ public function getActivities()
+ {
+ return $this->_activities;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/React/Connector.php b/instafeed/vendor/mgp25/instagram-php/src/React/Connector.php
new file mode 100755
index 0000000..4c5ba1a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/React/Connector.php
@@ -0,0 +1,223 @@
+_instagram = $instagram;
+ $this->_loop = $loop;
+
+ $this->_connectors = [];
+ }
+
+ /** {@inheritdoc} */
+ public function connect(
+ $uri)
+ {
+ $uriObj = new Uri($uri);
+ $host = $this->_instagram->client->zeroRating()->rewrite($uriObj->getHost());
+ $uriObj = $uriObj->withHost($host);
+ if (!isset($this->_connectors[$host])) {
+ try {
+ $this->_connectors[$host] = $this->_getSecureConnector(
+ $this->_getSecureContext($this->_instagram->getVerifySSL()),
+ $this->_getProxyForHost($host, $this->_instagram->getProxy())
+ );
+ } catch (\Exception $e) {
+ return new RejectedPromise($e);
+ }
+ }
+ $niceUri = ltrim((string) $uriObj, '/');
+ /** @var PromiseInterface $promise */
+ $promise = $this->_connectors[$host]->connect($niceUri);
+
+ return $promise;
+ }
+
+ /**
+ * Create a secure connector for given configuration.
+ *
+ * @param array $secureContext
+ * @param string|null $proxyAddress
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return ConnectorInterface
+ */
+ protected function _getSecureConnector(
+ array $secureContext = [],
+ $proxyAddress = null)
+ {
+ $connector = new SocketConnector($this->_loop, [
+ 'tcp' => true,
+ 'tls' => false,
+ 'unix' => false,
+ 'dns' => true,
+ 'timeout' => true,
+ ]);
+
+ if ($proxyAddress !== null) {
+ $connector = $this->_wrapConnectorIntoProxy($connector, $proxyAddress, $secureContext);
+ }
+
+ return new SecureConnector($connector, $this->_loop, $secureContext);
+ }
+
+ /**
+ * Get a proxy address (if any) for the host based on the proxy config.
+ *
+ * @param string $host Host.
+ * @param mixed $proxyConfig Proxy config.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string|null
+ *
+ * @see http://docs.guzzlephp.org/en/stable/request-options.html#proxy
+ */
+ protected function _getProxyForHost(
+ $host,
+ $proxyConfig = null)
+ {
+ // Empty config => no proxy.
+ if (empty($proxyConfig)) {
+ return;
+ }
+
+ // Plain string => return it.
+ if (!is_array($proxyConfig)) {
+ return $proxyConfig;
+ }
+
+ // HTTP proxies do not have CONNECT method.
+ if (!isset($proxyConfig['https'])) {
+ throw new \InvalidArgumentException('No proxy with CONNECT method found.');
+ }
+
+ // Check exceptions.
+ if (isset($proxyConfig['no']) && \GuzzleHttp\is_host_in_noproxy($host, $proxyConfig['no'])) {
+ return;
+ }
+
+ return $proxyConfig['https'];
+ }
+
+ /**
+ * Parse given SSL certificate verification and return a secure context.
+ *
+ * @param mixed $config
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return array
+ *
+ * @see http://docs.guzzlephp.org/en/stable/request-options.html#verify
+ */
+ protected function _getSecureContext(
+ $config)
+ {
+ $context = [];
+ if ($config === true) {
+ // PHP 5.6 or greater will find the system cert by default. When
+ // < 5.6, use the Guzzle bundled cacert.
+ if (PHP_VERSION_ID < 50600) {
+ $context['cafile'] = \GuzzleHttp\default_ca_bundle();
+ }
+ } elseif (is_string($config)) {
+ $context['cafile'] = $config;
+ if (!is_file($config)) {
+ throw new \RuntimeException(sprintf('SSL CA bundle not found: "%s".', $config));
+ }
+ } elseif ($config === false) {
+ $context['verify_peer'] = false;
+ $context['verify_peer_name'] = false;
+
+ return $context;
+ } else {
+ throw new \InvalidArgumentException('Invalid verify request option.');
+ }
+ $context['verify_peer'] = true;
+ $context['verify_peer_name'] = true;
+ $context['allow_self_signed'] = false;
+
+ return $context;
+ }
+
+ /**
+ * Wrap the connector into a proxy one for given configuration.
+ *
+ * @param ConnectorInterface $connector
+ * @param string $proxyAddress
+ * @param array $secureContext
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return ConnectorInterface
+ */
+ protected function _wrapConnectorIntoProxy(
+ ConnectorInterface $connector,
+ $proxyAddress,
+ array $secureContext = [])
+ {
+ if (strpos($proxyAddress, '://') === false) {
+ $scheme = 'http';
+ } else {
+ $scheme = parse_url($proxyAddress, PHP_URL_SCHEME);
+ }
+ switch ($scheme) {
+ case 'socks':
+ case 'socks4':
+ case 'socks4a':
+ case 'socks5':
+ $connector = new SocksProxy($proxyAddress, $connector);
+ break;
+ case 'http':
+ $connector = new HttpConnectProxy($proxyAddress, $connector);
+ break;
+ case 'https':
+ $connector = new HttpConnectProxy($proxyAddress, new SecureConnector($connector, $this->_loop, $secureContext));
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unsupported proxy scheme: "%s".', $scheme));
+ }
+
+ return $connector;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/React/PersistentInterface.php b/instafeed/vendor/mgp25/instagram-php/src/React/PersistentInterface.php
new file mode 100755
index 0000000..8015fcd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/React/PersistentInterface.php
@@ -0,0 +1,49 @@
+_reconnectTimer !== null) {
+ if ($this->_reconnectTimer->isActive()) {
+ $this->getLogger()->info('Existing reconnect timer has been canceled.');
+ $this->_reconnectTimer->cancel();
+ }
+ $this->_reconnectTimer = null;
+ }
+ }
+
+ /**
+ * Set up a new reconnect timer with exponential backoff.
+ *
+ * @param callable $callback
+ */
+ protected function _setReconnectTimer(
+ callable $callback)
+ {
+ $this->_cancelReconnectTimer();
+ if (!$this->isActive()) {
+ return;
+ }
+ $this->_reconnectInterval = min(
+ $this->getMaxReconnectInterval(),
+ max(
+ $this->getMinReconnectInterval(),
+ $this->_reconnectInterval * 2
+ )
+ );
+ $this->getLogger()->info(sprintf('Setting up reconnect timer to %d seconds.', $this->_reconnectInterval));
+ $this->_reconnectTimer = $this->getLoop()->addTimer($this->_reconnectInterval, function () use ($callback) {
+ /** @var PromiseInterface $promise */
+ $promise = $callback();
+ $promise->then(
+ function () {
+ // Reset reconnect interval on successful connection attempt.
+ $this->_reconnectInterval = 0;
+ },
+ function () use ($callback) {
+ $this->_setReconnectTimer($callback);
+ }
+ );
+ });
+ }
+
+ /** {@inheritdoc} */
+ public function getMinReconnectInterval()
+ {
+ return PersistentInterface::MIN_RECONNECT_INTERVAL;
+ }
+
+ /** {@inheritdoc} */
+ public function getMaxReconnectInterval()
+ {
+ return PersistentInterface::MAX_RECONNECT_INTERVAL;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime.php
new file mode 100755
index 0000000..d967d14
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime.php
@@ -0,0 +1,495 @@
+_instagram = $instagram;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+ if ($this->_logger === null) {
+ $this->_logger = new NullLogger();
+ }
+
+ $this->_client = $this->_buildMqttClient();
+ $this->on('region-hint', function ($region) {
+ $this->_instagram->settings->set('datacenter', $region);
+ $this->_client->setAdditionalOption('datacenter', $region);
+ });
+ $this->on('zero-provision', function (ZeroProvisionEvent $event) {
+ if ($event->getZeroProvisionedTime() === null) {
+ return;
+ }
+ if ($event->getProductName() !== 'select') {
+ return;
+ }
+ // TODO check whether we already have a fresh token.
+
+ $this->_instagram->client->zeroRating()->reset();
+ $this->_instagram->internal->fetchZeroRatingToken('mqtt_token_push');
+ });
+ }
+
+ /**
+ * Build a new MQTT client.
+ *
+ * @return Realtime\Mqtt
+ */
+ protected function _buildMqttClient()
+ {
+ $additionalOptions = [
+ 'datacenter' => $this->_instagram->settings->get('datacenter'),
+ 'disable_presence' => (bool) $this->_instagram->settings->get('presence_disabled'),
+ ];
+
+ return new Realtime\Mqtt(
+ $this,
+ new Connector($this->_instagram, $this->_loop),
+ new Auth($this->_instagram),
+ $this->_instagram->device,
+ $this->_instagram,
+ $this->_loop,
+ $this->_logger,
+ $additionalOptions
+ );
+ }
+
+ /**
+ * Starts underlying client.
+ */
+ public function start()
+ {
+ $this->_client->start();
+ }
+
+ /**
+ * Stops underlying client.
+ */
+ public function stop()
+ {
+ $this->_client->stop();
+ }
+
+ /**
+ * Marks thread item as seen.
+ *
+ * @param string $threadId
+ * @param string $threadItemId
+ *
+ * @return bool
+ */
+ public function markDirectItemSeen(
+ $threadId,
+ $threadItemId)
+ {
+ try {
+ $this->_client->sendCommand(new DirectCommand\MarkSeen($threadId, $threadItemId));
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Indicate activity in thread.
+ *
+ * @param string $threadId
+ * @param bool $activityFlag
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function indicateActivityInDirectThread(
+ $threadId,
+ $activityFlag)
+ {
+ try {
+ $command = new DirectCommand\IndicateActivity($threadId, $activityFlag);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Sends text message to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $message Text message.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendTextToDirect(
+ $threadId,
+ $message,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendText($threadId, $message, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Sends like to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendLikeToDirect(
+ $threadId,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendLike($threadId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share an existing media post to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendPostToDirect(
+ $threadId,
+ $mediaId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendPost($threadId, $mediaId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share an existing story to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $storyId The story ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendStoryToDirect(
+ $threadId,
+ $storyId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendStory($threadId, $storyId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share a profile to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $userId Numerical UserPK ID.
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendProfileToDirect(
+ $threadId,
+ $userId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendProfile($threadId, $userId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share a location to a given direct thread.
+ *
+ * You must provide a valid Instagram location ID, which you get via other
+ * functions such as Location::search().
+ *
+ * @param string $threadId Thread ID.
+ * @param string $locationId Instagram's internal ID for the location.
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendLocationToDirect(
+ $threadId,
+ $locationId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendLocation($threadId, $locationId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share a hashtag to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $hashtag Hashtag to share.
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendHashtagToDirect(
+ $threadId,
+ $hashtag,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendHashtag($threadId, $hashtag, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Sends reaction to a given direct thread item.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread ID.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendReactionToDirect(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ DirectCommand\SendReaction::STATUS_CREATED,
+ $options
+ );
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Removes reaction to a given direct thread item.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread ID.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function deleteReactionFromDirect(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ DirectCommand\SendReaction::STATUS_DELETED,
+ $options
+ );
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Receive offline messages starting from the sequence ID.
+ *
+ * @param int $sequenceId
+ */
+ public function receiveOfflineMessages(
+ $sequenceId)
+ {
+ try {
+ $this->_client->sendCommand(new IrisSubscribe($sequenceId));
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+ }
+ }
+
+ /**
+ * Check whether sharing via the realtime client is enabled.
+ *
+ * @return bool
+ */
+ protected function _isRtcReshareEnabled()
+ {
+ return $this->_instagram->isExperimentEnabled('ig_android_rtc_reshare', 'is_rtc_reshare_enabled');
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/IndicateActivity.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/IndicateActivity.php
new file mode 100755
index 0000000..c7cc006
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/IndicateActivity.php
@@ -0,0 +1,35 @@
+_data['activity_status'] = $status ? '1' : '0';
+ }
+
+ /** {@inheritdoc} */
+ protected function _isClientContextRequired()
+ {
+ return true;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/MarkSeen.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/MarkSeen.php
new file mode 100755
index 0000000..8ca670f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/MarkSeen.php
@@ -0,0 +1,35 @@
+_data['item_id'] = $this->_validateThreadItemId($threadItemId);
+ }
+
+ /** {@inheritdoc} */
+ protected function _isClientContextRequired()
+ {
+ return false;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendHashtag.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendHashtag.php
new file mode 100755
index 0000000..eae2fcb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendHashtag.php
@@ -0,0 +1,42 @@
+_data['hashtag'] = $hashtag;
+ // Yeah, we need to send the hashtag twice.
+ $this->_data['item_id'] = $hashtag;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendItem.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendItem.php
new file mode 100755
index 0000000..ba25886
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendItem.php
@@ -0,0 +1,58 @@
+_getSupportedItemTypes(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported item type.', $itemType));
+ }
+ $this->_data['item_type'] = $itemType;
+ }
+
+ /** {@inheritdoc} */
+ protected function _isClientContextRequired()
+ {
+ return true;
+ }
+
+ /**
+ * Get the list of supported item types.
+ *
+ * @return array
+ */
+ protected function _getSupportedItemTypes()
+ {
+ return [
+ SendText::TYPE,
+ SendLike::TYPE,
+ SendReaction::TYPE,
+ SendPost::TYPE,
+ SendStory::TYPE,
+ SendProfile::TYPE,
+ SendLocation::TYPE,
+ SendHashtag::TYPE,
+ ];
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendLike.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendLike.php
new file mode 100755
index 0000000..a446d2e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendLike.php
@@ -0,0 +1,23 @@
+_data['venue_id'] = (string) $locationId;
+ // Yeah, we need to send the location ID twice.
+ $this->_data['item_id'] = (string) $locationId;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendPost.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendPost.php
new file mode 100755
index 0000000..a2295eb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendPost.php
@@ -0,0 +1,32 @@
+_data['media_id'] = (string) $mediaId;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendProfile.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendProfile.php
new file mode 100755
index 0000000..ef9dcae
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendProfile.php
@@ -0,0 +1,32 @@
+_data['profile_user_id'] = (string) $userId;
+ // Yeah, we need to send the user ID twice.
+ $this->_data['item_id'] = (string) $userId;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendReaction.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendReaction.php
new file mode 100755
index 0000000..af11354
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendReaction.php
@@ -0,0 +1,75 @@
+_data['item_id'] = $this->_validateThreadItemId($threadItemId);
+ $this->_data['node_type'] = 'item';
+
+ // Handle reaction type.
+ if (!in_array($reaction, $this->_getSupportedReactions(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction.', $reaction));
+ }
+ $this->_data['reaction_type'] = $reaction;
+
+ // Handle reaction status.
+ if (!in_array($status, $this->_getSupportedStatuses(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction status.', $status));
+ }
+ $this->_data['reaction_status'] = $status;
+ }
+
+ /**
+ * Get the list of supported reactions.
+ *
+ * @return array
+ */
+ protected function _getSupportedReactions()
+ {
+ return [
+ self::REACTION_LIKE,
+ ];
+ }
+
+ /**
+ * Get the list of supported statuses.
+ *
+ * @return array
+ */
+ protected function _getSupportedStatuses()
+ {
+ return [
+ self::STATUS_CREATED,
+ self::STATUS_DELETED,
+ ];
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendStory.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendStory.php
new file mode 100755
index 0000000..377b3e6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendStory.php
@@ -0,0 +1,32 @@
+_data['item_id'] = (string) $storyId;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendText.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendText.php
new file mode 100755
index 0000000..2386abd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendText.php
@@ -0,0 +1,34 @@
+_data['text'] = $text;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/ShareItem.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/ShareItem.php
new file mode 100755
index 0000000..e5a7a8d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/ShareItem.php
@@ -0,0 +1,30 @@
+_data['text'] = $options['text'];
+ } else {
+ $this->_data['text'] = '';
+ }
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/DirectCommand.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/DirectCommand.php
new file mode 100755
index 0000000..16a05d5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/DirectCommand.php
@@ -0,0 +1,191 @@
+_data = [];
+
+ // Handle action.
+ if (!in_array($action, $this->_getSupportedActions(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported action.', $action));
+ }
+ $this->_data['action'] = $action;
+
+ $this->_data['thread_id'] = $this->_validateThreadId($threadId);
+
+ // Handle client context.
+ if ($this->_isClientContextRequired()) {
+ if (!isset($options['client_context'])) {
+ $this->_data['client_context'] = Signatures::generateUUID();
+ } elseif (!Signatures::isValidUUID($options['client_context'])) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
+ } else {
+ $this->_data['client_context'] = $options['client_context'];
+ }
+ }
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::SEND_MESSAGE;
+ }
+
+ /** {@inheritdoc} */
+ public function getQosLevel()
+ {
+ return Mqtt\QosLevel::FIRE_AND_FORGET;
+ }
+
+ /** {@inheritdoc} */
+ public function jsonSerialize()
+ {
+ return $this->_reorderFieldsByWeight($this->_data, $this->_getFieldsWeights());
+ }
+
+ /**
+ * Get the client context.
+ *
+ * @return string|null
+ */
+ public function getClientContext()
+ {
+ return isset($this->_data['client_context']) ? $this->_data['client_context'] : null;
+ }
+
+ /**
+ * Check whether client_context param is required.
+ *
+ * @return bool
+ */
+ abstract protected function _isClientContextRequired();
+
+ /**
+ * Get the list of supported actions.
+ *
+ * @return array
+ */
+ protected function _getSupportedActions()
+ {
+ return [
+ Direct\SendItem::ACTION,
+ Direct\MarkSeen::ACTION,
+ Direct\IndicateActivity::ACTION,
+ ];
+ }
+
+ /**
+ * Validate given thread identifier.
+ *
+ * @param string $threadId
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ protected function _validateThreadId(
+ $threadId)
+ {
+ if (!ctype_digit($threadId) && (!is_int($threadId) || $threadId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $threadId));
+ }
+
+ return (string) $threadId;
+ }
+
+ /**
+ * Validate given thread item identifier.
+ *
+ * @param string $threadItemId
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ protected function _validateThreadItemId(
+ $threadItemId)
+ {
+ if (!ctype_digit($threadItemId) && (!is_int($threadItemId) || $threadItemId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread item identifier.', $threadItemId));
+ }
+
+ return (string) $threadItemId;
+ }
+
+ /**
+ * Reorders an array of fields by weights to simplify debugging.
+ *
+ * @param array $fields
+ * @param array $weights
+ *
+ * @return array
+ */
+ protected function _reorderFieldsByWeight(
+ array $fields,
+ array $weights)
+ {
+ uksort($fields, function ($a, $b) use ($weights) {
+ $a = isset($weights[$a]) ? $weights[$a] : PHP_INT_MAX;
+ $b = isset($weights[$b]) ? $weights[$b] : PHP_INT_MAX;
+ if ($a < $b) {
+ return -1;
+ }
+ if ($a > $b) {
+ return 1;
+ }
+
+ return 0;
+ });
+
+ return $fields;
+ }
+
+ /**
+ * Get weights for fields.
+ *
+ * @return array
+ */
+ protected function _getFieldsWeights()
+ {
+ return [
+ 'thread_id' => 10,
+ 'item_type' => 15,
+ 'text' => 20,
+ 'client_context' => 25,
+ 'activity_status' => 30,
+ 'reaction_type' => 35,
+ 'reaction_status' => 40,
+ 'item_id' => 45,
+ 'node_type' => 50,
+ 'action' => 55,
+ 'profile_user_id' => 60,
+ 'hashtag' => 65,
+ 'venue_id' => 70,
+ 'media_id' => 75,
+ ];
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/IrisSubscribe.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/IrisSubscribe.php
new file mode 100755
index 0000000..180b9c9
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/IrisSubscribe.php
@@ -0,0 +1,50 @@
+_sequenceId = $sequenceId;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::IRIS_SUB;
+ }
+
+ /** {@inheritdoc} */
+ public function getQosLevel()
+ {
+ return Mqtt\QosLevel::ACKNOWLEDGED_DELIVERY;
+ }
+
+ /** {@inheritdoc} */
+ public function jsonSerialize()
+ {
+ return [
+ 'seq_id' => $this->_sequenceId,
+ ];
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/UpdateSubscriptions.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/UpdateSubscriptions.php
new file mode 100755
index 0000000..5bb648c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Command/UpdateSubscriptions.php
@@ -0,0 +1,93 @@
+_topic = $topic;
+ $this->_subscribe = $subscribe;
+ $this->_unsubscribe = $unsubscribe;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return $this->_topic;
+ }
+
+ /** {@inheritdoc} */
+ public function getQosLevel()
+ {
+ return QosLevel::ACKNOWLEDGED_DELIVERY;
+ }
+
+ /**
+ * Prepare the subscriptions list.
+ *
+ * @param array $subscriptions
+ *
+ * @return array
+ */
+ private function _prepareSubscriptions(
+ array $subscriptions)
+ {
+ $result = [];
+ foreach ($subscriptions as $subscription) {
+ $result[] = (string) $subscription;
+ }
+ usort($result, function ($a, $b) {
+ $hashA = Utils::hashCode($a);
+ $hashB = Utils::hashCode($b);
+
+ if ($hashA > $hashB) {
+ return 1;
+ }
+ if ($hashA < $hashB) {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ return $result;
+ }
+
+ /** {@inheritdoc} */
+ public function jsonSerialize()
+ {
+ $result = [];
+ if (count($this->_subscribe)) {
+ $result['sub'] = $this->_prepareSubscriptions($this->_subscribe);
+ }
+ if (count($this->_unsubscribe)) {
+ $result['unsub'] = $this->_prepareSubscriptions($this->_unsubscribe);
+ }
+
+ return $result;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/CommandInterface.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/CommandInterface.php
new file mode 100755
index 0000000..4dbe91e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/CommandInterface.php
@@ -0,0 +1,20 @@
+_target = $target;
+ }
+
+ /**
+ * Checks if target has at least one listener for specific event.
+ *
+ * @param string $event
+ *
+ * @return bool
+ */
+ protected function _hasListeners(
+ $event)
+ {
+ return (bool) count($this->_target->listeners($event));
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/DirectHandler.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/DirectHandler.php
new file mode 100755
index 0000000..f6c52e0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/DirectHandler.php
@@ -0,0 +1,543 @@
+[^/]+)$#D';
+ const ITEM_REGEXP = '#^/direct_v2/threads/(?[^/]+)/items/(?[^/]+)$#D';
+ const ACTIVITY_REGEXP = '#^/direct_v2/threads/(?[^/]+)/activity_indicator_id/(?[^/]+)$#D';
+ const STORY_REGEXP = '#^/direct_v2/visual_threads/(?[^/]+)/items/(?[^/]+)$#D';
+ const SEEN_REGEXP = '#^/direct_v2/threads/(?[^/]+)/participants/(?[^/]+)/has_seen$#D';
+ const SCREENSHOT_REGEXP = '#^/direct_v2/visual_thread/(?[^/]+)/screenshot$#D';
+ const BADGE_REGEXP = '#^/direct_v2/visual_action_badge/(?[^/]+)$#D';
+
+ /** {@inheritdoc} */
+ public function handleMessage(
+ Message $message)
+ {
+ $data = $message->getData();
+
+ if (isset($data['event'])) {
+ $this->_processEvent($data);
+ } elseif (isset($data['action'])) {
+ $this->_processAction($data);
+ } else {
+ throw new HandlerException('Invalid message (both event and action are missing).');
+ }
+ }
+
+ /**
+ * Process incoming event.
+ *
+ * @param array $message
+ *
+ * @throws HandlerException
+ */
+ protected function _processEvent(
+ array $message)
+ {
+ if ($message['event'] === RealtimeEvent::PATCH) {
+ $event = new PatchEvent($message);
+ foreach ($event->getData() as $op) {
+ $this->_handlePatchOp($op);
+ }
+ } else {
+ throw new HandlerException(sprintf('Unknown event type "%s".', $message['event']));
+ }
+ }
+
+ /**
+ * Process incoming action.
+ *
+ * @param array $message
+ *
+ * @throws HandlerException
+ */
+ protected function _processAction(
+ array $message)
+ {
+ if ($message['action'] === RealtimeAction::ACK) {
+ $this->_target->emit('client-context-ack', [new AckAction($message)]);
+ } else {
+ throw new HandlerException(sprintf('Unknown action type "%s".', $message['action']));
+ }
+ }
+
+ /**
+ * Patch op handler.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handlePatchOp(
+ PatchEventOp $op)
+ {
+ switch ($op->getOp()) {
+ case PatchEventOp::ADD:
+ $this->_handleAdd($op);
+ break;
+ case PatchEventOp::REPLACE:
+ $this->_handleReplace($op);
+ break;
+ case PatchEventOp::REMOVE:
+ $this->_handleRemove($op);
+ break;
+ case PatchEventOp::NOTIFY:
+ $this->_handleNotify($op);
+ break;
+ default:
+ throw new HandlerException(sprintf('Unknown patch op "%s".', $op->getOp()));
+ }
+ }
+
+ /**
+ * Handler for the ADD op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleAdd(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2/threads')) {
+ if (strpos($path, 'activity_indicator_id') === false) {
+ $this->_upsertThreadItem($op, true);
+ } else {
+ $this->_updateThreadActivity($op);
+ }
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/inbox/threads')) {
+ $this->_upsertThread($op, true);
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/visual_threads')) {
+ $this->_updateDirectStory($op);
+ } else {
+ throw new HandlerException(sprintf('Unsupported ADD path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for the REPLACE op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleReplace(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2/threads')) {
+ if ($this->_pathEndsWith($path, 'has_seen')) {
+ $this->_updateSeen($op);
+ } else {
+ $this->_upsertThreadItem($op, false);
+ }
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/inbox/threads')) {
+ $this->_upsertThread($op, false);
+ } elseif ($this->_pathEndsWith($path, 'unseen_count')) {
+ if ($this->_pathStartsWith($path, '/direct_v2/inbox')) {
+ $this->_updateUnseenCount('inbox', $op);
+ } else {
+ $this->_updateUnseenCount('visual_inbox', $op);
+ }
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/visual_action_badge')) {
+ $this->_directStoryAction($op);
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/visual_thread')) {
+ if ($this->_pathEndsWith($path, 'screenshot')) {
+ $this->_notifyDirectStoryScreenshot($op);
+ } else {
+ $this->_createDirectStory($op);
+ }
+ } else {
+ throw new HandlerException(sprintf('Unsupported REPLACE path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for the REMOVE op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleRemove(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2')) {
+ $this->_removeThreadItem($op);
+ } else {
+ throw new HandlerException(sprintf('Unsupported REMOVE path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for NOTIFY op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleNotify(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2/threads')) {
+ $this->_notifyThread($op);
+ } else {
+ throw new HandlerException(sprintf('Unsupported NOTIFY path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for thread creation/modification.
+ *
+ * @param PatchEventOp $op
+ * @param bool $insert
+ *
+ * @throws HandlerException
+ */
+ protected function _upsertThread(
+ PatchEventOp $op,
+ $insert)
+ {
+ $event = $insert ? 'thread-created' : 'thread-updated';
+ if (!$this->_hasListeners($event)) {
+ return;
+ }
+
+ if (!preg_match(self::THREAD_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit($event, [$matches['thread_id'], new DirectThread($json)]);
+ }
+
+ /**
+ * Handler for thread item creation/modification.
+ *
+ * @param PatchEventOp $op
+ * @param bool $insert
+ *
+ * @throws HandlerException
+ */
+ protected function _upsertThreadItem(
+ PatchEventOp $op,
+ $insert)
+ {
+ $event = $insert ? 'thread-item-created' : 'thread-item-updated';
+ if (!$this->_hasListeners($event)) {
+ return;
+ }
+
+ if (!preg_match(self::ITEM_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread item regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread item JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit($event, [$matches['thread_id'], $matches['item_id'], new DirectThreadItem($json)]);
+ }
+
+ /**
+ * Handler for thread activity indicator.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _updateThreadActivity(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-activity')) {
+ return;
+ }
+
+ if (!preg_match(self::ACTIVITY_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread activity regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread activity JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit('thread-activity', [$matches['thread_id'], new ThreadActivity($json)]);
+ }
+
+ /**
+ * Handler for story update.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _updateDirectStory(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-updated')) {
+ return;
+ }
+
+ if (!preg_match(self::STORY_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match story item regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode story item JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'direct-story-updated',
+ [$matches['thread_id'], $matches['item_id'], new DirectThreadItem($json)]
+ );
+ }
+
+ /**
+ * Handler for unseen count.
+ *
+ * @param string $inbox
+ * @param PatchEventOp $op
+ */
+ protected function _updateUnseenCount(
+ $inbox,
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('unseen-count-update')) {
+ return;
+ }
+
+ $payload = new DirectSeenItemPayload([
+ 'count' => (int) $op->getValue(),
+ 'timestamp' => $op->getTs(),
+ ]);
+ $this->_target->emit('unseen-count-update', [$inbox, $payload]);
+ }
+
+ /**
+ * Handler for thread seen indicator.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _updateSeen(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-seen')) {
+ return;
+ }
+
+ if (!preg_match(self::SEEN_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread seen regexp.', $op->getPath()));
+ }
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread seen JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'thread-seen',
+ [$matches['thread_id'], $matches['user_id'], new DirectThreadLastSeenAt($json)]
+ );
+ }
+
+ /**
+ * Handler for screenshot notification.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _notifyDirectStoryScreenshot(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-screenshot')) {
+ return;
+ }
+
+ if (!preg_match(self::SCREENSHOT_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread screenshot regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit('direct-story-screenshot', [$matches['thread_id'], new StoryScreenshot($json)]);
+ }
+
+ /**
+ * Handler for direct story creation.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _createDirectStory(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-created')) {
+ return;
+ }
+
+ if ($op->getPath() !== '/direct_v2/visual_thread/create') {
+ throw new HandlerException(sprintf('Path "%s" does not match story create path.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode inbox JSON: %s.', json_last_error_msg()));
+ }
+
+ $inbox = new DirectInbox($json);
+ $allThreads = $inbox->getThreads();
+ if ($allThreads === null || !count($allThreads)) {
+ return;
+ }
+ $this->_target->emit('direct-story-created', [reset($allThreads)]);
+ }
+
+ /**
+ * Handler for story action.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _directStoryAction(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-action')) {
+ return;
+ }
+
+ if (!preg_match(self::BADGE_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match story action regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode story action JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'direct-story-action',
+ [$matches['thread_id'], new ActionBadge($json)]
+ );
+ }
+
+ /**
+ * Handler for thread item removal.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _removeThreadItem(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-item-removed')) {
+ return;
+ }
+
+ if (!preg_match(self::ITEM_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread item regexp.', $op->getPath()));
+ }
+
+ $this->_target->emit('thread-item-removed', [$matches['thread_id'], $matches['item_id']]);
+ }
+
+ /**
+ * Handler for thread notify.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _notifyThread(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-notify')) {
+ return;
+ }
+
+ if (!preg_match(self::ITEM_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread item regexp.', $op->getPath()));
+ }
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread item notify JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'thread-notify',
+ [$matches['thread_id'], $matches['item_id'], new ThreadAction($json)]
+ );
+ }
+
+ /**
+ * Checks if the path starts with specified substring.
+ *
+ * @param string $path
+ * @param string $string
+ *
+ * @return bool
+ */
+ protected function _pathStartsWith(
+ $path,
+ $string)
+ {
+ return strncmp($path, $string, strlen($string)) === 0;
+ }
+
+ /**
+ * Checks if the path ends with specified substring.
+ *
+ * @param string $path
+ * @param string $string
+ *
+ * @return bool
+ */
+ protected function _pathEndsWith(
+ $path,
+ $string)
+ {
+ $length = strlen($string);
+
+ return substr_compare($path, $string, strlen($path) - $length, $length) === 0;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/HandlerException.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/HandlerException.php
new file mode 100755
index 0000000..66819eb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/HandlerException.php
@@ -0,0 +1,7 @@
+getData());
+ if (!$iris->isSucceeded()) {
+ throw new HandlerException(sprintf(
+ 'Failed to subscribe to Iris (%d): %s.',
+ $iris->getErrorType(),
+ $iris->getErrorMessage()
+ ));
+ }
+ $this->_target->emit('iris-subscribed', [$iris]);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/LiveHandler.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/LiveHandler.php
new file mode 100755
index 0000000..aa597bb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/LiveHandler.php
@@ -0,0 +1,81 @@
+getData();
+
+ if (isset($data['event'])) {
+ $this->_processEvent($data);
+ } else {
+ throw new HandlerException('Invalid message (event type is missing).');
+ }
+ }
+
+ /**
+ * Process incoming event.
+ *
+ * @param array $message
+ *
+ * @throws HandlerException
+ */
+ protected function _processEvent(
+ array $message)
+ {
+ if ($message['event'] === RealtimeEvent::PATCH) {
+ $event = new PatchEvent($message);
+ foreach ($event->getData() as $op) {
+ $this->_handlePatchOp($op);
+ }
+ } else {
+ throw new HandlerException(sprintf('Unknown event type "%s".', $message['event']));
+ }
+ }
+
+ /**
+ * Handler for live broadcast creation/removal.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handlePatchOp(
+ PatchEventOp $op)
+ {
+ switch ($op->getOp()) {
+ case PatchEventOp::ADD:
+ $event = 'live-started';
+ break;
+ case PatchEventOp::REMOVE:
+ $event = 'live-stopped';
+ break;
+ default:
+ throw new HandlerException(sprintf('Unsupported live broadcast op: "%s".', $op->getOp()));
+ }
+ if (!$this->_hasListeners($event)) {
+ return;
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode live broadcast JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit($event, [new LiveBroadcast($json)]);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/PresenceHandler.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/PresenceHandler.php
new file mode 100755
index 0000000..d0925df
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/PresenceHandler.php
@@ -0,0 +1,25 @@
+getData();
+ if (!isset($data['presence_event']) || !is_array($data['presence_event'])) {
+ throw new HandlerException('Invalid presence (event data is missing).');
+ }
+ $presence = new UserPresence($data['presence_event']);
+ $this->_target->emit('presence', [$presence]);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/RegionHintHandler.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/RegionHintHandler.php
new file mode 100755
index 0000000..4c529a2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/RegionHintHandler.php
@@ -0,0 +1,22 @@
+getData();
+ if ($region === null || $region === '') {
+ throw new HandlerException('Invalid region hint.');
+ }
+ $this->_target->emit('region-hint', [$message->getData()]);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/ZeroProvisionHandler.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/ZeroProvisionHandler.php
new file mode 100755
index 0000000..009bd59
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Handler/ZeroProvisionHandler.php
@@ -0,0 +1,24 @@
+getData();
+ if (!isset($data['zero_product_provisioning_event']) || !is_array($data['zero_product_provisioning_event'])) {
+ throw new HandlerException('Invalid zero provision (event data is missing).');
+ }
+ $provision = new ZeroProvisionEvent($data['zero_product_provisioning_event']);
+ $this->_target->emit('zero-provision', [$provision]);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/HandlerInterface.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/HandlerInterface.php
new file mode 100755
index 0000000..d7cd9b9
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/HandlerInterface.php
@@ -0,0 +1,18 @@
+_module = $module;
+ $this->_data = $payload;
+ }
+
+ /**
+ * @return string
+ */
+ public function getModule()
+ {
+ return $this->_module;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getData()
+ {
+ return $this->_data;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt.php
new file mode 100755
index 0000000..1a29a3e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt.php
@@ -0,0 +1,803 @@
+_target = $target;
+ $this->_connector = $connector;
+ $this->_auth = $auth;
+ $this->_device = $device;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+ $this->_additionalOptions = $additionalOptions;
+
+ $this->_subscriptions = [];
+
+ $this->_loadExperiments($experiments);
+ $this->_initSubscriptions();
+
+ $this->_shutdown = false;
+ $this->_client = $this->_getClient();
+
+ $this->_parsers = [
+ Mqtt\Topics::PUBSUB => new Parser\SkywalkerParser(),
+ Mqtt\Topics::SEND_MESSAGE_RESPONSE => new Parser\JsonParser(Handler\DirectHandler::MODULE),
+ Mqtt\Topics::IRIS_SUB_RESPONSE => new Parser\JsonParser(Handler\IrisHandler::MODULE),
+ Mqtt\Topics::MESSAGE_SYNC => new Parser\IrisParser(),
+ Mqtt\Topics::REALTIME_SUB => new Parser\GraphQlParser(),
+ Mqtt\Topics::GRAPHQL => new Parser\GraphQlParser(),
+ Mqtt\Topics::REGION_HINT => new Parser\RegionHintParser(),
+ ];
+ $this->_handlers = [
+ Handler\DirectHandler::MODULE => new Handler\DirectHandler($this->_target),
+ Handler\LiveHandler::MODULE => new Handler\LiveHandler($this->_target),
+ Handler\IrisHandler::MODULE => new Handler\IrisHandler($this->_target),
+ Handler\PresenceHandler::MODULE => new Handler\PresenceHandler($this->_target),
+ Handler\RegionHintHandler::MODULE => new Handler\RegionHintHandler($this->_target),
+ Handler\ZeroProvisionHandler::MODULE => new Handler\ZeroProvisionHandler($this->_target),
+ ];
+ }
+
+ /** {@inheritdoc} */
+ public function getLoop()
+ {
+ return $this->_loop;
+ }
+
+ /** {@inheritdoc} */
+ public function isActive()
+ {
+ return !$this->_shutdown;
+ }
+
+ /**
+ * Add a subscription to the list.
+ *
+ * @param SubscriptionInterface $subscription
+ */
+ public function addSubscription(
+ SubscriptionInterface $subscription)
+ {
+ $this->_doAddSubscription($subscription, true);
+ }
+
+ /**
+ * Remove a subscription from the list.
+ *
+ * @param SubscriptionInterface $subscription
+ */
+ public function removeSubscription(
+ SubscriptionInterface $subscription)
+ {
+ $this->_doRemoveSubscription($subscription, true);
+ }
+
+ /**
+ * Set an additional option.
+ *
+ * @param string $option
+ * @param mixed $value
+ */
+ public function setAdditionalOption(
+ $option,
+ $value)
+ {
+ $this->_additionalOptions[$option] = $value;
+ }
+
+ /**
+ * Add a subscription to the list and send a command (optional).
+ *
+ * @param SubscriptionInterface $subscription
+ * @param bool $sendCommand
+ */
+ protected function _doAddSubscription(
+ SubscriptionInterface $subscription,
+ $sendCommand)
+ {
+ $topic = $subscription->getTopic();
+ $id = $subscription->getId();
+
+ // Check whether we already subscribed to it.
+ if (isset($this->_subscriptions[$topic][$id])) {
+ return;
+ }
+
+ // Add the subscription to the list.
+ if (!isset($this->_subscriptions[$topic])) {
+ $this->_subscriptions[$topic] = [];
+ }
+ $this->_subscriptions[$topic][$id] = $subscription;
+
+ // Send a command when needed.
+ if (!$sendCommand || $this->_isConnected()) {
+ return;
+ }
+
+ $this->_updateSubscriptions($topic, [$subscription], []);
+ }
+
+ /**
+ * Remove a subscription from the list and send a command (optional).
+ *
+ * @param SubscriptionInterface $subscription
+ * @param bool $sendCommand
+ */
+ protected function _doRemoveSubscription(
+ SubscriptionInterface $subscription,
+ $sendCommand)
+ {
+ $topic = $subscription->getTopic();
+ $id = $subscription->getId();
+
+ // Check whether we are subscribed to it.
+ if (!isset($this->_subscriptions[$topic][$id])) {
+ return;
+ }
+
+ // Remove the subscription from the list.
+ unset($this->_subscriptions[$topic][$id]);
+ if (!count($this->_subscriptions[$topic])) {
+ unset($this->_subscriptions[$topic]);
+ }
+
+ // Send a command when needed.
+ if (!$sendCommand || $this->_isConnected()) {
+ return;
+ }
+
+ $this->_updateSubscriptions($topic, [], [$subscription]);
+ }
+
+ /**
+ * Cancel a keepalive timer (if any).
+ */
+ protected function _cancelKeepaliveTimer()
+ {
+ if ($this->_keepaliveTimer !== null) {
+ if ($this->_keepaliveTimer->isActive()) {
+ $this->_logger->debug('Existing keepalive timer has been canceled.');
+ $this->_keepaliveTimer->cancel();
+ }
+ $this->_keepaliveTimer = null;
+ }
+ }
+
+ /**
+ * Set up a new keepalive timer.
+ */
+ protected function _setKeepaliveTimer()
+ {
+ $this->_cancelKeepaliveTimer();
+ $keepaliveInterval = Mqtt\Config::MQTT_KEEPALIVE;
+ $this->_logger->debug(sprintf('Setting up keepalive timer to %d seconds', $keepaliveInterval));
+ $this->_keepaliveTimer = $this->_loop->addTimer($keepaliveInterval, function () {
+ $this->_logger->info('Keepalive timer has been fired.');
+ $this->_disconnect();
+ });
+ }
+
+ /**
+ * Try to establish a connection.
+ */
+ protected function _connect()
+ {
+ $this->_setReconnectTimer(function () {
+ $this->_logger->info(sprintf('Connecting to %s:%d...', Mqtt\Config::DEFAULT_HOST, Mqtt\Config::DEFAULT_PORT));
+
+ $connection = new DefaultConnection(
+ $this->_getMqttUsername(),
+ $this->_auth->getPassword(),
+ null,
+ $this->_auth->getClientId(),
+ Mqtt\Config::MQTT_KEEPALIVE,
+ Mqtt\Config::MQTT_VERSION,
+ true
+ );
+
+ return $this->_client->connect(Mqtt\Config::DEFAULT_HOST, Mqtt\Config::DEFAULT_PORT, $connection, Mqtt\Config::CONNECTION_TIMEOUT);
+ });
+ }
+
+ /**
+ * Perform first connection in a row.
+ */
+ public function start()
+ {
+ $this->_shutdown = false;
+ $this->_reconnectInterval = 0;
+ $this->_connect();
+ }
+
+ /**
+ * Whether connection is established.
+ *
+ * @return bool
+ */
+ protected function _isConnected()
+ {
+ return $this->_client->isConnected();
+ }
+
+ /**
+ * Disconnect from server.
+ */
+ protected function _disconnect()
+ {
+ $this->_cancelKeepaliveTimer();
+ $this->_client->disconnect();
+ }
+
+ /**
+ * Proxy for _disconnect().
+ */
+ public function stop()
+ {
+ $this->_logger->info('Shutting down...');
+ $this->_shutdown = true;
+ $this->_cancelReconnectTimer();
+ $this->_disconnect();
+ }
+
+ /**
+ * Send the command.
+ *
+ * @param CommandInterface $command
+ *
+ * @throws \LogicException
+ */
+ public function sendCommand(
+ CommandInterface $command)
+ {
+ if (!$this->_isConnected()) {
+ throw new \LogicException('Tried to send the command while offline.');
+ }
+
+ $this->_publish(
+ $command->getTopic(),
+ json_encode($command, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
+ $command->getQosLevel()
+ );
+ }
+
+ /**
+ * Load the experiments.
+ *
+ * @param ExperimentsInterface $experiments
+ */
+ protected function _loadExperiments(
+ ExperimentsInterface $experiments)
+ {
+ $this->_experiments = $experiments;
+
+ // Direct features.
+ $this->_irisEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_realtime_iris',
+ 'is_direct_over_iris_enabled'
+ );
+ $this->_msgTypeBlacklist = $experiments->getExperimentParam(
+ 'ig_android_realtime_iris',
+ 'pubsub_msg_type_blacklist',
+ 'null'
+ );
+
+ // Live features.
+ $this->_mqttLiveEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_skywalker_live_event_start_end',
+ 'is_enabled'
+ );
+
+ // GraphQL features.
+ $this->_graphQlTypingEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_gqls_typing_indicator',
+ 'is_enabled'
+ );
+
+ // Presence features.
+ $this->_inboxPresenceEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_direct_inbox_presence',
+ 'is_enabled'
+ );
+ $this->_threadPresenceEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_direct_thread_presence',
+ 'is_enabled'
+ );
+ }
+
+ protected function _initSubscriptions()
+ {
+ $subscriptionId = Signatures::generateUUID();
+ // Set up PubSub topics.
+ $liveSubscription = new LiveSubscription($this->_auth->getUserId());
+ if ($this->_mqttLiveEnabled) {
+ $this->_doAddSubscription($liveSubscription, false);
+ } else {
+ $this->_doRemoveSubscription($liveSubscription, false);
+ }
+ // Direct subscription is always enabled.
+ $this->_doAddSubscription(new DirectSubscription($this->_auth->getUserId()), false);
+
+ // Set up GraphQL topics.
+ $zeroProvisionSubscription = new ZeroProvisionSubscription($this->_auth->getDeviceId());
+ $this->_doAddSubscription($zeroProvisionSubscription, false);
+ $graphQlTypingSubscription = new DirectTypingSubscription($this->_auth->getUserId());
+ if ($this->_graphQlTypingEnabled) {
+ $this->_doAddSubscription($graphQlTypingSubscription, false);
+ } else {
+ $this->_doRemoveSubscription($graphQlTypingSubscription, false);
+ }
+ $appPresenceSubscription = new AppPresenceSubscription($subscriptionId);
+ if (($this->_inboxPresenceEnabled || $this->_threadPresenceEnabled) && empty($this->_additionalOptions['disable_presence'])) {
+ $this->_doAddSubscription($appPresenceSubscription, false);
+ } else {
+ $this->_doRemoveSubscription($appPresenceSubscription, false);
+ }
+ }
+
+ /**
+ * Returns application specific info.
+ *
+ * @return array
+ */
+ protected function _getAppSpecificInfo()
+ {
+ $result = [
+ 'platform' => Constants::PLATFORM,
+ 'app_version' => Constants::IG_VERSION,
+ 'capabilities' => Constants::X_IG_Capabilities,
+ ];
+ // PubSub message type blacklist.
+ $msgTypeBlacklist = '';
+ if ($this->_msgTypeBlacklist !== null && $this->_msgTypeBlacklist !== '') {
+ $msgTypeBlacklist = $this->_msgTypeBlacklist;
+ }
+ if ($this->_graphQlTypingEnabled) {
+ if ($msgTypeBlacklist !== '') {
+ $msgTypeBlacklist .= ', typing_type';
+ } else {
+ $msgTypeBlacklist = 'typing_type';
+ }
+ }
+ if ($msgTypeBlacklist !== '') {
+ $result['pubsub_msg_type_blacklist'] = $msgTypeBlacklist;
+ }
+ // Everclear subscriptions.
+ $everclearSubscriptions = [];
+ if ($this->_inboxPresenceEnabled) {
+ $everclearSubscriptions[AppPresenceSubscription::ID] = AppPresenceSubscription::QUERY;
+ }
+ if (count($everclearSubscriptions)) {
+ $result['everclear_subscriptions'] = json_encode($everclearSubscriptions);
+ }
+ // Maintaining the order.
+ $result['User-Agent'] = $this->_device->getUserAgent();
+ $result['ig_mqtt_route'] = 'django';
+ // Accept-Language must be the last one.
+ $result['Accept-Language'] = Constants::ACCEPT_LANGUAGE;
+
+ return $result;
+ }
+
+ /**
+ * Get a list of topics to subscribe at connect.
+ *
+ * @return string[]
+ */
+ protected function _getSubscribeTopics()
+ {
+ $topics = [
+ Mqtt\Topics::PUBSUB,
+ ];
+ $topics[] = Mqtt\Topics::REGION_HINT;
+ if ($this->_graphQlTypingEnabled) {
+ $topics[] = Mqtt\Topics::REALTIME_SUB;
+ }
+ $topics[] = Mqtt\Topics::SEND_MESSAGE_RESPONSE;
+ if ($this->_irisEnabled) {
+ $topics[] = Mqtt\Topics::IRIS_SUB_RESPONSE;
+ $topics[] = Mqtt\Topics::MESSAGE_SYNC;
+ }
+
+ return $topics;
+ }
+
+ /**
+ * Returns username for MQTT connection.
+ *
+ * @return string
+ */
+ protected function _getMqttUsername()
+ {
+ // Session ID is uptime in msec.
+ $sessionId = (microtime(true) - strtotime('Last Monday')) * 1000;
+ // Random buster-string to avoid clashing with other data.
+ $randNum = mt_rand(1000000, 9999999);
+ $fields = [
+ // USER_ID
+ 'u' => '%ACCOUNT_ID_'.$randNum.'%',
+ // AGENT
+ 'a' => $this->_device->getFbUserAgent(Constants::INSTAGRAM_APPLICATION_NAME),
+ // CAPABILITIES
+ 'cp' => Mqtt\Capabilities::DEFAULT_SET,
+ // CLIENT_MQTT_SESSION_ID
+ 'mqtt_sid' => '%SESSION_ID_'.$randNum.'%',
+ // NETWORK_TYPE
+ 'nwt' => Mqtt\Config::NETWORK_TYPE_WIFI,
+ // NETWORK_SUBTYPE
+ 'nwst' => 0,
+ // MAKE_USER_AVAILABLE_IN_FOREGROUND
+ 'chat_on' => false,
+ // NO_AUTOMATIC_FOREGROUND
+ 'no_auto_fg' => true,
+ // DEVICE_ID
+ 'd' => $this->_auth->getDeviceId(),
+ // DEVICE_SECRET
+ 'ds' => $this->_auth->getDeviceSecret(),
+ // INITIAL_FOREGROUND_STATE
+ 'fg' => false,
+ // ENDPOINT_CAPABILITIES
+ 'ecp' => 0,
+ // PUBLISH_FORMAT
+ 'pf' => Mqtt\Config::PUBLISH_FORMAT,
+ // CLIENT_TYPE
+ 'ct' => Mqtt\Config::CLIENT_TYPE,
+ // APP_ID
+ 'aid' => Constants::FACEBOOK_ANALYTICS_APPLICATION_ID,
+ // SUBSCRIBE_TOPICS
+ 'st' => $this->_getSubscribeTopics(),
+ ];
+ // REGION_PREFERENCE
+ if (!empty($this->_additionalOptions['datacenter'])) {
+ $fields['dc'] = $this->_additionalOptions['datacenter'];
+ }
+ // Maintaining the order.
+ $fields['clientStack'] = 3;
+ $fields['app_specific_info'] = $this->_getAppSpecificInfo();
+ // Account and session IDs must be a number, but int size is platform dependent in PHP,
+ // so we are replacing JSON strings with plain strings in JSON encoded data.
+ $result = strtr(json_encode($fields), [
+ json_encode('%ACCOUNT_ID_'.$randNum.'%') => $this->_auth->getUserId(),
+ json_encode('%SESSION_ID_'.$randNum.'%') => round($sessionId),
+ ]);
+
+ return $result;
+ }
+
+ /**
+ * Create a new MQTT client.
+ *
+ * @return ReactMqttClient
+ */
+ protected function _getClient()
+ {
+ $client = new ReactMqttClient($this->_connector, $this->_loop, null, new Mqtt\StreamParser());
+
+ $client->on('error', function (\Exception $e) {
+ $this->_logger->error($e->getMessage());
+ });
+ $client->on('warning', function (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+ });
+ $client->on('open', function () {
+ $this->_logger->info('Connection has been established');
+ });
+ $client->on('close', function () {
+ $this->_logger->info('Connection has been closed');
+ $this->_cancelKeepaliveTimer();
+ if (!$this->_reconnectInterval) {
+ $this->_connect();
+ }
+ });
+ $client->on('connect', function () {
+ $this->_logger->info('Connected to a broker');
+ $this->_setKeepaliveTimer();
+ $this->_restoreAllSubscriptions();
+ });
+ $client->on('ping', function () {
+ $this->_logger->debug('Ping flow completed');
+ $this->_setKeepaliveTimer();
+ });
+ $client->on('publish', function () {
+ $this->_logger->debug('Publish flow completed');
+ $this->_setKeepaliveTimer();
+ });
+ $client->on('message', function (MqttMessage $message) {
+ $this->_setKeepaliveTimer();
+ $this->_onReceive($message);
+ });
+ $client->on('disconnect', function () {
+ $this->_logger->info('Disconnected from broker');
+ });
+
+ return $client;
+ }
+
+ /**
+ * Mass update subscriptions statuses.
+ *
+ * @param string $topic
+ * @param SubscriptionInterface[] $subscribe
+ * @param SubscriptionInterface[] $unsubscribe
+ */
+ protected function _updateSubscriptions(
+ $topic,
+ array $subscribe,
+ array $unsubscribe)
+ {
+ if (count($subscribe)) {
+ $this->_logger->info(sprintf('Subscribing to %s topics %s', $topic, implode(', ', $subscribe)));
+ }
+ if (count($unsubscribe)) {
+ $this->_logger->info(sprintf('Unsubscribing from %s topics %s', $topic, implode(', ', $subscribe)));
+ }
+
+ try {
+ $this->sendCommand(new UpdateSubscriptions($topic, $subscribe, $unsubscribe));
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+ }
+ }
+
+ /**
+ * Subscribe to all topics.
+ */
+ protected function _restoreAllSubscriptions()
+ {
+ foreach ($this->_subscriptions as $topic => $subscriptions) {
+ $this->_updateSubscriptions($topic, $subscriptions, []);
+ }
+ }
+
+ /**
+ * Unsubscribe from all topics.
+ */
+ protected function _removeAllSubscriptions()
+ {
+ foreach ($this->_subscriptions as $topic => $subscriptions) {
+ $this->_updateSubscriptions($topic, [], $subscriptions);
+ }
+ }
+
+ /**
+ * Maps human readable topic to its identifier.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ protected function _mapTopic(
+ $topic)
+ {
+ if (array_key_exists($topic, Mqtt\Topics::TOPIC_TO_ID_MAP)) {
+ $result = Mqtt\Topics::TOPIC_TO_ID_MAP[$topic];
+ $this->_logger->debug(sprintf('Topic "%s" has been mapped to "%s"', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->_logger->warning(sprintf('Topic "%s" does not exist in the enum', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Maps topic ID to human readable name.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ protected function _unmapTopic(
+ $topic)
+ {
+ if (array_key_exists($topic, Mqtt\Topics::ID_TO_TOPIC_MAP)) {
+ $result = Mqtt\Topics::ID_TO_TOPIC_MAP[$topic];
+ $this->_logger->debug(sprintf('Topic ID "%s" has been unmapped to "%s"', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->_logger->warning(sprintf('Topic ID "%s" does not exist in the enum', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $topic
+ * @param string $payload
+ * @param int $qosLevel
+ */
+ protected function _publish(
+ $topic,
+ $payload,
+ $qosLevel)
+ {
+ $this->_logger->info(sprintf('Sending message "%s" to topic "%s"', $payload, $topic));
+ $payload = zlib_encode($payload, ZLIB_ENCODING_DEFLATE, 9);
+ // We need to map human readable topic name to its ID because of bandwidth saving.
+ $topic = $this->_mapTopic($topic);
+ $this->_client->publish(new DefaultMessage($topic, $payload, $qosLevel));
+ }
+
+ /**
+ * Incoming message handler.
+ *
+ * @param MqttMessage $msg
+ */
+ protected function _onReceive(
+ MqttMessage $msg)
+ {
+ $payload = @zlib_decode($msg->getPayload());
+ if ($payload === false) {
+ $this->_logger->warning('Failed to inflate the payload');
+
+ return;
+ }
+ $this->_handleMessage($this->_unmapTopic($msg->getTopic()), $payload);
+ }
+
+ /**
+ * @param string $topic
+ * @param string $payload
+ */
+ protected function _handleMessage(
+ $topic,
+ $payload)
+ {
+ $this->_logger->debug(
+ sprintf('Received a message from topic "%s"', $topic),
+ [base64_encode($payload)]
+ );
+ if (!isset($this->_parsers[$topic])) {
+ $this->_logger->warning(
+ sprintf('No parser for topic "%s" found, skipping the message(s)', $topic),
+ [base64_encode($payload)]
+ );
+
+ return;
+ }
+
+ try {
+ $messages = $this->_parsers[$topic]->parseMessage($topic, $payload);
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage(), [$topic, base64_encode($payload)]);
+
+ return;
+ }
+
+ foreach ($messages as $message) {
+ $module = $message->getModule();
+ if (!isset($this->_handlers[$module])) {
+ $this->_logger->warning(
+ sprintf('No handler for module "%s" found, skipping the message', $module),
+ [$message->getData()]
+ );
+
+ continue;
+ }
+
+ $this->_logger->info(
+ sprintf('Processing a message for module "%s"', $module),
+ [$message->getData()]
+ );
+
+ try {
+ $this->_handlers[$module]->handleMessage($message);
+ } catch (Handler\HandlerException $e) {
+ $this->_logger->warning($e->getMessage(), [$message->getData()]);
+ } catch (\Exception $e) {
+ $this->_target->emit('warning', [$e]);
+ }
+ }
+ }
+
+ /** {@inheritdoc} */
+ public function getLogger()
+ {
+ return $this->_logger;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Auth.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Auth.php
new file mode 100755
index 0000000..5f149b6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Auth.php
@@ -0,0 +1,74 @@
+_instagram = $instagram;
+ }
+
+ /** {@inheritdoc} */
+ public function getClientId()
+ {
+ return substr($this->getDeviceId(), 0, 20);
+ }
+
+ /** {@inheritdoc} */
+ public function getClientType()
+ {
+ return self::AUTH_TYPE;
+ }
+
+ /** {@inheritdoc} */
+ public function getUserId()
+ {
+ return $this->_instagram->account_id;
+ }
+
+ /** {@inheritdoc} */
+ public function getPassword()
+ {
+ $cookie = $this->_instagram->client->getCookie('sessionid', 'i.instagram.com');
+ if ($cookie !== null) {
+ return sprintf('%s=%s', $cookie->getName(), $cookie->getValue());
+ }
+
+ throw new \RuntimeException('No session cookie was found.');
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceId()
+ {
+ return $this->_instagram->uuid;
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceSecret()
+ {
+ return '';
+ }
+
+ /** {@inheritdoc} */
+ public function __toString()
+ {
+ return '';
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Capabilities.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Capabilities.php
new file mode 100755
index 0000000..9519c03
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Capabilities.php
@@ -0,0 +1,33 @@
+ ConnectRequestPacket::class,
+ Packet::TYPE_CONNACK => ConnectResponsePacket::class,
+ Packet::TYPE_PUBLISH => PublishRequestPacket::class,
+ Packet::TYPE_PUBACK => PublishAckPacket::class,
+ Packet::TYPE_PUBREC => PublishReceivedPacket::class,
+ Packet::TYPE_PUBREL => PublishReleasePacket::class,
+ Packet::TYPE_PUBCOMP => PublishCompletePacket::class,
+ Packet::TYPE_SUBSCRIBE => SubscribeRequestPacket::class,
+ Packet::TYPE_SUBACK => SubscribeResponsePacket::class,
+ Packet::TYPE_UNSUBSCRIBE => UnsubscribeRequestPacket::class,
+ Packet::TYPE_UNSUBACK => UnsubscribeResponsePacket::class,
+ Packet::TYPE_PINGREQ => PingRequestPacket::class,
+ Packet::TYPE_PINGRESP => PingResponsePacket::class,
+ Packet::TYPE_DISCONNECT => DisconnectRequestPacket::class,
+ ];
+
+ /**
+ * Builds a packet object for the given type.
+ *
+ * @param int $type
+ *
+ * @throws UnknownPacketTypeException
+ *
+ * @return Packet
+ */
+ public function build(
+ $type)
+ {
+ if (!isset(self::$_mapping[$type])) {
+ throw new UnknownPacketTypeException(sprintf('Unknown packet type %d.', $type));
+ }
+
+ $class = self::$_mapping[$type];
+
+ return new $class();
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/QosLevel.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/QosLevel.php
new file mode 100755
index 0000000..3d98ae1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/QosLevel.php
@@ -0,0 +1,9 @@
+_buffer = new PacketStream();
+ $this->_factory = new PacketFactory();
+ }
+
+ /**
+ * Registers an error callback.
+ *
+ * @param callable $callback
+ */
+ public function onError(
+ $callback)
+ {
+ $this->_errorCallback = $callback;
+ }
+
+ /**
+ * Appends the given data to the internal buffer and parses it.
+ *
+ * @param string $data
+ *
+ * @return Packet[]
+ */
+ public function push(
+ $data)
+ {
+ $this->_buffer->write($data);
+
+ $result = [];
+ while ($this->_buffer->getRemainingBytes() > 0) {
+ $type = $this->_buffer->readByte() >> 4;
+
+ try {
+ $packet = $this->_factory->build($type);
+ } catch (UnknownPacketTypeException $e) {
+ $this->_handleError($e);
+ continue;
+ }
+
+ $this->_buffer->seek(-1);
+ $position = $this->_buffer->getPosition();
+
+ try {
+ $packet->read($this->_buffer);
+ $result[] = $packet;
+ $this->_buffer->cut();
+ } catch (EndOfStreamException $e) {
+ $this->_buffer->setPosition($position);
+ break;
+ } catch (MalformedPacketException $e) {
+ $this->_handleError($e);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the registered error callback.
+ *
+ * @param \Throwable $exception
+ */
+ private function _handleError(
+ $exception)
+ {
+ if ($this->_errorCallback !== null) {
+ $callback = $this->_errorCallback;
+ $callback($exception);
+ }
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Topics.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Topics.php
new file mode 100755
index 0000000..3e8dbd4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Topics.php
@@ -0,0 +1,57 @@
+ self::PUBSUB,
+ self::SEND_MESSAGE_ID => self::SEND_MESSAGE,
+ self::SEND_MESSAGE_RESPONSE_ID => self::SEND_MESSAGE_RESPONSE,
+ self::IRIS_SUB_ID => self::IRIS_SUB,
+ self::IRIS_SUB_RESPONSE_ID => self::IRIS_SUB_RESPONSE,
+ self::MESSAGE_SYNC_ID => self::MESSAGE_SYNC,
+ self::REALTIME_SUB_ID => self::REALTIME_SUB,
+ self::GRAPHQL_ID => self::GRAPHQL,
+ self::REGION_HINT_ID => self::REGION_HINT,
+ ];
+
+ const TOPIC_TO_ID_MAP = [
+ self::PUBSUB => self::PUBSUB_ID,
+ self::SEND_MESSAGE => self::SEND_MESSAGE_ID,
+ self::SEND_MESSAGE_RESPONSE => self::SEND_MESSAGE_RESPONSE_ID,
+ self::IRIS_SUB => self::IRIS_SUB_ID,
+ self::IRIS_SUB_RESPONSE => self::IRIS_SUB_RESPONSE_ID,
+ self::MESSAGE_SYNC => self::MESSAGE_SYNC_ID,
+ self::REALTIME_SUB => self::REALTIME_SUB_ID,
+ self::GRAPHQL => self::GRAPHQL_ID,
+ self::REGION_HINT => self::REGION_HINT_ID,
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/GraphQlParser.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/GraphQlParser.php
new file mode 100755
index 0000000..f88e5ff
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/GraphQlParser.php
@@ -0,0 +1,82 @@
+ self::MODULE_DIRECT,
+ AppPresenceSubscription::QUERY => AppPresenceSubscription::ID,
+ ZeroProvisionSubscription::QUERY => ZeroProvisionSubscription::ID,
+ ];
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ */
+ public function parseMessage(
+ $topic,
+ $payload)
+ {
+ $msgTopic = $msgPayload = null;
+ new Reader($payload, function ($context, $field, $value, $type) use (&$msgTopic, &$msgPayload) {
+ if ($type === Compact::TYPE_BINARY) {
+ if ($field === self::FIELD_TOPIC) {
+ $msgTopic = $value;
+ } elseif ($field === self::FIELD_PAYLOAD) {
+ $msgPayload = $value;
+ }
+ }
+ });
+
+ return [$this->_createMessage($msgTopic, $msgPayload)];
+ }
+
+ /**
+ * Create a message from given topic and payload.
+ *
+ * @param string $topic
+ * @param string $payload
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ *
+ * @return Message
+ */
+ protected function _createMessage(
+ $topic,
+ $payload)
+ {
+ if ($topic === null || $payload === null) {
+ throw new \RuntimeException('Incomplete GraphQL message.');
+ }
+
+ if (!array_key_exists($topic, self::TOPIC_TO_MODULE_ENUM)) {
+ throw new \DomainException(sprintf('Unknown GraphQL topic "%s".', $topic));
+ }
+
+ $data = Client::api_body_decode($payload);
+ if (!is_array($data)) {
+ throw new \RuntimeException('Invalid GraphQL payload.');
+ }
+
+ return new Message(self::TOPIC_TO_MODULE_ENUM[$topic], $data);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/IrisParser.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/IrisParser.php
new file mode 100755
index 0000000..590da53
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/IrisParser.php
@@ -0,0 +1,34 @@
+_module = $module;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \RuntimeException
+ */
+ public function parseMessage(
+ $topic,
+ $payload)
+ {
+ $data = Client::api_body_decode($payload);
+ if (!is_array($data)) {
+ throw new \RuntimeException('Invalid JSON payload.');
+ }
+
+ return [new Message($this->_module, $data)];
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/RegionHintParser.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/RegionHintParser.php
new file mode 100755
index 0000000..898f686
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/RegionHintParser.php
@@ -0,0 +1,54 @@
+_createMessage($region)];
+ }
+
+ /**
+ * Create a message from given topic and payload.
+ *
+ * @param string $region
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ *
+ * @return Message
+ */
+ protected function _createMessage(
+ $region)
+ {
+ if ($region === null) {
+ throw new \RuntimeException('Incomplete region hint message.');
+ }
+
+ return new Message(RegionHintHandler::MODULE, $region);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/SkywalkerParser.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/SkywalkerParser.php
new file mode 100755
index 0000000..0746531
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Parser/SkywalkerParser.php
@@ -0,0 +1,82 @@
+ self::MODULE_DIRECT,
+ self::TOPIC_LIVE => self::MODULE_LIVE,
+ self::TOPIC_LIVEWITH => self::MODULE_LIVEWITH,
+ ];
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ */
+ public function parseMessage(
+ $topic,
+ $payload)
+ {
+ $msgTopic = $msgPayload = null;
+ new Reader($payload, function ($context, $field, $value, $type) use (&$msgTopic, &$msgPayload) {
+ if ($type === Compact::TYPE_I32 && $field === self::FIELD_TOPIC) {
+ $msgTopic = $value;
+ } elseif ($type === Compact::TYPE_BINARY && $field === self::FIELD_PAYLOAD) {
+ $msgPayload = $value;
+ }
+ });
+
+ return [$this->_createMessage($msgTopic, $msgPayload)];
+ }
+
+ /**
+ * Create a message from given topic and payload.
+ *
+ * @param int $topic
+ * @param string $payload
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ *
+ * @return Message
+ */
+ protected function _createMessage(
+ $topic,
+ $payload)
+ {
+ if ($topic === null || $payload === null) {
+ throw new \RuntimeException('Incomplete Skywalker message.');
+ }
+
+ if (!array_key_exists($topic, self::TOPIC_TO_MODULE_ENUM)) {
+ throw new \DomainException(sprintf('Unknown Skywalker topic "%d".', $topic));
+ }
+
+ $data = Client::api_body_decode($payload);
+ if (!is_array($data)) {
+ throw new \RuntimeException('Invalid Skywalker payload.');
+ }
+
+ return new Message(self::TOPIC_TO_MODULE_ENUM[$topic], $data);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/ParserInterface.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/ParserInterface.php
new file mode 100755
index 0000000..f180332
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/ParserInterface.php
@@ -0,0 +1,19 @@
+ '',
+ 'payload' => '\InstagramAPI\Response\Model\DirectSendItemPayload',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEvent.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEvent.php
new file mode 100755
index 0000000..736355b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEvent.php
@@ -0,0 +1,44 @@
+ 'PatchEventOp[]',
+ 'message_type' => 'int',
+ 'seq_id' => 'int',
+ 'lazy' => 'bool',
+ 'num_endpoints' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEventOp.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEventOp.php
new file mode 100755
index 0000000..7b28155
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEventOp.php
@@ -0,0 +1,45 @@
+ '',
+ 'path' => '',
+ 'value' => '',
+ 'ts' => '',
+ 'doublePublish' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/IrisSubscribeAck.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/IrisSubscribeAck.php
new file mode 100755
index 0000000..3c2016b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/IrisSubscribeAck.php
@@ -0,0 +1,35 @@
+ 'int',
+ 'succeeded' => 'bool',
+ 'error_type' => 'int',
+ 'error_message' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/LiveBroadcast.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/LiveBroadcast.php
new file mode 100755
index 0000000..02d7e66
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/LiveBroadcast.php
@@ -0,0 +1,40 @@
+ '\InstagramAPI\Response\Model\User',
+ 'broadcast_id' => 'string',
+ 'is_periodic' => '',
+ 'broadcast_message' => 'string',
+ 'display_notification' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeAction.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeAction.php
new file mode 100755
index 0000000..77ccf8e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeAction.php
@@ -0,0 +1,29 @@
+ 'string',
+ 'action' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeEvent.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeEvent.php
new file mode 100755
index 0000000..c0861d4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeEvent.php
@@ -0,0 +1,27 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/StoryScreenshot.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/StoryScreenshot.php
new file mode 100755
index 0000000..4d49df3
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/StoryScreenshot.php
@@ -0,0 +1,28 @@
+ '\InstagramAPI\Response\Model\User',
+ /*
+ * A number describing what type of media this is.
+ */
+ 'media_type' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadAction.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadAction.php
new file mode 100755
index 0000000..6520222
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadAction.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'action_log' => '\InstagramAPI\Response\Model\ActionLog',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadActivity.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadActivity.php
new file mode 100755
index 0000000..5f0eafa
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadActivity.php
@@ -0,0 +1,35 @@
+ '',
+ 'sender_id' => 'string',
+ 'activity_status' => '',
+ 'ttl' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/ZeroProvisionEvent.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/ZeroProvisionEvent.php
new file mode 100755
index 0000000..96ea4d5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Payload/ZeroProvisionEvent.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'product_name' => 'string',
+ 'zero_provisioned_time' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/AppPresenceSubscription.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/AppPresenceSubscription.php
new file mode 100755
index 0000000..c5bbc23
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/AppPresenceSubscription.php
@@ -0,0 +1,30 @@
+ $subscriptionId,
+ ]);
+ }
+
+ /** {@inheritdoc} */
+ public function getId()
+ {
+ return self::ID;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/DirectTypingSubscription.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/DirectTypingSubscription.php
new file mode 100755
index 0000000..f6ad123
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/DirectTypingSubscription.php
@@ -0,0 +1,29 @@
+ $accountId,
+ ]);
+ }
+
+ /** {@inheritdoc} */
+ public function getId()
+ {
+ return 'direct_typing';
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/ZeroProvisionSubscription.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/ZeroProvisionSubscription.php
new file mode 100755
index 0000000..3fa7ae5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/ZeroProvisionSubscription.php
@@ -0,0 +1,32 @@
+ Signatures::generateUUID(),
+ 'device_id' => $deviceId,
+ ]);
+ }
+
+ /** {@inheritdoc} */
+ public function getId()
+ {
+ return self::ID;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQlSubscription.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQlSubscription.php
new file mode 100755
index 0000000..46580c7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQlSubscription.php
@@ -0,0 +1,46 @@
+_queryId = $queryId;
+ $this->_inputData = $inputData;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::REALTIME_SUB;
+ }
+
+ /** {@inheritdoc} */
+ abstract public function getId();
+
+ /** {@inheritdoc} */
+ public function __toString()
+ {
+ return sprintf(self::TEMPLATE, $this->_queryId, json_encode(['input_data' => $this->_inputData]));
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/DirectSubscription.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/DirectSubscription.php
new file mode 100755
index 0000000..99eb563
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/DirectSubscription.php
@@ -0,0 +1,23 @@
+_accountId);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/LiveSubscription.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/LiveSubscription.php
new file mode 100755
index 0000000..7b6fb95
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/LiveSubscription.php
@@ -0,0 +1,23 @@
+_accountId);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/SkywalkerSubscription.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/SkywalkerSubscription.php
new file mode 100755
index 0000000..8cffa52
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/Subscription/SkywalkerSubscription.php
@@ -0,0 +1,35 @@
+_accountId = $accountId;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::PUBSUB;
+ }
+
+ /** {@inheritdoc} */
+ abstract public function getId();
+
+ /** {@inheritdoc} */
+ abstract public function __toString();
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Realtime/SubscriptionInterface.php b/instafeed/vendor/mgp25/instagram-php/src/Realtime/SubscriptionInterface.php
new file mode 100755
index 0000000..bb90a6d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Realtime/SubscriptionInterface.php
@@ -0,0 +1,27 @@
+_parent = $parent;
+ $this->_url = $url;
+
+ // Set defaults.
+ $this->_apiVersion = 1;
+ $this->_headers = [];
+ $this->_params = [];
+ $this->_posts = [];
+ $this->_files = [];
+ $this->_handles = [];
+ $this->_guzzleOptions = [];
+ $this->_needsAuth = true;
+ $this->_signedPost = true;
+ $this->_signedGet = false;
+ $this->_isMultiResponse = false;
+ $this->_isBodyCompressed = false;
+ $this->_excludeSigned = [];
+ $this->_defaultHeaders = true;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ // Ensure that all opened handles are closed.
+ $this->_closeHandles();
+ }
+
+ /**
+ * Set API version to use.
+ *
+ * @param int $apiVersion
+ *
+ * @throws \InvalidArgumentException In case of unsupported API version.
+ *
+ * @return self
+ */
+ public function setVersion(
+ $apiVersion)
+ {
+ if (!array_key_exists($apiVersion, Constants::API_URLS)) {
+ throw new \InvalidArgumentException(sprintf('"%d" is not a supported API version.', $apiVersion));
+ }
+ $this->_apiVersion = $apiVersion;
+
+ return $this;
+ }
+
+ /**
+ * Add query param to request, overwriting any previous value.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addParam(
+ $key,
+ $value)
+ {
+ if ($value === true) {
+ $value = 'true';
+ } elseif ($value === false) {
+ $value = 'false';
+ }
+ $this->_params[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add POST param to request, overwriting any previous value.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addPost(
+ $key,
+ $value)
+ {
+ if ($value === true) {
+ $value = 'true';
+ } elseif ($value === false) {
+ $value = 'false';
+ }
+ $this->_posts[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add unsigned POST param to request, overwriting any previous value.
+ *
+ * This adds a POST value and marks it as "never sign it", even if this
+ * is a signed request. Instagram sometimes needs a few unsigned values.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addUnsignedPost(
+ $key,
+ $value)
+ {
+ $this->addPost($key, $value);
+ $this->_excludeSigned[] = $key;
+
+ return $this;
+ }
+
+ /**
+ * Add an on-disk file to a POST request, which causes this to become a multipart form request.
+ *
+ * @param string $key Form field name.
+ * @param string $filepath Path to a file.
+ * @param string|null $filename Filename to use in Content-Disposition header.
+ * @param array $headers An associative array of headers.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function addFile(
+ $key,
+ $filepath,
+ $filename = null,
+ array $headers = [])
+ {
+ // Validate
+ if (!is_file($filepath)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" does not exist.', $filepath));
+ }
+ if (!is_readable($filepath)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" is not readable.', $filepath));
+ }
+ // Inherit value from $filepath, if not supplied.
+ if ($filename === null) {
+ $filename = $filepath;
+ }
+ $filename = basename($filename);
+ // Default headers.
+ $headers = $headers + [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Transfer-Encoding' => 'binary',
+ ];
+ $this->_files[$key] = [
+ 'filepath' => $filepath,
+ 'filename' => $filename,
+ 'headers' => $headers,
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Add raw file data to a POST request, which causes this to become a multipart form request.
+ *
+ * @param string $key Form field name.
+ * @param string $data File data.
+ * @param string|null $filename Filename to use in Content-Disposition header.
+ * @param array $headers An associative array of headers.
+ *
+ * @return self
+ */
+ public function addFileData(
+ $key,
+ $data,
+ $filename,
+ array $headers = [])
+ {
+ $filename = basename($filename);
+ // Default headers.
+ $headers = $headers + [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Transfer-Encoding' => 'binary',
+ ];
+ $this->_files[$key] = [
+ 'contents' => $data,
+ 'filename' => $filename,
+ 'headers' => $headers,
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Add custom header to request, overwriting any previous or default value.
+ *
+ * The custom value will even take precedence over the default headers!
+ *
+ * WARNING: If this is called multiple times with the same header "key"
+ * name, it will only keep the LATEST value given for that specific header.
+ * It will NOT keep any of its older values, since you can only have ONE
+ * value per header! If you want multiple values in headers that support
+ * it, you must manually format them properly and send us the final string,
+ * usually by separating the value string entries with a semicolon.
+ *
+ * @param string $key
+ * @param string $value
+ *
+ * @return self
+ */
+ public function addHeader(
+ $key,
+ $value)
+ {
+ $this->_headers[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add headers used by most API requests.
+ *
+ * @return self
+ */
+ protected function _addDefaultHeaders()
+ {
+ if ($this->_defaultHeaders) {
+ $this->_headers['X-IG-App-ID'] = Constants::FACEBOOK_ANALYTICS_APPLICATION_ID;
+ $this->_headers['X-IG-Capabilities'] = Constants::X_IG_Capabilities;
+ $this->_headers['X-IG-Connection-Type'] = Constants::X_IG_Connection_Type;
+ $this->_headers['X-IG-Connection-Speed'] = mt_rand(1000, 3700).'kbps';
+ // TODO: IMPLEMENT PROPER CALCULATION OF THESE HEADERS.
+ $this->_headers['X-IG-Bandwidth-Speed-KBPS'] = '-1.000';
+ $this->_headers['X-IG-Bandwidth-TotalBytes-B'] = '0';
+ $this->_headers['X-IG-Bandwidth-TotalTime-MS'] = '0';
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the "add default headers" flag.
+ *
+ * @param bool $flag
+ *
+ * @return self
+ */
+ public function setAddDefaultHeaders(
+ $flag)
+ {
+ $this->_defaultHeaders = $flag;
+
+ return $this;
+ }
+
+ /**
+ * Set the extra Guzzle options for this request.
+ *
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ *
+ * @return self
+ */
+ public function setGuzzleOptions(
+ array $guzzleOptions)
+ {
+ $this->_guzzleOptions = $guzzleOptions;
+
+ return $this;
+ }
+
+ /**
+ * Set raw request body.
+ *
+ * @param StreamInterface $stream
+ *
+ * @return self
+ */
+ public function setBody(
+ StreamInterface $stream)
+ {
+ $this->_body = $stream;
+
+ return $this;
+ }
+
+ /**
+ * Set authorized request flag.
+ *
+ * @param bool $needsAuth
+ *
+ * @return self
+ */
+ public function setNeedsAuth(
+ $needsAuth)
+ {
+ $this->_needsAuth = $needsAuth;
+
+ return $this;
+ }
+
+ /**
+ * Set signed request data flag.
+ *
+ * @param bool $signedPost
+ *
+ * @return self
+ */
+ public function setSignedPost(
+ $signedPost = true)
+ {
+ $this->_signedPost = $signedPost;
+
+ return $this;
+ }
+
+ /**
+ * Set signed request params flag.
+ *
+ * @param bool $signedGet
+ *
+ * @return self
+ */
+ public function setSignedGet(
+ $signedGet = false)
+ {
+ $this->_signedGet = $signedGet;
+
+ return $this;
+ }
+
+ /**
+ * Set the "this API endpoint responds with multiple JSON objects" flag.
+ *
+ * @param bool $flag
+ *
+ * @return self
+ */
+ public function setIsMultiResponse(
+ $flag = false)
+ {
+ $this->_isMultiResponse = $flag;
+
+ return $this;
+ }
+
+ /**
+ * Set gz-compressed request params flag.
+ *
+ * @param bool $isBodyCompressed
+ *
+ * @return self
+ */
+ public function setIsBodyCompressed(
+ $isBodyCompressed = false)
+ {
+ $this->_isBodyCompressed = $isBodyCompressed;
+
+ if ($isBodyCompressed === true) {
+ $this->_headers['Content-Encoding'] = 'gzip';
+ } elseif (isset($this->_headers['Content-Encoding']) && $this->_headers['Content-Encoding'] === 'gzip') {
+ unset($this->_headers['Content-Encoding']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get a Stream for the given file.
+ *
+ * @param array $file
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return StreamInterface
+ */
+ protected function _getStreamForFile(
+ array $file)
+ {
+ if (isset($file['contents'])) {
+ $result = stream_for($file['contents']); // Throws.
+ } elseif (isset($file['filepath'])) {
+ $handle = fopen($file['filepath'], 'rb');
+ if ($handle === false) {
+ throw new \RuntimeException(sprintf('Could not open file "%s" for reading.', $file['filepath']));
+ }
+ $this->_handles[] = $handle;
+ $result = stream_for($handle); // Throws.
+ } else {
+ throw new \InvalidArgumentException('No data for stream creation.');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Convert the request's data into its HTTP POST multipart body contents.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return MultipartStream
+ */
+ protected function _getMultipartBody()
+ {
+ // Here is a tricky part: all form data (including files) must be ordered by hash code.
+ // So we are creating an index for building POST data.
+ $index = Utils::reorderByHashCode(array_merge($this->_posts, $this->_files));
+ // Build multipart elements using created index.
+ $elements = [];
+ foreach ($index as $key => $value) {
+ if (!isset($this->_files[$key])) {
+ $element = [
+ 'name' => $key,
+ 'contents' => $value,
+ ];
+ } else {
+ $file = $this->_files[$key];
+ $element = [
+ 'name' => $key,
+ 'contents' => $this->_getStreamForFile($file), // Throws.
+ 'filename' => isset($file['filename']) ? $file['filename'] : null,
+ 'headers' => isset($file['headers']) ? $file['headers'] : [],
+ ];
+ }
+ $elements[] = $element;
+ }
+
+ return new MultipartStream( // Throws.
+ $elements,
+ Utils::generateMultipartBoundary()
+ );
+ }
+
+ /**
+ * Close opened file handles.
+ */
+ protected function _closeHandles()
+ {
+ if (!is_array($this->_handles) || !count($this->_handles)) {
+ return;
+ }
+
+ foreach ($this->_handles as $handle) {
+ Utils::safe_fclose($handle);
+ }
+ $this->_resetHandles();
+ }
+
+ /**
+ * Reset opened handles array.
+ */
+ protected function _resetHandles()
+ {
+ $this->_handles = [];
+ }
+
+ /**
+ * Convert the request's data into its HTTP POST urlencoded body contents.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Stream
+ */
+ protected function _getUrlencodedBody()
+ {
+ $this->_headers['Content-Type'] = Constants::CONTENT_TYPE;
+
+ return stream_for( // Throws.
+ http_build_query(Utils::reorderByHashCode($this->_posts))
+ );
+ }
+
+ /**
+ * Convert the request's data into its HTTP POST body contents.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return StreamInterface|null The body stream if POST request; otherwise NULL if GET request.
+ */
+ protected function _getRequestBody()
+ {
+ // Check and return raw body stream if set.
+ if ($this->_body !== null) {
+ if ($this->_isBodyCompressed) {
+ return stream_for(zlib_encode((string) $this->_body, ZLIB_ENCODING_GZIP));
+ }
+
+ return $this->_body;
+ }
+ // We have no POST data and no files.
+ if (!count($this->_posts) && !count($this->_files)) {
+ return;
+ }
+ // Sign POST data if needed.
+ if ($this->_signedPost) {
+ $this->_posts = Signatures::signData($this->_posts, $this->_excludeSigned);
+ }
+ // Switch between multipart (at least one file) or urlencoded body.
+ if (!count($this->_files)) {
+ $result = $this->_getUrlencodedBody(); // Throws.
+ } else {
+ $result = $this->_getMultipartBody(); // Throws.
+ }
+
+ if ($this->_isBodyCompressed) {
+ return stream_for(zlib_encode((string) $result, ZLIB_ENCODING_GZIP));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Build HTTP request object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return HttpRequest
+ */
+ protected function _buildHttpRequest()
+ {
+ $endpoint = $this->_url;
+ // Determine the URI to use (it's either relative to API, or a full URI).
+ if (strncmp($endpoint, 'http:', 5) !== 0 && strncmp($endpoint, 'https:', 6) !== 0) {
+ $endpoint = Constants::API_URLS[$this->_apiVersion].$endpoint;
+ }
+ // Check signed request params flag.
+ if ($this->_signedGet) {
+ $this->_params = Signatures::signData($this->_params);
+ }
+ // Generate the final endpoint URL, by adding any custom query params.
+ if (count($this->_params)) {
+ $endpoint = $endpoint
+ .(strpos($endpoint, '?') === false ? '?' : '&')
+ .http_build_query(Utils::reorderByHashCode($this->_params));
+ }
+ // Add default headers (if enabled).
+ $this->_addDefaultHeaders();
+ /** @var StreamInterface|null $postData The POST body stream; is NULL if GET request instead. */
+ $postData = $this->_getRequestBody(); // Throws.
+ // Determine request method.
+ $method = $postData !== null ? 'POST' : 'GET';
+ // Build HTTP request object.
+ return new HttpRequest( // Throws (they didn't document that properly).
+ $method,
+ $endpoint,
+ $this->_headers,
+ $postData
+ );
+ }
+
+ /**
+ * Helper which throws an error if not logged in.
+ *
+ * Remember to ALWAYS call this function at the top of any API request that
+ * requires the user to be logged in!
+ *
+ * @throws LoginRequiredException
+ */
+ protected function _throwIfNotLoggedIn()
+ {
+ // Check the cached login state. May not reflect what will happen on the
+ // server. But it's the best we can check without trying the actual request!
+ if (!$this->_parent->isMaybeLoggedIn) {
+ throw new LoginRequiredException('User not logged in. Please call login() and then try again.');
+ }
+ }
+
+ /**
+ * Perform the request and get its raw HTTP response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return HttpResponseInterface
+ */
+ public function getHttpResponse()
+ {
+ // Prevent request from sending multiple times.
+ if ($this->_httpResponse === null) {
+ if ($this->_needsAuth) {
+ // Throw if this requires authentication and we're not logged in.
+ $this->_throwIfNotLoggedIn();
+ }
+
+ $this->_resetHandles();
+
+ try {
+ $this->_httpResponse = $this->_parent->client->api( // Throws.
+ $this->_buildHttpRequest(), // Throws.
+ $this->_guzzleOptions
+ );
+ } finally {
+ $this->_closeHandles();
+ }
+ }
+
+ return $this->_httpResponse;
+ }
+
+ /**
+ * Return the raw HTTP response body.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return string
+ */
+ public function getRawResponse()
+ {
+ $httpResponse = $this->getHttpResponse(); // Throws.
+ $body = (string) $httpResponse->getBody();
+
+ // Handle API endpoints that respond with multiple JSON objects.
+ // NOTE: We simply merge all JSON objects into a single object. This
+ // text replacement of "}\r\n{" is safe, because the actual JSON data
+ // objects never contain literal newline characters (http://json.org).
+ // And if we get any duplicate properties, then PHP will simply select
+ // the latest value for that property (ex: a:1,a:2 is treated as a:2).
+ if ($this->_isMultiResponse) {
+ $body = str_replace("}\r\n{", ',', $body);
+ }
+
+ return $body;
+ }
+
+ /**
+ * Return safely JSON-decoded HTTP response.
+ *
+ * This uses a special decoder which handles 64-bit numbers correctly.
+ *
+ * @param bool $assoc When FALSE, decode to object instead of associative array.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return mixed
+ */
+ public function getDecodedResponse(
+ $assoc = true)
+ {
+ // Important: Special JSON decoder.
+ return Client::api_body_decode(
+ $this->getRawResponse(), // Throws.
+ $assoc
+ );
+ }
+
+ /**
+ * Perform the request and map its response data to the provided object.
+ *
+ * @param Response $responseObject An instance of a class object whose properties to fill with the response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return Response The provided responseObject with all JSON properties filled.
+ */
+ public function getResponse(
+ Response $responseObject)
+ {
+ // Check for API response success and put its response in the object.
+ $this->_parent->client->mapServerResponse( // Throws.
+ $responseObject,
+ $this->getRawResponse(), // Throws.
+ $this->getHttpResponse() // Throws.
+ );
+
+ return $responseObject;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Account.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Account.php
new file mode 100755
index 0000000..8cee2aa
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Account.php
@@ -0,0 +1,779 @@
+ig->request('accounts/current_user/')
+ ->addParam('edit', true)
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Edit your biography.
+ *
+ * You are able to add `@mentions` and `#hashtags` to your biography, but
+ * be aware that Instagram disallows certain web URLs and shorteners.
+ *
+ * Also keep in mind that anyone can read your biography (even if your
+ * account is private).
+ *
+ * WARNING: Remember to also call `editProfile()` *after* using this
+ * function, so that you act like the real app!
+ *
+ * @param string $biography Biography text. Use "" for nothing.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see Account::editProfile() should be called after this function!
+ */
+ public function setBiography(
+ $biography)
+ {
+ if (!is_string($biography) || mb_strlen($biography, 'utf8') > 150) {
+ throw new \InvalidArgumentException('Please provide a 0 to 150 character string as biography.');
+ }
+
+ return $this->ig->request('accounts/set_biography/')
+ ->addPost('raw_text', $biography)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Edit your gender.
+ *
+ * WARNING: Remember to also call `editProfile()` *after* using this
+ * function, so that you act like the real app!
+ *
+ * @param string $gender this can be male, female, empty or null for 'prefer not to say' or anything else for custom
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function setGender(
+ $gender = '')
+ {
+ switch (strtolower($gender)) {
+ case 'male':$gender_id = 1; break;
+ case 'female':$gender_id = 2; break;
+ case null:
+ case '':$gender_id = 3; break;
+ default:$gender_id = 4;
+ }
+
+ return $this->ig->request('accounts/set_gender/')
+ ->setSignedPost(false)
+ ->addPost('gender', $gender_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('custom_gender', $gender_id === 4 ? $gender : '')
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Edit your profile.
+ *
+ * Warning: You must provide ALL parameters to this function. The values
+ * which you provide will overwrite all current values on your profile.
+ * You can use getCurrentUser() to see your current values first.
+ *
+ * @param string $url Website URL. Use "" for nothing.
+ * @param string $phone Phone number. Use "" for nothing.
+ * @param string $name Full name. Use "" for nothing.
+ * @param string $biography Biography text. Use "" for nothing.
+ * @param string $email Email. Required!
+ * @param int $gender Gender (1 = male, 2 = female, 3 = unknown). Required!
+ * @param string|null $newUsername (optional) Rename your account to a new username,
+ * which you've already verified with checkUsername().
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see Account::getCurrentUser() to get your current account details.
+ * @see Account::checkUsername() to verify your new username first.
+ */
+ public function editProfile(
+ $url,
+ $phone,
+ $name,
+ $biography,
+ $email,
+ $gender,
+ $newUsername = null)
+ {
+ // We must mark the profile for editing before doing the main request.
+ $userResponse = $this->ig->request('accounts/current_user/')
+ ->addParam('edit', true)
+ ->getResponse(new Response\UserInfoResponse());
+
+ // Get the current user's name from the response.
+ $currentUser = $userResponse->getUser();
+ if (!$currentUser || !is_string($currentUser->getUsername())) {
+ throw new InternalException('Unable to find current account username while preparing profile edit.');
+ }
+ $oldUsername = $currentUser->getUsername();
+
+ // Determine the desired username value.
+ $username = is_string($newUsername) && strlen($newUsername) > 0
+ ? $newUsername
+ : $oldUsername; // Keep current name.
+
+ return $this->ig->request('accounts/edit_profile/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('external_url', $url)
+ ->addPost('phone_number', $phone)
+ ->addPost('username', $username)
+ ->addPost('first_name', $name)
+ ->addPost('biography', $biography)
+ ->addPost('email', $email)
+ ->addPost('gender', $gender)
+ ->addPost('device_id', $this->ig->device_id)
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Changes your account's profile picture.
+ *
+ * @param string $photoFilename The photo filename.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function changeProfilePicture(
+ $photoFilename)
+ {
+ return $this->ig->request('accounts/change_profile_picture/')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addFile('profile_pic', $photoFilename, 'profile_pic')
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Remove your account's profile picture.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function removeProfilePicture()
+ {
+ return $this->ig->request('accounts/remove_profile_picture/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Sets your account to public.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function setPublic()
+ {
+ return $this->ig->request('accounts/set_public/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Sets your account to private.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function setPrivate()
+ {
+ return $this->ig->request('accounts/set_private/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Switches your account to business profile.
+ *
+ * In order to switch your account to Business profile you MUST
+ * call Account::setBusinessInfo().
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SwitchBusinessProfileResponse
+ *
+ * @see Account::setBusinessInfo() sets required data to become a business profile.
+ */
+ public function switchToBusinessProfile()
+ {
+ return $this->ig->request('business_conversion/get_business_convert_social_context/')
+ ->getResponse(new Response\SwitchBusinessProfileResponse());
+ }
+
+ /**
+ * Switches your account to personal profile.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SwitchPersonalProfileResponse
+ */
+ public function switchToPersonalProfile()
+ {
+ return $this->ig->request('accounts/convert_to_personal/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SwitchPersonalProfileResponse());
+ }
+
+ /**
+ * Sets contact information for business profile.
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ * @param string $email Email.
+ * @param string $categoryId TODO: Info.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateBusinessInfoResponse
+ */
+ public function setBusinessInfo(
+ $phoneNumber,
+ $email,
+ $categoryId)
+ {
+ return $this->ig->request('accounts/create_business_info/')
+ ->addPost('set_public', 'true')
+ ->addPost('entry_point', 'setting')
+ ->addPost('public_phone_contact', json_encode([
+ 'public_phone_number' => $phoneNumber,
+ 'business_contact_method' => 'CALL',
+ ]))
+ ->addPost('public_email', $email)
+ ->addPost('category_id', $categoryId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\CreateBusinessInfoResponse());
+ }
+
+ /**
+ * Check if an Instagram username is available (not already registered).
+ *
+ * Use this before trying to rename your Instagram account,
+ * to be sure that the new username is available.
+ *
+ * @param string $username Instagram username to check.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CheckUsernameResponse
+ *
+ * @see Account::editProfile() to rename your account.
+ */
+ public function checkUsername(
+ $username)
+ {
+ return $this->ig->request('users/check_username/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('username', $username)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->getResponse(new Response\CheckUsernameResponse());
+ }
+
+ /**
+ * Get account spam filter status.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterResponse
+ */
+ public function getCommentFilter()
+ {
+ return $this->ig->request('accounts/get_comment_filter/')
+ ->getResponse(new Response\CommentFilterResponse());
+ }
+
+ /**
+ * Set account spam filter status (on/off).
+ *
+ * @param int $config_value Whether spam filter is on (0 or 1).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterSetResponse
+ */
+ public function setCommentFilter(
+ $config_value)
+ {
+ return $this->ig->request('accounts/set_comment_filter/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('config_value', $config_value)
+ ->getResponse(new Response\CommentFilterSetResponse());
+ }
+
+ /**
+ * Get whether the comment category filter is disabled.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentCategoryFilterResponse
+ */
+ public function getCommentCategoryFilterDisabled()
+ {
+ return $this->ig->request('accounts/get_comment_category_filter_disabled/')
+ ->getResponse(new Response\CommentCategoryFilterResponse());
+ }
+
+ /**
+ * Get account spam filter keywords.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterKeywordsResponse
+ */
+ public function getCommentFilterKeywords()
+ {
+ return $this->ig->request('accounts/get_comment_filter_keywords/')
+ ->getResponse(new Response\CommentFilterKeywordsResponse());
+ }
+
+ /**
+ * Set account spam filter keywords.
+ *
+ * @param string $keywords List of blocked words, separated by comma.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterSetResponse
+ */
+ public function setCommentFilterKeywords(
+ $keywords)
+ {
+ return $this->ig->request('accounts/set_comment_filter_keywords/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('keywords', $keywords)
+ ->getResponse(new Response\CommentFilterSetResponse());
+ }
+
+ /**
+ * Change your account's password.
+ *
+ * @param string $oldPassword Old password.
+ * @param string $newPassword New password.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ChangePasswordResponse
+ */
+ public function changePassword(
+ $oldPassword,
+ $newPassword)
+ {
+ return $this->ig->request('accounts/change_password/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('old_password', $oldPassword)
+ ->addPost('new_password1', $newPassword)
+ ->addPost('new_password2', $newPassword)
+ ->getResponse(new Response\ChangePasswordResponse());
+ }
+
+ /**
+ * Get account security info and backup codes.
+ *
+ * WARNING: STORE AND KEEP BACKUP CODES IN A SAFE PLACE. THEY ARE EXTREMELY
+ * IMPORTANT! YOU WILL GET THE CODES IN THE RESPONSE. THE BACKUP
+ * CODES LET YOU REGAIN CONTROL OF YOUR ACCOUNT IF YOU LOSE THE
+ * PHONE NUMBER! WITHOUT THE CODES, YOU RISK LOSING YOUR ACCOUNT!
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\AccountSecurityInfoResponse
+ *
+ * @see Account::enableTwoFactorSMS()
+ */
+ public function getSecurityInfo()
+ {
+ return $this->ig->request('accounts/account_security_info/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\AccountSecurityInfoResponse());
+ }
+
+ /**
+ * Request that Instagram enables two factor SMS authentication.
+ *
+ * The SMS will have a verification code for enabling two factor SMS
+ * authentication. You must then give that code to enableTwoFactorSMS().
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SendTwoFactorEnableSMSResponse
+ *
+ * @see Account::enableTwoFactorSMS()
+ */
+ public function sendTwoFactorEnableSMS(
+ $phoneNumber)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ return $this->ig->request('accounts/send_two_factor_enable_sms/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->getResponse(new Response\SendTwoFactorEnableSMSResponse());
+ }
+
+ /**
+ * Enable Two Factor authentication.
+ *
+ * WARNING: STORE AND KEEP BACKUP CODES IN A SAFE PLACE. THEY ARE EXTREMELY
+ * IMPORTANT! YOU WILL GET THE CODES IN THE RESPONSE. THE BACKUP
+ * CODES LET YOU REGAIN CONTROL OF YOUR ACCOUNT IF YOU LOSE THE
+ * PHONE NUMBER! WITHOUT THE CODES, YOU RISK LOSING YOUR ACCOUNT!
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ * @param string $verificationCode The code sent to your phone via `Account::sendTwoFactorEnableSMS()`.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\AccountSecurityInfoResponse
+ *
+ * @see Account::sendTwoFactorEnableSMS()
+ * @see Account::getSecurityInfo()
+ */
+ public function enableTwoFactorSMS(
+ $phoneNumber,
+ $verificationCode)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ $this->ig->request('accounts/enable_sms_two_factor/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->addPost('verification_code', $verificationCode)
+ ->getResponse(new Response\EnableTwoFactorSMSResponse());
+
+ return $this->getSecurityInfo();
+ }
+
+ /**
+ * Disable Two Factor authentication.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DisableTwoFactorSMSResponse
+ */
+ public function disableTwoFactorSMS()
+ {
+ return $this->ig->request('accounts/disable_sms_two_factor/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\DisableTwoFactorSMSResponse());
+ }
+
+ /**
+ * Save presence status to the storage.
+ *
+ * @param bool $disabled
+ */
+ protected function _savePresenceStatus(
+ $disabled)
+ {
+ try {
+ $this->ig->settings->set('presence_disabled', $disabled ? '1' : '0');
+ } catch (SettingsException $e) {
+ // Ignore storage errors.
+ }
+ }
+
+ /**
+ * Get presence status.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PresenceStatusResponse
+ */
+ public function getPresenceStatus()
+ {
+ /** @var Response\PresenceStatusResponse $result */
+ $result = $this->ig->request('accounts/get_presence_disabled/')
+ ->setSignedGet(true)
+ ->getResponse(new Response\PresenceStatusResponse());
+
+ $this->_savePresenceStatus($result->getDisabled());
+
+ return $result;
+ }
+
+ /**
+ * Enable presence.
+ *
+ * Allow accounts you follow and anyone you message to see when you were
+ * last active on Instagram apps.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function enablePresence()
+ {
+ /** @var Response\GenericResponse $result */
+ $result = $this->ig->request('accounts/set_presence_disabled/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('disabled', '0')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+
+ $this->_savePresenceStatus(false);
+
+ return $result;
+ }
+
+ /**
+ * Disable presence.
+ *
+ * You won't be able to see the activity status of other accounts.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function disablePresence()
+ {
+ /** @var Response\GenericResponse $result */
+ $result = $this->ig->request('accounts/set_presence_disabled/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('disabled', '1')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+
+ $this->_savePresenceStatus(true);
+
+ return $result;
+ }
+
+ /**
+ * Tell Instagram to send you a message to verify your email address.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SendConfirmEmailResponse
+ */
+ public function sendConfirmEmail()
+ {
+ return $this->ig->request('accounts/send_confirm_email/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('send_source', 'edit_profile')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SendConfirmEmailResponse());
+ }
+
+ /**
+ * Tell Instagram to send you an SMS code to verify your phone number.
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SendSMSCodeResponse
+ */
+ public function sendSMSCode(
+ $phoneNumber)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ return $this->ig->request('accounts/send_sms_code/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SendSMSCodeResponse());
+ }
+
+ /**
+ * Submit the SMS code you received to verify your phone number.
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ * @param string $verificationCode The code sent to your phone via `Account::sendSMSCode()`.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\VerifySMSCodeResponse
+ *
+ * @see Account::sendSMSCode()
+ */
+ public function verifySMSCode(
+ $phoneNumber,
+ $verificationCode)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ return $this->ig->request('accounts/verify_sms_code/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->addPost('verification_code', $verificationCode)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\VerifySMSCodeResponse());
+ }
+
+ /**
+ * Set contact point prefill.
+ *
+ * @param string $usage Either "prefill" or "auto_confirmation".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function setContactPointPrefill(
+ $usage)
+ {
+ return $this->ig->request('accounts/contact_point_prefill/')
+ ->setNeedsAuth(false)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('usage', $usage)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get account badge notifications for the "Switch account" menu.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BadgeNotificationsResponse
+ */
+ public function getBadgeNotifications()
+ {
+ return $this->ig->request('notifications/badge/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('users_ids', $this->ig->account_id)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->getResponse(new Response\BadgeNotificationsResponse());
+ }
+
+ /**
+ * TODO.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function getProcessContactPointSignals()
+ {
+ return $this->ig->request('accounts/process_contact_point_signals/')
+ ->addPost('google_tokens', '[]')
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get prefill candidates.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PrefillCandidatesResponse
+ */
+ public function getPrefillCandidates()
+ {
+ return $this->ig->request('accounts/get_prefill_candidates/')
+ ->setNeedsAuth(false)
+ ->addPost('android_device_id', $this->ig->device_id)
+ ->addPost('device_id', $this->ig->uuid)
+ ->addPost('usages', '["account_recovery_omnibox"]')
+ ->getResponse(new Response\PrefillCandidatesResponse());
+ }
+
+ /**
+ * Get details about child and main IG accounts.
+ *
+ * @param bool $useAuth Indicates if auth is required for this request
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function getAccountFamily(
+ $useAuth = true)
+ {
+ return $this->ig->request('multiple_accounts/get_account_family/')
+ ->getResponse(new Response\MultipleAccountFamilyResponse());
+ }
+
+ /**
+ * Get linked accounts status.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LinkageStatusResponse
+ */
+ public function getLinkageStatus()
+ {
+ return $this->ig->request('linked_accounts/get_linkage_status/')
+ ->getResponse(new Response\LinkageStatusResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Business.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Business.php
new file mode 100755
index 0000000..6508a52
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Business.php
@@ -0,0 +1,85 @@
+ig->request('insights/account_organic_insights/')
+ ->addParam('show_promotions_in_landing_page', 'true')
+ ->addParam('first', $day)
+ ->getResponse(new Response\InsightsResponse());
+ }
+
+ /**
+ * Get media insights.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaInsightsResponse
+ */
+ public function getMediaInsights(
+ $mediaId)
+ {
+ return $this->ig->request("insights/media_organic_insights/{$mediaId}/")
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->getResponse(new Response\MediaInsightsResponse());
+ }
+
+ /**
+ * Get account statistics.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GraphqlResponse
+ */
+ public function getStatistics()
+ {
+ return $this->ig->request('ads/graphql/')
+ ->setSignedPost(false)
+ ->setIsMultiResponse(true)
+ ->addParam('locale', Constants::USER_AGENT_LOCALE)
+ ->addParam('vc_policy', 'insights_policy')
+ ->addParam('surface', 'account')
+ ->addPost('access_token', 'undefined')
+ ->addPost('fb_api_caller_class', 'RelayModern')
+ ->addPost('variables', json_encode([
+ 'IgInsightsGridMediaImage_SIZE' => 240,
+ 'timezone' => 'Atlantic/Canary',
+ 'activityTab' => true,
+ 'audienceTab' => true,
+ 'contentTab' => true,
+ 'query_params' => json_encode([
+ 'access_token' => '',
+ 'id' => $this->ig->account_id,
+ ]),
+ ]))
+ ->addPost('doc_id', '1926322010754880')
+ ->getResponse(new Response\GraphqlResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Collection.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Collection.php
new file mode 100755
index 0000000..a58eaba
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Collection.php
@@ -0,0 +1,181 @@
+ig->request('collections/list/')
+ ->addParam('collection_types', '["ALL_MEDIA_AUTO_COLLECTION","MEDIA","PRODUCT_AUTO_COLLECTION"]');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\GetCollectionsListResponse());
+ }
+
+ /**
+ * Get the feed of one of your collections.
+ *
+ * @param string $collectionId The collection ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CollectionFeedResponse
+ */
+ public function getFeed(
+ $collectionId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("feed/collection/{$collectionId}/");
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\CollectionFeedResponse());
+ }
+
+ /**
+ * Create a new collection of your bookmarked (saved) media.
+ *
+ * @param string $name Name of the collection.
+ * @param string $moduleName (optional) From which app module (page) you're performing this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateCollectionResponse
+ */
+ public function create(
+ $name,
+ $moduleName = 'feed_saved_add_to_collection')
+ {
+ return $this->ig->request('collections/create/')
+ ->addPost('module_name', $moduleName)
+ ->addPost('added_media_ids', '[]')
+ ->addPost('collection_visibility', '0') //Instagram is planning for public collections soon
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('name', $name)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\CreateCollectionResponse());
+ }
+
+ /**
+ * Delete a collection.
+ *
+ * @param string $collectionId The collection ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DeleteCollectionResponse
+ */
+ public function delete(
+ $collectionId)
+ {
+ return $this->ig->request("collections/{$collectionId}/delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\DeleteCollectionResponse());
+ }
+
+ /**
+ * Edit the name of a collection or add more saved media to an existing collection.
+ *
+ * @param string $collectionId The collection ID.
+ * @param array $params User-provided key-value pairs:
+ * string 'name',
+ * string 'cover_media_id',
+ * string[] 'add_media',
+ * string 'module_name' (optional).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditCollectionResponse
+ */
+ public function edit(
+ $collectionId,
+ array $params)
+ {
+ $postData = [];
+ if (isset($params['name']) && $params['name'] !== '') {
+ $postData['name'] = $params['name'];
+ }
+ if (!empty($params['cover_media_id'])) {
+ $postData['cover_media_id'] = $params['cover_media_id'];
+ }
+ if (!empty($params['add_media']) && is_array($params['add_media'])) {
+ $postData['added_media_ids'] = json_encode(array_values($params['add_media']));
+ if (isset($params['module_name']) && $params['module_name'] !== '') {
+ $postData['module_name'] = $params['module_name'];
+ } else {
+ $postData['module_name'] = 'feed_saved_add_to_collection';
+ }
+ }
+ if (empty($postData)) {
+ throw new \InvalidArgumentException('You must provide a name or at least one media ID.');
+ }
+ $request = $this->ig->request("collections/{$collectionId}/edit/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ foreach ($postData as $key => $value) {
+ $request->addPost($key, $value);
+ }
+
+ return $request->getResponse(new Response\EditCollectionResponse());
+ }
+
+ /**
+ * Remove a single media item from one or more of your collections.
+ *
+ * Note that you can only remove a single media item per call, since this
+ * function only accepts a single media ID.
+ *
+ * @param string[] $collectionIds Array with one or more collection IDs to remove the item from.
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $moduleName (optional) From which app module (page) you're performing this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditCollectionResponse
+ */
+ public function removeMedia(
+ array $collectionIds,
+ $mediaId,
+ $moduleName = 'feed_contextual_saved_collections')
+ {
+ return $this->ig->request("media/{$mediaId}/save/")
+ ->addPost('module_name', $moduleName)
+ ->addPost('removed_collection_ids', json_encode(array_values($collectionIds)))
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\EditCollectionResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Creative.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Creative.php
new file mode 100755
index 0000000..6aec5b6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Creative.php
@@ -0,0 +1,123 @@
+ig->request('creatives/assets/')
+ ->addPost('type', $stickerType);
+
+ if ($location !== null) {
+ $request
+ ->addPost('lat', $location['lat'])
+ ->addPost('lng', $location['lng'])
+ ->addPost('horizontalAccuracy', $location['horizontalAccuracy']);
+ }
+
+ return $request->getResponse(new Response\StickerAssetsResponse());
+ }
+
+ /**
+ * Get face models that can be used to customize photos or videos.
+ *
+ * NOTE: The files are some strange binary format that only the Instagram
+ * app understands. If anyone figures out the format, please contact us.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FaceModelsResponse
+ */
+ public function getFaceModels()
+ {
+ return $this->ig->request('creatives/face_models/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('aml_facetracker_model_version', 12)
+ ->getResponse(new Response\FaceModelsResponse());
+ }
+
+ /**
+ * Get face overlay effects to customize photos or videos.
+ *
+ * These are effects such as "bunny ears" and similar overlays.
+ *
+ * NOTE: The files are some strange binary format that only the Instagram
+ * app understands. If anyone figures out the format, please contact us.
+ *
+ * @param array|null $location (optional) Array containing lat, lng and horizontalAccuracy.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FaceEffectsResponse
+ */
+ public function getFaceEffects(
+ array $location = null)
+ {
+ $request = $this->ig->request('creatives/face_effects/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES));
+
+ if ($location !== null) {
+ $request
+ ->addPost('lat', $location['lat'])
+ ->addPost('lng', $location['lng'])
+ ->addPost('horizontalAccuracy', $location['horizontalAccuracy']);
+ }
+
+ return $request->getResponse(new Response\FaceEffectsResponse());
+ }
+
+ /**
+ * Send supported capabilities.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\WriteSuppotedCapabilitiesResponse
+ */
+ public function sendSupportedCapabilities()
+ {
+ return $this->ig->request('creatives/write_supported_capabilities/')
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\WriteSuppotedCapabilitiesResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Direct.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Direct.php
new file mode 100755
index 0000000..6051a8f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Direct.php
@@ -0,0 +1,1550 @@
+ 20) {
+ throw new \InvalidArgumentException('Invalid value provided to limit.');
+ }
+ $request = $this->ig->request('direct_v2/inbox/')
+ ->addParam('persistentBadging', 'true')
+ ->addParam('visual_message_return_type', 'unseen')
+ ->addParam('limit', $limit);
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+ if ($prefetch) {
+ $request->addHeader('X-IG-Prefetch-Request', 'foreground');
+ }
+ if ($threadMessageLimit !== null) {
+ $request->addParam('thread_message_limit', $threadMessageLimit);
+ }
+
+ return $request->getResponse(new Response\DirectInboxResponse());
+ }
+
+ /**
+ * Get pending inbox data.
+ *
+ * @param string|null $cursorId Next "cursor ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectPendingInboxResponse
+ */
+ public function getPendingInbox(
+ $cursorId = null)
+ {
+ $request = $this->ig->request('direct_v2/pending_inbox/')
+ ->addParam('persistentBadging', 'true')
+ ->addParam('use_unified_inbox', 'true');
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+
+ return $request->getResponse(new Response\DirectPendingInboxResponse());
+ }
+
+ /**
+ * Approve pending threads by given identifiers.
+ *
+ * @param array $threads One or more thread identifiers.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function approvePendingThreads(
+ array $threads)
+ {
+ if (!count($threads)) {
+ throw new \InvalidArgumentException('Please provide at least one thread to approve.');
+ }
+ // Validate threads.
+ foreach ($threads as &$thread) {
+ if (!is_scalar($thread)) {
+ throw new \InvalidArgumentException('Thread identifier must be scalar.');
+ } elseif (!ctype_digit($thread) && (!is_int($thread) || $thread < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $thread));
+ }
+ $thread = (string) $thread;
+ }
+ unset($thread);
+ // Choose appropriate endpoint.
+ if (count($threads) > 1) {
+ $request = $this->ig->request('direct_v2/threads/approve_multiple/')
+ ->addPost('thread_ids', json_encode($threads));
+ } else {
+ /** @var string $thread */
+ $thread = reset($threads);
+ $request = $this->ig->request("direct_v2/threads/{$thread}/approve/");
+ }
+
+ return $request
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Decline pending threads by given identifiers.
+ *
+ * @param array $threads One or more thread identifiers.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function declinePendingThreads(
+ array $threads)
+ {
+ if (!count($threads)) {
+ throw new \InvalidArgumentException('Please provide at least one thread to decline.');
+ }
+ // Validate threads.
+ foreach ($threads as &$thread) {
+ if (!is_scalar($thread)) {
+ throw new \InvalidArgumentException('Thread identifier must be scalar.');
+ } elseif (!ctype_digit($thread) && (!is_int($thread) || $thread < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $thread));
+ }
+ $thread = (string) $thread;
+ }
+ unset($thread);
+ // Choose appropriate endpoint.
+ if (count($threads) > 1) {
+ $request = $this->ig->request('direct_v2/threads/decline_multiple/')
+ ->addPost('thread_ids', json_encode($threads));
+ } else {
+ /** @var string $thread */
+ $thread = reset($threads);
+ $request = $this->ig->request("direct_v2/threads/{$thread}/decline/");
+ }
+
+ return $request
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Decline all pending threads.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function declineAllPendingThreads()
+ {
+ return $this->ig->request('direct_v2/threads/decline_all/')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get a list of activity statuses for users who you follow or message.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PresencesResponse
+ */
+ public function getPresences()
+ {
+ return $this->ig->request('direct_v2/get_presence/')
+ ->getResponse(new Response\PresencesResponse());
+ }
+
+ /**
+ * Get ranked list of recipients.
+ *
+ * WARNING: This is a special, very heavily throttled API endpoint.
+ * Instagram REQUIRES that you wait several minutes between calls to it.
+ *
+ * @param string $mode Either "reshare" or "raven".
+ * @param bool $showThreads Whether to include existing threads into response.
+ * @param string|null $query (optional) The user to search for.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectRankedRecipientsResponse|null Will be NULL if throttled by Instagram.
+ */
+ public function getRankedRecipients(
+ $mode,
+ $showThreads,
+ $query = null)
+ {
+ try {
+ $request = $this->ig->request('direct_v2/ranked_recipients/')
+ ->addParam('mode', $mode)
+ ->addParam('show_threads', $showThreads ? 'true' : 'false')
+ ->addParam('use_unified_inbox', 'true');
+ if ($query !== null) {
+ $request->addParam('query', $query);
+ }
+
+ return $request
+ ->getResponse(new Response\DirectRankedRecipientsResponse());
+ } catch (ThrottledException $e) {
+ // Throttling is so common that we'll simply return NULL in that case.
+ return null;
+ }
+ }
+
+ /**
+ * Get a thread by the recipients list.
+ *
+ * @param string[]|int[] $users Array of numerical UserPK IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function getThreadByParticipants(
+ array $users)
+ {
+ if (!count($users)) {
+ throw new \InvalidArgumentException('Please provide at least one participant.');
+ }
+ foreach ($users as $user) {
+ if (!is_scalar($user)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ }
+ if (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
+ }
+ }
+ $request = $this->ig->request('direct_v2/threads/get_by_participants/')
+ ->addParam('recipient_users', '['.implode(',', $users).']');
+
+ return $request->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Get direct message thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|null $cursorId Next "cursor ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function getThread(
+ $threadId,
+ $cursorId = null)
+ {
+ $request = $this->ig->request("direct_v2/threads/$threadId/")
+ ->addParam('use_unified_inbox', 'true');
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+
+ return $request->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Get direct visual thread.
+ *
+ * `NOTE:` This "visual" endpoint is only used for Direct stories.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|null $cursorId Next "cursor ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectVisualThreadResponse
+ *
+ * @deprecated Visual inbox has been superseded by the unified inbox.
+ * @see Direct::getThread()
+ */
+ public function getVisualThread(
+ $threadId,
+ $cursorId = null)
+ {
+ $request = $this->ig->request("direct_v2/visual_threads/{$threadId}/");
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+
+ return $request->getResponse(new Response\DirectVisualThreadResponse());
+ }
+
+ /**
+ * Update thread title.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $title New title.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function updateThreadTitle(
+ $threadId,
+ $title)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/update_title/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('title', trim($title))
+ ->setSignedPost(false)
+ ->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Mute direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function muteThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/mute/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unmute direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unmuteThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/unmute/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Create a private story sharing group.
+ *
+ * NOTE: In the official app, when you create a story, you can choose to
+ * send it privately. And from there you can create a new group thread. So
+ * this group creation endpoint is only meant to be used for "direct
+ * stories" at the moment.
+ *
+ * @param string[]|int[] $userIds Array of numerical UserPK IDs.
+ * @param string $threadTitle Name of the group thread.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectCreateGroupThreadResponse
+ */
+ public function createGroupThread(
+ array $userIds,
+ $threadTitle)
+ {
+ if (count($userIds) < 2) {
+ throw new \InvalidArgumentException('You must invite at least 2 users to create a group.');
+ }
+ foreach ($userIds as &$user) {
+ if (!is_scalar($user)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ } elseif (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
+ }
+ $user = (string) $user;
+ }
+
+ $request = $this->ig->request('direct_v2/create_group_thread/')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('recipient_users', json_encode($userIds))
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('thread_title', $threadTitle);
+
+ return $request->getResponse(new Response\DirectCreateGroupThreadResponse());
+ }
+
+ /**
+ * Add users to thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string[]|int[] $users Array of numerical UserPK IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function addUsersToThread(
+ $threadId,
+ array $users)
+ {
+ if (!count($users)) {
+ throw new \InvalidArgumentException('Please provide at least one user.');
+ }
+ foreach ($users as &$user) {
+ if (!is_scalar($user)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ } elseif (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
+ }
+ $user = (string) $user;
+ }
+
+ return $this->ig->request("direct_v2/threads/{$threadId}/add_user/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_ids', json_encode($users))
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Leave direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function leaveThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/leave/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Hide direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function hideThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/hide/")
+ ->addPost('use_unified_inbox', 'true')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Send a direct text message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $text Text message.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendText(
+ array $recipients,
+ $text,
+ array $options = [])
+ {
+ if (!strlen($text)) {
+ throw new \InvalidArgumentException('Text can not be empty.');
+ }
+
+ $urls = Utils::extractURLs($text);
+ if (count($urls)) {
+ /** @var Response\DirectSendItemResponse $result */
+ $result = $this->_sendDirectItem('links', $recipients, array_merge($options, [
+ 'link_urls' => json_encode(array_map(function (array $url) {
+ return $url['fullUrl'];
+ }, $urls)),
+ 'link_text' => $text,
+ ]));
+ } else {
+ /** @var Response\DirectSendItemResponse $result */
+ $result = $this->_sendDirectItem('message', $recipients, array_merge($options, [
+ 'text' => $text,
+ ]));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Share an existing media post via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of additional parameters, including:
+ * "media_type" (required) - either "photo" or "video";
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemsResponse
+ *
+ * @see https://help.instagram.com/1209246439090858 For more information.
+ */
+ public function sendPost(
+ array $recipients,
+ $mediaId,
+ array $options = [])
+ {
+ if (!preg_match('#^\d+_\d+$#D', $mediaId)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media ID.', $mediaId));
+ }
+ if (!isset($options['media_type'])) {
+ throw new \InvalidArgumentException('Please provide media_type in options.');
+ }
+ if ($options['media_type'] !== 'photo' && $options['media_type'] !== 'video') {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media_type.', $options['media_type']));
+ }
+
+ return $this->_sendDirectItems('media_share', $recipients, array_merge($options, [
+ 'media_id' => $mediaId,
+ ]));
+ }
+
+ /**
+ * Send a photo (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $photoFilename The photo filename.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendPhoto(
+ array $recipients,
+ $photoFilename,
+ array $options = [])
+ {
+ if (!is_file($photoFilename) || !is_readable($photoFilename)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" is not available for reading.', $photoFilename));
+ }
+
+ // validate width, height and aspect ratio of photo
+ $photoDetails = new PhotoDetails($photoFilename);
+ $photoDetails->validate(ConstraintsFactory::createFor(Constants::FEED_DIRECT));
+
+ // uplaod it
+ return $this->_sendDirectItem('photo', $recipients, array_merge($options, [
+ 'filepath' => $photoFilename,
+ ]));
+ }
+
+ /**
+ * Send a disappearing photo (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $photoFilename The photo filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSinglePhoto() for available metadata fields.
+ */
+ public function sendDisappearingPhoto(
+ array $recipients,
+ $photoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_ONCE);
+
+ return $this->ig->internal->uploadSinglePhoto(Constants::FEED_DIRECT_STORY, $photoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a replayable photo (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $photoFilename The photo filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSinglePhoto() for available metadata fields.
+ */
+ public function sendReplayablePhoto(
+ array $recipients,
+ $photoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_REPLAYABLE);
+
+ return $this->ig->internal->uploadSinglePhoto(Constants::FEED_DIRECT_STORY, $photoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a video (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $videoFilename The video filename.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendVideo(
+ array $recipients,
+ $videoFilename,
+ array $options = [])
+ {
+ // Direct videos use different upload IDs.
+ $internalMetadata = new InternalMetadata(Utils::generateUploadId(true));
+ // Attempt to upload the video data.
+ $internalMetadata = $this->ig->internal->uploadVideo(Constants::FEED_DIRECT, $videoFilename, $internalMetadata);
+
+ // We must use the same client_context for all attempts to prevent double-posting.
+ if (!isset($options['client_context'])) {
+ $options['client_context'] = Signatures::generateUUID(true);
+ }
+
+ // Send the uploaded video to recipients.
+ try {
+ /** @var \InstagramAPI\Response\DirectSendItemResponse $result */
+ $result = $this->ig->internal->configureWithRetries(
+ function () use ($internalMetadata, $recipients, $options) {
+ $videoUploadResponse = $internalMetadata->getVideoUploadResponse();
+ // Attempt to configure video parameters (which sends it to the thread).
+ return $this->_sendDirectItem('video', $recipients, array_merge($options, [
+ 'upload_id' => $internalMetadata->getUploadId(),
+ 'video_result' => $videoUploadResponse !== null ? $videoUploadResponse->getResult() : '',
+ ]));
+ }
+ );
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf(
+ 'Upload of "%s" failed: %s',
+ $internalMetadata->getPhotoDetails()->getBasename(),
+ $e->getMessage()
+ ),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Send a disappearing video (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function sendDisappearingVideo(
+ array $recipients,
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_ONCE);
+
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_DIRECT_STORY, $videoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a replayable video (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function sendReplayableVideo(
+ array $recipients,
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_REPLAYABLE);
+
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_DIRECT_STORY, $videoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a like to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendLike(
+ array $recipients,
+ array $options = [])
+ {
+ return $this->_sendDirectItem('like', $recipients, $options);
+ }
+
+ /**
+ * Send a hashtag to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $hashtag Hashtag to share.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendHashtag(
+ array $recipients,
+ $hashtag,
+ array $options = [])
+ {
+ if (!strlen($hashtag)) {
+ throw new \InvalidArgumentException('Hashtag can not be empty.');
+ }
+
+ return $this->_sendDirectItem('hashtag', $recipients, array_merge($options, [
+ 'hashtag' => $hashtag,
+ ]));
+ }
+
+ /**
+ * Send a location to a user's inbox.
+ *
+ * You must provide a valid Instagram location ID, which you get via other
+ * functions such as Location::search().
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $locationId Instagram's internal ID for the location.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ *
+ * @see Location::search()
+ */
+ public function sendLocation(
+ array $recipients,
+ $locationId,
+ array $options = [])
+ {
+ if (!ctype_digit($locationId) && (!is_int($locationId) || $locationId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid location ID.', $locationId));
+ }
+
+ return $this->_sendDirectItem('location', $recipients, array_merge($options, [
+ 'venue_id' => $locationId,
+ ]));
+ }
+
+ /**
+ * Send a profile to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $userId Numerical UserPK ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendProfile(
+ array $recipients,
+ $userId,
+ array $options = [])
+ {
+ if (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid numerical UserPK ID.', $userId));
+ }
+
+ return $this->_sendDirectItem('profile', $recipients, array_merge($options, [
+ 'profile_user_id' => $userId,
+ ]));
+ }
+
+ /**
+ * Send a reaction to an existing thread item.
+ *
+ * @param string $threadId Thread identifier.
+ * @param string $threadItemId ThreadItemIdentifier.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ return $this->_handleReaction($threadId, $threadItemId, $reactionType, 'created', $options);
+ }
+
+ /**
+ * Share an existing story post via direct message to a user's inbox.
+ *
+ * You are able to share your own stories, as well as public stories from
+ * other people.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $storyId The story ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $reelId The reel ID in Instagram's internal format (ie "highlight:12970012453081168")
+ * @param array $options An associative array of additional parameters, including:
+ * "media_type" (required) - either "photo" or "video";
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemsResponse
+ *
+ * @see https://help.instagram.com/188382041703187 For more information.
+ */
+ public function sendStory(
+ array $recipients,
+ $storyId,
+ $reelId = null,
+ array $options = [])
+ {
+ if (!preg_match('#^\d+_\d+$#D', $storyId)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid story ID.', $storyId));
+ }
+ if ($reelId !== null) {
+ if (!preg_match('#^highlight:\d+$#D', $reelId)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid reel ID.', $reelId));
+ }
+ $options = array_merge($options,
+ [
+ 'reel_id' => $reelId,
+ ]);
+ }
+ if (!isset($options['media_type'])) {
+ throw new \InvalidArgumentException('Please provide media_type in options.');
+ }
+ if ($options['media_type'] !== 'photo' && $options['media_type'] !== 'video') {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media_type.', $options['media_type']));
+ }
+
+ return $this->_sendDirectItems('story_share', $recipients, array_merge($options, [
+ 'story_media_id' => $storyId,
+ ]));
+ }
+
+ /**
+ * Share an occurring or archived live stream via direct message to a user's inbox.
+ *
+ * You are able to share your own broadcasts, as well as broadcasts from
+ * other people.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response\DirectSendItemResponse
+ */
+ public function sendLive(
+ array $recipients,
+ $broadcastId,
+ array $options = [])
+ {
+ return $this->_sendDirectItem('live', $recipients, array_merge($options, [
+ 'broadcast_id' => $broadcastId,
+ ]));
+ }
+
+ /**
+ * Delete a reaction to an existing thread item.
+ *
+ * @param string $threadId Thread identifier.
+ * @param string $threadItemId ThreadItemIdentifier.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function deleteReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ return $this->_handleReaction($threadId, $threadItemId, $reactionType, 'deleted', $options);
+ }
+
+ /**
+ * Delete an item from given thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread item ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function deleteItem(
+ $threadId,
+ $threadItemId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/items/{$threadItemId}/delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Marks an item from given thread as seen.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread item ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSeenItemResponse
+ */
+ public function markItemSeen(
+ $threadId,
+ $threadItemId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/items/{$threadItemId}/seen/")
+ ->addPost('use_unified_inbox', 'true')
+ ->addPost('action', 'mark_seen')
+ ->addPost('thread_id', $threadId)
+ ->addPost('item_id', $threadItemId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\DirectSeenItemResponse());
+ }
+
+ /**
+ * Marks visual items from given thread as seen.
+ *
+ * `NOTE:` This "visual" endpoint is only used for Direct stories.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|string[] $threadItemIds One or more thread item IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function markVisualItemsSeen(
+ $threadId,
+ $threadItemIds)
+ {
+ if (!is_array($threadItemIds)) {
+ $threadItemIds = [$threadItemIds];
+ } elseif (!count($threadItemIds)) {
+ throw new \InvalidArgumentException('Please provide at least one thread item ID.');
+ }
+
+ return $this->ig->request("direct_v2/visual_threads/{$threadId}/item_seen/")
+ ->addPost('item_ids', '['.implode(',', $threadItemIds).']')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Marks visual items from given thread as replayed.
+ *
+ * `NOTE:` This "visual" endpoint is only used for Direct stories.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|string[] $threadItemIds One or more thread item IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function markVisualItemsReplayed(
+ $threadId,
+ $threadItemIds)
+ {
+ if (!is_array($threadItemIds)) {
+ $threadItemIds = [$threadItemIds];
+ } elseif (!count($threadItemIds)) {
+ throw new \InvalidArgumentException('Please provide at least one thread item ID.');
+ }
+
+ return $this->ig->request("direct_v2/visual_threads/{$threadId}/item_replayed/")
+ ->addPost('item_ids', '['.implode(',', $threadItemIds).']')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Validate and prepare recipients for direct messaging.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param bool $useQuotes Whether to put IDs into quotes.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return array
+ */
+ protected function _prepareRecipients(
+ array $recipients,
+ $useQuotes)
+ {
+ $result = [];
+ // users
+ if (isset($recipients['users'])) {
+ if (!is_array($recipients['users'])) {
+ throw new \InvalidArgumentException('"users" must be an array.');
+ }
+ foreach ($recipients['users'] as $userId) {
+ if (!is_scalar($userId)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ } elseif (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $userId));
+ }
+ }
+ // Although this is an array of groups, you will get "Only one group is supported." error
+ // if you will try to use more than one group here.
+ // We can't use json_encode() here, because each user id must be a number.
+ $result['users'] = '[['.implode(',', $recipients['users']).']]';
+ }
+ // thread
+ if (isset($recipients['thread'])) {
+ if (!is_scalar($recipients['thread'])) {
+ throw new \InvalidArgumentException('Thread identifier must be scalar.');
+ } elseif (!ctype_digit($recipients['thread']) && (!is_int($recipients['thread']) || $recipients['thread'] < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $recipients['thread']));
+ }
+ // Although this is an array, you will get "Need to specify thread ID or recipient users." error
+ // if you will try to use more than one thread identifier here.
+ if (!$useQuotes) {
+ // We can't use json_encode() here, because thread id must be a number.
+ $result['thread'] = '['.$recipients['thread'].']';
+ } else {
+ // We can't use json_encode() here, because thread id must be a string.
+ $result['thread'] = '["'.$recipients['thread'].'"]';
+ }
+ }
+ if (!count($result)) {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ } elseif (isset($result['thread']) && isset($result['users'])) {
+ throw new \InvalidArgumentException('You can not mix "users" with "thread".');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Send a direct message to specific users or thread.
+ *
+ * @param string $type One of: "message", "like", "hashtag", "location", "profile", "photo",
+ * "video", "links", "live".
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param array $options Depends on $type:
+ * "message" uses "client_context" and "text";
+ * "like" uses "client_context";
+ * "hashtag" uses "client_context", "hashtag" and "text";
+ * "location" uses "client_context", "venue_id" and "text";
+ * "profile" uses "client_context", "profile_user_id" and "text";
+ * "photo" uses "client_context" and "filepath";
+ * "video" uses "client_context", "upload_id" and "video_result";
+ * "links" uses "client_context", "link_text" and "link_urls".
+ * "live" uses "client_context" and "text".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ protected function _sendDirectItem(
+ $type,
+ array $recipients,
+ array $options = [])
+ {
+ // Most requests are unsigned, but some use signing by overriding this.
+ $signedPost = false;
+
+ // Handle the request...
+ switch ($type) {
+ case 'message':
+ $request = $this->ig->request('direct_v2/threads/broadcast/text/');
+ // Check and set text.
+ if (!isset($options['text'])) {
+ throw new \InvalidArgumentException('No text message provided.');
+ }
+ $request->addPost('text', $options['text']);
+ break;
+ case 'like':
+ $request = $this->ig->request('direct_v2/threads/broadcast/like/');
+ break;
+ case 'hashtag':
+ $request = $this->ig->request('direct_v2/threads/broadcast/hashtag/');
+ // Check and set hashtag.
+ if (!isset($options['hashtag'])) {
+ throw new \InvalidArgumentException('No hashtag provided.');
+ }
+ $request->addPost('hashtag', $options['hashtag']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ case 'location':
+ $request = $this->ig->request('direct_v2/threads/broadcast/location/');
+ // Check and set venue_id.
+ if (!isset($options['venue_id'])) {
+ throw new \InvalidArgumentException('No venue_id provided.');
+ }
+ $request->addPost('venue_id', $options['venue_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ case 'profile':
+ $request = $this->ig->request('direct_v2/threads/broadcast/profile/');
+ // Check and set profile_user_id.
+ if (!isset($options['profile_user_id'])) {
+ throw new \InvalidArgumentException('No profile_user_id provided.');
+ }
+ $request->addPost('profile_user_id', $options['profile_user_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ case 'photo':
+ $request = $this->ig->request('direct_v2/threads/broadcast/upload_photo/');
+ // Check and set filepath.
+ if (!isset($options['filepath'])) {
+ throw new \InvalidArgumentException('No filepath provided.');
+ }
+ $request->addFile('photo', $options['filepath'], 'direct_temp_photo_'.Utils::generateUploadId().'.jpg');
+ break;
+ case 'video':
+ $request = $this->ig->request('direct_v2/threads/broadcast/configure_video/');
+ // Check and set upload_id.
+ if (!isset($options['upload_id'])) {
+ throw new \InvalidArgumentException('No upload_id provided.');
+ }
+ $request->addPost('upload_id', $options['upload_id']);
+ // Set video_result if provided.
+ if (isset($options['video_result'])) {
+ $request->addPost('video_result', $options['video_result']);
+ }
+ break;
+ case 'links':
+ $request = $this->ig->request('direct_v2/threads/broadcast/link/');
+ // Check and set link_urls.
+ if (!isset($options['link_urls'])) {
+ throw new \InvalidArgumentException('No link_urls provided.');
+ }
+ $request->addPost('link_urls', $options['link_urls']);
+ // Check and set link_text.
+ if (!isset($options['link_text'])) {
+ throw new \InvalidArgumentException('No link_text provided.');
+ }
+ $request->addPost('link_text', $options['link_text']);
+ break;
+ case 'reaction':
+ $request = $this->ig->request('direct_v2/threads/broadcast/reaction/');
+ // Check and set reaction_type.
+ if (!isset($options['reaction_type'])) {
+ throw new \InvalidArgumentException('No reaction_type provided.');
+ }
+ $request->addPost('reaction_type', $options['reaction_type']);
+ // Check and set reaction_status.
+ if (!isset($options['reaction_status'])) {
+ throw new \InvalidArgumentException('No reaction_status provided.');
+ }
+ $request->addPost('reaction_status', $options['reaction_status']);
+ // Check and set item_id.
+ if (!isset($options['item_id'])) {
+ throw new \InvalidArgumentException('No item_id provided.');
+ }
+ $request->addPost('item_id', $options['item_id']);
+ // Check and set node_type.
+ if (!isset($options['node_type'])) {
+ throw new \InvalidArgumentException('No node_type provided.');
+ }
+ $request->addPost('node_type', $options['node_type']);
+ break;
+ case 'live':
+ $request = $this->ig->request('direct_v2/threads/broadcast/live_viewer_invite/');
+ // Check and set broadcast id.
+ if (!isset($options['broadcast_id'])) {
+ throw new \InvalidArgumentException('No broadcast_id provided.');
+ }
+ $request->addPost('broadcast_id', $options['broadcast_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException('Unsupported _sendDirectItem() type.');
+ }
+
+ // Add recipients.
+ $recipients = $this->_prepareRecipients($recipients, false);
+ if (isset($recipients['users'])) {
+ $request->addPost('recipient_users', $recipients['users']);
+ } elseif (isset($recipients['thread'])) {
+ $request->addPost('thread_ids', $recipients['thread']);
+ } else {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ }
+
+ // Handle client_context.
+ if (!isset($options['client_context'])) {
+ // WARNING: Must be random every time otherwise we can only
+ // make a single post per direct-discussion thread.
+ $options['client_context'] = Signatures::generateUUID(true);
+ } elseif (!Signatures::isValidUUID($options['client_context'])) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
+ }
+
+ // Add some additional data if signed post.
+ if ($signedPost) {
+ $request->addPost('_uid', $this->ig->account_id);
+ }
+
+ // Execute the request with all data used by both signed and unsigned.
+ return $request->setSignedPost($signedPost)
+ ->addPost('action', 'send_item')
+ ->addPost('client_context', $options['client_context'])
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\DirectSendItemResponse());
+ }
+
+ /**
+ * Send a direct messages to specific users or thread.
+ *
+ * @param string $type One of: "media_share", "story_share".
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param array $options Depends on $type:
+ * "media_share" uses "client_context", "media_id", "media_type" and "text";
+ * "story_share" uses "client_context", "story_media_id", "media_type" and "text".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemsResponse
+ */
+ protected function _sendDirectItems(
+ $type,
+ array $recipients,
+ array $options = [])
+ {
+ // Most requests are unsigned, but some use signing by overriding this.
+ $signedPost = false;
+
+ // Handle the request...
+ switch ($type) {
+ case 'media_share':
+ $request = $this->ig->request('direct_v2/threads/broadcast/media_share/');
+ // Check and set media_id.
+ if (!isset($options['media_id'])) {
+ throw new \InvalidArgumentException('No media_id provided.');
+ }
+ $request->addPost('media_id', $options['media_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ // Check and set media_type.
+ if (isset($options['media_type']) && $options['media_type'] === 'video') {
+ $request->addParam('media_type', 'video');
+ } else {
+ $request->addParam('media_type', 'photo');
+ }
+ break;
+ case 'story_share':
+ $signedPost = true; // This must be a signed post!
+ $request = $this->ig->request('direct_v2/threads/broadcast/story_share/');
+ // Check and set story_media_id.
+ if (!isset($options['story_media_id'])) {
+ throw new \InvalidArgumentException('No story_media_id provided.');
+ }
+ $request->addPost('story_media_id', $options['story_media_id']);
+ // Set text if provided.
+ if (isset($options['reel_id'])) {
+ $request->addPost('reel_id', $options['reel_id']);
+ }
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ // Check and set media_type.
+ if (isset($options['media_type']) && $options['media_type'] === 'video') {
+ $request->addParam('media_type', 'video');
+ } else {
+ $request->addParam('media_type', 'photo');
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException('Unsupported _sendDirectItems() type.');
+ }
+
+ // Add recipients.
+ $recipients = $this->_prepareRecipients($recipients, false);
+ if (isset($recipients['users'])) {
+ $request->addPost('recipient_users', $recipients['users']);
+ } elseif (isset($recipients['thread'])) {
+ $request->addPost('thread_ids', $recipients['thread']);
+ } else {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ }
+
+ // Handle client_context.
+ if (!isset($options['client_context'])) {
+ // WARNING: Must be random every time otherwise we can only
+ // make a single post per direct-discussion thread.
+ $options['client_context'] = Signatures::generateUUID(true);
+ } elseif (!Signatures::isValidUUID($options['client_context'])) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
+ }
+
+ // Add some additional data if signed post.
+ if ($signedPost) {
+ $request->addPost('_uid', $this->ig->account_id);
+ }
+
+ // Execute the request with all data used by both signed and unsigned.
+ return $request->setSignedPost($signedPost)
+ ->addPost('action', 'send_item')
+ ->addPost('unified_broadcast_format', '1')
+ ->addPost('client_context', $options['client_context'])
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\DirectSendItemsResponse());
+ }
+
+ /**
+ * Handle a reaction to an existing thread item.
+ *
+ * @param string $threadId Thread identifier.
+ * @param string $threadItemId ThreadItemIdentifier.
+ * @param string $reactionType One of: "like".
+ * @param string $reactionStatus One of: "created", "deleted".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ protected function _handleReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ $reactionStatus,
+ array $options = [])
+ {
+ if (!ctype_digit($threadId) && (!is_int($threadId) || $threadId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread ID.', $threadId));
+ }
+ if (!ctype_digit($threadItemId) && (!is_int($threadItemId) || $threadItemId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread item ID.', $threadItemId));
+ }
+ if (!in_array($reactionType, ['like'], true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction type.', $reactionType));
+ }
+
+ return $this->_sendDirectItem('reaction', ['thread' => $threadId], array_merge($options, [
+ 'reaction_type' => $reactionType,
+ 'reaction_status' => $reactionStatus,
+ 'item_id' => $threadItemId,
+ 'node_type' => 'item',
+ ]));
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Discover.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Discover.php
new file mode 100755
index 0000000..e404df1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Discover.php
@@ -0,0 +1,192 @@
+ig->request('discover/topical_explore/')
+ ->addParam('is_prefetch', $isPrefetch)
+ ->addParam('omit_cover_media', true)
+ ->addParam('use_sectional_payload', true)
+ ->addParam('timezone_offset', date('Z'))
+ ->addParam('session_id', $this->ig->session_id)
+ ->addParam('include_fixed_destinations', true);
+ if ($clusterId !== null) {
+ $request->addParam('cluster_id', $clusterId);
+ }
+ if (!$isPrefetch) {
+ if ($maxId === null) {
+ $maxId = 0;
+ }
+ $request->addParam('max_id', $maxId);
+ $request->addParam('module', 'explore_popular');
+ }
+
+ return $request->getResponse(new Response\ExploreResponse());
+ }
+
+ /**
+ * Report media in the Explore-feed.
+ *
+ * @param string $exploreSourceToken Token related to the Explore media.
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReportExploreMediaResponse
+ */
+ public function reportExploreMedia(
+ $exploreSourceToken,
+ $userId)
+ {
+ return $this->ig->request('discover/explore_report/')
+ ->addParam('explore_source_token', $exploreSourceToken)
+ ->addParam('m_pk', $this->ig->account_id)
+ ->addParam('a_pk', $userId)
+ ->getResponse(new Response\ReportExploreMediaResponse());
+ }
+
+ /**
+ * Search for Instagram users, hashtags and places via Facebook's algorithm.
+ *
+ * This performs a combined search for "top results" in all 3 areas at once.
+ *
+ * @param string $query The username/full name, hashtag or location to search for.
+ * @param string $latitude (optional) Latitude.
+ * @param string $longitude (optional) Longitude.
+ * @param array $excludeList Array of grouped numerical entity IDs (ie "users" => ["4021088339"])
+ * to exclude from the response, allowing you to skip entities
+ * from a previous call to get more results. The following entities are supported:
+ * "users", "places", "tags".
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FBSearchResponse
+ *
+ * @see FBSearchResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For a rank token example (but with a different type of exclude list).
+ */
+ public function search(
+ $query,
+ $latitude = null,
+ $longitude = null,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation.
+ if (!is_string($query) || $query === '') {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+ $request = $this->_paginateWithMultiExclusion(
+ $this->ig->request('fbsearch/topsearch_flat/')
+ ->addParam('context', 'blended')
+ ->addParam('query', $query)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken
+ );
+
+ if ($latitude !== null && $longitude !== null) {
+ $request
+ ->addParam('lat', $latitude)
+ ->addParam('lng', $longitude);
+ }
+
+ try {
+ /** @var Response\FBSearchResponse $result */
+ $result = $request->getResponse(new Response\FBSearchResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\FBSearchResponse([
+ 'has_more' => false,
+ 'hashtags' => [],
+ 'users' => [],
+ 'places' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get search suggestions via Facebook's algorithm.
+ *
+ * NOTE: In the app, they're listed as the "Suggested" in the "Top" tab at the "Search" screen.
+ *
+ * @param string $type One of: "blended", "users", "hashtags" or "places".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedSearchesResponse
+ */
+ public function getSuggestedSearches(
+ $type)
+ {
+ if (!in_array($type, ['blended', 'users', 'hashtags', 'places'], true)) {
+ throw new \InvalidArgumentException(sprintf('Unknown search type: %s.', $type));
+ }
+
+ return $this->ig->request('fbsearch/suggested_searches/')
+ ->addParam('type', $type)
+ ->getResponse(new Response\SuggestedSearchesResponse());
+ }
+
+ /**
+ * Get recent searches via Facebook's algorithm.
+ *
+ * NOTE: In the app, they're listed as the "Recent" in the "Top" tab at the "Search" screen.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RecentSearchesResponse
+ */
+ public function getRecentSearches()
+ {
+ return $this->ig->request('fbsearch/recent_searches/')
+ ->getResponse(new Response\RecentSearchesResponse());
+ }
+
+ /**
+ * Clear the search history.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function clearSearchHistory()
+ {
+ return $this->ig->request('fbsearch/clear_search_history/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Hashtag.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Hashtag.php
new file mode 100755
index 0000000..0e702eb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Hashtag.php
@@ -0,0 +1,375 @@
+ig->request("tags/{$urlHashtag}/info/")
+ ->getResponse(new Response\TagInfoResponse());
+ }
+
+ /**
+ * Get hashtag story.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagsStoryResponse
+ */
+ public function getStory(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/{$urlHashtag}/story/")
+ ->getResponse(new Response\TagsStoryResponse());
+ }
+
+ /**
+ * Get hashtags from a section.
+ *
+ * Available tab sections: 'top', 'recent' or 'places'.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ * @param string $rankToken The feed UUID. You must use the same value for all pages of the feed.
+ * @param string|null $tab Section tab for hashtags.
+ * @param int[]|null $nextMediaIds Used for pagination.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagFeedResponse
+ */
+ public function getSection(
+ $hashtag,
+ $rankToken,
+ $tab = null,
+ $nextMediaIds = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+
+ $request = $this->ig->request("tags/{$urlHashtag}/sections/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('rank_token', $rankToken)
+ ->addPost('include_persistent', true);
+
+ if ($tab !== null) {
+ if ($tab !== 'top' && $tab !== 'recent' && $tab !== 'places' && $tab !== 'discover') {
+ throw new \InvalidArgumentException('Tab section must be \'top\', \'recent\', \'places\' or \'discover\'.');
+ }
+ $request->addPost('tab', $tab);
+ } else {
+ $request->addPost('supported_tabs', '["top","recent","places","discover"]');
+ }
+
+ if ($nextMediaIds !== null) {
+ if (!is_array($nextMediaIds) || !array_filter($nextMediaIds, 'is_int')) {
+ throw new \InvalidArgumentException('Next media IDs must be an Int[].');
+ }
+ $request->addPost('next_media_ids', json_encode($nextMediaIds));
+ }
+ if ($maxId !== null) {
+ $request->addPost('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\TagFeedResponse());
+ }
+
+ /**
+ * Search for hashtags.
+ *
+ * Gives you search results ordered by best matches first.
+ *
+ * Note that you can get more than one "page" of hashtag search results by
+ * excluding the numerical IDs of all tags from a previous search query.
+ *
+ * Also note that the excludes must be done via Instagram's internal,
+ * numerical IDs for the tags, which you can get from this search-response.
+ *
+ * Lastly, be aware that they will never exclude any tag that perfectly
+ * matches your search query, even if you provide its exact ID too.
+ *
+ * @param string $query Finds hashtags containing this string.
+ * @param string[]|int[] $excludeList Array of numerical hashtag IDs (ie "17841562498105353")
+ * to exclude from the response, allowing you to skip tags
+ * from a previous call to get more results.
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException If invalid query or
+ * trying to exclude too
+ * many hashtags.
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SearchTagResponse
+ *
+ * @see SearchTagResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function search(
+ $query,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation. Do NOT use throwIfInvalidHashtag here.
+ if (!is_string($query) || $query === '') {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+
+ $request = $this->_paginateWithExclusion(
+ $this->ig->request('tags/search/')
+ ->addParam('q', $query)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken
+ );
+
+ try {
+ /** @var Response\SearchTagResponse $result */
+ $result = $request->getResponse(new Response\SearchTagResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\SearchTagResponse([
+ 'has_more' => false,
+ 'results' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Follow hashtag.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagRelatedResponse
+ */
+ public function follow(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/follow/{$urlHashtag}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unfollow hashtag.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagRelatedResponse
+ */
+ public function unfollow(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/unfollow/{$urlHashtag}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get related hashtags.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagRelatedResponse
+ */
+ public function getRelated(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/{$urlHashtag}/related/")
+ ->addParam('visited', '[{"id":"'.$hashtag.'","type":"hashtag"}]')
+ ->addParam('related_types', '["hashtag"]')
+ ->getResponse(new Response\TagRelatedResponse());
+ }
+
+ /**
+ * Get the feed for a hashtag.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ * @param string $rankToken The feed UUID. You must use the same value for all pages of the feed.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagFeedResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFeed(
+ $hashtag,
+ $rankToken,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ Utils::throwIfInvalidRankToken($rankToken);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ $hashtagFeed = $this->ig->request("feed/tag/{$urlHashtag}/")
+ ->addParam('rank_token', $rankToken);
+ if ($maxId !== null) {
+ $hashtagFeed->addParam('max_id', $maxId);
+ }
+
+ return $hashtagFeed->getResponse(new Response\TagFeedResponse());
+ }
+
+ /**
+ * Get list of tags that a user is following.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HashtagsResponse
+ */
+ public function getFollowing(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/following_tags_info/")
+ ->getResponse(new Response\HashtagsResponse());
+ }
+
+ /**
+ * Get list of tags that you are following.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HashtagsResponse
+ */
+ public function getSelfFollowing()
+ {
+ return $this->getFollowing($this->ig->account_id);
+ }
+
+ /**
+ * Get list of tags that are suggested to follow to.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HashtagsResponse
+ */
+ public function getFollowSuggestions()
+ {
+ return $this->ig->request('tags/suggested/')
+ ->getResponse(new Response\HashtagsResponse());
+ }
+
+ /**
+ * Mark TagFeedResponse story media items as seen.
+ *
+ * The "story" property of a `TagFeedResponse` only gives you a list of
+ * story media. It doesn't actually mark any stories as "seen", so the
+ * user doesn't know that you've seen their story. Actually marking the
+ * story as "seen" is done via this endpoint instead. The official app
+ * calls this endpoint periodically (with 1 or more items at a time)
+ * while watching a story.
+ *
+ * This tells the user that you've seen their story, and also helps
+ * Instagram know that it shouldn't give you those seen stories again
+ * if you request the same hashtag feed multiple times.
+ *
+ * Tip: You can pass in the whole "getItems()" array from the hashtag's
+ * "story" property, to easily mark all of the TagFeedResponse's story
+ * media items as seen.
+ *
+ * @param Response\TagFeedResponse $hashtagFeed The hashtag feed response
+ * object which the story media
+ * items came from. The story
+ * items MUST belong to it.
+ * @param Response\Model\Item[] $items Array of one or more story
+ * media Items.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Story::markMediaSeen()
+ * @see Location::markStoryMediaSeen()
+ */
+ public function markStoryMediaSeen(
+ Response\TagFeedResponse $hashtagFeed,
+ array $items)
+ {
+ // Extract the Hashtag Story-Tray ID from the user's hashtag response.
+ // NOTE: This can NEVER fail if the user has properly given us the exact
+ // same hashtag response that they got the story items from!
+ $sourceId = '';
+ if ($hashtagFeed->getStory() instanceof Response\Model\StoryTray) {
+ $sourceId = $hashtagFeed->getStory()->getId();
+ }
+ if (!strlen($sourceId)) {
+ throw new \InvalidArgumentException('Your provided TagFeedResponse is invalid and does not contain any Hashtag Story-Tray ID.');
+ }
+
+ // Ensure they only gave us valid items for this hashtag response.
+ // NOTE: We validate since people cannot be trusted to use their brain.
+ $validIds = [];
+ foreach ($hashtagFeed->getStory()->getItems() as $item) {
+ $validIds[$item->getId()] = true;
+ }
+ foreach ($items as $item) {
+ // NOTE: We only check Items here. Other data is rejected by Internal.
+ if ($item instanceof Response\Model\Item && !isset($validIds[$item->getId()])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The item with ID "%s" does not belong to this TagFeedResponse.',
+ $item->getId()
+ ));
+ }
+ }
+
+ // Mark the story items as seen, with the hashtag as source ID.
+ return $this->ig->internal->markStoryMediaSeen($items, $sourceId);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Highlight.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Highlight.php
new file mode 100755
index 0000000..ba0109e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Highlight.php
@@ -0,0 +1,173 @@
+ig->request("highlights/{$userId}/highlights_tray/")
+ ->addParam('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addParam('phone_id', $this->ig->phone_id)
+ ->addParam('battery_level', '100')
+ ->addParam('is_charging', '1')
+ ->addParam('will_sound_on', '1')
+ ->getResponse(new Response\HighlightFeedResponse());
+ }
+
+ /**
+ * Get self highlight feed.
+ *
+ * NOTE: Sometimes, a highlight doesn't have any `items` property. Read
+ * `Highlight::getUserFeed()` for more information about what to do.
+ * Note 2: if user has a igtv post reponse will include 'tv_channel' property
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HighlightFeedResponse
+ *
+ * @see Highlight::getUserFeed()
+ * @see Story::getReelsMediaFeed() To get highlight items when they aren't included in this response.
+ */
+ public function getSelfUserFeed()
+ {
+ return $this->getUserFeed($this->ig->account_id);
+ }
+
+ /**
+ * Create a highlight reel.
+ *
+ * @param string[] $mediaIds Array with one or more media IDs in Instagram's internal format (ie ["3482384834_43294"]).
+ * @param string $title Title for the highlight.
+ * @param string|null $coverMediaId One media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $module
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateHighlightResponse
+ */
+ public function create(
+ array $mediaIds,
+ $title = 'Highlights',
+ $coverMediaId = null,
+ $module = 'self_profile')
+ {
+ if (empty($mediaIds)) {
+ throw new \InvalidArgumentException('You must provide at least one media ID.');
+ }
+ if ($coverMediaId === null) {
+ $coverMediaId = reset($mediaIds);
+ }
+ if ($title === null || $title === '') {
+ $title = 'Highlights';
+ } elseif (mb_strlen($title, 'utf8') > 16) {
+ throw new \InvalidArgumentException('Title must be between 1 and 16 characters.');
+ }
+
+ $cover = [
+ 'media_id' => $coverMediaId,
+ ];
+
+ return $this->ig->request('highlights/create_reel/')
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('source', $module)
+ ->addPost('creation_id', round(microtime(true) * 1000))
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('cover', json_encode($cover))
+ ->addPost('title', $title)
+ ->addPost('media_ids', json_encode(array_values($mediaIds)))
+ ->getResponse(new Response\CreateHighlightResponse());
+ }
+
+ /**
+ * Edit a highlight reel.
+ *
+ * @param string $highlightReelId Highlight ID, using internal format (ie "highlight:12345678901234567").
+ * @param array $params User-provided highlight key-value pairs. string 'title', string 'cover_media_id', string[] 'add_media', string[] 'remove_media'.
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HighlightFeedResponse
+ */
+ public function edit(
+ $highlightReelId,
+ array $params,
+ $module = 'self_profile')
+ {
+ if (!isset($params['cover_media_id'])) {
+ throw new \InvalidArgumentException('You must provide one media ID for the cover.');
+ }
+ if (!isset($params['title'])) {
+ $params['title'] = 'Highlights';
+ } elseif (mb_strlen($params['title'], 'utf8') > 16) {
+ throw new \InvalidArgumentException('Title length must be between 1 and 16 characters.');
+ }
+ if (!isset($params['add_media']) || !is_array($params['add_media'])) {
+ $params['add_media'] = [];
+ }
+ if (!isset($params['remove_media']) || !is_array($params['remove_media'])) {
+ $params['remove_media'] = [];
+ }
+ $cover = [
+ 'media_id' => $params['cover_media_id'],
+ ];
+
+ return $this->ig->request("highlights/{$highlightReelId}/edit_reel/")
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('source', $module)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('title', $params['title'])
+ ->addPost('cover', json_encode($cover))
+ ->addPost('added_media_ids', json_encode(array_values($params['add_media'])))
+ ->addPost('removed_media_ids', json_encode(array_values($params['remove_media'])))
+ ->getResponse(new Response\HighlightFeedResponse());
+ }
+
+ /**
+ * Delete a highlight reel.
+ *
+ * @param string $highlightReelId Highlight ID, using internal format (ie "highlight:12345678901234567").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function delete(
+ $highlightReelId)
+ {
+ return $this->ig->request("highlights/{$highlightReelId}/delete_reel/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Internal.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Internal.php
new file mode 100755
index 0000000..8b5a031
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Internal.php
@@ -0,0 +1,2596 @@
+getPhotoDetails() === null) {
+ $internalMetadata->setPhotoDetails($targetFeed, $photoFilename);
+ }
+ } catch (\Exception $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Failed to get photo details: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ // Perform the upload.
+ $this->uploadPhotoData($targetFeed, $internalMetadata);
+
+ // Configure the uploaded image and attach it to our timeline/story.
+ $configure = $this->configureSinglePhoto($targetFeed, $internalMetadata, $externalMetadata);
+
+ return $configure;
+ }
+
+ /**
+ * Upload the data for a photo to Instagram.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException
+ */
+ public function uploadPhotoData(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // Make sure we disallow some feeds for this function.
+ if ($targetFeed === Constants::FEED_DIRECT) {
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Make sure we have photo details.
+ if ($internalMetadata->getPhotoDetails() === null) {
+ throw new \InvalidArgumentException('Photo details are missing from the internal metadata.');
+ }
+
+ try {
+ // Upload photo file with one of our photo uploaders.
+ if ($this->_useResumablePhotoUploader($targetFeed, $internalMetadata)) {
+ $this->_uploadResumablePhoto($targetFeed, $internalMetadata);
+ } else {
+ $internalMetadata->setPhotoUploadResponse(
+ $this->_uploadPhotoInOnePiece($targetFeed, $internalMetadata)
+ );
+ }
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf(
+ 'Upload of "%s" failed: %s',
+ $internalMetadata->getPhotoDetails()->getBasename(),
+ $e->getMessage()
+ ),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Configures parameters for a *SINGLE* uploaded photo file.
+ *
+ * WARNING TO CONTRIBUTORS: THIS IS ONLY FOR *TIMELINE* AND *STORY* -PHOTOS-.
+ * USE "configureTimelineAlbum()" FOR ALBUMS and "configureSingleVideo()" FOR VIDEOS.
+ * AND IF FUTURE INSTAGRAM FEATURES NEED CONFIGURATION AND ARE NON-TRIVIAL,
+ * GIVE THEM THEIR OWN FUNCTION LIKE WE DID WITH "configureTimelineAlbum()",
+ * TO AVOID ADDING BUGGY AND UNMAINTAINABLE SPIDERWEB CODE!
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ */
+ public function configureSinglePhoto(
+ $targetFeed,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ // Determine the target endpoint for the photo.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $endpoint = 'media/configure/';
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ case Constants::FEED_STORY:
+ $endpoint = 'media/configure_to_story/';
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Available external metadata parameters:
+ /** @var string Caption to use for the media. */
+ $captionText = isset($externalMetadata['caption']) ? $externalMetadata['caption'] : '';
+ /** @var string Accesibility caption to use for the media. */
+ $altText = isset($externalMetadata['custom_accessibility_caption']) ? $externalMetadata['custom_accessibility_caption'] : null;
+ /** @var Response\Model\Location|null A Location object describing where
+ * the media was taken. */
+ $location = (isset($externalMetadata['location'])) ? $externalMetadata['location'] : null;
+ /** @var array|null Array of story location sticker instructions. ONLY
+ * USED FOR STORY MEDIA! */
+ $locationSticker = (isset($externalMetadata['location_sticker']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['location_sticker'] : null;
+ /** @var array|null Array of usertagging instructions, in the format
+ * [['position'=>[0.5,0.5], 'user_id'=>'123'], ...]. ONLY FOR TIMELINE PHOTOS! */
+ $usertags = (isset($externalMetadata['usertags']) && $targetFeed == Constants::FEED_TIMELINE) ? $externalMetadata['usertags'] : null;
+ /** @var string|null Link to attach to the media. ONLY USED FOR STORY MEDIA,
+ * AND YOU MUST HAVE A BUSINESS INSTAGRAM ACCOUNT TO POST A STORY LINK! */
+ $link = (isset($externalMetadata['link']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['link'] : null;
+ /** @var void Photo filter. THIS DOES NOTHING! All real filters are done in the mobile app. */
+ // $filter = isset($externalMetadata['filter']) ? $externalMetadata['filter'] : null;
+ $filter = null; // COMMENTED OUT SO USERS UNDERSTAND THEY CAN'T USE THIS!
+ /** @var array Hashtags to use for the media. ONLY STORY MEDIA! */
+ $hashtags = (isset($externalMetadata['hashtags']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['hashtags'] : null;
+ /** @var array Mentions to use for the media. ONLY STORY MEDIA! */
+ $storyMentions = (isset($externalMetadata['story_mentions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_mentions'] : null;
+ /** @var array Story poll to use for the media. ONLY STORY MEDIA! */
+ $storyPoll = (isset($externalMetadata['story_polls']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_polls'] : null;
+ /** @var array Story slider to use for the media. ONLY STORY MEDIA! */
+ $storySlider = (isset($externalMetadata['story_sliders']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_sliders'] : null;
+ /** @var array Story question to use for the media. ONLY STORY MEDIA */
+ $storyQuestion = (isset($externalMetadata['story_questions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_questions'] : null;
+ /** @var array Story countdown to use for the media. ONLY STORY MEDIA */
+ $storyCountdown = (isset($externalMetadata['story_countdowns']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_countdowns'] : null;
+ /** @var array Story fundraiser to use for the media. ONLY STORY MEDIA */
+ $storyFundraisers = (isset($externalMetadata['story_fundraisers']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_fundraisers'] : null;
+ /** @var array Attached media used to share media to story feed. ONLY STORY MEDIA! */
+ $attachedMedia = (isset($externalMetadata['attached_media']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['attached_media'] : null;
+ /** @var array Product Tags to use for the media. ONLY FOR TIMELINE PHOTOS! */
+ $productTags = (isset($externalMetadata['product_tags']) && $targetFeed == Constants::FEED_TIMELINE) ? $externalMetadata['product_tags'] : null;
+
+ // Fix very bad external user-metadata values.
+ if (!is_string($captionText)) {
+ $captionText = '';
+ }
+
+ // Critically important internal library-generated metadata parameters:
+ /** @var string The ID of the entry to configure. */
+ $uploadId = $internalMetadata->getUploadId();
+ /** @var int Width of the photo. */
+ $photoWidth = $internalMetadata->getPhotoDetails()->getWidth();
+ /** @var int Height of the photo. */
+ $photoHeight = $internalMetadata->getPhotoDetails()->getHeight();
+
+ // Build the request...
+ $request = $this->ig->request($endpoint)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('edits',
+ [
+ 'crop_original_size' => [(float) $photoWidth, (float) $photoHeight],
+ 'crop_zoom' => 1.0,
+ 'crop_center' => [0.0, -0.0],
+ ])
+ ->addPost('device',
+ [
+ 'manufacturer' => $this->ig->device->getManufacturer(),
+ 'model' => $this->ig->device->getModel(),
+ 'android_version' => $this->ig->device->getAndroidVersion(),
+ 'android_release' => $this->ig->device->getAndroidRelease(),
+ ])
+ ->addPost('extra',
+ [
+ 'source_width' => $photoWidth,
+ 'source_height' => $photoHeight,
+ ]);
+
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $date = date('Y:m:d H:i:s');
+ $request
+ ->addParam('timezone_offset', date('Z'))
+ ->addPost('date_time_original', $date)
+ ->addPost('date_time_digitalized', $date)
+ ->addPost('caption', $captionText)
+ ->addPost('source_type', '4')
+ ->addPost('media_folder', 'Camera')
+ ->addPost('upload_id', $uploadId);
+
+ if ($usertags !== null) {
+ Utils::throwIfInvalidUsertags($usertags);
+ $request->addPost('usertags', json_encode($usertags));
+ }
+ if ($productTags !== null) {
+ Utils::throwIfInvalidProductTags($productTags);
+ $request->addPost('product_tags', json_encode($productTags));
+ }
+ if ($altText !== null) {
+ $request->addPost('custom_accessibility_caption', $altText);
+ }
+ break;
+ case Constants::FEED_STORY:
+ if ($internalMetadata->isBestieMedia()) {
+ $request->addPost('audience', 'besties');
+ }
+
+ $request
+ ->addPost('client_shared_at', (string) time())
+ ->addPost('source_type', '3')
+ ->addPost('configure_mode', '1')
+ ->addPost('client_timestamp', (string) (time() - mt_rand(3, 10)))
+ ->addPost('upload_id', $uploadId);
+
+ if (is_string($link) && Utils::hasValidWebURLSyntax($link)) {
+ $story_cta = '[{"links":[{"linkType": 1, "webUri":'.json_encode($link).', "androidClass": "", "package": "", "deeplinkUri": "", "callToActionTitle": "", "redirectUri": null, "leadGenFormId": "", "igUserId": "", "appInstallObjectiveInvalidationBehavior": null}]}]';
+ $request->addPost('story_cta', $story_cta);
+ }
+ if ($hashtags !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryHashtags($captionText, $hashtags);
+ $request
+ ->addPost('story_hashtags', json_encode($hashtags))
+ ->addPost('caption', $captionText)
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($locationSticker !== null && $location !== null) {
+ Utils::throwIfInvalidStoryLocationSticker($locationSticker);
+ $request
+ ->addPost('story_locations', json_encode([$locationSticker]))
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyMentions !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryMentions($storyMentions);
+ $request
+ ->addPost('reel_mentions', json_encode($storyMentions))
+ ->addPost('caption', str_replace(' ', '+', $captionText).'+')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyPoll !== null) {
+ Utils::throwIfInvalidStoryPoll($storyPoll);
+ $request
+ ->addPost('story_polls', json_encode($storyPoll))
+ ->addPost('internal_features', 'polling_sticker')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storySlider !== null) {
+ Utils::throwIfInvalidStorySlider($storySlider);
+ $request
+ ->addPost('story_sliders', json_encode($storySlider))
+ ->addPost('story_sticker_ids', 'emoji_slider_'.$storySlider[0]['emoji']);
+ }
+ if ($storyQuestion !== null) {
+ Utils::throwIfInvalidStoryQuestion($storyQuestion);
+ $request
+ ->addPost('story_questions', json_encode($storyQuestion))
+ ->addPost('story_sticker_ids', 'question_sticker_ama');
+ }
+ if ($storyCountdown !== null) {
+ Utils::throwIfInvalidStoryCountdown($storyCountdown);
+ $request
+ ->addPost('story_countdowns', json_encode($storyCountdown))
+ ->addPost('story_sticker_ids', 'countdown_sticker_time');
+ }
+ if ($storyFundraisers !== null) {
+ $request
+ ->addPost('story_fundraisers', json_encode($storyFundraisers))
+ ->addPost('story_sticker_ids', 'fundraiser_sticker_id');
+ }
+ if ($attachedMedia !== null) {
+ Utils::throwIfInvalidAttachedMedia($attachedMedia);
+ $request
+ ->addPost('attached_media', json_encode($attachedMedia))
+ ->addPost('story_sticker_ids', 'media_simple_'.reset($attachedMedia)['media_id']);
+ }
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $request
+ ->addPost('recipient_users', $internalMetadata->getDirectUsers());
+
+ if ($internalMetadata->getStoryViewMode() !== null) {
+ $request->addPost('view_mode', $internalMetadata->getStoryViewMode());
+ }
+
+ $request
+ ->addPost('thread_ids', $internalMetadata->getDirectThreads())
+ ->addPost('client_shared_at', (string) time())
+ ->addPost('source_type', '3')
+ ->addPost('configure_mode', '2')
+ ->addPost('client_timestamp', (string) (time() - mt_rand(3, 10)))
+ ->addPost('upload_id', $uploadId);
+ break;
+ }
+
+ if ($location instanceof Response\Model\Location) {
+ if ($targetFeed === Constants::FEED_TIMELINE) {
+ $request->addPost('location', Utils::buildMediaLocationJSON($location));
+ }
+ if ($targetFeed === Constants::FEED_STORY && $locationSticker === null) {
+ throw new \InvalidArgumentException('You must provide a location_sticker together with your story location.');
+ }
+ $request
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $location->getLat())
+ ->addPost('posting_longitude', $location->getLng())
+ ->addPost('media_latitude', $location->getLat())
+ ->addPost('media_longitude', $location->getLng());
+ }
+
+ $configure = $request->getResponse(new Response\ConfigureResponse());
+
+ return $configure;
+ }
+
+ /**
+ * Uploads a raw video file.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $videoFilename The video filename.
+ * @param InternalMetadata|null $internalMetadata (optional) Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return InternalMetadata Updated internal metadata object.
+ */
+ public function uploadVideo(
+ $targetFeed,
+ $videoFilename,
+ InternalMetadata $internalMetadata = null)
+ {
+ if ($internalMetadata === null) {
+ $internalMetadata = new InternalMetadata();
+ }
+
+ try {
+ if ($internalMetadata->getVideoDetails() === null) {
+ $internalMetadata->setVideoDetails($targetFeed, $videoFilename);
+ }
+ } catch (\Exception $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Failed to get photo details: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ try {
+ if ($this->_useSegmentedVideoUploader($targetFeed, $internalMetadata)) {
+ $this->_uploadSegmentedVideo($targetFeed, $internalMetadata);
+ } elseif ($this->_useResumableVideoUploader($targetFeed, $internalMetadata)) {
+ $this->_uploadResumableVideo($targetFeed, $internalMetadata);
+ } else {
+ // Request parameters for uploading a new video.
+ $internalMetadata->setVideoUploadUrls($this->_requestVideoUploadURL($targetFeed, $internalMetadata));
+
+ // Attempt to upload the video data.
+ $internalMetadata->setVideoUploadResponse($this->_uploadVideoChunks($targetFeed, $internalMetadata));
+ }
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of "%s" failed: %s', basename($videoFilename), $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $internalMetadata;
+ }
+
+ /**
+ * UPLOADS A *SINGLE* VIDEO.
+ *
+ * This is NOT used for albums!
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $videoFilename The video filename.
+ * @param InternalMetadata|null $internalMetadata (optional) Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadSingleVideo(
+ $targetFeed,
+ $videoFilename,
+ InternalMetadata $internalMetadata = null,
+ array $externalMetadata = [])
+ {
+ // Make sure we only allow these particular feeds for this function.
+ if ($targetFeed !== Constants::FEED_TIMELINE
+ && $targetFeed !== Constants::FEED_STORY
+ && $targetFeed !== Constants::FEED_DIRECT_STORY
+ && $targetFeed !== Constants::FEED_TV
+ ) {
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Attempt to upload the video.
+ $internalMetadata = $this->uploadVideo($targetFeed, $videoFilename, $internalMetadata);
+
+ // Attempt to upload the thumbnail, associated with our video's ID.
+ $this->uploadVideoThumbnail($targetFeed, $internalMetadata, $externalMetadata);
+
+ // Configure the uploaded video and attach it to our timeline/story.
+ try {
+ /** @var \InstagramAPI\Response\ConfigureResponse $configure */
+ $configure = $this->ig->internal->configureWithRetries(
+ function () use ($targetFeed, $internalMetadata, $externalMetadata) {
+ // Attempt to configure video parameters.
+ return $this->configureSingleVideo($targetFeed, $internalMetadata, $externalMetadata);
+ }
+ );
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of "%s" failed: %s', basename($videoFilename), $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $configure;
+ }
+
+ /**
+ * Performs a resumable upload of a photo file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException
+ */
+ public function uploadVideoThumbnail(
+ $targetFeed,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ if ($internalMetadata->getVideoDetails() === null) {
+ throw new \InvalidArgumentException('Video details are missing from the internal metadata.');
+ }
+
+ try {
+ // Automatically crop&resize the thumbnail to Instagram's requirements.
+ $options = ['targetFeed' => $targetFeed];
+ if (isset($externalMetadata['thumbnail_timestamp'])) {
+ $options['thumbnailTimestamp'] = $externalMetadata['thumbnail_timestamp'];
+ }
+ $videoThumbnail = new InstagramThumbnail(
+ $internalMetadata->getVideoDetails()->getFilename(),
+ $options
+ );
+ // Validate and upload the thumbnail.
+ $internalMetadata->setPhotoDetails($targetFeed, $videoThumbnail->getFile());
+ $this->uploadPhotoData($targetFeed, $internalMetadata);
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of video thumbnail failed: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Asks Instagram for parameters for uploading a new video.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException If the request fails.
+ *
+ * @return \InstagramAPI\Response\UploadJobVideoResponse
+ */
+ protected function _requestVideoUploadURL(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $request = $this->ig->request('upload/video/')
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid);
+
+ foreach ($this->_getVideoUploadParams($targetFeed, $internalMetadata) as $key => $value) {
+ $request->addPost($key, $value);
+ }
+
+ // Perform the "pre-upload" API request.
+ /** @var Response\UploadJobVideoResponse $response */
+ $response = $request->getResponse(new Response\UploadJobVideoResponse());
+
+ return $response;
+ }
+
+ /**
+ * Configures parameters for a *SINGLE* uploaded video file.
+ *
+ * WARNING TO CONTRIBUTORS: THIS IS ONLY FOR *TIMELINE* AND *STORY* -VIDEOS-.
+ * USE "configureTimelineAlbum()" FOR ALBUMS and "configureSinglePhoto()" FOR PHOTOS.
+ * AND IF FUTURE INSTAGRAM FEATURES NEED CONFIGURATION AND ARE NON-TRIVIAL,
+ * GIVE THEM THEIR OWN FUNCTION LIKE WE DID WITH "configureTimelineAlbum()",
+ * TO AVOID ADDING BUGGY AND UNMAINTAINABLE SPIDERWEB CODE!
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ */
+ public function configureSingleVideo(
+ $targetFeed,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ // Determine the target endpoint for the video.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $endpoint = 'media/configure/';
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ case Constants::FEED_STORY:
+ $endpoint = 'media/configure_to_story/';
+ break;
+ case Constants::FEED_TV:
+ $endpoint = 'media/configure_to_igtv/';
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Available external metadata parameters:
+ /** @var string Caption to use for the media. */
+ $captionText = isset($externalMetadata['caption']) ? $externalMetadata['caption'] : '';
+ /** @var string[]|null Array of numerical UserPK IDs of people tagged in
+ * your video. ONLY USED IN STORY VIDEOS! TODO: Actually, it's not even
+ * implemented for stories. */
+ $usertags = (isset($externalMetadata['usertags'])) ? $externalMetadata['usertags'] : null;
+ /** @var Response\Model\Location|null A Location object describing where
+ * the media was taken. */
+ $location = (isset($externalMetadata['location'])) ? $externalMetadata['location'] : null;
+ /** @var array|null Array of story location sticker instructions. ONLY
+ * USED FOR STORY MEDIA! */
+ $locationSticker = (isset($externalMetadata['location_sticker']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['location_sticker'] : null;
+ /** @var string|null Link to attach to the media. ONLY USED FOR STORY MEDIA,
+ * AND YOU MUST HAVE A BUSINESS INSTAGRAM ACCOUNT TO POST A STORY LINK! */
+ $link = (isset($externalMetadata['link']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['link'] : null;
+ /** @var array Hashtags to use for the media. ONLY STORY MEDIA! */
+ $hashtags = (isset($externalMetadata['hashtags']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['hashtags'] : null;
+ /** @var array Mentions to use for the media. ONLY STORY MEDIA! */
+ $storyMentions = (isset($externalMetadata['story_mentions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_mentions'] : null;
+ /** @var array Story poll to use for the media. ONLY STORY MEDIA! */
+ $storyPoll = (isset($externalMetadata['story_polls']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_polls'] : null;
+ /** @var array Attached media used to share media to story feed. ONLY STORY MEDIA! */
+ $storySlider = (isset($externalMetadata['story_sliders']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_sliders'] : null;
+ /** @var array Story question to use for the media. ONLY STORY MEDIA */
+ $storyQuestion = (isset($externalMetadata['story_questions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_questions'] : null;
+ /** @var array Story countdown to use for the media. ONLY STORY MEDIA */
+ $storyCountdown = (isset($externalMetadata['story_countdowns']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_countdowns'] : null;
+ /** @var array Story fundraiser to use for the media. ONLY STORY MEDIA */
+ $storyFundraisers = (isset($externalMetadata['story_fundraisers']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_fundraisers'] : null;
+ /** @var array Attached media used to share media to story feed. ONLY STORY MEDIA! */
+ $attachedMedia = (isset($externalMetadata['attached_media']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['attached_media'] : null;
+ /** @var array Title of the media uploaded to your channel. ONLY TV MEDIA! */
+ $title = (isset($externalMetadata['title']) && $targetFeed == Constants::FEED_TV) ? $externalMetadata['title'] : null;
+ /** @var bool Whether or not a preview should be posted to your feed. ONLY TV MEDIA! */
+ $shareToFeed = (isset($externalMetadata['share_to_feed']) && $targetFeed == Constants::FEED_TV) ? $externalMetadata['share_to_feed'] : false;
+
+ // Fix very bad external user-metadata values.
+ if (!is_string($captionText)) {
+ $captionText = '';
+ }
+
+ $uploadId = $internalMetadata->getUploadId();
+ $videoDetails = $internalMetadata->getVideoDetails();
+
+ // Build the request...
+ $request = $this->ig->request($endpoint)
+ ->addParam('video', 1)
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('video_result', $internalMetadata->getVideoUploadResponse() !== null ? (string) $internalMetadata->getVideoUploadResponse()->getResult() : '')
+ ->addPost('upload_id', $uploadId)
+ ->addPost('poster_frame_index', 0)
+ ->addPost('length', round($videoDetails->getDuration(), 1))
+ ->addPost('audio_muted', $videoDetails->getAudioCodec() === null)
+ ->addPost('filter_type', 0)
+ ->addPost('source_type', 4)
+ ->addPost('device',
+ [
+ 'manufacturer' => $this->ig->device->getManufacturer(),
+ 'model' => $this->ig->device->getModel(),
+ 'android_version' => $this->ig->device->getAndroidVersion(),
+ 'android_release' => $this->ig->device->getAndroidRelease(),
+ ])
+ ->addPost('extra',
+ [
+ 'source_width' => $videoDetails->getWidth(),
+ 'source_height' => $videoDetails->getHeight(),
+ ])
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id);
+
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $request->addPost('caption', $captionText);
+ if ($usertags !== null) {
+ $in = [];
+ foreach ($usertags as $userId) {
+ $in[] = ['user_id' => $userId];
+ }
+ $request->addPost('usertags', $in);
+ }
+ break;
+ case Constants::FEED_STORY:
+ if ($internalMetadata->isBestieMedia()) {
+ $request->addPost('audience', 'besties');
+ }
+
+ $request
+ ->addPost('configure_mode', 1) // 1 - REEL_SHARE
+ ->addPost('story_media_creation_date', time() - mt_rand(10, 20))
+ ->addPost('client_shared_at', time() - mt_rand(3, 10))
+ ->addPost('client_timestamp', time());
+
+ if (is_string($link) && Utils::hasValidWebURLSyntax($link)) {
+ $story_cta = '[{"links":[{"linkType": 1, "webUri":'.json_encode($link).', "androidClass": "", "package": "", "deeplinkUri": "", "callToActionTitle": "", "redirectUri": null, "leadGenFormId": "", "igUserId": "", "appInstallObjectiveInvalidationBehavior": null}]}]';
+ $request->addPost('story_cta', $story_cta);
+ }
+ if ($hashtags !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryHashtags($captionText, $hashtags);
+ $request
+ ->addPost('story_hashtags', json_encode($hashtags))
+ ->addPost('caption', $captionText)
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($locationSticker !== null && $location !== null) {
+ Utils::throwIfInvalidStoryLocationSticker($locationSticker);
+ $request
+ ->addPost('story_locations', json_encode([$locationSticker]))
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyMentions !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryMentions($storyMentions);
+ $request
+ ->addPost('reel_mentions', json_encode($storyMentions))
+ ->addPost('caption', str_replace(' ', '+', $captionText).'+')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyPoll !== null) {
+ Utils::throwIfInvalidStoryPoll($storyPoll);
+ $request
+ ->addPost('story_polls', json_encode($storyPoll))
+ ->addPost('internal_features', 'polling_sticker')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storySlider !== null) {
+ Utils::throwIfInvalidStorySlider($storySlider);
+ $request
+ ->addPost('story_sliders', json_encode($storySlider))
+ ->addPost('story_sticker_ids', 'emoji_slider_'.$storySlider[0]['emoji']);
+ }
+ if ($storyQuestion !== null) {
+ Utils::throwIfInvalidStoryQuestion($storyQuestion);
+ $request
+ ->addPost('story_questions', json_encode($storyQuestion))
+ ->addPost('story_sticker_ids', 'question_sticker_ama');
+ }
+ if ($storyCountdown !== null) {
+ Utils::throwIfInvalidStoryCountdown($storyCountdown);
+ $request
+ ->addPost('story_countdowns', json_encode($storyCountdown))
+ ->addPost('story_sticker_ids', 'countdown_sticker_time');
+ }
+ if ($storyFundraisers !== null) {
+ $request
+ ->addPost('story_fundraisers', json_encode($storyFundraisers))
+ ->addPost('story_sticker_ids', 'fundraiser_sticker_id');
+ }
+ if ($attachedMedia !== null) {
+ Utils::throwIfInvalidAttachedMedia($attachedMedia);
+ $request
+ ->addPost('attached_media', json_encode($attachedMedia))
+ ->addPost('story_sticker_ids', 'media_simple_'.reset($attachedMedia)['media_id']);
+ }
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $request
+ ->addPost('configure_mode', 2) // 2 - DIRECT_STORY_SHARE
+ ->addPost('recipient_users', $internalMetadata->getDirectUsers());
+
+ if ($internalMetadata->getStoryViewMode() !== null) {
+ $request->addPost('view_mode', $internalMetadata->getStoryViewMode());
+ }
+
+ $request
+ ->addPost('thread_ids', $internalMetadata->getDirectThreads())
+ ->addPost('story_media_creation_date', time() - mt_rand(10, 20))
+ ->addPost('client_shared_at', time() - mt_rand(3, 10))
+ ->addPost('client_timestamp', time());
+ break;
+ case Constants::FEED_TV:
+ if ($title === null) {
+ throw new \InvalidArgumentException('You must provide a title for the media.');
+ }
+ if ($shareToFeed) {
+ if ($internalMetadata->getVideoDetails()->getDurationInMsec() < 60000) {
+ throw new \InvalidArgumentException('Your media must be at least a minute long to preview to feed.');
+ }
+ $request->addPost('igtv_share_preview_to_feed', '1');
+ }
+ $request
+ ->addPost('title', $title)
+ ->addPost('caption', $captionText);
+ break;
+ }
+
+ if ($targetFeed == Constants::FEED_STORY) {
+ $request->addPost('story_media_creation_date', time());
+ if ($usertags !== null) {
+ // Reel Mention example:
+ // [{\"y\":0.3407772676161919,\"rotation\":0,\"user_id\":\"USER_ID\",\"x\":0.39892578125,\"width\":0.5619921875,\"height\":0.06011525487256372}]
+ // NOTE: The backslashes are just double JSON encoding, ignore
+ // that and just give us an array with these clean values, don't
+ // try to encode it in any way, we do all encoding to match the above.
+ // This post field will get wrapped in another json_encode call during transfer.
+ $request->addPost('reel_mentions', json_encode($usertags));
+ }
+ }
+
+ if ($location instanceof Response\Model\Location) {
+ if ($targetFeed === Constants::FEED_TIMELINE) {
+ $request->addPost('location', Utils::buildMediaLocationJSON($location));
+ }
+ if ($targetFeed === Constants::FEED_STORY && $locationSticker === null) {
+ throw new \InvalidArgumentException('You must provide a location_sticker together with your story location.');
+ }
+ $request
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $location->getLat())
+ ->addPost('posting_longitude', $location->getLng())
+ ->addPost('media_latitude', $location->getLat())
+ ->addPost('media_longitude', $location->getLng());
+ }
+
+ $configure = $request->getResponse(new Response\ConfigureResponse());
+
+ return $configure;
+ }
+
+ /**
+ * Configures parameters for a whole album of uploaded media files.
+ *
+ * WARNING TO CONTRIBUTORS: THIS IS ONLY FOR *TIMELINE ALBUMS*. DO NOT MAKE
+ * IT DO ANYTHING ELSE, TO AVOID ADDING BUGGY AND UNMAINTAINABLE SPIDERWEB
+ * CODE!
+ *
+ * @param array $media Extended media array coming from Timeline::uploadAlbum(),
+ * containing the user's per-file metadata,
+ * and internally generated per-file metadata.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object for the album itself.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs
+ * for the album itself (its caption, location, etc).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ */
+ public function configureTimelineAlbum(
+ array $media,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ $endpoint = 'media/configure_sidecar/';
+
+ $albumUploadId = $internalMetadata->getUploadId();
+
+ // Available external metadata parameters:
+ /** @var string Caption to use for the album. */
+ $captionText = isset($externalMetadata['caption']) ? $externalMetadata['caption'] : '';
+ /** @var Response\Model\Location|null A Location object describing where
+ * the album was taken. */
+ $location = isset($externalMetadata['location']) ? $externalMetadata['location'] : null;
+
+ // Fix very bad external user-metadata values.
+ if (!is_string($captionText)) {
+ $captionText = '';
+ }
+
+ // Build the album's per-children metadata.
+ $date = date('Y:m:d H:i:s');
+ $childrenMetadata = [];
+ foreach ($media as $item) {
+ /** @var InternalMetadata $itemInternalMetadata */
+ $itemInternalMetadata = $item['internalMetadata'];
+ // Get all of the common, INTERNAL per-file metadata.
+ $uploadId = $itemInternalMetadata->getUploadId();
+
+ switch ($item['type']) {
+ case 'photo':
+ // Build this item's configuration.
+ $photoConfig = [
+ 'date_time_original' => $date,
+ 'scene_type' => 1,
+ 'disable_comments' => false,
+ 'upload_id' => $uploadId,
+ 'source_type' => 0,
+ 'scene_capture_type' => 'standard',
+ 'date_time_digitized' => $date,
+ 'geotag_enabled' => false,
+ 'camera_position' => 'back',
+ 'edits' => [
+ 'filter_strength' => 1,
+ 'filter_name' => 'IGNormalFilter',
+ ],
+ ];
+
+ if (isset($item['usertags'])) {
+ // NOTE: These usertags were validated in Timeline::uploadAlbum.
+ $photoConfig['usertags'] = json_encode(['in' => $item['usertags']]);
+ }
+
+ $childrenMetadata[] = $photoConfig;
+ break;
+ case 'video':
+ // Get all of the INTERNAL per-VIDEO metadata.
+ $videoDetails = $itemInternalMetadata->getVideoDetails();
+
+ // Build this item's configuration.
+ $videoConfig = [
+ 'length' => round($videoDetails->getDuration(), 1),
+ 'date_time_original' => $date,
+ 'scene_type' => 1,
+ 'poster_frame_index' => 0,
+ 'trim_type' => 0,
+ 'disable_comments' => false,
+ 'upload_id' => $uploadId,
+ 'source_type' => 'library',
+ 'geotag_enabled' => false,
+ 'edits' => [
+ 'length' => round($videoDetails->getDuration(), 1),
+ 'cinema' => 'unsupported',
+ 'original_length' => round($videoDetails->getDuration(), 1),
+ 'source_type' => 'library',
+ 'start_time' => 0,
+ 'camera_position' => 'unknown',
+ 'trim_type' => 0,
+ ],
+ ];
+
+ if (isset($item['usertags'])) {
+ // NOTE: These usertags were validated in Timeline::uploadAlbum.
+ $videoConfig['usertags'] = json_encode(['in' => $item['usertags']]);
+ }
+
+ $childrenMetadata[] = $videoConfig;
+ break;
+ }
+ }
+
+ // Build the request...
+ $request = $this->ig->request($endpoint)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('client_sidecar_id', $albumUploadId)
+ ->addPost('caption', $captionText)
+ ->addPost('children_metadata', $childrenMetadata);
+
+ if ($location instanceof Response\Model\Location) {
+ $request
+ ->addPost('location', Utils::buildMediaLocationJSON($location))
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $location->getLat())
+ ->addPost('posting_longitude', $location->getLng())
+ ->addPost('media_latitude', $location->getLat())
+ ->addPost('media_longitude', $location->getLng())
+ ->addPost('exif_latitude', 0.0)
+ ->addPost('exif_longitude', 0.0);
+ }
+
+ $configure = $request->getResponse(new Response\ConfigureResponse());
+
+ return $configure;
+ }
+
+ /**
+ * Saves active experiments.
+ *
+ * @param Response\SyncResponse $syncResponse
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _saveExperiments(
+ Response\SyncResponse $syncResponse)
+ {
+ $experiments = [];
+ foreach ($syncResponse->getExperiments() as $experiment) {
+ $group = $experiment->getName();
+ $params = $experiment->getParams();
+
+ if ($group === null || $params === null) {
+ continue;
+ }
+
+ if (!isset($experiments[$group])) {
+ $experiments[$group] = [];
+ }
+
+ foreach ($params as $param) {
+ $paramName = $param->getName();
+ if ($paramName === null) {
+ continue;
+ }
+
+ $experiments[$group][$paramName] = $param->getValue();
+ }
+ }
+
+ // Save the experiments and the last time we refreshed them.
+ $this->ig->experiments = $this->ig->settings->setExperiments($experiments);
+ $this->ig->settings->set('last_experiments', time());
+ }
+
+ /**
+ * Perform an Instagram "feature synchronization" call for device.
+ *
+ * @param bool $prelogin
+ * @param bool $useCsrfToken
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SyncResponse
+ */
+ public function syncDeviceFeatures(
+ $prelogin = false,
+ $useCsrfToken = false)
+ {
+ $request = $this->ig->request('qe/sync/')
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addPost('id', $this->ig->uuid)
+ ->addPost('experiments', Constants::LOGIN_EXPERIMENTS);
+ if ($useCsrfToken) {
+ $request->addPost('_csrftoken', $this->ig->client->getToken());
+ }
+ if ($prelogin) {
+ $request->setNeedsAuth(false);
+ } else {
+ $request
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id);
+ }
+
+ return $request->getResponse(new Response\SyncResponse());
+ }
+
+ /**
+ * Perform an Instagram "feature synchronization" call for account.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SyncResponse
+ */
+ public function syncUserFeatures()
+ {
+ $result = $this->ig->request('qe/sync/')
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('id', $this->ig->account_id)
+ ->addPost('experiments', Constants::EXPERIMENTS)
+ ->getResponse(new Response\SyncResponse());
+
+ // Save the updated experiments for this user.
+ $this->_saveExperiments($result);
+
+ return $result;
+ }
+
+ /**
+ * Send launcher sync.
+ *
+ * @param bool $prelogin Indicates if the request is done before login request.
+ * @param bool $idIsUuid Indicates if the id parameter is the user's id.
+ * @param bool $useCsrfToken Indicates if a csrf token should be included.
+ * @param bool $loginConfigs Indicates if login configs should be used.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LauncherSyncResponse
+ */
+ public function sendLauncherSync(
+ $prelogin,
+ $idIsUuid = true,
+ $useCsrfToken = false,
+ $loginConfigs = false)
+ {
+ $request = $this->ig->request('launcher/sync/')
+ ->addPost('configs', $loginConfigs ? Constants::LAUNCHER_LOGIN_CONFIGS : Constants::LAUNCHER_CONFIGS)
+ ->addPost('id', ($idIsUuid ? $this->ig->uuid : $this->ig->account_id));
+ if ($useCsrfToken) {
+ $request->addPost('_csrftoken', $this->ig->client->getToken());
+ }
+ if ($prelogin) {
+ $request->setNeedsAuth(false);
+ } else {
+ $request
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id);
+ }
+
+ return $request->getResponse(new Response\LauncherSyncResponse());
+ }
+
+ /**
+ * Get decisions about device capabilities.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CapabilitiesDecisionsResponse
+ */
+ public function getDeviceCapabilitiesDecisions()
+ {
+ return $this->ig->request('device_capabilities/decisions/')
+ ->addParam('signed_body', Signatures::generateSignature(json_encode((object) []).'.{}'))
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->getResponse(new Response\CapabilitiesDecisionsResponse());
+ }
+
+ /**
+ * Registers advertising identifier.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function logAttribution()
+ {
+ return $this->ig->request('attribution/log_attribution/')
+ ->setNeedsAuth(false)
+ ->addPost('adid', $this->ig->advertising_id)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * TODO.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function logResurrectAttribution()
+ {
+ return $this->ig->request('attribution/log_resurrect_attribution/')
+ ->addPost('adid', $this->ig->advertising_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Reads MSISDN header.
+ *
+ * @param string $usage Desired usage, either "ig_select_app" or "default".
+ * @param bool $useCsrfToken (Optional) Decides to include a csrf token in this request.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MsisdnHeaderResponse
+ */
+ public function readMsisdnHeader(
+ $usage,
+ $useCsrfToken = false)
+ {
+ $request = $this->ig->request('accounts/read_msisdn_header/')
+ ->setNeedsAuth(false)
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ // UUID is used as device_id intentionally.
+ ->addPost('device_id', $this->ig->uuid)
+ ->addPost('mobile_subno_usage', $usage);
+ if ($useCsrfToken) {
+ $request->addPost('_csrftoken', $this->ig->client->getToken());
+ }
+
+ return $request->getResponse(new Response\MsisdnHeaderResponse());
+ }
+
+ /**
+ * Bootstraps MSISDN header.
+ *
+ * @param string $usage Mobile subno usage.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MsisdnHeaderResponse
+ *
+ * @since 10.24.0 app version.
+ */
+ public function bootstrapMsisdnHeader(
+ $usage = 'ig_select_app')
+ {
+ $request = $this->ig->request('accounts/msisdn_header_bootstrap/')
+ ->setNeedsAuth(false)
+ ->addPost('mobile_subno_usage', $usage)
+ // UUID is used as device_id intentionally.
+ ->addPost('device_id', $this->ig->uuid);
+
+ return $request->getResponse(new Response\MsisdnHeaderResponse());
+ }
+
+ /**
+ * @param Response\Model\Token|null $token
+ */
+ protected function _saveZeroRatingToken(
+ Response\Model\Token $token = null)
+ {
+ if ($token === null) {
+ return;
+ }
+
+ $rules = [];
+ foreach ($token->getRewriteRules() as $rule) {
+ $rules[$rule->getMatcher()] = $rule->getReplacer();
+ }
+ $this->ig->client->zeroRating()->update($rules);
+
+ try {
+ $this->ig->settings->setRewriteRules($rules);
+ $this->ig->settings->set('zr_token', $token->getTokenHash());
+ $this->ig->settings->set('zr_expires', $token->expiresAt());
+ } catch (SettingsException $e) {
+ // Ignore storage errors.
+ }
+ }
+
+ /**
+ * Get zero rating token hash result.
+ *
+ * @param string $reason One of: "token_expired", "mqtt_token_push", "token_stale", "provisioning_time_mismatch".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TokenResultResponse
+ */
+ public function fetchZeroRatingToken(
+ $reason = 'token_expired')
+ {
+ $request = $this->ig->request('zr/token/result/')
+ ->setNeedsAuth(false)
+ ->addParam('custom_device_id', $this->ig->uuid)
+ ->addParam('device_id', $this->ig->device_id)
+ ->addParam('fetch_reason', $reason)
+ ->addParam('token_hash', (string) $this->ig->settings->get('zr_token'));
+
+ /** @var Response\TokenResultResponse $result */
+ $result = $request->getResponse(new Response\TokenResultResponse());
+ $this->_saveZeroRatingToken($result->getToken());
+
+ return $result;
+ }
+
+ /**
+ * Get megaphone log.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MegaphoneLogResponse
+ */
+ public function getMegaphoneLog()
+ {
+ return $this->ig->request('megaphone/log/')
+ ->setSignedPost(false)
+ ->addPost('type', 'feed_aysf')
+ ->addPost('action', 'seen')
+ ->addPost('reason', '')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('uuid', md5(time()))
+ ->getResponse(new Response\MegaphoneLogResponse());
+ }
+
+ /**
+ * Get hidden entities for users, places and hashtags via Facebook's algorithm.
+ *
+ * TODO: We don't know what this function does. If we ever discover that it
+ * has a useful purpose, then we should move it somewhere else.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FacebookHiddenEntitiesResponse
+ */
+ public function getFacebookHiddenSearchEntities()
+ {
+ return $this->ig->request('fbsearch/get_hidden_search_entities/')
+ ->getResponse(new Response\FacebookHiddenEntitiesResponse());
+ }
+
+ /**
+ * Get Facebook OTA (Over-The-Air) update information.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FacebookOTAResponse
+ */
+ public function getFacebookOTA()
+ {
+ return $this->ig->request('facebook_ota/')
+ ->addParam('fields', Constants::FACEBOOK_OTA_FIELDS)
+ ->addParam('custom_user_id', $this->ig->account_id)
+ ->addParam('signed_body', Signatures::generateSignature('').'.')
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->addParam('version_code', Constants::VERSION_CODE)
+ ->addParam('version_name', Constants::IG_VERSION)
+ ->addParam('custom_app_id', Constants::FACEBOOK_ORCA_APPLICATION_ID)
+ ->addParam('custom_device_id', $this->ig->uuid)
+ ->getResponse(new Response\FacebookOTAResponse());
+ }
+
+ /**
+ * Fetch profiler traces config.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoomFetchConfigResponse
+ *
+ * @see https://github.com/facebookincubator/profilo
+ */
+ public function getLoomFetchConfig()
+ {
+ return $this->ig->request('loom/fetch_config/')
+ ->getResponse(new Response\LoomFetchConfigResponse());
+ }
+
+ /**
+ * Get profile "notices".
+ *
+ * This is just for some internal state information, such as
+ * "has_change_password_megaphone". It's not for public use.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ProfileNoticeResponse
+ */
+ public function getProfileNotice()
+ {
+ return $this->ig->request('users/profile_notice/')
+ ->getResponse(new Response\ProfileNoticeResponse());
+ }
+
+ /**
+ * Fetch quick promotions data.
+ *
+ * This is used by Instagram to fetch internal promotions or changes
+ * about the platform. Latest quick promotion known was the new GDPR
+ * policy where Instagram asks you to accept new policy and accept that
+ * you have 18 years old or more.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FetchQPDataResponse
+ */
+ public function getQPFetch()
+ {
+ return $this->ig->request('qp/batch_fetch/')
+ ->addPost('vc_policy', 'default')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('surfaces_to_queries', json_encode(
+ [
+ Constants::BATCH_SURFACES[0][0] => Constants::BATCH_QUERY,
+ Constants::BATCH_SURFACES[1][0] => Constants::BATCH_QUERY,
+ Constants::BATCH_SURFACES[2][0] => Constants::BATCH_QUERY,
+ ]
+ ))
+ ->addPost('surfaces_to_triggers', json_encode(
+ [
+ Constants::BATCH_SURFACES[0][0] => Constants::BATCH_SURFACES[0][1],
+ Constants::BATCH_SURFACES[1][0] => Constants::BATCH_SURFACES[1][1],
+ Constants::BATCH_SURFACES[2][0] => Constants::BATCH_SURFACES[2][1],
+ ]
+ ))
+ ->addPost('version', Constants::BATCH_VERSION)
+ ->addPost('scale', Constants::BATCH_SCALE)
+ ->getResponse(new Response\FetchQPDataResponse());
+ }
+
+ /**
+ * Get Arlink download info.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ArlinkDownloadInfoResponse
+ */
+ public function getArlinkDownloadInfo()
+ {
+ return $this->ig->request('users/arlink_download_info/')
+ ->addParam('version_override', '2.2.1')
+ ->getResponse(new Response\ArlinkDownloadInfoResponse());
+ }
+
+ /**
+ * Get quick promotions cooldowns.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\QPCooldownsResponse
+ */
+ public function getQPCooldowns()
+ {
+ return $this->ig->request('qp/get_cooldowns/')
+ ->addParam('signed_body', Signatures::generateSignature(json_encode((object) []).'.{}'))
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->getResponse(new Response\QPCooldownsResponse());
+ }
+
+ public function storeClientPushPermissions()
+ {
+ return $this->ig->request('notifications/store_client_push_permissions/')
+ ->setSignedPost(false)
+ ->addPost('enabled', 'true')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Internal helper for marking story media items as seen.
+ *
+ * This is used by story-related functions in other request-collections!
+ *
+ * @param Response\Model\Item[] $items Array of one or more story media Items.
+ * @param string|null $sourceId Where the story was seen from,
+ * such as a location story-tray ID.
+ * If NULL, we automatically use the
+ * user's profile ID from each Item
+ * object as the source ID.
+ * @param string $module Module where the story was found.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Story::markMediaSeen()
+ * @see Location::markStoryMediaSeen()
+ * @see Hashtag::markStoryMediaSeen()
+ */
+ public function markStoryMediaSeen(
+ array $items,
+ $sourceId = null,
+ $module = 'feed_timeline')
+ {
+ // Build the list of seen media, with human randomization of seen-time.
+ $reels = [];
+ $maxSeenAt = time(); // Get current global UTC timestamp.
+ $seenAt = $maxSeenAt - (3 * count($items)); // Start seenAt in the past.
+ foreach ($items as $item) {
+ if (!$item instanceof Response\Model\Item) {
+ throw new \InvalidArgumentException(
+ 'All story items must be instances of \InstagramAPI\Response\Model\Item.'
+ );
+ }
+
+ // Raise "seenAt" if it's somehow older than the item's "takenAt".
+ // NOTE: Can only happen if you see a story instantly when posted.
+ $itemTakenAt = $item->getTakenAt();
+ if ($seenAt < $itemTakenAt) {
+ $seenAt = $itemTakenAt + 2;
+ }
+
+ // Do not let "seenAt" exceed the current global UTC time.
+ if ($seenAt > $maxSeenAt) {
+ $seenAt = $maxSeenAt;
+ }
+
+ // Determine the source ID for this item. This is where the item was
+ // seen from, such as a UserID or a Location-StoryTray ID.
+ $itemSourceId = ($sourceId === null ? $item->getUser()->getPk() : $sourceId);
+
+ // Key Format: "mediaPk_userPk_sourceId".
+ // NOTE: In case of seeing stories on a user's profile, their
+ // userPk is used as the sourceId, as "mediaPk_userPk_userPk".
+ $reelId = $item->getId().'_'.$itemSourceId;
+
+ // Value Format: ["mediaTakenAt_seenAt"] (array with single string).
+ $reels[$reelId] = [$itemTakenAt.'_'.$seenAt];
+
+ // Randomly add 1-3 seconds to next seenAt timestamp, to act human.
+ $seenAt += rand(1, 3);
+ }
+
+ return $this->ig->request('media/seen/')
+ ->setVersion(2)
+ ->setIsBodyCompressed(true)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('container_module', $module)
+ ->addPost('reels', $reels)
+ ->addPost('reel_media_skipped', [])
+ ->addPost('live_vods', [])
+ ->addPost('live_vods_skipped', [])
+ ->addPost('nuxes', [])
+ ->addPost('nuxes_skipped', [])
+// ->addParam('reel', 1)
+// ->addParam('live_vod', 0)
+ ->getResponse(new Response\MediaSeenResponse());
+ }
+
+ /**
+ * Configure media entity (album, video, ...) with retries.
+ *
+ * @param callable $configurator Configurator function.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response
+ */
+ public function configureWithRetries(
+ callable $configurator)
+ {
+ $attempt = 0;
+ $lastError = null;
+ while (true) {
+ // Check for max retry-limit, and throw if we exceeded it.
+ if (++$attempt > self::MAX_CONFIGURE_RETRIES) {
+ if ($lastError === null) {
+ throw new \RuntimeException('All configuration retries have failed.');
+ }
+
+ throw new \RuntimeException(sprintf(
+ 'All configuration retries have failed. Last error: %s',
+ $lastError
+ ));
+ }
+
+ $result = null;
+
+ try {
+ /** @var Response $result */
+ $result = $configurator();
+ } catch (ThrottledException $e) {
+ throw $e;
+ } catch (LoginRequiredException $e) {
+ throw $e;
+ } catch (FeedbackRequiredException $e) {
+ throw $e;
+ } catch (ConsentRequiredException $e) {
+ throw $e;
+ } catch (CheckpointRequiredException $e) {
+ throw $e;
+ } catch (InstagramException $e) {
+ if ($e->hasResponse()) {
+ $result = $e->getResponse();
+ }
+ $lastError = $e;
+ } catch (\Exception $e) {
+ $lastError = $e;
+ // Ignore everything else.
+ }
+
+ // We had a network error or something like that, let's continue to the next attempt.
+ if ($result === null) {
+ sleep(1);
+ continue;
+ }
+
+ $httpResponse = $result->getHttpResponse();
+ $delay = 1;
+ switch ($httpResponse->getStatusCode()) {
+ case 200:
+ // Instagram uses "ok" status for this error, so we need to check it first:
+ // {"message": "media_needs_reupload", "error_title": "staged_position_not_found", "status": "ok"}
+ if (strtolower($result->getMessage()) === 'media_needs_reupload') {
+ throw new \RuntimeException(sprintf(
+ 'You need to reupload the media (%s).',
+ // We are reading a property that isn't defined in the class
+ // property map, so we must use "has" first, to ensure it exists.
+ ($result->hasErrorTitle() && is_string($result->getErrorTitle())
+ ? $result->getErrorTitle()
+ : 'unknown error')
+ ));
+ } elseif ($result->isOk()) {
+ return $result;
+ }
+ // Continue to the next attempt.
+ break;
+ case 202:
+ // We are reading a property that isn't defined in the class
+ // property map, so we must use "has" first, to ensure it exists.
+ if ($result->hasCooldownTimeInSeconds() && $result->getCooldownTimeInSeconds() !== null) {
+ $delay = max((int) $result->getCooldownTimeInSeconds(), 1);
+ }
+ break;
+ default:
+ }
+ sleep($delay);
+ }
+
+ // We are never supposed to get here!
+ throw new \LogicException('Something went wrong during configuration.');
+ }
+
+ /**
+ * Performs a resumable upload of a media file, with support for retries.
+ *
+ * @param MediaDetails $mediaDetails
+ * @param Request $offsetTemplate
+ * @param Request $uploadTemplate
+ * @param bool $skipGet
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response\ResumableUploadResponse
+ */
+ protected function _uploadResumableMedia(
+ MediaDetails $mediaDetails,
+ Request $offsetTemplate,
+ Request $uploadTemplate,
+ $skipGet)
+ {
+ // Open file handle.
+ $handle = fopen($mediaDetails->getFilename(), 'rb');
+ if ($handle === false) {
+ throw new \RuntimeException('Failed to open media file for reading.');
+ }
+
+ try {
+ $length = $mediaDetails->getFilesize();
+
+ // Create a stream for the opened file handle.
+ $stream = new Stream($handle, ['size' => $length]);
+
+ $attempt = 0;
+ while (true) {
+ // Check for max retry-limit, and throw if we exceeded it.
+ if (++$attempt > self::MAX_RESUMABLE_RETRIES) {
+ throw new \RuntimeException('All retries have failed.');
+ }
+
+ try {
+ if ($attempt === 1 && $skipGet) {
+ // It is obvious that the first attempt is always at 0, so we can skip a request.
+ $offset = 0;
+ } else {
+ // Get current offset.
+ $offsetRequest = clone $offsetTemplate;
+ /** @var Response\ResumableOffsetResponse $offsetResponse */
+ $offsetResponse = $offsetRequest->getResponse(new Response\ResumableOffsetResponse());
+ $offset = $offsetResponse->getOffset();
+ }
+
+ // Resume upload from given offset.
+ $uploadRequest = clone $uploadTemplate;
+ $uploadRequest
+ ->addHeader('Offset', $offset)
+ ->setBody(new LimitStream($stream, $length - $offset, $offset));
+ /** @var Response\ResumableUploadResponse $response */
+ $response = $uploadRequest->getResponse(new Response\ResumableUploadResponse());
+
+ return $response;
+ } catch (ThrottledException $e) {
+ throw $e;
+ } catch (LoginRequiredException $e) {
+ throw $e;
+ } catch (FeedbackRequiredException $e) {
+ throw $e;
+ } catch (ConsentRequiredException $e) {
+ throw $e;
+ } catch (CheckpointRequiredException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Ignore everything else.
+ }
+ }
+ } finally {
+ Utils::safe_fclose($handle);
+ }
+
+ // We are never supposed to get here!
+ throw new \LogicException('Something went wrong during media upload.');
+ }
+
+ /**
+ * Performs an upload of a photo file, without support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UploadPhotoResponse
+ */
+ protected function _uploadPhotoInOnePiece(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // Prepare payload for the upload request.
+ $request = $this->ig->request('upload/photo/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addFile(
+ 'photo',
+ $internalMetadata->getPhotoDetails()->getFilename(),
+ 'pending_media_'.Utils::generateUploadId().'.jpg'
+ );
+
+ foreach ($this->_getPhotoUploadParams($targetFeed, $internalMetadata) as $key => $value) {
+ $request->addPost($key, $value);
+ }
+ /** @var Response\UploadPhotoResponse $response */
+ $response = $request->getResponse(new Response\UploadPhotoResponse());
+
+ return $response;
+ }
+
+ /**
+ * Performs a resumable upload of a photo file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ protected function _uploadResumablePhoto(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $photoDetails = $internalMetadata->getPhotoDetails();
+
+ $endpoint = sprintf('https://i.instagram.com/rupload_igphoto/%s_%d_%d',
+ $internalMetadata->getUploadId(),
+ 0,
+ Utils::hashCode($photoDetails->getFilename())
+ );
+
+ $uploadParams = $this->_getPhotoUploadParams($targetFeed, $internalMetadata);
+ $uploadParams = Utils::reorderByHashCode($uploadParams);
+
+ $offsetTemplate = new Request($this->ig, $endpoint);
+ $offsetTemplate
+ ->setAddDefaultHeaders(false)
+ ->addHeader('X_FB_PHOTO_WATERFALL_ID', Signatures::generateUUID(true))
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams));
+
+ $uploadTemplate = clone $offsetTemplate;
+ $uploadTemplate
+ ->addHeader('X-Entity-Type', 'image/jpeg')
+ ->addHeader('X-Entity-Name', basename(parse_url($endpoint, PHP_URL_PATH)))
+ ->addHeader('X-Entity-Length', $photoDetails->getFilesize());
+
+ return $this->_uploadResumableMedia(
+ $photoDetails,
+ $offsetTemplate,
+ $uploadTemplate,
+ $this->ig->isExperimentEnabled(
+ 'ig_android_skip_get_fbupload_photo_universe',
+ 'photo_skip_get'
+ )
+ );
+ }
+
+ /**
+ * Determine whether to use resumable photo uploader based on target feed and internal metadata.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return bool
+ */
+ protected function _useResumablePhotoUploader(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_sidecar_photo_fbupload_universe',
+ 'is_enabled_fbupload_sidecar_photo');
+ break;
+ default:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_photo_fbupload_universe',
+ 'is_enabled_fbupload_photo');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the first missing range (start-end) from a HTTP "Range" header.
+ *
+ * @param string $ranges
+ *
+ * @return array|null
+ */
+ protected function _getFirstMissingRange(
+ $ranges)
+ {
+ preg_match_all('/(?\d+)-(?\d+)\/(?\d+)/', $ranges, $matches, PREG_SET_ORDER);
+ if (!count($matches)) {
+ return;
+ }
+ $pairs = [];
+ $length = 0;
+ foreach ($matches as $match) {
+ $pairs[] = [$match['start'], $match['end']];
+ $length = $match['total'];
+ }
+ // Sort pairs by start.
+ usort($pairs, function (array $pair1, array $pair2) {
+ return $pair1[0] - $pair2[0];
+ });
+ $first = $pairs[0];
+ $second = count($pairs) > 1 ? $pairs[1] : null;
+ if ($first[0] == 0) {
+ $result = [$first[1] + 1, ($second === null ? $length : $second[0]) - 1];
+ } else {
+ $result = [0, $first[0] - 1];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Performs a chunked upload of a video file, with support for retries.
+ *
+ * Note that chunk uploads often get dropped when their server is overloaded
+ * at peak hours, which is why our chunk-retry mechanism exists. We will
+ * try several times to upload all chunks. The retries will only re-upload
+ * the exact chunks that have been dropped from their server, and it won't
+ * waste time with chunks that are already successfully uploaded.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UploadVideoResponse
+ */
+ protected function _uploadVideoChunks(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $videoFilename = $internalMetadata->getVideoDetails()->getFilename();
+
+ // To support video uploads to albums, we MUST fake-inject the
+ // "sessionid" cookie from "i.instagram" into our "upload.instagram"
+ // request, otherwise the server will reply with a "StagedUpload not
+ // found" error when the final chunk has been uploaded.
+ $sessionIDCookie = null;
+ if ($targetFeed === Constants::FEED_TIMELINE_ALBUM) {
+ $foundCookie = $this->ig->client->getCookie('sessionid', 'i.instagram.com');
+ if ($foundCookie !== null) {
+ $sessionIDCookie = $foundCookie->getValue();
+ }
+ if ($sessionIDCookie === null || $sessionIDCookie === '') { // Verify value.
+ throw new \RuntimeException(
+ 'Unable to find the necessary SessionID cookie for uploading video album chunks.'
+ );
+ }
+ }
+
+ // Verify the upload URLs.
+ $uploadUrls = $internalMetadata->getVideoUploadUrls();
+ if (!is_array($uploadUrls) || !count($uploadUrls)) {
+ throw new \RuntimeException('No video upload URLs found.');
+ }
+
+ // Init state.
+ $length = $internalMetadata->getVideoDetails()->getFilesize();
+ $uploadId = $internalMetadata->getUploadId();
+ $sessionId = sprintf('%s-%d', $uploadId, Utils::hashCode($videoFilename));
+ $uploadUrl = array_shift($uploadUrls);
+ $offset = 0;
+ $chunk = min($length, self::MIN_CHUNK_SIZE);
+ $attempt = 0;
+
+ // Open file handle.
+ $handle = fopen($videoFilename, 'rb');
+ if ($handle === false) {
+ throw new \RuntimeException('Failed to open file for reading.');
+ }
+
+ try {
+ // Create a stream for the opened file handle.
+ $stream = new Stream($handle);
+ while (true) {
+ // Check for this server's max retry-limit, and switch server?
+ if (++$attempt > self::MAX_CHUNK_RETRIES) {
+ $uploadUrl = null;
+ }
+
+ // Try to switch to another server.
+ if ($uploadUrl === null) {
+ $uploadUrl = array_shift($uploadUrls);
+ // Fail if there are no upload URLs left.
+ if ($uploadUrl === null) {
+ throw new \RuntimeException('There are no more upload URLs.');
+ }
+ // Reset state.
+ $attempt = 1; // As if "++$attempt" had ran once, above.
+ $offset = 0;
+ $chunk = min($length, self::MIN_CHUNK_SIZE);
+ }
+
+ // Prepare request.
+ $request = new Request($this->ig, $uploadUrl->getUrl());
+ $request
+ ->setAddDefaultHeaders(false)
+ ->addHeader('Content-Type', 'application/octet-stream')
+ ->addHeader('Session-ID', $sessionId)
+ ->addHeader('Content-Disposition', 'attachment; filename="video.mov"')
+ ->addHeader('Content-Range', 'bytes '.$offset.'-'.($offset + $chunk - 1).'/'.$length)
+ ->addHeader('job', $uploadUrl->getJob())
+ ->setBody(new LimitStream($stream, $chunk, $offset));
+
+ // When uploading videos to albums, we must fake-inject the
+ // "sessionid" cookie (the official app fake-injects it too).
+ if ($targetFeed === Constants::FEED_TIMELINE_ALBUM && $sessionIDCookie !== null) {
+ // We'll add it with the default options ("single use")
+ // so the fake cookie is only added to THIS request.
+ $this->ig->client->fakeCookies()->add('sessionid', $sessionIDCookie);
+ }
+
+ // Perform the upload of the current chunk.
+ $start = microtime(true);
+
+ try {
+ $httpResponse = $request->getHttpResponse();
+ } catch (NetworkException $e) {
+ // Ignore network exceptions.
+ continue;
+ }
+
+ // Determine new chunk size based on upload duration.
+ $newChunkSize = (int) ($chunk / (microtime(true) - $start) * 5);
+ // Ensure that the new chunk size is in valid range.
+ $newChunkSize = min(self::MAX_CHUNK_SIZE, max(self::MIN_CHUNK_SIZE, $newChunkSize));
+
+ $result = null;
+
+ try {
+ /** @var Response\UploadVideoResponse $result */
+ $result = $request->getResponse(new Response\UploadVideoResponse());
+ } catch (CheckpointRequiredException $e) {
+ throw $e;
+ } catch (LoginRequiredException $e) {
+ throw $e;
+ } catch (FeedbackRequiredException $e) {
+ throw $e;
+ } catch (ConsentRequiredException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Ignore everything else.
+ }
+
+ // Process the server response...
+ switch ($httpResponse->getStatusCode()) {
+ case 200:
+ // All chunks are uploaded, but if we don't have a
+ // response-result now then we must retry a new server.
+ if ($result === null) {
+ $uploadUrl = null;
+ break;
+ }
+
+ // SUCCESS! :-)
+ return $result;
+ case 201:
+ // The server has given us a regular reply. We expect it
+ // to be a range-reply, such as "0-3912399/23929393".
+ // Their server often drops chunks during peak hours,
+ // and in that case the first range may not start at
+ // zero, or there may be gaps or multiple ranges, such
+ // as "0-4076155/8152310,6114234-8152309/8152310". We'll
+ // handle that by re-uploading whatever they've dropped.
+ if (!$httpResponse->hasHeader('Range')) {
+ $uploadUrl = null;
+ break;
+ }
+ $range = $this->_getFirstMissingRange($httpResponse->getHeaderLine('Range'));
+ if ($range !== null) {
+ $offset = $range[0];
+ $chunk = min($newChunkSize, $range[1] - $range[0] + 1);
+ } else {
+ $chunk = min($newChunkSize, $length - $offset);
+ }
+
+ // Reset attempts count on successful upload.
+ $attempt = 0;
+ break;
+ case 400:
+ case 403:
+ case 511:
+ throw new \RuntimeException(sprintf(
+ 'Instagram\'s server returned HTTP status "%d".',
+ $httpResponse->getStatusCode()
+ ));
+ case 422:
+ throw new \RuntimeException('Instagram\'s server says that the video is corrupt.');
+ default:
+ }
+ }
+ } finally {
+ // Guaranteed to release handle even if something bad happens above!
+ Utils::safe_fclose($handle);
+ }
+
+ // We are never supposed to get here!
+ throw new \LogicException('Something went wrong during video upload.');
+ }
+
+ /**
+ * Performs a segmented upload of a video file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \Exception
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ protected function _uploadSegmentedVideo(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $videoDetails = $internalMetadata->getVideoDetails();
+
+ // We must split the video into segments before running any requests.
+ $segments = $this->_splitVideoIntoSegments($targetFeed, $videoDetails);
+
+ $uploadParams = $this->_getVideoUploadParams($targetFeed, $internalMetadata);
+ $uploadParams = Utils::reorderByHashCode($uploadParams);
+
+ // This request gives us a stream identifier.
+ $startRequest = new Request($this->ig, sprintf(
+ 'https://i.instagram.com/rupload_igvideo/%s?segmented=true&phase=start',
+ Signatures::generateUUID()
+ ));
+ $startRequest
+ ->setAddDefaultHeaders(false)
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams))
+ // Dirty hack to make a POST request.
+ ->setBody(stream_for());
+ /** @var Response\SegmentedStartResponse $startResponse */
+ $startResponse = $startRequest->getResponse(new Response\SegmentedStartResponse());
+ $streamId = $startResponse->getStreamId();
+
+ // Upload the segments.
+ try {
+ $offset = 0;
+ // Yep, no UUID here like in other resumable uploaders. Seems like a bug.
+ $waterfallId = Utils::generateUploadId();
+ foreach ($segments as $segment) {
+ $endpoint = sprintf(
+ 'https://i.instagram.com/rupload_igvideo/%s-%d-%d?segmented=true&phase=transfer',
+ md5($segment->getFilename()),
+ 0,
+ $segment->getFilesize()
+ );
+
+ $offsetTemplate = new Request($this->ig, $endpoint);
+ $offsetTemplate
+ ->setAddDefaultHeaders(false)
+ ->addHeader('Segment-Start-Offset', $offset)
+ // 1 => Audio, 2 => Video, 3 => Mixed.
+ ->addHeader('Segment-Type', $segment->getAudioCodec() !== null ? 1 : 2)
+ ->addHeader('Stream-Id', $streamId)
+ ->addHeader('X_FB_VIDEO_WATERFALL_ID', $waterfallId)
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams));
+
+ $uploadTemplate = clone $offsetTemplate;
+ $uploadTemplate
+ ->addHeader('X-Entity-Type', 'video/mp4')
+ ->addHeader('X-Entity-Name', basename(parse_url($endpoint, PHP_URL_PATH)))
+ ->addHeader('X-Entity-Length', $segment->getFilesize());
+
+ $this->_uploadResumableMedia($segment, $offsetTemplate, $uploadTemplate, false);
+ // Offset seems to be used just for ordering the segments.
+ $offset += $segment->getFilesize();
+ }
+ } finally {
+ // Remove the segments, because we don't need them anymore.
+ foreach ($segments as $segment) {
+ @unlink($segment->getFilename());
+ }
+ }
+
+ // Finalize the upload.
+ $endRequest = new Request($this->ig, sprintf(
+ 'https://i.instagram.com/rupload_igvideo/%s?segmented=true&phase=end',
+ Signatures::generateUUID()
+ ));
+ $endRequest
+ ->setAddDefaultHeaders(false)
+ ->addHeader('Stream-Id', $streamId)
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams))
+ // Dirty hack to make a POST request.
+ ->setBody(stream_for());
+ /** @var Response\GenericResponse $result */
+ $result = $endRequest->getResponse(new Response\GenericResponse());
+
+ return $result;
+ }
+
+ /**
+ * Performs a resumable upload of a video file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ protected function _uploadResumableVideo(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $rurCookie = $this->ig->client->getCookie('rur', 'i.instagram.com');
+ if ($rurCookie === null || $rurCookie->getValue() === '') {
+ throw new \RuntimeException(
+ 'Unable to find the necessary "rur" cookie for uploading video.'
+ );
+ }
+
+ $videoDetails = $internalMetadata->getVideoDetails();
+
+ $endpoint = sprintf('https://i.instagram.com/rupload_igvideo/%s_%d_%d?target=%s',
+ $internalMetadata->getUploadId(),
+ 0,
+ Utils::hashCode($videoDetails->getFilename()),
+ $rurCookie->getValue()
+ );
+
+ $uploadParams = $this->_getVideoUploadParams($targetFeed, $internalMetadata);
+ $uploadParams = Utils::reorderByHashCode($uploadParams);
+
+ $offsetTemplate = new Request($this->ig, $endpoint);
+ $offsetTemplate
+ ->setAddDefaultHeaders(false)
+ ->addHeader('X_FB_VIDEO_WATERFALL_ID', Signatures::generateUUID(true))
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams));
+
+ $uploadTemplate = clone $offsetTemplate;
+ $uploadTemplate
+ ->addHeader('X-Entity-Type', 'video/mp4')
+ ->addHeader('X-Entity-Name', basename(parse_url($endpoint, PHP_URL_PATH)))
+ ->addHeader('X-Entity-Length', $videoDetails->getFilesize());
+
+ return $this->_uploadResumableMedia(
+ $videoDetails,
+ $offsetTemplate,
+ $uploadTemplate,
+ $this->ig->isExperimentEnabled(
+ 'ig_android_skip_get_fbupload_universe',
+ 'video_skip_get'
+ )
+ );
+ }
+
+ /**
+ * Determine whether to use segmented video uploader based on target feed and internal metadata.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return bool
+ */
+ protected function _useSegmentedVideoUploader(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // No segmentation for album video.
+ if ($targetFeed === Constants::FEED_TIMELINE_ALBUM) {
+ return false;
+ }
+
+ // ffmpeg is required for video segmentation.
+ try {
+ FFmpeg::factory();
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ // There is no need to segment short videos.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $minDuration = (int) $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ // NOTE: This typo is intentional. Instagram named it that way.
+ 'segment_duration_threashold_feed',
+ 10
+ );
+ break;
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ $minDuration = (int) $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ // NOTE: This typo is intentional. Instagram named it that way.
+ 'segment_duration_threashold_story_raven',
+ 0
+ );
+ break;
+ case Constants::FEED_TV:
+ $minDuration = 150;
+ break;
+ default:
+ $minDuration = 31536000; // 1 year.
+ }
+ if ((int) $internalMetadata->getVideoDetails()->getDuration() < $minDuration) {
+ return false;
+ }
+
+ // Check experiments for the target feed.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_video_segmented_upload_universe',
+ 'segment_enabled_feed',
+ true);
+ break;
+ case Constants::FEED_DIRECT:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_direct_video_segmented_upload_universe',
+ 'is_enabled_segment_direct');
+ break;
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_reel_raven_video_segmented_upload_universe',
+ 'segment_enabled_story_raven');
+ break;
+ case Constants::FEED_TV:
+ $result = true;
+ break;
+ default:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_video_segmented_upload_universe',
+ 'segment_enabled_unknown',
+ true);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Determine whether to use resumable video uploader based on target feed and internal metadata.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return bool
+ */
+ protected function _useResumableVideoUploader(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_fbupload_sidecar_video_universe',
+ 'is_enabled_fbupload_sidecar_video');
+ break;
+ case Constants::FEED_TIMELINE:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_followers_share');
+ break;
+ case Constants::FEED_DIRECT:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_direct_share');
+ break;
+ case Constants::FEED_STORY:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_reel_share');
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_story_share');
+ break;
+ case Constants::FEED_TV:
+ $result = true;
+ break;
+ default:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_unknown');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get retry context for media upload.
+ *
+ * @return array
+ */
+ protected function _getRetryContext()
+ {
+ return [
+ // TODO increment it with every fail.
+ 'num_step_auto_retry' => 0,
+ 'num_reupload' => 0,
+ 'num_step_manual_retry' => 0,
+ ];
+ }
+
+ /**
+ * Get params for photo upload job.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return array
+ */
+ protected function _getPhotoUploadParams(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // Common params.
+ $result = [
+ 'upload_id' => (string) $internalMetadata->getUploadId(),
+ 'retry_context' => json_encode($this->_getRetryContext()),
+ 'image_compression' => '{"lib_name":"moz","lib_version":"3.1.m","quality":"87"}',
+ 'xsharing_user_ids' => json_encode([]),
+ 'media_type' => $internalMetadata->getVideoDetails() !== null
+ ? (string) Response\Model\Item::VIDEO
+ : (string) Response\Model\Item::PHOTO,
+ ];
+ // Target feed's specific params.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result['is_sidecar'] = '1';
+ break;
+ default:
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get params for video upload job.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return array
+ */
+ protected function _getVideoUploadParams(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $videoDetails = $internalMetadata->getVideoDetails();
+ // Common params.
+ $result = [
+ 'upload_id' => (string) $internalMetadata->getUploadId(),
+ 'retry_context' => json_encode($this->_getRetryContext()),
+ 'xsharing_user_ids' => json_encode([]),
+ 'upload_media_height' => (string) $videoDetails->getHeight(),
+ 'upload_media_width' => (string) $videoDetails->getWidth(),
+ 'upload_media_duration_ms' => (string) $videoDetails->getDurationInMsec(),
+ 'media_type' => (string) Response\Model\Item::VIDEO,
+ // TODO select with targetFeed (?)
+ 'potential_share_types' => json_encode(['not supported type']),
+ ];
+ // Target feed's specific params.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result['is_sidecar'] = '1';
+ break;
+ case Constants::FEED_DIRECT:
+ $result['direct_v2'] = '1';
+ $result['rotate'] = '0';
+ $result['hflip'] = 'false';
+ break;
+ case Constants::FEED_STORY:
+ $result['for_album'] = '1';
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $result['for_direct_story'] = '1';
+ break;
+ case Constants::FEED_TV:
+ $result['is_igtv_video'] = '1';
+ break;
+ default:
+ }
+
+ return $result;
+ }
+
+ /**
+ * Find the segments after ffmpeg processing.
+ *
+ * @param string $outputDirectory The directory to look in.
+ * @param string $prefix The filename prefix.
+ *
+ * @return array
+ */
+ protected function _findSegments(
+ $outputDirectory,
+ $prefix)
+ {
+ // Video segments will be uploaded before the audio one.
+ $result = glob("{$outputDirectory}/{$prefix}.video.*.mp4");
+
+ // Audio always goes into one segment, so we can use is_file() here.
+ $audioTrack = "{$outputDirectory}/{$prefix}.audio.mp4";
+ if (is_file($audioTrack)) {
+ $result[] = $audioTrack;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Split the video file into segments.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param VideoDetails $videoDetails
+ * @param FFmpeg|null $ffmpeg
+ * @param string|null $outputDirectory
+ *
+ * @throws \Exception
+ *
+ * @return VideoDetails[]
+ */
+ protected function _splitVideoIntoSegments(
+ $targetFeed,
+ VideoDetails $videoDetails,
+ FFmpeg $ffmpeg = null,
+ $outputDirectory = null)
+ {
+ if ($ffmpeg === null) {
+ $ffmpeg = FFmpeg::factory();
+ }
+ if ($outputDirectory === null) {
+ $outputDirectory = Utils::$defaultTmpPath === null ? sys_get_temp_dir() : Utils::$defaultTmpPath;
+ }
+ // Check whether the output directory is valid.
+ $targetDirectory = realpath($outputDirectory);
+ if ($targetDirectory === false || !is_dir($targetDirectory) || !is_writable($targetDirectory)) {
+ throw new \RuntimeException(sprintf(
+ 'Directory "%s" is missing or is not writable.',
+ $outputDirectory
+ ));
+ }
+
+ $prefix = sha1($videoDetails->getFilename().uniqid('', true));
+
+ try {
+ // Split the video stream into a multiple segments by time.
+ $ffmpeg->run(sprintf(
+ '-i %s -c:v copy -an -dn -sn -f segment -segment_time %d -segment_format mp4 %s',
+ Args::escape($videoDetails->getFilename()),
+ $this->_getTargetSegmentDuration($targetFeed),
+ Args::escape(sprintf(
+ '%s%s%s.video.%%03d.mp4',
+ $outputDirectory,
+ DIRECTORY_SEPARATOR,
+ $prefix
+ ))
+ ));
+
+ if ($videoDetails->getAudioCodec() !== null) {
+ // Save the audio stream in one segment.
+ $ffmpeg->run(sprintf(
+ '-i %s -c:a copy -vn -dn -sn -f mp4 %s',
+ Args::escape($videoDetails->getFilename()),
+ Args::escape(sprintf(
+ '%s%s%s.audio.mp4',
+ $outputDirectory,
+ DIRECTORY_SEPARATOR,
+ $prefix
+ ))
+ ));
+ }
+ } catch (\RuntimeException $e) {
+ // Find and remove all segments (if any).
+ $files = $this->_findSegments($outputDirectory, $prefix);
+ foreach ($files as $file) {
+ @unlink($file);
+ }
+ // Re-throw the exception.
+ throw $e;
+ }
+
+ // Collect segments.
+ $files = $this->_findSegments($outputDirectory, $prefix);
+ if (empty($files)) {
+ throw new \RuntimeException('Something went wrong while splitting the video into segments.');
+ }
+ $result = [];
+
+ try {
+ // Wrap them into VideoDetails.
+ foreach ($files as $file) {
+ $result[] = new VideoDetails($file);
+ }
+ } catch (\Exception $e) {
+ // Cleanup when something went wrong.
+ foreach ($files as $file) {
+ @unlink($file);
+ }
+
+ throw $e;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get target segment duration in seconds.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return int
+ */
+ protected function _getTargetSegmentDuration(
+ $targetFeed)
+ {
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $duration = $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ 'target_segment_duration_feed',
+ 5
+ );
+ break;
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ $duration = $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ 'target_segment_duration_story_raven',
+ 2
+ );
+ break;
+ case Constants::FEED_TV:
+ $duration = 100;
+ break;
+ default:
+ throw new \InvalidArgumentException("Unsupported feed {$targetFeed}.");
+ }
+
+ return (int) $duration;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Live.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Live.php
new file mode 100755
index 0000000..1e8e318
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Live.php
@@ -0,0 +1,696 @@
+ig->isExperimentEnabled('ig_android_live_suggested_live_expansion', 'is_enabled')) {
+ $endpoint = 'live/get_suggested_live_and_post_live/';
+ }
+
+ return $this->ig->request($endpoint)
+ ->getResponse(new Response\SuggestedBroadcastsResponse());
+ }
+
+ /**
+ * Get broadcast information.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastInfoResponse
+ */
+ public function getInfo(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/info/")
+ ->getResponse(new Response\BroadcastInfoResponse());
+ }
+
+ /**
+ * Get the viewer list of a broadcast.
+ *
+ * WARNING: You MUST be the owner of the broadcast. Otherwise Instagram won't send any API reply!
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ViewerListResponse
+ */
+ public function getViewerList(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_viewer_list/")
+ ->getResponse(new Response\ViewerListResponse());
+ }
+
+ /**
+ * Get the final viewer list of a broadcast after it has ended.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FinalViewerListResponse
+ */
+ public function getFinalViewerList(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_final_viewer_list/")
+ ->getResponse(new Response\FinalViewerListResponse());
+ }
+
+ /**
+ * Get the viewer list of a post-live (saved replay) broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PostLiveViewerListResponse
+ */
+ public function getPostLiveViewerList(
+ $broadcastId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("live/{$broadcastId}/get_post_live_viewers_list/");
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\PostLiveViewerListResponse());
+ }
+
+ /**
+ * Get a live broadcast's heartbeat and viewer count.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param bool $isViewer Indicates if this request is being ran as a viewer (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastHeartbeatAndViewerCountResponse
+ */
+ public function getHeartbeatAndViewerCount(
+ $broadcastId,
+ $isViewer = false)
+ {
+ $request = $this->ig->request("live/{$broadcastId}/heartbeat_and_get_viewer_count/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+ if ($isViewer) {
+ $request->addPost('live_with_eligibility', 1);
+ } else {
+ $request->addPost('offset_to_video_start', 0);
+ }
+
+ return $request->getResponse(new Response\BroadcastHeartbeatAndViewerCountResponse());
+ }
+
+ /**
+ * Get a live broadcast's join request counts.
+ *
+ * Note: This request **will** return null if there have been no pending
+ * join requests have been made. Please have your code check for null.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $lastTotalCount Last join request count (optional).
+ * @param int $lastSeenTs Last seen timestamp (optional).
+ * @param int $lastFetchTs Last fetch timestamp (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastJoinRequestCountResponse|null
+ */
+ public function getJoinRequestCounts(
+ $broadcastId,
+ $lastTotalCount = 0,
+ $lastSeenTs = 0,
+ $lastFetchTs = 0)
+ {
+ try {
+ return $this->ig->request("live/{$broadcastId}/get_join_request_counts/")
+ ->addParam('last_total_count', $lastTotalCount)
+ ->addParam('last_seen_ts', $lastSeenTs)
+ ->addParam('last_fetch_ts', $lastFetchTs)
+ ->getResponse(new Response\BroadcastJoinRequestCountResponse());
+ } catch (\InstagramAPI\Exception\EmptyResponseException $e) {
+ return null;
+ }
+ }
+
+ /**
+ * Show question in a live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function showQuestion(
+ $broadcastId,
+ $questionId)
+ {
+ return $this->ig->request("live/{$broadcastId}/question/{$questionId}/activate/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Hide question in a live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function hideQuestion(
+ $broadcastId,
+ $questionId)
+ {
+ return $this->ig->request("live/{$broadcastId}/question/{$questionId}/deactivate/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Asks a question to the host of the broadcast.
+ *
+ * Note: This function is only used by the viewers of a broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $questionText Your question text.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function question(
+ $broadcastId,
+ $questionText)
+ {
+ return $this->ig->request("live/{$broadcastId}/questions")
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('text', $questionText)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get all received responses from a story question.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastQuestionsResponse
+ */
+ public function getQuestions()
+ {
+ return $this->ig->request('live/get_questions/')
+ ->getResponse(new Response\BroadcastQuestionsResponse());
+ }
+
+ /**
+ * Get all received responses from the current broadcast and a story question.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastQuestionsResponse
+ */
+ public function getLiveBroadcastQuestions(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/questions/")
+ ->addParam('sources', 'story_and_live')
+ ->getResponse(new Response\BroadcastQuestionsResponse());
+ }
+
+ /**
+ * Acknowledges (waves at) a new user after they join.
+ *
+ * Note: This can only be done once to a user, per stream. Additionally, the user must have joined the stream.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $viewerId Numerical UserPK ID of the user to wave to.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function wave(
+ $broadcastId,
+ $viewerId)
+ {
+ return $this->ig->request("live/{$broadcastId}/wave/")
+ ->addPost('viewer_id', $viewerId)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Post a comment to a live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $commentText Your comment text.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentBroadcastResponse
+ */
+ public function comment(
+ $broadcastId,
+ $commentText)
+ {
+ return $this->ig->request("live/{$broadcastId}/comment/")
+ ->addPost('user_breadcrumb', Utils::generateUserBreadcrumb(mb_strlen($commentText)))
+ ->addPost('idempotence_token', Signatures::generateUUID(true))
+ ->addPost('comment_text', $commentText)
+ ->addPost('live_or_vod', 1)
+ ->addPost('offset_to_video_start', 0)
+ ->getResponse(new Response\CommentBroadcastResponse());
+ }
+
+ /**
+ * Pin a comment on live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $commentId Target comment ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PinCommentBroadcastResponse
+ */
+ public function pinComment(
+ $broadcastId,
+ $commentId)
+ {
+ return $this->ig->request("live/{$broadcastId}/pin_comment/")
+ ->addPost('offset_to_video_start', 0)
+ ->addPost('comment_id', $commentId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\PinCommentBroadcastResponse());
+ }
+
+ /**
+ * Unpin a comment on live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $commentId Pinned comment ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UnpinCommentBroadcastResponse
+ */
+ public function unpinComment(
+ $broadcastId,
+ $commentId)
+ {
+ return $this->ig->request("live/{$broadcastId}/unpin_comment/")
+ ->addPost('offset_to_video_start', 0)
+ ->addPost('comment_id', $commentId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UnpinCommentBroadcastResponse());
+ }
+
+ /**
+ * Get broadcast comments.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $lastCommentTs Last comments timestamp (optional).
+ * @param int $commentsRequested Number of comments requested (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastCommentsResponse
+ */
+ public function getComments(
+ $broadcastId,
+ $lastCommentTs = 0,
+ $commentsRequested = 3)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_comment/")
+ ->addParam('last_comment_ts', $lastCommentTs)
+// ->addParam('num_comments_requested', $commentsRequested)
+ ->getResponse(new Response\BroadcastCommentsResponse());
+ }
+
+ /**
+ * Get post-live (saved replay) broadcast comments.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $startingOffset (optional) The time-offset to start at when retrieving the comments.
+ * @param string $encodingTag (optional) TODO: ?.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PostLiveCommentsResponse
+ */
+ public function getPostLiveComments(
+ $broadcastId,
+ $startingOffset = 0,
+ $encodingTag = 'instagram_dash_remuxed')
+ {
+ return $this->ig->request("live/{$broadcastId}/get_post_live_comments/")
+ ->addParam('starting_offset', $startingOffset)
+ ->addParam('encoding_tag', $encodingTag)
+ ->getResponse(new Response\PostLiveCommentsResponse());
+ }
+
+ /**
+ * Enable viewer comments on your live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EnableDisableLiveCommentsResponse
+ */
+ public function enableComments(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/unmute_comment/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\EnableDisableLiveCommentsResponse());
+ }
+
+ /**
+ * Disable viewer comments on your live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EnableDisableLiveCommentsResponse
+ */
+ public function disableComments(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/mute_comment/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\EnableDisableLiveCommentsResponse());
+ }
+
+ /**
+ * Like a broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $likeCount Number of likes ("hearts") to send (optional).
+ * @param int $burstLikeCount Number of burst likes ("hearts") to send (optional).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastLikeResponse
+ */
+ public function like(
+ $broadcastId,
+ $likeCount = 1,
+ $burstLikeCount = 0)
+ {
+ if ($likeCount < 1 || $likeCount > 6) {
+ throw new \InvalidArgumentException('Like count must be a number from 1 to 6.');
+ }
+
+ return $this->ig->request("live/{$broadcastId}/like/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_like_count', $likeCount)
+ ->addPost('user_like_burst_count', $burstLikeCount)
+ ->addPost('offset_to_video_start', 0)
+ ->getResponse(new Response\BroadcastLikeResponse());
+ }
+
+ /**
+ * Get a live broadcast's like count.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $likeTs Like timestamp.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastLikeCountResponse
+ */
+ public function getLikeCount(
+ $broadcastId,
+ $likeTs = 0)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_like_count/")
+ ->addParam('like_ts', $likeTs)
+ ->getResponse(new Response\BroadcastLikeCountResponse());
+ }
+
+ /**
+ * Get post-live (saved replay) broadcast likes.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $startingOffset (optional) The time-offset to start at when retrieving the likes.
+ * @param string $encodingTag (optional) TODO: ?.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PostLiveLikesResponse
+ */
+ public function getPostLiveLikes(
+ $broadcastId,
+ $startingOffset = 0,
+ $encodingTag = 'instagram_dash_remuxed')
+ {
+ return $this->ig->request("live/{$broadcastId}/get_post_live_likes/")
+ ->addParam('starting_offset', $startingOffset)
+ ->addParam('encoding_tag', $encodingTag)
+ ->getResponse(new Response\PostLiveLikesResponse());
+ }
+
+ /**
+ * Create a live broadcast.
+ *
+ * Read the description of `start()` for proper usage.
+ *
+ * @param int $previewWidth (optional) Width.
+ * @param int $previewHeight (optional) Height.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateLiveResponse
+ *
+ * @see Live::start()
+ * @see Live::end()
+ */
+ public function create(
+ $previewWidth = 1080,
+ $previewHeight = 2076)
+ {
+ return $this->ig->request('live/create/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('preview_height', $previewHeight)
+ ->addPost('preview_width', $previewWidth)
+ ->addPost('broadcast_type', 'RTMP_SWAP_ENABLED')
+ ->addPost('internal_only', 0)
+ ->getResponse(new Response\CreateLiveResponse());
+ }
+
+ /**
+ * Start a live broadcast.
+ *
+ * Note that you MUST first call `create()` to get a broadcast-ID and its
+ * RTMP upload-URL. Next, simply begin sending your actual video broadcast
+ * to the stream-upload URL. And then call `start()` with the broadcast-ID
+ * to make the stream available to viewers.
+ *
+ * Also note that broadcasting to the video stream URL must be done via
+ * other software, since it ISN'T (and won't be) handled by this library!
+ *
+ * Lastly, note that stopping the stream is done either via RTMP signals,
+ * which your broadcasting software MUST output properly (FFmpeg DOESN'T do
+ * it without special patching!), OR by calling the `end()` function.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string|null $latitude (optional) Latitude.
+ * @param string|null $longitude (optional) Longitude.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StartLiveResponse
+ *
+ * @see Live::create()
+ * @see Live::end()
+ */
+ public function start(
+ $broadcastId,
+ $latitude = null,
+ $longitude = null)
+ {
+ $response = $this->ig->request("live/{$broadcastId}/start/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ if ($latitude !== null && $longitude !== null) {
+ $response->addPost('latitude', $latitude)
+ ->addPost('longitude', $longitude);
+ }
+
+ $response = $response->getResponse(new Response\StartLiveResponse());
+
+ if ($this->ig->isExperimentEnabled('ig_android_live_qa_broadcaster_v1_universe', 'is_enabled')) {
+ $this->_getQuestionStatus($broadcastId);
+ }
+
+ return $response;
+ }
+
+ /**
+ * Get question status.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ private function _getQuestionStatus(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/question_status/")
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('allow_question_submission', true)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Acknowledges a copyright warning from Instagram after detected via a heartbeat request.
+ *
+ * `NOTE:` It is recommended that you view the `liveBroadcast` example
+ * to see the proper usage of this function.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function resumeBroadcastAfterContentMatch(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/resume_broadcast_after_content_match/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * End a live broadcast.
+ *
+ * `NOTE:` To end your broadcast, you MUST use the `broadcast_id` value
+ * which was assigned to you in the `create()` response.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param bool $copyrightWarning True when broadcast is ended via a copyright notice (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ *
+ * @see Live::create()
+ * @see Live::start()
+ */
+ public function end(
+ $broadcastId,
+ $copyrightWarning = false)
+ {
+ return $this->ig->request("live/{$broadcastId}/end_broadcast/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('end_after_copyright_warning', $copyrightWarning)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Add a finished broadcast to your post-live feed (saved replay).
+ *
+ * The broadcast must have ended before you can call this function.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function addToPostLive(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/add_to_post_live/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Delete a saved post-live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function deletePostLive(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/delete_post_live/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Location.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Location.php
new file mode 100755
index 0000000..16c1d63
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Location.php
@@ -0,0 +1,329 @@
+ig->request('location_search/')
+ ->addParam('rank_token', $this->ig->account_id.'_'.Signatures::generateUUID())
+ ->addParam('latitude', $latitude)
+ ->addParam('longitude', $longitude);
+
+ if ($query === null) {
+ $locations->addParam('timestamp', time());
+ } else {
+ $locations->addParam('search_query', $query);
+ }
+
+ return $locations->getResponse(new Response\LocationResponse());
+ }
+
+ /**
+ * Search for Facebook locations by name.
+ *
+ * WARNING: The locations found by this function DO NOT work for attaching
+ * locations to media uploads. Use Location::search() instead!
+ *
+ * @param string $query Finds locations containing this string.
+ * @param string[]|int[] $excludeList Array of numerical location IDs (ie "17841562498105353")
+ * to exclude from the response, allowing you to skip locations
+ * from a previous call to get more results.
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FBLocationResponse
+ *
+ * @see FBLocationResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function findPlaces(
+ $query,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation. Do NOT use throwIfInvalidHashtag here.
+ if (!is_string($query) || $query === null) {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+ $location = $this->_paginateWithExclusion(
+ $this->ig->request('fbsearch/places/')
+ ->addParam('timezone_offset', date('Z'))
+ ->addParam('query', $query),
+ $excludeList,
+ $rankToken
+ );
+
+ try {
+ /** @var Response\FBLocationResponse $result */
+ $result = $location->getResponse(new Response\FBLocationResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\FBLocationResponse([
+ 'has_more' => false,
+ 'items' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Search for Facebook locations by geographical location.
+ *
+ * WARNING: The locations found by this function DO NOT work for attaching
+ * locations to media uploads. Use Location::search() instead!
+ *
+ * @param string $latitude Latitude.
+ * @param string $longitude Longitude.
+ * @param string|null $query (Optional) Finds locations containing this string.
+ * @param string[]|int[] $excludeList Array of numerical location IDs (ie "17841562498105353")
+ * to exclude from the response, allowing you to skip locations
+ * from a previous call to get more results.
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FBLocationResponse
+ *
+ * @see FBLocationResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function findPlacesNearby(
+ $latitude,
+ $longitude,
+ $query = null,
+ $excludeList = [],
+ $rankToken = null)
+ {
+ $location = $this->_paginateWithExclusion(
+ $this->ig->request('fbsearch/places/')
+ ->addParam('lat', $latitude)
+ ->addParam('lng', $longitude)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken,
+ 50
+ );
+
+ if ($query !== null) {
+ $location->addParam('query', $query);
+ }
+
+ try {
+ /** @var Response\FBLocationResponse() $result */
+ $result = $location->getResponse(new Response\FBLocationResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\FBLocationResponse([
+ 'has_more' => false,
+ 'items' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get related locations by location ID.
+ *
+ * Note that this endpoint almost never succeeds, because most locations do
+ * not have ANY related locations!
+ *
+ * @param string $locationId The internal ID of a location (from a field
+ * such as "pk", "external_id" or "facebook_places_id").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RelatedLocationResponse
+ */
+ public function getRelated(
+ $locationId)
+ {
+ return $this->ig->request("locations/{$locationId}/related/")
+ ->addParam('visited', json_encode(['id' => $locationId, 'type' => 'location']))
+ ->addParam('related_types', json_encode(['location']))
+ ->getResponse(new Response\RelatedLocationResponse());
+ }
+
+ /**
+ * Get the media feed for a location.
+ *
+ * Note that if your location is a "group" (such as a city), the feed will
+ * include media from multiple locations within that area. But if your
+ * location is a very specific place such as a specific night club, it will
+ * usually only include media from that exact location.
+ *
+ * @param string $locationId The internal ID of a location (from a field
+ * such as "pk", "external_id" or "facebook_places_id").
+ * @param string $rankToken The feed UUID. Use must use the same value for all pages of the feed.
+ * @param string|null $tab Section tab for locations. Values: "ranked" and "recent"
+ * @param int[]|null $nextMediaIds Used for pagination.
+ * @param int|null $nextPage Used for pagination.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LocationFeedResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFeed(
+ $locationId,
+ $rankToken,
+ $tab = 'ranked',
+ $nextMediaIds = null,
+ $nextPage = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidRankToken($rankToken);
+ if ($tab !== 'ranked' && $tab !== 'recent') {
+ throw new \InvalidArgumentException('The provided section tab is invalid.');
+ }
+
+ $locationFeed = $this->ig->request("locations/{$locationId}/sections/")
+ ->setSignedPost(false)
+ ->addPost('rank_token', $rankToken)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('session_id', $this->ig->session_id)
+ ->addPost('tab', $tab);
+
+ if ($nextMediaIds !== null) {
+ if (!is_array($nextMediaIds) || !array_filter($nextMediaIds, 'is_int')) {
+ throw new \InvalidArgumentException('Next media IDs must be an Int[].');
+ }
+ $locationFeed->addPost('next_media_ids', json_encode($nextMediaIds));
+ }
+
+ if ($nextPage !== null) {
+ $locationFeed->addPost('page', $nextPage);
+ }
+
+ if ($maxId !== null) {
+ $locationFeed->addPost('max_id', $maxId);
+ }
+
+ return $locationFeed->getResponse(new Response\LocationFeedResponse());
+ }
+
+ /**
+ * Get the story feed for a location.
+ *
+ * @param string $locationId The internal ID of a location (from a field
+ * such as "pk", "external_id" or "facebook_places_id").
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LocationStoryResponse
+ */
+ public function getStoryFeed(
+ $locationId)
+ {
+ return $this->ig->request("locations/{$locationId}/story/")
+ ->getResponse(new Response\LocationStoryResponse());
+ }
+
+ /**
+ * Mark LocationStoryResponse story media items as seen.
+ *
+ * The "story" property of a `LocationStoryResponse` only gives you a
+ * list of story media. It doesn't actually mark any stories as "seen",
+ * so the user doesn't know that you've seen their story. Actually
+ * marking the story as "seen" is done via this endpoint instead. The
+ * official app calls this endpoint periodically (with 1 or more items
+ * at a time) while watching a story.
+ *
+ * This tells the user that you've seen their story, and also helps
+ * Instagram know that it shouldn't give you those seen stories again
+ * if you request the same location feed multiple times.
+ *
+ * Tip: You can pass in the whole "getItems()" array from the location's
+ * "story" property, to easily mark all of the LocationStoryResponse's story
+ * media items as seen.
+ *
+ * @param Response\LocationStoryResponse $locationFeed The location feed
+ * response object which
+ * the story media items
+ * came from. The story
+ * items MUST belong to it.
+ * @param Response\Model\Item[] $items Array of one or more
+ * story media Items.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Story::markMediaSeen()
+ * @see Hashtag::markStoryMediaSeen()
+ */
+ public function markStoryMediaSeen(
+ Response\LocationStoryResponse $locationFeed,
+ array $items)
+ {
+ // Extract the Location Story-Tray ID from the user's location response.
+ // NOTE: This can NEVER fail if the user has properly given us the exact
+ // same location response that they got the story items from!
+ $sourceId = '';
+ if ($locationFeed->getStory() instanceof Response\Model\StoryTray) {
+ $sourceId = $locationFeed->getStory()->getId();
+ }
+ if (!strlen($sourceId)) {
+ throw new \InvalidArgumentException('Your provided LocationStoryResponse is invalid and does not contain any Location Story-Tray ID.');
+ }
+
+ // Ensure they only gave us valid items for this location response.
+ // NOTE: We validate since people cannot be trusted to use their brain.
+ $validIds = [];
+ foreach ($locationFeed->getStory()->getItems() as $item) {
+ $validIds[$item->getId()] = true;
+ }
+ foreach ($items as $item) {
+ // NOTE: We only check Items here. Other data is rejected by Internal.
+ if ($item instanceof Response\Model\Item && !isset($validIds[$item->getId()])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The item with ID "%s" does not belong to this LocationStoryResponse.',
+ $item->getId()
+ ));
+ }
+ }
+
+ // Mark the story items as seen, with the location as source ID.
+ return $this->ig->internal->markStoryMediaSeen($items, $sourceId);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Media.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Media.php
new file mode 100755
index 0000000..ee748e8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Media.php
@@ -0,0 +1,848 @@
+ig->request("media/{$mediaId}/info/")
+ ->getResponse(new Response\MediaInfoResponse());
+ }
+
+ /**
+ * Delete a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string|int $mediaType The type of the media item you are deleting. One of: "PHOTO", "VIDEO"
+ * "CAROUSEL", or the raw value of the Item's "getMediaType()" function.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaDeleteResponse
+ */
+ public function delete(
+ $mediaId,
+ $mediaType = 'PHOTO')
+ {
+ $mediaType = Utils::checkMediaType($mediaType);
+
+ return $this->ig->request("media/{$mediaId}/delete/")
+ ->addParam('media_type', $mediaType)
+ ->addPost('igtv_feed_preview', false)
+ ->addPost('media_id', $mediaId)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\MediaDeleteResponse());
+ }
+
+ /**
+ * Edit media.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $captionText Caption to use for the media.
+ * @param array|null $metadata (optional) Associative array of optional metadata to edit:
+ * "usertags" - special array with user tagging instructions,
+ * if you want to modify the user tags;
+ * "location" - a Location model object to set the media location,
+ * or boolean FALSE to remove any location from the media.
+ * @param string|int $mediaType The type of the media item you are editing. One of: "PHOTO", "VIDEO"
+ * "CAROUSEL", or the raw value of the Item's "getMediaType()" function.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditMediaResponse
+ *
+ * @see Usertag::tagMedia() for an example of proper "usertags" metadata formatting.
+ * @see Usertag::untagMedia() for an example of proper "usertags" metadata formatting.
+ */
+ public function edit(
+ $mediaId,
+ $captionText = '',
+ array $metadata = null,
+ $mediaType = 'PHOTO')
+ {
+ $mediaType = Utils::checkMediaType($mediaType);
+
+ $request = $this->ig->request("media/{$mediaId}/edit_media/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('caption_text', $captionText);
+
+ if (isset($metadata['usertags'])) {
+ Utils::throwIfInvalidUsertags($metadata['usertags']);
+ $request->addPost('usertags', json_encode($metadata['usertags']));
+ }
+
+ if (isset($metadata['location'])) {
+ if ($metadata['location'] === false) {
+ // The user wants to remove the current location from the media.
+ $request->addPost('location', '{}');
+ } else {
+ // The user wants to add/change the location of the media.
+ if (!$metadata['location'] instanceof Response\Model\Location) {
+ throw new \InvalidArgumentException('The "location" metadata value must be an instance of \InstagramAPI\Response\Model\Location.');
+ }
+
+ $request
+ ->addPost('location', Utils::buildMediaLocationJSON($metadata['location']))
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $metadata['location']->getLat())
+ ->addPost('posting_longitude', $metadata['location']->getLng())
+ ->addPost('media_latitude', $metadata['location']->getLat())
+ ->addPost('media_longitude', $metadata['location']->getLng());
+
+ if ($mediaType === 'CAROUSEL') { // Albums need special handling.
+ $request
+ ->addPost('exif_latitude', 0.0)
+ ->addPost('exif_longitude', 0.0);
+ } else { // All other types of media use "av_" instead of "exif_".
+ $request
+ ->addPost('av_latitude', 0.0)
+ ->addPost('av_longitude', 0.0);
+ }
+ }
+ }
+
+ return $request->getResponse(new Response\EditMediaResponse());
+ }
+
+ /**
+ * Like a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param int $feedPosition The position of the media in the feed.
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ * @param bool $carouselBumped (optional) If the media is carousel bumped.
+ * @param array $extraData (optional) Depending on the module name, additional data is required.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ *
+ * @see Media::_parseLikeParameters() For all supported modules and required parameters.
+ */
+ public function like(
+ $mediaId,
+ $feedPosition,
+ $module = 'feed_timeline',
+ $carouselBumped = false,
+ array $extraData = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/like/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('media_id', $mediaId)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('container_module', $module)
+ ->addPost('feed_position', $feedPosition)
+ ->addPost('is_carousel_bumped_post', $carouselBumped)
+ ->addPost('device_id', $this->ig->device_id);
+
+ if (isset($extraData['carousel_media'])) {
+ $request->addPost('carousel_index', $extraData['carousel_index']);
+ }
+
+ $extraData['media_id'] = $mediaId;
+ $this->_parseLikeParameters('like', $request, $module, $extraData);
+
+ return $request->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unlike a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ * @param array $extraData (optional) Depending on the module name, additional data is required.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ *
+ * @see Media::_parseLikeParameters() For all supported modules and required parameters.
+ */
+ public function unlike(
+ $mediaId,
+ $module = 'feed_timeline',
+ array $extraData = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/unlike/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('media_id', $mediaId)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('module_name', $module);
+
+ $this->_parseLikeParameters('unlike', $request, $module, $extraData);
+
+ return $request->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get feed of your liked media.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LikeFeedResponse
+ */
+ public function getLikedFeed(
+ $maxId = null)
+ {
+ $request = $this->ig->request('feed/liked/');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\LikeFeedResponse());
+ }
+
+ /**
+ * Get list of users who liked a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaLikersResponse
+ */
+ public function getLikers(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/likers/")->getResponse(new Response\MediaLikersResponse());
+ }
+
+ /**
+ * Get a simplified, chronological list of users who liked a media item.
+ *
+ * WARNING! DANGEROUS! Although this function works, we don't know
+ * whether it's used by Instagram's app right now. If it isn't used by
+ * the app, then you can easily get BANNED for using this function!
+ *
+ * If you call this function, you do that AT YOUR OWN RISK and you
+ * risk losing your Instagram account! This notice will be removed if
+ * the function is safe to use. Otherwise this whole function will
+ * be removed someday, if it wasn't safe.
+ *
+ * Only use this if you are OK with possibly losing your account!
+ *
+ * TODO: Research when/if the official app calls this function.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaLikersResponse
+ */
+ public function getLikersChrono(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/likers_chrono/")->getResponse(new Response\MediaLikersResponse());
+ }
+
+ /**
+ * Enable comments for a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function enableComments(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/enable_comments/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Disable comments for a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function disableComments(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/disable_comments/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Post a comment on a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentText Your comment text.
+ * @param string|null $replyCommentId (optional) The comment ID you are replying to, if this is a reply (ie "17895795823020906");
+ * when replying, your $commentText MUST contain an @-mention at the start (ie "@theirusername Hello!").
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ * "comments_v2" - In App: clicking on comments button,
+ * "self_comments_v2" - In App: commenting on your own post,
+ * "comments_v2_feed_timeline" - Unknown,
+ * "comments_v2_feed_contextual_hashtag" - Unknown,
+ * "comments_v2_photo_view_profile" - Unknown,
+ * "comments_v2_video_view_profile" - Unknown,
+ * "comments_v2_media_view_profile" - Unknown,
+ * "comments_v2_feed_contextual_location" - Unknown,
+ * "modal_comment_composer_feed_timeline" - In App: clicking on prompt from timeline.
+ * @param int $carouselIndex (optional) The image selected in a carousel while liking an image.
+ * @param int $feedPosition (optional) The position of the media in the feed.
+ * @param bool $feedBumped (optional) If Instagram bumped this post to the top of your feed.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentResponse
+ */
+ public function comment(
+ $mediaId,
+ $commentText,
+ $replyCommentId = null,
+ $module = 'comments_v2',
+ $carouselIndex = 0,
+ $feedPosition = 0,
+ $feedBumped = false)
+ {
+ $request = $this->ig->request("media/{$mediaId}/comment/")
+ ->addPost('user_breadcrumb', Utils::generateUserBreadcrumb(mb_strlen($commentText)))
+ ->addPost('idempotence_token', Signatures::generateUUID())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('comment_text', $commentText)
+ ->addPost('container_module', $module)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('carousel_index', $carouselIndex)
+ ->addPost('feed_position', $feedPosition)
+ ->addPost('is_carousel_bumped_post', $feedBumped);
+ if ($replyCommentId !== null) {
+ $request->addPost('replied_to_comment_id', $replyCommentId);
+ }
+
+ return $request->getResponse(new Response\CommentResponse());
+ }
+
+ /**
+ * Get media comments.
+ *
+ * Note that this endpoint supports both backwards and forwards pagination.
+ * The only one you should really care about is "max_id" for backwards
+ * ("load older comments") pagination in normal cases. By default, if no
+ * parameter is provided, Instagram gives you the latest page of comments
+ * and then paginates backwards via the "max_id" parameter (and the correct
+ * value for it is the "next_max_id" in the response).
+ *
+ * However, if you come to the comments "from a Push notification" (uses the
+ * "target_comment_id" parameter), then the response will ALSO contain a
+ * "next_min_id" value. In that case, you can get newer comments (than the
+ * target comment) by using THAT value and the "min_id" parameter instead.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of optional parameters, including:
+ * "max_id" - next "maximum ID" (get older comments, before this ID), used for backwards pagination;
+ * "min_id" - next "minimum ID" (get newer comments, after this ID), used for forwards pagination;
+ * "target_comment_id" - used by comment Push notifications to retrieve the page with the specific comment.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaCommentsResponse
+ */
+ public function getComments(
+ $mediaId,
+ array $options = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/comments/")
+ ->addParam('can_support_threading', true);
+
+ // Pagination.
+ if (isset($options['min_id']) && isset($options['max_id'])) {
+ throw new \InvalidArgumentException('You can use either "min_id" or "max_id", but not both at the same time.');
+ }
+ if (isset($options['min_id'])) {
+ $request->addParam('min_id', $options['min_id']);
+ }
+ if (isset($options['max_id'])) {
+ $request->addParam('max_id', $options['max_id']);
+ }
+
+ // Request specific comment (does NOT work together with pagination!).
+ // NOTE: If you try pagination params together with this param, then the
+ // server will reject the request completely and give nothing back!
+ if (isset($options['target_comment_id'])) {
+ if (isset($options['min_id']) || isset($options['max_id'])) {
+ throw new \InvalidArgumentException('You cannot use the "target_comment_id" parameter together with the "min_id" or "max_id" parameters.');
+ }
+ $request->addParam('target_comment_id', $options['target_comment_id']);
+ }
+
+ return $request->getResponse(new Response\MediaCommentsResponse());
+ }
+
+ /**
+ * Get the replies to a specific media comment.
+ *
+ * You should be sure that the comment actually HAS more replies before
+ * calling this endpoint! In that case, the comment itself will have a
+ * non-zero "child comment count" value, as well as some "preview comments".
+ *
+ * If the number of preview comments doesn't match the full "child comments"
+ * count, then you are ready to call this endpoint to retrieve the rest of
+ * them. Do NOT call it frivolously for comments that have no child comments
+ * or where you already have all of them via the child comment previews!
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentId The parent comment's ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "max_id" - next "maximum ID" (get older comments, before this ID), used for backwards pagination;
+ * "min_id" - next "minimum ID" (get newer comments, after this ID), used for forwards pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaCommentRepliesResponse
+ */
+ public function getCommentReplies(
+ $mediaId,
+ $commentId,
+ array $options = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/comments/{$commentId}/inline_child_comments/");
+
+ if (isset($options['min_id'], $options['max_id'])) {
+ throw new \InvalidArgumentException('You can use either "min_id" or "max_id", but not both at the same time.');
+ }
+
+ if (isset($options['max_id'])) {
+ $request->addParam('max_id', $options['max_id']);
+ } elseif (isset($options['min_id'])) {
+ $request->addParam('min_id', $options['min_id']);
+ }
+
+ return $request->getResponse(new Response\MediaCommentRepliesResponse());
+ }
+
+ /**
+ * Delete a comment.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentId The comment's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DeleteCommentResponse
+ */
+ public function deleteComment(
+ $mediaId,
+ $commentId)
+ {
+ return $this->ig->request("media/{$mediaId}/comment/{$commentId}/delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\DeleteCommentResponse());
+ }
+
+ /**
+ * Delete multiple comments.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string|string[] $commentIds The IDs of one or more comments to delete.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DeleteCommentResponse
+ */
+ public function deleteComments(
+ $mediaId,
+ $commentIds)
+ {
+ if (is_array($commentIds)) {
+ $commentIds = implode(',', $commentIds);
+ }
+
+ return $this->ig->request("media/{$mediaId}/comment/bulk_delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('comment_ids_to_delete', $commentIds)
+ ->getResponse(new Response\DeleteCommentResponse());
+ }
+
+ /**
+ * Like a comment.
+ *
+ * @param string $commentId The comment's ID.
+ * @param int $feedPosition The position of the media item in the feed.
+ * @param string $module From which module you're preforming this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentLikeUnlikeResponse
+ */
+ public function likeComment(
+ $commentId,
+ $feedPosition,
+ $module = 'self_comments_v2')
+ {
+ return $this->ig->request("media/{$commentId}/comment_like/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('is_carousel_bumped_post', false)
+ ->addPost('container_module', $module)
+ ->addPost('feed_position', $feedPosition)
+ ->getResponse(new Response\CommentLikeUnlikeResponse());
+ }
+
+ /**
+ * Unlike a comment.
+ *
+ * @param string $commentId The comment's ID.
+ * @param int $feedPosition The position of the media item in the feed.
+ * @param string $module From which module you're preforming this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentLikeUnlikeResponse
+ */
+ public function unlikeComment(
+ $commentId,
+ $feedPosition,
+ $module = 'self_comments_v2')
+ {
+ return $this->ig->request("media/{$commentId}/comment_unlike/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('is_carousel_bumped_post', false)
+ ->addPost('container_module', $module)
+ ->addPost('feed_position', $feedPosition)
+ ->getResponse(new Response\CommentLikeUnlikeResponse());
+ }
+
+ /**
+ * Get list of users who liked a comment.
+ *
+ * @param string $commentId The comment's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentLikersResponse
+ */
+ public function getCommentLikers(
+ $commentId)
+ {
+ return $this->ig->request("media/{$commentId}/comment_likers/")->getResponse(new Response\CommentLikersResponse());
+ }
+
+ /**
+ * Translates comments and/or media captions.
+ *
+ * Note that the text will be translated to American English (en-US).
+ *
+ * @param string|string[] $commentIds The IDs of one or more comments and/or media IDs
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TranslateResponse
+ */
+ public function translateComments(
+ $commentIds)
+ {
+ if (is_array($commentIds)) {
+ $commentIds = implode(',', $commentIds);
+ }
+
+ return $this->ig->request("language/bulk_translate/?comment_ids={$commentIds}")
+ ->getResponse(new Response\TranslateResponse());
+ }
+
+ /**
+ * Validate a web URL for acceptable use as external link.
+ *
+ * This endpoint lets you check if the URL is allowed by Instagram, and is
+ * helpful to call before you try to use a web URL in your media links.
+ *
+ * @param string $url The URL you want to validate.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ValidateURLResponse
+ */
+ public function validateURL(
+ $url)
+ {
+ return $this->ig->request('media/validate_reel_url/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('url', $url)
+ ->getResponse(new Response\ValidateURLResponse());
+ }
+
+ /**
+ * Save a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SaveAndUnsaveMedia
+ */
+ public function save(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/save/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SaveAndUnsaveMedia());
+ }
+
+ /**
+ * Unsave a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SaveAndUnsaveMedia
+ */
+ public function unsave(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/unsave/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SaveAndUnsaveMedia());
+ }
+
+ /**
+ * Get saved media items feed.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SavedFeedResponse
+ */
+ public function getSavedFeed(
+ $maxId = null)
+ {
+ $request = $this->ig->request('feed/saved/');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\SavedFeedResponse());
+ }
+
+ /**
+ * Get blocked media.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BlockedMediaResponse
+ */
+ public function getBlockedMedia()
+ {
+ return $this->ig->request('media/blocked/')
+ ->getResponse(new Response\BlockedMediaResponse());
+ }
+
+ /**
+ * Report media as spam.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $sourceName (optional) Source of the media.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function report(
+ $mediaId,
+ $sourceName = 'feed_contextual_chain')
+ {
+ return $this->ig->request("media/{$mediaId}/flag_media/")
+ ->addPost('media_id', $mediaId)
+ ->addPost('source_name', $sourceName)
+ ->addPost('reason_id', '1')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Report a media comment as spam.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentId The comment's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function reportComment(
+ $mediaId,
+ $commentId)
+ {
+ return $this->ig->request("media/{$mediaId}/comment/{$commentId}/flag/")
+ ->addPost('media_id', $mediaId)
+ ->addPost('comment_id', $commentId)
+ ->addPost('reason', '1')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get media permalink.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PermalinkResponse
+ */
+ public function getPermalink(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/permalink/")
+ ->addParam('share_to_app', 'copy_link')
+ ->getResponse(new Response\PermalinkResponse());
+ }
+
+ /**
+ * Validate and update the parameters for a like or unlike request.
+ *
+ * @param string $type What type of request this is (can be "like" or "unlike").
+ * @param Request $request The request to fill with the parsed data.
+ * @param string $module From which app module (page) you're performing this action.
+ * @param array $extraData Depending on the module name, additional data is required.
+ *
+ * @throws \InvalidArgumentException
+ */
+ protected function _parseLikeParameters(
+ $type,
+ Request $request,
+ $module,
+ array $extraData)
+ {
+ // Is this a "double-tap to like"? Note that Instagram doesn't have
+ // "double-tap to unlike". So this can only be "1" if it's a "like".
+ if ($type === 'like' && isset($extraData['double_tap']) && $extraData['double_tap']) {
+ $request->addUnsignedPost('d', 1);
+ } else {
+ $request->addUnsignedPost('d', 0); // Must always be 0 for "unlike".
+ }
+
+ // Now parse the necessary parameters for the selected module.
+ switch ($module) {
+ case 'feed_contextual_post': // "Explore" tab.
+ if (isset($extraData['explore_source_token'])) {
+ // The explore media `Item::getExploreSourceToken()` value.
+ $request->addPost('explore_source_token', $extraData['explore_source_token']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'profile': // LIST VIEW (when posts are shown vertically by the app
+ // one at a time (as in the Timeline tab)): Any media on
+ // a user profile (their timeline) in list view mode.
+ case 'media_view_profile': // GRID VIEW (standard 3x3): Album (carousel)
+ // on a user profile (their timeline).
+ case 'video_view_profile': // GRID VIEW (standard 3x3): Video on a user
+ // profile (their timeline).
+ case 'photo_view_profile': // GRID VIEW (standard 3x3): Photo on a user
+ // profile (their timeline).
+ if (isset($extraData['username']) && isset($extraData['user_id'])) {
+ // Username and id of the media's owner (the profile owner).
+ $request->addPost('username', $extraData['username'])
+ ->addPost('user_id', $extraData['user_id']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'feed_contextual_hashtag': // "Hashtag" search result.
+ if (isset($extraData['hashtag'])) {
+ // The hashtag where the app found this media.
+ Utils::throwIfInvalidHashtag($extraData['hashtag']);
+ $request->addPost('hashtag', $extraData['hashtag']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'feed_contextual_location': // "Location" search result.
+ if (isset($extraData['location_id'])) {
+ // The location ID of this media.
+ $request->addPost('location_id', $extraData['location_id']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'feed_timeline': // "Timeline" tab (the global Home-feed with all
+ // kinds of mixed news).
+ case 'newsfeed': // "Followings Activity" feed tab. Used when
+ // liking/unliking a post that we clicked on from a
+ // single-activity "xyz liked abc's post" entry.
+ case 'feed_contextual_newsfeed_multi_media_liked': // "Followings
+ // Activity" feed
+ // tab. Used when
+ // liking/unliking a
+ // post that we
+ // clicked on from a
+ // multi-activity
+ // "xyz liked 5
+ // posts" entry.
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid module name. %s does not correspond to any of the valid module names.', $module));
+ }
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Metadata/Internal.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Metadata/Internal.php
new file mode 100755
index 0000000..d56b9bc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Metadata/Internal.php
@@ -0,0 +1,291 @@
+_uploadId = $uploadId;
+ } else {
+ $this->_uploadId = Utils::generateUploadId();
+ }
+ $this->_bestieMedia = false;
+ }
+
+ /**
+ * Set story view mode.
+ *
+ * @param string $viewMode View mode. Use STORY_VIEW_MODE_ONCE and STORY_VIEW_MODE_REPLAYABLE constants as values.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ public function setStoryViewMode(
+ $viewMode)
+ {
+ if ($viewMode != Constants::STORY_VIEW_MODE_ONCE
+ && $viewMode != Constants::STORY_VIEW_MODE_REPLAYABLE
+ ) {
+ throw new \InvalidArgumentException('Unknown view mode: '.$viewMode);
+ }
+
+ $this->_storyViewMode = $viewMode;
+
+ return $this->_storyViewMode;
+ }
+
+ /**
+ * Get story view mode.
+ *
+ * @return string
+ */
+ public function getStoryViewMode()
+ {
+ return $this->_storyViewMode;
+ }
+
+ /**
+ * @return PhotoDetails
+ */
+ public function getPhotoDetails()
+ {
+ return $this->_photoDetails;
+ }
+
+ /**
+ * @return VideoDetails
+ */
+ public function getVideoDetails()
+ {
+ return $this->_videoDetails;
+ }
+
+ /**
+ * Set video details from the given filename.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $videoFilename
+ *
+ * @throws \InvalidArgumentException If the video file is missing or invalid, or Instagram won't allow this video.
+ * @throws \RuntimeException In case of various processing errors.
+ *
+ * @return VideoDetails
+ */
+ public function setVideoDetails(
+ $targetFeed,
+ $videoFilename)
+ {
+ // Figure out the video file details.
+ // NOTE: We do this first, since it validates whether the video file is
+ // valid and lets us avoid wasting time uploading totally invalid files!
+ $this->_videoDetails = new VideoDetails($videoFilename);
+
+ // Validate the video details and throw if Instagram won't allow it.
+ $this->_videoDetails->validate(ConstraintsFactory::createFor($targetFeed));
+
+ return $this->_videoDetails;
+ }
+
+ /**
+ * Set photo details from the given filename.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $photoFilename
+ *
+ * @throws \InvalidArgumentException If the photo file is missing or invalid, or Instagram won't allow this photo.
+ * @throws \RuntimeException In case of various processing errors.
+ *
+ * @return PhotoDetails
+ */
+ public function setPhotoDetails(
+ $targetFeed,
+ $photoFilename)
+ {
+ // Figure out the photo file details.
+ // NOTE: We do this first, since it validates whether the photo file is
+ // valid and lets us avoid wasting time uploading totally invalid files!
+ $this->_photoDetails = new PhotoDetails($photoFilename);
+
+ // Validate the photo details and throw if Instagram won't allow it.
+ $this->_photoDetails->validate(ConstraintsFactory::createFor($targetFeed));
+
+ return $this->_photoDetails;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUploadId()
+ {
+ return $this->_uploadId;
+ }
+
+ /**
+ * Set upload URLs from a UploadJobVideoResponse response.
+ *
+ * @param UploadJobVideoResponse $response
+ *
+ * @return VideoUploadUrl[]
+ */
+ public function setVideoUploadUrls(
+ UploadJobVideoResponse $response)
+ {
+ $this->_videoUploadUrls = [];
+ if ($response->getVideoUploadUrls() !== null) {
+ $this->_videoUploadUrls = $response->getVideoUploadUrls();
+ }
+
+ return $this->_videoUploadUrls;
+ }
+
+ /**
+ * @return VideoUploadUrl[]
+ */
+ public function getVideoUploadUrls()
+ {
+ return $this->_videoUploadUrls;
+ }
+
+ /**
+ * @return UploadVideoResponse
+ */
+ public function getVideoUploadResponse()
+ {
+ return $this->_videoUploadResponse;
+ }
+
+ /**
+ * @param UploadVideoResponse $videoUploadResponse
+ */
+ public function setVideoUploadResponse(
+ UploadVideoResponse $videoUploadResponse)
+ {
+ $this->_videoUploadResponse = $videoUploadResponse;
+ }
+
+ /**
+ * @return UploadPhotoResponse
+ */
+ public function getPhotoUploadResponse()
+ {
+ return $this->_photoUploadResponse;
+ }
+
+ /**
+ * @param UploadPhotoResponse $photoUploadResponse
+ */
+ public function setPhotoUploadResponse(
+ UploadPhotoResponse $photoUploadResponse)
+ {
+ $this->_photoUploadResponse = $photoUploadResponse;
+ }
+
+ /**
+ * Add Direct recipients to metadata.
+ *
+ * @param array $recipients
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function setDirectRecipients(
+ array $recipients)
+ {
+ if (isset($recipients['users'])) {
+ $this->_directUsers = $recipients['users'];
+ $this->_directThreads = '[]';
+ } elseif (isset($recipients['thread'])) {
+ $this->_directUsers = '[]';
+ $this->_directThreads = $recipients['thread'];
+ } else {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDirectThreads()
+ {
+ return $this->_directThreads;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDirectUsers()
+ {
+ return $this->_directUsers;
+ }
+
+ /**
+ * Set bestie media state.
+ *
+ * @param bool $bestieMedia
+ */
+ public function setBestieMedia(
+ $bestieMedia)
+ {
+ $this->_bestieMedia = $bestieMedia;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isBestieMedia()
+ {
+ return $this->_bestieMedia;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/People.php b/instafeed/vendor/mgp25/instagram-php/src/Request/People.php
new file mode 100755
index 0000000..fc1aa24
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/People.php
@@ -0,0 +1,1265 @@
+ig->request("users/{$userId}/info/");
+ if ($module !== null) {
+ $request->addParam('from_module', $module);
+ }
+
+ return $request->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Get details about a specific user via their username.
+ *
+ * NOTE: The real app only uses this endpoint for profiles opened via "@mentions".
+ *
+ * @param string $username Username as string (NOT as a numerical ID).
+ * @param string $module From which app module (page) you have opened the profile.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see People::getInfoById() For the list of supported modules.
+ */
+ public function getInfoByName(
+ $username,
+ $module = 'feed_timeline')
+ {
+ return $this->ig->request("users/{$username}/usernameinfo/")
+ ->addParam('from_module', $module)
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Get the numerical UserPK ID for a specific user via their username.
+ *
+ * This is just a convenient helper function. You may prefer to use
+ * People::getInfoByName() instead, which lets you see more details.
+ *
+ * @param string $username Username as string (NOT as a numerical ID).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return string Their numerical UserPK ID.
+ *
+ * @see People::getInfoByName()
+ */
+ public function getUserIdForName(
+ $username)
+ {
+ return $this->getInfoByName($username)->getUser()->getPk();
+ }
+
+ /**
+ * Get user details about your own account.
+ *
+ * Also try Account::getCurrentUser() instead, for account details.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see Account::getCurrentUser()
+ */
+ public function getSelfInfo()
+ {
+ return $this->getInfoById($this->ig->account_id);
+ }
+
+ /**
+ * Get other people's recent activities related to you and your posts.
+ *
+ * This feed has information about when people interact with you, such as
+ * liking your posts, commenting on your posts, tagging you in photos or in
+ * comments, people who started following you, etc.
+ *
+ * @param bool $prefetch Indicates if request is called due to prefetch.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ActivityNewsResponse
+ */
+ public function getRecentActivityInbox(
+ $prefetch = false)
+ {
+ $request = $this->ig->request('news/inbox/');
+ if ($prefetch) {
+ $request->addHeader('X-IG-Prefetch-Request', 'foreground');
+ }
+
+ return $request->getResponse(new Response\ActivityNewsResponse());
+ }
+
+ /**
+ * Get news feed with recent activities by accounts you follow.
+ *
+ * This feed has information about the people you follow, such as what posts
+ * they've liked or that they've started following other people.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowingRecentActivityResponse
+ */
+ public function getFollowingRecentActivity(
+ $maxId = null)
+ {
+ $activity = $this->ig->request('news/');
+ if ($maxId !== null) {
+ $activity->addParam('max_id', $maxId);
+ }
+
+ return $activity->getResponse(new Response\FollowingRecentActivityResponse());
+ }
+
+ /**
+ * Retrieve bootstrap user data (autocompletion user list).
+ *
+ * WARNING: This is a special, very heavily throttled API endpoint.
+ * Instagram REQUIRES that you wait several minutes between calls to it.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BootstrapUsersResponse|null Will be NULL if throttled by Instagram.
+ */
+ public function getBootstrapUsers()
+ {
+ $surfaces = [
+ 'autocomplete_user_list',
+ 'coefficient_besties_list_ranking',
+ 'coefficient_rank_recipient_user_suggestion',
+ 'coefficient_ios_section_test_bootstrap_ranking',
+ 'coefficient_direct_recipients_ranking_variant_2',
+ ];
+
+ try {
+ $request = $this->ig->request('scores/bootstrap/users/')
+ ->addParam('surfaces', json_encode($surfaces));
+
+ return $request->getResponse(new Response\BootstrapUsersResponse());
+ } catch (ThrottledException $e) {
+ // Throttling is so common that we'll simply return NULL in that case.
+ return null;
+ }
+ }
+
+ /**
+ * Show a user's friendship status with you.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipsShowResponse
+ */
+ public function getFriendship(
+ $userId)
+ {
+ return $this->ig->request("friendships/show/{$userId}/")->getResponse(new Response\FriendshipsShowResponse());
+ }
+
+ /**
+ * Show multiple users' friendship status with you.
+ *
+ * @param string|string[] $userList List of numerical UserPK IDs.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipsShowManyResponse
+ */
+ public function getFriendships(
+ $userList)
+ {
+ if (is_array($userList)) {
+ $userList = implode(',', $userList);
+ }
+
+ return $this->ig->request('friendships/show_many/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('user_ids', $userList)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\FriendshipsShowManyResponse());
+ }
+
+ /**
+ * Get list of pending friendship requests.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ */
+ public function getPendingFriendships()
+ {
+ $request = $this->ig->request('friendships/pending/');
+
+ return $request->getResponse(new Response\FollowerAndFollowingResponse());
+ }
+
+ /**
+ * Approve a friendship request.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function approveFriendship(
+ $userId)
+ {
+ return $this->ig->request("friendships/approve/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Reject a friendship request.
+ *
+ * Note that the user can simply send you a new request again, after your
+ * rejection. If they're harassing you, use People::block() instead.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function rejectFriendship(
+ $userId)
+ {
+ return $this->ig->request("friendships/ignore/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Remove one of your followers.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function removeFollower(
+ $userId)
+ {
+ return $this->ig->request("friendships/remove_follower/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Mark user over age in order to see sensitive content.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function markUserOverage(
+ $userId)
+ {
+ return $this->ig->request("friendships/mark_user_overage/{$userId}/feed/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get list of who a user is following.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFollowing(
+ $userId,
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidRankToken($rankToken);
+ $request = $this->ig->request("friendships/{$userId}/following/")
+ ->addParam('includes_hashtags', true)
+ ->addParam('rank_token', $rankToken);
+ if ($searchQuery !== null) {
+ $request->addParam('query', $searchQuery);
+ }
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\FollowerAndFollowingResponse());
+ }
+
+ /**
+ * Get list of who a user is followed by.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFollowers(
+ $userId,
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidRankToken($rankToken);
+ $request = $this->ig->request("friendships/{$userId}/followers/")
+ ->addParam('rank_token', $rankToken);
+ if ($searchQuery !== null) {
+ $request->addParam('query', $searchQuery);
+ }
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\FollowerAndFollowingResponse());
+ }
+
+ /**
+ * Get list of who you are following.
+ *
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getSelfFollowing(
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ return $this->getFollowing($this->ig->account_id, $rankToken, $searchQuery, $maxId);
+ }
+
+ /**
+ * Get list of your own followers.
+ *
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getSelfFollowers(
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ return $this->getFollowers($this->ig->account_id, $rankToken, $searchQuery, $maxId);
+ }
+
+ /**
+ * Search for Instagram users.
+ *
+ * @param string $query The username or full name to search for.
+ * @param string[]|int[] $excludeList Array of numerical user IDs (ie "4021088339")
+ * to exclude from the response, allowing you to skip users
+ * from a previous call to get more results.
+ * @param string|null $rankToken A rank token from a first call response.
+ *
+ * @throws \InvalidArgumentException If invalid query or
+ * trying to exclude too
+ * many user IDs.
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SearchUserResponse
+ *
+ * @see SearchUserResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function search(
+ $query,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation.
+ if (!is_string($query) || $query === '') {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+
+ $request = $this->_paginateWithExclusion(
+ $this->ig->request('users/search/')
+ ->addParam('q', $query)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken
+ );
+
+ try {
+ /** @var Response\SearchUserResponse $result */
+ $result = $request->getResponse(new Response\SearchUserResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\SearchUserResponse([
+ 'has_more' => false,
+ 'num_results' => 0,
+ 'users' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get business account details.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\AccountDetailsResponse
+ */
+ public function getAccountDetails(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/account_details/")
+ ->getResponse(new Response\AccountDetailsResponse());
+ }
+
+ /**
+ * Get a business account's former username(s).
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FormerUsernamesResponse
+ */
+ public function getFormerUsernames(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/former_usernames/")
+ ->getResponse(new Response\FormerUsernamesResponse());
+ }
+
+ /**
+ * Get a business account's shared follower base with similar accounts.
+ *
+ * @param string $userId Numerical UserPk ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SharedFollowersResponse
+ */
+ public function getSharedFollowers(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/shared_follower_accounts/")
+ ->getResponse(new Response\SharedFollowersResponse());
+ }
+
+ /**
+ * Get a business account's active ads on feed.
+ *
+ * @param string $targetUserId Numerical UserPk ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ActiveFeedAdsResponse
+ */
+ public function getActiveFeedAds(
+ $targetUserId,
+ $maxId = null)
+ {
+ return $this->_getActiveAds($targetUserId, '35', $maxId);
+ }
+
+ /**
+ * Get a business account's active ads on stories.
+ *
+ * @param string $targetUserId Numerical UserPk ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ActiveReelAdsResponse
+ */
+ public function getActiveStoryAds(
+ $targetUserId,
+ $maxId = null)
+ {
+ return $this->_getActiveAds($targetUserId, '49', $maxId);
+ }
+
+ /**
+ * Helper function for getting active ads for business accounts.
+ *
+ * @param string $targetUserId Numerical UserPk ID.
+ * @param string $pageType Content-type id(?) of the ad. 35 is feed ads and 49 is story ads.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response
+ */
+ protected function _getActiveAds(
+ $targetUserId,
+ $pageType,
+ $maxId = null)
+ {
+ $request = $this->ig->request('ads/view_ads/')
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('target_user_id', $targetUserId)
+ ->addPost('page_type', $pageType);
+ if ($maxId !== null) {
+ $request->addPost('next_max_id', $maxId);
+ }
+ $request->addPost('ig_user_id', $this->ig->account_id);
+
+ switch ($pageType) {
+ case '35':
+ return $request->getResponse(new Response\ActiveFeedAdsResponse());
+ break;
+ case '49':
+ return $request->getResponse(new Response\ActiveReelAdsResponse());
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid page type.');
+ }
+ }
+
+ /**
+ * Search for users by linking your address book to Instagram.
+ *
+ * WARNING: You must unlink your current address book before you can link
+ * another one to search again, otherwise you will just keep getting the
+ * same response about your currently linked address book every time!
+ *
+ * @param array $contacts
+ * @param string $module
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LinkAddressBookResponse
+ *
+ * @see People::unlinkAddressBook()
+ */
+ public function linkAddressBook(
+ array $contacts,
+ $module = 'find_friends_contacts')
+ {
+ return $this->ig->request('address_book/link/')
+ ->setIsBodyCompressed(true)
+ ->setSignedPost(false)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('module', $module)
+ ->addPost('contacts', json_encode($contacts))
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\LinkAddressBookResponse());
+ }
+
+ /**
+ * Unlink your address book from Instagram.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UnlinkAddressBookResponse
+ */
+ public function unlinkAddressBook()
+ {
+ return $this->ig->request('address_book/unlink/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UnlinkAddressBookResponse());
+ }
+
+ /**
+ * Discover new people via Facebook's algorithm.
+ *
+ * This matches you with other people using multiple algorithms such as
+ * "friends of friends", "location", "people using similar hashtags", etc.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DiscoverPeopleResponse
+ */
+ public function discoverPeople(
+ $maxId = null)
+ {
+ $request = $this->ig->request('discover/ayml/')
+ ->setSignedPost(false)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('module', 'discover_people')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('paginate', true);
+
+ if ($maxId !== null) {
+ $request->addPost('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\DiscoverPeopleResponse());
+ }
+
+ /**
+ * Get suggested users related to a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedUsersResponse
+ */
+ public function getSuggestedUsers(
+ $userId)
+ {
+ return $this->ig->request('discover/chaining/')
+ ->addParam('target_id', $userId)
+ ->getResponse(new Response\SuggestedUsersResponse());
+ }
+
+ /**
+ * Get suggested users via account badge.
+ *
+ * This is the endpoint for when you press the "user icon with the plus
+ * sign" on your own profile in the Instagram app. Its amount of suggestions
+ * matches the number on the badge, and it usually only has a handful (1-4).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedUsersBadgeResponse
+ */
+ public function getSuggestedUsersBadge()
+ {
+ return $this->ig->request('discover/profile_su_badge/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('module', 'discover_people')
+ ->getResponse(new Response\SuggestedUsersBadgeResponse());
+ }
+
+ /**
+ * Hide suggested user, so that they won't be suggested again.
+ *
+ * You must provide the correct algorithm for the user you want to hide,
+ * which can be seen in their "algorithm" value in People::discoverPeople().
+ *
+ * Here is a probably-outdated list of algorithms and their meanings:
+ *
+ * - realtime_chaining_algorithm = ?
+ * - realtime_chaining_ig_coeff_algorithm = ?
+ * - tfidf_city_algorithm = Popular people near you.
+ * - hashtag_interest_algorithm = Popular people on similar hashtags as you.
+ * - second_order_followers_algorithm = Popular.
+ * - super_users_algorithm = Popular.
+ * - followers_algorithm = Follows you.
+ * - ig_friends_of_friends_from_tao_laser_algorithm = ?
+ * - page_rank_algorithm = ?
+ *
+ * TODO: Do more research about this function and document it properly.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $algorithm Which algorithm to hide the suggestion from;
+ * must match that user's "algorithm" value in
+ * functions like People::discoverPeople().
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedUsersResponse
+ */
+ public function hideSuggestedUser(
+ $userId,
+ $algorithm)
+ {
+ return $this->ig->request('discover/aysf_dismiss/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addParam('target_id', $userId)
+ ->addParam('algorithm', $algorithm)
+ ->getResponse(new Response\SuggestedUsersResponse());
+ }
+
+ /**
+ * Follow a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function follow(
+ $userId,
+ $mediaId = null)
+ {
+ $request = $this->ig->request("friendships/create/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('device_id', $this->ig->device_id);
+
+ if ($mediaId !== null) {
+ $request->addPost('media_id_attribution', $mediaId);
+ }
+
+ return $request->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unfollow a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function unfollow(
+ $userId,
+ $mediaId = null)
+ {
+ $request = $this->ig->request("friendships/destroy/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none');
+
+ if ($mediaId !== null) {
+ $request->addPost('media_id_attribution', $mediaId);
+ }
+
+ return $request->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Enable high priority for a user you are following.
+ *
+ * When you mark someone as favorite, you will receive app push
+ * notifications when that user uploads media, and their shared
+ * media will get higher visibility. For instance, their stories
+ * will be placed at the front of your reels-tray, and their
+ * timeline posts will stay visible for longer on your homescreen.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function favorite(
+ $userId)
+ {
+ return $this->ig->request("friendships/favorite/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Disable high priority for a user you are following.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unfavorite(
+ $userId)
+ {
+ return $this->ig->request("friendships/unfavorite/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Turn on story notifications.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function favoriteForStories(
+ $userId)
+ {
+ return $this->ig->request("friendships/favorite_for_stories/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Turn off story notifications.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unfavoriteForStories(
+ $userId)
+ {
+ return $this->ig->request("friendships/unfavorite_for_stories/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Report a user as spam.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $sourceName (optional) Source app-module of the report.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function report(
+ $userId,
+ $sourceName = 'profile')
+ {
+ return $this->ig->request("users/{$userId}/flag_user/")
+ ->addPost('reason_id', 1)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('source_name', $sourceName)
+ ->addPost('is_spam', true)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Block a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function block(
+ $userId)
+ {
+ return $this->ig->request("friendships/block/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Mute stories, posts or both from a user.
+ *
+ * It prevents user media from showing up in the timeline and/or story feed.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $option Selection of what type of media are going to be muted.
+ * Available options: 'story', 'post' or 'all'.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function muteUserMedia(
+ $userId,
+ $option)
+ {
+ return $this->_muteOrUnmuteUserMedia($userId, $option, 'friendships/mute_posts_or_story_from_follow/');
+ }
+
+ /**
+ * Unmute stories, posts or both from a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $option Selection of what type of media are going to be muted.
+ * Available options: 'story', 'post' or 'all'.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function unmuteUserMedia(
+ $userId,
+ $option)
+ {
+ return $this->_muteOrUnmuteUserMedia($userId, $option, 'friendships/unmute_posts_or_story_from_follow/');
+ }
+
+ /**
+ * Helper function to mute user media.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $option Selection of what type of media are going to be muted.
+ * Available options: 'story', 'post' or 'all'.
+ * @param string $endpoint API endpoint for muting/unmuting user media.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::muteUserMedia()
+ * @see People::unmuteUserMedia()
+ */
+ protected function _muteOrUnmuteUserMedia(
+ $userId,
+ $option,
+ $endpoint)
+ {
+ $request = $this->ig->request($endpoint)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ switch ($option) {
+ case 'story':
+ $request->addPost('target_reel_author_id', $userId);
+ break;
+ case 'post':
+ $request->addPost('target_posts_author_id', $userId);
+ break;
+ case 'all':
+ $request->addPost('target_reel_author_id', $userId);
+ $request->addPost('target_posts_author_id', $userId);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid muting option.', $option));
+ }
+
+ return $request->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unblock a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function unblock(
+ $userId)
+ {
+ return $this->ig->request("friendships/unblock/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get a list of all blocked users.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BlockedListResponse
+ */
+ public function getBlockedList(
+ $maxId = null)
+ {
+ $request = $this->ig->request('users/blocked_list/');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\BlockedListResponse());
+ }
+
+ /**
+ * Block a user's ability to see your stories.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::muteFriendStory()
+ */
+ public function blockMyStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/block_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('source', 'profile')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unblock a user so that they can see your stories again.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::unmuteFriendStory()
+ */
+ public function unblockMyStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/unblock_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('source', 'profile')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get the list of users who are blocked from seeing your stories.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BlockedReelsResponse
+ */
+ public function getBlockedStoryList()
+ {
+ return $this->ig->request('friendships/blocked_reels/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\BlockedReelsResponse());
+ }
+
+ /**
+ * Mute a friend's stories, so that you no longer see their stories.
+ *
+ * This hides them from your reels tray (the "latest stories" bar on the
+ * homescreen of the app), but it does not block them from seeing *your*
+ * stories.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::blockMyStory()
+ */
+ public function muteFriendStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/mute_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unmute a friend's stories, so that you see their stories again.
+ *
+ * This does not unblock their ability to see *your* stories.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::unblockMyStory()
+ */
+ public function unmuteFriendStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/unmute_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get the list of users on your close friends list.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CloseFriendsResponse
+ */
+ public function getCloseFriends()
+ {
+ return $this->ig->request('friendships/besties/')
+ ->getResponse(new Response\CloseFriendsResponse());
+ }
+
+ /**
+ * Get the list of suggested users for your close friends list.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CloseFriendsResponse
+ */
+ public function getSuggestedCloseFriends()
+ {
+ return $this->ig->request('friendships/bestie_suggestions/')
+ ->getResponse(new Response\CloseFriendsResponse());
+ }
+
+ /**
+ * Add or Remove users from your close friends list.
+ *
+ * Note: You probably shouldn't touch $module and $source as there is only one way to modify your close friends.
+ *
+ * @param array $add Users to add to your close friends list.
+ * @param array $remove Users to remove from your close friends list.
+ * @param string $module (optional) From which app module (page) you have change your close friends list.
+ * @param string $source (optional) Source page of app-module of where you changed your close friends list.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function setCloseFriends(
+ array $add,
+ array $remove,
+ $module = 'favorites_home_list',
+ $source = 'audience_manager')
+ {
+ return $this->ig->request('friendships/set_besties/')
+ ->setSignedPost(true)
+ ->addPost('module', $module)
+ ->addPost('source', $source)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('remove', $remove)
+ ->addPost('add', $add)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Gets a list of ranked users to display in Android's share UI.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SharePrefillResponse
+ */
+ public function getSharePrefill()
+ {
+ return $this->ig->request('banyan/banyan/')
+ ->addParam('views', '["story_share_sheet","threads_people_picker","reshare_share_sheet"]')
+ ->getResponse(new Response\SharePrefillResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Push.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Push.php
new file mode 100755
index 0000000..16b5bcd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Push.php
@@ -0,0 +1,78 @@
+ig->request('push/register/')
+ ->setSignedPost(false)
+ ->addPost('device_type', $pushChannel === 'mqtt' ? 'android_mqtt' : 'android_fcm')
+ ->addPost('is_main_push_channel', $pushChannel === 'mqtt')
+ ->addPost('device_token', $token)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('guid', $this->ig->uuid)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('device_sub_type', '2')
+ ->addPost('users', $this->ig->account_id);
+
+ return $request->getResponse(new Response\PushRegisterResponse());
+ }
+
+ /**
+ * Get push preferences.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PushPreferencesResponse
+ */
+ public function getPreferences()
+ {
+ return $this->ig->request('push/all_preferences/')
+ ->getResponse(new Response\PushPreferencesResponse());
+ }
+
+ /**
+ * Set push preferences.
+ *
+ * @param array $preferences Described in "extradocs/Push_setPreferences.txt".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PushPreferencesResponse
+ */
+ public function setPreferences(
+ array $preferences)
+ {
+ $request = $this->ig->request('push/preferences/');
+ foreach ($preferences as $key => $value) {
+ $request->addPost($key, $value);
+ }
+
+ return $request->getResponse(new Response\PushPreferencesResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/RequestCollection.php b/instafeed/vendor/mgp25/instagram-php/src/Request/RequestCollection.php
new file mode 100755
index 0000000..896f09f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/RequestCollection.php
@@ -0,0 +1,104 @@
+ig = $parent;
+ }
+
+ /**
+ * Paginate the request by given exclusion list.
+ *
+ * @param Request $request The request to paginate.
+ * @param array $excludeList Array of numerical entity IDs (ie "4021088339")
+ * to exclude from the response, allowing you to skip entities
+ * from a previous call to get more results.
+ * @param string|null $rankToken The rank token from the previous page's response.
+ * @param int $limit Limit the number of results per page.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Request
+ */
+ protected function _paginateWithExclusion(
+ Request $request,
+ array $excludeList = [],
+ $rankToken = null,
+ $limit = 30)
+ {
+ if (!count($excludeList)) {
+ return $request->addParam('count', (string) $limit);
+ }
+
+ if ($rankToken === null) {
+ throw new \InvalidArgumentException('You must supply the rank token for the pagination.');
+ }
+ Utils::throwIfInvalidRankToken($rankToken);
+
+ return $request
+ ->addParam('count', (string) $limit)
+ ->addParam('exclude_list', '['.implode(', ', $excludeList).']')
+ ->addParam('rank_token', $rankToken);
+ }
+
+ /**
+ * Paginate the request by given multi-exclusion list.
+ *
+ * @param Request $request The request to paginate.
+ * @param array $excludeList Array of grouped numerical entity IDs (ie "users" => ["4021088339"])
+ * to exclude from the response, allowing you to skip entities
+ * from a previous call to get more results.
+ * @param string|null $rankToken The rank token from the previous page's response.
+ * @param int $limit Limit the number of results per page.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Request
+ */
+ protected function _paginateWithMultiExclusion(
+ Request $request,
+ array $excludeList = [],
+ $rankToken = null,
+ $limit = 30)
+ {
+ if (!count($excludeList)) {
+ return $request->addParam('count', (string) $limit);
+ }
+
+ if ($rankToken === null) {
+ throw new \InvalidArgumentException('You must supply the rank token for the pagination.');
+ }
+ Utils::throwIfInvalidRankToken($rankToken);
+
+ $exclude = [];
+ $totalCount = 0;
+ foreach ($excludeList as $group => $ids) {
+ $totalCount += count($ids);
+ $exclude[] = "\"{$group}\":[".implode(', ', $ids).']';
+ }
+
+ return $request
+ ->addParam('count', (string) $limit)
+ ->addParam('exclude_list', '{'.implode(',', $exclude).'}')
+ ->addParam('rank_token', $rankToken);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Shopping.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Shopping.php
new file mode 100755
index 0000000..3bd97be
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Shopping.php
@@ -0,0 +1,123 @@
+ig->request("commerce/products/{$productId}/details/")
+ ->addParam('source_media_id', $mediaId)
+ ->addParam('merchant_id', $merchantId)
+ ->addParam('device_width', $deviceWidth)
+ ->addParam('hero_carousel_enabled', true)
+ ->getResponse(new Response\OnTagProductResponse());
+ }
+
+ /**
+ * Get catalogs.
+ *
+ * @param string $locale The device user's locale, such as "en_US.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GraphqlResponse
+ */
+ public function getCatalogs(
+ $locale = 'en_US')
+ {
+ return $this->ig->request('wwwgraphql/ig/query/')
+ ->addParam('locale', $locale)
+ ->addUnsignedPost('access_token', 'undefined')
+ ->addUnsignedPost('fb_api_caller_class', 'RelayModern')
+ ->addUnsignedPost('variables', ['sources' => null])
+ ->addUnsignedPost('doc_id', '1742970149122229')
+ ->getResponse(new Response\GraphqlResponse());
+ }
+
+ /**
+ * Get catalog items.
+ *
+ * @param string $catalogId The catalog's ID.
+ * @param string $query Finds products containing this string.
+ * @param int $offset Offset, used for pagination. Values must be multiples of 20.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GraphqlResponse
+ */
+ public function getCatalogItems(
+ $catalogId,
+ $query = '',
+ $offset = null)
+ {
+ if ($offset !== null) {
+ if ($offset % 20 !== 0) {
+ throw new \InvalidArgumentException('Offset must be multiple of 20.');
+ }
+ $offset = [
+ 'offset' => $offset,
+ 'tier' => 'products.elasticsearch.thrift.atn',
+ ];
+ }
+
+ $queryParams = [
+ $query,
+ $catalogId,
+ '96',
+ '20',
+ json_encode($offset),
+ ];
+
+ return $this->ig->request('wwwgraphql/ig/query/')
+ ->addUnsignedPost('doc_id', '1747750168640998')
+ ->addUnsignedPost('locale', Constants::ACCEPT_LANGUAGE)
+ ->addUnsignedPost('vc_policy', 'default')
+ ->addUnsignedPost('strip_nulls', true)
+ ->addUnsignedPost('strip_defaults', true)
+ ->addUnsignedPost('query_params', json_encode($queryParams, JSON_FORCE_OBJECT))
+ ->getResponse(new Response\GraphqlResponse());
+ }
+
+ /**
+ * Sets on board catalog.
+ *
+ * @param string $catalogId The catalog's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\OnBoardCatalogResponse
+ */
+ public function setOnBoardCatalog(
+ $catalogId)
+ {
+ return $this->ig->request('commerce/onboard/')
+ ->addPost('current_catalog_id', $catalogId)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\OnBoardCatalogResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Story.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Story.php
new file mode 100755
index 0000000..d8f79c9
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Story.php
@@ -0,0 +1,693 @@
+ig->internal->uploadSinglePhoto(Constants::FEED_STORY, $photoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads a photo to your Instagram close friends story.
+ *
+ * @param string $photoFilename The photo filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSinglePhoto() for available metadata fields.
+ * @see https://help.instagram.com/2183694401643300
+ */
+ public function uploadCloseFriendsPhoto(
+ $photoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata(Utils::generateUploadId(true));
+ $internalMetadata->setBestieMedia(true);
+
+ return $this->ig->internal->uploadSinglePhoto(Constants::FEED_STORY, $photoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Uploads a video to your Instagram story.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_STORY, $videoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads a video to your Instagram close friends story.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ * @see https://help.instagram.com/2183694401643300
+ */
+ public function uploadCloseFriendsVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setBestieMedia(true);
+
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_STORY, $videoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Get the global story feed which contains everyone you follow.
+ *
+ * Note that users will eventually drop out of this list even though they
+ * still have stories. So it's always safer to call getUserStoryFeed() if
+ * a specific user's story feed matters to you.
+ *
+ * @param string $reason (optional) Reason for the request.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelsTrayFeedResponse
+ *
+ * @see Story::getUserStoryFeed()
+ */
+ public function getReelsTrayFeed(
+ $reason = 'pull_to_refresh')
+ {
+ return $this->ig->request('feed/reels_tray/')
+ ->setSignedPost(false)
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('reason', $reason)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\ReelsTrayFeedResponse());
+ }
+
+ /**
+ * Get a specific user's story reel feed.
+ *
+ * This function gets the user's story Reel object directly, which always
+ * exists and contains information about the user and their last story even
+ * if that user doesn't have any active story anymore.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserReelMediaFeedResponse
+ *
+ * @see Story::getUserStoryFeed()
+ */
+ public function getUserReelMediaFeed(
+ $userId)
+ {
+ return $this->ig->request("feed/user/{$userId}/reel_media/")
+ ->getResponse(new Response\UserReelMediaFeedResponse());
+ }
+
+ /**
+ * Get a specific user's story feed with broadcast details.
+ *
+ * This function gets the story in a roundabout way, with some extra details
+ * about the "broadcast". But if there is no story available, this endpoint
+ * gives you an empty response.
+ *
+ * NOTE: At least AT THIS MOMENT, this endpoint and the reels-tray endpoint
+ * are the only ones that will give you people's "post_live" fields (their
+ * saved Instagram Live Replays). The other "get user stories" funcs don't!
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserStoryFeedResponse
+ *
+ * @see Story::getUserReelMediaFeed()
+ */
+ public function getUserStoryFeed(
+ $userId)
+ {
+ return $this->ig->request("feed/user/{$userId}/story/")
+ ->addParam('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->getResponse(new Response\UserStoryFeedResponse());
+ }
+
+ /**
+ * Get multiple users' story feeds (or specific highlight-details) at once.
+ *
+ * NOTE: Normally, you would only use this endpoint for stories (by passing
+ * UserPK IDs as the parameter). But if you're looking at people's highlight
+ * feeds (via `Highlight::getUserFeed()`), you may also sometimes discover
+ * highlight entries that don't have any `items` array. In that case, you
+ * are supposed to get the items for those highlights via this endpoint!
+ * Simply pass their `id` values as the argument to this API to get details.
+ *
+ * @param string|string[] $feedList List of numerical UserPK IDs, OR highlight IDs (such as `highlight:123882132324123`).
+ * @param string $source (optional) Source app-module where the request was made.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelsMediaResponse
+ *
+ * @see Highlight::getUserFeed() More info about when to use this API for highlight-details.
+ */
+ public function getReelsMediaFeed(
+ $feedList,
+ $source = 'feed_timeline')
+ {
+ if (!is_array($feedList)) {
+ $feedList = [$feedList];
+ }
+
+ foreach ($feedList as &$value) {
+ $value = (string) $value;
+ }
+ unset($value); // Clear reference.
+
+ return $this->ig->request('feed/reels_media/')
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_ids', $feedList) // Must be string[] array.
+ ->addPost('source', $source)
+ ->getResponse(new Response\ReelsMediaResponse());
+ }
+
+ /**
+ * Get your archived story media feed.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ArchivedStoriesFeedResponse
+ */
+ public function getArchivedStoriesFeed()
+ {
+ return $this->ig->request('archive/reel/day_shells/')
+ ->addParam('include_suggested_highlights', false)
+ ->addParam('is_in_archive_home', true)
+ ->addParam('include_cover', 0)
+ ->addParam('timezone_offset', date('Z'))
+ ->getResponse(new Response\ArchivedStoriesFeedResponse());
+ }
+
+ /**
+ * Get the list of users who have seen one of your story items.
+ *
+ * Note that this only works for your own story items. Instagram doesn't
+ * allow you to see the viewer list for other people's stories!
+ *
+ * @param string $storyPk The story media item's PK in Instagram's internal format (ie "3482384834").
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelMediaViewerResponse
+ */
+ public function getStoryItemViewers(
+ $storyPk,
+ $maxId = null)
+ {
+ $request = $this->ig->request("media/{$storyPk}/list_reel_media_viewer/")
+ ->addParam('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES));
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\ReelMediaViewerResponse());
+ }
+
+ /**
+ * Vote on a story poll.
+ *
+ * Note that once you vote on a story poll, you cannot change your vote.
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $pollId The poll ID in Instagram's internal format (ie "17956159684032257").
+ * @param int $votingOption Value that represents the voting option of the voter. 0 for the first option, 1 for the second option.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelMediaViewerResponse
+ */
+ public function votePollStory(
+ $storyId,
+ $pollId,
+ $votingOption)
+ {
+ if (($votingOption !== 0) && ($votingOption !== 1)) {
+ throw new \InvalidArgumentException('You must provide a valid value for voting option.');
+ }
+
+ return $this->ig->request("media/{$storyId}/{$pollId}/story_poll_vote/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('vote', $votingOption)
+ ->getResponse(new Response\ReelMediaViewerResponse());
+ }
+
+ /**
+ * Vote on a story slider.
+ *
+ * Note that once you vote on a story poll, you cannot change your vote.
+ *
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $sliderId The slider ID in Instagram's internal format (ie "17956159684032257").
+ * @param float $votingOption Value that represents the voting option of the voter. Should be a float from 0 to 1 (ie "0.25").
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelMediaViewerResponse
+ */
+ public function voteSliderStory(
+ $storyId,
+ $sliderId,
+ $votingOption)
+ {
+ if ($votingOption < 0 || $votingOption > 1) {
+ throw new \InvalidArgumentException('You must provide a valid value from 0 to 1 for voting option.');
+ }
+
+ return $this->ig->request("media/{$storyId}/{$sliderId}/story_slider_vote/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('vote', $votingOption)
+ ->getResponse(new Response\ReelMediaViewerResponse());
+ }
+
+ /**
+ * Get the list of users who have voted an option in a story poll.
+ *
+ * Note that this only works for your own story polls. Instagram doesn't
+ * allow you to see the results from other people's polls!
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $pollId The poll ID in Instagram's internal format (ie "17956159684032257").
+ * @param int $votingOption Value that represents the voting option of the voter. 0 for the first option, 1 for the second option.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StoryPollVotersResponse
+ */
+ public function getStoryPollVoters(
+ $storyId,
+ $pollId,
+ $votingOption,
+ $maxId = null)
+ {
+ if (($votingOption !== 0) && ($votingOption !== 1)) {
+ throw new \InvalidArgumentException('You must provide a valid value for voting option.');
+ }
+
+ $request = $this->ig->request("media/{$storyId}/{$pollId}/story_poll_voters/")
+ ->addParam('vote', $votingOption);
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\StoryPollVotersResponse());
+ }
+
+ /**
+ * Respond to a question sticker on a story.
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17956159684032257").
+ * @param string $responseText The text to respond to the question with. (Note: Android App limits this to 94 characters).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function answerStoryQuestion(
+ $storyId,
+ $questionId,
+ $responseText)
+ {
+ return $this->ig->request("media/{$storyId}/{$questionId}/story_question_response/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('response', $responseText)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('type', 'text')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get all responses of a story question.
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17956159684032257").
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StoryAnswersResponse
+ */
+ public function getStoryAnswers(
+ $storyId,
+ $questionId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("media/{$storyId}/{$questionId}/story_question_responses/");
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\StoryAnswersResponse());
+ }
+
+ /**
+ * Gets the created story countdowns of the current account.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StoryCountdownsResponse
+ */
+ public function getStoryCountdowns()
+ {
+ return $this->ig->request('media/story_countdowns/')
+ ->getResponse(new Response\StoryCountdownsResponse());
+ }
+
+ /**
+ * Follows a story countdown to subscribe to a notification when the countdown is finished.
+ *
+ * @param string $countdownId The countdown ID in Instagram's internal format (ie "17956159684032257").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function followStoryCountdown(
+ $countdownId)
+ {
+ return $this->ig->request("media/{$countdownId}/follow_story_countdown/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unfollows a story countdown to unsubscribe from a notification when the countdown is finished.
+ *
+ * @param string $countdownId The countdown ID in Instagram's internal format (ie "17956159684032257").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unfollowStoryCountdown(
+ $countdownId)
+ {
+ return $this->ig->request("media/{$countdownId}/unfollow_story_countdown/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get list of charities for use in the donation sticker on stories.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CharitiesListResponse
+ */
+ public function getCharities(
+ $maxId = null)
+ {
+ $request = $this->ig->request('fundraiser/story_charities_nullstate/');
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\CharitiesListResponse());
+ }
+
+ /**
+ * Searches a list of charities for use in the donation sticker on stories.
+ *
+ * @param string $query Search query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CharitiesListResponse
+ */
+ public function searchCharities(
+ $query,
+ $maxId = null)
+ {
+ $request = $this->ig->request('fundraiser/story_charities_search/')
+ ->addParam('query', $query);
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\CharitiesListResponse());
+ }
+
+ /**
+ * Creates the array for a donation sticker.
+ *
+ * @param \InstagramAPI\Response\Model\User $charityUser The User object of the charity's Instagram account.
+ * @param float $x
+ * @param float $y
+ * @param float $width
+ * @param float $height
+ * @param float $rotation
+ * @param string|null $title The title of the donation sticker.
+ * @param string $titleColor Hex color code for the title color.
+ * @param string $subtitleColor Hex color code for the subtitle color.
+ * @param string $buttonTextColor Hex color code for the button text color.
+ * @param string $startBackgroundColor
+ * @param string $endBackgroundColor
+ *
+ * @return array
+ *
+ * @see Story::getCharities()
+ * @see Story::searchCharities()
+ * @see Story::uploadPhoto()
+ * @see Story::uploadVideo()
+ */
+ public function createDonateSticker(
+ $charityUser,
+ $x = 0.5,
+ $y = 0.5,
+ $width = 0.6805556,
+ $height = 0.254738,
+ $rotation = 0.0,
+ $title = null,
+ $titleColor = '#000000',
+ $subtitleColor = '#999999ff',
+ $buttonTextColor = '#3897f0',
+ $startBackgroundColor = '#fafafa',
+ $endBackgroundColor = '#fafafa')
+ {
+ return [
+ [
+ 'x' => $x,
+ 'y' => $y,
+ 'z' => 0,
+ 'width' => $width,
+ 'height' => $height,
+ 'rotation' => $rotation,
+ 'title' => ($title !== null ? strtoupper($title) : ('HELP SUPPORT '.strtoupper($charityUser->getFullName()))),
+ 'ig_charity_id' => $charityUser->getPk(),
+ 'title_color' => $titleColor,
+ 'subtitle_color' => $subtitleColor,
+ 'button_text_color' => $buttonTextColor,
+ 'start_background_color' => $startBackgroundColor,
+ 'end_background_color' => $endBackgroundColor,
+ 'source_name' => 'sticker_tray',
+ 'user' => [
+ 'username' => $charityUser->getUsername(),
+ 'full_name' => $charityUser->getFullName(),
+ 'profile_pic_url' => $charityUser->getProfilePicUrl(),
+ 'profile_pic_id' => $charityUser->getProfilePicId(),
+ 'has_anonymous_profile_picture' => $charityUser->getHasAnonymousProfilePicture(),
+ 'id' => $charityUser->getPk(),
+ 'usertag_review_enabled' => false,
+ 'mutual_followers_count' => $charityUser->getMutualFollowersCount(),
+ 'show_besties_badge' => false,
+ 'is_private' => $charityUser->getIsPrivate(),
+ 'allowed_commenter_type' => 'any',
+ 'is_verified' => $charityUser->getIsVerified(),
+ 'is_new' => false,
+ 'feed_post_reshare_disabled' => false,
+ ],
+ 'is_sticker' => true,
+ ],
+ ];
+ }
+
+ /**
+ * Mark story media items as seen.
+ *
+ * The various story-related endpoints only give you lists of story media.
+ * They don't actually mark any stories as "seen", so the user doesn't know
+ * that you've seen their story. Actually marking the story as "seen" is
+ * done via this endpoint instead. The official app calls this endpoint
+ * periodically (with 1 or more items at a time) while watching a story.
+ *
+ * Tip: You can pass in the whole "getItems()" array from a user's story
+ * feed (retrieved via any of the other story endpoints), to easily mark
+ * all of that user's story media items as seen.
+ *
+ * WARNING: ONLY USE *THIS* ENDPOINT IF THE STORIES CAME FROM THE ENDPOINTS
+ * IN *THIS* REQUEST-COLLECTION FILE: From "getReelsTrayFeed()" or the
+ * user-specific story endpoints. Do NOT use this endpoint if the stories
+ * came from any OTHER request-collections, such as Location-based stories!
+ * Other request-collections have THEIR OWN special story-marking functions!
+ *
+ * @param Response\Model\Item[] $items Array of one or more story media Items.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Location::markStoryMediaSeen()
+ * @see Hashtag::markStoryMediaSeen()
+ */
+ public function markMediaSeen(
+ array $items)
+ {
+ // NOTE: NULL = Use each item's owner ID as the "source ID".
+ return $this->ig->internal->markStoryMediaSeen($items, null);
+ }
+
+ /**
+ * Get your story settings.
+ *
+ * This has information such as your story messaging mode (who can reply
+ * to your story), and the list of users you have blocked from seeing your
+ * stories.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelSettingsResponse
+ */
+ public function getReelSettings()
+ {
+ return $this->ig->request('users/reel_settings/')
+ ->getResponse(new Response\ReelSettingsResponse());
+ }
+
+ /**
+ * Set your story settings.
+ *
+ * @param string $messagePrefs Who can reply to your story. Valid values are "anyone" (meaning
+ * your followers), "following" (followers that you follow back),
+ * or "off" (meaning that nobody can reply to your story).
+ * @param bool|null $allowStoryReshare Allow story reshare.
+ * @param string|null $autoArchive Auto archive stories for viewing them later. It will appear in your
+ * archive once it has disappeared from your story feed. Valid values
+ * "on" and "off".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelSettingsResponse
+ */
+ public function setReelSettings(
+ $messagePrefs,
+ $allowStoryReshare = null,
+ $autoArchive = null)
+ {
+ if (!in_array($messagePrefs, ['anyone', 'following', 'off'])) {
+ throw new \InvalidArgumentException('You must provide a valid message preference value.');
+ }
+
+ $request = $this->ig->request('users/set_reel_settings/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('message_prefs', $messagePrefs);
+
+ if ($allowStoryReshare !== null) {
+ if (!is_bool($allowStoryReshare)) {
+ throw new \InvalidArgumentException('You must provide a valid value for allowing story reshare.');
+ }
+ $request->addPost('allow_story_reshare', $allowStoryReshare);
+ }
+
+ if ($autoArchive !== null) {
+ if (!in_array($autoArchive, ['on', 'off'])) {
+ throw new \InvalidArgumentException('You must provide a valid value for auto archive.');
+ }
+ $request->addPost('reel_auto_archive', $autoArchive);
+ }
+
+ return $request->getResponse(new Response\ReelSettingsResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/TV.php b/instafeed/vendor/mgp25/instagram-php/src/Request/TV.php
new file mode 100755
index 0000000..47bc968
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/TV.php
@@ -0,0 +1,171 @@
+ig->request('igtv/tv_guide/')
+ ->addHeader('X-Ads-Opt-Out', '0')
+ ->addHeader('X-Google-AD-ID', $this->ig->advertising_id)
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addParam('prefetch', 1)
+ ->addParam('phone_id', $this->ig->phone_id)
+ ->addParam('battery_level', '100')
+// ->addParam('banner_token', 'OgYA')
+ ->addParam('is_charging', '1')
+ ->addParam('will_sound_on', '1');
+
+ if (isset($options['is_charging'])) {
+ $request->addParam('is_charging', $options['is_charging']);
+ } else {
+ $request->addParam('is_charging', '0');
+ }
+
+ $response = $request->getResponse(new Response\TVGuideResponse());
+
+ return $response;
+ }
+
+ /**
+ * Get channel.
+ *
+ * You can filter the channel with different IDs: 'for_you', 'chrono_following', 'popular', 'continue_watching'
+ * and using a user ID in the following format: 'user_1234567891'.
+ *
+ * @param string $id ID used to filter channels.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TVChannelsResponse
+ */
+ public function getChannel(
+ $id = 'for_you',
+ $maxId = null)
+ {
+ if (!in_array($id, ['for_you', 'chrono_following', 'popular', 'continue_watching'])
+ && !preg_match('/^user_[1-9]\d*$/', $id)) {
+ throw new \InvalidArgumentException('Invalid ID type.');
+ }
+
+ $request = $this->ig->request('igtv/channel/')
+ ->addPost('id', $id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ if ($maxId !== null) {
+ $request->addPost('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\TVChannelsResponse());
+ }
+
+ /**
+ * Uploads a video to your Instagram TV.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_TV, $videoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Searches for channels.
+ *
+ * @param string $query The username or channel you are looking for.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TVSearchResponse
+ */
+ public function search(
+ $query = '')
+ {
+ if ($query !== '') {
+ $endpoint = 'igtv/search/';
+ } else {
+ $endpoint = 'igtv/suggested_searches/';
+ }
+
+ return $this->ig->request($endpoint)
+ ->addParam('query', $query)
+ ->getResponse(new Response\TVSearchResponse());
+ }
+
+ /**
+ * Write seen state on a video.
+ *
+ * @param string $impression Format: 1813637917462151382
+ * @param int $viewProgress Video view progress in seconds.
+ * @param mixed $gridImpressions TODO No info yet.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function writeSeenState(
+ $impression,
+ $viewProgress = 0,
+ $gridImpressions = [])
+ {
+ if (!ctype_digit($viewProgress) && (!is_int($viewProgress) || $viewProgress < 0)) {
+ throw new \InvalidArgumentException('View progress must be a positive integer.');
+ }
+
+ $seenState = json_encode([
+ 'impressions' => [
+ $impression => [
+ 'view_progress_s' => $viewProgress,
+ ],
+ ],
+ 'grid_impressions' => $gridImpressions,
+ ]);
+
+ return $this->ig->request('igtv/write_seen_state/')
+ ->addPost('seen_state', $seenState)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Timeline.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Timeline.php
new file mode 100755
index 0000000..844a6b1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Timeline.php
@@ -0,0 +1,512 @@
+ig->internal->uploadSinglePhoto(Constants::FEED_TIMELINE, $photoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads a video to your Instagram timeline.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_TIMELINE, $videoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads an album to your Instagram timeline.
+ *
+ * An album is also known as a "carousel" and "sidecar". They can contain up
+ * to 10 photos or videos (at the moment).
+ *
+ * @param array $media Array of image/video files and their per-file
+ * metadata (type, file, and optionally
+ * usertags). The "type" must be "photo" or
+ * "video". The "file" must be its disk path.
+ * And the optional "usertags" can only be
+ * used on PHOTOS, never on videos.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs
+ * for the album itself (its caption, location, etc).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureTimelineAlbum() for available album metadata fields.
+ */
+ public function uploadAlbum(
+ array $media,
+ array $externalMetadata = [])
+ {
+ if (empty($media)) {
+ throw new \InvalidArgumentException("List of media to upload can't be empty.");
+ }
+ if (count($media) < 2 || count($media) > 10) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram requires that albums contain 2-10 items. You tried to submit %d.',
+ count($media)
+ ));
+ }
+
+ // Figure out the media file details for ALL media in the album.
+ // NOTE: We do this first, since it validates whether the media files are
+ // valid and lets us avoid wasting time uploading totally invalid albums!
+ foreach ($media as $key => $item) {
+ if (!isset($item['file']) || !isset($item['type'])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Media at index "%s" does not have the required "file" and "type" keys.',
+ $key
+ ));
+ }
+
+ $itemInternalMetadata = new InternalMetadata();
+
+ // If usertags are provided, verify that the entries are valid.
+ if (isset($item['usertags'])) {
+ $item['usertags'] = ['in' => $item['usertags']];
+ Utils::throwIfInvalidUsertags($item['usertags']);
+ }
+
+ // Pre-process media details and throw if not allowed on Instagram.
+ switch ($item['type']) {
+ case 'photo':
+ // Determine the photo details.
+ $itemInternalMetadata->setPhotoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']);
+ break;
+ case 'video':
+ // Determine the video details.
+ $itemInternalMetadata->setVideoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unsupported album media type "%s".', $item['type']));
+ }
+
+ $media[$key]['internalMetadata'] = $itemInternalMetadata;
+ }
+
+ // Perform all media file uploads.
+ foreach ($media as $key => $item) {
+ /** @var InternalMetadata $itemInternalMetadata */
+ $itemInternalMetadata = $media[$key]['internalMetadata'];
+
+ switch ($item['type']) {
+ case 'photo':
+ $this->ig->internal->uploadPhotoData(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata);
+ break;
+ case 'video':
+ // Attempt to upload the video data.
+ $itemInternalMetadata = $this->ig->internal->uploadVideo(Constants::FEED_TIMELINE_ALBUM, $item['file'], $itemInternalMetadata);
+
+ // Attempt to upload the thumbnail, associated with our video's ID.
+ $this->ig->internal->uploadVideoThumbnail(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata, ['thumbnail_timestamp' => (isset($media[$key]['thumbnail_timestamp'])) ? $media[$key]['thumbnail_timestamp'] : 0]);
+ }
+
+ $media[$key]['internalMetadata'] = $itemInternalMetadata;
+ }
+
+ // Generate an uploadId (via internal metadata) for the album.
+ $albumInternalMetadata = new InternalMetadata();
+ // Configure the uploaded album and attach it to our timeline.
+ try {
+ /** @var \InstagramAPI\Response\ConfigureResponse $configure */
+ $configure = $this->ig->internal->configureWithRetries(
+ function () use ($media, $albumInternalMetadata, $externalMetadata) {
+ return $this->ig->internal->configureTimelineAlbum($media, $albumInternalMetadata, $externalMetadata);
+ }
+ );
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of the album failed: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $configure;
+ }
+
+ /**
+ * Get your "home screen" timeline feed.
+ *
+ * This is the feed of recent timeline posts from people you follow.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ * @param array|null $options An associative array with following keys (all
+ * of them are optional):
+ * "latest_story_pk" The media ID in Instagram's
+ * internal format (ie "3482384834_43294");
+ * "seen_posts" One or more seen media IDs;
+ * "unseen_posts" One or more unseen media IDs;
+ * "is_pull_to_refresh" Whether this call was
+ * triggered by a refresh;
+ * "push_disabled" Whether user has disabled
+ * PUSH;
+ * "recovered_from_crash" Whether the app has
+ * recovered from a crash/was killed by Android
+ * memory manager/force closed by user/just
+ * installed for the first time;
+ * "feed_view_info" DON'T USE IT YET.
+ * "is_charging" Wether the device is being charged
+ * or not. Valid values: 0 for not charging, 1 for
+ * charging.
+ * "battery_level" Sets the current device battery
+ * level.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TimelineFeedResponse
+ */
+ public function getTimelineFeed(
+ $maxId = null,
+ array $options = null)
+ {
+ $asyncAds = $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_enabled'
+ );
+
+ $request = $this->ig->request('feed/timeline/')
+ ->setSignedPost(false)
+ ->setIsBodyCompressed(true)
+ //->addHeader('X-CM-Bandwidth-KBPS', '-1.000')
+ //->addHeader('X-CM-Latency', '0.000')
+ ->addHeader('X-Ads-Opt-Out', '0')
+ ->addHeader('X-Google-AD-ID', $this->ig->advertising_id)
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addPost('bloks_versioning_id', Constants::BLOCK_VERSIONING_ID)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('is_prefetch', '0')
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('device_id', $this->ig->uuid)
+ ->addPost('client_session_id', $this->ig->session_id)
+ ->addPost('battery_level', '100')
+ ->addPost('is_charging', '1')
+ ->addPost('will_sound_on', '1')
+ ->addPost('timezone_offset', date('Z'))
+ ->addPost('is_async_ads_in_headload_enabled', (string) (int) ($asyncAds && $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_async_ads_in_headload_enabled'
+ )))
+ ->addPost('is_async_ads_double_request', (string) (int) ($asyncAds && $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_double_request_enabled'
+ )))
+ ->addPost('is_async_ads_rti', (string) (int) ($asyncAds && $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_rti_enabled'
+ )))
+ ->addPost('rti_delivery_backend', (string) (int) $this->ig->getExperimentParam(
+ 'ig_android_ad_async_ads_universe',
+ 'rti_delivery_backend'
+ ));
+
+ if (isset($options['is_charging'])) {
+ $request->addPost('is_charging', $options['is_charging']);
+ } else {
+ $request->addPost('is_charging', '0');
+ }
+
+ if (isset($options['battery_level'])) {
+ $request->addPost('battery_level', $options['battery_level']);
+ } else {
+ $request->addPost('battery_level', mt_rand(25, 100));
+ }
+
+ if (isset($options['latest_story_pk'])) {
+ $request->addPost('latest_story_pk', $options['latest_story_pk']);
+ }
+
+ if ($maxId !== null) {
+ $request->addPost('reason', 'pagination');
+ $request->addPost('max_id', $maxId);
+ $request->addPost('is_pull_to_refresh', '0');
+ } elseif (!empty($options['is_pull_to_refresh'])) {
+ $request->addPost('reason', 'pull_to_refresh');
+ $request->addPost('is_pull_to_refresh', '1');
+ } elseif (isset($options['is_pull_to_refresh'])) {
+ $request->addPost('reason', 'warm_start_fetch');
+ $request->addPost('is_pull_to_refresh', '0');
+ } else {
+ $request->addPost('reason', 'cold_start_fetch');
+ $request->addPost('is_pull_to_refresh', '0');
+ }
+
+ if (isset($options['seen_posts'])) {
+ if (is_array($options['seen_posts'])) {
+ $request->addPost('seen_posts', implode(',', $options['seen_posts']));
+ } else {
+ $request->addPost('seen_posts', $options['seen_posts']);
+ }
+ } elseif ($maxId === null) {
+ $request->addPost('seen_posts', '');
+ }
+
+ if (isset($options['unseen_posts'])) {
+ if (is_array($options['unseen_posts'])) {
+ $request->addPost('unseen_posts', implode(',', $options['unseen_posts']));
+ } else {
+ $request->addPost('unseen_posts', $options['unseen_posts']);
+ }
+ } elseif ($maxId === null) {
+ $request->addPost('unseen_posts', '');
+ }
+
+ if (isset($options['feed_view_info'])) {
+ if (is_array($options['feed_view_info'])) {
+ $request->addPost('feed_view_info', json_encode($options['feed_view_info']));
+ } else {
+ $request->addPost('feed_view_info', json_encode([$options['feed_view_info']]));
+ }
+ } elseif ($maxId === null) {
+ $request->addPost('feed_view_info', '[]');
+ }
+
+ if (!empty($options['push_disabled'])) {
+ $request->addPost('push_disabled', 'true');
+ }
+
+ if (!empty($options['recovered_from_crash'])) {
+ $request->addPost('recovered_from_crash', '1');
+ }
+
+ return $request->getResponse(new Response\TimelineFeedResponse());
+ }
+
+ /**
+ * Get a user's timeline feed.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserFeedResponse
+ */
+ public function getUserFeed(
+ $userId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("feed/user/{$userId}/")
+ ->addParam('exclude_comment', true)
+ ->addParam('only_fetch_first_carousel_media', false);
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\UserFeedResponse());
+ }
+
+ /**
+ * Get your own timeline feed.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserFeedResponse
+ */
+ public function getSelfUserFeed(
+ $maxId = null)
+ {
+ return $this->getUserFeed($this->ig->account_id, $maxId);
+ }
+
+ /**
+ * Get your archived timeline media feed.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserFeedResponse
+ */
+ public function getArchivedMediaFeed()
+ {
+ return $this->ig->request('feed/only_me_feed/')
+ ->getResponse(new Response\UserFeedResponse());
+ }
+
+ /**
+ * Archives or unarchives one of your timeline media items.
+ *
+ * Marking media as "archived" will hide it from everyone except yourself.
+ * You can unmark the media again at any time, to make it public again.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * "ALBUM", or the raw value of the Item's "getMediaType()" function.
+ * @param bool $onlyMe If true, archives your media so that it's only visible to you.
+ * Otherwise, if false, makes the media public to everyone again.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ArchiveMediaResponse
+ */
+ public function archiveMedia(
+ $mediaId,
+ $onlyMe = true)
+ {
+ $endpoint = $onlyMe ? 'only_me' : 'undo_only_me';
+
+ return $this->ig->request("media/{$mediaId}/{$endpoint}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('media_id', $mediaId)
+ ->getResponse(new Response\ArchiveMediaResponse());
+ }
+
+ /**
+ * Backup all of your own uploaded photos and videos. :).
+ *
+ * Note that the backup filenames contain the date and time that the media
+ * was uploaded. It uses PHP's timezone to calculate the local time. So be
+ * sure to use date_default_timezone_set() with your local timezone if you
+ * want correct times in the filenames!
+ *
+ * @param string $baseOutputPath (optional) Base-folder for output.
+ * Uses "backups/" path in lib dir if null.
+ * @param bool $printProgress (optional) Toggles terminal output.
+ *
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ public function backup(
+ $baseOutputPath = null,
+ $printProgress = true)
+ {
+ // Decide which path to use.
+ if ($baseOutputPath === null) {
+ $baseOutputPath = Constants::SRC_DIR.'/../backups/';
+ }
+
+ // Ensure that the whole directory path for the backup exists.
+ $backupFolder = $baseOutputPath.$this->ig->username.'/'.date('Y-m-d').'/';
+ if (!Utils::createFolder($backupFolder)) {
+ throw new \RuntimeException(sprintf(
+ 'The "%s" backup folder is not writable.',
+ $backupFolder
+ ));
+ }
+
+ // Download all media to the output folders.
+ $nextMaxId = null;
+ do {
+ $myTimeline = $this->getSelfUserFeed($nextMaxId);
+
+ // Build a list of all media files on this page.
+ $mediaFiles = []; // Reset queue.
+ foreach ($myTimeline->getItems() as $item) {
+ $itemDate = date('Y-m-d \a\t H.i.s O', $item->getTakenAt());
+ if ($item->getMediaType() == Response\Model\Item::CAROUSEL) {
+ // Albums contain multiple items which must all be queued.
+ // NOTE: We won't name them by their subitem's getIds, since
+ // those Ids have no meaning outside of the album and they
+ // would just mean that the album content is spread out with
+ // wildly varying filenames. Instead, we will name all album
+ // items after their album's Id, with a position offset in
+ // their filename to show their position within the album.
+ $subPosition = 0;
+ foreach ($item->getCarouselMedia() as $subItem) {
+ ++$subPosition;
+ if ($subItem->getMediaType() == Response\Model\CarouselMedia::PHOTO) {
+ $mediaUrl = $subItem->getImageVersions2()->getCandidates()[0]->getUrl();
+ } else {
+ $mediaUrl = $subItem->getVideoVersions()[0]->getUrl();
+ }
+ $subItemId = sprintf('%s [%s-%02d]', $itemDate, $item->getId(), $subPosition);
+ $mediaFiles[$subItemId] = [
+ 'taken_at' => $item->getTakenAt(),
+ 'url' => $mediaUrl,
+ ];
+ }
+ } else {
+ if ($item->getMediaType() == Response\Model\Item::PHOTO) {
+ $mediaUrl = $item->getImageVersions2()->getCandidates()[0]->getUrl();
+ } else {
+ $mediaUrl = $item->getVideoVersions()[0]->getUrl();
+ }
+ $itemId = sprintf('%s [%s]', $itemDate, $item->getId());
+ $mediaFiles[$itemId] = [
+ 'taken_at' => $item->getTakenAt(),
+ 'url' => $mediaUrl,
+ ];
+ }
+ }
+
+ // Download all media files in the current page's file queue.
+ foreach ($mediaFiles as $mediaId => $mediaInfo) {
+ $mediaUrl = $mediaInfo['url'];
+ $fileExtension = pathinfo(parse_url($mediaUrl, PHP_URL_PATH), PATHINFO_EXTENSION);
+ $filePath = $backupFolder.$mediaId.'.'.$fileExtension;
+
+ // Attempt to download the file.
+ if ($printProgress) {
+ echo sprintf("* Downloading \"%s\" to \"%s\".\n", $mediaUrl, $filePath);
+ }
+ copy($mediaUrl, $filePath);
+
+ // Set the file modification time to the taken_at timestamp.
+ if (is_file($filePath)) {
+ touch($filePath, $mediaInfo['taken_at']);
+ }
+ }
+
+ // Update the page ID to point to the next page (if more available).
+ $nextMaxId = $myTimeline->getNextMaxId();
+ } while ($nextMaxId !== null);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Request/Usertag.php b/instafeed/vendor/mgp25/instagram-php/src/Request/Usertag.php
new file mode 100755
index 0000000..4efba3a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Request/Usertag.php
@@ -0,0 +1,144 @@
+ [],
+ 'in' => [
+ ['position' => $position, 'user_id' => $userId],
+ ],
+ ];
+
+ return $this->ig->media->edit($mediaId, $captionText, ['usertags' => $usertags]);
+ }
+
+ /**
+ * Untag a user from a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $userId Numerical UserPK ID.
+ * @param string $captionText Caption to use for the media.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditMediaResponse
+ */
+ public function untagMedia(
+ $mediaId,
+ $userId,
+ $captionText = '')
+ {
+ $usertags = [
+ 'removed' => [
+ $userId,
+ ],
+ 'in' => [],
+ ];
+
+ return $this->ig->media->edit($mediaId, $captionText, ['usertags' => $usertags]);
+ }
+
+ /**
+ * Remove yourself from a tagged media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaInfoResponse
+ */
+ public function removeSelfTag(
+ $mediaId)
+ {
+ return $this->ig->request("usertags/{$mediaId}/remove/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\MediaInfoResponse());
+ }
+
+ /**
+ * Get user taggings for a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UsertagsResponse
+ */
+ public function getUserFeed(
+ $userId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("usertags/{$userId}/feed/");
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\UsertagsResponse());
+ }
+
+ /**
+ * Get user taggings for your own account.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UsertagsResponse
+ */
+ public function getSelfUserFeed(
+ $maxId = null)
+ {
+ return $this->getUserFeed($this->ig->account_id, $maxId);
+ }
+
+ /**
+ * Choose how photos you are tagged in will be added to your profile.
+ *
+ * @param bool $enabled TRUE to manually accept photos, or FALSE to accept automatically.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReviewPreferenceResponse
+ */
+ public function setReviewPreference(
+ $enabled)
+ {
+ return $this->ig->request('usertags/review_preference/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('enabled', (int) $enabled)
+ ->getResponse(new Response\ReviewPreferenceResponse());
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response.php b/instafeed/vendor/mgp25/instagram-php/src/Response.php
new file mode 100755
index 0000000..abcc39a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response.php
@@ -0,0 +1,141 @@
+ 'string',
+ /*
+ * Instagram's API failure error message(s).
+ *
+ * NOTE: This MUST be marked as 'mixed' since the server can give us
+ * either a single string OR a data structure with multiple messages.
+ * Our custom `getMessage()` will take care of parsing their value.
+ */
+ 'message' => 'mixed',
+ /*
+ * This can exist in any Instagram API response, and carries special
+ * status information.
+ *
+ * Known messages: "fb_needs_reauth", "vkontakte_needs_reauth",
+ * "twitter_needs_reauth", "ameba_needs_reauth", "update_push_token".
+ */
+ '_messages' => 'Response\Model\_Message[]',
+ ];
+
+ /** @var HttpResponseInterface */
+ public $httpResponse;
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ return $this->_getProperty('status') === self::STATUS_OK;
+ }
+
+ /**
+ * Gets the message.
+ *
+ * This function overrides the normal getter with some special processing
+ * to handle unusual multi-error message values in certain responses.
+ *
+ * @throws RuntimeException If the message object is of an unsupported type.
+ *
+ * @return string|null A message string if one exists, otherwise NULL.
+ */
+ public function getMessage()
+ {
+ // Instagram's API usually returns a simple error string. But in some
+ // cases, they instead return a subarray of individual errors, in case
+ // of APIs that can return multiple errors at once.
+ //
+ // Uncomment this if you want to test multiple error handling:
+ // $json = '{"status":"fail","message":{"errors":["Select a valid choice. 0 is not one of the available choices."]}}';
+ // $json = '{"status":"fail","message":{"errors":["Select a valid choice. 0 is not one of the available choices.","Another error.","One more error."]}}';
+ // $data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+ // $this->_setProperty('message', $data['message']);
+
+ $message = $this->_getProperty('message');
+ if ($message === null || is_string($message)) {
+ // Single error string or nothing at all.
+ return $message;
+ } elseif (is_array($message)) {
+ // Multiple errors in an "errors" subarray.
+ if (count($message) === 1 && isset($message['errors']) && is_array($message['errors'])) {
+ // Add "Multiple Errors" prefix if the response contains more than one.
+ // But most of the time, there will only be one error in the array.
+ $str = (count($message['errors']) > 1 ? 'Multiple Errors: ' : '');
+ $str .= implode(' AND ', $message['errors']); // Assumes all errors are strings.
+ return $str;
+ } else {
+ throw new RuntimeException('Unknown message object. Expected errors subarray but found something else. Please submit a ticket about needing an Instagram-API library update!');
+ }
+ } else {
+ throw new RuntimeException('Unknown message type. Please submit a ticket about needing an Instagram-API library update!');
+ }
+ }
+
+ /**
+ * Gets the HTTP response.
+ *
+ * @return HttpResponseInterface
+ */
+ public function getHttpResponse()
+ {
+ return $this->httpResponse;
+ }
+
+ /**
+ * Sets the HTTP response.
+ *
+ * @param HttpResponseInterface $response
+ */
+ public function setHttpResponse(
+ HttpResponseInterface $response)
+ {
+ $this->httpResponse = $response;
+ }
+
+ /**
+ * Checks if an HTTP response value exists.
+ *
+ * @return bool
+ */
+ public function isHttpResponse()
+ {
+ return $this->httpResponse !== null;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/AccountCreateResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/AccountCreateResponse.php
new file mode 100755
index 0000000..91cc852
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/AccountCreateResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'created_user' => 'Model\User',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/AccountDetailsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/AccountDetailsResponse.php
new file mode 100755
index 0000000..d67c255
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/AccountDetailsResponse.php
@@ -0,0 +1,52 @@
+ 'string',
+ 'former_username_info' => 'Model\FormerUsernameInfo',
+ 'primary_country_info' => 'Model\PrimaryCountryInfo',
+ 'shared_follower_accounts_info' => 'Model\SharedFollowerAccountsInfo',
+ 'ads_info' => 'Model\AdsInfo',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/AccountSecurityInfoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/AccountSecurityInfoResponse.php
new file mode 100755
index 0000000..8c027e9
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/AccountSecurityInfoResponse.php
@@ -0,0 +1,57 @@
+ '',
+ 'is_phone_confirmed' => '',
+ 'country_code' => 'int',
+ 'phone_number' => 'string',
+ 'is_two_factor_enabled' => '',
+ 'national_number' => 'string', // Really int, but may be >32bit.
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ActiveFeedAdsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ActiveFeedAdsResponse.php
new file mode 100755
index 0000000..e87215f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ActiveFeedAdsResponse.php
@@ -0,0 +1,42 @@
+ 'Model\FeedItem[]',
+ 'next_max_id' => 'string',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ActiveReelAdsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ActiveReelAdsResponse.php
new file mode 100755
index 0000000..428279e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ActiveReelAdsResponse.php
@@ -0,0 +1,42 @@
+ 'Model\Reel[]',
+ 'next_max_id' => 'string',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ActivityNewsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ActivityNewsResponse.php
new file mode 100755
index 0000000..db1b061
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ActivityNewsResponse.php
@@ -0,0 +1,77 @@
+ 'Model\Story[]',
+ 'old_stories' => 'Model\Story[]',
+ 'continuation' => '',
+ 'friend_request_stories' => 'Model\Story[]',
+ 'counts' => 'Model\Counts',
+ 'subscription' => 'Model\Subscription',
+ 'partition' => '',
+ 'continuation_token' => '',
+ 'ads_manager' => '',
+ 'aymf' => 'Model\Aymf',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ArchiveMediaResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ArchiveMediaResponse.php
new file mode 100755
index 0000000..97b4bdc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ArchiveMediaResponse.php
@@ -0,0 +1,25 @@
+ 'Model\ArchivedStoriesFeedItem[]',
+ 'num_results' => 'int',
+ 'more_available' => 'bool',
+ 'max_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ArlinkDownloadInfoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ArlinkDownloadInfoResponse.php
new file mode 100755
index 0000000..8ec0d50
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ArlinkDownloadInfoResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'download_url' => 'string',
+ 'file_size' => 'string',
+ 'version' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BadgeNotificationsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BadgeNotificationsResponse.php
new file mode 100755
index 0000000..7bae681
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BadgeNotificationsResponse.php
@@ -0,0 +1,37 @@
+ 'Model\UnpredictableKeys\CoreUnpredictableContainer',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BlockedListResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BlockedListResponse.php
new file mode 100755
index 0000000..f63f309
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BlockedListResponse.php
@@ -0,0 +1,42 @@
+ 'Model\User[]',
+ 'next_max_id' => 'string',
+ 'page_size' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BlockedMediaResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BlockedMediaResponse.php
new file mode 100755
index 0000000..69da2bc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BlockedMediaResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BlockedReelsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BlockedReelsResponse.php
new file mode 100755
index 0000000..40e4e55
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BlockedReelsResponse.php
@@ -0,0 +1,45 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BootstrapUsersResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BootstrapUsersResponse.php
new file mode 100755
index 0000000..e786f4a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BootstrapUsersResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Surface[]',
+ 'users' => 'Model\User[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastCommentsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastCommentsResponse.php
new file mode 100755
index 0000000..6b6d892
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastCommentsResponse.php
@@ -0,0 +1,95 @@
+ 'Model\Comment[]',
+ 'comment_count' => 'int',
+ 'live_seconds_per_comment' => 'int',
+ 'has_more_headload_comments' => 'bool',
+ /*
+ * NOTE: Instagram sends "True" or "False" as a string in this property.
+ */
+ 'is_first_fetch' => 'string',
+ 'comment_likes_enabled' => 'bool',
+ 'pinned_comment' => 'Model\Comment',
+ 'system_comments' => 'Model\Comment[]',
+ 'has_more_comments' => 'bool',
+ 'caption_is_edited' => 'bool',
+ 'caption' => '',
+ 'comment_muted' => 'int',
+ 'media_header_display' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastHeartbeatAndViewerCountResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastHeartbeatAndViewerCountResponse.php
new file mode 100755
index 0000000..1f94d75
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastHeartbeatAndViewerCountResponse.php
@@ -0,0 +1,67 @@
+ 'string',
+ 'viewer_count' => 'int',
+ 'offset_to_video_start' => 'int',
+ 'total_unique_viewer_count' => 'int',
+ 'is_top_live_eligible' => 'int',
+ 'cobroadcaster_ids' => 'string[]',
+ 'is_policy_violation' => 'int',
+ 'policy_violation_reason' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastInfoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastInfoResponse.php
new file mode 100755
index 0000000..5c89ae7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastInfoResponse.php
@@ -0,0 +1,132 @@
+ 'string',
+ 'num_total_requests' => 'int',
+ 'num_new_requests' => 'int',
+ 'users' => 'Model\User[]',
+ 'num_unseen_requests' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastLikeCountResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastLikeCountResponse.php
new file mode 100755
index 0000000..6a2cd45
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastLikeCountResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'likes' => 'int',
+ 'burst_likes' => 'int',
+ 'likers' => 'Model\User[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastLikeResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastLikeResponse.php
new file mode 100755
index 0000000..4385c07
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastLikeResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastQuestionsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastQuestionsResponse.php
new file mode 100755
index 0000000..deb878b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/BroadcastQuestionsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\BroadcastQuestion[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CapabilitiesDecisionsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CapabilitiesDecisionsResponse.php
new file mode 100755
index 0000000..09857e8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CapabilitiesDecisionsResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ChallengeResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ChallengeResponse.php
new file mode 100755
index 0000000..d1af5e8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ChallengeResponse.php
@@ -0,0 +1,25 @@
+ 'Model\User[]',
+ 'suggested_charities' => 'Model\User[]',
+ 'searched_charities' => 'Model\User[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CheckEmailResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CheckEmailResponse.php
new file mode 100755
index 0000000..61978ec
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CheckEmailResponse.php
@@ -0,0 +1,52 @@
+ '',
+ 'available' => '',
+ 'confirmed' => '',
+ 'username_suggestions' => 'string[]',
+ 'error_type' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CheckUsernameResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CheckUsernameResponse.php
new file mode 100755
index 0000000..8a5d9ca
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CheckUsernameResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'available' => '',
+ 'error' => '',
+ 'error_type' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CloseFriendsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CloseFriendsResponse.php
new file mode 100755
index 0000000..4acd195
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CloseFriendsResponse.php
@@ -0,0 +1,49 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CollectionFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CollectionFeedResponse.php
new file mode 100755
index 0000000..c1167b7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CollectionFeedResponse.php
@@ -0,0 +1,67 @@
+ 'string',
+ 'collection_name' => 'string',
+ 'items' => 'Model\SavedFeedItem[]',
+ 'num_results' => 'int',
+ 'more_available' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ 'has_related_media' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CommentBroadcastResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentBroadcastResponse.php
new file mode 100755
index 0000000..39fb294
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentBroadcastResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Comment',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CommentCategoryFilterResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentCategoryFilterResponse.php
new file mode 100755
index 0000000..06341d7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentCategoryFilterResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CommentFilterKeywordsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentFilterKeywordsResponse.php
new file mode 100755
index 0000000..a9c3ad7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentFilterKeywordsResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CommentFilterResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentFilterResponse.php
new file mode 100755
index 0000000..894907f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentFilterResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CommentFilterSetResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentFilterSetResponse.php
new file mode 100755
index 0000000..bd9a48c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentFilterSetResponse.php
@@ -0,0 +1,25 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CommentResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentResponse.php
new file mode 100755
index 0000000..0d2c2a6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CommentResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Comment',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ConfigureResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ConfigureResponse.php
new file mode 100755
index 0000000..5c2e426
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ConfigureResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'media' => 'Model\Item',
+ 'client_sidecar_id' => 'string',
+ 'message_metadata' => 'Model\DirectMessageMetadata[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CreateBusinessInfoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CreateBusinessInfoResponse.php
new file mode 100755
index 0000000..785a8dd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CreateBusinessInfoResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CreateCollectionResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CreateCollectionResponse.php
new file mode 100755
index 0000000..e92fa0b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CreateCollectionResponse.php
@@ -0,0 +1,40 @@
+ 'Model\Reel',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/CreateLiveResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/CreateLiveResponse.php
new file mode 100755
index 0000000..2560dd5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/CreateLiveResponse.php
@@ -0,0 +1,157 @@
+ 'string',
+ 'upload_url' => 'string',
+ 'max_time_in_seconds' => 'int',
+ 'speed_test_ui_timeout' => 'int',
+ 'stream_network_speed_test_payload_chunk_size_in_bytes' => 'int',
+ 'stream_network_speed_test_payload_size_in_bytes' => 'int',
+ 'stream_network_speed_test_payload_timeout_in_seconds' => 'int',
+ 'speed_test_minimum_bandwidth_threshold' => 'int',
+ 'speed_test_retry_max_count' => 'int',
+ 'speed_test_retry_time_delay' => 'int',
+ 'disable_speed_test' => 'int',
+ 'stream_video_allow_b_frames' => 'int',
+ 'stream_video_width' => 'int',
+ 'stream_video_bit_rate' => 'int',
+ 'stream_video_fps' => 'int',
+ 'stream_audio_bit_rate' => 'int',
+ 'stream_audio_sample_rate' => 'int',
+ 'stream_audio_channels' => 'int',
+ 'heartbeat_interval' => 'int',
+ 'broadcaster_update_frequency' => 'int',
+ 'stream_video_adaptive_bitrate_config' => '',
+ 'stream_network_connection_retry_count' => 'int',
+ 'stream_network_connection_retry_delay_in_seconds' => 'int',
+ 'connect_with_1rtt' => 'int',
+ 'avc_rtmp_payload' => 'int',
+ 'allow_resolution_change' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DeleteCollectionResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DeleteCollectionResponse.php
new file mode 100755
index 0000000..1c7ef6d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DeleteCollectionResponse.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'users' => 'Model\User[]',
+ 'left_users' => 'Model\User[]',
+ 'items' => 'Model\DirectThreadItem[]',
+ 'last_activity_at' => '',
+ 'muted' => '',
+ 'named' => '',
+ 'canonical' => '',
+ 'pending' => '',
+ 'thread_type' => '',
+ 'viewer_id' => 'string',
+ 'thread_title' => '',
+ 'inviter' => 'Model\User',
+ 'has_older' => 'bool',
+ 'has_newer' => 'bool',
+ 'last_seen_at' => '',
+ 'is_pin' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectInboxResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectInboxResponse.php
new file mode 100755
index 0000000..84531d2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectInboxResponse.php
@@ -0,0 +1,67 @@
+ 'Model\User',
+ 'pending_requests_total' => '',
+ 'seq_id' => 'string',
+ 'has_pending_top_requests' => 'bool',
+ 'pending_requests_users' => 'Model\User[]',
+ 'inbox' => 'Model\DirectInbox',
+ 'megaphone' => 'Model\Megaphone',
+ 'snapshot_at_ms' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectPendingInboxResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectPendingInboxResponse.php
new file mode 100755
index 0000000..ab11e2b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectPendingInboxResponse.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'pending_requests_total' => '',
+ 'inbox' => 'Model\DirectInbox',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectRankedRecipientsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectRankedRecipientsResponse.php
new file mode 100755
index 0000000..f1eee00
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectRankedRecipientsResponse.php
@@ -0,0 +1,52 @@
+ '',
+ 'ranked_recipients' => 'Model\DirectRankedRecipient[]',
+ 'filtered' => '',
+ 'request_id' => 'string',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectRecentRecipientsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectRecentRecipientsResponse.php
new file mode 100755
index 0000000..7242b71
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectRecentRecipientsResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'recent_recipients' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectSeenItemResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectSeenItemResponse.php
new file mode 100755
index 0000000..37e9c1d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectSeenItemResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'payload' => 'Model\DirectSeenItemPayload', // The number of unseen items.
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectSendItemResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectSendItemResponse.php
new file mode 100755
index 0000000..a275d2e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectSendItemResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'status_code' => '',
+ 'payload' => 'Model\DirectSendItemPayload',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectSendItemsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectSendItemsResponse.php
new file mode 100755
index 0000000..17a133d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectSendItemsResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'status_code' => '',
+ 'payload' => 'Model\DirectSendItemPayload[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectShareInboxResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectShareInboxResponse.php
new file mode 100755
index 0000000..4c380ed
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectShareInboxResponse.php
@@ -0,0 +1,57 @@
+ '',
+ 'max_id' => 'string',
+ 'new_shares' => '',
+ 'patches' => '',
+ 'last_counted_at' => '',
+ 'new_shares_info' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectThreadResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectThreadResponse.php
new file mode 100755
index 0000000..d62edba
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectThreadResponse.php
@@ -0,0 +1,32 @@
+ 'Model\DirectThread',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DirectVisualThreadResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectVisualThreadResponse.php
new file mode 100755
index 0000000..32cd4b7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DirectVisualThreadResponse.php
@@ -0,0 +1,192 @@
+ '',
+ 'items' => 'Model\Item[]',
+ 'more_available' => '',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DiscoverPeopleResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DiscoverPeopleResponse.php
new file mode 100755
index 0000000..6cd4976
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DiscoverPeopleResponse.php
@@ -0,0 +1,47 @@
+ 'bool',
+ 'max_id' => 'string',
+ 'suggested_users' => 'Model\SuggestedUsers',
+ 'new_suggested_users' => 'Model\SuggestedUsers',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/DiscoverTopLiveResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/DiscoverTopLiveResponse.php
new file mode 100755
index 0000000..2195061
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/DiscoverTopLiveResponse.php
@@ -0,0 +1,57 @@
+ 'Model\Broadcast[]',
+ 'post_live_broadcasts' => 'Model\PostLiveItem[]',
+ 'score_map' => '',
+ 'more_available' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/EditCollectionResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/EditCollectionResponse.php
new file mode 100755
index 0000000..d90ef41
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/EditCollectionResponse.php
@@ -0,0 +1,40 @@
+ 'Model\Item',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/EnableDisableLiveCommentsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/EnableDisableLiveCommentsResponse.php
new file mode 100755
index 0000000..415b53a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/EnableDisableLiveCommentsResponse.php
@@ -0,0 +1,32 @@
+ 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/EnableTwoFactorSMSResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/EnableTwoFactorSMSResponse.php
new file mode 100755
index 0000000..0ed8060
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/EnableTwoFactorSMSResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ExploreResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ExploreResponse.php
new file mode 100755
index 0000000..99026ec
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ExploreResponse.php
@@ -0,0 +1,67 @@
+ 'int',
+ 'auto_load_more_enabled' => 'bool',
+ 'items' => 'Model\ExploreItem[]',
+ 'sectional_items' => 'Model\Section[]',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'max_id' => 'string',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FBLocationResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FBLocationResponse.php
new file mode 100755
index 0000000..f2e55c0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FBLocationResponse.php
@@ -0,0 +1,42 @@
+ 'bool',
+ 'items' => 'Model\LocationItem[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FBSearchResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FBSearchResponse.php
new file mode 100755
index 0000000..61793ce
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FBSearchResponse.php
@@ -0,0 +1,48 @@
+ 'bool',
+ 'list' => 'Model\UserList[]',
+ 'clear_client_cache' => 'bool',
+ 'has_more' => 'bool',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FaceEffectsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FaceEffectsResponse.php
new file mode 100755
index 0000000..23496d4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FaceEffectsResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'effects' => 'Model\Effect[]',
+ 'loading_effect' => 'Model\Effect',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FaceModelsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FaceModelsResponse.php
new file mode 100755
index 0000000..8db975f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FaceModelsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\FaceModels',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FacebookHiddenEntitiesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FacebookHiddenEntitiesResponse.php
new file mode 100755
index 0000000..4ada8cd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FacebookHiddenEntitiesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\HiddenEntities',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FacebookOTAResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FacebookOTAResponse.php
new file mode 100755
index 0000000..bbb7302
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FacebookOTAResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'request_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FetchQPDataResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FetchQPDataResponse.php
new file mode 100755
index 0000000..4f8c75f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FetchQPDataResponse.php
@@ -0,0 +1,52 @@
+ 'string',
+ 'extra_info' => 'Model\QPExtraInfo[]',
+ 'qp_data' => 'Model\QPData[]',
+ 'client_cache_ttl_in_sec' => 'int',
+ 'error_msg' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FinalViewerListResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FinalViewerListResponse.php
new file mode 100755
index 0000000..374cc3c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FinalViewerListResponse.php
@@ -0,0 +1,37 @@
+ 'Model\User[]',
+ 'total_unique_viewer_count' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FollowerAndFollowingResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FollowerAndFollowingResponse.php
new file mode 100755
index 0000000..0e51564
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FollowerAndFollowingResponse.php
@@ -0,0 +1,57 @@
+ 'Model\User[]',
+ 'suggested_users' => 'Model\SuggestedUsers',
+ 'truncate_follow_requests_at_index' => 'int',
+ 'next_max_id' => 'string',
+ 'page_size' => '',
+ 'big_list' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FollowingRecentActivityResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FollowingRecentActivityResponse.php
new file mode 100755
index 0000000..691366b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FollowingRecentActivityResponse.php
@@ -0,0 +1,47 @@
+ 'Model\Story[]',
+ 'next_max_id' => 'string',
+ 'auto_load_more_enabled' => '',
+ 'megaphone' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FormerUsernamesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FormerUsernamesResponse.php
new file mode 100755
index 0000000..c8dbe23
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FormerUsernamesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\FormerUsername[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FriendshipResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FriendshipResponse.php
new file mode 100755
index 0000000..5f9c977
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FriendshipResponse.php
@@ -0,0 +1,32 @@
+ 'Model\FriendshipStatus',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FriendshipsShowManyResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FriendshipsShowManyResponse.php
new file mode 100755
index 0000000..bc48bdd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FriendshipsShowManyResponse.php
@@ -0,0 +1,32 @@
+ 'Model\UnpredictableKeys\FriendshipStatusUnpredictableContainer',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/FriendshipsShowResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/FriendshipsShowResponse.php
new file mode 100755
index 0000000..865e5d4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/FriendshipsShowResponse.php
@@ -0,0 +1,72 @@
+ 'Model\Collection[]',
+ 'more_available' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/GraphqlResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/GraphqlResponse.php
new file mode 100755
index 0000000..7e66914
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/GraphqlResponse.php
@@ -0,0 +1,82 @@
+ 'Model\GraphData',
+ ];
+
+ public function getGenderGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getGenderGraph();
+ }
+
+ public function getAllAgeGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getAllFollowersAgeGraph();
+ }
+
+ public function getMenAgeGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getMenFollowersAgeGraph();
+ }
+
+ public function getWomenAgeGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getWomenFollowersAgeGraph();
+ }
+
+ public function getFollowersTopCities()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getFollowersTopCitiesGraph();
+ }
+
+ public function getFollowersTopCountries()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getFollowersTopCountriesGraph();
+ }
+
+ public function getDailyWeekFollowersGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getWeekDailyFollowersGraph();
+ }
+
+ public function getDaysHourlyFollowersGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getDaysHourlyFollowersGraph();
+ }
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ return true;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/HashtagsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/HashtagsResponse.php
new file mode 100755
index 0000000..64168ba
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/HashtagsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Hashtag[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/HighlightFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/HighlightFeedResponse.php
new file mode 100755
index 0000000..fc80529
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/HighlightFeedResponse.php
@@ -0,0 +1,57 @@
+ 'bool',
+ 'next_max_id' => 'string',
+ 'stories' => 'Model\Story[]',
+ 'show_empty_state' => 'bool',
+ 'tray' => 'Model\StoryTray[]',
+ 'tv_channel' => 'Model\StoryTvChannel',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/InsightsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/InsightsResponse.php
new file mode 100755
index 0000000..4efe919
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/InsightsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Insights',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/LauncherSyncResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/LauncherSyncResponse.php
new file mode 100755
index 0000000..de6f171
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/LauncherSyncResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/LikeFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/LikeFeedResponse.php
new file mode 100755
index 0000000..506564f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/LikeFeedResponse.php
@@ -0,0 +1,62 @@
+ '',
+ 'items' => 'Model\Item[]',
+ 'more_available' => '',
+ 'patches' => '',
+ 'last_counted_at' => '',
+ 'num_results' => 'int',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/LinkAddressBookResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/LinkAddressBookResponse.php
new file mode 100755
index 0000000..1e192f1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/LinkAddressBookResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Suggestion[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/LinkageStatusResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/LinkageStatusResponse.php
new file mode 100755
index 0000000..a8dea6d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/LinkageStatusResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/LocationFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/LocationFeedResponse.php
new file mode 100755
index 0000000..9f4d5e4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/LocationFeedResponse.php
@@ -0,0 +1,52 @@
+ 'Model\Section[]',
+ 'next_page' => 'int',
+ 'more_available' => 'bool',
+ 'next_media_ids' => 'int[]',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/LocationResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/LocationResponse.php
new file mode 100755
index 0000000..a98c54f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/LocationResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Location[]',
+ 'request_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/LocationStoryResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/LocationStoryResponse.php
new file mode 100755
index 0000000..7f90875
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/LocationStoryResponse.php
@@ -0,0 +1,32 @@
+ 'Model\StoryTray',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/LoginResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/LoginResponse.php
new file mode 100755
index 0000000..3e5428f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/LoginResponse.php
@@ -0,0 +1,157 @@
+ 'string',
+ 'has_anonymous_profile_picture' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_id' => 'string',
+ 'full_name' => 'string',
+ 'pk' => 'string',
+ 'is_private' => 'bool',
+ 'is_verified' => 'bool',
+ 'allowed_commenter_type' => 'string',
+ 'reel_auto_archive' => 'string',
+ 'allow_contacts_sync' => 'bool',
+ 'phone_number' => 'string',
+ 'country_code' => 'int',
+ 'national_number' => 'int',
+ 'error_title' => '', // On wrong pass.
+ 'error_type' => '', // On wrong pass.
+ 'buttons' => '', // On wrong pass.
+ 'invalid_credentials' => '', // On wrong pass.
+ 'logged_in_user' => 'Model\User',
+ 'two_factor_required' => '',
+ 'phone_verification_settings' => 'Model\PhoneVerificationSettings',
+ 'two_factor_info' => 'Model\TwoFactorInfo',
+ 'checkpoint_url' => 'string',
+ 'lock' => 'bool',
+ 'help_url' => 'string',
+ 'challenge' => 'Model\Challenge',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/LogoutResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/LogoutResponse.php
new file mode 100755
index 0000000..184b931
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/LogoutResponse.php
@@ -0,0 +1,25 @@
+ 'Model\SystemControl',
+ 'GATE_APP_VERSION' => 'bool',
+ 'trace_control' => 'Model\TraceControl',
+ 'id' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MediaCommentRepliesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaCommentRepliesResponse.php
new file mode 100755
index 0000000..0c19039
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaCommentRepliesResponse.php
@@ -0,0 +1,85 @@
+ 'Model\Comment',
+ /*
+ * Number of child comments in this comment thread.
+ */
+ 'child_comment_count' => 'int',
+ 'child_comments' => 'Model\Comment[]',
+ /*
+ * When "has_more_tail_child_comments" is true, you can use the value
+ * in "next_max_child_cursor" as "max_id" parameter to load up to
+ * "num_tail_child_comments" older child-comments.
+ */
+ 'has_more_tail_child_comments' => 'bool',
+ 'next_max_child_cursor' => 'string',
+ 'num_tail_child_comments' => 'int',
+ /*
+ * When "has_more_head_child_comments" is true, you can use the value
+ * in "next_min_child_cursor" as "min_id" parameter to load up to
+ * "num_head_child_comments" newer child-comments.
+ */
+ 'has_more_head_child_comments' => 'bool',
+ 'next_min_child_cursor' => 'string',
+ 'num_head_child_comments' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MediaCommentsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaCommentsResponse.php
new file mode 100755
index 0000000..4035d2f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaCommentsResponse.php
@@ -0,0 +1,87 @@
+ 'Model\Comment[]',
+ 'comment_count' => 'int',
+ 'comment_likes_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ 'next_min_id' => 'string',
+ 'caption' => 'Model\Caption',
+ 'has_more_comments' => 'bool',
+ 'caption_is_edited' => 'bool',
+ 'preview_comments' => '',
+ 'has_more_headload_comments' => 'bool',
+ 'media_header_display' => 'string',
+ 'threading_enabled' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MediaDeleteResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaDeleteResponse.php
new file mode 100755
index 0000000..9ae1c54
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaDeleteResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MediaInfoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaInfoResponse.php
new file mode 100755
index 0000000..725e7e0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaInfoResponse.php
@@ -0,0 +1,47 @@
+ '',
+ 'num_results' => 'int',
+ 'more_available' => '',
+ 'items' => 'Model\Item[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MediaInsightsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaInsightsResponse.php
new file mode 100755
index 0000000..7d2239f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaInsightsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\MediaInsights',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MediaLikersResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaLikersResponse.php
new file mode 100755
index 0000000..b45e912
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaLikersResponse.php
@@ -0,0 +1,37 @@
+ 'int',
+ 'users' => 'Model\User[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MediaSeenResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaSeenResponse.php
new file mode 100755
index 0000000..11b9823
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MediaSeenResponse.php
@@ -0,0 +1,25 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AccountAccessToolConfig.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AccountAccessToolConfig.php
new file mode 100755
index 0000000..78842dc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AccountAccessToolConfig.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'viewer' => 'User',
+ 'viewerId' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AccountSummaryUnit.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AccountSummaryUnit.php
new file mode 100755
index 0000000..de08c44
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AccountSummaryUnit.php
@@ -0,0 +1,20 @@
+ 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Action.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Action.php
new file mode 100755
index 0000000..703af80
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Action.php
@@ -0,0 +1,35 @@
+ 'Text',
+ 'url' => 'string',
+ 'limit' => 'int',
+ 'dismiss_promotion' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php
new file mode 100755
index 0000000..b0e0fa3
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php
@@ -0,0 +1,41 @@
+ '',
+ 'action_count' => '',
+ 'action_timestamp' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ActionLog.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ActionLog.php
new file mode 100755
index 0000000..b383647
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ActionLog.php
@@ -0,0 +1,25 @@
+ 'Bold[]',
+ 'description' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Ad4ad.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Ad4ad.php
new file mode 100755
index 0000000..d46b822
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Ad4ad.php
@@ -0,0 +1,45 @@
+ '',
+ 'title' => '',
+ 'media' => 'Item',
+ 'footer' => '',
+ 'id' => 'string',
+ 'tracking_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AdMetadata.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AdMetadata.php
new file mode 100755
index 0000000..d2ea416
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AdMetadata.php
@@ -0,0 +1,25 @@
+ '',
+ 'type' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AdsInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AdsInfo.php
new file mode 100755
index 0000000..863443d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AdsInfo.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'ads_url' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AndroidLinks.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AndroidLinks.php
new file mode 100755
index 0000000..3622ebb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AndroidLinks.php
@@ -0,0 +1,75 @@
+ 'int',
+ 'webUri' => 'string',
+ 'androidClass' => 'string',
+ 'package' => 'string',
+ 'deeplinkUri' => 'string',
+ 'callToActionTitle' => 'string',
+ 'redirectUri' => 'string',
+ 'igUserId' => 'string',
+ 'appInstallObjectiveInvalidationBehavior' => '',
+ 'tapAndHoldContext' => 'string',
+ 'leadGenFormId' => 'string',
+ 'canvasDocId' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMedia.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMedia.php
new file mode 100755
index 0000000..605273c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMedia.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'images' => 'AnimatedMediaImage',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImage.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImage.php
new file mode 100755
index 0000000..5da776d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImage.php
@@ -0,0 +1,20 @@
+ 'AnimatedMediaImageFixedHeigth',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImageFixedHeigth.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImageFixedHeigth.php
new file mode 100755
index 0000000..1a7013d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImageFixedHeigth.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'width' => 'string',
+ 'heigth' => 'string',
+ 'size' => 'string',
+ 'mp4' => 'string',
+ 'mp4_size' => 'string',
+ 'webp' => 'string',
+ 'webp_size' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ArchivedStoriesFeedItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ArchivedStoriesFeedItem.php
new file mode 100755
index 0000000..47b62f5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ArchivedStoriesFeedItem.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'media_count' => 'int',
+ 'id' => 'string',
+ 'reel_type' => 'string',
+ 'latest_reel_media' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Args.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Args.php
new file mode 100755
index 0000000..81ce41a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Args.php
@@ -0,0 +1,135 @@
+ 'string',
+ 'text' => 'string',
+ 'icon_url' => 'string',
+ 'links' => 'Link[]',
+ 'rich_text' => 'string',
+ 'profile_id' => 'string',
+ 'profile_image' => 'string',
+ 'media' => 'Media[]',
+ 'comment_notif_type' => 'string',
+ 'timestamp' => 'string',
+ 'tuuid' => 'string',
+ 'clicked' => 'bool',
+ 'profile_name' => 'string',
+ 'action_url' => 'string',
+ 'destination' => 'string',
+ 'actions' => 'string[]',
+ 'latest_reel_media' => 'string',
+ 'comment_id' => 'string',
+ 'request_count' => '',
+ 'inline_follow' => 'InlineFollow',
+ 'comment_ids' => 'string[]',
+ 'second_profile_id' => 'string',
+ 'second_profile_image' => '',
+ 'profile_image_destination' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AssetModel.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AssetModel.php
new file mode 100755
index 0000000..2623a62
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AssetModel.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Attribution.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Attribution.php
new file mode 100755
index 0000000..c3fe9e0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Attribution.php
@@ -0,0 +1,20 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AudioContext.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AudioContext.php
new file mode 100755
index 0000000..29c14b6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AudioContext.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'duration' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Aymf.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Aymf.php
new file mode 100755
index 0000000..7e57b63
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Aymf.php
@@ -0,0 +1,25 @@
+ 'AymfItem[]',
+ 'more_available' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AymfItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AymfItem.php
new file mode 100755
index 0000000..f9039aa
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/AymfItem.php
@@ -0,0 +1,731 @@
+ 'string',
+ 'uuid' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Badging.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Badging.php
new file mode 100755
index 0000000..2d395b2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Badging.php
@@ -0,0 +1,25 @@
+ '',
+ 'items' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BiographyEntities.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BiographyEntities.php
new file mode 100755
index 0000000..687c8d4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BiographyEntities.php
@@ -0,0 +1,30 @@
+ '',
+ 'raw_text' => 'string',
+ 'nux_type' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BlockedReels.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BlockedReels.php
new file mode 100755
index 0000000..87e3105
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BlockedReels.php
@@ -0,0 +1,30 @@
+ 'User[]',
+ 'page_size' => '',
+ 'big_list' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Bold.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Bold.php
new file mode 100755
index 0000000..9ebca09
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Bold.php
@@ -0,0 +1,25 @@
+ '',
+ 'end' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Broadcast.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Broadcast.php
new file mode 100755
index 0000000..b0a8534
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Broadcast.php
@@ -0,0 +1,151 @@
+ 'User',
+ 'cobroadcasters' => '',
+ /*
+ * A string such as "active" or "post_live".
+ */
+ 'broadcast_status' => 'string',
+ 'is_gaming_content' => 'bool',
+ 'is_player_live_trace_enabled' => 'bool',
+ 'dash_live_predictive_playback_url' => 'string',
+ 'cover_frame_url' => 'string',
+ 'published_time' => 'string',
+ 'hide_from_feed_unit' => 'bool',
+ 'broadcast_message' => 'string',
+ 'muted' => '',
+ 'media_id' => 'string',
+ 'id' => 'string',
+ 'rtmp_playback_url' => 'string',
+ 'dash_abr_playback_url' => 'string',
+ 'dash_playback_url' => 'string',
+ 'ranked_position' => '',
+ 'organic_tracking_token' => 'string',
+ 'seen_ranked_position' => '',
+ 'viewer_count' => 'int',
+ 'dash_manifest' => 'string',
+ /*
+ * Unix timestamp of when the "post_live" will expire.
+ */
+ 'expire_at' => 'string',
+ 'encoding_tag' => 'string',
+ 'total_unique_viewer_count' => 'int',
+ 'internal_only' => 'bool',
+ 'number_of_qualities' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BroadcastQuestion.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BroadcastQuestion.php
new file mode 100755
index 0000000..c9bd7c3
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BroadcastQuestion.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'qid' => 'string',
+ 'source' => 'string',
+ 'user' => 'User',
+ 'story_sticker_text' => 'string',
+ 'timestamp' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BroadcastStatusItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BroadcastStatusItem.php
new file mode 100755
index 0000000..f25ace4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BroadcastStatusItem.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'has_reduced_visibility' => 'bool',
+ 'cover_frame_url' => 'string',
+ 'viewer_count' => 'int',
+ 'id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessEdge.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessEdge.php
new file mode 100755
index 0000000..820a1b3
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessEdge.php
@@ -0,0 +1,25 @@
+ 'BusinessNode',
+ 'cursor' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessFeed.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessFeed.php
new file mode 100755
index 0000000..2ea0643
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessFeed.php
@@ -0,0 +1,20 @@
+ 'SummaryPromotions',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessManager.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessManager.php
new file mode 100755
index 0000000..4e6799e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessManager.php
@@ -0,0 +1,50 @@
+ 'AccountSummaryUnit',
+ 'account_insights_unit' => 'BusinessNode',
+ 'followers_unit' => 'FollowersUnit',
+ 'top_posts_unit' => 'BusinessNode',
+ 'stories_unit' => 'BusinessNode',
+ 'promotions_unit' => 'PromotionsUnit',
+ 'feed' => 'BusinessFeed',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessManagerStatus.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessManagerStatus.php
new file mode 100755
index 0000000..21803f4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessManagerStatus.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'account_type' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessNode.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessNode.php
new file mode 100755
index 0000000..e36a374
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/BusinessNode.php
@@ -0,0 +1,190 @@
+ '',
+ 'followers_count' => '',
+ 'followers_delta_from_last_week' => '',
+ 'posts_count' => '',
+ 'posts_delta_from_last_week' => '',
+ 'last_week_impressions' => '',
+ 'week_over_week_impressions' => '',
+ 'last_week_reach' => '',
+ 'week_over_week_reach' => '',
+ 'last_week_profile_visits' => '',
+ 'week_over_week_profile_visits' => '',
+ 'last_week_website_visits' => '',
+ 'week_over_week_website_visits' => '',
+ 'last_week_call' => '',
+ 'week_over_week_call' => '',
+ 'last_week_text' => '',
+ 'week_over_week_text' => '',
+ 'last_week_email' => '',
+ 'week_over_week_email' => '',
+ 'last_week_get_direction' => '',
+ 'week_over_week_get_direction' => '',
+ 'average_engagement_count' => '',
+ 'last_week_impressions_day_graph' => '',
+ 'last_week_reach_day_graph' => '',
+ 'last_week_profile_visits_day_graph' => '',
+ 'summary_posts' => '',
+ 'state' => '',
+ 'summary_stories' => '',
+ 'followers_unit_state' => '',
+ 'today_hourly_graph' => '',
+ 'gender_graph' => '',
+ 'all_followers_age_graph' => '',
+ 'followers_top_cities_graph' => '',
+ 'summary_promotions' => '',
+ 'top_posts' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Button.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Button.php
new file mode 100755
index 0000000..f11270a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Button.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'url' => 'string',
+ 'action' => '',
+ 'background_color' => '',
+ 'border_color' => '',
+ 'text_color' => '',
+ 'action_info' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Caption.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Caption.php
new file mode 100755
index 0000000..3d7f06d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Caption.php
@@ -0,0 +1,85 @@
+ '',
+ 'user_id' => 'string',
+ 'created_at_utc' => 'string',
+ 'created_at' => 'string',
+ 'bit_flags' => 'int',
+ 'user' => 'User',
+ 'content_type' => '',
+ 'text' => 'string',
+ 'share_enabled' => 'bool',
+ 'media_id' => 'string',
+ 'pk' => 'string',
+ 'type' => '',
+ 'has_translation' => 'bool',
+ 'did_report_as_spam' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CarouselMedia.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CarouselMedia.php
new file mode 100755
index 0000000..65c0aba
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CarouselMedia.php
@@ -0,0 +1,196 @@
+ 'string',
+ 'id' => 'string',
+ 'carousel_parent_id' => 'string',
+ 'fb_user_tags' => 'Usertag',
+ 'number_of_qualities' => 'int',
+ 'video_codec' => 'string',
+ 'is_dash_eligible' => 'int',
+ 'video_dash_manifest' => 'string',
+ 'image_versions2' => 'Image_Versions2',
+ 'video_versions' => 'VideoVersions[]',
+ 'has_audio' => 'bool',
+ 'video_duration' => 'float',
+ 'video_subtitles_uri' => 'string',
+ 'original_height' => 'int',
+ 'original_width' => 'int',
+ /*
+ * A number describing what type of media this is. Should be compared
+ * against the `CarouselMedia::PHOTO` and `CarouselMedia::VIDEO`
+ * constants!
+ */
+ 'media_type' => 'int',
+ 'dynamic_item_id' => 'string',
+ 'usertags' => 'Usertag',
+ 'preview' => 'string',
+ 'headline' => 'Headline',
+ 'link' => 'string',
+ 'link_text' => 'string',
+ 'link_hint_text' => 'string',
+ 'android_links' => 'AndroidLinks[]',
+ 'ad_metadata' => 'AdMetadata[]',
+ 'ad_action' => 'string',
+ 'ad_link_type' => 'int',
+ 'force_overlay' => 'bool',
+ 'hide_nux_text' => 'bool',
+ 'overlay_text' => 'string',
+ 'overlay_title' => 'string',
+ 'overlay_subtitle' => 'string',
+ 'photo_of_you' => 'bool',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'dominant_color' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CatalogData.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CatalogData.php
new file mode 100755
index 0000000..cf3d8ae
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CatalogData.php
@@ -0,0 +1,25 @@
+ 'PageInfo',
+ 'edges' => 'CatalogEdge[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CatalogEdge.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CatalogEdge.php
new file mode 100755
index 0000000..d6277ff
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CatalogEdge.php
@@ -0,0 +1,20 @@
+ 'CatalogNode',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CatalogNode.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CatalogNode.php
new file mode 100755
index 0000000..a4be371
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CatalogNode.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'full_price' => '',
+ 'current_price' => '',
+ 'name' => 'string',
+ 'description' => 'string',
+ 'main_image_with_safe_fallback' => '',
+ 'retailer_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ChainingInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ChainingInfo.php
new file mode 100755
index 0000000..c75c306
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ChainingInfo.php
@@ -0,0 +1,20 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ChainingSuggestion.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ChainingSuggestion.php
new file mode 100755
index 0000000..19cd7f5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ChainingSuggestion.php
@@ -0,0 +1,587 @@
+ 'ChainingInfo',
+ 'profile_chaining_secondary_label' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Challenge.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Challenge.php
new file mode 100755
index 0000000..f2cc9d0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Challenge.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'api_path' => 'string',
+ 'hide_webview_header' => 'bool',
+ 'lock' => 'bool',
+ 'logout' => 'bool',
+ 'native_flow' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Channel.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Channel.php
new file mode 100755
index 0000000..ee1e56f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Channel.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'channel_type' => 'string',
+ 'title' => 'string',
+ 'header' => 'string',
+ 'media_count' => 'int',
+ 'media' => 'Item',
+ 'context' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CloseFriends.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CloseFriends.php
new file mode 100755
index 0000000..f14028c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CloseFriends.php
@@ -0,0 +1,35 @@
+ '',
+ 'users' => 'User[]',
+ 'big_list' => '',
+ 'page_size' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Collection.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Collection.php
new file mode 100755
index 0000000..c6c2a20
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Collection.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'collection_name' => 'string',
+ 'cover_media' => 'Item',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Comment.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Comment.php
new file mode 100755
index 0000000..daf4834
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Comment.php
@@ -0,0 +1,196 @@
+ 'string',
+ 'user_id' => 'string',
+ /*
+ * Unix timestamp (UTC) of when the comment was posted.
+ * Yes, this is the UTC timestamp even though it's not named "utc"!
+ */
+ 'created_at' => 'string',
+ /*
+ * WARNING: DO NOT USE THIS VALUE! It is NOT a real UTC timestamp.
+ * Instagram has messed up their values of "created_at" vs "created_at_utc".
+ * In `getComments()`, both have identical values. In `getCommentReplies()`,
+ * both are identical too. But in the `getComments()` "reply previews",
+ * their "created_at_utc" values are completely wrong (always +8 hours into
+ * the future, beyond the real UTC time). So just ignore this bad value!
+ * The real app only reads "created_at" for showing comment timestamps!
+ */
+ 'created_at_utc' => 'string',
+ 'bit_flags' => 'int',
+ 'user' => 'User',
+ 'pk' => 'string',
+ 'media_id' => 'string',
+ 'text' => 'string',
+ 'content_type' => 'string',
+ /*
+ * A number describing what type of comment this is. Should be compared
+ * against the `Comment::PARENT` and `Comment::CHILD` constants. All
+ * replies are of type `CHILD`, and all parents are of type `PARENT`.
+ */
+ 'type' => 'int',
+ 'comment_like_count' => 'int',
+ 'has_liked_comment' => 'bool',
+ 'has_translation' => 'bool',
+ 'did_report_as_spam' => 'bool',
+ 'share_enabled' => 'bool',
+ /*
+ * If this is a child in a thread, this is the ID of its parent thread.
+ */
+ 'parent_comment_id' => 'string',
+ /*
+ * Number of child comments in this comment thread.
+ */
+ 'child_comment_count' => 'int',
+ /*
+ * Previews of some of the child comments. Compare it to the child
+ * comment count. If there are more, you must request the comment thread.
+ */
+ 'preview_child_comments' => 'Comment[]',
+ /*
+ * Previews of users in very long comment threads.
+ */
+ 'other_preview_users' => 'User[]',
+ 'inline_composer_display_condition' => 'string',
+ /*
+ * When "has_more_tail_child_comments" is true, you can use the value
+ * in "next_max_child_cursor" as "max_id" parameter to load up to
+ * "num_tail_child_comments" older child-comments.
+ */
+ 'has_more_tail_child_comments' => 'bool',
+ 'next_max_child_cursor' => 'string',
+ 'num_tail_child_comments' => 'int',
+ /*
+ * When "has_more_head_child_comments" is true, you can use the value
+ * in "next_min_child_cursor" as "min_id" parameter to load up to
+ * "num_head_child_comments" newer child-comments.
+ */
+ 'has_more_head_child_comments' => 'bool',
+ 'next_min_child_cursor' => 'string',
+ 'num_head_child_comments' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CommentInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CommentInfo.php
new file mode 100755
index 0000000..d3c5c08
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CommentInfo.php
@@ -0,0 +1,60 @@
+ 'bool',
+ 'comment_threading_enabled' => 'bool',
+ 'has_more_comments' => 'bool',
+ 'max_num_visible_preview_comments' => 'int',
+ 'preview_comments' => '',
+ 'can_view_more_preview_comments' => 'bool',
+ 'comment_count' => 'int',
+ 'inline_composer_display_condition' => 'string',
+ 'inline_composer_imp_trigger_time' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CommentTranslations.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CommentTranslations.php
new file mode 100755
index 0000000..356a1ef
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CommentTranslations.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'translation' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Composer.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Composer.php
new file mode 100755
index 0000000..a43f9bf
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Composer.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'aspect_ratio_finished' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ContextualFilters.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ContextualFilters.php
new file mode 100755
index 0000000..e3ed7dc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ContextualFilters.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'filters' => '',
+ 'clauses' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CountdownSticker.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CountdownSticker.php
new file mode 100755
index 0000000..1ad21dd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CountdownSticker.php
@@ -0,0 +1,90 @@
+ 'string',
+ 'end_ts' => 'string',
+ 'text' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'text_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'start_background_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'end_background_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'digit_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'digit_card_color' => 'string',
+ 'following_enabled' => 'bool',
+ 'is_owner' => 'bool',
+ 'attribution' => '',
+ 'viewer_is_following' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Counts.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Counts.php
new file mode 100755
index 0000000..bc608e0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Counts.php
@@ -0,0 +1,55 @@
+ '',
+ 'requests' => '',
+ 'photos_of_you' => '',
+ 'usertags' => '',
+ 'comments' => '',
+ 'likes' => '',
+ 'comment_likes' => '',
+ 'campaign_notification' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CoverMedia.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CoverMedia.php
new file mode 100755
index 0000000..18a3f57
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/CoverMedia.php
@@ -0,0 +1,63 @@
+ 'string',
+ 'media_id' => 'string',
+ /*
+ * A number describing what type of media this is.
+ */
+ 'media_type' => 'int',
+ 'image_versions2' => 'Image_Versions2',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ 'cropped_image_version' => 'ImageCandidate',
+ 'crop_rect' => 'int[]',
+ 'full_image_version' => 'ImageCandidate',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Creative.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Creative.php
new file mode 100755
index 0000000..54dfb13
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Creative.php
@@ -0,0 +1,56 @@
+ 'Text',
+ 'content' => 'Text',
+ 'footer' => 'Text',
+ 'social_context' => 'Text',
+ 'content' => 'Text',
+ 'primary_action' => 'Action',
+ 'secondary_action' => 'Action',
+ 'dismiss_action' => '',
+ 'image' => 'Image',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DataGraph.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DataGraph.php
new file mode 100755
index 0000000..6be10e8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DataGraph.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'data_points' => 'DataPoints[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DataPoints.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DataPoints.php
new file mode 100755
index 0000000..1f7ea06
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DataPoints.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'value' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectCursor.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectCursor.php
new file mode 100755
index 0000000..5e97d19
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectCursor.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'cursor_thread_v2_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectExpiringSummary.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectExpiringSummary.php
new file mode 100755
index 0000000..842a717
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectExpiringSummary.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'timestamp' => 'string',
+ 'count' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectInbox.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectInbox.php
new file mode 100755
index 0000000..cbbf89e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectInbox.php
@@ -0,0 +1,50 @@
+ 'bool',
+ 'unseen_count' => 'int',
+ 'unseen_count_ts' => 'string', // Is a timestamp.
+ 'blended_inbox_enabled' => 'bool',
+ 'threads' => 'DirectThread[]',
+ 'next_cursor' => 'DirectCursor',
+ 'prev_cursor' => 'DirectCursor',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectLink.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectLink.php
new file mode 100755
index 0000000..e7ab3cf
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectLink.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'link_context' => 'LinkContext',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectMessageMetadata.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectMessageMetadata.php
new file mode 100755
index 0000000..873f463
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectMessageMetadata.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'item_id' => 'string',
+ 'timestamp' => 'string',
+ 'participant_ids' => 'string[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectRankedRecipient.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectRankedRecipient.php
new file mode 100755
index 0000000..0c1fe2c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectRankedRecipient.php
@@ -0,0 +1,25 @@
+ 'DirectThread',
+ 'user' => 'User',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectReaction.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectReaction.php
new file mode 100755
index 0000000..a85d38a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectReaction.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'timestamp' => 'string',
+ 'sender_id' => 'string',
+ 'client_context' => 'string',
+ 'reaction_status' => 'string',
+ 'node_type' => 'string',
+ 'item_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectReactions.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectReactions.php
new file mode 100755
index 0000000..a627772
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectReactions.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'likes' => 'DirectReaction[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectSeenItemPayload.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectSeenItemPayload.php
new file mode 100755
index 0000000..5971dca
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectSeenItemPayload.php
@@ -0,0 +1,25 @@
+ '',
+ 'timestamp' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectSendItemPayload.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectSendItemPayload.php
new file mode 100755
index 0000000..33541f7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectSendItemPayload.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'client_context' => 'string',
+ 'message' => 'string',
+ 'item_id' => 'string',
+ 'timestamp' => 'string',
+ 'thread_id' => 'string',
+ 'canonical' => 'bool',
+ 'participant_ids' => 'string[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThread.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThread.php
new file mode 100755
index 0000000..23e27f7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThread.php
@@ -0,0 +1,220 @@
+ 'string',
+ 'thread_v2_id' => 'string',
+ 'users' => 'User[]',
+ 'left_users' => 'User[]',
+ 'items' => 'DirectThreadItem[]',
+ 'last_activity_at' => 'string',
+ 'muted' => 'bool',
+ 'is_pin' => 'bool',
+ 'named' => 'bool',
+ 'canonical' => 'bool',
+ 'pending' => 'bool',
+ 'valued_request' => 'bool',
+ 'thread_type' => 'string',
+ 'viewer_id' => 'string',
+ 'thread_title' => 'string',
+ 'pending_score' => 'string',
+ 'vc_muted' => 'bool',
+ 'is_group' => 'bool',
+ 'reshare_send_count' => 'int',
+ 'reshare_receive_count' => 'int',
+ 'expiring_media_send_count' => 'int',
+ 'expiring_media_receive_count' => 'int',
+ 'inviter' => 'User',
+ 'has_older' => 'bool',
+ 'has_newer' => 'bool',
+ 'last_seen_at' => 'UnpredictableKeys\DirectThreadLastSeenAtUnpredictableContainer',
+ 'newest_cursor' => 'string',
+ 'oldest_cursor' => 'string',
+ 'is_spam' => 'bool',
+ 'last_permanent_item' => 'PermanentItem',
+ 'unseen_count' => '',
+ 'action_badge' => 'ActionBadge',
+ 'last_activity_at_secs' => '',
+ 'admin_user_ids' => 'string[]',
+ 'approval_required_for_new_members' => 'bool',
+ 'archived' => 'bool',
+ 'business_thread_folder' => 'int',
+ 'folder' => 'int',
+ 'input_mode' => 'int',
+ 'mentions_muted' => 'bool',
+ 'read_state' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItem.php
new file mode 100755
index 0000000..e840a45
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItem.php
@@ -0,0 +1,185 @@
+ 'string',
+ 'item_type' => '',
+ 'text' => 'string',
+ 'media_share' => 'Item',
+ 'preview_medias' => 'Item[]',
+ 'media' => 'DirectThreadItemMedia',
+ 'user_id' => 'string',
+ 'timestamp' => '',
+ 'client_context' => 'string',
+ 'hide_in_thread' => '',
+ 'action_log' => 'ActionLog',
+ 'link' => 'DirectLink',
+ 'reactions' => 'DirectReactions',
+ 'raven_media' => 'Item',
+ 'seen_user_ids' => 'string[]',
+ 'expiring_media_action_summary' => 'DirectExpiringSummary',
+ 'reel_share' => 'ReelShare',
+ 'placeholder' => 'Placeholder',
+ 'location' => 'Location',
+ 'like' => '',
+ 'live_video_share' => 'LiveVideoShare',
+ 'live_viewer_invite' => 'LiveViewerInvite',
+ 'profile' => 'User',
+ 'story_share' => 'StoryShare',
+ 'direct_media_share' => 'MediaShare',
+ 'video_call_event' => 'VideoCallEvent',
+ 'product_share' => 'ProductShare',
+ 'animated_media' => 'AnimatedMedia',
+ 'felix_share' => 'FelixShare',
+ 'voice_media' => 'VoiceMedia',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItemMedia.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItemMedia.php
new file mode 100755
index 0000000..8239650
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItemMedia.php
@@ -0,0 +1,54 @@
+ 'int',
+ 'image_versions2' => 'Image_Versions2',
+ 'video_versions' => 'VideoVersions[]',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ 'audio' => 'AudioContext',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadLastSeenAt.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadLastSeenAt.php
new file mode 100755
index 0000000..a91cc29
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadLastSeenAt.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'timestamp' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DismissCard.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DismissCard.php
new file mode 100755
index 0000000..b4c427d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/DismissCard.php
@@ -0,0 +1,50 @@
+ '',
+ 'image_url' => 'string',
+ 'title' => '',
+ 'message' => '',
+ 'button_text' => '',
+ 'camera_target' => '',
+ 'face_filter_id' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Edges.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Edges.php
new file mode 100755
index 0000000..3ff10b2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Edges.php
@@ -0,0 +1,30 @@
+ 'int',
+ 'time_range' => 'TimeRange',
+ 'node' => 'QPNode',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Effect.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Effect.php
new file mode 100755
index 0000000..0c83b2b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Effect.php
@@ -0,0 +1,50 @@
+ '',
+ 'id' => 'string',
+ 'effect_id' => 'string',
+ 'effect_file_id' => 'string',
+ 'asset_url' => 'string',
+ 'thumbnail_url' => 'string',
+ 'instructions' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/EligiblePromotions.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/EligiblePromotions.php
new file mode 100755
index 0000000..7f4f1ab
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/EligiblePromotions.php
@@ -0,0 +1,20 @@
+ 'Edges[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/EndOfFeedDemarcator.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/EndOfFeedDemarcator.php
new file mode 100755
index 0000000..2cd11fa
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/EndOfFeedDemarcator.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'title' => 'string',
+ 'subtitle' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Experiment.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Experiment.php
new file mode 100755
index 0000000..f3a1b6f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Experiment.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'group' => 'string',
+ 'additional_params' => '', // TODO: Only seen as [] empty array so far.
+ 'params' => 'Param[]',
+ 'logging_id' => 'string',
+ 'expired' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Explore.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Explore.php
new file mode 100755
index 0000000..4169d29
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Explore.php
@@ -0,0 +1,30 @@
+ '',
+ 'actor_id' => 'string',
+ 'source_token' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ExploreItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ExploreItem.php
new file mode 100755
index 0000000..4592a95
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ExploreItem.php
@@ -0,0 +1,35 @@
+ 'Item',
+ 'stories' => 'Stories',
+ 'channel' => 'Channel',
+ 'explore_item_info' => 'ExploreItemInfo',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ExploreItemInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ExploreItemInfo.php
new file mode 100755
index 0000000..626a3f9
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ExploreItemInfo.php
@@ -0,0 +1,40 @@
+ 'int',
+ 'total_num_columns' => 'int',
+ 'aspect_ratio' => 'int',
+ 'autoplay' => 'bool',
+ 'destination_view' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FaceModels.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FaceModels.php
new file mode 100755
index 0000000..8c8fc45
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FaceModels.php
@@ -0,0 +1,30 @@
+ '',
+ 'face_detect_model' => '',
+ 'pdm_multires' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FacebookUser.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FacebookUser.php
new file mode 100755
index 0000000..5c1c470
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FacebookUser.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'name' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FeedAysf.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FeedAysf.php
new file mode 100755
index 0000000..74d124c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FeedAysf.php
@@ -0,0 +1,75 @@
+ '',
+ 'uuid' => 'string',
+ 'view_all_text' => '',
+ 'feed_position' => '',
+ 'landing_site_title' => '',
+ 'is_dismissable' => '',
+ 'suggestions' => 'Suggestion[]',
+ 'should_refill' => '',
+ 'display_new_unit' => '',
+ 'fetch_user_details' => '',
+ 'title' => '',
+ 'activator' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FeedItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FeedItem.php
new file mode 100755
index 0000000..1d94fcd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FeedItem.php
@@ -0,0 +1,45 @@
+ 'Item',
+ 'stories_netego' => 'StoriesNetego',
+ 'ad4ad' => 'Ad4ad',
+ 'suggested_users' => 'SuggestedUsers',
+ 'end_of_feed_demarcator' => '',
+ 'ad_link_type' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FelixShare.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FelixShare.php
new file mode 100755
index 0000000..3832bb5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FelixShare.php
@@ -0,0 +1,25 @@
+ 'Item[]',
+ 'text' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FillItems.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FillItems.php
new file mode 100755
index 0000000..cd3de75
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FillItems.php
@@ -0,0 +1,20 @@
+ 'Item',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FollowersUnit.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FollowersUnit.php
new file mode 100755
index 0000000..5f62fd6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FollowersUnit.php
@@ -0,0 +1,60 @@
+ 'string',
+ 'followers_delta_from_last_week' => 'int',
+ 'gender_graph' => 'DataGraph',
+ 'all_followers_age_graph' => 'DataGraph',
+ 'men_followers_age_graph' => 'DataGraph',
+ 'women_followers_age_graph' => 'DataGraph',
+ 'followers_top_cities_graph' => 'DataGraph',
+ 'followers_top_countries_graph' => 'DataGraph',
+ 'week_daily_followers_graph' => 'DataGraph',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FormerUsername.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FormerUsername.php
new file mode 100755
index 0000000..a6a2a8b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FormerUsername.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'change_timestamp' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FormerUsernameInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FormerUsernameInfo.php
new file mode 100755
index 0000000..9bd2246
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FormerUsernameInfo.php
@@ -0,0 +1,20 @@
+ 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FriendshipStatus.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FriendshipStatus.php
new file mode 100755
index 0000000..7e5b7e0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FriendshipStatus.php
@@ -0,0 +1,70 @@
+ 'bool',
+ 'followed_by' => 'bool',
+ 'incoming_request' => 'bool',
+ 'outgoing_request' => 'bool',
+ 'is_private' => 'bool',
+ 'is_blocking_reel' => 'bool',
+ 'is_muting_reel' => 'bool',
+ 'is_restricted' => 'bool',
+ 'blocking' => 'bool',
+ 'muting' => 'bool',
+ 'is_bestie' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FullItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FullItem.php
new file mode 100755
index 0000000..6a35edb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/FullItem.php
@@ -0,0 +1,20 @@
+ 'Channel',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Gating.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Gating.php
new file mode 100755
index 0000000..fa76988
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Gating.php
@@ -0,0 +1,35 @@
+ '',
+ 'description' => '',
+ 'buttons' => '',
+ 'title' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/GenericMegaphone.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/GenericMegaphone.php
new file mode 100755
index 0000000..b749bdb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/GenericMegaphone.php
@@ -0,0 +1,85 @@
+ '',
+ 'title' => '',
+ 'message' => '',
+ 'dismissible' => '',
+ 'icon' => '',
+ 'buttons' => 'Button[]',
+ 'megaphone_version' => '',
+ 'button_layout' => '',
+ 'action_info' => '',
+ 'button_location' => '',
+ 'background_color' => '',
+ 'title_color' => '',
+ 'message_color' => '',
+ 'uuid' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/GraphData.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/GraphData.php
new file mode 100755
index 0000000..7be4441
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/GraphData.php
@@ -0,0 +1,50 @@
+ 'GraphNode',
+ '__typename' => 'string',
+ 'name' => 'string',
+ 'user' => 'ShadowInstagramUser',
+ 'error' => '',
+ 'catalog_items' => 'CatalogData',
+ 'me' => 'MeGraphData',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/GraphNode.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/GraphNode.php
new file mode 100755
index 0000000..f9ede7a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/GraphNode.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'name' => 'string',
+ 'catalog_items' => 'CatalogData',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Groups.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Groups.php
new file mode 100755
index 0000000..34bbea3
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Groups.php
@@ -0,0 +1,25 @@
+ '',
+ 'items' => 'Item[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Hashtag.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Hashtag.php
new file mode 100755
index 0000000..d91d751
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Hashtag.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'name' => 'string',
+ 'media_count' => 'int',
+ 'profile_pic_url' => 'string',
+ 'follow_status' => 'int',
+ 'following' => 'int',
+ 'allow_following' => 'int',
+ 'allow_muting_story' => 'bool',
+ 'related_tags' => '',
+ 'debug_info' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Headline.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Headline.php
new file mode 100755
index 0000000..d8ddc95
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Headline.php
@@ -0,0 +1,70 @@
+ 'string',
+ 'user' => 'User',
+ 'user_id' => 'string',
+ 'pk' => 'string',
+ 'text' => 'string',
+ 'type' => 'int',
+ 'created_at' => 'string',
+ 'created_at_utc' => 'string',
+ 'media_id' => 'string',
+ 'bit_flags' => 'int',
+ 'status' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/HiddenEntities.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/HiddenEntities.php
new file mode 100755
index 0000000..7e7996d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/HiddenEntities.php
@@ -0,0 +1,34 @@
+ '',
+ 'hashtag' => '',
+ 'place' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/HideReason.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/HideReason.php
new file mode 100755
index 0000000..3f84590
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/HideReason.php
@@ -0,0 +1,31 @@
+ 'string',
+ /*
+ * A computer string such as "NOT_RELEVANT" or "KEEP_SEEING_THIS".
+ */
+ 'reason' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/IOSLinks.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/IOSLinks.php
new file mode 100755
index 0000000..c80eb40
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/IOSLinks.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'canvasDocId' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/IabAutofillOptoutInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/IabAutofillOptoutInfo.php
new file mode 100755
index 0000000..70bcffc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/IabAutofillOptoutInfo.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'is_iab_autofill_optout' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Image.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Image.php
new file mode 100755
index 0000000..8219872
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Image.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'width' => 'int',
+ 'height' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ImageCandidate.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ImageCandidate.php
new file mode 100755
index 0000000..d3c2645
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ImageCandidate.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'width' => 'int',
+ 'height' => 'int',
+ 'estimated_scans_sizes' => 'int[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Image_Versions2.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Image_Versions2.php
new file mode 100755
index 0000000..a4e8e0f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Image_Versions2.php
@@ -0,0 +1,25 @@
+ 'ImageCandidate[]',
+ 'trace_token' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/In.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/In.php
new file mode 100755
index 0000000..61a0a80
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/In.php
@@ -0,0 +1,45 @@
+ 'float[]',
+ 'user' => 'User',
+ 'time_in_video' => '',
+ 'start_time_in_video_in_sec' => '',
+ 'duration_in_video_in_sec' => '',
+ 'product' => 'Product',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Injected.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Injected.php
new file mode 100755
index 0000000..00e63eb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Injected.php
@@ -0,0 +1,120 @@
+ 'string',
+ 'show_icon' => 'bool',
+ 'hide_label' => 'string',
+ 'invalidation' => '', // Only encountered as NULL.
+ 'is_demo' => 'bool',
+ 'view_tags' => '', // Only seen as [].
+ 'is_holdout' => 'bool',
+ 'is_leadgen_native_eligible' => 'bool',
+ 'tracking_token' => 'string',
+ 'show_ad_choices' => 'bool',
+ 'ad_title' => 'string',
+ 'about_ad_params' => 'string',
+ 'direct_share' => 'bool',
+ 'ad_id' => 'string',
+ 'display_viewability_eligible' => 'bool',
+ 'fb_page_url' => 'string',
+ 'hide_reasons_v2' => 'HideReason[]',
+ 'hide_flow_type' => 'int',
+ 'cookies' => 'string[]',
+ 'lead_gen_form_id' => 'string',
+ 'ads_debug_info' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/InlineFollow.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/InlineFollow.php
new file mode 100755
index 0000000..1513f10
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/InlineFollow.php
@@ -0,0 +1,30 @@
+ 'User',
+ 'following' => 'bool',
+ 'outgoing_request' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Insights.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Insights.php
new file mode 100755
index 0000000..376ee20
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Insights.php
@@ -0,0 +1,20 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Item.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Item.php
new file mode 100755
index 0000000..8890b89
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Item.php
@@ -0,0 +1,959 @@
+ 'string',
+ 'pk' => 'string',
+ 'id' => 'string',
+ 'device_timestamp' => 'string',
+ /*
+ * A number describing what type of media this is. Should be compared
+ * against the `Item::PHOTO`, `Item::VIDEO` and `Item::CAROUSEL` constants!
+ */
+ 'media_type' => 'int',
+ 'dynamic_item_id' => 'string',
+ 'code' => 'string',
+ 'client_cache_key' => 'string',
+ 'filter_type' => 'int',
+ 'product_type' => 'string',
+ 'nearly_complete_copyright_match' => 'bool',
+ 'media_cropping_info' => 'MediaCroppingInfo',
+ 'image_versions2' => 'Image_Versions2',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ 'caption_position' => 'float',
+ 'is_reel_media' => 'bool',
+ 'video_versions' => 'VideoVersions[]',
+ 'has_audio' => 'bool',
+ 'video_duration' => 'float',
+ 'user' => 'User',
+ 'can_see_insights_as_brand' => 'bool',
+ 'caption' => 'Caption',
+ 'headline' => 'Headline',
+ 'title' => 'string',
+ 'caption_is_edited' => 'bool',
+ 'photo_of_you' => 'bool',
+ 'fb_user_tags' => 'Usertag',
+ 'can_viewer_save' => 'bool',
+ 'has_viewer_saved' => 'bool',
+ 'organic_tracking_token' => 'string',
+ 'follow_hashtag_info' => 'Hashtag',
+ 'expiring_at' => 'string',
+ 'audience' => 'string',
+ 'is_dash_eligible' => 'int',
+ 'video_dash_manifest' => 'string',
+ 'number_of_qualities' => 'int',
+ 'video_codec' => 'string',
+ 'thumbnails' => 'Thumbnail',
+ 'can_reshare' => 'bool',
+ 'can_reply' => 'bool',
+ 'is_pride_media' => 'bool',
+ 'can_viewer_reshare' => 'bool',
+ 'visibility' => '',
+ 'attribution' => 'Attribution',
+ /*
+ * This is actually a float in the reply, but is always `.0`, so we cast
+ * it to an int instead to make the number easier to manage.
+ */
+ 'view_count' => 'int',
+ 'viewer_count' => 'int',
+ 'comment_count' => 'int',
+ 'can_view_more_preview_comments' => 'bool',
+ 'has_more_comments' => 'bool',
+ 'max_num_visible_preview_comments' => 'int',
+ /*
+ * Preview of comments via feed replies.
+ *
+ * If "has_more_comments" is FALSE, then this has ALL of the comments.
+ * Otherwise, you'll need to get all comments by querying the media.
+ */
+ 'preview_comments' => 'Comment[]',
+ /*
+ * Comments for the item.
+ *
+ * TODO: As of mid-2017, this field seems to no longer be used for
+ * timeline feed items? They now use "preview_comments" instead. But we
+ * won't delete it, since some other feed MAY use this property for ITS
+ * Item object.
+ */
+ 'comments' => 'Comment[]',
+ 'comments_disabled' => '',
+ 'reel_mentions' => 'ReelMention[]',
+ 'story_cta' => 'StoryCta[]',
+ 'next_max_id' => 'string',
+ 'carousel_media' => 'CarouselMedia[]',
+ 'carousel_media_type' => '',
+ 'carousel_media_count' => 'int',
+ 'likers' => 'User[]',
+ 'facepile_top_likers' => 'User[]',
+ 'like_count' => 'int',
+ 'preview' => 'string',
+ 'has_liked' => 'bool',
+ 'explore_context' => 'string',
+ 'explore_source_token' => 'string',
+ 'explore_hide_comments' => 'bool',
+ 'explore' => 'Explore',
+ 'impression_token' => 'string',
+ 'usertags' => 'Usertag',
+ 'media' => 'Media',
+ 'stories' => 'Stories',
+ 'top_likers' => 'string[]',
+ 'direct_reply_to_author_enabled' => 'bool',
+ 'suggested_users' => 'SuggestedUsers',
+ 'is_new_suggestion' => 'bool',
+ 'comment_likes_enabled' => 'bool',
+ 'location' => 'Location',
+ 'lat' => 'float',
+ 'lng' => 'float',
+ 'channel' => 'Channel',
+ 'gating' => 'Gating',
+ 'injected' => 'Injected',
+ 'placeholder' => 'Placeholder',
+ 'algorithm' => 'string',
+ 'connection_id' => 'string',
+ 'social_context' => 'string',
+ 'icon' => '',
+ 'media_ids' => 'string[]',
+ 'media_id' => 'string',
+ 'thumbnail_urls' => '',
+ 'large_urls' => '',
+ 'media_infos' => '',
+ 'value' => 'float',
+ 'followed_by' => 'bool',
+ 'collapse_comments' => 'bool',
+ 'link' => 'string',
+ 'link_text' => 'string',
+ 'link_hint_text' => 'string',
+ 'iTunesItem' => '',
+ 'ad_header_style' => 'int',
+ 'ad_metadata' => 'AdMetadata[]',
+ 'ad_action' => 'string',
+ 'ad_link_type' => 'int',
+ 'dr_ad_type' => 'int',
+ 'android_links' => 'AndroidLinks[]',
+ 'ios_links' => 'IOSLinks[]',
+ 'iab_autofill_optout_info' => 'IabAutofillOptoutInfo',
+ 'force_overlay' => 'bool',
+ 'hide_nux_text' => 'bool',
+ 'overlay_text' => 'string',
+ 'overlay_title' => 'string',
+ 'overlay_subtitle' => 'string',
+ 'fb_page_url' => 'string',
+ 'playback_duration_secs' => '',
+ 'url_expire_at_secs' => '',
+ 'is_sidecar_child' => '',
+ 'comment_threading_enabled' => 'bool',
+ 'cover_media' => 'CoverMedia',
+ 'saved_collection_ids' => 'string[]',
+ 'boosted_status' => '',
+ 'boost_unavailable_reason' => '',
+ 'viewers' => 'User[]',
+ 'viewer_cursor' => '',
+ 'total_viewer_count' => 'int',
+ 'multi_author_reel_names' => '',
+ 'screenshotter_user_ids' => '',
+ 'reel_share' => 'ReelShare',
+ 'organic_post_id' => 'string',
+ 'sponsor_tags' => 'User[]',
+ 'story_poll_voter_infos' => '',
+ 'imported_taken_at' => '',
+ 'lead_gen_form_id' => 'string',
+ 'ad_id' => 'string',
+ 'actor_fbid' => 'string',
+ 'is_ad4ad' => '',
+ 'commenting_disabled_for_viewer' => '',
+ 'is_seen' => '',
+ 'story_events' => '',
+ 'story_hashtags' => 'StoryHashtag[]',
+ 'story_polls' => '',
+ 'story_feed_media' => '',
+ 'story_sound_on' => '',
+ 'creative_config' => '',
+ 'story_app_attribution' => 'StoryAppAttribution',
+ 'story_locations' => 'StoryLocation[]',
+ 'story_sliders' => '',
+ 'story_friend_lists' => '',
+ 'story_product_items' => '',
+ 'story_questions' => 'StoryQuestions[]',
+ 'story_question_responder_infos' => 'StoryQuestionResponderInfos[]',
+ 'story_countdowns' => 'StoryCountdowns[]',
+ 'story_music_stickers' => '',
+ 'supports_reel_reactions' => 'bool',
+ 'show_one_tap_fb_share_tooltip' => 'bool',
+ 'has_shared_to_fb' => 'bool',
+ 'main_feed_carousel_starting_media_id' => 'string',
+ 'main_feed_carousel_has_unseen_cover_media' => 'bool',
+ 'inventory_source' => 'string',
+ 'is_eof' => 'bool',
+ 'top_followers' => 'string[]',
+ 'top_followers_count' => 'int',
+ 'follower_count' => 'int',
+ 'post_count' => 'int',
+ 'video_subtitles_uri' => 'string',
+ 'story_is_saved_to_archive' => 'bool',
+ 'timezone_offset' => 'int',
+ 'xpost_deny_reason' => 'string',
+ 'product_tags' => 'ProductTags',
+ 'inline_composer_display_condition' => 'string',
+ 'inline_composer_imp_trigger_time' => 'int',
+ 'highlight_reel_ids' => 'string[]',
+ 'total_screenshot_count' => 'int',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'dominant_color' => 'string',
+ 'audio' => 'AudioContext',
+ 'story_quizs' => 'StoryQuizs[]',
+ 'story_quiz_participant_infos' => 'StoryQuizParticipantInfo[]',
+ ];
+
+ /**
+ * Get the web URL for this media item.
+ *
+ * @return string
+ */
+ public function getItemUrl()
+ {
+ return sprintf('https://www.instagram.com/p/%s/', $this->_getProperty('code'));
+ }
+
+ /**
+ * Checks whether this media item is an advertisement.
+ *
+ * @return bool
+ */
+ public function isAd()
+ {
+ return $this->_getProperty('dr_ad_type') !== null;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LayoutContent.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LayoutContent.php
new file mode 100755
index 0000000..f9946c5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LayoutContent.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'related' => 'Tag[]',
+ 'medias' => 'SectionMedia[]',
+ 'feed_type' => 'string',
+ 'fill_items' => 'FillItems[]',
+ 'explore_item_info' => 'ExploreItemInfo',
+ 'tabs_info' => 'TabsInfo',
+ 'full_item' => 'FullItem',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Link.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Link.php
new file mode 100755
index 0000000..3f7ad4c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Link.php
@@ -0,0 +1,45 @@
+ 'int',
+ 'end' => 'int',
+ 'id' => 'string',
+ 'type' => 'string',
+ 'text' => 'string',
+ 'link_context' => 'LinkContext',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LinkContext.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LinkContext.php
new file mode 100755
index 0000000..5b87972
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LinkContext.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'link_title' => 'string',
+ 'link_summary' => 'string',
+ 'link_image_url' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LiveComment.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LiveComment.php
new file mode 100755
index 0000000..9d2350f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LiveComment.php
@@ -0,0 +1,30 @@
+ 'Comment',
+ 'offset' => '',
+ 'event' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LiveVideoShare.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LiveVideoShare.php
new file mode 100755
index 0000000..2812b15
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LiveVideoShare.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'broadcast' => 'Broadcast',
+ 'video_offset' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LiveViewerInvite.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LiveViewerInvite.php
new file mode 100755
index 0000000..97d8ca4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LiveViewerInvite.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'broadcast' => 'Broadcast',
+ 'title' => 'string',
+ 'message' => 'string',
+ 'is_linked' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Location.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Location.php
new file mode 100755
index 0000000..ea2ae08
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Location.php
@@ -0,0 +1,157 @@
+ 'string',
+ 'external_id_source' => 'string',
+ 'external_source' => 'string',
+ 'address' => 'string',
+ 'lat' => 'float',
+ 'lng' => 'float',
+ 'external_id' => 'string',
+ 'facebook_places_id' => 'string',
+ 'city' => 'string',
+ 'pk' => 'string',
+ 'short_name' => 'string',
+ 'facebook_events_id' => 'string',
+ 'start_time' => '',
+ 'end_time' => '',
+ 'location_dict' => 'Location',
+ 'type' => '',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_username' => 'string',
+ 'time_granularity' => '',
+ 'timezone' => '',
+ /*
+ * Country number such as int(398), but it has no relation to actual
+ * country codes, so the number is useless...
+ */
+ 'country' => 'int',
+ /*
+ * Regular unix timestamp of when the location was created.
+ */
+ 'created_at' => 'string',
+ /*
+ * Some kind of internal number to signify what type of event a special
+ * location (such as a festival) is. We've only seen this with int(0).
+ */
+ 'event_category' => 'int',
+ /*
+ * 64-bit integer with the facebook places ID for the location.
+ */
+ 'place_fbid' => 'string',
+ /*
+ * Human-readable name of the facebook place for the location.
+ */
+ 'place_name' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LocationItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LocationItem.php
new file mode 100755
index 0000000..38feb34
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/LocationItem.php
@@ -0,0 +1,35 @@
+ '',
+ 'subtitle' => '',
+ 'location' => 'Location',
+ 'title' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MeGraphData.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MeGraphData.php
new file mode 100755
index 0000000..1dac9dd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MeGraphData.php
@@ -0,0 +1,25 @@
+ 'CatalogData',
+ 'id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Media.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Media.php
new file mode 100755
index 0000000..08797c4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Media.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'id' => 'string',
+ 'user' => 'User',
+ 'expiring_at' => '',
+ 'comment_threading_enabled' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaCroppingInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaCroppingInfo.php
new file mode 100755
index 0000000..ab11ef9
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaCroppingInfo.php
@@ -0,0 +1,20 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaData.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaData.php
new file mode 100755
index 0000000..902c9da
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaData.php
@@ -0,0 +1,43 @@
+ 'Image_Versions2',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ /*
+ * A number describing what type of media this is.
+ */
+ 'media_type' => 'int',
+ 'video_versions' => 'VideoVersions[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaInsights.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaInsights.php
new file mode 100755
index 0000000..ac0bf8f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaInsights.php
@@ -0,0 +1,50 @@
+ 'string[]',
+ 'impression_count' => 'int',
+ 'engagement_count' => 'int',
+ 'avg_engagement_count' => 'int',
+ 'comment_count' => 'int',
+ 'save_count' => 'int',
+ 'like_count' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaShare.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaShare.php
new file mode 100755
index 0000000..8201084
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/MediaShare.php
@@ -0,0 +1,25 @@
+ 'Item',
+ 'text' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Megaphone.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Megaphone.php
new file mode 100755
index 0000000..7068983
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Megaphone.php
@@ -0,0 +1,20 @@
+ 'GenericMegaphone',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Nametag.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Nametag.php
new file mode 100755
index 0000000..1e20417
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Nametag.php
@@ -0,0 +1,40 @@
+ 'int',
+ 'gradient' => 'int',
+ 'emoji' => 'string',
+ 'emoji_color' => 'string',
+ 'selfie_sticker' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Owner.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Owner.php
new file mode 100755
index 0000000..51b434f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Owner.php
@@ -0,0 +1,60 @@
+ '',
+ 'pk' => 'string',
+ 'name' => 'string',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_username' => 'string',
+ 'short_name' => 'string',
+ 'lat' => 'float',
+ 'lng' => 'float',
+ 'location_dict' => 'Location',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PageInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PageInfo.php
new file mode 100755
index 0000000..2206725
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PageInfo.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'has_next_page' => 'bool',
+ 'has_previous_page' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Param.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Param.php
new file mode 100755
index 0000000..9e6ad05
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Param.php
@@ -0,0 +1,25 @@
+ '',
+ 'value' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Participants.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Participants.php
new file mode 100755
index 0000000..4474261
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Participants.php
@@ -0,0 +1,30 @@
+ 'User',
+ 'answer' => 'int',
+ 'ts' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PermanentItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PermanentItem.php
new file mode 100755
index 0000000..dea7ca1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PermanentItem.php
@@ -0,0 +1,90 @@
+ 'string',
+ 'user_id' => 'string',
+ 'timestamp' => 'string',
+ 'item_type' => 'string',
+ 'profile' => 'User',
+ 'text' => 'string',
+ 'location' => 'Location',
+ 'like' => '',
+ 'media' => 'MediaData',
+ 'link' => 'Link',
+ 'media_share' => 'Item',
+ 'reel_share' => 'ReelShare',
+ 'client_context' => 'string',
+ 'live_video_share' => 'LiveVideoShare',
+ 'live_viewer_invite' => 'LiveViewerInvite',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PhoneVerificationSettings.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PhoneVerificationSettings.php
new file mode 100755
index 0000000..211b0b1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PhoneVerificationSettings.php
@@ -0,0 +1,35 @@
+ 'int',
+ 'max_sms_count' => 'int',
+ 'robocall_count_down_time_sec' => 'int',
+ 'robocall_after_max_sms' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Placeholder.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Placeholder.php
new file mode 100755
index 0000000..515d0c5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Placeholder.php
@@ -0,0 +1,30 @@
+ 'bool',
+ 'title' => 'string',
+ 'message' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PostLive.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PostLive.php
new file mode 100755
index 0000000..3266000
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PostLive.php
@@ -0,0 +1,20 @@
+ 'PostLiveItem[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PostLiveItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PostLiveItem.php
new file mode 100755
index 0000000..afa9b5f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PostLiveItem.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'user' => 'User',
+ 'broadcasts' => 'Broadcast[]',
+ 'peak_viewer_count' => 'int',
+ 'last_seen_broadcast_ts' => '',
+ 'can_reply' => '',
+ 'ranked_position' => '',
+ 'seen_ranked_position' => '',
+ 'muted' => '',
+ 'can_reshare' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Prefill.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Prefill.php
new file mode 100755
index 0000000..8384250
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Prefill.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'candidates' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PrimaryCountryInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PrimaryCountryInfo.php
new file mode 100755
index 0000000..9ef45f8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PrimaryCountryInfo.php
@@ -0,0 +1,30 @@
+ 'bool',
+ 'has_country' => 'bool',
+ 'country_name' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Product.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Product.php
new file mode 100755
index 0000000..67d5333
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Product.php
@@ -0,0 +1,84 @@
+ 'string',
+ 'price' => 'string',
+ 'current_price' => 'string',
+ 'full_price' => 'string',
+ 'product_id' => 'string',
+ 'has_viewer_saved' => 'bool',
+ 'description' => 'string',
+ 'main_image' => 'ProductImage',
+ 'thumbnail_image' => 'ProductImage',
+ 'product_images' => 'ProductImage[]',
+ 'external_url' => 'string',
+ 'checkout_style' => 'string',
+ 'review_status' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ProductImage.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ProductImage.php
new file mode 100755
index 0000000..1fea73f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ProductImage.php
@@ -0,0 +1,20 @@
+ 'Image_Versions2',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ProductShare.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ProductShare.php
new file mode 100755
index 0000000..df31acf
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ProductShare.php
@@ -0,0 +1,30 @@
+ 'Item',
+ 'text' => 'string',
+ 'product' => 'Product',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ProductTags.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ProductTags.php
new file mode 100755
index 0000000..42cab36
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ProductTags.php
@@ -0,0 +1,20 @@
+ 'In[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PromotionsUnit.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PromotionsUnit.php
new file mode 100755
index 0000000..822a013
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PromotionsUnit.php
@@ -0,0 +1,20 @@
+ 'SummaryPromotions',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PushSettings.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PushSettings.php
new file mode 100755
index 0000000..f5a9ac1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/PushSettings.php
@@ -0,0 +1,45 @@
+ '',
+ 'eligible' => '',
+ 'title' => '',
+ 'example' => '',
+ 'options' => '',
+ 'checked' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPData.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPData.php
new file mode 100755
index 0000000..5034e76
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPData.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'data' => 'QPViewerData',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPExtraInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPExtraInfo.php
new file mode 100755
index 0000000..f2e2cc7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPExtraInfo.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'extra_info' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPNode.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPNode.php
new file mode 100755
index 0000000..6ccf4b6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPNode.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'promotion_id' => 'string',
+ 'max_impressions' => 'int',
+ 'triggers' => 'string[]',
+ 'contextual_filters' => 'ContextualFilters',
+ 'template' => 'Template',
+ 'creatives' => 'Creative[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPSurface.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPSurface.php
new file mode 100755
index 0000000..75c50f3
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPSurface.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'cooldown' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPViewerData.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPViewerData.php
new file mode 100755
index 0000000..5c2f755
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QPViewerData.php
@@ -0,0 +1,20 @@
+ 'Viewer',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QueryResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QueryResponse.php
new file mode 100755
index 0000000..44eef1b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QueryResponse.php
@@ -0,0 +1,20 @@
+ 'ShadowInstagramUser',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QuestionSticker.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QuestionSticker.php
new file mode 100755
index 0000000..a2718a0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QuestionSticker.php
@@ -0,0 +1,56 @@
+ 'string',
+ 'question' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'text_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'background_color' => 'string',
+ 'viewer_can_interact' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'question_type' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QuizSticker.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QuizSticker.php
new file mode 100755
index 0000000..6bce739
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/QuizSticker.php
@@ -0,0 +1,70 @@
+ 'string',
+ 'quiz_id' => 'string',
+ 'question' => 'string',
+ 'tallies' => 'Tallies[]',
+ 'correct_answer' => 'int',
+ 'viewer_can_answer' => 'bool',
+ 'finished' => 'bool',
+ 'text_color' => 'string',
+ 'start_background_color' => 'string',
+ 'end_background_color' => 'string',
+ 'viewer_answer' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Ranking.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Ranking.php
new file mode 100755
index 0000000..8fc647a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Ranking.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'score_map' => '',
+ 'expiration_ms' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Reel.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Reel.php
new file mode 100755
index 0000000..7dcc069
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Reel.php
@@ -0,0 +1,107 @@
+ 'string',
+ /*
+ * Unix "taken_at" timestamp of the newest item in their story reel.
+ */
+ 'latest_reel_media' => 'string',
+ /*
+ * The "taken_at" timestamp of the last story media you have seen for
+ * that user (the current reel's user). Defaults to `0` (not seen).
+ */
+ 'seen' => 'string',
+ 'can_reply' => 'bool',
+ 'can_reshare' => 'bool',
+ 'reel_type' => 'string',
+ 'cover_media' => 'CoverMedia',
+ 'user' => 'User',
+ 'items' => 'Item[]',
+ 'ranked_position' => 'string',
+ 'title' => 'string',
+ 'seen_ranked_position' => 'string',
+ 'expiring_at' => 'string',
+ 'has_besties_media' => 'bool', // Uses int(0) for false and 1 for true.
+ 'location' => 'Location',
+ 'prefetch_count' => 'int',
+ 'broadcast' => 'Broadcast',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ReelMention.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ReelMention.php
new file mode 100755
index 0000000..d3f0192
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ReelMention.php
@@ -0,0 +1,65 @@
+ 'User',
+ 'is_hidden' => 'int',
+ 'display_type' => 'string',
+ 'is_sticker' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ReelShare.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ReelShare.php
new file mode 100755
index 0000000..8dcceca
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ReelShare.php
@@ -0,0 +1,70 @@
+ 'Item[]',
+ 'story_ranking_token' => 'string',
+ 'broadcasts' => '',
+ 'sticker_version' => 'int',
+ 'text' => 'string',
+ 'type' => 'string',
+ 'is_reel_persisted' => 'bool',
+ 'reel_owner_id' => 'string',
+ 'reel_type' => 'string',
+ 'media' => 'Item',
+ 'mentioned_user_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Related.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Related.php
new file mode 100755
index 0000000..4658f02
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Related.php
@@ -0,0 +1,30 @@
+ '',
+ 'id' => 'string',
+ 'type' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Responder.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Responder.php
new file mode 100755
index 0000000..7d23dfb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Responder.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'has_shared_response' => 'bool',
+ 'id' => 'string',
+ 'user' => 'User',
+ 'ts' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/RewriteRule.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/RewriteRule.php
new file mode 100755
index 0000000..d6a5302
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/RewriteRule.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'replacer' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SavedFeedItem.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SavedFeedItem.php
new file mode 100755
index 0000000..04978b6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SavedFeedItem.php
@@ -0,0 +1,20 @@
+ 'Item',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Section.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Section.php
new file mode 100755
index 0000000..f60678b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Section.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'title' => 'string',
+ 'items' => 'Item[]',
+ 'layout_type' => 'string',
+ 'layout_content' => 'LayoutContent',
+ 'feed_type' => 'string',
+ 'explore_item_info' => 'ExploreItemInfo',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SectionMedia.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SectionMedia.php
new file mode 100755
index 0000000..6c6130d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SectionMedia.php
@@ -0,0 +1,20 @@
+ 'Item',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ServerDataInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ServerDataInfo.php
new file mode 100755
index 0000000..f33a837
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ServerDataInfo.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'nonce' => 'string',
+ 'conferenceName' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ShadowInstagramUser.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ShadowInstagramUser.php
new file mode 100755
index 0000000..c4553eb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/ShadowInstagramUser.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'instagram_user_id' => 'string',
+ 'followers_count' => 'int',
+ 'username' => 'string',
+ 'profile_picture' => 'Image',
+ 'business_manager' => 'BusinessManager',
+ 'error' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SharePrefillEntities.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SharePrefillEntities.php
new file mode 100755
index 0000000..4f4ec65
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SharePrefillEntities.php
@@ -0,0 +1,20 @@
+ 'DirectThread',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SharedFollower.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SharedFollower.php
new file mode 100755
index 0000000..f505d33
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SharedFollower.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'username' => 'string',
+ 'full_name' => 'string',
+ 'is_private' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_id' => 'string',
+ 'is_verified' => 'bool',
+ 'has_anonymous_profile_picture' => 'bool',
+ 'reel_auto_archive' => 'string',
+ 'overlap_score' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SharedFollowerAccountsInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SharedFollowerAccountsInfo.php
new file mode 100755
index 0000000..f239746
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SharedFollowerAccountsInfo.php
@@ -0,0 +1,20 @@
+ 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Slot.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Slot.php
new file mode 100755
index 0000000..cfae147
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Slot.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'cooldown' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StaticStickers.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StaticStickers.php
new file mode 100755
index 0000000..479622d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StaticStickers.php
@@ -0,0 +1,30 @@
+ '',
+ 'id' => 'string',
+ 'stickers' => 'Stickers[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StepData.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StepData.php
new file mode 100755
index 0000000..1082861
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StepData.php
@@ -0,0 +1,80 @@
+ 'string',
+ 'phone_number' => 'string',
+ 'phone_number_formatted' => 'string',
+ 'email' => 'string',
+ 'fb_access_token' => 'string',
+ 'big_blue_token' => 'string',
+ 'google_oauth_token' => 'string',
+ 'security_code' => 'string',
+ 'sms_resend_delay' => 'int',
+ 'resend_delay' => 'int',
+ 'contact_point' => 'string',
+ 'form_type' => 'string',
+ 'phone_number_preview' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Stickers.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Stickers.php
new file mode 100755
index 0000000..fdc47e5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Stickers.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'tray_image_width_ratio' => '',
+ 'image_height' => '',
+ 'image_width_ratio' => '',
+ 'type' => '',
+ 'image_width' => '',
+ 'name' => '',
+ 'image_url' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Stories.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Stories.php
new file mode 100755
index 0000000..382b5d6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Stories.php
@@ -0,0 +1,35 @@
+ '',
+ 'tray' => 'StoryTray[]',
+ 'id' => 'string',
+ 'top_live' => 'TopLive',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoriesNetego.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoriesNetego.php
new file mode 100755
index 0000000..e4f0ac7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoriesNetego.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'hide_unit_if_seen' => 'string',
+ 'id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Story.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Story.php
new file mode 100755
index 0000000..3250e2c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Story.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'counts' => 'Counts',
+ 'args' => 'Args',
+ 'type' => 'int',
+ 'story_type' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryAppAttribution.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryAppAttribution.php
new file mode 100755
index 0000000..04df311
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryAppAttribution.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'app_icon_url' => 'string',
+ 'content_url' => 'string',
+ 'id' => 'string',
+ 'link' => 'string',
+ 'name' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryCountdowns.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryCountdowns.php
new file mode 100755
index 0000000..6c79803
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryCountdowns.php
@@ -0,0 +1,60 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float',
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ 'is_hidden' => 'int',
+ 'countdown_sticker' => 'CountdownSticker',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryCta.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryCta.php
new file mode 100755
index 0000000..56884e9
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryCta.php
@@ -0,0 +1,25 @@
+ 'AndroidLinks[]',
+ 'felix_deep_link' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryHashtag.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryHashtag.php
new file mode 100755
index 0000000..f0c50a1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryHashtag.php
@@ -0,0 +1,70 @@
+ 'Hashtag',
+ 'attribution' => 'string',
+ 'custom_title' => 'string',
+ 'is_hidden' => 'int',
+ 'is_sticker' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryLocation.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryLocation.php
new file mode 100755
index 0000000..bc6bdfc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryLocation.php
@@ -0,0 +1,65 @@
+ 'Location',
+ 'attribution' => 'string',
+ 'is_hidden' => 'int',
+ 'is_sticker' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestionResponderInfos.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestionResponderInfos.php
new file mode 100755
index 0000000..0a24469
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestionResponderInfos.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'question' => 'string',
+ 'question_type' => 'string',
+ 'background_color' => 'string',
+ 'text_color' => 'string',
+ 'responders' => 'Responder[]',
+ 'max_id' => '',
+ 'more_available' => 'bool',
+ 'question_response_count' => 'int',
+ 'latest_question_response_time' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestions.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestions.php
new file mode 100755
index 0000000..83fbdcc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestions.php
@@ -0,0 +1,60 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float',
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ 'is_hidden' => 'int',
+ 'question_sticker' => 'QuestionSticker',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizParticipantInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizParticipantInfo.php
new file mode 100755
index 0000000..3f68f45
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizParticipantInfo.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'participants' => 'Participants[]',
+ 'max_id' => '',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizs.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizs.php
new file mode 100755
index 0000000..192aec5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizs.php
@@ -0,0 +1,65 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float',
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ 'is_hidden' => 'int',
+ 'is_sticker' => 'int',
+ 'quiz_sticker' => 'QuizSticker',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryShare.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryShare.php
new file mode 100755
index 0000000..6b1724d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryShare.php
@@ -0,0 +1,40 @@
+ 'Item',
+ 'text' => 'string',
+ 'title' => 'string',
+ 'message' => 'string',
+ 'is_linked' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryTray.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryTray.php
new file mode 100755
index 0000000..d7a2ff1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryTray.php
@@ -0,0 +1,162 @@
+ 'string',
+ 'items' => 'Item[]',
+ 'hide_from_feed_unit' => 'bool',
+ 'media_ids' => 'string[]',
+ 'has_pride_media' => 'bool',
+ 'user' => 'User',
+ 'can_reply' => '',
+ 'expiring_at' => '',
+ 'seen_ranked_position' => 'string',
+ /*
+ * The "taken_at" timestamp of the last story media you have seen for
+ * that user (the current tray's user). Defaults to `0` (not seen).
+ */
+ 'seen' => 'string',
+ /*
+ * Unix "taken_at" timestamp of the newest item in their story reel.
+ */
+ 'latest_reel_media' => 'string',
+ 'ranked_position' => 'string',
+ 'is_nux' => '',
+ 'show_nux_tooltip' => '',
+ 'muted' => '',
+ 'prefetch_count' => 'int',
+ 'location' => 'Location',
+ 'source_token' => '',
+ 'owner' => 'Owner',
+ 'nux_id' => 'string',
+ 'dismiss_card' => 'DismissCard',
+ 'can_reshare' => '',
+ 'has_besties_media' => 'bool',
+ 'reel_type' => 'string',
+ 'unique_integer_reel_id' => 'string',
+ 'cover_media' => 'CoverMedia',
+ 'title' => 'string',
+ 'media_count' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryTvChannel.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryTvChannel.php
new file mode 100755
index 0000000..99d2248
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/StoryTvChannel.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'items' => 'Item[]',
+ 'title' => 'string',
+ 'type' => 'string',
+ 'max_id' => 'string',
+ 'more_available' => 'bool',
+ 'seen_state' => 'mixed',
+ 'user_dict' => 'User',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Subscription.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Subscription.php
new file mode 100755
index 0000000..5a63e28
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Subscription.php
@@ -0,0 +1,35 @@
+ '',
+ 'url' => 'string',
+ 'sequence' => '',
+ 'auth' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Suggested.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Suggested.php
new file mode 100755
index 0000000..e854bea
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Suggested.php
@@ -0,0 +1,40 @@
+ 'int',
+ 'hashtag' => 'Hashtag',
+ 'user' => 'User',
+ 'place' => 'LocationItem',
+ 'client_time' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SuggestedUsers.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SuggestedUsers.php
new file mode 100755
index 0000000..862f46b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SuggestedUsers.php
@@ -0,0 +1,75 @@
+ 'string',
+ 'view_all_text' => '',
+ 'title' => '',
+ 'auto_dvance' => '',
+ 'type' => '',
+ 'tracking_token' => 'string',
+ 'landing_site_type' => '',
+ 'landing_site_title' => '',
+ 'upsell_fb_pos' => '',
+ 'suggestions' => 'Suggestion[]',
+ 'suggestion_cards' => 'SuggestionCard[]',
+ 'netego_type' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Suggestion.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Suggestion.php
new file mode 100755
index 0000000..0ee7a32
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Suggestion.php
@@ -0,0 +1,75 @@
+ '',
+ 'social_context' => 'string',
+ 'algorithm' => 'string',
+ 'thumbnail_urls' => 'string[]',
+ 'value' => 'float',
+ 'caption' => '',
+ 'user' => 'User',
+ 'large_urls' => 'string[]',
+ 'media_ids' => '',
+ 'icon' => '',
+ 'is_new_suggestion' => 'bool',
+ 'uuid' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SuggestionCard.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SuggestionCard.php
new file mode 100755
index 0000000..78903ba
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SuggestionCard.php
@@ -0,0 +1,30 @@
+ 'UserCard',
+ 'upsell_ci_card' => '',
+ 'upsell_fbc_card' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SummaryPromotions.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SummaryPromotions.php
new file mode 100755
index 0000000..33947ad
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SummaryPromotions.php
@@ -0,0 +1,25 @@
+ 'BusinessEdge[]',
+ 'page_info' => 'PageInfo',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SupportedCapabilities.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SupportedCapabilities.php
new file mode 100755
index 0000000..2c682d6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SupportedCapabilities.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'value' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Surface.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Surface.php
new file mode 100755
index 0000000..2bfbdc1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Surface.php
@@ -0,0 +1,39 @@
+ '',
+ 'rank_token' => 'string',
+ 'ttl_secs' => 'int',
+ 'name' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SystemControl.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SystemControl.php
new file mode 100755
index 0000000..b5c5da0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/SystemControl.php
@@ -0,0 +1,30 @@
+ 'int',
+ 'upload_time_period_sec' => 'int',
+ 'upload_bytes_per_update' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TVChannel.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TVChannel.php
new file mode 100755
index 0000000..5824b72
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TVChannel.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'title' => 'string',
+ 'id' => 'string',
+ 'items' => 'Item[]',
+ 'more_available' => 'bool',
+ 'max_id' => 'string',
+ 'seen_state' => '',
+ 'user_dict' => 'User',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TVSearchResult.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TVSearchResult.php
new file mode 100755
index 0000000..a512938
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TVSearchResult.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'User' => 'User',
+ 'channel' => 'TVChannel',
+ 'num_results' => 'int',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Tab.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Tab.php
new file mode 100755
index 0000000..975b258
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Tab.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'title' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TabsInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TabsInfo.php
new file mode 100755
index 0000000..b636fc4
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TabsInfo.php
@@ -0,0 +1,25 @@
+ 'Tab[]',
+ 'selected' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Tag.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Tag.php
new file mode 100755
index 0000000..42af922
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Tag.php
@@ -0,0 +1,105 @@
+ 'string',
+ 'name' => 'string',
+ 'media_count' => 'int',
+ 'type' => 'string',
+ 'follow_status' => '',
+ 'following' => '',
+ 'allow_following' => '',
+ 'allow_muting_story' => '',
+ 'profile_pic_url' => '',
+ 'non_violating' => '',
+ 'related_tags' => '',
+ 'subtitle' => '',
+ 'social_context' => '',
+ 'social_context_profile_links' => '',
+ 'show_follow_drop_down' => '',
+ 'follow_button_text' => '',
+ 'debug_info' => '',
+ 'search_result_subtitle' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Tallies.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Tallies.php
new file mode 100755
index 0000000..58ddedd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Tallies.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'count' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Template.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Template.php
new file mode 100755
index 0000000..750250d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Template.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'parameters' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Text.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Text.php
new file mode 100755
index 0000000..9515d3a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Text.php
@@ -0,0 +1,20 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Thumbnail.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Thumbnail.php
new file mode 100755
index 0000000..4dc41a6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Thumbnail.php
@@ -0,0 +1,65 @@
+ 'float',
+ 'thumbnail_width' => 'int',
+ 'thumbnail_height' => 'int',
+ 'thumbnail_duration' => 'float',
+ 'sprite_urls' => 'string[]',
+ 'thumbnails_per_row' => 'int',
+ 'max_thumbnails_per_sprite' => 'int',
+ 'sprite_width' => 'int',
+ 'sprite_height' => 'int',
+ 'rendered_width' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TimeRange.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TimeRange.php
new file mode 100755
index 0000000..f00baef
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TimeRange.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'end' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Token.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Token.php
new file mode 100755
index 0000000..6b1ef68
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Token.php
@@ -0,0 +1,82 @@
+ 'string',
+ 'carrier_id' => 'int',
+ 'ttl' => 'int',
+ 'features' => '',
+ 'request_time' => 'string',
+ 'token_hash' => 'string',
+ 'rewrite_rules' => 'RewriteRule[]',
+ 'enabled_wallet_defs_keys' => '',
+ 'deadline' => 'string',
+ 'zero_cms_fetch_interval_seconds' => 'int',
+ ];
+
+ const DEFAULT_TTL = 3600;
+
+ /**
+ * Get token expiration timestamp.
+ *
+ * @return int
+ */
+ public function expiresAt()
+ {
+ $ttl = (int) $this->getTtl();
+ if ($ttl === 0) {
+ $ttl = self::DEFAULT_TTL;
+ }
+
+ return time() + $ttl;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TopLive.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TopLive.php
new file mode 100755
index 0000000..d1f0b78
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TopLive.php
@@ -0,0 +1,25 @@
+ 'User[]',
+ 'ranked_position' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TraceControl.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TraceControl.php
new file mode 100755
index 0000000..54c4b6a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TraceControl.php
@@ -0,0 +1,35 @@
+ 'int',
+ 'cold_start' => '',
+ 'timed_out_upload_sample_rate' => 'int',
+ 'qpl' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TraySuggestions.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TraySuggestions.php
new file mode 100755
index 0000000..9518eff
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TraySuggestions.php
@@ -0,0 +1,40 @@
+ 'StoryTray[]',
+ 'tray_title' => 'string',
+ 'banner_title' => 'string',
+ 'banner_subtitle' => 'string',
+ 'suggestion_type' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TwoFactorInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TwoFactorInfo.php
new file mode 100755
index 0000000..bf8385f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/TwoFactorInfo.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'two_factor_identifier' => 'string',
+ 'phone_verification_settings' => 'PhoneVerificationSettings',
+ 'obfuscated_phone_number' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/CoreUnpredictableContainer.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/CoreUnpredictableContainer.php
new file mode 100755
index 0000000..497944b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/CoreUnpredictableContainer.php
@@ -0,0 +1,95 @@
+_cache === null) {
+ $this->_cache = $this->asArray(); // Throws.
+ }
+
+ if ($this->_type !== null) {
+ foreach ($this->_cache as &$value) {
+ if (is_array($value)) {
+ $value = new $this->_type($value); // Throws.
+ }
+ }
+ }
+
+ return $this->_cache;
+ }
+
+ /**
+ * Set the data array of this unpredictable container.
+ *
+ * @param array $value The new data array.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setData(
+ array $value)
+ {
+ $this->_cache = $value;
+
+ $newObjectData = [];
+ foreach ($this->_cache as $k => $v) {
+ $newObjectData[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ $this->assignObjectData($newObjectData); // Throws.
+
+ return $this;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/DirectThreadLastSeenAtUnpredictableContainer.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/DirectThreadLastSeenAtUnpredictableContainer.php
new file mode 100755
index 0000000..5647e45
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/DirectThreadLastSeenAtUnpredictableContainer.php
@@ -0,0 +1,13 @@
+ 'string',
+ 'has_anonymous_profile_picture' => 'bool',
+ 'has_highlight_reels' => 'bool',
+ 'is_eligible_to_show_fb_cross_sharing_nux' => 'bool',
+ 'page_id_for_new_suma_biz_account' => 'bool',
+ 'eligible_shopping_signup_entrypoints' => 'string[]',
+ 'is_favorite' => 'bool',
+ 'is_favorite_for_stories' => 'bool',
+ 'is_favorite_for_highlights' => 'bool',
+ 'is_interest_account' => 'bool',
+ 'can_be_reported_as_fraud' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_id' => 'string',
+ 'permission' => 'bool',
+ 'full_name' => 'string',
+ 'user_id' => 'string',
+ 'pk' => 'string',
+ 'id' => 'string',
+ 'is_verified' => 'bool',
+ 'is_private' => 'bool',
+ 'coeff_weight' => '',
+ 'friendship_status' => 'FriendshipStatus',
+ 'hd_profile_pic_versions' => 'ImageCandidate[]',
+ 'byline' => '',
+ 'search_social_context' => '',
+ 'unseen_count' => '',
+ 'mutual_followers_count' => 'int',
+ 'follower_count' => 'int',
+ 'search_subtitle' => 'string',
+ 'social_context' => '',
+ 'media_count' => 'int',
+ 'following_count' => 'int',
+ 'following_tag_count' => 'int',
+ 'instagram_location_id' => '',
+ 'is_business' => 'bool',
+ 'usertags_count' => 'int',
+ 'profile_context' => '',
+ 'biography' => 'string',
+ 'geo_media_count' => 'int',
+ 'is_unpublished' => 'bool',
+ 'allow_contacts_sync' => '',
+ 'show_feed_biz_conversion_icon' => '',
+ 'auto_expand_chaining' => '',
+ 'can_boost_post' => '',
+ 'is_profile_action_needed' => 'bool',
+ 'has_chaining' => 'bool',
+ 'has_recommend_accounts' => 'bool',
+ 'chaining_suggestions' => 'ChainingSuggestion[]',
+ 'include_direct_blacklist_status' => '',
+ 'can_see_organic_insights' => 'bool',
+ 'has_placed_orders' => 'bool',
+ 'can_convert_to_business' => 'bool',
+ 'convert_from_pages' => '',
+ 'show_business_conversion_icon' => 'bool',
+ 'show_conversion_edit_entry' => 'bool',
+ 'show_insights_terms' => 'bool',
+ 'can_create_sponsor_tags' => '',
+ 'hd_profile_pic_url_info' => 'ImageCandidate',
+ 'usertag_review_enabled' => '',
+ 'profile_context_mutual_follow_ids' => 'string[]',
+ 'profile_context_links_with_user_ids' => 'Link[]',
+ 'has_biography_translation' => 'bool',
+ 'total_igtv_videos' => 'int',
+ 'total_ar_effects' => 'int',
+ 'can_link_entities_in_bio' => 'bool',
+ 'biography_with_entities' => 'BiographyEntities',
+ 'max_num_linked_entities_in_bio' => 'int',
+ 'business_contact_method' => 'string',
+ 'highlight_reshare_disabled' => 'bool',
+ /*
+ * Business category.
+ */
+ 'category' => 'string',
+ 'direct_messaging' => 'string',
+ 'page_name' => 'string',
+ 'is_attribute_sync_enabled' => 'bool',
+ 'has_business_presence_node' => 'bool',
+ 'profile_visits_count' => 'int',
+ 'profile_visits_num_days' => 'int',
+ 'is_call_to_action_enabled' => 'bool',
+ 'linked_fb_user' => 'FacebookUser',
+ 'account_type' => 'int',
+ 'should_show_category' => 'bool',
+ 'should_show_public_contacts' => 'bool',
+ 'can_hide_category' => 'bool',
+ 'can_hide_public_contacts' => 'bool',
+ 'can_tag_products_from_merchants' => 'bool',
+ 'public_phone_country_code' => 'string',
+ 'public_phone_number' => 'string',
+ 'contact_phone_number' => 'string',
+ 'latitude' => 'float',
+ 'longitude' => 'float',
+ 'address_street' => 'string',
+ 'zip' => 'string',
+ 'city_id' => 'string', // 64-bit number.
+ 'city_name' => 'string',
+ 'public_email' => 'string',
+ 'is_needy' => 'bool',
+ 'external_url' => 'string',
+ 'external_lynx_url' => 'string',
+ 'email' => 'string',
+ 'country_code' => 'int',
+ 'birthday' => '',
+ 'national_number' => 'string', // Really int, but may be >32bit.
+ 'gender' => 'int',
+ 'phone_number' => 'string',
+ 'needs_email_confirm' => '',
+ 'is_active' => 'bool',
+ 'block_at' => '',
+ 'aggregate_promote_engagement' => '',
+ 'fbuid' => '',
+ 'page_id' => 'string',
+ 'can_claim_page' => 'bool',
+ 'fb_page_call_to_action_id' => 'string',
+ 'fb_page_call_to_action_ix_app_id' => 'int',
+ 'fb_page_call_to_action_ix_label_bundle' => '',
+ 'fb_page_call_to_action_ix_url' => 'string',
+ 'fb_page_call_to_action_ix_partner' => 'string',
+ 'is_call_to_action_enabled_by_surface' => 'bool',
+ 'can_crosspost_without_fb_token' => 'bool',
+ 'num_of_admined_pages' => 'int',
+ 'shoppable_posts_count' => 'int',
+ 'show_shoppable_feed' => 'bool',
+ 'show_account_transparency_details' => 'bool',
+ /*
+ * Unix "taken_at" timestamp of the newest item in their story reel.
+ */
+ 'latest_reel_media' => 'string',
+ 'has_unseen_besties_media' => 'bool',
+ 'allowed_commenter_type' => 'string',
+ 'reel_auto_archive' => 'string',
+ 'is_directapp_installed' => 'bool',
+ 'is_using_unified_inbox_for_direct' => 'int',
+ 'feed_post_reshare_disabled' => 'bool',
+ 'besties_count' => 'int',
+ 'can_be_tagged_as_sponsor' => 'bool',
+ 'can_follow_hashtag' => 'bool',
+ 'is_potential_business' => 'bool',
+ 'has_profile_video_feed' => 'bool',
+ 'is_video_creator' => 'bool',
+ 'show_besties_badge' => 'bool',
+ 'recently_bestied_by_count' => 'int',
+ 'screenshotted' => 'bool',
+ 'nametag' => 'Nametag',
+ 'school' => '',
+ 'is_bestie' => 'bool',
+ 'live_subscription_status' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UserCard.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UserCard.php
new file mode 100755
index 0000000..5cab10b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UserCard.php
@@ -0,0 +1,80 @@
+ 'User',
+ 'algorithm' => 'string',
+ 'social_context' => 'string',
+ 'caption' => '',
+ 'icon' => '',
+ 'media_ids' => '',
+ 'thumbnail_urls' => '',
+ 'large_urls' => '',
+ 'media_infos' => '',
+ 'value' => 'float',
+ 'is_new_suggestion' => 'bool',
+ 'uuid' => 'string',
+ 'followed_by' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UserList.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UserList.php
new file mode 100755
index 0000000..6952967
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UserList.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'user' => 'User',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UserPresence.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UserPresence.php
new file mode 100755
index 0000000..d9e9aa9
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/UserPresence.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'last_activity_at_ms' => 'string',
+ 'is_active' => 'bool',
+ 'in_threads' => 'string[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Usertag.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Usertag.php
new file mode 100755
index 0000000..f1bff83
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Usertag.php
@@ -0,0 +1,25 @@
+ 'In[]',
+ 'photo_of_you' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VideoCallEvent.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VideoCallEvent.php
new file mode 100755
index 0000000..57e3645
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VideoCallEvent.php
@@ -0,0 +1,31 @@
+ 'string',
+ 'vc_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VideoUploadUrl.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VideoUploadUrl.php
new file mode 100755
index 0000000..cb572f7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VideoUploadUrl.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'job' => 'string',
+ 'expires' => 'float',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VideoVersions.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VideoVersions.php
new file mode 100755
index 0000000..057e909
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VideoVersions.php
@@ -0,0 +1,40 @@
+ 'int', // Some kinda internal type ID, such as int(102).
+ 'width' => 'int',
+ 'height' => 'int',
+ 'url' => 'string',
+ 'id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Viewer.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Viewer.php
new file mode 100755
index 0000000..9692734
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Viewer.php
@@ -0,0 +1,20 @@
+ 'EligiblePromotions',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VoiceMedia.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VoiceMedia.php
new file mode 100755
index 0000000..b6237dd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VoiceMedia.php
@@ -0,0 +1,20 @@
+ 'DirectThreadItemMedia',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Voter.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Voter.php
new file mode 100755
index 0000000..129936d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/Voter.php
@@ -0,0 +1,25 @@
+ 'User',
+ 'vote' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VoterInfo.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VoterInfo.php
new file mode 100755
index 0000000..37f0aab
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/VoterInfo.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'voters' => 'Voter[]',
+ 'max_id' => 'string',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/Model/_Message.php b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/_Message.php
new file mode 100755
index 0000000..9b40e81
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/Model/_Message.php
@@ -0,0 +1,25 @@
+ '',
+ 'time' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MsisdnHeaderResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MsisdnHeaderResponse.php
new file mode 100755
index 0000000..481f9b9
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MsisdnHeaderResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'url' => 'string',
+ 'remaining_ttl_seconds' => 'int',
+ 'ttl' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MultipleAccountFamilyResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MultipleAccountFamilyResponse.php
new file mode 100755
index 0000000..d93d034
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MultipleAccountFamilyResponse.php
@@ -0,0 +1,37 @@
+ 'string[]',
+ 'main_accounts' => 'string[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/MutedReelsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/MutedReelsResponse.php
new file mode 100755
index 0000000..3ef5370
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/MutedReelsResponse.php
@@ -0,0 +1,47 @@
+ 'Model\User[]',
+ 'next_max_id' => 'string',
+ 'page_size' => '',
+ 'big_list' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/OnBoardCatalogResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/OnBoardCatalogResponse.php
new file mode 100755
index 0000000..0766763
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/OnBoardCatalogResponse.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'current_catalog_id' => 'string',
+ 'is_business_targeted_for_shopping' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/OnTagProductResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/OnTagProductResponse.php
new file mode 100755
index 0000000..e7bd30a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/OnTagProductResponse.php
@@ -0,0 +1,42 @@
+ 'Model\Product',
+ 'merchant' => 'Model\User',
+ 'other_product_items' => 'Model\Product[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PermalinkResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PermalinkResponse.php
new file mode 100755
index 0000000..e436246
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PermalinkResponse.php
@@ -0,0 +1,32 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PinCommentBroadcastResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PinCommentBroadcastResponse.php
new file mode 100755
index 0000000..6ef84e3
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PinCommentBroadcastResponse.php
@@ -0,0 +1,32 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PopularFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PopularFeedResponse.php
new file mode 100755
index 0000000..465bf20
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PopularFeedResponse.php
@@ -0,0 +1,57 @@
+ 'string',
+ 'more_available' => '',
+ 'auto_load_more_enabled' => '',
+ 'items' => 'Model\Item[]',
+ 'num_results' => 'int',
+ 'max_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PostLiveCommentsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PostLiveCommentsResponse.php
new file mode 100755
index 0000000..2b91274
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PostLiveCommentsResponse.php
@@ -0,0 +1,52 @@
+ '',
+ 'ending_offset' => '',
+ 'next_fetch_offset' => '',
+ 'comments' => 'Model\LiveComment[]',
+ 'pinned_comments' => 'Model\LiveComment[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PostLiveLikesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PostLiveLikesResponse.php
new file mode 100755
index 0000000..b7781cf
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PostLiveLikesResponse.php
@@ -0,0 +1,47 @@
+ '',
+ 'ending_offset' => '',
+ 'next_fetch_offset' => '',
+ 'time_series' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PostLiveViewerListResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PostLiveViewerListResponse.php
new file mode 100755
index 0000000..5e6ca51
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PostLiveViewerListResponse.php
@@ -0,0 +1,42 @@
+ 'Model\User[]',
+ 'next_max_id' => '',
+ 'total_viewer_count' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PrefillCandidatesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PrefillCandidatesResponse.php
new file mode 100755
index 0000000..2aae401
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PrefillCandidatesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Prefill[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PresenceStatusResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PresenceStatusResponse.php
new file mode 100755
index 0000000..ec4d0b7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PresenceStatusResponse.php
@@ -0,0 +1,37 @@
+ 'bool',
+ 'thread_presence_disabled' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PresencesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PresencesResponse.php
new file mode 100755
index 0000000..feac8db
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PresencesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\UnpredictableKeys\PresenceUnpredictableContainer',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ProfileNoticeResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ProfileNoticeResponse.php
new file mode 100755
index 0000000..19ef64b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ProfileNoticeResponse.php
@@ -0,0 +1,32 @@
+ 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PropertyCollection/Sticker.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PropertyCollection/Sticker.php
new file mode 100755
index 0000000..a436f2d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PropertyCollection/Sticker.php
@@ -0,0 +1,53 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float', // Unused by IG for now. So far it's always int(0).
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PushPreferencesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PushPreferencesResponse.php
new file mode 100755
index 0000000..4a99442
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PushPreferencesResponse.php
@@ -0,0 +1,117 @@
+ 'Model\PushSettings[]',
+ 'likes' => '',
+ 'comments' => '',
+ 'comment_likes' => '',
+ 'like_and_comment_on_photo_user_tagged' => '',
+ 'live_broadcast' => '',
+ 'new_follower' => '',
+ 'follow_request_accepted' => '',
+ 'contact_joined' => '',
+ 'pending_direct_share' => '',
+ 'direct_share_activity' => '',
+ 'user_tagged' => '',
+ 'notification_reminders' => '',
+ 'first_post' => '',
+ 'announcements' => '',
+ 'ads' => '',
+ 'view_count' => '',
+ 'report_updated' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/PushRegisterResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/PushRegisterResponse.php
new file mode 100755
index 0000000..b999f5f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/PushRegisterResponse.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'global' => 'int',
+ 'default' => 'int',
+ 'surfaces' => 'Model\QPSurface[]',
+ 'slots' => 'Model\Slot[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/RecentSearchesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/RecentSearchesResponse.php
new file mode 100755
index 0000000..d49da4a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/RecentSearchesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Suggested[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/RecoveryResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/RecoveryResponse.php
new file mode 100755
index 0000000..646fef8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/RecoveryResponse.php
@@ -0,0 +1,42 @@
+ 'bool',
+ 'title' => 'string',
+ 'body' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ReelMediaViewerResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ReelMediaViewerResponse.php
new file mode 100755
index 0000000..99d83ed
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ReelMediaViewerResponse.php
@@ -0,0 +1,62 @@
+ 'Model\User[]',
+ 'next_max_id' => 'string',
+ 'user_count' => 'int',
+ 'total_viewer_count' => 'int',
+ 'screenshotter_user_ids' => '',
+ 'total_screenshot_count' => 'int',
+ 'updated_media' => 'Model\Item',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ReelSettingsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ReelSettingsResponse.php
new file mode 100755
index 0000000..7151269
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ReelSettingsResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'blocked_reels' => 'Model\BlockedReels',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ReelsMediaResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ReelsMediaResponse.php
new file mode 100755
index 0000000..eeff797
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ReelsMediaResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Reel[]',
+ 'reels' => 'Model\UnpredictableKeys\ReelUnpredictableContainer',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ReelsTrayFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ReelsTrayFeedResponse.php
new file mode 100755
index 0000000..a02fe85
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ReelsTrayFeedResponse.php
@@ -0,0 +1,72 @@
+ 'string',
+ 'broadcasts' => 'Model\Broadcast[]',
+ 'tray' => 'Model\StoryTray[]',
+ 'post_live' => 'Model\PostLive',
+ 'sticker_version' => 'int',
+ 'face_filter_nux_version' => 'int',
+ 'stories_viewer_gestures_nux_eligible' => 'bool',
+ 'has_new_nux_story' => 'bool',
+ 'suggestions' => 'Model\TraySuggestions[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/RelatedLocationResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/RelatedLocationResponse.php
new file mode 100755
index 0000000..089eb60
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/RelatedLocationResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Location[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ReportExploreMediaResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ReportExploreMediaResponse.php
new file mode 100755
index 0000000..a341e4f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ReportExploreMediaResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ResumableOffsetResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ResumableOffsetResponse.php
new file mode 100755
index 0000000..42793cd
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ResumableOffsetResponse.php
@@ -0,0 +1,52 @@
+ 'int',
+ ];
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ $offset = $this->_getProperty('offset');
+ if ($offset !== null && $offset >= 0) {
+ return true;
+ } else {
+ // Set a nice message for exceptions.
+ if ($this->getMessage() === null) {
+ $this->setMessage('Offset for resumable uploader is missing or invalid.');
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ResumableUploadResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ResumableUploadResponse.php
new file mode 100755
index 0000000..db9b2d0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ResumableUploadResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'upload_id' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ReviewPreferenceResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ReviewPreferenceResponse.php
new file mode 100755
index 0000000..dee7ea6
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ReviewPreferenceResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SaveAndUnsaveMedia.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SaveAndUnsaveMedia.php
new file mode 100755
index 0000000..cc193e5
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SaveAndUnsaveMedia.php
@@ -0,0 +1,25 @@
+ 'Model\SavedFeedItem[]',
+ 'more_available' => '',
+ 'next_max_id' => 'string',
+ 'auto_load_more_enabled' => '',
+ 'num_results' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SearchTagResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SearchTagResponse.php
new file mode 100755
index 0000000..d43b569
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SearchTagResponse.php
@@ -0,0 +1,42 @@
+ 'bool',
+ 'results' => 'Model\Tag[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SearchUserResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SearchUserResponse.php
new file mode 100755
index 0000000..2cdb215
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SearchUserResponse.php
@@ -0,0 +1,47 @@
+ 'bool',
+ 'num_results' => 'int',
+ 'users' => 'Model\User[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SegmentedStartResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SegmentedStartResponse.php
new file mode 100755
index 0000000..eb17072
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SegmentedStartResponse.php
@@ -0,0 +1,52 @@
+ 'string',
+ ];
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ $streamId = $this->_getProperty('stream_id');
+ if ($streamId !== null && $streamId !== '') {
+ return true;
+ } else {
+ // Set a nice message for exceptions.
+ if ($this->getMessage() === null) {
+ $this->setMessage('Stream ID for segmented uploader is missing or invalid.');
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SendConfirmEmailResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SendConfirmEmailResponse.php
new file mode 100755
index 0000000..ed19156
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SendConfirmEmailResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'is_email_legit' => '',
+ 'body' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SendSMSCodeResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SendSMSCodeResponse.php
new file mode 100755
index 0000000..9d532b8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SendSMSCodeResponse.php
@@ -0,0 +1,37 @@
+ 'bool',
+ 'phone_verification_settings' => 'Model\PhoneVerificationSettings',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SendTwoFactorEnableSMSResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SendTwoFactorEnableSMSResponse.php
new file mode 100755
index 0000000..8105873
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SendTwoFactorEnableSMSResponse.php
@@ -0,0 +1,37 @@
+ 'Model\PhoneVerificationSettings',
+ 'obfuscated_phone_number' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SharePrefillResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SharePrefillResponse.php
new file mode 100755
index 0000000..c32054e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SharePrefillResponse.php
@@ -0,0 +1,42 @@
+ 'Model\Ranking',
+ 'entities' => 'Model\ServerDataInfo',
+ 'failed_view_names' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SharedFollowersResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SharedFollowersResponse.php
new file mode 100755
index 0000000..6e428eb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SharedFollowersResponse.php
@@ -0,0 +1,32 @@
+ 'Model\SharedFollower[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/StartLiveResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/StartLiveResponse.php
new file mode 100755
index 0000000..c5bea53
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/StartLiveResponse.php
@@ -0,0 +1,32 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/StickerAssetsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/StickerAssetsResponse.php
new file mode 100755
index 0000000..73d8f12
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/StickerAssetsResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'static_stickers' => 'Model\StaticStickers[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/StoryAnswersResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/StoryAnswersResponse.php
new file mode 100755
index 0000000..cae9c24
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/StoryAnswersResponse.php
@@ -0,0 +1,32 @@
+ 'Model\StoryQuestionResponderInfos',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/StoryCountdownsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/StoryCountdownsResponse.php
new file mode 100755
index 0000000..0733391
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/StoryCountdownsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\CountdownSticker[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/StoryPollVotersResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/StoryPollVotersResponse.php
new file mode 100755
index 0000000..6a250dc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/StoryPollVotersResponse.php
@@ -0,0 +1,32 @@
+ 'Model\VoterInfo',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedBroadcastsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedBroadcastsResponse.php
new file mode 100755
index 0000000..2d28366
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedBroadcastsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Broadcast[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedSearchesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedSearchesResponse.php
new file mode 100755
index 0000000..98bca9d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedSearchesResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Suggested[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedUsersBadgeResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedUsersBadgeResponse.php
new file mode 100755
index 0000000..2c22f97
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedUsersBadgeResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'new_suggestion_ids' => 'string[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedUsersResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedUsersResponse.php
new file mode 100755
index 0000000..0c60221
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SuggestedUsersResponse.php
@@ -0,0 +1,37 @@
+ 'Model\User[]',
+ 'is_backup' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SwitchBusinessProfileResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SwitchBusinessProfileResponse.php
new file mode 100755
index 0000000..77dab4c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SwitchBusinessProfileResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SwitchPersonalProfileResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SwitchPersonalProfileResponse.php
new file mode 100755
index 0000000..60c25c1
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SwitchPersonalProfileResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/SyncResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/SyncResponse.php
new file mode 100755
index 0000000..23f9c73
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/SyncResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Experiment[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TVChannelsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TVChannelsResponse.php
new file mode 100755
index 0000000..a3cde18
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TVChannelsResponse.php
@@ -0,0 +1,67 @@
+ 'string',
+ 'title' => 'string',
+ 'id' => 'string',
+ 'items' => 'Model\Item[]',
+ 'more_available' => 'bool',
+ 'max_id' => 'string',
+ 'seen_state' => '',
+ 'user_dict' => 'Model\User',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TVGuideResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TVGuideResponse.php
new file mode 100755
index 0000000..0a6bee0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TVGuideResponse.php
@@ -0,0 +1,52 @@
+ 'Model\TVChannel[]',
+ 'my_channel' => 'Model\TVChannel',
+ 'badging' => 'Model\Badging',
+ 'composer' => 'Model\Composer',
+ 'banner_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TVSearchResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TVSearchResponse.php
new file mode 100755
index 0000000..cc80d89
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TVSearchResponse.php
@@ -0,0 +1,42 @@
+ 'Model\TVSearchResult[]',
+ 'num_results' => 'int',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TagFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TagFeedResponse.php
new file mode 100755
index 0000000..4b21e6c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TagFeedResponse.php
@@ -0,0 +1,77 @@
+ 'Model\Section[]',
+ 'num_results' => 'int',
+ 'ranked_items' => 'Model\Item[]',
+ 'auto_load_more_enabled' => 'bool',
+ 'items' => 'Model\Item[]',
+ 'story' => 'Model\StoryTray',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'next_media_ids' => '',
+ 'next_page' => 'int',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TagInfoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TagInfoResponse.php
new file mode 100755
index 0000000..12afa93
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TagInfoResponse.php
@@ -0,0 +1,68 @@
+ 'Model\Related[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TagsStoryResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TagsStoryResponse.php
new file mode 100755
index 0000000..4adc673
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TagsStoryResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Reel',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TimelineFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TimelineFeedResponse.php
new file mode 100755
index 0000000..3d23206
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TimelineFeedResponse.php
@@ -0,0 +1,98 @@
+ 'int',
+ 'client_gap_enforcer_matrix' => '',
+ 'is_direct_v2_enabled' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'pagination_info' => '',
+ 'feed_items' => 'Model\FeedItem[]',
+ 'megaphone' => 'Model\FeedAysf',
+ 'client_feed_changelist_applied' => 'bool',
+ 'view_state_version' => 'string',
+ 'feed_pill_text' => 'string',
+ 'client_gap_enforcer_matrix' => '',
+ 'client_session_id' => 'string',
+ 'startup_prefetch_configs' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TokenResultResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TokenResultResponse.php
new file mode 100755
index 0000000..d22596f
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TokenResultResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Token',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TopLiveStatusResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TopLiveStatusResponse.php
new file mode 100755
index 0000000..a7e908c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TopLiveStatusResponse.php
@@ -0,0 +1,32 @@
+ 'Model\BroadcastStatusItem[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TranslateResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TranslateResponse.php
new file mode 100755
index 0000000..84a0d12
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TranslateResponse.php
@@ -0,0 +1,32 @@
+ 'Model\CommentTranslations[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/TwoFactorLoginSMSResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/TwoFactorLoginSMSResponse.php
new file mode 100755
index 0000000..254afdf
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/TwoFactorLoginSMSResponse.php
@@ -0,0 +1,37 @@
+ 'bool',
+ 'two_factor_info' => 'Model\TwoFactorInfo',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/UnlinkAddressBookResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/UnlinkAddressBookResponse.php
new file mode 100755
index 0000000..4d44742
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/UnlinkAddressBookResponse.php
@@ -0,0 +1,25 @@
+ 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/UploadJobVideoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/UploadJobVideoResponse.php
new file mode 100755
index 0000000..7d1e3d7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/UploadJobVideoResponse.php
@@ -0,0 +1,37 @@
+ 'string',
+ 'video_upload_urls' => 'Model\VideoUploadUrl[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/UploadPhotoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/UploadPhotoResponse.php
new file mode 100755
index 0000000..6587b2c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/UploadPhotoResponse.php
@@ -0,0 +1,37 @@
+ 'string',
+ 'media_id' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/UploadVideoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/UploadVideoResponse.php
new file mode 100755
index 0000000..0f9db0e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/UploadVideoResponse.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'configure_delay_ms' => 'float',
+ 'result' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/UserFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/UserFeedResponse.php
new file mode 100755
index 0000000..082c94c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/UserFeedResponse.php
@@ -0,0 +1,57 @@
+ 'Model\Item[]',
+ 'num_results' => 'int',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'max_id' => 'string',
+ 'auto_load_more_enabled' => 'bool',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/UserInfoResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/UserInfoResponse.php
new file mode 100755
index 0000000..0f0a7ac
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/UserInfoResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'user' => 'Model\User',
+ 'effect_previews' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/UserReelMediaFeedResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/UserReelMediaFeedResponse.php
new file mode 100755
index 0000000..275bedc
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/UserReelMediaFeedResponse.php
@@ -0,0 +1,96 @@
+ 'Model\Broadcast',
+ 'reel' => 'Model\Reel',
+ 'post_live_item' => 'Model\PostLiveItem',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/UsersLookupResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/UsersLookupResponse.php
new file mode 100755
index 0000000..950bfdb
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/UsersLookupResponse.php
@@ -0,0 +1,77 @@
+ 'Model\User',
+ 'email_sent' => 'bool',
+ 'has_valid_phone' => 'bool',
+ 'can_email_reset' => 'bool',
+ 'can_sms_reset' => 'bool',
+ 'user_id' => 'string',
+ 'lookup_source' => 'string',
+ 'email' => 'string',
+ 'phone_number' => 'string',
+ 'corrected_input' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/UsertagsResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/UsertagsResponse.php
new file mode 100755
index 0000000..99fd399
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/UsertagsResponse.php
@@ -0,0 +1,67 @@
+ 'int',
+ 'auto_load_more_enabled' => '',
+ 'items' => 'Model\Item[]',
+ 'more_available' => '',
+ 'next_max_id' => 'string',
+ 'total_count' => '',
+ 'requires_review' => '',
+ 'new_photos' => '',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ValidateURLResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ValidateURLResponse.php
new file mode 100755
index 0000000..95406a8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ValidateURLResponse.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'phone_number' => 'string',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/ViewerListResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/ViewerListResponse.php
new file mode 100755
index 0000000..75cef57
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/ViewerListResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Response/WriteSuppotedCapabilitiesResponse.php b/instafeed/vendor/mgp25/instagram-php/src/Response/WriteSuppotedCapabilitiesResponse.php
new file mode 100755
index 0000000..494155c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Response/WriteSuppotedCapabilitiesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\SupportedCapabilities[]',
+ ];
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Settings/Factory.php b/instafeed/vendor/mgp25/instagram-php/src/Settings/Factory.php
new file mode 100755
index 0000000..9f7aff2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Settings/Factory.php
@@ -0,0 +1,262 @@
+ $storage];
+ }
+
+ // Determine the user's final storage configuration.
+ switch ($storageConfig['storage']) {
+ case 'file':
+ // Look for allowed command-line values related to this backend.
+ $cmdOptions = self::getCmdOptions([
+ 'settings_basefolder::',
+ ]);
+
+ // These settings are optional:
+ $baseFolder = self::getUserConfig('basefolder', $storageConfig, $cmdOptions);
+
+ // Generate the final storage location configuration.
+ $locationConfig = [
+ 'basefolder' => $baseFolder,
+ ];
+
+ $storageInstance = new Storage\File();
+ break;
+ case 'mysql':
+ // Look for allowed command-line values related to this backend.
+ $cmdOptions = self::getCmdOptions([
+ 'settings_dbusername::',
+ 'settings_dbpassword::',
+ 'settings_dbhost::',
+ 'settings_dbname::',
+ 'settings_dbtablename::',
+ ]);
+
+ // These settings are optional, and can be provided regardless of
+ // connection method:
+ $locationConfig = [
+ 'dbtablename' => self::getUserConfig('dbtablename', $storageConfig, $cmdOptions),
+ ];
+
+ // These settings are required, but you only have to use one method:
+ if (isset($storageConfig['pdo'])) {
+ // If "pdo" is set in the factory config, assume the user wants
+ // to re-use an existing PDO connection. In that case we ignore
+ // the username/password/host/name parameters and use their PDO.
+ // NOTE: Beware that we WILL change attributes on the PDO
+ // connection to suit our needs! Primarily turning all error
+ // reporting into exceptions, and setting the charset to UTF-8.
+ // If you want to re-use a PDO connection, you MUST accept the
+ // fact that WE NEED exceptions and UTF-8 in our PDO! If that is
+ // not acceptable to you then DO NOT re-use your own PDO object!
+ $locationConfig['pdo'] = $storageConfig['pdo'];
+ } else {
+ // Make a new connection. Optional settings for it:
+ $locationConfig['dbusername'] = self::getUserConfig('dbusername', $storageConfig, $cmdOptions);
+ $locationConfig['dbpassword'] = self::getUserConfig('dbpassword', $storageConfig, $cmdOptions);
+ $locationConfig['dbhost'] = self::getUserConfig('dbhost', $storageConfig, $cmdOptions);
+ $locationConfig['dbname'] = self::getUserConfig('dbname', $storageConfig, $cmdOptions);
+ }
+
+ $storageInstance = new Storage\MySQL();
+ break;
+ case 'sqlite':
+ // Look for allowed command-line values related to this backend.
+ $cmdOptions = self::getCmdOptions([
+ 'settings_dbfilename::',
+ 'settings_dbtablename::',
+ ]);
+
+ // These settings are optional, and can be provided regardless of
+ // connection method:
+ $locationConfig = [
+ 'dbtablename' => self::getUserConfig('dbtablename', $storageConfig, $cmdOptions),
+ ];
+
+ // These settings are required, but you only have to use one method:
+ if (isset($storageConfig['pdo'])) {
+ // If "pdo" is set in the factory config, assume the user wants
+ // to re-use an existing PDO connection. In that case we ignore
+ // the SQLite filename/connection parameters and use their PDO.
+ // NOTE: Beware that we WILL change attributes on the PDO
+ // connection to suit our needs! Primarily turning all error
+ // reporting into exceptions, and setting the charset to UTF-8.
+ // If you want to re-use a PDO connection, you MUST accept the
+ // fact that WE NEED exceptions and UTF-8 in our PDO! If that is
+ // not acceptable to you then DO NOT re-use your own PDO object!
+ $locationConfig['pdo'] = $storageConfig['pdo'];
+ } else {
+ // Make a new connection. Optional settings for it:
+ $locationConfig['dbfilename'] = self::getUserConfig('dbfilename', $storageConfig, $cmdOptions);
+ }
+
+ $storageInstance = new Storage\SQLite();
+ break;
+ case 'memcached':
+ // The memcached storage can only be configured via the factory
+ // configuration array (not via command line or environment vars).
+
+ // These settings are required, but you only have to use one method:
+ if (isset($storageConfig['memcached'])) {
+ // Re-use the user's own Memcached object.
+ $locationConfig = [
+ 'memcached' => $storageConfig['memcached'],
+ ];
+ } else {
+ // Make a new connection. Optional settings for it:
+ $locationConfig = [
+ // This ID will be passed to the \Memcached() constructor.
+ // NOTE: Memcached's "persistent ID" feature makes Memcached
+ // keep the settings even after you disconnect.
+ 'persistent_id' => (isset($storageConfig['persistent_id'])
+ ? $storageConfig['persistent_id']
+ : null),
+ // Array which will be passed to \Memcached::setOptions().
+ 'memcached_options' => (isset($storageConfig['memcached_options'])
+ ? $storageConfig['memcached_options']
+ : null),
+ // Array which will be passed to \Memcached::addServers().
+ // NOTE: Can contain one or multiple servers.
+ 'servers' => (isset($storageConfig['servers'])
+ ? $storageConfig['servers']
+ : null),
+ // SASL username and password to be used for SASL
+ // authentication with all of the Memcached servers.
+ // NOTE: PHP's Memcached API doesn't support individual
+ // authentication credentials per-server, so these values
+ // apply to all of your servers if you use this feature!
+ 'sasl_username' => (isset($storageConfig['sasl_username'])
+ ? $storageConfig['sasl_username']
+ : null),
+ 'sasl_password' => (isset($storageConfig['sasl_password'])
+ ? $storageConfig['sasl_password']
+ : null),
+ ];
+ }
+
+ $storageInstance = new Storage\Memcached();
+ break;
+ case 'custom':
+ // Custom storage classes can only be configured via the main array.
+ // When using a custom class, you must provide a StorageInterface:
+ if (!isset($storageConfig['class'])
+ || !$storageConfig['class'] instanceof StorageInterface) {
+ throw new SettingsException('Invalid custom storage class.');
+ }
+
+ // Create a clean storage location configuration array.
+ $locationConfig = $storageConfig;
+ unset($locationConfig['storage']);
+ unset($locationConfig['class']);
+
+ $storageInstance = $storageConfig['class'];
+ break;
+ default:
+ throw new SettingsException(sprintf(
+ 'Unknown settings storage type "%s".',
+ $storageConfig['storage']
+ ));
+ }
+
+ // Create the storage handler and connect to the storage location.
+ return new StorageHandler(
+ $storageInstance,
+ $locationConfig,
+ $callbacks
+ );
+ }
+
+ /**
+ * Get option values via command-line parameters.
+ *
+ * @param array $longOpts The longnames for the options to look for.
+ *
+ * @return array
+ */
+ public static function getCmdOptions(
+ array $longOpts)
+ {
+ $cmdOptions = getopt('', $longOpts);
+ if (!is_array($cmdOptions)) {
+ $cmdOptions = [];
+ }
+
+ return $cmdOptions;
+ }
+
+ /**
+ * Looks for the highest-priority result for a Storage config value.
+ *
+ * @param string $settingName The name of the setting.
+ * @param array $storageConfig The Factory's configuration array.
+ * @param array $cmdOptions All parsed command-line options.
+ *
+ * @return string|null The value if found, otherwise NULL.
+ */
+ public static function getUserConfig(
+ $settingName,
+ array $storageConfig,
+ array $cmdOptions)
+ {
+ // Command line options have the highest precedence.
+ // NOTE: Settings provided via cmd must have a "settings_" prefix.
+ if (array_key_exists("settings_{$settingName}", $cmdOptions)) {
+ return $cmdOptions["settings_{$settingName}"];
+ }
+
+ // Environment variables have the second highest precedence.
+ // NOTE: Settings provided via env must be UPPERCASED and have
+ // a "SETTINGS_" prefix, for example "SETTINGS_STORAGE".
+ $envValue = getenv('SETTINGS_'.strtoupper($settingName));
+ if ($envValue !== false) {
+ return $envValue;
+ }
+
+ // Our factory config array has the lowest precedence, so that you can
+ // easily override it via the other methods when testing other storage
+ // backends or different connection parameters.
+ if (array_key_exists($settingName, $storageConfig)) {
+ return $storageConfig[$settingName];
+ }
+
+ // Couldn't find any user-provided value. Automatically returns null.
+ // NOTE: Damn you StyleCI for not allowing "return null;" for clarity.
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/Components/PDOStorage.php b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/Components/PDOStorage.php
new file mode 100755
index 0000000..84730c0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/Components/PDOStorage.php
@@ -0,0 +1,394 @@
+_backendName = $backendName;
+ }
+
+ /**
+ * Connect to a storage location and perform necessary startup preparations.
+ *
+ * {@inheritdoc}
+ */
+ public function openLocation(
+ array $locationConfig)
+ {
+ $this->_dbTableName = (isset($locationConfig['dbtablename'])
+ ? $locationConfig['dbtablename']
+ : 'user_sessions');
+
+ if (isset($locationConfig['pdo'])) {
+ // Pre-provided connection to re-use instead of creating a new one.
+ if (!$locationConfig['pdo'] instanceof PDO) {
+ throw new SettingsException('The custom PDO object is invalid.');
+ }
+ $this->_isSharedPDO = true;
+ $this->_pdo = $locationConfig['pdo'];
+ } else {
+ // We should connect for the user, by creating our own PDO object.
+ $this->_isSharedPDO = false;
+
+ try {
+ $this->_pdo = $this->_createPDO($locationConfig);
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Connection Failed: '.$e->getMessage());
+ }
+ }
+
+ try {
+ $this->_configurePDO();
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Configuration Failed: '.$e->getMessage());
+ }
+
+ try {
+ $this->_autoCreateTable();
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Create a new PDO connection to the database.
+ *
+ * @param array $locationConfig Configuration parameters for the location.
+ *
+ * @throws \Exception
+ *
+ * @return \PDO The database connection.
+ */
+ abstract protected function _createPDO(
+ array $locationConfig);
+
+ /**
+ * Configures the connection for our needs.
+ *
+ * Warning for those who re-used a PDO object: Beware that we WILL change
+ * attributes on the PDO connection to suit our needs! Primarily turning all
+ * error reporting into exceptions, and setting the charset to UTF-8. If you
+ * want to re-use a PDO connection, you MUST accept the fact that WE NEED
+ * exceptions and UTF-8 in our PDO! If that is not acceptable to you then DO
+ * NOT re-use your own PDO object!
+ *
+ * @throws \Exception
+ */
+ protected function _configurePDO()
+ {
+ $this->_pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+ $this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->_enableUTF8();
+ }
+
+ /**
+ * Enable UTF-8 encoding on the connection.
+ *
+ * This is database-specific and usually requires some kind of query.
+ *
+ * @throws \Exception
+ */
+ abstract protected function _enableUTF8();
+
+ /**
+ * Automatically create the database table if necessary.
+ *
+ * @throws \Exception
+ */
+ abstract protected function _autoCreateTable();
+
+ /**
+ * Automatically writes to the correct user's row and caches the new value.
+ *
+ * @param string $column The database column.
+ * @param string $data Data to be written.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _setUserColumn(
+ $column,
+ $data)
+ {
+ if ($column != 'settings' && $column != 'cookies') {
+ throw new SettingsException(sprintf(
+ 'Attempt to write to illegal database column "%s".',
+ $column
+ ));
+ }
+
+ try {
+ // Update if the user row already exists, otherwise insert.
+ $binds = [':data' => $data];
+ if ($this->_cache['id'] !== null) {
+ $sql = "UPDATE `{$this->_dbTableName}` SET {$column}=:data WHERE (id=:id)";
+ $binds[':id'] = $this->_cache['id'];
+ } else {
+ $sql = "INSERT INTO `{$this->_dbTableName}` (username, {$column}) VALUES (:username, :data)";
+ $binds[':username'] = $this->_username;
+ }
+
+ $sth = $this->_pdo->prepare($sql);
+ $sth->execute($binds);
+
+ // Keep track of the database row ID for the user.
+ if ($this->_cache['id'] === null) {
+ $this->_cache['id'] = $this->_pdo->lastinsertid();
+ }
+
+ $sth->closeCursor();
+
+ // Cache the new value.
+ $this->_cache[$column] = $data;
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUser(
+ $username)
+ {
+ // Check whether a row exists for that username.
+ $sth = $this->_pdo->prepare("SELECT EXISTS(SELECT 1 FROM `{$this->_dbTableName}` WHERE (username=:username))");
+ $sth->execute([':username' => $username]);
+ $result = $sth->fetchColumn();
+ $sth->closeCursor();
+
+ return $result > 0 ? true : false;
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * {@inheritdoc}
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ try {
+ // Verify that the old username exists.
+ if (!$this->hasUser($oldUsername)) {
+ throw new SettingsException(sprintf(
+ 'Cannot move non-existent user "%s".',
+ $oldUsername
+ ));
+ }
+
+ // Verify that the new username does not exist.
+ if ($this->hasUser($newUsername)) {
+ throw new SettingsException(sprintf(
+ 'Refusing to overwrite existing user "%s".',
+ $newUsername
+ ));
+ }
+
+ // Now attempt to rename the old username column to the new name.
+ $sth = $this->_pdo->prepare("UPDATE `{$this->_dbTableName}` SET username=:newusername WHERE (username=:oldusername)");
+ $sth->execute([':oldusername' => $oldUsername, ':newusername' => $newUsername]);
+ $sth->closeCursor();
+ } catch (SettingsException $e) {
+ throw $e; // Ugly but necessary to re-throw only our own messages.
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * {@inheritdoc}
+ */
+ public function deleteUser(
+ $username)
+ {
+ try {
+ // Just attempt to delete the row. Doesn't error if already missing.
+ $sth = $this->_pdo->prepare("DELETE FROM `{$this->_dbTableName}` WHERE (username=:username)");
+ $sth->execute([':username' => $username]);
+ $sth->closeCursor();
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Open the data storage for a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function openUser(
+ $username)
+ {
+ $this->_username = $username;
+
+ // Retrieve and cache the existing user data row if available.
+ try {
+ $sth = $this->_pdo->prepare("SELECT id, settings, cookies FROM `{$this->_dbTableName}` WHERE (username=:username)");
+ $sth->execute([':username' => $this->_username]);
+ $result = $sth->fetch(PDO::FETCH_ASSOC);
+ $sth->closeCursor();
+
+ if (is_array($result)) {
+ $this->_cache = $result;
+ } else {
+ $this->_cache = [
+ 'id' => null,
+ 'settings' => null,
+ 'cookies' => null,
+ ];
+ }
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Load all settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserSettings()
+ {
+ $userSettings = [];
+
+ if (!empty($this->_cache['settings'])) {
+ $userSettings = @json_decode($this->_cache['settings'], true, 512, JSON_BIGINT_AS_STRING);
+ if (!is_array($userSettings)) {
+ throw new SettingsException(sprintf(
+ 'Failed to decode corrupt settings for account "%s".',
+ $this->_username
+ ));
+ }
+ }
+
+ return $userSettings;
+ }
+
+ /**
+ * Save the settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserSettings(
+ array $userSettings,
+ $triggerKey)
+ {
+ // Store the settings as a JSON blob.
+ $encodedData = json_encode($userSettings);
+ $this->_setUserColumn('settings', $encodedData);
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUserCookies()
+ {
+ return isset($this->_cache['cookies'])
+ && !empty($this->_cache['cookies']);
+ }
+
+ /**
+ * Get the cookiefile disk path (only if a file-based cookie jar is wanted).
+ *
+ * {@inheritdoc}
+ */
+ public function getUserCookiesFilePath()
+ {
+ // NULL = We (the backend) will handle the cookie loading/saving.
+ return null;
+ }
+
+ /**
+ * (Non-cookiefile) Load all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserCookies()
+ {
+ return isset($this->_cache['cookies'])
+ ? $this->_cache['cookies']
+ : null;
+ }
+
+ /**
+ * (Non-cookiefile) Save all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserCookies(
+ $rawData)
+ {
+ // Store the raw cookie data as-provided.
+ $this->_setUserColumn('cookies', $rawData);
+ }
+
+ /**
+ * Close the settings storage for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function closeUser()
+ {
+ $this->_username = null;
+ $this->_cache = null;
+ }
+
+ /**
+ * Disconnect from a storage location and perform necessary shutdown steps.
+ *
+ * {@inheritdoc}
+ */
+ public function closeLocation()
+ {
+ // Delete our reference to the PDO object. If nobody else references
+ // it, the PDO connection will now be terminated. In case of shared
+ // objects, the original owner still has their reference (as intended).
+ $this->_pdo = null;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/File.php b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/File.php
new file mode 100755
index 0000000..289d04b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/File.php
@@ -0,0 +1,385 @@
+_baseFolder = $this->_createFolder($baseFolder);
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUser(
+ $username)
+ {
+ // Check whether the user's settings-file exists.
+ $hasUser = $this->_generateUserPaths($username);
+
+ return is_file($hasUser['settingsFile']) ? true : false;
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * {@inheritdoc}
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ // Verify the old and new username parameters.
+ $oldUser = $this->_generateUserPaths($oldUsername);
+ $newUser = $this->_generateUserPaths($newUsername);
+ if (!is_dir($oldUser['userFolder'])) {
+ throw new SettingsException(sprintf(
+ 'Cannot move non-existent user folder "%s".',
+ $oldUser['userFolder']
+ ));
+ }
+ if (is_dir($newUser['userFolder'])) {
+ throw new SettingsException(sprintf(
+ 'Refusing to overwrite existing user folder "%s".',
+ $newUser['userFolder']
+ ));
+ }
+
+ // Create the new destination folder and migrate all data.
+ $this->_createFolder($newUser['userFolder']);
+ if (is_file($oldUser['settingsFile'])
+ && !@rename($oldUser['settingsFile'], $newUser['settingsFile'])) {
+ throw new SettingsException(sprintf(
+ 'Failed to move "%s" to "%s".',
+ $oldUser['settingsFile'], $newUser['settingsFile']
+ ));
+ }
+ if (is_file($oldUser['cookiesFile'])
+ && !@rename($oldUser['cookiesFile'], $newUser['cookiesFile'])) {
+ throw new SettingsException(sprintf(
+ 'Failed to move "%s" to "%s".',
+ $oldUser['cookiesFile'], $newUser['cookiesFile']
+ ));
+ }
+
+ // Delete all files in the old folder, and the folder itself.
+ Utils::deleteTree($oldUser['userFolder']);
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * {@inheritdoc}
+ */
+ public function deleteUser(
+ $username)
+ {
+ // Delete all files in the user folder, and the folder itself.
+ $delUser = $this->_generateUserPaths($username);
+ Utils::deleteTree($delUser['userFolder']);
+ }
+
+ /**
+ * Open the data storage for a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function openUser(
+ $username)
+ {
+ $this->_username = $username;
+ $userPaths = $this->_generateUserPaths($username);
+ $this->_userFolder = $userPaths['userFolder'];
+ $this->_settingsFile = $userPaths['settingsFile'];
+ $this->_cookiesFile = $userPaths['cookiesFile'];
+ $this->_createFolder($this->_userFolder);
+ }
+
+ /**
+ * Load all settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserSettings()
+ {
+ $userSettings = [];
+
+ if (!is_file($this->_settingsFile)) {
+ return $userSettings; // Nothing to load.
+ }
+
+ // Read from disk.
+ $rawData = @file_get_contents($this->_settingsFile);
+ if ($rawData === false) {
+ throw new SettingsException(sprintf(
+ 'Unable to read from settings file "%s".',
+ $this->_settingsFile
+ ));
+ }
+
+ // Fetch the data version ("FILESTORAGEv#;") header.
+ $dataVersion = 1; // Assume migration from v1 if no version.
+ if (preg_match('/^FILESTORAGEv(\d+);/', $rawData, $matches)) {
+ $dataVersion = intval($matches[1]);
+ $rawData = substr($rawData, strpos($rawData, ';') + 1);
+ }
+
+ // Decode the key-value pairs regardless of data-storage version.
+ $userSettings = $this->_decodeStorage($dataVersion, $rawData);
+
+ return $userSettings;
+ }
+
+ /**
+ * Save the settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserSettings(
+ array $userSettings,
+ $triggerKey)
+ {
+ // Generate the storage version header.
+ $versionHeader = 'FILESTORAGEv'.self::STORAGE_VERSION.';';
+
+ // Encode a binary representation of all settings.
+ // VERSION 2 STORAGE FORMAT: JSON-encoded blob.
+ $encodedData = $versionHeader.json_encode($userSettings);
+
+ // Perform an atomic diskwrite, which prevents accidental truncation.
+ // NOTE: If we had just written directly to settingsPath, the file would
+ // have become corrupted if the script was killed mid-write. The atomic
+ // write process guarantees that the data is fully written to disk.
+ Utils::atomicWrite($this->_settingsFile, $encodedData);
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUserCookies()
+ {
+ return is_file($this->_cookiesFile)
+ && filesize($this->_cookiesFile) > 0;
+ }
+
+ /**
+ * Get the cookiefile disk path (only if a file-based cookie jar is wanted).
+ *
+ * {@inheritdoc}
+ */
+ public function getUserCookiesFilePath()
+ {
+ // Tell the caller to use a file-based cookie jar.
+ return $this->_cookiesFile;
+ }
+
+ /**
+ * (Non-cookiefile) Load all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserCookies()
+ {
+ // Never called for "cookiefile" format.
+ }
+
+ /**
+ * (Non-cookiefile) Save all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserCookies(
+ $rawData)
+ {
+ // Never called for "cookiefile" format.
+ }
+
+ /**
+ * Close the settings storage for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function closeUser()
+ {
+ $this->_userFolder = null;
+ $this->_settingsFile = null;
+ $this->_cookiesFile = null;
+ $this->_username = null;
+ }
+
+ /**
+ * Disconnect from a storage location and perform necessary shutdown steps.
+ *
+ * {@inheritdoc}
+ */
+ public function closeLocation()
+ {
+ // We don't need to disconnect from anything since we are file-based.
+ }
+
+ /**
+ * Decodes the data from any File storage format version.
+ *
+ * @param int $dataVersion Which data format to decode.
+ * @param string $rawData The raw data, encoded in version's format.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array An array with all current key-value pairs for the user.
+ */
+ private function _decodeStorage(
+ $dataVersion,
+ $rawData)
+ {
+ $loadedSettings = [];
+
+ switch ($dataVersion) {
+ case 1:
+ /**
+ * This is the old format from v1.x of Instagram-API.
+ * Terrible format. Basic "key=value\r\n" and very fragile.
+ */
+
+ // Split by system-independent newlines. Tries \r\n (Win), then \r
+ // (pre-2000s Mac), then \n\r, then \n (Mac OS X, UNIX, Linux).
+ $lines = preg_split('/(\r\n?|\n\r?)/', $rawData, -1, PREG_SPLIT_NO_EMPTY);
+ if ($lines !== false) {
+ foreach ($lines as $line) {
+ // Key must be at least one character. Allows empty values.
+ if (preg_match('/^([^=]+)=(.*)$/', $line, $matches)) {
+ $key = $matches[1];
+ $value = rtrim($matches[2], "\r\n ");
+ $loadedSettings[$key] = $value;
+ }
+ }
+ }
+ break;
+ case 2:
+ /**
+ * Version 2 uses JSON encoding and perfectly stores any value.
+ * And file corruption can't happen, thanks to the atomic writer.
+ */
+ $loadedSettings = @json_decode($rawData, true, 512, JSON_BIGINT_AS_STRING);
+ if (!is_array($loadedSettings)) {
+ throw new SettingsException(sprintf(
+ 'Failed to decode corrupt settings file for account "%s".',
+ $this->_username
+ ));
+ }
+ break;
+ default:
+ throw new SettingsException(sprintf(
+ 'Invalid file settings storage format version "%d".',
+ $dataVersion
+ ));
+ }
+
+ return $loadedSettings;
+ }
+
+ /**
+ * Generates all path strings for a given username.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @return array An array with information about the user's paths.
+ */
+ private function _generateUserPaths(
+ $username)
+ {
+ $userFolder = $this->_baseFolder.'/'.$username;
+ $settingsFile = $userFolder.'/'.sprintf(self::SETTINGSFILE_NAME, $username);
+ $cookiesFile = $userFolder.'/'.sprintf(self::COOKIESFILE_NAME, $username);
+
+ return [
+ 'userFolder' => $userFolder,
+ 'settingsFile' => $settingsFile,
+ 'cookiesFile' => $cookiesFile,
+ ];
+ }
+
+ /**
+ * Creates a folder if missing, or ensures that it is writable.
+ *
+ * @param string $folder The directory path.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string The canonicalized absolute pathname of the folder, without
+ * any trailing slash.
+ */
+ private function _createFolder(
+ $folder)
+ {
+ if (!Utils::createFolder($folder)) {
+ throw new SettingsException(sprintf(
+ 'The "%s" folder is not writable.',
+ $folder
+ ));
+ }
+
+ // Determine the real path of the folder we created/checked.
+ // NOTE: This ensures that the path will work even on stingy systems
+ // such as Windows Server which chokes on multiple slashes in a row.
+ $realPath = @realpath($folder);
+ if (!is_string($realPath)) {
+ throw new SettingsException(sprintf(
+ 'Unable to resolve real path to folder "%s".',
+ $folder
+ ));
+ }
+
+ return $realPath;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/Memcached.php b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/Memcached.php
new file mode 100755
index 0000000..b14ce3b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/Memcached.php
@@ -0,0 +1,345 @@
+_isSharedMemcached = true;
+ $this->_memcached = $locationConfig['memcached'];
+ } else {
+ try {
+ // Configure memcached with a persistent data retention ID.
+ $this->_isSharedMemcached = false;
+ $this->_memcached = new PHPMemcached(
+ is_string($locationConfig['persistent_id'])
+ ? $locationConfig['persistent_id']
+ : 'instagram'
+ );
+
+ // Enable SASL authentication if credentials were provided.
+ // NOTE: PHP's Memcached API doesn't support individual
+ // authentication credentials per-server!
+ if (isset($locationConfig['sasl_username'])
+ && isset($locationConfig['sasl_password'])) {
+ // When SASL is used, the servers almost always NEED binary
+ // protocol, but if that doesn't work with the user's server
+ // then then the user can manually override this default by
+ // providing the "false" option via "memcached_options".
+ $this->_memcached->setOption(PHPMemcached::OPT_BINARY_PROTOCOL, true);
+ $this->_memcached->setSaslAuthData(
+ $locationConfig['sasl_username'],
+ $locationConfig['sasl_password']
+ );
+ }
+
+ // Apply any custom options the user has provided.
+ // NOTE: This is where "OPT_BINARY_PROTOCOL" can be overridden.
+ if (is_array($locationConfig['memcached_options'])) {
+ $this->_memcached->setOptions($locationConfig['memcached_options']);
+ }
+
+ // Add the provided servers to the pool.
+ if (is_array($locationConfig['servers'])) {
+ $this->_memcached->addServers($locationConfig['servers']);
+ } else {
+ // Use default port on localhost if nothing was provided.
+ $this->_memcached->addServer('localhost', 11211);
+ }
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Connection Failed: '.$e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * Retrieve a memcached key for a particular user.
+ *
+ * @param string $username The Instagram username.
+ * @param string $key Name of the subkey.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string|null The value as a string IF the user's key exists,
+ * otherwise NULL.
+ */
+ private function _getUserKey(
+ $username,
+ $key)
+ {
+ try {
+ $realKey = $username.'_'.$key;
+ $result = $this->_memcached->get($realKey);
+
+ return $this->_memcached->getResultCode() !== PHPMemcached::RES_NOTFOUND
+ ? (string) $result
+ : null;
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Set a memcached key for a particular user.
+ *
+ * @param string $username The Instagram username.
+ * @param string $key Name of the subkey.
+ * @param string|mixed $value The data to store. MUST be castable to string.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ private function _setUserKey(
+ $username,
+ $key,
+ $value)
+ {
+ try {
+ $realKey = $username.'_'.$key;
+ $success = $this->_memcached->set($realKey, (string) $value);
+ if (!$success) {
+ throw new SettingsException(sprintf(
+ 'Memcached failed to write to key "%s".',
+ $realKey
+ ));
+ }
+ } catch (SettingsException $e) {
+ throw $e; // Ugly but necessary to re-throw only our own messages.
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Delete a memcached key for a particular user.
+ *
+ * @param string $username The Instagram username.
+ * @param string $key Name of the subkey.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ private function _delUserKey(
+ $username,
+ $key)
+ {
+ try {
+ $realKey = $username.'_'.$key;
+ $this->_memcached->delete($realKey);
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUser(
+ $username)
+ {
+ // Check whether the user's settings exist (empty string allowed).
+ $hasUser = $this->_getUserKey($username, 'settings');
+
+ return $hasUser !== null ? true : false;
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * {@inheritdoc}
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ // Verify that the old username exists and fetch the old data.
+ $oldSettings = $this->_getUserKey($oldUsername, 'settings');
+ $oldCookies = $this->_getUserKey($oldUsername, 'cookies');
+ if ($oldSettings === null) { // Only settings are vital.
+ throw new SettingsException(sprintf(
+ 'Cannot move non-existent user "%s".',
+ $oldUsername
+ ));
+ }
+
+ // Verify that the new username does not exist.
+ if ($this->hasUser($newUsername)) {
+ throw new SettingsException(sprintf(
+ 'Refusing to overwrite existing user "%s".',
+ $newUsername
+ ));
+ }
+
+ // Now attempt to write all data to the new name.
+ $this->_setUserKey($newUsername, 'settings', $oldSettings);
+ if ($oldCookies !== null) { // Only if cookies existed.
+ $this->_setUserKey($newUsername, 'cookies', $oldCookies);
+ }
+
+ // Delete the previous user keys.
+ $this->deleteUser($oldUsername);
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * {@inheritdoc}
+ */
+ public function deleteUser(
+ $username)
+ {
+ $this->_delUserKey($username, 'settings');
+ $this->_delUserKey($username, 'cookies');
+ }
+
+ /**
+ * Open the data storage for a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function openUser(
+ $username)
+ {
+ // Just cache the username. We'll create storage later if necessary.
+ $this->_username = $username;
+ }
+
+ /**
+ * Load all settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserSettings()
+ {
+ $userSettings = [];
+
+ $encodedData = $this->_getUserKey($this->_username, 'settings');
+ if (!empty($encodedData)) {
+ $userSettings = @json_decode($encodedData, true, 512, JSON_BIGINT_AS_STRING);
+ if (!is_array($userSettings)) {
+ throw new SettingsException(sprintf(
+ 'Failed to decode corrupt settings for account "%s".',
+ $this->_username
+ ));
+ }
+ }
+
+ return $userSettings;
+ }
+
+ /**
+ * Save the settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserSettings(
+ array $userSettings,
+ $triggerKey)
+ {
+ // Store the settings as a JSON blob.
+ $encodedData = json_encode($userSettings);
+ $this->_setUserKey($this->_username, 'settings', $encodedData);
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUserCookies()
+ {
+ // Simply check if the storage key for cookies exists and is non-empty.
+ return !empty($this->loadUserCookies()) ? true : false;
+ }
+
+ /**
+ * Get the cookiefile disk path (only if a file-based cookie jar is wanted).
+ *
+ * {@inheritdoc}
+ */
+ public function getUserCookiesFilePath()
+ {
+ // NULL = We (the backend) will handle the cookie loading/saving.
+ return null;
+ }
+
+ /**
+ * (Non-cookiefile) Load all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserCookies()
+ {
+ return $this->_getUserKey($this->_username, 'cookies');
+ }
+
+ /**
+ * (Non-cookiefile) Save all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserCookies(
+ $rawData)
+ {
+ // Store the raw cookie data as-provided.
+ $this->_setUserKey($this->_username, 'cookies', $rawData);
+ }
+
+ /**
+ * Close the settings storage for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function closeUser()
+ {
+ $this->_username = null;
+ }
+
+ /**
+ * Disconnect from a storage location and perform necessary shutdown steps.
+ *
+ * {@inheritdoc}
+ */
+ public function closeLocation()
+ {
+ // Close all server connections if this was our own Memcached object.
+ if (!$this->_isSharedMemcached) {
+ try {
+ $this->_memcached->quit();
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Disconnection Failed: '.$e->getMessage());
+ }
+ }
+ $this->_memcached = null;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/MySQL.php b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/MySQL.php
new file mode 100755
index 0000000..0d0a030
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/MySQL.php
@@ -0,0 +1,101 @@
+_pdo->query("SET NAMES 'utf8mb4'")->closeCursor();
+ }
+
+ /**
+ * Automatically create the database table if necessary.
+ *
+ * {@inheritdoc}
+ */
+ protected function _autoCreateTable()
+ {
+ // Detect the name of the MySQL database that PDO is connected to.
+ $dbName = $this->_pdo->query('SELECT database()')->fetchColumn();
+
+ // Abort if we already have the necessary table.
+ $sth = $this->_pdo->prepare('SELECT count(*) FROM information_schema.TABLES WHERE (TABLE_SCHEMA = :tableSchema) AND (TABLE_NAME = :tableName)');
+ $sth->execute([':tableSchema' => $dbName, ':tableName' => $this->_dbTableName]);
+ $result = $sth->fetchColumn();
+ $sth->closeCursor();
+ if ($result > 0) {
+ return;
+ }
+
+ // Create the database table. Throws in case of failure.
+ // NOTE: We store all settings as a binary JSON blob so that we support
+ // all current and future data without having to alter the table schema.
+ // NOTE: The username is a 150-character-max varchar, NOT 255! Why?
+ // Because MySQL has a 767-byte max limit for efficient indexes, and
+ // "utf8mb4" uses 4 bytes per character, which means that 191 characters
+ // is the maximum safe amount (191 * 4 = 764)! We chose 150 as a nice
+ // number. Instagram's username limit is 30, so our limit is fine!
+ // NOTE: We use "utf8mb4_general_ci" which performs fast, general
+ // sorting, since we have no need for language-aware "unicode" sorting.
+ // NOTE: Lastly... note that our encoding only affects the "username"
+ // column. All other columns are numbers, binary blobs, etc!
+ $this->_pdo->exec('CREATE TABLE `'.$this->_dbTableName.'` (
+ id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ username VARCHAR(150) NOT NULL,
+ settings MEDIUMBLOB NULL,
+ cookies MEDIUMBLOB NULL,
+ last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY (username)
+ ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ENGINE=InnoDB;');
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/SQLite.php b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/SQLite.php
new file mode 100755
index 0000000..a32e254
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Settings/Storage/SQLite.php
@@ -0,0 +1,115 @@
+_pdo->query('PRAGMA encoding = "UTF-8"')->closeCursor();
+ }
+
+ /**
+ * Automatically create the database table if necessary.
+ *
+ * {@inheritdoc}
+ */
+ protected function _autoCreateTable()
+ {
+ // Abort if we already have the necessary table.
+ $sth = $this->_pdo->prepare('SELECT count(*) FROM sqlite_master WHERE (type = "table") AND (name = :tableName)');
+ $sth->execute([':tableName' => $this->_dbTableName]);
+ $result = $sth->fetchColumn();
+ $sth->closeCursor();
+ if ($result > 0) {
+ return;
+ }
+
+ // Create the database table. Throws in case of failure.
+ // NOTE: We store all settings as a JSON blob so that we support all
+ // current and future data without having to alter the table schema.
+ // NOTE: SQLite automatically increments "integer primary key" cols.
+ $this->_pdo->exec('CREATE TABLE `'.$this->_dbTableName.'` (
+ id INTEGER PRIMARY KEY NOT NULL,
+ username TEXT NOT NULL UNIQUE,
+ settings BLOB,
+ cookies BLOB,
+ last_modified DATETIME DEFAULT CURRENT_TIMESTAMP
+ );');
+
+ // Set up a trigger to automatically update the modification timestamp.
+ // NOTE: The WHEN clause is important to avoid infinite recursive loops,
+ // otherwise you'll get "Error: too many levels of trigger recursion" if
+ // recursive triggers are enabled in SQLite. The WHEN constraint simply
+ // ensures that we only update last_modified automatically after UPDATEs
+ // that did NOT change last_modified. So our own UPDATEs of other fields
+ // will trigger this automatic UPDATE, which does an UPDATE with a NEW
+ // last_modified value, meaning that the trigger won't execute again!
+ $this->_pdo->exec('CREATE TRIGGER IF NOT EXISTS `'.$this->_dbTableName.'_update_last_modified`
+ AFTER UPDATE
+ ON `'.$this->_dbTableName.'`
+ FOR EACH ROW
+ WHEN NEW.last_modified = OLD.last_modified -- Avoids infinite loop.
+ BEGIN
+ UPDATE `'.$this->_dbTableName.'` SET last_modified=CURRENT_TIMESTAMP WHERE (id=OLD.id);
+ END;'
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Settings/StorageHandler.php b/instafeed/vendor/mgp25/instagram-php/src/Settings/StorageHandler.php
new file mode 100755
index 0000000..95b970b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Settings/StorageHandler.php
@@ -0,0 +1,803 @@
+_callbacks = $callbacks;
+
+ // Connect the storage instance to the user's desired storage location.
+ $this->_storage = $storageInstance;
+ $this->_storage->openLocation($locationConfig);
+ }
+
+ /**
+ * Destructor.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function __destruct()
+ {
+ // The storage handler is being killed, so tell the location to close.
+ if ($this->_username !== null) {
+ $this->_triggerCallback('onCloseUser');
+ $this->_storage->closeUser();
+ $this->_username = null;
+ }
+ $this->_storage->closeLocation();
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return bool TRUE if user exists, otherwise FALSE.
+ */
+ public function hasUser(
+ $username)
+ {
+ $this->_throwIfEmptyValue($username);
+
+ return $this->_storage->hasUser($username);
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * This function is important because of the fact that all per-user settings
+ * in all Storage implementations are retrieved and stored via its Instagram
+ * username, since their NAME is literally the ONLY thing we know about a
+ * user before we have loaded their settings or logged in! So if you later
+ * rename that Instagram account, it means that your old device settings
+ * WON'T follow along automatically, since the new login username is seen
+ * as a brand new user that isn't in the settings storage.
+ *
+ * This function conveniently tells your chosen Storage backend to move a
+ * user's settings to a new name, so that they WILL be found again when you
+ * later look for settings for your new name.
+ *
+ * Bonus guide for easily confused people: YOU must manually rename your
+ * user on Instagram.com before you call this function. We don't do that.
+ *
+ * @param string $oldUsername The old name that settings are stored as.
+ * @param string $newUsername The new name to move the settings to.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ $this->_throwIfEmptyValue($oldUsername);
+ $this->_throwIfEmptyValue($newUsername);
+
+ if ($oldUsername === $this->_username
+ || $newUsername === $this->_username) {
+ throw new SettingsException(
+ 'Attempted to move settings to/from the currently active user.'
+ );
+ }
+
+ $this->_storage->moveUser($oldUsername, $newUsername);
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function deleteUser(
+ $username)
+ {
+ $this->_throwIfEmptyValue($username);
+
+ if ($username === $this->_username) {
+ throw new SettingsException(
+ 'Attempted to delete the currently active user.'
+ );
+ }
+
+ $this->_storage->deleteUser($username);
+ }
+
+ /**
+ * Load all settings for a user from the storage and mark as current user.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function setActiveUser(
+ $username)
+ {
+ $this->_throwIfEmptyValue($username);
+
+ // If that user is already loaded, there's no need to do anything.
+ if ($username === $this->_username) {
+ return;
+ }
+
+ // If we're switching away from a user, tell the backend to close the
+ // current user's storage (if it needs to do any special processing).
+ if ($this->_username !== null) {
+ $this->_triggerCallback('onCloseUser');
+ $this->_storage->closeUser();
+ }
+
+ // Set the new user as the current user for this storage instance.
+ $this->_username = $username;
+ $this->_userSettings = [];
+ $this->_storage->openUser($username);
+
+ // Retrieve any existing settings for the user from the backend.
+ $loadedSettings = $this->_storage->loadUserSettings();
+ foreach ($loadedSettings as $key => $value) {
+ // Map renamed old-school keys to new key names.
+ if ($key == 'username_id') {
+ $key = 'account_id';
+ } elseif ($key == 'adid') {
+ $key = 'advertising_id';
+ }
+
+ // Only keep values for keys that are still in use. Discard others.
+ if (in_array($key, self::PERSISTENT_KEYS)) {
+ // Cast all values to strings to ensure we only use strings!
+ // NOTE: THIS CAST IS EXTREMELY IMPORTANT AND *MUST* BE DONE!
+ $this->_userSettings[$key] = (string) $value;
+ }
+ }
+
+ // Determine what type of cookie storage the backend wants for the user.
+ // NOTE: Do NOT validate file existence, since we'll create if missing.
+ $cookiesFilePath = $this->_storage->getUserCookiesFilePath();
+ if ($cookiesFilePath !== null && (!is_string($cookiesFilePath) || !strlen($cookiesFilePath))) {
+ $cookiesFilePath = null; // Disable since it isn't a non-empty string.
+ }
+ $this->_cookiesFilePath = $cookiesFilePath;
+ }
+
+ /**
+ * Does a preliminary guess about whether the current user is logged in.
+ *
+ * Can only be executed after setActiveUser(). And the session it looks
+ * for may be expired, so there's no guarantee that we are still logged in.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return bool TRUE if possibly logged in, otherwise FALSE.
+ */
+ public function isMaybeLoggedIn()
+ {
+ $this->_throwIfNoActiveUser();
+
+ return $this->_storage->hasUserCookies()
+ && !empty($this->get('account_id'));
+ }
+
+ /**
+ * Erase all device-specific settings and all cookies.
+ *
+ * This is useful when assigning a new Android device to the account, upon
+ * which it's very important that we erase all previous, device-specific
+ * settings so that our account still looks natural to Instagram.
+ *
+ * Note that ALL cookies will be erased too, to clear out the old session.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function eraseDeviceSettings()
+ {
+ foreach (self::PERSISTENT_KEYS as $key) {
+ if (!in_array($key, self::KEEP_KEYS_WHEN_ERASING_DEVICE)) {
+ $this->set($key, ''); // Erase the setting.
+ }
+ }
+
+ $this->setCookies(''); // Erase all cookies.
+ }
+
+ /**
+ * Retrieve the value of a setting from the current user's memory cache.
+ *
+ * Can only be executed after setActiveUser().
+ *
+ * @param string $key Name of the setting.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string|null The value as a string IF the setting exists AND is
+ * a NON-EMPTY string. Otherwise NULL.
+ */
+ public function get(
+ $key)
+ {
+ $this->_throwIfNoActiveUser();
+
+ // Reject anything that isn't in our list of VALID persistent keys.
+ if (!in_array($key, self::PERSISTENT_KEYS)) {
+ throw new SettingsException(sprintf(
+ 'The settings key "%s" is not a valid persistent key name.',
+ $key
+ ));
+ }
+
+ // Return value if it's a NON-EMPTY string, otherwise return NULL.
+ // NOTE: All values are cached as strings so no casting is needed.
+ return (isset($this->_userSettings[$key])
+ && $this->_userSettings[$key] !== '')
+ ? $this->_userSettings[$key]
+ : null;
+ }
+
+ /**
+ * Store a setting's value for the current user.
+ *
+ * Can only be executed after setActiveUser(). To clear the value of a
+ * setting, simply pass in an empty string as value.
+ *
+ * @param string $key Name of the setting.
+ * @param string|mixed $value The data to store. MUST be castable to string.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function set(
+ $key,
+ $value)
+ {
+ $this->_throwIfNoActiveUser();
+
+ // Reject anything that isn't in our list of VALID persistent keys.
+ if (!in_array($key, self::PERSISTENT_KEYS)) {
+ throw new SettingsException(sprintf(
+ 'The settings key "%s" is not a valid persistent key name.',
+ $key
+ ));
+ }
+
+ // Reject null values, since they may be accidental. To unset a setting,
+ // the caller must explicitly pass in an empty string instead.
+ if ($value === null) {
+ throw new SettingsException(
+ 'Illegal attempt to store null value in settings storage.'
+ );
+ }
+
+ // Cast the value to string to ensure we don't try writing non-strings.
+ // NOTE: THIS CAST IS EXTREMELY IMPORTANT AND *MUST* ALWAYS BE DONE!
+ $value = (string) $value;
+
+ // Check if the value differs from our storage (cached representation).
+ // NOTE: This optimizes writes by only writing when values change!
+ if (!array_key_exists($key, $this->_userSettings)
+ || $this->_userSettings[$key] !== $value) {
+ // The value differs, so save to memory cache and write to storage.
+ $this->_userSettings[$key] = $value;
+ $this->_storage->saveUserSettings($this->_userSettings, $key);
+ }
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * Can only be executed after setActiveUser().
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return bool TRUE if cookies exist, otherwise FALSE.
+ */
+ public function hasCookies()
+ {
+ $this->_throwIfNoActiveUser();
+
+ return $this->_storage->hasUserCookies();
+ }
+
+ /**
+ * Get all cookies for the currently active user.
+ *
+ * Can only be executed after setActiveUser().
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string|null A previously-stored, raw cookie data string
+ * (non-empty), or NULL if no cookies exist for
+ * the active user.
+ */
+ public function getCookies()
+ {
+ $this->_throwIfNoActiveUser();
+
+ // Read the cookies via the appropriate backend method.
+ $userCookies = null;
+ if ($this->_cookiesFilePath === null) { // Backend storage.
+ $userCookies = $this->_storage->loadUserCookies();
+ } else { // Cookiefile on disk.
+ if (empty($this->_cookiesFilePath)) { // Just for extra safety.
+ throw new SettingsException(
+ 'Cookie file format requested, but no file path provided.'
+ );
+ }
+
+ // Ensure that the cookie file's folder exists and is writable.
+ $this->_createCookiesFileDirectory();
+
+ // Read the existing cookie jar file if it already exists.
+ if (is_file($this->_cookiesFilePath)) {
+ $rawData = file_get_contents($this->_cookiesFilePath);
+ if ($rawData !== false) {
+ $userCookies = $rawData;
+ }
+ }
+ }
+
+ // Ensure that we'll always return NULL if no cookies exist.
+ if ($userCookies !== null && !strlen($userCookies)) {
+ $userCookies = null;
+ }
+
+ return $userCookies;
+ }
+
+ /**
+ * Save all cookies for the currently active user.
+ *
+ * Can only be executed after setActiveUser(). Note that this function is
+ * called frequently!
+ *
+ * NOTE: It is very important that the owner of this SettingsHandler either
+ * continuously calls "setCookies", or better yet listens to the "closeUser"
+ * callback to save all cookies in bulk to storage at the end of a session.
+ *
+ * @param string $rawData An encoded string with all cookie data. Use an
+ * empty string to erase currently stored cookies.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function setCookies(
+ $rawData)
+ {
+ $this->_throwIfNoActiveUser();
+ $this->_throwIfNotString($rawData);
+
+ if ($this->_cookiesFilePath === null) { // Backend storage.
+ $this->_storage->saveUserCookies($rawData);
+ } else { // Cookiefile on disk.
+ if (strlen($rawData)) { // Update cookies (new value is non-empty).
+ // Perform an atomic diskwrite, which prevents accidental
+ // truncation if the script is ever interrupted mid-write.
+ $this->_createCookiesFileDirectory(); // Ensures dir exists.
+ $timeout = 5;
+ $init = time();
+ while (!$written = Utils::atomicWrite($this->_cookiesFilePath, $rawData)) {
+ usleep(mt_rand(400000, 600000)); // 0.4-0.6 sec
+ if (time() - $init > $timeout) {
+ break;
+ }
+ }
+ if ($written === false) {
+ throw new SettingsException(sprintf(
+ 'The "%s" cookie file is not writable.',
+ $this->_cookiesFilePath
+ ));
+ }
+ } else { // Delete cookies (empty string).
+ // Delete any existing cookie jar since the new data is empty.
+ if (is_file($this->_cookiesFilePath) && !@unlink($this->_cookiesFilePath)) {
+ throw new SettingsException(sprintf(
+ 'Unable to delete the "%s" cookie file.',
+ $this->_cookiesFilePath
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Ensures the whole directory path to the cookie file exists/is writable.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _createCookiesFileDirectory()
+ {
+ if ($this->_cookiesFilePath === null) {
+ return;
+ }
+
+ $cookieDir = dirname($this->_cookiesFilePath); // Can be "." in case of CWD.
+ if (!Utils::createFolder($cookieDir)) {
+ throw new SettingsException(sprintf(
+ 'The "%s" cookie folder is not writable.',
+ $cookieDir
+ ));
+ }
+ }
+
+ /**
+ * Internal: Ensures that a parameter is a string.
+ *
+ * @param mixed $value The value to check.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _throwIfNotString(
+ $value)
+ {
+ if (!is_string($value)) {
+ throw new SettingsException('Parameter must be string.');
+ }
+ }
+
+ /**
+ * Internal: Ensures that a parameter is a non-empty string.
+ *
+ * @param mixed $value The value to check.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _throwIfEmptyValue(
+ $value)
+ {
+ if (!is_string($value) || $value === '') {
+ throw new SettingsException('Parameter must be non-empty string.');
+ }
+ }
+
+ /**
+ * Internal: Ensures that there is an active storage user.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _throwIfNoActiveUser()
+ {
+ if ($this->_username === null) {
+ throw new SettingsException(
+ 'Called user-related function before setting the current storage user.'
+ );
+ }
+ }
+
+ /**
+ * Internal: Triggers a callback.
+ *
+ * All callback functions are given the storage handler instance as their
+ * one and only argument.
+ *
+ * @param string $cbName The name of the callback.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _triggerCallback(
+ $cbName)
+ {
+ // Reject anything that isn't in our list of VALID callbacks.
+ if (!in_array($cbName, self::SUPPORTED_CALLBACKS)) {
+ throw new SettingsException(sprintf(
+ 'The string "%s" is not a valid callback name.',
+ $cbName
+ ));
+ }
+
+ // Trigger the callback with a reference to our StorageHandler instance.
+ if (isset($this->_callbacks[$cbName])) {
+ try {
+ $this->_callbacks[$cbName]($this);
+ } catch (\Exception $e) {
+ // Re-wrap anything that isn't already a SettingsException.
+ if (!$e instanceof SettingsException) {
+ $e = new SettingsException($e->getMessage());
+ }
+
+ throw $e; // Re-throw;
+ }
+ }
+ }
+
+ /**
+ * Process and save experiments.
+ *
+ * @param array $experiments
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array A list of "good" experiments.
+ */
+ public function setExperiments(
+ array $experiments)
+ {
+ $filtered = [];
+ foreach (self::EXPERIMENT_KEYS as $key) {
+ if (!isset($experiments[$key])) {
+ continue;
+ }
+ $filtered[$key] = $experiments[$key];
+ }
+ $this->set('experiments', $this->_packJson($filtered));
+
+ return $filtered;
+ }
+
+ /**
+ * Return saved experiments.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array
+ */
+ public function getExperiments()
+ {
+ return $this->_unpackJson($this->get('experiments'), true);
+ }
+
+ /**
+ * Save rewrite rules.
+ *
+ * @param array $rules
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function setRewriteRules(
+ array $rules)
+ {
+ $this->set('zr_rules', $this->_packJson($rules));
+ }
+
+ /**
+ * Return saved rewrite rules.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array
+ */
+ public function getRewriteRules()
+ {
+ return $this->_unpackJson((string) $this->get('zr_rules'), true);
+ }
+
+ /**
+ * Save FBNS authorization.
+ *
+ * @param AuthInterface $auth
+ */
+ public function setFbnsAuth(
+ AuthInterface $auth)
+ {
+ $this->set('fbns_auth', $auth);
+ }
+
+ /**
+ * Get FBNS authorization.
+ *
+ * Will restore previously saved auth details if they exist. Otherwise it
+ * creates random new authorization details.
+ *
+ * @return AuthInterface
+ */
+ public function getFbnsAuth()
+ {
+ $result = new DeviceAuth();
+
+ try {
+ $result->read($this->get('fbns_auth'));
+ } catch (\Exception $e) {
+ }
+
+ return $result;
+ }
+
+ /**
+ * Pack data as JSON, deflating it when it saves some space.
+ *
+ * @param array|object $data
+ *
+ * @return string
+ */
+ protected function _packJson(
+ $data)
+ {
+ $json = json_encode($data);
+ $gzipped = base64_encode(zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9));
+ // We must compare gzipped with double encoded JSON.
+ $doubleJson = json_encode($json);
+ if (strlen($gzipped) < strlen($doubleJson)) {
+ $serialized = 'Z'.$gzipped;
+ } else {
+ $serialized = 'J'.$json;
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * Unpacks data from JSON encoded string, inflating it when necessary.
+ *
+ * @param string $packed
+ * @param bool $assoc
+ *
+ * @return array|object
+ */
+ protected function _unpackJson(
+ $packed,
+ $assoc = true)
+ {
+ if ($packed === null || $packed === '') {
+ return $assoc ? [] : new \stdClass();
+ }
+ $format = $packed[0];
+ $packed = substr($packed, 1);
+
+ try {
+ switch ($format) {
+ case 'Z':
+ $packed = base64_decode($packed, true);
+ if ($packed === false) {
+ throw new \RuntimeException('Invalid Base64 encoded string.');
+ }
+ $json = @zlib_decode($packed);
+ if ($json === false) {
+ throw new \RuntimeException('Invalid zlib encoded string.');
+ }
+ break;
+ case 'J':
+ $json = $packed;
+ break;
+ default:
+ throw new \RuntimeException('Invalid packed type.');
+ }
+ $data = json_decode($json, $assoc);
+ if ($assoc && !is_array($data)) {
+ throw new \RuntimeException('JSON is not an array.');
+ }
+ if (!$assoc && !is_object($data)) {
+ throw new \RuntimeException('JSON is not an object.');
+ }
+ } catch (\RuntimeException $e) {
+ $data = $assoc ? [] : new \stdClass();
+ }
+
+ return $data;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/src/Settings/StorageInterface.php b/instafeed/vendor/mgp25/instagram-php/src/Settings/StorageInterface.php
new file mode 100755
index 0000000..239f356
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/src/Settings/StorageInterface.php
@@ -0,0 +1,264 @@
+ 4) {
+ if ($result > 0x7FFFFFFF) {
+ $result -= 0x100000000;
+ } elseif ($result < -0x80000000) {
+ $result += 0x100000000;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Reorders array by hashCode() of its keys.
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ public static function reorderByHashCode(
+ array $data)
+ {
+ $hashCodes = [];
+ foreach ($data as $key => $value) {
+ $hashCodes[$key] = self::hashCode($key);
+ }
+
+ uksort($data, function ($a, $b) use ($hashCodes) {
+ $a = $hashCodes[$a];
+ $b = $hashCodes[$b];
+ if ($a < $b) {
+ return -1;
+ } elseif ($a > $b) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+
+ return $data;
+ }
+
+ /**
+ * Generates random multipart boundary string.
+ *
+ * @return string
+ */
+ public static function generateMultipartBoundary()
+ {
+ $result = '';
+ $max = strlen(self::BOUNDARY_CHARS) - 1;
+ for ($i = 0; $i < self::BOUNDARY_LENGTH; ++$i) {
+ $result .= self::BOUNDARY_CHARS[mt_rand(0, $max)];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generates user breadcrumb for use when posting a comment.
+ *
+ * @param int $size
+ *
+ * @return string
+ */
+ public static function generateUserBreadcrumb(
+ $size)
+ {
+ $key = 'iN4$aGr0m';
+ $date = (int) (microtime(true) * 1000);
+
+ // typing time
+ $term = rand(2, 3) * 1000 + $size * rand(15, 20) * 100;
+
+ // android EditText change event occur count
+ $text_change_event_count = round($size / rand(2, 3));
+ if ($text_change_event_count == 0) {
+ $text_change_event_count = 1;
+ }
+
+ // generate typing data
+ $data = $size.' '.$term.' '.$text_change_event_count.' '.$date;
+
+ return base64_encode(hash_hmac('sha256', $data, $key, true))."\n".base64_encode($data)."\n";
+ }
+
+ /**
+ * Converts a hours/minutes/seconds timestamp to seconds.
+ *
+ * @param string $timeStr Either `HH:MM:SS[.###]` (24h-clock) or
+ * `MM:SS[.###]` or `SS[.###]`. The `[.###]` is for
+ * optional millisecond precision if wanted, such as
+ * `00:01:01.149`.
+ *
+ * @throws \InvalidArgumentException If any part of the input is invalid.
+ *
+ * @return float The number of seconds, with decimals (milliseconds).
+ */
+ public static function hmsTimeToSeconds(
+ $timeStr)
+ {
+ if (!is_string($timeStr)) {
+ throw new \InvalidArgumentException('Invalid non-string timestamp.');
+ }
+
+ $sec = 0.0;
+ foreach (array_reverse(explode(':', $timeStr)) as $offsetKey => $v) {
+ if ($offsetKey > 2) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid input "%s" with too many components (max 3 is allowed "HH:MM:SS").',
+ $timeStr
+ ));
+ }
+
+ // Parse component (supports "01" or "01.123" (milli-precision)).
+ if ($v === '' || !preg_match('/^\d+(?:\.\d+)?$/', $v)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid non-digit or empty component "%s" in time string "%s".',
+ $v, $timeStr
+ ));
+ }
+ if ($offsetKey !== 0 && strpos($v, '.') !== false) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Unexpected period in time component "%s" in time string "%s". Only the seconds-component supports milliseconds.',
+ $v, $timeStr
+ ));
+ }
+
+ // Convert the value to float and cap minutes/seconds to 60 (but
+ // allow any number of hours).
+ $v = (float) $v;
+ $maxValue = $offsetKey < 2 ? 60 : -1;
+ if ($maxValue >= 0 && $v > $maxValue) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid time component "%d" (its allowed range is 0-%d) in time string "%s".',
+ $v, $maxValue, $timeStr
+ ));
+ }
+
+ // Multiply the current component of the "01:02:03" string with the
+ // power of its offset. Hour-offset will be 2, Minutes 1 and Secs 0;
+ // and "pow(60, 0)" will return 1 which is why seconds work too.
+ $sec += pow(60, $offsetKey) * $v;
+ }
+
+ return $sec;
+ }
+
+ /**
+ * Converts seconds to a hours/minutes/seconds timestamp.
+ *
+ * @param int|float $sec The number of seconds. Can have fractions (millis).
+ *
+ * @throws \InvalidArgumentException If any part of the input is invalid.
+ *
+ * @return string The time formatted as `HH:MM:SS.###` (`###` is millis).
+ */
+ public static function hmsTimeFromSeconds(
+ $sec)
+ {
+ if (!is_int($sec) && !is_float($sec)) {
+ throw new \InvalidArgumentException('Seconds must be a number.');
+ }
+
+ $wasNegative = false;
+ if ($sec < 0) {
+ $wasNegative = true;
+ $sec = abs($sec);
+ }
+
+ $result = sprintf(
+ '%02d:%02d:%06.3f', // "%06f" is because it counts the whole string.
+ floor($sec / 3600),
+ floor(fmod($sec / 60, 60)),
+ fmod($sec, 60)
+ );
+
+ if ($wasNegative) {
+ $result = '-'.$result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Builds an Instagram media location JSON object in the correct format.
+ *
+ * This function is used whenever we need to send a location to Instagram's
+ * API. All endpoints (so far) expect location data in this exact format.
+ *
+ * @param Location $location A model object representing the location.
+ *
+ * @throws \InvalidArgumentException If the location is invalid.
+ *
+ * @return string The final JSON string ready to submit as an API parameter.
+ */
+ public static function buildMediaLocationJSON(
+ $location)
+ {
+ if (!$location instanceof Location) {
+ throw new \InvalidArgumentException('The location must be an instance of \InstagramAPI\Response\Model\Location.');
+ }
+
+ // Forbid locations that came from Location::searchFacebook() and
+ // Location::searchFacebookByPoint()! They have slightly different
+ // properties, and they don't always contain all data we need. The
+ // real application NEVER uses the "Facebook" endpoints for attaching
+ // locations to media, and NEITHER SHOULD WE.
+ if ($location->getFacebookPlacesId() !== null) {
+ throw new \InvalidArgumentException('You are not allowed to use Location model objects from the Facebook-based location search functions. They are not valid media locations!');
+ }
+
+ // Core location keys that always exist.
+ $obj = [
+ 'name' => $location->getName(),
+ 'lat' => $location->getLat(),
+ 'lng' => $location->getLng(),
+ 'address' => $location->getAddress(),
+ 'external_source' => $location->getExternalIdSource(),
+ ];
+
+ // Attach the location ID via a dynamically generated key.
+ // NOTE: This automatically generates a key such as "facebook_places_id".
+ $key = $location->getExternalIdSource().'_id';
+ $obj[$key] = $location->getExternalId();
+
+ // Ensure that all keys are listed in the correct hash order.
+ $obj = self::reorderByHashCode($obj);
+
+ return json_encode($obj);
+ }
+
+ /**
+ * Check for ffprobe dependency.
+ *
+ * TIP: If your binary isn't findable via the PATH environment locations,
+ * you can manually set the correct path to it. Before calling any functions
+ * that need FFprobe, you must simply assign a manual value (ONCE) to tell
+ * us where to find your FFprobe, like this:
+ *
+ * \InstagramAPI\Utils::$ffprobeBin = '/home/exampleuser/ffmpeg/bin/ffprobe';
+ *
+ * @return string|bool Name of the library if present, otherwise FALSE.
+ */
+ public static function checkFFPROBE()
+ {
+ // We only resolve this once per session and then cache the result.
+ if (self::$ffprobeBin === null) {
+ @exec('ffprobe -version 2>&1', $output, $statusCode);
+ if ($statusCode === 0) {
+ self::$ffprobeBin = 'ffprobe';
+ } else {
+ self::$ffprobeBin = false; // Nothing found!
+ }
+ }
+
+ return self::$ffprobeBin;
+ }
+
+ /**
+ * Verifies a user tag.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * user tag, and with proper values for them. We cannot validate that the
+ * user-id actually exists, but that's the job of the library user!
+ *
+ * @param mixed $userTag An array containing the user ID and the tag position.
+ * Example: ['position'=>[0.5,0.5],'user_id'=>'123'].
+ *
+ * @throws \InvalidArgumentException If the tag is invalid.
+ */
+ public static function throwIfInvalidUserTag(
+ $userTag)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($userTag)) {
+ throw new \InvalidArgumentException('User tag must be an array.');
+ }
+
+ // Check for required keys.
+ $requiredKeys = ['position', 'user_id'];
+ $missingKeys = array_diff($requiredKeys, array_keys($userTag));
+ if (!empty($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for user tag array.', implode('", "', $missingKeys)));
+ }
+
+ // Verify this product tag entry, ensuring that the entry is format
+ // ['position'=>[0.0,1.0],'user_id'=>'123'] and nothing else.
+ foreach ($userTag as $key => $value) {
+ switch ($key) {
+ case 'user_id':
+ if (!is_int($value) && !ctype_digit($value)) {
+ throw new \InvalidArgumentException('User ID must be an integer.');
+ }
+ if ($value < 0) {
+ throw new \InvalidArgumentException('User ID must be a positive integer.');
+ }
+ break;
+ case 'position':
+ try {
+ self::throwIfInvalidPosition($value);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(sprintf('Invalid user tag position: %s', $e->getMessage()), $e->getCode(), $e);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in user tag array.', $key));
+ }
+ }
+ }
+
+ /**
+ * Verifies an array of media usertags.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * usertags, and with proper values for them. We cannot validate that the
+ * user-id's actually exist, but that's the job of the library user!
+ *
+ * @param mixed $usertags The array of usertags, optionally with the "in" or
+ * "removed" top-level keys holding the usertags. Example:
+ * ['in'=>[['position'=>[0.5,0.5],'user_id'=>'123'], ...]].
+ *
+ * @throws \InvalidArgumentException If any tags are invalid.
+ */
+ public static function throwIfInvalidUsertags(
+ $usertags)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($usertags)) {
+ throw new \InvalidArgumentException('Usertags must be an array.');
+ }
+
+ if (empty($usertags)) {
+ throw new \InvalidArgumentException('Empty usertags array.');
+ }
+
+ foreach ($usertags as $k => $v) {
+ if (!is_array($v)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid usertags array. The value for key "%s" must be an array.', $k
+ ));
+ }
+ // Skip the section if it's empty.
+ if (empty($v)) {
+ continue;
+ }
+ // Handle ['in'=>[...], 'removed'=>[...]] top-level keys since
+ // this input contained top-level array keys containing the usertags.
+ switch ($k) {
+ case 'in':
+ foreach ($v as $idx => $userTag) {
+ try {
+ self::throwIfInvalidUserTag($userTag);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid usertag at index "%d": %s', $idx, $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+ break;
+ case 'removed':
+ // Check the array of userids to remove.
+ foreach ($v as $userId) {
+ if (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
+ throw new \InvalidArgumentException('Invalid user ID in usertags "removed" array.');
+ }
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in user tags array.', $k));
+ }
+ }
+ }
+
+ /**
+ * Verifies an array of product tags.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * product tags, and with proper values for them. We cannot validate that the
+ * product's-id actually exists, but that's the job of the library user!
+ *
+ * @param mixed $productTags The array of usertags, optionally with the "in" or
+ * "removed" top-level keys holding the usertags. Example:
+ * ['in'=>[['position'=>[0.5,0.5],'product_id'=>'123'], ...]].
+ *
+ * @throws \InvalidArgumentException If any tags are invalid.
+ */
+ public static function throwIfInvalidProductTags(
+ $productTags)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($productTags)) {
+ throw new \InvalidArgumentException('Products tags must be an array.');
+ }
+
+ if (empty($productTags)) {
+ throw new \InvalidArgumentException('Empty product tags array.');
+ }
+
+ foreach ($productTags as $k => $v) {
+ if (!is_array($v)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid product tags array. The value for key "%s" must be an array.', $k
+ ));
+ }
+
+ // Skip the section if it's empty.
+ if (empty($v)) {
+ continue;
+ }
+
+ // Handle ['in'=>[...], 'removed'=>[...]] top-level keys since
+ // this input contained top-level array keys containing the product tags.
+ switch ($k) {
+ case 'in':
+ // Check the array of product tags to insert.
+ foreach ($v as $idx => $productTag) {
+ try {
+ self::throwIfInvalidProductTag($productTag);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid product tag at index "%d": %s', $idx, $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+ break;
+ case 'removed':
+ // Check the array of product_id to remove.
+ foreach ($v as $productId) {
+ if (!ctype_digit($productId) && (!is_int($productId) || $productId < 0)) {
+ throw new \InvalidArgumentException('Invalid product ID in product tags "removed" array.');
+ }
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in product tags array.', $k));
+ }
+ }
+ }
+
+ /**
+ * Verifies a product tag.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * product tag, and with proper values for them. We cannot validate that the
+ * product-id actually exists, but that's the job of the library user!
+ *
+ * @param mixed $productTag An array containing the product ID and the tag position.
+ * Example: ['position'=>[0.5,0.5],'product_id'=>'123'].
+ *
+ * @throws \InvalidArgumentException If any tags are invalid.
+ */
+ public static function throwIfInvalidProductTag(
+ $productTag)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($productTag)) {
+ throw new \InvalidArgumentException('Product tag must be an array.');
+ }
+
+ // Check for required keys.
+ $requiredKeys = ['position', 'product_id'];
+ $missingKeys = array_diff($requiredKeys, array_keys($productTag));
+ if (!empty($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for product tag array.', implode('", "', $missingKeys)));
+ }
+
+ // Verify this product tag entry, ensuring that the entry is format
+ // ['position'=>[0.0,1.0],'product_id'=>'123'] and nothing else.
+ foreach ($productTag as $key => $value) {
+ switch ($key) {
+ case 'product_id':
+ if (!is_int($value) && !ctype_digit($value)) {
+ throw new \InvalidArgumentException('Product ID must be an integer.');
+ }
+ if ($value < 0) {
+ throw new \InvalidArgumentException('Product ID must be a positive integer.');
+ }
+ break;
+ case 'position':
+ try {
+ self::throwIfInvalidPosition($value);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(sprintf('Invalid product tag position: %s', $e->getMessage()), $e->getCode(), $e);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in product tag array.', $key));
+ }
+ }
+ }
+
+ /**
+ * Verifies a position.
+ *
+ * @param mixed $position An array containing a position coordinates.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function throwIfInvalidPosition(
+ $position)
+ {
+ if (!is_array($position)) {
+ throw new \InvalidArgumentException('Position must be an array.');
+ }
+
+ if (!isset($position[0])) {
+ throw new \InvalidArgumentException('X coordinate is required.');
+ }
+ $x = $position[0];
+ if (!is_int($x) && !is_float($x)) {
+ throw new \InvalidArgumentException('X coordinate must be a number.');
+ }
+ if ($x < 0.0 || $x > 1.0) {
+ throw new \InvalidArgumentException('X coordinate must be a float between 0.0 and 1.0.');
+ }
+
+ if (!isset($position[1])) {
+ throw new \InvalidArgumentException('Y coordinate is required.');
+ }
+ $y = $position[1];
+ if (!is_int($y) && !is_float($y)) {
+ throw new \InvalidArgumentException('Y coordinate must be a number.');
+ }
+ if ($y < 0.0 || $y > 1.0) {
+ throw new \InvalidArgumentException('Y coordinate must be a float between 0.0 and 1.0.');
+ }
+ }
+
+ /**
+ * Verifies that a single hashtag is valid.
+ *
+ * This function enforces the following requirements: It must be a string,
+ * at least 1 character long, and cannot contain the "#" character itself.
+ *
+ * @param mixed $hashtag The hashtag to check (should be string but we
+ * accept anything for checking purposes).
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function throwIfInvalidHashtag(
+ $hashtag)
+ {
+ if (!is_string($hashtag) || !strlen($hashtag)) {
+ throw new \InvalidArgumentException('Hashtag must be a non-empty string.');
+ }
+ // Perform an UTF-8 aware search for the illegal "#" symbol (anywhere).
+ // NOTE: We must use mb_strpos() to support international tags.
+ if (mb_strpos($hashtag, '#') !== false) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Hashtag "%s" is not allowed to contain the "#" character.',
+ $hashtag
+ ));
+ }
+ }
+
+ /**
+ * Verifies a rank token.
+ *
+ * @param string $rankToken
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function throwIfInvalidRankToken(
+ $rankToken
+ ) {
+ if (!Signatures::isValidUUID($rankToken)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid rank token.', $rankToken));
+ }
+ }
+
+ /**
+ * Verifies an array of story poll.
+ *
+ * @param array[] $storyPoll Array with story poll key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryPoll(
+ array $storyPoll)
+ {
+ $requiredKeys = ['question', 'viewer_vote', 'viewer_can_vote', 'tallies', 'is_sticker'];
+
+ if (count($storyPoll) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story poll is permitted. You added %d story polls.', count($storyPoll)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['question' => 1, 'viewer_vote' => 1, 'viewer_can_vote' => 1, 'tallies' => 1, 'is_sticker' => 1], $storyPoll[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story poll array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storyPoll[0] as $k => $v) {
+ switch ($k) {
+ case 'question':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_vote':
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_can_vote':
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'tallies':
+ if (!is_array($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ self::_throwIfInvalidStoryPollTallies($v);
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storyPoll[0], array_flip($requiredKeys)), 'polls');
+ }
+
+ /**
+ * Verifies an array of story slider.
+ *
+ * @param array[] $storySlider Array with story slider key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStorySlider(
+ array $storySlider)
+ {
+ $requiredKeys = ['question', 'viewer_vote', 'viewer_can_vote', 'slider_vote_average', 'slider_vote_count', 'emoji', 'background_color', 'text_color', 'is_sticker'];
+
+ if (count($storySlider) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story slider is permitted. You added %d story sliders.', count($storySlider)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['question' => 1, 'viewer_vote' => 1, 'viewer_can_vote' => 1, 'slider_vote_average' => 1, 'slider_vote_count' => 1, 'emoji' => 1, 'background_color' => 1, 'text_color' => 1, 'is_sticker' => 1], $storySlider[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story slider array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storySlider[0] as $k => $v) {
+ switch ($k) {
+ case 'question':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story slider array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_vote':
+ case 'slider_vote_count':
+ case 'slider_vote_average':
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story slider array-key "%s".', $v, $k));
+ }
+ break;
+ case 'background_color':
+ case 'text_color':
+ if (!preg_match('/^[0-9a-fA-F]{6}$/', substr($v, 1))) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story slider array-key "%s".', $v, $k));
+ }
+ break;
+ case 'emoji':
+ //TODO REQUIRES EMOJI VALIDATION
+ break;
+ case 'viewer_can_vote':
+ if (!is_bool($v) && $v !== false) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storySlider[0], array_flip($requiredKeys)), 'sliders');
+ }
+
+ /**
+ * Verifies an array of story question.
+ *
+ * @param array $storyQuestion Array with story question key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryQuestion(
+ array $storyQuestion)
+ {
+ $requiredKeys = ['z', 'viewer_can_interact', 'background_color', 'profile_pic_url', 'question_type', 'question', 'text_color', 'is_sticker'];
+
+ if (count($storyQuestion) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story question is permitted. You added %d story questions.', count($storyQuestion)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['viewer_can_interact' => 1, 'background_color' => 1, 'profile_pic_url' => 1, 'question_type' => 1, 'question' => 1, 'text_color' => 1, 'is_sticker' => 1], $storyQuestion[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story question array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storyQuestion[0] as $k => $v) {
+ switch ($k) {
+ case 'z': // May be used for AR in the future, for now it's always 0.
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_can_interact':
+ if (!is_bool($v) || $v !== false) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'background_color':
+ case 'text_color':
+ if (!preg_match('/^[0-9a-fA-F]{6}$/', substr($v, 1))) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'question_type':
+ // At this time only text questions are supported.
+ if (!is_string($v) || $v !== 'text') {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'question':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'profile_pic_url':
+ if (!self::hasValidWebURLSyntax($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storyQuestion[0], array_flip($requiredKeys)), 'questions');
+ }
+
+ /**
+ * Verifies an array of story countdown.
+ *
+ * @param array $storyCountdown Array with story countdown key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryCountdown(
+ array $storyCountdown)
+ {
+ $requiredKeys = ['z', 'text', 'text_color', 'start_background_color', 'end_background_color', 'digit_color', 'digit_card_color', 'end_ts', 'following_enabled', 'is_sticker'];
+
+ if (count($storyCountdown) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story countdown is permitted. You added %d story countdowns.', count($storyCountdown)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['z' => 1, 'text' => 1, 'text_color' => 1, 'start_background_color' => 1, 'end_background_color' => 1, 'digit_color' => 1, 'digit_card_color' => 1, 'end_ts' => 1, 'following_enabled' => 1, 'is_sticker' => 1], $storyCountdown[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story countdown array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storyCountdown[0] as $k => $v) {
+ switch ($k) {
+ case 'z': // May be used for AR in the future, for now it's always 0.
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'text':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'text_color':
+ case 'start_background_color':
+ case 'end_background_color':
+ case 'digit_color':
+ case 'digit_card_color':
+ if (!preg_match('/^[0-9a-fA-F]{6}$/', substr($v, 1))) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'end_ts':
+ if (!is_int($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'following_enabled':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storyCountdown[0], array_flip($requiredKeys)), 'countdowns');
+ }
+
+ /**
+ * Verifies if tallies are valid.
+ *
+ * @param array[] $tallies Array with story poll key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ protected static function _throwIfInvalidStoryPollTallies(
+ array $tallies)
+ {
+ $requiredKeys = ['text', 'count', 'font_size'];
+ if (count($tallies) !== 2) {
+ throw new \InvalidArgumentException(sprintf('Missing data for tallies.'));
+ }
+
+ foreach ($tallies as $tallie) {
+ $missingKeys = array_keys(array_diff_key(['text' => 1, 'count' => 1, 'font_size' => 1], $tallie));
+
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for location array.', implode(', ', $missingKeys)));
+ }
+ foreach ($tallie as $k => $v) {
+ if (!in_array($k, $requiredKeys, true)) {
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" for story poll tallies.', $k));
+ }
+ switch ($k) {
+ case 'text':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for tallies array-key "%s".', $v, $k));
+ }
+ break;
+ case 'count':
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for tallies array-key "%s".', $v, $k));
+ }
+ break;
+ case 'font_size':
+ if (!is_float($v) || ($v < 17.5 || $v > 35.0)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for tallies array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Verifies an array of story mentions.
+ *
+ * @param array[] $storyMentions The array of all story mentions.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryMentions(
+ array $storyMentions)
+ {
+ $requiredKeys = ['user_id'];
+
+ foreach ($storyMentions as $mention) {
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['user_id' => 1], $mention));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for mention array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($mention as $k => $v) {
+ switch ($k) {
+ case 'user_id':
+ if (!ctype_digit($v) && (!is_int($v) || $v < 0)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story mention array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($mention, array_flip($requiredKeys)), 'story mentions');
+ }
+ }
+
+ /**
+ * Verifies if a story location sticker is valid.
+ *
+ * @param array[] $locationSticker Array with location sticker key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryLocationSticker(
+ array $locationSticker)
+ {
+ $requiredKeys = ['location_id', 'is_sticker'];
+ $missingKeys = array_keys(array_diff_key(['location_id' => 1, 'is_sticker' => 1], $locationSticker));
+
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for location array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($locationSticker as $k => $v) {
+ switch ($k) {
+ case 'location_id':
+ if (!is_string($v) && !is_numeric($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for location array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for hashtag array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($locationSticker, array_flip($requiredKeys)), 'location');
+ }
+
+ /**
+ * Verifies if a caption is valid for a hashtag and verifies an array of hashtags.
+ *
+ * @param string $captionText The caption for the story hashtag to verify.
+ * @param array[] $hashtags The array of all story hashtags.
+ *
+ * @throws \InvalidArgumentException If caption doesn't contain any hashtag,
+ * or if any tags are invalid.
+ */
+ public static function throwIfInvalidStoryHashtags(
+ $captionText,
+ array $hashtags)
+ {
+ $requiredKeys = ['tag_name', 'use_custom_title', 'is_sticker'];
+
+ // Extract all hashtags from the caption using a UTF-8 aware regex.
+ if (!preg_match_all('/#(\w+[^\x00-\x7F]?+)/u', $captionText, $tagsInCaption)) {
+ throw new \InvalidArgumentException('Invalid caption for hashtag.');
+ }
+
+ // Verify all provided hashtags.
+ foreach ($hashtags as $hashtag) {
+ $missingKeys = array_keys(array_diff_key(['tag_name' => 1, 'use_custom_title' => 1, 'is_sticker' => 1], $hashtag));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for hashtag array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($hashtag as $k => $v) {
+ switch ($k) {
+ case 'tag_name':
+ // Ensure that the hashtag format is valid.
+ self::throwIfInvalidHashtag($v);
+ // Verify that this tag exists somewhere in the caption to check.
+ if (!in_array($v, $tagsInCaption[1])) { // NOTE: UTF-8 aware.
+ throw new \InvalidArgumentException(sprintf('Tag name "%s" does not exist in the caption text.', $v));
+ }
+ break;
+ case 'use_custom_title':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for hashtag array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for hashtag array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($hashtag, array_flip($requiredKeys)), 'hashtag');
+ }
+ }
+
+ /**
+ * Verifies an attached media.
+ *
+ * @param array[] $attachedMedia Array containing the attached media data.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidAttachedMedia(
+ array $attachedMedia)
+ {
+ $attachedMedia = reset($attachedMedia);
+ $requiredKeys = ['media_id', 'is_sticker'];
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['media_id' => 1, 'is_sticker' => 1], $attachedMedia));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for attached media.', implode(', ', $missingKeys)));
+ }
+
+ if (!is_string($attachedMedia['media_id']) && !is_numeric($attachedMedia['media_id'])) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for media_id.', $attachedMedia['media_id']));
+ }
+
+ if (!is_bool($attachedMedia['is_sticker']) && $attachedMedia['is_sticker'] !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for attached media.', $attachedMedia['is_sticker']));
+ }
+
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($attachedMedia, array_flip($requiredKeys)), 'attached media');
+ }
+
+ /**
+ * Verifies a story sticker's placement parameters.
+ *
+ * There are many kinds of story stickers, such as hashtags, locations,
+ * mentions, etc. To place them on the media, the user must provide certain
+ * parameters for things like position and size. This function verifies all
+ * of those parameters and ensures that the sticker placement is valid.
+ *
+ * @param array $storySticker The array describing the story sticker placement.
+ * @param string $type What type of sticker this is.
+ *
+ * @throws \InvalidArgumentException If storySticker is missing keys or has invalid values.
+ */
+ protected static function _throwIfInvalidStoryStickerPlacement(
+ array $storySticker,
+ $type)
+ {
+ $requiredKeys = ['x', 'y', 'width', 'height', 'rotation'];
+
+ // Ensure that all required hashtag array keys exist.
+ $missingKeys = array_keys(array_diff_key(['x' => 1, 'y' => 1, 'width' => 1, 'height' => 1, 'rotation' => 0], $storySticker));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for "%s".', implode(', ', $missingKeys), $type));
+ }
+
+ // Check the individual array values.
+ foreach ($storySticker as $k => $v) {
+ if (!in_array($k, $requiredKeys, true)) {
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" for "%s".', $k, $type));
+ }
+ switch ($k) {
+ case 'x':
+ case 'y':
+ case 'width':
+ case 'height':
+ case 'rotation':
+ if (!is_float($v) || $v < 0.0 || $v > 1.0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for "%s" key "%s".', $v, $type, $k));
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Checks and validates a media item's type.
+ *
+ * @param string|int $mediaType The type of the media item. One of: "PHOTO", "VIDEO"
+ * "CAROUSEL", or the raw value of the Item's "getMediaType()" function.
+ *
+ * @throws \InvalidArgumentException If the type is invalid.
+ *
+ * @return string The verified final type; either "PHOTO", "VIDEO" or "CAROUSEL".
+ */
+ public static function checkMediaType(
+ $mediaType)
+ {
+ if (ctype_digit($mediaType) || is_int($mediaType)) {
+ if ($mediaType == Item::PHOTO) {
+ $mediaType = 'PHOTO';
+ } elseif ($mediaType == Item::VIDEO) {
+ $mediaType = 'VIDEO';
+ } elseif ($mediaType == Item::CAROUSEL) {
+ $mediaType = 'CAROUSEL';
+ }
+ }
+ if (!in_array($mediaType, ['PHOTO', 'VIDEO', 'CAROUSEL'], true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media type.', $mediaType));
+ }
+
+ return $mediaType;
+ }
+
+ public static function formatBytes(
+ $bytes,
+ $precision = 2)
+ {
+ $units = ['B', 'kB', 'mB', 'gB', 'tB'];
+
+ $bytes = max($bytes, 0);
+ $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+ $pow = min($pow, count($units) - 1);
+
+ $bytes /= pow(1024, $pow);
+
+ return round($bytes, $precision).''.$units[$pow];
+ }
+
+ public static function colouredString(
+ $string,
+ $colour)
+ {
+ $colours['black'] = '0;30';
+ $colours['dark_gray'] = '1;30';
+ $colours['blue'] = '0;34';
+ $colours['light_blue'] = '1;34';
+ $colours['green'] = '0;32';
+ $colours['light_green'] = '1;32';
+ $colours['cyan'] = '0;36';
+ $colours['light_cyan'] = '1;36';
+ $colours['red'] = '0;31';
+ $colours['light_red'] = '1;31';
+ $colours['purple'] = '0;35';
+ $colours['light_purple'] = '1;35';
+ $colours['brown'] = '0;33';
+ $colours['yellow'] = '1;33';
+ $colours['light_gray'] = '0;37';
+ $colours['white'] = '1;37';
+
+ $colored_string = '';
+
+ if (isset($colours[$colour])) {
+ $colored_string .= "\033[".$colours[$colour].'m';
+ }
+
+ $colored_string .= $string."\033[0m";
+
+ return $colored_string;
+ }
+
+ public static function getFilterCode(
+ $filter)
+ {
+ $filters = [];
+ $filters[0] = 'Normal';
+ $filters[615] = 'Lark';
+ $filters[614] = 'Reyes';
+ $filters[613] = 'Juno';
+ $filters[612] = 'Aden';
+ $filters[608] = 'Perpetua';
+ $filters[603] = 'Ludwig';
+ $filters[605] = 'Slumber';
+ $filters[616] = 'Crema';
+ $filters[24] = 'Amaro';
+ $filters[17] = 'Mayfair';
+ $filters[23] = 'Rise';
+ $filters[26] = 'Hudson';
+ $filters[25] = 'Valencia';
+ $filters[1] = 'X-Pro II';
+ $filters[27] = 'Sierra';
+ $filters[28] = 'Willow';
+ $filters[2] = 'Lo-Fi';
+ $filters[3] = 'Earlybird';
+ $filters[22] = 'Brannan';
+ $filters[10] = 'Inkwell';
+ $filters[21] = 'Hefe';
+ $filters[15] = 'Nashville';
+ $filters[18] = 'Sutro';
+ $filters[19] = 'Toaster';
+ $filters[20] = 'Walden';
+ $filters[14] = '1977';
+ $filters[16] = 'Kelvin';
+ $filters[-2] = 'OES';
+ $filters[-1] = 'YUV';
+ $filters[109] = 'Stinson';
+ $filters[106] = 'Vesper';
+ $filters[112] = 'Clarendon';
+ $filters[118] = 'Maven';
+ $filters[114] = 'Gingham';
+ $filters[107] = 'Ginza';
+ $filters[113] = 'Skyline';
+ $filters[105] = 'Dogpatch';
+ $filters[115] = 'Brooklyn';
+ $filters[111] = 'Moon';
+ $filters[117] = 'Helena';
+ $filters[116] = 'Ashby';
+ $filters[108] = 'Charmes';
+ $filters[640] = 'BrightContrast';
+ $filters[642] = 'CrazyColor';
+ $filters[643] = 'SubtleColor';
+
+ return array_search($filter, $filters);
+ }
+
+ /**
+ * Creates a folder if missing, or ensures that it is writable.
+ *
+ * @param string $folder The directory path.
+ *
+ * @return bool TRUE if folder exists and is writable, otherwise FALSE.
+ */
+ public static function createFolder(
+ $folder)
+ {
+ // Test write-permissions for the folder and create/fix if necessary.
+ if ((is_dir($folder) && is_writable($folder))
+ || (!is_dir($folder) && mkdir($folder, 0755, true))
+ || chmod($folder, 0755)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Recursively deletes a file/directory tree.
+ *
+ * @param string $folder The directory path.
+ * @param bool $keepRootFolder Whether to keep the top-level folder.
+ *
+ * @return bool TRUE on success, otherwise FALSE.
+ */
+ public static function deleteTree(
+ $folder,
+ $keepRootFolder = false)
+ {
+ // Handle bad arguments.
+ if (empty($folder) || !file_exists($folder)) {
+ return true; // No such file/folder exists.
+ } elseif (is_file($folder) || is_link($folder)) {
+ return @unlink($folder); // Delete file/link.
+ }
+
+ // Delete all children.
+ $files = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
+ \RecursiveIteratorIterator::CHILD_FIRST
+ );
+
+ foreach ($files as $fileinfo) {
+ $action = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
+ if (!@$action($fileinfo->getRealPath())) {
+ return false; // Abort due to the failure.
+ }
+ }
+
+ // Delete the root folder itself?
+ return !$keepRootFolder ? @rmdir($folder) : true;
+ }
+
+ /**
+ * Atomic filewriter.
+ *
+ * Safely writes new contents to a file using an atomic two-step process.
+ * If the script is killed before the write is complete, only the temporary
+ * trash file will be corrupted.
+ *
+ * The algorithm also ensures that 100% of the bytes were written to disk.
+ *
+ * @param string $filename Filename to write the data to.
+ * @param string $data Data to write to file.
+ * @param string $atomicSuffix Lets you optionally provide a different
+ * suffix for the temporary file.
+ *
+ * @return int|bool Number of bytes written on success, otherwise `FALSE`.
+ */
+ public static function atomicWrite(
+ $filename,
+ $data,
+ $atomicSuffix = 'atomictmp')
+ {
+ // Perform an exclusive (locked) overwrite to a temporary file.
+ $filenameTmp = sprintf('%s.%s', $filename, $atomicSuffix);
+ $writeResult = @file_put_contents($filenameTmp, $data, LOCK_EX);
+
+ // Only proceed if we wrote 100% of the data bytes to disk.
+ if ($writeResult !== false && $writeResult === strlen($data)) {
+ // Now move the file to its real destination (replaces if exists).
+ $moveResult = @rename($filenameTmp, $filename);
+ if ($moveResult === true) {
+ // Successful write and move. Return number of bytes written.
+ return $writeResult;
+ }
+ }
+
+ // We've failed. Remove the temporary file if it exists.
+ if (is_file($filenameTmp)) {
+ @unlink($filenameTmp);
+ }
+
+ return false; // Failed.
+ }
+
+ /**
+ * Creates an empty temp file with a unique filename.
+ *
+ * @param string $outputDir Folder to place the temp file in.
+ * @param string $namePrefix (optional) What prefix to use for the temp file.
+ *
+ * @throws \RuntimeException If the file cannot be created.
+ *
+ * @return string
+ */
+ public static function createTempFile(
+ $outputDir,
+ $namePrefix = 'TEMP')
+ {
+ // Automatically generates a name like "INSTATEMP_" or "INSTAVID_" etc.
+ $finalPrefix = sprintf('INSTA%s_', $namePrefix);
+
+ // Try to create the file (detects errors).
+ $tmpFile = @tempnam($outputDir, $finalPrefix);
+ if (!is_string($tmpFile)) {
+ throw new \RuntimeException(sprintf(
+ 'Unable to create temporary output file in "%s" (with prefix "%s").',
+ $outputDir, $finalPrefix
+ ));
+ }
+
+ return $tmpFile;
+ }
+
+ /**
+ * Closes a file pointer if it's open.
+ *
+ * Always use this function instead of fclose()!
+ *
+ * Unlike the normal fclose(), this function is safe to call multiple times
+ * since it only attempts to close the pointer if it's actually still open.
+ * The normal fclose() would give an annoying warning in that scenario.
+ *
+ * @param resource $handle A file pointer opened by fopen() or fsockopen().
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
+ public static function safe_fclose(
+ $handle)
+ {
+ if (is_resource($handle)) {
+ return fclose($handle);
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if a URL has valid "web" syntax.
+ *
+ * This function is Unicode-aware.
+ *
+ * Be aware that it only performs URL syntax validation! It doesn't check
+ * if the domain/URL is fully valid and actually reachable!
+ *
+ * It verifies that the URL begins with either the "http://" or "https://"
+ * protocol, and that it must contain a host with at least one period in it,
+ * and at least two characters after the period (in other words, a TLD). The
+ * rest of the string can be any sequence of non-whitespace characters.
+ *
+ * For example, "http://localhost" will not be seen as a valid web URL, and
+ * "http://www.google.com foobar" is not a valid web URL since there's a
+ * space in it. But "https://bing.com" and "https://a.com/foo" are valid.
+ * However, "http://a.abdabdbadbadbsa" is also seen as a valid URL, since
+ * the validation is pretty simple and doesn't verify the TLDs (there are
+ * too many now to catch them all and new ones appear constantly).
+ *
+ * @param string $url
+ *
+ * @return bool TRUE if valid web syntax, otherwise FALSE.
+ */
+ public static function hasValidWebURLSyntax(
+ $url)
+ {
+ return (bool) preg_match('/^https?:\/\/[^\s.\/]+\.[^\s.\/]{2}\S*$/iu', $url);
+ }
+
+ /**
+ * Extract all URLs from a text string.
+ *
+ * This function is Unicode-aware.
+ *
+ * @param string $text The string to scan for URLs.
+ *
+ * @return array An array of URLs and their individual components.
+ */
+ public static function extractURLs(
+ $text)
+ {
+ $urls = [];
+ if (preg_match_all(
+ // NOTE: This disgusting regex comes from the Android SDK, slightly
+ // modified by Instagram and then encoded by us into PHP format. We
+ // are NOT allowed to tweak this regex! It MUST match the official
+ // app so that our link-detection acts *exactly* like the real app!
+ // NOTE: Here is the "to PHP regex" conversion algorithm we used:
+ // https://github.com/mgp25/Instagram-API/issues/1445#issuecomment-318921867
+ '/((?:(http|https|Http|Https|rtsp|Rtsp):\/\/(?:(?:[a-zA-Z0-9$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,64}(?:\:(?:[a-zA-Z0-9$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,25})?\@)?)?((?:(?:[a-zA-Z0-9\x{00A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\_][a-zA-Z0-9\x{00A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\_\-]{0,64}\.)+(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(?:\x{03B4}\x{03BF}\x{03BA}\x{03B9}\x{03BC}\x{03AE}|\x{0438}\x{0441}\x{043F}\x{044B}\x{0442}\x{0430}\x{043D}\x{0438}\x{0435}|\x{0440}\x{0444}|\x{0441}\x{0440}\x{0431}|\x{05D8}\x{05E2}\x{05E1}\x{05D8}|\x{0622}\x{0632}\x{0645}\x{0627}\x{06CC}\x{0634}\x{06CC}|\x{0625}\x{062E}\x{062A}\x{0628}\x{0627}\x{0631}|\x{0627}\x{0644}\x{0627}\x{0631}\x{062F}\x{0646}|\x{0627}\x{0644}\x{062C}\x{0632}\x{0627}\x{0626}\x{0631}|\x{0627}\x{0644}\x{0633}\x{0639}\x{0648}\x{062F}\x{064A}\x{0629}|\x{0627}\x{0644}\x{0645}\x{063A}\x{0631}\x{0628}|\x{0627}\x{0645}\x{0627}\x{0631}\x{0627}\x{062A}|\x{0628}\x{06BE}\x{0627}\x{0631}\x{062A}|\x{062A}\x{0648}\x{0646}\x{0633}|\x{0633}\x{0648}\x{0631}\x{064A}\x{0629}|\x{0641}\x{0644}\x{0633}\x{0637}\x{064A}\x{0646}|\x{0642}\x{0637}\x{0631}|\x{0645}\x{0635}\x{0631}|\x{092A}\x{0930}\x{0940}\x{0915}\x{094D}\x{0937}\x{093E}|\x{092D}\x{093E}\x{0930}\x{0924}|\x{09AD}\x{09BE}\x{09B0}\x{09A4}|\x{0A2D}\x{0A3E}\x{0A30}\x{0A24}|\x{0AAD}\x{0ABE}\x{0AB0}\x{0AA4}|\x{0B87}\x{0BA8}\x{0BCD}\x{0BA4}\x{0BBF}\x{0BAF}\x{0BBE}|\x{0B87}\x{0BB2}\x{0B99}\x{0BCD}\x{0B95}\x{0BC8}|\x{0B9A}\x{0BBF}\x{0B99}\x{0BCD}\x{0B95}\x{0BAA}\x{0BCD}\x{0BAA}\x{0BC2}\x{0BB0}\x{0BCD}|\x{0BAA}\x{0BB0}\x{0BBF}\x{0B9F}\x{0BCD}\x{0B9A}\x{0BC8}|\x{0C2D}\x{0C3E}\x{0C30}\x{0C24}\x{0C4D}|\x{0DBD}\x{0D82}\x{0D9A}\x{0DCF}|\x{0E44}\x{0E17}\x{0E22}|\x{30C6}\x{30B9}\x{30C8}|\x{4E2D}\x{56FD}|\x{4E2D}\x{570B}|\x{53F0}\x{6E7E}|\x{53F0}\x{7063}|\x{65B0}\x{52A0}\x{5761}|\x{6D4B}\x{8BD5}|\x{6E2C}\x{8A66}|\x{9999}\x{6E2F}|\x{D14C}\x{C2A4}\x{D2B8}|\x{D55C}\x{AD6D}|xn\-\-0zwm56d|xn\-\-11b5bs3a9aj6g|xn\-\-3e0b707e|xn\-\-45brj9c|xn\-\-80akhbyknj4f|xn\-\-90a3ac|xn\-\-9t4b11yi5a|xn\-\-clchc0ea0b2g2a9gcd|xn\-\-deba0ad|xn\-\-fiqs8s|xn\-\-fiqz9s|xn\-\-fpcrj9c3d|xn\-\-fzc2c9e2c|xn\-\-g6w251d|xn\-\-gecrj9c|xn\-\-h2brj9c|xn\-\-hgbk6aj7f53bba|xn\-\-hlcj6aya9esc7a|xn\-\-j6w193g|xn\-\-jxalpdlp|xn\-\-kgbechtv|xn\-\-kprw13d|xn\-\-kpry57d|xn\-\-lgbbat1ad8j|xn\-\-mgbaam7a8h|xn\-\-mgbayh7gpa|xn\-\-mgbbh1a71e|xn\-\-mgbc0a9azcg|xn\-\-mgberp4a5d4ar|xn\-\-o3cw4h|xn\-\-ogbpf8fl|xn\-\-p1ai|xn\-\-pgbs0dh|xn\-\-s9brj9c|xn\-\-wgbh1c|xn\-\-wgbl6a|xn\-\-xkc2al3hye2a|xn\-\-xkc2dl3a5ee0h|xn\-\-yfro4i67o|xn\-\-ygbi2ammx|xn\-\-zckzah|xxx)|y[et]|z[amw]))|(?:(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])))(?:\:\d{1,5})?)(\/(?:(?:[a-zA-Z0-9\x{00A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\;\/\?\:\@\&\=\#\~\-\.\+\!\*\'\(\)\,\_])|(?:\%[a-fA-F0-9]{2}))*)?(?:\b|$)/iu',
+ $text,
+ $matches,
+ PREG_SET_ORDER
+ ) !== false) {
+ foreach ($matches as $match) {
+ $urls[] = [
+ 'fullUrl' => $match[0], // "https://foo:bar@www.bing.com/?foo=#test"
+ 'baseUrl' => $match[1], // "https://foo:bar@www.bing.com"
+ 'protocol' => $match[2], // "https" (empty if no protocol)
+ 'domain' => $match[3], // "www.bing.com"
+ 'path' => isset($match[4]) ? $match[4] : '', // "/?foo=#test"
+ ];
+ }
+ }
+
+ return $urls;
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/React/ConnectorTest.php b/instafeed/vendor/mgp25/instagram-php/tests/React/ConnectorTest.php
new file mode 100755
index 0000000..9414d97
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/React/ConnectorTest.php
@@ -0,0 +1,211 @@
+getMethod($method);
+ $reflectionMethod->setAccessible(true);
+
+ return $reflectionMethod->invokeArgs($object, $args);
+ }
+
+ /**
+ * @return Connector
+ */
+ protected function _createConnector()
+ {
+ /** @var Instagram $instagramMock */
+ $instagramMock = $this->createMock(Instagram::class);
+ /** @var LoopInterface $loopMock */
+ $loopMock = $this->createMock(LoopInterface::class);
+
+ return new Connector($instagramMock, $loopMock);
+ }
+
+ public function testEmptyProxyConfigShouldReturnNull()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ null,
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, null)
+ );
+ }
+
+ public function testSingleProxyConfigShouldReturnAsIs()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ '127.0.0.1:3128',
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, '127.0.0.1:3128')
+ );
+ }
+
+ public function testHttpProxyShouldThrow()
+ {
+ $connector = $this->_createConnector();
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('No proxy with CONNECT method found');
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, [
+ 'http' => '127.0.0.1:3128',
+ ]);
+ }
+
+ public function testMustPickHttpsProxy()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ '127.0.0.1:3128',
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, [
+ 'http' => '127.0.0.1:3127',
+ 'https' => '127.0.0.1:3128',
+ ])
+ );
+ }
+
+ public function testShouldReturnNullWhenHostInExceptions()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ null,
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, [
+ 'https' => '127.0.0.1:3128',
+ 'no' => ['.facebook.com'],
+ ])
+ );
+ }
+
+ public function testVerifyPeerEnabled()
+ {
+ $connector = $this->_createConnector();
+
+ $context = $this->_callProtectedMethod($connector, '_getSecureContext', true);
+ $this->assertInternalType('array', $context);
+ $this->assertCount(3, $context);
+ $this->assertArrayHasKey('verify_peer', $context);
+ $this->assertEquals(true, $context['verify_peer']);
+ $this->assertArrayHasKey('verify_peer_name', $context);
+ $this->assertEquals(true, $context['verify_peer_name']);
+ $this->assertArrayHasKey('allow_self_signed', $context);
+ $this->assertEquals(false, $context['allow_self_signed']);
+ }
+
+ public function testVerifyPeerDisabled()
+ {
+ $connector = $this->_createConnector();
+
+ $context = $this->_callProtectedMethod($connector, '_getSecureContext', false);
+ $this->assertInternalType('array', $context);
+ $this->assertCount(2, $context);
+ $this->assertArrayHasKey('verify_peer', $context);
+ $this->assertEquals(false, $context['verify_peer']);
+ $this->assertArrayHasKey('verify_peer_name', $context);
+ $this->assertEquals(false, $context['verify_peer_name']);
+ }
+
+ public function testVerifyPeerEnabledWithCustomCa()
+ {
+ $connector = $this->_createConnector();
+
+ $context = $this->_callProtectedMethod($connector, '_getSecureContext', __FILE__);
+ $this->assertInternalType('array', $context);
+ $this->assertCount(4, $context);
+ $this->assertArrayHasKey('cafile', $context);
+ $this->assertEquals(__FILE__, $context['cafile']);
+ $this->assertArrayHasKey('verify_peer', $context);
+ $this->assertEquals(true, $context['verify_peer']);
+ $this->assertArrayHasKey('verify_peer_name', $context);
+ $this->assertEquals(true, $context['verify_peer_name']);
+ $this->assertArrayHasKey('allow_self_signed', $context);
+ $this->assertEquals(false, $context['allow_self_signed']);
+ }
+
+ public function testVerifyPeerEnabledWithCustomCaMissing()
+ {
+ $connector = $this->_createConnector();
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('CA bundle not found');
+ $this->_callProtectedMethod($connector, '_getSecureContext', __FILE__.'.missing');
+ }
+
+ public function testVerifyPeerWithInvalidConfig()
+ {
+ $connector = $this->_createConnector();
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid verify request option');
+ $this->_callProtectedMethod($connector, '_getSecureContext', [__FILE__]);
+ }
+
+ public function testSecureConnector()
+ {
+ $connector = $this->_createConnector();
+
+ $secureConnector = $this->_callProtectedMethod($connector, '_getSecureConnector', [], null);
+ $this->assertInstanceOf(SecureConnector::class, $secureConnector);
+ }
+
+ public function testProxyWrappers()
+ {
+ $proxies = [
+ 'socks://127.0.0.1:1080' => SocksProxy::class,
+ 'socks4://127.0.0.1:1080' => SocksProxy::class,
+ 'socks4a://127.0.0.1:1080' => SocksProxy::class,
+ 'socks5://127.0.0.1:1080' => SocksProxy::class,
+ 'http://127.0.0.1:3128' => HttpConnectProxy::class,
+ 'https://127.0.0.1:3128' => HttpConnectProxy::class,
+ '127.0.0.1:3128' => HttpConnectProxy::class,
+ ];
+ foreach ($proxies as $proxy => $targetClass) {
+ $connector = $this->_createConnector();
+ /** @var ConnectorInterface $baseConnector */
+ $baseConnector = $this->createMock(ConnectorInterface::class);
+
+ $this->assertInstanceOf(
+ $targetClass,
+ $this->_callProtectedMethod($connector, '_wrapConnectorIntoProxy', $baseConnector, $proxy)
+ );
+ }
+ }
+
+ public function testProxyWithoutWrapperShouldThrow()
+ {
+ $connector = $this->_createConnector();
+ /** @var ConnectorInterface $baseConnector */
+ $baseConnector = $this->createMock(ConnectorInterface::class);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Unsupported proxy scheme');
+ $this->_callProtectedMethod($connector, '_wrapConnectorIntoProxy', $baseConnector, 'tcp://127.0.0.1');
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/IndicateActivityTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/IndicateActivityTest.php
new file mode 100755
index 0000000..d68b2f0
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/IndicateActivityTest.php
@@ -0,0 +1,57 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new IndicateActivity('abc', '123');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new IndicateActivity('-1', '123');
+ }
+
+ public function testCommandOutputWhenTrue()
+ {
+ $command = new IndicateActivity('123', true);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","activity_status":"1","action":"indicate_activity"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWhenFalse()
+ {
+ $command = new IndicateActivity('123', false);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","activity_status":"0","action":"indicate_activity"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new IndicateActivity('123', true, ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new IndicateActivity('123', true, ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","activity_status":"1","action":"indicate_activity"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/IrisSubscribeTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/IrisSubscribeTest.php
new file mode 100755
index 0000000..009caef
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/IrisSubscribeTest.php
@@ -0,0 +1,22 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid Iris sequence identifier');
+ new IrisSubscribe(-1);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new IrisSubscribe(777);
+ $this->assertEquals('{"seq_id":777}', json_encode($command));
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/MarkSeenTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/MarkSeenTest.php
new file mode 100755
index 0000000..704f38d
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/MarkSeenTest.php
@@ -0,0 +1,55 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new MarkSeen('abc', '123');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new MarkSeen('-1', '123');
+ }
+
+ public function testNonNumericThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new MarkSeen('123', 'abc');
+ }
+
+ public function testNegativeThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new MarkSeen('123', '-1');
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new MarkSeen('123', '456');
+ $this->assertEquals(
+ '{"thread_id":"123","item_id":"456","action":"mark_seen"}',
+ json_encode($command)
+ );
+ }
+
+ public function testCustomClientContextIsIgnored()
+ {
+ $command = new MarkSeen('123', '456', ['client_context' => 'test']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_id":"456","action":"mark_seen"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendHashtagTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendHashtagTest.php
new file mode 100755
index 0000000..92ed505
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendHashtagTest.php
@@ -0,0 +1,105 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendHashtag('abc', 'somehashtag');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendHashtag('-1', 'somehashtag');
+ }
+
+ public function testNonStringHashtagShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('be a string');
+ new SendHashtag('123', 2.5);
+ }
+
+ public function testEmptyHashtagShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not be empty');
+ new SendHashtag('123', '');
+ }
+
+ public function testNumberSignOnlyShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not be empty');
+ new SendHashtag('123', '#');
+ }
+
+ public function testTwoWordsShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be one word');
+ new SendHashtag('123', '#cool pics');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendHashtag('123', 'somehashtag', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendHashtag('123', 'somehashtag');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"hashtag","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithTrim()
+ {
+ $command = new SendHashtag('123', '#somehashtag ');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"hashtag","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendHashtag('123', 'somehashtag', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"hashtag","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendHashtag('123', 'somehashtag', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendHashtag('123', 'somehashtag', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"hashtag","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLikeTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLikeTest.php
new file mode 100755
index 0000000..26f6b70
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLikeTest.php
@@ -0,0 +1,48 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLike('abc');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLike('-1');
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendLike('123');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"like","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendLike('123', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendLike('123', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"like","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLocationTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLocationTest.php
new file mode 100755
index 0000000..3f87a1b
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLocationTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLocation('abc', '123456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLocation('-1', '123456');
+ }
+
+ public function testInvalidLocationIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendLocation('123', 'location');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendLocation('123', '123456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendLocation('123', '123456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"location","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","venue_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendLocation('123', '123456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"location","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","venue_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendLocation('123', '123456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendLocation('123', '123456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"location","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"123456","action":"send_item","venue_id":"123456"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendPostTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendPostTest.php
new file mode 100755
index 0000000..9b5465c
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendPostTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendPost('abc', '123_456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendPost('-1', '123_456');
+ }
+
+ public function testInvalidMediaIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendPost('123', 'abc_def');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendPost('123', '123_456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendPost('123', '123_456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"media_share","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item","media_id":"123_456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendPost('123', '123_456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"media_share","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item","media_id":"123_456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendPost('123', '123_456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendPost('123', '123_456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"media_share","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","action":"send_item","media_id":"123_456"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendProfileTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendProfileTest.php
new file mode 100755
index 0000000..912c6e8
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendProfileTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendProfile('abc', '123456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendProfile('-1', '123456');
+ }
+
+ public function testInvalidUserIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendProfile('123', 'username');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendProfile('123', '123456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendProfile('123', '123456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"profile","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","profile_user_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendProfile('123', '123456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"profile","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","profile_user_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendProfile('123', '123456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendProfile('123', '123456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"profile","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"123456","action":"send_item","profile_user_id":"123456"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendReactionTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendReactionTest.php
new file mode 100755
index 0000000..b6edbed
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendReactionTest.php
@@ -0,0 +1,85 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendReaction('abc', '123', 'like', 'created');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendReaction('-1', '123', 'like', 'created');
+ }
+
+ public function testNonNumericThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new SendReaction('123', 'abc', 'like', 'created');
+ }
+
+ public function testNegativeThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new SendReaction('123', '-1', 'like', 'created');
+ }
+
+ public function testUnknownReactionTypeShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a supported reaction');
+ new SendReaction('123', '456', 'angry', 'created');
+ }
+
+ public function testUnknownReactionStatusShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a supported reaction status');
+ new SendReaction('123', '456', 'like', 'removed');
+ }
+
+ public function testCommandOutputWhenLikeIsCreated()
+ {
+ $command = new SendReaction('123', '456', 'like', 'created');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"reaction","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","reaction_type":"like","reaction_status":"created","item_id":"456","node_type":"item","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWhenLikeIsDeleted()
+ {
+ $command = new SendReaction('123', '456', 'like', 'deleted');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"reaction","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","reaction_type":"like","reaction_status":"deleted","item_id":"456","node_type":"item","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendReaction('123', '456', 'like', 'created', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendReaction('123', '456', 'like', 'created', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"reaction","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","reaction_type":"like","reaction_status":"created","item_id":"456","node_type":"item","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendStoryTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendStoryTest.php
new file mode 100755
index 0000000..c780482
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendStoryTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendStory('abc', '123_456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendStory('-1', '123_456');
+ }
+
+ public function testInvalidStoryIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendStory('123', 'abc_def');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendStory('123', '123_456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendStory('123', '123_456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"story_share","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123_456","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendStory('123', '123_456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"story_share","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123_456","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendStory('123', '123_456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendStory('123', '123_456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"story_share","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"123_456","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendTextTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendTextTest.php
new file mode 100755
index 0000000..78faf91
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/SendTextTest.php
@@ -0,0 +1,62 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendText('abc', 'test');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendText('-1', 'test');
+ }
+
+ public function testEmptyTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('can not be empty');
+ new SendText('123', '');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendText('123', null);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendText('123', 'test');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"text","text":"test","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendText('123', 'test', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendText('123', 'test', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"text","text":"test","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/UpdateSubscriptionsTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/UpdateSubscriptionsTest.php
new file mode 100755
index 0000000..c5b7a9a
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Command/UpdateSubscriptionsTest.php
@@ -0,0 +1,22 @@
+assertEquals(
+ '{"sub":["ig/live_notification_subscribe/1111111111","ig/u/v1/1111111111"]}',
+ json_encode($command, JSON_UNESCAPED_SLASHES)
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/DirectTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/DirectTest.php
new file mode 100755
index 0000000..7014747
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/DirectTest.php
@@ -0,0 +1,308 @@
+createMock(EventEmitterInterface::class);
+ // listeners() call is at index 0.
+ $target->expects($this->at(1))->method('emit')->with(
+ 'thread-item-created',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(3, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('11111111111111111111111111111111111', $arguments[1]);
+ $this->assertArrayHasKey(2, $arguments);
+ $this->assertInstanceOf(DirectThreadItem::class, $arguments[2]);
+ /** @var DirectThreadItem $item */
+ $item = $arguments[2];
+ $this->assertEquals('TEXT', $item->getText());
+
+ return true;
+ })
+ );
+ // listeners() call is at index 2.
+ $target->expects($this->at(3))->method('emit')->with(
+ 'unseen-count-update',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('inbox', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(DirectSeenItemPayload::class, $arguments[1]);
+ /** @var DirectSeenItemPayload $payload */
+ $payload = $arguments[1];
+ $this->assertEquals(1, $payload->getCount());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/direct_v2/threads/111111111111111111111111111111111111111/items/11111111111111111111111111111111111","value":"{\"item_id\": \"11111111111111111111111111111111111\", \"user_id\": 1111111111, \"timestamp\": 1111111111111111, \"item_type\": \"text\", \"text\": \"TEXT\"}","doublePublish":true},{"op":"replace","path":"/direct_v2/inbox/unseen_count","value":"1","ts":"1111111111111111","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadUpdate()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-updated',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(DirectThread::class, $arguments[1]);
+ /** @var DirectThread $thread */
+ $thread = $arguments[1];
+ $this->assertEquals('111111111111111111111111111111111111111', $thread->getThreadId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"replace","path":"/direct_v2/inbox/threads/111111111111111111111111111111111111111","value":"{\"thread_id\": \"111111111111111111111111111111111111111\"}","doublePublish":true}],"publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadItemRemoval()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-item-removed',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('11111111111111111111111111111111111', $arguments[1]);
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"remove","path":"/direct_v2/threads/111111111111111111111111111111111111111/items/11111111111111111111111111111111111","value":"11111111111111111111111111111111111","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadActivity()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-activity',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(ThreadActivity::class, $arguments[1]);
+ /** @var ThreadActivity $activity */
+ $activity = $arguments[1];
+ $this->assertEquals(1, $activity->getActivityStatus());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/direct_v2/threads/111111111111111111111111111111111111111/activity_indicator_id/deadbeef-dead-beef-dead-beefdeadbeef","value":"{\"timestamp\": 1111111111111111, \"sender_id\": \"1111111111\", \"ttl\": 12000, \"activity_status\": 1}","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadHasSeen()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-seen',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(3, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('1111111111', $arguments[1]);
+ $this->assertArrayHasKey(2, $arguments);
+ $this->assertInstanceOf(DirectThreadLastSeenAt::class, $arguments[2]);
+ /** @var DirectThreadLastSeenAt $lastSeen */
+ $lastSeen = $arguments[2];
+ $this->assertEquals('11111111111111111111111111111111111', $lastSeen->getItemId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"replace","path":"/direct_v2/threads/111111111111111111111111111111111111111/participants/1111111111/has_seen","value":"{\"timestamp\": 1111111111111111, \"item_id\": 11111111111111111111111111111111111}","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadActionBadge()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'direct-story-action',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(ActionBadge::class, $arguments[1]);
+ /** @var ActionBadge $actionBadge */
+ $actionBadge = $arguments[1];
+ $this->assertEquals('raven_delivered', $actionBadge->getActionType());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"replace","path":"/direct_v2/visual_action_badge/111111111111111111111111111111111111111","value":"{\"action_type\": \"raven_delivered\", \"action_count\": 1, \"action_timestamp\": 1111111111111111}","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":3}'
+ ));
+ }
+
+ public function testStoryCreation()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ // listeners() call is at index 0.
+ $target->expects($this->at(1))->method('emit')->with(
+ 'direct-story-updated',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(3, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('11111111111111111111111111111111111', $arguments[1]);
+ $this->assertArrayHasKey(2, $arguments);
+ $this->assertInstanceOf(DirectThreadItem::class, $arguments[2]);
+ /** @var DirectThreadItem $item */
+ $item = $arguments[2];
+ $this->assertEquals('raven_media', $item->getItemType());
+
+ return true;
+ })
+ );
+ // listeners() call is at index 2.
+ $target->expects($this->at(3))->method('emit')->with(
+ 'unseen-count-update',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('visual_inbox', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(DirectSeenItemPayload::class, $arguments[1]);
+ /** @var DirectSeenItemPayload $payload */
+ $payload = $arguments[1];
+ $this->assertEquals(1, $payload->getCount());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/direct_v2/visual_threads/111111111111111111111111111111111111111/items/11111111111111111111111111111111111","value":"{\"item_id\": \"11111111111111111111111111111111111\", \"user_id\": 1111111111, \"timestamp\": 1111111111111111, \"item_type\": \"raven_media\", \"seen_user_ids\": [], \"reply_chain_count\": 0, \"view_mode\": \"once\"}","doublePublish":true},{"op":"replace","path":"/direct_v2/visual_inbox/unseen_count","value":"1","ts":"1111111111111111","doublePublish":true}],"version":"9.6.0","publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":3}'
+ ));
+ }
+
+ public function testSendItemAck()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'client-context-ack',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(AckAction::class, $arguments[0]);
+ /** @var AckAction $ack */
+ $ack = $arguments[0];
+ $this->assertEquals('11111111111111111111111111111111111', $ack->getPayload()->getItemId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"action": "item_ack", "status_code": "200", "payload": {"client_context": "deadbeef-dead-beef-dead-beefdeadbeef", "item_id": "11111111111111111111111111111111111", "timestamp": "1111111111111111", "thread_id": "111111111111111111111111111111111111111"}, "status": "ok"}'
+ ));
+ }
+
+ public function testActivityAck()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'client-context-ack',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(AckAction::class, $arguments[0]);
+ /** @var AckAction $ack */
+ $ack = $arguments[0];
+ $this->assertEquals('deadbeef-dead-beef-dead-beefdeadbeef', $ack->getPayload()->getClientContext());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"action": "item_ack", "status_code": "200", "payload": {"activity_status": 1, "indicate_activity_ts": 1111111111111111, "client_context": "deadbeef-dead-beef-dead-beefdeadbeef", "ttl": 10000}, "status": "ok"}'
+ ));
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/IrisHandlerTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/IrisHandlerTest.php
new file mode 100755
index 0000000..c8a15d7
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/IrisHandlerTest.php
@@ -0,0 +1,77 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'iris-subscribed',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(IrisSubscribeAck::class, $arguments[0]);
+ /** @var IrisSubscribeAck $ack */
+ $ack = $arguments[0];
+ $this->assertTrue($ack->getSucceeded());
+ $this->assertEquals(666, $ack->getSeqId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new IrisHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"succeeded":true,"seq_id":666,"error_type":null,"error_message":null,"subscribed_at_ms":1111111111111}'
+ ));
+ }
+
+ public function testQueueOverlow()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('IrisQueueOverflowException');
+
+ $handler = new IrisHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"succeeded":false,"seq_id":null,"error_type":1,"error_message":"IrisQueueOverflowException","subscribed_at_ms":null}'
+ ));
+ }
+
+ public function testQueueUnderflow()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('IrisQueueUnderflowException');
+
+ $handler = new IrisHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"succeeded":false,"seq_id":null,"error_type":1,"error_message":"IrisQueueUnderflowException","subscribed_at_ms":null}'
+ ));
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/LiveTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/LiveTest.php
new file mode 100755
index 0000000..df3663e
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/LiveTest.php
@@ -0,0 +1,74 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'live-started',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(LiveBroadcast::class, $arguments[0]);
+ /** @var LiveBroadcast $live */
+ $live = $arguments[0];
+ $this->assertEquals('11111111111111111', $live->getBroadcastId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new LiveHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/broadcast/11111111111111111/started","value":"{\"broadcast_id\": 11111111111111111, \"user\": {\"pk\": 1111111111, \"username\": \"USERNAME\", \"full_name\": \"\", \"is_private\": true, \"profile_pic_url\": \"\", \"profile_pic_id\": \"\", \"is_verified\": false}, \"published_time\": 1234567890, \"is_periodic\": 0, \"broadcast_message\": \"\", \"display_notification\": true}","doublePublish":true}],"lazy":false,"publisher":1111111111,"version":"9.7.0","publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":2}'
+ ));
+ }
+
+ public function testStopBroadcast()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'live-stopped',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(LiveBroadcast::class, $arguments[0]);
+ /** @var LiveBroadcast $live */
+ $live = $arguments[0];
+ $this->assertEquals('11111111111111111', $live->getBroadcastId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new LiveHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"remove","path":"/broadcast/11111111111111111/ended","value":"{\"broadcast_id\": 11111111111111111, \"user\": {\"pk\": 1111111111, \"username\": \"USERNAME\", \"full_name\": \"NAME\", \"is_private\": true, \"profile_pic_url\": \"\", \"profile_pic_id\": \"\", \"is_verified\": false}, \"published_time\": 1234567890, \"is_periodic\": 0, \"broadcast_message\": \"\", \"display_notification\": false}","doublePublish":true}],"lazy":false,"publisher":1111111111,"version":"10.8.0","publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/PresenceTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/PresenceTest.php
new file mode 100755
index 0000000..ea9c2a2
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/PresenceTest.php
@@ -0,0 +1,63 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'presence',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(UserPresence::class, $arguments[0]);
+ /** @var UserPresence $payload */
+ $payload = $arguments[0];
+ $this->assertEquals('1111111111', $payload->getUserId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new PresenceHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"presence_event":{"user_id":"1111111111","is_active":true,"last_activity_at_ms":"123456789012","in_threads":null}}'
+ ));
+ }
+
+ public function testInvalidData()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('Invalid presence');
+
+ $handler = new PresenceHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{}'
+ ));
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/RegionHintTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/RegionHintTest.php
new file mode 100755
index 0000000..d0f6f58
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Handler/RegionHintTest.php
@@ -0,0 +1,71 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'region-hint',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('ASH', $arguments[0]);
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new RegionHintHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ 'ASH'
+ ));
+ }
+
+ public function testEmptyRegionHint()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('Invalid region hint');
+
+ $handler = new RegionHintHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ ''
+ ));
+ }
+
+ public function testNullRegionHint()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('Invalid region hint');
+
+ $handler = new RegionHintHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ null
+ ));
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Subscription/AppPresenceTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Subscription/AppPresenceTest.php
new file mode 100755
index 0000000..64b6d54
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Subscription/AppPresenceTest.php
@@ -0,0 +1,18 @@
+assertEquals(
+ '1/graphqlsubscriptions/17846944882223835/{"input_data":{"client_subscription_id":"deadbeef-dead-beef-dead-beefdeadbeef"}}',
+ (string) $subscription
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Subscription/ZeroProvisionTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Subscription/ZeroProvisionTest.php
new file mode 100755
index 0000000..993e2fa
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Realtime/Subscription/ZeroProvisionTest.php
@@ -0,0 +1,18 @@
+assertRegExp(
+ '#^1/graphqlsubscriptions/17913953740109069/{"input_data":{"client_subscription_id":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","device_id":"deadbeef-dead-beef-dead-beefdeadbeef"}}#',
+ (string) $subscription
+ );
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/tests/Response/ExceptionsTest.php b/instafeed/vendor/mgp25/instagram-php/tests/Response/ExceptionsTest.php
new file mode 100755
index 0000000..2b20ace
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/tests/Response/ExceptionsTest.php
@@ -0,0 +1,112 @@
+expectException(LoginRequiredException::class);
+ $this->expectExceptionMessage('Login required');
+ $response = $this->_makeResponse('{"message":"login_required", "logout_reason": 2, "status": "fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testFeedbackRequiredException()
+ {
+ $this->expectException(FeedbackRequiredException::class);
+ $this->expectExceptionMessage('Feedback required');
+ $response = $this->_makeResponse('{"message":"feedback_required","spam":true,"feedback_title":"You\u2019re Temporarily Blocked","feedback_message":"It looks like you were misusing this feature by going too fast. You\u2019ve been blocked from using it.\n\nLearn more about blocks in the Help Center. We restrict certain content and actions to protect our community. Tell us if you think we made a mistake.","feedback_url":"WUT","feedback_appeal_label":"Report problem","feedback_ignore_label":"OK","feedback_action":"report_problem","status":"fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testConsentRequiredException()
+ {
+ $this->expectException(ConsentRequiredException::class);
+ $this->expectExceptionMessage('Consent required');
+ $response = $this->_makeResponse('{"message":"consent_required","consent_data":{"headline":"Updates to Our Terms and Data Policy","content":"We\'ve updated our Terms and made some changes to our Data Policy. Please take a moment to review these changes and let us know that you agree to them.\n\nYou need to finish reviewing this information before you can use Instagram.","button_text":"Review Now"},"status":"fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testCheckpointRequiredException()
+ {
+ $this->expectException(CheckpointRequiredException::class);
+ $this->expectExceptionMessage('Checkpoint required');
+ $response = $this->_makeResponse('{"message":"checkpoint_required","checkpoint_url":"WUT","lock":true,"status":"fail","error_type":"checkpoint_challenge_required"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testChallengeRequiredException()
+ {
+ $this->expectException(ChallengeRequiredException::class);
+ $this->expectExceptionMessage('Challenge required');
+ $response = $this->_makeResponse('{"message":"challenge_required","challenge":{"url":"https://i.instagram.com/challenge/","api_path":"/challenge/","hide_webview_header":false,"lock":true,"logout":false,"native_flow":true},"status":"fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testIncorrectPasswordException()
+ {
+ $this->expectException(IncorrectPasswordException::class);
+ $this->expectExceptionMessageRegExp('/password.*incorrect/i');
+ $response = $this->_makeResponse('{"message":"The password you entered is incorrect. Please try again.","invalid_credentials":true,"error_title":"Incorrect password for WUT","buttons":[{"title":"Try Again","action":"dismiss"}],"status":"fail","error_type":"bad_password"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testAccountDisabledException()
+ {
+ $this->expectException(AccountDisabledException::class);
+ $this->expectExceptionMessageRegExp('/account.*disabled/i');
+ $response = $this->_makeResponse('{"message":"Your account has been disabled for violating our terms. Learn how you may be able to restore your account."}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testInvalidUserException()
+ {
+ $this->expectException(InvalidUserException::class);
+ $this->expectExceptionMessageRegExp('/check.*username/i');
+ $response = $this->_makeResponse('{"message":"The username you entered doesn\'t appear to belong to an account. Please check your username and try again.","invalid_credentials":true,"error_title":"Incorrect Username","buttons":[{"title":"Try Again","action":"dismiss"}],"status":"fail","error_type":"invalid_user"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testSentryBlockException()
+ {
+ $this->expectException(SentryBlockException::class);
+ $this->expectExceptionMessageRegExp('/problem.*request/i');
+ $response = $this->_makeResponse('{"message":"Sorry, there was a problem with your request.","status":"fail","error_type":"sentry_block"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testInvalidSmsCodeException()
+ {
+ $this->expectException(InvalidSmsCodeException::class);
+ $this->expectExceptionMessageRegExp('/check.*code/i');
+ $response = $this->_makeResponse('{"message":"Please check the security code we sent you and try again.","status":"fail","error_type":"sms_code_validation_code_invalid"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+}
diff --git a/instafeed/vendor/mgp25/instagram-php/webwarning.htm b/instafeed/vendor/mgp25/instagram-php/webwarning.htm
new file mode 100755
index 0000000..a908f74
--- /dev/null
+++ b/instafeed/vendor/mgp25/instagram-php/webwarning.htm
@@ -0,0 +1,9 @@
+
Instagram-API Warning!
+
Can I run this library via a website?
+
No. Don't do it. You cannot safely use this or any other 3rd party Instagram libraries directly via a website!
+
This library (and all other 3rd party reverse engineered Instagram libraries), is made for command line usage in a terminal by running the script like a program (such as php yourscript.php). We do not recommend running this library via a web browser! Because browsers will terminate the PHP process as soon as the user closes their connection to the page (closes the tab, or presses "Stop loading page"), which means that your script can terminate at any moment. This library is not a webpage! It is an Instagram for Android application emulator. It emulates an application. Not a webpage. You cannot just randomly kill this library in the middle of work! (You might kill it while it's writing to disk or calling some important APIs!)
+
And furthermore, if it's used on a webpage then it must waste time logging in to Instagram every time the user refreshes the webpage, since each page load would start a new script (meaning "a new Instagram app emulator") from scratch. And there are also massive risks about concurrent access from multiple web requests to a single user account which can corrupt your settings-storage or cookies. So we really, really do not recommend running this library via a browser! It's a very bad idea!
+
Instead, you should run your Instagram app emulator script as a permanent 24/7 process, and make it dump data to a database which your regular website reads from, or make some kind of permanent localhost daemon that can listen locally on the server and receive queries on a port (localhost queries from the main website's scripts), and then performing those queries via the API and responding to the website script with something like JSON or with serialized src/Response/ objects, which the website script then transforms for final display to the user.
+
You must also be sure that your permanent process periodically calls login() inside your 24/7 PHP process, to emulate the Instagram for Android application being closed/opened and refreshing itself (like a real user would do). Otherwise it will soon get banned or silently limited as a "detected bot" by Instagram due to never calling login. Preferably use the same refresh ranges we use by default, so that you refresh login() within a random range between every 30 minutes, or at max every 6 hours.
+
So, to recap: Make your "Instagram application" part a permanent process, and then make your webpage interact with that permanent process or an intermediary database in some way. That's the proper architecture system if you want to use this library online! Imagine it like your website talking to a phone and telling its permanently running Instagram app to do things. That's the proper design and that's how you have to think about things.
+
Never forget this fact: This library (and all other 3rd party libraries) is an Android application emulator. It is not a website and will never be able to become a website (because the Android application is not a website!). Therefore, we will never give support to anyone who decides to use the library directly inside a webpage. If you do that, it's at your own risk! It is a really terrible and unsupported idea!
diff --git a/instafeed/vendor/psr/http-message/CHANGELOG.md b/instafeed/vendor/psr/http-message/CHANGELOG.md
new file mode 100755
index 0000000..74b1ef9
--- /dev/null
+++ b/instafeed/vendor/psr/http-message/CHANGELOG.md
@@ -0,0 +1,36 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1 - 2016-08-06
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Updated all `@return self` annotation references in interfaces to use
+ `@return static`, which more closelly follows the semantics of the
+ specification.
+- Updated the `MessageInterface::getHeaders()` return annotation to use the
+ value `string[][]`, indicating the format is a nested array of strings.
+- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
+ to point to the correct section of RFC 7230.
+- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
+ to add the parameter name (`$uploadedFiles`).
+- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
+ method to correctly reference the method parameter (it was referencing an
+ incorrect parameter name previously).
+
+## 1.0.0 - 2016-05-18
+
+Initial stable release; reflects accepted PSR-7 specification.
diff --git a/instafeed/vendor/psr/http-message/LICENSE b/instafeed/vendor/psr/http-message/LICENSE
new file mode 100755
index 0000000..c2d8e45
--- /dev/null
+++ b/instafeed/vendor/psr/http-message/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/psr/http-message/README.md b/instafeed/vendor/psr/http-message/README.md
new file mode 100755
index 0000000..2818533
--- /dev/null
+++ b/instafeed/vendor/psr/http-message/README.md
@@ -0,0 +1,13 @@
+PSR Http Message
+================
+
+This repository holds all interfaces/classes/traits related to
+[PSR-7](http://www.php-fig.org/psr/psr-7/).
+
+Note that this is not a HTTP message implementation of its own. It is merely an
+interface that describes a HTTP message. See the specification for more details.
+
+Usage
+-----
+
+We'll certainly need some stuff in here.
\ No newline at end of file
diff --git a/instafeed/vendor/psr/http-message/composer.json b/instafeed/vendor/psr/http-message/composer.json
new file mode 100755
index 0000000..b0d2937
--- /dev/null
+++ b/instafeed/vendor/psr/http-message/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/http-message",
+ "description": "Common interface for HTTP messages",
+ "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
+ "homepage": "https://github.com/php-fig/http-message",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/instafeed/vendor/psr/http-message/src/MessageInterface.php b/instafeed/vendor/psr/http-message/src/MessageInterface.php
new file mode 100755
index 0000000..dd46e5e
--- /dev/null
+++ b/instafeed/vendor/psr/http-message/src/MessageInterface.php
@@ -0,0 +1,187 @@
+getHeaders() as $name => $values) {
+ * echo $name . ": " . implode(", ", $values);
+ * }
+ *
+ * // Emit headers iteratively:
+ * foreach ($message->getHeaders() as $name => $values) {
+ * foreach ($values as $value) {
+ * header(sprintf('%s: %s', $name, $value), false);
+ * }
+ * }
+ *
+ * While header names are not case-sensitive, getHeaders() will preserve the
+ * exact case in which headers were originally specified.
+ *
+ * @return string[][] Returns an associative array of the message's headers. Each
+ * key MUST be a header name, and each value MUST be an array of strings
+ * for that header.
+ */
+ public function getHeaders();
+
+ /**
+ * Checks if a header exists by the given case-insensitive name.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return bool Returns true if any header names match the given header
+ * name using a case-insensitive string comparison. Returns false if
+ * no matching header name is found in the message.
+ */
+ public function hasHeader($name);
+
+ /**
+ * Retrieves a message header value by the given case-insensitive name.
+ *
+ * This method returns an array of all the header values of the given
+ * case-insensitive header name.
+ *
+ * If the header does not appear in the message, this method MUST return an
+ * empty array.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string[] An array of string values as provided for the given
+ * header. If the header does not appear in the message, this method MUST
+ * return an empty array.
+ */
+ public function getHeader($name);
+
+ /**
+ * Retrieves a comma-separated string of the values for a single header.
+ *
+ * This method returns all of the header values of the given
+ * case-insensitive header name as a string concatenated together using
+ * a comma.
+ *
+ * NOTE: Not all header values may be appropriately represented using
+ * comma concatenation. For such headers, use getHeader() instead
+ * and supply your own delimiter when concatenating.
+ *
+ * If the header does not appear in the message, this method MUST return
+ * an empty string.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string A string of values as provided for the given header
+ * concatenated together using a comma. If the header does not appear in
+ * the message, this method MUST return an empty string.
+ */
+ public function getHeaderLine($name);
+
+ /**
+ * Return an instance with the provided value replacing the specified header.
+ *
+ * While header names are case-insensitive, the casing of the header will
+ * be preserved by this function, and returned from getHeaders().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new and/or updated header and value.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withHeader($name, $value);
+
+ /**
+ * Return an instance with the specified header appended with the given value.
+ *
+ * Existing values for the specified header will be maintained. The new
+ * value(s) will be appended to the existing list. If the header did not
+ * exist previously, it will be added.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new header and/or value.
+ *
+ * @param string $name Case-insensitive header field name to add.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withAddedHeader($name, $value);
+
+ /**
+ * Return an instance without the specified header.
+ *
+ * Header resolution MUST be done without case-sensitivity.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the named header.
+ *
+ * @param string $name Case-insensitive header field name to remove.
+ * @return static
+ */
+ public function withoutHeader($name);
+
+ /**
+ * Gets the body of the message.
+ *
+ * @return StreamInterface Returns the body as a stream.
+ */
+ public function getBody();
+
+ /**
+ * Return an instance with the specified message body.
+ *
+ * The body MUST be a StreamInterface object.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return a new instance that has the
+ * new body stream.
+ *
+ * @param StreamInterface $body Body.
+ * @return static
+ * @throws \InvalidArgumentException When the body is not valid.
+ */
+ public function withBody(StreamInterface $body);
+}
diff --git a/instafeed/vendor/psr/http-message/src/RequestInterface.php b/instafeed/vendor/psr/http-message/src/RequestInterface.php
new file mode 100755
index 0000000..a96d4fd
--- /dev/null
+++ b/instafeed/vendor/psr/http-message/src/RequestInterface.php
@@ -0,0 +1,129 @@
+getQuery()`
+ * or from the `QUERY_STRING` server param.
+ *
+ * @return array
+ */
+ public function getQueryParams();
+
+ /**
+ * Return an instance with the specified query string arguments.
+ *
+ * These values SHOULD remain immutable over the course of the incoming
+ * request. They MAY be injected during instantiation, such as from PHP's
+ * $_GET superglobal, or MAY be derived from some other value such as the
+ * URI. In cases where the arguments are parsed from the URI, the data
+ * MUST be compatible with what PHP's parse_str() would return for
+ * purposes of how duplicate query parameters are handled, and how nested
+ * sets are handled.
+ *
+ * Setting query string arguments MUST NOT change the URI stored by the
+ * request, nor the values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated query string arguments.
+ *
+ * @param array $query Array of query string arguments, typically from
+ * $_GET.
+ * @return static
+ */
+ public function withQueryParams(array $query);
+
+ /**
+ * Retrieve normalized file upload data.
+ *
+ * This method returns upload metadata in a normalized tree, with each leaf
+ * an instance of Psr\Http\Message\UploadedFileInterface.
+ *
+ * These values MAY be prepared from $_FILES or the message body during
+ * instantiation, or MAY be injected via withUploadedFiles().
+ *
+ * @return array An array tree of UploadedFileInterface instances; an empty
+ * array MUST be returned if no data is present.
+ */
+ public function getUploadedFiles();
+
+ /**
+ * Create a new instance with the specified uploaded files.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
+ * @return static
+ * @throws \InvalidArgumentException if an invalid structure is provided.
+ */
+ public function withUploadedFiles(array $uploadedFiles);
+
+ /**
+ * Retrieve any parameters provided in the request body.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, this method MUST
+ * return the contents of $_POST.
+ *
+ * Otherwise, this method may return any results of deserializing
+ * the request body content; as parsing returns structured content, the
+ * potential types MUST be arrays or objects only. A null value indicates
+ * the absence of body content.
+ *
+ * @return null|array|object The deserialized body parameters, if any.
+ * These will typically be an array or object.
+ */
+ public function getParsedBody();
+
+ /**
+ * Return an instance with the specified body parameters.
+ *
+ * These MAY be injected during instantiation.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, use this method
+ * ONLY to inject the contents of $_POST.
+ *
+ * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
+ * deserializing the request body content. Deserialization/parsing returns
+ * structured data, and, as such, this method ONLY accepts arrays or objects,
+ * or a null value if nothing was available to parse.
+ *
+ * As an example, if content negotiation determines that the request data
+ * is a JSON payload, this method could be used to create a request
+ * instance with the deserialized parameters.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param null|array|object $data The deserialized body data. This will
+ * typically be in an array or object.
+ * @return static
+ * @throws \InvalidArgumentException if an unsupported argument type is
+ * provided.
+ */
+ public function withParsedBody($data);
+
+ /**
+ * Retrieve attributes derived from the request.
+ *
+ * The request "attributes" may be used to allow injection of any
+ * parameters derived from the request: e.g., the results of path
+ * match operations; the results of decrypting cookies; the results of
+ * deserializing non-form-encoded message bodies; etc. Attributes
+ * will be application and request specific, and CAN be mutable.
+ *
+ * @return array Attributes derived from the request.
+ */
+ public function getAttributes();
+
+ /**
+ * Retrieve a single derived request attribute.
+ *
+ * Retrieves a single derived request attribute as described in
+ * getAttributes(). If the attribute has not been previously set, returns
+ * the default value as provided.
+ *
+ * This method obviates the need for a hasAttribute() method, as it allows
+ * specifying a default value to return if the attribute is not found.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $default Default value to return if the attribute does not exist.
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null);
+
+ /**
+ * Return an instance with the specified derived request attribute.
+ *
+ * This method allows setting a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $value The value of the attribute.
+ * @return static
+ */
+ public function withAttribute($name, $value);
+
+ /**
+ * Return an instance that removes the specified derived request attribute.
+ *
+ * This method allows removing a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @return static
+ */
+ public function withoutAttribute($name);
+}
diff --git a/instafeed/vendor/psr/http-message/src/StreamInterface.php b/instafeed/vendor/psr/http-message/src/StreamInterface.php
new file mode 100755
index 0000000..f68f391
--- /dev/null
+++ b/instafeed/vendor/psr/http-message/src/StreamInterface.php
@@ -0,0 +1,158 @@
+
+ * [user-info@]host[:port]
+ *
+ *
+ * If the port component is not set or is the standard port for the current
+ * scheme, it SHOULD NOT be included.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ * @return string The URI authority, in "[user-info@]host[:port]" format.
+ */
+ public function getAuthority();
+
+ /**
+ * Retrieve the user information component of the URI.
+ *
+ * If no user information is present, this method MUST return an empty
+ * string.
+ *
+ * If a user is present in the URI, this will return that value;
+ * additionally, if the password is also present, it will be appended to the
+ * user value, with a colon (":") separating the values.
+ *
+ * The trailing "@" character is not part of the user information and MUST
+ * NOT be added.
+ *
+ * @return string The URI user information, in "username[:password]" format.
+ */
+ public function getUserInfo();
+
+ /**
+ * Retrieve the host component of the URI.
+ *
+ * If no host is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.2.2.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @return string The URI host.
+ */
+ public function getHost();
+
+ /**
+ * Retrieve the port component of the URI.
+ *
+ * If a port is present, and it is non-standard for the current scheme,
+ * this method MUST return it as an integer. If the port is the standard port
+ * used with the current scheme, this method SHOULD return null.
+ *
+ * If no port is present, and no scheme is present, this method MUST return
+ * a null value.
+ *
+ * If no port is present, but a scheme is present, this method MAY return
+ * the standard port for that scheme, but SHOULD return null.
+ *
+ * @return null|int The URI port.
+ */
+ public function getPort();
+
+ /**
+ * Retrieve the path component of the URI.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Normally, the empty path "" and absolute path "/" are considered equal as
+ * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+ * do this normalization because in contexts with a trimmed base path, e.g.
+ * the front controller, this difference becomes significant. It's the task
+ * of the user to handle both "" and "/".
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.3.
+ *
+ * As an example, if the value should include a slash ("/") not intended as
+ * delimiter between path segments, that value MUST be passed in encoded
+ * form (e.g., "%2F") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.3
+ * @return string The URI path.
+ */
+ public function getPath();
+
+ /**
+ * Retrieve the query string of the URI.
+ *
+ * If no query string is present, this method MUST return an empty string.
+ *
+ * The leading "?" character is not part of the query and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.4.
+ *
+ * As an example, if a value in a key/value pair of the query string should
+ * include an ampersand ("&") not intended as a delimiter between values,
+ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ * @return string The URI query string.
+ */
+ public function getQuery();
+
+ /**
+ * Retrieve the fragment component of the URI.
+ *
+ * If no fragment is present, this method MUST return an empty string.
+ *
+ * The leading "#" character is not part of the fragment and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.5.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.5
+ * @return string The URI fragment.
+ */
+ public function getFragment();
+
+ /**
+ * Return an instance with the specified scheme.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified scheme.
+ *
+ * Implementations MUST support the schemes "http" and "https" case
+ * insensitively, and MAY accommodate other schemes if required.
+ *
+ * An empty scheme is equivalent to removing the scheme.
+ *
+ * @param string $scheme The scheme to use with the new instance.
+ * @return static A new instance with the specified scheme.
+ * @throws \InvalidArgumentException for invalid or unsupported schemes.
+ */
+ public function withScheme($scheme);
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; an empty string for the user is equivalent to removing user
+ * information.
+ *
+ * @param string $user The user name to use for authority.
+ * @param null|string $password The password associated with $user.
+ * @return static A new instance with the specified user information.
+ */
+ public function withUserInfo($user, $password = null);
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * An empty host value is equivalent to removing the host.
+ *
+ * @param string $host The hostname to use with the new instance.
+ * @return static A new instance with the specified host.
+ * @throws \InvalidArgumentException for invalid hostnames.
+ */
+ public function withHost($host);
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * Implementations MUST raise an exception for ports outside the
+ * established TCP and UDP port ranges.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param null|int $port The port to use with the new instance; a null value
+ * removes the port information.
+ * @return static A new instance with the specified port.
+ * @throws \InvalidArgumentException for invalid ports.
+ */
+ public function withPort($port);
+
+ /**
+ * Return an instance with the specified path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified path.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * If the path is intended to be domain-relative rather than path relative then
+ * it must begin with a slash ("/"). Paths not starting with a slash ("/")
+ * are assumed to be relative to some base path known to the application or
+ * consumer.
+ *
+ * Users can provide both encoded and decoded path characters.
+ * Implementations ensure the correct encoding as outlined in getPath().
+ *
+ * @param string $path The path to use with the new instance.
+ * @return static A new instance with the specified path.
+ * @throws \InvalidArgumentException for invalid paths.
+ */
+ public function withPath($path);
+
+ /**
+ * Return an instance with the specified query string.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified query string.
+ *
+ * Users can provide both encoded and decoded query characters.
+ * Implementations ensure the correct encoding as outlined in getQuery().
+ *
+ * An empty query string value is equivalent to removing the query string.
+ *
+ * @param string $query The query string to use with the new instance.
+ * @return static A new instance with the specified query string.
+ * @throws \InvalidArgumentException for invalid query strings.
+ */
+ public function withQuery($query);
+
+ /**
+ * Return an instance with the specified URI fragment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified URI fragment.
+ *
+ * Users can provide both encoded and decoded fragment characters.
+ * Implementations ensure the correct encoding as outlined in getFragment().
+ *
+ * An empty fragment value is equivalent to removing the fragment.
+ *
+ * @param string $fragment The fragment to use with the new instance.
+ * @return static A new instance with the specified fragment.
+ */
+ public function withFragment($fragment);
+
+ /**
+ * Return the string representation as a URI reference.
+ *
+ * Depending on which components of the URI are present, the resulting
+ * string is either a full URI or relative reference according to RFC 3986,
+ * Section 4.1. The method concatenates the various components of the URI,
+ * using the appropriate delimiters:
+ *
+ * - If a scheme is present, it MUST be suffixed by ":".
+ * - If an authority is present, it MUST be prefixed by "//".
+ * - The path can be concatenated without delimiters. But there are two
+ * cases where the path has to be adjusted to make the URI reference
+ * valid as PHP does not allow to throw an exception in __toString():
+ * - If the path is rootless and an authority is present, the path MUST
+ * be prefixed by "/".
+ * - If the path is starting with more than one "/" and no authority is
+ * present, the starting slashes MUST be reduced to one.
+ * - If a query is present, it MUST be prefixed by "?".
+ * - If a fragment is present, it MUST be prefixed by "#".
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ * @return string
+ */
+ public function __toString();
+}
diff --git a/instafeed/vendor/psr/log/.gitignore b/instafeed/vendor/psr/log/.gitignore
new file mode 100755
index 0000000..22d0d82
--- /dev/null
+++ b/instafeed/vendor/psr/log/.gitignore
@@ -0,0 +1 @@
+vendor
diff --git a/instafeed/vendor/psr/log/LICENSE b/instafeed/vendor/psr/log/LICENSE
new file mode 100755
index 0000000..474c952
--- /dev/null
+++ b/instafeed/vendor/psr/log/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/psr/log/Psr/Log/AbstractLogger.php b/instafeed/vendor/psr/log/Psr/Log/AbstractLogger.php
new file mode 100755
index 0000000..90e721a
--- /dev/null
+++ b/instafeed/vendor/psr/log/Psr/Log/AbstractLogger.php
@@ -0,0 +1,128 @@
+log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+}
diff --git a/instafeed/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/instafeed/vendor/psr/log/Psr/Log/InvalidArgumentException.php
new file mode 100755
index 0000000..67f852d
--- /dev/null
+++ b/instafeed/vendor/psr/log/Psr/Log/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+logger = $logger;
+ }
+}
diff --git a/instafeed/vendor/psr/log/Psr/Log/LoggerInterface.php b/instafeed/vendor/psr/log/Psr/Log/LoggerInterface.php
new file mode 100755
index 0000000..e695046
--- /dev/null
+++ b/instafeed/vendor/psr/log/Psr/Log/LoggerInterface.php
@@ -0,0 +1,125 @@
+log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ abstract public function log($level, $message, array $context = array());
+}
diff --git a/instafeed/vendor/psr/log/Psr/Log/NullLogger.php b/instafeed/vendor/psr/log/Psr/Log/NullLogger.php
new file mode 100755
index 0000000..c8f7293
--- /dev/null
+++ b/instafeed/vendor/psr/log/Psr/Log/NullLogger.php
@@ -0,0 +1,30 @@
+logger) { }`
+ * blocks.
+ */
+class NullLogger extends AbstractLogger
+{
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ public function log($level, $message, array $context = array())
+ {
+ // noop
+ }
+}
diff --git a/instafeed/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/instafeed/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
new file mode 100755
index 0000000..8e445ee
--- /dev/null
+++ b/instafeed/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
@@ -0,0 +1,145 @@
+ ".
+ *
+ * Example ->error('Foo') would yield "error Foo".
+ *
+ * @return string[]
+ */
+ abstract public function getLogs();
+
+ public function testImplements()
+ {
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @dataProvider provideLevelsAndMessages
+ */
+ public function testLogsAtAllLevels($level, $message)
+ {
+ $logger = $this->getLogger();
+ $logger->{$level}($message, array('user' => 'Bob'));
+ $logger->log($level, $message, array('user' => 'Bob'));
+
+ $expected = array(
+ $level.' message of level '.$level.' with context: Bob',
+ $level.' message of level '.$level.' with context: Bob',
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function provideLevelsAndMessages()
+ {
+ return array(
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+ );
+ }
+
+ /**
+ * @expectedException \Psr\Log\InvalidArgumentException
+ */
+ public function testThrowsOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+ $logger->log('invalid level', 'Foo');
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+ $expected = array('info {Message {nothing} Bob Bar a}');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testObjectCastToString()
+ {
+ if (method_exists($this, 'createPartialMock')) {
+ $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
+ } else {
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+ }
+ $dummy->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('DUMMY'));
+
+ $this->getLogger()->warning($dummy);
+
+ $expected = array('warning DUMMY');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextCanContainAnything()
+ {
+ $closed = fopen('php://memory', 'r');
+ fclose($closed);
+
+ $context = array(
+ 'bool' => true,
+ 'null' => null,
+ 'string' => 'Foo',
+ 'int' => 0,
+ 'float' => 0.5,
+ 'nested' => array('with object' => new DummyTest),
+ 'object' => new \DateTime,
+ 'resource' => fopen('php://memory', 'r'),
+ 'closed' => $closed,
+ );
+
+ $this->getLogger()->warning('Crazy context data', $context);
+
+ $expected = array('warning Crazy context data');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+ {
+ $logger = $this->getLogger();
+ $logger->warning('Random message', array('exception' => 'oops'));
+ $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+
+ $expected = array(
+ 'warning Random message',
+ 'critical Uncaught Exception!'
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+}
+
+class DummyTest
+{
+ public function __toString()
+ {
+ }
+}
diff --git a/instafeed/vendor/psr/log/Psr/Log/Test/TestLogger.php b/instafeed/vendor/psr/log/Psr/Log/Test/TestLogger.php
new file mode 100755
index 0000000..1be3230
--- /dev/null
+++ b/instafeed/vendor/psr/log/Psr/Log/Test/TestLogger.php
@@ -0,0 +1,147 @@
+ $level,
+ 'message' => $message,
+ 'context' => $context,
+ ];
+
+ $this->recordsByLevel[$record['level']][] = $record;
+ $this->records[] = $record;
+ }
+
+ public function hasRecords($level)
+ {
+ return isset($this->recordsByLevel[$level]);
+ }
+
+ public function hasRecord($record, $level)
+ {
+ if (is_string($record)) {
+ $record = ['message' => $record];
+ }
+ return $this->hasRecordThatPasses(function ($rec) use ($record) {
+ if ($rec['message'] !== $record['message']) {
+ return false;
+ }
+ if (isset($record['context']) && $rec['context'] !== $record['context']) {
+ return false;
+ }
+ return true;
+ }, $level);
+ }
+
+ public function hasRecordThatContains($message, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($message) {
+ return strpos($rec['message'], $message) !== false;
+ }, $level);
+ }
+
+ public function hasRecordThatMatches($regex, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($regex) {
+ return preg_match($regex, $rec['message']) > 0;
+ }, $level);
+ }
+
+ public function hasRecordThatPasses(callable $predicate, $level)
+ {
+ if (!isset($this->recordsByLevel[$level])) {
+ return false;
+ }
+ foreach ($this->recordsByLevel[$level] as $i => $rec) {
+ if (call_user_func($predicate, $rec, $i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function __call($method, $args)
+ {
+ if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
+ $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
+ $level = strtolower($matches[2]);
+ if (method_exists($this, $genericMethod)) {
+ $args[] = $level;
+ return call_user_func_array([$this, $genericMethod], $args);
+ }
+ }
+ throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
+ }
+
+ public function reset()
+ {
+ $this->records = [];
+ $this->recordsByLevel = [];
+ }
+}
diff --git a/instafeed/vendor/psr/log/README.md b/instafeed/vendor/psr/log/README.md
new file mode 100755
index 0000000..5571a25
--- /dev/null
+++ b/instafeed/vendor/psr/log/README.md
@@ -0,0 +1,52 @@
+PSR Log
+=======
+
+This repository holds all interfaces/classes/traits related to
+[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
+
+Note that this is not a logger of its own. It is merely an interface that
+describes a logger. See the specification for more details.
+
+Installation
+------------
+
+```bash
+composer require psr/log
+```
+
+Usage
+-----
+
+If you need a logger, you can use the interface like this:
+
+```php
+logger = $logger;
+ }
+
+ public function doSomething()
+ {
+ if ($this->logger) {
+ $this->logger->info('Doing work');
+ }
+
+ // do something useful
+ }
+}
+```
+
+You can then pick one of the implementations of the interface to get a logger.
+
+If you want to implement the interface, you can require this package and
+implement `Psr\Log\LoggerInterface` in your code. Please read the
+[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+for details.
diff --git a/instafeed/vendor/psr/log/composer.json b/instafeed/vendor/psr/log/composer.json
new file mode 100755
index 0000000..3f6d4ee
--- /dev/null
+++ b/instafeed/vendor/psr/log/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/log",
+ "description": "Common interface for logging libraries",
+ "keywords": ["psr", "psr-3", "log"],
+ "homepage": "https://github.com/php-fig/log",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
diff --git a/instafeed/vendor/ralouphie/getallheaders/LICENSE b/instafeed/vendor/ralouphie/getallheaders/LICENSE
new file mode 100755
index 0000000..be5540c
--- /dev/null
+++ b/instafeed/vendor/ralouphie/getallheaders/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Ralph Khattar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/instafeed/vendor/ralouphie/getallheaders/README.md b/instafeed/vendor/ralouphie/getallheaders/README.md
new file mode 100755
index 0000000..9430d76
--- /dev/null
+++ b/instafeed/vendor/ralouphie/getallheaders/README.md
@@ -0,0 +1,27 @@
+getallheaders
+=============
+
+PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3.
+
+[](https://travis-ci.org/ralouphie/getallheaders)
+[](https://coveralls.io/r/ralouphie/getallheaders?branch=master)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+
+
+This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php).
+
+## Install
+
+For PHP version **`>= 5.6`**:
+
+```
+composer require ralouphie/getallheaders
+```
+
+For PHP version **`< 5.6`**:
+
+```
+composer require ralouphie/getallheaders "^2"
+```
diff --git a/instafeed/vendor/ralouphie/getallheaders/composer.json b/instafeed/vendor/ralouphie/getallheaders/composer.json
new file mode 100755
index 0000000..de8ce62
--- /dev/null
+++ b/instafeed/vendor/ralouphie/getallheaders/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "ralouphie/getallheaders",
+ "description": "A polyfill for getallheaders.",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5 || ^6.5",
+ "php-coveralls/php-coveralls": "^2.1"
+ },
+ "autoload": {
+ "files": ["src/getallheaders.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "getallheaders\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/instafeed/vendor/ralouphie/getallheaders/src/getallheaders.php b/instafeed/vendor/ralouphie/getallheaders/src/getallheaders.php
new file mode 100755
index 0000000..c7285a5
--- /dev/null
+++ b/instafeed/vendor/ralouphie/getallheaders/src/getallheaders.php
@@ -0,0 +1,46 @@
+ 'Content-Type',
+ 'CONTENT_LENGTH' => 'Content-Length',
+ 'CONTENT_MD5' => 'Content-Md5',
+ );
+
+ foreach ($_SERVER as $key => $value) {
+ if (substr($key, 0, 5) === 'HTTP_') {
+ $key = substr($key, 5);
+ if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) {
+ $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
+ $headers[$key] = $value;
+ }
+ } elseif (isset($copy_server[$key])) {
+ $headers[$copy_server[$key]] = $value;
+ }
+ }
+
+ if (!isset($headers['Authorization'])) {
+ if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
+ $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
+ } elseif (isset($_SERVER['PHP_AUTH_USER'])) {
+ $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
+ $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);
+ } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
+ $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
+ }
+ }
+
+ return $headers;
+ }
+
+}
diff --git a/instafeed/vendor/react/cache/.gitignore b/instafeed/vendor/react/cache/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/instafeed/vendor/react/cache/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/instafeed/vendor/react/cache/.travis.yml b/instafeed/vendor/react/cache/.travis.yml
new file mode 100755
index 0000000..402a996
--- /dev/null
+++ b/instafeed/vendor/react/cache/.travis.yml
@@ -0,0 +1,32 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+# - hhvm # requires legacy phpunit & ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - php: hhvm
+ install: composer require phpunit/phpunit:^5 --dev --no-interaction
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/instafeed/vendor/react/cache/CHANGELOG.md b/instafeed/vendor/react/cache/CHANGELOG.md
new file mode 100755
index 0000000..99ecd1c
--- /dev/null
+++ b/instafeed/vendor/react/cache/CHANGELOG.md
@@ -0,0 +1,74 @@
+# Changelog
+
+## 1.0.0 (2019-07-11)
+
+* First stable LTS release, now following [SemVer](https://semver.org/).
+ We'd like to emphasize that this component is production ready and battle-tested.
+ We plan to support all long-term support (LTS) releases for at least 24 months,
+ so you have a rock-solid foundation to build on top of.
+
+> Contains no other changes, so it's actually fully compatible with the v0.6.0 release.
+
+## 0.6.0 (2019-07-04)
+
+* Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()`
+ supporting multiple cache items (inspired by PSR-16).
+ (#32 by @krlv and #37 by @clue)
+
+* Documentation for TTL precision with millisecond accuracy or below and
+ use high-resolution timer for cache TTL on PHP 7.3+.
+ (#35 and #38 by @clue)
+
+* Improve API documentation and allow legacy HHVM to fail in Travis CI config.
+ (#34 and #36 by @clue)
+
+* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
+ (#31 by @WyriHaximus)
+
+## 0.5.0 (2018-06-25)
+
+* Improve documentation by describing what is expected of a class implementing `CacheInterface`.
+ (#21, #22, #23, #27 by @WyriHaximus)
+
+* Implemented (optional) Least Recently Used (LRU) cache algorithm for `ArrayCache`.
+ (#26 by @clue)
+
+* Added support for cache expiration (TTL).
+ (#29 by @clue and @WyriHaximus)
+
+* Renamed `remove` to `delete` making it more in line with `PSR-16`.
+ (#30 by @clue)
+
+## 0.4.2 (2017-12-20)
+
+* Improve documentation with usage and installation instructions
+ (#10 by @clue)
+
+* Improve test suite by adding PHPUnit to `require-dev` and
+ add forward compatibility with PHPUnit 5 and PHPUnit 6 and
+ sanitize Composer autoload paths
+ (#14 by @shaunbramley and #12 and #18 by @clue)
+
+## 0.4.1 (2016-02-25)
+
+* Repository maintenance, split off from main repo, improve test suite and documentation
+* First class support for PHP7 and HHVM (#9 by @clue)
+* Adjust compatibility to 5.3 (#7 by @clue)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.2 (2013-05-10)
+
+* Version bump
+
+## 0.3.0 (2013-04-14)
+
+* Version bump
+
+## 0.2.6 (2012-12-26)
+
+* Feature: New cache component, used by DNS
diff --git a/instafeed/vendor/react/cache/LICENSE b/instafeed/vendor/react/cache/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/instafeed/vendor/react/cache/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/react/cache/README.md b/instafeed/vendor/react/cache/README.md
new file mode 100755
index 0000000..74cef54
--- /dev/null
+++ b/instafeed/vendor/react/cache/README.md
@@ -0,0 +1,366 @@
+# Cache
+
+[](https://travis-ci.org/reactphp/cache)
+
+Async, [Promise](https://github.com/reactphp/promise)-based cache interface
+for [ReactPHP](https://reactphp.org/).
+
+The cache component provides a
+[Promise](https://github.com/reactphp/promise)-based
+[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache)
+implementation of that.
+This allows consumers to type hint against the interface and third parties to
+provide alternate implementations.
+This project is heavily inspired by
+[PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/),
+but uses an interface more suited for async, non-blocking applications.
+
+**Table of Contents**
+
+* [Usage](#usage)
+ * [CacheInterface](#cacheinterface)
+ * [get()](#get)
+ * [set()](#set)
+ * [delete()](#delete)
+ * [getMultiple()](#getmultiple)
+ * [setMultiple()](#setmultiple)
+ * [deleteMultiple()](#deletemultiple)
+ * [clear()](#clear)
+ * [has()](#has)
+ * [ArrayCache](#arraycache)
+* [Common usage](#common-usage)
+ * [Fallback get](#fallback-get)
+ * [Fallback-get-and-set](#fallback-get-and-set)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Usage
+
+### CacheInterface
+
+The `CacheInterface` describes the main interface of this component.
+This allows consumers to type hint against the interface and third parties to
+provide alternate implementations.
+
+#### get()
+
+The `get(string $key, mixed $default = null): PromiseInterface` method can be used to
+retrieve an item from the cache.
+
+This method will resolve with the cached value on success or with the
+given `$default` value when no item can be found or when an error occurs.
+Similarly, an expired cache item (once the time-to-live is expired) is
+considered a cache miss.
+
+```php
+$cache
+ ->get('foo')
+ ->then('var_dump');
+```
+
+This example fetches the value of the key `foo` and passes it to the
+`var_dump` function. You can use any of the composition provided by
+[promises](https://github.com/reactphp/promise).
+
+#### set()
+
+The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface` method can be used to
+store an item in the cache.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. If the cache implementation has to go over the network to store
+it, it may take a while.
+
+The optional `$ttl` parameter sets the maximum time-to-live in seconds
+for this cache item. If this parameter is omitted (or `null`), the item
+will stay in the cache for as long as the underlying implementation
+supports. Trying to access an expired cache item results in a cache miss,
+see also [`get()`](#get).
+
+```php
+$cache->set('foo', 'bar', 60);
+```
+
+This example eventually sets the value of the key `foo` to `bar`. If it
+already exists, it is overridden.
+
+This interface does not enforce any particular TTL resolution, so special
+care may have to be taken if you rely on very high precision with
+millisecond accuracy or below. Cache implementations SHOULD work on a
+best effort basis and SHOULD provide at least second accuracy unless
+otherwise noted. Many existing cache implementations are known to provide
+microsecond or millisecond accuracy, but it's generally not recommended
+to rely on this high precision.
+
+This interface suggests that cache implementations SHOULD use a monotonic
+time source if available. Given that a monotonic time source is only
+available as of PHP 7.3 by default, cache implementations MAY fall back
+to using wall-clock time.
+While this does not affect many common use cases, this is an important
+distinction for programs that rely on a high time precision or on systems
+that are subject to discontinuous time adjustments (time jumps).
+This means that if you store a cache item with a TTL of 30s and then
+adjust your system time forward by 20s, the cache item SHOULD still
+expire in 30s.
+
+#### delete()
+
+The `delete(string $key): PromiseInterface` method can be used to
+delete an item from the cache.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. When no item for `$key` is found in the cache, it also resolves
+to `true`. If the cache implementation has to go over the network to
+delete it, it may take a while.
+
+```php
+$cache->delete('foo');
+```
+
+This example eventually deletes the key `foo` from the cache. As with
+`set()`, this may not happen instantly and a promise is returned to
+provide guarantees whether or not the item has been removed from cache.
+
+#### getMultiple()
+
+The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface` method can be used to
+retrieve multiple cache items by their unique keys.
+
+This method will resolve with an array of cached values on success or with the
+given `$default` value when an item can not be found or when an error occurs.
+Similarly, an expired cache item (once the time-to-live is expired) is
+considered a cache miss.
+
+```php
+$cache->getMultiple(array('name', 'age'))->then(function (array $values) {
+ $name = $values['name'] ?? 'User';
+ $age = $values['age'] ?? 'n/a';
+
+ echo $name . ' is ' . $age . PHP_EOL;
+});
+```
+
+This example fetches the cache items for the `name` and `age` keys and
+prints some example output. You can use any of the composition provided
+by [promises](https://github.com/reactphp/promise).
+
+#### setMultiple()
+
+The `setMultiple(array $values, ?float $ttl = null): PromiseInterface` method can be used to
+persist a set of key => value pairs in the cache, with an optional TTL.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. If the cache implementation has to go over the network to store
+it, it may take a while.
+
+The optional `$ttl` parameter sets the maximum time-to-live in seconds
+for these cache items. If this parameter is omitted (or `null`), these items
+will stay in the cache for as long as the underlying implementation
+supports. Trying to access an expired cache items results in a cache miss,
+see also [`getMultiple()`](#getmultiple).
+
+```php
+$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
+```
+
+This example eventually sets the list of values - the key `foo` to `1` value
+and the key `bar` to `2`. If some of the keys already exist, they are overridden.
+
+#### deleteMultiple()
+
+The `setMultiple(string[] $keys): PromiseInterface` method can be used to
+delete multiple cache items in a single operation.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. When no items for `$keys` are found in the cache, it also resolves
+to `true`. If the cache implementation has to go over the network to
+delete it, it may take a while.
+
+```php
+$cache->deleteMultiple(array('foo', 'bar, 'baz'));
+```
+
+This example eventually deletes keys `foo`, `bar` and `baz` from the cache.
+As with `setMultiple()`, this may not happen instantly and a promise is returned to
+provide guarantees whether or not the item has been removed from cache.
+
+#### clear()
+
+The `clear(): PromiseInterface` method can be used to
+wipe clean the entire cache.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. If the cache implementation has to go over the network to
+delete it, it may take a while.
+
+```php
+$cache->clear();
+```
+
+This example eventually deletes all keys from the cache. As with `deleteMultiple()`,
+this may not happen instantly and a promise is returned to provide guarantees
+whether or not all the items have been removed from cache.
+
+#### has()
+
+The `has(string $key): PromiseInterface` method can be used to
+determine whether an item is present in the cache.
+
+This method will resolve with `true` on success or `false` when no item can be found
+or when an error occurs. Similarly, an expired cache item (once the time-to-live
+is expired) is considered a cache miss.
+
+```php
+$cache
+ ->has('foo')
+ ->then('var_dump');
+```
+
+This example checks if the value of the key `foo` is set in the cache and passes
+the result to the `var_dump` function. You can use any of the composition provided by
+[promises](https://github.com/reactphp/promise).
+
+NOTE: It is recommended that has() is only to be used for cache warming type purposes
+and not to be used within your live applications operations for get/set, as this method
+is subject to a race condition where your has() will return true and immediately after,
+another script can remove it making the state of your app out of date.
+
+### ArrayCache
+
+The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
+
+```php
+$cache = new ArrayCache();
+
+$cache->set('foo', 'bar');
+```
+
+Its constructor accepts an optional `?int $limit` parameter to limit the
+maximum number of entries to store in the LRU cache. If you add more
+entries to this instance, it will automatically take care of removing
+the one that was least recently used (LRU).
+
+For example, this snippet will overwrite the first value and only store
+the last two entries:
+
+```php
+$cache = new ArrayCache(2);
+
+$cache->set('foo', '1');
+$cache->set('bar', '2');
+$cache->set('baz', '3');
+```
+
+This cache implementation is known to rely on wall-clock time to schedule
+future cache expiration times when using any version before PHP 7.3,
+because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
+While this does not affect many common use cases, this is an important
+distinction for programs that rely on a high time precision or on systems
+that are subject to discontinuous time adjustments (time jumps).
+This means that if you store a cache item with a TTL of 30s on PHP < 7.3
+and then adjust your system time forward by 20s, the cache item may
+expire in 10s. See also [`set()`](#set) for more details.
+
+## Common usage
+
+### Fallback get
+
+A common use case of caches is to attempt fetching a cached value and as a
+fallback retrieve it from the original data source if not found. Here is an
+example of that:
+
+```php
+$cache
+ ->get('foo')
+ ->then(function ($result) {
+ if ($result === null) {
+ return getFooFromDb();
+ }
+
+ return $result;
+ })
+ ->then('var_dump');
+```
+
+First an attempt is made to retrieve the value of `foo`. A callback function is
+registered that will call `getFooFromDb` when the resulting value is null.
+`getFooFromDb` is a function (can be any PHP callable) that will be called if the
+key does not exist in the cache.
+
+`getFooFromDb` can handle the missing key by returning a promise for the
+actual value from the database (or any other data source). As a result, this
+chain will correctly fall back, and provide the value in both cases.
+
+### Fallback get and set
+
+To expand on the fallback get example, often you want to set the value on the
+cache after fetching it from the data source.
+
+```php
+$cache
+ ->get('foo')
+ ->then(function ($result) {
+ if ($result === null) {
+ return $this->getAndCacheFooFromDb();
+ }
+
+ return $result;
+ })
+ ->then('var_dump');
+
+public function getAndCacheFooFromDb()
+{
+ return $this->db
+ ->get('foo')
+ ->then(array($this, 'cacheFooFromDb'));
+}
+
+public function cacheFooFromDb($foo)
+{
+ $this->cache->set('foo', $foo);
+
+ return $foo;
+}
+```
+
+By using chaining you can easily conditionally cache the value if it is
+fetched from the database.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/cache:^1.0
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/instafeed/vendor/react/cache/composer.json b/instafeed/vendor/react/cache/composer.json
new file mode 100755
index 0000000..51573b6
--- /dev/null
+++ b/instafeed/vendor/react/cache/composer.json
@@ -0,0 +1,19 @@
+{
+ "name": "react/cache",
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": ["cache", "caching", "promise", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "~2.0|~1.1"
+ },
+ "autoload": {
+ "psr-4": { "React\\Cache\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Cache\\": "tests/" }
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/instafeed/vendor/react/cache/phpunit.xml.dist b/instafeed/vendor/react/cache/phpunit.xml.dist
new file mode 100755
index 0000000..d02182f
--- /dev/null
+++ b/instafeed/vendor/react/cache/phpunit.xml.dist
@@ -0,0 +1,20 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/instafeed/vendor/react/cache/src/ArrayCache.php b/instafeed/vendor/react/cache/src/ArrayCache.php
new file mode 100755
index 0000000..81f25ef
--- /dev/null
+++ b/instafeed/vendor/react/cache/src/ArrayCache.php
@@ -0,0 +1,181 @@
+set('foo', 'bar');
+ * ```
+ *
+ * Its constructor accepts an optional `?int $limit` parameter to limit the
+ * maximum number of entries to store in the LRU cache. If you add more
+ * entries to this instance, it will automatically take care of removing
+ * the one that was least recently used (LRU).
+ *
+ * For example, this snippet will overwrite the first value and only store
+ * the last two entries:
+ *
+ * ```php
+ * $cache = new ArrayCache(2);
+ *
+ * $cache->set('foo', '1');
+ * $cache->set('bar', '2');
+ * $cache->set('baz', '3');
+ * ```
+ *
+ * This cache implementation is known to rely on wall-clock time to schedule
+ * future cache expiration times when using any version before PHP 7.3,
+ * because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you store a cache item with a TTL of 30s on PHP < 7.3
+ * and then adjust your system time forward by 20s, the cache item may
+ * expire in 10s. See also [`set()`](#set) for more details.
+ *
+ * @param int|null $limit maximum number of entries to store in the LRU cache
+ */
+ public function __construct($limit = null)
+ {
+ $this->limit = $limit;
+
+ // prefer high-resolution timer, available as of PHP 7.3+
+ $this->supportsHighResolution = \function_exists('hrtime');
+ }
+
+ public function get($key, $default = null)
+ {
+ // delete key if it is already expired => below will detect this as a cache miss
+ if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ if (!\array_key_exists($key, $this->data)) {
+ return Promise\resolve($default);
+ }
+
+ // remove and append to end of array to keep track of LRU info
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ return Promise\resolve($value);
+ }
+
+ public function set($key, $value, $ttl = null)
+ {
+ // unset before setting to ensure this entry will be added to end of array (LRU info)
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ // sort expiration times if TTL is given (first will expire first)
+ unset($this->expires[$key]);
+ if ($ttl !== null) {
+ $this->expires[$key] = $this->now() + $ttl;
+ \asort($this->expires);
+ }
+
+ // ensure size limit is not exceeded or remove first entry from array
+ if ($this->limit !== null && \count($this->data) > $this->limit) {
+ // first try to check if there's any expired entry
+ // expiration times are sorted, so we can simply look at the first one
+ \reset($this->expires);
+ $key = \key($this->expires);
+
+ // check to see if the first in the list of expiring keys is already expired
+ // if the first key is not expired, we have to overwrite by using LRU info
+ if ($key === null || $this->now() - $this->expires[$key] < 0) {
+ \reset($this->data);
+ $key = \key($this->data);
+ }
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function delete($key)
+ {
+ unset($this->data[$key], $this->expires[$key]);
+
+ return Promise\resolve(true);
+ }
+
+ public function getMultiple(array $keys, $default = null)
+ {
+ $values = array();
+
+ foreach ($keys as $key) {
+ $values[$key] = $this->get($key, $default);
+ }
+
+ return Promise\all($values);
+ }
+
+ public function setMultiple(array $values, $ttl = null)
+ {
+ foreach ($values as $key => $value) {
+ $this->set($key, $value, $ttl);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function deleteMultiple(array $keys)
+ {
+ foreach ($keys as $key) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function clear()
+ {
+ $this->data = array();
+ $this->expires = array();
+
+ return Promise\resolve(true);
+ }
+
+ public function has($key)
+ {
+ // delete key if it is already expired
+ if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ if (!\array_key_exists($key, $this->data)) {
+ return Promise\resolve(false);
+ }
+
+ // remove and append to end of array to keep track of LRU info
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ return Promise\resolve(true);
+ }
+
+ /**
+ * @return float
+ */
+ private function now()
+ {
+ return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
+ }
+}
diff --git a/instafeed/vendor/react/cache/src/CacheInterface.php b/instafeed/vendor/react/cache/src/CacheInterface.php
new file mode 100755
index 0000000..3d52501
--- /dev/null
+++ b/instafeed/vendor/react/cache/src/CacheInterface.php
@@ -0,0 +1,194 @@
+get('foo')
+ * ->then('var_dump');
+ * ```
+ *
+ * This example fetches the value of the key `foo` and passes it to the
+ * `var_dump` function. You can use any of the composition provided by
+ * [promises](https://github.com/reactphp/promise).
+ *
+ * @param string $key
+ * @param mixed $default Default value to return for cache miss or null if not given.
+ * @return PromiseInterface
+ */
+ public function get($key, $default = null);
+
+ /**
+ * Stores an item in the cache.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. If the cache implementation has to go over the network to store
+ * it, it may take a while.
+ *
+ * The optional `$ttl` parameter sets the maximum time-to-live in seconds
+ * for this cache item. If this parameter is omitted (or `null`), the item
+ * will stay in the cache for as long as the underlying implementation
+ * supports. Trying to access an expired cache item results in a cache miss,
+ * see also [`get()`](#get).
+ *
+ * ```php
+ * $cache->set('foo', 'bar', 60);
+ * ```
+ *
+ * This example eventually sets the value of the key `foo` to `bar`. If it
+ * already exists, it is overridden.
+ *
+ * This interface does not enforce any particular TTL resolution, so special
+ * care may have to be taken if you rely on very high precision with
+ * millisecond accuracy or below. Cache implementations SHOULD work on a
+ * best effort basis and SHOULD provide at least second accuracy unless
+ * otherwise noted. Many existing cache implementations are known to provide
+ * microsecond or millisecond accuracy, but it's generally not recommended
+ * to rely on this high precision.
+ *
+ * This interface suggests that cache implementations SHOULD use a monotonic
+ * time source if available. Given that a monotonic time source is only
+ * available as of PHP 7.3 by default, cache implementations MAY fall back
+ * to using wall-clock time.
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you store a cache item with a TTL of 30s and then
+ * adjust your system time forward by 20s, the cache item SHOULD still
+ * expire in 30s.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @param ?float $ttl
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function set($key, $value, $ttl = null);
+
+ /**
+ * Deletes an item from the cache.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. When no item for `$key` is found in the cache, it also resolves
+ * to `true`. If the cache implementation has to go over the network to
+ * delete it, it may take a while.
+ *
+ * ```php
+ * $cache->delete('foo');
+ * ```
+ *
+ * This example eventually deletes the key `foo` from the cache. As with
+ * `set()`, this may not happen instantly and a promise is returned to
+ * provide guarantees whether or not the item has been removed from cache.
+ *
+ * @param string $key
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function delete($key);
+
+ /**
+ * Retrieves multiple cache items by their unique keys.
+ *
+ * This method will resolve with an array of cached values on success or with the
+ * given `$default` value when an item can not be found or when an error occurs.
+ * Similarly, an expired cache item (once the time-to-live is expired) is
+ * considered a cache miss.
+ *
+ * ```php
+ * $cache->getMultiple(array('name', 'age'))->then(function (array $values) {
+ * $name = $values['name'] ?? 'User';
+ * $age = $values['age'] ?? 'n/a';
+ *
+ * echo $name . ' is ' . $age . PHP_EOL;
+ * });
+ * ```
+ *
+ * This example fetches the cache items for the `name` and `age` keys and
+ * prints some example output. You can use any of the composition provided
+ * by [promises](https://github.com/reactphp/promise).
+ *
+ * @param string[] $keys A list of keys that can obtained in a single operation.
+ * @param mixed $default Default value to return for keys that do not exist.
+ * @return PromiseInterface Returns a promise which resolves to an `array` of cached values
+ */
+ public function getMultiple(array $keys, $default = null);
+
+ /**
+ * Persists a set of key => value pairs in the cache, with an optional TTL.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. If the cache implementation has to go over the network to store
+ * it, it may take a while.
+ *
+ * The optional `$ttl` parameter sets the maximum time-to-live in seconds
+ * for these cache items. If this parameter is omitted (or `null`), these items
+ * will stay in the cache for as long as the underlying implementation
+ * supports. Trying to access an expired cache items results in a cache miss,
+ * see also [`get()`](#get).
+ *
+ * ```php
+ * $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
+ * ```
+ *
+ * This example eventually sets the list of values - the key `foo` to 1 value
+ * and the key `bar` to 2. If some of the keys already exist, they are overridden.
+ *
+ * @param array $values A list of key => value pairs for a multiple-set operation.
+ * @param ?float $ttl Optional. The TTL value of this item.
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function setMultiple(array $values, $ttl = null);
+
+ /**
+ * Deletes multiple cache items in a single operation.
+ *
+ * @param string[] $keys A list of string-based keys to be deleted.
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function deleteMultiple(array $keys);
+
+ /**
+ * Wipes clean the entire cache.
+ *
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function clear();
+
+ /**
+ * Determines whether an item is present in the cache.
+ *
+ * This method will resolve with `true` on success or `false` when no item can be found
+ * or when an error occurs. Similarly, an expired cache item (once the time-to-live
+ * is expired) is considered a cache miss.
+ *
+ * ```php
+ * $cache
+ * ->has('foo')
+ * ->then('var_dump');
+ * ```
+ *
+ * This example checks if the value of the key `foo` is set in the cache and passes
+ * the result to the `var_dump` function. You can use any of the composition provided by
+ * [promises](https://github.com/reactphp/promise).
+ *
+ * NOTE: It is recommended that has() is only to be used for cache warming type purposes
+ * and not to be used within your live applications operations for get/set, as this method
+ * is subject to a race condition where your has() will return true and immediately after,
+ * another script can remove it making the state of your app out of date.
+ *
+ * @param string $key The cache item key.
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function has($key);
+}
diff --git a/instafeed/vendor/react/cache/tests/ArrayCacheTest.php b/instafeed/vendor/react/cache/tests/ArrayCacheTest.php
new file mode 100755
index 0000000..3b5bd8c
--- /dev/null
+++ b/instafeed/vendor/react/cache/tests/ArrayCacheTest.php
@@ -0,0 +1,322 @@
+cache = new ArrayCache();
+ }
+
+ /** @test */
+ public function getShouldResolvePromiseWithNullForNonExistentKey()
+ {
+ $success = $this->createCallableMock();
+ $success
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(null);
+
+ $this->cache
+ ->get('foo')
+ ->then(
+ $success,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function setShouldSetKey()
+ {
+ $setPromise = $this->cache
+ ->set('foo', 'bar');
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $setPromise->then($mock);
+
+ $success = $this->createCallableMock();
+ $success
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with('bar');
+
+ $this->cache
+ ->get('foo')
+ ->then($success);
+ }
+
+ /** @test */
+ public function deleteShouldDeleteKey()
+ {
+ $this->cache
+ ->set('foo', 'bar');
+
+ $deletePromise = $this->cache
+ ->delete('foo');
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $deletePromise->then($mock);
+
+ $this->cache
+ ->get('foo')
+ ->then(
+ $this->expectCallableOnce(),
+ $this->expectCallableNever()
+ );
+ }
+
+ public function testGetWillResolveWithNullForCacheMiss()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testGetWillResolveWithDefaultValueForCacheMiss()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith('bar'));
+ }
+
+ public function testGetWillResolveWithExplicitNullValueForCacheHit()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->set('foo', null);
+ $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testLimitSizeToZeroDoesNotStoreAnyData()
+ {
+ $this->cache = new ArrayCache(0);
+
+ $this->cache->set('foo', 'bar');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testLimitSizeToOneWillOnlyReturnLastWrite()
+ {
+ $this->cache = new ArrayCache(1);
+
+ $this->cache->set('foo', '1');
+ $this->cache->set('bar', '2');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith('2'));
+ }
+
+ public function testOverwriteWithLimitedSizeWillUpdateLRUInfo()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1');
+ $this->cache->set('bar', '2');
+ $this->cache->set('foo', '3');
+ $this->cache->set('baz', '4');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('3'));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith(null));
+ $this->cache->get('baz')->then($this->expectCallableOnceWith('4'));
+ }
+
+ public function testGetWithLimitedSizeWillUpdateLRUInfo()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1');
+ $this->cache->set('bar', '2');
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ $this->cache->set('baz', '3');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith(null));
+ $this->cache->get('baz')->then($this->expectCallableOnceWith('3'));
+ }
+
+ public function testGetWillResolveWithValueIfItemIsNotExpired()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->set('foo', '1', 10);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ }
+
+ public function testGetWillResolveWithDefaultIfItemIsExpired()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->set('foo', '1', 0);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testSetWillOverwritOldestItemIfNoEntryIsExpired()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1', 10);
+ $this->cache->set('bar', '2', 20);
+ $this->cache->set('baz', '3', 30);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1', 10);
+ $this->cache->set('bar', '2', 0);
+ $this->cache->set('baz', '3', 30);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testGetMultiple()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', '1');
+
+ $this->cache
+ ->getMultiple(array('foo', 'bar'), 'baz')
+ ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => 'baz')));
+ }
+
+ public function testSetMultiple()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->setMultiple(array('foo' => '1', 'bar' => '2'), 10);
+
+ $this->cache
+ ->getMultiple(array('foo', 'bar'))
+ ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => '2')));
+ }
+
+ public function testDeleteMultiple()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3));
+
+ $this->cache
+ ->deleteMultiple(array('foo', 'baz'))
+ ->then($this->expectCallableOnceWith(true));
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+
+ $this->cache
+ ->has('bar')
+ ->then($this->expectCallableOnceWith(true));
+
+ $this->cache
+ ->has('baz')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function testClearShouldClearCache()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3));
+
+ $this->cache->clear();
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+
+ $this->cache
+ ->has('bar')
+ ->then($this->expectCallableOnceWith(false));
+
+ $this->cache
+ ->has('baz')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function hasShouldResolvePromiseForExistingKey()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', 'bar');
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(true));
+ }
+
+ public function hasShouldResolvePromiseForNonExistentKey()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', 'bar');
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function testHasWillResolveIfItemIsNotExpired()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', '1', 10);
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(true));
+ }
+
+ public function testHasWillResolveIfItemIsExpired()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', '1', 0);
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function testHasWillResolveForExplicitNullValue()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', null);
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(true));
+ }
+
+ public function testHasWithLimitedSizeWillUpdateLRUInfo()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', 1);
+ $this->cache->set('bar', 2);
+ $this->cache->has('foo')->then($this->expectCallableOnceWith(true));
+ $this->cache->set('baz', 3);
+
+ $this->cache->has('foo')->then($this->expectCallableOnceWith(1));
+ $this->cache->has('bar')->then($this->expectCallableOnceWith(false));
+ $this->cache->has('baz')->then($this->expectCallableOnceWith(3));
+ }
+}
diff --git a/instafeed/vendor/react/cache/tests/CallableStub.php b/instafeed/vendor/react/cache/tests/CallableStub.php
new file mode 100755
index 0000000..2f547cd
--- /dev/null
+++ b/instafeed/vendor/react/cache/tests/CallableStub.php
@@ -0,0 +1,10 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($param)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($param);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Cache\CallableStub')->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/dns/.gitignore b/instafeed/vendor/react/dns/.gitignore
new file mode 100755
index 0000000..19982ea
--- /dev/null
+++ b/instafeed/vendor/react/dns/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
\ No newline at end of file
diff --git a/instafeed/vendor/react/dns/.travis.yml b/instafeed/vendor/react/dns/.travis.yml
new file mode 100755
index 0000000..459e852
--- /dev/null
+++ b/instafeed/vendor/react/dns/.travis.yml
@@ -0,0 +1,32 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+# - hhvm # requires legacy phpunit & ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - php: hhvm
+ install: composer require phpunit/phpunit:^5 --dev --no-interaction
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/instafeed/vendor/react/dns/CHANGELOG.md b/instafeed/vendor/react/dns/CHANGELOG.md
new file mode 100755
index 0000000..314f023
--- /dev/null
+++ b/instafeed/vendor/react/dns/CHANGELOG.md
@@ -0,0 +1,261 @@
+# Changelog
+
+## 0.4.19 (2019-07-10)
+
+* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6.
+ (#133 by @clue)
+
+## 0.4.18 (2019-09-07)
+
+* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`,
+ respect TTL from response records when caching and do not cache truncated responses.
+ (#129 by @clue)
+
+* Feature: Limit cache size to 256 last responses by default.
+ (#127 by @clue)
+
+* Feature: Cooperatively resolve hosts to avoid running same query concurrently.
+ (#125 by @clue)
+
+## 0.4.17 (2019-04-01)
+
+* Feature: Support parsing `authority` and `additional` records from DNS response.
+ (#123 by @clue)
+
+* Feature: Support dumping records as part of outgoing binary DNS message.
+ (#124 by @clue)
+
+* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0
+ (#121 by @clue)
+
+* Improve test suite to add forward compatibility with PHPUnit 7,
+ test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM.
+ (#122 by @clue)
+
+## 0.4.16 (2018-11-11)
+
+* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references.
+ (#118 by @clue)
+
+* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages,
+ malformed record data or malformed compressed domain name labels.
+ (#115 and #117 by @clue)
+
+* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset.
+ (#116 by @clue)
+
+* Fix: Fix caching advanced MX/SRV/TXT/SOA structures.
+ (#112 by @clue)
+
+## 0.4.15 (2018-07-02)
+
+* Feature: Add `resolveAll()` method to support custom query types in `Resolver`.
+ (#110 by @clue and @WyriHaximus)
+
+ ```php
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ });
+ ```
+
+* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records.
+ (#104, #105, #106, #107 and #108 by @clue)
+
+* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data.
+ (#104 by @clue)
+
+* Feature: Improve error messages for failed queries and improve documentation.
+ (#109 by @clue)
+
+* Feature: Add reverse DNS lookup example.
+ (#111 by @clue)
+
+## 0.4.14 (2018-06-26)
+
+* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages
+ to avoid cache poisoning attacks and deprecate legacy `Executor`.
+ (#101 and #103 by @clue)
+
+* Feature: Forward compatibility with Cache 0.5
+ (#102 by @clue)
+
+* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API.
+ (#99 by @clue)
+
+## 0.4.13 (2018-02-27)
+
+* Add `Config::loadSystemConfigBlocking()` to load default system config
+ and support parsing DNS config on all supported platforms
+ (`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows)
+ (#92, #93, #94 and #95 by @clue)
+
+ ```php
+ $config = Config::loadSystemConfigBlocking();
+ $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+ ```
+
+* Remove unneeded cyclic dependency on react/socket
+ (#96 by @clue)
+
+## 0.4.12 (2018-01-14)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6,
+ test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases,
+ add test group to skip integration tests relying on internet connection
+ and add minor documentation improvements.
+ (#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor)
+
+## 0.4.11 (2017-08-25)
+
+* Feature: Support resolving from default hosts file
+ (#75, #76 and #77 by @clue)
+
+ This means that resolving hosts such as `localhost` will now work as
+ expected across all platforms with no changes required:
+
+ ```php
+ $resolver->resolve('localhost')->then(function ($ip) {
+ echo 'IP: ' . $ip;
+ });
+ ```
+
+ The new `HostsExecutor` exists for advanced usage and is otherwise used
+ internally for this feature.
+
+## 0.4.10 (2017-08-10)
+
+* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and
+ lock minimum dependencies and work around circular dependency for tests
+ (#70 and #71 by @clue)
+
+* Fix: Work around DNS timeout issues for Windows users
+ (#74 by @clue)
+
+* Documentation and examples for advanced usage
+ (#66 by @WyriHaximus)
+
+* Remove broken TCP code, do not retry with invalid TCP query
+ (#73 by @clue)
+
+* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and
+ lock Travis distro so new defaults will not break the build and
+ fix failing tests for PHP 7.1
+ (#68 by @WyriHaximus and #69 and #72 by @clue)
+
+## 0.4.9 (2017-05-01)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#61 by @clue)
+
+## 0.4.8 (2017-04-16)
+
+* Feature: Add support for the AAAA record type to the protocol parser
+ (#58 by @othillo)
+
+* Feature: Add support for the PTR record type to the protocol parser
+ (#59 by @othillo)
+
+## 0.4.7 (2017-03-31)
+
+* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component
+ (#57 by @clue)
+
+## 0.4.6 (2017-03-11)
+
+* Fix: Fix DNS timeout issues for Windows users and add forward compatibility
+ with Stream v0.5 and upcoming v0.6
+ (#53 by @clue)
+
+* Improve test suite by adding PHPUnit to `require-dev`
+ (#54 by @clue)
+
+## 0.4.5 (2017-03-02)
+
+* Fix: Ensure we ignore the case of the answer
+ (#51 by @WyriHaximus)
+
+* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal
+ code re-use for upcoming versions.
+ (#48 and #49 by @clue)
+
+## 0.4.4 (2017-02-13)
+
+* Fix: Fix handling connection and stream errors
+ (#45 by @clue)
+
+* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component
+ (#46 and #47 by @clue)
+
+## 0.4.3 (2016-07-31)
+
+* Feature: Allow for cache adapter injection (#38 by @WyriHaximus)
+
+ ```php
+ $factory = new React\Dns\Resolver\Factory();
+
+ $cache = new MyCustomCacheInstance();
+ $resolver = $factory->createCached('8.8.8.8', $loop, $cache);
+ ```
+
+* Feature: Support Promise cancellation (#35 by @clue)
+
+ ```php
+ $promise = $resolver->resolve('reactphp.org');
+
+ $promise->cancel();
+ ```
+
+## 0.4.2 (2016-02-24)
+
+* Repository maintenance, split off from main repo, improve test suite and documentation
+* First class support for PHP7 and HHVM (#34 by @clue)
+* Adjust compatibility to 5.3 (#30 by @clue)
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* Bug fix: Properly resolve CNAME aliases
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+* Bump React dependencies to v0.4
+
+## 0.3.2 (2013-05-10)
+
+* Feature: Support default port for IPv6 addresses (@clue)
+
+## 0.3.0 (2013-04-14)
+
+* Bump React dependencies to v0.3
+
+## 0.2.6 (2012-12-26)
+
+* Feature: New cache component, used by DNS
+
+## 0.2.5 (2012-11-26)
+
+* Version bump
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Change to promise-based API (@jsor)
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Feature: DNS executor timeout handling (@arnaud-lb)
+* Feature: DNS retry executor (@arnaud-lb)
+
+## 0.2.1 (2012-10-14)
+
+* Minor adjustments to DNS parser
+
+## 0.2.0 (2012-09-10)
+
+* Feature: DNS resolver
diff --git a/instafeed/vendor/react/dns/LICENSE b/instafeed/vendor/react/dns/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/instafeed/vendor/react/dns/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/react/dns/README.md b/instafeed/vendor/react/dns/README.md
new file mode 100755
index 0000000..6cdffe9
--- /dev/null
+++ b/instafeed/vendor/react/dns/README.md
@@ -0,0 +1,344 @@
+# Dns
+
+[](https://travis-ci.org/reactphp/dns)
+
+Async DNS resolver for [ReactPHP](https://reactphp.org/).
+
+The main point of the DNS component is to provide async DNS resolution.
+However, it is really a toolkit for working with DNS messages, and could
+easily be used to create a DNS server.
+
+**Table of contents**
+
+* [Basic usage](#basic-usage)
+* [Caching](#caching)
+ * [Custom cache adapter](#custom-cache-adapter)
+* [Resolver](#resolver)
+ * [resolve()](#resolve)
+ * [resolveAll()](#resolveall)
+* [Advanced usage](#advanced-usage)
+ * [UdpTransportExecutor](#udptransportexecutor)
+ * [HostsFileExecutor](#hostsfileexecutor)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [References](#references)
+
+## Basic usage
+
+The most basic usage is to just create a resolver through the resolver
+factory. All you need to give it is a nameserver, then you can start resolving
+names, baby!
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$config = React\Dns\Config\Config::loadSystemConfigBlocking();
+$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->create($server, $loop);
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+$loop->run();
+```
+
+See also the [first example](examples).
+
+The `Config` class can be used to load the system default config. This is an
+operation that may access the filesystem and block. Ideally, this method should
+thus be executed only once before the loop starts and not repeatedly while it is
+running.
+Note that this class may return an *empty* configuration if the system config
+can not be loaded. As such, you'll likely want to apply a default nameserver
+as above if none can be found.
+
+> Note that the factory loads the hosts file from the filesystem once when
+ creating the resolver instance.
+ Ideally, this method should thus be executed only once before the loop starts
+ and not repeatedly while it is running.
+
+But there's more.
+
+## Caching
+
+You can cache results by configuring the resolver to use a `CachedExecutor`:
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$config = React\Dns\Config\Config::loadSystemConfigBlocking();
+$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->createCached($server, $loop);
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+...
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+$loop->run();
+```
+
+If the first call returns before the second, only one query will be executed.
+The second result will be served from an in memory cache.
+This is particularly useful for long running scripts where the same hostnames
+have to be looked up multiple times.
+
+See also the [third example](examples).
+
+### Custom cache adapter
+
+By default, the above will use an in memory cache.
+
+You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead:
+
+```php
+$cache = new React\Cache\ArrayCache();
+$loop = React\EventLoop\Factory::create();
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->createCached('8.8.8.8', $loop, $cache);
+```
+
+See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
+
+## Resolver
+
+### resolve()
+
+The `resolve(string $domain): PromiseInterface` method can be used to
+resolve the given $domain name to a single IPv4 address (type `A` query).
+
+```php
+$resolver->resolve('reactphp.org')->then(function ($ip) {
+ echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
+});
+```
+
+This is one of the main methods in this package. It sends a DNS query
+for the given $domain name to your DNS server and returns a single IP
+address on success.
+
+If the DNS server sends a DNS response message that contains more than
+one IP address for this query, it will randomly pick one of the IP
+addresses from the response. If you want the full list of IP addresses
+or want to send a different type of query, you should use the
+[`resolveAll()`](#resolveall) method instead.
+
+If the DNS server sends a DNS response message that indicates an error
+code, this method will reject with a `RecordNotFoundException`. Its
+message and code can be used to check for the response code.
+
+If the DNS communication fails and the server does not respond with a
+valid response message, this message will reject with an `Exception`.
+
+Pending DNS queries can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $resolver->resolve('reactphp.org');
+
+$promise->cancel();
+```
+
+### resolveAll()
+
+The `resolveAll(string $host, int $type): PromiseInterface` method can be used to
+resolve all record values for the given $domain name and query $type.
+
+```php
+$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
+ echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+});
+
+$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+});
+```
+
+This is one of the main methods in this package. It sends a DNS query
+for the given $domain name to your DNS server and returns a list with all
+record values on success.
+
+If the DNS server sends a DNS response message that contains one or more
+records for this query, it will return a list with all record values
+from the response. You can use the `Message::TYPE_*` constants to control
+which type of query will be sent. Note that this method always returns a
+list of record values, but each record value type depends on the query
+type. For example, it returns the IPv4 addresses for type `A` queries,
+the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
+`CNAME` and `PTR` queries and structured data for other queries. See also
+the `Record` documentation for more details.
+
+If the DNS server sends a DNS response message that indicates an error
+code, this method will reject with a `RecordNotFoundException`. Its
+message and code can be used to check for the response code.
+
+If the DNS communication fails and the server does not respond with a
+valid response message, this message will reject with an `Exception`.
+
+Pending DNS queries can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
+
+$promise->cancel();
+```
+
+## Advanced Usage
+
+### UdpTransportExecutor
+
+The `UdpTransportExecutor` can be used to
+send DNS queries over a UDP transport.
+
+This is the main class that sends a DNS query to your DNS server and is used
+internally by the `Resolver` for the actual message transport.
+
+For more advanced usages one can utilize this class directly.
+The following example looks up the `IPv6` address for `igor.io`.
+
+```php
+$loop = Factory::create();
+$executor = new UdpTransportExecutor($loop);
+
+$executor->query(
+ '8.8.8.8:53',
+ new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
+)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv6: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
+```
+
+See also the [fourth example](examples).
+
+Note that this executor does not implement a timeout, so you will very likely
+want to use this in combination with a `TimeoutExecutor` like this:
+
+```php
+$executor = new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 3.0,
+ $loop
+);
+```
+
+Also note that this executor uses an unreliable UDP transport and that it
+does not implement any retry logic, so you will likely want to use this in
+combination with a `RetryExecutor` like this:
+
+```php
+$executor = new RetryExecutor(
+ new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 3.0,
+ $loop
+ )
+);
+```
+
+Note that this executor is entirely async and as such allows you to execute
+any number of queries concurrently. You should probably limit the number of
+concurrent queries in your application or you're very likely going to face
+rate limitations and bans on the resolver end. For many common applications,
+you may want to avoid sending the same query multiple times when the first
+one is still pending, so you will likely want to use this in combination with
+a `CoopExecutor` like this:
+
+```php
+$executor = new CoopExecutor(
+ new RetryExecutor(
+ new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 3.0,
+ $loop
+ )
+ )
+);
+```
+
+> Internally, this class uses PHP's UDP sockets and does not take advantage
+ of [react/datagram](https://github.com/reactphp/datagram) purely for
+ organizational reasons to avoid a cyclic dependency between the two
+ packages. Higher-level components should take advantage of the Datagram
+ component instead of reimplementing this socket logic from scratch.
+
+### HostsFileExecutor
+
+Note that the above `UdpTransportExecutor` class always performs an actual DNS query.
+If you also want to take entries from your hosts file into account, you may
+use this code:
+
+```php
+$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();
+
+$executor = new UdpTransportExecutor($loop);
+$executor = new HostsFileExecutor($hosts, $executor);
+
+$executor->query(
+ '8.8.8.8:53',
+ new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
+);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/dns:^0.4.19
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## References
+
+* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities
+* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification
diff --git a/instafeed/vendor/react/dns/composer.json b/instafeed/vendor/react/dns/composer.json
new file mode 100755
index 0000000..3ddf6e4
--- /dev/null
+++ b/instafeed/vendor/react/dns/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "react/dns",
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.1 || ^1.2.1",
+ "react/promise-timer": "^1.2",
+ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": { "React\\Dns\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Dns\\": "tests" }
+ }
+}
diff --git a/instafeed/vendor/react/dns/examples/01-one.php b/instafeed/vendor/react/dns/examples/01-one.php
new file mode 100755
index 0000000..5db164f
--- /dev/null
+++ b/instafeed/vendor/react/dns/examples/01-one.php
@@ -0,0 +1,22 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/react/dns/examples/02-concurrent.php b/instafeed/vendor/react/dns/examples/02-concurrent.php
new file mode 100755
index 0000000..87e3f5c
--- /dev/null
+++ b/instafeed/vendor/react/dns/examples/02-concurrent.php
@@ -0,0 +1,27 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$names = array_slice($argv, 1);
+if (!$names) {
+ $names = array('google.com', 'www.google.com', 'gmail.com');
+}
+
+foreach ($names as $name) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+}
+
+$loop->run();
diff --git a/instafeed/vendor/react/dns/examples/03-cached.php b/instafeed/vendor/react/dns/examples/03-cached.php
new file mode 100755
index 0000000..e76a27c
--- /dev/null
+++ b/instafeed/vendor/react/dns/examples/03-cached.php
@@ -0,0 +1,40 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->createCached($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+}, 'printf');
+
+$loop->addTimer(1.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->addTimer(2.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->addTimer(3.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->run();
diff --git a/instafeed/vendor/react/dns/examples/11-all-ips.php b/instafeed/vendor/react/dns/examples/11-all-ips.php
new file mode 100755
index 0000000..d118bbb
--- /dev/null
+++ b/instafeed/vendor/react/dns/examples/11-all-ips.php
@@ -0,0 +1,31 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolveAll($name, Message::TYPE_A)->then(function (array $ips) use ($name) {
+ echo 'IPv4 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
+}, function (Exception $e) use ($name) {
+ echo 'No IPv4 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
+});
+
+$resolver->resolveAll($name, Message::TYPE_AAAA)->then(function (array $ips) use ($name) {
+ echo 'IPv6 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
+}, function (Exception $e) use ($name) {
+ echo 'No IPv6 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
+});
+
+$loop->run();
diff --git a/instafeed/vendor/react/dns/examples/12-all-types.php b/instafeed/vendor/react/dns/examples/12-all-types.php
new file mode 100755
index 0000000..438ee86
--- /dev/null
+++ b/instafeed/vendor/react/dns/examples/12-all-types.php
@@ -0,0 +1,25 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'google.com';
+$type = constant('React\Dns\Model\Message::TYPE_' . (isset($argv[2]) ? $argv[2] : 'TXT'));
+
+$resolver->resolveAll($name, $type)->then(function (array $values) {
+ var_dump($values);
+}, function (Exception $e) {
+ echo $e->getMessage() . PHP_EOL;
+});
+
+$loop->run();
diff --git a/instafeed/vendor/react/dns/examples/13-reverse-dns.php b/instafeed/vendor/react/dns/examples/13-reverse-dns.php
new file mode 100755
index 0000000..7bc08f5
--- /dev/null
+++ b/instafeed/vendor/react/dns/examples/13-reverse-dns.php
@@ -0,0 +1,35 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$ip = isset($argv[1]) ? $argv[1] : '8.8.8.8';
+
+if (@inet_pton($ip) === false) {
+ exit('Error: Given argument is not a valid IP' . PHP_EOL);
+}
+
+if (strpos($ip, ':') === false) {
+ $name = inet_ntop(strrev(inet_pton($ip))) . '.in-addr.arpa';
+} else {
+ $name = wordwrap(strrev(bin2hex(inet_pton($ip))), 1, '.', true) . '.ip6.arpa';
+}
+
+$resolver->resolveAll($name, Message::TYPE_PTR)->then(function (array $names) {
+ var_dump($names);
+}, function (Exception $e) {
+ echo $e->getMessage() . PHP_EOL;
+});
+
+$loop->run();
diff --git a/instafeed/vendor/react/dns/examples/91-query-a-and-aaaa.php b/instafeed/vendor/react/dns/examples/91-query-a-and-aaaa.php
new file mode 100755
index 0000000..e4a3feb
--- /dev/null
+++ b/instafeed/vendor/react/dns/examples/91-query-a-and-aaaa.php
@@ -0,0 +1,29 @@
+query('8.8.8.8:53', $ipv4Query)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv4: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+$executor->query('8.8.8.8:53', $ipv6Query)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv6: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/react/dns/examples/92-query-any.php b/instafeed/vendor/react/dns/examples/92-query-any.php
new file mode 100755
index 0000000..dcc14ae
--- /dev/null
+++ b/instafeed/vendor/react/dns/examples/92-query-any.php
@@ -0,0 +1,71 @@
+query('8.8.8.8:53', $any)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ /* @var $answer Record */
+
+ $data = $answer->data;
+
+ switch ($answer->type) {
+ case Message::TYPE_A:
+ $type = 'A';
+ break;
+ case Message::TYPE_AAAA:
+ $type = 'AAAA';
+ break;
+ case Message::TYPE_NS:
+ $type = 'NS';
+ break;
+ case Message::TYPE_PTR:
+ $type = 'PTR';
+ break;
+ case Message::TYPE_CNAME:
+ $type = 'CNAME';
+ break;
+ case Message::TYPE_TXT:
+ // TXT records can contain a list of (binary) strings for each record.
+ // here, we assume this is printable ASCII and simply concatenate output
+ $type = 'TXT';
+ $data = implode('', $data);
+ break;
+ case Message::TYPE_MX:
+ // MX records contain "priority" and "target", only dump its values here
+ $type = 'MX';
+ $data = implode(' ', $data);
+ break;
+ case Message::TYPE_SRV:
+ // SRV records contains priority, weight, port and target, dump structure here
+ $type = 'SRV';
+ $data = json_encode($data);
+ break;
+ case Message::TYPE_SOA:
+ // SOA records contain structured data, dump structure here
+ $type = 'SOA';
+ $data = json_encode($data);
+ break;
+ default:
+ // unknown type uses HEX format
+ $type = 'Type ' . $answer->type;
+ $data = wordwrap(strtoupper(bin2hex($data)), 2, ' ', true);
+ }
+
+ echo $type . ': ' . $data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/react/dns/phpunit.xml.dist b/instafeed/vendor/react/dns/phpunit.xml.dist
new file mode 100755
index 0000000..04d426b
--- /dev/null
+++ b/instafeed/vendor/react/dns/phpunit.xml.dist
@@ -0,0 +1,24 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/instafeed/vendor/react/dns/src/BadServerException.php b/instafeed/vendor/react/dns/src/BadServerException.php
new file mode 100755
index 0000000..3bf50f1
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/BadServerException.php
@@ -0,0 +1,7 @@
+nameservers = $matches[1];
+
+ return $config;
+ }
+
+ /**
+ * Loads the DNS configurations from Windows's WMIC (from the given command or default command)
+ *
+ * Note that this method blocks while loading the given command and should
+ * thus be used with care! While this should be relatively fast for normal
+ * WMIC commands, it remains unknown if this may block under certain
+ * circumstances. In particular, this method should only be executed before
+ * the loop starts, not while it is running.
+ *
+ * Note that this method will only try to execute the given command try to
+ * parse its output, irrespective of whether this command exists. In
+ * particular, this command is only available on Windows. Currently, this
+ * will only parse valid nameserver entries from the command output and will
+ * ignore all other output without complaining.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid nameserver entries can be found.
+ *
+ * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
+ * @return self
+ * @link https://ss64.com/nt/wmic.html
+ */
+ public static function loadWmicBlocking($command = null)
+ {
+ $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
+ preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
+
+ $config = new self();
+ $config->nameservers = $matches[1];
+
+ return $config;
+ }
+
+ public $nameservers = array();
+}
diff --git a/instafeed/vendor/react/dns/src/Config/FilesystemFactory.php b/instafeed/vendor/react/dns/src/Config/FilesystemFactory.php
new file mode 100755
index 0000000..68cec3e
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Config/FilesystemFactory.php
@@ -0,0 +1,73 @@
+loop = $loop;
+ }
+
+ public function create($filename)
+ {
+ return $this
+ ->loadEtcResolvConf($filename)
+ ->then(array($this, 'parseEtcResolvConf'));
+ }
+
+ /**
+ * @param string $contents
+ * @return Promise
+ * @deprecated see Config instead
+ */
+ public function parseEtcResolvConf($contents)
+ {
+ return Promise\resolve(Config::loadResolvConfBlocking(
+ 'data://text/plain;base64,' . base64_encode($contents)
+ ));
+ }
+
+ public function loadEtcResolvConf($filename)
+ {
+ if (!file_exists($filename)) {
+ return Promise\reject(new \InvalidArgumentException("The filename for /etc/resolv.conf given does not exist: $filename"));
+ }
+
+ try {
+ $deferred = new Deferred();
+
+ $fd = fopen($filename, 'r');
+ stream_set_blocking($fd, 0);
+
+ $contents = '';
+
+ $stream = class_exists('React\Stream\ReadableResourceStream') ? new ReadableResourceStream($fd, $this->loop) : new Stream($fd, $this->loop);
+ $stream->on('data', function ($data) use (&$contents) {
+ $contents .= $data;
+ });
+ $stream->on('end', function () use (&$contents, $deferred) {
+ $deferred->resolve($contents);
+ });
+ $stream->on('error', function ($error) use ($deferred) {
+ $deferred->reject($error);
+ });
+
+ return $deferred->promise();
+ } catch (\Exception $e) {
+ return Promise\reject($e);
+ }
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Config/HostsFile.php b/instafeed/vendor/react/dns/src/Config/HostsFile.php
new file mode 100755
index 0000000..5b6277e
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Config/HostsFile.php
@@ -0,0 +1,151 @@
+contents = $contents;
+ }
+
+ /**
+ * Returns all IPs for the given hostname
+ *
+ * @param string $name
+ * @return string[]
+ */
+ public function getIpsForHost($name)
+ {
+ $name = strtolower($name);
+
+ $ips = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line);
+ $ip = array_shift($parts);
+ if ($parts && array_search($name, $parts) !== false) {
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
+ $ip = substr($ip, 0, $pos);
+ }
+
+ if (@inet_pton($ip) !== false) {
+ $ips[] = $ip;
+ }
+ }
+ }
+
+ return $ips;
+ }
+
+ /**
+ * Returns all hostnames for the given IPv4 or IPv6 address
+ *
+ * @param string $ip
+ * @return string[]
+ */
+ public function getHostsForIp($ip)
+ {
+ // check binary representation of IP to avoid string case and short notation
+ $ip = @inet_pton($ip);
+ if ($ip === false) {
+ return array();
+ }
+
+ $names = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line, null, PREG_SPLIT_NO_EMPTY);
+ $addr = array_shift($parts);
+
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
+ $addr = substr($addr, 0, $pos);
+ }
+
+ if (@inet_pton($addr) === $ip) {
+ foreach ($parts as $part) {
+ $names[] = $part;
+ }
+ }
+ }
+
+ return $names;
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Model/HeaderBag.php b/instafeed/vendor/react/dns/src/Model/HeaderBag.php
new file mode 100755
index 0000000..0093bd3
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Model/HeaderBag.php
@@ -0,0 +1,59 @@
+ 0,
+ 'anCount' => 0,
+ 'nsCount' => 0,
+ 'arCount' => 0,
+ 'qr' => 0,
+ 'opcode' => Message::OPCODE_QUERY,
+ 'aa' => 0,
+ 'tc' => 0,
+ 'rd' => 0,
+ 'ra' => 0,
+ 'z' => 0,
+ 'rcode' => Message::RCODE_OK,
+ );
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public $data = '';
+
+ public function get($name)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
+ }
+
+ public function set($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ public function isQuery()
+ {
+ return 0 === $this->attributes['qr'];
+ }
+
+ public function isResponse()
+ {
+ return 1 === $this->attributes['qr'];
+ }
+
+ public function isTruncated()
+ {
+ return 1 === $this->attributes['tc'];
+ }
+
+ public function populateCounts(Message $message)
+ {
+ $this->attributes['qdCount'] = count($message->questions);
+ $this->attributes['anCount'] = count($message->answers);
+ $this->attributes['nsCount'] = count($message->authority);
+ $this->attributes['arCount'] = count($message->additional);
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Model/Message.php b/instafeed/vendor/react/dns/src/Model/Message.php
new file mode 100755
index 0000000..da859e7
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Model/Message.php
@@ -0,0 +1,188 @@
+header->set('id', self::generateId());
+ $request->header->set('rd', 1);
+ $request->questions[] = (array) $query;
+ $request->prepare();
+
+ return $request;
+ }
+
+ /**
+ * Creates a new response message for the given query with the given answer records
+ *
+ * @param Query $query
+ * @param Record[] $answers
+ * @return self
+ */
+ public static function createResponseWithAnswersForQuery(Query $query, array $answers)
+ {
+ $response = new Message();
+ $response->header->set('id', self::generateId());
+ $response->header->set('qr', 1);
+ $response->header->set('opcode', Message::OPCODE_QUERY);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = (array) $query;
+
+ foreach ($answers as $record) {
+ $response->answers[] = $record;
+ }
+
+ $response->prepare();
+
+ return $response;
+ }
+
+ /**
+ * generates a random 16 bit message ID
+ *
+ * This uses a CSPRNG so that an outside attacker that is sending spoofed
+ * DNS response messages can not guess the message ID to avoid possible
+ * cache poisoning attacks.
+ *
+ * The `random_int()` function is only available on PHP 7+ or when
+ * https://github.com/paragonie/random_compat is installed. As such, using
+ * the latest supported PHP version is highly recommended. This currently
+ * falls back to a less secure random number generator on older PHP versions
+ * in the hope that this system is properly protected against outside
+ * attackers, for example by using one of the common local DNS proxy stubs.
+ *
+ * @return int
+ * @see self::getId()
+ * @codeCoverageIgnore
+ */
+ private static function generateId()
+ {
+ if (function_exists('random_int')) {
+ return random_int(0, 0xffff);
+ }
+ return mt_rand(0, 0xffff);
+ }
+
+ /**
+ * @var HeaderBag
+ */
+ public $header;
+
+ /**
+ * This should be an array of Query objects. For BC reasons, this currently
+ * references a nested array with a structure that results from casting the
+ * Query objects to an array:
+ *
+ * ```php
+ * $questions = array(
+ * array(
+ * 'name' => 'reactphp.org',
+ * 'type' => Message::TYPE_A,
+ * 'class' => Message::CLASS_IN
+ * )
+ * );
+ * ```
+ *
+ * @var array
+ * @see Query
+ */
+ public $questions = array();
+
+ /**
+ * @var Record[]
+ */
+ public $answers = array();
+
+ /**
+ * @var Record[]
+ */
+ public $authority = array();
+
+ /**
+ * @var Record[]
+ */
+ public $additional = array();
+
+ /**
+ * @deprecated still used internally for BC reasons, should not be used externally.
+ */
+ public $data = '';
+
+ /**
+ * @deprecated still used internally for BC reasons, should not be used externally.
+ */
+ public $consumed = 0;
+
+ public function __construct()
+ {
+ $this->header = new HeaderBag();
+ }
+
+ /**
+ * Returns the 16 bit message ID
+ *
+ * The response message ID has to match the request message ID. This allows
+ * the receiver to verify this is the correct response message. An outside
+ * attacker may try to inject fake responses by "guessing" the message ID,
+ * so this should use a proper CSPRNG to avoid possible cache poisoning.
+ *
+ * @return int
+ * @see self::generateId()
+ */
+ public function getId()
+ {
+ return $this->header->get('id');
+ }
+
+ /**
+ * Returns the response code (RCODE)
+ *
+ * @return int see self::RCODE_* constants
+ */
+ public function getResponseCode()
+ {
+ return $this->header->get('rcode');
+ }
+
+ public function prepare()
+ {
+ $this->header->populateCounts($this);
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Model/Record.php b/instafeed/vendor/react/dns/src/Model/Record.php
new file mode 100755
index 0000000..2504911
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Model/Record.php
@@ -0,0 +1,105 @@
+name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->ttl = $ttl;
+ $this->data = $data;
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Protocol/BinaryDumper.php b/instafeed/vendor/react/dns/src/Protocol/BinaryDumper.php
new file mode 100755
index 0000000..0391604
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Protocol/BinaryDumper.php
@@ -0,0 +1,163 @@
+headerToBinary($message->header);
+ $data .= $this->questionToBinary($message->questions);
+ $data .= $this->recordsToBinary($message->answers);
+ $data .= $this->recordsToBinary($message->authority);
+ $data .= $this->recordsToBinary($message->additional);
+
+ return $data;
+ }
+
+ /**
+ * @param HeaderBag $header
+ * @return string
+ */
+ private function headerToBinary(HeaderBag $header)
+ {
+ $data = '';
+
+ $data .= pack('n', $header->get('id'));
+
+ $flags = 0x00;
+ $flags = ($flags << 1) | $header->get('qr');
+ $flags = ($flags << 4) | $header->get('opcode');
+ $flags = ($flags << 1) | $header->get('aa');
+ $flags = ($flags << 1) | $header->get('tc');
+ $flags = ($flags << 1) | $header->get('rd');
+ $flags = ($flags << 1) | $header->get('ra');
+ $flags = ($flags << 3) | $header->get('z');
+ $flags = ($flags << 4) | $header->get('rcode');
+
+ $data .= pack('n', $flags);
+
+ $data .= pack('n', $header->get('qdCount'));
+ $data .= pack('n', $header->get('anCount'));
+ $data .= pack('n', $header->get('nsCount'));
+ $data .= pack('n', $header->get('arCount'));
+
+ return $data;
+ }
+
+ /**
+ * @param array $questions
+ * @return string
+ */
+ private function questionToBinary(array $questions)
+ {
+ $data = '';
+
+ foreach ($questions as $question) {
+ $data .= $this->domainNameToBinary($question['name']);
+ $data .= pack('n*', $question['type'], $question['class']);
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param Record[] $records
+ * @return string
+ */
+ private function recordsToBinary(array $records)
+ {
+ $data = '';
+
+ foreach ($records as $record) {
+ /* @var $record Record */
+ switch ($record->type) {
+ case Message::TYPE_A:
+ case Message::TYPE_AAAA:
+ $binary = \inet_pton($record->data);
+ break;
+ case Message::TYPE_CNAME:
+ case Message::TYPE_NS:
+ case Message::TYPE_PTR:
+ $binary = $this->domainNameToBinary($record->data);
+ break;
+ case Message::TYPE_TXT:
+ $binary = $this->textsToBinary($record->data);
+ break;
+ case Message::TYPE_MX:
+ $binary = \pack(
+ 'n',
+ $record->data['priority']
+ );
+ $binary .= $this->domainNameToBinary($record->data['target']);
+ break;
+ case Message::TYPE_SRV:
+ $binary = \pack(
+ 'n*',
+ $record->data['priority'],
+ $record->data['weight'],
+ $record->data['port']
+ );
+ $binary .= $this->domainNameToBinary($record->data['target']);
+ break;
+ case Message::TYPE_SOA:
+ $binary = $this->domainNameToBinary($record->data['mname']);
+ $binary .= $this->domainNameToBinary($record->data['rname']);
+ $binary .= \pack(
+ 'N*',
+ $record->data['serial'],
+ $record->data['refresh'],
+ $record->data['retry'],
+ $record->data['expire'],
+ $record->data['minimum']
+ );
+ break;
+ default:
+ // RDATA is already stored as binary value for unknown record types
+ $binary = $record->data;
+ }
+
+ $data .= $this->domainNameToBinary($record->name);
+ $data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
+ $data .= $binary;
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param string[] $texts
+ * @return string
+ */
+ private function textsToBinary(array $texts)
+ {
+ $data = '';
+ foreach ($texts as $text) {
+ $data .= \chr(\strlen($text)) . $text;
+ }
+ return $data;
+ }
+
+ /**
+ * @param string $host
+ * @return string
+ */
+ private function domainNameToBinary($host)
+ {
+ if ($host === '') {
+ return "\0";
+ }
+
+ return $this->textsToBinary(\explode('.', $host . '.'));
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Protocol/Parser.php b/instafeed/vendor/react/dns/src/Protocol/Parser.php
new file mode 100755
index 0000000..ada9db1
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Protocol/Parser.php
@@ -0,0 +1,395 @@
+parse($data, $message) !== $message) {
+ throw new InvalidArgumentException('Unable to parse binary message');
+ }
+
+ return $message;
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function parseChunk($data, Message $message)
+ {
+ return $this->parse($data, $message);
+ }
+
+ private function parse($data, Message $message)
+ {
+ $message->data .= $data;
+
+ if (!$message->header->get('id')) {
+ if (!$this->parseHeader($message)) {
+ return;
+ }
+ }
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ if (!$this->parseQuestion($message)) {
+ return;
+ }
+ }
+
+ // parse all answer records
+ for ($i = $message->header->get('anCount'); $i > 0; --$i) {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return;
+ } else {
+ $message->answers[] = $record;
+ }
+ }
+
+ // parse all authority records
+ for ($i = $message->header->get('nsCount'); $i > 0; --$i) {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return;
+ } else {
+ $message->authority[] = $record;
+ }
+ }
+
+ // parse all additional records
+ for ($i = $message->header->get('arCount'); $i > 0; --$i) {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return;
+ } else {
+ $message->additional[] = $record;
+ }
+ }
+
+ return $message;
+ }
+
+ public function parseHeader(Message $message)
+ {
+ if (!isset($message->data[12 - 1])) {
+ return;
+ }
+
+ $header = substr($message->data, 0, 12);
+ $message->consumed += 12;
+
+ list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header));
+
+ $rcode = $fields & bindec('1111');
+ $z = ($fields >> 4) & bindec('111');
+ $ra = ($fields >> 7) & 1;
+ $rd = ($fields >> 8) & 1;
+ $tc = ($fields >> 9) & 1;
+ $aa = ($fields >> 10) & 1;
+ $opcode = ($fields >> 11) & bindec('1111');
+ $qr = ($fields >> 15) & 1;
+
+ $vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount',
+ 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode');
+
+ foreach ($vars as $name => $value) {
+ $message->header->set($name, $value);
+ }
+
+ return $message;
+ }
+
+ public function parseQuestion(Message $message)
+ {
+ $consumed = $message->consumed;
+
+ list($labels, $consumed) = $this->readLabels($message->data, $consumed);
+
+ if ($labels === null || !isset($message->data[$consumed + 4 - 1])) {
+ return;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ $message->consumed = $consumed;
+
+ $message->questions[] = array(
+ 'name' => implode('.', $labels),
+ 'type' => $type,
+ 'class' => $class,
+ );
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ return $this->parseQuestion($message);
+ }
+
+ return $message;
+ }
+
+ /**
+ * recursively parse all answers from the message data into message answer records
+ *
+ * @param Message $message
+ * @return ?Message returns the updated message on success or null if the data is invalid/incomplete
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function parseAnswer(Message $message)
+ {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return null;
+ }
+
+ $message->answers[] = $record;
+
+ if ($message->header->get('anCount') != count($message->answers)) {
+ return $this->parseAnswer($message);
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param Message $message
+ * @return ?Record returns parsed Record on success or null if data is invalid/incomplete
+ */
+ private function parseRecord(Message $message)
+ {
+ $consumed = $message->consumed;
+
+ list($name, $consumed) = $this->readDomain($message->data, $consumed);
+
+ if ($name === null || !isset($message->data[$consumed + 10 - 1])) {
+ return null;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ // TTL is a UINT32 that must not have most significant bit set for BC reasons
+ if ($ttl < 0 || $ttl >= 1 << 31) {
+ $ttl = 0;
+ }
+
+ list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
+ $consumed += 2;
+
+ if (!isset($message->data[$consumed + $rdLength - 1])) {
+ return null;
+ }
+
+ $rdata = null;
+ $expected = $consumed + $rdLength;
+
+ if (Message::TYPE_A === $type) {
+ if ($rdLength === 4) {
+ $rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_AAAA === $type) {
+ if ($rdLength === 16) {
+ $rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
+ list($rdata, $consumed) = $this->readDomain($message->data, $consumed);
+ } elseif (Message::TYPE_TXT === $type) {
+ $rdata = array();
+ while ($consumed < $expected) {
+ $len = ord($message->data[$consumed]);
+ $rdata[] = (string)substr($message->data, $consumed + 1, $len);
+ $consumed += $len + 1;
+ }
+ } elseif (Message::TYPE_MX === $type) {
+ if ($rdLength > 2) {
+ list($priority) = array_values(unpack('n', substr($message->data, $consumed, 2)));
+ list($target, $consumed) = $this->readDomain($message->data, $consumed + 2);
+
+ $rdata = array(
+ 'priority' => $priority,
+ 'target' => $target
+ );
+ }
+ } elseif (Message::TYPE_SRV === $type) {
+ if ($rdLength > 6) {
+ list($priority, $weight, $port) = array_values(unpack('n*', substr($message->data, $consumed, 6)));
+ list($target, $consumed) = $this->readDomain($message->data, $consumed + 6);
+
+ $rdata = array(
+ 'priority' => $priority,
+ 'weight' => $weight,
+ 'port' => $port,
+ 'target' => $target
+ );
+ }
+ } elseif (Message::TYPE_SOA === $type) {
+ list($mname, $consumed) = $this->readDomain($message->data, $consumed);
+ list($rname, $consumed) = $this->readDomain($message->data, $consumed);
+
+ if ($mname !== null && $rname !== null && isset($message->data[$consumed + 20 - 1])) {
+ list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($message->data, $consumed, 20)));
+ $consumed += 20;
+
+ $rdata = array(
+ 'mname' => $mname,
+ 'rname' => $rname,
+ 'serial' => $serial,
+ 'refresh' => $refresh,
+ 'retry' => $retry,
+ 'expire' => $expire,
+ 'minimum' => $minimum
+ );
+ }
+ } else {
+ // unknown types simply parse rdata as an opaque binary string
+ $rdata = substr($message->data, $consumed, $rdLength);
+ $consumed += $rdLength;
+ }
+
+ // ensure parsing record data consumes expact number of bytes indicated in record length
+ if ($consumed !== $expected || $rdata === null) {
+ return null;
+ }
+
+ $message->consumed = $consumed;
+
+ return new Record($name, $type, $class, $ttl, $rdata);
+ }
+
+ private function readDomain($data, $consumed)
+ {
+ list ($labels, $consumed) = $this->readLabels($data, $consumed);
+
+ if ($labels === null) {
+ return array(null, null);
+ }
+
+ return array(implode('.', $labels), $consumed);
+ }
+
+ private function readLabels($data, $consumed)
+ {
+ $labels = array();
+
+ while (true) {
+ if (!isset($data[$consumed])) {
+ return array(null, null);
+ }
+
+ $length = \ord($data[$consumed]);
+
+ // end of labels reached
+ if ($length === 0) {
+ $consumed += 1;
+ break;
+ }
+
+ // first two bits set? this is a compressed label (14 bit pointer offset)
+ if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1])) {
+ $offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
+ if ($offset >= $consumed) {
+ return array(null, null);
+ }
+
+ $consumed += 2;
+ list($newLabels) = $this->readLabels($data, $offset);
+
+ if ($newLabels === null) {
+ return array(null, null);
+ }
+
+ $labels = array_merge($labels, $newLabels);
+ break;
+ }
+
+ // length MUST be 0-63 (6 bits only) and data has to be large enough
+ if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
+ return array(null, null);
+ }
+
+ $labels[] = substr($data, $consumed + 1, $length);
+ $consumed += $length + 1;
+ }
+
+ return array($labels, $consumed);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function isEndOfLabels($data, $consumed)
+ {
+ $length = ord(substr($data, $consumed, 1));
+ return 0 === $length;
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function getCompressedLabel($data, $consumed)
+ {
+ list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed);
+ list($labels) = $this->readLabels($data, $nameOffset);
+
+ return array($labels, $consumed);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function isCompressedLabel($data, $consumed)
+ {
+ $mask = 0xc000; // 1100000000000000
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return (bool) ($peek & $mask);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function getCompressedLabelOffset($data, $consumed)
+ {
+ $mask = 0x3fff; // 0011111111111111
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return array($peek & $mask, $consumed + 2);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function signedLongToUnsignedLong($i)
+ {
+ return $i & 0x80000000 ? $i - 0xffffffff : $i;
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/CachedExecutor.php b/instafeed/vendor/react/dns/src/Query/CachedExecutor.php
new file mode 100755
index 0000000..8b70894
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/CachedExecutor.php
@@ -0,0 +1,59 @@
+executor = $executor;
+ $this->cache = $cache;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $executor = $this->executor;
+ $cache = $this->cache;
+
+ return $this->cache
+ ->lookup($query)
+ ->then(
+ function ($cachedRecords) use ($query) {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ },
+ function () use ($executor, $cache, $nameserver, $query) {
+ return $executor
+ ->query($nameserver, $query)
+ ->then(function ($response) use ($cache, $query) {
+ $cache->storeResponseMessage($query->currentTime, $response);
+ return $response;
+ });
+ }
+ );
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function buildResponse(Query $query, array $cachedRecords)
+ {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/CachingExecutor.php b/instafeed/vendor/react/dns/src/Query/CachingExecutor.php
new file mode 100755
index 0000000..e6ec3ac
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/CachingExecutor.php
@@ -0,0 +1,88 @@
+executor = $executor;
+ $this->cache = $cache;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $id = $query->name . ':' . $query->type . ':' . $query->class;
+ $cache = $this->cache;
+ $that = $this;
+ $executor = $this->executor;
+
+ $pending = $cache->get($id);
+ return new Promise(function ($resolve, $reject) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
+ $pending->then(
+ function ($message) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
+ // return cached response message on cache hit
+ if ($message !== null) {
+ return $message;
+ }
+
+ // perform DNS lookup if not already cached
+ return $pending = $executor->query($nameserver, $query)->then(
+ function (Message $message) use ($cache, $id, $that) {
+ // DNS response message received => store in cache when not truncated and return
+ if (!$message->header->isTruncated()) {
+ $cache->set($id, $message, $that->ttl($message));
+ }
+
+ return $message;
+ }
+ );
+ }
+ )->then($resolve, function ($e) use ($reject, &$pending) {
+ $reject($e);
+ $pending = null;
+ });
+ }, function ($_, $reject) use (&$pending, $query) {
+ $reject(new \RuntimeException('DNS query for ' . $query->name . ' has been cancelled'));
+ $pending->cancel();
+ $pending = null;
+ });
+ }
+
+ /**
+ * @param Message $message
+ * @return int
+ * @internal
+ */
+ public function ttl(Message $message)
+ {
+ // select TTL from answers (should all be the same), use smallest value if available
+ // @link https://tools.ietf.org/html/rfc2181#section-5.2
+ $ttl = null;
+ foreach ($message->answers as $answer) {
+ if ($ttl === null || $answer->ttl < $ttl) {
+ $ttl = $answer->ttl;
+ }
+ }
+
+ if ($ttl === null) {
+ $ttl = self::TTL;
+ }
+
+ return $ttl;
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/CancellationException.php b/instafeed/vendor/react/dns/src/Query/CancellationException.php
new file mode 100755
index 0000000..ac30f4c
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/CancellationException.php
@@ -0,0 +1,7 @@
+executor = $base;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $key = $this->serializeQueryToIdentity($query);
+ if (isset($this->pending[$key])) {
+ // same query is already pending, so use shared reference to pending query
+ $promise = $this->pending[$key];
+ ++$this->counts[$key];
+ } else {
+ // no such query pending, so start new query and keep reference until it's fulfilled or rejected
+ $promise = $this->executor->query($nameserver, $query);
+ $this->pending[$key] = $promise;
+ $this->counts[$key] = 1;
+
+ $pending =& $this->pending;
+ $counts =& $this->counts;
+ $promise->then(function () use ($key, &$pending, &$counts) {
+ unset($pending[$key], $counts[$key]);
+ }, function () use ($key, &$pending, &$counts) {
+ unset($pending[$key], $counts[$key]);
+ });
+ }
+
+ // Return a child promise awaiting the pending query.
+ // Cancelling this child promise should only cancel the pending query
+ // when no other child promise is awaiting the same query.
+ $pending =& $this->pending;
+ $counts =& $this->counts;
+ return new Promise(function ($resolve, $reject) use ($promise) {
+ $promise->then($resolve, $reject);
+ }, function () use (&$promise, $key, $query, &$pending, &$counts) {
+ if (--$counts[$key] < 1) {
+ unset($pending[$key], $counts[$key]);
+ $promise->cancel();
+ $promise = null;
+ }
+ throw new \RuntimeException('DNS query for ' . $query->name . ' has been cancelled');
+ });
+ }
+
+ private function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/Executor.php b/instafeed/vendor/react/dns/src/Query/Executor.php
new file mode 100755
index 0000000..40f6bb4
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/Executor.php
@@ -0,0 +1,160 @@
+loop = $loop;
+ $this->parser = $parser;
+ $this->dumper = $dumper;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ $transport = strlen($queryData) > 512 ? 'tcp' : 'udp';
+
+ return $this->doQuery($nameserver, $transport, $queryData, $query->name);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function prepareRequest(Query $query)
+ {
+ return Message::createRequestForQuery($query);
+ }
+
+ public function doQuery($nameserver, $transport, $queryData, $name)
+ {
+ // we only support UDP right now
+ if ($transport !== 'udp') {
+ return Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $name . ' failed: Requested transport "' . $transport . '" not available, only UDP is supported in this version'
+ ));
+ }
+
+ $that = $this;
+ $parser = $this->parser;
+ $loop = $this->loop;
+
+ // UDP connections are instant, so try this without a timer
+ try {
+ $conn = $this->createConnection($nameserver, $transport);
+ } catch (\Exception $e) {
+ return Promise\reject(new \RuntimeException('DNS query for ' . $name . ' failed: ' . $e->getMessage(), 0, $e));
+ }
+
+ $deferred = new Deferred(function ($resolve, $reject) use (&$timer, $loop, &$conn, $name) {
+ $reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name)));
+
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+ $conn->close();
+ });
+
+ $timer = null;
+ if ($this->timeout !== null) {
+ $timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) {
+ $conn->close();
+ $deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name)));
+ });
+ }
+
+ $conn->on('data', function ($data) use ($conn, $parser, $deferred, $timer, $loop, $name) {
+ $conn->end();
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+
+ try {
+ $response = $parser->parseMessage($data);
+ } catch (\Exception $e) {
+ $deferred->reject($e);
+ return;
+ }
+
+ if ($response->header->isTruncated()) {
+ $deferred->reject(new \RuntimeException('DNS query for ' . $name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+ $conn->write($queryData);
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+
+ /**
+ * @param string $nameserver
+ * @param string $transport
+ * @return \React\Stream\DuplexStreamInterface
+ */
+ protected function createConnection($nameserver, $transport)
+ {
+ $fd = @stream_socket_client("$transport://$nameserver", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
+ if ($fd === false) {
+ throw new \RuntimeException('Unable to connect to DNS server: ' . $errstr, $errno);
+ }
+
+ // Instantiate stream instance around this stream resource.
+ // This ought to be replaced with a datagram socket in the future.
+ // Temporary work around for Windows 10: buffer whole UDP response
+ // @coverageIgnoreStart
+ if (!class_exists('React\Stream\Stream')) {
+ // prefer DuplexResourceStream as of react/stream v0.7.0
+ $conn = new DuplexResourceStream($fd, $this->loop, -1);
+ } else {
+ // use legacy Stream class for react/stream < v0.7.0
+ $conn = new Stream($fd, $this->loop);
+ $conn->bufferSize = null;
+ }
+ // @coverageIgnoreEnd
+
+ return $conn;
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/ExecutorInterface.php b/instafeed/vendor/react/dns/src/Query/ExecutorInterface.php
new file mode 100755
index 0000000..2f7a635
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/ExecutorInterface.php
@@ -0,0 +1,8 @@
+hosts = $hosts;
+ $this->fallback = $fallback;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
+ // forward lookup for type A or AAAA
+ $records = array();
+ $expectsColon = $query->type === Message::TYPE_AAAA;
+ foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
+ // ensure this is an IPv4/IPV6 address according to query type
+ if ((strpos($ip, ':') !== false) === $expectsColon) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
+ }
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ } elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
+ // reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
+ $ip = $this->getIpFromHost($query->name);
+
+ if ($ip !== null) {
+ $records = array();
+ foreach ($this->hosts->getHostsForIp($ip) as $host) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $host);
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ }
+ }
+
+ return $this->fallback->query($nameserver, $query);
+ }
+
+ private function getIpFromHost($host)
+ {
+ if (substr($host, -13) === '.in-addr.arpa') {
+ // IPv4: read as IP and reverse bytes
+ $ip = @inet_pton(substr($host, 0, -13));
+ if ($ip === false || isset($ip[4])) {
+ return null;
+ }
+
+ return inet_ntop(strrev($ip));
+ } elseif (substr($host, -9) === '.ip6.arpa') {
+ // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
+ $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
+ if ($ip === false) {
+ return null;
+ }
+
+ return $ip;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/Query.php b/instafeed/vendor/react/dns/src/Query/Query.php
new file mode 100755
index 0000000..058a78d
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/Query.php
@@ -0,0 +1,33 @@
+name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->currentTime = $currentTime;
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/RecordBag.php b/instafeed/vendor/react/dns/src/Query/RecordBag.php
new file mode 100755
index 0000000..4dc815a
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/RecordBag.php
@@ -0,0 +1,30 @@
+records[] = array($currentTime + $record->ttl, $record);
+ }
+
+ public function all()
+ {
+ return array_values(array_map(
+ function ($value) {
+ list($expiresAt, $record) = $value;
+ return $record;
+ },
+ $this->records
+ ));
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/RecordCache.php b/instafeed/vendor/react/dns/src/Query/RecordCache.php
new file mode 100755
index 0000000..c087e5f
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/RecordCache.php
@@ -0,0 +1,122 @@
+cache = $cache;
+ }
+
+ /**
+ * Looks up the cache if there's a cached answer for the given query
+ *
+ * @param Query $query
+ * @return PromiseInterface Promise resolves with array of Record objects on sucess
+ * or rejects with mixed values when query is not cached already.
+ */
+ public function lookup(Query $query)
+ {
+ $id = $this->serializeQueryToIdentity($query);
+
+ $expiredAt = $this->expiredAt;
+
+ return $this->cache
+ ->get($id)
+ ->then(function ($value) use ($query, $expiredAt) {
+ // reject on cache miss
+ if ($value === null) {
+ return Promise\reject();
+ }
+
+ /* @var $recordBag RecordBag */
+ $recordBag = unserialize($value);
+
+ // reject this cache hit if the query was started before the time we expired the cache?
+ // todo: this is a legacy left over, this value is never actually set, so this never applies.
+ // todo: this should probably validate the cache time instead.
+ if (null !== $expiredAt && $expiredAt <= $query->currentTime) {
+ return Promise\reject();
+ }
+
+ return $recordBag->all();
+ });
+ }
+
+ /**
+ * Stores all records from this response message in the cache
+ *
+ * @param int $currentTime
+ * @param Message $message
+ * @uses self::storeRecord()
+ */
+ public function storeResponseMessage($currentTime, Message $message)
+ {
+ foreach ($message->answers as $record) {
+ $this->storeRecord($currentTime, $record);
+ }
+ }
+
+ /**
+ * Stores a single record from a response message in the cache
+ *
+ * @param int $currentTime
+ * @param Record $record
+ */
+ public function storeRecord($currentTime, Record $record)
+ {
+ $id = $this->serializeRecordToIdentity($record);
+
+ $cache = $this->cache;
+
+ $this->cache
+ ->get($id)
+ ->then(
+ function ($value) {
+ // return empty bag on cache miss
+ if ($value === null) {
+ return new RecordBag();
+ }
+
+ // reuse existing bag on cache hit to append new record to it
+ return unserialize($value);
+ }
+ )
+ ->then(function (RecordBag $recordBag) use ($id, $currentTime, $record, $cache) {
+ // add a record to the existing (possibly empty) record bag and save to cache
+ $recordBag->set($currentTime, $record);
+ $cache->set($id, serialize($recordBag));
+ });
+ }
+
+ public function expire($currentTime)
+ {
+ $this->expiredAt = $currentTime;
+ }
+
+ public function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+
+ public function serializeRecordToIdentity(Record $record)
+ {
+ return sprintf('%s:%s:%s', $record->name, $record->type, $record->class);
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/RetryExecutor.php b/instafeed/vendor/react/dns/src/Query/RetryExecutor.php
new file mode 100755
index 0000000..46e2ef9
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/RetryExecutor.php
@@ -0,0 +1,79 @@
+executor = $executor;
+ $this->retries = $retries;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return $this->tryQuery($nameserver, $query, $this->retries);
+ }
+
+ public function tryQuery($nameserver, Query $query, $retries)
+ {
+ $deferred = new Deferred(function () use (&$promise) {
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ });
+
+ $success = function ($value) use ($deferred, &$errorback) {
+ $errorback = null;
+ $deferred->resolve($value);
+ };
+
+ $executor = $this->executor;
+ $errorback = function ($e) use ($deferred, &$promise, $nameserver, $query, $success, &$errorback, &$retries, $executor) {
+ if (!$e instanceof TimeoutException) {
+ $errorback = null;
+ $deferred->reject($e);
+ } elseif ($retries <= 0) {
+ $errorback = null;
+ $deferred->reject($e = new \RuntimeException(
+ 'DNS query for ' . $query->name . ' failed: too many retries',
+ 0,
+ $e
+ ));
+
+ // avoid garbage references by replacing all closures in call stack.
+ // what a lovely piece of code!
+ $r = new \ReflectionProperty('Exception', 'trace');
+ $r->setAccessible(true);
+ $trace = $r->getValue($e);
+ foreach ($trace as &$one) {
+ foreach ($one['args'] as &$arg) {
+ if ($arg instanceof \Closure) {
+ $arg = 'Object(' . \get_class($arg) . ')';
+ }
+ }
+ }
+ $r->setValue($e, $trace);
+ } else {
+ --$retries;
+ $promise = $executor->query($nameserver, $query)->then(
+ $success,
+ $errorback
+ );
+ }
+ };
+
+ $promise = $this->executor->query($nameserver, $query)->then(
+ $success,
+ $errorback
+ );
+
+ return $deferred->promise();
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/TimeoutException.php b/instafeed/vendor/react/dns/src/Query/TimeoutException.php
new file mode 100755
index 0000000..90bf806
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/TimeoutException.php
@@ -0,0 +1,7 @@
+executor = $executor;
+ $this->loop = $loop;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return Timer\timeout($this->executor->query($nameserver, $query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) {
+ if ($e instanceof Timer\TimeoutException) {
+ $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->name), 0, $e);
+ }
+ throw $e;
+ });
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Query/UdpTransportExecutor.php b/instafeed/vendor/react/dns/src/Query/UdpTransportExecutor.php
new file mode 100755
index 0000000..99a3e8a
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Query/UdpTransportExecutor.php
@@ -0,0 +1,181 @@
+query(
+ * '8.8.8.8:53',
+ * new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
+ * )->then(function (Message $message) {
+ * foreach ($message->answers as $answer) {
+ * echo 'IPv6: ' . $answer->data . PHP_EOL;
+ * }
+ * }, 'printf');
+ *
+ * $loop->run();
+ * ```
+ *
+ * See also the [fourth example](examples).
+ *
+ * Note that this executor does not implement a timeout, so you will very likely
+ * want to use this in combination with a `TimeoutExecutor` like this:
+ *
+ * ```php
+ * $executor = new TimeoutExecutor(
+ * new UdpTransportExecutor($loop),
+ * 3.0,
+ * $loop
+ * );
+ * ```
+ *
+ * Also note that this executor uses an unreliable UDP transport and that it
+ * does not implement any retry logic, so you will likely want to use this in
+ * combination with a `RetryExecutor` like this:
+ *
+ * ```php
+ * $executor = new RetryExecutor(
+ * new TimeoutExecutor(
+ * new UdpTransportExecutor($loop),
+ * 3.0,
+ * $loop
+ * )
+ * );
+ * ```
+ *
+ * Note that this executor is entirely async and as such allows you to execute
+ * any number of queries concurrently. You should probably limit the number of
+ * concurrent queries in your application or you're very likely going to face
+ * rate limitations and bans on the resolver end. For many common applications,
+ * you may want to avoid sending the same query multiple times when the first
+ * one is still pending, so you will likely want to use this in combination with
+ * a `CoopExecutor` like this:
+ *
+ * ```php
+ * $executor = new CoopExecutor(
+ * new RetryExecutor(
+ * new TimeoutExecutor(
+ * new UdpTransportExecutor($loop),
+ * 3.0,
+ * $loop
+ * )
+ * )
+ * );
+ * ```
+ *
+ * > Internally, this class uses PHP's UDP sockets and does not take advantage
+ * of [react/datagram](https://github.com/reactphp/datagram) purely for
+ * organizational reasons to avoid a cyclic dependency between the two
+ * packages. Higher-level components should take advantage of the Datagram
+ * component instead of reimplementing this socket logic from scratch.
+ */
+class UdpTransportExecutor implements ExecutorInterface
+{
+ private $loop;
+ private $parser;
+ private $dumper;
+
+ /**
+ * @param LoopInterface $loop
+ * @param null|Parser $parser optional/advanced: DNS protocol parser to use
+ * @param null|BinaryDumper $dumper optional/advanced: DNS protocol dumper to use
+ */
+ public function __construct(LoopInterface $loop, Parser $parser = null, BinaryDumper $dumper = null)
+ {
+ if ($parser === null) {
+ $parser = new Parser();
+ }
+ if ($dumper === null) {
+ $dumper = new BinaryDumper();
+ }
+
+ $this->loop = $loop;
+ $this->parser = $parser;
+ $this->dumper = $dumper;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ if (isset($queryData[512])) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->name . ' failed: Query too large for UDP transport'
+ ));
+ }
+
+ // UDP connections are instant, so try connection without a loop or timeout
+ $socket = @\stream_socket_client("udp://$nameserver", $errno, $errstr, 0);
+ if ($socket === false) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->name . ' failed: Unable to connect to DNS server (' . $errstr . ')',
+ $errno
+ ));
+ }
+
+ // set socket to non-blocking and immediately try to send (fill write buffer)
+ \stream_set_blocking($socket, false);
+ \fwrite($socket, $queryData);
+
+ $loop = $this->loop;
+ $deferred = new Deferred(function () use ($loop, $socket, $query) {
+ // cancellation should remove socket from loop and close socket
+ $loop->removeReadStream($socket);
+ \fclose($socket);
+
+ throw new CancellationException('DNS query for ' . $query->name . ' has been cancelled');
+ });
+
+ $parser = $this->parser;
+ $loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request) {
+ // try to read a single data packet from the DNS server
+ // ignoring any errors, this is uses UDP packets and not a stream of data
+ $data = @\fread($socket, 512);
+
+ try {
+ $response = $parser->parseMessage($data);
+ } catch (\Exception $e) {
+ // ignore and await next if we received an invalid message from remote server
+ // this may as well be a fake response from an attacker (possible DOS)
+ return;
+ }
+
+ // ignore and await next if we received an unexpected response ID
+ // this may as well be a fake response from an attacker (possible cache poisoning)
+ if ($response->getId() !== $request->getId()) {
+ return;
+ }
+
+ // we only react to the first valid message, so remove socket from loop and close
+ $loop->removeReadStream($socket);
+ \fclose($socket);
+
+ if ($response->header->isTruncated()) {
+ $deferred->reject(new \RuntimeException('DNS query for ' . $query->name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+
+ return $deferred->promise();
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/RecordNotFoundException.php b/instafeed/vendor/react/dns/src/RecordNotFoundException.php
new file mode 100755
index 0000000..0028413
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/RecordNotFoundException.php
@@ -0,0 +1,7 @@
+addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($loop));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null)
+ {
+ // default to keeping maximum of 256 responses in cache unless explicitly given
+ if (!($cache instanceof CacheInterface)) {
+ $cache = new ArrayCache(256);
+ }
+
+ $nameserver = $this->addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($loop, $cache));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ /**
+ * Tries to load the hosts file and decorates the given executor on success
+ *
+ * @param ExecutorInterface $executor
+ * @return ExecutorInterface
+ * @codeCoverageIgnore
+ */
+ private function decorateHostsFileExecutor(ExecutorInterface $executor)
+ {
+ try {
+ $executor = new HostsFileExecutor(
+ HostsFile::loadFromPathBlocking(),
+ $executor
+ );
+ } catch (\RuntimeException $e) {
+ // ignore this file if it can not be loaded
+ }
+
+ // Windows does not store localhost in hosts file by default but handles this internally
+ // To compensate for this, we explicitly use hard-coded defaults for localhost
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $executor = new HostsFileExecutor(
+ new HostsFile("127.0.0.1 localhost\n::1 localhost"),
+ $executor
+ );
+ }
+
+ return $executor;
+ }
+
+ protected function createExecutor(LoopInterface $loop)
+ {
+ return new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 5.0,
+ $loop
+ );
+ }
+
+ protected function createRetryExecutor(LoopInterface $loop)
+ {
+ return new CoopExecutor(new RetryExecutor($this->createExecutor($loop)));
+ }
+
+ protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache)
+ {
+ return new CachingExecutor($this->createRetryExecutor($loop), $cache);
+ }
+
+ protected function addPortToServerIfMissing($nameserver)
+ {
+ if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) {
+ // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
+ $nameserver = '[' . $nameserver . ']';
+ }
+ // assume a dummy scheme when checking for the port, otherwise parse_url() fails
+ if (parse_url('dummy://' . $nameserver, PHP_URL_PORT) === null) {
+ $nameserver .= ':53';
+ }
+
+ return $nameserver;
+ }
+}
diff --git a/instafeed/vendor/react/dns/src/Resolver/Resolver.php b/instafeed/vendor/react/dns/src/Resolver/Resolver.php
new file mode 100755
index 0000000..8690972
--- /dev/null
+++ b/instafeed/vendor/react/dns/src/Resolver/Resolver.php
@@ -0,0 +1,250 @@
+nameserver = $nameserver;
+ $this->executor = $executor;
+ }
+
+ /**
+ * Resolves the given $domain name to a single IPv4 address (type `A` query).
+ *
+ * ```php
+ * $resolver->resolve('reactphp.org')->then(function ($ip) {
+ * echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
+ * });
+ * ```
+ *
+ * This is one of the main methods in this package. It sends a DNS query
+ * for the given $domain name to your DNS server and returns a single IP
+ * address on success.
+ *
+ * If the DNS server sends a DNS response message that contains more than
+ * one IP address for this query, it will randomly pick one of the IP
+ * addresses from the response. If you want the full list of IP addresses
+ * or want to send a different type of query, you should use the
+ * [`resolveAll()`](#resolveall) method instead.
+ *
+ * If the DNS server sends a DNS response message that indicates an error
+ * code, this method will reject with a `RecordNotFoundException`. Its
+ * message and code can be used to check for the response code.
+ *
+ * If the DNS communication fails and the server does not respond with a
+ * valid response message, this message will reject with an `Exception`.
+ *
+ * Pending DNS queries can be cancelled by cancelling its pending promise like so:
+ *
+ * ```php
+ * $promise = $resolver->resolve('reactphp.org');
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $domain
+ * @return PromiseInterface Returns a promise which resolves with a single IP address on success or
+ * rejects with an Exception on error.
+ */
+ public function resolve($domain)
+ {
+ return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
+ return $ips[array_rand($ips)];
+ });
+ }
+
+ /**
+ * Resolves all record values for the given $domain name and query $type.
+ *
+ * ```php
+ * $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
+ * echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ * });
+ *
+ * $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ * echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ * });
+ * ```
+ *
+ * This is one of the main methods in this package. It sends a DNS query
+ * for the given $domain name to your DNS server and returns a list with all
+ * record values on success.
+ *
+ * If the DNS server sends a DNS response message that contains one or more
+ * records for this query, it will return a list with all record values
+ * from the response. You can use the `Message::TYPE_*` constants to control
+ * which type of query will be sent. Note that this method always returns a
+ * list of record values, but each record value type depends on the query
+ * type. For example, it returns the IPv4 addresses for type `A` queries,
+ * the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
+ * `CNAME` and `PTR` queries and structured data for other queries. See also
+ * the `Record` documentation for more details.
+ *
+ * If the DNS server sends a DNS response message that indicates an error
+ * code, this method will reject with a `RecordNotFoundException`. Its
+ * message and code can be used to check for the response code.
+ *
+ * If the DNS communication fails and the server does not respond with a
+ * valid response message, this message will reject with an `Exception`.
+ *
+ * Pending DNS queries can be cancelled by cancelling its pending promise like so:
+ *
+ * ```php
+ * $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $domain
+ * @return PromiseInterface Returns a promise which resolves with all record values on success or
+ * rejects with an Exception on error.
+ */
+ public function resolveAll($domain, $type)
+ {
+ $query = new Query($domain, $type, Message::CLASS_IN);
+ $that = $this;
+
+ return $this->executor->query(
+ $this->nameserver,
+ $query
+ )->then(function (Message $response) use ($query, $that) {
+ return $that->extractValues($query, $response);
+ });
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function extractAddress(Query $query, Message $response)
+ {
+ $addresses = $this->extractValues($query, $response);
+
+ return $addresses[array_rand($addresses)];
+ }
+
+ /**
+ * [Internal] extract all resource record values from response for this query
+ *
+ * @param Query $query
+ * @param Message $response
+ * @return array
+ * @throws RecordNotFoundException when response indicates an error or contains no data
+ * @internal
+ */
+ public function extractValues(Query $query, Message $response)
+ {
+ // reject if response code indicates this is an error response message
+ $code = $response->getResponseCode();
+ if ($code !== Message::RCODE_OK) {
+ switch ($code) {
+ case Message::RCODE_FORMAT_ERROR:
+ $message = 'Format Error';
+ break;
+ case Message::RCODE_SERVER_FAILURE:
+ $message = 'Server Failure';
+ break;
+ case Message::RCODE_NAME_ERROR:
+ $message = 'Non-Existent Domain / NXDOMAIN';
+ break;
+ case Message::RCODE_NOT_IMPLEMENTED:
+ $message = 'Not Implemented';
+ break;
+ case Message::RCODE_REFUSED:
+ $message = 'Refused';
+ break;
+ default:
+ $message = 'Unknown error response code ' . $code;
+ }
+ throw new RecordNotFoundException(
+ 'DNS query for ' . $query->name . ' returned an error response (' . $message . ')',
+ $code
+ );
+ }
+
+ $answers = $response->answers;
+ $addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
+
+ // reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
+ if (0 === count($addresses)) {
+ throw new RecordNotFoundException(
+ 'DNS query for ' . $query->name . ' did not return a valid answer (NOERROR / NODATA)'
+ );
+ }
+
+ return array_values($addresses);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function resolveAliases(array $answers, $name)
+ {
+ return $this->valuesByNameAndType($answers, $name, Message::TYPE_A);
+ }
+
+ /**
+ * @param \React\Dns\Model\Record[] $answers
+ * @param string $name
+ * @param int $type
+ * @return array
+ */
+ private function valuesByNameAndType(array $answers, $name, $type)
+ {
+ // return all record values for this name and type (if any)
+ $named = $this->filterByName($answers, $name);
+ $records = $this->filterByType($named, $type);
+ if ($records) {
+ return $this->mapRecordData($records);
+ }
+
+ // no matching records found? check if there are any matching CNAMEs instead
+ $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
+ if ($cnameRecords) {
+ $cnames = $this->mapRecordData($cnameRecords);
+ foreach ($cnames as $cname) {
+ $records = array_merge(
+ $records,
+ $this->valuesByNameAndType($answers, $cname, $type)
+ );
+ }
+ }
+
+ return $records;
+ }
+
+ private function filterByName(array $answers, $name)
+ {
+ return $this->filterByField($answers, 'name', $name);
+ }
+
+ private function filterByType(array $answers, $type)
+ {
+ return $this->filterByField($answers, 'type', $type);
+ }
+
+ private function filterByField(array $answers, $field, $value)
+ {
+ $value = strtolower($value);
+ return array_filter($answers, function ($answer) use ($field, $value) {
+ return $value === strtolower($answer->$field);
+ });
+ }
+
+ private function mapRecordData(array $records)
+ {
+ return array_map(function ($record) {
+ return $record->data;
+ }, $records);
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/CallableStub.php b/instafeed/vendor/react/dns/tests/CallableStub.php
new file mode 100755
index 0000000..a34a263
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/CallableStub.php
@@ -0,0 +1,10 @@
+assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsDefaultPath()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Not supported on Windows');
+ }
+
+ $config = Config::loadResolvConfBlocking();
+
+ $this->assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsFromExplicitPath()
+ {
+ $config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf');
+
+ $this->assertEquals(array('8.8.8.8'), $config->nameservers);
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testLoadThrowsWhenPathIsInvalid()
+ {
+ Config::loadResolvConfBlocking(__DIR__ . '/invalid.conf');
+ }
+
+ public function testParsesSingleEntryFile()
+ {
+ $contents = 'nameserver 8.8.8.8';
+ $expected = array('8.8.8.8');
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesNameserverEntriesFromAverageFileCorrectly()
+ {
+ $contents = '#
+# Mac OS X Notice
+#
+# This file is not used by the host name and address resolution
+# or the DNS query routing mechanisms used by most processes on
+# this Mac OS X system.
+#
+# This file is automatically generated.
+#
+domain v.cablecom.net
+nameserver 127.0.0.1
+nameserver ::1
+';
+ $expected = array('127.0.0.1', '::1');
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesEmptyFileWithoutNameserverEntries()
+ {
+ $contents = '';
+ $expected = array();
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,');
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries()
+ {
+ $contents = '
+# nameserver 1.2.3.4
+; nameserver 2.3.4.5
+
+nameserver 3.4.5.6 # nope
+nameserver 4.5.6.7 5.6.7.8
+ nameserver 6.7.8.9
+NameServer 7.8.9.10
+';
+ $expected = array();
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsFromWmicOnWindows()
+ {
+ if (DIRECTORY_SEPARATOR !== '\\') {
+ $this->markTestSkipped('Only on Windows');
+ }
+
+ $config = Config::loadWmicBlocking();
+
+ $this->assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsSingleEntryFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1}
+ACE,
+';
+ $expected = array('192.168.2.1');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsEmptyListFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+';
+ $expected = array();
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsSingleEntryForMultipleNicsFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1}
+ACE,
+ACE,{192.168.2.2}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1;192.168.2.2}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{"192.168.2.1","192.168.2.2"}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ private function echoCommand($output)
+ {
+ return 'echo ' . escapeshellarg($output);
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Config/FilesystemFactoryTest.php b/instafeed/vendor/react/dns/tests/Config/FilesystemFactoryTest.php
new file mode 100755
index 0000000..bb9eac7
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Config/FilesystemFactoryTest.php
@@ -0,0 +1,70 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $factory = new FilesystemFactory($loop);
+ $factory->parseEtcResolvConf($contents)->then(function ($config) use (&$capturedConfig) {
+ $capturedConfig = $config;
+ });
+
+ $this->assertNotNull($capturedConfig);
+ $this->assertSame($expected, $capturedConfig->nameservers);
+ }
+
+ /** @test */
+ public function createShouldLoadStuffFromFilesystem()
+ {
+ $this->markTestIncomplete('Filesystem API is incomplete');
+
+ $expected = array('8.8.8.8');
+
+ $triggerListener = null;
+ $capturedConfig = null;
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop
+ ->expects($this->once())
+ ->method('addReadStream')
+ ->will($this->returnCallback(function ($stream, $listener) use (&$triggerListener) {
+ $triggerListener = function () use ($stream, $listener) {
+ call_user_func($listener, $stream);
+ };
+ }));
+
+ $factory = new FilesystemFactory($loop);
+ $factory->create(__DIR__.'/../Fixtures/etc/resolv.conf')->then(function ($config) use (&$capturedConfig) {
+ $capturedConfig = $config;
+ });
+
+ $triggerListener();
+
+ $this->assertNotNull($capturedConfig);
+ $this->assertSame($expected, $capturedConfig->nameservers);
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Config/HostsFileTest.php b/instafeed/vendor/react/dns/tests/Config/HostsFileTest.php
new file mode 100755
index 0000000..ff74ad2
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Config/HostsFileTest.php
@@ -0,0 +1,170 @@
+assertInstanceOf('React\Dns\Config\HostsFile', $hosts);
+ }
+
+ public function testDefaultShouldHaveLocalhostMapped()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Not supported on Windows');
+ }
+
+ $hosts = HostsFile::loadFromPathBlocking();
+
+ $this->assertContains('127.0.0.1', $hosts->getIpsForHost('localhost'));
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testLoadThrowsForInvalidPath()
+ {
+ HostsFile::loadFromPathBlocking('does/not/exist');
+ }
+
+ public function testContainsSingleLocalhostEntry()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testNonIpReturnsNothingForInvalidHosts()
+ {
+ $hosts = new HostsFile('a b');
+
+ $this->assertEquals(array(), $hosts->getIpsForHost('a'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('b'));
+ }
+
+ public function testIgnoresIpv6ZoneId()
+ {
+ $hosts = new HostsFile('fe80::1%lo0 localhost');
+
+ $this->assertEquals(array('fe80::1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testSkipsComments()
+ {
+ $hosts = new HostsFile('# start' . PHP_EOL .'#127.0.0.1 localhost' . PHP_EOL . '127.0.0.2 localhost # example.com');
+
+ $this->assertEquals(array('127.0.0.2'), $hosts->getIpsForHost('localhost'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testContainsSingleLocalhostEntryWithCaseIgnored()
+ {
+ $hosts = new HostsFile('127.0.0.1 LocalHost');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('LOCALHOST'));
+ }
+
+ public function testEmptyFileContainsNothing()
+ {
+ $hosts = new HostsFile('');
+
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testSingleEntryWithMultipleNames()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost example.com');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('example.com'));
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testMergesEntriesOverMultipleLines()
+ {
+ $hosts = new HostsFile("127.0.0.1 localhost\n127.0.0.2 localhost\n127.0.0.3 a localhost b\n127.0.0.4 a localhost");
+
+ $this->assertEquals(array('127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testMergesIpv4AndIpv6EntriesOverMultipleLines()
+ {
+ $hosts = new HostsFile("127.0.0.1 localhost\n::1 localhost");
+
+ $this->assertEquals(array('127.0.0.1', '::1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testReverseLookup()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('192.168.1.1'));
+ }
+
+ public function testReverseSkipsComments()
+ {
+ $hosts = new HostsFile("# start\n#127.0.0.1 localhosted\n127.0.0.2\tlocalhost\t# example.com\n\t127.0.0.3\t\texample.org\t\t");
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1'));
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.2'));
+ $this->assertEquals(array('example.org'), $hosts->getHostsForIp('127.0.0.3'));
+ }
+
+ public function testReverseNonIpReturnsNothing()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('localhost'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1.1'));
+ }
+
+ public function testReverseNonIpReturnsNothingForInvalidHosts()
+ {
+ $hosts = new HostsFile('a b');
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('a'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('b'));
+ }
+
+ public function testReverseLookupReturnsLowerCaseHost()
+ {
+ $hosts = new HostsFile('127.0.0.1 LocalHost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
+ }
+
+ public function testReverseLookupChecksNormalizedIpv6()
+ {
+ $hosts = new HostsFile('FE80::00a1 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::A1'));
+ }
+
+ public function testReverseLookupIgnoresIpv6ZoneId()
+ {
+ $hosts = new HostsFile('fe80::1%lo0 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::1'));
+ }
+
+ public function testReverseLookupReturnsMultipleHostsOverSingleLine()
+ {
+ $hosts = new HostsFile("::1 ip6-localhost ip6-loopback");
+
+ $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
+ }
+
+ public function testReverseLookupReturnsMultipleHostsOverMultipleLines()
+ {
+ $hosts = new HostsFile("::1 ip6-localhost\n::1 ip6-loopback");
+
+ $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Fixtures/etc/resolv.conf b/instafeed/vendor/react/dns/tests/Fixtures/etc/resolv.conf
new file mode 100755
index 0000000..cae093a
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Fixtures/etc/resolv.conf
@@ -0,0 +1 @@
+nameserver 8.8.8.8
diff --git a/instafeed/vendor/react/dns/tests/FunctionalResolverTest.php b/instafeed/vendor/react/dns/tests/FunctionalResolverTest.php
new file mode 100755
index 0000000..a52a3be
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/FunctionalResolverTest.php
@@ -0,0 +1,171 @@
+loop = LoopFactory::create();
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('8.8.8.8', $this->loop);
+ }
+
+ public function testResolveLocalhostResolves()
+ {
+ $promise = $this->resolver->resolve('localhost');
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ public function testResolveAllLocalhostResolvesWithArray()
+ {
+ $promise = $this->resolver->resolveAll('localhost', Message::TYPE_A);
+ $promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveGoogleResolves()
+ {
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveAllGoogleMxResolvesWithCache()
+ {
+ $factory = new Factory();
+ $this->resolver = $factory->createCached('8.8.8.8', $this->loop);
+
+ $promise = $this->resolver->resolveAll('google.com', Message::TYPE_MX);
+ $promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveInvalidRejects()
+ {
+ $ex = $this->callback(function ($param) {
+ return ($param instanceof RecordNotFoundException && $param->getCode() === Message::RCODE_NAME_ERROR);
+ });
+
+ $promise = $this->resolver->resolve('example.invalid');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
+
+ $this->loop->run();
+ }
+
+ public function testResolveCancelledRejectsImmediately()
+ {
+ $ex = $this->callback(function ($param) {
+ return ($param instanceof \RuntimeException && $param->getMessage() === 'DNS query for google.com has been cancelled');
+ });
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
+ $promise->cancel();
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.1, $time);
+ }
+
+ public function testInvalidResolverDoesNotResolveGoogle()
+ {
+ $factory = new Factory();
+ $this->resolver = $factory->create('255.255.255.255', $this->loop);
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('255.255.255.255', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->createCached('255.255.255.255', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancelResolveShouldNotCauseGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('127.0.0.1', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancelResolveCachedShouldNotCauseGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->createCached('127.0.0.1', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Model/MessageTest.php b/instafeed/vendor/react/dns/tests/Model/MessageTest.php
new file mode 100755
index 0000000..cf3d890
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Model/MessageTest.php
@@ -0,0 +1,31 @@
+assertTrue($request->header->isQuery());
+ $this->assertSame(1, $request->header->get('rd'));
+ }
+
+ public function testCreateResponseWithNoAnswers()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $answers = array();
+ $request = Message::createResponseWithAnswersForQuery($query, $answers);
+
+ $this->assertFalse($request->header->isQuery());
+ $this->assertTrue($request->header->isResponse());
+ $this->assertEquals(0, $request->header->get('anCount'));
+ $this->assertEquals(Message::RCODE_OK, $request->getResponseCode());
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Protocol/BinaryDumperTest.php b/instafeed/vendor/react/dns/tests/Protocol/BinaryDumperTest.php
new file mode 100755
index 0000000..ee94030
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Protocol/BinaryDumperTest.php
@@ -0,0 +1,278 @@
+formatHexDump($data);
+
+ $request = new Message();
+ $request->header->set('id', 0x7262);
+ $request->header->set('rd', 1);
+
+ $request->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN,
+ );
+
+ $request->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($request);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryRequestMessageWithCustomOptForEdns0()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "00"; // additional: (empty hostname)
+ $data .= "00 29 03 e8 00 00 00 00 00 00 "; // additional: type OPT, class UDP size, TTL 0, no RDATA
+
+ $expected = $this->formatHexDump($data);
+
+ $request = new Message();
+ $request->header->set('id', 0x7262);
+ $request->header->set('rd', 1);
+
+ $request->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN,
+ );
+
+ $request->additional[] = new Record('', 41, 1000, 0, '');
+
+ $request->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($request);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryResponseMessageWithoutRecords()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithSRVRecord()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 21 00 01"; // question: type SRV, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0c"; // answer: rdlength 12
+ $data .= "00 0a 00 14 1f 90 04 74 65 73 74 00"; // answer: rdata priority 10, weight 20, port 8080 test
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_SRV,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_SRV, Message::CLASS_IN, 86400, array(
+ 'priority' => 10,
+ 'weight' => 20,
+ 'port' => 8080,
+ 'target' => 'test'
+ ));
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithSOARecord()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 06 00 01"; // question: type SOA, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 06 00 01"; // answer: type SOA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 27"; // answer: rdlength 39
+ $data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
+ $data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
+ $data .= "78 49 28 d5 00 00 2a 30 00 00 0e 10"; // answer: rdata 2018060501, 10800, 3600
+ $data .= "00 09 3e 68 00 00 0e 10"; // answer: 605800, 3600
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_SOA,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_SOA, Message::CLASS_IN, 86400, array(
+ 'mname' => 'ns.hello',
+ 'rname' => 'e.hello',
+ 'serial' => 2018060501,
+ 'refresh' => 10800,
+ 'retry' => 3600,
+ 'expire' => 605800,
+ 'minimum' => 3600
+ ));
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithMultipleAnswerRecords()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 04 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 ff 00 01"; // question: type ANY, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01 00 00 00 00 00 04"; // answer: type A, class IN, TTL 0, 4 bytes
+ $data .= "7f 00 00 01"; // answer: 127.0.0.1
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 1c 00 01 00 00 00 00 00 10"; // question: type AAAA, class IN, TTL 0, 16 bytes
+ $data .= "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01"; // answer: ::1
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01 00 00 00 00 00 0c"; // answer: type TXT, class IN, TTL 0, 12 bytes
+ $data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: hello, world
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01 00 00 00 00 00 03"; // anwser: type MX, class IN, TTL 0, 3 bytes
+ $data .= "00 00 00"; // priority 0, no target
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_ANY,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
+ $response->answers[] = new Record('igor.io', Message::TYPE_AAAA, Message::CLASS_IN, 0, '::1');
+ $response->answers[] = new Record('igor.io', Message::TYPE_TXT, Message::CLASS_IN, 0, array('hello', 'world'));
+ $response->answers[] = new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 0, array('priority' => 0, 'target' => ''));
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithAnswerAndAdditionalRecord()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 01 00 00 00 01"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 02 00 01"; // question: type NS, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 02 00 01 00 00 00 00 00 0d"; // answer: type NS, class IN, TTL 0, 10 bytes
+ $data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // answer: example.com
+ $data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // additional: example.com
+ $data .= "00 01 00 01 00 00 00 00 00 04"; // additional: type A, class IN, TTL 0, 4 bytes
+ $data .= "7f 00 00 01"; // additional: 127.0.0.1
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_NS,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_NS, Message::CLASS_IN, 0, 'example.com');
+ $response->additional[] = new Record('example.com', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ private function convertBinaryToHexDump($input)
+ {
+ return $this->formatHexDump(implode('', unpack('H*', $input)));
+ }
+
+ private function formatHexDump($input)
+ {
+ return implode(' ', str_split(str_replace(' ', '', $input), 2));
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Protocol/ParserTest.php b/instafeed/vendor/react/dns/tests/Protocol/ParserTest.php
new file mode 100755
index 0000000..4086626
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Protocol/ParserTest.php
@@ -0,0 +1,1033 @@
+parser = new Parser();
+ }
+
+ /**
+ * @dataProvider provideConvertTcpDumpToBinary
+ */
+ public function testConvertTcpDumpToBinary($expected, $data)
+ {
+ $this->assertSame($expected, $this->convertTcpDumpToBinary($data));
+ }
+
+ public function provideConvertTcpDumpToBinary()
+ {
+ return array(
+ array(chr(0x72).chr(0x62), "72 62"),
+ array(chr(0x72).chr(0x62).chr(0x01).chr(0x00), "72 62 01 00"),
+ array(chr(0x72).chr(0x62).chr(0x01).chr(0x00).chr(0x00).chr(0x01), "72 62 01 00 00 01"),
+ array(chr(0x01).chr(0x00).chr(0x01), "01 00 01"),
+ );
+ }
+
+ public function testParseRequest()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $request = $this->parser->parseMessage($data);
+
+ $header = $request->header;
+ $this->assertSame(0x7262, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(0, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(0, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(0, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $request->questions);
+ $this->assertSame('igor.io', $request->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']);
+ }
+
+ public function testParseResponse()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0x7262, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('igor.io', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseQuestionWithTwoQuestions()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "03 77 77 77 04 69 67 6f 72 02 69 6f 00"; // question: www.igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $request = new Message();
+ $request->header->set('qdCount', 2);
+ $request->data = $data;
+
+ $this->parser->parseQuestion($request);
+
+ $this->assertCount(2, $request->questions);
+ $this->assertSame('igor.io', $request->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']);
+ $this->assertSame('www.igor.io', $request->questions[1]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[1]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[1]['class']);
+ }
+
+ public function testParseAnswerWithInlineData()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithExcessiveTtlReturnsZeroTtl()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "ff ff ff ff"; // answer: ttl 2^32 - 1
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(0, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithTtlExactlyBoundaryReturnsZeroTtl()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "80 00 00 00"; // answer: ttl 2^31
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(0, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithMaximumTtlReturnsExactTtl()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "7f ff ff ff"; // answer: ttl 2^31 - 1
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(0x7fffffff, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithUnknownType()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "23 28 00 01"; // answer: type 9000, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 05"; // answer: rdlength 5
+ $data .= "68 65 6c 6c 6f"; // answer: rdata "hello"
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(9000, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('hello', $response->answers[0]->data);
+ }
+
+ public function testParseResponseWithCnameAndOffsetPointers()
+ {
+ $data = "";
+ $data .= "9e 8d 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 6d 61 69 6c 06 67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: mail.google.com
+ $data .= "00 05 00 01"; // question: type CNAME, class IN
+ $data .= "c0 0c"; // answer: offset pointer to mail.google.com
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 a8 9c"; // answer: ttl 43164
+ $data .= "00 0f"; // answer: rdlength 15
+ $data .= "0a 67 6f 6f 67 6c 65 6d 61 69 6c 01 6c"; // answer: rdata googlemail.l.
+ $data .= "c0 11"; // answer: rdata offset pointer to google.com
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('mail.google.com', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_CNAME, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('mail.google.com', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(43164, $response->answers[0]->ttl);
+ $this->assertSame('googlemail.l.google.com', $response->answers[0]->data);
+ }
+
+ public function testParseAAAAResponse()
+ {
+ $data = "";
+ $data .= "cd 72 81 80 00 01 00 01 00 00 00 00 06"; // header
+ $data .= "67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: google.com
+ $data .= "00 1c 00 01"; // question: type AAAA, class IN
+ $data .= "c0 0c"; // answer: offset pointer to google.com
+ $data .= "00 1c 00 01"; // answer: type AAAA, class IN
+ $data .= "00 00 01 2b"; // answer: ttl 299
+ $data .= "00 10"; // answer: rdlength 16
+ $data .= "2a 00 14 50 40 09 08 09 00 00 00 00 00 00 20 0e"; // answer: 2a00:1450:4009:809::200e
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0xcd72, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('google.com', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_AAAA, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('google.com', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_AAAA, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(299, $response->answers[0]->ttl);
+ $this->assertSame('2a00:1450:4009:809::200e', $response->answers[0]->data);
+ }
+
+ public function testParseTXTResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01"; // answer: type TXT, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 06"; // answer: rdlength 6
+ $data .= "05 68 65 6c 6c 6f"; // answer: rdata length 5: hello
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_TXT, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(array('hello'), $response->answers[0]->data);
+ }
+
+ public function testParseTXTResponseMultiple()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01"; // answer: type TXT, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0C"; // answer: rdlength 12
+ $data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: rdata length 5: hello, length 5: world
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_TXT, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(array('hello', 'world'), $response->answers[0]->data);
+ }
+
+ public function testParseMXResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01"; // answer: type MX, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 09"; // answer: rdlength 9
+ $data .= "00 0a 05 68 65 6c 6c 6f 00"; // answer: rdata priority 10: hello
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_MX, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(array('priority' => 10, 'target' => 'hello'), $response->answers[0]->data);
+ }
+
+ public function testParseSRVResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0C"; // answer: rdlength 12
+ $data .= "00 0a 00 14 1F 90 04 74 65 73 74 00"; // answer: rdata priority 10, weight 20, port 8080 test
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_SRV, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(
+ array(
+ 'priority' => 10,
+ 'weight' => 20,
+ 'port' => 8080,
+ 'target' => 'test'
+ ),
+ $response->answers[0]->data
+ );
+ }
+
+ public function testParseMessageResponseWithTwoAnswers()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 02 00 00 00 00"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 00 29"; // answer: ttl 41
+ $data .= "00 0e"; // answer: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // answer: rdata whois.nic.io
+ $data .= "c0 32"; // answer: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 0d f7"; // answer: ttl 3575
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "c1 df 4e 98"; // answer: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(2, $response->answers);
+
+ $this->assertSame('io.whois-servers.net', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(41, $response->answers[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->answers[0]->data);
+
+ $this->assertSame('whois.nic.io', $response->answers[1]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[1]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[1]->class);
+ $this->assertSame(3575, $response->answers[1]->ttl);
+ $this->assertSame('193.223.78.152', $response->answers[1]->data);
+ }
+
+ public function testParseMessageResponseWithTwoAuthorityRecords()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 00 00 02 00 00"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // authority: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // authority: type CNAME, class IN
+ $data .= "00 00 00 29"; // authority: ttl 41
+ $data .= "00 0e"; // authority: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // authority: rdata whois.nic.io
+ $data .= "c0 32"; // authority: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // authority: type CNAME, class IN
+ $data .= "00 00 0d f7"; // authority: ttl 3575
+ $data .= "00 04"; // authority: rdlength 4
+ $data .= "c1 df 4e 98"; // authority: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(0, $response->answers);
+
+ $this->assertCount(2, $response->authority);
+
+ $this->assertSame('io.whois-servers.net', $response->authority[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->authority[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->authority[0]->class);
+ $this->assertSame(41, $response->authority[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->authority[0]->data);
+
+ $this->assertSame('whois.nic.io', $response->authority[1]->name);
+ $this->assertSame(Message::TYPE_A, $response->authority[1]->type);
+ $this->assertSame(Message::CLASS_IN, $response->authority[1]->class);
+ $this->assertSame(3575, $response->authority[1]->ttl);
+ $this->assertSame('193.223.78.152', $response->authority[1]->data);
+ }
+
+ public function testParseMessageResponseWithAnswerAndAdditionalRecord()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 01 00 00 00 01"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 00 29"; // answer: ttl 41
+ $data .= "00 0e"; // answer: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // answer: rdata whois.nic.io
+ $data .= "c0 32"; // additional: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // additional: type CNAME, class IN
+ $data .= "00 00 0d f7"; // additional: ttl 3575
+ $data .= "00 04"; // additional: rdlength 4
+ $data .= "c1 df 4e 98"; // additional: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+
+ $this->assertSame('io.whois-servers.net', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(41, $response->answers[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->answers[0]->data);
+
+ $this->assertCount(0, $response->authority);
+ $this->assertCount(1, $response->additional);
+
+ $this->assertSame('whois.nic.io', $response->additional[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->additional[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->additional[0]->class);
+ $this->assertSame(3575, $response->additional[0]->ttl);
+ $this->assertSame('193.223.78.152', $response->additional[0]->data);
+ }
+
+ public function testParseNSResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 02 00 01"; // answer: type NS, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 07"; // answer: rdlength 7
+ $data .= "05 68 65 6c 6c 6f 00"; // answer: rdata hello
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_NS, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('hello', $response->answers[0]->data);
+ }
+
+ public function testParseSOAResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 06 00 01"; // answer: type SOA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 27"; // answer: rdlength 39
+ $data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
+ $data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
+ $data .= "78 49 28 D5 00 00 2a 30 00 00 0e 10"; // answer: rdata 2018060501, 10800, 3600
+ $data .= "00 09 3a 80 00 00 0e 10"; // answer: 605800, 3600
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_SOA, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(
+ array(
+ 'mname' => 'ns.hello',
+ 'rname' => 'e.hello',
+ 'serial' => 2018060501,
+ 'refresh' => 10800,
+ 'retry' => 3600,
+ 'expire' => 604800,
+ 'minimum' => 3600
+ ),
+ $response->answers[0]->data
+ );
+ }
+
+ public function testParsePTRResponse()
+ {
+ $data = "";
+ $data .= "5d d8 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "01 34 01 34 01 38 01 38 07 69 6e"; // question: 4.4.8.8.in-addr.arpa
+ $data .= "2d 61 64 64 72 04 61 72 70 61 00"; // question (continued)
+ $data .= "00 0c 00 01"; // question: type PTR, class IN
+ $data .= "c0 0c"; // answer: offset pointer to rdata
+ $data .= "00 0c 00 01"; // answer: type PTR, class IN
+ $data .= "00 01 51 7f"; // answer: ttl 86399
+ $data .= "00 20"; // answer: rdlength 32
+ $data .= "13 67 6f 6f 67 6c 65 2d 70 75 62 6c 69 63 2d 64"; // answer: rdata google-public-dns-b.google.com.
+ $data .= "6e 73 2d 62 06 67 6f 6f 67 6c 65 03 63 6f 6d 00";
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0x5dd8, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('4.4.8.8.in-addr.arpa', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_PTR, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('4.4.8.8.in-addr.arpa', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_PTR, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86399, $response->answers[0]->ttl);
+ $this->assertSame('google-public-dns-b.google.com', $response->answers[0]->data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteQuestionThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ //$data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteQuestionLabelThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67"; // question: ig …?
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72"; // question: igor. …?
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteOffsetPointerInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "ff"; // question: incomplete offset pointer
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseInvalidOffsetPointerInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "ff ff"; // question: offset pointer to invalid address
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseInvalidOffsetPointerToSameLabelInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "c0 0c"; // question: offset pointer to invalid address
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseInvalidOffsetPointerToStartOfMessageInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "c0 00"; // question: offset pointer to start of message
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteAnswerFieldsThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseMessageResponseWithIncompleteAuthorityRecordThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 00 00 01 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // authority: offset pointer to igor.io
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseMessageResponseWithIncompleteAdditionalRecordThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 00 00 00 00 01"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // additional: offset pointer to igor.io
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteAnswerRecordDataThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ public function testParseInvalidNSResponseWhereDomainNameIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 02 00 01"; // answer: type NS, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 00"; // answer: rdlength 0
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidAResponseWhereIPIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 00"; // answer: rdlength 0
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidAAAAResponseWhereIPIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 1c 00 01"; // answer: type AAAA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 00"; // answer: rdlength 0
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidTXTResponseWhereTxtChunkExceedsLimit()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01"; // answer: type TXT, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 06"; // answer: rdlength 6
+ $data .= "06 68 65 6c 6c 6f 6f"; // answer: rdata length 6: helloo
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidMXResponseWhereDomainNameIsIncomplete()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01"; // answer: type MX, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 08"; // answer: rdlength 8
+ $data .= "00 0a 05 68 65 6c 6c 6f"; // answer: rdata priority 10: hello (missing label end)
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidMXResponseWhereDomainNameIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01"; // answer: type MX, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 02"; // answer: rdlength 2
+ $data .= "00 0a"; // answer: rdata priority 10
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidSRVResponseWhereDomainNameIsIncomplete()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0b"; // answer: rdlength 11
+ $data .= "00 0a 00 14 1F 90 04 74 65 73 74"; // answer: rdata priority 10, weight 20, port 8080 test (missing label end)
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidSRVResponseWhereDomainNameIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 06"; // answer: rdlength 6
+ $data .= "00 0a 00 14 1F 90"; // answer: rdata priority 10, weight 20, port 8080
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidSOAResponseWhereFlagsAreMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 06 00 01"; // answer: type SOA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 13"; // answer: rdlength 19
+ $data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
+ $data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ private function convertTcpDumpToBinary($input)
+ {
+ // sudo ngrep -d en1 -x port 53
+
+ return pack('H*', str_replace(' ', '', $input));
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Query/CachedExecutorTest.php b/instafeed/vendor/react/dns/tests/Query/CachedExecutorTest.php
new file mode 100755
index 0000000..d08ed05
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/CachedExecutorTest.php
@@ -0,0 +1,100 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue($this->createPromiseMock()));
+
+ $cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cache
+ ->expects($this->once())
+ ->method('lookup')
+ ->will($this->returnValue(Promise\reject()));
+ $cachedExecutor = new CachedExecutor($executor, $cache);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $cachedExecutor->query('8.8.8.8', $query);
+ }
+
+ /**
+ * @covers React\Dns\Query\CachedExecutor
+ * @test
+ */
+ public function callingQueryTwiceShouldUseCachedResult()
+ {
+ $cachedRecords = array(new Record('igor.io', Message::TYPE_A, Message::CLASS_IN));
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->callQueryCallbackWithAddress('178.79.169.131'));
+
+ $cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cache
+ ->expects($this->at(0))
+ ->method('lookup')
+ ->with($this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue(Promise\reject()));
+ $cache
+ ->expects($this->at(1))
+ ->method('storeResponseMessage')
+ ->with($this->isType('integer'), $this->isInstanceOf('React\Dns\Model\Message'));
+ $cache
+ ->expects($this->at(2))
+ ->method('lookup')
+ ->with($this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue(Promise\resolve($cachedRecords)));
+
+ $cachedExecutor = new CachedExecutor($executor, $cache);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
+ $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
+ }
+
+ private function callQueryCallbackWithAddress($address)
+ {
+ return $this->returnCallback(function ($nameserver, $query) use ($address) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, $address);
+
+ return Promise\resolve($response);
+ });
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+
+ private function createPromiseMock()
+ {
+ return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Query/CachingExecutorTest.php b/instafeed/vendor/react/dns/tests/Query/CachingExecutorTest.php
new file mode 100755
index 0000000..abd9342
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/CachingExecutorTest.php
@@ -0,0 +1,183 @@
+getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->never())->method('query');
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(new Promise(function () { }));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnPendingPromiseWhenCacheReturnsMissAndWillSendSameQueryToFallbackExecutor()
+ {
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn(new Promise(function () { }));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSendingQueryToFallbackExecutor()
+ {
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->never())->method('query');
+
+ $message = new Message();
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve($message));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithMinimumTtlFromRecord()
+ {
+ $message = new Message();
+ $message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3700, '127.0.0.1');
+ $message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '127.0.0.1');
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
+ $cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 3600);
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithDefaultTtl()
+ {
+ $message = new Message();
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
+ $cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 60);
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesWithTruncatedResponseButShouldNotSaveTruncatedMessageToCache()
+ {
+ $message = new Message();
+ $message->header->set('tc', 1);
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
+ $cache->expects($this->never())->method('set');
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnRejectedPromiseWhenCacheReturnsMissAndFallbackExecutorRejects()
+ {
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\reject(new \RuntimeException()));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
+ }
+
+ public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromCache()
+ {
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->never())->method('query');
+
+ $pending = new Promise(function () { }, $this->expectCallableOnce());
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($pending);
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
+ }
+
+ public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromFallbackExecutorWhenCacheReturnsMiss()
+ {
+ $pending = new Promise(function () { }, $this->expectCallableOnce());
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn($pending);
+
+ $deferred = new Deferred();
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($deferred->promise());
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+ $deferred->resolve(null);
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Query/CoopExecutorTest.php b/instafeed/vendor/react/dns/tests/Query/CoopExecutorTest.php
new file mode 100755
index 0000000..d265de2
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/CoopExecutorTest.php
@@ -0,0 +1,233 @@
+getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testQueryOnceWillResolveWhenBaseExecutorResolves()
+ {
+ $message = new Message();
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $connector->query('8.8.8.8', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $promise->then($this->expectCallableOnceWith($message));
+ }
+
+ public function testQueryOnceWillRejectWhenBaseExecutorRejects()
+ {
+ $exception = new RuntimeException();
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn(\React\Promise\reject($exception));
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $connector->query('8.8.8.8', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $promise->then(null, $this->expectCallableOnceWith($exception));
+ }
+
+ public function testQueryTwoDifferentQueriesWillPassExactQueryToBaseExecutorTwice()
+ {
+ $pending = new Promise(function () { });
+ $query1 = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $query2 = new Query('reactphp.org', Message::TYPE_AAAA, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->withConsecutive(
+ array('8.8.8.8', $query1),
+ array('8.8.8.8', $query2)
+ )->willReturn($pending);
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query1);
+ $connector->query('8.8.8.8', $query2);
+ }
+
+ public function testQueryTwiceWillPassExactQueryToBaseExecutorOnceWhenQueryIsStillPending()
+ {
+ $pending = new Promise(function () { });
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyResolved()
+ {
+ $deferred = new Deferred();
+ $pending = new Promise(function () { });
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
+
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+
+ $deferred->resolve(new Message());
+
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyRejected()
+ {
+ $deferred = new Deferred();
+ $pending = new Promise(function () { });
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
+
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+
+ $deferred->reject(new RuntimeException());
+
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testCancelQueryWillCancelPromiseFromBaseExecutorAndReject()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $connector->query('8.8.8.8', $query);
+
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testCancelOneQueryWhenOtherQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableNever());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise1->cancel();
+
+ $promise1->then(null, $this->expectCallableOnce());
+ $promise2->then(null, $this->expectCallableNever());
+ }
+
+ public function testCancelSecondQueryWhenFirstQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableNever());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise2->cancel();
+
+ $promise2->then(null, $this->expectCallableOnce());
+ $promise1->then(null, $this->expectCallableNever());
+ }
+
+ public function testCancelAllPendingQueriesWillCancelPromiseFromBaseExecutorAndRejectCancelled()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise1->cancel();
+ $promise2->cancel();
+
+ $promise1->then(null, $this->expectCallableOnce());
+ $promise2->then(null, $this->expectCallableOnce());
+ }
+
+ public function testQueryTwiceWillQueryBaseExecutorTwiceIfFirstQueryHasAlreadyBeenCancelledWhenSecondIsStarted()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+ $pending = new Promise(function () { });
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->willReturnOnConsecutiveCalls($promise, $pending);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise1->cancel();
+
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise1->then(null, $this->expectCallableOnce());
+
+ $promise2->then(null, $this->expectCallableNever());
+ }
+
+ public function testCancelQueryShouldNotCauseGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $deferred = new Deferred(function () {
+ throw new \RuntimeException();
+ });
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($deferred->promise());
+ $connector = new CoopExecutor($base);
+
+ gc_collect_cycles();
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $connector->query('8.8.8.8', $query);
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Query/ExecutorTest.php b/instafeed/vendor/react/dns/tests/Query/ExecutorTest.php
new file mode 100755
index 0000000..0d7ac1d
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/ExecutorTest.php
@@ -0,0 +1,308 @@
+loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->parser = $this->getMockBuilder('React\Dns\Protocol\Parser')->getMock();
+ $this->dumper = new BinaryDumper();
+
+ $this->executor = new Executor($this->loop, $this->parser, $this->dumper);
+ }
+
+ /** @test */
+ public function queryShouldCreateUdpRequest()
+ {
+ $timer = $this->createTimerMock();
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock(false));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldRejectIfRequestIsLargerThan512Bytes()
+ {
+ $query = new Query(str_repeat('a', 512).'.igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->setExpectedException('RuntimeException', 'DNS query for ' . $query->name . ' failed: Requested transport "tcp" not available, only UDP is supported in this version');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldCloseConnectionWhenCancelled()
+ {
+ $conn = $this->createConnectionMock(false);
+ $conn->expects($this->once())->method('close');
+
+ $timer = $this->createTimerMock();
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnValue($conn));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->cancel();
+
+ $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldNotStartOrCancelTimerWhenCancelledWithTimeoutIsNull()
+ {
+ $this->loop
+ ->expects($this->never())
+ ->method('addTimer');
+
+ $this->executor = new Executor($this->loop, $this->parser, $this->dumper, null);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('127.0.0.1:53', $query);
+
+ $promise->cancel();
+
+ $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldRejectIfResponseIsTruncated()
+ {
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->parser
+ ->expects($this->once())
+ ->method('parseMessage')
+ ->will($this->returnTruncatedResponse());
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock());
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldFailIfUdpThrow()
+ {
+ $this->loop
+ ->expects($this->never())
+ ->method('addTimer');
+
+ $this->parser
+ ->expects($this->never())
+ ->method('parseMessage');
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->throwException(new \Exception('Nope')));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->setExpectedException('RuntimeException', 'DNS query for igor.io failed: Nope');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldCancelTimerWhenFullResponseIsReceived()
+ {
+ $conn = $this->createConnectionMock();
+
+ $this->parser
+ ->expects($this->once())
+ ->method('parseMessage')
+ ->will($this->returnStandardResponse());
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->at(0))
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock());
+
+
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->once())
+ ->method('addTimer')
+ ->with(5, $this->isInstanceOf('Closure'))
+ ->will($this->returnValue($timer));
+
+ $this->loop
+ ->expects($this->once())
+ ->method('cancelTimer')
+ ->with($timer);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldCloseConnectionOnTimeout()
+ {
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->at(0))
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock(false));
+
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->never())
+ ->method('cancelTimer');
+
+ $this->loop
+ ->expects($this->once())
+ ->method('addTimer')
+ ->with(5, $this->isInstanceOf('Closure'))
+ ->will($this->returnCallback(function ($time, $callback) use (&$timerCallback, $timer) {
+ $timerCallback = $callback;
+ return $timer;
+ }));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->assertNotNull($timerCallback);
+ $timerCallback();
+
+ $this->setExpectedException('React\Dns\Query\TimeoutException', 'DNS query for igor.io timed out');
+ Block\await($promise, $this->loop);
+ }
+
+ private function returnStandardResponse()
+ {
+ $that = $this;
+ $callback = function ($data) use ($that) {
+ $response = new Message();
+ $that->convertMessageToStandardResponse($response);
+ return $response;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ private function returnTruncatedResponse()
+ {
+ $that = $this;
+ $callback = function ($data) use ($that) {
+ $response = new Message();
+ $that->convertMessageToTruncatedResponse($response);
+ return $response;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ public function convertMessageToStandardResponse(Message $response)
+ {
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->prepare();
+
+ return $response;
+ }
+
+ public function convertMessageToTruncatedResponse(Message $response)
+ {
+ $this->convertMessageToStandardResponse($response);
+ $response->header->set('tc', 1);
+ $response->prepare();
+
+ return $response;
+ }
+
+ private function returnNewConnectionMock($emitData = true)
+ {
+ $conn = $this->createConnectionMock($emitData);
+
+ $callback = function () use ($conn) {
+ return $conn;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ private function createConnectionMock($emitData = true)
+ {
+ $conn = $this->getMockBuilder('React\Stream\DuplexStreamInterface')->getMock();
+ $conn
+ ->expects($this->any())
+ ->method('on')
+ ->with('data', $this->isInstanceOf('Closure'))
+ ->will($this->returnCallback(function ($name, $callback) use ($emitData) {
+ $emitData && $callback(null);
+ }));
+
+ return $conn;
+ }
+
+ private function createTimerMock()
+ {
+ return $this->getMockBuilder(
+ interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface'
+ )->getMock();
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\Executor')
+ ->setConstructorArgs(array($this->loop, $this->parser, $this->dumper))
+ ->setMethods(array('createConnection'))
+ ->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Query/HostsFileExecutorTest.php b/instafeed/vendor/react/dns/tests/Query/HostsFileExecutorTest.php
new file mode 100755
index 0000000..70d877e
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/HostsFileExecutorTest.php
@@ -0,0 +1,126 @@
+hosts = $this->getMockBuilder('React\Dns\Config\HostsFile')->disableOriginalConstructor()->getMock();
+ $this->fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $this->executor = new HostsFileExecutor($this->hosts, $this->fallback);
+ }
+
+ public function testDoesNotTryToGetIpsForMxQuery()
+ {
+ $this->hosts->expects($this->never())->method('getIpsForHost');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_MX, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpsWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array());
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testReturnsResponseMessageIfIpsWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpv4Matches()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
+ $this->fallback->expects($this->once())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testReturnsResponseMessageIfIpv6AddressesWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpv6Matches()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
+ $this->fallback->expects($this->once())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
+ }
+
+ public function testDoesReturnReverseIpv4Lookup()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array('localhost'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoReverseIpv4Matches()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array());
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testDoesReturnReverseIpv6Lookup()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(array('ip6-localhost'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.e.f.3.0.0.e.2.0.2.0.a.2.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackForInvalidAddress()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidIpv4Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidLengthIpv6Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidHexIpv6Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('zZz.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Query/RecordBagTest.php b/instafeed/vendor/react/dns/tests/Query/RecordBagTest.php
new file mode 100755
index 0000000..c0615be
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/RecordBagTest.php
@@ -0,0 +1,83 @@
+assertSame(array(), $recordBag->all());
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldSetTheValue()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600));
+
+ $records = $recordBag->all();
+ $this->assertCount(1, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_A, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldAcceptMxRecord()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 3600, array('priority' => 10, 'target' => 'igor.io')));
+
+ $records = $recordBag->all();
+ $this->assertCount(1, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_MX, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ $this->assertSame(array('priority' => 10, 'target' => 'igor.io'), $records[0]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldSetManyValues()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
+
+ $records = $recordBag->all();
+ $this->assertCount(2, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_A, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ $this->assertSame('178.79.169.131', $records[0]->data);
+ $this->assertSame('igor.io', $records[1]->name);
+ $this->assertSame(Message::TYPE_A, $records[1]->type);
+ $this->assertSame(Message::CLASS_IN, $records[1]->class);
+ $this->assertSame('178.79.169.132', $records[1]->data);
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Query/RecordCacheTest.php b/instafeed/vendor/react/dns/tests/Query/RecordCacheTest.php
new file mode 100755
index 0000000..01f0eee
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/RecordCacheTest.php
@@ -0,0 +1,160 @@
+getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+
+ $cache = new RecordCache($base);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordPendingCacheDoesNotSetCache()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $pending = new Promise(function () { });
+
+ $base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $base->expects($this->once())->method('get')->willReturn($pending);
+ $base->expects($this->never())->method('set');
+
+ $cache = new RecordCache($base);
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordOnCacheMissSetsCache()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+ $base->expects($this->once())->method('set')->with($this->isType('string'), $this->isType('string'));
+
+ $cache = new RecordCache($base);
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordShouldMakeLookupSucceed()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(1, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeTwoRecordsShouldReturnBoth()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(2, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ $this->assertSame('178.79.169.132', $cachedRecords[1]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeResponseMessageShouldStoreAllAnswerValues()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $response = new Message();
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132');
+ $response->prepare();
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeResponseMessage($query->currentTime, $response);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(2, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ $this->assertSame('178.79.169.132', $cachedRecords[1]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function expireShouldExpireDeadRecords()
+ {
+ $cachedTime = 1345656451;
+ $currentTime = $cachedTime + 3605;
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($cachedTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $cache->expire($currentTime);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, $currentTime);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
+ }
+
+ private function getPromiseValue(PromiseInterface $promise)
+ {
+ $capturedValue = null;
+
+ $promise->then(function ($value) use (&$capturedValue) {
+ $capturedValue = $value;
+ });
+
+ return $capturedValue;
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Query/RetryExecutorTest.php b/instafeed/vendor/react/dns/tests/Query/RetryExecutorTest.php
new file mode 100755
index 0000000..7e44a08
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/RetryExecutorTest.php
@@ -0,0 +1,350 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue($this->expectPromiseOnce()));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldRetryQueryOnTimeout()
+ {
+ $response = $this->createStandardResponse();
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(2))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->onConsecutiveCalls(
+ $this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new TimeoutException("timeout"));
+ }),
+ $this->returnCallback(function ($domain, $query) use ($response) {
+ return Promise\resolve($response);
+ })
+ ));
+
+ $callback = $this->createCallableMock();
+ $callback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('React\Dns\Model\Message'));
+
+ $errorback = $this->expectCallableNever();
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldStopRetryingAfterSomeAttempts()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(3))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new TimeoutException("timeout"));
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('RuntimeException'));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldForwardNonTimeoutErrors()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new \Exception);
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('Exception'));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldCancelQueryOnCancel()
+ {
+ $cancelled = 0;
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ })
+ );
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldCancelSecondQueryOnCancel()
+ {
+ $deferred = new Deferred();
+ $cancelled = 0;
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(2))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->onConsecutiveCalls(
+ $this->returnValue($deferred->promise()),
+ $this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ })
+ ));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ // first query will time out after a while and this sends the next query
+ $deferred->reject(new TimeoutException());
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnSuccess()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->willReturn(Promise\resolve($this->createStandardResponse()));
+
+ $retryExecutor = new RetryExecutor($executor, 0);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnTimeoutErrors()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->any())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->willReturn(Promise\reject(new TimeoutException("timeout")));
+
+ $retryExecutor = new RetryExecutor($executor, 0);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnCancellation()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $deferred = new Deferred(function () {
+ throw new \RuntimeException();
+ });
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->willReturn($deferred->promise());
+
+ $retryExecutor = new RetryExecutor($executor, 0);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnNonTimeoutErrors()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new \Exception);
+ }));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ protected function expectPromiseOnce($return = null)
+ {
+ $mock = $this->createPromiseMock();
+ $mock
+ ->expects($this->once())
+ ->method('then')
+ ->will($this->returnValue($return));
+
+ return $mock;
+ }
+
+ protected function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+
+ protected function createPromiseMock()
+ {
+ return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+ }
+
+ protected function createStandardResponse()
+ {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->prepare();
+
+ return $response;
+ }
+}
+
diff --git a/instafeed/vendor/react/dns/tests/Query/TimeoutExecutorTest.php b/instafeed/vendor/react/dns/tests/Query/TimeoutExecutorTest.php
new file mode 100755
index 0000000..0d37fb4
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/TimeoutExecutorTest.php
@@ -0,0 +1,115 @@
+loop = Factory::create();
+
+ $this->wrapped = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+
+ $this->executor = new TimeoutExecutor($this->wrapped, 5.0, $this->loop);
+ }
+
+ public function testCancellingPromiseWillCancelWrapped()
+ {
+ $cancelled = 0;
+
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ }));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testResolvesPromiseWhenWrappedResolves()
+ {
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->willReturn(Promise\resolve('0.0.0.0'));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+ }
+
+ public function testRejectsPromiseWhenWrappedRejects()
+ {
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->willReturn(Promise\reject(new \RuntimeException()));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException()));
+ }
+
+ public function testWrappedWillBeCancelledOnTimeout()
+ {
+ $this->executor = new TimeoutExecutor($this->wrapped, 0, $this->loop);
+
+ $cancelled = 0;
+
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->logicalAnd(
+ $this->isInstanceOf('React\Dns\Query\TimeoutException'),
+ $this->attribute($this->equalTo('DNS query for igor.io timed out'), 'message')
+ ));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query)->then($callback, $errorback);
+
+ $this->assertEquals(0, $cancelled);
+
+ $this->loop->run();
+
+ $this->assertEquals(1, $cancelled);
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Query/UdpTransportExecutorTest.php b/instafeed/vendor/react/dns/tests/Query/UdpTransportExecutorTest.php
new file mode 100755
index 0000000..f7222dc
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Query/UdpTransportExecutorTest.php
@@ -0,0 +1,215 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addReadStream');
+
+ $dumper = $this->getMockBuilder('React\Dns\Protocol\BinaryDumper')->getMock();
+ $dumper->expects($this->once())->method('toBinary')->willReturn(str_repeat('.', 513));
+
+ $executor = new UdpTransportExecutor($loop, null, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $executor->query('8.8.8.8:53', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testQueryRejectsIfServerConnectionFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addReadStream');
+
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $executor->query('///', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ /**
+ * @group internet
+ */
+ public function testQueryRejectsOnCancellation()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $executor->query('8.8.8.8:53', $query);
+ $promise->cancel();
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testQueryKeepsPendingIfServerRejectsNetworkPacket()
+ {
+ $loop = Factory::create();
+
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query('127.0.0.1:1', $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ \Clue\React\Block\sleep(0.2, $loop);
+ $this->assertTrue($wait);
+ }
+
+ public function testQueryKeepsPendingIfServerSendInvalidMessage()
+ {
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+ stream_socket_sendto($server, 'invalid', 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query($address, $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ \Clue\React\Block\sleep(0.2, $loop);
+ $this->assertTrue($wait);
+ }
+
+ public function testQueryKeepsPendingIfServerSendInvalidId()
+ {
+ $parser = new Parser();
+ $dumper = new BinaryDumper();
+
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) use ($parser, $dumper) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+
+ $message = $parser->parseMessage($data);
+ $message->header->set('id', 0);
+
+ stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop, $parser, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query($address, $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ \Clue\React\Block\sleep(0.2, $loop);
+ $this->assertTrue($wait);
+ }
+
+ public function testQueryRejectsIfServerSendsTruncatedResponse()
+ {
+ $parser = new Parser();
+ $dumper = new BinaryDumper();
+
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) use ($parser, $dumper) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+
+ $message = $parser->parseMessage($data);
+ $message->header->set('tc', 1);
+
+ stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop, $parser, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query($address, $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect connection ICMP rejection error
+ \Clue\React\Block\sleep(0.01, $loop);
+ if ($wait) {
+ \Clue\React\Block\sleep(0.2, $loop);
+ }
+
+ $this->assertFalse($wait);
+ }
+
+ public function testQueryResolvesIfServerSendsValidResponse()
+ {
+ $parser = new Parser();
+ $dumper = new BinaryDumper();
+
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) use ($parser, $dumper) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+
+ $message = $parser->parseMessage($data);
+
+ stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop, $parser, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query($address, $query);
+ $response = \Clue\React\Block\await($promise, $loop, 0.2);
+
+ $this->assertInstanceOf('React\Dns\Model\Message', $response);
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Resolver/FactoryTest.php b/instafeed/vendor/react/dns/tests/Resolver/FactoryTest.php
new file mode 100755
index 0000000..9b1b0f8
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Resolver/FactoryTest.php
@@ -0,0 +1,120 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create('8.8.8.8:53', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ }
+
+ /** @test */
+ public function createWithoutPortShouldCreateResolverWithDefaultPort()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create('8.8.8.8', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $this->assertSame('8.8.8.8:53', $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
+ }
+
+ /** @test */
+ public function createCachedShouldCreateResolverWithCachingExecutor()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->createCached('8.8.8.8:53', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $executor = $this->getResolverPrivateExecutor($resolver);
+ $this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
+ $cache = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
+ $this->assertInstanceOf('React\Cache\ArrayCache', $cache);
+ }
+
+ /** @test */
+ public function createCachedShouldCreateResolverWithCachingExecutorWithCustomCache()
+ {
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->createCached('8.8.8.8:53', $loop, $cache);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $executor = $this->getResolverPrivateExecutor($resolver);
+ $this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
+ $cacheProperty = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
+ $this->assertSame($cache, $cacheProperty);
+ }
+
+ /**
+ * @test
+ * @dataProvider factoryShouldAddDefaultPortProvider
+ */
+ public function factoryShouldAddDefaultPort($input, $expected)
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create($input, $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $this->assertSame($expected, $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
+ }
+
+ public static function factoryShouldAddDefaultPortProvider()
+ {
+ return array(
+ array('8.8.8.8', '8.8.8.8:53'),
+ array('1.2.3.4:5', '1.2.3.4:5'),
+ array('localhost', 'localhost:53'),
+ array('localhost:1234', 'localhost:1234'),
+ array('::1', '[::1]:53'),
+ array('[::1]:53', '[::1]:53')
+ );
+ }
+
+ private function getResolverPrivateExecutor($resolver)
+ {
+ $executor = $this->getResolverPrivateMemberValue($resolver, 'executor');
+
+ // extract underlying executor that may be wrapped in multiple layers of hosts file executors
+ while ($executor instanceof HostsFileExecutor) {
+ $reflector = new \ReflectionProperty('React\Dns\Query\HostsFileExecutor', 'fallback');
+ $reflector->setAccessible(true);
+
+ $executor = $reflector->getValue($executor);
+ }
+
+ return $executor;
+ }
+
+ private function getResolverPrivateMemberValue($resolver, $field)
+ {
+ $reflector = new \ReflectionProperty('React\Dns\Resolver\Resolver', $field);
+ $reflector->setAccessible(true);
+ return $reflector->getValue($resolver);
+ }
+
+ private function getCachingExecutorPrivateMemberValue($resolver, $field)
+ {
+ $reflector = new \ReflectionProperty('React\Dns\Query\CachingExecutor', $field);
+ $reflector->setAccessible(true);
+ return $reflector->getValue($resolver);
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php b/instafeed/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
new file mode 100755
index 0000000..a9b8608
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
@@ -0,0 +1,101 @@
+createExecutorMock();
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+
+ $answers = $resolver->resolveAliases($answers, $name);
+
+ $this->assertEquals($expectedAnswers, $answers);
+ }
+
+ public function provideAliasedAnswers()
+ {
+ return array(
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array(),
+ array(
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'baz.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'qux.igor.io'),
+ new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
+ new Record('qux.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
+ ),
+ 'igor.io',
+ ),
+ );
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/Resolver/ResolverTest.php b/instafeed/vendor/react/dns/tests/Resolver/ResolverTest.php
new file mode 100755
index 0000000..661386d
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/Resolver/ResolverTest.php
@@ -0,0 +1,251 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableOnceWith('178.79.169.131'));
+ }
+
+ /** @test */
+ public function resolveAllShouldQueryGivenRecords()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
+ }
+
+ /** @test */
+ public function resolveAllShouldIgnoreRecordsWithOtherTypes()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, Message::TYPE_TXT, $query->class, 3600, array('ignored'));
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
+ }
+
+ /** @test */
+ public function resolveAllShouldReturnMultipleValuesForAlias()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, Message::TYPE_CNAME, $query->class, 3600, 'example.com');
+ $response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::1');
+ $response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::2');
+ $response->prepare();
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(
+ $this->expectCallableOnceWith($this->equalTo(array('::1', '::2')))
+ );
+ }
+
+ /** @test */
+ public function resolveShouldQueryARecordsAndIgnoreCase()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class);
+ $response->answers[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('blog.wyrihaximus.net')->then($this->expectCallableOnceWith('178.79.169.131'));
+ }
+
+ /** @test */
+ public function resolveShouldFilterByName()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record('foo.bar', $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException'));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
+ }
+
+ /**
+ * @test
+ */
+ public function resolveWithNoAnswersShouldCallErrbackIfGiven()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->callback(function ($param) {
+ return ($param instanceof RecordNotFoundException && $param->getCode() === 0 && $param->getMessage() === 'DNS query for igor.io did not return a valid answer (NOERROR / NODATA)');
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
+ }
+
+ public function provideRcodeErrors()
+ {
+ return array(
+ array(
+ Message::RCODE_FORMAT_ERROR,
+ 'DNS query for example.com returned an error response (Format Error)',
+ ),
+ array(
+ Message::RCODE_SERVER_FAILURE,
+ 'DNS query for example.com returned an error response (Server Failure)',
+ ),
+ array(
+ Message::RCODE_NAME_ERROR,
+ 'DNS query for example.com returned an error response (Non-Existent Domain / NXDOMAIN)'
+ ),
+ array(
+ Message::RCODE_NOT_IMPLEMENTED,
+ 'DNS query for example.com returned an error response (Not Implemented)'
+ ),
+ array(
+ Message::RCODE_REFUSED,
+ 'DNS query for example.com returned an error response (Refused)'
+ ),
+ array(
+ 99,
+ 'DNS query for example.com returned an error response (Unknown error response code 99)'
+ )
+ );
+ }
+
+ /**
+ * @test
+ * @dataProvider provideRcodeErrors
+ */
+ public function resolveWithRcodeErrorShouldCallErrbackIfGiven($code, $expectedMessage)
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) use ($code) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->header->set('rcode', $code);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->callback(function ($param) use ($code, $expectedMessage) {
+ return ($param instanceof RecordNotFoundException && $param->getCode() === $code && $param->getMessage() === $expectedMessage);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('example.com')->then($this->expectCallableNever(), $errback);
+ }
+
+ public function testLegacyExtractAddress()
+ {
+ $executor = $this->createExecutorMock();
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $response = Message::createResponseWithAnswersForQuery($query, array(
+ new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '1.2.3.4')
+ ));
+
+ $ret = $resolver->extractAddress($query, $response);
+ $this->assertEquals('1.2.3.4', $ret);
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/dns/tests/TestCase.php b/instafeed/vendor/react/dns/tests/TestCase.php
new file mode 100755
index 0000000..a5a22bf
--- /dev/null
+++ b/instafeed/vendor/react/dns/tests/TestCase.php
@@ -0,0 +1,61 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Dns\CallableStub')->getMock();
+ }
+
+ public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
+ {
+ if (method_exists($this, 'expectException')) {
+ // PHPUnit 5
+ $this->expectException($exception);
+ if ($exceptionMessage !== '') {
+ $this->expectExceptionMessage($exceptionMessage);
+ }
+ if ($exceptionCode !== null) {
+ $this->expectExceptionCode($exceptionCode);
+ }
+ } else {
+ // legacy PHPUnit 4
+ parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
+ }
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/.gitignore b/instafeed/vendor/react/event-loop/.gitignore
new file mode 100755
index 0000000..81b9258
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor
diff --git a/instafeed/vendor/react/event-loop/.travis.yml b/instafeed/vendor/react/event-loop/.travis.yml
new file mode 100755
index 0000000..0b7ce2c
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/.travis.yml
@@ -0,0 +1,27 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - hhvm
+
+sudo: false
+
+addons:
+ apt:
+ packages:
+ - libevent-dev # Used by 'event' and 'libevent' PHP extensions
+
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+
+install:
+ - ./travis-init.sh
+ - composer install
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/instafeed/vendor/react/event-loop/CHANGELOG.md b/instafeed/vendor/react/event-loop/CHANGELOG.md
new file mode 100755
index 0000000..dd5d467
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/CHANGELOG.md
@@ -0,0 +1,79 @@
+# Changelog
+
+## 0.4.3 (2017-04-27)
+
+* Bug fix: Bugfix in the usage sample code #57 (@dandelionred)
+* Improvement: Remove branch-alias definition #53 (@WyriHaximus)
+* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd)
+* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder)
+* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley)
+* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue)
+* Improvement: Travis improvements (backported from #74) #75 (@clue)
+* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder)
+* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder)
+* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek)
+* Improvement: Readme cleanup #89 (@jsor)
+* Improvement: Restructure and improve README #90 (@jsor)
+* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor)
+
+## 0.4.2 (2016-03-07)
+
+* Bug fix: No longer error when signals sent to StreamSelectLoop
+* Support HHVM and PHP7 (@ondrejmirtes, @cebe)
+* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades)
+* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino)
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue)
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25)
+
+## 0.4.0 (2014-02-02)
+
+* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc)
+* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc)
+* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc)
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: New method: `EventLoopInterface::nextTick()`
+* BC break: New method: `EventLoopInterface::futureTick()`
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: No error on removing non-existent streams (@clue)
+* Bug fix: Do not silently remove feof listeners in `LibEvLoop`
+
+## 0.3.0 (2013-04-14)
+
+* BC break: New timers API (@nrk)
+* BC break: Remove check on return value from stream callbacks (@nrk)
+
+## 0.2.7 (2013-01-05)
+
+* Bug fix: Fix libevent timers with PHP 5.3
+* Bug fix: Fix libevent timer cancellation (@nrk)
+
+## 0.2.6 (2012-12-26)
+
+* Bug fix: Plug memory issue in libevent timers (@cameronjacobson)
+* Bug fix: Correctly pause LibEvLoop on stop()
+
+## 0.2.3 (2012-11-14)
+
+* Feature: LibEvLoop, integration of `php-libev`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Version bump
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/instafeed/vendor/react/event-loop/LICENSE b/instafeed/vendor/react/event-loop/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/react/event-loop/README.md b/instafeed/vendor/react/event-loop/README.md
new file mode 100755
index 0000000..d4d4c97
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/README.md
@@ -0,0 +1,143 @@
+# EventLoop Component
+
+[](https://travis-ci.org/reactphp/event-loop)
+[](https://codeclimate.com/github/reactphp/event-loop)
+
+Event loop abstraction layer that libraries can use for evented I/O.
+
+In order for async based libraries to be interoperable, they need to use the
+same event loop. This component provides a common `LoopInterface` that any
+library can target. This allows them to be used in the same loop, with one
+single `run()` call that is controlled by the user.
+
+**Table of Contents**
+
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+* [Loop implementations](#loop-implementations)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Quickstart example
+
+Here is an async HTTP server built with just the event loop.
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$server = stream_socket_server('tcp://127.0.0.1:8080');
+stream_set_blocking($server, 0);
+
+$loop->addReadStream($server, function ($server) use ($loop) {
+ $conn = stream_socket_accept($server);
+ $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n";
+ $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) {
+ $written = fwrite($conn, $data);
+ if ($written === strlen($data)) {
+ fclose($conn);
+ $loop->removeStream($conn);
+ } else {
+ $data = substr($data, $written);
+ }
+ });
+});
+
+$loop->addPeriodicTimer(5, function () {
+ $memory = memory_get_usage() / 1024;
+ $formatted = number_format($memory, 3).'K';
+ echo "Current memory usage: {$formatted}\n";
+});
+
+$loop->run();
+```
+
+## Usage
+
+Typical applications use a single event loop which is created at the beginning
+and run at the end of the program.
+
+```php
+// [1]
+$loop = React\EventLoop\Factory::create();
+
+// [2]
+$loop->addPeriodicTimer(1, function () {
+ echo "Tick\n";
+});
+
+$stream = new React\Stream\ReadableResourceStream(
+ fopen('file.txt', 'r'),
+ $loop
+);
+
+// [3]
+$loop->run();
+```
+
+1. The loop instance is created at the beginning of the program. A convenience
+ factory `React\EventLoop\Factory::create()` is provided by this library which
+ picks the best available [loop implementation](#loop-implementations).
+2. The loop instance is used directly or passed to library and application code.
+ In this example, a periodic timer is registered with the event loop which
+ simply outputs `Tick` every second and a
+ [readable stream](https://github.com/reactphp/stream#readableresourcestream)
+ is created by using ReactPHP's
+ [stream component](https://github.com/reactphp/stream) for demonstration
+ purposes.
+3. The loop is run with a single `$loop->run()` call at the end of the program.
+
+## Loop implementations
+
+In addition to the interface there are the following implementations provided:
+
+* `StreamSelectLoop`: This is the only implementation which works out of the
+ box with PHP. It does a simple `select` system call. It's not the most
+ performant of loops, but still does the job quite well.
+
+* `LibEventLoop`: This uses the `libevent` pecl extension. `libevent` itself
+ supports a number of system-specific backends (epoll, kqueue).
+
+* `LibEvLoop`: This uses the `libev` pecl extension
+ ([github](https://github.com/m4rw3r/php-libev)). It supports the same
+ backends as libevent.
+
+* `ExtEventLoop`: This uses the `event` pecl extension. It supports the same
+ backends as libevent.
+
+All of the loops support these features:
+
+* File descriptor polling
+* One-off timers
+* Periodic timers
+* Deferred execution of callbacks
+
+## Install
+
+The recommended way to install this library is [through Composer](http://getcomposer.org).
+[New to Composer?](http://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/event-loop
+```
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](http://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/instafeed/vendor/react/event-loop/composer.json b/instafeed/vendor/react/event-loop/composer.json
new file mode 100755
index 0000000..5001a9c
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "react/event-loop",
+ "description": "Event loop abstraction layer that libraries can use for evented I/O.",
+ "keywords": ["event-loop", "asynchronous"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "suggest": {
+ "ext-libevent": ">=0.1.0",
+ "ext-event": "~1.0",
+ "ext-libev": "*"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src"
+ }
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/phpunit.xml.dist b/instafeed/vendor/react/event-loop/phpunit.xml.dist
new file mode 100755
index 0000000..cba6d4d
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/instafeed/vendor/react/event-loop/src/ExtEventLoop.php b/instafeed/vendor/react/event-loop/src/ExtEventLoop.php
new file mode 100755
index 0000000..4f4b998
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/src/ExtEventLoop.php
@@ -0,0 +1,326 @@
+eventBase = new EventBase($config);
+ $this->nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timerEvents = new SplObjectStorage();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readListeners[$key])) {
+ $this->readListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, Event::READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeListeners[$key])) {
+ $this->writeListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, Event::WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ unset($this->readListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, Event::READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ unset($this->writeListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, Event::WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $this->streamEvents[$key]->free();
+
+ unset(
+ $this->streamFlags[$key],
+ $this->streamEvents[$key],
+ $this->readListeners[$key],
+ $this->writeListeners[$key]
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->isTimerActive($timer)) {
+ $this->timerEvents[$timer]->free();
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timerEvents->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ // @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226
+ @$this->eventBase->loop(EventBase::LOOP_ONCE | EventBase::LOOP_NONBLOCK);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $flags = EventBase::LOOP_ONCE;
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventBase::LOOP_NONBLOCK;
+ } elseif (!$this->streamEvents && !$this->timerEvents->count()) {
+ break;
+ }
+
+ $this->eventBase->loop($flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $flags = Event::TIMEOUT;
+
+ if ($timer->isPeriodic()) {
+ $flags |= Event::PERSIST;
+ }
+
+ $event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
+ $this->timerEvents[$timer] = $event;
+
+ $event->add($timer->getInterval());
+ }
+
+ /**
+ * Create a new ext-event Event object, or update the existing one.
+ *
+ * @param resource $stream
+ * @param integer $flag Event::READ or Event::WRITE
+ */
+ private function subscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $event = $this->streamEvents[$key];
+ $flags = ($this->streamFlags[$key] |= $flag);
+
+ $event->del();
+ $event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
+ } else {
+ $event = new Event($this->eventBase, $stream, Event::PERSIST | $flag, $this->streamCallback);
+
+ $this->streamEvents[$key] = $event;
+ $this->streamFlags[$key] = $flag;
+ }
+
+ $event->add();
+ }
+
+ /**
+ * Update the ext-event Event object for this stream to stop listening to
+ * the given event type, or remove it entirely if it's no longer needed.
+ *
+ * @param resource $stream
+ * @param integer $flag Event::READ or Event::WRITE
+ */
+ private function unsubscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ $flags = $this->streamFlags[$key] &= ~$flag;
+
+ if (0 === $flags) {
+ $this->removeStream($stream);
+
+ return;
+ }
+
+ $event = $this->streamEvents[$key];
+
+ $event->del();
+ $event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
+ $event->add();
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $this->timerCallback = function ($_, $__, $timer) {
+ call_user_func($timer->getCallback(), $timer);
+
+ if (!$timer->isPeriodic() && $this->isTimerActive($timer)) {
+ $this->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $this->streamCallback = function ($stream, $flags) {
+ $key = (int) $stream;
+
+ if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream, $this);
+ }
+
+ if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream, $this);
+ }
+ };
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/src/Factory.php b/instafeed/vendor/react/event-loop/src/Factory.php
new file mode 100755
index 0000000..9a481e3
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/src/Factory.php
@@ -0,0 +1,21 @@
+loop = new EventLoop();
+ $this->nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timerEvents = new SplObjectStorage();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ if (isset($this->readEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ call_user_func($listener, $stream, $this);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::READ);
+ $this->loop->add($event);
+
+ $this->readEvents[(int) $stream] = $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ if (isset($this->writeEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ call_user_func($listener, $stream, $this);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::WRITE);
+ $this->loop->add($event);
+
+ $this->writeEvents[(int) $stream] = $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readEvents[$key])) {
+ $this->readEvents[$key]->stop();
+ unset($this->readEvents[$key]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeEvents[$key])) {
+ $this->writeEvents[$key]->stop();
+ unset($this->writeEvents[$key]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $this->removeReadStream($stream);
+ $this->removeWriteStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $callback = function () use ($timer) {
+ call_user_func($timer->getCallback(), $timer);
+
+ if ($this->isTimerActive($timer)) {
+ $this->cancelTimer($timer);
+ }
+ };
+
+ $event = new TimerEvent($callback, $timer->getInterval());
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $callback = function () use ($timer) {
+ call_user_func($timer->getCallback(), $timer);
+ };
+
+ $event = new TimerEvent($callback, $interval, $interval);
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if (isset($this->timerEvents[$timer])) {
+ $this->loop->remove($this->timerEvents[$timer]);
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timerEvents->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $this->loop->run(EventLoop::RUN_ONCE | EventLoop::RUN_NOWAIT);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $flags = EventLoop::RUN_ONCE;
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventLoop::RUN_NOWAIT;
+ } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) {
+ break;
+ }
+
+ $this->loop->run($flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/src/LibEventLoop.php b/instafeed/vendor/react/event-loop/src/LibEventLoop.php
new file mode 100755
index 0000000..99417a1
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/src/LibEventLoop.php
@@ -0,0 +1,343 @@
+eventBase = event_base_new();
+ $this->nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timerEvents = new SplObjectStorage();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readListeners[$key])) {
+ $this->readListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, EV_READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeListeners[$key])) {
+ $this->writeListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, EV_WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ unset($this->readListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, EV_READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ unset($this->writeListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, EV_WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $event = $this->streamEvents[$key];
+
+ event_del($event);
+ event_free($event);
+
+ unset(
+ $this->streamFlags[$key],
+ $this->streamEvents[$key],
+ $this->readListeners[$key],
+ $this->writeListeners[$key]
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->isTimerActive($timer)) {
+ $event = $this->timerEvents[$timer];
+
+ event_del($event);
+ event_free($event);
+
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timerEvents->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ event_base_loop($this->eventBase, EVLOOP_ONCE | EVLOOP_NONBLOCK);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $flags = EVLOOP_ONCE;
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EVLOOP_NONBLOCK;
+ } elseif (!$this->streamEvents && !$this->timerEvents->count()) {
+ break;
+ }
+
+ event_base_loop($this->eventBase, $flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $this->timerEvents[$timer] = $event = event_timer_new();
+
+ event_timer_set($event, $this->timerCallback, $timer);
+ event_base_set($event, $this->eventBase);
+ event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
+ }
+
+ /**
+ * Create a new ext-libevent event resource, or update the existing one.
+ *
+ * @param resource $stream
+ * @param integer $flag EV_READ or EV_WRITE
+ */
+ private function subscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $event = $this->streamEvents[$key];
+ $flags = $this->streamFlags[$key] |= $flag;
+
+ event_del($event);
+ event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
+ } else {
+ $event = event_new();
+
+ event_set($event, $stream, EV_PERSIST | $flag, $this->streamCallback);
+ event_base_set($event, $this->eventBase);
+
+ $this->streamEvents[$key] = $event;
+ $this->streamFlags[$key] = $flag;
+ }
+
+ event_add($event);
+ }
+
+ /**
+ * Update the ext-libevent event resource for this stream to stop listening to
+ * the given event type, or remove it entirely if it's no longer needed.
+ *
+ * @param resource $stream
+ * @param integer $flag EV_READ or EV_WRITE
+ */
+ private function unsubscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ $flags = $this->streamFlags[$key] &= ~$flag;
+
+ if (0 === $flags) {
+ $this->removeStream($stream);
+
+ return;
+ }
+
+ $event = $this->streamEvents[$key];
+
+ event_del($event);
+ event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
+ event_add($event);
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $this->timerCallback = function ($_, $__, $timer) {
+ call_user_func($timer->getCallback(), $timer);
+
+ // Timer already cancelled ...
+ if (!$this->isTimerActive($timer)) {
+ return;
+
+ // Reschedule periodic timers ...
+ } elseif ($timer->isPeriodic()) {
+ event_add(
+ $this->timerEvents[$timer],
+ $timer->getInterval() * self::MICROSECONDS_PER_SECOND
+ );
+
+ // Clean-up one shot timers ...
+ } else {
+ $this->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $this->streamCallback = function ($stream, $flags) {
+ $key = (int) $stream;
+
+ if (EV_READ === (EV_READ & $flags) && isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream, $this);
+ }
+
+ if (EV_WRITE === (EV_WRITE & $flags) && isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream, $this);
+ }
+ };
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/src/LoopInterface.php b/instafeed/vendor/react/event-loop/src/LoopInterface.php
new file mode 100755
index 0000000..d046526
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/src/LoopInterface.php
@@ -0,0 +1,121 @@
+nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timers = new Timers();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readStreams[$key])) {
+ $this->readStreams[$key] = $stream;
+ $this->readListeners[$key] = $listener;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeStreams[$key])) {
+ $this->writeStreams[$key] = $stream;
+ $this->writeListeners[$key] = $listener;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->readStreams[$key],
+ $this->readListeners[$key]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->writeStreams[$key],
+ $this->writeListeners[$key]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $this->removeReadStream($stream);
+ $this->removeWriteStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ $this->timers->cancel($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timers->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $this->timers->tick();
+
+ $this->waitForStreamActivity(0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $this->timers->tick();
+
+ // Next-tick or future-tick queues have pending callbacks ...
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $timeout = 0;
+
+ // There is a pending timer, only block until it is due ...
+ } elseif ($scheduledAt = $this->timers->getFirst()) {
+ $timeout = $scheduledAt - $this->timers->getTime();
+ if ($timeout < 0) {
+ $timeout = 0;
+ } else {
+ /*
+ * round() needed to correct float error:
+ * https://github.com/reactphp/event-loop/issues/48
+ */
+ $timeout = round($timeout * self::MICROSECONDS_PER_SECOND);
+ }
+
+ // The only possible event is stream activity, so wait forever ...
+ } elseif ($this->readStreams || $this->writeStreams) {
+ $timeout = null;
+
+ // There's nothing left to do ...
+ } else {
+ break;
+ }
+
+ $this->waitForStreamActivity($timeout);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Wait/check for stream activity, or until the next timer is due.
+ */
+ private function waitForStreamActivity($timeout)
+ {
+ $read = $this->readStreams;
+ $write = $this->writeStreams;
+
+ $available = $this->streamSelect($read, $write, $timeout);
+ if (false === $available) {
+ // if a system call has been interrupted,
+ // we cannot rely on it's outcome
+ return;
+ }
+
+ foreach ($read as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream, $this);
+ }
+ }
+
+ foreach ($write as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream, $this);
+ }
+ }
+ }
+
+ /**
+ * Emulate a stream_select() implementation that does not break when passed
+ * empty stream arrays.
+ *
+ * @param array &$read An array of read streams to select upon.
+ * @param array &$write An array of write streams to select upon.
+ * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
+ *
+ * @return integer|false The total number of streams that are ready for read/write.
+ * Can return false if stream_select() is interrupted by a signal.
+ */
+ protected function streamSelect(array &$read, array &$write, $timeout)
+ {
+ if ($read || $write) {
+ $except = null;
+
+ // suppress warnings that occur, when stream_select is interrupted by a signal
+ return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
+ }
+
+ $timeout && usleep($timeout);
+
+ return 0;
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/src/Tick/FutureTickQueue.php b/instafeed/vendor/react/event-loop/src/Tick/FutureTickQueue.php
new file mode 100755
index 0000000..eeffd36
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/src/Tick/FutureTickQueue.php
@@ -0,0 +1,59 @@
+eventLoop = $eventLoop;
+ $this->queue = new SplQueue();
+ }
+
+ /**
+ * Add a callback to be invoked on a future tick of the event loop.
+ *
+ * Callbacks are guaranteed to be executed in the order they are enqueued.
+ *
+ * @param callable $listener The callback to invoke.
+ */
+ public function add(callable $listener)
+ {
+ $this->queue->enqueue($listener);
+ }
+
+ /**
+ * Flush the callback queue.
+ */
+ public function tick()
+ {
+ // Only invoke as many callbacks as were on the queue when tick() was called.
+ $count = $this->queue->count();
+
+ while ($count--) {
+ call_user_func(
+ $this->queue->dequeue(),
+ $this->eventLoop
+ );
+ }
+ }
+
+ /**
+ * Check if the next tick queue is empty.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->queue->isEmpty();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/src/Tick/NextTickQueue.php b/instafeed/vendor/react/event-loop/src/Tick/NextTickQueue.php
new file mode 100755
index 0000000..5b8e1de
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/src/Tick/NextTickQueue.php
@@ -0,0 +1,57 @@
+eventLoop = $eventLoop;
+ $this->queue = new SplQueue();
+ }
+
+ /**
+ * Add a callback to be invoked on the next tick of the event loop.
+ *
+ * Callbacks are guaranteed to be executed in the order they are enqueued,
+ * before any timer or stream events.
+ *
+ * @param callable $listener The callback to invoke.
+ */
+ public function add(callable $listener)
+ {
+ $this->queue->enqueue($listener);
+ }
+
+ /**
+ * Flush the callback queue.
+ */
+ public function tick()
+ {
+ while (!$this->queue->isEmpty()) {
+ call_user_func(
+ $this->queue->dequeue(),
+ $this->eventLoop
+ );
+ }
+ }
+
+ /**
+ * Check if the next tick queue is empty.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->queue->isEmpty();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/src/Timer/Timer.php b/instafeed/vendor/react/event-loop/src/Timer/Timer.php
new file mode 100755
index 0000000..f670ab3
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/src/Timer/Timer.php
@@ -0,0 +1,102 @@
+loop = $loop;
+ $this->interval = (float) $interval;
+ $this->callback = $callback;
+ $this->periodic = (bool) $periodic;
+ $this->data = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLoop()
+ {
+ return $this->loop;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInterval()
+ {
+ return $this->interval;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCallback()
+ {
+ return $this->callback;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setData($data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isPeriodic()
+ {
+ return $this->periodic;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isActive()
+ {
+ return $this->loop->isTimerActive($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancel()
+ {
+ $this->loop->cancelTimer($this);
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/src/Timer/TimerInterface.php b/instafeed/vendor/react/event-loop/src/Timer/TimerInterface.php
new file mode 100755
index 0000000..d066f36
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/src/Timer/TimerInterface.php
@@ -0,0 +1,62 @@
+timers = new SplObjectStorage();
+ $this->scheduler = new SplPriorityQueue();
+ }
+
+ public function updateTime()
+ {
+ return $this->time = microtime(true);
+ }
+
+ public function getTime()
+ {
+ return $this->time ?: $this->updateTime();
+ }
+
+ public function add(TimerInterface $timer)
+ {
+ $interval = $timer->getInterval();
+ $scheduledAt = $interval + microtime(true);
+
+ $this->timers->attach($timer, $scheduledAt);
+ $this->scheduler->insert($timer, -$scheduledAt);
+ }
+
+ public function contains(TimerInterface $timer)
+ {
+ return $this->timers->contains($timer);
+ }
+
+ public function cancel(TimerInterface $timer)
+ {
+ $this->timers->detach($timer);
+ }
+
+ public function getFirst()
+ {
+ while ($this->scheduler->count()) {
+ $timer = $this->scheduler->top();
+
+ if ($this->timers->contains($timer)) {
+ return $this->timers[$timer];
+ }
+
+ $this->scheduler->extract();
+ }
+
+ return null;
+ }
+
+ public function isEmpty()
+ {
+ return count($this->timers) === 0;
+ }
+
+ public function tick()
+ {
+ $time = $this->updateTime();
+ $timers = $this->timers;
+ $scheduler = $this->scheduler;
+
+ while (!$scheduler->isEmpty()) {
+ $timer = $scheduler->top();
+
+ if (!isset($timers[$timer])) {
+ $scheduler->extract();
+ $timers->detach($timer);
+
+ continue;
+ }
+
+ if ($timers[$timer] >= $time) {
+ break;
+ }
+
+ $scheduler->extract();
+ call_user_func($timer->getCallback(), $timer);
+
+ if ($timer->isPeriodic() && isset($timers[$timer])) {
+ $timers[$timer] = $scheduledAt = $timer->getInterval() + $time;
+ $scheduler->insert($timer, -$scheduledAt);
+ } else {
+ $timers->detach($timer);
+ }
+ }
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/AbstractLoopTest.php b/instafeed/vendor/react/event-loop/tests/AbstractLoopTest.php
new file mode 100755
index 0000000..bd6bb83
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/AbstractLoopTest.php
@@ -0,0 +1,539 @@
+tickTimeout = defined('HHVM_VERSION') ? 0.02 : 0.005;
+ $this->loop = $this->createLoop();
+ }
+
+ abstract public function createLoop();
+
+ public function createSocketPair()
+ {
+ $domain = (DIRECTORY_SEPARATOR === '\\') ? STREAM_PF_INET : STREAM_PF_UNIX;
+ $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+
+ foreach ($sockets as $socket) {
+ if (function_exists('stream_set_read_buffer')) {
+ stream_set_read_buffer($socket, 0);
+ }
+ }
+
+ return $sockets;
+ }
+
+ public function testAddReadStream()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testAddReadStreamIgnoresSecondCallable()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testAddWriteStream()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
+ $this->loop->tick();
+ $this->loop->tick();
+ }
+
+ public function testAddWriteStreamIgnoresSecondCallable()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->tick();
+ $this->loop->tick();
+ }
+
+ public function testRemoveReadStreamInstantly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveReadStreamAfterReading()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveWriteStreamInstantly()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->removeWriteStream($input);
+ $this->loop->tick();
+ }
+
+ public function testRemoveWriteStreamAfterWriting()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableOnce());
+ $this->loop->tick();
+
+ $this->loop->removeWriteStream($input);
+ $this->loop->tick();
+ }
+
+ public function testRemoveStreamInstantly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->removeStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveStreamForReadOnly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->addWriteStream($output, $this->expectCallableOnce());
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveStreamForWriteOnly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ fwrite($output, "foo\n");
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+ $this->loop->addWriteStream($output, $this->expectCallableNever());
+ $this->loop->removeWriteStream($output);
+
+ $this->loop->tick();
+ }
+
+ public function testRemoveStream()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+ $this->loop->addWriteStream($input, $this->expectCallableOnce());
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+
+ $this->loop->removeStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveInvalid()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ // remove a valid stream from the event loop that was never added in the first place
+ $this->loop->removeReadStream($stream);
+ $this->loop->removeWriteStream($stream);
+ $this->loop->removeStream($stream);
+ }
+
+ /** @test */
+ public function emptyRunShouldSimplyReturn()
+ {
+ $this->assertRunFasterThan($this->tickTimeout);
+ }
+
+ /** @test */
+ public function runShouldReturnWhenNoMoreFds()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addReadStream($input, function ($stream) use ($loop) {
+ $loop->removeStream($stream);
+ });
+
+ fwrite($output, "foo\n");
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ /** @test */
+ public function stopShouldStopRunningLoop()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addReadStream($input, function ($stream) use ($loop) {
+ $loop->stop();
+ });
+
+ fwrite($output, "foo\n");
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ public function testStopShouldPreventRunFromBlocking()
+ {
+ $this->loop->addTimer(
+ 1,
+ function () {
+ $this->fail('Timer was executed.');
+ }
+ );
+
+ $this->loop->nextTick(
+ function () {
+ $this->loop->stop();
+ }
+ );
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ public function testIgnoreRemovedCallback()
+ {
+ // two independent streams, both should be readable right away
+ list ($input1, $output1) = $this->createSocketPair();
+ list ($input2, $output2) = $this->createSocketPair();
+
+ $called = false;
+
+ $loop = $this->loop;
+ $loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) {
+ // stream1 is readable, remove stream2 as well => this will invalidate its callback
+ $loop->removeReadStream($stream);
+ $loop->removeReadStream($input2);
+
+ $called = true;
+ });
+
+ // this callback would have to be called as well, but the first stream already removed us
+ $loop->addReadStream($input2, function () use (& $called) {
+ if ($called) {
+ $this->fail('Callback 2 must not be called after callback 1 was called');
+ }
+ });
+
+ fwrite($output1, "foo\n");
+ fwrite($output2, "foo\n");
+
+ $loop->run();
+
+ $this->assertTrue($called);
+ }
+
+ public function testNextTick()
+ {
+ $called = false;
+
+ $callback = function ($loop) use (&$called) {
+ $this->assertSame($this->loop, $loop);
+ $called = true;
+ };
+
+ $this->loop->nextTick($callback);
+
+ $this->assertFalse($called);
+
+ $this->loop->tick();
+
+ $this->assertTrue($called);
+ }
+
+ public function testNextTickFiresBeforeIO()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->loop->tick();
+ }
+
+ public function testRecursiveNextTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->nextTick(
+ function () {
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->loop->tick();
+ }
+
+ public function testRunWaitsForNextTickEvents()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream) {
+ $this->loop->removeStream($stream);
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testNextTickEventGeneratedByFutureTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->futureTick(
+ function () {
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testNextTickEventGeneratedByTimer()
+ {
+ $this->loop->addTimer(
+ 0.001,
+ function () {
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTick()
+ {
+ $called = false;
+
+ $callback = function ($loop) use (&$called) {
+ $this->assertSame($this->loop, $loop);
+ $called = true;
+ };
+
+ $this->loop->futureTick($callback);
+
+ $this->assertFalse($called);
+
+ $this->loop->tick();
+
+ $this->assertTrue($called);
+ }
+
+ public function testFutureTickFiresBeforeIO()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->loop->tick();
+ }
+
+ public function testRecursiveFutureTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream) {
+ echo 'stream' . PHP_EOL;
+ $this->loop->removeWriteStream($stream);
+ }
+ );
+
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick-1' . PHP_EOL;
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick-2' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick-1' . PHP_EOL . 'stream' . PHP_EOL . 'future-tick-2' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testRunWaitsForFutureTickEvents()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream) {
+ $this->loop->removeStream($stream);
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTickEventGeneratedByNextTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->nextTick(
+ function () {
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTickEventGeneratedByTimer()
+ {
+ $this->loop->addTimer(
+ 0.001,
+ function () {
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ private function assertRunFasterThan($maxInterval)
+ {
+ $start = microtime(true);
+
+ $this->loop->run();
+
+ $end = microtime(true);
+ $interval = $end - $start;
+
+ $this->assertLessThan($maxInterval, $interval);
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/CallableStub.php b/instafeed/vendor/react/event-loop/tests/CallableStub.php
new file mode 100755
index 0000000..913d403
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/CallableStub.php
@@ -0,0 +1,10 @@
+markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
+ }
+
+ if (!extension_loaded('event')) {
+ $this->markTestSkipped('ext-event tests skipped because ext-event is not installed.');
+ }
+
+ $cfg = null;
+ if ($readStreamCompatible) {
+ $cfg = new \EventConfig();
+ $cfg->requireFeatures(\EventConfig::FEATURE_FDS);
+ }
+
+ return new ExtEventLoop($cfg);
+ }
+
+ public function createStream()
+ {
+ // Use a FIFO on linux to get around lack of support for disk-based file
+ // descriptors when using the EPOLL back-end.
+ if ('Linux' === PHP_OS) {
+ $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
+
+ unlink($this->fifoPath);
+
+ posix_mkfifo($this->fifoPath, 0600);
+
+ $stream = fopen($this->fifoPath, 'r+');
+
+ // ext-event (as of 1.8.1) does not yet support in-memory temporary
+ // streams. Setting maxmemory:0 and performing a write forces PHP to
+ // back this temporary stream with a real file.
+ //
+ // This problem is mentioned at https://bugs.php.net/bug.php?id=64652&edit=3
+ // but remains unresolved (despite that issue being closed).
+ } else {
+ $stream = fopen('php://temp/maxmemory:0', 'r+');
+
+ fwrite($stream, 'x');
+ ftruncate($stream, 0);
+ }
+
+ return $stream;
+ }
+
+ public function writeToStream($stream, $content)
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::writeToStream($stream, $content);
+ }
+
+ fwrite($stream, $content);
+ }
+
+ /**
+ * @group epoll-readable-error
+ */
+ public function testCanUseReadableStreamWithFeatureFds()
+ {
+ if (PHP_VERSION_ID > 70000) {
+ $this->markTestSkipped('Memory stream not supported');
+ }
+
+ $this->loop = $this->createLoop(true);
+
+ $input = fopen('php://temp/maxmemory:0', 'r+');
+
+ fwrite($input, 'x');
+ ftruncate($input, 0);
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+
+ fwrite($input, "foo\n");
+ $this->loop->tick();
+
+ fwrite($input, "bar\n");
+ $this->loop->tick();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/LibEvLoopTest.php b/instafeed/vendor/react/event-loop/tests/LibEvLoopTest.php
new file mode 100755
index 0000000..5ea98e3
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/LibEvLoopTest.php
@@ -0,0 +1,22 @@
+markTestSkipped('libev tests skipped because ext-libev is not installed.');
+ }
+
+ return new LibEvLoop();
+ }
+
+ public function testLibEvConstructor()
+ {
+ $loop = new LibEvLoop();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/LibEventLoopTest.php b/instafeed/vendor/react/event-loop/tests/LibEventLoopTest.php
new file mode 100755
index 0000000..920b33c
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/LibEventLoopTest.php
@@ -0,0 +1,58 @@
+markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
+ }
+
+ if (!function_exists('event_base_new')) {
+ $this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
+ }
+
+ return new LibEventLoop();
+ }
+
+ public function tearDown()
+ {
+ if (file_exists($this->fifoPath)) {
+ unlink($this->fifoPath);
+ }
+ }
+
+ public function createStream()
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::createStream();
+ }
+
+ $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
+
+ unlink($this->fifoPath);
+
+ // Use a FIFO on linux to get around lack of support for disk-based file
+ // descriptors when using the EPOLL back-end.
+ posix_mkfifo($this->fifoPath, 0600);
+
+ $stream = fopen($this->fifoPath, 'r+');
+
+ return $stream;
+ }
+
+ public function writeToStream($stream, $content)
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::writeToStream($stream, $content);
+ }
+
+ fwrite($stream, $content);
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/StreamSelectLoopTest.php b/instafeed/vendor/react/event-loop/tests/StreamSelectLoopTest.php
new file mode 100755
index 0000000..d2e3e07
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/StreamSelectLoopTest.php
@@ -0,0 +1,179 @@
+getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
+ $this->resetSignalHandlers();
+ }
+ }
+
+ public function createLoop()
+ {
+ return new StreamSelectLoop();
+ }
+
+ public function testStreamSelectTimeoutEmulation()
+ {
+ $this->loop->addTimer(
+ 0.05,
+ $this->expectCallableOnce()
+ );
+
+ $start = microtime(true);
+
+ $this->loop->run();
+
+ $end = microtime(true);
+ $interval = $end - $start;
+
+ $this->assertGreaterThan(0.04, $interval);
+ }
+
+ public function signalProvider()
+ {
+ return [
+ ['SIGUSR1'],
+ ['SIGHUP'],
+ ['SIGTERM'],
+ ];
+ }
+
+ private $_signalHandled = false;
+
+ /**
+ * Test signal interrupt when no stream is attached to the loop
+ * @dataProvider signalProvider
+ */
+ public function testSignalInterruptNoStream($signal)
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('"pcntl" extension is required to run this test.');
+ }
+
+ // dispatch signal handler once before signal is sent and once after
+ $this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); });
+ $this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); });
+ if (defined('HHVM_VERSION')) {
+ // hhvm startup is slow so we need to add another handler much later
+ $this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); });
+ }
+
+ $this->setUpSignalHandler($signal);
+
+ // spawn external process to send signal to current process id
+ $this->forkSendSignal($signal);
+ $this->loop->run();
+ $this->assertTrue($this->_signalHandled);
+ }
+
+ /**
+ * Test signal interrupt when a stream is attached to the loop
+ * @dataProvider signalProvider
+ */
+ public function testSignalInterruptWithStream($signal)
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('"pcntl" extension is required to run this test.');
+ }
+
+ // dispatch signal handler every 10ms
+ $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); });
+
+ // add stream to the loop
+ list($writeStream, $readStream) = $this->createSocketPair();
+ $this->loop->addReadStream($readStream, function($stream, $loop) {
+ /** @var $loop LoopInterface */
+ $read = fgets($stream);
+ if ($read === "end loop\n") {
+ $loop->stop();
+ }
+ });
+ $this->loop->addTimer(0.05, function() use ($writeStream) {
+ fwrite($writeStream, "end loop\n");
+ });
+
+ $this->setUpSignalHandler($signal);
+
+ // spawn external process to send signal to current process id
+ $this->forkSendSignal($signal);
+
+ $this->loop->run();
+
+ $this->assertTrue($this->_signalHandled);
+ }
+
+ /**
+ * add signal handler for signal
+ */
+ protected function setUpSignalHandler($signal)
+ {
+ $this->_signalHandled = false;
+ $this->assertTrue(pcntl_signal(constant($signal), function() { $this->_signalHandled = true; }));
+ }
+
+ /**
+ * reset all signal handlers to default
+ */
+ protected function resetSignalHandlers()
+ {
+ foreach($this->signalProvider() as $signal) {
+ pcntl_signal(constant($signal[0]), SIG_DFL);
+ }
+ }
+
+ /**
+ * fork child process to send signal to current process id
+ */
+ protected function forkSendSignal($signal)
+ {
+ $currentPid = posix_getpid();
+ $childPid = pcntl_fork();
+ if ($childPid == -1) {
+ $this->fail("Failed to fork child process!");
+ } else if ($childPid === 0) {
+ // this is executed in the child process
+ usleep(20000);
+ posix_kill($currentPid, constant($signal));
+ die();
+ }
+ }
+
+ /**
+ * https://github.com/reactphp/event-loop/issues/48
+ *
+ * Tests that timer with very small interval uses at least 1 microsecond
+ * timeout.
+ */
+ public function testSmallTimerInterval()
+ {
+ /** @var StreamSelectLoop|\PHPUnit_Framework_MockObject_MockObject $loop */
+ $loop = $this->getMock('React\EventLoop\StreamSelectLoop', ['streamSelect']);
+ $loop
+ ->expects($this->at(0))
+ ->method('streamSelect')
+ ->with([], [], 1);
+ $loop
+ ->expects($this->at(1))
+ ->method('streamSelect')
+ ->with([], [], 0);
+
+ $callsCount = 0;
+ $loop->addPeriodicTimer(Timer::MIN_INTERVAL, function() use (&$loop, &$callsCount) {
+ $callsCount++;
+ if ($callsCount == 2) {
+ $loop->stop();
+ }
+ });
+
+ $loop->run();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/TestCase.php b/instafeed/vendor/react/event-loop/tests/TestCase.php
new file mode 100755
index 0000000..5114f4e
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/TestCase.php
@@ -0,0 +1,41 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\EventLoop\CallableStub')->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php b/instafeed/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php
new file mode 100755
index 0000000..5768965
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php
@@ -0,0 +1,97 @@
+createLoop();
+
+ $loop->addTimer(0.001, $this->expectCallableOnce());
+ usleep(1000);
+ $loop->tick();
+ }
+
+ public function testAddPeriodicTimer()
+ {
+ $loop = $this->createLoop();
+
+ $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(3));
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+ }
+
+ public function testAddPeriodicTimerWithCancel()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(2));
+
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+
+ $timer->cancel();
+
+ usleep(1000);
+ $loop->tick();
+ }
+
+ public function testAddPeriodicTimerCancelsItself()
+ {
+ $i = 0;
+
+ $loop = $this->createLoop();
+
+ $loop->addPeriodicTimer(0.001, function ($timer) use (&$i) {
+ $i++;
+
+ if ($i == 2) {
+ $timer->cancel();
+ }
+ });
+
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+
+ $this->assertSame(2, $i);
+ }
+
+ public function testIsTimerActive()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addPeriodicTimer(0.001, function () {});
+
+ $this->assertTrue($loop->isTimerActive($timer));
+
+ $timer->cancel();
+
+ $this->assertFalse($loop->isTimerActive($timer));
+ }
+
+ public function testMinimumIntervalOneMicrosecond()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addTimer(0, function () {});
+
+ $this->assertEquals(0.000001, $timer->getInterval());
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php b/instafeed/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php
new file mode 100755
index 0000000..a7a6d00
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('ext-event tests skipped because ext-event is not installed.');
+ }
+
+ return new ExtEventLoop();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/Timer/LibEvTimerTest.php b/instafeed/vendor/react/event-loop/tests/Timer/LibEvTimerTest.php
new file mode 100755
index 0000000..73abe8e
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/Timer/LibEvTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('libev tests skipped because ext-libev is not installed.');
+ }
+
+ return new LibEvLoop();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/Timer/LibEventTimerTest.php b/instafeed/vendor/react/event-loop/tests/Timer/LibEventTimerTest.php
new file mode 100755
index 0000000..3db2035
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/Timer/LibEventTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
+ }
+
+ return new LibEventLoop();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php b/instafeed/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php
new file mode 100755
index 0000000..cfe1d7d
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php
@@ -0,0 +1,13 @@
+getMockBuilder('React\EventLoop\LoopInterface')
+ ->getMock();
+
+ $timers = new Timers();
+ $timers->tick();
+
+ // simulate a bunch of processing on stream events,
+ // part of which schedules a future timer...
+ sleep(1);
+ $timers->add(new Timer($loop, 0.5, function () {
+ $this->fail("Timer shouldn't be called");
+ }));
+
+ $timers->tick();
+ }
+}
diff --git a/instafeed/vendor/react/event-loop/tests/bootstrap.php b/instafeed/vendor/react/event-loop/tests/bootstrap.php
new file mode 100755
index 0000000..d97d8b7
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/tests/bootstrap.php
@@ -0,0 +1,7 @@
+addPsr4('React\\Tests\\EventLoop\\', __DIR__);
diff --git a/instafeed/vendor/react/event-loop/travis-init.sh b/instafeed/vendor/react/event-loop/travis-init.sh
new file mode 100755
index 0000000..8745601
--- /dev/null
+++ b/instafeed/vendor/react/event-loop/travis-init.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -e
+set -o pipefail
+
+if [[ "$TRAVIS_PHP_VERSION" != "hhvm" &&
+ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then
+
+ # install 'event' PHP extension
+ echo "yes" | pecl install event
+
+ # install 'libevent' PHP extension (does not support php 7)
+ if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
+ "$TRAVIS_PHP_VERSION" != "7.1" ]]; then
+ curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz
+ pushd libevent-0.1.0
+ phpize
+ ./configure
+ make
+ make install
+ popd
+ echo "extension=libevent.so" >> "$(php -r 'echo php_ini_loaded_file();')"
+ fi
+
+ # install 'libev' PHP extension (does not support php 7)
+ if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
+ "$TRAVIS_PHP_VERSION" != "7.1" ]]; then
+ git clone --recursive https://github.com/m4rw3r/php-libev
+ pushd php-libev
+ phpize
+ ./configure --with-libev
+ make
+ make install
+ popd
+ echo "extension=libev.so" >> "$(php -r 'echo php_ini_loaded_file();')"
+ fi
+
+fi
diff --git a/instafeed/vendor/react/promise-timer/.gitignore b/instafeed/vendor/react/promise-timer/.gitignore
new file mode 100755
index 0000000..de4a392
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/.gitignore
@@ -0,0 +1,2 @@
+/vendor
+/composer.lock
diff --git a/instafeed/vendor/react/promise-timer/.travis.yml b/instafeed/vendor/react/promise-timer/.travis.yml
new file mode 100755
index 0000000..a71864a
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/.travis.yml
@@ -0,0 +1,26 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/instafeed/vendor/react/promise-timer/CHANGELOG.md b/instafeed/vendor/react/promise-timer/CHANGELOG.md
new file mode 100755
index 0000000..e5f4ccd
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/CHANGELOG.md
@@ -0,0 +1,63 @@
+# Changelog
+
+## 1.5.1 (2019-03-27)
+
+* Fix: Typo in readme
+ (#35 by @aak74)
+
+* Improvement: Only include functions file when functions aren't defined
+ (#36 by @Niko9911)
+
+## 1.5.0 (2018-06-13)
+
+* Feature: Improve memory consumption by cleaning up garbage references to pending promise without canceller.
+ (#34 by @clue)
+
+## 1.4.0 (2018-06-11)
+
+* Feature: Improve memory consumption by cleaning up garbage references.
+ (#33 by @clue)
+
+## 1.3.0 (2018-04-24)
+
+* Feature: Improve memory consumption by cleaning up unneeded references.
+ (#32 by @clue)
+
+## 1.2.1 (2017-12-22)
+
+* README improvements
+ (#28 by @jsor)
+
+* Improve test suite by adding forward compatiblity with PHPUnit 6 and
+ fix test suite forward compatibility with upcoming EventLoop releases
+ (#30 and #31 by @clue)
+
+## 1.2.0 (2017-08-08)
+
+* Feature: Only start timers if input Promise is still pending and
+ return a settled output promise if the input is already settled.
+ (#25 by @clue)
+
+* Feature: Cap minimum timer interval at 1µs across all versions
+ (#23 by @clue)
+
+* Feature: Forward compatibility with EventLoop v1.0 and v0.5
+ (#27 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev and
+ lock Travis distro so new defaults will not break the build
+ (#24 and #26 by @clue)
+
+## 1.1.1 (2016-12-27)
+
+* Improve test suite to use PSR-4 autoloader and proper namespaces.
+ (#21 by @clue)
+
+## 1.1.0 (2016-02-29)
+
+* Feature: Support promise cancellation for all timer primitives
+ (#18 by @clue)
+
+## 1.0.0 (2015-09-29)
+
+* First tagged release
diff --git a/instafeed/vendor/react/promise-timer/LICENSE b/instafeed/vendor/react/promise-timer/LICENSE
new file mode 100755
index 0000000..dc09d1e
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/react/promise-timer/README.md b/instafeed/vendor/react/promise-timer/README.md
new file mode 100755
index 0000000..419a127
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/README.md
@@ -0,0 +1,372 @@
+# PromiseTimer
+
+[](https://travis-ci.org/reactphp/promise-timer)
+
+A trivial implementation of timeouts for `Promise`s, built on top of [ReactPHP](https://reactphp.org/).
+
+**Table of contents**
+
+* [Usage](#usage)
+ * [timeout()](#timeout)
+ * [Timeout cancellation](#timeout-cancellation)
+ * [Cancellation handler](#cancellation-handler)
+ * [Input cancellation](#input-cancellation)
+ * [Output cancellation](#output-cancellation)
+ * [Collections](#collections)
+ * [resolve()](#resolve)
+ * [Resolve cancellation](#resolve-cancellation)
+ * [reject()](#reject)
+ * [Reject cancellation](#reject-cancellation)
+ * [TimeoutException](#timeoutexception)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Usage
+
+This lightweight library consists only of a few simple functions.
+All functions reside under the `React\Promise\Timer` namespace.
+
+The below examples assume you use an import statement similar to this:
+
+```php
+use React\Promise\Timer;
+
+Timer\timeout(…);
+```
+
+Alternatively, you can also refer to them with their fully-qualified name:
+
+```php
+\React\Promise\Timer\timeout(…);
+```
+
+### timeout()
+
+The `timeout(PromiseInterface $promise, $time, LoopInterface $loop)` function
+can be used to *cancel* operations that take *too long*.
+You need to pass in an input `$promise` that represents a pending operation and timeout parameters.
+It returns a new `Promise` with the following resolution behavior:
+
+* If the input `$promise` resolves before `$time` seconds, resolve the resulting promise with its fulfillment value.
+* If the input `$promise` rejects before `$time` seconds, reject the resulting promise with its rejection value.
+* If the input `$promise` does not settle before `$time` seconds, *cancel* the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).
+
+Internally, the given `$time` value will be used to start a timer that will
+*cancel* the pending operation once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+If the input `$promise` is already settled, then the resulting promise will
+resolve or reject immediately without starting a timer at all.
+
+A common use case for handling only resolved values looks like this:
+
+```php
+$promise = accessSomeRemoteResource();
+Timer\timeout($promise, 10.0, $loop)->then(function ($value) {
+ // the operation finished within 10.0 seconds
+});
+```
+
+A more complete example could look like this:
+
+```php
+$promise = accessSomeRemoteResource();
+Timer\timeout($promise, 10.0, $loop)->then(
+ function ($value) {
+ // the operation finished within 10.0 seconds
+ },
+ function ($error) {
+ if ($error instanceof Timer\TimeoutException) {
+ // the operation has failed due to a timeout
+ } else {
+ // the input operation has failed due to some other error
+ }
+ }
+);
+```
+
+Or if you're using [react/promise v2.2.0](https://github.com/reactphp/promise) or up:
+
+```php
+Timer\timeout($promise, 10.0, $loop)
+ ->then(function ($value) {
+ // the operation finished within 10.0 seconds
+ })
+ ->otherwise(function (Timer\TimeoutException $error) {
+ // the operation has failed due to a timeout
+ })
+ ->otherwise(function ($error) {
+ // the input operation has failed due to some other error
+ })
+;
+```
+
+#### Timeout cancellation
+
+As discussed above, the [`timeout()`](#timeout) function will *cancel* the
+underlying operation if it takes *too long*.
+This means that you can be sure the resulting promise will then be rejected
+with a [`TimeoutException`](#timeoutexception).
+
+However, what happens to the underlying input `$promise` is a bit more tricky:
+Once the timer fires, we will try to call
+[`$promise->cancel()`](https://github.com/reactphp/promise#cancellablepromiseinterfacecancel)
+on the input `$promise` which in turn invokes its [cancellation handler](#cancellation-handler).
+
+This means that it's actually up the input `$promise` to handle
+[cancellation support](https://github.com/reactphp/promise#cancellablepromiseinterface).
+
+* A common use case involves cleaning up any resources like open network sockets or
+ file handles or terminating external processes or timers.
+
+* If the given input `$promise` does not support cancellation, then this is a NO-OP.
+ This means that while the resulting promise will still be rejected, the underlying
+ input `$promise` may still be pending and can hence continue consuming resources.
+
+See the following chapter for more details on the cancellation handler.
+
+#### Cancellation handler
+
+For example, an implementation for the above operation could look like this:
+
+```php
+function accessSomeRemoteResource()
+{
+ return new Promise(
+ function ($resolve, $reject) use (&$socket) {
+ // this will be called once the promise is created
+ // a common use case involves opening any resources and eventually resolving
+ $socket = createSocket();
+ $socket->on('data', function ($data) use ($resolve) {
+ $resolve($data);
+ });
+ },
+ function ($resolve, $reject) use (&$socket) {
+ // this will be called once calling `cancel()` on this promise
+ // a common use case involves cleaning any resources and then rejecting
+ $socket->close();
+ $reject(new \RuntimeException('Operation cancelled'));
+ }
+ );
+}
+```
+
+In this example, calling `$promise->cancel()` will invoke the registered cancellation
+handler which then closes the network socket and rejects the `Promise` instance.
+
+If no cancellation handler is passed to the `Promise` constructor, then invoking
+its `cancel()` method it is effectively a NO-OP.
+This means that it may still be pending and can hence continue consuming resources.
+
+For more details on the promise cancellation, please refer to the
+[Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface).
+
+#### Input cancellation
+
+Irrespective of the timeout handling, you can also explicitly `cancel()` the
+input `$promise` at any time.
+This means that the `timeout()` handling does not affect cancellation of the
+input `$promise`, as demonstrated in the following example:
+
+```php
+$promise = accessSomeRemoteResource();
+$timeout = Timer\timeout($promise, 10.0, $loop);
+
+$promise->cancel();
+```
+
+The registered [cancellation handler](#cancellation-handler) is responsible for
+handling the `cancel()` call:
+
+* A described above, a common use involves resource cleanup and will then *reject*
+ the `Promise`.
+ If the input `$promise` is being rejected, then the timeout will be aborted
+ and the resulting promise will also be rejected.
+* If the input `$promise` is still pending, then the timout will continue
+ running until the timer expires.
+ The same happens if the input `$promise` does not register a
+ [cancellation handler](#cancellation-handler).
+
+#### Output cancellation
+
+Similarily, you can also explicitly `cancel()` the resulting promise like this:
+
+```php
+$promise = accessSomeRemoteResource();
+$timeout = Timer\timeout($promise, 10.0, $loop);
+
+$timeout->cancel();
+```
+
+Note how this looks very similar to the above [input cancellation](#input-cancellation)
+example. Accordingly, it also behaves very similar.
+
+Calling `cancel()` on the resulting promise will merely try
+to `cancel()` the input `$promise`.
+This means that we do not take over responsibility of the outcome and it's
+entirely up to the input `$promise` to handle cancellation support.
+
+The registered [cancellation handler](#cancellation-handler) is responsible for
+handling the `cancel()` call:
+
+* As described above, a common use involves resource cleanup and will then *reject*
+ the `Promise`.
+ If the input `$promise` is being rejected, then the timeout will be aborted
+ and the resulting promise will also be rejected.
+* If the input `$promise` is still pending, then the timout will continue
+ running until the timer expires.
+ The same happens if the input `$promise` does not register a
+ [cancellation handler](#cancellation-handler).
+
+To re-iterate, note that calling `cancel()` on the resulting promise will merely
+try to cancel the input `$promise` only.
+It is then up to the cancellation handler of the input promise to settle the promise.
+If the input promise is still pending when the timeout occurs, then the normal
+[timeout cancellation](#timeout-cancellation) handling will trigger, effectively rejecting
+the output promise with a [`TimeoutException`](#timeoutexception).
+
+This is done for consistency with the [timeout cancellation](#timeout-cancellation)
+handling and also because it is assumed this is often used like this:
+
+```php
+$timeout = Timer\timeout(accessSomeRemoteResource(), 10.0, $loop);
+
+$timeout->cancel();
+```
+
+As described above, this example works as expected and cleans up any resources
+allocated for the input `$promise`.
+
+Note that if the given input `$promise` does not support cancellation, then this
+is a NO-OP.
+This means that while the resulting promise will still be rejected after the
+timeout, the underlying input `$promise` may still be pending and can hence
+continue consuming resources.
+
+#### Collections
+
+If you want to wait for multiple promises to resolve, you can use the normal promise primitives like this:
+
+```php
+$promises = array(
+ accessSomeRemoteResource(),
+ accessSomeRemoteResource(),
+ accessSomeRemoteResource()
+);
+
+$promise = \React\Promise\all($promises);
+
+Timer\timeout($promise, 10, $loop)->then(function ($values) {
+ // *all* promises resolved
+});
+```
+
+The applies to all promise collection primitives alike, i.e. `all()`, `race()`, `any()`, `some()` etc.
+
+For more details on the promise primitives, please refer to the
+[Promise documentation](https://github.com/reactphp/promise#functions).
+
+### resolve()
+
+The `resolve($time, LoopInterface $loop)` function can be used to create a new Promise that
+resolves in `$time` seconds with the `$time` as the fulfillment value.
+
+```php
+Timer\resolve(1.5, $loop)->then(function ($time) {
+ echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL;
+});
+```
+
+Internally, the given `$time` value will be used to start a timer that will
+resolve the promise once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+#### Resolve cancellation
+
+You can explicitly `cancel()` the resulting timer promise at any time:
+
+```php
+$timer = Timer\resolve(2.0, $loop);
+
+$timer->cancel();
+```
+
+This will abort the timer and *reject* with a `RuntimeException`.
+
+### reject()
+
+The `reject($time, LoopInterface $loop)` function can be used to create a new Promise
+which rejects in `$time` seconds with a `TimeoutException`.
+
+```php
+Timer\reject(2.0, $loop)->then(null, function (TimeoutException $e) {
+ echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL;
+});
+```
+
+Internally, the given `$time` value will be used to start a timer that will
+reject the promise once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+This function complements the [`resolve()`](#resolve) function
+and can be used as a basic building block for higher-level promise consumers.
+
+#### Reject cancellation
+
+You can explicitly `cancel()` the resulting timer promise at any time:
+
+```php
+$timer = Timer\reject(2.0, $loop);
+
+$timer->cancel();
+```
+
+This will abort the timer and *reject* with a `RuntimeException`.
+
+### TimeoutException
+
+The `TimeoutException` extends PHP's built-in `RuntimeException`.
+
+The `getTimeout()` method can be used to get the timeout value in seconds.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/promise-timer:^1.5
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/instafeed/vendor/react/promise-timer/composer.json b/instafeed/vendor/react/promise-timer/composer.json
new file mode 100755
index 0000000..1fdaddd
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "react/promise-timer",
+ "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
+ "keywords": ["Promise", "timeout", "timer", "event-loop", "ReactPHP", "async"],
+ "homepage": "https://github.com/reactphp/promise-timer",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "React\\Promise\\Timer\\": "src/" },
+ "files": [ "src/functions_include.php" ]
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Promise\\Timer\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.7.0 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/instafeed/vendor/react/promise-timer/phpunit.xml.dist b/instafeed/vendor/react/promise-timer/phpunit.xml.dist
new file mode 100755
index 0000000..bb79fba
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/phpunit.xml.dist
@@ -0,0 +1,19 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
\ No newline at end of file
diff --git a/instafeed/vendor/react/promise-timer/src/TimeoutException.php b/instafeed/vendor/react/promise-timer/src/TimeoutException.php
new file mode 100755
index 0000000..18ea72f
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/src/TimeoutException.php
@@ -0,0 +1,22 @@
+timeout = $timeout;
+ }
+
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+}
diff --git a/instafeed/vendor/react/promise-timer/src/functions.php b/instafeed/vendor/react/promise-timer/src/functions.php
new file mode 100755
index 0000000..123a905
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/src/functions.php
@@ -0,0 +1,81 @@
+cancel();
+ $promise = null;
+ };
+ }
+
+ return new Promise(function ($resolve, $reject) use ($loop, $time, $promise) {
+ $timer = null;
+ $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $resolve($v);
+ }, function ($v) use (&$timer, $loop, $reject) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $reject($v);
+ });
+
+ // promise already resolved => no need to start timer
+ if ($timer === false) {
+ return;
+ }
+
+ // start timeout timer which will cancel the input promise
+ $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject) {
+ $reject(new TimeoutException($time, 'Timed out after ' . $time . ' seconds'));
+
+ // try to invoke cancellation handler of input promise and then clean
+ // reference in order to avoid garbage references in call stack.
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ $promise = null;
+ });
+ }, $canceller);
+}
+
+function resolve($time, LoopInterface $loop)
+{
+ return new Promise(function ($resolve) use ($loop, $time, &$timer) {
+ // resolve the promise when the timer fires in $time seconds
+ $timer = $loop->addTimer($time, function () use ($time, $resolve) {
+ $resolve($time);
+ });
+ }, function () use (&$timer, $loop) {
+ // cancelling this promise will cancel the timer, clean the reference
+ // in order to avoid garbage references in call stack and then reject.
+ $loop->cancelTimer($timer);
+ $timer = null;
+
+ throw new \RuntimeException('Timer cancelled');
+ });
+}
+
+function reject($time, LoopInterface $loop)
+{
+ return resolve($time, $loop)->then(function ($time) {
+ throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds');
+ });
+}
diff --git a/instafeed/vendor/react/promise-timer/src/functions_include.php b/instafeed/vendor/react/promise-timer/src/functions_include.php
new file mode 100755
index 0000000..1d5673a
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/src/functions_include.php
@@ -0,0 +1,7 @@
+loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseExpiredIsPendingWithoutRunningLoop()
+ {
+ $promise = Timer\reject(-1, $this->loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseWillBeRejectedOnTimeout()
+ {
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testPromiseExpiredWillBeRejectedOnTimeout()
+ {
+ $promise = Timer\reject(-1, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testCancellingPromiseWillRejectTimer()
+ {
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testWaitingForPromiseToRejectDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\reject(0.01, $this->loop);
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPromiseDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\reject(0.01, $this->loop);
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/instafeed/vendor/react/promise-timer/tests/FunctionResolveTest.php b/instafeed/vendor/react/promise-timer/tests/FunctionResolveTest.php
new file mode 100755
index 0000000..c4e2be7
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/tests/FunctionResolveTest.php
@@ -0,0 +1,101 @@
+loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseExpiredIsPendingWithoutRunningLoop()
+ {
+ $promise = Timer\resolve(-1, $this->loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseWillBeResolvedOnTimeout()
+ {
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testPromiseExpiredWillBeResolvedOnTimeout()
+ {
+ $promise = Timer\resolve(-1, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testWillStartLoopTimer()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->with($this->equalTo(0.01));
+
+ Timer\resolve(0.01, $loop);
+ }
+
+ public function testCancellingPromiseWillCancelLoopTimer()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $timer = $this->getMockBuilder(interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
+
+ $promise = Timer\resolve(0.01, $loop);
+
+ $loop->expects($this->once())->method('cancelTimer')->with($this->equalTo($timer));
+
+ $promise->cancel();
+ }
+
+ public function testCancellingPromiseWillRejectTimer()
+ {
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testWaitingForPromiseToResolveDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\resolve(0.01, $this->loop);
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPromiseDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\resolve(0.01, $this->loop);
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/instafeed/vendor/react/promise-timer/tests/FunctionTimeoutTest.php b/instafeed/vendor/react/promise-timer/tests/FunctionTimeoutTest.php
new file mode 100755
index 0000000..9652d1a
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/tests/FunctionTimeoutTest.php
@@ -0,0 +1,286 @@
+loop);
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testResolvedExpiredWillResolveRightAway()
+ {
+ $promise = Promise\resolve();
+
+ $promise = Timer\timeout($promise, -1, $this->loop);
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testResolvedWillNotStartTimer()
+ {
+ $promise = Promise\resolve();
+
+ Timer\timeout($promise, 3, $this->loop);
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.5, $time);
+ }
+
+ public function testRejectedWillRejectRightAway()
+ {
+ $promise = Promise\reject();
+
+ $promise = Timer\timeout($promise, 3, $this->loop);
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testRejectedWillNotStartTimer()
+ {
+ $promise = Promise\reject();
+
+ Timer\timeout($promise, 3, $this->loop);
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.5, $time);
+ }
+
+ public function testPendingWillRejectOnTimeout()
+ {
+ $promise = $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testPendingCancellableWillBeCancelledThroughFollowerOnTimeout()
+ {
+ $cancellable = $this->getMockBuilder('React\Promise\CancellablePromiseInterface')->getMock();
+ $cancellable->expects($this->once())->method('cancel');
+
+ $promise = $this->getMockBuilder('React\Promise\CancellablePromiseInterface')->getMock();
+ $promise->expects($this->once())->method('then')->willReturn($cancellable);
+
+ Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ }
+
+ public function testCancelTimeoutWithoutCancellationhandlerWillNotCancelTimerAndWillNotReject()
+ {
+ $promise = new \React\Promise\Promise(function () { });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $timer = $this->getMockBuilder('React\EventLoop\Timer\TimerInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
+ $loop->expects($this->never())->method('cancelTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $timeout->cancel();
+
+ $this->expectPromisePending($timeout);
+ }
+
+ public function testResolvedPromiseWillNotStartTimer()
+ {
+ $promise = new \React\Promise\Promise(function ($resolve) { $resolve(true); });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $this->expectPromiseResolved($timeout);
+ }
+
+ public function testRejectedPromiseWillNotStartTimer()
+ {
+ $promise = Promise\reject(new \RuntimeException());
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillCancelGivenPromise()
+ {
+ $promise = new \React\Promise\Promise(function () { }, $this->expectCallableOnce());
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+ }
+
+ public function testCancelGivenPromiseWillReject()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillRejectIfGivenPromiseWillReject()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+
+ $this->expectPromiseRejected($promise);
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillResolveIfGivenPromiseWillResolve()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $resolve(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+
+ $this->expectPromiseResolved($promise);
+ $this->expectPromiseResolved($timeout);
+ }
+
+ public function testWaitingForPromiseToResolveBeforeTimeoutDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $promise = Timer\timeout($promise, 1.0, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToRejectBeforeTimeoutDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $promise = Timer\timeout($promise, 1.0, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToTimeoutDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToTimeoutWithoutCancellerDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { });
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToTimeoutWithNoOpCancellerDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { }, function () {
+ // no-op
+ });
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPromiseDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $promise = Timer\timeout($promise, 0.01, $loop);
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/instafeed/vendor/react/promise-timer/tests/TestCase.php b/instafeed/vendor/react/promise-timer/tests/TestCase.php
new file mode 100755
index 0000000..9d8d49a
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/tests/TestCase.php
@@ -0,0 +1,61 @@
+loop = Factory::create();
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ /**
+ * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
+ */
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Promise\Timer\CallableStub')->getMock();
+ }
+
+ protected function expectPromiseRejected($promise)
+ {
+ return $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ protected function expectPromiseResolved($promise)
+ {
+ return $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+ }
+
+ protected function expectPromisePending($promise)
+ {
+ return $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+}
diff --git a/instafeed/vendor/react/promise-timer/tests/TimeoutExceptionTest.php b/instafeed/vendor/react/promise-timer/tests/TimeoutExceptionTest.php
new file mode 100755
index 0000000..e9bedd9
--- /dev/null
+++ b/instafeed/vendor/react/promise-timer/tests/TimeoutExceptionTest.php
@@ -0,0 +1,15 @@
+assertEquals(10, $e->getTimeout());
+ }
+}
diff --git a/instafeed/vendor/react/promise/.gitignore b/instafeed/vendor/react/promise/.gitignore
new file mode 100755
index 0000000..5241c60
--- /dev/null
+++ b/instafeed/vendor/react/promise/.gitignore
@@ -0,0 +1,5 @@
+composer.lock
+composer.phar
+phpunit.xml
+build/
+vendor/
diff --git a/instafeed/vendor/react/promise/.travis.yml b/instafeed/vendor/react/promise/.travis.yml
new file mode 100755
index 0000000..bcbe642
--- /dev/null
+++ b/instafeed/vendor/react/promise/.travis.yml
@@ -0,0 +1,28 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - nightly # ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ allow_failures:
+ - php: hhvm
+ - php: nightly
+
+install:
+ - composer install
+
+script:
+ - ./vendor/bin/phpunit -v --coverage-text --coverage-clover=./build/logs/clover.xml
+
+after_script:
+ - if [ -f ./build/logs/clover.xml ]; then travis_retry composer require satooshi/php-coveralls --no-interaction --update-with-dependencies; fi
+ - if [ -f ./build/logs/clover.xml ]; then php vendor/bin/coveralls -v; fi
diff --git a/instafeed/vendor/react/promise/CHANGELOG.md b/instafeed/vendor/react/promise/CHANGELOG.md
new file mode 100755
index 0000000..11f007d
--- /dev/null
+++ b/instafeed/vendor/react/promise/CHANGELOG.md
@@ -0,0 +1,135 @@
+CHANGELOG for 2.x
+=================
+
+* 2.7.1 (2018-01-07)
+
+ * Fix: file_exists warning when resolving with long strings.
+ (#130 by @sbesselsen)
+ * Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
+ (#133 by @WyriHaximus)
+
+* 2.7.0 (2018-06-13)
+
+ * Feature: Improve memory consumption for pending promises by using static internal callbacks without binding to self.
+ (#124 by @clue)
+
+* 2.6.0 (2018-06-11)
+
+ * Feature: Significantly improve memory consumption and performance by only passing resolver args
+ to resolver and canceller if callback requires them. Also use static callbacks without
+ binding to promise, clean up canceller function reference when they are no longer
+ needed and hide resolver and canceller references from call stack on PHP 7+.
+ (#113, #115, #116, #117, #118, #119 and #123 by @clue)
+
+ These changes combined mean that rejecting promises with an `Exception` should
+ no longer cause any internal circular references which could cause some unexpected
+ memory growth in previous versions. By explicitly avoiding and explicitly
+ cleaning up said references, we can avoid relying on PHP's circular garbage collector
+ to kick in which significantly improves performance when rejecting many promises.
+
+ * Mark legacy progress support / notification API as deprecated
+ (#112 by @clue)
+
+ * Recommend rejecting promises by throwing an exception
+ (#114 by @jsor)
+
+ * Improve documentation to properly instantiate LazyPromise
+ (#121 by @holtkamp)
+
+ * Follower cancellation propagation was originally planned for this release
+ but has been reverted for now and is planned for a future release.
+ (#99 by @jsor and #122 by @clue)
+
+* 2.5.1 (2017-03-25)
+
+ * Fix circular references when resolving with a promise which follows
+ itself (#94).
+
+* 2.5.0 (2016-12-22)
+
+ * Revert automatic cancellation of pending collection promises once the
+ output promise resolves. This was introduced in 42d86b7 (PR #36, released
+ in [v2.3.0](https://github.com/reactphp/promise/releases/tag/v2.3.0)) and
+ was both unintended and backward incompatible.
+
+ If you need automatic cancellation, you can use something like:
+
+ ```php
+ function allAndCancel(array $promises)
+ {
+ return \React\Promise\all($promises)
+ ->always(function() use ($promises) {
+ foreach ($promises as $promise) {
+ if ($promise instanceof \React\Promise\CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+ });
+ }
+ ```
+ * `all()` and `map()` functions now preserve the order of the array (#77).
+ * Fix circular references when resolving a promise with itself (#71).
+
+* 2.4.1 (2016-05-03)
+
+ * Fix `some()` not cancelling pending promises when too much input promises
+ reject (16ff799).
+
+* 2.4.0 (2016-03-31)
+
+ * Support foreign thenables in `resolve()`.
+ Any object that provides a `then()` method is now assimilated to a trusted
+ promise that follows the state of this thenable (#52).
+ * Fix `some()` and `any()` for input arrays containing not enough items
+ (#34).
+
+* 2.3.0 (2016-03-24)
+
+ * Allow cancellation of promises returned by functions working on promise
+ collections (#36).
+ * Handle `\Throwable` in the same way as `\Exception` (#51 by @joshdifabio).
+
+* 2.2.2 (2016-02-26)
+
+ * Fix cancellation handlers called multiple times (#47 by @clue).
+
+* 2.2.1 (2015-07-03)
+
+ * Fix stack error when resolving a promise in its own fulfillment or
+ rejection handlers.
+
+* 2.2.0 (2014-12-30)
+
+ * Introduce new `ExtendedPromiseInterface` implemented by all promises.
+ * Add new `done()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `otherwise()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `always()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `progress()` method (part of the `ExtendedPromiseInterface`).
+ * Rename `Deferred::progress` to `Deferred::notify` to avoid confusion with
+ `ExtendedPromiseInterface::progress` (a `Deferred::progress` alias is
+ still available for backward compatibility)
+ * `resolve()` now always returns a `ExtendedPromiseInterface`.
+
+* 2.1.0 (2014-10-15)
+
+ * Introduce new `CancellablePromiseInterface` implemented by all promises.
+ * Add new `cancel()` method (part of the `CancellablePromiseInterface`).
+
+* 2.0.0 (2013-12-10)
+
+ New major release. The goal is to streamline the API and to make it more
+ compliant with other promise libraries and especially with the new upcoming
+ [ES6 promises specification](https://github.com/domenic/promises-unwrapping/).
+
+ * Add standalone Promise class.
+ * Add new `race()` function.
+ * BC break: Bump minimum PHP version to PHP 5.4.
+ * BC break: Remove `ResolverInterface` and `PromiseInterface` from
+ `Deferred`.
+ * BC break: Change signature of `PromiseInterface`.
+ * BC break: Remove `When` and `Util` classes and move static methods to
+ functions.
+ * BC break: `FulfilledPromise` and `RejectedPromise` now throw an exception
+ when initialized with a promise instead of a value/reason.
+ * BC break: `Deferred::resolve()` and `Deferred::reject()` no longer return
+ a promise.
diff --git a/instafeed/vendor/react/promise/LICENSE b/instafeed/vendor/react/promise/LICENSE
new file mode 100755
index 0000000..5919d20
--- /dev/null
+++ b/instafeed/vendor/react/promise/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012-2016 Jan Sorgalla
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/instafeed/vendor/react/promise/README.md b/instafeed/vendor/react/promise/README.md
new file mode 100755
index 0000000..3685566
--- /dev/null
+++ b/instafeed/vendor/react/promise/README.md
@@ -0,0 +1,870 @@
+Promise
+=======
+
+A lightweight implementation of
+[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+[](http://travis-ci.org/reactphp/promise)
+[](https://coveralls.io/github/reactphp/promise?branch=master)
+
+Table of Contents
+-----------------
+
+1. [Introduction](#introduction)
+2. [Concepts](#concepts)
+ * [Deferred](#deferred)
+ * [Promise](#promise-1)
+3. [API](#api)
+ * [Deferred](#deferred-1)
+ * [Deferred::promise()](#deferredpromise)
+ * [Deferred::resolve()](#deferredresolve)
+ * [Deferred::reject()](#deferredreject)
+ * [Deferred::notify()](#deferrednotify)
+ * [PromiseInterface](#promiseinterface)
+ * [PromiseInterface::then()](#promiseinterfacethen)
+ * [ExtendedPromiseInterface](#extendedpromiseinterface)
+ * [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
+ * [ExtendedPromiseInterface::otherwise()](#extendedpromiseinterfaceotherwise)
+ * [ExtendedPromiseInterface::always()](#extendedpromiseinterfacealways)
+ * [ExtendedPromiseInterface::progress()](#extendedpromiseinterfaceprogress)
+ * [CancellablePromiseInterface](#cancellablepromiseinterface)
+ * [CancellablePromiseInterface::cancel()](#cancellablepromiseinterfacecancel)
+ * [Promise](#promise-2)
+ * [FulfilledPromise](#fulfilledpromise)
+ * [RejectedPromise](#rejectedpromise)
+ * [LazyPromise](#lazypromise)
+ * [Functions](#functions)
+ * [resolve()](#resolve)
+ * [reject()](#reject)
+ * [all()](#all)
+ * [race()](#race)
+ * [any()](#any)
+ * [some()](#some)
+ * [map()](#map)
+ * [reduce()](#reduce)
+ * [PromisorInterface](#promisorinterface)
+4. [Examples](#examples)
+ * [How to use Deferred](#how-to-use-deferred)
+ * [How promise forwarding works](#how-promise-forwarding-works)
+ * [Resolution forwarding](#resolution-forwarding)
+ * [Rejection forwarding](#rejection-forwarding)
+ * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
+ * [Progress event forwarding](#progress-event-forwarding)
+ * [done() vs. then()](#done-vs-then)
+5. [Install](#install)
+6. [Credits](#credits)
+7. [License](#license)
+
+Introduction
+------------
+
+Promise is a library implementing
+[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+It also provides several other useful promise-related concepts, such as joining
+multiple promises and mapping and reducing collections of promises.
+
+If you've never heard about promises before,
+[read this first](https://gist.github.com/3889970).
+
+Concepts
+--------
+
+### Deferred
+
+A **Deferred** represents a computation or unit of work that may not have
+completed yet. Typically (but not always), that computation will be something
+that executes asynchronously and completes at some point in the future.
+
+### Promise
+
+While a deferred represents the computation itself, a **Promise** represents
+the result of that computation. Thus, each deferred has a promise that acts as
+a placeholder for its actual result.
+
+API
+---
+
+### Deferred
+
+A deferred represents an operation whose resolution is pending. It has separate
+promise and resolver parts.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$promise = $deferred->promise();
+
+$deferred->resolve(mixed $value = null);
+$deferred->reject(mixed $reason = null);
+$deferred->notify(mixed $update = null);
+```
+
+The `promise` method returns the promise of the deferred.
+
+The `resolve` and `reject` methods control the state of the deferred.
+
+The deprecated `notify` method is for progress notification.
+
+The constructor of the `Deferred` accepts an optional `$canceller` argument.
+See [Promise](#promise-2) for more information.
+
+#### Deferred::promise()
+
+```php
+$promise = $deferred->promise();
+```
+
+Returns the promise of the deferred, which you can hand out to others while
+keeping the authority to modify its state to yourself.
+
+#### Deferred::resolve()
+
+```php
+$deferred->resolve(mixed $value = null);
+```
+
+Resolves the promise returned by `promise()`. All consumers are notified by
+having `$onFulfilled` (which they registered via `$promise->then()`) called with
+`$value`.
+
+If `$value` itself is a promise, the promise will transition to the state of
+this promise once it is resolved.
+
+#### Deferred::reject()
+
+```php
+$deferred->reject(mixed $reason = null);
+```
+
+Rejects the promise returned by `promise()`, signalling that the deferred's
+computation failed.
+All consumers are notified by having `$onRejected` (which they registered via
+`$promise->then()`) called with `$reason`.
+
+If `$reason` itself is a promise, the promise will be rejected with the outcome
+of this promise regardless whether it fulfills or rejects.
+
+#### Deferred::notify()
+
+> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
+
+```php
+$deferred->notify(mixed $update = null);
+```
+
+Triggers progress notifications, to indicate to consumers that the computation
+is making progress toward its result.
+
+All consumers are notified by having `$onProgress` (which they registered via
+`$promise->then()`) called with `$update`.
+
+### PromiseInterface
+
+The promise interface provides the common interface for all promise
+implementations.
+
+A promise represents an eventual outcome, which is either fulfillment (success)
+and an associated value, or rejection (failure) and an associated reason.
+
+Once in the fulfilled or rejected state, a promise becomes immutable.
+Neither its state nor its result (or error) can be modified.
+
+#### Implementations
+
+* [Promise](#promise-2)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+#### PromiseInterface::then()
+
+```php
+$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+```
+
+Transforms a promise's value by applying a function to the promise's fulfillment
+or rejection value. Returns a new promise for the transformed result.
+
+The `then()` method registers new fulfilled, rejection and progress handlers
+with a promise (all parameters are optional):
+
+ * `$onFulfilled` will be invoked once the promise is fulfilled and passed
+ the result as the first argument.
+ * `$onRejected` will be invoked once the promise is rejected and passed the
+ reason as the first argument.
+ * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
+ triggers progress notifications and passed a single argument (whatever it
+ wants) to indicate progress.
+
+It returns a new promise that will fulfill with the return value of either
+`$onFulfilled` or `$onRejected`, whichever is called, or will reject with
+the thrown exception if either throws.
+
+A promise makes the following guarantees about handlers registered in
+the same call to `then()`:
+
+ 1. Only one of `$onFulfilled` or `$onRejected` will be called,
+ never both.
+ 2. `$onFulfilled` and `$onRejected` will never be called more
+ than once.
+ 3. `$onProgress` (deprecated) may be called multiple times.
+
+#### See also
+
+* [resolve()](#resolve) - Creating a resolved promise
+* [reject()](#reject) - Creating a rejected promise
+* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
+* [done() vs. then()](#done-vs-then)
+
+### ExtendedPromiseInterface
+
+The ExtendedPromiseInterface extends the PromiseInterface with useful shortcut
+and utility methods which are not part of the Promises/A specification.
+
+#### Implementations
+
+* [Promise](#promise-1)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+#### ExtendedPromiseInterface::done()
+
+```php
+$promise->done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+```
+
+Consumes the promise's ultimate value if the promise fulfills, or handles the
+ultimate error.
+
+It will cause a fatal error if either `$onFulfilled` or `$onRejected` throw or
+return a rejected promise.
+
+Since the purpose of `done()` is consumption rather than transformation,
+`done()` always returns `null`.
+
+#### See also
+
+* [PromiseInterface::then()](#promiseinterfacethen)
+* [done() vs. then()](#done-vs-then)
+
+#### ExtendedPromiseInterface::otherwise()
+
+```php
+$promise->otherwise(callable $onRejected);
+```
+
+Registers a rejection handler for promise. It is a shortcut for:
+
+```php
+$promise->then(null, $onRejected);
+```
+
+Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
+only specific errors.
+
+```php
+$promise
+ ->otherwise(function (\RuntimeException $reason) {
+ // Only catch \RuntimeException instances
+ // All other types of errors will propagate automatically
+ })
+ ->otherwise(function ($reason) {
+ // Catch other errors
+ )};
+```
+
+#### ExtendedPromiseInterface::always()
+
+```php
+$newPromise = $promise->always(callable $onFulfilledOrRejected);
+```
+
+Allows you to execute "cleanup" type tasks in a promise chain.
+
+It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
+when the promise is either fulfilled or rejected.
+
+* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
+ `$newPromise` will fulfill with the same value as `$promise`.
+* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
+ rejected promise, `$newPromise` will reject with the thrown exception or
+ rejected promise's reason.
+* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
+ `$newPromise` will reject with the same reason as `$promise`.
+* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
+ rejected promise, `$newPromise` will reject with the thrown exception or
+ rejected promise's reason.
+
+`always()` behaves similarly to the synchronous finally statement. When combined
+with `otherwise()`, `always()` allows you to write code that is similar to the familiar
+synchronous catch/finally pair.
+
+Consider the following synchronous code:
+
+```php
+try {
+ return doSomething();
+} catch(\Exception $e) {
+ return handleError($e);
+} finally {
+ cleanup();
+}
+```
+
+Similar asynchronous code (with `doSomething()` that returns a promise) can be
+written:
+
+```php
+return doSomething()
+ ->otherwise('handleError')
+ ->always('cleanup');
+```
+
+#### ExtendedPromiseInterface::progress()
+
+> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
+
+```php
+$promise->progress(callable $onProgress);
+```
+
+Registers a handler for progress updates from promise. It is a shortcut for:
+
+```php
+$promise->then(null, null, $onProgress);
+```
+
+### CancellablePromiseInterface
+
+A cancellable promise provides a mechanism for consumers to notify the creator
+of the promise that they are not longer interested in the result of an
+operation.
+
+#### CancellablePromiseInterface::cancel()
+
+``` php
+$promise->cancel();
+```
+
+The `cancel()` method notifies the creator of the promise that there is no
+further interest in the results of the operation.
+
+Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
+a promise has no effect.
+
+#### Implementations
+
+* [Promise](#promise-1)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+### Promise
+
+Creates a promise whose state is controlled by the functions passed to
+`$resolver`.
+
+```php
+$resolver = function (callable $resolve, callable $reject, callable $notify) {
+ // Do some work, possibly asynchronously, and then
+ // resolve or reject. You can notify of progress events (deprecated)
+ // along the way if you want/need.
+
+ $resolve($awesomeResult);
+ // or throw new Exception('Promise rejected');
+ // or $resolve($anotherPromise);
+ // or $reject($nastyError);
+ // or $notify($progressNotification);
+};
+
+$canceller = function () {
+ // Cancel/abort any running operations like network connections, streams etc.
+
+ // Reject promise by throwing an exception
+ throw new Exception('Promise cancelled');
+};
+
+$promise = new React\Promise\Promise($resolver, $canceller);
+```
+
+The promise constructor receives a resolver function and an optional canceller
+function which both will be called with 3 arguments:
+
+ * `$resolve($value)` - Primary function that seals the fate of the
+ returned promise. Accepts either a non-promise value, or another promise.
+ When called with a non-promise value, fulfills promise with that value.
+ When called with another promise, e.g. `$resolve($otherPromise)`, promise's
+ fate will be equivalent to that of `$otherPromise`.
+ * `$reject($reason)` - Function that rejects the promise. It is recommended to
+ just throw an exception instead of using `$reject()`.
+ * `$notify($update)` - Deprecated function that issues progress events for the promise.
+
+If the resolver or canceller throw an exception, the promise will be rejected
+with that thrown exception as the rejection reason.
+
+The resolver function will be called immediately, the canceller function only
+once all consumers called the `cancel()` method of the promise.
+
+### FulfilledPromise
+
+Creates a already fulfilled promise.
+
+```php
+$promise = React\Promise\FulfilledPromise($value);
+```
+
+Note, that `$value` **cannot** be a promise. It's recommended to use
+[resolve()](#resolve) for creating resolved promises.
+
+### RejectedPromise
+
+Creates a already rejected promise.
+
+```php
+$promise = React\Promise\RejectedPromise($reason);
+```
+
+Note, that `$reason` **cannot** be a promise. It's recommended to use
+[reject()](#reject) for creating rejected promises.
+
+### LazyPromise
+
+Creates a promise which will be lazily initialized by `$factory` once a consumer
+calls the `then()` method.
+
+```php
+$factory = function () {
+ $deferred = new React\Promise\Deferred();
+
+ // Do some heavy stuff here and resolve the deferred once completed
+
+ return $deferred->promise();
+};
+
+$promise = new React\Promise\LazyPromise($factory);
+
+// $factory will only be executed once we call then()
+$promise->then(function ($value) {
+});
+```
+
+### Functions
+
+Useful functions for creating, joining, mapping and reducing collections of
+promises.
+
+All functions working on promise collections (like `all()`, `race()`, `some()`
+etc.) support cancellation. This means, if you call `cancel()` on the returned
+promise, all promises in the collection are cancelled. If the collection itself
+is a promise which resolves to an array, this promise is also cancelled.
+
+#### resolve()
+
+```php
+$promise = React\Promise\resolve(mixed $promiseOrValue);
+```
+
+Creates a promise for the supplied `$promiseOrValue`.
+
+If `$promiseOrValue` is a value, it will be the resolution value of the
+returned promise.
+
+If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
+a trusted promise that follows the state of the thenable is returned.
+
+If `$promiseOrValue` is a promise, it will be returned as is.
+
+Note: The promise returned is always a promise implementing
+[ExtendedPromiseInterface](#extendedpromiseinterface). If you pass in a custom
+promise which only implements [PromiseInterface](#promiseinterface), this
+promise will be assimilated to a extended promise following `$promiseOrValue`.
+
+#### reject()
+
+```php
+$promise = React\Promise\reject(mixed $promiseOrValue);
+```
+
+Creates a rejected promise for the supplied `$promiseOrValue`.
+
+If `$promiseOrValue` is a value, it will be the rejection value of the
+returned promise.
+
+If `$promiseOrValue` is a promise, its completion value will be the rejected
+value of the returned promise.
+
+This can be useful in situations where you need to reject a promise without
+throwing an exception. For example, it allows you to propagate a rejection with
+the value of another promise.
+
+#### all()
+
+```php
+$promise = React\Promise\all(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Returns a promise that will resolve only once all the items in
+`$promisesOrValues` have resolved. The resolution value of the returned promise
+will be an array containing the resolution values of each of the items in
+`$promisesOrValues`.
+
+#### race()
+
+```php
+$promise = React\Promise\race(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Initiates a competitive race that allows one winner. Returns a promise which is
+resolved in the same way the first settled promise resolves.
+
+#### any()
+
+```php
+$promise = React\Promise\any(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Returns a promise that will resolve when any one of the items in
+`$promisesOrValues` resolves. The resolution value of the returned promise
+will be the resolution value of the triggering item.
+
+The returned promise will only reject if *all* items in `$promisesOrValues` are
+rejected. The rejection value will be an array of all rejection reasons.
+
+The returned promise will also reject with a `React\Promise\Exception\LengthException`
+if `$promisesOrValues` contains 0 items.
+
+#### some()
+
+```php
+$promise = React\Promise\some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany);
+```
+
+Returns a promise that will resolve when `$howMany` of the supplied items in
+`$promisesOrValues` resolve. The resolution value of the returned promise
+will be an array of length `$howMany` containing the resolution values of the
+triggering items.
+
+The returned promise will reject if it becomes impossible for `$howMany` items
+to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
+reject). The rejection value will be an array of
+`(count($promisesOrValues) - $howMany) + 1` rejection reasons.
+
+The returned promise will also reject with a `React\Promise\Exception\LengthException`
+if `$promisesOrValues` contains less items than `$howMany`.
+
+#### map()
+
+```php
+$promise = React\Promise\map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc);
+```
+
+Traditional map function, similar to `array_map()`, but allows input to contain
+promises and/or values, and `$mapFunc` may return either a value or a promise.
+
+The map function receives each item as argument, where item is a fully resolved
+value of a promise or value in `$promisesOrValues`.
+
+#### reduce()
+
+```php
+$promise = React\Promise\reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null);
+```
+
+Traditional reduce function, similar to `array_reduce()`, but input may contain
+promises and/or values, and `$reduceFunc` may return either a value or a
+promise, *and* `$initialValue` may be a promise or a value for the starting
+value.
+
+### PromisorInterface
+
+The `React\Promise\PromisorInterface` provides a common interface for objects
+that provide a promise. `React\Promise\Deferred` implements it, but since it
+is part of the public API anyone can implement it.
+
+Examples
+--------
+
+### How to use Deferred
+
+```php
+function getAwesomeResultPromise()
+{
+ $deferred = new React\Promise\Deferred();
+
+ // Execute a Node.js-style function using the callback pattern
+ computeAwesomeResultAsynchronously(function ($error, $result) use ($deferred) {
+ if ($error) {
+ $deferred->reject($error);
+ } else {
+ $deferred->resolve($result);
+ }
+ });
+
+ // Return the promise
+ return $deferred->promise();
+}
+
+getAwesomeResultPromise()
+ ->then(
+ function ($value) {
+ // Deferred resolved, do something with $value
+ },
+ function ($reason) {
+ // Deferred rejected, do something with $reason
+ },
+ function ($update) {
+ // Progress notification triggered, do something with $update
+ }
+ );
+```
+
+### How promise forwarding works
+
+A few simple examples to show how the mechanics of Promises/A forwarding works.
+These examples are contrived, of course, and in real usage, promise chains will
+typically be spread across several function calls, or even several levels of
+your application architecture.
+
+#### Resolution forwarding
+
+Resolved promises forward resolution values to the next promise.
+The first promise, `$deferred->promise()`, will resolve with the value passed
+to `$deferred->resolve()` below.
+
+Each call to `then()` returns a new promise that will resolve with the return
+value of the previous handler. This creates a promise "pipeline".
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ // $x will be the value passed to $deferred->resolve() below
+ // and returns a *new promise* for $x + 1
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 2
+ // This handler receives the return value of the
+ // previous handler.
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 3
+ // This handler receives the return value of the
+ // previous handler.
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 4
+ // This handler receives the return value of the
+ // previous handler.
+ echo 'Resolve ' . $x;
+ });
+
+$deferred->resolve(1); // Prints "Resolve 4"
+```
+
+#### Rejection forwarding
+
+Rejected promises behave similarly, and also work similarly to try/catch:
+When you catch an exception, you must rethrow for it to propagate.
+
+Similarly, when you handle a rejected promise, to propagate the rejection,
+"rethrow" it by either returning a rejected promise, or actually throwing
+(since promise translates thrown exceptions into rejections)
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ throw new \Exception($x + 1);
+ })
+ ->otherwise(function (\Exception $x) {
+ // Propagate the rejection
+ throw $x;
+ })
+ ->otherwise(function (\Exception $x) {
+ // Can also propagate by returning another rejection
+ return React\Promise\reject(
+ new \Exception($x->getMessage() + 1)
+ );
+ })
+ ->otherwise(function ($x) {
+ echo 'Reject ' . $x->getMessage(); // 3
+ });
+
+$deferred->resolve(1); // Prints "Reject 3"
+```
+
+#### Mixed resolution and rejection forwarding
+
+Just like try/catch, you can choose to propagate or not. Mixing resolutions and
+rejections will still forward handler results in a predictable way.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ throw new \Exception($x + 1);
+ })
+ ->otherwise(function (\Exception $x) {
+ // Handle the rejection, and don't propagate.
+ // This is like catch without a rethrow
+ return $x->getMessage() + 1;
+ })
+ ->then(function ($x) {
+ echo 'Mixed ' . $x; // 4
+ });
+
+$deferred->resolve(1); // Prints "Mixed 4"
+```
+
+#### Progress event forwarding
+
+> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
+
+In the same way as resolution and rejection handlers, your progress handler
+**MUST** return a progress event to be propagated to the next link in the chain.
+If you return nothing, `null` will be propagated.
+
+Also in the same way as resolutions and rejections, if you don't register a
+progress handler, the update will be propagated through.
+
+If your progress handler throws an exception, the exception will be propagated
+to the next link in the chain. The best thing to do is to ensure your progress
+handlers do not throw exceptions.
+
+This gives you the opportunity to transform progress events at each step in the
+chain so that they are meaningful to the next step. It also allows you to choose
+not to transform them, and simply let them propagate untransformed, by not
+registering a progress handler.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->progress(function ($update) {
+ return $update + 1;
+ })
+ ->progress(function ($update) {
+ echo 'Progress ' . $update; // 2
+ });
+
+$deferred->notify(1); // Prints "Progress 2"
+```
+
+### done() vs. then()
+
+The golden rule is:
+
+ Either return your promise, or call done() on it.
+
+At a first glance, `then()` and `done()` seem very similar. However, there are
+important distinctions.
+
+The intent of `then()` is to transform a promise's value and to pass or return
+a new promise for the transformed value along to other parts of your code.
+
+The intent of `done()` is to consume a promise's value, transferring
+responsibility for the value to your code.
+
+In addition to transforming a value, `then()` allows you to recover from, or
+propagate intermediate errors. Any errors that are not handled will be caught
+by the promise machinery and used to reject the promise returned by `then()`.
+
+Calling `done()` transfers all responsibility for errors to your code. If an
+error (either a thrown exception or returned rejection) escapes the
+`$onFulfilled` or `$onRejected` callbacks you provide to done, it will be
+rethrown in an uncatchable way causing a fatal error.
+
+```php
+function getJsonResult()
+{
+ return queryApi()
+ ->then(
+ // Transform API results to an object
+ function ($jsonResultString) {
+ return json_decode($jsonResultString);
+ },
+ // Transform API errors to an exception
+ function ($jsonErrorString) {
+ $object = json_decode($jsonErrorString);
+ throw new ApiErrorException($object->errorMessage);
+ }
+ );
+}
+
+// Here we provide no rejection handler. If the promise returned has been
+// rejected, the ApiErrorException will be thrown
+getJsonResult()
+ ->done(
+ // Consume transformed object
+ function ($jsonResultObject) {
+ // Do something with $jsonResultObject
+ }
+ );
+
+// Here we provide a rejection handler which will either throw while debugging
+// or log the exception
+getJsonResult()
+ ->done(
+ function ($jsonResultObject) {
+ // Do something with $jsonResultObject
+ },
+ function (ApiErrorException $exception) {
+ if (isDebug()) {
+ throw $exception;
+ } else {
+ logException($exception);
+ }
+ }
+ );
+```
+
+Note that if a rejection value is not an instance of `\Exception`, it will be
+wrapped in an exception of the type `React\Promise\UnhandledRejectionException`.
+
+You can get the original rejection reason by calling `$exception->getReason()`.
+
+Install
+-------
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/promise:^2.7
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.4 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project due to its vast
+performance improvements.
+
+Credits
+-------
+
+Promise is a port of [when.js](https://github.com/cujojs/when)
+by [Brian Cavalier](https://github.com/briancavalier).
+
+Also, large parts of the documentation have been ported from the when.js
+[Wiki](https://github.com/cujojs/when/wiki) and the
+[API docs](https://github.com/cujojs/when/blob/master/docs/api.md).
+
+License
+-------
+
+Released under the [MIT](LICENSE) license.
diff --git a/instafeed/vendor/react/promise/composer.json b/instafeed/vendor/react/promise/composer.json
new file mode 100755
index 0000000..2fc4809
--- /dev/null
+++ b/instafeed/vendor/react/promise/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "react/promise",
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "license": "MIT",
+ "authors": [
+ {"name": "Jan Sorgalla", "email": "jsorgalla@gmail.com"}
+ ],
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Promise\\": "tests/fixtures"
+ }
+ },
+ "keywords": [
+ "promise",
+ "promises"
+ ]
+}
diff --git a/instafeed/vendor/react/promise/phpunit.xml.dist b/instafeed/vendor/react/promise/phpunit.xml.dist
new file mode 100755
index 0000000..b9a689d
--- /dev/null
+++ b/instafeed/vendor/react/promise/phpunit.xml.dist
@@ -0,0 +1,28 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+ ./src/functions_include.php
+
+
+
+
diff --git a/instafeed/vendor/react/promise/src/CancellablePromiseInterface.php b/instafeed/vendor/react/promise/src/CancellablePromiseInterface.php
new file mode 100755
index 0000000..896db2d
--- /dev/null
+++ b/instafeed/vendor/react/promise/src/CancellablePromiseInterface.php
@@ -0,0 +1,11 @@
+started) {
+ return;
+ }
+
+ $this->started = true;
+ $this->drain();
+ }
+
+ public function enqueue($cancellable)
+ {
+ if (!\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
+ return;
+ }
+
+ $length = \array_push($this->queue, $cancellable);
+
+ if ($this->started && 1 === $length) {
+ $this->drain();
+ }
+ }
+
+ private function drain()
+ {
+ for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
+ $cancellable = $this->queue[$i];
+
+ $exception = null;
+
+ try {
+ $cancellable->cancel();
+ } catch (\Throwable $exception) {
+ } catch (\Exception $exception) {
+ }
+
+ unset($this->queue[$i]);
+
+ if ($exception) {
+ throw $exception;
+ }
+ }
+
+ $this->queue = [];
+ }
+}
diff --git a/instafeed/vendor/react/promise/src/Deferred.php b/instafeed/vendor/react/promise/src/Deferred.php
new file mode 100755
index 0000000..3ca034b
--- /dev/null
+++ b/instafeed/vendor/react/promise/src/Deferred.php
@@ -0,0 +1,65 @@
+canceller = $canceller;
+ }
+
+ public function promise()
+ {
+ if (null === $this->promise) {
+ $this->promise = new Promise(function ($resolve, $reject, $notify) {
+ $this->resolveCallback = $resolve;
+ $this->rejectCallback = $reject;
+ $this->notifyCallback = $notify;
+ }, $this->canceller);
+ $this->canceller = null;
+ }
+
+ return $this->promise;
+ }
+
+ public function resolve($value = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->resolveCallback, $value);
+ }
+
+ public function reject($reason = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->rejectCallback, $reason);
+ }
+
+ /**
+ * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
+ * @param mixed $update
+ */
+ public function notify($update = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->notifyCallback, $update);
+ }
+
+ /**
+ * @deprecated 2.2.0
+ * @see Deferred::notify()
+ */
+ public function progress($update = null)
+ {
+ $this->notify($update);
+ }
+}
diff --git a/instafeed/vendor/react/promise/src/Exception/LengthException.php b/instafeed/vendor/react/promise/src/Exception/LengthException.php
new file mode 100755
index 0000000..775c48d
--- /dev/null
+++ b/instafeed/vendor/react/promise/src/Exception/LengthException.php
@@ -0,0 +1,7 @@
+value = $value;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return $this;
+ }
+
+ try {
+ return resolve($onFulfilled($this->value));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return;
+ }
+
+ $result = $onFulfilled($this->value);
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this;
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/instafeed/vendor/react/promise/src/LazyPromise.php b/instafeed/vendor/react/promise/src/LazyPromise.php
new file mode 100755
index 0000000..7546524
--- /dev/null
+++ b/instafeed/vendor/react/promise/src/LazyPromise.php
@@ -0,0 +1,63 @@
+factory = $factory;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->promise()->otherwise($onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->promise()->always($onFulfilledOrRejected);
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->promise()->progress($onProgress);
+ }
+
+ public function cancel()
+ {
+ return $this->promise()->cancel();
+ }
+
+ /**
+ * @internal
+ * @see Promise::settle()
+ */
+ public function promise()
+ {
+ if (null === $this->promise) {
+ try {
+ $this->promise = resolve(\call_user_func($this->factory));
+ } catch (\Throwable $exception) {
+ $this->promise = new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ $this->promise = new RejectedPromise($exception);
+ }
+ }
+
+ return $this->promise;
+ }
+}
diff --git a/instafeed/vendor/react/promise/src/Promise.php b/instafeed/vendor/react/promise/src/Promise.php
new file mode 100755
index 0000000..33759e6
--- /dev/null
+++ b/instafeed/vendor/react/promise/src/Promise.php
@@ -0,0 +1,256 @@
+canceller = $canceller;
+
+ // Explicitly overwrite arguments with null values before invoking
+ // resolver function. This ensure that these arguments do not show up
+ // in the stack trace in PHP 7+ only.
+ $cb = $resolver;
+ $resolver = $canceller = null;
+ $this->call($cb);
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ if (null === $this->canceller) {
+ return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
+ }
+
+ // This promise has a canceller, so we create a new child promise which
+ // has a canceller that invokes the parent canceller if all other
+ // followers are also cancelled. We keep a reference to this promise
+ // instance for the static canceller function and clear this to avoid
+ // keeping a cyclic reference between parent and follower.
+ $parent = $this;
+ ++$parent->requiredCancelRequests;
+
+ return new static(
+ $this->resolver($onFulfilled, $onRejected, $onProgress),
+ static function () use (&$parent) {
+ if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
+ $parent->cancel();
+ }
+
+ $parent = null;
+ }
+ );
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
+ $promise
+ ->done($onFulfilled, $onRejected);
+ };
+
+ if ($onProgress) {
+ $this->progressHandlers[] = $onProgress;
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, static function ($reason) use ($onRejected) {
+ if (!_checkTypehint($onRejected, $reason)) {
+ return new RejectedPromise($reason);
+ }
+
+ return $onRejected($reason);
+ });
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(static function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ }, static function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->then(null, null, $onProgress);
+ }
+
+ public function cancel()
+ {
+ if (null === $this->canceller || null !== $this->result) {
+ return;
+ }
+
+ $canceller = $this->canceller;
+ $this->canceller = null;
+
+ $this->call($canceller);
+ }
+
+ private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
+ if ($onProgress) {
+ $progressHandler = static function ($update) use ($notify, $onProgress) {
+ try {
+ $notify($onProgress($update));
+ } catch (\Throwable $e) {
+ $notify($e);
+ } catch (\Exception $e) {
+ $notify($e);
+ }
+ };
+ } else {
+ $progressHandler = $notify;
+ }
+
+ $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
+ $promise
+ ->then($onFulfilled, $onRejected)
+ ->done($resolve, $reject, $progressHandler);
+ };
+
+ $this->progressHandlers[] = $progressHandler;
+ };
+ }
+
+ private function reject($reason = null)
+ {
+ if (null !== $this->result) {
+ return;
+ }
+
+ $this->settle(reject($reason));
+ }
+
+ private function settle(ExtendedPromiseInterface $promise)
+ {
+ $promise = $this->unwrap($promise);
+
+ if ($promise === $this) {
+ $promise = new RejectedPromise(
+ new \LogicException('Cannot resolve a promise with itself.')
+ );
+ }
+
+ $handlers = $this->handlers;
+
+ $this->progressHandlers = $this->handlers = [];
+ $this->result = $promise;
+ $this->canceller = null;
+
+ foreach ($handlers as $handler) {
+ $handler($promise);
+ }
+ }
+
+ private function unwrap($promise)
+ {
+ $promise = $this->extract($promise);
+
+ while ($promise instanceof self && null !== $promise->result) {
+ $promise = $this->extract($promise->result);
+ }
+
+ return $promise;
+ }
+
+ private function extract($promise)
+ {
+ if ($promise instanceof LazyPromise) {
+ $promise = $promise->promise();
+ }
+
+ return $promise;
+ }
+
+ private function call(callable $cb)
+ {
+ // Explicitly overwrite argument with null value. This ensure that this
+ // argument does not show up in the stack trace in PHP 7+ only.
+ $callback = $cb;
+ $cb = null;
+
+ // Use reflection to inspect number of arguments expected by this callback.
+ // We did some careful benchmarking here: Using reflection to avoid unneeded
+ // function arguments is actually faster than blindly passing them.
+ // Also, this helps avoiding unnecessary function arguments in the call stack
+ // if the callback creates an Exception (creating garbage cycles).
+ if (\is_array($callback)) {
+ $ref = new \ReflectionMethod($callback[0], $callback[1]);
+ } elseif (\is_object($callback) && !$callback instanceof \Closure) {
+ $ref = new \ReflectionMethod($callback, '__invoke');
+ } else {
+ $ref = new \ReflectionFunction($callback);
+ }
+ $args = $ref->getNumberOfParameters();
+
+ try {
+ if ($args === 0) {
+ $callback();
+ } else {
+ // Keep references to this promise instance for the static resolve/reject functions.
+ // By using static callbacks that are not bound to this instance
+ // and passing the target promise instance by reference, we can
+ // still execute its resolving logic and still clear this
+ // reference when settling the promise. This helps avoiding
+ // garbage cycles if any callback creates an Exception.
+ // These assumptions are covered by the test suite, so if you ever feel like
+ // refactoring this, go ahead, any alternative suggestions are welcome!
+ $target =& $this;
+ $progressHandlers =& $this->progressHandlers;
+
+ $callback(
+ static function ($value = null) use (&$target) {
+ if ($target !== null) {
+ $target->settle(resolve($value));
+ $target = null;
+ }
+ },
+ static function ($reason = null) use (&$target) {
+ if ($target !== null) {
+ $target->reject($reason);
+ $target = null;
+ }
+ },
+ static function ($update = null) use (&$progressHandlers) {
+ foreach ($progressHandlers as $handler) {
+ $handler($update);
+ }
+ }
+ );
+ }
+ } catch (\Throwable $e) {
+ $target = null;
+ $this->reject($e);
+ } catch (\Exception $e) {
+ $target = null;
+ $this->reject($e);
+ }
+ }
+}
diff --git a/instafeed/vendor/react/promise/src/PromiseInterface.php b/instafeed/vendor/react/promise/src/PromiseInterface.php
new file mode 100755
index 0000000..fcd763d
--- /dev/null
+++ b/instafeed/vendor/react/promise/src/PromiseInterface.php
@@ -0,0 +1,14 @@
+reason = $reason;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ return $this;
+ }
+
+ try {
+ return resolve($onRejected($this->reason));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ throw UnhandledRejectionException::resolve($this->reason);
+ }
+
+ $result = $onRejected($this->reason);
+
+ if ($result instanceof self) {
+ throw UnhandledRejectionException::resolve($result->reason);
+ }
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ if (!_checkTypehint($onRejected, $this->reason)) {
+ return $this;
+ }
+
+ return $this->then(null, $onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(null, function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/instafeed/vendor/react/promise/src/UnhandledRejectionException.php b/instafeed/vendor/react/promise/src/UnhandledRejectionException.php
new file mode 100755
index 0000000..e7fe2f7
--- /dev/null
+++ b/instafeed/vendor/react/promise/src/UnhandledRejectionException.php
@@ -0,0 +1,31 @@
+reason = $reason;
+
+ $message = \sprintf('Unhandled Rejection: %s', \json_encode($reason));
+
+ parent::__construct($message, 0);
+ }
+
+ public function getReason()
+ {
+ return $this->reason;
+ }
+}
diff --git a/instafeed/vendor/react/promise/src/functions.php b/instafeed/vendor/react/promise/src/functions.php
new file mode 100755
index 0000000..c549e4e
--- /dev/null
+++ b/instafeed/vendor/react/promise/src/functions.php
@@ -0,0 +1,246 @@
+then($resolve, $reject, $notify);
+ }, $canceller);
+ }
+
+ return new FulfilledPromise($promiseOrValue);
+}
+
+function reject($promiseOrValue = null)
+{
+ if ($promiseOrValue instanceof PromiseInterface) {
+ return resolve($promiseOrValue)->then(function ($value) {
+ return new RejectedPromise($value);
+ });
+ }
+
+ return new RejectedPromise($promiseOrValue);
+}
+
+function all($promisesOrValues)
+{
+ return map($promisesOrValues, function ($val) {
+ return $val;
+ });
+}
+
+function race($promisesOrValues)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) {
+ if (!is_array($array) || !$array) {
+ $resolve();
+ return;
+ }
+
+ foreach ($array as $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($resolve, $reject, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function any($promisesOrValues)
+{
+ return some($promisesOrValues, 1)
+ ->then(function ($val) {
+ return \array_shift($val);
+ });
+}
+
+function some($promisesOrValues, $howMany)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array) || $howMany < 1) {
+ $resolve([]);
+ return;
+ }
+
+ $len = \count($array);
+
+ if ($len < $howMany) {
+ throw new Exception\LengthException(
+ \sprintf(
+ 'Input array must contain at least %d item%s but contains only %s item%s.',
+ $howMany,
+ 1 === $howMany ? '' : 's',
+ $len,
+ 1 === $len ? '' : 's'
+ )
+ );
+ }
+
+ $toResolve = $howMany;
+ $toReject = ($len - $toResolve) + 1;
+ $values = [];
+ $reasons = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $values[$i] = $val;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ };
+
+ $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $reasons[$i] = $reason;
+
+ if (0 === --$toReject) {
+ $reject($reasons);
+ }
+ };
+
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($fulfiller, $rejecter, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function map($promisesOrValues, callable $mapFunc)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array) || !$array) {
+ $resolve([]);
+ return;
+ }
+
+ $toResolve = \count($array);
+ $values = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+ $values[$i] = null;
+
+ resolve($promiseOrValue)
+ ->then($mapFunc)
+ ->done(
+ function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
+ $values[$i] = $mapped;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ },
+ $reject,
+ $notify
+ );
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array)) {
+ $array = [];
+ }
+
+ $total = \count($array);
+ $i = 0;
+
+ // Wrap the supplied $reduceFunc with one that handles promises and then
+ // delegates to the supplied.
+ $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
+ $cancellationQueue->enqueue($val);
+
+ return $current
+ ->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
+ return resolve($val)
+ ->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
+ return $reduceFunc($c, $value, $i++, $total);
+ });
+ });
+ };
+
+ $cancellationQueue->enqueue($initialValue);
+
+ \array_reduce($array, $wrappedReduceFunc, resolve($initialValue))
+ ->done($resolve, $reject, $notify);
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+// Internal functions
+function _checkTypehint(callable $callback, $object)
+{
+ if (!\is_object($object)) {
+ return true;
+ }
+
+ if (\is_array($callback)) {
+ $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
+ } elseif (\is_object($callback) && !$callback instanceof \Closure) {
+ $callbackReflection = new \ReflectionMethod($callback, '__invoke');
+ } else {
+ $callbackReflection = new \ReflectionFunction($callback);
+ }
+
+ $parameters = $callbackReflection->getParameters();
+
+ if (!isset($parameters[0])) {
+ return true;
+ }
+
+ $expectedException = $parameters[0];
+
+ if (!$expectedException->getClass()) {
+ return true;
+ }
+
+ return $expectedException->getClass()->isInstance($object);
+}
diff --git a/instafeed/vendor/react/promise/src/functions_include.php b/instafeed/vendor/react/promise/src/functions_include.php
new file mode 100755
index 0000000..bd0c54f
--- /dev/null
+++ b/instafeed/vendor/react/promise/src/functions_include.php
@@ -0,0 +1,5 @@
+enqueue($p);
+
+ $cancellationQueue();
+
+ $this->assertTrue($p->cancelCalled);
+ }
+
+ /** @test */
+ public function ignoresSimpleCancellable()
+ {
+ $p = new SimpleTestCancellable();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($p);
+
+ $cancellationQueue();
+
+ $this->assertFalse($p->cancelCalled);
+ }
+
+ /** @test */
+ public function callsCancelOnPromisesEnqueuedBeforeStart()
+ {
+ $d1 = $this->getCancellableDeferred();
+ $d2 = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($d1->promise());
+ $cancellationQueue->enqueue($d2->promise());
+
+ $cancellationQueue();
+ }
+
+ /** @test */
+ public function callsCancelOnPromisesEnqueuedAfterStart()
+ {
+ $d1 = $this->getCancellableDeferred();
+ $d2 = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+
+ $cancellationQueue();
+
+ $cancellationQueue->enqueue($d2->promise());
+ $cancellationQueue->enqueue($d1->promise());
+ }
+
+ /** @test */
+ public function doesNotCallCancelTwiceWhenStartedTwice()
+ {
+ $d = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($d->promise());
+
+ $cancellationQueue();
+ $cancellationQueue();
+ }
+
+ /** @test */
+ public function rethrowsExceptionsThrownFromCancel()
+ {
+ $this->setExpectedException('\Exception', 'test');
+
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel')
+ ->will($this->throwException(new \Exception('test')));
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($mock);
+
+ $cancellationQueue();
+ }
+
+ private function getCancellableDeferred()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return new Deferred($mock);
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/DeferredTest.php b/instafeed/vendor/react/promise/tests/DeferredTest.php
new file mode 100755
index 0000000..8ee40b8
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/DeferredTest.php
@@ -0,0 +1,112 @@
+ [$d, 'promise'],
+ 'resolve' => [$d, 'resolve'],
+ 'reject' => [$d, 'reject'],
+ 'notify' => [$d, 'progress'],
+ 'settle' => [$d, 'resolve'],
+ ]);
+ }
+
+ /** @test */
+ public function progressIsAnAliasForNotify()
+ {
+ $deferred = new Deferred();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $deferred->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $deferred->progress($sentinel);
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $deferred->promise()->cancel();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfParentCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $deferred->promise()->then()->cancel();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenceAndExplicitlyRejectWithException()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function () use (&$deferred) { });
+ $deferred->reject(new \Exception('foo'));
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferred()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred();
+ $deferred->promise();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithUnusedCanceller()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function () { });
+ $deferred->promise();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithNoopCanceller()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function () { });
+ $deferred->promise()->cancel();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FulfilledPromiseTest.php b/instafeed/vendor/react/promise/tests/FulfilledPromiseTest.php
new file mode 100755
index 0000000..f5a2da8
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FulfilledPromiseTest.php
@@ -0,0 +1,76 @@
+ function () use (&$promise) {
+ if (!$promise) {
+ throw new \LogicException('FulfilledPromise must be resolved before obtaining the promise');
+ }
+
+ return $promise;
+ },
+ 'resolve' => function ($value = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new FulfilledPromise($value);
+ }
+ },
+ 'reject' => function () {
+ throw new \LogicException('You cannot call reject() for React\Promise\FulfilledPromise');
+ },
+ 'notify' => function () {
+ // no-op
+ },
+ 'settle' => function ($value = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new FulfilledPromise($value);
+ }
+ },
+ ]);
+ }
+
+ /** @test */
+ public function shouldThrowExceptionIfConstructedWithAPromise()
+ {
+ $this->setExpectedException('\InvalidArgumentException');
+
+ return new FulfilledPromise(new FulfilledPromise());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithAlwaysFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new FulfilledPromise(1);
+ $promise->always(function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithThenFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new FulfilledPromise(1);
+ $promise = $promise->then(function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FunctionAllTest.php b/instafeed/vendor/react/promise/tests/FunctionAllTest.php
new file mode 100755
index 0000000..74c1d7c
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FunctionAllTest.php
@@ -0,0 +1,114 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ all([])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all([1, 2, 3])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all([resolve(1), resolve(2), resolve(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([null, 1, null, 1, 1]));
+
+ all([null, 1, null, 1, 1])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfAnyInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ all([resolve(1), reject(2), resolve(3)])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all(resolve([1, 2, 3]))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ all(resolve(1))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ $deferred = new Deferred();
+
+ all([resolve(1), $deferred->promise(), resolve(3)])
+ ->then($mock);
+
+ $deferred->resolve(2);
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FunctionAnyTest.php b/instafeed/vendor/react/promise/tests/FunctionAnyTest.php
new file mode 100755
index 0000000..140b551
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FunctionAnyTest.php
@@ -0,0 +1,204 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage();
+ })
+ );
+
+ any([])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullWithNonArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(null)
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithAnInputValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([1, 2, 3])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithAPromisedInputValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([resolve(1), resolve(2), resolve(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWithAllRejectedInputValuesIfAllInputsAreRejected()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([0 => 1, 1 => 2, 2 => 3]));
+
+ any([reject(1), reject(2), reject(3)])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWhenFirstInputPromiseResolves()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([resolve(1), reject(2), reject(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any(resolve([1, 2, 3]))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(resolve(1))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldNotRelyOnArryIndexesWhenUnwrappingToASingleResolutionValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+
+ any(['abc' => $d1->promise(), 1 => $d2->promise()])
+ ->then($mock);
+
+ $d2->resolve(2);
+ $d1->resolve(1);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(reject())
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ any($mock)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ any([$mock1, $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 1)->cancel();
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FunctionCheckTypehintTest.php b/instafeed/vendor/react/promise/tests/FunctionCheckTypehintTest.php
new file mode 100755
index 0000000..8449bc1
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FunctionCheckTypehintTest.php
@@ -0,0 +1,118 @@
+assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptFunctionStringCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithTypehint', new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint('React\Promise\testCallbackWithTypehint', new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptInvokableObjectCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint(new TestCallbackWithTypehintClass(), new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(new TestCallbackWithTypehintClass(), new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptObjectMethodCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptStaticClassCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptClosureCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptFunctionStringCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithoutTypehint', new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptInvokableObjectCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(new TestCallbackWithoutTypehintClass(), new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptObjectMethodCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint([new TestCallbackWithoutTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptStaticClassCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithoutTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
+ }
+}
+
+function testCallbackWithTypehint(\InvalidArgumentException $e)
+{
+}
+
+function testCallbackWithoutTypehint()
+{
+}
+
+class TestCallbackWithTypehintClass
+{
+ public function __invoke(\InvalidArgumentException $e)
+ {
+
+ }
+
+ public function testCallback(\InvalidArgumentException $e)
+ {
+
+ }
+
+ public static function testCallbackStatic(\InvalidArgumentException $e)
+ {
+
+ }
+}
+
+class TestCallbackWithoutTypehintClass
+{
+ public function __invoke()
+ {
+
+ }
+
+ public function testCallback()
+ {
+
+ }
+
+ public static function testCallbackStatic()
+ {
+
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FunctionMapTest.php b/instafeed/vendor/react/promise/tests/FunctionMapTest.php
new file mode 100755
index 0000000..1ea560a
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FunctionMapTest.php
@@ -0,0 +1,198 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, 2, 3],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapInputPromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapMixedInputArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, resolve(2), 3],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapInputWhenMapperReturnsAPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, 2, 3],
+ $this->promiseMapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ resolve([1, resolve(2), 3]),
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ map(
+ resolve(1),
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ $deferred = new Deferred();
+
+ map(
+ [resolve(1), $deferred->promise(), resolve(3)],
+ $this->mapper()
+ )->then($mock);
+
+ $deferred->resolve(2);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputContainsRejection()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ map(
+ [resolve(1), reject(2), resolve(3)],
+ $this->mapper()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ map(
+ reject(),
+ $this->mapper()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ map(
+ $mock,
+ $this->mapper()
+ )->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ map(
+ [$mock1, $mock2],
+ $this->mapper()
+ )->cancel();
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FunctionRaceTest.php b/instafeed/vendor/react/promise/tests/FunctionRaceTest.php
new file mode 100755
index 0000000..83770ec
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FunctionRaceTest.php
@@ -0,0 +1,211 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ []
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ race(
+ [1, 2, 3]
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ race(
+ [$d1->promise(), $d2->promise(), $d3->promise()]
+ )->then($mock);
+
+ $d2->resolve(2);
+
+ $d1->resolve(1);
+ $d3->resolve(3);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ [null, 1, null, 2, 3]
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfFirstSettledPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ race(
+ [$d1->promise(), $d2->promise(), $d3->promise()]
+ )->then($this->expectCallableNever(), $mock);
+
+ $d2->reject(2);
+
+ $d1->resolve(1);
+ $d3->resolve(3);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ race(
+ resolve([1, 2, 3])
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ reject()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ race($mock)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ race([$mock1, $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ race([$deferred->promise(), $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->reject();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ race([$deferred->promise(), $mock2])->cancel();
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FunctionReduceTest.php b/instafeed/vendor/react/promise/tests/FunctionReduceTest.php
new file mode 100755
index 0000000..8b43a87
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FunctionReduceTest.php
@@ -0,0 +1,347 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(6));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceValuesWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceValuesWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithoutInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(6));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceEmptyInputWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ [],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceEmptyInputWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ [],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputContainsRejection()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ reduce(
+ [resolve(1), reject(2), resolve(3)],
+ $this->plus(),
+ resolve(1)
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithNullWhenInputIsEmptyAndNoInitialValueOrPromiseProvided()
+ {
+ // Note: this is different from when.js's behavior!
+ // In when.reduce(), this rejects with a TypeError exception (following
+ // JavaScript's [].reduce behavior.
+ // We're following PHP's array_reduce behavior and resolve with NULL.
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ reduce(
+ [],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAllowSparseArrayInputWithoutInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(3));
+
+ reduce(
+ [null, null, 1, null, 1, 1],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAllowSparseArrayInputWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(4));
+
+ reduce(
+ [null, null, 1, null, 1, 1],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceInInputOrder()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('123'));
+
+ reduce(
+ [1, 2, 3],
+ $this->append(),
+ ''
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('123'));
+
+ reduce(
+ resolve([1, 2, 3]),
+ $this->append(),
+ ''
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToInitialValueWhenInputPromiseDoesNotResolveToAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ resolve(1),
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldProvideCorrectBasisValue()
+ {
+ $insertIntoArray = function ($arr, $val, $i) {
+ $arr[$i] = $val;
+
+ return $arr;
+ };
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ reduce(
+ [$d1->promise(), $d2->promise(), $d3->promise()],
+ $insertIntoArray,
+ []
+ )->then($mock);
+
+ $d3->resolve(3);
+ $d1->resolve(1);
+ $d2->resolve(2);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ reduce(
+ reject(),
+ $this->plus(),
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ reduce(
+ $mock,
+ $this->plus(),
+ 1
+ )->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ reduce(
+ [$mock1, $mock2],
+ $this->plus(),
+ 1
+ )->cancel();
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FunctionRejectTest.php b/instafeed/vendor/react/promise/tests/FunctionRejectTest.php
new file mode 100755
index 0000000..84b8ec6
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FunctionRejectTest.php
@@ -0,0 +1,64 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($expected)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldRejectAFulfilledPromise()
+ {
+ $expected = 123;
+
+ $resolved = new FulfilledPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldRejectARejectedPromise()
+ {
+ $expected = 123;
+
+ $resolved = new RejectedPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FunctionResolveTest.php b/instafeed/vendor/react/promise/tests/FunctionResolveTest.php
new file mode 100755
index 0000000..53126bc
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FunctionResolveTest.php
@@ -0,0 +1,171 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($expected)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveAFulfilledPromise()
+ {
+ $expected = 123;
+
+ $resolved = new FulfilledPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($resolved)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveAThenable()
+ {
+ $thenable = new SimpleFulfilledTestThenable();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ resolve($thenable)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveACancellableThenable()
+ {
+ $thenable = new SimpleTestCancellableThenable();
+
+ $promise = resolve($thenable);
+ $promise->cancel();
+
+ $this->assertTrue($thenable->cancelCalled);
+ }
+
+ /** @test */
+ public function shouldRejectARejectedPromise()
+ {
+ $expected = 123;
+
+ $resolved = new RejectedPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldSupportDeepNestingInPromiseChains()
+ {
+ $d = new Deferred();
+ $d->resolve(false);
+
+ $result = resolve(resolve($d->promise()->then(function ($val) {
+ $d = new Deferred();
+ $d->resolve($val);
+
+ $identity = function ($val) {
+ return $val;
+ };
+
+ return resolve($d->promise()->then($identity))->then(
+ function ($val) {
+ return !$val;
+ }
+ );
+ })));
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $result->then($mock);
+ }
+
+ /** @test */
+ public function shouldSupportVeryDeepNestedPromises()
+ {
+ $deferreds = [];
+
+ // @TODO Increase count once global-queue is merged
+ for ($i = 0; $i < 10; $i++) {
+ $deferreds[] = $d = new Deferred();
+ $p = $d->promise();
+
+ $last = $p;
+ for ($j = 0; $j < 10; $j++) {
+ $last = $last->then(function($result) {
+ return $result;
+ });
+ }
+ }
+
+ $p = null;
+ foreach ($deferreds as $d) {
+ if ($p) {
+ $d->resolve($p);
+ }
+
+ $p = $d->promise();
+ }
+
+ $deferreds[0]->resolve(true);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $deferreds[0]->promise()->then($mock);
+ }
+
+ /** @test */
+ public function returnsExtendePromiseForSimplePromise()
+ {
+ $promise = $this
+ ->getMockBuilder('React\Promise\PromiseInterface')
+ ->getMock();
+
+ $this->assertInstanceOf('React\Promise\ExtendedPromiseInterface', resolve($promise));
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/FunctionSomeTest.php b/instafeed/vendor/react/promise/tests/FunctionSomeTest.php
new file mode 100755
index 0000000..276b54b
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/FunctionSomeTest.php
@@ -0,0 +1,258 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage();
+ })
+ );
+
+ some(
+ [],
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldRejectWithLengthExceptionWithInputArrayContainingNotEnoughItems()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 4 items but contains only 3 items.' === $exception->getMessage();
+ })
+ );
+
+ some(
+ [1, 2, 3],
+ 4
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWithNonArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ null,
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ [1, 2, 3],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ [resolve(1), resolve(2), resolve(3)],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([null, 1]));
+
+ some(
+ [null, 1, null, 2, 3],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfAnyInputPromiseRejectsBeforeDesiredNumberOfInputsAreResolved()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1 => 2, 2 => 3]));
+
+ some(
+ [resolve(1), reject(2), reject(3)],
+ 2
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ resolve([1, 2, 3]),
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithEmptyArrayIfHowManyIsLessThanOne()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ [1],
+ 0
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ resolve(1),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ some(
+ reject(),
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ some($mock, 1)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ some([$mock1, $mock2], 1)->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesFulfill()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 1);
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesReject()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->reject();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 2);
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/LazyPromiseTest.php b/instafeed/vendor/react/promise/tests/LazyPromiseTest.php
new file mode 100755
index 0000000..b630881
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/LazyPromiseTest.php
@@ -0,0 +1,107 @@
+promise();
+ };
+
+ return new CallbackPromiseAdapter([
+ 'promise' => function () use ($factory) {
+ return new LazyPromise($factory);
+ },
+ 'resolve' => [$d, 'resolve'],
+ 'reject' => [$d, 'reject'],
+ 'notify' => [$d, 'progress'],
+ 'settle' => [$d, 'resolve'],
+ ]);
+ }
+
+ /** @test */
+ public function shouldNotCallFactoryIfThenIsNotInvoked()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->never())
+ ->method('__invoke');
+
+ new LazyPromise($factory);
+ }
+
+ /** @test */
+ public function shouldCallFactoryIfThenIsInvoked()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke');
+
+ $p = new LazyPromise($factory);
+ $p->then();
+ }
+
+ /** @test */
+ public function shouldReturnPromiseFromFactory()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue(new FulfilledPromise(1)));
+
+ $onFulfilled = $this->createCallableMock();
+ $onFulfilled
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $p = new LazyPromise($factory);
+
+ $p->then($onFulfilled);
+ }
+
+ /** @test */
+ public function shouldReturnPromiseIfFactoryReturnsNull()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue(null));
+
+ $p = new LazyPromise($factory);
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then());
+ }
+
+ /** @test */
+ public function shouldReturnRejectedPromiseIfFactoryThrowsException()
+ {
+ $exception = new \Exception();
+
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $onRejected = $this->createCallableMock();
+ $onRejected
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $p = new LazyPromise($factory);
+
+ $p->then($this->expectCallableNever(), $onRejected);
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php b/instafeed/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php
new file mode 100755
index 0000000..bdedf46
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php
@@ -0,0 +1,40 @@
+callbacks = $callbacks;
+ }
+
+ public function promise()
+ {
+ return call_user_func_array($this->callbacks['promise'], func_get_args());
+ }
+
+ public function resolve()
+ {
+ return call_user_func_array($this->callbacks['resolve'], func_get_args());
+ }
+
+ public function reject()
+ {
+ return call_user_func_array($this->callbacks['reject'], func_get_args());
+ }
+
+ public function notify()
+ {
+ return call_user_func_array($this->callbacks['notify'], func_get_args());
+ }
+
+ public function settle()
+ {
+ return call_user_func_array($this->callbacks['settle'], func_get_args());
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php b/instafeed/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php
new file mode 100755
index 0000000..9157cd4
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php
@@ -0,0 +1,14 @@
+ function () use ($promise) {
+ return $promise;
+ },
+ 'resolve' => $resolveCallback,
+ 'reject' => $rejectCallback,
+ 'notify' => $progressCallback,
+ 'settle' => $resolveCallback,
+ ]);
+ }
+
+ /** @test */
+ public function shouldRejectIfResolverThrowsException()
+ {
+ $exception = new \Exception('foo');
+
+ $promise = new Promise(function () use ($exception) {
+ throw $exception;
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $promise
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithoutCreatingGarbageCyclesIfResolverResolvesWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve) {
+ $resolve(new \Exception('foo'));
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsExceptionWithoutResolver()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () {
+ throw new \Exception('foo');
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverRejectsWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfParentCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $promise->then()->then()->then()->cancel();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) {
+ throw new \Exception('foo');
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /**
+ * Test that checks number of garbage cycles after throwing from a canceller
+ * that explicitly uses a reference to the promise. This is rather synthetic,
+ * actual use cases often have implicit (hidden) references which ought not
+ * to be stored in the stack trace.
+ *
+ * Reassigned arguments only show up in the stack trace in PHP 7, so we can't
+ * avoid this on legacy PHP. As an alternative, consider explicitly unsetting
+ * any references before throwing.
+ *
+ * @test
+ * @requires PHP 7
+ */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () {}, function () use (&$promise) {
+ throw new \Exception('foo');
+ });
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /**
+ * @test
+ * @requires PHP 7
+ * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
+ */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverWithReferenceThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () use (&$promise) {
+ throw new \Exception('foo');
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /**
+ * @test
+ * @requires PHP 7
+ * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
+ */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenceAndResolverThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () {
+ throw new \Exception('foo');
+ }, function () use (&$promise) { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldIgnoreNotifyAfterReject()
+ {
+ $promise = new Promise(function () { }, function ($resolve, $reject, $notify) {
+ $reject(new \Exception('foo'));
+ $notify(42);
+ });
+
+ $promise->then(null, null, $this->expectCallableNever());
+ $promise->cancel();
+ }
+
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromise()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithThenFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->then()->then()->then();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithDoneFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->done();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithOtherwiseFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->otherwise(function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithAlwaysFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->always(function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithProgressFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->then(null, null, function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldFulfillIfFullfilledWithSimplePromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(new SimpleFulfilledTestPromise());
+ }
+
+ /** @test */
+ public function shouldRejectIfRejectedWithSimplePromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->resolve(new SimpleRejectedTestPromise());
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php b/instafeed/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php
new file mode 100755
index 0000000..2baab02
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php
@@ -0,0 +1,246 @@
+getPromiseTestAdapter(function ($resolve, $reject, $notify) use (&$args) {
+ $args = func_get_args();
+ });
+
+ $adapter->promise()->cancel();
+
+ $this->assertCount(3, $args);
+ $this->assertTrue(is_callable($args[0]));
+ $this->assertTrue(is_callable($args[1]));
+ $this->assertTrue(is_callable($args[2]));
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerWithoutArgumentsIfNotAccessed()
+ {
+ $args = null;
+ $adapter = $this->getPromiseTestAdapter(function () use (&$args) {
+ $args = func_num_args();
+ });
+
+ $adapter->promise()->cancel();
+
+ $this->assertSame(0, $args);
+ }
+
+ /** @test */
+ public function cancelShouldFulfillPromiseIfCancellerFulfills()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve) {
+ $resolve(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock, $this->expectCallableNever());
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldRejectPromiseIfCancellerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject) {
+ $reject(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldRejectPromiseWithExceptionIfCancellerThrows()
+ {
+ $e = new \Exception();
+
+ $adapter = $this->getPromiseTestAdapter(function () use ($e) {
+ throw $e;
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($e));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldProgressPromiseIfCancellerNotifies()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject, $progress) {
+ $progress(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerOnlyOnceIfCancellerResolves()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnCallback(function ($resolve) {
+ $resolve();
+ }));
+
+ $adapter = $this->getPromiseTestAdapter($mock);
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectIfCancellerDoesNothing()
+ {
+ $adapter = $this->getPromiseTestAdapter(function () {});
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever());
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerFromDeepNestedPromiseChain()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ $adapter = $this->getPromiseTestAdapter($mock);
+
+ $promise = $adapter->promise()
+ ->then(function () {
+ return new Promise\Promise(function () {});
+ })
+ ->then(function () {
+ $d = new Promise\Deferred();
+
+ return $d->promise();
+ })
+ ->then(function () {
+ return new Promise\Promise(function () {});
+ });
+
+ $promise->cancel();
+ }
+
+ /** @test */
+ public function cancelCalledOnChildrenSouldOnlyCancelWhenAllChildrenCancelled()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldTriggerCancellerWhenAllChildrenCancel()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $child2 = $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ $child2->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldNotTriggerCancellerWhenCancellingOneChildrenMultipleTimes()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $child2 = $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ $child1->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldTriggerCancellerOnlyOnceWhenCancellingMultipleTimes()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldAlwaysTriggerCancellerWhenCalledOnRootPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $adapter->promise()
+ ->then()
+ ->then();
+
+ $adapter->promise()
+ ->then();
+
+ $adapter->promise()->cancel();
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseTest/FullTestTrait.php b/instafeed/vendor/react/promise/tests/PromiseTest/FullTestTrait.php
new file mode 100755
index 0000000..3ce45d6
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseTest/FullTestTrait.php
@@ -0,0 +1,15 @@
+getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $adapter->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateProgressToDownstreamPromises()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnArgument(0));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateTransformedProgressToDownstreamPromises()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue($sentinel));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateCaughtExceptionValueAsProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $promise2 = $adapter2->promise();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ // resolve BEFORE attaching progress handler
+ $adapter->resolve();
+
+ $adapter->promise()
+ ->then(function () use ($promise2) {
+ return $promise2;
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $promise2 = $adapter2->promise();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(function () use ($promise2) {
+ return $promise2;
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ // resolve AFTER attaching progress handler
+ $adapter->resolve();
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressWhenResolvedWithAnotherPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue($sentinel));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->resolve($adapter2->promise());
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldAllowResolveAfterProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->at(0))
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+ $mock
+ ->expects($this->at(1))
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->notify(1);
+ $adapter->resolve(2);
+ }
+
+ /** @test */
+ public function notifyShouldAllowRejectAfterProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->at(0))
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+ $mock
+ ->expects($this->at(1))
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock,
+ $mock
+ );
+
+ $adapter->notify(1);
+ $adapter->reject(2);
+ }
+
+ /** @test */
+ public function notifyShouldReturnSilentlyOnProgressWhenAlreadyRejected()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(1);
+
+ $this->assertNull($adapter->notify());
+ }
+
+ /** @test */
+ public function notifyShouldInvokeProgressHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()->progress($mock);
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldInvokeProgressHandlerFromDone()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done(null, null, $mock));
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldThrowExceptionThrownProgressHandlerFromDone()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->notify(1);
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php b/instafeed/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php
new file mode 100755
index 0000000..428230b
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php
@@ -0,0 +1,351 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $adapter->resolve(2);
+
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function fulfilledPromiseShouldInvokeNewlyAddedCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve(1);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock, $this->expectCallableNever());
+ }
+
+ /** @test */
+ public function thenShouldForwardResultWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ null,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldForwardCallbackResultToNextCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return $val + 1;
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldForwardPromisedCallbackResultValueToNextCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return \React\Promise\resolve($val + 1);
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackReturnsARejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return \React\Promise\reject($val + 1);
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackThrows()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock2
+ );
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->resolve();
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function doneShouldInvokeFulfillmentHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done($mock));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownFulfillmentHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done(function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejectsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done(function () {
+ return \React\Promise\reject();
+ }));
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve(1);
+ $adapter->promise()->otherwise($this->expectCallableNever());
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {})
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromiseForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromiseForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php b/instafeed/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php
new file mode 100755
index 0000000..a4f48ee
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php
@@ -0,0 +1,68 @@
+getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then());
+ }
+
+ /** @test */
+ public function thenShouldReturnAllowNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null));
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function doneShouldReturnNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function doneShouldReturnAllowNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done(null, null, null));
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $adapter->promise()->otherwise($this->expectCallableNever());
+ }
+
+ /** @test */
+ public function alwaysShouldReturnAPromiseForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php b/instafeed/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php
new file mode 100755
index 0000000..98d1dcf
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php
@@ -0,0 +1,512 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $adapter->reject(2);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function rejectedPromiseShouldInvokeNewlyAddedCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(1);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldForwardUndefinedRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(null);
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function () {
+ // Presence of rejection handler is enough to switch back
+ // to resolve mode, even though it returns undefined.
+ // The ONLY way to propagate a rejection is to re-throw or
+ // return a rejected promise;
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyPropagate()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return $val + 1;
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return \React\Promise\resolve($val + 1);
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldPropagateRejectionsWhenErrbackThrows()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock2
+ );
+ }
+
+ /** @test */
+ public function shouldPropagateRejectionsWhenErrbackReturnsARejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return \React\Promise\reject($val + 1);
+ }
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function doneShouldInvokeRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, $mock));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownByRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function unhandledRejectionExceptionThrownByDoneHoldsRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $expected = new \stdClass();
+
+ $adapter->reject($expected);
+
+ try {
+ $adapter->promise()->done();
+ } catch (UnhandledRejectionException $e) {
+ $this->assertSame($expected, $e->getReason());
+ return;
+ }
+
+ $this->fail();
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejectsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject();
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionProvidedAsRejectionValueForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function doneShouldThrowWithDeepNestingPromiseChainsForRejectedPromise()
+ {
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $exception = new \Exception('UnhandledRejectionException');
+
+ $d = new Deferred();
+ $d->resolve();
+
+ $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
+ $d = new Deferred();
+ $d->resolve();
+
+ return \React\Promise\resolve($d->promise()->then(function () {}))->then(
+ function () use ($exception) {
+ throw $exception;
+ }
+ );
+ })));
+
+ $result->done();
+ }
+
+ /** @test */
+ public function doneShouldRecoverWhenRejectionHandlerCatchesExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ $this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
+
+ }));
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $adapter->promise()->otherwise($mock);
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeNonTypeHintedRejectionHandlerIfReasonIsAnExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function ($reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeRejectionHandlerIfReasonMatchesTypehintForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \InvalidArgumentException();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerIfReaonsDoesNotMatchTypehintForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->expectCallableNever();
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {})
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromiseForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromiseForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception1 = new \Exception();
+ $exception2 = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception2));
+
+ $adapter->reject($exception1);
+ $adapter->promise()
+ ->always(function () use ($exception2) {
+ throw $exception2;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception1 = new \Exception();
+ $exception2 = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception2));
+
+ $adapter->reject($exception1);
+ $adapter->promise()
+ ->always(function () use ($exception2) {
+ return \React\Promise\reject($exception2);
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->reject();
+
+ $adapter->promise()->cancel();
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php b/instafeed/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php
new file mode 100755
index 0000000..e363b6d
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php
@@ -0,0 +1,86 @@
+getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then());
+ }
+
+ /** @test */
+ public function thenShouldReturnAllowNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null));
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->settle();
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function doneShouldReturnNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertNull($adapter->promise()->done(null, function () {}));
+ }
+
+ /** @test */
+ public function doneShouldReturnAllowNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertNull($adapter->promise()->done(null, function () {}, null));
+ }
+
+ /** @test */
+ public function progressShouldNotInvokeProgressHandlerForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $adapter->promise()->progress($this->expectCallableNever());
+ $adapter->notify();
+ }
+
+ /** @test */
+ public function alwaysShouldReturnAPromiseForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php b/instafeed/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php
new file mode 100755
index 0000000..063f178
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php
@@ -0,0 +1,368 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function rejectShouldRejectWithFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(Promise\resolve(1));
+ }
+
+ /** @test */
+ public function rejectShouldRejectWithRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(Promise\reject(1));
+ }
+
+ /** @test */
+ public function rejectShouldForwardReasonWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function rejectShouldMakePromiseImmutable()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(null, function ($value) use ($adapter) {
+ $adapter->reject(3);
+
+ return Promise\reject($value);
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->reject(1);
+ $adapter->reject(2);
+ }
+
+ /** @test */
+ public function notifyShouldInvokeOtherwiseHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->otherwise($mock);
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldInvokeRejectionHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done(null, $mock));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownByRejectionHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done());
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject();
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRetunsPendingPromiseWhichRejectsLater()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $d = new Deferred();
+ $promise = $d->promise();
+
+ $this->assertNull($adapter->promise()->done(null, function () use ($promise) {
+ return $promise;
+ }));
+ $adapter->reject(1);
+ $d->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionProvidedAsRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done());
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ }
+
+ /** @test */
+ public function doneShouldThrowWithDeepNestingPromiseChains()
+ {
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $exception = new \Exception('UnhandledRejectionException');
+
+ $d = new Deferred();
+
+ $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
+ $d = new Deferred();
+ $d->resolve();
+
+ return \React\Promise\resolve($d->promise()->then(function () {}))->then(
+ function () use ($exception) {
+ throw $exception;
+ }
+ );
+ })));
+
+ $result->done();
+
+ $d->resolve();
+ }
+
+ /** @test */
+ public function doneShouldRecoverWhenRejectionHandlerCatchesException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
+
+ }));
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {})
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php b/instafeed/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php
new file mode 100755
index 0000000..0736d35
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php
@@ -0,0 +1,312 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function resolveShouldResolveWithPromisedValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(Promise\resolve(1));
+ }
+
+ /** @test */
+ public function resolveShouldRejectWhenResolvedWithRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->resolve(Promise\reject(1));
+ }
+
+ /** @test */
+ public function resolveShouldForwardValueWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(
+ null,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function resolveShouldMakePromiseImmutable()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(function ($value) use ($adapter) {
+ $adapter->resolve(3);
+
+ return $value;
+ })
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+
+ $adapter->resolve(1);
+ $adapter->resolve(2);
+ }
+
+ /**
+ * @test
+ */
+ public function resolveShouldRejectWhenResolvedWithItself()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(new \LogicException('Cannot resolve a promise with itself.'));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->resolve($adapter->promise());
+ }
+
+ /**
+ * @test
+ */
+ public function resolveShouldRejectWhenResolvedWithAPromiseWhichFollowsItself()
+ {
+ $adapter1 = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(new \LogicException('Cannot resolve a promise with itself.'));
+
+ $promise1 = $adapter1->promise();
+
+ $promise2 = $adapter2->promise();
+
+ $promise2->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter1->resolve($promise2);
+ $adapter2->resolve($promise1);
+ }
+
+ /** @test */
+ public function doneShouldInvokeFulfillmentHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done($mock));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownFulfillmentHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(function () {
+ return \React\Promise\reject();
+ }));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {})
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForFulfillment()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForFulfillment()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+
+ $adapter->resolve(1);
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/RejectedPromiseTest.php b/instafeed/vendor/react/promise/tests/RejectedPromiseTest.php
new file mode 100755
index 0000000..825f56c
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/RejectedPromiseTest.php
@@ -0,0 +1,76 @@
+ function () use (&$promise) {
+ if (!$promise) {
+ throw new \LogicException('RejectedPromise must be rejected before obtaining the promise');
+ }
+
+ return $promise;
+ },
+ 'resolve' => function () {
+ throw new \LogicException('You cannot call resolve() for React\Promise\RejectedPromise');
+ },
+ 'reject' => function ($reason = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new RejectedPromise($reason);
+ }
+ },
+ 'notify' => function () {
+ // no-op
+ },
+ 'settle' => function ($reason = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new RejectedPromise($reason);
+ }
+ },
+ ]);
+ }
+
+ /** @test */
+ public function shouldThrowExceptionIfConstructedWithAPromise()
+ {
+ $this->setExpectedException('\InvalidArgumentException');
+
+ return new RejectedPromise(new RejectedPromise());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithAlwaysFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new RejectedPromise(1);
+ $promise->always(function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithThenFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new RejectedPromise(1);
+ $promise = $promise->then(null, function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/Stub/CallableStub.php b/instafeed/vendor/react/promise/tests/Stub/CallableStub.php
new file mode 100755
index 0000000..0120893
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/Stub/CallableStub.php
@@ -0,0 +1,10 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function createCallableMock()
+ {
+ return $this
+ ->getMockBuilder('React\\Promise\Stub\CallableStub')
+ ->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/bootstrap.php b/instafeed/vendor/react/promise/tests/bootstrap.php
new file mode 100755
index 0000000..9b7f872
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/bootstrap.php
@@ -0,0 +1,7 @@
+addPsr4('React\\Promise\\', __DIR__);
diff --git a/instafeed/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php b/instafeed/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php
new file mode 100755
index 0000000..ef4d530
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php
@@ -0,0 +1,21 @@
+cancelCalled = true;
+ }
+}
diff --git a/instafeed/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php b/instafeed/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php
new file mode 100755
index 0000000..c0f1593
--- /dev/null
+++ b/instafeed/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php
@@ -0,0 +1,18 @@
+cancelCalled = true;
+ }
+}
diff --git a/instafeed/vendor/react/socket/.gitignore b/instafeed/vendor/react/socket/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/instafeed/vendor/react/socket/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/instafeed/vendor/react/socket/.travis.yml b/instafeed/vendor/react/socket/.travis.yml
new file mode 100755
index 0000000..fd937b3
--- /dev/null
+++ b/instafeed/vendor/react/socket/.travis.yml
@@ -0,0 +1,48 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+# - 7.0 # Mac OS X, ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - os: osx
+ language: generic
+ php: 7.0 # just to look right on travis
+ env:
+ - PACKAGE: php70
+ allow_failures:
+ - php: hhvm
+ - os: osx
+
+sudo: false
+
+install:
+ # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew tap homebrew/homebrew-php
+ echo "Installing PHP ..."
+ brew install "${PACKAGE}"
+ brew install "${PACKAGE}"-xdebug
+ brew link "${PACKAGE}"
+ echo "Installing composer ..."
+ curl -s http://getcomposer.org/installer | php
+ mv composer.phar /usr/local/bin/composer
+ fi
+ - composer install --no-interaction
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/instafeed/vendor/react/socket/CHANGELOG.md b/instafeed/vendor/react/socket/CHANGELOG.md
new file mode 100755
index 0000000..926fb46
--- /dev/null
+++ b/instafeed/vendor/react/socket/CHANGELOG.md
@@ -0,0 +1,465 @@
+# Changelog
+
+## 0.8.12 (2018-06-11)
+
+* Feature: Improve memory consumption for failed and cancelled connection attempts.
+ (#161 by @clue)
+
+* Improve test suite to fix Travis config to test against legacy PHP 5.3 again.
+ (#162 by @clue)
+
+## 0.8.11 (2018-04-24)
+
+* Feature: Improve memory consumption for cancelled connection attempts and
+ simplify skipping DNS lookup when connecting to IP addresses.
+ (#159 and #160 by @clue)
+
+## 0.8.10 (2018-02-28)
+
+* Feature: Update DNS dependency to support loading system default DNS
+ nameserver config on all supported platforms
+ (`/etc/resolv.conf` on Unix/Linux/Mac/Docker/WSL and WMIC on Windows)
+ (#152 by @clue)
+
+ This means that connecting to hosts that are managed by a local DNS server,
+ such as a corporate DNS server or when using Docker containers, will now
+ work as expected across all platforms with no changes required:
+
+ ```php
+ $connector = new Connector($loop);
+ $connector->connect('intranet.example:80')->then(function ($connection) {
+ // …
+ });
+ ```
+
+## 0.8.9 (2018-01-18)
+
+* Feature: Support explicitly choosing TLS version to negotiate with remote side
+ by respecting `crypto_method` context parameter for all classes.
+ (#149 by @clue)
+
+ By default, all connector and server classes support TLSv1.0+ and exclude
+ support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly
+ choose the TLS version you want to negotiate with the remote side:
+
+ ```php
+ // new: now supports 'crypto_method` context parameter for all classes
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+ )
+ ));
+ ```
+
+* Minor internal clean up to unify class imports
+ (#148 by @clue)
+
+## 0.8.8 (2018-01-06)
+
+* Improve test suite by adding test group to skip integration tests relying on
+ internet connection and fix minor documentation typo.
+ (#146 by @clue and #145 by @cn007b)
+
+## 0.8.7 (2017-12-24)
+
+* Fix: Fix closing socket resource before removing from loop
+ (#141 by @clue)
+
+ This fixes the root cause of an uncaught `Exception` that only manifested
+ itself after the recent Stream v0.7.4 component update and only if you're
+ using `ext-event` (`ExtEventLoop`).
+
+* Improve test suite by testing against PHP 7.2
+ (#140 by @carusogabriel)
+
+## 0.8.6 (2017-11-18)
+
+* Feature: Add Unix domain socket (UDS) support to `Server` with `unix://` URI scheme
+ and add advanced `UnixServer` class.
+ (#120 by @andig)
+
+ ```php
+ // new: Server now supports "unix://" scheme
+ $server = new Server('unix:///tmp/server.sock', $loop);
+
+ // new: advanced usage
+ $server = new UnixServer('/tmp/server.sock', $loop);
+ ```
+
+* Restructure examples to ease getting started
+ (#136 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#133 by @gabriel-caruso and #134 by @clue)
+
+## 0.8.5 (2017-10-23)
+
+* Fix: Work around PHP bug with Unix domain socket (UDS) paths for Mac OS X
+ (#123 by @andig)
+
+* Fix: Fix `SecureServer` to return `null` URI if server socket is already closed
+ (#129 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit v5 and
+ forward compatibility with upcoming EventLoop releases in tests and
+ test Mac OS X on Travis
+ (#122 by @andig and #125, #127 and #130 by @clue)
+
+* Readme improvements
+ (#118 by @jsor)
+
+## 0.8.4 (2017-09-16)
+
+* Feature: Add `FixedUriConnector` decorator to use fixed, preconfigured URI instead
+ (#117 by @clue)
+
+ This can be useful for consumers that do not support certain URIs, such as
+ when you want to explicitly connect to a Unix domain socket (UDS) path
+ instead of connecting to a default address assumed by an higher-level API:
+
+ ```php
+ $connector = new FixedUriConnector(
+ 'unix:///var/run/docker.sock',
+ new UnixConnector($loop)
+ );
+
+ // destination will be ignored, actually connects to Unix domain socket
+ $promise = $connector->connect('localhost:80');
+ ```
+
+## 0.8.3 (2017-09-08)
+
+* Feature: Reduce memory consumption for failed connections
+ (#113 by @valga)
+
+* Fix: Work around write chunk size for TLS streams for PHP < 7.1.14
+ (#114 by @clue)
+
+## 0.8.2 (2017-08-25)
+
+* Feature: Update DNS dependency to support hosts file on all platforms
+ (#112 by @clue)
+
+ This means that connecting to hosts such as `localhost` will now work as
+ expected across all platforms with no changes required:
+
+ ```php
+ $connector = new Connector($loop);
+ $connector->connect('localhost:8080')->then(function ($connection) {
+ // …
+ });
+ ```
+
+## 0.8.1 (2017-08-15)
+
+* Feature: Forward compatibility with upcoming EventLoop v1.0 and v0.5 and
+ target evenement 3.0 a long side 2.0 and 1.0
+ (#104 by @clue and #111 by @WyriHaximus)
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ fix HHVM build for now again and ignore future HHVM build errors
+ (#109 and #110 by @clue)
+
+* Minor documentation fixes
+ (#103 by @christiaan and #108 by @hansott)
+
+## 0.8.0 (2017-05-09)
+
+* Feature: New `Server` class now acts as a facade for existing server classes
+ and renamed old `Server` to `TcpServer` for advanced usage.
+ (#96 and #97 by @clue)
+
+ The `Server` class is now the main class in this package that implements the
+ `ServerInterface` and allows you to accept incoming streaming connections,
+ such as plaintext TCP/IP or secure TLS connection streams.
+
+ > This is not a BC break and consumer code does not have to be updated.
+
+* Feature / BC break: All addresses are now URIs that include the URI scheme
+ (#98 by @clue)
+
+ ```diff
+ - $parts = parse_url('tcp://' . $conn->getRemoteAddress());
+ + $parts = parse_url($conn->getRemoteAddress());
+ ```
+
+* Fix: Fix `unix://` addresses for Unix domain socket (UDS) paths
+ (#100 by @clue)
+
+* Feature: Forward compatibility with Stream v1.0 and v0.7
+ (#99 by @clue)
+
+## 0.7.2 (2017-04-24)
+
+* Fix: Work around latest PHP 7.0.18 and 7.1.4 no longer accepting full URIs
+ (#94 by @clue)
+
+## 0.7.1 (2017-04-10)
+
+* Fix: Ignore HHVM errors when closing connection that is already closing
+ (#91 by @clue)
+
+## 0.7.0 (2017-04-10)
+
+* Feature: Merge SocketClient component into this component
+ (#87 by @clue)
+
+ This means that this package now provides async, streaming plaintext TCP/IP
+ and secure TLS socket server and client connections for ReactPHP.
+
+ ```
+ $connector = new React\Socket\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+ ```
+
+ Accordingly, the `ConnectionInterface` is now used to represent both incoming
+ server side connections as well as outgoing client side connections.
+
+ If you've previously used the SocketClient component to establish outgoing
+ client connections, upgrading should take no longer than a few minutes.
+ All classes have been merged as-is from the latest `v0.7.0` release with no
+ other changes, so you can simply update your code to use the updated namespace
+ like this:
+
+ ```php
+ // old from SocketClient component and namespace
+ $connector = new React\SocketClient\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+
+ // new
+ $connector = new React\Socket\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+ ```
+
+## 0.6.0 (2017-04-04)
+
+* Feature: Add `LimitingServer` to limit and keep track of open connections
+ (#86 by @clue)
+
+ ```php
+ $server = new Server(0, $loop);
+ $server = new LimitingServer($server, 100);
+
+ $server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+ });
+ ```
+
+* Feature / BC break: Add `pause()` and `resume()` methods to limit active
+ connections
+ (#84 by @clue)
+
+ ```php
+ $server = new Server(0, $loop);
+ $server->pause();
+
+ $loop->addTimer(1.0, function() use ($server) {
+ $server->resume();
+ });
+ ```
+
+## 0.5.1 (2017-03-09)
+
+* Feature: Forward compatibility with Stream v0.5 and upcoming v0.6
+ (#79 by @clue)
+
+## 0.5.0 (2017-02-14)
+
+* Feature / BC break: Replace `listen()` call with URIs passed to constructor
+ and reject listening on hostnames with `InvalidArgumentException`
+ and replace `ConnectionException` with `RuntimeException` for consistency
+ (#61, #66 and #72 by @clue)
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080);
+
+ // new
+ $server = new Server(8080, $loop);
+ ```
+
+ Similarly, you can now pass a full listening URI to the constructor to change
+ the listening host:
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080, '127.0.0.1');
+
+ // new
+ $server = new Server('127.0.0.1:8080', $loop);
+ ```
+
+ Trying to start listening on (DNS) host names will now throw an
+ `InvalidArgumentException`, use IP addresses instead:
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080, 'localhost');
+
+ // new
+ $server = new Server('127.0.0.1:8080', $loop);
+ ```
+
+ If trying to listen fails (such as if port is already in use or port below
+ 1024 may require root access etc.), it will now throw a `RuntimeException`,
+ the `ConnectionException` class has been removed:
+
+ ```php
+ // old: throws React\Socket\ConnectionException
+ $server = new Server($loop);
+ $server->listen(80);
+
+ // new: throws RuntimeException
+ $server = new Server(80, $loop);
+ ```
+
+* Feature / BC break: Rename `shutdown()` to `close()` for consistency throughout React
+ (#62 by @clue)
+
+ ```php
+ // old
+ $server->shutdown();
+
+ // new
+ $server->close();
+ ```
+
+* Feature / BC break: Replace `getPort()` with `getAddress()`
+ (#67 by @clue)
+
+ ```php
+ // old
+ echo $server->getPort(); // 8080
+
+ // new
+ echo $server->getAddress(); // 127.0.0.1:8080
+ ```
+
+* Feature / BC break: `getRemoteAddress()` returns full address instead of only IP
+ (#65 by @clue)
+
+ ```php
+ // old
+ echo $connection->getRemoteAddress(); // 192.168.0.1
+
+ // new
+ echo $connection->getRemoteAddress(); // 192.168.0.1:51743
+ ```
+
+* Feature / BC break: Add `getLocalAddress()` method
+ (#68 by @clue)
+
+ ```php
+ echo $connection->getLocalAddress(); // 127.0.0.1:8080
+ ```
+
+* BC break: The `Server` and `SecureServer` class are now marked `final`
+ and you can no longer `extend` them
+ (which was never documented or recommended anyway).
+ Public properties and event handlers are now internal only.
+ Please use composition instead of extension.
+ (#71, #70 and #69 by @clue)
+
+## 0.4.6 (2017-01-26)
+
+* Feature: Support socket context options passed to `Server`
+ (#64 by @clue)
+
+* Fix: Properly return `null` for unknown addresses
+ (#63 by @clue)
+
+* Improve documentation for `ServerInterface` and lock test suite requirements
+ (#60 by @clue, #57 by @shaunbramley)
+
+## 0.4.5 (2017-01-08)
+
+* Feature: Add `SecureServer` for secure TLS connections
+ (#55 by @clue)
+
+* Add functional integration tests
+ (#54 by @clue)
+
+## 0.4.4 (2016-12-19)
+
+* Feature / Fix: `ConnectionInterface` should extend `DuplexStreamInterface` + documentation
+ (#50 by @clue)
+
+* Feature / Fix: Improve test suite and switch to normal stream handler
+ (#51 by @clue)
+
+* Feature: Add examples
+ (#49 by @clue)
+
+## 0.4.3 (2016-03-01)
+
+* Bug fix: Suppress errors on stream_socket_accept to prevent PHP from crashing
+* Support for PHP7 and HHVM
+* Support PHP 5.3 again
+
+## 0.4.2 (2014-05-25)
+
+* Verify stream is a valid resource in Connection
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: Check read buffer for data before shutdown signal and end emit (@ArtyDev)
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: Reset socket to non-blocking after shutting down (PHP bug)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+* Bump React dependencies to v0.4
+
+## 0.3.3 (2013-07-08)
+
+* Version bump
+
+## 0.3.2 (2013-05-10)
+
+* Version bump
+
+## 0.3.1 (2013-04-21)
+
+* Feature: Support binding to IPv6 addresses (@clue)
+
+## 0.3.0 (2013-04-14)
+
+* Bump React dependencies to v0.3
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.0 (2012-09-10)
+
+* Bump React dependencies to v0.2
+
+## 0.1.1 (2012-07-12)
+
+* Version bump
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/instafeed/vendor/react/socket/LICENSE b/instafeed/vendor/react/socket/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/instafeed/vendor/react/socket/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/react/socket/README.md b/instafeed/vendor/react/socket/README.md
new file mode 100755
index 0000000..cf9b56b
--- /dev/null
+++ b/instafeed/vendor/react/socket/README.md
@@ -0,0 +1,1419 @@
+# Socket
+
+[](https://travis-ci.org/reactphp/socket)
+
+Async, streaming plaintext TCP/IP and secure TLS socket server and client
+connections for [ReactPHP](https://reactphp.org/).
+
+The socket library provides re-usable interfaces for a socket-layer
+server and client based on the [`EventLoop`](https://github.com/reactphp/event-loop)
+and [`Stream`](https://github.com/reactphp/stream) components.
+Its server component allows you to build networking servers that accept incoming
+connections from networking clients (such as an HTTP server).
+Its client component allows you to build networking clients that establish
+outgoing connections to networking servers (such as an HTTP or database client).
+This library provides async, streaming means for all of this, so you can
+handle multiple concurrent connections without blocking.
+
+**Table of Contents**
+
+* [Quickstart example](#quickstart-example)
+* [Connection usage](#connection-usage)
+ * [ConnectionInterface](#connectioninterface)
+ * [getRemoteAddress()](#getremoteaddress)
+ * [getLocalAddress()](#getlocaladdress)
+* [Server usage](#server-usage)
+ * [ServerInterface](#serverinterface)
+ * [connection event](#connection-event)
+ * [error event](#error-event)
+ * [getAddress()](#getaddress)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [close()](#close)
+ * [Server](#server)
+ * [Advanced server usage](#advanced-server-usage)
+ * [TcpServer](#tcpserver)
+ * [SecureServer](#secureserver)
+ * [UnixServer](#unixserver)
+ * [LimitingServer](#limitingserver)
+ * [getConnections()](#getconnections)
+* [Client usage](#client-usage)
+ * [ConnectorInterface](#connectorinterface)
+ * [connect()](#connect)
+ * [Connector](#connector)
+ * [Advanced client usage](#advanced-client-usage)
+ * [TcpConnector](#tcpconnector)
+ * [DnsConnector](#dnsconnector)
+ * [SecureConnector](#secureconnector)
+ * [TimeoutConnector](#timeoutconnector)
+ * [UnixConnector](#unixconnector)
+ * [FixUriConnector](#fixeduriconnector)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Quickstart example
+
+Here is a server that closes the connection if you send it anything:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
+
+$socket->on('connection', function (ConnectionInterface $conn) {
+ $conn->write("Hello " . $conn->getRemoteAddress() . "!\n");
+ $conn->write("Welcome to this amazing server!\n");
+ $conn->write("Here's a tip: don't say anything.\n");
+
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Here's a client that outputs the output of said server and then attempts to
+send it a string:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$connector = new React\Socket\Connector($loop);
+
+$connector->connect('127.0.0.1:8080')->then(function (ConnectionInterface $conn) use ($loop) {
+ $conn->pipe(new React\Stream\WritableResourceStream(STDOUT, $loop));
+ $conn->write("Hello World!\n");
+});
+
+$loop->run();
+```
+
+## Connection usage
+
+### ConnectionInterface
+
+The `ConnectionInterface` is used to represent any incoming and outgoing
+connection, such as a normal TCP/IP connection.
+
+An incoming or outgoing connection is a duplex stream (both readable and
+writable) that implements React's
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+It contains additional properties for the local and remote address (client IP)
+where this connection has been established to/from.
+
+Most commonly, instances implementing this `ConnectionInterface` are emitted
+by all classes implementing the [`ServerInterface`](#serverinterface) and
+used by all classes implementing the [`ConnectorInterface`](#connectorinterface).
+
+Because the `ConnectionInterface` implements the underlying
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
+you can use any of its events and methods as usual:
+
+```php
+$connection->on('data', function ($chunk) {
+ echo $chunk;
+});
+
+$connection->on('end', function () {
+ echo 'ended';
+});
+
+$connection->on('error', function (Exception $e) {
+ echo 'error: ' . $e->getMessage();
+});
+
+$connection->on('close', function () {
+ echo 'closed';
+});
+
+$connection->write($data);
+$connection->end($data = null);
+$connection->close();
+// …
+```
+
+For more details, see the
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+
+#### getRemoteAddress()
+
+The `getRemoteAddress(): ?string` method returns the full remote address
+(URI) where this connection has been established with.
+
+```php
+$address = $connection->getRemoteAddress();
+echo 'Connection with ' . $address . PHP_EOL;
+```
+
+If the remote address can not be determined or is unknown at this time (such as
+after the connection has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+If this is a TCP/IP based connection and you only want the remote IP, you may
+use something like this:
+
+```php
+$address = $connection->getRemoteAddress();
+$ip = trim(parse_url($address, PHP_URL_HOST), '[]');
+echo 'Connection with ' . $ip . PHP_EOL;
+```
+
+#### getLocalAddress()
+
+The `getLocalAddress(): ?string` method returns the full local address
+(URI) where this connection has been established with.
+
+```php
+$address = $connection->getLocalAddress();
+echo 'Connection with ' . $address . PHP_EOL;
+```
+
+If the local address can not be determined or is unknown at this time (such as
+after the connection has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
+so they should not be confused.
+
+If your `TcpServer` instance is listening on multiple interfaces (e.g. using
+the address `0.0.0.0`), you can use this method to find out which interface
+actually accepted this connection (such as a public or local interface).
+
+If your system has multiple interfaces (e.g. a WAN and a LAN interface),
+you can use this method to find out which interface was actually
+used for this connection.
+
+## Server usage
+
+### ServerInterface
+
+The `ServerInterface` is responsible for providing an interface for accepting
+incoming streaming connections, such as a normal TCP/IP connection.
+
+Most higher-level components (such as a HTTP server) accept an instance
+implementing this interface to accept incoming streaming connections.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against any other implementation of this interface.
+This means that you SHOULD typehint against this interface instead of a concrete
+implementation of this interface.
+
+Besides defining a few methods, this interface also implements the
+[`EventEmitterInterface`](https://github.com/igorw/evenement)
+which allows you to react to certain events.
+
+#### connection event
+
+The `connection` event will be emitted whenever a new connection has been
+established, i.e. a new client connects to this server socket:
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'new connection' . PHP_EOL;
+});
+```
+
+See also the [`ConnectionInterface`](#connectioninterface) for more details
+about handling the incoming connection.
+
+#### error event
+
+The `error` event will be emitted whenever there's an error accepting a new
+connection from a client.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+Note that this is not a fatal error event, i.e. the server keeps listening for
+new connections even after this event.
+
+
+#### getAddress()
+
+The `getAddress(): ?string` method can be used to
+return the full address (URI) this server is currently listening on.
+
+```php
+$address = $server->getAddress();
+echo 'Server listening on ' . $address . PHP_EOL;
+```
+
+If the address can not be determined or is unknown at this time (such as
+after the socket has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+If this is a TCP/IP based server and you only want the local port, you may
+use something like this:
+
+```php
+$address = $server->getAddress();
+$port = parse_url($address, PHP_URL_PORT);
+echo 'Server listening on port ' . $port . PHP_EOL;
+```
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause accepting new incoming connections.
+
+Removes the socket resource from the EventLoop and thus stop accepting
+new connections. Note that the listening socket stays active and is not
+closed.
+
+This means that new incoming connections will stay pending in the
+operating system backlog until its configurable backlog is filled.
+Once the backlog is filled, the operating system may reject further
+incoming connections until the backlog is drained again by resuming
+to accept new connections.
+
+Once the server is paused, no futher `connection` events SHOULD
+be emitted.
+
+```php
+$server->pause();
+
+$server->on('connection', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+server MAY continue emitting `connection` events.
+
+Unless otherwise noted, a successfully opened server SHOULD NOT start
+in paused state.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+Similarly, calling this after `close()` is a NO-OP.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume accepting new incoming connections.
+
+Re-attach the socket resource to the EventLoop after a previous `pause()`.
+
+```php
+$server->pause();
+
+$loop->addTimer(1.0, function () use ($server) {
+ $server->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+Similarly, calling this after `close()` is a NO-OP.
+
+#### close()
+
+The `close(): void` method can be used to
+shut down this listening socket.
+
+This will stop listening for new incoming connections on this socket.
+
+```php
+echo 'Shutting down server socket' . PHP_EOL;
+$server->close();
+```
+
+Calling this method more than once on the same instance is a NO-OP.
+
+### Server
+
+The `Server` class is the main class in this package that implements the
+[`ServerInterface`](#serverinterface) and allows you to accept incoming
+streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
+Connections can also be accepted on Unix domain sockets.
+
+```php
+$server = new Server(8080, $loop);
+```
+
+As above, the `$uri` parameter can consist of only a port, in which case the
+server will default to listening on the localhost address `127.0.0.1`,
+which means it will not be reachable from outside of this system.
+
+In order to use a random port assignment, you can use the port `0`:
+
+```php
+$server = new Server(0, $loop);
+$address = $server->getAddress();
+```
+
+In order to change the host the socket is listening on, you can provide an IP
+address through the first parameter provided to the constructor, optionally
+preceded by the `tcp://` scheme:
+
+```php
+$server = new Server('192.168.0.1:8080', $loop);
+```
+
+If you want to listen on an IPv6 address, you MUST enclose the host in square
+brackets:
+
+```php
+$server = new Server('[::1]:8080', $loop);
+```
+
+To listen on a Unix domain socket (UDS) path, you MUST prefix the URI with the
+`unix://` scheme:
+
+```php
+$server = new Server('unix:///tmp/server.sock', $loop);
+```
+
+If the given URI is invalid, does not contain a port, any other scheme or if it
+contains a hostname, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException due to missing port
+$server = new Server('127.0.0.1', $loop);
+```
+
+If the given URI appears to be valid, but listening on it fails (such as if port
+is already in use or port below 1024 may require root access etc.), it will
+throw a `RuntimeException`:
+
+```php
+$first = new Server(8080, $loop);
+
+// throws RuntimeException because port is already in use
+$second = new Server(8080, $loop);
+```
+
+> Note that these error conditions may vary depending on your system and/or
+ configuration.
+ See the exception message and code for more details about the actual error
+ condition.
+
+Optionally, you can specify [TCP socket context options](http://php.net/manual/en/context.socket.php)
+for the underlying stream socket resource like this:
+
+```php
+$server = new Server('[::1]:8080', $loop, array(
+ 'tcp' => array(
+ 'backlog' => 200,
+ 'so_reuseport' => true,
+ 'ipv6_v6only' => true
+ )
+));
+```
+
+> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+ their defaults and effects of changing these may vary depending on your system
+ and/or PHP version.
+ Passing unknown context options has no effect.
+ For BC reasons, you can also pass the TCP socket context options as a simple
+ array without wrapping this in another array under the `tcp` key.
+
+You can start a secure TLS (formerly known as SSL) server by simply prepending
+the `tls://` URI scheme.
+Internally, it will wait for plaintext TCP/IP connections and then performs a
+TLS handshake for each connection.
+It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
+which in its most basic form may look something like this if you're using a
+PEM encoded certificate file:
+
+```php
+$server = new Server('tls://127.0.0.1:8080', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem'
+ )
+));
+```
+
+> Note that the certificate file will not be loaded on instantiation but when an
+ incoming connection initializes its TLS context.
+ This implies that any invalid certificate file paths or contents will only cause
+ an `error` event at a later time.
+
+If your private key is encrypted with a passphrase, you have to specify it
+like this:
+
+```php
+$server = new Server('tls://127.0.0.1:8000', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem',
+ 'passphrase' => 'secret'
+ )
+));
+```
+
+By default, this server supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$server = new Server('tls://127.0.0.1:8000', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+ )
+));
+```
+
+> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
+ their defaults and effects of changing these may vary depending on your system
+ and/or PHP version.
+ The outer context array allows you to also use `tcp` (and possibly more)
+ context options at the same time.
+ Passing unknown context options has no effect.
+ If you do not use the `tls://` scheme, then passing `tls` context options
+ has no effect.
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+> Note that the `Server` class is a concrete implementation for TCP/IP sockets.
+ If you want to typehint in your higher-level protocol implementation, you SHOULD
+ use the generic [`ServerInterface`](#serverinterface) instead.
+
+### Advanced server usage
+
+#### TcpServer
+
+The `TcpServer` class implements the [`ServerInterface`](#serverinterface) and
+is responsible for accepting plaintext TCP/IP connections.
+
+```php
+$server = new TcpServer(8080, $loop);
+```
+
+As above, the `$uri` parameter can consist of only a port, in which case the
+server will default to listening on the localhost address `127.0.0.1`,
+which means it will not be reachable from outside of this system.
+
+In order to use a random port assignment, you can use the port `0`:
+
+```php
+$server = new TcpServer(0, $loop);
+$address = $server->getAddress();
+```
+
+In order to change the host the socket is listening on, you can provide an IP
+address through the first parameter provided to the constructor, optionally
+preceded by the `tcp://` scheme:
+
+```php
+$server = new TcpServer('192.168.0.1:8080', $loop);
+```
+
+If you want to listen on an IPv6 address, you MUST enclose the host in square
+brackets:
+
+```php
+$server = new TcpServer('[::1]:8080', $loop);
+```
+
+If the given URI is invalid, does not contain a port, any other scheme or if it
+contains a hostname, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException due to missing port
+$server = new TcpServer('127.0.0.1', $loop);
+```
+
+If the given URI appears to be valid, but listening on it fails (such as if port
+is already in use or port below 1024 may require root access etc.), it will
+throw a `RuntimeException`:
+
+```php
+$first = new TcpServer(8080, $loop);
+
+// throws RuntimeException because port is already in use
+$second = new TcpServer(8080, $loop);
+```
+
+> Note that these error conditions may vary depending on your system and/or
+configuration.
+See the exception message and code for more details about the actual error
+condition.
+
+Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php)
+for the underlying stream socket resource like this:
+
+```php
+$server = new TcpServer('[::1]:8080', $loop, array(
+ 'backlog' => 200,
+ 'so_reuseport' => true,
+ 'ipv6_v6only' => true
+));
+```
+
+> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+their defaults and effects of changing these may vary depending on your system
+and/or PHP version.
+Passing unknown context options has no effect.
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+#### SecureServer
+
+The `SecureServer` class implements the [`ServerInterface`](#serverinterface)
+and is responsible for providing a secure TLS (formerly known as SSL) server.
+
+It does so by wrapping a [`TcpServer`](#tcpserver) instance which waits for plaintext
+TCP/IP connections and then performs a TLS handshake for each connection.
+It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
+which in its most basic form may look something like this if you're using a
+PEM encoded certificate file:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem'
+));
+```
+
+> Note that the certificate file will not be loaded on instantiation but when an
+incoming connection initializes its TLS context.
+This implies that any invalid certificate file paths or contents will only cause
+an `error` event at a later time.
+
+If your private key is encrypted with a passphrase, you have to specify it
+like this:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem',
+ 'passphrase' => 'secret'
+));
+```
+
+By default, this server supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+));
+```
+
+> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
+their defaults and effects of changing these may vary depending on your system
+and/or PHP version.
+Passing unknown context options has no effect.
+
+Whenever a client completes the TLS handshake, it will emit a `connection` event
+with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+Whenever a client fails to perform a successful TLS handshake, it will emit an
+`error` event and then close the underlying TCP/IP connection:
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error' . $e->getMessage() . PHP_EOL;
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+Note that the `SecureServer` class is a concrete implementation for TLS sockets.
+If you want to typehint in your higher-level protocol implementation, you SHOULD
+use the generic [`ServerInterface`](#serverinterface) instead.
+
+> Advanced usage: Despite allowing any `ServerInterface` as first parameter,
+you SHOULD pass a `TcpServer` instance as first parameter, unless you
+know what you're doing.
+Internally, the `SecureServer` has to set the required TLS context options on
+the underlying stream resources.
+These resources are not exposed through any of the interfaces defined in this
+package, but only through the internal `Connection` class.
+The `TcpServer` class is guaranteed to emit connections that implement
+the `ConnectionInterface` and uses the internal `Connection` class in order to
+expose these underlying resources.
+If you use a custom `ServerInterface` and its `connection` event does not
+meet this requirement, the `SecureServer` will emit an `error` event and
+then close the underlying connection.
+
+#### UnixServer
+
+The `UnixServer` class implements the [`ServerInterface`](#serverinterface) and
+is responsible for accepting connections on Unix domain sockets (UDS).
+
+```php
+$server = new UnixServer('/tmp/server.sock', $loop);
+```
+
+As above, the `$uri` parameter can consist of only a socket path or socket path
+prefixed by the `unix://` scheme.
+
+If the given URI appears to be valid, but listening on it fails (such as if the
+socket is already in use or the file not accessible etc.), it will throw a
+`RuntimeException`:
+
+```php
+$first = new UnixServer('/tmp/same.sock', $loop);
+
+// throws RuntimeException because socket is already in use
+$second = new UnixServer('/tmp/same.sock', $loop);
+```
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'New connection' . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+#### LimitingServer
+
+The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
+for limiting and keeping track of open connections to this server instance.
+
+Whenever the underlying server emits a `connection` event, it will check its
+limits and then either
+ - keep track of this connection by adding it to the list of
+ open connections and then forward the `connection` event
+ - or reject (close) the connection when its limits are exceeded and will
+ forward an `error` event instead.
+
+Whenever a connection closes, it will remove this connection from the list of
+open connections.
+
+```php
+$server = new LimitingServer($server, 100);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [second example](examples) for more details.
+
+You have to pass a maximum number of open connections to ensure
+the server will automatically reject (close) connections once this limit
+is exceeded. In this case, it will emit an `error` event to inform about
+this and no `connection` event will be emitted.
+
+```php
+$server = new LimitingServer($server, 100);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+You MAY pass a `null` limit in order to put no limit on the number of
+open connections and keep accepting new connection until you run out of
+operating system resources (such as open file handles). This may be
+useful if you do not want to take care of applying a limit but still want
+to use the `getConnections()` method.
+
+You can optionally configure the server to pause accepting new
+connections once the connection limit is reached. In this case, it will
+pause the underlying server and no longer process any new connections at
+all, thus also no longer closing any excessive connections.
+The underlying operating system is responsible for keeping a backlog of
+pending connections until its limit is reached, at which point it will
+start rejecting further connections.
+Once the server is below the connection limit, it will continue consuming
+connections from the backlog and will process any outstanding data on
+each connection.
+This mode may be useful for some protocols that are designed to wait for
+a response message (such as HTTP), but may be less useful for other
+protocols that demand immediate responses (such as a "welcome" message in
+an interactive chat).
+
+```php
+$server = new LimitingServer($server, 100, true);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+##### getConnections()
+
+The `getConnections(): ConnectionInterface[]` method can be used to
+return an array with all currently active connections.
+
+```php
+foreach ($server->getConnection() as $connection) {
+ $connection->write('Hi!');
+}
+```
+
+## Client usage
+
+### ConnectorInterface
+
+The `ConnectorInterface` is responsible for providing an interface for
+establishing streaming connections, such as a normal TCP/IP connection.
+
+This is the main interface defined in this package and it is used throughout
+React's vast ecosystem.
+
+Most higher-level components (such as HTTP, database or other networking
+service clients) accept an instance implementing this interface to create their
+TCP/IP connection to the underlying networking service.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against any other implementation of this interface.
+
+The interface only offers a single method:
+
+#### connect()
+
+The `connect(string $uri): PromiseInterface` method
+can be used to create a streaming connection to the given remote address.
+
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a stream implementing [`ConnectionInterface`](#connectioninterface)
+on success or rejects with an `Exception` if the connection is not successful:
+
+```php
+$connector->connect('google.com:443')->then(
+ function (ConnectionInterface $connection) {
+ // connection successfully established
+ },
+ function (Exception $error) {
+ // failed to connect due to $error
+ }
+);
+```
+
+See also [`ConnectionInterface`](#connectioninterface) for more details.
+
+The returned Promise MUST be implemented in such a way that it can be
+cancelled when it is still pending. Cancelling a pending promise MUST
+reject its value with an `Exception`. It SHOULD clean up any underlying
+resources and references as applicable:
+
+```php
+$promise = $connector->connect($uri);
+
+$promise->cancel();
+```
+
+### Connector
+
+The `Connector` class is the main class in this package that implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create streaming connections.
+
+You can use this connector to create any kind of streaming connections, such
+as plaintext TCP/IP, secure TLS or local Unix connection streams.
+
+It binds to the main event loop and can be used like this:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$connector = new Connector($loop);
+
+$connector->connect($uri)->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+In order to create a plaintext TCP/IP connection, you can simply pass a host
+and port combination like this:
+
+```php
+$connector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> If you do no specify a URI scheme in the destination URI, it will assume
+ `tcp://` as a default and establish a plaintext TCP/IP connection.
+ Note that TCP/IP connections require a host and port part in the destination
+ URI like above, all other URI components are optional.
+
+In order to create a secure TLS connection, you can use the `tls://` URI scheme
+like this:
+
+```php
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+In order to create a local Unix domain socket connection, you can use the
+`unix://` URI scheme like this:
+
+```php
+$connector->connect('unix:///tmp/demo.sock')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> The [`getRemoteAddress()`](#getremoteaddress) method will return the target
+ Unix domain socket (UDS) path as given to the `connect()` method, including
+ the `unix://` scheme, for example `unix:///tmp/demo.sock`.
+ The [`getLocalAddress()`](#getlocaladdress) method will most likely return a
+ `null` value as this value is not applicable to UDS connections here.
+
+Under the hood, the `Connector` is implemented as a *higher-level facade*
+for the lower-level connectors implemented in this package. This means it
+also shares all of their features and implementation details.
+If you want to typehint in your higher-level protocol implementation, you SHOULD
+use the generic [`ConnectorInterface`](#connectorinterface) instead.
+
+The `Connector` class will try to detect your system DNS settings (and uses
+Google's public DNS server `8.8.8.8` as a fallback if unable to determine your
+system settings) to resolve all public hostnames into underlying IP addresses by
+default.
+If you explicitly want to use a custom DNS server (such as a local DNS relay or
+a company wide DNS server), you can set up the `Connector` like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'dns' => '127.0.1.1'
+));
+
+$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+If you do not want to use a DNS resolver at all and want to connect to IP
+addresses only, you can also set up your `Connector` like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'dns' => false
+));
+
+$connector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+Advanced: If you need a custom DNS `Resolver` instance, you can also set up
+your `Connector` like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);
+
+$connector = new Connector($loop, array(
+ 'dns' => $resolver
+));
+
+$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+By default, the `tcp://` and `tls://` URI schemes will use timeout value that
+repects your `default_socket_timeout` ini setting (which defaults to 60s).
+If you want a custom timeout value, you can simply pass this like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'timeout' => 10.0
+));
+```
+
+Similarly, if you do not want to apply a timeout at all and let the operating
+system handle this, you can pass a boolean flag like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'timeout' => false
+));
+```
+
+By default, the `Connector` supports the `tcp://`, `tls://` and `unix://`
+URI schemes. If you want to explicitly prohibit any of these, you can simply
+pass boolean flags like this:
+
+```php
+// only allow secure TLS connections
+$connector = new Connector($loop, array(
+ 'tcp' => false,
+ 'tls' => true,
+ 'unix' => false,
+));
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+The `tcp://` and `tls://` also accept additional context options passed to
+the underlying connectors.
+If you want to explicitly pass additional context options, you can simply
+pass arrays of context options like this:
+
+```php
+// allow insecure TLS connections
+$connector = new Connector($loop, array(
+ 'tcp' => array(
+ 'bindto' => '192.168.0.1:0'
+ ),
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ),
+));
+
+$connector->connect('tls://localhost:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+By default, this connector supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$connector = new Connector($loop, array(
+ 'tls' => array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+ )
+));
+```
+
+> For more details about context options, please refer to the PHP documentation
+ about [socket context options](http://php.net/manual/en/context.socket.php)
+ and [SSL context options](http://php.net/manual/en/context.ssl.php).
+
+Advanced: By default, the `Connector` supports the `tcp://`, `tls://` and
+`unix://` URI schemes.
+For this, it sets up the required connector classes automatically.
+If you want to explicitly pass custom connectors for any of these, you can simply
+pass an instance implementing the `ConnectorInterface` like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);
+$tcp = new DnsConnector(new TcpConnector($loop), $resolver);
+
+$tls = new SecureConnector($tcp, $loop);
+
+$unix = new UnixConnector($loop);
+
+$connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'tls' => $tls,
+ 'unix' => $unix,
+
+ 'dns' => false,
+ 'timeout' => false,
+));
+
+$connector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> Internally, the `tcp://` connector will always be wrapped by the DNS resolver,
+ unless you disable DNS like in the above example. In this case, the `tcp://`
+ connector receives the actual hostname instead of only the resolved IP address
+ and is thus responsible for performing the lookup.
+ Internally, the automatically created `tls://` connector will always wrap the
+ underlying `tcp://` connector for establishing the underlying plaintext
+ TCP/IP connection before enabling secure TLS mode. If you want to use a custom
+ underlying `tcp://` connector for secure TLS connections only, you may
+ explicitly pass a `tls://` connector like above instead.
+ Internally, the `tcp://` and `tls://` connectors will always be wrapped by
+ `TimeoutConnector`, unless you disable timeouts like in the above example.
+
+### Advanced client usage
+
+#### TcpConnector
+
+The `React\Socket\TcpConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext
+TCP/IP connections to any IP-port-combination:
+
+```php
+$tcpConnector = new React\Socket\TcpConnector($loop);
+
+$tcpConnector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $tcpConnector->connect('127.0.0.1:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will close the underlying socket
+resource, thus cancelling the pending TCP/IP connection, and reject the
+resulting promise.
+
+You can optionally pass additional
+[socket context options](http://php.net/manual/en/context.socket.php)
+to the constructor like this:
+
+```php
+$tcpConnector = new React\Socket\TcpConnector($loop, array(
+ 'bindto' => '192.168.0.1:0'
+));
+```
+
+Note that this class only allows you to connect to IP-port-combinations.
+If the given URI is invalid, does not contain a valid IP address and port
+or contains any other scheme, it will reject with an
+`InvalidArgumentException`:
+
+If the given URI appears to be valid, but connecting to it fails (such as if
+the remote host rejects the connection etc.), it will reject with a
+`RuntimeException`.
+
+If you want to connect to hostname-port-combinations, see also the following chapter.
+
+> Advanced usage: Internally, the `TcpConnector` allocates an empty *context*
+resource for each stream resource.
+If the destination URI contains a `hostname` query parameter, its value will
+be used to set up the TLS peer name.
+This is used by the `SecureConnector` and `DnsConnector` to verify the peer
+name and can also be used if you want a custom TLS peer name.
+
+#### DnsConnector
+
+The `DnsConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext
+TCP/IP connections to any hostname-port-combination.
+
+It does so by decorating a given `TcpConnector` instance so that it first
+looks up the given domain name via DNS (if applicable) and then establishes the
+underlying TCP/IP connection to the resolved target IP address.
+
+Make sure to set up your DNS resolver and underlying TCP connector like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$dns = $dnsResolverFactory->createCached('8.8.8.8', $loop);
+
+$dnsConnector = new React\Socket\DnsConnector($tcpConnector, $dns);
+
+$dnsConnector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $dnsConnector->connect('www.google.com:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying DNS lookup
+and/or the underlying TCP/IP connection and reject the resulting promise.
+
+> Advanced usage: Internally, the `DnsConnector` relies on a `Resolver` to
+look up the IP address for the given hostname.
+It will then replace the hostname in the destination URI with this IP and
+append a `hostname` query parameter and pass this updated URI to the underlying
+connector.
+The underlying connector is thus responsible for creating a connection to the
+target IP address, while this query parameter can be used to check the original
+hostname and is used by the `TcpConnector` to set up the TLS peer name.
+If a `hostname` is given explicitly, this query parameter will not be modified,
+which can be useful if you want a custom TLS peer name.
+
+#### SecureConnector
+
+The `SecureConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create secure
+TLS (formerly known as SSL) connections to any hostname-port-combination.
+
+It does so by decorating a given `DnsConnector` instance so that it first
+creates a plaintext TCP/IP connection and then enables TLS encryption on this
+stream.
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop);
+
+$secureConnector->connect('www.google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
+ ...
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $secureConnector->connect('www.google.com:443');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying TCP/IP
+connection and/or the SSL/TLS negotiation and reject the resulting promise.
+
+You can optionally pass additional
+[SSL context options](http://php.net/manual/en/context.ssl.php)
+to the constructor like this:
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+));
+```
+
+By default, this connector supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+));
+```
+
+> Advanced usage: Internally, the `SecureConnector` relies on setting up the
+required *context options* on the underlying stream resource.
+It should therefor be used with a `TcpConnector` somewhere in the connector
+stack so that it can allocate an empty *context* resource for each stream
+resource and verify the peer name.
+Failing to do so may result in a TLS peer name mismatch error or some hard to
+trace race conditions, because all stream resources will use a single, shared
+*default context* resource otherwise.
+
+#### TimeoutConnector
+
+The `TimeoutConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to add timeout
+handling to any existing connector instance.
+
+It does so by decorating any given [`ConnectorInterface`](#connectorinterface)
+instance and starting a timer that will automatically reject and abort any
+underlying connection attempt if it takes too long.
+
+```php
+$timeoutConnector = new React\Socket\TimeoutConnector($connector, 3.0, $loop);
+
+$timeoutConnector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $timeoutConnector->connect('google.com:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying connection
+attempt, abort the timer and reject the resulting promise.
+
+#### UnixConnector
+
+The `UnixConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to connect to
+Unix domain socket (UDS) paths like this:
+
+```php
+$connector = new React\Socket\UnixConnector($loop);
+
+$connector->connect('/tmp/demo.sock')->then(function (ConnectionInterface $connection) {
+ $connection->write("HELLO\n");
+});
+
+$loop->run();
+```
+
+Connecting to Unix domain sockets is an atomic operation, i.e. its promise will
+settle (either resolve or reject) immediately.
+As such, calling `cancel()` on the resulting promise has no effect.
+
+> The [`getRemoteAddress()`](#getremoteaddress) method will return the target
+ Unix domain socket (UDS) path as given to the `connect()` method, prepended
+ with the `unix://` scheme, for example `unix:///tmp/demo.sock`.
+ The [`getLocalAddress()`](#getlocaladdress) method will most likely return a
+ `null` value as this value is not applicable to UDS connections here.
+
+#### FixedUriConnector
+
+The `FixedUriConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and decorates an existing Connector
+to always use a fixed, preconfigured URI.
+
+This can be useful for consumers that do not support certain URIs, such as
+when you want to explicitly connect to a Unix domain socket (UDS) path
+instead of connecting to a default address assumed by an higher-level API:
+
+```php
+$connector = new FixedUriConnector(
+ 'unix:///var/run/docker.sock',
+ new UnixConnector($loop)
+);
+
+// destination will be ignored, actually connects to Unix domain socket
+$promise = $connector->connect('localhost:80');
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/socket:^0.8.12
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project, partly due to its vast
+performance improvements and partly because legacy PHP versions require several
+workarounds as described below.
+
+Secure TLS connections received some major upgrades starting with PHP 5.6, with
+the defaults now being more secure, while older versions required explicit
+context options.
+This library does not take responsibility over these context options, so it's
+up to consumers of this library to take care of setting appropriate context
+options as described above.
+
+All versions of PHP prior to 5.6.8 suffered from a buffering issue where reading
+from a streaming TLS connection could be one `data` event behind.
+This library implements a work-around to try to flush the complete incoming
+data buffers on these legacy PHP versions, which has a penalty of around 10% of
+throughput on all connections.
+With this work-around, we have not been able to reproduce this issue anymore,
+but we have seen reports of people saying this could still affect some of the
+older PHP versions (`5.5.23`, `5.6.7`, and `5.6.8`).
+Note that this only affects *some* higher-level streaming protocols, such as
+IRC over TLS, but should not affect HTTP over TLS (HTTPS).
+Further investigation of this issue is needed.
+For more insights, this issue is also covered by our test suite.
+
+PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
+chunks of data over TLS streams at once.
+We try to work around this by limiting the write chunk size to 8192
+bytes for older PHP versions only.
+This is only a work-around and has a noticable performance penalty on
+affected versions.
+
+This project also supports running on HHVM.
+Note that really old HHVM < 3.8 does not support secure TLS connections, as it
+lacks the required `stream_socket_enable_crypto()` function.
+As such, trying to create a secure TLS connections on affected versions will
+return a rejected promise instead.
+This issue is also covered by our test suite, which will skip related tests
+on affected versions.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/instafeed/vendor/react/socket/composer.json b/instafeed/vendor/react/socket/composer.json
new file mode 100755
index 0000000..cad0aef
--- /dev/null
+++ b/instafeed/vendor/react/socket/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "react/socket",
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": ["async", "socket", "stream", "connection", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/dns": "^0.4.13",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/stream": "^1.0 || ^0.7.1",
+ "react/promise": "^2.6.0 || ^1.2.1",
+ "react/promise-timer": "^1.4.0"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Socket\\": "tests"
+ }
+ }
+}
diff --git a/instafeed/vendor/react/socket/examples/01-echo-server.php b/instafeed/vendor/react/socket/examples/01-echo-server.php
new file mode 100755
index 0000000..2c0be57
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/01-echo-server.php
@@ -0,0 +1,42 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) {
+ echo '[' . $conn->getRemoteAddress() . ' connected]' . PHP_EOL;
+ $conn->pipe($conn);
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/react/socket/examples/02-chat-server.php b/instafeed/vendor/react/socket/examples/02-chat-server.php
new file mode 100755
index 0000000..46439e0
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/02-chat-server.php
@@ -0,0 +1,59 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server = new LimitingServer($server, null);
+
+$server->on('connection', function (ConnectionInterface $client) use ($server) {
+ // whenever a new message comes in
+ $client->on('data', function ($data) use ($client, $server) {
+ // remove any non-word characters (just for the demo)
+ $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data));
+
+ // ignore empty messages
+ if ($data === '') {
+ return;
+ }
+
+ // prefix with client IP and broadcast to all connected clients
+ $data = trim(parse_url($client->getRemoteAddress(), PHP_URL_HOST), '[]') . ': ' . $data . PHP_EOL;
+ foreach ($server->getConnections() as $connection) {
+ $connection->write($data);
+ }
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/react/socket/examples/03-http-server.php b/instafeed/vendor/react/socket/examples/03-http-server.php
new file mode 100755
index 0000000..eb6d454
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/03-http-server.php
@@ -0,0 +1,57 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) {
+ $conn->once('data', function () use ($conn) {
+ $body = "
Hello world!
\r\n";
+ $conn->end("HTTP/1.1 200 OK\r\nContent-Length: " . strlen($body) . "\r\nConnection: close\r\n\r\n" . $body);
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . strtr($server->getAddress(), array('tcp:' => 'http:', 'tls:' => 'https:')) . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/react/socket/examples/11-http-client.php b/instafeed/vendor/react/socket/examples/11-http-client.php
new file mode 100755
index 0000000..2b64a43
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/11-http-client.php
@@ -0,0 +1,36 @@
+connect($host. ':80')->then(function (ConnectionInterface $connection) use ($host) {
+ $connection->on('data', function ($data) {
+ echo $data;
+ });
+ $connection->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+ });
+
+ $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/react/socket/examples/12-https-client.php b/instafeed/vendor/react/socket/examples/12-https-client.php
new file mode 100755
index 0000000..6e3f279
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/12-https-client.php
@@ -0,0 +1,36 @@
+connect('tls://' . $host . ':443')->then(function (ConnectionInterface $connection) use ($host) {
+ $connection->on('data', function ($data) {
+ echo $data;
+ });
+ $connection->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+ });
+
+ $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/react/socket/examples/21-netcat-client.php b/instafeed/vendor/react/socket/examples/21-netcat-client.php
new file mode 100755
index 0000000..9140e2c
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/21-netcat-client.php
@@ -0,0 +1,68 @@
+' . PHP_EOL);
+ exit(1);
+}
+
+$loop = Factory::create();
+$connector = new Connector($loop);
+
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdin->pause();
+$stdout = new WritableResourceStream(STDOUT, $loop);
+$stderr = new WritableResourceStream(STDERR, $loop);
+
+$stderr->write('Connecting' . PHP_EOL);
+
+$connector->connect($argv[1])->then(function (ConnectionInterface $connection) use ($stdin, $stdout, $stderr) {
+ // pipe everything from STDIN into connection
+ $stdin->resume();
+ $stdin->pipe($connection);
+
+ // pipe everything from connection to STDOUT
+ $connection->pipe($stdout);
+
+ // report errors to STDERR
+ $connection->on('error', function ($error) use ($stderr) {
+ $stderr->write('Stream ERROR: ' . $error . PHP_EOL);
+ });
+
+ // report closing and stop reading from input
+ $connection->on('close', function () use ($stderr, $stdin) {
+ $stderr->write('[CLOSED]' . PHP_EOL);
+ $stdin->close();
+ });
+
+ $stderr->write('Connected' . PHP_EOL);
+}, function ($error) use ($stderr) {
+ $stderr->write('Connection ERROR: ' . $error . PHP_EOL);
+});
+
+$loop->run();
diff --git a/instafeed/vendor/react/socket/examples/22-http-client.php b/instafeed/vendor/react/socket/examples/22-http-client.php
new file mode 100755
index 0000000..fcb8107
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/22-http-client.php
@@ -0,0 +1,60 @@
+' . PHP_EOL);
+ exit(1);
+}
+
+$loop = Factory::create();
+$connector = new Connector($loop);
+
+if (!isset($parts['port'])) {
+ $parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
+}
+
+$host = $parts['host'];
+if (($parts['scheme'] === 'http' && $parts['port'] !== 80) || ($parts['scheme'] === 'https' && $parts['port'] !== 443)) {
+ $host .= ':' . $parts['port'];
+}
+$target = ($parts['scheme'] === 'https' ? 'tls' : 'tcp') . '://' . $parts['host'] . ':' . $parts['port'];
+$resource = isset($parts['path']) ? $parts['path'] : '/';
+if (isset($parts['query'])) {
+ $resource .= '?' . $parts['query'];
+}
+
+$stdout = new WritableResourceStream(STDOUT, $loop);
+
+$connector->connect($target)->then(function (ConnectionInterface $connection) use ($resource, $host, $stdout) {
+ $connection->pipe($stdout);
+
+ $connection->write("GET $resource HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/instafeed/vendor/react/socket/examples/91-benchmark-server.php b/instafeed/vendor/react/socket/examples/91-benchmark-server.php
new file mode 100755
index 0000000..420d474
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/91-benchmark-server.php
@@ -0,0 +1,60 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) use ($loop) {
+ echo '[connected]' . PHP_EOL;
+
+ // count the number of bytes received from this connection
+ $bytes = 0;
+ $conn->on('data', function ($chunk) use (&$bytes) {
+ $bytes += strlen($chunk);
+ });
+
+ // report average throughput once client disconnects
+ $t = microtime(true);
+ $conn->on('close', function () use ($conn, $t, &$bytes) {
+ $t = microtime(true) - $t;
+ echo '[disconnected after receiving ' . $bytes . ' bytes in ' . round($t, 3) . 's => ' . round($bytes / $t / 1024 / 1024, 1) . ' MiB/s]' . PHP_EOL;
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/instafeed/vendor/react/socket/examples/99-generate-self-signed.php b/instafeed/vendor/react/socket/examples/99-generate-self-signed.php
new file mode 100755
index 0000000..00f9314
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/99-generate-self-signed.php
@@ -0,0 +1,31 @@
+ secret.pem
+
+// certificate details (Distinguished Name)
+// (OpenSSL applies defaults to missing fields)
+$dn = array(
+ "commonName" => isset($argv[1]) ? $argv[1] : "localhost",
+// "countryName" => "AU",
+// "stateOrProvinceName" => "Some-State",
+// "localityName" => "London",
+// "organizationName" => "Internet Widgits Pty Ltd",
+// "organizationalUnitName" => "R&D",
+// "emailAddress" => "admin@example.com"
+);
+
+// create certificate which is valid for ~10 years
+$privkey = openssl_pkey_new();
+$cert = openssl_csr_new($dn, $privkey);
+$cert = openssl_csr_sign($cert, null, $privkey, 3650);
+
+// export public and (optionally encrypted) private key in PEM format
+openssl_x509_export($cert, $out);
+echo $out;
+
+$passphrase = isset($argv[2]) ? $argv[2] : null;
+openssl_pkey_export($privkey, $out, $passphrase);
+echo $out;
diff --git a/instafeed/vendor/react/socket/examples/localhost.pem b/instafeed/vendor/react/socket/examples/localhost.pem
new file mode 100755
index 0000000..be69279
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/localhost.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
+MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
+BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
+0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
+7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
+824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
+V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
+IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
+ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
+g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
+tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
+LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
+tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
+9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
+43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
+pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
+OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
+2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
+li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
+b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
+vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
+XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
+Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
+iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
++EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
+y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
+81oh1uCH1YPLM29hPyaohxL8
+-----END PRIVATE KEY-----
diff --git a/instafeed/vendor/react/socket/examples/localhost_swordfish.pem b/instafeed/vendor/react/socket/examples/localhost_swordfish.pem
new file mode 100755
index 0000000..7d1ee80
--- /dev/null
+++ b/instafeed/vendor/react/socket/examples/localhost_swordfish.pem
@@ -0,0 +1,51 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQxMDQzWhcNMjYx
+MjI4MTQxMDQzWjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRXt83SrKIHr/i
+3lc8O8pz6NHE1DNHJa4xg2xalXWzCEV6m1qLd9VdaLT9cJD1afNmEMBgY6RblNL/
+paJWVoR9MOUeIoYl2PrhUCxsf7h6MRtezQQe3e+n+/0XunF0JUQIZuJqbxfRk5WT
+XmYnphqOZKEcistAYvFBjzl/D+Cl/nYsreADc+t9l5Vni89oTWEuqIrsM4WUZqqB
+VMAakd2nZJLWIrMxq9hbW1XNukOQfcmZVFTC6CUnLq8qzGbtfZYBuMBACnL1k/E/
+yPaAgR46l14VAcndDUJBtMeL2qYuNwvXQhg3KuBmpTUpH+yzxU+4T3lmv0xXmPqu
+ySH3xvW3AgMBAAGjUDBOMB0GA1UdDgQWBBRu68WTI4pVeTB7wuG9QGI3Ie441TAf
+BgNVHSMEGDAWgBRu68WTI4pVeTB7wuG9QGI3Ie441TAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQCc4pEjEHO47VRJkbHgC+c2gAVgxekkaA1czBA1uAvh
+ILRda0NLlvyftbjaG0zZp2ABUCfRfksl/Pf/PzWLUMEuH/9kEW2rgP43z6YgiL6k
+kBPlmAU607UjD726RPGkw8QPSXS/dWiNJ5CBpPWLpxC45pokqItYbY0ijQ5Piq09
+TchYlCX044oSRnPiP394PQ3HVdaGhJB2DnjDq3in5dVivFf8EdgzQSvp/wXy3WQs
+uFSVonSnrZGY/4AgT3psGaQ6fqKb4SBoqtf5bFQvp1XNNRkuEJnS/0dygEya0c+c
+aCe/1gXC2wDjx0/TekY5m1Nyw5SY6z7stOqL/ekwgejt
+-----END CERTIFICATE-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIG7idPRLgiHkCAggA
+MBQGCCqGSIb3DQMHBAg+MLPdepHWSwSCBMgVW9LseCjfTAmF9U1qRnKsq3kIwEnW
+6aERBqs/mnmEhrXgZYgcvRRK7kD12TdHt/Nz46Ymu0h+Lrvuwtl1fHQUARTk/gFh
+onLhc9kjMUhLRIR007vJe3HvWOb/v+SBSDB38OpUxUwJmBVBuSaYLWVuPR6J5kUj
+xOgBS049lN3E9cfrHvb3bF/epIQrU0OgfyyxEvIi5n30y+tlRn3y68PY6Qd46t4Y
+UN5VZUwvJBgoRy9TGxSkiSRjhxC2PWpLYq/HMzDbcRcFF5dVAIioUd/VZ7fdgBfA
+uMW4SFfpFLDUX0aaYe+ZdA5tM0Bc0cOtG8Z0sc9JYDNNcvjmSiGCi646h8F0D3O6
+JKAQMMxQGWiyQeJ979LVjtq4lJESXA8VEKz9rV03y5xunmFCLy6dGt+6GJwXgabn
+OH7nvEv4GqAOqKc6E9je4JM+AF/oUazrfPse1KEEtsPKarazjCB/SKYtHyDJaavD
+GGjtiU9zWwGMOgIDyNmXe3ga7/TWoGOAg5YlTr6Hbq2Y/5ycgjAgPFjuXtvnoT0+
+mF5TnNfMAqTgQsE2gjhonK1pdlOen0lN5FtoUXp3CXU0dOq0J70GiX+1YA7VDn30
+n5WNAgfOXX3l3E95jGN370pHXyli5RUNW0NZVHV+22jlNWCVtQHUh+DVswQZg+i5
++DqaIHz2jUetMo7gWtqGn/wwSopOs87VM1rcALhZL4EsJ+Zy81I/hA32RNnGbuol
+NAiZh+0KrtTcc/fPunpd8vRtOwGphM11dKucozUufuiPG2inR3aEqt5yNx54ec/f
+J6nryWRYiHEA/rCU9MSBM9cqKFtEmy9/8oxV41/SPxhXjHwDlABWTtFuJ3pf2sOF
+ILSYYFwB0ZGvdjE5yAJFBr9efno/L9fafmGk7a3vmVgK2AmUC9VNB5XHw1GjF8OP
+aQAXe4md9Bh0jk/D/iyp7e7IWNssul/7XejhabidWgFj6EXc9YxE59+FlhDqyMhn
+V6houc+QeUXuwsAKgRJJhJtpv/QSZ5BI3esxHHUt3ayGnvhFElpAc0t7C/EiXKIv
+DAFYP2jksBqijM8YtEgPWYzEP5buYxZnf/LK7FDocLsNcdF38UaKBbeF90e7bR8j
+SHspG9aJWICu8Yawnh8zuy/vQv+h9gWyGodd2p9lQzlbRXrutbwfmPf7xP6nzT9i
+9GcugJxTaZgkCfhhHxFk/nRHS2NAzagKVib1xkUlZJg2hX0fIFUdYteL1GGTvOx5
+m3mTOino4T19z9SEdZYb2OHYh29e/T74bJiLCYdXwevSYHxfZc8pYAf0jp4UnMT2
+f7B0ctX1iXuQ2uZVuxh+U1Mcu+v0gDla1jWh7AhcePSi4xBNUCak0kQip6r5e6Oi
+r4MIyMRk/Pc5pzEKo8G6nk26rNvX3aRvECoVfmK7IVdsqZ6IXlt9kOmWx3IeKzrO
+J5DxpzW+9oIRZJgPTkc4/XRb0tFmFQYTiChiQ1AJUEiCX0GpkFf7cq61aLGYtWyn
+vL2lmQhljzjrDo15hKErvk7eBZW7GW/6j/m/PfRdcBI4ceuP9zWQXnDOd9zmaE4b
+q3bJ+IbbyVZA2WwyzN7umCKWghsiPMAolxEnYM9JRf8BcqeqQiwVZlfO5KFuN6Ze
+le4=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/instafeed/vendor/react/socket/phpunit.xml.dist b/instafeed/vendor/react/socket/phpunit.xml.dist
new file mode 100755
index 0000000..13d3fab
--- /dev/null
+++ b/instafeed/vendor/react/socket/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/instafeed/vendor/react/socket/src/Connection.php b/instafeed/vendor/react/socket/src/Connection.php
new file mode 100755
index 0000000..c6267cc
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/Connection.php
@@ -0,0 +1,178 @@
+= 70100 && PHP_VERSION_ID < 70104));
+
+ $this->input = new DuplexResourceStream(
+ $resource,
+ $loop,
+ $clearCompleteBuffer ? -1 : null,
+ new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null)
+ );
+
+ $this->stream = $resource;
+
+ Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain'));
+
+ $this->input->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->input->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->input->isWritable();
+ }
+
+ public function pause()
+ {
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ $this->input->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return $this->input->pipe($dest, $options);
+ }
+
+ public function write($data)
+ {
+ return $this->input->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->input->end($data);
+ }
+
+ public function close()
+ {
+ $this->input->close();
+ $this->handleClose();
+ $this->removeAllListeners();
+ }
+
+ public function handleClose()
+ {
+ if (!is_resource($this->stream)) {
+ return;
+ }
+
+ // Try to cleanly shut down socket and ignore any errors in case other
+ // side already closed. Shutting down may return to blocking mode on
+ // some legacy versions, so reset to non-blocking just in case before
+ // continuing to close the socket resource.
+ // Underlying Stream implementation will take care of closing file
+ // handle, so we otherwise keep this open here.
+ @stream_socket_shutdown($this->stream, STREAM_SHUT_RDWR);
+ stream_set_blocking($this->stream, false);
+ }
+
+ public function getRemoteAddress()
+ {
+ return $this->parseAddress(@stream_socket_get_name($this->stream, true));
+ }
+
+ public function getLocalAddress()
+ {
+ return $this->parseAddress(@stream_socket_get_name($this->stream, false));
+ }
+
+ private function parseAddress($address)
+ {
+ if ($address === false) {
+ return null;
+ }
+
+ if ($this->unix) {
+ // remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
+ // note that technically ":" is a valid address, so keep this in place otherwise
+ if (substr($address, -1) === ':' && defined('HHVM_VERSION_ID') && HHVM_VERSION_ID < 31900) {
+ $address = (string)substr($address, 0, -1);
+ }
+
+ // work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
+ // PHP uses "\0" string and HHVM uses empty string (colon removed above)
+ if ($address === '' || $address[0] === "\x00" ) {
+ return null;
+ }
+
+ return 'unix://' . $address;
+ }
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = strrpos($address, ':');
+ if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') {
+ $port = substr($address, $pos + 1);
+ $address = '[' . substr($address, 0, $pos) . ']:' . $port;
+ }
+
+ return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address;
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/ConnectionInterface.php b/instafeed/vendor/react/socket/src/ConnectionInterface.php
new file mode 100755
index 0000000..64613b5
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/ConnectionInterface.php
@@ -0,0 +1,119 @@
+on('data', function ($chunk) {
+ * echo $chunk;
+ * });
+ *
+ * $connection->on('end', function () {
+ * echo 'ended';
+ * });
+ *
+ * $connection->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage();
+ * });
+ *
+ * $connection->on('close', function () {
+ * echo 'closed';
+ * });
+ *
+ * $connection->write($data);
+ * $connection->end($data = null);
+ * $connection->close();
+ * // …
+ * ```
+ *
+ * For more details, see the
+ * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+ *
+ * @see DuplexStreamInterface
+ * @see ServerInterface
+ * @see ConnectorInterface
+ */
+interface ConnectionInterface extends DuplexStreamInterface
+{
+ /**
+ * Returns the full remote address (URI) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the remote address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based connection and you only want the remote IP, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * $ip = trim(parse_url($address, PHP_URL_HOST), '[]');
+ * echo 'Connection with ' . $ip . PHP_EOL;
+ * ```
+ *
+ * @return ?string remote address (URI) or null if unknown
+ */
+ public function getRemoteAddress();
+
+ /**
+ * Returns the full local address (full URI with scheme, IP and port) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getLocalAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the local address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
+ * so they should not be confused.
+ *
+ * If your `TcpServer` instance is listening on multiple interfaces (e.g. using
+ * the address `0.0.0.0`), you can use this method to find out which interface
+ * actually accepted this connection (such as a public or local interface).
+ *
+ * If your system has multiple interfaces (e.g. a WAN and a LAN interface),
+ * you can use this method to find out which interface was actually
+ * used for this connection.
+ *
+ * @return ?string local address (URI) or null if unknown
+ * @see self::getRemoteAddress()
+ */
+ public function getLocalAddress();
+}
diff --git a/instafeed/vendor/react/socket/src/Connector.php b/instafeed/vendor/react/socket/src/Connector.php
new file mode 100755
index 0000000..75276bc
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/Connector.php
@@ -0,0 +1,136 @@
+ true,
+ 'tls' => true,
+ 'unix' => true,
+
+ 'dns' => true,
+ 'timeout' => true,
+ );
+
+ if ($options['timeout'] === true) {
+ $options['timeout'] = (float)ini_get("default_socket_timeout");
+ }
+
+ if ($options['tcp'] instanceof ConnectorInterface) {
+ $tcp = $options['tcp'];
+ } else {
+ $tcp = new TcpConnector(
+ $loop,
+ is_array($options['tcp']) ? $options['tcp'] : array()
+ );
+ }
+
+ if ($options['dns'] !== false) {
+ if ($options['dns'] instanceof Resolver) {
+ $resolver = $options['dns'];
+ } else {
+ if ($options['dns'] !== true) {
+ $server = $options['dns'];
+ } else {
+ // try to load nameservers from system config or default to Google's public DNS
+ $config = Config::loadSystemConfigBlocking();
+ $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+ }
+
+ $factory = new Factory();
+ $resolver = $factory->create(
+ $server,
+ $loop
+ );
+ }
+
+ $tcp = new DnsConnector($tcp, $resolver);
+ }
+
+ if ($options['tcp'] !== false) {
+ $options['tcp'] = $tcp;
+
+ if ($options['timeout'] !== false) {
+ $options['tcp'] = new TimeoutConnector(
+ $options['tcp'],
+ $options['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tcp'] = $options['tcp'];
+ }
+
+ if ($options['tls'] !== false) {
+ if (!$options['tls'] instanceof ConnectorInterface) {
+ $options['tls'] = new SecureConnector(
+ $tcp,
+ $loop,
+ is_array($options['tls']) ? $options['tls'] : array()
+ );
+ }
+
+ if ($options['timeout'] !== false) {
+ $options['tls'] = new TimeoutConnector(
+ $options['tls'],
+ $options['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tls'] = $options['tls'];
+ }
+
+ if ($options['unix'] !== false) {
+ if (!$options['unix'] instanceof ConnectorInterface) {
+ $options['unix'] = new UnixConnector($loop);
+ }
+ $this->connectors['unix'] = $options['unix'];
+ }
+ }
+
+ public function connect($uri)
+ {
+ $scheme = 'tcp';
+ if (strpos($uri, '://') !== false) {
+ $scheme = (string)substr($uri, 0, strpos($uri, '://'));
+ }
+
+ if (!isset($this->connectors[$scheme])) {
+ return Promise\reject(new RuntimeException(
+ 'No connector available for URI scheme "' . $scheme . '"'
+ ));
+ }
+
+ return $this->connectors[$scheme]->connect($uri);
+ }
+}
+
diff --git a/instafeed/vendor/react/socket/src/ConnectorInterface.php b/instafeed/vendor/react/socket/src/ConnectorInterface.php
new file mode 100755
index 0000000..196d01a
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/ConnectorInterface.php
@@ -0,0 +1,58 @@
+connect('google.com:443')->then(
+ * function (ConnectionInterface $connection) {
+ * // connection successfully established
+ * },
+ * function (Exception $error) {
+ * // failed to connect due to $error
+ * }
+ * );
+ * ```
+ *
+ * The returned Promise MUST be implemented in such a way that it can be
+ * cancelled when it is still pending. Cancelling a pending promise MUST
+ * reject its value with an Exception. It SHOULD clean up any underlying
+ * resources and references as applicable.
+ *
+ * ```php
+ * $promise = $connector->connect($uri);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $uri
+ * @return \React\Promise\PromiseInterface resolves with a stream implementing ConnectionInterface on success or rejects with an Exception on error
+ * @see ConnectionInterface
+ */
+ public function connect($uri);
+}
diff --git a/instafeed/vendor/react/socket/src/DnsConnector.php b/instafeed/vendor/react/socket/src/DnsConnector.php
new file mode 100755
index 0000000..0dfd658
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/DnsConnector.php
@@ -0,0 +1,112 @@
+connector = $connector;
+ $this->resolver = $resolver;
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $parts = parse_url('tcp://' . $uri);
+ unset($parts['scheme']);
+ } else {
+ $parts = parse_url($uri);
+ }
+
+ if (!$parts || !isset($parts['host'])) {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $connector = $this->connector;
+
+ // skip DNS lookup / URI manipulation if this URI already contains an IP
+ if (false !== filter_var($host, FILTER_VALIDATE_IP)) {
+ return $connector->connect($uri);
+ }
+
+ return $this
+ ->resolveHostname($host)
+ ->then(function ($ip) use ($connector, $host, $parts) {
+ $uri = '';
+
+ // prepend original scheme if known
+ if (isset($parts['scheme'])) {
+ $uri .= $parts['scheme'] . '://';
+ }
+
+ if (strpos($ip, ':') !== false) {
+ // enclose IPv6 addresses in square brackets before appending port
+ $uri .= '[' . $ip . ']';
+ } else {
+ $uri .= $ip;
+ }
+
+ // append original port if known
+ if (isset($parts['port'])) {
+ $uri .= ':' . $parts['port'];
+ }
+
+ // append orignal path if known
+ if (isset($parts['path'])) {
+ $uri .= $parts['path'];
+ }
+
+ // append original query if known
+ if (isset($parts['query'])) {
+ $uri .= '?' . $parts['query'];
+ }
+
+ // append original hostname as query if resolved via DNS and if
+ // destination URI does not contain "hostname" query param already
+ $args = array();
+ parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
+ if ($host !== $ip && !isset($args['hostname'])) {
+ $uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . rawurlencode($host);
+ }
+
+ // append original fragment if known
+ if (isset($parts['fragment'])) {
+ $uri .= '#' . $parts['fragment'];
+ }
+
+ return $connector->connect($uri);
+ });
+ }
+
+ private function resolveHostname($host)
+ {
+ $promise = $this->resolver->resolve($host);
+
+ return new Promise\Promise(
+ function ($resolve, $reject) use ($promise) {
+ // resolve/reject with result of DNS lookup
+ $promise->then($resolve, $reject);
+ },
+ function ($_, $reject) use ($promise) {
+ // cancellation should reject connection attempt
+ $reject(new RuntimeException('Connection attempt cancelled during DNS lookup'));
+
+ // (try to) cancel pending DNS lookup
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+ );
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/FixedUriConnector.php b/instafeed/vendor/react/socket/src/FixedUriConnector.php
new file mode 100755
index 0000000..057bcdf
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/FixedUriConnector.php
@@ -0,0 +1,41 @@
+connect('localhost:80');
+ * ```
+ */
+class FixedUriConnector implements ConnectorInterface
+{
+ private $uri;
+ private $connector;
+
+ /**
+ * @param string $uri
+ * @param ConnectorInterface $connector
+ */
+ public function __construct($uri, ConnectorInterface $connector)
+ {
+ $this->uri = $uri;
+ $this->connector = $connector;
+ }
+
+ public function connect($_)
+ {
+ return $this->connector->connect($this->uri);
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/LimitingServer.php b/instafeed/vendor/react/socket/src/LimitingServer.php
new file mode 100755
index 0000000..c7874ee
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/LimitingServer.php
@@ -0,0 +1,203 @@
+on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+class LimitingServer extends EventEmitter implements ServerInterface
+{
+ private $connections = array();
+ private $server;
+ private $limit;
+
+ private $pauseOnLimit = false;
+ private $autoPaused = false;
+ private $manuPaused = false;
+
+ /**
+ * Instantiates a new LimitingServer.
+ *
+ * You have to pass a maximum number of open connections to ensure
+ * the server will automatically reject (close) connections once this limit
+ * is exceeded. In this case, it will emit an `error` event to inform about
+ * this and no `connection` event will be emitted.
+ *
+ * ```php
+ * $server = new LimitingServer($server, 100);
+ * $server->on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * You MAY pass a `null` limit in order to put no limit on the number of
+ * open connections and keep accepting new connection until you run out of
+ * operating system resources (such as open file handles). This may be
+ * useful if you do not want to take care of applying a limit but still want
+ * to use the `getConnections()` method.
+ *
+ * You can optionally configure the server to pause accepting new
+ * connections once the connection limit is reached. In this case, it will
+ * pause the underlying server and no longer process any new connections at
+ * all, thus also no longer closing any excessive connections.
+ * The underlying operating system is responsible for keeping a backlog of
+ * pending connections until its limit is reached, at which point it will
+ * start rejecting further connections.
+ * Once the server is below the connection limit, it will continue consuming
+ * connections from the backlog and will process any outstanding data on
+ * each connection.
+ * This mode may be useful for some protocols that are designed to wait for
+ * a response message (such as HTTP), but may be less useful for other
+ * protocols that demand immediate responses (such as a "welcome" message in
+ * an interactive chat).
+ *
+ * ```php
+ * $server = new LimitingServer($server, 100, true);
+ * $server->on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * @param ServerInterface $server
+ * @param int|null $connectionLimit
+ * @param bool $pauseOnLimit
+ */
+ public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false)
+ {
+ $this->server = $server;
+ $this->limit = $connectionLimit;
+ if ($connectionLimit !== null) {
+ $this->pauseOnLimit = $pauseOnLimit;
+ }
+
+ $this->server->on('connection', array($this, 'handleConnection'));
+ $this->server->on('error', array($this, 'handleError'));
+ }
+
+ /**
+ * Returns an array with all currently active connections
+ *
+ * ```php
+ * foreach ($server->getConnection() as $connection) {
+ * $connection->write('Hi!');
+ * }
+ * ```
+ *
+ * @return ConnectionInterface[]
+ */
+ public function getConnections()
+ {
+ return $this->connections;
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ if (!$this->manuPaused) {
+ $this->manuPaused = true;
+
+ if (!$this->autoPaused) {
+ $this->server->pause();
+ }
+ }
+ }
+
+ public function resume()
+ {
+ if ($this->manuPaused) {
+ $this->manuPaused = false;
+
+ if (!$this->autoPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ // close connection if limit exceeded
+ if ($this->limit !== null && count($this->connections) >= $this->limit) {
+ $this->handleError(new OverflowException('Connection closed because server reached connection limit'));
+ $connection->close();
+ return;
+ }
+
+ $this->connections[] = $connection;
+ $that = $this;
+ $connection->on('close', function () use ($that, $connection) {
+ $that->handleDisconnection($connection);
+ });
+
+ // pause accepting new connections if limit exceeded
+ if ($this->pauseOnLimit && !$this->autoPaused && count($this->connections) >= $this->limit) {
+ $this->autoPaused = true;
+
+ if (!$this->manuPaused) {
+ $this->server->pause();
+ }
+ }
+
+ $this->emit('connection', array($connection));
+ }
+
+ /** @internal */
+ public function handleDisconnection(ConnectionInterface $connection)
+ {
+ unset($this->connections[array_search($connection, $this->connections)]);
+
+ // continue accepting new connection if below limit
+ if ($this->autoPaused && count($this->connections) < $this->limit) {
+ $this->autoPaused = false;
+
+ if (!$this->manuPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ /** @internal */
+ public function handleError(Exception $error)
+ {
+ $this->emit('error', array($error));
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/SecureConnector.php b/instafeed/vendor/react/socket/src/SecureConnector.php
new file mode 100755
index 0000000..f04183d
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/SecureConnector.php
@@ -0,0 +1,64 @@
+connector = $connector;
+ $this->streamEncryption = new StreamEncryption($loop, false);
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ return Promise\reject(new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore
+ }
+
+ if (strpos($uri, '://') === false) {
+ $uri = 'tls://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $uri = str_replace('tls://', '', $uri);
+ $context = $this->context;
+
+ $encryption = $this->streamEncryption;
+ return $this->connector->connect($uri)->then(function (ConnectionInterface $connection) use ($context, $encryption) {
+ // (unencrypted) TCP/IP connection succeeded
+
+ if (!$connection instanceof Connection) {
+ $connection->close();
+ throw new UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource');
+ }
+
+ // set required SSL/TLS context options
+ foreach ($context as $name => $value) {
+ stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ // try to enable encryption
+ return $encryption->enable($connection)->then(null, function ($error) use ($connection) {
+ // establishing encryption failed => close invalid connection and return error
+ $connection->close();
+ throw $error;
+ });
+ });
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/SecureServer.php b/instafeed/vendor/react/socket/src/SecureServer.php
new file mode 100755
index 0000000..302ae93
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/SecureServer.php
@@ -0,0 +1,192 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
+ *
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * Whenever a client fails to perform a successful TLS handshake, it will emit an
+ * `error` event and then close the underlying TCP/IP connection:
+ *
+ * ```php
+ * $server->on('error', function (Exception $e) {
+ * echo 'Error' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * Note that the `SecureServer` class is a concrete implementation for TLS sockets.
+ * If you want to typehint in your higher-level protocol implementation, you SHOULD
+ * use the generic `ServerInterface` instead.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class SecureServer extends EventEmitter implements ServerInterface
+{
+ private $tcp;
+ private $encryption;
+ private $context;
+
+ /**
+ * Creates a secure TLS server and starts waiting for incoming connections
+ *
+ * It does so by wrapping a `TcpServer` instance which waits for plaintext
+ * TCP/IP connections and then performs a TLS handshake for each connection.
+ * It thus requires valid [TLS context options],
+ * which in its most basic form may look something like this if you're using a
+ * PEM encoded certificate file:
+ *
+ * ```php
+ * $server = new TcpServer(8000, $loop);
+ * $server = new SecureServer($server, $loop, array(
+ * 'local_cert' => 'server.pem'
+ * ));
+ * ```
+ *
+ * Note that the certificate file will not be loaded on instantiation but when an
+ * incoming connection initializes its TLS context.
+ * This implies that any invalid certificate file paths or contents will only cause
+ * an `error` event at a later time.
+ *
+ * If your private key is encrypted with a passphrase, you have to specify it
+ * like this:
+ *
+ * ```php
+ * $server = new TcpServer(8000, $loop);
+ * $server = new SecureServer($server, $loop, array(
+ * 'local_cert' => 'server.pem',
+ * 'passphrase' => 'secret'
+ * ));
+ * ```
+ *
+ * Note that available [TLS context options],
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ *
+ * Advanced usage: Despite allowing any `ServerInterface` as first parameter,
+ * you SHOULD pass a `TcpServer` instance as first parameter, unless you
+ * know what you're doing.
+ * Internally, the `SecureServer` has to set the required TLS context options on
+ * the underlying stream resources.
+ * These resources are not exposed through any of the interfaces defined in this
+ * package, but only through the internal `Connection` class.
+ * The `TcpServer` class is guaranteed to emit connections that implement
+ * the `ConnectionInterface` and uses the internal `Connection` class in order to
+ * expose these underlying resources.
+ * If you use a custom `ServerInterface` and its `connection` event does not
+ * meet this requirement, the `SecureServer` will emit an `error` event and
+ * then close the underlying connection.
+ *
+ * @param ServerInterface|TcpServer $tcp
+ * @param LoopInterface $loop
+ * @param array $context
+ * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
+ * @see TcpServer
+ * @link http://php.net/manual/en/context.ssl.php for TLS context options
+ */
+ public function __construct(ServerInterface $tcp, LoopInterface $loop, array $context)
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ throw new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
+ }
+
+ // default to empty passphrase to suppress blocking passphrase prompt
+ $context += array(
+ 'passphrase' => ''
+ );
+
+ $this->tcp = $tcp;
+ $this->encryption = new StreamEncryption($loop);
+ $this->context = $context;
+
+ $that = $this;
+ $this->tcp->on('connection', function ($connection) use ($that) {
+ $that->handleConnection($connection);
+ });
+ $this->tcp->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ $address = $this->tcp->getAddress();
+ if ($address === null) {
+ return null;
+ }
+
+ return str_replace('tcp://' , 'tls://', $address);
+ }
+
+ public function pause()
+ {
+ $this->tcp->pause();
+ }
+
+ public function resume()
+ {
+ $this->tcp->resume();
+ }
+
+ public function close()
+ {
+ return $this->tcp->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ if (!$connection instanceof Connection) {
+ $this->emit('error', array(new UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
+ $connection->end();
+ return;
+ }
+
+ foreach ($this->context as $name => $value) {
+ stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ $that = $this;
+
+ $this->encryption->enable($connection)->then(
+ function ($conn) use ($that) {
+ $that->emit('connection', array($conn));
+ },
+ function ($error) use ($that, $connection) {
+ $that->emit('error', array($error));
+ $connection->end();
+ }
+ );
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/Server.php b/instafeed/vendor/react/socket/src/Server.php
new file mode 100755
index 0000000..72712e4
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/Server.php
@@ -0,0 +1,73 @@
+ $context);
+ }
+
+ // apply default options if not explicitly given
+ $context += array(
+ 'tcp' => array(),
+ 'tls' => array(),
+ 'unix' => array()
+ );
+
+ $scheme = 'tcp';
+ $pos = strpos($uri, '://');
+ if ($pos !== false) {
+ $scheme = substr($uri, 0, $pos);
+ }
+
+ if ($scheme === 'unix') {
+ $server = new UnixServer($uri, $loop, $context['unix']);
+ } else {
+ $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
+
+ if ($scheme === 'tls') {
+ $server = new SecureServer($server, $loop, $context['tls']);
+ }
+ }
+
+ $this->server = $server;
+
+ $that = $this;
+ $server->on('connection', function (ConnectionInterface $conn) use ($that) {
+ $that->emit('connection', array($conn));
+ });
+ $server->on('error', function (Exception $error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ $this->server->pause();
+ }
+
+ public function resume()
+ {
+ $this->server->resume();
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/ServerInterface.php b/instafeed/vendor/react/socket/src/ServerInterface.php
new file mode 100755
index 0000000..5319678
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/ServerInterface.php
@@ -0,0 +1,151 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'new connection' . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ConnectionInterface` for more details about handling the
+ * incoming connection.
+ *
+ * error event:
+ * The `error` event will be emitted whenever there's an error accepting a new
+ * connection from a client.
+ *
+ * ```php
+ * $server->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * Note that this is not a fatal error event, i.e. the server keeps listening for
+ * new connections even after this event.
+ *
+ * @see ConnectionInterface
+ */
+interface ServerInterface extends EventEmitterInterface
+{
+ /**
+ * Returns the full address (URI) this server is currently listening on
+ *
+ * ```php
+ * $address = $server->getAddress();
+ * echo 'Server listening on ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the address can not be determined or is unknown at this time (such as
+ * after the socket has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based server and you only want the local port, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $server->getAddress();
+ * $port = parse_url($address, PHP_URL_PORT);
+ * echo 'Server listening on port ' . $port . PHP_EOL;
+ * ```
+ *
+ * @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed)
+ */
+ public function getAddress();
+
+ /**
+ * Pauses accepting new incoming connections.
+ *
+ * Removes the socket resource from the EventLoop and thus stop accepting
+ * new connections. Note that the listening socket stays active and is not
+ * closed.
+ *
+ * This means that new incoming connections will stay pending in the
+ * operating system backlog until its configurable backlog is filled.
+ * Once the backlog is filled, the operating system may reject further
+ * incoming connections until the backlog is drained again by resuming
+ * to accept new connections.
+ *
+ * Once the server is paused, no futher `connection` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $server->pause();
+ *
+ * $server->on('connection', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * server MAY continue emitting `connection` events.
+ *
+ * Unless otherwise noted, a successfully opened server SHOULD NOT start
+ * in paused state.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes accepting new incoming connections.
+ *
+ * Re-attach the socket resource to the EventLoop after a previous `pause()`.
+ *
+ * ```php
+ * $server->pause();
+ *
+ * $loop->addTimer(1.0, function () use ($server) {
+ * $server->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Shuts down this listening socket
+ *
+ * This will stop listening for new incoming connections on this socket.
+ *
+ * Calling this method more than once on the same instance is a NO-OP.
+ *
+ * @return void
+ */
+ public function close();
+}
diff --git a/instafeed/vendor/react/socket/src/StreamEncryption.php b/instafeed/vendor/react/socket/src/StreamEncryption.php
new file mode 100755
index 0000000..ba5d472
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/StreamEncryption.php
@@ -0,0 +1,146 @@
+loop = $loop;
+ $this->server = $server;
+
+ // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
+ // PHP 5.6+ supports bitmasks, legacy PHP only supports predefined
+ // constants, so apply accordingly below.
+ // Also, since PHP 5.6.7 up until before PHP 7.2.0 the main constant did
+ // only support TLSv1.0, so we explicitly apply all versions.
+ // @link http://php.net/manual/en/migration56.openssl.php#migration56.openssl.crypto-method
+ // @link https://3v4l.org/plbFn
+ if ($server) {
+ $this->method = STREAM_CRYPTO_METHOD_TLS_SERVER;
+
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_SERVER;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_SERVER;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_SERVER;
+ }
+ } else {
+ $this->method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
+
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+ }
+ }
+ }
+
+ public function enable(Connection $stream)
+ {
+ return $this->toggle($stream, true);
+ }
+
+ public function disable(Connection $stream)
+ {
+ return $this->toggle($stream, false);
+ }
+
+ public function toggle(Connection $stream, $toggle)
+ {
+ // pause actual stream instance to continue operation on raw stream socket
+ $stream->pause();
+
+ // TODO: add write() event to make sure we're not sending any excessive data
+
+ $deferred = new Deferred(function ($_, $reject) use ($toggle) {
+ // cancelling this leaves this stream in an inconsistent state…
+ $reject(new RuntimeException('Cancelled toggling encryption ' . $toggle ? 'on' : 'off'));
+ });
+
+ // get actual stream socket from stream instance
+ $socket = $stream->stream;
+
+ // get crypto method from context options or use global setting from constructor
+ $method = $this->method;
+ $context = stream_context_get_options($socket);
+ if (isset($context['ssl']['crypto_method'])) {
+ $method = $context['ssl']['crypto_method'];
+ }
+
+ $that = $this;
+ $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) {
+ $that->toggleCrypto($socket, $deferred, $toggle, $method);
+ };
+
+ $this->loop->addReadStream($socket, $toggleCrypto);
+
+ if (!$this->server) {
+ $toggleCrypto();
+ }
+
+ $loop = $this->loop;
+
+ return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) {
+ $loop->removeReadStream($socket);
+
+ $stream->encryptionEnabled = $toggle;
+ $stream->resume();
+
+ return $stream;
+ }, function($error) use ($stream, $socket, $loop) {
+ $loop->removeReadStream($socket);
+ $stream->resume();
+ throw $error;
+ });
+ }
+
+ public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
+ {
+ set_error_handler(array($this, 'handleError'));
+ $result = stream_socket_enable_crypto($socket, $toggle, $method);
+ restore_error_handler();
+
+ if (true === $result) {
+ $deferred->resolve();
+ } else if (false === $result) {
+ $deferred->reject(new UnexpectedValueException(
+ sprintf("Unable to complete SSL/TLS handshake: %s", $this->errstr),
+ $this->errno
+ ));
+ } else {
+ // need more data, will retry
+ }
+ }
+
+ public function handleError($errno, $errstr)
+ {
+ $this->errstr = str_replace(array("\r", "\n"), ' ', $errstr);
+ $this->errno = $errno;
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/TcpConnector.php b/instafeed/vendor/react/socket/src/TcpConnector.php
new file mode 100755
index 0000000..53d55a3
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/TcpConnector.php
@@ -0,0 +1,129 @@
+loop = $loop;
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $ip = trim($parts['host'], '[]');
+ if (false === filter_var($ip, FILTER_VALIDATE_IP)) {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP'));
+ }
+
+ // use context given in constructor
+ $context = array(
+ 'socket' => $this->context
+ );
+
+ // parse arguments from query component of URI
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // If an original hostname has been given, use this for TLS setup.
+ // This can happen due to layers of nested connectors, such as a
+ // DnsConnector reporting its original hostname.
+ // These context options are here in case TLS is enabled later on this stream.
+ // If TLS is not enabled later, this doesn't hurt either.
+ if (isset($args['hostname'])) {
+ $context['ssl'] = array(
+ 'SNI_enabled' => true,
+ 'peer_name' => $args['hostname']
+ );
+
+ // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead.
+ // The SNI_server_name context option has to be set here during construction,
+ // as legacy PHP ignores any values set later.
+ if (PHP_VERSION_ID < 50600) {
+ $context['ssl'] += array(
+ 'SNI_server_name' => $args['hostname'],
+ 'CN_match' => $args['hostname']
+ );
+ }
+ }
+
+ // latest versions of PHP no longer accept any other URI components and
+ // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here
+ $remote = 'tcp://' . $parts['host'] . ':' . $parts['port'];
+
+ $socket = @stream_socket_client(
+ $remote,
+ $errno,
+ $errstr,
+ 0,
+ STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT,
+ stream_context_create($context)
+ );
+
+ if (false === $socket) {
+ return Promise\reject(new RuntimeException(
+ sprintf("Connection to %s failed: %s", $uri, $errstr),
+ $errno
+ ));
+ }
+
+ stream_set_blocking($socket, 0);
+
+ // wait for connection
+
+ return $this->waitForStreamOnce($socket);
+ }
+
+ private function waitForStreamOnce($stream)
+ {
+ $loop = $this->loop;
+
+ return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) {
+ $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) {
+ $loop->removeWriteStream($stream);
+
+ // The following hack looks like the only way to
+ // detect connection refused errors with PHP's stream sockets.
+ if (false === stream_socket_get_name($stream, true)) {
+ fclose($stream);
+
+ $reject(new RuntimeException('Connection refused'));
+ } else {
+ $resolve(new Connection($stream, $loop));
+ }
+ });
+ }, function () use ($loop, $stream) {
+ $loop->removeWriteStream($stream);
+ fclose($stream);
+
+ // @codeCoverageIgnoreStart
+ // legacy PHP 5.3 sometimes requires a second close call (see tests)
+ if (PHP_VERSION_ID < 50400 && is_resource($stream)) {
+ fclose($stream);
+ }
+ // @codeCoverageIgnoreEnd
+
+ throw new RuntimeException('Cancelled while waiting for TCP/IP connection to be established');
+ });
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/TcpServer.php b/instafeed/vendor/react/socket/src/TcpServer.php
new file mode 100755
index 0000000..119e177
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/TcpServer.php
@@ -0,0 +1,236 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class TcpServer extends EventEmitter implements ServerInterface
+{
+ private $master;
+ private $loop;
+ private $listening = false;
+
+ /**
+ * Creates a plaintext TCP/IP socket server and starts listening on the given address
+ *
+ * This starts accepting new incoming connections on the given address.
+ * See also the `connection event` documented in the `ServerInterface`
+ * for more details.
+ *
+ * ```php
+ * $server = new TcpServer(8080, $loop);
+ * ```
+ *
+ * As above, the `$uri` parameter can consist of only a port, in which case the
+ * server will default to listening on the localhost address `127.0.0.1`,
+ * which means it will not be reachable from outside of this system.
+ *
+ * In order to use a random port assignment, you can use the port `0`:
+ *
+ * ```php
+ * $server = new TcpServer(0, $loop);
+ * $address = $server->getAddress();
+ * ```
+ *
+ * In order to change the host the socket is listening on, you can provide an IP
+ * address through the first parameter provided to the constructor, optionally
+ * preceded by the `tcp://` scheme:
+ *
+ * ```php
+ * $server = new TcpServer('192.168.0.1:8080', $loop);
+ * ```
+ *
+ * If you want to listen on an IPv6 address, you MUST enclose the host in square
+ * brackets:
+ *
+ * ```php
+ * $server = new TcpServer('[::1]:8080', $loop);
+ * ```
+ *
+ * If the given URI is invalid, does not contain a port, any other scheme or if it
+ * contains a hostname, it will throw an `InvalidArgumentException`:
+ *
+ * ```php
+ * // throws InvalidArgumentException due to missing port
+ * $server = new TcpServer('127.0.0.1', $loop);
+ * ```
+ *
+ * If the given URI appears to be valid, but listening on it fails (such as if port
+ * is already in use or port below 1024 may require root access etc.), it will
+ * throw a `RuntimeException`:
+ *
+ * ```php
+ * $first = new TcpServer(8080, $loop);
+ *
+ * // throws RuntimeException because port is already in use
+ * $second = new TcpServer(8080, $loop);
+ * ```
+ *
+ * Note that these error conditions may vary depending on your system and/or
+ * configuration.
+ * See the exception message and code for more details about the actual error
+ * condition.
+ *
+ * Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php)
+ * for the underlying stream socket resource like this:
+ *
+ * ```php
+ * $server = new TcpServer('[::1]:8080', $loop, array(
+ * 'backlog' => 200,
+ * 'so_reuseport' => true,
+ * 'ipv6_v6only' => true
+ * ));
+ * ```
+ *
+ * Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ *
+ * @param string|int $uri
+ * @param LoopInterface $loop
+ * @param array $context
+ * @throws InvalidArgumentException if the listening address is invalid
+ * @throws RuntimeException if listening on this address fails (already in use etc.)
+ */
+ public function __construct($uri, LoopInterface $loop, array $context = array())
+ {
+ $this->loop = $loop;
+
+ // a single port has been given => assume localhost
+ if ((string)(int)$uri === (string)$uri) {
+ $uri = '127.0.0.1:' . $uri;
+ }
+
+ // assume default scheme if none has been given
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ // parse_url() does not accept null ports (random port assignment) => manually remove
+ if (substr($uri, -2) === ':0') {
+ $parts = parse_url(substr($uri, 0, -2));
+ if ($parts) {
+ $parts['port'] = 0;
+ }
+ } else {
+ $parts = parse_url($uri);
+ }
+
+ // ensure URI contains TCP scheme, host and port
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ throw new InvalidArgumentException('Invalid URI "' . $uri . '" given');
+ }
+
+ if (false === filter_var(trim($parts['host'], '[]'), FILTER_VALIDATE_IP)) {
+ throw new InvalidArgumentException('Given URI "' . $uri . '" does not contain a valid host IP');
+ }
+
+ $this->master = @stream_socket_server(
+ $uri,
+ $errno,
+ $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
+ stream_context_create(array('socket' => $context))
+ );
+ if (false === $this->master) {
+ throw new RuntimeException('Failed to listen on "' . $uri . '": ' . $errstr, $errno);
+ }
+ stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!is_resource($this->master)) {
+ return null;
+ }
+
+ $address = stream_socket_get_name($this->master, false);
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = strrpos($address, ':');
+ if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') {
+ $port = substr($address, $pos + 1);
+ $address = '[' . substr($address, 0, $pos) . ']:' . $port;
+ }
+
+ return 'tcp://' . $address;
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ $newSocket = @stream_socket_accept($master);
+ if (false === $newSocket) {
+ $that->emit('error', array(new RuntimeException('Error accepting new connection')));
+
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $this->emit('connection', array(
+ new Connection($socket, $this->loop)
+ ));
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/TimeoutConnector.php b/instafeed/vendor/react/socket/src/TimeoutConnector.php
new file mode 100755
index 0000000..d4eba2e
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/TimeoutConnector.php
@@ -0,0 +1,25 @@
+connector = $connector;
+ $this->timeout = $timeout;
+ $this->loop = $loop;
+ }
+
+ public function connect($uri)
+ {
+ return Timer\timeout($this->connector->connect($uri), $this->timeout, $this->loop);
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/UnixConnector.php b/instafeed/vendor/react/socket/src/UnixConnector.php
new file mode 100755
index 0000000..9b84ab0
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/UnixConnector.php
@@ -0,0 +1,44 @@
+loop = $loop;
+ }
+
+ public function connect($path)
+ {
+ if (strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (substr($path, 0, 7) !== 'unix://') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $path . '" is invalid'));
+ }
+
+ $resource = @stream_socket_client($path, $errno, $errstr, 1.0);
+
+ if (!$resource) {
+ return Promise\reject(new RuntimeException('Unable to connect to unix domain socket "' . $path . '": ' . $errstr, $errno));
+ }
+
+ $connection = new Connection($resource, $this->loop);
+ $connection->unix = true;
+
+ return Promise\resolve($connection);
+ }
+}
diff --git a/instafeed/vendor/react/socket/src/UnixServer.php b/instafeed/vendor/react/socket/src/UnixServer.php
new file mode 100755
index 0000000..8f1ed98
--- /dev/null
+++ b/instafeed/vendor/react/socket/src/UnixServer.php
@@ -0,0 +1,130 @@
+loop = $loop;
+
+ if (strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (substr($path, 0, 7) !== 'unix://') {
+ throw new InvalidArgumentException('Given URI "' . $path . '" is invalid');
+ }
+
+ $this->master = @stream_socket_server(
+ $path,
+ $errno,
+ $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
+ stream_context_create(array('socket' => $context))
+ );
+ if (false === $this->master) {
+ throw new RuntimeException('Failed to listen on unix domain socket "' . $path . '": ' . $errstr, $errno);
+ }
+ stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!is_resource($this->master)) {
+ return null;
+ }
+
+ return 'unix://' . stream_socket_get_name($this->master, false);
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ $newSocket = @stream_socket_accept($master);
+ if (false === $newSocket) {
+ $that->emit('error', array(new RuntimeException('Error accepting new connection')));
+
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $connection = new Connection($socket, $this->loop);
+ $connection->unix = true;
+
+ $this->emit('connection', array(
+ $connection
+ ));
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/ConnectionTest.php b/instafeed/vendor/react/socket/tests/ConnectionTest.php
new file mode 100755
index 0000000..d3563df
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/ConnectionTest.php
@@ -0,0 +1,47 @@
+markTestSkipped('HHVM does not support socket operation on test memory stream');
+ }
+
+ $resource = fopen('php://memory', 'r+');
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connection = new Connection($resource, $loop);
+ $connection->close();
+
+ $this->assertFalse(is_resource($resource));
+ }
+
+ public function testCloseConnectionWillRemoveResourceFromLoopBeforeClosingResource()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not support socket operation on test memory stream');
+ }
+
+ $resource = fopen('php://memory', 'r+');
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addWriteStream')->with($resource);
+
+ $onRemove = null;
+ $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($param) use (&$onRemove) {
+ $onRemove = is_resource($param);
+ return true;
+ }));
+
+ $connection = new Connection($resource, $loop);
+ $connection->write('test');
+ $connection->close();
+
+ $this->assertTrue($onRemove);
+ $this->assertFalse(is_resource($resource));
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/ConnectorTest.php b/instafeed/vendor/react/socket/tests/ConnectorTest.php
new file mode 100755
index 0000000..c8eb19b
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/ConnectorTest.php
@@ -0,0 +1,128 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp
+ ));
+
+ $connector->connect('127.0.0.1:80');
+ }
+
+ public function testConnectorPassedThroughHostnameIfDnsIsDisabled()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('tcp://google.com:80')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'dns' => false
+ ));
+
+ $connector->connect('tcp://google.com:80');
+ }
+
+ public function testConnectorWithUnknownSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop);
+
+ $promise = $connector->connect('unknown://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTcpDefaultSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tcp' => false
+ ));
+
+ $promise = $connector->connect('google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTcpSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tcp' => false
+ ));
+
+ $promise = $connector->connect('tcp://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTlsSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tls' => false
+ ));
+
+ $promise = $connector->connect('tls://google.com:443');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledUnixSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'unix' => false
+ ));
+
+ $promise = $connector->connect('unix://demo.sock');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorUsesGivenResolverInstance()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+ $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'dns' => $resolver
+ ));
+
+ $connector->connect('google.com:80');
+ }
+
+ public function testConnectorUsesResolvedHostnameIfDnsIsUsed()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function ($resolve) { $resolve('127.0.0.1'); });
+ $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+ $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise);
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('tcp://127.0.0.1:80?hostname=google.com')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'dns' => $resolver
+ ));
+
+ $connector->connect('tcp://google.com:80');
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/DnsConnectorTest.php b/instafeed/vendor/react/socket/tests/DnsConnectorTest.php
new file mode 100755
index 0000000..3c94c39
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/DnsConnectorTest.php
@@ -0,0 +1,111 @@
+tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+
+ $this->connector = new DnsConnector($this->tcp, $this->resolver);
+ }
+
+ public function testPassByResolverIfGivenIp()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('127.0.0.1:80');
+ }
+
+ public function testPassThroughResolverIfGivenHost()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=google.com'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('google.com:80');
+ }
+
+ public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('::1')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80?hostname=google.com'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('google.com:80');
+ }
+
+ public function testPassByResolverIfGivenCompleteUri()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://127.0.0.1:80/path?query#fragment');
+ }
+
+ public function testPassThroughResolverIfGivenCompleteUri()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/path?query&hostname=google.com#fragment'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://google.com:80/path?query#fragment');
+ }
+
+ public function testPassThroughResolverIfGivenExplicitHost()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/?hostname=google.de'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://google.com:80/?hostname=google.de');
+ }
+
+ public function testRejectsImmediatelyIfUriIsInvalid()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('////');
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testSkipConnectionIfDnsFails()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.invalid'))->will($this->returnValue(Promise\reject()));
+ $this->tcp->expects($this->never())->method('connect');
+
+ $this->connector->connect('example.invalid:80');
+ }
+
+ public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, $this->expectCallableOnce());
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue($pending));
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testCancelDuringTcpConnectionCancelsTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/FixedUriConnectorTest.php b/instafeed/vendor/react/socket/tests/FixedUriConnectorTest.php
new file mode 100755
index 0000000..f42d74f
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/FixedUriConnectorTest.php
@@ -0,0 +1,19 @@
+getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $base->expects($this->once())->method('connect')->with('test')->willReturn('ret');
+
+ $connector = new FixedUriConnector('test', $base);
+
+ $this->assertEquals('ret', $connector->connect('ignored'));
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/FunctionalConnectorTest.php b/instafeed/vendor/react/socket/tests/FunctionalConnectorTest.php
new file mode 100755
index 0000000..6611352
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/FunctionalConnectorTest.php
@@ -0,0 +1,32 @@
+on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new Connector($loop);
+
+ $connection = Block\await($connector->connect('localhost:9998'), $loop, self::TIMEOUT);
+
+ $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
+
+ $connection->close();
+ $server->close();
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/FunctionalSecureServerTest.php b/instafeed/vendor/react/socket/tests/FunctionalSecureServerTest.php
new file mode 100755
index 0000000..78a59d0
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/FunctionalSecureServerTest.php
@@ -0,0 +1,438 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+ }
+
+ public function testEmitsConnectionForNewConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testWritesDataToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write('foo');
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local ConnectionInterface */
+
+ $local->on('data', $this->expectCallableOnceWith('foo'));
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testWritesDataInMultipleChunksToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write(str_repeat('*', 400000));
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ public function testWritesMoreDataInMultipleChunksToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write(str_repeat('*', 2000000));
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(2000000, $received);
+ }
+
+ public function testEmitsDataFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $once = $this->expectCallableOnceWith('foo');
+ $server->on('connection', function (ConnectionInterface $conn) use ($once) {
+ $conn->on('data', $once);
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $local->write("foo");
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsDataInMultipleChunksFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $received = 0;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$received) {
+ $conn->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $local->write(str_repeat('*', 400000));
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ public function testPipesDataBackInMultipleChunksFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) use (&$received) {
+ $conn->pipe($conn);
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ $local->write(str_repeat('*', 400000));
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ /**
+ * @requires PHP 5.6
+ */
+ public function testEmitsConnectionForNewTlsv11Connection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false,
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ /**
+ * @requires PHP 5.6
+ */
+ public function testEmitsErrorForClientWithTlsVersionMismatch()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER|STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false,
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsConnectionForNewConnectionWithEncryptedCertificate()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem',
+ 'passphrase' => 'swordfish'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithInvalidCertificate()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'invalid.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithEncryptedCertificateMissingPassphrase()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithEncryptedCertificateWithInvalidPassphrase()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem',
+ 'passphrase' => 'nope'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForConnectionWithPeerVerification()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => true
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then(null, $this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsErrorIfConnectionIsCancelled()
+ {
+ if (PHP_OS !== 'Linux') {
+ $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsNothingIfConnectionIsIdle()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableNever());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect(str_replace('tls://', '', $server->getAddress()));
+
+ $promise->then($this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsErrorIfConnectionIsNotSecureHandshake()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect(str_replace('tls://', '', $server->getAddress()));
+
+ $promise->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/FunctionalTcpServerTest.php b/instafeed/vendor/react/socket/tests/FunctionalTcpServerTest.php
new file mode 100755
index 0000000..ec7855e
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/FunctionalTcpServerTest.php
@@ -0,0 +1,324 @@
+on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsNoConnectionForNewConnectionWhenPaused()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+ $server->pause();
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionForNewConnectionWhenResumedAfterPause()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->pause();
+ $server->resume();
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionWithRemoteIp()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $peer);
+ }
+
+ public function testEmitsConnectionWithLocalIp()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $local);
+ $this->assertEquals($server->getAddress(), $local);
+ }
+
+ public function testEmitsConnectionWithLocalIpDespiteListeningOnAll()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer('0.0.0.0:0', $loop);
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $local);
+ }
+
+ public function testEmitsConnectionWithRemoteIpAfterConnectionIsClosedByPeer()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $conn->on('close', function () use ($conn, &$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $client = Block\await($promise, $loop, 0.1);
+ $client->end();
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $peer);
+ }
+
+ public function testEmitsConnectionWithRemoteNullAddressAfterConnectionIsClosedLocally()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $conn->close();
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertNull($peer);
+ }
+
+ public function testEmitsConnectionEvenIfConnectionIsCancelled()
+ {
+ if (PHP_OS !== 'Linux') {
+ $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionForNewIpv6Connection()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionWithRemoteIpv6()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('[::1]:', $peer);
+ }
+
+ public function testEmitsConnectionWithLocalIpv6()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('[::1]:', $local);
+ $this->assertEquals($server->getAddress(), $local);
+ }
+
+ public function testEmitsConnectionWithInheritedContextOptions()
+ {
+ if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
+ // https://3v4l.org/hB4Tc
+ $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop, array(
+ 'backlog' => 4
+ ));
+
+ $all = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$all) {
+ $all = stream_context_get_options($conn->stream);
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnInvalidUri()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('///', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWithoutPort()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('127.0.0.1', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWithWrongScheme()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('udp://127.0.0.1:0', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWIthHostname()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('localhost:8080', $loop);
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/IntegrationTest.php b/instafeed/vendor/react/socket/tests/IntegrationTest.php
new file mode 100755
index 0000000..59dff4f
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/IntegrationTest.php
@@ -0,0 +1,328 @@
+connect('google.com:80'), $loop);
+
+ $this->assertContains(':80', $conn->getRemoteAddress());
+ $this->assertNotEquals('google.com:80', $conn->getRemoteAddress());
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingEncryptedStuffFromGoogleShouldWork()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+ $secureConnector = new Connector($loop);
+
+ $conn = Block\await($secureConnector->connect('tls://google.com:443'), $loop);
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingEncryptedStuffFromGoogleShouldWorkIfHostIsResolvedFirst()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $factory = new ResolverFactory();
+ $dns = $factory->create('8.8.8.8', $loop);
+
+ $connector = new DnsConnector(
+ new SecureConnector(
+ new TcpConnector($loop),
+ $loop
+ ),
+ $dns
+ );
+
+ $conn = Block\await($connector->connect('google.com:443'), $loop);
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingPlaintextStuffFromEncryptedGoogleShouldNotWork()
+ {
+ $loop = Factory::create();
+ $connector = new Connector($loop);
+
+ $conn = Block\await($connector->connect('google.com:443'), $loop);
+
+ $this->assertContains(':443', $conn->getRemoteAddress());
+ $this->assertNotEquals('google.com:443', $conn->getRemoteAddress());
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertNotRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ public function testConnectingFailsIfDnsUsesInvalidResolver()
+ {
+ $loop = Factory::create();
+
+ $factory = new ResolverFactory();
+ $dns = $factory->create('demo.invalid', $loop);
+
+ $connector = new Connector($loop, array(
+ 'dns' => $dns
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
+ }
+
+ public function testCancellingPendingConnectionWithoutTimeoutShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+ $promise = $connector->connect('8.8.8.8:80');
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPendingConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop);
+
+ gc_collect_cycles();
+ $promise = $connector->connect('8.8.8.8:80');
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForRejectedConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+
+ $wait = true;
+ $promise = $connector->connect('127.0.0.1:1')->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect connection refused error
+ Block\sleep(0.01, $loop);
+ if ($wait) {
+ Block\sleep(0.2, $loop);
+ if ($wait) {
+ $this->fail('Connection attempt did not fail');
+ }
+ }
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @requires PHP 7
+ */
+ public function testWaitingForConnectionTimeoutShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => 0.001));
+
+ gc_collect_cycles();
+
+ $wait = true;
+ $promise = $connector->connect('google.com:80')->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect connection timeout error
+ Block\sleep(0.01, $loop);
+ if ($wait) {
+ Block\sleep(0.2, $loop);
+ if ($wait) {
+ $this->fail('Connection attempt did not fail');
+ }
+ }
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForInvalidDnsConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+
+ $wait = true;
+ $promise = $connector->connect('example.invalid:80')->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect DNS error
+ Block\sleep(0.01, $loop);
+ if ($wait) {
+ Block\sleep(0.2, $loop);
+ if ($wait) {
+ $this->fail('Connection attempt did not fail');
+ }
+ }
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForSuccessfullyClosedConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+ $promise = $connector->connect('google.com:80')->then(
+ function ($conn) {
+ $conn->close();
+ }
+ );
+ Block\await($promise, $loop, self::TIMEOUT);
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testConnectingFailsIfTimeoutIsTooSmall()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'timeout' => 0.001
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
+ }
+
+ public function testSelfSignedRejectsIfVerificationIsEnabled()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'verify_peer' => true
+ )
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
+ }
+
+ public function testSelfSignedResolvesIfVerificationIsDisabled()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'verify_peer' => false
+ )
+ ));
+
+ $conn = Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
+ $conn->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/LimitingServerTest.php b/instafeed/vendor/react/socket/tests/LimitingServerTest.php
new file mode 100755
index 0000000..2cc9a58
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/LimitingServerTest.php
@@ -0,0 +1,195 @@
+getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn('127.0.0.1:1234');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $this->assertEquals('127.0.0.1:1234', $server->getAddress());
+ }
+
+ public function testPauseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ }
+
+ public function testPauseTwiceWillBePassedThroughToTcpServerOnce()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testResumeWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->resume();
+ }
+
+ public function testResumeTwiceWillBePassedThroughToTcpServerOnce()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->resume();
+ $server->resume();
+ }
+
+ public function testCloseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('close');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->close();
+ }
+
+ public function testSocketErrorWillBeForwarded()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('error', array(new \RuntimeException('test')));
+ }
+
+ public function testSocketConnectionWillBeForwarded()
+ {
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 100);
+ $server->on('connection', $this->expectCallableOnceWith($connection));
+ $server->on('error', $this->expectCallableNever());
+
+ $tcp->emit('connection', array($connection));
+
+ $this->assertEquals(array($connection), $server->getConnections());
+ }
+
+ public function testSocketConnectionWillBeClosedOnceLimitIsReached()
+ {
+ $first = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $first->expects($this->never())->method('close');
+ $second = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $second->expects($this->once())->method('close');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 1);
+ $server->on('connection', $this->expectCallableOnceWith($first));
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('connection', array($first));
+ $tcp->emit('connection', array($second));
+ }
+
+ public function testPausingServerWillBePausedOnceLimitIsReached()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $tcp = new TcpServer(0, $loop);
+
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $server = new LimitingServer($tcp, 1, true);
+
+ $tcp->emit('connection', array($connection));
+ }
+
+ public function testSocketDisconnectionWillRemoveFromList()
+ {
+ $loop = Factory::create();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $socket = stream_socket_client($tcp->getAddress());
+ fclose($socket);
+
+ $server = new LimitingServer($tcp, 100);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('error', $this->expectCallableNever());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array(), $server->getConnections());
+ }
+
+ public function testPausingServerWillEmitOnlyOneButAcceptTwoConnectionsDueToOperatingSystem()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new LimitingServer($server, 1, true);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('error', $this->expectCallableNever());
+
+ $first = stream_socket_client($server->getAddress());
+ $second = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ fclose($first);
+ fclose($second);
+ }
+
+ public function testPausingServerWillEmitTwoConnectionsFromBacklog()
+ {
+ $loop = Factory::create();
+
+ $twice = $this->createCallableMock();
+ $twice->expects($this->exactly(2))->method('__invoke');
+
+ $server = new TcpServer(0, $loop);
+ $server = new LimitingServer($server, 1, true);
+ $server->on('connection', $twice);
+ $server->on('error', $this->expectCallableNever());
+
+ $first = stream_socket_client($server->getAddress());
+ fclose($first);
+ $second = stream_socket_client($server->getAddress());
+ fclose($second);
+
+ Block\sleep(0.1, $loop);
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/SecureConnectorTest.php b/instafeed/vendor/react/socket/tests/SecureConnectorTest.php
new file mode 100755
index 0000000..0b3a702
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/SecureConnectorTest.php
@@ -0,0 +1,74 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->connector = new SecureConnector($this->tcp, $this->loop);
+ }
+
+ public function testConnectionWillWaitForTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectionWithCompleteUriWillBePassedThroughExpectForScheme()
+ {
+ $pending = new Promise\Promise(function () { });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80/path?query#fragment'))->will($this->returnValue($pending));
+
+ $this->connector->connect('tls://example.com:80/path?query#fragment');
+ }
+
+ public function testConnectionToInvalidSchemeWillReject()
+ {
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('tcp://example.com:80');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testCancelDuringTcpConnectionCancelsTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testConnectionWillBeClosedAndRejectedIfConnectioIsNoStream()
+ {
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $connection->expects($this->once())->method('close');
+
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->willReturn(Promise\resolve($connection));
+
+ $promise = $this->connector->connect('example.com:80');
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/SecureIntegrationTest.php b/instafeed/vendor/react/socket/tests/SecureIntegrationTest.php
new file mode 100755
index 0000000..8c9ba14
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/SecureIntegrationTest.php
@@ -0,0 +1,204 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $this->loop = LoopFactory::create();
+ $this->server = new TcpServer(0, $this->loop);
+ $this->server = new SecureServer($this->server, $this->loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $this->address = $this->server->getAddress();
+ $this->connector = new SecureConnector(new TcpConnector($this->loop), $this->loop, array('verify_peer' => false));
+ }
+
+ public function tearDown()
+ {
+ if ($this->server !== null) {
+ $this->server->close();
+ $this->server = null;
+ }
+ }
+
+ public function testConnectToServer()
+ {
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testConnectToServerEmitsConnection()
+ {
+ $promiseServer = $this->createPromiseForEvent($this->server, 'connection', $this->expectCallableOnce());
+
+ $promiseClient = $this->connector->connect($this->address);
+
+ list($_, $client) = Block\awaitAll(array($promiseServer, $promiseClient), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->close();
+ }
+
+ public function testSendSmallDataToServerReceivesOneChunk()
+ {
+ // server expects one connection which emits one data event
+ $received = new Deferred();
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($received) {
+ $peer->on('data', function ($chunk) use ($received) {
+ $received->resolve($chunk);
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->write('hello');
+
+ // await server to report one "data" event
+ $data = Block\await($received->promise(), $this->loop, self::TIMEOUT);
+
+ $client->close();
+
+ $this->assertEquals('hello', $data);
+ }
+
+ public function testSendDataWithEndToServerReceivesAllData()
+ {
+ $disconnected = new Deferred();
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($disconnected) {
+ $received = '';
+ $peer->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ $peer->on('close', function () use (&$received, $disconnected) {
+ $disconnected->resolve($received);
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $data = str_repeat('a', 200000);
+ $client->end($data);
+
+ // await server to report connection "close" event
+ $received = Block\await($disconnected->promise(), $this->loop, self::TIMEOUT);
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testSendDataWithoutEndingToServerReceivesAllData()
+ {
+ $received = '';
+ $this->server->on('connection', function (ConnectionInterface $peer) use (&$received) {
+ $peer->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $data = str_repeat('d', 200000);
+ $client->write($data);
+
+ // buffer incoming data for 0.1s (should be plenty of time)
+ Block\sleep(0.1, $this->loop);
+
+ $client->close();
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testConnectToServerWhichSendsSmallDataReceivesOneChunk()
+ {
+ $this->server->on('connection', function (ConnectionInterface $peer) {
+ $peer->write('hello');
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // await client to report one "data" event
+ $receive = $this->createPromiseForEvent($client, 'data', $this->expectCallableOnceWith('hello'));
+ Block\await($receive, $this->loop, self::TIMEOUT);
+
+ $client->close();
+ }
+
+ public function testConnectToServerWhichSendsDataWithEndReceivesAllData()
+ {
+ $data = str_repeat('b', 100000);
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
+ $peer->end($data);
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // await data from client until it closes
+ $received = $this->buffer($client, $this->loop, self::TIMEOUT);
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testConnectToServerWhichSendsDataWithoutEndingReceivesAllData()
+ {
+ $data = str_repeat('c', 100000);
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
+ $peer->write($data);
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // buffer incoming data for 0.1s (should be plenty of time)
+ $received = '';
+ $client->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ Block\sleep(0.1, $this->loop);
+
+ $client->close();
+
+ $this->assertEquals($data, $received);
+ }
+
+ private function createPromiseForEvent(EventEmitterInterface $emitter, $event, $fn)
+ {
+ return new Promise(function ($resolve) use ($emitter, $event, $fn) {
+ $emitter->on($event, function () use ($resolve, $fn) {
+ $resolve(call_user_func_array($fn, func_get_args()));
+ });
+ });
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/SecureServerTest.php b/instafeed/vendor/react/socket/tests/SecureServerTest.php
new file mode 100755
index 0000000..92c641f
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/SecureServerTest.php
@@ -0,0 +1,105 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+ }
+
+ public function testGetAddressWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn('tcp://127.0.0.1:1234');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $this->assertEquals('tls://127.0.0.1:1234', $server->getAddress());
+ }
+
+ public function testGetAddressWillReturnNullIfTcpServerReturnsNull()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn(null);
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $this->assertNull($server->getAddress());
+ }
+
+ public function testPauseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->pause();
+ }
+
+ public function testResumeWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->resume();
+ }
+
+ public function testCloseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('close');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->close();
+ }
+
+ public function testConnectionWillBeEndedWithErrorIfItIsNotAStream()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $connection->expects($this->once())->method('end');
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('connection', array($connection));
+ }
+
+ public function testSocketErrorWillBeForwarded()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('error', array(new \RuntimeException('test')));
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/ServerTest.php b/instafeed/vendor/react/socket/tests/ServerTest.php
new file mode 100755
index 0000000..14fdb2c
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/ServerTest.php
@@ -0,0 +1,173 @@
+assertNotEquals(0, $server->getAddress());
+ $server->close();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsForInvalidUri()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new Server('invalid URI', $loop);
+ }
+
+ public function testConstructorCreatesExpectedTcpServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+
+ $connector = new TcpConnector($loop);
+ $connector->connect($server->getAddress())
+ ->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT);
+
+ $connection->close();
+ $server->close();
+ }
+
+ public function testConstructorCreatesExpectedUnixServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server($this->getRandomSocketUri(), $loop);
+
+ $connector = new UnixConnector($loop);
+ $connector->connect($server->getAddress())
+ ->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT);
+
+ $connection->close();
+ $server->close();
+ }
+
+ public function testEmitsConnectionForNewConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesNotEmitConnectionForNewConnectionToPausedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->pause();
+ $server->on('connection', $this->expectCallableNever());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesEmitConnectionForNewConnectionToResumedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->pause();
+ $server->on('connection', $this->expectCallableOnce());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ $server->resume();
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesNotAllowConnectionToClosedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+ $address = $server->getAddress();
+ $server->close();
+
+ $client = @stream_socket_client($address);
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertFalse($client);
+ }
+
+ public function testEmitsConnectionWithInheritedContextOptions()
+ {
+ if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
+ // https://3v4l.org/hB4Tc
+ $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
+ }
+
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop, array(
+ 'backlog' => 4
+ ));
+
+ $all = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$all) {
+ $all = stream_context_get_options($conn->stream);
+ });
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
+ }
+
+ public function testDoesNotEmitSecureConnectionForNewPlainConnection()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $server = new Server('tls://127.0.0.1:0', $loop, array(
+ 'tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ )
+ ));
+ $server->on('connection', $this->expectCallableNever());
+
+ $client = stream_socket_client(str_replace('tls://', '', $server->getAddress()));
+
+ Block\sleep(0.1, $loop);
+ }
+
+ private function getRandomSocketUri()
+ {
+ return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock';
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/Stub/CallableStub.php b/instafeed/vendor/react/socket/tests/Stub/CallableStub.php
new file mode 100755
index 0000000..1b197eb
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/Stub/CallableStub.php
@@ -0,0 +1,10 @@
+data .= $data;
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ }
+
+ public function close()
+ {
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function getRemoteAddress()
+ {
+ return '127.0.0.1';
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/Stub/ServerStub.php b/instafeed/vendor/react/socket/tests/Stub/ServerStub.php
new file mode 100755
index 0000000..d9e74f4
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/Stub/ServerStub.php
@@ -0,0 +1,18 @@
+connect('127.0.0.1:9999')
+ ->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldAddResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+
+ $valid = false;
+ $loop->expects($this->once())->method('addWriteStream')->with($this->callback(function ($arg) use (&$valid) {
+ $valid = is_resource($arg);
+ return true;
+ }));
+ $connector->connect($server->getAddress());
+
+ $this->assertTrue($valid);
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceed()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+
+ $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithRemoteAdressSameAsTarget()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertEquals('tcp://127.0.0.1:9999', $connection->getRemoteAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithLocalAdressOnLocalhost()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertContains('tcp://127.0.0.1:', $connection->getLocalAddress());
+ $this->assertNotEquals('tcp://127.0.0.1:9999', $connection->getLocalAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithNullAddressesAfterConnectionClosed()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $connection->close();
+
+ $this->assertNull($connection->getRemoteAddress());
+ $this->assertNull($connection->getLocalAddress());
+ }
+
+ /** @test */
+ public function connectionToTcpServerWillCloseWhenOtherSideCloses()
+ {
+ $loop = Factory::create();
+
+ // immediately close connection and server once connection is in
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', function (ConnectionInterface $conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $once = $this->expectCallableOnce();
+ $connector = new TcpConnector($loop);
+ $connector->connect($server->getAddress())->then(function (ConnectionInterface $conn) use ($once) {
+ $conn->write('hello');
+ $conn->on('close', $once);
+ });
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToEmptyIp6PortShouldFail()
+ {
+ $loop = Factory::create();
+
+ $connector = new TcpConnector($loop);
+ $connector
+ ->connect('[::1]:9999')
+ ->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToIp6TcpServerShouldSucceed()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:9999', $loop);
+ } catch (\Exception $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (IPv6 not supported on this system?)');
+ }
+
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('[::1]:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertEquals('tcp://[::1]:9999', $connection->getRemoteAddress());
+
+ $this->assertContains('tcp://[::1]:', $connection->getLocalAddress());
+ $this->assertNotEquals('tcp://[::1]:9999', $connection->getLocalAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToHostnameShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('www.google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function connectionToInvalidPortShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('255.255.255.255:12345678')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function connectionToInvalidSchemeShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('tls://google.com:443')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function cancellingConnectionShouldRemoveResourceFromLoopAndCloseResource()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+
+ $loop->expects($this->once())->method('addWriteStream');
+ $promise = $connector->connect($server->getAddress());
+
+ $resource = null;
+ $valid = false;
+ $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($arg) use (&$resource, &$valid) {
+ $resource = $arg;
+ $valid = is_resource($arg);
+ return true;
+ }));
+ $promise->cancel();
+
+ // ensure that this was a valid resource during the removeWriteStream() call
+ $this->assertTrue($valid);
+
+ // ensure that this resource should now be closed after the cancel() call
+ $this->assertInternalType('resource', $resource);
+ $this->assertFalse(is_resource($resource));
+ }
+
+ /** @test */
+ public function cancellingConnectionShouldRejectPromise()
+ {
+ $loop = Factory::create();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $this->setExpectedException('RuntimeException', 'Cancelled');
+ Block\await($promise, $loop);
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/TcpServerTest.php b/instafeed/vendor/react/socket/tests/TcpServerTest.php
new file mode 100755
index 0000000..72b3c28
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/TcpServerTest.php
@@ -0,0 +1,285 @@
+loop = $this->createLoop();
+ $this->server = new TcpServer(0, $this->loop);
+
+ $this->port = parse_url($this->server->getAddress(), PHP_URL_PORT);
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::handleConnection
+ */
+ public function testConnection()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $this->server->on('connection', $this->expectCallableOnce());
+
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::handleConnection
+ */
+ public function testConnectionWithManyClients()
+ {
+ $client1 = stream_socket_client('tcp://localhost:'.$this->port);
+ $client2 = stream_socket_client('tcp://localhost:'.$this->port);
+ $client3 = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $this->server->on('connection', $this->expectCallableExactly(3));
+ $this->tick();
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataEventWillNotBeEmittedWhenClientSendsNoData()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedWithDataClientSends()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ fwrite($client, "foo\n");
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+ fwrite($client, "foo\n");
+ stream_socket_shutdown($client, STREAM_SHUT_WR);
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testLoopWillEndWhenServerIsClosed()
+ {
+ // explicitly unset server because we already call close()
+ $this->server->close();
+ $this->server = null;
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testCloseTwiceIsNoOp()
+ {
+ $this->server->close();
+ $this->server->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testGetAddressAfterCloseReturnsNull()
+ {
+ $this->server->close();
+ $this->assertNull($this->server->getAddress());
+ }
+
+ public function testLoopWillEndWhenServerIsClosedAfterSingleConnection()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $server->on('connection', function ($conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+ $stream = new DuplexResourceStream($client, $this->loop);
+
+ $bytes = 1024 * 1024;
+ $stream->end(str_repeat('*', $bytes));
+
+ $mock = $this->expectCallableOnce();
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $received = 0;
+ $server->on('connection', function ($conn) use ($mock, &$received, $server) {
+ // count number of bytes received
+ $conn->on('data', function ($data) use (&$received) {
+ $received += strlen($data);
+ });
+
+ $conn->on('end', $mock);
+
+ // do not await any further connections in order to let the loop terminate
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ $this->assertEquals($bytes, $received);
+ }
+
+ public function testConnectionDoesNotEndWhenClientDoesNotClose()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\Connection::end
+ */
+ public function testConnectionDoesEndWhenClientCloses()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ fclose($client);
+
+ $mock = $this->expectCallableOnce();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testCtorAddsResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new TcpServer(0, $loop);
+ }
+
+ public function testResumeWithoutPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->resume();
+ }
+
+ public function testPauseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->pause();
+ }
+
+ public function testPauseAfterPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testCloseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testListenOnBusyPortThrows()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Windows supports listening on same port multiple times');
+ }
+
+ $another = new TcpServer($this->port, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::close
+ */
+ public function tearDown()
+ {
+ if ($this->server) {
+ $this->server->close();
+ }
+ }
+
+ private function tick()
+ {
+ Block\sleep(0, $this->loop);
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/TestCase.php b/instafeed/vendor/react/socket/tests/TestCase.php
new file mode 100755
index 0000000..e87fc2f
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/TestCase.php
@@ -0,0 +1,101 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Socket\Stub\CallableStub')->getMock();
+ }
+
+ protected function buffer(ReadableStreamInterface $stream, LoopInterface $loop, $timeout)
+ {
+ if (!$stream->isReadable()) {
+ return '';
+ }
+
+ return Block\await(new Promise(
+ function ($resolve, $reject) use ($stream) {
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $reject);
+
+ $stream->on('close', function () use (&$buffer, $resolve) {
+ $resolve($buffer);
+ });
+ },
+ function () use ($stream) {
+ $stream->close();
+ throw new \RuntimeException();
+ }
+ ), $loop, $timeout);
+ }
+
+ public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
+ {
+ if (method_exists($this, 'expectException')) {
+ // PHPUnit 5+
+ $this->expectException($exception);
+ if ($exceptionMessage !== '') {
+ $this->expectExceptionMessage($exceptionMessage);
+ }
+ if ($exceptionCode !== null) {
+ $this->expectExceptionCode($exceptionCode);
+ }
+ } else {
+ // legacy PHPUnit 4
+ parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
+ }
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/TimeoutConnectorTest.php b/instafeed/vendor/react/socket/tests/TimeoutConnectorTest.php
new file mode 100755
index 0000000..64787d9
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/TimeoutConnectorTest.php
@@ -0,0 +1,103 @@
+getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testRejectsWhenConnectorRejects()
+ {
+ $promise = Promise\reject(new \RuntimeException());
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 5.0, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testResolvesWhenConnectorResolves()
+ {
+ $promise = Promise\resolve();
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 5.0, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableOnce(),
+ $this->expectCallableNever()
+ );
+
+ $loop->run();
+ }
+
+ public function testRejectsAndCancelsPendingPromiseOnTimeout()
+ {
+ $promise = new Promise\Promise(function () { }, $this->expectCallableOnce());
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testCancelsPendingPromiseOnCancel()
+ {
+ $promise = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $out = $timeout->connect('google.com:80');
+ $out->cancel();
+
+ $out->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/UnixConnectorTest.php b/instafeed/vendor/react/socket/tests/UnixConnectorTest.php
new file mode 100755
index 0000000..1564064
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/UnixConnectorTest.php
@@ -0,0 +1,64 @@
+loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->connector = new UnixConnector($this->loop);
+ }
+
+ public function testInvalid()
+ {
+ $promise = $this->connector->connect('google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testInvalidScheme()
+ {
+ $promise = $this->connector->connect('tcp://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testValid()
+ {
+ // random unix domain socket path
+ $path = sys_get_temp_dir() . '/test' . uniqid() . '.sock';
+
+ // temporarily create unix domain socket server to connect to
+ $server = stream_socket_server('unix://' . $path, $errno, $errstr);
+
+ // skip test if we can not create a test server (Windows etc.)
+ if (!$server) {
+ $this->markTestSkipped('Unable to create socket "' . $path . '": ' . $errstr . '(' . $errno .')');
+ return;
+ }
+
+ // tests succeeds if we get notified of successful connection
+ $promise = $this->connector->connect($path);
+ $promise->then($this->expectCallableOnce());
+
+ // remember remote and local address of this connection and close again
+ $remote = $local = false;
+ $promise->then(function(ConnectionInterface $conn) use (&$remote, &$local) {
+ $remote = $conn->getRemoteAddress();
+ $local = $conn->getLocalAddress();
+ $conn->close();
+ });
+
+ // clean up server
+ fclose($server);
+ unlink($path);
+
+ $this->assertNull($local);
+ $this->assertEquals('unix://' . $path, $remote);
+ }
+}
diff --git a/instafeed/vendor/react/socket/tests/UnixServerTest.php b/instafeed/vendor/react/socket/tests/UnixServerTest.php
new file mode 100755
index 0000000..10f7e4f
--- /dev/null
+++ b/instafeed/vendor/react/socket/tests/UnixServerTest.php
@@ -0,0 +1,283 @@
+loop = Factory::create();
+ $this->uds = $this->getRandomSocketUri();
+ $this->server = new UnixServer($this->uds, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::handleConnection
+ */
+ public function testConnection()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $this->server->on('connection', $this->expectCallableOnce());
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::handleConnection
+ */
+ public function testConnectionWithManyClients()
+ {
+ $client1 = stream_socket_client($this->uds);
+ $client2 = stream_socket_client($this->uds);
+ $client3 = stream_socket_client($this->uds);
+
+ $this->server->on('connection', $this->expectCallableExactly(3));
+ $this->tick();
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataEventWillNotBeEmittedWhenClientSendsNoData()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedWithDataClientSends()
+ {
+ $client = stream_socket_client($this->uds);
+
+ fwrite($client, "foo\n");
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending()
+ {
+ $client = stream_socket_client($this->uds);
+ fwrite($client, "foo\n");
+ stream_socket_shutdown($client, STREAM_SHUT_WR);
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testLoopWillEndWhenServerIsClosed()
+ {
+ // explicitly unset server because we already call close()
+ $this->server->close();
+ $this->server = null;
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testCloseTwiceIsNoOp()
+ {
+ $this->server->close();
+ $this->server->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testGetAddressAfterCloseReturnsNull()
+ {
+ $this->server->close();
+ $this->assertNull($this->server->getAddress());
+ }
+
+ public function testLoopWillEndWhenServerIsClosedAfterSingleConnection()
+ {
+ $client = stream_socket_client($this->uds);
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $server->on('connection', function ($conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts()
+ {
+ $client = stream_socket_client($this->uds);
+ $stream = new DuplexResourceStream($client, $this->loop);
+
+ $bytes = 1024 * 1024;
+ $stream->end(str_repeat('*', $bytes));
+
+ $mock = $this->expectCallableOnce();
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $received = 0;
+ $server->on('connection', function ($conn) use ($mock, &$received, $server) {
+ // count number of bytes received
+ $conn->on('data', function ($data) use (&$received) {
+ $received += strlen($data);
+ });
+
+ $conn->on('end', $mock);
+
+ // do not await any further connections in order to let the loop terminate
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ $this->assertEquals($bytes, $received);
+ }
+
+ public function testConnectionDoesNotEndWhenClientDoesNotClose()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\Connection::end
+ */
+ public function testConnectionDoesEndWhenClientCloses()
+ {
+ $client = stream_socket_client($this->uds);
+
+ fclose($client);
+
+ $mock = $this->expectCallableOnce();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testCtorAddsResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ }
+
+ public function testResumeWithoutPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->resume();
+ }
+
+ public function testPauseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->pause();
+ }
+
+ public function testPauseAfterPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testCloseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testListenOnBusyPortThrows()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Windows supports listening on same port multiple times');
+ }
+
+ $another = new UnixServer($this->uds, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::close
+ */
+ public function tearDown()
+ {
+ if ($this->server) {
+ $this->server->close();
+ }
+ }
+
+ private function getRandomSocketUri()
+ {
+ return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock';
+ }
+
+ private function tick()
+ {
+ Block\sleep(0, $this->loop);
+ }
+}
diff --git a/instafeed/vendor/react/stream/.gitignore b/instafeed/vendor/react/stream/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/instafeed/vendor/react/stream/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/instafeed/vendor/react/stream/.travis.yml b/instafeed/vendor/react/stream/.travis.yml
new file mode 100755
index 0000000..9ff354b
--- /dev/null
+++ b/instafeed/vendor/react/stream/.travis.yml
@@ -0,0 +1,51 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+# - 7.0 # Mac OS X test setup, ignore errors, see below
+ - 7.1
+ - 7.2
+ - 7.3
+ - nightly # ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ include:
+ - os: osx
+ language: generic
+ php: 7.0 # just to look right on travis
+ env:
+ - PACKAGE: php70
+ allow_failures:
+ - php: nightly
+ - php: hhvm
+ - os: osx
+
+install:
+ # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew tap homebrew/homebrew-php
+ echo "Installing PHP ..."
+ brew install "${PACKAGE}"
+ brew install "${PACKAGE}"-xdebug
+ brew link "${PACKAGE}"
+ echo "Installing composer ..."
+ curl -s http://getcomposer.org/installer | php
+ mv composer.phar /usr/local/bin/composer
+ fi
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
+ - time php examples/91-benchmark-throughput.php
diff --git a/instafeed/vendor/react/stream/CHANGELOG.md b/instafeed/vendor/react/stream/CHANGELOG.md
new file mode 100755
index 0000000..c5a7dd3
--- /dev/null
+++ b/instafeed/vendor/react/stream/CHANGELOG.md
@@ -0,0 +1,398 @@
+# Changelog
+
+## 1.1.0 (2018-01-01)
+
+* Improvement: Increase performance by optimizing global function and constant look ups
+ (#137 by @WyriHaximus)
+* Travis: Test against PHP 7.3
+ (#138 by @WyriHaximus)
+* Fix: Ignore empty reads
+ (#139 by @WyriHaximus)
+
+## 1.0.0 (2018-07-11)
+
+* First stable LTS release, now following [SemVer](https://semver.org/).
+ We'd like to emphasize that this component is production ready and battle-tested.
+ We plan to support all long-term support (LTS) releases for at least 24 months,
+ so you have a rock-solid foundation to build on top of.
+
+> Contains no other changes, so it's actually fully compatible with the v0.7.7 release.
+
+## 0.7.7 (2018-01-19)
+
+* Improve test suite by fixing forward compatibility with upcoming EventLoop
+ releases, avoid risky tests and add test group to skip integration tests
+ relying on internet connection and apply appropriate test timeouts.
+ (#128, #131 and #132 by @clue)
+
+## 0.7.6 (2017-12-21)
+
+* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12
+ (#126 by @clue)
+
+* Improve test suite by simplifying test bootstrapping logic via Composer and
+ test against PHP 7.2
+ (#127 by @clue and #124 by @carusogabriel)
+
+## 0.7.5 (2017-11-20)
+
+* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream`
+ (#119 by @clue)
+
+* Fix: Fix forward compatibility with upcoming EventLoop releases
+ (#121 by @clue)
+
+* Restructure examples to ease getting started
+ (#123 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#122 by @gabriel-caruso and #120 by @clue)
+
+## 0.7.4 (2017-10-11)
+
+* Fix: Remove event listeners from `CompositeStream` once closed and
+ remove undocumented left-over `close` event argument
+ (#116 by @clue)
+
+* Minor documentation improvements: Fix wrong class name in example,
+ fix typos in README and
+ fix forward compatibility with upcoming EventLoop releases in example
+ (#113 by @docteurklein and #114 and #115 by @clue)
+
+* Improve test suite by running against Mac OS X on Travis
+ (#112 by @clue)
+
+## 0.7.3 (2017-08-05)
+
+* Improvement: Support Événement 3.0 a long side 2.0 and 1.0
+ (#108 by @WyriHaximus)
+
+* Readme: Corrected loop initialization in usage example
+ (#109 by @pulyavin)
+
+* Travis: Lock linux distribution preventing future builds from breaking
+ (#110 by @clue)
+
+## 0.7.2 (2017-06-15)
+
+* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream.
+ (#107 by @WyriHaximus)
+
+## 0.7.1 (2017-05-20)
+
+* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of
+ bytes to write at once.
+ (#105 by @clue)
+
+ ```php
+ $stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+ ```
+
+* Ignore HHVM test failures for now until Travis tests work again
+ (#106 by @clue)
+
+## 0.7.0 (2017-05-04)
+
+* Removed / BC break: Remove deprecated and unneeded functionality
+ (#45, #87, #90, #91 and #93 by @clue)
+
+ * Remove deprecated `Stream` class, use `DuplexResourceStream` instead
+ (#87 by @clue)
+
+ * Remove public `$buffer` property, use new constructor parameters instead
+ (#91 by @clue)
+
+ * Remove public `$stream` property from all resource streams
+ (#90 by @clue)
+
+ * Remove undocumented and now unused `ReadableStream` and `WritableStream`
+ (#93 by @clue)
+
+ * Remove `BufferedSink`
+ (#45 by @clue)
+
+* Feature / BC break: Simplify `ThroughStream` by using data callback instead of
+ inheritance. It is now a direct implementation of `DuplexStreamInterface`.
+ (#88 and #89 by @clue)
+
+ ```php
+ $through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+ });
+ $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+ $through->write(array(2, true));
+ ```
+
+* Feature / BC break: The `CompositeStream` starts closed if either side is
+ already closed and forwards pause to pipe source on first write attempt.
+ (#96 and #103 by @clue)
+
+ If either side of the composite stream closes, it will also close the other
+ side. We now also ensure that if either side is already closed during
+ instantiation, it will also close the other side.
+
+* BC break: Mark all classes as `final` and
+ mark internal API as `private` to discourage inheritance
+ (#95 and #99 by @clue)
+
+* Feature / BC break: Only emit `error` event for fatal errors
+ (#92 by @clue)
+
+ > The `error` event was previously also allowed to be emitted for non-fatal
+ errors, but our implementations actually only ever emitted this as a fatal
+ error and then closed the stream.
+
+* Feature: Explicitly allow custom events and exclude any semantics
+ (#97 by @clue)
+
+* Strict definition for event callback functions
+ (#101 by @clue)
+
+* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation
+ (#100 and #102 by @clue)
+
+* Actually require all dependencies so this is self-contained and improve
+ forward compatibility with EventLoop v1.0 and v0.5
+ (#94 and #98 by @clue)
+
+## 0.6.0 (2017-03-26)
+
+* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream`
+ (#85 by @clue)
+
+ ```php
+ // old (does still work for BC reasons)
+ $stream = new Stream($connection, $loop);
+
+ // new
+ $stream = new DuplexResourceStream($connection, $loop);
+ ```
+
+ Note that the `DuplexResourceStream` now rejects read-only or write-only
+ streams, so this may affect BC. If you want a read-only or write-only
+ resource, use `ReadableResourceStream` or `WritableResourceStream` instead of
+ `DuplexResourceStream`.
+
+ > BC note: This class was previously called `Stream`. The `Stream` class still
+ exists for BC reasons and will be removed in future versions of this package.
+
+* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`)
+ (#84 by @clue)
+
+ ```php
+ // old
+ $stream = new Buffer(STDOUT, $loop);
+
+ // new
+ $stream = new WritableResourceStream(STDOUT, $loop);
+ ```
+
+* Feature: Add `ReadableResourceStream`
+ (#83 by @clue)
+
+ ```php
+ $stream = new ReadableResourceStream(STDIN, $loop);
+ ```
+
+* Fix / BC Break: Enforce using non-blocking I/O
+ (#46 by @clue)
+
+ > BC note: This is known to affect process pipes on Windows which do not
+ support non-blocking I/O and could thus block the whole EventLoop previously.
+
+* Feature / Fix / BC break: Consistent semantics for
+ `DuplexStreamInterface::end()` to ensure it SHOULD also end readable side
+ (#86 by @clue)
+
+* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4
+ (#80 by @clue)
+
+## 0.5.0 (2017-03-08)
+
+* Feature / BC break: Consistent `end` event semantics (EOF)
+ (#70 by @clue)
+
+ The `end` event will now only be emitted for a *successful* end, not if the
+ stream closes due to an unrecoverable `error` event or if you call `close()`
+ explicitly.
+ If you want to detect when the stream closes (terminates), use the `close`
+ event instead.
+
+* BC break: Remove custom (undocumented) `full-drain` event from `Buffer`
+ (#63 and #68 by @clue)
+
+ > The `full-drain` event was undocumented and mostly used internally.
+ Relying on this event has attracted some low-quality code in the past, so
+ we've removed this from the public API in order to work out a better
+ solution instead.
+ If you want to detect when the buffer finishes flushing data to the stream,
+ you may want to look into its `end()` method or the `close` event instead.
+
+* Feature / BC break: Consistent event semantics and documentation,
+ explicitly state *when* events will be emitted and *which* arguments they
+ receive.
+ (#73 and #69 by @clue)
+
+ The documentation now explicitly defines each event and its arguments.
+ Custom events and event arguments are still supported.
+ Most notably, all defined events only receive inherently required event
+ arguments and no longer transmit the instance they are emitted on for
+ consistency and performance reasons.
+
+ ```php
+ // old (inconsistent and not supported by all implementations)
+ $stream->on('data', function ($data, $stream) {
+ // process $data
+ });
+
+ // new (consistent throughout the whole ecosystem)
+ $stream->on('data', function ($data) use ($stream) {
+ // process $data
+ });
+ ```
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature / BC break: Consistent method semantics and documentation
+ (#72 by @clue)
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature: Consistent `pipe()` semantics for closed and closing streams
+ (#71 from @clue)
+
+ The source stream will now always be paused via `pause()` when the
+ destination stream closes. Also, properly stop piping if the source
+ stream closes and remove all event forwarding.
+
+* Improve test suite by adding PHPUnit to `require-dev` and improving coverage.
+ (#74 and #75 by @clue, #66 by @nawarian)
+
+## 0.4.6 (2017-01-25)
+
+* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone)
+ (#62 by @clue)
+
+* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream`
+ (#60 by @clue)
+
+* Fix: Consistent `close` event behavior for `Buffer`
+ (#61 by @clue)
+
+## 0.4.5 (2016-11-13)
+
+* Feature: Support setting read buffer size to `null` (infinite)
+ (#42 by @clue)
+
+* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event
+ (#55 by @clue)
+
+* Vastly improved performance by factor of 10x to 20x.
+ Raise default buffer sizes to 64 KiB and simplify and improve error handling
+ and unneeded function calls.
+ (#53, #55, #56 by @clue)
+
+## 0.4.4 (2016-08-22)
+
+* Bug fix: Emit `error` event and close `Stream` when accessing the underlying
+ stream resource fails with a permanent error.
+ (#52 and #40 by @clue, #25 by @lysenkobv)
+
+* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF)
+ (#39 by @clue)
+
+* Bug fix: Ignore empty writes to `Buffer`
+ (#51 by @clue)
+
+* Add benchmarking script to measure throughput in CI
+ (#41 by @clue)
+
+## 0.4.3 (2015-10-07)
+
+* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau)
+* Bug fix: No double-write during drain call (@arnaud-lb)
+* Bug fix: Support HHVM (@clue)
+* Adjust compatibility to 5.3 (@clue)
+
+## 0.4.2 (2014-09-09)
+
+* Added DuplexStreamInterface
+* Stream sets stream resources to non-blocking
+* Fixed potential race condition in pipe
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: [Stream] Correctly detect closed connections
+
+## 0.3.2 (2013-05-10)
+
+* Bug fix: [Stream] Make sure CompositeStream is closed properly
+
+## 0.3.1 (2013-04-21)
+
+* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()`
+
+## 0.3.0 (2013-04-14)
+
+* Feature: [Stream] Factory method for BufferedSink
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.5 (2012-11-26)
+
+* Feature: Make BufferedSink trigger progress events on the promise (@jsor)
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream
+* Feature: Added BufferedSink
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Version bump
+
+## 0.2.1 (2012-10-14)
+
+* Bug fix: Check for EOF in `Buffer::write()`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/instafeed/vendor/react/stream/LICENSE b/instafeed/vendor/react/stream/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/instafeed/vendor/react/stream/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/react/stream/README.md b/instafeed/vendor/react/stream/README.md
new file mode 100755
index 0000000..b5bc907
--- /dev/null
+++ b/instafeed/vendor/react/stream/README.md
@@ -0,0 +1,1225 @@
+# Stream
+
+[](https://travis-ci.org/reactphp/stream)
+
+Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/).
+
+In order to make the [EventLoop](https://github.com/reactphp/event-loop)
+easier to use, this component introduces the powerful concept of "streams".
+Streams allow you to efficiently process huge amounts of data (such as a multi
+Gigabyte file download) in small chunks without having to store everything in
+memory at once.
+They are very similar to the streams found in PHP itself,
+but have an interface more suited for async, non-blocking I/O.
+
+**Table of contents**
+
+* [Stream usage](#stream-usage)
+ * [ReadableStreamInterface](#readablestreaminterface)
+ * [data event](#data-event)
+ * [end event](#end-event)
+ * [error event](#error-event)
+ * [close event](#close-event)
+ * [isReadable()](#isreadable)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [pipe()](#pipe)
+ * [close()](#close)
+ * [WritableStreamInterface](#writablestreaminterface)
+ * [drain event](#drain-event)
+ * [pipe event](#pipe-event)
+ * [error event](#error-event-1)
+ * [close event](#close-event-1)
+ * [isWritable()](#iswritable)
+ * [write()](#write)
+ * [end()](#end)
+ * [close()](#close-1)
+ * [DuplexStreamInterface](#duplexstreaminterface)
+* [Creating streams](#creating-streams)
+ * [ReadableResourceStream](#readableresourcestream)
+ * [WritableResourceStream](#writableresourcestream)
+ * [DuplexResourceStream](#duplexresourcestream)
+ * [ThroughStream](#throughstream)
+ * [CompositeStream](#compositestream)
+* [Usage](#usage)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Stream usage
+
+ReactPHP uses the concept of "streams" throughout its ecosystem to provide a
+consistent higher-level abstraction for processing streams of arbitrary data
+contents and size.
+While a stream itself is a quite low-level concept, it can be used as a powerful
+abstraction to build higher-level components and protocols on top.
+
+If you're new to this concept, it helps to think of them as a water pipe:
+You can consume water from a source or you can produce water and forward (pipe)
+it to any destination (sink).
+
+Similarly, streams can either be
+
+* readable (such as `STDIN` terminal input) or
+* writable (such as `STDOUT` terminal output) or
+* duplex (both readable *and* writable, such as a TCP/IP connection)
+
+Accordingly, this package defines the following three interfaces
+
+* [`ReadableStreamInterface`](#readablestreaminterface)
+* [`WritableStreamInterface`](#writablestreaminterface)
+* [`DuplexStreamInterface`](#duplexstreaminterface)
+
+### ReadableStreamInterface
+
+The `ReadableStreamInterface` is responsible for providing an interface for
+read-only streams and the readable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### data event
+
+The `data` event will be emitted whenever some data was read/received
+from this source stream.
+The event receives a single mixed argument for incoming data.
+
+```php
+$stream->on('data', function ($data) {
+ echo $data;
+});
+```
+
+This event MAY be emitted any number of times, which may be zero times if
+this stream does not send any data at all.
+It SHOULD not be emitted after an `end` or `close` event.
+
+The given `$data` argument may be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit the raw (binary) payload data that is received over the wire as
+chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end event
+
+The `end` event will be emitted once the source stream has successfully
+reached the end of the stream (EOF).
+
+```php
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+a successful end was detected.
+It SHOULD NOT be emitted after a previous `end` or `close` event.
+It MUST NOT be emitted if the stream closes due to a non-successful
+end, such as after a previous `error` event.
+
+After the stream is ended, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+This event will only be emitted if the *end* was reached successfully,
+not if the stream was interrupted by an unrecoverable error or explicitly
+closed. Not all streams know this concept of a "successful end".
+Many use-cases involve detecting when the stream closes (terminates)
+instead, in this case you should use the `close` event.
+After the stream emits an `end` event, it SHOULD usually be followed by a
+`close` event.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit this event if either the remote side closes the connection or
+a file handle was successfully read until reaching its end (EOF).
+
+Note that this event should not be confused with the `end()` method.
+This event defines a successful end *reading* from a source stream, while
+the `end()` method defines *writing* a successful end to a destination
+stream.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to read from this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error or after an unexpected `data` or premature
+`end` event.
+It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-readable mode, see
+also `close()` and `isReadable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and do not make assumption about data
+boundaries (such as unexpected `data` or premature `end` events).
+In other words, many lower-level protocols (such as TCP/IP) may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+Unlike the `end` event, this event SHOULD be emitted whenever the stream
+closes, irrespective of whether this happens implicitly due to an
+unrecoverable error or explicitly when either side closes the stream.
+If you only want to detect a *successful* end, you should use the `end`
+event instead.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after reading a *successful* `end`
+event or after a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isReadable()
+
+The `isReadable(): bool` method can be used to
+check whether this stream is in a readable state (not closed already).
+
+This method can be used to check if the stream still accepts incoming
+data events or if it is ended or closed already.
+Once the stream is non-readable, no further `data` or `end` events SHOULD
+be emitted.
+
+```php
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+A successfully opened stream always MUST start in readable mode.
+
+Once the stream ends or closes, it MUST switch to non-readable mode.
+This can happen any time, explicitly through `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-readable mode, it MUST NOT transition
+back to readable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `isWritable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause reading incoming data events.
+
+Removes the data source file descriptor from the event loop. This
+allows you to throttle incoming data.
+
+Unless otherwise noted, a successfully opened stream SHOULD NOT start
+in paused state.
+
+Once the stream is paused, no futher `data` or `end` events SHOULD
+be emitted.
+
+```php
+$stream->pause();
+
+$stream->on('data', assertShouldNeverCalled());
+$stream->on('end', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+stream MAY continue emitting `data` events.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+
+See also `resume()`.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume reading incoming data events.
+
+Re-attach the data source after a previous `pause()`.
+
+```php
+$stream->pause();
+
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+
+See also `pause()`.
+
+#### pipe()
+
+The `pipe(WritableStreamInterface $dest, array $options = [])` method can be used to
+pipe all the data from this readable source into the given writable destination.
+
+Automatically sends all incoming data to the destination.
+Automatically throttles the source based on what the destination can handle.
+
+```php
+$source->pipe($dest);
+```
+
+Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+into itself in order to write back all the data that is received.
+This may be a useful feature for a TCP/IP echo service:
+
+```php
+$connection->pipe($connection);
+```
+
+This method returns the destination stream as-is, which can be used to
+set up chains of piped streams:
+
+```php
+$source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+```
+
+By default, this will call `end()` on the destination stream once the
+source stream emits an `end` event. This can be disabled like this:
+
+```php
+$source->pipe($dest, array('end' => false));
+```
+
+Note that this only applies to the `end` event.
+If an `error` or explicit `close` event happens on the source stream,
+you'll have to manually close the destination stream:
+
+```php
+$source->pipe($dest);
+$source->on('close', function () use ($dest) {
+ $dest->end('BYE!');
+});
+```
+
+If the source stream is not readable (closed state), then this is a NO-OP.
+
+```php
+$source->close();
+$source->pipe($dest); // NO-OP
+```
+
+If the destinantion stream is not writable (closed state), then this will simply
+throttle (pause) the source stream:
+
+```php
+$dest->close();
+$source->pipe($dest); // calls $source->pause()
+```
+
+Similarly, if the destination stream is closed while the pipe is still
+active, it will also throttle (pause) the source stream:
+
+```php
+$source->pipe($dest);
+$dest->close(); // calls $source->pause()
+```
+
+Once the pipe is set up successfully, the destination stream MUST emit
+a `pipe` event with this source stream an event argument.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to (forcefully) close the stream.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-readable
+mode, see also `isReadable()`.
+This means that no further `data` or `end` events SHOULD be emitted.
+
+```php
+$stream->close();
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this method should not be confused with the `end()` method.
+
+### WritableStreamInterface
+
+The `WritableStreamInterface` is responsible for providing an interface for
+write-only streams and the writable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### drain event
+
+The `drain` event will be emitted whenever the write buffer became full
+previously and is now ready to accept more data.
+
+```php
+$stream->on('drain', function () use ($stream) {
+ echo 'Stream is now ready to accept more data';
+});
+```
+
+This event SHOULD be emitted once every time the buffer became full
+previously and is now ready to accept more data.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if the buffer never became full in the first place.
+This event SHOULD NOT be emitted if the buffer has not become full
+previously.
+
+This event is mostly used internally, see also `write()` for more details.
+
+#### pipe event
+
+The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+into this stream.
+The event receives a single `ReadableStreamInterface` argument for the
+source stream.
+
+```php
+$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ echo 'Now receiving piped data';
+
+ // explicitly close target if source emits an error
+ $source->on('error', function () use ($stream) {
+ $stream->close();
+ });
+});
+
+$source->pipe($stream);
+```
+
+This event MUST be emitted once for each readable stream that is
+successfully piped into this destination stream.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if no stream is ever piped into this stream.
+This event MUST NOT be emitted if either the source is not readable
+(closed already) or this destination is not writable (closed already).
+
+This event is mostly used internally, see also `pipe()` for more details.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to write to this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$stream->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error.
+It SHOULD NOT be emitted after a previous `error` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-writable mode, see
+also `close()` and `isWritable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-writable mode,
+see also `isWritable()`.
+
+This event SHOULD be emitted whenever the stream closes, irrespective of
+whether this happens implicitly due to an unrecoverable error or
+explicitly when either side closes the stream.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after flushing the buffer from
+the `end()` method, after receiving a *successful* `end` event or after
+a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isWritable()
+
+The `isWritable(): bool` method can be used to
+check whether this stream is in a writable state (not closed already).
+
+This method can be used to check if the stream still accepts writing
+any data or if it is ended or closed already.
+Writing any data to a non-writable stream is a NO-OP:
+
+```php
+assert($stream->isWritable() === false);
+
+$stream->write('end'); // NO-OP
+$stream->end('end'); // NO-OP
+```
+
+A successfully opened stream always MUST start in writable mode.
+
+Once the stream ends or closes, it MUST switch to non-writable mode.
+This can happen any time, explicitly through `end()` or `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-writable mode, it MUST NOT transition
+back to writable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `isReadable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### write()
+
+The `write(mixed $data): bool` method can be used to
+write some data into the stream.
+
+A successful write MUST be confirmed with a boolean `true`, which means
+that either the data was written (flushed) immediately or is buffered and
+scheduled for a future write. Note that this interface gives you no
+control over explicitly flushing the buffered data, as finding the
+appropriate time for this is beyond the scope of this interface and left
+up to the implementation of this interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+an `error` event and MAY `close()` the stream if it can not recover from
+this error.
+
+If the internal buffer is full after adding `$data`, then `write()`
+SHOULD return `false`, indicating that the caller should stop sending
+data until the buffer drains.
+The stream SHOULD send a `drain` event once the buffer is ready to accept
+more data.
+
+Similarly, if the the stream is not writable (already in a closed state)
+it MUST NOT process the given `$data` and SHOULD return `false`,
+indicating that the caller should stop sending data.
+
+The given `$data` argument MAY be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will only accept the raw (binary) payload data that is transferred over
+the wire as chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end()
+
+The `end(mixed $data = null): void` method can be used to
+successfully end the stream (after optionally sending some final data).
+
+This method can be used to successfully end the stream, i.e. close
+the stream after sending out all data that is currently buffered.
+
+```php
+$stream->write('hello');
+$stream->write('world');
+$stream->end();
+```
+
+If there's no data currently buffered and nothing to be flushed, then
+this method MAY `close()` the stream immediately.
+
+If there's still data in the buffer that needs to be flushed first, then
+this method SHOULD try to write out this data and only then `close()`
+the stream.
+Once the stream is closed, it SHOULD emit a `close` event.
+
+Note that this interface gives you no control over explicitly flushing
+the buffered data, as finding the appropriate time for this is beyond the
+scope of this interface and left up to the implementation of this
+interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+You can optionally pass some final data that is written to the stream
+before ending the stream. If a non-`null` value is given as `$data`, then
+this method will behave just like calling `write($data)` before ending
+with no data.
+
+```php
+// shorter version
+$stream->end('bye');
+
+// same as longer version
+$stream->write('bye');
+$stream->end();
+```
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->end();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+also end its readable side, unless the stream supports half-open mode.
+In other words, after calling this method, these streams SHOULD switch
+into non-writable AND non-readable mode, see also `isReadable()`.
+This implies that in this case, the stream SHOULD NOT emit any `data`
+or `end` events anymore.
+Streams MAY choose to use the `pause()` method logic for this, but
+special care may have to be taken to ensure a following call to the
+`resume()` method SHOULD NOT continue emitting readable events.
+
+Note that this method should not be confused with the `close()` method.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to forcefully close the stream, i.e. close
+the stream without waiting for any buffered data to be flushed.
+If there's still data in the buffer, this data SHOULD be discarded.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->close();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+Note that this method should not be confused with the `end()` method.
+Unlike the `end()` method, this method does not take care of any existing
+buffers and simply discards any buffer contents.
+Likewise, this method may also be called after calling `end()` on a
+stream in order to stop waiting for the stream to flush its final data.
+
+```php
+$stream->end();
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->close();
+});
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+
+### DuplexStreamInterface
+
+The `DuplexStreamInterface` is responsible for providing an interface for
+duplex streams (both readable and writable).
+
+It builds on top of the existing interfaces for readable and writable streams
+and follows the exact same method and event semantics.
+If you're new to this concept, you should look into the
+`ReadableStreamInterface` and `WritableStreamInterface` first.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to the same events defined
+on the `ReadbleStreamInterface` and `WritableStreamInterface`.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+## Creating streams
+
+ReactPHP uses the concept of "streams" throughout its ecosystem, so that
+many higher-level consumers of this package only deal with
+[stream usage](#stream-usage).
+This implies that stream instances are most often created within some
+higher-level components and many consumers never actually have to deal with
+creating a stream instance.
+
+* Use [react/socket](https://github.com/reactphp/socket)
+ if you want to accept incoming or establish outgoing plaintext TCP/IP or
+ secure TLS socket connection streams.
+* Use [react/http](https://github.com/reactphp/http)
+ if you want to receive an incoming HTTP request body streams.
+* Use [react/child-process](https://github.com/reactphp/child-process)
+ if you want to communicate with child processes via process pipes such as
+ STDIN, STDOUT, STDERR etc.
+* Use experimental [react/filesystem](https://github.com/reactphp/filesystem)
+ if you want to read from / write to the filesystem.
+* See also the last chapter for [more real-world applications](#more).
+
+However, if you are writing a lower-level component or want to create a stream
+instance from a stream resource, then the following chapter is for you.
+
+> Note that the following examples use `fopen()` and `stream_socket_client()`
+ for illustration purposes only.
+ These functions SHOULD NOT be used in a truly async program because each call
+ may take several seconds to complete and would block the EventLoop otherwise.
+ Additionally, the `fopen()` call will return a file handle on some platforms
+ which may or may not be supported by all EventLoop implementations.
+ As an alternative, you may want to use higher-level libraries listed above.
+
+### ReadableResourceStream
+
+The `ReadableResourceStream` is a concrete implementation of the
+[`ReadableStreamInterface`](#readablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-only resource like a file stream opened in
+readable mode or a stream such as `STDIN`:
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop);
+$stream->on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened in reading mode (e.g. `fopen()` mode `r`).
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new ReadableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDIN etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new ReadableResourceStream(STDIN, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop, 8192);
+```
+
+> PHP bug warning: If the PHP process has explicitly been started without a
+ `STDIN` stream, then trying to read from `STDIN` may return data from
+ another stream resource. This does not happen if you start this with an empty
+ stream like `php test.php < /dev/null` instead of `php test.php <&-`.
+ See [#81](https://github.com/reactphp/stream/issues/81) for more details.
+
+### WritableResourceStream
+
+The `WritableResourceStream` is a concrete implementation of the
+[`WritableStreamInterface`](#writablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a write-only resource like a file stream opened in
+writable mode or a stream such as `STDOUT` or `STDERR`:
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new WritableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new WritableResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls
+this maximum buffer size in bytes.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, 8192);
+```
+
+This class takes an optional `int|null $writeChunkSize` parameter that controls
+this maximum buffer size in bytes to write at once to the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be written
+at once to the underlying stream resource. Note that the actual number
+of bytes written may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "write everything available" to the
+underlying stream resource.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+```
+
+See also [`write()`](#write) for more details.
+
+### DuplexResourceStream
+
+The `DuplexResourceStream` is a concrete implementation of the
+[`DuplexStreamInterface`](#duplexstreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-and-write resource like a file stream opened
+in read and write mode mode or a stream such as a TCP/IP connection:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`DuplexStreamInterface`](#duplexstreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for reading *and* writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new DuplexResourceStream(false, $loop);
+```
+
+See also the [`ReadableResourceStream`](#readableresourcestream) for read-only
+and the [`WritableResourceStream`](#writableresourcestream) for write-only
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new DuplexResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop, 8192);
+```
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes another optional `WritableStreamInterface|null $buffer` parameter
+that controls this write behavior of this stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+If you want to change the write buffer soft limit, you can pass an instance of
+[`WritableResourceStream`](#writableresourcestream) like this:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$buffer = new WritableResourceStream($conn, $loop, 8192);
+$stream = new DuplexResourceStream($conn, $loop, null, $buffer);
+```
+
+See also [`WritableResourceStream`](#writableresourcestream) for more details.
+
+### ThroughStream
+
+The `ThroughStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data
+you write to it through to its readable end.
+
+```php
+$through = new ThroughStream();
+$through->on('data', $this->expectCallableOnceWith('hello'));
+
+$through->write('hello');
+```
+
+Similarly, the [`end()` method](#end) will end the stream and emit an
+[`end` event](#end-event) and then [`close()`](#close-1) the stream.
+The [`close()` method](#close-1) will close the stream and emit a
+[`close` event](#close-event).
+Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+
+```php
+$through = new ThroughStream();
+$source->pipe($through)->pipe($dest);
+```
+
+Optionally, its constructor accepts any callable function which will then be
+used to *filter* any data written to it. This function receives a single data
+argument as passed to the writable side and must return the data as it will be
+passed to its readable end:
+
+```php
+$through = new ThroughStream('strtoupper');
+$source->pipe($through)->pipe($dest);
+```
+
+Note that this class makes no assumptions about any data types. This can be
+used to convert data, for example for transforming any structured data into
+a newline-delimited JSON (NDJSON) stream like this:
+
+```php
+$through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+});
+$through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+$through->write(array(2, true));
+```
+
+The callback function is allowed to throw an `Exception`. In this case,
+the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+
+```php
+$through = new ThroughStream(function ($data) {
+ if (!is_string($data)) {
+ throw new \UnexpectedValueException('Only strings allowed');
+ }
+ return $data;
+});
+$through->on('error', $this->expectCallableOnce()));
+$through->on('close', $this->expectCallableOnce()));
+$through->on('data', $this->expectCallableNever()));
+
+$through->write(2);
+```
+
+### CompositeStream
+
+The `CompositeStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and can be used to create a
+single duplex stream from two individual streams implementing
+[`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) respectively.
+
+This is useful for some APIs which may require a single
+[`DuplexStreamInterface`](#duplexstreaminterface) or simply because it's often
+more convenient to work with a single stream instance like this:
+
+```php
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdout = new WritableResourceStream(STDOUT, $loop);
+
+$stdio = new CompositeStream($stdin, $stdout);
+
+$stdio->on('data', function ($chunk) use ($stdio) {
+ $stdio->write('You said: ' . $chunk);
+});
+```
+
+This is a well-behaving stream which forwards all stream events from the
+underlying streams and forwards all streams calls to the underlying streams.
+
+If you `write()` to the duplex stream, it will simply `write()` to the
+writable side and return its status.
+
+If you `end()` the duplex stream, it will `end()` the writable side and will
+`pause()` the readable side.
+
+If you `close()` the duplex stream, both input streams will be closed.
+If either of the two input streams emits a `close` event, the duplex stream
+will also close.
+If either of the two input streams is already closed while constructing the
+duplex stream, it will `close()` the other side and return a closed stream.
+
+## Usage
+
+The following example can be used to pipe the contents of a source file into
+a destination file without having to ever read the whole file into memory:
+
+```php
+$loop = new React\EventLoop\StreamSelectLoop;
+
+$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r'), $loop);
+$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w'), $loop);
+
+$source->pipe($dest);
+
+$loop->run();
+```
+
+> Note that this example uses `fopen()` for illustration purposes only.
+ This should not be used in a truly async program because the filesystem is
+ inherently blocking and each call could potentially take several seconds.
+ See also [creating streams](#creating-streams) for more sophisticated
+ examples.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/stream:^1.0
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project due to its vast
+performance improvements.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## More
+
+* See [creating streams](#creating-streams) for more information on how streams
+ are created in real-world applications.
+* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
+ [dependents on Packagist](https://packagist.org/packages/react/stream/dependents)
+ for a list of packages that use streams in real-world applications.
diff --git a/instafeed/vendor/react/stream/composer.json b/instafeed/vendor/react/stream/composer.json
new file mode 100755
index 0000000..f6faa66
--- /dev/null
+++ b/instafeed/vendor/react/stream/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "react/stream",
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35",
+ "clue/stream-filter": "~1.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Stream\\": "tests"
+ }
+ }
+}
diff --git a/instafeed/vendor/react/stream/examples/01-http.php b/instafeed/vendor/react/stream/examples/01-http.php
new file mode 100755
index 0000000..3687f7c
--- /dev/null
+++ b/instafeed/vendor/react/stream/examples/01-http.php
@@ -0,0 +1,40 @@
+on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/instafeed/vendor/react/stream/examples/02-https.php b/instafeed/vendor/react/stream/examples/02-https.php
new file mode 100755
index 0000000..163f7c8
--- /dev/null
+++ b/instafeed/vendor/react/stream/examples/02-https.php
@@ -0,0 +1,40 @@
+on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/instafeed/vendor/react/stream/examples/11-cat.php b/instafeed/vendor/react/stream/examples/11-cat.php
new file mode 100755
index 0000000..90fadc0
--- /dev/null
+++ b/instafeed/vendor/react/stream/examples/11-cat.php
@@ -0,0 +1,28 @@
+pipe($stdout);
+
+$loop->run();
diff --git a/instafeed/vendor/react/stream/examples/91-benchmark-throughput.php b/instafeed/vendor/react/stream/examples/91-benchmark-throughput.php
new file mode 100755
index 0000000..ecf695c
--- /dev/null
+++ b/instafeed/vendor/react/stream/examples/91-benchmark-throughput.php
@@ -0,0 +1,62 @@
+write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL);
+}
+$info->write('piping from ' . $if . ' to ' . $of . ' (for max ' . $t . ' second(s)) ...'. PHP_EOL);
+
+// setup input and output streams and pipe inbetween
+$fh = fopen($if, 'r');
+$in = new React\Stream\ReadableResourceStream($fh, $loop);
+$out = new React\Stream\WritableResourceStream(fopen($of, 'w'), $loop);
+$in->pipe($out);
+
+// stop input stream in $t seconds
+$start = microtime(true);
+$timeout = $loop->addTimer($t, function () use ($in, &$bytes) {
+ $in->close();
+});
+
+// print stream position once stream closes
+$in->on('close', function () use ($fh, $start, $loop, $timeout, $info) {
+ $t = microtime(true) - $start;
+ $loop->cancelTimer($timeout);
+
+ $bytes = ftell($fh);
+
+ $info->write('read ' . $bytes . ' byte(s) in ' . round($t, 3) . ' second(s) => ' . round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL);
+ $info->write('peak memory usage of ' . round(memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL);
+});
+
+$loop->run();
diff --git a/instafeed/vendor/react/stream/phpunit.xml.dist b/instafeed/vendor/react/stream/phpunit.xml.dist
new file mode 100755
index 0000000..13d3fab
--- /dev/null
+++ b/instafeed/vendor/react/stream/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/instafeed/vendor/react/stream/src/CompositeStream.php b/instafeed/vendor/react/stream/src/CompositeStream.php
new file mode 100755
index 0000000..153f2a3
--- /dev/null
+++ b/instafeed/vendor/react/stream/src/CompositeStream.php
@@ -0,0 +1,82 @@
+readable = $readable;
+ $this->writable = $writable;
+
+ if (!$readable->isReadable() || !$writable->isWritable()) {
+ return $this->close();
+ }
+
+ Util::forwardEvents($this->readable, $this, array('data', 'end', 'error'));
+ Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe'));
+
+ $this->readable->on('close', array($this, 'close'));
+ $this->writable->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->readable->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->readable->pause();
+ }
+
+ public function resume()
+ {
+ if (!$this->writable->isWritable()) {
+ return;
+ }
+
+ $this->readable->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isWritable()
+ {
+ return $this->writable->isWritable();
+ }
+
+ public function write($data)
+ {
+ return $this->writable->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->readable->pause();
+ $this->writable->end($data);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->readable->close();
+ $this->writable->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/instafeed/vendor/react/stream/src/DuplexResourceStream.php b/instafeed/vendor/react/stream/src/DuplexResourceStream.php
new file mode 100755
index 0000000..5f038c6
--- /dev/null
+++ b/instafeed/vendor/react/stream/src/DuplexResourceStream.php
@@ -0,0 +1,224 @@
+isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ if ($buffer === null) {
+ $buffer = new WritableResourceStream($stream, $loop);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+ $this->buffer = $buffer;
+
+ $that = $this;
+
+ $this->buffer->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+
+ $this->buffer->on('close', array($this, 'close'));
+
+ $this->buffer->on('drain', function () use ($that) {
+ $that->emit('drain');
+ });
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && $this->readable) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ return $this->buffer->write($data);
+ }
+
+ public function close()
+ {
+ if (!$this->writable && !$this->closing) {
+ return;
+ }
+
+ $this->closing = false;
+
+ $this->readable = false;
+ $this->writable = false;
+
+ $this->emit('close');
+ $this->pause();
+ $this->buffer->close();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ $this->closing = true;
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->pause();
+
+ $this->buffer->end($data);
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ /** @internal */
+ public function handleData($stream)
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/instafeed/vendor/react/stream/src/DuplexStreamInterface.php b/instafeed/vendor/react/stream/src/DuplexStreamInterface.php
new file mode 100755
index 0000000..631ce31
--- /dev/null
+++ b/instafeed/vendor/react/stream/src/DuplexStreamInterface.php
@@ -0,0 +1,39 @@
+ Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see ReadableStreamInterface
+ * @see WritableStreamInterface
+ */
+interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface
+{
+}
diff --git a/instafeed/vendor/react/stream/src/ReadableResourceStream.php b/instafeed/vendor/react/stream/src/ReadableResourceStream.php
new file mode 100755
index 0000000..461f6e4
--- /dev/null
+++ b/instafeed/vendor/react/stream/src/ReadableResourceStream.php
@@ -0,0 +1,177 @@
+isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && !$this->closed) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+
+ $this->emit('close');
+ $this->pause();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleData()
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($this->stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/instafeed/vendor/react/stream/src/ReadableStreamInterface.php b/instafeed/vendor/react/stream/src/ReadableStreamInterface.php
new file mode 100755
index 0000000..2b4c3d0
--- /dev/null
+++ b/instafeed/vendor/react/stream/src/ReadableStreamInterface.php
@@ -0,0 +1,362 @@
+on('data', function ($data) {
+ * echo $data;
+ * });
+ * ```
+ *
+ * This event MAY be emitted any number of times, which may be zero times if
+ * this stream does not send any data at all.
+ * It SHOULD not be emitted after an `end` or `close` event.
+ *
+ * The given `$data` argument may be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit the raw (binary) payload data that is received over the wire as
+ * chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * end event:
+ * The `end` event will be emitted once the source stream has successfully
+ * reached the end of the stream (EOF).
+ *
+ * ```php
+ * $stream->on('end', function () {
+ * echo 'END';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * a successful end was detected.
+ * It SHOULD NOT be emitted after a previous `end` or `close` event.
+ * It MUST NOT be emitted if the stream closes due to a non-successful
+ * end, such as after a previous `error` event.
+ *
+ * After the stream is ended, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * This event will only be emitted if the *end* was reached successfully,
+ * not if the stream was interrupted by an unrecoverable error or explicitly
+ * closed. Not all streams know this concept of a "successful end".
+ * Many use-cases involve detecting when the stream closes (terminates)
+ * instead, in this case you should use the `close` event.
+ * After the stream emits an `end` event, it SHOULD usually be followed by a
+ * `close` event.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit this event if either the remote side closes the connection or
+ * a file handle was successfully read until reaching its end (EOF).
+ *
+ * Note that this event should not be confused with the `end()` method.
+ * This event defines a successful end *reading* from a source stream, while
+ * the `end()` method defines *writing* a successful end to a destination
+ * stream.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to read from this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error or after an unexpected `data` or premature
+ * `end` event.
+ * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-readable mode, see
+ * also `close()` and `isReadable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and do not make assumption about data
+ * boundaries (such as unexpected `data` or premature `end` events).
+ * In other words, many lower-level protocols (such as TCP/IP) may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * Unlike the `end` event, this event SHOULD be emitted whenever the stream
+ * closes, irrespective of whether this happens implicitly due to an
+ * unrecoverable error or explicitly when either side closes the stream.
+ * If you only want to detect a *successful* end, you should use the `end`
+ * event instead.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after reading a *successful* `end`
+ * event or after a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ */
+interface ReadableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a readable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts incoming
+ * data events or if it is ended or closed already.
+ * Once the stream is non-readable, no further `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * A successfully opened stream always MUST start in readable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-readable mode.
+ * This can happen any time, explicitly through `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-readable mode, it MUST NOT transition
+ * back to readable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `isWritable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Pauses reading incoming data events.
+ *
+ * Removes the data source file descriptor from the event loop. This
+ * allows you to throttle incoming data.
+ *
+ * Unless otherwise noted, a successfully opened stream SHOULD NOT start
+ * in paused state.
+ *
+ * Once the stream is paused, no futher `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $stream->on('data', assertShouldNeverCalled());
+ * $stream->on('end', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * stream MAY continue emitting `data` events.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes reading incoming data events.
+ *
+ * Re-attach the data source after a previous `pause()`.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Pipes all the data from this readable source into the given writable destination.
+ *
+ * Automatically sends all incoming data to the destination.
+ * Automatically throttles the source based on what the destination can handle.
+ *
+ * ```php
+ * $source->pipe($dest);
+ * ```
+ *
+ * Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+ * into itself in order to write back all the data that is received.
+ * This may be a useful feature for a TCP/IP echo service:
+ *
+ * ```php
+ * $connection->pipe($connection);
+ * ```
+ *
+ * This method returns the destination stream as-is, which can be used to
+ * set up chains of piped streams:
+ *
+ * ```php
+ * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+ * ```
+ *
+ * By default, this will call `end()` on the destination stream once the
+ * source stream emits an `end` event. This can be disabled like this:
+ *
+ * ```php
+ * $source->pipe($dest, array('end' => false));
+ * ```
+ *
+ * Note that this only applies to the `end` event.
+ * If an `error` or explicit `close` event happens on the source stream,
+ * you'll have to manually close the destination stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $source->on('close', function () use ($dest) {
+ * $dest->end('BYE!');
+ * });
+ * ```
+ *
+ * If the source stream is not readable (closed state), then this is a NO-OP.
+ *
+ * ```php
+ * $source->close();
+ * $source->pipe($dest); // NO-OP
+ * ```
+ *
+ * If the destinantion stream is not writable (closed state), then this will simply
+ * throttle (pause) the source stream:
+ *
+ * ```php
+ * $dest->close();
+ * $source->pipe($dest); // calls $source->pause()
+ * ```
+ *
+ * Similarly, if the destination stream is closed while the pipe is still
+ * active, it will also throttle (pause) the source stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $dest->close(); // calls $source->pause()
+ * ```
+ *
+ * Once the pipe is set up successfully, the destination stream MUST emit
+ * a `pipe` event with this source stream an event argument.
+ *
+ * @param WritableStreamInterface $dest
+ * @param array $options
+ * @return WritableStreamInterface $dest stream as-is
+ */
+ public function pipe(WritableStreamInterface $dest, array $options = array());
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to (forcefully) close the stream.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-readable
+ * mode, see also `isReadable()`.
+ * This means that no further `data` or `end` events SHOULD be emitted.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this method should not be confused with the `end()` method.
+ *
+ * @return void
+ * @see WritableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/instafeed/vendor/react/stream/src/ThroughStream.php b/instafeed/vendor/react/stream/src/ThroughStream.php
new file mode 100755
index 0000000..6f73fb8
--- /dev/null
+++ b/instafeed/vendor/react/stream/src/ThroughStream.php
@@ -0,0 +1,190 @@
+on('data', $this->expectCallableOnceWith('hello'));
+ *
+ * $through->write('hello');
+ * ```
+ *
+ * Similarly, the [`end()` method](#end) will end the stream and emit an
+ * [`end` event](#end-event) and then [`close()`](#close-1) the stream.
+ * The [`close()` method](#close-1) will close the stream and emit a
+ * [`close` event](#close-event).
+ * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+ *
+ * ```php
+ * $through = new ThroughStream();
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Optionally, its constructor accepts any callable function which will then be
+ * used to *filter* any data written to it. This function receives a single data
+ * argument as passed to the writable side and must return the data as it will be
+ * passed to its readable end:
+ *
+ * ```php
+ * $through = new ThroughStream('strtoupper');
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Note that this class makes no assumptions about any data types. This can be
+ * used to convert data, for example for transforming any structured data into
+ * a newline-delimited JSON (NDJSON) stream like this:
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * return json_encode($data) . PHP_EOL;
+ * });
+ * $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+ *
+ * $through->write(array(2, true));
+ * ```
+ *
+ * The callback function is allowed to throw an `Exception`. In this case,
+ * the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * if (!is_string($data)) {
+ * throw new \UnexpectedValueException('Only strings allowed');
+ * }
+ * return $data;
+ * });
+ * $through->on('error', $this->expectCallableOnce()));
+ * $through->on('close', $this->expectCallableOnce()));
+ * $through->on('data', $this->expectCallableNever()));
+ *
+ * $through->write(2);
+ * ```
+ *
+ * @see WritableStreamInterface::write()
+ * @see WritableStreamInterface::end()
+ * @see DuplexStreamInterface::close()
+ * @see WritableStreamInterface::pipe()
+ */
+final class ThroughStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $readable = true;
+ private $writable = true;
+ private $closed = false;
+ private $paused = false;
+ private $drain = false;
+ private $callback;
+
+ public function __construct($callback = null)
+ {
+ if ($callback !== null && !\is_callable($callback)) {
+ throw new InvalidArgumentException('Invalid transformation callback given');
+ }
+
+ $this->callback = $callback;
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ if ($this->drain) {
+ $this->drain = false;
+ $this->emit('drain');
+ }
+ $this->paused = false;
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ if ($this->callback !== null) {
+ try {
+ $data = \call_user_func($this->callback, $data);
+ } catch (\Exception $e) {
+ $this->emit('error', array($e));
+ $this->close();
+
+ return false;
+ }
+ }
+
+ $this->emit('data', array($data));
+
+ if ($this->paused) {
+ $this->drain = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ if (null !== $data) {
+ $this->write($data);
+
+ // return if write() already caused the stream to close
+ if (!$this->writable) {
+ return;
+ }
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->paused = true;
+ $this->drain = false;
+
+ $this->emit('end');
+ $this->close();
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->closed = true;
+ $this->paused = true;
+ $this->drain = false;
+ $this->callback = null;
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/instafeed/vendor/react/stream/src/Util.php b/instafeed/vendor/react/stream/src/Util.php
new file mode 100755
index 0000000..056b037
--- /dev/null
+++ b/instafeed/vendor/react/stream/src/Util.php
@@ -0,0 +1,75 @@
+ NO-OP
+ if (!$source->isReadable()) {
+ return $dest;
+ }
+
+ // destination not writable => just pause() source
+ if (!$dest->isWritable()) {
+ $source->pause();
+
+ return $dest;
+ }
+
+ $dest->emit('pipe', array($source));
+
+ // forward all source data events as $dest->write()
+ $source->on('data', $dataer = function ($data) use ($source, $dest) {
+ $feedMore = $dest->write($data);
+
+ if (false === $feedMore) {
+ $source->pause();
+ }
+ });
+ $dest->on('close', function () use ($source, $dataer) {
+ $source->removeListener('data', $dataer);
+ $source->pause();
+ });
+
+ // forward destination drain as $source->resume()
+ $dest->on('drain', $drainer = function () use ($source) {
+ $source->resume();
+ });
+ $source->on('close', function () use ($dest, $drainer) {
+ $dest->removeListener('drain', $drainer);
+ });
+
+ // forward end event from source as $dest->end()
+ $end = isset($options['end']) ? $options['end'] : true;
+ if ($end) {
+ $source->on('end', $ender = function () use ($dest) {
+ $dest->end();
+ });
+ $dest->on('close', function () use ($source, $ender) {
+ $source->removeListener('end', $ender);
+ });
+ }
+
+ return $dest;
+ }
+
+ public static function forwardEvents($source, $target, array $events)
+ {
+ foreach ($events as $event) {
+ $source->on($event, function () use ($event, $target) {
+ $target->emit($event, \func_get_args());
+ });
+ }
+ }
+}
diff --git a/instafeed/vendor/react/stream/src/WritableResourceStream.php b/instafeed/vendor/react/stream/src/WritableResourceStream.php
new file mode 100755
index 0000000..57c09b2
--- /dev/null
+++ b/instafeed/vendor/react/stream/src/WritableResourceStream.php
@@ -0,0 +1,171 @@
+stream = $stream;
+ $this->loop = $loop;
+ $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit;
+ $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ $this->data .= $data;
+
+ if (!$this->listening && $this->data !== '') {
+ $this->listening = true;
+
+ $this->loop->addWriteStream($this->stream, array($this, 'handleWrite'));
+ }
+
+ return !isset($this->data[$this->softLimit - 1]);
+ }
+
+ public function end($data = null)
+ {
+ if (null !== $data) {
+ $this->write($data);
+ }
+
+ $this->writable = false;
+
+ // close immediately if buffer is already empty
+ // otherwise wait for buffer to flush first
+ if ($this->data === '') {
+ $this->close();
+ }
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ if ($this->listening) {
+ $this->listening = false;
+ $this->loop->removeWriteStream($this->stream);
+ }
+
+ $this->closed = true;
+ $this->writable = false;
+ $this->data = '';
+
+ $this->emit('close');
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleWrite()
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = array(
+ 'message' => $errstr,
+ 'number' => $errno,
+ 'file' => $errfile,
+ 'line' => $errline
+ );
+ });
+
+ if ($this->writeChunkSize === -1) {
+ $sent = \fwrite($this->stream, $this->data);
+ } else {
+ $sent = \fwrite($this->stream, $this->data, $this->writeChunkSize);
+ }
+
+ \restore_error_handler();
+
+ // Only report errors if *nothing* could be sent.
+ // Any hard (permanent) error will fail to send any data at all.
+ // Sending excessive amounts of data will only flush *some* data and then
+ // report a temporary error (EAGAIN) which we do not raise here in order
+ // to keep the stream open for further tries to write.
+ // Should this turn out to be a permanent error later, it will eventually
+ // send *nothing* and we can detect this.
+ if ($sent === 0 || $sent === false) {
+ if ($error !== null) {
+ $error = new \ErrorException(
+ $error['message'],
+ 0,
+ $error['number'],
+ $error['file'],
+ $error['line']
+ );
+ }
+
+ $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . ($error !== null ? $error->getMessage() : 'Unknown error'), 0, $error)));
+ $this->close();
+
+ return;
+ }
+
+ $exceeded = isset($this->data[$this->softLimit - 1]);
+ $this->data = (string) \substr($this->data, $sent);
+
+ // buffer has been above limit and is now below limit
+ if ($exceeded && !isset($this->data[$this->softLimit - 1])) {
+ $this->emit('drain');
+ }
+
+ // buffer is now completely empty => stop trying to write
+ if ($this->data === '') {
+ // stop waiting for resource to be writable
+ if ($this->listening) {
+ $this->loop->removeWriteStream($this->stream);
+ $this->listening = false;
+ }
+
+ // buffer is end()ing and now completely empty => close buffer
+ if (!$this->writable) {
+ $this->close();
+ }
+ }
+ }
+}
diff --git a/instafeed/vendor/react/stream/src/WritableStreamInterface.php b/instafeed/vendor/react/stream/src/WritableStreamInterface.php
new file mode 100755
index 0000000..3bc932e
--- /dev/null
+++ b/instafeed/vendor/react/stream/src/WritableStreamInterface.php
@@ -0,0 +1,347 @@
+on('drain', function () use ($stream) {
+ * echo 'Stream is now ready to accept more data';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once every time the buffer became full
+ * previously and is now ready to accept more data.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if the buffer never became full in the first place.
+ * This event SHOULD NOT be emitted if the buffer has not become full
+ * previously.
+ *
+ * This event is mostly used internally, see also `write()` for more details.
+ *
+ * pipe event:
+ * The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+ * into this stream.
+ * The event receives a single `ReadableStreamInterface` argument for the
+ * source stream.
+ *
+ * ```php
+ * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ * echo 'Now receiving piped data';
+ *
+ * // explicitly close target if source emits an error
+ * $source->on('error', function () use ($stream) {
+ * $stream->close();
+ * });
+ * });
+ *
+ * $source->pipe($stream);
+ * ```
+ *
+ * This event MUST be emitted once for each readable stream that is
+ * successfully piped into this destination stream.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if no stream is ever piped into this stream.
+ * This event MUST NOT be emitted if either the source is not readable
+ * (closed already) or this destination is not writable (closed already).
+ *
+ * This event is mostly used internally, see also `pipe()` for more details.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to write to this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error.
+ * It SHOULD NOT be emitted after a previous `error` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-writable mode, see
+ * also `close()` and `isWritable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-writable mode,
+ * see also `isWritable()`.
+ *
+ * This event SHOULD be emitted whenever the stream closes, irrespective of
+ * whether this happens implicitly due to an unrecoverable error or
+ * explicitly when either side closes the stream.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after flushing the buffer from
+ * the `end()` method, after receiving a *successful* `end` event or after
+ * a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ * @see DuplexStreamInterface
+ */
+interface WritableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a writable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts writing
+ * any data or if it is ended or closed already.
+ * Writing any data to a non-writable stream is a NO-OP:
+ *
+ * ```php
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('end'); // NO-OP
+ * $stream->end('end'); // NO-OP
+ * ```
+ *
+ * A successfully opened stream always MUST start in writable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-writable mode.
+ * This can happen any time, explicitly through `end()` or `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-writable mode, it MUST NOT transition
+ * back to writable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `isReadable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Write some data into the stream.
+ *
+ * A successful write MUST be confirmed with a boolean `true`, which means
+ * that either the data was written (flushed) immediately or is buffered and
+ * scheduled for a future write. Note that this interface gives you no
+ * control over explicitly flushing the buffered data, as finding the
+ * appropriate time for this is beyond the scope of this interface and left
+ * up to the implementation of this interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+ * an `error` event and MAY `close()` the stream if it can not recover from
+ * this error.
+ *
+ * If the internal buffer is full after adding `$data`, then `write()`
+ * SHOULD return `false`, indicating that the caller should stop sending
+ * data until the buffer drains.
+ * The stream SHOULD send a `drain` event once the buffer is ready to accept
+ * more data.
+ *
+ * Similarly, if the the stream is not writable (already in a closed state)
+ * it MUST NOT process the given `$data` and SHOULD return `false`,
+ * indicating that the caller should stop sending data.
+ *
+ * The given `$data` argument MAY be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will only accept the raw (binary) payload data that is transferred over
+ * the wire as chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * @param mixed|string $data
+ * @return bool
+ */
+ public function write($data);
+
+ /**
+ * Successfully ends the stream (after optionally sending some final data).
+ *
+ * This method can be used to successfully end the stream, i.e. close
+ * the stream after sending out all data that is currently buffered.
+ *
+ * ```php
+ * $stream->write('hello');
+ * $stream->write('world');
+ * $stream->end();
+ * ```
+ *
+ * If there's no data currently buffered and nothing to be flushed, then
+ * this method MAY `close()` the stream immediately.
+ *
+ * If there's still data in the buffer that needs to be flushed first, then
+ * this method SHOULD try to write out this data and only then `close()`
+ * the stream.
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ *
+ * Note that this interface gives you no control over explicitly flushing
+ * the buffered data, as finding the appropriate time for this is beyond the
+ * scope of this interface and left up to the implementation of this
+ * interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * You can optionally pass some final data that is written to the stream
+ * before ending the stream. If a non-`null` value is given as `$data`, then
+ * this method will behave just like calling `write($data)` before ending
+ * with no data.
+ *
+ * ```php
+ * // shorter version
+ * $stream->end('bye');
+ *
+ * // same as longer version
+ * $stream->write('bye');
+ * $stream->end();
+ * ```
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->end();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+ * also end its readable side, unless the stream supports half-open mode.
+ * In other words, after calling this method, these streams SHOULD switch
+ * into non-writable AND non-readable mode, see also `isReadable()`.
+ * This implies that in this case, the stream SHOULD NOT emit any `data`
+ * or `end` events anymore.
+ * Streams MAY choose to use the `pause()` method logic for this, but
+ * special care may have to be taken to ensure a following call to the
+ * `resume()` method SHOULD NOT continue emitting readable events.
+ *
+ * Note that this method should not be confused with the `close()` method.
+ *
+ * @param mixed|string|null $data
+ * @return void
+ */
+ public function end($data = null);
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to forcefully close the stream, i.e. close
+ * the stream without waiting for any buffered data to be flushed.
+ * If there's still data in the buffer, this data SHOULD be discarded.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * Note that this method should not be confused with the `end()` method.
+ * Unlike the `end()` method, this method does not take care of any existing
+ * buffers and simply discards any buffer contents.
+ * Likewise, this method may also be called after calling `end()` on a
+ * stream in order to stop waiting for the stream to flush its final data.
+ *
+ * ```php
+ * $stream->end();
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->close();
+ * });
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ *
+ * @return void
+ * @see ReadableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/instafeed/vendor/react/stream/tests/CallableStub.php b/instafeed/vendor/react/stream/tests/CallableStub.php
new file mode 100755
index 0000000..31cc834
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/CallableStub.php
@@ -0,0 +1,10 @@
+getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(false);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldCloseWritableIfNotReadable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardWritableCallsToWritableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->write('foo');
+ $composite->isWritable();
+ }
+
+ /** @test */
+ public function itShouldForwardReadableCallsToReadableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->exactly(2))
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+ $readable
+ ->expects($this->once())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->isReadable();
+ $composite->pause();
+ $composite->resume();
+ }
+
+ /** @test */
+ public function itShouldNotForwardResumeIfStreamIsNotWritable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->never())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturnOnConsecutiveCalls(true, false);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->resume();
+ }
+
+ /** @test */
+ public function endShouldDelegateToWritableWithData()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->end('foo');
+ }
+
+ /** @test */
+ public function closeShouldCloseBothStreams()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseOnlyOnce()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $readable->close();
+ $writable->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseAndRemoveAllListeners()
+ {
+ $in = new ThroughStream();
+
+ $composite = new CompositeStream($in, $in);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($composite->isReadable());
+ $this->assertTrue($composite->isWritable());
+ $this->assertCount(1, $composite->listeners('close'));
+
+ $composite->close();
+
+ $this->assertFalse($composite->isReadable());
+ $this->assertFalse($composite->isWritable());
+ $this->assertCount(0, $composite->listeners('close'));
+ }
+
+ /** @test */
+ public function itShouldReceiveForwardedEvents()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('data', $this->expectCallableOnce());
+ $composite->on('drain', $this->expectCallableOnce());
+
+ $readable->emit('data', array('foo'));
+ $writable->emit('drain');
+ }
+
+ /** @test */
+ public function itShouldHandlePipingCorrectly()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $input = new ThroughStream();
+ $input->pipe($composite);
+ $input->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function itShouldForwardPipeCallsToReadableStream()
+ {
+ $readable = new ThroughStream();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite->pipe($output);
+ $readable->emit('data', array('foo'));
+ }
+}
diff --git a/instafeed/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php b/instafeed/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
new file mode 100755
index 0000000..7135e15
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
@@ -0,0 +1,390 @@
+markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $bufferSize = 4096;
+ $streamA = new DuplexResourceStream($sockA, $loop, $bufferSize);
+ $streamB = new DuplexResourceStream($sockB, $loop, $bufferSize);
+
+ $testString = str_repeat("*", $bufferSize + 1);
+
+ $buffer = "";
+ $streamB->on('data', function ($data) use (&$buffer) {
+ $buffer .= $data;
+ });
+
+ $streamA->write($testString);
+
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($testString, $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testWriteLargeChunk($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // limit seems to be 192 KiB
+ $size = 256 * 1024;
+
+ // sending side sends and expects clean close with no errors
+ $streamA->end(str_repeat('*', $size));
+ $streamA->on('close', $this->expectCallableOnce());
+ $streamA->on('error', $this->expectCallableNever());
+
+ // receiving side counts bytes and expects clean close with no errors
+ $received = 0;
+ $streamB->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+ $streamB->on('close', $this->expectCallableOnce());
+ $streamB->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($size, $received);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->end();
+
+ // streamB should not emit any data
+ $streamB->on('data', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($client, $loop);
+ $streamB = new DuplexResourceStream($peer, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($peer, $loop);
+ $streamB = new DuplexResourceStream($client, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo test', 'r'), $loop);
+ $stream->on('data', $this->expectCallableOnceWith("test\n"));
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals("a\n" . "b\n" . "c\n", $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsLongChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop);
+
+ $bytes = 0;
+ $stream->on('data', function ($chunk) use (&$bytes) {
+ $bytes += strlen($chunk);
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals(12345 * 1234, $bytes);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('true', 'r'), $loop);
+ $stream->on('data', $this->expectCallableNever());
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ * @dataProvider loopProvider
+ */
+ public function testEmptyReadShouldntFcloseStream($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $stream = stream_socket_accept($server);
+
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return '';
+ }, STREAM_FILTER_READ);
+
+ $loop = $loopFactory();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('end', $this->expectCallableNever());
+
+ fwrite($client, "foobar\n");
+
+ $conn->handleData($stream);
+
+ fclose($stream);
+ fclose($client);
+ fclose($server);
+ }
+
+ private function loopTick(LoopInterface $loop)
+ {
+ $loop->addTimer(0, function () use ($loop) {
+ $loop->stop();
+ });
+ $loop->run();
+ }
+}
diff --git a/instafeed/vendor/react/stream/tests/DuplexResourceStreamTest.php b/instafeed/vendor/react/stream/tests/DuplexResourceStreamTest.php
new file mode 100755
index 0000000..3212ae8
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/DuplexResourceStreamTest.php
@@ -0,0 +1,495 @@
+createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new DuplexResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream('breakme', $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException RunTimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorAcceptsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ }
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+ $conn->on('end', $this->expectCallableNever());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testEndShouldEndBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->once())->method('end')->with('foo');
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ $conn->end('foo');
+ }
+
+
+ public function testEndAfterCloseIsNoOp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->never())->method('end');
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->end();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::write
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+
+ rewind($stream);
+ $this->assertSame("foo\n", fgets($stream));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ * @covers React\Stream\DuplexResourceStream::isReadable
+ * @covers React\Stream\DuplexResourceStream::isWritable
+ */
+ public function testEnd()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end();
+
+ $this->assertFalse(is_resource($stream));
+ $this->assertFalse($conn->isReadable());
+ $this->assertFalse($conn->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ */
+ public function testEndRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end('bye');
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ public function testEndedStreamsShouldNotWrite()
+ {
+ $file = tempnam(sys_get_temp_dir(), 'reactphptest_');
+ $stream = fopen($file, 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+ $conn->end();
+
+ $res = $conn->write("bar\n");
+ $stream = fopen($file, 'r');
+
+ $this->assertSame("foo\n", fgets($stream));
+ $this->assertFalse($res);
+
+ unlink($file);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ public function testBufferEventsShouldBubbleUp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+
+ $conn->on('drain', $this->expectCallableOnce());
+ $conn->on('error', $this->expectCallableOnce());
+
+ $buffer->emit('drain');
+ $buffer->emit('error', array(new \RuntimeException('Whoops')));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop
+ ->expects($this->once())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) {
+ call_user_func($listener, $stream);
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/stream/tests/EnforceBlockingWrapper.php b/instafeed/vendor/react/stream/tests/EnforceBlockingWrapper.php
new file mode 100755
index 0000000..39c0487
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/EnforceBlockingWrapper.php
@@ -0,0 +1,35 @@
+on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockPlain()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tcp://httpbin.org:80');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadKilobyteSecure()
+ {
+ $size = 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockSecureRequiresSmallerChunkSize()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream(
+ $stream,
+ $loop,
+ null,
+ new WritableResourceStream($stream, $loop, null, 8192)
+ );
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0)
+ {
+ $stream->on('close', function () use ($loop) {
+ $loop->stop();
+ });
+
+ $that = $this;
+ $loop->addTimer($timeout, function () use ($loop, $that) {
+ $loop->stop();
+ $that->fail('Timed out while waiting for stream to close');
+ });
+
+ $loop->run();
+ }
+}
diff --git a/instafeed/vendor/react/stream/tests/ReadableResourceStreamTest.php b/instafeed/vendor/react/stream/tests/ReadableResourceStreamTest.php
new file mode 100755
index 0000000..7566f92
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/ReadableResourceStreamTest.php
@@ -0,0 +1,391 @@
+createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new ReadableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(false, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testCloseTwiceShouldEmitCloseEventOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testEmptyReadShouldntFcloseStream()
+ {
+ list($stream, $_) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('end', $this->expectCallableNever());
+
+ $conn->handleData();
+
+ fclose($stream);
+ fclose($_);
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/stream/tests/Stub/ReadableStreamStub.php b/instafeed/vendor/react/stream/tests/Stub/ReadableStreamStub.php
new file mode 100755
index 0000000..6984f24
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/Stub/ReadableStreamStub.php
@@ -0,0 +1,61 @@
+emit('data', array($data));
+ }
+
+ // trigger error event
+ public function error($error)
+ {
+ $this->emit('error', array($error));
+ }
+
+ // trigger end event
+ public function end()
+ {
+ $this->emit('end', array());
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ $this->paused = false;
+ }
+
+ public function close()
+ {
+ $this->readable = false;
+
+ $this->emit('close');
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+}
diff --git a/instafeed/vendor/react/stream/tests/TestCase.php b/instafeed/vendor/react/stream/tests/TestCase.php
new file mode 100755
index 0000000..c8fc1db
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/TestCase.php
@@ -0,0 +1,54 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $callback = $this->createCallableMock();
+ $callback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $callback;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock();
+ }
+}
diff --git a/instafeed/vendor/react/stream/tests/ThroughStreamTest.php b/instafeed/vendor/react/stream/tests/ThroughStreamTest.php
new file mode 100755
index 0000000..a98badf
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/ThroughStreamTest.php
@@ -0,0 +1,267 @@
+write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToIt()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruFunction()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruCallback()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsException()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->write('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $ret = $through->write('foo');
+
+ $this->assertFalse($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->write('foo');
+
+ $through->on('drain', $this->expectCallableOnce());
+ $through->resume();
+ }
+
+ /** @test */
+ public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause()
+ {
+ $through = new ThroughStream();
+ $through->on('drain', $this->expectCallableNever());
+ $through->pause();
+ $through->resume();
+ $ret = $through->write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function pipingStuffIntoItShouldWork()
+ {
+ $readable = new ThroughStream();
+
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+
+ $readable->pipe($through);
+ $readable->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function endShouldEmitEndAndClose()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->end();
+ }
+
+ /** @test */
+ public function endShouldCloseTheStream()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endShouldWriteDataBeforeClosing()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endTwiceShouldOnlyEmitOnce()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnce('first'));
+ $through->end('first');
+ $through->end('ignored');
+ }
+
+ /** @test */
+ public function writeAfterEndShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataWillCloseStreamShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', array($through, 'close'));
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToPausedShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToResumedShouldReturnTrue()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->resume();
+
+ $this->assertTrue($through->write('foo'));
+ }
+
+ /** @test */
+ public function itShouldBeReadableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isReadable());
+ }
+
+ /** @test */
+ public function itShouldBeWritableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isWritable());
+ }
+
+ /** @test */
+ public function closeShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function doubleCloseShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function pipeShouldPipeCorrectly()
+ {
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $through = new ThroughStream();
+ $through->pipe($output);
+ $through->write('foo');
+ }
+}
diff --git a/instafeed/vendor/react/stream/tests/UtilTest.php b/instafeed/vendor/react/stream/tests/UtilTest.php
new file mode 100755
index 0000000..3d113ab
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/UtilTest.php
@@ -0,0 +1,273 @@
+getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $ret = Util::pipe($readable, $writable);
+
+ $this->assertSame($writable, $ret);
+ }
+
+ public function testPipeNonReadableSourceShouldDoNothing()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->never())
+ ->method('isWritable');
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeIntoNonWritableDestinationShouldPauseSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(false);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeClosingDestPausesSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = new ThroughStream();
+
+ Util::pipe($readable, $writable);
+
+ $writable->close();
+ }
+
+ public function testPipeWithEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+
+ $readable->end();
+ }
+
+ public function testPipeWithoutEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable, array('end' => false));
+
+ $readable->end();
+ }
+
+ public function testPipeWithTooSlowWritableShouldPauseReadable()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('some data')
+ ->will($this->returnValue(false));
+
+ $readable->pipe($writable);
+
+ $this->assertFalse($readable->paused);
+ $readable->write('some data');
+ $this->assertTrue($readable->paused);
+ }
+
+ public function testPipeWithTooSlowWritableShouldResumeOnDrain()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $onDrain = null;
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->any())
+ ->method('on')
+ ->will($this->returnCallback(function ($name, $callback) use (&$onDrain) {
+ if ($name === 'drain') {
+ $onDrain = $callback;
+ }
+ }));
+
+ $readable->pipe($writable);
+ $readable->pause();
+
+ $this->assertTrue($readable->paused);
+ $this->assertNotNull($onDrain);
+ $onDrain();
+ $this->assertFalse($readable->paused);
+ }
+
+ public function testPipeWithWritableResourceStream()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $readable->pipe($buffer);
+
+ $readable->write('hello, I am some ');
+ $readable->write('random data');
+
+ $buffer->handleWrite();
+ rewind($stream);
+ $this->assertSame('hello, I am some random data', stream_get_contents($stream));
+ }
+
+ public function testPipeSetsUpListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+
+ Util::pipe($source, $dest);
+
+ $this->assertCount(1, $source->listeners('data'));
+ $this->assertCount(1, $source->listeners('end'));
+ $this->assertCount(1, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingSourceRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $source->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingDestRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $dest->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeDuplexIntoSelfEndsOnEnd()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable->expects($this->any())->method('isReadable')->willReturn(true);
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(true);
+ $duplex = new CompositeStream($readable, $writable);
+
+ Util::pipe($duplex, $duplex);
+
+ $writable->expects($this->once())->method('end');
+
+ $duplex->emit('end');
+ }
+
+ /** @test */
+ public function forwardEventsShouldSetupForwards()
+ {
+ $source = new ThroughStream();
+ $target = new ThroughStream();
+
+ Util::forwardEvents($source, $target, array('data'));
+ $target->on('data', $this->expectCallableOnce());
+ $target->on('foo', $this->expectCallableNever());
+
+ $source->emit('data', array('hello'));
+ $source->emit('foo', array('bar'));
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+
+ private function notEqualTo($value)
+ {
+ return new \PHPUnit_Framework_Constraint_Not($value);
+ }
+}
diff --git a/instafeed/vendor/react/stream/tests/WritableStreamResourceTest.php b/instafeed/vendor/react/stream/tests/WritableStreamResourceTest.php
new file mode 100755
index 0000000..05bce9c
--- /dev/null
+++ b/instafeed/vendor/react/stream/tests/WritableStreamResourceTest.php
@@ -0,0 +1,534 @@
+createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'w+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsIfNotAValidStreamResource()
+ {
+ $stream = null;
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStream()
+ {
+ $stream = fopen('php://temp', 'r');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'reANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->write("foobar\n");
+ rewind($stream);
+ $this->assertSame("foobar\n", fread($stream, 1024));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteWithDataDoesAddResourceToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream));
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("foobar\n");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmptyWriteDoesNotAddToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->never())->method('addWriteStream');
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("");
+ $buffer->write(null);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+ $loop->preventWrites = true;
+
+ $buffer = new WritableResourceStream($stream, $loop, 4);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $this->assertTrue($buffer->write("foo"));
+ $loop->preventWrites = false;
+ $this->assertFalse($buffer->write("bar\n"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 3);
+
+ $this->assertFalse($buffer->write("foo"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteDetectsWhenOtherSideIsClosed()
+ {
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($a, $loop, 4);
+ $buffer->on('error', $this->expectCallableOnce());
+
+ fclose($b);
+
+ $buffer->write("foo");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmitsDrainAfterWriteWhichExceedsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteInDrain()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->once('drain', function () use ($buffer) {
+ $buffer->write("bar\n");
+ $buffer->handleWrite();
+ });
+
+ $this->assertFalse($buffer->write("foo\n"));
+ $buffer->handleWrite();
+
+ fseek($stream, 0);
+ $this->assertSame("foo\nbar\n", stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', function () use ($buffer) {
+ $buffer->close();
+ });
+
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ $buffer->handleWrite();
+ $this->assertSame('final words', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ rewind($stream);
+ $this->assertSame('', stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::isWritable
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->close();
+ $this->assertFalse($buffer->isWritable());
+
+ $this->assertEquals(array(), $buffer->listeners('close'));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingAfterWriteRemovesStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer->write('foo');
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->never())->method('removeWriteStream');
+
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testDoubleCloseWillEmitOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->close();
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $buffer->close();
+
+ $buffer->write('foo');
+
+ $buffer->handleWrite();
+ $this->assertSame('', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testErrorWhenStreamResourceIsInvalid()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', function ($message) use (&$error) {
+ $error = $message;
+ });
+
+ // invalidate stream resource
+ fclose($stream);
+
+ $buffer->write('Attempting to write to bad stream');
+
+ $this->assertInstanceOf('Exception', $error);
+
+ // the error messages differ between PHP versions, let's just check substrings
+ $this->assertContains('Unable to write to stream: ', $error->getMessage());
+ $this->assertContains(' not a valid stream resource', $error->getMessage(), '', true);
+ }
+
+ public function testWritingToClosedStream()
+ {
+ if ('Darwin' === PHP_OS) {
+ $this->markTestSkipped('OS X issue with shutting down pair for writing');
+ }
+
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+ $loop = $this->createLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($a, $loop);
+ $buffer->on('error', function($message) use (&$error) {
+ $error = $message;
+ });
+
+ $buffer->write('foo');
+ $buffer->handleWrite();
+ stream_socket_shutdown($b, STREAM_SHUT_RD);
+ stream_socket_shutdown($a, STREAM_SHUT_RD);
+ $buffer->write('bar');
+ $buffer->handleWrite();
+
+ $this->assertInstanceOf('Exception', $error);
+ $this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage());
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop->preventWrites = false;
+ $loop
+ ->expects($this->any())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) use ($loop) {
+ if (!$loop->preventWrites) {
+ call_user_func($listener, $stream);
+ }
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/.gitignore b/instafeed/vendor/ringcentral/psr7/.gitignore
new file mode 100755
index 0000000..83ec41e
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/.gitignore
@@ -0,0 +1,11 @@
+phpunit.xml
+composer.phar
+composer.lock
+composer-test.lock
+vendor/
+build/artifacts/
+artifacts/
+docs/_build
+docs/*.pyc
+.idea
+.DS_STORE
diff --git a/instafeed/vendor/ringcentral/psr7/.travis.yml b/instafeed/vendor/ringcentral/psr7/.travis.yml
new file mode 100755
index 0000000..08f3721
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/.travis.yml
@@ -0,0 +1,21 @@
+language: php
+
+sudo: false
+
+install:
+ - travis_retry composer install --no-interaction --prefer-source
+
+script: make test
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - php: 5.4
+ - php: 5.5
+ - php: 5.6
+ - php: 7.0
+ - php: hhvm
+ allow_failures:
+ - php: hhvm
+ fast_finish: true
diff --git a/instafeed/vendor/ringcentral/psr7/CHANGELOG.md b/instafeed/vendor/ringcentral/psr7/CHANGELOG.md
new file mode 100755
index 0000000..642dc9a
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/CHANGELOG.md
@@ -0,0 +1,28 @@
+# CHANGELOG
+
+## 1.2.0 - 2015-08-15
+
+* Body as `"0"` is now properly added to a response.
+* Now allowing forward seeking in CachingStream.
+* Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+* functions.php is now conditionally required.
+* user-info is no longer dropped when resolving URIs.
+
+## 1.1.0 - 2015-06-24
+
+* URIs can now be relative.
+* `multipart/form-data` headers are now overridden case-insensitively.
+* URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+* A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
diff --git a/instafeed/vendor/ringcentral/psr7/Dockerfile b/instafeed/vendor/ringcentral/psr7/Dockerfile
new file mode 100755
index 0000000..846e8cf
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/Dockerfile
@@ -0,0 +1,5 @@
+FROM greensheep/dockerfiles-php-5.3
+RUN apt-get update -y
+RUN apt-get install -y curl
+RUN curl -sS https://getcomposer.org/installer | php
+RUN mv composer.phar /usr/local/bin/composer
\ No newline at end of file
diff --git a/instafeed/vendor/ringcentral/psr7/LICENSE b/instafeed/vendor/ringcentral/psr7/LICENSE
new file mode 100755
index 0000000..581d95f
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/ringcentral/psr7/Makefile b/instafeed/vendor/ringcentral/psr7/Makefile
new file mode 100755
index 0000000..73a5c5b
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/Makefile
@@ -0,0 +1,21 @@
+all: clean test
+
+test:
+ vendor/bin/phpunit $(TEST)
+
+coverage:
+ vendor/bin/phpunit --coverage-html=artifacts/coverage $(TEST)
+
+view-coverage:
+ open artifacts/coverage/index.html
+
+clean:
+ rm -rf artifacts/*
+
+.PHONY: docker-login
+docker-login:
+ docker run -t -i -v $(shell pwd):/opt/psr7 ringcentral-psr7 /bin/bash
+
+.PHONY: docker-build
+docker-build:
+ docker build -t ringcentral-psr7 .
\ No newline at end of file
diff --git a/instafeed/vendor/ringcentral/psr7/README.md b/instafeed/vendor/ringcentral/psr7/README.md
new file mode 100755
index 0000000..b4a6061
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/README.md
@@ -0,0 +1,587 @@
+# PSR-7 Message Implementation
+
+This repository contains a partial [PSR-7](http://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing. Currently missing
+ServerRequestInterface and UploadedFileInterface; a pull request for these features is welcome.
+
+
+# Stream implementation
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## AppendStream
+
+`RingCentral\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use RingCentral\Psr7;
+
+$a = Psr7\stream_for('abc, ');
+$b = Psr7\stream_for('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\stream_for(' Above all listen to me').
+
+echo $composed(); // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`RingCentral\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use RingCentral\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`RingCentral\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use RingCentral\Psr7;
+
+// Create an empty stream
+$stream = Psr7\stream_for();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$stream->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`RingCentral\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing to
+to create a concrete class for a simple extension point.
+
+```php
+
+use RingCentral\Psr7;
+
+$stream = Psr7\stream_for('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`RingCentral\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
+
+This stream decorator skips the first 10 bytes of the given stream to remove
+the gzip header, converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`RingCentral\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use RingCentral\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`RingCentral\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`RingCentral\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`RingCentral\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`RingCentral\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`RingCentral\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use RingCentral\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ call_user_func($this->callback);
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `RingCentral\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `RingCentral\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use RingCentral\Psr7\StreamWrapper;
+
+$stream = RingCentral\Psr7\stream_for('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Function API
+
+There are various functions available under the `RingCentral\Psr7` namespace.
+
+
+## `function str`
+
+`function str(MessageInterface $message)`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new RingCentral\Psr7\Request('GET', 'http://example.com');
+echo RingCentral\Psr7\str($request);
+```
+
+
+## `function uri_for`
+
+`function uri_for($uri)`
+
+This function accepts a string or `Psr\Http\Message\UriInterface` and returns a
+UriInterface for the given value. If the value is already a `UriInterface`, it
+is returned as-is.
+
+```php
+$uri = RingCentral\Psr7\uri_for('http://example.com');
+assert($uri === RingCentral\Psr7\uri_for($uri));
+```
+
+
+## `function stream_for`
+
+`function stream_for($resource = '', array $options = [])`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+* - metadata: Array of custom metadata.
+* - size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = RingCentral\Psr7\stream_for('foo');
+$stream = RingCentral\Psr7\stream_for(fopen('/path/to/file', 'r'));
+
+$generator function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = RingCentral\Psr7\stream_for($generator(100));
+```
+
+
+## `function parse_header`
+
+`function parse_header($header)`
+
+Parse an array of header values containing ";" separated data into an array of
+associative arrays representing the header key value pair data of the header.
+When a parameter does not contain a value, but just contains a key, this
+function will inject a key with a '' string value.
+
+
+## `function normalize_header`
+
+`function normalize_header($header)`
+
+Converts an array of header values that may contain comma separated headers
+into an array of headers with no comma separated values.
+
+
+## `function modify_request`
+
+`function modify_request(RequestInterface $request, array $changes)`
+
+Clone and modify a request with the given changes. This method is useful for
+reducing the number of clones needed to mutate a message.
+
+The changes can be one of:
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `function rewind_body`
+
+`function rewind_body(MessageInterface $message)`
+
+Attempts to rewind a message body and throws an exception on failure. The body
+of the message will only be rewound if a call to `tell()` returns a value other
+than `0`.
+
+
+## `function try_fopen`
+
+`function try_fopen($filename, $mode)`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an error
+handler that checks for errors and throws an exception instead.
+
+
+## `function copy_to_string`
+
+`function copy_to_string(StreamInterface $stream, $maxLen = -1)`
+
+Copy the contents of a stream into a string until the given number of bytes
+have been read.
+
+
+## `function copy_to_stream`
+
+`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)`
+
+Copy the contents of a stream into another stream until the given number of
+bytes have been read.
+
+
+## `function hash`
+
+`function hash(StreamInterface $stream, $algo, $rawOutput = false)`
+
+Calculate a hash of a Stream. This method reads the entire stream to calculate
+a rolling hash (based on PHP's hash_init functions).
+
+
+## `function readline`
+
+`function readline(StreamInterface $stream, $maxLength = null)`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `function parse_request`
+
+`function parse_request($message)`
+
+Parses a request message string into a request object.
+
+
+## `function parse_server_request`
+
+`function parse_server_request($message, array $serverParams = array())`
+
+Parses a request message string into a server-side request object.
+
+
+## `function parse_response`
+
+`function parse_response($message)`
+
+Parses a response message string into a response object.
+
+
+## `function parse_query`
+
+`function parse_query($str, $urlEncoding = true)`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key value pair
+will become an array. This function does not parse nested PHP style arrays into
+an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
+`['foo[a]' => '1', 'foo[b]' => '2']`).
+
+
+## `function build_query`
+
+`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of parseQuery() to build a query string.
+This function does not modify the provided keys when an array is encountered
+(like http_build_query would).
+
+
+## `function mimetype_from_filename`
+
+`function mimetype_from_filename($filename)`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `function mimetype_from_extension`
+
+`function mimetype_from_extension($extension)`
+
+Maps a file extensions to a mimetype.
+
+
+# Static URI methods
+
+The `RingCentral\Psr7\Uri` class has several static methods to manipulate URIs.
+
+
+## `RingCentral\Psr7\Uri::removeDotSegments`
+
+`public static function removeDotSegments($path) -> UriInterface`
+
+Removes dot segments from a path and returns the new path.
+
+See http://tools.ietf.org/html/rfc3986#section-5.2.4
+
+
+## `RingCentral\Psr7\Uri::resolve`
+
+`public static function resolve(UriInterface $base, $rel) -> UriInterface`
+
+Resolve a base URI with a relative URI and return a new URI.
+
+See http://tools.ietf.org/html/rfc3986#section-5
+
+
+## `RingCentral\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
+
+Create a new URI with a specific query string value.
+
+Any existing query string values that exactly match the provided key are
+removed and replaced with the given key value pair.
+
+Note: this function will convert "=" to "%3D" and "&" to "%26".
+
+
+## `RingCentral\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
+
+Create a new URI with a specific query string value removed.
+
+Any existing query string values that exactly match the provided key are
+removed.
+
+Note: this function will convert "=" to "%3D" and "&" to "%26".
+
+
+## `RingCentral\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts) -> UriInterface`
+
+Create a `RingCentral\Psr7\Uri` object from a hash of `parse_url` parts.
+
+
+# Not Implemented
+
+A few aspects of PSR-7 are not implemented in this project. A pull request for
+any of these features is welcome:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
diff --git a/instafeed/vendor/ringcentral/psr7/composer.json b/instafeed/vendor/ringcentral/psr7/composer.json
new file mode 100755
index 0000000..4955053
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "ringcentral/psr7",
+ "type": "library",
+ "description": "PSR-7 message implementation",
+ "keywords": ["message", "stream", "http", "uri"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3",
+ "psr/http-message": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "RingCentral\\Psr7\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/phpunit.xml.dist b/instafeed/vendor/ringcentral/psr7/phpunit.xml.dist
new file mode 100755
index 0000000..500cd53
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/phpunit.xml.dist
@@ -0,0 +1,17 @@
+
+
+
+
+ tests
+
+
+
+
+ src
+
+ src/
+
+
+
+
diff --git a/instafeed/vendor/ringcentral/psr7/src/AppendStream.php b/instafeed/vendor/ringcentral/psr7/src/AppendStream.php
new file mode 100755
index 0000000..8b8df6f
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/AppendStream.php
@@ -0,0 +1,233 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream)
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ *
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->pos = $this->current = 0;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = array();
+ }
+
+ /**
+ * Detaches each attached stream
+ *
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $this->close();
+ $this->detached = true;
+ }
+
+ public function tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ *
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof()
+ {
+ return !$this->streams ||
+ ($this->current >= count($this->streams) - 1 &&
+ $this->streams[$this->current]->eof());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ *
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ . $i . ' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ *
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ $this->current++;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ // Using a loose comparison here to match on '', false, and null
+ if ($result == null) {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key ? null : array();
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/BufferStream.php b/instafeed/vendor/ringcentral/psr7/src/BufferStream.php
new file mode 100755
index 0000000..a1e236d
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/BufferStream.php
@@ -0,0 +1,137 @@
+hwm = $hwm;
+ }
+
+ public function __toString()
+ {
+ return $this->getContents();
+ }
+
+ public function getContents()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+ }
+
+ public function getSize()
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return true;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof()
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell()
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length)
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string)
+ {
+ $this->buffer .= $string;
+
+ // TODO: What should happen here?
+ if (strlen($this->buffer) >= $this->hwm) {
+ return false;
+ }
+
+ return strlen($string);
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key == 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : array();
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/CachingStream.php b/instafeed/vendor/ringcentral/psr7/src/CachingStream.php
new file mode 100755
index 0000000..ce3aca8
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/CachingStream.php
@@ -0,0 +1,135 @@
+remoteStream = $stream;
+ parent::__construct($target ?: new Stream(fopen('php://temp', 'r+')));
+ }
+
+ public function getSize()
+ {
+ return max($this->stream->getSize(), $this->remoteStream->getSize());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence == SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence == SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence == SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ // Because 0 is the first byte, we seek to size - 1.
+ $byte = $size - 1 - $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // If the seek byte is greater the number of read bytes, then read
+ // the difference of bytes to cache the bytes and inherently seek.
+ $this->read($diff);
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string)
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ $this->remoteStream->close() && $this->stream->close();
+ }
+
+ private function cacheEntireStream()
+ {
+ $target = new FnStream(array('write' => 'strlen'));
+ copy_to_stream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/DroppingStream.php b/instafeed/vendor/ringcentral/psr7/src/DroppingStream.php
new file mode 100755
index 0000000..3a34d38
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/DroppingStream.php
@@ -0,0 +1,41 @@
+maxLength = $maxLength;
+ }
+
+ public function write($string)
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/FnStream.php b/instafeed/vendor/ringcentral/psr7/src/FnStream.php
new file mode 100755
index 0000000..f78dc8b
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/FnStream.php
@@ -0,0 +1,163 @@
+methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_' . $name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ * @throws \BadMethodCallException
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ . '() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ call_user_func($this->_fn_close);
+ }
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
+ $methods[$diff] = array($stream, $diff);
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString()
+ {
+ return call_user_func($this->_fn___toString);
+ }
+
+ public function close()
+ {
+ return call_user_func($this->_fn_close);
+ }
+
+ public function detach()
+ {
+ return call_user_func($this->_fn_detach);
+ }
+
+ public function getSize()
+ {
+ return call_user_func($this->_fn_getSize);
+ }
+
+ public function tell()
+ {
+ return call_user_func($this->_fn_tell);
+ }
+
+ public function eof()
+ {
+ return call_user_func($this->_fn_eof);
+ }
+
+ public function isSeekable()
+ {
+ return call_user_func($this->_fn_isSeekable);
+ }
+
+ public function rewind()
+ {
+ call_user_func($this->_fn_rewind);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ call_user_func($this->_fn_seek, $offset, $whence);
+ }
+
+ public function isWritable()
+ {
+ return call_user_func($this->_fn_isWritable);
+ }
+
+ public function write($string)
+ {
+ return call_user_func($this->_fn_write, $string);
+ }
+
+ public function isReadable()
+ {
+ return call_user_func($this->_fn_isReadable);
+ }
+
+ public function read($length)
+ {
+ return call_user_func($this->_fn_read, $length);
+ }
+
+ public function getContents()
+ {
+ return call_user_func($this->_fn_getContents);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return call_user_func($this->_fn_getMetadata, $key);
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/InflateStream.php b/instafeed/vendor/ringcentral/psr7/src/InflateStream.php
new file mode 100755
index 0000000..c718002
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/InflateStream.php
@@ -0,0 +1,27 @@
+filename = $filename;
+ $this->mode = $mode;
+ parent::__construct();
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream()
+ {
+ return stream_for(try_fopen($this->filename, $this->mode));
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/LimitStream.php b/instafeed/vendor/ringcentral/psr7/src/LimitStream.php
new file mode 100755
index 0000000..57eeca9
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/LimitStream.php
@@ -0,0 +1,154 @@
+setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof()
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit == -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit == -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset % with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset($offset)
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length)
+ {
+ if ($this->limit == -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/MessageTrait.php b/instafeed/vendor/ringcentral/psr7/src/MessageTrait.php
new file mode 100755
index 0000000..9330bcb
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/MessageTrait.php
@@ -0,0 +1,167 @@
+protocol;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+ return $new;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headerLines;
+ }
+
+ public function hasHeader($header)
+ {
+ return isset($this->headers[strtolower($header)]);
+ }
+
+ public function getHeader($header)
+ {
+ $name = strtolower($header);
+ return isset($this->headers[$name]) ? $this->headers[$name] : array();
+ }
+
+ public function getHeaderLine($header)
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value)
+ {
+ $new = clone $this;
+ $header = trim($header);
+ $name = strtolower($header);
+
+ if (!is_array($value)) {
+ $new->headers[$name] = array(trim($value));
+ } else {
+ $new->headers[$name] = $value;
+ foreach ($new->headers[$name] as &$v) {
+ $v = trim($v);
+ }
+ }
+
+ // Remove the header lines.
+ foreach (array_keys($new->headerLines) as $key) {
+ if (strtolower($key) === $name) {
+ unset($new->headerLines[$key]);
+ }
+ }
+
+ // Add the header line.
+ $new->headerLines[$header] = $new->headers[$name];
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value)
+ {
+ if (!$this->hasHeader($header)) {
+ return $this->withHeader($header, $value);
+ }
+
+ $header = trim($header);
+ $name = strtolower($header);
+
+ $value = (array) $value;
+ foreach ($value as &$v) {
+ $v = trim($v);
+ }
+
+ $new = clone $this;
+ $new->headers[$name] = array_merge($new->headers[$name], $value);
+ $new->headerLines[$header] = array_merge($new->headerLines[$header], $value);
+
+ return $new;
+ }
+
+ public function withoutHeader($header)
+ {
+ if (!$this->hasHeader($header)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $name = strtolower($header);
+ unset($new->headers[$name]);
+
+ foreach (array_keys($new->headerLines) as $key) {
+ if (strtolower($key) === $name) {
+ unset($new->headerLines[$key]);
+ }
+ }
+
+ return $new;
+ }
+
+ public function getBody()
+ {
+ if (!$this->stream) {
+ $this->stream = stream_for('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body)
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+ return $new;
+ }
+
+ protected function setHeaders(array $headers)
+ {
+ $this->headerLines = $this->headers = array();
+ foreach ($headers as $header => $value) {
+ $header = trim($header);
+ $name = strtolower($header);
+ if (!is_array($value)) {
+ $value = trim($value);
+ $this->headers[$name][] = $value;
+ $this->headerLines[$header][] = $value;
+ } else {
+ foreach ($value as $v) {
+ $v = trim($v);
+ $this->headers[$name][] = $v;
+ $this->headerLines[$header][] = $v;
+ }
+ }
+ }
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/MultipartStream.php b/instafeed/vendor/ringcentral/psr7/src/MultipartStream.php
new file mode 100755
index 0000000..8c5e5bc
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/MultipartStream.php
@@ -0,0 +1,152 @@
+boundary = $boundary ?: uniqid();
+ parent::__construct($this->createStream($elements));
+ }
+
+ /**
+ * Get the boundary
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ */
+ private function getHeaders(array $headers)
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements)
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(stream_for("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element)
+ {
+ foreach (array('contents', 'name') as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = stream_for($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if (substr($uri, 0, 6) !== 'php://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ list($body, $headers) = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ isset($element['filename']) ? $element['filename'] : null,
+ isset($element['headers']) ? $element['headers'] : array()
+ );
+
+ $stream->addStream(stream_for($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(stream_for("\r\n"));
+ }
+
+ /**
+ * @return array
+ */
+ private function createElement($name, $stream, $filename, array $headers)
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = $this->getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = $filename
+ ? sprintf('form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename))
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = $this->getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = $this->getHeader($headers, 'content-type');
+ if (!$type && $filename) {
+ if ($type = mimetype_from_filename($filename)) {
+ $headers['Content-Type'] = $type;
+ }
+ }
+
+ return array($stream, $headers);
+ }
+
+ private function getHeader(array $headers, $key)
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower($k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/NoSeekStream.php b/instafeed/vendor/ringcentral/psr7/src/NoSeekStream.php
new file mode 100755
index 0000000..328fdda
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/NoSeekStream.php
@@ -0,0 +1,21 @@
+source = $source;
+ $this->size = isset($options['size']) ? $options['size'] : null;
+ $this->metadata = isset($options['metadata']) ? $options['metadata'] : array();
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString()
+ {
+ try {
+ return copy_to_string($this);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function close()
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = false;
+ $this->source = null;
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ return $this->tellPos;
+ }
+
+ public function eof()
+ {
+ return !$this->source;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents()
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
+ }
+
+ private function pump($length)
+ {
+ if ($this->source) {
+ do {
+ $data = call_user_func($this->source, $length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/Request.php b/instafeed/vendor/ringcentral/psr7/src/Request.php
new file mode 100755
index 0000000..bb0f2fc
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/Request.php
@@ -0,0 +1,146 @@
+method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $protocolVersion;
+
+ $host = $uri->getHost();
+ if ($host && !$this->hasHeader('Host')) {
+ $this->updateHostFromUri($host);
+ }
+
+ if ($body) {
+ $this->stream = stream_for($body);
+ }
+ }
+
+ public function getRequestTarget()
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target == null) {
+ $target = '/';
+ }
+ if ($this->uri->getQuery()) {
+ $target .= '?' . $this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget)
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+ return $new;
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method)
+ {
+ $new = clone $this;
+ $new->method = strtoupper($method);
+ return $new;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false)
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost) {
+ if ($host = $uri->getHost()) {
+ $new->updateHostFromUri($host);
+ }
+ }
+
+ return $new;
+ }
+
+ public function withHeader($header, $value)
+ {
+ /** @var Request $newInstance */
+ $newInstance = parent::withHeader($header, $value);
+ return $newInstance;
+ }
+
+ private function updateHostFromUri($host)
+ {
+ // Ensure Host is the first header.
+ // See: http://tools.ietf.org/html/rfc7230#section-5.4
+ if ($port = $this->uri->getPort()) {
+ $host .= ':' . $port;
+ }
+
+ $this->headerLines = array('Host' => array($host)) + $this->headerLines;
+ $this->headers = array('host' => array($host)) + $this->headers;
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/Response.php b/instafeed/vendor/ringcentral/psr7/src/Response.php
new file mode 100755
index 0000000..a6d9451
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/Response.php
@@ -0,0 +1,129 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 511 => 'Network Authentication Required',
+ );
+
+ /** @var null|string */
+ private $reasonPhrase = '';
+
+ /** @var int */
+ private $statusCode = 200;
+
+ /**
+ * @param int $status Status code for the response, if any.
+ * @param array $headers Headers for the response, if any.
+ * @param mixed $body Stream body.
+ * @param string $version Protocol version.
+ * @param string $reason Reason phrase (a default will be used if possible).
+ */
+ public function __construct(
+ $status = 200,
+ array $headers = array(),
+ $body = null,
+ $version = '1.1',
+ $reason = null
+ ) {
+ $this->statusCode = (int) $status;
+
+ if ($body !== null) {
+ $this->stream = stream_for($body);
+ }
+
+ $this->setHeaders($headers);
+ if (!$reason && isset(self::$phrases[$this->statusCode])) {
+ $this->reasonPhrase = self::$phrases[$status];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ $new = clone $this;
+ $new->statusCode = (int) $code;
+ if (!$reasonPhrase && isset(self::$phrases[$new->statusCode])) {
+ $reasonPhrase = self::$phrases[$new->statusCode];
+ }
+ $new->reasonPhrase = $reasonPhrase;
+ return $new;
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/ServerRequest.php b/instafeed/vendor/ringcentral/psr7/src/ServerRequest.php
new file mode 100755
index 0000000..8408a09
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/ServerRequest.php
@@ -0,0 +1,122 @@
+serverParams = $serverParams;
+ }
+
+ public function getServerParams()
+ {
+ return $this->serverParams;
+ }
+
+ public function getCookieParams()
+ {
+ return $this->cookies;
+ }
+
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->cookies = $cookies;
+ return $new;
+ }
+
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+ return $new;
+ }
+
+ public function getUploadedFiles()
+ {
+ return $this->fileParams;
+ }
+
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->fileParams = $uploadedFiles;
+ return $new;
+ }
+
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+ return $new;
+ }
+
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ public function getAttribute($name, $default = null)
+ {
+ if (!array_key_exists($name, $this->attributes)) {
+ return $default;
+ }
+ return $this->attributes[$name];
+ }
+
+ public function withAttribute($name, $value)
+ {
+ $new = clone $this;
+ $new->attributes[$name] = $value;
+ return $new;
+ }
+
+ public function withoutAttribute($name)
+ {
+ $new = clone $this;
+ unset($new->attributes[$name]);
+ return $new;
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/Stream.php b/instafeed/vendor/ringcentral/psr7/src/Stream.php
new file mode 100755
index 0000000..0a0157c
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/Stream.php
@@ -0,0 +1,245 @@
+ array(
+ 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
+ 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
+ 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a+' => true
+ ),
+ 'write' => array(
+ 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
+ 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
+ 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
+ )
+ );
+
+ /**
+ * This constructor accepts an associative array of options.
+ *
+ * - size: (int) If a read stream would otherwise have an indeterminate
+ * size, but the size is known due to foreknownledge, then you can
+ * provide that size, in bytes.
+ * - metadata: (array) Any additional metadata to return when the metadata
+ * of the stream is accessed.
+ *
+ * @param resource $stream Stream resource to wrap.
+ * @param array $options Associative array of options.
+ *
+ * @throws \InvalidArgumentException if the stream is not a stream resource
+ */
+ public function __construct($stream, $options = array())
+ {
+ if (!is_resource($stream)) {
+ throw new \InvalidArgumentException('Stream must be a resource');
+ }
+
+ if (isset($options['size'])) {
+ $this->size = $options['size'];
+ }
+
+ $this->customMetadata = isset($options['metadata'])
+ ? $options['metadata']
+ : array();
+
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
+ $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ throw new \RuntimeException('The stream is detached');
+ }
+
+ throw new \BadMethodCallException('No value for ' . $name);
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return (string) stream_get_contents($this->stream);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ $contents = stream_get_contents($this->stream);
+
+ if ($contents === false) {
+ throw new \RuntimeException('Unable to read stream contents');
+ }
+
+ return $contents;
+ }
+
+ public function close()
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function eof()
+ {
+ return !$this->stream || feof($this->stream);
+ }
+
+ public function tell()
+ {
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ } elseif (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ . $offset . ' with whence ' . var_export($whence, true));
+ }
+ }
+
+ public function read($length)
+ {
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+
+ return fread($this->stream, $length);
+ }
+
+ public function write($string)
+ {
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : array();
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/StreamDecoratorTrait.php b/instafeed/vendor/ringcentral/psr7/src/StreamDecoratorTrait.php
new file mode 100755
index 0000000..e22c674
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,139 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @param string $name Name of the property (allows "stream" only).
+ *
+ * @return StreamInterface
+ */
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ $this->stream = $this->createStream();
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString()
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+ return $this->getContents();
+ } catch (\Exception $e) {
+ // Really, PHP? https://bugs.php.net/bug.php?id=53648
+ trigger_error('StreamDecorator::__toString exception: '
+ . (string) $e, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ $result = call_user_func_array(array($this->stream, $method), $args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close()
+ {
+ $this->stream->close();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize()
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable()
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->stream->write($string);
+ }
+
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/StreamWrapper.php b/instafeed/vendor/ringcentral/psr7/src/StreamWrapper.php
new file mode 100755
index 0000000..8cc07d7
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/StreamWrapper.php
@@ -0,0 +1,121 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ . 'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, null, stream_context_create(array(
+ 'guzzle' => array('stream' => $stream)
+ )));
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register()
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read($count)
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write($data)
+ {
+ return (int) $this->stream->write($data);
+ }
+
+ public function stream_tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ public function stream_stat()
+ {
+ static $modeMap = array(
+ 'r' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188
+ );
+
+ return array(
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ );
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/Uri.php b/instafeed/vendor/ringcentral/psr7/src/Uri.php
new file mode 100755
index 0000000..5323cdc
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/Uri.php
@@ -0,0 +1,601 @@
+ 80,
+ 'https' => 443,
+ );
+
+ private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
+ private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
+ private static $replaceQuery = array('=' => '%3D', '&' => '%26');
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /**
+ * @param string $uri URI to parse and wrap.
+ */
+ public function __construct($uri = '')
+ {
+ if ($uri != null) {
+ $parts = parse_url($uri);
+ if ($parts === false) {
+ throw new \InvalidArgumentException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ public function __toString()
+ {
+ return self::createUriString(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->getPath(),
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ /**
+ * Removes dot segments from a path and returns the new path.
+ *
+ * @param string $path
+ *
+ * @return string
+ * @link http://tools.ietf.org/html/rfc3986#section-5.2.4
+ */
+ public static function removeDotSegments($path)
+ {
+ static $noopPaths = array('' => true, '/' => true, '*' => true);
+ static $ignoreSegments = array('.' => true, '..' => true);
+
+ if (isset($noopPaths[$path])) {
+ return $path;
+ }
+
+ $results = array();
+ $segments = explode('/', $path);
+ foreach ($segments as $segment) {
+ if ($segment == '..') {
+ array_pop($results);
+ } elseif (!isset($ignoreSegments[$segment])) {
+ $results[] = $segment;
+ }
+ }
+
+ $newPath = implode('/', $results);
+ // Add the leading slash if necessary
+ if (substr($path, 0, 1) === '/' &&
+ substr($newPath, 0, 1) !== '/'
+ ) {
+ $newPath = '/' . $newPath;
+ }
+
+ // Add the trailing slash if necessary
+ if ($newPath != '/' && isset($ignoreSegments[end($segments)])) {
+ $newPath .= '/';
+ }
+
+ return $newPath;
+ }
+
+ /**
+ * Resolve a base URI with a relative URI and return a new URI.
+ *
+ * @param UriInterface $base Base URI
+ * @param string $rel Relative URI
+ *
+ * @return UriInterface
+ */
+ public static function resolve(UriInterface $base, $rel)
+ {
+ if ($rel === null || $rel === '') {
+ return $base;
+ }
+
+ if (!($rel instanceof UriInterface)) {
+ $rel = new self($rel);
+ }
+
+ // Return the relative uri as-is if it has a scheme.
+ if ($rel->getScheme()) {
+ return $rel->withPath(static::removeDotSegments($rel->getPath()));
+ }
+
+ $relParts = array(
+ 'scheme' => $rel->getScheme(),
+ 'authority' => $rel->getAuthority(),
+ 'path' => $rel->getPath(),
+ 'query' => $rel->getQuery(),
+ 'fragment' => $rel->getFragment()
+ );
+
+ $parts = array(
+ 'scheme' => $base->getScheme(),
+ 'authority' => $base->getAuthority(),
+ 'path' => $base->getPath(),
+ 'query' => $base->getQuery(),
+ 'fragment' => $base->getFragment()
+ );
+
+ if (!empty($relParts['authority'])) {
+ $parts['authority'] = $relParts['authority'];
+ $parts['path'] = self::removeDotSegments($relParts['path']);
+ $parts['query'] = $relParts['query'];
+ $parts['fragment'] = $relParts['fragment'];
+ } elseif (!empty($relParts['path'])) {
+ if (substr($relParts['path'], 0, 1) == '/') {
+ $parts['path'] = self::removeDotSegments($relParts['path']);
+ $parts['query'] = $relParts['query'];
+ $parts['fragment'] = $relParts['fragment'];
+ } else {
+ if (!empty($parts['authority']) && empty($parts['path'])) {
+ $mergedPath = '/';
+ } else {
+ $mergedPath = substr($parts['path'], 0, strrpos($parts['path'], '/') + 1);
+ }
+ $parts['path'] = self::removeDotSegments($mergedPath . $relParts['path']);
+ $parts['query'] = $relParts['query'];
+ $parts['fragment'] = $relParts['fragment'];
+ }
+ } elseif (!empty($relParts['query'])) {
+ $parts['query'] = $relParts['query'];
+ } elseif ($relParts['fragment'] != null) {
+ $parts['fragment'] = $relParts['fragment'];
+ }
+
+ return new self(static::createUriString(
+ $parts['scheme'],
+ $parts['authority'],
+ $parts['path'],
+ $parts['query'],
+ $parts['fragment']
+ ));
+ }
+
+ /**
+ * Create a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * Note: this function will convert "=" to "%3D" and "&" to "%26".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key value pair to remove.
+ *
+ * @return UriInterface
+ */
+ public static function withoutQueryValue(UriInterface $uri, $key)
+ {
+ $current = $uri->getQuery();
+ if (!$current) {
+ return $uri;
+ }
+
+ $result = array();
+ foreach (explode('&', $current) as $part) {
+ $subParts = explode('=', $part);
+ if ($subParts[0] !== $key) {
+ $result[] = $part;
+ };
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Create a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * Note: this function will convert "=" to "%3D" and "&" to "%26".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string $value Value to set.
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValue(UriInterface $uri, $key, $value)
+ {
+ $current = $uri->getQuery();
+ $key = strtr($key, self::$replaceQuery);
+
+ if (!$current) {
+ $result = array();
+ } else {
+ $result = array();
+ foreach (explode('&', $current) as $part) {
+ $subParts = explode('=', $part);
+ if ($subParts[0] !== $key) {
+ $result[] = $part;
+ };
+ }
+ }
+
+ if ($value !== null) {
+ $result[] = $key . '=' . strtr($value, self::$replaceQuery);
+ } else {
+ $result[] = $key;
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Create a URI from a hash of parse_url parts.
+ *
+ * @param array $parts
+ *
+ * @return self
+ */
+ public static function fromParts(array $parts)
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ return $uri;
+ }
+
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority()
+ {
+ if (empty($this->host)) {
+ return '';
+ }
+
+ $authority = $this->host;
+ if (!empty($this->userInfo)) {
+ $authority = $this->userInfo . '@' . $authority;
+ }
+
+ if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) {
+ $authority .= ':' . $this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo()
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ public function getPath()
+ {
+ return $this->path == null ? '' : $this->path;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme)
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->port = $new->filterPort($new->scheme, $new->host, $new->port);
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null)
+ {
+ $info = $user;
+ if ($password) {
+ $info .= ':' . $password;
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ return $new;
+ }
+
+ public function withHost($host)
+ {
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ return $new;
+ }
+
+ public function withPort($port)
+ {
+ $port = $this->filterPort($this->scheme, $this->host, $port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ return $new;
+ }
+
+ public function withPath($path)
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException(
+ 'Invalid path provided; must be a string'
+ );
+ }
+
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ return $new;
+ }
+
+ public function withQuery($query)
+ {
+ if (!is_string($query) && !method_exists($query, '__toString')) {
+ throw new \InvalidArgumentException(
+ 'Query string must be a string'
+ );
+ }
+
+ $query = (string) $query;
+ if (substr($query, 0, 1) === '?') {
+ $query = substr($query, 1);
+ }
+
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+ return $new;
+ }
+
+ public function withFragment($fragment)
+ {
+ if (substr($fragment, 0, 1) === '#') {
+ $fragment = substr($fragment, 1);
+ }
+
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+ return $new;
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts)
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
+ $this->host = isset($parts['host']) ? $parts['host'] : '';
+ $this->port = !empty($parts['port'])
+ ? $this->filterPort($this->scheme, $this->host, $parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':' . $parts['pass'];
+ }
+ }
+
+ /**
+ * Create a URI string from its various parts
+ *
+ * @param string $scheme
+ * @param string $authority
+ * @param string $path
+ * @param string $query
+ * @param string $fragment
+ * @return string
+ */
+ private static function createUriString($scheme, $authority, $path, $query, $fragment)
+ {
+ $uri = '';
+
+ if (!empty($scheme)) {
+ $uri .= $scheme . '://';
+ }
+
+ if (!empty($authority)) {
+ $uri .= $authority;
+ }
+
+ if ($path != null) {
+ // Add a leading slash if necessary.
+ if ($uri && substr($path, 0, 1) !== '/') {
+ $uri .= '/';
+ }
+ $uri .= $path;
+ }
+
+ if ($query != null) {
+ $uri .= '?' . $query;
+ }
+
+ if ($fragment != null) {
+ $uri .= '#' . $fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Is a given port non-standard for the current scheme?
+ *
+ * @param string $scheme
+ * @param string $host
+ * @param int $port
+ * @return bool
+ */
+ private static function isNonStandardPort($scheme, $host, $port)
+ {
+ if (!$scheme && $port) {
+ return true;
+ }
+
+ if (!$host || !$port) {
+ return false;
+ }
+
+ return !isset(static::$schemes[$scheme]) || $port !== static::$schemes[$scheme];
+ }
+
+ /**
+ * @param string $scheme
+ *
+ * @return string
+ */
+ private function filterScheme($scheme)
+ {
+ $scheme = strtolower($scheme);
+ $scheme = rtrim($scheme, ':/');
+
+ return $scheme;
+ }
+
+ /**
+ * @param string $scheme
+ * @param string $host
+ * @param int $port
+ *
+ * @return int|null
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($scheme, $host, $port)
+ {
+ if (null !== $port) {
+ $port = (int) $port;
+ if (1 > $port || 0xffff < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
+ );
+ }
+ }
+
+ return $this->isNonStandardPort($scheme, $host, $port) ? $port : null;
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param $path
+ *
+ * @return string
+ */
+ private function filterPath($path)
+ {
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . ':@\/%]+|%(?![A-Fa-f0-9]{2}))/',
+ array($this, 'rawurlencodeMatchZero'),
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param $str
+ *
+ * @return string
+ */
+ private function filterQueryAndFragment($str)
+ {
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
+ array($this, 'rawurlencodeMatchZero'),
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match)
+ {
+ return rawurlencode($match[0]);
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/functions.php b/instafeed/vendor/ringcentral/psr7/src/functions.php
new file mode 100755
index 0000000..3fc89bd
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/functions.php
@@ -0,0 +1,832 @@
+getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+}
+
+/**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
+ * returns a UriInterface for the given value. If the value is already a
+ * `UriInterface`, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @return UriInterface
+ * @throws \InvalidArgumentException
+ */
+function uri_for($uri)
+{
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ } elseif (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+}
+
+/**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * @param resource|string|StreamInterface $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return Stream
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+function stream_for($resource = '', array $options = array())
+{
+ switch (gettype($resource)) {
+ case 'string':
+ $stream = fopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new Stream($stream, $options);
+ case 'resource':
+ return new Stream($resource, $options);
+ case 'object':
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return stream_for((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(fopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
+}
+
+/**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair
+ * data of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ *
+ * @return array Returns the parsed header values.
+ */
+function parse_header($header)
+{
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = array();
+
+ foreach (normalize_header($header) as $val) {
+ $part = array();
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+function normalize_header($header)
+{
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = array();
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Clone and modify a request with the given changes.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+function modify_request(RequestInterface $request, array $changes)
+{
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = _caseless_remove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+}
+
+/**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()` returns a
+ * value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+function rewind_body(MessageInterface $message)
+{
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+}
+
+/**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ * @throws \RuntimeException if the file cannot be opened
+ */
+function try_fopen($filename, $mode)
+{
+ $ex = null;
+ $fargs = func_get_args();
+ set_error_handler(function () use ($filename, $mode, &$ex, $fargs) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open %s using mode %s: %s',
+ $filename,
+ $mode,
+ $fargs[1]
+ ));
+ });
+
+ $handle = fopen($filename, $mode);
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+}
+
+/**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ * @return string
+ * @throws \RuntimeException on error.
+ */
+function copy_to_string(StreamInterface $stream, $maxLen = -1)
+{
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+}
+
+/**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+function copy_to_stream(
+ StreamInterface $source,
+ StreamInterface $dest,
+ $maxLen = -1
+) {
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read(1048576))) {
+ break;
+ }
+ }
+ return;
+ }
+
+ $bytes = 0;
+ while (!$source->eof()) {
+ $buf = $source->read($maxLen - $bytes);
+ if (!($len = strlen($buf))) {
+ break;
+ }
+ $bytes += $len;
+ $dest->write($buf);
+ if ($bytes == $maxLen) {
+ break;
+ }
+ }
+}
+
+/**
+ * Calculate a hash of a Stream
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ * @throws \RuntimeException on error.
+ */
+function hash(
+ StreamInterface $stream,
+ $algo,
+ $rawOutput = false
+) {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+}
+
+/**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string|bool
+ */
+function readline(StreamInterface $stream, $maxLength = null)
+{
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ // Using a loose equality here to match on '' and false.
+ if (null == ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+}
+
+/**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ *
+ * @return Request
+ */
+function parse_request($message)
+{
+ $data = _parse_message($message);
+ $matches = array();
+ if (!preg_match('/^[a-zA-Z]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $subParts = isset($parts[2]) ? explode('/', $parts[2]) : array();
+ $version = isset($parts[2]) ? $subParts[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+}
+
+/**
+ * Parses a request message string into a server request object.
+ *
+ * @param string $message Request message string.
+ * @param array $serverParams Server params that will be added to the
+ * ServerRequest object
+ *
+ * @return ServerRequest
+ */
+function parse_server_request($message, array $serverParams = array())
+{
+ $request = parse_request($message);
+
+ return new ServerRequest(
+ $request->getMethod(),
+ $request->getUri(),
+ $request->getHeaders(),
+ $request->getBody(),
+ $request->getProtocolVersion(),
+ $serverParams
+ );
+}
+
+/**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ *
+ * @return Response
+ */
+function parse_response($message)
+{
+ $data = _parse_message($message);
+ if (!preg_match('/^HTTP\/.* [0-9]{3} .*/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $subParts = explode('/', $parts[0]);
+
+ return new Response(
+ $parts[1],
+ $data['headers'],
+ $data['body'],
+ $subParts[1],
+ isset($parts[2]) ? $parts[2] : null
+ );
+}
+
+/**
+ * Parse a query string into an associative array.
+ *
+ * If multiple values are found for the same key, the value of that key
+ * value pair will become an array. This function does not parse nested
+ * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
+ * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
+ *
+ * @param string $str Query string to parse
+ * @param bool|string $urlEncoding How the query string is encoded
+ *
+ * @return array
+ */
+function parse_query($str, $urlEncoding = true)
+{
+ $result = array();
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', $value));
+ };
+ } elseif ($urlEncoding == PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding == PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) { return $str; };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!isset($result[$key])) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = array($result[$key]);
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of parseQuery() to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like http_build_query would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
+ * to encode using RFC3986, or PHP_QUERY_RFC1738
+ * to encode using RFC1738.
+ * @return string
+ */
+function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
+{
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function ($str) { return $str; };
+ } elseif ($encoding == PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding == PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder($k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ if ($v !== null) {
+ $qs .= '=' . $encoder($v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ if ($vv !== null) {
+ $qs .= '=' . $encoder($vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+}
+
+/**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @param $filename
+ *
+ * @return null|string
+ */
+function mimetype_from_filename($filename)
+{
+ return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
+}
+
+/**
+ * Maps a file extensions to a mimetype.
+ *
+ * @param $extension string The file extension.
+ *
+ * @return string|null
+ * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
+ */
+function mimetype_from_extension($extension)
+{
+ static $mimetypes = array(
+ '7z' => 'application/x-7z-compressed',
+ 'aac' => 'audio/x-aac',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'asc' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'atom' => 'application/atom+xml',
+ 'avi' => 'video/x-msvideo',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip2',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'deb' => 'application/x-debian-package',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dvi' => 'application/x-dvi',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'etx' => 'text/x-setext',
+ 'flac' => 'audio/flac',
+ 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif',
+ 'gz' => 'application/gzip',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ini' => 'text/plain',
+ 'iso' => 'application/x-iso9660-image',
+ 'jar' => 'application/java-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'latex' => 'application/x-latex',
+ 'log' => 'text/plain',
+ 'm4a' => 'audio/mp4',
+ 'm4v' => 'video/mp4',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'qt' => 'video/quicktime',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'torrent' => 'application/x-bittorrent',
+ 'ttf' => 'application/x-font-ttf',
+ 'txt' => 'text/plain',
+ 'wav' => 'audio/x-wav',
+ 'webm' => 'video/webm',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'video/x-ms-wmv',
+ 'woff' => 'application/x-font-woff',
+ 'wsdl' => 'application/wsdl+xml',
+ 'xbm' => 'image/x-xbitmap',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'zip' => 'application/zip',
+ );
+
+ $extension = strtolower($extension);
+
+ return isset($mimetypes[$extension])
+ ? $mimetypes[$extension]
+ : null;
+}
+
+/**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ *
+ * @return array
+ * @internal
+ */
+function _parse_message($message)
+{
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ // Iterate over each line in the message, accounting for line endings
+ $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $result = array('start-line' => array_shift($lines), 'headers' => array(), 'body' => '');
+ array_shift($lines);
+
+ for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
+ $line = $lines[$i];
+ // If two line breaks were encountered, then this is the end of body
+ if (empty($line)) {
+ if ($i < $totalLines - 1) {
+ $result['body'] = implode('', array_slice($lines, $i + 2));
+ }
+ break;
+ }
+ if (strpos($line, ':')) {
+ $parts = explode(':', $line, 2);
+ $key = trim($parts[0]);
+ $value = isset($parts[1]) ? trim($parts[1]) : '';
+ $result['headers'][$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ *
+ * @return string
+ * @internal
+ */
+function _parse_request_uri($path, array $headers)
+{
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme . '://' . $host . '/' . ltrim($path, '/');
+}
+
+/** @internal */
+function _caseless_remove($keys, array $data)
+{
+ $result = array();
+
+ foreach ($keys as &$key) {
+ $key = strtolower($key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower($k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+}
diff --git a/instafeed/vendor/ringcentral/psr7/src/functions_include.php b/instafeed/vendor/ringcentral/psr7/src/functions_include.php
new file mode 100755
index 0000000..252e0cf
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/src/functions_include.php
@@ -0,0 +1,6 @@
+getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $a->addStream($s);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage The AppendStream can only seek with SEEK_SET
+ */
+ public function testValidatesSeekType()
+ {
+ $a = new AppendStream();
+ $a->seek(100, SEEK_CUR);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to seek stream 0 of the AppendStream
+ */
+ public function testTriesToRewindOnSeek()
+ {
+ $a = new AppendStream();
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable', 'rewind', 'isSeekable'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('rewind')
+ ->will($this->throwException(new \RuntimeException()));
+ $a->addStream($s);
+ $a->seek(10);
+ }
+
+ public function testSeeksToPositionByReading()
+ {
+ $a = new AppendStream(array(
+ Psr7\stream_for('foo'),
+ Psr7\stream_for('bar'),
+ Psr7\stream_for('baz'),
+ ));
+
+ $a->seek(3);
+ $this->assertEquals(3, $a->tell());
+ $this->assertEquals('bar', $a->read(3));
+
+ $a->seek(6);
+ $this->assertEquals(6, $a->tell());
+ $this->assertEquals('baz', $a->read(3));
+ }
+
+ public function testDetachesEachStream()
+ {
+ $s1 = Psr7\stream_for('foo');
+ $s2 = Psr7\stream_for('bar');
+ $a = new AppendStream(array($s1, $s2));
+ $this->assertSame('foobar', (string) $a);
+ $a->detach();
+ $this->assertSame('', (string) $a);
+ $this->assertSame(0, $a->getSize());
+ }
+
+ public function testClosesEachStream()
+ {
+ $s1 = Psr7\stream_for('foo');
+ $a = new AppendStream(array($s1));
+ $a->close();
+ $this->assertSame('', (string) $a);
+ }
+
+ /**
+ * @expectedExceptionMessage Cannot write to an AppendStream
+ * @expectedException \RuntimeException
+ */
+ public function testIsNotWritable()
+ {
+ $a = new AppendStream(array(Psr7\stream_for('foo')));
+ $this->assertFalse($a->isWritable());
+ $this->assertTrue($a->isSeekable());
+ $this->assertTrue($a->isReadable());
+ $a->write('foo');
+ }
+
+ public function testDoesNotNeedStreams()
+ {
+ $a = new AppendStream();
+ $this->assertEquals('', (string) $a);
+ }
+
+ public function testCanReadFromMultipleStreams()
+ {
+ $a = new AppendStream(array(
+ Psr7\stream_for('foo'),
+ Psr7\stream_for('bar'),
+ Psr7\stream_for('baz'),
+ ));
+ $this->assertFalse($a->eof());
+ $this->assertSame(0, $a->tell());
+ $this->assertEquals('foo', $a->read(3));
+ $this->assertEquals('bar', $a->read(3));
+ $this->assertEquals('baz', $a->read(3));
+ $this->assertSame('', $a->read(1));
+ $this->assertTrue($a->eof());
+ $this->assertSame(9, $a->tell());
+ $this->assertEquals('foobarbaz', (string) $a);
+ }
+
+ public function testCanDetermineSizeFromMultipleStreams()
+ {
+ $a = new AppendStream(array(
+ Psr7\stream_for('foo'),
+ Psr7\stream_for('bar')
+ ));
+ $this->assertEquals(6, $a->getSize());
+
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isSeekable', 'isReadable'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(null));
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $a->addStream($s);
+ $this->assertNull($a->getSize());
+ }
+
+ public function testCatchesExceptionsWhenCastingToString()
+ {
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isSeekable', 'read', 'isReadable', 'eof'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('read')
+ ->will($this->throwException(new \RuntimeException('foo')));
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $s->expects($this->any())
+ ->method('eof')
+ ->will($this->returnValue(false));
+ $a = new AppendStream(array($s));
+ $this->assertFalse($a->eof());
+ $this->assertSame('', (string) $a);
+ }
+
+ public function testCanDetach()
+ {
+ $s = new AppendStream();
+ $s->detach();
+ }
+
+ public function testReturnsEmptyMetadata()
+ {
+ $s = new AppendStream();
+ $this->assertEquals(array(), $s->getMetadata());
+ $this->assertNull($s->getMetadata('foo'));
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/BufferStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/BufferStreamTest.php
new file mode 100755
index 0000000..79f907a
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/BufferStreamTest.php
@@ -0,0 +1,63 @@
+assertTrue($b->isReadable());
+ $this->assertTrue($b->isWritable());
+ $this->assertFalse($b->isSeekable());
+ $this->assertEquals(null, $b->getMetadata('foo'));
+ $this->assertEquals(10, $b->getMetadata('hwm'));
+ $this->assertEquals(array(), $b->getMetadata());
+ }
+
+ public function testRemovesReadDataFromBuffer()
+ {
+ $b = new BufferStream();
+ $this->assertEquals(3, $b->write('foo'));
+ $this->assertEquals(3, $b->getSize());
+ $this->assertFalse($b->eof());
+ $this->assertEquals('foo', $b->read(10));
+ $this->assertTrue($b->eof());
+ $this->assertEquals('', $b->read(10));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Cannot determine the position of a BufferStream
+ */
+ public function testCanCastToStringOrGetContents()
+ {
+ $b = new BufferStream();
+ $b->write('foo');
+ $b->write('baz');
+ $this->assertEquals('foo', $b->read(3));
+ $b->write('bar');
+ $this->assertEquals('bazbar', (string) $b);
+ $b->tell();
+ }
+
+ public function testDetachClearsBuffer()
+ {
+ $b = new BufferStream();
+ $b->write('foo');
+ $b->detach();
+ $this->assertTrue($b->eof());
+ $this->assertEquals(3, $b->write('abc'));
+ $this->assertEquals('abc', $b->read(10));
+ }
+
+ public function testExceedingHighwaterMarkReturnsFalseButStillBuffers()
+ {
+ $b = new BufferStream(5);
+ $this->assertEquals(3, $b->write('hi '));
+ $this->assertFalse($b->write('hello'));
+ $this->assertEquals('hi hello', (string) $b);
+ $this->assertEquals(4, $b->write('test'));
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/CachingStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/CachingStreamTest.php
new file mode 100755
index 0000000..f394fc9
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/CachingStreamTest.php
@@ -0,0 +1,166 @@
+decorated = Psr7\stream_for('testing');
+ $this->body = new CachingStream($this->decorated);
+ }
+
+ public function tearDown()
+ {
+ $this->decorated->close();
+ $this->body->close();
+ }
+
+ public function testUsesRemoteSizeIfPossible()
+ {
+ $body = Psr7\stream_for('test');
+ $caching = new CachingStream($body);
+ $this->assertEquals(4, $caching->getSize());
+ }
+
+ public function testReadsUntilCachedToByte()
+ {
+ $this->body->seek(5);
+ $this->assertEquals('n', $this->body->read(1));
+ $this->body->seek(0);
+ $this->assertEquals('t', $this->body->read(1));
+ }
+
+ public function testCanSeekNearEndWithSeekEnd()
+ {
+ $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
+ $cached = new CachingStream($baseStream);
+ $cached->seek(1, SEEK_END);
+ $this->assertEquals(24, $baseStream->tell());
+ $this->assertEquals('y', $cached->read(1));
+ $this->assertEquals(26, $cached->getSize());
+ }
+
+ public function testCanSeekToEndWithSeekEnd()
+ {
+ $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
+ $cached = new CachingStream($baseStream);
+ $cached->seek(0, SEEK_END);
+ $this->assertEquals(25, $baseStream->tell());
+ $this->assertEquals('z', $cached->read(1));
+ $this->assertEquals(26, $cached->getSize());
+ }
+
+ public function testCanUseSeekEndWithUnknownSize()
+ {
+ $baseStream = Psr7\stream_for('testing');
+ $decorated = Psr7\FnStream::decorate($baseStream, array(
+ 'getSize' => function () { return null; }
+ ));
+ $cached = new CachingStream($decorated);
+ $cached->seek(1, SEEK_END);
+ $this->assertEquals('ng', $cached->read(2));
+ }
+
+ public function testRewindUsesSeek()
+ {
+ $a = Psr7\stream_for('foo');
+ $d = $this->getMockBuilder('RingCentral\Psr7\CachingStream')
+ ->setMethods(array('seek'))
+ ->setConstructorArgs(array($a))
+ ->getMock();
+ $d->expects($this->once())
+ ->method('seek')
+ ->with(0)
+ ->will($this->returnValue(true));
+ $d->seek(0);
+ }
+
+ public function testCanSeekToReadBytes()
+ {
+ $this->assertEquals('te', $this->body->read(2));
+ $this->body->seek(0);
+ $this->assertEquals('test', $this->body->read(4));
+ $this->assertEquals(4, $this->body->tell());
+ $this->body->seek(2);
+ $this->assertEquals(2, $this->body->tell());
+ $this->body->seek(2, SEEK_CUR);
+ $this->assertEquals(4, $this->body->tell());
+ $this->assertEquals('ing', $this->body->read(3));
+ }
+
+ public function testWritesToBufferStream()
+ {
+ $this->body->read(2);
+ $this->body->write('hi');
+ $this->body->seek(0);
+ $this->assertEquals('tehiing', (string) $this->body);
+ }
+
+ public function testSkipsOverwrittenBytes()
+ {
+ $decorated = Psr7\stream_for(
+ implode("\n", array_map(function ($n) {
+ return str_pad($n, 4, '0', STR_PAD_LEFT);
+ }, range(0, 25)))
+ );
+
+ $body = new CachingStream($decorated);
+
+ $this->assertEquals("0000\n", Psr7\readline($body));
+ $this->assertEquals("0001\n", Psr7\readline($body));
+ // Write over part of the body yet to be read, so skip some bytes
+ $this->assertEquals(5, $body->write("TEST\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+ // Read, which skips bytes, then reads
+ $this->assertEquals("0003\n", Psr7\readline($body));
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("0004\n", Psr7\readline($body));
+ $this->assertEquals("0005\n", Psr7\readline($body));
+
+ // Overwrite part of the cached body (so don't skip any bytes)
+ $body->seek(5);
+ $this->assertEquals(5, $body->write("ABCD\n"));
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("TEST\n", Psr7\readline($body));
+ $this->assertEquals("0003\n", Psr7\readline($body));
+ $this->assertEquals("0004\n", Psr7\readline($body));
+ $this->assertEquals("0005\n", Psr7\readline($body));
+ $this->assertEquals("0006\n", Psr7\readline($body));
+ $this->assertEquals(5, $body->write("1234\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+
+ // Seek to 0 and ensure the overwritten bit is replaced
+ $body->seek(0);
+ $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50));
+
+ // Ensure that casting it to a string does not include the bit that was overwritten
+ $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
+ }
+
+ public function testClosesBothStreams()
+ {
+ $s = fopen('php://temp', 'r');
+ $a = Psr7\stream_for($s);
+ $d = new CachingStream($a);
+ $d->close();
+ $this->assertFalse(is_resource($s));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsuresValidWhence()
+ {
+ $this->body->seek(10, -123456);
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/DroppingStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/DroppingStreamTest.php
new file mode 100755
index 0000000..1ae9443
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/DroppingStreamTest.php
@@ -0,0 +1,26 @@
+assertEquals(3, $drop->write('hel'));
+ $this->assertEquals(2, $drop->write('lo'));
+ $this->assertEquals(5, $drop->getSize());
+ $this->assertEquals('hello', $drop->read(5));
+ $this->assertEquals(0, $drop->getSize());
+ $drop->write('12345678910');
+ $this->assertEquals(5, $stream->getSize());
+ $this->assertEquals(5, $drop->getSize());
+ $this->assertEquals('12345', (string) $drop);
+ $this->assertEquals(0, $drop->getSize());
+ $drop->write('hello');
+ $this->assertSame(0, $drop->write('test'));
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/FnStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/FnStreamTest.php
new file mode 100755
index 0000000..def436c
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/FnStreamTest.php
@@ -0,0 +1,92 @@
+seek(1);
+ }
+
+ public function testProxiesToFunction()
+ {
+ $self = $this;
+ $s = new FnStream(array(
+ 'read' => function ($len) use ($self) {
+ $self->assertEquals(3, $len);
+ return 'foo';
+ }
+ ));
+
+ $this->assertEquals('foo', $s->read(3));
+ }
+
+ public function testCanCloseOnDestruct()
+ {
+ $called = false;
+ $s = new FnStream(array(
+ 'close' => function () use (&$called) {
+ $called = true;
+ }
+ ));
+ unset($s);
+ $this->assertTrue($called);
+ }
+
+ public function testDoesNotRequireClose()
+ {
+ $s = new FnStream(array());
+ unset($s);
+ }
+
+ public function testDecoratesStream()
+ {
+ $a = Psr7\stream_for('foo');
+ $b = FnStream::decorate($a, array());
+ $this->assertEquals(3, $b->getSize());
+ $this->assertEquals($b->isWritable(), true);
+ $this->assertEquals($b->isReadable(), true);
+ $this->assertEquals($b->isSeekable(), true);
+ $this->assertEquals($b->read(3), 'foo');
+ $this->assertEquals($b->tell(), 3);
+ $this->assertEquals($a->tell(), 3);
+ $this->assertSame('', $a->read(1));
+ $this->assertEquals($b->eof(), true);
+ $this->assertEquals($a->eof(), true);
+ $b->seek(0);
+ $this->assertEquals('foo', (string) $b);
+ $b->seek(0);
+ $this->assertEquals('foo', $b->getContents());
+ $this->assertEquals($a->getMetadata(), $b->getMetadata());
+ $b->seek(0, SEEK_END);
+ $b->write('bar');
+ $this->assertEquals('foobar', (string) $b);
+ $this->assertInternalType('resource', $b->detach());
+ $b->close();
+ }
+
+ public function testDecoratesWithCustomizations()
+ {
+ $called = false;
+ $a = Psr7\stream_for('foo');
+ $b = FnStream::decorate($a, array(
+ 'read' => function ($len) use (&$called, $a) {
+ $called = true;
+ return $a->read($len);
+ }
+ ));
+ $this->assertEquals('foo', $b->read(3));
+ $this->assertTrue($called);
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/FunctionsTest.php b/instafeed/vendor/ringcentral/psr7/tests/FunctionsTest.php
new file mode 100755
index 0000000..032fc56
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/FunctionsTest.php
@@ -0,0 +1,604 @@
+assertEquals('foobaz', Psr7\copy_to_string($s));
+ $s->seek(0);
+ $this->assertEquals('foo', Psr7\copy_to_string($s, 3));
+ $this->assertEquals('baz', Psr7\copy_to_string($s, 3));
+ $this->assertEquals('', Psr7\copy_to_string($s));
+ }
+
+ public function testCopiesToStringStopsWhenReadFails()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s1 = FnStream::decorate($s1, array(
+ 'read' => function () { return ''; }
+ ));
+ $result = Psr7\copy_to_string($s1);
+ $this->assertEquals('', $result);
+ }
+
+ public function testCopiesToStream()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s2 = Psr7\stream_for('');
+ Psr7\copy_to_stream($s1, $s2);
+ $this->assertEquals('foobaz', (string) $s2);
+ $s2 = Psr7\stream_for('');
+ $s1->seek(0);
+ Psr7\copy_to_stream($s1, $s2, 3);
+ $this->assertEquals('foo', (string) $s2);
+ Psr7\copy_to_stream($s1, $s2, 3);
+ $this->assertEquals('foobaz', (string) $s2);
+ }
+
+ public function testStopsCopyToStreamWhenWriteFails()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s2 = Psr7\stream_for('');
+ $s2 = FnStream::decorate($s2, array('write' => function () { return 0; }));
+ Psr7\copy_to_stream($s1, $s2);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testStopsCopyToSteamWhenWriteFailsWithMaxLen()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s2 = Psr7\stream_for('');
+ $s2 = FnStream::decorate($s2, array('write' => function () { return 0; }));
+ Psr7\copy_to_stream($s1, $s2, 10);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testStopsCopyToSteamWhenReadFailsWithMaxLen()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s1 = FnStream::decorate($s1, array('read' => function () { return ''; }));
+ $s2 = Psr7\stream_for('');
+ Psr7\copy_to_stream($s1, $s2, 10);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testReadsLines()
+ {
+ $s = Psr7\stream_for("foo\nbaz\nbar");
+ $this->assertEquals("foo\n", Psr7\readline($s));
+ $this->assertEquals("baz\n", Psr7\readline($s));
+ $this->assertEquals("bar", Psr7\readline($s));
+ }
+
+ public function testReadsLinesUpToMaxLength()
+ {
+ $s = Psr7\stream_for("12345\n");
+ $this->assertEquals("123", Psr7\readline($s, 4));
+ $this->assertEquals("45\n", Psr7\readline($s));
+ }
+
+ public function testReadsLineUntilFalseReturnedFromRead()
+ {
+ $s = $this->getMockBuilder('RingCentral\Psr7\Stream')
+ ->setMethods(array('read', 'eof'))
+ ->disableOriginalConstructor()
+ ->getMock();
+ $s->expects($this->exactly(2))
+ ->method('read')
+ ->will($this->returnCallback(function () {
+ static $c = false;
+ if ($c) {
+ return false;
+ }
+ $c = true;
+ return 'h';
+ }));
+ $s->expects($this->exactly(2))
+ ->method('eof')
+ ->will($this->returnValue(false));
+ $this->assertEquals("h", Psr7\readline($s));
+ }
+
+ public function testCalculatesHash()
+ {
+ $s = Psr7\stream_for('foobazbar');
+ $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testCalculatesHashThrowsWhenSeekFails()
+ {
+ $s = new NoSeekStream(Psr7\stream_for('foobazbar'));
+ $s->read(2);
+ Psr7\hash($s, 'md5');
+ }
+
+ public function testCalculatesHashSeeksToOriginalPosition()
+ {
+ $s = Psr7\stream_for('foobazbar');
+ $s->seek(4);
+ $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
+ $this->assertEquals(4, $s->tell());
+ }
+
+ public function testOpensFilesSuccessfully()
+ {
+ $r = Psr7\try_fopen(__FILE__, 'r');
+ $this->assertInternalType('resource', $r);
+ fclose($r);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r
+ */
+ public function testThrowsExceptionNotWarning()
+ {
+ Psr7\try_fopen('/path/to/does/not/exist', 'r');
+ }
+
+ public function parseQueryProvider()
+ {
+ return array(
+ // Does not need to parse when the string is empty
+ array('', array()),
+ // Can parse mult-values items
+ array('q=a&q=b', array('q' => array('a', 'b'))),
+ // Can parse multi-valued items that use numeric indices
+ array('q[0]=a&q[1]=b', array('q[0]' => 'a', 'q[1]' => 'b')),
+ // Can parse duplicates and does not include numeric indices
+ array('q[]=a&q[]=b', array('q[]' => array('a', 'b'))),
+ // Ensures that the value of "q" is an array even though one value
+ array('q[]=a', array('q[]' => 'a')),
+ // Does not modify "." to "_" like PHP's parse_str()
+ array('q.a=a&q.b=b', array('q.a' => 'a', 'q.b' => 'b')),
+ // Can decode %20 to " "
+ array('q%20a=a%20b', array('q a' => 'a b')),
+ // Can parse funky strings with no values by assigning each to null
+ array('q&a', array('q' => null, 'a' => null)),
+ // Does not strip trailing equal signs
+ array('data=abc=', array('data' => 'abc=')),
+ // Can store duplicates without affecting other values
+ array('foo=a&foo=b&?µ=c', array('foo' => array('a', 'b'), '?µ' => 'c')),
+ // Sets value to null when no "=" is present
+ array('foo', array('foo' => null)),
+ // Preserves "0" keys.
+ array('0', array('0' => null)),
+ // Sets the value to an empty string when "=" is present
+ array('0=', array('0' => '')),
+ // Preserves falsey keys
+ array('var=0', array('var' => '0')),
+ array('a[b][c]=1&a[b][c]=2', array('a[b][c]' => array('1', '2'))),
+ array('a[b]=c&a[d]=e', array('a[b]' => 'c', 'a[d]' => 'e')),
+ // Ensure it doesn't leave things behind with repeated values
+ // Can parse mult-values items
+ array('q=a&q=b&q=c', array('q' => array('a', 'b', 'c'))),
+ );
+ }
+
+ /**
+ * @dataProvider parseQueryProvider
+ */
+ public function testParsesQueries($input, $output)
+ {
+ $result = Psr7\parse_query($input);
+ $this->assertSame($output, $result);
+ }
+
+ public function testDoesNotDecode()
+ {
+ $str = 'foo%20=bar';
+ $data = Psr7\parse_query($str, false);
+ $this->assertEquals(array('foo%20' => 'bar'), $data);
+ }
+
+ /**
+ * @dataProvider parseQueryProvider
+ */
+ public function testParsesAndBuildsQueries($input, $output)
+ {
+ $result = Psr7\parse_query($input, false);
+ $this->assertSame($input, Psr7\build_query($result, false));
+ }
+
+ public function testEncodesWithRfc1738()
+ {
+ $str = Psr7\build_query(array('foo bar' => 'baz+'), PHP_QUERY_RFC1738);
+ $this->assertEquals('foo+bar=baz%2B', $str);
+ }
+
+ public function testEncodesWithRfc3986()
+ {
+ $str = Psr7\build_query(array('foo bar' => 'baz+'), PHP_QUERY_RFC3986);
+ $this->assertEquals('foo%20bar=baz%2B', $str);
+ }
+
+ public function testDoesNotEncode()
+ {
+ $str = Psr7\build_query(array('foo bar' => 'baz+'), false);
+ $this->assertEquals('foo bar=baz+', $str);
+ }
+
+ public function testCanControlDecodingType()
+ {
+ $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC3986);
+ $this->assertEquals('foo+bar', $result['var']);
+ $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC1738);
+ $this->assertEquals('foo bar', $result['var']);
+ }
+
+ public function testParsesRequestMessages()
+ {
+ $req = "GET /abc HTTP/1.0\r\nHost: foo.com\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('GET', $request->getMethod());
+ $this->assertEquals('/abc', $request->getRequestTarget());
+ $this->assertEquals('1.0', $request->getProtocolVersion());
+ $this->assertEquals('foo.com', $request->getHeaderLine('Host'));
+ $this->assertEquals('Bar', $request->getHeaderLine('Foo'));
+ $this->assertEquals('Bam, Qux', $request->getHeaderLine('Baz'));
+ $this->assertEquals('Test', (string) $request->getBody());
+ $this->assertEquals('http://foo.com/abc', (string) $request->getUri());
+ }
+
+ public function testParsesRequestMessagesWithHttpsScheme()
+ {
+ $req = "PUT /abc?baz=bar HTTP/1.1\r\nHost: foo.com:443\r\n\r\n";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('/abc?baz=bar', $request->getRequestTarget());
+ $this->assertEquals('1.1', $request->getProtocolVersion());
+ $this->assertEquals('foo.com:443', $request->getHeaderLine('Host'));
+ $this->assertEquals('', (string) $request->getBody());
+ $this->assertEquals('https://foo.com/abc?baz=bar', (string) $request->getUri());
+ }
+
+ public function testParsesRequestMessagesWithUriWhenHostIsNotFirst()
+ {
+ $req = "PUT / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('/', $request->getRequestTarget());
+ $this->assertEquals('http://foo.com/', (string) $request->getUri());
+ }
+
+ public function testParsesRequestMessagesWithFullUri()
+ {
+ $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('GET', $request->getMethod());
+ $this->assertEquals('https://www.google.com:443/search?q=foobar', $request->getRequestTarget());
+ $this->assertEquals('1.1', $request->getProtocolVersion());
+ $this->assertEquals('www.google.com', $request->getHeaderLine('Host'));
+ $this->assertEquals('', (string) $request->getBody());
+ $this->assertEquals('https://www.google.com/search?q=foobar', (string) $request->getUri());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesRequestMessages()
+ {
+ Psr7\parse_request("HTTP/1.1 200 OK\r\n\r\n");
+ }
+
+ public function testParsesResponseMessages()
+ {
+ $res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
+ $response = Psr7\parse_response($res);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals('OK', $response->getReasonPhrase());
+ $this->assertEquals('1.0', $response->getProtocolVersion());
+ $this->assertEquals('Bar', $response->getHeaderLine('Foo'));
+ $this->assertEquals('Bam, Qux', $response->getHeaderLine('Baz'));
+ $this->assertEquals('Test', (string) $response->getBody());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesResponseMessages()
+ {
+ Psr7\parse_response("GET / HTTP/1.1\r\n\r\n");
+ }
+
+ public function testDetermineMimetype()
+ {
+ $this->assertNull(Psr7\mimetype_from_extension('not-a-real-extension'));
+ $this->assertEquals(
+ 'application/json',
+ Psr7\mimetype_from_extension('json')
+ );
+ $this->assertEquals(
+ 'image/jpeg',
+ Psr7\mimetype_from_filename('/tmp/images/IMG034821.JPEG')
+ );
+ }
+
+ public function testCreatesUriForValue()
+ {
+ $this->assertInstanceOf('RingCentral\Psr7\Uri', Psr7\uri_for('/foo'));
+ $this->assertInstanceOf(
+ 'RingCentral\Psr7\Uri',
+ Psr7\uri_for(new Psr7\Uri('/foo'))
+ );
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesUri()
+ {
+ Psr7\uri_for(array());
+ }
+
+ public function testKeepsPositionOfResource()
+ {
+ $h = fopen(__FILE__, 'r');
+ fseek($h, 10);
+ $stream = Psr7\stream_for($h);
+ $this->assertEquals(10, $stream->tell());
+ $stream->close();
+ }
+
+ public function testCreatesWithFactory()
+ {
+ $stream = Psr7\stream_for('foo');
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $stream);
+ $this->assertEquals('foo', $stream->getContents());
+ $stream->close();
+ }
+
+ public function testFactoryCreatesFromEmptyString()
+ {
+ $s = Psr7\stream_for();
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ }
+
+ public function testFactoryCreatesFromNull()
+ {
+ $s = Psr7\stream_for(null);
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ }
+
+ public function testFactoryCreatesFromResource()
+ {
+ $r = fopen(__FILE__, 'r');
+ $s = Psr7\stream_for($r);
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ $this->assertSame(file_get_contents(__FILE__), (string) $s);
+ }
+
+ public function testFactoryCreatesFromObjectWithToString()
+ {
+ $r = new HasToString();
+ $s = Psr7\stream_for($r);
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ $this->assertEquals('foo', (string) $s);
+ }
+
+ public function testCreatePassesThrough()
+ {
+ $s = Psr7\stream_for('foo');
+ $this->assertSame($s, Psr7\stream_for($s));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testThrowsExceptionForUnknown()
+ {
+ Psr7\stream_for(new \stdClass());
+ }
+
+ public function testReturnsCustomMetadata()
+ {
+ $s = Psr7\stream_for('foo', array('metadata' => array('hwm' => 3)));
+ $this->assertEquals(3, $s->getMetadata('hwm'));
+ $this->assertArrayHasKey('hwm', $s->getMetadata());
+ }
+
+ public function testCanSetSize()
+ {
+ $s = Psr7\stream_for('', array('size' => 10));
+ $this->assertEquals(10, $s->getSize());
+ }
+
+ public function testCanCreateIteratorBasedStream()
+ {
+ $a = new \ArrayIterator(array('foo', 'bar', '123'));
+ $p = Psr7\stream_for($a);
+ $this->assertInstanceOf('RingCentral\Psr7\PumpStream', $p);
+ $this->assertEquals('foo', $p->read(3));
+ $this->assertFalse($p->eof());
+ $this->assertEquals('b', $p->read(1));
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals('r12', $p->read(3));
+ $this->assertFalse($p->eof());
+ $this->assertEquals('3', $p->getContents());
+ $this->assertTrue($p->eof());
+ $this->assertEquals(9, $p->tell());
+ }
+
+ public function testConvertsRequestsToStrings()
+ {
+ $request = new Psr7\Request('PUT', 'http://foo.com/hi?123', array(
+ 'Baz' => 'bar',
+ 'Qux' => ' ipsum'
+ ), 'hello', '1.0');
+ $this->assertEquals(
+ "PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
+ Psr7\str($request)
+ );
+ }
+
+ public function testConvertsResponsesToStrings()
+ {
+ $response = new Psr7\Response(200, array(
+ 'Baz' => 'bar',
+ 'Qux' => ' ipsum'
+ ), 'hello', '1.0', 'FOO');
+ $this->assertEquals(
+ "HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
+ Psr7\str($response)
+ );
+ }
+
+ public function parseParamsProvider()
+ {
+ $res1 = array(
+ array(
+ '',
+ 'rel' => 'front',
+ 'type' => 'image/jpeg',
+ ),
+ array(
+ '',
+ 'rel' => 'back',
+ 'type' => 'image/jpeg',
+ ),
+ );
+ return array(
+ array(
+ '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"',
+ $res1
+ ),
+ array(
+ '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"',
+ $res1
+ ),
+ array(
+ 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"',
+ array(
+ array('foo' => 'baz', 'bar' => '123'),
+ array('boo'),
+ array('test' => '123'),
+ array('foobar' => 'foo;bar')
+ )
+ ),
+ array(
+ '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"',
+ array(
+ array('', 'rel' => 'side', 'type' => 'image/jpeg'),
+ array('', 'rel' => 'side', 'type' => 'image/jpeg')
+ )
+ ),
+ array(
+ '',
+ array()
+ )
+ );
+ }
+ /**
+ * @dataProvider parseParamsProvider
+ */
+ public function testParseParams($header, $result)
+ {
+ $this->assertEquals($result, Psr7\parse_header($header));
+ }
+
+ public function testParsesArrayHeaders()
+ {
+ $header = array('a, b', 'c', 'd, e');
+ $this->assertEquals(array('a', 'b', 'c', 'd', 'e'), Psr7\normalize_header($header));
+ }
+
+ public function testRewindsBody()
+ {
+ $body = Psr7\stream_for('abc');
+ $res = new Psr7\Response(200, array(), $body);
+ Psr7\rewind_body($res);
+ $this->assertEquals(0, $body->tell());
+ $body->rewind(1);
+ Psr7\rewind_body($res);
+ $this->assertEquals(0, $body->tell());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testThrowsWhenBodyCannotBeRewound()
+ {
+ $body = Psr7\stream_for('abc');
+ $body->read(1);
+ $body = FnStream::decorate($body, array(
+ 'rewind' => function () { throw new \RuntimeException('a'); }
+ ));
+ $res = new Psr7\Response(200, array(), $body);
+ Psr7\rewind_body($res);
+ }
+
+ public function testCanModifyRequestWithUri()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com');
+ $r2 = Psr7\modify_request($r1, array(
+ 'uri' => new Psr7\Uri('http://www.foo.com')
+ ));
+ $this->assertEquals('http://www.foo.com', (string) $r2->getUri());
+ $this->assertEquals('www.foo.com', (string) $r2->getHeaderLine('host'));
+ }
+
+ public function testCanModifyRequestWithCaseInsensitiveHeader()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com', array('User-Agent' => 'foo'));
+ $r2 = Psr7\modify_request($r1, array('set_headers' => array('User-agent' => 'bar')));
+ $this->assertEquals('bar', $r2->getHeaderLine('User-Agent'));
+ $this->assertEquals('bar', $r2->getHeaderLine('User-agent'));
+ }
+
+ public function testReturnsAsIsWhenNoChanges()
+ {
+ $request = new Psr7\Request('GET', 'http://foo.com');
+ $this->assertSame($request, Psr7\modify_request($request, array()));
+ }
+
+ public function testReturnsUriAsIsWhenNoChanges()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com');
+ $r2 = Psr7\modify_request($r1, array('set_headers' => array('foo' => 'bar')));
+ $this->assertNotSame($r1, $r2);
+ $this->assertEquals('bar', $r2->getHeaderLine('foo'));
+ }
+
+ public function testRemovesHeadersFromMessage()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com', array('foo' => 'bar'));
+ $r2 = Psr7\modify_request($r1, array('remove_headers' => array('foo')));
+ $this->assertNotSame($r1, $r2);
+ $this->assertFalse($r2->hasHeader('foo'));
+ }
+
+ public function testAddsQueryToUri()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com');
+ $r2 = Psr7\modify_request($r1, array('query' => 'foo=bar'));
+ $this->assertNotSame($r1, $r2);
+ $this->assertEquals('foo=bar', $r2->getUri()->getQuery());
+ }
+
+ public function testServerRequestWithServerParams()
+ {
+ $requestString = "GET /abc HTTP/1.1\r\nHost: foo.com\r\n\r\n";
+ $request = Psr7\parse_server_request($requestString);
+
+ $this->assertEquals(array(), $request->getServerParams());
+ }
+
+ public function testServerRequestWithoutServerParams()
+ {
+ $requestString = "GET /abc HTTP/1.1\r\nHost: foo.com\r\n\r\n";
+ $serverParams = array('server_address' => '127.0.0.1', 'server_port' => 80);
+
+ $request = Psr7\parse_server_request($requestString, $serverParams);
+
+ $this->assertEquals(array('server_address' => '127.0.0.1', 'server_port' => 80), $request->getServerParams());
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/InflateStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/InflateStreamTest.php
new file mode 100755
index 0000000..cd699eb
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/InflateStreamTest.php
@@ -0,0 +1,21 @@
+assertEquals('test', (string) $b);
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/LazyOpenStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/LazyOpenStreamTest.php
new file mode 100755
index 0000000..ca0c18e
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/LazyOpenStreamTest.php
@@ -0,0 +1,64 @@
+fname = tempnam('/tmp', 'tfile');
+
+ if (file_exists($this->fname)) {
+ unlink($this->fname);
+ }
+ }
+
+ public function tearDown()
+ {
+ if (file_exists($this->fname)) {
+ unlink($this->fname);
+ }
+ }
+
+ public function testOpensLazily()
+ {
+ $l = new LazyOpenStream($this->fname, 'w+');
+ $l->write('foo');
+ $this->assertInternalType('array', $l->getMetadata());
+ $this->assertFileExists($this->fname);
+ $this->assertEquals('foo', file_get_contents($this->fname));
+ $this->assertEquals('foo', (string) $l);
+ }
+
+ public function testProxiesToFile()
+ {
+ file_put_contents($this->fname, 'foo');
+ $l = new LazyOpenStream($this->fname, 'r');
+ $this->assertEquals('foo', $l->read(4));
+ $this->assertTrue($l->eof());
+ $this->assertEquals(3, $l->tell());
+ $this->assertTrue($l->isReadable());
+ $this->assertTrue($l->isSeekable());
+ $this->assertFalse($l->isWritable());
+ $l->seek(1);
+ $this->assertEquals('oo', $l->getContents());
+ $this->assertEquals('foo', (string) $l);
+ $this->assertEquals(3, $l->getSize());
+ $this->assertInternalType('array', $l->getMetadata());
+ $l->close();
+ }
+
+ public function testDetachesUnderlyingStream()
+ {
+ file_put_contents($this->fname, 'foo');
+ $l = new LazyOpenStream($this->fname, 'r');
+ $r = $l->detach();
+ $this->assertInternalType('resource', $r);
+ fseek($r, 0);
+ $this->assertEquals('foo', stream_get_contents($r));
+ fclose($r);
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/LimitStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/LimitStreamTest.php
new file mode 100755
index 0000000..7053300
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/LimitStreamTest.php
@@ -0,0 +1,166 @@
+decorated = Psr7\stream_for(fopen(__FILE__, 'r'));
+ $this->body = new LimitStream($this->decorated, 10, 3);
+ }
+
+ public function testReturnsSubset()
+ {
+ $body = new LimitStream(Psr7\stream_for('foo'), -1, 1);
+ $this->assertEquals('oo', (string) $body);
+ $this->assertTrue($body->eof());
+ $body->seek(0);
+ $this->assertFalse($body->eof());
+ $this->assertEquals('oo', $body->read(100));
+ $this->assertSame('', $body->read(1));
+ $this->assertTrue($body->eof());
+ }
+
+ public function testReturnsSubsetWhenCastToString()
+ {
+ $body = Psr7\stream_for('foo_baz_bar');
+ $limited = new LimitStream($body, 3, 4);
+ $this->assertEquals('baz', (string) $limited);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to seek to stream position 10 with whence 0
+ */
+ public function testEnsuresPositionCanBeekSeekedTo()
+ {
+ new LimitStream(Psr7\stream_for(''), 0, 10);
+ }
+
+ public function testReturnsSubsetOfEmptyBodyWhenCastToString()
+ {
+ $body = Psr7\stream_for('01234567891234');
+ $limited = new LimitStream($body, 0, 10);
+ $this->assertEquals('', (string) $limited);
+ }
+
+ public function testReturnsSpecificSubsetOBodyWhenCastToString()
+ {
+ $body = Psr7\stream_for('0123456789abcdef');
+ $limited = new LimitStream($body, 3, 10);
+ $this->assertEquals('abc', (string) $limited);
+ }
+
+ public function testSeeksWhenConstructed()
+ {
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ }
+
+ public function testAllowsBoundedSeek()
+ {
+ $this->body->seek(100);
+ $this->assertEquals(10, $this->body->tell());
+ $this->assertEquals(13, $this->decorated->tell());
+ $this->body->seek(0);
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ try {
+ $this->body->seek(-10);
+ $this->fail();
+ } catch (\RuntimeException $e) {}
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ $this->body->seek(5);
+ $this->assertEquals(5, $this->body->tell());
+ $this->assertEquals(8, $this->decorated->tell());
+ // Fail
+ try {
+ $this->body->seek(1000, SEEK_END);
+ $this->fail();
+ } catch (\RuntimeException $e) {}
+ }
+
+ public function testReadsOnlySubsetOfData()
+ {
+ $data = $this->body->read(100);
+ $this->assertEquals(10, strlen($data));
+ $this->assertSame('', $this->body->read(1000));
+
+ $this->body->setOffset(10);
+ $newData = $this->body->read(100);
+ $this->assertEquals(10, strlen($newData));
+ $this->assertNotSame($data, $newData);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Could not seek to stream offset 2
+ */
+ public function testThrowsWhenCurrentGreaterThanOffsetSeek()
+ {
+ $a = Psr7\stream_for('foo_bar');
+ $b = new NoSeekStream($a);
+ $c = new LimitStream($b);
+ $a->getContents();
+ $c->setOffset(2);
+ }
+
+ public function testCanGetContentsWithoutSeeking()
+ {
+ $a = Psr7\stream_for('foo_bar');
+ $b = new NoSeekStream($a);
+ $c = new LimitStream($b);
+ $this->assertEquals('foo_bar', $c->getContents());
+ }
+
+ public function testClaimsConsumedWhenReadLimitIsReached()
+ {
+ $this->assertFalse($this->body->eof());
+ $this->body->read(1000);
+ $this->assertTrue($this->body->eof());
+ }
+
+ public function testContentLengthIsBounded()
+ {
+ $this->assertEquals(10, $this->body->getSize());
+ }
+
+ public function testGetContentsIsBasedOnSubset()
+ {
+ $body = new LimitStream(Psr7\stream_for('foobazbar'), 3, 3);
+ $this->assertEquals('baz', $body->getContents());
+ }
+
+ public function testReturnsNullIfSizeCannotBeDetermined()
+ {
+ $a = new FnStream(array(
+ 'getSize' => function () { return null; },
+ 'tell' => function () { return 0; },
+ ));
+ $b = new LimitStream($a);
+ $this->assertNull($b->getSize());
+ }
+
+ public function testLengthLessOffsetWhenNoLimitSize()
+ {
+ $a = Psr7\stream_for('foo_bar');
+ $b = new LimitStream($a, -1, 4);
+ $this->assertEquals(3, $b->getSize());
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/MultipartStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/MultipartStreamTest.php
new file mode 100755
index 0000000..22edea4
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/MultipartStreamTest.php
@@ -0,0 +1,214 @@
+assertNotEmpty($b->getBoundary());
+ }
+
+ public function testCanProvideBoundary()
+ {
+ $b = new MultipartStream(array(), 'foo');
+ $this->assertEquals('foo', $b->getBoundary());
+ }
+
+ public function testIsNotWritable()
+ {
+ $b = new MultipartStream();
+ $this->assertFalse($b->isWritable());
+ }
+
+ public function testCanCreateEmptyStream()
+ {
+ $b = new MultipartStream();
+ $boundary = $b->getBoundary();
+ $this->assertSame("--{$boundary}--\r\n", $b->getContents());
+ $this->assertSame(strlen($boundary) + 6, $b->getSize());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesFilesArrayElement()
+ {
+ new MultipartStream(array(array('foo' => 'bar')));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsuresFileHasName()
+ {
+ new MultipartStream(array(array('contents' => 'bar')));
+ }
+
+ public function testSerializesFields()
+ {
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => 'bar'
+ ),
+ array(
+ 'name' => 'baz',
+ 'contents' => 'bam'
+ )
+ ), 'boundary');
+ $this->assertEquals(
+ "--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\nContent-Length: 3\r\n\r\n"
+ . "bar\r\n--boundary\r\nContent-Disposition: form-data; name=\"baz\"\r\nContent-Length: 3"
+ . "\r\n\r\nbam\r\n--boundary--\r\n", (string) $b);
+ }
+
+ public function testSerializesFiles()
+ {
+ $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.txt';
+ }
+ ));
+
+ $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), array(
+ 'getMetadata' => function () {
+ return '/foo/baz.jpg';
+ }
+ ));
+
+ $f3 = Psr7\FnStream::decorate(Psr7\stream_for('bar'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.gif';
+ }
+ ));
+
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => $f1
+ ),
+ array(
+ 'name' => 'qux',
+ 'contents' => $f2
+ ),
+ array(
+ 'name' => 'qux',
+ 'contents' => $f3
+ ),
+ ), 'boundary');
+
+ $expected = <<assertEquals($expected, str_replace("\r", '', $b));
+ }
+
+ public function testSerializesFilesWithCustomHeaders()
+ {
+ $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.txt';
+ }
+ ));
+
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => $f1,
+ 'headers' => array(
+ 'x-foo' => 'bar',
+ 'content-disposition' => 'custom'
+ )
+ )
+ ), 'boundary');
+
+ $expected = <<assertEquals($expected, str_replace("\r", '', $b));
+ }
+
+ public function testSerializesFilesWithCustomHeadersAndMultipleValues()
+ {
+ $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.txt';
+ }
+ ));
+
+ $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), array(
+ 'getMetadata' => function () {
+ return '/foo/baz.jpg';
+ }
+ ));
+
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => $f1,
+ 'headers' => array(
+ 'x-foo' => 'bar',
+ 'content-disposition' => 'custom'
+ )
+ ),
+ array(
+ 'name' => 'foo',
+ 'contents' => $f2,
+ 'headers' => array('cOntenT-Type' => 'custom'),
+ )
+ ), 'boundary');
+
+ $expected = <<assertEquals($expected, str_replace("\r", '', $b));
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/NoSeekStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/NoSeekStreamTest.php
new file mode 100755
index 0000000..a831789
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/NoSeekStreamTest.php
@@ -0,0 +1,40 @@
+getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isSeekable', 'seek'))
+ ->getMockForAbstractClass();
+ $s->expects($this->never())->method('seek');
+ $s->expects($this->never())->method('isSeekable');
+ $wrapped = new NoSeekStream($s);
+ $this->assertFalse($wrapped->isSeekable());
+ $wrapped->seek(2);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Cannot write to a non-writable stream
+ */
+ public function testHandlesClose()
+ {
+ $s = Psr7\stream_for('foo');
+ $wrapped = new NoSeekStream($s);
+ $wrapped->close();
+ $wrapped->write('foo');
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/PumpStreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/PumpStreamTest.php
new file mode 100755
index 0000000..6b146e1
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/PumpStreamTest.php
@@ -0,0 +1,72 @@
+ array('foo' => 'bar'),
+ 'size' => 100
+ ));
+
+ $this->assertEquals('bar', $p->getMetadata('foo'));
+ $this->assertEquals(array('foo' => 'bar'), $p->getMetadata());
+ $this->assertEquals(100, $p->getSize());
+ }
+
+ public function testCanReadFromCallable()
+ {
+ $p = Psr7\stream_for(function ($size) {
+ return 'a';
+ });
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals(1, $p->tell());
+ $this->assertEquals('aaaaa', $p->read(5));
+ $this->assertEquals(6, $p->tell());
+ }
+
+ public function testStoresExcessDataInBuffer()
+ {
+ $called = array();
+ $p = Psr7\stream_for(function ($size) use (&$called) {
+ $called[] = $size;
+ return 'abcdef';
+ });
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals('b', $p->read(1));
+ $this->assertEquals('cdef', $p->read(4));
+ $this->assertEquals('abcdefabc', $p->read(9));
+ $this->assertEquals(array(1, 9, 3), $called);
+ }
+
+ public function testInifiniteStreamWrappedInLimitStream()
+ {
+ $p = Psr7\stream_for(function () { return 'a'; });
+ $s = new LimitStream($p, 5);
+ $this->assertEquals('aaaaa', (string) $s);
+ }
+
+ public function testDescribesCapabilities()
+ {
+ $p = Psr7\stream_for(function () {});
+ $this->assertTrue($p->isReadable());
+ $this->assertFalse($p->isSeekable());
+ $this->assertFalse($p->isWritable());
+ $this->assertNull($p->getSize());
+ $this->assertEquals('', $p->getContents());
+ $this->assertEquals('', (string) $p);
+ $p->close();
+ $this->assertEquals('', $p->read(10));
+ $this->assertTrue($p->eof());
+
+ try {
+ $this->assertFalse($p->write('aa'));
+ $this->fail();
+ } catch (\RuntimeException $e) {}
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/RequestTest.php b/instafeed/vendor/ringcentral/psr7/tests/RequestTest.php
new file mode 100755
index 0000000..ad6f0cb
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/RequestTest.php
@@ -0,0 +1,157 @@
+assertEquals('/', (string) $r->getUri());
+ }
+
+ public function testRequestUriMayBeUri()
+ {
+ $uri = new Uri('/');
+ $r = new Request('GET', $uri);
+ $this->assertSame($uri, $r->getUri());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidateRequestUri()
+ {
+ new Request('GET', true);
+ }
+
+ public function testCanConstructWithBody()
+ {
+ $r = new Request('GET', '/', array(), 'baz');
+ $this->assertEquals('baz', (string) $r->getBody());
+ }
+
+ public function testCapitalizesMethod()
+ {
+ $r = new Request('get', '/');
+ $this->assertEquals('GET', $r->getMethod());
+ }
+
+ public function testCapitalizesWithMethod()
+ {
+ $r = new Request('GET', '/');
+ $this->assertEquals('PUT', $r->withMethod('put')->getMethod());
+ }
+
+ public function testWithUri()
+ {
+ $r1 = new Request('GET', '/');
+ $u1 = $r1->getUri();
+ $u2 = new Uri('http://www.example.com');
+ $r2 = $r1->withUri($u2);
+ $this->assertNotSame($r1, $r2);
+ $this->assertSame($u2, $r2->getUri());
+ $this->assertSame($u1, $r1->getUri());
+ }
+
+ public function testSameInstanceWhenSameUri()
+ {
+ $r1 = new Request('GET', 'http://foo.com');
+ $r2 = $r1->withUri($r1->getUri());
+ $this->assertSame($r1, $r2);
+ }
+
+ public function testWithRequestTarget()
+ {
+ $r1 = new Request('GET', '/');
+ $r2 = $r1->withRequestTarget('*');
+ $this->assertEquals('*', $r2->getRequestTarget());
+ $this->assertEquals('/', $r1->getRequestTarget());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testRequestTargetDoesNotAllowSpaces()
+ {
+ $r1 = new Request('GET', '/');
+ $r1->withRequestTarget('/foo bar');
+ }
+
+ public function testRequestTargetDefaultsToSlash()
+ {
+ $r1 = new Request('GET', '');
+ $this->assertEquals('/', $r1->getRequestTarget());
+ $r2 = new Request('GET', '*');
+ $this->assertEquals('*', $r2->getRequestTarget());
+ $r3 = new Request('GET', 'http://foo.com/bar baz/');
+ $this->assertEquals('/bar%20baz/', $r3->getRequestTarget());
+ }
+
+ public function testBuildsRequestTarget()
+ {
+ $r1 = new Request('GET', 'http://foo.com/baz?bar=bam');
+ $this->assertEquals('/baz?bar=bam', $r1->getRequestTarget());
+ }
+
+ public function testHostIsAddedFirst()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam', array('Foo' => 'Bar'));
+ $this->assertEquals(array(
+ 'Host' => array('foo.com'),
+ 'Foo' => array('Bar')
+ ), $r->getHeaders());
+ }
+
+ public function testCanGetHeaderAsCsv()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam', array(
+ 'Foo' => array('a', 'b', 'c')
+ ));
+ $this->assertEquals('a, b, c', $r->getHeaderLine('Foo'));
+ $this->assertEquals('', $r->getHeaderLine('Bar'));
+ }
+
+ public function testHostIsNotOverwrittenWhenPreservingHost()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam', array('Host' => 'a.com'));
+ $this->assertEquals(array('Host' => array('a.com')), $r->getHeaders());
+ $r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true);
+ $this->assertEquals('a.com', $r2->getHeaderLine('Host'));
+ }
+
+ public function testOverridesHostWithUri()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam');
+ $this->assertEquals(array('Host' => array('foo.com')), $r->getHeaders());
+ $r2 = $r->withUri(new Uri('http://www.baz.com/bar'));
+ $this->assertEquals('www.baz.com', $r2->getHeaderLine('Host'));
+ }
+
+ public function testAggregatesHeaders()
+ {
+ $r = new Request('GET', 'http://foo.com', array(
+ 'ZOO' => 'zoobar',
+ 'zoo' => array('foobar', 'zoobar')
+ ));
+ $this->assertEquals('zoobar, foobar, zoobar', $r->getHeaderLine('zoo'));
+ }
+
+ public function testAddsPortToHeader()
+ {
+ $r = new Request('GET', 'http://foo.com:8124/bar');
+ $this->assertEquals('foo.com:8124', $r->getHeaderLine('host'));
+ }
+
+ public function testAddsPortToHeaderAndReplacePreviousPort()
+ {
+ $r = new Request('GET', 'http://foo.com:8124/bar');
+ $r = $r->withUri(new Uri('http://foo.com:8125/bar'));
+ $this->assertEquals('foo.com:8125', $r->getHeaderLine('host'));
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/ResponseTest.php b/instafeed/vendor/ringcentral/psr7/tests/ResponseTest.php
new file mode 100755
index 0000000..52b7ba1
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/ResponseTest.php
@@ -0,0 +1,154 @@
+assertSame(200, $r->getStatusCode());
+ $this->assertEquals('OK', $r->getReasonPhrase());
+ }
+
+ public function testCanGiveCustomReason()
+ {
+ $r = new Response(200, array(), null, '1.1', 'bar');
+ $this->assertEquals('bar', $r->getReasonPhrase());
+ }
+
+ public function testCanGiveCustomProtocolVersion()
+ {
+ $r = new Response(200, array(), null, '1000');
+ $this->assertEquals('1000', $r->getProtocolVersion());
+ }
+
+ public function testCanCreateNewResponseWithStatusAndNoReason()
+ {
+ $r = new Response(200);
+ $r2 = $r->withStatus(201);
+ $this->assertEquals(200, $r->getStatusCode());
+ $this->assertEquals('OK', $r->getReasonPhrase());
+ $this->assertEquals(201, $r2->getStatusCode());
+ $this->assertEquals('Created', $r2->getReasonPhrase());
+ }
+
+ public function testCanCreateNewResponseWithStatusAndReason()
+ {
+ $r = new Response(200);
+ $r2 = $r->withStatus(201, 'Foo');
+ $this->assertEquals(200, $r->getStatusCode());
+ $this->assertEquals('OK', $r->getReasonPhrase());
+ $this->assertEquals(201, $r2->getStatusCode());
+ $this->assertEquals('Foo', $r2->getReasonPhrase());
+ }
+
+ public function testCreatesResponseWithAddedHeaderArray()
+ {
+ $r = new Response();
+ $r2 = $r->withAddedHeader('foo', array('baz', 'bar'));
+ $this->assertFalse($r->hasHeader('foo'));
+ $this->assertEquals('baz, bar', $r2->getHeaderLine('foo'));
+ }
+
+ public function testReturnsIdentityWhenRemovingMissingHeader()
+ {
+ $r = new Response();
+ $this->assertSame($r, $r->withoutHeader('foo'));
+ }
+
+ public function testAlwaysReturnsBody()
+ {
+ $r = new Response();
+ $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
+ }
+
+ public function testCanSetHeaderAsArray()
+ {
+ $r = new Response(200, array(
+ 'foo' => array('baz ', ' bar ')
+ ));
+ $this->assertEquals('baz, bar', $r->getHeaderLine('foo'));
+ $this->assertEquals(array('baz', 'bar'), $r->getHeader('foo'));
+ }
+
+ public function testSameInstanceWhenSameBody()
+ {
+ $r = new Response(200, array(), 'foo');
+ $b = $r->getBody();
+ $this->assertSame($r, $r->withBody($b));
+ }
+
+ public function testNewInstanceWhenNewBody()
+ {
+ $r = new Response(200, array(), 'foo');
+ $b2 = Psr7\stream_for('abc');
+ $this->assertNotSame($r, $r->withBody($b2));
+ }
+
+ public function testSameInstanceWhenSameProtocol()
+ {
+ $r = new Response(200);
+ $this->assertSame($r, $r->withProtocolVersion('1.1'));
+ }
+
+ public function testNewInstanceWhenNewProtocol()
+ {
+ $r = new Response(200);
+ $this->assertNotSame($r, $r->withProtocolVersion('1.0'));
+ }
+
+ public function testNewInstanceWhenRemovingHeader()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withoutHeader('Foo');
+ $this->assertNotSame($r, $r2);
+ $this->assertFalse($r2->hasHeader('foo'));
+ }
+
+ public function testNewInstanceWhenAddingHeader()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withAddedHeader('Foo', 'Baz');
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals('Bar, Baz', $r2->getHeaderLine('foo'));
+ }
+
+ public function testNewInstanceWhenAddingHeaderArray()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withAddedHeader('Foo', array('Baz', 'Qux'));
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals(array('Bar', 'Baz', 'Qux'), $r2->getHeader('foo'));
+ }
+
+ public function testNewInstanceWhenAddingHeaderThatWasNotThereBefore()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withAddedHeader('Baz', 'Bam');
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals('Bam', $r2->getHeaderLine('Baz'));
+ $this->assertEquals('Bar', $r2->getHeaderLine('Foo'));
+ }
+
+ public function testRemovesPreviouslyAddedHeaderOfDifferentCase()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withHeader('foo', 'Bam');
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals('Bam', $r2->getHeaderLine('Foo'));
+ }
+
+ public function testBodyConsistent()
+ {
+ $r = new Response(200, array(), '0');
+ $this->assertEquals('0', (string)$r->getBody());
+ }
+
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/ServerRequestTest.php b/instafeed/vendor/ringcentral/psr7/tests/ServerRequestTest.php
new file mode 100755
index 0000000..4bdfe8e
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/ServerRequestTest.php
@@ -0,0 +1,85 @@
+request = new ServerRequest('GET', 'http://localhost');
+ }
+
+ public function testGetNoAttributes()
+ {
+ $this->assertEquals(array(), $this->request->getAttributes());
+ }
+
+ public function testWithAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('hello' => 'world'), $request->getAttributes());
+ }
+
+ public function testGetAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals('world', $request->getAttribute('hello'));
+ }
+
+ public function testGetDefaultAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(null, $request->getAttribute('hi', null));
+ }
+
+ public function testWithoutAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+ $request = $request->withAttribute('test', 'nice');
+
+ $request = $request->withoutAttribute('hello');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'nice'), $request->getAttributes());
+ }
+
+ public function testWithCookieParams()
+ {
+ $request = $this->request->withCookieParams(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getCookieParams());
+ }
+
+ public function testWithQueryParams()
+ {
+ $request = $this->request->withQueryParams(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getQueryParams());
+ }
+
+ public function testWithUploadedFiles()
+ {
+ $request = $this->request->withUploadedFiles(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getUploadedFiles());
+ }
+
+ public function testWithParsedBody()
+ {
+ $request = $this->request->withParsedBody(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getParsedBody());
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/StreamDecoratorTraitTest.php b/instafeed/vendor/ringcentral/psr7/tests/StreamDecoratorTraitTest.php
new file mode 100755
index 0000000..b0c3dc5
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/StreamDecoratorTraitTest.php
@@ -0,0 +1,123 @@
+c = fopen('php://temp', 'r+');
+ fwrite($this->c, 'foo');
+ fseek($this->c, 0);
+ $this->a = Psr7\stream_for($this->c);
+ $this->b = new Str($this->a);
+ }
+
+ public function testCatchesExceptionsWhenCastingToString()
+ {
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('read'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('read')
+ ->will($this->throwException(new \Exception('foo')));
+ $msg = '';
+ set_error_handler(function ($errNo, $str) use (&$msg) {
+ $msg = $str;
+ });
+ echo new Str($s);
+ restore_error_handler();
+ $this->assertContains('foo', $msg);
+ }
+
+ public function testToString()
+ {
+ $this->assertEquals('foo', (string)$this->b);
+ }
+
+ public function testHasSize()
+ {
+ $this->assertEquals(3, $this->b->getSize());
+ }
+
+ public function testReads()
+ {
+ $this->assertEquals('foo', $this->b->read(10));
+ }
+
+ public function testCheckMethods()
+ {
+ $this->assertEquals($this->a->isReadable(), $this->b->isReadable());
+ $this->assertEquals($this->a->isWritable(), $this->b->isWritable());
+ $this->assertEquals($this->a->isSeekable(), $this->b->isSeekable());
+ }
+
+ public function testSeeksAndTells()
+ {
+ $this->b->seek(1);
+ $this->assertEquals(1, $this->a->tell());
+ $this->assertEquals(1, $this->b->tell());
+ $this->b->seek(0);
+ $this->assertEquals(0, $this->a->tell());
+ $this->assertEquals(0, $this->b->tell());
+ $this->b->seek(0, SEEK_END);
+ $this->assertEquals(3, $this->a->tell());
+ $this->assertEquals(3, $this->b->tell());
+ }
+
+ public function testGetsContents()
+ {
+ $this->assertEquals('foo', $this->b->getContents());
+ $this->assertEquals('', $this->b->getContents());
+ $this->b->seek(1);
+ $this->assertEquals('oo', $this->b->getContents(1));
+ }
+
+ public function testCloses()
+ {
+ $this->b->close();
+ $this->assertFalse(is_resource($this->c));
+ }
+
+ public function testDetaches()
+ {
+ $this->b->detach();
+ $this->assertFalse($this->b->isReadable());
+ }
+
+ public function testWrapsMetadata()
+ {
+ $this->assertSame($this->b->getMetadata(), $this->a->getMetadata());
+ $this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri'));
+ }
+
+ public function testWrapsWrites()
+ {
+ $this->b->seek(0, SEEK_END);
+ $this->b->write('foo');
+ $this->assertEquals('foofoo', (string)$this->a);
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ */
+ public function testThrowsWithInvalidGetter()
+ {
+ $this->b->foo;
+ }
+
+}
\ No newline at end of file
diff --git a/instafeed/vendor/ringcentral/psr7/tests/StreamTest.php b/instafeed/vendor/ringcentral/psr7/tests/StreamTest.php
new file mode 100755
index 0000000..5501805
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/StreamTest.php
@@ -0,0 +1,163 @@
+assertTrue($stream->isReadable());
+ $this->assertTrue($stream->isWritable());
+ $this->assertTrue($stream->isSeekable());
+ $this->assertEquals('php://temp', $stream->getMetadata('uri'));
+ $this->assertInternalType('array', $stream->getMetadata());
+ $this->assertEquals(4, $stream->getSize());
+ $this->assertFalse($stream->eof());
+ $stream->close();
+ }
+
+ public function testStreamClosesHandleOnDestruct()
+ {
+ $handle = fopen('php://temp', 'r');
+ $stream = new Stream($handle);
+ unset($stream);
+ $this->assertFalse(is_resource($handle));
+ }
+
+ public function testConvertsToString()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals('data', (string) $stream);
+ $this->assertEquals('data', (string) $stream);
+ $stream->close();
+ }
+
+ public function testGetsContents()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals('', $stream->getContents());
+ $stream->seek(0);
+ $this->assertEquals('data', $stream->getContents());
+ $this->assertEquals('', $stream->getContents());
+ }
+
+ public function testChecksEof()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertFalse($stream->eof());
+ $stream->read(4);
+ $this->assertTrue($stream->eof());
+ $stream->close();
+ }
+
+ public function testGetSize()
+ {
+ $size = filesize(__FILE__);
+ $handle = fopen(__FILE__, 'r');
+ $stream = new Stream($handle);
+ $this->assertEquals($size, $stream->getSize());
+ // Load from cache
+ $this->assertEquals($size, $stream->getSize());
+ $stream->close();
+ }
+
+ public function testEnsuresSizeIsConsistent()
+ {
+ $h = fopen('php://temp', 'w+');
+ $this->assertEquals(3, fwrite($h, 'foo'));
+ $stream = new Stream($h);
+ $this->assertEquals(3, $stream->getSize());
+ $this->assertEquals(4, $stream->write('test'));
+ $this->assertEquals(7, $stream->getSize());
+ $this->assertEquals(7, $stream->getSize());
+ $stream->close();
+ }
+
+ public function testProvidesStreamPosition()
+ {
+ $handle = fopen('php://temp', 'w+');
+ $stream = new Stream($handle);
+ $this->assertEquals(0, $stream->tell());
+ $stream->write('foo');
+ $this->assertEquals(3, $stream->tell());
+ $stream->seek(1);
+ $this->assertEquals(1, $stream->tell());
+ $this->assertSame(ftell($handle), $stream->tell());
+ $stream->close();
+ }
+
+ public function testCanDetachStream()
+ {
+ $r = fopen('php://temp', 'w+');
+ $stream = new Stream($r);
+ $stream->write('foo');
+ $this->assertTrue($stream->isReadable());
+ $this->assertSame($r, $stream->detach());
+ $stream->detach();
+
+ $this->assertFalse($stream->isReadable());
+ $this->assertFalse($stream->isWritable());
+ $this->assertFalse($stream->isSeekable());
+
+ $self = $this;
+
+ $throws = function ($fn) use ($stream, $self) {
+ try {
+ $fn($stream);
+ $self->fail();
+ } catch (\Exception $e) {}
+ };
+
+ $throws(function ($stream) { $stream->read(10); });
+ $throws(function ($stream) { $stream->write('bar'); });
+ $throws(function ($stream) { $stream->seek(10); });
+ $throws(function ($stream) { $stream->tell(); });
+ $throws(function ($stream) { $stream->eof(); });
+ $throws(function ($stream) { $stream->getSize(); });
+ $throws(function ($stream) { $stream->getContents(); });
+ $this->assertSame('', (string) $stream);
+ $stream->close();
+ }
+
+ public function testCloseClearProperties()
+ {
+ $handle = fopen('php://temp', 'r+');
+ $stream = new Stream($handle);
+ $stream->close();
+
+ $this->assertFalse($stream->isSeekable());
+ $this->assertFalse($stream->isReadable());
+ $this->assertFalse($stream->isWritable());
+ $this->assertNull($stream->getSize());
+ $this->assertEmpty($stream->getMetadata());
+ }
+
+ public function testDoesNotThrowInToString()
+ {
+ $s = \RingCentral\Psr7\stream_for('foo');
+ $s = new NoSeekStream($s);
+ $this->assertEquals('foo', (string) $s);
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/StreamWrapperTest.php b/instafeed/vendor/ringcentral/psr7/tests/StreamWrapperTest.php
new file mode 100755
index 0000000..dd08a83
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/StreamWrapperTest.php
@@ -0,0 +1,100 @@
+assertSame('foo', fread($handle, 3));
+ $this->assertSame(3, ftell($handle));
+ $this->assertSame(3, fwrite($handle, 'bar'));
+ $this->assertSame(0, fseek($handle, 0));
+ $this->assertSame('foobar', fread($handle, 6));
+ $this->assertSame('', fread($handle, 1));
+ $this->assertTrue(feof($handle));
+
+ // This fails on HHVM for some reason
+ if (!defined('HHVM_VERSION')) {
+ $this->assertEquals(array(
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 33206,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 6,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ 0 => 0,
+ 1 => 0,
+ 2 => 33206,
+ 3 => 0,
+ 4 => 0,
+ 5 => 0,
+ 6 => 0,
+ 7 => 6,
+ 8 => 0,
+ 9 => 0,
+ 10 => 0,
+ 11 => 0,
+ 12 => 0,
+ ), fstat($handle));
+ }
+
+ $this->assertTrue(fclose($handle));
+ $this->assertSame('foobar', (string) $stream);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesStream()
+ {
+ $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable', 'isWritable'))
+ ->getMockForAbstractClass();
+ $stream->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $stream->expects($this->once())
+ ->method('isWritable')
+ ->will($this->returnValue(false));
+ StreamWrapper::getResource($stream);
+ }
+
+ /**
+ * @expectedException \PHPUnit_Framework_Error_Warning
+ */
+ public function testReturnsFalseWhenStreamDoesNotExist()
+ {
+ fopen('guzzle://foo', 'r');
+ }
+
+ public function testCanOpenReadonlyStream()
+ {
+ $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable', 'isWritable'))
+ ->getMockForAbstractClass();
+ $stream->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $stream->expects($this->once())
+ ->method('isWritable')
+ ->will($this->returnValue(true));
+ $r = StreamWrapper::getResource($stream);
+ $this->assertInternalType('resource', $r);
+ fclose($r);
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/UriTest.php b/instafeed/vendor/ringcentral/psr7/tests/UriTest.php
new file mode 100755
index 0000000..b53c97e
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/UriTest.php
@@ -0,0 +1,258 @@
+assertEquals(
+ 'https://michael:test@test.com/path/123?q=abc#test',
+ (string) $uri
+ );
+
+ $this->assertEquals('test', $uri->getFragment());
+ $this->assertEquals('test.com', $uri->getHost());
+ $this->assertEquals('/path/123', $uri->getPath());
+ $this->assertEquals(null, $uri->getPort());
+ $this->assertEquals('q=abc', $uri->getQuery());
+ $this->assertEquals('https', $uri->getScheme());
+ $this->assertEquals('michael:test', $uri->getUserInfo());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Unable to parse URI
+ */
+ public function testValidatesUriCanBeParsed()
+ {
+ // Due to 5.4.7 "Fixed host recognition when scheme is omitted and a leading component separator is present" this does not work in 5.3
+ //new Uri('///');
+ throw new \InvalidArgumentException('Unable to parse URI');
+ }
+
+ public function testCanTransformAndRetrievePartsIndividually()
+ {
+ $uri = new Uri('');
+ $uri = $uri->withFragment('#test')
+ ->withHost('example.com')
+ ->withPath('path/123')
+ ->withPort(8080)
+ ->withQuery('?q=abc')
+ ->withScheme('http')
+ ->withUserInfo('user', 'pass');
+
+ // Test getters.
+ $this->assertEquals('user:pass@example.com:8080', $uri->getAuthority());
+ $this->assertEquals('test', $uri->getFragment());
+ $this->assertEquals('example.com', $uri->getHost());
+ $this->assertEquals('path/123', $uri->getPath());
+ $this->assertEquals(8080, $uri->getPort());
+ $this->assertEquals('q=abc', $uri->getQuery());
+ $this->assertEquals('http', $uri->getScheme());
+ $this->assertEquals('user:pass', $uri->getUserInfo());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testPortMustBeValid()
+ {
+ $uri = new Uri('');
+ $uri->withPort(100000);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testPathMustBeValid()
+ {
+ $uri = new Uri('');
+ $uri->withPath(array());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testQueryMustBeValid()
+ {
+ $uri = new Uri('');
+ $uri->withQuery(new \stdClass);
+ }
+
+ public function testAllowsFalseyUrlParts()
+ {
+ $url = new Uri('http://a:1/0?0#0');
+ $this->assertSame('a', $url->getHost());
+ $this->assertEquals(1, $url->getPort());
+ $this->assertSame('/0', $url->getPath());
+ $this->assertEquals('0', (string) $url->getQuery());
+ $this->assertSame('0', $url->getFragment());
+ $this->assertEquals('http://a:1/0?0#0', (string) $url);
+ $url = new Uri('');
+ $this->assertSame('', (string) $url);
+ $url = new Uri('0');
+ $this->assertSame('0', (string) $url);
+ $url = new Uri('/');
+ $this->assertSame('/', (string) $url);
+ }
+
+ /**
+ * @dataProvider getResolveTestCases
+ */
+ public function testResolvesUris($base, $rel, $expected)
+ {
+ $uri = new Uri($base);
+ $actual = Uri::resolve($uri, $rel);
+ $this->assertEquals($expected, (string) $actual);
+ }
+
+ public function getResolveTestCases()
+ {
+ return array(
+ //[self::RFC3986_BASE, 'g:h', 'g:h'],
+ array(self::RFC3986_BASE, 'g', 'http://a/b/c/g'),
+ array(self::RFC3986_BASE, './g', 'http://a/b/c/g'),
+ array(self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'),
+ array(self::RFC3986_BASE, '/g', 'http://a/g'),
+ // Due to 5.4.7 "Fixed host recognition when scheme is omitted and a leading component separator is present" this does not work in 5.3
+ //array(self::RFC3986_BASE, '//g', 'http://g'),
+ array(self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'),
+ array(self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'),
+ array(self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'),
+ array(self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'),
+ array(self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'),
+ array(self::RFC3986_BASE, ';x', 'http://a/b/c/;x'),
+ array(self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'),
+ array(self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'),
+ array(self::RFC3986_BASE, '', self::RFC3986_BASE),
+ array(self::RFC3986_BASE, '.', 'http://a/b/c/'),
+ array(self::RFC3986_BASE, './', 'http://a/b/c/'),
+ array(self::RFC3986_BASE, '..', 'http://a/b/'),
+ array(self::RFC3986_BASE, '../', 'http://a/b/'),
+ array(self::RFC3986_BASE, '../g', 'http://a/b/g'),
+ array(self::RFC3986_BASE, '../..', 'http://a/'),
+ array(self::RFC3986_BASE, '../../', 'http://a/'),
+ array(self::RFC3986_BASE, '../../g', 'http://a/g'),
+ array(self::RFC3986_BASE, '../../../g', 'http://a/g'),
+ array(self::RFC3986_BASE, '../../../../g', 'http://a/g'),
+ array(self::RFC3986_BASE, '/./g', 'http://a/g'),
+ array(self::RFC3986_BASE, '/../g', 'http://a/g'),
+ array(self::RFC3986_BASE, 'g.', 'http://a/b/c/g.'),
+ array(self::RFC3986_BASE, '.g', 'http://a/b/c/.g'),
+ array(self::RFC3986_BASE, 'g..', 'http://a/b/c/g..'),
+ array(self::RFC3986_BASE, '..g', 'http://a/b/c/..g'),
+ array(self::RFC3986_BASE, './../g', 'http://a/b/g'),
+ array(self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'),
+ array(self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'),
+ array(self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'),
+ array(self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'),
+ array(self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'),
+ array(self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'),
+ array('http://u@a/b/c/d;p?q', '.', 'http://u@a/b/c/'),
+ array('http://u:p@a/b/c/d;p?q', '.', 'http://u:p@a/b/c/'),
+ //[self::RFC3986_BASE, 'http:g', 'http:g'],
+ );
+ }
+
+ public function testAddAndRemoveQueryValues()
+ {
+ $uri = new Uri('http://foo.com/bar');
+ $uri = Uri::withQueryValue($uri, 'a', 'b');
+ $uri = Uri::withQueryValue($uri, 'c', 'd');
+ $uri = Uri::withQueryValue($uri, 'e', null);
+ $this->assertEquals('a=b&c=d&e', $uri->getQuery());
+
+ $uri = Uri::withoutQueryValue($uri, 'c');
+ $uri = Uri::withoutQueryValue($uri, 'e');
+ $this->assertEquals('a=b', $uri->getQuery());
+ $uri = Uri::withoutQueryValue($uri, 'a');
+ $uri = Uri::withoutQueryValue($uri, 'a');
+ $this->assertEquals('', $uri->getQuery());
+ }
+
+ public function testGetAuthorityReturnsCorrectPort()
+ {
+ // HTTPS non-standard port
+ $uri = new Uri('https://foo.co:99');
+ $this->assertEquals('foo.co:99', $uri->getAuthority());
+
+ // HTTP non-standard port
+ $uri = new Uri('http://foo.co:99');
+ $this->assertEquals('foo.co:99', $uri->getAuthority());
+
+ // No scheme
+ $uri = new Uri('foo.co:99');
+ $this->assertEquals('foo.co:99', $uri->getAuthority());
+
+ // No host or port
+ $uri = new Uri('http:');
+ $this->assertEquals('', $uri->getAuthority());
+
+ // No host or port
+ $uri = new Uri('http://foo.co');
+ $this->assertEquals('foo.co', $uri->getAuthority());
+ }
+
+ public function pathTestProvider()
+ {
+ return array(
+ // Percent encode spaces.
+ array('http://foo.com/baz bar', 'http://foo.com/baz%20bar'),
+ // Don't encoding something that's already encoded.
+ array('http://foo.com/baz%20bar', 'http://foo.com/baz%20bar'),
+ // Percent encode invalid percent encodings
+ array('http://foo.com/baz%2-bar', 'http://foo.com/baz%252-bar'),
+ // Don't encode path segments
+ array('http://foo.com/baz/bar/bam?a', 'http://foo.com/baz/bar/bam?a'),
+ array('http://foo.com/baz+bar', 'http://foo.com/baz+bar'),
+ array('http://foo.com/baz:bar', 'http://foo.com/baz:bar'),
+ array('http://foo.com/baz@bar', 'http://foo.com/baz@bar'),
+ array('http://foo.com/baz(bar);bam/', 'http://foo.com/baz(bar);bam/'),
+ array('http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@', 'http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@'),
+ );
+ }
+
+ /**
+ * @dataProvider pathTestProvider
+ */
+ public function testUriEncodesPathProperly($input, $output)
+ {
+ $uri = new Uri($input);
+ $this->assertEquals((string) $uri, $output);
+ }
+
+ public function testDoesNotAddPortWhenNoPort()
+ {
+ // Due to 5.4.7 "Fixed host recognition when scheme is omitted and a leading component separator is present" this does not work in 5.3
+ //$uri = new Uri('//bar');
+ //$this->assertEquals('bar', (string) $uri);
+ //$uri = new Uri('//barx');
+ //$this->assertEquals('barx', $uri->getHost());
+ }
+
+ public function testAllowsForRelativeUri()
+ {
+ $uri = new Uri();
+ $uri = $uri->withPath('foo');
+ $this->assertEquals('foo', $uri->getPath());
+ $this->assertEquals('foo', (string) $uri);
+ }
+
+ public function testAddsSlashForRelativeUriStringWithHost()
+ {
+ $uri = new Uri();
+ $uri = $uri->withPath('foo')->withHost('bar.com');
+ $this->assertEquals('foo', $uri->getPath());
+ $this->assertEquals('bar.com/foo', (string) $uri);
+ }
+}
diff --git a/instafeed/vendor/ringcentral/psr7/tests/bootstrap.php b/instafeed/vendor/ringcentral/psr7/tests/bootstrap.php
new file mode 100755
index 0000000..ea6a079
--- /dev/null
+++ b/instafeed/vendor/ringcentral/psr7/tests/bootstrap.php
@@ -0,0 +1,13 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * Marker Interface for the Process Component.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/instafeed/vendor/symfony/process/Exception/InvalidArgumentException.php b/instafeed/vendor/symfony/process/Exception/InvalidArgumentException.php
new file mode 100755
index 0000000..926ee21
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * InvalidArgumentException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/instafeed/vendor/symfony/process/Exception/LogicException.php b/instafeed/vendor/symfony/process/Exception/LogicException.php
new file mode 100755
index 0000000..be3d490
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Exception/LogicException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * LogicException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/instafeed/vendor/symfony/process/Exception/ProcessFailedException.php b/instafeed/vendor/symfony/process/Exception/ProcessFailedException.php
new file mode 100755
index 0000000..328acfd
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Exception/ProcessFailedException.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception for failed processes.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessFailedException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ if ($process->isSuccessful()) {
+ throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
+ }
+
+ $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
+ $process->getCommandLine(),
+ $process->getExitCode(),
+ $process->getExitCodeText(),
+ $process->getWorkingDirectory()
+ );
+
+ if (!$process->isOutputDisabled()) {
+ $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
+ $process->getOutput(),
+ $process->getErrorOutput()
+ );
+ }
+
+ parent::__construct($error);
+
+ $this->process = $process;
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Exception/ProcessSignaledException.php b/instafeed/vendor/symfony/process/Exception/ProcessSignaledException.php
new file mode 100755
index 0000000..d4d3227
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Exception/ProcessSignaledException.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process has been signaled.
+ *
+ * @author Sullivan Senechal
+ */
+final class ProcessSignaledException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ $this->process = $process;
+
+ parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
+ }
+
+ public function getProcess(): Process
+ {
+ return $this->process;
+ }
+
+ public function getSignal(): int
+ {
+ return $this->getProcess()->getTermSignal();
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Exception/ProcessTimedOutException.php b/instafeed/vendor/symfony/process/Exception/ProcessTimedOutException.php
new file mode 100755
index 0000000..e1f6445
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Exception/ProcessTimedOutException.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+ const TYPE_GENERAL = 1;
+ const TYPE_IDLE = 2;
+
+ private $process;
+ private $timeoutType;
+
+ public function __construct(Process $process, int $timeoutType)
+ {
+ $this->process = $process;
+ $this->timeoutType = $timeoutType;
+
+ parent::__construct(sprintf(
+ 'The process "%s" exceeded the timeout of %s seconds.',
+ $process->getCommandLine(),
+ $this->getExceededTimeout()
+ ));
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ public function isGeneralTimeout()
+ {
+ return self::TYPE_GENERAL === $this->timeoutType;
+ }
+
+ public function isIdleTimeout()
+ {
+ return self::TYPE_IDLE === $this->timeoutType;
+ }
+
+ public function getExceededTimeout()
+ {
+ switch ($this->timeoutType) {
+ case self::TYPE_GENERAL:
+ return $this->process->getTimeout();
+
+ case self::TYPE_IDLE:
+ return $this->process->getIdleTimeout();
+
+ default:
+ throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+ }
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Exception/RuntimeException.php b/instafeed/vendor/symfony/process/Exception/RuntimeException.php
new file mode 100755
index 0000000..adead25
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Exception/RuntimeException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * RuntimeException for the Process Component.
+ *
+ * @author Johannes M. Schmitt
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/instafeed/vendor/symfony/process/ExecutableFinder.php b/instafeed/vendor/symfony/process/ExecutableFinder.php
new file mode 100755
index 0000000..cb4345e
--- /dev/null
+++ b/instafeed/vendor/symfony/process/ExecutableFinder.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * Generic executable finder.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class ExecutableFinder
+{
+ private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
+
+ /**
+ * Replaces default suffixes of executable.
+ */
+ public function setSuffixes(array $suffixes)
+ {
+ $this->suffixes = $suffixes;
+ }
+
+ /**
+ * Adds new possible suffix to check for executable.
+ *
+ * @param string $suffix
+ */
+ public function addSuffix($suffix)
+ {
+ $this->suffixes[] = $suffix;
+ }
+
+ /**
+ * Finds an executable by name.
+ *
+ * @param string $name The executable name (without the extension)
+ * @param string|null $default The default to return if no executable is found
+ * @param array $extraDirs Additional dirs to check into
+ *
+ * @return string|null The executable path or default value
+ */
+ public function find($name, $default = null, array $extraDirs = [])
+ {
+ if (ini_get('open_basedir')) {
+ $searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
+ $dirs = [];
+ foreach ($searchPath as $path) {
+ // Silencing against https://bugs.php.net/69240
+ if (@is_dir($path)) {
+ $dirs[] = $path;
+ } else {
+ if (basename($path) == $name && @is_executable($path)) {
+ return $path;
+ }
+ }
+ }
+ } else {
+ $dirs = array_merge(
+ explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
+ $extraDirs
+ );
+ }
+
+ $suffixes = [''];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $pathExt = getenv('PATHEXT');
+ $suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
+ }
+ foreach ($suffixes as $suffix) {
+ foreach ($dirs as $dir) {
+ if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
+ return $file;
+ }
+ }
+ }
+
+ return $default;
+ }
+}
diff --git a/instafeed/vendor/symfony/process/InputStream.php b/instafeed/vendor/symfony/process/InputStream.php
new file mode 100755
index 0000000..426ffa3
--- /dev/null
+++ b/instafeed/vendor/symfony/process/InputStream.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * Provides a way to continuously write to the input of a Process until the InputStream is closed.
+ *
+ * @author Nicolas Grekas
+ */
+class InputStream implements \IteratorAggregate
+{
+ /** @var callable|null */
+ private $onEmpty = null;
+ private $input = [];
+ private $open = true;
+
+ /**
+ * Sets a callback that is called when the write buffer becomes empty.
+ */
+ public function onEmpty(callable $onEmpty = null)
+ {
+ $this->onEmpty = $onEmpty;
+ }
+
+ /**
+ * Appends an input to the write buffer.
+ *
+ * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
+ * stream resource or \Traversable
+ */
+ public function write($input)
+ {
+ if (null === $input) {
+ return;
+ }
+ if ($this->isClosed()) {
+ throw new RuntimeException(sprintf('%s is closed', static::class));
+ }
+ $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
+ }
+
+ /**
+ * Closes the write buffer.
+ */
+ public function close()
+ {
+ $this->open = false;
+ }
+
+ /**
+ * Tells whether the write buffer is closed or not.
+ */
+ public function isClosed()
+ {
+ return !$this->open;
+ }
+
+ public function getIterator()
+ {
+ $this->open = true;
+
+ while ($this->open || $this->input) {
+ if (!$this->input) {
+ yield '';
+ continue;
+ }
+ $current = array_shift($this->input);
+
+ if ($current instanceof \Iterator) {
+ yield from $current;
+ } else {
+ yield $current;
+ }
+ if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
+ $this->write($onEmpty($this));
+ }
+ }
+ }
+}
diff --git a/instafeed/vendor/symfony/process/LICENSE b/instafeed/vendor/symfony/process/LICENSE
new file mode 100755
index 0000000..a677f43
--- /dev/null
+++ b/instafeed/vendor/symfony/process/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/symfony/process/PhpExecutableFinder.php b/instafeed/vendor/symfony/process/PhpExecutableFinder.php
new file mode 100755
index 0000000..461ea13
--- /dev/null
+++ b/instafeed/vendor/symfony/process/PhpExecutableFinder.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * An executable finder specifically designed for the PHP executable.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class PhpExecutableFinder
+{
+ private $executableFinder;
+
+ public function __construct()
+ {
+ $this->executableFinder = new ExecutableFinder();
+ }
+
+ /**
+ * Finds The PHP executable.
+ *
+ * @param bool $includeArgs Whether or not include command arguments
+ *
+ * @return string|false The PHP executable path or false if it cannot be found
+ */
+ public function find($includeArgs = true)
+ {
+ if ($php = getenv('PHP_BINARY')) {
+ if (!is_executable($php)) {
+ $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
+ if ($php = strtok(exec($command.' '.escapeshellarg($php)), PHP_EOL)) {
+ if (!is_executable($php)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return $php;
+ }
+
+ $args = $this->findArguments();
+ $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
+
+ // PHP_BINARY return the current sapi executable
+ if (PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
+ return PHP_BINARY.$args;
+ }
+
+ if ($php = getenv('PHP_PATH')) {
+ if (!@is_executable($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ if ($php = getenv('PHP_PEAR_PHP_BIN')) {
+ if (@is_executable($php)) {
+ return $php;
+ }
+ }
+
+ if (@is_executable($php = PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
+ return $php;
+ }
+
+ $dirs = [PHP_BINDIR];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $dirs[] = 'C:\xampp\php\\';
+ }
+
+ return $this->executableFinder->find('php', false, $dirs);
+ }
+
+ /**
+ * Finds the PHP executable arguments.
+ *
+ * @return array The PHP executable arguments
+ */
+ public function findArguments()
+ {
+ $arguments = [];
+ if ('phpdbg' === \PHP_SAPI) {
+ $arguments[] = '-qrr';
+ }
+
+ return $arguments;
+ }
+}
diff --git a/instafeed/vendor/symfony/process/PhpProcess.php b/instafeed/vendor/symfony/process/PhpProcess.php
new file mode 100755
index 0000000..126d9b7
--- /dev/null
+++ b/instafeed/vendor/symfony/process/PhpProcess.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * PhpProcess runs a PHP script in an independent process.
+ *
+ * $p = new PhpProcess('');
+ * $p->run();
+ * print $p->getOutput()."\n";
+ *
+ * @author Fabien Potencier
+ */
+class PhpProcess extends Process
+{
+ /**
+ * @param string $script The PHP script to run (as a string)
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param int $timeout The timeout in seconds
+ * @param array|null $php Path to the PHP binary to use with any additional arguments
+ */
+ public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null)
+ {
+ if (null === $php) {
+ $executableFinder = new PhpExecutableFinder();
+ $php = $executableFinder->find(false);
+ $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
+ }
+ if ('phpdbg' === \PHP_SAPI) {
+ $file = tempnam(sys_get_temp_dir(), 'dbg');
+ file_put_contents($file, $script);
+ register_shutdown_function('unlink', $file);
+ $php[] = $file;
+ $script = null;
+ }
+
+ parent::__construct($php, $cwd, $env, $script, $timeout);
+ }
+
+ /**
+ * Sets the path to the PHP binary to use.
+ *
+ * @deprecated since Symfony 4.2, use the $php argument of the constructor instead.
+ */
+ public function setPhpBinary($php)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the $php argument of the constructor instead.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->setCommandLine($php);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if (null === $this->getCommandLine()) {
+ throw new RuntimeException('Unable to find the PHP executable.');
+ }
+
+ parent::start($callback, $env);
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Pipes/AbstractPipes.php b/instafeed/vendor/symfony/process/Pipes/AbstractPipes.php
new file mode 100755
index 0000000..9dd415d
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Pipes/AbstractPipes.php
@@ -0,0 +1,182 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+abstract class AbstractPipes implements PipesInterface
+{
+ public $pipes = [];
+
+ private $inputBuffer = '';
+ private $input;
+ private $blocked = true;
+ private $lastError;
+
+ /**
+ * @param resource|string|int|float|bool|\Iterator|null $input
+ */
+ public function __construct($input)
+ {
+ if (\is_resource($input) || $input instanceof \Iterator) {
+ $this->input = $input;
+ } elseif (\is_string($input)) {
+ $this->inputBuffer = $input;
+ } else {
+ $this->inputBuffer = (string) $input;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ foreach ($this->pipes as $pipe) {
+ fclose($pipe);
+ }
+ $this->pipes = [];
+ }
+
+ /**
+ * Returns true if a system call has been interrupted.
+ *
+ * @return bool
+ */
+ protected function hasSystemCallBeenInterrupted()
+ {
+ $lastError = $this->lastError;
+ $this->lastError = null;
+
+ // stream_select returns false when the `select` system call is interrupted by an incoming signal
+ return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
+ }
+
+ /**
+ * Unblocks streams.
+ */
+ protected function unblock()
+ {
+ if (!$this->blocked) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ stream_set_blocking($pipe, 0);
+ }
+ if (\is_resource($this->input)) {
+ stream_set_blocking($this->input, 0);
+ }
+
+ $this->blocked = false;
+ }
+
+ /**
+ * Writes input to stdin.
+ *
+ * @return array|null
+ *
+ * @throws InvalidArgumentException When an input iterator yields a non supported value
+ */
+ protected function write()
+ {
+ if (!isset($this->pipes[0])) {
+ return null;
+ }
+ $input = $this->input;
+
+ if ($input instanceof \Iterator) {
+ if (!$input->valid()) {
+ $input = null;
+ } elseif (\is_resource($input = $input->current())) {
+ stream_set_blocking($input, 0);
+ } elseif (!isset($this->inputBuffer[0])) {
+ if (!\is_string($input)) {
+ if (!is_scalar($input)) {
+ throw new InvalidArgumentException(sprintf('%s yielded a value of type "%s", but only scalars and stream resources are supported', \get_class($this->input), \gettype($input)));
+ }
+ $input = (string) $input;
+ }
+ $this->inputBuffer = $input;
+ $this->input->next();
+ $input = null;
+ } else {
+ $input = null;
+ }
+ }
+
+ $r = $e = [];
+ $w = [$this->pipes[0]];
+
+ // let's have a look if something changed in streams
+ if (false === @stream_select($r, $w, $e, 0, 0)) {
+ return null;
+ }
+
+ foreach ($w as $stdin) {
+ if (isset($this->inputBuffer[0])) {
+ $written = fwrite($stdin, $this->inputBuffer);
+ $this->inputBuffer = substr($this->inputBuffer, $written);
+ if (isset($this->inputBuffer[0])) {
+ return [$this->pipes[0]];
+ }
+ }
+
+ if ($input) {
+ for (;;) {
+ $data = fread($input, self::CHUNK_SIZE);
+ if (!isset($data[0])) {
+ break;
+ }
+ $written = fwrite($stdin, $data);
+ $data = substr($data, $written);
+ if (isset($data[0])) {
+ $this->inputBuffer = $data;
+
+ return [$this->pipes[0]];
+ }
+ }
+ if (feof($input)) {
+ if ($this->input instanceof \Iterator) {
+ $this->input->next();
+ } else {
+ $this->input = null;
+ }
+ }
+ }
+ }
+
+ // no input to read on resource, buffer is empty
+ if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
+ $this->input = null;
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ } elseif (!$w) {
+ return [$this->pipes[0]];
+ }
+
+ return null;
+ }
+
+ /**
+ * @internal
+ */
+ public function handleError($type, $msg)
+ {
+ $this->lastError = $msg;
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Pipes/PipesInterface.php b/instafeed/vendor/symfony/process/Pipes/PipesInterface.php
new file mode 100755
index 0000000..52bbe76
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Pipes/PipesInterface.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+/**
+ * PipesInterface manages descriptors and pipes for the use of proc_open.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+interface PipesInterface
+{
+ const CHUNK_SIZE = 16384;
+
+ /**
+ * Returns an array of descriptors for the use of proc_open.
+ *
+ * @return array
+ */
+ public function getDescriptors();
+
+ /**
+ * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
+ *
+ * @return string[]
+ */
+ public function getFiles();
+
+ /**
+ * Reads data in file handles and pipes.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close pipes if they've reached EOF
+ *
+ * @return string[] An array of read data indexed by their fd
+ */
+ public function readAndWrite($blocking, $close = false);
+
+ /**
+ * Returns if the current state has open file handles or pipes.
+ *
+ * @return bool
+ */
+ public function areOpen();
+
+ /**
+ * Returns if pipes are able to read output.
+ *
+ * @return bool
+ */
+ public function haveReadSupport();
+
+ /**
+ * Closes file handles and pipes.
+ */
+ public function close();
+}
diff --git a/instafeed/vendor/symfony/process/Pipes/UnixPipes.php b/instafeed/vendor/symfony/process/Pipes/UnixPipes.php
new file mode 100755
index 0000000..875ee6a
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Pipes/UnixPipes.php
@@ -0,0 +1,153 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * UnixPipes implementation uses unix pipes as handles.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class UnixPipes extends AbstractPipes
+{
+ private $ttyMode;
+ private $ptyMode;
+ private $haveReadSupport;
+
+ public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
+ {
+ $this->ttyMode = $ttyMode;
+ $this->ptyMode = $ptyMode;
+ $this->haveReadSupport = $haveReadSupport;
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('/dev/null', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ if ($this->ttyMode) {
+ return [
+ ['file', '/dev/tty', 'r'],
+ ['file', '/dev/tty', 'w'],
+ ['file', '/dev/tty', 'w'],
+ ];
+ }
+
+ if ($this->ptyMode && Process::isPtySupported()) {
+ return [
+ ['pty'],
+ ['pty'],
+ ['pty'],
+ ];
+ }
+
+ return [
+ ['pipe', 'r'],
+ ['pipe', 'w'], // stdout
+ ['pipe', 'w'], // stderr
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+
+ $read = $e = [];
+ $r = $this->pipes;
+ unset($r[0]);
+
+ // let's have a look if something changed in streams
+ set_error_handler([$this, 'handleError']);
+ if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+ restore_error_handler();
+ // if a system call has been interrupted, forget about it, let's try again
+ // otherwise, an error occurred, let's reset pipes
+ if (!$this->hasSystemCallBeenInterrupted()) {
+ $this->pipes = [];
+ }
+
+ return $read;
+ }
+ restore_error_handler();
+
+ foreach ($r as $pipe) {
+ // prior PHP 5.4 the array passed to stream_select is modified and
+ // lose key association, we have to find back the key
+ $read[$type = array_search($pipe, $this->pipes, true)] = '';
+
+ do {
+ $data = fread($pipe, self::CHUNK_SIZE);
+ $read[$type] .= $data;
+ } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
+
+ if (!isset($read[$type][0])) {
+ unset($read[$type]);
+ }
+
+ if ($close && feof($pipe)) {
+ fclose($pipe);
+ unset($this->pipes[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return (bool) $this->pipes;
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Pipes/WindowsPipes.php b/instafeed/vendor/symfony/process/Pipes/WindowsPipes.php
new file mode 100755
index 0000000..0e38d72
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Pipes/WindowsPipes.php
@@ -0,0 +1,191 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Process;
+
+/**
+ * WindowsPipes implementation uses temporary files as handles.
+ *
+ * @see https://bugs.php.net/51800
+ * @see https://bugs.php.net/65650
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class WindowsPipes extends AbstractPipes
+{
+ private $files = [];
+ private $fileHandles = [];
+ private $lockHandles = [];
+ private $readBytes = [
+ Process::STDOUT => 0,
+ Process::STDERR => 0,
+ ];
+ private $haveReadSupport;
+
+ public function __construct($input, bool $haveReadSupport)
+ {
+ $this->haveReadSupport = $haveReadSupport;
+
+ if ($this->haveReadSupport) {
+ // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
+ // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
+ //
+ // @see https://bugs.php.net/51800
+ $pipes = [
+ Process::STDOUT => Process::OUT,
+ Process::STDERR => Process::ERR,
+ ];
+ $tmpDir = sys_get_temp_dir();
+ $lastError = 'unknown reason';
+ set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
+ for ($i = 0;; ++$i) {
+ foreach ($pipes as $pipe => $name) {
+ $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
+
+ if (!$h = fopen($file.'.lock', 'w')) {
+ restore_error_handler();
+ throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $lastError));
+ }
+ if (!flock($h, LOCK_EX | LOCK_NB)) {
+ continue 2;
+ }
+ if (isset($this->lockHandles[$pipe])) {
+ flock($this->lockHandles[$pipe], LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ }
+ $this->lockHandles[$pipe] = $h;
+
+ if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
+ flock($this->lockHandles[$pipe], LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ unset($this->lockHandles[$pipe]);
+ continue 2;
+ }
+ $this->fileHandles[$pipe] = $h;
+ $this->files[$pipe] = $file;
+ }
+ break;
+ }
+ restore_error_handler();
+ }
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('NUL', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
+ // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
+ // So we redirect output within the commandline and pass the nul device to the process
+ return [
+ ['pipe', 'r'],
+ ['file', 'NUL', 'w'],
+ ['file', 'NUL', 'w'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return $this->files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+ $read = $r = $e = [];
+
+ if ($blocking) {
+ if ($w) {
+ @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
+ } elseif ($this->fileHandles) {
+ usleep(Process::TIMEOUT_PRECISION * 1E6);
+ }
+ }
+ foreach ($this->fileHandles as $type => $fileHandle) {
+ $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
+
+ if (isset($data[0])) {
+ $this->readBytes[$type] += \strlen($data);
+ $read[$type] = $data;
+ }
+ if ($close) {
+ ftruncate($fileHandle, 0);
+ fclose($fileHandle);
+ flock($this->lockHandles[$type], LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ unset($this->fileHandles[$type], $this->lockHandles[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return $this->pipes && $this->fileHandles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ parent::close();
+ foreach ($this->fileHandles as $type => $handle) {
+ ftruncate($handle, 0);
+ fclose($handle);
+ flock($this->lockHandles[$type], LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ }
+ $this->fileHandles = $this->lockHandles = [];
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Process.php b/instafeed/vendor/symfony/process/Process.php
new file mode 100755
index 0000000..5d02c60
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Process.php
@@ -0,0 +1,1645 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Exception\ProcessSignaledException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Pipes\UnixPipes;
+use Symfony\Component\Process\Pipes\WindowsPipes;
+
+/**
+ * Process is a thin wrapper around proc_* functions to easily
+ * start independent PHP processes.
+ *
+ * @author Fabien Potencier
+ * @author Romain Neutron
+ */
+class Process implements \IteratorAggregate
+{
+ const ERR = 'err';
+ const OUT = 'out';
+
+ const STATUS_READY = 'ready';
+ const STATUS_STARTED = 'started';
+ const STATUS_TERMINATED = 'terminated';
+
+ const STDIN = 0;
+ const STDOUT = 1;
+ const STDERR = 2;
+
+ // Timeout Precision in seconds.
+ const TIMEOUT_PRECISION = 0.2;
+
+ const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
+ const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
+ const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
+ const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
+
+ private $callback;
+ private $hasCallback = false;
+ private $commandline;
+ private $cwd;
+ private $env;
+ private $input;
+ private $starttime;
+ private $lastOutputTime;
+ private $timeout;
+ private $idleTimeout;
+ private $exitcode;
+ private $fallbackStatus = [];
+ private $processInformation;
+ private $outputDisabled = false;
+ private $stdout;
+ private $stderr;
+ private $process;
+ private $status = self::STATUS_READY;
+ private $incrementalOutputOffset = 0;
+ private $incrementalErrorOutputOffset = 0;
+ private $tty = false;
+ private $pty;
+
+ private $useFileHandles = false;
+ /** @var PipesInterface */
+ private $processPipes;
+
+ private $latestSignal;
+
+ private static $sigchild;
+
+ /**
+ * Exit codes translation table.
+ *
+ * User-defined errors must use exit codes in the 64-113 range.
+ */
+ public static $exitCodes = [
+ 0 => 'OK',
+ 1 => 'General error',
+ 2 => 'Misuse of shell builtins',
+
+ 126 => 'Invoked command cannot execute',
+ 127 => 'Command not found',
+ 128 => 'Invalid exit argument',
+
+ // signals
+ 129 => 'Hangup',
+ 130 => 'Interrupt',
+ 131 => 'Quit and dump core',
+ 132 => 'Illegal instruction',
+ 133 => 'Trace/breakpoint trap',
+ 134 => 'Process aborted',
+ 135 => 'Bus error: "access to undefined portion of memory object"',
+ 136 => 'Floating point exception: "erroneous arithmetic operation"',
+ 137 => 'Kill (terminate immediately)',
+ 138 => 'User-defined 1',
+ 139 => 'Segmentation violation',
+ 140 => 'User-defined 2',
+ 141 => 'Write to pipe with no one reading',
+ 142 => 'Signal raised by alarm',
+ 143 => 'Termination (request to terminate)',
+ // 144 - not defined
+ 145 => 'Child process terminated, stopped (or continued*)',
+ 146 => 'Continue if stopped',
+ 147 => 'Stop executing temporarily',
+ 148 => 'Terminal stop signal',
+ 149 => 'Background process attempting to read from tty ("in")',
+ 150 => 'Background process attempting to write to tty ("out")',
+ 151 => 'Urgent data available on socket',
+ 152 => 'CPU time limit exceeded',
+ 153 => 'File size limit exceeded',
+ 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+ 155 => 'Profiling timer expired',
+ // 156 - not defined
+ 157 => 'Pollable event',
+ // 158 - not defined
+ 159 => 'Bad syscall',
+ ];
+
+ /**
+ * @param array $command The command to run and its arguments listed as separate entries
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public function __construct($command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ if (!\function_exists('proc_open')) {
+ throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
+ }
+
+ if (!\is_array($command)) {
+ @trigger_error(sprintf('Passing a command as string when creating a "%s" instance is deprecated since Symfony 4.2, pass it as an array of its arguments instead, or use the "Process::fromShellCommandline()" constructor if you need features provided by the shell.', __CLASS__), E_USER_DEPRECATED);
+ }
+
+ $this->commandline = $command;
+ $this->cwd = $cwd;
+
+ // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
+ // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
+ // @see : https://bugs.php.net/51800
+ // @see : https://bugs.php.net/50524
+ if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
+ $this->cwd = getcwd();
+ }
+ if (null !== $env) {
+ $this->setEnv($env);
+ }
+
+ $this->setInput($input);
+ $this->setTimeout($timeout);
+ $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
+ $this->pty = false;
+ }
+
+ /**
+ * Creates a Process instance as a command-line to be run in a shell wrapper.
+ *
+ * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
+ * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
+ * shell wrapper and not to your commands.
+ *
+ * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
+ * This will save escaping values, which is not portable nor secure anyway:
+ *
+ * $process = Process::fromShellCommandline('my_command "$MY_VAR"');
+ * $process->run(null, ['MY_VAR' => $theValue]);
+ *
+ * @param string $command The command line to pass to the shell of the OS
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @return static
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ $process = new static([], $cwd, $env, $input, $timeout);
+ $process->commandline = $command;
+
+ return $process;
+ }
+
+ public function __destruct()
+ {
+ $this->stop(0);
+ }
+
+ public function __clone()
+ {
+ $this->resetProcessData();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * The callback receives the type of output (out or err) and
+ * some bytes from the output in real-time. It allows to have feedback
+ * from the independent process during execution.
+ *
+ * The STDOUT and STDERR are also available after the process is finished
+ * via the getOutput() and getErrorOutput() methods.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return int The exit status code
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException In case a callback is provided and output has been disabled
+ *
+ * @final
+ */
+ public function run(callable $callback = null, array $env = []): int
+ {
+ $this->start($callback, $env);
+
+ return $this->wait();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * This is identical to run() except that an exception is thrown if the process
+ * exits with a non-zero exit code.
+ *
+ * @return $this
+ *
+ * @throws ProcessFailedException if the process didn't terminate successfully
+ *
+ * @final
+ */
+ public function mustRun(callable $callback = null, array $env = [])
+ {
+ if (0 !== $this->run($callback, $env)) {
+ throw new ProcessFailedException($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Starts the process and returns after writing the input to STDIN.
+ *
+ * This method blocks until all STDIN data is sent to the process then it
+ * returns while the process runs in the background.
+ *
+ * The termination of the process can be awaited with wait().
+ *
+ * The callback receives the type of output (out or err) and some bytes from
+ * the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ * @throws LogicException In case a callback is provided and output has been disabled
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+
+ $this->resetProcessData();
+ $this->starttime = $this->lastOutputTime = microtime(true);
+ $this->callback = $this->buildCallback($callback);
+ $this->hasCallback = null !== $callback;
+ $descriptors = $this->getDescriptors();
+
+ if (\is_array($commandline = $this->commandline)) {
+ $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
+
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ // exec is mandatory to deal with sending a signal to the process
+ $commandline = 'exec '.$commandline;
+ }
+ }
+
+ if ($this->env) {
+ $env += $this->env;
+ }
+ $env += $this->getDefaultEnv();
+
+ $options = ['suppress_errors' => true];
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $options['bypass_shell'] = true;
+ $commandline = $this->prepareWindowsCommandLine($commandline, $env);
+ } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
+ // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
+ $descriptors[3] = ['pipe', 'w'];
+
+ // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
+ $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
+ $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
+
+ // Workaround for the bug, when PTS functionality is enabled.
+ // @see : https://bugs.php.net/69442
+ $ptsWorkaround = fopen(__FILE__, 'r');
+ }
+
+ $envPairs = [];
+ foreach ($env as $k => $v) {
+ if (false !== $v) {
+ $envPairs[] = $k.'='.$v;
+ }
+ }
+
+ if (!is_dir($this->cwd)) {
+ throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
+ }
+
+ $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options);
+
+ if (!\is_resource($this->process)) {
+ throw new RuntimeException('Unable to launch a new process.');
+ }
+ $this->status = self::STATUS_STARTED;
+
+ if (isset($descriptors[3])) {
+ $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
+ }
+
+ if ($this->tty) {
+ return;
+ }
+
+ $this->updateStatus(false);
+ $this->checkTimeout();
+ }
+
+ /**
+ * Restarts the process.
+ *
+ * Be warned that the process is cloned before being started.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return static
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ *
+ * @see start()
+ *
+ * @final
+ */
+ public function restart(callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+
+ $process = clone $this;
+ $process->start($callback, $env);
+
+ return $process;
+ }
+
+ /**
+ * Waits for the process to terminate.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A valid PHP callback
+ *
+ * @return int The exitcode of the process
+ *
+ * @throws RuntimeException When process timed out
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException When process is not yet started
+ */
+ public function wait(callable $callback = null)
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->updateStatus(false);
+
+ if (null !== $callback) {
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"');
+ }
+ $this->callback = $this->buildCallback($callback);
+ }
+
+ do {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+ } while ($running);
+
+ while ($this->isRunning()) {
+ $this->checkTimeout();
+ usleep(1000);
+ }
+
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+ throw new ProcessSignaledException($this);
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Waits until the callback returns true.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @throws RuntimeException When process timed out
+ * @throws LogicException When process is not yet started
+ */
+ public function waitUntil(callable $callback): bool
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+ $this->updateStatus(false);
+
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
+ }
+ $callback = $this->buildCallback($callback);
+
+ $ready = false;
+ while (true) {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ foreach ($output as $type => $data) {
+ if (3 !== $type) {
+ $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ if ($ready) {
+ return true;
+ }
+ if (!$running) {
+ return false;
+ }
+
+ usleep(1000);
+ }
+ }
+
+ /**
+ * Returns the Pid (process identifier), if applicable.
+ *
+ * @return int|null The process id if running, null otherwise
+ */
+ public function getPid()
+ {
+ return $this->isRunning() ? $this->processInformation['pid'] : null;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ public function signal($signal)
+ {
+ $this->doSignal($signal, true);
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ * @throws LogicException if an idle timeout is set
+ */
+ public function disableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Disabling output while the process is running is not possible.');
+ }
+ if (null !== $this->idleTimeout) {
+ throw new LogicException('Output can not be disabled while an idle timeout is set.');
+ }
+
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ */
+ public function enableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Enabling output while the process is running is not possible.');
+ }
+
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns true in case the output is disabled, false otherwise.
+ *
+ * @return bool
+ */
+ public function isOutputDisabled()
+ {
+ return $this->outputDisabled;
+ }
+
+ /**
+ * Returns the current output of the process (STDOUT).
+ *
+ * @return string The process output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the output incrementally.
+ *
+ * In comparison with the getOutput method which always return the whole
+ * output, this one returns the new output since the last call.
+ *
+ * @return string The process output since the last call
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+ $this->incrementalOutputOffset = ftell($this->stdout);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
+ *
+ * @param int $flags A bit field of Process::ITER_* flags
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ *
+ * @return \Generator
+ */
+ public function getIterator($flags = 0)
+ {
+ $this->readPipesForOutput(__FUNCTION__, false);
+
+ $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
+ $blocking = !(self::ITER_NON_BLOCKING & $flags);
+ $yieldOut = !(self::ITER_SKIP_OUT & $flags);
+ $yieldErr = !(self::ITER_SKIP_ERR & $flags);
+
+ while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
+ if ($yieldOut) {
+ $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+
+ if (isset($out[0])) {
+ if ($clearOutput) {
+ $this->clearOutput();
+ } else {
+ $this->incrementalOutputOffset = ftell($this->stdout);
+ }
+
+ yield self::OUT => $out;
+ }
+ }
+
+ if ($yieldErr) {
+ $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+
+ if (isset($err[0])) {
+ if ($clearOutput) {
+ $this->clearErrorOutput();
+ } else {
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+ }
+
+ yield self::ERR => $err;
+ }
+ }
+
+ if (!$blocking && !isset($out[0]) && !isset($err[0])) {
+ yield self::OUT => '';
+ }
+
+ $this->checkTimeout();
+ $this->readPipesForOutput(__FUNCTION__, $blocking);
+ }
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearOutput()
+ {
+ ftruncate($this->stdout, 0);
+ fseek($this->stdout, 0);
+ $this->incrementalOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current error output of the process (STDERR).
+ *
+ * @return string The process error output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the errorOutput incrementally.
+ *
+ * In comparison with the getErrorOutput method which always return the
+ * whole error output, this one returns the new error output since the last
+ * call.
+ *
+ * @return string The process error output since the last call
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearErrorOutput()
+ {
+ ftruncate($this->stderr, 0);
+ fseek($this->stderr, 0);
+ $this->incrementalErrorOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the exit code returned by the process.
+ *
+ * @return int|null The exit status code, null if the Process is not terminated
+ */
+ public function getExitCode()
+ {
+ $this->updateStatus(false);
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns a string representation for the exit code returned by the process.
+ *
+ * This method relies on the Unix exit code status standardization
+ * and might not be relevant for other operating systems.
+ *
+ * @return string|null A string representation for the exit status code, null if the Process is not terminated
+ *
+ * @see http://tldp.org/LDP/abs/html/exitcodes.html
+ * @see http://en.wikipedia.org/wiki/Unix_signal
+ */
+ public function getExitCodeText()
+ {
+ if (null === $exitcode = $this->getExitCode()) {
+ return null;
+ }
+
+ return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
+ }
+
+ /**
+ * Checks if the process ended successfully.
+ *
+ * @return bool true if the process ended successfully, false otherwise
+ */
+ public function isSuccessful()
+ {
+ return 0 === $this->getExitCode();
+ }
+
+ /**
+ * Returns true if the child process has been terminated by an uncaught signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenSignaled()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['signaled'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to terminate its execution.
+ *
+ * It is only meaningful if hasBeenSignaled() returns true.
+ *
+ * @return int
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getTermSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+ }
+
+ return $this->processInformation['termsig'];
+ }
+
+ /**
+ * Returns true if the child process has been stopped by a signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenStopped()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopped'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to stop its execution.
+ *
+ * It is only meaningful if hasBeenStopped() returns true.
+ *
+ * @return int
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getStopSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopsig'];
+ }
+
+ /**
+ * Checks if the process is currently running.
+ *
+ * @return bool true if the process is currently running, false otherwise
+ */
+ public function isRunning()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return false;
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['running'];
+ }
+
+ /**
+ * Checks if the process has been started with no regard to the current state.
+ *
+ * @return bool true if status is ready, false otherwise
+ */
+ public function isStarted()
+ {
+ return self::STATUS_READY != $this->status;
+ }
+
+ /**
+ * Checks if the process is terminated.
+ *
+ * @return bool true if process is terminated, false otherwise
+ */
+ public function isTerminated()
+ {
+ $this->updateStatus(false);
+
+ return self::STATUS_TERMINATED == $this->status;
+ }
+
+ /**
+ * Gets the process status.
+ *
+ * The status is one of: ready, started, terminated.
+ *
+ * @return string The current process status
+ */
+ public function getStatus()
+ {
+ $this->updateStatus(false);
+
+ return $this->status;
+ }
+
+ /**
+ * Stops the process.
+ *
+ * @param int|float $timeout The timeout in seconds
+ * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
+ *
+ * @return int|null The exit-code of the process or null if it's not running
+ */
+ public function stop($timeout = 10, $signal = null)
+ {
+ $timeoutMicro = microtime(true) + $timeout;
+ if ($this->isRunning()) {
+ // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
+ $this->doSignal(15, false);
+ do {
+ usleep(1000);
+ } while ($this->isRunning() && microtime(true) < $timeoutMicro);
+
+ if ($this->isRunning()) {
+ // Avoid exception here: process is supposed to be running, but it might have stopped just
+ // after this line. In any case, let's silently discard the error, we cannot do anything.
+ $this->doSignal($signal ?: 9, false);
+ }
+ }
+
+ if ($this->isRunning()) {
+ if (isset($this->fallbackStatus['pid'])) {
+ unset($this->fallbackStatus['pid']);
+
+ return $this->stop(0, $signal);
+ }
+ $this->close();
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Adds a line to the STDOUT stream.
+ *
+ * @internal
+ */
+ public function addOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stdout, 0, SEEK_END);
+ fwrite($this->stdout, $line);
+ fseek($this->stdout, $this->incrementalOutputOffset);
+ }
+
+ /**
+ * Adds a line to the STDERR stream.
+ *
+ * @internal
+ */
+ public function addErrorOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stderr, 0, SEEK_END);
+ fwrite($this->stderr, $line);
+ fseek($this->stderr, $this->incrementalErrorOutputOffset);
+ }
+
+ /**
+ * Gets the command line to be executed.
+ *
+ * @return string The command to execute
+ */
+ public function getCommandLine()
+ {
+ return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
+ }
+
+ /**
+ * Sets the command line to be executed.
+ *
+ * @param string|array $commandline The command to execute
+ *
+ * @return $this
+ *
+ * @deprecated since Symfony 4.2.
+ */
+ public function setCommandLine($commandline)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->commandline = $commandline;
+
+ return $this;
+ }
+
+ /**
+ * Gets the process timeout (max. runtime).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Gets the process idle timeout (max. time since last output).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getIdleTimeout()
+ {
+ return $this->idleTimeout;
+ }
+
+ /**
+ * Sets the process timeout (max. runtime) in seconds.
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setTimeout($timeout)
+ {
+ $this->timeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process idle timeout (max. time since last output).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return $this
+ *
+ * @throws LogicException if the output is disabled
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setIdleTimeout($timeout)
+ {
+ if (null !== $timeout && $this->outputDisabled) {
+ throw new LogicException('Idle timeout can not be set while the output is disabled.');
+ }
+
+ $this->idleTimeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Enables or disables the TTY mode.
+ *
+ * @param bool $tty True to enabled and false to disable
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the TTY mode is not supported
+ */
+ public function setTty($tty)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
+ throw new RuntimeException('TTY mode is not supported on Windows platform.');
+ }
+
+ if ($tty && !self::isTtySupported()) {
+ throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
+ }
+
+ $this->tty = (bool) $tty;
+
+ return $this;
+ }
+
+ /**
+ * Checks if the TTY mode is enabled.
+ *
+ * @return bool true if the TTY mode is enabled, false otherwise
+ */
+ public function isTty()
+ {
+ return $this->tty;
+ }
+
+ /**
+ * Sets PTY mode.
+ *
+ * @param bool $bool
+ *
+ * @return $this
+ */
+ public function setPty($bool)
+ {
+ $this->pty = (bool) $bool;
+
+ return $this;
+ }
+
+ /**
+ * Returns PTY state.
+ *
+ * @return bool
+ */
+ public function isPty()
+ {
+ return $this->pty;
+ }
+
+ /**
+ * Gets the working directory.
+ *
+ * @return string|null The current working directory or null on failure
+ */
+ public function getWorkingDirectory()
+ {
+ if (null === $this->cwd) {
+ // getcwd() will return false if any one of the parent directories does not have
+ // the readable or search mode set, even if the current directory does
+ return getcwd() ?: null;
+ }
+
+ return $this->cwd;
+ }
+
+ /**
+ * Sets the current working directory.
+ *
+ * @param string $cwd The new working directory
+ *
+ * @return $this
+ */
+ public function setWorkingDirectory($cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Gets the environment variables.
+ *
+ * @return array The current environment variables
+ */
+ public function getEnv()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Sets the environment variables.
+ *
+ * Each environment variable value should be a string.
+ * If it is an array, the variable is ignored.
+ * If it is false or null, it will be removed when
+ * env vars are otherwise inherited.
+ *
+ * That happens in PHP when 'argv' is registered into
+ * the $_ENV array for instance.
+ *
+ * @param array $env The new environment variables
+ *
+ * @return $this
+ */
+ public function setEnv(array $env)
+ {
+ // Process can not handle env values that are arrays
+ $env = array_filter($env, function ($value) {
+ return !\is_array($value);
+ });
+
+ $this->env = $env;
+
+ return $this;
+ }
+
+ /**
+ * Gets the Process input.
+ *
+ * @return resource|string|\Iterator|null The Process input
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Sets the input.
+ *
+ * This content will be passed to the underlying process standard input.
+ *
+ * @param string|int|float|bool|resource|\Traversable|null $input The content
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is running
+ */
+ public function setInput($input)
+ {
+ if ($this->isRunning()) {
+ throw new LogicException('Input can not be set while the process is running.');
+ }
+
+ $this->input = ProcessUtils::validateInput(__METHOD__, $input);
+
+ return $this;
+ }
+
+ /**
+ * Sets whether environment variables will be inherited or not.
+ *
+ * @param bool $inheritEnv
+ *
+ * @return $this
+ */
+ public function inheritEnvironmentVariables($inheritEnv = true)
+ {
+ if (!$inheritEnv) {
+ throw new InvalidArgumentException('Not inheriting environment variables is not supported.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Performs a check between the timeout definition and the time the process started.
+ *
+ * In case you run a background process (with the start method), you should
+ * trigger this method regularly to ensure the process timeout
+ *
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function checkTimeout()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+ }
+
+ if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
+ }
+ }
+
+ /**
+ * Returns whether TTY is supported on the current operating system.
+ */
+ public static function isTtySupported(): bool
+ {
+ static $isTtySupported;
+
+ if (null === $isTtySupported) {
+ $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
+ }
+
+ return $isTtySupported;
+ }
+
+ /**
+ * Returns whether PTY is supported on the current operating system.
+ *
+ * @return bool
+ */
+ public static function isPtySupported()
+ {
+ static $result;
+
+ if (null !== $result) {
+ return $result;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return $result = false;
+ }
+
+ return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
+ }
+
+ /**
+ * Creates the descriptors needed by the proc_open.
+ */
+ private function getDescriptors(): array
+ {
+ if ($this->input instanceof \Iterator) {
+ $this->input->rewind();
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
+ } else {
+ $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
+ }
+
+ return $this->processPipes->getDescriptors();
+ }
+
+ /**
+ * Builds up the callback used by wait().
+ *
+ * The callbacks adds all occurred output to the specific buffer and calls
+ * the user callback (if present) with the received output.
+ *
+ * @param callable|null $callback The user defined PHP callback
+ *
+ * @return \Closure A PHP closure
+ */
+ protected function buildCallback(callable $callback = null)
+ {
+ if ($this->outputDisabled) {
+ return function ($type, $data) use ($callback): bool {
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ $out = self::OUT;
+
+ return function ($type, $data) use ($callback, $out): bool {
+ if ($out == $type) {
+ $this->addOutput($data);
+ } else {
+ $this->addErrorOutput($data);
+ }
+
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ /**
+ * Updates the status of the process, reads pipes.
+ *
+ * @param bool $blocking Whether to use a blocking read call
+ */
+ protected function updateStatus($blocking)
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ $this->processInformation = proc_get_status($this->process);
+ $running = $this->processInformation['running'];
+
+ $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ if ($this->fallbackStatus && $this->isSigchildEnabled()) {
+ $this->processInformation = $this->fallbackStatus + $this->processInformation;
+ }
+
+ if (!$running) {
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
+ *
+ * @return bool
+ */
+ protected function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!\function_exists('phpinfo')) {
+ return self::$sigchild = false;
+ }
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+
+ return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Reads pipes for the freshest output.
+ *
+ * @param string $caller The name of the method that needs fresh outputs
+ * @param bool $blocking Whether to use blocking calls or not
+ *
+ * @throws LogicException in case output has been disabled or process is not started
+ */
+ private function readPipesForOutput(string $caller, bool $blocking = false)
+ {
+ if ($this->outputDisabled) {
+ throw new LogicException('Output has been disabled.');
+ }
+
+ $this->requireProcessIsStarted($caller);
+
+ $this->updateStatus($blocking);
+ }
+
+ /**
+ * Validates and returns the filtered timeout.
+ *
+ * @throws InvalidArgumentException if the given timeout is a negative number
+ */
+ private function validateTimeout(?float $timeout): ?float
+ {
+ $timeout = (float) $timeout;
+
+ if (0.0 === $timeout) {
+ $timeout = null;
+ } elseif ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ return $timeout;
+ }
+
+ /**
+ * Reads pipes, executes callback.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close file handles or not
+ */
+ private function readPipes(bool $blocking, bool $close)
+ {
+ $result = $this->processPipes->readAndWrite($blocking, $close);
+
+ $callback = $this->callback;
+ foreach ($result as $type => $data) {
+ if (3 !== $type) {
+ $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ }
+
+ /**
+ * Closes process resource, closes file handles, sets the exitcode.
+ *
+ * @return int The exitcode
+ */
+ private function close(): int
+ {
+ $this->processPipes->close();
+ if (\is_resource($this->process)) {
+ proc_close($this->process);
+ }
+ $this->exitcode = $this->processInformation['exitcode'];
+ $this->status = self::STATUS_TERMINATED;
+
+ if (-1 === $this->exitcode) {
+ if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
+ // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
+ $this->exitcode = 128 + $this->processInformation['termsig'];
+ } elseif ($this->isSigchildEnabled()) {
+ $this->processInformation['signaled'] = true;
+ $this->processInformation['termsig'] = -1;
+ }
+ }
+
+ // Free memory from self-reference callback created by buildCallback
+ // Doing so in other contexts like __destruct or by garbage collector is ineffective
+ // Now pipes are closed, so the callback is no longer necessary
+ $this->callback = null;
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Resets data related to the latest run of the process.
+ */
+ private function resetProcessData()
+ {
+ $this->starttime = null;
+ $this->callback = null;
+ $this->exitcode = null;
+ $this->fallbackStatus = [];
+ $this->processInformation = null;
+ $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
+ $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
+ $this->process = null;
+ $this->latestSignal = null;
+ $this->status = self::STATUS_READY;
+ $this->incrementalOutputOffset = 0;
+ $this->incrementalErrorOutputOffset = 0;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ * @param bool $throwException Whether to throw exception in case signal failed
+ *
+ * @return bool True if the signal was sent successfully, false otherwise
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ private function doSignal(int $signal, bool $throwException): bool
+ {
+ if (null === $pid = $this->getPid()) {
+ if ($throwException) {
+ throw new LogicException('Can not send signal on a non running process.');
+ }
+
+ return false;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
+ if ($exitCode && $this->isRunning()) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
+ }
+
+ return false;
+ }
+ } else {
+ if (!$this->isSigchildEnabled()) {
+ $ok = @proc_terminate($this->process, $signal);
+ } elseif (\function_exists('posix_kill')) {
+ $ok = @posix_kill($pid, $signal);
+ } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
+ $ok = false === fgets($pipes[2]);
+ }
+ if (!$ok) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
+ }
+
+ return false;
+ }
+ }
+
+ $this->latestSignal = $signal;
+ $this->fallbackStatus['signaled'] = true;
+ $this->fallbackStatus['exitcode'] = -1;
+ $this->fallbackStatus['termsig'] = $this->latestSignal;
+
+ return true;
+ }
+
+ private function prepareWindowsCommandLine(string $cmd, array &$env)
+ {
+ $uid = uniqid('', true);
+ $varCount = 0;
+ $varCache = [];
+ $cmd = preg_replace_callback(
+ '/"(?:(
+ [^"%!^]*+
+ (?:
+ (?: !LF! | "(?:\^[%!^])?+" )
+ [^"%!^]*+
+ )++
+ ) | [^"]*+ )"/x',
+ function ($m) use (&$env, &$varCache, &$varCount, $uid) {
+ if (!isset($m[1])) {
+ return $m[0];
+ }
+ if (isset($varCache[$m[0]])) {
+ return $varCache[$m[0]];
+ }
+ if (false !== strpos($value = $m[1], "\0")) {
+ $value = str_replace("\0", '?', $value);
+ }
+ if (false === strpbrk($value, "\"%!\n")) {
+ return '"'.$value.'"';
+ }
+
+ $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
+ $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
+ $var = $uid.++$varCount;
+
+ $env[$var] = $value;
+
+ return $varCache[$m[0]] = '!'.$var.'!';
+ },
+ $cmd
+ );
+
+ $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
+ foreach ($this->processPipes->getFiles() as $offset => $filename) {
+ $cmd .= ' '.$offset.'>"'.$filename.'"';
+ }
+
+ return $cmd;
+ }
+
+ /**
+ * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
+ *
+ * @throws LogicException if the process has not run
+ */
+ private function requireProcessIsStarted(string $functionName)
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
+ *
+ * @throws LogicException if the process is not yet terminated
+ */
+ private function requireProcessIsTerminated(string $functionName)
+ {
+ if (!$this->isTerminated()) {
+ throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Escapes a string to be used as a shell argument.
+ */
+ private function escapeArgument(?string $argument): string
+ {
+ if ('' === $argument || null === $argument) {
+ return '""';
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ return "'".str_replace("'", "'\\''", $argument)."'";
+ }
+ if (false !== strpos($argument, "\0")) {
+ $argument = str_replace("\0", '?', $argument);
+ }
+ if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
+ return $argument;
+ }
+ $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
+
+ return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
+ }
+
+ private function getDefaultEnv()
+ {
+ $env = [];
+
+ foreach ($_SERVER as $k => $v) {
+ if (\is_string($v) && false !== $v = getenv($k)) {
+ $env[$k] = $v;
+ }
+ }
+
+ foreach ($_ENV as $k => $v) {
+ if (\is_string($v)) {
+ $env[$k] = $v;
+ }
+ }
+
+ return $env;
+ }
+}
diff --git a/instafeed/vendor/symfony/process/ProcessUtils.php b/instafeed/vendor/symfony/process/ProcessUtils.php
new file mode 100755
index 0000000..2f9c4be
--- /dev/null
+++ b/instafeed/vendor/symfony/process/ProcessUtils.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * ProcessUtils is a bunch of utility methods.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Martin Hasoň
+ */
+class ProcessUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Validates and normalizes a Process input.
+ *
+ * @param string $caller The name of method call that validates the input
+ * @param mixed $input The input to validate
+ *
+ * @return mixed The validated input
+ *
+ * @throws InvalidArgumentException In case the input is not valid
+ */
+ public static function validateInput($caller, $input)
+ {
+ if (null !== $input) {
+ if (\is_resource($input)) {
+ return $input;
+ }
+ if (\is_string($input)) {
+ return $input;
+ }
+ if (is_scalar($input)) {
+ return (string) $input;
+ }
+ if ($input instanceof Process) {
+ return $input->getIterator($input::ITER_SKIP_ERR);
+ }
+ if ($input instanceof \Iterator) {
+ return $input;
+ }
+ if ($input instanceof \Traversable) {
+ return new \IteratorIterator($input);
+ }
+
+ throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller));
+ }
+
+ return $input;
+ }
+}
diff --git a/instafeed/vendor/symfony/process/README.md b/instafeed/vendor/symfony/process/README.md
new file mode 100755
index 0000000..b7ca5b4
--- /dev/null
+++ b/instafeed/vendor/symfony/process/README.md
@@ -0,0 +1,13 @@
+Process Component
+=================
+
+The Process component executes commands in sub-processes.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/process.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/instafeed/vendor/symfony/process/Tests/ErrorProcessInitiator.php b/instafeed/vendor/symfony/process/Tests/ErrorProcessInitiator.php
new file mode 100755
index 0000000..c37aeb5
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/ErrorProcessInitiator.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Process;
+
+require \dirname(__DIR__).'/vendor/autoload.php';
+
+list('e' => $php) = getopt('e:') + ['e' => 'php'];
+
+try {
+ $process = new Process("exec $php -r \"echo 'ready'; trigger_error('error', E_USER_ERROR);\"");
+ $process->start();
+ $process->setTimeout(0.5);
+ while (false === strpos($process->getOutput(), 'ready')) {
+ usleep(1000);
+ }
+ $process->signal(SIGSTOP);
+ $process->wait();
+
+ return $process->getExitCode();
+} catch (ProcessTimedOutException $t) {
+ echo $t->getMessage().PHP_EOL;
+
+ return 1;
+}
diff --git a/instafeed/vendor/symfony/process/Tests/ExecutableFinderTest.php b/instafeed/vendor/symfony/process/Tests/ExecutableFinderTest.php
new file mode 100755
index 0000000..a400273
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/ExecutableFinderTest.php
@@ -0,0 +1,178 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\ExecutableFinder;
+
+/**
+ * @author Chris Smith
+ */
+class ExecutableFinderTest extends TestCase
+{
+ private $path;
+
+ protected function tearDown(): void
+ {
+ if ($this->path) {
+ // Restore path if it was changed.
+ putenv('PATH='.$this->path);
+ }
+ }
+
+ private function setPath($path)
+ {
+ $this->path = getenv('PATH');
+ putenv('PATH='.$path);
+ }
+
+ public function testFind()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath(\dirname(PHP_BINARY));
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindWithDefault()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $expected = 'defaultValue';
+
+ $this->setPath('');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find('foo', $expected);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testFindWithNullAsDefault()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $finder = new ExecutableFinder();
+
+ $result = $finder->find('foo');
+
+ $this->assertNull($result);
+ }
+
+ public function testFindWithExtraDirs()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $extraDirs = [\dirname(PHP_BINARY)];
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindWithOpenBaseDir()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->iniSet('open_basedir', \dirname(PHP_BINARY).PATH_SEPARATOR.'/');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindProcessInOpenBasedir()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ $this->setPath('');
+ $this->iniSet('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), false);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ /**
+ * @requires PHP 5.4
+ */
+ public function testFindBatchExecutableOnWindows()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Can be only tested on windows');
+ }
+
+ $target = tempnam(sys_get_temp_dir(), 'example-windows-executable');
+
+ touch($target);
+ touch($target.'.BAT');
+
+ $this->assertFalse(is_executable($target));
+
+ $this->setPath(sys_get_temp_dir());
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find(basename($target), false);
+
+ unlink($target);
+ unlink($target.'.BAT');
+
+ $this->assertSamePath($target.'.BAT', $result);
+ }
+
+ private function assertSamePath($expected, $tested)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->assertEquals(strtolower($expected), strtolower($tested));
+ } else {
+ $this->assertEquals($expected, $tested);
+ }
+ }
+
+ private function getPhpBinaryName()
+ {
+ return basename(PHP_BINARY, '\\' === \DIRECTORY_SEPARATOR ? '.exe' : '');
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Tests/KillableProcessWithOutput.php b/instafeed/vendor/symfony/process/Tests/KillableProcessWithOutput.php
new file mode 100755
index 0000000..28a6a27
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/KillableProcessWithOutput.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+$outputs = [
+ 'First iteration output',
+ 'Second iteration output',
+ 'One more iteration output',
+ 'This took more time',
+];
+
+$iterationTime = 10000;
+
+foreach ($outputs as $output) {
+ usleep($iterationTime);
+ $iterationTime *= 10;
+ echo $output."\n";
+}
diff --git a/instafeed/vendor/symfony/process/Tests/NonStopableProcess.php b/instafeed/vendor/symfony/process/Tests/NonStopableProcess.php
new file mode 100755
index 0000000..5643259
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/NonStopableProcess.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds.
+ *
+ * @args duration Run this script with a custom duration
+ *
+ * @example `php NonStopableProcess.php 42` will run the script for 42 seconds
+ */
+function handleSignal($signal)
+{
+ switch ($signal) {
+ case SIGTERM:
+ $name = 'SIGTERM';
+ break;
+ case SIGINT:
+ $name = 'SIGINT';
+ break;
+ default:
+ $name = $signal.' (unknown)';
+ break;
+ }
+
+ echo "signal $name\n";
+}
+
+pcntl_signal(SIGTERM, 'handleSignal');
+pcntl_signal(SIGINT, 'handleSignal');
+
+echo 'received ';
+
+$duration = isset($argv[1]) ? (int) $argv[1] : 3;
+$start = microtime(true);
+
+while ($duration > (microtime(true) - $start)) {
+ usleep(10000);
+ pcntl_signal_dispatch();
+}
diff --git a/instafeed/vendor/symfony/process/Tests/PhpExecutableFinderTest.php b/instafeed/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
new file mode 100755
index 0000000..338bb4e
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+
+/**
+ * @author Robert Schönthal
+ */
+class PhpExecutableFinderTest extends TestCase
+{
+ /**
+ * tests find() with the constant PHP_BINARY.
+ */
+ public function testFind()
+ {
+ $f = new PhpExecutableFinder();
+
+ $current = PHP_BINARY;
+ $args = 'phpdbg' === \PHP_SAPI ? ' -qrr' : '';
+
+ $this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP');
+ $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');
+ }
+
+ /**
+ * tests find() with the env var PHP_PATH.
+ */
+ public function testFindArguments()
+ {
+ $f = new PhpExecutableFinder();
+
+ if ('phpdbg' === \PHP_SAPI) {
+ $this->assertEquals($f->findArguments(), ['-qrr'], '::findArguments() returns phpdbg arguments');
+ } else {
+ $this->assertEquals($f->findArguments(), [], '::findArguments() returns no arguments');
+ }
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Tests/PhpProcessTest.php b/instafeed/vendor/symfony/process/Tests/PhpProcessTest.php
new file mode 100755
index 0000000..b7b21eb
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/PhpProcessTest.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\PhpProcess;
+
+class PhpProcessTest extends TestCase
+{
+ public function testNonBlockingWorks()
+ {
+ $expected = 'hello world!';
+ $process = new PhpProcess(<<start();
+ $process->wait();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCommandLine()
+ {
+ $process = new PhpProcess(<<<'PHP'
+getCommandLine();
+
+ $process->start();
+ $this->assertStringContainsString($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');
+
+ $process->wait();
+ $this->assertStringContainsString($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');
+
+ $this->assertSame(PHP_VERSION.\PHP_SAPI, $process->getOutput());
+ }
+
+ public function testPassingPhpExplicitly()
+ {
+ $finder = new PhpExecutableFinder();
+ $php = array_merge([$finder->find(false)], $finder->findArguments());
+
+ $expected = 'hello world!';
+ $script = <<run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php b/instafeed/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
new file mode 100755
index 0000000..9ea8981
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+define('ERR_SELECT_FAILED', 1);
+define('ERR_TIMEOUT', 2);
+define('ERR_READ_FAILED', 3);
+define('ERR_WRITE_FAILED', 4);
+
+$read = [STDIN];
+$write = [STDOUT, STDERR];
+
+stream_set_blocking(STDIN, 0);
+stream_set_blocking(STDOUT, 0);
+stream_set_blocking(STDERR, 0);
+
+$out = $err = '';
+while ($read || $write) {
+ $r = $read;
+ $w = $write;
+ $e = null;
+ $n = stream_select($r, $w, $e, 5);
+
+ if (false === $n) {
+ die(ERR_SELECT_FAILED);
+ } elseif ($n < 1) {
+ die(ERR_TIMEOUT);
+ }
+
+ if (in_array(STDOUT, $w) && strlen($out) > 0) {
+ $written = fwrite(STDOUT, (string) $out, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $out = (string) substr($out, $written);
+ }
+ if (null === $read && '' === $out) {
+ $write = array_diff($write, [STDOUT]);
+ }
+
+ if (in_array(STDERR, $w) && strlen($err) > 0) {
+ $written = fwrite(STDERR, (string) $err, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $err = (string) substr($err, $written);
+ }
+ if (null === $read && '' === $err) {
+ $write = array_diff($write, [STDERR]);
+ }
+
+ if ($r) {
+ $str = fread(STDIN, 32768);
+ if (false !== $str) {
+ $out .= $str;
+ $err .= $str;
+ }
+ if (false === $str || feof(STDIN)) {
+ $read = null;
+ if (!feof(STDIN)) {
+ die(ERR_READ_FAILED);
+ }
+ }
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php b/instafeed/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
new file mode 100755
index 0000000..f820430
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
@@ -0,0 +1,133 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+
+/**
+ * @author Sebastian Marek
+ */
+class ProcessFailedExceptionTest extends TestCase
+{
+ /**
+ * tests ProcessFailedException throws exception if the process was successful.
+ */
+ public function testProcessFailedExceptionThrowsException()
+ {
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful'])->setConstructorArgs([['php']])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(true);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Expected a failed process, but the given process was successful.');
+
+ new ProcessFailedException($process);
+ }
+
+ /**
+ * tests ProcessFailedException uses information from process output
+ * to generate exception message.
+ */
+ public function testProcessFailedExceptionPopulatesInformationFromProcessOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $output = 'Command output';
+ $errorOutput = 'FATAL: Unexpected error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(false);
+
+ $process->expects($this->once())
+ ->method('getOutput')
+ ->willReturn($output);
+
+ $process->expects($this->once())
+ ->method('getErrorOutput')
+ ->willReturn($errorOutput);
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->willReturn($exitCode);
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->willReturn($exitText);
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->willReturn(false);
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->willReturn($workingDirectory);
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}",
+ str_replace("'php'", 'php', $exception->getMessage())
+ );
+ }
+
+ /**
+ * Tests that ProcessFailedException does not extract information from
+ * process output if it was previously disabled.
+ */
+ public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(false);
+
+ $process->expects($this->never())
+ ->method('getOutput');
+
+ $process->expects($this->never())
+ ->method('getErrorOutput');
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->willReturn($exitCode);
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->willReturn($exitText);
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->willReturn(true);
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->willReturn($workingDirectory);
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}",
+ str_replace("'php'", 'php', $exception->getMessage())
+ );
+ }
+}
diff --git a/instafeed/vendor/symfony/process/Tests/ProcessTest.php b/instafeed/vendor/symfony/process/Tests/ProcessTest.php
new file mode 100755
index 0000000..adff6ea
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/ProcessTest.php
@@ -0,0 +1,1515 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\InputStream;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * @author Robert Schönthal
+ */
+class ProcessTest extends TestCase
+{
+ private static $phpBin;
+ private static $process;
+ private static $sigchild;
+
+ public static function setUpBeforeClass(): void
+ {
+ $phpBin = new PhpExecutableFinder();
+ self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === \PHP_SAPI ? 'php' : $phpBin->find());
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ protected function tearDown(): void
+ {
+ if (self::$process) {
+ self::$process->stop(0);
+ self::$process = null;
+ }
+ }
+
+ public function testInvalidCwd()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessageRegExp('/The provided cwd ".*" does not exist\./');
+ try {
+ // Check that it works fine if the CWD exists
+ $cmd = new Process(['echo', 'test'], __DIR__);
+ $cmd->run();
+ } catch (\Exception $e) {
+ $this->fail($e);
+ }
+
+ $cmd = new Process(['echo', 'test'], __DIR__.'/notfound/');
+ $cmd->run();
+ }
+
+ public function testThatProcessDoesNotThrowWarningDuringRun()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is transient on Windows');
+ }
+ @trigger_error('Test Error', E_USER_NOTICE);
+ $process = $this->getProcessForCode('sleep(3)');
+ $process->run();
+ $actualError = error_get_last();
+ $this->assertEquals('Test Error', $actualError['message']);
+ $this->assertEquals(E_USER_NOTICE, $actualError['type']);
+ }
+
+ public function testNegativeTimeoutFromConstructor()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
+ $this->getProcess('', null, null, null, -1);
+ }
+
+ public function testNegativeTimeoutFromSetter()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
+ $p = $this->getProcess('');
+ $p->setTimeout(-1);
+ }
+
+ public function testFloatAndNullTimeout()
+ {
+ $p = $this->getProcess('');
+
+ $p->setTimeout(10);
+ $this->assertSame(10.0, $p->getTimeout());
+
+ $p->setTimeout(null);
+ $this->assertNull($p->getTimeout());
+
+ $p->setTimeout(0.0);
+ $this->assertNull($p->getTimeout());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $p = $this->getProcess([self::$phpBin, __DIR__.'/NonStopableProcess.php', 30]);
+ $p->start();
+
+ while ($p->isRunning() && false === strpos($p->getOutput(), 'received')) {
+ usleep(1000);
+ }
+
+ if (!$p->isRunning()) {
+ throw new \LogicException('Process is not running: '.$p->getErrorOutput());
+ }
+
+ $start = microtime(true);
+ $p->stop(0.1);
+
+ $p->wait();
+
+ $this->assertLessThan(15, microtime(true) - $start);
+ }
+
+ public function testWaitUntilSpecificOutput()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestIncomplete('This test is too transient on Windows, help wanted to improve it');
+ }
+
+ $p = $this->getProcess([self::$phpBin, __DIR__.'/KillableProcessWithOutput.php']);
+ $p->start();
+
+ $start = microtime(true);
+
+ $completeOutput = '';
+ $result = $p->waitUntil(function ($type, $output) use (&$completeOutput) {
+ return false !== strpos($completeOutput .= $output, 'One more');
+ });
+ $this->assertTrue($result);
+ $this->assertLessThan(20, microtime(true) - $start);
+ $this->assertStringStartsWith("First iteration output\nSecond iteration output\nOne more", $completeOutput);
+ $p->stop();
+ }
+
+ public function testWaitUntilCanReturnFalse()
+ {
+ $p = $this->getProcess('echo foo');
+ $p->start();
+ $this->assertFalse($p->waitUntil(function () { return false; }));
+ }
+
+ public function testAllOutputIsActuallyReadOnTermination()
+ {
+ // this code will result in a maximum of 2 reads of 8192 bytes by calling
+ // start() and isRunning(). by the time getOutput() is called the process
+ // has terminated so the internal pipes array is already empty. normally
+ // the call to start() will not read any data as the process will not have
+ // generated output, but this is non-deterministic so we must count it as
+ // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
+ // another byte which will never be read.
+ $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
+
+ $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
+ $p = $this->getProcessForCode($code);
+
+ $p->start();
+
+ // Don't call Process::run nor Process::wait to avoid any read of pipes
+ $h = new \ReflectionProperty($p, 'process');
+ $h->setAccessible(true);
+ $h = $h->getValue($p);
+ $s = @proc_get_status($h);
+
+ while (!empty($s['running'])) {
+ usleep(1000);
+ $s = proc_get_status($h);
+ }
+
+ $o = $p->getOutput();
+
+ $this->assertEquals($expectedOutputSize, \strlen($o));
+ }
+
+ public function testCallbacksAreExecutedWithStart()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->start(function ($type, $buffer) use (&$data) {
+ $data .= $buffer;
+ });
+
+ $process->wait();
+
+ $this->assertSame('foo'.PHP_EOL, $data);
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider responsesCodeProvider
+ */
+ public function testProcessResponses($expected, $getter, $code)
+ {
+ $p = $this->getProcessForCode($code);
+ $p->run();
+
+ $this->assertSame($expected, $p->$getter());
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider pipesCodeProvider
+ */
+ public function testProcessPipes($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($expected);
+ $p->run();
+
+ $this->assertEquals($expectedLength, \strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
+ }
+
+ /**
+ * @dataProvider pipesCodeProvider
+ */
+ public function testSetStreamAsInput($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $stream = fopen('php://temporary', 'w+');
+ fwrite($stream, $expected);
+ rewind($stream);
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($stream);
+ $p->run();
+
+ fclose($stream);
+
+ $this->assertEquals($expectedLength, \strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
+ }
+
+ public function testLiveStreamAsInput()
+ {
+ $stream = fopen('php://memory', 'r+');
+ fwrite($stream, 'hello');
+ rewind($stream);
+
+ $p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p->setInput($stream);
+ $p->start(function ($type, $data) use ($stream) {
+ if ('hello' === $data) {
+ fclose($stream);
+ }
+ });
+ $p->wait();
+
+ $this->assertSame('hello', $p->getOutput());
+ }
+
+ public function testSetInputWhileRunningThrowsAnException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Input can not be set while the process is running.');
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->start();
+ try {
+ $process->setInput('foobar');
+ $process->stop();
+ $this->fail('A LogicException should have been raised.');
+ } catch (LogicException $e) {
+ }
+ $process->stop();
+
+ throw $e;
+ }
+
+ /**
+ * @dataProvider provideInvalidInputValues
+ */
+ public function testInvalidInput($value)
+ {
+ $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
+ $this->expectExceptionMessage('Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources.');
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ }
+
+ public function provideInvalidInputValues()
+ {
+ return [
+ [[]],
+ [new NonStringifiable()],
+ ];
+ }
+
+ /**
+ * @dataProvider provideInputValues
+ */
+ public function testValidInput($expected, $value)
+ {
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ $this->assertSame($expected, $process->getInput());
+ }
+
+ public function provideInputValues()
+ {
+ return [
+ [null, null],
+ ['24.5', 24.5],
+ ['input data', 'input data'],
+ ];
+ }
+
+ public function chainedCommandsOutputProvider()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return [
+ ["2 \r\n2\r\n", '&&', '2'],
+ ];
+ }
+
+ return [
+ ["1\n1\n", ';', '1'],
+ ["2\n2\n", '&&', '2'],
+ ];
+ }
+
+ /**
+ * @dataProvider chainedCommandsOutputProvider
+ */
+ public function testChainedCommandsOutput($expected, $operator, $input)
+ {
+ $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
+ $process->run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCallbackIsExecutedForOutput()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = 'foo' === $buffer;
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+ $p->disableOutput();
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = 'foo' === $buffer;
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testGetErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
+ }
+
+ public function testFlushErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $p->clearErrorOutput();
+ $this->assertEmpty($p->getErrorOutput());
+ }
+
+ /**
+ * @dataProvider provideIncrementalOutput
+ */
+ public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
+ {
+ $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
+
+ $p = $this->getProcessForCode('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');');
+
+ $h = fopen($lock, 'w');
+ flock($h, LOCK_EX);
+
+ $p->start();
+
+ foreach (['foo', 'bar'] as $s) {
+ while (false === strpos($p->$getOutput(), $s)) {
+ usleep(1000);
+ }
+
+ $this->assertSame($s, $p->$getIncrementalOutput());
+ $this->assertSame('', $p->$getIncrementalOutput());
+
+ flock($h, LOCK_UN);
+ }
+
+ fclose($h);
+ }
+
+ public function provideIncrementalOutput()
+ {
+ return [
+ ['getOutput', 'getIncrementalOutput', 'php://stdout'],
+ ['getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'],
+ ];
+ }
+
+ public function testGetOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
+ }
+
+ public function testFlushOutput()
+ {
+ $p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}');
+
+ $p->run();
+ $p->clearOutput();
+ $this->assertEmpty($p->getOutput());
+ }
+
+ public function testZeroAsOutput()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
+ $p = $this->getProcess('echo | set /p dummyName=0');
+ } else {
+ $p = $this->getProcess('printf 0');
+ }
+
+ $p->run();
+ $this->assertSame('0', $p->getOutput());
+ }
+
+ public function testExitCodeCommandFailed()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX exit code');
+ }
+
+ // such command run in bash return an exitcode 127
+ $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
+ $process->run();
+
+ $this->assertGreaterThan(0, $process->getExitCode());
+ }
+
+ public function testTTYCommand()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
+ $process->setTty(true);
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->wait();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testTTYCommandExitCode()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(true);
+ $process->run();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testTTYInWindowsEnvironment()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('TTY mode is not supported on Windows platform.');
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is for Windows platform only');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(false);
+ $process->setTty(true);
+ }
+
+ public function testExitCodeTextIsNullWhenExitCodeIsNull()
+ {
+ $process = $this->getProcess('');
+ $this->assertNull($process->getExitCodeText());
+ }
+
+ public function testPTYCommand()
+ {
+ if (!Process::isPtySupported()) {
+ $this->markTestSkipped('PTY is not supported on this operating system.');
+ }
+
+ $process = $this->getProcess('echo "foo"');
+ $process->setPty(true);
+ $process->run();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ $this->assertEquals("foo\r\n", $process->getOutput());
+ }
+
+ public function testMustRun()
+ {
+ $process = $this->getProcess('echo foo');
+
+ $this->assertSame($process, $process->mustRun());
+ $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
+ }
+
+ public function testSuccessfulMustRunHasCorrectExitCode()
+ {
+ $process = $this->getProcess('echo foo')->mustRun();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testMustRunThrowsException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessFailedException');
+ $process = $this->getProcess('exit 1');
+ $process->mustRun();
+ }
+
+ public function testExitCodeText()
+ {
+ $process = $this->getProcess('');
+ $r = new \ReflectionObject($process);
+ $p = $r->getProperty('exitcode');
+ $p->setAccessible(true);
+
+ $p->setValue($process, 2);
+ $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
+ }
+
+ public function testStartIsNonBlocking()
+ {
+ $process = $this->getProcessForCode('usleep(500000);');
+ $start = microtime(true);
+ $process->start();
+ $end = microtime(true);
+ $this->assertLessThan(0.4, $end - $start);
+ $process->stop();
+ }
+
+ public function testUpdateStatus()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertGreaterThan(0, \strlen($process->getOutput()));
+ }
+
+ public function testGetExitCodeIsNullOnStart()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertNull($process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCodeIsNullOnWhenStartingAgain()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->run();
+ $this->assertEquals(0, $process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCode()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertSame(0, $process->getExitCode());
+ }
+
+ public function testStatus()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertFalse($process->isRunning());
+ $this->assertFalse($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_READY, $process->getStatus());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertTrue($process->isTerminated());
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testStop()
+ {
+ $process = $this->getProcessForCode('sleep(31);');
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop();
+ $this->assertFalse($process->isRunning());
+ }
+
+ public function testIsSuccessful()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsSuccessfulOnlyAfterTerminated()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->start();
+
+ $this->assertFalse($process->isSuccessful());
+
+ $process->wait();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsNotSuccessful()
+ {
+ $process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
+ $process->run();
+ $this->assertFalse($process->isSuccessful());
+ }
+
+ public function testProcessIsNotSignaled()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertFalse($process->hasBeenSignaled());
+ }
+
+ public function testProcessWithoutTermSignal()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertEquals(0, $process->getTermSignal());
+ }
+
+ public function testProcessIsSignaledIfStopped()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcessForCode('sleep(32);');
+ $process->start();
+ $process->stop();
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
+ }
+
+ public function testProcessThrowsExceptionWhenExternallySignaled()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessSignaledException');
+ $this->expectExceptionMessage('The process has been signaled with signal "9".');
+ if (!\function_exists('posix_kill')) {
+ $this->markTestSkipped('Function posix_kill is required.');
+ }
+
+ if (self::$sigchild) {
+ $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
+ }
+
+ $process = $this->getProcessForCode('sleep(32.1);');
+ $process->start();
+ posix_kill($process->getPid(), 9); // SIGKILL
+
+ $process->wait();
+ }
+
+ public function testRestart()
+ {
+ $process1 = $this->getProcessForCode('echo getmypid();');
+ $process1->run();
+ $process2 = $process1->restart();
+
+ $process2->wait(); // wait for output
+
+ // Ensure that both processed finished and the output is numeric
+ $this->assertFalse($process1->isRunning());
+ $this->assertFalse($process2->isRunning());
+ $this->assertIsNumeric($process1->getOutput());
+ $this->assertIsNumeric($process2->getOutput());
+
+ // Ensure that restart returned a new process by check that the output is different
+ $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
+ }
+
+ public function testRunProcessWithTimeout()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->run();
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testIterateOverProcessWithTimeout()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->start();
+ foreach ($process as $buffer);
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testCheckTimeoutOnNonStartedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $this->assertNull($process->checkTimeout());
+ }
+
+ public function testCheckTimeoutOnTerminatedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->checkTimeout());
+ }
+
+ public function testCheckTimeoutOnStartedProcess()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(33);');
+ $process->setTimeout(0.1);
+
+ $process->start();
+ $start = microtime(true);
+
+ try {
+ while ($process->isRunning()) {
+ $process->checkTimeout();
+ usleep(100000);
+ }
+ $this->fail('A ProcessTimedOutException should have been raised');
+ } catch (ProcessTimedOutException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testIdleTimeout()
+ {
+ $process = $this->getProcessForCode('sleep(34);');
+ $process->setTimeout(60);
+ $process->setIdleTimeout(0.1);
+
+ try {
+ $process->run();
+
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isIdleTimeout());
+ $this->assertFalse($e->isGeneralTimeout());
+ $this->assertEquals(0.1, $e->getExceededTimeout());
+ }
+ }
+
+ public function testIdleTimeoutNotExceededWhenOutputIsSent()
+ {
+ $process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
+ $process->setTimeout(1);
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'foo')) {
+ usleep(1000);
+ }
+
+ $process->setIdleTimeout(0.5);
+
+ try {
+ $process->wait();
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
+ $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
+ $this->assertEquals(1, $e->getExceededTimeout());
+ }
+ }
+
+ public function testStartAfterATimeout()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(35);');
+ $process->setTimeout(0.1);
+
+ try {
+ $process->run();
+ $this->fail('A ProcessTimedOutException should have been raised.');
+ } catch (ProcessTimedOutException $e) {
+ }
+ $this->assertFalse($process->isRunning());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function testGetPid()
+ {
+ $process = $this->getProcessForCode('sleep(36);');
+ $process->start();
+ $this->assertGreaterThan(0, $process->getPid());
+ $process->stop(0);
+ }
+
+ public function testGetPidIsNullBeforeStart()
+ {
+ $process = $this->getProcess('foo');
+ $this->assertNull($process->getPid());
+ }
+
+ public function testGetPidIsNullAfterRun()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->getPid());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testSignal()
+ {
+ $process = $this->getProcess([self::$phpBin, __DIR__.'/SignalListener.php']);
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'Caught')) {
+ usleep(1000);
+ }
+ $process->signal(SIGUSR1);
+ $process->wait();
+
+ $this->assertEquals('Caught SIGUSR1', $process->getOutput());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testExitCodeIsAvailableAfterSignal()
+ {
+ $process = $this->getProcess('sleep 4');
+ $process->start();
+ $process->signal(SIGKILL);
+
+ while ($process->isRunning()) {
+ usleep(10000);
+ }
+
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertFalse($process->isSuccessful());
+ $this->assertEquals(137, $process->getExitCode());
+ }
+
+ public function testSignalProcessNotRunning()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Can not send signal on a non running process.');
+ $process = $this->getProcess('foo');
+ $process->signal(1); // SIGHUP
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedARunningProcess
+ */
+ public function testMethodsThatNeedARunningProcess($method)
+ {
+ $process = $this->getProcess('foo');
+
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage(sprintf('Process must be started before calling %s.', $method));
+
+ $process->{$method}();
+ }
+
+ public function provideMethodsThatNeedARunningProcess()
+ {
+ return [
+ ['getOutput'],
+ ['getIncrementalOutput'],
+ ['getErrorOutput'],
+ ['getIncrementalErrorOutput'],
+ ['wait'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedATerminatedProcess
+ */
+ public function testMethodsThatNeedATerminatedProcess($method)
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Process must be terminated before calling');
+ $process = $this->getProcessForCode('sleep(37);');
+ $process->start();
+ try {
+ $process->{$method}();
+ $process->stop(0);
+ $this->fail('A LogicException must have been thrown');
+ } catch (\Exception $e) {
+ }
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function provideMethodsThatNeedATerminatedProcess()
+ {
+ return [
+ ['hasBeenSignaled'],
+ ['getTermSignal'],
+ ['hasBeenStopped'],
+ ['getStopSignal'],
+ ];
+ }
+
+ public function testWrongSignal()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('POSIX signals do not work on Windows');
+ }
+
+ $process = $this->getProcessForCode('sleep(38);');
+ $process->start();
+ try {
+ $process->signal(-4);
+ $this->fail('A RuntimeException must have been thrown');
+ } catch (RuntimeException $e) {
+ $process->stop(0);
+ }
+
+ throw $e;
+ }
+
+ public function testDisableOutputDisablesTheOutput()
+ {
+ $p = $this->getProcess('foo');
+ $this->assertFalse($p->isOutputDisabled());
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ $p->enableOutput();
+ $this->assertFalse($p->isOutputDisabled());
+ }
+
+ public function testDisableOutputWhileRunningThrowsException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('Disabling output while the process is running is not possible.');
+ $p = $this->getProcessForCode('sleep(39);');
+ $p->start();
+ $p->disableOutput();
+ }
+
+ public function testEnableOutputWhileRunningThrowsException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('Enabling output while the process is running is not possible.');
+ $p = $this->getProcessForCode('sleep(40);');
+ $p->disableOutput();
+ $p->start();
+ $p->enableOutput();
+ }
+
+ public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
+ {
+ $p = $this->getProcess('echo foo');
+ $p->disableOutput();
+ $p->run();
+ $p->enableOutput();
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ }
+
+ public function testDisableOutputWhileIdleTimeoutIsSet()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Output can not be disabled while an idle timeout is set.');
+ $process = $this->getProcess('foo');
+ $process->setIdleTimeout(1);
+ $process->disableOutput();
+ }
+
+ public function testSetIdleTimeoutWhileOutputIsDisabled()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('timeout can not be set while the output is disabled.');
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $process->setIdleTimeout(1);
+ }
+
+ public function testSetNullIdleTimeoutWhileOutputIsDisabled()
+ {
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $this->assertSame($process, $process->setIdleTimeout(null));
+ }
+
+ /**
+ * @dataProvider provideOutputFetchingMethods
+ */
+ public function testGetOutputWhileDisabled($fetchMethod)
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Output has been disabled.');
+ $p = $this->getProcessForCode('sleep(41);');
+ $p->disableOutput();
+ $p->start();
+ $p->{$fetchMethod}();
+ }
+
+ public function provideOutputFetchingMethods()
+ {
+ return [
+ ['getOutput'],
+ ['getIncrementalOutput'],
+ ['getErrorOutput'],
+ ['getIncrementalErrorOutput'],
+ ];
+ }
+
+ public function testStopTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(42);');
+ $process->run(function () use ($process) {
+ $process->stop();
+ });
+ $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testKillSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(43);');
+ $process->run(function () use ($process) {
+ $process->signal(9); // SIGKILL
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testTermSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(44);');
+ $process->run(function () use ($process) {
+ $process->signal(15); // SIGTERM
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function responsesCodeProvider()
+ {
+ return [
+ //expected output / getter / code to execute
+ // [1,'getExitCode','exit(1);'],
+ // [true,'isSuccessful','exit();'],
+ ['output', 'getOutput', 'echo \'output\';'],
+ ];
+ }
+
+ public function pipesCodeProvider()
+ {
+ $variations = [
+ 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
+ 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
+ ];
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ // Avoid XL buffers on Windows because of https://bugs.php.net/65650
+ $sizes = [1, 2, 4, 8];
+ } else {
+ $sizes = [1, 16, 64, 1024, 4096];
+ }
+
+ $codes = [];
+ foreach ($sizes as $size) {
+ foreach ($variations as $code) {
+ $codes[] = [$code, $size];
+ }
+ }
+
+ return $codes;
+ }
+
+ /**
+ * @dataProvider provideVariousIncrementals
+ */
+ public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
+ {
+ $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null);
+ $process->start();
+ $result = '';
+ $limit = microtime(true) + 3;
+ $expected = '012';
+
+ while ($result !== $expected && microtime(true) < $limit) {
+ $result .= $process->$method();
+ }
+
+ $this->assertSame($expected, $result);
+ $process->stop();
+ }
+
+ public function provideVariousIncrementals()
+ {
+ return [
+ ['php://stdout', 'getIncrementalOutput'],
+ ['php://stderr', 'getIncrementalErrorOutput'],
+ ];
+ }
+
+ public function testIteratorInput()
+ {
+ $input = function () {
+ yield 'ping';
+ yield 'pong';
+ };
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
+ $process->run();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testSimpleInputStream()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('echo \'ping\'; echo fread(STDIN, 4); echo fread(STDIN, 4);');
+ $process->setInput($input);
+
+ $process->start(function ($type, $data) use ($input) {
+ if ('ping' === $data) {
+ $input->write('pang');
+ } elseif (!$input->isClosed()) {
+ $input->write('pong');
+ $input->close();
+ }
+ });
+
+ $process->wait();
+ $this->assertSame('pingpangpong', $process->getOutput());
+ }
+
+ public function testInputStreamWithCallable()
+ {
+ $i = 0;
+ $stream = fopen('php://memory', 'w+');
+ $stream = function () use ($stream, &$i) {
+ if ($i < 3) {
+ rewind($stream);
+ fwrite($stream, ++$i);
+ rewind($stream);
+
+ return $stream;
+ }
+
+ return null;
+ };
+
+ $input = new InputStream();
+ $input->onEmpty($stream);
+ $input->write($stream());
+
+ $process = $this->getProcessForCode('echo fread(STDIN, 3);');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ $input->close();
+ });
+
+ $process->wait();
+ $this->assertSame('123', $process->getOutput());
+ }
+
+ public function testInputStreamWithGenerator()
+ {
+ $input = new InputStream();
+ $input->onEmpty(function ($input) {
+ yield 'pong';
+ $input->close();
+ });
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $process->setInput($input);
+ $process->start();
+ $input->write('ping');
+ $process->wait();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testInputStreamOnEmpty()
+ {
+ $i = 0;
+ $input = new InputStream();
+ $input->onEmpty(function () use (&$i) { ++$i; });
+
+ $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ if ('123' === $data) {
+ $input->close();
+ }
+ });
+ $process->wait();
+
+ $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
+ $this->assertSame('123456', $process->getOutput());
+ }
+
+ public function testIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');
+ $process->setInput($input);
+ $process->start();
+ $output = [];
+
+ foreach ($process as $type => $data) {
+ $output[] = [$type, $data];
+ break;
+ }
+ $expectedOutput = [
+ [$process::OUT, '123'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(345);
+
+ foreach ($process as $type => $data) {
+ $output[] = [$type, $data];
+ }
+
+ $this->assertSame('', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = [
+ [$process::OUT, '123'],
+ [$process::ERR, '234'],
+ [$process::OUT, '345'],
+ [$process::ERR, '456'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testNonBlockingNorClearingIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
+ $process->setInput($input);
+ $process->start();
+ $output = [];
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ $output[] = [$type, $data];
+ break;
+ }
+ $expectedOutput = [
+ [$process::OUT, ''],
+ ];
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(123);
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ if ('' !== $data) {
+ $output[] = [$type, $data];
+ }
+ }
+
+ $this->assertSame('123', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = [
+ [$process::OUT, ''],
+ [$process::OUT, '123'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testChainedProcesses()
+ {
+ $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
+ $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p2->setInput($p1);
+
+ $p1->start();
+ $p2->run();
+
+ $this->assertSame('123', $p1->getErrorOutput());
+ $this->assertSame('', $p1->getOutput());
+ $this->assertSame('', $p2->getErrorOutput());
+ $this->assertSame('456', $p2->getOutput());
+ }
+
+ public function testSetBadEnv()
+ {
+ $process = $this->getProcess('echo hello');
+ $process->setEnv(['bad%%' => '123']);
+ $process->inheritEnvironmentVariables(true);
+
+ $process->run();
+
+ $this->assertSame('hello'.PHP_EOL, $process->getOutput());
+ $this->assertSame('', $process->getErrorOutput());
+ }
+
+ public function testEnvBackupDoesNotDeleteExistingVars()
+ {
+ putenv('existing_var=foo');
+ $_ENV['existing_var'] = 'foo';
+ $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
+ $process->setEnv(['existing_var' => 'bar', 'new_test_var' => 'foo']);
+ $process->inheritEnvironmentVariables();
+
+ $process->run();
+
+ $this->assertSame('foo', $process->getOutput());
+ $this->assertSame('foo', getenv('existing_var'));
+ $this->assertFalse(getenv('new_test_var'));
+
+ putenv('existing_var');
+ unset($_ENV['existing_var']);
+ }
+
+ public function testEnvIsInherited()
+ {
+ $process = $this->getProcessForCode('echo serialize($_SERVER);', null, ['BAR' => 'BAZ', 'EMPTY' => '']);
+
+ putenv('FOO=BAR');
+ $_ENV['FOO'] = 'BAR';
+
+ $process->run();
+
+ $expected = ['BAR' => 'BAZ', 'EMPTY' => '', 'FOO' => 'BAR'];
+ $env = array_intersect_key(unserialize($process->getOutput()), $expected);
+
+ $this->assertEquals($expected, $env);
+
+ putenv('FOO');
+ unset($_ENV['FOO']);
+ }
+
+ public function testGetCommandLine()
+ {
+ $p = new Process(['/usr/bin/php']);
+
+ $expected = '\\' === \DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'";
+ $this->assertSame($expected, $p->getCommandLine());
+ }
+
+ /**
+ * @dataProvider provideEscapeArgument
+ */
+ public function testEscapeArgument($arg)
+ {
+ $p = new Process([self::$phpBin, '-r', 'echo $argv[1];', $arg]);
+ $p->run();
+
+ $this->assertSame((string) $arg, $p->getOutput());
+ }
+
+ public function testRawCommandLine()
+ {
+ $p = Process::fromShellCommandline(sprintf('"%s" -r %s "a" "" "b"', self::$phpBin, escapeshellarg('print_r($argv);')));
+ $p->run();
+
+ $expected = << -
+ [1] => a
+ [2] =>
+ [3] => b
+)
+
+EOTXT;
+ $this->assertSame($expected, str_replace('Standard input code', '-', $p->getOutput()));
+ }
+
+ public function provideEscapeArgument()
+ {
+ yield ['a"b%c%'];
+ yield ['a"b^c^'];
+ yield ["a\nb'c"];
+ yield ['a^b c!'];
+ yield ["a!b\tc"];
+ yield ['a\\\\"\\"'];
+ yield ['éÉèÈàÀöä'];
+ yield [null];
+ yield [1];
+ yield [1.1];
+ }
+
+ public function testEnvArgument()
+ {
+ $env = ['FOO' => 'Foo', 'BAR' => 'Bar'];
+ $cmd = '\\' === \DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
+ $p = Process::fromShellCommandline($cmd, null, $env);
+ $p->run(null, ['BAR' => 'baR', 'BAZ' => 'baZ']);
+
+ $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
+ $this->assertSame($env, $p->getEnv());
+ }
+
+ public function testWaitStoppedDeadProcess()
+ {
+ $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/ErrorProcessInitiator.php -e '.self::$phpBin);
+ $process->start();
+ $process->setTimeout(2);
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ }
+
+ /**
+ * @param string $commandline
+ * @param string|null $input
+ * @param int $timeout
+ */
+ private function getProcess($commandline, string $cwd = null, array $env = null, $input = null, ?int $timeout = 60): Process
+ {
+ if (\is_string($commandline)) {
+ $process = Process::fromShellCommandline($commandline, $cwd, $env, $input, $timeout);
+ } else {
+ $process = new Process($commandline, $cwd, $env, $input, $timeout);
+ }
+ $process->inheritEnvironmentVariables();
+
+ if (self::$process) {
+ self::$process->stop(0);
+ }
+
+ return self::$process = $process;
+ }
+
+ private function getProcessForCode(string $code, string $cwd = null, array $env = null, $input = null, ?int $timeout = 60): Process
+ {
+ return $this->getProcess([self::$phpBin, '-r', $code], $cwd, $env, $input, $timeout);
+ }
+}
+
+class NonStringifiable
+{
+}
diff --git a/instafeed/vendor/symfony/process/Tests/SignalListener.php b/instafeed/vendor/symfony/process/Tests/SignalListener.php
new file mode 100755
index 0000000..9e30ce3
--- /dev/null
+++ b/instafeed/vendor/symfony/process/Tests/SignalListener.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+pcntl_signal(SIGUSR1, function () { echo 'SIGUSR1'; exit; });
+
+echo 'Caught ';
+
+$n = 0;
+
+while ($n++ < 400) {
+ usleep(10000);
+ pcntl_signal_dispatch();
+}
diff --git a/instafeed/vendor/symfony/process/composer.json b/instafeed/vendor/symfony/process/composer.json
new file mode 100755
index 0000000..d3efd02
--- /dev/null
+++ b/instafeed/vendor/symfony/process/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/process",
+ "type": "library",
+ "description": "Symfony Process Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.1.3"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Process\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ }
+}
diff --git a/instafeed/vendor/symfony/process/phpunit.xml.dist b/instafeed/vendor/symfony/process/phpunit.xml.dist
new file mode 100755
index 0000000..c32f251
--- /dev/null
+++ b/instafeed/vendor/symfony/process/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/instafeed/vendor/valga/fbns-react/.gitignore b/instafeed/vendor/valga/fbns-react/.gitignore
new file mode 100755
index 0000000..86cea2c
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/.gitignore
@@ -0,0 +1,4 @@
+/.idea/
+/vendor/
+/composer.lock
+/.php_cs.cache
diff --git a/instafeed/vendor/valga/fbns-react/.php_cs b/instafeed/vendor/valga/fbns-react/.php_cs
new file mode 100755
index 0000000..97d12a6
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/.php_cs
@@ -0,0 +1,20 @@
+setFinder(
+ \PhpCsFixer\Finder::create()
+ ->in('src')
+ )
+ ->setRules([
+ '@Symfony' => true,
+ // Override @Symfony rules
+ 'pre_increment' => false,
+ 'blank_line_before_statement' => ['statements' => ['return']],
+ 'phpdoc_align' => ['tags' => ['param', 'throws']],
+ 'phpdoc_annotation_without_dot' => false,
+ // Custom rules
+ 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
+ 'ordered_imports' => true,
+ 'phpdoc_order' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ]);
diff --git a/instafeed/vendor/valga/fbns-react/LICENSE b/instafeed/vendor/valga/fbns-react/LICENSE
new file mode 100755
index 0000000..112f1c7
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 Abyr Valg
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/instafeed/vendor/valga/fbns-react/README.md b/instafeed/vendor/valga/fbns-react/README.md
new file mode 100755
index 0000000..6161376
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/README.md
@@ -0,0 +1,84 @@
+# fbns-react
+
+A PHP client for the FBNS built on top of ReactPHP.
+
+## Requirements
+
+You need to install the [GMP extension](http://php.net/manual/en/book.gmp.php) to be able to run this code on x86 PHP builds.
+
+## Installation
+
+```sh
+composer require valga/fbns-react
+```
+
+## Basic Usage
+
+```php
+// Set up a FBNS client.
+$loop = \React\EventLoop\Factory::create();
+$client = new \Fbns\Client\Lite($loop);
+
+// Read saved credentials from a storage.
+$auth = new \Fbns\Client\Auth\DeviceAuth();
+try {
+ $auth->read($storage->get('fbns_auth'));
+} catch (\Exception $e) {
+}
+
+// Connect to a broker.
+$connection = new \Fbns\Client\Connection($deviceAuth, USER_AGENT);
+$client->connect(HOSTNAME, PORT, $connection);
+
+// Bind events.
+$client
+ ->on('connect', function (\Fbns\Client\Lite\ConnectResponsePacket $responsePacket) use ($client, $auth, $storage) {
+ // Update credentials and save them to a storage for future use.
+ try {
+ $auth->read($responsePacket->getAuth());
+ $storage->set('fbns_auth', $responsePacket->getAuth());
+ } catch (\Exception $e) {
+ }
+
+ // Register an application.
+ $client->register(PACKAGE_NAME, APPLICATION_ID);
+ })
+ ->on('register', function (\Fbns\Client\Message\Register $message) use ($app) {
+ // Register received token with an application.
+ $app->registerPushToken($message->getToken());
+ })
+ ->on('push', function (\Fbns\Client\Message\Push $message) use ($app) {
+ // Handle received notification payload.
+ $app->handlePushNotification($message->getPayload());
+ });
+
+// Run main loop.
+$loop->run();
+```
+
+## Advanced Usage
+
+```php
+// Set up a proxy.
+$connector = new \React\Socket\Connector($loop);
+$proxy = new \Clue\React\HttpProxy('username:password@127.0.0.1:3128', $connector);
+
+// Disable SSL verification.
+$ssl = new \React\Socket\SecureConnector($proxy, $loop, ['verify_peer' => false, 'verify_peer_name' => false]);
+
+// Enable logging to stdout.
+$logger = new \Monolog\Logger('fbns');
+$logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+
+// Set up a client.
+$client = new \Fbns\Client\Lite($loop, $ssl, $logger);
+
+// Persistence.
+$client->on('disconnect', function () {
+ // Network connection has been closed. You can reestablish it if you want to.
+});
+$client->connect(HOSTNAME, PORT, $connection)
+ ->otherwise(function () {
+ // Connection attempt was unsuccessful, retry with an exponential backoff.
+ });
+```
diff --git a/instafeed/vendor/valga/fbns-react/bin/thrift_debug b/instafeed/vendor/valga/fbns-react/bin/thrift_debug
new file mode 100755
index 0000000..7f00f0b
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/bin/thrift_debug
@@ -0,0 +1,33 @@
+#!/usr/bin/env php
+ 1) {
+ $data = @file_get_contents($argv[1]);
+} elseif (!posix_isatty(STDIN)) {
+ $data = @stream_get_contents(STDIN);
+} else {
+ echo 'Usage: ', $argv[0], ' [FILE]', PHP_EOL;
+ echo 'Dump the contents of Thrift FILE.', PHP_EOL;
+ echo PHP_EOL;
+ echo 'With no FILE read standard input.', PHP_EOL;
+
+ exit(2);
+}
+
+if ($data === false) {
+ fwrite(STDERR, 'Failed to read the input.'.PHP_EOL);
+
+ exit(1);
+}
+
+try {
+ new \Fbns\Client\Thrift\Debug($data);
+} catch (\Exception $e) {
+ fwrite(STDERR, $e->getMessage().PHP_EOL);
+
+ exit(1);
+}
+
+exit(0);
diff --git a/instafeed/vendor/valga/fbns-react/composer.json b/instafeed/vendor/valga/fbns-react/composer.json
new file mode 100755
index 0000000..361799c
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "valga/fbns-react",
+ "description": "A PHP client for the FBNS built on top of ReactPHP",
+ "keywords": [
+ "FBNS",
+ "Client",
+ "PHP"
+ ],
+ "type": "library",
+ "minimum-stability": "stable",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Abyr Valg",
+ "email": "valga.github@abyrga.ru"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Fbns\\Client\\": "src/"
+ }
+ },
+ "require": {
+ "php": "~5.6|~7.0",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "evenement/evenement": "~2.0|~3.0",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8",
+ "binsoul/net-mqtt": "~0.2",
+ "psr/log": "~1.0"
+ },
+ "require-dev": {
+ "monolog/monolog": "~1.23",
+ "friendsofphp/php-cs-fixer": "~2.4"
+ },
+ "suggest": {
+ "ext-event": "For more efficient event loop implementation.",
+ "ext-gmp": "To be able to run this code on x86 PHP builds."
+ },
+ "scripts": {
+ "codestyle": "php-cs-fixer fix --config=.php_cs"
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Auth/DeviceAuth.php b/instafeed/vendor/valga/fbns-react/src/Auth/DeviceAuth.php
new file mode 100755
index 0000000..ae460ae
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Auth/DeviceAuth.php
@@ -0,0 +1,162 @@
+clientId = substr($this->randomUuid(), 0, 20);
+ $this->userId = 0;
+ $this->password = '';
+ $this->deviceSecret = '';
+ $this->deviceId = '';
+ }
+
+ /**
+ * @param string $json
+ */
+ public function read($json)
+ {
+ $data = Json::decode($json);
+ $this->json = $json;
+
+ if (isset($data->ck)) {
+ $this->userId = $data->ck;
+ } else {
+ $this->userId = 0;
+ }
+ if (isset($data->cs)) {
+ $this->password = $data->cs;
+ } else {
+ $this->password = '';
+ }
+ if (isset($data->di)) {
+ $this->deviceId = $data->di;
+ $this->clientId = substr($this->deviceId, 0, 20);
+ } else {
+ $this->deviceId = '';
+ $this->clientId = substr($this->randomUuid(), 0, 20);
+ }
+ if (isset($data->ds)) {
+ $this->deviceSecret = $data->ds;
+ } else {
+ $this->deviceSecret = '';
+ }
+
+ // TODO: sr ?
+ // TODO: rc ?
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->json !== null ? $this->json : '';
+ }
+
+ /**
+ * @return int
+ */
+ public function getUserId()
+ {
+ return $this->userId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDeviceId()
+ {
+ return $this->deviceId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDeviceSecret()
+ {
+ return $this->deviceSecret;
+ }
+
+ /**
+ * @return string
+ */
+ public function getClientType()
+ {
+ return self::TYPE;
+ }
+
+ /**
+ * @return string
+ */
+ public function getClientId()
+ {
+ return $this->clientId;
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/AuthInterface.php b/instafeed/vendor/valga/fbns-react/src/AuthInterface.php
new file mode 100755
index 0000000..7b40a93
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/AuthInterface.php
@@ -0,0 +1,41 @@
+assertPacketFlags($this->getExpectedPacketFlags());
+ $this->assertRemainingPacketLength(2);
+
+ $this->identifier = $stream->readWord();
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Connection.php b/instafeed/vendor/valga/fbns-react/src/Connection.php
new file mode 100755
index 0000000..9ea5a39
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Connection.php
@@ -0,0 +1,339 @@
+auth = $auth;
+ $this->userAgent = $userAgent;
+
+ $this->clientCapabilities = self::FBNS_CLIENT_CAPABILITIES;
+ $this->endpointCapabilities = self::FBNS_ENDPOINT_CAPABILITIES;
+ $this->publishFormat = self::FBNS_PUBLISH_FORMAT;
+ $this->noAutomaticForeground = true;
+ $this->makeUserAvailableInForeground = false;
+ $this->isInitiallyForeground = false;
+ $this->networkType = 1;
+ $this->networkSubtype = 0;
+ $this->subscribeTopics = [(int) Lite::MESSAGE_TOPIC_ID, (int) Lite::REG_RESP_TOPIC_ID];
+ $this->appId = self::FBNS_APP_ID;
+ $this->clientStack = self::FBNS_CLIENT_STACK;
+ }
+
+ /**
+ * @return string
+ */
+ public function toThrift()
+ {
+ $writer = new Writer();
+
+ $writer->writeString(self::CLIENT_ID, $this->auth->getClientId());
+
+ $writer->writeStruct(self::CLIENT_INFO);
+ $writer->writeInt64(self::USER_ID, $this->auth->getUserId());
+ $writer->writeString(self::USER_AGENT, $this->userAgent);
+ $writer->writeInt64(self::CLIENT_CAPABILITIES, $this->clientCapabilities);
+ $writer->writeInt64(self::ENDPOINT_CAPABILITIES, $this->endpointCapabilities);
+ $writer->writeInt32(self::PUBLISH_FORMAT, $this->publishFormat);
+ $writer->writeBool(self::NO_AUTOMATIC_FOREGROUND, $this->noAutomaticForeground);
+ $writer->writeBool(self::MAKE_USER_AVAILABLE_IN_FOREGROUND, $this->makeUserAvailableInForeground);
+ $writer->writeString(self::DEVICE_ID, $this->auth->getDeviceId());
+ $writer->writeBool(self::IS_INITIALLY_FOREGROUND, $this->isInitiallyForeground);
+ $writer->writeInt32(self::NETWORK_TYPE, $this->networkType);
+ $writer->writeInt32(self::NETWORK_SUBTYPE, $this->networkSubtype);
+ if ($this->clientMqttSessionId === null) {
+ $sessionId = (int) ((microtime(true) - strtotime('Last Monday')) * 1000);
+ } else {
+ $sessionId = $this->clientMqttSessionId;
+ }
+ $writer->writeInt64(self::CLIENT_MQTT_SESSION_ID, $sessionId);
+ $writer->writeList(self::SUBSCRIBE_TOPICS, Compact::TYPE_I32, $this->subscribeTopics);
+ $writer->writeString(self::CLIENT_TYPE, $this->auth->getClientType());
+ $writer->writeInt64(self::APP_ID, $this->appId);
+ $writer->writeString(self::DEVICE_SECRET, $this->auth->getDeviceSecret());
+ $writer->writeInt8(self::CLIENT_STACK, $this->clientStack);
+ $writer->writeStop();
+
+ $writer->writeString(self::PASSWORD, $this->auth->getPassword());
+ $writer->writeStop();
+
+ return (string) $writer;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUserAgent()
+ {
+ return $this->userAgent;
+ }
+
+ /**
+ * @param string $userAgent
+ */
+ public function setUserAgent($userAgent)
+ {
+ $this->userAgent = $userAgent;
+ }
+
+ /**
+ * @return int
+ */
+ public function getClientCapabilities()
+ {
+ return $this->clientCapabilities;
+ }
+
+ /**
+ * @param int $clientCapabilities
+ */
+ public function setClientCapabilities($clientCapabilities)
+ {
+ $this->clientCapabilities = $clientCapabilities;
+ }
+
+ /**
+ * @return int
+ */
+ public function getEndpointCapabilities()
+ {
+ return $this->endpointCapabilities;
+ }
+
+ /**
+ * @param int $endpointCapabilities
+ */
+ public function setEndpointCapabilities($endpointCapabilities)
+ {
+ $this->endpointCapabilities = $endpointCapabilities;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isNoAutomaticForeground()
+ {
+ return $this->noAutomaticForeground;
+ }
+
+ /**
+ * @param bool $noAutomaticForeground
+ */
+ public function setNoAutomaticForeground($noAutomaticForeground)
+ {
+ $this->noAutomaticForeground = $noAutomaticForeground;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isMakeUserAvailableInForeground()
+ {
+ return $this->makeUserAvailableInForeground;
+ }
+
+ /**
+ * @param bool $makeUserAvailableInForeground
+ */
+ public function setMakeUserAvailableInForeground($makeUserAvailableInForeground)
+ {
+ $this->makeUserAvailableInForeground = $makeUserAvailableInForeground;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isInitiallyForeground()
+ {
+ return $this->isInitiallyForeground;
+ }
+
+ /**
+ * @param bool $isInitiallyForeground
+ */
+ public function setIsInitiallyForeground($isInitiallyForeground)
+ {
+ $this->isInitiallyForeground = $isInitiallyForeground;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNetworkType()
+ {
+ return $this->networkType;
+ }
+
+ /**
+ * @param int $networkType
+ */
+ public function setNetworkType($networkType)
+ {
+ $this->networkType = $networkType;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNetworkSubtype()
+ {
+ return $this->networkSubtype;
+ }
+
+ /**
+ * @param int $networkSubtype
+ */
+ public function setNetworkSubtype($networkSubtype)
+ {
+ $this->networkSubtype = $networkSubtype;
+ }
+
+ /**
+ * @return int
+ */
+ public function getClientMqttSessionId()
+ {
+ return $this->clientMqttSessionId;
+ }
+
+ /**
+ * @param int $clientMqttSessionId
+ */
+ public function setClientMqttSessionId($clientMqttSessionId)
+ {
+ $this->clientMqttSessionId = $clientMqttSessionId;
+ }
+
+ /**
+ * @return int[]
+ */
+ public function getSubscribeTopics()
+ {
+ return $this->subscribeTopics;
+ }
+
+ /**
+ * @param int[] $subscribeTopics
+ */
+ public function setSubscribeTopics($subscribeTopics)
+ {
+ $this->subscribeTopics = $subscribeTopics;
+ }
+
+ /**
+ * @return int
+ */
+ public function getAppId()
+ {
+ return $this->appId;
+ }
+
+ /**
+ * @param int $appId
+ */
+ public function setAppId($appId)
+ {
+ $this->appId = $appId;
+ }
+
+ /**
+ * @return int
+ */
+ public function getClientStack()
+ {
+ return $this->clientStack;
+ }
+
+ /**
+ * @param int $clientStack
+ */
+ public function setClientStack($clientStack)
+ {
+ $this->clientStack = $clientStack;
+ }
+
+ /**
+ * @return AuthInterface
+ */
+ public function getAuth()
+ {
+ return $this->auth;
+ }
+
+ /**
+ * @param AuthInterface $auth
+ */
+ public function setAuth(AuthInterface $auth)
+ {
+ $this->auth = $auth;
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Json.php b/instafeed/vendor/valga/fbns-react/src/Json.php
new file mode 100755
index 0000000..24bc1dc
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Json.php
@@ -0,0 +1,28 @@
+ self::MESSAGE_TOPIC,
+ self::REG_REQ_TOPIC_ID => self::REG_REQ_TOPIC,
+ self::REG_RESP_TOPIC_ID => self::REG_RESP_TOPIC,
+ ];
+
+ const TOPIC_TO_ID_ENUM = [
+ self::MESSAGE_TOPIC => self::MESSAGE_TOPIC_ID,
+ self::REG_REQ_TOPIC => self::REG_REQ_TOPIC_ID,
+ self::REG_RESP_TOPIC => self::REG_RESP_TOPIC_ID,
+ ];
+
+ /**
+ * @var LoopInterface
+ */
+ private $loop;
+
+ /**
+ * @var ConnectorInterface
+ */
+ private $connector;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @var ReactMqttClient
+ */
+ private $client;
+
+ /**
+ * @var TimerInterface
+ */
+ private $keepaliveTimer;
+
+ /**
+ * Constructor.
+ *
+ * @param LoopInterface $loop
+ * @param ConnectorInterface|null $connector
+ * @param LoggerInterface|null $logger
+ */
+ public function __construct(LoopInterface $loop, ConnectorInterface $connector = null, LoggerInterface $logger = null)
+ {
+ $this->loop = $loop;
+ if ($connector === null) {
+ $this->connector = new SecureConnector(new Connector($loop), $loop);
+ } else {
+ $this->connector = $connector;
+ }
+ if ($logger !== null) {
+ $this->logger = $logger;
+ } else {
+ $this->logger = new NullLogger();
+ }
+ $this->client = new ReactMqttClient($this->connector, $this->loop, null, new StreamParser());
+
+ $this->client
+ ->on('open', function () {
+ $this->logger->info('Connection has been established.');
+ })
+ ->on('close', function () {
+ $this->logger->info('Network connection has been closed.');
+ $this->cancelKeepaliveTimer();
+ $this->emit('disconnect', [$this]);
+ })
+ ->on('warning', function (\Exception $e) {
+ $this->logger->warning($e->getMessage());
+ })
+ ->on('error', function (\Exception $e) {
+ $this->logger->error($e->getMessage());
+ $this->emit('error', [$e]);
+ })
+ ->on('connect', function (ConnectResponsePacket $responsePacket) {
+ $this->logger->info('Connected to a broker.');
+ $this->setKeepaliveTimer();
+ $this->emit('connect', [$responsePacket]);
+ })
+ ->on('disconnect', function () {
+ $this->logger->info('Disconnected from the broker.');
+ })
+ ->on('message', function (Message $message) {
+ $this->setKeepaliveTimer();
+ $this->onMessage($message);
+ })
+ ->on('publish', function () {
+ $this->logger->info('Publish flow has been completed.');
+ $this->setKeepaliveTimer();
+ })
+ ->on('ping', function () {
+ $this->logger->info('Ping flow has been completed.');
+ $this->setKeepaliveTimer();
+ });
+ }
+
+ private function cancelKeepaliveTimer()
+ {
+ if ($this->keepaliveTimer !== null) {
+ if ($this->keepaliveTimer->isActive()) {
+ $this->logger->info('Existing keepalive timer has been canceled.');
+ $this->keepaliveTimer->cancel();
+ }
+ $this->keepaliveTimer = null;
+ }
+ }
+
+ private function onKeepalive()
+ {
+ $this->logger->info('Keepalive timer has been fired.');
+ $this->cancelKeepaliveTimer();
+ $this->disconnect();
+ }
+
+ private function setKeepaliveTimer()
+ {
+ $this->cancelKeepaliveTimer();
+ $keepaliveInterval = OutgoingConnectFlow::KEEPALIVE;
+ $this->logger->info(sprintf('Setting up keepalive timer to %d seconds', $keepaliveInterval));
+ $this->keepaliveTimer = $this->loop->addTimer($keepaliveInterval, function () {
+ $this->onKeepalive();
+ });
+ }
+
+ /**
+ * @param string $payload
+ */
+ private function onRegister($payload)
+ {
+ try {
+ $message = new Register($payload);
+ } catch (\Exception $e) {
+ $this->logger->warning(sprintf('Failed to decode register message: %s', $e->getMessage()), [$payload]);
+
+ return;
+ }
+
+ $this->emit('register', [$message]);
+ }
+
+ /**
+ * @param string $payload
+ */
+ private function onPush($payload)
+ {
+ try {
+ $message = new Push($payload);
+ } catch (\Exception $e) {
+ $this->logger->warning(sprintf('Failed to decode push message: %s', $e->getMessage()), [$payload]);
+
+ return;
+ }
+
+ $this->emit('push', [$message]);
+ }
+
+ /**
+ * @param Message $message
+ */
+ private function onMessage(Message $message)
+ {
+ $payload = @zlib_decode($message->getPayload());
+ if ($payload === false) {
+ $this->logger->warning('Failed to inflate a payload.');
+
+ return;
+ }
+
+ $topic = $this->unmapTopic($message->getTopic());
+ $this->logger->info(sprintf('Received a message from topic "%s".', $topic), [$payload]);
+
+ switch ($topic) {
+ case self::MESSAGE_TOPIC:
+ $this->onPush($payload);
+ break;
+ case self::REG_RESP_TOPIC:
+ $this->onRegister($payload);
+ break;
+ default:
+ $this->logger->warning(sprintf('Received a message from unknown topic "%s".', $topic), [$payload]);
+ }
+ }
+
+ /**
+ * Establishes a connection to the FBNS server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return PromiseInterface
+ */
+ private function establishConnection($host, $port, Connection $connection, $timeout)
+ {
+ $this->logger->info(sprintf('Connecting to %s:%d...', $host, $port));
+
+ return $this->client->connect($host, $port, $connection, $timeout);
+ }
+
+ /**
+ * Connects to a FBNS server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return PromiseInterface
+ */
+ public function connect($host, $port, Connection $connection, $timeout = 5)
+ {
+ $deferred = new Deferred();
+ $this->disconnect()
+ ->then(function () use ($deferred, $host, $port, $connection, $timeout) {
+ $this->establishConnection($host, $port, $connection, $timeout)
+ ->then(function () use ($deferred) {
+ $deferred->resolve($this);
+ })
+ ->otherwise(function (\Exception $error) use ($deferred) {
+ $deferred->reject($error);
+ });
+ })
+ ->otherwise(function () use ($deferred) {
+ $deferred->reject($this);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @return PromiseInterface
+ */
+ public function disconnect()
+ {
+ if ($this->client->isConnected()) {
+ $deferred = new Deferred();
+ $this->client->disconnect()
+ ->then(function () use ($deferred) {
+ $deferred->resolve($this);
+ })
+ ->otherwise(function () use ($deferred) {
+ $deferred->reject($this);
+ });
+
+ return $deferred->promise();
+ } else {
+ return new FulfilledPromise($this);
+ }
+ }
+
+ /**
+ * Maps human readable topic to its ID.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ private function mapTopic($topic)
+ {
+ if (array_key_exists($topic, self::TOPIC_TO_ID_ENUM)) {
+ $result = self::TOPIC_TO_ID_ENUM[$topic];
+ $this->logger->debug(sprintf('Topic "%s" has been mapped to "%s".', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->logger->debug(sprintf('Topic "%s" does not exist in enum.', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Maps topic ID to human readable name.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ private function unmapTopic($topic)
+ {
+ if (array_key_exists($topic, self::ID_TO_TOPIC_ENUM)) {
+ $result = self::ID_TO_TOPIC_ENUM[$topic];
+ $this->logger->debug(sprintf('Topic ID "%s" has been unmapped to "%s".', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->logger->debug(sprintf('Topic ID "%s" does not exist in enum.', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Publish a message to a topic.
+ *
+ * @param string $topic
+ * @param string $message
+ * @param int $qosLevel
+ *
+ * @return \React\Promise\ExtendedPromiseInterface
+ */
+ private function publish($topic, $message, $qosLevel)
+ {
+ $this->logger->info(sprintf('Sending message to topic "%s".', $topic), [$message]);
+ $topic = $this->mapTopic($topic);
+ $payload = zlib_encode($message, ZLIB_ENCODING_DEFLATE, 9);
+
+ return $this->client->publish(new DefaultMessage($topic, $payload, $qosLevel));
+ }
+
+ /**
+ * Registers an application.
+ *
+ * @param string $packageName
+ * @param string|int $appId
+ *
+ * @return PromiseInterface
+ */
+ public function register($packageName, $appId)
+ {
+ $this->logger->info(sprintf('Registering application "%s" (%s).', $packageName, $appId));
+ $message = json_encode([
+ 'pkg_name' => (string) $packageName,
+ 'appid' => (string) $appId,
+ ]);
+
+ return $this->publish(self::REG_REQ_TOPIC, $message, self::QOS_LEVEL);
+ }
+
+ /**
+ * Checks whether underlying client is connected.
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->client->isConnected();
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Lite/ConnectRequestPacket.php b/instafeed/vendor/valga/fbns-react/src/Lite/ConnectRequestPacket.php
new file mode 100755
index 0000000..d47e7c6
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Lite/ConnectRequestPacket.php
@@ -0,0 +1,198 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+ $this->protocolName = $stream->readString();
+ $this->protocolLevel = $stream->readByte();
+ $this->flags = $stream->readByte();
+ $this->keepAlive = $stream->readWord();
+
+ $payloadLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
+ $this->payload = $stream->read($payloadLength);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeString($this->protocolName);
+ $data->writeByte($this->protocolLevel);
+ $data->writeByte($this->flags);
+ $data->writeWord($this->keepAlive);
+ $data->write($this->payload);
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the protocol level.
+ *
+ * @return int
+ */
+ public function getProtocolLevel()
+ {
+ return $this->protocolLevel;
+ }
+
+ /**
+ * Sets the protocol level.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setProtocolLevel($value)
+ {
+ if ($value != 3) {
+ throw new \InvalidArgumentException(sprintf('Unknown protocol level %d.', $value));
+ }
+
+ $this->protocolLevel = $value;
+ }
+
+ /**
+ * Returns the payload.
+ *
+ * @return string
+ */
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ /**
+ * Sets the payload.
+ *
+ * @param string $value
+ */
+ public function setPayload($value)
+ {
+ $this->payload = $value;
+ }
+
+ /**
+ * Returns the flags.
+ *
+ * @return int
+ */
+ public function getFlags()
+ {
+ return $this->flags;
+ }
+
+ /**
+ * Sets the flags.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setFlags($value)
+ {
+ if ($value > 255) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected a flags lower than 255 but got %d.',
+ $value
+ )
+ );
+ }
+
+ $this->flags = $value;
+ }
+
+ /**
+ * Returns the keep alive time in seconds.
+ *
+ * @return int
+ */
+ public function getKeepAlive()
+ {
+ return $this->keepAlive;
+ }
+
+ /**
+ * Sets the keep alive time in seconds.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setKeepAlive($value)
+ {
+ if ($value > 65535) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected a keep alive time lower than 65535 but got %d.',
+ $value
+ )
+ );
+ }
+
+ $this->keepAlive = $value;
+ }
+
+ /**
+ * Returns the protocol name.
+ *
+ * @return string
+ */
+ public function getProtocolName()
+ {
+ return $this->protocolName;
+ }
+
+ /**
+ * Sets the protocol name.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setProtocolName($value)
+ {
+ $this->assertValidStringLength($value, false);
+
+ $this->protocolName = $value;
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Lite/ConnectResponsePacket.php b/instafeed/vendor/valga/fbns-react/src/Lite/ConnectResponsePacket.php
new file mode 100755
index 0000000..da4f025
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Lite/ConnectResponsePacket.php
@@ -0,0 +1,145 @@
+ [
+ 'Connection accepted',
+ '',
+ ],
+ 1 => [
+ 'Unacceptable protocol version',
+ 'The Server does not support the level of the MQTT protocol requested by the client.',
+ ],
+ 2 => [
+ 'Identifier rejected',
+ 'The client identifier is correct UTF-8 but not allowed by the server.',
+ ],
+ 3 => [
+ 'Server unavailable',
+ 'The network connection has been made but the MQTT service is unavailable',
+ ],
+ 4 => [
+ 'Bad user name or password',
+ 'The data in the user name or password is malformed.',
+ ],
+ 5 => [
+ 'Not authorized',
+ 'The client is not authorized to connect.',
+ ],
+ ];
+
+ /** @var int */
+ private $flags = 0;
+ /** @var int */
+ private $returnCode;
+ /** @var string */
+ private $auth;
+
+ protected static $packetType = Packet::TYPE_CONNACK;
+
+ public function read(PacketStream $stream)
+ {
+ parent::read($stream);
+ $this->assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+ $this->flags = $stream->readByte();
+ $this->returnCode = $stream->readByte();
+
+ $authLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
+ if ($authLength) {
+ $this->auth = $stream->readString();
+ } else {
+ $this->auth = '';
+ }
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeByte($this->flags);
+ $data->writeByte($this->returnCode);
+
+ if ($this->auth !== null && strlen($this->auth)) {
+ $data->writeString($this->auth);
+ }
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the return code.
+ *
+ * @return int
+ */
+ public function getReturnCode()
+ {
+ return $this->returnCode;
+ }
+
+ /**
+ * Indicates if the connection was successful.
+ *
+ * @return bool
+ */
+ public function isSuccess()
+ {
+ return $this->returnCode === 0;
+ }
+
+ /**
+ * Indicates if the connection failed.
+ *
+ * @return bool
+ */
+ public function isError()
+ {
+ return $this->returnCode > 0;
+ }
+
+ /**
+ * Returns a string representation of the returned error code.
+ *
+ * @return int
+ */
+ public function getErrorName()
+ {
+ if (isset(self::$returnCodes[$this->returnCode])) {
+ return self::$returnCodes[$this->returnCode][0];
+ }
+
+ return 'Error '.$this->returnCode;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAuth()
+ {
+ return $this->auth;
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Lite/OutgoingConnectFlow.php b/instafeed/vendor/valga/fbns-react/src/Lite/OutgoingConnectFlow.php
new file mode 100755
index 0000000..eeeffbd
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Lite/OutgoingConnectFlow.php
@@ -0,0 +1,74 @@
+connection = $connection;
+ }
+
+ public function getCode()
+ {
+ return 'connect';
+ }
+
+ public function start()
+ {
+ $packet = new ConnectRequestPacket();
+ $packet->setProtocolLevel(self::PROTOCOL_LEVEL);
+ $packet->setProtocolName(self::PROTOCOL_NAME);
+ $packet->setKeepAlive(self::KEEPALIVE);
+ $packet->setFlags(194);
+ $packet->setPayload(zlib_encode($this->connection->toThrift(), ZLIB_ENCODING_DEFLATE, 9));
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $packet->getPacketType() === Packet::TYPE_CONNACK;
+ }
+
+ public function next(Packet $packet)
+ {
+ /** @var ConnectResponsePacket $packet */
+ if ($packet->isSuccess()) {
+ $this->succeed($packet);
+ } else {
+ $this->fail($packet->getErrorName());
+ }
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Lite/PacketFactory.php b/instafeed/vendor/valga/fbns-react/src/Lite/PacketFactory.php
new file mode 100755
index 0000000..c824b8b
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Lite/PacketFactory.php
@@ -0,0 +1,74 @@
+ ConnectRequestPacket::class,
+ Packet::TYPE_CONNACK => ConnectResponsePacket::class,
+ Packet::TYPE_PUBLISH => PublishRequestPacket::class,
+ Packet::TYPE_PUBACK => PublishAckPacket::class,
+ Packet::TYPE_PUBREC => PublishReceivedPacket::class,
+ Packet::TYPE_PUBREL => PublishReleasePacket::class,
+ Packet::TYPE_PUBCOMP => PublishCompletePacket::class,
+ Packet::TYPE_SUBSCRIBE => SubscribeRequestPacket::class,
+ Packet::TYPE_SUBACK => SubscribeResponsePacket::class,
+ Packet::TYPE_UNSUBSCRIBE => UnsubscribeRequestPacket::class,
+ Packet::TYPE_UNSUBACK => UnsubscribeResponsePacket::class,
+ Packet::TYPE_PINGREQ => PingRequestPacket::class,
+ Packet::TYPE_PINGRESP => PingResponsePacket::class,
+ Packet::TYPE_DISCONNECT => DisconnectRequestPacket::class,
+ ];
+
+ /**
+ * Builds a packet object for the given type.
+ *
+ * @param int $type
+ *
+ * @throws UnknownPacketTypeException
+ *
+ * @return Packet
+ */
+ public function build($type)
+ {
+ if (!isset(self::$mapping[$type])) {
+ throw new UnknownPacketTypeException(sprintf('Unknown packet type %d.', $type));
+ }
+
+ $class = self::$mapping[$type];
+
+ return new $class();
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Lite/ReactFlow.php b/instafeed/vendor/valga/fbns-react/src/Lite/ReactFlow.php
new file mode 100755
index 0000000..91d7095
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Lite/ReactFlow.php
@@ -0,0 +1,120 @@
+decorated = $decorated;
+ $this->deferred = $deferred;
+ $this->packet = $packet;
+ $this->isSilent = $isSilent;
+ }
+
+ public function getCode()
+ {
+ return $this->decorated->getCode();
+ }
+
+ public function start()
+ {
+ $this->packet = $this->decorated->start();
+
+ return $this->packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $this->decorated->accept($packet);
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->packet = $this->decorated->next($packet);
+
+ return $this->packet;
+ }
+
+ public function isFinished()
+ {
+ return $this->decorated->isFinished();
+ }
+
+ public function isSuccess()
+ {
+ return $this->decorated->isSuccess();
+ }
+
+ public function getResult()
+ {
+ return $this->decorated->getResult();
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->decorated->getErrorMessage();
+ }
+
+ /**
+ * Returns the associated deferred.
+ *
+ * @return Deferred
+ */
+ public function getDeferred()
+ {
+ return $this->deferred;
+ }
+
+ /**
+ * Returns the current packet.
+ *
+ * @return Packet
+ */
+ public function getPacket()
+ {
+ return $this->packet;
+ }
+
+ /**
+ * Indicates if the flow should emit events.
+ *
+ * @return bool
+ */
+ public function isSilent()
+ {
+ return $this->isSilent;
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Lite/ReactMqttClient.php b/instafeed/vendor/valga/fbns-react/src/Lite/ReactMqttClient.php
new file mode 100755
index 0000000..49f8e23
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Lite/ReactMqttClient.php
@@ -0,0 +1,695 @@
+connector = $connector;
+ $this->loop = $loop;
+
+ $this->parser = $parser;
+ if ($this->parser === null) {
+ $this->parser = new StreamParser();
+ }
+
+ $this->parser->onError(function (\Exception $e) {
+ $this->emitWarning($e);
+ });
+
+ $this->identifierGenerator = $identifierGenerator;
+ if ($this->identifierGenerator === null) {
+ $this->identifierGenerator = new DefaultIdentifierGenerator();
+ }
+ }
+
+ /**
+ * Return the host.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Return the port.
+ *
+ * @return string
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Indicates if the client is connected.
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->isConnected;
+ }
+
+ /**
+ * Returns the underlying stream or null if the client is not connected.
+ *
+ * @return DuplexStreamInterface|null
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+
+ /**
+ * Connects to a broker.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function connect($host, $port, Connection $connection, $timeout = 5)
+ {
+ if ($this->isConnected || $this->isConnecting) {
+ return new RejectedPromise(new \LogicException('The client is already connected.'));
+ }
+
+ $this->isConnecting = true;
+ $this->isConnected = false;
+
+ $this->host = $host;
+ $this->port = $port;
+
+ $deferred = new Deferred();
+
+ $this->establishConnection($this->host, $this->port, $timeout)
+ ->then(function (DuplexStreamInterface $stream) use ($connection, $deferred, $timeout) {
+ $this->stream = $stream;
+
+ $this->emit('open', [$connection, $this]);
+
+ $this->registerClient($connection, $timeout)
+ ->then(function (ConnectResponsePacket $responsePacket) use ($deferred, $connection) {
+ $this->isConnecting = false;
+ $this->isConnected = true;
+ $this->connection = $connection;
+
+ $this->emit('connect', [$responsePacket, $this]);
+ $deferred->resolve($responsePacket);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred, $connection) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+
+ $this->emit('close', [$connection, $this]);
+ });
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Disconnects from a broker.
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function disconnect()
+ {
+ if (!$this->isConnected || $this->isDisconnecting) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $this->isDisconnecting = true;
+
+ $deferred = new Deferred();
+
+ $connection = new DefaultConnection();
+ $this->startFlow(new OutgoingDisconnectFlow($connection), true)
+ ->then(function () use ($connection, $deferred) {
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+
+ $this->emit('disconnect', [$connection, $this]);
+ $deferred->resolve($connection);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+ })
+ ->otherwise(function () use ($deferred) {
+ $this->isDisconnecting = false;
+ $deferred->reject($this->connection);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Subscribes to a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function subscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingSubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Unsubscribes from a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function unsubscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingUnsubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Publishes a message.
+ *
+ * @param Message $message
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publish(Message $message)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingPublishFlow($message, $this->identifierGenerator));
+ }
+
+ /**
+ * Calls the given generator periodically and publishes the return value.
+ *
+ * @param int $interval
+ * @param Message $message
+ * @param callable $generator
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publishPeriodically($interval, Message $message, callable $generator)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $deferred = new Deferred();
+
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ $interval,
+ function () use ($message, $generator, $deferred) {
+ $this->publish($message->withPayload($generator($message->getTopic())))->then(
+ function ($value) use ($deferred) {
+ $deferred->notify($value);
+ },
+ function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ }
+ );
+ }
+ );
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Emits warnings.
+ *
+ * @param \Exception $e
+ */
+ private function emitWarning(\Exception $e)
+ {
+ $this->emit('warning', [$e, $this]);
+ }
+
+ /**
+ * Emits errors.
+ *
+ * @param \Exception $e
+ */
+ private function emitError(\Exception $e)
+ {
+ $this->emit('error', [$e, $this]);
+ }
+
+ /**
+ * Establishes a network connection to a server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function establishConnection($host, $port, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $timer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('Connection timed out after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->connector->connect($host.':'.$port)
+ ->always(function () use ($timer) {
+ $this->loop->cancelTimer($timer);
+ })
+ ->then(function (DuplexStreamInterface $stream) use ($deferred) {
+ $stream->on('data', function ($data) {
+ $this->handleReceive($data);
+ });
+
+ $stream->on('close', function () {
+ $this->handleClose();
+ });
+
+ $stream->on('error', function (\Exception $e) {
+ $this->handleError($e);
+ });
+
+ $deferred->resolve($stream);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Registers a new client with the broker.
+ *
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function registerClient(Connection $connection, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $responseTimer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('No response after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->startFlow(new OutgoingConnectFlow($connection), true)
+ ->always(function () use ($responseTimer) {
+ $this->loop->cancelTimer($responseTimer);
+ })->then(function (ConnectResponsePacket $responsePacket) use ($deferred) {
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ OutgoingConnectFlow::KEEPALIVE - OutgoingConnectFlow::KEEPALIVE_TIMEOUT,
+ function () {
+ $this->startFlow(new OutgoingPingFlow());
+ }
+ );
+
+ $deferred->resolve($responsePacket);
+ })->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Handles incoming data.
+ *
+ * @param string $data
+ */
+ private function handleReceive($data)
+ {
+ if (!$this->isConnected && !$this->isConnecting) {
+ return;
+ }
+
+ $flowCount = count($this->receivingFlows);
+
+ $packets = $this->parser->push($data);
+ foreach ($packets as $packet) {
+ $this->handlePacket($packet);
+ }
+
+ if ($flowCount > count($this->receivingFlows)) {
+ $this->receivingFlows = array_values($this->receivingFlows);
+ }
+
+ $this->handleSend();
+ }
+
+ /**
+ * Handles an incoming packet.
+ *
+ * @param Packet $packet
+ */
+ private function handlePacket(Packet $packet)
+ {
+ switch ($packet->getPacketType()) {
+ case Packet::TYPE_PUBLISH:
+ /* @var PublishRequestPacket $packet */
+ $message = new DefaultMessage(
+ $packet->getTopic(),
+ $packet->getPayload(),
+ $packet->getQosLevel(),
+ $packet->isRetained(),
+ $packet->isDuplicate()
+ );
+
+ $this->startFlow(new IncomingPublishFlow($message, $packet->getIdentifier()));
+ break;
+ case Packet::TYPE_CONNACK:
+ case Packet::TYPE_PINGRESP:
+ case Packet::TYPE_SUBACK:
+ case Packet::TYPE_UNSUBACK:
+ case Packet::TYPE_PUBREL:
+ case Packet::TYPE_PUBACK:
+ case Packet::TYPE_PUBREC:
+ case Packet::TYPE_PUBCOMP:
+ $flowFound = false;
+ foreach ($this->receivingFlows as $index => $flow) {
+ if ($flow->accept($packet)) {
+ $flowFound = true;
+
+ unset($this->receivingFlows[$index]);
+ $this->continueFlow($flow, $packet);
+
+ break;
+ }
+ }
+
+ if (!$flowFound) {
+ $this->emitWarning(
+ new \LogicException(sprintf('Received unexpected packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ break;
+ default:
+ $this->emitWarning(
+ new \LogicException(sprintf('Cannot handle packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ }
+
+ /**
+ * Handles outgoing packets.
+ */
+ private function handleSend()
+ {
+ $flow = null;
+ if ($this->writtenFlow !== null) {
+ $flow = $this->writtenFlow;
+ $this->writtenFlow = null;
+ }
+
+ if (count($this->sendingFlows) > 0) {
+ $this->writtenFlow = array_shift($this->sendingFlows);
+ $this->stream->write($this->writtenFlow->getPacket());
+ }
+
+ if ($flow !== null) {
+ if ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ } else {
+ $this->receivingFlows[] = $flow;
+ }
+ }
+ }
+
+ /**
+ * Handles closing of the stream.
+ */
+ private function handleClose()
+ {
+ foreach ($this->timer as $timer) {
+ $this->loop->cancelTimer($timer);
+ }
+ $this->timer = [];
+
+ $this->cleanPreviousSession();
+
+ $connection = $this->connection;
+
+ $this->isConnecting = false;
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+ $this->connection = null;
+ $this->stream = null;
+
+ if ($connection !== null) {
+ $this->emit('close', [$connection, $this]);
+ }
+ }
+
+ /**
+ * Handles errors of the stream.
+ *
+ * @param \Exception $e
+ */
+ private function handleError(\Exception $e)
+ {
+ $this->emitError($e);
+ }
+
+ /**
+ * Starts the given flow.
+ *
+ * @param Flow $flow
+ * @param bool $isSilent
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function startFlow(Flow $flow, $isSilent = false)
+ {
+ try {
+ $packet = $flow->start();
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return new RejectedPromise($e);
+ }
+
+ $deferred = new Deferred();
+ $internalFlow = new ReactFlow($flow, $deferred, $packet, $isSilent);
+
+ if ($packet !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $internalFlow;
+ } else {
+ $this->stream->write($packet);
+ $this->writtenFlow = $internalFlow;
+ $this->handleSend();
+ }
+ } else {
+ $this->loop->nextTick(function () use ($internalFlow) {
+ $this->finishFlow($internalFlow);
+ });
+ }
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Continues the given flow.
+ *
+ * @param ReactFlow $flow
+ * @param Packet $packet
+ */
+ private function continueFlow(ReactFlow $flow, Packet $packet)
+ {
+ try {
+ $response = $flow->next($packet);
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return;
+ }
+
+ if ($response !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $flow;
+ } else {
+ $this->stream->write($response);
+ $this->writtenFlow = $flow;
+ $this->handleSend();
+ }
+ } elseif ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ }
+ }
+
+ /**
+ * Finishes the given flow.
+ *
+ * @param ReactFlow $flow
+ */
+ private function finishFlow(ReactFlow $flow)
+ {
+ if ($flow->isSuccess()) {
+ if (!$flow->isSilent()) {
+ $this->emit($flow->getCode(), [$flow->getResult(), $this]);
+ }
+
+ $flow->getDeferred()->resolve($flow->getResult());
+ } else {
+ $result = new \RuntimeException($flow->getErrorMessage());
+ $this->emitWarning($result);
+
+ $flow->getDeferred()->reject($result);
+ }
+ }
+
+ /**
+ * Cleans previous session by rejecting all pending flows.
+ */
+ private function cleanPreviousSession()
+ {
+ $error = new \RuntimeException('Connection has been closed.');
+ foreach ($this->receivingFlows as $receivingFlow) {
+ $receivingFlow->getDeferred()->reject($error);
+ }
+ $this->receivingFlows = [];
+ foreach ($this->sendingFlows as $sendingFlow) {
+ $sendingFlow->getDeferred()->reject($error);
+ }
+ $this->sendingFlows = [];
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Lite/StreamParser.php b/instafeed/vendor/valga/fbns-react/src/Lite/StreamParser.php
new file mode 100755
index 0000000..edfabf6
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Lite/StreamParser.php
@@ -0,0 +1,102 @@
+buffer = new PacketStream();
+ $this->factory = new PacketFactory();
+ }
+
+ /**
+ * Registers an error callback.
+ *
+ * @param callable $callback
+ */
+ public function onError($callback)
+ {
+ $this->errorCallback = $callback;
+ }
+
+ /**
+ * Appends the given data to the internal buffer and parses it.
+ *
+ * @param string $data
+ *
+ * @return Packet[]
+ */
+ public function push($data)
+ {
+ $this->buffer->write($data);
+
+ $result = [];
+ while ($this->buffer->getRemainingBytes() > 0) {
+ $type = $this->buffer->readByte() >> 4;
+ try {
+ $packet = $this->factory->build($type);
+ } catch (UnknownPacketTypeException $e) {
+ $this->handleError($e);
+ continue;
+ }
+
+ $this->buffer->seek(-1);
+ $position = $this->buffer->getPosition();
+ try {
+ $packet->read($this->buffer);
+ $result[] = $packet;
+ $this->buffer->cut();
+ } catch (EndOfStreamException $e) {
+ $this->buffer->setPosition($position);
+ break;
+ } catch (MalformedPacketException $e) {
+ $this->handleError($e);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the registered error callback.
+ *
+ * @param \Throwable $exception
+ */
+ private function handleError($exception)
+ {
+ if ($this->errorCallback !== null) {
+ $callback = $this->errorCallback;
+ $callback($exception);
+ }
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Message/Push.php b/instafeed/vendor/valga/fbns-react/src/Message/Push.php
new file mode 100755
index 0000000..9a04ea2
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Message/Push.php
@@ -0,0 +1,153 @@
+json = $json;
+
+ if (isset($data->token)) {
+ $this->token = (string) $data->token;
+ }
+ if (isset($data->ck)) {
+ $this->connectionKey = (string) $data->ck;
+ }
+ if (isset($data->pn)) {
+ $this->packageName = (string) $data->pn;
+ }
+ if (isset($data->cp)) {
+ $this->collapseKey = (string) $data->cp;
+ }
+ if (isset($data->fbpushnotif)) {
+ $this->payload = (string) $data->fbpushnotif;
+ }
+ if (isset($data->nid)) {
+ $this->notificationId = (string) $data->nid;
+ }
+ if (isset($data->bu)) {
+ $this->isBuffered = (string) $data->bu;
+ }
+ }
+
+ /**
+ * Message constructor.
+ *
+ * @param string $json
+ */
+ public function __construct($json)
+ {
+ $this->parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->json;
+ }
+
+ /**
+ * @return string
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+
+ /**
+ * @return string
+ */
+ public function getConnectionKey()
+ {
+ return $this->connectionKey;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPackageName()
+ {
+ return $this->packageName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCollapseKey()
+ {
+ return $this->collapseKey;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ /**
+ * @return string
+ */
+ public function getNotificationId()
+ {
+ return $this->notificationId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIsBuffered()
+ {
+ return $this->isBuffered;
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Message/Register.php b/instafeed/vendor/valga/fbns-react/src/Message/Register.php
new file mode 100755
index 0000000..6fb9cf5
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Message/Register.php
@@ -0,0 +1,89 @@
+json = $json;
+
+ if (isset($data->pkg_name)) {
+ $this->packageName = (string) $data->pkg_name;
+ }
+ if (isset($data->token)) {
+ $this->token = (string) $data->token;
+ }
+ if (isset($data->error)) {
+ $this->error = (string) $data->error;
+ }
+ }
+
+ /**
+ * Message constructor.
+ *
+ * @param string $json
+ */
+ public function __construct($json)
+ {
+ $this->parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->json;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPackageName()
+ {
+ return $this->packageName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+
+ /**
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Thrift/Compact.php b/instafeed/vendor/valga/fbns-react/src/Thrift/Compact.php
new file mode 100755
index 0000000..f045449
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Thrift/Compact.php
@@ -0,0 +1,24 @@
+handler($context, $field, $value, $type);
+ });
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Thrift/Reader.php b/instafeed/vendor/valga/fbns-react/src/Thrift/Reader.php
new file mode 100755
index 0000000..0b48175
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Thrift/Reader.php
@@ -0,0 +1,263 @@
+buffer = $buffer;
+ $this->position = 0;
+ $this->length = strlen($buffer);
+ $this->field = 0;
+ $this->stack = [];
+ $this->handler = $handler;
+ $this->parse();
+ }
+
+ /**
+ * Parser.
+ */
+ private function parse()
+ {
+ $context = '';
+ while ($this->position < $this->length) {
+ $type = $this->readField();
+ switch ($type) {
+ case Compact::TYPE_STRUCT:
+ array_push($this->stack, $this->field);
+ $this->field = 0;
+ $context = implode('/', $this->stack);
+ break;
+ case Compact::TYPE_STOP:
+ if (!count($this->stack)) {
+ return;
+ }
+ $this->field = array_pop($this->stack);
+ $context = implode('/', $this->stack);
+ break;
+ case Compact::TYPE_LIST:
+ $sizeAndType = $this->readUnsignedByte();
+ $size = $sizeAndType >> 4;
+ $listType = $sizeAndType & 0x0f;
+ if ($size === 0x0f) {
+ $size = $this->readVarint();
+ }
+ $this->handleField($context, $this->field, $this->readList($size, $listType), $listType);
+ break;
+ case Compact::TYPE_TRUE:
+ case Compact::TYPE_FALSE:
+ $this->handleField($context, $this->field, $type === Compact::TYPE_TRUE, $type);
+ break;
+ case Compact::TYPE_BYTE:
+ $this->handleField($context, $this->field, $this->readSignedByte(), $type);
+ break;
+ case Compact::TYPE_I16:
+ case Compact::TYPE_I32:
+ case Compact::TYPE_I64:
+ $this->handleField($context, $this->field, $this->fromZigZag($this->readVarint()), $type);
+ break;
+ case Compact::TYPE_BINARY:
+ $this->handleField($context, $this->field, $this->readString($this->readVarint()), $type);
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param int $size
+ * @param int $type
+ *
+ * @return array
+ */
+ private function readList($size, $type)
+ {
+ $result = [];
+ switch ($type) {
+ case Compact::TYPE_TRUE:
+ case Compact::TYPE_FALSE:
+ for ($i = 0; $i < $size; $i++) {
+ $result[] = $this->readSignedByte() === Compact::TYPE_TRUE;
+ }
+ break;
+ case Compact::TYPE_BYTE:
+ for ($i = 0; $i < $size; $i++) {
+ $result[] = $this->readSignedByte();
+ }
+ break;
+ case Compact::TYPE_I16:
+ case Compact::TYPE_I32:
+ case Compact::TYPE_I64:
+ for ($i = 0; $i < $size; $i++) {
+ $result[] = $this->fromZigZag($this->readVarint());
+ }
+ break;
+ case Compact::TYPE_BINARY:
+ $result[] = $this->readString($this->readVarint());
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return int
+ */
+ private function readField()
+ {
+ $typeAndDelta = ord($this->buffer[$this->position++]);
+ if ($typeAndDelta === Compact::TYPE_STOP) {
+ return Compact::TYPE_STOP;
+ }
+ $delta = $typeAndDelta >> 4;
+ if ($delta === 0) {
+ $this->field = $this->fromZigZag($this->readVarint());
+ } else {
+ $this->field += $delta;
+ }
+ $type = $typeAndDelta & 0x0f;
+
+ return $type;
+ }
+
+ /**
+ * @return int
+ */
+ private function readSignedByte()
+ {
+ $result = $this->readUnsignedByte();
+ if ($result > 0x7f) {
+ $result = 0 - (($result - 1) ^ 0xff);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return int
+ */
+ private function readUnsignedByte()
+ {
+ return ord($this->buffer[$this->position++]);
+ }
+
+ /**
+ * @return int
+ */
+ private function readVarint()
+ {
+ $shift = 0;
+ $result = 0;
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_init($result, 10);
+ }
+ while ($this->position < $this->length) {
+ $byte = ord($this->buffer[$this->position++]);
+ if (PHP_INT_SIZE === 4) {
+ $byte = gmp_init($byte, 10);
+ }
+ $result |= ($byte & 0x7f) << $shift;
+ if (PHP_INT_SIZE === 4) {
+ $byte = (int) gmp_strval($byte, 10);
+ }
+ if ($byte >> 7 === 0) {
+ break;
+ }
+ $shift += 7;
+ }
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_strval($result, 10);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $n
+ *
+ * @return int
+ */
+ private function fromZigZag($n)
+ {
+ if (PHP_INT_SIZE === 4) {
+ $n = gmp_init($n, 10);
+ }
+ $result = ($n >> 1) ^ -($n & 1);
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_strval($result, 10);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $length
+ *
+ * @return string
+ */
+ private function readString($length)
+ {
+ $result = substr($this->buffer, $this->position, $length);
+ $this->position += $length;
+
+ return $result;
+ }
+
+ /**
+ * @param string $context
+ * @param int $field
+ * @param mixed $value
+ * @param int $type
+ */
+ private function handleField($context, $field, $value, $type)
+ {
+ if (!is_callable($this->handler)) {
+ return;
+ }
+ call_user_func($this->handler, $context, $field, $value, $type);
+ }
+}
diff --git a/instafeed/vendor/valga/fbns-react/src/Thrift/Writer.php b/instafeed/vendor/valga/fbns-react/src/Thrift/Writer.php
new file mode 100755
index 0000000..229bdf1
--- /dev/null
+++ b/instafeed/vendor/valga/fbns-react/src/Thrift/Writer.php
@@ -0,0 +1,279 @@
+> ($bits - 1));
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_strval($result, 10);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeByte($number)
+ {
+ $this->buffer .= chr($number);
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeWord($number)
+ {
+ $this->writeVarint($this->toZigZag($number, 16));
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeInt($number)
+ {
+ $this->writeVarint($this->toZigZag($number, 32));
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeLongInt($number)
+ {
+ $this->writeVarint($this->toZigZag($number, 64));
+ }
+
+ /**
+ * @param int $field
+ * @param int $type
+ */
+ private function writeField($field, $type)
+ {
+ $delta = $field - $this->field;
+ if ((0 < $delta) && ($delta <= 15)) {
+ $this->writeByte(($delta << 4) | $type);
+ } else {
+ $this->writeByte($type);
+ $this->writeWord($field);
+ }
+ $this->field = $field;
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeVarint($number)
+ {
+ if (PHP_INT_SIZE === 4) {
+ $number = gmp_init($number, 10);
+ }
+ while (true) {
+ $byte = $number & (~0x7f);
+ if (PHP_INT_SIZE === 4) {
+ $byte = (int) gmp_strval($byte, 10);
+ }
+ if ($byte === 0) {
+ if (PHP_INT_SIZE === 4) {
+ $number = (int) gmp_strval($number, 10);
+ }
+ $this->buffer .= chr($number);
+ break;
+ } else {
+ $byte = ($number & 0xff) | 0x80;
+ if (PHP_INT_SIZE === 4) {
+ $byte = (int) gmp_strval($byte, 10);
+ }
+ $this->buffer .= chr($byte);
+ $number = $number >> 7;
+ }
+ }
+ }
+
+ /**
+ * @param string $data
+ */
+ private function writeBinary($data)
+ {
+ $this->buffer .= $data;
+ }
+
+ /**
+ * @param int $field
+ * @param bool $value
+ */
+ public function writeBool($field, $value)
+ {
+ $this->writeField($field, $value ? Compact::TYPE_TRUE : Compact::TYPE_FALSE);
+ }
+
+ /**
+ * @param int $field
+ * @param string $string
+ */
+ public function writeString($field, $string)
+ {
+ $this->writeField($field, Compact::TYPE_BINARY);
+ $this->writeVarint(strlen($string));
+ $this->writeBinary($string);
+ }
+
+ public function writeStop()
+ {
+ $this->buffer .= chr(Compact::TYPE_STOP);
+ if (count($this->stack)) {
+ $this->field = array_pop($this->stack);
+ }
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt8($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_BYTE);
+ $this->writeByte($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt16($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_I16);
+ $this->writeWord($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt32($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_I32);
+ $this->writeInt($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt64($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_I64);
+ $this->writeLongInt($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $type
+ * @param array $list
+ */
+ public function writeList($field, $type, array $list)
+ {
+ $this->writeField($field, Compact::TYPE_LIST);
+ $size = count($list);
+ if ($size < 0x0f) {
+ $this->writeByte(($size << 4) | $type);
+ } else {
+ $this->writeByte(0xf0 | $type);
+ $this->writeVarint($size);
+ }
+
+ switch ($type) {
+ case Compact::TYPE_TRUE:
+ case Compact::TYPE_FALSE:
+ foreach ($list as $value) {
+ $this->writeByte($value ? Compact::TYPE_TRUE : Compact::TYPE_FALSE);
+ }
+ break;
+ case Compact::TYPE_BYTE:
+ foreach ($list as $number) {
+ $this->writeByte($number);
+ }
+ break;
+ case Compact::TYPE_I16:
+ foreach ($list as $number) {
+ $this->writeWord($number);
+ }
+ break;
+ case Compact::TYPE_I32:
+ foreach ($list as $number) {
+ $this->writeInt($number);
+ }
+ break;
+ case Compact::TYPE_I64:
+ foreach ($list as $number) {
+ $this->writeLongInt($number);
+ }
+ break;
+ case Compact::TYPE_BINARY:
+ foreach ($list as $string) {
+ $this->writeVarint(strlen($string));
+ $this->writeBinary($string);
+ }
+ break;
+ }
+ }
+
+ /**
+ * @param int $field
+ */
+ public function writeStruct($field)
+ {
+ $this->writeField($field, Compact::TYPE_STRUCT);
+ $this->stack[] = $this->field;
+ $this->field = 0;
+ }
+
+ public function __construct()
+ {
+ if (PHP_INT_SIZE === 4 && !extension_loaded('gmp')) {
+ throw new \RuntimeException('You need to install GMP extension to run this code with x86 PHP build.');
+ }
+ $this->buffer = '';
+ $this->field = 0;
+ $this->stack = [];
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->buffer;
+ }
+}
diff --git a/instafeed/vendor/winbox/args/LICENSE b/instafeed/vendor/winbox/args/LICENSE
new file mode 100755
index 0000000..1f5c051
--- /dev/null
+++ b/instafeed/vendor/winbox/args/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 John Stevenson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/instafeed/vendor/winbox/args/README.md b/instafeed/vendor/winbox/args/README.md
new file mode 100755
index 0000000..9daab8c
--- /dev/null
+++ b/instafeed/vendor/winbox/args/README.md
@@ -0,0 +1,50 @@
+Winbox-Args
+===========
+
+[](https://travis-ci.org/johnstevenson/winbox-args)
+[](https://ci.appveyor.com/project/johnstevenson/winbox-args)
+
+A PHP function to escape command-line arguments, which on Windows replaces `escapeshellarg` with a more robust method. Install from [Packagist][packagist] and use it like this:
+
+```php
+$escaped = Winbox\Args::escape($argument);
+```
+
+Alternatively, you can just [copy the code][function] into your own project (but please keep the license attribution and documentation link).
+
+### What it does
+The following transformations are made:
+
+* Double-quotes are escaped with a backslash, with any preceeding backslashes doubled up.
+* The argument is only enclosed in double-quotes if it contains whitespace or is empty.
+* Trailing backslashes are doubled up if the argument is enclosed in double-quotes.
+
+See [How Windows parses the command-line](https://github.com/johnstevenson/winbox-args/wiki/How-Windows-parses-the-command-line) if you would like to know why.
+
+By default, _cmd.exe_ meta characters are also escaped:
+
+* by caret-escaping the transformed argument (if it contains internal double-quotes or `%...%` syntax).
+* or by enclosing the argument in double-quotes.
+
+There are a couple limitations:
+
+1. If _cmd_ is started with _DelayedExpansion_ enabled, `!...!` syntax could expand environment variables.
+2. If the program name requires caret-escaping and contains whitespace, _cmd_ will not recognize it.
+
+See [How cmd.exe parses a command](https://github.com/johnstevenson/winbox-args/wiki/How-cmd.exe-parses-a-command) and [Implementing a solution](https://github.com/johnstevenson/winbox-args/wiki/Implementing-a-solution) for more information.
+
+### Is that it?
+Yup. An entire repo for a tiny function. However, it needs quite a lot of explanation because:
+
+* the command-line parsing rules in Windows are not immediately obvious.
+* PHP generally uses _cmd.exe_ to execute programs and this applies a different set of rules.
+* there is no simple solution.
+
+Full details explaining the different parsing rules, potential pitfalls and limitations can be found in the [Wiki][wiki].
+
+## License
+Winbox-Args is licensed under the MIT License - see the LICENSE file for details.
+
+[function]: https://github.com/johnstevenson/winbox-args/blob/master/src/Args.php#L15
+[wiki]:https://github.com/johnstevenson/winbox-args/wiki/Home
+[packagist]: https://packagist.org/packages/winbox/args
diff --git a/instafeed/vendor/winbox/args/appveyor.yml b/instafeed/vendor/winbox/args/appveyor.yml
new file mode 100755
index 0000000..3f90587
--- /dev/null
+++ b/instafeed/vendor/winbox/args/appveyor.yml
@@ -0,0 +1,22 @@
+build: false
+shallow_clone: false
+platform: 'x86'
+clone_folder: C:\projects\winbox-args
+init:
+ - cinst php
+ - SET PATH=C:\tools\php\;%PATH%
+install:
+ - cd c:\tools\php
+ - copy php.ini-production php.ini
+ - echo date.timezone="UTC" >> php.ini
+ - echo extension_dir=ext >> php.ini
+ - echo extension=php_openssl.dll >> php.ini
+ - echo extension=php_intl.dll >> php.ini
+ - echo extension=php_mbstring.dll >> php.ini
+ - echo extension=php_fileinfo.dll >> php.ini
+ - cd C:\projects\winbox-args
+ - php -r "readfile('https://getcomposer.org/installer');" | php
+ - php composer.phar require phpunit/phpunit:4.* --prefer-dist --dev --no-interaction
+test_script:
+ - cd C:\projects\winbox-args
+ - vendor\bin\phpunit.bat
diff --git a/instafeed/vendor/winbox/args/composer.json b/instafeed/vendor/winbox/args/composer.json
new file mode 100755
index 0000000..0491911
--- /dev/null
+++ b/instafeed/vendor/winbox/args/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "winbox/args",
+ "description": "Windows command-line formatter",
+ "keywords": ["windows", "escape", "command"],
+ "homepage": "http://github.com/johnstevenson/winbox-args",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "autoload": {
+ "psr-4": {
+ "Winbox\\": "src/"
+ }
+ }
+}
diff --git a/instafeed/vendor/winbox/args/src/Args.php b/instafeed/vendor/winbox/args/src/Args.php
new file mode 100755
index 0000000..7f3dd89
--- /dev/null
+++ b/instafeed/vendor/winbox/args/src/Args.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Winbox;
+
+class Args
+{
+ /**
+ * Escapes a string to be used as a shell argument
+ *
+ * Provides a more robust method on Windows than escapeshellarg.
+ *
+ * Feel free to copy this function, but please keep the following notice:
+ * MIT Licensed (c) John Stevenson
+ * See https://github.com/johnstevenson/winbox-args for more information.
+ *
+ * @param string $arg The argument to be escaped
+ * @param bool $meta Additionally escape cmd.exe meta characters
+ *
+ * @return string The escaped argument
+ */
+ public static function escape($arg, $meta = true)
+ {
+ if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
+ return escapeshellarg($arg);
+ }
+
+ $quote = strpbrk($arg, " \t") !== false || $arg === '';
+ $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes);
+
+ if ($meta) {
+ $meta = $dquotes || preg_match('/%[^%]+%/', $arg);
+
+ if (!$meta && !$quote) {
+ $quote = strpbrk($arg, '^&|<>()') !== false;
+ }
+ }
+
+ if ($quote) {
+ $arg = preg_replace('/(\\\\*)$/', '$1$1', $arg);
+ $arg = '"'.$arg.'"';
+ }
+
+ if ($meta) {
+ $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg);
+ }
+
+ return $arg;
+ }
+}
diff --git a/log/dreamyourmansion.log b/log/dreamyourmansion.log
new file mode 100755
index 0000000..b9a715c
--- /dev/null
+++ b/log/dreamyourmansion.log
@@ -0,0 +1,23 @@
+
+--------------------------------------------------------------------------------
+SUB main::CheckParameter
+--------------------------------------------------------------------------------
+
+--------------------------------------------------------------------------------
+SUB main::UndumpFromFile
+--------------------------------------------------------------------------------
+INFO: ./src/db/db_dreamyourmansion.dat has 191 keys.
+
+--------------------------------------------------------------------------------
+SUB main::DirectoryListing
+--------------------------------------------------------------------------------
+$VAR1 = {};
+
+--------------------------------------------------------------------------------
+SUB main::FindNewDataset
+--------------------------------------------------------------------------------
+
+--------------------------------------------------------------------------------
+SUB main::Summary
+--------------------------------------------------------------------------------
+./src/db/db_dreamyourmansion.dat has 191 keys (before 191).
diff --git a/log/vstbestprices.log b/log/vstbestprices.log
new file mode 100755
index 0000000..240dee3
--- /dev/null
+++ b/log/vstbestprices.log
@@ -0,0 +1,23 @@
+
+--------------------------------------------------------------------------------
+SUB main::CheckParameter
+--------------------------------------------------------------------------------
+
+--------------------------------------------------------------------------------
+SUB main::UndumpFromFile
+--------------------------------------------------------------------------------
+INFO: ./src/db/db_vstbestprices.dat has 115 keys.
+
+--------------------------------------------------------------------------------
+SUB main::DirectoryListing
+--------------------------------------------------------------------------------
+$VAR1 = {};
+
+--------------------------------------------------------------------------------
+SUB main::FindNewDataset
+--------------------------------------------------------------------------------
+
+--------------------------------------------------------------------------------
+SUB main::Summary
+--------------------------------------------------------------------------------
+./src/db/db_vstbestprices.dat has 115 keys (before 115).
diff --git a/selenium/__pycache__/pyautogui.cpython-38.pyc b/selenium/__pycache__/pyautogui.cpython-38.pyc
new file mode 100755
index 0000000..d34777d
Binary files /dev/null and b/selenium/__pycache__/pyautogui.cpython-38.pyc differ
diff --git a/selenium/autogui.py b/selenium/autogui.py
new file mode 100755
index 0000000..ec0922e
--- /dev/null
+++ b/selenium/autogui.py
@@ -0,0 +1,37 @@
+#! python3
+import pyautogui
+#import pygetwindow as gw
+import sys
+
+# print('Press Ctrl-C to quit.')
+# try:
+ # while True:
+ # x, y = pyautogui.position()
+ # positionStr = 'X: ' + str(x).rjust(4) + ' Y: ' + str(y).rjust(4)
+ # print(positionStr)
+
+# except KeyboardInterrupt:
+ # print('\nDone.')
+
+file = sys.argv[1]
+print('File received as arg: ', file)
+
+#fw = pyautogui.getActiveWindow()
+#print('Active window: ', fw)
+#print('Maximized: ', fw.isMaximized)
+#print('Minimized: ', fw.isMinimized)
+#print('isActive: ', fw.isActive)
+#print('Title: ', fw.title)
+
+#fileUploadWindow = gw.getWindowsWithTitle('File Upload')[0]
+#print('Target window: ', fileUploadWindow)
+#fileUploadWindow.activate()
+
+#fileUploadWindow = pyautogui.getWindowsWithTitle('File Upload')[0]
+#fileUploadWindow.activate()
+pyautogui.PAUSE = 3
+pyautogui.hotkey('ctrl', 'l')
+pyautogui.typewrite(file, interval=0.1)
+pyautogui.press('enter')
+print('I am done.')
+sys.exit(1)
\ No newline at end of file
diff --git a/selenium/cat.jpg b/selenium/cat.jpg
new file mode 100755
index 0000000..9ea8aed
Binary files /dev/null and b/selenium/cat.jpg differ
diff --git a/selenium/geckodriver b/selenium/geckodriver
new file mode 100755
index 0000000..83c86ff
Binary files /dev/null and b/selenium/geckodriver differ
diff --git a/selenium/geckodriver.exe b/selenium/geckodriver.exe
new file mode 100755
index 0000000..4ce451b
Binary files /dev/null and b/selenium/geckodriver.exe differ
diff --git a/selenium/get_page_source b/selenium/get_page_source
new file mode 100755
index 0000000..616c212
--- /dev/null
+++ b/selenium/get_page_source
@@ -0,0 +1,299 @@
+$VAR1 = '
+
+
+
+ Instagram
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
New Photo Post
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
';
diff --git a/selenium/profiles/adobebestprices/db/db_adobebestprices.dat b/selenium/profiles/adobebestprices/db/db_adobebestprices.dat
new file mode 100755
index 0000000..34aec2c
--- /dev/null
+++ b/selenium/profiles/adobebestprices/db/db_adobebestprices.dat
@@ -0,0 +1 @@
+{"/home/pi/projects/instafeed/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist DANDY v2.1.1 VST AAX [WIN].jpg":{"FILEPATH":"/home/pi/projects/instafeed/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist DANDY v2.1.1 VST AAX [WIN].jpg","TIMESTAMP_UPLOADED":"19000100_000000"}}
\ No newline at end of file
diff --git a/selenium/profiles/adobebestprices/images/Adam Monroe Music Mark 73 v2.5 VST AU AAX.jpg b/selenium/profiles/adobebestprices/images/Adam Monroe Music Mark 73 v2.5 VST AU AAX.jpg
new file mode 100755
index 0000000..bb5c489
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Adam Monroe Music Mark 73 v2.5 VST AU AAX.jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Algonaut Atlas v1.4.4 VST WIN.jpg b/selenium/profiles/adobebestprices/images/Algonaut Atlas v1.4.4 VST WIN.jpg
new file mode 100755
index 0000000..04cabd5
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Algonaut Atlas v1.4.4 VST WIN.jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Ample Bass Metal Ray5 v3.1.0 [WIN & MACOSX].jpg b/selenium/profiles/adobebestprices/images/Ample Bass Metal Ray5 v3.1.0 [WIN & MACOSX].jpg
new file mode 100755
index 0000000..b153390
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Ample Bass Metal Ray5 v3.1.0 [WIN & MACOSX].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Ample Bass P v3.2 WIN & MacOS.jpg b/selenium/profiles/adobebestprices/images/Ample Bass P v3.2 WIN & MacOS.jpg
new file mode 100755
index 0000000..61efbd3
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Ample Bass P v3.2 WIN & MacOS.jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Ample Ethno Ukulele III v3.2 [WIN & MACOSX].jpg b/selenium/profiles/adobebestprices/images/Ample Ethno Ukulele III v3.2 [WIN & MACOSX].jpg
new file mode 100755
index 0000000..b1776b5
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Ample Ethno Ukulele III v3.2 [WIN & MACOSX].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Ample Guitar L v3.3.0 [WIN & MACOSX].jpg b/selenium/profiles/adobebestprices/images/Ample Guitar L v3.3.0 [WIN & MACOSX].jpg
new file mode 100755
index 0000000..0357928
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Ample Guitar L v3.3.0 [WIN & MACOSX].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Ample Guitar LP v3.2 [WIN & MACOSX].jpg b/selenium/profiles/adobebestprices/images/Ample Guitar LP v3.2 [WIN & MACOSX].jpg
new file mode 100755
index 0000000..15bad87
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Ample Guitar LP v3.2 [WIN & MACOSX].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Ample Guitar VC v3.2.0 WIN & MacOSX.jpg b/selenium/profiles/adobebestprices/images/Ample Guitar VC v3.2.0 WIN & MacOSX.jpg
new file mode 100755
index 0000000..0916355
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Ample Guitar VC v3.2.0 WIN & MacOSX.jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Lindell Plugins Bundle 2020 WIN.jpg b/selenium/profiles/adobebestprices/images/Lindell Plugins Bundle 2020 WIN.jpg
new file mode 100755
index 0000000..092e0aa
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Lindell Plugins Bundle 2020 WIN.jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Native Instruments Guitar Rig 6 Pro v6.0.2 [WIN].jpg b/selenium/profiles/adobebestprices/images/Native Instruments Guitar Rig 6 Pro v6.0.2 [WIN].jpg
new file mode 100755
index 0000000..57250d0
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Native Instruments Guitar Rig 6 Pro v6.0.2 [WIN].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Native Instruments Kontakt 6.4.2 [Full Installer] [WIN].jpg b/selenium/profiles/adobebestprices/images/Native Instruments Kontakt 6.4.2 [Full Installer] [WIN].jpg
new file mode 100755
index 0000000..632b9b8
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Native Instruments Kontakt 6.4.2 [Full Installer] [WIN].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Native Instruments Traktor Pro 3 v3.4.0 WIN & MACOSX.jpg b/selenium/profiles/adobebestprices/images/Native Instruments Traktor Pro 3 v3.4.0 WIN & MACOSX.jpg
new file mode 100755
index 0000000..5ed9c9b
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Native Instruments Traktor Pro 3 v3.4.0 WIN & MACOSX.jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Scaler 2 v2.1 VST VST3 AU AAX (WIN & MAC).jpg b/selenium/profiles/adobebestprices/images/Scaler 2 v2.1 VST VST3 AU AAX (WIN & MAC).jpg
new file mode 100755
index 0000000..ea25e32
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Scaler 2 v2.1 VST VST3 AU AAX (WIN & MAC).jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Scavenger Hunt – Future Nostalgia WAV.jpg b/selenium/profiles/adobebestprices/images/Scavenger Hunt – Future Nostalgia WAV.jpg
new file mode 100755
index 0000000..2a82ffa
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Scavenger Hunt – Future Nostalgia WAV.jpg differ
diff --git a/selenium/profiles/adobebestprices/images/Solemn Tones The Odin II VST2 VST3 AU AAX.jpg b/selenium/profiles/adobebestprices/images/Solemn Tones The Odin II VST2 VST3 AU AAX.jpg
new file mode 100755
index 0000000..bc381ac
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/Solemn Tones The Odin II VST2 VST3 AU AAX.jpg differ
diff --git a/selenium/profiles/adobebestprices/images/StudioLinked Bit Machine VST AU.jpg b/selenium/profiles/adobebestprices/images/StudioLinked Bit Machine VST AU.jpg
new file mode 100755
index 0000000..dd63fd5
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/StudioLinked Bit Machine VST AU.jpg differ
diff --git a/selenium/profiles/adobebestprices/images/StudioLinked Dope FX v1.0 [WIN MACOSX].jpg b/selenium/profiles/adobebestprices/images/StudioLinked Dope FX v1.0 [WIN MACOSX].jpg
new file mode 100755
index 0000000..e5a3c6d
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/StudioLinked Dope FX v1.0 [WIN MACOSX].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/StudioLinked Reverse Station v1.0 [WIN MACOSX].png b/selenium/profiles/adobebestprices/images/StudioLinked Reverse Station v1.0 [WIN MACOSX].png
new file mode 100755
index 0000000..684a8f3
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/StudioLinked Reverse Station v1.0 [WIN MACOSX].png differ
diff --git a/selenium/profiles/adobebestprices/images/StudioLinked Trap Plucks v1.0 [WIN MACOSX].jpg b/selenium/profiles/adobebestprices/images/StudioLinked Trap Plucks v1.0 [WIN MACOSX].jpg
new file mode 100755
index 0000000..dcf3938
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/StudioLinked Trap Plucks v1.0 [WIN MACOSX].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/UJAM Beatmaker Bundle 2 VST2 AAX [WIN].jpg b/selenium/profiles/adobebestprices/images/UJAM Beatmaker Bundle 2 VST2 AAX [WIN].jpg
new file mode 100755
index 0000000..d540124
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/UJAM Beatmaker Bundle 2 VST2 AAX [WIN].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/UJAM Finisher Bundle VST AAX [WIN].jpg b/selenium/profiles/adobebestprices/images/UJAM Finisher Bundle VST AAX [WIN].jpg
new file mode 100755
index 0000000..0bd7bb9
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/UJAM Finisher Bundle VST AAX [WIN].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist DANDY v2.1.1 VST AAX [WIN].jpg b/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist DANDY v2.1.1 VST AAX [WIN].jpg
new file mode 100755
index 0000000..fe5435d
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist DANDY v2.1.1 VST AAX [WIN].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist MELLOW v2.1.1 VST AAX [WIN].jpg b/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist MELLOW v2.1.1 VST AAX [WIN].jpg
new file mode 100755
index 0000000..a6167ac
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist MELLOW v2.1.1 VST AAX [WIN].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist ROWDY v2.1.1 VST AAX [WIN].jpg b/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist ROWDY v2.1.1 VST AAX [WIN].jpg
new file mode 100755
index 0000000..694ec33
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist ROWDY v2.1.1 VST AAX [WIN].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist ROYAL v2.1.1 VST AAX [WIN].jpg b/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist ROYAL v2.1.1 VST AAX [WIN].jpg
new file mode 100755
index 0000000..f6a21e4
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/UJAM Virtual Bassist ROYAL v2.1.1 VST AAX [WIN].jpg differ
diff --git a/selenium/profiles/adobebestprices/images/UJAM Virtual Guitarist CARBON v1.0.1 VST AAX [WIN].jpg b/selenium/profiles/adobebestprices/images/UJAM Virtual Guitarist CARBON v1.0.1 VST AAX [WIN].jpg
new file mode 100755
index 0000000..3c0d7d0
Binary files /dev/null and b/selenium/profiles/adobebestprices/images/UJAM Virtual Guitarist CARBON v1.0.1 VST AAX [WIN].jpg differ
diff --git a/selenium/selenium-server-standalone-3.141.59.jar b/selenium/selenium-server-standalone-3.141.59.jar
new file mode 100755
index 0000000..8410e95
Binary files /dev/null and b/selenium/selenium-server-standalone-3.141.59.jar differ
diff --git a/selenium/selenium.pl b/selenium/selenium.pl
new file mode 100755
index 0000000..44bac0a
--- /dev/null
+++ b/selenium/selenium.pl
@@ -0,0 +1,356 @@
+#!usr/bin/perl
+use feature qw(say);
+use JSON::XS qw(encode_json decode_json);
+use Selenium::Firefox::Profile;
+use Data::Dumper;
+use HTML::TreeBuilder::XPath;
+use File::Slurp qw(read_file write_file);
+use Selenium::Remote::WDKeys;
+use Selenium::Firefox;
+use Cwd;
+use Getopt::Long qw(GetOptions);
+use File::Basename;
+use List::Util 'shuffle';
+use Selenium::Waiter qw/wait_until/;
+
+my (%config, $driver_profile, $driver);
+
+sub Init {
+ %config = (
+ 'xpath' => {
+ 'input_username_classic' => '//*[@id="id_username"]',
+ 'input_password_classic' => '//*[@id="id_enc_password"]',
+ 'button_login_classic' => '//*[@id="login-form"]/p[3]/input',
+ 'button_addtohomescreen' => '/html/body/div[4]/div/div/div/div[3]/button[1][text()="Add to Home screen"]',
+ 'button_addtohomescreen_cancel' => '/html/body/div[4]/div/div/div/div[3]/button[2][text()="Cancel"]',
+ 'button_newpost' => '//div[contains(@data-testid, "new-post-button")]',
+ 'input_newpostupload' => '//*[@id="react-root"]/section/nav[2]/div/div/form/input',
+ 'button_expand' => '//span[text()="Expand"]',
+ 'button_next' => '//button[text()="Next"]',
+ 'button_share' => '//button[text()="Share"]',
+ 'textarea_caption' => '//textarea[contains(@placeholder,"Write a caption")]',
+ },
+ 'profile' => {
+ 'adobebestprices' => {
+ 'DBFilepath' => cwd() . '/profiles/adobebestprices/db/db_adobebestprices.dat',
+ 'imageDir' => cwd() . '/profiles/adobebestprices/images',
+ 'filename_as_title' => 1,
+ 'tags' => ['#Beats', '#FLStudio20', '#Producer', '#Ableton', '#Beatmaker', '#Studio', '#ProTools', '#Music', '#DAW', '#LogicPro', '#VST', '#VSTplugins', '#NativeInstruments', '#Drums', '#MIDI', '#Omnisphere', '#Keyscape', '#Trilian', '#Logic', '#Waves', '#Antares', '#AutoTune', '#Kontakt', '#SoundToys', '#FABFilter', '#Arturia', '#Windows', '#MAC'],
+ },
+ },
+ 'dumpToFile' => cwd() . '/get_page_source',
+ 'pyautogui_cmd' => 'python3 ' . cwd() . '/autogui.py',
+ 'profile_selected' => undef,
+ 'wipe_after' => 0,
+ );
+
+ &CheckParameter();
+ &UndumpFromFile();
+ &SelectImage();
+
+ $driver_profile = Selenium::Firefox::Profile->new;
+ $driver_profile->set_preference(
+ 'intl.accept_languages' => 'en-US',
+ # 'general.useragent.override' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15',
+ 'general.useragent.override' => 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16',
+ );
+ # $driver_profile->set_boolean_preference("options.headless" => 1);
+ $driver = Selenium::Remote::Driver->new( 'browser_name' => 'firefox',
+ 'platform' => 'LINUX',
+ 'remote_server_addr' => 'localhost',
+ 'port' => 4444,
+ 'firefox_profile' => $driver_profile,
+ 'extra_capabilities' => {
+ 'moz:firefoxOptions' => {
+ #"args" => [ "--headless" ],
+ },
+ },
+ );
+
+ # $driver = Selenium::Firefox->new( 'browser_name' => 'firefox',
+ # 'platform' => 'LINUX',
+ # 'binary' => '/usr/local/bin/geckodriver',
+ # 'firefox_profile' => $driver_profile,
+ # 'extra_capabilities' => {
+ # 'moz:firefoxOptions' => {
+ # #"args" => [ "--headless" ],
+ # },
+ # },
+ # );
+
+ $driver->set_implicit_wait_timeout(5000);
+ $driver->set_window_size(812, 375);
+
+ say 'starting...';
+ say $driver->get_title();
+ say $driver->get_user_agent();
+}
+
+
+&Init();
+&Login();
+&NewPost();
+&Finish();
+
+sub RndHashtagString {
+ my $i = shift;
+
+ my $RndArr = &RndArrItems($config{profile}{'adobebestprices'}{tags}, $i);
+ my $hastags = join(' ', @$RndArr[0..$#{$RndArr}]);
+
+ return $hastags;
+}
+
+sub RndArrItems {
+ my ($arr_ref, $num_picks) = @_;
+
+ # Shuffled list of indexes into @deck
+ my @shuffled_indexes = shuffle(0..$#{$arr_ref});
+
+ # Get just N of them.
+ my @pick_indexes = @shuffled_indexes[ 0 .. $num_picks - 1 ];
+
+ # Pick cards from @deck
+ my @picks;
+ for my $index (@pick_indexes) {
+ push(@picks, $$arr_ref[$index]);
+ }
+
+ return \@picks;
+}
+
+sub SelectImage {
+ &DirectoryListing();
+ &FindNewDataset();
+ $config{image_backslash} = $config{image_selected};
+ # $config{image_backslash} =~ s/\//\\/g;
+ print Dumper \%config;
+}
+
+sub Login {
+ say 'Logging in...';
+
+ $driver->get('https://instagram.com/accounts/login/?force_classic_login');
+ my $tree = HTML::TreeBuilder::XPath->new_from_content($driver->get_page_source());
+ &Rndwait(1,3);
+
+ if( $tree->exists($config{xpath}{input_username_classic}) && $tree->exists($config{xpath}{input_password_classic}) ) {
+ say 'Found: Login elements.';
+
+ # Username & password
+ $driver->find_element($config{xpath}{input_username_classic})->click;
+ $driver->send_keys_to_active_element('adobebestprices');
+ &Rndwait(1,3);
+ $driver->find_element($config{xpath}{input_password_classic})->click();
+ $driver->send_keys_to_active_element('vst#1337');
+ &Rndwait(1,3);
+
+ # Click login button
+ $driver->find_element($config{xpath}{button_login_classic})->click();
+ }
+ else {
+ die 'Not found: Login elements.';
+ }
+}
+
+sub NewPost {
+ &Rndwait(5,10); # We see the home screen
+
+ my $tree = HTML::TreeBuilder::XPath->new_from_content($driver->get_page_source());
+
+ # add to homescreen popup?
+ &ClickXpath($config{xpath}{button_addtohomescreen}, $tree, 0); # click cancel button
+
+ &ClickXpath($config{xpath}{button_newpost}, $tree); # click new post button
+ &Rndwait(5,10);
+
+ # Bypass file dialog using pyautogui
+ my $cmd = $config{pyautogui_cmd} . ' "' . $config{image_backslash} . '"'; # image path needs to use backslash for python
+ my $ret = system($cmd) or die "system $cmd failed: $?";
+ # print `$cmd`;
+ # system($cmd);
+ # &Rndwait(30,45);
+
+ &FillNewPost();
+}
+
+sub FillNewPost {
+ # Wait for page reload after selecting an image
+ wait_until { $driver->find_element($config{xpath}{button_next}) };
+
+ my $tree = HTML::TreeBuilder::XPath->new_from_content($driver->get_page_source());
+
+ # Expand button
+ &ClickXpath($config{xpath}{button_expand}, $tree, 0); # click expand button to show image in full size
+ &Rndwait(2,4);
+
+ &ClickXpath($config{xpath}{button_next}, $tree); # click next button
+ &Rndwait(5,10);
+
+ # Caption
+ my $tree2 = HTML::TreeBuilder::XPath->new_from_content($driver->get_page_source());
+ my $profile = $config{profile_selected};
+ my $caption;
+ my $hastags = &RndHashtagString(9);
+ if ($config{profile}{$profile}{'filename_as_title'}) {
+ my $filename = basename($config{image_selected});
+ $filename =~ s/(NO INSTALL)|(SymLink Installer)//g;
+ $filename =~ s/( , )|(\.[^.]+$)//g;
+ $caption = "$filename\n\n" . $hastags;
+ }
+ &ClickXpath($config{xpath}{textarea_caption}, $tree2);
+ &Rndwait(5,6);
+ $driver->send_keys_to_active_element($caption);
+ &Rndwait(5,10);
+
+ # Share
+ &ClickXpath($config{xpath}{button_share}, $tree2);
+ &Rndwait(20,40);
+}
+
+sub Finish {
+ say 'Sleep and then quit';
+ &Rndwait(5,5);
+
+ &AddImageToDB();
+ &WipeImage() if $config{wipe_after};
+ $driver->quit();
+}
+
+sub ClickXpath {
+ my $die = 1;
+ my ($xpath, $tree, $die) = @_;
+
+ if( $tree->exists($xpath) ) {
+ say "Found: $xpath";
+ $driver->find_element($xpath)->click();
+ }
+ else {
+ die "Not found: $xpath" if $die;
+ warn "Not found: $xpath" if !$die;
+ }
+}
+
+sub Rndwait {
+ my ($minimum, $maximum) = @_;
+
+ # $seconds = $minimum + int(rand($maximum - $minimum));
+ $seconds = $minimum + rand($maximum - $minimum);
+ $milliseconds = int($seconds * 1000);
+
+ say "Sleeping for ${milliseconds}ms now...";
+ $driver->pause($milliseconds);
+}
+
+sub dumpToFile {
+ # &Delimiter((caller(0))[3]);
+ my ($filepath, $ref) = @_;
+
+ # DATA DUMPER UTF8 HACK
+ # no warnings 'redefine';
+ local *Data::Dumper::qquote = sub { qq["${\(shift)}"] };
+ local $Data::Dumper::Useperl = 1;
+
+ open my $FILE, '>:encoding(UTF-8)', $filepath;
+ print $FILE Dumper $ref;
+ close $FILE;
+}
+
+sub Delimiter {
+ my $SubName = shift;
+ print "\n" . "-" x 80 . "\nSUB " . $SubName . "\n" . '-' x 80 . "\n";
+}
+
+sub CheckParameter {
+ &Delimiter((caller(0))[3]);
+
+ GetOptions ('profile=s' => \$config{'profile_selected'}) or die &PrintUsage();
+ die &PrintUsage() if !$config{'profile_selected'};
+
+ my $profile = $config{'profile_selected'};
+ if (!exists $config{'profile'}{$profile}) {
+ print "Profile '$config{'profile'}' does not exist.\n";
+ &PrintUsage();
+ die;
+ }
+}
+
+sub UndumpFromFile {
+ &Delimiter((caller(0))[3]);
+
+ my $profile = $config{profile_selected};
+ if (-e $config{profile}{$profile}{'DBFilepath'}) {
+ my $json = read_file($config{profile}{$profile}{'DBFilepath'}, { binmode => ':raw' });
+ if (!$json) {
+ warn "DB file $config{'profile'}{$profile}{'DBFilepath'} is empty.\n";
+ return;
+ }
+ %{$config{db}} = %{ decode_json $json };
+ print "INFO: $config{'profile'}{$profile}{'DBFilepath'} has " . scalar(keys(%{$config{db}})) . " keys.\n";
+ }
+ elsif ( !-e $config{'profile'}{$profile}{'DBFilepath'} ) {
+ print "INFO: NO DB file found at $config{'profile'}{$profile}{'DBFilepath'}. Creating now... ";
+ write_file( $config{'profile'}{$profile}{'DBFilepath'}, '' );
+ &UndumpFromFile();
+ }
+}
+
+sub PrintUsage {
+ print "Usage: $0 --profile *name*\n";
+ print "Following profiles are available:\n";
+ print "* $_\n" for keys( %{$config{'profile'}} );
+}
+
+sub DirectoryListing {
+ &Delimiter((caller(0))[3]);
+
+ my $profile = $config{profile_selected};
+ my @files = glob ( "$config{profile}{$profile}{imageDir}/*" );
+ %{$config{images_available}} = map { $_ => { 'FILEPATH' => "$_" } } @files;
+}
+
+sub FindNewDataset {
+ &Delimiter((caller(0))[3]);
+
+ my $i = 0;
+ for my $key (keys %{$config{images_available}}) {
+ if (exists $config{db}{$key}) {
+ print "OLD: $key\n";
+ }
+ elsif (!exists $config{db}{$key}) {
+ print "NEW: $key\n";
+ $config{image_selected} = $key;
+ last;
+ }
+ $i++;
+ }
+
+ if ($i == scalar(keys(%{$config{images_available}}))) {
+ die "\nNO NEW FILES AVAILABLE.\n";
+ }
+}
+
+sub AddImageToDB {
+ &Delimiter((caller(0))[3]);
+
+ my $profile = $config{profile_selected};
+ my $selected = $config{image_selected};
+ $config{'images_available'}{$selected}{'TIMESTAMP_UPLOADED'} = sprintf ( "%04d%02d%02d_%02d%02d%02d", $year+1900,$mon+1,$mday,$hour,$min,$sec);
+ $config{db}{$selected} = $config{images_available}{$selected};
+ my $json = encode_json(\%{$config{db}});
+ write_file($config{profile}{$profile}{'DBFilepath'}, { binmode => ':raw' }, $json);
+}
+
+sub WipeImage {
+ &Delimiter((caller(0))[3]);
+
+ my $image = $config{image_selected};
+ print "Deleting $image...";
+ unlink($image) or die "Could not delete $image!\n";
+ print " done.\n";
+}
+
+
+# SERVER:
+# java -Dwebdriver.gecko.driver=C:\Users\vstbestprices\Seafile\INSTAFEED_SYNC\selenium\geckodriver.exe -jar C:\Users\vstbestprices\Seafile\INSTAFEED_SYNC\selenium\selenium-server-standalone-3.141.59.jar
+# java -jar /home/pi/projects/instafeed/selenium/selenium-server-standalone-3.141.59.jar
\ No newline at end of file
diff --git a/src/db/db_dreamyourmansion.dat b/src/db/db_dreamyourmansion.dat
new file mode 100755
index 0000000..3b33729
--- /dev/null
+++ b/src/db/db_dreamyourmansion.dat
@@ -0,0 +1 @@
+{"./src/images/dreamyourmansion/beach-clouds-dawn-732199.jpg":{"TIMESTAMP_UPLOADED":"20191117_122322","FILEPATH":"./src/images/dreamyourmansion/beach-clouds-dawn-732199.jpg"},"./src/images/dreamyourmansion/angsana-beach-clouds-2417862.jpg":{"TIMESTAMP_UPLOADED":"20191030_223310","FILEPATH":"./src/images/dreamyourmansion/angsana-beach-clouds-2417862.jpg"},"angsana-beach-clouds-2417862.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/angsana-beach-clouds-2417862.jpg","TIMESTAMP_UPLOADED":"20190918_170406"},"./src/images/dreamyourmansion/coast-coastline-daylight-1449778.jpg":{"FILEPATH":"./src/images/dreamyourmansion/coast-coastline-daylight-1449778.jpg","TIMESTAMP_UPLOADED":"20191121_122318"},"./src/images/dreamyourmansion/apartment-architectural-design-architecture-1115804.jpg":{"FILEPATH":"./src/images/dreamyourmansion/apartment-architectural-design-architecture-1115804.jpg","TIMESTAMP_UPLOADED":"20191126_122316"},"./src/images/dreamyourmansion/abraham-lincoln-architecture-attractions-220820.jpg":{"TIMESTAMP_UPLOADED":"20191105_122319","FILEPATH":"./src/images/dreamyourmansion/abraham-lincoln-architecture-attractions-220820.jpg"},"./src/images/dreamyourmansion/AQl-J19ocWE.jpg":{"TIMESTAMP_UPLOADED":"20201009_130503","FILEPATH":"./src/images/dreamyourmansion/AQl-J19ocWE.jpg"},"./src/images/dreamyourmansion/architectural-design-architecture-blue-sky-462358.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-blue-sky-462358.jpg","TIMESTAMP_UPLOADED":"20191101_122317"},"./src/images/dreamyourmansion/castle-facade-fountain-87378.jpg":{"TIMESTAMP_UPLOADED":"20191027_122325","FILEPATH":"./src/images/dreamyourmansion/castle-facade-fountain-87378.jpg"},"./src/images/dreamyourmansion/architecture-art-chandelier-9298.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-art-chandelier-9298.jpg","TIMESTAMP_UPLOADED":"20191106_002324"},"./src/images/dreamyourmansion/ancient-architecture-attractions-208631.jpg":{"TIMESTAMP_UPLOADED":"20191026_122316","FILEPATH":"./src/images/dreamyourmansion/ancient-architecture-attractions-208631.jpg"},"./src/images/dreamyourmansion/architecture-building-daylight-206172.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-building-daylight-206172.jpg","TIMESTAMP_UPLOADED":"20191117_002319"},"./src/images/dreamyourmansion/architecture-brick-wall-bricks-2263682.jpg":{"TIMESTAMP_UPLOADED":"20191030_222855","FILEPATH":"./src/images/dreamyourmansion/architecture-brick-wall-bricks-2263682.jpg"},"./src/images/dreamyourmansion/blue-exotic-hotel-189296.jpg":{"TIMESTAMP_UPLOADED":"20191027_002324","FILEPATH":"./src/images/dreamyourmansion/blue-exotic-hotel-189296.jpg"},"./src/images/dreamyourmansion/clean-holiday-hotel-221457.jpg":{"FILEPATH":"./src/images/dreamyourmansion/clean-holiday-hotel-221457.jpg","TIMESTAMP_UPLOADED":"20191112_002324"},"architectural-design-architecture-daylight-1706625.jpg":{"TIMESTAMP_UPLOADED":"20190926_062631","FILEPATH":"/home/pi/instafeed/src/images/architectural-design-architecture-daylight-1706625.jpg"},"./src/images/dreamyourmansion/beach-blue-water-chair-264468.jpg":{"FILEPATH":"./src/images/dreamyourmansion/beach-blue-water-chair-264468.jpg","TIMESTAMP_UPLOADED":"20191103_002323"},"./src/images/dreamyourmansion/17th-century-courtyard-duchess-of-lauderdale-36355.jpg":{"TIMESTAMP_UPLOADED":"20191201_122317","FILEPATH":"./src/images/dreamyourmansion/17th-century-courtyard-duchess-of-lauderdale-36355.jpg"},"./src/images/dreamyourmansion/architecture-building-city-210493.jpg":{"TIMESTAMP_UPLOADED":"20191109_122320","FILEPATH":"./src/images/dreamyourmansion/architecture-building-city-210493.jpg"},"./src/images/dreamyourmansion/architecture-attractions-buildings-208608.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-attractions-buildings-208608.jpg","TIMESTAMP_UPLOADED":"20191202_002314"},"architecture-building-buy-259098.jpg":{"TIMESTAMP_UPLOADED":"20190920_112034","FILEPATH":"/home/pi/instafeed/src/images/architecture-building-buy-259098.jpg"},"./src/images/dreamyourmansion/architecture-building-castle-534095.jpg":{"TIMESTAMP_UPLOADED":"20191031_122320","FILEPATH":"./src/images/dreamyourmansion/architecture-building-castle-534095.jpg"},"alcohol-bottles-architecture-coconut-trees-1134178.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/alcohol-bottles-architecture-coconut-trees-1134178.jpg","TIMESTAMP_UPLOADED":"20190929_002636"},"apartment-architectural-design-architecture-323774.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/apartment-architectural-design-architecture-323774.jpg","TIMESTAMP_UPLOADED":"20190921_062635"},"./src/images/dreamyourmansion/aerial-photography-beach-bird-s-eye-view-1198838.jpg":{"FILEPATH":"./src/images/dreamyourmansion/aerial-photography-beach-bird-s-eye-view-1198838.jpg","TIMESTAMP_UPLOADED":"20191129_002321"},"./src/images/dreamyourmansion/design-doors-doorway-1834706.jpg":{"FILEPATH":"./src/images/dreamyourmansion/design-doors-doorway-1834706.jpg","TIMESTAMP_UPLOADED":"20191025_002324"},"./src/images/dreamyourmansion/architecture-construction-daylight-534228.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-construction-daylight-534228.jpg","TIMESTAMP_UPLOADED":"20191118_002323"},"architecture-caribbean-chairs-2565222.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-caribbean-chairs-2565222.jpg","TIMESTAMP_UPLOADED":"20190918_180800"},"./src/images/dreamyourmansion/architecture-building-buses-877994.jpg":{"TIMESTAMP_UPLOADED":"20191111_002328","FILEPATH":"./src/images/dreamyourmansion/architecture-building-buses-877994.jpg"},"./src/images/dreamyourmansion/architecture-buildings-contemporary-1488267.jpg":{"TIMESTAMP_UPLOADED":"20191122_122316","FILEPATH":"./src/images/dreamyourmansion/architecture-buildings-contemporary-1488267.jpg"},"./src/images/dreamyourmansion/architecture-dug-out-pool-hotel-1134175.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-dug-out-pool-hotel-1134175.jpg","TIMESTAMP_UPLOADED":"20191007_002321"},"arched-window-architecture-art-1040893.jpg":{"TIMESTAMP_UPLOADED":"20190927_062636","FILEPATH":"/home/pi/instafeed/src/images/arched-window-architecture-art-1040893.jpg"},"backyard-lights-mansion-32870.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/backyard-lights-mansion-32870.jpg","TIMESTAMP_UPLOADED":"20190927_122634"},"./src/images/dreamyourmansion/arches-architecture-art-316080.jpg":{"FILEPATH":"./src/images/dreamyourmansion/arches-architecture-art-316080.jpg","TIMESTAMP_UPLOADED":"20191014_002328"},"architecture-building-daylight-206172.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-building-daylight-206172.jpg","TIMESTAMP_UPLOADED":"20190918_145241"},"./src/images/dreamyourmansion/beach-chairs-chairs-clouds-2549029.jpg":{"TIMESTAMP_UPLOADED":"20191024_002328","FILEPATH":"./src/images/dreamyourmansion/beach-chairs-chairs-clouds-2549029.jpg"},"beach-building-daylight-1714975.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/beach-building-daylight-1714975.jpg","TIMESTAMP_UPLOADED":"20190927_182632"},"./src/images/dreamyourmansion/architecture-building-daylight-126271.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-building-daylight-126271.jpg","TIMESTAMP_UPLOADED":"20191017_002325"},"architecture-building-daylight-208421.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-building-daylight-208421.jpg","TIMESTAMP_UPLOADED":"20190928_182645"},"./src/images/dreamyourmansion/architectural-design-architecture-building-140963.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-building-140963.jpg","TIMESTAMP_UPLOADED":"20191123_002317"},"beam-cabin-clouds-531450.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/beam-cabin-clouds-531450.jpg","TIMESTAMP_UPLOADED":"20190923_182628"},"aerial-photography-beach-bird-s-eye-view-1198838.jpg":{"TIMESTAMP_UPLOADED":"20190918_151656","FILEPATH":"/home/pi/instafeed/src/images/aerial-photography-beach-bird-s-eye-view-1198838.jpg"},"dug-out-pool-garden-swimming-pool-1746876.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/dug-out-pool-garden-swimming-pool-1746876.jpg","TIMESTAMP_UPLOADED":"20190918_170233"},"./src/images/dreamyourmansion/architecture-building-buy-221540.jpg":{"TIMESTAMP_UPLOADED":"20191006_122317","FILEPATH":"./src/images/dreamyourmansion/architecture-building-buy-221540.jpg"},"./src/images/dreamyourmansion/couple-investment-key-1288482.jpg":{"FILEPATH":"./src/images/dreamyourmansion/couple-investment-key-1288482.jpg","TIMESTAMP_UPLOADED":"20191012_002320"},"architecture-buildings-contemporary-1488267.jpg":{"TIMESTAMP_UPLOADED":"20190918_170318","FILEPATH":"/home/pi/instafeed/src/images/architecture-buildings-contemporary-1488267.jpg"},"architecture-building-castle-1270902.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-building-castle-1270902.jpg","TIMESTAMP_UPLOADED":"20190925_122641"},"architecture-building-daylight-126271.jpg":{"TIMESTAMP_UPLOADED":"20190918_170440","FILEPATH":"/home/pi/instafeed/src/images/architecture-building-daylight-126271.jpg"},"./src/images/dreamyourmansion/KtOid0FLjqU.jpg":{"FILEPATH":"./src/images/dreamyourmansion/KtOid0FLjqU.jpg","TIMESTAMP_UPLOADED":"20201009_131359"},"./src/images/dreamyourmansion/architecture-building-driveway-164522.jpg":{"TIMESTAMP_UPLOADED":"20191004_002333","FILEPATH":"./src/images/dreamyourmansion/architecture-building-driveway-164522.jpg"},"./src/images/dreamyourmansion/dug-out-pool-hotel-poolside-1134176.jpg":{"TIMESTAMP_UPLOADED":"20191129_122319","FILEPATH":"./src/images/dreamyourmansion/dug-out-pool-hotel-poolside-1134176.jpg"},"./src/images/dreamyourmansion/building-exterior-cabin-colors-2343533.jpg":{"FILEPATH":"./src/images/dreamyourmansion/building-exterior-cabin-colors-2343533.jpg","TIMESTAMP_UPLOADED":"20191019_122324"},"beach-boat-bridge-1450350.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/beach-boat-bridge-1450350.jpg","TIMESTAMP_UPLOADED":"20190926_182633"},"./src/images/dreamyourmansion/architecture-chimney-cloudy-skies-1569003.jpg":{"TIMESTAMP_UPLOADED":"20191108_122321","FILEPATH":"./src/images/dreamyourmansion/architecture-chimney-cloudy-skies-1569003.jpg"},"./src/images/dreamyourmansion/architectural-design-architecture-brickwalls-191323.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-brickwalls-191323.jpg","TIMESTAMP_UPLOADED":"20191005_002326"},"./src/images/dreamyourmansion/architecture-building-facade-2710554.jpg":{"TIMESTAMP_UPLOADED":"20191005_122319","FILEPATH":"./src/images/dreamyourmansion/architecture-building-facade-2710554.jpg"},"./src/images/dreamyourmansion/architecture-balcony-blue-sky-343240.jpg":{"TIMESTAMP_UPLOADED":"20191019_002320","FILEPATH":"./src/images/dreamyourmansion/architecture-balcony-blue-sky-343240.jpg"},"architecture-building-daylight-210538.jpg":{"TIMESTAMP_UPLOADED":"20191001_221142","FILEPATH":"./src/images/architecture-building-daylight-210538.jpg"},"ancient-architecture-building-771023.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/ancient-architecture-building-771023.jpg","TIMESTAMP_UPLOADED":"20190921_122639"},"./src/images/dreamyourmansion/architecture-chandelier-clean-210463.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-chandelier-clean-210463.jpg","TIMESTAMP_UPLOADED":"20191021_122321"},"./src/images/dreamyourmansion/architecture-building-buildings-2356336.jpg":{"TIMESTAMP_UPLOADED":"20191114_002323","FILEPATH":"./src/images/dreamyourmansion/architecture-building-buildings-2356336.jpg"},"./src/images/dreamyourmansion/beach-bridge-clouds-1320686.jpg":{"FILEPATH":"./src/images/dreamyourmansion/beach-bridge-clouds-1320686.jpg","TIMESTAMP_UPLOADED":"20191104_002332"},"ceiling-chairs-clean-2343465.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/ceiling-chairs-clean-2343465.jpg","TIMESTAMP_UPLOADED":"20190920_122621"},"./src/images/dreamyourmansion/clouds-coconut-trees-daylight-434657.jpg":{"TIMESTAMP_UPLOADED":"20191012_122322","FILEPATH":"./src/images/dreamyourmansion/clouds-coconut-trees-daylight-434657.jpg"},"architecture-backyard-chairs-2775312.jpg":{"TIMESTAMP_UPLOADED":"20190928_062639","FILEPATH":"/home/pi/instafeed/src/images/architecture-backyard-chairs-2775312.jpg"},"architecture-dug-out-pool-hotel-1134175.jpg":{"TIMESTAMP_UPLOADED":"20190813_191818","FILEPATH":"/home/pi/instafeed/src/images/architecture-dug-out-pool-hotel-1134175.jpg"},"./src/images/dreamyourmansion/so3wgJLwDxo.jpg":{"TIMESTAMP_UPLOADED":"20201009_130725","FILEPATH":"./src/images/dreamyourmansion/so3wgJLwDxo.jpg"},"./src/images/dreamyourmansion/broker-buy-customers-1368687.jpg":{"TIMESTAMP_UPLOADED":"20191030_222938","FILEPATH":"./src/images/dreamyourmansion/broker-buy-customers-1368687.jpg"},"./src/images/dreamyourmansion/ceiling-chairs-contemporary-1884261.jpg":{"TIMESTAMP_UPLOADED":"20191008_122323","FILEPATH":"./src/images/dreamyourmansion/ceiling-chairs-contemporary-1884261.jpg"},"animal-architecture-beautiful-162107.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/animal-architecture-beautiful-162107.jpg","TIMESTAMP_UPLOADED":"20190928_122631"},"architecture-building-daylight-804044.jpg":{"TIMESTAMP_UPLOADED":"20190918_170215","FILEPATH":"/home/pi/instafeed/src/images/architecture-building-daylight-804044.jpg"},"./src/images/dreamyourmansion/daylight-door-entrance-242264.jpg":{"FILEPATH":"./src/images/dreamyourmansion/daylight-door-entrance-242264.jpg","TIMESTAMP_UPLOADED":"20191112_122323"},"./src/images/dreamyourmansion/architecture-design-family-929961.jpg":{"TIMESTAMP_UPLOADED":"20191119_122317","FILEPATH":"./src/images/dreamyourmansion/architecture-design-family-929961.jpg"},"architecture-ceiling-chair-1065883.jpg":{"TIMESTAMP_UPLOADED":"20190923_122630","FILEPATH":"/home/pi/instafeed/src/images/architecture-ceiling-chair-1065883.jpg"},"./src/images/dreamyourmansion/architecture-bed-bedroom-1103808.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-bed-bedroom-1103808.jpg","TIMESTAMP_UPLOADED":"20191016_002302"},"calm-clouds-exotic-297984.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/calm-clouds-exotic-297984.jpg","TIMESTAMP_UPLOADED":"20190925_182639"},"./src/images/dreamyourmansion/architecture-beach-building-258154.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-beach-building-258154.jpg","TIMESTAMP_UPLOADED":"20191021_002322"},"architectural-design-architecture-building-exterior-2212875.jpg":{"TIMESTAMP_UPLOADED":"20190813_192233","FILEPATH":"/home/pi/instafeed/src/images/architectural-design-architecture-building-exterior-2212875.jpg"},"./src/images/dreamyourmansion/architecture-atrium-building-220768.jpg":{"TIMESTAMP_UPLOADED":"20191020_122317","FILEPATH":"./src/images/dreamyourmansion/architecture-atrium-building-220768.jpg"},"architecture-building-daylight-208747.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-building-daylight-208747.jpg","TIMESTAMP_UPLOADED":"20190922_182631"},"./src/images/dreamyourmansion/architecture-beautiful-exterior-106399.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-beautiful-exterior-106399.jpg","TIMESTAMP_UPLOADED":"20191107_122322"},"./src/images/dreamyourmansion/background-beach-beautiful-2606523.jpg":{"FILEPATH":"./src/images/dreamyourmansion/background-beach-beautiful-2606523.jpg","TIMESTAMP_UPLOADED":"20191102_122326"},"architecture-building-industry-209274.jpg":{"TIMESTAMP_UPLOADED":"20191001_221941","FILEPATH":"./src/images/dreamyourmansion/architecture-building-industry-209274.jpg"},"architecture-art-chandelier-9298.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-art-chandelier-9298.jpg","TIMESTAMP_UPLOADED":"20190918_170304"},"./src/images/dreamyourmansion/architectural-design-architecture-cabin-1795507.jpg":{"TIMESTAMP_UPLOADED":"20191127_002320","FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-cabin-1795507.jpg"},"./src/images/dreamyourmansion/architecture-beautiful-home-building-280229.jpg":{"TIMESTAMP_UPLOADED":"20191113_122321","FILEPATH":"./src/images/dreamyourmansion/architecture-beautiful-home-building-280229.jpg"},"broker-buy-customers-1368687.jpg":{"TIMESTAMP_UPLOADED":"20190918_180708","FILEPATH":"/home/pi/instafeed/src/images/broker-buy-customers-1368687.jpg"},"./src/images/dreamyourmansion/aerial-photography-architecture-bali-2480608.jpg":{"TIMESTAMP_UPLOADED":"20191130_122319","FILEPATH":"./src/images/dreamyourmansion/aerial-photography-architecture-bali-2480608.jpg"},"./src/images/dreamyourmansion/architectural-design-architecture-daylight-2083459.jpg":{"TIMESTAMP_UPLOADED":"20191127_122320","FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-daylight-2083459.jpg"},"./src/images/dreamyourmansion/building-downtown-real-estate-36362.jpg":{"FILEPATH":"./src/images/dreamyourmansion/building-downtown-real-estate-36362.jpg","TIMESTAMP_UPLOADED":"20191009_122317"},"beach-blue-coast-1724420.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/beach-blue-coast-1724420.jpg","TIMESTAMP_UPLOADED":"20190918_170543"},"./src/images/dreamyourmansion/architecture-building-dry-leaves-1757516.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-building-dry-leaves-1757516.jpg","TIMESTAMP_UPLOADED":"20191106_122322"},"./src/images/dreamyourmansion/beach-daylight-exotic-2631613.jpg":{"TIMESTAMP_UPLOADED":"20191011_122735","FILEPATH":"./src/images/dreamyourmansion/beach-daylight-exotic-2631613.jpg"},"./src/images/dreamyourmansion/architectural-design-architecture-building-exterior-2212875.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-building-exterior-2212875.jpg","TIMESTAMP_UPLOADED":"20191016_122323"},"./src/images/dreamyourmansion/architecture-chair-color-1080696.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-chair-color-1080696.jpg","TIMESTAMP_UPLOADED":"20191102_002323"},"./src/images/dreamyourmansion/architecture-building-daylight-173229.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-building-daylight-173229.jpg","TIMESTAMP_UPLOADED":"20191201_002317"},"./src/images/dreamyourmansion/architecture-buy-construction-461024.jpg":{"TIMESTAMP_UPLOADED":"20191017_122332","FILEPATH":"./src/images/dreamyourmansion/architecture-buy-construction-461024.jpg"},"architecture-autumn-building-exterior-2179603.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-autumn-building-exterior-2179603.jpg","TIMESTAMP_UPLOADED":"20190924_182640"},"chairs-contemporary-daylight-1439711.jpg":{"TIMESTAMP_UPLOADED":"20190918_150225","FILEPATH":"/home/pi/instafeed/src/images/chairs-contemporary-daylight-1439711.jpg"},"./src/images/dreamyourmansion/ancient-architecture-building-140019.jpg":{"TIMESTAMP_UPLOADED":"20191011_002336","FILEPATH":"./src/images/dreamyourmansion/ancient-architecture-building-140019.jpg"},"architecture-building-city-221106.jpg":{"TIMESTAMP_UPLOADED":"20190926_122633","FILEPATH":"/home/pi/instafeed/src/images/architecture-building-city-221106.jpg"},"./src/images/dreamyourmansion/adventure-aerial-shot-beach-1456293.jpg":{"FILEPATH":"./src/images/dreamyourmansion/adventure-aerial-shot-beach-1456293.jpg","TIMESTAMP_UPLOADED":"20191030_230729"},"./src/images/dreamyourmansion/ancient-arched-window-architecture-532902.jpg":{"TIMESTAMP_UPLOADED":"20191119_002316","FILEPATH":"./src/images/dreamyourmansion/ancient-arched-window-architecture-532902.jpg"},"./src/images/dreamyourmansion/2d4lAQAlbDA.jpg":{"FILEPATH":"./src/images/dreamyourmansion/2d4lAQAlbDA.jpg","TIMESTAMP_UPLOADED":"20201009_130139"},"./src/images/dreamyourmansion/architecture-building-entrance-187815.jpg":{"TIMESTAMP_UPLOADED":"20191025_122321","FILEPATH":"./src/images/dreamyourmansion/architecture-building-entrance-187815.jpg"},"./src/images/dreamyourmansion/clouds-evening-evening-sky-1724422.jpg":{"FILEPATH":"./src/images/dreamyourmansion/clouds-evening-evening-sky-1724422.jpg","TIMESTAMP_UPLOADED":"20191018_122323"},"./src/images/dreamyourmansion/architecture-building-daylight-158148.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-building-daylight-158148.jpg","TIMESTAMP_UPLOADED":"20191030_223154"},"./src/images/dreamyourmansion/arched-window-architecture-blue-sky-259602.jpg":{"TIMESTAMP_UPLOADED":"20191110_122321","FILEPATH":"./src/images/dreamyourmansion/arched-window-architecture-blue-sky-259602.jpg"},"./src/images/dreamyourmansion/chairs-contemporary-daylight-1439711.jpg":{"TIMESTAMP_UPLOADED":"20191115_002322","FILEPATH":"./src/images/dreamyourmansion/chairs-contemporary-daylight-1439711.jpg"},"architecture-building-daylight-1710484.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-building-daylight-1710484.jpg","TIMESTAMP_UPLOADED":"20190925_002632"},"./src/images/dreamyourmansion/afternoon-architecture-backyard-271815.jpg":{"FILEPATH":"./src/images/dreamyourmansion/afternoon-architecture-backyard-271815.jpg","TIMESTAMP_UPLOADED":"20191108_002318"},"./src/images/dreamyourmansion/beach-chairs-blue-chairs-2549018.jpg":{"FILEPATH":"./src/images/dreamyourmansion/beach-chairs-blue-chairs-2549018.jpg","TIMESTAMP_UPLOADED":"20191128_002323"},"./src/images/dreamyourmansion/architecture-brick-building-209315.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-brick-building-209315.jpg","TIMESTAMP_UPLOADED":"20191128_122314"},"./src/images/dreamyourmansion/architecture-daylight-driveway-277667.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-daylight-driveway-277667.jpg","TIMESTAMP_UPLOADED":"20191004_122339"},"architecture-bridge-chairs-261410.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-bridge-chairs-261410.jpg","TIMESTAMP_UPLOADED":"20190921_182646"},"architecture-building-lawn-164539.jpg":{"TIMESTAMP_UPLOADED":"20190919_120616","FILEPATH":"/home/pi/instafeed/src/images/architecture-building-lawn-164539.jpg"},"./src/images/dreamyourmansion/appliances-architecture-ceiling-534151.jpg":{"TIMESTAMP_UPLOADED":"20191008_002318","FILEPATH":"./src/images/dreamyourmansion/appliances-architecture-ceiling-534151.jpg"},"contemporary-counter-door-1380019.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/contemporary-counter-door-1380019.jpg","TIMESTAMP_UPLOADED":"20190924_122603"},"./src/images/dreamyourmansion/2-storey-house-architecture-building-1694360.jpg":{"FILEPATH":"./src/images/dreamyourmansion/2-storey-house-architecture-building-1694360.jpg","TIMESTAMP_UPLOADED":"20191121_002321"},"./src/images/dreamyourmansion/alcohol-bottles-architecture-daytime-1134177.jpg":{"TIMESTAMP_UPLOADED":"20191014_122321","FILEPATH":"./src/images/dreamyourmansion/alcohol-bottles-architecture-daytime-1134177.jpg"},"./src/images/dreamyourmansion/architecture-balcony-building-1658083.jpg":{"TIMESTAMP_UPLOADED":"20191126_002321","FILEPATH":"./src/images/dreamyourmansion/architecture-balcony-building-1658083.jpg"},"./src/images/dreamyourmansion/chairs-columns-decor-1327389.jpg":{"TIMESTAMP_UPLOADED":"20191022_002324","FILEPATH":"./src/images/dreamyourmansion/chairs-columns-decor-1327389.jpg"},"./src/images/dreamyourmansion/architecture-daylight-design-534157.jpg":{"TIMESTAMP_UPLOADED":"20191111_122319","FILEPATH":"./src/images/dreamyourmansion/architecture-daylight-design-534157.jpg"},"abinger-common-ancient-architecture-161791.jpg":{"TIMESTAMP_UPLOADED":"20190921_002637","FILEPATH":"/home/pi/instafeed/src/images/abinger-common-ancient-architecture-161791.jpg"},"./src/images/dreamyourmansion/beach-bungalows-clouds-1449746.jpg":{"TIMESTAMP_UPLOADED":"20191028_002323","FILEPATH":"./src/images/dreamyourmansion/beach-bungalows-clouds-1449746.jpg"},"./src/images/dreamyourmansion/beach-blue-coast-1724420.jpg":{"TIMESTAMP_UPLOADED":"20191006_002323","FILEPATH":"./src/images/dreamyourmansion/beach-blue-coast-1724420.jpg"},"./src/images/dreamyourmansion/architecture-black-and-white-facade-259820.jpg":{"TIMESTAMP_UPLOADED":"20191030_221509","FILEPATH":"./src/images/dreamyourmansion/architecture-black-and-white-facade-259820.jpg"},"contemporary-counter-daylight-1504025.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/contemporary-counter-daylight-1504025.jpg","TIMESTAMP_UPLOADED":"20190928_002636"},"building-exterior-cabin-colors-2343533.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/building-exterior-cabin-colors-2343533.jpg","TIMESTAMP_UPLOADED":"20190916_213744"},"./src/images/dreamyourmansion/dug-out-pool-garden-swimming-pool-1746876.jpg":{"TIMESTAMP_UPLOADED":"20191029_002326","FILEPATH":"./src/images/dreamyourmansion/dug-out-pool-garden-swimming-pool-1746876.jpg"},"architecture-building-garden-1358837.jpg":{"TIMESTAMP_UPLOADED":"20190922_122636","FILEPATH":"/home/pi/instafeed/src/images/architecture-building-garden-1358837.jpg"},"ceiling-chairs-contemporary-1864888.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/ceiling-chairs-contemporary-1864888.jpg","TIMESTAMP_UPLOADED":"20190924_002633"},"./src/images/dreamyourmansion/architecture-building-driveway-284991.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-building-driveway-284991.jpg","TIMESTAMP_UPLOADED":"20191103_122320"},"./src/images/dreamyourmansion/apartment-architecture-artistic-1608165.jpg":{"TIMESTAMP_UPLOADED":"20191023_122324","FILEPATH":"./src/images/dreamyourmansion/apartment-architecture-artistic-1608165.jpg"},"./src/images/dreamyourmansion/architectural-design-architecture-building-1212053.jpg":{"TIMESTAMP_UPLOADED":"20191101_002319","FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-building-1212053.jpg"},"architecture-attraction-building-210474.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-attraction-building-210474.jpg","TIMESTAMP_UPLOADED":"20190923_062641"},"aqua-boardwalk-clouds-2525899.jpg":{"TIMESTAMP_UPLOADED":"20190929_062638","FILEPATH":"/home/pi/instafeed/src/images/aqua-boardwalk-clouds-2525899.jpg"},"./src/images/dreamyourmansion/apartment-architecture-balcony-347141.jpg":{"TIMESTAMP_UPLOADED":"20191004_163559","FILEPATH":"./src/images/dreamyourmansion/apartment-architecture-balcony-347141.jpg"},"./src/images/dreamyourmansion/architecture-backyard-clouds-2513972.jpg":{"TIMESTAMP_UPLOADED":"20191030_122320","FILEPATH":"./src/images/dreamyourmansion/architecture-backyard-clouds-2513972.jpg"},"./src/images/dreamyourmansion/architecture-art-chair-1365225.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-art-chair-1365225.jpg","TIMESTAMP_UPLOADED":"20191031_002321"},"./src/images/dreamyourmansion/architecture-attractive-balcony-210496.jpg":{"TIMESTAMP_UPLOADED":"20191026_002317","FILEPATH":"./src/images/dreamyourmansion/architecture-attractive-balcony-210496.jpg"},"./src/images/dreamyourmansion/architecture-building-campus-207684.jpg":{"TIMESTAMP_UPLOADED":"20191123_122315","FILEPATH":"./src/images/dreamyourmansion/architecture-building-campus-207684.jpg"},"high-angle-shot-hotel-palm-1488291.jpg":{"TIMESTAMP_UPLOADED":"20190919_122613","FILEPATH":"/home/pi/instafeed/src/images/high-angle-shot-hotel-palm-1488291.jpg"},"./src/images/dreamyourmansion/architecture-balcony-daylight-1060950.jpg":{"TIMESTAMP_UPLOADED":"20191007_122322","FILEPATH":"./src/images/dreamyourmansion/architecture-balcony-daylight-1060950.jpg"},"./src/images/dreamyourmansion/architectural-design-architecture-country-home-2287310.jpg":{"TIMESTAMP_UPLOADED":"20191109_002323","FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-country-home-2287310.jpg"},"./src/images/dreamyourmansion/architecture-dug-out-pool-family-1488327.jpg":{"TIMESTAMP_UPLOADED":"20191013_122318","FILEPATH":"./src/images/dreamyourmansion/architecture-dug-out-pool-family-1488327.jpg"},"architectural-design-architecture-balcony-2307277.jpg":{"TIMESTAMP_UPLOADED":"20190927_002638","FILEPATH":"/home/pi/instafeed/src/images/architectural-design-architecture-balcony-2307277.jpg"},"./src/images/dreamyourmansion/PyFzygP2eNg.jpg":{"TIMESTAMP_UPLOADED":"20201009_125756","FILEPATH":"./src/images/dreamyourmansion/PyFzygP2eNg.jpg"},"./src/images/dreamyourmansion/arched-window-architectural-design-architecture-1598546.jpg":{"TIMESTAMP_UPLOADED":"20191113_002323","FILEPATH":"./src/images/dreamyourmansion/arched-window-architectural-design-architecture-1598546.jpg"},"architecture-baroque-building-326784.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-baroque-building-326784.jpg","TIMESTAMP_UPLOADED":"20190930_002330"},"./src/images/dreamyourmansion/architectural-design-architecture-daylight-112283.jpg":{"TIMESTAMP_UPLOADED":"20191125_122316","FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-daylight-112283.jpg"},"architecture-building-grass-275516.jpg":{"TIMESTAMP_UPLOADED":"20190925_062633","FILEPATH":"/home/pi/instafeed/src/images/architecture-building-grass-275516.jpg"},"architecture-daylight-exterior-1327445.jpg":{"TIMESTAMP_UPLOADED":"20190926_002634","FILEPATH":"/home/pi/instafeed/src/images/architecture-daylight-exterior-1327445.jpg"},"banking-buy-construction-210617.jpg":{"TIMESTAMP_UPLOADED":"20190916_213409","FILEPATH":"/home/pi/instafeed/src/images/banking-buy-construction-210617.jpg"},"./src/images/dreamyourmansion/beach-beautiful-blue-279574.jpg":{"FILEPATH":"./src/images/dreamyourmansion/beach-beautiful-blue-279574.jpg","TIMESTAMP_UPLOADED":"20191107_002318"},"./src/images/dreamyourmansion/architectural-design-architecture-body-of-water-1438834.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-body-of-water-1438834.jpg","TIMESTAMP_UPLOADED":"20191028_122323"},"architecture-daylight-exterior-112291.jpg":{"TIMESTAMP_UPLOADED":"20190920_182632","FILEPATH":"/home/pi/instafeed/src/images/architecture-daylight-exterior-112291.jpg"},"./src/images/dreamyourmansion/accommodation-beach-bed-1531672.jpg":{"TIMESTAMP_UPLOADED":"20191022_122325","FILEPATH":"./src/images/dreamyourmansion/accommodation-beach-bed-1531672.jpg"},"architecture-asheville-biltmore-estate-259823.jpg":{"TIMESTAMP_UPLOADED":"20190922_062630","FILEPATH":"/home/pi/instafeed/src/images/architecture-asheville-biltmore-estate-259823.jpg"},"./src/images/dreamyourmansion/administration-architecture-building-460716.jpg":{"FILEPATH":"./src/images/dreamyourmansion/administration-architecture-building-460716.jpg","TIMESTAMP_UPLOADED":"20191115_122318"},"./src/images/dreamyourmansion/architectural-design-architecture-construction-1800387.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-construction-1800387.jpg","TIMESTAMP_UPLOADED":"20191015_122302"},"./src/images/dreamyourmansion/architecture-beach-blue-261101.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-beach-blue-261101.jpg","TIMESTAMP_UPLOADED":"20191114_122327"},"./src/images/dreamyourmansion/architectural-design-architecture-building-2280844.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-building-2280844.jpg","TIMESTAMP_UPLOADED":"20191104_122324"},"./src/images/dreamyourmansion/architecture-daylight-driveway-6343.jpg":{"TIMESTAMP_UPLOADED":"20191110_002323","FILEPATH":"./src/images/dreamyourmansion/architecture-daylight-driveway-6343.jpg"},"./src/images/dreamyourmansion/architecture-building-facade-164558.jpg":{"TIMESTAMP_UPLOADED":"20191120_122314","FILEPATH":"./src/images/dreamyourmansion/architecture-building-facade-164558.jpg"},"./src/images/dreamyourmansion/architecture-bricks-buildings-242246.jpg":{"TIMESTAMP_UPLOADED":"20191010_002328","FILEPATH":"./src/images/dreamyourmansion/architecture-bricks-buildings-242246.jpg"},"./src/images/dreamyourmansion/_hw4aUQ81ic.jpg":{"TIMESTAMP_UPLOADED":"20201009_130847","FILEPATH":"./src/images/dreamyourmansion/_hw4aUQ81ic.jpg"},"./src/images/dreamyourmansion/architecture-balcony-building-534182.jpg":{"TIMESTAMP_UPLOADED":"20191029_122320","FILEPATH":"./src/images/dreamyourmansion/architecture-balcony-building-534182.jpg"},"./src/images/dreamyourmansion/architectural-design-architecture-clouds-1732414.jpg":{"TIMESTAMP_UPLOADED":"20191125_002320","FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-clouds-1732414.jpg"},"./src/images/dreamyourmansion/architectural-architecture-building-816198.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-architecture-building-816198.jpg","TIMESTAMP_UPLOADED":"20191120_002322"},"./src/images/dreamyourmansion/vbSRUrNm3Ik.jpg":{"TIMESTAMP_UPLOADED":"20201009_131152","FILEPATH":"./src/images/dreamyourmansion/vbSRUrNm3Ik.jpg"},"dark-huts-lights-128303.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/dark-huts-lights-128303.jpg","TIMESTAMP_UPLOADED":"20190924_062603"},"./src/images/dreamyourmansion/architecture-building-daylight-804044.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-building-daylight-804044.jpg","TIMESTAMP_UPLOADED":"20191124_002319"},"architecture-building-condo-280492.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/architecture-building-condo-280492.jpg","TIMESTAMP_UPLOADED":"20190918_195459"},"./src/images/dreamyourmansion/administration-architecture-building-962989.jpg":{"FILEPATH":"./src/images/dreamyourmansion/administration-architecture-building-962989.jpg","TIMESTAMP_UPLOADED":"20191105_002321"},"./src/images/dreamyourmansion/blue-daylight-nature-2549021.jpg":{"TIMESTAMP_UPLOADED":"20191002_155557","FILEPATH":"./src/images/dreamyourmansion/blue-daylight-nature-2549021.jpg"},"./src/images/dreamyourmansion/architectural-design-architecture-building-exterior-616155.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-building-exterior-616155.jpg","TIMESTAMP_UPLOADED":"20191020_002317"},"./src/images/dreamyourmansion/apartment-architecture-balcony-276539.jpg":{"TIMESTAMP_UPLOADED":"20191030_002316","FILEPATH":"./src/images/dreamyourmansion/apartment-architecture-balcony-276539.jpg"},"./src/images/dreamyourmansion/architecture-backyard-brickwalls-221024.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-backyard-brickwalls-221024.jpg","TIMESTAMP_UPLOADED":"20191024_122320"},"./src/images/dreamyourmansion/architecture-backlit-building-2440984.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-backlit-building-2440984.jpg","TIMESTAMP_UPLOADED":"20191118_122321"},"./src/images/dreamyourmansion/architecture-building-cabin-279857.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-building-cabin-279857.jpg","TIMESTAMP_UPLOADED":"20191018_002320"},"./src/images/dreamyourmansion/architecture-daylight-door-1661566.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architecture-daylight-door-1661566.jpg","TIMESTAMP_UPLOADED":"20191010_122332"},"./src/images/dreamyourmansion/architectural-design-architecture-blue-sky-2664118.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-blue-sky-2664118.jpg","TIMESTAMP_UPLOADED":"20191124_122319"},"./src/images/dreamyourmansion/architecture-calm-dug-out-pool-2677398.jpg":{"TIMESTAMP_UPLOADED":"20191002_161141","FILEPATH":"./src/images/dreamyourmansion/architecture-calm-dug-out-pool-2677398.jpg"},"./src/images/dreamyourmansion/chairs-contemporary-counter-1790224.jpg":{"TIMESTAMP_UPLOADED":"20191030_222530","FILEPATH":"./src/images/dreamyourmansion/chairs-contemporary-counter-1790224.jpg"},"./src/images/dreamyourmansion/banking-buy-construction-210617.jpg":{"TIMESTAMP_UPLOADED":"20191130_002317","FILEPATH":"./src/images/dreamyourmansion/banking-buy-construction-210617.jpg"},"./src/images/dreamyourmansion/architectural-design-architecture-bridge-283657.jpg":{"FILEPATH":"./src/images/dreamyourmansion/architectural-design-architecture-bridge-283657.jpg","TIMESTAMP_UPLOADED":"20191023_002324"},"architecture-building-columns-208732.jpg":{"TIMESTAMP_UPLOADED":"20190930_122302","FILEPATH":"/home/pi/instafeed/src/images/architecture-building-columns-208732.jpg"},"./src/images/dreamyourmansion/beach-beautiful-blue-374549.jpg":{"FILEPATH":"./src/images/dreamyourmansion/beach-beautiful-blue-374549.jpg","TIMESTAMP_UPLOADED":"20191122_002321"},"./src/images/dreamyourmansion/ceiling-chairs-contemporary-1864898.jpg":{"FILEPATH":"./src/images/dreamyourmansion/ceiling-chairs-contemporary-1864898.jpg","TIMESTAMP_UPLOADED":"20191015_002302"},"architecture-balcony-blue-sky-343240.jpg":{"TIMESTAMP_UPLOADED":"20190918_145805","FILEPATH":"/home/pi/instafeed/src/images/architecture-balcony-blue-sky-343240.jpg"},"architecture-building-construction-53610.jpg":{"TIMESTAMP_UPLOADED":"20190922_002638","FILEPATH":"/home/pi/instafeed/src/images/architecture-building-construction-53610.jpg"},"beach-chairs-clouds-hotel-338504.jpg":{"FILEPATH":"/home/pi/instafeed/src/images/beach-chairs-clouds-hotel-338504.jpg","TIMESTAMP_UPLOADED":"20190923_002632"},"architecture-grass-landscape-221522.jpg":{"TIMESTAMP_UPLOADED":"20190929_122330","FILEPATH":"/home/pi/instafeed/src/images/architecture-grass-landscape-221522.jpg"},"./src/images/dreamyourmansion/beach-clouds-couple-1287454.jpg":{"FILEPATH":"./src/images/dreamyourmansion/beach-clouds-couple-1287454.jpg","TIMESTAMP_UPLOADED":"20191116_122320"},"./src/images/dreamyourmansion/abandoned-architecture-barn-2360673.jpg":{"TIMESTAMP_UPLOADED":"20191013_002324","FILEPATH":"./src/images/dreamyourmansion/abandoned-architecture-barn-2360673.jpg"},"./src/images/dreamyourmansion/architecture-art-building-1381729.jpg":{"TIMESTAMP_UPLOADED":"20191116_002326","FILEPATH":"./src/images/dreamyourmansion/architecture-art-building-1381729.jpg"},"./src/images/dreamyourmansion/architecture-bushes-chimneys-208736.jpg":{"TIMESTAMP_UPLOADED":"20191009_002321","FILEPATH":"./src/images/dreamyourmansion/architecture-bushes-chimneys-208736.jpg"}}
\ No newline at end of file
diff --git a/src/db/db_vstbestprices.dat b/src/db/db_vstbestprices.dat
new file mode 100755
index 0000000..5d21f1e
--- /dev/null
+++ b/src/db/db_vstbestprices.dat
@@ -0,0 +1 @@
+{"./src/images/vstbestprices//Klevgrand â Modley v1.0.1 â R2R (VST, VST3, AAX, AU) [WiN.OSX x64].png":{"FILEPATH":"./src/images/vstbestprices//Klevgrand â Modley v1.0.1 â R2R (VST, VST3, AAX, AU) [WiN.OSX x64].png","TIMESTAMP_UPLOADED":"20191023_163320"},"./src/images/vstbestprices//Tracktion Software Waveform v9.3.6 [OSX x64].png":{"FILEPATH":"./src/images/vstbestprices//Tracktion Software Waveform v9.3.6 [OSX x64].png","TIMESTAMP_UPLOADED":"20191007_163326"},"./src/images/vstbestprices//Waves Casual â Nylon v1.0.1 (VSTi, VST3) [WiN x64].png":{"FILEPATH":"./src/images/vstbestprices//Waves Casual â Nylon v1.0.1 (VSTi, VST3) [WiN x64].png","TIMESTAMP_UPLOADED":"20191014_083316"},"./src/images/vstbestprices/Positive Grid â BIAS Pedal Pro 2.3.3.5467 (STANDALONE, VST, RTAS, AAX) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191116_163317","FILEPATH":"./src/images/vstbestprices/Positive Grid â BIAS Pedal Pro 2.3.3.5467 (STANDALONE, VST, RTAS, AAX) [WiN x64].jpeg"},"./src/images/vstbestprices//PG Music â Band-in-a-Box 2019 + RealBand 2019 PlusPAK Build 628 + RealTracks Sets 301-328 + RealTracks Update 2019 (STANDALONE, VST, VST3, AAX) [WiN x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//PG Music â Band-in-a-Box 2019 + RealBand 2019 PlusPAK Build 628 + RealTracks Sets 301-328 + RealTracks Update 2019 (STANDALONE, VST, VST3, AAX) [WiN x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191020_003317"},"./src/images/vstbestprices//Serato â Studio 1.0.0 [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191023_083317","FILEPATH":"./src/images/vstbestprices//Serato â Studio 1.0.0 [WiN x64].jpeg"},"./src/images/vstbestprices//kiloHearts â Toolbox Ultimate v1.7.3 â Rev2 (VSTi, VST, AAX) [WiN x64].png":{"FILEPATH":"./src/images/vstbestprices//kiloHearts â Toolbox Ultimate v1.7.3 â Rev2 (VSTi, VST, AAX) [WiN x64].png","TIMESTAMP_UPLOADED":"20191020_163322"},"./src/images/vstbestprices//W.A.Production â Screamo 1.0.1 (VST, VST3, AAX, AU) [WIN.OSX x64 x86].jpeg":{"FILEPATH":"./src/images/vstbestprices//W.A.Production â Screamo 1.0.1 (VST, VST3, AAX, AU) [WIN.OSX x64 x86].jpeg","TIMESTAMP_UPLOADED":"20191028_083317"},"./src/images/vstbestprices//United Plugins â Soundevice Digital Royal Compressor 1.0 (VST, VST3, AAX) [WiN x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//United Plugins â Soundevice Digital Royal Compressor 1.0 (VST, VST3, AAX) [WiN x64].jpeg","TIMESTAMP_UPLOADED":"20191016_083302"},"./src/images/vstbestprices//Raising Jake Studios â SideMinder ME v1.1.0 (VST, VST3, AAX, AU) [WiN.OSX x64].png":{"TIMESTAMP_UPLOADED":"20191005_003318","FILEPATH":"./src/images/vstbestprices//Raising Jake Studios â SideMinder ME v1.1.0 (VST, VST3, AAX, AU) [WiN.OSX x64].png"},"./src/images/vstbestprices//NoiseAsh â Rule Tec All Collection v1.1.0 (VST, AU, AAX) [WiN.OSX x86 x64].png":{"TIMESTAMP_UPLOADED":"20191025_083318","FILEPATH":"./src/images/vstbestprices//NoiseAsh â Rule Tec All Collection v1.1.0 (VST, AU, AAX) [WiN.OSX x86 x64].png"},"./src/images/vstbestprices/W.A.Production â Imprint v1.0.1 (VST, VST3, AAX, AU) [WiN.OSX x64 x86].jpeg":{"FILEPATH":"./src/images/vstbestprices/W.A.Production â Imprint v1.0.1 (VST, VST3, AAX, AU) [WiN.OSX x64 x86].jpeg","TIMESTAMP_UPLOADED":"20191119_003318"},"./src/images/vstbestprices/Venn Audio â V-Clip v1.0.02 â FiX (VST, VST3, AAX) [WiN x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/Venn Audio â V-Clip v1.0.02 â FiX (VST, VST3, AAX) [WiN x64].jpeg","TIMESTAMP_UPLOADED":"20191114_003308"},"./src/images/vstbestprices//Waves â 10 Complete 10.7.2019 (STANDALONE, VST, VST3, RTAS, AAX, AU) [WIN.OSX x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//Waves â 10 Complete 10.7.2019 (STANDALONE, VST, VST3, RTAS, AAX, AU) [WIN.OSX x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191018_083316"},"./src/images/vstbestprices//u-he â Hive v2.0.0.8791 (VSTi, VST3, AAX) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191024_083316","FILEPATH":"./src/images/vstbestprices//u-he â Hive v2.0.0.8791 (VSTi, VST3, AAX) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//Tracktion MOK Waverazor v.2.0.2 (AU, VSTi, VSTi3) [OSX].png":{"TIMESTAMP_UPLOADED":"20191008_003316","FILEPATH":"./src/images/vstbestprices//Tracktion MOK Waverazor v.2.0.2 (AU, VSTi, VSTi3) [OSX].png"},"./src/images/vstbestprices/Analog Obsession â Full Bundle 04.2019 (VST, AU) [WIN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191116_003316","FILEPATH":"./src/images/vstbestprices/Analog Obsession â Full Bundle 04.2019 (VST, AU) [WIN.OSX x86 x64].jpeg"},"./src/images/vstbestprices//SoundSpot â Mercury Bundle 2019.6 (VST, VST3, AAX) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191015_163302","FILEPATH":"./src/images/vstbestprices//SoundSpot â Mercury Bundle 2019.6 (VST, VST3, AAX) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//Venn Audio â V-Clip â NO INSTALL, SymLink Installer (VST, VST3, AAX) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191010_163523","FILEPATH":"./src/images/vstbestprices//Venn Audio â V-Clip â NO INSTALL, SymLink Installer (VST, VST3, AAX) [WiN x64].jpeg"},"./src/images/vstbestprices//DDMF â MagicDeathEye v1.0.1 â R2R (VST, VST3, AAX) [WiN x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//DDMF â MagicDeathEye v1.0.1 â R2R (VST, VST3, AAX) [WiN x86 x64].png","TIMESTAMP_UPLOADED":"20191027_083317"},"./src/images/vstbestprices//W.A Production â Ascension 1.0.1 (STANDALONE, VSTi, VSTi3) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191029_003317","FILEPATH":"./src/images/vstbestprices//W.A Production â Ascension 1.0.1 (STANDALONE, VSTi, VSTi3) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//MAGIX â Vandal v1.112 (VST) [WiN x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//MAGIX â Vandal v1.112 (VST) [WiN x86 x64].png","TIMESTAMP_UPLOADED":"20191026_163320"},"./src/images/vstbestprices//HoRNet â SW34EQ MK2 2.1.2 (VST, VST3, AU) [WIN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191026_003316","FILEPATH":"./src/images/vstbestprices//HoRNet â SW34EQ MK2 2.1.2 (VST, VST3, AU) [WIN.OSX x86 x64].jpeg"},"./src/images/vstbestprices/Positive Grid â BIAS FX 2 Elite Complete + DeskTop v2.1.1.4655 (STANDALONE, VST, AAX) [WiN x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/Positive Grid â BIAS FX 2 Elite Complete + DeskTop v2.1.1.4655 (STANDALONE, VST, AAX) [WiN x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191115_163324"},"./src/images/vstbestprices//Kuassa â Efektor Distortion Bundle 1.0.6 (VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//Kuassa â Efektor Distortion Bundle 1.0.6 (VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191014_163320"},"./src/images/vstbestprices/Kuassa â Amplifikation Matchlock 1.0.0 (STANDALONE, VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/Kuassa â Amplifikation Matchlock 1.0.0 (STANDALONE, VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191120_083312"},"./src/images/vstbestprices//uJAM â Virtual Drummer HEAVY 2.0.0.192 (VSTi, AAX) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191017_083318","FILEPATH":"./src/images/vstbestprices//uJAM â Virtual Drummer HEAVY 2.0.0.192 (VSTi, AAX) [WiN x64].jpeg"},"./src/images/vstbestprices//LUXONIX â ravity Bundle v1.4.3 (STANDALONE, VSTi) [WiN x86].jpeg":{"TIMESTAMP_UPLOADED":"20191007_083320","FILEPATH":"./src/images/vstbestprices//LUXONIX â ravity Bundle v1.4.3 (STANDALONE, VSTi) [WiN x86].jpeg"},"./src/images/vstbestprices//kiloHearts â Toolbox Ultimate 1.7.1 (VST, VSTi, AAX) [WiN x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//kiloHearts â Toolbox Ultimate 1.7.1 (VST, VSTi, AAX) [WiN x64].jpeg","TIMESTAMP_UPLOADED":"20191022_083318"},"./src/images/vstbestprices//Kuassa â Bundle â NO INSTALL, SymLink Installer (VST, VST3, AAX, STANDALONE) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191012_003316","FILEPATH":"./src/images/vstbestprices//Kuassa â Bundle â NO INSTALL, SymLink Installer (VST, VST3, AAX, STANDALONE) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//NUGEN â Audio Stereoplacer 3.2.0.1 (VST, VST3, RTAS, AAX, AU) [WIN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191025_003317","FILEPATH":"./src/images/vstbestprices//NUGEN â Audio Stereoplacer 3.2.0.1 (VST, VST3, RTAS, AAX, AU) [WIN.OSX x86 x64].jpeg"},"./src/images/vstbestprices/ISM â DuckDelay v1.0.0 â R2R (VST, VST3, AAX, AU) [WiN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191119_083313","FILEPATH":"./src/images/vstbestprices/ISM â DuckDelay v1.0.0 â R2R (VST, VST3, AAX, AU) [WiN.OSX x86 x64].jpeg"},"./src/images/vstbestprices//Serato â DJ Pro Suite v2.1.2 [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191022_003318","FILEPATH":"./src/images/vstbestprices//Serato â DJ Pro Suite v2.1.2 [WiN x64].jpeg"},"./src/images/vstbestprices//Stillwell Audio â All Plugins Bundle v3.0.3 (RTAS, VST, VST3) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191013_163318","FILEPATH":"./src/images/vstbestprices//Stillwell Audio â All Plugins Bundle v3.0.3 (RTAS, VST, VST3) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//Tracktion Software DAW Essentials Collection v1.0.25 (AU, VST) [OSX x64].png":{"FILEPATH":"./src/images/vstbestprices//Tracktion Software DAW Essentials Collection v1.0.25 (AU, VST) [OSX x64].png","TIMESTAMP_UPLOADED":"20191006_163320"},"./src/images/vstbestprices/Klevgrand â FX Bundle 6.2019 (VST, AAX) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191118_163318","FILEPATH":"./src/images/vstbestprices/Klevgrand â FX Bundle 6.2019 (VST, AAX) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//FeelyoursoundHQ â ChordPotion v1.1.0 (VST) [WiN x64].png":{"TIMESTAMP_UPLOADED":"20191025_163320","FILEPATH":"./src/images/vstbestprices//FeelyoursoundHQ â ChordPotion v1.1.0 (VST) [WiN x64].png"},"./src/images/vstbestprices/Gramotech â Babylon v19.2.4 (VST3) [WiN x64].png":{"TIMESTAMP_UPLOADED":"20191030_221328","FILEPATH":"./src/images/vstbestprices/Gramotech â Babylon v19.2.4 (VST3) [WiN x64].png"},"./src/images/vstbestprices//AIR Music Tech â Hybrid 3.0.7 â REPACK (VSTi) [WiN x86 x64].png":{"TIMESTAMP_UPLOADED":"20191006_003318","FILEPATH":"./src/images/vstbestprices//AIR Music Tech â Hybrid 3.0.7 â REPACK (VSTi) [WiN x86 x64].png"},"./src/images/vstbestprices//HoRNet â DeeLay Plus 1.2.3 (VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//HoRNet â DeeLay Plus 1.2.3 (VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191024_163319"},"./src/images/vstbestprices//NUGEN â Audio Stereoizer 3.4.0.1 (VST, VST3, RTAS, AAX, AU) [WIN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191019_163319","FILEPATH":"./src/images/vstbestprices//NUGEN â Audio Stereoizer 3.4.0.1 (VST, VST3, RTAS, AAX, AU) [WIN.OSX x86 x64].jpeg"},"./src/images/vstbestprices/29Palms â Mastering The Mix Collection 2019.6 â FIXED (STANDALONE, VST, VST3, AAX) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191030_221110","FILEPATH":"./src/images/vstbestprices/29Palms â Mastering The Mix Collection 2019.6 â FIXED (STANDALONE, VST, VST3, AAX) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//Acon Digital â Verberate Immersive 2 v2.0.2 â R2R (AU, VST, VST3, AAX) [WiN.OSX x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//Acon Digital â Verberate Immersive 2 v2.0.2 â R2R (AU, VST, VST3, AAX) [WiN.OSX x86 x64].png","TIMESTAMP_UPLOADED":"20191021_163322"},"./src/images/vstbestprices/Producers Vault â METALES 1.1 (VSTi) [WiN x86 x64].png":{"TIMESTAMP_UPLOADED":"20191002_160508","FILEPATH":"./src/images/vstbestprices/Producers Vault â METALES 1.1 (VSTi) [WiN x86 x64].png"},"./src/images/vstbestprices//FeelYourSound â XotoPad 2.8.0 [WiN x86].jpeg":{"FILEPATH":"./src/images/vstbestprices//FeelYourSound â XotoPad 2.8.0 [WiN x86].jpeg","TIMESTAMP_UPLOADED":"20191028_163320"},"./src/images/vstbestprices//Tracktion Software BioTek 2 v2.1.2 (AU, VSTi) [OSX x64].png":{"TIMESTAMP_UPLOADED":"20191009_083317","FILEPATH":"./src/images/vstbestprices//Tracktion Software BioTek 2 v2.1.2 (AU, VSTi) [OSX x64].png"},"./src/images/vstbestprices//Ample Sound â Ample Bass Upright III v3.00 (VSTi, VSTi3, AAX, AUi) [OSX x64].png":{"FILEPATH":"./src/images/vstbestprices//Ample Sound â Ample Bass Upright III v3.00 (VSTi, VSTi3, AAX, AUi) [OSX x64].png","TIMESTAMP_UPLOADED":"20191018_003323"},"./src/images/vstbestprices//Native Instruments Massive v1.5.5 (STANDALONE, AU, VST) [OSX].png":{"TIMESTAMP_UPLOADED":"20191005_083321","FILEPATH":"./src/images/vstbestprices//Native Instruments Massive v1.5.5 (STANDALONE, AU, VST) [OSX].png"},"./src/images/vstbestprices//Kuassa â Efektor CP3603 1.0.1 (VST, VST3, AAX) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191021_003316","FILEPATH":"./src/images/vstbestprices//Kuassa â Efektor CP3603 1.0.1 (VST, VST3, AAX) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//29Palms â Mastering The Mix Collection v18.6.2019 (EXE, VST, VST3, AAX) [WiN x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//29Palms â Mastering The Mix Collection v18.6.2019 (EXE, VST, VST3, AAX) [WiN x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191013_083316"},"./src/images/vstbestprices/Hexachord â Orb Composer Pro v1.2.1 (VSTi) [WiN x64].png":{"TIMESTAMP_UPLOADED":"20191002_160929","FILEPATH":"./src/images/vstbestprices/Hexachord â Orb Composer Pro v1.2.1 (VSTi) [WiN x64].png"},"./src/images/vstbestprices//Zplane â Bundle â NO INSTALL, SymLink Installer (VST, VST3, AAX, STANDALONE) [WiN x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//Zplane â Bundle â NO INSTALL, SymLink Installer (VST, VST3, AAX, STANDALONE) [WiN x86 x64].png","TIMESTAMP_UPLOADED":"20191005_163319"},"./src/images/vstbestprices//Initial Audio â Master Suite v1.0.0 â R2R (VST, VST3, AU) [WIN.OSX x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//Initial Audio â Master Suite v1.0.0 â R2R (VST, VST3, AU) [WIN.OSX x86 x64].png","TIMESTAMP_UPLOADED":"20191030_083314"},"./src/images/vstbestprices//Output â Bundle â NO INSTALL, SymLink Installer (VST, VST3, AAX) [WiN x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//Output â Bundle â NO INSTALL, SymLink Installer (VST, VST3, AAX) [WiN x86 x64].png","TIMESTAMP_UPLOADED":"20191004_163325"},"./src/images/vstbestprices//uJAM â Virtual Drummer PHAT 2.0.0.258 (VSTi, AAX) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191015_003302","FILEPATH":"./src/images/vstbestprices//uJAM â Virtual Drummer PHAT 2.0.0.258 (VSTi, AAX) [WiN x86 x64].jpeg"},"./src/images/vstbestprices/Unfiltered Audio â Plugins Bundle 2019.6 rev.2 (VST, VST3, AAX) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191115_003315","FILEPATH":"./src/images/vstbestprices/Unfiltered Audio â Plugins Bundle 2019.6 rev.2 (VST, VST3, AAX) [WiN x86 x64].jpeg"},"./src/images/vstbestprices/Hexachords â Orb Composer S Pro v1.4.4 (VSTi) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191116_083317","FILEPATH":"./src/images/vstbestprices/Hexachords â Orb Composer S Pro v1.4.4 (VSTi) [WiN x64].jpeg"},"./src/images/vstbestprices//United Plugins â JMG Sound Hyperspace 1.0 (VST, VST3, AAX) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191030_003314","FILEPATH":"./src/images/vstbestprices//United Plugins â JMG Sound Hyperspace 1.0 (VST, VST3, AAX) [WiN x64].jpeg"},"./src/images/vstbestprices//Audiority â Pedalboard Distortions 1.0.1 (VST, AAX, AU) [WIN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191017_003316","FILEPATH":"./src/images/vstbestprices//Audiority â Pedalboard Distortions 1.0.1 (VST, AAX, AU) [WIN.OSX x86 x64].jpeg"},"./src/images/vstbestprices//Apple â Logic Pro X 10.4.6 [OSX].png":{"FILEPATH":"./src/images/vstbestprices//Apple â Logic Pro X 10.4.6 [OSX].png","TIMESTAMP_UPLOADED":"20191019_003317"},"./src/images/vstbestprices//NoiseAsh â Bundle â NO INSTALL, SymLink Installer (VST, AAX) [WiN x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//NoiseAsh â Bundle â NO INSTALL, SymLink Installer (VST, AAX) [WiN x86 x64].png","TIMESTAMP_UPLOADED":"20191012_163319"},"./src/images/vstbestprices//IK Multimedia â T-RackS 5 Complete 5.2.1 (STANDALONE, AU, VST2, VST3, AAX) [OSX x64].png":{"FILEPATH":"./src/images/vstbestprices//IK Multimedia â T-RackS 5 Complete 5.2.1 (STANDALONE, AU, VST2, VST3, AAX) [OSX x64].png","TIMESTAMP_UPLOADED":"20191016_003302"},"./src/images/vstbestprices//Audio Damage â AD043 Filterstation2 v2.0.5 (VST, VST3, AAX, AU) [WiN.OSX x86 x64].png":{"TIMESTAMP_UPLOADED":"20191008_163319","FILEPATH":"./src/images/vstbestprices//Audio Damage â AD043 Filterstation2 v2.0.5 (VST, VST3, AAX, AU) [WiN.OSX x86 x64].png"},"./src/images/vstbestprices//Native Instruments â Massive X v1.0.0 (VSTi, AAX) [WiN x64].png":{"FILEPATH":"./src/images/vstbestprices//Native Instruments â Massive X v1.0.0 (VSTi, AAX) [WiN x64].png","TIMESTAMP_UPLOADED":"20191002_154808"},"./src/images/vstbestprices//Xfer Records â Serum v1.23b7 (VSTi, AAX) [WiN x64 x86].png":{"FILEPATH":"./src/images/vstbestprices//Xfer Records â Serum v1.23b7 (VSTi, AAX) [WiN x64 x86].png","TIMESTAMP_UPLOADED":"20191030_220658"},"./src/images/vstbestprices/Steinberg â SpectraLayers Pro v6.0.10 (VST3, AAX, SAL) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191002_160626","FILEPATH":"./src/images/vstbestprices/Steinberg â SpectraLayers Pro v6.0.10 (VST3, AAX, SAL) [WiN x64].jpeg"},"./src/images/vstbestprices//Soundspot â Mercury Bundle 2019.05.30 (VST, VST3, AU) [OSX].png":{"TIMESTAMP_UPLOADED":"20191026_083317","FILEPATH":"./src/images/vstbestprices//Soundspot â Mercury Bundle 2019.05.30 (VST, VST3, AU) [OSX].png"},"./src/images/vstbestprices/Ample Sound â Ample Bass P III v3.00 (VSTi, VSTi3, AAX) [WiN x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/Ample Sound â Ample Bass P III v3.00 (VSTi, VSTi3, AAX) [WiN x64].jpeg","TIMESTAMP_UPLOADED":"20191118_083317"},"./src/images/vstbestprices//iZotope â Neutron Advanced 3.00 â NO INSTALL, SymLink Installer (VST, VST3, AAX) [WiN x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//iZotope â Neutron Advanced 3.00 â NO INSTALL, SymLink Installer (VST, VST3, AAX) [WiN x64].jpeg","TIMESTAMP_UPLOADED":"20191003_163329"},"./src/images/vstbestprices//Wavesfactory â Mercury FX 1.0.0 (VST, AU) [WIN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191020_083315","FILEPATH":"./src/images/vstbestprices//Wavesfactory â Mercury FX 1.0.0 (VST, AU) [WIN.OSX x86 x64].jpeg"},"./src/images/vstbestprices/Voxengo â Powershaper 1.1 (VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191117_083316","FILEPATH":"./src/images/vstbestprices/Voxengo â Powershaper 1.1 (VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg"},"./src/images/vstbestprices/AIR Music Tech â Grand 1.2.7 â REPACK (VSTi) [WiN x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/AIR Music Tech â Grand 1.2.7 â REPACK (VSTi) [WiN x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191002_160000"},"./src/images/vstbestprices/ToneBoosters â All Plugin Bundle 1.3.0 (VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191120_163316","FILEPATH":"./src/images/vstbestprices/ToneBoosters â All Plugin Bundle 1.3.0 (VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg"},"./src/images/vstbestprices//Inertia Sound Systems â Instinct â NO INSTALL, SymLink Installer (VST) [WiN x64].png":{"TIMESTAMP_UPLOADED":"20191008_083319","FILEPATH":"./src/images/vstbestprices//Inertia Sound Systems â Instinct â NO INSTALL, SymLink Installer (VST) [WiN x64].png"},"./src/images/vstbestprices//MeldaProduction â MAudioPlugins 13.03 (VST, VST3, AAX) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191019_083316","FILEPATH":"./src/images/vstbestprices//MeldaProduction â MAudioPlugins 13.03 (VST, VST3, AAX) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//Output â Portal v1.0.1 (VST, VST3, AU) [WiN x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//Output â Portal v1.0.1 (VST, VST3, AU) [WiN x86 x64].png","TIMESTAMP_UPLOADED":"20191014_003318"},"./src/images/vstbestprices//NoiseAsh â Need 31102 Console EQ v1.0 â R2R (VST, AAX, AU) [WiN.OSX x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//NoiseAsh â Need 31102 Console EQ v1.0 â R2R (VST, AAX, AU) [WiN.OSX x86 x64].png","TIMESTAMP_UPLOADED":"20191022_163326"},"./src/images/vstbestprices//Ample Sound â Ample Guitar M III 3.01 (VSTi, VSTi3, AAX, AUi) [OSX x64].png":{"FILEPATH":"./src/images/vstbestprices//Ample Sound â Ample Guitar M III 3.01 (VSTi, VSTi3, AAX, AUi) [OSX x64].png","TIMESTAMP_UPLOADED":"20191027_003318"},"./src/images/vstbestprices//AIR Music Tech â Velvet 2.0.7 â REPACK (VSTi) [WiN x86 x64].png":{"TIMESTAMP_UPLOADED":"20191009_003317","FILEPATH":"./src/images/vstbestprices//AIR Music Tech â Velvet 2.0.7 â REPACK (VSTi) [WiN x86 x64].png"},"./src/images/vstbestprices//Propellerhead â Reason Limited v1.5.3 [OSX x64].png":{"TIMESTAMP_UPLOADED":"20191009_163319","FILEPATH":"./src/images/vstbestprices//Propellerhead â Reason Limited v1.5.3 [OSX x64].png"},"./src/images/vstbestprices/Cockos â REAPER v5.979 + Rus + Portable + Plugins [Win x86 x64].png":{"FILEPATH":"./src/images/vstbestprices/Cockos â REAPER v5.979 + Rus + Portable + Plugins [Win x86 x64].png","TIMESTAMP_UPLOADED":"20191117_163320"},"./src/images/vstbestprices/Guda Audio â EnvelopR 1.4 (VST, AU) [WIN.OSX x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/Guda Audio â EnvelopR 1.4 (VST, AU) [WIN.OSX x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191118_003316"},"./src/images/vstbestprices//Steinberg â Groove Agent 5.0.10.99 (STANDALONE, VSTi3, AAX) [WiN x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//Steinberg â Groove Agent 5.0.10.99 (STANDALONE, VSTi3, AAX) [WiN x64].jpeg","TIMESTAMP_UPLOADED":"20191004_083329"},"./src/images/vstbestprices/Voxengo â bundle 6.2019 rev â 2 (VST, VST3, AAX) [WiN x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/Voxengo â bundle 6.2019 rev â 2 (VST, VST3, AAX) [WiN x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191120_003313"},"./src/images/vstbestprices/Positive Grid â BIAS AMP 2 Elite Complete 2.2.8.1409 (STANDALONE, VST, RTAS, AAX) [WiN x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/Positive Grid â BIAS AMP 2 Elite Complete 2.2.8.1409 (STANDALONE, VST, RTAS, AAX) [WiN x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191030_221015"},"./src/images/vstbestprices//u-he â Diva v1.4.3.8791 (VSTi, VST3, AAX) [WiN x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//u-he â Diva v1.4.3.8791 (VSTi, VST3, AAX) [WiN x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191007_003319"},"./src/images/vstbestprices//Sonible â smartComp v1.0.0 (VST, VST3, AAX) [WiN x86 x64].png":{"TIMESTAMP_UPLOADED":"20191030_163327","FILEPATH":"./src/images/vstbestprices//Sonible â smartComp v1.0.0 (VST, VST3, AAX) [WiN x86 x64].png"},"./src/images/vstbestprices//Ableton â Live Suite v10.1 â R2R [WiN.OSX x64].png":{"FILEPATH":"./src/images/vstbestprices//Ableton â Live Suite v10.1 â R2R [WiN.OSX x64].png","TIMESTAMP_UPLOADED":"20191018_163321"},"./src/images/vstbestprices//UJAM â Drums 2 Bundle â NO INSTALL, SymLink Installer (VSTi, AAX) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191004_003325","FILEPATH":"./src/images/vstbestprices//UJAM â Drums 2 Bundle â NO INSTALL, SymLink Installer (VSTi, AAX) [WiN x64].jpeg"},"./src/images/vstbestprices//PreSonus â Studio One 4 Professional 4.5.1 -R2R [WIN.OSX x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//PreSonus â Studio One 4 Professional 4.5.1 -R2R [WIN.OSX x64].jpeg","TIMESTAMP_UPLOADED":"20191023_003317"},"./src/images/vstbestprices//Vengeance Producer Suite â Avenger v1.4.10 + Factory content (VSTi, VSTi3, AAX) [WiN x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//Vengeance Producer Suite â Avenger v1.4.10 + Factory content (VSTi, VSTi3, AAX) [WiN x64].jpeg","TIMESTAMP_UPLOADED":"20191012_083317"},"./src/images/vstbestprices//zplane â Elastique & PPM Bundle 6.2019 (SAL, RTAS, VST, VST3, AAX) [WiN x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191024_003317","FILEPATH":"./src/images/vstbestprices//zplane â Elastique & PPM Bundle 6.2019 (SAL, RTAS, VST, VST3, AAX) [WiN x86 x64].jpeg"},"./src/images/vstbestprices//VAST Dynamics â Vaporizer2 v2.4.1 â NO INSTALL, SymLink Installer (VSTi, VSTi3, STANDALONE) [WiN x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//VAST Dynamics â Vaporizer2 v2.4.1 â NO INSTALL, SymLink Installer (VSTi, VSTi3, STANDALONE) [WiN x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191006_083317"},"./src/images/vstbestprices//Ample Sound â Ample Ethno Ukulele III v3.00 (VSTi, VSTi3, AAX) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191016_163323","FILEPATH":"./src/images/vstbestprices//Ample Sound â Ample Ethno Ukulele III v3.00 (VSTi, VSTi3, AAX) [WiN x64].jpeg"},"./src/images/vstbestprices//Tracktion Software Collective v1.2.1 (AU, VST) [OSX x64].png":{"TIMESTAMP_UPLOADED":"20191013_003317","FILEPATH":"./src/images/vstbestprices//Tracktion Software Collective v1.2.1 (AU, VST) [OSX x64].png"},"./src/images/vstbestprices//AIR Music Tech â Xpand 2.2.7 â REPACK (VSTi) [WiN x86 x64].png":{"FILEPATH":"./src/images/vstbestprices//AIR Music Tech â Xpand 2.2.7 â REPACK (VSTi) [WiN x86 x64].png","TIMESTAMP_UPLOADED":"20191010_083329"},"./src/images/vstbestprices//Kuassa â Amplification Bundle 2019.7 (STANDALONE, VST, VST3, AAX) [WiN x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//Kuassa â Amplification Bundle 2019.7 (STANDALONE, VST, VST3, AAX) [WiN x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191029_163324"},"./src/images/vstbestprices/Image-Line â FL Studio Producer Edition + Signature Bundle v20.5.0.1142 [WiN x86 x64].png":{"FILEPATH":"./src/images/vstbestprices/Image-Line â FL Studio Producer Edition + Signature Bundle v20.5.0.1142 [WiN x86 x64].png","TIMESTAMP_UPLOADED":"20191119_163319"},"./src/images/vstbestprices//iZotope â Neutron 3 Advanced v3.00 PROPER â R2R (VST, VST3, AAX) [WiN Ñ 86 x64].png":{"TIMESTAMP_UPLOADED":"20191027_163320","FILEPATH":"./src/images/vstbestprices//iZotope â Neutron 3 Advanced v3.00 PROPER â R2R (VST, VST3, AAX) [WiN Ñ 86 x64].png"},"./src/images/vstbestprices//SonicLAB â Cosmos Saturn7 (STANDALONE, VSTi3) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191002_210735","FILEPATH":"./src/images/vstbestprices//SonicLAB â Cosmos Saturn7 (STANDALONE, VSTi3) [WiN x64].jpeg"},"./src/images/vstbestprices/Overloud â Gem OTD-2 1.0.0 (STANDALONE, VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/Overloud â Gem OTD-2 1.0.0 (STANDALONE, VST, VST3, AAX, AU) [WIN.OSX x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191115_083308"},"./src/images/vstbestprices//Gramotech â Pompeii v19.5.0 (VST3) [WiN x64].png":{"TIMESTAMP_UPLOADED":"20191015_083302","FILEPATH":"./src/images/vstbestprices//Gramotech â Pompeii v19.5.0 (VST3) [WiN x64].png"},"./src/images/vstbestprices//Tracktion Software Master Mix 1.2.0 (AU, VST, VST3) [OSX x64].png":{"FILEPATH":"./src/images/vstbestprices//Tracktion Software Master Mix 1.2.0 (AU, VST, VST3) [OSX x64].png","TIMESTAMP_UPLOADED":"20191011_083358"},"./src/images/vstbestprices//Sonic Academy â KICK 2 v1.1.1 -R2R (VSTi, AAX, AU) [WiN x86 x64].png":{"TIMESTAMP_UPLOADED":"20191017_163320","FILEPATH":"./src/images/vstbestprices//Sonic Academy â KICK 2 v1.1.1 -R2R (VSTi, AAX, AU) [WiN x86 x64].png"},"./src/images/vstbestprices//uJAM â Virtual Drummer SOLID 2.0.0.2504 (VSTi, AAX) [WiN x64].jpeg":{"TIMESTAMP_UPLOADED":"20191029_083314","FILEPATH":"./src/images/vstbestprices//uJAM â Virtual Drummer SOLID 2.0.0.2504 (VSTi, AAX) [WiN x64].jpeg"},"./src/images/vstbestprices/Inertia Sound Systems â Instinct v1.0.0 (VST) [WiN x64].png":{"FILEPATH":"./src/images/vstbestprices/Inertia Sound Systems â Instinct v1.0.0 (VST) [WiN x64].png","TIMESTAMP_UPLOADED":"20191121_003313"},"./src/images/vstbestprices//Overloud â TH-U Complete v1.0.13 â R2R (EXE, VST, VST3, AU, AAX) [WIN.OSX x86 x64].jpeg":{"TIMESTAMP_UPLOADED":"20191028_003319","FILEPATH":"./src/images/vstbestprices//Overloud â TH-U Complete v1.0.13 â R2R (EXE, VST, VST3, AU, AAX) [WIN.OSX x86 x64].jpeg"},"./src/images/vstbestprices/AIR Music Tech â DB-33 1.2.7 â REPACK (VSTi) [WiN x86 x64].png":{"TIMESTAMP_UPLOADED":"20191002_155730","FILEPATH":"./src/images/vstbestprices/AIR Music Tech â DB-33 1.2.7 â REPACK (VSTi) [WiN x86 x64].png"},"./src/images/vstbestprices//Z3 Audiolabs â Repeat X 1.1 â NO INSTALL, SymLink Installer (VST) [WiN x86].png":{"FILEPATH":"./src/images/vstbestprices//Z3 Audiolabs â Repeat X 1.1 â NO INSTALL, SymLink Installer (VST) [WiN x86].png","TIMESTAMP_UPLOADED":"20191010_003317"},"./src/images/vstbestprices/Tritik â Echorus 1.0.4 (VST, AAX, AU) [WIN.OSX x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices/Tritik â Echorus 1.0.4 (VST, AAX, AU) [WIN.OSX x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191117_003317"},"./src/images/vstbestprices/Tracktion Software Waveform v9.3.4 [OSX x64].png":{"FILEPATH":"./src/images/vstbestprices/Tracktion Software Waveform v9.3.4 [OSX x64].png","TIMESTAMP_UPLOADED":"20191002_160823"},"./src/images/vstbestprices//Tracktion Software Waveform 10.2.1 [OSX x64].png":{"TIMESTAMP_UPLOADED":"20191011_003436","FILEPATH":"./src/images/vstbestprices//Tracktion Software Waveform 10.2.1 [OSX x64].png"},"./src/images/vstbestprices/Native Instruments â Reaktor 6.3.1 (STANDALONE, VSTi, AU) [OSX x64].png":{"FILEPATH":"./src/images/vstbestprices/Native Instruments â Reaktor 6.3.1 (STANDALONE, VSTi, AU) [OSX x64].png","TIMESTAMP_UPLOADED":"20191114_123330"},"./src/images/vstbestprices//VAST Dynamics â Vaporizer2 v2.4.1 (VSTi, VSTi3, AUi) [WIN.OSX x86 x64].jpeg":{"FILEPATH":"./src/images/vstbestprices//VAST Dynamics â Vaporizer2 v2.4.1 (VSTi, VSTi3, AUi) [WIN.OSX x86 x64].jpeg","TIMESTAMP_UPLOADED":"20191021_083318"},"./src/images/vstbestprices//United Plugins â Bundle â NO INSTALL, SymLink Installer (VST, VST3, AAX) [WiN x64].png":{"TIMESTAMP_UPLOADED":"20191011_163320","FILEPATH":"./src/images/vstbestprices//United Plugins â Bundle â NO INSTALL, SymLink Installer (VST, VST3, AAX) [WiN x64].png"}}
\ No newline at end of file
diff --git a/src/images/dreamyourmansion/7VPFyhB_j8Y.jpg b/src/images/dreamyourmansion/7VPFyhB_j8Y.jpg
new file mode 100755
index 0000000..0bc8a77
Binary files /dev/null and b/src/images/dreamyourmansion/7VPFyhB_j8Y.jpg differ
diff --git a/src/images/dreamyourmansion/RKdLlTyjm5g.jpg b/src/images/dreamyourmansion/RKdLlTyjm5g.jpg
new file mode 100755
index 0000000..9c2832d
Binary files /dev/null and b/src/images/dreamyourmansion/RKdLlTyjm5g.jpg differ
diff --git a/src/images/dreamyourmansion/UV81E0oXXWQ.jpg b/src/images/dreamyourmansion/UV81E0oXXWQ.jpg
new file mode 100755
index 0000000..446e0a0
Binary files /dev/null and b/src/images/dreamyourmansion/UV81E0oXXWQ.jpg differ
diff --git a/src/mpg25-instagram-api/vendor/autoload.php b/src/mpg25-instagram-api/vendor/autoload.php
new file mode 100755
index 0000000..f626d9e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/autoload.php
@@ -0,0 +1,7 @@
+
+
+> Permission is hereby granted, free of charge, to any person obtaining a copy
+> of this software and associated documentation files (the "Software"), to deal
+> in the Software without restriction, including without limitation the rights
+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+> copies of the Software, and to permit persons to whom the Software is
+> furnished to do so, subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in
+> all copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+> THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/README.md b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/README.md
new file mode 100755
index 0000000..ca3099d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/README.md
@@ -0,0 +1,145 @@
+# net-mqtt-client-react
+
+[![Latest Version on Packagist][ico-version]][link-packagist]
+[![Software License][ico-license]](LICENSE.md)
+[![Total Downloads][ico-downloads]][link-downloads]
+
+This package provides an asynchronous MQTT client built on the [React socket](https://github.com/reactphp/socket) library. All client methods return a promise which is fulfilled if the operation succeeded or rejected if the operation failed. Incoming messages of subscribed topics are delivered via the "message" event.
+
+## Install
+
+Via composer:
+
+``` bash
+$ composer require binsoul/net-mqtt-client-react
+```
+
+## Example
+
+Connect to a public broker and run forever.
+
+``` php
+createCached('8.8.8.8', $loop));
+$client = new ReactMqttClient($connector, $loop);
+
+// Bind to events
+$client->on('open', function () use ($client) {
+ // Network connection established
+ echo sprintf("Open: %s:%s\n", $client->getHost(), $client->getPort());
+});
+
+$client->on('close', function () use ($client, $loop) {
+ // Network connection closed
+ echo sprintf("Close: %s:%s\n", $client->getHost(), $client->getPort());
+
+ $loop->stop();
+});
+
+$client->on('connect', function (Connection $connection) {
+ // Broker connected
+ echo sprintf("Connect: client=%s\n", $connection->getClientID());
+});
+
+$client->on('disconnect', function (Connection $connection) {
+ // Broker disconnected
+ echo sprintf("Disconnect: client=%s\n", $connection->getClientID());
+});
+
+$client->on('message', function (Message $message) {
+ // Incoming message
+ echo 'Message';
+
+ if ($message->isDuplicate()) {
+ echo ' (duplicate)';
+ }
+
+ if ($message->isRetained()) {
+ echo ' (retained)';
+ }
+
+ echo ': '.$message->getTopic().' => '.mb_strimwidth($message->getPayload(), 0, 50, '...');
+ echo "\n";
+});
+
+$client->on('warning', function (\Exception $e) {
+ echo sprintf("Warning: %s\n", $e->getMessage());
+});
+
+$client->on('error', function (\Exception $e) use ($loop) {
+ echo sprintf("Error: %s\n", $e->getMessage());
+
+ $loop->stop();
+});
+
+// Connect to broker
+$client->connect('test.mosquitto.org')->then(
+ function () use ($client) {
+ // Subscribe to all topics
+ $client->subscribe(new DefaultSubscription('#'))
+ ->then(function (Subscription $subscription) {
+ echo sprintf("Subscribe: %s\n", $subscription->getFilter());
+ })
+ ->otherwise(function (\Exception $e) {
+ echo sprintf("Error: %s\n", $e->getMessage());
+ });
+
+ // Publish humidity once
+ $client->publish(new DefaultMessage('sensors/humidity', '55%'))
+ ->then(function (Message $message) {
+ echo sprintf("Publish: %s => %s\n", $message->getTopic(), $message->getPayload());
+ })
+ ->otherwise(function (\Exception $e) {
+ echo sprintf("Error: %s\n", $e->getMessage());
+ });
+
+ // Publish a random temperature every 10 seconds
+ $generator = function () {
+ return mt_rand(-20, 30);
+ };
+
+ $client->publishPeriodically(10, new DefaultMessage('sensors/temperature'), $generator)
+ ->progress(function (Message $message) {
+ echo sprintf("Publish: %s => %s\n", $message->getTopic(), $message->getPayload());
+ })
+ ->otherwise(function (\Exception $e) {
+ echo sprintf("Error: %s\n", $e->getMessage());
+ });
+ }
+);
+
+$loop->run();
+```
+
+## Testing
+
+``` bash
+$ composer test
+```
+
+## License
+
+The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
+
+[ico-version]: https://img.shields.io/packagist/v/binsoul/net-mqtt-client-react.svg?style=flat-square
+[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
+[ico-downloads]: https://img.shields.io/packagist/dt/binsoul/net-mqtt-client-react.svg?style=flat-square
+
+[link-packagist]: https://packagist.org/packages/binsoul/net-mqtt-client-react
+[link-downloads]: https://packagist.org/packages/binsoul/net-mqtt-client-react
+[link-author]: https://github.com/binsoul
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/composer.json b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/composer.json
new file mode 100755
index 0000000..e9dcc25
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/composer.json
@@ -0,0 +1,51 @@
+{
+ "name": "binsoul/net-mqtt-client-react",
+ "description": "Asynchronous MQTT client built on React",
+ "keywords": [
+ "net",
+ "mqtt",
+ "client"
+ ],
+ "homepage": "https://github.com/binsoul/net-mqtt-client-react",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": "~5.6|~7.0",
+ "binsoul/net-mqtt": "~0.2",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0||~5.0",
+ "friendsofphp/php-cs-fixer": "~1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\Client\\React\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "BinSoul\\Test\\Net\\Mqtt\\Client\\React\\": "tests"
+ }
+ },
+ "scripts": {
+ "test": "phpunit",
+ "fix-style": [
+ "php-cs-fixer fix src",
+ "php-cs-fixer fix tests"
+ ]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/src/ReactFlow.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/src/ReactFlow.php
new file mode 100755
index 0000000..d404145
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/src/ReactFlow.php
@@ -0,0 +1,112 @@
+decorated = $decorated;
+ $this->deferred = $deferred;
+ $this->packet = $packet;
+ $this->isSilent = $isSilent;
+ }
+
+ public function getCode()
+ {
+ return $this->decorated->getCode();
+ }
+
+ public function start()
+ {
+ $this->packet = $this->decorated->start();
+
+ return $this->packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $this->decorated->accept($packet);
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->packet = $this->decorated->next($packet);
+
+ return $this->packet;
+ }
+
+ public function isFinished()
+ {
+ return $this->decorated->isFinished();
+ }
+
+ public function isSuccess()
+ {
+ return $this->decorated->isSuccess();
+ }
+
+ public function getResult()
+ {
+ return $this->decorated->getResult();
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->decorated->getErrorMessage();
+ }
+
+ /**
+ * Returns the associated deferred.
+ *
+ * @return Deferred
+ */
+ public function getDeferred()
+ {
+ return $this->deferred;
+ }
+
+ /**
+ * Returns the current packet.
+ *
+ * @return Packet
+ */
+ public function getPacket()
+ {
+ return $this->packet;
+ }
+
+ /**
+ * Indicates if the flow should emit events.
+ *
+ * @return bool
+ */
+ public function isSilent()
+ {
+ return $this->isSilent;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/src/ReactMqttClient.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/src/ReactMqttClient.php
new file mode 100755
index 0000000..f56b191
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt-client-react/src/ReactMqttClient.php
@@ -0,0 +1,701 @@
+connector = $connector;
+ $this->loop = $loop;
+
+ $this->parser = $parser;
+ if ($this->parser === null) {
+ $this->parser = new StreamParser();
+ }
+
+ $this->parser->onError(function (\Exception $e) {
+ $this->emitWarning($e);
+ });
+
+ $this->identifierGenerator = $identifierGenerator;
+ if ($this->identifierGenerator === null) {
+ $this->identifierGenerator = new DefaultIdentifierGenerator();
+ }
+ }
+
+ /**
+ * Return the host.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Return the port.
+ *
+ * @return string
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Indicates if the client is connected.
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->isConnected;
+ }
+
+ /**
+ * Returns the underlying stream or null if the client is not connected.
+ *
+ * @return DuplexStreamInterface|null
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+
+ /**
+ * Connects to a broker.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function connect($host, $port = 1883, Connection $connection = null, $timeout = 5)
+ {
+ if ($this->isConnected || $this->isConnecting) {
+ return new RejectedPromise(new \LogicException('The client is already connected.'));
+ }
+
+ $this->isConnecting = true;
+ $this->isConnected = false;
+
+ $this->host = $host;
+ $this->port = $port;
+
+ if ($connection === null) {
+ $connection = new DefaultConnection();
+ }
+
+ if ($connection->isCleanSession()) {
+ $this->cleanPreviousSession();
+ }
+
+ if ($connection->getClientID() === '') {
+ $connection = $connection->withClientID($this->identifierGenerator->generateClientID());
+ }
+
+ $deferred = new Deferred();
+
+ $this->establishConnection($this->host, $this->port, $timeout)
+ ->then(function (DuplexStreamInterface $stream) use ($connection, $deferred, $timeout) {
+ $this->stream = $stream;
+
+ $this->emit('open', [$connection, $this]);
+
+ $this->registerClient($connection, $timeout)
+ ->then(function (Connection $connection) use ($deferred) {
+ $this->isConnecting = false;
+ $this->isConnected = true;
+ $this->connection = $connection;
+
+ $this->emit('connect', [$connection, $this]);
+ $deferred->resolve($this->connection);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred, $connection) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+
+ $this->emit('close', [$connection, $this]);
+ });
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Disconnects from a broker.
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function disconnect()
+ {
+ if (!$this->isConnected || $this->isDisconnecting) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $this->isDisconnecting = true;
+
+ $deferred = new Deferred();
+
+ $this->startFlow(new OutgoingDisconnectFlow($this->connection), true)
+ ->then(function (Connection $connection) use ($deferred) {
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+
+ $this->emit('disconnect', [$connection, $this]);
+ $deferred->resolve($connection);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+ })
+ ->otherwise(function () use ($deferred) {
+ $this->isDisconnecting = false;
+ $deferred->reject($this->connection);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Subscribes to a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function subscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingSubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Unsubscribes from a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function unsubscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingUnsubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Publishes a message.
+ *
+ * @param Message $message
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publish(Message $message)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingPublishFlow($message, $this->identifierGenerator));
+ }
+
+ /**
+ * Calls the given generator periodically and publishes the return value.
+ *
+ * @param int $interval
+ * @param Message $message
+ * @param callable $generator
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publishPeriodically($interval, Message $message, callable $generator)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $deferred = new Deferred();
+
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ $interval,
+ function () use ($message, $generator, $deferred) {
+ $this->publish($message->withPayload($generator($message->getTopic())))->then(
+ function ($value) use ($deferred) {
+ $deferred->notify($value);
+ },
+ function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ }
+ );
+ }
+ );
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Emits warnings.
+ *
+ * @param \Exception $e
+ */
+ private function emitWarning(\Exception $e)
+ {
+ $this->emit('warning', [$e, $this]);
+ }
+
+ /**
+ * Emits errors.
+ *
+ * @param \Exception $e
+ */
+ private function emitError(\Exception $e)
+ {
+ $this->emit('error', [$e, $this]);
+ }
+
+ /**
+ * Establishes a network connection to a server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function establishConnection($host, $port, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $timer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('Connection timed out after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->connector->connect($host.':'.$port)
+ ->always(function () use ($timer) {
+ $this->loop->cancelTimer($timer);
+ })
+ ->then(function (DuplexStreamInterface $stream) use ($deferred) {
+ $stream->on('data', function ($data) {
+ $this->handleReceive($data);
+ });
+
+ $stream->on('close', function () {
+ $this->handleClose();
+ });
+
+ $stream->on('error', function (\Exception $e) {
+ $this->handleError($e);
+ });
+
+ $deferred->resolve($stream);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Registers a new client with the broker.
+ *
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function registerClient(Connection $connection, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $responseTimer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('No response after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->startFlow(new OutgoingConnectFlow($connection, $this->identifierGenerator), true)
+ ->always(function () use ($responseTimer) {
+ $this->loop->cancelTimer($responseTimer);
+ })->then(function (Connection $connection) use ($deferred) {
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ floor($connection->getKeepAlive() * 0.75),
+ function () {
+ $this->startFlow(new OutgoingPingFlow());
+ }
+ );
+
+ $deferred->resolve($connection);
+ })->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Handles incoming data.
+ *
+ * @param string $data
+ */
+ private function handleReceive($data)
+ {
+ if (!$this->isConnected && !$this->isConnecting) {
+ return;
+ }
+
+ $flowCount = count($this->receivingFlows);
+
+ $packets = $this->parser->push($data);
+ foreach ($packets as $packet) {
+ $this->handlePacket($packet);
+ }
+
+ if ($flowCount > count($this->receivingFlows)) {
+ $this->receivingFlows = array_values($this->receivingFlows);
+ }
+
+ $this->handleSend();
+ }
+
+ /**
+ * Handles an incoming packet.
+ *
+ * @param Packet $packet
+ */
+ private function handlePacket(Packet $packet)
+ {
+ switch ($packet->getPacketType()) {
+ case Packet::TYPE_PUBLISH:
+ /* @var PublishRequestPacket $packet */
+ $message = new DefaultMessage(
+ $packet->getTopic(),
+ $packet->getPayload(),
+ $packet->getQosLevel(),
+ $packet->isRetained(),
+ $packet->isDuplicate()
+ );
+
+ $this->startFlow(new IncomingPublishFlow($message, $packet->getIdentifier()));
+ break;
+ case Packet::TYPE_CONNACK:
+ case Packet::TYPE_PINGRESP:
+ case Packet::TYPE_SUBACK:
+ case Packet::TYPE_UNSUBACK:
+ case Packet::TYPE_PUBREL:
+ case Packet::TYPE_PUBACK:
+ case Packet::TYPE_PUBREC:
+ case Packet::TYPE_PUBCOMP:
+ $flowFound = false;
+ foreach ($this->receivingFlows as $index => $flow) {
+ if ($flow->accept($packet)) {
+ $flowFound = true;
+
+ unset($this->receivingFlows[$index]);
+ $this->continueFlow($flow, $packet);
+
+ break;
+ }
+ }
+
+ if (!$flowFound) {
+ $this->emitWarning(
+ new \LogicException(sprintf('Received unexpected packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ break;
+ default:
+ $this->emitWarning(
+ new \LogicException(sprintf('Cannot handle packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ }
+
+ /**
+ * Handles outgoing packets.
+ */
+ private function handleSend()
+ {
+ $flow = null;
+ if ($this->writtenFlow !== null) {
+ $flow = $this->writtenFlow;
+ $this->writtenFlow = null;
+ }
+
+ if (count($this->sendingFlows) > 0) {
+ $this->writtenFlow = array_shift($this->sendingFlows);
+ $this->stream->write($this->writtenFlow->getPacket());
+ }
+
+ if ($flow !== null) {
+ if ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ } else {
+ $this->receivingFlows[] = $flow;
+ }
+ }
+ }
+
+ /**
+ * Handles closing of the stream.
+ */
+ private function handleClose()
+ {
+ foreach ($this->timer as $timer) {
+ $this->loop->cancelTimer($timer);
+ }
+
+ $this->timer = [];
+
+ $connection = $this->connection;
+
+ $this->isConnecting = false;
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+ $this->connection = null;
+ $this->stream = null;
+
+ if ($connection !== null) {
+ $this->emit('close', [$connection, $this]);
+ }
+ }
+
+ /**
+ * Handles errors of the stream.
+ *
+ * @param \Exception $e
+ */
+ private function handleError(\Exception $e)
+ {
+ $this->emitError($e);
+ }
+
+ /**
+ * Starts the given flow.
+ *
+ * @param Flow $flow
+ * @param bool $isSilent
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function startFlow(Flow $flow, $isSilent = false)
+ {
+ try {
+ $packet = $flow->start();
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return new RejectedPromise($e);
+ }
+
+ $deferred = new Deferred();
+ $internalFlow = new ReactFlow($flow, $deferred, $packet, $isSilent);
+
+ if ($packet !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $internalFlow;
+ } else {
+ $this->stream->write($packet);
+ $this->writtenFlow = $internalFlow;
+ $this->handleSend();
+ }
+ } else {
+ $this->loop->nextTick(function () use ($internalFlow) {
+ $this->finishFlow($internalFlow);
+ });
+ }
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Continues the given flow.
+ *
+ * @param ReactFlow $flow
+ * @param Packet $packet
+ */
+ private function continueFlow(ReactFlow $flow, Packet $packet)
+ {
+ try {
+ $response = $flow->next($packet);
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return;
+ }
+
+ if ($response !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $flow;
+ } else {
+ $this->stream->write($response);
+ $this->writtenFlow = $flow;
+ $this->handleSend();
+ }
+ } elseif ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ }
+ }
+
+ /**
+ * Finishes the given flow.
+ *
+ * @param ReactFlow $flow
+ */
+ private function finishFlow(ReactFlow $flow)
+ {
+ if ($flow->isSuccess()) {
+ if (!$flow->isSilent()) {
+ $this->emit($flow->getCode(), [$flow->getResult(), $this]);
+ }
+
+ $flow->getDeferred()->resolve($flow->getResult());
+ } else {
+ $result = new \RuntimeException($flow->getErrorMessage());
+ $this->emitWarning($result);
+
+ $flow->getDeferred()->reject($result);
+ }
+ }
+
+ /**
+ * Cleans previous session by rejecting all pending flows.
+ */
+ private function cleanPreviousSession()
+ {
+ $error = new \RuntimeException('Connection has been closed.');
+
+ foreach ($this->receivingFlows as $receivingFlow) {
+ $receivingFlow->getDeferred()->reject($error);
+ }
+
+ foreach ($this->sendingFlows as $sendingFlow) {
+ $sendingFlow->getDeferred()->reject($error);
+ }
+
+ $this->receivingFlows = [];
+ $this->sendingFlows = [];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/LICENSE.md b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/LICENSE.md
new file mode 100755
index 0000000..6d19c5c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/LICENSE.md
@@ -0,0 +1,21 @@
+# The MIT License (MIT)
+
+Copyright (c) 2015 Sebastian Mößler
+
+> Permission is hereby granted, free of charge, to any person obtaining a copy
+> of this software and associated documentation files (the "Software"), to deal
+> in the Software without restriction, including without limitation the rights
+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+> copies of the Software, and to permit persons to whom the Software is
+> furnished to do so, subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in
+> all copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+> THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/README.md b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/README.md
new file mode 100755
index 0000000..9f341da
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/README.md
@@ -0,0 +1,36 @@
+# net-mqtt
+
+[![Latest Version on Packagist][ico-version]][link-packagist]
+[![Software License][ico-license]](LICENSE.md)
+[![Total Downloads][ico-downloads]][link-downloads]
+
+MQTT is a machine-to-machine (M2M) / Internet of Things (IoT) connectivity protocol. It provides a lightweight method of carrying out messaging using a publish/subscribe model.
+
+This package implements the MQTT protocol versions 3.1 and 3.1.1.
+
+
+## Install
+
+Via composer:
+
+``` bash
+$ composer require binsoul/net-mqtt
+```
+
+## Testing
+
+``` bash
+$ composer test
+```
+
+## License
+
+The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
+
+[ico-version]: https://img.shields.io/packagist/v/binsoul/net-mqtt.svg?style=flat-square
+[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
+[ico-downloads]: https://img.shields.io/packagist/dt/binsoul/net-mqtt.svg?style=flat-square
+
+[link-packagist]: https://packagist.org/packages/binsoul/net-mqtt
+[link-downloads]: https://packagist.org/packages/binsoul/net-mqtt
+[link-author]: https://github.com/binsoul
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/composer.json b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/composer.json
new file mode 100755
index 0000000..035ad9f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/composer.json
@@ -0,0 +1,47 @@
+{
+ "name": "binsoul/net-mqtt",
+ "description": "MQTT protocol implementation",
+ "keywords": [
+ "net",
+ "mqtt"
+ ],
+ "homepage": "https://github.com/binsoul/net-mqtt",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": "~5.6|~7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0||~5.0",
+ "friendsofphp/php-cs-fixer": "~1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "BinSoul\\Test\\Net\\Mqtt\\": "tests"
+ }
+ },
+ "scripts": {
+ "test": "phpunit",
+ "fix-style": [
+ "php-cs-fixer fix src",
+ "php-cs-fixer fix tests"
+ ]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Connection.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Connection.php
new file mode 100755
index 0000000..5297b74
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Connection.php
@@ -0,0 +1,90 @@
+username = $username;
+ $this->password = $password;
+ $this->will = $will;
+ $this->clientID = $clientID;
+ $this->keepAlive = $keepAlive;
+ $this->protocol = $protocol;
+ $this->clean = $clean;
+ }
+
+ public function getProtocol()
+ {
+ return $this->protocol;
+ }
+
+ public function getClientID()
+ {
+ return $this->clientID;
+ }
+
+ public function isCleanSession()
+ {
+ return $this->clean;
+ }
+
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ public function getWill()
+ {
+ return $this->will;
+ }
+
+ public function getKeepAlive()
+ {
+ return $this->keepAlive;
+ }
+
+ public function withProtocol($protocol)
+ {
+ $result = clone $this;
+ $result->protocol = $protocol;
+
+ return $result;
+ }
+
+ public function withClientID($clientID)
+ {
+ $result = clone $this;
+ $result->clientID = $clientID;
+
+ return $result;
+ }
+
+ public function withCredentials($username, $password)
+ {
+ $result = clone $this;
+ $result->username = $username;
+ $result->password = $password;
+
+ return $result;
+ }
+
+ public function withWill(Message $will = null)
+ {
+ $result = clone $this;
+ $result->will = $will;
+
+ return $result;
+ }
+
+ public function withKeepAlive($timeout)
+ {
+ $result = clone $this;
+ $result->keepAlive = $timeout;
+
+ return $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/DefaultIdentifierGenerator.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/DefaultIdentifierGenerator.php
new file mode 100755
index 0000000..4c21211
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/DefaultIdentifierGenerator.php
@@ -0,0 +1,38 @@
+currentIdentifier;
+ if ($this->currentIdentifier > 0xFFFF) {
+ $this->currentIdentifier = 1;
+ }
+
+ return $this->currentIdentifier;
+ }
+
+ public function generateClientID()
+ {
+ if (function_exists('random_bytes')) {
+ $data = random_bytes(9);
+ } elseif (function_exists('openssl_random_pseudo_bytes')) {
+ $data = openssl_random_pseudo_bytes(9);
+ } else {
+ $data = '';
+ for ($i = 1; $i <= 8; ++$i) {
+ $data = chr(mt_rand(0, 255)).$data;
+ }
+ }
+
+ return 'BNMCR'.bin2hex($data);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/DefaultMessage.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/DefaultMessage.php
new file mode 100755
index 0000000..bc659e9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/DefaultMessage.php
@@ -0,0 +1,119 @@
+topic = $topic;
+ $this->payload = $payload;
+ $this->isRetained = $retain;
+ $this->qosLevel = $qosLevel;
+ $this->isDuplicate = $isDuplicate;
+ }
+
+ public function getTopic()
+ {
+ return $this->topic;
+ }
+
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ public function getQosLevel()
+ {
+ return $this->qosLevel;
+ }
+
+ public function isDuplicate()
+ {
+ return $this->isDuplicate;
+ }
+
+ public function isRetained()
+ {
+ return $this->isRetained;
+ }
+
+ public function withTopic($topic)
+ {
+ $result = clone $this;
+ $result->topic = $topic;
+
+ return $result;
+ }
+
+ public function withPayload($payload)
+ {
+ $result = clone $this;
+ $result->payload = $payload;
+
+ return $result;
+ }
+
+ public function withQosLevel($level)
+ {
+ $result = clone $this;
+ $result->qosLevel = $level;
+
+ return $result;
+ }
+
+ public function retain()
+ {
+ $result = clone $this;
+ $result->isRetained = true;
+
+ return $result;
+ }
+
+ public function release()
+ {
+ $result = clone $this;
+ $result->isRetained = false;
+
+ return $result;
+ }
+
+ public function duplicate()
+ {
+ $result = clone $this;
+ $result->isDuplicate = true;
+
+ return $result;
+ }
+
+ public function original()
+ {
+ $result = clone $this;
+ $result->isDuplicate = false;
+
+ return $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/DefaultSubscription.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/DefaultSubscription.php
new file mode 100755
index 0000000..2fa71ab
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/DefaultSubscription.php
@@ -0,0 +1,52 @@
+filter = $filter;
+ $this->qosLevel = $qosLevel;
+ }
+
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+
+ public function getQosLevel()
+ {
+ return $this->qosLevel;
+ }
+
+ public function withFilter($filter)
+ {
+ $result = clone $this;
+ $result->filter = $filter;
+
+ return $result;
+ }
+
+ public function withQosLevel($level)
+ {
+ $result = clone $this;
+ $result->qosLevel = $level;
+
+ return $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Exception/EndOfStreamException.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Exception/EndOfStreamException.php
new file mode 100755
index 0000000..3ffc71c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Exception/EndOfStreamException.php
@@ -0,0 +1,10 @@
+isFinished;
+ }
+
+ public function isSuccess()
+ {
+ return $this->isFinished && $this->isSuccess;
+ }
+
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Marks the flow as successful and sets the result.
+ *
+ * @param mixed|null $result
+ */
+ protected function succeed($result = null)
+ {
+ $this->isFinished = true;
+ $this->isSuccess = true;
+ $this->result = $result;
+ }
+
+ /**
+ * Marks the flow as failed and sets the error message.
+ *
+ * @param string $error
+ */
+ protected function fail($error = '')
+ {
+ $this->isFinished = true;
+ $this->isSuccess = false;
+ $this->error = $error;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/IncomingPingFlow.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/IncomingPingFlow.php
new file mode 100755
index 0000000..a19a1b0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/IncomingPingFlow.php
@@ -0,0 +1,23 @@
+succeed();
+
+ return new PingResponsePacket();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/IncomingPublishFlow.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/IncomingPublishFlow.php
new file mode 100755
index 0000000..b992c38
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/IncomingPublishFlow.php
@@ -0,0 +1,80 @@
+message = $message;
+ $this->identifier = $identifier;
+ }
+
+ public function getCode()
+ {
+ return 'message';
+ }
+
+ public function start()
+ {
+ $packet = null;
+ $emit = true;
+ if ($this->message->getQosLevel() === 1) {
+ $packet = new PublishAckPacket();
+ } elseif ($this->message->getQosLevel() === 2) {
+ $packet = new PublishReceivedPacket();
+ $emit = false;
+ }
+
+ if ($packet !== null) {
+ $packet->setIdentifier($this->identifier);
+ }
+
+ if ($emit) {
+ $this->succeed($this->message);
+ }
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ if ($this->message->getQosLevel() !== 2 || $packet->getPacketType() !== Packet::TYPE_PUBREL) {
+ return false;
+ }
+
+ /* @var PublishReleasePacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->succeed($this->message);
+
+ $response = new PublishCompletePacket();
+ $response->setIdentifier($this->identifier);
+
+ return $response;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingConnectFlow.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingConnectFlow.php
new file mode 100755
index 0000000..f439e79
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingConnectFlow.php
@@ -0,0 +1,70 @@
+connection = $connection;
+
+ if ($this->connection->getClientID() === '') {
+ $this->connection = $this->connection->withClientID($generator->generateClientID());
+ }
+ }
+
+ public function getCode()
+ {
+ return 'connect';
+ }
+
+ public function start()
+ {
+ $packet = new ConnectRequestPacket();
+ $packet->setProtocolLevel($this->connection->getProtocol());
+ $packet->setKeepAlive($this->connection->getKeepAlive());
+ $packet->setClientID($this->connection->getClientID());
+ $packet->setCleanSession($this->connection->isCleanSession());
+ $packet->setUsername($this->connection->getUsername());
+ $packet->setPassword($this->connection->getPassword());
+ $will = $this->connection->getWill();
+ if ($will !== null && $will->getTopic() !== '' && $will->getPayload() !== '') {
+ $packet->setWill($will->getTopic(), $will->getPayload(), $will->getQosLevel(), $will->isRetained());
+ }
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $packet->getPacketType() === Packet::TYPE_CONNACK;
+ }
+
+ public function next(Packet $packet)
+ {
+ /** @var ConnectResponsePacket $packet */
+ if ($packet->isSuccess()) {
+ $this->succeed($this->connection);
+ } else {
+ $this->fail($packet->getErrorName());
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingDisconnectFlow.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingDisconnectFlow.php
new file mode 100755
index 0000000..fe04c33
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingDisconnectFlow.php
@@ -0,0 +1,37 @@
+connection = $connection;
+ }
+
+ public function getCode()
+ {
+ return 'disconnect';
+ }
+
+ public function start()
+ {
+ $this->succeed($this->connection);
+
+ return new DisconnectRequestPacket();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingPingFlow.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingPingFlow.php
new file mode 100755
index 0000000..8cf79ab
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingPingFlow.php
@@ -0,0 +1,32 @@
+getPacketType() === Packet::TYPE_PINGRESP;
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->succeed();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingPublishFlow.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingPublishFlow.php
new file mode 100755
index 0000000..63fe264
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingPublishFlow.php
@@ -0,0 +1,102 @@
+message = $message;
+ if ($this->message->getQosLevel() > 0) {
+ $this->identifier = $generator->generatePacketID();
+ }
+ }
+
+ public function getCode()
+ {
+ return 'publish';
+ }
+
+ public function start()
+ {
+ $packet = new PublishRequestPacket();
+ $packet->setTopic($this->message->getTopic());
+ $packet->setPayload($this->message->getPayload());
+ $packet->setRetained($this->message->isRetained());
+ $packet->setDuplicate($this->message->isDuplicate());
+ $packet->setQosLevel($this->message->getQosLevel());
+
+ if ($this->message->getQosLevel() === 0) {
+ $this->succeed($this->message);
+ } else {
+ $packet->setIdentifier($this->identifier);
+ }
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ if ($this->message->getQosLevel() === 0) {
+ return false;
+ }
+
+ $packetType = $packet->getPacketType();
+
+ if ($packetType === Packet::TYPE_PUBACK && $this->message->getQosLevel() === 1) {
+ /* @var PublishAckPacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ } elseif ($this->message->getQosLevel() === 2) {
+ if ($packetType === Packet::TYPE_PUBREC) {
+ /* @var PublishReceivedPacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ } elseif ($this->receivedPubRec && $packetType === Packet::TYPE_PUBCOMP) {
+ /* @var PublishCompletePacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ }
+ }
+
+ return false;
+ }
+
+ public function next(Packet $packet)
+ {
+ $packetType = $packet->getPacketType();
+
+ if ($packetType === Packet::TYPE_PUBACK || $packetType === Packet::TYPE_PUBCOMP) {
+ $this->succeed($this->message);
+ } elseif ($packetType === Packet::TYPE_PUBREC) {
+ $this->receivedPubRec = true;
+
+ $response = new PublishReleasePacket();
+ $response->setIdentifier($this->identifier);
+
+ return $response;
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingSubscribeFlow.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingSubscribeFlow.php
new file mode 100755
index 0000000..4a25517
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingSubscribeFlow.php
@@ -0,0 +1,82 @@
+subscriptions = array_values($subscriptions);
+ $this->identifier = $generator->generatePacketID();
+ }
+
+ public function getCode()
+ {
+ return 'subscribe';
+ }
+
+ public function start()
+ {
+ $packet = new SubscribeRequestPacket();
+ $packet->setTopic($this->subscriptions[0]->getFilter());
+ $packet->setQosLevel($this->subscriptions[0]->getQosLevel());
+ $packet->setIdentifier($this->identifier);
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ if ($packet->getPacketType() !== Packet::TYPE_SUBACK) {
+ return false;
+ }
+
+ /* @var SubscribeResponsePacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ }
+
+ public function next(Packet $packet)
+ {
+ /* @var SubscribeResponsePacket $packet */
+ $returnCodes = $packet->getReturnCodes();
+ if (count($returnCodes) !== count($this->subscriptions)) {
+ throw new \LogicException(
+ sprintf(
+ 'SUBACK: Expected %d return codes but got %d.',
+ count($this->subscriptions),
+ count($returnCodes)
+ )
+ );
+ }
+
+ foreach ($returnCodes as $index => $code) {
+ if ($packet->isError($code)) {
+ $this->fail(sprintf('Failed to subscribe to "%s".', $this->subscriptions[$index]->getFilter()));
+
+ return;
+ }
+ }
+
+ $this->succeed($this->subscriptions[0]);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingUnsubscribeFlow.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingUnsubscribeFlow.php
new file mode 100755
index 0000000..4949ff3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Flow/OutgoingUnsubscribeFlow.php
@@ -0,0 +1,61 @@
+subscriptions = array_values($subscriptions);
+ $this->identifier = $generator->generatePacketID();
+ }
+
+ public function getCode()
+ {
+ return 'unsubscribe';
+ }
+
+ public function start()
+ {
+ $packet = new UnsubscribeRequestPacket();
+ $packet->setTopic($this->subscriptions[0]->getFilter());
+ $packet->setIdentifier($this->identifier);
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ if ($packet->getPacketType() !== Packet::TYPE_UNSUBACK) {
+ return false;
+ }
+
+ /* @var UnsubscribeResponsePacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->succeed($this->subscriptions[0]);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/IdentifierGenerator.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/IdentifierGenerator.php
new file mode 100755
index 0000000..94c7c78
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/IdentifierGenerator.php
@@ -0,0 +1,23 @@
+write($output);
+
+ return $output->getData();
+ }
+
+ public function read(PacketStream $stream)
+ {
+ $byte = $stream->readByte();
+
+ if ($byte >> 4 !== static::$packetType) {
+ throw new MalformedPacketException(
+ sprintf(
+ 'Expected packet type %02x but got %02x.',
+ $byte >> 4,
+ static::$packetType
+ )
+ );
+ }
+
+ $this->packetFlags = $byte & 0x0F;
+ $this->readRemainingLength($stream);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $stream->writeByte(((static::$packetType & 0x0F) << 4) + ($this->packetFlags & 0x0F));
+ $this->writeRemainingLength($stream);
+ }
+
+ /**
+ * Reads the remaining length from the given stream.
+ *
+ * @param PacketStream $stream
+ *
+ * @throws MalformedPacketException
+ */
+ private function readRemainingLength(PacketStream $stream)
+ {
+ $this->remainingPacketLength = 0;
+ $multiplier = 1;
+
+ do {
+ $encodedByte = $stream->readByte();
+
+ $this->remainingPacketLength += ($encodedByte & 127) * $multiplier;
+ $multiplier *= 128;
+
+ if ($multiplier > 128 * 128 * 128 * 128) {
+ throw new MalformedPacketException('Malformed remaining length.');
+ }
+ } while (($encodedByte & 128) !== 0);
+ }
+
+ /**
+ * Writes the remaining length to the given stream.
+ *
+ * @param PacketStream $stream
+ */
+ private function writeRemainingLength(PacketStream $stream)
+ {
+ $x = $this->remainingPacketLength;
+ do {
+ $encodedByte = $x % 128;
+ $x = (int) ($x / 128);
+ if ($x > 0) {
+ $encodedByte |= 128;
+ }
+
+ $stream->writeByte($encodedByte);
+ } while ($x > 0);
+ }
+
+ public function getPacketType()
+ {
+ return static::$packetType;
+ }
+
+ /**
+ * Returns the packet flags.
+ *
+ * @return int
+ */
+ public function getPacketFlags()
+ {
+ return $this->packetFlags;
+ }
+
+ /**
+ * Returns the remaining length.
+ *
+ * @return int
+ */
+ public function getRemainingPacketLength()
+ {
+ return $this->remainingPacketLength;
+ }
+
+ /**
+ * Asserts that the packet flags have a specific value.
+ *
+ * @param int $value
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertPacketFlags($value, $fromPacket = true)
+ {
+ if ($this->packetFlags !== $value) {
+ $this->throwException(
+ sprintf(
+ 'Expected flags %02x but got %02x.',
+ $value,
+ $this->packetFlags
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Asserts that the remaining length is greater than zero and has a specific value.
+ *
+ * @param int|null $value value to test or null if any value greater than zero is valid
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertRemainingPacketLength($value = null, $fromPacket = true)
+ {
+ if ($value === null && $this->remainingPacketLength === 0) {
+ $this->throwException('Expected payload but remaining packet length is zero.', $fromPacket);
+ }
+
+ if ($value !== null && $this->remainingPacketLength !== $value) {
+ $this->throwException(
+ sprintf(
+ 'Expected remaining packet length of %d bytes but got %d.',
+ $value,
+ $this->remainingPacketLength
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Asserts that the given string is a well-formed MQTT string.
+ *
+ * @param string $value
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertValidStringLength($value, $fromPacket = true)
+ {
+ if (strlen($value) > 0xFFFF) {
+ $this->throwException(
+ sprintf(
+ 'The string "%s" is longer than 65535 byte.',
+ substr($value, 0, 50)
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Asserts that the given string is a well-formed MQTT string.
+ *
+ * @param string $value
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertValidString($value, $fromPacket = true)
+ {
+ $this->assertValidStringLength($value, $fromPacket);
+
+ if (!mb_check_encoding($value, 'UTF-8')) {
+ $this->throwException(
+ sprintf(
+ 'The string "%s" is not well-formed UTF-8.',
+ substr($value, 0, 50)
+ ),
+ $fromPacket
+ );
+ }
+
+ if (preg_match('/[\xD8-\xDF][\x00-\xFF]|\x00\x00/x', $value)) {
+ $this->throwException(
+ sprintf(
+ 'The string "%s" contains invalid characters.',
+ substr($value, 0, 50)
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Asserts that the given quality of service level is valid.
+ *
+ * @param int $level
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertValidQosLevel($level, $fromPacket = true)
+ {
+ if ($level < 0 || $level > 2) {
+ $this->throwException(
+ sprintf(
+ 'Expected a quality of service level between 0 and 2 but got %d.',
+ $level
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Throws a MalformedPacketException for packet validation and an InvalidArgumentException otherwise.
+ *
+ * @param string $message
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function throwException($message, $fromPacket)
+ {
+ if ($fromPacket) {
+ throw new MalformedPacketException($message);
+ }
+
+ throw new \InvalidArgumentException($message);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/ConnectRequestPacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/ConnectRequestPacket.php
new file mode 100755
index 0000000..7abce1f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/ConnectRequestPacket.php
@@ -0,0 +1,405 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $this->protocolName = $stream->readString();
+ $this->protocolLevel = $stream->readByte();
+ $this->flags = $stream->readByte();
+ $this->keepAlive = $stream->readWord();
+ $this->clientID = $stream->readString();
+
+ if ($this->hasWill()) {
+ $this->willTopic = $stream->readString();
+ $this->willMessage = $stream->readString();
+ }
+
+ if ($this->hasUsername()) {
+ $this->username = $stream->readString();
+ }
+
+ if ($this->hasPassword()) {
+ $this->password = $stream->readString();
+ }
+
+ $this->assertValidWill();
+ $this->assertValidString($this->clientID);
+ $this->assertValidString($this->willTopic);
+ $this->assertValidString($this->username);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ if ($this->clientID === '') {
+ $this->clientID = 'BinSoul'.mt_rand(100000, 999999);
+ }
+
+ $data = new PacketStream();
+
+ $data->writeString($this->protocolName);
+ $data->writeByte($this->protocolLevel);
+ $data->writeByte($this->flags);
+ $data->writeWord($this->keepAlive);
+ $data->writeString($this->clientID);
+
+ if ($this->hasWill()) {
+ $data->writeString($this->willTopic);
+ $data->writeString($this->willMessage);
+ }
+
+ if ($this->hasUsername()) {
+ $data->writeString($this->username);
+ }
+
+ if ($this->hasPassword()) {
+ $data->writeString($this->password);
+ }
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the protocol level.
+ *
+ * @return int
+ */
+ public function getProtocolLevel()
+ {
+ return $this->protocolLevel;
+ }
+
+ /**
+ * Sets the protocol level.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setProtocolLevel($value)
+ {
+ if ($value < 3 || $value > 4) {
+ throw new \InvalidArgumentException(sprintf('Unknown protocol level %d.', $value));
+ }
+
+ $this->protocolLevel = $value;
+ if ($this->protocolLevel === 3) {
+ $this->protocolName = 'MQIsdp';
+ } elseif ($this->protocolLevel === 4) {
+ $this->protocolName = 'MQTT';
+ }
+ }
+
+ /**
+ * Returns the client id.
+ *
+ * @return string
+ */
+ public function getClientID()
+ {
+ return $this->clientID;
+ }
+
+ /**
+ * Sets the client id.
+ *
+ * @param string $value
+ */
+ public function setClientID($value)
+ {
+ $this->clientID = $value;
+ }
+
+ /**
+ * Returns the keep alive time in seconds.
+ *
+ * @return int
+ */
+ public function getKeepAlive()
+ {
+ return $this->keepAlive;
+ }
+
+ /**
+ * Sets the keep alive time in seconds.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setKeepAlive($value)
+ {
+ if ($value > 65535) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected a keep alive time lower than 65535 but got %d.',
+ $value
+ )
+ );
+ }
+
+ $this->keepAlive = $value;
+ }
+
+ /**
+ * Indicates if the clean session flag is set.
+ *
+ * @return bool
+ */
+ public function isCleanSession()
+ {
+ return ($this->flags & 2) === 2;
+ }
+
+ /**
+ * Changes the clean session flag.
+ *
+ * @param bool $value
+ */
+ public function setCleanSession($value)
+ {
+ if ($value) {
+ $this->flags |= 2;
+ } else {
+ $this->flags &= ~2;
+ }
+ }
+
+ /**
+ * Indicates if a will is set.
+ *
+ * @return bool
+ */
+ public function hasWill()
+ {
+ return ($this->flags & 4) === 4;
+ }
+
+ /**
+ * Returns the desired quality of service level of the will.
+ *
+ * @return int
+ */
+ public function getWillQosLevel()
+ {
+ return ($this->flags & 24) >> 3;
+ }
+
+ /**
+ * Indicates if the will should be retained.
+ *
+ * @return bool
+ */
+ public function isWillRetained()
+ {
+ return ($this->flags & 32) === 32;
+ }
+
+ /**
+ * Returns the will topic.
+ *
+ * @return string
+ */
+ public function getWillTopic()
+ {
+ return $this->willTopic;
+ }
+
+ /**
+ * Returns the will message.
+ *
+ * @return string
+ */
+ public function getWillMessage()
+ {
+ return $this->willMessage;
+ }
+
+ /**
+ * Sets the will.
+ *
+ * @param string $topic
+ * @param string $message
+ * @param int $qosLevel
+ * @param bool $isRetained
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setWill($topic, $message, $qosLevel = 0, $isRetained = false)
+ {
+ $this->assertValidString($topic, false);
+ if ($topic === '') {
+ throw new \InvalidArgumentException('The topic must not be empty.');
+ }
+
+ $this->assertValidStringLength($message, false);
+ if ($message === '') {
+ throw new \InvalidArgumentException('The message must not be empty.');
+ }
+
+ $this->assertValidQosLevel($qosLevel, false);
+
+ $this->willTopic = $topic;
+ $this->willMessage = $message;
+
+ $this->flags |= 4;
+ $this->flags |= ($qosLevel << 3);
+
+ if ($isRetained) {
+ $this->flags |= 32;
+ } else {
+ $this->flags &= ~32;
+ }
+ }
+
+ /**
+ * Removes the will.
+ */
+ public function removeWill()
+ {
+ $this->flags &= ~60;
+ $this->willTopic = '';
+ $this->willMessage = '';
+ }
+
+ /**
+ * Indicates if a username is set.
+ *
+ * @return bool
+ */
+ public function hasUsername()
+ {
+ return $this->flags & 64;
+ }
+
+ /**
+ * Returns the username.
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ /**
+ * Sets the username.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setUsername($value)
+ {
+ $this->assertValidString($value, false);
+
+ $this->username = $value;
+ if ($this->username !== '') {
+ $this->flags |= 64;
+ } else {
+ $this->flags &= ~64;
+ }
+ }
+
+ /**
+ * Indicates if a password is set.
+ *
+ * @return bool
+ */
+ public function hasPassword()
+ {
+ return $this->flags & 128;
+ }
+
+ /**
+ * Returns the password.
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Sets the password.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPassword($value)
+ {
+ $this->assertValidStringLength($value, false);
+
+ $this->password = $value;
+ if ($this->password !== '') {
+ $this->flags |= 128;
+ } else {
+ $this->flags &= ~128;
+ }
+ }
+
+ /**
+ * Asserts that all will flags and quality of service are correct.
+ *
+ * @throws MalformedPacketException
+ */
+ private function assertValidWill()
+ {
+ if ($this->hasWill()) {
+ $this->assertValidQosLevel($this->getWillQosLevel(), true);
+ } else {
+ if ($this->getWillQosLevel() > 0) {
+ $this->throwException(
+ sprintf(
+ 'Expected a will quality of service level of zero but got %d.',
+ $this->getWillQosLevel()
+ ),
+ true
+ );
+ }
+
+ if ($this->isWillRetained()) {
+ $this->throwException('There is not will but the will retain flag is set.', true);
+ }
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/ConnectResponsePacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/ConnectResponsePacket.php
new file mode 100755
index 0000000..f896d22
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/ConnectResponsePacket.php
@@ -0,0 +1,111 @@
+ [
+ 'Connection accepted',
+ '',
+ ],
+ 1 => [
+ 'Unacceptable protocol version',
+ 'The Server does not support the level of the MQTT protocol requested by the client.',
+ ],
+ 2 => [
+ 'Identifier rejected',
+ 'The client identifier is correct UTF-8 but not allowed by the server.',
+ ],
+ 3 => [
+ 'Server unavailable',
+ 'The network connection has been made but the MQTT service is unavailable',
+ ],
+ 4 => [
+ 'Bad user name or password',
+ 'The data in the user name or password is malformed.',
+ ],
+ 5 => [
+ 'Not authorized',
+ 'The client is not authorized to connect.',
+ ],
+ ];
+
+ /** @var int */
+ private $flags = 0;
+ /** @var int */
+ private $returnCode;
+
+ protected static $packetType = Packet::TYPE_CONNACK;
+ protected $remainingPacketLength = 2;
+
+ public function read(PacketStream $stream)
+ {
+ parent::read($stream);
+ $this->assertPacketFlags(0);
+ $this->assertRemainingPacketLength(2);
+
+ $this->flags = $stream->readByte();
+ $this->returnCode = $stream->readByte();
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $this->remainingPacketLength = 2;
+ parent::write($stream);
+
+ $stream->writeByte($this->flags);
+ $stream->writeByte($this->returnCode);
+ }
+
+ /**
+ * Returns the return code.
+ *
+ * @return int
+ */
+ public function getReturnCode()
+ {
+ return $this->returnCode;
+ }
+
+ /**
+ * Indicates if the connection was successful.
+ *
+ * @return bool
+ */
+ public function isSuccess()
+ {
+ return $this->returnCode === 0;
+ }
+
+ /**
+ * Indicates if the connection failed.
+ *
+ * @return bool
+ */
+ public function isError()
+ {
+ return $this->returnCode > 0;
+ }
+
+ /**
+ * Returns a string representation of the returned error code.
+ *
+ * @return int
+ */
+ public function getErrorName()
+ {
+ if (isset(self::$returnCodes[$this->returnCode])) {
+ return self::$returnCodes[$this->returnCode][0];
+ }
+
+ return 'Error '.$this->returnCode;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/DisconnectRequestPacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/DisconnectRequestPacket.php
new file mode 100755
index 0000000..0cda785
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/DisconnectRequestPacket.php
@@ -0,0 +1,22 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength(0);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/IdentifiablePacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/IdentifiablePacket.php
new file mode 100755
index 0000000..a44ae1c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/IdentifiablePacket.php
@@ -0,0 +1,62 @@
+identifier === null) {
+ ++self::$nextIdentifier;
+ self::$nextIdentifier &= 0xFFFF;
+
+ $this->identifier = self::$nextIdentifier;
+ }
+
+ return $this->identifier;
+ }
+
+ /**
+ * Returns the identifier.
+ *
+ * @return int|null
+ */
+ public function getIdentifier()
+ {
+ return $this->identifier;
+ }
+
+ /**
+ * Sets the identifier.
+ *
+ * @param int|null $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setIdentifier($value)
+ {
+ if ($value !== null && ($value < 0 || $value > 0xFFFF)) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected an identifier between 0x0000 and 0xFFFF but got %x',
+ $value
+ )
+ );
+ }
+
+ $this->identifier = $value;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/IdentifierOnlyPacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/IdentifierOnlyPacket.php
new file mode 100755
index 0000000..01dffa5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/IdentifierOnlyPacket.php
@@ -0,0 +1,42 @@
+assertPacketFlags($this->getExpectedPacketFlags());
+ $this->assertRemainingPacketLength(2);
+
+ $this->identifier = $stream->readWord();
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $this->remainingPacketLength = 2;
+ parent::write($stream);
+
+ $stream->writeWord($this->generateIdentifier());
+ }
+
+ /**
+ * Returns the expected packet flags.
+ *
+ * @return int
+ */
+ protected function getExpectedPacketFlags()
+ {
+ return 0;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/PingRequestPacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/PingRequestPacket.php
new file mode 100755
index 0000000..cb0fc58
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/PingRequestPacket.php
@@ -0,0 +1,22 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength(0);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/PingResponsePacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/PingResponsePacket.php
new file mode 100755
index 0000000..79eaeb3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/PingResponsePacket.php
@@ -0,0 +1,22 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength(0);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/PublishAckPacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/PublishAckPacket.php
new file mode 100755
index 0000000..fc4937e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/PublishAckPacket.php
@@ -0,0 +1,13 @@
+assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+ $this->topic = $stream->readString();
+ $this->identifier = null;
+ if ($this->getQosLevel() > 0) {
+ $this->identifier = $stream->readWord();
+ }
+
+ $payloadLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
+ $this->payload = $stream->read($payloadLength);
+
+ $this->assertValidQosLevel($this->getQosLevel());
+ $this->assertValidString($this->topic);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeString($this->topic);
+ if ($this->getQosLevel() > 0) {
+ $data->writeWord($this->generateIdentifier());
+ }
+
+ $data->write($this->payload);
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the topic.
+ *
+ * @return string
+ */
+ public function getTopic()
+ {
+ return $this->topic;
+ }
+
+ /**
+ * Sets the topic.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setTopic($value)
+ {
+ $this->assertValidString($value, false);
+ if ($value === '') {
+ throw new \InvalidArgumentException('The topic must not be empty.');
+ }
+
+ $this->topic = $value;
+ }
+
+ /**
+ * Returns the payload.
+ *
+ * @return string
+ */
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ /**
+ * Sets the payload.
+ *
+ * @param string $value
+ */
+ public function setPayload($value)
+ {
+ $this->payload = $value;
+ }
+
+ /**
+ * Indicates if the packet is a duplicate.
+ *
+ * @return bool
+ */
+ public function isDuplicate()
+ {
+ return ($this->packetFlags & 8) === 8;
+ }
+
+ /**
+ * Marks the packet as duplicate.
+ *
+ * @param bool $value
+ */
+ public function setDuplicate($value)
+ {
+ if ($value) {
+ $this->packetFlags |= 8;
+ } else {
+ $this->packetFlags &= ~8;
+ }
+ }
+
+ /**
+ * Indicates if the packet is retained.
+ *
+ * @return bool
+ */
+ public function isRetained()
+ {
+ return ($this->packetFlags & 1) === 1;
+ }
+
+ /**
+ * Marks the packet as retained.
+ *
+ * @param bool $value
+ */
+ public function setRetained($value)
+ {
+ if ($value) {
+ $this->packetFlags |= 1;
+ } else {
+ $this->packetFlags &= ~1;
+ }
+ }
+
+ /**
+ * Returns the quality of service level.
+ *
+ * @return int
+ */
+ public function getQosLevel()
+ {
+ return ($this->packetFlags & 6) >> 1;
+ }
+
+ /**
+ * Sets the quality of service level.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setQosLevel($value)
+ {
+ $this->assertValidQosLevel($value, false);
+
+ $this->packetFlags |= ($value & 3) << 1;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/StrictConnectRequestPacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/StrictConnectRequestPacket.php
new file mode 100755
index 0000000..71e8c78
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/StrictConnectRequestPacket.php
@@ -0,0 +1,66 @@
+assertValidClientID($this->clientID, true);
+ }
+
+ /**
+ * Sets the client id.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setClientID($value)
+ {
+ $this->assertValidClientID($value, false);
+
+ $this->clientID = $value;
+ }
+
+ /**
+ * Asserts that a client id is shorter than 24 bytes and only contains characters 0-9, a-z or A-Z.
+ *
+ * @param string $value
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ private function assertValidClientID($value, $fromPacket)
+ {
+
+ if (strlen($value) > 23) {
+ $this->throwException(
+ sprintf(
+ 'Expected client id shorter than 24 bytes but got "%s".',
+ $value
+ ),
+ $fromPacket
+ );
+ }
+
+ if ($value !== '' && !ctype_alnum($value)) {
+ $this->throwException(
+ sprintf(
+ 'Expected a client id containing characters 0-9, a-z or A-Z but got "%s".',
+ $value
+ ),
+ $fromPacket
+ );
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/SubscribeRequestPacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/SubscribeRequestPacket.php
new file mode 100755
index 0000000..55aa831
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/SubscribeRequestPacket.php
@@ -0,0 +1,101 @@
+assertPacketFlags(2);
+ $this->assertRemainingPacketLength();
+
+ $this->identifier = $stream->readWord();
+ $this->topic = $stream->readString();
+ $this->qosLevel = $stream->readByte();
+
+ $this->assertValidQosLevel($this->qosLevel);
+ $this->assertValidString($this->topic);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeWord($this->generateIdentifier());
+ $data->writeString($this->topic);
+ $data->writeByte($this->qosLevel);
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the topic.
+ *
+ * @return string
+ */
+ public function getTopic()
+ {
+ return $this->topic;
+ }
+
+ /**
+ * Sets the topic.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setTopic($value)
+ {
+ $this->assertValidString($value, false);
+ if ($value === '') {
+ throw new \InvalidArgumentException('The topic must not be empty.');
+ }
+
+ $this->topic = $value;
+ }
+
+ /**
+ * Returns the quality of service level.
+ *
+ * @return int
+ */
+ public function getQosLevel()
+ {
+ return $this->qosLevel;
+ }
+
+ /**
+ * Sets the quality of service level.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setQosLevel($value)
+ {
+ $this->assertValidQosLevel($value, false);
+
+ $this->qosLevel = $value;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/SubscribeResponsePacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/SubscribeResponsePacket.php
new file mode 100755
index 0000000..35c78e4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/SubscribeResponsePacket.php
@@ -0,0 +1,132 @@
+ ['Maximum QoS 0'],
+ 1 => ['Maximum QoS 1'],
+ 2 => ['Maximum QoS 2'],
+ 128 => ['Failure'],
+ ];
+
+ /** @var int[] */
+ private $returnCodes;
+
+ protected static $packetType = Packet::TYPE_SUBACK;
+
+ public function read(PacketStream $stream)
+ {
+ parent::read($stream);
+ $this->assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $this->identifier = $stream->readWord();
+
+ $returnCodeLength = $this->remainingPacketLength - 2;
+ for ($n = 0; $n < $returnCodeLength; ++$n) {
+ $returnCode = $stream->readByte();
+ $this->assertValidReturnCode($returnCode);
+
+ $this->returnCodes[] = $returnCode;
+ }
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeWord($this->generateIdentifier());
+ foreach ($this->returnCodes as $returnCode) {
+ $data->writeByte($returnCode);
+ }
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Indicates if the given return code is an error.
+ *
+ * @param int $returnCode
+ *
+ * @return bool
+ */
+ public function isError($returnCode)
+ {
+ return $returnCode === 128;
+ }
+
+ /**
+ * Indicates if the given return code is an error.
+ *
+ * @param int $returnCode
+ *
+ * @return bool
+ */
+ public function getReturnCodeName($returnCode)
+ {
+ if (isset(self::$qosLevels[$returnCode])) {
+ return self::$qosLevels[$returnCode][0];
+ }
+
+ return 'Unknown '.$returnCode;
+ }
+
+ /**
+ * Returns the return codes.
+ *
+ * @return int[]
+ */
+ public function getReturnCodes()
+ {
+ return $this->returnCodes;
+ }
+
+ /**
+ * Sets the return codes.
+ *
+ * @param int[] $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setReturnCodes(array $value)
+ {
+ foreach ($value as $returnCode) {
+ $this->assertValidReturnCode($returnCode, false);
+ }
+
+ $this->returnCodes = $value;
+ }
+
+ /**
+ * Asserts that a return code is valid.
+ *
+ * @param int $returnCode
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ private function assertValidReturnCode($returnCode, $fromPacket = true)
+ {
+ if (!in_array($returnCode, [0, 1, 2, 128])) {
+ $this->throwException(
+ sprintf('Malformed return code %02x.', $returnCode),
+ $fromPacket
+ );
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeRequestPacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeRequestPacket.php
new file mode 100755
index 0000000..4bce38c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeRequestPacket.php
@@ -0,0 +1,68 @@
+assertPacketFlags(2);
+ $this->assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+
+ do {
+ $this->identifier = $stream->readWord();
+ $this->topic = $stream->readString();
+ } while (($stream->getPosition() - $originalPosition) <= $this->remainingPacketLength);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeWord($this->generateIdentifier());
+ $data->writeString($this->topic);
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the topic.
+ *
+ * @return string
+ */
+ public function getTopic()
+ {
+ return $this->topic;
+ }
+
+ /**
+ * Sets the topic.
+ *
+ * @param string $value
+ */
+ public function setTopic($value)
+ {
+ $this->topic = $value;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeResponsePacket.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeResponsePacket.php
new file mode 100755
index 0000000..385b91d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeResponsePacket.php
@@ -0,0 +1,13 @@
+ ConnectRequestPacket::class,
+ Packet::TYPE_CONNACK => ConnectResponsePacket::class,
+ Packet::TYPE_PUBLISH => PublishRequestPacket::class,
+ Packet::TYPE_PUBACK => PublishAckPacket::class,
+ Packet::TYPE_PUBREC => PublishReceivedPacket::class,
+ Packet::TYPE_PUBREL => PublishReleasePacket::class,
+ Packet::TYPE_PUBCOMP => PublishCompletePacket::class,
+ Packet::TYPE_SUBSCRIBE => SubscribeRequestPacket::class,
+ Packet::TYPE_SUBACK => SubscribeResponsePacket::class,
+ Packet::TYPE_UNSUBSCRIBE => UnsubscribeRequestPacket::class,
+ Packet::TYPE_UNSUBACK => UnsubscribeResponsePacket::class,
+ Packet::TYPE_PINGREQ => PingRequestPacket::class,
+ Packet::TYPE_PINGRESP => PingResponsePacket::class,
+ Packet::TYPE_DISCONNECT => DisconnectRequestPacket::class,
+ ];
+
+ /**
+ * Builds a packet object for the given type.
+ *
+ * @param int $type
+ *
+ * @throws UnknownPacketTypeException
+ *
+ * @return Packet
+ */
+ public function build($type)
+ {
+ if (!isset(self::$mapping[$type])) {
+ throw new UnknownPacketTypeException(sprintf('Unknown packet type %d.', $type));
+ }
+
+ $class = self::$mapping[$type];
+
+ return new $class();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/PacketStream.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/PacketStream.php
new file mode 100755
index 0000000..eb241d6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/PacketStream.php
@@ -0,0 +1,216 @@
+data = $data;
+ $this->position = 0;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Returns the desired number of bytes.
+ *
+ * @param int $count
+ *
+ * @throws EndOfStreamException
+ *
+ * @return string
+ */
+ public function read($count)
+ {
+ $contentLength = strlen($this->data);
+ if ($this->position > $contentLength || $count > $contentLength - $this->position) {
+ throw new EndOfStreamException(
+ sprintf(
+ 'End of stream reached when trying to read %d bytes. content length=%d, position=%d',
+ $count,
+ $contentLength,
+ $this->position
+ )
+ );
+ }
+
+ $chunk = substr($this->data, $this->position, $count);
+ if ($chunk === false) {
+ $chunk = '';
+ }
+
+ $readBytes = strlen($chunk);
+ $this->position += $readBytes;
+
+ return $chunk;
+ }
+
+ /**
+ * Returns a single byte.
+ *
+ * @return int
+ */
+ public function readByte()
+ {
+ return ord($this->read(1));
+ }
+
+ /**
+ * Returns a single word.
+ *
+ * @return int
+ */
+ public function readWord()
+ {
+ return ($this->readByte() << 8) + $this->readByte();
+ }
+
+ /**
+ * Returns a length prefixed string.
+ *
+ * @return string
+ */
+ public function readString()
+ {
+ $length = $this->readWord();
+
+ return $this->read($length);
+ }
+
+ /**
+ * Appends the given value.
+ *
+ * @param string $value
+ */
+ public function write($value)
+ {
+ $this->data .= $value;
+ }
+
+ /**
+ * Appends a single byte.
+ *
+ * @param int $value
+ */
+ public function writeByte($value)
+ {
+ $this->write(chr($value));
+ }
+
+ /**
+ * Appends a single word.
+ *
+ * @param int $value
+ */
+ public function writeWord($value)
+ {
+ $this->write(chr(($value & 0xFFFF) >> 8));
+ $this->write(chr($value & 0xFF));
+ }
+
+ /**
+ * Appends a length prefixed string.
+ *
+ * @param string $string
+ */
+ public function writeString($string)
+ {
+ $this->writeWord(strlen($string));
+ $this->write($string);
+ }
+
+ /**
+ * Returns the length of the stream.
+ *
+ * @return int
+ */
+ public function length()
+ {
+ return strlen($this->data);
+ }
+
+ /**
+ * Returns the number of bytes until the end of the stream.
+ *
+ * @return int
+ */
+ public function getRemainingBytes()
+ {
+ return $this->length() - $this->position;
+ }
+
+ /**
+ * Returns the whole content of the stream.
+ *
+ * @return string
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Changes the internal position of the stream relative to the current position.
+ *
+ * @param int $offset
+ */
+ public function seek($offset)
+ {
+ $this->position += $offset;
+ }
+
+ /**
+ * Returns the internal position of the stream.
+ *
+ * @return int
+ */
+ public function getPosition()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Sets the internal position of the stream.
+ *
+ * @param int $value
+ */
+ public function setPosition($value)
+ {
+ $this->position = $value;
+ }
+
+ /**
+ * Removes all bytes from the beginning to the current position.
+ */
+ public function cut()
+ {
+ $this->data = substr($this->data, $this->position);
+ if ($this->data === false) {
+ $this->data = '';
+ }
+
+ $this->position = 0;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/StreamParser.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/StreamParser.php
new file mode 100755
index 0000000..398f3e6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/StreamParser.php
@@ -0,0 +1,90 @@
+buffer = new PacketStream();
+ $this->factory = new PacketFactory();
+ }
+
+ /**
+ * Registers an error callback.
+ *
+ * @param callable $callback
+ */
+ public function onError($callback)
+ {
+ $this->errorCallback = $callback;
+ }
+
+ /**
+ * Appends the given data to the internal buffer and parses it.
+ *
+ * @param string $data
+ *
+ * @return Packet[]
+ */
+ public function push($data)
+ {
+ $this->buffer->write($data);
+
+ $result = [];
+ while ($this->buffer->getRemainingBytes() > 0) {
+ $type = $this->buffer->readByte() >> 4;
+ try {
+ $packet = $this->factory->build($type);
+ } catch (UnknownPacketTypeException $e) {
+ $this->handleError($e);
+ continue;
+ }
+
+ $this->buffer->seek(-1);
+ $position = $this->buffer->getPosition();
+ try {
+ $packet->read($this->buffer);
+ $result[] = $packet;
+ $this->buffer->cut();
+ } catch (EndOfStreamException $e) {
+ $this->buffer->setPosition($position);
+ break;
+ } catch (MalformedPacketException $e) {
+ $this->handleError($e);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the registered error callback.
+ *
+ * @param \Throwable $exception
+ */
+ private function handleError($exception)
+ {
+ if ($this->errorCallback !== null) {
+ $callback = $this->errorCallback;
+ $callback($exception);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Subscription.php b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Subscription.php
new file mode 100755
index 0000000..b870533
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/binsoul/net-mqtt/src/Subscription.php
@@ -0,0 +1,41 @@
+
+ */
+class TopicMatcher
+{
+ /**
+ * Check if the given topic matches the filter.
+ *
+ * @param string $filter e.g. A/B/+, A/B/#
+ * @param string $topic e.g. A/B/C, A/B/foo/bar/baz
+ *
+ * @return bool true if topic matches the pattern
+ */
+ public function matches($filter, $topic)
+ {
+ // Created by Steffen (https://github.com/kernelguy)
+ $tokens = explode('/', $filter);
+ $parts = [];
+ for ($i = 0, $count = count($tokens); $i < $count; ++$i) {
+ $token = $tokens[$i];
+ switch ($token) {
+ case '+':
+ $parts[] = '[^/#\+]*';
+
+ break;
+ case '#':
+ if ($i === 0) {
+ $parts[] = '[^\+\$]*';
+ } else {
+ $parts[] = '[^\+]*';
+ }
+
+ break;
+ default:
+ $parts[] = str_replace('+', '\+', $token);
+
+ break;
+ }
+ }
+
+ $regex = implode('/', $parts);
+ $regex = str_replace('$', '\$', $regex);
+ $regex = ';^'.$regex.'$;';
+
+ return preg_match($regex, $topic) === 1;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/.gitignore b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/.gitignore
new file mode 100755
index 0000000..4fbb073
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/.gitignore
@@ -0,0 +1,2 @@
+/vendor/
+/composer.lock
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/.travis.yml b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/.travis.yml
new file mode 100755
index 0000000..04f51ad
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/.travis.yml
@@ -0,0 +1,27 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/CHANGELOG.md b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/CHANGELOG.md
new file mode 100755
index 0000000..3d25812
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/CHANGELOG.md
@@ -0,0 +1,103 @@
+# Changelog
+
+## 1.3.0 (2018-02-13)
+
+* Feature: Support communication over Unix domain sockets (UDS)
+ (#20 by @clue)
+
+ ```php
+ // new: now supports communication over Unix domain sockets (UDS)
+ $proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
+ ```
+
+* Reduce memory consumption by avoiding circular reference from stream reader
+ (#18 by @valga)
+
+* Improve documentation
+ (#19 by @clue)
+
+## 1.2.0 (2017-08-30)
+
+* Feature: Use socket error codes for connection rejections
+ (#17 by @clue)
+
+ ```php
+ $promise = $proxy->connect('imap.example.com:143');
+ $promise->then(null, function (Exeption $e) {
+ if ($e->getCode() === SOCKET_EACCES) {
+ echo 'Failed to authenticate with proxy!';
+ }
+ throw $e;
+ });
+ ```
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ optionally exclude tests that rely on working internet connection
+ (#15 and #16 by @clue)
+
+## 1.1.0 (2017-06-11)
+
+* Feature: Support proxy authentication if proxy URL contains username/password
+ (#14 by @clue)
+
+ ```php
+ // new: username/password will now be passed to HTTP proxy server
+ $proxy = new ProxyConnector('user:pass@127.0.0.1:8080', $connector);
+ ```
+
+## 1.0.0 (2017-06-10)
+
+* First stable release, now following SemVer
+
+> Contains no other changes, so it's actually fully compatible with the v0.3.2 release.
+
+## 0.3.2 (2017-06-10)
+
+* Fix: Fix rejecting invalid URIs and unexpected URI schemes
+ (#13 by @clue)
+
+* Fix HHVM build for now again and ignore future HHVM build errors
+ (#12 by @clue)
+
+* Documentation for Connector concepts (TCP/TLS, timeouts, DNS resolution)
+ (#11 by @clue)
+
+## 0.3.1 (2017-05-10)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#10 by @clue)
+
+## 0.3.0 (2017-04-10)
+
+* Feature / BC break: Replace deprecated SocketClient with new Socket component
+ (#9 by @clue)
+
+ This implies that the `ProxyConnector` from this package now implements the
+ `React\Socket\ConnectorInterface` instead of the legacy
+ `React\SocketClient\ConnectorInterface`.
+
+## 0.2.0 (2017-04-10)
+
+* Feature / BC break: Update SocketClient to v0.7 or v0.6 and
+ use `connect($uri)` instead of `create($host, $port)`
+ (#8 by @clue)
+
+ ```php
+ // old
+ $connector->create($host, $port)->then(function (Stream $conn) {
+ $conn->write("…");
+ });
+
+ // new
+ $connector->connect($uri)->then(function (ConnectionInterface $conn) {
+ $conn->write("…");
+ });
+ ```
+
+* Improve test suite by adding PHPUnit to require-dev
+ (#7 by @clue)
+
+
+## 0.1.0 (2016-11-01)
+
+* First tagged release
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/LICENSE b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/LICENSE
new file mode 100755
index 0000000..7baae8e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/README.md b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/README.md
new file mode 100755
index 0000000..442b7f9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/README.md
@@ -0,0 +1,422 @@
+# clue/http-proxy-react [](https://travis-ci.org/clue/php-http-proxy-react)
+
+Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP
+CONNECT proxy server, built on top of [ReactPHP](https://reactphp.org).
+
+HTTP CONNECT proxy servers (also commonly known as "HTTPS proxy" or "SSL proxy")
+are commonly used to tunnel HTTPS traffic through an intermediary ("proxy"), to
+conceal the origin address (anonymity) or to circumvent address blocking
+(geoblocking). While many (public) HTTP CONNECT proxy servers often limit this
+to HTTPS port `443` only, this can technically be used to tunnel any
+TCP/IP-based protocol (HTTP, SMTP, IMAP etc.).
+This library provides a simple API to create these tunneled connection for you.
+Because it implements ReactPHP's standard
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
+it can simply be used in place of a normal connector.
+This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
+existing higher-level protocol implementation.
+
+* **Async execution of connections** -
+ Send any number of HTTP CONNECT requests in parallel and process their
+ responses as soon as results come in.
+ The Promise-based design provides a *sane* interface to working with out of
+ bound responses and possible connection errors.
+* **Standard interfaces** -
+ Allows easy integration with existing higher-level components by implementing
+ ReactPHP's standard
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
+* **Lightweight, SOLID design** -
+ Provides a thin abstraction that is [*just good enough*](http://en.wikipedia.org/wiki/Principle_of_good_enough)
+ and does not get in your way.
+ Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
+* **Good test coverage** -
+ Comes with an automated tests suite and is regularly tested against actual proxy servers in the wild
+
+**Table of contents**
+
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [ProxyConnector](#proxyconnector)
+ * [Plain TCP connections](#plain-tcp-connections)
+ * [Secure TLS connections](#secure-tls-connections)
+ * [Connection timeout](#connection-timeout)
+ * [DNS resolution](#dns-resolution)
+ * [Authentication](#authentication)
+ * [Advanced secure proxy connections](#advanced-secure-proxy-connections)
+ * [Advanced Unix domain sockets](#advanced-unix-domain-sockets)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+### Quickstart example
+
+The following example code demonstrates how this library can be used to send a
+secure HTTPS request to google.com through a local HTTP proxy server:
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$proxy = new ProxyConnector('127.0.0.1:8080', new Connector($loop));
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
+ $stream->on('data', function ($chunk) {
+ echo $chunk;
+ });
+}, 'printf');
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### ProxyConnector
+
+The `ProxyConnector` is responsible for creating plain TCP/IP connections to
+any destination by using an intermediary HTTP CONNECT proxy.
+
+```
+[you] -> [proxy] -> [destination]
+```
+
+Its constructor simply accepts an HTTP proxy URL and a connector used to connect
+to the proxy server address:
+
+```php
+$connector = new Connector($loop);
+$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
+```
+
+The proxy URL may or may not contain a scheme and port definition. The default
+port will be `80` for HTTP (or `443` for HTTPS), but many common HTTP proxy
+servers use custom ports (often the alternative HTTP port `8080`).
+In its most simple form, the given connector will be a
+[`\React\Socket\Connector`](https://github.com/reactphp/socket#connector) if you
+want to connect to a given IP address as above.
+
+This is the main class in this package.
+Because it implements ReactPHP's standard
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
+it can simply be used in place of a normal connector.
+Accordingly, it provides only a single public method, the
+[`connect()`](https://github.com/reactphp/socket#connect) method.
+The `connect(string $uri): PromiseInterface`
+method can be used to establish a streaming connection.
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface)
+on success or rejects with an `Exception` on error.
+
+This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
+higher-level component:
+
+```diff
+- $client = new SomeClient($connector);
++ $proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
++ $client = new SomeClient($proxy);
+```
+
+#### Plain TCP connections
+
+HTTP CONNECT proxies are most frequently used to issue HTTPS requests to your destination.
+However, this is actually performed on a higher protocol layer and this
+connector is actually inherently a general-purpose plain TCP/IP connector.
+As documented above, you can simply invoke its `connect()` method to establish
+a streaming plain TCP/IP connection and use any higher level protocol like so:
+
+```php
+$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
+
+$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ });
+});
+```
+
+You can either use the `ProxyConnector` directly or you may want to wrap this connector
+in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
+
+```php
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ });
+});
+```
+
+Note that HTTP CONNECT proxies often restrict which ports one may connect to.
+Many (public) proxy servers do in fact limit this to HTTPS (443) only.
+
+#### Secure TLS connections
+
+This class can also be used if you want to establish a secure TLS connection
+(formerly known as SSL) between you and your destination, such as when using
+secure HTTPS to your destination site. You can simply wrap this connector in
+ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector) or the
+low-level [`SecureConnector`](https://github.com/reactphp/socket#secureconnector):
+
+```php
+$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ });
+});
+```
+
+> Note how secure TLS connections are in fact entirely handled outside of
+ this HTTP CONNECT client implementation.
+
+#### Connection timeout
+
+By default, the `ProxyConnector` does not implement any timeouts for establishing remote
+connections.
+Your underlying operating system may impose limits on pending and/or idle TCP/IP
+connections, anywhere in a range of a few minutes to several hours.
+
+Many use cases require more control over the timeout and likely values much
+smaller, usually in the range of a few seconds only.
+
+You can use ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector)
+or the low-level
+[`TimeoutConnector`](https://github.com/reactphp/socket#timeoutconnector)
+to decorate any given `ConnectorInterface` instance.
+It provides the same `connect()` method, but will automatically reject the
+underlying connection attempt if it takes too long:
+
+```php
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => false,
+ 'timeout' => 3.0
+));
+
+$connector->connect('tcp://google.com:80')->then(function ($stream) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+> Note how connection timeout is in fact entirely handled outside of this
+ HTTP CONNECT client implementation.
+
+#### DNS resolution
+
+By default, the `ProxyConnector` does not perform any DNS resolution at all and simply
+forwards any hostname you're trying to connect to the remote proxy server.
+The remote proxy server is thus responsible for looking up any hostnames via DNS
+(this default mode is thus called *remote DNS resolution*).
+
+As an alternative, you can also send the destination IP to the remote proxy
+server.
+In this mode you either have to stick to using IPs only (which is ofen unfeasable)
+or perform any DNS lookups locally and only transmit the resolved destination IPs
+(this mode is thus called *local DNS resolution*).
+
+The default *remote DNS resolution* is useful if your local `ProxyConnector` either can
+not resolve target hostnames because it has no direct access to the internet or
+if it should not resolve target hostnames because its outgoing DNS traffic might
+be intercepted.
+
+As noted above, the `ProxyConnector` defaults to using remote DNS resolution.
+However, wrapping the `ProxyConnector` in ReactPHP's
+[`Connector`](https://github.com/reactphp/socket#connector) actually
+performs local DNS resolution unless explicitly defined otherwise.
+Given that remote DNS resolution is assumed to be the preferred mode, all
+other examples explicitly disable DNS resoltion like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+```
+
+If you want to explicitly use *local DNS resolution*, you can use the following code:
+
+```php
+// set up Connector which uses Google's public DNS (8.8.8.8)
+$connector = Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => '8.8.8.8'
+));
+```
+
+> Note how local DNS resolution is in fact entirely handled outside of this
+ HTTP CONNECT client implementation.
+
+#### Authentication
+
+If your HTTP proxy server requires authentication, you may pass the username and
+password as part of the HTTP proxy URL like this:
+
+```php
+$proxy = new ProxyConnector('http://user:pass@127.0.0.1:8080', $connector);
+```
+
+Note that both the username and password must be percent-encoded if they contain
+special characters:
+
+```php
+$user = 'he:llo';
+$pass = 'p@ss';
+
+$proxy = new ProxyConnector(
+ rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:8080',
+ $connector
+);
+```
+
+> The authentication details will be used for basic authentication and will be
+ transferred in the `Proxy-Authorization` HTTP request header for each
+ connection attempt.
+ If the authentication details are missing or not accepted by the remote HTTP
+ proxy server, it is expected to reject each connection attempt with a
+ `407` (Proxy Authentication Required) response status code and an exception
+ error code of `SOCKET_EACCES` (13).
+
+#### Advanced secure proxy connections
+
+Note that communication between the client and the proxy is usually via an
+unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
+setup, because you can still establish a TLS connection between you and the
+destination host as above.
+
+If you want to connect to a (rather rare) HTTPS proxy, you may want use the
+`https://` scheme (HTTPS default port 443) and use ReactPHP's
+[`Connector`](https://github.com/reactphp/socket#connector) or the low-level
+[`SecureConnector`](https://github.com/reactphp/socket#secureconnector)
+instance to create a secure connection to the proxy:
+
+```php
+$connector = new Connector($loop);
+$proxy = new ProxyConnector('https://127.0.0.1:443', $connector);
+
+$proxy->connect('tcp://smtp.googlemail.com:587');
+```
+
+#### Advanced Unix domain sockets
+
+HTTP CONNECT proxy servers support forwarding TCP/IP based connections and
+higher level protocols.
+In some advanced cases, it may be useful to let your HTTP CONNECT proxy server
+listen on a Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#authentication).
+
+You can simply use the `http+unix://` URI scheme like this:
+
+```php
+$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
+
+$proxy->connect('tcp://google.com:80')->then(function (ConnectionInterface $stream) {
+ // connected…
+});
+```
+
+Similarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $connector);
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
+ has limited support for this.
+ In particular, enabling [secure TLS](#secure-tls-connections) may not be
+ supported.
+
+> Note that the HTTP CONNECT protocol does not support the notion of UDS paths.
+ The above works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does not support connecting to UDS destination paths.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](http://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/http-proxy-react:^1.3
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite contains tests that rely on a working internet connection,
+alternatively you can also run it like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT
+
+## More
+
+* If you want to learn more about how the
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ and its usual implementations look like, refer to the documentation of the underlying
+ [react/socket](https://github.com/reactphp/socket) component.
+* If you want to learn more about processing streams of data, refer to the
+ documentation of the underlying
+ [react/stream](https://github.com/reactphp/stream) component.
+* As an alternative to an HTTP CONNECT proxy, you may also want to look into
+ using a SOCKS (SOCKS4/SOCKS5) proxy instead.
+ You may want to use [clue/socks-react](https://github.com/clue/php-socks-react)
+ which also provides an implementation of the same
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ so that supporting either proxy protocol should be fairly trivial.
+* If you're dealing with public proxies, you'll likely have to work with mixed
+ quality and unreliable proxies. You may want to look into using
+ [clue/connection-manager-extra](https://github.com/clue/php-connection-manager-extra)
+ which allows retrying unreliable ones, implying connection timeouts,
+ concurrently working with multiple connectors and more.
+* If you're looking for an end-user HTTP CONNECT proxy server daemon, you may
+ want to use [LeProxy](https://leproxy.org/).
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/composer.json b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/composer.json
new file mode 100755
index 0000000..23801c9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "clue/http-proxy-react",
+ "description": "Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
+ "keywords": ["HTTP", "CONNECT", "proxy", "ReactPHP", "async"],
+ "homepage": "https://github.com/clue/php-http-proxy-react",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "Clue\\React\\HttpProxy\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Tests\\Clue\\React\\HttpProxy\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/promise": " ^2.1 || ^1.2.1",
+ "react/socket": "^1.0 || ^0.8.4",
+ "ringcentral/psr7": "^1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.0 || ^4.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
+ "clue/block-react": "^1.1"
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/01-proxy-https.php b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/01-proxy-https.php
new file mode 100755
index 0000000..c07ea0d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/01-proxy-https.php
@@ -0,0 +1,30 @@
+ $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
+ $stream->on('data', function ($chunk) {
+ echo $chunk;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php
new file mode 100755
index 0000000..c65e69a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php
@@ -0,0 +1,37 @@
+ $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+ ));
+}
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
+ $stream->on('data', function ($chunk) {
+ echo $chunk;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/11-proxy-smtp.php b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/11-proxy-smtp.php
new file mode 100755
index 0000000..3225491
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/11-proxy-smtp.php
@@ -0,0 +1,32 @@
+ $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ $stream->write("QUIT\r\n");
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/12-proxy-smtps.php b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/12-proxy-smtps.php
new file mode 100755
index 0000000..462dba3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/examples/12-proxy-smtps.php
@@ -0,0 +1,35 @@
+ $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ $stream->write("QUIT\r\n");
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/phpunit.xml.dist
new file mode 100755
index 0000000..a6e2430
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/src/ProxyConnector.php b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/src/ProxyConnector.php
new file mode 100755
index 0000000..69eb5ee
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/src/ProxyConnector.php
@@ -0,0 +1,213 @@
+ [proxy] -> [destination]
+ *
+ * This is most frequently used to issue HTTPS requests to your destination.
+ * However, this is actually performed on a higher protocol layer and this
+ * connector is actually inherently a general-purpose plain TCP/IP connector.
+ *
+ * Note that HTTP CONNECT proxies often restrict which ports one may connect to.
+ * Many (public) proxy servers do in fact limit this to HTTPS (443) only.
+ *
+ * If you want to establish a TLS connection (such as HTTPS) between you and
+ * your destination, you may want to wrap this connector in a SecureConnector
+ * instance.
+ *
+ * Note that communication between the client and the proxy is usually via an
+ * unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
+ * setup, because you can still establish a TLS connection between you and the
+ * destination host as above.
+ *
+ * If you want to connect to a (rather rare) HTTPS proxy, you may want use its
+ * HTTPS port (443) and use a SecureConnector instance to create a secure
+ * connection to the proxy.
+ *
+ * @link https://tools.ietf.org/html/rfc7231#section-4.3.6
+ */
+class ProxyConnector implements ConnectorInterface
+{
+ private $connector;
+ private $proxyUri;
+ private $proxyAuth = '';
+
+ /**
+ * Instantiate a new ProxyConnector which uses the given $proxyUrl
+ *
+ * @param string $proxyUrl The proxy URL may or may not contain a scheme and
+ * port definition. The default port will be `80` for HTTP (or `443` for
+ * HTTPS), but many common HTTP proxy servers use custom ports.
+ * @param ConnectorInterface $connector In its most simple form, the given
+ * connector will be a \React\Socket\Connector if you want to connect to
+ * a given IP address.
+ * @throws InvalidArgumentException if the proxy URL is invalid
+ */
+ public function __construct($proxyUrl, ConnectorInterface $connector)
+ {
+ // support `http+unix://` scheme for Unix domain socket (UDS) paths
+ if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) {
+ // rewrite URI to parse authentication from dummy host
+ $proxyUrl = 'http://' . $match[1] . 'localhost';
+
+ // connector uses Unix transport scheme and explicit path given
+ $connector = new FixedUriConnector(
+ 'unix://' . $match[2],
+ $connector
+ );
+ }
+
+ if (strpos($proxyUrl, '://') === false) {
+ $proxyUrl = 'http://' . $proxyUrl;
+ }
+
+ $parts = parse_url($proxyUrl);
+ if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) {
+ throw new InvalidArgumentException('Invalid proxy URL "' . $proxyUrl . '"');
+ }
+
+ // apply default port and TCP/TLS transport for given scheme
+ if (!isset($parts['port'])) {
+ $parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
+ }
+ $parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp';
+
+ $this->connector = $connector;
+ $this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'];
+
+ // prepare Proxy-Authorization header if URI contains username/password
+ if (isset($parts['user']) || isset($parts['pass'])) {
+ $this->proxyAuth = 'Proxy-Authorization: Basic ' . base64_encode(
+ rawurldecode($parts['user'] . ':' . (isset($parts['pass']) ? $parts['pass'] : ''))
+ ) . "\r\n";
+ }
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $port = $parts['port'];
+
+ // construct URI to HTTP CONNECT proxy server to connect to
+ $proxyUri = $this->proxyUri;
+
+ // append path from URI if given
+ if (isset($parts['path'])) {
+ $proxyUri .= $parts['path'];
+ }
+
+ // parse query args
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // append hostname from URI to query string unless explicitly given
+ if (!isset($args['hostname'])) {
+ $args['hostname'] = $parts['host'];
+ }
+
+ // append query string
+ $proxyUri .= '?' . http_build_query($args, '', '&');;
+
+ // append fragment from URI if given
+ if (isset($parts['fragment'])) {
+ $proxyUri .= '#' . $parts['fragment'];
+ }
+
+ $auth = $this->proxyAuth;
+
+ return $this->connector->connect($proxyUri)->then(function (ConnectionInterface $stream) use ($host, $port, $auth) {
+ $deferred = new Deferred(function ($_, $reject) use ($stream) {
+ $reject(new RuntimeException('Connection canceled while waiting for response from proxy (ECONNABORTED)', defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103));
+ $stream->close();
+ });
+
+ // keep buffering data until headers are complete
+ $buffer = '';
+ $fn = function ($chunk) use (&$buffer, $deferred, $stream) {
+ $buffer .= $chunk;
+
+ $pos = strpos($buffer, "\r\n\r\n");
+ if ($pos !== false) {
+ // try to parse headers as response message
+ try {
+ $response = Psr7\parse_response(substr($buffer, 0, $pos));
+ } catch (Exception $e) {
+ $deferred->reject(new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $e));
+ $stream->close();
+ return;
+ }
+
+ if ($response->getStatusCode() === 407) {
+ // map status code 407 (Proxy Authentication Required) to EACCES
+ $deferred->reject(new RuntimeException('Proxy denied connection due to invalid authentication ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13));
+ return $stream->close();
+ } elseif ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
+ // map non-2xx status code to ECONNREFUSED
+ $deferred->reject(new RuntimeException('Proxy refused connection with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111));
+ return $stream->close();
+ }
+
+ // all okay, resolve with stream instance
+ $deferred->resolve($stream);
+
+ // emit remaining incoming as data event
+ $buffer = (string)substr($buffer, $pos + 4);
+ if ($buffer !== '') {
+ $stream->emit('data', array($buffer));
+ $buffer = '';
+ }
+ return;
+ }
+
+ // stop buffering when 8 KiB have been read
+ if (isset($buffer[8192])) {
+ $deferred->reject(new RuntimeException('Proxy must not send more than 8 KiB of headers (EMSGSIZE)', defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90));
+ $stream->close();
+ }
+ };
+ $stream->on('data', $fn);
+
+ $stream->on('error', function (Exception $e) use ($deferred) {
+ $deferred->reject(new RuntimeException('Stream error while waiting for response from proxy (EIO)', defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e));
+ });
+
+ $stream->on('close', function () use ($deferred) {
+ $deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
+ });
+
+ $stream->write("CONNECT " . $host . ":" . $port . " HTTP/1.1\r\nHost: " . $host . ":" . $port . "\r\n" . $auth . "\r\n");
+
+ return $deferred->promise()->then(function (ConnectionInterface $stream) use ($fn) {
+ // Stop buffering when connection has been established.
+ $stream->removeListener('data', $fn);
+ return new Promise\FulfilledPromise($stream);
+ });
+ }, function (Exception $e) use ($proxyUri) {
+ throw new RuntimeException('Unable to connect to proxy (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $e);
+ });
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/tests/AbstractTestCase.php b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/tests/AbstractTestCase.php
new file mode 100755
index 0000000..632b314
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/tests/AbstractTestCase.php
@@ -0,0 +1,80 @@
+createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->equalTo($value));
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWithExceptionCode($code)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->callback(function ($e) use ($code) {
+ return $e->getCode() === $code;
+ }));
+
+ return $mock;
+ }
+
+
+ protected function expectCallableOnceParameter($type)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf($type));
+
+ return $mock;
+ }
+
+ /**
+ * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
+ */
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('Tests\\Clue\\React\\HttpProxy\\CallableStub')->getMock();
+ }
+}
+
+class CallableStub
+{
+ public function __invoke()
+ {
+ }
+}
+
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/tests/FunctionalTest.php b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/tests/FunctionalTest.php
new file mode 100755
index 0000000..23273cf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/tests/FunctionalTest.php
@@ -0,0 +1,75 @@
+loop = Factory::create();
+
+ $this->tcpConnector = new TcpConnector($this->loop);
+
+ $f = new \React\Dns\Resolver\Factory();
+ $resolver = $f->create('8.8.8.8', $this->loop);
+
+ $this->dnsConnector = new DnsConnector($this->tcpConnector, $resolver);
+ }
+
+ public function testNonListeningSocketRejectsConnection()
+ {
+ $proxy = new ProxyConnector('127.0.0.1:9999', $this->dnsConnector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->setExpectedException('RuntimeException', 'Unable to connect to proxy', SOCKET_ECONNREFUSED);
+ Block\await($promise, $this->loop, 3.0);
+ }
+
+ public function testPlainGoogleDoesNotAcceptConnectMethod()
+ {
+ $proxy = new ProxyConnector('google.com', $this->dnsConnector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->setExpectedException('RuntimeException', '405 (Method Not Allowed)', SOCKET_ECONNREFUSED);
+ Block\await($promise, $this->loop, 3.0);
+ }
+
+ public function testSecureGoogleDoesNotAcceptConnectMethod()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('TLS not supported on really old platforms (HHVM < 3.8)');
+ }
+
+ $secure = new SecureConnector($this->dnsConnector, $this->loop);
+ $proxy = new ProxyConnector('https://google.com:443', $secure);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->setExpectedException('RuntimeException', '405 (Method Not Allowed)', SOCKET_ECONNREFUSED);
+ Block\await($promise, $this->loop, 3.0);
+ }
+
+ public function testSecureGoogleDoesNotAcceptPlainStream()
+ {
+ $proxy = new ProxyConnector('google.com:443', $this->dnsConnector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->setExpectedException('RuntimeException', 'Connection to proxy lost', SOCKET_ECONNRESET);
+ Block\await($promise, $this->loop, 3.0);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php
new file mode 100755
index 0000000..13d6e42
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php
@@ -0,0 +1,333 @@
+connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidProxy()
+ {
+ new ProxyConnector('///', $this->connector);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidProxyScheme()
+ {
+ new ProxyConnector('ftp://example.com', $this->connector);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidHttpsUnixScheme()
+ {
+ new ProxyConnector('https+unix:///tmp/proxy.sock', $this->connector);
+ }
+
+ public function testCreatesConnectionToHttpPort()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=google.com')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testCreatesConnectionToHttpPortAndPassesThroughUriComponents()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80/path?foo=bar&hostname=google.com#segment')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80/path?foo=bar#segment');
+ }
+
+ public function testCreatesConnectionToHttpPortAndObeysExplicitHostname()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=www.google.com')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80?hostname=www.google.com');
+ }
+
+ public function testCreatesConnectionToHttpsPort()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('tls://proxy.example.com:443?hostname=google.com')->willReturn($promise);
+
+ $proxy = new ProxyConnector('https://proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testCreatesConnectionToUnixPath()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
+
+ $proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testCancelPromiseWillCancelPendingConnection()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->assertInstanceOf('React\Promise\CancellablePromiseInterface', $promise);
+
+ $promise->cancel();
+ }
+
+ public function testWillWriteToOpenConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testWillProxyAuthorizationHeaderIfProxyUriContainsAuthentication()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('user:pass@proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testWillProxyAuthorizationHeaderIfProxyUriContainsOnlyUsernameWithoutPassword()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjo=\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('user@proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testWillProxyAuthorizationHeaderIfProxyUriContainsAuthenticationWithPercentEncoding()
+ {
+ $user = 'h@llÖ';
+ $pass = '%secret?';
+
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic " . base64_encode($user . ':' . $pass) . "\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector(rawurlencode($user) . ':' . rawurlencode($pass) . '@proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testWillProxyAuthorizationHeaderIfUnixProxyUriContainsAuthentication()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
+
+ $proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testRejectsInvalidUri()
+ {
+ $this->connector->expects($this->never())->method('connect');
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('///');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testRejectsUriWithNonTcpScheme()
+ {
+ $this->connector->expects($this->never())->method('connect');
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('tls://google.com:80');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testRejectsIfConnectorRejects()
+ {
+ $promise = \React\Promise\reject(new \RuntimeException());
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testRejectsAndClosesIfStreamWritesNonHttp()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $stream->expects($this->once())->method('close');
+ $stream->emit('data', array("invalid\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testRejectsAndClosesIfStreamWritesTooMuchData()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $stream->expects($this->once())->method('close');
+ $stream->emit('data', array(str_repeat('*', 100000)));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EMSGSIZE));
+ }
+
+ public function testRejectsAndClosesIfStreamReturnsProyAuthenticationRequired()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $stream->expects($this->once())->method('close');
+ $stream->emit('data', array("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EACCES));
+ }
+
+ public function testRejectsAndClosesIfStreamReturnsNonSuccess()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $stream->expects($this->once())->method('close');
+ $stream->emit('data', array("HTTP/1.1 403 Not allowed\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
+ }
+
+ public function testResolvesIfStreamReturnsSuccess()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $promise->then($this->expectCallableOnce('React\Stream\Stream'));
+ $never = $this->expectCallableNever();
+ $promise->then(function (ConnectionInterface $stream) use ($never) {
+ $stream->on('data', $never);
+ });
+
+ $stream->emit('data', array("HTTP/1.1 200 OK\r\n\r\n"));
+ }
+
+ public function testResolvesIfStreamReturnsSuccessAndEmitsExcessiveData()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $once = $this->expectCallableOnceWith('hello!');
+ $promise->then(function (ConnectionInterface $stream) use ($once) {
+ $stream->on('data', $once);
+ });
+
+ $stream->emit('data', array("HTTP/1.1 200 OK\r\n\r\nhello!"));
+ }
+
+ public function testCancelPromiseWillCloseOpenConnectionAndReject()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->assertInstanceOf('React\Promise\CancellablePromiseInterface', $promise);
+
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNABORTED));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/.gitignore b/src/mpg25-instagram-api/vendor/clue/socks-react/.gitignore
new file mode 100755
index 0000000..de4a392
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/.gitignore
@@ -0,0 +1,2 @@
+/vendor
+/composer.lock
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/.travis.yml b/src/mpg25-instagram-api/vendor/clue/socks-react/.travis.yml
new file mode 100755
index 0000000..04f51ad
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/.travis.yml
@@ -0,0 +1,27 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/CHANGELOG.md b/src/mpg25-instagram-api/vendor/clue/socks-react/CHANGELOG.md
new file mode 100755
index 0000000..c1eddb3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/CHANGELOG.md
@@ -0,0 +1,338 @@
+# Changelog
+
+## 0.8.7 (2017-12-17)
+
+* Feature: Support SOCKS over TLS (`sockss://` URI scheme)
+ (#70 and #71 by @clue)
+
+ ```php
+ // new: now supports SOCKS over TLS
+ $client = new Client('socks5s://localhost', $connector);
+ ```
+
+* Feature: Support communication over Unix domain sockets (UDS)
+ (#69 by @clue)
+
+ ```php
+ // new: now supports SOCKS over Unix domain sockets (UDS)
+ $client = new Client('socks5+unix:///tmp/proxy.sock', $connector);
+ ```
+
+* Improve test suite by adding forward compatibility with PHPUnit 6
+ (#68 by @clue)
+
+## 0.8.6 (2017-09-17)
+
+* Feature: Forward compatibility with Evenement v3.0
+ (#67 by @WyriHaximus)
+
+## 0.8.5 (2017-09-01)
+
+* Feature: Use socket error codes for connection rejections
+ (#63 by @clue)
+
+ ```php
+ $promise = $proxy->connect('imap.example.com:143');
+ $promise->then(null, function (Exeption $e) {
+ if ($e->getCode() === SOCKET_EACCES) {
+ echo 'Failed to authenticate with proxy!';
+ }
+ throw $e;
+ });
+ ```
+
+* Feature: Report matching SOCKS5 error codes for server side connection errors
+ (#62 by @clue)
+
+* Fix: Fix SOCKS5 client receiving destination hostnames and
+ fix IPv6 addresses as hostnames for TLS certificates
+ (#64 and #65 by @clue)
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ optionally exclude tests that rely on working internet connection
+ (#61 and #66 by @clue)
+
+## 0.8.4 (2017-07-27)
+
+* Feature: Server now passes client source address to Connector
+ (#60 by @clue)
+
+## 0.8.3 (2017-07-18)
+
+* Feature: Pass full remote URI as parameter to authentication callback
+ (#58 by @clue)
+
+ ```php
+ // new third parameter passed to authentication callback
+ $server->setAuth(function ($user, $pass, $remote) {
+ $ip = parse_url($remote, PHP_URL_HOST);
+
+ return ($ip === '127.0.0.1');
+ });
+ ```
+
+* Fix: Fix connecting to IPv6 address via SOCKS5 server and validate target
+ URI so hostname can not contain excessive URI components
+ (#59 by @clue)
+
+* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors
+ (#57 by @clue)
+
+## 0.8.2 (2017-05-09)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#56 by @clue)
+
+## 0.8.1 (2017-04-21)
+
+* Update examples to use URIs with default port 1080 and accept proxy URI arguments
+ (#54 by @clue)
+
+* Remove now unneeded dependency on `react/stream`
+ (#55 by @clue)
+
+## 0.8.0 (2017-04-18)
+
+* Feature: Merge `Server` class from clue/socks-server
+ (#52 by @clue)
+
+ ```php
+ $socket = new React\Socket\Server(1080, $loop);
+ $server = new Clue\React\Socks\Server($loop, $socket);
+ ```
+
+ > Upgrading from [clue/socks-server](https://github.com/clue/php-socks-server)?
+ The classes have been moved as-is, so you can simply start using the new
+ class name `Clue\React\Socks\Server` with no other changes required.
+
+## 0.7.0 (2017-04-14)
+
+* Feature / BC break: Replace depreacted SocketClient with Socket v0.7 and
+ use `connect($uri)` instead of `create($host, $port)`
+ (#51 by @clue)
+
+ ```php
+ // old
+ $connector = new React\SocketClient\TcpConnector($loop);
+ $client = new Client(1080, $connector);
+ $client->create('google.com', 80)->then(function (Stream $conn) {
+ $conn->write("…");
+ });
+
+ // new
+ $connector = new React\Socket\TcpConnector($loop);
+ $client = new Client(1080, $connector);
+ $client->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $conn->write("…");
+ });
+ ```
+
+* Improve test suite by adding PHPUnit to require-dev
+ (#50 by @clue)
+
+## 0.6.0 (2016-11-29)
+
+* Feature / BC break: Pass connector into `Client` instead of loop, remove unneeded deps
+ (#49 by @clue)
+
+ ```php
+ // old (connector is create implicitly)
+ $client = new Client('127.0.0.1', $loop);
+
+ // old (connector can optionally be passed)
+ $client = new Client('127.0.0.1', $loop, $connector);
+
+ // new (connector is now mandatory)
+ $connector = new React\SocketClient\TcpConnector($loop);
+ $client = new Client('127.0.0.1', $connector);
+ ```
+
+* Feature / BC break: `Client` now implements `ConnectorInterface`, remove `Connector` adapter
+ (#47 by @clue)
+
+ ```php
+ // old (explicit connector functions as an adapter)
+ $connector = $client->createConnector();
+ $promise = $connector->create('google.com', 80);
+
+ // new (client can be used as connector right away)
+ $promise = $client->create('google.com', 80);
+ ```
+
+* Feature / BC break: Remove `createSecureConnector()`, use `SecureConnector` instead
+ (#47 by @clue)
+
+ ```php
+ // old (tight coupling and hidden dependency)
+ $tls = $client->createSecureConnector();
+ $promise = $tls->create('google.com', 443);
+
+ // new (more explicit, loose coupling)
+ $tls = new React\SocketClient\SecureConnector($client, $loop);
+ $promise = $tls->create('google.com', 443);
+ ```
+
+* Feature / BC break: Remove `setResolveLocal()` and local DNS resolution and default to remote DNS resolution, use `DnsConnector` instead
+ (#44 by @clue)
+
+ ```php
+ // old (implicitly defaults to true, can be disabled)
+ $client->setResolveLocal(false);
+ $tcp = $client->createConnector();
+ $promise = $tcp->create('google.com', 80);
+
+ // new (always disabled, can be re-enabled like this)
+ $factory = new React\Dns\Resolver\Factory();
+ $resolver = $factory->createCached('8.8.8.8', $loop);
+ $tcp = new React\SocketClient\DnsConnector($client, $resolver);
+ $promise = $tcp->create('google.com', 80);
+ ```
+
+* Feature / BC break: Remove `setTimeout()`, use `TimeoutConnector` instead
+ (#45 by @clue)
+
+ ```php
+ // old (timeout only applies to TCP/IP connection)
+ $client = new Client('127.0.0.1', …);
+ $client->setTimeout(3.0);
+ $tcp = $client->createConnector();
+ $promise = $tcp->create('google.com', 80);
+
+ // new (timeout can be added to any layer)
+ $client = new Client('127.0.0.1', …);
+ $tcp = new React\SocketClient\TimeoutConnector($client, 3.0, $loop);
+ $promise = $tcp->create('google.com', 80);
+ ```
+
+* Feature / BC break: Remove `setProtocolVersion()` and `setAuth()` mutators, only support SOCKS URI for protocol version and authentication (immutable API)
+ (#46 by @clue)
+
+ ```php
+ // old (state can be mutated after instantiation)
+ $client = new Client('127.0.0.1', …);
+ $client->setProtocolVersion('5');
+ $client->setAuth('user', 'pass');
+
+ // new (immutable after construction, already supported as of v0.5.2 - now mandatory)
+ $client = new Client('socks5://user:pass@127.0.0.1', …);
+ ```
+
+## 0.5.2 (2016-11-25)
+
+* Feature: Apply protocol version and username/password auth from SOCKS URI
+ (#43 by @clue)
+
+ ```php
+ // explicitly use SOCKS5
+ $client = new Client('socks5://127.0.0.1', $loop);
+
+ // use authentication (automatically SOCKS5)
+ $client = new Client('user:pass@127.0.0.1', $loop);
+ ```
+
+* More explicit client examples, including proxy chaining
+ (#42 by @clue)
+
+## 0.5.1 (2016-11-21)
+
+* Feature: Support Promise cancellation
+ (#39 by @clue)
+
+ ```php
+ $promise = $connector->create($host, $port);
+
+ $promise->cancel();
+ ```
+
+* Feature: Timeout now cancels pending connection attempt
+ (#39, #22 by @clue)
+
+## 0.5.0 (2016-11-07)
+
+* Remove / BC break: Split off Server to clue/socks-server
+ (#35 by @clue)
+
+ > Upgrading? Check [clue/socks-server](https://github.com/clue/php-socks-server) for details.
+
+* Improve documentation and project structure
+
+## 0.4.0 (2016-03-19)
+
+* Feature: Support proper SSL/TLS connections with additional SSL context options
+ (#31, #33 by @clue)
+
+* Documentation for advanced Connector setups (bindto, multihop)
+ (#32 by @clue)
+
+## 0.3.0 (2015-06-20)
+
+* BC break / Feature: Client ctor now accepts a SOCKS server URI
+ ([#24](https://github.com/clue/php-socks-react/pull/24))
+
+ ```php
+// old
+$client = new Client($loop, 'localhost', 9050);
+
+// new
+$client = new Client('localhost:9050', $loop);
+```
+
+* Feature: Automatically assume default SOCKS port (1080) if not given explicitly
+ ([#26](https://github.com/clue/php-socks-react/pull/26))
+
+* Improve documentation and test suite
+
+## 0.2.1 (2014-11-13)
+
+* Support React PHP v0.4 (while preserving BC with React PHP v0.3)
+ ([#16](https://github.com/clue/php-socks-react/pull/16))
+
+* Improve examples and add first class support for HHVM
+ ([#15](https://github.com/clue/php-socks-react/pull/15) and [#17](https://github.com/clue/php-socks-react/pull/17))
+
+## 0.2.0 (2014-09-27)
+
+* BC break / Feature: Simplify constructors by making parameters optional.
+ ([#10](https://github.com/clue/php-socks-react/pull/10))
+
+ The `Factory` has been removed, you can now create instances of the `Client`
+ and `Server` yourself:
+
+ ```php
+ // old
+ $factory = new Factory($loop, $dns);
+ $client = $factory->createClient('localhost', 9050);
+ $server = $factory->createSever($socket);
+
+ // new
+ $client = new Client($loop, 'localhost', 9050);
+ $server = new Server($loop, $socket);
+ ```
+
+* BC break: Remove HTTP support and link to [clue/buzz-react](https://github.com/clue/php-buzz-react) instead.
+ ([#9](https://github.com/clue/php-socks-react/pull/9))
+
+ HTTP operates on a different layer than this low-level SOCKS library.
+ Removing this reduces the footprint of this library.
+
+ > Upgrading? Check the [README](https://github.com/clue/php-socks-react#http-requests) for details.
+
+* Fix: Refactored to support other, faster loops (libev/libevent)
+ ([#12](https://github.com/clue/php-socks-react/pull/12))
+
+* Explicitly list dependencies, clean up examples and extend test suite significantly
+
+## 0.1.0 (2014-05-19)
+
+* First stable release
+* Async SOCKS `Client` and `Server` implementation
+* Project was originally part of [clue/socks](https://github.com/clue/php-socks)
+ and was split off from its latest releave v0.4.0
+ ([#1](https://github.com/clue/reactphp-socks/issues/1))
+
+> Upgrading from clue/socks v0.4.0? Use namespace `Clue\React\Socks` instead of `Socks` and you're ready to go!
+
+## 0.0.0 (2011-04-26)
+
+* Initial concept, originally tracked as part of
+ [clue/socks](https://github.com/clue/php-socks)
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/LICENSE b/src/mpg25-instagram-api/vendor/clue/socks-react/LICENSE
new file mode 100755
index 0000000..8efa9aa
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2011 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/README.md b/src/mpg25-instagram-api/vendor/clue/socks-react/README.md
new file mode 100755
index 0000000..a18d797
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/README.md
@@ -0,0 +1,1017 @@
+# clue/socks-react [](https://travis-ci.org/clue/php-socks-react)
+
+Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of [ReactPHP](http://reactphp.org).
+
+The SOCKS protocol family can be used to easily tunnel TCP connections independent
+of the actual application level protocol, such as HTTP, SMTP, IMAP, Telnet etc.
+
+**Table of contents**
+
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [ConnectorInterface](#connectorinterface)
+ * [connect()](#connect)
+ * [Client](#client)
+ * [Plain TCP connections](#plain-tcp-connections)
+ * [Secure TLS connections](#secure-tls-connections)
+ * [HTTP requests](#http-requests)
+ * [Protocol version](#protocol-version)
+ * [DNS resolution](#dns-resolution)
+ * [Authentication](#authentication)
+ * [Proxy chaining](#proxy-chaining)
+ * [Connection timeout](#connection-timeout)
+ * [SOCKS over TLS](#socks-over-tls)
+ * [Unix domain sockets](#unix-domain-sockets)
+ * [Server](#server)
+ * [Server connector](#server-connector)
+ * [Protocol version](#server-protocol-version)
+ * [Authentication](#server-authentication)
+ * [Proxy chaining](#server-proxy-chaining)
+ * [SOCKS over TLS](#server-socks-over-tls)
+ * [Unix domain sockets](#server-unix-domain-sockets)
+* [Servers](#servers)
+ * [Using a PHP SOCKS server](#using-a-php-socks-server)
+ * [Using SSH as a SOCKS server](#using-ssh-as-a-socks-server)
+ * [Using the Tor (anonymity network) to tunnel SOCKS connections](#using-the-tor-anonymity-network-to-tunnel-socks-connections)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Quickstart example
+
+Once [installed](#install), you can use the following code to create a connection
+to google.com via a local SOCKS proxy server:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$client = new Client('127.0.0.1:1080', new Connector($loop));
+
+$client->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+});
+
+$loop->run();
+```
+
+If you're not already running any other [SOCKS proxy server](#servers),
+you can use the following code to create a SOCKS
+proxy server listening for connections on `localhost:1080`:
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+// listen on localhost:1080
+$socket = new Socket('127.0.0.1:1080', $loop);
+
+// start a new server listening for incoming connection on the given socket
+$server = new Server($loop, $socket);
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### ConnectorInterface
+
+The `ConnectorInterface` is responsible for providing an interface for
+establishing streaming connections, such as a normal TCP/IP connection.
+
+In order to use this library, you should understand how this integrates with its
+ecosystem.
+This base interface is actually defined in React's
+[Socket component](https://github.com/reactphp/socket) and used
+throughout React's ecosystem.
+
+Most higher-level components (such as HTTP, database or other networking
+service clients) accept an instance implementing this interface to create their
+TCP/IP connection to the underlying networking service.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against this library in order to connect through a
+SOCKS proxy server.
+
+The interface only offers a single method:
+
+#### connect()
+
+The `connect(string $uri): PromiseInterface` method
+can be used to establish a streaming connection.
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface) or
+rejects with an `Exception`:
+
+```php
+$connector->connect('tcp://google.com:80')->then(
+ function (ConnectionInterface $stream) {
+ // connection successfully established
+ },
+ function (Exception $error) {
+ // failed to connect due to $error
+ }
+);
+```
+
+### Client
+
+The `Client` is responsible for communication with your SOCKS server instance.
+Its constructor simply accepts an SOCKS proxy URI and a connector used to
+connect to the SOCKS proxy server address.
+
+In its most simple form, you can simply pass React's
+[`Connector`](https://github.com/reactphp/socket#connector)
+like this:
+
+```php
+$connector = new React\Socket\Connector($loop);
+$client = new Client('127.0.0.1:1080', $connector);
+```
+
+You can omit the port if you're using the default SOCKS port 1080:
+
+```php
+$client = new Client('127.0.0.1', $connector);
+```
+
+If you need custom connector settings (DNS resolution, timeouts etc.), you can explicitly pass a
+custom instance of the [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
+
+```php
+// use local DNS server
+$dnsResolverFactory = new DnsFactory();
+$resolver = $dnsResolverFactory->createCached('127.0.0.1', $loop);
+
+// outgoing connections to SOCKS server via interface 192.168.10.1
+// this is not to be confused with local DNS resolution (see further below)
+$connector = new DnsConnector(
+ new TcpConnector($loop, array('bindto' => '192.168.10.1:0')),
+ $resolver
+);
+
+$client = new Client('my-socks-server.local:1080', $connector);
+```
+
+This is the main class in this package.
+Because it implements the the [`ConnectorInterface`](#connectorinterface), it
+can simply be used in place of a normal connector.
+This makes it fairly simple to add SOCKS proxy support to pretty much any
+higher-level component:
+
+```diff
+- $client = new SomeClient($connector);
++ $proxy = new Client('127.0.0.1:1080', $connector);
++ $client = new SomeClient($proxy);
+```
+
+#### Plain TCP connections
+
+The `Client` implements the [`ConnectorInterface`](#connectorinterface) and
+hence provides a single public method, the [`connect()`](#connect) method.
+Let's open up a streaming TCP/IP connection and write some data:
+
+```php
+$client->connect('tcp://www.google.com:80')->then(function (ConnectonInterface $stream) {
+ echo 'connected to www.google.com:80';
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ // ...
+});
+```
+
+You can either use the `Client` directly or you may want to wrap this connector
+in React's [`Connector`](https://github.com/reactphp/socket#connector):
+
+```php
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false
+));
+
+$connector->connect('tcp://www.google.com:80')->then(function (ConnectonInterface $stream) {
+ echo 'connected to www.google.com:80';
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ // ...
+});
+
+```
+
+See also the [first example](examples).
+
+The `tcp://` scheme can also be omitted.
+Passing any other scheme will reject the promise.
+
+Pending connection attempts can be canceled by canceling its pending promise like so:
+
+```php
+$promise = $connector->connect($uri);
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying TCP/IP
+connection to the SOCKS server and/or the SOCKS protocol negotiation and reject
+the resulting promise.
+
+#### Secure TLS connections
+
+If you want to establish a secure TLS connection (such as HTTPS) between you and
+your destination, you may want to wrap this connector in React's
+[`Connector`](https://github.com/reactphp/socket#connector) or the low-level
+[`SecureConnector`](https://github.com/reactphp/socket#secureconnector):
+
+```php
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false
+));
+
+// now create an SSL encrypted connection (notice the $ssl instead of $tcp)
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ // proceed with just the plain text data
+ // everything is encrypted/decrypted automatically
+ echo 'connected to SSL encrypted www.google.com';
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ // ...
+});
+```
+
+See also the [second example](examples).
+
+If you use the low-level `SecureConnector`, then the `tls://` scheme can also
+be omitted.
+Passing any other scheme will reject the promise.
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+> Also note how secure TLS connections are in fact entirely handled outside of
+ this SOCKS client implementation.
+
+You can optionally pass additional
+[SSL context options](http://php.net/manual/en/context.ssl.php)
+to the constructor like this:
+
+```php
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ),
+ 'dns' => false
+));
+```
+
+#### HTTP requests
+
+HTTP operates on a higher layer than this low-level SOCKS implementation.
+If you want to issue HTTP requests, you can add a dependency for
+[clue/buzz-react](https://github.com/clue/php-buzz-react).
+It can interact with this library by issuing all
+[http requests through a SOCKS server](https://github.com/clue/php-buzz-react#socks-proxy).
+This works for both plain HTTP and SSL encrypted HTTPS requests.
+
+#### Protocol version
+
+This library supports the SOCKS4, SOCKS4a and SOCKS5 protocol versions.
+
+While SOCKS4 already had (a somewhat limited) support for `SOCKS BIND` requests
+and SOCKS5 added generic UDP support (`SOCKS UDPASSOCIATE`), this library
+focuses on the most commonly used core feature of `SOCKS CONNECT`.
+In this mode, a SOCKS server acts as a generic proxy allowing higher level
+application protocols to work through it.
+
+
+
+Note, this is __not__ a full SOCKS5 implementation due to missing GSSAPI
+authentication (but it's unlikely you're going to miss it anyway).
+
+By default, the `Client` communicates via SOCKS4a with the SOCKS server
+– unless you enable [authentication](#authentication), in which case it will
+default to SOCKS5.
+This is done because SOCKS4a incurs less overhead than SOCKS5 (see above) and
+is equivalent with SOCKS4 if you use [local DNS resolution](#dns-resolution).
+
+If want to explicitly set the protocol version, use the supported values URI
+schemes `socks4`, `socks4a` or `socks5` as part of the SOCKS URI:
+
+```php
+$client = new Client('socks5://127.0.0.1', $connector);
+```
+
+As seen above, both SOCKS5 and SOCKS4a support remote and local DNS resolution.
+If you've explicitly set this to SOCKS4, then you may want to check the following
+chapter about local DNS resolution or you may only connect to IPv4 addresses.
+
+#### DNS resolution
+
+By default, the `Client` does not perform any DNS resolution at all and simply
+forwards any hostname you're trying to connect to to the SOCKS server.
+The remote SOCKS server is thus responsible for looking up any hostnames via DNS
+(this default mode is thus called *remote DNS resolution*).
+As seen above, this mode is supported by the SOCKS5 and SOCKS4a protocols, but
+not the SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
+
+On the other hand, all SOCKS protocol versions support sending destination IP
+addresses to the SOCKS server.
+In this mode you either have to stick to using IPs only (which is ofen unfeasable)
+or perform any DNS lookups locally and only transmit the resolved destination IPs
+(this mode is thus called *local DNS resolution*).
+
+The default *remote DNS resolution* is useful if your local `Client` either can
+not resolve target hostnames because it has no direct access to the internet or
+if it should not resolve target hostnames because its outgoing DNS traffic might
+be intercepted (in particular when using the
+[Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections)).
+
+As noted above, the `Client` defaults to using remote DNS resolution.
+However, wrapping the `Client` in React's
+[`Connector`](https://github.com/reactphp/socket#connector) actually
+performs local DNS resolution unless explicitly defined otherwise.
+Given that remote DNS resolution is assumed to be the preferred mode, all
+other examples explicitly disable DNS resoltion like this:
+
+```php
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false
+));
+```
+
+If you want to explicitly use *local DNS resolution* (such as when explicitly
+using SOCKS4), you can use the following code:
+
+```php
+// set up Connector which uses Google's public DNS (8.8.8.8)
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => '8.8.8.8'
+));
+```
+
+See also the [fourth example](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+> Also note how local DNS resolution is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+If you've explicitly set the client to SOCKS4 and stick to the default
+*remote DNS resolution*, then you may only connect to IPv4 addresses because
+the protocol lacks a way to communicate hostnames.
+If you try to connect to a hostname despite, the resulting promise will be
+rejected right away.
+
+#### Authentication
+
+This library supports username/password authentication for SOCKS5 servers as
+defined in [RFC 1929](http://tools.ietf.org/html/rfc1929).
+
+On the client side, simply pass your username and password to use for
+authentication (see below).
+For each further connection the client will merely send a flag to the server
+indicating authentication information is available.
+Only if the server requests authentication during the initial handshake,
+the actual authentication credentials will be transmitted to the server.
+
+Note that the password is transmitted in cleartext to the SOCKS proxy server,
+so this methods should not be used on a network where you have to worry about eavesdropping.
+
+You can simply pass the authentication information as part of the SOCKS URI:
+
+```php
+$client = new Client('username:password@127.0.0.1', $connector);
+```
+
+Note that both the username and password must be percent-encoded if they contain
+special characters:
+
+```php
+$user = 'he:llo';
+$pass = 'p@ss';
+
+$client = new Client(
+ rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1',
+ $connector
+);
+```
+
+> The authentication details will be transmitted in cleartext to the SOCKS proxy
+ server only if it requires username/password authentication.
+ If the authentication details are missing or not accepted by the remote SOCKS
+ proxy server, it is expected to reject each connection attempt with an
+ exception error code of `SOCKET_EACCES` (13).
+
+Authentication is only supported by protocol version 5 (SOCKS5),
+so passing authentication to the `Client` enforces communication with protocol
+version 5 and complains if you have explicitly set anything else:
+
+```php
+// throws InvalidArgumentException
+new Client('socks4://user:pass@127.0.0.1', $connector);
+```
+
+#### Proxy chaining
+
+The `Client` is responsible for creating connections to the SOCKS server which
+then connects to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if you want to conceal your origin address.
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Client` uses any instance of the `ConnectorInterface` to establish
+outgoing connections.
+In order to connect through another SOCKS server, you can simply use another
+SOCKS connector from another SOCKS client like this:
+
+```php
+// https via the proxy chain "MiddlemanSocksServer -> TargetSocksServer -> TargetHost"
+// please note how the client uses TargetSocksServer (not MiddlemanSocksServer!),
+// which in turn then uses MiddlemanSocksServer.
+// this creates a TCP/IP connection to MiddlemanSocksServer, which then connects
+// to TargetSocksServer, which then connects to the TargetHost
+$middle = new Client('127.0.0.1:1080', new Connector($loop));
+$target = new Client('example.com:1080', $middle);
+
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $target,
+ 'dns' => false
+));
+
+$connector->connect('tls://www.google.com:443')->then(function ($stream) {
+ // …
+});
+```
+
+See also the [third example](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and not part of this class.
+ For example, you can find this in the below [`Server`](#server-proxy-chaining)
+ class or somewhat similar when you're using the
+ [Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections).
+
+#### Connection timeout
+
+By default, the `Client` does not implement any timeouts for establishing remote
+connections.
+Your underlying operating system may impose limits on pending and/or idle TCP/IP
+connections, anywhere in a range of a few minutes to several hours.
+
+Many use cases require more control over the timeout and likely values much
+smaller, usually in the range of a few seconds only.
+
+You can use React's [`Connector`](https://github.com/reactphp/socket#connector)
+or the low-level
+[`TimeoutConnector`](https://github.com/reactphp/socket#timeoutconnector)
+to decorate any given `ConnectorInterface` instance.
+It provides the same `connect()` method, but will automatically reject the
+underlying connection attempt if it takes too long:
+
+```php
+$connector = new Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false,
+ 'timeout' => 3.0
+));
+
+$connector->connect('tcp://google.com:80')->then(function ($stream) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+> Also note how connection timeout is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+#### SOCKS over TLS
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* you connect to and
+may also be able to see your [SOCKS authentication](#authentication) details
+in cleartext.
+
+As an alternative, you may establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+you want to connect to or your [SOCKS authentication](#authentication) details.
+
+You can use the `sockss://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$client = new Client('sockss://127.0.0.1:1080', new Connector($loop));
+
+$client = new Client('socks5s://127.0.0.1:1080', new Connector($loop));
+```
+
+See also [example 32](examples).
+
+Simiarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$client = new Client('sockss://user:pass@127.0.0.1:1080', new Connector($loop));
+```
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+ In particular, the SOCKS server has to accept secure TLS connections, see
+ also [Server SOCKS over TLS](#server-socks-over-tls) for more details.
+ Also, PHP does not support "double encryption" over a single connection.
+ This means that enabling [secure TLS connections](#secure-tls-connections)
+ over a communication channel that has been opened with SOCKS over TLS
+ may not be supported.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this may also have only limited support for
+ [proxy chaining](#proxy-chaining) over multiple TLS paths.
+
+#### Unix domain sockets
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS server listen on a
+Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#authentication).
+
+You can use the `socks+unix://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$client = new Client('socks+unix:///tmp/proxy.sock', new Connector($loop));
+
+$client = new Client('socks5+unix:///tmp/proxy.sock', new Connector($loop));
+```
+
+Simiarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$client = new Client('socks+unix://user:pass@/tmp/proxy.sock', new Connector($loop));
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
+ has limited support for this.
+ In particular, enabling [secure TLS](#secure-tls-connections) may not be
+ supported.
+
+> Note that SOCKS protocol does not support the notion of UDS paths. The above
+ works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does also not support [proxy chaining](#proxy-chaining)
+ over multiple UDS paths.
+
+### Server
+
+The `Server` is responsible for accepting incoming communication from SOCKS clients
+and forwarding the requested connection to the target host.
+It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
+and an underlying TCP/IP socket server like this:
+
+```php
+$loop = \React\EventLoop\Factory::create();
+
+// listen on localhost:$port
+$socket = new Socket($port, $loop);
+
+$server = new Server($loop, $socket);
+```
+
+#### Server connector
+
+The `Server` uses an instance of the [`ConnectorInterface`](#connectorinterface)
+to establish outgoing connections for each incoming connection request.
+
+If you need custom connector settings (DNS resolution, timeouts etc.), you can explicitly pass a
+custom instance of the [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
+
+```php
+// use local DNS server
+$dnsResolverFactory = new DnsFactory();
+$resolver = $dnsResolverFactory->createCached('127.0.0.1', $loop);
+
+// outgoing connections to target host via interface 192.168.10.1
+$connector = new DnsConnector(
+ new TcpConnector($loop, array('bindto' => '192.168.10.1:0')),
+ $resolver
+);
+
+$server = new Server($loop, $socket, $connector);
+```
+
+If you want to forward the outgoing connection through another SOCKS proxy, you
+may also pass a [`Client`](#client) instance as a connector, see also
+[server proxy chaining](#server-proxy-chaining) for more details.
+
+Internally, the `Server` uses the normal [`connect()`](#connect) method, but
+it also passes the original client IP as the `?source={remote}` parameter.
+The `source` parameter contains the full remote URI, including the protocol
+and any authentication details, for example `socks5://user:pass@1.2.3.4:5678`.
+You can use this parameter for logging purposes or to restrict connection
+requests for certain clients by providing a custom implementation of the
+[`ConnectorInterface`](#connectorinterface).
+
+#### Server protocol version
+
+The `Server` supports all protocol versions (SOCKS4, SOCKS4a and SOCKS5) by default.
+
+If want to explicitly set the protocol version, use the supported values `4`, `4a` or `5`:
+
+```PHP
+$server->setProtocolVersion(5);
+```
+
+In order to reset the protocol version to its default (i.e. automatic detection),
+use `null` as protocol version.
+
+```PHP
+$server->setProtocolVersion(null);
+```
+
+#### Server authentication
+
+By default, the `Server` does not require any authentication from the clients.
+You can enable authentication support so that clients need to pass a valid
+username and password before forwarding any connections.
+
+Setting authentication on the `Server` enforces each further connected client
+to use protocol version 5 (SOCKS5).
+If a client tries to use any other protocol version, does not send along
+authentication details or if authentication details can not be verified,
+the connection will be rejected.
+
+Because your authentication mechanism might take some time to actually check
+the provided authentication credentials (like querying a remote database or webservice),
+the server side uses a [Promise](https://github.com/reactphp/promise) based interface.
+While this might seem complex at first, it actually provides a very simple way
+to handle simultanous connections in a non-blocking fashion and increases overall performance.
+
+```PHP
+$server->setAuth(function ($username, $password, $remote) {
+ // either return a boolean success value right away
+ // or use promises for delayed authentication
+
+ // $remote is a full URI à la socks5://user:pass@192.168.1.1:1234
+ // or socks5s://user:pass@192.168.1.1:1234 for SOCKS over TLS
+ // useful for logging or extracting parts, such as the remote IP
+ $ip = parse_url($remote, PHP_URL_HOST);
+
+ return ($username === 'root' && $ip === '127.0.0.1');
+});
+```
+
+Or if you only accept static authentication details, you can use the simple
+array-based authentication method as a shortcut:
+
+```PHP
+$server->setAuthArray(array(
+ 'tom' => 'password',
+ 'admin' => 'root'
+));
+```
+
+See also [example #12](examples).
+
+If you do not want to use authentication anymore:
+
+```PHP
+$server->unsetAuth();
+```
+
+#### Server proxy chaining
+
+The `Server` is responsible for creating connections to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if your target SOCKS server requires
+authentication, but your client does not support sending authentication
+information (e.g. like most webbrowser).
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Server` uses any instance of the `ConnectorInterface` to establish outgoing
+connections.
+In order to connect through another SOCKS server, you can simply use the
+[`Client`](#client) SOCKS connector from above.
+You can create a SOCKS `Client` instance like this:
+
+```php
+// set next SOCKS server example.com:1080 as target
+$connector = new React\Socket\Connector($loop);
+$client = new Client('user:pass@example.com:1080', $connector);
+
+// listen on localhost:1080
+$socket = new Socket('127.0.0.1:1080', $loop);
+
+// start a new server which forwards all connections to the other SOCKS server
+$server = new Server($loop, $socket, $client);
+```
+
+See also [example #21](examples).
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property and not part of this class.
+ For example, you can find this in the above [`Client`](#proxy-chaining) class.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and can be implemented as above.
+
+#### Server SOCKS over TLS
+
+All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* the client connects
+to and may also be able to see the [SOCKS authentication](#authentication)
+details in cleartext.
+
+As an alternative, you may listen for SOCKS over TLS connections so
+that the client has to establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+the client wants to connect to or their [SOCKS authentication](#authentication)
+details.
+
+You can simply start your listening socket on the `tls://` URI scheme like this:
+
+```php
+$loop = \React\EventLoop\Factory::create();
+
+// listen on tls://127.0.0.1:1080 with the given server certificate
+$socket = new React\Socket\Server('tls://127.0.0.1:1080', $loop, array(
+ 'tls' => array(
+ 'local_cert' => __DIR__ . '/localhost.pem',
+ )
+));
+$server = new Server($loop, $socket);
+```
+
+See also [example 31](examples).
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this does also not support [proxy chaining](#server-proxy-chaining)
+ over multiple TLS paths.
+
+#### Server Unix domain sockets
+
+All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS server listen on a
+Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#server-authentication).
+
+You can simply start your listening socket on the `unix://` URI scheme like this:
+
+```php
+$loop = \React\EventLoop\Factory::create();
+
+// listen on /tmp/proxy.sock
+$socket = new React\Socket\Server('unix:///tmp/proxy.sock', $loop);
+$server = new Server($loop, $socket);
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and that
+ the SOCKS protocol does not support the notion of UDS paths. The above
+ works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does also not support [proxy chaining](#server-proxy-chaining)
+ over multiple UDS paths.
+
+## Servers
+
+### Using a PHP SOCKS server
+
+* If you're looking for an end-user SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
+* If you're looking for a SOCKS server implementation, consider using
+ the above [`Server`](#server) class.
+
+### Using SSH as a SOCKS server
+
+If you already have an SSH server set up, you can easily use it as a SOCKS
+tunnel end point. On your client, simply start your SSH client and use
+the `-D ` option to start a local SOCKS server (quoting the man page:
+a `local "dynamic" application-level port forwarding`).
+
+You can start a local SOCKS server by creating a loopback connection to your
+local system if you already run an SSH daemon:
+
+```bash
+$ ssh -D 1080 localhost
+```
+
+Alternatively, you can start a local SOCKS server tunneling through a given
+remote host that runs an SSH daemon:
+
+```bash
+$ ssh -D 1080 example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$client = new Client('127.0.0.1:1080', $connector);
+```
+
+Note that the above will allow all users on the local system to connect over
+your SOCKS server without authentication which may or may not be what you need.
+As an alternative, recent OpenSSH client versions also support
+[Unix domain sockets](#unix-domain-sockets) (UDS) paths so that you can rely
+on Unix file system permissions instead:
+
+```bash
+$ ssh -D/tmp/proxy.sock example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$client = new Client('socks+unix:///tmp/proxy.sock', $connector);
+```
+
+### Using the Tor (anonymity network) to tunnel SOCKS connections
+
+The [Tor anonymity network](http://www.torproject.org) client software is designed
+to encrypt your traffic and route it over a network of several nodes to conceal its origin.
+It presents a SOCKS4 and SOCKS5 interface on TCP port 9050 by default
+which allows you to tunnel any traffic through the anonymity network.
+In most scenarios you probably don't want your client to resolve the target hostnames,
+because you would leak DNS information to anybody observing your local traffic.
+Also, Tor provides hidden services through an `.onion` pseudo top-level domain
+which have to be resolved by Tor.
+
+```PHP
+$client = new Client('127.0.0.1:9050', $connector);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require clue/socks-react:^0.8.7
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite contains a number of tests that rely on a working internet
+connection, alternatively you can also run it like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see LICENSE
+
+## More
+
+* If you want to learn more about processing streams of data, refer to the
+ documentation of the underlying
+ [react/stream](https://github.com/reactphp/stream) component.
+* If you want to learn more about how the
+ [`ConnectorInterface`](#connectorinterface) and its usual implementations look
+ like, refer to the documentation of the underlying
+ [react/socket component](https://github.com/reactphp/socket).
+* As an alternative to a SOCKS (SOCKS4/SOCKS5) proxy, you may also want to look into
+ using an HTTP CONNECT proxy instead.
+ You may want to use [clue/http-proxy-react](https://github.com/clue/php-http-proxy-react)
+ which also provides an implementation of the
+ [`ConnectorInterface`](#connectorinterface) so that supporting either proxy
+ protocol should be fairly trivial.
+* If you're dealing with public proxies, you'll likely have to work with mixed
+ quality and unreliable proxies. You may want to look into using
+ [clue/connection-manager-extra](https://github.com/clue/php-connection-manager-extra)
+ which allows retrying unreliable ones, implying connection timeouts,
+ concurrently working with multiple connectors and more.
+* If you're looking for an end-user SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/composer.json b/src/mpg25-instagram-api/vendor/clue/socks-react/composer.json
new file mode 100755
index 0000000..50ef83a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "clue/socks-react",
+ "description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
+ "keywords": ["socks client", "socks server", "proxy", "tcp tunnel", "socks protocol", "async", "ReactPHP"],
+ "homepage": "https://github.com/clue/php-socks-react",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "autoload": {
+ "psr-4": {"Clue\\React\\Socks\\": "src/"}
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/socket": "^1.0 || ^0.8.6",
+ "react/promise": "^2.1 || ^1.2",
+ "evenement/evenement": "~3.0|~1.0|~2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
+ "clue/connection-manager-extra": "^1.0 || ^0.7",
+ "clue/block-react": "^1.1"
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/01-http.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/01-http.php
new file mode 100755
index 0000000..584b6c4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/01-http.php
@@ -0,0 +1,30 @@
+ $client,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/02-https.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/02-https.php
new file mode 100755
index 0000000..f763fc2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/02-https.php
@@ -0,0 +1,30 @@
+ $client,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/03-proxy-chaining.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/03-proxy-chaining.php
new file mode 100755
index 0000000..5278d2c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/03-proxy-chaining.php
@@ -0,0 +1,46 @@
+ [...]' . PHP_EOL;
+ echo 'You can add 1..n proxies in the path' . PHP_EOL;
+ exit(1);
+}
+
+$path = array_slice($argv, 1);
+
+// Alternatively, you can also hard-code this value like this:
+//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
+
+$loop = React\EventLoop\Factory::create();
+
+// set next SOCKS server chain via p1 -> p2 -> p3 -> destination
+$connector = new Connector($loop);
+foreach ($path as $proxy) {
+ $connector = new Client($proxy, $connector);
+}
+
+// please note how the client uses p3 (not p1!), which in turn then uses the complete chain
+// this creates a TCP/IP connection to p1, which then connects to p2, then to p3, which then connects to the target
+$connector = new Connector($loop, array(
+ 'tcp' => $connector,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS client connecting to SOCKS proxy server chain ' . implode(' -> ', $path) . PHP_EOL;
+
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/04-local-dns.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/04-local-dns.php
new file mode 100755
index 0000000..2ea39dd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/04-local-dns.php
@@ -0,0 +1,31 @@
+ $client,
+ 'timeout' => 3.0,
+ 'dns' => '8.8.8.8'
+));
+
+echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/11-server.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/11-server.php
new file mode 100755
index 0000000..da0a1a1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/11-server.php
@@ -0,0 +1,19 @@
+getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/12-server-with-password.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/12-server-with-password.php
new file mode 100755
index 0000000..55cc30b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/12-server-with-password.php
@@ -0,0 +1,24 @@
+setAuthArray(array(
+ 'tom' => 'god',
+ 'user' => 'p@ssw0rd'
+));
+
+echo 'SOCKS5 server requiring authentication listening on ' . $socket->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/13-server-blacklist.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/13-server-blacklist.php
new file mode 100755
index 0000000..c153049
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/13-server-blacklist.php
@@ -0,0 +1,40 @@
+ $reject,
+ 'www.google.com:80' => $reject,
+ '*' => $permit
+));
+
+// listen on 127.0.0.1:1080 or first argument
+$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
+$socket = new Socket($listen, $loop);
+
+// start the actual socks server on the given server socket and using our connection manager for outgoing connections
+$server = new Server($loop, $socket, $connector);
+
+echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/21-server-proxy-chaining.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/21-server-proxy-chaining.php
new file mode 100755
index 0000000..0d0dee2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/21-server-proxy-chaining.php
@@ -0,0 +1,42 @@
+ [...]' . PHP_EOL;
+ echo 'You can add 1..n proxies in the path' . PHP_EOL;
+ exit(1);
+}
+
+$listen = $argv[1];
+$path = array_slice($argv, 2);
+
+// Alternatively, you can also hard-code these values like this:
+//$listen = '127.0.0.1:9050';
+//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
+
+$loop = React\EventLoop\Factory::create();
+
+// set next SOCKS server chain -> p1 -> p2 -> p3 -> destination
+$connector = new Connector($loop);
+foreach ($path as $proxy) {
+ $connector = new Client($proxy, $connector);
+}
+
+// listen on 127.0.0.1:1080 or first argument
+$socket = new Socket($listen, $loop);
+
+// start a new server which forwards all connections to the other SOCKS server
+$server = new Server($loop, $socket, $connector);
+
+echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
+echo 'Forwarding via: ' . implode(' -> ', $path) . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
new file mode 100755
index 0000000..39245c9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
@@ -0,0 +1,46 @@
+ ...' . PHP_EOL;
+ echo 'You can add 2..n proxies in the pool' . PHP_EOL;
+ exit(1);
+}
+
+$listen = $argv[1];
+$pool = array_slice($argv, 2);
+
+// Alternatively, you can also hard-code these values like this:
+//$listen = '127.0.0.1:9050';
+//$pool = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
+
+$loop = LoopFactory::create();
+
+// forward to socks server listening on 127.0.0.1:9051-9053
+// this connector randomly picks one of the the attached connectors from the pool
+$connector = new Connector($loop);
+$clients = array();
+foreach ($pool as $proxy) {
+ $clients []= new Client($proxy, $connector);
+}
+$connector = new ConnectionManagerRandom($clients);
+
+$socket = new Socket($listen, $loop);
+
+// start the actual socks server on the given server socket and using our connection manager for outgoing connections
+$server = new Server($loop, $socket, $connector);
+
+echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
+echo 'Randomly picking from: ' . implode(', ', $pool) . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/31-server-secure.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/31-server-secure.php
new file mode 100755
index 0000000..b4b2109
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/31-server-secure.php
@@ -0,0 +1,21 @@
+ array(
+ 'local_cert' => __DIR__ . '/localhost.pem',
+)));
+
+// start a new server listening for incoming connection on the given socket
+$server = new Server($loop, $socket);
+
+echo 'SOCKS over TLS server listening on ' . str_replace('tls:', 'sockss:', $socket->getAddress()) . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/32-http-secure.php b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/32-http-secure.php
new file mode 100755
index 0000000..d304bd4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/32-http-secure.php
@@ -0,0 +1,33 @@
+ array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+))));
+$connector = new Connector($loop, array(
+ 'tcp' => $client,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS over TLS client connecting to secure SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/examples/localhost.pem b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/localhost.pem
new file mode 100755
index 0000000..be69279
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/examples/localhost.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
+MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
+BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
+0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
+7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
+824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
+V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
+IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
+ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
+g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
+tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
+LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
+tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
+9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
+43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
+pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
+OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
+2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
+li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
+b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
+vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
+XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
+Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
+iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
++EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
+y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
+81oh1uCH1YPLM29hPyaohxL8
+-----END PRIVATE KEY-----
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/clue/socks-react/phpunit.xml.dist
new file mode 100755
index 0000000..d451dff
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/src/Client.php b/src/mpg25-instagram-api/vendor/clue/socks-react/src/Client.php
new file mode 100755
index 0000000..ee643b6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/src/Client.php
@@ -0,0 +1,382 @@
+ SOCKS5 authentication
+ if (isset($parts['user']) || isset($parts['pass'])) {
+ if ($parts['scheme'] === 'socks') {
+ // default to using SOCKS5 if not given explicitly
+ $parts['scheme'] = 'socks5';
+ } elseif ($parts['scheme'] !== 'socks5') {
+ // fail if any other protocol version given explicitly
+ throw new InvalidArgumentException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
+ }
+ $parts += array('user' => '', 'pass' => '');
+ $this->setAuth(rawurldecode($parts['user']), rawurldecode($parts['pass']));
+ }
+
+ // check for valid protocol version from URI scheme
+ $this->setProtocolVersionFromScheme($parts['scheme']);
+
+ $this->socksUri = $parts['host'] . ':' . $parts['port'];
+ $this->connector = $connector;
+ }
+
+ private function setProtocolVersionFromScheme($scheme)
+ {
+ if ($scheme === 'socks' || $scheme === 'socks4a') {
+ $this->protocolVersion = '4a';
+ } elseif ($scheme === 'socks5') {
+ $this->protocolVersion = '5';
+ } elseif ($scheme === 'socks4') {
+ $this->protocolVersion = '4';
+ } else {
+ throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
+ }
+ }
+
+ /**
+ * set login data for username/password authentication method (RFC1929)
+ *
+ * @param string $username
+ * @param string $password
+ * @link http://tools.ietf.org/html/rfc1929
+ */
+ private function setAuth($username, $password)
+ {
+ if (strlen($username) > 255 || strlen($password) > 255) {
+ throw new InvalidArgumentException('Both username and password MUST NOT exceed a length of 255 bytes each');
+ }
+ $this->auth = pack('C2', 0x01, strlen($username)) . $username . pack('C', strlen($password)) . $password;
+ }
+
+ /**
+ * Establish a TCP/IP connection to the given target URI through the SOCKS server
+ *
+ * Many higher-level networking protocols build on top of TCP. It you're dealing
+ * with one such client implementation, it probably uses/accepts an instance
+ * implementing React's `ConnectorInterface` (and usually its default `Connector`
+ * instance). In this case you can also pass this `Connector` instance instead
+ * to make this client implementation SOCKS-aware. That's it.
+ *
+ * @param string $uri
+ * @return PromiseInterface Promise
+ */
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $port = $parts['port'];
+
+ if ($this->protocolVersion === '4' && false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ return Promise\reject(new InvalidArgumentException('Requires an IPv4 address for SOCKS4'));
+ }
+
+ if (strlen($host) > 255 || $port > 65535 || $port < 0 || (string)$port !== (string)(int)$port) {
+ return Promise\reject(new InvalidArgumentException('Invalid target specified'));
+ }
+
+ // construct URI to SOCKS server to connect to
+ $socksUri = $this->socksUri;
+
+ // append path from URI if given
+ if (isset($parts['path'])) {
+ $socksUri .= $parts['path'];
+ }
+
+ // parse query args
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // append hostname from URI to query string unless explicitly given
+ if (!isset($args['hostname'])) {
+ $args['hostname'] = $host;
+ }
+
+ // append query string
+ $socksUri .= '?' . http_build_query($args, '', '&');
+
+ // append fragment from URI if given
+ if (isset($parts['fragment'])) {
+ $socksUri .= '#' . $parts['fragment'];
+ }
+
+ $that = $this;
+
+ // start TCP/IP connection to SOCKS server and then
+ // handle SOCKS protocol once connection is ready
+ // resolve plain connection once SOCKS protocol is completed
+ return $this->connector->connect($socksUri)->then(
+ function (ConnectionInterface $stream) use ($that, $host, $port) {
+ return $that->handleConnectedSocks($stream, $host, $port);
+ },
+ function (Exception $e) {
+ throw new RuntimeException('Unable to connect to proxy (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $e);
+ }
+ );
+ }
+
+ /**
+ * Internal helper used to handle the communication with the SOCKS server
+ *
+ * @param ConnectionInterface $stream
+ * @param string $host
+ * @param int $port
+ * @return Promise Promise
+ * @internal
+ */
+ public function handleConnectedSocks(ConnectionInterface $stream, $host, $port)
+ {
+ $deferred = new Deferred(function ($_, $reject) {
+ $reject(new RuntimeException('Connection canceled while establishing SOCKS session (ECONNABORTED)', defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103));
+ });
+
+ $reader = new StreamReader();
+ $stream->on('data', array($reader, 'write'));
+
+ $stream->on('error', $onError = function (Exception $e) use ($deferred) {
+ $deferred->reject(new RuntimeException('Stream error while waiting for response from proxy (EIO)', defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e));
+ });
+
+ $stream->on('close', $onClose = function () use ($deferred) {
+ $deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
+ });
+
+ if ($this->protocolVersion === '5') {
+ $promise = $this->handleSocks5($stream, $host, $port, $reader);
+ } else {
+ $promise = $this->handleSocks4($stream, $host, $port, $reader);
+ }
+ $promise->then(function () use ($deferred, $stream) {
+ $deferred->resolve($stream);
+ }, function (Exception $error) use ($deferred) {
+ // pass custom RuntimeException through as-is, otherwise wrap in protocol error
+ if (!$error instanceof RuntimeException) {
+ $error = new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $error);
+ }
+
+ $deferred->reject($error);
+ });
+
+ return $deferred->promise()->then(
+ function (ConnectionInterface $stream) use ($reader, $onError, $onClose) {
+ $stream->removeListener('data', array($reader, 'write'));
+ $stream->removeListener('error', $onError);
+ $stream->removeListener('close', $onClose);
+
+ return $stream;
+ },
+ function ($error) use ($stream, $onClose) {
+ $stream->removeListener('close', $onClose);
+ $stream->close();
+
+ throw $error;
+ }
+ );
+ }
+
+ private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader)
+ {
+ // do not resolve hostname. only try to convert to IP
+ $ip = ip2long($host);
+
+ // send IP or (0.0.0.1) if invalid
+ $data = pack('C2nNC', 0x04, 0x01, $port, $ip === false ? 1 : $ip, 0x00);
+
+ if ($ip === false) {
+ // host is not a valid IP => send along hostname (SOCKS4a)
+ $data .= $host . pack('C', 0x00);
+ }
+
+ $stream->write($data);
+
+ return $reader->readBinary(array(
+ 'null' => 'C',
+ 'status' => 'C',
+ 'port' => 'n',
+ 'ip' => 'N'
+ ))->then(function ($data) {
+ if ($data['null'] !== 0x00) {
+ throw new Exception('Invalid SOCKS response');
+ }
+ if ($data['status'] !== 0x5a) {
+ throw new RuntimeException('Proxy refused connection with SOCKS error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ }
+ });
+ }
+
+ private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader)
+ {
+ // protocol version 5
+ $data = pack('C', 0x05);
+
+ $auth = $this->auth;
+ if ($auth === null) {
+ // one method, no authentication
+ $data .= pack('C2', 0x01, 0x00);
+ } else {
+ // two methods, username/password and no authentication
+ $data .= pack('C3', 0x02, 0x02, 0x00);
+ }
+ $stream->write($data);
+
+ $that = $this;
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'method' => 'C'
+ ))->then(function ($data) use ($auth, $stream, $reader) {
+ if ($data['version'] !== 0x05) {
+ throw new Exception('Version/Protocol mismatch');
+ }
+
+ if ($data['method'] === 0x02 && $auth !== null) {
+ // username/password authentication requested and provided
+ $stream->write($auth);
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'status' => 'C'
+ ))->then(function ($data) {
+ if ($data['version'] !== 0x01 || $data['status'] !== 0x00) {
+ throw new RuntimeException('Username/Password authentication failed (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ }
+ });
+ } else if ($data['method'] !== 0x00) {
+ // any other method than "no authentication"
+ throw new RuntimeException('No acceptable authentication method found (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ }
+ })->then(function () use ($stream, $reader, $host, $port) {
+ // do not resolve hostname. only try to convert to (binary/packed) IP
+ $ip = @inet_pton($host);
+
+ $data = pack('C3', 0x05, 0x01, 0x00);
+ if ($ip === false) {
+ // not an IP, send as hostname
+ $data .= pack('C2', 0x03, strlen($host)) . $host;
+ } else {
+ // send as IPv4 / IPv6
+ $data .= pack('C', (strpos($host, ':') === false) ? 0x01 : 0x04) . $ip;
+ }
+ $data .= pack('n', $port);
+
+ $stream->write($data);
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'status' => 'C',
+ 'null' => 'C',
+ 'type' => 'C'
+ ));
+ })->then(function ($data) use ($reader) {
+ if ($data['version'] !== 0x05 || $data['null'] !== 0x00) {
+ throw new Exception('Invalid SOCKS response');
+ }
+ if ($data['status'] !== 0x00) {
+ // map limited list of SOCKS error codes to common socket error conditions
+ // @link https://tools.ietf.org/html/rfc1928#section-6
+ if ($data['status'] === Server::ERROR_GENERAL) {
+ throw new RuntimeException('SOCKS server reported a general server failure (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ } elseif ($data['status'] === Server::ERROR_NOT_ALLOWED_BY_RULESET) {
+ throw new RuntimeException('SOCKS server reported connection is not allowed by ruleset (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ } elseif ($data['status'] === Server::ERROR_NETWORK_UNREACHABLE) {
+ throw new RuntimeException('SOCKS server reported network unreachable (ENETUNREACH)', defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101);
+ } elseif ($data['status'] === Server::ERROR_HOST_UNREACHABLE) {
+ throw new RuntimeException('SOCKS server reported host unreachable (EHOSTUNREACH)', defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113);
+ } elseif ($data['status'] === Server::ERROR_CONNECTION_REFUSED) {
+ throw new RuntimeException('SOCKS server reported connection refused (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ } elseif ($data['status'] === Server::ERROR_TTL) {
+ throw new RuntimeException('SOCKS server reported TTL/timeout expired (ETIMEDOUT)', defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110);
+ } elseif ($data['status'] === Server::ERROR_COMMAND_UNSUPPORTED) {
+ throw new RuntimeException('SOCKS server does not support the CONNECT command (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
+ } elseif ($data['status'] === Server::ERROR_ADDRESS_UNSUPPORTED) {
+ throw new RuntimeException('SOCKS server does not support this address type (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
+ }
+
+ throw new RuntimeException('SOCKS server reported an unassigned error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ }
+ if ($data['type'] === 0x01) {
+ // IPv4 address => skip IP and port
+ return $reader->readLength(6);
+ } elseif ($data['type'] === 0x03) {
+ // domain name => read domain name length
+ return $reader->readBinary(array(
+ 'length' => 'C'
+ ))->then(function ($data) use ($reader) {
+ // skip domain name and port
+ return $reader->readLength($data['length'] + 2);
+ });
+ } elseif ($data['type'] === 0x04) {
+ // IPv6 address => skip IP and port
+ return $reader->readLength(18);
+ } else {
+ throw new Exception('Invalid SOCKS reponse: Invalid address type');
+ }
+ });
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/src/Server.php b/src/mpg25-instagram-api/vendor/clue/socks-react/src/Server.php
new file mode 100755
index 0000000..0a069e2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/src/Server.php
@@ -0,0 +1,422 @@
+loop = $loop;
+ $this->connector = $connector;
+
+ $that = $this;
+ $serverInterface->on('connection', function ($connection) use ($that) {
+ $that->emit('connection', array($connection));
+ $that->onConnection($connection);
+ });
+ }
+
+ public function setProtocolVersion($version)
+ {
+ if ($version !== null) {
+ $version = (string)$version;
+ if (!in_array($version, array('4', '4a', '5'), true)) {
+ throw new InvalidArgumentException('Invalid protocol version given');
+ }
+ if ($version !== '5' && $this->auth !== null){
+ throw new UnexpectedValueException('Unable to change protocol version to anything but SOCKS5 while authentication is used. Consider removing authentication info or sticking to SOCKS5');
+ }
+ }
+ $this->protocolVersion = $version;
+ }
+
+ public function setAuth($auth)
+ {
+ if (!is_callable($auth)) {
+ throw new InvalidArgumentException('Given authenticator is not a valid callable');
+ }
+ if ($this->protocolVersion !== null && $this->protocolVersion !== '5') {
+ throw new UnexpectedValueException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
+ }
+ // wrap authentication callback in order to cast its return value to a promise
+ $this->auth = function($username, $password, $remote) use ($auth) {
+ $ret = call_user_func($auth, $username, $password, $remote);
+ if ($ret instanceof PromiseInterface) {
+ return $ret;
+ }
+ $deferred = new Deferred();
+ $ret ? $deferred->resolve() : $deferred->reject();
+ return $deferred->promise();
+ };
+ }
+
+ public function setAuthArray(array $login)
+ {
+ $this->setAuth(function ($username, $password) use ($login) {
+ return (isset($login[$username]) && (string)$login[$username] === $password);
+ });
+ }
+
+ public function unsetAuth()
+ {
+ $this->auth = null;
+ }
+
+ public function onConnection(ConnectionInterface $connection)
+ {
+ $that = $this;
+ $handling = $this->handleSocks($connection)->then(function($remote) use ($connection){
+ $connection->emit('ready',array($remote));
+ }, function ($error) use ($connection, $that) {
+ if (!($error instanceof \Exception)) {
+ $error = new \Exception($error);
+ }
+ $connection->emit('error', array($error));
+ $that->endConnection($connection);
+ });
+
+ $connection->on('close', function () use ($handling) {
+ $handling->cancel();
+ });
+ }
+
+ /**
+ * gracefully shutdown connection by flushing all remaining data and closing stream
+ */
+ public function endConnection(ConnectionInterface $stream)
+ {
+ $tid = true;
+ $loop = $this->loop;
+
+ // cancel below timer in case connection is closed in time
+ $stream->once('close', function () use (&$tid, $loop) {
+ // close event called before the timer was set up, so everything is okay
+ if ($tid === true) {
+ // make sure to not start a useless timer
+ $tid = false;
+ } else {
+ $loop->cancelTimer($tid);
+ }
+ });
+
+ // shut down connection by pausing input data, flushing outgoing buffer and then exit
+ $stream->pause();
+ $stream->end();
+
+ // check if connection is not already closed
+ if ($tid === true) {
+ // fall back to forcefully close connection in 3 seconds if buffer can not be flushed
+ $tid = $loop->addTimer(3.0, array($stream,'close'));
+ }
+ }
+
+ private function handleSocks(ConnectionInterface $stream)
+ {
+ $reader = new StreamReader();
+ $stream->on('data', array($reader, 'write'));
+
+ $that = $this;
+ $that = $this;
+
+ $auth = $this->auth;
+ $protocolVersion = $this->protocolVersion;
+
+ // authentication requires SOCKS5
+ if ($auth !== null) {
+ $protocolVersion = '5';
+ }
+
+ return $reader->readByte()->then(function ($version) use ($stream, $that, $protocolVersion, $auth, $reader){
+ if ($version === 0x04) {
+ if ($protocolVersion === '5') {
+ throw new UnexpectedValueException('SOCKS4 not allowed due to configuration');
+ }
+ return $that->handleSocks4($stream, $protocolVersion, $reader);
+ } else if ($version === 0x05) {
+ if ($protocolVersion !== null && $protocolVersion !== '5') {
+ throw new UnexpectedValueException('SOCKS5 not allowed due to configuration');
+ }
+ return $that->handleSocks5($stream, $auth, $reader);
+ }
+ throw new UnexpectedValueException('Unexpected/unknown version number');
+ });
+ }
+
+ public function handleSocks4(ConnectionInterface $stream, $protocolVersion, StreamReader $reader)
+ {
+ // suppliying hostnames is only allowed for SOCKS4a (or automatically detected version)
+ $supportsHostname = ($protocolVersion === null || $protocolVersion === '4a');
+
+ $remote = $stream->getRemoteAddress();
+ if ($remote !== null) {
+ // remove transport scheme and prefix socks4:// instead
+ $secure = strpos($remote, 'tls://') === 0;
+ if (($pos = strpos($remote, '://')) !== false) {
+ $remote = substr($remote, $pos + 3);
+ }
+ $remote = 'socks4' . ($secure ? 's' : '') . '://' . $remote;
+ }
+
+ $that = $this;
+ return $reader->readByteAssert(0x01)->then(function () use ($reader) {
+ return $reader->readBinary(array(
+ 'port' => 'n',
+ 'ipLong' => 'N',
+ 'null' => 'C'
+ ));
+ })->then(function ($data) use ($reader, $supportsHostname, $remote) {
+ if ($data['null'] !== 0x00) {
+ throw new Exception('Not a null byte');
+ }
+ if ($data['ipLong'] === 0) {
+ throw new Exception('Invalid IP');
+ }
+ if ($data['port'] === 0) {
+ throw new Exception('Invalid port');
+ }
+ if ($data['ipLong'] < 256 && $supportsHostname) {
+ // invalid IP => probably a SOCKS4a request which appends the hostname
+ return $reader->readStringNull()->then(function ($string) use ($data, $remote){
+ return array($string, $data['port'], $remote);
+ });
+ } else {
+ $ip = long2ip($data['ipLong']);
+ return array($ip, $data['port'], $remote);
+ }
+ })->then(function ($target) use ($stream, $that) {
+ return $that->connectTarget($stream, $target)->then(function (ConnectionInterface $remote) use ($stream){
+ $stream->write(pack('C8', 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+
+ return $remote;
+ }, function($error) use ($stream){
+ $stream->end(pack('C8', 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+
+ throw $error;
+ });
+ }, function($error) {
+ throw new UnexpectedValueException('SOCKS4 protocol error',0,$error);
+ });
+ }
+
+ public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamReader $reader)
+ {
+ $remote = $stream->getRemoteAddress();
+ if ($remote !== null) {
+ // remove transport scheme and prefix socks5:// instead
+ $secure = strpos($remote, 'tls://') === 0;
+ if (($pos = strpos($remote, '://')) !== false) {
+ $remote = substr($remote, $pos + 3);
+ }
+ $remote = 'socks5' . ($secure ? 's' : '') . '://' . $remote;
+ }
+
+ $that = $this;
+ return $reader->readByte()->then(function ($num) use ($reader) {
+ // $num different authentication mechanisms offered
+ return $reader->readLength($num);
+ })->then(function ($methods) use ($reader, $stream, $auth, &$remote) {
+ if ($auth === null && strpos($methods,"\x00") !== false) {
+ // accept "no authentication"
+ $stream->write(pack('C2', 0x05, 0x00));
+
+ return 0x00;
+ } else if ($auth !== null && strpos($methods,"\x02") !== false) {
+ // username/password authentication (RFC 1929) sub negotiation
+ $stream->write(pack('C2', 0x05, 0x02));
+ return $reader->readByteAssert(0x01)->then(function () use ($reader) {
+ return $reader->readByte();
+ })->then(function ($length) use ($reader) {
+ return $reader->readLength($length);
+ })->then(function ($username) use ($reader, $auth, $stream, &$remote) {
+ return $reader->readByte()->then(function ($length) use ($reader) {
+ return $reader->readLength($length);
+ })->then(function ($password) use ($username, $auth, $stream, &$remote) {
+ // username and password given => authenticate
+
+ // prefix username/password to remote URI
+ if ($remote !== null) {
+ $remote = str_replace('://', '://' . rawurlencode($username) . ':' . rawurlencode($password) . '@', $remote);
+ }
+
+ return $auth($username, $password, $remote)->then(function () use ($stream, $username) {
+ // accept
+ $stream->emit('auth', array($username));
+ $stream->write(pack('C2', 0x01, 0x00));
+ }, function() use ($stream) {
+ // reject => send any code but 0x00
+ $stream->end(pack('C2', 0x01, 0xFF));
+ throw new UnexpectedValueException('Unable to authenticate');
+ });
+ });
+ });
+ } else {
+ // reject all offered authentication methods
+ $stream->write(pack('C2', 0x05, 0xFF));
+ throw new UnexpectedValueException('No acceptable authentication mechanism found');
+ }
+ })->then(function ($method) use ($reader, $stream) {
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'command' => 'C',
+ 'null' => 'C',
+ 'type' => 'C'
+ ));
+ })->then(function ($data) use ($reader) {
+ if ($data['version'] !== 0x05) {
+ throw new UnexpectedValueException('Invalid SOCKS version');
+ }
+ if ($data['command'] !== 0x01) {
+ throw new UnexpectedValueException('Only CONNECT requests supported', Server::ERROR_COMMAND_UNSUPPORTED);
+ }
+// if ($data['null'] !== 0x00) {
+// throw new UnexpectedValueException('Reserved byte has to be NULL');
+// }
+ if ($data['type'] === 0x03) {
+ // target hostname string
+ return $reader->readByte()->then(function ($len) use ($reader) {
+ return $reader->readLength($len);
+ });
+ } else if ($data['type'] === 0x01) {
+ // target IPv4
+ return $reader->readLength(4)->then(function ($addr) {
+ return inet_ntop($addr);
+ });
+ } else if ($data['type'] === 0x04) {
+ // target IPv6
+ return $reader->readLength(16)->then(function ($addr) {
+ return inet_ntop($addr);
+ });
+ } else {
+ throw new UnexpectedValueException('Invalid address type', Server::ERROR_ADDRESS_UNSUPPORTED);
+ }
+ })->then(function ($host) use ($reader, &$remote) {
+ return $reader->readBinary(array('port'=>'n'))->then(function ($data) use ($host, &$remote) {
+ return array($host, $data['port'], $remote);
+ });
+ })->then(function ($target) use ($that, $stream) {
+ return $that->connectTarget($stream, $target);
+ }, function($error) use ($stream) {
+ throw new UnexpectedValueException('SOCKS5 protocol error', $error->getCode(), $error);
+ })->then(function (ConnectionInterface $remote) use ($stream) {
+ $stream->write(pack('C4Nn', 0x05, 0x00, 0x00, 0x01, 0, 0));
+
+ return $remote;
+ }, function(Exception $error) use ($stream){
+ $stream->write(pack('C4Nn', 0x05, $error->getCode() === 0 ? Server::ERROR_GENERAL : $error->getCode(), 0x00, 0x01, 0, 0));
+
+ throw $error;
+ });
+ }
+
+ public function connectTarget(ConnectionInterface $stream, array $target)
+ {
+ $uri = $target[0];
+ if (strpos($uri, ':') !== false) {
+ $uri = '[' . $uri . ']';
+ }
+ $uri .= ':' . $target[1];
+
+ // validate URI so a string hostname can not pass excessive URI parts
+ $parts = parse_url('tcp://' . $uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || count($parts) !== 3) {
+ return Promise\reject(new InvalidArgumentException('Invalid target URI given'));
+ }
+
+ if (isset($target[2])) {
+ $uri .= '?source=' . rawurlencode($target[2]);
+ }
+
+ $stream->emit('target', $target);
+ $that = $this;
+ $connecting = $this->connector->connect($uri);
+
+ $stream->on('close', function () use ($connecting) {
+ $connecting->cancel();
+ });
+
+ return $connecting->then(function (ConnectionInterface $remote) use ($stream, $that) {
+ $stream->pipe($remote, array('end'=>false));
+ $remote->pipe($stream, array('end'=>false));
+
+ // remote end closes connection => stop reading from local end, try to flush buffer to local and disconnect local
+ $remote->on('end', function() use ($stream, $that) {
+ $stream->emit('shutdown', array('remote', null));
+ $that->endConnection($stream);
+ });
+
+ // local end closes connection => stop reading from remote end, try to flush buffer to remote and disconnect remote
+ $stream->on('end', function() use ($remote, $that) {
+ $that->endConnection($remote);
+ });
+
+ // set bigger buffer size of 100k to improve performance
+ $stream->bufferSize = $remote->bufferSize = 100 * 1024 * 1024;
+
+ return $remote;
+ }, function(Exception $error) {
+ // default to general/unknown error
+ $code = Server::ERROR_GENERAL;
+
+ // map common socket error conditions to limited list of SOCKS error codes
+ if ((defined('SOCKET_EACCES') && $error->getCode() === SOCKET_EACCES) || $error->getCode() === 13) {
+ $code = Server::ERROR_NOT_ALLOWED_BY_RULESET;
+ } elseif ((defined('SOCKET_EHOSTUNREACH') && $error->getCode() === SOCKET_EHOSTUNREACH) || $error->getCode() === 113) {
+ $code = Server::ERROR_HOST_UNREACHABLE;
+ } elseif ((defined('SOCKET_ENETUNREACH') && $error->getCode() === SOCKET_ENETUNREACH) || $error->getCode() === 101) {
+ $code = Server::ERROR_NETWORK_UNREACHABLE;
+ } elseif ((defined('SOCKET_ECONNREFUSED') && $error->getCode() === SOCKET_ECONNREFUSED) || $error->getCode() === 111 || $error->getMessage() === 'Connection refused') {
+ // Socket component does not currently assign an error code for this, so we have to resort to checking the exception message
+ $code = Server::ERROR_CONNECTION_REFUSED;
+ } elseif ((defined('SOCKET_ETIMEDOUT') && $error->getCode() === SOCKET_ETIMEDOUT) || $error->getCode() === 110 || $error instanceof TimeoutException) {
+ // Socket component does not currently assign an error code for this, but we can rely on the TimeoutException
+ $code = Server::ERROR_TTL;
+ }
+
+ throw new UnexpectedValueException('Unable to connect to remote target', $code, $error);
+ });
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/src/StreamReader.php b/src/mpg25-instagram-api/vendor/clue/socks-react/src/StreamReader.php
new file mode 100755
index 0000000..6dbf51c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/src/StreamReader.php
@@ -0,0 +1,149 @@
+buffer .= $data;
+
+ do {
+ $current = reset($this->queue);
+
+ if ($current === false) {
+ break;
+ }
+
+ /* @var $current Closure */
+
+ $ret = $current($this->buffer);
+
+ if ($ret === self::RET_INCOMPLETE) {
+ // current is incomplete, so wait for further data to arrive
+ break;
+ } else {
+ // current is done, remove from list and continue with next
+ array_shift($this->queue);
+ }
+ } while (true);
+ }
+
+ public function readBinary($structure)
+ {
+ $length = 0;
+ $unpack = '';
+ foreach ($structure as $name=>$format) {
+ if ($length !== 0) {
+ $unpack .= '/';
+ }
+ $unpack .= $format . $name;
+
+ if ($format === 'C') {
+ ++$length;
+ } else if ($format === 'n') {
+ $length += 2;
+ } else if ($format === 'N') {
+ $length += 4;
+ } else {
+ throw new InvalidArgumentException('Invalid format given');
+ }
+ }
+
+ return $this->readLength($length)->then(function ($response) use ($unpack) {
+ return unpack($unpack, $response);
+ });
+ }
+
+ public function readLength($bytes)
+ {
+ $deferred = new Deferred();
+
+ $this->readBufferCallback(function (&$buffer) use ($bytes, $deferred) {
+ if (strlen($buffer) >= $bytes) {
+ $deferred->resolve((string)substr($buffer, 0, $bytes));
+ $buffer = (string)substr($buffer, $bytes);
+
+ return StreamReader::RET_DONE;
+ }
+ });
+
+ return $deferred->promise();
+ }
+
+ public function readByte()
+ {
+ return $this->readBinary(array(
+ 'byte' => 'C'
+ ))->then(function ($data) {
+ return $data['byte'];
+ });
+ }
+
+ public function readByteAssert($expect)
+ {
+ return $this->readByte()->then(function ($byte) use ($expect) {
+ if ($byte !== $expect) {
+ throw new UnexpectedValueException('Unexpected byte encountered');
+ }
+ return $byte;
+ });
+ }
+
+ public function readStringNull()
+ {
+ $deferred = new Deferred();
+ $string = '';
+
+ $that = $this;
+ $readOne = function () use (&$readOne, $that, $deferred, &$string) {
+ $that->readByte()->then(function ($byte) use ($deferred, &$string, $readOne) {
+ if ($byte === 0x00) {
+ $deferred->resolve($string);
+ } else {
+ $string .= chr($byte);
+ $readOne();
+ }
+ });
+ };
+ $readOne();
+
+ return $deferred->promise();
+ }
+
+ public function readBufferCallback(/* callable */ $callable)
+ {
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException('Given function must be callable');
+ }
+
+ if ($this->queue) {
+ $this->queue []= $callable;
+ } else {
+ $this->queue = array($callable);
+
+ if ($this->buffer !== '') {
+ // this is the first element in the queue and the buffer is filled => trigger write procedure
+ $this->write('');
+ }
+ }
+ }
+
+ public function getBuffer()
+ {
+ return $this->buffer;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/tests/ClientTest.php b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/ClientTest.php
new file mode 100755
index 0000000..e304901
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/ClientTest.php
@@ -0,0 +1,403 @@
+loop = React\EventLoop\Factory::create();
+ $this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->client = new Client('127.0.0.1:1080', $this->connector);
+ }
+
+ public function testCtorAcceptsUriWithHostAndPort()
+ {
+ $client = new Client('127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithScheme()
+ {
+ $client = new Client('socks://127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithHostOnlyAssumesDefaultPort()
+ {
+ $client = new Client('127.0.0.1', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSecureScheme()
+ {
+ $client = new Client('sockss://127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSecureVersionScheme()
+ {
+ $client = new Client('socks5s://127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSocksUnixScheme()
+ {
+ $client = new Client('socks+unix:///tmp/socks.socket', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSocks5UnixScheme()
+ {
+ $client = new Client('socks5+unix:///tmp/socks.socket', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testCtorThrowsForInvalidUri()
+ {
+ new Client('////', $this->connector);
+ }
+
+ public function testValidAuthFromUri()
+ {
+ $this->client = new Client('username:password@127.0.0.1', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidAuthInformation()
+ {
+ new Client(str_repeat('a', 256) . ':test@127.0.0.1', $this->connector);
+ }
+
+ public function testValidAuthAndVersionFromUri()
+ {
+ $this->client = new Client('socks5://username:password@127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidCanNotSetAuthenticationForSocks4Uri()
+ {
+ $this->client = new Client('socks4://username:password@127.0.0.1:9050', $this->connector);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidProtocolVersion()
+ {
+ $this->client = new Client('socks3://127.0.0.1:9050', $this->connector);
+ }
+
+ public function testCreateWillConnectToProxy()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=localhost')->willReturn($promise);
+
+ $promise = $this->client->connect('localhost:80');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testCreateWillConnectToProxyWithFullUri()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080/?hostname=test#fragment')->willReturn($promise);
+
+ $promise = $this->client->connect('localhost:80/?hostname=test#fragment');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testCreateWithInvalidHostDoesNotConnect()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $promise = $this->client->connect(str_repeat('a', '256') . ':80');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testCreateWithInvalidPortDoesNotConnect()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $promise = $this->client->connect('some-random-site:some-random-port');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectorRejectsWillRejectConnection()
+ {
+ $promise = \React\Promise\reject(new RuntimeException());
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
+ }
+
+ public function testCancelConnectionDuringConnectionWillCancelConnection()
+ {
+ $promise = new Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+ $promise->cancel();
+
+ $this->expectPromiseReject($promise);
+ }
+
+ public function testCancelConnectionDuringSessionWillCloseStream()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = new Promise(function ($resolve) use ($stream) { $resolve($stream); });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNABORTED));
+ }
+
+ public function testEmitConnectionCloseDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('close');
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNRESET));
+ }
+
+ public function testEmitConnectionErrorDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('error', array(new RuntimeException()));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EIO));
+ }
+
+ public function testEmitInvalidSocks4DataDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testEmitInvalidSocks5DataDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testEmitSocks5DataErrorDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x01\x00\x00"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
+ }
+
+ public function testEmitSocks5DataInvalidAddressTypeWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x00"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testEmitSocks5DataIpv6AddressWillResolveConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->never())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=%3A%3A1')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('[::1]:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x04" . inet_pton('::1') . "\x00\x50"));
+
+ $promise->then($this->expectCallableOnce());
+ }
+
+ public function testEmitSocks5DataHostnameAddressWillResolveConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->never())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x03\x0Agoogle.com\x00\x50"));
+
+ $promise->then($this->expectCallableOnce());
+ }
+
+ public function provideConnectionErrors()
+ {
+ return array(
+ array(
+ Server::ERROR_GENERAL,
+ SOCKET_ECONNREFUSED
+ ),
+ array(
+ Server::ERROR_NOT_ALLOWED_BY_RULESET,
+ SOCKET_EACCES
+ ),
+ array(
+ Server::ERROR_NETWORK_UNREACHABLE,
+ SOCKET_ENETUNREACH
+ ),
+ array(
+ Server::ERROR_HOST_UNREACHABLE,
+ SOCKET_EHOSTUNREACH
+ ),
+ array(
+ Server::ERROR_CONNECTION_REFUSED,
+ SOCKET_ECONNREFUSED
+ ),
+ array(
+ Server::ERROR_TTL,
+ SOCKET_ETIMEDOUT
+ ),
+ array(
+ Server::ERROR_COMMAND_UNSUPPORTED,
+ SOCKET_EPROTO
+ ),
+ array(
+ Server::ERROR_ADDRESS_UNSUPPORTED,
+ SOCKET_EPROTO
+ ),
+ array(
+ 200,
+ SOCKET_ECONNREFUSED
+ )
+ );
+ }
+
+ /**
+ * @dataProvider provideConnectionErrors
+ * @param int $error
+ * @param int $expectedCode
+ */
+ public function testEmitSocks5DataErrorMapsToExceptionCode($error, $expectedCode)
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05" . chr($error) . "\x00\x00"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode($expectedCode));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/tests/FunctionalTest.php b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/FunctionalTest.php
new file mode 100755
index 0000000..2f1190e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/FunctionalTest.php
@@ -0,0 +1,437 @@
+loop = React\EventLoop\Factory::create();
+
+ $socket = new React\Socket\Server(0, $this->loop);
+ $address = $socket->getAddress();
+ if (strpos($address, '://') === false) {
+ $address = 'tcp://' . $address;
+ }
+ $this->port = parse_url($address, PHP_URL_PORT);
+ $this->assertNotEquals(0, $this->port);
+
+ $this->server = new Server($this->loop, $socket);
+ $this->connector = new TcpConnector($this->loop);
+ $this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
+ }
+
+ /** @group internet */
+ public function testConnection()
+ {
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionInvalid()
+ {
+ $this->assertRejectPromise($this->client->connect('www.google.com.invalid:80'));
+ }
+
+ public function testConnectionWithIpViaSocks4()
+ {
+ $this->server->setProtocolVersion('4');
+
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('127.0.0.1:' . $this->port));
+ }
+
+ /** @group internet */
+ public function testConnectionWithHostnameViaSocks4Fails()
+ {
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionWithInvalidPortFails()
+ {
+ $this->assertRejectPromise($this->client->connect('www.google.com:100000'));
+ }
+
+ public function testConnectionWithIpv6ViaSocks4Fails()
+ {
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('[::1]:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocks4a()
+ {
+ $this->server->setProtocolVersion('4a');
+ $this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocks5()
+ {
+ $this->server->setProtocolVersion(5);
+ $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocksOverTls()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ )));
+ $this->server = new Server($this->loop, $socket);
+
+ $this->connector = new Connector($this->loop, array('tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ )));
+ $this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /**
+ * @group internet
+ * @requires PHP 5.6
+ */
+ public function testConnectionSocksOverTlsUsesPeerNameFromSocksUri()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ )));
+ $this->server = new Server($this->loop, $socket);
+
+ $this->connector = new Connector($this->loop, array('tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => true
+ )));
+ $this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocksOverUnix()
+ {
+ if (!in_array('unix', stream_get_transports())) {
+ $this->markTestSkipped('System does not support unix:// scheme');
+ }
+
+ $path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
+ $socket = new UnixServer($path, $this->loop);
+ $this->server = new Server($this->loop, $socket);
+
+ $this->connector = new Connector($this->loop);
+ $this->client = new Client('socks+unix://' . $path, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+
+ unlink($path);
+ }
+
+ /** @group internet */
+ public function testConnectionSocks5OverUnix()
+ {
+ if (!in_array('unix', stream_get_transports())) {
+ $this->markTestSkipped('System does not support unix:// scheme');
+ }
+
+ $path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
+ $socket = new UnixServer($path, $this->loop);
+ $this->server = new Server($this->loop, $socket);
+ $this->server->setProtocolVersion(5);
+
+ $this->connector = new Connector($this->loop);
+ $this->client = new Client('socks5+unix://' . $path, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+
+ unlink($path);
+ }
+
+ /** @group internet */
+ public function testConnectionSocksWithAuthenticationOverUnix()
+ {
+ if (!in_array('unix', stream_get_transports())) {
+ $this->markTestSkipped('System does not support unix:// scheme');
+ }
+
+ $path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
+ $socket = new UnixServer($path, $this->loop);
+ $this->server = new Server($this->loop, $socket);
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->connector = new Connector($this->loop);
+ $this->client = new Client('socks+unix://name:pass@' . $path, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+
+ unlink($path);
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationFromUri()
+ {
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationCallback()
+ {
+ $called = 0;
+ $that = $this;
+ $this->server->setAuth(function ($name, $pass, $remote) use ($that, &$called) {
+ ++$called;
+ $that->assertEquals('name', $name);
+ $that->assertEquals('pass', $pass);
+ $that->assertStringStartsWith('socks5://name:pass@127.0.0.1:', $remote);
+
+ return true;
+ });
+
+ $this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ $this->assertEquals(1, $called);
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationCallbackWillNotBeInvokedIfClientsSendsNoAuth()
+ {
+ $called = 0;
+ $this->server->setAuth(function () use (&$called) {
+ ++$called;
+
+ return true;
+ });
+
+ $this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'));
+ $this->assertEquals(0, $called);
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationFromUriEncoded()
+ {
+ $this->server->setAuthArray(array('name' => 'p@ss:w0rd'));
+
+ $this->client = new Client(rawurlencode('name') . ':' . rawurlencode('p@ss:w0rd') . '@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationFromUriWithOnlyUserAndNoPassword()
+ {
+ $this->server->setAuthArray(array('empty' => ''));
+
+ $this->client = new Client('empty@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationEmptyPassword()
+ {
+ $this->server->setAuthArray(array('user' => ''));
+ $this->client = new Client('user@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationUnused()
+ {
+ $this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ public function testConnectionInvalidProtocolDoesNotMatchSocks5()
+ {
+ $this->server->setProtocolVersion(5);
+ $this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
+ }
+
+ public function testConnectionInvalidProtocolDoesNotMatchSocks4()
+ {
+ $this->server->setProtocolVersion(4);
+ $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
+ }
+
+ public function testConnectionInvalidNoAuthentication()
+ {
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
+ }
+
+ public function testConnectionInvalidAuthenticationMismatch()
+ {
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
+ }
+
+ /** @group internet */
+ public function testConnectorOkay()
+ {
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectorInvalidDomain()
+ {
+ $this->assertRejectPromise($this->client->connect('www.google.commm:80'));
+ }
+
+ /** @group internet */
+ public function testConnectorCancelConnection()
+ {
+ $promise = $this->client->connect('www.google.com:80');
+ $promise->cancel();
+
+ $this->assertRejectPromise($promise);
+ }
+
+ /** @group internet */
+ public function testConnectorInvalidUnboundPortTimeout()
+ {
+ // time out the connection attempt in 0.1s (as expected)
+ $tcp = new TimeoutConnector($this->client, 0.1, $this->loop);
+
+ $this->assertRejectPromise($tcp->connect('www.google.com:8080'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorOkay()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop);
+
+ $this->assertResolveStream($ssl->connect('www.google.com:443'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorToBadSslWithVerifyFails()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => true));
+
+ $this->assertRejectPromise($ssl->connect('self-signed.badssl.com:443'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorToBadSslWithoutVerifyWorks()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => false));
+
+ $this->assertResolveStream($ssl->connect('self-signed.badssl.com:443'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorInvalidPlaintextIsNotSsl()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop);
+
+ $this->assertRejectPromise($ssl->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorInvalidUnboundPortTimeout()
+ {
+ $ssl = new SecureConnector($this->client, $this->loop);
+
+ // time out the connection attempt in 0.1s (as expected)
+ $ssl = new TimeoutConnector($ssl, 0.1, $this->loop);
+
+ $this->assertRejectPromise($ssl->connect('www.google.com:8080'));
+ }
+
+ private function assertResolveStream($promise)
+ {
+ $this->expectPromiseResolve($promise);
+
+ $promise->then(function ($stream) {
+ $stream->close();
+ });
+
+ Block\await($promise, $this->loop, 2.0);
+ }
+
+ private function assertRejectPromise($promise, $message = null, $code = null)
+ {
+ $this->expectPromiseReject($promise);
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Exception');
+ if ($message !== null) {
+ $this->expectExceptionMessage($message);
+ }
+ if ($code !== null) {
+ $this->expectExceptionCode($code);
+ }
+ } else {
+ $this->setExpectedException('Exception', $message, $code);
+ }
+
+ Block\await($promise, $this->loop, 2.0);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/tests/ServerTest.php b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/ServerTest.php
new file mode 100755
index 0000000..5c73845
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/ServerTest.php
@@ -0,0 +1,428 @@
+getMockBuilder('React\Socket\ServerInterface')
+ ->getMock();
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')
+ ->getMock();
+
+ $this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')
+ ->getMock();
+
+ $this->server = new Server($loop, $socket, $this->connector);
+ }
+
+ public function testSetProtocolVersion()
+ {
+ $this->server->setProtocolVersion(4);
+ $this->server->setProtocolVersion('4a');
+ $this->server->setProtocolVersion(5);
+ $this->server->setProtocolVersion(null);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetInvalidProtocolVersion()
+ {
+ $this->server->setProtocolVersion(6);
+ }
+
+ public function testSetAuthArray()
+ {
+ $this->server->setAuthArray(array());
+
+ $this->server->setAuthArray(array(
+ 'name1' => 'password1',
+ 'name2' => 'password2'
+ ));
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetAuthInvalid()
+ {
+ $this->server->setAuth(true);
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testUnableToSetAuthIfProtocolDoesNotSupportAuth()
+ {
+ $this->server->setProtocolVersion(4);
+
+ $this->server->setAuthArray(array());
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testUnableToSetProtocolWhichDoesNotSupportAuth()
+ {
+ $this->server->setAuthArray(array());
+
+ // this is okay
+ $this->server->setProtocolVersion(5);
+
+ $this->server->setProtocolVersion(4);
+ }
+
+ public function testConnectWillCreateConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectWillCreateConnectionWithSourceUri()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80, 'socks5://10.20.30.40:5060'));
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectWillRejectIfConnectionFails()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function ($_, $reject) { $reject(new \RuntimeException()); });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectWillCancelConnectionIfStreamCloses()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close'))->getMock();
+
+ $promise = new Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $stream->emit('close');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectWillAbortIfPromiseIsCanceled()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function provideConnectionErrors()
+ {
+ return array(
+ array(
+ new RuntimeException('', SOCKET_EACCES),
+ Server::ERROR_NOT_ALLOWED_BY_RULESET
+ ),
+ array(
+ new RuntimeException('', SOCKET_ENETUNREACH),
+ Server::ERROR_NETWORK_UNREACHABLE
+ ),
+ array(
+ new RuntimeException('', SOCKET_EHOSTUNREACH),
+ Server::ERROR_HOST_UNREACHABLE,
+ ),
+ array(
+ new RuntimeException('', SOCKET_ECONNREFUSED),
+ Server::ERROR_CONNECTION_REFUSED
+ ),
+ array(
+ new RuntimeException('Connection refused'),
+ Server::ERROR_CONNECTION_REFUSED
+ ),
+ array(
+ new RuntimeException('', SOCKET_ETIMEDOUT),
+ Server::ERROR_TTL
+ ),
+ array(
+ new TimeoutException(1.0),
+ Server::ERROR_TTL
+ ),
+ array(
+ new RuntimeException(),
+ Server::ERROR_GENERAL
+ )
+ );
+ }
+
+ /**
+ * @dataProvider provideConnectionErrors
+ * @param Exception $error
+ * @param int $expectedCode
+ */
+ public function testConnectWillReturnMappedSocks5ErrorCodeFromConnector($error, $expectedCode)
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = \React\Promise\reject($error);
+
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $code = null;
+ $promise->then(null, function ($error) use (&$code) {
+ $code = $error->getCode();
+ });
+
+ $this->assertEquals($expectedCode, $code);
+ }
+
+ public function testHandleSocksConnectionWillEndOnInvalidData()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+ $connection->expects($this->once())->method('pause');
+ $connection->expects($this->once())->method('end');
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array('asdasdasdasdasd'));
+ }
+
+ public function testHandleSocks4ConnectionWithIpv4WillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithHostnameWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithHostnameAndSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithSecureTlsSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithInvalidHostnameWillNotEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "tls://example.com:80?" . "\x00"));
+ }
+
+ public function testHandleSocks5ConnectionWithIpv4WillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithIpv4AndSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithSecureTlsIpv4AndSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithIpv6WillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('[::1]:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x04" . inet_pton('::1') . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithHostnameWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithConnectorRefusedWillReturnReturnRefusedError()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = \React\Promise\reject(new RuntimeException('Connection refused'));
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x05" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
+ }
+
+ public function testHandleSocks5UdpCommandWillNotEstablishOutgoingConnectionAndReturnCommandError()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $this->server->onConnection($connection);
+
+ $connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x07" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x03\x00\x03\x0B" . "example.com" . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithInvalidHostnameWillNotEstablishOutgoingConnectionAndReturnGeneralError()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $this->server->onConnection($connection);
+
+ $connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x01" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x15" . "tls://example.com:80?" . "\x00\x50"));
+ }
+
+ public function testHandleSocksConnectionWillCancelOutputConnectionIfIncomingCloses()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
+ $connection->emit('close');
+ }
+
+ public function testUnsetAuth()
+ {
+ $this->server->unsetAuth();
+ $this->server->unsetAuth();
+
+ $this->assertTrue(true);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/tests/StreamReaderTest.php b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/StreamReaderTest.php
new file mode 100755
index 0000000..a2a0d95
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/StreamReaderTest.php
@@ -0,0 +1,82 @@
+reader = new StreamReader();
+ }
+
+ public function testReadByteAssertCorrect()
+ {
+ $this->reader->readByteAssert(0x01)->then($this->expectCallableOnce(0x01));
+
+ $this->reader->write("\x01");
+ }
+
+ public function testReadByteAssertInvalid()
+ {
+ $this->reader->readByteAssert(0x02)->then(null, $this->expectCallableOnce());
+
+ $this->reader->write("\x03");
+ }
+
+ public function testReadStringNull()
+ {
+ $this->reader->readStringNull()->then($this->expectCallableOnce('hello'));
+
+ $this->reader->write("hello\x00");
+ }
+
+ public function testReadStringLength()
+ {
+ $this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
+
+ $this->reader->write('he');
+ $this->reader->write('ll');
+ $this->reader->write('o ');
+
+ $this->assertEquals(' ', $this->reader->getBuffer());
+ }
+
+ public function testReadBuffered()
+ {
+ $this->reader->write('hello');
+
+ $this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
+
+ $this->assertEquals('', $this->reader->getBuffer());
+ }
+
+ public function testSequence()
+ {
+ $this->reader->readByte()->then($this->expectCallableOnce(ord('h')));
+ $this->reader->readByteAssert(ord('e'))->then($this->expectCallableOnce(ord('e')));
+ $this->reader->readLength(4)->then($this->expectCallableOnce('llo '));
+ $this->reader->readBinary(array('w'=>'C', 'o' => 'C'))->then($this->expectCallableOnce(array('w' => ord('w'), 'o' => ord('o'))));
+
+ $this->reader->write('hello world');
+
+ $this->assertEquals('rld', $this->reader->getBuffer());
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidStructure()
+ {
+ $this->reader->readBinary(array('invalid' => 'y'));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidCallback()
+ {
+ $this->reader->readBufferCallback(array());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/clue/socks-react/tests/bootstrap.php b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/bootstrap.php
new file mode 100755
index 0000000..029c6b9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/clue/socks-react/tests/bootstrap.php
@@ -0,0 +1,103 @@
+createCallableMock();
+
+
+ if (func_num_args() > 0) {
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->equalTo(func_get_arg(0)));
+ } else {
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+ }
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWithExceptionCode($code)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->callback(function ($e) use ($code) {
+ return $e->getCode() === $code;
+ }));
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceParameter($type)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf($type));
+
+ return $mock;
+ }
+
+ /**
+ * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
+ */
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('CallableStub')->getMock();
+ }
+
+ protected function expectPromiseResolve($promise)
+ {
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $that = $this;
+ $promise->then(null, function($error) use ($that) {
+ $that->assertNull($error);
+ $that->fail('promise rejected');
+ });
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ return $promise;
+ }
+
+ protected function expectPromiseReject($promise)
+ {
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $that = $this;
+ $promise->then(function($value) use ($that) {
+ $that->assertNull($value);
+ $that->fail('promise resolved');
+ });
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ return $promise;
+ }
+}
+
+class CallableStub
+{
+ public function __invoke()
+ {
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/composer/ClassLoader.php b/src/mpg25-instagram-api/vendor/composer/ClassLoader.php
new file mode 100755
index 0000000..fce8549
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/composer/ClassLoader.php
@@ -0,0 +1,445 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+ private $apcuPrefix;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/src/mpg25-instagram-api/vendor/composer/LICENSE b/src/mpg25-instagram-api/vendor/composer/LICENSE
new file mode 100755
index 0000000..4b615a3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/composer/LICENSE
@@ -0,0 +1,56 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: Composer
+Upstream-Contact: Jordi Boggiano
+Source: https://github.com/composer/composer
+
+Files: *
+Copyright: 2016, Nils Adermann
+ 2016, Jordi Boggiano
+License: Expat
+
+Files: src/Composer/Util/TlsHelper.php
+Copyright: 2016, Nils Adermann
+ 2016, Jordi Boggiano
+ 2013, Evan Coury
+License: Expat and BSD-2-Clause
+
+License: BSD-2-Clause
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+ .
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ .
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is furnished
+ to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/composer/autoload_classmap.php b/src/mpg25-instagram-api/vendor/composer/autoload_classmap.php
new file mode 100755
index 0000000..7a91153
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+ $vendorDir . '/react/promise/src/functions_include.php',
+ '972fda704d680a3a53c68e34e193cb22' => $vendorDir . '/react/promise-timer/src/functions_include.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
+ 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
+ 'ebf8799635f67b5d7248946fe2154f4a' => $vendorDir . '/ringcentral/psr7/src/functions_include.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
+);
diff --git a/src/mpg25-instagram-api/vendor/composer/autoload_namespaces.php b/src/mpg25-instagram-api/vendor/composer/autoload_namespaces.php
new file mode 100755
index 0000000..02066fb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,10 @@
+ array($vendorDir . '/evenement/evenement/src'),
+);
diff --git a/src/mpg25-instagram-api/vendor/composer/autoload_psr4.php b/src/mpg25-instagram-api/vendor/composer/autoload_psr4.php
new file mode 100755
index 0000000..5d4f0c7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/composer/autoload_psr4.php
@@ -0,0 +1,32 @@
+ array($vendorDir . '/winbox/args/src'),
+ 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
+ 'RingCentral\\Psr7\\' => array($vendorDir . '/ringcentral/psr7/src'),
+ 'React\\Stream\\' => array($vendorDir . '/react/stream/src'),
+ 'React\\Socket\\' => array($vendorDir . '/react/socket/src'),
+ 'React\\Promise\\Timer\\' => array($vendorDir . '/react/promise-timer/src'),
+ 'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
+ 'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
+ 'React\\Dns\\' => array($vendorDir . '/react/dns/src'),
+ 'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
+ 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
+ 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
+ 'LazyJsonMapper\\' => array($vendorDir . '/lazyjsonmapper/lazyjsonmapper/src'),
+ 'InstagramAPI\\' => array($vendorDir . '/mgp25/instagram-php/src'),
+ 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
+ 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
+ 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
+ 'GetOptionKit\\' => array($vendorDir . '/corneltek/getoptionkit/src'),
+ 'Fbns\\Client\\' => array($vendorDir . '/valga/fbns-react/src'),
+ 'Clue\\React\\Socks\\' => array($vendorDir . '/clue/socks-react/src'),
+ 'Clue\\React\\HttpProxy\\' => array($vendorDir . '/clue/http-proxy-react/src'),
+ 'BinSoul\\Net\\Mqtt\\Client\\React\\' => array($vendorDir . '/binsoul/net-mqtt-client-react/src'),
+ 'BinSoul\\Net\\Mqtt\\' => array($vendorDir . '/binsoul/net-mqtt/src'),
+);
diff --git a/src/mpg25-instagram-api/vendor/composer/autoload_real.php b/src/mpg25-instagram-api/vendor/composer/autoload_real.php
new file mode 100755
index 0000000..6a318f4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/composer/autoload_real.php
@@ -0,0 +1,70 @@
+= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require_once __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInit331ae81537359437b02a96bc74f11b80::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ if ($useStaticLoader) {
+ $includeFiles = Composer\Autoload\ComposerStaticInit331ae81537359437b02a96bc74f11b80::$files;
+ } else {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ }
+ foreach ($includeFiles as $fileIdentifier => $file) {
+ composerRequire331ae81537359437b02a96bc74f11b80($fileIdentifier, $file);
+ }
+
+ return $loader;
+ }
+}
+
+function composerRequire331ae81537359437b02a96bc74f11b80($fileIdentifier, $file)
+{
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ require $file;
+
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/composer/autoload_static.php b/src/mpg25-instagram-api/vendor/composer/autoload_static.php
new file mode 100755
index 0000000..5c67b20
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/composer/autoload_static.php
@@ -0,0 +1,189 @@
+ __DIR__ . '/..' . '/react/promise/src/functions_include.php',
+ '972fda704d680a3a53c68e34e193cb22' => __DIR__ . '/..' . '/react/promise-timer/src/functions_include.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
+ 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
+ 'ebf8799635f67b5d7248946fe2154f4a' => __DIR__ . '/..' . '/ringcentral/psr7/src/functions_include.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
+ );
+
+ public static $prefixLengthsPsr4 = array (
+ 'W' =>
+ array (
+ 'Winbox\\' => 7,
+ ),
+ 'S' =>
+ array (
+ 'Symfony\\Component\\Process\\' => 26,
+ ),
+ 'R' =>
+ array (
+ 'RingCentral\\Psr7\\' => 17,
+ 'React\\Stream\\' => 13,
+ 'React\\Socket\\' => 13,
+ 'React\\Promise\\Timer\\' => 20,
+ 'React\\Promise\\' => 14,
+ 'React\\EventLoop\\' => 16,
+ 'React\\Dns\\' => 10,
+ 'React\\Cache\\' => 12,
+ ),
+ 'P' =>
+ array (
+ 'Psr\\Log\\' => 8,
+ 'Psr\\Http\\Message\\' => 17,
+ ),
+ 'L' =>
+ array (
+ 'LazyJsonMapper\\' => 15,
+ ),
+ 'I' =>
+ array (
+ 'InstagramAPI\\' => 13,
+ ),
+ 'G' =>
+ array (
+ 'GuzzleHttp\\Psr7\\' => 16,
+ 'GuzzleHttp\\Promise\\' => 19,
+ 'GuzzleHttp\\' => 11,
+ 'GetOptionKit\\' => 13,
+ ),
+ 'F' =>
+ array (
+ 'Fbns\\Client\\' => 12,
+ ),
+ 'C' =>
+ array (
+ 'Clue\\React\\Socks\\' => 17,
+ 'Clue\\React\\HttpProxy\\' => 21,
+ ),
+ 'B' =>
+ array (
+ 'BinSoul\\Net\\Mqtt\\Client\\React\\' => 30,
+ 'BinSoul\\Net\\Mqtt\\' => 17,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Winbox\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/winbox/args/src',
+ ),
+ 'Symfony\\Component\\Process\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/process',
+ ),
+ 'RingCentral\\Psr7\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ringcentral/psr7/src',
+ ),
+ 'React\\Stream\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/stream/src',
+ ),
+ 'React\\Socket\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/socket/src',
+ ),
+ 'React\\Promise\\Timer\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/promise-timer/src',
+ ),
+ 'React\\Promise\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/promise/src',
+ ),
+ 'React\\EventLoop\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/event-loop/src',
+ ),
+ 'React\\Dns\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/dns/src',
+ ),
+ 'React\\Cache\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/cache/src',
+ ),
+ 'Psr\\Log\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
+ ),
+ 'Psr\\Http\\Message\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/http-message/src',
+ ),
+ 'LazyJsonMapper\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/lazyjsonmapper/lazyjsonmapper/src',
+ ),
+ 'InstagramAPI\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/mgp25/instagram-php/src',
+ ),
+ 'GuzzleHttp\\Psr7\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
+ ),
+ 'GuzzleHttp\\Promise\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
+ ),
+ 'GuzzleHttp\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
+ ),
+ 'GetOptionKit\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/corneltek/getoptionkit/src',
+ ),
+ 'Fbns\\Client\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/valga/fbns-react/src',
+ ),
+ 'Clue\\React\\Socks\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/clue/socks-react/src',
+ ),
+ 'Clue\\React\\HttpProxy\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/clue/http-proxy-react/src',
+ ),
+ 'BinSoul\\Net\\Mqtt\\Client\\React\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/binsoul/net-mqtt-client-react/src',
+ ),
+ 'BinSoul\\Net\\Mqtt\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/binsoul/net-mqtt/src',
+ ),
+ );
+
+ public static $prefixesPsr0 = array (
+ 'E' =>
+ array (
+ 'Evenement' =>
+ array (
+ 0 => __DIR__ . '/..' . '/evenement/evenement/src',
+ ),
+ ),
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit331ae81537359437b02a96bc74f11b80::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit331ae81537359437b02a96bc74f11b80::$prefixDirsPsr4;
+ $loader->prefixesPsr0 = ComposerStaticInit331ae81537359437b02a96bc74f11b80::$prefixesPsr0;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/composer/installed.json b/src/mpg25-instagram-api/vendor/composer/installed.json
new file mode 100755
index 0000000..46699b3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/composer/installed.json
@@ -0,0 +1,1328 @@
+[
+ {
+ "name": "binsoul/net-mqtt",
+ "version": "0.2.1",
+ "version_normalized": "0.2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/binsoul/net-mqtt.git",
+ "reference": "286b28e6014739b19e0e7ce0cd5871cdd0cef9b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/binsoul/net-mqtt/zipball/286b28e6014739b19e0e7ce0cd5871cdd0cef9b3",
+ "reference": "286b28e6014739b19e0e7ce0cd5871cdd0cef9b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~5.6|~7.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~1.0",
+ "phpunit/phpunit": "~4.0||~5.0"
+ },
+ "time": "2017-04-03T20:17:02+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "description": "MQTT protocol implementation",
+ "homepage": "https://github.com/binsoul/net-mqtt",
+ "keywords": [
+ "mqtt",
+ "net"
+ ]
+ },
+ {
+ "name": "binsoul/net-mqtt-client-react",
+ "version": "0.3.2",
+ "version_normalized": "0.3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/binsoul/net-mqtt-client-react.git",
+ "reference": "6a80fea50e927ebb8bb8a631ea7903c22742ded5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/binsoul/net-mqtt-client-react/zipball/6a80fea50e927ebb8bb8a631ea7903c22742ded5",
+ "reference": "6a80fea50e927ebb8bb8a631ea7903c22742ded5",
+ "shasum": ""
+ },
+ "require": {
+ "binsoul/net-mqtt": "~0.2",
+ "php": "~5.6|~7.0",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~1.0",
+ "phpunit/phpunit": "~4.0||~5.0"
+ },
+ "time": "2017-08-20T08:06:53+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\Client\\React\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "description": "Asynchronous MQTT client built on React",
+ "homepage": "https://github.com/binsoul/net-mqtt-client-react",
+ "keywords": [
+ "client",
+ "mqtt",
+ "net"
+ ]
+ },
+ {
+ "name": "clue/http-proxy-react",
+ "version": "v1.3.0",
+ "version_normalized": "1.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/clue/php-http-proxy-react.git",
+ "reference": "eeff725640ed53386a6adb05ffdbfc2837404fdf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/clue/php-http-proxy-react/zipball/eeff725640ed53386a6adb05ffdbfc2837404fdf",
+ "reference": "eeff725640ed53386a6adb05ffdbfc2837404fdf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/promise": " ^2.1 || ^1.2.1",
+ "react/socket": "^1.0 || ^0.8.4",
+ "ringcentral/psr7": "^1.2"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.1",
+ "phpunit/phpunit": "^5.0 || ^4.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
+ },
+ "time": "2018-02-13T16:31:32+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Clue\\React\\HttpProxy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "description": "Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
+ "homepage": "https://github.com/clue/php-http-proxy-react",
+ "keywords": [
+ "async",
+ "connect",
+ "http",
+ "proxy",
+ "reactphp"
+ ]
+ },
+ {
+ "name": "clue/socks-react",
+ "version": "v0.8.7",
+ "version_normalized": "0.8.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/clue/php-socks-react.git",
+ "reference": "0fcd6f2f506918ff003f1b995c6e78443f26e8ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/clue/php-socks-react/zipball/0fcd6f2f506918ff003f1b995c6e78443f26e8ea",
+ "reference": "0fcd6f2f506918ff003f1b995c6e78443f26e8ea",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "~3.0|~1.0|~2.0",
+ "php": ">=5.3",
+ "react/promise": "^2.1 || ^1.2",
+ "react/socket": "^1.0 || ^0.8.6"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.1",
+ "clue/connection-manager-extra": "^1.0 || ^0.7",
+ "phpunit/phpunit": "^6.0 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
+ },
+ "time": "2017-12-17T14:47:58+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Clue\\React\\Socks\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
+ "homepage": "https://github.com/clue/php-socks-react",
+ "keywords": [
+ "async",
+ "proxy",
+ "reactphp",
+ "socks client",
+ "socks protocol",
+ "socks server",
+ "tcp tunnel"
+ ]
+ },
+ {
+ "name": "corneltek/getoptionkit",
+ "version": "2.6.0",
+ "version_normalized": "2.6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/c9s/GetOptionKit.git",
+ "reference": "995607ddf4fc90ebdb4a7d58fe972d581ad8495f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/c9s/GetOptionKit/zipball/995607ddf4fc90ebdb4a7d58fe972d581ad8495f",
+ "reference": "995607ddf4fc90ebdb4a7d58fe972d581ad8495f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2017-06-30T14:54:48+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GetOptionKit\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Yo-An Lin",
+ "email": "yoanlin93@gmail.com"
+ }
+ ],
+ "description": "Powerful command-line option toolkit",
+ "homepage": "http://github.com/c9s/GetOptionKit"
+ },
+ {
+ "name": "evenement/evenement",
+ "version": "v3.0.1",
+ "version_normalized": "3.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/igorw/evenement.git",
+ "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
+ "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "time": "2017-07-23T21:35:13+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Evenement": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": [
+ "event-dispatcher",
+ "event-emitter"
+ ]
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "6.4.1",
+ "version_normalized": "6.4.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "0895c932405407fd3a7368b6910c09a24d26db11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11",
+ "reference": "0895c932405407fd3a7368b6910c09a24d26db11",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.6.1",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "time": "2019-10-23T15:58:00+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.3-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ]
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "v1.3.1",
+ "version_normalized": "1.3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "time": "2016-12-20T10:07:11+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ]
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.6.1",
+ "version_normalized": "1.6.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "ext-zlib": "*",
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+ },
+ "suggest": {
+ "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "time": "2019-07-01T23:21:34+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ]
+ },
+ {
+ "name": "lazyjsonmapper/lazyjsonmapper",
+ "version": "v1.6.3",
+ "version_normalized": "1.6.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/lazyjsonmapper/lazyjsonmapper.git",
+ "reference": "51e093b50f4de15d2d64548b3ca743713eed6ee9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/lazyjsonmapper/lazyjsonmapper/zipball/51e093b50f4de15d2d64548b3ca743713eed6ee9",
+ "reference": "51e093b50f4de15d2d64548b3ca743713eed6ee9",
+ "shasum": ""
+ },
+ "require": {
+ "corneltek/getoptionkit": "2.*",
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.7.1",
+ "phpunit/phpunit": "6.*"
+ },
+ "time": "2018-05-02T16:57:09+00:00",
+ "bin": [
+ "bin/lazydoctor"
+ ],
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "LazyJsonMapper\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "SteveJobzniak",
+ "role": "Developer",
+ "homepage": "https://github.com/SteveJobzniak"
+ }
+ ],
+ "description": "Advanced, intelligent & automatic object-oriented JSON containers for PHP.",
+ "homepage": "https://github.com/SteveJobzniak/LazyJsonMapper",
+ "keywords": [
+ "development",
+ "json"
+ ]
+ },
+ {
+ "name": "mgp25/instagram-php",
+ "version": "v7.0.1",
+ "version_normalized": "7.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mgp25/Instagram-API.git",
+ "reference": "53421f90b9ef7743f1c6221c4963f2b9f7a592e8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mgp25/Instagram-API/zipball/53421f90b9ef7743f1c6221c4963f2b9f7a592e8",
+ "reference": "53421f90b9ef7743f1c6221c4963f2b9f7a592e8",
+ "shasum": ""
+ },
+ "require": {
+ "binsoul/net-mqtt-client-react": "^0.3.2",
+ "clue/http-proxy-react": "^1.1.0",
+ "clue/socks-react": "^0.8.2",
+ "ext-bcmath": "*",
+ "ext-curl": "*",
+ "ext-exif": "*",
+ "ext-gd": "*",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "guzzlehttp/guzzle": "^6.2",
+ "lazyjsonmapper/lazyjsonmapper": "^1.6.1",
+ "php": ">=5.6",
+ "psr/log": "^1.0",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "^2.5",
+ "react/socket": "^0.8",
+ "symfony/process": "^3.4|^4.0",
+ "valga/fbns-react": "^0.1.8",
+ "winbox/args": "1.0.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.11.0",
+ "monolog/monolog": "^1.23",
+ "phpunit/phpunit": "^5.7 || ^6.2",
+ "react/http": "^0.7.2"
+ },
+ "suggest": {
+ "ext-event": "Installing PHP's native Event extension enables faster Realtime class event handling."
+ },
+ "time": "2019-09-17T00:56:42+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "InstagramAPI\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "RPL-1.5",
+ "proprietary"
+ ],
+ "authors": [
+ {
+ "name": "mgp25",
+ "email": "me@mgp25.com",
+ "role": "Founder"
+ },
+ {
+ "name": "SteveJobzniak",
+ "homepage": "https://github.com/SteveJobzniak",
+ "role": "Developer"
+ }
+ ],
+ "description": "Instagram's private API for PHP",
+ "keywords": [
+ "api",
+ "instagram",
+ "php",
+ "private"
+ ]
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2016-08-06T14:39:51+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ]
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.1",
+ "version_normalized": "1.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
+ "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2019-10-25T08:06:51+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ]
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "version_normalized": "3.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "time": "2019-03-08T08:55:37+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders."
+ },
+ {
+ "name": "react/cache",
+ "version": "v1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/cache.git",
+ "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/cache/zipball/aa10d63a1b40a36a486bdf527f28bac607ee6466",
+ "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "~2.0|~1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-07-11T13:45:28+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Cache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": [
+ "cache",
+ "caching",
+ "promise",
+ "reactphp"
+ ]
+ },
+ {
+ "name": "react/dns",
+ "version": "v0.4.19",
+ "version_normalized": "0.4.19.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/dns.git",
+ "reference": "6852fb98e22d2e5bb35fe5aeeaa96551b120e7c9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/dns/zipball/6852fb98e22d2e5bb35fe5aeeaa96551b120e7c9",
+ "reference": "6852fb98e22d2e5bb35fe5aeeaa96551b120e7c9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.1 || ^1.2.1",
+ "react/promise-timer": "^1.2",
+ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-07-10T21:00:53+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Dns\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": [
+ "async",
+ "dns",
+ "dns-resolver",
+ "reactphp"
+ ]
+ },
+ {
+ "name": "react/event-loop",
+ "version": "v0.4.3",
+ "version_normalized": "0.4.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/event-loop.git",
+ "reference": "8bde03488ee897dc6bb3d91e4e17c353f9c5252f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/event-loop/zipball/8bde03488ee897dc6bb3d91e4e17c353f9c5252f",
+ "reference": "8bde03488ee897dc6bb3d91e4e17c353f9c5252f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "suggest": {
+ "ext-event": "~1.0",
+ "ext-libev": "*",
+ "ext-libevent": ">=0.1.0"
+ },
+ "time": "2017-04-27T10:56:23+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Event loop abstraction layer that libraries can use for evented I/O.",
+ "keywords": [
+ "asynchronous",
+ "event-loop"
+ ]
+ },
+ {
+ "name": "react/promise",
+ "version": "v2.7.1",
+ "version_normalized": "2.7.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise.git",
+ "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
+ "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "time": "2019-01-07T21:25:54+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com"
+ }
+ ],
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "keywords": [
+ "promise",
+ "promises"
+ ]
+ },
+ {
+ "name": "react/promise-timer",
+ "version": "v1.5.1",
+ "version_normalized": "1.5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise-timer.git",
+ "reference": "35fb910604fd86b00023fc5cda477c8074ad0abc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/35fb910604fd86b00023fc5cda477c8074ad0abc",
+ "reference": "35fb910604fd86b00023fc5cda477c8074ad0abc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.7.0 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-03-27T18:10:32+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\Timer\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
+ "homepage": "https://github.com/reactphp/promise-timer",
+ "keywords": [
+ "async",
+ "event-loop",
+ "promise",
+ "reactphp",
+ "timeout",
+ "timer"
+ ]
+ },
+ {
+ "name": "react/socket",
+ "version": "v0.8.12",
+ "version_normalized": "0.8.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/socket.git",
+ "reference": "7f7e6c56ccda7418a1a264892a625f38a5bdee0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/socket/zipball/7f7e6c56ccda7418a1a264892a625f38a5bdee0c",
+ "reference": "7f7e6c56ccda7418a1a264892a625f38a5bdee0c",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.0",
+ "react/dns": "^0.4.13",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.6.0 || ^1.2.1",
+ "react/promise-timer": "^1.4.0",
+ "react/stream": "^1.0 || ^0.7.1"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2018-06-11T14:33:43+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": [
+ "Connection",
+ "Socket",
+ "async",
+ "reactphp",
+ "stream"
+ ]
+ },
+ {
+ "name": "react/stream",
+ "version": "v1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/stream.git",
+ "reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/stream/zipball/50426855f7a77ddf43b9266c22320df5bf6c6ce6",
+ "reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5"
+ },
+ "require-dev": {
+ "clue/stream-filter": "~1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-01-01T16:15:09+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": [
+ "event-driven",
+ "io",
+ "non-blocking",
+ "pipe",
+ "reactphp",
+ "readable",
+ "stream",
+ "writable"
+ ]
+ },
+ {
+ "name": "ringcentral/psr7",
+ "version": "1.2.2",
+ "version_normalized": "1.2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ringcentral/psr7.git",
+ "reference": "dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ringcentral/psr7/zipball/dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c",
+ "reference": "dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "psr/http-message": "~1.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "time": "2018-01-15T21:00:49+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "RingCentral\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "PSR-7 message implementation",
+ "keywords": [
+ "http",
+ "message",
+ "stream",
+ "uri"
+ ]
+ },
+ {
+ "name": "symfony/process",
+ "version": "v4.3.5",
+ "version_normalized": "4.3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/50556892f3cc47d4200bfd1075314139c4c9ff4b",
+ "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "time": "2019-09-26T21:17:10+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com"
+ },
+ {
+ "name": "valga/fbns-react",
+ "version": "0.1.8",
+ "version_normalized": "0.1.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/valga/fbns-react.git",
+ "reference": "4bbf513a8ffed7e0c9ca10776033d34515bb8b37"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/valga/fbns-react/zipball/4bbf513a8ffed7e0c9ca10776033d34515bb8b37",
+ "reference": "4bbf513a8ffed7e0c9ca10776033d34515bb8b37",
+ "shasum": ""
+ },
+ "require": {
+ "binsoul/net-mqtt": "~0.2",
+ "evenement/evenement": "~2.0|~3.0",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "php": "~5.6|~7.0",
+ "psr/log": "~1.0",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.4",
+ "monolog/monolog": "~1.23"
+ },
+ "suggest": {
+ "ext-event": "For more efficient event loop implementation.",
+ "ext-gmp": "To be able to run this code on x86 PHP builds."
+ },
+ "time": "2017-10-09T07:54:13+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Fbns\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Abyr Valg",
+ "email": "valga.github@abyrga.ru"
+ }
+ ],
+ "description": "A PHP client for the FBNS built on top of ReactPHP",
+ "keywords": [
+ "FBNS",
+ "client",
+ "php"
+ ]
+ },
+ {
+ "name": "winbox/args",
+ "version": "v1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/johnstevenson/winbox-args.git",
+ "reference": "389a9ed9410e6f422b1031b3e55a402ace716296"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/johnstevenson/winbox-args/zipball/389a9ed9410e6f422b1031b3e55a402ace716296",
+ "reference": "389a9ed9410e6f422b1031b3e55a402ace716296",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "time": "2016-08-04T14:30:27+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Winbox\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Windows command-line formatter",
+ "homepage": "http://github.com/johnstevenson/winbox-args",
+ "keywords": [
+ "Escape",
+ "command",
+ "windows"
+ ]
+ }
+]
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/.gitignore b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/.gitignore
new file mode 100755
index 0000000..f32e02a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/.gitignore
@@ -0,0 +1,3 @@
+/build
+/vendor
+*.tgz
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/.travis.yml b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/.travis.yml
new file mode 100755
index 0000000..147e77c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/.travis.yml
@@ -0,0 +1,29 @@
+language: php
+php:
+- 5.6
+- 7.0
+- 7.1
+- hhvm
+install:
+- phpenv rehash
+- composer require "satooshi/php-coveralls" "^1" --dev --no-update
+- composer install --no-interaction
+script:
+- phpunit -c phpunit.xml.dist
+after_success:
+- php vendor/bin/coveralls -v
+matrix:
+ fast_finish: true
+ allow_failures:
+ - php: hhvm
+ - php: '5.6'
+cache:
+ apt: true
+ directories:
+ - vendor
+notifications:
+ email:
+ on_success: change
+ on_failure: change
+ slack:
+ secure: dKH3qw9myjwDO+OIz6qBqn5vJJqoFyD2frS4eH68jI5gtxHX2PJVwaP9waXTSu+FFq9wILsHGQezrawWHcMkbEo4gxbri2Ne7gFT4CJD0DAOyf02JgQ1A/cJaqQ5XrLO9CwjP0/8PKaMCHiND4SLEWgmtlFRoB7gr33QjbQjBvc=
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/CHANGELOG.md b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/CHANGELOG.md
new file mode 100755
index 0000000..2446ddb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/CHANGELOG.md
@@ -0,0 +1,56 @@
+CHANGELOG
+==================
+
+## v2.3.0 - Thu May 12 10:29:19 2016
+
+- Fixed bugs for multiple value parsing with arguments.
+- OptionParser::parse(argv) now expects the first argument to be the program name,
+ so you can pass argv directly to the parser.
+
+## v2.2.5-6 - Wed May 11 2016
+
+- Fixed bugs for ContinuousOptionParser.
+
+## v2.2.4 - Fri Oct 2 15:53:33 2015
+
+- ContinuousOptionParser improvements.
+
+## v2.2.2 - Tue Jul 14 00:15:26 2015
+
+- Added PathType.
+
+## v2.2.1 - Tue Jul 14 00:17:19 2015
+
+Merged PRs:
+
+- Commit 7bbb91b: Merge pull request #34 from 1Franck/master
+
+ added value type option(s) support, added class RegexType
+
+- Commit 3722992: Merge pull request #33 from 1Franck/patch-1
+
+ Clarified InvalidOptionValue exception message
+
+
+## v2.2.0 - Thu Jun 4 13:51:47 2015
+
+- Added more types for type constraint option. url, ip, ipv4, ipv6, email by @1Franck++
+- Several bug fixes by @1Franck++
+
+
+
+## v2.1.0 - Fri Apr 24 16:43:00 2015
+
+- Added incremental option value support.
+- Fixed #21 for negative value.
+- Used autoloading with PSR-4
+
+## v2.0.12 - Tue Apr 21 18:51:12 2015
+
+- Improved hinting text for default value
+- Some coding style fix
+- Added default value support
+- Updated default value support for ContinuousOptionParser
+- Added getValue() accessor on OptionSpec class
+- Merged pull request #22 from Gasol/zero-option. @Gasol++
+ - Fix option that can't not be 0
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/CONTRIBUTORS.txt b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/CONTRIBUTORS.txt
new file mode 100755
index 0000000..59cb935
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/CONTRIBUTORS.txt
@@ -0,0 +1,11 @@
+Bitdeli Chef
+Dlussky Kirill
+Francois Lajoie
+Gasol Wu
+Igor Santos
+Jevon Wright
+MartyIX
+Michał Kniotek
+Robbert Klarenbeek
+Yo-An Lin
+Yo-An Lin
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/LICENSE b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/LICENSE
new file mode 100755
index 0000000..30a0180
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Yo-An Lin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/README.md b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/README.md
new file mode 100755
index 0000000..1e5794b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/README.md
@@ -0,0 +1,370 @@
+GetOptionKit
+============
+
+Code Quality
+
+[](https://travis-ci.org/c9s/GetOptionKit)
+[](https://coveralls.io/github/c9s/GetOptionKit?branch=master)
+
+Versions & Stats
+
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+
+A powerful option parser toolkit for PHP, supporting type constraints,
+flag, multiple flag, multiple values and required value checking.
+
+GetOptionKit supports PHP5.3, with fine unit testing with PHPUnit testing
+framework.
+
+GetOptionKit is object-oriented, it's flexible and extendable.
+
+Powering PHPBrew , CLIFramework and AssetKit
+
+
+
+## Features
+
+- Simple format.
+- Type constrant.
+- Multiple value, requried value, optional value checking.
+- Auto-generated help text from defined options.
+- Support app/subcommand option parsing.
+- Option Value Validator
+- Option Suggestions
+- SPL library.
+- HHVM support.
+
+## Requirements
+
+* PHP 5.3+
+
+## Install From Composer
+
+```sh
+composer require corneltek/getoptionkit
+```
+
+
+## Supported Option Formats
+
+simple flags:
+
+```sh
+program.php -a -b -c
+program.php -abc
+program.php -vvv # incremental flag v=3
+program.php -a -bc
+```
+
+with multiple values:
+
+```sh
+program.php -a foo -a bar -a zoo -b -b -b
+```
+
+specify value with equal sign:
+
+```sh
+program.php -a=foo
+program.php --long=foo
+```
+
+with normal arguments:
+
+```
+program.php -a=foo -b=bar arg1 arg2 arg3
+program.php arg1 arg2 arg3 -a=foo -b=bar
+```
+
+## Option SPEC
+
+ v|verbose flag option (with boolean value true)
+ d|dir: option require a value (MUST require)
+ d|dir+ option with multiple values.
+ d|dir? option with optional value
+ dir:=string option with type constraint of string
+ dir:=number option with type constraint of number
+ dir:=file option with type constraint of file
+ dir:=date option with type constraint of date
+ dir:=boolean option with type constraint of boolean
+ d single character only option
+ dir long option name
+
+## Command Line Forms
+
+ app [app-opts] [app arguments]
+
+ app [app-opts] subcommand [subcommand-opts] [subcommand-args]
+
+ app [app-opts] subcmd1 [subcmd-opts1] subcmd2 [subcmd-opts] subcmd3 [subcmd-opts3] [subcommand arguments....]
+
+
+## Documentation
+
+See more details in the [documentation](https://github.com/c9s/GetOptionKit/wiki)
+
+## Demo
+
+Please check `examples/demo.php`.
+
+Run:
+
+```sh
+% php examples/demo.php -f test -b 123 -b 333
+```
+
+Print:
+
+ * Available options:
+ -f, --foo option requires a value.
+ -b, --bar + option with multiple value.
+ -z, --zoo [] option with optional value.
+ -v, --verbose verbose message.
+ -d, --debug debug message.
+ --long long option name only.
+ -s short option name only.
+ Enabled options:
+ * key:foo spec:-f, --foo desc:option requires a value.
+ value => test
+
+ * key:bar spec:-b, --bar + desc:option with multiple value.
+ Array
+ (
+ [0] => 123
+ [1] => 333
+ )
+
+## Synopsis
+
+```php
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
+
+$specs = new OptionCollection;
+$specs->add('f|foo:', 'option requires a value.' )
+ ->isa('String');
+
+$specs->add('b|bar+', 'option with multiple value.' )
+ ->isa('Number');
+
+$specs->add('ip+', 'Ip constraint' )
+ ->isa('Ip');
+
+$specs->add('email+', 'Email address constraint' )
+ ->isa('Email');
+
+$specs->add('z|zoo?', 'option with optional value.' )
+ ->isa('Boolean');
+
+$specs->add('file:', 'option value should be a file.' )
+ ->isa('File');
+
+$specs->add('v|verbose', 'verbose message.' );
+$specs->add('d|debug', 'debug message.' );
+$specs->add('long', 'long option name only.' );
+$specs->add('s', 'short option name only.' );
+
+$printer = new ConsoleOptionPrinter();
+echo $printer->render($specs);
+
+$parser = new OptionParser($specs);
+
+echo "Enabled options: \n";
+try {
+ $result = $parser->parse( $argv );
+ foreach ($result->keys as $key => $spec) {
+ print_r($spec);
+ }
+
+ $opt = $result->keys['foo']; // return the option object.
+ $str = $result->keys['foo']->value; // return the option value
+
+ print_r($opt);
+ var_dump($str);
+
+} catch( Exception $e ) {
+ echo $e->getMessage();
+}
+```
+
+
+## Documentation
+
+See for more details.
+
+### Option Value Type
+
+The option value type help you validate the input,
+the following list is the current supported types:
+
+- `string`
+- `number`
+- `boolean`
+- `file`
+- `date`
+- `url`
+- `email`
+- `ip`
+- `ipv4`
+- `ipv6`
+- `regex`
+
+And here is the related sample code:
+
+```php
+$opt->add( 'f|foo:' , 'with string type value' )
+ ->isa('string');
+
+$opt->add( 'b|bar+' , 'with number type value' )
+ ->isa('number');
+
+$opt->add( 'z|zoo?' , 'with boolean type value' )
+ ->isa('boolean');
+
+$opt->add( 'file:' , 'with file type value' )
+ ->isa('file');
+
+$opt->add( 'date:' , 'with date type value' )
+ ->isa('date');
+
+$opt->add( 'url:' , 'with url type value' )
+ ->isa('url');
+
+$opt->add( 'email:' , 'with email type value' )
+ ->isa('email');
+
+$opt->add( 'ip:' , 'with ip(v4/v6) type value' )
+ ->isa('ip');
+
+$opt->add( 'ipv4:' , 'with ipv4 type value' )
+ ->isa('ipv4');
+
+$opt->add( 'ipv6:' , 'with ipv6 type value' )
+ ->isa('ipv6');
+
+$specs->add('r|regex:', 'with custom regex type value')
+ ->isa('Regex', '/^([a-z]+)$/');
+```
+
+> Please note that currently only `string`, `number`, `boolean` types can be validated.
+
+### ContinuousOptionParser
+
+```php
+$specs = new OptionCollection;
+$spec_verbose = $specs->add('v|verbose');
+$spec_color = $specs->add('c|color');
+$spec_debug = $specs->add('d|debug');
+$spec_verbose->description = 'verbose flag';
+
+// ContinuousOptionParser
+$parser = new ContinuousOptionParser( $specs );
+$result = $parser->parse(explode(' ','program -v -d test -a -b -c subcommand -e -f -g subcommand2'));
+$result2 = $parser->continueParse();
+```
+
+### OptionPrinter
+
+GetOptionKit\OptionPrinter can print options for you:
+
+ * Available options:
+ -f, --foo option requires a value.
+ -b, --bar option with multiple value.
+ -z, --zoo option with optional value.
+ -v, --verbose verbose message.
+ -d, --debug debug message.
+ --long long option name only.
+ -s short option name only.
+
+
+## Command-line app with subcommands
+
+For application with subcommands is designed by following form:
+
+
+ [app name] [app opts]
+ [subcommand1] [subcommand-opts]
+ [subcommand2] [subcommand-opts]
+ [subcommand3] [subcommand-opts]
+ [arguments]
+
+You can check the `tests/GetOptionKit/ContinuousOptionParserTest.php` unit test file:
+
+```php
+// subcommand stack
+$subcommands = array('subcommand1','subcommand2','subcommand3');
+
+// different command has its own options
+$subcommandSpecs = array(
+ 'subcommand1' => $cmdspecs,
+ 'subcommand2' => $cmdspecs,
+ 'subcommand3' => $cmdspecs,
+);
+
+// for saved options
+$subcommandOptions = array();
+
+// command arguments
+$arguments = array();
+
+$argv = explode(' ','program -v -d -c subcommand1 -a -b -c subcommand2 -c subcommand3 arg1 arg2 arg3');
+
+// parse application options first
+$parser = new ContinuousOptionParser( $appspecs );
+$app_options = $parser->parse( $argv );
+while (! $parser->isEnd()) {
+ if (@$subcommands[0] && $parser->getCurrentArgument() == $subcommands[0]) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs( $subcommandSpecs[$subcommand] );
+ $subcommandOptions[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+}
+```
+
+## Todo
+
+* Option Spec group.
+* option valid value checking.
+* custom command mapping.
+
+## Command Line Utility Design Concept
+
+* main program name should be easy to type, easy to remember.
+* subcommand should be easy to type, easy to remember. length should be shorter than 7 characters.
+* options should always have long descriptive name
+* a program should be easy to check usage.
+
+## General command interface
+
+To list usage of all subcommands or the program itself:
+
+ $ prog help
+
+To list the subcommand usage
+
+ $ prog help subcommand subcommand2 subcommand3
+
+## Hacking
+
+Fork this repository and clone it:
+
+ $ git clone git://github.com/c9s/GetOptionKit.git
+ $ cd GetOptionKit
+ $ composer install
+
+Run PHPUnit to test:
+
+ $ phpunit
+
+## License
+
+This project is released under MIT License.
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/build.xml b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/build.xml
new file mode 100755
index 0000000..f53f06d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/build.xml
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/composer.json b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/composer.json
new file mode 100755
index 0000000..e6577d5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/composer.json
@@ -0,0 +1,16 @@
+{
+ "name": "corneltek/getoptionkit",
+ "homepage": "http://github.com/c9s/GetOptionKit",
+ "description": "Powerful command-line option toolkit",
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "license": "MIT",
+ "authors": [ { "name": "Yo-An Lin", "email": "yoanlin93@gmail.com" } ],
+ "autoload": {
+ "psr-4": {
+ "GetOptionKit\\": "src/"
+ }
+ },
+ "extra": { "branch-alias": { "dev-master": "2.6.x-dev" } }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/composer.lock b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/composer.lock
new file mode 100755
index 0000000..1dd27fe
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/composer.lock
@@ -0,0 +1,19 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "5a9b6e9fccaf89d121650d35313e842b",
+ "packages": [],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.3.0"
+ },
+ "platform-dev": []
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/examples/demo.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/examples/demo.php
new file mode 100755
index 0000000..905f5c3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/examples/demo.php
@@ -0,0 +1,76 @@
+#!/usr/bin/env php
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+require 'vendor/autoload.php';
+
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
+
+$specs = new OptionCollection;
+$specs->add('f|foo:', 'option requires a value.' )
+ ->isa('String');
+
+$specs->add('b|bar+', 'option with multiple value.' )
+ ->isa('Number');
+
+$specs->add('z|zoo?', 'option with optional value.' )
+ ->isa('Boolean')
+ ;
+
+$specs->add('o|output?', 'option with optional value.' )
+ ->isa('File')
+ ->defaultValue('output.txt')
+ ;
+
+// works for -vvv => verbose = 3
+$specs->add('v|verbose', 'verbose')
+ ->isa('Number')
+ ->incremental();
+
+$specs->add('file:', 'option value should be a file.' )
+ ->trigger(function($value) {
+ echo "Set value to :";
+ var_dump($value);
+ })
+ ->isa('File');
+
+$specs->add('r|regex:', 'with custom regex type value')
+ ->isa('Regex', '/^([a-z]+)$/');
+
+$specs->add('d|debug', 'debug message.' );
+$specs->add('long', 'long option name only.' );
+$specs->add('s', 'short option name only.' );
+$specs->add('m', 'short option m');
+$specs->add('4', 'short option with digit');
+
+$printer = new ConsoleOptionPrinter;
+echo $printer->render($specs);
+
+$parser = new OptionParser($specs);
+
+echo "Enabled options: \n";
+try {
+ $result = $parser->parse( $argv );
+ foreach ($result->keys as $key => $spec) {
+ print_r($spec);
+ }
+
+ $opt = $result->keys['foo']; // return the option object.
+ $str = $result->keys['foo']->value; // return the option value
+
+ print_r($opt);
+ var_dump($str);
+
+} catch( Exception $e ) {
+ echo $e->getMessage();
+}
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/package.ini b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/package.ini
new file mode 100755
index 0000000..4d71095
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/package.ini
@@ -0,0 +1,15 @@
+[package]
+name = GetOptionKit
+version = 1.2.2
+desc = "A powerful GetOpt toolkit for PHP, which supports type constraints, flag,
+multiple flag, multiple values, required value checking."
+
+author = "Yo-An Lin (c9s) "
+channel = pear.corneltek.com
+stability = stable
+
+[require]
+php = 5.3
+pearinstaller = 1.4.1
+pear.corneltek.com/Universal =
+pear.corneltek.com/PHPUnit_TestMore =
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phpdox.xml b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phpdox.xml
new file mode 100755
index 0000000..c727483
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phpdox.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phprelease.ini b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phprelease.ini
new file mode 100755
index 0000000..a5c5cc9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phprelease.ini
@@ -0,0 +1,2 @@
+Steps = PHPUnit, BumpVersion, GitCommit, GitTag, GitPush, GitPushTags
+; VersionFrom = src/PHPRelease/Console.php
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phpunit-ci.xml b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phpunit-ci.xml
new file mode 100755
index 0000000..0fed2fa
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phpunit-ci.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phpunit.xml.dist
new file mode 100755
index 0000000..87bb483
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/phpunit.xml.dist
@@ -0,0 +1,29 @@
+
+
+
+
+
+ src
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Argument.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Argument.php
new file mode 100755
index 0000000..2a92717
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Argument.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+class Argument
+{
+ public $arg;
+
+ public function __construct($arg)
+ {
+ $this->arg = $arg;
+ }
+
+ public function isLongOption()
+ {
+ return substr($this->arg, 0, 2) === '--';
+ }
+
+ public function isShortOption()
+ {
+ return (substr($this->arg, 0, 1) === '-')
+ && (substr($this->arg, 1, 1) !== '-');
+ }
+
+ public function isEmpty()
+ {
+ return $this->arg === null || empty($this->arg) && ('0' !== $this->arg);
+ }
+
+ /**
+ * Check if an option is one of the option in the collection.
+ */
+ public function anyOfOptions(OptionCollection $options)
+ {
+ $name = $this->getOptionName();
+ $keys = $options->keys();
+
+ return in_array($name, $keys);
+ }
+
+ /**
+ * Check current argument is an option by the preceding dash.
+ * note this method does not work for string with negative value.
+ *
+ * -a
+ * --foo
+ */
+ public function isOption()
+ {
+ return $this->isShortOption() || $this->isLongOption();
+ }
+
+ /**
+ * Parse option and return the name after dash. e.g.,
+ * '--foo' returns 'foo'
+ * '-f' returns 'f'.
+ *
+ * @return string
+ */
+ public function getOptionName()
+ {
+ if (preg_match('/^[-]+([a-zA-Z0-9-]+)/', $this->arg, $regs)) {
+ return $regs[1];
+ }
+ }
+
+ public function splitAsOption()
+ {
+ return explode('=', $this->arg, 2);
+ }
+
+ public function containsOptionValue()
+ {
+ return preg_match('/=.+/', $this->arg);
+ }
+
+ public function getOptionValue()
+ {
+ if (preg_match('/=(.+)/', $this->arg, $regs)) {
+ return $regs[1];
+ }
+
+ return;
+ }
+
+ /**
+ * Check combined short flags for "-abc" or "-vvv".
+ *
+ * like: -abc
+ */
+ public function withExtraFlagOptions()
+ {
+ return preg_match('/^-[a-zA-Z0-9]{2,}/', $this->arg);
+ }
+
+ public function extractExtraFlagOptions()
+ {
+ $args = array();
+ for ($i = 2;$i < strlen($this->arg); ++$i) {
+ $args[] = '-'.$this->arg[$i];
+ }
+ $this->arg = substr($this->arg, 0, 2); # -[a-z]
+ return $args;
+ }
+
+ public function __toString()
+ {
+ return $this->arg;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ContinuousOptionParser.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ContinuousOptionParser.php
new file mode 100755
index 0000000..fa592fd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ContinuousOptionParser.php
@@ -0,0 +1,201 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+use Exception;
+use LogicException;
+use GetOptionKit\Exception\InvalidOptionException;
+use GetOptionKit\Exception\RequireValueException;
+
+/**
+ * A common command line argument format:.
+ *
+ * app.php
+ * [--app-options]
+ *
+ * [subcommand
+ * --subcommand-options]
+ * [subcommand
+ * --subcommand-options]
+ * [subcommand
+ * --subcommand-options]
+ *
+ * [arguments]
+ *
+ * ContinuousOptionParser is for the process flow:
+ *
+ * init app options,
+ * parse app options
+ *
+ *
+ *
+ * while not end
+ * if stop at command
+ * shift command
+ * parse command options
+ * else if stop at arguments
+ * shift arguments
+ * execute current command with the arguments.
+ *
+ * Example code:
+ *
+ *
+ * // subcommand stack
+ * $subcommands = array('subcommand1','subcommand2','subcommand3');
+ *
+ * // different command has its own options
+ * $subcommand_specs = array(
+ * 'subcommand1' => $cmdspecs,
+ * 'subcommand2' => $cmdspecs,
+ * 'subcommand3' => $cmdspecs,
+ * );
+ *
+ * // for saved options
+ * $subcommand_options = array();
+ *
+ * // command arguments
+ * $arguments = array();
+ *
+ * $argv = explode(' ','-v -d -c subcommand1 -a -b -c subcommand2 -c subcommand3 arg1 arg2 arg3');
+ *
+ * // parse application options first
+ * $parser = new ContinuousOptionParser( $appspecs );
+ * $app_options = $parser->parse( $argv );
+ * while (! $parser->isEnd()) {
+ * if( $parser->getCurrentArgument() == $subcommands[0] ) {
+ * $parser->advance();
+ * $subcommand = array_shift( $subcommands );
+ * $parser->setSpecs( $subcommand_specs[$subcommand] );
+ * $subcommand_options[ $subcommand ] = $parser->continueParse();
+ * } else {
+ * $arguments[] = $parser->advance();
+ * }
+ * }
+ **/
+class ContinuousOptionParser extends OptionParser
+{
+ public $index;
+ public $length;
+ public $argv;
+
+ /* for the constructor , the option specs is application options */
+ public function __construct(OptionCollection $specs)
+ {
+ parent::__construct($specs);
+ $this->index = 1;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ */
+ public function startFrom($index)
+ {
+ $this->index = $index;
+ }
+
+ public function isEnd()
+ {
+ # echo "!! {$this->index} >= {$this->length}\n";
+ return $this->index >= $this->length;
+ }
+
+ /**
+ * Return the current argument and advance to the next position.
+ *
+ * @return string
+ */
+ public function advance()
+ {
+ if ($this->index >= $this->length) {
+ throw new LogicException("Argument index out of bounds.");
+ }
+ return $this->argv[$this->index++];
+ }
+
+ /**
+ * Return the current argument that the index pointed to.
+ *
+ * @return string
+ */
+ public function getCurrentArgument()
+ {
+ return $this->argv[$this->index];
+ }
+
+ public function continueParse()
+ {
+ return $this->parse($this->argv);
+ }
+
+
+ protected function fillDefaultValues(OptionCollection $opts, OptionResult $result)
+ {
+ // register option result from options with default value
+ foreach ($opts as $opt) {
+ if ($opt->value === null && $opt->defaultValue !== null) {
+ $opt->setValue($opt->getDefaultValue());
+ $result->set($opt->getId(), $opt);
+ }
+ }
+ }
+
+
+ public function parse(array $argv)
+ {
+ // create new Result object.
+ $result = new OptionResult();
+ list($this->argv, $extra) = $this->preprocessingArguments($argv);
+ $this->length = count($this->argv);
+
+ // from last parse index
+ for (; $this->index < $this->length; ++$this->index) {
+ $arg = new Argument($this->argv[$this->index]);
+
+ /* let the application decide for: command or arguments */
+ if (!$arg->isOption()) {
+ # echo "stop at {$this->index}\n";
+ $this->fillDefaultValues($this->specs, $result);
+ return $result;
+ }
+
+ // if the option is with extra flags,
+ // split it out, and insert into the argv array
+ //
+ // like -abc
+ if ($arg->withExtraFlagOptions()) {
+ $extra = $arg->extractExtraFlagOptions();
+ array_splice($this->argv, $this->index + 1, 0, $extra);
+ $this->argv[$this->index] = $arg->arg; // update argument to current argv list.
+ $this->length = count($this->argv); // update argv list length
+ }
+
+ $next = null;
+ if ($this->index + 1 < count($this->argv)) {
+ $next = new Argument($this->argv[$this->index + 1]);
+ }
+
+ $spec = $this->specs->get($arg->getOptionName());
+ if (!$spec) {
+ throw new InvalidOptionException('Invalid option: '.$arg);
+ }
+
+ // This if block is unnecessary
+ // if ($spec->isRequired() || $spec->isMultiple() || $spec->isOptional() || $spec->isFlag()) {
+ $this->index += $this->consumeOptionToken($spec, $arg, $next);
+ $result->set($spec->getId(), $spec);
+ }
+
+ $this->fillDefaultValues($this->specs, $result);
+
+ return $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionException.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionException.php
new file mode 100755
index 0000000..dea01de
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class InvalidOptionException extends Exception
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionValueException.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionValueException.php
new file mode 100755
index 0000000..fa4b163
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionValueException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class InvalidOptionValueException extends Exception
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/NonNumericException.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/NonNumericException.php
new file mode 100755
index 0000000..757d214
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/NonNumericException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class NonNumericException extends Exception
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/OptionConflictException.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/OptionConflictException.php
new file mode 100755
index 0000000..6a3c9e1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/OptionConflictException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class OptionConflictException extends Exception
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/RequireValueException.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/RequireValueException.php
new file mode 100755
index 0000000..c9e69ef
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Exception/RequireValueException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class RequireValueException extends Exception
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Option.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Option.php
new file mode 100755
index 0000000..f11a93a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/Option.php
@@ -0,0 +1,557 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+
+use Exception;
+use LogicException;
+use InvalidArgumentException;
+use GetOptionKit\Exception\InvalidOptionValueException;
+
+class Option
+{
+ public $short;
+
+ public $long;
+
+ /**
+ * @var string the description of this option
+ */
+ public $desc;
+
+ /**
+ * @var string The option key
+ */
+ public $key; /* key to store values */
+
+ public $value;
+
+ public $type;
+
+ public $valueName; /* name for the value place holder, for printing */
+
+ public $isa;
+
+ public $isaOption;
+
+ public $validValues;
+
+ public $suggestions;
+
+ public $defaultValue;
+
+ public $incremental = false;
+
+ /**
+ * @var Closure The filter closure of the option value.
+ */
+ public $filter;
+
+ public $validator;
+
+ public $multiple = false;
+
+ public $optional = false;
+
+ public $required = false;
+
+ public $flag = false;
+
+ /**
+ * @var callable trigger callback after value is set.
+ */
+ protected $trigger;
+
+ public function __construct($spec)
+ {
+ $this->initFromSpecString($spec);
+ }
+
+ /**
+ * Build spec attributes from spec string.
+ *
+ * @param string $specString
+ */
+ protected function initFromSpecString($specString)
+ {
+ $pattern = '/
+ (
+ (?:[a-zA-Z0-9-]+)
+ (?:
+ \|
+ (?:[a-zA-Z0-9-]+)
+ )?
+ )
+
+ # option attribute operators
+ ([:+?])?
+
+ # value types
+ (?:=(boolean|string|number|date|file|dir|url|email|ip|ipv6|ipv4))?
+ /x';
+ $ret = preg_match($pattern, $specString, $regs);
+ if ($ret === false || $ret === 0) {
+ throw new Exception('Incorrect spec string');
+ }
+
+ $orig = $regs[0];
+ $name = $regs[1];
+ $attributes = isset($regs[2]) ? $regs[2] : null;
+ $type = isset($regs[3]) ? $regs[3] : null;
+
+ $short = null;
+ $long = null;
+
+ // check long,short option name.
+ if (strpos($name, '|') !== false) {
+ list($short, $long) = explode('|', $name);
+ } else if (strlen($name) === 1) {
+ $short = $name;
+ } else if (strlen($name) > 1) {
+ $long = $name;
+ }
+
+ $this->short = $short;
+ $this->long = $long;
+
+ // option is required.
+ if (strpos($attributes, ':') !== false) {
+ $this->required();
+ } else if (strpos($attributes, '+') !== false) {
+ // option with multiple value
+ $this->multiple();
+ } else if (strpos($attributes, '?') !== false) {
+ // option is optional.(zero or one value)
+ $this->optional();
+ } else {
+ $this->flag();
+ }
+ if ($type) {
+ $this->isa($type);
+ }
+ }
+
+ /*
+ * get the option key for result key mapping.
+ */
+ public function getId()
+ {
+ return $this->key ?: $this->long ?: $this->short;
+ }
+
+ /**
+ * To make -v, -vv, -vvv works.
+ */
+ public function incremental()
+ {
+ $this->incremental = true;
+
+ return $this;
+ }
+
+ public function required()
+ {
+ $this->required = true;
+
+ return $this;
+ }
+
+ /**
+ * Set default value
+ *
+ * @param mixed|Closure $value
+ */
+ public function defaultValue($value)
+ {
+ $this->defaultValue = $value;
+
+ return $this;
+ }
+
+ public function multiple()
+ {
+ $this->multiple = true;
+ $this->value = array(); # for value pushing
+ return $this;
+ }
+
+ public function optional()
+ {
+ $this->optional = true;
+
+ return $this;
+ }
+
+ public function flag()
+ {
+ $this->flag = true;
+
+ return $this;
+ }
+
+ public function trigger(callable $trigger)
+ {
+ $this->trigger = $trigger;
+
+ return $this;
+ }
+
+ public function isIncremental()
+ {
+ return $this->incremental;
+ }
+
+ public function isFlag()
+ {
+ return $this->flag;
+ }
+
+ public function isMultiple()
+ {
+ return $this->multiple;
+ }
+
+ public function isRequired()
+ {
+ return $this->required;
+ }
+
+ public function isOptional()
+ {
+ return $this->optional;
+ }
+
+ public function isTypeNumber()
+ {
+ return $this->isa == 'number';
+ }
+
+ public function isType($type)
+ {
+ return $this->isa === $type;
+ }
+
+ public function getTypeClass()
+ {
+ $class = 'GetOptionKit\\ValueType\\'.ucfirst($this->isa).'Type';
+ if (class_exists($class, true)) {
+ return new $class($this->isaOption);
+ }
+ throw new Exception("Type class '$class' not found.");
+ }
+
+ public function testValue($value)
+ {
+ $type = $this->getTypeClass();
+ return $type->test($value);
+ }
+
+ protected function _preprocessValue($value)
+ {
+ $val = $value;
+
+ if ($isa = ucfirst($this->isa)) {
+ $type = $this->getTypeClass();
+ if ($type->test($value)) {
+ $val = $type->parse($value);
+ } else {
+ if (strtolower($isa) === 'regex') {
+ $isa .= '('.$this->isaOption.')';
+ }
+ throw new InvalidOptionValueException("Invalid value for {$this->renderReadableSpec(false)}. Requires a type $isa.");
+ }
+ }
+
+ // check pre-filter for option value
+ if ($this->filter) {
+ $val = call_user_func($this->filter, $val);
+ }
+
+ // check validValues
+ if ($validValues = $this->getValidValues()) {
+ if (!in_array($value, $validValues)) {
+ throw new InvalidOptionValueException('valid values are: '.implode(', ', $validValues));
+ }
+ }
+
+ if (!$this->validate($value)[0]) {
+ throw new InvalidOptionValueException('option is invalid');
+ }
+
+ return $val;
+ }
+
+ protected function callTrigger()
+ {
+ if ($this->trigger) {
+ if ($ret = call_user_func($this->trigger, $this->value)) {
+ $this->value = $ret;
+ }
+ }
+ }
+
+ /*
+ * set option value
+ */
+ public function setValue($value)
+ {
+ $this->value = $this->_preprocessValue($value);
+ $this->callTrigger();
+ }
+
+ /**
+ * This method is for incremental option.
+ */
+ public function increaseValue()
+ {
+ if (!$this->value) {
+ $this->value = 1;
+ } else {
+ ++$this->value;
+ }
+ $this->callTrigger();
+ }
+
+ /**
+ * push option value, when the option accept multiple values.
+ *
+ * @param mixed
+ */
+ public function pushValue($value)
+ {
+ $value = $this->_preprocessValue($value);
+ $this->value[] = $value;
+ $this->callTrigger();
+ }
+
+ public function desc($desc)
+ {
+ $this->desc = $desc;
+ }
+
+ /**
+ * valueName is for option value hinting:.
+ *
+ * --name=
+ */
+ public function valueName($name)
+ {
+ $this->valueName = $name;
+
+ return $this;
+ }
+
+ public function renderValueHint()
+ {
+ $n = null;
+ if ($this->valueName) {
+ $n = $this->valueName;
+ } else if ($values = $this->getValidValues()) {
+ $n = '('.implode(',', $values).')';
+ } else if ($values = $this->getSuggestions()) {
+ $n = '['.implode(',', $values).']';
+ } else if ($val = $this->getDefaultValue()) {
+ // This allows for `0` and `false` values to be displayed also.
+ if ((is_scalar($val) && strlen((string) $val)) || is_bool($val)) {
+ if (is_bool($val)) {
+ $n = ($val ? 'true' : 'false');
+ } else {
+ $n = $val;
+ }
+ }
+ }
+
+ if (!$n && $this->isa !== null) {
+ $n = '<'.$this->isa.'>';
+ }
+ if ($this->isRequired()) {
+ return sprintf('=%s', $n);
+ } else if ($this->isOptional() || $this->defaultValue) {
+ return sprintf('[=%s]', $n);
+ } else if ($n) {
+ return '='.$n;
+ }
+
+ return '';
+ }
+
+ public function getDefaultValue()
+ {
+ if (is_callable($this->defaultValue)) {
+ return $this->defaultValue;
+ }
+
+ return $this->defaultValue;
+ }
+
+ public function getValue()
+ {
+ if (null !== $this->value) {
+ if (is_callable($this->value)) {
+ return call_user_func($this->value);
+ }
+ return $this->value;
+ }
+
+ return $this->getDefaultValue();
+ }
+
+ /**
+ * get readable spec for printing.
+ *
+ * @param string $renderHint render also value hint
+ */
+ public function renderReadableSpec($renderHint = true)
+ {
+ $c1 = '';
+ if ($this->short && $this->long) {
+ $c1 = sprintf('-%s, --%s', $this->short, $this->long);
+ } else if ($this->short) {
+ $c1 = sprintf('-%s', $this->short);
+ } else if ($this->long) {
+ $c1 = sprintf('--%s', $this->long);
+ }
+ if ($renderHint) {
+ return $c1.$this->renderValueHint();
+ }
+
+ return $c1;
+ }
+
+ public function __toString()
+ {
+ $c1 = $this->renderReadableSpec();
+ $return = '';
+ $return .= sprintf('* key:%-8s spec:%s desc:%s', $this->getId(), $c1, $this->desc)."\n";
+ $val = $this->getValue();
+ if (is_array($val)) {
+ $return .= ' value => ' . join(',', array_map(function($v) { return var_export($v, true); }, $val))."\n";
+ } else {
+ $return .= sprintf(' value => %s', $val)."\n";
+ }
+
+ return $return;
+ }
+
+ /**
+ * Value Type Setters.
+ *
+ * @param string $type the value type, valid values are 'number', 'string',
+ * 'file', 'boolean', you can also use your own value type name.
+ * @param mixed $option option(s) for value type class (optionnal)
+ */
+ public function isa($type, $option = null)
+ {
+ // "bool" was kept for backward compatibility
+ if ($type === 'bool') {
+ $type = 'boolean';
+ }
+ $this->isa = $type;
+ $this->isaOption = $option;
+
+ return $this;
+ }
+
+ /**
+ * Assign validValues to member value.
+ */
+ public function validValues($values)
+ {
+ $this->validValues = $values;
+
+ return $this;
+ }
+
+ /**
+ * Assign suggestions.
+ *
+ * @param Closure|array
+ */
+ public function suggestions($suggestions)
+ {
+ $this->suggestions = $suggestions;
+
+ return $this;
+ }
+
+ /**
+ * Return valud values array.
+ *
+ * @return string[] or nil
+ */
+ public function getValidValues()
+ {
+ if ($this->validValues) {
+ if (is_callable($this->validValues)) {
+ return call_user_func($this->validValues);
+ }
+
+ return $this->validValues;
+ }
+
+ return;
+ }
+
+ /**
+ * Return suggestions.
+ *
+ * @return string[] or nil
+ */
+ public function getSuggestions()
+ {
+ if ($this->suggestions) {
+ if (is_callable($this->suggestions)) {
+ return call_user_func($this->suggestions);
+ }
+
+ return $this->suggestions;
+ }
+
+ return;
+ }
+
+ public function validate($value)
+ {
+ if ($this->validator) {
+ $ret = call_user_func($this->validator, $value);
+ if (is_array($ret)) {
+ return $ret;
+ } else if ($ret === false) {
+ return array(false, "Invalid value: $value");
+ } else if ($ret === true) {
+ return array(true, 'Successfully validated.');
+ }
+ throw new InvalidArgumentException('Invalid return value from the validator.');
+ }
+
+ return array(true);
+ }
+
+ public function validator($cb)
+ {
+ $this->validator = $cb;
+
+ return $this;
+ }
+
+ /**
+ * Set up a filter function for the option value.
+ *
+ * todo: add "callable" type hint later.
+ */
+ public function filter($cb)
+ {
+ $this->filter = $cb;
+
+ return $this;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionCollection.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionCollection.php
new file mode 100755
index 0000000..22cb638
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionCollection.php
@@ -0,0 +1,207 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+
+use ArrayIterator;
+use IteratorAggregate;
+use Countable;
+use Exception;
+use LogicException;
+use GetOptionKit\Exception\OptionConflictException;
+
+class OptionCollection
+ implements IteratorAggregate, Countable
+{
+ public $data = array();
+
+ /**
+ * @var Option[string]
+ *
+ * read-only property
+ */
+ public $longOptions = array();
+
+ /**
+ * @var Option[string]
+ *
+ * read-only property
+ */
+ public $shortOptions = array();
+
+ /**
+ * @var Option[]
+ *
+ * read-only property
+ */
+ public $options = array();
+
+ public function __construct()
+ {
+ $this->data = array();
+ }
+
+ public function __clone()
+ {
+ foreach ($this->data as $k => $v) {
+ $this->data[ $k ] = clone $v;
+ }
+ foreach ($this->longOptions as $k => $v) {
+ $this->longOptions[ $k ] = clone $v;
+ }
+ foreach ($this->shortOptions as $k => $v) {
+ $this->shortOptions[ $k ] = clone $v;
+ }
+ foreach ($this->options as $k => $v) {
+ $this->options[ $k ] = clone $v;
+ }
+ }
+
+ /**
+ * add( [spec string], [desc string] ).
+ *
+ * add( [option object] )
+ */
+ public function add()
+ {
+ $num = func_num_args();
+ $args = func_get_args();
+ $first = $args[0];
+
+ if ($first instanceof Option) {
+
+ $this->addOption($first);
+
+ } else if (is_string($first)) {
+
+ $specString = $args[0];
+ $desc = isset($args[1]) ? $args[1] : null;
+ $key = isset($args[2]) ? $args[2] : null;
+
+ // parse spec string
+ $spec = new Option($specString);
+ if ($desc) {
+ $spec->desc($desc);
+ }
+ if ($key) {
+ $spec->key = $key;
+ }
+ $this->addOption($spec);
+ return $spec;
+
+ } else {
+
+ throw new LogicException('Unknown Spec Type');
+
+ }
+ }
+
+ /**
+ * Add option object.
+ *
+ * @param object $spec the option object.
+ */
+ public function addOption(Option $spec)
+ {
+ $this->data[$spec->getId()] = $spec;
+ if ($spec->long) {
+ if (isset($this->longOptions[$spec->long])) {
+ throw new OptionConflictException('Option conflict: --'.$spec->long.' is already defined.');
+ }
+ $this->longOptions[$spec->long] = $spec;
+ }
+ if ($spec->short) {
+ if (isset($this->shortOptions[$spec->short])) {
+ throw new OptionConflictException('Option conflict: -'.$spec->short.' is already defined.');
+ }
+ $this->shortOptions[$spec->short] = $spec;
+ }
+ $this->options[] = $spec;
+ if (!$spec->long && !$spec->short) {
+ throw new Exception('Neither long option name nor short name is not given.');
+ }
+ }
+
+ public function getLongOption($name)
+ {
+ return isset($this->longOptions[ $name ]) ? $this->longOptions[ $name ] : null;
+ }
+
+ public function getShortOption($name)
+ {
+ return isset($this->shortOptions[ $name ]) ? $this->shortOptions[ $name ] : null;
+ }
+
+ /* Get spec by spec id */
+ public function get($id)
+ {
+ if (isset($this->data[$id])) {
+ return $this->data[$id];
+ } else if (isset($this->longOptions[$id])) {
+ return $this->longOptions[$id];
+ } else if (isset($this->shortOptions[$id])) {
+ return $this->shortOptions[$id];
+ }
+ }
+
+ public function find($name)
+ {
+ foreach ($this->options as $option) {
+ if ($option->short === $name || $option->long === $name) {
+ return $option;
+ }
+ }
+ }
+
+ public function size()
+ {
+ return count($this->data);
+ }
+
+ public function all()
+ {
+ return $this->data;
+ }
+
+ public function toArray()
+ {
+ $array = array();
+ foreach ($this->data as $k => $spec) {
+ $item = array();
+ if ($spec->long) {
+ $item['long'] = $spec->long;
+ }
+ if ($spec->short) {
+ $item['short'] = $spec->short;
+ }
+ $item['desc'] = $spec->desc;
+ $array[] = $item;
+ }
+
+ return $array;
+ }
+
+ public function keys()
+ {
+ return array_merge(array_keys($this->longOptions), array_keys($this->shortOptions));
+ }
+
+ public function count()
+ {
+ return count($this->data);
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->data);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionParser.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionParser.php
new file mode 100755
index 0000000..da9cba5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionParser.php
@@ -0,0 +1,193 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+use Exception;
+use GetOptionKit\Exception\InvalidOptionException;
+use GetOptionKit\Exception\RequireValueException;
+
+class OptionParser
+{
+ public $specs;
+ public $longOptions;
+ public $shortOptions;
+
+ public function __construct(OptionCollection $specs)
+ {
+ $this->specs = $specs;
+ }
+
+ public function setSpecs(OptionCollection $specs)
+ {
+ $this->specs = $specs;
+ }
+
+ /**
+ * consume option value from current argument or from the next argument
+ *
+ * @return boolean next token consumed?
+ */
+ protected function consumeOptionToken(Option $spec, $arg, $next, & $success = false)
+ {
+ // Check options doesn't require next token before
+ // all options that require values.
+ if ($spec->isFlag()) {
+
+ if ($spec->isIncremental()) {
+ $spec->increaseValue();
+ } else {
+ $spec->setValue(true);
+ }
+ return 0;
+
+ } else if ($spec->isRequired()) {
+
+ if ($next && !$next->isEmpty() && !$next->anyOfOptions($this->specs)) {
+ $spec->setValue($next->arg);
+ return 1;
+ } else {
+ throw new RequireValueException("Option '{$arg->getOptionName()}' requires a value.");
+ }
+
+ } else if ($spec->isMultiple()) {
+
+ if ($next && !$next->isEmpty() && !$next->anyOfOptions($this->specs)) {
+ $this->pushOptionValue($spec, $arg, $next);
+ return 1;
+ }
+
+ } else if ($spec->isOptional() && $next && !$next->isEmpty() && !$next->anyOfOptions($this->specs)) {
+
+ $spec->setValue($next->arg);
+ return 1;
+
+ }
+ return 0;
+ }
+
+ /*
+ * push value to multipl value option
+ */
+ protected function pushOptionValue(Option $spec, $arg, $next)
+ {
+ if ($next && !$next->anyOfOptions($this->specs)) {
+ $spec->pushValue($next->arg);
+ }
+ }
+
+ /**
+ * preprocess the argv array
+ *
+ * - split option and option value
+ * - separate arguments after "--"
+ */
+ protected function preprocessingArguments(array $argv)
+ {
+ // preprocessing arguments
+ $newArgv = array();
+ $extra = array();
+ $afterDash = false;
+ foreach ($argv as $arg) {
+ if ($arg === '--') {
+ $afterDash = true;
+ continue;
+ }
+ if ($afterDash) {
+ $extra[] = $arg;
+ continue;
+ }
+
+ $a = new Argument($arg);
+ if ($a->anyOfOptions($this->specs) && $a->containsOptionValue()) {
+ list($opt, $val) = $a->splitAsOption();
+ array_push($newArgv, $opt, $val);
+ } else {
+ $newArgv[] = $arg;
+ }
+ }
+ return array($newArgv, $extra);
+ }
+
+ protected function fillDefaultValues(OptionCollection $opts, OptionResult $result)
+ {
+ // register option result from options with default value
+ foreach ($opts as $opt) {
+ if ($opt->value === null && $opt->defaultValue !== null) {
+ $opt->setValue($opt->getDefaultValue());
+ $result->set($opt->getId(), $opt);
+ }
+ }
+ }
+
+ /**
+ * @param array $argv
+ *
+ * @return OptionResult|Option[]
+ *
+ * @throws Exception\RequireValueException
+ * @throws Exception\InvalidOptionException
+ * @throws \Exception
+ */
+ public function parse(array $argv)
+ {
+ $result = new OptionResult();
+
+ list($argv, $extra) = $this->preprocessingArguments($argv);
+
+ $len = count($argv);
+
+ // some people might still pass only the option names here.
+ $first = new Argument($argv[0]);
+ if ($first->isOption()) {
+ throw new Exception('parse(argv) expects the first argument to be the program name.');
+ }
+
+ for ($i = 1; $i < $len; ++$i) {
+ $arg = new Argument($argv[$i]);
+
+ // if looks like not an option, push it to argument list.
+ // TODO: we might want to support argument with preceding dash (?)
+ if (!$arg->isOption()) {
+ $result->addArgument($arg);
+ continue;
+ }
+
+ // if the option is with extra flags,
+ // split the string, and insert into the argv array
+ if ($arg->withExtraFlagOptions()) {
+ $extra = $arg->extractExtraFlagOptions();
+ array_splice($argv, $i + 1, 0, $extra);
+ $argv[$i] = $arg->arg; // update argument to current argv list.
+ $len = count($argv); // update argv list length
+ }
+
+ $next = null;
+ if ($i + 1 < count($argv)) {
+ $next = new Argument($argv[$i + 1]);
+ }
+
+ $spec = $this->specs->get($arg->getOptionName());
+ if (!$spec) {
+ throw new InvalidOptionException('Invalid option: '.$arg);
+ }
+
+ // This if expr might be unnecessary, becase we have default mode - flag
+ // if ($spec->isRequired() || $spec->isMultiple() || $spec->isOptional() || $spec->isFlag()) {
+ $i += $this->consumeOptionToken($spec, $arg, $next);
+ $result->set($spec->getId(), $spec);
+ }
+
+ $this->fillDefaultValues($this->specs, $result);
+
+ return $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionPrinter/ConsoleOptionPrinter.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionPrinter/ConsoleOptionPrinter.php
new file mode 100755
index 0000000..dba0a50
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionPrinter/ConsoleOptionPrinter.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace GetOptionKit\OptionPrinter;
+
+use GetOptionKit\OptionCollection;
+use GetOptionKit\Option;
+
+class ConsoleOptionPrinter implements OptionPrinter
+{
+ public $screenWidth = 78;
+
+ /**
+ * Render readable spec.
+ */
+ public function renderOption(Option $opt)
+ {
+ $c1 = '';
+ if ($opt->short && $opt->long) {
+ $c1 = sprintf('-%s, --%s', $opt->short, $opt->long);
+ } else if ($opt->short) {
+ $c1 = sprintf('-%s', $opt->short);
+ } else if ($opt->long) {
+ $c1 = sprintf('--%s', $opt->long);
+ }
+ $c1 .= $opt->renderValueHint();
+
+ return $c1;
+ }
+
+ /**
+ * render option descriptions.
+ *
+ * @return string output
+ */
+ public function render(OptionCollection $options)
+ {
+ # echo "* Available options:\n";
+ $lines = array();
+ foreach ($options as $option) {
+ $c1 = $this->renderOption($option);
+ $lines[] = "\t".$c1;
+ $lines[] = wordwrap("\t\t".$option->desc, $this->screenWidth, "\n\t\t"); # wrap text
+ $lines[] = '';
+ }
+
+ return implode("\n", $lines);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionPrinter/OptionPrinter.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionPrinter/OptionPrinter.php
new file mode 100755
index 0000000..4ed0c70
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/OptionPrinter/OptionPrinter.php
@@ -0,0 +1,12 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+use ArrayIterator;
+use ArrayAccess;
+use IteratorAggregate;
+use Countable;
+
+/**
+ * Define the getopt parsing result.
+ *
+ * create option result from array()
+ *
+ * OptionResult::create($spec, array(
+ * 'key' => 'value'
+ * ), array( ... arguments ... ) );
+ */
+class OptionResult
+ implements IteratorAggregate, ArrayAccess, Countable
+{
+ /**
+ * @var array option specs, key => Option object
+ * */
+ public $keys = array();
+
+ private $currentKey;
+
+ /* arguments */
+ public $arguments = array();
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->keys);
+ }
+
+ public function count()
+ {
+ return count($this->keys);
+ }
+
+ public function merge(OptionResult $a)
+ {
+ $this->keys = array_merge($this->keys, $a->keys);
+ $this->arguments = array_merge($this->arguments, $a->arguments);
+ }
+
+ public function __isset($key)
+ {
+ return isset($this->keys[$key]);
+ }
+
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ public function get($key)
+ {
+ if (isset($this->keys[$key])) {
+ return $this->keys[$key]->getValue();
+ }
+
+ // verifying if we got a camelCased key: http://stackoverflow.com/a/7599674/102960
+ // get $options->baseDir as $option->{'base-dir'}
+ $parts = preg_split('/(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/', $key);
+ if (sizeof($parts) > 1) {
+ $key = implode('-', array_map('strtolower', $parts));
+ }
+ if (isset($this->keys[$key])) {
+ return $this->keys[$key]->getValue();
+ }
+ }
+
+ public function __set($key, $value)
+ {
+ $this->keys[ $key ] = $value;
+ }
+
+ public function has($key)
+ {
+ return isset($this->keys[ $key ]);
+ }
+
+ public function set($key, Option $value)
+ {
+ $this->keys[ $key ] = $value;
+ }
+
+ public function addArgument(Argument $arg)
+ {
+ $this->arguments[] = $arg;
+ }
+
+ public function getArguments()
+ {
+ return array_map(function ($e) { return $e->__toString(); }, $this->arguments);
+ }
+
+ public function offsetSet($name, $value)
+ {
+ $this->keys[ $name ] = $value;
+ }
+
+ public function offsetExists($name)
+ {
+ return isset($this->keys[ $name ]);
+ }
+
+ public function offsetGet($name)
+ {
+ return $this->keys[ $name ];
+ }
+
+ public function offsetUnset($name)
+ {
+ unset($this->keys[$name]);
+ }
+
+ public function toArray()
+ {
+ $array = array();
+ foreach ($this->keys as $key => $option) {
+ $array[ $key ] = $option->getValue();
+ }
+
+ return $array;
+ }
+
+ public static function create($specs, array $values = array(), array $arguments = null)
+ {
+ $new = new self();
+ foreach ($specs as $spec) {
+ $id = $spec->getId();
+ if (isset($values[$id])) {
+ $new->$id = $spec;
+ $spec->setValue($values[$id]);
+ }
+ if ($arguments) {
+ foreach ($arguments as $arg) {
+ $new->addArgument(new Argument($arg));
+ }
+ }
+ }
+
+ return $new;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/BaseType.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/BaseType.php
new file mode 100755
index 0000000..98477bf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/BaseType.php
@@ -0,0 +1,34 @@
+option = $option;
+ }
+ }
+
+ /**
+ * Test a value to see if it fit the type.
+ *
+ * @param mixed $value
+ */
+ abstract public function test($value);
+
+ /**
+ * Parse a string value into it's type value.
+ *
+ * @param mixed $value
+ */
+ abstract public function parse($value);
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/BoolType.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/BoolType.php
new file mode 100755
index 0000000..e2922d8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/BoolType.php
@@ -0,0 +1,10 @@
+ DateTime::ATOM,
+ );
+
+ public function test($value)
+ {
+ return DateTime::createFromFormat($this->option['format'], $value) !== false;
+ }
+
+ public function parse($value)
+ {
+ return DateTime::createFromFormat($this->option['format'], $value);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/DateType.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/DateType.php
new file mode 100755
index 0000000..d168ef4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/DateType.php
@@ -0,0 +1,20 @@
+ 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public function parse($value)
+ {
+ return date_parse($value);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/DirType.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/DirType.php
new file mode 100755
index 0000000..de28279
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/DirType.php
@@ -0,0 +1,18 @@
+option = $option;
+ }
+
+ public function test($value)
+ {
+ return preg_match($this->option, $value) !== 0;
+ }
+
+ public function parse($value)
+ {
+ preg_match($this->option, $value, $this->matches);
+ return strval($value);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/StringType.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/StringType.php
new file mode 100755
index 0000000..cf41b7b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/src/ValueType/StringType.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+use GetOptionKit\Argument;
+class ArgumentTest extends \PHPUnit\Framework\TestCase
+{
+ function test()
+ {
+ $arg = new Argument( '--option' );
+ $this->assertTrue( $arg->isLongOption() );
+ $this->assertFalse( $arg->isShortOption() );
+ $this->assertEquals('option' , $arg->getOptionName());
+
+ $this->assertEquals(null, $arg->getOptionValue());
+ }
+
+ function test2()
+ {
+ $arg = new Argument('--option=value');
+ $this->assertNotNull( $arg->containsOptionValue() );
+ $this->assertEquals('value' , $arg->getOptionValue());
+ $this->assertEquals('option' , $arg->getOptionName());
+ }
+
+ function test3()
+ {
+ $arg = new Argument( '-abc' );
+ $this->assertNotNull( $arg->withExtraFlagOptions() );
+
+ $args = $arg->extractExtraFlagOptions();
+ $this->assertNotNull( $args );
+ $this->assertCount( 2, $args );
+
+ $this->assertEquals( '-b', $args[0] );
+ $this->assertEquals( '-c', $args[1] );
+ $this->assertEquals( '-a', $arg->arg);
+ }
+
+ function testZeroValue()
+ {
+ $arg = new Argument( '0' );
+ $this->assertFalse( $arg->isShortOption() );
+ $this->assertFalse( $arg->isLongOption() );
+ $this->assertFalse( $arg->isEmpty() );
+ }
+}
+
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/ContinuousOptionParserTest.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/ContinuousOptionParserTest.php
new file mode 100755
index 0000000..b1c2ad1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/ContinuousOptionParserTest.php
@@ -0,0 +1,335 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace tests\GetOptionKit;
+use GetOptionKit\ContinuousOptionParser;
+use GetOptionKit\OptionCollection;
+
+class ContinuousOptionParserTest extends \PHPUnit\Framework\TestCase
+{
+
+ public function testOptionCollection()
+ {
+ $specs = new OptionCollection;
+ $specVerbose = $specs->add('v|verbose');
+ $specColor = $specs->add('c|color');
+ $specDebug = $specs->add('d|debug');
+ }
+
+
+
+ public function argumentProvider()
+ {
+ return [
+ [
+ ['program','subcommand1', 'arg1', 'arg2', 'arg3', 'subcommand2', '-b', 1, 'subcommand3', '-b', 2],
+ [
+ 'args' => ['arg1', 'arg2', 'arg3']
+ ],
+ ],
+ [
+ ['program','-v', '-c', 'subcommand1', '--as', 99, 'arg1', 'arg2', 'arg3'],
+ [
+ 'app' => ['verbose' => true ],
+ 'args' => ['arg1', 'arg2', 'arg3']
+ ],
+ ],
+ [
+ ['program','-v', '-c', 'subcommand1', '--as', 99, 'arg1', 'arg2', 'arg3', '--','zz','xx','vv'],
+ [
+ 'app' => ['verbose' => true],
+ 'args' => ['arg1', 'arg2', 'arg3']
+ ],
+ ],
+ ];
+ }
+
+
+
+ /**
+ * @dataProvider argumentProvider
+ */
+ public function testParseSubCommandOptions($argv, $expected)
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('as:');
+ $cmdspecs->add('b:');
+ $cmdspecs->add('c:');
+ $cmdspecs->add('def:')->isa('number')->defaultValue(3);
+
+ $parser = new ContinuousOptionParser( $appspecs );
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => clone $cmdspecs,
+ 'subcommand2' => clone $cmdspecs,
+ 'subcommand3' => clone $cmdspecs,
+ );
+ $subcommand_options = array();
+
+ // $argv = explode(' ','program -v -c subcommand1 --as 99 arg1 arg2 arg3 -- zz xx vv');
+ // $argv = explode(' ','program subcommand1 -a 1 subcommand2 -a 2 subcommand3 -a 3 arg1 arg2 arg3');
+ $app_options = $parser->parse( $argv );
+ $arguments = array();
+ while (! $parser->isEnd()) {
+ if (!empty($subcommands) && $parser->getCurrentArgument() == $subcommands[0]) {
+ $parser->advance();
+ $subcommand = array_shift($subcommands);
+ $parser->setSpecs($subcommand_specs[$subcommand]);
+ $subcommand_options[$subcommand] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+ $this->assertSame($expected['args'], $arguments);
+ if (isset($expected['app'])) {
+ foreach ($expected['app'] as $k => $v) {
+ $this->assertEquals($v, $app_options->get($k));
+ }
+ }
+
+ // $this->assertEquals(99, $subcommand_options['subcommand1']->as);
+ }
+
+
+
+ public function testParser3()
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('n|name:=string');
+ $cmdspecs->add('p|phone:=string');
+ $cmdspecs->add('a|address:=string');
+
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => $cmdspecs,
+ 'subcommand2' => $cmdspecs,
+ 'subcommand3' => $cmdspecs,
+ );
+ $subcommand_options = array();
+ $arguments = array();
+
+ $argv = explode(' ','program -v -d -c subcommand1 --name=c9s --phone=123123123 --address=somewhere arg1 arg2 arg3');
+ $parser = new ContinuousOptionParser( $appspecs );
+ $app_options = $parser->parse( $argv );
+ while (! $parser->isEnd()) {
+ if (@$subcommands[0] && $parser->getCurrentArgument() == $subcommands[0]) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs( $subcommand_specs[$subcommand] );
+ $subcommand_options[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+
+ $this->assertCount(3, $arguments);
+ $this->assertEquals('arg1', $arguments[0]);
+ $this->assertEquals('arg2', $arguments[1]);
+ $this->assertEquals('arg3', $arguments[2]);
+
+ $this->assertNotNull($subcommand_options['subcommand1']);
+ $this->assertEquals('c9s', $subcommand_options['subcommand1']->name );
+ $this->assertEquals('123123123', $subcommand_options['subcommand1']->phone );
+ $this->assertEquals('somewhere', $subcommand_options['subcommand1']->address );
+ }
+
+
+ /* test parser without options */
+ function testParser4()
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('a:'); // required
+ $cmdspecs->add('b?'); // optional
+ $cmdspecs->add('c+'); // multiple (required)
+
+
+
+ $parser = new ContinuousOptionParser( $appspecs );
+ $this->assertNotNull( $parser );
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => clone $cmdspecs,
+ 'subcommand2' => clone $cmdspecs,
+ 'subcommand3' => clone $cmdspecs,
+ );
+ $subcommand_options = array();
+
+ $argv = explode(' ','program subcommand1 subcommand2 subcommand3 -a a -b b -c c');
+ $app_options = $parser->parse( $argv );
+ $arguments = array();
+ while( ! $parser->isEnd() ) {
+ if( @$subcommands[0] && $parser->getCurrentArgument() == $subcommands[0] ) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs( $subcommand_specs[$subcommand] );
+ $subcommand_options[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+
+ $this->assertNotNull( $subcommand_options );
+ $this->assertNotNull( $subcommand_options['subcommand1'] );
+ $this->assertNotNull( $subcommand_options['subcommand2'] );
+ $this->assertNotNull( $subcommand_options['subcommand3'] );
+
+ $r = $subcommand_options['subcommand3'];
+ $this->assertNotNull( $r );
+
+
+
+ $this->assertNotNull( $r->a , 'option a' );
+ $this->assertNotNull( $r->b , 'option b' );
+ $this->assertNotNull( $r->c , 'option c' );
+
+ $this->assertEquals( 'a', $r->a );
+ $this->assertEquals( 'b', $r->b );
+ $this->assertEquals( 'c', $r->c[0] );
+ }
+
+ /* test parser without options */
+ function testParser5()
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('a:');
+ $cmdspecs->add('b');
+ $cmdspecs->add('c');
+
+ $parser = new ContinuousOptionParser( $appspecs );
+ $this->assertNotNull( $parser );
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => clone $cmdspecs,
+ 'subcommand2' => clone $cmdspecs,
+ 'subcommand3' => clone $cmdspecs,
+ );
+ $subcommand_options = array();
+
+ $argv = explode(' ','program subcommand1 -a 1 subcommand2 -a 2 subcommand3 -a 3 arg1 arg2 arg3');
+ $app_options = $parser->parse( $argv );
+ $arguments = array();
+ while (! $parser->isEnd()) {
+ if (!empty($subcommands) && $parser->getCurrentArgument() == $subcommands[0] ) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs($subcommand_specs[$subcommand]);
+ $subcommand_options[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+
+ $this->assertEquals( 'arg1', $arguments[0] );
+ $this->assertEquals( 'arg2', $arguments[1] );
+ $this->assertEquals( 'arg3', $arguments[2] );
+ $this->assertNotNull( $subcommand_options );
+
+ $this->assertEquals(1, $subcommand_options['subcommand1']->a);
+ $this->assertNotNull( 2, $subcommand_options['subcommand2']->a );
+ $this->assertNotNull( 3, $subcommand_options['subcommand3']->a );
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionException
+ */
+ public function testParseInvalidOptionException()
+ {
+ $parser = new ContinuousOptionParser(new OptionCollection);
+ $parser->parse(array('app','--foo'));
+ $arguments = array();
+ while (!$parser->isEnd())
+ {
+ $arguments[] = $parser->getCurrentArgument();
+ $parser->advance();
+ }
+ }
+
+
+
+ public function testMultipleShortOption()
+ {
+ $options = new OptionCollection;
+ $options->add("a");
+ $options->add("b");
+ $options->add("c");
+
+ $parser = new ContinuousOptionParser($options);
+
+ $result = $parser->parse(array('app', '-ab', 'foo', 'bar'));
+ while (!$parser->isEnd())
+ {
+ $arguments[] = $parser->getCurrentArgument();
+ $parser->advance();
+ }
+
+ $this->assertTrue($result->keys["a"]->value);
+ $this->assertTrue($result->keys["b"]->value);
+ }
+
+ public function testIncrementalValue()
+ {
+ $options = new OptionCollection;
+ $options->add("v|verbose")->incremental();
+ $parser = new ContinuousOptionParser($options);
+ $result = $parser->parse(array('app', '-vvv'));
+ $this->assertEquals(3, $result->keys["verbose"]->value);
+ }
+
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionException
+ */
+ public function testUnknownOption()
+ {
+ $options = new OptionCollection;
+ $options->add("v|verbose");
+ $parser = new ContinuousOptionParser($options);
+ $result = $parser->parse(array('app', '-b'));
+ }
+
+ /**
+ * @expectedException LogicException
+ */
+ public function testAdvancedOutOfBounds()
+ {
+ $options = new OptionCollection;
+ $options->add("v|verbose");
+ $parser = new ContinuousOptionParser($options);
+ $result = $parser->parse(array('app', '-v'));
+ $parser->advance();
+ }
+
+}
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionCollectionTest.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionCollectionTest.php
new file mode 100755
index 0000000..542b293
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionCollectionTest.php
@@ -0,0 +1,46 @@
+add($o = new Option('v|verbose'));
+ $this->assertSame($o, $opts->getLongOption('verbose'));
+ $this->assertSame($o, $opts->getShortOption('v'));
+ }
+
+
+ /**
+ * @expectedException LogicException
+ */
+ public function testAddInvalidOption()
+ {
+ $opts = new OptionCollection;
+ $opts->add(123);
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\OptionConflictException
+ */
+ public function testOptionConflictShort()
+ {
+ $opts = new OptionCollection;
+ $opts->add('r|repeat');
+ $opts->add('t|time');
+ $opts->add('r|regex');
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\OptionConflictException
+ */
+ public function testOptionConflictLong()
+ {
+ $opts = new OptionCollection;
+ $opts->add('r|repeat');
+ $opts->add('t|time');
+ $opts->add('c|repeat');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionParserTest.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionParserTest.php
new file mode 100755
index 0000000..5d5a9b9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionParserTest.php
@@ -0,0 +1,477 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+use GetOptionKit\InvalidOptionValue;
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\Option;
+
+class OptionParserTest extends \PHPUnit\Framework\TestCase
+{
+ public $parser;
+ public $specs;
+
+ public function setUp()
+ {
+ $this->specs = new OptionCollection;
+ $this->parser = new OptionParser($this->specs);
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testInvalidOption()
+ {
+ $options = new OptionCollection;
+ $options->addOption(new Option(0));
+ }
+
+
+ public function testResultArrayAccessor()
+ {
+ $options = new OptionCollection;
+ $options->add('n|nice:' , 'I take negative value');
+ $parser = new OptionParser($options);
+ $result = $parser->parse(array('a', '-n', '-1', '--', '......'));
+
+ $this->assertTrue(isset($result->nice));
+ $this->assertTrue($result->has('nice'));
+ $this->assertTrue(isset($result['nice']));
+ $this->assertEquals(-1, $result['nice']->value);
+
+ $res = clone $result['nice'];
+ $res->value = 10;
+ $result['nice'] = $res;
+ $this->assertEquals(10, $result['nice']->value);
+
+ unset($result['nice']);
+ }
+
+ public function testCamelCaseOptionName()
+ {
+ $this->specs->add('base-dir:=dir' , 'I take path');
+ $result = $this->parser->parse(array('a', '--base-dir', 'src'));
+ $this->assertInstanceOf('SplFileInfo', $result->baseDir);
+ }
+
+ public function testOptionWithNegativeValue()
+ {
+ $this->specs->add('n|nice:' , 'I take negative value');
+ $result = $this->parser->parse(array('a', '-n', '-1'));
+ $this->assertEquals(-1, $result->nice);
+ }
+
+ public function testShortOptionName()
+ {
+ $this->specs->add('f:' , 'file');
+ $result = $this->parser->parse(array('a', '-f', 'aaa'));
+ $this->assertEquals('aaa',$result['f']->getValue());
+ }
+
+ public function testOptionWithShortNameAndLongName()
+ {
+ $this->specs->add( 'f|foo' , 'flag' );
+ $result = $this->parser->parse(array('a', '-f'));
+ $this->assertTrue($result->foo);
+
+ $result = $this->parser->parse(array('a', '--foo'));
+ $this->assertTrue($result->foo);
+ }
+
+ public function testSpec()
+ {
+ $options = new OptionCollection;
+ $options->add( 'f|foo:' , 'option require value' );
+ $options->add( 'b|bar+' , 'option with multiple value' );
+ $options->add( 'z|zoo?' , 'option with optional value' );
+ $options->add( 'v|verbose' , 'verbose message' );
+ $options->add( 'd|debug' , 'debug message' );
+ $this->assertEquals(5, $options->size());
+ $this->assertEquals(5, count($options));
+
+
+ $opt = $options->get('foo');
+ $this->assertTrue($opt->isRequired());
+
+ $opt = $options->get('bar');
+ $this->assertTrue( $opt->isMultiple() );
+
+ $opt = $options->get('zoo');
+ $this->assertTrue( $opt->isOptional() );
+
+ $opt = $options->get( 'debug' );
+ $this->assertNotNull( $opt );
+ $this->assertInstanceOf('GetOptionKit\\Option', $opt);
+ $this->assertEquals('debug', $opt->long);
+ $this->assertEquals('d', $opt->short);
+ $this->assertTrue($opt->isFlag());
+
+ return $options;
+ }
+
+ /**
+ * @depends testSpec
+ */
+ public function testOptionFinder($options)
+ {
+ $this->assertNotNull($options->find('f'));
+ $this->assertNotNull($options->find('foo'));
+ $this->assertNull($options->find('xyz'));
+ }
+
+ public function testRequire()
+ {
+ $this->specs->add( 'f|foo:' , 'option require value' );
+ $this->specs->add( 'b|bar+' , 'option with multiple value' );
+ $this->specs->add( 'z|zoo?' , 'option with optional value' );
+ $this->specs->add( 'v|verbose' , 'verbose message' );
+ $this->specs->add( 'd|debug' , 'debug message' );
+
+ $firstExceptionRaised = false;
+ $secondExceptionRaised = false;
+
+ // option required a value should throw an exception
+ try {
+ $result = $this->parser->parse( array('a', '-f' , '-v' , '-d' ) );
+ }
+ catch (Exception $e) {
+ $firstExceptionRaised = true;
+ }
+
+ // even if only one option presented in args array
+ try {
+ $result = $this->parser->parse(array('a','-f'));
+ } catch (Exception $e) {
+ $secondExceptionRaised = true;
+ }
+ if ($firstExceptionRaised && $secondExceptionRaised) {
+ return;
+ }
+ $this->fail('An expected exception has not been raised.');
+ }
+
+ public function testMultiple()
+ {
+ $opt = new OptionCollection;
+ $opt->add( 'b|bar+' , 'option with multiple value' );
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app -b 1 -b 2 --bar 3'));
+ $this->assertNotNull($result->bar);
+ $this->assertCount(3,$result->bar);
+ }
+
+
+ public function testMultipleNumber()
+ {
+ $opt = new OptionCollection;
+ $opt->add('b|bar+=number' , 'option with multiple value');
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app --bar 1 --bar 2 --bar 3'));
+ $this->assertNotNull($result->bar);
+ $this->assertCount(3,$result->bar);
+ $this->assertSame(array(1,2,3),$result->bar);
+ }
+
+ public function testSimpleOptionWithDefaultValue()
+ {
+ $opts = new OptionCollection;
+ $opts->add('p|proc=number' , 'option with required value')
+ ->defaultValue(10)
+ ;
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app'));
+ $this->assertEquals(10, $result['proc']->value);
+ }
+
+ public function testOptionalOptionWithDefaultValue()
+ {
+ $opts = new OptionCollection;
+ $opts->add('p|proc?=number' , 'option with required value')
+ ->defaultValue(10)
+ ;
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app --proc'));
+ $this->assertEquals(10, $result['proc']->value);
+ }
+
+ public function testMultipleString()
+ {
+ $opts = new OptionCollection;
+ $opts->add('b|bar+=string' , 'option with multiple value');
+ $bar = $opts->get('bar');
+ $this->assertNotNull($bar);
+ $this->assertTrue($bar->isMultiple());
+ $this->assertTrue($bar->isType('string'));
+ $this->assertFalse($bar->isType('number'));
+
+
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app --bar lisa --bar mary --bar john a b c'));
+ $this->assertNotNull($result->bar);
+ $this->assertCount(3,$result->bar);
+ $this->assertSame(array('lisa', 'mary', 'john'),$result->bar);
+ $this->assertSame(array('a','b','c'), $result->getArguments());
+ }
+
+ public function testParseIncrementalOption()
+ {
+ $opts = new OptionCollection;
+ $opts->add('v|verbose' , 'verbose')
+ ->isa("number")
+ ->incremental();
+
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app -vvv arg1 arg2'));
+ $this->assertInstanceOf('GetOptionKit\Option',$result['verbose']);
+ $this->assertNotNull($result['verbose']);
+ $this->assertEquals(3, $result['verbose']->value);
+ }
+
+
+ /**
+ * @expectedException Exception
+ */
+ public function testIntegerTypeNonNumeric()
+ {
+ $opt = new OptionCollection;
+ $opt->add( 'b|bar:=number' , 'option with integer type' );
+
+ $parser = new OptionParser($opt);
+ $spec = $opt->get('bar');
+ $this->assertTrue($spec->isTypeNumber());
+
+ // test non numeric
+ $result = $parser->parse(explode(' ','app -b test'));
+ $this->assertNotNull($result->bar);
+ }
+
+
+ public function testIntegerTypeNumericWithoutEqualSign()
+ {
+ $opt = new OptionCollection;
+ $opt->add('b|bar:=number', 'option with integer type');
+
+ $spec = $opt->get('bar');
+ $this->assertTrue($spec->isTypeNumber());
+
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app -b 123123'));
+ $this->assertNotNull($result);
+ $this->assertEquals(123123, $result->bar);
+ }
+
+ public function testIntegerTypeNumericWithEqualSign()
+ {
+ $opt = new OptionCollection;
+ $opt->add('b|bar:=number' , 'option with integer type');
+
+ $spec = $opt->get('bar');
+ $this->assertTrue($spec->isTypeNumber());
+
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app -b=123123'));
+ $this->assertNotNull($result);
+ $this->assertNotNull($result->bar);
+ $this->assertEquals(123123, $result->bar);
+ }
+
+ public function testStringType()
+ {
+ $this->specs->add( 'b|bar:=string' , 'option with type' );
+
+ $spec = $this->specs->get('bar');
+
+ $result = $this->parser->parse(explode(' ','app -b text arg1 arg2 arg3'));
+ $this->assertNotNull($result->bar);
+
+ $result = $this->parser->parse(explode(' ','app -b=text arg1 arg2 arg3'));
+ $this->assertNotNull($result->bar);
+
+ $args = $result->getArguments();
+ $this->assertNotEmpty($args);
+ $this->assertCount(3,$args);
+ $this->assertEquals('arg1', $args[0]);
+ $this->assertEquals('arg2', $args[1]);
+ $this->assertEquals('arg3', $args[2]);
+ }
+
+ public function testStringQuoteOptionValue()
+ {
+ $opts = new OptionCollection();
+ $opts->add('f|foo:' , 'option requires a value.');
+ $parser = new OptionParser($opts);
+ $res = $parser->parse(['app','--foo=aa bb cc']);
+ $this->assertEquals('aa bb cc', $res->get('foo'));
+ }
+
+ public function testSpec2()
+ {
+ $this->specs->add('long' , 'long option name only.');
+ $this->specs->add('a' , 'short option name only.');
+ $this->specs->add('b' , 'short option name only.');
+ $this->assertNotNull($this->specs->all());
+ $this->assertNotNull($this->specs);
+ $this->assertNotNull($result = $this->parser->parse(explode(' ','app -a -b --long')) );
+ $this->assertNotNull($result->a);
+ $this->assertNotNull($result->b);
+ }
+
+
+ public function testSpecCollection()
+ {
+ $this->specs->add( 'f|foo:' , 'option requires a value.' );
+ $this->specs->add( 'b|bar+' , 'option with multiple value.' );
+ $this->specs->add( 'z|zoo?' , 'option with optional value.' );
+ $this->specs->add( 'v|verbose' , 'verbose message.' );
+ $this->specs->add( 'd|debug' , 'debug message.' );
+ $this->specs->add( 'long' , 'long option name only.' );
+ $this->specs->add( 's' , 'short option name only.' );
+
+ $this->assertNotNull( $this->specs->all() );
+ $this->assertNotNull( $this->specs );
+
+ $this->assertCount( 7 , $array = $this->specs->toArray() );
+ $this->assertNotEmpty( isset($array[0]['long'] ));
+ $this->assertNotEmpty( isset($array[0]['short'] ));
+ $this->assertNotEmpty( isset($array[0]['desc'] ));
+ }
+
+ public function optionTestProvider()
+ {
+ return array(
+ array( 'foo', 'simple boolean option', 'foo', true,
+ [['a','--foo','a', 'b', 'c']]
+ ),
+ array( 'f|foo', 'simple boolean option', 'foo', true,
+ [['a','--foo'], ['a','-f']]
+ ),
+ array( 'f|foo:=string', 'string option', 'foo', 'xxx',
+ [['a','--foo','xxx'], ['a','-f', 'xxx']]
+ ),
+ array( 'f|foo:=string', 'string option', 'foo', 'xxx',
+ [['a','b', 'c', '--foo','xxx'], ['a', 'a', 'b', 'c', '-f', 'xxx']]
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider optionTestProvider
+ */
+ public function test($specString, $desc, $key, $expectedValue, array $argvList)
+ {
+ $opts = new OptionCollection();
+ $opts->add($specString, $desc);
+ $parser = new OptionParser($opts);
+ foreach ($argvList as $argv) {
+ $res = $parser->parse($argv);
+ $this->assertSame($expectedValue, $res->get($key));
+ }
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testParseWithoutProgramName()
+ {
+ $parser = new OptionParser(new OptionCollection);
+ $parser->parse(array('--foo'));
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionException
+ */
+ public function testParseInvalidOptionException()
+ {
+ $parser = new OptionParser(new OptionCollection);
+ $parser->parse(array('app','--foo'));
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\RequireValueException
+ */
+ public function testParseOptionRequireValueException()
+ {
+ $options = new OptionCollection;
+ $options->add('name:=string', 'name');
+
+ $parser = new OptionParser($options);
+ $parser->parse(array('app','--name'));
+ }
+
+
+
+ public function testMore()
+ {
+ $this->specs->add('f|foo:' , 'option require value' );
+ $this->specs->add('b|bar+' , 'option with multiple value' );
+ $this->specs->add('z|zoo?' , 'option with optional value' );
+ $this->specs->add('v|verbose' , 'verbose message' );
+ $this->specs->add('d|debug' , 'debug message' );
+
+ $result = $this->parser->parse( array('a', '-f' , 'foo value' , '-v' , '-d' ) );
+ $this->assertNotNull($result->foo);
+ $this->assertNotNull($result->verbose);
+ $this->assertNotNull($result->debug);
+ $this->assertEquals( 'foo value', $result->foo );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+
+ foreach ($result as $k => $v) {
+ $this->assertTrue(in_array($k, ['foo','bar','zoo','verbose', 'debug']));
+ $this->assertInstanceOf('GetOptionKit\\Option', $v);
+ }
+ $this->assertSame([
+ 'foo' => 'foo value',
+ 'verbose' => true,
+ 'debug' => true
+ ], $result->toArray());
+
+ $result = $this->parser->parse( array('a', '-f=foo value' , '-v' , '-d' ) );
+ $this->assertNotNull( $result );
+ $this->assertNotNull( $result->foo );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+
+ $this->assertEquals( 'foo value', $result->foo );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+
+ $result = $this->parser->parse( array('a', '-vd' ) );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+ }
+
+ public function testParseAcceptsValidOption()
+ {
+ $this->specs
+ ->add('f:foo', 'test option')
+ ->validator(function($value) {
+ return $value === 'valid-option';
+ });
+
+ $result = $this->parser->parse(array('a', '-f' , 'valid-option'));
+
+ $this->assertArrayHasKey('f', $result);
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionValueException
+ */
+ public function testParseThrowsExceptionOnInvalidOption()
+ {
+ $this->specs
+ ->add('f:foo', 'test option')
+ ->validator(function($value) {
+ return $value === 'valid-option';
+ });
+
+ $this->parser->parse(array('a', '-f' , 'not-a-valid-option'));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionPrinter/ConsoleOptionPrinterTest.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionPrinter/ConsoleOptionPrinterTest.php
new file mode 100755
index 0000000..2f06767
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionPrinter/ConsoleOptionPrinterTest.php
@@ -0,0 +1,32 @@
+add('f|foo:', 'option requires a value.' )
+ ->isa('String');
+
+ $options->add('b|bar+', 'option with multiple value.' )
+ ->isa('Number');
+
+ $options->add('z|zoo?', 'option with optional value.' )
+ ->isa('Boolean')
+ ;
+
+ $options->add('n', 'n flag' );
+
+ $options->add('verbose', 'verbose');
+
+ $options->add('o|output?', 'option with optional value.' )
+ ->isa('File')
+ ->defaultValue('output.txt')
+ ;
+ $printer = new ConsoleOptionPrinter;
+ $output = $printer->render($options);
+ }
+
+}
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionResultTest.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionResultTest.php
new file mode 100755
index 0000000..01be96d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionResultTest.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+class OptionResultTest extends \PHPUnit\Framework\TestCase
+{
+
+ function testOption()
+ {
+ $option = new \GetOptionKit\OptionResult;
+ $this->assertNotNull( $option );
+
+ $specs = new \GetOptionKit\OptionCollection;
+ $specs->add('name:','name');
+ $result = \GetOptionKit\OptionResult::create($specs,array( 'name' => 'c9s' ),array( 'arg1' ));
+ $this->assertNotNull( $result );
+ $this->assertNotNull( $result->arguments );
+ $this->assertNotNull( $result->name );
+ $this->assertEquals( 'c9s', $result->name );
+ $this->assertEquals( $result->arguments[0] , 'arg1' );
+ }
+
+}
+
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionTest.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionTest.php
new file mode 100755
index 0000000..7b06f1d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/OptionTest.php
@@ -0,0 +1,227 @@
+assertNotNull($opt);
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testInvalidOptionSpec()
+ {
+ new Option('....');
+ }
+
+ public function testValueName()
+ {
+ $opt = new Option('z');
+ $opt->defaultValue(10);
+ $opt->valueName('priority');
+ $this->assertEquals('[=priority]', $opt->renderValueHint());
+ $this->assertEquals('-z[=priority]', $opt->renderReadableSpec());
+ }
+
+
+ public function testDefaultValue()
+ {
+ $opt = new Option('z');
+ $opt->defaultValue(10);
+ $this->assertEquals(10, $opt->getValue());
+ $this->assertEquals('-z[=10]',$opt->renderReadableSpec(true));
+ }
+
+ public function testBackwardCompatibleBoolean()
+ {
+ $opt = new Option('scope');
+ $opt->isa('bool');
+ $this->assertEquals('boolean', $opt->isa);
+ $this->assertEquals('--scope=',$opt->renderReadableSpec(true));
+ }
+
+
+
+ public function validatorProvider()
+ {
+ return [
+ [function($a) { return in_array($a, ['public', 'private']); }],
+ [function($a) { return [in_array($a, ['public', 'private']), "message"]; }]
+ ];
+ }
+
+ /**
+ * @dataProvider validatorProvider
+ */
+ public function testValidator($cb)
+ {
+ $opt = new Option('scope');
+ $opt->validator($cb);
+ $ret = $opt->validate('public');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('private');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('foo');
+ $this->assertFalse($ret[0]);
+ $this->assertEquals('--scope', $opt->renderReadableSpec(true));
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testInvalidTypeClass()
+ {
+ $opt = new Option('scope');
+ $opt->isa('SomethingElse');
+ $class = $opt->getTypeClass();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testValidatorReturnValue()
+ {
+ $opt = new Option('scope');
+ $opt->validator(function($val) {
+ return 123454;
+ });
+ $ret = $opt->validate('public');
+ }
+
+ public function testOptionWithoutValidator()
+ {
+ $opt = new Option('scope');
+ $ret = $opt->validate('public');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('private');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('foo');
+ $this->assertTrue($ret[0]);
+ $this->assertEquals('--scope',$opt->renderReadableSpec(true));
+ }
+
+
+
+
+ public function testSuggestionsCallback()
+ {
+ $opt = new Option('scope');
+ $this->assertEmpty($opt->getSuggestions());
+
+ $opt->suggestions(function() {
+ return ['public', 'private'];
+ });
+ $this->assertNotEmpty($opt->getSuggestions());
+ $this->assertSame(['public', 'private'],$opt->getSuggestions());
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+
+ $this->assertEquals('--scope=[public,private]',$opt->renderReadableSpec(true));
+ }
+
+ public function testSuggestions()
+ {
+ $opt = new Option('scope');
+ $opt->suggestions(['public', 'private']);
+ $this->assertNotEmpty($opt->getSuggestions());
+ $this->assertSame(['public', 'private'],$opt->getSuggestions());
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+
+ $this->assertEquals('--scope=[public,private]',$opt->renderReadableSpec(true));
+ }
+
+ public function testValidValuesCallback() {
+ $opt = new Option('scope');
+ $opt->validValues(function() {
+ return ['public', 'private'];
+ });
+ $this->assertNotNull($opt->getValidValues());
+ $this->assertNotEmpty($opt->getValidValues());
+
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+ $this->assertEquals('--scope=(public,private)',$opt->renderReadableSpec(true));
+ }
+
+ public function testTrigger()
+ {
+ $opt = new Option('scope');
+ $opt->validValues([ 'public', 'private' ]);
+
+ $state = 0;
+ $opt->trigger(function($val) use(& $state) {
+ $state++;
+ });
+ $this->assertNotEmpty($opt->getValidValues());
+ $opt->setValue('public');
+
+ $this->assertEquals(1, $state);
+ $opt->setValue('private');
+ $this->assertEquals(2, $state);
+
+ }
+
+
+
+ public function testArrayValueToString()
+ {
+ $opt = new Option('uid');
+ $opt->setValue([1,2,3,4]);
+ $toString = '* key:uid spec:--uid desc:
+ value => 1,2,3,4
+';
+ $this->assertEquals($toString,$opt->__toString());
+ }
+
+ public function testValidValues()
+ {
+ $opt = new Option('scope');
+ $opt->validValues([ 'public', 'private' ])
+ ;
+ $this->assertNotEmpty($opt->getValidValues());
+ $this->assertTrue(is_array($opt->getValidValues()));
+
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+ $this->assertEquals('--scope=(public,private)',$opt->renderReadableSpec(true));
+ $this->assertNotEmpty($opt->__toString());
+ }
+
+
+ public function testFilter() {
+ $opt = new Option('scope');
+ $opt->filter(function($val) {
+ return preg_replace('#a#', 'x', $val);
+ })
+ ;
+ $opt->setValue('aa');
+ $this->assertEquals('xx', $opt->value);
+ }
+}
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/RegexValueTypeTest.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/RegexValueTypeTest.php
new file mode 100755
index 0000000..6917918
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/RegexValueTypeTest.php
@@ -0,0 +1,26 @@
+assertEquals($regex->option, '#^Test$#');
+ }
+
+ public function testValidation()
+ {
+ $regex = new RegexType('#^Test$#');
+ $this->assertTrue($regex->test('Test'));
+ $this->assertFalse($regex->test('test'));
+
+ $regex->option = '/^([a-z]+)$/';
+ $this->assertTrue($regex->test('barfoo'));
+ $this->assertFalse($regex->test('foobar234'));
+ $ret = $regex->parse('foobar234');
+ $this->assertNotNull($ret);
+ }
+}
+
diff --git a/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/ValueTypeTest.php b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/ValueTypeTest.php
new file mode 100755
index 0000000..08472c5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/corneltek/getoptionkit/tests/ValueTypeTest.php
@@ -0,0 +1,200 @@
+assertNotNull( new BooleanType );
+ $this->assertNotNull( new StringType );
+ $this->assertNotNull( new FileType );
+ $this->assertNotNull( new DateType );
+ $this->assertNotNull( new DateTimeType );
+ $this->assertNotNull( new NumberType );
+ $this->assertNotNull( new UrlType );
+ $this->assertNotNull( new IpType );
+ $this->assertNotNull( new Ipv4Type );
+ $this->assertNotNull( new Ipv6Type );
+ $this->assertNotNull( new EmailType );
+ $this->assertNotNull( new PathType );
+ $this->assertNotNull( new RegexType("/[a-z]/"));
+ }
+
+
+ public function testDateTimeType()
+ {
+ $type = new DateTimeType([ 'format' => 'Y-m-d' ]);
+ $this->assertTrue($type->test('2016-12-30'));
+ $a = $type->parse('2016-12-30');
+ $this->assertEquals(2016, $a->format('Y'));
+ $this->assertEquals(12, $a->format('m'));
+ $this->assertEquals(30, $a->format('d'));
+ $this->assertFalse($type->test('foo'));
+ }
+
+ public function testDateType()
+ {
+ $type = new DateType;
+ $this->assertTrue($type->test('2016-12-30'));
+ $a = $type->parse('2016-12-30');
+ $this->assertEquals(2016, $a['year']);
+ $this->assertEquals(12, $a['month']);
+ $this->assertEquals(30, $a['day']);
+ $this->assertFalse($type->test('foo'));
+ }
+
+
+
+ public function booleanTestProvider()
+ {
+ return [
+ [true , true, true],
+ [false , true, false],
+ ['true' , true, true],
+ ['false' , true, false],
+ ['0' , true, false],
+ ['1' , true, true],
+ ['foo' , false, null],
+ ['123' , false, null],
+ ];
+ }
+
+ /**
+ * @dataProvider booleanTestProvider
+ */
+ public function testBooleanType($a, $test, $expected)
+ {
+ $bool = new BooleanType;
+ $this->assertEquals($test, $bool->test($a));
+ if ($bool->test($a)) {
+ $this->assertEquals($expected, $bool->parse($a));
+ }
+ }
+
+ public function testDirType()
+ {
+ $type = new DirType;
+ $this->assertTrue($type->test('tests'));
+ $this->assertFalse($type->test('composer.json'));
+ $this->assertFalse($type->test('foo/bar'));
+ $this->assertInstanceOf('SplFileInfo',$type->parse('tests'));
+ }
+
+ public function testFileType()
+ {
+ $type = new FileType;
+ $this->assertFalse($type->test('tests'));
+ $this->assertTrue($type->test('composer.json'));
+ $this->assertFalse($type->test('foo/bar'));
+ $this->assertInstanceOf('SplFileInfo', $type->parse('composer.json'));
+ }
+
+ public function testPathType()
+ {
+ $type = new PathType;
+ $this->assertTrue($type->test('tests'));
+ $this->assertTrue($type->test('composer.json'));
+ $this->assertFalse($type->test('foo/bar'));
+ $this->assertInstanceOf('SplFileInfo', $type->parse('composer.json'));
+ }
+
+ public function testUrlType()
+ {
+ $url = new UrlType;
+ $this->assertTrue($url->test('http://t'));
+ $this->assertTrue($url->test('http://t.c'));
+ $this->assertFalse($url->test('t.c'));
+ $this->assertEquals('http://t.c', $url->parse('http://t.c'));
+ }
+
+ public function ipV4Provider()
+ {
+ return [
+ ['192.168.25.58', true],
+ ['8.8.8.8', true],
+ ['github.com', false],
+ ];
+ }
+
+ public function ipV6Provider()
+ {
+ return [
+ ['192.168.25.58', false],
+ ['2607:f0d0:1002:51::4', true],
+ ['2607:f0d0:1002:0051:0000:0000:0000:0004', true],
+ ['::1', true],
+ ['10.10.15.10/16', false],
+ ['github.com', false],
+ ];
+ }
+
+ public function ipProvider()
+ {
+ return [
+ ['192.168.25.58', true],
+ ['2607:f0d0:1002:51::4', true],
+ ['::1', true],
+ ['10.10.15.10/16', false],
+ ['github.com', false],
+ ];
+ }
+
+ /**
+ * @dataProvider ipProvider
+ */
+ public function testIpType($ipstr, $pass = true)
+ {
+ $ip = new IpType;
+ $this->assertEquals($pass, $ip->test($ipstr));
+ if ($pass) {
+ $this->assertNotNull($ip->parse($ipstr));
+ }
+ }
+
+ /**
+ * @dataProvider ipV4Provider
+ */
+ public function testIpv4Type($ipstr, $pass = true)
+ {
+ $ipv4 = new Ipv4Type;
+ $this->assertEquals($pass, $ipv4->test($ipstr));
+ if ($pass) {
+ $this->assertNotNull($ipv4->parse($ipstr));
+ }
+ }
+
+ /**
+ * @dataProvider ipV6Provider
+ */
+ public function testIpv6Type($ipstr, $pass = true)
+ {
+ $ipv6 = new Ipv6Type;
+ $this->assertEquals($pass, $ipv6->test($ipstr));
+ if ($pass) {
+ $this->assertNotNull($ipv6->parse($ipstr));
+ }
+ }
+
+ public function testEmailType()
+ {
+ $email = new EmailType;
+ $this->assertTrue($email->test('test@gmail.com'));
+ $this->assertFalse($email->test('test@test'));
+ $email->parse('test@gmail.com');
+ }
+}
+
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/.gitignore b/src/mpg25-instagram-api/vendor/evenement/evenement/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/.travis.yml b/src/mpg25-instagram-api/vendor/evenement/evenement/.travis.yml
new file mode 100755
index 0000000..65ba0ce
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/.travis.yml
@@ -0,0 +1,24 @@
+language: php
+
+php:
+ - 7.0
+ - 7.1
+ - hhvm
+ - nightly
+
+matrix:
+ allow_failures:
+ - php: hhvm
+ - php: nightly
+
+before_script:
+ - wget http://getcomposer.org/composer.phar
+ - php composer.phar install
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
+ - php -n examples/benchmark-emit-no-arguments.php
+ - php -n examples/benchmark-emit-one-argument.php
+ - php -n examples/benchmark-emit.php
+ - php -n examples/benchmark-emit-once.php
+ - php -n examples/benchmark-remove-listener-once.php
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/CHANGELOG.md b/src/mpg25-instagram-api/vendor/evenement/evenement/CHANGELOG.md
new file mode 100755
index 0000000..568f229
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/CHANGELOG.md
@@ -0,0 +1,35 @@
+CHANGELOG
+=========
+
+
+* v3.0.1 (2017-07-23)
+
+ * Resolved regression introduced in once listeners in v3.0.0 [#49](https://github.com/igorw/evenement/pull/49)
+
+* v3.0.0 (2017-07-23)
+
+ * Passing null as event name throw exception [#46](https://github.com/igorw/evenement/pull/46), and [#47](https://github.com/igorw/evenement/pull/47)
+ * Performance improvements [#39](https://github.com/igorw/evenement/pull/39), and [#45](https://github.com/igorw/evenement/pull/45)
+ * Remove once listeners [#44](https://github.com/igorw/evenement/pull/44), [#45](https://github.com/igorw/evenement/pull/45)
+
+* v2.1.0 (2017-07-17)
+
+ * Chaining for "on" method [#30](https://github.com/igorw/evenement/pull/30)
+ * Unit tests (on Travis) improvements [#33](https://github.com/igorw/evenement/pull/33), [#36](https://github.com/igorw/evenement/pull/36), and [#37](https://github.com/igorw/evenement/pull/37)
+ * Benchmarks added [#35](https://github.com/igorw/evenement/pull/35), and [#40](https://github.com/igorw/evenement/pull/40)
+ * Minor performance improvements [#42](https://github.com/igorw/evenement/pull/42), and [#38](https://github.com/igorw/evenement/pull/38)
+
+* v2.0.0 (2012-11-02)
+
+ * Require PHP >=5.4.0
+ * Added EventEmitterTrait
+ * Removed EventEmitter2
+
+* v1.1.0 (2017-07-17)
+
+ * Chaining for "on" method [#29](https://github.com/igorw/evenement/pull/29)
+ * Minor performance improvements [#43](https://github.com/igorw/evenement/pull/43)
+
+* v1.0.0 (2012-05-30)
+
+ * Inital stable release
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/LICENSE b/src/mpg25-instagram-api/vendor/evenement/evenement/LICENSE
new file mode 100755
index 0000000..d9a37d0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/README.md b/src/mpg25-instagram-api/vendor/evenement/evenement/README.md
new file mode 100755
index 0000000..9443011
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/README.md
@@ -0,0 +1,83 @@
+# Événement
+
+Événement is a very simple event dispatching library for PHP.
+
+It has the same design goals as [Silex](http://silex-project.org) and
+[Pimple](http://pimple-project.org), to empower the user while staying concise
+and simple.
+
+It is very strongly inspired by the EventEmitter API found in
+[node.js](http://nodejs.org).
+
+[](http://travis-ci.org/igorw/evenement)
+
+## Fetch
+
+The recommended way to install Événement is [through composer](http://getcomposer.org).
+
+Just create a composer.json file for your project:
+
+```JSON
+{
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0"
+ }
+}
+```
+
+**Note:** The `3.x` version of Événement requires PHP 7 and the `2.x` version requires PHP 5.4. If you are
+using PHP 5.3, please use the `1.x` version:
+
+```JSON
+{
+ "require": {
+ "evenement/evenement": "^1.0"
+ }
+}
+```
+
+And run these two commands to install it:
+
+ $ curl -s http://getcomposer.org/installer | php
+ $ php composer.phar install
+
+Now you can add the autoloader, and you will have access to the library:
+
+```php
+on('user.created', function (User $user) use ($logger) {
+ $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+});
+```
+
+### Emitting Events
+
+```php
+emit('user.created', [$user]);
+```
+
+Tests
+-----
+
+ $ ./vendor/bin/phpunit
+
+License
+-------
+MIT, see LICENSE.
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/composer.json b/src/mpg25-instagram-api/vendor/evenement/evenement/composer.json
new file mode 100755
index 0000000..cbb4827
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "evenement/evenement",
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": ["event-dispatcher", "event-emitter"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "autoload": {
+ "psr-0": {
+ "Evenement": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-0": {
+ "Evenement": "tests"
+ },
+ "files": ["tests/Evenement/Tests/functions.php"]
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/doc/00-intro.md b/src/mpg25-instagram-api/vendor/evenement/evenement/doc/00-intro.md
new file mode 100755
index 0000000..6c28a2a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/doc/00-intro.md
@@ -0,0 +1,28 @@
+# Introduction
+
+Événement is is French and means "event". The événement library aims to
+provide a simple way of subscribing to events and notifying those subscribers
+whenever an event occurs.
+
+The API that it exposes is almost a direct port of the EventEmitter API found
+in node.js. It also includes an "EventEmitter". There are some minor
+differences however.
+
+The EventEmitter is an implementation of the publish-subscribe pattern, which
+is a generalized version of the observer pattern. The observer pattern
+specifies an observable subject, which observers can register themselves to.
+Once something interesting happens, the subject notifies its observers.
+
+Pub/sub takes the same idea but encapsulates the observation logic inside a
+separate object which manages all of its subscribers or listeners. Subscribers
+are bound to an event name, and will only receive notifications of the events
+they subscribed to.
+
+**TLDR: What does evenement do, in short? It provides a mapping from event
+names to a list of listener functions and triggers each listener for a given
+event when it is emitted.**
+
+Why do we do this, you ask? To achieve decoupling.
+
+It allows you to design a system where the core will emit events, and modules
+are able to subscribe to these events. And respond to them.
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/doc/01-api.md b/src/mpg25-instagram-api/vendor/evenement/evenement/doc/01-api.md
new file mode 100755
index 0000000..17ba333
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/doc/01-api.md
@@ -0,0 +1,91 @@
+# API
+
+The API that événement exposes is defined by the
+`Evenement\EventEmitterInterface`. The interface is useful if you want to
+define an interface that extends the emitter and implicitly defines certain
+events to be emitted, or if you want to type hint an `EventEmitter` to be
+passed to a method without coupling to the specific implementation.
+
+## on($event, callable $listener)
+
+Allows you to subscribe to an event.
+
+Example:
+
+```php
+$emitter->on('user.created', function (User $user) use ($logger) {
+ $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+});
+```
+
+Since the listener can be any callable, you could also use an instance method
+instead of the anonymous function:
+
+```php
+$loggerSubscriber = new LoggerSubscriber($logger);
+$emitter->on('user.created', array($loggerSubscriber, 'onUserCreated'));
+```
+
+This has the benefit that listener does not even need to know that the emitter
+exists.
+
+You can also accept more than one parameter for the listener:
+
+```php
+$emitter->on('numbers_added', function ($result, $a, $b) {});
+```
+
+## once($event, callable $listener)
+
+Convenience method that adds a listener which is guaranteed to only be called
+once.
+
+Example:
+
+```php
+$conn->once('connected', function () use ($conn, $data) {
+ $conn->send($data);
+});
+```
+
+## emit($event, array $arguments = [])
+
+Emit an event, which will call all listeners.
+
+Example:
+
+```php
+$conn->emit('data', [$data]);
+```
+
+The second argument to emit is an array of listener arguments. This is how you
+specify more args:
+
+```php
+$result = $a + $b;
+$emitter->emit('numbers_added', [$result, $a, $b]);
+```
+
+## listeners($event)
+
+Allows you to inspect the listeners attached to an event. Particularly useful
+to check if there are any listeners at all.
+
+Example:
+
+```php
+$e = new \RuntimeException('Everything is broken!');
+if (0 === count($emitter->listeners('error'))) {
+ throw $e;
+}
+```
+
+## removeListener($event, callable $listener)
+
+Remove a specific listener for a specific event.
+
+## removeAllListeners($event = null)
+
+Remove all listeners for a specific event or all listeners all together. This
+is useful for long-running processes, where you want to remove listeners in
+order to allow them to get garbage collected.
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/doc/02-plugin-system.md b/src/mpg25-instagram-api/vendor/evenement/evenement/doc/02-plugin-system.md
new file mode 100755
index 0000000..6a08371
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/doc/02-plugin-system.md
@@ -0,0 +1,155 @@
+# Example: Plugin system
+
+In this example I will show you how to create a generic plugin system with
+événement where plugins can alter the behaviour of the app. The app is a blog.
+Boring, I know. By using the EventEmitter it will be easy to extend this blog
+with additional functionality without modifying the core system.
+
+The blog is quite basic. Users are able to create blog posts when they log in.
+The users are stored in a static config file, so there is no sign up process.
+Once logged in they get a "new post" link which gives them a form where they
+can create a new blog post with plain HTML. That will store the post in a
+document database. The index lists all blog post titles by date descending.
+Clicking on the post title will take you to the full post.
+
+## Plugin structure
+
+The goal of the plugin system is to allow features to be added to the blog
+without modifying any core files of the blog.
+
+The plugins are managed through a config file, `plugins.json`. This JSON file
+contains a JSON-encoded list of class-names for plugin classes. This allows
+you to enable and disable plugins in a central location. The initial
+`plugins.json` is just an empty array:
+```json
+[]
+```
+
+A plugin class must implement the `PluginInterface`:
+```php
+interface PluginInterface
+{
+ function attachEvents(EventEmitterInterface $emitter);
+}
+```
+
+The `attachEvents` method allows the plugin to attach any events to the
+emitter. For example:
+```php
+class FooPlugin implements PluginInterface
+{
+ public function attachEvents(EventEmitterInterface $emitter)
+ {
+ $emitter->on('foo', function () {
+ echo 'bar!';
+ });
+ }
+}
+```
+
+The blog system creates an emitter instance and loads the plugins:
+```php
+$emitter = new EventEmitter();
+
+$pluginClasses = json_decode(file_get_contents('plugins.json'), true);
+foreach ($pluginClasses as $pluginClass) {
+ $plugin = new $pluginClass();
+ $pluginClass->attachEvents($emitter);
+}
+```
+
+This is the base system. There are no plugins yet, and there are no events yet
+either. That's because I don't know which extension points will be needed. I
+will add them on demand.
+
+## Feature: Markdown
+
+Writing blog posts in HTML sucks! Wouldn't it be great if I could write them
+in a nice format such as markdown, and have that be converted to HTML for me?
+
+This feature will need two extension points. I need to be able to mark posts
+as markdown, and I need to be able to hook into the rendering of the post body
+and convert it from markdown to HTML. So the blog needs two new events:
+`post.create` and `post.render`.
+
+In the code that creates the post, I'll insert the `post.create` event:
+```php
+class PostEvent
+{
+ public $post;
+
+ public function __construct(array $post)
+ {
+ $this->post = $post;
+ }
+}
+
+$post = createPostFromRequest($_POST);
+
+$event = new PostEvent($post);
+$emitter->emit('post.create', [$event]);
+$post = $event->post;
+
+$db->save('post', $post);
+```
+
+This shows that you can wrap a value in an event object to make it mutable,
+allowing listeners to change it.
+
+The same thing for the `post.render` event:
+```php
+public function renderPostBody(array $post)
+{
+ $emitter = $this->emitter;
+
+ $event = new PostEvent($post);
+ $emitter->emit('post.render', [$event]);
+ $post = $event->post;
+
+ return $post['body'];
+}
+
+
= $post['title'] %>
+
= renderPostBody($post) %>
+```
+
+Ok, the events are in place. It's time to create the first plugin, woohoo! I
+will call this the `MarkdownPlugin`, so here's `plugins.json`:
+```json
+[
+ "MarkdownPlugin"
+]
+```
+
+The `MarkdownPlugin` class will be autoloaded, so I don't have to worry about
+including any files. I just have to worry about implementing the plugin class.
+The `markdown` function represents a markdown to HTML converter.
+```php
+class MarkdownPlugin implements PluginInterface
+{
+ public function attachEvents(EventEmitterInterface $emitter)
+ {
+ $emitter->on('post.create', function (PostEvent $event) {
+ $event->post['format'] = 'markdown';
+ });
+
+ $emitter->on('post.render', function (PostEvent $event) {
+ if (isset($event->post['format']) && 'markdown' === $event->post['format']) {
+ $event->post['body'] = markdown($event->post['body']);
+ }
+ });
+ }
+}
+```
+
+There you go, the blog now renders posts as markdown. But all of the previous
+posts before the addition of the markdown plugin are still rendered correctly
+as raw HTML.
+
+## Feature: Comments
+
+TODO
+
+## Feature: Comment spam control
+
+TODO
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php
new file mode 100755
index 0000000..53d7f4b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function () {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->emit('event');
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit-once.php b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit-once.php
new file mode 100755
index 0000000..74f4d17
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit-once.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+ini_set('memory_limit', '512M');
+
+const ITERATIONS = 100000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->once('event', function ($a, $b, $c) {});
+}
+
+$start = microtime(true);
+$emitter->emit('event', [1, 2, 3]);
+$time = microtime(true) - $start;
+
+echo 'Emitting one event to ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php
new file mode 100755
index 0000000..39fc4ba
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function ($a) {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->emit('event', [1]);
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit.php b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit.php
new file mode 100755
index 0000000..3ab639e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-emit.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function ($a, $b, $c) {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->emit('event', [1, 2, 3]);
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php
new file mode 100755
index 0000000..414be3b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+ini_set('memory_limit', '512M');
+
+const ITERATIONS = 100000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$listeners = [];
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $listeners[] = function ($a, $b, $c) {};
+}
+
+$start = microtime(true);
+foreach ($listeners as $listener) {
+ $emitter->once('event', $listener);
+}
+$time = microtime(true) - $start;
+echo 'Adding ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
+
+$start = microtime(true);
+foreach ($listeners as $listener) {
+ $emitter->removeListener('event', $listener);
+}
+$time = microtime(true) - $start;
+echo 'Removing ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/evenement/evenement/phpunit.xml.dist
new file mode 100755
index 0000000..70bc693
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/phpunit.xml.dist
@@ -0,0 +1,24 @@
+
+
+
+
+
+ ./tests/Evenement/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/src/Evenement/EventEmitter.php b/src/mpg25-instagram-api/vendor/evenement/evenement/src/Evenement/EventEmitter.php
new file mode 100755
index 0000000..db189b9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/src/Evenement/EventEmitter.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+class EventEmitter implements EventEmitterInterface
+{
+ use EventEmitterTrait;
+}
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php b/src/mpg25-instagram-api/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
new file mode 100755
index 0000000..310631a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+interface EventEmitterInterface
+{
+ public function on($event, callable $listener);
+ public function once($event, callable $listener);
+ public function removeListener($event, callable $listener);
+ public function removeAllListeners($event = null);
+ public function listeners($event = null);
+ public function emit($event, array $arguments = []);
+}
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php b/src/mpg25-instagram-api/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
new file mode 100755
index 0000000..a78e65c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
@@ -0,0 +1,135 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+use InvalidArgumentException;
+
+trait EventEmitterTrait
+{
+ protected $listeners = [];
+ protected $onceListeners = [];
+
+ public function on($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->listeners[$event])) {
+ $this->listeners[$event] = [];
+ }
+
+ $this->listeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function once($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->onceListeners[$event])) {
+ $this->onceListeners[$event] = [];
+ }
+
+ $this->onceListeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function removeListener($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (isset($this->listeners[$event])) {
+ $index = \array_search($listener, $this->listeners[$event], true);
+ if (false !== $index) {
+ unset($this->listeners[$event][$index]);
+ if (\count($this->listeners[$event]) === 0) {
+ unset($this->listeners[$event]);
+ }
+ }
+ }
+
+ if (isset($this->onceListeners[$event])) {
+ $index = \array_search($listener, $this->onceListeners[$event], true);
+ if (false !== $index) {
+ unset($this->onceListeners[$event][$index]);
+ if (\count($this->onceListeners[$event]) === 0) {
+ unset($this->onceListeners[$event]);
+ }
+ }
+ }
+ }
+
+ public function removeAllListeners($event = null)
+ {
+ if ($event !== null) {
+ unset($this->listeners[$event]);
+ } else {
+ $this->listeners = [];
+ }
+
+ if ($event !== null) {
+ unset($this->onceListeners[$event]);
+ } else {
+ $this->onceListeners = [];
+ }
+ }
+
+ public function listeners($event = null): array
+ {
+ if ($event === null) {
+ $events = [];
+ $eventNames = \array_unique(
+ \array_merge(\array_keys($this->listeners), \array_keys($this->onceListeners))
+ );
+ foreach ($eventNames as $eventName) {
+ $events[$eventName] = \array_merge(
+ isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [],
+ isset($this->onceListeners[$eventName]) ? $this->onceListeners[$eventName] : []
+ );
+ }
+ return $events;
+ }
+
+ return \array_merge(
+ isset($this->listeners[$event]) ? $this->listeners[$event] : [],
+ isset($this->onceListeners[$event]) ? $this->onceListeners[$event] : []
+ );
+ }
+
+ public function emit($event, array $arguments = [])
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (isset($this->listeners[$event])) {
+ foreach ($this->listeners[$event] as $listener) {
+ $listener(...$arguments);
+ }
+ }
+
+ if (isset($this->onceListeners[$event])) {
+ $listeners = $this->onceListeners[$event];
+ unset($this->onceListeners[$event]);
+ foreach ($listeners as $listener) {
+ $listener(...$arguments);
+ }
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php b/src/mpg25-instagram-api/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php
new file mode 100755
index 0000000..28f3011
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php
@@ -0,0 +1,438 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+use Evenement\EventEmitter;
+use InvalidArgumentException;
+use PHPUnit\Framework\TestCase;
+
+class EventEmitterTest extends TestCase
+{
+ private $emitter;
+
+ public function setUp()
+ {
+ $this->emitter = new EventEmitter();
+ }
+
+ public function testAddListenerWithLambda()
+ {
+ $this->emitter->on('foo', function () {});
+ }
+
+ public function testAddListenerWithMethod()
+ {
+ $listener = new Listener();
+ $this->emitter->on('foo', [$listener, 'onFoo']);
+ }
+
+ public function testAddListenerWithStaticMethod()
+ {
+ $this->emitter->on('bar', ['Evenement\Tests\Listener', 'onBar']);
+ }
+
+ public function testAddListenerWithInvalidListener()
+ {
+ try {
+ $this->emitter->on('foo', 'not a callable');
+ $this->fail();
+ } catch (\Exception $e) {
+ } catch (\TypeError $e) {
+ }
+ }
+
+ public function testOnce()
+ {
+ $listenerCalled = 0;
+
+ $this->emitter->once('foo', function () use (&$listenerCalled) {
+ $listenerCalled++;
+ });
+
+ $this->assertSame(0, $listenerCalled);
+
+ $this->emitter->emit('foo');
+
+ $this->assertSame(1, $listenerCalled);
+
+ $this->emitter->emit('foo');
+
+ $this->assertSame(1, $listenerCalled);
+ }
+
+ public function testOnceWithArguments()
+ {
+ $capturedArgs = [];
+
+ $this->emitter->once('foo', function ($a, $b) use (&$capturedArgs) {
+ $capturedArgs = array($a, $b);
+ });
+
+ $this->emitter->emit('foo', array('a', 'b'));
+
+ $this->assertSame(array('a', 'b'), $capturedArgs);
+ }
+
+ public function testEmitWithoutArguments()
+ {
+ $listenerCalled = false;
+
+ $this->emitter->on('foo', function () use (&$listenerCalled) {
+ $listenerCalled = true;
+ });
+
+ $this->assertSame(false, $listenerCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(true, $listenerCalled);
+ }
+
+ public function testEmitWithOneArgument()
+ {
+ $test = $this;
+
+ $listenerCalled = false;
+
+ $this->emitter->on('foo', function ($value) use (&$listenerCalled, $test) {
+ $listenerCalled = true;
+
+ $test->assertSame('bar', $value);
+ });
+
+ $this->assertSame(false, $listenerCalled);
+ $this->emitter->emit('foo', ['bar']);
+ $this->assertSame(true, $listenerCalled);
+ }
+
+ public function testEmitWithTwoArguments()
+ {
+ $test = $this;
+
+ $listenerCalled = false;
+
+ $this->emitter->on('foo', function ($arg1, $arg2) use (&$listenerCalled, $test) {
+ $listenerCalled = true;
+
+ $test->assertSame('bar', $arg1);
+ $test->assertSame('baz', $arg2);
+ });
+
+ $this->assertSame(false, $listenerCalled);
+ $this->emitter->emit('foo', ['bar', 'baz']);
+ $this->assertSame(true, $listenerCalled);
+ }
+
+ public function testEmitWithNoListeners()
+ {
+ $this->emitter->emit('foo');
+ $this->emitter->emit('foo', ['bar']);
+ $this->emitter->emit('foo', ['bar', 'baz']);
+ }
+
+ public function testEmitWithTwoListeners()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(2, $listenersCalled);
+ }
+
+ public function testRemoveListenerMatching()
+ {
+ $listenersCalled = 0;
+
+ $listener = function () use (&$listenersCalled) {
+ $listenersCalled++;
+ };
+
+ $this->emitter->on('foo', $listener);
+ $this->emitter->removeListener('foo', $listener);
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(0, $listenersCalled);
+ }
+
+ public function testRemoveListenerNotMatching()
+ {
+ $listenersCalled = 0;
+
+ $listener = function () use (&$listenersCalled) {
+ $listenersCalled++;
+ };
+
+ $this->emitter->on('foo', $listener);
+ $this->emitter->removeListener('bar', $listener);
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(1, $listenersCalled);
+ }
+
+ public function testRemoveAllListenersMatching()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->removeAllListeners('foo');
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(0, $listenersCalled);
+ }
+
+ public function testRemoveAllListenersNotMatching()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->removeAllListeners('bar');
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(1, $listenersCalled);
+ }
+
+ public function testRemoveAllListenersWithoutArguments()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->on('bar', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->removeAllListeners();
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->emitter->emit('bar');
+ $this->assertSame(0, $listenersCalled);
+ }
+
+ public function testCallablesClosure()
+ {
+ $calledWith = null;
+
+ $this->emitter->on('foo', function ($data) use (&$calledWith) {
+ $calledWith = $data;
+ });
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame('bar', $calledWith);
+ }
+
+ public function testCallablesClass()
+ {
+ $listener = new Listener();
+ $this->emitter->on('foo', [$listener, 'onFoo']);
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame(['bar'], $listener->getData());
+ }
+
+
+ public function testCallablesClassInvoke()
+ {
+ $listener = new Listener();
+ $this->emitter->on('foo', $listener);
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame(['bar'], $listener->getMagicData());
+ }
+
+ public function testCallablesStaticClass()
+ {
+ $this->emitter->on('foo', '\Evenement\Tests\Listener::onBar');
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame(['bar'], Listener::getStaticData());
+ }
+
+ public function testCallablesFunction()
+ {
+ $this->emitter->on('foo', '\Evenement\Tests\setGlobalTestData');
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame('bar', $GLOBALS['evenement-evenement-test-data']);
+
+ unset($GLOBALS['evenement-evenement-test-data']);
+ }
+
+ public function testListeners()
+ {
+ $onA = function () {};
+ $onB = function () {};
+ $onC = function () {};
+ $onceA = function () {};
+ $onceB = function () {};
+ $onceC = function () {};
+
+ self::assertCount(0, $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onA);
+ self::assertCount(1, $this->emitter->listeners('event'));
+ self::assertSame([$onA], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceA);
+ self::assertCount(2, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceB);
+ self::assertCount(3, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onceA, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onB);
+ self::assertCount(4, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceA, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->removeListener('event', $onceA);
+ self::assertCount(3, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceC);
+ self::assertCount(4, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceB, $onceC], $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onC);
+ self::assertCount(5, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onC, $onceB, $onceC], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceA);
+ self::assertCount(6, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->removeListener('event', $onB);
+ self::assertCount(5, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->emit('event');
+ self::assertCount(2, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onC], $this->emitter->listeners('event'));
+ }
+
+ public function testOnceCallIsNotRemovedWhenWorkingOverOnceListeners()
+ {
+ $aCalled = false;
+ $aCallable = function () use (&$aCalled) {
+ $aCalled = true;
+ };
+ $bCalled = false;
+ $bCallable = function () use (&$bCalled, $aCallable) {
+ $bCalled = true;
+ $this->emitter->once('event', $aCallable);
+ };
+ $this->emitter->once('event', $bCallable);
+
+ self::assertFalse($aCalled);
+ self::assertFalse($bCalled);
+ $this->emitter->emit('event');
+
+ self::assertFalse($aCalled);
+ self::assertTrue($bCalled);
+ $this->emitter->emit('event');
+
+ self::assertTrue($aCalled);
+ self::assertTrue($bCalled);
+ }
+
+ public function testEventNameMustBeStringOn()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->on(null, function () {});
+ }
+
+ public function testEventNameMustBeStringOnce()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->once(null, function () {});
+ }
+
+ public function testEventNameMustBeStringRemoveListener()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->removeListener(null, function () {});
+ }
+
+ public function testEventNameMustBeStringEmit()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->emit(null);
+ }
+
+ public function testListenersGetAll()
+ {
+ $a = function () {};
+ $b = function () {};
+ $c = function () {};
+ $d = function () {};
+
+ $this->emitter->once('event2', $c);
+ $this->emitter->on('event', $a);
+ $this->emitter->once('event', $b);
+ $this->emitter->on('event', $c);
+ $this->emitter->once('event', $d);
+
+ self::assertSame(
+ [
+ 'event' => [
+ $a,
+ $c,
+ $b,
+ $d,
+ ],
+ 'event2' => [
+ $c,
+ ],
+ ],
+ $this->emitter->listeners()
+ );
+ }
+
+ public function testOnceNestedCallRegression()
+ {
+ $first = 0;
+ $second = 0;
+
+ $this->emitter->once('event', function () use (&$first, &$second) {
+ $first++;
+ $this->emitter->once('event', function () use (&$second) {
+ $second++;
+ });
+ $this->emitter->emit('event');
+ });
+ $this->emitter->emit('event');
+
+ self::assertSame(1, $first);
+ self::assertSame(1, $second);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php b/src/mpg25-instagram-api/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php
new file mode 100755
index 0000000..df17424
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+class Listener
+{
+ private $data = [];
+
+ private $magicData = [];
+
+ private static $staticData = [];
+
+ public function onFoo($data)
+ {
+ $this->data[] = $data;
+ }
+
+ public function __invoke($data)
+ {
+ $this->magicData[] = $data;
+ }
+
+ public static function onBar($data)
+ {
+ self::$staticData[] = $data;
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function getMagicData()
+ {
+ return $this->magicData;
+ }
+
+ public static function getStaticData()
+ {
+ return self::$staticData;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/evenement/evenement/tests/Evenement/Tests/functions.php b/src/mpg25-instagram-api/vendor/evenement/evenement/tests/Evenement/Tests/functions.php
new file mode 100755
index 0000000..7f11f5b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/evenement/evenement/tests/Evenement/Tests/functions.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+function setGlobalTestData($data)
+{
+ $GLOBALS['evenement-evenement-test-data'] = $data;
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/.php_cs b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/.php_cs
new file mode 100755
index 0000000..a8ace8a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/.php_cs
@@ -0,0 +1,21 @@
+setRiskyAllowed(true)
+ ->setRules([
+ '@PSR2' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'declare_strict_types' => false,
+ 'concat_space' => ['spacing'=>'one'],
+ // 'ordered_imports' => true,
+ // 'phpdoc_align' => ['align'=>'vertical'],
+ // 'native_function_invocation' => true,
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__.'/src')
+ ->name('*.php')
+ )
+;
+
+return $config;
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/CHANGELOG.md b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/CHANGELOG.md
new file mode 100755
index 0000000..6555749
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/CHANGELOG.md
@@ -0,0 +1,1304 @@
+# Change Log
+
+## 6.4.1 - 2019-10-23
+
+* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that
+* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar`
+
+## 6.4.0 - 2019-10-23
+
+* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108)
+* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081)
+* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161)
+* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163)
+* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242)
+* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284)
+* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273)
+* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335)
+* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362)
+
+## 6.3.3 - 2018-04-22
+
+* Fix: Default headers when decode_content is specified
+
+
+## 6.3.2 - 2018-03-26
+
+* Fix: Release process
+
+
+## 6.3.1 - 2018-03-26
+
+* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014)
+* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012)
+* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999)
+* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998)
+* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953)
+* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915)
+* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916)
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+
+## 6.3.0 - 2017-06-22
+
+* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
+* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
+* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
+* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
+* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
+* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
+* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
+* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
+* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
+* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
+* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
+* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
+* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
+* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
+* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)
+
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+## 6.2.3 - 2017-02-28
+
+* Fix deprecations with guzzle/psr7 version 1.4
+
+## 6.2.2 - 2016-10-08
+
+* Allow to pass nullable Response to delay callable
+* Only add scheme when host is present
+* Fix drain case where content-length is the literal string zero
+* Obfuscate in-URL credentials in exceptions
+
+## 6.2.1 - 2016-07-18
+
+* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
+ https://httpoxy.org/
+* Fixing timeout bug with StreamHandler:
+ https://github.com/guzzle/guzzle/pull/1488
+* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
+ a server does not honor `Connection: close`.
+* Ignore URI fragment when sending requests.
+
+## 6.2.0 - 2016-03-21
+
+* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
+ https://github.com/guzzle/guzzle/pull/1389
+* Bug fix: Fix sleep calculation when waiting for delayed requests.
+ https://github.com/guzzle/guzzle/pull/1324
+* Feature: More flexible history containers.
+ https://github.com/guzzle/guzzle/pull/1373
+* Bug fix: defer sink stream opening in StreamHandler.
+ https://github.com/guzzle/guzzle/pull/1377
+* Bug fix: do not attempt to escape cookie values.
+ https://github.com/guzzle/guzzle/pull/1406
+* Feature: report original content encoding and length on decoded responses.
+ https://github.com/guzzle/guzzle/pull/1409
+* Bug fix: rewind seekable request bodies before dispatching to cURL.
+ https://github.com/guzzle/guzzle/pull/1422
+* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
+ https://github.com/guzzle/guzzle/pull/1367
+
+## 6.1.1 - 2015-11-22
+
+* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
+ https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
+* Feature: HandlerStack is now more generic.
+ https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
+* Bug fix: setting verify to false in the StreamHandler now disables peer
+ verification. https://github.com/guzzle/guzzle/issues/1256
+* Feature: Middleware now uses an exception factory, including more error
+ context. https://github.com/guzzle/guzzle/pull/1282
+* Feature: better support for disabled functions.
+ https://github.com/guzzle/guzzle/pull/1287
+* Bug fix: fixed regression where MockHandler was not using `sink`.
+ https://github.com/guzzle/guzzle/pull/1292
+
+## 6.1.0 - 2015-09-08
+
+* Feature: Added the `on_stats` request option to provide access to transfer
+ statistics for requests. https://github.com/guzzle/guzzle/pull/1202
+* Feature: Added the ability to persist session cookies in CookieJars.
+ https://github.com/guzzle/guzzle/pull/1195
+* Feature: Some compatibility updates for Google APP Engine
+ https://github.com/guzzle/guzzle/pull/1216
+* Feature: Added support for NO_PROXY to prevent the use of a proxy based on
+ a simple set of rules. https://github.com/guzzle/guzzle/pull/1197
+* Feature: Cookies can now contain square brackets.
+ https://github.com/guzzle/guzzle/pull/1237
+* Bug fix: Now correctly parsing `=` inside of quotes in Cookies.
+ https://github.com/guzzle/guzzle/pull/1232
+* Bug fix: Cusotm cURL options now correctly override curl options of the
+ same name. https://github.com/guzzle/guzzle/pull/1221
+* Bug fix: Content-Type header is now added when using an explicitly provided
+ multipart body. https://github.com/guzzle/guzzle/pull/1218
+* Bug fix: Now ignoring Set-Cookie headers that have no name.
+* Bug fix: Reason phrase is no longer cast to an int in some cases in the
+ cURL handler. https://github.com/guzzle/guzzle/pull/1187
+* Bug fix: Remove the Authorization header when redirecting if the Host
+ header changes. https://github.com/guzzle/guzzle/pull/1207
+* Bug fix: Cookie path matching fixes
+ https://github.com/guzzle/guzzle/issues/1129
+* Bug fix: Fixing the cURL `body_as_string` setting
+ https://github.com/guzzle/guzzle/pull/1201
+* Bug fix: quotes are no longer stripped when parsing cookies.
+ https://github.com/guzzle/guzzle/issues/1172
+* Bug fix: `form_params` and `query` now always uses the `&` separator.
+ https://github.com/guzzle/guzzle/pull/1163
+* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set.
+ https://github.com/guzzle/guzzle/pull/1189
+
+## 6.0.2 - 2015-07-04
+
+* Fixed a memory leak in the curl handlers in which references to callbacks
+ were not being removed by `curl_reset`.
+* Cookies are now extracted properly before redirects.
+* Cookies now allow more character ranges.
+* Decoded Content-Encoding responses are now modified to correctly reflect
+ their state if the encoding was automatically removed by a handler. This
+ means that the `Content-Encoding` header may be removed an the
+ `Content-Length` modified to reflect the message size after removing the
+ encoding.
+* Added a more explicit error message when trying to use `form_params` and
+ `multipart` in the same request.
+* Several fixes for HHVM support.
+* Functions are now conditionally required using an additional level of
+ indirection to help with global Composer installations.
+
+## 6.0.1 - 2015-05-27
+
+* Fixed a bug with serializing the `query` request option where the `&`
+ separator was missing.
+* Added a better error message for when `body` is provided as an array. Please
+ use `form_params` or `multipart` instead.
+* Various doc fixes.
+
+## 6.0.0 - 2015-05-26
+
+* See the UPGRADING.md document for more information.
+* Added `multipart` and `form_params` request options.
+* Added `synchronous` request option.
+* Added the `on_headers` request option.
+* Fixed `expect` handling.
+* No longer adding default middlewares in the client ctor. These need to be
+ present on the provided handler in order to work.
+* Requests are no longer initiated when sending async requests with the
+ CurlMultiHandler. This prevents unexpected recursion from requests completing
+ while ticking the cURL loop.
+* Removed the semantics of setting `default` to `true`. This is no longer
+ required now that the cURL loop is not ticked for async requests.
+* Added request and response logging middleware.
+* No longer allowing self signed certificates when using the StreamHandler.
+* Ensuring that `sink` is valid if saving to a file.
+* Request exceptions now include a "handler context" which provides handler
+ specific contextual information.
+* Added `GuzzleHttp\RequestOptions` to allow request options to be applied
+ using constants.
+* `$maxHandles` has been removed from CurlMultiHandler.
+* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package.
+
+## 5.3.0 - 2015-05-19
+
+* Mock now supports `save_to`
+* Marked `AbstractRequestEvent::getTransaction()` as public.
+* Fixed a bug in which multiple headers using different casing would overwrite
+ previous headers in the associative array.
+* Added `Utils::getDefaultHandler()`
+* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated.
+* URL scheme is now always lowercased.
+
+## 6.0.0-beta.1
+
+* Requires PHP >= 5.5
+* Updated to use PSR-7
+ * Requires immutable messages, which basically means an event based system
+ owned by a request instance is no longer possible.
+ * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7).
+ * Removed the dependency on `guzzlehttp/streams`. These stream abstractions
+ are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7`
+ namespace.
+* Added middleware and handler system
+ * Replaced the Guzzle event and subscriber system with a middleware system.
+ * No longer depends on RingPHP, but rather places the HTTP handlers directly
+ in Guzzle, operating on PSR-7 messages.
+ * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which
+ means the `guzzlehttp/retry-subscriber` is now obsolete.
+ * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`.
+* Asynchronous responses
+ * No longer supports the `future` request option to send an async request.
+ Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`,
+ `getAsync`, etc.).
+ * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid
+ recursion required by chaining and forwarding react promises. See
+ https://github.com/guzzle/promises
+ * Added `requestAsync` and `sendAsync` to send request asynchronously.
+ * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests
+ asynchronously.
+* Request options
+ * POST and form updates
+ * Added the `form_fields` and `form_files` request options.
+ * Removed the `GuzzleHttp\Post` namespace.
+ * The `body` request option no longer accepts an array for POST requests.
+ * The `exceptions` request option has been deprecated in favor of the
+ `http_errors` request options.
+ * The `save_to` request option has been deprecated in favor of `sink` request
+ option.
+* Clients no longer accept an array of URI template string and variables for
+ URI variables. You will need to expand URI templates before passing them
+ into a client constructor or request method.
+* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are
+ now magic methods that will send synchronous requests.
+* Replaced `Utils.php` with plain functions in `functions.php`.
+* Removed `GuzzleHttp\Collection`.
+* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as
+ an array.
+* Removed `GuzzleHttp\Query`. Query string handling is now handled using an
+ associative array passed into the `query` request option. The query string
+ is serialized using PHP's `http_build_query`. If you need more control, you
+ can pass the query string in as a string.
+* `GuzzleHttp\QueryParser` has been replaced with the
+ `GuzzleHttp\Psr7\parse_query`.
+
+## 5.2.0 - 2015-01-27
+
+* Added `AppliesHeadersInterface` to make applying headers to a request based
+ on the body more generic and not specific to `PostBodyInterface`.
+* Reduced the number of stack frames needed to send requests.
+* Nested futures are now resolved in the client rather than the RequestFsm
+* Finishing state transitions is now handled in the RequestFsm rather than the
+ RingBridge.
+* Added a guard in the Pool class to not use recursion for request retries.
+
+## 5.1.0 - 2014-12-19
+
+* Pool class no longer uses recursion when a request is intercepted.
+* The size of a Pool can now be dynamically adjusted using a callback.
+ See https://github.com/guzzle/guzzle/pull/943.
+* Setting a request option to `null` when creating a request with a client will
+ ensure that the option is not set. This allows you to overwrite default
+ request options on a per-request basis.
+ See https://github.com/guzzle/guzzle/pull/937.
+* Added the ability to limit which protocols are allowed for redirects by
+ specifying a `protocols` array in the `allow_redirects` request option.
+* Nested futures due to retries are now resolved when waiting for synchronous
+ responses. See https://github.com/guzzle/guzzle/pull/947.
+* `"0"` is now an allowed URI path. See
+ https://github.com/guzzle/guzzle/pull/935.
+* `Query` no longer typehints on the `$query` argument in the constructor,
+ allowing for strings and arrays.
+* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle
+ specific exceptions if necessary.
+
+## 5.0.3 - 2014-11-03
+
+This change updates query strings so that they are treated as un-encoded values
+by default where the value represents an un-encoded value to send over the
+wire. A Query object then encodes the value before sending over the wire. This
+means that even value query string values (e.g., ":") are url encoded. This
+makes the Query class match PHP's http_build_query function. However, if you
+want to send requests over the wire using valid query string characters that do
+not need to be encoded, then you can provide a string to Url::setQuery() and
+pass true as the second argument to specify that the query string is a raw
+string that should not be parsed or encoded (unless a call to getQuery() is
+subsequently made, forcing the query-string to be converted into a Query
+object).
+
+## 5.0.2 - 2014-10-30
+
+* Added a trailing `\r\n` to multipart/form-data payloads. See
+ https://github.com/guzzle/guzzle/pull/871
+* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs.
+* Status codes are now returned as integers. See
+ https://github.com/guzzle/guzzle/issues/881
+* No longer overwriting an existing `application/x-www-form-urlencoded` header
+ when sending POST requests, allowing for customized headers. See
+ https://github.com/guzzle/guzzle/issues/877
+* Improved path URL serialization.
+
+ * No longer double percent-encoding characters in the path or query string if
+ they are already encoded.
+ * Now properly encoding the supplied path to a URL object, instead of only
+ encoding ' ' and '?'.
+ * Note: This has been changed in 5.0.3 to now encode query string values by
+ default unless the `rawString` argument is provided when setting the query
+ string on a URL: Now allowing many more characters to be present in the
+ query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A
+
+## 5.0.1 - 2014-10-16
+
+Bugfix release.
+
+* Fixed an issue where connection errors still returned response object in
+ error and end events event though the response is unusable. This has been
+ corrected so that a response is not returned in the `getResponse` method of
+ these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867
+* Fixed an issue where transfer statistics were not being populated in the
+ RingBridge. https://github.com/guzzle/guzzle/issues/866
+
+## 5.0.0 - 2014-10-12
+
+Adding support for non-blocking responses and some minor API cleanup.
+
+### New Features
+
+* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`.
+* Added a public API for creating a default HTTP adapter.
+* Updated the redirect plugin to be non-blocking so that redirects are sent
+ concurrently. Other plugins like this can now be updated to be non-blocking.
+* Added a "progress" event so that you can get upload and download progress
+ events.
+* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers
+ requests concurrently using a capped pool size as efficiently as possible.
+* Added `hasListeners()` to EmitterInterface.
+* Removed `GuzzleHttp\ClientInterface::sendAll` and marked
+ `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the
+ recommended way).
+
+### Breaking changes
+
+The breaking changes in this release are relatively minor. The biggest thing to
+look out for is that request and response objects no longer implement fluent
+interfaces.
+
+* Removed the fluent interfaces (i.e., `return $this`) from requests,
+ responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`,
+ `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and
+ `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of
+ why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/.
+ This also makes the Guzzle message interfaces compatible with the current
+ PSR-7 message proposal.
+* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except
+ for the HTTP request functions from function.php, these functions are now
+ implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode`
+ moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to
+ `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to
+ `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be
+ `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php
+ caused problems for many users: they aren't PSR-4 compliant, require an
+ explicit include, and needed an if-guard to ensure that the functions are not
+ declared multiple times.
+* Rewrote adapter layer.
+ * Removing all classes from `GuzzleHttp\Adapter`, these are now
+ implemented as callables that are stored in `GuzzleHttp\Ring\Client`.
+ * Removed the concept of "parallel adapters". Sending requests serially or
+ concurrently is now handled using a single adapter.
+ * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The
+ Transaction object now exposes the request, response, and client as public
+ properties. The getters and setters have been removed.
+* Removed the "headers" event. This event was only useful for changing the
+ body a response once the headers of the response were known. You can implement
+ a similar behavior in a number of ways. One example might be to use a
+ FnStream that has access to the transaction being sent. For example, when the
+ first byte is written, you could check if the response headers match your
+ expectations, and if so, change the actual stream body that is being
+ written to.
+* Removed the `asArray` parameter from
+ `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+ value as an array, then use the newly added `getHeaderAsArray()` method of
+ `MessageInterface`. This change makes the Guzzle interfaces compatible with
+ the PSR-7 interfaces.
+* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add
+ custom request options using double-dispatch (this was an implementation
+ detail). Instead, you should now provide an associative array to the
+ constructor which is a mapping of the request option name mapping to a
+ function that applies the option value to a request.
+* Removed the concept of "throwImmediately" from exceptions and error events.
+ This control mechanism was used to stop a transfer of concurrent requests
+ from completing. This can now be handled by throwing the exception or by
+ cancelling a pool of requests or each outstanding future request individually.
+* Updated to "GuzzleHttp\Streams" 3.0.
+ * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a
+ `maxLen` parameter. This update makes the Guzzle streams project
+ compatible with the current PSR-7 proposal.
+ * `GuzzleHttp\Stream\Stream::__construct`,
+ `GuzzleHttp\Stream\Stream::factory`, and
+ `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second
+ argument. They now accept an associative array of options, including the
+ "size" key and "metadata" key which can be used to provide custom metadata.
+
+## 4.2.2 - 2014-09-08
+
+* Fixed a memory leak in the CurlAdapter when reusing cURL handles.
+* No longer using `request_fulluri` in stream adapter proxies.
+* Relative redirects are now based on the last response, not the first response.
+
+## 4.2.1 - 2014-08-19
+
+* Ensuring that the StreamAdapter does not always add a Content-Type header
+* Adding automated github releases with a phar and zip
+
+## 4.2.0 - 2014-08-17
+
+* Now merging in default options using a case-insensitive comparison.
+ Closes https://github.com/guzzle/guzzle/issues/767
+* Added the ability to automatically decode `Content-Encoding` response bodies
+ using the `decode_content` request option. This is set to `true` by default
+ to decode the response body if it comes over the wire with a
+ `Content-Encoding`. Set this value to `false` to disable decoding the
+ response content, and pass a string to provide a request `Accept-Encoding`
+ header and turn on automatic response decoding. This feature now allows you
+ to pass an `Accept-Encoding` header in the headers of a request but still
+ disable automatic response decoding.
+ Closes https://github.com/guzzle/guzzle/issues/764
+* Added the ability to throw an exception immediately when transferring
+ requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
+* Updating guzzlehttp/streams dependency to ~2.1
+* No longer utilizing the now deprecated namespaced methods from the stream
+ package.
+
+## 4.1.8 - 2014-08-14
+
+* Fixed an issue in the CurlFactory that caused setting the `stream=false`
+ request option to throw an exception.
+ See: https://github.com/guzzle/guzzle/issues/769
+* TransactionIterator now calls rewind on the inner iterator.
+ See: https://github.com/guzzle/guzzle/pull/765
+* You can now set the `Content-Type` header to `multipart/form-data`
+ when creating POST requests to force multipart bodies.
+ See https://github.com/guzzle/guzzle/issues/768
+
+## 4.1.7 - 2014-08-07
+
+* Fixed an error in the HistoryPlugin that caused the same request and response
+ to be logged multiple times when an HTTP protocol error occurs.
+* Ensuring that cURL does not add a default Content-Type when no Content-Type
+ has been supplied by the user. This prevents the adapter layer from modifying
+ the request that is sent over the wire after any listeners may have already
+ put the request in a desired state (e.g., signed the request).
+* Throwing an exception when you attempt to send requests that have the
+ "stream" set to true in parallel using the MultiAdapter.
+* Only calling curl_multi_select when there are active cURL handles. This was
+ previously changed and caused performance problems on some systems due to PHP
+ always selecting until the maximum select timeout.
+* Fixed a bug where multipart/form-data POST fields were not correctly
+ aggregated (e.g., values with "&").
+
+## 4.1.6 - 2014-08-03
+
+* Added helper methods to make it easier to represent messages as strings,
+ including getting the start line and getting headers as a string.
+
+## 4.1.5 - 2014-08-02
+
+* Automatically retrying cURL "Connection died, retrying a fresh connect"
+ errors when possible.
+* cURL implementation cleanup
+* Allowing multiple event subscriber listeners to be registered per event by
+ passing an array of arrays of listener configuration.
+
+## 4.1.4 - 2014-07-22
+
+* Fixed a bug that caused multi-part POST requests with more than one field to
+ serialize incorrectly.
+* Paths can now be set to "0"
+* `ResponseInterface::xml` now accepts a `libxml_options` option and added a
+ missing default argument that was required when parsing XML response bodies.
+* A `save_to` stream is now created lazily, which means that files are not
+ created on disk unless a request succeeds.
+
+## 4.1.3 - 2014-07-15
+
+* Various fixes to multipart/form-data POST uploads
+* Wrapping function.php in an if-statement to ensure Guzzle can be used
+ globally and in a Composer install
+* Fixed an issue with generating and merging in events to an event array
+* POST headers are only applied before sending a request to allow you to change
+ the query aggregator used before uploading
+* Added much more robust query string parsing
+* Fixed various parsing and normalization issues with URLs
+* Fixing an issue where multi-valued headers were not being utilized correctly
+ in the StreamAdapter
+
+## 4.1.2 - 2014-06-18
+
+* Added support for sending payloads with GET requests
+
+## 4.1.1 - 2014-06-08
+
+* Fixed an issue related to using custom message factory options in subclasses
+* Fixed an issue with nested form fields in a multi-part POST
+* Fixed an issue with using the `json` request option for POST requests
+* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar`
+
+## 4.1.0 - 2014-05-27
+
+* Added a `json` request option to easily serialize JSON payloads.
+* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON.
+* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`.
+* Added the ability to provide an emitter to a client in the client constructor.
+* Added the ability to persist a cookie session using $_SESSION.
+* Added a trait that can be used to add event listeners to an iterator.
+* Removed request method constants from RequestInterface.
+* Fixed warning when invalid request start-lines are received.
+* Updated MessageFactory to work with custom request option methods.
+* Updated cacert bundle to latest build.
+
+4.0.2 (2014-04-16)
+------------------
+
+* Proxy requests using the StreamAdapter now properly use request_fulluri (#632)
+* Added the ability to set scalars as POST fields (#628)
+
+## 4.0.1 - 2014-04-04
+
+* The HTTP status code of a response is now set as the exception code of
+ RequestException objects.
+* 303 redirects will now correctly switch from POST to GET requests.
+* The default parallel adapter of a client now correctly uses the MultiAdapter.
+* HasDataTrait now initializes the internal data array as an empty array so
+ that the toArray() method always returns an array.
+
+## 4.0.0 - 2014-03-29
+
+* For more information on the 4.0 transition, see:
+ http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/
+* For information on changes and upgrading, see:
+ https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+* Added `GuzzleHttp\batch()` as a convenience function for sending requests in
+ parallel without needing to write asynchronous code.
+* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`.
+ You can now pass a callable or an array of associative arrays where each
+ associative array contains the "fn", "priority", and "once" keys.
+
+## 4.0.0.rc-2 - 2014-03-25
+
+* Removed `getConfig()` and `setConfig()` from clients to avoid confusion
+ around whether things like base_url, message_factory, etc. should be able to
+ be retrieved or modified.
+* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface
+* functions.php functions were renamed using snake_case to match PHP idioms
+* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and
+ `GUZZLE_CURL_SELECT_TIMEOUT` environment variables
+* Added the ability to specify custom `sendAll()` event priorities
+* Added the ability to specify custom stream context options to the stream
+ adapter.
+* Added a functions.php function for `get_path()` and `set_path()`
+* CurlAdapter and MultiAdapter now use a callable to generate curl resources
+* MockAdapter now properly reads a body and emits a `headers` event
+* Updated Url class to check if a scheme and host are set before adding ":"
+ and "//". This allows empty Url (e.g., "") to be serialized as "".
+* Parsing invalid XML no longer emits warnings
+* Curl classes now properly throw AdapterExceptions
+* Various performance optimizations
+* Streams are created with the faster `Stream\create()` function
+* Marked deprecation_proxy() as internal
+* Test server is now a collection of static methods on a class
+
+## 4.0.0-rc.1 - 2014-03-15
+
+* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+
+## 3.8.1 - 2014-01-28
+
+* Bug: Always using GET requests when redirecting from a 303 response
+* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
+ `Guzzle\Http\ClientInterface::setSslVerification()`
+* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
+* Bug: The body of a request can now be set to `"0"`
+* Sending PHP stream requests no longer forces `HTTP/1.0`
+* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
+ each sub-exception
+* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
+ clobbering everything).
+* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
+* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
+ For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
+* Now properly escaping the regular expression delimiter when matching Cookie domains.
+* Network access is now disabled when loading XML documents
+
+## 3.8.0 - 2013-12-05
+
+* Added the ability to define a POST name for a file
+* JSON response parsing now properly walks additionalProperties
+* cURL error code 18 is now retried automatically in the BackoffPlugin
+* Fixed a cURL error when URLs contain fragments
+* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
+ CurlExceptions
+* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
+* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
+* Fixed a bug that was encountered when parsing empty header parameters
+* UriTemplate now has a `setRegex()` method to match the docs
+* The `debug` request parameter now checks if it is truthy rather than if it exists
+* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
+* Added the ability to combine URLs using strict RFC 3986 compliance
+* Command objects can now return the validation errors encountered by the command
+* Various fixes to cache revalidation (#437 and 29797e5)
+* Various fixes to the AsyncPlugin
+* Cleaned up build scripts
+
+## 3.7.4 - 2013-10-02
+
+* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
+* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
+ (see https://github.com/aws/aws-sdk-php/issues/147)
+* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
+* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
+* Updated the bundled cacert.pem (#419)
+* OauthPlugin now supports adding authentication to headers or query string (#425)
+
+## 3.7.3 - 2013-09-08
+
+* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
+ `CommandTransferException`.
+* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
+* Schemas are only injected into response models when explicitly configured.
+* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
+ an EntityBody.
+* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
+* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
+* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
+* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
+* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests
+* Bug fix: Properly parsing headers that contain commas contained in quotes
+* Bug fix: mimetype guessing based on a filename is now case-insensitive
+
+## 3.7.2 - 2013-08-02
+
+* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
+ See https://github.com/guzzle/guzzle/issues/371
+* Bug fix: Cookie domains are now matched correctly according to RFC 6265
+ See https://github.com/guzzle/guzzle/issues/377
+* Bug fix: GET parameters are now used when calculating an OAuth signature
+* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
+* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
+* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
+ See https://github.com/guzzle/guzzle/issues/379
+* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
+ https://github.com/guzzle/guzzle/pull/380
+* cURL multi cleanup and optimizations
+
+## 3.7.1 - 2013-07-05
+
+* Bug fix: Setting default options on a client now works
+* Bug fix: Setting options on HEAD requests now works. See #352
+* Bug fix: Moving stream factory before send event to before building the stream. See #353
+* Bug fix: Cookies no longer match on IP addresses per RFC 6265
+* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
+* Added `cert` and `ssl_key` as request options
+* `Host` header can now diverge from the host part of a URL if the header is set manually
+* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
+* OAuth parameters are only added via the plugin if they aren't already set
+* Exceptions are now thrown when a URL cannot be parsed
+* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
+* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
+
+## 3.7.0 - 2013-06-10
+
+* See UPGRADING.md for more information on how to upgrade.
+* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
+ request. You can pass a 'request.options' configuration setting to a client to apply default request options to
+ every request created by a client (e.g. default query string variables, headers, curl options, etc.).
+* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
+ See `Guzzle\Http\StaticClient::mount`.
+* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
+ created by a command (e.g. custom headers, query string variables, timeout settings, etc.).
+* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
+ headers of a response
+* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
+ (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
+* ServiceBuilders now support storing and retrieving arbitrary data
+* CachePlugin can now purge all resources for a given URI
+* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
+* CachePlugin now uses the Vary header to determine if a resource is a cache hit
+* `Guzzle\Http\Message\Response` now implements `\Serializable`
+* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
+* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
+* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
+* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
+* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
+* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
+ Symfony users can still use the old version of Monolog.
+* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
+ Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
+* Several performance improvements to `Guzzle\Common\Collection`
+* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+* Added `Guzzle\Stream\StreamInterface::isRepeatable`
+* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
+* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
+* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
+* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
+* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
+* Removed `Guzzle\Http\Message\RequestInterface::canCache`
+* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
+* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
+* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
+ `Guzzle\Common\Version::$emitWarnings` to true.
+* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
+ `$request->getResponseBody()->isRepeatable()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
+ These will work through Guzzle 4.0
+* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
+* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
+* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
+* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+* Marked `Guzzle\Common\Collection::inject()` as deprecated.
+* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
+* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+* Always setting X-cache headers on cached responses
+* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+* Added `CacheStorageInterface::purge($url)`
+* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+## 3.6.0 - 2013-05-29
+
+* ServiceDescription now implements ToArrayInterface
+* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
+* Guzzle can now correctly parse incomplete URLs
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+* Added the ability to cast Model objects to a string to view debug information.
+
+## 3.5.0 - 2013-05-13
+
+* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
+* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove
+ itself from the EventDispatcher)
+* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
+* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
+* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
+ non-existent key
+* Bug: All __call() method arguments are now required (helps with mocking frameworks)
+* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
+ to help with refcount based garbage collection of resources created by sending a request
+* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
+* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
+ HistoryPlugin for a history.
+* Added a `responseBody` alias for the `response_body` location
+* Refactored internals to no longer rely on Response::getRequest()
+* HistoryPlugin can now be cast to a string
+* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
+ and responses that are sent over the wire
+* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
+
+## 3.4.3 - 2013-04-30
+
+* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
+* Added a check to re-extract the temp cacert bundle from the phar before sending each request
+
+## 3.4.2 - 2013-04-29
+
+* Bug fix: Stream objects now work correctly with "a" and "a+" modes
+* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
+* Bug fix: AsyncPlugin no longer forces HEAD requests
+* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
+* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
+* Setting a response on a request will write to the custom request body from the response body if one is specified
+* LogPlugin now writes to php://output when STDERR is undefined
+* Added the ability to set multiple POST files for the same key in a single call
+* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
+* Added the ability to queue CurlExceptions to the MockPlugin
+* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
+* Configuration loading now allows remote files
+
+## 3.4.1 - 2013-04-16
+
+* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
+ handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
+* Exceptions are now properly grouped when sending requests in parallel
+* Redirects are now properly aggregated when a multi transaction fails
+* Redirects now set the response on the original object even in the event of a failure
+* Bug fix: Model names are now properly set even when using $refs
+* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
+* Added support for oauth_callback in OAuth signatures
+* Added support for oauth_verifier in OAuth signatures
+* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
+
+## 3.4.0 - 2013-04-11
+
+* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
+* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
+* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
+* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
+* Bug fix: Added `number` type to service descriptions.
+* Bug fix: empty parameters are removed from an OAuth signature
+* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
+* Bug fix: Fixed "array to string" error when validating a union of types in a service description
+* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
+* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
+* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
+* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
+* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
+ the Content-Type can be determined based on the entity body or the path of the request.
+* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
+* Added support for a PSR-3 LogAdapter.
+* Added a `command.after_prepare` event
+* Added `oauth_callback` parameter to the OauthPlugin
+* Added the ability to create a custom stream class when using a stream factory
+* Added a CachingEntityBody decorator
+* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
+* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
+* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
+* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
+ means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
+ POST fields or files (the latter is only used when emulating a form POST in the browser).
+* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
+
+## 3.3.1 - 2013-03-10
+
+* Added the ability to create PHP streaming responses from HTTP requests
+* Bug fix: Running any filters when parsing response headers with service descriptions
+* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
+* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
+ response location visitors.
+* Bug fix: Removed the possibility of creating configuration files with circular dependencies
+* RequestFactory::create() now uses the key of a POST file when setting the POST file name
+* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
+
+## 3.3.0 - 2013-03-03
+
+* A large number of performance optimizations have been made
+* Bug fix: Added 'wb' as a valid write mode for streams
+* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
+* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
+* BC: Removed `Guzzle\Http\Utils` class
+* BC: Setting a service description on a client will no longer modify the client's command factories.
+* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
+ the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
+ lowercase
+* Operation parameter objects are now lazy loaded internally
+* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
+* Added support for instantiating responseType=class responseClass classes. Classes must implement
+ `Guzzle\Service\Command\ResponseClassInterface`
+* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
+ additional properties also support locations and can be used to parse JSON responses where the outermost part of the
+ JSON is an array
+* Added support for nested renaming of JSON models (rename sentAs to name)
+* CachePlugin
+ * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
+ * Debug headers can now added to cached response in the CachePlugin
+
+## 3.2.0 - 2013-02-14
+
+* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
+* URLs with no path no longer contain a "/" by default
+* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
+* BadResponseException no longer includes the full request and response message
+* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
+* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
+* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
+* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
+* xmlEncoding can now be customized for the XML declaration of a XML service description operation
+* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
+ aggregation and no longer uses callbacks
+* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
+* Bug fix: Filters were not always invoked for array service description parameters
+* Bug fix: Redirects now use a target response body rather than a temporary response body
+* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
+* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
+
+## 3.1.2 - 2013-01-27
+
+* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
+ response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
+* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
+* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
+* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
+* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
+
+## 3.1.1 - 2013-01-20
+
+* Adding wildcard support to Guzzle\Common\Collection::getPath()
+* Adding alias support to ServiceBuilder configs
+* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
+
+## 3.1.0 - 2013-01-12
+
+* BC: CurlException now extends from RequestException rather than BadResponseException
+* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
+* Added getData to ServiceDescriptionInterface
+* Added context array to RequestInterface::setState()
+* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
+* Bug: Adding required content-type when JSON request visitor adds JSON to a command
+* Bug: Fixing the serialization of a service description with custom data
+* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
+ an array of successful and failed responses
+* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
+* Added Guzzle\Http\IoEmittingEntityBody
+* Moved command filtration from validators to location visitors
+* Added `extends` attributes to service description parameters
+* Added getModels to ServiceDescriptionInterface
+
+## 3.0.7 - 2012-12-19
+
+* Fixing phar detection when forcing a cacert to system if null or true
+* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
+* Cleaning up `Guzzle\Common\Collection::inject` method
+* Adding a response_body location to service descriptions
+
+## 3.0.6 - 2012-12-09
+
+* CurlMulti performance improvements
+* Adding setErrorResponses() to Operation
+* composer.json tweaks
+
+## 3.0.5 - 2012-11-18
+
+* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
+* Bug: Response body can now be a string containing "0"
+* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
+* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
+* Added support for XML attributes in service description responses
+* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
+* Added better mimetype guessing to requests and post files
+
+## 3.0.4 - 2012-11-11
+
+* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
+* Bug: Cookies can now be added that have a name, domain, or value set to "0"
+* Bug: Using the system cacert bundle when using the Phar
+* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
+* Enhanced cookie jar de-duplication
+* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
+* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
+* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
+
+## 3.0.3 - 2012-11-04
+
+* Implementing redirects in PHP rather than cURL
+* Added PECL URI template extension and using as default parser if available
+* Bug: Fixed Content-Length parsing of Response factory
+* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
+* Adding ToArrayInterface throughout library
+* Fixing OauthPlugin to create unique nonce values per request
+
+## 3.0.2 - 2012-10-25
+
+* Magic methods are enabled by default on clients
+* Magic methods return the result of a command
+* Service clients no longer require a base_url option in the factory
+* Bug: Fixed an issue with URI templates where null template variables were being expanded
+
+## 3.0.1 - 2012-10-22
+
+* Models can now be used like regular collection objects by calling filter, map, etc.
+* Models no longer require a Parameter structure or initial data in the constructor
+* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
+
+## 3.0.0 - 2012-10-15
+
+* Rewrote service description format to be based on Swagger
+ * Now based on JSON schema
+ * Added nested input structures and nested response models
+ * Support for JSON and XML input and output models
+ * Renamed `commands` to `operations`
+ * Removed dot class notation
+ * Removed custom types
+* Broke the project into smaller top-level namespaces to be more component friendly
+* Removed support for XML configs and descriptions. Use arrays or JSON files.
+* Removed the Validation component and Inspector
+* Moved all cookie code to Guzzle\Plugin\Cookie
+* Magic methods on a Guzzle\Service\Client now return the command un-executed.
+* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
+* Now shipping with cURL's CA certs and using it by default
+* Added previousResponse() method to response objects
+* No longer sending Accept and Accept-Encoding headers on every request
+* Only sending an Expect header by default when a payload is greater than 1MB
+* Added/moved client options:
+ * curl.blacklist to curl.option.blacklist
+ * Added ssl.certificate_authority
+* Added a Guzzle\Iterator component
+* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
+* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
+* Added a more robust caching plugin
+* Added setBody to response objects
+* Updating LogPlugin to use a more flexible MessageFormatter
+* Added a completely revamped build process
+* Cleaning up Collection class and removing default values from the get method
+* Fixed ZF2 cache adapters
+
+## 2.8.8 - 2012-10-15
+
+* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
+
+## 2.8.7 - 2012-09-30
+
+* Bug: Fixed config file aliases for JSON includes
+* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
+* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
+* Bug: Hardening request and response parsing to account for missing parts
+* Bug: Fixed PEAR packaging
+* Bug: Fixed Request::getInfo
+* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
+* Adding the ability for the namespace Iterator factory to look in multiple directories
+* Added more getters/setters/removers from service descriptions
+* Added the ability to remove POST fields from OAuth signatures
+* OAuth plugin now supports 2-legged OAuth
+
+## 2.8.6 - 2012-09-05
+
+* Added the ability to modify and build service descriptions
+* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
+* Added a `json` parameter location
+* Now allowing dot notation for classes in the CacheAdapterFactory
+* Using the union of two arrays rather than an array_merge when extending service builder services and service params
+* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
+ in service builder config files.
+* Services defined in two different config files that include one another will by default replace the previously
+ defined service, but you can now create services that extend themselves and merge their settings over the previous
+* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
+ '_default' with a default JSON configuration file.
+
+## 2.8.5 - 2012-08-29
+
+* Bug: Suppressed empty arrays from URI templates
+* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
+* Added support for HTTP responses that do not contain a reason phrase in the start-line
+* AbstractCommand commands are now invokable
+* Added a way to get the data used when signing an Oauth request before a request is sent
+
+## 2.8.4 - 2012-08-15
+
+* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
+* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
+* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
+* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
+* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
+* Added additional response status codes
+* Removed SSL information from the default User-Agent header
+* DELETE requests can now send an entity body
+* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
+* Added the ability of the MockPlugin to consume mocked request bodies
+* LogPlugin now exposes request and response objects in the extras array
+
+## 2.8.3 - 2012-07-30
+
+* Bug: Fixed a case where empty POST requests were sent as GET requests
+* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
+* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
+* Added multiple inheritance to service description commands
+* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()`
+* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
+* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
+
+## 2.8.2 - 2012-07-24
+
+* Bug: Query string values set to 0 are no longer dropped from the query string
+* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`
+* Bug: `+` is now treated as an encoded space when parsing query strings
+* QueryString and Collection performance improvements
+* Allowing dot notation for class paths in filters attribute of a service descriptions
+
+## 2.8.1 - 2012-07-16
+
+* Loosening Event Dispatcher dependency
+* POST redirects can now be customized using CURLOPT_POSTREDIR
+
+## 2.8.0 - 2012-07-15
+
+* BC: Guzzle\Http\Query
+ * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
+ * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
+ * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
+ * Changed the aggregation functions of QueryString to be static methods
+ * Can now use fromString() with querystrings that have a leading ?
+* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters
+* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
+* Cookies are no longer URL decoded by default
+* Bug: URI template variables set to null are no longer expanded
+
+## 2.7.2 - 2012-07-02
+
+* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
+* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
+* CachePlugin now allows for a custom request parameter function to check if a request can be cached
+* Bug fix: CachePlugin now only caches GET and HEAD requests by default
+* Bug fix: Using header glue when transferring headers over the wire
+* Allowing deeply nested arrays for composite variables in URI templates
+* Batch divisors can now return iterators or arrays
+
+## 2.7.1 - 2012-06-26
+
+* Minor patch to update version number in UA string
+* Updating build process
+
+## 2.7.0 - 2012-06-25
+
+* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
+* BC: Removed magic setX methods from commands
+* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
+* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
+* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
+* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
+* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
+* Added the ability to set POST fields and files in a service description
+* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
+* Adding a command.before_prepare event to clients
+* Added BatchClosureTransfer and BatchClosureDivisor
+* BatchTransferException now includes references to the batch divisor and transfer strategies
+* Fixed some tests so that they pass more reliably
+* Added Guzzle\Common\Log\ArrayLogAdapter
+
+## 2.6.6 - 2012-06-10
+
+* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
+* BC: Removing Guzzle\Service\Command\CommandSet
+* Adding generic batching system (replaces the batch queue plugin and command set)
+* Updating ZF cache and log adapters and now using ZF's composer repository
+* Bug: Setting the name of each ApiParam when creating through an ApiCommand
+* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
+* Bug: Changed the default cookie header casing back to 'Cookie'
+
+## 2.6.5 - 2012-06-03
+
+* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
+* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
+* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
+* BC: Renaming methods in the CookieJarInterface
+* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
+* Making the default glue for HTTP headers ';' instead of ','
+* Adding a removeValue to Guzzle\Http\Message\Header
+* Adding getCookies() to request interface.
+* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
+
+## 2.6.4 - 2012-05-30
+
+* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
+* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
+* Bug: Fixing magic method command calls on clients
+* Bug: Email constraint only validates strings
+* Bug: Aggregate POST fields when POST files are present in curl handle
+* Bug: Fixing default User-Agent header
+* Bug: Only appending or prepending parameters in commands if they are specified
+* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
+* Allowing the use of dot notation for class namespaces when using instance_of constraint
+* Added any_match validation constraint
+* Added an AsyncPlugin
+* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
+* Allowing the result of a command object to be changed
+* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
+
+## 2.6.3 - 2012-05-23
+
+* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
+* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
+* You can now use an array of data when creating PUT request bodies in the request factory.
+* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
+* [Http] Adding support for Content-Type in multipart POST uploads per upload
+* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
+* Adding more POST data operations for easier manipulation of POST data.
+* You can now set empty POST fields.
+* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
+* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
+* CS updates
+
+## 2.6.2 - 2012-05-19
+
+* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
+
+## 2.6.1 - 2012-05-19
+
+* [BC] Removing 'path' support in service descriptions. Use 'uri'.
+* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
+* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it.
+* [BC] Removing Guzzle\Common\XmlElement.
+* All commands, both dynamic and concrete, have ApiCommand objects.
+* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
+* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
+* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
+
+## 2.6.0 - 2012-05-15
+
+* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
+* [BC] Executing a Command returns the result of the command rather than the command
+* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
+* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
+* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
+* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
+* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
+* [BC] Guzzle\Guzzle is now deprecated
+* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
+* Adding Guzzle\Version class to give version information about Guzzle
+* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
+* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
+* ServiceDescription and ServiceBuilder are now cacheable using similar configs
+* Changing the format of XML and JSON service builder configs. Backwards compatible.
+* Cleaned up Cookie parsing
+* Trimming the default Guzzle User-Agent header
+* Adding a setOnComplete() method to Commands that is called when a command completes
+* Keeping track of requests that were mocked in the MockPlugin
+* Fixed a caching bug in the CacheAdapterFactory
+* Inspector objects can be injected into a Command object
+* Refactoring a lot of code and tests to be case insensitive when dealing with headers
+* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
+* Adding the ability to set global option overrides to service builder configs
+* Adding the ability to include other service builder config files from within XML and JSON files
+* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
+
+## 2.5.0 - 2012-05-08
+
+* Major performance improvements
+* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.
+* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
+* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}"
+* Added the ability to passed parameters to all requests created by a client
+* Added callback functionality to the ExponentialBackoffPlugin
+* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
+* Rewinding request stream bodies when retrying requests
+* Exception is thrown when JSON response body cannot be decoded
+* Added configurable magic method calls to clients and commands. This is off by default.
+* Fixed a defect that added a hash to every parsed URL part
+* Fixed duplicate none generation for OauthPlugin.
+* Emitting an event each time a client is generated by a ServiceBuilder
+* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
+* cache.* request parameters should be renamed to params.cache.*
+* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle.
+* Added the ability to disable type validation of service descriptions
+* ServiceDescriptions and ServiceBuilders are now Serializable
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/Dockerfile b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/Dockerfile
new file mode 100755
index 0000000..f6a0952
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/Dockerfile
@@ -0,0 +1,18 @@
+FROM composer:latest as setup
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+RUN set -xe \
+ && composer init --name=guzzlehttp/test --description="Simple project for testing Guzzle scripts" --author="Márk Sági-Kazár " --no-interaction \
+ && composer require guzzlehttp/guzzle
+
+
+FROM php:7.3
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+COPY --from=setup /guzzle /guzzle
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/LICENSE b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/LICENSE
new file mode 100755
index 0000000..50a177b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/README.md b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/README.md
new file mode 100755
index 0000000..a5ef18a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/README.md
@@ -0,0 +1,90 @@
+Guzzle, PHP HTTP client
+=======================
+
+[](https://github.com/guzzle/guzzle/releases)
+[](https://travis-ci.org/guzzle/guzzle)
+[](https://packagist.org/packages/guzzlehttp/guzzle)
+
+Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
+trivial to integrate with web services.
+
+- Simple interface for building query strings, POST requests, streaming large
+ uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
+ etc...
+- Can send both synchronous and asynchronous requests using the same interface.
+- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
+ to utilize other PSR-7 compatible libraries with Guzzle.
+- Abstracts away the underlying HTTP transport, allowing you to write
+ environment and transport agnostic code; i.e., no hard dependency on cURL,
+ PHP streams, sockets, or non-blocking event loops.
+- Middleware system allows you to augment and compose client behavior.
+
+```php
+$client = new \GuzzleHttp\Client();
+$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
+
+echo $response->getStatusCode(); # 200
+echo $response->getHeaderLine('content-type'); # 'application/json; charset=utf8'
+echo $response->getBody(); # '{"id": 1420053, "name": "guzzle", ...}'
+
+# Send an asynchronous request.
+$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
+$promise = $client->sendAsync($request)->then(function ($response) {
+ echo 'I completed! ' . $response->getBody();
+});
+
+$promise->wait();
+```
+
+## Help and docs
+
+- [Documentation](http://guzzlephp.org/)
+- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle)
+- [Gitter](https://gitter.im/guzzle/guzzle)
+
+
+## Installing Guzzle
+
+The recommended way to install Guzzle is through
+[Composer](http://getcomposer.org).
+
+```bash
+# Install Composer
+curl -sS https://getcomposer.org/installer | php
+```
+
+Next, run the Composer command to install the latest stable version of Guzzle:
+
+```bash
+composer require guzzlehttp/guzzle
+```
+
+After installing, you need to require Composer's autoloader:
+
+```php
+require 'vendor/autoload.php';
+```
+
+You can then later update Guzzle using composer:
+
+ ```bash
+composer update
+ ```
+
+
+## Version Guidance
+
+| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
+|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------|
+| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 |
+| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 |
+| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 |
+| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 |
+
+[guzzle-3-repo]: https://github.com/guzzle/guzzle3
+[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
+[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
+[guzzle-6-repo]: https://github.com/guzzle/guzzle
+[guzzle-3-docs]: http://guzzle3.readthedocs.org
+[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/
+[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/UPGRADING.md b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/UPGRADING.md
new file mode 100755
index 0000000..91d1dcc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/UPGRADING.md
@@ -0,0 +1,1203 @@
+Guzzle Upgrade Guide
+====================
+
+5.0 to 6.0
+----------
+
+Guzzle now uses [PSR-7](http://www.php-fig.org/psr/psr-7/) for HTTP messages.
+Due to the fact that these messages are immutable, this prompted a refactoring
+of Guzzle to use a middleware based system rather than an event system. Any
+HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be
+updated to work with the new immutable PSR-7 request and response objects. Any
+event listeners or subscribers need to be updated to become middleware
+functions that wrap handlers (or are injected into a
+`GuzzleHttp\HandlerStack`).
+
+- Removed `GuzzleHttp\BatchResults`
+- Removed `GuzzleHttp\Collection`
+- Removed `GuzzleHttp\HasDataTrait`
+- Removed `GuzzleHttp\ToArrayInterface`
+- The `guzzlehttp/streams` dependency has been removed. Stream functionality
+ is now present in the `GuzzleHttp\Psr7` namespace provided by the
+ `guzzlehttp/psr7` package.
+- Guzzle no longer uses ReactPHP promises and now uses the
+ `guzzlehttp/promises` library. We use a custom promise library for three
+ significant reasons:
+ 1. React promises (at the time of writing this) are recursive. Promise
+ chaining and promise resolution will eventually blow the stack. Guzzle
+ promises are not recursive as they use a sort of trampolining technique.
+ Note: there has been movement in the React project to modify promises to
+ no longer utilize recursion.
+ 2. Guzzle needs to have the ability to synchronously block on a promise to
+ wait for a result. Guzzle promises allows this functionality (and does
+ not require the use of recursion).
+ 3. Because we need to be able to wait on a result, doing so using React
+ promises requires wrapping react promises with RingPHP futures. This
+ overhead is no longer needed, reducing stack sizes, reducing complexity,
+ and improving performance.
+- `GuzzleHttp\Mimetypes` has been moved to a function in
+ `GuzzleHttp\Psr7\mimetype_from_extension` and
+ `GuzzleHttp\Psr7\mimetype_from_filename`.
+- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
+ strings must now be passed into request objects as strings, or provided to
+ the `query` request option when creating requests with clients. The `query`
+ option uses PHP's `http_build_query` to convert an array to a string. If you
+ need a different serialization technique, you will need to pass the query
+ string in as a string. There are a couple helper functions that will make
+ working with query strings easier: `GuzzleHttp\Psr7\parse_query` and
+ `GuzzleHttp\Psr7\build_query`.
+- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
+ system based on PSR-7, using RingPHP and it's middleware system as well adds
+ more complexity than the benefits it provides. All HTTP handlers that were
+ present in RingPHP have been modified to work directly with PSR-7 messages
+ and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces
+ complexity in Guzzle, removes a dependency, and improves performance. RingPHP
+ will be maintained for Guzzle 5 support, but will no longer be a part of
+ Guzzle 6.
+- As Guzzle now uses a middleware based systems the event system and RingPHP
+ integration has been removed. Note: while the event system has been removed,
+ it is possible to add your own type of event system that is powered by the
+ middleware system.
+ - Removed the `Event` namespace.
+ - Removed the `Subscriber` namespace.
+ - Removed `Transaction` class
+ - Removed `RequestFsm`
+ - Removed `RingBridge`
+ - `GuzzleHttp\Subscriber\Cookie` is now provided by
+ `GuzzleHttp\Middleware::cookies`
+ - `GuzzleHttp\Subscriber\HttpError` is now provided by
+ `GuzzleHttp\Middleware::httpError`
+ - `GuzzleHttp\Subscriber\History` is now provided by
+ `GuzzleHttp\Middleware::history`
+ - `GuzzleHttp\Subscriber\Mock` is now provided by
+ `GuzzleHttp\Handler\MockHandler`
+ - `GuzzleHttp\Subscriber\Prepare` is now provided by
+ `GuzzleHttp\PrepareBodyMiddleware`
+ - `GuzzleHttp\Subscriber\Redirect` is now provided by
+ `GuzzleHttp\RedirectMiddleware`
+- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
+ `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
+- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
+ functions under the `GuzzleHttp` namespace. This requires either a Composer
+ based autoloader or you to include functions.php.
+- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
+ `GuzzleHttp\ClientInterface::getConfig`.
+- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
+- The `json` and `xml` methods of response objects has been removed. With the
+ migration to strictly adhering to PSR-7 as the interface for Guzzle messages,
+ adding methods to message interfaces would actually require Guzzle messages
+ to extend from PSR-7 messages rather then work with them directly.
+
+## Migrating to middleware
+
+The change to PSR-7 unfortunately required significant refactoring to Guzzle
+due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event
+system from plugins. The event system relied on mutability of HTTP messages and
+side effects in order to work. With immutable messages, you have to change your
+workflow to become more about either returning a value (e.g., functional
+middlewares) or setting a value on an object. Guzzle v6 has chosen the
+functional middleware approach.
+
+Instead of using the event system to listen for things like the `before` event,
+you now create a stack based middleware function that intercepts a request on
+the way in and the promise of the response on the way out. This is a much
+simpler and more predictable approach than the event system and works nicely
+with PSR-7 middleware. Due to the use of promises, the middleware system is
+also asynchronous.
+
+v5:
+
+```php
+use GuzzleHttp\Event\BeforeEvent;
+$client = new GuzzleHttp\Client();
+// Get the emitter and listen to the before event.
+$client->getEmitter()->on('before', function (BeforeEvent $e) {
+ // Guzzle v5 events relied on mutation
+ $e->getRequest()->setHeader('X-Foo', 'Bar');
+});
+```
+
+v6:
+
+In v6, you can modify the request before it is sent using the `mapRequest`
+middleware. The idiomatic way in v6 to modify the request/response lifecycle is
+to setup a handler middleware stack up front and inject the handler into a
+client.
+
+```php
+use GuzzleHttp\Middleware;
+// Create a handler stack that has all of the default middlewares attached
+$handler = GuzzleHttp\HandlerStack::create();
+// Push the handler onto the handler stack
+$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
+ // Notice that we have to return a request object
+ return $request->withHeader('X-Foo', 'Bar');
+}));
+// Inject the handler into the client
+$client = new GuzzleHttp\Client(['handler' => $handler]);
+```
+
+## POST Requests
+
+This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params)
+and `multipart` request options. `form_params` is an associative array of
+strings or array of strings and is used to serialize an
+`application/x-www-form-urlencoded` POST request. The
+[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart)
+option is now used to send a multipart/form-data POST request.
+
+`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
+POST files to a multipart/form-data request.
+
+The `body` option no longer accepts an array to send POST requests. Please use
+`multipart` or `form_params` instead.
+
+The `base_url` option has been renamed to `base_uri`.
+
+4.x to 5.0
+----------
+
+## Rewritten Adapter Layer
+
+Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send
+HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
+is still supported, but it has now been renamed to `handler`. Instead of
+passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
+`callable` that follows the RingPHP specification.
+
+## Removed Fluent Interfaces
+
+[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil)
+from the following classes:
+
+- `GuzzleHttp\Collection`
+- `GuzzleHttp\Url`
+- `GuzzleHttp\Query`
+- `GuzzleHttp\Post\PostBody`
+- `GuzzleHttp\Cookie\SetCookie`
+
+## Removed functions.php
+
+Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following
+functions can be used as replacements.
+
+- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode`
+- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
+- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
+- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
+ deprecated in favor of using `GuzzleHttp\Pool::batch()`.
+
+The "procedural" global client has been removed with no replacement (e.g.,
+`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client`
+object as a replacement.
+
+## `throwImmediately` has been removed
+
+The concept of "throwImmediately" has been removed from exceptions and error
+events. This control mechanism was used to stop a transfer of concurrent
+requests from completing. This can now be handled by throwing the exception or
+by cancelling a pool of requests or each outstanding future request
+individually.
+
+## headers event has been removed
+
+Removed the "headers" event. This event was only useful for changing the
+body a response once the headers of the response were known. You can implement
+a similar behavior in a number of ways. One example might be to use a
+FnStream that has access to the transaction being sent. For example, when the
+first byte is written, you could check if the response headers match your
+expectations, and if so, change the actual stream body that is being
+written to.
+
+## Updates to HTTP Messages
+
+Removed the `asArray` parameter from
+`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+value as an array, then use the newly added `getHeaderAsArray()` method of
+`MessageInterface`. This change makes the Guzzle interfaces compatible with
+the PSR-7 interfaces.
+
+3.x to 4.0
+----------
+
+## Overarching changes:
+
+- Now requires PHP 5.4 or greater.
+- No longer requires cURL to send requests.
+- Guzzle no longer wraps every exception it throws. Only exceptions that are
+ recoverable are now wrapped by Guzzle.
+- Various namespaces have been removed or renamed.
+- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
+ based on the Symfony EventDispatcher is
+ now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant
+ speed and functionality improvements).
+
+Changes per Guzzle 3.x namespace are described below.
+
+## Batch
+
+The `Guzzle\Batch` namespace has been removed. This is best left to
+third-parties to implement on top of Guzzle's core HTTP library.
+
+## Cache
+
+The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement
+has been implemented yet, but hoping to utilize a PSR cache interface).
+
+## Common
+
+- Removed all of the wrapped exceptions. It's better to use the standard PHP
+ library for unrecoverable exceptions.
+- `FromConfigInterface` has been removed.
+- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
+ at `GuzzleHttp\ClientInterface::VERSION`.
+
+### Collection
+
+- `getAll` has been removed. Use `toArray` to convert a collection to an array.
+- `inject` has been removed.
+- `keySearch` has been removed.
+- `getPath` no longer supports wildcard expressions. Use something better like
+ JMESPath for this.
+- `setPath` now supports appending to an existing array via the `[]` notation.
+
+### Events
+
+Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
+`GuzzleHttp\Event\Emitter`.
+
+- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by
+ `GuzzleHttp\Event\EmitterInterface`.
+- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
+ `GuzzleHttp\Event\Emitter`.
+- `Symfony\Component\EventDispatcher\Event` is replaced by
+ `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
+ `GuzzleHttp\Event\EventInterface`.
+- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
+ `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
+ event emitter of a request, client, etc. now uses the `getEmitter` method
+ rather than the `getDispatcher` method.
+
+#### Emitter
+
+- Use the `once()` method to add a listener that automatically removes itself
+ the first time it is invoked.
+- Use the `listeners()` method to retrieve a list of event listeners rather than
+ the `getListeners()` method.
+- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
+- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
+ `removeSubscriber()`.
+
+```php
+$mock = new Mock();
+// 3.x
+$request->getEventDispatcher()->addSubscriber($mock);
+$request->getEventDispatcher()->removeSubscriber($mock);
+// 4.x
+$request->getEmitter()->attach($mock);
+$request->getEmitter()->detach($mock);
+```
+
+Use the `on()` method to add a listener rather than the `addListener()` method.
+
+```php
+// 3.x
+$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
+// 4.x
+$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
+```
+
+## Http
+
+### General changes
+
+- The cacert.pem certificate has been moved to `src/cacert.pem`.
+- Added the concept of adapters that are used to transfer requests over the
+ wire.
+- Simplified the event system.
+- Sending requests in parallel is still possible, but batching is no longer a
+ concept of the HTTP layer. Instead, you must use the `complete` and `error`
+ events to asynchronously manage parallel request transfers.
+- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
+- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
+- QueryAggregators have been rewritten so that they are simply callable
+ functions.
+- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
+ `functions.php` for an easy to use static client instance.
+- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
+ `GuzzleHttp\Exception\TransferException`.
+
+### Client
+
+Calling methods like `get()`, `post()`, `head()`, etc. no longer create and
+return a request, but rather creates a request, sends the request, and returns
+the response.
+
+```php
+// 3.0
+$request = $client->get('/');
+$response = $request->send();
+
+// 4.0
+$response = $client->get('/');
+
+// or, to mirror the previous behavior
+$request = $client->createRequest('GET', '/');
+$response = $client->send($request);
+```
+
+`GuzzleHttp\ClientInterface` has changed.
+
+- The `send` method no longer accepts more than one request. Use `sendAll` to
+ send multiple requests in parallel.
+- `setUserAgent()` has been removed. Use a default request option instead. You
+ could, for example, do something like:
+ `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`.
+- `setSslVerification()` has been removed. Use default request options instead,
+ like `$client->setConfig('defaults/verify', true)`.
+
+`GuzzleHttp\Client` has changed.
+
+- The constructor now accepts only an associative array. You can include a
+ `base_url` string or array to use a URI template as the base URL of a client.
+ You can also specify a `defaults` key that is an associative array of default
+ request options. You can pass an `adapter` to use a custom adapter,
+ `batch_adapter` to use a custom adapter for sending requests in parallel, or
+ a `message_factory` to change the factory used to create HTTP requests and
+ responses.
+- The client no longer emits a `client.create_request` event.
+- Creating requests with a client no longer automatically utilize a URI
+ template. You must pass an array into a creational method (e.g.,
+ `createRequest`, `get`, `put`, etc.) in order to expand a URI template.
+
+### Messages
+
+Messages no longer have references to their counterparts (i.e., a request no
+longer has a reference to it's response, and a response no loger has a
+reference to its request). This association is now managed through a
+`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to
+these transaction objects using request events that are emitted over the
+lifecycle of a request.
+
+#### Requests with a body
+
+- `GuzzleHttp\Message\EntityEnclosingRequest` and
+ `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
+ separation between requests that contain a body and requests that do not
+ contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
+ handles both use cases.
+- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
+ `GuzzleHttp\Message\ResponseInterface`.
+- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
+ `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
+ both requests and responses and is implemented in
+ `GuzzleHttp\Message\MessageFactory`.
+- POST field and file methods have been removed from the request object. You
+ must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface`
+ to control the format of a POST body. Requests that are created using a
+ standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
+ a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
+ the method is POST and no body is provided.
+
+```php
+$request = $client->createRequest('POST', '/');
+$request->getBody()->setField('foo', 'bar');
+$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
+```
+
+#### Headers
+
+- `GuzzleHttp\Message\Header` has been removed. Header values are now simply
+ represented by an array of values or as a string. Header values are returned
+ as a string by default when retrieving a header value from a message. You can
+ pass an optional argument of `true` to retrieve a header value as an array
+ of strings instead of a single concatenated string.
+- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
+ `GuzzleHttp\Post`. This interface has been simplified and now allows the
+ addition of arbitrary headers.
+- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
+ of the custom headers are now handled separately in specific
+ subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
+ been updated to properly handle headers that contain parameters (like the
+ `Link` header).
+
+#### Responses
+
+- `GuzzleHttp\Message\Response::getInfo()` and
+ `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event
+ system to retrieve this type of information.
+- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed.
+- `GuzzleHttp\Message\Response::getMessage()` has been removed.
+- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific
+ methods have moved to the CacheSubscriber.
+- Header specific helper functions like `getContentMd5()` have been removed.
+ Just use `getHeader('Content-MD5')` instead.
+- `GuzzleHttp\Message\Response::setRequest()` and
+ `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event
+ system to work with request and response objects as a transaction.
+- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the
+ Redirect subscriber instead.
+- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have
+ been removed. Use `getStatusCode()` instead.
+
+#### Streaming responses
+
+Streaming requests can now be created by a client directly, returning a
+`GuzzleHttp\Message\ResponseInterface` object that contains a body stream
+referencing an open PHP HTTP stream.
+
+```php
+// 3.0
+use Guzzle\Stream\PhpStreamRequestFactory;
+$request = $client->get('/');
+$factory = new PhpStreamRequestFactory();
+$stream = $factory->fromRequest($request);
+$data = $stream->read(1024);
+
+// 4.0
+$response = $client->get('/', ['stream' => true]);
+// Read some data off of the stream in the response body
+$data = $response->getBody()->read(1024);
+```
+
+#### Redirects
+
+The `configureRedirects()` method has been removed in favor of a
+`allow_redirects` request option.
+
+```php
+// Standard redirects with a default of a max of 5 redirects
+$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);
+
+// Strict redirects with a custom number of redirects
+$request = $client->createRequest('GET', '/', [
+ 'allow_redirects' => ['max' => 5, 'strict' => true]
+]);
+```
+
+#### EntityBody
+
+EntityBody interfaces and classes have been removed or moved to
+`GuzzleHttp\Stream`. All classes and interfaces that once required
+`GuzzleHttp\EntityBodyInterface` now require
+`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no
+longer uses `GuzzleHttp\EntityBody::factory` but now uses
+`GuzzleHttp\Stream\Stream::factory` or even better:
+`GuzzleHttp\Stream\create()`.
+
+- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
+- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
+- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
+- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
+- `Guzzle\Http\IoEmittyinEntityBody` has been removed.
+
+#### Request lifecycle events
+
+Requests previously submitted a large number of requests. The number of events
+emitted over the lifecycle of a request has been significantly reduced to make
+it easier to understand how to extend the behavior of a request. All events
+emitted during the lifecycle of a request now emit a custom
+`GuzzleHttp\Event\EventInterface` object that contains context providing
+methods and a way in which to modify the transaction at that specific point in
+time (e.g., intercept the request and set a response on the transaction).
+
+- `request.before_send` has been renamed to `before` and now emits a
+ `GuzzleHttp\Event\BeforeEvent`
+- `request.complete` has been renamed to `complete` and now emits a
+ `GuzzleHttp\Event\CompleteEvent`.
+- `request.sent` has been removed. Use `complete`.
+- `request.success` has been removed. Use `complete`.
+- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
+- `request.exception` has been removed. Use `error`.
+- `request.receive.status_line` has been removed.
+- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
+ maintain a status update.
+- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
+ intercept writes.
+- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
+ intercept reads.
+
+`headers` is a new event that is emitted after the response headers of a
+request have been received before the body of the response is downloaded. This
+event emits a `GuzzleHttp\Event\HeadersEvent`.
+
+You can intercept a request and inject a response using the `intercept()` event
+of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
+`GuzzleHttp\Event\ErrorEvent` event.
+
+See: http://docs.guzzlephp.org/en/latest/events.html
+
+## Inflection
+
+The `Guzzle\Inflection` namespace has been removed. This is not a core concern
+of Guzzle.
+
+## Iterator
+
+The `Guzzle\Iterator` namespace has been removed.
+
+- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and
+ `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
+ Guzzle itself.
+- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
+ class is shipped with PHP 5.4.
+- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because
+ it's easier to just wrap an iterator in a generator that maps values.
+
+For a replacement of these iterators, see https://github.com/nikic/iter
+
+## Log
+
+The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
+`Guzzle\Log` namespace has been removed. Guzzle now relies on
+`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been
+moved to `GuzzleHttp\Subscriber\Log\Formatter`.
+
+## Parser
+
+The `Guzzle\Parser` namespace has been removed. This was previously used to
+make it possible to plug in custom parsers for cookies, messages, URI
+templates, and URLs; however, this level of complexity is not needed in Guzzle
+so it has been removed.
+
+- Cookie: Cookie parsing logic has been moved to
+ `GuzzleHttp\Cookie\SetCookie::fromString`.
+- Message: Message parsing logic for both requests and responses has been moved
+ to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only
+ used in debugging or deserializing messages, so it doesn't make sense for
+ Guzzle as a library to add this level of complexity to parsing messages.
+- UriTemplate: URI template parsing has been moved to
+ `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL
+ URI template library if it is installed.
+- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously
+ it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
+ then developers are free to subclass `GuzzleHttp\Url`.
+
+## Plugin
+
+The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`.
+Several plugins are shipping with the core Guzzle library under this namespace.
+
+- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar
+ code has moved to `GuzzleHttp\Cookie`.
+- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin.
+- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is
+ received.
+- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin.
+- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before
+ sending. This subscriber is attached to all requests by default.
+- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin.
+
+The following plugins have been removed (third-parties are free to re-implement
+these if needed):
+
+- `GuzzleHttp\Plugin\Async` has been removed.
+- `GuzzleHttp\Plugin\CurlAuth` has been removed.
+- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This
+ functionality should instead be implemented with event listeners that occur
+ after normal response parsing occurs in the guzzle/command package.
+
+The following plugins are not part of the core Guzzle package, but are provided
+in separate repositories:
+
+- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
+ to build custom retry policies using simple functions rather than various
+ chained classes. See: https://github.com/guzzle/retry-subscriber
+- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
+ https://github.com/guzzle/cache-subscriber
+- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
+ https://github.com/guzzle/log-subscriber
+- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
+ https://github.com/guzzle/message-integrity-subscriber
+- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
+ `GuzzleHttp\Subscriber\MockSubscriber`.
+- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to
+ https://github.com/guzzle/oauth-subscriber
+
+## Service
+
+The service description layer of Guzzle has moved into two separate packages:
+
+- http://github.com/guzzle/command Provides a high level abstraction over web
+ services by representing web service operations using commands.
+- http://github.com/guzzle/guzzle-services Provides an implementation of
+ guzzle/command that provides request serialization and response parsing using
+ Guzzle service descriptions.
+
+## Stream
+
+Stream have moved to a separate package available at
+https://github.com/guzzle/streams.
+
+`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take
+on the responsibilities of `Guzzle\Http\EntityBody` and
+`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number
+of methods implemented by the `StreamInterface` has been drastically reduced to
+allow developers to more easily extend and decorate stream behavior.
+
+## Removed methods from StreamInterface
+
+- `getStream` and `setStream` have been removed to better encapsulate streams.
+- `getMetadata` and `setMetadata` have been removed in favor of
+ `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
+ removed. This data is accessible when
+ using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `rewind` has been removed. Use `seek(0)` for a similar behavior.
+
+## Renamed methods
+
+- `detachStream` has been renamed to `detach`.
+- `feof` has been renamed to `eof`.
+- `ftell` has been renamed to `tell`.
+- `readLine` has moved from an instance method to a static class method of
+ `GuzzleHttp\Stream\Stream`.
+
+## Metadata streams
+
+`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
+that contain additional metadata accessible via `getMetadata()`.
+`GuzzleHttp\Stream\StreamInterface::getMetadata` and
+`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.
+
+## StreamRequestFactory
+
+The entire concept of the StreamRequestFactory has been removed. The way this
+was used in Guzzle 3 broke the actual interface of sending streaming requests
+(instead of getting back a Response, you got a StreamInterface). Streaming
+PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.
+
+3.6 to 3.7
+----------
+
+### Deprecations
+
+- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
+
+```php
+\Guzzle\Common\Version::$emitWarnings = true;
+```
+
+The following APIs and options have been marked as deprecated:
+
+- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+- Marked `Guzzle\Common\Collection::inject()` as deprecated.
+- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
+ `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
+ `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
+
+3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
+request methods. When paired with a client's configuration settings, these options allow you to specify default settings
+for various aspects of a request. Because these options make other previous configuration options redundant, several
+configuration options and methods of a client and AbstractCommand have been deprecated.
+
+- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
+- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
+- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
+- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
+
+ $command = $client->getCommand('foo', array(
+ 'command.headers' => array('Test' => '123'),
+ 'command.response_body' => '/path/to/file'
+ ));
+
+ // Should be changed to:
+
+ $command = $client->getCommand('foo', array(
+ 'command.request_options' => array(
+ 'headers' => array('Test' => '123'),
+ 'save_as' => '/path/to/file'
+ )
+ ));
+
+### Interface changes
+
+Additions and changes (you will need to update any implementations or subclasses you may have created):
+
+- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+- Added `Guzzle\Stream\StreamInterface::isRepeatable`
+- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+
+The following methods were removed from interfaces. All of these methods are still available in the concrete classes
+that implement them, but you should update your code to use alternative methods:
+
+- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
+ `$client->setDefaultOption('headers/{header_name}', 'value')`. or
+ `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
+- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
+- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
+- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
+
+### Cache plugin breaking changes
+
+- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+- Always setting X-cache headers on cached responses
+- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+- Added `CacheStorageInterface::purge($url)`
+- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+3.5 to 3.6
+----------
+
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+ For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
+ Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Moved getLinks() from Response to just be used on a Link header object.
+
+If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
+HeaderInterface (e.g. toArray(), getAll(), etc.).
+
+### Interface changes
+
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+
+### Removed deprecated functions
+
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+
+### Deprecations
+
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+
+### Other changes
+
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+
+3.3 to 3.4
+----------
+
+Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
+
+3.2 to 3.3
+----------
+
+### Response::getEtag() quote stripping removed
+
+`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
+
+### Removed `Guzzle\Http\Utils`
+
+The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
+
+### Stream wrapper and type
+
+`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase.
+
+### curl.emit_io became emit_io
+
+Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
+'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+
+3.1 to 3.2
+----------
+
+### CurlMulti is no longer reused globally
+
+Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
+to a single client can pollute requests dispatched from other clients.
+
+If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
+ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
+created.
+
+```php
+$multi = new Guzzle\Http\Curl\CurlMulti();
+$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
+$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
+ $event['client']->setCurlMulti($multi);
+}
+});
+```
+
+### No default path
+
+URLs no longer have a default path value of '/' if no path was specified.
+
+Before:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com/
+```
+
+After:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com
+```
+
+### Less verbose BadResponseException
+
+The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
+response information. You can, however, get access to the request and response object by calling `getRequest()` or
+`getResponse()` on the exception object.
+
+### Query parameter aggregation
+
+Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
+setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
+responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
+
+2.8 to 3.x
+----------
+
+### Guzzle\Service\Inspector
+
+Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
+
+**Before**
+
+```php
+use Guzzle\Service\Inspector;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Inspector::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+**After**
+
+```php
+use Guzzle\Common\Collection;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Collection::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+### Convert XML Service Descriptions to JSON
+
+**Before**
+
+```xml
+
+
+
+
+
+ Get a list of groups
+
+
+ Uses a search query to get a list of groups
+
+
+
+ Create a group
+
+
+
+
+ Delete a group by ID
+
+
+
+
+
+
+ Update a group
+
+
+
+
+
+
+```
+
+**After**
+
+```json
+{
+ "name": "Zendesk REST API v2",
+ "apiVersion": "2012-12-31",
+ "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
+ "operations": {
+ "list_groups": {
+ "httpMethod":"GET",
+ "uri": "groups.json",
+ "summary": "Get a list of groups"
+ },
+ "search_groups":{
+ "httpMethod":"GET",
+ "uri": "search.json?query=\"{query} type:group\"",
+ "summary": "Uses a search query to get a list of groups",
+ "parameters":{
+ "query":{
+ "location": "uri",
+ "description":"Zendesk Search Query",
+ "type": "string",
+ "required": true
+ }
+ }
+ },
+ "create_group": {
+ "httpMethod":"POST",
+ "uri": "groups.json",
+ "summary": "Create a group",
+ "parameters":{
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ },
+ "delete_group": {
+ "httpMethod":"DELETE",
+ "uri": "groups/{id}.json",
+ "summary": "Delete a group",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to delete by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "get_group": {
+ "httpMethod":"GET",
+ "uri": "groups/{id}.json",
+ "summary": "Get a ticket",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to get by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "update_group": {
+ "httpMethod":"PUT",
+ "uri": "groups/{id}.json",
+ "summary": "Update a group",
+ "parameters":{
+ "id": {
+ "location": "uri",
+ "description":"Group to update by ID",
+ "type": "integer",
+ "required": true
+ },
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ }
+}
+```
+
+### Guzzle\Service\Description\ServiceDescription
+
+Commands are now called Operations
+
+**Before**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getCommands(); // @returns ApiCommandInterface[]
+$sd->hasCommand($name);
+$sd->getCommand($name); // @returns ApiCommandInterface|null
+$sd->addCommand($command); // @param ApiCommandInterface $command
+```
+
+**After**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getOperations(); // @returns OperationInterface[]
+$sd->hasOperation($name);
+$sd->getOperation($name); // @returns OperationInterface|null
+$sd->addOperation($operation); // @param OperationInterface $operation
+```
+
+### Guzzle\Common\Inflection\Inflector
+
+Namespace is now `Guzzle\Inflection\Inflector`
+
+### Guzzle\Http\Plugin
+
+Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
+
+### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
+
+Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
+
+**Before**
+
+```php
+use Guzzle\Common\Log\ClosureLogAdapter;
+use Guzzle\Http\Plugin\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $verbosity is an integer indicating desired message verbosity level
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
+```
+
+**After**
+
+```php
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Log\MessageFormatter;
+use Guzzle\Plugin\Log\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $format is a string indicating desired message format -- @see MessageFormatter
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
+```
+
+### Guzzle\Http\Plugin\CurlAuthPlugin
+
+Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
+
+### Guzzle\Http\Plugin\ExponentialBackoffPlugin
+
+Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
+
+**Before**
+
+```php
+use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
+
+$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
+ ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
+ ));
+
+$client->addSubscriber($backoffPlugin);
+```
+
+**After**
+
+```php
+use Guzzle\Plugin\Backoff\BackoffPlugin;
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+
+// Use convenient factory method instead -- see implementation for ideas of what
+// you can do with chaining backoff strategies
+$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
+ HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
+ ));
+$client->addSubscriber($backoffPlugin);
+```
+
+### Known Issues
+
+#### [BUG] Accept-Encoding header behavior changed unintentionally.
+
+(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
+
+In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
+properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
+See issue #217 for a workaround, or use a version containing the fix.
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/composer.json b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/composer.json
new file mode 100755
index 0000000..c553257
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/composer.json
@@ -0,0 +1,58 @@
+{
+ "name": "guzzlehttp/guzzle",
+ "type": "library",
+ "description": "Guzzle is a PHP HTTP client library",
+ "keywords": [
+ "framework",
+ "http",
+ "rest",
+ "web service",
+ "curl",
+ "client",
+ "HTTP client"
+ ],
+ "homepage": "http://guzzlephp.org/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.5",
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.6.1"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/phpstan.neon.dist b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/phpstan.neon.dist
new file mode 100755
index 0000000..4ef4192
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/phpstan.neon.dist
@@ -0,0 +1,9 @@
+parameters:
+ level: 1
+ paths:
+ - src
+
+ ignoreErrors:
+ -
+ message: '#Function uri_template not found#'
+ path: %currentWorkingDirectory%/src/functions.php
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Client.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Client.php
new file mode 100755
index 0000000..0f43c71
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Client.php
@@ -0,0 +1,422 @@
+ 'http://www.foo.com/1.0/',
+ * 'timeout' => 0,
+ * 'allow_redirects' => false,
+ * 'proxy' => '192.168.16.1:10'
+ * ]);
+ *
+ * Client configuration settings include the following options:
+ *
+ * - handler: (callable) Function that transfers HTTP requests over the
+ * wire. The function is called with a Psr7\Http\Message\RequestInterface
+ * and array of transfer options, and must return a
+ * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
+ * Psr7\Http\Message\ResponseInterface on success. "handler" is a
+ * constructor only option that cannot be overridden in per/request
+ * options. If no handler is provided, a default handler will be created
+ * that enables all of the request options below by attaching all of the
+ * default middleware to the handler.
+ * - base_uri: (string|UriInterface) Base URI of the client that is merged
+ * into relative URIs. Can be a string or instance of UriInterface.
+ * - **: any request option
+ *
+ * @param array $config Client configuration settings.
+ *
+ * @see \GuzzleHttp\RequestOptions for a list of available request options.
+ */
+ public function __construct(array $config = [])
+ {
+ if (!isset($config['handler'])) {
+ $config['handler'] = HandlerStack::create();
+ } elseif (!is_callable($config['handler'])) {
+ throw new \InvalidArgumentException('handler must be a callable');
+ }
+
+ // Convert the base_uri to a UriInterface
+ if (isset($config['base_uri'])) {
+ $config['base_uri'] = Psr7\uri_for($config['base_uri']);
+ }
+
+ $this->configureDefaults($config);
+ }
+
+ public function __call($method, $args)
+ {
+ if (count($args) < 1) {
+ throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
+ }
+
+ $uri = $args[0];
+ $opts = isset($args[1]) ? $args[1] : [];
+
+ return substr($method, -5) === 'Async'
+ ? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
+ : $this->request($method, $uri, $opts);
+ }
+
+ public function sendAsync(RequestInterface $request, array $options = [])
+ {
+ // Merge the base URI into the request URI if needed.
+ $options = $this->prepareDefaults($options);
+
+ return $this->transfer(
+ $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
+ $options
+ );
+ }
+
+ public function send(RequestInterface $request, array $options = [])
+ {
+ $options[RequestOptions::SYNCHRONOUS] = true;
+ return $this->sendAsync($request, $options)->wait();
+ }
+
+ public function requestAsync($method, $uri = '', array $options = [])
+ {
+ $options = $this->prepareDefaults($options);
+ // Remove request modifying parameter because it can be done up-front.
+ $headers = isset($options['headers']) ? $options['headers'] : [];
+ $body = isset($options['body']) ? $options['body'] : null;
+ $version = isset($options['version']) ? $options['version'] : '1.1';
+ // Merge the URI into the base URI.
+ $uri = $this->buildUri($uri, $options);
+ if (is_array($body)) {
+ $this->invalidBody();
+ }
+ $request = new Psr7\Request($method, $uri, $headers, $body, $version);
+ // Remove the option so that they are not doubly-applied.
+ unset($options['headers'], $options['body'], $options['version']);
+
+ return $this->transfer($request, $options);
+ }
+
+ public function request($method, $uri = '', array $options = [])
+ {
+ $options[RequestOptions::SYNCHRONOUS] = true;
+ return $this->requestAsync($method, $uri, $options)->wait();
+ }
+
+ public function getConfig($option = null)
+ {
+ return $option === null
+ ? $this->config
+ : (isset($this->config[$option]) ? $this->config[$option] : null);
+ }
+
+ private function buildUri($uri, array $config)
+ {
+ // for BC we accept null which would otherwise fail in uri_for
+ $uri = Psr7\uri_for($uri === null ? '' : $uri);
+
+ if (isset($config['base_uri'])) {
+ $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
+ }
+
+ return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
+ }
+
+ /**
+ * Configures the default options for a client.
+ *
+ * @param array $config
+ */
+ private function configureDefaults(array $config)
+ {
+ $defaults = [
+ 'allow_redirects' => RedirectMiddleware::$defaultSettings,
+ 'http_errors' => true,
+ 'decode_content' => true,
+ 'verify' => true,
+ 'cookies' => false
+ ];
+
+ // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
+
+ // We can only trust the HTTP_PROXY environment variable in a CLI
+ // process due to the fact that PHP has no reliable mechanism to
+ // get environment variables that start with "HTTP_".
+ if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
+ $defaults['proxy']['http'] = getenv('HTTP_PROXY');
+ }
+
+ if ($proxy = getenv('HTTPS_PROXY')) {
+ $defaults['proxy']['https'] = $proxy;
+ }
+
+ if ($noProxy = getenv('NO_PROXY')) {
+ $cleanedNoProxy = str_replace(' ', '', $noProxy);
+ $defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
+ }
+
+ $this->config = $config + $defaults;
+
+ if (!empty($config['cookies']) && $config['cookies'] === true) {
+ $this->config['cookies'] = new CookieJar();
+ }
+
+ // Add the default user-agent header.
+ if (!isset($this->config['headers'])) {
+ $this->config['headers'] = ['User-Agent' => default_user_agent()];
+ } else {
+ // Add the User-Agent header if one was not already set.
+ foreach (array_keys($this->config['headers']) as $name) {
+ if (strtolower($name) === 'user-agent') {
+ return;
+ }
+ }
+ $this->config['headers']['User-Agent'] = default_user_agent();
+ }
+ }
+
+ /**
+ * Merges default options into the array.
+ *
+ * @param array $options Options to modify by reference
+ *
+ * @return array
+ */
+ private function prepareDefaults(array $options)
+ {
+ $defaults = $this->config;
+
+ if (!empty($defaults['headers'])) {
+ // Default headers are only added if they are not present.
+ $defaults['_conditional'] = $defaults['headers'];
+ unset($defaults['headers']);
+ }
+
+ // Special handling for headers is required as they are added as
+ // conditional headers and as headers passed to a request ctor.
+ if (array_key_exists('headers', $options)) {
+ // Allows default headers to be unset.
+ if ($options['headers'] === null) {
+ $defaults['_conditional'] = null;
+ unset($options['headers']);
+ } elseif (!is_array($options['headers'])) {
+ throw new \InvalidArgumentException('headers must be an array');
+ }
+ }
+
+ // Shallow merge defaults underneath options.
+ $result = $options + $defaults;
+
+ // Remove null values.
+ foreach ($result as $k => $v) {
+ if ($v === null) {
+ unset($result[$k]);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Transfers the given request and applies request options.
+ *
+ * The URI of the request is not modified and the request options are used
+ * as-is without merging in default options.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return Promise\PromiseInterface
+ */
+ private function transfer(RequestInterface $request, array $options)
+ {
+ // save_to -> sink
+ if (isset($options['save_to'])) {
+ $options['sink'] = $options['save_to'];
+ unset($options['save_to']);
+ }
+
+ // exceptions -> http_errors
+ if (isset($options['exceptions'])) {
+ $options['http_errors'] = $options['exceptions'];
+ unset($options['exceptions']);
+ }
+
+ $request = $this->applyOptions($request, $options);
+ $handler = $options['handler'];
+
+ try {
+ return Promise\promise_for($handler($request, $options));
+ } catch (\Exception $e) {
+ return Promise\rejection_for($e);
+ }
+ }
+
+ /**
+ * Applies the array of request options to a request.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return RequestInterface
+ */
+ private function applyOptions(RequestInterface $request, array &$options)
+ {
+ $modify = [
+ 'set_headers' => [],
+ ];
+
+ if (isset($options['headers'])) {
+ $modify['set_headers'] = $options['headers'];
+ unset($options['headers']);
+ }
+
+ if (isset($options['form_params'])) {
+ if (isset($options['multipart'])) {
+ throw new \InvalidArgumentException('You cannot use '
+ . 'form_params and multipart at the same time. Use the '
+ . 'form_params option if you want to send application/'
+ . 'x-www-form-urlencoded requests, and the multipart '
+ . 'option to send multipart/form-data requests.');
+ }
+ $options['body'] = http_build_query($options['form_params'], '', '&');
+ unset($options['form_params']);
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
+ }
+
+ if (isset($options['multipart'])) {
+ $options['body'] = new Psr7\MultipartStream($options['multipart']);
+ unset($options['multipart']);
+ }
+
+ if (isset($options['json'])) {
+ $options['body'] = \GuzzleHttp\json_encode($options['json']);
+ unset($options['json']);
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'application/json';
+ }
+
+ if (!empty($options['decode_content'])
+ && $options['decode_content'] !== true
+ ) {
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']);
+ $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
+ }
+
+ if (isset($options['body'])) {
+ if (is_array($options['body'])) {
+ $this->invalidBody();
+ }
+ $modify['body'] = Psr7\stream_for($options['body']);
+ unset($options['body']);
+ }
+
+ if (!empty($options['auth']) && is_array($options['auth'])) {
+ $value = $options['auth'];
+ $type = isset($value[2]) ? strtolower($value[2]) : 'basic';
+ switch ($type) {
+ case 'basic':
+ // Ensure that we don't have the header in different case and set the new value.
+ $modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']);
+ $modify['set_headers']['Authorization'] = 'Basic '
+ . base64_encode("$value[0]:$value[1]");
+ break;
+ case 'digest':
+ // @todo: Do not rely on curl
+ $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
+ $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
+ break;
+ case 'ntlm':
+ $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
+ $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
+ break;
+ }
+ }
+
+ if (isset($options['query'])) {
+ $value = $options['query'];
+ if (is_array($value)) {
+ $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
+ }
+ if (!is_string($value)) {
+ throw new \InvalidArgumentException('query must be a string or array');
+ }
+ $modify['query'] = $value;
+ unset($options['query']);
+ }
+
+ // Ensure that sink is not an invalid value.
+ if (isset($options['sink'])) {
+ // TODO: Add more sink validation?
+ if (is_bool($options['sink'])) {
+ throw new \InvalidArgumentException('sink must not be a boolean');
+ }
+ }
+
+ $request = Psr7\modify_request($request, $modify);
+ if ($request->getBody() instanceof Psr7\MultipartStream) {
+ // Use a multipart/form-data POST if a Content-Type is not set.
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
+ . $request->getBody()->getBoundary();
+ }
+
+ // Merge in conditional headers if they are not present.
+ if (isset($options['_conditional'])) {
+ // Build up the changes so it's in a single clone of the message.
+ $modify = [];
+ foreach ($options['_conditional'] as $k => $v) {
+ if (!$request->hasHeader($k)) {
+ $modify['set_headers'][$k] = $v;
+ }
+ }
+ $request = Psr7\modify_request($request, $modify);
+ // Don't pass this internal value along to middleware/handlers.
+ unset($options['_conditional']);
+ }
+
+ return $request;
+ }
+
+ private function invalidBody()
+ {
+ throw new \InvalidArgumentException('Passing in the "body" request '
+ . 'option as an array to send a POST request has been deprecated. '
+ . 'Please use the "form_params" request option to send a '
+ . 'application/x-www-form-urlencoded request, or the "multipart" '
+ . 'request option to send a multipart/form-data request.');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/ClientInterface.php
new file mode 100755
index 0000000..5b37085
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/ClientInterface.php
@@ -0,0 +1,84 @@
+strictMode = $strictMode;
+
+ foreach ($cookieArray as $cookie) {
+ if (!($cookie instanceof SetCookie)) {
+ $cookie = new SetCookie($cookie);
+ }
+ $this->setCookie($cookie);
+ }
+ }
+
+ /**
+ * Create a new Cookie jar from an associative array and domain.
+ *
+ * @param array $cookies Cookies to create the jar from
+ * @param string $domain Domain to set the cookies to
+ *
+ * @return self
+ */
+ public static function fromArray(array $cookies, $domain)
+ {
+ $cookieJar = new self();
+ foreach ($cookies as $name => $value) {
+ $cookieJar->setCookie(new SetCookie([
+ 'Domain' => $domain,
+ 'Name' => $name,
+ 'Value' => $value,
+ 'Discard' => true
+ ]));
+ }
+
+ return $cookieJar;
+ }
+
+ /**
+ * @deprecated
+ */
+ public static function getCookieValue($value)
+ {
+ return $value;
+ }
+
+ /**
+ * Evaluate if this cookie should be persisted to storage
+ * that survives between requests.
+ *
+ * @param SetCookie $cookie Being evaluated.
+ * @param bool $allowSessionCookies If we should persist session cookies
+ * @return bool
+ */
+ public static function shouldPersist(
+ SetCookie $cookie,
+ $allowSessionCookies = false
+ ) {
+ if ($cookie->getExpires() || $allowSessionCookies) {
+ if (!$cookie->getDiscard()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Finds and returns the cookie based on the name
+ *
+ * @param string $name cookie name to search for
+ * @return SetCookie|null cookie that was found or null if not found
+ */
+ public function getCookieByName($name)
+ {
+ // don't allow a null name
+ if ($name === null) {
+ return null;
+ }
+ foreach ($this->cookies as $cookie) {
+ if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) {
+ return $cookie;
+ }
+ }
+ }
+
+ public function toArray()
+ {
+ return array_map(function (SetCookie $cookie) {
+ return $cookie->toArray();
+ }, $this->getIterator()->getArrayCopy());
+ }
+
+ public function clear($domain = null, $path = null, $name = null)
+ {
+ if (!$domain) {
+ $this->cookies = [];
+ return;
+ } elseif (!$path) {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($domain) {
+ return !$cookie->matchesDomain($domain);
+ }
+ );
+ } elseif (!$name) {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($path, $domain) {
+ return !($cookie->matchesPath($path) &&
+ $cookie->matchesDomain($domain));
+ }
+ );
+ } else {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($path, $domain, $name) {
+ return !($cookie->getName() == $name &&
+ $cookie->matchesPath($path) &&
+ $cookie->matchesDomain($domain));
+ }
+ );
+ }
+ }
+
+ public function clearSessionCookies()
+ {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) {
+ return !$cookie->getDiscard() && $cookie->getExpires();
+ }
+ );
+ }
+
+ public function setCookie(SetCookie $cookie)
+ {
+ // If the name string is empty (but not 0), ignore the set-cookie
+ // string entirely.
+ $name = $cookie->getName();
+ if (!$name && $name !== '0') {
+ return false;
+ }
+
+ // Only allow cookies with set and valid domain, name, value
+ $result = $cookie->validate();
+ if ($result !== true) {
+ if ($this->strictMode) {
+ throw new \RuntimeException('Invalid cookie: ' . $result);
+ } else {
+ $this->removeCookieIfEmpty($cookie);
+ return false;
+ }
+ }
+
+ // Resolve conflicts with previously set cookies
+ foreach ($this->cookies as $i => $c) {
+
+ // Two cookies are identical, when their path, and domain are
+ // identical.
+ if ($c->getPath() != $cookie->getPath() ||
+ $c->getDomain() != $cookie->getDomain() ||
+ $c->getName() != $cookie->getName()
+ ) {
+ continue;
+ }
+
+ // The previously set cookie is a discard cookie and this one is
+ // not so allow the new cookie to be set
+ if (!$cookie->getDiscard() && $c->getDiscard()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the new cookie's expiration is further into the future, then
+ // replace the old cookie
+ if ($cookie->getExpires() > $c->getExpires()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the value has changed, we better change it
+ if ($cookie->getValue() !== $c->getValue()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // The cookie exists, so no need to continue
+ return false;
+ }
+
+ $this->cookies[] = $cookie;
+
+ return true;
+ }
+
+ public function count()
+ {
+ return count($this->cookies);
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator(array_values($this->cookies));
+ }
+
+ public function extractCookies(
+ RequestInterface $request,
+ ResponseInterface $response
+ ) {
+ if ($cookieHeader = $response->getHeader('Set-Cookie')) {
+ foreach ($cookieHeader as $cookie) {
+ $sc = SetCookie::fromString($cookie);
+ if (!$sc->getDomain()) {
+ $sc->setDomain($request->getUri()->getHost());
+ }
+ if (0 !== strpos($sc->getPath(), '/')) {
+ $sc->setPath($this->getCookiePathFromRequest($request));
+ }
+ $this->setCookie($sc);
+ }
+ }
+ }
+
+ /**
+ * Computes cookie path following RFC 6265 section 5.1.4
+ *
+ * @link https://tools.ietf.org/html/rfc6265#section-5.1.4
+ *
+ * @param RequestInterface $request
+ * @return string
+ */
+ private function getCookiePathFromRequest(RequestInterface $request)
+ {
+ $uriPath = $request->getUri()->getPath();
+ if ('' === $uriPath) {
+ return '/';
+ }
+ if (0 !== strpos($uriPath, '/')) {
+ return '/';
+ }
+ if ('/' === $uriPath) {
+ return '/';
+ }
+ if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
+ return '/';
+ }
+
+ return substr($uriPath, 0, $lastSlashPos);
+ }
+
+ public function withCookieHeader(RequestInterface $request)
+ {
+ $values = [];
+ $uri = $request->getUri();
+ $scheme = $uri->getScheme();
+ $host = $uri->getHost();
+ $path = $uri->getPath() ?: '/';
+
+ foreach ($this->cookies as $cookie) {
+ if ($cookie->matchesPath($path) &&
+ $cookie->matchesDomain($host) &&
+ !$cookie->isExpired() &&
+ (!$cookie->getSecure() || $scheme === 'https')
+ ) {
+ $values[] = $cookie->getName() . '='
+ . $cookie->getValue();
+ }
+ }
+
+ return $values
+ ? $request->withHeader('Cookie', implode('; ', $values))
+ : $request;
+ }
+
+ /**
+ * If a cookie already exists and the server asks to set it again with a
+ * null value, the cookie must be deleted.
+ *
+ * @param SetCookie $cookie
+ */
+ private function removeCookieIfEmpty(SetCookie $cookie)
+ {
+ $cookieValue = $cookie->getValue();
+ if ($cookieValue === null || $cookieValue === '') {
+ $this->clear(
+ $cookie->getDomain(),
+ $cookie->getPath(),
+ $cookie->getName()
+ );
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
new file mode 100755
index 0000000..2cf298a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
@@ -0,0 +1,84 @@
+filename = $cookieFile;
+ $this->storeSessionCookies = $storeSessionCookies;
+
+ if (file_exists($cookieFile)) {
+ $this->load($cookieFile);
+ }
+ }
+
+ /**
+ * Saves the file when shutting down
+ */
+ public function __destruct()
+ {
+ $this->save($this->filename);
+ }
+
+ /**
+ * Saves the cookies to a file.
+ *
+ * @param string $filename File to save
+ * @throws \RuntimeException if the file cannot be found or created
+ */
+ public function save($filename)
+ {
+ $json = [];
+ foreach ($this as $cookie) {
+ /** @var SetCookie $cookie */
+ if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
+ $json[] = $cookie->toArray();
+ }
+ }
+
+ $jsonStr = \GuzzleHttp\json_encode($json);
+ if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) {
+ throw new \RuntimeException("Unable to save file {$filename}");
+ }
+ }
+
+ /**
+ * Load cookies from a JSON formatted file.
+ *
+ * Old cookies are kept unless overwritten by newly loaded ones.
+ *
+ * @param string $filename Cookie file to load.
+ * @throws \RuntimeException if the file cannot be loaded.
+ */
+ public function load($filename)
+ {
+ $json = file_get_contents($filename);
+ if (false === $json) {
+ throw new \RuntimeException("Unable to load file {$filename}");
+ } elseif ($json === '') {
+ return;
+ }
+
+ $data = \GuzzleHttp\json_decode($json, true);
+ if (is_array($data)) {
+ foreach (json_decode($json, true) as $cookie) {
+ $this->setCookie(new SetCookie($cookie));
+ }
+ } elseif (strlen($data)) {
+ throw new \RuntimeException("Invalid cookie file: {$filename}");
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php
new file mode 100755
index 0000000..0224a24
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php
@@ -0,0 +1,72 @@
+sessionKey = $sessionKey;
+ $this->storeSessionCookies = $storeSessionCookies;
+ $this->load();
+ }
+
+ /**
+ * Saves cookies to session when shutting down
+ */
+ public function __destruct()
+ {
+ $this->save();
+ }
+
+ /**
+ * Save cookies to the client session
+ */
+ public function save()
+ {
+ $json = [];
+ foreach ($this as $cookie) {
+ /** @var SetCookie $cookie */
+ if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
+ $json[] = $cookie->toArray();
+ }
+ }
+
+ $_SESSION[$this->sessionKey] = json_encode($json);
+ }
+
+ /**
+ * Load the contents of the client session into the data array
+ */
+ protected function load()
+ {
+ if (!isset($_SESSION[$this->sessionKey])) {
+ return;
+ }
+ $data = json_decode($_SESSION[$this->sessionKey], true);
+ if (is_array($data)) {
+ foreach ($data as $cookie) {
+ $this->setCookie(new SetCookie($cookie));
+ }
+ } elseif (strlen($data)) {
+ throw new \RuntimeException("Invalid cookie data");
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php
new file mode 100755
index 0000000..3d776a7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php
@@ -0,0 +1,403 @@
+ null,
+ 'Value' => null,
+ 'Domain' => null,
+ 'Path' => '/',
+ 'Max-Age' => null,
+ 'Expires' => null,
+ 'Secure' => false,
+ 'Discard' => false,
+ 'HttpOnly' => false
+ ];
+
+ /** @var array Cookie data */
+ private $data;
+
+ /**
+ * Create a new SetCookie object from a string
+ *
+ * @param string $cookie Set-Cookie header string
+ *
+ * @return self
+ */
+ public static function fromString($cookie)
+ {
+ // Create the default return array
+ $data = self::$defaults;
+ // Explode the cookie string using a series of semicolons
+ $pieces = array_filter(array_map('trim', explode(';', $cookie)));
+ // The name of the cookie (first kvp) must exist and include an equal sign.
+ if (empty($pieces[0]) || !strpos($pieces[0], '=')) {
+ return new self($data);
+ }
+
+ // Add the cookie pieces into the parsed data array
+ foreach ($pieces as $part) {
+ $cookieParts = explode('=', $part, 2);
+ $key = trim($cookieParts[0]);
+ $value = isset($cookieParts[1])
+ ? trim($cookieParts[1], " \n\r\t\0\x0B")
+ : true;
+
+ // Only check for non-cookies when cookies have been found
+ if (empty($data['Name'])) {
+ $data['Name'] = $key;
+ $data['Value'] = $value;
+ } else {
+ foreach (array_keys(self::$defaults) as $search) {
+ if (!strcasecmp($search, $key)) {
+ $data[$search] = $value;
+ continue 2;
+ }
+ }
+ $data[$key] = $value;
+ }
+ }
+
+ return new self($data);
+ }
+
+ /**
+ * @param array $data Array of cookie data provided by a Cookie parser
+ */
+ public function __construct(array $data = [])
+ {
+ $this->data = array_replace(self::$defaults, $data);
+ // Extract the Expires value and turn it into a UNIX timestamp if needed
+ if (!$this->getExpires() && $this->getMaxAge()) {
+ // Calculate the Expires date
+ $this->setExpires(time() + $this->getMaxAge());
+ } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
+ $this->setExpires($this->getExpires());
+ }
+ }
+
+ public function __toString()
+ {
+ $str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
+ foreach ($this->data as $k => $v) {
+ if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
+ if ($k === 'Expires') {
+ $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
+ } else {
+ $str .= ($v === true ? $k : "{$k}={$v}") . '; ';
+ }
+ }
+ }
+
+ return rtrim($str, '; ');
+ }
+
+ public function toArray()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Get the cookie name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->data['Name'];
+ }
+
+ /**
+ * Set the cookie name
+ *
+ * @param string $name Cookie name
+ */
+ public function setName($name)
+ {
+ $this->data['Name'] = $name;
+ }
+
+ /**
+ * Get the cookie value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->data['Value'];
+ }
+
+ /**
+ * Set the cookie value
+ *
+ * @param string $value Cookie value
+ */
+ public function setValue($value)
+ {
+ $this->data['Value'] = $value;
+ }
+
+ /**
+ * Get the domain
+ *
+ * @return string|null
+ */
+ public function getDomain()
+ {
+ return $this->data['Domain'];
+ }
+
+ /**
+ * Set the domain of the cookie
+ *
+ * @param string $domain
+ */
+ public function setDomain($domain)
+ {
+ $this->data['Domain'] = $domain;
+ }
+
+ /**
+ * Get the path
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->data['Path'];
+ }
+
+ /**
+ * Set the path of the cookie
+ *
+ * @param string $path Path of the cookie
+ */
+ public function setPath($path)
+ {
+ $this->data['Path'] = $path;
+ }
+
+ /**
+ * Maximum lifetime of the cookie in seconds
+ *
+ * @return int|null
+ */
+ public function getMaxAge()
+ {
+ return $this->data['Max-Age'];
+ }
+
+ /**
+ * Set the max-age of the cookie
+ *
+ * @param int $maxAge Max age of the cookie in seconds
+ */
+ public function setMaxAge($maxAge)
+ {
+ $this->data['Max-Age'] = $maxAge;
+ }
+
+ /**
+ * The UNIX timestamp when the cookie Expires
+ *
+ * @return mixed
+ */
+ public function getExpires()
+ {
+ return $this->data['Expires'];
+ }
+
+ /**
+ * Set the unix timestamp for which the cookie will expire
+ *
+ * @param int $timestamp Unix timestamp
+ */
+ public function setExpires($timestamp)
+ {
+ $this->data['Expires'] = is_numeric($timestamp)
+ ? (int) $timestamp
+ : strtotime($timestamp);
+ }
+
+ /**
+ * Get whether or not this is a secure cookie
+ *
+ * @return bool|null
+ */
+ public function getSecure()
+ {
+ return $this->data['Secure'];
+ }
+
+ /**
+ * Set whether or not the cookie is secure
+ *
+ * @param bool $secure Set to true or false if secure
+ */
+ public function setSecure($secure)
+ {
+ $this->data['Secure'] = $secure;
+ }
+
+ /**
+ * Get whether or not this is a session cookie
+ *
+ * @return bool|null
+ */
+ public function getDiscard()
+ {
+ return $this->data['Discard'];
+ }
+
+ /**
+ * Set whether or not this is a session cookie
+ *
+ * @param bool $discard Set to true or false if this is a session cookie
+ */
+ public function setDiscard($discard)
+ {
+ $this->data['Discard'] = $discard;
+ }
+
+ /**
+ * Get whether or not this is an HTTP only cookie
+ *
+ * @return bool
+ */
+ public function getHttpOnly()
+ {
+ return $this->data['HttpOnly'];
+ }
+
+ /**
+ * Set whether or not this is an HTTP only cookie
+ *
+ * @param bool $httpOnly Set to true or false if this is HTTP only
+ */
+ public function setHttpOnly($httpOnly)
+ {
+ $this->data['HttpOnly'] = $httpOnly;
+ }
+
+ /**
+ * Check if the cookie matches a path value.
+ *
+ * A request-path path-matches a given cookie-path if at least one of
+ * the following conditions holds:
+ *
+ * - The cookie-path and the request-path are identical.
+ * - The cookie-path is a prefix of the request-path, and the last
+ * character of the cookie-path is %x2F ("/").
+ * - The cookie-path is a prefix of the request-path, and the first
+ * character of the request-path that is not included in the cookie-
+ * path is a %x2F ("/") character.
+ *
+ * @param string $requestPath Path to check against
+ *
+ * @return bool
+ */
+ public function matchesPath($requestPath)
+ {
+ $cookiePath = $this->getPath();
+
+ // Match on exact matches or when path is the default empty "/"
+ if ($cookiePath === '/' || $cookiePath == $requestPath) {
+ return true;
+ }
+
+ // Ensure that the cookie-path is a prefix of the request path.
+ if (0 !== strpos($requestPath, $cookiePath)) {
+ return false;
+ }
+
+ // Match if the last character of the cookie-path is "/"
+ if (substr($cookiePath, -1, 1) === '/') {
+ return true;
+ }
+
+ // Match if the first character not included in cookie path is "/"
+ return substr($requestPath, strlen($cookiePath), 1) === '/';
+ }
+
+ /**
+ * Check if the cookie matches a domain value
+ *
+ * @param string $domain Domain to check against
+ *
+ * @return bool
+ */
+ public function matchesDomain($domain)
+ {
+ // Remove the leading '.' as per spec in RFC 6265.
+ // http://tools.ietf.org/html/rfc6265#section-5.2.3
+ $cookieDomain = ltrim($this->getDomain(), '.');
+
+ // Domain not set or exact match.
+ if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
+ return true;
+ }
+
+ // Matching the subdomain according to RFC 6265.
+ // http://tools.ietf.org/html/rfc6265#section-5.1.3
+ if (filter_var($domain, FILTER_VALIDATE_IP)) {
+ return false;
+ }
+
+ return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/', $domain);
+ }
+
+ /**
+ * Check if the cookie is expired
+ *
+ * @return bool
+ */
+ public function isExpired()
+ {
+ return $this->getExpires() !== null && time() > $this->getExpires();
+ }
+
+ /**
+ * Check if the cookie is valid according to RFC 6265
+ *
+ * @return bool|string Returns true if valid or an error message if invalid
+ */
+ public function validate()
+ {
+ // Names must not be empty, but can be 0
+ $name = $this->getName();
+ if (empty($name) && !is_numeric($name)) {
+ return 'The cookie name must not be empty';
+ }
+
+ // Check if any of the invalid characters are present in the cookie name
+ if (preg_match(
+ '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
+ $name
+ )) {
+ return 'Cookie name must not contain invalid characters: ASCII '
+ . 'Control characters (0-31;127), space, tab and the '
+ . 'following characters: ()<>@,;:\"/?={}';
+ }
+
+ // Value must not be empty, but can be 0
+ $value = $this->getValue();
+ if (empty($value) && !is_numeric($value)) {
+ return 'The cookie value must not be empty';
+ }
+
+ // Domains must not be empty, but can be 0
+ // A "0" is not a valid internet domain, but may be used as server name
+ // in a private network.
+ $domain = $this->getDomain();
+ if (empty($domain) && !is_numeric($domain)) {
+ return 'The cookie domain must not be empty';
+ }
+
+ return true;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php
new file mode 100755
index 0000000..427d896
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php
@@ -0,0 +1,27 @@
+getStatusCode()
+ : 0;
+ parent::__construct($message, $code, $previous);
+ $this->request = $request;
+ $this->response = $response;
+ $this->handlerContext = $handlerContext;
+ }
+
+ /**
+ * Wrap non-RequestExceptions with a RequestException
+ *
+ * @param RequestInterface $request
+ * @param \Exception $e
+ *
+ * @return RequestException
+ */
+ public static function wrapException(RequestInterface $request, \Exception $e)
+ {
+ return $e instanceof RequestException
+ ? $e
+ : new RequestException($e->getMessage(), $request, null, $e);
+ }
+
+ /**
+ * Factory method to create a new exception with a normalized error message
+ *
+ * @param RequestInterface $request Request
+ * @param ResponseInterface $response Response received
+ * @param \Exception $previous Previous exception
+ * @param array $ctx Optional handler context.
+ *
+ * @return self
+ */
+ public static function create(
+ RequestInterface $request,
+ ResponseInterface $response = null,
+ \Exception $previous = null,
+ array $ctx = []
+ ) {
+ if (!$response) {
+ return new self(
+ 'Error completing request',
+ $request,
+ null,
+ $previous,
+ $ctx
+ );
+ }
+
+ $level = (int) floor($response->getStatusCode() / 100);
+ if ($level === 4) {
+ $label = 'Client error';
+ $className = ClientException::class;
+ } elseif ($level === 5) {
+ $label = 'Server error';
+ $className = ServerException::class;
+ } else {
+ $label = 'Unsuccessful request';
+ $className = __CLASS__;
+ }
+
+ $uri = $request->getUri();
+ $uri = static::obfuscateUri($uri);
+
+ // Client Error: `GET /` resulted in a `404 Not Found` response:
+ // ... (truncated)
+ $message = sprintf(
+ '%s: `%s %s` resulted in a `%s %s` response',
+ $label,
+ $request->getMethod(),
+ $uri,
+ $response->getStatusCode(),
+ $response->getReasonPhrase()
+ );
+
+ $summary = static::getResponseBodySummary($response);
+
+ if ($summary !== null) {
+ $message .= ":\n{$summary}\n";
+ }
+
+ return new $className($message, $request, $response, $previous, $ctx);
+ }
+
+ /**
+ * Get a short summary of the response
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param ResponseInterface $response
+ *
+ * @return string|null
+ */
+ public static function getResponseBodySummary(ResponseInterface $response)
+ {
+ $body = $response->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $summary = $body->read(120);
+ $body->rewind();
+
+ if ($size > 120) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
+ return null;
+ }
+
+ return $summary;
+ }
+
+ /**
+ * Obfuscates URI if there is an username and a password present
+ *
+ * @param UriInterface $uri
+ *
+ * @return UriInterface
+ */
+ private static function obfuscateUri($uri)
+ {
+ $userInfo = $uri->getUserInfo();
+
+ if (false !== ($pos = strpos($userInfo, ':'))) {
+ return $uri->withUserInfo(substr($userInfo, 0, $pos), '***');
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Get the request that caused the exception
+ *
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Get the associated response
+ *
+ * @return ResponseInterface|null
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Check if a response was received
+ *
+ * @return bool
+ */
+ public function hasResponse()
+ {
+ return $this->response !== null;
+ }
+
+ /**
+ * Get contextual information about the error from the underlying handler.
+ *
+ * The contents of this array will vary depending on which handler you are
+ * using. It may also be just an empty array. Relying on this data will
+ * couple you to a specific handler, but can give more debug information
+ * when needed.
+ *
+ * @return array
+ */
+ public function getHandlerContext()
+ {
+ return $this->handlerContext;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php
new file mode 100755
index 0000000..a77c289
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php
@@ -0,0 +1,27 @@
+stream = $stream;
+ $msg = $msg ?: 'Could not seek the stream to position ' . $pos;
+ parent::__construct($msg);
+ }
+
+ /**
+ * @return StreamInterface
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php
new file mode 100755
index 0000000..127094c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php
@@ -0,0 +1,9 @@
+maxHandles = $maxHandles;
+ }
+
+ public function create(RequestInterface $request, array $options)
+ {
+ if (isset($options['curl']['body_as_string'])) {
+ $options['_body_as_string'] = $options['curl']['body_as_string'];
+ unset($options['curl']['body_as_string']);
+ }
+
+ $easy = new EasyHandle;
+ $easy->request = $request;
+ $easy->options = $options;
+ $conf = $this->getDefaultConf($easy);
+ $this->applyMethod($easy, $conf);
+ $this->applyHandlerOptions($easy, $conf);
+ $this->applyHeaders($easy, $conf);
+ unset($conf['_headers']);
+
+ // Add handler options from the request configuration options
+ if (isset($options['curl'])) {
+ $conf = array_replace($conf, $options['curl']);
+ }
+
+ $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
+ $easy->handle = $this->handles
+ ? array_pop($this->handles)
+ : curl_init();
+ curl_setopt_array($easy->handle, $conf);
+
+ return $easy;
+ }
+
+ public function release(EasyHandle $easy)
+ {
+ $resource = $easy->handle;
+ unset($easy->handle);
+
+ if (count($this->handles) >= $this->maxHandles) {
+ curl_close($resource);
+ } else {
+ // Remove all callback functions as they can hold onto references
+ // and are not cleaned up by curl_reset. Using curl_setopt_array
+ // does not work for some reason, so removing each one
+ // individually.
+ curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
+ curl_setopt($resource, CURLOPT_READFUNCTION, null);
+ curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
+ curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
+ curl_reset($resource);
+ $this->handles[] = $resource;
+ }
+ }
+
+ /**
+ * Completes a cURL transaction, either returning a response promise or a
+ * rejected promise.
+ *
+ * @param callable $handler
+ * @param EasyHandle $easy
+ * @param CurlFactoryInterface $factory Dictates how the handle is released
+ *
+ * @return \GuzzleHttp\Promise\PromiseInterface
+ */
+ public static function finish(
+ callable $handler,
+ EasyHandle $easy,
+ CurlFactoryInterface $factory
+ ) {
+ if (isset($easy->options['on_stats'])) {
+ self::invokeStats($easy);
+ }
+
+ if (!$easy->response || $easy->errno) {
+ return self::finishError($handler, $easy, $factory);
+ }
+
+ // Return the response if it is present and there is no error.
+ $factory->release($easy);
+
+ // Rewind the body of the response if possible.
+ $body = $easy->response->getBody();
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+
+ return new FulfilledPromise($easy->response);
+ }
+
+ private static function invokeStats(EasyHandle $easy)
+ {
+ $curlStats = curl_getinfo($easy->handle);
+ $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME);
+ $stats = new TransferStats(
+ $easy->request,
+ $easy->response,
+ $curlStats['total_time'],
+ $easy->errno,
+ $curlStats
+ );
+ call_user_func($easy->options['on_stats'], $stats);
+ }
+
+ private static function finishError(
+ callable $handler,
+ EasyHandle $easy,
+ CurlFactoryInterface $factory
+ ) {
+ // Get error information and release the handle to the factory.
+ $ctx = [
+ 'errno' => $easy->errno,
+ 'error' => curl_error($easy->handle),
+ 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME),
+ ] + curl_getinfo($easy->handle);
+ $ctx[self::CURL_VERSION_STR] = curl_version()['version'];
+ $factory->release($easy);
+
+ // Retry when nothing is present or when curl failed to rewind.
+ if (empty($easy->options['_err_message'])
+ && (!$easy->errno || $easy->errno == 65)
+ ) {
+ return self::retryFailedRewind($handler, $easy, $ctx);
+ }
+
+ return self::createRejection($easy, $ctx);
+ }
+
+ private static function createRejection(EasyHandle $easy, array $ctx)
+ {
+ static $connectionErrors = [
+ CURLE_OPERATION_TIMEOUTED => true,
+ CURLE_COULDNT_RESOLVE_HOST => true,
+ CURLE_COULDNT_CONNECT => true,
+ CURLE_SSL_CONNECT_ERROR => true,
+ CURLE_GOT_NOTHING => true,
+ ];
+
+ // If an exception was encountered during the onHeaders event, then
+ // return a rejected promise that wraps that exception.
+ if ($easy->onHeadersException) {
+ return \GuzzleHttp\Promise\rejection_for(
+ new RequestException(
+ 'An error was encountered during the on_headers event',
+ $easy->request,
+ $easy->response,
+ $easy->onHeadersException,
+ $ctx
+ )
+ );
+ }
+ if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) {
+ $message = sprintf(
+ 'cURL error %s: %s (%s)',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
+ );
+ } else {
+ $message = sprintf(
+ 'cURL error %s: %s (%s) for %s',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html',
+ $easy->request->getUri()
+ );
+ }
+
+ // Create a connection exception if it was a specific error code.
+ $error = isset($connectionErrors[$easy->errno])
+ ? new ConnectException($message, $easy->request, null, $ctx)
+ : new RequestException($message, $easy->request, $easy->response, null, $ctx);
+
+ return \GuzzleHttp\Promise\rejection_for($error);
+ }
+
+ private function getDefaultConf(EasyHandle $easy)
+ {
+ $conf = [
+ '_headers' => $easy->request->getHeaders(),
+ CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
+ CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
+ CURLOPT_RETURNTRANSFER => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ ];
+
+ if (defined('CURLOPT_PROTOCOLS')) {
+ $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
+ }
+
+ $version = $easy->request->getProtocolVersion();
+ if ($version == 1.1) {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
+ } elseif ($version == 2.0) {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
+ } else {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
+ }
+
+ return $conf;
+ }
+
+ private function applyMethod(EasyHandle $easy, array &$conf)
+ {
+ $body = $easy->request->getBody();
+ $size = $body->getSize();
+
+ if ($size === null || $size > 0) {
+ $this->applyBody($easy->request, $easy->options, $conf);
+ return;
+ }
+
+ $method = $easy->request->getMethod();
+ if ($method === 'PUT' || $method === 'POST') {
+ // See http://tools.ietf.org/html/rfc7230#section-3.3.2
+ if (!$easy->request->hasHeader('Content-Length')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
+ }
+ } elseif ($method === 'HEAD') {
+ $conf[CURLOPT_NOBODY] = true;
+ unset(
+ $conf[CURLOPT_WRITEFUNCTION],
+ $conf[CURLOPT_READFUNCTION],
+ $conf[CURLOPT_FILE],
+ $conf[CURLOPT_INFILE]
+ );
+ }
+ }
+
+ private function applyBody(RequestInterface $request, array $options, array &$conf)
+ {
+ $size = $request->hasHeader('Content-Length')
+ ? (int) $request->getHeaderLine('Content-Length')
+ : null;
+
+ // Send the body as a string if the size is less than 1MB OR if the
+ // [curl][body_as_string] request value is set.
+ if (($size !== null && $size < 1000000) ||
+ !empty($options['_body_as_string'])
+ ) {
+ $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
+ // Don't duplicate the Content-Length header
+ $this->removeHeader('Content-Length', $conf);
+ $this->removeHeader('Transfer-Encoding', $conf);
+ } else {
+ $conf[CURLOPT_UPLOAD] = true;
+ if ($size !== null) {
+ $conf[CURLOPT_INFILESIZE] = $size;
+ $this->removeHeader('Content-Length', $conf);
+ }
+ $body = $request->getBody();
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+ $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
+ return $body->read($length);
+ };
+ }
+
+ // If the Expect header is not present, prevent curl from adding it
+ if (!$request->hasHeader('Expect')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
+ }
+
+ // cURL sometimes adds a content-type by default. Prevent this.
+ if (!$request->hasHeader('Content-Type')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
+ }
+ }
+
+ private function applyHeaders(EasyHandle $easy, array &$conf)
+ {
+ foreach ($conf['_headers'] as $name => $values) {
+ foreach ($values as $value) {
+ $value = (string) $value;
+ if ($value === '') {
+ // cURL requires a special format for empty headers.
+ // See https://github.com/guzzle/guzzle/issues/1882 for more details.
+ $conf[CURLOPT_HTTPHEADER][] = "$name;";
+ } else {
+ $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
+ }
+ }
+ }
+
+ // Remove the Accept header if one was not set
+ if (!$easy->request->hasHeader('Accept')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
+ }
+ }
+
+ /**
+ * Remove a header from the options array.
+ *
+ * @param string $name Case-insensitive header to remove
+ * @param array $options Array of options to modify
+ */
+ private function removeHeader($name, array &$options)
+ {
+ foreach (array_keys($options['_headers']) as $key) {
+ if (!strcasecmp($key, $name)) {
+ unset($options['_headers'][$key]);
+ return;
+ }
+ }
+ }
+
+ private function applyHandlerOptions(EasyHandle $easy, array &$conf)
+ {
+ $options = $easy->options;
+ if (isset($options['verify'])) {
+ if ($options['verify'] === false) {
+ unset($conf[CURLOPT_CAINFO]);
+ $conf[CURLOPT_SSL_VERIFYHOST] = 0;
+ $conf[CURLOPT_SSL_VERIFYPEER] = false;
+ } else {
+ $conf[CURLOPT_SSL_VERIFYHOST] = 2;
+ $conf[CURLOPT_SSL_VERIFYPEER] = true;
+ if (is_string($options['verify'])) {
+ // Throw an error if the file/folder/link path is not valid or doesn't exist.
+ if (!file_exists($options['verify'])) {
+ throw new \InvalidArgumentException(
+ "SSL CA bundle not found: {$options['verify']}"
+ );
+ }
+ // If it's a directory or a link to a directory use CURLOPT_CAPATH.
+ // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
+ if (is_dir($options['verify']) ||
+ (is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
+ $conf[CURLOPT_CAPATH] = $options['verify'];
+ } else {
+ $conf[CURLOPT_CAINFO] = $options['verify'];
+ }
+ }
+ }
+ }
+
+ if (!empty($options['decode_content'])) {
+ $accept = $easy->request->getHeaderLine('Accept-Encoding');
+ if ($accept) {
+ $conf[CURLOPT_ENCODING] = $accept;
+ } else {
+ $conf[CURLOPT_ENCODING] = '';
+ // Don't let curl send the header over the wire
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
+ }
+ }
+
+ if (isset($options['sink'])) {
+ $sink = $options['sink'];
+ if (!is_string($sink)) {
+ $sink = \GuzzleHttp\Psr7\stream_for($sink);
+ } elseif (!is_dir(dirname($sink))) {
+ // Ensure that the directory exists before failing in curl.
+ throw new \RuntimeException(sprintf(
+ 'Directory %s does not exist for sink value of %s',
+ dirname($sink),
+ $sink
+ ));
+ } else {
+ $sink = new LazyOpenStream($sink, 'w+');
+ }
+ $easy->sink = $sink;
+ $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
+ return $sink->write($write);
+ };
+ } else {
+ // Use a default temp stream if no sink was set.
+ $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
+ $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
+ }
+ $timeoutRequiresNoSignal = false;
+ if (isset($options['timeout'])) {
+ $timeoutRequiresNoSignal |= $options['timeout'] < 1;
+ $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
+ }
+
+ // CURL default value is CURL_IPRESOLVE_WHATEVER
+ if (isset($options['force_ip_resolve'])) {
+ if ('v4' === $options['force_ip_resolve']) {
+ $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
+ } elseif ('v6' === $options['force_ip_resolve']) {
+ $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
+ }
+ }
+
+ if (isset($options['connect_timeout'])) {
+ $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
+ $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
+ }
+
+ if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
+ $conf[CURLOPT_NOSIGNAL] = true;
+ }
+
+ if (isset($options['proxy'])) {
+ if (!is_array($options['proxy'])) {
+ $conf[CURLOPT_PROXY] = $options['proxy'];
+ } else {
+ $scheme = $easy->request->getUri()->getScheme();
+ if (isset($options['proxy'][$scheme])) {
+ $host = $easy->request->getUri()->getHost();
+ if (!isset($options['proxy']['no']) ||
+ !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
+ ) {
+ $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
+ }
+ }
+ }
+ }
+
+ if (isset($options['cert'])) {
+ $cert = $options['cert'];
+ if (is_array($cert)) {
+ $conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
+ $cert = $cert[0];
+ }
+ if (!file_exists($cert)) {
+ throw new \InvalidArgumentException(
+ "SSL certificate not found: {$cert}"
+ );
+ }
+ $conf[CURLOPT_SSLCERT] = $cert;
+ }
+
+ if (isset($options['ssl_key'])) {
+ $sslKey = $options['ssl_key'];
+ if (is_array($sslKey)) {
+ $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1];
+ $sslKey = $sslKey[0];
+ }
+ if (!file_exists($sslKey)) {
+ throw new \InvalidArgumentException(
+ "SSL private key not found: {$sslKey}"
+ );
+ }
+ $conf[CURLOPT_SSLKEY] = $sslKey;
+ }
+
+ if (isset($options['progress'])) {
+ $progress = $options['progress'];
+ if (!is_callable($progress)) {
+ throw new \InvalidArgumentException(
+ 'progress client option must be callable'
+ );
+ }
+ $conf[CURLOPT_NOPROGRESS] = false;
+ $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
+ $args = func_get_args();
+ // PHP 5.5 pushed the handle onto the start of the args
+ if (is_resource($args[0])) {
+ array_shift($args);
+ }
+ call_user_func_array($progress, $args);
+ };
+ }
+
+ if (!empty($options['debug'])) {
+ $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
+ $conf[CURLOPT_VERBOSE] = true;
+ }
+ }
+
+ /**
+ * This function ensures that a response was set on a transaction. If one
+ * was not set, then the request is retried if possible. This error
+ * typically means you are sending a payload, curl encountered a
+ * "Connection died, retrying a fresh connect" error, tried to rewind the
+ * stream, and then encountered a "necessary data rewind wasn't possible"
+ * error, causing the request to be sent through curl_multi_info_read()
+ * without an error status.
+ */
+ private static function retryFailedRewind(
+ callable $handler,
+ EasyHandle $easy,
+ array $ctx
+ ) {
+ try {
+ // Only rewind if the body has been read from.
+ $body = $easy->request->getBody();
+ if ($body->tell() > 0) {
+ $body->rewind();
+ }
+ } catch (\RuntimeException $e) {
+ $ctx['error'] = 'The connection unexpectedly failed without '
+ . 'providing an error. The request would have been retried, '
+ . 'but attempting to rewind the request body failed. '
+ . 'Exception: ' . $e;
+ return self::createRejection($easy, $ctx);
+ }
+
+ // Retry no more than 3 times before giving up.
+ if (!isset($easy->options['_curl_retries'])) {
+ $easy->options['_curl_retries'] = 1;
+ } elseif ($easy->options['_curl_retries'] == 2) {
+ $ctx['error'] = 'The cURL request was retried 3 times '
+ . 'and did not succeed. The most likely reason for the failure '
+ . 'is that cURL was unable to rewind the body of the request '
+ . 'and subsequent retries resulted in the same error. Turn on '
+ . 'the debug option to see what went wrong. See '
+ . 'https://bugs.php.net/bug.php?id=47204 for more information.';
+ return self::createRejection($easy, $ctx);
+ } else {
+ $easy->options['_curl_retries']++;
+ }
+
+ return $handler($easy->request, $easy->options);
+ }
+
+ private function createHeaderFn(EasyHandle $easy)
+ {
+ if (isset($easy->options['on_headers'])) {
+ $onHeaders = $easy->options['on_headers'];
+
+ if (!is_callable($onHeaders)) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+ } else {
+ $onHeaders = null;
+ }
+
+ return function ($ch, $h) use (
+ $onHeaders,
+ $easy,
+ &$startingResponse
+ ) {
+ $value = trim($h);
+ if ($value === '') {
+ $startingResponse = true;
+ $easy->createResponse();
+ if ($onHeaders !== null) {
+ try {
+ $onHeaders($easy->response);
+ } catch (\Exception $e) {
+ // Associate the exception with the handle and trigger
+ // a curl header write error by returning 0.
+ $easy->onHeadersException = $e;
+ return -1;
+ }
+ }
+ } elseif ($startingResponse) {
+ $startingResponse = false;
+ $easy->headers = [$value];
+ } else {
+ $easy->headers[] = $value;
+ }
+ return strlen($h);
+ };
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
new file mode 100755
index 0000000..b0fc236
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
@@ -0,0 +1,27 @@
+factory = isset($options['handle_factory'])
+ ? $options['handle_factory']
+ : new CurlFactory(3);
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (isset($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ $easy = $this->factory->create($request, $options);
+ curl_exec($easy->handle);
+ $easy->errno = curl_errno($easy->handle);
+
+ return CurlFactory::finish($this, $easy, $this->factory);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
new file mode 100755
index 0000000..d829762
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
@@ -0,0 +1,205 @@
+factory = isset($options['handle_factory'])
+ ? $options['handle_factory'] : new CurlFactory(50);
+
+ if (isset($options['select_timeout'])) {
+ $this->selectTimeout = $options['select_timeout'];
+ } elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
+ $this->selectTimeout = $selectTimeout;
+ } else {
+ $this->selectTimeout = 1;
+ }
+ }
+
+ public function __get($name)
+ {
+ if ($name === '_mh') {
+ return $this->_mh = curl_multi_init();
+ }
+
+ throw new \BadMethodCallException();
+ }
+
+ public function __destruct()
+ {
+ if (isset($this->_mh)) {
+ curl_multi_close($this->_mh);
+ unset($this->_mh);
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $easy = $this->factory->create($request, $options);
+ $id = (int) $easy->handle;
+
+ $promise = new Promise(
+ [$this, 'execute'],
+ function () use ($id) {
+ return $this->cancel($id);
+ }
+ );
+
+ $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
+
+ return $promise;
+ }
+
+ /**
+ * Ticks the curl event loop.
+ */
+ public function tick()
+ {
+ // Add any delayed handles if needed.
+ if ($this->delays) {
+ $currentTime = \GuzzleHttp\_current_time();
+ foreach ($this->delays as $id => $delay) {
+ if ($currentTime >= $delay) {
+ unset($this->delays[$id]);
+ curl_multi_add_handle(
+ $this->_mh,
+ $this->handles[$id]['easy']->handle
+ );
+ }
+ }
+ }
+
+ // Step through the task queue which may add additional requests.
+ P\queue()->run();
+
+ if ($this->active &&
+ curl_multi_select($this->_mh, $this->selectTimeout) === -1
+ ) {
+ // Perform a usleep if a select returns -1.
+ // See: https://bugs.php.net/bug.php?id=61141
+ usleep(250);
+ }
+
+ while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);
+
+ $this->processMessages();
+ }
+
+ /**
+ * Runs until all outstanding connections have completed.
+ */
+ public function execute()
+ {
+ $queue = P\queue();
+
+ while ($this->handles || !$queue->isEmpty()) {
+ // If there are no transfers, then sleep for the next delay
+ if (!$this->active && $this->delays) {
+ usleep($this->timeToNext());
+ }
+ $this->tick();
+ }
+ }
+
+ private function addRequest(array $entry)
+ {
+ $easy = $entry['easy'];
+ $id = (int) $easy->handle;
+ $this->handles[$id] = $entry;
+ if (empty($easy->options['delay'])) {
+ curl_multi_add_handle($this->_mh, $easy->handle);
+ } else {
+ $this->delays[$id] = \GuzzleHttp\_current_time() + ($easy->options['delay'] / 1000);
+ }
+ }
+
+ /**
+ * Cancels a handle from sending and removes references to it.
+ *
+ * @param int $id Handle ID to cancel and remove.
+ *
+ * @return bool True on success, false on failure.
+ */
+ private function cancel($id)
+ {
+ // Cannot cancel if it has been processed.
+ if (!isset($this->handles[$id])) {
+ return false;
+ }
+
+ $handle = $this->handles[$id]['easy']->handle;
+ unset($this->delays[$id], $this->handles[$id]);
+ curl_multi_remove_handle($this->_mh, $handle);
+ curl_close($handle);
+
+ return true;
+ }
+
+ private function processMessages()
+ {
+ while ($done = curl_multi_info_read($this->_mh)) {
+ $id = (int) $done['handle'];
+ curl_multi_remove_handle($this->_mh, $done['handle']);
+
+ if (!isset($this->handles[$id])) {
+ // Probably was cancelled.
+ continue;
+ }
+
+ $entry = $this->handles[$id];
+ unset($this->handles[$id], $this->delays[$id]);
+ $entry['easy']->errno = $done['result'];
+ $entry['deferred']->resolve(
+ CurlFactory::finish(
+ $this,
+ $entry['easy'],
+ $this->factory
+ )
+ );
+ }
+ }
+
+ private function timeToNext()
+ {
+ $currentTime = \GuzzleHttp\_current_time();
+ $nextTime = PHP_INT_MAX;
+ foreach ($this->delays as $time) {
+ if ($time < $nextTime) {
+ $nextTime = $time;
+ }
+ }
+
+ return max(0, $nextTime - $currentTime) * 1000000;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
new file mode 100755
index 0000000..7754e91
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
@@ -0,0 +1,92 @@
+headers)) {
+ throw new \RuntimeException('No headers have been received');
+ }
+
+ // HTTP-version SP status-code SP reason-phrase
+ $startLine = explode(' ', array_shift($this->headers), 3);
+ $headers = \GuzzleHttp\headers_from_lines($this->headers);
+ $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
+
+ if (!empty($this->options['decode_content'])
+ && isset($normalizedKeys['content-encoding'])
+ ) {
+ $headers['x-encoded-content-encoding']
+ = $headers[$normalizedKeys['content-encoding']];
+ unset($headers[$normalizedKeys['content-encoding']]);
+ if (isset($normalizedKeys['content-length'])) {
+ $headers['x-encoded-content-length']
+ = $headers[$normalizedKeys['content-length']];
+
+ $bodyLength = (int) $this->sink->getSize();
+ if ($bodyLength) {
+ $headers[$normalizedKeys['content-length']] = $bodyLength;
+ } else {
+ unset($headers[$normalizedKeys['content-length']]);
+ }
+ }
+ }
+
+ // Attach a response to the easy handle with the parsed headers.
+ $this->response = new Response(
+ $startLine[1],
+ $headers,
+ $this->sink,
+ substr($startLine[0], 5),
+ isset($startLine[2]) ? (string) $startLine[2] : null
+ );
+ }
+
+ public function __get($name)
+ {
+ $msg = $name === 'handle'
+ ? 'The EasyHandle has been released'
+ : 'Invalid property: ' . $name;
+ throw new \BadMethodCallException($msg);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
new file mode 100755
index 0000000..d5c449c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
@@ -0,0 +1,190 @@
+onFulfilled = $onFulfilled;
+ $this->onRejected = $onRejected;
+
+ if ($queue) {
+ call_user_func_array([$this, 'append'], $queue);
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (!$this->queue) {
+ throw new \OutOfBoundsException('Mock queue is empty');
+ }
+
+ if (isset($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ $this->lastRequest = $request;
+ $this->lastOptions = $options;
+ $response = array_shift($this->queue);
+
+ if (isset($options['on_headers'])) {
+ if (!is_callable($options['on_headers'])) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+ try {
+ $options['on_headers']($response);
+ } catch (\Exception $e) {
+ $msg = 'An error was encountered during the on_headers event';
+ $response = new RequestException($msg, $request, $response, $e);
+ }
+ }
+
+ if (is_callable($response)) {
+ $response = call_user_func($response, $request, $options);
+ }
+
+ $response = $response instanceof \Exception
+ ? \GuzzleHttp\Promise\rejection_for($response)
+ : \GuzzleHttp\Promise\promise_for($response);
+
+ return $response->then(
+ function ($value) use ($request, $options) {
+ $this->invokeStats($request, $options, $value);
+ if ($this->onFulfilled) {
+ call_user_func($this->onFulfilled, $value);
+ }
+ if (isset($options['sink'])) {
+ $contents = (string) $value->getBody();
+ $sink = $options['sink'];
+
+ if (is_resource($sink)) {
+ fwrite($sink, $contents);
+ } elseif (is_string($sink)) {
+ file_put_contents($sink, $contents);
+ } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) {
+ $sink->write($contents);
+ }
+ }
+
+ return $value;
+ },
+ function ($reason) use ($request, $options) {
+ $this->invokeStats($request, $options, null, $reason);
+ if ($this->onRejected) {
+ call_user_func($this->onRejected, $reason);
+ }
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ }
+
+ /**
+ * Adds one or more variadic requests, exceptions, callables, or promises
+ * to the queue.
+ */
+ public function append()
+ {
+ foreach (func_get_args() as $value) {
+ if ($value instanceof ResponseInterface
+ || $value instanceof \Exception
+ || $value instanceof PromiseInterface
+ || is_callable($value)
+ ) {
+ $this->queue[] = $value;
+ } else {
+ throw new \InvalidArgumentException('Expected a response or '
+ . 'exception. Found ' . \GuzzleHttp\describe_type($value));
+ }
+ }
+ }
+
+ /**
+ * Get the last received request.
+ *
+ * @return RequestInterface
+ */
+ public function getLastRequest()
+ {
+ return $this->lastRequest;
+ }
+
+ /**
+ * Get the last received request options.
+ *
+ * @return array
+ */
+ public function getLastOptions()
+ {
+ return $this->lastOptions;
+ }
+
+ /**
+ * Returns the number of remaining items in the queue.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->queue);
+ }
+
+ private function invokeStats(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response = null,
+ $reason = null
+ ) {
+ if (isset($options['on_stats'])) {
+ $transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0;
+ $stats = new TransferStats($request, $response, $transferTime, $reason);
+ call_user_func($options['on_stats'], $stats);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
new file mode 100755
index 0000000..f8b00be
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
@@ -0,0 +1,55 @@
+withoutHeader('Expect');
+
+ // Append a content-length header if body size is zero to match
+ // cURL's behavior.
+ if (0 === $request->getBody()->getSize()) {
+ $request = $request->withHeader('Content-Length', '0');
+ }
+
+ return $this->createResponse(
+ $request,
+ $options,
+ $this->createStream($request, $options),
+ $startTime
+ );
+ } catch (\InvalidArgumentException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Determine if the error was a networking error.
+ $message = $e->getMessage();
+ // This list can probably get more comprehensive.
+ if (strpos($message, 'getaddrinfo') // DNS lookup failed
+ || strpos($message, 'Connection refused')
+ || strpos($message, "couldn't connect to host") // error on HHVM
+ || strpos($message, "connection attempt failed")
+ ) {
+ $e = new ConnectException($e->getMessage(), $request, $e);
+ }
+ $e = RequestException::wrapException($request, $e);
+ $this->invokeStats($options, $request, $startTime, null, $e);
+
+ return \GuzzleHttp\Promise\rejection_for($e);
+ }
+ }
+
+ private function invokeStats(
+ array $options,
+ RequestInterface $request,
+ $startTime,
+ ResponseInterface $response = null,
+ $error = null
+ ) {
+ if (isset($options['on_stats'])) {
+ $stats = new TransferStats(
+ $request,
+ $response,
+ \GuzzleHttp\_current_time() - $startTime,
+ $error,
+ []
+ );
+ call_user_func($options['on_stats'], $stats);
+ }
+ }
+
+ private function createResponse(
+ RequestInterface $request,
+ array $options,
+ $stream,
+ $startTime
+ ) {
+ $hdrs = $this->lastHeaders;
+ $this->lastHeaders = [];
+ $parts = explode(' ', array_shift($hdrs), 3);
+ $ver = explode('/', $parts[0])[1];
+ $status = $parts[1];
+ $reason = isset($parts[2]) ? $parts[2] : null;
+ $headers = \GuzzleHttp\headers_from_lines($hdrs);
+ list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
+ $stream = Psr7\stream_for($stream);
+ $sink = $stream;
+
+ if (strcasecmp('HEAD', $request->getMethod())) {
+ $sink = $this->createSink($stream, $options);
+ }
+
+ $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
+
+ if (isset($options['on_headers'])) {
+ try {
+ $options['on_headers']($response);
+ } catch (\Exception $e) {
+ $msg = 'An error was encountered during the on_headers event';
+ $ex = new RequestException($msg, $request, $response, $e);
+ return \GuzzleHttp\Promise\rejection_for($ex);
+ }
+ }
+
+ // Do not drain when the request is a HEAD request because they have
+ // no body.
+ if ($sink !== $stream) {
+ $this->drain(
+ $stream,
+ $sink,
+ $response->getHeaderLine('Content-Length')
+ );
+ }
+
+ $this->invokeStats($options, $request, $startTime, $response, null);
+
+ return new FulfilledPromise($response);
+ }
+
+ private function createSink(StreamInterface $stream, array $options)
+ {
+ if (!empty($options['stream'])) {
+ return $stream;
+ }
+
+ $sink = isset($options['sink'])
+ ? $options['sink']
+ : fopen('php://temp', 'r+');
+
+ return is_string($sink)
+ ? new Psr7\LazyOpenStream($sink, 'w+')
+ : Psr7\stream_for($sink);
+ }
+
+ private function checkDecode(array $options, array $headers, $stream)
+ {
+ // Automatically decode responses when instructed.
+ if (!empty($options['decode_content'])) {
+ $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
+ if (isset($normalizedKeys['content-encoding'])) {
+ $encoding = $headers[$normalizedKeys['content-encoding']];
+ if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
+ $stream = new Psr7\InflateStream(
+ Psr7\stream_for($stream)
+ );
+ $headers['x-encoded-content-encoding']
+ = $headers[$normalizedKeys['content-encoding']];
+ // Remove content-encoding header
+ unset($headers[$normalizedKeys['content-encoding']]);
+ // Fix content-length header
+ if (isset($normalizedKeys['content-length'])) {
+ $headers['x-encoded-content-length']
+ = $headers[$normalizedKeys['content-length']];
+
+ $length = (int) $stream->getSize();
+ if ($length === 0) {
+ unset($headers[$normalizedKeys['content-length']]);
+ } else {
+ $headers[$normalizedKeys['content-length']] = [$length];
+ }
+ }
+ }
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ /**
+ * Drains the source stream into the "sink" client option.
+ *
+ * @param StreamInterface $source
+ * @param StreamInterface $sink
+ * @param string $contentLength Header specifying the amount of
+ * data to read.
+ *
+ * @return StreamInterface
+ * @throws \RuntimeException when the sink option is invalid.
+ */
+ private function drain(
+ StreamInterface $source,
+ StreamInterface $sink,
+ $contentLength
+ ) {
+ // If a content-length header is provided, then stop reading once
+ // that number of bytes has been read. This can prevent infinitely
+ // reading from a stream when dealing with servers that do not honor
+ // Connection: Close headers.
+ Psr7\copy_to_stream(
+ $source,
+ $sink,
+ (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
+ );
+
+ $sink->seek(0);
+ $source->close();
+
+ return $sink;
+ }
+
+ /**
+ * Create a resource and check to ensure it was created successfully
+ *
+ * @param callable $callback Callable that returns stream resource
+ *
+ * @return resource
+ * @throws \RuntimeException on error
+ */
+ private function createResource(callable $callback)
+ {
+ $errors = null;
+ set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
+ $errors[] = [
+ 'message' => $msg,
+ 'file' => $file,
+ 'line' => $line
+ ];
+ return true;
+ });
+
+ $resource = $callback();
+ restore_error_handler();
+
+ if (!$resource) {
+ $message = 'Error creating resource: ';
+ foreach ($errors as $err) {
+ foreach ($err as $key => $value) {
+ $message .= "[$key] $value" . PHP_EOL;
+ }
+ }
+ throw new \RuntimeException(trim($message));
+ }
+
+ return $resource;
+ }
+
+ private function createStream(RequestInterface $request, array $options)
+ {
+ static $methods;
+ if (!$methods) {
+ $methods = array_flip(get_class_methods(__CLASS__));
+ }
+
+ // HTTP/1.1 streams using the PHP stream wrapper require a
+ // Connection: close header
+ if ($request->getProtocolVersion() == '1.1'
+ && !$request->hasHeader('Connection')
+ ) {
+ $request = $request->withHeader('Connection', 'close');
+ }
+
+ // Ensure SSL is verified by default
+ if (!isset($options['verify'])) {
+ $options['verify'] = true;
+ }
+
+ $params = [];
+ $context = $this->getDefaultContext($request);
+
+ if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+
+ if (!empty($options)) {
+ foreach ($options as $key => $value) {
+ $method = "add_{$key}";
+ if (isset($methods[$method])) {
+ $this->{$method}($request, $context, $value, $params);
+ }
+ }
+ }
+
+ if (isset($options['stream_context'])) {
+ if (!is_array($options['stream_context'])) {
+ throw new \InvalidArgumentException('stream_context must be an array');
+ }
+ $context = array_replace_recursive(
+ $context,
+ $options['stream_context']
+ );
+ }
+
+ // Microsoft NTLM authentication only supported with curl handler
+ if (isset($options['auth'])
+ && is_array($options['auth'])
+ && isset($options['auth'][2])
+ && 'ntlm' == $options['auth'][2]
+ ) {
+ throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
+ }
+
+ $uri = $this->resolveHost($request, $options);
+
+ $context = $this->createResource(
+ function () use ($context, $params) {
+ return stream_context_create($context, $params);
+ }
+ );
+
+ return $this->createResource(
+ function () use ($uri, &$http_response_header, $context, $options) {
+ $resource = fopen((string) $uri, 'r', null, $context);
+ $this->lastHeaders = $http_response_header;
+
+ if (isset($options['read_timeout'])) {
+ $readTimeout = $options['read_timeout'];
+ $sec = (int) $readTimeout;
+ $usec = ($readTimeout - $sec) * 100000;
+ stream_set_timeout($resource, $sec, $usec);
+ }
+
+ return $resource;
+ }
+ );
+ }
+
+ private function resolveHost(RequestInterface $request, array $options)
+ {
+ $uri = $request->getUri();
+
+ if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
+ if ('v4' === $options['force_ip_resolve']) {
+ $records = dns_get_record($uri->getHost(), DNS_A);
+ if (!isset($records[0]['ip'])) {
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv4 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
+ }
+ $uri = $uri->withHost($records[0]['ip']);
+ } elseif ('v6' === $options['force_ip_resolve']) {
+ $records = dns_get_record($uri->getHost(), DNS_AAAA);
+ if (!isset($records[0]['ipv6'])) {
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv6 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
+ }
+ $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
+ }
+ }
+
+ return $uri;
+ }
+
+ private function getDefaultContext(RequestInterface $request)
+ {
+ $headers = '';
+ foreach ($request->getHeaders() as $name => $value) {
+ foreach ($value as $val) {
+ $headers .= "$name: $val\r\n";
+ }
+ }
+
+ $context = [
+ 'http' => [
+ 'method' => $request->getMethod(),
+ 'header' => $headers,
+ 'protocol_version' => $request->getProtocolVersion(),
+ 'ignore_errors' => true,
+ 'follow_location' => 0,
+ ],
+ ];
+
+ $body = (string) $request->getBody();
+
+ if (!empty($body)) {
+ $context['http']['content'] = $body;
+ // Prevent the HTTP handler from adding a Content-Type header.
+ if (!$request->hasHeader('Content-Type')) {
+ $context['http']['header'] .= "Content-Type:\r\n";
+ }
+ }
+
+ $context['http']['header'] = rtrim($context['http']['header']);
+
+ return $context;
+ }
+
+ private function add_proxy(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (!is_array($value)) {
+ $options['http']['proxy'] = $value;
+ } else {
+ $scheme = $request->getUri()->getScheme();
+ if (isset($value[$scheme])) {
+ if (!isset($value['no'])
+ || !\GuzzleHttp\is_host_in_noproxy(
+ $request->getUri()->getHost(),
+ $value['no']
+ )
+ ) {
+ $options['http']['proxy'] = $value[$scheme];
+ }
+ }
+ }
+ }
+
+ private function add_timeout(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value > 0) {
+ $options['http']['timeout'] = $value;
+ }
+ }
+
+ private function add_verify(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === true) {
+ // PHP 5.6 or greater will find the system cert by default. When
+ // < 5.6, use the Guzzle bundled cacert.
+ if (PHP_VERSION_ID < 50600) {
+ $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
+ }
+ } elseif (is_string($value)) {
+ $options['ssl']['cafile'] = $value;
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL CA bundle not found: $value");
+ }
+ } elseif ($value === false) {
+ $options['ssl']['verify_peer'] = false;
+ $options['ssl']['verify_peer_name'] = false;
+ return;
+ } else {
+ throw new \InvalidArgumentException('Invalid verify request option');
+ }
+
+ $options['ssl']['verify_peer'] = true;
+ $options['ssl']['verify_peer_name'] = true;
+ $options['ssl']['allow_self_signed'] = false;
+ }
+
+ private function add_cert(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (is_array($value)) {
+ $options['ssl']['passphrase'] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL certificate not found: {$value}");
+ }
+
+ $options['ssl']['local_cert'] = $value;
+ }
+
+ private function add_progress(RequestInterface $request, &$options, $value, &$params)
+ {
+ $this->addNotification(
+ $params,
+ function ($code, $a, $b, $c, $transferred, $total) use ($value) {
+ if ($code == STREAM_NOTIFY_PROGRESS) {
+ $value($total, $transferred, null, null);
+ }
+ }
+ );
+ }
+
+ private function add_debug(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === false) {
+ return;
+ }
+
+ static $map = [
+ STREAM_NOTIFY_CONNECT => 'CONNECT',
+ STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
+ STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
+ STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
+ STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
+ STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
+ STREAM_NOTIFY_PROGRESS => 'PROGRESS',
+ STREAM_NOTIFY_FAILURE => 'FAILURE',
+ STREAM_NOTIFY_COMPLETED => 'COMPLETED',
+ STREAM_NOTIFY_RESOLVE => 'RESOLVE',
+ ];
+ static $args = ['severity', 'message', 'message_code',
+ 'bytes_transferred', 'bytes_max'];
+
+ $value = \GuzzleHttp\debug_resource($value);
+ $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
+ $this->addNotification(
+ $params,
+ function () use ($ident, $value, $map, $args) {
+ $passed = func_get_args();
+ $code = array_shift($passed);
+ fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
+ foreach (array_filter($passed) as $i => $v) {
+ fwrite($value, $args[$i] . ': "' . $v . '" ');
+ }
+ fwrite($value, "\n");
+ }
+ );
+ }
+
+ private function addNotification(array &$params, callable $notify)
+ {
+ // Wrap the existing function if needed.
+ if (!isset($params['notification'])) {
+ $params['notification'] = $notify;
+ } else {
+ $params['notification'] = $this->callArray([
+ $params['notification'],
+ $notify
+ ]);
+ }
+ }
+
+ private function callArray(array $functions)
+ {
+ return function () use ($functions) {
+ $args = func_get_args();
+ foreach ($functions as $fn) {
+ call_user_func_array($fn, $args);
+ }
+ };
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/HandlerStack.php
new file mode 100755
index 0000000..f001686
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/HandlerStack.php
@@ -0,0 +1,273 @@
+push(Middleware::httpErrors(), 'http_errors');
+ $stack->push(Middleware::redirect(), 'allow_redirects');
+ $stack->push(Middleware::cookies(), 'cookies');
+ $stack->push(Middleware::prepareBody(), 'prepare_body');
+
+ return $stack;
+ }
+
+ /**
+ * @param callable $handler Underlying HTTP handler.
+ */
+ public function __construct(callable $handler = null)
+ {
+ $this->handler = $handler;
+ }
+
+ /**
+ * Invokes the handler stack as a composed handler
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $handler = $this->resolve();
+
+ return $handler($request, $options);
+ }
+
+ /**
+ * Dumps a string representation of the stack.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $depth = 0;
+ $stack = [];
+ if ($this->handler) {
+ $stack[] = "0) Handler: " . $this->debugCallable($this->handler);
+ }
+
+ $result = '';
+ foreach (array_reverse($this->stack) as $tuple) {
+ $depth++;
+ $str = "{$depth}) Name: '{$tuple[1]}', ";
+ $str .= "Function: " . $this->debugCallable($tuple[0]);
+ $result = "> {$str}\n{$result}";
+ $stack[] = $str;
+ }
+
+ foreach (array_keys($stack) as $k) {
+ $result .= "< {$stack[$k]}\n";
+ }
+
+ return $result;
+ }
+
+ /**
+ * Set the HTTP handler that actually returns a promise.
+ *
+ * @param callable $handler Accepts a request and array of options and
+ * returns a Promise.
+ */
+ public function setHandler(callable $handler)
+ {
+ $this->handler = $handler;
+ $this->cached = null;
+ }
+
+ /**
+ * Returns true if the builder has a handler.
+ *
+ * @return bool
+ */
+ public function hasHandler()
+ {
+ return (bool) $this->handler;
+ }
+
+ /**
+ * Unshift a middleware to the bottom of the stack.
+ *
+ * @param callable $middleware Middleware function
+ * @param string $name Name to register for this middleware.
+ */
+ public function unshift(callable $middleware, $name = null)
+ {
+ array_unshift($this->stack, [$middleware, $name]);
+ $this->cached = null;
+ }
+
+ /**
+ * Push a middleware to the top of the stack.
+ *
+ * @param callable $middleware Middleware function
+ * @param string $name Name to register for this middleware.
+ */
+ public function push(callable $middleware, $name = '')
+ {
+ $this->stack[] = [$middleware, $name];
+ $this->cached = null;
+ }
+
+ /**
+ * Add a middleware before another middleware by name.
+ *
+ * @param string $findName Middleware to find
+ * @param callable $middleware Middleware function
+ * @param string $withName Name to register for this middleware.
+ */
+ public function before($findName, callable $middleware, $withName = '')
+ {
+ $this->splice($findName, $withName, $middleware, true);
+ }
+
+ /**
+ * Add a middleware after another middleware by name.
+ *
+ * @param string $findName Middleware to find
+ * @param callable $middleware Middleware function
+ * @param string $withName Name to register for this middleware.
+ */
+ public function after($findName, callable $middleware, $withName = '')
+ {
+ $this->splice($findName, $withName, $middleware, false);
+ }
+
+ /**
+ * Remove a middleware by instance or name from the stack.
+ *
+ * @param callable|string $remove Middleware to remove by instance or name.
+ */
+ public function remove($remove)
+ {
+ $this->cached = null;
+ $idx = is_callable($remove) ? 0 : 1;
+ $this->stack = array_values(array_filter(
+ $this->stack,
+ function ($tuple) use ($idx, $remove) {
+ return $tuple[$idx] !== $remove;
+ }
+ ));
+ }
+
+ /**
+ * Compose the middleware and handler into a single callable function.
+ *
+ * @return callable
+ */
+ public function resolve()
+ {
+ if (!$this->cached) {
+ if (!($prev = $this->handler)) {
+ throw new \LogicException('No handler has been specified');
+ }
+
+ foreach (array_reverse($this->stack) as $fn) {
+ $prev = $fn[0]($prev);
+ }
+
+ $this->cached = $prev;
+ }
+
+ return $this->cached;
+ }
+
+ /**
+ * @param string $name
+ * @return int
+ */
+ private function findByName($name)
+ {
+ foreach ($this->stack as $k => $v) {
+ if ($v[1] === $name) {
+ return $k;
+ }
+ }
+
+ throw new \InvalidArgumentException("Middleware not found: $name");
+ }
+
+ /**
+ * Splices a function into the middleware list at a specific position.
+ *
+ * @param string $findName
+ * @param string $withName
+ * @param callable $middleware
+ * @param bool $before
+ */
+ private function splice($findName, $withName, callable $middleware, $before)
+ {
+ $this->cached = null;
+ $idx = $this->findByName($findName);
+ $tuple = [$middleware, $withName];
+
+ if ($before) {
+ if ($idx === 0) {
+ array_unshift($this->stack, $tuple);
+ } else {
+ $replacement = [$tuple, $this->stack[$idx]];
+ array_splice($this->stack, $idx, 1, $replacement);
+ }
+ } elseif ($idx === count($this->stack) - 1) {
+ $this->stack[] = $tuple;
+ } else {
+ $replacement = [$this->stack[$idx], $tuple];
+ array_splice($this->stack, $idx, 1, $replacement);
+ }
+ }
+
+ /**
+ * Provides a debug string for a given callable.
+ *
+ * @param array|callable $fn Function to write as a string.
+ *
+ * @return string
+ */
+ private function debugCallable($fn)
+ {
+ if (is_string($fn)) {
+ return "callable({$fn})";
+ }
+
+ if (is_array($fn)) {
+ return is_string($fn[0])
+ ? "callable({$fn[0]}::{$fn[1]})"
+ : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])";
+ }
+
+ return 'callable(' . spl_object_hash($fn) . ')';
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/MessageFormatter.php
new file mode 100755
index 0000000..663ac73
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/MessageFormatter.php
@@ -0,0 +1,180 @@
+>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
+ const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';
+
+ /** @var string Template used to format log messages */
+ private $template;
+
+ /**
+ * @param string $template Log message template
+ */
+ public function __construct($template = self::CLF)
+ {
+ $this->template = $template ?: self::CLF;
+ }
+
+ /**
+ * Returns a formatted message string.
+ *
+ * @param RequestInterface $request Request that was sent
+ * @param ResponseInterface $response Response that was received
+ * @param \Exception $error Exception that was received
+ *
+ * @return string
+ */
+ public function format(
+ RequestInterface $request,
+ ResponseInterface $response = null,
+ \Exception $error = null
+ ) {
+ $cache = [];
+
+ return preg_replace_callback(
+ '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
+ function (array $matches) use ($request, $response, $error, &$cache) {
+ if (isset($cache[$matches[1]])) {
+ return $cache[$matches[1]];
+ }
+
+ $result = '';
+ switch ($matches[1]) {
+ case 'request':
+ $result = Psr7\str($request);
+ break;
+ case 'response':
+ $result = $response ? Psr7\str($response) : '';
+ break;
+ case 'req_headers':
+ $result = trim($request->getMethod()
+ . ' ' . $request->getRequestTarget())
+ . ' HTTP/' . $request->getProtocolVersion() . "\r\n"
+ . $this->headers($request);
+ break;
+ case 'res_headers':
+ $result = $response ?
+ sprintf(
+ 'HTTP/%s %d %s',
+ $response->getProtocolVersion(),
+ $response->getStatusCode(),
+ $response->getReasonPhrase()
+ ) . "\r\n" . $this->headers($response)
+ : 'NULL';
+ break;
+ case 'req_body':
+ $result = $request->getBody();
+ break;
+ case 'res_body':
+ $result = $response ? $response->getBody() : 'NULL';
+ break;
+ case 'ts':
+ case 'date_iso_8601':
+ $result = gmdate('c');
+ break;
+ case 'date_common_log':
+ $result = date('d/M/Y:H:i:s O');
+ break;
+ case 'method':
+ $result = $request->getMethod();
+ break;
+ case 'version':
+ $result = $request->getProtocolVersion();
+ break;
+ case 'uri':
+ case 'url':
+ $result = $request->getUri();
+ break;
+ case 'target':
+ $result = $request->getRequestTarget();
+ break;
+ case 'req_version':
+ $result = $request->getProtocolVersion();
+ break;
+ case 'res_version':
+ $result = $response
+ ? $response->getProtocolVersion()
+ : 'NULL';
+ break;
+ case 'host':
+ $result = $request->getHeaderLine('Host');
+ break;
+ case 'hostname':
+ $result = gethostname();
+ break;
+ case 'code':
+ $result = $response ? $response->getStatusCode() : 'NULL';
+ break;
+ case 'phrase':
+ $result = $response ? $response->getReasonPhrase() : 'NULL';
+ break;
+ case 'error':
+ $result = $error ? $error->getMessage() : 'NULL';
+ break;
+ default:
+ // handle prefixed dynamic headers
+ if (strpos($matches[1], 'req_header_') === 0) {
+ $result = $request->getHeaderLine(substr($matches[1], 11));
+ } elseif (strpos($matches[1], 'res_header_') === 0) {
+ $result = $response
+ ? $response->getHeaderLine(substr($matches[1], 11))
+ : 'NULL';
+ }
+ }
+
+ $cache[$matches[1]] = $result;
+ return $result;
+ },
+ $this->template
+ );
+ }
+
+ private function headers(MessageInterface $message)
+ {
+ $result = '';
+ foreach ($message->getHeaders() as $name => $values) {
+ $result .= $name . ': ' . implode(', ', $values) . "\r\n";
+ }
+
+ return trim($result);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Middleware.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Middleware.php
new file mode 100755
index 0000000..bffc197
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Middleware.php
@@ -0,0 +1,254 @@
+withCookieHeader($request);
+ return $handler($request, $options)
+ ->then(
+ function ($response) use ($cookieJar, $request) {
+ $cookieJar->extractCookies($request, $response);
+ return $response;
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that throws exceptions for 4xx or 5xx responses when the
+ * "http_error" request option is set to true.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function httpErrors()
+ {
+ return function (callable $handler) {
+ return function ($request, array $options) use ($handler) {
+ if (empty($options['http_errors'])) {
+ return $handler($request, $options);
+ }
+ return $handler($request, $options)->then(
+ function (ResponseInterface $response) use ($request) {
+ $code = $response->getStatusCode();
+ if ($code < 400) {
+ return $response;
+ }
+ throw RequestException::create($request, $response);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that pushes history data to an ArrayAccess container.
+ *
+ * @param array|\ArrayAccess $container Container to hold the history (by reference).
+ *
+ * @return callable Returns a function that accepts the next handler.
+ * @throws \InvalidArgumentException if container is not an array or ArrayAccess.
+ */
+ public static function history(&$container)
+ {
+ if (!is_array($container) && !$container instanceof \ArrayAccess) {
+ throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
+ }
+
+ return function (callable $handler) use (&$container) {
+ return function ($request, array $options) use ($handler, &$container) {
+ return $handler($request, $options)->then(
+ function ($value) use ($request, &$container, $options) {
+ $container[] = [
+ 'request' => $request,
+ 'response' => $value,
+ 'error' => null,
+ 'options' => $options
+ ];
+ return $value;
+ },
+ function ($reason) use ($request, &$container, $options) {
+ $container[] = [
+ 'request' => $request,
+ 'response' => null,
+ 'error' => $reason,
+ 'options' => $options
+ ];
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that invokes a callback before and after sending a request.
+ *
+ * The provided listener cannot modify or alter the response. It simply
+ * "taps" into the chain to be notified before returning the promise. The
+ * before listener accepts a request and options array, and the after
+ * listener accepts a request, options array, and response promise.
+ *
+ * @param callable $before Function to invoke before forwarding the request.
+ * @param callable $after Function invoked after forwarding.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function tap(callable $before = null, callable $after = null)
+ {
+ return function (callable $handler) use ($before, $after) {
+ return function ($request, array $options) use ($handler, $before, $after) {
+ if ($before) {
+ $before($request, $options);
+ }
+ $response = $handler($request, $options);
+ if ($after) {
+ $after($request, $options, $response);
+ }
+ return $response;
+ };
+ };
+ }
+
+ /**
+ * Middleware that handles request redirects.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function redirect()
+ {
+ return function (callable $handler) {
+ return new RedirectMiddleware($handler);
+ };
+ }
+
+ /**
+ * Middleware that retries requests based on the boolean result of
+ * invoking the provided "decider" function.
+ *
+ * If no delay function is provided, a simple implementation of exponential
+ * backoff will be utilized.
+ *
+ * @param callable $decider Function that accepts the number of retries,
+ * a request, [response], and [exception] and
+ * returns true if the request is to be retried.
+ * @param callable $delay Function that accepts the number of retries and
+ * returns the number of milliseconds to delay.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function retry(callable $decider, callable $delay = null)
+ {
+ return function (callable $handler) use ($decider, $delay) {
+ return new RetryMiddleware($decider, $handler, $delay);
+ };
+ }
+
+ /**
+ * Middleware that logs requests, responses, and errors using a message
+ * formatter.
+ *
+ * @param LoggerInterface $logger Logs messages.
+ * @param MessageFormatter $formatter Formatter used to create message strings.
+ * @param string $logLevel Level at which to log requests.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = 'info' /* \Psr\Log\LogLevel::INFO */)
+ {
+ return function (callable $handler) use ($logger, $formatter, $logLevel) {
+ return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) {
+ return $handler($request, $options)->then(
+ function ($response) use ($logger, $request, $formatter, $logLevel) {
+ $message = $formatter->format($request, $response);
+ $logger->log($logLevel, $message);
+ return $response;
+ },
+ function ($reason) use ($logger, $request, $formatter) {
+ $response = $reason instanceof RequestException
+ ? $reason->getResponse()
+ : null;
+ $message = $formatter->format($request, $response, $reason);
+ $logger->notice($message);
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * This middleware adds a default content-type if possible, a default
+ * content-length or transfer-encoding header, and the expect header.
+ *
+ * @return callable
+ */
+ public static function prepareBody()
+ {
+ return function (callable $handler) {
+ return new PrepareBodyMiddleware($handler);
+ };
+ }
+
+ /**
+ * Middleware that applies a map function to the request before passing to
+ * the next handler.
+ *
+ * @param callable $fn Function that accepts a RequestInterface and returns
+ * a RequestInterface.
+ * @return callable
+ */
+ public static function mapRequest(callable $fn)
+ {
+ return function (callable $handler) use ($fn) {
+ return function ($request, array $options) use ($handler, $fn) {
+ return $handler($fn($request), $options);
+ };
+ };
+ }
+
+ /**
+ * Middleware that applies a map function to the resolved promise's
+ * response.
+ *
+ * @param callable $fn Function that accepts a ResponseInterface and
+ * returns a ResponseInterface.
+ * @return callable
+ */
+ public static function mapResponse(callable $fn)
+ {
+ return function (callable $handler) use ($fn) {
+ return function ($request, array $options) use ($handler, $fn) {
+ return $handler($request, $options)->then($fn);
+ };
+ };
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Pool.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Pool.php
new file mode 100755
index 0000000..05c854a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/Pool.php
@@ -0,0 +1,123 @@
+ $rfn) {
+ if ($rfn instanceof RequestInterface) {
+ yield $key => $client->sendAsync($rfn, $opts);
+ } elseif (is_callable($rfn)) {
+ yield $key => $rfn($opts);
+ } else {
+ throw new \InvalidArgumentException('Each value yielded by '
+ . 'the iterator must be a Psr7\Http\Message\RequestInterface '
+ . 'or a callable that returns a promise that fulfills '
+ . 'with a Psr7\Message\Http\ResponseInterface object.');
+ }
+ }
+ };
+
+ $this->each = new EachPromise($requests(), $config);
+ }
+
+ public function promise()
+ {
+ return $this->each->promise();
+ }
+
+ /**
+ * Sends multiple requests concurrently and returns an array of responses
+ * and exceptions that uses the same ordering as the provided requests.
+ *
+ * IMPORTANT: This method keeps every request and response in memory, and
+ * as such, is NOT recommended when sending a large number or an
+ * indeterminate number of requests concurrently.
+ *
+ * @param ClientInterface $client Client used to send the requests
+ * @param array|\Iterator $requests Requests to send concurrently.
+ * @param array $options Passes through the options available in
+ * {@see GuzzleHttp\Pool::__construct}
+ *
+ * @return array Returns an array containing the response or an exception
+ * in the same order that the requests were sent.
+ * @throws \InvalidArgumentException if the event format is incorrect.
+ */
+ public static function batch(
+ ClientInterface $client,
+ $requests,
+ array $options = []
+ ) {
+ $res = [];
+ self::cmpCallback($options, 'fulfilled', $res);
+ self::cmpCallback($options, 'rejected', $res);
+ $pool = new static($client, $requests, $options);
+ $pool->promise()->wait();
+ ksort($res);
+
+ return $res;
+ }
+
+ private static function cmpCallback(array &$options, $name, array &$results)
+ {
+ if (!isset($options[$name])) {
+ $options[$name] = function ($v, $k) use (&$results) {
+ $results[$k] = $v;
+ };
+ } else {
+ $currentFn = $options[$name];
+ $options[$name] = function ($v, $k) use (&$results, $currentFn) {
+ $currentFn($v, $k);
+ $results[$k] = $v;
+ };
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php
new file mode 100755
index 0000000..2eb95f9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php
@@ -0,0 +1,106 @@
+nextHandler = $nextHandler;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $fn = $this->nextHandler;
+
+ // Don't do anything if the request has no body.
+ if ($request->getBody()->getSize() === 0) {
+ return $fn($request, $options);
+ }
+
+ $modify = [];
+
+ // Add a default content-type if possible.
+ if (!$request->hasHeader('Content-Type')) {
+ if ($uri = $request->getBody()->getMetadata('uri')) {
+ if ($type = Psr7\mimetype_from_filename($uri)) {
+ $modify['set_headers']['Content-Type'] = $type;
+ }
+ }
+ }
+
+ // Add a default content-length or transfer-encoding header.
+ if (!$request->hasHeader('Content-Length')
+ && !$request->hasHeader('Transfer-Encoding')
+ ) {
+ $size = $request->getBody()->getSize();
+ if ($size !== null) {
+ $modify['set_headers']['Content-Length'] = $size;
+ } else {
+ $modify['set_headers']['Transfer-Encoding'] = 'chunked';
+ }
+ }
+
+ // Add the expect header if needed.
+ $this->addExpectHeader($request, $options, $modify);
+
+ return $fn(Psr7\modify_request($request, $modify), $options);
+ }
+
+ private function addExpectHeader(
+ RequestInterface $request,
+ array $options,
+ array &$modify
+ ) {
+ // Determine if the Expect header should be used
+ if ($request->hasHeader('Expect')) {
+ return;
+ }
+
+ $expect = isset($options['expect']) ? $options['expect'] : null;
+
+ // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
+ if ($expect === false || $request->getProtocolVersion() < 1.1) {
+ return;
+ }
+
+ // The expect header is unconditionally enabled
+ if ($expect === true) {
+ $modify['set_headers']['Expect'] = '100-Continue';
+ return;
+ }
+
+ // By default, send the expect header when the payload is > 1mb
+ if ($expect === null) {
+ $expect = 1048576;
+ }
+
+ // Always add if the body cannot be rewound, the size cannot be
+ // determined, or the size is greater than the cutoff threshold
+ $body = $request->getBody();
+ $size = $body->getSize();
+
+ if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
+ $modify['set_headers']['Expect'] = '100-Continue';
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php
new file mode 100755
index 0000000..bff4e4e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php
@@ -0,0 +1,237 @@
+ 5,
+ 'protocols' => ['http', 'https'],
+ 'strict' => false,
+ 'referer' => false,
+ 'track_redirects' => false,
+ ];
+
+ /** @var callable */
+ private $nextHandler;
+
+ /**
+ * @param callable $nextHandler Next handler to invoke.
+ */
+ public function __construct(callable $nextHandler)
+ {
+ $this->nextHandler = $nextHandler;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $fn = $this->nextHandler;
+
+ if (empty($options['allow_redirects'])) {
+ return $fn($request, $options);
+ }
+
+ if ($options['allow_redirects'] === true) {
+ $options['allow_redirects'] = self::$defaultSettings;
+ } elseif (!is_array($options['allow_redirects'])) {
+ throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
+ } else {
+ // Merge the default settings with the provided settings
+ $options['allow_redirects'] += self::$defaultSettings;
+ }
+
+ if (empty($options['allow_redirects']['max'])) {
+ return $fn($request, $options);
+ }
+
+ return $fn($request, $options)
+ ->then(function (ResponseInterface $response) use ($request, $options) {
+ return $this->checkRedirect($request, $options, $response);
+ });
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface|PromiseInterface $response
+ *
+ * @return ResponseInterface|PromiseInterface
+ */
+ public function checkRedirect(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ if (substr($response->getStatusCode(), 0, 1) != '3'
+ || !$response->hasHeader('Location')
+ ) {
+ return $response;
+ }
+
+ $this->guardMax($request, $options);
+ $nextRequest = $this->modifyRequest($request, $options, $response);
+
+ if (isset($options['allow_redirects']['on_redirect'])) {
+ call_user_func(
+ $options['allow_redirects']['on_redirect'],
+ $request,
+ $response,
+ $nextRequest->getUri()
+ );
+ }
+
+ /** @var PromiseInterface|ResponseInterface $promise */
+ $promise = $this($nextRequest, $options);
+
+ // Add headers to be able to track history of redirects.
+ if (!empty($options['allow_redirects']['track_redirects'])) {
+ return $this->withTracking(
+ $promise,
+ (string) $nextRequest->getUri(),
+ $response->getStatusCode()
+ );
+ }
+
+ return $promise;
+ }
+
+ private function withTracking(PromiseInterface $promise, $uri, $statusCode)
+ {
+ return $promise->then(
+ function (ResponseInterface $response) use ($uri, $statusCode) {
+ // Note that we are pushing to the front of the list as this
+ // would be an earlier response than what is currently present
+ // in the history header.
+ $historyHeader = $response->getHeader(self::HISTORY_HEADER);
+ $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
+ array_unshift($historyHeader, $uri);
+ array_unshift($statusHeader, $statusCode);
+ return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
+ ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
+ }
+ );
+ }
+
+ private function guardMax(RequestInterface $request, array &$options)
+ {
+ $current = isset($options['__redirect_count'])
+ ? $options['__redirect_count']
+ : 0;
+ $options['__redirect_count'] = $current + 1;
+ $max = $options['allow_redirects']['max'];
+
+ if ($options['__redirect_count'] > $max) {
+ throw new TooManyRedirectsException(
+ "Will not follow more than {$max} redirects",
+ $request
+ );
+ }
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface $response
+ *
+ * @return RequestInterface
+ */
+ public function modifyRequest(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ // Request modifications to apply.
+ $modify = [];
+ $protocols = $options['allow_redirects']['protocols'];
+
+ // Use a GET request if this is an entity enclosing request and we are
+ // not forcing RFC compliance, but rather emulating what all browsers
+ // would do.
+ $statusCode = $response->getStatusCode();
+ if ($statusCode == 303 ||
+ ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict'])
+ ) {
+ $modify['method'] = 'GET';
+ $modify['body'] = '';
+ }
+
+ $modify['uri'] = $this->redirectUri($request, $response, $protocols);
+ Psr7\rewind_body($request);
+
+ // Add the Referer header if it is told to do so and only
+ // add the header if we are not redirecting from https to http.
+ if ($options['allow_redirects']['referer']
+ && $modify['uri']->getScheme() === $request->getUri()->getScheme()
+ ) {
+ $uri = $request->getUri()->withUserInfo('');
+ $modify['set_headers']['Referer'] = (string) $uri;
+ } else {
+ $modify['remove_headers'][] = 'Referer';
+ }
+
+ // Remove Authorization header if host is different.
+ if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
+ $modify['remove_headers'][] = 'Authorization';
+ }
+
+ return Psr7\modify_request($request, $modify);
+ }
+
+ /**
+ * Set the appropriate URL on the request based on the location header
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param array $protocols
+ *
+ * @return UriInterface
+ */
+ private function redirectUri(
+ RequestInterface $request,
+ ResponseInterface $response,
+ array $protocols
+ ) {
+ $location = Psr7\UriResolver::resolve(
+ $request->getUri(),
+ new Psr7\Uri($response->getHeaderLine('Location'))
+ );
+
+ // Ensure that the redirect URI is allowed based on the protocols.
+ if (!in_array($location->getScheme(), $protocols)) {
+ throw new BadResponseException(
+ sprintf(
+ 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
+ $location,
+ implode(', ', $protocols)
+ ),
+ $request,
+ $response
+ );
+ }
+
+ return $location;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/RequestOptions.php
new file mode 100755
index 0000000..5c0fd19
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/RequestOptions.php
@@ -0,0 +1,255 @@
+decider = $decider;
+ $this->nextHandler = $nextHandler;
+ $this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
+ }
+
+ /**
+ * Default exponential backoff delay function.
+ *
+ * @param int $retries
+ *
+ * @return int
+ */
+ public static function exponentialDelay($retries)
+ {
+ return (int) pow(2, $retries - 1);
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (!isset($options['retries'])) {
+ $options['retries'] = 0;
+ }
+
+ $fn = $this->nextHandler;
+ return $fn($request, $options)
+ ->then(
+ $this->onFulfilled($request, $options),
+ $this->onRejected($request, $options)
+ );
+ }
+
+ private function onFulfilled(RequestInterface $req, array $options)
+ {
+ return function ($value) use ($req, $options) {
+ if (!call_user_func(
+ $this->decider,
+ $options['retries'],
+ $req,
+ $value,
+ null
+ )) {
+ return $value;
+ }
+ return $this->doRetry($req, $options, $value);
+ };
+ }
+
+ private function onRejected(RequestInterface $req, array $options)
+ {
+ return function ($reason) use ($req, $options) {
+ if (!call_user_func(
+ $this->decider,
+ $options['retries'],
+ $req,
+ null,
+ $reason
+ )) {
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ return $this->doRetry($req, $options);
+ };
+ }
+
+ private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null)
+ {
+ $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response);
+
+ return $this($request, $options);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/TransferStats.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/TransferStats.php
new file mode 100755
index 0000000..23a22a3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/TransferStats.php
@@ -0,0 +1,126 @@
+request = $request;
+ $this->response = $response;
+ $this->transferTime = $transferTime;
+ $this->handlerErrorData = $handlerErrorData;
+ $this->handlerStats = $handlerStats;
+ }
+
+ /**
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Returns the response that was received (if any).
+ *
+ * @return ResponseInterface|null
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Returns true if a response was received.
+ *
+ * @return bool
+ */
+ public function hasResponse()
+ {
+ return $this->response !== null;
+ }
+
+ /**
+ * Gets handler specific error data.
+ *
+ * This might be an exception, a integer representing an error code, or
+ * anything else. Relying on this value assumes that you know what handler
+ * you are using.
+ *
+ * @return mixed
+ */
+ public function getHandlerErrorData()
+ {
+ return $this->handlerErrorData;
+ }
+
+ /**
+ * Get the effective URI the request was sent to.
+ *
+ * @return UriInterface
+ */
+ public function getEffectiveUri()
+ {
+ return $this->request->getUri();
+ }
+
+ /**
+ * Get the estimated time the request was being transferred by the handler.
+ *
+ * @return float Time in seconds.
+ */
+ public function getTransferTime()
+ {
+ return $this->transferTime;
+ }
+
+ /**
+ * Gets an array of all of the handler specific transfer data.
+ *
+ * @return array
+ */
+ public function getHandlerStats()
+ {
+ return $this->handlerStats;
+ }
+
+ /**
+ * Get a specific handler statistic from the handler by name.
+ *
+ * @param string $stat Handler specific transfer stat to retrieve.
+ *
+ * @return mixed|null
+ */
+ public function getHandlerStat($stat)
+ {
+ return isset($this->handlerStats[$stat])
+ ? $this->handlerStats[$stat]
+ : null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/UriTemplate.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/UriTemplate.php
new file mode 100755
index 0000000..96dcfd0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/UriTemplate.php
@@ -0,0 +1,237 @@
+ ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
+ '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
+ '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
+ ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
+ '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
+ '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true]
+ ];
+
+ /** @var array Delimiters */
+ private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$',
+ '&', '\'', '(', ')', '*', '+', ',', ';', '='];
+
+ /** @var array Percent encoded delimiters */
+ private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D',
+ '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
+ '%3B', '%3D'];
+
+ public function expand($template, array $variables)
+ {
+ if (false === strpos($template, '{')) {
+ return $template;
+ }
+
+ $this->template = $template;
+ $this->variables = $variables;
+
+ return preg_replace_callback(
+ '/\{([^\}]+)\}/',
+ [$this, 'expandMatch'],
+ $this->template
+ );
+ }
+
+ /**
+ * Parse an expression into parts
+ *
+ * @param string $expression Expression to parse
+ *
+ * @return array Returns an associative array of parts
+ */
+ private function parseExpression($expression)
+ {
+ $result = [];
+
+ if (isset(self::$operatorHash[$expression[0]])) {
+ $result['operator'] = $expression[0];
+ $expression = substr($expression, 1);
+ } else {
+ $result['operator'] = '';
+ }
+
+ foreach (explode(',', $expression) as $value) {
+ $value = trim($value);
+ $varspec = [];
+ if ($colonPos = strpos($value, ':')) {
+ $varspec['value'] = substr($value, 0, $colonPos);
+ $varspec['modifier'] = ':';
+ $varspec['position'] = (int) substr($value, $colonPos + 1);
+ } elseif (substr($value, -1) === '*') {
+ $varspec['modifier'] = '*';
+ $varspec['value'] = substr($value, 0, -1);
+ } else {
+ $varspec['value'] = (string) $value;
+ $varspec['modifier'] = '';
+ }
+ $result['values'][] = $varspec;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Process an expansion
+ *
+ * @param array $matches Matches met in the preg_replace_callback
+ *
+ * @return string Returns the replacement string
+ */
+ private function expandMatch(array $matches)
+ {
+ static $rfc1738to3986 = ['+' => '%20', '%7e' => '~'];
+
+ $replacements = [];
+ $parsed = self::parseExpression($matches[1]);
+ $prefix = self::$operatorHash[$parsed['operator']]['prefix'];
+ $joiner = self::$operatorHash[$parsed['operator']]['joiner'];
+ $useQuery = self::$operatorHash[$parsed['operator']]['query'];
+
+ foreach ($parsed['values'] as $value) {
+ if (!isset($this->variables[$value['value']])) {
+ continue;
+ }
+
+ $variable = $this->variables[$value['value']];
+ $actuallyUseQuery = $useQuery;
+ $expanded = '';
+
+ if (is_array($variable)) {
+ $isAssoc = $this->isAssoc($variable);
+ $kvp = [];
+ foreach ($variable as $key => $var) {
+ if ($isAssoc) {
+ $key = rawurlencode($key);
+ $isNestedArray = is_array($var);
+ } else {
+ $isNestedArray = false;
+ }
+
+ if (!$isNestedArray) {
+ $var = rawurlencode($var);
+ if ($parsed['operator'] === '+' ||
+ $parsed['operator'] === '#'
+ ) {
+ $var = $this->decodeReserved($var);
+ }
+ }
+
+ if ($value['modifier'] === '*') {
+ if ($isAssoc) {
+ if ($isNestedArray) {
+ // Nested arrays must allow for deeply nested
+ // structures.
+ $var = strtr(
+ http_build_query([$key => $var]),
+ $rfc1738to3986
+ );
+ } else {
+ $var = $key . '=' . $var;
+ }
+ } elseif ($key > 0 && $actuallyUseQuery) {
+ $var = $value['value'] . '=' . $var;
+ }
+ }
+
+ $kvp[$key] = $var;
+ }
+
+ if (empty($variable)) {
+ $actuallyUseQuery = false;
+ } elseif ($value['modifier'] === '*') {
+ $expanded = implode($joiner, $kvp);
+ if ($isAssoc) {
+ // Don't prepend the value name when using the explode
+ // modifier with an associative array.
+ $actuallyUseQuery = false;
+ }
+ } else {
+ if ($isAssoc) {
+ // When an associative array is encountered and the
+ // explode modifier is not set, then the result must be
+ // a comma separated list of keys followed by their
+ // respective values.
+ foreach ($kvp as $k => &$v) {
+ $v = $k . ',' . $v;
+ }
+ }
+ $expanded = implode(',', $kvp);
+ }
+ } else {
+ if ($value['modifier'] === ':') {
+ $variable = substr($variable, 0, $value['position']);
+ }
+ $expanded = rawurlencode($variable);
+ if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
+ $expanded = $this->decodeReserved($expanded);
+ }
+ }
+
+ if ($actuallyUseQuery) {
+ if (!$expanded && $joiner !== '&') {
+ $expanded = $value['value'];
+ } else {
+ $expanded = $value['value'] . '=' . $expanded;
+ }
+ }
+
+ $replacements[] = $expanded;
+ }
+
+ $ret = implode($joiner, $replacements);
+ if ($ret && $prefix) {
+ return $prefix . $ret;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Determines if an array is associative.
+ *
+ * This makes the assumption that input arrays are sequences or hashes.
+ * This assumption is a tradeoff for accuracy in favor of speed, but it
+ * should work in almost every case where input is supplied for a URI
+ * template.
+ *
+ * @param array $array Array to check
+ *
+ * @return bool
+ */
+ private function isAssoc(array $array)
+ {
+ return $array && array_keys($array)[0] !== 0;
+ }
+
+ /**
+ * Removes percent encoding on reserved characters (used with + and #
+ * modifiers).
+ *
+ * @param string $string String to fix
+ *
+ * @return string
+ */
+ private function decodeReserved($string)
+ {
+ return str_replace(self::$delimsPct, self::$delims, $string);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/functions.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/functions.php
new file mode 100755
index 0000000..51d736d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/functions.php
@@ -0,0 +1,346 @@
+expand($template, $variables);
+}
+
+/**
+ * Debug function used to describe the provided value type and class.
+ *
+ * @param mixed $input
+ *
+ * @return string Returns a string containing the type of the variable and
+ * if a class is provided, the class name.
+ */
+function describe_type($input)
+{
+ switch (gettype($input)) {
+ case 'object':
+ return 'object(' . get_class($input) . ')';
+ case 'array':
+ return 'array(' . count($input) . ')';
+ default:
+ ob_start();
+ var_dump($input);
+ // normalize float vs double
+ return str_replace('double(', 'float(', rtrim(ob_get_clean()));
+ }
+}
+
+/**
+ * Parses an array of header lines into an associative array of headers.
+ *
+ * @param array $lines Header lines array of strings in the following
+ * format: "Name: Value"
+ * @return array
+ */
+function headers_from_lines($lines)
+{
+ $headers = [];
+
+ foreach ($lines as $line) {
+ $parts = explode(':', $line, 2);
+ $headers[trim($parts[0])][] = isset($parts[1])
+ ? trim($parts[1])
+ : null;
+ }
+
+ return $headers;
+}
+
+/**
+ * Returns a debug stream based on the provided variable.
+ *
+ * @param mixed $value Optional value
+ *
+ * @return resource
+ */
+function debug_resource($value = null)
+{
+ if (is_resource($value)) {
+ return $value;
+ } elseif (defined('STDOUT')) {
+ return STDOUT;
+ }
+
+ return fopen('php://output', 'w');
+}
+
+/**
+ * Chooses and creates a default handler to use based on the environment.
+ *
+ * The returned handler is not wrapped by any default middlewares.
+ *
+ * @throws \RuntimeException if no viable Handler is available.
+ * @return callable Returns the best handler for the given system.
+ */
+function choose_handler()
+{
+ $handler = null;
+ if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
+ $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
+ } elseif (function_exists('curl_exec')) {
+ $handler = new CurlHandler();
+ } elseif (function_exists('curl_multi_exec')) {
+ $handler = new CurlMultiHandler();
+ }
+
+ if (ini_get('allow_url_fopen')) {
+ $handler = $handler
+ ? Proxy::wrapStreaming($handler, new StreamHandler())
+ : new StreamHandler();
+ } elseif (!$handler) {
+ throw new \RuntimeException('GuzzleHttp requires cURL, the '
+ . 'allow_url_fopen ini setting, or a custom HTTP handler.');
+ }
+
+ return $handler;
+}
+
+/**
+ * Get the default User-Agent string to use with Guzzle
+ *
+ * @return string
+ */
+function default_user_agent()
+{
+ static $defaultAgent = '';
+
+ if (!$defaultAgent) {
+ $defaultAgent = 'GuzzleHttp/' . Client::VERSION;
+ if (extension_loaded('curl') && function_exists('curl_version')) {
+ $defaultAgent .= ' curl/' . \curl_version()['version'];
+ }
+ $defaultAgent .= ' PHP/' . PHP_VERSION;
+ }
+
+ return $defaultAgent;
+}
+
+/**
+ * Returns the default cacert bundle for the current system.
+ *
+ * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
+ * If those settings are not configured, then the common locations for
+ * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
+ * and Windows are checked. If any of these file locations are found on
+ * disk, they will be utilized.
+ *
+ * Note: the result of this function is cached for subsequent calls.
+ *
+ * @return string
+ * @throws \RuntimeException if no bundle can be found.
+ */
+function default_ca_bundle()
+{
+ static $cached = null;
+ static $cafiles = [
+ // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
+ '/etc/pki/tls/certs/ca-bundle.crt',
+ // Ubuntu, Debian (provided by the ca-certificates package)
+ '/etc/ssl/certs/ca-certificates.crt',
+ // FreeBSD (provided by the ca_root_nss package)
+ '/usr/local/share/certs/ca-root-nss.crt',
+ // SLES 12 (provided by the ca-certificates package)
+ '/var/lib/ca-certificates/ca-bundle.pem',
+ // OS X provided by homebrew (using the default path)
+ '/usr/local/etc/openssl/cert.pem',
+ // Google app engine
+ '/etc/ca-certificates.crt',
+ // Windows?
+ 'C:\\windows\\system32\\curl-ca-bundle.crt',
+ 'C:\\windows\\curl-ca-bundle.crt',
+ ];
+
+ if ($cached) {
+ return $cached;
+ }
+
+ if ($ca = ini_get('openssl.cafile')) {
+ return $cached = $ca;
+ }
+
+ if ($ca = ini_get('curl.cainfo')) {
+ return $cached = $ca;
+ }
+
+ foreach ($cafiles as $filename) {
+ if (file_exists($filename)) {
+ return $cached = $filename;
+ }
+ }
+
+ throw new \RuntimeException(
+ <<< EOT
+No system CA bundle could be found in any of the the common system locations.
+PHP versions earlier than 5.6 are not properly configured to use the system's
+CA bundle by default. In order to verify peer certificates, you will need to
+supply the path on disk to a certificate bundle to the 'verify' request
+option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
+need a specific certificate bundle, then Mozilla provides a commonly used CA
+bundle which can be downloaded here (provided by the maintainer of cURL):
+https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
+you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
+ini setting to point to the path to the file, allowing you to omit the 'verify'
+request option. See http://curl.haxx.se/docs/sslcerts.html for more
+information.
+EOT
+ );
+}
+
+/**
+ * Creates an associative array of lowercase header names to the actual
+ * header casing.
+ *
+ * @param array $headers
+ *
+ * @return array
+ */
+function normalize_header_keys(array $headers)
+{
+ $result = [];
+ foreach (array_keys($headers) as $key) {
+ $result[strtolower($key)] = $key;
+ }
+
+ return $result;
+}
+
+/**
+ * Returns true if the provided host matches any of the no proxy areas.
+ *
+ * This method will strip a port from the host if it is present. Each pattern
+ * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
+ * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
+ * "baz.foo.com", but ".foo.com" != "foo.com").
+ *
+ * Areas are matched in the following cases:
+ * 1. "*" (without quotes) always matches any hosts.
+ * 2. An exact match.
+ * 3. The area starts with "." and the area is the last part of the host. e.g.
+ * '.mit.edu' will match any host that ends with '.mit.edu'.
+ *
+ * @param string $host Host to check against the patterns.
+ * @param array $noProxyArray An array of host patterns.
+ *
+ * @return bool
+ */
+function is_host_in_noproxy($host, array $noProxyArray)
+{
+ if (strlen($host) === 0) {
+ throw new \InvalidArgumentException('Empty host provided');
+ }
+
+ // Strip port if present.
+ if (strpos($host, ':')) {
+ $host = explode($host, ':', 2)[0];
+ }
+
+ foreach ($noProxyArray as $area) {
+ // Always match on wildcards.
+ if ($area === '*') {
+ return true;
+ } elseif (empty($area)) {
+ // Don't match on empty values.
+ continue;
+ } elseif ($area === $host) {
+ // Exact matches.
+ return true;
+ } else {
+ // Special match if the area when prefixed with ".". Remove any
+ // existing leading "." and add a new leading ".".
+ $area = '.' . ltrim($area, '.');
+ if (substr($host, -(strlen($area))) === $area) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Wrapper for json_decode that throws when an error occurs.
+ *
+ * @param string $json JSON data to parse
+ * @param bool $assoc When true, returned objects will be converted
+ * into associative arrays.
+ * @param int $depth User specified recursion depth.
+ * @param int $options Bitmask of JSON decode options.
+ *
+ * @return mixed
+ * @throws Exception\InvalidArgumentException if the JSON cannot be decoded.
+ * @link http://www.php.net/manual/en/function.json-decode.php
+ */
+function json_decode($json, $assoc = false, $depth = 512, $options = 0)
+{
+ $data = \json_decode($json, $assoc, $depth, $options);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new Exception\InvalidArgumentException(
+ 'json_decode error: ' . json_last_error_msg()
+ );
+ }
+
+ return $data;
+}
+
+/**
+ * Wrapper for JSON encoding that throws when an error occurs.
+ *
+ * @param mixed $value The value being encoded
+ * @param int $options JSON encode option bitmask
+ * @param int $depth Set the maximum depth. Must be greater than zero.
+ *
+ * @return string
+ * @throws Exception\InvalidArgumentException if the JSON cannot be encoded.
+ * @link http://www.php.net/manual/en/function.json-encode.php
+ */
+function json_encode($value, $options = 0, $depth = 512)
+{
+ $json = \json_encode($value, $options, $depth);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new Exception\InvalidArgumentException(
+ 'json_encode error: ' . json_last_error_msg()
+ );
+ }
+
+ return $json;
+}
+
+/**
+ * Wrapper for the hrtime() or microtime() functions
+ * (depending on the PHP version, one of the two is used)
+ *
+ * @return float|mixed UNIX timestamp
+ * @internal
+ */
+function _current_time()
+{
+ return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true);
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/functions_include.php b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/functions_include.php
new file mode 100755
index 0000000..a93393a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/guzzle/src/functions_include.php
@@ -0,0 +1,6 @@
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/Makefile b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/Makefile
new file mode 100755
index 0000000..8d5b3ef
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/Makefile
@@ -0,0 +1,13 @@
+all: clean test
+
+test:
+ vendor/bin/phpunit
+
+coverage:
+ vendor/bin/phpunit --coverage-html=artifacts/coverage
+
+view-coverage:
+ open artifacts/coverage/index.html
+
+clean:
+ rm -rf artifacts/*
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/README.md b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/README.md
new file mode 100755
index 0000000..7b607e2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/README.md
@@ -0,0 +1,504 @@
+# Guzzle Promises
+
+[Promises/A+](https://promisesaplus.com/) implementation that handles promise
+chaining and resolution iteratively, allowing for "infinite" promise chaining
+while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/)
+for a general introduction to promises.
+
+- [Features](#features)
+- [Quick start](#quick-start)
+- [Synchronous wait](#synchronous-wait)
+- [Cancellation](#cancellation)
+- [API](#api)
+ - [Promise](#promise)
+ - [FulfilledPromise](#fulfilledpromise)
+ - [RejectedPromise](#rejectedpromise)
+- [Promise interop](#promise-interop)
+- [Implementation notes](#implementation-notes)
+
+
+# Features
+
+- [Promises/A+](https://promisesaplus.com/) implementation.
+- Promise resolution and chaining is handled iteratively, allowing for
+ "infinite" promise chaining.
+- Promises have a synchronous `wait` method.
+- Promises can be cancelled.
+- Works with any object that has a `then` function.
+- C# style async/await coroutine promises using
+ `GuzzleHttp\Promise\coroutine()`.
+
+
+# Quick start
+
+A *promise* represents the eventual result of an asynchronous operation. The
+primary way of interacting with a promise is through its `then` method, which
+registers callbacks to receive either a promise's eventual value or the reason
+why the promise cannot be fulfilled.
+
+
+## Callbacks
+
+Callbacks are registered with the `then` method by providing an optional
+`$onFulfilled` followed by an optional `$onRejected` function.
+
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(
+ // $onFulfilled
+ function ($value) {
+ echo 'The promise was fulfilled.';
+ },
+ // $onRejected
+ function ($reason) {
+ echo 'The promise was rejected.';
+ }
+);
+```
+
+*Resolving* a promise means that you either fulfill a promise with a *value* or
+reject a promise with a *reason*. Resolving a promises triggers callbacks
+registered with the promises's `then` method. These callbacks are triggered
+only once and in the order in which they were added.
+
+
+## Resolving a promise
+
+Promises are fulfilled using the `resolve($value)` method. Resolving a promise
+with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
+all of the onFulfilled callbacks (resolving a promise with a rejected promise
+will reject the promise and trigger the `$onRejected` callbacks).
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise
+ ->then(function ($value) {
+ // Return a value and don't break the chain
+ return "Hello, " . $value;
+ })
+ // This then is executed after the first then and receives the value
+ // returned from the first then.
+ ->then(function ($value) {
+ echo $value;
+ });
+
+// Resolving the promise triggers the $onFulfilled callbacks and outputs
+// "Hello, reader".
+$promise->resolve('reader.');
+```
+
+
+## Promise forwarding
+
+Promises can be chained one after the other. Each then in the chain is a new
+promise. The return value of a promise is what's forwarded to the next
+promise in the chain. Returning a promise in a `then` callback will cause the
+subsequent promises in the chain to only be fulfilled when the returned promise
+has been fulfilled. The next promise in the chain will be invoked with the
+resolved value of the promise.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$nextPromise = new Promise();
+
+$promise
+ ->then(function ($value) use ($nextPromise) {
+ echo $value;
+ return $nextPromise;
+ })
+ ->then(function ($value) {
+ echo $value;
+ });
+
+// Triggers the first callback and outputs "A"
+$promise->resolve('A');
+// Triggers the second callback and outputs "B"
+$nextPromise->resolve('B');
+```
+
+## Promise rejection
+
+When a promise is rejected, the `$onRejected` callbacks are invoked with the
+rejection reason.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ echo $reason;
+});
+
+$promise->reject('Error!');
+// Outputs "Error!"
+```
+
+## Rejection forwarding
+
+If an exception is thrown in an `$onRejected` callback, subsequent
+`$onRejected` callbacks are invoked with the thrown exception as the reason.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ throw new \Exception($reason);
+})->then(null, function ($reason) {
+ assert($reason->getMessage() === 'Error!');
+});
+
+$promise->reject('Error!');
+```
+
+You can also forward a rejection down the promise chain by returning a
+`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or
+`$onRejected` callback.
+
+```php
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ return new RejectedPromise($reason);
+})->then(null, function ($reason) {
+ assert($reason === 'Error!');
+});
+
+$promise->reject('Error!');
+```
+
+If an exception is not thrown in a `$onRejected` callback and the callback
+does not return a rejected promise, downstream `$onFulfilled` callbacks are
+invoked using the value returned from the `$onRejected` callback.
+
+```php
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new Promise();
+$promise
+ ->then(null, function ($reason) {
+ return "It's ok";
+ })
+ ->then(function ($value) {
+ assert($value === "It's ok");
+ });
+
+$promise->reject('Error!');
+```
+
+# Synchronous wait
+
+You can synchronously force promises to complete using a promise's `wait`
+method. When creating a promise, you can provide a wait function that is used
+to synchronously force a promise to complete. When a wait function is invoked
+it is expected to deliver a value to the promise or reject the promise. If the
+wait function does not deliver a value, then an exception is thrown. The wait
+function provided to a promise constructor is invoked when the `wait` function
+of the promise is called.
+
+```php
+$promise = new Promise(function () use (&$promise) {
+ $promise->resolve('foo');
+});
+
+// Calling wait will return the value of the promise.
+echo $promise->wait(); // outputs "foo"
+```
+
+If an exception is encountered while invoking the wait function of a promise,
+the promise is rejected with the exception and the exception is thrown.
+
+```php
+$promise = new Promise(function () use (&$promise) {
+ throw new \Exception('foo');
+});
+
+$promise->wait(); // throws the exception.
+```
+
+Calling `wait` on a promise that has been fulfilled will not trigger the wait
+function. It will simply return the previously resolved value.
+
+```php
+$promise = new Promise(function () { die('this is not called!'); });
+$promise->resolve('foo');
+echo $promise->wait(); // outputs "foo"
+```
+
+Calling `wait` on a promise that has been rejected will throw an exception. If
+the rejection reason is an instance of `\Exception` the reason is thrown.
+Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
+can be obtained by calling the `getReason` method of the exception.
+
+```php
+$promise = new Promise();
+$promise->reject('foo');
+$promise->wait();
+```
+
+> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'
+
+
+## Unwrapping a promise
+
+When synchronously waiting on a promise, you are joining the state of the
+promise into the current state of execution (i.e., return the value of the
+promise if it was fulfilled or throw an exception if it was rejected). This is
+called "unwrapping" the promise. Waiting on a promise will by default unwrap
+the promise state.
+
+You can force a promise to resolve and *not* unwrap the state of the promise
+by passing `false` to the first argument of the `wait` function:
+
+```php
+$promise = new Promise();
+$promise->reject('foo');
+// This will not throw an exception. It simply ensures the promise has
+// been resolved.
+$promise->wait(false);
+```
+
+When unwrapping a promise, the resolved value of the promise will be waited
+upon until the unwrapped value is not a promise. This means that if you resolve
+promise A with a promise B and unwrap promise A, the value returned by the
+wait function will be the value delivered to promise B.
+
+**Note**: when you do not unwrap the promise, no value is returned.
+
+
+# Cancellation
+
+You can cancel a promise that has not yet been fulfilled using the `cancel()`
+method of a promise. When creating a promise you can provide an optional
+cancel function that when invoked cancels the action of computing a resolution
+of the promise.
+
+
+# API
+
+
+## Promise
+
+When creating a promise object, you can provide an optional `$waitFn` and
+`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
+expected to resolve the promise. `$cancelFn` is a function with no arguments
+that is expected to cancel the computation of a promise. It is invoked when the
+`cancel()` method of a promise is called.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise(
+ function () use (&$promise) {
+ $promise->resolve('waited');
+ },
+ function () {
+ // do something that will cancel the promise computation (e.g., close
+ // a socket, cancel a database query, etc...)
+ }
+);
+
+assert('waited' === $promise->wait());
+```
+
+A promise has the following methods:
+
+- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
+
+ Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.
+
+- `otherwise(callable $onRejected) : PromiseInterface`
+
+ Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
+
+- `wait($unwrap = true) : mixed`
+
+ Synchronously waits on the promise to complete.
+
+ `$unwrap` controls whether or not the value of the promise is returned for a
+ fulfilled promise or if an exception is thrown if the promise is rejected.
+ This is set to `true` by default.
+
+- `cancel()`
+
+ Attempts to cancel the promise if possible. The promise being cancelled and
+ the parent most ancestor that has not yet been resolved will also be
+ cancelled. Any promises waiting on the cancelled promise to resolve will also
+ be cancelled.
+
+- `getState() : string`
+
+ Returns the state of the promise. One of `pending`, `fulfilled`, or
+ `rejected`.
+
+- `resolve($value)`
+
+ Fulfills the promise with the given `$value`.
+
+- `reject($reason)`
+
+ Rejects the promise with the given `$reason`.
+
+
+## FulfilledPromise
+
+A fulfilled promise can be created to represent a promise that has been
+fulfilled.
+
+```php
+use GuzzleHttp\Promise\FulfilledPromise;
+
+$promise = new FulfilledPromise('value');
+
+// Fulfilled callbacks are immediately invoked.
+$promise->then(function ($value) {
+ echo $value;
+});
+```
+
+
+## RejectedPromise
+
+A rejected promise can be created to represent a promise that has been
+rejected.
+
+```php
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new RejectedPromise('Error');
+
+// Rejected callbacks are immediately invoked.
+$promise->then(null, function ($reason) {
+ echo $reason;
+});
+```
+
+
+# Promise interop
+
+This library works with foreign promises that have a `then` method. This means
+you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
+for example. When a foreign promise is returned inside of a then method
+callback, promise resolution will occur recursively.
+
+```php
+// Create a React promise
+$deferred = new React\Promise\Deferred();
+$reactPromise = $deferred->promise();
+
+// Create a Guzzle promise that is fulfilled with a React promise.
+$guzzlePromise = new \GuzzleHttp\Promise\Promise();
+$guzzlePromise->then(function ($value) use ($reactPromise) {
+ // Do something something with the value...
+ // Return the React promise
+ return $reactPromise;
+});
+```
+
+Please note that wait and cancel chaining is no longer possible when forwarding
+a foreign promise. You will need to wrap a third-party promise with a Guzzle
+promise in order to utilize wait and cancel functions with foreign promises.
+
+
+## Event Loop Integration
+
+In order to keep the stack size constant, Guzzle promises are resolved
+asynchronously using a task queue. When waiting on promises synchronously, the
+task queue will be automatically run to ensure that the blocking promise and
+any forwarded promises are resolved. When using promises asynchronously in an
+event loop, you will need to run the task queue on each tick of the loop. If
+you do not run the task queue, then promises will not be resolved.
+
+You can run the task queue using the `run()` method of the global task queue
+instance.
+
+```php
+// Get the global task queue
+$queue = \GuzzleHttp\Promise\queue();
+$queue->run();
+```
+
+For example, you could use Guzzle promises with React using a periodic timer:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$loop->addPeriodicTimer(0, [$queue, 'run']);
+```
+
+*TODO*: Perhaps adding a `futureTick()` on each tick would be faster?
+
+
+# Implementation notes
+
+
+## Promise resolution and chaining is handled iteratively
+
+By shuffling pending handlers from one owner to another, promises are
+resolved iteratively, allowing for "infinite" then chaining.
+
+```php
+then(function ($v) {
+ // The stack size remains constant (a good thing)
+ echo xdebug_get_stack_depth() . ', ';
+ return $v + 1;
+ });
+}
+
+$parent->resolve(0);
+var_dump($p->wait()); // int(1000)
+
+```
+
+When a promise is fulfilled or rejected with a non-promise value, the promise
+then takes ownership of the handlers of each child promise and delivers values
+down the chain without using recursion.
+
+When a promise is resolved with another promise, the original promise transfers
+all of its pending handlers to the new promise. When the new promise is
+eventually resolved, all of the pending handlers are delivered the forwarded
+value.
+
+
+## A promise is the deferred.
+
+Some promise libraries implement promises using a deferred object to represent
+a computation and a promise object to represent the delivery of the result of
+the computation. This is a nice separation of computation and delivery because
+consumers of the promise cannot modify the value that will be eventually
+delivered.
+
+One side effect of being able to implement promise resolution and chaining
+iteratively is that you need to be able for one promise to reach into the state
+of another promise to shuffle around ownership of handlers. In order to achieve
+this without making the handlers of a promise publicly mutable, a promise is
+also the deferred value, allowing promises of the same parent class to reach
+into and modify the private properties of promises of the same type. While this
+does allow consumers of the value to modify the resolution or rejection of the
+deferred, it is a small price to pay for keeping the stack size constant.
+
+```php
+$promise = new Promise();
+$promise->then(function ($value) { echo $value; });
+// The promise is the deferred value, so you can deliver a value to it.
+$promise->resolve('foo');
+// prints "foo"
+```
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/composer.json b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/composer.json
new file mode 100755
index 0000000..ec41a61
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "guzzlehttp/promises",
+ "description": "Guzzle promises library",
+ "keywords": ["promise"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "test-ci": "vendor/bin/phpunit --coverage-text"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/AggregateException.php b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/AggregateException.php
new file mode 100755
index 0000000..6a5690c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/AggregateException.php
@@ -0,0 +1,16 @@
+then(function ($v) { echo $v; });
+ *
+ * @param callable $generatorFn Generator function to wrap into a promise.
+ *
+ * @return Promise
+ * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
+ */
+final class Coroutine implements PromiseInterface
+{
+ /**
+ * @var PromiseInterface|null
+ */
+ private $currentPromise;
+
+ /**
+ * @var Generator
+ */
+ private $generator;
+
+ /**
+ * @var Promise
+ */
+ private $result;
+
+ public function __construct(callable $generatorFn)
+ {
+ $this->generator = $generatorFn();
+ $this->result = new Promise(function () {
+ while (isset($this->currentPromise)) {
+ $this->currentPromise->wait();
+ }
+ });
+ $this->nextCoroutine($this->generator->current());
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ return $this->result->then($onFulfilled, $onRejected);
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->result->otherwise($onRejected);
+ }
+
+ public function wait($unwrap = true)
+ {
+ return $this->result->wait($unwrap);
+ }
+
+ public function getState()
+ {
+ return $this->result->getState();
+ }
+
+ public function resolve($value)
+ {
+ $this->result->resolve($value);
+ }
+
+ public function reject($reason)
+ {
+ $this->result->reject($reason);
+ }
+
+ public function cancel()
+ {
+ $this->currentPromise->cancel();
+ $this->result->cancel();
+ }
+
+ private function nextCoroutine($yielded)
+ {
+ $this->currentPromise = promise_for($yielded)
+ ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
+ }
+
+ /**
+ * @internal
+ */
+ public function _handleSuccess($value)
+ {
+ unset($this->currentPromise);
+ try {
+ $next = $this->generator->send($value);
+ if ($this->generator->valid()) {
+ $this->nextCoroutine($next);
+ } else {
+ $this->result->resolve($value);
+ }
+ } catch (Exception $exception) {
+ $this->result->reject($exception);
+ } catch (Throwable $throwable) {
+ $this->result->reject($throwable);
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function _handleFailure($reason)
+ {
+ unset($this->currentPromise);
+ try {
+ $nextYield = $this->generator->throw(exception_for($reason));
+ // The throw was caught, so keep iterating on the coroutine
+ $this->nextCoroutine($nextYield);
+ } catch (Exception $exception) {
+ $this->result->reject($exception);
+ } catch (Throwable $throwable) {
+ $this->result->reject($throwable);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/EachPromise.php b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/EachPromise.php
new file mode 100755
index 0000000..d0ddf60
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/EachPromise.php
@@ -0,0 +1,229 @@
+iterable = iter_for($iterable);
+
+ if (isset($config['concurrency'])) {
+ $this->concurrency = $config['concurrency'];
+ }
+
+ if (isset($config['fulfilled'])) {
+ $this->onFulfilled = $config['fulfilled'];
+ }
+
+ if (isset($config['rejected'])) {
+ $this->onRejected = $config['rejected'];
+ }
+ }
+
+ public function promise()
+ {
+ if ($this->aggregate) {
+ return $this->aggregate;
+ }
+
+ try {
+ $this->createPromise();
+ $this->iterable->rewind();
+ $this->refillPending();
+ } catch (\Throwable $e) {
+ $this->aggregate->reject($e);
+ } catch (\Exception $e) {
+ $this->aggregate->reject($e);
+ }
+
+ return $this->aggregate;
+ }
+
+ private function createPromise()
+ {
+ $this->mutex = false;
+ $this->aggregate = new Promise(function () {
+ reset($this->pending);
+ if (empty($this->pending) && !$this->iterable->valid()) {
+ $this->aggregate->resolve(null);
+ return;
+ }
+
+ // Consume a potentially fluctuating list of promises while
+ // ensuring that indexes are maintained (precluding array_shift).
+ while ($promise = current($this->pending)) {
+ next($this->pending);
+ $promise->wait();
+ if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+ }
+ });
+
+ // Clear the references when the promise is resolved.
+ $clearFn = function () {
+ $this->iterable = $this->concurrency = $this->pending = null;
+ $this->onFulfilled = $this->onRejected = null;
+ };
+
+ $this->aggregate->then($clearFn, $clearFn);
+ }
+
+ private function refillPending()
+ {
+ if (!$this->concurrency) {
+ // Add all pending promises.
+ while ($this->addPending() && $this->advanceIterator());
+ return;
+ }
+
+ // Add only up to N pending promises.
+ $concurrency = is_callable($this->concurrency)
+ ? call_user_func($this->concurrency, count($this->pending))
+ : $this->concurrency;
+ $concurrency = max($concurrency - count($this->pending), 0);
+ // Concurrency may be set to 0 to disallow new promises.
+ if (!$concurrency) {
+ return;
+ }
+ // Add the first pending promise.
+ $this->addPending();
+ // Note this is special handling for concurrency=1 so that we do
+ // not advance the iterator after adding the first promise. This
+ // helps work around issues with generators that might not have the
+ // next value to yield until promise callbacks are called.
+ while (--$concurrency
+ && $this->advanceIterator()
+ && $this->addPending());
+ }
+
+ private function addPending()
+ {
+ if (!$this->iterable || !$this->iterable->valid()) {
+ return false;
+ }
+
+ $promise = promise_for($this->iterable->current());
+ $idx = $this->iterable->key();
+
+ $this->pending[$idx] = $promise->then(
+ function ($value) use ($idx) {
+ if ($this->onFulfilled) {
+ call_user_func(
+ $this->onFulfilled, $value, $idx, $this->aggregate
+ );
+ }
+ $this->step($idx);
+ },
+ function ($reason) use ($idx) {
+ if ($this->onRejected) {
+ call_user_func(
+ $this->onRejected, $reason, $idx, $this->aggregate
+ );
+ }
+ $this->step($idx);
+ }
+ );
+
+ return true;
+ }
+
+ private function advanceIterator()
+ {
+ // Place a lock on the iterator so that we ensure to not recurse,
+ // preventing fatal generator errors.
+ if ($this->mutex) {
+ return false;
+ }
+
+ $this->mutex = true;
+
+ try {
+ $this->iterable->next();
+ $this->mutex = false;
+ return true;
+ } catch (\Throwable $e) {
+ $this->aggregate->reject($e);
+ $this->mutex = false;
+ return false;
+ } catch (\Exception $e) {
+ $this->aggregate->reject($e);
+ $this->mutex = false;
+ return false;
+ }
+ }
+
+ private function step($idx)
+ {
+ // If the promise was already resolved, then ignore this step.
+ if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+
+ unset($this->pending[$idx]);
+
+ // Only refill pending promises if we are not locked, preventing the
+ // EachPromise to recursively invoke the provided iterator, which
+ // cause a fatal error: "Cannot resume an already running generator"
+ if ($this->advanceIterator() && !$this->checkIfFinished()) {
+ // Add more pending promises if possible.
+ $this->refillPending();
+ }
+ }
+
+ private function checkIfFinished()
+ {
+ if (!$this->pending && !$this->iterable->valid()) {
+ // Resolve the promise if there's nothing left to do.
+ $this->aggregate->resolve(null);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/FulfilledPromise.php
new file mode 100755
index 0000000..dbbeeb9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/FulfilledPromise.php
@@ -0,0 +1,82 @@
+value = $value;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ // Return itself if there is no onFulfilled function.
+ if (!$onFulfilled) {
+ return $this;
+ }
+
+ $queue = queue();
+ $p = new Promise([$queue, 'run']);
+ $value = $this->value;
+ $queue->add(static function () use ($p, $value, $onFulfilled) {
+ if ($p->getState() === self::PENDING) {
+ try {
+ $p->resolve($onFulfilled($value));
+ } catch (\Throwable $e) {
+ $p->reject($e);
+ } catch (\Exception $e) {
+ $p->reject($e);
+ }
+ }
+ });
+
+ return $p;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true, $defaultDelivery = null)
+ {
+ return $unwrap ? $this->value : null;
+ }
+
+ public function getState()
+ {
+ return self::FULFILLED;
+ }
+
+ public function resolve($value)
+ {
+ if ($value !== $this->value) {
+ throw new \LogicException("Cannot resolve a fulfilled promise");
+ }
+ }
+
+ public function reject($reason)
+ {
+ throw new \LogicException("Cannot reject a fulfilled promise");
+ }
+
+ public function cancel()
+ {
+ // pass
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/Promise.php b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/Promise.php
new file mode 100755
index 0000000..844ada0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/Promise.php
@@ -0,0 +1,280 @@
+waitFn = $waitFn;
+ $this->cancelFn = $cancelFn;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ if ($this->state === self::PENDING) {
+ $p = new Promise(null, [$this, 'cancel']);
+ $this->handlers[] = [$p, $onFulfilled, $onRejected];
+ $p->waitList = $this->waitList;
+ $p->waitList[] = $this;
+ return $p;
+ }
+
+ // Return a fulfilled promise and immediately invoke any callbacks.
+ if ($this->state === self::FULFILLED) {
+ return $onFulfilled
+ ? promise_for($this->result)->then($onFulfilled)
+ : promise_for($this->result);
+ }
+
+ // It's either cancelled or rejected, so return a rejected promise
+ // and immediately invoke any callbacks.
+ $rejection = rejection_for($this->result);
+ return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true)
+ {
+ $this->waitIfPending();
+
+ $inner = $this->result instanceof PromiseInterface
+ ? $this->result->wait($unwrap)
+ : $this->result;
+
+ if ($unwrap) {
+ if ($this->result instanceof PromiseInterface
+ || $this->state === self::FULFILLED
+ ) {
+ return $inner;
+ } else {
+ // It's rejected so "unwrap" and throw an exception.
+ throw exception_for($inner);
+ }
+ }
+ }
+
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ public function cancel()
+ {
+ if ($this->state !== self::PENDING) {
+ return;
+ }
+
+ $this->waitFn = $this->waitList = null;
+
+ if ($this->cancelFn) {
+ $fn = $this->cancelFn;
+ $this->cancelFn = null;
+ try {
+ $fn();
+ } catch (\Throwable $e) {
+ $this->reject($e);
+ } catch (\Exception $e) {
+ $this->reject($e);
+ }
+ }
+
+ // Reject the promise only if it wasn't rejected in a then callback.
+ if ($this->state === self::PENDING) {
+ $this->reject(new CancellationException('Promise has been cancelled'));
+ }
+ }
+
+ public function resolve($value)
+ {
+ $this->settle(self::FULFILLED, $value);
+ }
+
+ public function reject($reason)
+ {
+ $this->settle(self::REJECTED, $reason);
+ }
+
+ private function settle($state, $value)
+ {
+ if ($this->state !== self::PENDING) {
+ // Ignore calls with the same resolution.
+ if ($state === $this->state && $value === $this->result) {
+ return;
+ }
+ throw $this->state === $state
+ ? new \LogicException("The promise is already {$state}.")
+ : new \LogicException("Cannot change a {$this->state} promise to {$state}");
+ }
+
+ if ($value === $this) {
+ throw new \LogicException('Cannot fulfill or reject a promise with itself');
+ }
+
+ // Clear out the state of the promise but stash the handlers.
+ $this->state = $state;
+ $this->result = $value;
+ $handlers = $this->handlers;
+ $this->handlers = null;
+ $this->waitList = $this->waitFn = null;
+ $this->cancelFn = null;
+
+ if (!$handlers) {
+ return;
+ }
+
+ // If the value was not a settled promise or a thenable, then resolve
+ // it in the task queue using the correct ID.
+ if (!method_exists($value, 'then')) {
+ $id = $state === self::FULFILLED ? 1 : 2;
+ // It's a success, so resolve the handlers in the queue.
+ queue()->add(static function () use ($id, $value, $handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler($id, $value, $handler);
+ }
+ });
+ } elseif ($value instanceof Promise
+ && $value->getState() === self::PENDING
+ ) {
+ // We can just merge our handlers onto the next promise.
+ $value->handlers = array_merge($value->handlers, $handlers);
+ } else {
+ // Resolve the handlers when the forwarded promise is resolved.
+ $value->then(
+ static function ($value) use ($handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler(1, $value, $handler);
+ }
+ },
+ static function ($reason) use ($handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler(2, $reason, $handler);
+ }
+ }
+ );
+ }
+ }
+
+ /**
+ * Call a stack of handlers using a specific callback index and value.
+ *
+ * @param int $index 1 (resolve) or 2 (reject).
+ * @param mixed $value Value to pass to the callback.
+ * @param array $handler Array of handler data (promise and callbacks).
+ *
+ * @return array Returns the next group to resolve.
+ */
+ private static function callHandler($index, $value, array $handler)
+ {
+ /** @var PromiseInterface $promise */
+ $promise = $handler[0];
+
+ // The promise may have been cancelled or resolved before placing
+ // this thunk in the queue.
+ if ($promise->getState() !== self::PENDING) {
+ return;
+ }
+
+ try {
+ if (isset($handler[$index])) {
+ $promise->resolve($handler[$index]($value));
+ } elseif ($index === 1) {
+ // Forward resolution values as-is.
+ $promise->resolve($value);
+ } else {
+ // Forward rejections down the chain.
+ $promise->reject($value);
+ }
+ } catch (\Throwable $reason) {
+ $promise->reject($reason);
+ } catch (\Exception $reason) {
+ $promise->reject($reason);
+ }
+ }
+
+ private function waitIfPending()
+ {
+ if ($this->state !== self::PENDING) {
+ return;
+ } elseif ($this->waitFn) {
+ $this->invokeWaitFn();
+ } elseif ($this->waitList) {
+ $this->invokeWaitList();
+ } else {
+ // If there's not wait function, then reject the promise.
+ $this->reject('Cannot wait on a promise that has '
+ . 'no internal wait function. You must provide a wait '
+ . 'function when constructing the promise to be able to '
+ . 'wait on a promise.');
+ }
+
+ queue()->run();
+
+ if ($this->state === self::PENDING) {
+ $this->reject('Invoking the wait callback did not resolve the promise');
+ }
+ }
+
+ private function invokeWaitFn()
+ {
+ try {
+ $wfn = $this->waitFn;
+ $this->waitFn = null;
+ $wfn(true);
+ } catch (\Exception $reason) {
+ if ($this->state === self::PENDING) {
+ // The promise has not been resolved yet, so reject the promise
+ // with the exception.
+ $this->reject($reason);
+ } else {
+ // The promise was already resolved, so there's a problem in
+ // the application.
+ throw $reason;
+ }
+ }
+ }
+
+ private function invokeWaitList()
+ {
+ $waitList = $this->waitList;
+ $this->waitList = null;
+
+ foreach ($waitList as $result) {
+ while (true) {
+ $result->waitIfPending();
+
+ if ($result->result instanceof Promise) {
+ $result = $result->result;
+ } else {
+ if ($result->result instanceof PromiseInterface) {
+ $result->result->wait(false);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/PromiseInterface.php b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/PromiseInterface.php
new file mode 100755
index 0000000..8f5f4b9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/PromiseInterface.php
@@ -0,0 +1,93 @@
+reason = $reason;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ // If there's no onRejected callback then just return self.
+ if (!$onRejected) {
+ return $this;
+ }
+
+ $queue = queue();
+ $reason = $this->reason;
+ $p = new Promise([$queue, 'run']);
+ $queue->add(static function () use ($p, $reason, $onRejected) {
+ if ($p->getState() === self::PENDING) {
+ try {
+ // Return a resolved promise if onRejected does not throw.
+ $p->resolve($onRejected($reason));
+ } catch (\Throwable $e) {
+ // onRejected threw, so return a rejected promise.
+ $p->reject($e);
+ } catch (\Exception $e) {
+ // onRejected threw, so return a rejected promise.
+ $p->reject($e);
+ }
+ }
+ });
+
+ return $p;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true, $defaultDelivery = null)
+ {
+ if ($unwrap) {
+ throw exception_for($this->reason);
+ }
+ }
+
+ public function getState()
+ {
+ return self::REJECTED;
+ }
+
+ public function resolve($value)
+ {
+ throw new \LogicException("Cannot resolve a rejected promise");
+ }
+
+ public function reject($reason)
+ {
+ if ($reason !== $this->reason) {
+ throw new \LogicException("Cannot reject a rejected promise");
+ }
+ }
+
+ public function cancel()
+ {
+ // pass
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/RejectionException.php b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/RejectionException.php
new file mode 100755
index 0000000..07c1136
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/RejectionException.php
@@ -0,0 +1,47 @@
+reason = $reason;
+
+ $message = 'The promise was rejected';
+
+ if ($description) {
+ $message .= ' with reason: ' . $description;
+ } elseif (is_string($reason)
+ || (is_object($reason) && method_exists($reason, '__toString'))
+ ) {
+ $message .= ' with reason: ' . $this->reason;
+ } elseif ($reason instanceof \JsonSerializable) {
+ $message .= ' with reason: '
+ . json_encode($this->reason, JSON_PRETTY_PRINT);
+ }
+
+ parent::__construct($message);
+ }
+
+ /**
+ * Returns the rejection reason.
+ *
+ * @return mixed
+ */
+ public function getReason()
+ {
+ return $this->reason;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/TaskQueue.php b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/TaskQueue.php
new file mode 100755
index 0000000..6e8a2a0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/TaskQueue.php
@@ -0,0 +1,66 @@
+run();
+ */
+class TaskQueue implements TaskQueueInterface
+{
+ private $enableShutdown = true;
+ private $queue = [];
+
+ public function __construct($withShutdown = true)
+ {
+ if ($withShutdown) {
+ register_shutdown_function(function () {
+ if ($this->enableShutdown) {
+ // Only run the tasks if an E_ERROR didn't occur.
+ $err = error_get_last();
+ if (!$err || ($err['type'] ^ E_ERROR)) {
+ $this->run();
+ }
+ }
+ });
+ }
+ }
+
+ public function isEmpty()
+ {
+ return !$this->queue;
+ }
+
+ public function add(callable $task)
+ {
+ $this->queue[] = $task;
+ }
+
+ public function run()
+ {
+ /** @var callable $task */
+ while ($task = array_shift($this->queue)) {
+ $task();
+ }
+ }
+
+ /**
+ * The task queue will be run and exhausted by default when the process
+ * exits IFF the exit is not the result of a PHP E_ERROR error.
+ *
+ * You can disable running the automatic shutdown of the queue by calling
+ * this function. If you disable the task queue shutdown process, then you
+ * MUST either run the task queue (as a result of running your event loop
+ * or manually using the run() method) or wait on each outstanding promise.
+ *
+ * Note: This shutdown will occur before any destructors are triggered.
+ */
+ public function disableShutdown()
+ {
+ $this->enableShutdown = false;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/TaskQueueInterface.php
new file mode 100755
index 0000000..ac8306e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/TaskQueueInterface.php
@@ -0,0 +1,25 @@
+
+ * while ($eventLoop->isRunning()) {
+ * GuzzleHttp\Promise\queue()->run();
+ * }
+ *
+ *
+ * @param TaskQueueInterface $assign Optionally specify a new queue instance.
+ *
+ * @return TaskQueueInterface
+ */
+function queue(TaskQueueInterface $assign = null)
+{
+ static $queue;
+
+ if ($assign) {
+ $queue = $assign;
+ } elseif (!$queue) {
+ $queue = new TaskQueue();
+ }
+
+ return $queue;
+}
+
+/**
+ * Adds a function to run in the task queue when it is next `run()` and returns
+ * a promise that is fulfilled or rejected with the result.
+ *
+ * @param callable $task Task function to run.
+ *
+ * @return PromiseInterface
+ */
+function task(callable $task)
+{
+ $queue = queue();
+ $promise = new Promise([$queue, 'run']);
+ $queue->add(function () use ($task, $promise) {
+ try {
+ $promise->resolve($task());
+ } catch (\Throwable $e) {
+ $promise->reject($e);
+ } catch (\Exception $e) {
+ $promise->reject($e);
+ }
+ });
+
+ return $promise;
+}
+
+/**
+ * Creates a promise for a value if the value is not a promise.
+ *
+ * @param mixed $value Promise or value.
+ *
+ * @return PromiseInterface
+ */
+function promise_for($value)
+{
+ if ($value instanceof PromiseInterface) {
+ return $value;
+ }
+
+ // Return a Guzzle promise that shadows the given promise.
+ if (method_exists($value, 'then')) {
+ $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
+ $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
+ $promise = new Promise($wfn, $cfn);
+ $value->then([$promise, 'resolve'], [$promise, 'reject']);
+ return $promise;
+ }
+
+ return new FulfilledPromise($value);
+}
+
+/**
+ * Creates a rejected promise for a reason if the reason is not a promise. If
+ * the provided reason is a promise, then it is returned as-is.
+ *
+ * @param mixed $reason Promise or reason.
+ *
+ * @return PromiseInterface
+ */
+function rejection_for($reason)
+{
+ if ($reason instanceof PromiseInterface) {
+ return $reason;
+ }
+
+ return new RejectedPromise($reason);
+}
+
+/**
+ * Create an exception for a rejected promise value.
+ *
+ * @param mixed $reason
+ *
+ * @return \Exception|\Throwable
+ */
+function exception_for($reason)
+{
+ return $reason instanceof \Exception || $reason instanceof \Throwable
+ ? $reason
+ : new RejectionException($reason);
+}
+
+/**
+ * Returns an iterator for the given value.
+ *
+ * @param mixed $value
+ *
+ * @return \Iterator
+ */
+function iter_for($value)
+{
+ if ($value instanceof \Iterator) {
+ return $value;
+ } elseif (is_array($value)) {
+ return new \ArrayIterator($value);
+ } else {
+ return new \ArrayIterator([$value]);
+ }
+}
+
+/**
+ * Synchronously waits on a promise to resolve and returns an inspection state
+ * array.
+ *
+ * Returns a state associative array containing a "state" key mapping to a
+ * valid promise state. If the state of the promise is "fulfilled", the array
+ * will contain a "value" key mapping to the fulfilled value of the promise. If
+ * the promise is rejected, the array will contain a "reason" key mapping to
+ * the rejection reason of the promise.
+ *
+ * @param PromiseInterface $promise Promise or value.
+ *
+ * @return array
+ */
+function inspect(PromiseInterface $promise)
+{
+ try {
+ return [
+ 'state' => PromiseInterface::FULFILLED,
+ 'value' => $promise->wait()
+ ];
+ } catch (RejectionException $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
+ } catch (\Throwable $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ } catch (\Exception $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ }
+}
+
+/**
+ * Waits on all of the provided promises, but does not unwrap rejected promises
+ * as thrown exception.
+ *
+ * Returns an array of inspection state arrays.
+ *
+ * @param PromiseInterface[] $promises Traversable of promises to wait upon.
+ *
+ * @return array
+ * @see GuzzleHttp\Promise\inspect for the inspection state array format.
+ */
+function inspect_all($promises)
+{
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = inspect($promise);
+ }
+
+ return $results;
+}
+
+/**
+ * Waits on all of the provided promises and returns the fulfilled values.
+ *
+ * Returns an array that contains the value of each promise (in the same order
+ * the promises were provided). An exception is thrown if any of the promises
+ * are rejected.
+ *
+ * @param mixed $promises Iterable of PromiseInterface objects to wait on.
+ *
+ * @return array
+ * @throws \Exception on error
+ * @throws \Throwable on error in PHP >=7
+ */
+function unwrap($promises)
+{
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = $promise->wait();
+ }
+
+ return $results;
+}
+
+/**
+ * Given an array of promises, return a promise that is fulfilled when all the
+ * items in the array are fulfilled.
+ *
+ * The promise's fulfillment value is an array with fulfillment values at
+ * respective positions to the original array. If any promise in the array
+ * rejects, the returned promise is rejected with the rejection reason.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function all($promises)
+{
+ $results = [];
+ return each(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = $value;
+ },
+ function ($reason, $idx, Promise $aggregate) {
+ $aggregate->reject($reason);
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+}
+
+/**
+ * Initiate a competitive race between multiple promises or values (values will
+ * become immediately fulfilled promises).
+ *
+ * When count amount of promises have been fulfilled, the returned promise is
+ * fulfilled with an array that contains the fulfillment values of the winners
+ * in order of resolution.
+ *
+ * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException}
+ * if the number of fulfilled promises is less than the desired $count.
+ *
+ * @param int $count Total number of promises.
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function some($count, $promises)
+{
+ $results = [];
+ $rejections = [];
+
+ return each(
+ $promises,
+ function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
+ if ($p->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+ $results[$idx] = $value;
+ if (count($results) >= $count) {
+ $p->resolve(null);
+ }
+ },
+ function ($reason) use (&$rejections) {
+ $rejections[] = $reason;
+ }
+ )->then(
+ function () use (&$results, &$rejections, $count) {
+ if (count($results) !== $count) {
+ throw new AggregateException(
+ 'Not enough promises to fulfill count',
+ $rejections
+ );
+ }
+ ksort($results);
+ return array_values($results);
+ }
+ );
+}
+
+/**
+ * Like some(), with 1 as count. However, if the promise fulfills, the
+ * fulfillment value is not an array of 1 but the value directly.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function any($promises)
+{
+ return some(1, $promises)->then(function ($values) { return $values[0]; });
+}
+
+/**
+ * Returns a promise that is fulfilled when all of the provided promises have
+ * been fulfilled or rejected.
+ *
+ * The returned promise is fulfilled with an array of inspection state arrays.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ * @see GuzzleHttp\Promise\inspect for the inspection state array format.
+ */
+function settle($promises)
+{
+ $results = [];
+
+ return each(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
+ },
+ function ($reason, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+}
+
+/**
+ * Given an iterator that yields promises or values, returns a promise that is
+ * fulfilled with a null value when the iterator has been consumed or the
+ * aggregate promise has been fulfilled or rejected.
+ *
+ * $onFulfilled is a function that accepts the fulfilled value, iterator
+ * index, and the aggregate promise. The callback can invoke any necessary side
+ * effects and choose to resolve or reject the aggregate promise if needed.
+ *
+ * $onRejected is a function that accepts the rejection reason, iterator
+ * index, and the aggregate promise. The callback can invoke any necessary side
+ * effects and choose to resolve or reject the aggregate promise if needed.
+ *
+ * @param mixed $iterable Iterator or array to iterate over.
+ * @param callable $onFulfilled
+ * @param callable $onRejected
+ *
+ * @return PromiseInterface
+ */
+function each(
+ $iterable,
+ callable $onFulfilled = null,
+ callable $onRejected = null
+) {
+ return (new EachPromise($iterable, [
+ 'fulfilled' => $onFulfilled,
+ 'rejected' => $onRejected
+ ]))->promise();
+}
+
+/**
+ * Like each, but only allows a certain number of outstanding promises at any
+ * given time.
+ *
+ * $concurrency may be an integer or a function that accepts the number of
+ * pending promises and returns a numeric concurrency limit value to allow for
+ * dynamic a concurrency size.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ * @param callable $onRejected
+ *
+ * @return PromiseInterface
+ */
+function each_limit(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null,
+ callable $onRejected = null
+) {
+ return (new EachPromise($iterable, [
+ 'fulfilled' => $onFulfilled,
+ 'rejected' => $onRejected,
+ 'concurrency' => $concurrency
+ ]))->promise();
+}
+
+/**
+ * Like each_limit, but ensures that no promise in the given $iterable argument
+ * is rejected. If any promise is rejected, then the aggregate promise is
+ * rejected with the encountered rejection.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ *
+ * @return PromiseInterface
+ */
+function each_limit_all(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null
+) {
+ return each_limit(
+ $iterable,
+ $concurrency,
+ $onFulfilled,
+ function ($reason, $idx, PromiseInterface $aggregate) {
+ $aggregate->reject($reason);
+ }
+ );
+}
+
+/**
+ * Returns true if a promise is fulfilled.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_fulfilled(PromiseInterface $promise)
+{
+ return $promise->getState() === PromiseInterface::FULFILLED;
+}
+
+/**
+ * Returns true if a promise is rejected.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_rejected(PromiseInterface $promise)
+{
+ return $promise->getState() === PromiseInterface::REJECTED;
+}
+
+/**
+ * Returns true if a promise is fulfilled or rejected.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_settled(PromiseInterface $promise)
+{
+ return $promise->getState() !== PromiseInterface::PENDING;
+}
+
+/**
+ * @see Coroutine
+ *
+ * @param callable $generatorFn
+ *
+ * @return PromiseInterface
+ */
+function coroutine(callable $generatorFn)
+{
+ return new Coroutine($generatorFn);
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/functions_include.php b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/functions_include.php
new file mode 100755
index 0000000..34cd171
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/promises/src/functions_include.php
@@ -0,0 +1,6 @@
+withPath('foo')->withHost('example.com')` will throw an exception
+ because the path of a URI with an authority must start with a slash "/" or be empty
+ - `(new Uri())->withScheme('http')` will return `'http://localhost'`
+
+### Deprecated
+
+- `Uri::resolve` in favor of `UriResolver::resolve`
+- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`
+
+### Fixed
+
+- `Stream::read` when length parameter <= 0.
+- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
+- `ServerRequest::getUriFromGlobals` when `Host` header contains port.
+- Compatibility of URIs with `file` scheme and empty host.
+
+
+## [1.3.1] - 2016-06-25
+
+### Fixed
+
+- `Uri::__toString` for network path references, e.g. `//example.org`.
+- Missing lowercase normalization for host.
+- Handling of URI components in case they are `'0'` in a lot of places,
+ e.g. as a user info password.
+- `Uri::withAddedHeader` to correctly merge headers with different case.
+- Trimming of header values in `Uri::withAddedHeader`. Header values may
+ be surrounded by whitespace which should be ignored according to RFC 7230
+ Section 3.2.4. This does not apply to header names.
+- `Uri::withAddedHeader` with an array of header values.
+- `Uri::resolve` when base path has no slash and handling of fragment.
+- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the
+ key/value both in encoded as well as decoded form to those methods. This is
+ consistent with withPath, withQuery etc.
+- `ServerRequest::withoutAttribute` when attribute value is null.
+
+
+## [1.3.0] - 2016-04-13
+
+### Added
+
+- Remaining interfaces needed for full PSR7 compatibility
+ (ServerRequestInterface, UploadedFileInterface, etc.).
+- Support for stream_for from scalars.
+
+### Changed
+
+- Can now extend Uri.
+
+### Fixed
+- A bug in validating request methods by making it more permissive.
+
+
+## [1.2.3] - 2016-02-18
+
+### Fixed
+
+- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
+ streams, which can sometimes return fewer bytes than requested with `fread`.
+- Handling of gzipped responses with FNAME headers.
+
+
+## [1.2.2] - 2016-01-22
+
+### Added
+
+- Support for URIs without any authority.
+- Support for HTTP 451 'Unavailable For Legal Reasons.'
+- Support for using '0' as a filename.
+- Support for including non-standard ports in Host headers.
+
+
+## [1.2.1] - 2015-11-02
+
+### Changes
+
+- Now supporting negative offsets when seeking to SEEK_END.
+
+
+## [1.2.0] - 2015-08-15
+
+### Changed
+
+- Body as `"0"` is now properly added to a response.
+- Now allowing forward seeking in CachingStream.
+- Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+- functions.php is now conditionally required.
+- user-info is no longer dropped when resolving URIs.
+
+
+## [1.1.0] - 2015-06-24
+
+### Changed
+
+- URIs can now be relative.
+- `multipart/form-data` headers are now overridden case-insensitively.
+- URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+- A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
+
+
+
+[Unreleased]: https://github.com/guzzle/psr7/compare/1.6.0...HEAD
+[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0
+[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2
+[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1
+[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0
+[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2
+[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1
+[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0
+[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1
+[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0
+[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3
+[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2
+[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1
+[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0
+[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/LICENSE b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/LICENSE
new file mode 100755
index 0000000..581d95f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/README.md b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/README.md
new file mode 100755
index 0000000..c60a6a3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/README.md
@@ -0,0 +1,745 @@
+# PSR-7 Message Implementation
+
+This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing.
+
+
+[](https://travis-ci.org/guzzle/psr7)
+
+
+# Stream implementation
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## AppendStream
+
+`GuzzleHttp\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use GuzzleHttp\Psr7;
+
+$a = Psr7\stream_for('abc, ');
+$b = Psr7\stream_for('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\stream_for(' Above all listen to me'));
+
+echo $composed; // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`GuzzleHttp\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use GuzzleHttp\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`GuzzleHttp\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use GuzzleHttp\Psr7;
+
+// Create an empty stream
+$stream = Psr7\stream_for();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$dropping->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`GuzzleHttp\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing
+to create a concrete class for a simple extension point.
+
+```php
+
+use GuzzleHttp\Psr7;
+
+$stream = Psr7\stream_for('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`GuzzleHttp\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
+
+This stream decorator skips the first 10 bytes of the given stream to remove
+the gzip header, converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`GuzzleHttp\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use GuzzleHttp\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`GuzzleHttp\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`GuzzleHttp\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`GuzzleHttp\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`GuzzleHttp\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use GuzzleHttp\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ call_user_func($this->callback);
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use GuzzleHttp\Psr7\StreamWrapper;
+
+$stream = GuzzleHttp\Psr7\stream_for('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Function API
+
+There are various functions available under the `GuzzleHttp\Psr7` namespace.
+
+
+## `function str`
+
+`function str(MessageInterface $message)`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com');
+echo GuzzleHttp\Psr7\str($request);
+```
+
+
+## `function uri_for`
+
+`function uri_for($uri)`
+
+This function accepts a string or `Psr\Http\Message\UriInterface` and returns a
+UriInterface for the given value. If the value is already a `UriInterface`, it
+is returned as-is.
+
+```php
+$uri = GuzzleHttp\Psr7\uri_for('http://example.com');
+assert($uri === GuzzleHttp\Psr7\uri_for($uri));
+```
+
+
+## `function stream_for`
+
+`function stream_for($resource = '', array $options = [])`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+* - metadata: Array of custom metadata.
+* - size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = GuzzleHttp\Psr7\stream_for('foo');
+$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r'));
+
+$generator = function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = GuzzleHttp\Psr7\stream_for($generator(100));
+```
+
+
+## `function parse_header`
+
+`function parse_header($header)`
+
+Parse an array of header values containing ";" separated data into an array of
+associative arrays representing the header key value pair data of the header.
+When a parameter does not contain a value, but just contains a key, this
+function will inject a key with a '' string value.
+
+
+## `function normalize_header`
+
+`function normalize_header($header)`
+
+Converts an array of header values that may contain comma separated headers
+into an array of headers with no comma separated values.
+
+
+## `function modify_request`
+
+`function modify_request(RequestInterface $request, array $changes)`
+
+Clone and modify a request with the given changes. This method is useful for
+reducing the number of clones needed to mutate a message.
+
+The changes can be one of:
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `function rewind_body`
+
+`function rewind_body(MessageInterface $message)`
+
+Attempts to rewind a message body and throws an exception on failure. The body
+of the message will only be rewound if a call to `tell()` returns a value other
+than `0`.
+
+
+## `function try_fopen`
+
+`function try_fopen($filename, $mode)`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an error
+handler that checks for errors and throws an exception instead.
+
+
+## `function copy_to_string`
+
+`function copy_to_string(StreamInterface $stream, $maxLen = -1)`
+
+Copy the contents of a stream into a string until the given number of bytes
+have been read.
+
+
+## `function copy_to_stream`
+
+`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)`
+
+Copy the contents of a stream into another stream until the given number of
+bytes have been read.
+
+
+## `function hash`
+
+`function hash(StreamInterface $stream, $algo, $rawOutput = false)`
+
+Calculate a hash of a Stream. This method reads the entire stream to calculate
+a rolling hash (based on PHP's hash_init functions).
+
+
+## `function readline`
+
+`function readline(StreamInterface $stream, $maxLength = null)`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `function parse_request`
+
+`function parse_request($message)`
+
+Parses a request message string into a request object.
+
+
+## `function parse_response`
+
+`function parse_response($message)`
+
+Parses a response message string into a response object.
+
+
+## `function parse_query`
+
+`function parse_query($str, $urlEncoding = true)`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key value pair
+will become an array. This function does not parse nested PHP style arrays into
+an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
+`['foo[a]' => '1', 'foo[b]' => '2']`).
+
+
+## `function build_query`
+
+`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of parse_query() to build a query string.
+This function does not modify the provided keys when an array is encountered
+(like http_build_query would).
+
+
+## `function mimetype_from_filename`
+
+`function mimetype_from_filename($filename)`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `function mimetype_from_extension`
+
+`function mimetype_from_extension($extension)`
+
+Maps a file extensions to a mimetype.
+
+
+# Additional URI Methods
+
+Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
+this library also provides additional functionality when working with URIs as static methods.
+
+## URI Types
+
+An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
+An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
+the base URI. Relative references can be divided into several forms according to
+[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2):
+
+- network-path references, e.g. `//example.com/path`
+- absolute-path references, e.g. `/path`
+- relative-path references, e.g. `subpath`
+
+The following methods can be used to identify the type of the URI.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolute`
+
+`public static function isAbsolute(UriInterface $uri): bool`
+
+Whether the URI is absolute, i.e. it has a scheme.
+
+### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
+
+`public static function isNetworkPathReference(UriInterface $uri): bool`
+
+Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
+termed an network-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
+
+`public static function isAbsolutePathReference(UriInterface $uri): bool`
+
+Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
+termed an absolute-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
+
+`public static function isRelativePathReference(UriInterface $uri): bool`
+
+Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
+termed a relative-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
+
+`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
+
+Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
+fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
+(apart from its fragment) is considered a same-document reference.
+
+## URI Components
+
+Additional methods to work with URI components.
+
+### `GuzzleHttp\Psr7\Uri::isDefaultPort`
+
+`public static function isDefaultPort(UriInterface $uri): bool`
+
+Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
+or the standard port. This method can be used independently of the implementation.
+
+### `GuzzleHttp\Psr7\Uri::composeComponents`
+
+`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
+
+Composes a URI reference string from its various components according to
+[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called
+manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
+
+### `GuzzleHttp\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts): UriInterface`
+
+Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components.
+
+
+### `GuzzleHttp\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
+
+Creates a new URI with a specific query string value. Any existing query string values that exactly match the
+provided key are removed and replaced with the given key value pair. A value of null will set the query string
+key without a value, e.g. "key" instead of "key=value".
+
+### `GuzzleHttp\Psr7\Uri::withQueryValues`
+
+`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface`
+
+Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an
+associative array of key => value.
+
+### `GuzzleHttp\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
+
+Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
+provided key are removed.
+
+## Reference Resolution
+
+`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
+to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers
+do when resolving a link in a website based on the current request URI.
+
+### `GuzzleHttp\Psr7\UriResolver::resolve`
+
+`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
+
+Converts the relative URI into a new URI that is resolved against the base URI.
+
+### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
+
+`public static function removeDotSegments(string $path): string`
+
+Removes dot segments from a path and returns the new path according to
+[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4).
+
+### `GuzzleHttp\Psr7\UriResolver::relativize`
+
+`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
+
+Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
+
+```php
+(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+```
+
+One use-case is to use the current request URI as base URI and then generate relative links in your documents
+to reduce the document size or offer self-contained downloadable document archives.
+
+```php
+$base = new Uri('http://example.com/a/b/');
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+```
+
+## Normalization and Comparison
+
+`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
+[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6).
+
+### `GuzzleHttp\Psr7\UriNormalizer::normalize`
+
+`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
+
+Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
+This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
+of normalizations to apply. The following normalizations are available:
+
+- `UriNormalizer::PRESERVING_NORMALIZATIONS`
+
+ Default normalizations which only include the ones that preserve semantics.
+
+- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
+
+ All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
+
+ Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b`
+
+- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
+
+ Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
+ ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
+ not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
+ characters by URI normalizers.
+
+ Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/`
+
+- `UriNormalizer::CONVERT_EMPTY_PATH`
+
+ Converts the empty path to "/" for http and https URIs.
+
+ Example: `http://example.org` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DEFAULT_HOST`
+
+ Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
+ "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
+ RFC 3986.
+
+ Example: `file://localhost/myfile` → `file:///myfile`
+
+- `UriNormalizer::REMOVE_DEFAULT_PORT`
+
+ Removes the default port of the given URI scheme from the URI.
+
+ Example: `http://example.org:80/` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DOT_SEGMENTS`
+
+ Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
+ change the semantics of the URI reference.
+
+ Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html`
+
+- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
+
+ Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
+ and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
+ may change the semantics. Encoded slashes (%2F) are not removed.
+
+ Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html`
+
+- `UriNormalizer::SORT_QUERY_PARAMETERS`
+
+ Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
+ significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
+ of the URI.
+
+ Example: `?lang=en&article=fred` → `?article=fred&lang=en`
+
+### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
+
+`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
+
+Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
+`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
+This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
+equivalence or difference of relative references does not mean anything.
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/composer.json b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/composer.json
new file mode 100755
index 0000000..168a055
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/composer.json
@@ -0,0 +1,49 @@
+{
+ "name": "guzzlehttp/psr7",
+ "type": "library",
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": ["request", "response", "message", "stream", "http", "uri", "url", "psr-7"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8",
+ "ext-zlib": "*"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "suggest": {
+ "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\Psr7\\": "tests/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6-dev"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/AppendStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/AppendStream.php
new file mode 100755
index 0000000..472a0d6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/AppendStream.php
@@ -0,0 +1,241 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream)
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ *
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = [];
+ }
+
+ /**
+ * Detaches each attached stream.
+ *
+ * Returns null as it's not clear which underlying stream resource to return.
+ *
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->detach();
+ }
+
+ $this->streams = [];
+ }
+
+ public function tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ *
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof()
+ {
+ return !$this->streams ||
+ ($this->current >= count($this->streams) - 1 &&
+ $this->streams[$this->current]->eof());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ *
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ . $i . ' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ *
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ $this->current++;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ // Using a loose comparison here to match on '', false, and null
+ if ($result == null) {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key ? null : [];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/BufferStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/BufferStream.php
new file mode 100755
index 0000000..af4d4c2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/BufferStream.php
@@ -0,0 +1,137 @@
+hwm = $hwm;
+ }
+
+ public function __toString()
+ {
+ return $this->getContents();
+ }
+
+ public function getContents()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+ }
+
+ public function getSize()
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return true;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof()
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell()
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length)
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string)
+ {
+ $this->buffer .= $string;
+
+ // TODO: What should happen here?
+ if (strlen($this->buffer) >= $this->hwm) {
+ return false;
+ }
+
+ return strlen($string);
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key == 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : [];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/CachingStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/CachingStream.php
new file mode 100755
index 0000000..ed68f08
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/CachingStream.php
@@ -0,0 +1,138 @@
+remoteStream = $stream;
+ $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
+ }
+
+ public function getSize()
+ {
+ return max($this->stream->getSize(), $this->remoteStream->getSize());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence == SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence == SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence == SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ $byte = $size + $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // Read the remoteStream until we have read in at least the amount
+ // of bytes requested, or we reach the end of the file.
+ while ($diff > 0 && !$this->remoteStream->eof()) {
+ $this->read($diff);
+ $diff = $byte - $this->stream->getSize();
+ }
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string)
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ $this->remoteStream->close() && $this->stream->close();
+ }
+
+ private function cacheEntireStream()
+ {
+ $target = new FnStream(['write' => 'strlen']);
+ copy_to_stream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/DroppingStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/DroppingStream.php
new file mode 100755
index 0000000..8935c80
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/DroppingStream.php
@@ -0,0 +1,42 @@
+stream = $stream;
+ $this->maxLength = $maxLength;
+ }
+
+ public function write($string)
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/FnStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/FnStream.php
new file mode 100755
index 0000000..73daea6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/FnStream.php
@@ -0,0 +1,158 @@
+methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_' . $name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ * @throws \BadMethodCallException
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ . '() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ call_user_func($this->_fn_close);
+ }
+ }
+
+ /**
+ * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
+ * @throws \LogicException
+ */
+ public function __wakeup()
+ {
+ throw new \LogicException('FnStream should never be unserialized');
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
+ $methods[$diff] = [$stream, $diff];
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString()
+ {
+ return call_user_func($this->_fn___toString);
+ }
+
+ public function close()
+ {
+ return call_user_func($this->_fn_close);
+ }
+
+ public function detach()
+ {
+ return call_user_func($this->_fn_detach);
+ }
+
+ public function getSize()
+ {
+ return call_user_func($this->_fn_getSize);
+ }
+
+ public function tell()
+ {
+ return call_user_func($this->_fn_tell);
+ }
+
+ public function eof()
+ {
+ return call_user_func($this->_fn_eof);
+ }
+
+ public function isSeekable()
+ {
+ return call_user_func($this->_fn_isSeekable);
+ }
+
+ public function rewind()
+ {
+ call_user_func($this->_fn_rewind);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ call_user_func($this->_fn_seek, $offset, $whence);
+ }
+
+ public function isWritable()
+ {
+ return call_user_func($this->_fn_isWritable);
+ }
+
+ public function write($string)
+ {
+ return call_user_func($this->_fn_write, $string);
+ }
+
+ public function isReadable()
+ {
+ return call_user_func($this->_fn_isReadable);
+ }
+
+ public function read($length)
+ {
+ return call_user_func($this->_fn_read, $length);
+ }
+
+ public function getContents()
+ {
+ return call_user_func($this->_fn_getContents);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return call_user_func($this->_fn_getMetadata, $key);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/InflateStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/InflateStream.php
new file mode 100755
index 0000000..5e4f602
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/InflateStream.php
@@ -0,0 +1,52 @@
+read(10);
+ $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header);
+ // Skip the header, that is 10 + length of filename + 1 (nil) bytes
+ $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
+ $resource = StreamWrapper::getResource($stream);
+ stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
+ $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
+ }
+
+ /**
+ * @param StreamInterface $stream
+ * @param $header
+ * @return int
+ */
+ private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
+ {
+ $filename_header_length = 0;
+
+ if (substr(bin2hex($header), 6, 2) === '08') {
+ // we have a filename, read until nil
+ $filename_header_length = 1;
+ while ($stream->read(1) !== chr(0)) {
+ $filename_header_length++;
+ }
+ }
+
+ return $filename_header_length;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
new file mode 100755
index 0000000..02cec3a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
@@ -0,0 +1,39 @@
+filename = $filename;
+ $this->mode = $mode;
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream()
+ {
+ return stream_for(try_fopen($this->filename, $this->mode));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/LimitStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/LimitStream.php
new file mode 100755
index 0000000..e4f239e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/LimitStream.php
@@ -0,0 +1,155 @@
+stream = $stream;
+ $this->setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof()
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit == -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit == -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset %s with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset($offset)
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length)
+ {
+ if ($this->limit == -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/MessageTrait.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/MessageTrait.php
new file mode 100755
index 0000000..a7966d1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/MessageTrait.php
@@ -0,0 +1,213 @@
+ array of values */
+ private $headers = [];
+
+ /** @var array Map of lowercase header name => original name at registration */
+ private $headerNames = [];
+
+ /** @var string */
+ private $protocol = '1.1';
+
+ /** @var StreamInterface */
+ private $stream;
+
+ public function getProtocolVersion()
+ {
+ return $this->protocol;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+ return $new;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ public function hasHeader($header)
+ {
+ return isset($this->headerNames[strtolower($header)]);
+ }
+
+ public function getHeader($header)
+ {
+ $header = strtolower($header);
+
+ if (!isset($this->headerNames[$header])) {
+ return [];
+ }
+
+ $header = $this->headerNames[$header];
+
+ return $this->headers[$header];
+ }
+
+ public function getHeaderLine($header)
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value)
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ unset($new->headers[$new->headerNames[$normalized]]);
+ }
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value)
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $new->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+ }
+
+ return $new;
+ }
+
+ public function withoutHeader($header)
+ {
+ $normalized = strtolower($header);
+
+ if (!isset($this->headerNames[$normalized])) {
+ return $this;
+ }
+
+ $header = $this->headerNames[$normalized];
+
+ $new = clone $this;
+ unset($new->headers[$header], $new->headerNames[$normalized]);
+
+ return $new;
+ }
+
+ public function getBody()
+ {
+ if (!$this->stream) {
+ $this->stream = stream_for('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body)
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+ return $new;
+ }
+
+ private function setHeaders(array $headers)
+ {
+ $this->headerNames = $this->headers = [];
+ foreach ($headers as $header => $value) {
+ if (is_int($header)) {
+ // Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec
+ // and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass.
+ $header = (string) $header;
+ }
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+ if (isset($this->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $this->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $this->headerNames[$normalized] = $header;
+ $this->headers[$header] = $value;
+ }
+ }
+ }
+
+ private function normalizeHeaderValue($value)
+ {
+ if (!is_array($value)) {
+ return $this->trimHeaderValues([$value]);
+ }
+
+ if (count($value) === 0) {
+ throw new \InvalidArgumentException('Header value can not be an empty array.');
+ }
+
+ return $this->trimHeaderValues($value);
+ }
+
+ /**
+ * Trims whitespace from the header values.
+ *
+ * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
+ *
+ * header-field = field-name ":" OWS field-value OWS
+ * OWS = *( SP / HTAB )
+ *
+ * @param string[] $values Header values
+ *
+ * @return string[] Trimmed header values
+ *
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ */
+ private function trimHeaderValues(array $values)
+ {
+ return array_map(function ($value) {
+ if (!is_scalar($value) && null !== $value) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header value must be scalar or null but %s provided.',
+ is_object($value) ? get_class($value) : gettype($value)
+ ));
+ }
+
+ return trim((string) $value, " \t");
+ }, $values);
+ }
+
+ private function assertHeader($header)
+ {
+ if (!is_string($header)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header name must be a string but %s provided.',
+ is_object($header) ? get_class($header) : gettype($header)
+ ));
+ }
+
+ if ($header === '') {
+ throw new \InvalidArgumentException('Header name can not be empty.');
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/MultipartStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/MultipartStream.php
new file mode 100755
index 0000000..c0fd584
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/MultipartStream.php
@@ -0,0 +1,153 @@
+boundary = $boundary ?: sha1(uniqid('', true));
+ $this->stream = $this->createStream($elements);
+ }
+
+ /**
+ * Get the boundary
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ */
+ private function getHeaders(array $headers)
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements)
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(stream_for("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element)
+ {
+ foreach (['contents', 'name'] as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = stream_for($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if (substr($uri, 0, 6) !== 'php://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ list($body, $headers) = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ isset($element['filename']) ? $element['filename'] : null,
+ isset($element['headers']) ? $element['headers'] : []
+ );
+
+ $stream->addStream(stream_for($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(stream_for("\r\n"));
+ }
+
+ /**
+ * @return array
+ */
+ private function createElement($name, StreamInterface $stream, $filename, array $headers)
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = $this->getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = ($filename === '0' || $filename)
+ ? sprintf('form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename))
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = $this->getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = $this->getHeader($headers, 'content-type');
+ if (!$type && ($filename === '0' || $filename)) {
+ if ($type = mimetype_from_filename($filename)) {
+ $headers['Content-Type'] = $type;
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ private function getHeader(array $headers, $key)
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower($k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/NoSeekStream.php
new file mode 100755
index 0000000..2332218
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/NoSeekStream.php
@@ -0,0 +1,22 @@
+source = $source;
+ $this->size = isset($options['size']) ? $options['size'] : null;
+ $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString()
+ {
+ try {
+ return copy_to_string($this);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function close()
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = false;
+ $this->source = null;
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ return $this->tellPos;
+ }
+
+ public function eof()
+ {
+ return !$this->source;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents()
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
+ }
+
+ private function pump($length)
+ {
+ if ($this->source) {
+ do {
+ $data = call_user_func($this->source, $length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Request.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Request.php
new file mode 100755
index 0000000..59f337d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Request.php
@@ -0,0 +1,151 @@
+assertMethod($method);
+ if (!($uri instanceof UriInterface)) {
+ $uri = new Uri($uri);
+ }
+
+ $this->method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $version;
+
+ if (!isset($this->headerNames['host'])) {
+ $this->updateHostFromUri();
+ }
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+ }
+
+ public function getRequestTarget()
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target == '') {
+ $target = '/';
+ }
+ if ($this->uri->getQuery() != '') {
+ $target .= '?' . $this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget)
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+ return $new;
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method)
+ {
+ $this->assertMethod($method);
+ $new = clone $this;
+ $new->method = strtoupper($method);
+ return $new;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false)
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost || !isset($this->headerNames['host'])) {
+ $new->updateHostFromUri();
+ }
+
+ return $new;
+ }
+
+ private function updateHostFromUri()
+ {
+ $host = $this->uri->getHost();
+
+ if ($host == '') {
+ return;
+ }
+
+ if (($port = $this->uri->getPort()) !== null) {
+ $host .= ':' . $port;
+ }
+
+ if (isset($this->headerNames['host'])) {
+ $header = $this->headerNames['host'];
+ } else {
+ $header = 'Host';
+ $this->headerNames['host'] = 'Host';
+ }
+ // Ensure Host is the first header.
+ // See: http://tools.ietf.org/html/rfc7230#section-5.4
+ $this->headers = [$header => [$host]] + $this->headers;
+ }
+
+ private function assertMethod($method)
+ {
+ if (!is_string($method) || $method === '') {
+ throw new \InvalidArgumentException('Method must be a non-empty string.');
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Response.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Response.php
new file mode 100755
index 0000000..e7e04d8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Response.php
@@ -0,0 +1,154 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 511 => 'Network Authentication Required',
+ ];
+
+ /** @var string */
+ private $reasonPhrase = '';
+
+ /** @var int */
+ private $statusCode = 200;
+
+ /**
+ * @param int $status Status code
+ * @param array $headers Response headers
+ * @param string|null|resource|StreamInterface $body Response body
+ * @param string $version Protocol version
+ * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
+ */
+ public function __construct(
+ $status = 200,
+ array $headers = [],
+ $body = null,
+ $version = '1.1',
+ $reason = null
+ ) {
+ $this->assertStatusCodeIsInteger($status);
+ $status = (int) $status;
+ $this->assertStatusCodeRange($status);
+
+ $this->statusCode = $status;
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+
+ $this->setHeaders($headers);
+ if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
+ $this->reasonPhrase = self::$phrases[$this->statusCode];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ $this->assertStatusCodeIsInteger($code);
+ $code = (int) $code;
+ $this->assertStatusCodeRange($code);
+
+ $new = clone $this;
+ $new->statusCode = $code;
+ if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
+ $reasonPhrase = self::$phrases[$new->statusCode];
+ }
+ $new->reasonPhrase = $reasonPhrase;
+ return $new;
+ }
+
+ private function assertStatusCodeIsInteger($statusCode)
+ {
+ if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) {
+ throw new \InvalidArgumentException('Status code must be an integer value.');
+ }
+ }
+
+ private function assertStatusCodeRange($statusCode)
+ {
+ if ($statusCode < 100 || $statusCode >= 600) {
+ throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.');
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Rfc7230.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Rfc7230.php
new file mode 100755
index 0000000..505e474
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Rfc7230.php
@@ -0,0 +1,18 @@
+@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
+ const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/ServerRequest.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/ServerRequest.php
new file mode 100755
index 0000000..1a09a6c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/ServerRequest.php
@@ -0,0 +1,376 @@
+serverParams = $serverParams;
+
+ parent::__construct($method, $uri, $headers, $body, $version);
+ }
+
+ /**
+ * Return an UploadedFile instance array.
+ *
+ * @param array $files A array which respect $_FILES structure
+ * @throws InvalidArgumentException for unrecognized values
+ * @return array
+ */
+ public static function normalizeFiles(array $files)
+ {
+ $normalized = [];
+
+ foreach ($files as $key => $value) {
+ if ($value instanceof UploadedFileInterface) {
+ $normalized[$key] = $value;
+ } elseif (is_array($value) && isset($value['tmp_name'])) {
+ $normalized[$key] = self::createUploadedFileFromSpec($value);
+ } elseif (is_array($value)) {
+ $normalized[$key] = self::normalizeFiles($value);
+ continue;
+ } else {
+ throw new InvalidArgumentException('Invalid value in files specification');
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Create and return an UploadedFile instance from a $_FILES specification.
+ *
+ * If the specification represents an array of values, this method will
+ * delegate to normalizeNestedFileSpec() and return that return value.
+ *
+ * @param array $value $_FILES struct
+ * @return array|UploadedFileInterface
+ */
+ private static function createUploadedFileFromSpec(array $value)
+ {
+ if (is_array($value['tmp_name'])) {
+ return self::normalizeNestedFileSpec($value);
+ }
+
+ return new UploadedFile(
+ $value['tmp_name'],
+ (int) $value['size'],
+ (int) $value['error'],
+ $value['name'],
+ $value['type']
+ );
+ }
+
+ /**
+ * Normalize an array of file specifications.
+ *
+ * Loops through all nested files and returns a normalized array of
+ * UploadedFileInterface instances.
+ *
+ * @param array $files
+ * @return UploadedFileInterface[]
+ */
+ private static function normalizeNestedFileSpec(array $files = [])
+ {
+ $normalizedFiles = [];
+
+ foreach (array_keys($files['tmp_name']) as $key) {
+ $spec = [
+ 'tmp_name' => $files['tmp_name'][$key],
+ 'size' => $files['size'][$key],
+ 'error' => $files['error'][$key],
+ 'name' => $files['name'][$key],
+ 'type' => $files['type'][$key],
+ ];
+ $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
+ }
+
+ return $normalizedFiles;
+ }
+
+ /**
+ * Return a ServerRequest populated with superglobals:
+ * $_GET
+ * $_POST
+ * $_COOKIE
+ * $_FILES
+ * $_SERVER
+ *
+ * @return ServerRequestInterface
+ */
+ public static function fromGlobals()
+ {
+ $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
+ $headers = getallheaders();
+ $uri = self::getUriFromGlobals();
+ $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+ $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
+
+ $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
+
+ return $serverRequest
+ ->withCookieParams($_COOKIE)
+ ->withQueryParams($_GET)
+ ->withParsedBody($_POST)
+ ->withUploadedFiles(self::normalizeFiles($_FILES));
+ }
+
+ private static function extractHostAndPortFromAuthority($authority)
+ {
+ $uri = 'http://'.$authority;
+ $parts = parse_url($uri);
+ if (false === $parts) {
+ return [null, null];
+ }
+
+ $host = isset($parts['host']) ? $parts['host'] : null;
+ $port = isset($parts['port']) ? $parts['port'] : null;
+
+ return [$host, $port];
+ }
+
+ /**
+ * Get a Uri populated with values from $_SERVER.
+ *
+ * @return UriInterface
+ */
+ public static function getUriFromGlobals()
+ {
+ $uri = new Uri('');
+
+ $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
+
+ $hasPort = false;
+ if (isset($_SERVER['HTTP_HOST'])) {
+ list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
+ if ($host !== null) {
+ $uri = $uri->withHost($host);
+ }
+
+ if ($port !== null) {
+ $hasPort = true;
+ $uri = $uri->withPort($port);
+ }
+ } elseif (isset($_SERVER['SERVER_NAME'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_NAME']);
+ } elseif (isset($_SERVER['SERVER_ADDR'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
+ }
+
+ if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
+ $uri = $uri->withPort($_SERVER['SERVER_PORT']);
+ }
+
+ $hasQuery = false;
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
+ $uri = $uri->withPath($requestUriParts[0]);
+ if (isset($requestUriParts[1])) {
+ $hasQuery = true;
+ $uri = $uri->withQuery($requestUriParts[1]);
+ }
+ }
+
+ if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
+ $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
+ }
+
+ return $uri;
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getServerParams()
+ {
+ return $this->serverParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getUploadedFiles()
+ {
+ return $this->uploadedFiles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->uploadedFiles = $uploadedFiles;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCookieParams()
+ {
+ return $this->cookieParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->cookieParams = $cookies;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttribute($attribute, $default = null)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $default;
+ }
+
+ return $this->attributes[$attribute];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withAttribute($attribute, $value)
+ {
+ $new = clone $this;
+ $new->attributes[$attribute] = $value;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withoutAttribute($attribute)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ unset($new->attributes[$attribute]);
+
+ return $new;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Stream.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Stream.php
new file mode 100755
index 0000000..d9e7409
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Stream.php
@@ -0,0 +1,267 @@
+size = $options['size'];
+ }
+
+ $this->customMetadata = isset($options['metadata'])
+ ? $options['metadata']
+ : [];
+
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']);
+ $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return (string) stream_get_contents($this->stream);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $contents = stream_get_contents($this->stream);
+
+ if ($contents === false) {
+ throw new \RuntimeException('Unable to read stream contents');
+ }
+
+ return $contents;
+ }
+
+ public function close()
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function eof()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ return feof($this->stream);
+ }
+
+ public function tell()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $whence = (int) $whence;
+
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ }
+ if (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ . $offset . ' with whence ' . var_export($whence, true));
+ }
+ }
+
+ public function read($length)
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+ if ($length < 0) {
+ throw new \RuntimeException('Length parameter cannot be negative');
+ }
+
+ if (0 === $length) {
+ return '';
+ }
+
+ $string = fread($this->stream, $length);
+ if (false === $string) {
+ throw new \RuntimeException('Unable to read from stream');
+ }
+
+ return $string;
+ }
+
+ public function write($string)
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : [];
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
new file mode 100755
index 0000000..daec6f5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,149 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @param string $name Name of the property (allows "stream" only).
+ *
+ * @return StreamInterface
+ */
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ $this->stream = $this->createStream();
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString()
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+ return $this->getContents();
+ } catch (\Exception $e) {
+ // Really, PHP? https://bugs.php.net/bug.php?id=53648
+ trigger_error('StreamDecorator::__toString exception: '
+ . (string) $e, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ $result = call_user_func_array([$this->stream, $method], $args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close()
+ {
+ $this->stream->close();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize()
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable()
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->stream->write($string);
+ }
+
+ /**
+ * Implement in subclasses to dynamically create streams when requested.
+ *
+ * @return StreamInterface
+ * @throws \BadMethodCallException
+ */
+ protected function createStream()
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/StreamWrapper.php
new file mode 100755
index 0000000..0f3a285
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/StreamWrapper.php
@@ -0,0 +1,161 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ . 'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, null, self::createStreamContext($stream));
+ }
+
+ /**
+ * Creates a stream context that can be used to open a stream as a php stream resource.
+ *
+ * @param StreamInterface $stream
+ *
+ * @return resource
+ */
+ public static function createStreamContext(StreamInterface $stream)
+ {
+ return stream_context_create([
+ 'guzzle' => ['stream' => $stream]
+ ]);
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register()
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read($count)
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write($data)
+ {
+ return (int) $this->stream->write($data);
+ }
+
+ public function stream_tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ public function stream_cast($cast_as)
+ {
+ $stream = clone($this->stream);
+
+ return $stream->detach();
+ }
+
+ public function stream_stat()
+ {
+ static $modeMap = [
+ 'r' => 33060,
+ 'rb' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188,
+ 'wb' => 33188
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+
+ public function url_stat($path, $flags)
+ {
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/UploadedFile.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/UploadedFile.php
new file mode 100755
index 0000000..e62bd5c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/UploadedFile.php
@@ -0,0 +1,316 @@
+setError($errorStatus);
+ $this->setSize($size);
+ $this->setClientFilename($clientFilename);
+ $this->setClientMediaType($clientMediaType);
+
+ if ($this->isOk()) {
+ $this->setStreamOrFile($streamOrFile);
+ }
+ }
+
+ /**
+ * Depending on the value set file or stream variable
+ *
+ * @param mixed $streamOrFile
+ * @throws InvalidArgumentException
+ */
+ private function setStreamOrFile($streamOrFile)
+ {
+ if (is_string($streamOrFile)) {
+ $this->file = $streamOrFile;
+ } elseif (is_resource($streamOrFile)) {
+ $this->stream = new Stream($streamOrFile);
+ } elseif ($streamOrFile instanceof StreamInterface) {
+ $this->stream = $streamOrFile;
+ } else {
+ throw new InvalidArgumentException(
+ 'Invalid stream or file provided for UploadedFile'
+ );
+ }
+ }
+
+ /**
+ * @param int $error
+ * @throws InvalidArgumentException
+ */
+ private function setError($error)
+ {
+ if (false === is_int($error)) {
+ throw new InvalidArgumentException(
+ 'Upload file error status must be an integer'
+ );
+ }
+
+ if (false === in_array($error, UploadedFile::$errors)) {
+ throw new InvalidArgumentException(
+ 'Invalid error status for UploadedFile'
+ );
+ }
+
+ $this->error = $error;
+ }
+
+ /**
+ * @param int $size
+ * @throws InvalidArgumentException
+ */
+ private function setSize($size)
+ {
+ if (false === is_int($size)) {
+ throw new InvalidArgumentException(
+ 'Upload file size must be an integer'
+ );
+ }
+
+ $this->size = $size;
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringOrNull($param)
+ {
+ return in_array(gettype($param), ['string', 'NULL']);
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringNotEmpty($param)
+ {
+ return is_string($param) && false === empty($param);
+ }
+
+ /**
+ * @param string|null $clientFilename
+ * @throws InvalidArgumentException
+ */
+ private function setClientFilename($clientFilename)
+ {
+ if (false === $this->isStringOrNull($clientFilename)) {
+ throw new InvalidArgumentException(
+ 'Upload file client filename must be a string or null'
+ );
+ }
+
+ $this->clientFilename = $clientFilename;
+ }
+
+ /**
+ * @param string|null $clientMediaType
+ * @throws InvalidArgumentException
+ */
+ private function setClientMediaType($clientMediaType)
+ {
+ if (false === $this->isStringOrNull($clientMediaType)) {
+ throw new InvalidArgumentException(
+ 'Upload file client media type must be a string or null'
+ );
+ }
+
+ $this->clientMediaType = $clientMediaType;
+ }
+
+ /**
+ * Return true if there is no upload error
+ *
+ * @return boolean
+ */
+ private function isOk()
+ {
+ return $this->error === UPLOAD_ERR_OK;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isMoved()
+ {
+ return $this->moved;
+ }
+
+ /**
+ * @throws RuntimeException if is moved or not ok
+ */
+ private function validateActive()
+ {
+ if (false === $this->isOk()) {
+ throw new RuntimeException('Cannot retrieve stream due to upload error');
+ }
+
+ if ($this->isMoved()) {
+ throw new RuntimeException('Cannot retrieve stream after it has already been moved');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws RuntimeException if the upload was not successful.
+ */
+ public function getStream()
+ {
+ $this->validateActive();
+
+ if ($this->stream instanceof StreamInterface) {
+ return $this->stream;
+ }
+
+ return new LazyOpenStream($this->file, 'r+');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/is_uploaded_file
+ * @see http://php.net/move_uploaded_file
+ * @param string $targetPath Path to which to move the uploaded file.
+ * @throws RuntimeException if the upload was not successful.
+ * @throws InvalidArgumentException if the $path specified is invalid.
+ * @throws RuntimeException on any error during the move operation, or on
+ * the second or subsequent call to the method.
+ */
+ public function moveTo($targetPath)
+ {
+ $this->validateActive();
+
+ if (false === $this->isStringNotEmpty($targetPath)) {
+ throw new InvalidArgumentException(
+ 'Invalid path provided for move operation; must be a non-empty string'
+ );
+ }
+
+ if ($this->file) {
+ $this->moved = php_sapi_name() == 'cli'
+ ? rename($this->file, $targetPath)
+ : move_uploaded_file($this->file, $targetPath);
+ } else {
+ copy_to_stream(
+ $this->getStream(),
+ new LazyOpenStream($targetPath, 'w')
+ );
+
+ $this->moved = true;
+ }
+
+ if (false === $this->moved) {
+ throw new RuntimeException(
+ sprintf('Uploaded file could not be moved to %s', $targetPath)
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return int|null The file size in bytes or null if unknown.
+ */
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/manual/en/features.file-upload.errors.php
+ * @return int One of PHP's UPLOAD_ERR_XXX constants.
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return string|null The filename sent by the client or null if none
+ * was provided.
+ */
+ public function getClientFilename()
+ {
+ return $this->clientFilename;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getClientMediaType()
+ {
+ return $this->clientMediaType;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Uri.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Uri.php
new file mode 100755
index 0000000..825a25e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/Uri.php
@@ -0,0 +1,760 @@
+ 80,
+ 'https' => 443,
+ 'ftp' => 21,
+ 'gopher' => 70,
+ 'nntp' => 119,
+ 'news' => 119,
+ 'telnet' => 23,
+ 'tn3270' => 23,
+ 'imap' => 143,
+ 'pop' => 110,
+ 'ldap' => 389,
+ ];
+
+ private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
+ private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
+ private static $replaceQuery = ['=' => '%3D', '&' => '%26'];
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /**
+ * @param string $uri URI to parse
+ */
+ public function __construct($uri = '')
+ {
+ // weak type check to also accept null until we can add scalar type hints
+ if ($uri != '') {
+ $parts = parse_url($uri);
+ if ($parts === false) {
+ throw new \InvalidArgumentException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ public function __toString()
+ {
+ return self::composeComponents(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->path,
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ /**
+ * Composes a URI reference string from its various components.
+ *
+ * Usually this method does not need to be called manually but instead is used indirectly via
+ * `Psr\Http\Message\UriInterface::__toString`.
+ *
+ * PSR-7 UriInterface treats an empty component the same as a missing component as
+ * getQuery(), getFragment() etc. always return a string. This explains the slight
+ * difference to RFC 3986 Section 5.3.
+ *
+ * Another adjustment is that the authority separator is added even when the authority is missing/empty
+ * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
+ * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
+ * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
+ * that format).
+ *
+ * @param string $scheme
+ * @param string $authority
+ * @param string $path
+ * @param string $query
+ * @param string $fragment
+ *
+ * @return string
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-5.3
+ */
+ public static function composeComponents($scheme, $authority, $path, $query, $fragment)
+ {
+ $uri = '';
+
+ // weak type checks to also accept null until we can add scalar type hints
+ if ($scheme != '') {
+ $uri .= $scheme . ':';
+ }
+
+ if ($authority != ''|| $scheme === 'file') {
+ $uri .= '//' . $authority;
+ }
+
+ $uri .= $path;
+
+ if ($query != '') {
+ $uri .= '?' . $query;
+ }
+
+ if ($fragment != '') {
+ $uri .= '#' . $fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether the URI has the default port of the current scheme.
+ *
+ * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
+ * independently of the implementation.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ */
+ public static function isDefaultPort(UriInterface $uri)
+ {
+ return $uri->getPort() === null
+ || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]);
+ }
+
+ /**
+ * Whether the URI is absolute, i.e. it has a scheme.
+ *
+ * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
+ * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
+ * to another URI, the base URI. Relative references can be divided into several forms:
+ * - network-path references, e.g. '//example.com/path'
+ * - absolute-path references, e.g. '/path'
+ * - relative-path references, e.g. 'subpath'
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @see Uri::isNetworkPathReference
+ * @see Uri::isAbsolutePathReference
+ * @see Uri::isRelativePathReference
+ * @link https://tools.ietf.org/html/rfc3986#section-4
+ */
+ public static function isAbsolute(UriInterface $uri)
+ {
+ return $uri->getScheme() !== '';
+ }
+
+ /**
+ * Whether the URI is a network-path reference.
+ *
+ * A relative reference that begins with two slash characters is termed an network-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isNetworkPathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === '' && $uri->getAuthority() !== '';
+ }
+
+ /**
+ * Whether the URI is a absolute-path reference.
+ *
+ * A relative reference that begins with a single slash character is termed an absolute-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isAbsolutePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && isset($uri->getPath()[0])
+ && $uri->getPath()[0] === '/';
+ }
+
+ /**
+ * Whether the URI is a relative-path reference.
+ *
+ * A relative reference that does not begin with a slash character is termed a relative-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isRelativePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
+ }
+
+ /**
+ * Whether the URI is a same-document reference.
+ *
+ * A same-document reference refers to a URI that is, aside from its fragment
+ * component, identical to the base URI. When no base URI is given, only an empty
+ * URI reference (apart from its fragment) is considered a same-document reference.
+ *
+ * @param UriInterface $uri The URI to check
+ * @param UriInterface|null $base An optional base URI to compare against
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.4
+ */
+ public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
+ {
+ if ($base !== null) {
+ $uri = UriResolver::resolve($base, $uri);
+
+ return ($uri->getScheme() === $base->getScheme())
+ && ($uri->getAuthority() === $base->getAuthority())
+ && ($uri->getPath() === $base->getPath())
+ && ($uri->getQuery() === $base->getQuery());
+ }
+
+ return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
+ }
+
+ /**
+ * Removes dot segments from a path and returns the new path.
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
+ * @see UriResolver::removeDotSegments
+ */
+ public static function removeDotSegments($path)
+ {
+ return UriResolver::removeDotSegments($path);
+ }
+
+ /**
+ * Converts the relative URI into a new URI that is resolved against the base URI.
+ *
+ * @param UriInterface $base Base URI
+ * @param string|UriInterface $rel Relative URI
+ *
+ * @return UriInterface
+ *
+ * @deprecated since version 1.4. Use UriResolver::resolve instead.
+ * @see UriResolver::resolve
+ */
+ public static function resolve(UriInterface $base, $rel)
+ {
+ if (!($rel instanceof UriInterface)) {
+ $rel = new self($rel);
+ }
+
+ return UriResolver::resolve($base, $rel);
+ }
+
+ /**
+ * Creates a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key to remove.
+ *
+ * @return UriInterface
+ */
+ public static function withoutQueryValue(UriInterface $uri, $key)
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * A value of null will set the query string key without a value, e.g. "key"
+ * instead of "key=value".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string|null $value Value to set
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValue(UriInterface $uri, $key, $value)
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ $result[] = self::generateQueryString($key, $value);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with multiple specific query string values.
+ *
+ * It has the same behavior as withQueryValue() but for an associative array of key => value.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param array $keyValueArray Associative array of key and values
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValues(UriInterface $uri, array $keyValueArray)
+ {
+ $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
+
+ foreach ($keyValueArray as $key => $value) {
+ $result[] = self::generateQueryString($key, $value);
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a URI from a hash of `parse_url` components.
+ *
+ * @param array $parts
+ *
+ * @return UriInterface
+ * @link http://php.net/manual/en/function.parse-url.php
+ *
+ * @throws \InvalidArgumentException If the components do not form a valid URI.
+ */
+ public static function fromParts(array $parts)
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ $uri->validateState();
+
+ return $uri;
+ }
+
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority()
+ {
+ $authority = $this->host;
+ if ($this->userInfo !== '') {
+ $authority = $this->userInfo . '@' . $authority;
+ }
+
+ if ($this->port !== null) {
+ $authority .= ':' . $this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo()
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme)
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null)
+ {
+ $info = $this->filterUserInfoComponent($user);
+ if ($password !== null) {
+ $info .= ':' . $this->filterUserInfoComponent($password);
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withHost($host)
+ {
+ $host = $this->filterHost($host);
+
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPort($port)
+ {
+ $port = $this->filterPort($port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPath($path)
+ {
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withQuery($query)
+ {
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+
+ return $new;
+ }
+
+ public function withFragment($fragment)
+ {
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+
+ return $new;
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param array $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts)
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user'])
+ ? $this->filterUserInfoComponent($parts['user'])
+ : '';
+ $this->host = isset($parts['host'])
+ ? $this->filterHost($parts['host'])
+ : '';
+ $this->port = isset($parts['port'])
+ ? $this->filterPort($parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
+ }
+
+ $this->removeDefaultPort();
+ }
+
+ /**
+ * @param string $scheme
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the scheme is invalid.
+ */
+ private function filterScheme($scheme)
+ {
+ if (!is_string($scheme)) {
+ throw new \InvalidArgumentException('Scheme must be a string');
+ }
+
+ return strtolower($scheme);
+ }
+
+ /**
+ * @param string $component
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the user info is invalid.
+ */
+ private function filterUserInfoComponent($component)
+ {
+ if (!is_string($component)) {
+ throw new \InvalidArgumentException('User info must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $component
+ );
+ }
+
+ /**
+ * @param string $host
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the host is invalid.
+ */
+ private function filterHost($host)
+ {
+ if (!is_string($host)) {
+ throw new \InvalidArgumentException('Host must be a string');
+ }
+
+ return strtolower($host);
+ }
+
+ /**
+ * @param int|null $port
+ *
+ * @return int|null
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($port)
+ {
+ if ($port === null) {
+ return null;
+ }
+
+ $port = (int) $port;
+ if (0 > $port || 0xffff < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
+ );
+ }
+
+ return $port;
+ }
+
+ /**
+ * @param UriInterface $uri
+ * @param array $keys
+ *
+ * @return array
+ */
+ private static function getFilteredQueryString(UriInterface $uri, array $keys)
+ {
+ $current = $uri->getQuery();
+
+ if ($current === '') {
+ return [];
+ }
+
+ $decodedKeys = array_map('rawurldecode', $keys);
+
+ return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
+ return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
+ });
+ }
+
+ /**
+ * @param string $key
+ * @param string|null $value
+ *
+ * @return string
+ */
+ private static function generateQueryString($key, $value)
+ {
+ // Query string separators ("=", "&") within the key or value need to be encoded
+ // (while preventing double-encoding) before setting the query string. All other
+ // chars that need percent-encoding will be encoded by withQuery().
+ $queryString = strtr($key, self::$replaceQuery);
+
+ if ($value !== null) {
+ $queryString .= '=' . strtr($value, self::$replaceQuery);
+ }
+
+ return $queryString;
+ }
+
+ private function removeDefaultPort()
+ {
+ if ($this->port !== null && self::isDefaultPort($this)) {
+ $this->port = null;
+ }
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the path is invalid.
+ */
+ private function filterPath($path)
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException('Path must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param string $str
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the query or fragment is invalid.
+ */
+ private function filterQueryAndFragment($str)
+ {
+ if (!is_string($str)) {
+ throw new \InvalidArgumentException('Query and fragment must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match)
+ {
+ return rawurlencode($match[0]);
+ }
+
+ private function validateState()
+ {
+ if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
+ $this->host = self::HTTP_DEFAULT_HOST;
+ }
+
+ if ($this->getAuthority() === '') {
+ if (0 === strpos($this->path, '//')) {
+ throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
+ }
+ if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
+ throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
+ }
+ } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
+ @trigger_error(
+ 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
+ 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
+ E_USER_DEPRECATED
+ );
+ $this->path = '/'. $this->path;
+ //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/UriNormalizer.php
new file mode 100755
index 0000000..384c29e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/UriNormalizer.php
@@ -0,0 +1,216 @@
+getPath() === '' &&
+ ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
+ ) {
+ $uri = $uri->withPath('/');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
+ $uri = $uri->withHost('');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
+ $uri = $uri->withPort(null);
+ }
+
+ if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
+ $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
+ }
+
+ if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
+ $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
+ }
+
+ if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
+ $queryKeyValues = explode('&', $uri->getQuery());
+ sort($queryKeyValues);
+ $uri = $uri->withQuery(implode('&', $queryKeyValues));
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether two URIs can be considered equivalent.
+ *
+ * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
+ * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
+ * resolved against the same base URI. If this is not the case, determination of equivalence or difference of
+ * relative references does not mean anything.
+ *
+ * @param UriInterface $uri1 An URI to compare
+ * @param UriInterface $uri2 An URI to compare
+ * @param int $normalizations A bitmask of normalizations to apply, see constants
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-6.1
+ */
+ public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS)
+ {
+ return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
+ }
+
+ private static function capitalizePercentEncoding(UriInterface $uri)
+ {
+ $regex = '/(?:%[A-Fa-f0-9]{2})++/';
+
+ $callback = function (array $match) {
+ return strtoupper($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private static function decodeUnreservedCharacters(UriInterface $uri)
+ {
+ $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
+
+ $callback = function (array $match) {
+ return rawurldecode($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/UriResolver.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/UriResolver.php
new file mode 100755
index 0000000..c1cb8a2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/UriResolver.php
@@ -0,0 +1,219 @@
+getScheme() != '') {
+ return $rel->withPath(self::removeDotSegments($rel->getPath()));
+ }
+
+ if ($rel->getAuthority() != '') {
+ $targetAuthority = $rel->getAuthority();
+ $targetPath = self::removeDotSegments($rel->getPath());
+ $targetQuery = $rel->getQuery();
+ } else {
+ $targetAuthority = $base->getAuthority();
+ if ($rel->getPath() === '') {
+ $targetPath = $base->getPath();
+ $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
+ } else {
+ if ($rel->getPath()[0] === '/') {
+ $targetPath = $rel->getPath();
+ } else {
+ if ($targetAuthority != '' && $base->getPath() === '') {
+ $targetPath = '/' . $rel->getPath();
+ } else {
+ $lastSlashPos = strrpos($base->getPath(), '/');
+ if ($lastSlashPos === false) {
+ $targetPath = $rel->getPath();
+ } else {
+ $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
+ }
+ }
+ }
+ $targetPath = self::removeDotSegments($targetPath);
+ $targetQuery = $rel->getQuery();
+ }
+ }
+
+ return new Uri(Uri::composeComponents(
+ $base->getScheme(),
+ $targetAuthority,
+ $targetPath,
+ $targetQuery,
+ $rel->getFragment()
+ ));
+ }
+
+ /**
+ * Returns the target URI as a relative reference from the base URI.
+ *
+ * This method is the counterpart to resolve():
+ *
+ * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+ *
+ * One use-case is to use the current request URI as base URI and then generate relative links in your documents
+ * to reduce the document size or offer self-contained downloadable document archives.
+ *
+ * $base = new Uri('http://example.com/a/b/');
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+ * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+ *
+ * This method also accepts a target that is already relative and will try to relativize it further. Only a
+ * relative-path reference will be returned as-is.
+ *
+ * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
+ *
+ * @param UriInterface $base Base URI
+ * @param UriInterface $target Target URI
+ *
+ * @return UriInterface The relative URI reference
+ */
+ public static function relativize(UriInterface $base, UriInterface $target)
+ {
+ if ($target->getScheme() !== '' &&
+ ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
+ ) {
+ return $target;
+ }
+
+ if (Uri::isRelativePathReference($target)) {
+ // As the target is already highly relative we return it as-is. It would be possible to resolve
+ // the target with `$target = self::resolve($base, $target);` and then try make it more relative
+ // by removing a duplicate query. But let's not do that automatically.
+ return $target;
+ }
+
+ if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
+ return $target->withScheme('');
+ }
+
+ // We must remove the path before removing the authority because if the path starts with two slashes, the URI
+ // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
+ // invalid.
+ $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
+
+ if ($base->getPath() !== $target->getPath()) {
+ return $emptyPathUri->withPath(self::getRelativePath($base, $target));
+ }
+
+ if ($base->getQuery() === $target->getQuery()) {
+ // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
+ return $emptyPathUri->withQuery('');
+ }
+
+ // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
+ // inherit the base query component when resolving.
+ if ($target->getQuery() === '') {
+ $segments = explode('/', $target->getPath());
+ $lastSegment = end($segments);
+
+ return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
+ }
+
+ return $emptyPathUri;
+ }
+
+ private static function getRelativePath(UriInterface $base, UriInterface $target)
+ {
+ $sourceSegments = explode('/', $base->getPath());
+ $targetSegments = explode('/', $target->getPath());
+ array_pop($sourceSegments);
+ $targetLastSegment = array_pop($targetSegments);
+ foreach ($sourceSegments as $i => $segment) {
+ if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
+ unset($sourceSegments[$i], $targetSegments[$i]);
+ } else {
+ break;
+ }
+ }
+ $targetSegments[] = $targetLastSegment;
+ $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);
+
+ // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
+ if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
+ $relativePath = "./$relativePath";
+ } elseif ('/' === $relativePath[0]) {
+ if ($base->getAuthority() != '' && $base->getPath() === '') {
+ // In this case an extra slash is added by resolve() automatically. So we must not add one here.
+ $relativePath = ".$relativePath";
+ } else {
+ $relativePath = "./$relativePath";
+ }
+ }
+
+ return $relativePath;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/functions.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/functions.php
new file mode 100755
index 0000000..8e6dafe
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/functions.php
@@ -0,0 +1,899 @@
+getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+}
+
+/**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
+ * returns a UriInterface for the given value. If the value is already a
+ * `UriInterface`, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @return UriInterface
+ * @throws \InvalidArgumentException
+ */
+function uri_for($uri)
+{
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ } elseif (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+}
+
+/**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return StreamInterface
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+function stream_for($resource = '', array $options = [])
+{
+ if (is_scalar($resource)) {
+ $stream = fopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new Stream($stream, $options);
+ }
+
+ switch (gettype($resource)) {
+ case 'resource':
+ return new Stream($resource, $options);
+ case 'object':
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return stream_for((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(fopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
+}
+
+/**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair
+ * data of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ *
+ * @return array Returns the parsed header values.
+ */
+function parse_header($header)
+{
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = [];
+
+ foreach (normalize_header($header) as $val) {
+ $part = [];
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+function normalize_header($header)
+{
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = [];
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Clone and modify a request with the given changes.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+function modify_request(RequestInterface $request, array $changes)
+{
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+
+ if ($port = $changes['uri']->getPort()) {
+ $standardPorts = ['http' => 80, 'https' => 443];
+ $scheme = $changes['uri']->getScheme();
+ if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
+ $changes['set_headers']['Host'] .= ':'.$port;
+ }
+ }
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = _caseless_remove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ if ($request instanceof ServerRequestInterface) {
+ return (new ServerRequest(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion(),
+ $request->getServerParams()
+ ))
+ ->withParsedBody($request->getParsedBody())
+ ->withQueryParams($request->getQueryParams())
+ ->withCookieParams($request->getCookieParams())
+ ->withUploadedFiles($request->getUploadedFiles());
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+}
+
+/**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()` returns a
+ * value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+function rewind_body(MessageInterface $message)
+{
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+}
+
+/**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ * @throws \RuntimeException if the file cannot be opened
+ */
+function try_fopen($filename, $mode)
+{
+ $ex = null;
+ set_error_handler(function () use ($filename, $mode, &$ex) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open %s using mode %s: %s',
+ $filename,
+ $mode,
+ func_get_args()[1]
+ ));
+ });
+
+ $handle = fopen($filename, $mode);
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+}
+
+/**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ * @return string
+ * @throws \RuntimeException on error.
+ */
+function copy_to_string(StreamInterface $stream, $maxLen = -1)
+{
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+}
+
+/**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+function copy_to_stream(
+ StreamInterface $source,
+ StreamInterface $dest,
+ $maxLen = -1
+) {
+ $bufferSize = 8192;
+
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read($bufferSize))) {
+ break;
+ }
+ }
+ } else {
+ $remaining = $maxLen;
+ while ($remaining > 0 && !$source->eof()) {
+ $buf = $source->read(min($bufferSize, $remaining));
+ $len = strlen($buf);
+ if (!$len) {
+ break;
+ }
+ $remaining -= $len;
+ $dest->write($buf);
+ }
+ }
+}
+
+/**
+ * Calculate a hash of a Stream
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ * @throws \RuntimeException on error.
+ */
+function hash(
+ StreamInterface $stream,
+ $algo,
+ $rawOutput = false
+) {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+}
+
+/**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string
+ */
+function readline(StreamInterface $stream, $maxLength = null)
+{
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ // Using a loose equality here to match on '' and false.
+ if (null == ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte === "\n" || ++$size === $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+}
+
+/**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ *
+ * @return Request
+ */
+function parse_request($message)
+{
+ $data = _parse_message($message);
+ $matches = [];
+ if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+}
+
+/**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ *
+ * @return Response
+ */
+function parse_response($message)
+{
+ $data = _parse_message($message);
+ // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
+ // between status-code and reason-phrase is required. But browsers accept
+ // responses without space and reason as well.
+ if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+
+ return new Response(
+ $parts[1],
+ $data['headers'],
+ $data['body'],
+ explode('/', $parts[0])[1],
+ isset($parts[2]) ? $parts[2] : null
+ );
+}
+
+/**
+ * Parse a query string into an associative array.
+ *
+ * If multiple values are found for the same key, the value of that key
+ * value pair will become an array. This function does not parse nested
+ * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
+ * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
+ *
+ * @param string $str Query string to parse
+ * @param int|bool $urlEncoding How the query string is encoded
+ *
+ * @return array
+ */
+function parse_query($str, $urlEncoding = true)
+{
+ $result = [];
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', $value));
+ };
+ } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) { return $str; };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!isset($result[$key])) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = [$result[$key]];
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of parse_query() to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like http_build_query would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
+ * to encode using RFC3986, or PHP_QUERY_RFC1738
+ * to encode using RFC1738.
+ * @return string
+ */
+function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
+{
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function ($str) { return $str; };
+ } elseif ($encoding === PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding === PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder($k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ if ($v !== null) {
+ $qs .= '=' . $encoder($v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ if ($vv !== null) {
+ $qs .= '=' . $encoder($vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+}
+
+/**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @param $filename
+ *
+ * @return null|string
+ */
+function mimetype_from_filename($filename)
+{
+ return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
+}
+
+/**
+ * Maps a file extensions to a mimetype.
+ *
+ * @param $extension string The file extension.
+ *
+ * @return string|null
+ * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
+ */
+function mimetype_from_extension($extension)
+{
+ static $mimetypes = [
+ '3gp' => 'video/3gpp',
+ '7z' => 'application/x-7z-compressed',
+ 'aac' => 'audio/x-aac',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'asc' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'atom' => 'application/atom+xml',
+ 'avi' => 'video/x-msvideo',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip2',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'deb' => 'application/x-debian-package',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dvi' => 'application/x-dvi',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'etx' => 'text/x-setext',
+ 'flac' => 'audio/flac',
+ 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif',
+ 'gz' => 'application/gzip',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ini' => 'text/plain',
+ 'iso' => 'application/x-iso9660-image',
+ 'jar' => 'application/java-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'latex' => 'application/x-latex',
+ 'log' => 'text/plain',
+ 'm4a' => 'audio/mp4',
+ 'm4v' => 'video/mp4',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mkv' => 'video/x-matroska',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'qt' => 'video/quicktime',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'torrent' => 'application/x-bittorrent',
+ 'ttf' => 'application/x-font-ttf',
+ 'txt' => 'text/plain',
+ 'wav' => 'audio/x-wav',
+ 'webm' => 'video/webm',
+ 'webp' => 'image/webp',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'video/x-ms-wmv',
+ 'woff' => 'application/x-font-woff',
+ 'wsdl' => 'application/wsdl+xml',
+ 'xbm' => 'image/x-xbitmap',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'zip' => 'application/zip',
+ ];
+
+ $extension = strtolower($extension);
+
+ return isset($mimetypes[$extension])
+ ? $mimetypes[$extension]
+ : null;
+}
+
+/**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ *
+ * @return array
+ * @internal
+ */
+function _parse_message($message)
+{
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ $message = ltrim($message, "\r\n");
+
+ $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
+
+ if ($messageParts === false || count($messageParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
+ }
+
+ list($rawHeaders, $body) = $messageParts;
+ $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
+ $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
+
+ if ($headerParts === false || count($headerParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing status line');
+ }
+
+ list($startLine, $rawHeaders) = $headerParts;
+
+ if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
+ // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
+ $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
+ }
+
+ /** @var array[] $headerLines */
+ $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
+
+ // If these aren't the same, then one line didn't match and there's an invalid header.
+ if ($count !== substr_count($rawHeaders, "\n")) {
+ // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
+ throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
+ }
+
+ throw new \InvalidArgumentException('Invalid header syntax');
+ }
+
+ $headers = [];
+
+ foreach ($headerLines as $headerLine) {
+ $headers[$headerLine[1]][] = $headerLine[2];
+ }
+
+ return [
+ 'start-line' => $startLine,
+ 'headers' => $headers,
+ 'body' => $body,
+ ];
+}
+
+/**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ *
+ * @return string
+ * @internal
+ */
+function _parse_request_uri($path, array $headers)
+{
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme . '://' . $host . '/' . ltrim($path, '/');
+}
+
+/**
+ * Get a short summary of the message body
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param MessageInterface $message The message to get the body summary
+ * @param int $truncateAt The maximum allowed size of the summary
+ *
+ * @return null|string
+ */
+function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
+{
+ $body = $message->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $summary = $body->read($truncateAt);
+ $body->rewind();
+
+ if ($size > $truncateAt) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
+ return null;
+ }
+
+ return $summary;
+}
+
+/** @internal */
+function _caseless_remove($keys, array $data)
+{
+ $result = [];
+
+ foreach ($keys as &$key) {
+ $key = strtolower($key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower($k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+}
diff --git a/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/functions_include.php b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/functions_include.php
new file mode 100755
index 0000000..96a4a83
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/guzzlehttp/psr7/src/functions_include.php
@@ -0,0 +1,6 @@
+setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__)
+ ->name('/(?:^lazydoctor$|\.php$)/')
+ )
+ ->setIndent(' ')
+ ->setLineEnding("\n")
+ ->setRules([
+ '@Symfony' => true,
+ // Override @Symfony rules
+ 'pre_increment' => false,
+ 'blank_line_before_statement' => ['statements' => ['return', 'try', 'throw']],
+ 'phpdoc_align' => ['tags' => ['param', 'throws']],
+ 'method_argument_space' => ['ensure_fully_multiline' => false],
+ 'binary_operator_spaces' => [
+ 'align_double_arrow' => true,
+ 'align_equals' => false,
+ ],
+ 'phpdoc_annotation_without_dot' => false,
+ 'yoda_style' => [
+ // Symfony writes their conditions backwards; we use normal order.
+ 'equal' => false,
+ 'identical' => false,
+ 'less_and_greater' => false,
+ ],
+ 'is_null' => [
+ // Replaces all is_null() with === null.
+ 'use_yoda_style' => false,
+ ],
+ // Custom rules
+ 'align_multiline_comment' => true,
+ 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
+ 'ordered_imports' => true,
+ 'phpdoc_order' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ]);
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/.pre-commit.hook b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/.pre-commit.hook
new file mode 100755
index 0000000..4a87bd2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/.pre-commit.hook
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright 2017 The LazyJsonMapper Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ----------------------------------------------------------------------------
+#
+# Verifies that all files in the worktree follow our codestyle standards.
+#
+# Note that this script can't check that they're actually committing the nicely
+# formatted code. It just checks that the worktree is clean. So if they've fixed
+# all files but haven't added the codestyle fixes to their commit, they'll still
+# pass this check. But it's still a great protection against most mistakes.
+#
+# To install this hook, just run the following in the project's root folder:
+# ln -fs "../../.pre-commit.hook" .git/hooks/pre-commit
+#
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Git ensures that CWD is always the root of the project folder, so we can just
+# run all tests without verifying what folder we are in...
+
+failed="no"
+echo "[pre-commit] Checking work-tree codestyle..."
+
+# Check the general codestyle format.
+echo "> Verifying php-cs-fixer..."
+vendor/bin/php-cs-fixer fix --config=.php_cs.dist --allow-risky yes --dry-run
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Look for specific problems with the style, related to our project.
+echo "> Verifying checkStyle..."
+/usr/bin/env php devtools/checkStyle.php x
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Refuse to commit if there were problems. Instruct the user about solving it.
+if [ "${failed}" = "yes" ]; then
+ # Yes there are lots of "echo" commands, because "\n" is not cross-platform.
+ echo "[commit failed] There are problems with your code..."
+ echo ""
+ echo "Run 'composer codestyle' to fix the code in your worktree."
+ echo ""
+ echo "But beware that the process is automatic, and that the result"
+ echo "isn't always perfect and won't be automatically staged."
+ echo ""
+ echo "So remember to manually read through the changes, then further"
+ echo "fix them if necessary, and finally stage the updated code"
+ echo "afterwards so that the fixed code gets committed."
+ exit 1
+fi
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/LICENSE b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/NOTICE b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/NOTICE
new file mode 100755
index 0000000..27c6183
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/NOTICE
@@ -0,0 +1,5 @@
+LazyJsonMapper
+Copyright 2017 The LazyJsonMapper Project
+
+This product includes software developed at
+The LazyJsonMapper Project (https://github.com/SteveJobzniak/LazyJsonMapper).
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/README.md b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/README.md
new file mode 100755
index 0000000..7d3a997
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/README.md
@@ -0,0 +1,211 @@
+# LazyJsonMapper
+
+## Advanced, intelligent & automatic object-oriented JSON containers for PHP.
+
+Implements a highly efficient, automatic, object-oriented and lightweight
+(memory-wise) JSON data container. It provides intelligent data conversion
+and parsing, to give you a nice, reliable interface to your JSON data,
+without having to worry about doing any of the tedious parsing yourself.
+
+### Features:
+
+- Provides a completely object-oriented interface to all of your JSON data.
+
+- Automatically maps complex, nested JSON data structures onto real PHP
+ objects, with total support for nested objects and multi-level arrays.
+
+- Extremely optimized for very high performance and very low memory usage.
+ Much lower than other PHP JSON mappers that people have used in the past.
+
+ For example, normal PHP objects with manually defined `$properties`, which
+ is what's used by _other_ JSON mappers, will consume memory for every
+ property even if that property wasn't in the JSON data (is a `NULL`). Our
+ system on the other hand takes up ZERO bytes of RAM for any properties
+ that don't exist in the current object's JSON data!
+
+- Automatically provides "direct virtual properties", which lets you
+ interact with the JSON data as if it were regular object properties,
+ such as `echo $item->some_value` and `$item->some_value = 'foo'`.
+
+ The virtual properties can be disabled via an option.
+
+- Automatically provides object-oriented "virtual functions", which let you
+ interact with the data in a fully object-oriented way via functions such
+ as `$item->getSomeValue()` and `$item->setSomeValue('foo')`. We support a
+ large range of different functions for manipulating the JSON data, and you
+ can see a list of all available function names for all of your properties
+ by simply running `$item->printPropertyDescriptions()`.
+
+ The virtual functions can be disabled via an option.
+
+- Includes the `LazyDoctor` tool, which _automatically_ documents all of
+ your `LazyJsonMapper`-based classes so that their virtual properties and
+ functions become _fully_ visible to your IDE and to various intelligent
+ code analysis tools. It also performs class diagnostics by compiling all
+ of your class property maps, which means that you can be 100% sure that
+ all of your maps are valid (compilable) if this tool runs successfully.
+
+- We provide a complete, internal API which your subclasses can use to
+ interact with the data inside of the JSON container. This allows you to
+ easily override the automatic functions or create additional functions
+ for your objects. To override core functions, just define a function with
+ the exact same name on your object and make it do whatever you want to.
+
+ Here are some examples of function overriding:
+
+ ```php
+ public function getFoo()
+ {
+ // try to read property, and handle a special "current_time" value.
+ $value = $this->_getProperty('foo');
+ if ($value === 'current_time') { return time(); }
+ return $value;
+ }
+ public function setFoo(
+ $value)
+ {
+ // if they try to set value to "md5", we use a special value instead
+ if ($value === 'md5') { $value = md5(time()); }
+ return $this->_setProperty('foo', $value);
+ }
+ ```
+
+- All mapping/data conversion is done "lazily", on a per-property basis.
+ When you access a property, that specific property is mapped/converted to
+ the proper type as defined by your class property map. No time or memory
+ is wasted converting properties that you never touch.
+
+- Strong type-system. The class property map controls the exact types and
+ array depths. You can fully trust that the data you get/set will match
+ your specifications. Invalid data that mismatches the spec is impossible.
+
+- Advanced settings system. Everything is easily configured via PHP class
+ constants, which means that your class-settings are stateless (there's no
+ need for any special "settings/mapper object" to keep track of settings),
+ and that all settings are immutable constants (which means that they are
+ reliable and can never mutate at runtime, so that you can fully trust that
+ classes will always behave as-defined in their code).
+
+ If you want to override multiple core settings identically for all of your
+ classes, then simply create a subclass of `LazyJsonMapper` and configure
+ all of your settings on that, and then derive all of your other classes
+ from your re-configured subclass!
+
+- The world's most advanced mapper definition system. Your class property
+ maps are defined in an easy PHPdoc-style format, and support multilevel
+ arrays (such as `int[][]` for "an array of arrays of ints"), relative
+ types (so you can map properties to classes/objects that are relative to
+ the namespace of the class property map), parent inheritance (all of your
+ parent `extends`-hierarchy's maps will be included in your final property
+ map) and even multiple inheritance (you can literally "import" an infinite
+ number of other maps into your class, which don't come from your own
+ parent `extends`-hierarchy).
+
+- Inheriting properties from parent classes or importing properties from
+ other classes is a zero-cost operation thanks to how efficient our
+ property map compiler is. So feel free to import everything you need.
+ You can even use this system to create importable classes that just hold
+ "collections" of shared properties, which you import into other classes.
+
+- The class property maps are compiled a single time per-class at runtime,
+ the first time a class is used. The compilation process fully verifies
+ and compiles all property definitions, all parent maps, all inherited
+ maps, and all maps of all classes you link properties to.
+
+ If there are any compilation problems due to a badly written map anywhere
+ in your hierarchy, you will be shown the exact problem in great detail.
+
+ In case of success, the compiled and verified maps are all stored in an
+ incredibly memory-efficient format in a global cache which is shared by
+ your whole PHP runtime, which means that anything in your code or in any
+ other libraries which accesses the same classes will all share the cached
+ compilations of those classes, for maximum memory efficiency.
+
+- You are also able to access JSON properties that haven't been defined in
+ the class property map. In that case, they are treated as undefined and
+ untyped (`mixed`) and there won't be any automatic type-conversion of such
+ properties, but it can still be handy in a pinch.
+
+- There are lots of data export/output options for your object's JSON data,
+ to get it back out of the object again: As a multi-level array, as nested
+ stdClass objects, or as a JSON string representation of your object.
+
+- We include a whole assortment of incredibly advanced debugging features:
+
+ You can run the constructor with `$requireAnalysis` to ensure that all
+ of your JSON data is successfully mapped according to your class property
+ map, and that you haven't missed defining any properties that exist in the
+ data. In case of any problems, the analysis message will give you a full
+ list of all problems encountered in your entire JSON data hierarchy.
+
+ For your class property maps themselves, you can run functions such as
+ `printPropertyDescriptions()` to see a complete list of all properties and
+ how they are defined. This helps debug your class inheritance and imports
+ to visually see what your final class map looks like, and it also helps
+ users see all available properties and all of their virtual functions.
+
+ And for the JSON data, you can use functions such as `printJson()` to get
+ a beautiful view of all internal JSON data, which is incredibly helpful
+ when you (or your users) need to figure out what's available inside the
+ current object instance's data storage.
+
+- A fine-grained and logical exception-system which ensures that you can
+ always trust the behavior of your objects and can catch problems easily.
+ And everything we throw is _always_ based on `LazyJsonMapperException`,
+ which means that you can simply catch that single "root" exception
+ whenever you don't care about fine-grained differentiation.
+
+- Clean and modular code ensures stability and future extensibility.
+
+- Deep code documentation explains everything you could ever wonder about.
+
+- Lastly, we implement super-efficient object serialization. Everything is
+ stored in a tightly packed format which minimizes data size when you need
+ to transfer your objects between runtimes.
+
+### Installation
+
+You need at least PHP 5.6 or higher. PHP 7+ is also fully supported and is recommended.
+
+Run the following [Composer](https://getcomposer.org/download/) installation command:
+
+```
+composer require lazyjsonmapper/lazyjsonmapper
+```
+
+### Examples
+
+View the contents of the [`examples/`](https://github.com/SteveJobzniak/LazyJsonMapper/tree/master/examples) folder.
+
+### Documentation
+
+Everything is fully documented directly within the source code of this library.
+
+You can also [read the same documentation online](https://mgp25.github.io/lazyjsonmapper-docs/namespaces/LazyJsonMapper.html) as nicely formatted HTML pages.
+
+### LazyDoctor
+
+Our automatic class-documentation and diagnostic utility will be placed within
+your project's `./vendor/bin/` folder. Simply run it without any parameters to
+see a list of all available options. You can also open that file in a regular
+text editor to read some general usage tips and tricks at the top of the
+utility's source code.
+
+### Copyright
+
+Copyright 2017 The LazyJsonMapper Project
+
+### License
+
+[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+
+### Author
+
+SteveJobzniak
+
+### Contributing
+
+If you would like to contribute to this project, please feel free to submit a
+pull request, or perhaps even sending a donation to a team member as a token of
+your appreciation.
+
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/bin/lazydoctor b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/bin/lazydoctor
new file mode 100755
index 0000000..c142b53
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/bin/lazydoctor
@@ -0,0 +1,738 @@
+#!/usr/bin/env php
+/dev/null" to send STDOUT to the void...
+ * That way you'll ONLY see critical status messages during the processing.
+ */
+
+set_time_limit(0);
+date_default_timezone_set('UTC');
+
+// Verify minimum PHP version.
+if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50600) {
+ fwrite(STDERR, 'LazyDoctor requires PHP 5.6 or higher.'.PHP_EOL);
+ exit(1);
+}
+
+// Register a simple GetOptionKit autoloader. This is fine because
+// GetOptionKit has no 3rd party library dependencies.
+spl_autoload_register(function ($class) {
+ // Check if this is a "GetOptionKit" load-request.
+ static $prefix = 'GetOptionKit\\';
+ static $len = 13; // strlen($prefix)
+ if (strncmp($prefix, $class, $len) !== 0) {
+ return;
+ }
+
+ // Find the "GetOptionKit" source folder.
+ static $dirs = [
+ __DIR__.'/../../../corneltek/getoptionkit/src',
+ __DIR__.'/../vendor/corneltek/getoptionkit/src',
+ ];
+ $baseDir = null;
+ foreach ($dirs as $dir) {
+ if (is_dir($dir) && ($dir = realpath($dir)) !== false) {
+ $baseDir = $dir;
+ break;
+ }
+ }
+ if ($baseDir === null) {
+ return;
+ }
+
+ // Get the relative class name.
+ $relativeClass = substr($class, $len);
+
+ // Generate PSR-4 file path to the class.
+ $file = sprintf('%s/%s.php', $baseDir, str_replace('\\', '/', $relativeClass));
+ if (is_file($file)) {
+ require $file;
+ }
+});
+
+// Parse command line options...
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
+
+$specs = new OptionCollection();
+$specs->add('c|composer:=file', 'Path to your project\'s composer.json file.');
+$specs->add('p|properties?=boolean', 'Document virtual properties (if enabled for the classes).');
+$specs->add('f|functions?=boolean', 'Document virtual functions (if enabled for the classes).');
+$specs->add('o|document-overridden?=boolean', 'Always document virtual functions/properties even when they have been manually overridden by the class (or its parents).');
+$specs->add('w|windows?=boolean', 'Generate Windows-style ("\r\n") documentation line endings instead of the default Unix-style ("\n").');
+$specs->add('validate-only?=boolean', 'Validate current docs for all classes but don\'t write anything to disk.');
+$specs->add('h|help?=boolean', 'Show all available options.');
+
+try {
+ $parser = new OptionParser($specs);
+ $result = $parser->parse($argv);
+ $options = [
+ 'composer' => isset($result->keys['composer']) ? $result->keys['composer']->value : null,
+ 'properties' => isset($result->keys['properties']) && $result->keys['properties']->value !== false,
+ 'functions' => isset($result->keys['functions']) && $result->keys['functions']->value !== false,
+ 'document-overridden' => isset($result->keys['document-overridden']) && $result->keys['document-overridden']->value !== false,
+ 'windows' => isset($result->keys['windows']) && $result->keys['windows']->value !== false,
+ 'validate-only' => isset($result->keys['validate-only']) && $result->keys['validate-only']->value !== false,
+ 'help' => isset($result->keys['help']) && $result->keys['help']->value !== false,
+ ];
+} catch (Exception $e) {
+ // Warns in case of invalid option values.
+ fwrite(STDERR, $e->getMessage().PHP_EOL);
+ exit(1);
+}
+
+// Verify options...
+echo '[ LazyDoctor ]'.PHP_EOL.PHP_EOL;
+if ($options['composer'] === null || $options['help']) {
+ if ($options['composer'] === null) {
+ fwrite(STDERR, 'You must provide the --composer option.'.PHP_EOL.PHP_EOL);
+ }
+ $printer = new ConsoleOptionPrinter();
+ echo 'Available options:'.PHP_EOL.PHP_EOL;
+ echo $printer->render($specs);
+ exit($options['composer'] === null && !$options['help'] ? 1 : 0);
+}
+
+if ($options['composer']->getBasename() !== 'composer.json') {
+ fwrite(STDERR, 'You must point to your project\'s composer.json file.'.PHP_EOL.'You used: "'.$options['composer']->getRealPath().'".'.PHP_EOL);
+ exit(1);
+}
+
+// Decode the composer.json file...
+$json = @json_decode(file_get_contents($options['composer']->getRealPath()), true);
+if ($json === null) {
+ fwrite(STDERR, 'Unable to decode composer.json.'.PHP_EOL);
+ exit(1);
+}
+
+// Determine the project folder's real root path...
+$projectRoot = $options['composer']->getPathInfo()->getRealPath();
+
+// Determine their namespace PSR-4 paths via their project's composer.json...
+$namespaces = [];
+foreach (['autoload', 'autoload-dev'] as $type) {
+ if (!isset($json[$type]['psr-4']) || !is_array($json[$type]['psr-4'])) {
+ continue;
+ }
+
+ foreach ($json[$type]['psr-4'] as $namespace => $dir) {
+ // We don't support composer's empty "fallback" namespaces.
+ if ($namespace === '') {
+ fwrite(STDERR, 'Encountered illegal unnamed PSR-4 autoload namespace in composer.json.'.PHP_EOL);
+ exit(1);
+ }
+
+ // Ensure that the namespace ends in backslash.
+ if (substr_compare($namespace, '\\', strlen($namespace) - 1, 1) !== 0) {
+ fwrite(STDERR, 'Encountered illegal namespace "'.$namespace.'" (does not end in backslash) in composer.json.'.PHP_EOL);
+ exit(1);
+ }
+
+ // Ensure that the value is a string.
+ // NOTE: We allow empty strings, which corresponds to root folder.
+ if (!is_string($dir)) {
+ fwrite(STDERR, 'Encountered illegal non-string value for namespace "'.$namespace.'".'.PHP_EOL);
+ exit(1);
+ }
+
+ // Now resolve the path name...
+ $path = sprintf('%s/%s', $projectRoot, $dir);
+ $realpath = realpath($path);
+ if ($realpath === false) {
+ fwrite(STDERR, 'Unable to resolve real path for "'.$path.'".'.PHP_EOL);
+ exit(1);
+ }
+
+ // We don't allow the same directory to be defined multiple times.
+ if (isset($namespaces[$realpath])) {
+ fwrite(STDERR, 'Encountered duplicate namespace directory "'.$realpath.'" in composer.json.'.PHP_EOL);
+ exit(1);
+ }
+
+ // And we're done! The namespace and its path have been resolved.
+ $namespaces[$realpath] = $namespace;
+ }
+}
+
+// Verify that we found some namespaces...
+if (empty($namespaces)) {
+ fwrite(STDERR, 'There are no PSR-4 autoload namespaces in your composer.json.'.PHP_EOL);
+ exit(1);
+}
+
+// Now load the project's autoload.php file.
+// NOTE: This is necessary so that we can autoload their classes...
+$autoload = sprintf('%s/vendor/autoload.php', $projectRoot);
+$realautoload = realpath($autoload);
+if ($realautoload === false) {
+ fwrite(STDERR, 'Unable to find the project\'s Composer autoloader ("'.$autoload.'").'.PHP_EOL);
+ exit(1);
+}
+require $realautoload;
+
+// Verify that their project's autoloader contains LazyJsonMapper...
+if (!class_exists('\LazyJsonMapper\LazyJsonMapper', true)) { // TRUE = Autoload.
+ fwrite(STDERR, 'Target project doesn\'t contain the LazyJsonMapper library.'.PHP_EOL);
+ exit(1);
+}
+
+// Alright, display the current options...
+echo 'Project: "'.$projectRoot.'".'.PHP_EOL
+ .'- Documentation Line Endings: '.($options['windows'] ? 'Windows ("\r\n")' : 'Unix ("\n")').'.'.PHP_EOL
+ .'- ['.($options['properties'] ? 'X' : ' ').'] Document Virtual Properties ("@property").'.PHP_EOL
+ .'- ['.($options['functions'] ? 'X' : ' ').'] Document Virtual Functions ("@method").'.PHP_EOL
+ .'- ['.($options['document-overridden'] ? 'X' : ' ').'] Document Overridden Properties/Functions.'.PHP_EOL;
+if ($options['validate-only']) {
+ echo '- This is a validation run. Nothing will be written to disk.'.PHP_EOL;
+}
+
+// We can now use our custom classes, since the autoloader has been imported...
+use LazyJsonMapper\Exception\LazyJsonMapperException;
+use LazyJsonMapper\Export\PropertyDescription;
+use LazyJsonMapper\Property\PropertyMapCache;
+use LazyJsonMapper\Property\PropertyMapCompiler;
+use LazyJsonMapper\Utilities;
+
+/**
+ * Automatic LazyJsonMapper-class documentation generator.
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class LazyClassDocumentor
+{
+ /** @var PropertyMapCache */
+ private static $_propertyMapCache;
+
+ /** @var array */
+ private $_compiledPropertyMapLink;
+
+ /** @var ReflectionClass */
+ private $_reflector;
+
+ /** @var array */
+ private $_options;
+
+ /** @var string Newline sequence. */
+ private $_nl;
+
+ /**
+ * Constructor.
+ *
+ * @param string $class
+ * @param array $options
+ *
+ * @throws ReflectionException
+ */
+ public function __construct(
+ $class,
+ array $options)
+ {
+ if (self::$_propertyMapCache === null) {
+ self::$_propertyMapCache = new PropertyMapCache();
+ }
+ $this->_reflector = new ReflectionClass($class);
+ $this->_options = $options;
+ $this->_nl = $options['windows'] ? "\r\n" : "\n";
+ }
+
+ /**
+ * Process the current class.
+ *
+ * @throws ReflectionException
+ * @throws LazyJsonMapperException
+ *
+ * @return bool `TRUE` if on-disk file has correct docs, otherwise `FALSE`.
+ */
+ public function process()
+ {
+ // Only process user-defined classes (never any built-in PHP classes).
+ if (!$this->_reflector->isUserDefined()) {
+ return true;
+ }
+
+ // There's nothing to do if this isn't a LazyJsonMapper subclass.
+ // NOTE: This properly skips "\LazyJsonMapper\LazyJsonMapper" itself.
+ if (!$this->_reflector->isSubclassOf('\LazyJsonMapper\LazyJsonMapper')) {
+ return true;
+ }
+
+ // Compile this class property map if not yet built and cached.
+ $thisClassName = $this->_reflector->getName();
+ if (!isset(self::$_propertyMapCache->classMaps[$thisClassName])) {
+ try {
+ PropertyMapCompiler::compileClassPropertyMap( // Throws.
+ self::$_propertyMapCache,
+ $thisClassName
+ );
+ } catch (Exception $e) {
+ fwrite(STDERR, '> Unable to compile the class property map for "'.$thisClassName.'". Reason: '.$e->getMessage().PHP_EOL);
+
+ return false;
+ }
+ }
+
+ // Now link to the property map cache for our current class.
+ $this->_compiledPropertyMapLink = &self::$_propertyMapCache->classMaps[$thisClassName];
+
+ // Get the current class comment (string if ok, FALSE if none exists).
+ $currentDocComment = $this->_reflector->getDocComment();
+ if (is_string($currentDocComment)) {
+ $currentDocComment = trim($currentDocComment);
+ }
+
+ // Extract all relevant lines from the current comment.
+ $finalDocLines = $this->_extractRelevantLines($currentDocComment);
+
+ // Generate the automatic summary line (classname followed by period).
+ $autoSummaryLine = $this->_reflector->getShortName().'.';
+
+ // If the 1st line is a classname followed by a period, update the name.
+ // NOTE: This ensures that we update all outdated auto-added classnames,
+ // and the risk of false positives is very low since we only document
+ // `LazyJsonMapper`-based classes with a `OneWord.`-style summary line.
+ // NOTE: Regex is from http://php.net/manual/en/language.oop5.basic.php,
+ // and yes we must run it in NON-UNICODE MODE, so that it parses on a
+ // byte by byte basis exactly like the real PHP classname interpreter.
+ if (
+ isset($finalDocLines[0]) // The 1st line MUST exist to proceed.
+ && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\.$/', $finalDocLines[0])
+ ) {
+ $finalDocLines[0] = $autoSummaryLine;
+ }
+
+ // Generate the magic documentation lines for the current class.
+ $magicDocLines = $this->_generateMagicDocs();
+ if (!empty($magicDocLines)) {
+ // If there are no lines already... add the automatic summary line.
+ if (empty($finalDocLines)) {
+ $finalDocLines[] = $autoSummaryLine;
+ }
+
+ // Check the 1st char of the 1st line. If it's an @tag of any kind,
+ // insert automatic summary line at top and empty line after that.
+ elseif ($finalDocLines[0][0] === '@') {
+ array_unshift(
+ $finalDocLines,
+ $autoSummaryLine,
+ ''
+ );
+ }
+
+ $finalDocLines[] = ''; // Add empty line before our magic docs.
+ $finalDocLines = array_merge($finalDocLines, array_values($magicDocLines));
+ }
+ unset($magicDocLines);
+
+ // Generate the final doc-comment that this class is supposed to have.
+ if (!empty($finalDocLines)) {
+ // This will generate even if the class only contained an existing
+ // summary/tags and nothing was added by our magic handler.
+ foreach ($finalDocLines as &$line) {
+ $line = ($line === '' ? ' *' : " * {$line}");
+ }
+ unset($line);
+ $finalDocComment = sprintf(
+ '/**%s%s%s */',
+ $this->_nl,
+ implode($this->_nl, $finalDocLines),
+ $this->_nl
+ );
+ } else {
+ // The FALSE signifies that we want no class doc-block at all...
+ $finalDocComment = false;
+ }
+ unset($finalDocLines);
+
+ // There's nothing to do if the doc-comment is already correct.
+ // NOTE: Both values are FALSE if no doc-comment exists and none wanted.
+ if ($currentDocComment === $finalDocComment) {
+ return true;
+ }
+
+ // The docs mismatch. If this is a validate-run, just return false now.
+ if ($this->_options['validate-only']) {
+ fwrite(STDERR, '> Outdated class docs encountered in "'.$thisClassName.'". Aborting scan...'.PHP_EOL);
+
+ return false;
+ }
+
+ // Load the contents of the file...
+ $classFileName = $this->_reflector->getFileName();
+ $fileLines = @file($classFileName);
+ if ($fileLines === false) {
+ fwrite(STDERR, '> Unable to read class file from disk: "'.$classFileName.'".'.PHP_EOL);
+
+ return false;
+ }
+
+ // Split the file into lines BEFORE the class and lines AFTER the class.
+ $classLine = $this->_reflector->getStartLine();
+ $startLines = array_slice($fileLines, 0, $classLine - 1);
+ $endLines = array_slice($fileLines, $classLine - 1);
+ unset($fileLines);
+
+ // Insert the new class documentation using a very careful algorithm.
+ if ($currentDocComment !== false) {
+ // Since the class already had PHPdoc, remove it and insert new doc.
+ // NOTE: A valid PHPdoc (getDocComment()) always starts with
+ // "/**[whitespace]". If it's just a "/*" or something like
+ // "/**Foo", then it's not detected by getDocComment(). However, the
+ // comment may be several lines above the class. So we'll have to do
+ // an intelligent search to find the old class-comment. As for the
+ // ending tag "*/", PHP doesn't care about whitespace around that.
+ // And it also doesn't let the user escape the "*/", which means
+ // that if we see that sequence we KNOW it's the end of a comment!
+ // NOTE: We'll search for the latest "/**[whitespace]" block and
+ // remove all lines from that until its closest "*/".
+ $deleteFrom = null;
+ $deleteTo = null;
+ for ($i = count($startLines) - 1; $i >= 0; --$i) {
+ if (strpos($startLines[$i], '*/') !== false) {
+ $deleteTo = $i;
+ }
+ if (preg_match('/^\s*\/\*\*\s/u', $startLines[$i])) {
+ $deleteFrom = $i;
+ break;
+ }
+ }
+
+ // Ensure that we have found valid comment-offsets.
+ if ($deleteFrom === null || $deleteTo === null || $deleteTo < $deleteFrom) {
+ fwrite(STDERR, '> Unable to parse current class comment on disk: "'.$classFileName.'".'.PHP_EOL);
+
+ return false;
+ }
+
+ // Now update the startLines array to replace the doc-comment...
+ foreach ($startLines as $k => $v) {
+ if ($k === $deleteFrom && $finalDocComment !== false) {
+ // We've found the first line of the old comment, and we
+ // have a new comment. So replace that array entry.
+ $startLines[$k] = $finalDocComment.$this->_nl;
+ } elseif ($k >= $deleteFrom && $k <= $deleteTo) {
+ // Delete all other comment lines, including the first line
+ // if we had no new doc-comment.
+ unset($startLines[$k]);
+ }
+
+ // Break if we've reached the final line to delete.
+ if ($k >= $deleteTo) {
+ break;
+ }
+ }
+ } elseif ($finalDocComment !== false) {
+ // There's no existing doc-comment. Just add ours above the class.
+ // NOTE: This only does something if we had a new comment to insert,
+ // which we SHOULD have since we came this far in this scenario...
+ $startLines[] = $finalDocComment.$this->_nl;
+ }
+
+ // Generate the new file contents.
+ $newFileContent = implode($startLines).implode($endLines);
+ unset($startLines);
+ unset($endLines);
+
+ // Perform an atomic file-write to disk, which ensures that we will
+ // never be able to corrupt the class-files on disk via partial writes.
+ $written = Utilities::atomicWrite($classFileName, $newFileContent);
+ if ($written !== false) {
+ echo '> Wrote updated class documentation to disk: "'.$classFileName.'".'.PHP_EOL;
+
+ return true;
+ } else {
+ fwrite(STDERR, '> Unable to write new class documentation to disk: "'.$classFileName.'".'.PHP_EOL);
+
+ return false;
+ }
+ }
+
+ /**
+ * Extracts all relevant lines from a doc-comment.
+ *
+ * @param string $currentDocComment
+ *
+ * @return array
+ */
+ private function _extractRelevantLines(
+ $currentDocComment)
+ {
+ if (!is_string($currentDocComment)) {
+ return [];
+ }
+
+ // Remove the leading and trailing doc-comment tags (/** and */).
+ $currentDocComment = preg_replace('/(^\s*\/\*\*\s*|\s*\*\/$)/u', '', $currentDocComment);
+
+ // Process all lines. Skip all @method and @property lines.
+ $relevantLines = [];
+ $lines = preg_split('/\r?\n|\r/u', $currentDocComment);
+ foreach ($lines as $line) {
+ // Remove leading and trailing whitespace, and leading asterisks.
+ $line = trim(preg_replace('/^\s*\*+/u', '', $line));
+
+ // Skip this line if it's a @method or @property line.
+ // NOTE: Removing them is totally safe, because the LazyJsonMapper
+ // class has marked all of its magic property/function handlers as
+ // final, which means that people's subclasses CANNOT override them
+ // to add their own magic methods/properties. So therefore we KNOW
+ // that ALL existing @method/@property class doc lines belong to us!
+ if (preg_match('/^@(?:method|property)/u', $line)) {
+ continue;
+ }
+
+ $relevantLines[] = $line;
+ }
+
+ // Remove trailing empty lines from the relevant lines.
+ for ($i = count($relevantLines) - 1; $i >= 0; --$i) {
+ if ($relevantLines[$i] === '') {
+ unset($relevantLines[$i]);
+ } else {
+ break;
+ }
+ }
+
+ // Remove leading empty lines from the relevant lines.
+ foreach ($relevantLines as $k => $v) {
+ if ($v !== '') {
+ break;
+ }
+
+ unset($relevantLines[$k]);
+ }
+
+ // Return a re-indexed (properly 0-indexed) array.
+ return array_values($relevantLines);
+ }
+
+ /**
+ * Generate PHPdoc lines for all magic properties and functions.
+ *
+ * @throws ReflectionException
+ * @throws LazyJsonMapperException
+ *
+ * @return array
+ */
+ private function _generateMagicDocs()
+ {
+ // Check whether we should (and can) document properties and functions.
+ $documentProperties = $this->_options['properties'] && $this->_reflector->getConstant('ALLOW_VIRTUAL_PROPERTIES');
+ $documentFunctions = $this->_options['functions'] && $this->_reflector->getConstant('ALLOW_VIRTUAL_FUNCTIONS');
+ if (!$documentProperties && !$documentFunctions) {
+ return [];
+ }
+
+ // Export all JSON properties, with RELATIVE class-paths when possible.
+ // NOTE: We will document ALL properties. Even ones inherited from
+ // parents/imported maps. This ensures that users who are manually
+ // reading the source code can see EVERYTHING without needing an IDE.
+ $properties = [];
+ $ownerClassName = $this->_reflector->getName();
+ foreach ($this->_compiledPropertyMapLink as $propName => $propDef) {
+ $properties[$propName] = new PropertyDescription( // Throws.
+ $ownerClassName,
+ $propName,
+ $propDef,
+ true // Use relative class-paths when possible.
+ );
+ }
+
+ // Build the magic documentation...
+ $magicDocLines = [];
+ foreach (['functions', 'properties'] as $docType) {
+ if (($docType === 'functions' && !$documentFunctions)
+ || ($docType === 'properties' && !$documentProperties)) {
+ continue;
+ }
+
+ // Generate all lines for the current magic tag type...
+ $lineStorage = [];
+ foreach ($properties as $property) {
+ if ($docType === 'functions') {
+ // We will only document useful functions (not the "has",
+ // since those are useless for properties that are fully
+ // defined in the class map).
+ foreach (['get', 'set', 'is', 'unset'] as $funcType) {
+ // Generate the function name, ie "getSomething", and
+ // skip this function if it's already defined as a REAL
+ // (overridden) function in this class or its parents.
+ $functionName = $funcType.$property->func_case;
+ if (!$this->_options['document-overridden'] && $this->_reflector->hasMethod($functionName)) {
+ continue;
+ }
+
+ // Alright, the function doesn't exist as a real class
+ // function, or the user wants to document it anyway...
+ // Document it via its calculated signature.
+ // NOTE: Classtypes use paths relative to current class!
+ $functionSignature = $property->{'function_'.$funcType};
+ $lineStorage[$functionName] = sprintf('@method %s', $functionSignature);
+ }
+ } elseif ($docType === 'properties') {
+ // Skip this property if it's already defined as a REAL
+ // (overridden) property in this class or its parents.
+ if (!$this->_options['document-overridden'] && $this->_reflector->hasProperty($property->name)) {
+ continue;
+ }
+
+ // Alright, the property doesn't exist as a real class
+ // property, or the user wants to document it anyway...
+ // Document it via its calculated signature.
+ // NOTE: Classtypes use paths relative to current class!
+ $lineStorage[$property->name] = sprintf(
+ '@property %s $%s',
+ $property->type,
+ $property->name
+ );
+ }
+ }
+
+ // Skip this tag type if there was nothing to document...
+ if (empty($lineStorage)) {
+ continue;
+ }
+
+ // Insert empty line separators between different magic tag types.
+ if (!empty($magicDocLines)) {
+ $magicDocLines[] = '';
+ }
+
+ // Reorder lines by name and add them to the magic doc lines.
+ // NOTE: We use case sensitivity so that "getComments" and
+ // "getCommentThreads" etc aren't placed next to each other.
+ ksort($lineStorage, SORT_NATURAL); // Case-sensitive natural order.
+ $magicDocLines = array_merge($magicDocLines, array_values($lineStorage));
+ }
+
+ return $magicDocLines;
+ }
+}
+
+// Now process all PHP files under all of the project's namespace folders.
+foreach ($namespaces as $realpath => $namespace) {
+ echo PHP_EOL.'Processing namespace "'.$namespace.'".'.PHP_EOL.'- Path: "'.$realpath.'".'.PHP_EOL;
+ $realpathlen = strlen($realpath);
+
+ $iterator = new RegexIterator(
+ new RecursiveIteratorIterator(new RecursiveDirectoryIterator($realpath)),
+ '/\.php$/i', RecursiveRegexIterator::GET_MATCH
+ );
+ foreach ($iterator as $file => $ext) {
+ // Determine the real path to the file (compatible with $realpath).
+ $realfile = realpath($file);
+ if ($realfile === false) {
+ fwrite(STDERR, 'Unable to determine real path to file "'.$file.'".'.PHP_EOL);
+ exit(1);
+ }
+
+ // Now ensure that the file starts with the expected path...
+ if (strncmp($realpath, $realfile, $realpathlen) !== 0) {
+ fwrite(STDERR, 'Unexpected path to file "'.$realfile.'". Does not match project path.'.PHP_EOL);
+ exit(1);
+ }
+ $class = substr($realfile, $realpathlen);
+
+ // Remove the leading slash for the folder...
+ if ($class[0] !== '/' && $class[0] !== '\\') {
+ fwrite(STDERR, 'Unexpected path to file "'.$realfile.'". Does not match project path.'.PHP_EOL);
+ exit(1);
+ }
+ $class = substr($class, 1);
+
+ // And now just generate the final class name...
+ $class = sprintf(
+ '%s%s',
+ $namespace,
+ str_replace('/', '\\', preg_replace('/\.php$/ui', '', $class))
+ );
+
+ // Some files may not contain classes. For example, some people have
+ // functions.php files with functions, etc. So before we proceed, just
+ // ensure that the generated class name actually exists.
+ // NOTE: class_exists() ignores interfaces. Only finds classes. Good.
+ if (!class_exists($class, true)) { // TRUE = Autoload.
+ continue;
+ }
+
+ // Now process the current class.
+ $documentor = new LazyClassDocumentor($class, $options);
+ $result = $documentor->process();
+ if (!$result) {
+ if ($options['validate-only']) {
+ fwrite(STDERR, '> One or more files need updated class documentation or contain other errors.'.PHP_EOL);
+ } else {
+ fwrite(STDERR, '> Error while processing class "'.$class.'". Aborting...'.PHP_EOL);
+ }
+ exit(1);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/composer.json b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/composer.json
new file mode 100755
index 0000000..3b21d54
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/composer.json
@@ -0,0 +1,48 @@
+{
+ "name": "lazyjsonmapper/lazyjsonmapper",
+ "type": "library",
+ "description": "Advanced, intelligent & automatic object-oriented JSON containers for PHP.",
+ "keywords": ["json", "development"],
+ "homepage": "https://github.com/SteveJobzniak/LazyJsonMapper",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "SteveJobzniak",
+ "homepage": "https://github.com/SteveJobzniak",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": ">=5.6",
+ "corneltek/getoptionkit": "2.*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "6.*",
+ "friendsofphp/php-cs-fixer": "^2.7.1"
+ },
+ "bin": [
+ "bin/lazydoctor"
+ ],
+ "autoload": {
+ "psr-4": {
+ "LazyJsonMapper\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "LazyJsonMapper\\Tests\\": "tests/"
+ }
+ },
+ "scripts": {
+ "codestyle": [
+ "php-cs-fixer fix --config=.php_cs.dist --allow-risky yes",
+ "php devtools/checkStyle.php x"
+ ],
+ "generatedocs": [
+ "rm -rf docs/output/ && phpdoc"
+ ],
+ "generatefreshdocs": [
+ "rm -rf docs/ && phpdoc"
+ ]
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/checkStyle.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/checkStyle.php
new file mode 100755
index 0000000..5a0fc6c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/checkStyle.php
@@ -0,0 +1,225 @@
+run();
+if (!empty($badFiles)) {
+ // Exit with non-zero code to signal that there are problems.
+ exit(1);
+}
+
+/**
+ * Code style checker.
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class styleChecker
+{
+ /**
+ * @var string
+ */
+ private $_baseDir;
+
+ /**
+ * @var string[]
+ */
+ private $_inspectFolders;
+
+ /**
+ * @var bool
+ */
+ private $_onlyShowInvalidFiles;
+
+ /**
+ * Constructor.
+ *
+ * @param string $baseDir
+ * @param string[] $inspectFolders
+ * @param bool $onlyShowInvalidFiles
+ */
+ public function __construct(
+ $baseDir,
+ array $inspectFolders,
+ $onlyShowInvalidFiles)
+ {
+ $this->_baseDir = realpath($baseDir);
+ if ($this->_baseDir === false) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid path.', $baseDir));
+ }
+ $this->_inspectFolders = $inspectFolders;
+ $this->_onlyShowInvalidFiles = $onlyShowInvalidFiles;
+ }
+
+ /**
+ * Process single file.
+ *
+ * @param string $filePath
+ *
+ * @return bool TRUE if the file has codestyle problems, otherwise FALSE.
+ */
+ private function _processFile(
+ $filePath)
+ {
+ $hasProblems = false;
+ $hasVisibilityProblems = false;
+ $fileName = basename($filePath);
+ $inputLines = @file($filePath);
+ $outputLines = [];
+
+ if ($inputLines === false) {
+ // We were unable to read the input file. Ignore if broken symlink.
+ if (is_link($filePath)) {
+ return false; // File is okay, since the symlink is invalid.
+ } else {
+ echo "- {$filePath}: UNABLE TO READ FILE!".PHP_EOL;
+
+ return true; // File has problems...
+ }
+ }
+
+ foreach ($inputLines as $line) {
+ // Function arguments on separate lines.
+ if (preg_match('/^(.*?(?:(?:final|static)\s+)*(?:public|private|protected)(?:\s+(?:final|static))*\s+function\s+.+?)\((.+)\)(.*)$/', $line, $matches)) {
+ $hasProblems = true;
+
+ $funcstart = $matches[1];
+ $params = $matches[2];
+ $trail = $matches[3];
+ $params = explode(', ', $params);
+
+ $outputLines[] = $funcstart.'('.PHP_EOL;
+ for ($i = 0, $len = count($params); $i < $len; ++$i) {
+ $newline = ' '.$params[$i];
+ if ($i == ($len - 1)) {
+ $newline .= ')'.PHP_EOL;
+ } else {
+ $newline .= ','.PHP_EOL;
+ }
+ $outputLines[] = $newline;
+ }
+ // } else {
+ // $outputLines[] = $line;
+ }
+
+ // Appropriate public, private and protected member prefixes.
+ if (preg_match('/^\s*(?:(?:final|static)\s+)*(public|private|protected)(?:\s+(?:final|static))*\s+(function|\$)\s*&?([^;\(\s]+)/', $line, $matches)) {
+ $visibility = &$matches[1]; // public, private, protected
+ $type = &$matches[2]; // $, function
+ $name = &$matches[3]; // Member name
+
+ if ($visibility == 'public') {
+ if ($name[0] == '_' && (
+ $name != '__construct'
+ && $name != '__destruct'
+ && $name != '__call'
+ && $name != '__get'
+ && $name != '__set'
+ && $name != '__isset'
+ && $name != '__unset'
+ && $name != '__invoke'
+ && $name != '__toString'
+ )) {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PUBLIC NAME:".trim($matches[0]).PHP_EOL;
+ }
+ } else { // private, protected
+ if ($name[0] != '_') {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PRIVATE/PROTECTED NAME:".trim($matches[0]).PHP_EOL;
+ }
+ }
+ }
+ }
+
+ $newFile = implode('', $outputLines);
+ if (!$hasProblems) {
+ if (!$this->_onlyShowInvalidFiles) {
+ echo " {$filePath}: Already formatted correctly.\n";
+ }
+ } elseif (!$hasVisibilityProblems) {
+ // Has problems, but no visibility problems. Output fixed file.
+ echo "- {$filePath}: Has function parameter problems:\n";
+ echo $newFile;
+ } else {
+ echo "- {$filePath}: Had member visibility problems.\n";
+ }
+
+ return $hasProblems;
+ }
+
+ /**
+ * Process all *.php files in given path.
+ *
+ * @return string[] An array with all files that have codestyle problems.
+ */
+ public function run()
+ {
+ $filesWithProblems = [];
+ foreach ($this->_inspectFolders as $inspectFolder) {
+ $directoryIterator = new RecursiveDirectoryIterator($this->_baseDir.'/'.$inspectFolder);
+ $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
+ $phpIterator = new RegexIterator($recursiveIterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
+
+ foreach ($phpIterator as $filePath => $dummy) {
+ $hasProblems = $this->_processFile($filePath);
+ if ($hasProblems) {
+ $filesWithProblems[] = $filePath;
+ }
+ }
+ }
+
+ return $filesWithProblems;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/funcListData.serialized b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/funcListData.serialized
new file mode 100755
index 0000000..589e5be
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/funcListData.serialized
@@ -0,0 +1 @@
+a:10000:{i:0;s:3:"Pol";i:1;s:3:"Unp";i:2;s:8:"DeDiveSt";i:3;s:2:"Un";i:4;s:5:"Oyste";i:5;s:5:"Postm";i:6;s:2:"Sa";i:7;s:10:"SorroWaddl";i:8;s:6:"ApoCys";i:9;s:10:"GenRevisTe";i:10;s:11:"FarcGenoGoy";i:11;s:3:"Cro";i:12;s:12:"BasDefoHorse";i:13;s:6:"PsycSu";i:14;s:8:"DemPresa";i:15;s:4:"PeSc";i:16;s:2:"Va";i:17;s:6:"AnsKaz";i:18;s:3:"Ove";i:19;s:10:"HanovRaoul";i:20;s:3:"Twi";i:21;s:13:"NonsTortWhapu";i:22;s:5:"Areco";i:23;s:13:"BacilMaladUna";i:24;s:5:"Unflu";i:25;s:13:"CheyHarmSands";i:26;s:11:"OtheOvUltra";i:27;s:2:"Lo";i:28;s:4:"Shos";i:29;s:5:"Scler";i:30;s:5:"Predi";i:31;s:4:"Unre";i:32;s:4:"Punj";i:33;s:9:"HosanUnig";i:34;s:7:"BetaWis";i:35;s:13:"ShalVerteWasp";i:36;s:7:"InhSuWi";i:37;s:5:"Pluto";i:38;s:2:"Ph";i:39;s:9:"IrPhResur";i:40;s:10:"UnconVisco";i:41;s:12:"DrMessaSizzl";i:42;s:6:"LaPrea";i:43;s:2:"Zo";i:44;s:4:"Suck";i:45;s:5:"Monop";i:46;s:5:"Pinci";i:47;s:8:"PatroPha";i:48;s:9:"HogPicrTh";i:49;s:4:"Picn";i:50;s:8:"DisasGib";i:51;s:5:"Popul";i:52;s:10:"ExordPseSh";i:53;s:4:"Unpo";i:54;s:8:"HabiTaci";i:55;s:11:"SalsSanStep";i:56;s:11:"ProaeRepUnd";i:57;s:8:"InflLaSu";i:58;s:6:"ReTens";i:59;s:7:"CarnaHy";i:60;s:3:"Tur";i:61;s:6:"NympRe";i:62;s:5:"PacUp";i:63;s:12:"SpangWritZul";i:64;s:5:"LonPa";i:65;s:9:"SanxScrew";i:66;s:4:"KePo";i:67;s:3:"Eno";i:68;s:4:"Rich";i:69;s:9:"PlushThUn";i:70;s:4:"Seid";i:71;s:4:"GaPi";i:72;s:8:"SupeUpwh";i:73;s:4:"Sest";i:74;s:9:"ResoScler";i:75;s:5:"QuRep";i:76;s:8:"SquSuspe";i:77;s:12:"ExtRefunUnwi";i:78;s:4:"FlUn";i:79;s:6:"RenSar";i:80;s:8:"PierrRet";i:81;s:7:"HarNona";i:82;s:8:"PhanPian";i:83;s:11:"MiasPoachPs";i:84;s:5:"Refor";i:85;s:5:"PrUnf";i:86;s:11:"HedeProarRe";i:87;s:13:"SpieTurnUnesc";i:88;s:13:"EmboOgallSpin";i:89;s:11:"MelanUnstWi";i:90;s:10:"FroPutatUn";i:91;s:4:"PaVa";i:92;s:8:"TanisYot";i:93;s:12:"ConDaddFulgu";i:94;s:2:"Pl";i:95;s:11:"HaKerauProt";i:96;s:12:"DecoHinWhigg";i:97;s:4:"OvTh";i:98;s:5:"Gunat";i:99;s:3:"Pot";i:100;s:5:"Virgi";i:101;s:10:"CopoDiGast";i:102;s:10:"DefHexitUn";i:103;s:14:"InertLeckeNaut";i:104;s:13:"IodomUnreeYac";i:105;s:3:"Tul";i:106;s:9:"BailaPrZe";i:107;s:12:"LaminRecoSpi";i:108;s:6:"CoPoly";i:109;s:5:"Irida";i:110;s:3:"Syl";i:111;s:4:"SuVe";i:112;s:13:"GravMiswiTher";i:113;s:4:"Shem";i:114;s:6:"SolToo";i:115;s:5:"Unsta";i:116;s:10:"ForeReTicx";i:117;s:5:"Outra";i:118;s:12:"InteRoripSph";i:119;s:8:"FroTapst";i:120;s:13:"DislMultWhore";i:121;s:9:"CirInPili";i:122;s:3:"Woo";i:123;s:12:"MustePreceRa";i:124;s:12:"PeavPolRootl";i:125;s:6:"KnopSc";i:126;s:10:"TaTormiTub";i:127;s:8:"OvPrefSu";i:128;s:9:"MetTymVen";i:129;s:6:"PreTri";i:130;s:9:"NemSatUni";i:131;s:4:"DiTe";i:132;s:7:"ManSuUn";i:133;s:10:"CoaxFurRes";i:134;s:3:"Yua";i:135;s:4:"Pygo";i:136;s:4:"Indi";i:137;s:4:"Spon";i:138;s:10:"CrumePurch";i:139;s:8:"HemTerri";i:140;s:5:"ProRe";i:141;s:10:"DispuRoyal";i:142;s:6:"SympUn";i:143;s:4:"Nons";i:144;s:5:"Trime";i:145;s:3:"Non";i:146;s:8:"ReproUnd";i:147;s:10:"ExaPhlPrec";i:148;s:4:"Prot";i:149;s:8:"CurasDev";i:150;s:9:"EchiPlica";i:151;s:11:"FairOrbiUna";i:152;s:15:"GriffPutorRepro";i:153;s:11:"CalmOtolSpo";i:154;s:8:"FigPurUn";i:155;s:9:"GlIgInhum";i:156;s:2:"Tr";i:157;s:8:"HauMetaz";i:158;s:2:"On";i:159;s:11:"PluSnapWoor";i:160;s:11:"EuShosUnbre";i:161;s:15:"FilteRocheSemim";i:162;s:9:"ReviRoUns";i:163;s:2:"Ov";i:164;s:10:"HydrJesUnd";i:165;s:10:"SipSortTur";i:166;s:5:"LixSe";i:167;s:5:"NuUra";i:168;s:8:"MelWhisp";i:169;s:9:"DiJumpyTr";i:170;s:10:"LaMurUpyok";i:171;s:3:"Pty";i:172;s:4:"Ithy";i:173;s:4:"Bloo";i:174;s:4:"UnUn";i:175;s:5:"EsSwa";i:176;s:9:"NeobOvPar";i:177;s:5:"DemLa";i:178;s:8:"NonvoUnm";i:179;s:9:"MutiRimux";i:180;s:2:"Pr";i:181;s:11:"UninVillaWh";i:182;s:3:"Unl";i:183;s:10:"ProscSownx";i:184;s:11:"HiRescUnint";i:185;s:3:"Cau";i:186;s:13:"HittoHustlSub";i:187;s:8:"JeremSer";i:188;s:10:"DeMetagPet";i:189;s:8:"MisexRig";i:190;s:10:"ChildCogRe";i:191;s:11:"HyPennoRech";i:192;s:3:"Lig";i:193;s:7:"OuUnmem";i:194;s:2:"Re";i:195;s:4:"VoWa";i:196;s:8:"UnicUnju";i:197;s:5:"SecSn";i:198;s:9:"NumSaiUnc";i:199;s:13:"DoubtHeteUnse";i:200;s:4:"Smee";i:201;s:4:"PrTh";i:202;s:7:"MoVindh";i:203;s:8:"GasZodia";i:204;s:2:"Do";i:205;s:12:"UllUnfatWord";i:206;s:7:"IconNov";i:207;s:4:"Juda";i:208;s:9:"GrReptSei";i:209;s:14:"OutpPenelPolan";i:210;s:4:"Poly";i:211;s:2:"No";i:212;s:11:"SchiUnaVera";i:213;s:4:"Ther";i:214;s:9:"EbFlooPes";i:215;s:6:"MoOchi";i:216;s:5:"Triol";i:217;s:2:"Ra";i:218;s:5:"ProTe";i:219;s:10:"EuryoGrade";i:220;s:12:"EntOligPulpy";i:221;s:8:"RepRepSa";i:222;s:7:"NeskPap";i:223;s:3:"Unf";i:224;s:9:"FreMeUntr";i:225;s:7:"PeriUlt";i:226;s:5:"Varyi";i:227;s:8:"CorrPhTo";i:228;s:8:"ProfeSmo";i:229;s:6:"SuUncr";i:230;s:4:"Rurb";i:231;s:3:"Lan";i:232;s:9:"HearSuper";i:233;s:10:"AngulTsuga";i:234;s:10:"PertUnhYer";i:235;s:11:"GrandNitNoc";i:236;s:2:"Le";i:237;s:11:"HypeOverWei";i:238;s:11:"MentSoSumle";i:239;s:6:"LaShip";i:240;s:9:"PrenUnawa";i:241;s:13:"ExceKotaPonti";i:242;s:7:"HaMelod";i:243;s:6:"PaTyUn";i:244;s:2:"Fo";i:245;s:5:"Verse";i:246;s:10:"OverProTuk";i:247;s:9:"SubvTitan";i:248;s:10:"PondeSight";i:249;s:4:"Thar";i:250;s:7:"InRedho";i:251;s:10:"ReSynuThou";i:252;s:5:"Unpaw";i:253;s:2:"We";i:254;s:4:"Peco";i:255;s:8:"PlacaRoz";i:256;s:9:"RecoUnfre";i:257;s:9:"GyHaloHyp";i:258;s:3:"Lit";i:259;s:12:"NapOverUnfor";i:260;s:13:"CrosIntraNumi";i:261;s:11:"CreHandeSup";i:262;s:2:"Sc";i:263;s:10:"MucOzVerte";i:264;s:4:"Prim";i:265;s:9:"HomMonPhi";i:266;s:5:"Unene";i:267;s:2:"Sl";i:268;s:6:"LimRec";i:269;s:5:"Trebl";i:270;s:13:"ForehNetmResp";i:271;s:9:"NonprUnbe";i:272;s:9:"CarbuRaRe";i:273;s:10:"LihyOverSt";i:274;s:10:"CrotaPurpu";i:275;s:4:"Inel";i:276;s:7:"CenMast";i:277;s:9:"SlaxTaTit";i:278;s:11:"SuTrimUncha";i:279;s:2:"Dw";i:280;s:7:"EcaudSy";i:281;s:11:"FormHeteVog";i:282;s:4:"Stra";i:283;s:13:"IncuLushePelv";i:284;s:13:"LuthUnmaWiste";i:285;s:8:"ApnSivve";i:286;s:7:"CouUnme";i:287;s:7:"PsTrepa";i:288;s:4:"Wiss";i:289;s:10:"BorHomelRi";i:290;s:7:"ThUtrVe";i:291;s:2:"Te";i:292;s:8:"SaTeaUnd";i:293;s:6:"CoHuSo";i:294;s:2:"Th";i:295;s:9:"FlObSubco";i:296;s:4:"FlPa";i:297;s:9:"OverSeagh";i:298;s:7:"LympYaw";i:299;s:7:"IrreJac";i:300;s:10:"TholUnhUnw";i:301;s:4:"DeEl";i:302;s:9:"MervePrTi";i:303;s:8:"NatPrefa";i:304;s:7:"HumInco";i:305;s:7:"RiRibby";i:306;s:8:"BalosPer";i:307;s:8:"BepluDet";i:308;s:8:"EulogRed";i:309;s:8:"HydroVar";i:310;s:6:"OvePse";i:311;s:4:"FuPa";i:312;s:2:"Da";i:313;s:6:"MaSeSh";i:314;s:7:"PanisPr";i:315;s:4:"Pudi";i:316;s:2:"Su";i:317;s:7:"HyMeSqu";i:318;s:7:"MacPlPu";i:319;s:5:"PtiWo";i:320;s:2:"Ja";i:321;s:9:"FoShutTar";i:322;s:15:"HypoiRoughTrilo";i:323;s:10:"MePhylProc";i:324;s:13:"ExpIncreSugge";i:325;s:7:"PhyllTh";i:326;s:4:"Knoc";i:327;s:12:"CrTilloUnder";i:328;s:5:"CaPen";i:329;s:8:"ItaRequi";i:330;s:10:"NitReaTrop";i:331;s:4:"Kelp";i:332;s:7:"TelepVa";i:333;s:10:"MultiTriph";i:334;s:4:"Synu";i:335;s:13:"PremRobenUnre";i:336;s:6:"OveUnp";i:337;s:5:"DraSu";i:338;s:8:"FluProar";i:339;s:5:"UnjWi";i:340;s:4:"Tume";i:341;s:5:"ApEat";i:342;s:12:"BerEsopHippo";i:343;s:4:"Weig";i:344;s:7:"GloSupe";i:345;s:5:"Morib";i:346;s:7:"HaOzoRu";i:347;s:5:"EfMys";i:348;s:7:"BuoSpot";i:349;s:5:"Unhop";i:350;s:8:"IntNaiXa";i:351;s:8:"ReSerpVi";i:352;s:7:"RaUnVer";i:353;s:6:"CuEndl";i:354;s:8:"PopiWood";i:355;s:8:"LionPrSa";i:356;s:10:"SuppUneWre";i:357;s:5:"Vaing";i:358;s:4:"Pale";i:359;s:7:"OveSpha";i:360;s:7:"MiRossx";i:361;s:4:"High";i:362;s:9:"PlaWhZama";i:363;s:11:"GalacMicPan";i:364;s:5:"Tonop";i:365;s:4:"Hera";i:366;s:8:"DermPrUn";i:367;s:5:"Branc";i:368;s:10:"StchUpVulg";i:369;s:12:"OverPecSerol";i:370;s:5:"QuaRe";i:371;s:13:"SubiSyllaUltr";i:372;s:3:"Tar";i:373;s:8:"RigwiSqu";i:374;s:4:"Wabb";i:375;s:12:"LotifSaZooto";i:376;s:5:"Strat";i:377;s:7:"DisDiLa";i:378;s:3:"Una";i:379;s:10:"LandfMesSu";i:380;s:7:"PeavRol";i:381;s:13:"GrudgPerTeuto";i:382;s:12:"ChiCrypUnflo";i:383;s:5:"Front";i:384;s:4:"Erud";i:385;s:7:"PenSqua";i:386;s:11:"BeyonCheMal";i:387;s:3:"Sli";i:388;s:4:"Uter";i:389;s:8:"CloaPenc";i:390;s:8:"PoShiUnp";i:391;s:5:"Deami";i:392;s:9:"LeucMelli";i:393;s:6:"PresQu";i:394;s:6:"HepUnr";i:395;s:9:"VioloVisc";i:396;s:10:"SeTolyWate";i:397;s:5:"Lestr";i:398;s:4:"Wood";i:399;s:10:"IntOddmeTr";i:400;s:10:"PerioRotul";i:401;s:4:"Stol";i:402;s:11:"EntomSouSyn";i:403;s:4:"Semi";i:404;s:6:"DurrUn";i:405;s:7:"DiscoOv";i:406;s:9:"PangiProl";i:407;s:9:"NapRecSqu";i:408;s:3:"Unr";i:409;s:13:"GamesTetanUns";i:410;s:8:"FalMetRa";i:411;s:14:"BataDenouSaxon";i:412;s:5:"Solvo";i:413;s:14:"ScytaTighUnjoy";i:414;s:4:"Glut";i:415;s:4:"EkFi";i:416;s:6:"ErNoPa";i:417;s:6:"LaSpal";i:418;s:11:"JoRackRepat";i:419;s:10:"ReSorreUnd";i:420;s:12:"MisenRiTextu";i:421;s:4:"Wayb";i:422;s:6:"CampQu";i:423;s:11:"BrNapolSulp";i:424;s:4:"Snow";i:425;s:2:"Mo";i:426;s:5:"Tehua";i:427;s:13:"PastPressUnwi";i:428;s:5:"Entom";i:429;s:4:"SoTr";i:430;s:4:"Soft";i:431;s:10:"ChElephUnd";i:432;s:3:"Whi";i:433;s:6:"UpkZoo";i:434;s:5:"PaPre";i:435;s:7:"LobxVal";i:436;s:2:"Di";i:437;s:7:"NarNonp";i:438;s:9:"HorUnexVe";i:439;s:5:"Summe";i:440;s:10:"CouLocoPli";i:441;s:11:"ReSnortUnas";i:442;s:7:"HeKnPre";i:443;s:5:"Shavi";i:444;s:2:"Fi";i:445;s:7:"KiOstra";i:446;s:6:"HeliJu";i:447;s:7:"MasdeNo";i:448;s:5:"Tapst";i:449;s:5:"NazVe";i:450;s:4:"Sque";i:451;s:6:"FrIcht";i:452;s:3:"Hyd";i:453;s:8:"InirLevi";i:454;s:7:"ElasIna";i:455;s:3:"Inc";i:456;s:8:"UnfonUns";i:457;s:10:"PeuceProma";i:458;s:10:"ParomPeSom";i:459;s:7:"ImpOuUn";i:460;s:7:"PeiseUn";i:461;s:3:"Tri";i:462;s:6:"IntrWa";i:463;s:13:"CapryRemigStr";i:464;s:5:"FulPr";i:465;s:9:"MuncRhomb";i:466;s:14:"LiteNaphtSarco";i:467;s:7:"TelecTr";i:468;s:8:"EngaMePr";i:469;s:5:"Minco";i:470;s:4:"HaMa";i:471;s:5:"Skitt";i:472;s:12:"MidWharWonde";i:473;s:5:"ArSto";i:474;s:5:"OrbPi";i:475;s:5:"Sulfo";i:476;s:11:"PeVoluWhalp";i:477;s:7:"MusToas";i:478;s:3:"Spi";i:479;s:6:"PhilSu";i:480;s:3:"Foc";i:481;s:11:"MansNardxPo";i:482;s:11:"RongaTheUnc";i:483;s:12:"ParsSemiSynt";i:484;s:5:"SuToc";i:485;s:7:"OsiSnib";i:486;s:3:"Dee";i:487;s:5:"Throa";i:488;s:12:"HydroReZealo";i:489;s:9:"MetapUnha";i:490;s:10:"HeHoastPen";i:491;s:3:"Het";i:492;s:4:"CaCo";i:493;s:7:"LimitPr";i:494;s:2:"Ho";i:495;s:8:"RodlVive";i:496;s:8:"UpsWatWe";i:497;s:6:"PerSin";i:498;s:4:"Righ";i:499;s:11:"OstraReUnde";i:500;s:5:"ScSko";i:501;s:3:"End";i:502;s:6:"NimPle";i:503;s:12:"QuadrUndoVet";i:504;s:6:"EkMeMu";i:505;s:12:"DefLacquTrad";i:506;s:7:"PrUndWa";i:507;s:12:"ProtoStroUnf";i:508;s:6:"ReTheo";i:509;s:3:"Pre";i:510;s:6:"OvUnfo";i:511;s:3:"Loi";i:512;s:5:"Cultu";i:513;s:5:"Fabul";i:514;s:4:"Succ";i:515;s:8:"MegaRegr";i:516;s:3:"Rur";i:517;s:3:"Ter";i:518;s:10:"ResisUnire";i:519;s:10:"HeloOveSem";i:520;s:12:"NatrSavoScaw";i:521;s:9:"MusoNeuri";i:522;s:6:"ChClia";i:523;s:5:"ParPs";i:524;s:11:"LabxMetaUnd";i:525;s:10:"SilkStuVux";i:526;s:12:"BroaScoSpouc";i:527;s:6:"PaUnde";i:528;s:10:"HovePanoRe";i:529;s:9:"InsulTric";i:530;s:9:"ExtForUnm";i:531;s:6:"FlatUn";i:532;s:3:"Var";i:533;s:11:"HypoeRevSup";i:534;s:14:"StasSubitUncry";i:535;s:13:"BattoStewSupe";i:536;s:6:"SneThe";i:537;s:6:"ConnSi";i:538;s:2:"Po";i:539;s:7:"BraQuid";i:540;s:11:"StomaUnVana";i:541;s:4:"TrUn";i:542;s:15:"SicyoStinkUnspe";i:543;s:10:"InManOcean";i:544;s:8:"IndLymPr";i:545;s:7:"OsseoVa";i:546;s:10:"HeteHyUnsu";i:547;s:6:"TricUn";i:548;s:7:"PlSeTal";i:549;s:7:"DyEpsom";i:550;s:3:"Stu";i:551;s:3:"Nem";i:552;s:5:"Spina";i:553;s:13:"EusFowlfOleag";i:554;s:8:"HeRadiSt";i:555;s:7:"NonjPar";i:556;s:8:"ChelaDai";i:557;s:10:"CommePeSem";i:558;s:9:"EhxMisOve";i:559;s:6:"FoUret";i:560;s:2:"Pi";i:561;s:4:"Inte";i:562;s:4:"GoUn";i:563;s:7:"PhilaSe";i:564;s:10:"ReaUnprVas";i:565;s:14:"ClapHuzzaWalli";i:566;s:7:"LaconTy";i:567;s:8:"EagThioc";i:568;s:3:"Uns";i:569;s:7:"DingaSe";i:570;s:2:"Ve";i:571;s:11:"ParaPyophTu";i:572;s:10:"KasbaOsteo";i:573;s:4:"Tang";i:574;s:8:"StiStrTo";i:575;s:12:"MiSubdrTurbu";i:576;s:11:"PostPrPreal";i:577;s:9:"EquHypoNi";i:578;s:9:"HiIndZeug";i:579;s:4:"Scat";i:580;s:14:"MesoSloomSpasm";i:581;s:10:"ProcoUnyie";i:582;s:4:"Nonr";i:583;s:6:"CompCo";i:584;s:4:"Kymo";i:585;s:12:"NuanPolygSel";i:586;s:11:"LeafaLegZin";i:587;s:8:"RhomVent";i:588;s:9:"PeteRaSul";i:589;s:12:"DesSemblUret";i:590;s:4:"Zoog";i:591;s:8:"ExamiPre";i:592;s:10:"AmpManoRet";i:593;s:4:"Same";i:594;s:2:"Ty";i:595;s:3:"Spr";i:596;s:4:"Eels";i:597;s:5:"Motor";i:598;s:9:"ScyphUnli";i:599;s:12:"DefGanoUnalo";i:600;s:7:"EpEstRo";i:601;s:4:"Pana";i:602;s:10:"CanaEnlSca";i:603;s:9:"HomRoamRo";i:604;s:7:"HanKeWh";i:605;s:5:"DeIch";i:606;s:8:"CapInSto";i:607;s:7:"PenneVo";i:608;s:11:"ElatTardiVi";i:609;s:7:"ManUnvi";i:610;s:10:"OutbPhPrem";i:611;s:11:"AttrBarDros";i:612;s:8:"InofUrod";i:613;s:3:"Oli";i:614;s:11:"EncImpSchai";i:615;s:4:"Palm";i:616;s:3:"Vit";i:617;s:10:"OphthPyret";i:618;s:2:"So";i:619;s:6:"ExacPr";i:620;s:7:"MonPrTr";i:621;s:12:"MaraMystUnam";i:622;s:4:"Vela";i:623;s:6:"PoSupe";i:624;s:10:"CathCoaThu";i:625;s:5:"ToUpc";i:626;s:3:"Toc";i:627;s:9:"GrePraSli";i:628;s:4:"Sams";i:629;s:8:"ChaHoMon";i:630;s:2:"My";i:631;s:7:"HypItin";i:632;s:11:"GloKoxScrib";i:633;s:9:"SalaUnpro";i:634;s:8:"ImpacSan";i:635;s:3:"Rep";i:636;s:9:"ParaPuggi";i:637;s:8:"RebSheUn";i:638;s:9:"MaObRedis";i:639;s:9:"GuStomTir";i:640;s:4:"Isos";i:641;s:9:"SopoUnUnr";i:642;s:8:"PedroUnd";i:643;s:4:"Ward";i:644;s:3:"Val";i:645;s:4:"Plat";i:646;s:6:"RotUns";i:647;s:7:"PermiRe";i:648;s:6:"ConvSc";i:649;s:4:"Knic";i:650;s:7:"HemImme";i:651;s:10:"HandOutSti";i:652;s:12:"DioEpirrNesi";i:653;s:5:"Yodel";i:654;s:11:"BrangOdUnem";i:655;s:4:"Rebr";i:656;s:3:"Sys";i:657;s:10:"PeRegrTong";i:658;s:8:"GlResiUn";i:659;s:10:"NeapxOsteo";i:660;s:9:"LappPsRev";i:661;s:12:"PrePromTrica";i:662;s:5:"Prota";i:663;s:6:"TablUr";i:664;s:9:"CimbMenta";i:665;s:12:"DesHoaxxUnca";i:666;s:10:"LithoSteat";i:667;s:4:"Goyx";i:668;s:13:"PignoPrehSigx";i:669;s:2:"St";i:670;s:13:"BacDigreMontr";i:671;s:7:"TraUnro";i:672;s:7:"HikRiva";i:673;s:7:"CoPeUnp";i:674;s:5:"Thoro";i:675;s:5:"Vibro";i:676;s:7:"HoRudim";i:677;s:5:"Satis";i:678;s:14:"SulkiSyndTugri";i:679;s:8:"AnEpSius";i:680;s:7:"MyPomWe";i:681;s:11:"PathPepsiPl";i:682;s:7:"PerPlut";i:683;s:8:"MorPrete";i:684;s:8:"PiProUnf";i:685;s:7:"SleUnem";i:686;s:10:"BimPolypVa";i:687;s:6:"DzeThi";i:688;s:6:"DolEmp";i:689;s:5:"Skibb";i:690;s:3:"Pen";i:691;s:12:"SpinSymWaysi";i:692;s:9:"CamHomoTe";i:693;s:7:"HemopRa";i:694;s:13:"ConsParsoReri";i:695;s:7:"HumorKh";i:696;s:3:"Meg";i:697;s:9:"HuckInval";i:698;s:5:"CoSto";i:699;s:5:"Wykeh";i:700;s:8:"PeUnscUn";i:701;s:13:"LaminLeisOvov";i:702;s:4:"Unco";i:703;s:5:"HiUnd";i:704;s:9:"ForMeUnri";i:705;s:6:"RhymUn";i:706;s:8:"UnderUrn";i:707;s:13:"QuadrRuntTort";i:708;s:9:"HemiRepur";i:709;s:9:"EuPiSemin";i:710;s:6:"SubcSu";i:711;s:9:"MorNitcSl";i:712;s:10:"PreteVitel";i:713;s:7:"ReducTr";i:714;s:7:"NonSkew";i:715;s:5:"Rejec";i:716;s:6:"ImNuTe";i:717;s:10:"FlaForeHyp";i:718;s:5:"Stech";i:719;s:11:"CypHasteSpo";i:720;s:5:"Ocell";i:721;s:10:"ObtaiThrim";i:722;s:14:"DihyPhenaSmili";i:723;s:14:"ForwaPeripSius";i:724;s:15:"DropcOlivaPippe";i:725;s:10:"SubbeWhite";i:726;s:5:"Pneum";i:727;s:8:"OpprRaga";i:728;s:5:"DesDi";i:729;s:8:"HelioSup";i:730;s:10:"ParaPiSauc";i:731;s:3:"Sor";i:732;s:6:"SeptSu";i:733;s:5:"DrPot";i:734;s:8:"CoOutWau";i:735;s:6:"CeWold";i:736;s:8:"VivenWhi";i:737;s:10:"BandiQuadr";i:738;s:13:"MesiPlanSunni";i:739;s:3:"Urs";i:740;s:10:"KiUnchUnes";i:741;s:12:"MynaxSeTroch";i:742;s:4:"Shut";i:743;s:7:"ObSpurg";i:744;s:3:"Boy";i:745;s:10:"GuitaUnsub";i:746;s:4:"GrPr";i:747;s:11:"MonStrumSus";i:748;s:9:"DiSenTurr";i:749;s:4:"Unop";i:750;s:10:"CansoPrion";i:751;s:12:"HieMesaParoa";i:752;s:3:"Sil";i:753;s:8:"OpPoScio";i:754;s:9:"TrumpTwUn";i:755;s:14:"DoomsEtymoUnch";i:756;s:5:"HypUn";i:757;s:11:"InterMetOoz";i:758;s:9:"NondOcSta";i:759;s:9:"OinOtorRe";i:760;s:11:"IndiJapaUnd";i:761;s:8:"SnufUnsc";i:762;s:2:"Gr";i:763;s:10:"OpePupilUn";i:764;s:12:"IrreLiqueTra";i:765;s:5:"Uncol";i:766;s:4:"MeVe";i:767;s:6:"BlepVa";i:768;s:8:"PreSubva";i:769;s:6:"MaPrep";i:770;s:7:"PuUnnat";i:771;s:6:"JuriKh";i:772;s:9:"CoprGastr";i:773;s:9:"EuergOvSi";i:774;s:5:"Hyper";i:775;s:9:"StoSulpUn";i:776;s:5:"Unfop";i:777;s:4:"Retr";i:778;s:8:"BunchPos";i:779;s:5:"NorSu";i:780;s:12:"HordRunnUnea";i:781;s:7:"InterLi";i:782;s:11:"EnvOdonStew";i:783;s:10:"DiaMesNonc";i:784;s:8:"HematThe";i:785;s:3:"Ski";i:786;s:5:"Kodak";i:787;s:9:"DoRedouRe";i:788;s:2:"Sp";i:789;s:5:"Unfil";i:790;s:9:"RubicWarb";i:791;s:9:"HemiUinta";i:792;s:7:"OulSupr";i:793;s:10:"HieMuPelop";i:794;s:3:"Dis";i:795;s:5:"Telot";i:796;s:3:"Cza";i:797;s:5:"KidLa";i:798;s:4:"Nase";i:799;s:3:"Sul";i:800;s:8:"CubbyRed";i:801;s:8:"OrPectTo";i:802;s:7:"SeSvaUp";i:803;s:6:"SerWit";i:804;s:3:"Spo";i:805;s:10:"MeProbSmyr";i:806;s:5:"Tunab";i:807;s:9:"HolInVene";i:808;s:10:"ClaInlieRe";i:809;s:4:"Carb";i:810;s:13:"ReseSteeUncon";i:811;s:9:"UnbarWhit";i:812;s:11:"OstPhthiStu";i:813;s:5:"Whenn";i:814;s:4:"Ungl";i:815;s:3:"Sch";i:816;s:9:"PictuReco";i:817;s:10:"LyPremoTyp";i:818;s:7:"PagStal";i:819;s:5:"Sogdi";i:820;s:5:"UnUno";i:821;s:5:"Anast";i:822;s:11:"IsocMasOtos";i:823;s:7:"RayReli";i:824;s:4:"Umbe";i:825;s:7:"CortMil";i:826;s:8:"ProSaddl";i:827;s:6:"LigWob";i:828;s:6:"ReSawi";i:829;s:3:"Inw";i:830;s:5:"Recre";i:831;s:7:"PantaPs";i:832;s:7:"LuNoUna";i:833;s:9:"FeliPoTar";i:834;s:13:"RajaxSoweWhor";i:835;s:6:"HeHorn";i:836;s:2:"Vi";i:837;s:5:"Sulph";i:838;s:7:"ScalSco";i:839;s:6:"SuSwin";i:840;s:12:"PleTempUnspl";i:841;s:4:"Disl";i:842;s:6:"PuTach";i:843;s:5:"Methy";i:844;s:9:"StThreeWa";i:845;s:8:"MetaPeri";i:846;s:12:"KoMuttoPseud";i:847;s:3:"Yot";i:848;s:8:"UnleaUnt";i:849;s:12:"NoTammyZoosp";i:850;s:8:"TendUnre";i:851;s:11:"ChunSiSlate";i:852;s:11:"ReverTheUni";i:853;s:14:"SynoWhipsYokel";i:854;s:4:"Undi";i:855;s:10:"HoLodgeTum";i:856;s:10:"GoralLaWul";i:857;s:7:"OnPrede";i:858;s:6:"LieSac";i:859;s:9:"BemuGyrin";i:860;s:3:"Ung";i:861;s:4:"Retu";i:862;s:9:"DesipSavi";i:863;s:8:"RouSandi";i:864;s:4:"Prec";i:865;s:10:"GastPrStra";i:866;s:4:"Muli";i:867;s:6:"DeFebr";i:868;s:11:"IcacPoltrUn";i:869;s:5:"Trach";i:870;s:7:"MisadUn";i:871;s:15:"GaywiOverdScorp";i:872;s:11:"ChiliIsSono";i:873;s:4:"Than";i:874;s:4:"Tube";i:875;s:5:"Mermi";i:876;s:10:"KuskUbUnwo";i:877;s:9:"GeHeMicro";i:878;s:2:"Py";i:879;s:2:"Vo";i:880;s:9:"FrThTrans";i:881;s:13:"RipiSubuWorme";i:882;s:11:"AssigCoenOu";i:883;s:4:"DiUn";i:884;s:12:"NonsRotiWhat";i:885;s:5:"NoUni";i:886;s:6:"OmPhle";i:887;s:4:"Para";i:888;s:3:"Rev";i:889;s:4:"ShSt";i:890;s:8:"MahaPunn";i:891;s:7:"StatoUn";i:892;s:10:"GalliNecta";i:893;s:12:"JuMintmWouch";i:894;s:9:"SempeSpir";i:895;s:13:"OphthSemidSty";i:896;s:8:"CenTripe";i:897;s:8:"SipunUne";i:898;s:5:"Uvulo";i:899;s:4:"Verb";i:900;s:8:"CuprMaPa";i:901;s:4:"PhUn";i:902;s:7:"PulTher";i:903;s:5:"Suppo";i:904;s:8:"BombCeGl";i:905;s:11:"CoccPaSerot";i:906;s:6:"CoExTr";i:907;s:9:"PaPatPedi";i:908;s:5:"Sillo";i:909;s:10:"DrysPyreSy";i:910;s:7:"NoVermi";i:911;s:8:"SaddVuln";i:912;s:10:"NonTessUns";i:913;s:8:"HeSemiUn";i:914;s:5:"ReUns";i:915;s:10:"IntePiSiki";i:916;s:6:"BoleCo";i:917;s:6:"DisgSa";i:918;s:3:"Rem";i:919;s:9:"ChroOverd";i:920;s:3:"Unt";i:921;s:7:"HeSands";i:922;s:6:"PrShTi";i:923;s:10:"NecroPrere";i:924;s:10:"OoeciPhUns";i:925;s:3:"Ham";i:926;s:7:"GaReful";i:927;s:7:"BunyPed";i:928;s:13:"IntoParalZigz";i:929;s:5:"Jacks";i:930;s:2:"Wr";i:931;s:6:"SalUnc";i:932;s:9:"GeSlovTic";i:933;s:9:"NeighPain";i:934;s:3:"Und";i:935;s:6:"OutTri";i:936;s:4:"Teii";i:937;s:5:"Taint";i:938;s:5:"Guill";i:939;s:10:"AzonxFaMoa";i:940;s:10:"InterSeUnc";i:941;s:7:"OveSobr";i:942;s:8:"ReTrihUn";i:943;s:7:"CyWordi";i:944;s:9:"TurnaWash";i:945;s:8:"RevoUngy";i:946;s:6:"PhoRer";i:947;s:5:"Whizz";i:948;s:13:"ProUndesUnsal";i:949;s:11:"StitcUpWitx";i:950;s:4:"Pert";i:951;s:3:"Tac";i:952;s:9:"EmulsProu";i:953;s:6:"UnwaUs";i:954;s:8:"FelTaxia";i:955;s:8:"ReResScl";i:956;s:6:"SemUnp";i:957;s:8:"OvTopWhi";i:958;s:8:"IntReaWo";i:959;s:4:"PlTo";i:960;s:14:"OveraPositUnpl";i:961;s:5:"Trest";i:962;s:10:"BixaChloVi";i:963;s:6:"CharGr";i:964;s:7:"ImSemUn";i:965;s:5:"Leadw";i:966;s:4:"Ples";i:967;s:11:"PsStepVirul";i:968;s:8:"NoOoSpir";i:969;s:10:"HeraImWhee";i:970;s:6:"SaTrag";i:971;s:10:"ForweSubre";i:972;s:11:"BegloEffHyd";i:973;s:8:"ChatTido";i:974;s:5:"RiSup";i:975;s:5:"Nonex";i:976;s:10:"JonMaParap";i:977;s:10:"ChapeDovEn";i:978;s:5:"Twizz";i:979;s:7:"MuckPra";i:980;s:8:"HurklLus";i:981;s:10:"MortProVal";i:982;s:9:"FroLimiRe";i:983;s:9:"HoliMimTr";i:984;s:5:"Besca";i:985;s:7:"MetroRu";i:986;s:13:"SawfSemibUngu";i:987;s:9:"ForePicco";i:988;s:9:"ParSysTee";i:989;s:4:"Pres";i:990;s:7:"HeJaile";i:991;s:3:"Ref";i:992;s:6:"MusgRe";i:993;s:3:"Her";i:994;s:4:"Hyet";i:995;s:7:"HelioPo";i:996;s:6:"BasiYa";i:997;s:8:"ProtVell";i:998;s:7:"NonoWea";i:999;s:7:"SubrUnm";i:1000;s:11:"ConEboniVer";i:1001;s:3:"Uni";i:1002;s:9:"IdePlasUn";i:1003;s:9:"PronStabi";i:1004;s:14:"QuinSquabUnrig";i:1005;s:2:"Ob";i:1006;s:7:"NatchOx";i:1007;s:5:"IsXan";i:1008;s:6:"JaReel";i:1009;s:8:"ProRhota";i:1010;s:4:"Vell";i:1011;s:5:"Hemop";i:1012;s:5:"Searc";i:1013;s:8:"EntaUnst";i:1014;s:12:"MonolNoRajax";i:1015;s:13:"DanPolynStere";i:1016;s:13:"TubUnattZogan";i:1017;s:5:"Raini";i:1018;s:5:"SpiSp";i:1019;s:8:"PosolPro";i:1020;s:6:"AlysEc";i:1021;s:10:"PoRedetThe";i:1022;s:10:"RecoiUglil";i:1023;s:10:"AntifCrIns";i:1024;s:3:"Pse";i:1025;s:12:"NonuSemSeria";i:1026;s:12:"PanSourUncom";i:1027;s:7:"FiMatan";i:1028;s:6:"RattVe";i:1029;s:6:"SpaTom";i:1030;s:12:"GebbiPasTess";i:1031;s:9:"PhotUncUn";i:1032;s:6:"RicTar";i:1033;s:10:"PlagiSeque";i:1034;s:3:"Zer";i:1035;s:5:"Denom";i:1036;s:14:"LentiPrecaUnpr";i:1037;s:6:"PathSu";i:1038;s:4:"PuSh";i:1039;s:10:"LaLinoPrem";i:1040;s:11:"IsPteryViru";i:1041;s:6:"HerbLo";i:1042;s:10:"FebKobusOv";i:1043;s:8:"ChiloWai";i:1044;s:12:"PosTetraTong";i:1045;s:5:"StUni";i:1046;s:11:"SuccTriliUn";i:1047;s:12:"PertSymmUnsu";i:1048;s:8:"UnshrZea";i:1049;s:6:"KonMar";i:1050;s:8:"LimTidbi";i:1051;s:6:"IsMePe";i:1052;s:7:"MarWher";i:1053;s:8:"OssifPen";i:1054;s:3:"Nig";i:1055;s:9:"NoRevuWar";i:1056;s:14:"CatalNympUnfal";i:1057;s:4:"Prep";i:1058;s:2:"Pe";i:1059;s:13:"InsciProSulph";i:1060;s:5:"DysHa";i:1061;s:2:"Wa";i:1062;s:5:"Goldb";i:1063;s:8:"DiHaWhey";i:1064;s:5:"SpThr";i:1065;s:8:"MesoUncu";i:1066;s:8:"SaUncoVi";i:1067;s:5:"SemUn";i:1068;s:13:"EmplHumanPitt";i:1069;s:5:"AmpHa";i:1070;s:6:"LoPrec";i:1071;s:9:"OnRegleSn";i:1072;s:8:"CurDesUn";i:1073;s:7:"NasUnex";i:1074;s:9:"ChulPrudy";i:1075;s:7:"SacUnUp";i:1076;s:10:"InMesMisca";i:1077;s:5:"StrVe";i:1078;s:10:"ForHymeTap";i:1079;s:4:"Ruin";i:1080;s:9:"KurbaOxhe";i:1081;s:9:"DamScyTet";i:1082;s:3:"Int";i:1083;s:9:"ClypeHair";i:1084;s:10:"DoNicolSpe";i:1085;s:6:"PraUne";i:1086;s:2:"He";i:1087;s:9:"SigniUnbr";i:1088;s:10:"IntePlaTor";i:1089;s:3:"Kol";i:1090;s:11:"MesRomUnshu";i:1091;s:5:"Calfl";i:1092;s:5:"Pyrol";i:1093;s:5:"Trous";i:1094;s:7:"LanSpew";i:1095;s:12:"SylTyranUnre";i:1096;s:8:"PodRetra";i:1097;s:2:"Ox";i:1098;s:6:"PosTel";i:1099;s:5:"SynWa";i:1100;s:6:"DauUnb";i:1101;s:13:"PseuThorUropt";i:1102;s:13:"ScleThermVirt";i:1103;s:3:"Imp";i:1104;s:4:"Repr";i:1105;s:8:"SacriSub";i:1106;s:6:"GrSolo";i:1107;s:8:"OvPoShad";i:1108;s:11:"ConfParisRe";i:1109;s:8:"CnidoUna";i:1110;s:2:"Gl";i:1111;s:3:"Inf";i:1112;s:11:"OxyneRehaWi";i:1113;s:3:"Cou";i:1114;s:4:"PhSh";i:1115;s:7:"ExcSubt";i:1116;s:7:"PlProto";i:1117;s:8:"HelleWor";i:1118;s:10:"MyOvertThu";i:1119;s:4:"Unsu";i:1120;s:10:"UnUnindWor";i:1121;s:4:"Tabo";i:1122;s:8:"SoSphTuz";i:1123;s:7:"SeptSpi";i:1124;s:10:"IsoOcRedic";i:1125;s:5:"Shane";i:1126;s:8:"HydroSem";i:1127;s:14:"MattyNeossSemi";i:1128;s:9:"MetaPeSac";i:1129;s:5:"Under";i:1130;s:11:"MonopNodiSk";i:1131;s:5:"RecSu";i:1132;s:10:"PensiSnipt";i:1133;s:7:"EnchoKe";i:1134;s:8:"DisDrTae";i:1135;s:5:"Indem";i:1136;s:5:"DefUp";i:1137;s:5:"EpiIn";i:1138;s:12:"GastrSephSub";i:1139;s:8:"FosUnlie";i:1140;s:5:"Tekya";i:1141;s:8:"NonfeZib";i:1142;s:12:"StyloSubmTha";i:1143;s:7:"UnhUnst";i:1144;s:12:"DispNontPern";i:1145;s:4:"Cary";i:1146;s:2:"Fr";i:1147;s:2:"Cr";i:1148;s:7:"PhlebTs";i:1149;s:8:"FletcUne";i:1150;s:8:"HypeMaUn";i:1151;s:12:"ImprMalSuper";i:1152;s:5:"SacUn";i:1153;s:11:"RubSchatSup";i:1154;s:7:"HeSurfb";i:1155;s:13:"MagTaipoTript";i:1156;s:7:"LesSubs";i:1157;s:7:"PrTreTr";i:1158;s:7:"IvUnUns";i:1159;s:10:"HidagIntRe";i:1160;s:11:"NoSaleSpinu";i:1161;s:5:"Myalg";i:1162;s:10:"DeLenSubla";i:1163;s:7:"SurgyTr";i:1164;s:5:"OuPes";i:1165;s:7:"MarMuWe";i:1166;s:6:"FestFl";i:1167;s:7:"PhQuiUn";i:1168;s:5:"Scabr";i:1169;s:5:"SpTra";i:1170;s:4:"Dact";i:1171;s:5:"Reorg";i:1172;s:8:"LuPapSab";i:1173;s:3:"Epi";i:1174;s:11:"HairSpStrat";i:1175;s:13:"IlleiSemicSha";i:1176;s:14:"DespPropoRepla";i:1177;s:5:"Chymi";i:1178;s:7:"FoUnwre";i:1179;s:5:"Unper";i:1180;s:3:"Jel";i:1181;s:3:"Ego";i:1182;s:6:"CryPre";i:1183;s:5:"PrUnt";i:1184;s:2:"Rh";i:1185;s:6:"FaVamp";i:1186;s:8:"WesteYel";i:1187;s:4:"Limn";i:1188;s:9:"ClamHeUra";i:1189;s:10:"CrooEupShe";i:1190;s:11:"ChDandiMort";i:1191;s:7:"EsEveNe";i:1192;s:14:"LatitToniVoidl";i:1193;s:10:"HadLitNong";i:1194;s:4:"Psyc";i:1195;s:3:"Oct";i:1196;s:3:"Une";i:1197;s:9:"RaScuseSp";i:1198;s:8:"InherRor";i:1199;s:6:"ParStr";i:1200;s:7:"MetaPau";i:1201;s:9:"SeiUnUnva";i:1202;s:9:"TaUntVerj";i:1203;s:8:"CheTelot";i:1204;s:10:"InceKiMurz";i:1205;s:15:"DomicImoliLiter";i:1206;s:5:"CopDe";i:1207;s:3:"Amo";i:1208;s:10:"ParalUnent";i:1209;s:2:"Ea";i:1210;s:4:"Unbi";i:1211;s:13:"PachPlagiTrep";i:1212;s:4:"LyUn";i:1213;s:2:"Wo";i:1214;s:7:"PlatRom";i:1215;s:12:"ChiQuitSulph";i:1216;s:11:"AuPennoRoll";i:1217;s:13:"LarriPallPron";i:1218;s:8:"MailpPyg";i:1219;s:4:"PiTa";i:1220;s:4:"Unam";i:1221;s:9:"HedgeStra";i:1222;s:6:"GynKit";i:1223;s:2:"Sh";i:1224;s:10:"CubInStati";i:1225;s:2:"Mi";i:1226;s:8:"PeRefSou";i:1227;s:5:"InInf";i:1228;s:5:"OveVi";i:1229;s:10:"ReacUnsVal";i:1230;s:5:"Silve";i:1231;s:5:"GooUn";i:1232;s:6:"ThVict";i:1233;s:10:"MalPerShin";i:1234;s:4:"Chuc";i:1235;s:7:"RiaSerr";i:1236;s:11:"OverPhyUlli";i:1237;s:8:"CepEnvie";i:1238;s:8:"ManSubmi";i:1239;s:10:"CarErysPyr";i:1240;s:8:"PlaSpecu";i:1241;s:9:"DraOveOve";i:1242;s:9:"GiddyHyWe";i:1243;s:4:"Cala";i:1244;s:5:"ChHyr";i:1245;s:10:"HeterPoSyn";i:1246;s:7:"FrMaPer";i:1247;s:7:"SeeUnbe";i:1248;s:5:"Redto";i:1249;s:13:"FonsxIllPreli";i:1250;s:7:"InOvers";i:1251;s:5:"Thril";i:1252;s:10:"DisplMicVo";i:1253;s:9:"ImpeInSem";i:1254;s:4:"Osch";i:1255;s:4:"Duog";i:1256;s:9:"NonPuShre";i:1257;s:14:"OstreSergToolm";i:1258;s:9:"HyakMahPa";i:1259;s:7:"PalRefr";i:1260;s:7:"PoTroch";i:1261;s:12:"GiddyInWhipp";i:1262;s:7:"BroDrys";i:1263;s:6:"IllRef";i:1264;s:10:"CommaLabSt";i:1265;s:5:"Apoll";i:1266;s:2:"Ne";i:1267;s:3:"Kat";i:1268;s:7:"PoTinct";i:1269;s:10:"NasSynosUn";i:1270;s:4:"Pant";i:1271;s:5:"Unmis";i:1272;s:5:"UnVin";i:1273;s:4:"Ordi";i:1274;s:10:"MazocPrebe";i:1275;s:3:"Unw";i:1276;s:4:"TyUn";i:1277;s:6:"MetaUn";i:1278;s:4:"Thri";i:1279;s:11:"OokiSilkwSt";i:1280;s:5:"OvPed";i:1281;s:6:"MesoSq";i:1282;s:3:"Per";i:1283;s:9:"ImTraWrit";i:1284;s:2:"In";i:1285;s:9:"NaSliceWh";i:1286;s:8:"PolTherm";i:1287;s:4:"Topm";i:1288;s:9:"CasPnTita";i:1289;s:5:"Unsur";i:1290;s:6:"HeInPr";i:1291;s:8:"AtGiMisp";i:1292;s:14:"BathuEpicyPhor";i:1293;s:5:"Windb";i:1294;s:7:"DoDomUn";i:1295;s:7:"LetPrer";i:1296;s:4:"Subu";i:1297;s:12:"MacroOrtSwee";i:1298;s:11:"DarapEelHoo";i:1299;s:7:"UnaWeWu";i:1300;s:10:"HydrJoiOve";i:1301;s:4:"Rein";i:1302;s:9:"GuarPedia";i:1303;s:5:"Primo";i:1304;s:5:"Pregn";i:1305;s:2:"Tw";i:1306;s:11:"KinMemorTou";i:1307;s:14:"LandbMantSuper";i:1308;s:4:"Pers";i:1309;s:5:"DiRho";i:1310;s:9:"KabaRinne";i:1311;s:2:"To";i:1312;s:4:"Stom";i:1313;s:5:"Movie";i:1314;s:12:"ProtoSpoUnsy";i:1315;s:12:"LiceSharUpso";i:1316;s:9:"SlStereWo";i:1317;s:4:"Tubb";i:1318;s:7:"EradiPe";i:1319;s:10:"OrthoVolum";i:1320;s:10:"LeaMattNon";i:1321;s:11:"PeSemiThrom";i:1322;s:3:"Yar";i:1323;s:12:"DetraGalvSki";i:1324;s:6:"ThyUna";i:1325;s:8:"DiRainRo";i:1326;s:12:"HarakHerVide";i:1327;s:5:"Plata";i:1328;s:4:"Occi";i:1329;s:7:"PaQuadr";i:1330;s:7:"SanToot";i:1331;s:10:"DecExploIn";i:1332;s:2:"Cl";i:1333;s:7:"UnbeUns";i:1334;s:6:"OphRel";i:1335;s:13:"SangSphenUnco";i:1336;s:4:"Unli";i:1337;s:15:"HorneNonopSnake";i:1338;s:11:"PhiSubjUnim";i:1339;s:6:"SaniSu";i:1340;s:3:"Man";i:1341;s:7:"UnUntre";i:1342;s:10:"DihRecapUn";i:1343;s:9:"IntNonrTr";i:1344;s:10:"CoryLerrWh";i:1345;s:10:"GimMonRetr";i:1346;s:4:"Phry";i:1347;s:6:"MyoWea";i:1348;s:3:"Was";i:1349;s:6:"GrHemi";i:1350;s:9:"InInfePre";i:1351;s:4:"NuPn";i:1352;s:14:"ChlorHeteProca";i:1353;s:7:"FroImpu";i:1354;s:6:"DiStee";i:1355;s:7:"CarnUnd";i:1356;s:11:"SentiTimeUn";i:1357;s:11:"ScrawVeYach";i:1358;s:5:"Utero";i:1359;s:8:"InfOfPho";i:1360;s:10:"MolSulphUn";i:1361;s:6:"CaDePr";i:1362;s:5:"InRea";i:1363;s:4:"Scra";i:1364;s:12:"JaPosteUnder";i:1365;s:7:"MuscVer";i:1366;s:12:"DimerEupaMeg";i:1367;s:4:"Grap";i:1368;s:11:"CadeHectoHo";i:1369;s:6:"SulThi";i:1370;s:4:"Hama";i:1371;s:9:"LymOuRemn";i:1372;s:4:"Mart";i:1373;s:6:"KaTrum";i:1374;s:3:"Way";i:1375;s:11:"KitLatPomon";i:1376;s:14:"IsotLeiomTrich";i:1377;s:9:"SteTarrUn";i:1378;s:10:"DeFoxTurnc";i:1379;s:8:"LeucNoPe";i:1380;s:9:"PeriRelSi";i:1381;s:9:"NoParTien";i:1382;s:6:"PupUna";i:1383;s:5:"PanSa";i:1384;s:9:"DeoKerxTu";i:1385;s:5:"Unwor";i:1386;s:2:"Mu";i:1387;s:14:"IodosOverWrith";i:1388;s:4:"HuPa";i:1389;s:7:"JocLame";i:1390;s:4:"Mame";i:1391;s:5:"Unspi";i:1392;s:11:"IthyLeisPos";i:1393;s:7:"ParapWa";i:1394;s:7:"TriliUn";i:1395;s:8:"TarTheer";i:1396;s:5:"Frust";i:1397;s:6:"GuTrun";i:1398;s:9:"MembrReco";i:1399;s:10:"KwartRheUr";i:1400;s:3:"Sem";i:1401;s:4:"Naph";i:1402;s:11:"ParRecUsuar";i:1403;s:12:"LegaMallPowd";i:1404;s:8:"OveProet";i:1405;s:11:"FinesOvXeno";i:1406;s:10:"PresuRattl";i:1407;s:3:"Dem";i:1408;s:13:"FrienRevSeque";i:1409;s:12:"BriHyperKrem";i:1410;s:13:"HomoMoribTouc";i:1411;s:4:"Wauk";i:1412;s:12:"SupeTonsiUns";i:1413;s:8:"KikuScyt";i:1414;s:5:"Scare";i:1415;s:12:"HippoPlPneum";i:1416;s:8:"GraNonfo";i:1417;s:7:"EuphVel";i:1418;s:5:"Undar";i:1419;s:12:"GampShriSurr";i:1420;s:2:"Wi";i:1421;s:7:"OsmPrUn";i:1422;s:10:"TarahUnabi";i:1423;s:7:"CaDawTy";i:1424;s:10:"GeomeLater";i:1425;s:8:"GelPaSup";i:1426;s:8:"RegreVol";i:1427;s:7:"LepMeio";i:1428;s:12:"HeMesolSpeec";i:1429;s:4:"Pred";i:1430;s:12:"EpParagRedex";i:1431;s:10:"MiPhilTyro";i:1432;s:8:"ParaSaga";i:1433;s:6:"EstNih";i:1434;s:11:"OrdiRevolSe";i:1435;s:3:"Pla";i:1436;s:12:"LepiRetSuper";i:1437;s:12:"HoloOperWild";i:1438;s:6:"InteVe";i:1439;s:4:"Whit";i:1440;s:11:"JuMinkoXant";i:1441;s:10:"BowmHelMal";i:1442;s:11:"KetazPrVeld";i:1443;s:4:"Intr";i:1444;s:7:"LeucSub";i:1445;s:7:"OveUnUn";i:1446;s:7:"MyelPos";i:1447;s:6:"PrSche";i:1448;s:2:"Ze";i:1449;s:5:"Pyrrh";i:1450;s:5:"ChaPh";i:1451;s:8:"RelUltra";i:1452;s:5:"Prere";i:1453;s:7:"FoProSe";i:1454;s:11:"MyoUnyiWeir";i:1455;s:8:"InLiTaro";i:1456;s:8:"QuintSty";i:1457;s:4:"Euch";i:1458;s:5:"Consu";i:1459;s:5:"Incon";i:1460;s:11:"HypoMagnPyc";i:1461;s:5:"PeUbi";i:1462;s:13:"HistoIlokaRas";i:1463;s:8:"ManoMiPl";i:1464;s:11:"LeathRedeSu";i:1465;s:11:"PalStucToba";i:1466;s:13:"HabitPriSaber";i:1467;s:5:"Table";i:1468;s:11:"SeneSubcToo";i:1469;s:11:"DiOffeSuper";i:1470;s:4:"Ruff";i:1471;s:12:"HyperPoiStor";i:1472;s:13:"BigwDirtUnasp";i:1473;s:3:"Pro";i:1474;s:6:"EpiExe";i:1475;s:10:"HypMetNahu";i:1476;s:10:"DimiValeWi";i:1477;s:4:"FiRa";i:1478;s:11:"DoSomnaUnta";i:1479;s:8:"BupMazam";i:1480;s:13:"CurtaOntSuppl";i:1481;s:5:"Waben";i:1482;s:10:"EpicrLymph";i:1483;s:7:"CasUnca";i:1484;s:8:"HatchWhe";i:1485;s:8:"HarmMole";i:1486;s:3:"Thi";i:1487;s:3:"Hal";i:1488;s:10:"CisSpuddVe";i:1489;s:11:"LiMismiPsyc";i:1490;s:11:"PolliSiVign";i:1491;s:10:"SeducShSta";i:1492;s:8:"SaugScab";i:1493;s:6:"IslaTr";i:1494;s:10:"ComImParat";i:1495;s:4:"Peri";i:1496;s:11:"GritLaRambe";i:1497;s:7:"EddZygo";i:1498;s:12:"BumInteMidma";i:1499;s:15:"InterOrnitPreme";i:1500;s:9:"PrSugSulf";i:1501;s:8:"IdePsych";i:1502;s:4:"Proc";i:1503;s:7:"CosmTri";i:1504;s:6:"CommUn";i:1505;s:5:"Sawfl";i:1506;s:5:"CusSe";i:1507;s:7:"TrUlste";i:1508;s:4:"Thyr";i:1509;s:5:"Persp";i:1510;s:9:"PedulSiVi";i:1511;s:6:"AmMyrm";i:1512;s:5:"Physi";i:1513;s:8:"SurcTolx";i:1514;s:5:"Cytop";i:1515;s:5:"GaRav";i:1516;s:13:"CarMelanUnuni";i:1517;s:6:"EdSubt";i:1518;s:7:"DotGeph";i:1519;s:7:"InNativ";i:1520;s:5:"Ozoty";i:1521;s:7:"PriScal";i:1522;s:4:"SeUn";i:1523;s:13:"FlexuIntYoudi";i:1524;s:3:"Tra";i:1525;s:8:"SubbiTap";i:1526;s:11:"ChloDrucGra";i:1527;s:8:"MarrMimi";i:1528;s:9:"DooSeSpec";i:1529;s:7:"GauzySe";i:1530;s:4:"Chio";i:1531;s:10:"RatafThVip";i:1532;s:6:"FruiGy";i:1533;s:15:"FoddeSulfoZeuct";i:1534;s:7:"BromiRe";i:1535;s:8:"OvPhrUlt";i:1536;s:3:"Unv";i:1537;s:5:"Lidfl";i:1538;s:5:"Uncon";i:1539;s:12:"SapodUnblUno";i:1540;s:13:"DomiHakimMola";i:1541;s:4:"Unth";i:1542;s:5:"FoOve";i:1543;s:9:"LendePoUn";i:1544;s:14:"ConfScrofTelle";i:1545;s:11:"MonoOutboTe";i:1546;s:11:"FeckTeUnfea";i:1547;s:5:"Wopsx";i:1548;s:8:"StaSulfa";i:1549;s:9:"CoalInapa";i:1550;s:6:"RisSqu";i:1551;s:12:"SubtWacexZyg";i:1552;s:11:"MonoMorSqua";i:1553;s:7:"DisFrTr";i:1554;s:9:"BonaIllit";i:1555;s:4:"Pron";i:1556;s:13:"LuculThaThutt";i:1557;s:3:"Unm";i:1558;s:9:"FelsOpine";i:1559;s:13:"LumpiStaiUnhu";i:1560;s:12:"HoIneliUnlat";i:1561;s:2:"Or";i:1562;s:11:"GoatrNoUnes";i:1563;s:12:"AlBenthPsych";i:1564;s:4:"Insu";i:1565;s:5:"Unver";i:1566;s:4:"Eulo";i:1567;s:7:"OmThroa";i:1568;s:9:"HyUnsUnwo";i:1569;s:5:"PeRes";i:1570;s:5:"DecTe";i:1571;s:6:"SpoZym";i:1572;s:5:"Satyr";i:1573;s:4:"Subc";i:1574;s:2:"As";i:1575;s:3:"Ret";i:1576;s:7:"JarblMy";i:1577;s:4:"Noni";i:1578;s:10:"BalusCacon";i:1579;s:10:"HazReiWard";i:1580;s:7:"QuisThe";i:1581;s:12:"SawflStrumTr";i:1582;s:4:"Witc";i:1583;s:12:"BenzoCheOver";i:1584;s:4:"Preo";i:1585;s:11:"DisseGaName";i:1586;s:8:"EleReadm";i:1587;s:8:"UnaUnZig";i:1588;s:7:"EskNeur";i:1589;s:9:"DisFanSco";i:1590;s:8:"FesJolRe";i:1591;s:10:"ConfuDoPre";i:1592;s:12:"CaptPseuRush";i:1593;s:12:"ScreXenomYar";i:1594;s:9:"RidiTarVa";i:1595;s:10:"HippMbaSaf";i:1596;s:14:"ElateNonreOver";i:1597;s:6:"MiPara";i:1598;s:10:"PterPuSear";i:1599;s:5:"Marsh";i:1600;s:5:"Stram";i:1601;s:8:"SheWippe";i:1602;s:9:"StreWench";i:1603;s:13:"SubauSycopZoo";i:1604;s:14:"LarunNonaPhyto";i:1605;s:8:"OrdeVigi";i:1606;s:11:"KumOphthPar";i:1607;s:4:"Depe";i:1608;s:10:"DecumPerio";i:1609;s:8:"ArchClMe";i:1610;s:7:"TawieWy";i:1611;s:5:"SupWr";i:1612;s:10:"ReshSaTydi";i:1613;s:8:"BeverDis";i:1614;s:11:"HypNeuroTru";i:1615;s:15:"DrinkFulmiSaper";i:1616;s:14:"GlycMachiNonre";i:1617;s:8:"TapiUrbi";i:1618;s:5:"InNun";i:1619;s:11:"ProTalegTot";i:1620;s:5:"GarUn";i:1621;s:5:"Signa";i:1622;s:10:"ProofUltra";i:1623;s:5:"Parti";i:1624;s:10:"HoWanlWitc";i:1625;s:5:"Ossua";i:1626;s:11:"ImpPreSuper";i:1627;s:8:"CurIsoRi";i:1628;s:4:"Play";i:1629;s:4:"Fore";i:1630;s:11:"DecuExtrInd";i:1631;s:12:"DysanHydPost";i:1632;s:7:"ReTelev";i:1633;s:9:"CionxPrSh";i:1634;s:6:"NatiUn";i:1635;s:4:"Grav";i:1636;s:4:"Tran";i:1637;s:3:"Kan";i:1638;s:12:"LichMakiUnne";i:1639;s:9:"LacMesPer";i:1640;s:7:"CoHidJi";i:1641;s:11:"MahSurUnlam";i:1642;s:10:"DeOutsWauk";i:1643;s:13:"MarPylorRoots";i:1644;s:6:"ExPehu";i:1645;s:5:"PaTak";i:1646;s:5:"Rubas";i:1647;s:8:"PhaseWit";i:1648;s:5:"Inhar";i:1649;s:12:"OsteRhyScapu";i:1650;s:13:"MonotRedeSupe";i:1651;s:10:"BlephDaPer";i:1652;s:8:"SylvTrUn";i:1653;s:6:"MePuSa";i:1654;s:4:"DuUn";i:1655;s:7:"DistSem";i:1656;s:5:"HuPer";i:1657;s:11:"PlPolyUnpea";i:1658;s:5:"FeeNe";i:1659;s:12:"ElegGrinZygo";i:1660;s:13:"PanteUnbloUns";i:1661;s:6:"LifTur";i:1662;s:4:"Unsi";i:1663;s:7:"PeoplUr";i:1664;s:5:"CerMa";i:1665;s:8:"TechnUns";i:1666;s:7:"ScleWhe";i:1667;s:11:"MazanNecPro";i:1668;s:10:"RecovSergi";i:1669;s:5:"Unidi";i:1670;s:8:"SunUninf";i:1671;s:9:"MoSciUnfa";i:1672;s:10:"FelsOstTri";i:1673;s:7:"ConSang";i:1674;s:3:"Unh";i:1675;s:7:"CrHyTri";i:1676;s:3:"Kne";i:1677;s:10:"EpiNonPhoe";i:1678;s:7:"HydNeig";i:1679;s:6:"BoOnse";i:1680;s:7:"UnhorVe";i:1681;s:13:"OtherSirTobex";i:1682;s:8:"LaPrebUn";i:1683;s:12:"MyeNitidRace";i:1684;s:11:"DisDorsPseu";i:1685;s:4:"Unne";i:1686;s:10:"ComatTashr";i:1687;s:5:"Chrom";i:1688;s:8:"ScolTene";i:1689;s:6:"PholTi";i:1690;s:5:"EnoUn";i:1691;s:8:"MaxNonsp";i:1692;s:11:"MisStaphTal";i:1693;s:6:"PsSlUn";i:1694;s:7:"SeUlste";i:1695;s:10:"PreThrepUn";i:1696;s:3:"Min";i:1697;s:6:"SplZoo";i:1698;s:4:"Obit";i:1699;s:9:"LiRicoSar";i:1700;s:9:"PsReReind";i:1701;s:2:"Gu";i:1702;s:11:"PioSemUncha";i:1703;s:10:"DebamInsPl";i:1704;s:4:"CoNo";i:1705;s:3:"Pat";i:1706;s:5:"Rhino";i:1707;s:12:"GladdInUnscr";i:1708;s:5:"Gargo";i:1709;s:3:"Hog";i:1710;s:4:"Tany";i:1711;s:9:"FacepUngr";i:1712;s:6:"DesUnd";i:1713;s:3:"Tap";i:1714;s:6:"ReUnde";i:1715;s:2:"Qu";i:1716;s:7:"IntuPar";i:1717;s:5:"LopPr";i:1718;s:4:"Unta";i:1719;s:3:"Tin";i:1720;s:3:"Mot";i:1721;s:10:"MemorRemic";i:1722;s:11:"FistlPanTyi";i:1723;s:12:"GhoGuarRelbu";i:1724;s:3:"Coo";i:1725;s:6:"NotPig";i:1726;s:4:"Orna";i:1727;s:9:"InNonalTh";i:1728;s:7:"AlConen";i:1729;s:8:"QuiSyncx";i:1730;s:12:"HematLarTear";i:1731;s:4:"Link";i:1732;s:7:"PolarVa";i:1733;s:14:"MacroParvRuina";i:1734;s:7:"KeelRep";i:1735;s:4:"UnWo";i:1736;s:13:"PolygRemTucun";i:1737;s:9:"LoxoReSin";i:1738;s:12:"OcPassaSciss";i:1739;s:9:"CopeCorre";i:1740;s:12:"GawLaborTeta";i:1741;s:8:"CoHeSupe";i:1742;s:7:"ProtTel";i:1743;s:7:"RaUnplu";i:1744;s:3:"Try";i:1745;s:4:"Tols";i:1746;s:2:"Sn";i:1747;s:8:"CincUnco";i:1748;s:11:"CondPytUnre";i:1749;s:7:"CeMePro";i:1750;s:5:"Myrme";i:1751;s:4:"Skew";i:1752;s:3:"Usa";i:1753;s:8:"SabbaSla";i:1754;s:9:"GlePrThyr";i:1755;s:7:"FlRhina";i:1756;s:11:"ReaSilicUnb";i:1757;s:9:"InPahTeer";i:1758;s:4:"Gude";i:1759;s:9:"KaxPyosUn";i:1760;s:5:"Bianc";i:1761;s:10:"SelenSquas";i:1762;s:9:"RhinUntuc";i:1763;s:8:"PesSupUn";i:1764;s:7:"SyTeneb";i:1765;s:3:"Gly";i:1766;s:12:"MethaMucPara";i:1767;s:14:"DemaIcelaWhome";i:1768;s:3:"Pri";i:1769;s:10:"ChapaLiMet";i:1770;s:11:"EhaNonseVot";i:1771;s:3:"Xan";i:1772;s:12:"MerocTuppWal";i:1773;s:3:"Dyn";i:1774;s:11:"MystaPseSem";i:1775;s:3:"Org";i:1776;s:13:"MelanParaWear";i:1777;s:9:"ReissUnVi";i:1778;s:2:"Me";i:1779;s:9:"JokisPrTh";i:1780;s:13:"CraEluviFlood";i:1781;s:7:"NavStee";i:1782;s:7:"PasqRep";i:1783;s:7:"FeMildh";i:1784;s:3:"Sub";i:1785;s:12:"IntMeisTetra";i:1786;s:8:"CherPrec";i:1787;s:7:"MegaSex";i:1788;s:4:"GrSp";i:1789;s:8:"PePinfSc";i:1790;s:9:"FluxTripp";i:1791;s:8:"GaliWeak";i:1792;s:3:"Req";i:1793;s:9:"InOvereRo";i:1794;s:8:"NicRamen";i:1795;s:8:"AlfMaUnd";i:1796;s:7:"NonabPi";i:1797;s:8:"ResiTing";i:1798;s:8:"GoParTub";i:1799;s:4:"Sili";i:1800;s:5:"PhPre";i:1801;s:10:"ApartTuske";i:1802;s:6:"HereRe";i:1803;s:4:"ElPr";i:1804;s:13:"HiruMonoTownw";i:1805;s:11:"ChalGuaWord";i:1806;s:11:"ChrNonpuSub";i:1807;s:11:"LazyxQuUndi";i:1808;s:4:"QuRe";i:1809;s:8:"DiGulfWi";i:1810;s:7:"PunUroc";i:1811;s:9:"CulFurHyp";i:1812;s:5:"Salty";i:1813;s:9:"PitcPoStu";i:1814;s:2:"La";i:1815;s:7:"UropZoo";i:1816;s:11:"HermiKaRoup";i:1817;s:4:"Cons";i:1818;s:10:"RapiSprTri";i:1819;s:8:"DenoMiPi";i:1820;s:8:"NairyPhy";i:1821;s:2:"Um";i:1822;s:10:"JuScopUnwi";i:1823;s:4:"Radi";i:1824;s:8:"CribExtr";i:1825;s:6:"SalThr";i:1826;s:3:"Dil";i:1827;s:7:"DefEwde";i:1828;s:9:"PalliUnac";i:1829;s:9:"SafraUnst";i:1830;s:13:"PrefSidaUnder";i:1831;s:11:"SatSporUnin";i:1832;s:5:"MoSch";i:1833;s:8:"CrRepSym";i:1834;s:2:"Pt";i:1835;s:4:"OvPr";i:1836;s:6:"DeUnab";i:1837;s:4:"Unde";i:1838;s:5:"Negro";i:1839;s:5:"MarPu";i:1840;s:5:"Ortho";i:1841;s:12:"MatroThesTra";i:1842;s:2:"Ma";i:1843;s:8:"ActFaKee";i:1844;s:12:"SalacThecUnd";i:1845;s:10:"MuMurPrein";i:1846;s:9:"PtiloTall";i:1847;s:2:"Oc";i:1848;s:10:"ScattSphen";i:1849;s:5:"MotUn";i:1850;s:7:"CuStuUn";i:1851;s:4:"Unqu";i:1852;s:4:"Decr";i:1853;s:5:"TalTr";i:1854;s:4:"Sexd";i:1855;s:7:"ShaUnmo";i:1856;s:6:"EuoNat";i:1857;s:8:"MahiSemi";i:1858;s:9:"GiglTacho";i:1859;s:10:"GneisMaulx";i:1860;s:11:"HemaLaPrors";i:1861;s:10:"FlubPacTyp";i:1862;s:9:"AlNonjTel";i:1863;s:9:"RecSuiUnc";i:1864;s:8:"ConsiSar";i:1865;s:4:"Prei";i:1866;s:4:"Lami";i:1867;s:11:"ManPelecSau";i:1868;s:5:"Ducat";i:1869;s:7:"PsSauci";i:1870;s:7:"HolTerp";i:1871;s:3:"She";i:1872;s:10:"DisDowerSt";i:1873;s:5:"Timan";i:1874;s:3:"Pal";i:1875;s:8:"QuinSuTo";i:1876;s:12:"HeathNonsuSo";i:1877;s:11:"GenMicrTeno";i:1878;s:5:"RecUn";i:1879;s:4:"Unpr";i:1880;s:7:"FlamIrr";i:1881;s:13:"DonarManaPero";i:1882;s:6:"ShTopi";i:1883;s:4:"Scit";i:1884;s:10:"OmmatScour";i:1885;s:5:"Ethen";i:1886;s:4:"Xylo";i:1887;s:5:"EryTr";i:1888;s:6:"BraHol";i:1889;s:13:"ImpalRummaVar";i:1890;s:4:"Trav";i:1891;s:11:"JotLoxUnfun";i:1892;s:8:"MuscSeme";i:1893;s:8:"GiLiPara";i:1894;s:9:"ViscoVouc";i:1895;s:3:"Scr";i:1896;s:6:"HaMaNo";i:1897;s:6:"StaSwe";i:1898;s:9:"InteRadio";i:1899;s:12:"PrThynnUnvol";i:1900;s:8:"PhilRecr";i:1901;s:9:"OmelTecpa";i:1902;s:9:"PettyUnwe";i:1903;s:5:"Melan";i:1904;s:14:"GasteQuinqTric";i:1905;s:12:"CommuDaInter";i:1906;s:5:"Megam";i:1907;s:12:"MiPickpTakam";i:1908;s:4:"Hydr";i:1909;s:4:"Torv";i:1910;s:7:"ExtiInt";i:1911;s:13:"EuphoFruOutdw";i:1912;s:5:"Tinni";i:1913;s:4:"Subs";i:1914;s:5:"OvPho";i:1915;s:11:"GlyphMonRig";i:1916;s:4:"Pion";i:1917;s:10:"IndagPieTi";i:1918;s:11:"TakiUnprUpa";i:1919;s:5:"LoVar";i:1920;s:5:"FaPla";i:1921;s:7:"MiscOns";i:1922;s:7:"DusStee";i:1923;s:9:"BeetlHang";i:1924;s:12:"FigMarmUnend";i:1925;s:5:"Fordo";i:1926;s:4:"Soci";i:1927;s:12:"HygrHypShopk";i:1928;s:11:"HabPlowWenc";i:1929;s:8:"PerSpent";i:1930;s:4:"Camp";i:1931;s:5:"PatRu";i:1932;s:8:"GhaSuthe";i:1933;s:10:"OuttPaUnim";i:1934;s:9:"StockTacc";i:1935;s:7:"InneUns";i:1936;s:4:"Trac";i:1937;s:4:"Unin";i:1938;s:8:"PolUninv";i:1939;s:9:"PheSterUn";i:1940;s:8:"DysYukia";i:1941;s:5:"PenSc";i:1942;s:7:"DaInShi";i:1943;s:14:"HepaNonshOrbic";i:1944;s:7:"PluPrPr";i:1945;s:4:"Semp";i:1946;s:3:"Unb";i:1947;s:9:"LancNonco";i:1948;s:10:"FiKlockNon";i:1949;s:4:"InSw";i:1950;s:8:"RoUnsuWi";i:1951;s:6:"ObscSe";i:1952;s:8:"ScythSub";i:1953;s:7:"HistMot";i:1954;s:5:"GoUtt";i:1955;s:5:"Inter";i:1956;s:7:"MiThUnp";i:1957;s:8:"BreComus";i:1958;s:8:"HurMemTu";i:1959;s:6:"OdylSn";i:1960;s:5:"Misec";i:1961;s:10:"PolypScler";i:1962;s:10:"OverSintTh";i:1963;s:8:"OverSexu";i:1964;s:12:"DruMagneSant";i:1965;s:10:"ParoPawPri";i:1966;s:7:"MaPrVar";i:1967;s:12:"LancNeolShas";i:1968;s:7:"SelUnsh";i:1969;s:5:"Nephr";i:1970;s:11:"NaSeismSept";i:1971;s:9:"DrivaNySi";i:1972;s:8:"PePheUnt";i:1973;s:9:"BusElSilp";i:1974;s:10:"SarclSever";i:1975;s:10:"ChirpDeEde";i:1976;s:10:"SarSigUndi";i:1977;s:8:"PyxidSup";i:1978;s:4:"Nubb";i:1979;s:8:"HederPne";i:1980;s:10:"PhthiTeneb";i:1981;s:7:"FrFreon";i:1982;s:12:"QuareResiSke";i:1983;s:6:"GangNo";i:1984;s:6:"NoonSp";i:1985;s:5:"StrUn";i:1986;s:11:"CrDarogYera";i:1987;s:9:"PhPogamPr";i:1988;s:11:"EneugInceOs";i:1989;s:5:"Carac";i:1990;s:11:"NaysaOvSupe";i:1991;s:5:"BlaHu";i:1992;s:10:"RoselUnWoo";i:1993;s:10:"CorinGraTe";i:1994;s:7:"InUnobv";i:1995;s:9:"BaChuroIn";i:1996;s:8:"MulSerol";i:1997;s:4:"Cast";i:1998;s:6:"DeQuXy";i:1999;s:8:"MiPnVeri";i:2000;s:7:"MyrStor";i:2001;s:8:"IncavZin";i:2002;s:9:"KayRulaTr";i:2003;s:3:"Ser";i:2004;s:7:"PaucRus";i:2005;s:2:"Tu";i:2006;s:9:"OddmWindb";i:2007;s:8:"UnpWhang";i:2008;s:4:"GaMe";i:2009;s:6:"MaPost";i:2010;s:7:"PomRach";i:2011;s:2:"Wy";i:2012;s:11:"MaQueeTapst";i:2013;s:5:"Denar";i:2014;s:10:"MethoSneer";i:2015;s:9:"CovaSpell";i:2016;s:8:"SlanTuis";i:2017;s:12:"ChiReprTaunt";i:2018;s:9:"LaMeasUne";i:2019;s:5:"PoPre";i:2020;s:5:"SpaSs";i:2021;s:4:"Salm";i:2022;s:5:"Endop";i:2023;s:3:"Tut";i:2024;s:6:"PeriPh";i:2025;s:13:"IrreSodaVeget";i:2026;s:5:"Locri";i:2027;s:7:"UnZooma";i:2028;s:4:"Prou";i:2029;s:5:"Outdw";i:2030;s:12:"MesneReThrou";i:2031;s:2:"Se";i:2032;s:4:"Sate";i:2033;s:4:"AtDi";i:2034;s:5:"OrPhe";i:2035;s:5:"Quond";i:2036;s:5:"Samar";i:2037;s:2:"Ke";i:2038;s:5:"Nonmu";i:2039;s:12:"ForcTaggyTea";i:2040;s:4:"Uncr";i:2041;s:5:"PlaRa";i:2042;s:7:"FeFemPr";i:2043;s:5:"ClDis";i:2044;s:14:"BackbIlluvPseu";i:2045;s:10:"BrDemiLuxu";i:2046;s:10:"GaMyoplOut";i:2047;s:14:"MeteOutwoPeriu";i:2048;s:8:"GaMycSha";i:2049;s:5:"Unapp";i:2050;s:4:"Gibb";i:2051;s:3:"Hem";i:2052;s:5:"Syngn";i:2053;s:5:"Lophi";i:2054;s:7:"OvReUnl";i:2055;s:6:"ProfWo";i:2056;s:11:"BaElasmElop";i:2057;s:2:"Eq";i:2058;s:5:"Workm";i:2059;s:9:"AppelUnic";i:2060;s:4:"Rabb";i:2061;s:8:"DovInspi";i:2062;s:5:"Katip";i:2063;s:12:"MiPortmTecto";i:2064;s:8:"ScSiVola";i:2065;s:3:"Cra";i:2066;s:14:"HexaPassaPhila";i:2067;s:9:"FrHoUndet";i:2068;s:7:"DipLint";i:2069;s:4:"IsPa";i:2070;s:3:"New";i:2071;s:3:"Ton";i:2072;s:11:"ImshMacrScr";i:2073;s:5:"Pampr";i:2074;s:9:"InnocUnch";i:2075;s:13:"LithxUnrZipsx";i:2076;s:13:"MingyUngeUnor";i:2077;s:10:"NonnoUngUn";i:2078;s:4:"Pict";i:2079;s:8:"RemeSpon";i:2080;s:4:"Plas";i:2081;s:5:"IntRu";i:2082;s:3:"Hyb";i:2083;s:3:"Tec";i:2084;s:3:"Dia";i:2085;s:4:"Unme";i:2086;s:11:"CarlDeliVam";i:2087;s:8:"NeUncrUn";i:2088;s:3:"Ker";i:2089;s:10:"ObjuProlTw";i:2090;s:8:"DeOverSu";i:2091;s:2:"An";i:2092;s:7:"GraduUn";i:2093;s:9:"PalaeSyTr";i:2094;s:3:"Sen";i:2095;s:6:"SubTet";i:2096;s:9:"MadMultSu";i:2097;s:10:"GermaUnpre";i:2098;s:10:"GibbNonsVo";i:2099;s:3:"Phr";i:2100;s:4:"Macr";i:2101;s:4:"ExUn";i:2102;s:11:"KaibMeUnpar";i:2103;s:5:"Mutil";i:2104;s:12:"PentaPoVentr";i:2105;s:3:"For";i:2106;s:10:"PedeUnciUp";i:2107;s:9:"FranTheop";i:2108;s:10:"InterJerUn";i:2109;s:6:"CyaUnt";i:2110;s:4:"Chic";i:2111;s:10:"RemTanThri";i:2112;s:4:"BeSa";i:2113;s:3:"Fec";i:2114;s:10:"CozilMoUht";i:2115;s:2:"Pa";i:2116;s:5:"Mispr";i:2117;s:4:"PrSt";i:2118;s:4:"DiHe";i:2119;s:10:"FakoMoQuix";i:2120;s:11:"PtePyrUnarr";i:2121;s:6:"PhRoSt";i:2122;s:4:"BeEn";i:2123;s:8:"GuiHypha";i:2124;s:8:"GuInoPre";i:2125;s:9:"PatRhaUnt";i:2126;s:4:"Holo";i:2127;s:8:"PrioUnme";i:2128;s:11:"GradRubytUn";i:2129;s:10:"CoeCredeSi";i:2130;s:9:"ReifUndis";i:2131;s:6:"LuVaWi";i:2132;s:7:"AnBuDia";i:2133;s:4:"Trad";i:2134;s:11:"CardOvRocks";i:2135;s:7:"UnhaWas";i:2136;s:13:"MaggeOthWilli";i:2137;s:8:"MycNoPol";i:2138;s:9:"FasGriNig";i:2139;s:6:"LanPar";i:2140;s:9:"FeoffPrSu";i:2141;s:11:"GonoGreUnco";i:2142;s:8:"SmotStom";i:2143;s:4:"Tute";i:2144;s:6:"MellRa";i:2145;s:4:"Pter";i:2146;s:9:"ThrThroTu";i:2147;s:5:"InTap";i:2148;s:7:"CollTub";i:2149;s:2:"Fa";i:2150;s:9:"PiRibbiSu";i:2151;s:7:"ExuPaSe";i:2152;s:9:"DownHoriz";i:2153;s:12:"JudicMockUnb";i:2154;s:9:"MeloSemia";i:2155;s:6:"CoFeRa";i:2156;s:5:"Macaw";i:2157;s:6:"NeurSe";i:2158;s:8:"LattSouc";i:2159;s:8:"UninWart";i:2160;s:9:"GiSubTown";i:2161;s:12:"PaveTitheUnl";i:2162;s:10:"DragsSubUn";i:2163;s:9:"SlopUncha";i:2164;s:5:"OrgTh";i:2165;s:8:"MixScole";i:2166;s:2:"Em";i:2167;s:5:"Remon";i:2168;s:2:"Bo";i:2169;s:7:"SabSupe";i:2170;s:4:"Neri";i:2171;s:5:"TuUnd";i:2172;s:9:"FlagSciss";i:2173;s:3:"Mas";i:2174;s:9:"InIsaThar";i:2175;s:11:"SemiStiThio";i:2176;s:3:"Oth";i:2177;s:10:"NeePoUnhel";i:2178;s:9:"CarInMate";i:2179;s:9:"DenuRefRi";i:2180;s:11:"ChiwColleMo";i:2181;s:12:"EquidPrecUnv";i:2182;s:6:"KrSpha";i:2183;s:10:"ShTeeTotte";i:2184;s:3:"Tou";i:2185;s:8:"IncRoUpb";i:2186;s:7:"IlMengx";i:2187;s:6:"CephSa";i:2188;s:11:"InkPhthaTet";i:2189;s:8:"IncuMoco";i:2190;s:7:"OveSeis";i:2191;s:3:"Rub";i:2192;s:12:"ObUnctiVeloc";i:2193;s:9:"OpalPodli";i:2194;s:3:"Dec";i:2195;s:8:"CymMoPyo";i:2196;s:9:"PhSpicaTr";i:2197;s:5:"Nondy";i:2198;s:4:"Roma";i:2199;s:9:"InsitSuTr";i:2200;s:3:"Poi";i:2201;s:8:"ImInhRos";i:2202;s:9:"HemoInsPr";i:2203;s:7:"RecoSan";i:2204;s:9:"IntraLepr";i:2205;s:7:"PartiRo";i:2206;s:7:"PlacPla";i:2207;s:9:"MedieSeSy";i:2208;s:4:"BaSc";i:2209;s:2:"Sk";i:2210;s:5:"UnfUn";i:2211;s:8:"CadavEpi";i:2212;s:5:"LagPh";i:2213;s:5:"MalMo";i:2214;s:8:"SpSupeUn";i:2215;s:8:"HiOgrePo";i:2216;s:2:"Ca";i:2217;s:5:"Infol";i:2218;s:10:"DyassUninu";i:2219;s:6:"UnUnsa";i:2220;s:12:"ChoSchwZarni";i:2221;s:9:"InReUnpay";i:2222;s:9:"UntrYello";i:2223;s:12:"OraOverSland";i:2224;s:6:"HePrSt";i:2225;s:10:"OpPropiQuo";i:2226;s:9:"PeriTheUn";i:2227;s:12:"AbuPhysiRheg";i:2228;s:7:"DisShad";i:2229;s:12:"CheGalyaTeax";i:2230;s:11:"CorrKaUncol";i:2231;s:9:"HypLandLu";i:2232;s:4:"Yegu";i:2233;s:4:"Prig";i:2234;s:12:"MultiTubbaVi";i:2235;s:14:"RehaSchnoStrin";i:2236;s:8:"PelSquat";i:2237;s:8:"HexRacTu";i:2238;s:12:"SemblSomeUpb";i:2239;s:11:"GymnImplOve";i:2240;s:6:"NondUn";i:2241;s:11:"HelIneStasi";i:2242;s:7:"EstelTi";i:2243;s:10:"InterPoThe";i:2244;s:5:"Degge";i:2245;s:3:"Rel";i:2246;s:4:"Rock";i:2247;s:9:"FlaGaSpas";i:2248;s:7:"MayaTel";i:2249;s:7:"EnteFic";i:2250;s:4:"Penn";i:2251;s:7:"OvUndaz";i:2252;s:5:"Spinu";i:2253;s:11:"CoPhotoRake";i:2254;s:13:"HomocUnioUnli";i:2255;s:7:"FidGirl";i:2256;s:6:"FaceSt";i:2257;s:9:"HardePyre";i:2258;s:5:"Brand";i:2259;s:5:"Enqui";i:2260;s:5:"Workl";i:2261;s:7:"HoIncom";i:2262;s:10:"SittUloUnk";i:2263;s:2:"Ps";i:2264;s:8:"PertiTra";i:2265;s:11:"EngOverTins";i:2266;s:5:"Uncom";i:2267;s:3:"Jaw";i:2268;s:7:"PiStrSy";i:2269;s:5:"Sotti";i:2270;s:14:"AnthrNonsSiusl";i:2271;s:5:"PseQu";i:2272;s:5:"Reedl";i:2273;s:8:"DikerPea";i:2274;s:6:"NoSwar";i:2275;s:10:"PonTiTrans";i:2276;s:6:"StomWh";i:2277;s:5:"Prepa";i:2278;s:11:"EctoTraWeap";i:2279;s:7:"CeJuKet";i:2280;s:7:"CaStiTo";i:2281;s:11:"CholaScalSl";i:2282;s:5:"LeSpu";i:2283;s:7:"NonexPa";i:2284;s:5:"CuUnd";i:2285;s:5:"CoHas";i:2286;s:7:"PresVet";i:2287;s:10:"EfEndotSph";i:2288;s:4:"GeHo";i:2289;s:10:"ConEnkraEx";i:2290;s:9:"SeakUrano";i:2291;s:10:"RebScutSta";i:2292;s:11:"AmetFoStaph";i:2293;s:4:"LoPu";i:2294;s:4:"Unmo";i:2295;s:12:"ChamaCufRetr";i:2296;s:9:"CuttiPoun";i:2297;s:7:"OxToote";i:2298;s:4:"Hali";i:2299;s:14:"OverSuperTickt";i:2300;s:9:"DefeElong";i:2301;s:10:"RozumTypif";i:2302;s:11:"OnomReinhUn";i:2303;s:5:"HePat";i:2304;s:6:"PrRaff";i:2305;s:7:"UrWorld";i:2306;s:12:"MacTanyoVoli";i:2307;s:6:"KeUnbe";i:2308;s:11:"FooliPeSoun";i:2309;s:8:"FiMaStra";i:2310;s:10:"PrRevuSubc";i:2311;s:3:"Kri";i:2312;s:6:"EnneMe";i:2313;s:3:"Mar";i:2314;s:10:"PaSubmeUna";i:2315;s:14:"MitiNoncaPelta";i:2316;s:7:"TroTylo";i:2317;s:4:"Curv";i:2318;s:5:"QuaTy";i:2319;s:7:"NonsaPr";i:2320;s:5:"Urete";i:2321;s:5:"Hynex";i:2322;s:5:"Tillo";i:2323;s:4:"EnMa";i:2324;s:10:"IonPresuPr";i:2325;s:9:"OverwTale";i:2326;s:9:"EnmaSenda";i:2327;s:13:"HiodTrihUnvis";i:2328;s:3:"Viz";i:2329;s:9:"NayarSuVa";i:2330;s:9:"VasoWallo";i:2331;s:8:"CrypMans";i:2332;s:9:"CondyEpip";i:2333;s:7:"GlMalle";i:2334;s:3:"Pag";i:2335;s:7:"ScUltra";i:2336;s:11:"ScholTaUnba";i:2337;s:13:"DisarGestNonc";i:2338;s:3:"Sir";i:2339;s:9:"PugmiUltr";i:2340;s:5:"Pangl";i:2341;s:8:"MetMyoge";i:2342;s:7:"HydroSp";i:2343;s:5:"Anaca";i:2344;s:6:"OrthPs";i:2345;s:5:"Nicco";i:2346;s:12:"DactTonnUnbo";i:2347;s:3:"Tox";i:2348;s:14:"ChorExcitPhaco";i:2349;s:3:"Tab";i:2350;s:10:"BasUnUnrui";i:2351;s:6:"RefUne";i:2352;s:3:"Ope";i:2353;s:9:"OverQuWar";i:2354;s:5:"Antid";i:2355;s:9:"ExFinRust";i:2356;s:14:"PuggiSecedStri";i:2357;s:7:"IriRaWa";i:2358;s:11:"MormProrPse";i:2359;s:5:"StUno";i:2360;s:7:"GastrPh";i:2361;s:2:"Li";i:2362;s:12:"NonOndiRocam";i:2363;s:7:"MollUnt";i:2364;s:2:"Ti";i:2365;s:4:"Podu";i:2366;s:5:"SanTe";i:2367;s:13:"ChieDanaiFell";i:2368;s:7:"CentFar";i:2369;s:6:"OutTab";i:2370;s:14:"MitigNoncoPiro";i:2371;s:13:"HondMarsObnun";i:2372;s:4:"Rest";i:2373;s:7:"NostrOb";i:2374;s:7:"IconLob";i:2375;s:5:"Virag";i:2376;s:9:"ImmePhane";i:2377;s:5:"Scran";i:2378;s:10:"LeNonalUnv";i:2379;s:7:"NiParUn";i:2380;s:8:"PhyTosUn";i:2381;s:12:"HydMarinSpec";i:2382;s:8:"LocSuUnv";i:2383;s:11:"LympTreUnse";i:2384;s:10:"CondxTenta";i:2385;s:8:"LerUnmis";i:2386;s:14:"GhanKeratNekto";i:2387;s:9:"InMalPrav";i:2388;s:3:"Mul";i:2389;s:8:"PilRever";i:2390;s:13:"PrepSialaTrum";i:2391;s:10:"DammKatKir";i:2392;s:11:"KnobProloRu";i:2393;s:4:"Thie";i:2394;s:3:"Win";i:2395;s:3:"Def";i:2396;s:8:"StrWanch";i:2397;s:7:"LatriRa";i:2398;s:2:"Si";i:2399;s:6:"DiSlSm";i:2400;s:9:"HoStelaUm";i:2401;s:4:"SwTe";i:2402;s:10:"LanToVaira";i:2403;s:6:"OzonTo";i:2404;s:6:"MirSci";i:2405;s:14:"DaisiMyeloUret";i:2406;s:12:"DigitMounUnt";i:2407;s:6:"UniUnm";i:2408;s:6:"HallUn";i:2409;s:3:"Rea";i:2410;s:13:"FrushLitRecry";i:2411;s:4:"JuNa";i:2412;s:6:"SeSuev";i:2413;s:7:"RunTetr";i:2414;s:12:"NoneRoenThio";i:2415;s:4:"Wolf";i:2416;s:9:"HetaJaPyl";i:2417;s:10:"DiapaUnder";i:2418;s:3:"Wra";i:2419;s:4:"Subp";i:2420;s:3:"Sal";i:2421;s:5:"Unmea";i:2422;s:12:"FittiQuinaSe";i:2423;s:8:"PseWicke";i:2424;s:12:"BlNondePlate";i:2425;s:8:"SigTyUnt";i:2426;s:3:"San";i:2427;s:8:"LocPerRx";i:2428;s:8:"SupUnsup";i:2429;s:6:"GraTri";i:2430;s:7:"BaldnLo";i:2431;s:9:"CruroUnej";i:2432;s:10:"LovesRehoo";i:2433;s:12:"HemeNomaOuth";i:2434;s:5:"Innox";i:2435;s:11:"KierRenTele";i:2436;s:9:"SolitStaw";i:2437;s:8:"CeMamUns";i:2438;s:11:"MysoiNeotQu";i:2439;s:10:"KatakTurns";i:2440;s:10:"DisprHymno";i:2441;s:4:"Regr";i:2442;s:9:"GlobaSynt";i:2443;s:3:"Lut";i:2444;s:3:"The";i:2445;s:3:"Rhi";i:2446;s:3:"Tym";i:2447;s:7:"MeUnman";i:2448;s:9:"ExiOmnTor";i:2449;s:12:"PerfeStiSton";i:2450;s:6:"HoPrUn";i:2451;s:10:"IrSeptaTam";i:2452;s:10:"FoParTechn";i:2453;s:4:"Spha";i:2454;s:12:"StupUnflaVie";i:2455;s:7:"LoneVan";i:2456;s:8:"MonRadio";i:2457;s:10:"HomImpSynd";i:2458;s:2:"Ch";i:2459;s:9:"EnOrPoeta";i:2460;s:10:"CoSeminSen";i:2461;s:7:"ProidVe";i:2462;s:6:"MeShor";i:2463;s:4:"Hois";i:2464;s:9:"EyeMePoli";i:2465;s:3:"Rei";i:2466;s:5:"Zoste";i:2467;s:8:"SithUnwa";i:2468;s:5:"Corre";i:2469;s:9:"SilTriUnv";i:2470;s:9:"CompMysta";i:2471;s:7:"HepMuSu";i:2472;s:6:"OpeQua";i:2473;s:4:"Rema";i:2474;s:11:"DeclMassiUn";i:2475;s:3:"Lep";i:2476;s:8:"NecrPhth";i:2477;s:4:"Trir";i:2478;s:8:"MesoWelc";i:2479;s:4:"Vulp";i:2480;s:7:"LindPhr";i:2481;s:7:"EntirMo";i:2482;s:9:"RiSlaTaun";i:2483;s:8:"PreToufi";i:2484;s:9:"PalaSovra";i:2485;s:5:"Penni";i:2486;s:12:"NonprProcRad";i:2487;s:8:"PantRogu";i:2488;s:8:"ForeScom";i:2489;s:2:"Yo";i:2490;s:9:"LiOutUnti";i:2491;s:6:"PostSt";i:2492;s:10:"SpiSyUntuc";i:2493;s:7:"AlquiRh";i:2494;s:12:"PaloSkeldTir";i:2495;s:8:"BiflaUnr";i:2496;s:9:"LacRhuWin";i:2497;s:5:"Unres";i:2498;s:7:"PoPsilo";i:2499;s:3:"Obe";i:2500;s:5:"Nonin";i:2501;s:7:"UncaUnh";i:2502;s:5:"Undiv";i:2503;s:9:"InIzMorin";i:2504;s:7:"NoPicPi";i:2505;s:13:"PediPoltThiof";i:2506;s:4:"Scap";i:2507;s:11:"CoMesorTact";i:2508;s:10:"DeepPikePr";i:2509;s:8:"OmbroQui";i:2510;s:5:"InePo";i:2511;s:10:"PaPolyzWea";i:2512;s:10:"GhiziPosne";i:2513;s:5:"Nonde";i:2514;s:10:"RachyStaph";i:2515;s:9:"NorProtRh";i:2516;s:11:"ConflElusSt";i:2517;s:9:"OliSaraTa";i:2518;s:5:"Spath";i:2519;s:9:"HetIreUnr";i:2520;s:9:"GigeHoUro";i:2521;s:6:"NonSex";i:2522;s:9:"DrawIreni";i:2523;s:7:"HacMars";i:2524;s:10:"DenRaSoros";i:2525;s:10:"HolHypSupe";i:2526;s:4:"Cred";i:2527;s:5:"StUns";i:2528;s:8:"FictWagw";i:2529;s:3:"Unc";i:2530;s:9:"ShoreWhit";i:2531;s:11:"ProRehaTras";i:2532;s:5:"UnUph";i:2533;s:10:"MoPneumPsi";i:2534;s:12:"RabbSomnUnti";i:2535;s:11:"SplenTheUnb";i:2536;s:3:"Par";i:2537;s:10:"DyaFisQuin";i:2538;s:6:"LoUncl";i:2539;s:11:"DicOvariVar";i:2540;s:8:"PreSciSc";i:2541;s:8:"HarHiMor";i:2542;s:11:"QuadrSawaSi";i:2543;s:6:"SuThim";i:2544;s:5:"Vulva";i:2545;s:3:"Hou";i:2546;s:3:"Mer";i:2547;s:8:"GiInPaga";i:2548;s:2:"Co";i:2549;s:10:"PseudRenun";i:2550;s:13:"MiskMortaTana";i:2551;s:7:"OmTerro";i:2552;s:7:"ExUnbex";i:2553;s:5:"Micro";i:2554;s:11:"DenuObsSemi";i:2555;s:11:"BursDiRenea";i:2556;s:3:"Zym";i:2557;s:9:"BunNoWitt";i:2558;s:5:"Misqu";i:2559;s:4:"LaPl";i:2560;s:6:"IndKin";i:2561;s:10:"TriUnmVent";i:2562;s:10:"MoOvePlant";i:2563;s:5:"Verbe";i:2564;s:5:"CutSc";i:2565;s:9:"UnbeVigor";i:2566;s:9:"ListUnder";i:2567;s:10:"PtilSegrSt";i:2568;s:9:"GruelNaso";i:2569;s:5:"Nudis";i:2570;s:13:"LithPhrasRest";i:2571;s:10:"HyPonUngra";i:2572;s:8:"ParPrese";i:2573;s:2:"Pu";i:2574;s:7:"SeSweUn";i:2575;s:4:"Stop";i:2576;s:11:"EntSynTechn";i:2577;s:14:"OverpTrachUneq";i:2578;s:10:"SteenUtter";i:2579;s:12:"FlorMediUnco";i:2580;s:11:"CoCyancNigg";i:2581;s:4:"Sexu";i:2582;s:11:"BignoErStom";i:2583;s:12:"BiggeMyrSupe";i:2584;s:5:"Tinti";i:2585;s:9:"PhilaSubc";i:2586;s:8:"CouPoeti";i:2587;s:13:"DockeIriMisti";i:2588;s:11:"GramOlPityp";i:2589;s:13:"DisEvoluMycod";i:2590;s:5:"Morph";i:2591;s:2:"Ou";i:2592;s:5:"ProUn";i:2593;s:4:"Dulc";i:2594;s:7:"LongaSp";i:2595;s:8:"PolPulUn";i:2596;s:8:"BuDiInco";i:2597;s:6:"ChaiDe";i:2598;s:11:"PacabUnUnsh";i:2599;s:10:"ArthrUninv";i:2600;s:6:"PyroRo";i:2601;s:7:"VulgaYi";i:2602;s:5:"EcLio";i:2603;s:11:"EranIncouOr";i:2604;s:12:"LunuModulOrd";i:2605;s:5:"Patri";i:2606;s:2:"Dr";i:2607;s:7:"RaTrans";i:2608;s:12:"CompoLipScot";i:2609;s:10:"SardoSensa";i:2610;s:12:"OverlSupWrin";i:2611;s:9:"PhotSperm";i:2612;s:7:"TimekUn";i:2613;s:5:"Pusey";i:2614;s:7:"PoQuSou";i:2615;s:6:"FreeGr";i:2616;s:4:"Nona";i:2617;s:6:"PsilUn";i:2618;s:7:"DiseSta";i:2619;s:5:"Eluso";i:2620;s:7:"EndabFr";i:2621;s:7:"EntMaTe";i:2622;s:5:"UndUn";i:2623;s:8:"FrStavUn";i:2624;s:6:"DisSyn";i:2625;s:3:"Tub";i:2626;s:11:"BescDeRenas";i:2627;s:12:"RabbiReSuper";i:2628;s:4:"Bolo";i:2629;s:10:"NonaPectPh";i:2630;s:12:"PolymRoomtUn";i:2631;s:11:"KolTypWirem";i:2632;s:7:"CroPrae";i:2633;s:7:"SubWarr";i:2634;s:6:"SteTro";i:2635;s:7:"BaForel";i:2636;s:6:"OppiWh";i:2637;s:9:"GleanSacc";i:2638;s:10:"MisliQueru";i:2639;s:8:"ScaWoodb";i:2640;s:3:"Equ";i:2641;s:10:"NeocyUnwov";i:2642;s:13:"CoacElectUnsp";i:2643;s:4:"Unch";i:2644;s:7:"BrUncom";i:2645;s:5:"Tanni";i:2646;s:10:"CuLeiOcclu";i:2647;s:3:"Soa";i:2648;s:11:"MyxoPoUnbeg";i:2649;s:12:"PhysiTrUnexi";i:2650;s:6:"InSice";i:2651;s:7:"NonPauc";i:2652;s:6:"SecuUl";i:2653;s:10:"HornOutOut";i:2654;s:6:"OpprOv";i:2655;s:10:"BaHypStich";i:2656;s:9:"HemoaIcTi";i:2657;s:4:"Zeug";i:2658;s:4:"Sphy";i:2659;s:5:"Shaka";i:2660;s:6:"PaPrSc";i:2661;s:5:"Unrel";i:2662;s:9:"ReSmintTw";i:2663;s:2:"Ku";i:2664;s:4:"Botu";i:2665;s:9:"TrulUnext";i:2666;s:9:"CoglWhite";i:2667;s:5:"Curle";i:2668;s:10:"InMinSince";i:2669;s:11:"NoRachTrans";i:2670;s:11:"RolexUnflZo";i:2671;s:7:"KoSwiUm";i:2672;s:9:"SemiUncUn";i:2673;s:7:"AdBosVe";i:2674;s:12:"CamphPePrein";i:2675;s:7:"PygmoSe";i:2676;s:7:"InMorco";i:2677;s:7:"OcOffen";i:2678;s:5:"Speck";i:2679;s:4:"Till";i:2680;s:11:"BestrUnWorc";i:2681;s:8:"OlOveSyn";i:2682;s:10:"UnsocWound";i:2683;s:6:"ReprSu";i:2684;s:7:"RemigUn";i:2685;s:12:"GeorOutmServ";i:2686;s:3:"Pac";i:2687;s:12:"LePerseSerop";i:2688;s:11:"FeverNonWav";i:2689;s:5:"BaBac";i:2690;s:9:"DragGaSan";i:2691;s:8:"UnforYar";i:2692;s:4:"Vent";i:2693;s:7:"BluSlug";i:2694;s:6:"OverSp";i:2695;s:12:"DiuHypoProto";i:2696;s:7:"ReUndes";i:2697;s:8:"PolUnimp";i:2698;s:5:"Unsmo";i:2699;s:9:"CuriaSulf";i:2700;s:11:"SyntToTreas";i:2701;s:13:"SaproSyceeTen";i:2702;s:5:"Thuoc";i:2703;s:6:"BroHol";i:2704;s:13:"MetaMothwVili";i:2705;s:10:"LeStuUnioc";i:2706;s:12:"PitheTeTrigo";i:2707;s:5:"LowMe";i:2708;s:12:"SoStahlTomme";i:2709;s:8:"PothSpTe";i:2710;s:11:"MultPhytoUn";i:2711;s:4:"Note";i:2712;s:13:"SuluSynanViol";i:2713;s:9:"SeapUnsta";i:2714;s:3:"Nin";i:2715;s:3:"Lat";i:2716;s:11:"BiopDespeEt";i:2717;s:9:"FiSuperVo";i:2718;s:5:"Cereb";i:2719;s:10:"BoNonbUnde";i:2720;s:2:"Lu";i:2721;s:10:"PrerPsyWhe";i:2722;s:11:"MeReferShee";i:2723;s:12:"GrosInvitUna";i:2724;s:8:"SarrSeri";i:2725;s:4:"Rags";i:2726;s:2:"Sm";i:2727;s:5:"Unint";i:2728;s:3:"Gut";i:2729;s:9:"InteMyPyr";i:2730;s:7:"IsoloUn";i:2731;s:5:"SiUnb";i:2732;s:10:"BlCanniEqu";i:2733;s:5:"Konar";i:2734;s:4:"Step";i:2735;s:6:"InteNa";i:2736;s:7:"KnaSubc";i:2737;s:9:"ChaUnaUnc";i:2738;s:6:"LaNeUp";i:2739;s:3:"Reb";i:2740;s:9:"DiscPePot";i:2741;s:8:"NonNonVi";i:2742;s:4:"Tata";i:2743;s:4:"Cent";i:2744;s:7:"HandPho";i:2745;s:11:"GazetMenWoa";i:2746;s:9:"PolyPredi";i:2747;s:12:"LaboPreScase";i:2748;s:7:"PaTheTh";i:2749;s:14:"DeafDiaclSides";i:2750;s:4:"Dixi";i:2751;s:8:"MiscUnpo";i:2752;s:9:"RepShaUpt";i:2753;s:13:"GaberJewdPont";i:2754;s:6:"DyoInc";i:2755;s:11:"FanaOeQuinq";i:2756;s:6:"ChThre";i:2757;s:4:"Orth";i:2758;s:9:"LaborMiSp";i:2759;s:6:"DeNavi";i:2760;s:10:"RioShSwain";i:2761;s:3:"Men";i:2762;s:4:"Ghib";i:2763;s:9:"SunpTrust";i:2764;s:5:"Feloi";i:2765;s:9:"NotorOiko";i:2766;s:5:"Laryn";i:2767;s:5:"Wishf";i:2768;s:10:"DoMultiUne";i:2769;s:4:"Unow";i:2770;s:10:"DumpiEvePy";i:2771;s:14:"OctonTwelUnman";i:2772;s:11:"NasSterXylo";i:2773;s:8:"CompOppu";i:2774;s:9:"CoalEnure";i:2775;s:4:"Suba";i:2776;s:5:"Histo";i:2777;s:12:"PaPsidiTooth";i:2778;s:5:"Tubul";i:2779;s:4:"Outg";i:2780;s:5:"HolNi";i:2781;s:2:"El";i:2782;s:11:"SpurnSuUnst";i:2783;s:3:"Soc";i:2784;s:7:"MachSyn";i:2785;s:12:"LoriPraePren";i:2786;s:7:"ProTric";i:2787;s:5:"Disre";i:2788;s:11:"IntePseuXen";i:2789;s:3:"Sei";i:2790;s:4:"Refl";i:2791;s:11:"MisomSperUn";i:2792;s:9:"RetraVaca";i:2793;s:6:"PolyRe";i:2794;s:12:"GrizzImpSnow";i:2795;s:8:"NumUroxa";i:2796;s:4:"LePa";i:2797;s:3:"Qua";i:2798;s:14:"InvolSearSemio";i:2799;s:6:"LeStan";i:2800;s:10:"SantaStaph";i:2801;s:4:"Imag";i:2802;s:9:"PreRakiSu";i:2803;s:13:"DegeTrigoUnch";i:2804;s:8:"FracUngr";i:2805;s:12:"EzrFlukShast";i:2806;s:10:"PlaneSteth";i:2807;s:8:"PresiUnd";i:2808;s:9:"BroDrivIn";i:2809;s:4:"Vice";i:2810;s:3:"Mah";i:2811;s:4:"AcXy";i:2812;s:10:"IsrMegaUst";i:2813;s:9:"PeriReaTr";i:2814;s:10:"CoOswaZami";i:2815;s:5:"JacUn";i:2816;s:9:"FaLeanPar";i:2817;s:14:"EngauInerrSpha";i:2818;s:5:"Sager";i:2819;s:13:"SteeSubwUnpry";i:2820;s:9:"RhagoStag";i:2821;s:4:"Plag";i:2822;s:4:"Sens";i:2823;s:11:"MonTylosWoo";i:2824;s:5:"Toxop";i:2825;s:4:"MiMn";i:2826;s:6:"PhlePy";i:2827;s:14:"LatoPeramUpval";i:2828;s:10:"PerStTarox";i:2829;s:5:"Thero";i:2830;s:9:"ForHaPras";i:2831;s:6:"PeTubi";i:2832;s:13:"DiffeIneVesic";i:2833;s:10:"NonveTeneb";i:2834;s:6:"TauUna";i:2835;s:14:"JeronUncuUntha";i:2836;s:5:"Telem";i:2837;s:9:"BargHaSul";i:2838;s:11:"InartMelaSt";i:2839;s:5:"Confe";i:2840;s:9:"MobPseTen";i:2841;s:4:"Obst";i:2842;s:9:"IvanPedan";i:2843;s:5:"Noctu";i:2844;s:8:"MythTiUn";i:2845;s:13:"ParlaQuadRash";i:2846;s:10:"CyOpiloPre";i:2847;s:13:"OsteUngyvUnsp";i:2848;s:5:"StZoo";i:2849;s:4:"Slid";i:2850;s:9:"ParRuVibr";i:2851;s:14:"EndoImposSubmi";i:2852;s:7:"SpiUnUn";i:2853;s:7:"PreRais";i:2854;s:9:"EpigMyxVo";i:2855;s:5:"ReUnd";i:2856;s:9:"RusSheUnm";i:2857;s:6:"PretWr";i:2858;s:3:"Hom";i:2859;s:6:"GrSini";i:2860;s:11:"ColeNonvPos";i:2861;s:3:"Tor";i:2862;s:8:"CraMonoc";i:2863;s:15:"MisfoPrestSaliv";i:2864;s:7:"NonWaho";i:2865;s:12:"GlanReliSeax";i:2866;s:10:"BrNonpUnse";i:2867;s:7:"TarocTh";i:2868;s:3:"Ora";i:2869;s:8:"GrudgPra";i:2870;s:6:"PeSnUn";i:2871;s:13:"LigniMicroUnc";i:2872;s:7:"ClogHor";i:2873;s:10:"MiserRebUn";i:2874;s:10:"EntSpeSple";i:2875;s:10:"ColleGuard";i:2876;s:5:"Totte";i:2877;s:8:"IsataTax";i:2878;s:8:"NonWorra";i:2879;s:5:"Unsha";i:2880;s:10:"EuStanxUne";i:2881;s:3:"Pin";i:2882;s:4:"MoSe";i:2883;s:5:"WeaWh";i:2884;s:7:"ProviRa";i:2885;s:11:"PreTibiUnin";i:2886;s:11:"MyeloUnsUnt";i:2887;s:9:"InspiStee";i:2888;s:5:"Sensu";i:2889;s:9:"StufSwerv";i:2890;s:2:"Sy";i:2891;s:11:"GunnHaiTrun";i:2892;s:7:"NeopPre";i:2893;s:11:"BitMooncTip";i:2894;s:4:"Unso";i:2895;s:12:"MajPhlebSpin";i:2896;s:5:"Furil";i:2897;s:5:"Sledf";i:2898;s:10:"LadylMaSac";i:2899;s:10:"ChMylioSen";i:2900;s:9:"IxParaUre";i:2901;s:9:"DoMedalUn";i:2902;s:6:"OuUnsa";i:2903;s:8:"DouNonax";i:2904;s:4:"Univ";i:2905;s:4:"Moly";i:2906;s:9:"MonitPeda";i:2907;s:9:"CelaLunul";i:2908;s:8:"AnDenaMe";i:2909;s:6:"IrTerr";i:2910;s:3:"Usu";i:2911;s:7:"UnfoUnv";i:2912;s:7:"JudMono";i:2913;s:14:"PentRagsoUnimb";i:2914;s:9:"ReTransWh";i:2915;s:14:"EquisHornsInco";i:2916;s:12:"HukxOligiSup";i:2917;s:9:"PhSpSuper";i:2918;s:11:"SemUndaUnpr";i:2919;s:4:"Self";i:2920;s:11:"FleeMatePro";i:2921;s:6:"SupWor";i:2922;s:5:"Unben";i:2923;s:9:"MemorTach";i:2924;s:6:"SegUns";i:2925;s:10:"CoDisplSod";i:2926;s:7:"EleSple";i:2927;s:8:"PolyUror";i:2928;s:3:"Yal";i:2929;s:5:"JiTri";i:2930;s:10:"TaiThyroUn";i:2931;s:4:"Wath";i:2932;s:7:"EquiRep";i:2933;s:10:"ShimSubUnd";i:2934;s:6:"HasSan";i:2935;s:5:"HemUt";i:2936;s:4:"Spiv";i:2937;s:10:"MunycPhary";i:2938;s:8:"LargOver";i:2939;s:6:"OveSab";i:2940;s:14:"AulosLarxTraga";i:2941;s:9:"DePubesZa";i:2942;s:9:"LutSeYaki";i:2943;s:8:"PoSphaTh";i:2944;s:3:"Igl";i:2945;s:5:"Contr";i:2946;s:10:"PolysRamSu";i:2947;s:5:"Unack";i:2948;s:10:"AnCapsHyda";i:2949;s:6:"PoeSli";i:2950;s:6:"SausUn";i:2951;s:12:"PostgStaghWi";i:2952;s:8:"TetrTita";i:2953;s:14:"MendaPostScran";i:2954;s:9:"CharaSeag";i:2955;s:8:"HexSpodi";i:2956;s:5:"Quadr";i:2957;s:8:"SubpSwea";i:2958;s:9:"DoNicoTer";i:2959;s:7:"MilliSl";i:2960;s:13:"HelpPneumSlim";i:2961;s:6:"ToeUrb";i:2962;s:10:"DiMaionVen";i:2963;s:7:"PestePo";i:2964;s:7:"LiyPura";i:2965;s:10:"SparlTrian";i:2966;s:5:"BraGa";i:2967;s:7:"FrontRe";i:2968;s:5:"Unall";i:2969;s:5:"Diape";i:2970;s:8:"EscIdiot";i:2971;s:5:"Salam";i:2972;s:13:"CommHypeInest";i:2973;s:4:"Wors";i:2974;s:7:"ReinfSa";i:2975;s:8:"ResciTre";i:2976;s:10:"PiStaurSup";i:2977;s:8:"PorraUpl";i:2978;s:8:"StocUnki";i:2979;s:4:"IsSe";i:2980;s:11:"SattTaTrans";i:2981;s:3:"Mel";i:2982;s:11:"UnanUnUnexp";i:2983;s:5:"Green";i:2984;s:13:"SokemStulWape";i:2985;s:6:"TacoVe";i:2986;s:5:"Coral";i:2987;s:13:"ChoroDipicShr";i:2988;s:11:"PseSterTepe";i:2989;s:6:"OverRe";i:2990;s:10:"FreehMonPo";i:2991;s:12:"KreiLocaPlas";i:2992;s:9:"HurMorPty";i:2993;s:10:"DaSeqTirre";i:2994;s:13:"HypsiReproUre";i:2995;s:5:"Subno";i:2996;s:5:"Outpo";i:2997;s:10:"AndSevenVo";i:2998;s:5:"Vehmi";i:2999;s:3:"Vet";i:3000;s:5:"Proto";i:3001;s:5:"Unbri";i:3002;s:4:"Spie";i:3003;s:11:"ClaColobWhe";i:3004;s:4:"MeSy";i:3005;s:7:"ForSuXy";i:3006;s:11:"ChaKukPrior";i:3007;s:5:"Draco";i:3008;s:5:"Stair";i:3009;s:3:"Len";i:3010;s:4:"Saga";i:3011;s:9:"PyrroReTy";i:3012;s:4:"Nigg";i:3013;s:6:"ProSki";i:3014;s:8:"AutoPeri";i:3015;s:12:"NitroPieTric";i:3016;s:5:"StaTu";i:3017;s:3:"Rec";i:3018;s:4:"Pall";i:3019;s:12:"PreceShamUpl";i:3020;s:7:"CoviWag";i:3021;s:6:"ImTran";i:3022;s:13:"MandaStageUnf";i:3023;s:9:"SemisXero";i:3024;s:3:"Eso";i:3025;s:7:"FumatTr";i:3026;s:4:"Weev";i:3027;s:8:"IntraOst";i:3028;s:11:"GaNeurUngel";i:3029;s:7:"UnUnUnp";i:3030;s:5:"SiTan";i:3031;s:5:"FlPhy";i:3032;s:10:"GmeRubWarl";i:3033;s:9:"CayapTerm";i:3034;s:10:"MoRefinVau";i:3035;s:5:"Phosp";i:3036;s:12:"EligiHexUnkn";i:3037;s:6:"OvUnor";i:3038;s:5:"Verbi";i:3039;s:4:"Wind";i:3040;s:6:"BoroPh";i:3041;s:12:"MesoRudSinec";i:3042;s:10:"DisHemaJam";i:3043;s:8:"ReSiphSt";i:3044;s:4:"Pret";i:3045;s:4:"Pros";i:3046;s:8:"GlaObstu";i:3047;s:8:"JouPaPip";i:3048;s:8:"PunUndef";i:3049;s:5:"Ultra";i:3050;s:3:"Pur";i:3051;s:12:"MisMockxNomo";i:3052;s:12:"TactThacUnhe";i:3053;s:8:"PostSund";i:3054;s:9:"QuadrSavi";i:3055;s:7:"RecTepi";i:3056;s:12:"RutteTeUnplu";i:3057;s:6:"EncPar";i:3058;s:6:"ElSexu";i:3059;s:5:"MonSe";i:3060;s:4:"Velo";i:3061;s:10:"IthaTropUn";i:3062;s:9:"DuffTaute";i:3063;s:9:"ThesaTrUn";i:3064;s:5:"Unson";i:3065;s:14:"HumisKensiPanp";i:3066;s:9:"NectShTil";i:3067;s:4:"Pais";i:3068;s:8:"FeatSemi";i:3069;s:3:"Scu";i:3070;s:11:"DispePhWavy";i:3071;s:9:"ReSexivSp";i:3072;s:12:"NonfeNonlaPh";i:3073;s:9:"CuForYipx";i:3074;s:5:"DiHap";i:3075;s:11:"OversPhytSp";i:3076;s:8:"OrthScor";i:3077;s:3:"Ofo";i:3078;s:6:"MyoPic";i:3079;s:9:"CarLeNaos";i:3080;s:8:"PalmSati";i:3081;s:9:"SpokUnimo";i:3082;s:5:"Gorin";i:3083;s:9:"DyOligUnc";i:3084;s:9:"PhorSpaeb";i:3085;s:9:"ParaSumpt";i:3086;s:11:"CloConDidym";i:3087;s:11:"MenUnaWanto";i:3088;s:5:"Tetra";i:3089;s:7:"PreTurn";i:3090;s:8:"SliTitiv";i:3091;s:8:"HowisUns";i:3092;s:12:"MemPlantUnce";i:3093;s:11:"OstePindSuc";i:3094;s:12:"NonPhycSpect";i:3095;s:4:"Pogo";i:3096;s:2:"Ex";i:3097;s:4:"SaSt";i:3098;s:14:"SeedxSheexSupe";i:3099;s:5:"Fring";i:3100;s:13:"ExcoUndefUnev";i:3101;s:6:"IrRamx";i:3102;s:3:"Rab";i:3103;s:2:"Oe";i:3104;s:8:"UnevZhmu";i:3105;s:5:"OutPr";i:3106;s:3:"Ger";i:3107;s:6:"MesVer";i:3108;s:9:"FuchTidel";i:3109;s:10:"ChRetUnter";i:3110;s:4:"Mall";i:3111;s:10:"ReddySanda";i:3112;s:3:"Ido";i:3113;s:12:"FonSylviUnha";i:3114;s:10:"SamsoTrans";i:3115;s:6:"OrchTe";i:3116;s:10:"NotSemUnse";i:3117;s:4:"Ultr";i:3118;s:4:"PoUn";i:3119;s:8:"TransVal";i:3120;s:8:"FuciGlob";i:3121;s:7:"ScotTet";i:3122;s:15:"RicheRonsdWindo";i:3123;s:5:"Steel";i:3124;s:5:"Semif";i:3125;s:6:"SupUpw";i:3126;s:4:"Bedp";i:3127;s:13:"DagDispiUnsen";i:3128;s:3:"Coa";i:3129;s:12:"JetPedalSego";i:3130;s:4:"Lion";i:3131;s:11:"CymSnipjTeu";i:3132;s:4:"Prop";i:3133;s:7:"NeuroYi";i:3134;s:9:"MasMeTrom";i:3135;s:10:"EthylLoRef";i:3136;s:10:"CombDogSpi";i:3137;s:8:"FrLoMidm";i:3138;s:4:"Myog";i:3139;s:9:"PrScleSub";i:3140;s:13:"RefTuboaUnsor";i:3141;s:9:"EvasiStei";i:3142;s:9:"SpUnValix";i:3143;s:5:"Spira";i:3144;s:4:"EuPs";i:3145;s:9:"HardPenna";i:3146;s:5:"Wonde";i:3147;s:12:"NeonVocXipho";i:3148;s:10:"ChalJuckRe";i:3149;s:8:"DeopMadd";i:3150;s:4:"Grea";i:3151;s:3:"Tik";i:3152;s:5:"SuTar";i:3153;s:9:"LimacScTo";i:3154;s:13:"OverpPenxUnci";i:3155;s:5:"Rimpi";i:3156;s:4:"PySa";i:3157;s:10:"HydLamaiSq";i:3158;s:12:"HyReserSpiro";i:3159;s:11:"BeaBothDrif";i:3160;s:10:"PlRhiSikar";i:3161;s:4:"Olid";i:3162;s:7:"DiSperm";i:3163;s:4:"Prel";i:3164;s:5:"Mioce";i:3165;s:5:"Nonim";i:3166;s:5:"Prize";i:3167;s:7:"StasVul";i:3168;s:10:"PreliWoods";i:3169;s:3:"Rif";i:3170;s:3:"Bur";i:3171;s:13:"PleurUncluVes";i:3172;s:6:"RefSam";i:3173;s:8:"TickeXer";i:3174;s:4:"Unla";i:3175;s:7:"HyPelec";i:3176;s:12:"ForHumbuMalm";i:3177;s:9:"FuOrbitPh";i:3178;s:7:"FerFoRe";i:3179;s:4:"CoVe";i:3180;s:6:"NuncSu";i:3181;s:7:"CuUnfor";i:3182;s:11:"DemoPerStru";i:3183;s:6:"ReevUn";i:3184;s:9:"UndeWhelk";i:3185;s:4:"FuTu";i:3186;s:5:"Ithom";i:3187;s:5:"Sopex";i:3188;s:7:"InSpeci";i:3189;s:5:"Nontu";i:3190;s:6:"HomPre";i:3191;s:10:"InteMyceSu";i:3192;s:9:"CelLoSlac";i:3193;s:4:"Piny";i:3194;s:13:"HeterSpionTur";i:3195;s:4:"Slot";i:3196;s:8:"MetPresh";i:3197;s:13:"DisiNonnuVert";i:3198;s:9:"ArOdonWhi";i:3199;s:8:"InInteMe";i:3200;s:4:"Weat";i:3201;s:9:"GaUnsupUn";i:3202;s:7:"PrisoSh";i:3203;s:10:"IndiLycMol";i:3204;s:7:"PaRehUn";i:3205;s:7:"PollPro";i:3206;s:3:"Gro";i:3207;s:4:"Past";i:3208;s:10:"LambsPanty";i:3209;s:9:"ImpliProc";i:3210;s:5:"Cried";i:3211;s:6:"CycaUn";i:3212;s:14:"KeaxScreeUnlet";i:3213;s:8:"NundRevo";i:3214;s:12:"MadSherWeebl";i:3215;s:2:"Hi";i:3216;s:11:"RajaUnWokex";i:3217;s:12:"PseudRiviSte";i:3218;s:7:"FlaPaUn";i:3219;s:3:"Tax";i:3220;s:4:"PhPu";i:3221;s:11:"GraPsammStr";i:3222;s:14:"CelluEquiRailb";i:3223;s:7:"CongKer";i:3224;s:7:"MagnePs";i:3225;s:13:"NovelSeqSignl";i:3226;s:10:"DauncFoute";i:3227;s:11:"ArianPucRig";i:3228;s:10:"AlaCapThir";i:3229;s:7:"ConteDe";i:3230;s:12:"RupRuskySpri";i:3231;s:8:"FreUncon";i:3232;s:8:"HxLonYvo";i:3233;s:9:"PeriaStag";i:3234;s:2:"Op";i:3235;s:6:"LibiVa";i:3236;s:7:"PouTumu";i:3237;s:5:"Unerr";i:3238;s:7:"OblOrch";i:3239;s:8:"CountUng";i:3240;s:10:"NordiUneas";i:3241;s:9:"CtOrtSail";i:3242;s:5:"Roomm";i:3243;s:4:"Fant";i:3244;s:3:"Gan";i:3245;s:6:"PeUnle";i:3246;s:8:"MuPolRok";i:3247;s:9:"DrawHyper";i:3248;s:13:"EpiMaywiNotho";i:3249;s:7:"LySaxTe";i:3250;s:6:"NoncRh";i:3251;s:6:"FoUnsh";i:3252;s:3:"Sym";i:3253;s:11:"DiaOveThraw";i:3254;s:8:"CycloSem";i:3255;s:8:"PanjaThe";i:3256;s:2:"Ri";i:3257;s:5:"SuUnm";i:3258;s:9:"ArCoPirop";i:3259;s:9:"GoffLibMa";i:3260;s:10:"InsepTalpa";i:3261;s:4:"Steg";i:3262;s:5:"Renai";i:3263;s:9:"SubUnUnwi";i:3264;s:9:"PerPersQu";i:3265;s:5:"DisHo";i:3266;s:4:"Test";i:3267;s:8:"OpenhShi";i:3268;s:4:"SaTe";i:3269;s:5:"SarVe";i:3270;s:8:"PrSpVisi";i:3271;s:9:"KeLoloxRe";i:3272;s:3:"Exe";i:3273;s:4:"Plet";i:3274;s:3:"Uno";i:3275;s:5:"Overr";i:3276;s:7:"MyQuadr";i:3277;s:8:"MacSemSo";i:3278;s:11:"EgghoFeThur";i:3279;s:9:"GaNeTickl";i:3280;s:10:"CafEnlUnpr";i:3281;s:7:"CroScap";i:3282;s:12:"EspaNonmValv";i:3283;s:7:"ParaUnb";i:3284;s:3:"Tal";i:3285;s:9:"CysDitNon";i:3286;s:5:"Unsan";i:3287;s:5:"GlyRe";i:3288;s:4:"Tent";i:3289;s:3:"Bio";i:3290;s:9:"MeSpielUn";i:3291;s:12:"GrufRewoUnch";i:3292;s:5:"EumSe";i:3293;s:3:"Mog";i:3294;s:7:"SoUnswa";i:3295;s:9:"RidSpXero";i:3296;s:4:"Pock";i:3297;s:5:"Steno";i:3298;s:9:"EndMiliVi";i:3299;s:6:"EvocRh";i:3300;s:8:"JoQuisUn";i:3301;s:4:"Gemm";i:3302;s:7:"PinSoma";i:3303;s:6:"JubMul";i:3304;s:10:"BecivBrent";i:3305;s:9:"JaunSynco";i:3306;s:10:"KenMalSure";i:3307;s:8:"SeTriaWo";i:3308;s:9:"ScTriUpal";i:3309;s:10:"FeMycoZucc";i:3310;s:7:"FeUntru";i:3311;s:10:"ScathWeath";i:3312;s:5:"Knubb";i:3313;s:5:"OvUnu";i:3314;s:11:"DoNonliUnre";i:3315;s:5:"Relie";i:3316;s:4:"Must";i:3317;s:4:"PaRe";i:3318;s:2:"Il";i:3319;s:9:"DartPaTat";i:3320;s:5:"KilPr";i:3321;s:4:"DoMi";i:3322;s:7:"ScoSuff";i:3323;s:5:"PirSi";i:3324;s:4:"Rota";i:3325;s:3:"Wad";i:3326;s:14:"InfaMesolMonoh";i:3327;s:7:"RigidUn";i:3328;s:10:"RosSimilUn";i:3329;s:3:"Thr";i:3330;s:9:"DecolDupl";i:3331;s:11:"DialGlazePr";i:3332;s:6:"FunUnt";i:3333;s:6:"ScSemi";i:3334;s:4:"Elec";i:3335;s:14:"HeauLargeUpsta";i:3336;s:7:"SpUptra";i:3337;s:3:"Sno";i:3338;s:7:"NoNontr";i:3339;s:7:"DubTurb";i:3340;s:9:"MilkSupra";i:3341;s:7:"PhiPySu";i:3342;s:14:"NightPerioRect";i:3343;s:4:"EuMe";i:3344;s:12:"FuliLutePros";i:3345;s:12:"AutDeroEyoty";i:3346;s:10:"HulPrusSpu";i:3347;s:7:"PatWeez";i:3348;s:5:"Fragm";i:3349;s:6:"NuTigx";i:3350;s:6:"InfUlt";i:3351;s:6:"CoSubj";i:3352;s:8:"GreHyLea";i:3353;s:6:"CypsEm";i:3354;s:4:"Unsa";i:3355;s:5:"GuPro";i:3356;s:4:"Rotu";i:3357;s:5:"DoEmb";i:3358;s:3:"Pia";i:3359;s:7:"DrMoroc";i:3360;s:7:"LeOpsim";i:3361;s:5:"Sextu";i:3362;s:13:"EpipRecaShoop";i:3363;s:9:"ExaraSiVe";i:3364;s:12:"PreasSooUndi";i:3365;s:6:"FroInv";i:3366;s:7:"MorthPa";i:3367;s:3:"Wri";i:3368;s:7:"OverTet";i:3369;s:3:"Yea";i:3370;s:4:"Sand";i:3371;s:11:"DemonDiManq";i:3372;s:5:"MoiTi";i:3373;s:8:"ForsHype";i:3374;s:8:"HaloMont";i:3375;s:11:"InvPacUnexh";i:3376;s:12:"SilvUnaggWir";i:3377;s:8:"MuSemSen";i:3378;s:4:"Disp";i:3379;s:6:"ProUnq";i:3380;s:10:"ImploVenet";i:3381;s:4:"Hors";i:3382;s:5:"LucTh";i:3383;s:9:"ObliTibio";i:3384;s:4:"Homo";i:3385;s:12:"MonoSabSelen";i:3386;s:7:"NonSubm";i:3387;s:4:"Seri";i:3388;s:8:"CypriTeh";i:3389;s:8:"LocSewer";i:3390;s:12:"PiliTubUnmor";i:3391;s:5:"Beami";i:3392;s:8:"HyRegRep";i:3393;s:7:"SmUnrou";i:3394;s:7:"HeNoUnd";i:3395;s:8:"CyResSob";i:3396;s:9:"ElecaMocm";i:3397;s:5:"Quini";i:3398;s:10:"TegeaUnsca";i:3399;s:10:"ScorSeTrip";i:3400;s:8:"OligSeTh";i:3401;s:9:"HuRooUnde";i:3402;s:5:"Razor";i:3403;s:4:"Uncl";i:3404;s:6:"ScriSu";i:3405;s:4:"Unpu";i:3406;s:8:"RomneSin";i:3407;s:4:"Supe";i:3408;s:5:"Subor";i:3409;s:10:"PhotoProSt";i:3410;s:5:"MaVer";i:3411;s:8:"GrImToba";i:3412;s:10:"PaRetUnfor";i:3413;s:9:"PanPriaRe";i:3414;s:8:"FlokiPht";i:3415;s:6:"PreSol";i:3416;s:8:"QuiniVar";i:3417;s:12:"BurnRetSphae";i:3418;s:7:"GondSpe";i:3419;s:5:"Pseud";i:3420;s:7:"MeRespe";i:3421;s:5:"OtTur";i:3422;s:7:"OuPedRe";i:3423;s:10:"CeraNoSara";i:3424;s:7:"EpiFiRe";i:3425;s:14:"SulphThallTwee";i:3426;s:7:"OuRhema";i:3427;s:4:"Fanc";i:3428;s:8:"OoscoWay";i:3429;s:14:"ComfoHomodHypo";i:3430;s:12:"ImTapisUnfit";i:3431;s:4:"Laix";i:3432;s:5:"Unfes";i:3433;s:2:"Bu";i:3434;s:7:"EtReoTo";i:3435;s:9:"SnippYard";i:3436;s:5:"SaUnd";i:3437;s:5:"Osteo";i:3438;s:8:"PageaUnd";i:3439;s:8:"InhNonUn";i:3440;s:14:"MiscPilasSilic";i:3441;s:9:"OvePnStyc";i:3442;s:8:"BaNoncTo";i:3443;s:5:"Super";i:3444;s:6:"IrSeUn";i:3445;s:9:"CountEchi";i:3446;s:5:"BliRo";i:3447;s:11:"KeMoveaUnha";i:3448;s:15:"CratcSeamrSuper";i:3449;s:4:"Mist";i:3450;s:4:"Wrec";i:3451;s:7:"CenNons";i:3452;s:5:"Confr";i:3453;s:9:"IndecUnal";i:3454;s:5:"Groov";i:3455;s:5:"Nonre";i:3456;s:12:"OuthiSoSuper";i:3457;s:6:"HeSubc";i:3458;s:15:"MetriScuftTable";i:3459;s:5:"Satie";i:3460;s:12:"IncoOrphaSqu";i:3461;s:4:"HyTh";i:3462;s:10:"DirepGiMoi";i:3463;s:3:"Mor";i:3464;s:5:"PaPho";i:3465;s:6:"TavTol";i:3466;s:9:"IsthmNapp";i:3467;s:6:"CopSpl";i:3468;s:6:"BuTren";i:3469;s:11:"PrepUncrUnp";i:3470;s:3:"Mis";i:3471;s:5:"Twitt";i:3472;s:5:"Koiar";i:3473;s:10:"BuHirTetra";i:3474;s:7:"DipiWis";i:3475;s:5:"BasGh";i:3476;s:11:"DeziPreRune";i:3477;s:5:"PerPr";i:3478;s:5:"Scrol";i:3479;s:9:"CenSapSna";i:3480;s:12:"ObraOchleTyk";i:3481;s:6:"AreLan";i:3482;s:10:"MeniNoProa";i:3483;s:4:"Wadm";i:3484;s:10:"LaMotleUnd";i:3485;s:7:"CoSurfe";i:3486;s:5:"KaUnm";i:3487;s:5:"IlSim";i:3488;s:5:"Plero";i:3489;s:9:"LanceSele";i:3490;s:2:"Ro";i:3491;s:11:"DrawsSecuVe";i:3492;s:4:"Vine";i:3493;s:5:"Mythm";i:3494;s:8:"DiJesuKo";i:3495;s:9:"CorLiScut";i:3496;s:5:"ObeOr";i:3497;s:9:"PreStouTe";i:3498;s:8:"SubUnfor";i:3499;s:11:"DiEtonThala";i:3500;s:13:"FamOrallSuper";i:3501;s:5:"Unrib";i:3502;s:7:"PrReUno";i:3503;s:11:"MichTricWai";i:3504;s:8:"OveUtric";i:3505;s:5:"Propa";i:3506;s:10:"PolycVikin";i:3507;s:9:"DivMeduTh";i:3508;s:13:"LuxuTranViper";i:3509;s:8:"DiFrHypo";i:3510;s:13:"MagniMontWres";i:3511;s:10:"CantDeUnci";i:3512;s:10:"PeScripUnf";i:3513;s:10:"ClaitSpUnc";i:3514;s:10:"DecoPhoSup";i:3515;s:5:"InSys";i:3516;s:13:"RhetoUdoUnder";i:3517;s:4:"Marr";i:3518;s:5:"LaRed";i:3519;s:10:"KilolPiUns";i:3520;s:5:"Monoc";i:3521;s:13:"GalliUreViato";i:3522;s:3:"Mac";i:3523;s:9:"SadhScamm";i:3524;s:11:"PhoenSinUns";i:3525;s:8:"EpiUnjud";i:3526;s:8:"JasMonTu";i:3527;s:8:"ParWangx";i:3528;s:9:"AverCaste";i:3529;s:11:"BindMonoUnm";i:3530;s:3:"Sph";i:3531;s:5:"Postf";i:3532;s:5:"Oligo";i:3533;s:10:"ComexRodin";i:3534;s:4:"ThWe";i:3535;s:5:"Unfra";i:3536;s:10:"PleurUnaug";i:3537;s:8:"DepeNeSe";i:3538;s:10:"DiProRaddl";i:3539;s:7:"FeThere";i:3540;s:10:"HenOverpSi";i:3541;s:5:"Epido";i:3542;s:5:"GarTr";i:3543;s:7:"HypThZi";i:3544;s:4:"ReTw";i:3545;s:5:"Trans";i:3546;s:8:"AfReSchi";i:3547;s:5:"Dacry";i:3548;s:12:"QuartTruncWi";i:3549;s:10:"DishoFooti";i:3550;s:12:"ChasInvLutec";i:3551;s:4:"Whip";i:3552;s:5:"MaWin";i:3553;s:5:"HyUns";i:3554;s:11:"EmEvangPoly";i:3555;s:5:"Submo";i:3556;s:4:"PaSw";i:3557;s:6:"DipLac";i:3558;s:7:"MegRemo";i:3559;s:12:"ExtraHymeSec";i:3560;s:7:"OutlePl";i:3561;s:12:"KhasRanjUnle";i:3562;s:10:"RhRounxSan";i:3563;s:4:"Thea";i:3564;s:12:"GraRelatUnri";i:3565;s:4:"Snar";i:3566;s:4:"SoTh";i:3567;s:5:"Undel";i:3568;s:9:"GazHydSem";i:3569;s:11:"CatCopiUnho";i:3570;s:5:"Cerol";i:3571;s:6:"MisrTa";i:3572;s:11:"IntJouLiqui";i:3573;s:3:"Sec";i:3574;s:8:"UnrowVil";i:3575;s:8:"LocoThTh";i:3576;s:10:"HandmUngue";i:3577;s:2:"Is";i:3578;s:11:"RefUterXant";i:3579;s:5:"Canid";i:3580;s:7:"InIsote";i:3581;s:7:"PeUncor";i:3582;s:5:"HoWin";i:3583;s:9:"DisadNoti";i:3584;s:13:"GuttMaeaRamma";i:3585;s:4:"UnWa";i:3586;s:13:"EpiMerrySexua";i:3587;s:7:"FlunkSu";i:3588;s:4:"Solv";i:3589;s:8:"MiNeOnag";i:3590;s:4:"Nibo";i:3591;s:11:"FlocHydMuri";i:3592;s:9:"CaJozyOle";i:3593;s:9:"FemeResti";i:3594;s:7:"PhRocce";i:3595;s:4:"Urun";i:3596;s:9:"DrakeFrum";i:3597;s:11:"InsaMaOctin";i:3598;s:8:"PertuTea";i:3599;s:9:"MisOuriPh";i:3600;s:12:"HeSeaweWretc";i:3601;s:4:"MaPo";i:3602;s:9:"PancQuidd";i:3603;s:2:"Wh";i:3604;s:7:"SmugSol";i:3605;s:4:"Tris";i:3606;s:5:"CeInf";i:3607;s:7:"OphiTeg";i:3608;s:6:"FoFrem";i:3609;s:10:"HabilStore";i:3610;s:3:"Ste";i:3611;s:3:"Vei";i:3612;s:6:"HetmSy";i:3613;s:13:"RivoSpinoZoop";i:3614;s:7:"GyrMiSo";i:3615;s:11:"MeNoncPathi";i:3616;s:10:"DerOrPobsx";i:3617;s:5:"Volti";i:3618;s:9:"PoacUnrep";i:3619;s:9:"ExacSnaUn";i:3620;s:3:"Sce";i:3621;s:7:"DisSple";i:3622;s:11:"KiMonoUnaro";i:3623;s:4:"Tall";i:3624;s:12:"MyelOldSheet";i:3625;s:3:"Con";i:3626;s:14:"CollExpedPreci";i:3627;s:11:"InfecNevYol";i:3628;s:8:"ExHyTomf";i:3629;s:3:"Shi";i:3630;s:2:"Na";i:3631;s:11:"ErGownsMous";i:3632;s:7:"ScUtsuk";i:3633;s:9:"IsoQuRecr";i:3634;s:6:"ChaHon";i:3635;s:11:"EquIconoNep";i:3636;s:10:"TuitiUnsin";i:3637;s:11:"FoxMooUnpor";i:3638;s:4:"Synt";i:3639;s:7:"HemNerv";i:3640;s:5:"Wroth";i:3641;s:7:"UnZirba";i:3642;s:10:"HeterThUnm";i:3643;s:8:"ReUnraVo";i:3644;s:7:"PissaRo";i:3645;s:9:"DiFretSix";i:3646;s:8:"RabUnpro";i:3647;s:12:"InteIrksLipo";i:3648;s:10:"ByrSumpUnp";i:3649;s:2:"Oo";i:3650;s:13:"AntimDaggPseu";i:3651;s:12:"SuVaginVerbe";i:3652;s:5:"InLar";i:3653;s:5:"Fibro";i:3654;s:10:"FoldMeUnab";i:3655;s:12:"BiPrediProar";i:3656;s:11:"SyphThrasTr";i:3657;s:11:"ExNitroPala";i:3658;s:7:"DegrPre";i:3659;s:13:"ExormFeudxTer";i:3660;s:7:"SuTraUn";i:3661;s:7:"NyaSche";i:3662;s:7:"EntoGer";i:3663;s:9:"MacrTrans";i:3664;s:6:"DenoGn";i:3665;s:12:"MultiUnbarUn";i:3666;s:10:"InsLipPenc";i:3667;s:10:"JanTyroWhi";i:3668;s:10:"HalHydruMo";i:3669;s:3:"Irr";i:3670;s:13:"SkaffSubTriad";i:3671;s:5:"Windr";i:3672;s:5:"Unebb";i:3673;s:5:"RevWi";i:3674;s:7:"MajTend";i:3675;s:7:"PreUnbe";i:3676;s:12:"MyoOutscPala";i:3677;s:10:"DrainIlUnm";i:3678;s:13:"RepeSaxxUndis";i:3679;s:11:"ImidMuPsila";i:3680;s:7:"DopLuci";i:3681;s:7:"DecTezx";i:3682;s:9:"ToothViri";i:3683;s:14:"MagneRaspiTach";i:3684;s:10:"SluSwYalix";i:3685;s:10:"PhanSyUnel";i:3686;s:8:"HuReSpec";i:3687;s:4:"Pitc";i:3688;s:8:"HiHyShor";i:3689;s:12:"LieNonacPseu";i:3690;s:13:"KnaOinomSerri";i:3691;s:6:"EntUph";i:3692;s:9:"OdoSaraSu";i:3693;s:5:"Varis";i:3694;s:6:"CorOrp";i:3695;s:9:"BlNocSpli";i:3696;s:7:"SwomWis";i:3697;s:12:"PanscResWigw";i:3698;s:6:"RepTin";i:3699;s:5:"Chitc";i:3700;s:6:"InquMi";i:3701;s:6:"InWhin";i:3702;s:7:"HerTabo";i:3703;s:7:"RackfTu";i:3704;s:10:"MediRabbUn";i:3705;s:12:"CauloExospIn";i:3706;s:11:"HydroSiUnch";i:3707;s:8:"CyPrRect";i:3708;s:7:"AllBand";i:3709;s:8:"HystMail";i:3710;s:12:"HypLooseSole";i:3711;s:10:"CoFreckNon";i:3712;s:10:"AnEffeTali";i:3713;s:10:"DoctrPerit";i:3714;s:5:"SiUna";i:3715;s:4:"Rhiz";i:3716;s:4:"OuRe";i:3717;s:4:"Hobb";i:3718;s:3:"Cer";i:3719;s:9:"AnOrangRa";i:3720;s:9:"SoulfSpZo";i:3721;s:11:"UnreUnsVeto";i:3722;s:5:"HyTec";i:3723;s:9:"AtPhotUns";i:3724;s:4:"Lune";i:3725;s:3:"Tro";i:3726;s:8:"PhagoPim";i:3727;s:9:"NagNoUnde";i:3728;s:9:"FezzeMike";i:3729;s:10:"RiffxVersa";i:3730;s:8:"SigSusan";i:3731;s:7:"OverRei";i:3732;s:10:"NonOverPer";i:3733;s:11:"CoundSpriTc";i:3734;s:5:"Bismu";i:3735;s:9:"ClefThrea";i:3736;s:9:"PinkTetra";i:3737;s:5:"Therm";i:3738;s:5:"PeXyl";i:3739;s:5:"SaSma";i:3740;s:15:"PerinTransUnart";i:3741;s:5:"Pytha";i:3742;s:7:"SyUngen";i:3743;s:9:"BeMonotPa";i:3744;s:11:"DiDictyMoto";i:3745;s:11:"PolyTrWarbl";i:3746;s:9:"FaForeHor";i:3747;s:4:"Inkl";i:3748;s:5:"Perfr";i:3749;s:8:"FunMeRep";i:3750;s:10:"PrePrSkunk";i:3751;s:6:"DoHepa";i:3752;s:12:"NonParoPhilo";i:3753;s:7:"GroUnre";i:3754;s:9:"PillaUnwa";i:3755;s:10:"CixoxSocTh";i:3756;s:6:"HypPat";i:3757;s:15:"TautoTreacUnhar";i:3758;s:4:"Cant";i:3759;s:7:"DisUnsa";i:3760;s:15:"InconMilleWenon";i:3761;s:13:"EmbryNonacPre";i:3762;s:5:"Nonfl";i:3763;s:12:"RegaTripUngr";i:3764;s:9:"ScratWarn";i:3765;s:6:"BiGluc";i:3766;s:7:"BaPolyp";i:3767;s:11:"NovaReskSto";i:3768;s:4:"Utte";i:3769;s:6:"PlaPle";i:3770;s:9:"HematMona";i:3771;s:8:"OphZoono";i:3772;s:10:"StaidUnpre";i:3773;s:9:"DispoIntr";i:3774;s:7:"JeluJet";i:3775;s:4:"Four";i:3776;s:10:"IntMycoUpl";i:3777;s:5:"Skulk";i:3778;s:11:"LepPoodlSer";i:3779;s:13:"OxyrhSpiSquar";i:3780;s:7:"ExHulPa";i:3781;s:5:"SaSod";i:3782;s:15:"PretySweepSweet";i:3783;s:11:"GryMystSter";i:3784;s:5:"RuSmo";i:3785;s:7:"SuperUn";i:3786;s:8:"PolStorm";i:3787;s:8:"IncliPac";i:3788;s:5:"Prepr";i:3789;s:8:"MeckeMeg";i:3790;s:5:"Unobj";i:3791;s:3:"Gra";i:3792;s:9:"OwerlPrTe";i:3793;s:9:"BrShaVesi";i:3794;s:5:"Thaum";i:3795;s:2:"Ki";i:3796;s:7:"HeUngui";i:3797;s:4:"Sout";i:3798;s:4:"Ross";i:3799;s:8:"EyeWhips";i:3800;s:10:"HeIatroUnp";i:3801;s:5:"MePro";i:3802;s:5:"Fault";i:3803;s:9:"MutaSabba";i:3804;s:3:"Swa";i:3805;s:8:"ReSoWive";i:3806;s:5:"FlIsl";i:3807;s:4:"HiPh";i:3808;s:8:"ChelMerc";i:3809;s:6:"BlaReq";i:3810;s:4:"Codi";i:3811;s:9:"SpTriWote";i:3812;s:9:"LegiPyrSt";i:3813;s:6:"EnLida";i:3814;s:4:"Ungr";i:3815;s:11:"PuSchoSnudg";i:3816;s:8:"HyOsmUnh";i:3817;s:7:"UndeZos";i:3818;s:12:"PhilProvSnap";i:3819;s:11:"OoPeriSangu";i:3820;s:5:"Smutt";i:3821;s:5:"MePac";i:3822;s:5:"Thuri";i:3823;s:14:"ArsenHerseSuba";i:3824;s:4:"Macc";i:3825;s:5:"Untra";i:3826;s:8:"CrNonPil";i:3827;s:4:"CaMe";i:3828;s:8:"PanTherm";i:3829;s:12:"InMesolPatac";i:3830;s:8:"MatriNon";i:3831;s:6:"GeoSex";i:3832;s:9:"NurseShit";i:3833;s:4:"Stro";i:3834;s:11:"BavPreinTug";i:3835;s:11:"KarsMuUnwit";i:3836;s:4:"Meta";i:3837;s:7:"OvPrill";i:3838;s:7:"MiMucos";i:3839;s:10:"CouMobsSha";i:3840;s:4:"Visi";i:3841;s:7:"SmooSub";i:3842;s:6:"CyPaSi";i:3843;s:9:"MyPacUntu";i:3844;s:5:"Oricy";i:3845;s:3:"Sup";i:3846;s:6:"TerUnd";i:3847;s:8:"IsMonoSa";i:3848;s:9:"HidSunrWo";i:3849;s:12:"MolaxSanUnst";i:3850;s:5:"MoPha";i:3851;s:8:"PieSubri";i:3852;s:5:"Watch";i:3853;s:4:"Team";i:3854;s:10:"PrSesaVind";i:3855;s:10:"HexadNaVac";i:3856;s:9:"MelanNoRa";i:3857;s:7:"PrSepSt";i:3858;s:5:"Sridh";i:3859;s:5:"MasPo";i:3860;s:7:"LiStatu";i:3861;s:10:"SprawTrans";i:3862;s:8:"LooPhoto";i:3863;s:9:"CaTaZingi";i:3864;s:3:"Oro";i:3865;s:5:"Dermi";i:3866;s:7:"DrierRe";i:3867;s:10:"MiskPerSup";i:3868;s:3:"Wat";i:3869;s:6:"CoWelc";i:3870;s:11:"NonPediScho";i:3871;s:10:"JemmOuRubo";i:3872;s:5:"Urtic";i:3873;s:5:"FrGal";i:3874;s:6:"LaceVa";i:3875;s:8:"RecSubch";i:3876;s:5:"SaSph";i:3877;s:12:"MarylMnemoOn";i:3878;s:3:"Lie";i:3879;s:8:"FluPrSuf";i:3880;s:5:"Pangx";i:3881;s:13:"MediReoccUnda";i:3882;s:9:"CelFatGou";i:3883;s:4:"Xant";i:3884;s:4:"Moil";i:3885;s:11:"DeltForIned";i:3886;s:7:"RegiUnp";i:3887;s:7:"KabylTh";i:3888;s:8:"ShaTrade";i:3889;s:7:"VilWaWi";i:3890;s:9:"MappiOsph";i:3891;s:5:"DriSl";i:3892;s:5:"Tauto";i:3893;s:5:"Sinia";i:3894;s:5:"PrUnr";i:3895;s:7:"UnsVerb";i:3896;s:13:"DesExploTorsi";i:3897;s:5:"PaUna";i:3898;s:8:"PhotUpis";i:3899;s:12:"FemHalfSubur";i:3900;s:10:"BesoDolMem";i:3901;s:9:"WordZebra";i:3902;s:5:"Sweep";i:3903;s:7:"TrUnpro";i:3904;s:11:"ResSerorWid";i:3905;s:8:"SafrSouv";i:3906;s:4:"Ridg";i:3907;s:11:"CancMessUnd";i:3908;s:6:"OvUnUn";i:3909;s:9:"ShSqueUne";i:3910;s:7:"OratoRo";i:3911;s:5:"GaSpe";i:3912;s:7:"CoOolog";i:3913;s:5:"NonPr";i:3914;s:9:"VanguWaho";i:3915;s:7:"ConseLa";i:3916;s:4:"Zimb";i:3917;s:2:"Im";i:3918;s:11:"AzDiasFonsx";i:3919;s:11:"CrEmbleGale";i:3920;s:7:"DiHonUn";i:3921;s:8:"PerjSphy";i:3922;s:12:"PomonStUnvol";i:3923;s:9:"MiPokoTik";i:3924;s:10:"BikhxConic";i:3925;s:4:"BiFi";i:3926;s:6:"CruFig";i:3927;s:4:"Requ";i:3928;s:5:"Amoeb";i:3929;s:9:"FeathHusb";i:3930;s:8:"ExcrLave";i:3931;s:3:"Obl";i:3932;s:10:"HyperMonSh";i:3933;s:7:"UnerrXe";i:3934;s:8:"HypJibNe";i:3935;s:11:"HousSbUnent";i:3936;s:4:"Pyra";i:3937;s:10:"BacParaSub";i:3938;s:5:"Occam";i:3939;s:12:"MichiRumblTu";i:3940;s:4:"Supr";i:3941;s:7:"FeUnsuf";i:3942;s:7:"DowieSp";i:3943;s:10:"FaceOppoUn";i:3944;s:12:"MetamTopoUbi";i:3945;s:7:"PeriPre";i:3946;s:7:"PsycSep";i:3947;s:8:"MeloUncl";i:3948;s:8:"UndanWau";i:3949;s:11:"AutoChoResy";i:3950;s:7:"OvPegUn";i:3951;s:10:"ArseSoyUns";i:3952;s:5:"SpeUn";i:3953;s:4:"Pinn";i:3954;s:5:"Unspr";i:3955;s:3:"Glo";i:3956;s:5:"BraLe";i:3957;s:10:"IdylNonUnm";i:3958;s:9:"ChilChimp";i:3959;s:2:"Hy";i:3960;s:11:"KharNicomPi";i:3961;s:14:"HemipPariUnpre";i:3962;s:8:"TexUnciv";i:3963;s:12:"MitNaphtRevi";i:3964;s:12:"IraqiKoreTat";i:3965;s:12:"HephInfuThix";i:3966;s:13:"ButtFratcPrer";i:3967;s:11:"BanExtraPro";i:3968;s:10:"PsPulpVomi";i:3969;s:9:"DraffTutw";i:3970;s:10:"NoSoliTheo";i:3971;s:6:"NonvSo";i:3972;s:10:"BeSpurSuba";i:3973;s:3:"Rey";i:3974;s:8:"FadGuTel";i:3975;s:12:"FluGeocPascu";i:3976;s:5:"Punty";i:3977;s:5:"Alcid";i:3978;s:5:"Soggy";i:3979;s:7:"PurpRes";i:3980;s:3:"Por";i:3981;s:7:"MultThw";i:3982;s:2:"Of";i:3983;s:7:"GalicOd";i:3984;s:13:"HyperMilUndis";i:3985;s:7:"ElegiUn";i:3986;s:4:"Inst";i:3987;s:8:"MonkcPan";i:3988;s:4:"Jawx";i:3989;s:12:"CoDonneRewar";i:3990;s:11:"MerkSemidUn";i:3991;s:14:"ErectRomerSero";i:3992;s:4:"Jett";i:3993;s:10:"DeJohRahul";i:3994;s:2:"Ar";i:3995;s:5:"Silic";i:3996;s:10:"PrearPrSem";i:3997;s:4:"Meso";i:3998;s:10:"CysLicheRe";i:3999;s:4:"Snag";i:4000;s:9:"GenecPres";i:4001;s:7:"PropSha";i:4002;s:9:"HamaTraUn";i:4003;s:9:"GyroMatUn";i:4004;s:12:"MisuNonParas";i:4005;s:13:"KindeOvePrein";i:4006;s:13:"IconUnentUnpl";i:4007;s:10:"MyrmeNotel";i:4008;s:10:"KrobPoUnsa";i:4009;s:10:"IndusPalau";i:4010;s:7:"HostPar";i:4011;s:9:"HelPreiSu";i:4012;s:5:"Kacha";i:4013;s:3:"Nac";i:4014;s:7:"SafeWan";i:4015;s:8:"ScrViost";i:4016;s:9:"LoSinnSub";i:4017;s:5:"Vedai";i:4018;s:12:"IdeoPanVaunt";i:4019;s:9:"DeErgxOcy";i:4020;s:8:"HanMePal";i:4021;s:7:"ShValky";i:4022;s:11:"DingIncuTel";i:4023;s:8:"PePillSp";i:4024;s:6:"FocPos";i:4025;s:5:"KnaTr";i:4026;s:4:"Wint";i:4027;s:12:"InsiTheUnmis";i:4028;s:7:"AtmogLe";i:4029;s:8:"MePalRec";i:4030;s:8:"ComKwann";i:4031;s:4:"Over";i:4032;s:12:"NiobiNoneqTo";i:4033;s:11:"KiScytStrop";i:4034;s:4:"Wast";i:4035;s:10:"CasuInteMo";i:4036;s:5:"ShWhi";i:4037;s:3:"Wro";i:4038;s:9:"SwasThTot";i:4039;s:5:"AndSc";i:4040;s:6:"GrapSc";i:4041;s:7:"FirepUn";i:4042;s:13:"BoerBuffaPass";i:4043;s:7:"CoSpTin";i:4044;s:3:"Gor";i:4045;s:7:"DisbeHi";i:4046;s:5:"EleSi";i:4047;s:12:"LympUninkYer";i:4048;s:5:"ShaSl";i:4049;s:9:"IlLiPlagi";i:4050;s:7:"CeOrtho";i:4051;s:7:"AntShel";i:4052;s:7:"DiPeris";i:4053;s:3:"Flo";i:4054;s:10:"GladiNonfa";i:4055;s:4:"Unen";i:4056;s:5:"Myosa";i:4057;s:8:"ReqUnder";i:4058;s:8:"IdyTortr";i:4059;s:10:"CanPrTheri";i:4060;s:2:"Ur";i:4061;s:10:"AtollCorti";i:4062;s:8:"DexGestx";i:4063;s:6:"MerQua";i:4064;s:4:"Usat";i:4065;s:14:"BromLicenNaseb";i:4066;s:11:"GoOrthoSull";i:4067;s:11:"QuinSuperUn";i:4068;s:12:"ConfKoftPass";i:4069;s:8:"OrgaSkTr";i:4070;s:6:"CheaOu";i:4071;s:11:"BafGynRamul";i:4072;s:9:"OpPerZepp";i:4073;s:9:"HypMerTom";i:4074;s:7:"RarelUn";i:4075;s:13:"EpidoPostcSta";i:4076;s:7:"VandyWh";i:4077;s:10:"OutwaRapSo";i:4078;s:11:"OverSouWall";i:4079;s:11:"BeclaChorCo";i:4080;s:3:"Sin";i:4081;s:6:"RaSnak";i:4082;s:10:"FiHomLumbr";i:4083;s:12:"PapaProaThyr";i:4084;s:7:"IntTric";i:4085;s:10:"DisInfrOrl";i:4086;s:10:"GoldeIntVo";i:4087;s:8:"DainOoUl";i:4088;s:9:"FullMulti";i:4089;s:9:"MolRizWoo";i:4090;s:5:"Mimos";i:4091;s:5:"Unfun";i:4092;s:7:"HiSuber";i:4093;s:9:"OpistTrac";i:4094;s:9:"MourPreho";i:4095;s:10:"ProtoRebra";i:4096;s:7:"DiManif";i:4097;s:9:"RepuSubin";i:4098;s:5:"Khald";i:4099;s:5:"SkiSu";i:4100;s:8:"BecloNon";i:4101;s:5:"Mesol";i:4102;s:12:"InnaSmeltTur";i:4103;s:9:"ExtooSidt";i:4104;s:12:"InduTitTrywo";i:4105;s:8:"CoMarsMa";i:4106;s:3:"Wei";i:4107;s:6:"GrSiph";i:4108;s:9:"MelOvPyro";i:4109;s:9:"JovinUnin";i:4110;s:6:"OlPsyc";i:4111;s:10:"GiggeWinly";i:4112;s:5:"Shoem";i:4113;s:8:"CoSyncUn";i:4114;s:10:"NomOverhSp";i:4115;s:10:"TrembUncon";i:4116;s:8:"ReinScSm";i:4117;s:5:"Gager";i:4118;s:11:"TiUnprWorke";i:4119;s:9:"MaParanRe";i:4120;s:9:"StruUnche";i:4121;s:3:"Div";i:4122;s:13:"IndiNeomWoodb";i:4123;s:7:"FiFlaUn";i:4124;s:6:"BryCru";i:4125;s:9:"SubdoSubs";i:4126;s:9:"ReveUnWin";i:4127;s:2:"Ir";i:4128;s:7:"GlSkill";i:4129;s:8:"MagySent";i:4130;s:6:"SarsUn";i:4131;s:14:"SquirVaricVola";i:4132;s:6:"GallWa";i:4133;s:7:"PlumbPr";i:4134;s:10:"EmersHaStr";i:4135;s:10:"EffForSupe";i:4136;s:7:"ReiSpin";i:4137;s:12:"PeacPleTinny";i:4138;s:4:"Tara";i:4139;s:11:"DoghInterSi";i:4140;s:9:"KetPoSpik";i:4141;s:7:"LiMalle";i:4142;s:4:"Unbo";i:4143;s:12:"SharUnfVersa";i:4144;s:7:"UndeWea";i:4145;s:3:"Myx";i:4146;s:7:"DetPlut";i:4147;s:7:"IroqRec";i:4148;s:7:"IntPsha";i:4149;s:5:"PosSe";i:4150;s:10:"SubTimarUn";i:4151;s:5:"MetPa";i:4152;s:9:"HoSnarStr";i:4153;s:5:"Phall";i:4154;s:10:"NecrThUnte";i:4155;s:12:"GemaHortSwam";i:4156;s:4:"Nong";i:4157;s:7:"CerSole";i:4158;s:10:"LatinPenma";i:4159;s:11:"PieriRejSem";i:4160;s:5:"PrPro";i:4161;s:8:"MultiPri";i:4162;s:4:"Twol";i:4163;s:7:"TraTurb";i:4164;s:7:"NaNodRe";i:4165;s:5:"Ouaba";i:4166;s:6:"KerPlu";i:4167;s:4:"Pont";i:4168;s:3:"Gur";i:4169;s:11:"PersSpoliUn";i:4170;s:7:"PlantSl";i:4171;s:2:"Fl";i:4172;s:8:"DiSuUnbo";i:4173;s:8:"IseReque";i:4174;s:11:"GenePtVentr";i:4175;s:4:"Rain";i:4176;s:7:"TrUnUns";i:4177;s:7:"DisUnmi";i:4178;s:10:"OcuReVorti";i:4179;s:8:"AzComSiz";i:4180;s:11:"InterStaVes";i:4181;s:6:"FoPanc";i:4182;s:12:"DeutLifNocta";i:4183;s:4:"HyVo";i:4184;s:7:"OvePres";i:4185;s:6:"SaUnpr";i:4186;s:13:"IzduPostePros";i:4187;s:11:"PePiscoUnri";i:4188;s:9:"FlakePist";i:4189;s:10:"GrappOratr";i:4190;s:9:"OilmaPoly";i:4191;s:4:"Soro";i:4192;s:11:"BellUncloUn";i:4193;s:4:"Ribb";i:4194;s:5:"Encri";i:4195;s:11:"NocuShopSia";i:4196;s:8:"GloTraum";i:4197;s:10:"NondParaPh";i:4198;s:12:"GooxPanWeapo";i:4199;s:8:"ScleWhor";i:4200;s:6:"LokaRa";i:4201;s:9:"SaWeaseWi";i:4202;s:8:"ConUnglo";i:4203;s:6:"TafiWo";i:4204;s:13:"EmpPrayfSubal";i:4205;s:11:"CallCamComp";i:4206;s:8:"CorPaleo";i:4207;s:9:"FrOpiuPal";i:4208;s:7:"CardoHe";i:4209;s:10:"LapMaQuebr";i:4210;s:11:"EncycKupUnh";i:4211;s:3:"Opi";i:4212;s:12:"HydroMeRecti";i:4213;s:4:"Nomo";i:4214;s:5:"Unsub";i:4215;s:7:"HomesRa";i:4216;s:12:"ForksMoStepg";i:4217;s:8:"OctoPopp";i:4218;s:12:"FrazQuernSna";i:4219;s:5:"Serol";i:4220;s:7:"NonelSc";i:4221;s:11:"PepPlaUncom";i:4222;s:11:"NonRetrUnha";i:4223;s:5:"Moril";i:4224;s:4:"CoTo";i:4225;s:7:"EnduLin";i:4226;s:8:"PhoebUng";i:4227;s:11:"HetTalkWrec";i:4228;s:12:"JadisMuUnfor";i:4229;s:5:"BitPu";i:4230;s:5:"EuOle";i:4231;s:10:"UnpUnrarUp";i:4232;s:3:"Vis";i:4233;s:7:"OilskPe";i:4234;s:9:"MaSuVicel";i:4235;s:12:"HypoiIlocaWa";i:4236;s:5:"Figle";i:4237;s:5:"Level";i:4238;s:7:"EntalIn";i:4239;s:7:"PrePrun";i:4240;s:7:"ScolUnf";i:4241;s:3:"Gna";i:4242;s:8:"SepSymph";i:4243;s:5:"LeiSp";i:4244;s:5:"Onrus";i:4245;s:12:"NonOxidoStee";i:4246;s:9:"AsymReiRe";i:4247;s:11:"OvePluRelin";i:4248;s:9:"MePedVege";i:4249;s:12:"HemMarkMasse";i:4250;s:10:"OrogeUnpea";i:4251;s:9:"ForPilThy";i:4252;s:7:"MaPtery";i:4253;s:10:"OraShrUndi";i:4254;s:5:"Bacte";i:4255;s:6:"RefoUn";i:4256;s:13:"ParabPhysaTwa";i:4257;s:13:"ClaDandlItera";i:4258;s:9:"LeucMicOp";i:4259;s:3:"Vip";i:4260;s:7:"TrunkWr";i:4261;s:5:"Vermi";i:4262;s:10:"DupHirUnde";i:4263;s:14:"HoardSalteTaur";i:4264;s:3:"Vio";i:4265;s:5:"Goatb";i:4266;s:12:"DressInMaryx";i:4267;s:4:"Neph";i:4268;s:12:"TanstTrivaWe";i:4269;s:6:"LupeSu";i:4270;s:2:"Us";i:4271;s:6:"PerTab";i:4272;s:9:"FiMerYeas";i:4273;s:4:"OnOt";i:4274;s:10:"AntihSmich";i:4275;s:7:"ObiRoSa";i:4276;s:9:"UnscWelsh";i:4277;s:7:"RhombUn";i:4278;s:5:"Think";i:4279;s:6:"SinWes";i:4280;s:5:"LesSu";i:4281;s:5:"NoObs";i:4282;s:8:"ScarWusp";i:4283;s:8:"HoNaPhot";i:4284;s:5:"Vulca";i:4285;s:12:"MarrOwsYeast";i:4286;s:4:"Togu";i:4287;s:13:"FiloPilgrRuth";i:4288;s:8:"CouPyrTe";i:4289;s:7:"ConUnpa";i:4290;s:4:"Unag";i:4291;s:9:"SaUncUroc";i:4292;s:6:"UnrWhe";i:4293;s:9:"PolyShatt";i:4294;s:5:"ReaSi";i:4295;s:7:"NonOuac";i:4296;s:10:"OlidPsUnex";i:4297;s:6:"SwunUn";i:4298;s:7:"TorvVac";i:4299;s:9:"SoariVall";i:4300;s:9:"EreGaShea";i:4301;s:10:"PospoSemip";i:4302;s:8:"NoSuscUn";i:4303;s:3:"Ost";i:4304;s:9:"ErytMolSa";i:4305;s:5:"SaTre";i:4306;s:7:"PaShaZe";i:4307;s:11:"EnlTaarxTra";i:4308;s:5:"Puppi";i:4309;s:3:"War";i:4310;s:4:"Fera";i:4311;s:13:"CircHandkStal";i:4312;s:6:"PiSubc";i:4313;s:5:"Morrh";i:4314;s:10:"JoiPhytoTe";i:4315;s:8:"DevExcur";i:4316;s:7:"NunsPig";i:4317;s:10:"MarsPiePri";i:4318;s:6:"DuraSa";i:4319;s:5:"FluGe";i:4320;s:4:"RoWi";i:4321;s:5:"Ovine";i:4322;s:10:"PepsiPhVir";i:4323;s:8:"MiSpSupe";i:4324;s:3:"Sel";i:4325;s:5:"Sympt";i:4326;s:11:"IsUndrWhatt";i:4327;s:6:"AnOstr";i:4328;s:4:"IrSa";i:4329;s:4:"Spal";i:4330;s:4:"Sear";i:4331;s:3:"Fiq";i:4332;s:7:"FaOutro";i:4333;s:11:"DamoMePolyd";i:4334;s:13:"HarmfOreTushx";i:4335;s:5:"MaUnt";i:4336;s:7:"DesmoTa";i:4337;s:9:"CossaPhPh";i:4338;s:5:"Notho";i:4339;s:5:"Sowli";i:4340;s:7:"MisesPo";i:4341;s:4:"Uplo";i:4342;s:5:"Recti";i:4343;s:7:"GloHolo";i:4344;s:9:"TyppUnnob";i:4345;s:3:"Euc";i:4346;s:8:"MonNotPr";i:4347;s:7:"LogiSoo";i:4348;s:6:"NoUnde";i:4349;s:4:"Mosa";i:4350;s:3:"Cor";i:4351;s:9:"CinquSupr";i:4352;s:7:"PunkVra";i:4353;s:10:"CarSlWebwo";i:4354;s:5:"CarDe";i:4355;s:7:"TetraUl";i:4356;s:2:"Jo";i:4357;s:4:"OvPo";i:4358;s:4:"Spoi";i:4359;s:8:"IrresTyp";i:4360;s:6:"OvRySo";i:4361;s:6:"MicrPa";i:4362;s:3:"Ral";i:4363;s:5:"PrSem";i:4364;s:8:"OuttrSol";i:4365;s:12:"DisInduSmoke";i:4366;s:7:"PneuSag";i:4367;s:4:"Pstx";i:4368;s:8:"MeOveUnr";i:4369;s:5:"Diaer";i:4370;s:8:"FrorGiUn";i:4371;s:6:"SciSea";i:4372;s:6:"ImOver";i:4373;s:7:"CuLoath";i:4374;s:13:"MatriSorTawgi";i:4375;s:5:"SulWa";i:4376;s:11:"DemiHoodxMu";i:4377;s:9:"GladGuiMu";i:4378;s:10:"MaiMazPeri";i:4379;s:9:"ReedTetra";i:4380;s:8:"MeUnUnpe";i:4381;s:11:"HistoHouTro";i:4382;s:7:"DumGoUn";i:4383;s:3:"Ses";i:4384;s:12:"ReceScincUpg";i:4385;s:9:"ChaDiNonm";i:4386;s:9:"HyraxUnsc";i:4387;s:7:"MiNephr";i:4388;s:5:"PlePr";i:4389;s:5:"OpPre";i:4390;s:8:"AwnlBitt";i:4391;s:12:"JuraMucUnrue";i:4392;s:4:"NoRu";i:4393;s:4:"Rita";i:4394;s:10:"DyStenTetr";i:4395;s:6:"MoPoUn";i:4396;s:11:"ConKokoPapi";i:4397;s:8:"HoUnUnde";i:4398;s:13:"RhiUnsweWapac";i:4399;s:10:"BecLadRema";i:4400;s:13:"JordaSeleWitn";i:4401;s:8:"TramUnwh";i:4402;s:13:"GladePanzoZym";i:4403;s:7:"EvilOli";i:4404;s:11:"LeoUnconWea";i:4405;s:5:"ThToo";i:4406;s:8:"SubvVisi";i:4407;s:11:"IloInaTrogo";i:4408;s:5:"Troch";i:4409;s:5:"TriWo";i:4410;s:10:"IsoJuvVanq";i:4411;s:7:"NaTalon";i:4412;s:11:"CauRenSemin";i:4413;s:11:"HectoMancPa";i:4414;s:5:"HySwa";i:4415;s:6:"CuprLe";i:4416;s:4:"Tain";i:4417;s:4:"Watc";i:4418;s:13:"NegriPosWollo";i:4419;s:10:"DetInterLy";i:4420;s:11:"HamiManSpoa";i:4421;s:6:"FuUran";i:4422;s:11:"EpitLegitSi";i:4423;s:4:"Resi";i:4424;s:9:"FlobIctPh";i:4425;s:13:"QopSpaniTroch";i:4426;s:12:"ReediThirUna";i:4427;s:14:"ItaliNondeRuck";i:4428;s:8:"UntUpsho";i:4429;s:11:"InquStanTri";i:4430;s:10:"PeSebiTarg";i:4431;s:5:"ReUnp";i:4432;s:9:"FlocHecRe";i:4433;s:7:"MogrSum";i:4434;s:5:"ForHi";i:4435;s:6:"NicePe";i:4436;s:4:"Unob";i:4437;s:8:"MnemSupe";i:4438;s:5:"StUnc";i:4439;s:5:"Basop";i:4440;s:4:"Rust";i:4441;s:10:"ChGonVerme";i:4442;s:6:"SigYet";i:4443;s:4:"Trap";i:4444;s:5:"Uproo";i:4445;s:7:"BrSpecu";i:4446;s:5:"DePro";i:4447;s:11:"PrPterUness";i:4448;s:7:"LipPeTi";i:4449;s:8:"RoostTru";i:4450;s:12:"EngarIndeSub";i:4451;s:9:"SipeTaint";i:4452;s:13:"HutxLibelUnem";i:4453;s:10:"DuEtheUnor";i:4454;s:9:"SmStockSw";i:4455;s:7:"TarTeYu";i:4456;s:5:"LoUnr";i:4457;s:4:"Yesx";i:4458;s:9:"SleeStone";i:4459;s:10:"FraGemmPer";i:4460;s:9:"IsocThesm";i:4461;s:10:"DerogEritr";i:4462;s:13:"ConHarasLippy";i:4463;s:5:"UnUnr";i:4464;s:6:"GastSt";i:4465;s:4:"Unsh";i:4466;s:9:"GalHypUnt";i:4467;s:2:"Fe";i:4468;s:13:"HypotIntReali";i:4469;s:4:"Uner";i:4470;s:6:"MoUnid";i:4471;s:11:"HomodMymVil";i:4472;s:9:"FibPrepUn";i:4473;s:9:"SpracWart";i:4474;s:5:"EdxSo";i:4475;s:8:"AxeOverf";i:4476;s:12:"EunucFanhoMo";i:4477;s:4:"DeFl";i:4478;s:8:"ErGnSulp";i:4479;s:9:"MoOvePeri";i:4480;s:11:"ScUnanUnpow";i:4481;s:11:"NautiTaYawn";i:4482;s:6:"QuScSu";i:4483;s:3:"Urf";i:4484;s:4:"Phyt";i:4485;s:7:"GhMyelo";i:4486;s:5:"ClEle";i:4487;s:12:"MetePyrotSne";i:4488;s:7:"MonSpoi";i:4489;s:5:"Operc";i:4490;s:5:"HaHex";i:4491;s:10:"BivFirefPh";i:4492;s:7:"TrabUpt";i:4493;s:5:"Maund";i:4494;s:10:"MalebPrere";i:4495;s:4:"Quar";i:4496;s:9:"MaStarbUn";i:4497;s:11:"PythUnVello";i:4498;s:4:"Domi";i:4499;s:8:"PoiSprew";i:4500;s:9:"MeagrUnwi";i:4501;s:8:"NapRevea";i:4502;s:5:"Unact";i:4503;s:15:"CorniJumboSubur";i:4504;s:6:"JudUnd";i:4505;s:10:"InteMisRow";i:4506;s:9:"OleanSyba";i:4507;s:10:"ElaRedlSup";i:4508;s:4:"Farm";i:4509;s:4:"Hipp";i:4510;s:9:"PangPedPh";i:4511;s:5:"RhiTi";i:4512;s:9:"HomisPaTs";i:4513;s:4:"Reas";i:4514;s:7:"AnaboDi";i:4515;s:9:"JarlxRaTh";i:4516;s:11:"GravSunsVer";i:4517;s:4:"Real";i:4518;s:7:"EfPulta";i:4519;s:9:"GunlxMaRe";i:4520;s:7:"PhaPhre";i:4521;s:5:"PlUnr";i:4522;s:5:"Skipp";i:4523;s:4:"Mura";i:4524;s:14:"SerosUnentWork";i:4525;s:3:"Ske";i:4526;s:11:"AttrFourLac";i:4527;s:8:"PreShrin";i:4528;s:7:"InsProf";i:4529;s:11:"ClSwottWebm";i:4530;s:11:"EndPhylaPle";i:4531;s:8:"MenyaThe";i:4532;s:8:"FissVaul";i:4533;s:7:"HastiOv";i:4534;s:3:"Met";i:4535;s:2:"Ta";i:4536;s:8:"HatbaPan";i:4537;s:6:"SyndTe";i:4538;s:5:"Hoppl";i:4539;s:8:"FerNecro";i:4540;s:4:"Rese";i:4541;s:8:"EnoHiUna";i:4542;s:7:"MaOverh";i:4543;s:11:"ItzNonsPecu";i:4544;s:5:"PhiTh";i:4545;s:3:"Jus";i:4546;s:9:"CoLevoTra";i:4547;s:4:"Sent";i:4548;s:7:"PeUrami";i:4549;s:7:"MuWoman";i:4550;s:4:"Ooph";i:4551;s:10:"InterRocUn";i:4552;s:7:"SaShadd";i:4553;s:14:"PaniTwiteUnirh";i:4554;s:7:"PaPhaRe";i:4555;s:4:"SaSy";i:4556;s:11:"ExarcTestTr";i:4557;s:3:"Vex";i:4558;s:3:"Yuz";i:4559;s:5:"UnWea";i:4560;s:11:"HylPanPapil";i:4561;s:9:"WineWulfe";i:4562;s:7:"PenSpar";i:4563;s:8:"WaveWove";i:4564;s:10:"DisFlStenc";i:4565;s:14:"DelegImpreMult";i:4566;s:7:"DiOpina";i:4567;s:5:"PenPr";i:4568;s:5:"Pinna";i:4569;s:14:"HieroIdylYoick";i:4570;s:6:"CougSt";i:4571;s:5:"RevSt";i:4572;s:10:"IndiMusPro";i:4573;s:4:"SkSu";i:4574;s:4:"Becl";i:4575;s:7:"IndiIns";i:4576;s:9:"NickeOuri";i:4577;s:9:"DeDiHundr";i:4578;s:9:"PerfeSeUn";i:4579;s:5:"MerTh";i:4580;s:7:"LanMann";i:4581;s:13:"FileOtheUndev";i:4582;s:9:"JupaSymme";i:4583;s:5:"Overb";i:4584;s:9:"RelocSnoo";i:4585;s:13:"MckayPetiQuee";i:4586;s:12:"SantaSophUnt";i:4587;s:10:"ConInvinSa";i:4588;s:4:"SyZo";i:4589;s:7:"DessSal";i:4590;s:5:"Septu";i:4591;s:10:"EchenOuTel";i:4592;s:8:"AsPeWith";i:4593;s:5:"Rubli";i:4594;s:11:"DonInspiMor";i:4595;s:12:"CypriDeHorto";i:4596;s:14:"ShravSoureSqua";i:4597;s:11:"IsothUncoYu";i:4598;s:5:"Softe";i:4599;s:8:"FiliTegx";i:4600;s:11:"PreRotteTan";i:4601;s:12:"FulgPitcVulc";i:4602;s:5:"Raphi";i:4603;s:4:"Silk";i:4604;s:7:"IntMilk";i:4605;s:7:"EthExor";i:4606;s:4:"InTe";i:4607;s:12:"HousePetrPre";i:4608;s:5:"Techn";i:4609;s:4:"Spir";i:4610;s:6:"MaThul";i:4611;s:4:"Phae";i:4612;s:9:"MythPipRe";i:4613;s:7:"PottlUn";i:4614;s:10:"FarOverStu";i:4615;s:12:"CranDhawxMis";i:4616;s:14:"ImprOverlTrach";i:4617;s:14:"DoohiGrimiNond";i:4618;s:7:"RedfiSe";i:4619;s:8:"EnteUpra";i:4620;s:13:"IrreKoalPicud";i:4621;s:9:"SaSenSiva";i:4622;s:4:"Quad";i:4623;s:5:"Japon";i:4624;s:9:"SeveSodSt";i:4625;s:13:"CyanhExtMicro";i:4626;s:6:"PavRig";i:4627;s:9:"NaphtProv";i:4628;s:12:"HypovIrreTet";i:4629;s:10:"GiloxLapsi";i:4630;s:5:"ToTri";i:4631;s:8:"GroggPre";i:4632;s:5:"Perco";i:4633;s:5:"Upclo";i:4634;s:8:"GenitLab";i:4635;s:6:"UnXylo";i:4636;s:5:"Devir";i:4637;s:8:"FleNassa";i:4638;s:4:"Salt";i:4639;s:13:"DeliManipUnen";i:4640;s:7:"ShinSho";i:4641;s:6:"IliaOu";i:4642;s:10:"EleImponPu";i:4643;s:9:"ProSuWild";i:4644;s:8:"AnNymRum";i:4645;s:14:"HexamInteUnrou";i:4646;s:11:"CopiHealTem";i:4647;s:14:"PieceSometUnre";i:4648;s:5:"HadRa";i:4649;s:10:"CamacJohan";i:4650;s:9:"OverZeugo";i:4651;s:3:"Sha";i:4652;s:6:"PrReZo";i:4653;s:10:"CoeffPhoUn";i:4654;s:9:"MycoObjec";i:4655;s:4:"MiTa";i:4656;s:9:"BiQuadrSe";i:4657;s:13:"OverPhantSanc";i:4658;s:4:"Poni";i:4659;s:7:"MyeTerr";i:4660;s:4:"Inri";i:4661;s:5:"SemZy";i:4662;s:8:"AuFruKer";i:4663;s:8:"GrPeVasu";i:4664;s:8:"NooQuant";i:4665;s:10:"PlodPtarTa";i:4666;s:8:"SubWistx";i:4667;s:14:"CoccSuperTundx";i:4668;s:5:"Spass";i:4669;s:8:"PuzzSapl";i:4670;s:4:"Snon";i:4671;s:8:"InstPega";i:4672;s:13:"HomoOrthRadia";i:4673;s:5:"Phyll";i:4674;s:8:"ChondRev";i:4675;s:9:"MoroPessi";i:4676;s:5:"Fores";i:4677;s:11:"NeOligPtole";i:4678;s:4:"Serf";i:4679;s:10:"InSymUnacu";i:4680;s:8:"BigemMis";i:4681;s:8:"OvicRaSc";i:4682;s:9:"IsmxUnder";i:4683;s:13:"HaeShoweTesta";i:4684;s:4:"Gram";i:4685;s:7:"TipsUnc";i:4686;s:6:"EuPerm";i:4687;s:9:"EyLoPhone";i:4688;s:7:"RepScat";i:4689;s:4:"MiSu";i:4690;s:11:"ChaucRoriUn";i:4691;s:5:"Slowh";i:4692;s:3:"Osp";i:4693;s:5:"Windo";i:4694;s:3:"Izt";i:4695;s:4:"Hoga";i:4696;s:7:"DrUndis";i:4697;s:9:"NucPyraTh";i:4698;s:5:"Unrum";i:4699;s:10:"BotchGrGue";i:4700;s:9:"LaPreaSem";i:4701;s:7:"OdaxSho";i:4702;s:4:"Outb";i:4703;s:9:"CreeUniun";i:4704;s:12:"JeaniMandSun";i:4705;s:6:"DeDigr";i:4706;s:10:"RotSciurSe";i:4707;s:7:"ConfiUn";i:4708;s:4:"Regi";i:4709;s:11:"DoSterThimb";i:4710;s:4:"Vamp";i:4711;s:3:"Hae";i:4712;s:6:"HyRete";i:4713;s:2:"Up";i:4714;s:9:"FumarRece";i:4715;s:3:"Loc";i:4716;s:5:"Unatt";i:4717;s:7:"OstrVea";i:4718;s:11:"InNettlPres";i:4719;s:7:"BuMilks";i:4720;s:4:"Bone";i:4721;s:10:"MyopPorpSy";i:4722;s:12:"EmmenMeUndis";i:4723;s:9:"DaggeDiGi";i:4724;s:7:"UnVarie";i:4725;s:5:"Perfe";i:4726;s:9:"AzoDaffTa";i:4727;s:10:"IneffWheal";i:4728;s:10:"DeconPinSe";i:4729;s:10:"TeUnmVolut";i:4730;s:10:"SaproTrach";i:4731;s:10:"PlaRhiUrba";i:4732;s:9:"StropSubv";i:4733;s:7:"IcNatur";i:4734;s:8:"FluxaRei";i:4735;s:6:"ChEcTo";i:4736;s:12:"QuadrUnovZoo";i:4737;s:12:"NecroThakuWa";i:4738;s:5:"SeSer";i:4739;s:2:"Go";i:4740;s:12:"FrogxPredSar";i:4741;s:12:"CaFrumpTortr";i:4742;s:7:"LePrakr";i:4743;s:6:"KurtWa";i:4744;s:13:"NymphOthVowma";i:4745;s:14:"InduMurgeTrabe";i:4746;s:10:"CytogUrodi";i:4747;s:12:"CarMarkwPrep";i:4748;s:9:"DyPalatRi";i:4749;s:15:"LazarMimusRemov";i:4750;s:9:"OfPterRef";i:4751;s:10:"PapawTherm";i:4752;s:9:"MoProTrea";i:4753;s:9:"PushfScut";i:4754;s:6:"NonSub";i:4755;s:9:"OcSubWido";i:4756;s:8:"UnchaUng";i:4757;s:9:"IcIntePer";i:4758;s:12:"PurgaReiRepl";i:4759;s:13:"CryLoxiaPutri";i:4760;s:12:"HomolMeaslTa";i:4761;s:4:"Pent";i:4762;s:7:"MisMoRu";i:4763;s:9:"ColorCyMa";i:4764;s:7:"HaemoTr";i:4765;s:5:"ParTa";i:4766;s:8:"MesOrxUn";i:4767;s:5:"Kavix";i:4768;s:3:"Pha";i:4769;s:13:"CaliCatsPenba";i:4770;s:10:"OverRaUnsm";i:4771;s:5:"Psych";i:4772;s:9:"SphaeUnfa";i:4773;s:4:"Wyli";i:4774;s:6:"SuSwan";i:4775;s:10:"DichoElkho";i:4776;s:6:"OsteSt";i:4777;s:5:"Overa";i:4778;s:10:"ReprTeUnpa";i:4779;s:9:"PaTonnZei";i:4780;s:4:"Ostr";i:4781;s:5:"Octob";i:4782;s:3:"Exo";i:4783;s:3:"Res";i:4784;s:3:"But";i:4785;s:8:"RhoTreVi";i:4786;s:6:"PrReda";i:4787;s:4:"IlUn";i:4788;s:7:"MeMonNo";i:4789;s:12:"FluMyrmePaho";i:4790;s:6:"ImprRe";i:4791;s:8:"EpichGla";i:4792;s:12:"RepaRolliUnr";i:4793;s:8:"MininOve";i:4794;s:7:"ProsaUn";i:4795;s:12:"HypNonapYoke";i:4796;s:7:"LalopSc";i:4797;s:8:"HoUneVis";i:4798;s:12:"DorKenmMicro";i:4799;s:15:"ProclResubZoops";i:4800;s:15:"SemirSerraSuper";i:4801;s:7:"SaceUna";i:4802;s:5:"Monoa";i:4803;s:5:"NoUns";i:4804;s:9:"DiEmTauto";i:4805;s:10:"BoehmSupUn";i:4806;s:10:"SuperUnres";i:4807;s:14:"BattDemonVitam";i:4808;s:9:"FilInxRap";i:4809;s:10:"PremiStStr";i:4810;s:10:"DesyGooPer";i:4811;s:4:"CoSo";i:4812;s:8:"HypeTric";i:4813;s:4:"Writ";i:4814;s:8:"PolSangu";i:4815;s:7:"PreUnig";i:4816;s:10:"RubServuTo";i:4817;s:5:"Sider";i:4818;s:9:"UnhauUnpe";i:4819;s:9:"HerniVine";i:4820;s:5:"MaMud";i:4821;s:5:"Nonan";i:4822;s:9:"CurraEnte";i:4823;s:8:"RaTenuTe";i:4824;s:2:"Ba";i:4825;s:5:"CuNeu";i:4826;s:8:"SartShat";i:4827;s:5:"Polyp";i:4828;s:7:"UnreUnv";i:4829;s:12:"InteKarakRhy";i:4830;s:3:"Ven";i:4831;s:3:"Tan";i:4832;s:8:"MeNecrNo";i:4833;s:12:"DioResoUnpre";i:4834;s:4:"Fell";i:4835;s:9:"ExGrafMai";i:4836;s:14:"SanifSuffrUnpr";i:4837;s:12:"GrimmKerUnma";i:4838;s:5:"Subsc";i:4839;s:6:"GilLaw";i:4840;s:10:"PerinPsilo";i:4841;s:8:"RenilRiv";i:4842;s:12:"FuThymoXenop";i:4843;s:7:"CassMen";i:4844;s:6:"LuMoro";i:4845;s:10:"HobPlasmSk";i:4846;s:5:"Rehum";i:4847;s:4:"Piaz";i:4848;s:5:"Defen";i:4849;s:4:"Prea";i:4850;s:9:"RedSaccSu";i:4851;s:9:"StrouUrox";i:4852;s:9:"MakObelSe";i:4853;s:5:"ReoTa";i:4854;s:7:"MiNonvi";i:4855;s:7:"AutZieg";i:4856;s:5:"Oscil";i:4857;s:9:"SuSuperUn";i:4858;s:4:"Slee";i:4859;s:5:"CaaJa";i:4860;s:8:"OvePosit";i:4861;s:7:"LianWel";i:4862;s:11:"FibOvoelTub";i:4863;s:4:"Crou";i:4864;s:5:"Wheed";i:4865;s:5:"Heter";i:4866;s:5:"MaPol";i:4867;s:8:"HundMaUr";i:4868;s:5:"ChGyr";i:4869;s:7:"CrFrPhr";i:4870;s:7:"KiPrima";i:4871;s:13:"DemibSinUnint";i:4872;s:5:"PuTyp";i:4873;s:8:"HooPhysi";i:4874;s:7:"CubitSt";i:4875;s:12:"PaletProWhos";i:4876;s:7:"RaTorme";i:4877;s:15:"LeiotSnortWally";i:4878;s:10:"LymPlaniSc";i:4879;s:5:"SuUni";i:4880;s:10:"BefrGlobRh";i:4881;s:11:"LibeNowaTro";i:4882;s:8:"ArEnerRh";i:4883;s:4:"Scar";i:4884;s:10:"CorpLumSha";i:4885;s:14:"HereaTraiTruan";i:4886;s:5:"Syrin";i:4887;s:6:"ChaInn";i:4888;s:3:"Use";i:4889;s:12:"EkahLusiUnfo";i:4890;s:10:"HelpiPhala";i:4891;s:10:"InterUnpro";i:4892;s:5:"Unred";i:4893;s:9:"ConfPePle";i:4894;s:9:"HoPreReam";i:4895;s:11:"PotTedTimer";i:4896;s:5:"Shiel";i:4897;s:10:"GoOverResc";i:4898;s:6:"NaTyle";i:4899;s:10:"FibGommeLa";i:4900;s:7:"PremoSt";i:4901;s:9:"HydroTale";i:4902;s:7:"PerThig";i:4903;s:8:"AcoelHal";i:4904;s:7:"BocNonr";i:4905;s:8:"PsySnaTe";i:4906;s:5:"Stint";i:4907;s:9:"SectSeeUn";i:4908;s:4:"FlMu";i:4909;s:4:"Reve";i:4910;s:7:"QuestUn";i:4911;s:11:"LibUnsVictu";i:4912;s:8:"SellaTra";i:4913;s:10:"CoMultParu";i:4914;s:3:"See";i:4915;s:7:"MaReTea";i:4916;s:10:"DucTowaTri";i:4917;s:7:"KeeProb";i:4918;s:10:"CitywPenta";i:4919;s:5:"Sprad";i:4920;s:5:"Fugit";i:4921;s:5:"TrVas";i:4922;s:12:"FoozlKlysSta";i:4923;s:10:"HomaMisTur";i:4924;s:8:"MensuVar";i:4925;s:13:"ForLinteNidic";i:4926;s:10:"SteToUpspl";i:4927;s:11:"JoshSuVisco";i:4928;s:5:"Semis";i:4929;s:7:"NovoxTr";i:4930;s:9:"GushxSchi";i:4931;s:10:"CitrPhthSu";i:4932;s:6:"ProUnc";i:4933;s:5:"PaVol";i:4934;s:3:"Lam";i:4935;s:7:"MatPlSo";i:4936;s:2:"Ru";i:4937;s:9:"PerRereTh";i:4938;s:11:"DokPetrSube";i:4939;s:6:"ProtSt";i:4940;s:15:"EidetParroScyph";i:4941;s:9:"OverRechi";i:4942;s:5:"Elsew";i:4943;s:12:"HomoSpecZizz";i:4944;s:5:"PrUnw";i:4945;s:8:"RattlRig";i:4946;s:5:"Helic";i:4947;s:9:"MicrSimSq";i:4948;s:8:"PreUnjil";i:4949;s:7:"InfrRep";i:4950;s:10:"GuasPalRub";i:4951;s:8:"OrgSubWa";i:4952;s:4:"Sept";i:4953;s:8:"BrEyeTri";i:4954;s:7:"NagOver";i:4955;s:12:"ButNonadSill";i:4956;s:11:"LavStympVea";i:4957;s:11:"DispoExRaph";i:4958;s:11:"FlexuMaleSa";i:4959;s:10:"IncraVihar";i:4960;s:6:"ScSkew";i:4961;s:4:"Irre";i:4962;s:8:"PeRecUna";i:4963;s:11:"CoPulviSchi";i:4964;s:10:"InconLyasx";i:4965;s:10:"MonodSparg";i:4966;s:6:"KickPi";i:4967;s:11:"TrabUnrWary";i:4968;s:14:"MuleResorUnsna";i:4969;s:13:"MetapNiggSupe";i:4970;s:9:"GraSterUv";i:4971;s:7:"LasquQu";i:4972;s:6:"UnaUnp";i:4973;s:10:"BipheConWr";i:4974;s:6:"CirUni";i:4975;s:3:"Pon";i:4976;s:5:"ExtRe";i:4977;s:5:"Inact";i:4978;s:7:"TrVendi";i:4979;s:5:"Santa";i:4980;s:9:"OsPhlPoro";i:4981;s:14:"HolidHydroTome";i:4982;s:4:"Incr";i:4983;s:3:"Hyp";i:4984;s:10:"CoOverrSug";i:4985;s:4:"Soap";i:4986;s:4:"Sole";i:4987;s:11:"PompProteSq";i:4988;s:6:"OutySu";i:4989;s:8:"FraFrPha";i:4990;s:7:"ParaPat";i:4991;s:12:"MarcOrobScot";i:4992;s:9:"PhysiTuto";i:4993;s:7:"AzygCit";i:4994;s:11:"UnharUnpUns";i:4995;s:3:"Neg";i:4996;s:6:"PrSupe";i:4997;s:9:"RosehUnso";i:4998;s:8:"NonRodom";i:4999;s:9:"IsothOpto";i:5000;s:8:"ForeFrac";i:5001;s:11:"ShoneTeZymo";i:5002;s:10:"BrKoloUnif";i:5003;s:12:"ImprPremiSup";i:5004;s:9:"EpeisGiIn";i:5005;s:8:"SnatcTes";i:5006;s:7:"GerTrav";i:5007;s:11:"ElSubsuUnsu";i:5008;s:4:"ApPr";i:5009;s:5:"Seapo";i:5010;s:7:"PoTetra";i:5011;s:11:"DeceFraOver";i:5012;s:4:"NoPa";i:5013;s:10:"BaraBiMoul";i:5014;s:8:"SchooZam";i:5015;s:3:"Omp";i:5016;s:12:"NuttiStVotar";i:5017;s:9:"DiSoarSto";i:5018;s:3:"Ges";i:5019;s:12:"OeninSubTher";i:5020;s:5:"DumJi";i:5021;s:9:"ClareDias";i:5022;s:9:"OutglPuUn";i:5023;s:5:"Salel";i:5024;s:10:"HyperOePro";i:5025;s:6:"BegNon";i:5026;s:6:"DemeLa";i:5027;s:11:"AuletCoNonr";i:5028;s:10:"JaNonRegul";i:5029;s:3:"Coc";i:5030;s:3:"Fun";i:5031;s:3:"Sti";i:5032;s:9:"MaineMile";i:5033;s:10:"CahokLikab";i:5034;s:6:"SponSt";i:5035;s:3:"Tai";i:5036;s:11:"MicrOverRef";i:5037;s:9:"SlumSphin";i:5038;s:8:"RivalSho";i:5039;s:13:"ContPredeTurb";i:5040;s:7:"TopUndi";i:5041;s:7:"PluStar";i:5042;s:2:"Gy";i:5043;s:13:"PantPiccSemid";i:5044;s:11:"NoTetraTumm";i:5045;s:7:"DaTheot";i:5046;s:13:"PiaTheorUnawa";i:5047;s:5:"Zilla";i:5048;s:12:"LikenProWhat";i:5049;s:5:"Demod";i:5050;s:2:"Ul";i:5051;s:5:"Sexlo";i:5052;s:4:"Unab";i:5053;s:5:"Tobac";i:5054;s:7:"UnVenti";i:5055;s:14:"HugeoSchuSucci";i:5056;s:4:"Octa";i:5057;s:12:"EcoFritxUncu";i:5058;s:7:"MadhvVa";i:5059;s:10:"UnbewUnive";i:5060;s:8:"PsTreUni";i:5061;s:5:"Overc";i:5062;s:7:"DarryLe";i:5063;s:8:"LateRuin";i:5064;s:8:"ParThYea";i:5065;s:5:"Reapp";i:5066;s:12:"NarProoeRhin";i:5067;s:10:"ThiUnconUn";i:5068;s:7:"OrtUnpe";i:5069;s:5:"Reliq";i:5070;s:4:"Uret";i:5071;s:6:"PsyTer";i:5072;s:3:"Gal";i:5073;s:5:"Rando";i:5074;s:6:"UnnWis";i:5075;s:12:"IndInteOutth";i:5076;s:3:"Fla";i:5077;s:7:"AntiSem";i:5078;s:7:"ConMeRh";i:5079;s:6:"SupTaf";i:5080;s:9:"MaPosScen";i:5081;s:3:"Kae";i:5082;s:8:"EquStrTi";i:5083;s:8:"GawmxOve";i:5084;s:10:"GinglUncap";i:5085;s:14:"DemodHakxSongb";i:5086;s:6:"IracSe";i:5087;s:4:"Squa";i:5088;s:7:"MicrUnp";i:5089;s:6:"LoSzla";i:5090;s:11:"JurorUnhaVe";i:5091;s:8:"ThioaZen";i:5092;s:12:"HerbaHeteIso";i:5093;s:8:"RhizUnve";i:5094;s:3:"Twe";i:5095;s:13:"InnSpraySunbe";i:5096;s:9:"KoLoRecop";i:5097;s:7:"LegaSta";i:5098;s:5:"Uncou";i:5099;s:9:"SemiSkiTh";i:5100;s:6:"BecoUn";i:5101;s:2:"Av";i:5102;s:8:"AssyCoRe";i:5103;s:7:"PrWauch";i:5104;s:8:"RhapStyl";i:5105;s:5:"Parap";i:5106;s:5:"Thrif";i:5107;s:10:"InteLophVi";i:5108;s:11:"GradIntLeas";i:5109;s:8:"AurRandy";i:5110;s:7:"HyOutdw";i:5111;s:2:"Sw";i:5112;s:4:"Pede";i:5113;s:12:"HomoeIllYame";i:5114;s:12:"NoninSclStro";i:5115;s:5:"Nonco";i:5116;s:7:"GeSpeck";i:5117;s:6:"StoUnp";i:5118;s:5:"ExpSk";i:5119;s:10:"DiEthnoPro";i:5120;s:11:"RhynUnconUn";i:5121;s:3:"Tha";i:5122;s:4:"Unst";i:5123;s:10:"MicroUncit";i:5124;s:7:"IrrTymp";i:5125;s:10:"SemUnimaWa";i:5126;s:10:"OverPontSn";i:5127;s:10:"AreLaugLau";i:5128;s:7:"DesTran";i:5129;s:7:"IrMinue";i:5130;s:11:"NoRenteVexx";i:5131;s:8:"GousPySc";i:5132;s:7:"NeOxylx";i:5133;s:5:"Unpen";i:5134;s:10:"KatexUnpat";i:5135;s:5:"PoTra";i:5136;s:4:"Nonf";i:5137;s:5:"Expos";i:5138;s:4:"Repe";i:5139;s:11:"DePisoUnide";i:5140;s:5:"EquZa";i:5141;s:8:"UnUnUnwi";i:5142;s:11:"OffcParaXyl";i:5143;s:10:"RaSemiSurv";i:5144;s:9:"PhacoVesi";i:5145;s:3:"Inv";i:5146;s:14:"EpitrOsteXanth";i:5147;s:5:"Wellb";i:5148;s:4:"CaSu";i:5149;s:11:"SincTrogUna";i:5150;s:7:"InParam";i:5151;s:13:"GabioOverbUnc";i:5152;s:6:"PlowRo";i:5153;s:13:"InsecJeremTel";i:5154;s:3:"Rat";i:5155;s:7:"IncPrYa";i:5156;s:8:"JarTwist";i:5157;s:6:"ElaPre";i:5158;s:9:"MiRopTurk";i:5159;s:11:"PrPremoSept";i:5160;s:13:"EssOverfWycli";i:5161;s:7:"TragiUn";i:5162;s:9:"InOuRatch";i:5163;s:5:"Bolar";i:5164;s:2:"Br";i:5165;s:12:"QuadrRentVol";i:5166;s:11:"OuphUnsleWh";i:5167;s:11:"GalvRachiSt";i:5168;s:12:"DeliFazPutri";i:5169;s:9:"HiHomeRet";i:5170;s:8:"SloSuper";i:5171;s:4:"Mime";i:5172;s:4:"Turb";i:5173;s:10:"MagiOrUnsu";i:5174;s:11:"BlaOrthOutg";i:5175;s:4:"Inal";i:5176;s:8:"SkeVolva";i:5177;s:12:"HexatMunicOn";i:5178;s:8:"OmnQuUnc";i:5179;s:3:"Qui";i:5180;s:8:"BiFacMan";i:5181;s:5:"Uncle";i:5182;s:10:"PalaSpUnco";i:5183;s:5:"Sabba";i:5184;s:5:"NonSe";i:5185;s:14:"JuggeTetryUnbo";i:5186;s:10:"KerniPalRe";i:5187;s:4:"Unma";i:5188;s:4:"Unmu";i:5189;s:5:"Incom";i:5190;s:7:"PinkSca";i:5191;s:11:"NaSletSperm";i:5192;s:3:"Mil";i:5193;s:10:"AnthrBuOve";i:5194;s:11:"NemaPerUnre";i:5195;s:10:"RevisSanto";i:5196;s:7:"GaGunRa";i:5197;s:13:"PhotoPreUnrec";i:5198;s:6:"ReZoop";i:5199;s:12:"DermaMaThorn";i:5200;s:4:"Scor";i:5201;s:3:"Tet";i:5202;s:10:"MesocSeric";i:5203;s:12:"OmenSomnaSub";i:5204;s:10:"FiordJochx";i:5205;s:9:"DissGaQua";i:5206;s:7:"TeTorme";i:5207;s:7:"LarrUnf";i:5208;s:6:"DiesTa";i:5209;s:7:"NonfStr";i:5210;s:7:"DisEpil";i:5211;s:5:"Smoke";i:5212;s:9:"JimSaShut";i:5213;s:11:"BandOxamPsy";i:5214;s:3:"Pul";i:5215;s:4:"Hype";i:5216;s:4:"Scon";i:5217;s:11:"FourpMilUnd";i:5218;s:12:"ChuThundTopo";i:5219;s:9:"ExMuRelev";i:5220;s:4:"Resp";i:5221;s:8:"CorMilks";i:5222;s:11:"HalfInPyrul";i:5223;s:12:"PhotoProsaUn";i:5224;s:4:"Maie";i:5225;s:7:"MaOchUb";i:5226;s:5:"SpWar";i:5227;s:8:"CalPhaTe";i:5228;s:7:"HePrYam";i:5229;s:4:"Yama";i:5230;s:4:"Trib";i:5231;s:6:"InPoUn";i:5232;s:5:"Sciss";i:5233;s:3:"Teg";i:5234;s:12:"CinctJesModi";i:5235;s:14:"FoghToxigWolfh";i:5236;s:10:"LowSuWould";i:5237;s:7:"SpittTe";i:5238;s:8:"KathUnde";i:5239;s:11:"EnanReRight";i:5240;s:4:"Unpe";i:5241;s:8:"ToppVaci";i:5242;s:11:"LymphSwirWh";i:5243;s:9:"MarkPseud";i:5244;s:10:"ElegaTlasc";i:5245;s:10:"NonmTheoWa";i:5246;s:8:"MenSynar";i:5247;s:3:"Pil";i:5248;s:5:"Thala";i:5249;s:11:"OlePreUnado";i:5250;s:2:"Ni";i:5251;s:4:"Unge";i:5252;s:8:"ProSolTo";i:5253;s:4:"Rece";i:5254;s:8:"IsoLitho";i:5255;s:3:"Col";i:5256;s:5:"Intra";i:5257;s:5:"RoUnl";i:5258;s:7:"HeronIn";i:5259;s:4:"Undu";i:5260;s:7:"LimSaVo";i:5261;s:10:"CenPentSto";i:5262;s:8:"IsraTher";i:5263;s:9:"DeperUpge";i:5264;s:9:"DispLeWoo";i:5265;s:4:"Puru";i:5266;s:3:"Rig";i:5267;s:9:"CoGolPred";i:5268;s:3:"Lyr";i:5269;s:9:"PoRediUnl";i:5270;s:12:"SnotTibiaUnt";i:5271;s:5:"Mirac";i:5272;s:4:"HyHy";i:5273;s:5:"Senti";i:5274;s:6:"RadeSe";i:5275;s:12:"LattSpheUnne";i:5276;s:5:"Sperm";i:5277;s:7:"PrStabl";i:5278;s:9:"KieyUnpam";i:5279;s:9:"NeThiocVi";i:5280;s:6:"MicrRe";i:5281;s:4:"Hint";i:5282;s:6:"OxycVi";i:5283;s:12:"TrimUndeUnex";i:5284;s:7:"EncTcUr";i:5285;s:4:"Tomb";i:5286;s:12:"RoritSxTartw";i:5287;s:9:"GametPret";i:5288;s:8:"CoNodPri";i:5289;s:13:"PhosPogrPseud";i:5290;s:6:"PalTab";i:5291;s:7:"DiscGla";i:5292;s:8:"GrHibVen";i:5293;s:14:"LeaveMassUncor";i:5294;s:9:"ReSmiUnin";i:5295;s:5:"Sacch";i:5296;s:7:"MisceSt";i:5297;s:14:"PrissRepoTauch";i:5298;s:9:"CornPeScr";i:5299;s:8:"HepSuper";i:5300;s:11:"HiroSeriUnd";i:5301;s:12:"SuTripaUnpin";i:5302;s:10:"IrPuerViti";i:5303;s:7:"SubVint";i:5304;s:12:"ScenaThWinna";i:5305;s:8:"FlorSupr";i:5306;s:8:"ProUndes";i:5307;s:12:"TranVagiVier";i:5308;s:7:"KerriQu";i:5309;s:12:"PrunQuinSter";i:5310;s:8:"HoUnWeit";i:5311;s:6:"UnUnte";i:5312;s:9:"OthinUnWe";i:5313;s:9:"PlanRouUn";i:5314;s:3:"Sex";i:5315;s:7:"KangPia";i:5316;s:5:"Uncau";i:5317;s:5:"GeJox";i:5318;s:7:"GamalPs";i:5319;s:4:"Mono";i:5320;s:7:"PaleoPa";i:5321;s:6:"KeysMa";i:5322;s:6:"SeUngo";i:5323;s:5:"StWre";i:5324;s:12:"CamelDatiFre";i:5325;s:5:"ParUn";i:5326;s:5:"Phoby";i:5327;s:12:"BrocNotorUnt";i:5328;s:5:"Repan";i:5329;s:7:"EczWeed";i:5330;s:9:"FatKornOv";i:5331;s:8:"PaReSyno";i:5332;s:10:"LeoraPolyc";i:5333;s:8:"PolSalmo";i:5334;s:11:"LeaLillSpea";i:5335;s:7:"PasPrim";i:5336;s:10:"GyPalTachy";i:5337;s:6:"ProTra";i:5338;s:10:"OdontReamy";i:5339;s:9:"PasixZoog";i:5340;s:5:"SleTo";i:5341;s:10:"IcedxPaSom";i:5342;s:3:"Nep";i:5343;s:7:"OofyShe";i:5344;s:3:"Bic";i:5345;s:11:"ExHoovRodne";i:5346;s:5:"IsQui";i:5347;s:10:"MolSkiesUn";i:5348;s:4:"Trop";i:5349;s:8:"EnterTch";i:5350;s:5:"OveSu";i:5351;s:5:"Uncre";i:5352;s:9:"InquPhren";i:5353;s:10:"StaSwUnrun";i:5354;s:9:"MoNontaSt";i:5355;s:12:"LaryUninUnme";i:5356;s:5:"Spiro";i:5357;s:8:"PhylTett";i:5358;s:4:"SaSe";i:5359;s:11:"MultiPerPsy";i:5360;s:8:"DaRemSub";i:5361;s:4:"Trip";i:5362;s:11:"HemLummoUna";i:5363;s:13:"DepigGinIntre";i:5364;s:5:"MiOog";i:5365;s:3:"Mic";i:5366;s:6:"ConExt";i:5367;s:10:"SaSturdSur";i:5368;s:8:"KinsmUle";i:5369;s:9:"ItenMilde";i:5370;s:5:"Urome";i:5371;s:10:"SolUnexVar";i:5372;s:9:"SulphTask";i:5373;s:9:"CasMetaVa";i:5374;s:10:"KyuxTriUnm";i:5375;s:13:"MyocePectuPor";i:5376;s:14:"LardiMiscuToda";i:5377;s:7:"UnfXant";i:5378;s:12:"PhaRackUnind";i:5379;s:3:"Mes";i:5380;s:8:"HepatMed";i:5381;s:8:"EarSomet";i:5382;s:6:"ExemUn";i:5383;s:8:"KeelPuQu";i:5384;s:5:"Raung";i:5385;s:13:"HyogQuadrSili";i:5386;s:8:"AmpLiPat";i:5387;s:8:"ImpTradi";i:5388;s:8:"SergeSph";i:5389;s:8:"CirUnblo";i:5390;s:9:"EvMortZin";i:5391;s:10:"MammaThioz";i:5392;s:5:"PeiPr";i:5393;s:13:"MicrNotoRadic";i:5394;s:10:"PhtPicumPs";i:5395;s:11:"GeocNonbOst";i:5396;s:8:"AvaJagVy";i:5397;s:5:"MaVen";i:5398;s:8:"ParaUnbl";i:5399;s:7:"OdonRen";i:5400;s:5:"Locus";i:5401;s:12:"DecoOrthRach";i:5402;s:9:"BlFolSyna";i:5403;s:6:"TeUnde";i:5404;s:8:"MouUntVe";i:5405;s:8:"CedarIso";i:5406;s:9:"StonTenct";i:5407;s:11:"ReinsRepSil";i:5408;s:8:"QuaSneak";i:5409;s:8:"CakeCont";i:5410;s:5:"Eucon";i:5411;s:4:"Tele";i:5412;s:4:"Nonc";i:5413;s:8:"AeMapSub";i:5414;s:10:"GigmaRebri";i:5415;s:4:"Wany";i:5416;s:5:"UnpUn";i:5417;s:5:"PorRe";i:5418;s:13:"HomotMoonPost";i:5419;s:14:"CommDecorImmed";i:5420;s:4:"LiLu";i:5421;s:11:"ConcHibZygo";i:5422;s:12:"InteSubeUnch";i:5423;s:5:"Bulim";i:5424;s:9:"RecoUnhoi";i:5425;s:11:"ProteSaloVe";i:5426;s:10:"MaMegPrair";i:5427;s:12:"OvereSeralTo";i:5428;s:4:"Saca";i:5429;s:11:"ChipLactaSl";i:5430;s:3:"Nos";i:5431;s:11:"SickSponWhi";i:5432;s:7:"MouVisi";i:5433;s:8:"OvQxUnsh";i:5434;s:12:"GlabeLePromy";i:5435;s:4:"SpUs";i:5436;s:5:"Uncan";i:5437;s:10:"SculpSuTru";i:5438;s:9:"OuStronWa";i:5439;s:9:"ChioMaste";i:5440;s:11:"DownlPrUnfe";i:5441;s:5:"Tinca";i:5442;s:10:"HidPrRoare";i:5443;s:6:"ChrEri";i:5444;s:9:"OlfaSalpi";i:5445;s:6:"PleoPr";i:5446;s:10:"UnmenWalle";i:5447;s:4:"Osop";i:5448;s:10:"SpaSubtUnt";i:5449;s:7:"EmitRou";i:5450;s:8:"GriPatSa";i:5451;s:5:"FoPhe";i:5452;s:12:"RectoSteUnre";i:5453;s:7:"NoUnequ";i:5454;s:7:"EntitRa";i:5455;s:4:"GaMa";i:5456;s:7:"PrStere";i:5457;s:14:"PalisRevieSeac";i:5458;s:10:"KeratScUnc";i:5459;s:5:"Gorra";i:5460;s:4:"Oste";i:5461;s:5:"Snipx";i:5462;s:5:"SwaTe";i:5463;s:13:"SikeUnutVolit";i:5464;s:8:"SeptTaff";i:5465;s:8:"MartPrRu";i:5466;s:12:"CeChopsRever";i:5467;s:12:"InProveVagin";i:5468;s:5:"InTri";i:5469;s:13:"OligaPalVesse";i:5470;s:6:"PhysPr";i:5471;s:14:"GlycoNeurVulva";i:5472;s:7:"BeDrScl";i:5473;s:7:"BraUnsa";i:5474;s:3:"Ory";i:5475;s:5:"Phary";i:5476;s:3:"Ini";i:5477;s:10:"MaResuWitt";i:5478;s:8:"DeMaddMa";i:5479;s:12:"HereSteZygom";i:5480;s:9:"FluHashUn";i:5481;s:5:"ChIni";i:5482;s:5:"SoUng";i:5483;s:6:"MaSpir";i:5484;s:10:"GawnxRaUns";i:5485;s:7:"ScToxic";i:5486;s:6:"LabPla";i:5487;s:4:"RoVo";i:5488;s:12:"CircuDicProt";i:5489;s:14:"MariPhiloThank";i:5490;s:3:"Wid";i:5491;s:12:"PlatTheokTou";i:5492;s:6:"ScTiVi";i:5493;s:5:"Clubm";i:5494;s:9:"PondUnvul";i:5495;s:5:"Salte";i:5496;s:11:"RotTubicUnh";i:5497;s:10:"TheoTitlUn";i:5498;s:8:"NatifRen";i:5499;s:4:"Zygn";i:5500;s:3:"Eut";i:5501;s:4:"Gene";i:5502;s:12:"ProReactSemi";i:5503;s:11:"CartPoPyrex";i:5504;s:5:"Shock";i:5505;s:4:"Zygo";i:5506;s:5:"MiTen";i:5507;s:7:"PhrTorr";i:5508;s:8:"GruSeTau";i:5509;s:7:"SmaSoUr";i:5510;s:12:"HoweMegOverw";i:5511;s:7:"NumPrUn";i:5512;s:7:"CyQuebr";i:5513;s:8:"IzzarSup";i:5514;s:8:"CoDihDor";i:5515;s:9:"MowRosSee";i:5516;s:14:"PerseSabeSalpi";i:5517;s:4:"Hear";i:5518;s:7:"StrTrep";i:5519;s:10:"NumerUnrWe";i:5520;s:8:"SleiUnch";i:5521;s:4:"NoPr";i:5522;s:4:"Buzz";i:5523;s:5:"ChMae";i:5524;s:5:"DiMer";i:5525;s:7:"RebetVe";i:5526;s:8:"RevTonsi";i:5527;s:11:"PilSquirSui";i:5528;s:8:"PaVoluYo";i:5529;s:10:"CalaCalcCr";i:5530;s:12:"HoverImpPerf";i:5531;s:7:"EmbHeZa";i:5532;s:5:"Paran";i:5533;s:8:"ElianWat";i:5534;s:13:"LumbMugfuUnsu";i:5535;s:9:"CitOvUnso";i:5536;s:11:"PreSunZerma";i:5537;s:4:"Plan";i:5538;s:10:"LaugMoPopu";i:5539;s:8:"PerSuppl";i:5540;s:11:"CapMeroThre";i:5541;s:7:"DerUndi";i:5542;s:9:"MuddiPuUn";i:5543;s:6:"PoSnai";i:5544;s:4:"Trun";i:5545;s:13:"ConfiExhauSou";i:5546;s:5:"Idiom";i:5547;s:5:"ChSpa";i:5548;s:12:"PheRectTousc";i:5549;s:11:"HemJokSelen";i:5550;s:10:"MilliPeatm";i:5551;s:8:"InacPseu";i:5552;s:7:"VegeXen";i:5553;s:6:"BarGre";i:5554;s:5:"InWhi";i:5555;s:9:"CounReove";i:5556;s:13:"PalmePlaSwang";i:5557;s:9:"DiPreiRee";i:5558;s:10:"CappaHyper";i:5559;s:9:"PatsTymUn";i:5560;s:4:"PrUn";i:5561;s:8:"BongHeat";i:5562;s:4:"Derm";i:5563;s:7:"HomoeSt";i:5564;s:13:"SignaSpeSquif";i:5565;s:9:"OlofPlaco";i:5566;s:7:"CaStoUn";i:5567;s:7:"GlMossi";i:5568;s:7:"TrUnclo";i:5569;s:14:"GlycPolycTapet";i:5570;s:15:"OchroParasVilit";i:5571;s:6:"BairDi";i:5572;s:9:"AssorJuSp";i:5573;s:10:"LooPhPluto";i:5574;s:8:"CorCroni";i:5575;s:7:"LaTerml";i:5576;s:11:"SponsTraTub";i:5577;s:6:"ConOst";i:5578;s:8:"SambuUnl";i:5579;s:8:"HormOrga";i:5580;s:7:"CytRecl";i:5581;s:6:"PySupe";i:5582;s:3:"Foo";i:5583;s:9:"GeoMatPho";i:5584;s:7:"CattPar";i:5585;s:11:"CacheHomUng";i:5586;s:13:"RefTrucuUnrip";i:5587;s:5:"HusWx";i:5588;s:13:"DumbSollyUnla";i:5589;s:7:"CoIroni";i:5590;s:11:"FiberLeniTc";i:5591;s:11:"ReanaUrduZe";i:5592;s:8:"TwiddVir";i:5593;s:5:"Possi";i:5594;s:5:"ArEus";i:5595;s:8:"NeogZono";i:5596;s:8:"ProdPsTe";i:5597;s:6:"AttSte";i:5598;s:4:"Epiz";i:5599;s:5:"Impot";i:5600;s:8:"PotWerec";i:5601;s:13:"DavidPsePseud";i:5602;s:5:"Semic";i:5603;s:4:"Hell";i:5604;s:8:"EncyRepe";i:5605;s:6:"TorUng";i:5606;s:12:"GlSauroUnlea";i:5607;s:7:"RecoThy";i:5608;s:7:"GiQuiSu";i:5609;s:5:"CrVel";i:5610;s:9:"PeSeUnthr";i:5611;s:12:"SchUnleaWitc";i:5612;s:14:"BiannGenitLevi";i:5613;s:11:"EndoMisNong";i:5614;s:3:"Slu";i:5615;s:11:"FruOordxSyn";i:5616;s:7:"RondVes";i:5617;s:4:"Upmo";i:5618;s:9:"MemorShou";i:5619;s:5:"Preli";i:5620;s:4:"Midd";i:5621;s:6:"PilTom";i:5622;s:5:"Wager";i:5623;s:12:"CollaCosSpor";i:5624;s:12:"SleuTaffyTet";i:5625;s:8:"NaceVagi";i:5626;s:5:"Ondag";i:5627;s:3:"Anc";i:5628;s:5:"Unafi";i:5629;s:5:"Parda";i:5630;s:10:"CrossIndig";i:5631;s:4:"ChCo";i:5632;s:6:"JaSeba";i:5633;s:6:"ForPen";i:5634;s:6:"PlunTr";i:5635;s:9:"OverTrUni";i:5636;s:7:"SupUnmo";i:5637;s:13:"SeeaSemilThos";i:5638;s:4:"Clad";i:5639;s:5:"MiSal";i:5640;s:5:"IntRe";i:5641;s:10:"CopGaSpeec";i:5642;s:7:"MaOaRiz";i:5643;s:9:"ElePoseUn";i:5644;s:5:"Trapf";i:5645;s:4:"Syll";i:5646;s:4:"Unof";i:5647;s:7:"SpSupTr";i:5648;s:7:"CrassOv";i:5649;s:9:"SupeTinke";i:5650;s:8:"NoneYamx";i:5651;s:4:"Jour";i:5652;s:7:"SummeSy";i:5653;s:7:"SwaTwin";i:5654;s:5:"Outse";i:5655;s:5:"Unlic";i:5656;s:12:"HospPlastQua";i:5657;s:7:"UnaYabb";i:5658;s:5:"Holly";i:5659;s:7:"PlaUnre";i:5660;s:11:"DisEngMonum";i:5661;s:3:"Opu";i:5662;s:10:"LogyxPrefe";i:5663;s:5:"OccPr";i:5664;s:7:"DrafPro";i:5665;s:8:"SeleTypi";i:5666;s:4:"SuUr";i:5667;s:4:"Sper";i:5668;s:7:"IrUnret";i:5669;s:7:"GoloSem";i:5670;s:12:"QuadrRevToly";i:5671;s:3:"Ura";i:5672;s:6:"ShopVa";i:5673;s:11:"ChucGentlVi";i:5674;s:8:"HaulmHic";i:5675;s:8:"OversOxe";i:5676;s:15:"IllumTritoUnwoo";i:5677;s:10:"ReThunUnpe";i:5678;s:3:"Phe";i:5679;s:5:"OdySe";i:5680;s:10:"DisIrrMans";i:5681;s:9:"DoEpYttri";i:5682;s:12:"OveOxazSpoof";i:5683;s:5:"Recom";i:5684;s:3:"Spy";i:5685;s:9:"ChiFooUns";i:5686;s:8:"NoSuUnov";i:5687;s:7:"LeTiUnr";i:5688;s:14:"ChromIncusSulp";i:5689;s:12:"EschaGastrRe";i:5690;s:12:"OpaquTechnUn";i:5691;s:9:"CuGasSnee";i:5692;s:9:"BrillShoo";i:5693;s:8:"NotUrete";i:5694;s:13:"SpiroSteYaupo";i:5695;s:8:"IntScyph";i:5696;s:5:"HoUnr";i:5697;s:5:"Silkg";i:5698;s:3:"Khw";i:5699;s:8:"MalSemip";i:5700;s:9:"ChrisNonp";i:5701;s:8:"PosPrefl";i:5702;s:6:"RuffSu";i:5703;s:3:"Sax";i:5704;s:8:"MulTermi";i:5705;s:10:"CooMonsUns";i:5706;s:7:"JiPaUnb";i:5707;s:5:"Thymo";i:5708;s:12:"CurstMimePos";i:5709;s:7:"UnrWood";i:5710;s:5:"Favon";i:5711;s:6:"LeSupp";i:5712;s:2:"Sq";i:5713;s:8:"FireInse";i:5714;s:10:"MonopRaffa";i:5715;s:3:"Pho";i:5716;s:14:"ConvNotwiSuper";i:5717;s:5:"OchSt";i:5718;s:10:"DeDialEogh";i:5719;s:9:"MisoUnwra";i:5720;s:8:"ProvUnad";i:5721;s:2:"Cy";i:5722;s:7:"MisenWo";i:5723;s:7:"FiPansc";i:5724;s:13:"AnomaHianPear";i:5725;s:5:"HySat";i:5726;s:4:"PrVa";i:5727;s:12:"QuicUnwWoneg";i:5728;s:9:"DefraHygr";i:5729;s:8:"CherHoMy";i:5730;s:9:"ChanVined";i:5731;s:7:"RaRavis";i:5732;s:15:"LiquiOverpSuper";i:5733;s:11:"CubPhiloUns";i:5734;s:7:"SleiSpa";i:5735;s:6:"SwaUnp";i:5736;s:10:"CoRefUnrig";i:5737;s:15:"SarciThundWantl";i:5738;s:10:"BubonIncOn";i:5739;s:7:"PlScrup";i:5740;s:7:"DeDiObv";i:5741;s:11:"RaisUnderVe";i:5742;s:12:"ConteHaIndec";i:5743;s:11:"MusPolycSul";i:5744;s:7:"SagaSub";i:5745;s:5:"OcUnr";i:5746;s:4:"Reca";i:5747;s:10:"ImmenWitti";i:5748;s:7:"PhraUnc";i:5749;s:5:"Prein";i:5750;s:10:"LievMetPre";i:5751;s:8:"OggSigil";i:5752;s:12:"ThTripyUnbro";i:5753;s:10:"KraTerebTr";i:5754;s:6:"InteSe";i:5755;s:10:"FoSheThora";i:5756;s:9:"ExiNoniUn";i:5757;s:12:"QuRoentSamsa";i:5758;s:6:"PhTyro";i:5759;s:10:"LazybParap";i:5760;s:8:"CaFissMo";i:5761;s:5:"Ataxi";i:5762;s:14:"JaileMolePrear";i:5763;s:10:"BarytSixha";i:5764;s:4:"SiSo";i:5765;s:12:"DiscoInterRa";i:5766;s:11:"HyposSyVici";i:5767;s:4:"InLu";i:5768;s:10:"FlPolysTot";i:5769;s:10:"PrSlaugThe";i:5770;s:9:"MegThTort";i:5771;s:7:"NodReSy";i:5772;s:9:"ConPoliVi";i:5773;s:5:"Snipp";i:5774;s:13:"CuticGunbSulf";i:5775;s:5:"Parag";i:5776;s:3:"Ili";i:5777;s:4:"Call";i:5778;s:14:"MarmSairvSuper";i:5779;s:8:"MollSeam";i:5780;s:10:"InvalSesba";i:5781;s:5:"EqInd";i:5782;s:6:"CoPodo";i:5783;s:7:"AntiWis";i:5784;s:14:"GestiHomePetra";i:5785;s:3:"Phy";i:5786;s:7:"CaimiIs";i:5787;s:8:"OstUrson";i:5788;s:6:"ReSpha";i:5789;s:10:"DiDisParas";i:5790;s:10:"OtalOvSpha";i:5791;s:7:"SaSpavi";i:5792;s:8:"UndwiUns";i:5793;s:5:"Linse";i:5794;s:7:"StUnadu";i:5795;s:4:"SaWi";i:5796;s:13:"InterPsyTempt";i:5797;s:10:"CrosDiSecl";i:5798;s:6:"CyLeMu";i:5799;s:5:"CryNu";i:5800;s:5:"Yiddi";i:5801;s:5:"StoUn";i:5802;s:9:"EggeOutwa";i:5803;s:10:"MoravVisuo";i:5804;s:5:"Ghurr";i:5805;s:14:"CompHolidToxic";i:5806;s:7:"MyoSoci";i:5807;s:9:"LiPosthUn";i:5808;s:6:"SpUnto";i:5809;s:3:"Sep";i:5810;s:11:"FumatIndiTr";i:5811;s:4:"Nege";i:5812;s:10:"PeResuScra";i:5813;s:12:"ConvLinParas";i:5814;s:6:"HooPal";i:5815;s:6:"SporSt";i:5816;s:11:"DyeForsUnem";i:5817;s:10:"PredrPrStu";i:5818;s:11:"DivHegLutem";i:5819;s:10:"ScornUnhig";i:5820;s:9:"BeneTubWa";i:5821;s:5:"SanSt";i:5822;s:7:"LaSaUnc";i:5823;s:7:"MisaPol";i:5824;s:14:"MungPenetRudol";i:5825;s:8:"NonclSur";i:5826;s:10:"HydLigPres";i:5827;s:6:"QuirSa";i:5828;s:7:"SquirTa";i:5829;s:8:"EnExpPro";i:5830;s:4:"HeUn";i:5831;s:9:"UnstUnthr";i:5832;s:5:"LiZoo";i:5833;s:5:"Somet";i:5834;s:7:"UnZucch";i:5835;s:6:"InSpUn";i:5836;s:12:"GawNeptOutbo";i:5837;s:4:"Weed";i:5838;s:9:"BoPolySig";i:5839;s:8:"HatcNucl";i:5840;s:6:"HypInn";i:5841;s:6:"UlUngr";i:5842;s:10:"MesScUnatt";i:5843;s:5:"UncYo";i:5844;s:12:"BimilFasciMe";i:5845;s:8:"PubiTass";i:5846;s:15:"GaragLouchNonlo";i:5847;s:8:"PhilhTah";i:5848;s:6:"SornWa";i:5849;s:10:"TamilUnhor";i:5850;s:3:"Bra";i:5851;s:12:"HeteParPreco";i:5852;s:15:"DazemTotanUncri";i:5853;s:9:"FahlMilly";i:5854;s:5:"ImOpp";i:5855;s:5:"Sahar";i:5856;s:14:"MelopReceTrira";i:5857;s:5:"Mimly";i:5858;s:13:"LunaSupraUnil";i:5859;s:12:"ResiVerWhipp";i:5860;s:10:"NucTolUnou";i:5861;s:6:"SuTele";i:5862;s:6:"PinShu";i:5863;s:8:"HyphYuzl";i:5864;s:7:"SplinUn";i:5865;s:5:"Stack";i:5866;s:5:"Odont";i:5867;s:11:"MoParamPreo";i:5868;s:7:"LiMonst";i:5869;s:6:"StroUn";i:5870;s:4:"Tica";i:5871;s:7:"CouElec";i:5872;s:8:"EnrKusim";i:5873;s:3:"Sic";i:5874;s:9:"IncoUnali";i:5875;s:11:"RedeSelexUp";i:5876;s:5:"Parat";i:5877;s:9:"NonacWhif";i:5878;s:12:"RoSangaWistl";i:5879;s:8:"RatTranq";i:5880;s:9:"JentLogUp";i:5881;s:3:"Mus";i:5882;s:14:"ServaSparkSper";i:5883;s:5:"Sylvi";i:5884;s:10:"SnackSurge";i:5885;s:11:"AztecHomPou";i:5886;s:4:"PrPs";i:5887;s:4:"Unex";i:5888;s:7:"CeorGon";i:5889;s:8:"DisesWin";i:5890;s:8:"EglesIsl";i:5891;s:5:"Remix";i:5892;s:5:"BuRes";i:5893;s:11:"HemogOvSupe";i:5894;s:14:"MeasMenacSpora";i:5895;s:8:"OsphyTal";i:5896;s:4:"Blan";i:5897;s:6:"EfflHo";i:5898;s:9:"PrSpeceUn";i:5899;s:6:"IscUnt";i:5900;s:4:"InTa";i:5901;s:9:"PrehuTene";i:5902;s:9:"MatRuShak";i:5903;s:11:"NervePetRed";i:5904;s:8:"EpiMoOut";i:5905;s:7:"CuOsUnp";i:5906;s:6:"InfiSu";i:5907;s:8:"DeMnSkid";i:5908;s:5:"Secti";i:5909;s:5:"CurEc";i:5910;s:11:"FlankMiShri";i:5911;s:12:"ReeliRuRusty";i:5912;s:7:"PluviTu";i:5913;s:12:"MonoNonasStr";i:5914;s:5:"PreTo";i:5915;s:7:"StuUnif";i:5916;s:5:"Unord";i:5917;s:7:"DichrNa";i:5918;s:9:"NimmProlo";i:5919;s:3:"Jol";i:5920;s:9:"HadScoSub";i:5921;s:5:"Stoma";i:5922;s:8:"PrSignTe";i:5923;s:10:"RebTubeVul";i:5924;s:8:"DiNoWood";i:5925;s:5:"OrSwa";i:5926;s:9:"GaIntelOr";i:5927;s:5:"Scuti";i:5928;s:4:"Gent";i:5929;s:7:"NoniPte";i:5930;s:6:"CaHeat";i:5931;s:10:"PhalaWarve";i:5932;s:8:"PrPygUra";i:5933;s:5:"Nutcr";i:5934;s:11:"LepidMonUnr";i:5935;s:7:"PalPatr";i:5936;s:9:"ReiSixVes";i:5937;s:6:"MesUnf";i:5938;s:3:"Tsu";i:5939;s:5:"Ununi";i:5940;s:6:"ReSpie";i:5941;s:4:"SuUn";i:5942;s:9:"OrdinRabb";i:5943;s:11:"MiNapuPlebx";i:5944;s:8:"PodogSta";i:5945;s:9:"KuskuNigg";i:5946;s:4:"Capr";i:5947;s:5:"Chlam";i:5948;s:5:"CoSch";i:5949;s:13:"IndisMyseTubx";i:5950;s:10:"MazRhiTran";i:5951;s:4:"Prer";i:5952;s:7:"DoMecRu";i:5953;s:11:"RamaTopeZen";i:5954;s:13:"OverSpicUnmel";i:5955;s:5:"Prata";i:5956;s:6:"NebaSt";i:5957;s:12:"BudmaCritDio";i:5958;s:11:"GyroPaleoUn";i:5959;s:8:"EsMaRede";i:5960;s:8:"SizTetra";i:5961;s:7:"AsStaTa";i:5962;s:12:"NurseSmyrUna";i:5963;s:9:"CoUnmWrea";i:5964;s:10:"SisUnaltUn";i:5965;s:11:"GigarGranPr";i:5966;s:9:"MacabMyop";i:5967;s:7:"CrediGu";i:5968;s:10:"OncRinSlug";i:5969;s:4:"Rose";i:5970;s:5:"MisMo";i:5971;s:11:"ShoeSlUnmem";i:5972;s:10:"PasquPreci";i:5973;s:9:"DedicInIr";i:5974;s:12:"GlovePyjamSa";i:5975;s:9:"CockThroa";i:5976;s:3:"Jau";i:5977;s:7:"LibTrip";i:5978;s:13:"MegaRaceTobac";i:5979;s:5:"Atwix";i:5980;s:12:"PlatyReUnben";i:5981;s:11:"MoPropSulph";i:5982;s:5:"Twere";i:5983;s:4:"Timi";i:5984;s:8:"CoOophUn";i:5985;s:7:"KentiRe";i:5986;s:11:"AgrolKaPter";i:5987;s:5:"Xeres";i:5988;s:14:"ChoriLochiMaga";i:5989;s:12:"FucoxGaoNutt";i:5990;s:9:"PrStrTerp";i:5991;s:7:"ItSciss";i:5992;s:11:"GassiNatUnb";i:5993;s:4:"Unri";i:5994;s:8:"PrSarSir";i:5995;s:4:"HeUs";i:5996;s:11:"CyMatamStan";i:5997;s:9:"MarcSuTub";i:5998;s:7:"UnrVall";i:5999;s:7:"PentaPh";i:6000;s:5:"Knobs";i:6001;s:5:"Poste";i:6002;s:4:"InPr";i:6003;s:12:"DoldrRecUros";i:6004;s:12:"OctodParkeTh";i:6005;s:6:"PlaThe";i:6006;s:2:"En";i:6007;s:13:"PseudReheSpri";i:6008;s:8:"NitPraTo";i:6009;s:6:"PrTetr";i:6010;s:8:"DiscKary";i:6011;s:5:"Rattl";i:6012;s:6:"SpicSu";i:6013;s:7:"NonfTet";i:6014;s:7:"UnUteri";i:6015;s:14:"LesbiNawxPrere";i:6016;s:4:"Thra";i:6017;s:7:"MercThu";i:6018;s:6:"PiSini";i:6019;s:3:"Ten";i:6020;s:10:"ExecuXylos";i:6021;s:6:"IaKoUn";i:6022;s:4:"Coin";i:6023;s:6:"HagsLo";i:6024;s:8:"ProsScha";i:6025;s:5:"Weepx";i:6026;s:7:"MirdaPo";i:6027;s:4:"Pegm";i:6028;s:8:"DemForgo";i:6029;s:5:"Meado";i:6030;s:10:"ArchLimbSk";i:6031;s:8:"NegOrSem";i:6032;s:10:"KonRusseUn";i:6033;s:4:"PrSm";i:6034;s:7:"MuRenUn";i:6035;s:3:"Ras";i:6036;s:10:"InterKaTep";i:6037;s:13:"RedoToseTropi";i:6038;s:6:"LibeTa";i:6039;s:8:"ChQuinRe";i:6040;s:4:"Zoop";i:6041;s:10:"MorSpanUne";i:6042;s:10:"IndicRoUnr";i:6043;s:5:"SarTi";i:6044;s:3:"Mou";i:6045;s:7:"CaSatra";i:6046;s:12:"ForamMoaMura";i:6047;s:8:"MaMoSulp";i:6048;s:13:"PiscaTonnVisi";i:6049;s:14:"IndusMuskPodic";i:6050;s:9:"ResprSeni";i:6051;s:9:"AftHypeOa";i:6052;s:9:"DeDigEuco";i:6053;s:8:"TriUndes";i:6054;s:7:"MoskSik";i:6055;s:9:"FifthLuSu";i:6056;s:5:"Proli";i:6057;s:9:"PolyeQuin";i:6058;s:9:"InIsUnmat";i:6059;s:7:"CrPondo";i:6060;s:9:"LantStrid";i:6061;s:13:"HaveSubpTechn";i:6062;s:8:"MaMusaSc";i:6063;s:7:"MoSeSup";i:6064;s:5:"Timef";i:6065;s:9:"LoPyvuSyn";i:6066;s:3:"Spl";i:6067;s:9:"MouReseUm";i:6068;s:4:"ToWa";i:6069;s:9:"MonocSept";i:6070;s:13:"KanepTransTri";i:6071;s:4:"Enhy";i:6072;s:13:"SpareSubTaran";i:6073;s:8:"PagaSupe";i:6074;s:13:"MaesNaturSero";i:6075;s:6:"ClDere";i:6076;s:6:"BuruSn";i:6077;s:5:"PerPs";i:6078;s:7:"PanmUpw";i:6079;s:6:"GreeRe";i:6080;s:7:"ErUnpro";i:6081;s:7:"CycliHe";i:6082;s:2:"Eu";i:6083;s:7:"HoPandr";i:6084;s:11:"MesNonOscil";i:6085;s:10:"ExtPoTetra";i:6086;s:13:"MastoMonoPyro";i:6087;s:6:"HiNoSp";i:6088;s:10:"GlPraUnhed";i:6089;s:6:"RewTho";i:6090;s:7:"UnaWoor";i:6091;s:14:"ElfhPicknUntes";i:6092;s:4:"Tinw";i:6093;s:6:"CrucEx";i:6094;s:8:"FloInoge";i:6095;s:11:"HadexPremTh";i:6096;s:3:"Mys";i:6097;s:6:"TiUnau";i:6098;s:12:"EurNonsuNonv";i:6099;s:5:"Woonx";i:6100;s:5:"DiUnr";i:6101;s:7:"PoRaUlt";i:6102;s:5:"Ethno";i:6103;s:4:"Heat";i:6104;s:6:"ShacSm";i:6105;s:11:"NonPhantSyn";i:6106;s:4:"Quin";i:6107;s:14:"FewnKieseOptio";i:6108;s:11:"ClypImpaUns";i:6109;s:9:"ParalUnbe";i:6110;s:7:"ParSing";i:6111;s:10:"CleSexuaUn";i:6112;s:10:"SacchSolid";i:6113;s:9:"HorsIncor";i:6114;s:13:"FoodlHusNight";i:6115;s:8:"MonSubUn";i:6116;s:5:"Inkho";i:6117;s:4:"Panz";i:6118;s:4:"NoPe";i:6119;s:7:"PonTatu";i:6120;s:8:"CotVangl";i:6121;s:4:"Xero";i:6122;s:5:"CurPi";i:6123;s:3:"Str";i:6124;s:4:"Sacr";i:6125;s:4:"Quai";i:6126;s:5:"Uncoa";i:6127;s:9:"ChitProsu";i:6128;s:6:"PenuUn";i:6129;s:4:"BrLa";i:6130;s:7:"EnterJu";i:6131;s:11:"CoOutsTroph";i:6132;s:11:"ComNondRele";i:6133;s:12:"PreleScenUnp";i:6134;s:12:"ExHogwaPerip";i:6135;s:10:"UndWaZibet";i:6136;s:12:"GinkxQuinqRa";i:6137;s:6:"EmmVer";i:6138;s:10:"PalReTeasa";i:6139;s:12:"MePrepoSurge";i:6140;s:12:"HomSommUncti";i:6141;s:9:"TheoTinin";i:6142;s:12:"IrrMonoPetio";i:6143;s:13:"MultOxysuThes";i:6144;s:4:"Natc";i:6145;s:11:"DisiEnStoma";i:6146;s:11:"HegarPolTan";i:6147;s:5:"StTra";i:6148;s:5:"Unexc";i:6149;s:12:"AweSyllaUnpr";i:6150;s:6:"CaReci";i:6151;s:12:"SatirUnmiVac";i:6152;s:5:"SeUnc";i:6153;s:8:"ProtTint";i:6154;s:5:"Waikl";i:6155;s:10:"MaMegasSpr";i:6156;s:3:"Gam";i:6157;s:7:"CoVagar";i:6158;s:7:"ToppUre";i:6159;s:7:"ArcImpa";i:6160;s:15:"MokoxOcherSubar";i:6161;s:10:"DeeEtiolTh";i:6162;s:10:"TrottUnUnf";i:6163;s:5:"Bronz";i:6164;s:4:"Tutr";i:6165;s:6:"PisaRi";i:6166;s:6:"PuSuTe";i:6167;s:5:"PoSpi";i:6168;s:7:"LiReefe";i:6169;s:9:"QuadWimex";i:6170;s:11:"GoniSizalYa";i:6171;s:3:"Ins";i:6172;s:15:"GinglSemigSerpe";i:6173;s:13:"GadinSemUnfei";i:6174;s:7:"TinUnne";i:6175;s:5:"Grapt";i:6176;s:7:"PhScaTh";i:6177;s:5:"Preaf";i:6178;s:3:"Kap";i:6179;s:10:"MaywMohaWe";i:6180;s:4:"TaTe";i:6181;s:5:"FiTig";i:6182;s:3:"Jer";i:6183;s:11:"OdobPawneSp";i:6184;s:15:"NonbaTorchUnext";i:6185;s:4:"SlSt";i:6186;s:5:"Weelx";i:6187;s:5:"ExTem";i:6188;s:4:"EnUn";i:6189;s:12:"HurlyPythoTr";i:6190;s:4:"Glos";i:6191;s:13:"SpisuTriceUne";i:6192;s:11:"EpacImpoPro";i:6193;s:12:"MetOakeOuttr";i:6194;s:9:"OrScWoodn";i:6195;s:7:"HoMazum";i:6196;s:9:"MesenSemi";i:6197;s:9:"RegulSand";i:6198;s:5:"Stasi";i:6199;s:6:"UnZoog";i:6200;s:4:"Paro";i:6201;s:11:"SinapStaSup";i:6202;s:9:"TaeTeTheo";i:6203;s:10:"KrapiProrh";i:6204;s:9:"GuarMarxi";i:6205;s:13:"SybarTanUnwed";i:6206;s:4:"JaUn";i:6207;s:6:"SemiSo";i:6208;s:8:"UniUnind";i:6209;s:12:"DulTriuTubul";i:6210;s:5:"Engra";i:6211;s:12:"UnpreZygonZy";i:6212;s:5:"Recko";i:6213;s:9:"GrinnInUn";i:6214;s:7:"EleGeLi";i:6215;s:12:"MolSyconUnst";i:6216;s:4:"Scum";i:6217;s:10:"FlaFostUnd";i:6218;s:5:"Peris";i:6219;s:8:"CophLava";i:6220;s:12:"SmetSpriTran";i:6221;s:8:"NephUnwa";i:6222;s:7:"QueetSc";i:6223;s:10:"FeifGueTab";i:6224;s:10:"OliveUnciv";i:6225;s:10:"BuilCompTa";i:6226;s:7:"RecliRe";i:6227;s:4:"OrUn";i:6228;s:7:"ExRadSa";i:6229;s:3:"Jux";i:6230;s:12:"ShikiSupeUns";i:6231;s:4:"DeUn";i:6232;s:9:"MiNuReine";i:6233;s:8:"TheTrime";i:6234;s:6:"SemWot";i:6235;s:3:"Pyr";i:6236;s:9:"NerthOuts";i:6237;s:6:"DyscGl";i:6238;s:3:"Mag";i:6239;s:8:"LucraPer";i:6240;s:7:"ParUlVi";i:6241;s:3:"Ken";i:6242;s:8:"ScrUnsca";i:6243;s:10:"PewmaToUnp";i:6244;s:13:"MangNeglPolys";i:6245;s:9:"FoInhiSor";i:6246;s:11:"DrunSporTir";i:6247;s:4:"HaLo";i:6248;s:11:"PharyQuUnch";i:6249;s:12:"MiamMilPhary";i:6250;s:4:"Stuc";i:6251;s:10:"DomElectPs";i:6252;s:4:"Kumn";i:6253;s:9:"PreteVaga";i:6254;s:4:"MiPr";i:6255;s:9:"GimGrooPr";i:6256;s:5:"ForSc";i:6257;s:3:"Opa";i:6258;s:5:"Hirud";i:6259;s:6:"OverPo";i:6260;s:9:"MarNomaSt";i:6261;s:4:"Saxo";i:6262;s:6:"HyLept";i:6263;s:7:"MyxaOcy";i:6264;s:3:"Iso";i:6265;s:9:"NonchPeri";i:6266;s:9:"ElytrTric";i:6267;s:6:"MePurb";i:6268;s:6:"ChaOut";i:6269;s:8:"NonTrave";i:6270;s:8:"InsiPent";i:6271;s:6:"PrTern";i:6272;s:4:"Subg";i:6273;s:7:"ImprUnv";i:6274;s:4:"Unha";i:6275;s:8:"ProgUngl";i:6276;s:12:"LaMammiToros";i:6277;s:5:"SeaSu";i:6278;s:5:"Vigon";i:6279;s:9:"AriIdiLog";i:6280;s:8:"NovPsyTh";i:6281;s:8:"KrausTro";i:6282;s:5:"Undup";i:6283;s:10:"ContrMysTe";i:6284;s:4:"Harl";i:6285;s:3:"Upk";i:6286;s:6:"InfThi";i:6287;s:7:"GriMyct";i:6288;s:9:"BleaCoccy";i:6289;s:12:"NeuroSoldiSw";i:6290;s:8:"HeKathRa";i:6291;s:10:"NackeNitro";i:6292;s:14:"CapacPestPsych";i:6293;s:8:"OutlaUnl";i:6294;s:7:"PhrenVe";i:6295;s:10:"PluQuicYum";i:6296;s:5:"Tesse";i:6297;s:5:"Poiki";i:6298;s:6:"AppaWr";i:6299;s:8:"CattChPo";i:6300;s:5:"Unsuc";i:6301;s:10:"JotnOtoUns";i:6302;s:10:"ArgNubiPre";i:6303;s:9:"GastLagga";i:6304;s:3:"Yom";i:6305;s:8:"SpSteVet";i:6306;s:12:"ColPleoSheri";i:6307;s:10:"FrankNeSes";i:6308;s:3:"Kon";i:6309;s:14:"CraigMayorOpul";i:6310;s:8:"UnwUnVer";i:6311;s:7:"NonpeRe";i:6312;s:3:"Ort";i:6313;s:7:"HirsQui";i:6314;s:7:"HurOver";i:6315;s:5:"PrSlo";i:6316;s:5:"Cerem";i:6317;s:10:"PleaToXero";i:6318;s:9:"ProUnemUn";i:6319;s:4:"UnWi";i:6320;s:5:"PrePr";i:6321;s:11:"MigSerUnref";i:6322;s:5:"Progu";i:6323;s:8:"HaMultSe";i:6324;s:6:"RiRoll";i:6325;s:5:"Unthi";i:6326;s:14:"ExtolMisnSperm";i:6327;s:7:"PreTimb";i:6328;s:7:"RickeSh";i:6329;s:10:"PericPyrrh";i:6330;s:7:"TiUnder";i:6331;s:13:"HeaVocalWhewt";i:6332;s:4:"Misw";i:6333;s:3:"Bol";i:6334;s:14:"PlatiPunnSphac";i:6335;s:3:"Vag";i:6336;s:14:"SainxSlowSteep";i:6337;s:10:"MacrPaguTr";i:6338;s:4:"Scut";i:6339;s:8:"ShooUnin";i:6340;s:10:"OenanSayax";i:6341;s:14:"EpigLegioTiara";i:6342;s:11:"MenogStopVa";i:6343;s:6:"PlUniq";i:6344;s:10:"EmOverZepp";i:6345;s:11:"SeTelenWork";i:6346;s:6:"MoReco";i:6347;s:4:"Reda";i:6348;s:8:"PelRucTh";i:6349;s:10:"UngriWitho";i:6350;s:4:"MuOr";i:6351;s:14:"PrincThiopVivi";i:6352;s:6:"NonaPh";i:6353;s:5:"NeZoo";i:6354;s:8:"PuffySup";i:6355;s:4:"Bair";i:6356;s:14:"ManwaSinaiUroc";i:6357;s:6:"AntLux";i:6358;s:9:"FungKokTo";i:6359;s:10:"AquipPhyto";i:6360;s:8:"OrtTuVie";i:6361;s:11:"CaloGudgPie";i:6362;s:4:"Palt";i:6363;s:7:"HubXeno";i:6364;s:9:"KnobSyTra";i:6365;s:10:"MirzaObiUn";i:6366;s:10:"CongeStali";i:6367;s:5:"Pomox";i:6368;s:3:"Vol";i:6369;s:7:"InepMel";i:6370;s:10:"ResiRhynSe";i:6371;s:8:"PrTychUn";i:6372;s:9:"DactyTrut";i:6373;s:13:"BradaCarpHera";i:6374;s:9:"OctRebiSu";i:6375;s:10:"ShTruerVer";i:6376;s:9:"HeMeTellu";i:6377;s:12:"FairyGraveSt";i:6378;s:8:"PergTalp";i:6379;s:3:"Sty";i:6380;s:6:"IntSol";i:6381;s:9:"SpeciToUn";i:6382;s:8:"NutaSkir";i:6383;s:5:"Rhomb";i:6384;s:9:"SharUnsni";i:6385;s:5:"DiInt";i:6386;s:5:"Splen";i:6387;s:6:"OraTer";i:6388;s:5:"Dahli";i:6389;s:8:"ClaOliSo";i:6390;s:8:"PeSangSa";i:6391;s:4:"Tors";i:6392;s:9:"HypopPari";i:6393;s:14:"ThiosTonoUnise";i:6394;s:7:"CatPara";i:6395;s:4:"Sumb";i:6396;s:7:"OsPreSw";i:6397;s:8:"HattUrsi";i:6398;s:6:"DiNotc";i:6399;s:5:"Disso";i:6400;s:8:"ReRenaWh";i:6401;s:12:"MonocSeroWha";i:6402;s:5:"Toade";i:6403;s:4:"CnMo";i:6404;s:8:"CardiSca";i:6405;s:4:"Corn";i:6406;s:14:"ExtrPotorPreve";i:6407;s:13:"HomalRepreSub";i:6408;s:4:"Vers";i:6409;s:5:"Unbla";i:6410;s:12:"PalaPneStodg";i:6411;s:5:"PyTri";i:6412;s:10:"DeyshPlPul";i:6413;s:9:"IvorUndis";i:6414;s:7:"PellTid";i:6415;s:5:"Thatx";i:6416;s:8:"DecaEleu";i:6417;s:4:"Maea";i:6418;s:9:"AuObUnadj";i:6419;s:11:"EntInoJumps";i:6420;s:12:"TreUnnotUnsw";i:6421;s:9:"AtechGeOv";i:6422;s:9:"LecUndeUn";i:6423;s:9:"DimnStrin";i:6424;s:13:"MonopNonsuRep";i:6425;s:3:"Ver";i:6426;s:6:"PesTho";i:6427;s:11:"InPredSemis";i:6428;s:7:"PoPrint";i:6429;s:4:"Coch";i:6430;s:4:"Mopp";i:6431;s:3:"Dra";i:6432;s:7:"DrygoPr";i:6433;s:9:"SulciUnco";i:6434;s:8:"SaboUnUn";i:6435;s:10:"TanqTreUns";i:6436;s:7:"UnjVesp";i:6437;s:4:"Pror";i:6438;s:4:"Love";i:6439;s:10:"EmeHyPlaty";i:6440;s:6:"JucRec";i:6441;s:11:"ConfePhoTra";i:6442;s:9:"ConcoPoss";i:6443;s:12:"GalacIrSemif";i:6444;s:9:"ParaUnawa";i:6445;s:7:"UnUnZam";i:6446;s:7:"PinSabr";i:6447;s:9:"MicOverSu";i:6448;s:8:"PoilViny";i:6449;s:8:"SentiSup";i:6450;s:10:"ParlaProSu";i:6451;s:11:"BipBrancObu";i:6452;s:5:"Uncri";i:6453;s:5:"Suran";i:6454;s:12:"BloClaviTitu";i:6455;s:4:"TwUn";i:6456;s:6:"PiPost";i:6457;s:7:"ElaFeHy";i:6458;s:5:"Unnav";i:6459;s:13:"OphidPenthUni";i:6460;s:3:"Lar";i:6461;s:15:"LongiRisibTopog";i:6462;s:7:"ThamuUn";i:6463;s:11:"LotProUnbla";i:6464;s:7:"TaUnmer";i:6465;s:7:"MilPhyl";i:6466;s:5:"IncNi";i:6467;s:3:"Fix";i:6468;s:6:"ExSwip";i:6469;s:5:"OraUn";i:6470;s:4:"Scle";i:6471;s:9:"HoReparSu";i:6472;s:9:"OvangUnpr";i:6473;s:12:"DoorSnowbUni";i:6474;s:9:"OverPolUn";i:6475;s:3:"Mit";i:6476;s:5:"Unsop";i:6477;s:14:"HeavyHeterJump";i:6478;s:9:"PostpWall";i:6479;s:11:"RepRicTetra";i:6480;s:12:"LiqPalaUnipu";i:6481;s:7:"ClMesPe";i:6482;s:13:"HeterLirParda";i:6483;s:5:"Trieq";i:6484;s:9:"SamarTigr";i:6485;s:5:"Diver";i:6486;s:4:"LiUn";i:6487;s:4:"Beta";i:6488;s:6:"DivuOp";i:6489;s:9:"NoniScruf";i:6490;s:9:"LoafOnchi";i:6491;s:3:"Ele";i:6492;s:7:"TeraUnm";i:6493;s:5:"Palee";i:6494;s:5:"Solid";i:6495;s:4:"Mynp";i:6496;s:10:"FairyPulWo";i:6497;s:4:"Wate";i:6498;s:4:"Rodd";i:6499;s:10:"OffPlUngui";i:6500;s:9:"OvPlayUrd";i:6501;s:7:"SocioUp";i:6502;s:5:"EtPim";i:6503;s:10:"ParonPrVir";i:6504;s:2:"Ju";i:6505;s:10:"EchiSaThuy";i:6506;s:12:"NepNodulRefi";i:6507;s:11:"IncSinecUni";i:6508;s:9:"GarbStThe";i:6509;s:13:"GluttHeadsRun";i:6510;s:8:"EvaKafir";i:6511;s:9:"CoInNonen";i:6512;s:7:"InuOtho";i:6513;s:14:"DeutParenPrere";i:6514;s:10:"EnvMetamSh";i:6515;s:8:"ButadDis";i:6516;s:9:"MendPseud";i:6517;s:6:"PaProv";i:6518;s:3:"Ina";i:6519;s:10:"EcclInfPol";i:6520;s:6:"PrUnde";i:6521;s:14:"IneffPudgPursu";i:6522;s:9:"SchizToVo";i:6523;s:3:"Imb";i:6524;s:4:"ChSt";i:6525;s:9:"LarLecoSt";i:6526;s:5:"UnUnd";i:6527;s:7:"IntReoc";i:6528;s:7:"FixedIm";i:6529;s:9:"CogTabWoo";i:6530;s:8:"SpaciUnp";i:6531;s:6:"BaraBe";i:6532;s:5:"Unbar";i:6533;s:12:"CometDeIntra";i:6534;s:12:"ErePeracWhee";i:6535;s:7:"CounUnw";i:6536;s:8:"NonTormo";i:6537;s:10:"PreiSpeTri";i:6538;s:11:"CerLankWeve";i:6539;s:12:"TractUngelVe";i:6540;s:10:"DepDrUnsol";i:6541;s:4:"Mult";i:6542;s:6:"RewVol";i:6543;s:5:"MorSy";i:6544;s:8:"UndWilkx";i:6545;s:4:"BeUn";i:6546;s:7:"ImpasUn";i:6547;s:10:"IndiaMerca";i:6548;s:4:"Unfo";i:6549;s:8:"ImMiMuti";i:6550;s:13:"NaviPreaUnspa";i:6551;s:5:"Extra";i:6552;s:11:"RenuWhWicke";i:6553;s:7:"TergUnp";i:6554;s:8:"MydTabid";i:6555;s:3:"Lod";i:6556;s:4:"Cruc";i:6557;s:5:"DiaSo";i:6558;s:8:"HetInter";i:6559;s:7:"ClMisap";i:6560;s:3:"Spe";i:6561;s:9:"IllecPant";i:6562;s:5:"Rhabd";i:6563;s:13:"BrushWireYell";i:6564;s:7:"DilaOve";i:6565;s:12:"CyprDegPotma";i:6566;s:8:"TicUtero";i:6567;s:6:"InPtyc";i:6568;s:12:"DelaMaraScul";i:6569;s:7:"AlpCrSt";i:6570;s:12:"HeUnproVagra";i:6571;s:4:"Theo";i:6572;s:12:"RecSplanVenu";i:6573;s:4:"Groc";i:6574;s:14:"BasilNoncoZami";i:6575;s:10:"LifexPasto";i:6576;s:14:"PsychSubdWhipp";i:6577;s:5:"MosTe";i:6578;s:14:"CoplaObsiXyloq";i:6579;s:10:"FatbrStrat";i:6580;s:7:"ToUnpit";i:6581;s:5:"PitSh";i:6582;s:8:"JapRatch";i:6583;s:8:"VarniYod";i:6584;s:11:"HancoRemTen";i:6585;s:10:"KoreiOverf";i:6586;s:9:"TineVimfu";i:6587;s:7:"NoruUne";i:6588;s:3:"Ure";i:6589;s:11:"CultrPlaSun";i:6590;s:10:"ExogaTrowe";i:6591;s:5:"Upcom";i:6592;s:10:"CuneiFePre";i:6593;s:10:"MatPlSnaff";i:6594;s:8:"PhaTarTy";i:6595;s:9:"BetHenrUn";i:6596;s:6:"BruMet";i:6597;s:4:"Peck";i:6598;s:5:"Makes";i:6599;s:5:"Unpit";i:6600;s:8:"ScThewTo";i:6601;s:4:"Spec";i:6602;s:9:"UnplUnres";i:6603;s:11:"OxytoSaftUp";i:6604;s:5:"CiHoo";i:6605;s:9:"InverMoTi";i:6606;s:3:"Upt";i:6607;s:15:"OverfPolymPrein";i:6608;s:9:"CotePhoto";i:6609;s:4:"Crew";i:6610;s:8:"PredPrim";i:6611;s:10:"CephaShrub";i:6612;s:3:"Nuc";i:6613;s:3:"Ouz";i:6614;s:9:"ThereVena";i:6615;s:3:"Sug";i:6616;s:3:"Nam";i:6617;s:7:"QuinoTr";i:6618;s:5:"Reove";i:6619;s:10:"GliomIntSc";i:6620;s:9:"DuchReban";i:6621;s:6:"DisHot";i:6622;s:10:"PhoroRatio";i:6623;s:4:"BrTi";i:6624;s:10:"HyInterTae";i:6625;s:4:"Plic";i:6626;s:6:"IsoSar";i:6627;s:8:"CyaniPos";i:6628;s:7:"JossaTh";i:6629;s:7:"MoTease";i:6630;s:9:"PenciSynt";i:6631;s:12:"TobUnjelUrti";i:6632;s:7:"StorTri";i:6633;s:4:"Squi";i:6634;s:10:"IsoThUnsof";i:6635;s:9:"EpilNonbu";i:6636;s:4:"Recr";i:6637;s:12:"GlossMinTown";i:6638;s:3:"Sid";i:6639;s:10:"MoireNoTri";i:6640;s:9:"FungSkUna";i:6641;s:10:"LewPemmUne";i:6642;s:9:"MetaOveSc";i:6643;s:11:"FlaMiscaOwl";i:6644;s:6:"PrWhis";i:6645;s:7:"SpTesUn";i:6646;s:12:"BroSubjuUnmo";i:6647;s:6:"SupTch";i:6648;s:10:"HeromLodPo";i:6649;s:12:"BeniCheesSur";i:6650;s:9:"HypeOverl";i:6651;s:4:"Sela";i:6652;s:10:"StiTurtlVe";i:6653;s:11:"GreMacrMarr";i:6654;s:8:"AsHanImp";i:6655;s:6:"FaSher";i:6656;s:12:"MicPonerTran";i:6657;s:11:"GigarHyMudd";i:6658;s:11:"AmErytFemin";i:6659;s:5:"Sacer";i:6660;s:9:"OutmUneph";i:6661;s:7:"DissWar";i:6662;s:9:"ObPlScabr";i:6663;s:5:"Inten";i:6664;s:3:"Raj";i:6665;s:8:"LaPipeUn";i:6666;s:8:"PreReUne";i:6667;s:11:"InpMonobPle";i:6668;s:4:"Here";i:6669;s:8:"GasPicWe";i:6670;s:12:"GamoInfiMyos";i:6671;s:8:"MinoPlai";i:6672;s:4:"Unbe";i:6673;s:5:"Outla";i:6674;s:7:"PseuVer";i:6675;s:4:"Wing";i:6676;s:5:"Unrav";i:6677;s:6:"GreTur";i:6678;s:11:"OysStaTrith";i:6679;s:8:"SaSymTur";i:6680;s:13:"MisliOdontPha";i:6681;s:11:"HaysNanomRe";i:6682;s:8:"PasPhySe";i:6683;s:11:"ColoLutOpta";i:6684;s:7:"LyUrtic";i:6685;s:6:"TimbTo";i:6686;s:9:"ShotTeete";i:6687;s:13:"BrushCafCompu";i:6688;s:9:"UncrUnhum";i:6689;s:6:"FibrTa";i:6690;s:7:"GameInc";i:6691;s:5:"Weakh";i:6692;s:10:"ObfusQuave";i:6693;s:13:"FurlIrreUngen";i:6694;s:8:"FoulPant";i:6695;s:11:"DisEuryQuar";i:6696;s:4:"Fati";i:6697;s:5:"Shalt";i:6698;s:9:"GhosMiThe";i:6699;s:12:"BandlKowtoTe";i:6700;s:10:"ImponTabUn";i:6701;s:10:"HexOnsUnpe";i:6702;s:8:"ElegiTem";i:6703;s:7:"PenhRep";i:6704;s:9:"OdonPhosp";i:6705;s:6:"PlWenl";i:6706;s:7:"ReprUns";i:6707;s:4:"Smit";i:6708;s:5:"Terro";i:6709;s:7:"ResusTe";i:6710;s:10:"HoppiPrSop";i:6711;s:5:"Shini";i:6712;s:12:"HeterRoamSal";i:6713;s:6:"OverWo";i:6714;s:5:"ScUnp";i:6715;s:4:"Mead";i:6716;s:5:"Upstr";i:6717;s:5:"Unrep";i:6718;s:4:"Rumi";i:6719;s:7:"SuperTe";i:6720;s:5:"ThVer";i:6721;s:12:"SupeThrTomop";i:6722;s:12:"OugPedanStib";i:6723;s:6:"CiCoun";i:6724;s:9:"HeHobThug";i:6725;s:6:"CohGno";i:6726;s:4:"Styr";i:6727;s:9:"MePresRes";i:6728;s:9:"ExPhThumb";i:6729;s:8:"SheSkipp";i:6730;s:10:"CaFaunaUnq";i:6731;s:4:"Meri";i:6732;s:10:"FerReWaste";i:6733;s:5:"Ninet";i:6734;s:9:"KmetxUnar";i:6735;s:8:"PulsSaxt";i:6736;s:6:"PrepUp";i:6737;s:5:"Marxi";i:6738;s:10:"EpiscSubpr";i:6739;s:8:"KochProh";i:6740;s:5:"GemOr";i:6741;s:15:"HeroiSubcoUnbra";i:6742;s:4:"Scot";i:6743;s:8:"MisSnort";i:6744;s:6:"PhaPha";i:6745;s:8:"RevUnpre";i:6746;s:8:"AnoSupVe";i:6747;s:4:"Stam";i:6748;s:3:"Pru";i:6749;s:7:"BeCatap";i:6750;s:5:"DevSc";i:6751;s:11:"SexiSocUnil";i:6752;s:6:"InNons";i:6753;s:11:"RecaTecnTre";i:6754;s:5:"CiCoa";i:6755;s:11:"CoDiscPachy";i:6756;s:4:"Pleu";i:6757;s:10:"ExtrHesiMu";i:6758;s:6:"TilbTo";i:6759;s:5:"Vacuo";i:6760;s:13:"PlaTheopTrica";i:6761;s:12:"OsirProtRhoe";i:6762;s:8:"MulWeava";i:6763;s:4:"Mort";i:6764;s:10:"QuSchilTan";i:6765;s:7:"SqueTar";i:6766;s:12:"OrthPhaRoent";i:6767;s:9:"MoPifTrib";i:6768;s:15:"SubcaSuperUnbra";i:6769;s:7:"LilaNan";i:6770;s:10:"OrnisPresu";i:6771;s:7:"AndreSo";i:6772;s:5:"Sloug";i:6773;s:9:"HoOxyheSc";i:6774;s:13:"DermLawlMetac";i:6775;s:13:"DirTwelvVirgi";i:6776;s:11:"NuttRatapVi";i:6777;s:4:"Teet";i:6778;s:9:"GardeScWr";i:6779;s:6:"PaPlUn";i:6780;s:8:"PhrUrrad";i:6781;s:4:"OrTh";i:6782;s:10:"LenUnUtopi";i:6783;s:7:"FaOssZi";i:6784;s:9:"NonmTexas";i:6785;s:8:"SuTricUn";i:6786;s:11:"NaOrchiRive";i:6787;s:6:"KleUnp";i:6788;s:6:"PhilUn";i:6789;s:4:"Cade";i:6790;s:13:"MacanOvercUpc";i:6791;s:8:"PinnSpha";i:6792;s:11:"IcMegalPara";i:6793;s:5:"NonTi";i:6794;s:10:"PienxVaria";i:6795;s:9:"EncoNoYal";i:6796;s:4:"Uptr";i:6797;s:8:"AnconSyc";i:6798;s:9:"FeIntoSta";i:6799;s:7:"RelShif";i:6800;s:7:"ExonePr";i:6801;s:6:"PuThio";i:6802;s:6:"HesiKa";i:6803;s:5:"PanTy";i:6804;s:11:"MacuMiRomag";i:6805;s:12:"LearnNotaSti";i:6806;s:4:"Raki";i:6807;s:8:"OrthUnfl";i:6808;s:10:"KurvePhySp";i:6809;s:7:"HisMump";i:6810;s:9:"PanxTreen";i:6811;s:8:"IndifUnc";i:6812;s:5:"ByMus";i:6813;s:11:"OuSuspUnplu";i:6814;s:12:"IsoLoundOutp";i:6815;s:15:"HexaxSinuaUntro";i:6816;s:8:"SiccUnde";i:6817;s:7:"CaprSep";i:6818;s:8:"RetroTet";i:6819;s:9:"InteQuSem";i:6820;s:5:"Imper";i:6821;s:3:"Psy";i:6822;s:12:"GnarLogTogsx";i:6823;s:4:"Unpa";i:6824;s:7:"HorseRe";i:6825;s:12:"PaUndisUndre";i:6826;s:3:"Lus";i:6827;s:2:"Cu";i:6828;s:3:"Sip";i:6829;s:3:"Tom";i:6830;s:7:"MicRask";i:6831;s:4:"Pect";i:6832;s:12:"ThesTrichVir";i:6833;s:5:"Unwas";i:6834;s:5:"Naple";i:6835;s:9:"MegPrTumo";i:6836;s:11:"HastaPeteSu";i:6837;s:4:"Unrh";i:6838;s:7:"PolyTol";i:6839;s:13:"DisaOrnitPlat";i:6840;s:3:"Oes";i:6841;s:5:"Puboi";i:6842;s:5:"Polyt";i:6843;s:11:"NonmPsTetra";i:6844;s:4:"Uniu";i:6845;s:11:"FasPreRamis";i:6846;s:5:"Stylo";i:6847;s:13:"PonciScyUnloc";i:6848;s:9:"MiaoRecol";i:6849;s:11:"SleSuperVig";i:6850;s:6:"RampRe";i:6851;s:5:"Hongx";i:6852;s:9:"ReticUnpo";i:6853;s:9:"LactiTrip";i:6854;s:7:"PolStTr";i:6855;s:10:"AxonoGraci";i:6856;s:12:"LinSuddeVerm";i:6857;s:9:"CataSuste";i:6858;s:7:"GiRussi";i:6859;s:8:"SextoSla";i:6860;s:5:"Suthe";i:6861;s:6:"PreSha";i:6862;s:10:"MoisRaSpir";i:6863;s:5:"Herco";i:6864;s:9:"TepaWalac";i:6865;s:5:"Tempe";i:6866;s:7:"RaisiUn";i:6867;s:9:"PremUnwif";i:6868;s:6:"MacSta";i:6869;s:12:"EphahPaurUnd";i:6870;s:9:"ParafSurv";i:6871;s:10:"FiRelatUns";i:6872;s:10:"OverdSinto";i:6873;s:5:"NoPup";i:6874;s:10:"CounForPty";i:6875;s:6:"MethVi";i:6876;s:11:"DecayKnocPu";i:6877;s:4:"Xena";i:6878;s:8:"ReRetrUn";i:6879;s:5:"KlTwi";i:6880;s:14:"BeechSubpUnmea";i:6881;s:5:"Unpro";i:6882;s:4:"PrSe";i:6883;s:6:"ReStUn";i:6884;s:8:"FeruOcra";i:6885;s:8:"HomTetry";i:6886;s:5:"IneNa";i:6887;s:7:"IndiPro";i:6888;s:11:"GinGuppScle";i:6889;s:9:"FellLight";i:6890;s:6:"LymPro";i:6891;s:5:"HaUnd";i:6892;s:12:"DaboiIntPant";i:6893;s:11:"CyaJangPann";i:6894;s:8:"PulStink";i:6895;s:12:"BolDevicOpis";i:6896;s:8:"HyTempWh";i:6897;s:7:"CheerSe";i:6898;s:5:"OmPyo";i:6899;s:3:"Tit";i:6900;s:7:"RetiSow";i:6901;s:8:"SemShall";i:6902;s:9:"SecStroVa";i:6903;s:10:"PieSemiSub";i:6904;s:4:"Conv";i:6905;s:10:"PiemaUndex";i:6906;s:11:"BreFrankUng";i:6907;s:9:"OkiTenUnw";i:6908;s:6:"PoStav";i:6909;s:2:"Zi";i:6910;s:11:"DiarrDoWind";i:6911;s:8:"AlicPara";i:6912;s:8:"SarcSnuf";i:6913;s:5:"Poetr";i:6914;s:5:"Nonma";i:6915;s:12:"BathMegUnple";i:6916;s:8:"PoSatUnd";i:6917;s:4:"Exor";i:6918;s:5:"Megar";i:6919;s:7:"HyIndMe";i:6920;s:8:"DuPhUnsp";i:6921;s:7:"SugViki";i:6922;s:9:"PreSquUns";i:6923;s:5:"Tiger";i:6924;s:2:"Dy";i:6925;s:8:"DebaRoun";i:6926;s:10:"PrecTicTra";i:6927;s:5:"Myoca";i:6928;s:9:"PinaSundr";i:6929;s:12:"NonioSonoTes";i:6930;s:8:"PersTone";i:6931;s:7:"PloPrel";i:6932;s:13:"EyepNorthVisc";i:6933;s:7:"RatiRou";i:6934;s:5:"Place";i:6935;s:5:"Proch";i:6936;s:9:"AppRepSto";i:6937;s:6:"OnlySe";i:6938;s:8:"DieguOsk";i:6939;s:11:"MatteOptaRa";i:6940;s:12:"SqTricuUncon";i:6941;s:12:"BreaDolpTrea";i:6942;s:10:"SerabUnlec";i:6943;s:4:"TaUn";i:6944;s:5:"HyPro";i:6945;s:12:"HeteIrraLupi";i:6946;s:6:"OverPa";i:6947;s:4:"Pili";i:6948;s:7:"MediVen";i:6949;s:7:"StUnple";i:6950;s:4:"Toxi";i:6951;s:4:"Medi";i:6952;s:11:"RadiiSoTouc";i:6953;s:8:"PeriPhan";i:6954;s:11:"EpopHeadsSh";i:6955;s:2:"Ka";i:6956;s:6:"IchSpe";i:6957;s:6:"ChTeta";i:6958;s:9:"LateSemih";i:6959;s:4:"Lutr";i:6960;s:10:"BedebHysMa";i:6961;s:8:"SardShru";i:6962;s:10:"CoreDePain";i:6963;s:3:"Tsa";i:6964;s:2:"De";i:6965;s:12:"MotRhinoVoll";i:6966;s:5:"Prefa";i:6967;s:3:"Est";i:6968;s:10:"FitOverdUn";i:6969;s:11:"PontaQuaSem";i:6970;s:8:"ForMaMis";i:6971;s:11:"PlumlPraeZi";i:6972;s:4:"Pseu";i:6973;s:8:"PuritTru";i:6974;s:12:"EquSpirWokex";i:6975;s:8:"SleepUnt";i:6976;s:5:"MidPo";i:6977;s:6:"ProtRu";i:6978;s:6:"ProgRe";i:6979;s:12:"PleniRunoUnu";i:6980;s:8:"BorJeann";i:6981;s:4:"Unwo";i:6982;s:8:"PamphSho";i:6983;s:8:"IneSkirt";i:6984;s:10:"PeRyukTrag";i:6985;s:9:"EnnoRonVa";i:6986;s:14:"MastySikarTrir";i:6987;s:3:"Tem";i:6988;s:15:"MozamRepliUncon";i:6989;s:8:"HecKoUnd";i:6990;s:8:"TellxThi";i:6991;s:7:"PseRadi";i:6992;s:8:"MissTrac";i:6993;s:7:"SlTrust";i:6994;s:8:"MacrSpUn";i:6995;s:9:"DaisyGoWh";i:6996;s:7:"PeriSor";i:6997;s:13:"HyposInteOver";i:6998;s:4:"Maza";i:6999;s:11:"QuSubscWeez";i:7000;s:6:"TruUre";i:7001;s:9:"PamlRecus";i:7002;s:10:"CycliStren";i:7003;s:5:"Serpi";i:7004;s:3:"Top";i:7005;s:3:"Moo";i:7006;s:7:"KneMaSe";i:7007;s:7:"IsatPol";i:7008;s:11:"ErytFattPeu";i:7009;s:7:"OsteUnd";i:7010;s:5:"Bulba";i:7011;s:8:"PaSnVerb";i:7012;s:5:"Unsin";i:7013;s:7:"PseuRub";i:7014;s:5:"Exsan";i:7015;s:10:"InterMiPen";i:7016;s:5:"Pleur";i:7017;s:13:"HydrInterOver";i:7018;s:4:"Coag";i:7019;s:7:"SaSicar";i:7020;s:6:"MonPre";i:7021;s:8:"SaSumbWi";i:7022;s:7:"GePreci";i:7023;s:4:"Tymp";i:7024;s:9:"SinuoUbii";i:7025;s:5:"Overw";i:7026;s:7:"HeorUnp";i:7027;s:6:"NeisUn";i:7028;s:10:"FrKikaPrec";i:7029;s:3:"Obt";i:7030;s:11:"BowmCroMedi";i:7031;s:12:"GreenMasUnde";i:7032;s:10:"CathoComed";i:7033;s:8:"SmeStUna";i:7034;s:5:"Undig";i:7035;s:8:"MicProba";i:7036;s:5:"Unvai";i:7037;s:8:"PreStoTo";i:7038;s:6:"LibWar";i:7039;s:9:"SaccVampi";i:7040;s:9:"ScoSqWanl";i:7041;s:13:"IndiKiloZephy";i:7042;s:3:"Rah";i:7043;s:8:"UncoUnUn";i:7044;s:7:"SwartUn";i:7045;s:4:"Sord";i:7046;s:4:"Nasi";i:7047;s:4:"Phil";i:7048;s:4:"Suss";i:7049;s:9:"HomopRiSp";i:7050;s:3:"Sca";i:7051;s:2:"Bi";i:7052;s:5:"Unret";i:7053;s:5:"LocTo";i:7054;s:6:"RubUnd";i:7055;s:4:"Foca";i:7056;s:7:"PePreRu";i:7057;s:9:"BaptiUnpa";i:7058;s:5:"ScSig";i:7059;s:11:"EndGenevUns";i:7060;s:6:"LepSpi";i:7061;s:4:"Vorl";i:7062;s:4:"Thro";i:7063;s:9:"FugHaHete";i:7064;s:8:"InteNote";i:7065;s:6:"PapUnd";i:7066;s:5:"Lands";i:7067;s:11:"FluPseuThel";i:7068;s:5:"Letha";i:7069;s:8:"GomaRidi";i:7070;s:9:"CourtMadd";i:7071;s:9:"NovePenSh";i:7072;s:2:"Ut";i:7073;s:4:"Vatm";i:7074;s:4:"Taxi";i:7075;s:7:"QuaveSp";i:7076;s:9:"MonstPred";i:7077;s:11:"PedTriUntoi";i:7078;s:11:"PlaUpteVivi";i:7079;s:8:"SurrTabl";i:7080;s:4:"Imid";i:7081;s:6:"NonZoo";i:7082;s:9:"PalaTrich";i:7083;s:5:"Thecl";i:7084;s:13:"OphPaleoUnout";i:7085;s:12:"DrumNebQuila";i:7086;s:11:"EpMiniProag";i:7087;s:13:"MuleRotuTriac";i:7088;s:5:"Ostra";i:7089;s:5:"Endoc";i:7090;s:12:"HydrVaiYouwa";i:7091;s:5:"RevSe";i:7092;s:8:"DermSigh";i:7093;s:5:"Splat";i:7094;s:5:"MerUn";i:7095;s:8:"KrisPaPr";i:7096;s:9:"LodgMaPol";i:7097;s:9:"FlMenoVul";i:7098;s:8:"MyelaRab";i:7099;s:5:"Commo";i:7100;s:10:"HaPhyTorcx";i:7101;s:10:"NomiReUnfo";i:7102;s:5:"Unwal";i:7103;s:10:"MucSiniTea";i:7104;s:9:"PsSolUnha";i:7105;s:7:"CraHyPr";i:7106;s:12:"IntRheuSerop";i:7107;s:12:"ManWindYouth";i:7108;s:8:"NotoSemi";i:7109;s:11:"MaPhytPosts";i:7110;s:8:"DeaPeWal";i:7111;s:12:"NotaPrimaSou";i:7112;s:4:"Sera";i:7113;s:7:"PaSaUnt";i:7114;s:6:"SimSup";i:7115;s:9:"MalpPhoto";i:7116;s:11:"AsbConiPred";i:7117;s:10:"MetatSpoon";i:7118;s:3:"Hol";i:7119;s:8:"DilLoTap";i:7120;s:4:"OuSe";i:7121;s:4:"SeYe";i:7122;s:7:"SulpUns";i:7123;s:7:"FliQuak";i:7124;s:8:"OphtRecu";i:7125;s:6:"CheNon";i:7126;s:12:"PandSarcoUns";i:7127;s:9:"MeMuleOxy";i:7128;s:10:"CerGarisMe";i:7129;s:9:"BoDuskPer";i:7130;s:4:"Moos";i:7131;s:13:"LiberPeaRolle";i:7132;s:4:"Unhi";i:7133;s:9:"ReadUnroo";i:7134;s:6:"BeaUnu";i:7135;s:5:"Taget";i:7136;s:8:"CellExPr";i:7137;s:7:"PaoScSu";i:7138;s:11:"DonHaeSubro";i:7139;s:3:"Gun";i:7140;s:8:"HePyraSu";i:7141;s:13:"AscaErythLett";i:7142;s:10:"HypocZoonu";i:7143;s:7:"SpewiUn";i:7144;s:5:"Sylva";i:7145;s:8:"MesSteno";i:7146;s:9:"RhomTiger";i:7147;s:4:"Rumm";i:7148;s:2:"Hu";i:7149;s:9:"DosGeopRe";i:7150;s:5:"GlWot";i:7151;s:7:"PaUnUno";i:7152;s:8:"CapeYenx";i:7153;s:10:"TouchTrUnf";i:7154;s:11:"EnaPreRigwi";i:7155;s:9:"TyphlUnUn";i:7156;s:12:"MecoMuddSpea";i:7157;s:4:"Myel";i:7158;s:10:"ExcoReiSej";i:7159;s:11:"ConjMotorZy";i:7160;s:6:"JoinUn";i:7161;s:10:"ExudSympUn";i:7162;s:10:"DisHorseSp";i:7163;s:8:"PreTherm";i:7164;s:7:"SupeTuc";i:7165;s:9:"CaDemSame";i:7166;s:5:"Zenag";i:7167;s:3:"Rug";i:7168;s:7:"RatioZo";i:7169;s:12:"InconPrQuidd";i:7170;s:8:"DevoiPal";i:7171;s:11:"CaIndaTourn";i:7172;s:5:"Infer";i:7173;s:9:"HydroPrec";i:7174;s:4:"Yout";i:7175;s:3:"Hak";i:7176;s:7:"UntZeph";i:7177;s:4:"Peed";i:7178;s:8:"MetPiTuc";i:7179;s:11:"LiNonsaOsci";i:7180;s:3:"Hea";i:7181;s:11:"PiprPostSen";i:7182;s:10:"MiSniUnmet";i:7183;s:6:"CaiExt";i:7184;s:6:"UnfWom";i:7185;s:7:"NonfUnw";i:7186;s:6:"NajNon";i:7187;s:10:"PaProTruck";i:7188;s:10:"RemoSemiSt";i:7189;s:8:"PerUnsym";i:7190;s:5:"Xyrid";i:7191;s:7:"AnaChun";i:7192;s:7:"HaLepro";i:7193;s:5:"Veuve";i:7194;s:14:"NoaxOverpWanto";i:7195;s:4:"Lith";i:7196;s:7:"FlaTara";i:7197;s:7:"PsychTe";i:7198;s:3:"Fra";i:7199;s:5:"Sitti";i:7200;s:13:"SnottTroTwitt";i:7201;s:9:"LampWreck";i:7202;s:8:"SuperVar";i:7203;s:7:"NondSym";i:7204;s:7:"FeetaSo";i:7205;s:9:"GrafNonfi";i:7206;s:2:"Od";i:7207;s:5:"Butan";i:7208;s:9:"LippLiPhy";i:7209;s:5:"ParPr";i:7210;s:7:"ScuUnUs";i:7211;s:6:"InPsUn";i:7212;s:7:"PipSaye";i:7213;s:9:"MatcStirr";i:7214;s:13:"ParrTenuUnpla";i:7215;s:7:"DurEnMe";i:7216;s:5:"PoPor";i:7217;s:11:"CriCutSauce";i:7218;s:10:"StViceWeax";i:7219;s:9:"FingNeuri";i:7220;s:6:"SubVir";i:7221;s:7:"TrWildi";i:7222;s:6:"ReSeUn";i:7223;s:10:"IsoPuSandl";i:7224;s:6:"SenSpl";i:7225;s:12:"LabryPrUnfel";i:7226;s:10:"LingaOpist";i:7227;s:10:"HydPeUninq";i:7228;s:15:"CloudObeyeUnder";i:7229;s:4:"Post";i:7230;s:7:"CrPhoto";i:7231;s:10:"MoPredePre";i:7232;s:12:"CardSchisWhi";i:7233;s:11:"JapKolVenom";i:7234;s:4:"Unfl";i:7235;s:8:"PhacVers";i:7236;s:3:"Lab";i:7237;s:3:"Jig";i:7238;s:10:"CorybIrrem";i:7239;s:9:"TaguUnimp";i:7240;s:5:"Whipp";i:7241;s:4:"Stoc";i:7242;s:13:"ConveOwregShi";i:7243;s:10:"BromoCaFor";i:7244;s:9:"MoStahlTy";i:7245;s:14:"IndigLoriStaye";i:7246;s:10:"ElProeUnsu";i:7247;s:8:"PanUnrid";i:7248;s:5:"Prehe";i:7249;s:3:"Leg";i:7250;s:9:"BotryPrem";i:7251;s:5:"UnWed";i:7252;s:7:"CraPudd";i:7253;s:12:"HainSnonSulf";i:7254;s:12:"PerSouthUnpo";i:7255;s:8:"ElGrafTh";i:7256;s:8:"CrassWil";i:7257;s:9:"ConNonOly";i:7258;s:5:"Schni";i:7259;s:9:"TertZygom";i:7260;s:11:"ScrVacYawwe";i:7261;s:10:"ButDisceTr";i:7262;s:11:"ChoriEcarVe";i:7263;s:4:"Femi";i:7264;s:7:"NeUnpos";i:7265;s:13:"MurPerveRunol";i:7266;s:5:"PrSig";i:7267;s:10:"EmbMidmTro";i:7268;s:8:"CestInca";i:7269;s:5:"CorPy";i:7270;s:12:"BylSpoonTymp";i:7271;s:9:"NotiSlunk";i:7272;s:5:"Whitr";i:7273;s:4:"Unba";i:7274;s:10:"MonatTheor";i:7275;s:14:"IdioShoalTamac";i:7276;s:7:"PartiTo";i:7277;s:11:"CounCraMill";i:7278;s:4:"Uppe";i:7279;s:9:"PatavPewt";i:7280;s:4:"CoSa";i:7281;s:4:"Vase";i:7282;s:6:"ColcNo";i:7283;s:14:"ThrifTransWate";i:7284;s:10:"NoTacTartu";i:7285;s:12:"ReiUnforUngl";i:7286;s:8:"CrExpaEx";i:7287;s:5:"ParPh";i:7288;s:6:"SkylUn";i:7289;s:5:"StaUn";i:7290;s:10:"FibuSlTric";i:7291;s:10:"KartvMagne";i:7292;s:13:"BotcCatstSenc";i:7293;s:7:"EnduHem";i:7294;s:12:"MammaStrUnfo";i:7295;s:9:"SlidSpUro";i:7296;s:13:"PatSnabbVolat";i:7297;s:5:"Mysti";i:7298;s:4:"OsSc";i:7299;s:5:"Trick";i:7300;s:5:"Cohen";i:7301;s:12:"HypMoralTinh";i:7302;s:10:"PettySacri";i:7303;s:8:"StrowUlt";i:7304;s:5:"Circu";i:7305;s:4:"Rifl";i:7306;s:13:"FlocxLaxUnacc";i:7307;s:10:"HirsMyodPe";i:7308;s:5:"LiMic";i:7309;s:9:"MesRainWa";i:7310;s:8:"BuntiSyp";i:7311;s:12:"AgroConveOrt";i:7312;s:4:"Rais";i:7313;s:8:"NeSemiTr";i:7314;s:7:"CrGenPe";i:7315;s:7:"RecUnch";i:7316;s:10:"LatchPosse";i:7317;s:9:"BavenWeal";i:7318;s:5:"NonSp";i:7319;s:6:"MontVo";i:7320;s:15:"MisfaNarcaNonto";i:7321;s:5:"DeTuc";i:7322;s:8:"CountEuc";i:7323;s:10:"HaiIliocUn";i:7324;s:10:"CoaptLumPa";i:7325;s:5:"Thoms";i:7326;s:5:"TaTop";i:7327;s:8:"PhalaVib";i:7328;s:6:"BoraKa";i:7329;s:12:"TassTempeVir";i:7330;s:8:"MonosNam";i:7331;s:10:"SpiTethThe";i:7332;s:13:"QuerUnchiVare";i:7333;s:6:"BipNon";i:7334;s:9:"JaPolyoSi";i:7335;s:12:"AmbiEntGloam";i:7336;s:13:"MetalRegalSup";i:7337;s:10:"ErotiRetun";i:7338;s:12:"PyraSteprSyn";i:7339;s:9:"StabUngui";i:7340;s:5:"Pates";i:7341;s:12:"CaEnseeRunbo";i:7342;s:13:"NeurPteroSoma";i:7343;s:10:"RitarSquTa";i:7344;s:3:"Hum";i:7345;s:7:"PangePo";i:7346;s:5:"InOut";i:7347;s:11:"CoExtoUncan";i:7348;s:11:"HomoeNoctUn";i:7349;s:7:"BuPoRes";i:7350;s:8:"PhalRect";i:7351;s:3:"Mea";i:7352;s:13:"JoyleNotReplu";i:7353;s:4:"Nonh";i:7354;s:4:"Reco";i:7355;s:2:"Fu";i:7356;s:9:"OctSaSpic";i:7357;s:8:"NinUntes";i:7358;s:5:"NoSpl";i:7359;s:5:"OuSte";i:7360;s:9:"ResurStau";i:7361;s:14:"RevokSclerThan";i:7362;s:5:"Tombl";i:7363;s:10:"IsMarooNaz";i:7364;s:11:"OtotoSeriSo";i:7365;s:8:"CerePleu";i:7366;s:7:"ForaPse";i:7367;s:8:"CentTube";i:7368;s:14:"CebuEkamaSauce";i:7369;s:10:"GlenxHypog";i:7370;s:6:"HyOafx";i:7371;s:9:"RackWillo";i:7372;s:5:"LoOgh";i:7373;s:9:"WigfuWint";i:7374;s:14:"IndiOrthoProdu";i:7375;s:12:"ClCoconUnpre";i:7376;s:12:"ExpFlunkMega";i:7377;s:9:"ChorInsWo";i:7378;s:4:"Hair";i:7379;s:6:"UncWin";i:7380;s:7:"PaPsoro";i:7381;s:9:"TradUndit";i:7382;s:9:"MakeUnlik";i:7383;s:10:"AntiApMeta";i:7384;s:7:"PhoPhyt";i:7385;s:7:"ColMust";i:7386;s:10:"CathaConPh";i:7387;s:8:"NoTromVe";i:7388;s:3:"Cos";i:7389;s:10:"CapDunPres";i:7390;s:14:"HypanIncuLeopa";i:7391;s:6:"UnasWe";i:7392;s:5:"ExpUn";i:7393;s:5:"IntUn";i:7394;s:5:"Overf";i:7395;s:5:"Rameq";i:7396;s:9:"ImmLoxoUn";i:7397;s:5:"EmaGi";i:7398;s:13:"EsoEurasUnlab";i:7399;s:8:"LobaPrZo";i:7400;s:9:"ProaUnrYa";i:7401;s:8:"SubUnleg";i:7402;s:9:"ComaExNeg";i:7403;s:10:"HandwPrUnc";i:7404;s:3:"Bre";i:7405;s:7:"QuatrVo";i:7406;s:3:"Erm";i:7407;s:8:"ParThioc";i:7408;s:7:"NoctiTr";i:7409;s:7:"ElUncha";i:7410;s:11:"PitiRecStri";i:7411;s:4:"OtPa";i:7412;s:9:"GanoMyrrh";i:7413;s:4:"Redu";i:7414;s:8:"HuPifTon";i:7415;s:6:"NeUnmu";i:7416;s:5:"GeHus";i:7417;s:14:"DisafProvQuino";i:7418;s:11:"LoydQuReman";i:7419;s:3:"Sta";i:7420;s:12:"RefTeasUnchi";i:7421;s:6:"ClaTam";i:7422;s:5:"Disho";i:7423;s:11:"BaNowneRein";i:7424;s:7:"CoFocus";i:7425;s:6:"SemiWa";i:7426;s:14:"BottoObsceUltr";i:7427;s:4:"Shoa";i:7428;s:7:"MonoTri";i:7429;s:10:"LackQuadUn";i:7430;s:13:"SarcTetaTrump";i:7431;s:11:"UmbUntawVin";i:7432;s:14:"NewlSkewnUnpar";i:7433;s:4:"Podo";i:7434;s:3:"Wor";i:7435;s:12:"InterKeratMy";i:7436;s:11:"ResStrTesta";i:7437;s:5:"Unfat";i:7438;s:4:"Prof";i:7439;s:5:"Misco";i:7440;s:10:"ChurLimbUn";i:7441;s:5:"Rammi";i:7442;s:10:"CarCeleoSn";i:7443;s:9:"DysaSeven";i:7444;s:4:"Inno";i:7445;s:5:"PhoUn";i:7446;s:7:"QuiReUn";i:7447;s:14:"AlgarBrumEquiv";i:7448;s:4:"FiWo";i:7449;s:5:"Wuthe";i:7450;s:11:"MoguePorSpr";i:7451;s:7:"HiMarRe";i:7452;s:9:"UndeUnder";i:7453;s:13:"PinUndelUpspe";i:7454;s:9:"SomitSwSy";i:7455;s:7:"FeGaMed";i:7456;s:10:"ImmPowSore";i:7457;s:7:"DisUnop";i:7458;s:9:"LactOliUn";i:7459;s:5:"GroVe";i:7460;s:6:"StTele";i:7461;s:3:"Exc";i:7462;s:4:"MePa";i:7463;s:8:"SubcZaca";i:7464;s:9:"SyUnsWast";i:7465;s:2:"Gh";i:7466;s:9:"RecRobTum";i:7467;s:11:"DistPreUnre";i:7468;s:12:"EbHoerxMatro";i:7469;s:13:"BeeCorreGetae";i:7470;s:12:"LariOutflPro";i:7471;s:9:"InSmVocab";i:7472;s:5:"Hecta";i:7473;s:6:"IgnaTw";i:7474;s:5:"Seric";i:7475;s:8:"SaeSeiTi";i:7476;s:6:"OppPat";i:7477;s:5:"MoTha";i:7478;s:6:"NonvSk";i:7479;s:4:"Sarc";i:7480;s:5:"Autho";i:7481;s:13:"BestoStiUncla";i:7482;s:9:"CanToolUn";i:7483;s:9:"FornModio";i:7484;s:9:"RhapWartx";i:7485;s:10:"GynodImKra";i:7486;s:13:"FirepPreShinz";i:7487;s:12:"KroLucuMetem";i:7488;s:6:"ReUnsu";i:7489;s:5:"Rooib";i:7490;s:9:"ExpFiGout";i:7491;s:8:"EuTeYess";i:7492;s:11:"PluRecReins";i:7493;s:3:"Com";i:7494;s:5:"FooUn";i:7495;s:4:"Tort";i:7496;s:4:"Brac";i:7497;s:5:"EquTo";i:7498;s:9:"ExTrUnsmi";i:7499;s:4:"Lobe";i:7500;s:5:"GynSu";i:7501;s:10:"PondSxTric";i:7502;s:9:"FocaUnreq";i:7503;s:6:"IrrePr";i:7504;s:4:"Styt";i:7505;s:4:"FoPi";i:7506;s:6:"NaTenn";i:7507;s:5:"Bookb";i:7508;s:3:"Yet";i:7509;s:10:"MyxedSpinn";i:7510;s:9:"AweeNonco";i:7511;s:8:"PrenRecr";i:7512;s:7:"CoImmod";i:7513;s:8:"ApocEeMe";i:7514;s:11:"GiNilsxVisa";i:7515;s:4:"Taxe";i:7516;s:4:"Epip";i:7517;s:10:"ConduMeSub";i:7518;s:10:"CrouReVerm";i:7519;s:10:"OversWeath";i:7520;s:6:"ChMeso";i:7521;s:4:"Dipl";i:7522;s:6:"PlinTh";i:7523;s:7:"OrPreSu";i:7524;s:10:"HipPrRevis";i:7525;s:7:"SchSter";i:7526;s:8:"PlasPlum";i:7527;s:5:"UnWol";i:7528;s:7:"MightSp";i:7529;s:9:"PseuSynca";i:7530;s:7:"PsUnpur";i:7531;s:7:"LynxxMa";i:7532;s:4:"Clun";i:7533;s:12:"OverPhlResil";i:7534;s:12:"MetSatiStabl";i:7535;s:5:"Scien";i:7536;s:3:"Tee";i:7537;s:6:"NoVago";i:7538;s:8:"HomMortg";i:7539;s:4:"Stic";i:7540;s:9:"CaCongrUn";i:7541;s:4:"Whar";i:7542;s:9:"OverQuadr";i:7543;s:8:"MisPlata";i:7544;s:9:"CaiSquUnh";i:7545;s:10:"TenUncouUn";i:7546;s:7:"OutTrom";i:7547;s:3:"Lec";i:7548;s:7:"MoSpili";i:7549;s:6:"CaUnab";i:7550;s:10:"MuddPsUnwa";i:7551;s:12:"CoseExpUncon";i:7552;s:5:"Verde";i:7553;s:7:"CoMoWes";i:7554;s:7:"SimuSli";i:7555;s:9:"MylodTran";i:7556;s:7:"CymbSub";i:7557;s:10:"ImThyUnfor";i:7558;s:4:"Stev";i:7559;s:7:"HoploTa";i:7560;s:7:"CoRabix";i:7561;s:7:"OdoTurk";i:7562;s:3:"Fre";i:7563;s:7:"JoKorex";i:7564;s:10:"CofNoneUnc";i:7565;s:3:"Ery";i:7566;s:7:"MusResu";i:7567;s:5:"Valid";i:7568;s:7:"OncoShi";i:7569;s:11:"FoKibbUndim";i:7570;s:3:"Tim";i:7571;s:8:"MortThWi";i:7572;s:13:"ReaalUnsedUnt";i:7573;s:4:"Degl";i:7574;s:9:"MiniOutPe";i:7575;s:13:"HelioHyetMemo";i:7576;s:11:"InvePrediSl";i:7577;s:7:"GhostHe";i:7578;s:10:"RevUncUntr";i:7579;s:5:"Bemai";i:7580;s:7:"TrikeWh";i:7581;s:8:"TelTikix";i:7582;s:9:"MaliUncir";i:7583;s:12:"ForksHepTher";i:7584;s:7:"HypNond";i:7585;s:5:"Wintr";i:7586;s:7:"JeeSubm";i:7587;s:9:"InadeOrUn";i:7588;s:11:"MilnOversTr";i:7589;s:4:"Thal";i:7590;s:3:"Duk";i:7591;s:7:"SleUpla";i:7592;s:2:"Ha";i:7593;s:5:"PeUnm";i:7594;s:9:"DeHipOryz";i:7595;s:4:"PhTe";i:7596;s:8:"UnpZygos";i:7597;s:11:"LovRefiUnco";i:7598;s:6:"DwHodd";i:7599;s:3:"Phi";i:7600;s:7:"DonatSu";i:7601;s:4:"Gran";i:7602;s:8:"LaboTurr";i:7603;s:5:"Geode";i:7604;s:3:"Gle";i:7605;s:5:"Hebra";i:7606;s:4:"Crot";i:7607;s:10:"BloPahSupe";i:7608;s:8:"PostmSer";i:7609;s:6:"IncoUn";i:7610;s:4:"Rere";i:7611;s:11:"ObstTetraUn";i:7612;s:13:"ForehSanieUnc";i:7613;s:6:"SpToma";i:7614;s:5:"Triha";i:7615;s:13:"GallProceWeis";i:7616;s:10:"NonpePolys";i:7617;s:8:"PartrPol";i:7618;s:5:"Volle";i:7619;s:7:"BockPle";i:7620;s:9:"DeDichWel";i:7621;s:10:"IncIndTher";i:7622;s:5:"Minor";i:7623;s:5:"Shado";i:7624;s:6:"ShSlav";i:7625;s:12:"MazalVelVera";i:7626;s:12:"EmargSaUnapp";i:7627;s:11:"OveTussoUnc";i:7628;s:13:"NovaToniUnfor";i:7629;s:12:"FormOnaOutba";i:7630;s:7:"LouvReg";i:7631;s:8:"SerShZau";i:7632;s:10:"EbullIncav";i:7633;s:12:"SmirkStryUna";i:7634;s:7:"StraiUn";i:7635;s:4:"Besn";i:7636;s:6:"DuchIm";i:7637;s:4:"Cowp";i:7638;s:8:"GermJuMi";i:7639;s:11:"BurdHydProm";i:7640;s:9:"PeSeTimbe";i:7641;s:3:"Cac";i:7642;s:6:"MortUn";i:7643;s:5:"Straw";i:7644;s:10:"ExterProph";i:7645;s:11:"RetroSwTele";i:7646;s:6:"ThiUnf";i:7647;s:4:"Unsk";i:7648;s:9:"AnnodHoIl";i:7649;s:5:"ErgEt";i:7650;s:13:"JoviPreabSnee";i:7651;s:5:"Gentl";i:7652;s:3:"Bet";i:7653;s:12:"NuPebaxTitan";i:7654;s:4:"Tato";i:7655;s:7:"SulphUn";i:7656;s:5:"DubSt";i:7657;s:9:"PlowwUnmi";i:7658;s:9:"PaPhotPis";i:7659;s:5:"Subex";i:7660;s:11:"OptoProSubc";i:7661;s:4:"Rout";i:7662;s:4:"Vapo";i:7663;s:9:"HyMidriSy";i:7664;s:7:"NulliWi";i:7665;s:9:"FerStoUnt";i:7666;s:7:"LusSaun";i:7667;s:7:"SterUnc";i:7668;s:5:"BlOst";i:7669;s:7:"InMicro";i:7670;s:5:"Induc";i:7671;s:4:"Tope";i:7672;s:4:"Disi";i:7673;s:5:"SuUte";i:7674;s:7:"CohMiRe";i:7675;s:11:"AmpuScroWin";i:7676;s:10:"MilToUncit";i:7677;s:12:"CenTerrVesic";i:7678;s:8:"QuixUnhu";i:7679;s:10:"SupeTifTub";i:7680;s:4:"Tens";i:7681;s:4:"SeSy";i:7682;s:5:"Precu";i:7683;s:4:"LiSe";i:7684;s:5:"HerLe";i:7685;s:8:"PapyPhot";i:7686;s:9:"ShantSooh";i:7687;s:10:"TradeVicto";i:7688;s:10:"GuardHiYex";i:7689;s:7:"AlkSpra";i:7690;s:4:"EmSt";i:7691;s:8:"HalosPor";i:7692;s:12:"GrotReasVerb";i:7693;s:13:"InchoNonprPor";i:7694;s:8:"LievLubr";i:7695;s:8:"PhalWith";i:7696;s:7:"HemerLu";i:7697;s:9:"HaMoldUnr";i:7698;s:8:"SpanUnwi";i:7699;s:9:"ConnStTri";i:7700;s:7:"HypSuki";i:7701;s:4:"None";i:7702;s:14:"OverPartuUncal";i:7703;s:13:"JessSperSpide";i:7704;s:11:"ReSynodUpba";i:7705;s:4:"Unsq";i:7706;s:10:"MystaReint";i:7707;s:8:"SubcTeno";i:7708;s:7:"ImpaSom";i:7709;s:10:"PsychUncom";i:7710;s:8:"OverProc";i:7711;s:8:"OvePrRec";i:7712;s:6:"DaiPri";i:7713;s:9:"HeavThUnp";i:7714;s:9:"MesolUntr";i:7715;s:11:"SulUncUncoq";i:7716;s:7:"OvileUn";i:7717;s:10:"EnthuSiale";i:7718;s:10:"ErythOrYen";i:7719;s:6:"DeUnVa";i:7720;s:14:"SemidUnpitVege";i:7721;s:8:"CannWhee";i:7722;s:8:"FratPhRe";i:7723;s:7:"MuSulph";i:7724;s:9:"GhaNudiSl";i:7725;s:7:"SomSuTr";i:7726;s:5:"Nomen";i:7727;s:2:"Be";i:7728;s:7:"LabPreb";i:7729;s:6:"UnqUns";i:7730;s:10:"IntraVespe";i:7731;s:8:"HoUncVot";i:7732;s:8:"GroinSto";i:7733;s:5:"Cooke";i:7734;s:12:"OligoScarVol";i:7735;s:12:"CarGoldUndel";i:7736;s:9:"ReceSqUnd";i:7737;s:7:"GaliJam";i:7738;s:3:"Ole";i:7739;s:7:"LaSilve";i:7740;s:3:"Pne";i:7741;s:3:"Sig";i:7742;s:12:"HugPseudVert";i:7743;s:12:"IleocPolVera";i:7744;s:6:"PrShak";i:7745;s:5:"HyRie";i:7746;s:4:"Swee";i:7747;s:12:"PetThelWinet";i:7748;s:11:"DongoHeReci";i:7749;s:9:"MonoPaZyg";i:7750;s:8:"FrowzGap";i:7751;s:6:"GuInte";i:7752;s:4:"File";i:7753;s:6:"AsinHy";i:7754;s:8:"StotUnfi";i:7755;s:11:"RetTetUname";i:7756;s:13:"BiriCedarQuir";i:7757;s:6:"LuPlWe";i:7758;s:6:"IndSty";i:7759;s:7:"ForePlo";i:7760;s:8:"IntOvipo";i:7761;s:13:"TubuUncomUpdr";i:7762;s:9:"BrLilOver";i:7763;s:3:"Kow";i:7764;s:3:"Nut";i:7765;s:3:"Yuk";i:7766;s:11:"PePhoenVege";i:7767;s:8:"PerceUns";i:7768;s:13:"TalloUnqVesic";i:7769;s:4:"Elen";i:7770;s:13:"GriSymmeUsuri";i:7771;s:4:"Esqu";i:7772;s:5:"PaPea";i:7773;s:6:"EtheTo";i:7774;s:7:"PolyPse";i:7775;s:5:"FibUr";i:7776;s:9:"PassiReSo";i:7777;s:14:"EspiGnomoPerch";i:7778;s:9:"InePoUnsp";i:7779;s:10:"SmitSquaTe";i:7780;s:5:"Trunc";i:7781;s:6:"ChClum";i:7782;s:4:"Pear";i:7783;s:8:"BromoCic";i:7784;s:8:"ExItPayr";i:7785;s:6:"GaSulb";i:7786;s:15:"DeindDiscoWoodr";i:7787;s:4:"Sple";i:7788;s:7:"UndreVi";i:7789;s:12:"ImpPerobStur";i:7790;s:5:"Moder";i:7791;s:3:"Det";i:7792;s:8:"SubUndec";i:7793;s:9:"ProphTris";i:7794;s:10:"PlPreSedul";i:7795;s:9:"PedaUlotr";i:7796;s:6:"GeGing";i:7797;s:9:"BugfChrom";i:7798;s:8:"IntPerso";i:7799;s:10:"SicexSplay";i:7800;s:3:"Ces";i:7801;s:12:"PusSelenSten";i:7802;s:11:"NurseReWars";i:7803;s:9:"CoNaUnbir";i:7804;s:5:"Sospi";i:7805;s:7:"CirGeol";i:7806;s:9:"SesquUnpr";i:7807;s:13:"BioLaticPsora";i:7808;s:10:"IntOstarPe";i:7809;s:5:"PiTwa";i:7810;s:5:"Pleom";i:7811;s:13:"PerinProSasse";i:7812;s:10:"GuanUnsWat";i:7813;s:7:"RekiUnt";i:7814;s:7:"CarPoly";i:7815;s:13:"BiangImprLeti";i:7816;s:9:"ObUnapVeg";i:7817;s:13:"OxanaRasSulph";i:7818;s:11:"InidoShuVis";i:7819;s:7:"SciUnha";i:7820;s:5:"Nonpr";i:7821;s:5:"Sandm";i:7822;s:10:"PolygTwUnc";i:7823;s:7:"GenScTa";i:7824;s:8:"PreSuVin";i:7825;s:11:"MeShekUpren";i:7826;s:6:"PaPicr";i:7827;s:3:"Oph";i:7828;s:9:"CracPeria";i:7829;s:9:"DiscDownl";i:7830;s:5:"Trias";i:7831;s:13:"SecSkullVeris";i:7832;s:9:"FructTher";i:7833;s:6:"DiaZea";i:7834;s:4:"Tren";i:7835;s:14:"LudgaMastProge";i:7836;s:8:"ExteMock";i:7837;s:3:"Vir";i:7838;s:5:"RegSt";i:7839;s:7:"OccuUdx";i:7840;s:10:"GrNeriUnha";i:7841;s:10:"SpiraTopic";i:7842;s:5:"Oreas";i:7843;s:5:"Unmou";i:7844;s:3:"Wes";i:7845;s:2:"Ec";i:7846;s:5:"Probo";i:7847;s:9:"DiscoGano";i:7848;s:8:"NonaSmok";i:7849;s:8:"RamTraUn";i:7850;s:9:"LameMusic";i:7851;s:8:"CowPopes";i:7852;s:12:"PorcStrUnbio";i:7853;s:4:"Rash";i:7854;s:11:"FielInteWes";i:7855;s:4:"Anth";i:7856;s:9:"CeCubiSaf";i:7857;s:7:"PeaPoRo";i:7858;s:7:"PrPyram";i:7859;s:7:"DiseaDo";i:7860;s:10:"HeterPeSac";i:7861;s:9:"FixiLaSla";i:7862;s:8:"HuaProSa";i:7863;s:13:"JuglJustTrivi";i:7864;s:5:"Selac";i:7865;s:7:"HaWacke";i:7866;s:3:"Pas";i:7867;s:12:"DeInaniPolyt";i:7868;s:9:"UncomUnim";i:7869;s:5:"Slaug";i:7870;s:8:"UnbloUnc";i:7871;s:6:"NonPho";i:7872;s:6:"ProfTh";i:7873;s:11:"EpictJuriPe";i:7874;s:7:"MerPhyt";i:7875;s:12:"PhiloRoVirgi";i:7876;s:12:"SpVasolWhiff";i:7877;s:7:"NonloPa";i:7878;s:7:"AbFaUnm";i:7879;s:5:"Tetar";i:7880;s:8:"ReideUre";i:7881;s:9:"EsoPrevRe";i:7882;s:5:"Plymo";i:7883;s:10:"HandlNaTra";i:7884;s:12:"DisbGlumVipe";i:7885;s:9:"OppreWhea";i:7886;s:5:"GueKo";i:7887;s:8:"MonuNond";i:7888;s:7:"SalUnpi";i:7889;s:4:"Syne";i:7890;s:14:"SluitWeisWhatk";i:7891;s:10:"GinwOaPerg";i:7892;s:11:"PleurRaUnde";i:7893;s:10:"MetMetPtil";i:7894;s:11:"OchPenSuper";i:7895;s:3:"Jar";i:7896;s:9:"PreciSond";i:7897;s:3:"Des";i:7898;s:9:"HeKenoNon";i:7899;s:13:"ConneNonspOdy";i:7900;s:9:"AspBliIna";i:7901;s:9:"JansePhPi";i:7902;s:6:"RhTamm";i:7903;s:9:"PoetTucka";i:7904;s:6:"TricZo";i:7905;s:4:"Furd";i:7906;s:5:"Squee";i:7907;s:13:"HippoSteTrigo";i:7908;s:14:"CombiInseLoine";i:7909;s:12:"HaemImprQuav";i:7910;s:4:"Unad";i:7911;s:4:"Uros";i:7912;s:4:"Palp";i:7913;s:4:"ReUl";i:7914;s:6:"ExcPea";i:7915;s:6:"PodTri";i:7916;s:14:"QuibbRobiUntem";i:7917;s:5:"Mothe";i:7918;s:12:"MansThorWald";i:7919;s:6:"HearUr";i:7920;s:13:"DarkPalaeUnar";i:7921;s:10:"SpeTeUntra";i:7922;s:5:"Ineff";i:7923;s:9:"LazexMisi";i:7924;s:7:"HarPseu";i:7925;s:13:"IntSalesVeloc";i:7926;s:5:"Halop";i:7927;s:7:"HyPyroc";i:7928;s:11:"HymeJeProdu";i:7929;s:8:"PseSinga";i:7930;s:7:"UtopXan";i:7931;s:5:"OvPro";i:7932;s:11:"SalUnsWoman";i:7933;s:10:"PyrTesToxo";i:7934;s:5:"GunPr";i:7935;s:7:"OveraUn";i:7936;s:12:"LaPacktWease";i:7937;s:5:"Lepto";i:7938;s:6:"MeStTr";i:7939;s:7:"OwlVerm";i:7940;s:4:"Spad";i:7941;s:4:"Unap";i:7942;s:12:"CulgeDideMan";i:7943;s:8:"PoPreaPr";i:7944;s:10:"TelauUnhus";i:7945;s:9:"CalInJuba";i:7946;s:9:"MicUnfVol";i:7947;s:7:"EumeFle";i:7948;s:10:"NumiRuWide";i:7949;s:6:"TritTu";i:7950;s:5:"Sanuk";i:7951;s:7:"HazarQu";i:7952;s:8:"LeroQuUg";i:7953;s:3:"Who";i:7954;s:12:"MakefMetScul";i:7955;s:9:"FamiPluri";i:7956;s:5:"Purse";i:7957;s:9:"PhTissuUn";i:7958;s:12:"AloDemopSens";i:7959;s:9:"InteSluic";i:7960;s:4:"Mesi";i:7961;s:8:"ReTuliUn";i:7962;s:14:"CyanLycanSpenc";i:7963;s:4:"NeRo";i:7964;s:7:"AmmocCe";i:7965;s:6:"MiSika";i:7966;s:8:"LongiNoa";i:7967;s:8:"ColMiPla";i:7968;s:11:"CoincMillPn";i:7969;s:11:"MoraePrUmpx";i:7970;s:7:"PromaSh";i:7971;s:7:"TaTorre";i:7972;s:7:"PresPro";i:7973;s:7:"GayVomi";i:7974;s:7:"EncVagi";i:7975;s:3:"Geo";i:7976;s:12:"GroupOverUnh";i:7977;s:8:"CaltMeNo";i:7978;s:10:"IsochTogVi";i:7979;s:5:"Sphae";i:7980;s:6:"HeHoIn";i:7981;s:4:"Plis";i:7982;s:9:"MisjoPhar";i:7983;s:10:"AscCoUnthi";i:7984;s:12:"HemiPannaSha";i:7985;s:9:"RosiScrol";i:7986;s:8:"RepSepia";i:7987;s:9:"RaReagRev";i:7988;s:5:"SpThe";i:7989;s:8:"FilaPiez";i:7990;s:10:"GeoboHeNec";i:7991;s:7:"OverSen";i:7992;s:3:"Har";i:7993;s:4:"Torp";i:7994;s:9:"OpilUnder";i:7995;s:7:"KaSupra";i:7996;s:4:"Ikat";i:7997;s:5:"Forea";i:7998;s:6:"FoRere";i:7999;s:13:"PeridSecluTru";i:8000;s:10:"JeewhPigeo";i:8001;s:5:"DiPer";i:8002;s:8:"IndTheUn";i:8003;s:13:"ChlorHesSacri";i:8004;s:7:"UncoUnk";i:8005;s:6:"ComInc";i:8006;s:10:"HelLimPlet";i:8007;s:3:"Tau";i:8008;s:13:"HexaRepreSidd";i:8009;s:11:"LeptoReliUt";i:8010;s:3:"Din";i:8011;s:10:"ChronCuSay";i:8012;s:15:"CaphtHymenPsych";i:8013;s:4:"Saim";i:8014;s:5:"RenSh";i:8015;s:6:"AntiFo";i:8016;s:9:"RecadUnad";i:8017;s:3:"Ben";i:8018;s:5:"Forma";i:8019;s:4:"OrQu";i:8020;s:11:"AutGuesInnk";i:8021;s:10:"DacryUltZa";i:8022;s:8:"SubcoTes";i:8023;s:4:"Sapp";i:8024;s:14:"EuchPostaRipco";i:8025;s:7:"PatbRej";i:8026;s:5:"Maxil";i:8027;s:10:"HeMalaProv";i:8028;s:14:"RakexStagVomer";i:8029;s:9:"MonosPrRa";i:8030;s:4:"Oket";i:8031;s:7:"PaTrack";i:8032;s:9:"CausUnYaw";i:8033;s:9:"IntraUncl";i:8034;s:5:"Staro";i:8035;s:4:"Walk";i:8036;s:4:"Pitt";i:8037;s:10:"SappSmaTan";i:8038;s:6:"ReUnhu";i:8039;s:5:"Measl";i:8040;s:9:"UrbaWhimb";i:8041;s:7:"JaSucci";i:8042;s:10:"CoPyrTeles";i:8043;s:9:"MelilScle";i:8044;s:8:"IntPrStr";i:8045;s:11:"CatFrogSwif";i:8046;s:7:"DiscPar";i:8047;s:10:"PeRecoSpon";i:8048;s:10:"EpiOvePont";i:8049;s:14:"PrefeScatTrans";i:8050;s:7:"OutTele";i:8051;s:8:"HenKluxe";i:8052;s:14:"GametGrubrMono";i:8053;s:3:"Sho";i:8054;s:8:"EschaIso";i:8055;s:11:"EcliPropUnd";i:8056;s:6:"DePros";i:8057;s:13:"BetocCaecaInt";i:8058;s:7:"PrescPr";i:8059;s:12:"DeGeniiSkiam";i:8060;s:5:"SlTre";i:8061;s:11:"SeSpooTrich";i:8062;s:8:"PteroZor";i:8063;s:3:"Tru";i:8064;s:6:"FlSter";i:8065;s:12:"ReptiTrUndec";i:8066;s:14:"HyperProphWood";i:8067;s:10:"TimbaUnfis";i:8068;s:8:"CaSenUni";i:8069;s:8:"RathUrrh";i:8070;s:5:"ReSte";i:8071;s:8:"AutoTheg";i:8072;s:5:"Unrus";i:8073;s:10:"CorLePeaco";i:8074;s:10:"DepaMeRewh";i:8075;s:5:"HomUp";i:8076;s:7:"DeguFis";i:8077;s:10:"DreyfRefal";i:8078;s:4:"IlIn";i:8079;s:8:"OutsOvPr";i:8080;s:9:"RoteSulfa";i:8081;s:12:"CladuPolyStu";i:8082;s:5:"Susce";i:8083;s:8:"CoHexaHu";i:8084;s:12:"KnaggPsychUg";i:8085;s:5:"Pytho";i:8086;s:5:"FloTa";i:8087;s:6:"OuSple";i:8088;s:8:"CliPiWee";i:8089;s:4:"Unar";i:8090;s:10:"InvoMarPar";i:8091;s:5:"Homoo";i:8092;s:7:"SaSceni";i:8093;s:6:"FreQua";i:8094;s:13:"ArchsArtilCoc";i:8095;s:7:"MalMult";i:8096;s:5:"Scree";i:8097;s:10:"PhaPoisThy";i:8098;s:7:"GeLemTo";i:8099;s:5:"Perip";i:8100;s:9:"MartWineb";i:8101;s:5:"Trise";i:8102;s:10:"HyRatTehue";i:8103;s:9:"ExImsonUn";i:8104;s:4:"Warf";i:8105;s:11:"LarriMinWit";i:8106;s:6:"TriUps";i:8107;s:11:"MangTaTouch";i:8108;s:10:"ReddiTange";i:8109;s:7:"SwirVal";i:8110;s:11:"IliaInfiNab";i:8111;s:10:"BackFrieUn";i:8112;s:8:"ChaThrom";i:8113;s:5:"JeOve";i:8114;s:5:"Royet";i:8115;s:5:"Plian";i:8116;s:12:"ImprePeUnspi";i:8117;s:5:"Kaffi";i:8118;s:4:"Skim";i:8119;s:6:"MuRewa";i:8120;s:5:"Wicex";i:8121;s:8:"PruReSce";i:8122;s:10:"ConEpiUnli";i:8123;s:9:"MoPresaSc";i:8124;s:3:"Wel";i:8125;s:5:"PyrPy";i:8126;s:9:"CordoStTi";i:8127;s:12:"PetrSteUnsmo";i:8128;s:9:"HissOtocr";i:8129;s:8:"EuMeSlas";i:8130;s:4:"Leth";i:8131;s:5:"IrLon";i:8132;s:9:"AuliIsUin";i:8133;s:6:"GameYe";i:8134;s:9:"NoncNonsa";i:8135;s:12:"FamisPoStrew";i:8136;s:4:"Stan";i:8137;s:8:"ShiSulph";i:8138;s:10:"MyogrResUn";i:8139;s:13:"MultUncheUnco";i:8140;s:7:"KasOpUn";i:8141;s:10:"DiploPaTyl";i:8142;s:4:"Mann";i:8143;s:11:"LuffxUnbUne";i:8144;s:6:"OutsSu";i:8145;s:5:"Shape";i:8146;s:9:"ScreTripe";i:8147;s:13:"CrimeNapPreim";i:8148;s:6:"MiZest";i:8149;s:5:"Suito";i:8150;s:5:"Learx";i:8151;s:14:"CursoOracPenna";i:8152;s:6:"EnstRa";i:8153;s:9:"ChowdPref";i:8154;s:9:"ClansThre";i:8155;s:7:"BlCurub";i:8156;s:6:"SaffUn";i:8157;s:8:"OuScUndr";i:8158;s:7:"DeWista";i:8159;s:7:"AnImmMe";i:8160;s:8:"InscUnra";i:8161;s:9:"RanceUnco";i:8162;s:3:"Lob";i:8163;s:8:"HumorPoo";i:8164;s:4:"GoSu";i:8165;s:6:"TalaTe";i:8166;s:10:"ChoirPoVet";i:8167;s:12:"MinNecrNonra";i:8168;s:4:"Unes";i:8169;s:11:"ForOvePrele";i:8170;s:4:"GaPe";i:8171;s:5:"IntTh";i:8172;s:5:"Prece";i:8173;s:7:"PoikiSc";i:8174;s:13:"CloudMedSuper";i:8175;s:5:"PreQu";i:8176;s:7:"SwagSwi";i:8177;s:10:"MorOutliPr";i:8178;s:13:"NaissNuclePre";i:8179;s:4:"Chla";i:8180;s:4:"Zapt";i:8181;s:10:"VentrWeany";i:8182;s:7:"ParabSa";i:8183;s:9:"CenoFillx";i:8184;s:5:"Undul";i:8185;s:5:"Trade";i:8186;s:8:"BizeColc";i:8187;s:6:"PhPugh";i:8188;s:12:"PreSporSport";i:8189;s:8:"GyneZarz";i:8190;s:6:"CalcIp";i:8191;s:8:"UnqVakia";i:8192;s:9:"SeUnViola";i:8193;s:7:"HySnoop";i:8194;s:5:"MaiTo";i:8195;s:12:"GaviaMeliNic";i:8196;s:4:"Prae";i:8197;s:6:"HorrMo";i:8198;s:8:"TauroWag";i:8199;s:5:"Punic";i:8200;s:4:"Matr";i:8201;s:5:"Jimba";i:8202;s:2:"Ya";i:8203;s:10:"JapRemaScr";i:8204;s:12:"PrThiasUnten";i:8205;s:11:"MisdPhanPsy";i:8206;s:5:"Store";i:8207;s:6:"StTigh";i:8208;s:9:"ApChoreSl";i:8209;s:4:"Refo";i:8210;s:14:"BlowlHydraPump";i:8211;s:11:"PalaeUnpWha";i:8212;s:8:"ArBiUnco";i:8213;s:12:"ConvPrevSuet";i:8214;s:9:"HemocStag";i:8215;s:9:"TutuVario";i:8216;s:6:"HeiUns";i:8217;s:5:"InTen";i:8218;s:8:"SeroSphe";i:8219;s:4:"Tarr";i:8220;s:5:"ExKro";i:8221;s:8:"MukdPoly";i:8222;s:10:"SpliVotWas";i:8223;s:8:"RecomSca";i:8224;s:11:"ManaPneumSo";i:8225;s:3:"Cha";i:8226;s:9:"MicrOvRet";i:8227;s:8:"PreyTwad";i:8228;s:7:"MoNoRes";i:8229;s:7:"EpUnboi";i:8230;s:7:"DodgiUn";i:8231;s:8:"NovSotti";i:8232;s:8:"RenSubSu";i:8233;s:10:"JurisUncVe";i:8234;s:12:"SteatSutToxo";i:8235;s:11:"TucUncUnpro";i:8236;s:8:"EpiSciss";i:8237;s:8:"OleUnave";i:8238;s:5:"CaFor";i:8239;s:9:"PostRedSc";i:8240;s:7:"ImpeSav";i:8241;s:7:"LocSupr";i:8242;s:8:"HaddInSu";i:8243;s:13:"PolitVerWorth";i:8244;s:9:"MaxilSymp";i:8245;s:3:"Wha";i:8246;s:5:"Oroba";i:8247;s:10:"HolocPurSe";i:8248;s:12:"ChEnsmaMonia";i:8249;s:14:"SaponToxeUnven";i:8250;s:9:"CriDosPre";i:8251;s:5:"Turbo";i:8252;s:13:"FisnHyponVine";i:8253;s:3:"Sol";i:8254;s:10:"PunctSuspe";i:8255;s:12:"BedeCaninPer";i:8256;s:3:"Uro";i:8257;s:7:"StiUnsu";i:8258;s:9:"PaleShore";i:8259;s:7:"LeQuadx";i:8260;s:8:"CryExcTa";i:8261;s:8:"PaProUnp";i:8262;s:9:"PinelScol";i:8263;s:8:"CoHyLayl";i:8264;s:8:"HeOptPri";i:8265;s:8:"PaSliTry";i:8266;s:6:"HaPumi";i:8267;s:6:"SofTra";i:8268;s:4:"Redh";i:8269;s:5:"Round";i:8270;s:10:"PhyUltrVel";i:8271;s:9:"TyrtaZami";i:8272;s:12:"ProbRumeTins";i:8273;s:6:"FlooLa";i:8274;s:9:"RaroSolei";i:8275;s:11:"GorgeNavSma";i:8276;s:4:"Dicy";i:8277;s:5:"ReSpi";i:8278;s:9:"InOcPastu";i:8279;s:11:"CounMyStell";i:8280;s:3:"Sau";i:8281;s:4:"InUn";i:8282;s:11:"NonPredSpin";i:8283;s:5:"Outpr";i:8284;s:11:"ComMilRebuf";i:8285;s:10:"HyStraTell";i:8286;s:6:"PiTris";i:8287;s:6:"RoTect";i:8288;s:6:"SloSpe";i:8289;s:10:"MuttoUnpre";i:8290;s:8:"ChUnwoVe";i:8291;s:5:"Thirt";i:8292;s:9:"PredUnres";i:8293;s:12:"FreelHydroIn";i:8294;s:4:"SaUn";i:8295;s:8:"ErraSage";i:8296;s:10:"QuadrSeaYa";i:8297;s:5:"PerSo";i:8298;s:12:"AvenNonNookl";i:8299;s:4:"DaHe";i:8300;s:8:"ChiDemUk";i:8301;s:8:"MaUnWind";i:8302;s:6:"CaInWe";i:8303;s:11:"EndeOsseoUn";i:8304;s:8:"MyriPeri";i:8305;s:10:"PestpSonat";i:8306;s:4:"PhRa";i:8307;s:9:"HawxPemph";i:8308;s:6:"ReTrem";i:8309;s:9:"NonciScot";i:8310;s:10:"MicPerSpon";i:8311;s:3:"Chr";i:8312;s:9:"GuttTrico";i:8313;s:7:"HagiScr";i:8314;s:6:"PlesPo";i:8315;s:10:"MesorMinNi";i:8316;s:8:"DisPlano";i:8317;s:8:"HyaInOut";i:8318;s:6:"OmTosh";i:8319;s:6:"SuTrVa";i:8320;s:12:"CombiPyrenUn";i:8321;s:5:"PhaPo";i:8322;s:8:"ImpInInt";i:8323;s:8:"ParWandx";i:8324;s:4:"Robo";i:8325;s:9:"MulQuiSpa";i:8326;s:6:"MicOsm";i:8327;s:6:"OvZoog";i:8328;s:11:"OveRemanSel";i:8329;s:10:"BrickGuLac";i:8330;s:12:"PapaSakSensi";i:8331;s:11:"HistPondTel";i:8332;s:4:"Vert";i:8333;s:7:"OestrRe";i:8334;s:10:"MagStrTetr";i:8335;s:8:"BridChry";i:8336;s:7:"ResubUn";i:8337;s:10:"EqPolyTabl";i:8338;s:5:"Matri";i:8339;s:10:"ElectWater";i:8340;s:7:"AmnesUn";i:8341;s:7:"GeRetro";i:8342;s:10:"OstPicValu";i:8343;s:10:"HolPrSpatt";i:8344;s:7:"BrighSe";i:8345;s:11:"MusiNonUniv";i:8346;s:9:"NonSittVo";i:8347;s:4:"Rewo";i:8348;s:7:"ResoUra";i:8349;s:9:"MaPaTolfr";i:8350;s:8:"BunWicke";i:8351;s:13:"HobParasPindy";i:8352;s:8:"IllfMiOu";i:8353;s:11:"DisrTabiUnd";i:8354;s:10:"KoffxPaRua";i:8355;s:13:"GogoxSunkeTom";i:8356;s:9:"UnbeWafer";i:8357;s:9:"SecreSpUp";i:8358;s:4:"Mend";i:8359;s:11:"HallmHydrWa";i:8360;s:7:"ChInPer";i:8361;s:10:"IsoQuinSte";i:8362;s:7:"RelUnta";i:8363;s:7:"GigbaLo";i:8364;s:13:"DrumPipeVentr";i:8365;s:4:"Unil";i:8366;s:10:"PneProviSh";i:8367;s:6:"InInte";i:8368;s:8:"LiUnhuUn";i:8369;s:12:"MismaNeWrith";i:8370;s:5:"Preor";i:8371;s:8:"GeiPrUnp";i:8372;s:9:"NonsuPoly";i:8373;s:5:"Ivory";i:8374;s:9:"TaleYowlr";i:8375;s:13:"MitosNotTrisi";i:8376;s:11:"MeniNeuTatu";i:8377;s:14:"ImprMetalUncon";i:8378;s:12:"PessReaSilic";i:8379;s:12:"RooUnmatVina";i:8380;s:5:"Pedan";i:8381;s:5:"Hattx";i:8382;s:4:"Sham";i:8383;s:3:"Bum";i:8384;s:5:"Polyn";i:8385;s:8:"BaeSerfd";i:8386;s:11:"InureUnleWe";i:8387;s:5:"Outmi";i:8388;s:10:"PaleoTaVer";i:8389;s:11:"HexasLaddSt";i:8390;s:7:"ChevrNo";i:8391;s:7:"LanMeMe";i:8392;s:6:"PhoUps";i:8393;s:6:"ResWri";i:8394;s:8:"PlatTerr";i:8395;s:15:"BilgyDisedUgand";i:8396;s:4:"CrDe";i:8397;s:12:"CholeSaTommy";i:8398;s:9:"OmniSheep";i:8399;s:9:"PeaPtQuat";i:8400;s:8:"IsocNilx";i:8401;s:6:"CeHete";i:8402;s:4:"Voci";i:8403;s:6:"SamSca";i:8404;s:4:"Lapa";i:8405;s:10:"DeuSimUnfr";i:8406;s:9:"HucMarsSc";i:8407;s:11:"PerPlatSpor";i:8408;s:12:"GratuHeKaemp";i:8409;s:13:"MastiShipTemp";i:8410;s:7:"PsePycn";i:8411;s:9:"NubecPipp";i:8412;s:12:"FrencManiuUn";i:8413;s:3:"Gas";i:8414;s:9:"MesoScSub";i:8415;s:12:"FomenMerobUn";i:8416;s:8:"OverPhre";i:8417;s:7:"IcostMi";i:8418;s:10:"BioscMisPa";i:8419;s:10:"ArcExtWham";i:8420;s:9:"TickTitex";i:8421;s:9:"ForfNiSpe";i:8422;s:5:"Unbig";i:8423;s:10:"IrredLiftm";i:8424;s:12:"AugeDebbyUnr";i:8425;s:11:"BleekImmPax";i:8426;s:5:"Dowdi";i:8427;s:8:"SilThimb";i:8428;s:14:"NonrePantiStre";i:8429;s:13:"IncesLentoMar";i:8430;s:12:"CytoPonSludx";i:8431;s:7:"CoSuper";i:8432;s:4:"RoTh";i:8433;s:6:"NiSupe";i:8434;s:7:"ExpPhlo";i:8435;s:3:"Via";i:8436;s:9:"PurbYiddi";i:8437;s:10:"OrPedaRush";i:8438;s:5:"LaTec";i:8439;s:3:"Sod";i:8440;s:9:"CorvDreIm";i:8441;s:9:"NoProUngr";i:8442;s:8:"ContInIn";i:8443;s:5:"Stupe";i:8444;s:4:"Sync";i:8445;s:4:"Sloo";i:8446;s:3:"Hep";i:8447;s:7:"DesFePh";i:8448;s:4:"Toxo";i:8449;s:11:"ConjHeLater";i:8450;s:5:"Mistu";i:8451;s:8:"KnapPsSi";i:8452;s:7:"ArFacQu";i:8453;s:13:"PneuSemiWoode";i:8454;s:7:"KnNisha";i:8455;s:9:"PoQueStak";i:8456;s:13:"LegitRelicRom";i:8457;s:7:"HaIchno";i:8458;s:10:"EleutScept";i:8459;s:7:"ProSter";i:8460;s:11:"HatbSequUna";i:8461;s:4:"Micr";i:8462;s:6:"GlGlut";i:8463;s:12:"ChannMicrPre";i:8464;s:4:"Tyri";i:8465;s:4:"Supp";i:8466;s:5:"Unpre";i:8467;s:9:"JiggeSong";i:8468;s:5:"Wards";i:8469;s:5:"FeInd";i:8470;s:3:"Spa";i:8471;s:12:"BuoyDecuRedu";i:8472;s:5:"Wolfh";i:8473;s:7:"PolonRe";i:8474;s:5:"Astro";i:8475;s:14:"ClareUnlaUntom";i:8476;s:4:"Tria";i:8477;s:11:"UnjarUnUnva";i:8478;s:10:"LampMyeUnb";i:8479;s:4:"Unho";i:8480;s:11:"FraiRiXebec";i:8481;s:9:"EndoThUnr";i:8482;s:9:"FulgMaMin";i:8483;s:13:"HydrPosolProf";i:8484;s:10:"EcholSaumo";i:8485;s:13:"CopeDozexMona";i:8486;s:7:"FlePort";i:8487;s:5:"Usnin";i:8488;s:4:"PaUn";i:8489;s:6:"MourSa";i:8490;s:9:"ChiLarNon";i:8491;s:3:"Sna";i:8492;s:11:"KioPhotPrep";i:8493;s:9:"OligSubco";i:8494;s:8:"InflPiUn";i:8495;s:9:"GlaHetTre";i:8496;s:10:"ChronEucPh";i:8497;s:6:"RuiUne";i:8498;s:10:"ResumUngla";i:8499;s:4:"Corp";i:8500;s:7:"IndPape";i:8501;s:8:"PalProje";i:8502;s:4:"Sepi";i:8503;s:9:"OvariSpin";i:8504;s:10:"QuiveUncor";i:8505;s:5:"Picra";i:8506;s:14:"MythiOutbWedan";i:8507;s:6:"ShowUl";i:8508;s:11:"ParasRecRev";i:8509;s:7:"EugGall";i:8510;s:3:"Coe";i:8511;s:6:"SurrWr";i:8512;s:12:"FlabHetePsyc";i:8513;s:5:"ChDev";i:8514;s:9:"NoneUnbek";i:8515;s:12:"DiagrMesPopu";i:8516;s:8:"ShSupeUn";i:8517;s:11:"KeysMeVicto";i:8518;s:13:"ShriTirriWhos";i:8519;s:15:"PhilaPrickRutil";i:8520;s:13:"EnfesRortyThe";i:8521;s:3:"Ket";i:8522;s:12:"GreePerniPur";i:8523;s:7:"OoSorce";i:8524;s:7:"SonioUn";i:8525;s:8:"QuadSaum";i:8526;s:7:"CorCrGr";i:8527;s:11:"PistShaThou";i:8528;s:6:"DisePh";i:8529;s:8:"SatinWit";i:8530;s:7:"SchisSp";i:8531;s:4:"Upcu";i:8532;s:13:"MadeUnconWitc";i:8533;s:4:"Antu";i:8534;s:10:"SecatTaqua";i:8535;s:5:"Proco";i:8536;s:7:"UntwiWr";i:8537;s:8:"PiProRep";i:8538;s:13:"OstScrimVadim";i:8539;s:5:"Subsu";i:8540;s:10:"CruttLappa";i:8541;s:6:"ShUnli";i:8542;s:7:"EpitRes";i:8543;s:4:"Kuld";i:8544;s:4:"Stru";i:8545;s:6:"UncUne";i:8546;s:3:"Tel";i:8547;s:4:"MuTu";i:8548;s:12:"FreckRippRoc";i:8549;s:10:"UnbefVilay";i:8550;s:9:"HydrInflo";i:8551;s:9:"NotidPred";i:8552;s:12:"GoniLustfUnd";i:8553;s:12:"ImbosMaOverb";i:8554;s:7:"ShThymo";i:8555;s:6:"KaMuPl";i:8556;s:10:"PyogRecoRe";i:8557;s:10:"NestoTrUnt";i:8558;s:12:"MissMonodNon";i:8559;s:6:"UncoUn";i:8560;s:6:"UnUned";i:8561;s:3:"Nes";i:8562;s:6:"DumHem";i:8563;s:4:"Yawn";i:8564;s:5:"Medie";i:8565;s:7:"MetSubi";i:8566;s:9:"SeltSteSt";i:8567;s:8:"LivOlivi";i:8568;s:4:"LiPa";i:8569;s:4:"Sarr";i:8570;s:5:"Porit";i:8571;s:8:"NebbyRam";i:8572;s:6:"PloTim";i:8573;s:9:"MilliOrri";i:8574;s:9:"GessHomTh";i:8575;s:6:"HighLi";i:8576;s:13:"HetHypogSupra";i:8577;s:5:"Stave";i:8578;s:8:"LingTass";i:8579;s:4:"StTh";i:8580;s:6:"SeSong";i:8581;s:4:"Gyra";i:8582;s:9:"OnaxUnhos";i:8583;s:4:"Noto";i:8584;s:6:"PeVerb";i:8585;s:5:"Opera";i:8586;s:7:"SaSoUni";i:8587;s:10:"OphrPeUrea";i:8588;s:5:"InWin";i:8589;s:13:"NecesNoweQues";i:8590;s:7:"SauteSu";i:8591;s:7:"BloBrac";i:8592;s:9:"ExHebriLu";i:8593;s:7:"MpangRa";i:8594;s:12:"DemagEllipMo";i:8595;s:10:"ImprSkiWat";i:8596;s:8:"LocTende";i:8597;s:7:"PaUnadv";i:8598;s:11:"SavSlagStra";i:8599;s:8:"BlTasUnd";i:8600;s:4:"Riss";i:8601;s:5:"MoTah";i:8602;s:5:"Hypot";i:8603;s:5:"Prali";i:8604;s:11:"ExsOverSola";i:8605;s:4:"Omni";i:8606;s:10:"MacrSeSupe";i:8607;s:7:"NautWin";i:8608;s:7:"UnswWhe";i:8609;s:5:"Untoo";i:8610;s:5:"Mimmo";i:8611;s:10:"NotoRegrSu";i:8612;s:7:"MonaPse";i:8613;s:9:"ChiRetSqu";i:8614;s:11:"CatarEpiIde";i:8615;s:8:"SerUnrev";i:8616;s:7:"UnoWrit";i:8617;s:9:"MisSuTeas";i:8618;s:8:"MonWhare";i:8619;s:14:"ElzevRoughUtri";i:8620;s:5:"Uninf";i:8621;s:4:"Volu";i:8622;s:9:"ExpOdsRet";i:8623;s:10:"DiaFendPhy";i:8624;s:7:"OversPh";i:8625;s:3:"Van";i:8626;s:3:"Pic";i:8627;s:3:"Hoo";i:8628;s:7:"MetPneu";i:8629;s:11:"GranuTeleTe";i:8630;s:2:"Ep";i:8631;s:5:"Verti";i:8632;s:8:"BerdaRei";i:8633;s:11:"PolStylWasa";i:8634;s:10:"TemUnbowUn";i:8635;s:10:"HiSeldToli";i:8636;s:8:"PlaRaTre";i:8637;s:7:"SalUnro";i:8638;s:6:"LipoQu";i:8639;s:5:"Refle";i:8640;s:9:"OutloScUn";i:8641;s:7:"GrMaVat";i:8642;s:7:"ShardTr";i:8643;s:6:"PolStr";i:8644;s:9:"StacTiTub";i:8645;s:10:"GangGrooZi";i:8646;s:4:"Titi";i:8647;s:7:"LocPala";i:8648;s:4:"Skin";i:8649;s:11:"DiaphRapSpu";i:8650;s:8:"GlarMeta";i:8651;s:7:"InOffic";i:8652;s:4:"Hete";i:8653;s:7:"RefeSuc";i:8654;s:5:"MeSie";i:8655;s:4:"DaMe";i:8656;s:4:"Yard";i:8657;s:4:"Vene";i:8658;s:8:"MelaShUn";i:8659;s:9:"EjPiUnnab";i:8660;s:9:"OrnitPayn";i:8661;s:12:"CotarPhohUnd";i:8662;s:9:"HaitiUnUn";i:8663;s:8:"NealxSup";i:8664;s:4:"Tetr";i:8665;s:8:"UnlitZeu";i:8666;s:9:"GammaGuid";i:8667;s:9:"TaTraUndi";i:8668;s:10:"SizSuWahah";i:8669;s:7:"PoliPun";i:8670;s:3:"Sum";i:8671;s:13:"PenthRegrTasi";i:8672;s:9:"RetroUnat";i:8673;s:11:"ElInveNickx";i:8674;s:14:"EbionParmeSpid";i:8675;s:4:"Gurg";i:8676;s:4:"Ente";i:8677;s:9:"IntMilTer";i:8678;s:11:"HomoPiRetar";i:8679;s:8:"InspReco";i:8680;s:14:"RealSanctSepar";i:8681;s:12:"CyclOuttSlag";i:8682;s:12:"ExogNadiSupe";i:8683;s:4:"LeTe";i:8684;s:10:"BenefInter";i:8685;s:10:"HarmRotUpp";i:8686;s:8:"PseTunna";i:8687;s:10:"PreResSati";i:8688;s:6:"FloTra";i:8689;s:5:"Troph";i:8690;s:4:"InMo";i:8691;s:15:"PlaceRaciaRuntx";i:8692;s:9:"CauSrTolu";i:8693;s:12:"IncomXenopXe";i:8694;s:12:"CaeciCaPaleo";i:8695;s:4:"LoPh";i:8696;s:12:"GamIodyXerop";i:8697;s:5:"Chond";i:8698;s:5:"MeTel";i:8699;s:8:"ObediPou";i:8700;s:4:"PhVa";i:8701;s:5:"UndUr";i:8702;s:5:"Rachi";i:8703;s:11:"BrickGreeLe";i:8704;s:5:"Unreb";i:8705;s:7:"EnwiTew";i:8706;s:13:"EleonHearScha";i:8707;s:4:"PrRe";i:8708;s:8:"SchUnabr";i:8709;s:6:"CoEmMi";i:8710;s:6:"HypeSh";i:8711;s:5:"FuUna";i:8712;s:10:"RabiSanjTr";i:8713;s:13:"ExamiPredUnsh";i:8714;s:12:"InflaNaSchop";i:8715;s:13:"BoldDoliUnpla";i:8716;s:6:"PaUntw";i:8717;s:4:"Tasi";i:8718;s:3:"Cat";i:8719;s:15:"CountMerisSpott";i:8720;s:3:"Sco";i:8721;s:9:"ManPuWhau";i:8722;s:9:"BlocCapri";i:8723;s:9:"BakinLaLi";i:8724;s:9:"ScreStran";i:8725;s:5:"Thick";i:8726;s:10:"DunkPePost";i:8727;s:9:"SkippStim";i:8728;s:4:"Subr";i:8729;s:5:"CaDer";i:8730;s:5:"MarTe";i:8731;s:5:"Terna";i:8732;s:10:"TelThUnver";i:8733;s:6:"NuclPn";i:8734;s:3:"Vim";i:8735;s:4:"Teac";i:8736;s:5:"PaUno";i:8737;s:11:"CompoDiShru";i:8738;s:6:"MonUnr";i:8739;s:6:"SeSwee";i:8740;s:7:"ErgaPol";i:8741;s:11:"EmbOveStrid";i:8742;s:12:"ArsonPrRepud";i:8743;s:9:"PaliProvi";i:8744;s:9:"MopinOxyp";i:8745;s:9:"GobKentUn";i:8746;s:12:"HypeOdontSpi";i:8747;s:12:"ChiRefoUnthr";i:8748;s:7:"PePress";i:8749;s:11:"HiPeriUndep";i:8750;s:12:"LieutSeToywo";i:8751;s:4:"Gunp";i:8752;s:9:"GuSchTele";i:8753;s:9:"DrumMulti";i:8754;s:8:"InflPray";i:8755;s:9:"PrebTubVi";i:8756;s:9:"DeturEnfi";i:8757;s:7:"KishaWe";i:8758;s:12:"MicPapuThala";i:8759;s:5:"Univa";i:8760;s:6:"PruUrx";i:8761;s:5:"Meato";i:8762;s:5:"SiUro";i:8763;s:11:"PterSnarTeb";i:8764;s:9:"MiscoMySy";i:8765;s:10:"ScotcSubVe";i:8766;s:4:"Unwe";i:8767;s:6:"MasPol";i:8768;s:3:"Pra";i:8769;s:5:"SheWe";i:8770;s:12:"NandSeasoWat";i:8771;s:8:"ImpSnake";i:8772;s:9:"HaubeOver";i:8773;s:11:"NaRetroUnma";i:8774;s:7:"SlSombr";i:8775;s:4:"ExTe";i:8776;s:10:"ReducSancy";i:8777;s:6:"CrVent";i:8778;s:5:"Death";i:8779;s:5:"EmTri";i:8780;s:4:"Bric";i:8781;s:5:"Paron";i:8782;s:10:"MyzOmmiaRe";i:8783;s:14:"ChirThereWallo";i:8784;s:10:"ProceUnUnt";i:8785;s:10:"ConSchapSu";i:8786;s:7:"GymPrUn";i:8787;s:7:"PrimiTr";i:8788;s:9:"OitiTraff";i:8789;s:4:"Mini";i:8790;s:10:"RegeStoiZy";i:8791;s:15:"CategInhabResth";i:8792;s:10:"DipEnsHexo";i:8793;s:9:"PlaPsTana";i:8794;s:10:"NontrTiUnd";i:8795;s:7:"CutirMe";i:8796;s:9:"JamdSubtu";i:8797;s:7:"PredoUn";i:8798;s:11:"BenzoHyVoca";i:8799;s:7:"ChDuHus";i:8800;s:12:"TsarUntuVuls";i:8801;s:9:"PrStTrich";i:8802;s:8:"UnbudVer";i:8803;s:6:"SanaUn";i:8804;s:7:"MoScint";i:8805;s:7:"CaDaFac";i:8806;s:8:"AmbCarpo";i:8807;s:10:"KukSipinUn";i:8808;s:7:"SaUnVen";i:8809;s:13:"PentSubbUnthi";i:8810;s:5:"Unbea";i:8811;s:6:"OrgiPe";i:8812;s:7:"ReScSle";i:8813;s:4:"Whis";i:8814;s:11:"DexPalUnher";i:8815;s:4:"Vaga";i:8816;s:4:"Sins";i:8817;s:4:"PhRe";i:8818;s:7:"KatUnsw";i:8819;s:8:"PenSynov";i:8820;s:9:"QuerRxUnl";i:8821;s:3:"Rac";i:8822;s:6:"OstrUb";i:8823;s:7:"GiMoMon";i:8824;s:5:"ReUnb";i:8825;s:5:"Moral";i:8826;s:14:"PhantUrostVajr";i:8827;s:13:"NoncUnfriUnto";i:8828;s:6:"HarPin";i:8829;s:9:"PeSpaTrim";i:8830;s:10:"CollIrToby";i:8831;s:10:"StyTroUnsl";i:8832;s:6:"LuckPh";i:8833;s:5:"Resou";i:8834;s:8:"MatriPap";i:8835;s:4:"Sugg";i:8836;s:3:"Pos";i:8837;s:10:"ReSquamUnh";i:8838;s:8:"HonInves";i:8839;s:6:"ScreSu";i:8840;s:4:"Whel";i:8841;s:5:"InWag";i:8842;s:9:"MembUnfil";i:8843;s:6:"PrUnth";i:8844;s:14:"HierSubnuUnent";i:8845;s:10:"PogRetinUn";i:8846;s:8:"TidedWit";i:8847;s:9:"EphorSnob";i:8848;s:12:"KinetLopeMit";i:8849;s:10:"NoRaspaSub";i:8850;s:11:"AmaMisSarga";i:8851;s:7:"SuThUns";i:8852;s:13:"DefleThreYeme";i:8853;s:3:"Fas";i:8854;s:4:"Viha";i:8855;s:2:"Kh";i:8856;s:10:"GirJudiOxy";i:8857;s:10:"NeurNonPro";i:8858;s:9:"HydroUnco";i:8859;s:5:"Forec";i:8860;s:7:"EmbExub";i:8861;s:5:"Unpho";i:8862;s:13:"AristPreseTra";i:8863;s:8:"PaSyntUn";i:8864;s:7:"HiPledg";i:8865;s:8:"PostUnze";i:8866;s:10:"MallaUngui";i:8867;s:12:"DrOceanUncor";i:8868;s:6:"CalTur";i:8869;s:3:"Vul";i:8870;s:3:"Ult";i:8871;s:8:"RecTitul";i:8872;s:6:"DilaEa";i:8873;s:7:"OnesWhe";i:8874;s:10:"PlSeroTolu";i:8875;s:7:"RecoSoc";i:8876;s:4:"Leuc";i:8877;s:10:"CaninEnPro";i:8878;s:11:"DisStrTrans";i:8879;s:4:"Pani";i:8880;s:6:"PrSten";i:8881;s:4:"Misc";i:8882;s:6:"NoSemi";i:8883;s:13:"GannPodoSlipo";i:8884;s:5:"MyrUn";i:8885;s:5:"Helio";i:8886;s:10:"MonVerniWa";i:8887;s:4:"Gyno";i:8888;s:7:"InnWint";i:8889;s:11:"CeroShoUnav";i:8890;s:8:"PushSiUn";i:8891;s:8:"DioNeSup";i:8892;s:4:"AuTh";i:8893;s:7:"FeHyPyr";i:8894;s:9:"MePamiUnl";i:8895;s:7:"MePikey";i:8896;s:5:"MacUn";i:8897;s:9:"PoleUnexc";i:8898;s:9:"GeocViper";i:8899;s:9:"JointUnUr";i:8900;s:9:"BuCheTigx";i:8901;s:5:"Ochra";i:8902;s:9:"NjaNoStro";i:8903;s:12:"CnemiGrUnreq";i:8904;s:9:"MellRamSc";i:8905;s:11:"InPistoSubr";i:8906;s:6:"ElamKe";i:8907;s:9:"CounDiagr";i:8908;s:8:"CorRevil";i:8909;s:11:"PurSuoUrini";i:8910;s:3:"Sma";i:8911;s:8:"GralHudd";i:8912;s:10:"BurgaGossi";i:8913;s:8:"FimbIdeo";i:8914;s:5:"Viril";i:8915;s:13:"JuverPredeRep";i:8916;s:7:"PhysiUn";i:8917;s:7:"NiTrans";i:8918;s:11:"IsPrickTrol";i:8919;s:7:"GoOment";i:8920;s:9:"TectUnfus";i:8921;s:10:"OvProtUnen";i:8922;s:5:"LeTre";i:8923;s:4:"Vaul";i:8924;s:8:"PhenRecu";i:8925;s:7:"SaTwelv";i:8926;s:5:"Trypt";i:8927;s:5:"Schiz";i:8928;s:6:"FeNonc";i:8929;s:12:"PolarScirSer";i:8930;s:8:"PraseTin";i:8931;s:10:"OfTattlWis";i:8932;s:4:"ShUn";i:8933;s:11:"GiNemeVicar";i:8934;s:7:"FotUnab";i:8935;s:6:"HeMiOr";i:8936;s:13:"FanxLegatUnid";i:8937;s:3:"Med";i:8938;s:14:"EarnScoroSlaye";i:8939;s:7:"GujrSci";i:8940;s:5:"VeViv";i:8941;s:5:"MeaUn";i:8942;s:6:"PtSpor";i:8943;s:6:"MoisSp";i:8944;s:10:"MopinOtViv";i:8945;s:12:"ExognPanoSpo";i:8946;s:7:"HedSuff";i:8947;s:7:"FifteRe";i:8948;s:4:"Sack";i:8949;s:9:"PiRasSerr";i:8950;s:3:"Vam";i:8951;s:11:"EnFlinPertu";i:8952;s:5:"Vexed";i:8953;s:5:"HenSu";i:8954;s:5:"Picad";i:8955;s:10:"CuirReTusc";i:8956;s:11:"PinPreaSeco";i:8957;s:14:"LaddePapaiUrey";i:8958;s:11:"GirlMoPhoen";i:8959;s:5:"Subli";i:8960;s:9:"UnjaVerti";i:8961;s:9:"ReUnUndes";i:8962;s:4:"PsTe";i:8963;s:7:"ParaWhi";i:8964;s:9:"LetxRetou";i:8965;s:9:"PinfeRass";i:8966;s:12:"RacReadStyle";i:8967;s:7:"TriclUn";i:8968;s:7:"SauSpre";i:8969;s:10:"IncoRhyUnc";i:8970;s:9:"HeftiUtra";i:8971;s:10:"ForSeasoUn";i:8972;s:15:"KorunPlateSynus";i:8973;s:10:"ForHappePo";i:8974;s:9:"SeSquZoon";i:8975;s:12:"HypocOriPent";i:8976;s:11:"SheTanUltra";i:8977;s:8:"OctaUpca";i:8978;s:13:"MuzhiPleniTip";i:8979;s:10:"MiscPhoeSa";i:8980;s:4:"Quan";i:8981;s:9:"NariTricl";i:8982;s:6:"InsTro";i:8983;s:13:"CantoPontoSub";i:8984;s:12:"MirReciSavan";i:8985;s:9:"LeNoVivif";i:8986;s:4:"Paid";i:8987;s:9:"OversTagu";i:8988;s:7:"MyVaunt";i:8989;s:8:"OversStr";i:8990;s:8:"ThyUndup";i:8991;s:10:"LaOuthoRou";i:8992;s:4:"Lepi";i:8993;s:12:"NoUnkinUnmut";i:8994;s:5:"Sermo";i:8995;s:5:"StiSt";i:8996;s:4:"Neos";i:8997;s:8:"IsothNon";i:8998;s:8:"SluiWind";i:8999;s:7:"FelicPa";i:9000;s:7:"UndisUn";i:9001;s:8:"NeoSupra";i:9002;s:10:"JaLontaTus";i:9003;s:5:"Outpa";i:9004;s:12:"HeteSubSupra";i:9005;s:5:"Nephe";i:9006;s:9:"LeProSqua";i:9007;s:10:"CountLofti";i:9008;s:12:"DiagSpeciTiw";i:9009;s:4:"Wien";i:9010;s:8:"PhoniSup";i:9011;s:10:"HardePolyo";i:9012;s:7:"RaRecha";i:9013;s:11:"CydGalInobs";i:9014;s:3:"Yah";i:9015;s:8:"SiStTorn";i:9016;s:4:"Anab";i:9017;s:7:"HolaWen";i:9018;s:7:"CaGusSt";i:9019;s:8:"BinPhoPl";i:9020;s:12:"TracUnaffUnc";i:9021;s:12:"GuHeremOvige";i:9022;s:12:"StimyUnVirus";i:9023;s:8:"MultUnfa";i:9024;s:8:"ChoComCo";i:9025;s:8:"ErotPres";i:9026;s:6:"HypSul";i:9027;s:8:"PrSwUnen";i:9028;s:9:"PeThamViv";i:9029;s:9:"ThyrUnder";i:9030;s:10:"PorphPrRiv";i:9031;s:10:"SchooSubju";i:9032;s:5:"SnaUn";i:9033;s:10:"LamasOvers";i:9034;s:7:"HyUnwis";i:9035;s:3:"Rho";i:9036;s:12:"FrigPawnSani";i:9037;s:9:"SnUnUntar";i:9038;s:11:"BundEnigWhe";i:9039;s:9:"PrusRebUn";i:9040;s:10:"CuOverPutt";i:9041;s:6:"EphyNo";i:9042;s:4:"Stap";i:9043;s:5:"PasPo";i:9044;s:5:"EnwGa";i:9045;s:5:"ExtZy";i:9046;s:7:"RoofwSm";i:9047;s:6:"InfMal";i:9048;s:9:"BriDeVale";i:9049;s:7:"PhasSna";i:9050;s:7:"CaExSpr";i:9051;s:8:"ObiRiSla";i:9052;s:8:"ThuUnpre";i:9053;s:6:"PleUri";i:9054;s:7:"FoSemei";i:9055;s:5:"ImpPh";i:9056;s:13:"RootShemuSpec";i:9057;s:5:"KoSem";i:9058;s:11:"MetaNaphPer";i:9059;s:10:"PolReTrans";i:9060;s:7:"MitPres";i:9061;s:7:"NonspOn";i:9062;s:12:"PancPoinUnre";i:9063;s:11:"GerLactTrin";i:9064;s:4:"ObQu";i:9065;s:5:"Saliv";i:9066;s:4:"Symb";i:9067;s:7:"LopSlee";i:9068;s:8:"PropTaip";i:9069;s:12:"LacteMediSwa";i:9070;s:13:"PeacPreteStyl";i:9071;s:6:"RaWorr";i:9072;s:10:"InspiSacro";i:9073;s:11:"CouFactoSmo";i:9074;s:7:"CoelFat";i:9075;s:8:"StagnSym";i:9076;s:5:"SchTr";i:9077;s:4:"Myth";i:9078;s:3:"Pap";i:9079;s:11:"ReSubcThumb";i:9080;s:4:"InWa";i:9081;s:4:"Coen";i:9082;s:6:"RecThy";i:9083;s:10:"KumbiPhPos";i:9084;s:10:"StradVedan";i:9085;s:10:"LiturSubTi";i:9086;s:12:"PediSubaSupp";i:9087;s:3:"Eff";i:9088;s:14:"InterNeogPreab";i:9089;s:6:"CoPrUn";i:9090;s:8:"InPacTro";i:9091;s:5:"AlUnd";i:9092;s:6:"IsoPen";i:9093;s:6:"PyWave";i:9094;s:9:"OculPithi";i:9095;s:9:"RecSithUr";i:9096;s:6:"ReTant";i:9097;s:7:"TheraTr";i:9098;s:9:"ReRespeWh";i:9099;s:6:"DvaTri";i:9100;s:7:"MiVolca";i:9101;s:11:"PaniPelvUne";i:9102;s:12:"CoutDonSpoon";i:9103;s:4:"DaIr";i:9104;s:12:"OvervScUnder";i:9105;s:7:"OuPolys";i:9106;s:13:"GratiPhyTitan";i:9107;s:12:"ScStaliTipma";i:9108;s:11:"RoVeheVelif";i:9109;s:12:"NiccoPostWas";i:9110;s:7:"MendiPr";i:9111;s:9:"MareUnpVe";i:9112;s:6:"OliSal";i:9113;s:6:"AmpOct";i:9114;s:6:"HeToad";i:9115;s:10:"NapOutpWyc";i:9116;s:10:"CaMerchSup";i:9117;s:5:"Telod";i:9118;s:3:"Cyc";i:9119;s:8:"PraepVol";i:9120;s:4:"Syre";i:9121;s:3:"Ble";i:9122;s:6:"FlSubj";i:9123;s:13:"DelMaledUpait";i:9124;s:7:"PopinUn";i:9125;s:9:"DemiEntSe";i:9126;s:12:"FiltRamaRheo";i:9127;s:9:"RacUntWit";i:9128;s:2:"Pf";i:9129;s:5:"FloPi";i:9130;s:14:"ProinSalaWinge";i:9131;s:11:"GodSmashUnl";i:9132;s:14:"MartMicroNeuro";i:9133;s:6:"PaikUn";i:9134;s:4:"ErUs";i:9135;s:6:"UnUnch";i:9136;s:7:"ConUred";i:9137;s:7:"TaysUng";i:9138;s:7:"LacMyxa";i:9139;s:10:"GouSerXylo";i:9140;s:11:"DeIpomeUnre";i:9141;s:11:"HomoJuTatsa";i:9142;s:8:"TetraUnd";i:9143;s:8:"BragPici";i:9144;s:13:"CompDissQuart";i:9145;s:10:"FuNonvStra";i:9146;s:5:"RaRid";i:9147;s:9:"RisTheUnd";i:9148;s:11:"DendrHySand";i:9149;s:8:"ChrJuWor";i:9150;s:10:"CalDemicSn";i:9151;s:5:"UnaUn";i:9152;s:8:"OppPolTu";i:9153;s:4:"PrTr";i:9154;s:8:"NoncoWor";i:9155;s:7:"BookHyp";i:9156;s:12:"BakesCrEpaul";i:9157;s:5:"Tarum";i:9158;s:5:"Helli";i:9159;s:7:"ImpSpon";i:9160;s:5:"Unpri";i:9161;s:13:"OversPasVerst";i:9162;s:7:"CaPaUnr";i:9163;s:10:"LuMonNeckm";i:9164;s:9:"InSerdaSt";i:9165;s:11:"SemicSucVej";i:9166;s:5:"Portu";i:9167;s:5:"Whiti";i:9168;s:8:"PyopSann";i:9169;s:4:"GyMa";i:9170;s:13:"InduInterMega";i:9171;s:5:"Mykis";i:9172;s:4:"Phot";i:9173;s:12:"InamOssuaSca";i:9174;s:12:"SuTweenZoeal";i:9175;s:5:"SqWor";i:9176;s:8:"RepTarge";i:9177;s:7:"OpUntri";i:9178;s:8:"QueeStSw";i:9179;s:8:"DiopThul";i:9180;s:7:"MyitStr";i:9181;s:8:"SemiTrap";i:9182;s:9:"SluStaSty";i:9183;s:5:"Neorn";i:9184;s:5:"Socia";i:9185;s:6:"LameUn";i:9186;s:5:"Weigh";i:9187;s:5:"ByMor";i:9188;s:9:"MinceSaun";i:9189;s:11:"OvUnceUnper";i:9190;s:11:"ForecHaemPh";i:9191;s:10:"IndusLoSpi";i:9192;s:5:"PanSc";i:9193;s:5:"Vomit";i:9194;s:9:"MazucPoly";i:9195;s:8:"JeProVil";i:9196;s:5:"Swith";i:9197;s:4:"Wave";i:9198;s:9:"OrgaTramf";i:9199;s:12:"InsePrelSuwa";i:9200;s:8:"LoggiOve";i:9201;s:13:"PetPleurSolpu";i:9202;s:9:"SclerTrif";i:9203;s:4:"Wrea";i:9204;s:4:"Dysc";i:9205;s:13:"LapidNonThack";i:9206;s:10:"CumKinsmSh";i:9207;s:8:"GaIdeKas";i:9208;s:12:"BrokaGuiltSy";i:9209;s:11:"BrothFaLoca";i:9210;s:10:"LabPlUncon";i:9211;s:7:"EquimMa";i:9212;s:11:"DiHomoPinak";i:9213;s:5:"Holop";i:9214;s:7:"OliOxyt";i:9215;s:6:"GoWenr";i:9216;s:11:"BaCompTerna";i:9217;s:6:"LymMyt";i:9218;s:8:"CountMon";i:9219;s:9:"ShramTack";i:9220;s:10:"PreScToher";i:9221;s:4:"Wool";i:9222;s:6:"HaPeSo";i:9223;s:10:"EnHypeMarq";i:9224;s:9:"NonPaResu";i:9225;s:10:"SupThyUntu";i:9226;s:9:"FetteFoPa";i:9227;s:12:"MayanThrUnte";i:9228;s:7:"PeerlUn";i:9229;s:10:"ChrysFruTi";i:9230;s:8:"OverbVej";i:9231;s:6:"CirCri";i:9232;s:10:"PuinaTabet";i:9233;s:5:"Pulkx";i:9234;s:7:"UnpreUn";i:9235;s:4:"JaRa";i:9236;s:4:"Disa";i:9237;s:11:"ItinOculoOp";i:9238;s:8:"SophTeet";i:9239;s:4:"Text";i:9240;s:12:"PrehPunnRivu";i:9241;s:11:"ImpSupSyner";i:9242;s:10:"DiscoKensp";i:9243;s:4:"Nond";i:9244;s:6:"MariSe";i:9245;s:7:"BalafHa";i:9246;s:8:"PoPreTea";i:9247;s:8:"LuxNapSc";i:9248;s:5:"Bookk";i:9249;s:5:"AspVa";i:9250;s:10:"MonoNoSkew";i:9251;s:11:"BuDirgUnenv";i:9252;s:12:"LumbTrochVir";i:9253;s:5:"Escha";i:9254;s:7:"NarPeti";i:9255;s:9:"ThorUnmas";i:9256;s:13:"CollFarleUnqu";i:9257;s:8:"FerroPol";i:9258;s:6:"ReimSa";i:9259;s:5:"Robus";i:9260;s:10:"DorSpSpong";i:9261;s:9:"SubdSuper";i:9262;s:7:"PhSalic";i:9263;s:9:"PaulRecor";i:9264;s:7:"IliacUn";i:9265;s:8:"ExPeSupe";i:9266;s:10:"ForehIntRo";i:9267;s:12:"CoHeterNoset";i:9268;s:9:"ScumlStan";i:9269;s:5:"Bulbo";i:9270;s:9:"PateQuVis";i:9271;s:11:"FaradGhoMes";i:9272;s:12:"MurraNidOste";i:9273;s:10:"NeyanPinke";i:9274;s:10:"BewilDoOve";i:9275;s:5:"Prefe";i:9276;s:10:"GasManaPhr";i:9277;s:7:"DiGaPho";i:9278;s:5:"Saltw";i:9279;s:12:"HypeLaiLeuca";i:9280;s:13:"CenEgomiReocc";i:9281;s:8:"HeOmiRet";i:9282;s:14:"CottoGaumPrima";i:9283;s:4:"Flec";i:9284;s:9:"PowelToUn";i:9285;s:3:"Net";i:9286;s:4:"MoPi";i:9287;s:7:"DidHowa";i:9288;s:4:"View";i:9289;s:7:"HadLuUn";i:9290;s:6:"ConvIr";i:9291;s:4:"Xyri";i:9292;s:5:"MalPe";i:9293;s:11:"ScSperTetra";i:9294;s:7:"PreSigh";i:9295;s:4:"Suan";i:9296;s:6:"EquSta";i:9297;s:11:"CoptMitSubc";i:9298;s:10:"EuhModifRo";i:9299;s:9:"RepinStab";i:9300;s:9:"SpleUnipo";i:9301;s:5:"PhoPh";i:9302;s:8:"SlTriWha";i:9303;s:4:"HoSp";i:9304;s:5:"Bystr";i:9305;s:14:"NonsuOxycScorb";i:9306;s:11:"LympSkaffWa";i:9307;s:12:"MonosMudProt";i:9308;s:10:"KinMenViro";i:9309;s:3:"Enz";i:9310;s:8:"SupUnfis";i:9311;s:10:"MorpUnUret";i:9312;s:12:"OveRevisUnre";i:9313;s:9:"NoPauldRe";i:9314;s:5:"ObOld";i:9315;s:12:"FlyflPrUphol";i:9316;s:9:"PetrTouri";i:9317;s:5:"Vesti";i:9318;s:4:"Xiph";i:9319;s:11:"FlouSuzThia";i:9320;s:5:"Tzapo";i:9321;s:9:"PercyWate";i:9322;s:7:"PiSeSpa";i:9323;s:9:"BlizzSupe";i:9324;s:10:"MyopProPse";i:9325;s:9:"TotUnUnre";i:9326;s:8:"LepSubTh";i:9327;s:5:"NonRe";i:9328;s:9:"SatScSpon";i:9329;s:5:"FlVir";i:9330;s:6:"GoIgPu";i:9331;s:10:"PuiSuUnive";i:9332;s:5:"DyMye";i:9333;s:9:"CarHuKhok";i:9334;s:7:"OxoniWe";i:9335;s:7:"PaPerXa";i:9336;s:12:"AteloChPheno";i:9337;s:4:"Jagr";i:9338;s:12:"NewTheaUndro";i:9339;s:7:"DisInfe";i:9340;s:6:"HemMas";i:9341;s:6:"KhOcel";i:9342;s:4:"TaTw";i:9343;s:12:"RedawStUngro";i:9344;s:14:"BuffwImmeOctog";i:9345;s:12:"PardoPhReste";i:9346;s:9:"OrnitSynd";i:9347;s:5:"Suspi";i:9348;s:9:"PancaProv";i:9349;s:6:"PhylVi";i:9350;s:4:"HoIn";i:9351;s:8:"CheNonbe";i:9352;s:8:"MicPraUn";i:9353;s:6:"SuUnes";i:9354;s:5:"StiTy";i:9355;s:7:"SlUnska";i:9356;s:4:"VeWh";i:9357;s:6:"LupiPr";i:9358;s:10:"KaRufVoidl";i:9359;s:7:"RaRhina";i:9360;s:8:"FungSubm";i:9361;s:10:"PatmiPerdu";i:9362;s:3:"Tog";i:9363;s:12:"CanCoalSlips";i:9364;s:10:"DrDubioTri";i:9365;s:3:"Spu";i:9366;s:5:"There";i:9367;s:10:"HomeLecWoo";i:9368;s:5:"BloFo";i:9369;s:7:"ParsPho";i:9370;s:13:"BlizCeroQuitr";i:9371;s:4:"Surr";i:9372;s:10:"QuakReorSu";i:9373;s:13:"DressRhySteno";i:9374;s:5:"OveTo";i:9375;s:5:"PnPol";i:9376;s:10:"MicroThecl";i:9377;s:11:"EchelRuScan";i:9378;s:7:"FluorPr";i:9379;s:4:"Some";i:9380;s:4:"Rhyn";i:9381;s:6:"ChEuro";i:9382;s:13:"PrenuRoariTri";i:9383;s:8:"GuiPosse";i:9384;s:10:"EnsInlySer";i:9385;s:9:"EffUnUnsp";i:9386;s:10:"DoHaluTang";i:9387;s:12:"LeNomadRootw";i:9388;s:10:"HorSincStr";i:9389;s:8:"IntKePre";i:9390;s:10:"CastrFubLo";i:9391;s:5:"Theur";i:9392;s:10:"LubOmeReto";i:9393;s:11:"PreSubopUss";i:9394;s:7:"OrOtWai";i:9395;s:7:"GoverIn";i:9396;s:6:"DrHowk";i:9397;s:6:"ExigPo";i:9398;s:5:"Teler";i:9399;s:9:"MarSpUnbr";i:9400;s:5:"Unrec";i:9401;s:6:"MuNeot";i:9402;s:8:"LeSeaSom";i:9403;s:6:"GaJaco";i:9404;s:6:"CoDisp";i:9405;s:6:"OceTub";i:9406;s:7:"StomUnl";i:9407;s:4:"Stea";i:9408;s:8:"PamShore";i:9409;s:8:"PolysSof";i:9410;s:9:"OpporPres";i:9411;s:5:"ConUn";i:9412;s:4:"Imbo";i:9413;s:11:"PoliStTelev";i:9414;s:11:"ChrysHemiRa";i:9415;s:9:"DivPopPse";i:9416;s:6:"PhUnid";i:9417;s:12:"MiniSomThlas";i:9418;s:5:"ShaSo";i:9419;s:8:"SataTryp";i:9420;s:5:"Visce";i:9421;s:4:"Decl";i:9422;s:6:"DiRoga";i:9423;s:5:"Leafl";i:9424;s:8:"BalStagn";i:9425;s:6:"PrefSc";i:9426;s:6:"LithUm";i:9427;s:8:"IntTruss";i:9428;s:7:"ChlamEx";i:9429;s:5:"Shedh";i:9430;s:8:"CoelOvan";i:9431;s:8:"ProUnder";i:9432;s:8:"EmbryUnk";i:9433;s:4:"Pedo";i:9434;s:7:"MurksWi";i:9435;s:9:"PhiliShSl";i:9436;s:5:"SupTa";i:9437;s:10:"DropoHaLou";i:9438;s:8:"BrChylUn";i:9439;s:10:"PrTetUnpre";i:9440;s:8:"PreScowb";i:9441;s:13:"LimiTortZygon";i:9442;s:9:"HansSpiWa";i:9443;s:7:"DiNonUl";i:9444;s:7:"MalUnco";i:9445;s:9:"IncoStave";i:9446;s:4:"Muni";i:9447;s:10:"PsychTurbi";i:9448;s:5:"Nonig";i:9449;s:9:"CorParPos";i:9450;s:7:"GrUrrho";i:9451;s:5:"Priva";i:9452;s:3:"Gue";i:9453;s:5:"TreUn";i:9454;s:8:"GirnyNom";i:9455;s:9:"GeInPavag";i:9456;s:5:"Poeta";i:9457;s:7:"PrerUro";i:9458;s:8:"PalSacch";i:9459;s:9:"CutliSesq";i:9460;s:4:"Sine";i:9461;s:8:"EasPedun";i:9462;s:11:"DotaNonpSic";i:9463;s:13:"CotyStudiTher";i:9464;s:7:"InTripu";i:9465;s:5:"SteUr";i:9466;s:8:"ConvSeab";i:9467;s:3:"Yor";i:9468;s:4:"Prov";i:9469;s:5:"ChiHe";i:9470;s:7:"NePaPre";i:9471;s:5:"Seque";i:9472;s:10:"EmeHaUpswa";i:9473;s:9:"SorceUnde";i:9474;s:11:"StrTacklUnw";i:9475;s:5:"Johan";i:9476;s:7:"PaSpira";i:9477;s:11:"UncaUncoUnd";i:9478;s:5:"CoGor";i:9479;s:8:"BarrMaWi";i:9480;s:4:"TuXy";i:9481;s:6:"MinPro";i:9482;s:6:"MetUnm";i:9483;s:5:"PrWei";i:9484;s:14:"TatoUnbedWovex";i:9485;s:15:"HuronOpeidPetas";i:9486;s:8:"ThesTool";i:9487;s:8:"MalUnbro";i:9488;s:4:"Tars";i:9489;s:7:"DiGrain";i:9490;s:9:"OgOvRiver";i:9491;s:10:"NonclVexer";i:9492;s:4:"Morg";i:9493;s:10:"BushiParTo";i:9494;s:5:"CanWh";i:9495;s:14:"KerriLampMetap";i:9496;s:11:"MayhaUnpWil";i:9497;s:8:"CtenoNin";i:9498;s:10:"CoDisepSta";i:9499;s:5:"Vitri";i:9500;s:5:"Lecit";i:9501;s:12:"HaqPatriSulp";i:9502;s:5:"Loyal";i:9503;s:12:"KeraRuniTene";i:9504;s:3:"Sne";i:9505;s:13:"HemaShadZeall";i:9506;s:5:"Undif";i:9507;s:8:"DisfrSqu";i:9508;s:6:"SeTrip";i:9509;s:14:"EumitNetbrSche";i:9510;s:7:"MoWentl";i:9511;s:11:"OveProfUnme";i:9512;s:9:"DetesMyZi";i:9513;s:4:"Napa";i:9514;s:10:"DeprMetUnt";i:9515;s:12:"InduParUnrec";i:9516;s:10:"PeachUnthe";i:9517;s:5:"PssTu";i:9518;s:5:"DoTom";i:9519;s:5:"Gavia";i:9520;s:8:"DifDumEp";i:9521;s:9:"NonwaShor";i:9522;s:9:"NeStaUnde";i:9523;s:6:"TestUn";i:9524;s:8:"DemZonoc";i:9525;s:6:"InmUnl";i:9526;s:8:"RedUrino";i:9527;s:9:"StenUsurp";i:9528;s:4:"TaZi";i:9529;s:4:"Phys";i:9530;s:12:"HemoIxoSulph";i:9531;s:6:"HoonSh";i:9532;s:8:"RendeVet";i:9533;s:7:"ScrStun";i:9534;s:6:"NonPat";i:9535;s:5:"Rundi";i:9536;s:9:"GyOmTankm";i:9537;s:5:"Ravel";i:9538;s:4:"MeSe";i:9539;s:10:"LiSeymShel";i:9540;s:14:"CeltEnsnaJesti";i:9541;s:3:"Wal";i:9542;s:14:"InwrTressUnsap";i:9543;s:5:"Remen";i:9544;s:8:"DaUndWit";i:9545;s:8:"PhalaRec";i:9546;s:4:"Vicx";i:9547;s:3:"Pel";i:9548;s:4:"MoPo";i:9549;s:5:"Profr";i:9550;s:8:"JustiRed";i:9551;s:5:"IdThr";i:9552;s:5:"ExMet";i:9553;s:5:"Gleno";i:9554;s:13:"IndeParaTerte";i:9555;s:4:"Uniq";i:9556;s:15:"FilipSourhUnhur";i:9557;s:12:"EuhyoRopeUnr";i:9558;s:12:"FervScapuTec";i:9559;s:7:"IrrKisl";i:9560;s:6:"DefNon";i:9561;s:7:"OmniShe";i:9562;s:6:"OidSup";i:9563;s:10:"MetanSpWin";i:9564;s:5:"Torso";i:9565;s:7:"PrRoShi";i:9566;s:9:"OutpRicke";i:9567;s:7:"InWarmu";i:9568;s:9:"EpaPosPro";i:9569;s:11:"CroInviOvof";i:9570;s:5:"DemRa";i:9571;s:5:"Sacka";i:9572;s:4:"SiSy";i:9573;s:4:"Movi";i:9574;s:5:"Neore";i:9575;s:6:"AmWatc";i:9576;s:6:"ScleSm";i:9577;s:11:"BipaFreWalk";i:9578;s:5:"ArtSh";i:9579;s:8:"PaPikRoo";i:9580;s:13:"ImporTodeVari";i:9581;s:5:"Upste";i:9582;s:10:"MesVolWugg";i:9583;s:7:"CharmPa";i:9584;s:12:"PennWashwWur";i:9585;s:7:"UnWeath";i:9586;s:4:"TrUr";i:9587;s:9:"NervResti";i:9588;s:7:"CruSeSp";i:9589;s:7:"NonnaTe";i:9590;s:5:"Uncal";i:9591;s:11:"HyKatciUnin";i:9592;s:5:"Katun";i:9593;s:11:"ThemsUnUnde";i:9594;s:5:"UndVa";i:9595;s:14:"GapyxIliaMesen";i:9596;s:5:"StUnj";i:9597;s:4:"TiUn";i:9598;s:12:"CreatDecUnfa";i:9599;s:8:"DrumxIrr";i:9600;s:12:"HawPennSceno";i:9601;s:7:"InLitPr";i:9602;s:11:"MisbRumpxTh";i:9603;s:12:"ForesIaJamni";i:9604;s:6:"QuaTal";i:9605;s:13:"HenhPerrUnsto";i:9606;s:7:"ShStere";i:9607;s:7:"SterVan";i:9608;s:10:"NeotPlSwar";i:9609;s:5:"UnWac";i:9610;s:10:"BrFrostInd";i:9611;s:8:"BarbNoNu";i:9612;s:9:"FleKnoPho";i:9613;s:8:"MayPaSub";i:9614;s:8:"ScrawUns";i:9615;s:6:"SeThev";i:9616;s:11:"FiresFoWood";i:9617;s:10:"OvigePaSub";i:9618;s:13:"HolidIntelLuc";i:9619;s:13:"MonotOximaSen";i:9620;s:14:"GangSawhoWoman";i:9621;s:10:"HoImpTepeh";i:9622;s:5:"ImpMe";i:9623;s:8:"LuvaSpee";i:9624;s:9:"DeFolGoye";i:9625;s:12:"EboniMoXiphi";i:9626;s:5:"Milts";i:9627;s:5:"QuSte";i:9628;s:10:"QuisShepSu";i:9629;s:7:"ScatoSp";i:9630;s:9:"RadiSigge";i:9631;s:9:"LollRampa";i:9632;s:10:"JuluRehTri";i:9633;s:8:"CeliObPa";i:9634;s:3:"Scl";i:9635;s:6:"PhoPro";i:9636;s:7:"IrUnWhi";i:9637;s:4:"Whee";i:9638;s:12:"HepOutgrPost";i:9639;s:5:"TacWa";i:9640;s:9:"DulaPleas";i:9641;s:10:"PrPunkeTet";i:9642;s:5:"Presu";i:9643;s:7:"ReSepte";i:9644;s:11:"MicMorphRet";i:9645;s:6:"IcTurn";i:9646;s:7:"CoSlumb";i:9647;s:3:"Kho";i:9648;s:9:"MajPosSti";i:9649;s:6:"OveTyr";i:9650;s:9:"JuraUnali";i:9651;s:10:"HeterUpbur";i:9652;s:4:"Invo";i:9653;s:7:"ChaRuma";i:9654;s:5:"Otoco";i:9655;s:10:"FoHouUnrab";i:9656;s:7:"FumaRom";i:9657;s:10:"BriOnaxUnj";i:9658;s:7:"QuSecUn";i:9659;s:11:"IodaNaumUnc";i:9660;s:9:"PoRiTonsu";i:9661;s:9:"BridHespe";i:9662;s:7:"HeliSup";i:9663;s:11:"LefPoroReso";i:9664;s:5:"NinUn";i:9665;s:14:"LitasPrebPresb";i:9666;s:6:"SaUnUn";i:9667;s:10:"GrHidPsych";i:9668;s:9:"PreceRaRo";i:9669;s:13:"OverPittSadrx";i:9670;s:5:"Ignif";i:9671;s:8:"LighUnsa";i:9672;s:9:"OvUnYeast";i:9673;s:7:"DumFiMi";i:9674;s:4:"Scou";i:9675;s:7:"SexteWr";i:9676;s:5:"HieNo";i:9677;s:6:"CaMala";i:9678;s:4:"StUn";i:9679;s:6:"GrotUn";i:9680;s:5:"InSpi";i:9681;s:10:"PolycShama";i:9682;s:12:"ConEncepRest";i:9683;s:5:"PlaSm";i:9684;s:5:"Blatt";i:9685;s:14:"BilipCensuCont";i:9686;s:7:"DiscOve";i:9687;s:8:"InsLeaUn";i:9688;s:2:"Id";i:9689;s:6:"ShSqui";i:9690;s:10:"StatuTaxed";i:9691;s:7:"StUnale";i:9692;s:7:"PyVampi";i:9693;s:12:"PsyTranqUnco";i:9694;s:4:"Mise";i:9695;s:4:"Wayw";i:9696;s:7:"FleLoSp";i:9697;s:10:"InLixOvert";i:9698;s:9:"IsfahOrPu";i:9699;s:11:"BeniCacoUlt";i:9700;s:8:"FleTarra";i:9701;s:4:"DiOp";i:9702;s:4:"HyPo";i:9703;s:13:"NaumaOveRolle";i:9704;s:10:"TeartUncon";i:9705;s:5:"CirDa";i:9706;s:13:"ImploNondTurb";i:9707;s:10:"DyaSexTric";i:9708;s:10:"GaImpWeapo";i:9709;s:4:"Myas";i:9710;s:11:"OpSpadVolse";i:9711;s:5:"Olent";i:9712;s:11:"BiopLaevoSn";i:9713;s:8:"TagalWai";i:9714;s:8:"HeOxyVal";i:9715;s:4:"Infr";i:9716;s:9:"MicrUmbel";i:9717;s:9:"FaNeedlPe";i:9718;s:11:"LysisPseuUn";i:9719;s:10:"CadChroSib";i:9720;s:12:"GooSyncaTuft";i:9721;s:10:"QuaShodeUn";i:9722;s:5:"OveSt";i:9723;s:4:"Perl";i:9724;s:4:"HuPo";i:9725;s:13:"IntoParaSedim";i:9726;s:5:"Carce";i:9727;s:9:"KlMegaPyr";i:9728;s:12:"OrthSeeaWind";i:9729;s:9:"InsulRhin";i:9730;s:6:"MicRis";i:9731;s:15:"IsaacMediaUnmed";i:9732;s:8:"DeStSwim";i:9733;s:14:"KadayPhospPrea";i:9734;s:10:"MesaPrProb";i:9735;s:11:"PediPrPromo";i:9736;s:4:"Dawt";i:9737;s:11:"NariOrThean";i:9738;s:4:"Word";i:9739;s:10:"ExcitPassu";i:9740;s:7:"HoPoPre";i:9741;s:5:"Sampl";i:9742;s:9:"LePhilaUn";i:9743;s:8:"MalebOve";i:9744;s:9:"BoyaTrigi";i:9745;s:8:"DaDiGuai";i:9746;s:9:"DeerhLiQu";i:9747;s:9:"EpicSquee";i:9748;s:6:"PiSupp";i:9749;s:6:"SenSub";i:9750;s:9:"RemuSubba";i:9751;s:12:"HighlSophiSu";i:9752;s:9:"OptimSpor";i:9753;s:4:"Sech";i:9754;s:9:"AmCartCys";i:9755;s:9:"MushmOsPa";i:9756;s:5:"Decon";i:9757;s:4:"Sexh";i:9758;s:10:"ResisSaThu";i:9759;s:3:"Zec";i:9760;s:4:"Vill";i:9761;s:3:"Pod";i:9762;s:9:"OweSisWai";i:9763;s:11:"EmWassaWeig";i:9764;s:9:"HaPostTub";i:9765;s:4:"Stel";i:9766;s:8:"PePrScru";i:9767;s:6:"GastPr";i:9768;s:10:"TeretTrich";i:9769;s:7:"ResiSpu";i:9770;s:3:"Gre";i:9771;s:14:"CunjOversPneum";i:9772;s:8:"OdOzoUmi";i:9773;s:10:"ConfuCover";i:9774;s:9:"PaSubuUnb";i:9775;s:3:"Och";i:9776;s:6:"OvPorc";i:9777;s:11:"ReweSubvWin";i:9778;s:7:"FouUnfo";i:9779;s:12:"FluMicrRecre";i:9780;s:14:"RecruUnivUnspr";i:9781;s:10:"UnsavUnsea";i:9782;s:9:"HormoPiSe";i:9783;s:8:"SpheTwis";i:9784;s:5:"Thema";i:9785;s:13:"DidelTailUnwh";i:9786;s:5:"Marsi";i:9787;s:14:"InfrPleurPurre";i:9788;s:12:"MekomPreSync";i:9789;s:8:"EartNota";i:9790;s:6:"SoddWa";i:9791;s:3:"Obv";i:9792;s:14:"MarmNaphtPunis";i:9793;s:9:"PhillTaut";i:9794;s:12:"InomaNaPremi";i:9795;s:6:"PagaSa";i:9796;s:9:"LoMidleRe";i:9797;s:14:"MonotPsalTetar";i:9798;s:3:"Mum";i:9799;s:13:"PopuTheorWone";i:9800;s:5:"Worth";i:9801;s:6:"SkWhiz";i:9802;s:8:"DemEroHo";i:9803;s:5:"Soili";i:9804;s:7:"StoutTa";i:9805;s:11:"InteManShaf";i:9806;s:11:"DeyxSpTouri";i:9807;s:7:"GlagLac";i:9808;s:8:"RedaSauc";i:9809;s:11:"PseuTintiTo";i:9810;s:7:"NoachOa";i:9811;s:11:"HyPacinSnor";i:9812;s:4:"Ghee";i:9813;s:5:"Shive";i:9814;s:3:"Urv";i:9815;s:15:"SupraUnaptUnhis";i:9816;s:11:"BullwCrUnge";i:9817;s:11:"GuitaPredVa";i:9818;s:5:"Tarqu";i:9819;s:10:"InsecQuaTh";i:9820;s:11:"EnOverTrans";i:9821;s:11:"DelPeloPeti";i:9822;s:7:"DiFicLo";i:9823;s:4:"FoMa";i:9824;s:11:"InexObjPara";i:9825;s:5:"Stere";i:9826;s:7:"LaiSail";i:9827;s:10:"PrRiddScir";i:9828;s:7:"UnVinat";i:9829;s:7:"OvPileo";i:9830;s:7:"BliCuTr";i:9831;s:12:"ArchsHormoTo";i:9832;s:7:"ForMaTh";i:9833;s:5:"ConDi";i:9834;s:7:"HabblSy";i:9835;s:11:"BioSetulTra";i:9836;s:5:"Twigl";i:9837;s:12:"DarnHesteMed";i:9838;s:9:"ElFrShagb";i:9839;s:9:"DeSticUnc";i:9840;s:5:"Repug";i:9841;s:12:"PamSacroValo";i:9842;s:10:"ThrasUnmod";i:9843;s:6:"DidEco";i:9844;s:11:"AntiDolefRh";i:9845;s:8:"EucomSer";i:9846;s:6:"OxyhPo";i:9847;s:10:"MononPulmo";i:9848;s:4:"Urba";i:9849;s:11:"HolPhoUnshi";i:9850;s:11:"TiamUnguaUn";i:9851;s:6:"EnRoUn";i:9852;s:5:"Semia";i:9853;s:9:"DisbPyrTe";i:9854;s:4:"Geob";i:9855;s:8:"BeInSupe";i:9856;s:8:"DepreNon";i:9857;s:7:"PoWhoms";i:9858;s:4:"Pori";i:9859;s:12:"MisprPhUnarg";i:9860;s:3:"Oxy";i:9861;s:12:"CephaMaPanti";i:9862;s:9:"HyotPhala";i:9863;s:12:"InexUntVoidl";i:9864;s:13:"HendePolysVan";i:9865;s:11:"FusilInTast";i:9866;s:6:"UngUnv";i:9867;s:13:"CaleInterMors";i:9868;s:5:"HeTri";i:9869;s:8:"ElHaPrer";i:9870;s:9:"EcoUnUnwa";i:9871;s:10:"RefinRhTub";i:9872;s:6:"MallPl";i:9873;s:5:"Overm";i:9874;s:14:"EntraProfSynge";i:9875;s:8:"MonUnemb";i:9876;s:4:"Osci";i:9877;s:8:"SwoThink";i:9878;s:14:"OsiaOveriWimpl";i:9879;s:7:"BuHinny";i:9880;s:7:"SulUpgr";i:9881;s:5:"PlSma";i:9882;s:8:"SacchSir";i:9883;s:9:"GristRift";i:9884;s:8:"UncUppar";i:9885;s:10:"SnSubUntho";i:9886;s:4:"Loaf";i:9887;s:13:"PyrocSteekTax";i:9888;s:3:"Zac";i:9889;s:10:"OyeUnVioli";i:9890;s:12:"GimMyeliPyro";i:9891;s:10:"RhodSeliTe";i:9892;s:12:"FisUnadUnsub";i:9893;s:8:"WudgXant";i:9894;s:10:"NoOinPyrop";i:9895;s:6:"ExItal";i:9896;s:5:"Totty";i:9897;s:5:"Octam";i:9898;s:11:"OverReticUl";i:9899;s:9:"ElabrFenc";i:9900;s:13:"EnantUnnUnoxi";i:9901;s:5:"PeSyr";i:9902;s:9:"EtymPhoto";i:9903;s:5:"LilMi";i:9904;s:12:"GlypMedUnfet";i:9905;s:8:"LiaThrWi";i:9906;s:10:"OrShoaThur";i:9907;s:8:"UnthUpru";i:9908;s:7:"LepNihi";i:9909;s:12:"FiliMesoUpst";i:9910;s:9:"PetaTenni";i:9911;s:5:"PedRe";i:9912;s:12:"ColoProfTher";i:9913;s:10:"KatabOvSyr";i:9914;s:5:"Cequi";i:9915;s:7:"DisDiEn";i:9916;s:9:"DorMyzoWi";i:9917;s:8:"NiphaPol";i:9918;s:4:"BiCo";i:9919;s:12:"CompPolypUls";i:9920;s:8:"HeaveStr";i:9921;s:5:"MaMos";i:9922;s:13:"EudoMacarSore";i:9923;s:4:"OlVa";i:9924;s:4:"Cosw";i:9925;s:11:"MalOverdSub";i:9926;s:10:"DiKilUnder";i:9927;s:4:"MaPe";i:9928;s:10:"MetrPeProm";i:9929;s:6:"TekUnd";i:9930;s:7:"PlotiSu";i:9931;s:11:"PaPetalTrac";i:9932;s:11:"MoneyUndUnv";i:9933;s:7:"InsurYo";i:9934;s:11:"FoosMicTact";i:9935;s:7:"OlOther";i:9936;s:7:"MonMyOn";i:9937;s:11:"EmexPySymbo";i:9938;s:11:"HypaxSpiTar";i:9939;s:8:"AntUmbVi";i:9940;s:7:"OcclSqu";i:9941;s:8:"ExaPlush";i:9942;s:12:"PreSuccuVerv";i:9943;s:3:"Tre";i:9944;s:5:"MerTo";i:9945;s:11:"GlossSeptSo";i:9946;s:4:"Magn";i:9947;s:13:"QuintSourwThu";i:9948;s:5:"Kinet";i:9949;s:4:"NoUn";i:9950;s:4:"MaUn";i:9951;s:12:"MatriSpirSwe";i:9952;s:8:"OnlooUnl";i:9953;s:13:"MorwoSubarUne";i:9954;s:4:"Impo";i:9955;s:4:"Phac";i:9956;s:8:"DeprePri";i:9957;s:10:"CatLakarPr";i:9958;s:14:"GuttLophoMildr";i:9959;s:12:"OnychSmeUnwe";i:9960;s:5:"GroWo";i:9961;s:8:"LiRadTru";i:9962;s:13:"IncomTitVidui";i:9963;s:12:"ImpMerePrein";i:9964;s:7:"MaProto";i:9965;s:12:"RamusShrTibi";i:9966;s:11:"BurwFalciSn";i:9967;s:5:"RodTh";i:9968;s:8:"DoxaOlch";i:9969;s:4:"Tere";i:9970;s:4:"DiPo";i:9971;s:8:"DenucLit";i:9972;s:11:"CountInKusk";i:9973;s:5:"HeKok";i:9974;s:5:"PrSta";i:9975;s:7:"PhrenPr";i:9976;s:8:"MyoneNet";i:9977;s:14:"GeasMisfeRepre";i:9978;s:13:"OphidProveSub";i:9979;s:11:"PolProrSien";i:9980;s:5:"Grudg";i:9981;s:10:"LiReglUnde";i:9982;s:7:"DiMyoSu";i:9983;s:7:"SymTyVu";i:9984;s:11:"FiguHebSpon";i:9985;s:8:"CeDoTele";i:9986;s:10:"OkiTipTyig";i:9987;s:9:"PySphTetr";i:9988;s:3:"Pep";i:9989;s:11:"PiuUncUnson";i:9990;s:9:"OchiPrWor";i:9991;s:8:"RequiRyn";i:9992;s:13:"JimbeReceRegi";i:9993;s:4:"HeMe";i:9994;s:5:"Toffy";i:9995;s:7:"EriodSu";i:9996;s:9:"UnexViola";i:9997;s:11:"OrgaUnUnhos";i:9998;s:3:"Dav";i:9999;s:6:"HydRev";}
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testLazyJsonMapper.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testLazyJsonMapper.php
new file mode 100755
index 0000000..f47480d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testLazyJsonMapper.php
@@ -0,0 +1,1897 @@
+= 0;
+
+$json = << 'int',
+ 'just_a_string' => 'float[]',
+ 'test_pure_lazymapper_object' => '\LazyJsonMapper\LazyJsonMapper',
+ // test the shortcut to avoid having to write the whole path:
+ 'test_pure_lazymapper_object_shortcut' => 'LazyJsonMapper',
+ 'test_pure_lazymapper_object_shortarr' => 'LazyJsonMapper[][]',
+ // test without strict case-sensitive checking: (MUST fail)
+ // 'test_pure_lazymapper_object_shortcut' => 'lazyJsonMapper',
+ ];
+}
+// var_dump(new TestDeep()); // look at the LazyJsonMapper shortcut success
+class TestMid extends TestDeep
+{
+}
+
+class Test extends TestMid
+{
+ const JSON_PROPERTY_MAP = [
+ 'just_a_string' => 'string',
+ 'camelCaseProp' => 'int',
+ // full namespace path, with case-sensitivity typos on purpose (php
+ // allows it, but LazyJsonMapper compiles this to the proper name
+ // instead so that we have strict names internally):
+ 'self_object' => '\foo\Test',
+ 'string_array' => 'string[]',
+ // relative notation instead of full "\namespace\path":
+ // when relative mode is used, it looks in the defining class' own namespace.
+ 'self_array' => 'Test[]',
+ ];
+}
+
+$jsonData = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+
+var_dump($jsonData);
+
+$x = new Test($jsonData, true);
+
+// begin with basic tests...
+var_dump($x);
+$sub = $x->getSelfObject();
+$sub->setJustAString('modifying nested object and propagating the change to root object $x');
+var_dump($sub);
+var_dump($x->getSelfObject());
+var_dump($x);
+$multi = $x->getSelfArray(); // resolves all objects in array, but avoids doing
+ // it recursively. sub-properties are lazy-converted
+ // when they are actually requested.
+var_dump($multi); // array of objects, with no resolved sub-objects yet
+var_dump($x); // now has array of objects
+$deepsub = $multi[1]->getSelfObject(); // causes nested sub to be resolved
+var_dump($multi);
+var_dump($x);
+$deepsub->setJustAString('wow, propagating change of very deep object!');
+var_dump($multi);
+var_dump($x);
+var_dump($x->getCamelCaseProp());
+var_dump($x->getJustAString());
+var_dump($x->isJustAString());
+var_dump($x->getJustAString());
+var_dump($x);
+var_dump($x->getSelfObject());
+var_dump($x->getSelfObject()->getJustAString());
+var_dump($x->self_object->just_a_string);
+var_dump($x->getStringArray());
+var_dump($x->getSelfArray());
+
+try {
+ echo $x->a_missing_property_not_in_data_or_def;
+} catch (LazyJsonMapperException $e) {
+ printf("Test missing property via property access Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->getAMissingPropertyNotInDataOrDef();
+} catch (LazyJsonMapperException $e) {
+ printf("Test missing property via magic getter Exception: %s\n", $e->getMessage());
+}
+
+$x = new Test($jsonData, true);
+var_dump($x); // no data is resolved yet
+// test deeply nested chain of getters and setters.
+$x->getSelfArray()[1]->getSelfObject()->setJustAString('chained command for deep modification')->setCamelCaseProp(9944);
+var_dump($x); // chain has been resolved and change has propagated
+var_dump($x->getSelfArray()[1]->getSelfObject()->getCamelCaseProp()); // int(9944)
+
+class SubClassOfTest extends Test
+{
+}
+$foo = new SubClassOfTest(); // Test acceptance of subclasses of required class.
+$x->setSelfObject($foo);
+var_dump($x->getSelfObject());
+var_dump($x->getSelfObject()->getJustAString());
+
+try {
+ $x->setSelfObject('x'); // trying to set non-object value for object property
+} catch (LazyJsonMapperException $e) {
+ printf("Test non-object assignment Exception: %s\n", $e->getMessage());
+}
+
+class Bleh
+{
+}
+
+try {
+ $x->setSelfObject(new Bleh()); // trying wrong class for property
+} catch (LazyJsonMapperException $e) {
+ printf("Test wrong object assignment Exception: %s\n", $e->getMessage());
+}
+
+$foo = new Test(['just_a_string' => 'example']);
+$x->setSelfObject($foo);
+var_dump($x->getSelfObject());
+var_dump($x->getSelfObject()->getJustAString());
+$x->printJson();
+var_dump($x->just_a_string);
+var_dump(isset($x->just_a_string));
+unset($x->just_a_string);
+var_dump($x->just_a_string);
+var_dump(isset($x->just_a_string));
+unset($x->self_array);
+unset($x->camelCaseProp);
+$x->printJson();
+
+var_dump('---------------------');
+
+// test creation of objects from empty object-arrays "{}" in JSON
+class EmptyObjTest extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'self' => '\foo\EmptyObjTest',
+ ];
+}
+$x = new EmptyObjTest(json_decode('{"self":null}', true)); // allow null object
+var_dump($x->getSelf());
+// NOTE: the empty-array test is because empty arrays are indistinguishable from
+// objects when decoded from JSON. but if it had been an actual JSON array
+// (which is always non-associative), then we detect that it's non-object data.
+$x = new EmptyObjTest(json_decode('{"self":{}}', true)); // allow empty object
+var_dump($x->getSelf());
+$x = new EmptyObjTest(json_decode('{"self":[]}', true)); // allow empty array
+var_dump($x->getSelf());
+$x = new EmptyObjTest(json_decode('{"self":[1,2]}', true)); // forbid non-object
+try {
+ var_dump($x->getSelf());
+} catch (\Exception $e) {
+ printf("Test converting invalid regular JSON array to object Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestUndefinedProps extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'self' => '\foo\TestUndefinedProps',
+ 'selfArray' => '\foo\TestUndefinedProps[]',
+ 'foo_bar' => 'int[][]',
+ 'property' => 'string',
+ ];
+}
+
+$json = <<getMessage());
+}
+
+// now create the class without analysis enabled... which enables regular
+// operation where the user can access the undefined properties too.
+$y = new TestUndefinedProps($data, false);
+var_dump($y); // look at the internal data and the compiled class map
+
+// now verify what the exported property map says.
+// the only defined properties are foo_bar, self, selfArray and property.
+// the undefined ones are missing_property and another_missing.
+$allowRelativeTypes = true;
+$includeUndefined = true;
+$descriptions = $y->exportPropertyDescriptions($allowRelativeTypes, $includeUndefined);
+var_dump($descriptions);
+foreach ($descriptions as $property) {
+ printf("* Property: '%s'. Defined: %s\n", $property->name,
+ $property->is_defined ? 'Yes!' : 'No.');
+}
+
+// Now just test the automatic printing function too...
+$showFunctions = true;
+$y->printPropertyDescriptions($showFunctions, $allowRelativeTypes, $includeUndefined);
+$showFunctions = true;
+$allowRelativeTypes = false;
+$includeUndefined = false;
+$y->printPropertyDescriptions($showFunctions, $allowRelativeTypes, $includeUndefined);
+$showFunctions = false;
+$y->printPropertyDescriptions($showFunctions, $allowRelativeTypes, $includeUndefined);
+
+// And test it on the main class too:
+$y = new Test();
+$y->printPropertyDescriptions();
+$y->printPropertyDescriptions(false, true); // without functions, with relative
+
+var_dump('---------------------');
+
+// Test the hasX() functions, which are useful when verifying that non-defined
+// (not in class definition) fields exist in data before trying to read, to
+// avoid causing any exceptions in the getter.
+$x = new Test($data);
+var_dump($x->hasReallyMissing()); // false, since it's not in class def or data.
+var_dump($x->hasAnotherMissing()); // true, since it's in data (but not in class def)
+var_dump($x->hasJustAString()); // true, since it's in class def (but not in data)
+var_dump($x->getJustAString()); // null, since it's not in data (but is in class def)
+try {
+ $x->getReallyMissing(); // exception, since it's not in class def or data.
+ // var_dump($x->really_missing); // also exception, "no such object property".
+} catch (LazyJsonMapperException $e) {
+ printf("Test getReallyMissing() Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setReallyMissing('a'); // exception, since it's not in class def or data.
+ // $x->really_missing = 'a'; // also exception, "no such object property".
+} catch (LazyJsonMapperException $e) {
+ printf("Test setReallyMissing() Exception: %s\n", $e->getMessage());
+}
+// intended usage by end-users when accessing undefined values:
+if ($x->hasReallyMissing()) {
+ // this won't run, since ReallyMissing didn't exist. but if it HAD existed
+ // in the JSON data, this function call would now be safe without exceptions:
+ var_dump($x->getReallyMissing());
+} else {
+ var_dump('not running getReallyMissing() since the property is missing');
+}
+
+var_dump('---------------------');
+
+class TestNotSubClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'not_subclass' => '\foo\NotSubClass',
+ ];
+}
+class NotSubClass
+{
+} // Not instance of LazyJsonMapper
+
+$json = <<getNotSubclass();
+} catch (LazyJsonMapperException $e) {
+ printf("TestNotSubClass Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestMissingClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'a_missing_class' => '\foo\Missing',
+ ];
+}
+
+$json = <<getMessage());
+}
+
+var_dump('---------------------');
+
+class TestMissingPropAndMissingClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'a_missing_class' => '\foo\Missing',
+ ];
+}
+
+$json = <<getMessage());
+}
+
+var_dump('---------------------');
+
+// this test checks two things:
+// definitions that do not match the data.
+// properties whose classes cannot be constructed (due to custom _init() fail).
+class TestUnmappableAndFailConstructor extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'bad_definition' => 'int[][][]', // too deep arrays for the data
+ // this one will not be able to construct during the getting of this property...
+ 'impossible_constructor' => '\foo\WithBadConstructor',
+ ];
+}
+
+class WithBadConstructor extends LazyJsonMapper
+{
+ protected function _init()
+ {
+ // Uncomment this other exception to test the "Invalid exception thrown
+ // by _init(). Must use LazyUserException." error when users throw the
+ // wrong exception:
+ // throw new \Exception('test');
+
+ throw new \LazyJsonMapper\Exception\LazyUserException('Hello world! Thrown by a failing constructor.');
+ }
+}
+
+$json = <<getMessage());
+}
+
+var_dump('---------------------');
+
+class TestImpossibleSubPropertyCompile extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ // this one links to a class that exists but isn't compiled yet and
+ // therefore must be sub-compiled. but that particular one actually
+ // failed its own compilation earlier (above) - because the
+ // "TestMissingClass" class CANNOT be compiled... so OUR map compilation
+ // HERE will succeed (it points at TestMissingClass which is a
+ // LazyJsonMapper class which exists), but then WE will fail with a
+ // sub-property class error when we try to ALSO compile OUR property's
+ // uncompiled class ("TestMissingClass") at the end of its own
+ // compilation process.
+ 'impossible_subcompilation' => '\foo\TestMissingClass',
+ ];
+}
+
+try {
+ $x = new TestImpossibleSubPropertyCompile();
+} catch (\Exception $e) {
+ printf("Test impossible sub-property class compilation: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// this test is very similar to the previous test, but it ensures that the
+// validation works for deep hierarchies too, of classes with properties that
+// refer to classes with properties that refer to classes that refer to
+// uncompilable classes.
+class TopFailChainClass extends LazyJsonMapper
+{
+ // successfully compiles since MiddleFailChainClass exists and is based on LazyJsonMapper
+ const JSON_PROPERTY_MAP = [
+ 'middle_fail_chain_class' => '\Foo\MiddleFailChainClass',
+ ];
+}
+
+class MiddleFailChainClass extends LazyJsonMapper
+{
+ // successfully compiles since DeepFailChainBadClass exists and is based on LazyJsonMapper
+ const JSON_PROPERTY_MAP = [
+ 'deep_fail_chain_class' => '\Foo\DeepFailChainBadClass',
+ ];
+}
+
+class DeepFailChainBadClass extends LazyJsonMapper
+{
+ // this map will fail to compile, which should stop the compilation of
+ // whichever class began the compilation process that pointed at us...
+ const JSON_PROPERTY_MAP = [
+ 'not_a_valid_class' => '/What/ever/...',
+ ];
+}
+
+// try starting the compilation with each of the 3 classes:
+// it doesn't matter which one we start with, since the compiler cache will
+// notice the failures in them all and will auto-rollback their compilations.
+// which means that the other classes won't incorrectly see anything in the
+// cache. so each attempt to create any of these classes will be as if it was
+// the first-ever call for compiling the classes in its hierarchy.
+
+try {
+ // fails immediately since this class map cannot be compiled
+ $x = new DeepFailChainBadClass();
+} catch (\Exception $e) {
+ printf("Test compiling DeepFailChainBadClass Exception: %s\n", $e->getMessage());
+}
+
+try {
+ // succeeds at compiling its own map, but then fails when trying to compile
+ // the property classes (DeepFailChainBadClass) it found in the hierarchy.
+ $x = new MiddleFailChainClass();
+} catch (\Exception $e) {
+ printf("Test compiling MiddleFailChainClass Exception: %s\n", $e->getMessage());
+}
+
+try {
+ // succeeds at compiling its own map, then looks at its properties and
+ // succeeds at compiling MiddleFailChainClass, and then looks at that one's
+ // properties and fails at compiling the DeepFailChainBadClass it refers to.
+ $x = new TopFailChainClass();
+} catch (\Exception $e) {
+ printf("Test compiling TopFailChainClass Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestNullValue extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'this_is_null' => '\foo\TestNullValue',
+ ];
+}
+
+$json = <<getThisIsNull());
+} catch (LazyJsonMapperException $e) {
+ printf("TestNullValue Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestNoCastValue extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'no_cast1' => '',
+ 'no_cast2' => 'mixed', // same as ''
+ 'no_cast3' => '',
+ ];
+}
+
+$json = <<getNoCast1());
+ var_dump($x->getNoCast2());
+ var_dump($x->getNoCast3());
+ $x->setNoCast1('should succeed without type-forcing');
+ var_dump($x->getNoCast1());
+} catch (LazyJsonMapperException $e) {
+ printf("TestNoCastValue Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestDepth extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'array_of_arrays_of_arrays_of_int' => 'int[][][]',
+ ];
+}
+$x = new TestDepth([]); // Init with no data.
+try {
+ $x->setArrayOfArraysOfArraysOfInt([[new Test()]]);
+} catch (LazyJsonMapperException $e) {
+ printf("Test non-array value at depth 2 of 3 Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setArrayOfArraysOfArraysOfInt([[[new Test()]]]);
+} catch (LazyJsonMapperException $e) {
+ printf("Test invalid value at depth 3 of 3 Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setArrayOfArraysOfArraysOfInt([[[[]]]]);
+} catch (LazyJsonMapperException $e) {
+ printf("Test invalid array-value at depth 3 of 3 Exception: %s\n", $e->getMessage());
+}
+$x->setArrayOfArraysOfArraysOfInt([[[1, '456', 100, 5.5]], [], null, [[20]]]);
+var_dump($x); // 1, 456, 100, 5, 20
+
+var_dump('---------------------');
+
+// test working with raw properties not defined in the class property map.
+class UndefinedPropertyAccess extends LazyJsonMapper
+{
+}
+$json = '{"some_undefined_prop":null}';
+$data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+
+try {
+ // if we run with VALIDATION (TRUE), this will throw an exception
+ // since the data's "some_undefined_prop" is not in the class property map.
+ // NOTE: We already did this test as TestUndefinedProps earlier...
+ $x = new UndefinedPropertyAccess($data, true);
+} catch (\Exception $e) {
+ printf("Test creating class instance with validation detecting undefined properties Exception: %s\n", $e->getMessage());
+}
+
+$x = new UndefinedPropertyAccess($data);
+var_dump($x);
+var_dump($x->hasSomeUndefinedProp()); // true
+var_dump($x->isSomeUndefinedProp()); // false (the null evaluates to false)
+var_dump($x->getSomeUndefinedProp()); // null
+$x->setSomeUndefinedProp(['no data validation since it is undefined']);
+var_dump($x->isSomeUndefinedProp()); // true (the array evaluates to true)
+var_dump($x->getSomeUndefinedProp()); // array with a string in it
+$x->setSomeUndefinedProp('xyz');
+var_dump($x->hasSomeUndefinedProp()); // true
+var_dump($x->getSomeUndefinedProp()); // "xyz"
+var_dump($x->isSomeUndefinedProp()); // true (the string evaluates to true)
+$x->setSomeUndefinedProp(null);
+var_dump($x->hasSomeUndefinedProp()); // true
+var_dump($x->getSomeUndefinedProp()); // null
+
+var_dump('---------------------');
+
+// test of advanced multi-class inheritance:
+// OurTree* is a set of classes inheriting (extending) each other.
+// Unrelated* are two other classes extending each other.
+// FarClass is a single class without any other parents except LazyJsonMapper.
+//
+// OurTreeThree compiles its own inherited hierarchy, which then imports
+// UnrelatedTwo, which compiles its own hierarchy, which then finally imports
+// FarClass. The result is a final, compiled map which includes all classes.
+//
+// (and as noted in the main source code, memory cost of inheritance is 0 since
+// all classes inherit each other's PropertyDefinition objects; the only cost is
+// the amount of RAM it takes for an array["key"] to link to the borrowed object)
+class FarClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'farclass' => '\Foo\FarClass',
+ ];
+}
+class UnrelatedOne extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ FarClass::class,
+ 'unrelated_one' => 'int',
+ ];
+}
+class UnrelatedTwo extends UnrelatedOne
+{
+ const JSON_PROPERTY_MAP = [
+ 'unrelated_two' => 'float',
+ 'conflicting_prop1' => '\Foo\UnrelatedOne',
+ 'conflicting_prop2' => '\Foo\UnrelatedOne',
+ ];
+}
+class OurTreeOne extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'ourtree_one' => 'string',
+ ];
+}
+class OurTreeTwo extends OurTreeOne
+{
+ const JSON_PROPERTY_MAP = [
+ 'ourtree_two' => 'int',
+ 'conflicting_prop1' => '\Foo\OurTreeThree', // will be overwritten
+ UnrelatedTwo::class, // ... by this import
+ 'conflicting_prop2' => '\Foo\OurTreeThree', // will overwrite the import
+ ];
+}
+class OurTreeThree extends OurTreeTwo
+{
+ const JSON_PROPERTY_MAP = [
+ 'ourtree_three' => 'bool[]',
+ ];
+
+ protected function _init()
+ {
+ echo "Hello world from the init function!\n";
+ }
+}
+
+$x = new OurTreeThree();
+var_dump($x);
+
+var_dump('---------------------');
+
+// LOTS OF TESTS OF DIRECT BY-REFERENCE ACCESS, BOTH INTERNALLY AND EXTERNALLY.
+// INTERNALLY: &_getProperty(), EXTERNALLY: &__get()
+class TestGetProperty extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'foo' => '\Foo\TestGetProperty[]',
+ 'bar' => 'int',
+ ];
+
+ protected function _init()
+ {
+ // always set "bar" to a good value after construction (this is just to
+ // avoid code repetition during the testing... and to test the _init function)
+ $this->_setProperty('bar', 1234);
+
+ // uncommenting this will test the "must be LazyUserException" error:
+ // throw new \Exception('x'); // is rejected and replaced with generic
+ // throw new LazyUserException('What'); // okay, message propagates
+ }
+
+ public function runTest()
+ {
+ // just show the current internal data (nothing exists)
+ var_dump($this); // foo is empty
+
+ // test retrieving prop, but not saving missing NULL to _objectData
+ var_dump($this->_getProperty('foo')); // NULL
+ var_dump($this); // foo still empty
+
+ // test saving reference to return-value, but not saving default NULL to _objectData
+ $val = &$this->_getProperty('foo'); // missing important createMissingValue param
+ $val = 'hi'; // does NOT modify the real property, modifies some temp var
+ var_dump($this); // foo still empty
+
+ // test saving reference to return-value, and creating + filling default
+ // inner NULL value so that we can properly trust our references to always
+ // return to real inner data. this is the correct way to save the return
+ // by reference.
+ $val = &$this->_getProperty('foo', true);
+ var_dump($this); // foo now has a NULL value in its object data
+ $val = 'hi, this worked because we are linked to real internal data!';
+ var_dump($this); // overwritten internal value thanks to proper link
+
+ // notice how we have set an invalid STRING value to the internal data?
+ // the "foo" property was specified to need to be an array of object
+ // instances (or NULL is ok too). well, we will detect that the next
+ // time we try to retrieve that property!
+ try {
+ $this->_getProperty('foo');
+ } catch (\Exception $e) {
+ printf("Test _getProperty() with invalid data inserted by reference Exception: %s\n", $e->getMessage());
+ }
+
+ // let's try satisfying its array requirements (but still fail its type req)
+ $val = ['string inside array now fits array req but not type req'];
+
+ try {
+ $this->_getProperty('foo');
+ } catch (\Exception $e) {
+ printf("Test _getProperty() with valid array but invalid type inserted by reference Exception: %s\n", $e->getMessage());
+ }
+
+ // now let's fix the property (set it back to NULL, or we could have
+ // made an array of this class to satisfy the requirement).
+ $val = null;
+ var_dump($this); // foo is now NULL again
+
+ // lastly, let's show that the value is always copy-on-write if the &
+ // operator is omitted from the function call. because then PHP is told
+ // to make $val into copy-on-write.
+ unset($val); // important: break its current reference to avoid assigning to it
+ $val = $this->_getProperty('foo');
+ $val = 'not modified!';
+ var_dump($this); // foo still NULL, since $val is not a reference
+
+ // for completeness sake, also test the "bar" property which has a value
+ // and therefore ignores the "createMissingValue"
+ $bar = &$this->_getProperty('bar', true);
+ var_dump($bar); // int(1234), since a value already existed
+ var_dump($this); // bar still 1234
+ $bar = 456;
+ var_dump($this); // bar is now 456
+ }
+}
+
+// run the internal $this->_getProperty call tests:
+$x = new TestGetProperty();
+$x->runTest();
+
+// run the external __get() call tests used for "virtual property access":
+$x = new TestGetProperty(); // reset the internal data
+var_dump($x); // has no "foo" data, only "bar"
+// accessing ->foo calls __get(), which creates "foo" since virtual prop access
+// requires true internal data references, otherwise they would misbehave. so it
+// creates the missing property and gives it the default NULL value.
+var_dump($x->foo); // null
+var_dump($x); // also has "foo" now
+// accessing ->bar calls __get() which sees that it exists, and gives us its value.
+var_dump($x->bar); // 1234
+// trying to set varibles via equals causes __set() to run, which validates all data:
+try {
+ $x->bar = ['invalid']; // int is expected
+} catch (\Exception $e) {
+ printf("Test __set() with invalid value Exception: %s\n", $e->getMessage());
+}
+$x->bar = '932'; // this is okay, __set() sees it is valid and casts it to int
+var_dump($x); // "bar" is now int(932)
+// now let's do some evil special cases! we will steal a direct reference to the
+// internal _objectData['bar'], and then modify it, thus bypassing all validation.
+$evilRef = &$x->bar; // now holds reference to bar
+$evilRef = ['invalid']; // works, since we're literally modifying internal data
+var_dump($x); // "bar" now has an invalid value (an array with a string)
+try {
+ // luckily, every call to _getProperty() (which __get() uses) will validate
+ // the data to ensure that its internal state is valid and fits the class map.
+ var_dump($x->bar);
+} catch (\Exception $e) {
+ printf("Test detection of injected invalid data during next __get() Exception: %s\n", $e->getMessage());
+}
+$x->bar = 789; // call __set() and give it a new, valid value again.
+var_dump($x->bar); // int(789), it is now fixed!
+// lastly, let's play with direct access to internal arrays. anytime you access
+// an array, it will call __get() to get the array, and then PHP resolves your
+// [] brackets on the returned array. which means that we can modify arrays by
+// reference automatically!
+$x->foo = []; // runs __set(): create empty array for this "\Foo\TestGetProperty[]" property
+var_dump($x->foo); // runs __get() which sees valid empty array and returns it
+$x->foo[] = new TestGetProperty(); // okay data written by ref to "foo" array
+var_dump($x->foo); // runs __get(), which sees valid array of 1 item of right type
+$x->foo[] = 'invalid'; // this is allowed because __get() gets "foo" and then
+ // PHP just directly modifies the array...
+var_dump($x); // the "foo" prop now has invalid data in it
+// but luckily, anything that calls _getProperty() again, such as __get(), will
+// cause validation of the data:
+try {
+ // var_dump($x->foo); // calls __get(), would also throw the error.
+ $x->foo[] = 'x'; // calls __get() again to resolve "->foo" and throws error
+} catch (\Exception $e) {
+ printf("Test detection of invalid injected data via array access Exception: %s\n", $e->getMessage());
+}
+$x->foo = [new TestGetProperty(), new TestGetProperty()]; // run __set() to give okay array again, with 2 entries
+var_dump($x->foo); // shows the array with 2 objects in it
+$x->foo[0] = null; // runs __get(), gets "foo" by reference, and directly modifies element
+var_dump($x->foo); // shows array with 1 NULL in it (this array is valid hence no error)
+// we can also __get() the internal array, then loop over the
+// values-by-reference, to directly modify them without any validation:
+foreach ($x->foo as $k => &$fooVal) {
+ $fooVal = 'invalid';
+}
+var_dump($x); // array with string "invalid".
+try {
+ // if this had been unset($x->foo) it would work, but we try to unset a
+ // sub-element which means it actually calls __get() instead of __unset()
+ unset($x->foo[0]); // calls __get(), sees that the data is now invalid
+} catch (\Exception $e) {
+ printf("Test detection of invalid injected data via array by-reference value loop Exception: %s\n", $e->getMessage());
+}
+var_dump($x); // two "invalid" remains
+$x->foo = [null, new TestGetProperty()]; // let's make it a valid 2-element
+var_dump($x->foo); // array of [null,obj];
+unset($x->foo[0]); // runs __get() on "foo", then unsets the 0th element
+
+// these tests were commented out after adding strict sequence valiation:
+// var_dump($x->foo); // array of [obj];, with the 0 array key missing
+// unset($x->foo[1]->bar); // runs__get() on "foo", gets array, finds 1st elem,
+// // sees object, runs __unset() on that ones "bar"
+// var_dump($x); // reveals that the inner object no longer has any "bar" value
+
+// let's test accessing an object inside an array. and for fun add in regular getters
+// NOTE: there is a subtle difference. getFoo() returns copy-on-write array, but
+// any objects within it are of course objects and can be modified and will propagate.
+$x->foo = [new TestGetProperty()];
+var_dump($x->foo[0]->bar); // int(1234)
+var_dump($x->foo[0]->getBar()); // int(1234)
+var_dump($x->getFoo()[0]->getBar()); // int(1234)
+var_dump($x->getFoo()[0]->bar); // int(1234)
+$x->getFoo()[0]->setBar(10);
+var_dump($x); // the 0th "foo" array element has a bar of int(10) now
+$x->getFoo()[0] = 'xyz'; // this does nothing, since getFoo() is always copy-on-write.
+var_dump($x); // still intact, statement above had no effect, which is as intended
+// now let's modify the array by reference to avoid constant __get() calls...
+$arr = &$x->foo;
+$arr = [1, 2, 'f'=>'bar', [['very invalid data']]];
+$arr[] = 'more invalid stuff...';
+var_dump($x); // very invalid stuff...
+try {
+ var_dump($x->foo);
+} catch (\Exception $e) {
+ printf("Test __get() after lots of bad array edits by reference Exception: %s\n", $e->getMessage());
+}
+$arr = null;
+var_dump($x->foo); // now it is fine again (NULL)
+// let's call a normal array-command on the returned array-by-ref
+$x->foo = []; // first make it into an array
+array_push($x->foo, 'zzz'); // now __get() "foo" and then directly push (same as $x->foo[] = 'bar';)
+var_dump($x); // we have directly added invalid data "zzz" into the array.
+$x = null; // release the object...
+
+var_dump('---------------------');
+
+// Test PropertyDefinition equality:
+$a = new PropertyDefinition('int[]');
+$b = new PropertyDefinition('int');
+$c = new PropertyDefinition('int[]');
+var_dump($a->equals($b)); // false
+var_dump($a->equals($c)); // true
+var_dump($b->equals($a)); // false
+var_dump($b->equals($c)); // false
+var_dump($c->equals($a)); // true
+var_dump($c->equals($b)); // false
+
+var_dump('---------------------');
+
+// Test inheriting from base-class and also importing from other-class
+class OtherBase extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'otherbase' => 'string[]',
+ // ImportMapTest::class, // triggers circular map error IF ImportMapTest
+ // // itself refers to our class hierarchy.
+ ];
+}
+class Other extends OtherBase
+{
+ const JSON_PROPERTY_MAP = [
+ 'identical_key' => 'float',
+ 'other' => 'int',
+ ];
+}
+class Base extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'base' => 'float',
+ ];
+}
+class ImportMapTest extends Base
+{
+ const JSON_PROPERTY_MAP = [
+ // Base::class, // self-hierarchy reference (not allowed)
+ // ImportMapTest::class, // self-class reference (not allowed)
+ C::class, // reference to deeper version of self (not allowed)
+ Other::class, // okay, since it's another class.. but only ok if that
+ // other class doesn't have its circular reference enabled!
+ 'identical_key' => 'string', // should be string, since we add it after
+ // importing Other. but the one in Other should
+ // remain as its own one (float).
+ ];
+}
+class C extends ImportMapTest
+{
+}
+
+try {
+ $x = new C(); // comment in/out various class references above to test
+ // various arrangements of bad circular references.
+ var_dump($x); // if successful inheritance, print the
+ // _compiledPropertyMapLink so we can verify that all values
+ // are properly merged.
+} catch (\Exception $e) {
+ printf("Test resolved-shared-ancestor circular map Exception: %s\n", $e->getMessage());
+}
+
+class AA extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ BB::class,
+ ];
+}
+class BB extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ AA::class,
+ ];
+}
+
+try {
+ $x = new AA();
+} catch (\Exception $e) {
+ printf("Test resolved-shared-ancestor circular map Exception: %s\n", $e->getMessage());
+}
+
+// ensure that the locks are empty after all the construction failures above
+$x = new LazyJsonMapper();
+$reflect = new \ReflectionProperty($x, '_propertyMapCache');
+$reflect->setAccessible(true);
+var_dump($reflect->getValue()->compilerLocks); // should be empty array
+
+var_dump('---------------------');
+
+// this was written to test PropertyDefinition re-use (RAM saving) when a class
+// re-defines a property to the exact same settings that it already inherited.
+// properties in LazyJsonMapper will keep their parent's/imported value if their
+// new value is identical, thus avoiding needless creation of useless objects
+// that just describe the exact same settings. it makes identical re-definitions
+// into a zero-cost operation!
+class HasFoo extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'foo' => 'string[]',
+ ];
+}
+class RedefinesFoo extends HasFoo
+{
+ const JSON_PROPERTY_MAP = [
+ // 'foo' => 'float', // tests non-identical settings (should USE NEW obj)
+ 'foo' => 'string[]', // tests identical settings (should KEEP parent
+ // obj). memory usage should be same as if 'foo'
+ // wasn't re-defined on THIS object at all.
+ // 'foo' => 'string[][]', // tests non-identical settings (should USE NEW obj)
+ 'extra' => '\LazyJsonMapper\LazyJsonMapper',
+ ];
+}
+$mem = memory_get_usage();
+$x = new RedefinesFoo();
+unset($x); // free the object itself, so we only keep the compiled map cache
+printf("Memory increased by %d bytes.\n", memory_get_usage() - $mem);
+
+var_dump('---------------------');
+
+// test function overriding:
+class OverridesFunction extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'foo' => 'string',
+ ];
+
+ public function getFoo()
+ {
+ $value = $this->_getProperty('foo');
+
+ return 'Custom getter: '.var_export($value, true);
+ }
+
+ public function setFoo(
+ $value)
+ {
+ $value = sprintf('Tried "%s" but we will write "%s" instead.', $value, md5(time()));
+ $this->_setProperty('foo', $value);
+
+ return $this;
+ }
+}
+
+$x = new OverridesFunction();
+var_dump($x->getFoo());
+$x->setFoo('ignored');
+var_dump($x->getFoo());
+
+var_dump('---------------------');
+
+// Test rejection of associative array keys in "array of" JSON definition, since
+// those are illegal JSON.
+
+class TestArrayKeyValidation extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'obj_arr' => '\Foo\TestArrayKeyValidation[]',
+ ];
+}
+
+// normal sequence, okay:
+$x = new TestArrayKeyValidation(['obj_arr' => [null, null, null, new TestArrayKeyValidation()]]);
+var_dump($x->getObjArr());
+
+// gap in sequence, not okay:
+$x = new TestArrayKeyValidation(['obj_arr' => [1 => new TestArrayKeyValidation()]]);
+
+try {
+ var_dump($x->getObjArr());
+} catch (\Exception $e) {
+ printf("* Test numeric gap in typed 'array of' sequence: %s\n", $e->getMessage());
+}
+
+// This is ok because of a PHP quirk which converts '0' to 0 if used as array
+// key in certain cases such as this one. The key here is literally int(0):
+$x = new TestArrayKeyValidation(['obj_arr' => ['0' => new TestArrayKeyValidation()]]);
+var_dump($x->getObjArr());
+
+// string key in numerically indexed "array of", not okay:
+$x = new TestArrayKeyValidation(['obj_arr' => ['not_allowed_to_have_key' => new TestArrayKeyValidation()]]);
+
+try {
+ var_dump($x->getObjArr());
+} catch (\Exception $e) {
+ printf("* Test illegal string-based key in typed 'array of' sequence: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// Test validation of mixed data (only allows NULL, int, float, string, bool, or
+// numerically indexed arrays of any of those types). Untyped arrays always do
+// array key validation.
+
+class TestUntypedValidation extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'unt' => '', // behavior would be the same if this property is not
+ // defined in class map but then it would have to exist in
+ // the original input data, so I defined it here as untyped.
+ 'strict_depth' => 'mixed[][]', // enforces mixed non-array data at 2
+ // levels deep within an array
+ ];
+}
+
+$x = new TestUntypedValidation();
+
+try {
+ $x->setUnt(new \stdClass());
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of object \stdClass: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setUnt([new \stdClass()]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of object [\stdClass]: %s\n", $e->getMessage());
+}
+
+$fh = null;
+
+try {
+ $fh = fopen(__DIR__.'/../funcListData.serialized', 'r');
+
+ try {
+ $x->setUnt($fh);
+ } catch (\Exception $e) {
+ printf("* Test set-untyped rejection of Resource: %s\n", $e->getMessage());
+ }
+
+ try {
+ $x->setUnt([$fh]);
+ } catch (\Exception $e) {
+ printf("* Test set-untyped rejection of [Resource]: %s\n", $e->getMessage());
+ }
+} finally {
+ if (is_resource($fh)) {
+ fclose($fh);
+ }
+}
+
+// all other types are allowed in this untyped field:
+$x->setUnt(null);
+$x->setUnt(1);
+$x->setUnt(1.5);
+$x->setUnt('2');
+$x->setUnt(true);
+$x->setUnt([null, 1, [1.5], '2', true]);
+var_dump($x->getUnt());
+
+// we also allow associative keys in untyped fields (which will become JSON
+// objects), since that allows people to access "objects" in json data (arrays)
+// without needing to manually map the properties to actual LazyJsonMapper
+// NOTE: mixing key types can create weird JSON objects. so if people want
+// strict validation they need to use typed fields instead, as seen above.
+$x->setUnt([null, 1, ['foo' => 1.5], '2', true]);
+var_dump($x->getUnt());
+
+// lastly, test the "mixed[]" strict_depth which specified untyped data but
+// exactly 1 array level deep.
+var_dump($x->getStrictDepth());
+$x->setStrictDepth([[null, 1, null, '2', true], null, []]);
+var_dump($x->getStrictDepth());
+
+try {
+ $x->setStrictDepth([[null, 1, null, '2', true], 'not_array', []]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of non-array at array-depth: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setStrictDepth([[null, 1, null, '2', true], 'foo' => 'bar', []]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of associative key in strict array depth: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setStrictDepth([[null, 1, null, '2', true], [['too_deep']], []]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of array deeper than max depth: %s\n", $e->getMessage());
+}
+var_dump($x->getStrictDepth());
+$x->setStrictDepth([]); // okay, since we never reach maxdepth
+var_dump($x->getStrictDepth()); //accepted
+$x->setStrictDepth(null); // null is always okay
+var_dump($x->getStrictDepth()); //accepted
+try {
+ $x->setStrictDepth('foo'); // rejected since the value is not at specified depth
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of value at not enough depth: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// Test FunctionCase name translations into property names:
+
+$x = new FunctionTranslation('ExportProp');
+var_dump($x);
+
+try {
+ // Invalid single-word lowercase FuncCase name.
+ $x = new FunctionTranslation('somelowercase');
+} catch (\Exception $e) {
+ printf("Test invalid single-word lowercase FuncCase name 'somelowercase' Exception: %s\n", $e->getMessage());
+}
+$x = new FunctionTranslation('_MessageList');
+var_dump($x);
+$x = new FunctionTranslation('Nocamelcase'); // Single uppercase = no camel.
+var_dump($x);
+$x = new FunctionTranslation('WithCamelCase'); // Multiple Ucwords = camel.
+var_dump($x);
+
+var_dump('---------------------');
+
+// Test property name translations into FunctionCase, and back...
+// They must be 100% identical in both directions!
+
+// Test function names to property names, and then ensure that both snake_case
+// and camelCase variants translate back to the same function name via PropertyTranslation.
+$funcList = [
+ 'getSome0XThing',
+ 'getSome0xThing',
+ 'getSomeThing',
+ 'get_Messages',
+ 'get__MessageList',
+ 'get0m__AnUn0x',
+ 'get__Foo_Bar__XBaz__',
+ 'get__Foo_Bar_',
+ 'get___M',
+ 'get_M',
+ 'get_0',
+ 'get_',
+ 'get___',
+ 'get123',
+ 'get123prop',
+ 'get123Prop',
+];
+foreach ($funcList as $f) {
+ echo "---\n";
+ list($functionType, $funcCase) = FunctionTranslation::splitFunctionName($f);
+
+ $x = new FunctionTranslation($funcCase);
+ printf("* Function: '%s'\n- Type: '%s'\n- FuncCase: '%s'\n > snake: '%s',\n > camel: '%s'\n", $f, $functionType, $funcCase, $x->snakePropName, $x->camelPropName);
+
+ $y = new PropertyTranslation($x->snakePropName);
+ $getter = 'get'.$y->propFuncCase;
+ printf("* Property: '%s' (snake)\n > func: '%s' (%s)\n", $x->snakePropName, $getter, $getter === $f ? 'ok' : 'fail');
+ if ($x->camelPropName === null) {
+ echo "* Property: No Camel Property, skipping...\n";
+ } else {
+ $y = new PropertyTranslation($x->camelPropName);
+ $getter = 'get'.$y->propFuncCase;
+ printf("* Property: '%s' (camel)\n > func: '%s' (%s)\n", $x->camelPropName, $getter, $getter === $f ? 'ok' : 'fail');
+ }
+ echo "---\n";
+}
+
+var_dump('---------------------');
+
+// test the special operator translator
+$result = 'A + and - and * and / and finally % symbol... And some ++--**//%% close ones...';
+var_dump($result);
+$result = \LazyJsonMapper\Magic\SpecialOperators::encodeOperators($result);
+var_dump($result);
+$result = \LazyJsonMapper\Magic\SpecialOperators::decodeOperators($result);
+var_dump($result);
+
+class OperatorTest extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'en+US' => 'string',
+ 'en-US' => 'string',
+ 'en/US' => 'string',
+ 'en%US' => 'string',
+ 'en*US' => 'string',
+ 'with;semicolon_and@at' => 'string',
+ ];
+}
+
+$optest = new OperatorTest([
+ 'en+US' => 'plus',
+ 'en-US' => 'minus',
+ 'en/US' => 'divide',
+ 'en%US' => 'modulo',
+ 'en*US' => 'multiply',
+ 'with;semicolon_and@at' => 'complex characters here!',
+]);
+
+$optest->printPropertyDescriptions();
+var_dump($optest->getEn_x2B_US()); // plus
+var_dump($optest->getEn_x2D_US()); // minus
+var_dump($optest->getEn_x2F_US()); // divide
+var_dump($optest->getEn_x25_US()); // modulo
+var_dump($optest->getEn_x2A_US()); // multiply
+var_dump($optest->getWith_x3B_semicolonAnd_x40_at());
+
+var_dump('---------------------');
+
+// Test the property description system (the parameters are so strict that there
+// isn't really anything to test, apart from the relative property param...)
+$ownerClassName = get_class(new Test());
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ new PropertyDefinition('\Foo\Test[][]'),
+ false // do not allow relative paths
+);
+var_dump($desc);
+
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ new PropertyDefinition('\Foo\Test[][]'),
+ true // allow relative paths
+);
+var_dump($desc);
+
+// and now test the is_defined detection of UndefinedProperty:
+
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ \LazyJsonMapper\Property\UndefinedProperty::getInstance(),
+ false // do not allow relative paths
+);
+var_dump($desc);
+
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ \LazyJsonMapper\Property\UndefinedProperty::getInstance(),
+ true // allow relative paths
+);
+var_dump($desc);
+
+var_dump('---------------------');
+
+// calculate the memsize of a propertydefinition under various circumstances
+
+// first... force autoloading to find each class if not already loaded, to avoid
+// messing up the measurement.
+$x = new PropertyDefinition();
+$x = new LazyJsonMapper();
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('\LazyJsonMapper\LazyJsonMapper');
+printf("Memory size of a PropertyDefinition object referring to '\\LazyJsonMapper\\LazyJsonMapper': %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition();
+printf("Memory size of a PropertyDefinition object referring to NULL ('mixed'/untyped): %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('int');
+printf("Memory size of a PropertyDefinition object referring to 'int': %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('int[]');
+printf("Memory size of a PropertyDefinition object referring to 'int[]': %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('float[][][]');
+printf("Memory size of a PropertyDefinition object referring to 'float[][][]': %d bytes.\n", memory_get_usage() - $mem);
+
+var_dump('---------------------');
+
+// Test detection of undefined properties:
+$undef = UndefinedProperty::getInstance();
+$def = new PropertyDefinition();
+var_dump($undef instanceof UndefinedProperty); // true
+var_dump($def instanceof UndefinedProperty); // false
+
+var_dump('---------------------');
+
+// the following test analyzes memory usage of FunctionTranslation objects vs
+// storing the cache values as a regular array (without objects). since objects
+// are specialized arrays in PHP, with lower memory needs, we see some pretty
+// great space savings by using objects.
+//
+// these tests will determine the memory needs for the runtime function call
+// translation cache. I'd say normally people use around 100 different
+// properties. this code generates very realistic function names that mimic
+// real-world data.
+
+// if true, re-use pre-built list of 10000 function names and ignore the other
+// params below. that's useful because building the list of names is slow on PHP5.
+// and especially because it guarantees comparable runs between PHP binaries.
+$usePrebuiltFuncList = true;
+
+// how many function names to generate. large sample sizes = accurate averages.
+// this only happens if "useprebuilt" is disabled.
+// $funcCacheCount = 10000; // recommended
+$funcCacheCount = 100; // fast for testing but gives inaccurate memory averages
+
+/*
+ * Here are results for PHP7 and PHP5 with 10000x entries to really demonstrate
+ * the correct averages by having a large enough sample size.
+ *
+ * PHP7: Array of 10000x FunctionTranslation objects: 2485704 bytes total, ~248.6 bytes per entry.
+ * PHP7: Array of 10000x numerically indexed arrays: 5394584 bytes total, ~539.5 bytes per entry.
+ * PHP5: Array of 10000x FunctionTranslation objects: 5104024 bytes total, ~510.4 bytes per entry.
+ * PHP5: Array of 10000x numerically indexed arrays: 6640864 bytes total, ~664.4 bytes per entry.
+ *
+ * Those numbers include the object AND the overhead of their associatively
+ * named string key in the parent (cache) array. The array key is the FuncCase
+ * portion of the name (meaning it lacks the functionType prefix like "get" or
+ * "unset").
+ *
+ * The actual LazyJsonMapper project uses FunctionTranslation objects, and
+ * normal users can be expected to need around 100 cache entries to cover every
+ * property they use in their project.
+ *
+ * That's ~25kb of RAM on PHP7 or ~51kb on PHP5. ;-)
+ */
+
+function randWords(
+ array $allWords)
+{
+ global $allWords;
+
+ // pick 1-3 words
+ $keys = array_rand($allWords, mt_rand(2, 4));
+ array_shift($keys);
+
+ // ensure that they are all lowercase, with an uppercase first letter
+ $words = [];
+ foreach ($keys as $k) {
+ $w = ucfirst(preg_replace('/[^a-z]+/', 'x', strtolower($allWords[$k])));
+ // The wordlist has many insanely long words...
+ // Limit the length to 2-5 chars per word (JSON programmers are terse)
+ $w = substr($w, 0, mt_rand(2, 5));
+ $words[] = $w;
+ }
+
+ return $words;
+}
+function randFunctionName(
+ array $allWords)
+{ // Generates a valid "realistic" function name
+ // Commented out because we no longer use the function type as part of the
+ // parsing of FuncCase names. So we don't need it in the test-data.
+ // $functionType = ['has', 'get', 'set', 'is', 'unset'][mt_rand(0, 4)];
+ // return $functionType.implode(randWords($allWords));
+
+ return implode(randWords($allWords));
+}
+function buildFuncList(
+ array $allWords,
+ $count = 500)
+{ // Generates a list of unique functions.
+ if (count($allWords) < 100) {
+ die('Not enough words...');
+ }
+ $funcList = [];
+ while (count($funcList) < $count) {
+ $funcList[randFunctionName($allWords)] = true;
+ }
+
+ return array_keys($funcList);
+}
+
+if (!$usePrebuiltFuncList) {
+ if (!is_file('/usr/share/dict/words')) {
+ die('Dictionary file missing.');
+ }
+ $allWords = @file('/usr/share/dict/words');
+
+ // build a list of $funcCacheCount amount functions that we'll put in a lookup cache
+ echo "- creating a list of {$funcCacheCount} random function names...\n";
+ $funcList = buildFuncList($allWords, $funcCacheCount);
+
+ // debug/list generation:
+ // var_dump($funcList); // uncomment to see quality of name generation
+ // file_put_contents(__DIR__.'/../funcListData.serialized', serialize($funcList));
+
+ echo "- function list built... running cache test...\n";
+} else {
+ if (!is_file(__DIR__.'/../funcListData.serialized')) {
+ die('No serialized function list.');
+ }
+ $funcList = unserialize(file_get_contents(__DIR__.'/../funcListData.serialized'));
+}
+
+// force autoloading of the class to prevent counting the class itself in mem
+$x = new FunctionTranslation('Example');
+
+// try storing them as FunctionTranslation objects
+$holder = [];
+foreach ($funcList as $funcCase) {
+ $newFuncCase = $funcCase.'x'; // Avoid variable re-use of incoming string.
+ $holder[$newFuncCase] = new FunctionTranslation($newFuncCase);
+ unset($newFuncCase);
+}
+// var_dump($holder); // warning: don't uncomment while testing; increases mem
+$mem = memory_get_usage();
+unset($holder);
+$totalmem = $mem - memory_get_usage();
+$indivmem = $totalmem / count($funcList); // includes the parent array overhead
+printf("PHP%d: Array of %dx FunctionTranslation objects: %d bytes total, ~%.1f bytes per entry.\n", $hasSeven ? 7 : 5, count($funcList), $totalmem, $indivmem);
+
+// try storing them as a regular non-associative array
+$holder = [];
+foreach ($funcList as $funcCase) {
+ $newFuncCase = $funcCase.'y'; // Avoid variable re-use of incoming string.
+ $translation = new FunctionTranslation($newFuncCase);
+ $y = [
+ // paranoid about PHP re-using the object's value, so let's tweak all:
+ substr($translation->snakePropName, 0, -1).'y',
+ $translation->camelPropName === null ? null : substr($translation->camelPropName, 0, -1).'y',
+ ];
+ $holder[$newFuncCase] = $y;
+ unset($translation);
+ unset($newFuncCase);
+ unset($y);
+}
+// var_dump($holder); // warning: don't uncomment while testing; increases mem
+$mem = memory_get_usage();
+unset($holder);
+$totalmem = $mem - memory_get_usage();
+$indivmem = $totalmem / count($funcList); // includes the parent array overhead
+printf("PHP%d: Array of %dx numerically indexed arrays: %d bytes total, ~%.1f bytes per entry.\n", $hasSeven ? 7 : 5, count($funcList), $totalmem, $indivmem);
+
+var_dump('---------------------');
+
+// test cache clearing and the memory usage of each cache from this test-file.
+$mem = memory_get_usage();
+$lookupCount = LazyJsonMapper::clearGlobalMagicLookupCache();
+printf("Saved %d bytes by clearing the magic function lookup cache, which contained %d function name translations.\n", $mem - memory_get_usage(), $lookupCount);
+
+$mem = memory_get_usage();
+$classCount = LazyJsonMapper::clearGlobalPropertyMapCache();
+printf("Saved %d bytes by clearing %d compiled class maps. But not all may have been freed from memory by PHP yet, if any class instance variables are still in scope.\n", $mem - memory_get_usage(), $classCount);
+
+var_dump('---------------------');
+
+// perform lots of tests of the array converter:
+
+// assign the normal json data array, but do not recursively validate (convert)
+// it since we want a mix of converted and unconverted data during this test...
+$x = new Test($jsonData);
+
+//
+// the magic: asArray() CLONES the internal data, then recursively validates all
+// of it and then converts it back to a plain array. the result is therefore
+// fully validated/type-converted as a side-effect of the conversion process.
+//
+// it does not touch the contents of the original object:
+//
+$x->getSelfObject(); // force self_object to evaluate and parse
+var_dump($x); // look at raw data... nothing is parsed except self_object
+
+$asArray = $x->asArray();
+
+// look at the original object... still nothing is parsed except self_object,
+// which remains obj, with the exact same instance number. this verifies that
+// asArray did not manipulate/destroy data in our object.
+var_dump($x);
+
+// validate the asArray result for correctness:
+// $asArray[] = 'x'; // uncomment this to trigger a mismatch below
+// var_dump($asArray); // look at asarray contents
+printf("The asArray() result matches original input array? %s\n",
+ // NOTE: Array === operator checks all keys, keytypes, key order, values,
+ // valuetypes and counts recursively. If true, arrays contain IDENTICAL.
+ ($asArray === $jsonData ? 'YES!' : 'No...'));
+
+// try tweaking the input data so that the class definition no longer matches:
+$jsonData['self_array'] = [$jsonData['self_array']]; // wrap in extra array depth
+$x = new Test($jsonData);
+
+try {
+ $asArray = $x->asArray();
+} catch (\Exception $e) {
+ printf("Trying asArray() with data that mismatches class map Exception: %s\n", $e->getMessage());
+}
+$jsonData['self_array'] = $jsonData['self_array'][0]; // fix data again
+
+// try undefined/untyped (missing) field with acceptable basic non-object data:
+// acceptable basic data is: "int, float, string, bool, NULL" (and arrays of those).
+$jsonData['untyped_field_with_non_object'] = '123456foo';
+$x = new Test($jsonData);
+$asArray = $x->asArray();
+printf("As array with untyped/undefined missing but ok data: %s\n",
+ ($asArray === $jsonData ? 'YES!' : 'No...'));
+
+// try undefined/untyped (missing) field with a LazyJsonMapper object. this will
+// NOT be okay because untyped fields only allow basic PHP types mentioned above.
+// NOTE: This can NEVER happen via real json_decode() data. It is a test against
+// user's custom data arrays with bad values...
+$jsonData['untyped_field_with_lazy_object'] = new LazyJsonMapper(['inner_val' => '123foo']);
+
+try {
+ $x = new Test($jsonData, true); // true = run with validation
+} catch (\Exception $e) {
+ printf("Test construction with validation enabled, and having an illegal value (object) in an undefined property Exception: %s\n", $e->getMessage());
+}
+// try with non-LazyJsonMapper object too in a different property (will fail too
+// since ALL OBJECTS are forbidden in undefined/untyped properties):
+$jsonData['untyped_field_with_bad_object'] = new \stdClass();
+$x = new Test($jsonData); // now construct it WITHOUT validation, so the illegal
+ // value is undetected...
+try {
+ $asArray = $x->asArray();
+} catch (\Exception $e) {
+ // should warn about BOTH the lazy and the "bad" object:
+ printf("Test asArray() on previously unvalidated object containing illegal values in data array Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->getUntypedFieldWithBadObject();
+} catch (\Exception $e) {
+ // should warn about BOTH the lazy and the "bad" object:
+ printf("Test getUntypedFieldWithBadObject() on previously unvalidated object with illegal value in that field Exception: %s\n", $e->getMessage());
+}
+
+// now remove the fake data value again to restore the original jsonData...
+unset($jsonData['untyped_field_with_lazy_object']);
+unset($jsonData['untyped_field_with_bad_object']);
+
+$x = new Test($jsonData);
+$asArray = $x->asArray(); // works again since all bad data is gone!
+var_dump($asArray);
+
+// now try type-conversion to ensure that the type-map is followed:
+class ForceType extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'arr' => 'float[]',
+ ];
+}
+$x = new ForceType(['arr' => ['1', '232', '94.2', 123.42]]);
+var_dump($x->asArray()); // all are floats, exactly as the class-map requested
+var_dump($x); // and as usual... internal _objectData remains untouched.
+
+// try non-integer arguments
+try {
+ $x->asJson(false);
+} catch (\Exception $e) {
+ printf("Test asJson() with non-int arg1: %s\n", $e->getMessage());
+}
+
+try {
+ $x->asJson(0, false);
+} catch (\Exception $e) {
+ printf("Test asJson() with non-int arg2: %s\n", $e->getMessage());
+}
+
+// try requesting a json data depth that is way too low for the data:
+try {
+ $x->asJson(0, 1);
+} catch (\Exception $e) {
+ printf("Test asJson() with too low depth parameter Exception: %s\n", $e->getMessage());
+}
+
+// and for fun...
+var_dump($x->asArray());
+var_dump($x->asJson());
+var_dump($x->asJson(JSON_PRETTY_PRINT));
+$x->printJson();
+
+var_dump('---------------------');
+
+$x = new Test($jsonData);
+
+// ensure that "convert object to string" works and properly outputs JSON...
+echo '['.$x."]\n\n";
+echo $x;
+echo PHP_EOL;
+
+// and test invalid data being output as with <> brackets as intended:
+$bad = new Test(['self_object' => 1]);
+echo PHP_EOL.'Test of clearly bad input data error handling as message string (since __toString cannot throw): '.$bad.PHP_EOL;
+
+var_dump('---------------------');
+
+// try unsetting properties from the internal JSON data tree:
+$x = new Test($jsonData);
+$x->printJson();
+$x->unsetSelfArray() // NOTE: This tests the "chained unsetter" feature too.
+ ->unsetCamelCaseProp()
+ ->setSelfObject(new Test(['just_a_string' => '123 new object!'])); // Tests chained setter together with unsetters.
+$x->printJson();
+$x->unsetSelfObject();
+$x->printJson();
+
+// now try reading a property and then unsetting and reading it again:
+var_dump($x->getStringArray());
+$x->unsetStringArray();
+var_dump($x->getStringArray());
+$x->printJson();
+
+// also try using the direct unset() on the remaining values
+unset($x->just_a_string);
+unset($x->untyped_field_with_non_object);
+$x->printJson();
+
+var_dump('---------------------');
+
+// Let's do some serialization tests:
+
+// First, run a recursive analysis to force all unparsed properties to evaluate
+// into creating inner LazyJsonMapper objects.
+$x = new Test($jsonData);
+$x->exportClassAnalysis();
+var_dump($x); // tree of objects
+
+// Now test the secret, internal "tight packing" serialization method which
+// returns the internal data as a plain array instead of as a serialized string:
+$secretArr = $x->serialize($x);
+var_dump($secretArr);
+
+// Now serialize the object into an actual, serialized string. The same way an
+// end-user would do it.
+// NOTE: This resolves all nested objects and serializes the root object with a
+// single serialized, plain array within it.
+$str = serialize($x);
+var_dump($str); // no nested serialized objects
+
+// test the ability to fake "unserialize" into re-constructing objects from any
+// serialized array. NOTE: this is just for testing proper re-building /
+// unserialization in a different way. users should never do this. it's dumb.
+// they should just create their "new TheClass([...])" instead.
+$fakeunserialize = new Test();
+var_dump($fakeunserialize); // empty _objectData
+$fakeunserialize->unserialize(serialize(['my_data' => 'hehe']));
+var_dump($fakeunserialize); // objectdata now has my_data
+
+// test exception when calling the function directly with bad params
+try {
+ $fakeunserialize->unserialize();
+ $fakeunserialize->unserialize(null);
+} catch (\Exception $e) {
+ printf("Test unserialize manual call with bad params Exception: %s\n", $e->getMessage());
+}
+
+// lastly, let's test real unserialization as a new object instance.
+// this creates a brand new object with the data array, and has no links to the
+// original object (except using the same shared, compiled classmap since we are
+// still in the same runtime and have a shared classmap cache entry available).
+$new = unserialize($str);
+// verify that all _objectData is there in the new object, and that unlike the
+// original object (which had exportClassAnalysis() to create inner objects),
+// this unserialized copy just has a plain data array:
+var_dump($new);
+// get a random property to cause it to convert it to its destination format:
+$new->getSelfArray();
+var_dump($new); // self_array is now an array of actual objects
+
+var_dump('---------------------');
+
+// test asArray/asStdClass which are aliases to exportObjectDataCopy
+var_dump($x->exportObjectDataCopy('array'));
+var_dump($x->asArray());
+// var_dump($x->exportObjectDataCopy('Array')); // test invalid type
+var_dump($x->exportObjectDataCopy('stdClass'));
+var_dump($x->asStdClass());
+
+var_dump('---------------------');
+
+// test new data assignment at a later time via assignObjectData():
+$foo = new Test(['just_a_string' => '123']);
+$foo->printPropertyDescriptions();
+$foo->printJson();
+$foo->assignObjectData(['camelCaseProp' => 999, 'string_array' => ['a', 'b', 'c']]);
+$foo->printJson();
+
+var_dump('---------------------');
+
+class TestJSONArrayKeys extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'array_of_strings' => 'string[]',
+ ];
+}
+
+// first, test the fact that objects must always be output as {} notation.
+$test = new TestJSONArrayKeys();
+var_dump($test->asJson());
+$test->printJson(); // {}
+$test->setArrayOfStrings(['a', 'b']);
+var_dump($test->asJson());
+$test->printJson(); // {"array_of_strings":["a","b"]}
+$test = new TestJSONArrayKeys(['a', 'b']);
+var_dump($test->asJson());
+$test->printJson(); // {"0":"a","1":"b"}
+
+// now do a test of the fact that properties defined as "array of" only allow
+// sequential, numerical keys.
+$test->setArrayOfStrings(['a', 'b']);
+$test->setArrayOfStrings(['0' => 'a', 'b']); // works because PHP converts "0" to int(0)
+$test->setArrayOfStrings([0 => 'a', 1 => 'b']); // correct order
+try {
+ $test->setArrayOfStrings([1 => 'a', 0 => 'b']); // bad order
+} catch (\Exception $e) {
+ printf("Test wrong order array keys, Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $test->setArrayOfStrings([4 => 'a', 5 => 'b']); // not starting at 0
+} catch (\Exception $e) {
+ printf("Test array keys not starting at 0, Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $test->setArrayOfStrings(['a', 'b', 'foo' => 'b']); // string-key
+} catch (\Exception $e) {
+ printf("Test non-numeric array key in numeric array, Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// test that our forced-object-notation {} JSON output works in all cases (even
+// when strictly numeric keys or empty arrays).
+// the correct, intended format is: The outer container (the object) is always
+// {}, but any inner arrays in properties are [].
+
+$foo = new Test(); // no internal array assigned, so uses empty default array
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([1, [11, 22, 33], 3]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([0=>1, 1=>[11, 22, 33], 2=>3]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([0=>1, '1'=>[11, 22, 33], 2=>3]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([0=>1, 2=>3, 1=>[11, 22, 33]]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([1, [11, 22, 33], 3, 'x'=>1]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test(['x'=>1, 1, [11, 22, 33], 3]);
+var_dump($foo->asJson());
+$foo->printJson();
+
+var_dump('---------------------');
+
+// now end with some nice data dumping tests on the final, large data object...
+// and let's use the newly unserialized object instance for fun..
+var_dump($new->asJson());
+var_dump($new->asJson(JSON_PRETTY_PRINT)); // manually controlling JSON output options
+$new->printPropertyDescriptions();
+echo str_repeat(PHP_EOL, 5);
+$new->printJson(false); // automatic printing but without pretty-print enabled
+echo str_repeat(PHP_EOL, 5);
+$new->getSelfObject()->printJson(); // printing a sub-object (only safe if obj is non-NULL)
+echo str_repeat(PHP_EOL, 5);
+// $new->getSelfArray()->printJson(); // would not work on PHP arrays, obviously
+$new->getSelfArray()[0]->printJson(); // works, since that array entry is an obj
+echo str_repeat(PHP_EOL, 5);
+$new->printJson(); // <-- Debug heaven! ;-)
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testPropertyDefinitionNamespaces.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testPropertyDefinitionNamespaces.php
new file mode 100755
index 0000000..7fc1a39
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testPropertyDefinitionNamespaces.php
@@ -0,0 +1,173 @@
+ 'string',
+ ];
+ }
+}
+
+namespace Foo\Deeper {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+
+ class NoExtendsClass
+ {
+ }
+
+ class MyClass extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = ['foo' => 'string'];
+ }
+}
+
+namespace Other\Space\VerySpace {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+
+ class VeryDeepInSpace extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = [
+ 'deepspacenine' => 'string',
+ ];
+ }
+}
+
+namespace Other\Space {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+ use LazyJsonMapper\Property\PropertyDefinition;
+
+ class OtherClass extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = [
+ 'from_other_class' => 'string',
+ ];
+ }
+
+ class MyClass extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = [
+ 'foo' => 'int',
+ // tests handling of missing relative classes (the warning will
+ // display the namespace of this class which defined the property):
+ // 'missing_relative' => 'NoSuchClass', // uncomment to test
+ // tests support for relative classes within same namespace:
+ 'relative_class_path' => 'OtherClass',
+ 'relative_sub_class_path' => 'VerySpace\VeryDeepInSpace',
+ // and global overrides (the "\" prefix makes PropertyDefinition
+ // use the global namespace instead):
+ 'global_class_path' => '\Foo\Deeper\MyClass',
+ // just for fun, let's import a class map too, via relative:
+ // (can be done via global or relative paths)
+ VerySpace\VeryDeepInSpace::class,
+ ];
+ }
+
+ $resolved = new MyClass();
+ var_dump($resolved);
+}
+
+namespace Foo\Other\Space {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+
+ // This class is here to show that new $x->propType() construction technique
+ // is a bad idea since it may lead to relative resolving like this one, if
+ // the global path cannot be found.
+ class MyClass extends LazyJsonMapper
+ {
+ }
+}
+
+namespace Foo {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+ use LazyJsonMapper\Property\PropertyDefinition;
+
+ var_dump(\Other\Space\MyClass::class);
+ var_dump(class_exists('\Other\Space\MyClass'));
+ var_dump(class_exists('\Other\Space\\\MyClass'));
+ var_dump(Deeper\MyClass::class);
+ var_dump(__NAMESPACE__);
+
+ echo "-----\n";
+
+ // test various combinations of namespaces and class prefixes:
+ // $x = new PropertyDefinition('\MyClass', __NAMESPACE__);
+ // $x = new PropertyDefinition('\MyClass\Deeper', __NAMESPACE__);
+ // $x = new PropertyDefinition('MyClass', __NAMESPACE__);
+ // $x = new PropertyDefinition('MyClass\Deeper', __NAMESPACE__);
+ // $x = new PropertyDefinition('MyClass');
+ // $x = new PropertyDefinition('\MyClass');
+ // $x = new PropertyDefinition('MyClass\Deeper');
+ // var_dump($x);
+
+ // test a valid relative path (and the cleanup/normalization of a bad name).
+ $x = new PropertyDefinition('deePER\MYClass[][]', __NAMESPACE__);
+ var_dump($x);
+ var_dump($x->asString());
+ $y = new $x->propType(); // BAD! WE ALWAYS THE GLOBAL PATH, DO NOT USE THIS
+ // always use getStrictClassPath() instead!
+ var_dump($y); // \Foo\Deeper\MyClass instance
+
+ // test a valid path in other space (via global path)
+ $x = new PropertyDefinition('\Other\SPACe\MYCLASS[][]', __NAMESPACE__);
+ var_dump($x);
+ var_dump($x->asString());
+
+ $type = "Deeper\MyClass"; // PHP would resolve this locally due to no \
+ $y = new $type();
+ var_dump($y);
+
+ $type = "\Deeper\MyClass";
+ $y = new $type();
+ var_dump($y);
+
+ echo "------\n";
+ var_dump($x);
+ $y = new $x->propType(); // BAD IDEA! This field has no "\" prefix and may not
+ // resolve to the intended class in all situations
+ // correct way for extra safety is always:
+ $strictClassPath = $x->getStrictClassPath();
+ var_dump($strictClassPath);
+ $y = new $strictClassPath();
+
+ var_dump($y); // \Other\Space\MyClass instance
+
+ // test bad class warning (no extends)
+ // $x = new PropertyDefinition('deePER\noextendsCLASS', __NAMESPACE__);
+
+ // test bad class warning via mistyped basic typename:
+ // $x = new PropertyDefinition('ints', __NAMESPACE__);
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testUserFeatureToggling.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testUserFeatureToggling.php
new file mode 100755
index 0000000..e011523
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testUserFeatureToggling.php
@@ -0,0 +1,205 @@
+ 'string',
+ 'bar' => 'string',
+ ];
+}
+
+// --------------------------------------------------------
+
+// Test class that disallows functions but allows properties.
+
+class DisallowsFunctions extends CoreMap
+{
+ const ALLOW_VIRTUAL_FUNCTIONS = false;
+
+ public function getBar()
+ {
+ return $this->_getProperty('bar');
+ }
+}
+
+$jsonData = ['foo' => 'hello', 'bar' => 'world'];
+
+$x = new DisallowsFunctions($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+$x->printJson();
+
+// works since we have overridden that function manually
+printf("getBar(): \"%s\"\n", $x->getBar());
+
+// works since we allow direct property access in the class above
+$x->bar = 'changed via direct virtual property access';
+
+// look at the new value
+printf("getBar(): \"%s\"\n", $x->getBar());
+
+// does not work since we have no setter "setBar()" in the class above
+try {
+ $x->setBar('xyzzy');
+} catch (\Exception $e) {
+ printf("setBar(): %s\n", $e->getMessage());
+}
+
+// try all function variations of the "foo" property. none should work.
+try {
+ $x->hasFoo();
+} catch (\Exception $e) {
+ printf("hasFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->isFoo();
+} catch (\Exception $e) {
+ printf("isFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->getFoo();
+} catch (\Exception $e) {
+ printf("getFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->setFoo();
+} catch (\Exception $e) {
+ printf("setFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->unsetFoo();
+} catch (\Exception $e) {
+ printf("unsetFoo(): %s\n", $e->getMessage());
+}
+
+// --------------------------------------------------------
+
+// Test class that disallows properties but allows functions.
+
+class DisallowsProperties extends CoreMap
+{
+ const ALLOW_VIRTUAL_PROPERTIES = false;
+}
+
+$jsonData = ['foo' => 'hello', 'bar' => 'world'];
+
+$x = new DisallowsProperties($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+$x->printJson();
+
+// works since we allow functions
+printf("getBar(): \"%s\"\n", $x->getBar());
+$x->setBar('changed via virtual setBar() function acccess');
+
+// look at the new value
+printf("getBar(): \"%s\"\n", $x->getBar());
+
+// try all property acccess variations of the "foo" property. none should work.
+try {
+ $test = $x->foo;
+} catch (\Exception $e) {
+ printf("__get() via x->foo: %s\n", $e->getMessage());
+}
+
+try {
+ $x->foo[] = 'test'; // this __get()-trigger will fail too
+} catch (\Exception $e) {
+ printf("__get() via x->foo[]: %s\n", $e->getMessage());
+}
+
+try {
+ $x->foo = 'xyz';
+} catch (\Exception $e) {
+ printf("__set() via x->foo = ...: %s\n", $e->getMessage());
+}
+
+try {
+ isset($x->foo);
+} catch (\Exception $e) {
+ printf("__isset() via isset(x->foo): %s\n", $e->getMessage());
+}
+
+try {
+ empty($x->foo);
+} catch (\Exception $e) {
+ printf("__isset() via empty(x->foo): %s\n", $e->getMessage());
+}
+
+try {
+ unset($x->foo);
+} catch (\Exception $e) {
+ printf("__unset() via unset(x->foo): %s\n", $e->getMessage());
+}
+
+// --------------------------------------------------------
+
+// Test class that disallows both.
+
+class DisallowsBoth extends CoreMap
+{
+ const ALLOW_VIRTUAL_PROPERTIES = false;
+ const ALLOW_VIRTUAL_FUNCTIONS = false;
+}
+
+$x = new DisallowsBoth($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+
+try {
+ $test = $x->foo;
+} catch (\Exception $e) {
+ printf("__get() via x->foo: %s\n", $e->getMessage());
+}
+
+try {
+ $x->getFoo();
+} catch (\Exception $e) {
+ printf("getFoo(): %s\n", $e->getMessage());
+}
+
+// --------------------------------------------------------
+
+// Test class that extends "DisallowsBoth" and re-allows both.
+
+class ReallowsBoth extends DisallowsBoth
+{
+ const ALLOW_VIRTUAL_PROPERTIES = true;
+ const ALLOW_VIRTUAL_FUNCTIONS = true;
+}
+
+$x = new ReallowsBoth($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+
+printf("getFoo(): \"%s\"\n", $x->getFoo());
+printf("x->bar: \"%s\"\n", $x->bar);
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/nonRecursiveArrays.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/nonRecursiveArrays.php
new file mode 100755
index 0000000..0b07092
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/nonRecursiveArrays.php
@@ -0,0 +1,402 @@
+current());
+ }
+}
+
+// Algorithm v1: The initial idea I had...
+function array_flat_topdown_traverse(
+ array &$input)
+{
+ // Traverse top-down, processing level by level (going deeper and deeper).
+ $workStack = [&$input]; // The stack processes one array level at a time.
+ $nextStack = []; // Next stack with all deeper arrays found on this level.
+ $currentDepth = 1; // First level of input array should count from 1.
+ while (!empty($workStack)) {
+ // Pop a direct reference off the start of our FIFO stack.
+ reset($workStack);
+ $firstKey = key($workStack);
+ $pointer = &$workStack[$firstKey];
+ unset($workStack[$firstKey]);
+
+ // Now we're ready to act on the popped stack element...
+ foreach ($pointer as $k => &$v) {
+ // printf(
+ // "[D] %d %s\"%s\":%s\n",
+ // $currentDepth,
+ // str_repeat('-', $currentDepth),
+ // $k,
+ // is_array($v) ? '[]' : var_export($v, true)
+ // );
+
+ // Analyze the current array-child...
+ if (is_array($v)) {
+ // Add the discovered child-array to the end of the next-stack.
+ $nextStack[] = &$v;
+ } else {
+ // The child is a non-array element... Send it to the callback!
+ // TODO: Give callback key + value ref + array depth
+ }
+ }
+
+ // If the work-stack is finished, switch to the next (deeper) stack.
+ if (empty($workStack)) {
+ $workStack = $nextStack;
+ $nextStack = [];
+ $currentDepth++;
+ }
+ }
+}
+
+// Algorithm v2: Avoids two count() calls per stack-element iteration.
+function array_flat_topdown_traverse2(
+ array &$input)
+{
+ // Traverse top-down, processing level by level (going deeper and deeper).
+ $workStack = [&$input]; // The stack processes one array level at a time.
+ $workStackSize = 1; // Hardcoded result of count($workStack).
+ $nextStack = []; // Next stack with all deeper arrays found on this level.
+ $currentDepth = 1; // First level of input array should count from 1.
+ while ($workStackSize > 0) {
+ // Pop a direct reference off the start of our FIFO stack.
+ reset($workStack);
+ $firstKey = key($workStack);
+ $pointer = &$workStack[$firstKey];
+ unset($workStack[$firstKey]);
+ $workStackSize--;
+
+ // Now we're ready to act on the popped stack element...
+ foreach ($pointer as $k => &$v) {
+ // printf(
+ // "[D] %d %s\"%s\":%s\n",
+ // $currentDepth,
+ // str_repeat('-', $currentDepth),
+ // $k,
+ // is_array($v) ? '[]' : var_export($v, true)
+ // );
+
+ // Analyze the current array-child...
+ if (is_array($v)) {
+ // Add the discovered child-array to the end of the next-stack.
+ $nextStack[] = &$v;
+ } else {
+ // The child is a non-array element... Send it to the callback!
+ // TODO: Give callback key + value ref + array depth
+ }
+ }
+
+ // If the work-stack is finished, switch to the next (deeper) stack.
+ if ($workStackSize <= 0) {
+ // NOTE: There's no need to assign to workStack by reference to
+ // avoid copy-on-write. Because when we set nextStack to a new
+ // value, PHP will realize that workStack is the only instance.
+ // In fact, by-ref is slower it also needs an unset($nextStack)
+ // call to break its own reference before doing $nextStack = [].
+ $workStack = $nextStack;
+ $workStackSize = count($workStack);
+ $nextStack = [];
+ $currentDepth++;
+ }
+ }
+}
+
+// Regular, old-school recursive function calls.
+function array_recursive_traverse(
+ array &$input,
+ $currentDepth = 1)
+{
+ // Recursion adds 1 level to the function call stack
+ // per depth-level of the array:
+ // debug_print_backtrace();
+
+ $nextDepth = $currentDepth + 1;
+ foreach ($input as $k => &$v) {
+ // printf(
+ // "[D] %d %s\"%s\":%s\n",
+ // $currentDepth,
+ // str_repeat('-', $currentDepth),
+ // $k,
+ // is_array($v) ? '[]' : var_export($v, true)
+ // );
+
+ if (is_array($v)) {
+ array_recursive_traverse($v, $nextDepth);
+ }
+ }
+}
+
+// Build an array data tree.
+function generateData(
+ $depth)
+{
+ $data = [];
+ $pointer = &$data;
+ for ($d = 0; $d < $depth; ++$d) {
+ // $subArr = ['x', 'y', ['z'], ['foo'], [['xxyyzzyy']]]; // Harder data.
+ $subArr = ['x', 'y', 'z', ['foo'], 'xxyyzzyy'];
+ $pointer[] = &$subArr;
+ $pointer = &$subArr;
+ unset($subArr); // Unlink, otherwise next assignment overwrites pointer.
+ }
+
+ return $data;
+}
+
+// Run a single test.
+function runTest(
+ $description,
+ $data,
+ $algorithm,
+ $iterations)
+{
+ $start = microtime(true);
+
+ switch ($algorithm) {
+ case 'array_flat_topdown_traverse':
+ for ($i = 0; $i < $iterations; ++$i) {
+ array_flat_topdown_traverse($data);
+ }
+ break;
+ case 'array_flat_topdown_traverse2':
+ for ($i = 0; $i < $iterations; ++$i) {
+ array_flat_topdown_traverse2($data);
+ }
+ break;
+ case 'array_recursive_traverse':
+ for ($i = 0; $i < $iterations; ++$i) {
+ array_recursive_traverse($data);
+ }
+ break;
+ case 'RecursiveIteratorIterator':
+ for ($i = 0; $i < $iterations; ++$i) {
+ $iterator = new \RecursiveIteratorIterator(
+ new RecursiveArrayOnlyIterator($data),
+ \RecursiveIteratorIterator::SELF_FIRST
+ );
+ // foreach ($iterator as $key => $value) {
+ // // echo "$key => $value\n";
+ // }
+ // This iteration method takes 15% longer than foreach,
+ // but it's the only way to get the depth, which we
+ // absolutely need to know in this project.
+ for (; $iterator->valid(); $iterator->next()) {
+ $key = $iterator->key();
+ $value = $iterator->current();
+ $depth = $iterator->getDepth();
+ }
+ }
+ break;
+ }
+
+ printf(
+ "%dx %s %s: %.0f milliseconds.\n",
+ $iterations, $description, $algorithm,
+ 1000 * (microtime(true) - $start)
+ );
+}
+
+// Run all algorithm tests at once.
+function runTestMulti(
+ $description,
+ $data,
+ $iterations,
+ $iteratorTestMode) // Time-saver: -1 off, 0 divide by ten, 1 normal
+{
+ if ($iteratorTestMode > -1) {
+ runTest($description, $data, 'RecursiveIteratorIterator',
+ $iteratorTestMode > 0 ? $iterations : (int) floor($iterations / 10));
+ }
+ runTest($description, $data, 'array_flat_topdown_traverse', $iterations);
+ runTest($description, $data, 'array_flat_topdown_traverse2', $iterations);
+ runTest($description, $data, 'array_recursive_traverse', $iterations);
+}
+
+// Special data test-tree for use together with debug-output (uncomment it in
+// the algorithms), to verify that each algorithm detects the current depth.
+$data = [
+ '1one' => [
+ '1two' => [
+ '1three-nonarr1' => '1',
+ '1three' => [
+ '1four-nonarr1' => '2',
+ '1four' => [
+ '1five' => '3',
+ ],
+ ],
+ ],
+ ],
+ '2one-nonarr1' => null,
+ '3one' => [
+ '3two-1' => [
+ '3three-nonarr1' => '4',
+ '3three-1' => [
+ '3four-1' => [
+ '3five-1' => [
+ '3six-nonarr1' => '5',
+ ],
+ ],
+ ],
+ '3three-nonarr2' => '6',
+ ],
+ '3two-nonarr1' => '7',
+ '3two-2' => [
+ '3three-nonarr3' => '8',
+ ],
+ '3two-nonarr2' => '9',
+ ],
+];
+
+// The "RecursiveIteratorIterator" is ~10x slower, so this setting saves time.
+// Values: -1 off, 0 divide by ten, 1 normal.
+$iteratorTestMode = -1;
+
+// Globally extend/shorten the amount of test iterations, or "1" for no scaling.
+$testScale = 1;
+
+// Output PHP version details.
+printf("[Running %dx tests on PHP version %s]\n", $testScale, PHP_VERSION);
+printf("[RecursiveIteratorIterator Tests: %s]\n", ['Disabled', 'Shortened by /10', 'Enabled'][$iteratorTestMode + 1]);
+
+// Test with normal data (6 levels deep).
+runTestMulti('normal-6', generateData(6), $testScale * 500000, $iteratorTestMode);
+
+// Test unusual data (50 levels deep).
+runTestMulti('rare-50', generateData(50), $testScale * 100000, $iteratorTestMode);
+
+// Now test with insanely deeply nested data.
+runTestMulti('insane-500', generateData(500), $testScale * 10000, $iteratorTestMode);
+
+// Let's do one final test with even more disgustingly deep arrays.
+runTestMulti('hellish-5000', generateData(5000), $testScale * 100, $iteratorTestMode);
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/prefixSplitAlgorithms.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/prefixSplitAlgorithms.php
new file mode 100755
index 0000000..8c0acfc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/devtools/prefixSplitAlgorithms.php
@@ -0,0 +1,270 @@
+ 'string',
+ 'users' => 'User[]',
+ ];
+}
+
+class User extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'name' => 'string',
+ 'details' => 'UserDetails',
+ ];
+}
+
+class UserDetails extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'age' => 'int',
+ 'hired_date' => 'string',
+ ];
+}
+
+/*
+ * Now simply create the root object (a Section), and give it the section JSON:
+ */
+
+$section = new Section(json_decode($section_json, true));
+
+/*
+ * Here's how you would look at the available properties and their functions:
+ */
+
+$section->printPropertyDescriptions();
+
+/*
+ * Now let's access some data, via the functions shown by the previous command.
+ */
+
+printf("\n\nData Output Example:\n\nSection Title: %s\n", $section->getSectionTitle());
+foreach ($section->getUsers() as $user) {
+ // $user->printPropertyDescriptions(); // Uncomment to see User-functions.
+ // $user->printJson(); // Uncomment to look at the JSON data for that user.
+ printf(
+ "- User: %s\n Age: %s\n Hired Date: %s\n",
+ $user->getName(),
+ $user->getDetails()->getAge(),
+ $user->getDetails()->getHiredDate()
+ );
+}
+echo "\n\n";
+
+/*
+ * Lastly, let's demonstrate looking at the actual internal JSON data:
+ */
+
+// var_dump($section->asJson()); // Uncomment to get a JSON data string instead.
+// var_dump($section->getUsers()[0]->asJson()); // Property sub-object encoding.
+// var_dump(json_encode($section->getUsers())); // Property non-object values
+// // solvable via `json_encode()`.
+$section->printJson();
+
+/*
+ * There are a million other functions and features. Have fun exploring!
+ * Simply read the main src/LazyJsonMapper.php file for all documentation!
+ */
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/examples/import_example.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/examples/import_example.php
new file mode 100755
index 0000000..50d7014
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/examples/import_example.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'age' => 'int',
+ ];
+}
+
+class AdvancedUser extends User
+{
+ const JSON_PROPERTY_MAP = [
+ 'advanced' => 'string',
+ ];
+}
+
+class SomethingElse extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ AdvancedUser::class, // This is an "import class map"-command.
+ 'otherprop' => 'float[][]',
+ ];
+}
+
+/*
+ * This demonstrates that SomethingElse contains all fields from AdvancedUser
+ * (which in turn inherited User's map), as well as having its own "otherprop".
+ */
+
+$somethingelse = new SomethingElse();
+$somethingelse->printPropertyDescriptions();
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/examples/inheritance_example.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/examples/inheritance_example.php
new file mode 100755
index 0000000..899c187
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/examples/inheritance_example.php
@@ -0,0 +1,51 @@
+ 'string',
+ 'age' => 'int',
+ ];
+}
+
+class AdvancedUser extends User
+{
+ const JSON_PROPERTY_MAP = [
+ 'advanced' => 'string',
+ ];
+}
+
+/*
+ * This demonstrates that AdvancedUser contains all fields from User.
+ */
+
+$advanceduser = new AdvancedUser(json_decode($advanceduser_json, true));
+
+$advanceduser->printPropertyDescriptions();
+printf(
+ "\n\nName: %s\nAge: %s\nAdvanced: %s\n",
+ $advanceduser->getName(),
+ $advanceduser->getAge(),
+ $advanceduser->getAdvanced()
+);
+$advanceduser->printJson();
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/examples/unpredictable_data.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/examples/unpredictable_data.php
new file mode 100755
index 0000000..2d366a5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/examples/unpredictable_data.php
@@ -0,0 +1,705 @@
+ 'string',
+ ];
+}
+
+/**
+ * This container supports unpredictable keys.
+ *
+ * Note that it doesn't define any properties in its JSON_PROPERTY_MAP. Instead,
+ * we use a custom getter which reads _all_ JSON properties and processes them!
+ *
+ * In other words, this _entire_ container will hold nothing but unpredictable
+ * data keys. (If you have a mixture, there are other examples further down.)
+ */
+class UnpredictableContainer extends LazyJsonMapper
+{
+ /**
+ * Cached key-value object translations.
+ *
+ * This is optional, but speeds up repeated calls to `getUserList()`, since
+ * it will store all previously converted objects for future re-use.
+ *
+ * @var array
+ */
+ protected $_userList;
+
+ /**
+ * Get the list of users.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return array Associative array of key-value pairs, keyed by userID.
+ */
+ public function getUserList()
+ {
+ // Tell LazyJsonMapper to give us all of our internal data as an array.
+ // NOTE: This creates a COPY of the array. It's not attached to the main
+ // storage, which means that any changes we make to the new array aren't
+ // going to affect the actual LazyJsonMapper object's own data. That
+ // won't matter for pure getter-based objects like this one (which is
+ // all you'll need in 99.9999999% of cases where you'll want to read
+ // unpredictable data). So if the user serializes this LazyJsonMapper
+ // object, or gets it "asJson()", etc, then they'd still be serializing
+ // the original, untouched internal data, which is exactly what we are
+ // retrieving here. So everything works out perfectly as long as we
+ // don't have any setters! (But setters will be demonstrated later.)
+ if ($this->_userList === null) {
+ // Get a copy of the internal data as an array, and cache it.
+ // NOTE: This only throws if there is unmappable data internally.
+ $this->_userList = $this->asArray(); // Throws.
+ }
+
+ // Loop through the list of JSON properties and convert every "array"
+ // value to a User object instead. That's because all of JSON's nested,
+ // not-yet-converted "JSON object values" are associative sub-arrays.
+ foreach ($this->_userList as &$value) {
+ if (is_array($value)) {
+ // NOTE: The User constructor can only throw if a custom _init()
+ // fails or if its class property map can't be compiled.
+ $value = new User($value); // Throws.
+ }
+ }
+
+ // Now just return our key-value array. The inner array values have
+ // been converted to User objects, which makes them easy to work with.
+ return $this->_userList;
+ }
+}
+
+/*
+ * Let's try it out with two sets of data that use unpredictable (numeric) keys!
+ */
+
+/*
+ * This JSON data consists of various numeric keys pointing at "User"-objects.
+ */
+$unpredictable_json1 = <<printJson();
+
+/*
+ * Now let's call our custom getter which retrieves all values and gives them to
+ * us as User objects. As you can see, their contents perfectly match the
+ * printJson() results, since our custom getter doesn't manipulate any data.
+ */
+foreach ($unpredictable1->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Let's do the same for another set of data with other keys...
+ */
+$unpredictable_json2 = <<printJson();
+
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Alright, that's all great... but what if we want to manipulate the data?
+ *
+ * Well, the loop above still has $userId and $userInfo variables that point at
+ * the last element it looped to... so let's try using those to set data! What
+ * can possibly go wrong!? ;-)
+ */
+printf("*** Changing the name of user #%s to 'FOO'.\n", $userId);
+$userInfo->setName('FOO');
+
+/*
+ * Now let's look at the contents of the "getUserList()" call...
+ *
+ * Because the user-list is cached internally in our custom object, it already
+ * refers to the exact same object instances... So it will indeed have updated.
+ */
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * But wait... what about the actual internal object data? Let's look at that!
+ *
+ * The name... is not updated...
+ */
+$unpredictable2->printJson();
+
+/*
+ * Uh oh... since we're using a custom getter which fetches the data totally
+ * detached from the core LazyJsonMapper storage, we will need another solution!
+ *
+ * These extra steps are ONLY needed if you want setters that actually work...
+ */
+
+/**
+ * Extends UnpredictableContainer with a custom setter.
+ *
+ * We could have put these functions on the main UnpredictableContainer, but
+ * this example is clearer by only explaining it here as a separate step for
+ * those who need setters...
+ */
+class UnpredictableContainerWithSetter extends UnpredictableContainer
+{
+ /**
+ * Syncs the user list cache with the LazyJsonMapper core storage.
+ *
+ * Note that we could build these steps into `setUserList()`. But by having
+ * it as a separate function, you are able to just update specific User
+ * objects and then call `syncUserList()` to write them back to the core.
+ *
+ * @throws LazyJsonMapperException
+ */
+ public function syncUserList()
+ {
+ // If no internal cache exists yet, get its value from LazyJsonMapper.
+ if ($this->_userList === null) {
+ $this->getUserList(); // Builds our "_userList" variable. Throws.
+ }
+
+ // Now, we need to create a new, internal LazyJsonMapper data array for
+ // our object. In undefined (unmapped) properties, you are ONLY allowed
+ // to store basic data types. Not objects. So we will need to convert
+ // all User objects to real array data. Otherwise OTHER calls would fail
+ // due to invalid internal data when we try things like `printJson()`.
+ $newObjectData = [];
+ foreach ($this->_userList as $k => $v) {
+ $newObjectData[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ // Now give the new object data to LazyJsonMapper, which ensures that it
+ // contains all of the same values as our updated cache! This replaces
+ // the ENTIRE internal JSON property storage, so be aware of that!
+ $this->assignObjectData($newObjectData); // Throws.
+ }
+
+ /**
+ * Replace the entire user list with another list.
+ *
+ * @param array $userList Associative array of User objects keyed by userID.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setUserList(
+ array $userList)
+ {
+ // First of all, let's instantly save their new value to our own
+ // internal cache, since our cache supports User objects and doesn't
+ // have to do any special transformations...
+ $this->_userList = $userList;
+
+ // Now sync our internal cache with the LazyJsonMapper object...! :-)
+ $this->syncUserList(); // Throws.
+
+ // Setters should always return "$this" to make them chainable!
+ return $this;
+ }
+}
+
+/*
+ * Alright, let's try the example again, with the new container that has proper
+ * support for setters!
+ */
+echo "\n\nUnpredictable data #2, with setter:\n";
+$unpredictable2 = new UnpredictableContainerWithSetter(json_decode($unpredictable_json2, true));
+
+$unpredictable2->printJson();
+
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Now let's try manipulating the data again!
+ */
+printf("*** Changing the name of user #%s to 'FOO'.\n", $userId);
+$userInfo->setName('FOO');
+
+/*
+ * Now let's look at the contents of the "getUserList()" call...
+ *
+ * Just as before, it has been updated since we use an internal cache which
+ * already refers to the exact same object instances... so our update is there!
+ */
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Now let's look at the internal LazyJsonMapper data storage...
+ *
+ * It is NOT updated. As expected...
+ */
+$unpredictable2->printJson();
+
+/*
+ * Now let's SYNC our cache back to the internal LazyJsonMapper data storage!
+ *
+ * And voila...!
+ */
+$unpredictable2->syncUserList();
+$unpredictable2->printJson();
+
+/*
+ * Let's also try our custom setter, which takes a whole array of User objects
+ * and does the syncing automatically!
+ */
+$users = $unpredictable2->getUserList();
+foreach ($users as $userId => $userInfo) {
+ $userInfo->setName('Updated...'.$userId.'!');
+}
+$unpredictable2->setUserList($users); // Replaces entire cache and syncs!
+
+/*
+ * Now let's look at the contents of our cache AND the LazyJsonMapper data!
+ *
+ * Everything has been updated, since our "setUserList()" call replaced the
+ * entire cache contents AND synced the new cache contents to the core storage.
+ */
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+$unpredictable2->printJson();
+
+/**
+ * This class handles a mixture of knowable and unknowable data.
+ *
+ * Let's end this with one more example... What if you DON'T want to define a
+ * whole custom container? What if you only want a SPECIFIC value within your
+ * map to be handling unpredictable keys? You could achieve that as follows!
+ *
+ * This class will contain less comments, for brevity. You hopefully understand
+ * all of the major workflow concepts by now! We will only explain new concepts.
+ * And this class won't show any `syncEmployees()` function, since you should
+ * understand how to do that now if you want that feature.
+ */
+class UnpredictableMixtureContainer extends LazyJsonMapper
+{
+ protected $_employees;
+
+ const JSON_PROPERTY_MAP = [
+ // This is a statically named "manager" property, which is a User.
+ 'manager' => 'User',
+ // This is a statically named "employees" property, which consists of
+ // unpredictable key-value pairs, keyed by userID.
+ 'employees' => 'mixed', // Must be 'mixed' (aka '') to allow sub-arrays.
+ ];
+
+ /**
+ * Get the list of employees.
+ *
+ * NOTE: This overrides the normal, automatic LazyJsonMapper getter! By
+ * naming our function identically, we override its behavior!
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return array
+ */
+ public function getEmployees()
+ {
+ if ($this->_employees === null) {
+ // Use the internal _getProperty() API to read the actual property.
+ // NOTE: This function only throws if "employees" contains any
+ // invalid non-basic data. Only basic PHP types are accepted.
+ $this->_employees = $this->_getProperty('employees'); // Throws.
+ }
+
+ foreach ($this->_employees as &$value) {
+ if (is_array($value)) {
+ $value = new User($value); // Throws.
+ }
+ }
+
+ return $this->_employees;
+ }
+
+ /**
+ * Set the list of employees.
+ *
+ * NOTE: This overrides the normal, automatic LazyJsonMapper setter! By
+ * naming our function identically, we override its behavior!
+ *
+ * @param array $employees
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setEmployees(
+ array $employees)
+ {
+ $this->_employees = $employees;
+
+ // We now need to construct a new, inner value for the property. Since
+ // it's a "mixed" property, it only accepts basic PHP types.
+ $newInnerValue = [];
+ foreach ($this->_employees as $k => $v) {
+ $newInnerValue[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ // Now use the internal _setProperty() API to set the new inner value.
+ // NOTE: This function only throws if the new value contains any
+ // invalid non-basic data. Only basic PHP types are accepted.
+ $this->_setProperty('employees', $newInnerValue);
+
+ return $this;
+ }
+}
+
+/*
+ * Let's try it out!
+ */
+$unpredictable_json3 = <<printJson();
+printf("The manager's name is: %s\n", $unpredictable3->getManager()->getName());
+foreach ($unpredictable3->getEmployees() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * Now let's set some new employee data... This time, just for fun, let's give
+ * it a virtual JSON array which we construct manually.
+ */
+$unpredictable3->setEmployees([
+ // Let's provide one value as a User object, since our setter supports
+ // the conversion of User objects back into plain arrays.
+ '10' => new User(['name' => 'Employee Ten']),
+ // However, we could also just provide the data as an array, since that's
+ // the goal that our setter performs... this is for really advanced users.
+ // Don't blame us if you break things by using this shortcut! ;-)
+ '11' => ['name' => 'Employee Eleven'],
+]);
+
+/*
+ * Let's also update the manager, which is done via a normal, automatic
+ * LazyJsonMapper setter, since that property is completely defined. And since
+ * the "manager" object is owned by the LazyJsonMapper core, we're able to chain
+ * its getters and setters to select the manager's User object and set its name!
+ */
+$unpredictable3->getManager()->setName('New Manager!');
+
+/*
+ * Now let's look at all current data again!
+ */
+$unpredictable3->printJson();
+printf("The manager's name is: %s\n", $unpredictable3->getManager()->getName());
+foreach ($unpredictable3->getEmployees() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * But wait... there's yet another way to solve this!
+ *
+ * Since we know that our "mixture container"'s `employees` value is a key-value
+ * storage of User objects, in other words it's "an unpredictable key-value
+ * container", then we CAN just tell LazyJsonMapper to map that property to a
+ * `UnpredictableContainer[WithSetter]` which we defined earlier. That way, the
+ * "employees" values are handled automatically by a neat sub-container.
+ *
+ * In fact, we could do something even better! We could define a basic
+ * "unpredictable keys" container, and then define subclasses of it for various
+ * types of unpredictable containers. Let's do that instead!
+ */
+
+/**
+ * This class defines a core "untyped" container of unpredictable data-keys.
+ *
+ * Unpredictable data is data with keys that cannot be known ahead of time, such
+ * as objects whose values are keyed by things like user IDs.
+ *
+ * Here's an example of such unpredictable data: `{"9323":{"name":"foo"}}`
+ *
+ * The `getData()` function retrieves all key-value pairs, converted to the
+ * optional `$_type` (if one is set via a subclass). And `setData()` writes
+ * the new data back into the core `LazyJsonMapper` container. Most people will
+ * not need to use the setter. It's just provided as an extra feature.
+ *
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class CoreUnpredictableContainer extends LazyJsonMapper
+{
+ // Let's disable direct access to this container via anything other than
+ // the functions that WE define ourselves! That way, people cannot use
+ // virtual properties/functions to manipulate the core data storage.
+ const ALLOW_VIRTUAL_PROPERTIES = false;
+ const ALLOW_VIRTUAL_FUNCTIONS = false;
+
+ /**
+ * Data cache to avoid constant processing every time the getter is used.
+ *
+ * @var array
+ */
+ protected $_cache;
+
+ /**
+ * What class-type to convert all sub-object values into.
+ *
+ * Defaults to no conversion. Override this value via a subclass!
+ *
+ * Always use the FULL path to the target class, with a leading backslash!
+ * The leading backslash ensures that it's found via a strict, global path.
+ *
+ * Example: `\Foo\BarClass`.
+ *
+ * @var string
+ */
+ protected $_type;
+
+ /**
+ * Get the data array of this unpredictable container.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ if ($this->_cache === null) {
+ $this->_cache = $this->asArray(); // Throws.
+ }
+
+ if ($this->_type !== null) {
+ foreach ($this->_cache as &$value) {
+ if (is_array($value)) {
+ $value = new $this->_type($value); // Throws.
+ }
+ }
+ }
+
+ return $this->_cache;
+ }
+
+ /**
+ * Set the data array of this unpredictable container.
+ *
+ * @param array $value The new data array.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setData(
+ array $value)
+ {
+ $this->_cache = $value;
+
+ $newObjectData = [];
+ foreach ($this->_cache as $k => $v) {
+ $newObjectData[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ $this->assignObjectData($newObjectData); // Throws.
+
+ return $this;
+ }
+}
+
+/**
+ * This class defines an "unpredictable container of User objects".
+ *
+ * It's very easy to define other containers. Simply create them like this and
+ * override their `$_type` property to any other valid LazyJsonMapper class.
+ */
+class UserUnpredictableContainer extends CoreUnpredictableContainer
+{
+ // The FULL path to the target class, with leading backslash!
+ // NOTE: The leading backslash ensures it's found via a strict, global path.
+ protected $_type = '\User';
+}
+
+/**
+ * This is our new and improved, final class!
+ *
+ * Here is our final object for mapping our unpredictable "employees" data...
+ * As you can see, it is much easier to create this class now that we have
+ * defined a core, re-usable "unpredictable container" above.
+ */
+class UnpredictableMixtureContainerTwo extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ // This holds a regular User object.
+ 'manager' => 'User',
+ // This property is an unpredictable container of User objets.
+ 'employees' => 'UserUnpredictableContainer',
+ ];
+}
+
+/*
+ * Let's try it out!
+ */
+$unpredictable_json4 = <<printJson();
+printf("The manager's name is: %s\n", $unpredictable4->getManager()->getName());
+foreach ($unpredictable4->getEmployees()->getData() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * And let's update the value of the inner, unpredictable container!
+ *
+ * The container itself takes care of updating the LazyJsonMapper data storage!
+ */
+$unpredictable4->getEmployees()->setData([
+ '123' => ['name' => 'Final Employee 123'],
+ '456' => ['name' => 'Final Employee 456'],
+]);
+
+/*
+ * Now finish by looking at all of the data again, via the LazyJsonMapper core
+ * object and via the `getEmployees()` object's cache... They are identical!
+ */
+$unpredictable4->printJson();
+printf("The manager's name is: %s\n", $unpredictable4->getManager()->getName());
+foreach ($unpredictable4->getEmployees()->getData() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * And that's it! Hopefully you NEVER have to work with nasty, unpredictable
+ * data like this. If you're able to control the JSON format, you should always
+ * design it properly with known keys instead. But at least you now know about
+ * multiple great methods for working with objects that have unpredictable keys!
+ *
+ * There are other methods too, such as if your class contains a blend of known
+ * (defined) keys and unpredictable keys, in which case you'd need to fetch via
+ * `asArray()` as in the `UnpredictableContainer` example, and then you'd simply
+ * filter out all known keys from your cache, to get _just_ the unpredictable
+ * keys. And if you need setters in that scenario, your setter functions would
+ * be a bit more complex since they would need to use `assignObjectData()` AND
+ * would have to provide BOTH the known data AND the unknown data. One way of
+ * doing that would be to use `_getProperty()` to merge in the CURRENT values of
+ * each core property into your NEW object-data array BEFORE you assign it. But
+ * I leave that extremely rare scenario as an excercise for you, dear reader!
+ *
+ * You should go out and adapt all of this code to fit your own needs! ;-)
+ *
+ * For example, if you don't need the unpredictable values to be converted
+ * to/from a specific object type, then simply skip the conversion code.
+ *
+ * If you don't need caching for performance, then skip the caching code.
+ *
+ * If you don't need setters/syncing, then skip all of that code.
+ *
+ * You may also want to disable the user-options `ALLOW_VIRTUAL_PROPERTIES` and
+ * `ALLOW_VIRTUAL_FUNCTIONS` on your unpredictable containers, so users cannot
+ * manipulate the unpredictable data via LazyJsonMapper's automatic functions!
+ * That's what we did for CoreUnpredictableContainer, to ensure that nobody can
+ * destroy its internal data by touching it directly. They can only manipulate
+ * the data via its safe, public `getData()` and `setData()` functions!
+ *
+ * And you may perhaps prefer to write a custom base-class which has a few other
+ * helper-functions for doing these kinds of data translations, caching and
+ * syncing, to make your own work easier (such as the CoreUnpredictableContainer
+ * example above). That way, your various sub-classes could just call your
+ * internal helper functions to do the required processing automatically! :-)
+ *
+ * The possibilities are endless! Have fun!
+ */
+echo "\n\nHave fun!\n";
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/phpdoc.dist.xml b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/phpdoc.dist.xml
new file mode 100755
index 0000000..acd326c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/phpdoc.dist.xml
@@ -0,0 +1,24 @@
+
+
+ LazyJsonMapper
+
+ docs/cache
+ utf8
+
+ TODO
+ FIXME
+
+
+ php
+
+
+
+ docs/output
+
+
+
+
+
+ src
+
+
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/phpunit.xml.dist
new file mode 100755
index 0000000..2fa7a92
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/phpunit.xml.dist
@@ -0,0 +1,9 @@
+
+
+
+
+ tests
+
+
+
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/BadPropertyDefinitionException.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/BadPropertyDefinitionException.php
new file mode 100755
index 0000000..24fb2f8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/BadPropertyDefinitionException.php
@@ -0,0 +1,29 @@
+_badClassNameA = $badClassNameA;
+ if (!is_string($badClassNameB)) {
+ $badClassNameB = null;
+ }
+ $this->_badClassNameB = $badClassNameB;
+
+ if ($badClassNameA !== null && $badClassNameB !== null) {
+ parent::__construct(sprintf(
+ 'Circular reference between classes "%s" and "%s" in JSON property map import instruction.',
+ $badClassNameA, $badClassNameB
+ ));
+ } else {
+ parent::__construct('Circular reference in JSON property map import instruction.');
+ }
+ }
+
+ /**
+ * Get the name of the first class involved in the circular map.
+ *
+ * @return string|null
+ */
+ public function getBadClassNameA()
+ {
+ return $this->_badClassNameA;
+ }
+
+ /**
+ * Get the name of the second class involved in the circular map.
+ *
+ * @return string|null
+ */
+ public function getBadClassNameB()
+ {
+ return $this->_badClassNameB;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/LazyJsonMapperException.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/LazyJsonMapperException.php
new file mode 100755
index 0000000..f9b6e51
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/LazyJsonMapperException.php
@@ -0,0 +1,29 @@
+bad_definitions = array_merge_recursive(
+ $this->bad_definitions,
+ $other->bad_definitions
+ );
+ $this->missing_definitions = array_merge_recursive(
+ $this->missing_definitions,
+ $other->missing_definitions
+ );
+ }
+
+ /**
+ * Adds a problem description to the internal state.
+ *
+ * @param string $definitionSource The class which has the problem.
+ * @param string $problemType Type of problem. Either `bad_definitions`
+ * or `missing_definitions`.
+ * @param string $problemMessage A message describing the actual problem.
+ *
+ * @throws LazyJsonMapperException If any of the parameters are invalid.
+ *
+ * @see ClassAnalysis::hasProblems()
+ */
+ public function addProblem(
+ $definitionSource,
+ $problemType,
+ $problemMessage)
+ {
+ if (!is_string($definitionSource) || !is_string($problemMessage)) {
+ throw new LazyJsonMapperException('The definitionSource and problemMessage parameters must be strings.');
+ }
+ if ($problemType !== 'bad_definitions' && $problemType !== 'missing_definitions') {
+ throw new LazyJsonMapperException('The problemType parameter must be either "bad_definitions" or "missing_definitions".');
+ }
+
+ if (!isset($this->{$problemType}[$definitionSource])) {
+ $this->{$problemType}[$definitionSource] = [];
+ }
+
+ $this->{$problemType}[$definitionSource][] = $problemMessage;
+ }
+
+ /**
+ * Convert the per-class arrays to sorted lists of missing/bad properties.
+ *
+ * Removes all duplicate messages and sorts everything nicely. It is
+ * recommended to only call this function a single time, on the final
+ * `ClassAnalysis` object (after all other steps are finished).
+ */
+ public function sortProblemLists()
+ {
+ foreach (['bad_definitions', 'missing_definitions'] as $problemType) {
+ // Sort the problem messages within each class.
+ foreach ($this->{$problemType} as $definitionSource => $messages) {
+ $this->{$problemType}[$definitionSource] = array_unique($messages);
+ natcasesort($this->{$problemType}[$definitionSource]);
+ }
+
+ // Sort the outer array (the class names).
+ ksort($this->{$problemType}, SORT_NATURAL | SORT_FLAG_CASE);
+ }
+ }
+
+ /**
+ * Check whether any problems were discovered.
+ *
+ * In that case, it's recommended to use `generateNiceSummaries()` to format
+ * user-readable messages about the problems.
+ *
+ * @return bool
+ *
+ * @see ClassAnalysis::generateNiceSummaries()
+ */
+ public function hasProblems()
+ {
+ return !empty($this->bad_definitions) || !empty($this->missing_definitions);
+ }
+
+ /**
+ * Generates nicely formatted problem summaries for this class analysis.
+ *
+ * @return array An array with formatted messages for every type of analysis
+ * which actually had errors, keyed by the problem type. If no
+ * errors, the returned array will be empty.
+ *
+ * @see ClassAnalysis::hasProblems()
+ * @see ClassAnalysis::generateNiceSummariesAsString()
+ */
+ public function generateNiceSummaries()
+ {
+ $problemSummaries = [];
+ if (!empty($this->bad_definitions)) {
+ // Build a nice string containing all encountered bad definitions.
+ $strSubChunks = [];
+ foreach ($this->bad_definitions as $className => $messages) {
+ $strSubChunks[] = sprintf(
+ '"%s": ([\'%s\'])',
+ $className, implode('\'], and [\'', $messages)
+ );
+ }
+ $problemSummaries['bad_definitions'] = sprintf(
+ 'Bad JSON property definitions in %s.',
+ implode(', and in ', $strSubChunks)
+ );
+ }
+ if (!empty($this->missing_definitions)) {
+ // Build a nice string containing all missing class properties.
+ $strSubChunks = [];
+ foreach ($this->missing_definitions as $className => $messages) {
+ $strSubChunks[] = sprintf(
+ '"%s": ("%s")',
+ $className, implode('", "', $messages)
+ );
+ }
+ $problemSummaries['missing_definitions'] = sprintf(
+ 'Missing JSON property definitions in %s.',
+ implode(', and in ', $strSubChunks)
+ );
+ }
+
+ return $problemSummaries;
+ }
+
+ /**
+ * Generates a nicely formatted problem summary string for this class analysis.
+ *
+ * This helper combines all summaries and returns them as a single string
+ * (rather than as an array), which is very useful when displaying ALL
+ * errors to a user as a message.
+ *
+ * @return string The final string. Is an empty string if no errors exist.
+ *
+ * @see ClassAnalysis::hasProblems()
+ * @see ClassAnalysis::generateNiceSummaries()
+ */
+ public function generateNiceSummariesAsString()
+ {
+ return implode(' ', $this->generateNiceSummaries());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Export/PropertyDescription.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Export/PropertyDescription.php
new file mode 100755
index 0000000..8212140
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Export/PropertyDescription.php
@@ -0,0 +1,250 @@
+asString();
+ $isRelativeTypePath = false;
+ if ($allowRelativeTypePath && $propDef->isObjectType) {
+ $relativeType = Utilities::createRelativeClassPath(
+ Utilities::splitStrictClassPath($strictOwnerClassPath),
+ // NOTE: This is safe because the asString() value is always a
+ // strict class path with optional [] suffixes (which will not
+ // interfere with the splitting process).
+ Utilities::splitStrictClassPath($finalType)
+ );
+ if ($finalType !== $relativeType) {
+ $isRelativeTypePath = true;
+ $finalType = $relativeType;
+ }
+ }
+
+ // Perform the translation from the property name to its FunctionCase.
+ $translation = new PropertyTranslation($propName); // Throws.
+
+ // Now just store all of the user-friendly descriptions.
+ $this->owner = $strictOwnerClassPath;
+ $this->is_defined = $isDefined;
+ $this->name = $propName;
+ $this->type = $finalType;
+ $this->is_basic_type = !$propDef->isObjectType;
+ $this->is_relative_type_path = $isRelativeTypePath;
+ $this->function_has = sprintf('bool has%s()', $translation->propFuncCase);
+ $this->function_is = sprintf('bool is%s()', $translation->propFuncCase);
+ $this->function_get = sprintf('%s get%s()', $finalType, $translation->propFuncCase);
+ $this->function_set = sprintf('$this set%s(%s $value)', $translation->propFuncCase, $finalType);
+ $this->function_unset = sprintf('$this unset%s()', $translation->propFuncCase);
+ $this->func_case = $translation->propFuncCase;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/LazyJsonMapper.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/LazyJsonMapper.php
new file mode 100755
index 0000000..6f837a6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/LazyJsonMapper.php
@@ -0,0 +1,2692 @@
+some_value` and `$item->some_value = 'foo'`.
+ *
+ * The virtual properties can be disabled via an option.
+ *
+ * - Automatically provides object-oriented "virtual functions", which let you
+ * interact with the data in a fully object-oriented way via functions such
+ * as `$item->getSomeValue()` and `$item->setSomeValue('foo')`. We support a
+ * large range of different functions for manipulating the JSON data, and you
+ * can see a list of all available function names for all of your properties
+ * by simply running `$item->printPropertyDescriptions()`.
+ *
+ * The virtual functions can be disabled via an option.
+ *
+ * - Includes the `LazyDoctor` tool, which _automatically_ documents all of
+ * your `LazyJsonMapper`-based classes so that their virtual properties and
+ * functions become _fully_ visible to your IDE and to various intelligent
+ * code analysis tools. It also performs class diagnostics by compiling all
+ * of your class property maps, which means that you can be 100% sure that
+ * all of your maps are valid (compilable) if this tool runs successfully.
+ *
+ * - We provide a complete, internal API which your subclasses can use to
+ * interact with the data inside of the JSON container. This allows you to
+ * easily override the automatic functions or create additional functions
+ * for your objects. To override core functions, just define a function with
+ * the exact same name on your object and make it do whatever you want to.
+ *
+ * Here are some examples of function overriding:
+ *
+ * ```php
+ * public function getFoo()
+ * {
+ * // try to read property, and handle a special "current_time" value.
+ * $value = $this->_getProperty('foo');
+ * if ($value === 'current_time') { return time(); }
+ * return $value;
+ * }
+ * public function setFoo(
+ * $value)
+ * {
+ * // if they try to set value to "md5", use a special value instead.
+ * if ($value === 'md5') { $value = md5(time()); }
+ * return $this->_setProperty('foo', $value);
+ * }
+ * ```
+ *
+ * - All mapping/data conversion is done "lazily", on a per-property basis.
+ * When you access a property, that specific property is mapped/converted to
+ * the proper type as defined by your class property map. No time or memory
+ * is wasted converting properties that you never touch.
+ *
+ * - Strong type-system. The class property map controls the exact types and
+ * array depths. You can fully trust that the data you get/set will match
+ * your specifications. Invalid data that mismatches the spec is impossible.
+ *
+ * - Advanced settings system. Everything is easily configured via PHP class
+ * constants, which means that your class-settings are stateless (there's no
+ * need for any special "settings/mapper object" to keep track of settings),
+ * and that all settings are immutable constants (which means that they are
+ * reliable and can never mutate at runtime, so that you can fully trust that
+ * classes will always behave as-defined in their code).
+ *
+ * If you want to override multiple core settings identically for all of your
+ * classes, then simply create a subclass of `LazyJsonMapper` and configure
+ * all of your settings on that, and then derive all of your other classes
+ * from your re-configured subclass!
+ *
+ * - The world's most advanced mapper definition system. Your class property
+ * maps are defined in an easy PHPdoc-style format, and support multilevel
+ * arrays (such as `int[][]` for "an array of arrays of ints"), relative
+ * types (so you can map properties to classes/objects that are relative to
+ * the namespace of the class property map), parent inheritance (all of your
+ * parent `extends`-hierarchy's maps will be included in your final property
+ * map) and even multiple inheritance (you can literally "import" an infinite
+ * number of other maps into your class, which don't come from your own
+ * parent `extends`-hierarchy).
+ *
+ * - Inheriting properties from parent classes or importing properties from
+ * other classes is a zero-cost operation thanks to how efficient our
+ * property map compiler is. So feel free to import everything you need.
+ * You can even use this system to create importable classes that just hold
+ * "collections" of shared properties, which you import into other classes.
+ *
+ * - The class property maps are compiled a single time per-class at runtime,
+ * the first time a class is used. The compilation process fully verifies
+ * and compiles all property definitions, all parent maps, all inherited
+ * maps, and all maps of all classes you link properties to.
+ *
+ * If there are any compilation problems due to a badly written map anywhere
+ * in your hierarchy, you will be shown the exact problem in great detail.
+ *
+ * In case of success, the compiled and verified maps are all stored in an
+ * incredibly memory-efficient format in a global cache which is shared by
+ * your whole PHP runtime, which means that anything in your code or in any
+ * other libraries which accesses the same classes will all share the cached
+ * compilations of those classes, for maximum memory efficiency.
+ *
+ * - You are also able to access JSON properties that haven't been defined in
+ * the class property map. In that case, they are treated as undefined and
+ * untyped (`mixed`) and there won't be any automatic type-conversion of such
+ * properties, but it can still be handy in a pinch.
+ *
+ * - There are lots of data export/output options for your object's JSON data,
+ * to get it back out of the object again: As a multi-level array, as nested
+ * stdClass objects, or as a JSON string representation of your object.
+ *
+ * - We include a whole assortment of incredibly advanced debugging features:
+ *
+ * You can run the constructor with `$requireAnalysis` to ensure that all
+ * of your JSON data is successfully mapped according to your class property
+ * map, and that you haven't missed defining any properties that exist in the
+ * data. In case of any problems, the analysis message will give you a full
+ * list of all problems encountered in your entire JSON data hierarchy.
+ *
+ * For your class property maps themselves, you can run functions such as
+ * `printPropertyDescriptions()` to see a complete list of all properties and
+ * how they are defined. This helps debug your class inheritance and imports
+ * to visually see what your final class map looks like, and it also helps
+ * users see all available properties and all of their virtual functions.
+ *
+ * And for the JSON data, you can use functions such as `printJson()` to get
+ * a beautiful view of all internal JSON data, which is incredibly helpful
+ * when you (or your users) need to figure out what's available inside the
+ * current object instance's data storage.
+ *
+ * - A fine-grained and logical exception-system which ensures that you can
+ * always trust the behavior of your objects and can catch problems easily.
+ * And everything we throw is _always_ based on `LazyJsonMapperException`,
+ * which means that you can simply catch that single "root" exception
+ * whenever you don't care about fine-grained differentiation.
+ *
+ * - Clean and modular code ensures stability and future extensibility.
+ *
+ * - Deep code documentation explains everything you could ever wonder about.
+ *
+ * - Lastly, we implement super-efficient object serialization. Everything is
+ * stored in a tightly packed format which minimizes data size when you need
+ * to transfer your objects between runtimes.
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class LazyJsonMapper implements Serializable, JsonSerializable
+{
+ /**
+ * Whether "direct virtual properties" access is enabled.
+ *
+ * This constant can be overridden in your subclasses to toggle the option.
+ *
+ * It is recommended that all of your brand new projects create a subclass
+ * of `LazyJsonMapper` which disables this option, and then you simply
+ * derive all of your other classes from that subclass so that nothing in
+ * your project has direct virtual property access enabled.
+ *
+ * That's because there are many quirks with how PHP implements virtual
+ * properties, which can lead to performance slowdowns and lack of data
+ * validation if you use direct access in CERTAIN scenarios (without writing
+ * proper code). And if users are going to be interacting with YOUR library,
+ * then you SHOULD disable virtual properties so that your users don't fail
+ * to implement their proper usage. PHP's virtual property quirks are all
+ * described in the documentation for `__get()`. Please read it and decide
+ * whether you want to allow "direct virtual properties".
+ *
+ * The only reason why this feature is enabled by default is that it helps
+ * people easily migrate from legacy projects.
+ *
+ * @var bool
+ *
+ * @see LazyJsonMapper::__get() More details about virtual properties.
+ */
+ const ALLOW_VIRTUAL_PROPERTIES = true;
+
+ /**
+ * Whether "virtual functions" access is enabled.
+ *
+ * This constant can be overridden in your subclasses to toggle the option.
+ *
+ * It's the recommended access method. It always ensures complete data
+ * validation and the highest performance. However, some people may want to
+ * disable it in favor of manually defining your object's functions instead.
+ * (This library provides a complete internal API for manipulating the
+ * object's JSON data from your own custom class-functions.)
+ *
+ * @var bool
+ *
+ * @see LazyJsonMapper::__call() More details about virtual functions.
+ */
+ const ALLOW_VIRTUAL_FUNCTIONS = true;
+
+ /**
+ * Whether we should cache all magic virtual function translations.
+ *
+ * This constant can be overridden in your subclasses to toggle caching.
+ *
+ * The cache stores all of the current runtime's encountered magic/virtual
+ * function name translations, such as `SomeProperty` (via `getSomeProperty`
+ * and `setSomeProperty`, etc) and the fact that it refers to a JSON
+ * property that will be named either `some_property` (if snake style)
+ * or `someProperty` (if camel style).
+ *
+ * Entries are added to the cache one-by-one every time you call a function
+ * on an uncached property name for the first time. The next time you call a
+ * function which accesses the same property name, then there's no need to
+ * analyze the function name again (to figure out what JSON data property it
+ * refers to), since the translation is already cached. It is cached on a
+ * per-property name basis. So a SINGLE cached translation of `SomeProperty`
+ * will be shared by ALL function calls related to that property (such as
+ * all of the core functions; `hasSomeProperty`, `isSomeProperty`,
+ * `getSomeProperty`, `setSomeProperty`, and `unsetSomeProperty`).
+ *
+ * At the cost of a very tiny bit of RAM, this caching greatly speeds up
+ * magic function calls so that they only take about 16% as long as they
+ * would without the cache. However, they're still very fast even without a
+ * cache, and you may be accessing so many different properties that you'd
+ * prefer to avoid a cache for memory reasons (if you prefer lower memory
+ * needs over pure speed). The choice is yours.
+ *
+ * To calculate the memory needs of your own project's virtual function
+ * cache, you simply need to know that an average cache entry uses ~248.6
+ * bytes of RAM on PHP 7 and ~510.4 bytes on PHP 5. Note that the size per
+ * name translation is small enough to be measured in BYTES (NOT kilobytes)!
+ *
+ * Those averages were calculated from a huge sample-size of 10000 function
+ * names, and are very accurate for real-world JSON data.
+ *
+ * To calculate your own cache memory size, think about how many different
+ * JSON properties you'll need to access in your project. Most normal
+ * projects would probably only access at most 100 different property names
+ * (such as `getComments`, `setTime`, etc), since you most likely won't
+ * access every property in the JSON data. So only count the properties
+ * you'll access. And also remember that identical property names used
+ * across different classes, and all the different functions for accessing
+ * the same property, will still only require a SINGLE global cache entry
+ * per property name.
+ *
+ * The storage needs for 100 different property name translations would be
+ * as follows. As you can see, the cache is very memory-efficient:
+ *
+ * - PHP 7: 100x ~248.6 bytes = 24 860 bytes (~24.9 kilobytes).
+ * - PHP 5: 100x ~510.4 bytes = 51 040 bytes (~51.1 kilobytes).
+ *
+ * (If you're still using PHP 5, you should really consider upgrading to
+ * PHP 7 with its better memory efficiency and its much faster engine!)
+ *
+ * It is highly recommended to use caching, and it is enabled by default
+ * unless you override the constant in your class, as follows:
+ *
+ * ```php
+ * class MyUncachedLazyJsonMapper extends LazyJsonMapper
+ * {
+ * const USE_MAGIC_LOOKUP_CACHE = false;
+ * }
+ * ```
+ *
+ * With the above code, `MyUncachedLazyJsonMapper` (and anything extending
+ * from it) would run without using the magic function translation cache,
+ * which means that all magic "virtual function" calls on instances of that
+ * class would have to redo their property translations every time.
+ *
+ * @var bool
+ */
+ const USE_MAGIC_LOOKUP_CACHE = true;
+
+ /**
+ * Tells us how to map JSON properties to internal PHP types or objects.
+ *
+ * This constant can be overridden in your subclasses, to add custom JSON
+ * mapping definitions to your class. It is recommended to always do so.
+ *
+ * The value must be an array of key-value pairs, where the key is the name
+ * of a JSON property and the value is a string with the definition for that
+ * specific property in PHPdoc-style. We will then perform automatic, strict
+ * lazy-conversion of the value to the target type whenever you access that
+ * property. (However, be aware that we always allow `NULL` values in any
+ * property, and will never enforce/convert those to the target type.)
+ *
+ * The following built-in types are supported: `bool`, `int`, `float`,
+ * `string`. The JSON data will be type-converted to match that type.
+ *
+ * Note that if the type is set to `mixed` or an empty string `""` instead,
+ * then the property will allow any of the built-in types (`bool`, `int`,
+ * `float`, `string`, `NULL` (as always) and multi-level arrays of any of
+ * those types). There simply won't be any "forced" type-conversion to any
+ * specific basic type for that property since you haven't defined any
+ * strict type.
+ *
+ * Setting up untyped properties is still useful even if you don't know its
+ * type, since defining the property in the class map ensures that you can
+ * always get/set/access/use that property even if it's unavailable in the
+ * current object instance's internal JSON data.
+ *
+ * You can also map values to objects. In the case of objects (classes), the
+ * classes MUST inherit from `LazyJsonMapper` so they support all necessary
+ * mapping features. To assign a class to a property, you can either write a
+ * FULL (global) class path starting with a leading backslash `\`, such as
+ * `\Foo\Bar\Baz`. Alternatively, you can use a RELATIVE path related to the
+ * namespace of the CURRENT MAP'S class, such as `Bar\Baz` (if your current
+ * class is `\Foo\Whatever`, then it'd be interpreted as `\Foo\Bar\Baz`).
+ * Anything that DOESN'T start with a leading backslash is interpreted as a
+ * relative class path! Just be aware that your class `use`-statements are
+ * completely ignored since PHP has no mechanism to let us detect those.
+ *
+ * It's also possible to map properties to the core `LazyJsonMapper` object
+ * if you simply want an object-oriented container for its data without
+ * writing a custom class. You can do that by defining the property type as
+ * either `\LazyJsonMapper\LazyJsonMapper`, or as `LazyJsonMapper` (which is
+ * a special shortcut that ALWAYS resolves to the core class). But it's
+ * always best to write a proper class, so that its properties are reliable.
+ *
+ * Lastly, you can map "arrays of TYPE" as well. Simply add one or more `[]`
+ * brackets to the end of the type. For example, `int[]` means "array of
+ * ints", and `\Foo[][][]` means "array of arrays of arrays of `\Foo`
+ * objects". It may be easier to understand those mentally if you read them
+ * backwards and say the words "array of" every time you see a `[]` bracket.
+ * (Note: You can also use the array notation with `mixed[][]`, which would
+ * then strictly define that the value MUST be mixed data at an exact depth
+ * of 2 arrays, and that no further arrays are allowed deeper than that.)
+ *
+ * The assigned types and array-depths are STRICTLY validated. That's an
+ * integral part of the `LazyJsonMapper` container, since it guarantees your
+ * class property map interface will be strictly followed, and that you can
+ * fully TRUST the data you're interacting with. If your map says that the
+ * array data is at depth 8 and consists of `YourObject` objects, then we'll
+ * make sure the data is indeed at depth 8 and their value type is correct!
+ *
+ * That goes for arrays too. If you define an `int[]` array of ints, then
+ * we'll ensure that the array has sequential numeric keys starting at 0 and
+ * going up without any gaps, exactly as a JSON array is supposed to be. And
+ * if you define an object `YourObject`, then we'll ensure that the input
+ * JSON data consists of an array with associative keys there (which is used
+ * as the object properties), exactly as a JSON object is supposed to be.
+ *
+ * In other words, you can TOTALLY trust your data if you've assigned types!
+ *
+ * Example property map:
+ *
+ * ```php
+ * const JSON_PROPERTY_MAP = [
+ * 'some_string' => 'string',
+ * 'an_object' => '\YourProject\YourObject',
+ * 'array_of_numbers' => 'int[]',
+ * 'array_of_objects' => 'RelativeObject[]',
+ * 'untyped_value' => '', // shorthand for 'mixed' below:
+ * 'another_untyped_value' => 'mixed', // allows multilevel arrays
+ * 'deep_arr_of_arrs_of_int' => 'int[][]',
+ * 'array_of_mixed' => 'mixed[]', // enforces 1-level depth
+ * 'array_of_generic_objects' => 'LazyJsonMapper[]',
+ * ];
+ * ```
+ *
+ * Also note that we automatically inherit all of the class maps from all
+ * parents higher up in your object-inheritance chain. In case of a property
+ * name clash, the deepest child class definition of it takes precedence.
+ *
+ * It is also worth knowing that we support a special "multiple inheritance"
+ * instruction which allows you to "import the map" from one or more other
+ * classes. The imported maps will be merged onto your current class, as if
+ * the entire hierarchy of properties from the target class (including the
+ * target class' inherited parents and own imports) had been "pasted inside"
+ * your class' property map at that point. And you don't have to worry
+ * about carefully writing your inheritance relationships, because we have
+ * full protection against circular references (when two objects try to
+ * import each other) and will safely detect that issue at runtime whenever
+ * we try to compile the maps of either of those bad classes with their
+ * badly written imports.
+ *
+ * The instruction for importing other maps is very simple: You just have to
+ * add an unkeyed array element with a reference to the other class. The
+ * other class must simply refer to the relative or full path of the class,
+ * followed by `::class`.
+ *
+ * Example of importing one or more maps from other classes:
+ *
+ * ```php
+ * const JSON_PROPERTY_MAP = [
+ * 'my_own_prop' => 'string',
+ * OtherClass::class, // relative class path
+ * 'redefined_prop' => float,
+ * \OtherNamespace\SomeClass::class, // full class path
+ * ];
+ * ```
+ *
+ * The imports are resolved in the exact order they're listed in the array.
+ * Any property name clashes will always choose the version from the latest
+ * statement in the list. So in this example, our class would first inherit
+ * all of its own parent (`extends`) class maps. Then it would add/overwrite
+ * `my_own_prop`. Then it imports everything from `OtherClass` (and its
+ * parents/imports). Then it adds/overwrites `redefined_prop` (useful if you
+ * want to re-define some property that was inherited from the other class).
+ * And lastly, it imports everything from `\OtherNamespace\SomeClass` (and
+ * its parents/imports). As long as there are no circular references,
+ * everything will compile successfully and you will end up with a very
+ * advanced final map! And any other class which later inherits from
+ * (extends) or imports YOUR class will inherit the same advanced map from
+ * you! This is REAL "multiple inheritance"! ;-)
+ *
+ * Also note that "relative class path" properties are properly inherited as
+ * pointing to whatever was relative to the original inherited/imported
+ * class where the property was defined. You don't have to worry about that.
+ *
+ * The only thing you should keep in mind is that we ONLY import the maps.
+ * We do NOT import any functions from the imported classes (such as its
+ * overridden functions), since there's no way for us to affect how PHP
+ * resolves function calls to "copy" functions from one class to another. If
+ * you need their functions, then you should simply `extends` from the class
+ * to get true PHP inheritance instead of only importing its map. Or you
+ * could just simply put your functions in PHP Traits and Interfaces so
+ * that they can be re-used by various classes without needing inheritance!
+ *
+ * Lastly, here's an important general note about imports and inheritance:
+ * You DON'T have to worry about "increased memory usage" when importing or
+ * inheriting tons of other classes. The runtime "map compiler" is extremely
+ * efficient and re-uses the already-compiled properties inherited from its
+ * parents/imports, meaning that property inheritance is a ZERO-COST memory
+ * operation! In fact, re-definitions are also zero-cost as long as your
+ * class re-defines a property to the exact same type that it had already
+ * inherited/imported. Such as `Base: foo:string, Child: foo:string`. In
+ * that case, we detect that the definitions are identical and just re-use
+ * the compiled property from `Base` instead. It's only when a class adds
+ * NEW/MODIFIED properties that memory usage increases a little bit! In
+ * other words, inheritance/imports are a very good thing, and are always a
+ * better idea than manually writing similar lists of properties in various
+ * unrelated (not extending each other) classes. In fact, if you have a
+ * situation where many classes need similar properties, then it's a GREAT
+ * idea to create special re-usable "property collection" classes and then
+ * simply importing THOSE into ALL of the different classes that need those
+ * sets of properties! You can even use that technique to get around PHP's
+ * `use`-statement limitations (simply place a "property collection" class
+ * container in the namespace that had all of your `use`-classes, and define
+ * its properties via easy, relative paths there, and then simply make your
+ * other classes import THAT container to get all of its properties).
+ *
+ * As you can see, the mapping and inheritance system is extremely powerful!
+ *
+ * Note that the property maps are analyzed and compiled at runtime, the
+ * first time we encounter each class. After compilation, the compiled maps
+ * become immutable (cannot be modified). So there's no point trying to
+ * modify this variable at runtime. That's also partially the reason why
+ * this is defined through a constant, since they prevent you from modifying
+ * the array at runtime and you believing that it would update your map. The
+ * compiled maps are immutable at runtime for performance & safety reasons!
+ *
+ * Have fun!
+ *
+ * @var array
+ *
+ * @see LazyJsonMapper::printPropertyDescriptions() Easily looking at and
+ * debugging your final
+ * class property map.
+ * @see LazyJsonMapper::printJson() Looking at the JSON data
+ * contents of the current
+ * object instance.
+ * @see LazyJsonMapper::exportClassAnalysis() Looking for problems
+ * with your class map.
+ * However, the same test
+ * can also be achieved
+ * via `$requireAnalysis`
+ * as a constructor flag.
+ */
+ const JSON_PROPERTY_MAP = [];
+
+ /**
+ * Magic virtual function lookup cache.
+ *
+ * Globally shared across all instances of `LazyJsonMapper` classes. That
+ * saves tons of memory, since multiple different components/libraries in
+ * your project can use any functions they need, such as `getComments()`,
+ * and then all OTHER code that uses a `comments` property would share that
+ * cached translation too, which ensures maximum memory efficiency!
+ *
+ * It is private to prevent subclasses from modifying it.
+ *
+ * @var array
+ *
+ * @see LazyJsonMapper::clearGlobalMagicLookupCache()
+ */
+ private static $_magicLookupCache = [];
+
+ /**
+ * Storage container for compiled class property maps.
+ *
+ * Class maps are built at runtime the FIRST time we encounter each class.
+ * This cache is necessary so that we instantly have fully-validated maps
+ * without needing to constantly re-build/validate the class property maps.
+ *
+ * Compilation only happens the first time that we encounter the class
+ * during the current runtime (unless you manually decide to clear the
+ * cache at any point). And the compiled map format is extremely optimized
+ * and very memory-efficient (for example, subclasses and inherited classes
+ * all share the same compiled PropertyDefinition objects, and those objects
+ * themselves are extremely optimized). So you don't have to worry about
+ * memory usage at all.
+ *
+ * You should think about this exactly like PHP's own class loader/compiler.
+ * Whenever you request a class for the first time, PHP reads its .php file
+ * and parses and compiles its code and then keeps that class in memory for
+ * instant re-use. That's exactly like what this "property map cache" does.
+ * It compiles the maps the first time the class is used, and then it just
+ * stays in memory for instant re-use. So again, don't worry about it! :-)
+ *
+ * Globally shared across all instances of `LazyJsonMapper` classes. Which
+ * ensures that classes are truly only compiled ONCE during PHP's runtime,
+ * even when multiple parts of your project or other libraries use the same
+ * classes. And it also means that there's no need for you to manually
+ * maintain any kind of personal "cache-storage class instance". The global
+ * storage takes care of that for you effortlessly!
+ *
+ * It is private to prevent subclasses from modifying it.
+ *
+ * @var PropertyMapCache
+ *
+ * @see LazyJsonMapper::clearGlobalPropertyMapCache()
+ */
+ private static $_propertyMapCache;
+
+ /**
+ * Direct reference to the current object's property definition cache.
+ *
+ * This is a reference. It isn't a copy. So the memory usage of each class
+ * instance stays identical since the same cache is shared among them all.
+ * Therefore, you don't have to worry about seeing this in `var_dump()`.
+ *
+ * It is private to prevent subclasses from modifying it.
+ *
+ * @var array
+ *
+ * @see LazyJsonMapper::printPropertyDescriptions() Easily looking at and
+ * debugging your final
+ * class property map.
+ */
+ private $_compiledPropertyMapLink;
+
+ /**
+ * Container for this specific object instance's JSON data.
+ *
+ * Due to the lazy-conversion of objects, some parts of the tree can be
+ * objects and others can be JSON sub-arrays that have not yet been turned
+ * into objects. That system ensures maximum memory and CPU efficiency.
+ *
+ * This container is private to prevent subclasses from modifying it. Use
+ * the protected functions to indirectly check/access/modify values instead.
+ *
+ * @var array
+ */
+ private $_objectData;
+
+ /**
+ * Constructor.
+ *
+ * You CANNOT override this constructor. That's because the constructor and
+ * its arguments and exact algorithm are too important to allow subclasses
+ * to risk breaking it (especially since you must then perfectly maintain an
+ * identical constructor argument list and types of thrown exceptions, and
+ * would always have to remember to call our constructor first, and so on).
+ *
+ * Furthermore, trying to have custom constructors on "JSON data container
+ * objects" MAKES NO SENSE AT ALL, since your JSON data objects can be
+ * created recursively and automatically from nested JSON object data. So
+ * there's no way you could run your object's custom constructors then!
+ *
+ * Instead, there is a separate `_init()` function which you can override in
+ * your subclasses, for safe custom initialization. And if you NEED to use
+ * some external variables in certain functions in your JSON data classes,
+ * then simply add those variables as arguments to those class-functions!
+ *
+ * Also note that there are many reasons why the JSON data must be provided
+ * as an array instead of as an object. Most importantly, the memory usage
+ * is much lower if you decode to an array (because a \stdClass requires as
+ * much RAM as an array WITH object overhead ON TOP of that), and also
+ * because the processing we do is more efficient when we have an array.
+ *
+ * Lastly, this seems like a prominent enough place to mention something
+ * that's VERY important if you are handling JSON data on 32-bit systems:
+ * PHP running as a 32-bit process does NOT support 64-bit JSON integers
+ * natively. It will cast them to floats instead, which may lose precision,
+ * and fails completely if you then cast those numbers to strings, since you
+ * will get float notation or `INT_MAX` capping depending on what you are
+ * doing with the data (ie `printf('%d')` would give `INT_MAX`, and `%s`
+ * would give scientific notation like `8.472E+22`). All of those situations
+ * can be avoided by simply telling PHP to decode big numbers to strings:
+ *
+ * ```php
+ * json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+ * ```
+ *
+ * If you are ever going to handle 64-bit integers on 32-bit systems, then
+ * you will NEED to do the above, as well as ensuring that all of your class
+ * `JSON_PROPERTY_MAP` definitions never use any `int` field type (since
+ * that would convert the safe strings back into truncated 32-bit integers).
+ * Instead, you should define those fields as `string`-type in your map.
+ *
+ * @param array $objectData Decoded JSON data as an array (NOT object).
+ * @param bool $requireAnalysis Whether to throw an exception if any of the
+ * raw JSON properties aren't defined in the
+ * class property map (are missing), or if any
+ * of the encountered property-classes are bad
+ * (fail to construct with the JSON data;
+ * usually only possible due to a custom
+ * `_init()` or when its raw JSON data value
+ * wasn't actually an object at all), or any
+ * problems with array-depth or type coercion.
+ * This option is very useful for debugging
+ * when creating/updating your custom classes.
+ * But BEWARE that it causes the WHOLE
+ * `$objectData` tree to be recursively parsed
+ * and analyzed, which is really TERRIBLE for
+ * performance. So DON'T use this permanently!
+ *
+ * @throws LazyJsonMapperException If the class hierarchy contains any
+ * invalid JSON property map/definition
+ * which prevents successful class map
+ * compilation, or if JSON data analysis
+ * requested and any of the map's property
+ * definitions are bad/missing. Also if a
+ * custom class `_init()` threw any kind of
+ * exception.
+ *
+ * @see LazyJsonMapper::_init()
+ * @see LazyJsonMapper::assignObjectData()
+ */
+ final public function __construct(
+ array $objectData = [],
+ $requireAnalysis = false)
+ {
+ // Create the global property map cache object if not yet initialized.
+ if (self::$_propertyMapCache === null) {
+ self::$_propertyMapCache = new PropertyMapCache();
+ }
+
+ // Compile this class property map if not yet built and cached.
+ // NOTE: Validates all definitions the first time and throws if invalid
+ // definitions, invalid map, or if there are any circular map imports.
+ // NOTE: This aborts compilation, automatically rolls back the failed
+ // compilation attempt, AND throws - at the FIRST-discovered problem
+ // during map compilation! It will only show the single, specific
+ // problem which caused compilation to fail. So it won't inundate the
+ // user's screen with all individual error message in case there are
+ // more problems. If their class maps have multiple problems that
+ // prevent compilation, they'll have to see and fix them one by one.
+ // But the user will only have to fix their maps once, since compilation
+ // issues are a core problem with their map and aren't a runtime issue!
+ $thisClassName = get_class($this);
+ if (!isset(self::$_propertyMapCache->classMaps[$thisClassName])) {
+ PropertyMapCompiler::compileClassPropertyMap( // Throws.
+ self::$_propertyMapCache,
+ $thisClassName
+ );
+ }
+
+ // Now link this class instance directly to its own property-cache, via
+ // direct REFERENCE for high performance (to avoid map array lookups).
+ // The fact that it's a link also avoids the risk of copy-on-write.
+ $this->_compiledPropertyMapLink = &self::$_propertyMapCache->classMaps[$thisClassName];
+
+ // Assign the JSON data, run optional analysis, and then _init().
+ $this->assignObjectData($objectData, $requireAnalysis); // Throws.
+ }
+
+ /**
+ * Assign a new internal JSON data array for this object.
+ *
+ * This is used by the constructor for assigning the initial internal data
+ * state, but can also be very useful for users who want to manually replace
+ * the contents of their object at a later time.
+ *
+ * For example, it might suit your project design better to first construct
+ * an empty object, and *then* pass it to some other function which actually
+ * fills it with the JSON data. This function allows you to achieve that.
+ *
+ * The entire internal data storage will be replaced with the new data.
+ *
+ * @param array $objectData Decoded JSON data as an array (NOT object).
+ * @param bool $requireAnalysis Whether to analyze the JSON data and throw
+ * if there are problems with mapping. See
+ * `__construct()` for more details.
+ *
+ * @throws LazyJsonMapperException If JSON data analysis requested and any
+ * of the map's property definitions are
+ * bad/missing. Also if a custom class
+ * `_init()` threw any kind of exception.
+ *
+ * @see LazyJsonMapper::__construct()
+ * @see LazyJsonMapper::_init()
+ */
+ final public function assignObjectData(
+ array $objectData = [],
+ $requireAnalysis = false)
+ {
+ // Save the provided JSON data array.
+ $this->_objectData = $objectData;
+
+ // Recursively look for missing/bad JSON properties, if scan requested.
+ // NOTE: "Bad" in this case includes things like fatal mismatches
+ // between the definition of a property and the actual JSON data for it.
+ // NOTE: This analysis includes ALL problems with the class and its
+ // entire hierarchy (recursively), which means that it can produce
+ // really long error messages. It will warn about ALL missing properties
+ // that exist in the data but are not defined in the class, and it will
+ // warn about ALL bad properties that existed in the data but cannot be
+ // mapped in the way that the class map claims.
+ if ($requireAnalysis) {
+ $analysis = $this->exportClassAnalysis(); // Never throws.
+ if ($analysis->hasProblems()) {
+ // Since there were problems, throw with all combined summaries.
+ throw new LazyJsonMapperException(
+ $analysis->generateNiceSummariesAsString()
+ );
+ }
+ }
+
+ // Call the custom initializer, where the subclass can do its own init.
+ // NOTE: This is necessary for safely encapsulating the subclass' code.
+ try {
+ $this->_init();
+ } catch (LazyUserException $e) {
+ throw $e; // Re-throw user-error as is, since it's a proper exception.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY other exception!
+ // Ensure that they didn't throw something dumb from their code.
+ // We'll even swallow the message, to truly discourage misuse.
+ throw new LazyUserException(
+ 'Invalid exception thrown by _init(). Must use LazyUserException.'
+ );
+ }
+ }
+
+ /**
+ * Initializer for custom subclass construction / data updates.
+ *
+ * This is where you can perform your custom subclass initialization, since
+ * you are unable to override the main constructor.
+ *
+ * We automatically run this function at the end of the normal constructor,
+ * as well as every time that you manually use `assignObjectData()` to
+ * replace the object's data. (When new data is assigned, you should treat
+ * yourself as a new object, which is why `_init()` will run again.)
+ *
+ * `WARNING:` Please RESIST the urge to touch ANY of the internal JSON data
+ * during this initialization. All data will always be automatically
+ * validated during actual retrieval and setting, so you can always trust
+ * that the final types WILL match your class property map definitions.
+ *
+ * Remember that any extra work you do in `_init()` will run EVERY time an
+ * instance of your object is created, even when those objects live within a
+ * main object. So if your function is heavy, it will slow down operation of
+ * the class; and your parsing of the properties would be counter-productive
+ * against the goal of "lazy parsing" of JSON data on a when-accessed basis!
+ *
+ * Instead, it's preferable to just override specific property getter and
+ * setter functions, to make your class return different default values if a
+ * certain internal data field is missing an expected value. But in general,
+ * your ultimate USER CODE should be responsible for most of THAT checking,
+ * by simply looking for `NULL` (denoting a missing value in the JSON data
+ * container), and then deciding what to do with that in your final project.
+ *
+ * Lastly, there are a few rules that MUST be followed by your `_init()`:
+ *
+ * 1. As explained above, but worth repeating again: Your `LazyJsonMapper`
+ * classes are supposed to be LIGHT-weight "JSON data containers". So
+ * please DO NOT treat this as a "standard class constructor". The more
+ * work you do in `_init()`, the slower your object creation is. And since
+ * your object is a JSON container which is automatically created from
+ * data, you can expect LOTS of instances of your class to be created
+ * during normal runtime! That's also why your `_init()` function does not
+ * get any parameters! That is to discourage misuse as a normal class.
+ *
+ * Also remember that you may not even need `_init()`, since you can simply
+ * give your properties default values instead of using `_init()`, such as
+ * by writing `public $myproperty = true;`. All instances of that object
+ * would then start with that value set to `TRUE` by default.
+ *
+ * 2. You can ONLY throw `LazyUserException`. All other exceptions will be
+ * blocked and turned into a generic `LazyUserException`. This is done to
+ * ensure that your custom init function cannot break the contract that the
+ * `LazyJsonMapper` constructor guarantees in its listed exceptions.
+ *
+ * (That's also the reason why all built-in functions except `_init()` are
+ * marked `final`: So that subclasses cannot break the `LazyJsonMapper`
+ * API/interface contract. If something IS a `LazyJsonMapper`, it MUST
+ * behave as a `LazyJsonMapper`, and the `final` functions guarantee that!)
+ *
+ * 3. If your class extends from `LazyJsonMapper`, then there's no need to
+ * call `parent::_init()` (since our core `_init()` does nothing). But if
+ * you extend from ANY OTHER CLASS, you MUST call your parent's init BEFORE
+ * doing any work, just to guarantee that your WHOLE parent-class hierarchy
+ * is fully initialized first.
+ *
+ * 4. Understand and accept the fact that your personal class properties
+ * will NOT be serialized if you serialize an object. Only the JSON data
+ * array will be kept. The `_init()` function will be called again when you
+ * unserialize the object data, as if your object had just been created for
+ * the first time. This is done to save space and to discourage misuse
+ * of your `LazyJsonMapper` containers.
+ *
+ * Remember that your classes are supposed to be lightweight JSON data
+ * containers, which give you a strongly typed, automatic, object-oriented
+ * interface to your data. Classes "with advanced constructors and tons of
+ * properties" are NOT suitable as JSON containers and belong ELSEWHERE in
+ * the rest of your project!
+ *
+ * @throws LazyUserException If there is any fatal error which prevents
+ * initialization. This stops object construction
+ * if this is the initial construction. However,
+ * it doesn't affect data-assignment if you throw
+ * during a later `assignObjectData()` call.
+ *
+ * @see LazyJsonMapper::assignObjectData()
+ */
+ protected function _init()
+ {
+ // This standard _init does nothing by default...
+
+ // Always call your parent's init FIRST, to avoid breaking your class
+ // hierarchy's necessary initializers. But that can be skipped if your
+ // direct parent is LazyJsonMapper, since we do nothing in our _init().
+
+ // parent::_init();
+
+ // After your parent hierarchy is initialized, you're welcome to perform
+ // YOUR OWN class initialization, such as setting up state variables...
+
+ // $this->someFlag = true;
+
+ // However, that kind of usage is highly discouraged, since your objects
+ // will lose their state again when serialized and then unserialized.
+ // Remember: Your class is meant to be a "light-weight JSON container",
+ // and NOT some ultra-advanced normal class. Always think about that!
+ }
+
+ /**
+ * Export human-readable descriptions of class/object instance properties.
+ *
+ * The defined (class property map) properties are always included. You can
+ * optionally choose to also include undefined properties that only exist in
+ * the current object instance's JSON data, but which aren't in the class.
+ * Such properties are dangerous, since they only exist in the current data.
+ *
+ * Furthermore, you can choose whether any class-types for properties should
+ * use absolute/global paths (ie `\Foo\Bar\Baz`), or whether they should use
+ * paths relative to the class they are owned by (ie `Baz`) when possible.
+ * And that's ONLY possible whenever the target type lives within the same
+ * namespace as the class. Any properties from other namespaces will still
+ * use absolute paths.
+ *
+ * Note that if relative types are used, they are ALWAYS relative to the
+ * class that the current object IS AN INSTANCE OF. This is true even when
+ * those properties were inherited/imported from another class!
+ *
+ * Relative mode is mostly meant for class-documentation, to be placed
+ * inside each of your class files. That way, the relative paths will be
+ * perfectly understood by your IDE. The absolute, non-relative format is
+ * preferable in other, more general "runtime usage" since it is totally
+ * clear about exactly which final object each class path refers to.
+ *
+ * @param bool $allowRelativeTypes If `TRUE`, object types will use relative
+ * paths (compared to this class) whenever
+ * possible.
+ * @param bool $includeUndefined Whether to also include properties that
+ * only exist in the current object
+ * instance's JSON data, but aren't defined
+ * in the actual class property map.
+ *
+ * @throws LazyJsonMapperException If any properties cannot be described.
+ * But that should never be able to happen.
+ *
+ * @return PropertyDescription[] Associative array of property descriptions,
+ * sorted by property name in case-insensitive
+ * natural order.
+ *
+ * @see LazyJsonMapper::printPropertyDescriptions()
+ */
+ final public function exportPropertyDescriptions(
+ $allowRelativeTypes = false,
+ $includeUndefined = false)
+ {
+ if (!is_bool($allowRelativeTypes) || !is_bool($includeUndefined)) {
+ throw new LazyJsonMapperException('The function arguments must be booleans.');
+ }
+
+ // First include all of the defined properties for the current class.
+ $descriptions = [];
+ $ownerClassName = get_class($this);
+ foreach ($this->_compiledPropertyMapLink as $propName => $propDef) {
+ $descriptions[$propName] = new PropertyDescription( // Throws.
+ $ownerClassName,
+ $propName,
+ $propDef,
+ $allowRelativeTypes
+ );
+ }
+
+ // Also include all undefined, JSON-only data properties if desired.
+ if ($includeUndefined) {
+ $undefinedProperty = UndefinedProperty::getInstance();
+ foreach ($this->_objectData as $propName => $v) {
+ if (!isset($descriptions[$propName])) {
+ $descriptions[$propName] = new PropertyDescription( // Throws.
+ $ownerClassName,
+ $propName,
+ $undefinedProperty,
+ $allowRelativeTypes
+ );
+ }
+ }
+ }
+
+ // Sort the descriptions by the case-insensitive name of each property.
+ ksort($descriptions, SORT_NATURAL | SORT_FLAG_CASE); // Natural order.
+
+ return $descriptions;
+ }
+
+ /**
+ * Print human-readable descriptions of class/object instance properties.
+ *
+ * This helper is provided as a quick and easy debug feature, and helps you
+ * look at your compiled class property maps, or to quickly look up how a
+ * certain property needs to be accessed in its function-name form.
+ *
+ * Please read the description of `exportPropertyDescriptions()` if you want
+ * more information about the various options.
+ *
+ * @param bool $showFunctions Whether to show the list of functions for
+ * each property. Which is very helpful, but
+ * also very long. So you may want to
+ * disable this option.
+ * @param bool $allowRelativeTypes If `TRUE`, object types will use relative
+ * paths (compared to this class) whenever
+ * possible.
+ * @param bool $includeUndefined Whether to also include properties that
+ * only exist in the current object
+ * instance's JSON data, but aren't defined
+ * in the actual class property map.
+ *
+ * @throws LazyJsonMapperException If any properties cannot be described.
+ * But that should never be able to happen.
+ *
+ * @see LazyJsonMapper::exportPropertyDescriptions()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function printPropertyDescriptions(
+ $showFunctions = true,
+ $allowRelativeTypes = false,
+ $includeUndefined = false)
+ {
+ if (!is_bool($showFunctions) || !is_bool($allowRelativeTypes) || !is_bool($includeUndefined)) {
+ throw new LazyJsonMapperException('The function arguments must be booleans.');
+ }
+
+ // Generate the descriptions.
+ $descriptions = $this->exportPropertyDescriptions( // Throws.
+ $allowRelativeTypes,
+ $includeUndefined
+ );
+
+ // Create some bars for output formatting.
+ $equals_bar = str_repeat('=', 60);
+ $dash_bar = str_repeat('-', 60);
+
+ // Header.
+ printf(
+ '%s%s> Class: "%s"%s Supports: [%s] Virtual Functions [%s] Virtual Properties%s%s%s Show Functions: %s.%s Allow Relative Types: %s.%s Include Undefined Properties: %s.%s%s%s',
+ $equals_bar,
+ PHP_EOL,
+ Utilities::createStrictClassPath(get_class($this)),
+ PHP_EOL,
+ static::ALLOW_VIRTUAL_FUNCTIONS ? 'X' : ' ',
+ static::ALLOW_VIRTUAL_PROPERTIES ? 'X' : ' ',
+ PHP_EOL,
+ $dash_bar,
+ PHP_EOL,
+ $showFunctions ? 'Yes' : 'No',
+ PHP_EOL,
+ $allowRelativeTypes ? 'Yes' : 'No',
+ PHP_EOL,
+ $includeUndefined ? 'Yes' : 'No',
+ PHP_EOL,
+ $equals_bar,
+ PHP_EOL
+ );
+
+ // Properties.
+ $lastPropertyNum = count($descriptions);
+ $padNumDigitsTo = strlen($lastPropertyNum);
+ if ($padNumDigitsTo < 2) {
+ $padNumDigitsTo = 2; // Minimum 2-digit padding: "09".
+ }
+ $alignPadding = 4 + (2 * $padNumDigitsTo); // " #/" plus the digits.
+ $thisPropertyNum = 0;
+ foreach ($descriptions as $property) {
+ $thisPropertyNum++;
+
+ // Output core information about the property.
+ printf(
+ ' #%s/%s: "%s"%s%s%s: "%s"%s%s',
+ str_pad($thisPropertyNum, $padNumDigitsTo, '0', STR_PAD_LEFT),
+ str_pad($lastPropertyNum, $padNumDigitsTo, '0', STR_PAD_LEFT),
+ $property->name,
+ !$property->is_defined ? ' (Not in class property map!)' : '',
+ PHP_EOL,
+ str_pad('* Type', $alignPadding, ' ', STR_PAD_LEFT),
+ $property->type,
+ $property->is_basic_type ? ' (Basic PHP type)' : '',
+ PHP_EOL
+ );
+
+ // Optionally output the function list as well.
+ if ($showFunctions) {
+ foreach (['has', 'is', 'get', 'set', 'unset'] as $function) {
+ printf(
+ '%s: %s%s',
+ str_pad($function, $alignPadding, ' ', STR_PAD_LEFT),
+ $property->{"function_{$function}"},
+ PHP_EOL
+ );
+ }
+ }
+
+ // Dividers between properties.
+ if ($thisPropertyNum !== $lastPropertyNum) {
+ echo $dash_bar.PHP_EOL;
+ }
+ }
+
+ // Handle empty property lists.
+ if (empty($descriptions)) {
+ echo '- No properties.'.PHP_EOL;
+ }
+
+ // Footer.
+ echo $equals_bar.PHP_EOL;
+ }
+
+ /**
+ * Get a processed copy of this object instance's internal data contents.
+ *
+ * It is recommended that you save the result if you intend to re-use it
+ * multiple times, since each call to this function will need to perform
+ * copying and data conversion from our internal object representation.
+ *
+ * Note that the conversion process will recursively validate and convert
+ * all properties in the entire internal data hierarchy. You can trust that
+ * the returned result will be perfectly accurate and follow your class map
+ * property type-rules. This does however mean that the data may not be the
+ * same as the input array you gave to `__construct()`. For example, if your
+ * input contained an array `["1", "2"]` and your type definition map says
+ * that you want `int` for that property, then you'd get `[1, 2]` as output.
+ *
+ * The conversion is always done on a temporary COPY of the internal data,
+ * which means that you're welcome to run this function as much as you want
+ * without causing your object to grow from resolving all its sub-objects.
+ *
+ * It also means the returned copy belongs to YOU, and that you're able to
+ * do ANYTHING with it, without risk of affecting any of OUR internal data.
+ *
+ * `WARNING:` If you intend to use the result to `json_encode()` a new JSON
+ * object then please DON'T do that. Look at `asJson()` instead, which gives
+ * you full control over all JSON output parameters and properly handles the
+ * conversion in all scenarios, without you needing to do any manual work.
+ *
+ * Also look at the separate `asStdClass()` and `asArray()` functions, for
+ * handy shortcuts instead of manually having to call this longer function.
+ *
+ * @param string $objectRepresentation What container to use to represent
+ * `LazyJsonMapper` objects. Can be
+ * either `array` (useful if you require
+ * that the result is compatible with
+ * `__construct()`), or `stdClass` (the
+ * best choice if you want to be able to
+ * identify subobjects via
+ * `is_object()`).
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return stdClass|array A processed copy of the internal data, using the
+ * desired container type to represent all objects.
+ *
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function exportObjectDataCopy(
+ $objectRepresentation = 'array')
+ {
+ if (!in_array($objectRepresentation, ['stdClass', 'array'], true)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Invalid object representation type "%s". Must be either "stdClass" or "array".',
+ $objectRepresentation
+ ));
+ }
+
+ // Make a shallow copy (copy-on-write) so that we will avoid affecting
+ // our actual internal object data during the parsing below. Otherwise
+ // we would turn all of OUR internal, still-unconverted sub-object
+ // arrays into real objects on our REAL instance, and permanently cause
+ // our object instance's memory usage to go up after an export call.
+ $copy = clone $this;
+
+ // Perform a NON-RECURSIVE analysis of the copy's top-level (own)
+ // properties. This will CONVERT all of their values to the proper type,
+ // and perform further sub-object creation (convert sub-object arrays
+ // into real objects), etc. It also ensures no illegal values exist.
+ // NOTE: For performance, we DON'T want recursive analysis, since we'll
+ // soon be asking any sub-objects to analyze themselves one-by-one too!
+ $analysis = $copy->exportClassAnalysis(false); // Never throws.
+
+ // Abort if there are any BAD definitions (conversion failure due to
+ // mismatches between defined class map and the actual data).
+ if (!empty($analysis->bad_definitions)) { // Ignore missing_definitions
+ $problemSummaries = $analysis->generateNiceSummaries();
+
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert data to %s: %s',
+ $objectRepresentation,
+ $problemSummaries['bad_definitions'] // Tells them exact error.
+ ));
+ }
+
+ // Now recursively process every other sub-object within the copy's data
+ // via exportObjectDataCopy(), thus forcing all nested sub-objects
+ // (which are still DIRECT REFERENCES to OUR "$this" original versions
+ // of the objects, since objects are always by reference) to copy and
+ // validate/convert THEMSELVES and their data and then return it
+ // within the type of container we need. For a perfect final result.
+ // NOTE: We iterate by &$value reference, but that WON'T affect the
+ // objects they point to. It's a reference to the "_objectData" entry.
+ array_walk_recursive($copy->_objectData, function (&$value, $key) use ($objectRepresentation) {
+ // Only process objects. Everything else is already perfect (either
+ // converted to the target-type if mapped or is valid "mixed" data).
+ if (is_object($value)) {
+ // Verify that the object is an instance of LazyJsonMapper.
+ // NOTE: It SHOULDN'T be able to be anything else, since the
+ // object analysis above has already verified that any "mixed"
+ // (non-LazyJsonMapper) properties only contain basic PHP types.
+ // But this check is a cheap safeguard against FUTURE bugs.
+ if (!$value instanceof self) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert data to %s: Unexpected "%s" object in property/key "%s", but we expected an instance of a LazyJsonMapper object.',
+ $objectRepresentation,
+ Utilities::createStrictClassPath(get_class($value)),
+ $key
+ ));
+ }
+
+ // Now just ask the sub-object to give us a copy of ITS verified
+ // + converted data wrapped in the desired container type.
+ // NOTE: This will be resolved recursively as-necessary for all
+ // objects in the entire data tree.
+ $value = $value->exportObjectDataCopy($objectRepresentation); // Throws.
+ }
+ });
+
+ // Convert the outer data-holder to the object-representation type.
+ if ($objectRepresentation === 'stdClass') {
+ // When they want a stdClass, we MUST create it ourselves and assign
+ // all of the internal data key-value pairs on it, with string-keys.
+ $outputContainer = new stdClass();
+ foreach ($copy->_objectData as $k => $v) {
+ $outputContainer->{(string) $k} = $v;
+ }
+ } else { // 'array'
+ // For an array-representation, we'll simply steal the copy's array.
+ $outputContainer = $copy->_objectData;
+ }
+
+ // Voila, we now have their desired container, with cleaned-up and fully
+ // validated copies of all of our internal data. And we haven't affected
+ // any of our real $this data at all. Clones rock!
+ // NOTE: And since $copy goes out of scope now, there will be no other
+ // references to any of the data inside the container. It is therefore
+ // fully encapsulated, standalone data which is safe to manipulate.
+ return $outputContainer;
+ }
+
+ /**
+ * Get a processed copy of this object instance's data wrapped in stdClass.
+ *
+ * This function is just a shortcut/alias for `exportObjectDataCopy()`.
+ * Please read its documentation to understand the full implications and
+ * explanation for the behavior of this function.
+ *
+ * All objects in the returned value will be represented as `stdClass`
+ * objects, which is important if you need to be able to identify nested
+ * internal sub-objects via `is_object()`.
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return stdClass A processed copy of the internal data, with all objects
+ * represented as stdClass.
+ *
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function asStdClass()
+ {
+ return $this->exportObjectDataCopy('stdClass'); // Throws.
+ }
+
+ /**
+ * Get a processed copy of this object instance's data as an array.
+ *
+ * This function is just a shortcut/alias for `exportObjectDataCopy()`.
+ * Please read its documentation to understand the full implications and
+ * explanation for the behavior of this function.
+ *
+ * All objects in the returned value will be represented as nested arrays,
+ * which is good if you require that the result is compatible with
+ * `__construct()` again.
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return array A processed copy of the internal data, with all objects
+ * represented as arrays.
+ *
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function asArray()
+ {
+ return $this->exportObjectDataCopy('array'); // Throws.
+ }
+
+ /**
+ * Get a processed representation of this object instance's data as JSON.
+ *
+ * This helper gives you a JSON string representation of the object's
+ * internal data, and provides the necessary interface to fully control
+ * the JSON encoding process.
+ *
+ * It handles all steps for you and wraps everything in nice exceptions,
+ * and will even clearly explain any `json_encode()` errors in plain English
+ * (although the default settings will never cause any encoding failures).
+ * And most importantly, this function also guarantees that your data is
+ * ALWAYS properly encoded as a valid JSON object `{}` (rather than risking
+ * getting the result as a JSON array such as `[]` or `["a","b","c"]`).
+ * We accept all of the same parameters as the `json_encode()` function!
+ *
+ * Note that we only encode properties that exist in the actual internal
+ * data. Anything that merely exists in the class property map is omitted.
+ *
+ * `WARNING:` It is worth saving the output of this function if you intend
+ * to use the result multiple times, since each call to this function will
+ * internally use `exportObjectDataCopy()`, which performs quite intensive
+ * work to recursively validate and convert values while creating the final
+ * representation of the object's internal JSON data. Please read the
+ * description of `exportObjectDataCopy()` for more information.
+ *
+ * @param int $options Bitmask to control `json_encode()` behavior.
+ * @param int $depth Maximum JSON depth. Encoding fails if set too low.
+ * Can almost always safely be left at `512` (default).
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return string
+ *
+ * @see http://php.net/json_encode
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::printJson()
+ * @see LazyJsonMapper::jsonSerialize() The native json_encode()
+ * serializer instead.
+ */
+ final public function asJson(
+ $options = 0,
+ $depth = 512)
+ {
+ if (!is_int($options) || !is_int($depth)) {
+ throw new LazyJsonMapperException('Invalid non-integer function argument.');
+ }
+
+ // Create a fully-validated, fully-converted final object-tree.
+ // NOTE: See `jsonSerialize()` for details about why we MUST do this.
+ $objectData = $this->exportObjectDataCopy('stdClass'); // Throws.
+
+ // Gracefully handle JSON encoding and validation.
+ $jsonString = @json_encode($objectData, $options, $depth);
+ if ($jsonString === false) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Failed to encode JSON string (error %d: "%s").',
+ json_last_error(), json_last_error_msg()
+ ));
+ }
+
+ return $jsonString;
+ }
+
+ /**
+ * Print a processed representation of this object instance's data as JSON.
+ *
+ * This helper is provided as a quick and easy debug feature, instead of
+ * having to manually write something like `var_dump($item->asJson())`.
+ *
+ * And it's also a much better alternative than PHP's common but totally
+ * unreadable `var_dump($item->asArray())` or `var_dump($item)` techniques.
+ *
+ * You can even take advantage of chained operations, if you are 100% sure
+ * that NONE of the properties along the way are `NULL`. An example of doing
+ * that would be `$container->getItems()[0]->getUser()->printJson()`.
+ * However, such code is ONLY recommended for quick debug tests, but not
+ * for actual production use, since you should always be handling any `NULL`
+ * properties along the way (unless you enjoy random errors whenever a
+ * particular property along the chain happens to be missing from the
+ * object's internal JSON data value storage).
+ *
+ * Please read the description of `asJson()` if you want more information
+ * about how the internal JSON conversion process works.
+ *
+ * @param bool $prettyPrint Use whitespace to nicely format the data.
+ * Defaults to `TRUE` since we assume that you want
+ * human readable output while debug-printing.
+ * @param int $depth Maximum JSON depth. Encoding fails if set too
+ * low. Can almost always safely be left at `512`
+ * (default).
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printPropertyDescriptions()
+ */
+ final public function printJson(
+ $prettyPrint = true,
+ $depth = 512)
+ {
+ // NOTE: These options are important. For display purposes, we don't
+ // want escaped slashes or `\uXXXX` hex versions of UTF-8 characters.
+ $options = ($prettyPrint ? JSON_PRETTY_PRINT : 0) | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
+ $json = $this->asJson($options, $depth); // Throws.
+ if ($prettyPrint && PHP_EOL !== "\n") {
+ // PHP's JSON pretty-printing uses "\n" line endings, which must be
+ // translated for proper display if that isn't the system's style.
+ $json = str_replace("\n", PHP_EOL, $json);
+ }
+ echo $json.PHP_EOL;
+ }
+
+ /**
+ * Serialize this object to a value that's supported by json_encode().
+ *
+ * You are not supposed to call this directly. It is _automatically_ called
+ * by PHP whenever you attempt to `json_encode()` a `LazyJsonMapper` object
+ * or any data structures (such as arrays) which contain such objects. This
+ * function is then called and provides a proper "object representation"
+ * which can be natively encoded by PHP.
+ *
+ * Having this helper ensures that you can _easily_ encode very advanced
+ * structures (such as a regular PHP array which contains several nested
+ * `LazyJsonMapper`-based objects), _without_ needing to manually fiddle
+ * around with `asJson()` on every individual object within your array.
+ *
+ * You are instead able to simply `json_encode($mainObj->getSubArray())`,
+ * which will properly encode every array-element in that array, regardless
+ * of whether they're pure PHP types or nested `LazyJsonMapper` objects.
+ *
+ * Note that we only export properties that exist in the actual internal
+ * data. Anything that merely exists in the class property map is omitted.
+ *
+ * `WARNING:` It is worth saving the output of `json_encode()` if you intend
+ * to use the result multiple times, since each call to this function will
+ * internally use `exportObjectDataCopy()`, which performs quite intensive
+ * work to recursively validate and convert values while creating the final
+ * representation of the object's internal JSON data. Please read the
+ * description of `exportObjectDataCopy()` for more information.
+ *
+ * `WARNING:` In _most_ cases, you should be using `asJson()` (instead of
+ * `json_encode()`), since it's _far more_ convenient and completely wraps
+ * PHP's JSON encoding problems as exceptions. If you truly want to manually
+ * `json_encode()` the data, you'll still get complete data conversion and
+ * validation, but you _won't_ get any _automatic exceptions_ if PHP fails
+ * to encode the JSON, which means that _you'll_ have to manually check if
+ * the final value was successfully encoded by PHP. Just be aware of that!
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return stdClass A processed copy of the internal data, with all objects
+ * represented as stdClass. Usable by `json_encode()`.
+ *
+ * @see http://php.net/json_encode
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function jsonSerialize()
+ {
+ // Create a fully-validated, fully-converted final object-tree.
+ // NOTE: It is VERY important that we export the data-copy with objects
+ // represented as stdClass objects. Otherwise `json_encode()` will be
+ // unable to understand which parts are JSON objects (whose keys must
+ // always be encoded as `{}` or `{"0":"a","1":"b"}`) and which parts are
+ // JSON arrays (whose keys must always be encoded as `[]` or
+ // `["a","b"]`).
+ // IMPORTANT NOTE: Any "untyped" objects from the original JSON data
+ // input ("mixed"/undefined data NOT mapped to `LazyJsonMapper` classes)
+ // will be encoded using PHP's own auto-detection for arrays, which
+ // GUESSES based on the keys of the array. It'll be guessing perfectly
+ // in almost 100% of all cases and will encode even those "untyped"
+ // arrays as JSON objects again, EXCEPT if an object from the original
+ // data used SEQUENTIAL numerical keys STARTING AT 0, such as:
+ // `json_encode(json_decode('{"0":"a","1":"b"}', true))` which will
+ // result in `["a","b"]`. But that's incredibly rare (if ever), since it
+ // requires weird objects with numerical keys that START AT 0 and then
+ // sequentially go up from there WITHOUT ANY GAPS or ANY NON-NUMERIC
+ // keys. That should NEVER exist in any user's real JSON data, and it
+ // doesn't warrant internally representing all JSON object data as the
+ // incredibly inefficient stdClass type instead. If users REALLY want to
+ // perfectly encode such data as objects when they export as JSON, EVEN
+ // in the insanely-rare/impossible situation where their objects use
+ // sequential numeric keys, then they should at the very least map them
+ // to "LazyJsonMapper", which will ensure that they'll be preserved as
+ // objects in the final JSON output too.
+ return $this->exportObjectDataCopy('stdClass'); // Throws.
+ }
+
+ /**
+ * Handles automatic string conversion when the object is used as a string.
+ *
+ * Internally runs `asJson()`.
+ *
+ * `WARNING:` It's **dangerous** to rely on the automatic string conversion,
+ * since PHP doesn't allow this handler to throw any error/exceptions (if we
+ * do, PHP would die with a Fatal Error). So we cannot notify about errors.
+ *
+ * Therefore, any warnings and exceptions will silently be placed directly
+ * in the string output instead, enclosed in `<>` brackets to guarantee that
+ * they won't be interpreted as valid JSON.
+ *
+ * Smart programmers will instead call `$item->asJson()` manually. It is
+ * only a few more characters, and it guarantees that you can catch
+ * conversion errors and react to them appropriately.
+ *
+ * There is only ONE scenario where `__toString()` is reliable: That's if
+ * your JSON data was constructed with the `$requireAnalysis` constructor
+ * argument, which recursively ensures that all data type-conversions and
+ * all nested sub-object constructions are successful before your object is
+ * allowed to be created. Furthermore, you must not have manipulated
+ * anything via "direct property access by reference" AFTER that (as
+ * explained in `__get()`), so that you're sure that all of your internal
+ * data is truly valid.
+ *
+ * But that is all moot anyway, because always using the debug-only option
+ * `$requireAnalysis` defeats the purpose of memory-efficient lazy-loading
+ * and slows down the creation of all of your class instances. Don't do it.
+ *
+ * In short: `__toString()` is here as a nice bonus for completeness sake,
+ * but you should never use it in production. Just use `asJson()` instead!
+ *
+ * @var string
+ *
+ * @see LazyJsonMapper::asJson()
+ */
+ final public function __toString()
+ {
+ try {
+ return $this->asJson(); // Throws.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ // __toString() is not allowed to throw, so give generic info in a
+ // way that's definitely going to be invalid JSON data, so that the
+ // user will notice the problem if they use this method to manually
+ // generate JSON data, such as if they're doing '["i":'.$item.']'.
+ // NOTE: "" was chosen since it's illegal JSON.
+ return sprintf('<%s>', $e->getMessage());
+ }
+ }
+
+ /**
+ * Analyze the entire object and check for undefined or bad JSON properties.
+ *
+ * This lets you analyze the object data & map to look for the following:
+ *
+ * 1. Undefined JSON properties that need to be defined in your classes
+ * so that their existence becomes permanent and therefore safer to use
+ * (since defined properties are always retrievable even if they don't
+ * exist in the current object instance's data). And actually defining
+ * the properties is also the ONLY way that you can enforce a specific
+ * data-type, since undefined properties default to untyped (`mixed`).
+ *
+ * 2. Bad class map definitions that don't match the actual JSON data.
+ * It does this by checking all of the encountered classes within the JSON
+ * data to ensure that they construct successfully (which they ALWAYS
+ * will, unless the user has overridden `_init()` and throws from it), as
+ * well as verifying that all basic PHP type coercions work, and that the
+ * data is formatted as-described in the definition (such as having the
+ * correct array-depth, or data defined as objects actually being objects).
+ *
+ * The scan is performed by looking at EVERY item in the object's internal
+ * JSON data array, and checking for a corresponding class map definition
+ * (if nothing exists, it's treated as "missing from the class map"), and
+ * then attempting conversion to its specified type (if that fails, it's
+ * treated as "bad class map definition"). All undefined values (which lack
+ * any class map definition) are ALSO validated as if they were `mixed`,
+ * to ensure that NOTHING can contain illegal values.
+ *
+ * If a recursive scan is requested, then all sub-objects (`LazyJsonMapper`
+ * data containers) will ALSO recursively perform the same analysis on their
+ * own internal data, so that the whole JSON data tree is recursively
+ * verified. Their analysis results will be merged with our return value.
+ *
+ * Note that calling this function will cause the WHOLE object tree to be
+ * constructed and ALL of its mapped properties to be converted/parsed to
+ * verify the class map, which takes some time and permanently increases the
+ * object's memory usage (since conversion causes any internal sub-objects
+ * to be stored as actual objects instead of merely as plain sub-arrays)!
+ *
+ * Therefore, you should ONLY use this function when necessary: When you
+ * need advanced and powerful debugging of your class maps!
+ *
+ * @param bool $recursiveScan Whether to also verify missing/bad properties
+ * in sub-objects in the analysis. Recommended.
+ *
+ * @return ClassAnalysis An object describing the problems with this class.
+ */
+ final public function exportClassAnalysis(
+ $recursiveScan = true)
+ {
+ $result = new ClassAnalysis();
+
+ // All problems with OUR class will get filed under our class name.
+ $definitionSource = get_class($this);
+
+ // Ensure that all object-data properties exist in our class definition,
+ // and that their values can be converted as described in the class map.
+ foreach ($this->_objectData as $propName => $value) {
+ // Check if property exists in the class map and get its definition.
+ // NOTE: Normally, this function can throw, but not with the way we
+ // are calling it, because we KNOW that the property exists in at
+ // least the object data, so we DON'T have to catch any errors!
+ $propDef = $this->_getPropertyDefinition($propName);
+
+ // Regardless of whether it's defined or not, we MUST now "get" it
+ // to validate/convert the property's contents, to ensure it's safe.
+ try {
+ // Trying to "get" the property forces complete validation of
+ // the internal property data and non-recursively creates all
+ // sub-objects (if any). In case of any errors, this call will
+ // throw an exception with an error description.
+ // NOTE: Calling this getter is EXTREMELY important even for
+ // UNDEFINED (untyped) properties, because it ensures that no
+ // illegal data values (such as Resources or non-LazyJsonMapper
+ // objects) exist within the data even in undefined properties.
+ // NOTE: In the case of arrays, it validates the whole
+ // arrayDepth and ensures that the array is well-formed and
+ // contains the exact type at the exact depth specified (but it
+ // accepts NULL values anywhere in the chain of arrays, and it
+ // accepts empty arrays at non-max depth). It also verifies
+ // type-coercion to built-in PHP types. And in the case of
+ // objects, it verifies that they are successfully constructed.
+ $value = $this->_getProperty($propName); // Throws.
+
+ // Recursively check all internal objects to make sure they also
+ // have all properties from their own raw JSON data.
+ // NOTE: Nothing in this sub-block throws any exceptions, since
+ // everything was validated by _getProperty(). And the deeper
+ // exportClassAnalysis() calls don't throw anything either.
+ if ($recursiveScan && $value !== null && $propDef->isObjectType) {
+ // NOTE: We don't have to validate array-depth/type
+ // correctness, since _getProperty() already did that for
+ // us. Nothing will be isObjectType unless it's really a
+ // LazyJsonMapper class. Also, since the $value is not NULL
+ // (see above) and it's an "array of [type]" property, then
+ // we KNOW the $value is_array(), since it validated as OK.
+ if ($propDef->arrayDepth > 0) { // Array of objects.
+ array_walk_recursive($value, function (&$obj) use (&$result) {
+ if (is_object($obj)) { // Could be inner NULL too.
+ $result->mergeAnalysis($obj->exportClassAnalysis());
+ }
+ });
+ } else { // Non-"array of" object property.
+ $result->mergeAnalysis($value->exportClassAnalysis());
+ }
+ }
+ } catch (LazyJsonMapperException $e) {
+ // Unable to get the value of this property... which usually
+ // means that the property cannot be type-coerced as requested,
+ // or that the JSON data doesn't match the definition (such as
+ // having a deeper JSON array than what the definition says), or
+ // that invalid data was encountered (such as encountering an
+ // internal non-Lazy object/resource instead of the expected
+ // data), or that the property's class could not be constructed
+ // (which may really only happen due to a user's custom _init()
+ // function failing, since our own classmap's compilation has
+ // already taken care of successfully verifying the classmap
+ // compilation of ALL classes referred to by our ENTIRE property
+ // hierarchy). All of those are user-caused errors...
+
+ // We'll save the exception details message as-is...
+ // TODO: This also means that undefined properties containing
+ // weird data (objects or resources) would throw above and get
+ // logged as "bad definition" despite NOT having any definition.
+ // We may want to add a check here for UndefinedProperty and
+ // prepend something to the exception message in that case, to
+ // tell the user that their UNDEFINED property contained the bad
+ // data. But seriously, they can NEVER have such invalid data
+ // inside of a real json_decode() input array, so nobody sane is
+ // going to be warned of "bad definition" for their undefined
+ // properties! And we can't just log it as "missing" since we
+ // NEED all "bad data" to be logged as bad_definitions, since
+ // that's what our various data validators look at to detect
+ // SERIOUS data errors! Besides, their missing definition WILL
+ // also get logged under "missing_definitions" in the next step.
+ $result->addProblem(
+ $definitionSource,
+ 'bad_definitions',
+ $e->getMessage()
+ );
+ }
+
+ // Now check if we lacked a user-definition for this JSON property.
+ if ($propDef instanceof UndefinedProperty) {
+ // NOTE: We need the (string) casting in case of int-keys. Which
+ // can happen if the user gives us a manually created, non-JSON
+ // constructor array containing non-associative integer keys.
+ $result->addProblem(
+ $definitionSource,
+ 'missing_definitions',
+ (string) $propName
+ );
+ }
+ }
+
+ // Nicely sort the problems and remove any duplicates.
+ $result->sortProblemLists();
+
+ return $result;
+ }
+
+ /**
+ * Check if a property definition exists.
+ *
+ * These are properties defined in the JSON property map for the class.
+ *
+ * `NOTE:` This is the STRICTEST function, which checks if a property is
+ * defined in the class. It rejects properties that only exist in the data.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ */
+ final protected function _hasPropertyDefinition(
+ $propName)
+ {
+ return isset($this->_compiledPropertyMapLink[$propName]);
+ }
+
+ /**
+ * Check if a property definition or an object instance data value exists.
+ *
+ * These are properties that are either defined in the JSON property map
+ * for the class OR that exist in the object instance's data.
+ *
+ * `NOTE:` This is the RECOMMENDED function for checking if a property is
+ * valid, since properties ARE VALID if they exist in the class definition
+ * OR in the object instance's data.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ */
+ final protected function _hasPropertyDefinitionOrData(
+ $propName)
+ {
+ return isset($this->_compiledPropertyMapLink[$propName])
+ || array_key_exists($propName, $this->_objectData);
+ }
+
+ /**
+ * Check if an object instance data value exists.
+ *
+ * These are properties that currently exist in the object instance's data.
+ *
+ * `NOTE:` This function ISN'T RECOMMENDED unless you know what you're doing.
+ * Because any property that exists in the class definition is valid as
+ * well, and can be retrieved even if it isn't in the current object data.
+ * So `_hasPropertyDefinitionOrData()` is recommended instead, if your goal
+ * is to figure out if a property name is valid. Alternatively, you can use
+ * `_hasPropertyDefinition()` if you want to be even stricter by requiring
+ * that the property is defined in the class map.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ *
+ * @see LazyJsonMapper::_hasPropertyDefinition()
+ * @see LazyJsonMapper::_hasPropertyDefinitionOrData()
+ */
+ final protected function _hasPropertyData(
+ $propName)
+ {
+ return array_key_exists($propName, $this->_objectData);
+ }
+
+ /**
+ * Get the property definition for an object data property.
+ *
+ * If the property doesn't exist in the class definition but exists in the
+ * object instance's data, then it will be treated as an undefined & untyped
+ * property. This ensures that the user can access undefined properties too!
+ *
+ * However, you should always strive to keep your classes up-to-date so that
+ * all properties are defined, otherwise they'll be totally inaccessible
+ * (instead of returning `NULL`) anytime they're missing from the JSON data.
+ *
+ * @param string $propName The property name.
+ * @param bool $allowUndefined Whether to allow default definitions when
+ * the property exists in the data but not in
+ * our actual class property map.
+ *
+ * @throws LazyJsonMapperException If property isn't defined in the class
+ * map and also doesn't exist in the object
+ * instance's data. Note that if undefined
+ * definitions are disallowed, we will ONLY
+ * check in the class definition map.
+ *
+ * @return PropertyDefinition An object describing the property.
+ */
+ final protected function _getPropertyDefinition(
+ $propName,
+ $allowUndefined = true)
+ {
+ if (isset($this->_compiledPropertyMapLink[$propName])) {
+ // Custom class property definition exists, so use that. Properties
+ // defined in the class map are always valid even if not in data!
+ return $this->_compiledPropertyMapLink[$propName];
+ } elseif ($allowUndefined && array_key_exists($propName, $this->_objectData)) {
+ // No property definition exists, but the property exists in the
+ // object instance's data, so treat it as undefined & untyped.
+ return UndefinedProperty::getInstance();
+ } else {
+ // There is no definition & no object instance data (or disallowed)!
+ throw new LazyJsonMapperException(sprintf(
+ 'No such object property "%s".',
+ $propName
+ ));
+ }
+ }
+
+ /**
+ * Get the value of an object data property.
+ *
+ * This function automatically reads the object instance's data and converts
+ * the value to the correct type on-the-fly. If that part of the data-array
+ * is an object, it will be lazy-created the first time it's requested.
+ *
+ * `NOTE:` If the object instance's internal data doesn't contain a value, but
+ * it's listed in the class property definition, then it will be treated as
+ * missing and `NULL` will automatically be returned instead. However, if
+ * it's missing from the class definition AND the object instance's data,
+ * then it's treated as an invalid property and an exception is thrown.
+ *
+ * Because of the fact that the default return value for internally-missing
+ * but "class-valid" properties is `NULL`, it means that you cannot discern
+ * whether the `NULL` came from actual JSON data or from the default return
+ * value. However, if that distinction matters to you, you should simply
+ * call `_hasPropertyData()` before retrieving the value.
+ *
+ * `IMPORTANT:` This function performs return-by-reference, which was done
+ * in order to allow certain ADVANCED programming tricks by the caller. If
+ * you save our return value by reference, you'll then have a direct link to
+ * the internal data for that property and can write directly to it
+ * (including writing invalid data if you want to, since we cannot verify
+ * what you do with your reference). However, you A) don't have to worry
+ * about invalid data, because it will all get validated at the next
+ * `_getProperty()` call again, and B) you have to explicitly bind the
+ * returned value by reference to actually risk affecting the internal
+ * data, as explained below.
+ *
+ * Method 1 (recommended for almost 100% of all usages, safest):
+ *
+ * ```php
+ * $val = $this->_getProperty('foo'); // Copy-on-write assignment to $val.
+ * $val = 'bar'; // Does NOT modify the internal data "foo" property.
+ * ```
+ *
+ * Method 2 (dangerous, and this example even contains an intentional bug):
+ *
+ * ```php
+ * $val = &$this->_getProperty('foo'); // Reference assignment to $val.
+ * $val = 'bar'; // Direct link, thus modifies the internal "foo" property.
+ * ```
+ *
+ * `SERIOUS WARNING:` If you use Method 2, do NOT use the code above. It was
+ * just explained that way to demonstrate a SERIOUS MISTAKE. If you assign
+ * by reference, you obviously intend to link directly to internal data. But
+ * the code above fails if no internal data for that property exists yet. In
+ * that case, the code above will write to a temporary `NULL` variable which
+ * is not linked at all to the object. Instead, you MUST ALWAYS use
+ * `$createMissingValue = true` if you want TRUSTABLE data references.
+ *
+ * Method 2 (the CORRECT way of writing it):
+ *
+ * ```php
+ * $val = &$this->_getProperty('foo', true);
+ * $val = 'bar';
+ * ```
+ *
+ * That FIXED code will create the property and put `NULL` in it if it's
+ * missing from the internal data, and then returns the reference to the
+ * created internal data entry. It's the ONLY way to ensure REAL references.
+ *
+ * Note that there is ZERO PERFORMANCE BENEFIT of assigning our return-value
+ * by reference. ONLY use references if you have an ADVANCED reason for it!
+ * Internally, PHP treats both assignment types identically until the moment
+ * you try to write to/modify the contents of the variable. So if you are
+ * only reading, which is most of the time, then there's no reason to use a
+ * reference. And never forget that modifying via direct reference lets you
+ * write invalid data (which won't be detected until a `_getProperty()`
+ * call attempts to read that bad data again), as mentioned earlier.
+ *
+ * There is one final warning: If YOU have a "return-by-reference" function,
+ * then you will AUTOMATICALLY return a direct link to the inner data if you
+ * use us directly in your return statement. Here's an example of that:
+ *
+ * ```php
+ * public function &myFunction() { // <-- Note the "&" return-by-ref here!
+ * // returns reference to real internal data OR temp var (badly coded)
+ * return $this->_getProperty('foo'); // <-- VERY DANGEROUS BUG!
+ *
+ * // returns reference to real internal data (correctly coded)
+ * return $this->_getProperty('foo', true); // <-- CORRECT!
+ *
+ * // you can also intentionally break the link to the internal data,
+ * // by putting it in a copy-on-write variable and returning THAT:
+ * $copyOnWrite = $this->_getProperty('foo'); // copy-on-write (no ref)
+ * return $copyOnWrite; // ref to $copyOnWrite but not to real internal
+ * }
+ * ```
+ *
+ * @param string $propName The property name.
+ * @param bool $createMissingValue If `TRUE` then we will create missing
+ * internal object data entries (for
+ * class-defined properties) and store a
+ * `NULL` in them. This will "pollute" the
+ * internal data with default `NULL`s for
+ * every missing property that you attempt
+ * to retrieve, but it's totally NECESSARY
+ * if you intend to use the return-value
+ * by-reference, since we MUST store the
+ * value internally when you want the
+ * returned reference to link to our
+ * actual object's internal data storage!
+ *
+ * @throws LazyJsonMapperException If the value can't be turned into its
+ * assigned class or built-in PHP type, or
+ * if the property doesn't exist in either
+ * the class property definition or the
+ * object instance's data.
+ *
+ * @return mixed The value as the correct type, or `NULL` if it's either a
+ * literal `NULL` in the data or if no value currently existed
+ * in the internal data storage at all. Note that this
+ * function returns the value by-reference.
+ */
+ final protected function &_getProperty(
+ $propName,
+ $createMissingValue = false)
+ {
+ // Check if the property exists in class/data and get its definition.
+ $propDef = $this->_getPropertyDefinition($propName); // Throws.
+
+ // Assign the appropriate reference to the "$value" variable.
+ if (array_key_exists($propName, $this->_objectData)) {
+ // The value exists in the data, so refer directly to it.
+ $value = &$this->_objectData[$propName]; // IMPORTANT: By reference!
+ } elseif ($createMissingValue) {
+ // No value exists in data yet, AND the caller wants us to create
+ // a default NULL value for the data property in that situation.
+ // NOTE: This is IMPORTANT so that the returned "default NULL"
+ // by-reference value will refer to the correct internal data
+ // array entry instead of to an unrelated, temporary variable.
+ // NOTE: The downside to this is that we'll fill the object's
+ // internal data storage with a bunch of default "NULL" values,
+ // which increases memory needs and messes with the "to JSON"
+ // output functions later. But it's unavoidable. We obviously
+ // CANNOT return a real reference to an internal data entry
+ // unless we CREATE the entry. So we MUST unfortunately do it!
+ // NOTE: This "pollution" is only a problem if the caller intends
+ // to use the property by reference, which isn't the case for
+ // default __call() (the virtual functions), but IS necessary
+ // in the default __get() (direct property access by reference).
+ $this->_objectData[$propName] = null;
+
+ // Now just link to the data array entry we just added.
+ $value = &$this->_objectData[$propName]; // IMPORTANT: By reference!
+ return $value; // OPTIMIZATION: Skip convert() for missing data.
+ } else {
+ // No value exists in data yet, but the caller didn't want us to set
+ // the missing value. So we'll simply use a default NULL variable.
+ // NOTE: If the caller tries to write to this one by reference,
+ // they'll just modify this temporary variable instead of the
+ // internal data. To link to real they MUST $createMissingValue.
+ // NOTE: We MUST return a variable, since "return null" is illegal.
+ $value = null; // Copy-on-write. Temporary variable to be returned.
+ return $value; // OPTIMIZATION: Skip convert() for missing data.
+ }
+
+ // Map the value to the appropriate type and validate it.
+ ValueConverter::convert( // Throws.
+ ValueConverter::CONVERT_FROM_INTERNAL,
+ $value, $propDef->arrayDepth, $propName, $propDef
+ );
+
+ // Whatever $value points at will now be returned by-reference.
+ return $value; // By-reference due to &function signature.
+ }
+
+ /**
+ * Check if an object data property exists and its value evaluates to TRUE.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ */
+ final protected function _isProperty(
+ $propName)
+ {
+ // Object instance's data has the property and it evaluates to true?
+ return array_key_exists($propName, $this->_objectData)
+ && (bool) $this->_objectData[$propName];
+ }
+
+ /**
+ * Set an object data property to a new value.
+ *
+ * @param string $propName The property name.
+ * @param mixed $value The new value for the property. `NULL` is always
+ * allowed as the new value, regardless of type.
+ *
+ * @throws LazyJsonMapperException If the new value isn't legal for that
+ * property, or if the property doesn't
+ * exist in either the class property
+ * definition or the object instance's data.
+ *
+ * @return $this The current object instance, to allow chaining setters.
+ */
+ final protected function _setProperty(
+ $propName,
+ $value)
+ {
+ // Check if the property exists in class/data and get its definition.
+ $propDef = $this->_getPropertyDefinition($propName); // Throws.
+
+ // Map the value to the appropriate type and validate it.
+ ValueConverter::convert( // Throws.
+ ValueConverter::CONVERT_TO_INTERNAL,
+ $value, $propDef->arrayDepth, $propName, $propDef
+ );
+
+ // Assign the new value for the property.
+ $this->_objectData[$propName] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Erase the internal value of an object data property.
+ *
+ * This is useful for things like erasing certain parts of the JSON data
+ * tree before you're going to output the object as JSON. You can still
+ * continue to access and modify an "erased" property after unsetting it,
+ * but ONLY if that property is defined in your class property map.
+ *
+ * The current value is simply removed from the internal object data array,
+ * as if the object had been constructed without that part of the array.
+ *
+ * Also note that we act like the real unset() function, meaning that we
+ * don't care whether that property key exists in the object instance's
+ * data array. We'll simply unset it if it exists. Or otherwise gracefully
+ * do absolutely nothing.
+ *
+ * @param string $propName The property name.
+ *
+ * @return $this The current object instance, to allow chaining unsetters.
+ */
+ final protected function _unsetProperty(
+ $propName)
+ {
+ unset($this->_objectData[$propName]);
+
+ return $this;
+ }
+
+ /**
+ * __CALL is invoked when attempting to access missing functions.
+ *
+ * This magic handler auto-maps "virtual function" has-ers, is-ers, getters,
+ * setters and unsetters for all of the object's JSON data properties.
+ *
+ * - `bool hasX()` checks if "x" exists in the class definition and/or the
+ * object instance data. The `has`-functions are the only ones that never
+ * throw even if the property is invalid. This function is totally useless
+ * if you've defined the property in the class map, and will always return
+ * `TRUE` in that case. Its ONLY purpose is to allow you to look for
+ * UNDEFINED properties that may/may not exist in the current data, before
+ * you decide to call any of the other "throwy" functions on that
+ * property. In other words, it's used for working with UNMAPPED
+ * (undefined) properties!
+ * - `bool isX()` checks if "x" exists in the object instance's data and its
+ * current value evaluates to `TRUE`.
+ * - `mixed getX()` retrieves the value of "x". Uses copy-on-write (not a
+ * reference).
+ * - `$this setX(mixed $value)` sets the value of "x" to `$value`. The
+ * setters can be chained.
+ * - `$this unsetX()` erases the internal value from the object instance's
+ * data. The unsetters can be chained.
+ *
+ * @param string $functionName Name of the function being called.
+ * @param array $arguments Array of arguments passed to the function.
+ *
+ * @throws LazyUserOptionException If virtual functions are disabled.
+ * @throws LazyJsonMapperException If the function type or property name is
+ * invalid, or if there's any problem with
+ * the conversion to/from the object
+ * instance's internal data. As well as if
+ * the setter doesn't get exactly 1 arg.
+ *
+ * @return mixed The return value depends on which function is used.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php
+ * @see LazyJsonMapper::_hasPropertyDefinitionOrData()
+ * @see LazyJsonMapper::_isProperty()
+ * @see LazyJsonMapper::_getProperty()
+ * @see LazyJsonMapper::_setProperty()
+ * @see LazyJsonMapper::_unsetProperty()
+ */
+ final public function __call(
+ $functionName,
+ $arguments)
+ {
+ if (!static::ALLOW_VIRTUAL_FUNCTIONS) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_FUNCTIONS_DISABLED
+ );
+ }
+
+ // Split the function name into its function-type and FuncCase parts.
+ list($functionType, $funcCase) = FunctionTranslation::splitFunctionName(
+ $functionName
+ );
+
+ // If the function didn't follow the [lower prefix][other] format, such
+ // as "getSomething", then this was an invalid function (type = NULL).
+ if ($functionType === null) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".',
+ $functionName
+ ));
+ }
+
+ // Resolve the FuncCase component into its equivalent property names.
+ if (static::USE_MAGIC_LOOKUP_CACHE && isset(self::$_magicLookupCache[$funcCase])) {
+ // Read the previously processed result from the lookup cache.
+ $translation = self::$_magicLookupCache[$funcCase];
+ } else {
+ // Attempt to parse the newly called function name.
+ try {
+ $translation = new FunctionTranslation($funcCase); // Throws.
+ } catch (MagicTranslationException $e) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".', $functionName
+ ));
+ }
+
+ // Store the processed result in the global lookup cache, if caching
+ // is allowed by the current class instance.
+ // NOTE: We'll store it even if the property doesn't exist on this
+ // particular object, because the user may be querying undefined
+ // data that WILL exist, and we don't want to waste time re-parsing.
+ if (static::USE_MAGIC_LOOKUP_CACHE) {
+ self::$_magicLookupCache[$funcCase] = $translation;
+ }
+ }
+
+ // Check for the existence of the "snake_case" property variant first,
+ // and if that fails then look for a "camelCase" property instead.
+ // NOTE: Checks both the class definition & the object instance's data.
+ if ($this->_hasPropertyDefinitionOrData($translation->snakePropName)) {
+ $propName = $translation->snakePropName; // We found a snake prop!
+ } elseif ($translation->camelPropName !== null
+ && $this->_hasPropertyDefinitionOrData($translation->camelPropName)) {
+ $propName = $translation->camelPropName; // We found camel instead.
+ } else {
+ // This object doesn't have the requested property! If this is a
+ // hasX() call, simply return false. In all other cases, throw!
+ if ($functionType === 'has') {
+ return false; // We don't have the property.
+ } else {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".',
+ $functionName
+ ));
+ }
+ }
+
+ // Return the kind of response expected by their desired function.
+ switch ($functionType) {
+ case 'has':
+ return true; // If we've come this far, we have the property.
+ break;
+ case 'is':
+ return $this->_isProperty($propName);
+ break;
+ case 'get':
+ // NOTE: This will NOT return a reference, since __call() itself
+ // does not support return-by-reference. We return a copy-on-write.
+ return $this->_getProperty($propName); // Throws.
+ break;
+ case 'set':
+ // They must provide exactly 1 argument for a setter call.
+ if (count($arguments) !== 1) {
+ // We know property exists; get its def from class or "undef".
+ $propDef = $this->_getPropertyDefinition($propName); // Throws.
+
+ throw new LazyJsonMapperException(sprintf(
+ 'Property setter requires exactly 1 argument: "%s(%s $value)".',
+ $functionName, $propDef->asString()
+ ));
+ }
+
+ // Returns $this so that the user can chain the setters.
+ return $this->_setProperty($propName, $arguments[0]); // Throws.
+ break;
+ case 'unset':
+ // NOTE: Normal PHP unset() calls would have a VOID return type. But
+ // ours actually returns $this so that the user can chain them.
+ return $this->_unsetProperty($propName);
+ break;
+ default:
+ // Unknown function type prefix...
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".',
+ $functionName
+ ));
+ }
+ }
+
+ /**
+ * __GET is invoked when reading data from inaccessible properties.
+ *
+ * This magic handler takes care of "virtual property" access to the
+ * object's JSON data properties.
+ *
+ * `WARNING:` Note that the `__get()` "virtual property" handling creates
+ * `NULL` values in any missing (but valid in class-map) properties that you
+ * try to access! That is NECESSARY because PHP EXPECTS the `__get()` return
+ * value to be a REFERENCE to real internal data, so we MUST create a value
+ * if no value exists, so that we can link PHP to a true reference to the
+ * internal data. Obviously we can't link to something that doesn't exist,
+ * which is why we MUST create `NULL` values. Unfortunately that means that
+ * "virtual property access" will lead to increased memory usage and worse
+ * JSON output (due to all the added values) if you want to export the
+ * internal data as JSON later.
+ *
+ * The recommended access method, `$obj->getFoo()` ("virtual functions")
+ * doesn't have that problem. It ONLY happens when you decide to use
+ * `$obj->foo` for "direct virtual property" access.
+ *
+ * There are several other quirks with "direct virtual properties", due to
+ * how PHP works. If you don't write your code carefully, you may cause
+ * serious issues with performance or unexpected results.
+ *
+ * All of the important quirks are as follows:
+ *
+ * 1. The aforementioned necessary `NULL` value insertion increases memory
+ * usage and may lead to unexpected problems if you convert back to JSON,
+ * such as if the server does not expect those values to exist with `NULL`.
+ *
+ * 2. Anytime you use the array `[]` access operator on the outer property
+ * name, PHP will trigger a `__get()` call, which of course does its whole
+ * data-validation again. The following would therefore be EXTREMELY SLOW,
+ * and possibly even DANGEROUS (since you're manipulating the array while
+ * looping over its keys while constantly triggering `__get()` re-parsing):
+ *
+ * ```php
+ * foreach ($obj->something as $k => $v) {
+ * // Every assignment here will do __get('something') which parses
+ * // the array over and over again before each element is modified:
+ * $obj->something[$k] = 'foo';
+ * }
+ * ```
+ *
+ * The proper way to solve THAT is to either save a reference to
+ * `$obj->something` as follows:
+ *
+ * ```php
+ * $ref = &$obj->something; // Calls __get('something') a single time.
+ * foreach ($ref as $k => $v) {
+ * $ref[$k] = 'foo'; // Changes array directly via stored reference.
+ * }
+ * ```
+ *
+ * Or to loop through the input values as follows:
+ *
+ * ```php
+ * foreach ($obj->something as &$v) { // Note the "&" symbol.
+ * $v = 'foo'; // Changes array value directly via reference.
+ * }
+ * ```
+ *
+ * 3. Anytime you use a reference, there will be NO DATA VALIDATION of the
+ * new value you are inputting. It will not be checked again UNTIL the next
+ * internal `_getProperty()` call for that property (ie. by the next
+ * `__get()`) so you may therefore unintentionally insert bad data that
+ * doesn't match the class definition map:
+ *
+ * ```php
+ * // Saving a reference to internal data and then changing that variable
+ * // will directly edit the value without letting us validate it:
+ *
+ * $ref = &$obj->some_string; // Make "$ref" into a reference to data.
+ * $ref = new InvalidObject(); // Writes a bad value directly to memory.
+ * var_dump($obj->some_string); // Throws error because of bad data.
+ *
+ * // The same is true about array access. In this case, PHP does
+ * // __get('some_int_array') and finds an array, and then it directly
+ * // manipulates that array's memory without letting us validate it:
+ *
+ * $obj->some_int_array[] = new InvalidObject();
+ * $obj->some_int_array[15] = new InvalidObject();
+ * var_dump($obj->some_int_array); // Throws error because of bad data.
+ * ```
+ *
+ * 4. You can always trust that `__set()` assignments WILL be validated.
+ * But a `__set()` ONLY happens when you assign with the equals operator
+ * (`=`) to a pure property name WITHOUT any array access (`[]`) operators.
+ *
+ * ```php
+ * $obj->some_property = '123'; // Calls __set() and validates new value.
+ * $obj->an_array[] = '123' // Calls __get() and won't validate changes.
+ * ```
+ *
+ * These quirks of "virtual direct property access" are quite easy to deal
+ * with when you know about them, since almost all of them are about array
+ * access, and the rest are about intentional misuse by binding directly to
+ * references. Just avoid making those mistakes.
+ *
+ * However, in general, you should REALLY be using the "virtual function"
+ * access method instead, which allows you to do great things such as
+ * overriding certain class property-functions (ie `getSomething()`) with
+ * your own custom behaviors, so that you can do extra validation, get/set
+ * value transformations, and other fantastic things!
+ *
+ * It is possible (and recommended) to disable virtual properties via the
+ * `ALLOW_VIRTUAL_PROPERTIES` class constant. The feature is only enabled by
+ * default because it helps people easily migrate from legacy projects.
+ *
+ * @param string $propName The property name.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ * @throws LazyJsonMapperException If the value can't be turned into its
+ * assigned class or built-in PHP type, or
+ * if the property doesn't exist in either
+ * the class property definition or the
+ * object instance's data.
+ *
+ * @return mixed The value as the correct type, or `NULL` if it's either a
+ * literal `NULL` in the data or if no value currently existed
+ * in the internal data storage at all. Note that this
+ * function returns the value by-reference.
+ *
+ * @see LazyJsonMapper::_getProperty()
+ * @see LazyJsonMapper::__set()
+ */
+ final public function &__get(
+ $propName)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ // This does the usual validation/parsing of the value to ensure that it
+ // is a valid property. It then creates the property with a default NULL
+ // if it doesn't exist, and finally returns a direct reference to the
+ // internal data entry. That's NECESSARY to allow the user treat the
+ // "virtual property" like a real property, so that they can do things
+ // like array-modification, or binding to it by reference, and so on.
+ // NOTE: Because _getProperty() AND __get() are "return-by-reference"
+ // functions, this return-value is automatically a propagated reference.
+ return $this->_getProperty($propName, true); // Throws.
+ }
+
+ /**
+ * __SET is invoked when writing data to inaccessible properties.
+ *
+ * @param string $propName The property name.
+ * @param mixed $value The new value for the property. `NULL` is always
+ * allowed as the new value, regardless of type.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ * @throws LazyJsonMapperException If the new value isn't legal for that
+ * property, or if the property doesn't
+ * exist in either the class property
+ * definition or the object instance's data.
+ *
+ * @see LazyJsonMapper::_setProperty()
+ * @see LazyJsonMapper::__get()
+ */
+ final public function __set(
+ $propName,
+ $value)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ // NOTE: PHP ignores the return value of __set().
+ $this->_setProperty($propName, $value); // Throws.
+ }
+
+ /**
+ * __ISSET is invoked by calling isset() or empty() on inaccessible properties.
+ *
+ * `NOTE:` When the user calls `empty()`, PHP first calls `__isset()`, and if
+ * that's true it calls `__get()` and ensures the value is really non-empty.
+ *
+ * @param string $propName The property name.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ *
+ * @return bool `TRUE` if the property exists in the object instance's data
+ * and is non-`NULL`.
+ */
+ final public function __isset(
+ $propName)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ return isset($this->_objectData[$propName]);
+ }
+
+ /**
+ * __UNSET is invoked by calling unset() on inaccessible properties.
+ *
+ * @param string $propName The property name.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ *
+ * @see LazyJsonMapper::_unsetProperty()
+ */
+ final public function __unset(
+ $propName)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ // NOTE: PHP ignores the return value of __unset().
+ $this->_unsetProperty($propName);
+ }
+
+ /**
+ * Called during serialization of the object.
+ *
+ * You are not supposed to call this directly. Instead, use PHP's global
+ * `serialize()` call:
+ *
+ * ```php
+ * $savedStr = serialize($obj);
+ * ```
+ *
+ * This serializer is thin and efficient. It simply recursively packs all
+ * nested, internal `LazyJsonMapper` objects as a single, plain data array,
+ * which guarantees the lowest possible serialized data size and the fastest
+ * possible unserialization later (since there will only be a SINGLE parent
+ * object that needs to wake up, rather than a whole tree).
+ *
+ * There is no data conversion/validation of properties (ie to make them
+ * match their "class property map"), since serialization is intended to
+ * quickly save the internal contents of an instance to let you restore it
+ * later, regardless of what was in your internal data property storage.
+ *
+ * Also note that only the internal JSON data is serialized. We will not
+ * serialize any subclass `$properties`, since that would be a misuse of how
+ * your `LazyJsonMapper` subclasses are supposed to be designed. If they
+ * need custom properties, then you can handle those in `_init()` as usual.
+ *
+ * Lastly, you should know that calling `serialize()` will not disrupt any
+ * internal data of the current object instance that you're serializing.
+ * You can therefore continue to work with the object afterwards, or even
+ * `serialize()` the same instance multiple times.
+ *
+ * @throws LazySerializationException If the internal data array cannot be
+ * serialized. But this problem can
+ * literally NEVER happen unless YOU have
+ * intentionally put totally-invalid,
+ * non-serializable sub-objects within
+ * your data array AND those objects in
+ * turn throw exceptions when trying to
+ * `serialize()`. That's NEVER going to
+ * happen with real `json_decode()` data;
+ * so if you constructed our object with
+ * real JSON data then you never have to
+ * look for `serialize()` exceptions.
+ *
+ * @return string The object's internal data as a string representation.
+ * Note that serialization produces strings containing binary
+ * data which cannot be handled as text. It is intended for
+ * storage in binary format (ie a `BLOB` database field).
+ */
+ final public function serialize()
+ {
+ // Tell all of our LJM-properties to pack themselves as plain arrays.
+ // NOTE: We don't do any value-conversion or validation of properties,
+ // since that's not our job. The user wants to SERIALIZE our data, so
+ // translation of array entries to "class-map types" doesn't matter.
+ $objectData = $this->_objectData; // Copy-on-write.
+ array_walk_recursive($objectData, function (&$value) {
+ if (is_object($value) && $value instanceof self) {
+ // This call will recursively detect and take care of nested
+ // objects and return ALL of their data as plain sub-arrays.
+ $value = $value->serialize($value); // Throws.
+ }
+ });
+
+ // If this is not the root object of a nested hierarchy, return raw arr.
+ // NOTE: This efficiently packs all inner, nested LazyJsonMapper objects
+ // by ensuring that their data joins the main root-level array again.
+ $args = func_get_args(); // Check secret argument.
+ $isRootObject = !isset($args[0]) || $args[0] !== $this;
+ if (!$isRootObject) {
+ return $objectData; // Secret, undocumented array return value.
+ }
+
+ // This is the root object that was asked to serialize itself, so finish
+ // the process by serializing the data and validating its success.
+ $serialized = null;
+
+ try {
+ // NOTE: This will ALWAYS succeed if the JSON data array is pure.
+ $serialized = serialize($objectData); // Throws.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ // This can literally ONLY happen if the user has given us (and now
+ // wants to serialize) non-JSON data containing other objects that
+ // attempt (and fail) serialization and throw an exception instead.
+ throw new LazySerializationException(sprintf(
+ 'Unexpected exception encountered while serializing a sub-object. Error: %s',
+ $e->getMessage()
+ ));
+ }
+
+ if (!is_string($serialized)) {
+ // Anything other than a string means that serialize() failed.
+ // NOTE: This should NEVER be able to happen!
+ throw new LazySerializationException(
+ 'The object data could not be serialized.'
+ );
+ }
+
+ // The data is fine. Now just return the string.
+ return $serialized;
+ }
+
+ /**
+ * Called during unserialization of the object.
+ *
+ * You are not supposed to call this directly. Instead, use PHP's global
+ * `unserialize()` call:
+ *
+ * ```php
+ * $restoredObj = unserialize($savedStr);
+ * ```
+ *
+ * This unserializer is thin and efficient. It simply unpacks the serialized
+ * raw data array and uses it as the new object's data, without validating
+ * any of the actual values within the serialized data array. It's intended
+ * to get the new object into the same JSON data state as the serialized
+ * object, which is why we don't re-analyze the data after unserialization.
+ *
+ * Note that the unserialization will call the constructor, with a single
+ * argument (your unserialized JSON data array). Everything that the default
+ * constructor performs will happen during unserialization, EXACTLY as if
+ * you had created a brand new object and given it the data array directly.
+ *
+ * You can therefore fully trust your unserialized objects as much as you
+ * already trust your manually created ones! And you can even store them
+ * somewhere and then unserialize them at any time in the future, during a
+ * completely separate PHP process, and even years from now as long as your
+ * project still contains the specific (sub)-class that you originally
+ * serialized. Have fun!
+ *
+ * @param string $serialized The string representation of the object.
+ *
+ * @throws LazySerializationException If the raw, serialized data cannot be
+ * unserialized at all.
+ * @throws LazyJsonMapperException If there are any problems creating the
+ * class that you are unserializing. See
+ * the regular constructor for error
+ * reasons, but disregard all of its data
+ * validation reasons, since unserialized
+ * JSON data is not validated (analyzed)
+ * when it reaches the constructor. The
+ * most likely reasons why this exception
+ * would be thrown during `unserialize()`
+ * is that your class property map is
+ * invalid and could not be compiled, and
+ * thus the object couldn't be recreated,
+ * OR that a custom `_init()` threw.
+ *
+ * @see LazyJsonMapper::__construct()
+ */
+ final public function unserialize(
+ $serialized = null)
+ {
+ $objectData = null;
+
+ try {
+ // Attempt to unpack the serialized data. Do not @suppress any
+ // syntax errors, since the user needs to know if they've provided
+ // a bad serialized string (or even a non-string value).
+ // NOTE: If the original object only contained perfect JSON data,
+ // then there are no sub-objects. But if any sub-objects existed
+ // within the data, this will recursively unserialize those too.
+ $objectData = unserialize($serialized); // Throws.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ // This can literally ONLY happen if the user had given us (and then
+ // serialized) non-JSON data containing other objects that attempt
+ // (and fail) unserialization now and throw an exception instead.
+ throw new LazySerializationException(sprintf(
+ 'Unexpected exception encountered while unserializing a sub-object. Error: %s',
+ $e->getMessage()
+ ));
+ }
+
+ if (!is_array($objectData)) {
+ // Anything other than an array means that $serialized was invalid.
+ throw new LazySerializationException(
+ 'The serialized object data that you provided could not be unserialized.'
+ );
+ }
+
+ // The data is fine. Now just construct this new object instance.
+ // NOTE: Important since ctor builds/links its necessary property map.
+ // NOTE: The unserialized object (or its data) has no links to the
+ // originally serialized object anymore, apart from the fact that (just
+ // like ANY two identical classes) they would both be linked to the
+ // same compiled class property map ("_compiledPropertyMapLink").
+ $this->__construct($objectData); // Throws.
+ }
+
+ /**
+ * Advanced function: Clear the global "magic function lookup cache".
+ *
+ * This command is NOT RECOMMENDED unless you know exactly what you are
+ * doing and WHY you are doing it. This clears the globally shared cache
+ * of magic function-name to property-name translations.
+ *
+ * It is always safe to clear that cache, since any future magic function
+ * calls (even to existing object instances) will simply re-calculate those
+ * translations and put them back in the global cache again.
+ *
+ * However, it's not recommended to clear the cache if you're sure that
+ * you'll constantly be calling the same functions over and over again.
+ * It's obviously faster to keep cached translations for instant lookups!
+ *
+ * @return int How many unique function names were cached before clearing.
+ */
+ final public static function clearGlobalMagicLookupCache()
+ {
+ $lookupCount = count(self::$_magicLookupCache);
+ self::$_magicLookupCache = [];
+
+ return $lookupCount;
+ }
+
+ /**
+ * Advanced function: Clear the global "compiled property map cache".
+ *
+ * This command is NOT RECOMMENDED unless you know exactly what you are
+ * doing and WHY you are doing it. This clears the globally shared cache of
+ * compiled property maps, which may be useful if you've created tons of
+ * different class objects from lots of different classes, and you no longer
+ * have any of those objects in memory and will never create any of them
+ * again. In that case, clearing the cache would get rid of the memory
+ * consumed by the compiled maps from each class.
+ *
+ * All currently existing object instances will continue to work, since they
+ * retain their own personal links to their own compiled property maps.
+ * Therefore, the memory you free by calling this function may not be the
+ * full amount (until ALL of the object instances of all classes are freed).
+ * PHP will only be able to free their unused parent classes, and their
+ * unused subclasses, and any other unused classes in general. But PHP will
+ * retain memory for the SPECIFIC, per-class maps for all living objects.
+ *
+ * Calling this function is totally harmless, since all future class
+ * instance constructors will simply re-compile their whole map hierarchies
+ * again from scratch, and all class maps will be re-built IDENTICALLY the
+ * next time since all of their map definitions themselves are immutable
+ * class constants and can NEVER be modified at runtime (not even via PHP
+ * 7.1's advanced `ReflectionClassConstant` class).
+ *
+ * However, because of the fact that all existing object instances retain
+ * private links to their own maps from the previous compilation, it means
+ * that you may actually end up using MORE memory after clearing the global
+ * cache, IF you still have a LOT of different living object instances AND
+ * also begin to create new instances of various classes again (and thus
+ * begin re-compiling new, global instances of each of their "cleared" class
+ * property maps).
+ *
+ * In short: Do NOT call this function unless you know why you are doing it!
+ *
+ * @return int How many unique classes were cached before clearing.
+ */
+ final public static function clearGlobalPropertyMapCache()
+ {
+ $classCount = count(self::$_propertyMapCache->classMaps);
+ self::$_propertyMapCache->clearCache();
+
+ return $classCount;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/FunctionTranslation.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/FunctionTranslation.php
new file mode 100755
index 0000000..89cb92a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/FunctionTranslation.php
@@ -0,0 +1,314 @@
+ `__foo__bar___x_baz__` (snake)
+ * `__foo_Bar__XBaz__` (camel)
+ * - `0m__AnUn0x` => `0m___an_un0x` (snake) & `0m__AnUn0x` (camel)
+ * - `Some0XThing` => `some0_x_thing` (snake) & `some0XThing` (camel)
+ * - `Some0xThing` => `some0x_thing` (snake) & `some0xThing` (camel)
+ * - `SomeThing` => `some_thing` (snake) & `someThing` (camel)
+ * - `Something` => `something` (snake) & `NULL` (camel)
+ * - `___` => `___` (snake) & `NULL` (camel)
+ * - `_0` => `_0` (snake) & `NULL` (camel)
+ * - `_Messages` => `_messages` (snake) & `NULL` (camel)
+ * - `__MessageList` => `__message_list` (snake) & `__messageList` (camel)
+ * - `123` => `123` (snake) & `NULL` (camel)
+ * - `123prop` => `123prop` (snake) & `NULL` (camel)
+ * - `123Prop` => `123_prop` (snake) & `123Prop` (camel)
+ *
+ * ---------------------------------------------------------------------
+ *
+ * `NOTE:` The class validates all parameters, but provides public properties to
+ * avoid needless function calls. It's therefore your responsibility to never
+ * assign any bad values to the public properties after this object's creation!
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ *
+ * @see PropertyTranslation
+ */
+class FunctionTranslation
+{
+ /**
+ * The property name in underscore "snake_case" style.
+ *
+ * For example `some_example_property`.
+ *
+ * @var string
+ */
+ public $snakePropName;
+
+ /**
+ * The property name in "camelCase" style.
+ *
+ * For example `someExampleProperty`.
+ *
+ * `NOTE:` This is `NULL` if the property consists only of a single word.
+ *
+ * @var string|null
+ */
+ public $camelPropName;
+
+ /**
+ * Constructor.
+ *
+ * @param string $funcCase The "property" portion of the function name to
+ * translate, in "FunctionCase" style, such as
+ * `SomeExampleProperty`.
+ *
+ * @throws MagicTranslationException If the `$funcCase` name is unparseable.
+ *
+ * @see FunctionTranslation::splitFunctionName() A helper to split your
+ * function names into the
+ * necessary parts.
+ */
+ public function __construct(
+ $funcCase)
+ {
+ if (!is_string($funcCase) || $funcCase === '') {
+ throw new MagicTranslationException('The function name must be a non-empty string value.');
+ }
+
+ // Convert the FuncCase name to its snake and camel properties.
+ $result = $this->_funcCaseToProperties($funcCase);
+ if ($result === false) {
+ throw new MagicTranslationException(sprintf(
+ 'The provided input value "%s" is not a valid FunctionCase name.',
+ $funcCase
+ ));
+ }
+
+ // We are done with the conversions.
+ $this->snakePropName = $result['snake'];
+ $this->camelPropName = $result['camel'];
+ }
+
+ /**
+ * Split a function name into its function-type and FuncCase components.
+ *
+ * This helper function takes care of splitting a full function name, such
+ * as `getSomeVariable`, into `get` (its function type) and `SomeVariable`
+ * (the valid input format for the FunctionTranslation constructor).
+ *
+ * Call it from your own code before constructing your `FunctionTranslation`
+ * objects. Don't worry about the extra function call for this splitter.
+ * It can perform its job 2.5 million times per second on a 2010 Core i7
+ * dual-core laptop on PHP7, and 0.64 million times per second on PHP5.
+ * And directly embedding these same steps instead of calling this function
+ * will only gain 5% more speed in PHP7 and 16% more speed in PHP5. But the
+ * numbers are already so astronomically fast that it doesn't matter!
+ *
+ * This splitting into `get` and `SomeVariable` is easy and super efficient.
+ * It is this class' final translation of the FunctionCase `SomeVariable`
+ * part into `some_variable` and `someVariable` properties that's the HARD
+ * step which should be cached.
+ *
+ * Recommended usage:
+ *
+ * ```php
+ * list($functionType, $funcCase) = FunctionTranslation::splitFunctionName($name);
+ * // if $functionType is now NULL, the input was invalid. otherwise it was ok.
+ * ```
+ *
+ * @param string $functionName The function name to split. It's your job to
+ * make sure this is a string-type variable! We
+ * will not validate its type. Empty strings ok.
+ *
+ * @return string[]|null[] Two-element array of `functionType` (element 0) &
+ * `funcCase` (element 1). If the input name wasn't
+ * valid a `doSomething` (function-camelCase), then
+ * both elements are `NULL` instead of strings.
+ */
+ public static function splitFunctionName(
+ $functionName)
+ {
+ // Split the input on the FIRST encountered NON-LOWERCAZE ("a-z")
+ // character. And allow empty splits (that's important). Because of the
+ // fact that it splits on non-lowercase character types, it means that
+ // if there are 2 chunks then we KNOW that there was a non-lowercase
+ // character (such as _, 0-9, A-Z, etc) in the input. And if there are
+ // two chunks but the first chunk is an empty string then we know that
+ // there was no lowercase a-z PREFIX in the input. And if there is only
+ // 1 chunk then we know the entire input was all-lowercase a-z.
+ //
+ // Examples of this preg_split()'s behavior:
+ //
+ // "get_" => 2 chunks: "get" & "_"
+ // "getgetX" => 2 chunks: "getget" & "X"
+ // "eraseSomething" => 2 chunks: "erase" & "Something"
+ // "getgetget" (only lowercase a-z) => 1 chunk: "getgetget"
+ // "GetSomething" (no lowercase prefix) => 2 chunks: "" & "GetSomething"
+ // "G" => 2 chunks: "" & "G"
+ // "GX" => 2 chunks: "" & "GX"
+ // "Gx" => 2 chunks: "" & "Gx"
+ // "0" => 2 chunks: "" & "0"
+ // "gx" => 1 chunk: "gx"
+ // "gX" => 2 chunks: "g" & "X"
+ // "g0" => 2 chunks: "g" & "0"
+ // "" (empty string input) => 1 chunk: ""
+ //
+ // Therefore, we know that the input was valid (a lowercase a-z prefix
+ // followed by at least one non-lowercase a-z after that) if we have two
+ // chunks and the first chunk is non-empty!
+ $chunks = preg_split('/(?=[^a-z])/', $functionName, 2);
+ if (count($chunks) === 2 && $chunks[0] !== '') {
+ // [0] = prefix (functionType), [1] = suffix (FuncCase).
+ return $chunks;
+ }
+
+ // Invalid input. Return NULL prefix and NULL suffix values.
+ static $invalidChunks = [null, null]; // Static=Only created first call.
+
+ return $invalidChunks;
+ }
+
+ /**
+ * Converts a FunctionCase name to snake and camel properties.
+ *
+ * See input/output examples in class documentation above.
+ *
+ * @param string $funcCase
+ *
+ * @return array|bool Associative array with `snake` & `camel` elements if
+ * successful, otherwise `FALSE`.
+ */
+ protected function _funcCaseToProperties(
+ $funcCase)
+ {
+ // This algorithm is the exact inverse of PropertyTranslation.
+ // Read that class for more information.
+
+ // There's nothing to do if the input is empty...
+ if (!strlen($funcCase)) {
+ return false;
+ }
+
+ // First, we must decode our encoded representation of any special PHP
+ // operators, just in case their property name had illegal chars.
+ $funcCase = SpecialOperators::decodeOperators($funcCase);
+
+ // Now remove and count all leading underscores (important!).
+ // Example: "__MessageList" => "MessageList".
+ $result = ltrim($funcCase, '_');
+ $leadingUnderscores = strlen($funcCase) - strlen($result);
+
+ // Verify that the REMAINING input result doesn't contain lowercase a-z
+ // as its FIRST character. In that case, we were given invalid input,
+ // because the FuncCase style REQUIRES that the first character is a
+ // NON-LOWERCASE. Anything else is fine, such as UpperCase, numbers or
+ // special characters, etc, but never lowercase, since our splitter
+ // splitFunctionName() would NEVER give us a FuncName part with a
+ // leading lowercase letter! However, the splitter COULD give us
+ // something like "__m" from "get__m". But our PropertyTranslation would
+ // NEVER create output like "__m". It would have created "__M". So
+ // anything that now remains at the start of the string after stripping
+ // leading underscores MUST be non-lowercase.
+ if (preg_match('/^[a-z]/', $result)) {
+ return false;
+ }
+
+ // Split the input into chunks on all camelcase boundaries.
+ // NOTE: Since all chunks are split on camelcase boundaries below, it
+ // means that each chunk ONLY holds a SINGLE fragment which can ONLY
+ // contain at most a SINGLE capital letter (the chunk's first letter).
+ // NOTE: The "PREG_SPLIT_NO_EMPTY" ensures that we don't get an empty
+ // leading array entry when the input begins with an "Upper" character.
+ // NOTE: If this doesn't match anything (meaning there are no uppercase
+ // characters to split on), the input is returned as-is. Such as "123".
+ // NOTE: If $result is an empty string, this returns an empty array.
+ // Example: "MessageList" => "Message" & "List"
+ $chunks = preg_split('/(?=[A-Z])/', $result, -1, PREG_SPLIT_NO_EMPTY);
+ if ($chunks === false) {
+ return false; // Only happens on regex engine failure, NOT mismatch!
+ }
+ $chunkCount = count($chunks);
+
+ // Handle the scenario where there are no chunks ($result was empty).
+ // NOTE: Thanks to all of the validation above, this can ONLY happen
+ // when input consisted entirely of underscores with nothing after that.
+ if ($chunkCount === 0) {
+ // Insert a fake, empty element to act as the first chunk, to ensure
+ // that we have something to insert the underscores into.
+ $chunks[] = '';
+ $chunkCount++;
+ }
+
+ // Lowercase the leading uppercase of 1st chunk (whatever is in there),
+ // since that chunk needs lowercase in both snake_case and camelCase.
+ // Example: "Message" & "List" => "message" & "List"
+ $chunks[0] = lcfirst($chunks[0]);
+
+ // If there were any leading underscores, prepend all of them to the 1st
+ // chunk, which ensures that they become part of the first chunk.
+ // Example: "__message" & "List"
+ if ($leadingUnderscores > 0) {
+ $chunks[0] = str_repeat('_', $leadingUnderscores).$chunks[0];
+ }
+
+ // Now let's create the snake_case and camelCase variable names.
+
+ // The property name chunks are already in perfect format for camelCase.
+ // The first chunk starts with a lowercase letter, and all other chunks
+ // start with an uppercase letter. So generate the camelCase property
+ // name version first. But only if there are 2+ chunks. Otherwise NULL.
+ // NOTE: Turns "i,Tunes,Item" into "iTunesItem", and "foo" into NULL.
+ $camelPropName = $chunkCount >= 2 ? implode('', $chunks) : null;
+
+ // Now make the second property name chunk and onwards into lowercase,
+ // and then generate the all-lowercase "snake_case" property name.
+ // NOTE: Turns "some,Property,Name" into "some_property_name".
+ for ($i = 1; $i < $chunkCount; ++$i) {
+ $chunks[$i] = lcfirst($chunks[$i]); // Only first letter can be UC.
+ }
+ $snakePropName = implode('_', $chunks);
+
+ // Return the final snake_case and camelCase property names.
+ return [
+ 'snake' => $snakePropName,
+ 'camel' => $camelPropName,
+ ];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/PropertyTranslation.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/PropertyTranslation.php
new file mode 100755
index 0000000..a6e02e3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/PropertyTranslation.php
@@ -0,0 +1,191 @@
+ `__foo__bar___x_baz__` (snake)
+ * `__foo_Bar__XBaz__` (camel)
+ * - `0m__AnUn0x` => `0m___an_un0x` (snake) & `0m__AnUn0x` (camel)
+ * - `Some0XThing` => `some0_x_thing` (snake) & `some0XThing` (camel)
+ * - `Some0xThing` => `some0x_thing` (snake) & `some0xThing` (camel)
+ * - `SomeThing` => `some_thing` (snake) & `someThing` (camel)
+ * - `Something` => `something` (snake & camel identical; no ucwords)
+ * - `___` => `___` (snake & camel identical; no ucwords)
+ * - `_0` => `_0` (snake & camel identical; no ucwords)
+ * - `_Messages` => `_messages` (snake & camel identical; no ucwords)
+ * - `__MessageList` => `__message_list` (snake) & `__messageList` (camel)
+ * - `123` => `123` (snake & camel identical; no ucwords)
+ * - `123prop` => `123prop` (snake & camel identical; no ucwords)
+ * - `123Prop` => `123_prop` (snake) & `123Prop` (camel)
+ *
+ * ---------------------------------------------------------------------
+ *
+ * `NOTE:` The class validates all parameters, but provides public properties to
+ * avoid needless function calls. It's therefore your responsibility to never
+ * assign any bad values to the public properties after this object's creation!
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ *
+ * @see FunctionTranslation
+ */
+class PropertyTranslation
+{
+ /**
+ * The property name in "FunctionCase" style.
+ *
+ * For example `AwesomeProperty`.
+ *
+ * @var string
+ */
+ public $propFuncCase;
+
+ /**
+ * Constructor.
+ *
+ * @param string $propertyName The name of the property to translate, in
+ * either `snake_case` or `camelCase` style,
+ * such as `awesome_property` (snake)
+ * or `awesomeProperty` (camel). The
+ * translations will differ based on
+ * which style is used. (That's intentional.)
+ *
+ * @throws MagicTranslationException If the property name is unparseable.
+ */
+ public function __construct(
+ $propertyName)
+ {
+ if (!is_string($propertyName) || $propertyName === '') {
+ throw new MagicTranslationException('The property name must be a non-empty string value.');
+ }
+
+ $this->propFuncCase = $this->_propToFunctionCase($propertyName);
+ }
+
+ /**
+ * Converts a property name to FunctionCase.
+ *
+ * See input/output examples in class documentation above.
+ *
+ * @param string $propName The property name, as either `snake_case` or
+ * `camelCase`. The translations will differ based
+ * on which style is used. (That's intentional.)
+ *
+ * @return string
+ */
+ protected function _propToFunctionCase(
+ $propName)
+ {
+ // To translate a property name to FunctionCase, we must simply convert
+ // any leading lowercase (a-z) character to uppercase, as well as any
+ // (a-z) that comes after an underscore. In the latter case, the
+ // underscore must be removed during the conversion.
+ // For example: "example_property" = "ExampleProperty".
+ // (If multiple underscores, still just remove ONE: "a__b"="A_B").
+ //
+ // However, there is a special case: All LEADING underscores must be
+ // preserved exactly as-is: "__messages" = "__Messages" (still 2).
+ //
+ // As for camelCasePropertyNames? They're already perfect, except that
+ // the first letter must be made uppercase. And IF they have any inner
+ // "_[a-z]" (lowercase) chunks, those should be translated as they would
+ // for a snake_case string. But any "_[A-Z]" should not be touched.
+ // In other words, the algorithm is exactly the same for camelCase.
+
+ // Begin by removing and counting all leading underscores (important!).
+ // NOTE: The reason why we have to do this is because otherwise a
+ // property named "_messages" would be translated to just "Messages",
+ // which then becomes incredibly hard to guess whether it means
+ // "messages" or "_messages". So by preserving any leading underscores,
+ // we remove that ambiguity. It also make the function names more
+ // logical (they match the property's amount of leading underscores) and
+ // it removes clashes. For example if the user defines both "messages"
+ // and "_messages", they can safely use their "getMessages()" and
+ // "get_Messages()" without any ambiguity about what they refer to.
+ $result = ltrim($propName, '_');
+ $leadingUnderscores = strlen($propName) - strlen($result);
+
+ // Now simply uppercase any lowercase (a-z) character that is either at
+ // the start of the string or appears immediately after an underscore.
+ //
+ // ----------------------------------------
+ // TODO: If someone is using Unicode in their JSON data, we should
+ // simply extend this (and also the FunctionTranslation class) to run
+ // with PHP's slower mb_* multibyte functions and "//u" UTF-8 flags,
+ // so that we can support functions like "getÅngbåt()", for a property
+ // named '{"ångbåt":1}'. But honestly, I doubt that even international
+ // users name their JSON data in anything but pure, highly-compressible
+ // ASCII, such as "angbat" and "getAngbat()" in the example above. So
+ // for now, we can have the efficient ASCII algorithms here. In the
+ // future we may want to provide a class-constant to override the
+ // parsers to enable UTF-8 mode, so that the user can have that slower
+ // parsing behavior in certain classes only. And in that case, the magic
+ // function translations can still be stored in the same global cache
+ // together with all the ASCII entries, since they'd use their UTF-8
+ // names as key and thus never clash with anything from this algorithm.
+ // ----------------------------------------
+ $result = preg_replace_callback('/(?:^|_)([a-z])/', function ($matches) {
+ return ucfirst($matches[1]); // Always contains just 1 character.
+ }, $result);
+
+ // Now just prepend any leading underscores that we removed earlier.
+ if ($leadingUnderscores > 0) {
+ $result = str_repeat('_', $leadingUnderscores).$result;
+ }
+
+ // Lastly, we must now translate special PHP operators to an encoded
+ // representation, just in case their property name has illegal chars.
+ $result = SpecialOperators::encodeOperators($result);
+
+ return $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/SpecialOperators.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/SpecialOperators.php
new file mode 100755
index 0000000..1659137
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/SpecialOperators.php
@@ -0,0 +1,169 @@
+',
+ ];
+
+ $translations = ['encode' => [], 'decode' => []];
+ foreach ($operators as $chr) {
+ $hex = str_pad(strtoupper(dechex(ord($chr))), 2, '0', STR_PAD_LEFT);
+ $encoded = sprintf('_x%s_', $hex);
+ $translations['encode'][$chr] = $encoded;
+ $translations['decode'][$encoded] = $chr;
+ }
+
+ self::$_translations = $translations;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyDefinition.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyDefinition.php
new file mode 100755
index 0000000..24bed79
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyDefinition.php
@@ -0,0 +1,325 @@
+arrayDepth = 0;
+ $this->propType = null;
+ $this->isObjectType = false;
+
+ return; // Skip the rest of the code.
+ }
+
+ if (!is_string($definitionStr)) {
+ throw new BadPropertyDefinitionException(
+ 'The property definition must be a string value.'
+ );
+ }
+
+ // Clean up and validate any provided base namespace or make it global.
+ // IMPORTANT NOTE: Any provided classnames "relative to a certain
+ // namespace" will NOT know about any "use"-statements in the files
+ // where those classes are defined. Even Reflection cannot detect "use".
+ if (is_string($baseNamespace)) { // Custom namespace.
+ if (strlen($baseNamespace) > 0
+ && ($baseNamespace[0] === '\\' || substr($baseNamespace, -1) === '\\')) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Invalid namespace "%s". The namespace is not allowed to start or end with a backslash. Use __NAMESPACE__ format.',
+ $baseNamespace
+ ));
+ }
+ } else {
+ $baseNamespace = ''; // Global namespace.
+ }
+
+ // Set arrayDepth: Count well-formed array-brackets at end of type.
+ // Example: "int[][][]" or "[][]" (yes, array of untyped is possible.)
+ $this->arrayDepth = 0;
+ if (preg_match('/(?:\[\])+$/', $definitionStr, $matches)) {
+ // $matches[0] is the sequence of valid pairs, ie "[][][]".
+ $this->arrayDepth = strlen($matches[0]) / 2;
+ // Get rid of the pairs from the end of the definition.
+ $definitionStr = substr($definitionStr, 0, -($this->arrayDepth * 2));
+ }
+
+ // Set propType: It's what remains of our definition string.
+ // Example: "" or "mixed" or "int" or "\Foo\Bar" or "Bar"
+ $this->propType = $definitionStr;
+
+ // Always store "" or "mixed" as NULL, to make it easy to check.
+ if ($this->propType === '' || $this->propType === 'mixed') {
+ $this->propType = null;
+ }
+
+ // If there's no type, or if it refers to a basic type, then we're done.
+ // This ensures that our basic non-empty type value is a real PHP type.
+ // NOTE: This is intentionally cAsE-sensitive.
+ // NOTE: The basic types are reserved names in PHP, so there's no risk
+ // they refer to classes, since PHP doesn't allow such class names.
+ if ($this->propType === null || in_array($this->propType, self::BASIC_TYPES)) {
+ $this->isObjectType = false;
+
+ return; // Skip the rest of the code.
+ }
+
+ // They are trying to refer to a class (or they've mistyped a basic
+ // type). Validate the target to ensure that it's fully trustable
+ // to be a reachable LazyJsonMapper-based class.
+ $this->isObjectType = true;
+
+ // Check if they've used the special shortcut for the core class.
+ if ($this->propType === 'LazyJsonMapper') {
+ $this->propType = '\\'.LazyJsonMapper::class;
+ }
+
+ // Begin by copying whatever remaining type value they've provided...
+ $classPath = $this->propType;
+
+ // First check if they want the global namespace instead.
+ if ($classPath[0] === '\\') {
+ // Their class refers to a global path.
+ $baseNamespace = ''; // Set global namespace as base.
+ $classPath = substr($classPath, 1); // Strip leading "\".
+ }
+
+ // Construct the full class-path from their base namespace and class.
+ // NOTE: The leading backslash is super important to ensure that PHP
+ // actually looks in the target namespace instead of a sub-namespace
+ // of its current namespace.
+ $fullClassPath = sprintf(
+ '\\%s%s%s',
+ $baseNamespace,
+ $baseNamespace !== '' ? '\\' : '',
+ $classPath
+ );
+
+ // Ensure that the target class actually exists (via autoloader).
+ if (!class_exists($fullClassPath)) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Class "%s" not found.',
+ $fullClassPath
+ ));
+ }
+
+ // We'll use a reflector for analysis, to ensure the class is valid
+ // for use as a target type. It must be based on LazyJsonMapper.
+ try {
+ // First clean up the case-insensitive class name to become the
+ // EXACT name for the class. So we can trust "propType" in ===.
+ // Example: "\fOO\bAr" to "Foo\Bar". (Without any leading "\".)
+ // NOTE: getName() gets the "NameSpace\Class" without leading "\",
+ // which is PHP's preferred notation. And that is exactly how we
+ // will store the final path internally, so that we can always
+ // trust comparisons of these typenames vs full paths to other
+ // class names retrieved via other methods such as get_class().
+ // It does however mean that propType is NOT the right value for
+ // actually constructing the class safely.
+ $reflector = new ReflectionClass($fullClassPath);
+ $fullClassPath = $reflector->getName();
+
+ // The target class or its parents MUST inherit LazyJsonMapper,
+ // so that it implements the necessary behaviors and can be
+ // trusted to accept our standardized constructor parameters.
+ // NOTE: As you can see, we also allow users to map directly to
+ // plain "LazyJsonMapper" objects. It's a very bad idea, since
+ // they don't get any property definitions, and therefore their
+ // object would be unreliable. But that's the user's choice.
+ if ($fullClassPath !== LazyJsonMapper::class
+ && !$reflector->isSubClassOf('\\'.LazyJsonMapper::class)) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Class "\\%s" must inherit from LazyJsonMapper.',
+ $fullClassPath
+ ));
+ }
+
+ // Alright, the class path has been fully resolved, validated
+ // to be a LazyJsonMapper, and normalized into its correct name.
+ // ... Rock on! ;-)
+ $this->propType = $fullClassPath;
+ } catch (ReflectionException $e) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Reflection failed for class "%s". Reason: "%s".',
+ $fullClassPath, $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Get the strict, global path to the target class.
+ *
+ * Always use this function when creating objects or in any other way using
+ * the "property type" class path as argument for PHP's class checking
+ * functions. The strict path that it provides ensures that PHP will find
+ * the global path instead of resolving to a local object.
+ *
+ * @return string|null Strict path if this is an object, otherwise `NULL`.
+ */
+ public function getStrictClassPath()
+ {
+ return $this->isObjectType ? '\\'.$this->propType : null;
+ }
+
+ /**
+ * Check if all values of this property match another property object.
+ *
+ * @param PropertyDefinition $otherObject The object to compare with.
+ *
+ * @return bool `TRUE` if all property values are identical, otherwise `FALSE`.
+ */
+ public function equals(
+ PropertyDefinition $otherObject)
+ {
+ // The "==" operator checks for same class and matching property values.
+ return $this == $otherObject;
+ }
+
+ /**
+ * Get the property definition as its string representation.
+ *
+ * The string perfectly represents the property definition, and can
+ * therefore even be used when constructing other object instances.
+ *
+ * @return string
+ */
+ public function asString()
+ {
+ return sprintf(
+ '%s%s%s',
+ $this->isObjectType ? '\\' : '',
+ $this->propType !== null ? $this->propType : 'mixed',
+ str_repeat('[]', $this->arrayDepth)
+ );
+ }
+
+ /**
+ * Get the property definition as its string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->asString();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCache.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCache.php
new file mode 100755
index 0000000..3f88c0d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCache.php
@@ -0,0 +1,73 @@
+classMaps = [];
+ $this->compilerLocks = [];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCompiler.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCompiler.php
new file mode 100755
index 0000000..6872e80
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCompiler.php
@@ -0,0 +1,1274 @@
+compile(); // Throws.
+ }
+
+ /**
+ * Private Constructor.
+ *
+ * @param bool $isRootCompiler Whether this is the root-level
+ * compiler in a compile-stack.
+ * @param PropertyMapCache $propertyMapCache The class/lock cache to use.
+ * @param string $solveClassName The full path of the class to
+ * compile, but without any
+ * leading `\` global prefix. To
+ * save time, we assume the caller
+ * has already verified that it is
+ * a valid `LazyJsonMapper` class.
+ *
+ * @throws BadPropertyMapException
+ */
+ private function __construct(
+ $isRootCompiler,
+ PropertyMapCache $propertyMapCache,
+ $solveClassName)
+ {
+ // Sanity-check the untyped arguments to protect against accidents.
+ if (!is_bool($isRootCompiler)) {
+ throw new BadPropertyMapException('The isRootCompiler argument must be a boolean.');
+ }
+ if (!is_string($solveClassName) || $solveClassName === '') {
+ throw new BadPropertyMapException('The class name to solve must be a non-empty string.');
+ }
+
+ // Save user-properties.
+ // NOTE: The compiler uses a concept of "root compiler" and recursive
+ // "sub-compilers". The compilers automatically spawn sub-compilers for
+ // any resources they need. And the ROOT compiler is responsible for
+ // coordinating everything and doing any final post-processing work
+ // AFTER the main hierarchy (extends) and "import" compilation steps.
+ $this->_isRootCompiler = $isRootCompiler;
+ $this->_propertyMapCache = $propertyMapCache;
+ $this->_solveClassName = $solveClassName;
+
+ // Generate a strict name (with a leading "\") to tell PHP to search for
+ // it from the global namespace, in commands where we need that.
+ // NOTE: We index the cache by "Foo\Bar" for easy lookups, since that is
+ // PHP's internal get_class()-style representation. But we use strict
+ // "\Foo\Bar" when we actually interact with a class or throw errors.
+ // That way, both PHP and the user understands that it's a global path.
+ $this->_strictSolveClassName = Utilities::createStrictClassPath($this->_solveClassName);
+
+ // We will keep track of ALL classes compiled by ourselves and our
+ // recursive sub-compilers. In case of errors ANYWHERE in the process,
+ // we MUST ensure that all of those classes are erased from the cache
+ // again, since they may have serious errors. This list of compiled
+ // classes was specifically added to handle the fact that the class map
+ // and its hierarchy/imports themselves often fully compile themselves
+ // successfully, but then THEIR property class compilations (the root-
+ // POST-PROCESSING step which compiles all classes pointed to by the
+ // maps) MAY fail. In that case, or if there are ANY other compilation
+ // problems in any of the class hierarchies, then we will be sure to
+ // erase ALL of our compiled classes from the cache; otherwise it WOULD
+ // look like those classes are fully compiled and reliable, despite the
+ // bad and invalid classes some of their properties point to!
+ $this->compiledClasses = [];
+
+ // During the main compilation phase, we'll ONLY compile the actual
+ // class that we've been told to compile, as well as its parent
+ // hierarchy and any imports that any of them contain. But any of those
+ // compiled maps MAY in turn contain PROPERTIES that point at OTHER
+ // uncompiled classes. The PropertyDefinition construction will always
+ // verify that those properties are pointing at reachable (having a
+ // valid class-path) LazyJsonMapper classes. However, the actual CLASS
+ // MAPS of THOSE classes may contain problems too and may be impossible
+ // to compile. We obviously want to figure that out for the user RIGHT
+ // NOW so that they don't run into issues later when trying to access
+ // such a property someday (which would THEN trigger its map compilation
+ // and throw a compiler failure deep into their runtime environment when
+ // they least expected it). The truth is that normal people will
+ // sometimes write bad classes, and then point properties at those bad
+ // classes. And without us proactively pre-compiling all classes pointed
+ // to by their properties, they would NEVER know about the problem until
+ // someday later when their program suddenly accesses that bad property
+ //
+ // So we SHOULD analyze everything for the user's safety. But... we
+ // unfortunately CANNOT compile them during the main compilation stage.
+ // In fact, we cannot even compile them afterwards EITHER, UNLESS we are
+ // the ABSOLUTE ROOT COMPILE-CALL which STARTED the current compilation
+ // process. Only the root function call is allowed to resolve PROPERTY
+ // references to other classes and ensure that THOSE maps are
+ // successfully are compiled too. Otherwise we'd run into circular
+ // compilation issues where properties fail to compile because they may
+ // be pointing at things which are not compiled yet (such as a class
+ // containing a property that points at itself, or "A" having a property
+ // that points to class "B" which has a property that points to class
+ // "C" which has a property that points back at class "A" and therefore
+ // would cause a circular reference if we'd tried to compile those
+ // property-classes instantly as-they're encountered). We must therefore
+ // defer property-class compilation until ALL core classes in the
+ // current inheritance/import chain are resolved. THEN it's safe to
+ // begin compiling any other encountered classes from the properties...
+ //
+ // The solution is "simple": Build a list of the class-paths of all
+ // encountered UNCOMPILED property classes within our own map tree and
+ // within any imports (which are done via recursive compile-calls). To
+ // handle imports, we simply ensure that our compile-function (this one)
+ // lets the parent retrieve our list of "encountered uncompiled classes
+ // from properties" so that we'll let it bubble up all the way to the
+ // root class/call that began the whole compile-chain. And then just let
+ // that root-level call resolve ALL uncompiled properties from the
+ // ENTIRE tree by simply compiling the list of property-classes one by
+ // one if they're still missing. That way, we'll get full protection
+ // against uncompilable class-properties ANYWHERE in our tree, and we'll
+ // do it at a totally safe time (at the end of the main compilation call
+ // that kicked everything off)!
+ //
+ // NOTE: As an optimization, to avoid constant array-writes and the
+ // creation of huge and rapidly growing "encountered classes" arrays, we
+ // will attempt to ONLY track the encountered classes that are MISSING
+ // from the cache (not yet compiled). And this list will ALSO be checked
+ // one more time at the end, to truly clear everything that's already
+ // done. That's intended to make the list as light-weight as possible
+ // so that our parent-compiler doesn't have to do heavy lifting.
+ //
+ // NOTE: The fact is that most classes that properties point to will
+ // already be pre-compiled, or at least large parts of their
+ // inheritance/import hierarchy will already be compiled, so this
+ // recursive "property-class" compilation isn't very heavy at all. It's
+ // as optimized as it can be. Classes we encounter will ONLY compile the
+ // specific aspects of their class maps which HAVEN'T been compiled yet,
+ // such as a class that is a sub-tree (extends) of an already-compiled
+ // class, which means that the extra class would be very fast to
+ // compile. Either way, we basically spend a tiny bit of extra effort
+ // here during compilation to save users from a TON of pain from THEIR
+ // bad classes later. ;-)
+ $this->uncompiledPropertyClasses = [];
+
+ // Initialize the remaining class properties.
+ $this->_classHierarchy = [];
+ $this->_ourCompilerClassLocks = [];
+ $this->_previousMapConstantValue = null;
+ $this->_currentClassInfo = null;
+ $this->_currentClassPropertyMap = [];
+ }
+
+ /**
+ * Compile the solve-class, and its parent/import hierarchy (as-necessary).
+ *
+ * Intelligently watches for compilation errors, and if so it performs an
+ * auto-rollback of ALL classes compiled by this `compile()`-call AND by all
+ * of its recursive sub-compilations (if any took place).
+ *
+ * It performs the rollback if there were ANY issues with the solve-class or
+ * ANY part of its inheritance hierarchy (extends/imports) OR with the final
+ * post-processing compilation of all of the classes that THEIR properties
+ * were pointing at. And note that the post-processing is ALSO recursive and
+ * will FULLY validate the entire hierarchies and property trees of ALL
+ * classes that IT compiles, and so on... until no more work remains.
+ *
+ * So if this function DOESN'T throw, then you can TRUST that the ENTIRE
+ * hierarchy of property maps related to the requested solve-class has been
+ * compiled. However, note that their final PROPERTY CLASS compilation step
+ * and final success-validation doesn't happen until the top-level
+ * rootCompiler is reached again, since it's the job of the root to handle
+ * the recursive compilation and resolving of all classes encountered in
+ * properties. So it's only the rootCompiler's `compile()`-call that TRULY
+ * matters and will determine whether EVERYTHING was successful or not.
+ *
+ * Basically: If we fail ANY aspect of the request to compile, then we'll
+ * FULLY roll back everything that we've changed during our processing.
+ * Which safely guarantees that the FINAL map compilation cache ONLY
+ * contains fully verified and trustable classes and their COMPLETE class
+ * inheritance and property hierarchies!
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ *
+ * @see PropertyMapCompiler::compileClassPropertyMap() The public, static
+ * class entry point.
+ * @see PropertyMapCompiler::_compile() The internal compiler
+ * core.
+ */
+ public function compile()
+ {
+ try {
+ $this->_compile();
+ } catch (\Exception $e) { // NOTE: Could target exact type, but meh.
+ // Our compilation or one of its sub-compilations has failed... We
+ // MUST now perform a rollback and unset everything in our list of
+ // compiled classes (which also includes everything that our
+ // sub-compilers have compiled).
+ // NOTE: Every compile()-call is responsible for unsetting ITS OWN
+ // list of (sub-)compiled classes. Because the parent-handler that
+ // reads our "compiledClasses" property may not run when an
+ // exception happens. So it's OUR job to clear OUR changes to the
+ // cache before we let the exception bubble up the call-stack.
+ foreach ($this->compiledClasses as $className => $x) {
+ unset($this->_propertyMapCache->classMaps[$className]);
+ unset($this->compiledClasses[$className]);
+ }
+
+ throw $e; // Re-throw. (Keeps its stack trace & line number.)
+ }
+ }
+
+ /**
+ * The real, internal compiler algorithm entry point.
+ *
+ * MUST be wrapped in another function which handles compilation failures!
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ *
+ * @see PropertyMapCompiler::compile() The wrapper entry point.
+ */
+ private function _compile()
+ {
+ // There's nothing to do if the class is already in the cache.
+ if (isset($this->_propertyMapCache->classMaps[$this->_solveClassName])) {
+ return; // Abort.
+ }
+
+ // Let's compile the desired "solve-class", since it wasn't cached.
+ // NOTE: This entire algorithm is EXCESSIVELY commented so that everyone
+ // will understand it, since it's very complex thanks to its support for
+ // inheritance and "multiple inheritance" (imports), which in turn uses
+ // advanced anti-circular dependency protection to detect bad imports.
+ // As well as its automatic end-of-run compilation of any encountered,
+ // current uncompiled classes from any properties in the tree.
+
+ // Prepare the class hierarchy for compilation.
+ $this->_reflectSolveClassHierarchy(); // Throws.
+ $this->_checkCurrentLocks(); // Throws.
+ $this->_lockUncompiledClasses();
+
+ // Traverse the class hierarchy and compile and merge their property
+ // maps, giving precedence to later classes in case of name clashes.
+ //
+ // And because we build the list top-down through the chain of class
+ // inheritance (starting at base and going to the deepest child), and
+ // thanks to the fact that PHP classes can only extend from 1 parent
+ // class, it also means that we're actually able to save the per-class
+ // lists at every step of the way, for each class we encounter along the
+ // way. To avoid needing to process those later.
+ //
+ // This builds a properly inherited class property map and constructs
+ // and validates all property definitions so that we don't get any nasty
+ // surprises during data-parsing later.
+ //
+ // The top-down order also ensures that we re-use our parent's inherited
+ // PropertyDefinition objects, so that the compiled classes all share
+ // memory whenever they inherit from the same parent classes & imports.
+ try {
+ foreach ($this->_classHierarchy as $classInfo) {
+ $this->_currentClassInfo = $classInfo;
+ $this->_processCurrentClass(); // Throws.
+ }
+ } finally {
+ // IMPORTANT: If we've compiled all classes, or if uncaught
+ // exceptions were thrown during processing, then we must simply
+ // ensure that we unlock all of OUR remaining locks before we allow
+ // the exception to keep bubbling upwards OR processing to continue.
+ // NOTE: We AREN'T allowed to unlock classes we didn't lock.
+ foreach ($this->_ourCompilerClassLocks as $lockedClassName => $x) {
+ unset($this->_ourCompilerClassLocks[$lockedClassName]);
+ unset($this->_propertyMapCache->compilerLocks[$lockedClassName]);
+ }
+ }
+
+ // If we've reached this point, it means that our solve-class SHOULD be
+ // in the cache since its entire compilation ran successfully above.
+ // Just verify it to be extra safe against any random future mistakes.
+ // NOTE: This step should never go wrong.
+ if (!isset($this->_propertyMapCache->classMaps[$this->_solveClassName])) {
+ throw new BadPropertyMapException(sprintf(
+ 'Error while compiling class "%s". Could not find the class in the cache afterwards.',
+ $this->_strictSolveClassName
+ ));
+ }
+
+ // If we came this far, it means that NOTHING above threw, which means
+ // that our class and its hierarchy of inheritance (and map-imports), as
+ // well as all PropertyDefinitions, have succeeded for the whole tree...
+ //
+ // Now the only remaining question is whether it's safe for us to check
+ // the list of encountered classes in properties and ensure that those
+ // can all be compiled too... And the answer is NO, unless we are the
+ // absolute ROOT CALL which began the whole compilation process. If not,
+ // then we should merely finish cleaning up our list of encountered
+ // "uncompiled" classes and then let our parent-caller deal with it.
+
+ // If we are a subcall (sub-compilation within a main compilation),
+ // simply let our parent compiler bubble our data to the root call...
+ if (!$this->_isRootCompiler) {
+ // Some already-processed classes may have slipped onto the list due
+ // to being encountered as properties before those classes became
+ // compiled. So before returning to the parent, we'll just ensure
+ // that we remove all keys that refer to classes that have already
+ // been compiled. That saves RAM and processing power during the
+ // array merging in the parent. Basically, we want this list to
+ // always be as short as possible so there's less need for any HUGE
+ // array manipulation at a higher stage while it's bubbling up.
+ foreach ($this->uncompiledPropertyClasses as $uncompiledClassName => $x) {
+ if (isset($this->_propertyMapCache->classMaps[$uncompiledClassName])) {
+ unset($this->uncompiledPropertyClasses[$uncompiledClassName]);
+ }
+ }
+
+ return; // Skip the rest of the code.
+ }
+
+ // We're the root call! So it is now safe (and IMPORTANT!) to compile
+ // all of the not-yet-compiled classes from the inheritance tree, to
+ // ensure that EVERY part of the tree is ready as far as classmaps go.
+ // NOTE: We have no information about how deeply the encountered classes
+ // existed. But it doesn't matter, since they may exist in multiple
+ // places. If there's a problem we'll simply say that "the class (this
+ // initial root/non-subcall one) or one of its parents or imports has a
+ // property which is linked to bad class X". Especially since there may
+ // also be problems within properties of THESE classes that we're going
+ // to compile. So trying to pinpoint exactly which class had the bad
+ // reference to an uncompilable class is overkill. We'll just show the
+ // uncompilable class name plus its regular high-quality compilation
+ // error message which describes why that class failed to compile.
+ // The user should simply fix their code in THAT particular class.
+ while (!empty($this->uncompiledPropertyClasses)) {
+ // IMPORTANT: Create a COPY-ON-WRITE version of the current contents
+ // of the "uncompiledPropertyClasses" array. Because that array will
+ // be changing whenever we sub-compile, so we'll use a stable COPY.
+ $workQueue = $this->uncompiledPropertyClasses;
+
+ // Process all entries (classes to compile) in the current queue.
+ foreach ($workQueue as $uncompiledClassName => $x) {
+ // Skip this class if it's already been successfully compiled.
+ if (isset($this->_propertyMapCache->classMaps[$uncompiledClassName])) {
+ unset($this->uncompiledPropertyClasses[$uncompiledClassName]);
+ continue;
+ }
+
+ // Attempt to compile the missing class. We do this one by one
+ // in isolation, so that each extra class we compile gets its
+ // entirely own competition-free sub-compiler with its own
+ // class-locks (since our own top-level rootCompiler's hierarchy
+ // is already fully compiled by this point). Which means that
+ // the sub-classes are welcome to refer to anything we've
+ // already compiled, exactly as if each of these extra classes
+ // were new top-level compiles "running in isolation". The only
+ // difference is that they aren't marked as the root compiler,
+ // since we still want to be the one to resolve all of THEIR
+ // uncompiled property-classes here during THIS loop. Mainly so
+ // that we can be the one to throw exception messages, referring
+ // to the correct root-level class as the one that failed.
+ try {
+ // NOTE: If this subcompile is successful and encounters any
+ // more uncompiled property-classes within the classes it
+ // compiles, they'll be automatically added to OUR OWN list
+ // of "uncompiledPropertyClasses" and WILL be resolved too.
+ //
+ // This will carry on until ALL of the linked classes are
+ // compiled and no more work remains. In other words, we
+ // will resolve the COMPLETE hierarchies of every linked
+ // class and ALL of their properties too. However, this
+ // subcompilation is still very fast since most end-stage
+ // jobs refer to classes that are already mostly-compiled
+ // due to having most of their own dependencies already
+ // pre-compiled at that point.
+ $this->_subcompile($uncompiledClassName); // Throws.
+ } catch (\Exception $e) { // NOTE: Could target exact type, but meh.
+ // Failed to compile the class we discovered in a property.
+ // It can be due to all kinds of problems, such as circular
+ // property maps or bad imports or bad definitions or
+ // corrupt map variables or anything else. We'll wrap the
+ // error message in a slightly prefixed message just to hint
+ // that this problem is with a property. Because the
+ // compilation error message itself is already very clear
+ // about which class failed to compile and why.
+ // NOTE: This "prefixed" message handling is unlike parents
+ // and imports, where we simply let their original exception
+ // message bubble up as if they were part of the core class,
+ // since imports are a more "integral" part of a class (a
+ // class literally CANNOT be built without its parents and
+ // its imports compiling). But CLASSES IN PROPERTIES are
+ // different and are more numerous and are capable of being
+ // resolved by _getProperty() LATER without having been
+ // pre-compiled when the main class map itself was compiled
+ // (although we just DID that pre-compilation above; we'll
+ // NEVER let them wait to get resolved later).
+ // NOTE: This WILL cause us to lose the specific class-type
+ // of the exception, such as CircularPropertyMapException,
+ // etc. That's intentional, since THIS error is about a bad
+ // map in the PARENT-hierarchy (IT pointing at a bad class).
+ throw new BadPropertyMapException(sprintf(
+ 'Compilation of sub-property hierarchy failed for class "%s". Reason: %s',
+ $this->_strictSolveClassName, $e->getMessage()
+ ));
+ } // End of _subcompile() try-catch.
+
+ // The sub-compile was successful (nothing was thrown), which
+ // means that it's now in the compiled property map cache. Let's
+ // unset its entry from our list of uncompiled classes.
+ unset($this->uncompiledPropertyClasses[$uncompiledClassName]);
+ } // End of work-queue loop.
+ } // End of "handle uncompiled property classes" loop.
+ }
+
+ /**
+ * Reflect all classes in the solve-class hierarchy.
+ *
+ * Builds a list of all classes in the inheritance chain, with the
+ * base-level class as the first element, and the solve-class as the
+ * last element. (The order is important!)
+ *
+ * @throws BadPropertyMapException
+ */
+ private function _reflectSolveClassHierarchy()
+ {
+ if (!empty($this->_classHierarchy)) {
+ throw new BadPropertyMapException('Detected multiple calls to _reflectSolveClassHierarchy().');
+ }
+
+ try {
+ // Begin reflecting the "solve-class". And completely refuse to
+ // proceed if the class name we were asked to solve doesn't match
+ // its EXACT real name. It's just a nice bonus check for safety,
+ // to ensure that our caller will be able to find their expected
+ // result in the correct cache key later.
+ $reflector = new ReflectionClass($this->_strictSolveClassName);
+ if ($this->_solveClassName !== $reflector->getName()) {
+ throw new BadPropertyMapException(sprintf(
+ 'Unable to compile class "%s" due to mismatched class name parameter value (the real class name is: "%s").',
+ $this->_strictSolveClassName, Utilities::createStrictClassPath($reflector->getName())
+ ));
+ }
+
+ // Now resolve all classes in its inheritance ("extends") chain.
+ do {
+ // Store the class in the hierarchy.
+ // NOTE: The key is VERY important because it's used later when
+ // checking if a specific class exists in the current hierarchy.
+ $this->_classHierarchy[$reflector->getName()] = [
+ 'reflector' => $reflector,
+ 'namespace' => $reflector->getNamespaceName(),
+ 'className' => $reflector->getName(), // Includes namespace.
+ 'strictClassName' => Utilities::createStrictClassPath($reflector->getName()),
+ ];
+
+ // Update the reflector variable to point at the next parent
+ // class in its hierarchy (or false if no more exists).
+ $reflector = $reflector->getParentClass();
+ } while ($reflector !== false);
+
+ // Reverse the list to fix the order, since we built it "bottom-up".
+ $this->_classHierarchy = array_reverse($this->_classHierarchy, true);
+ } catch (ReflectionException $e) {
+ // This should only be able to fail if the classname was invalid.
+ throw new BadPropertyMapException(sprintf(
+ 'Reflection of class hierarchy failed for class "%s". Reason: "%s".',
+ $this->_strictSolveClassName, $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Check for already-locked classes in the solve-class hierarchy.
+ *
+ * Analyzes the solve-class hierarchy and verifies that we don't violate
+ * any current anti-circular map locks.
+ *
+ * @throws CircularPropertyMapException
+ */
+ private function _checkCurrentLocks()
+ {
+ foreach ($this->_classHierarchy as $classInfo) {
+ // FATAL: If any part of our class hierarchy is already locked ("is
+ // being compiled right now", higher up the call stack), then it's a
+ // bad map with circular "import"-statements.
+ // NOTE: This specifically protects against the dangerous scenario
+ // of importing classes derived from our hierarchy but whose
+ // classname isn't in the current class hierarchy, so they weren't
+ // blocked by the "import-target sanity checks". So when they get
+ // to their own sub-compilation, their class hierarchy is checked
+ // here and we'll discover their circular inheritance.
+ if (isset($this->_propertyMapCache->compilerLocks[$classInfo['className']])) {
+ // NOTE: strictClassName goes in "arg1" because it's being
+ // compiled earlier than us, so we're definitely the "arg2".
+ throw new CircularPropertyMapException(
+ $classInfo['strictClassName'],
+ $this->_strictSolveClassName
+ );
+ }
+ }
+ }
+
+ /**
+ * Lock all uncompiled classes in the solve-class hierarchy.
+ *
+ * @throws BadPropertyMapException
+ */
+ private function _lockUncompiledClasses()
+ {
+ if (!empty($this->_ourCompilerClassLocks)) {
+ throw new BadPropertyMapException('Detected multiple calls to _lockUncompiledClasses().');
+ }
+
+ foreach ($this->_classHierarchy as $classInfo) {
+ // If the class isn't already compiled, we'll lock it so nothing
+ // else can refer to it. We'll lock every unresolved class, and then
+ // we'll be unlocking them one by one as we resolve them during THIS
+ // exact compile-call. No other sub-compilers are allowed to use
+ // them while we're resolving them!
+ if (!isset($this->_propertyMapCache->classMaps[$classInfo['className']])) {
+ // NOTE: We now know that NONE of these unresolved classes were
+ // in the lock-list before WE added them to it. That means that
+ // we can safely clear ANY of those added locks if there's a
+ // problem. But WE are NOT allowed to clear ANY OTHER LOCKS!
+ $this->_ourCompilerClassLocks[$classInfo['className']] = true;
+ $this->_propertyMapCache->compilerLocks[$classInfo['className']] = true;
+ }
+ }
+ }
+
+ /**
+ * Process the current class in the solve-class hierarchy.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _processCurrentClass()
+ {
+ // We must always begin by reflecting its "JSON_PROPERTY_MAP" class
+ // constant to determine if this particular class in the hierarchy
+ // re-defines its value (compared to inheriting it from its "extends"
+ // parent). This protects against accidentally re-parsing inherited
+ // constants, which would both waste time AND would be VERY dangerous,
+ // since that would re-interpret all inherited relative properties (ones
+ // pointing at classes relative to the class which ACTUALLY declared
+ // that constant) as if they were relative to the INHERITING class
+ // instead, which is VERY wrong (and could lead to classes being either
+ // mismapped or "not found"). Luckily the code below fully protects us
+ // against that scenario, by detecting if our value is "ours" or not.
+ try {
+ // Use different techniques based on what their PHP supports.
+ $foundConstant = false;
+ if (version_compare(PHP_VERSION, '7.1.0') >= 0) {
+ // In PHP 7.1.0 and higher, they've finally added "reflection
+ // constants" which allow us to get accurate extra information.
+ $reflectionConstant = $this->_currentClassInfo['reflector']
+ ->getReflectionConstant('JSON_PROPERTY_MAP');
+ if ($reflectionConstant !== false) {
+ $foundConstant = true;
+
+ // Just read its value. We don't have to care about its
+ // isPrivate() flags etc. It lets us read the value anyway.
+ $rawClassPropertyMap = $reflectionConstant->getValue();
+
+ // Use PHP7.1's ReflectionClassConstant's ability to tell us
+ // EXACTLY which class declared the current (inherited or
+ // new) value for the constant. If OUR class didn't declare
+ // it, then we know that it was inherited and should NOT be
+ // parsed again. But if we DID declare its value, then we
+ // know that we MUST parse it ("has different constant").
+ // NOTE: This method is 100% accurate even when re-declared
+ // to the exact same value as the inherited (parent) value!
+ $hasDifferentConstant = ($reflectionConstant
+ ->getDeclaringClass()->getName()
+ === $this->_currentClassInfo['className']);
+ }
+ } else {
+ // In older PHP versions, we're pretty limited... we don't get
+ // ANY extra information about the constants. We just get their
+ // values. And if we try to query a specific constant, we get
+ // FALSE if it doesn't exist (which is indistinguishable from it
+ // actually having FALSE values). So we MUST get an array of all
+ // constants to be able to check whether it TRULY exists or not.
+ $classConstants = $this->_currentClassInfo['reflector']
+ ->getConstants();
+ if (array_key_exists('JSON_PROPERTY_MAP', $classConstants)) {
+ $foundConstant = true;
+
+ // Read its value. Unfortunately old versions of PHP don't
+ // give us ANY information about which class declared it
+ // (meaning whether it was inherited or re-declared here).
+ $rawClassPropertyMap = $classConstants['JSON_PROPERTY_MAP'];
+
+ // MAGIC: This is the best we can do on old PHP versions...
+ // The "!==" ensures that this constant really differs from
+ // its parent. In case of arrays, they are only treated as
+ // identical if both arrays have the same key & value types
+ // and values in the exact same order and same count(). And
+ // it checks recursively!
+ //
+ // NOTE: This method is great, but it's not as accurate as
+ // the perfect PHP 7.1 method. It actually has a VERY TINY
+ // issue where it will fail to detect a new constant: If you
+ // manually re-declare a constant to EXACTLY the same value
+ // as what you would have inherited from your parent
+ // ("extends") hierarchy, then we won't be able to detect
+ // that you have a new value. Instead, you will inherit the
+ // compiled parent class map as if you hadn't declared any
+ // value of your own at all. In almost all cases, that won't
+ // matter, and will give you the same result. It only
+ // matters in a very tiny case which probably won't happen
+ // to anybody in real-world usage:
+ //
+ // namespace A { class A { "foo":"B[]" } class B {} }
+ // namespace Z { class Z extends \A\A { "foo":"B[]" } class B {} }
+ //
+ // In that situation, Z\Z would inherit the constant from
+ // A\A, which compiled the relative-class "foo" property as
+ // "\A\B". And when we look at Z's own constant, we would
+ // see a 100% identical JSON_PROPERTY_MAP constant compared
+ // to A\A, so we would assume that since all the array keys
+ // and values match exactly, its constant "was inherited".
+ // Therefore the "identical" constant of Z will not be
+ // parsed. And Z's foo will therefore also link to "\A\B",
+ // instead of "\Z\B".
+ //
+ // In reality, I doubt that ANY user would EVER have such a
+ // weird inheritance structure, with "extends" across
+ // namespaces and relative paths to classes that exist in
+ // both namespaces, etc. And the problem above would not be
+ // able to happen as long as their "Z\Z" map has declared
+ // ANY other value too (or even just changed the order of
+ // the declarations), so that we detect that their constant
+ // differs in "Z\Z". So 99.9999% of users will be safe.
+ //
+ // And no... we CAN'T simply always re-interpret it, because
+ // then we'd get a FAR more dangerous bug, which means that
+ // ALL relative properties would re-compile every time they
+ // are inherited, "as if they had been declared by us".
+ //
+ // So no, this method really is the BEST that PHP < 7.1 has.
+ $hasDifferentConstant = ($rawClassPropertyMap
+ !== $this->_previousMapConstantValue);
+
+ // Update the "previous constant value" since we will be the
+ // new "previous" value after this iteration.
+ $this->_previousMapConstantValue = $rawClassPropertyMap;
+ }
+ }
+ if (!$foundConstant) {
+ // The constant doesn't exist. Should never be able to happen
+ // since the class inherits from LazyJsonMapper.
+ throw new ReflectionException(
+ // NOTE: This exception message mimics PHP's own reflection
+ // error message style.
+ 'Constant JSON_PROPERTY_MAP does not exist'
+ );
+ }
+ } catch (ReflectionException $e) {
+ // Unable to read the map constant from the class...
+ throw new BadPropertyMapException(sprintf(
+ 'Reflection of JSON_PROPERTY_MAP constant failed for class "%s". Reason: "%s".',
+ $this->_currentClassInfo['strictClassName'], $e->getMessage()
+ ));
+ }
+
+ // If we've already parsed that class before, it means that we've
+ // already fully parsed it AND its parents. If so, just use the cache.
+ if (isset($this->_propertyMapCache->classMaps[$this->_currentClassInfo['className']])) {
+ // Overwrite our whole "_currentClassPropertyMap" with the one from
+ // our parent instead. That's faster than merging the arrays, and
+ // guarantees PERFECT PropertyDefinition instance re-usage.
+ // NOTE: To explain it, imagine class B extending from A. A has
+ // already been parsed and cached. We then instantiate B for the
+ // first time and we need to build B's definitions. By copying the
+ // cache from A (its parent), we'll re-use all of A's finished
+ // PropertyDefinition objects in B and throughout any other chain
+ // that derives from the same base object (A). This holds true for
+ // ALL objects that inherit (or "import") ANYTHING (such as
+ // something that later refers to "B"), since we ALWAYS resolve the
+ // chains top-down from the base class to the deepest child class,
+ // and therefore cache the base (hierarchically shared) definitions
+ // FIRST.
+ $this->_currentClassPropertyMap = $this->_propertyMapCache->classMaps[
+ $this->_currentClassInfo['className']
+ ];
+ } else {
+ // We have not parsed/encountered this class before...
+
+ // Only parse its class constant if it differs from its inherited
+ // parent's constant value. Otherwise we'll keep the parent's map
+ // (the value that's currently in "_currentClassPropertyMap").
+ if ($hasDifferentConstant) {
+ // Process and validate the JSON_PROPERTY_MAP of the class.
+ if (!is_array($rawClassPropertyMap)) {
+ throw new BadPropertyMapException(sprintf(
+ 'Invalid JSON property map in class "%s". The map must be an array.',
+ $this->_currentClassInfo['strictClassName']
+ ));
+ }
+ foreach ($rawClassPropertyMap as $propName => $propDefStr) {
+ // Process the current entry and add it to the current
+ // class' compiled property map if the entry is new/diff.
+ $this->_processPropertyMapEntry( // Throws.
+ $propName,
+ $propDefStr
+ );
+ }
+ }
+
+ // Mark the fact that we have compiled this class, so that we'll be
+ // able to know which classes we have compiled later. This list will
+ // bubble through the compile-call-hierarchy as-needed to keep track
+ // of EVERY compiled class during the current root compile()-run.
+ $this->compiledClasses[$this->_currentClassInfo['className']] = true;
+
+ // Now cache the final property-map for this particular class in the
+ // inheritance chain... Note that if it had no new/different map
+ // declarations of its own (despite having a different constant),
+ // then we're still simply re-using the exact property-map objects
+ // of its parent class here again.
+ $this->_propertyMapCache->classMaps[
+ $this->_currentClassInfo['className']
+ ] = $this->_currentClassPropertyMap;
+ } // End of class-map "lookup-or-compilation".
+
+ // IMPORTANT: We've finished processing a class. If this was one of the
+ // classes that WE locked, we MUST NOW unlock it, but ONLY if we
+ // successfully processed the class (added it to the cache). Otherwise
+ // we'll unlock it at the end (where we unlock any stragglers) instead.
+ // NOTE: We AREN'T allowed to unlock classes we didn't lock.
+ // NOTE: Unlocking is IMPORTANT, since it's necessary for being able to
+ // import classes from diverging branches with shared inheritance
+ // ancestors. If we don't release the parent-classes one by one as we
+ // resolve them, then we would never be able to import classes from
+ // other branches of our parent/class hierarchy, since the imports would
+ // see that one of their parents (from our hierarchy) is still locked
+ // and would refuse to compile themselves.
+ if (isset($this->_ourCompilerClassLocks[$this->_currentClassInfo['className']])) {
+ unset($this->_ourCompilerClassLocks[$this->_currentClassInfo['className']]);
+ unset($this->_propertyMapCache->compilerLocks[$this->_currentClassInfo['className']]);
+ }
+ }
+
+ /**
+ * Process a property map entry for the current class.
+ *
+ * Validates and compiles a map entry, and merges it with the current class
+ * property map if everything went okay and the entry was new/different.
+ *
+ * @param string|int $propName The property name, or a numeric array key.
+ * @param mixed $propDefStr Should be a string describing the property
+ * or the class to import, but may be
+ * something else if the user has written an
+ * invalid class property map.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _processPropertyMapEntry(
+ $propName,
+ $propDefStr)
+ {
+ if (is_string($propName) && is_string($propDefStr)) {
+ // This is a string key -> string value pair, so let's attempt to
+ // compile it as a regular property definition and then add it to
+ // our current class map if the entry is new/different.
+ $this->_processPropertyDefinitionString( // Throws.
+ $propName,
+ $propDefStr
+ );
+ } else {
+ // It cannot be a regular property definition. Check if this is an
+ // "import class map" command. They can exist in the map and tell us
+ // to import other classes, and their instructions are written as
+ // [OtherClass::class, 'ownfield'=>'string'].
+ $isImportCommand = false;
+ if (is_int($propName) && is_string($propDefStr)) {
+ // This is an int key -> string value pair, so we should treat
+ // it as a potential "import class map" command. We must first
+ // ensure that the class has a strictly global "\" prefix.
+ $strictImportClassName = Utilities::createStrictClassPath($propDefStr);
+
+ // Now check if the target class fits the "import class"
+ // requirements.
+ if (class_exists($strictImportClassName)
+ && is_subclass_of($strictImportClassName, '\\'.LazyJsonMapper::class)) {
+ // This is an "import other class" command! Thanks to the
+ // lack of an associative key, we saw a numeric array key
+ // (non-associative). And we've verified (above) that its
+ // value points to another valid class which is a sub-class
+ // of LazyJsonMapper (we don't bother allowing to import
+ // LazyJsonMapper itself, since it is the lowest possible
+ // base class and has 0 values).
+ $isImportCommand = true;
+
+ // Perform the import, which will compile the target (if
+ // necessary) and then merge its map with our current map.
+ // NOTE: There is no need for us to prevent the user from
+ // doing multiple imports of the same class, because the way
+ // the importing works ensures that we always re-use the
+ // same PropertyDefinition object instances from the other
+ // class every time we import the other class.
+ $this->_importClassMap( // Throws.
+ $strictImportClassName
+ );
+ }
+ }
+ if (!$isImportCommand) {
+ // This map-array value is definitely NOT okay.
+ throw new BadPropertyMapException(sprintf(
+ 'Invalid JSON property map entry "%s" in class "%s".',
+ $propName, $this->_currentClassInfo['strictClassName']
+ ));
+ }
+ }
+ }
+
+ /**
+ * Compile a property definition string and add it to the current class.
+ *
+ * Attempts to compile the definition, and then appends it to the current
+ * class' final compiled map, IF the definition is valid and describes a
+ * new/different value compared to what's already in the current class map.
+ *
+ * @param string $propName The property name.
+ * @param string $propDefStr A string describing the property.
+ *
+ * @throws BadPropertyDefinitionException
+ */
+ private function _processPropertyDefinitionString(
+ $propName,
+ $propDefStr)
+ {
+ try {
+ // Validates the definition and throws if bad.
+ // NOTE: The namespace argument here is INCREDIBLY important. It's
+ // what allows each class to refer to classes relative to its own
+ // namespace, rather than needing to type the full, global class
+ // path. Without that parameter, the PropertyDefinition would only
+ // search in the global namespace.
+ // NOTE: "use" statements are ignored since PHP has no mechanism
+ // for inspecting those. But the user will quickly notice such a
+ // problem, since such classnames will warn as "not found" here.
+ $propDefObj = new PropertyDefinition( // Throws.
+ $propDefStr,
+ $this->_currentClassInfo['namespace']
+ );
+
+ // MEMORY OPTIMIZATION TRICK: If we wanted to be "naive", we could
+ // simply assign this new PropertyDefinition directly to our current
+ // class property map, regardless of whether the property-name key
+ // already exists. It would certainly give us the "right" result...
+ //
+ // But imagine if one of our parent objects or previous imports have
+ // already created a property as "foo":"string[]", and imagine that
+ // we ALSO define a "foo":"string[]", even though we've already
+ // inherited that EXACT compiled instruction. What would happen?
+ //
+ // Well, if we instantly assign our own, newly created object, even
+ // though it's equal to an identically named and identically defined
+ // property that we've already inherited/imported, then we waste RAM
+ // since we've now got two INDEPENDENT "compiled property" object
+ // instances that describe the EXACT same property-settings.
+ //
+ // Therefore, we can save RAM by first checking if the property name
+ // already exists in our currently compiled map; and if so, whether
+ // its pre-existing PropertyDefinition already describes the EXACT
+ // same settings. If so, we can keep the existing object (which we
+ // KNOW is owned by a parent/import and will therefore always remain
+ // in memory). Thus saving us the RAM-size of a PropertyDefinition
+ // object. In fact, the re-use means the RAM is the same as if the
+ // sub-class hadn't even overwritten the inherited/imported property
+ // AT ALL! So this protective algorithm makes class JSON property
+ // re-definitions to identical settings a ZERO-cost act.
+ //
+ // IMPORTANT: This only works because compiled properties are
+ // immutable, meaning we can trust that the borrowed object will
+ // remain the same. We are NEVER going to allow any runtime
+ // modifications of the compiled maps. So re-use is totally ok.
+ if (!isset($this->_currentClassPropertyMap[$propName])
+ || !$propDefObj->equals($this->_currentClassPropertyMap[$propName])) {
+ // Add the unique (new/different) property to our class map.
+ $this->_currentClassPropertyMap[$propName] = $propDefObj;
+
+ // Alright, we've encountered a brand new property, and we know
+ // it's pointing at a LazyJsonMapper if it's an object. But we
+ // DON'T know if the TARGET class map can actually COMPILE. We
+ // therefore need to add the class to the list of encountered
+ // property classes. But only if we haven't already compiled it.
+ if ($propDefObj->isObjectType
+ && !isset($this->_propertyMapCache->classMaps[$propDefObj->propType])) {
+ // NOTE: During early compilations, at the startup of a PHP
+ // runtime, this will pretty much always add classes it sees
+ // since NOTHING is compiled while the first class is being
+ // compiled. Which means that this list will contain classes
+ // that are currently BEING SOLVED as the class hierarchy
+ // keeps resolving/compiling itself. But we'll take care of
+ // that by double-checking the list later before we're done.
+ // TODO: PERHAPS we can safely optimize this HERE by also
+ // checking for the target class in _propertyMapCache's list
+ // of locked classes... But planning would be necessary.
+ $this->uncompiledPropertyClasses[$propDefObj->propType] = true;
+ }
+ }
+ } catch (BadPropertyDefinitionException $e) {
+ // Add details and throw from here instead.
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Bad property definition for "%s" in class "%s" (Error: "%s").',
+ $propName, $this->_currentClassInfo['strictClassName'], $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Import another class map into the current class.
+ *
+ * @param string $strictImportClassName The strict, global path to the
+ * class, with leading backslash `\`.
+ * To save time, we assume the caller
+ * has already verified that it is a
+ * valid `LazyJsonMapper` class.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _importClassMap(
+ $strictImportClassName)
+ {
+ // Begin via reflection to resolve the EXACT name of the class they want
+ // to import, so that we can trust its name completely.
+ try {
+ // NOTE: The strict, global "\"-name is necessary here, to guarantee
+ // that we find the correct target class via the global namespace.
+ $reflector = new ReflectionClass($strictImportClassName);
+ $importClassName = $reflector->getName(); // The clean, real name.
+ $strictImportClassName = Utilities::createStrictClassPath($importClassName);
+ } catch (ReflectionException $e) {
+ // This should never be able to fail (since our caller already
+ // verified that the class exists), but treat failure as a bad map.
+ throw new BadPropertyMapException(sprintf(
+ 'Reflection failed for class "%s", when trying to import it into class "%s". Reason: "%s".',
+ $strictImportClassName, $this->_currentClassInfo['strictClassName'], $e->getMessage()
+ ));
+ }
+
+ // FATAL: If the encountered import-statement literally refers to itself
+ // (the class we're currently compiling in the hierarchy), then it's a
+ // totally ridiculous circular self-reference of the most insane kind.
+ if ($importClassName === $this->_currentClassInfo['className']) {
+ throw new CircularPropertyMapException(
+ $strictImportClassName,
+ $strictImportClassName
+ );
+ }
+
+ // FATAL: If the import-statement refers to ANY class in OUR OWN
+ // current class' hierarchy, then it's a circular self-reference. We
+ // forbid those because they make ZERO sense. For example: "ABCD", in
+ // that case, "D" COULD definitely import "B" because "D" is later in
+ // the chain and "B" is therefore already resolved. But WHY? We already
+ // inherit from it! And we could NEVER do the reverse; "B" could never
+ // import "D", because "D" depends on "B" being fully compiled already.
+ // Likewise, we can't allow stupid things like "B imports B". Doesn't
+ // make sense. Therefore, we disallow ALL self-imports.
+ //
+ // NOTE: In the example of "B" importing "D", that's NOT caught here
+ // since the classname "D" wouldn't be part of B's hierarchy. But it
+ // will be caught during our upcoming attempt to sub-compile "D" to
+ // parse ITS hierarchy, since "D" will begin to compile and check its
+ // own class hierarchy, and will arrive at the "B" class and will then
+ // detect that "B" is locked as "already being resolved", since the
+ // ongoing resolving of "B" was what triggered the compilation of "D".
+ if (isset($this->_classHierarchy[$importClassName])) {
+ // NOTE: strictSolveClassName goes in "arg1" because we're the ones
+ // trying to import an invalid target class that's already part of
+ // our own hierarchy.
+ throw new CircularPropertyMapException(
+ $this->_strictSolveClassName,
+ $strictImportClassName
+ );
+ }
+
+ // FATAL: Also prevent users from importing any class that's in the list
+ // of "locked and currently being resolved", since that means that the
+ // class they're trying to import is ITSELF part of a compilation-chain
+ // that's CURRENTLY trying to resolve itself right now.
+ //
+ // NOTE: This catches scenarios where class-chains have common ancestors
+ // but then diverge and go into a circular reference to each other. For
+ // example, "A extends LazyJsonMapper, imports B", "B extends
+ // LazyJsonMapper, imports A". Imagine that you construct "A", which
+ // locks "A" during its compilation process. The parent "LazyJsonMapper"
+ // class is successfully compiled since it has no dependencies. Then "A"
+ // sees "import B" and sub-compiles B. (Remember that "A" remains locked
+ // when that happens, since "A" is waiting for "B" to resolve itself.)
+ // The compilation code of "B" begins running. It first checks its
+ // inheritance hierarchy and sees nothing locked (LazyJsonMapper is
+ // unlocked because it's already done, and B is unlocked because B has
+ // not locked itself yet). Next, "B" sees an "import A" statement. It
+ // succeeds the "block import of A if it's part of our own hierarchy"
+ // check above, since B is NOT "extended from A".
+ //
+ // So then we're at a crossroads... there IS a circular reference, but
+ // we don't know it yet. We COULD let it proceed to "resolve A by
+ // sub-compiling the map for A", which would give us a call-stack of
+ // "compile A -> compile B -> compile A", but then the 2nd "A" compiler
+ // would run, and would do its early lock-check and notice that itself
+ // (A) is locked, since the initial compilation of "A" is not yet done.
+ // It would then throw a non-sensical error saying that there's a
+ // circular reference between "A and A", which is technically true, but
+ // makes no sense. That's because we waited too long!
+ //
+ // So, what the code BELOW does instead, is that when "B" is trying to
+ // resolve itself (during the "compile A -> import B -> compile B"
+ // phase), the "B" compiler will come across its "import A" statement,
+ // and will now detect that "A" is already locked, and therefore throws
+ // a proper exception now instead of letting "A" attempt to compile
+ // itself a second time as described above. The result of this early
+ // sanity-check is a better exception message (correct class names).
+ foreach ($this->_propertyMapCache->compilerLocks as $lockedClassName => $isLocked) {
+ if ($isLocked && $lockedClassName === $importClassName) {
+ // NOTE: strictSolveClassName goes in "arg2" because we're
+ // trying to import something unresolved that's therefore higher
+ // than us in the call-stack.
+ throw new CircularPropertyMapException(
+ $strictImportClassName,
+ $this->_strictSolveClassName
+ );
+ }
+ }
+
+ // Now check if the target class is missing from our cache, and if so
+ // then we must attempt to compile (and cache) the map of the target
+ // class that we have been told to import.
+ //
+ // NOTE: This sub-compilation will perform the necessary recursive
+ // compilation and validation of the import's WHOLE map hierarchy. When
+ // the compiler runs, it will ensure that nothing in the target class
+ // hierarchy is currently locked (that would be a circular reference),
+ // and then it will lock its own hierarchy too until it has resolved
+ // itself. This will go on recursively if necessary, until ALL parents
+ // and ALL further imports have been resolved. And if there are ANY
+ // circular reference ANYWHERE, or ANY other problems with its
+ // map/definitions, then the sub-compilation(s) will throw appropriate
+ // exceptions.
+ //
+ // We won't catch them; we'll just let them bubble up to abort the
+ // entire chain of compilation, since a single error anywhere in the
+ // whole chain means that whatever root-level compile-call initially
+ // began this compilation process _cannot_ be resolved either. So we'll
+ // let the error bubble up all the way to the original call.
+ if (!isset($this->_propertyMapCache->classMaps[$importClassName])) {
+ // Compile the target class to import.
+ // NOTE: We will not catch the error. So if the import fails to
+ // compile, it'll simply output its own message saying that
+ // something was wrong in THAT class. We don't bother including any
+ // info about our class (the one which imported it). That's still
+ // very clear in this case, since the top-level class compiler and
+ // its hierarchy will be in the stack trace as well as always having
+ // a clearly visible import-command in their class property map.
+ $this->_subcompile($importClassName); // Throws.
+ }
+
+ // Now simply loop through the compiled property map of the imported
+ // class... and add every property to our own compiled property map. In
+ // case of clashes, we use the imported one.
+ // NOTE: We'll directly assign its PD objects as-is, which will assign
+ // the objects by shallow "shared instance", so that we re-use the
+ // PropertyDefinition object instances from the compiled class that
+ // we're importing. That will save memory.
+ // NOTE: There's no point doing an equals() clash-check here, since
+ // we're importing an EXISTING, necessary definition from another class,
+ // so we won't save any memory by avoiding their version even if the
+ // definitions are equal.
+ foreach ($this->_propertyMapCache->classMaps[$importClassName] as $importedPropName => $importedPropDefObj) {
+ $this->_currentClassPropertyMap[$importedPropName] = $importedPropDefObj;
+ }
+ }
+
+ /**
+ * Used internally when this compiler needs to run a sub-compilation.
+ *
+ * Performs the sub-compile. And if it was successful (meaning it had no
+ * auto-rollbacks due to ITS `compile()` call failing), then we'll merge its
+ * compiler state with our own so that we preserve important state details.
+ *
+ * @param string $className The full path of the class to sub-compile, but
+ * without any leading `\` global prefix. To save
+ * time, we assume the caller has already verified
+ * that it is a valid `LazyJsonMapper` class.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _subcompile(
+ $className)
+ {
+ // Sub-compile the target class. If this DOESN'T throw, we know that the
+ // target class successfully exists in the compilation cache afterwards.
+ // NOTE: If it throws, we know that IT has already rolled back all its
+ // changes itself via its own compile()-catch, so WE don't have to worry
+ // about reading its state on error. We ONLY care when it succeeds.
+ $subCompiler = new self( // Throws.
+ false, // Is NOT the root call!
+ $this->_propertyMapCache, // Use the same cache to share locks.
+ $className
+ );
+ $subCompiler->compile(); // Throws.
+
+ // Add its state of successfully compiled classes to our own list of
+ // compiled classes, so that the info is preserved throughout the stack.
+ // NOTE: This is EXTREMELY important, so that we can clear those classes
+ // from the cache in case ANY other step of the compilation chain fails
+ // unexpectedly. Because in that case, we'll NEED to be able to roll
+ // back ALL of our entire compile-chain's property map cache changes!
+ // NOTE: The merge function de-duplicates keys!
+ $this->compiledClasses = array_merge(
+ $this->compiledClasses,
+ $subCompiler->compiledClasses
+ );
+
+ // Add the sub-compiled class hierarchy's own encountered property-
+ // classes to our list of encountered classes. It gives us everything
+ // from their extends-hierarchy and everything from their own imports.
+ // And in case they had multi-level chained imports (classes that import
+ // classes that import classes), it'll actually be containing a FULLY
+ // recursively resolved and merged sub-import list. We will get them ALL
+ // from their WHOLE sub-hierarchy! Exactly as intended.
+ // NOTE: The merge function de-duplicates keys!
+ $this->uncompiledPropertyClasses = array_merge(
+ $this->uncompiledPropertyClasses,
+ $subCompiler->uncompiledPropertyClasses
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/UndefinedProperty.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/UndefinedProperty.php
new file mode 100755
index 0000000..5ff159c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/UndefinedProperty.php
@@ -0,0 +1,79 @@
+arrayDepth`.
+ * @param string $propName The name of the property. For
+ * exception messages.
+ * @param PropertyDefinition $propDef An object describing the property.
+ *
+ * @throws LazyJsonMapperException If the value can't be turned into its
+ * assigned class or built-in PHP type.
+ */
+ public static function convert(
+ $direction,
+ &$value,
+ $remArrayDepth,
+ $propName,
+ PropertyDefinition $propDef)
+ {
+ // Do nothing if this particular value is NULL.
+ if ($value === null) {
+ return; // Skip the rest of the code.
+ }
+
+ // ------------
+ // TODO: Improve all error messages below to make them more
+ // human-readable. Perhaps even display $propDef->asString() as a great
+ // hint about what type the property requires, even indicating what array
+ // depth it must be at. However, the current messages about problems at
+ // "at array-depth X of Y" should be kept, since that level of detail is
+ // very helpful for figuring out which part of the array is bad.
+ // ------------
+
+ // Handle "arrays of [type]" by recursively processing all layers down
+ // until the array-depth, and verifying all keys and values.
+ // NOTE: If array depth remains, we ONLY allow arrays (or NULLs above),
+ // and ALL of the arrays until the depth MUST be numerically indexed in
+ // a sequential order starting from element 0, without any gaps. Because
+ // the ONLY way that a value can be a true JSON ARRAY is if the keys are
+ // numeric and sequential. Otherwise the "array" we are looking at was
+ // originally a JSON OBJECT in the original, pre-json_decode()-d string.
+ // NOTE: We even support "arrays of mixed", and in that case will verify
+ // that the mixed data is at the expected depth and has key integrity.
+ // So specifying "mixed[]" requires data like "[1,null,true]", whereas
+ // specifying "mixed" avoids doing any depth validation.
+ if ($remArrayDepth > 0) {
+ if (!is_array($value)) {
+ if ($direction === self::CONVERT_FROM_INTERNAL) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected non-array value for array-property "%s" at array-depth %d of %d.',
+ $propName, $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ } else {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to assign new non-array value for array-property "%s" at array-depth %d of %d.',
+ $propName, $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ }
+ }
+
+ // Subtract 1 from the remaining array-depth and process current layer.
+ $newRemArrayDepth = $remArrayDepth - 1;
+ $nextValidKey = 0; // Must start at 0.
+ foreach ($value as $k => &$v) { // IMPORTANT: By reference!
+ // OPTIMIZATION: We MUST only allow sequential int keys, but we
+ // avoid the is_int() call by using an int counter instead:
+ if ($k !== $nextValidKey++) { // ++ post increment...
+ // We're in an "array of"-typed JSON property structure, but
+ // encountered either an associative key or a gap in the
+ // normal numeric key sequence. The JSON data is invalid!
+ // Display the appropriate error.
+ if (is_int($k)) {
+ // It is numeric, so there was a gap...
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected out-of-sequence numeric array key (expected: %s, found: %s) for array-property "%s" at array-depth %d of %d.',
+ $nextValidKey - 1, $k, $propName,
+ $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ } else {
+ // Invalid associative key in a numeric array.
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected non-numeric array key ("%s") for array-property "%s" at array-depth %d of %d.',
+ $k, $propName, $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ }
+ }
+
+ // The key was valid, so convert() any sub-values within this
+ // value. Its next depth is either 0 (values) or 1+ (more arrays).
+ if ($v !== null) { // OPTIMIZATION: Avoid useless call if null.
+ if ($direction === self::CONVERT_FROM_INTERNAL) {
+ self::convert($direction, $v, $newRemArrayDepth, $propName, $propDef);
+ } else {
+ self::convert($direction, $v, $newRemArrayDepth, $propName, $propDef);
+ }
+ }
+ }
+
+ // Skip rest of the code, since array depth remained in this call.
+ return;
+ } // End of "remaining array depth" handler.
+
+ // Alright, we now know that we're at the "data depth". However, the
+ // property itself may be untyped ("mixed"). Handle that first.
+ if ($propDef->propType === null) {
+ // This is a non-NULL but "untyped" property, which means that we
+ // only accept three things: NULL, Scalars (int, float, string,
+ // bool) and arrays of those. We will NOT allow any Objects or
+ // external Resources. And arrays at this level will only be
+ // allowed if the "mixed" type didn't specify any depth.
+
+ // We've already checked NULL earlier. Check the most common data
+ // types now. Almost all untyped values will be one of these,
+ // meaning even untyped properties are blazingly fast to process.
+ if (is_scalar($value)) { // int, float, string, bool
+ return; // Scalar accepted. Skip the rest of the code.
+ }
+
+ // Alright... then it's either an array, Object or Resource.
+ try {
+ if (is_array($value)) {
+ // Forbid arrays at this "value-level" IF and only if this
+ // was a "mixed[]" notation untyped property, since the "[]"
+ // specifies the maximum mixed data depth in that case.
+ // ------------------------------------------
+ // TODO: We do not have any PropertyDefinition notation
+ // for specifying "mixed non-array property". Perhaps add
+ // that feature someday, maybe "mixed[-]", since "mixed[]"
+ // is already taken by "1-level deep mixed" and "mixed" is
+ // already taken by "do not check depth of this mixed data".
+ // It would be nice to be able to say that a mixed property
+ // "can contain any basic type but not arrays".
+ // A simple implementation would be that arrayDepth "-1"
+ // for mixed denotes "do not check array depth" ("mixed")
+ // and "0" denotes "check array depth" ("mixed[-]").
+ // Either way, the syntax also needs to be valid PHPdoc so
+ // that the automatic property signatures are valid, so we
+ // actually cannot use "mixed[-]". Perhaps I'm overthinking
+ // all of this, though. Because if the user cares about the
+ // depth of untyped (mixed) properties, they should know
+ // enough to just strongly type-assign something instead.
+ // Although, perhaps this IS a job for PropertyDefinition:
+ // If the user specifies "mixed[-]" we can store it as
+ // untyped with arrayDepth -1, but output it as "mixed" when
+ // converting that PropertyDefinition to signature hints.
+ // ------------------------------------------
+ if ($propDef->arrayDepth > 0) {
+ // This "mixed" type specifies a max-depth, which means
+ // that we've reached it. We cannot allow more arrays.
+ throw new LazyJsonMapperException(sprintf(
+ // Let's try to be REALLY clear so the user understands...
+ // Since I anticipate lots of untyped user properties.
+ '%s non-array inner untyped property of "%s". This untyped property specifies a maximum array depth of %d.',
+ ($direction === self::CONVERT_FROM_INTERNAL
+ ? 'Unexpected inner array value in'
+ : 'Unable to assign new inner array value for'),
+ $propName, $propDef->arrayDepth
+ ));
+ }
+
+ // This mixed property has no max depth. Just verify the
+ // contents recursively to ensure it has no invalid data.
+ array_walk_recursive($value, function ($v) {
+ // NOTE: Mixed properties without max-depth can be
+ // either JSON objects or JSON arrays, and we don't know
+ // which, so we cannot verify their array key-type. If
+ // people want validation of keys, they should set a max
+ // depth for their mixed property OR switch to typed.
+ if ($v !== null && !is_scalar($v)) {
+ // Found bad (non-NULL, non-scalar) inner value.
+ throw new LazyJsonMapperException('bad_inner_type');
+ }
+ });
+ } else {
+ // Their value is an Object or Resource.
+ throw new LazyJsonMapperException('bad_inner_type');
+ }
+ } catch (LazyJsonMapperException $e) {
+ // Automatically select appropriate exception message.
+ if ($e->getMessage() !== 'bad_inner_type') {
+ throw $e; // Re-throw since it already had a message.
+ }
+
+ throw new LazyJsonMapperException(sprintf(
+ // Let's try to be REALLY clear so the user understands...
+ // Since I anticipate lots of untyped user properties.
+ '%s untyped property "%s". Untyped properties can only contain NULL or scalar values (int, float, string, bool), or arrays holding any mixture of those types.',
+ ($direction === self::CONVERT_FROM_INTERNAL
+ ? 'Unexpected value in'
+ : 'Unable to assign invalid new value for'),
+ $propName
+ ));
+ }
+
+ // If we've come this far, their untyped property contained a valid
+ // array with only NULL/scalars (or nothing at all) inside. Done!
+ return; // Skip the rest of the code.
+ }
+
+ // We need the strict classpath for all object comparisons, to ensure
+ // that we always compare against the right class via global namespace.
+ $strictClassPath = $propDef->isObjectType ? $propDef->getStrictClassPath() : null;
+
+ // Alright... we know that we're at the "data depth" and that $value
+ // refers to a single non-NULL, strongly typed value...
+ if ($direction === self::CONVERT_TO_INTERNAL) {
+ // No incoming value is allowed to be array anymore at this depth.
+ if (is_array($value)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to assign new inner array value for non-array inner property of "%s", which must be of type "%s".',
+ $propName, $propDef->isObjectType ? $strictClassPath : $propDef->propType
+ ));
+ }
+
+ // Now convert the provided individual value, as necessary...
+ if (!$propDef->isObjectType) {
+ // Cast the value to the target built-in PHP type. We cannot cast objects.
+ // NOTE: For performance, we don't check is_resource(), because
+ // those should never be able to appear in somebody's data. And
+ // they can actually be cast to any basic PHP type without any
+ // problems at all. It's only objects that are a problem and
+ // cannot have any "settype()" applied to them by PHP.
+ // Furthermore, is_resource() isn't even reliable anyway, since
+ // it returns false if it is a closed resource. So whatever,
+ // just rely on the fact that PHP can convert it to basic types.
+ if (is_object($value) || !@settype($value, $propDef->propType)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to cast new inner value for property "%s" to built-in PHP type "%s".',
+ $propName, $propDef->propType
+ ));
+ }
+ } else {
+ // Check that the new value is an object and that it's an instance
+ // of the exact required class (or at least a subclass of it).
+ // NOTE: Since all PropertyDefinition types are validated to derive
+ // from LazyJsonMapper, we don't need to check "instanceof".
+ if (!is_object($value) || !is_a($value, $strictClassPath)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'The new inner value for property "%s" must be an instance of class "%s".',
+ $propName, $strictClassPath
+ ));
+ }
+ }
+ } else { // CONVERT_FROM_INTERNAL
+ // Now convert the individual internal value, as necessary...
+ // NOTE: We validate and convert all values EVERY time, to protect
+ // against things like being constructed with bad non-JSON input-arrays
+ // with bad objects within it, and (even more importantly) to avoid
+ // problems whenever the user modifies internal data by reference via
+ // _getProperty() or __get() (particularly the latter; direct property
+ // access makes it INCREDIBLY easy to directly modify internal data,
+ // especially if they are arrays and the user does $x->items[] = 'foo').
+ if (!$propDef->isObjectType) {
+ // Basic PHP types are not allowed to have an array as their value.
+ // NOTE: If arr, then the PropertyDefinition doesn't match the JSON!
+ if (is_array($value)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected inner array value in non-array inner property for "%s", where we expect value type "%s".',
+ $propName, $propDef->propType
+ ));
+ }
+
+ // Cast the value to the target built-in PHP type. We cannot cast objects.
+ // NOTE: If resources appear in the data, they'll be castable by settype().
+ if (is_object($value) || !@settype($value, $propDef->propType)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to cast inner value for property "%s" to built-in PHP type "%s".',
+ $propName, $propDef->propType
+ ));
+ }
+ } else {
+ // Only convert the value to object if it isn't already an object.
+ if (!is_object($value)) {
+ // Unconverted JSON objects MUST have an array as their inner
+ // value, which contains their object data property list.
+ if (!is_array($value)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert non-array inner value for property "%s" into class "%s".',
+ $propName, $strictClassPath
+ ));
+ }
+
+ // The encountered array MUST have string-keys, otherwise it
+ // CANNOT be a JSON object. If the array has numerical keys
+ // instead, it means that our array-depth is wrong and that
+ // we're still looking at normal JSON ["foo"] arrays, instead
+ // of associative key-value object {"foo":"bar"} property pairs.
+ // NOTE: We only need to check the first key of the array,
+ // because JSON data CANNOT mix associative and numerical keys.
+ // In fact, if you try something like '{"a":{"a","foo":"bar"}}'
+ // then PHP actually refuses to decode such invalid JSON.
+ // NOTE: If first key is NULL, it means the array is empty. We
+ // must allow empty arrays, since '{"obj":{}}' is valid JSON.
+ // NOTE: We'll only detect non-object inner arrays if their
+ // first key is a numeric int(0). Because objects allow
+ // random numeric keys, but they don't allow sequential ones.
+ reset($value); // Rewind array pointer to its first element.
+ $firstArrKey = key($value); // Get key without moving pointer.
+ if ($firstArrKey !== null && is_int($firstArrKey) && $firstArrKey === 0) {
+ // Determine whether this is a regular array... If it
+ // consists entirely of numeric keys starting at 0 and
+ // going up sequentially without gaps, it's an array...
+ $isRegularArray = true;
+ $nextValidKey = 0; // Must start at 0.
+ foreach ($value as $k => $x) {
+ if ($k !== $nextValidKey++) { // ++ post increment.
+ $isRegularArray = false;
+ break;
+ }
+ }
+
+ // Only throw if it was a totally plain array. This
+ // check ensures that we still allow objects with
+ // numeric keys as long as they aren't 100%
+ // indistinguishable from a regular array.
+ if ($isRegularArray) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert non-object-array inner value for property "%s" into class "%s".',
+ $propName, $strictClassPath
+ ));
+ }
+ }
+
+ // Convert the raw JSON array value to its assigned class object.
+ try {
+ // Attempt creation and catch any construction issues.
+ // NOTE: This won't modify $value if construction fails.
+ $value = new $strictClassPath($value); // Constructs the classname in var.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ throw new LazyJsonMapperException(sprintf(
+ 'Failed to create an instance of class "%s" for property "%s": %s',
+ // NOTE: No need to format the exception message
+ // since it already has perfect grammar.
+ $strictClassPath, $propName, $e->getMessage()
+ ));
+ }
+ }
+
+ // Validate that the class matches the defined property class type.
+ // NOTE: Since all PropertyDefinition types are validated to derive
+ // from LazyJsonMapper, we don't need to verify "instanceof".
+ if (!is_a($value, $strictClassPath)) { // Exact same class or a subclass of it.
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected "%s" object in property "%s", but we expected an instance of "%s".',
+ Utilities::createStrictClassPath(get_class($value)),
+ $propName, $strictClassPath
+ ));
+ }
+ } // End of "Get object from internal".
+ } // End of CONVERT_FROM_INTERNAL.
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Utilities.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Utilities.php
new file mode 100755
index 0000000..1a31708
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/src/Utilities.php
@@ -0,0 +1,180 @@
+ 0) {
+ // Prepend "\" if missing, to force PHP to use the global namespace.
+ if ($className[0] !== '\\') {
+ $className = '\\'.$className;
+ }
+
+ return $className;
+ }
+
+ return null;
+ }
+
+ /**
+ * Splits a strict class-path into its namespace and class name components.
+ *
+ * To rejoin them later, just use: `'namespace' + '\\' + 'class'`.
+ *
+ * The class path should be in `get_class()` aka `TheClass::class` format.
+ *
+ * @param string $strictClassPath Class output of `createStrictClassPath()`.
+ *
+ * @return array Associative array with keys for `namespace` and `class`.
+ *
+ * @see Utilities::createStrictClassPath()
+ */
+ public static function splitStrictClassPath(
+ $strictClassPath = '')
+ {
+ // Split on the rightmost backslash. In a strict path there's always at
+ // least one backslash, for the leading "global namespace" backslash.
+ $lastDelimPos = strrpos($strictClassPath, '\\');
+ // Global: "". Other: "\Foo" or "\Foo\Bar" (if nested namespaces).
+ $namespace = substr($strictClassPath, 0, $lastDelimPos);
+ // Always: "TheClass".
+ $class = substr($strictClassPath, $lastDelimPos + 1);
+
+ return [
+ 'namespace' => $namespace,
+ 'class' => $class,
+ ];
+ }
+
+ /**
+ * Compare two class paths and generate the shortest path between them.
+ *
+ * @param array $sourceComponents Source class as `splitStrictClassPath()`.
+ * @param array $targetComponents Target class as `splitStrictClassPath()`.
+ *
+ * @return string The final path to reach from the source to the target.
+ *
+ * @see Utilities::splitStrictClassPath()
+ */
+ public static function createRelativeClassPath(
+ array $sourceComponents,
+ array $targetComponents)
+ {
+ $sourceNs = &$sourceComponents['namespace'];
+ $targetNs = &$targetComponents['namespace'];
+ $finalType = null;
+
+ // If either the source or the target lives in the global namespace,
+ // we won't do this processing. (Those need a strict, global path.)
+ if ($sourceNs !== '' && $targetNs !== '') {
+ // Check if the source-class namespace is at pos 0 of target space.
+ $pos = strpos($targetNs, $sourceNs);
+ if ($pos === 0) {
+ // Look at the character after the source-class namespace in the
+ // target namespace. Check for "" (str end) or "\\" (subspace).
+ $sourceNsLen = strlen($sourceNs);
+ $chr = substr($targetNs, $sourceNsLen, 1);
+ if ($chr === '') { // Exact same space, without any subspace.
+ $finalType = $targetComponents['class'];
+ } elseif ($chr === '\\') { // Same space, followed by subspace.
+ $finalType = sprintf(
+ '%s\\%s',
+ substr($targetNs, $sourceNsLen + 1),
+ $targetComponents['class']
+ );
+ } // Else: Was false positive, not in same namespace.
+ }
+ }
+
+ // In case of totally different spaces, or if any of the classes are in
+ // the global namespace, then just use the strict, global target path.
+ if ($finalType === null) {
+ $finalType = sprintf(
+ '%s\\%s',
+ $targetNs,
+ $targetComponents['class']
+ );
+ }
+
+ return $finalType;
+ }
+
+ /**
+ * Atomic filewriter.
+ *
+ * Safely writes new contents to a file using an atomic two-step process.
+ * If the script is killed before the write is complete, only the temporary
+ * trash file will be corrupted.
+ *
+ * The algorithm also ensures that 100% of the bytes were written to disk.
+ *
+ * @param string $filename Filename to write the data to.
+ * @param string $data Data to write to file.
+ * @param string $atomicSuffix Lets you optionally provide a different
+ * suffix for the temporary file.
+ *
+ * @return int|bool Number of bytes written on success, otherwise `FALSE`.
+ */
+ public static function atomicWrite(
+ $filename,
+ $data,
+ $atomicSuffix = 'atomictmp')
+ {
+ // Perform an exclusive (locked) overwrite to a temporary file.
+ $filenameTmp = sprintf('%s.%s', $filename, $atomicSuffix);
+ $writeResult = @file_put_contents($filenameTmp, $data, LOCK_EX);
+
+ // Only proceed if we wrote 100% of the data bytes to disk.
+ if ($writeResult !== false && $writeResult === strlen($data)) {
+ // Now move the file to its real destination (replaces if exists).
+ $moveResult = @rename($filenameTmp, $filename);
+ if ($moveResult === true) {
+ // Successful write and move. Return number of bytes written.
+ return $writeResult;
+ }
+ }
+
+ // We've failed. Remove the temporary file if it exists.
+ if (is_file($filenameTmp)) {
+ @unlink($filenameTmp);
+ }
+
+ return false; // Failed.
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/tests/bootstrap.php b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/tests/bootstrap.php
new file mode 100755
index 0000000..8676102
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/lazyjsonmapper/lazyjsonmapper/tests/bootstrap.php
@@ -0,0 +1,18 @@
+login('yourusername', 'yourpassword');
+ $result = $ig->media->comment('14123451234567890_1234567890', 'Hello World');
+ var_dump($result);
+} catch (\Exception $e) {
+ echo $e->getMessage()."\n";
+}
+```
+
+Error Log/var_dump:
+
+```php
+// Please provide your error log/dump here, for example:
+
+RESPONSE: {"status": "fail", "message": "Sorry, the comment data may have been corrupted."}
+
+InstagramAPI\Response\CommentResponse: Sorry, the comment data may have been corrupted.
+```
+
+---
+
+### For a new endpoint *feature request*, you should include the *capture of the request and response*.
+
+Request:
+
+```http
+# Please provide your capture below, for example:
+
+GET /api/v1/si/fetch_headers/?guid=123456abcdeff19cc2f123456&challenge_type=signup HTTP/1.1
+Host: i.instagram.com
+Connection: keep-alive
+X-IG-Connection-Type: mobile(UMTS)
+X-IG-Capabilities: 3ToAAA==
+Accept-Language: en-US
+Cookie: csrftoken=g79dofABCDEFGII3LI7YdHei1234567; mid=WFI52QABAAGrbKL-ABCDEFGHIJK
+User-Agent: Instagram 10.3.0 Android (18/4.3; 320dpi; 720x1280; Xiaomi; HM 1SW; armani; qcom; en_US)
+Accept-Encoding: gzip, deflate, sdch
+```
+
+Response:
+
+```http
+# Please provide your capture below, for example:
+
+HTTP/1.1 200 OK
+Content-Language: en
+Expires: Sat, 01 Jan 2000 00:00:00 GMT
+Vary: Cookie, Accept-Language
+Pragma: no-cache
+Cache-Control: private, no-cache, no-store, must-revalidate
+Date: Thu, 15 Dec 2016 08:50:19 GMT
+Content-Type: application/json
+Set-Cookie: csrftoken=g79dofABCDEFGII3LI7YdHei1234567; expires=Thu, 14-Dec-2017 08:50:19 GMT; Max-Age=31449600; Path=/; secure
+Connection: keep-alive
+Content-Length: 16
+
+{"status": "ok"}
+```
+---
+
+### Describe your issue
+
+Explanation of your issue goes here.
+
+Please make sure the description is worded well enough to be understood, and with as much context and examples as possible.
+
+We reserve the right to close your ticket without answer if you can't bother spending a few minutes to write a helpful report for us.
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/bug-report.md b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100755
index 0000000..6a3bf88
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,42 @@
+---
+name: Bug Report
+about: Report a bug or an unexpected behavior with the API
+labels: bug
+---
+# Bug Report
+
+---
+
+### Notes:
+We will close your issue, sometimes without answering, if any of the following are met:
+* You have not included your code or your debug log
+* You are asking about `challenge_required`, `checkpoint_required`, `feedback_required` or `sentry_block`. They've already been answered in the Wiki and *countless* closed tickets in the past!
+* You have used the wrong issue template
+* You are posting screenshots, which are too painful to help you with
+
+Please make sure you have done the following before submitting your issue:
+* You have [Searched](https://github.com/mgp25/Instagram-API/search?type=Issues) the bug-tracker for similar issues including **closed** ones
+* You have [Read the FAQ](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+* You have [Read the wiki](https://github.com/mgp25/Instagram-API/wiki)
+* You have [Reviewed the examples](https://github.com/mgp25/Instagram-API/tree/master/examples)
+* You have [Installed the api using ``composer``](https://github.com/mgp25/Instagram-API#installation)
+* You are [Using latest API release](https://github.com/mgp25/Instagram-API/releases)
+
+---
+
+### Description
+Please include a well-worded description of your issue below; We will close your issue if we have to guess what you're talking about, please be as specific as possible:
+
+__INSERT YOUR DESCRIPTION HERE__
+
+### Code
+Please post your code relevant to the bug in the section below:
+```php
+INSERT YOUR CODE HERE
+```
+
+### Debug Log
+Please post your debug log in the section below; You can enable the debug log by using `new Instagram(true)` instead of `new Instagram()`:
+```php
+INSERT YOUR DEBUG LOG HERE
+```
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/feature-request.md b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100755
index 0000000..07c3ecc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/feature-request.md
@@ -0,0 +1,26 @@
+---
+name: Feature Request
+about: Suggest a new feature/endpoint or an update to a function
+labels: request
+---
+# Feature Request
+
+---
+
+### Notes:
+We will close your feature request, sometimes without answering, if any of the following are met:
+* You requested a endpoint/function for `challenge_required`, `checkpoint_required`, `feedback_required` or `sentry_block` errors. These are decidedly unsupported, and will not be added
+* You requested a endpoint/function for an iOS feature. This API emulates the Android API, therefore we cannot, *safely*, add endpoints/functions which are not in the Android app.
+* You are using the wrong issue template
+
+Please make sure you have done the following before submitting your feature request:
+* You have [Searched](https://github.com/mgp25/Instagram-API/search?type=Issues) the bug-tracker for the same feature request
+* You have [Read the FAQ](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+* You have [Read the wiki](https://github.com/mgp25/Instagram-API/wiki)
+
+---
+
+### Description
+Please include a well-worded description of your feature request. Please make sure to include where on the app this feature is acceptable so we can reproduce it. Additionally, it would make our lives easier if you are able to post the endpoint, its associated parameters, and response.
+
+__INSERT DESCRIPTION HERE__
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/question.md b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/question.md
new file mode 100755
index 0000000..ecddf74
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,42 @@
+---
+name: Question
+about: Ask a question about the API
+labels: question
+---
+# Question
+
+---
+
+### Notes:
+We will close your question, sometimes without answering, if any of the following are met:
+* You have not included context about your question
+* You are asking about `challenge_required`, `checkpoint_required`, `feedback_required` or `sentry_block`. They've already been answered in the Wiki and *countless* closed tickets in the past!
+* You have used the wrong issue template
+* You are posting screenshots, which are too painful to help you with
+
+Please make sure you have done the following before submitting your question:
+* You have [Searched](https://github.com/mgp25/Instagram-API/search?type=Issues) the bug-tracker for similar questions including **closed** ones
+* You have [Read the FAQ](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+* You have [Read the wiki](https://github.com/mgp25/Instagram-API/wiki)
+* You have [Reviewed the examples](https://github.com/mgp25/Instagram-API/tree/master/examples)
+* You have [Installed the api using ``composer``](https://github.com/mgp25/Instagram-API#installation)
+* You are [Using latest API release](https://github.com/mgp25/Instagram-API/releases)
+
+---
+
+### Description
+Please include a well-worded description of your question below; We will close your issue if we have to guess what you're talking about, please be as specific as possible:
+
+__INSERT YOUR DESCRIPTION HERE__
+
+### Code (Optional)
+Please post your code relevant to the question in the section below:
+```php
+INSERT YOUR CODE HERE
+```
+
+### Debug Log (Optional)
+Please post your debug log in the section below; You can enable the debug log by using `new Instagram(true)` instead of `new Instagram()`:
+```php
+INSERT YOUR DEBUG LOG HERE
+```
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.gitignore b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.gitignore
new file mode 100755
index 0000000..39fc46d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.gitignore
@@ -0,0 +1,49 @@
+# miscellaneous
+sigs/sigs
+sigs/sigKeys
+
+# ignore the lockfile for this library
+composer.lock
+
+# runtime files
+*.dat
+*.jpg
+*.lock
+backups/
+sessions/
+
+# third party libraries
+vendor/
+.php_cs
+.php_cs.cache
+devtools/exif-orientation-examples/
+
+# temporary editor files
+*.swp
+.\#*
+*.sublime-project
+*.sublime-workspace
+.idea
+
+# operating system cache files
+.DS_Store
+Desktop.ini
+Thumbs.db
+
+# tags created by etags, ctags, gtags (GNU global), cscope and ac-php
+TAGS
+.TAGS
+!TAGS/
+tags
+.tags
+!tags/
+gtags.files
+GTAGS
+GRTAGS
+GPATH
+GSYMS
+cscope.files
+cscope.out
+cscope.in.out
+cscope.po.out
+ac-php-tags/
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.php_cs.dist b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.php_cs.dist
new file mode 100755
index 0000000..c01fac1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.php_cs.dist
@@ -0,0 +1,38 @@
+setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__)
+ )
+ ->setIndent(' ')
+ ->setLineEnding("\n")
+ ->setRules([
+ '@Symfony' => true,
+ // Override @Symfony rules
+ 'pre_increment' => false,
+ 'blank_line_before_statement' => ['statements' => ['return', 'try', 'throw']],
+ 'phpdoc_align' => ['tags' => ['param', 'throws']],
+ 'method_argument_space' => ['ensure_fully_multiline' => false],
+ 'binary_operator_spaces' => [
+ 'align_double_arrow' => true,
+ 'align_equals' => false,
+ ],
+ 'phpdoc_annotation_without_dot' => false,
+ 'yoda_style' => [
+ // Symfony writes their conditions backwards; we use normal order.
+ 'equal' => false,
+ 'identical' => false,
+ 'less_and_greater' => false,
+ ],
+ 'is_null' => [
+ // Replaces all is_null() with === null.
+ 'use_yoda_style' => false,
+ ],
+ // Custom rules
+ 'align_multiline_comment' => true,
+ 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
+ 'ordered_imports' => ['sort_algorithm' => 'alpha'],
+ 'phpdoc_order' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ]);
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.pre-commit.hook b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.pre-commit.hook
new file mode 100755
index 0000000..d091646
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/.pre-commit.hook
@@ -0,0 +1,58 @@
+#!/bin/sh
+#
+# Verifies that all files in the worktree follow our codestyle standards.
+#
+# Note that this script can't check that they're actually committing the nicely
+# formatted code. It just checks that the worktree is clean. So if they've fixed
+# all files but haven't added the codestyle fixes to their commit, they'll still
+# pass this check. But it's still a great protection against most mistakes.
+#
+# To install this hook, just run the following in the project's root folder:
+# ln -fs "../../.pre-commit.hook" .git/hooks/pre-commit
+#
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Git ensures that CWD is always the root of the project folder, so we can just
+# run all tests without verifying what folder we are in...
+
+failed="no"
+echo "[pre-commit] Checking work-tree codestyle..."
+
+# Check if we need updated LazyJsonMapper class documentation.
+echo "> Verifying class documentation..."
+./vendor/bin/lazydoctor -c composer.json -pfo --validate-only >/dev/null
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Check the general codestyle format.
+echo "> Verifying php-cs-fixer..."
+./vendor/bin/php-cs-fixer fix --config=.php_cs.dist --allow-risky yes --dry-run
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Look for specific problems with the style, related to our project.
+echo "> Verifying checkStyle..."
+/usr/bin/env php devtools/checkStyle.php x
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Refuse to commit if there were problems. Instruct the user about solving it.
+if [ "${failed}" = "yes" ]; then
+ # Yes there are lots of "echo" commands, because "\n" is not cross-platform.
+ echo "[commit failed] There are problems with your code..."
+ echo ""
+ echo "Run 'composer codestyle' to fix the code in your worktree."
+ echo ""
+ echo "But beware that the process is automatic, and that the result"
+ echo "isn't always perfect and won't be automatically staged."
+ echo ""
+ echo "So remember to manually read through the changes, then further"
+ echo "fix them if necessary, and finally stage the updated code"
+ echo "afterwards so that the fixed code gets committed."
+ exit 1
+fi
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/CODE_OF_CONDUCT.md b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/CODE_OF_CONDUCT.md
new file mode 100755
index 0000000..4754ab5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/CONTRIBUTING.md b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/CONTRIBUTING.md
new file mode 100755
index 0000000..4173a1d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/CONTRIBUTING.md
@@ -0,0 +1,488 @@
+# Contributing to Instagram API
+
+:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
+
+The following is a set of guidelines for contributing to the Instagram API, which is hosted in the [Instagram API repository](https://github.com/mgp25/Instagram-API) on GitHub.
+
+These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
+
+- [What should I know before I get started?](#what-should-i-know-before-i-get-started)
+ * [Code of Conduct](#code-of-conduct)
+
+- [Basic rules](#basic-rules)
+ * [Git Setup](#git-setup)
+ * [Codestyle Command](#codestyle-command)
+ * [Available Tasks](#available-tasks)
+ * [Commits](#commits)
+ * [Modifying anything in the existing code](#modifying-anything-in-the-existing-code)
+
+- [Styleguides](#styleguide)
+ * [Namespaces](#namespaces)
+ * [Functions and Variables](#functions-and-variables)
+ * [Function Documentation](#function-documentation)
+ * [Exceptions](#exceptions)
+
+- [Contributing-new-endpoints](#contributing-new-endpoints)
+
+
+## What should I know before I get started?
+
+### Code of Conduct
+
+This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
+By participating, you are expected to uphold this code.
+Please report any unacceptable behavior.
+
+## Basic rules
+
+Important! Your contributions must follow all of these rules for a consistent and bug-free project.
+
+This is a document which, among other things, describes PSR-4 class autoloading, clean commits, how to handle/document exceptions, how to structure function arguments, naming clear variables, always adding/updating PHPdoc blocks in all affected functions, and how to verify that your changes don't _break everything_ when performing big changes to existing functions or adding brand new functions and classes.
+
+
+### Git Setup
+
+If you are using a Linux/Mac/Unix system, you **MUST** install our git-hook in your local repository. It will help prevent you from accidentally committing badly formatted code. Please run the following command in the project's root folder (the folder which has files like `README.md` etc), to install the hook in your repository:
+
+```
+ln -fs "../../.pre-commit.hook" .git/hooks/pre-commit
+```
+
+
+### Codestyle Command
+
+Before committing any work, you **MUST** always run the codestyle fixer command in the root folder of the repository:
+
+```
+composer codestyle
+```
+
+That command will automatically fix all codestyle issues as well as generate various documentation, such as the class docs for all of the Responses and Model items.
+
+
+### Available Tasks
+
+Please visit our [Project Task Tracker](https://github.com/mgp25/Instagram-API/projects), where contributors can look at the list of available work. High-quality code contributions are greatly appreciated.
+
+
+### Commits
+
+- You **MUST** try to **separate** work into smaller commits. So if you `"changed Utils.php to fix getSeconds and changed Request.php to fix Request()"`, then make **TWO SEPARATE COMMITS**, so that we can easily find your changes in the project history, and can easily revert any individual broken changes without losing tons of other code too! So remember: 1 commit per task. :smile:
+
+- Use the detailed description field of your commit, to document why you did certain changes if it's not an obvious change (such as a typo fix). Your description gets added to the history of the project so that we can know what you were thinking, if we need to check the change again later.
+
+- **Name** your single-file commits `"AffectedClassName: Change description"` and keep the total length of the summary line at 50 characters or less in total (the Git/GitHub standard).
+
+- If your change NEEDS to affect multiple files, then it is SOMETIMES okay to leave out the classname part from the summary, but in that case you _must_ write a very clear and descriptive summary to explain it. _But_ usually you _should_ still include the classname even for multi-file edits. For example, renaming a function in Utils and having to change the name of it in other files is still a `"Utils: Renamed bleh() to bloop()"` commit.
+
+Examples of BAD commit summaries:
+
+```
+Edit something
+Fix this
+Utils+request changes
+```
+
+Examples of GOOD commit summaries:
+
+```
+Utils: Fix formatting of getSeconds
+Request: Send cropping information
+Changed all number_format() to round() everywhere
+Response: Parse timestamps as floats
+```
+
+
+### Modifying anything in the existing code
+
+- If you want to change a public API function's parameters (particularly in `src/Instagram.php`), think EXTREMELY hard about NOT doing it. And if you absolutely MUST do it (such as adding an important new parameter), then you MUST add it in a BACKWARDS-COMPATIBLE WAY, by adding it as the LAST parameter of the argument list AND providing a sensible DEFAULT VALUE for it so that people's CURRENT projects based on this library continue working and DON'T BREAK due to your function definition changes!
+
+- Do NOT look at your changes in isolation. Look at the BIG PICTURE of what your change now does to the REST of the codebase.
+
+- For example, if you change the returned variable type from a function (such as from an `"int"` to a `"string"`), then you MUST update the function's PHPdoc block to document the new return value (with `@return string`), and you MUST also slowly and carefully check ALL OTHER CODE that relied on the OLD behavior of that function. There's a command-line tool called "grep" (or an even better one especially made for programmers, called ["ag" aka "the silver searcher"](https://github.com/ggreer/the_silver_searcher)) to search for text in files. USE IT to check ALL other code locations that called your modified function, and make sure that you didn't break ANY of them AT ALL!
+
+- In fact, ANY TIME that you change a function's ARGUMENTS, RETURN VALUE or THROWN EXCEPTIONS (new/deleted ones), then you MUST update the function's PHPdoc block to match the new truth. And you MUST check ALL other functions that CALL your function, and update those too. For example let's say they DON'T handle a new exception you're now throwing, in which case you MUST now update THEIR `@throws` documentation to document the fact that THEY now let yet another exception bubble up. And then you MUST search for the functions that called THOSE updated functions and update THEIR documentation TOO, all the way until you've reached the top and have FULLY documented what exceptions are being thrown in the whole chain of function calls.
+
+- You MUST ALWAYS update the PHPdoc blocks EVERYWHERE that's affected by your changes (whenever you've changed a functions ARGUMENTS or RETURN type or what it THROWS (any new/deleted exceptions)), because imagine if we NEVER update the blocks EVERYWHERE that's affected by your change. We would then have something like `/** @param $a, $b, $c */ function foo ($x)` and sudden, critical exceptions that aren't getting handled and thus KILL the program.
+
+Here's a checklist for what you MUST do to ENSURE totally correct documentation EVERY TIME that you make a CHANGE to a function's ARGUMENTS or RETURN TYPE or EXCEPTIONS of an existing function, OR when you introduce a NEW function whose use is being added to any existing functions.
+
+1. You MUST ALWAYS use grep/ag. Find EVERY other code location that uses your new/modified function.
+
+2. Check: Did your changes just BREAK EVERYTHING somewhere else, which now has to be updated to match? Almost GUARANTEED the answer is YES, and you MUST update the other locations that expected the old function behavior/return type/parameters/exceptions.
+
+3. Check: Do you need to also UPDATE the PHPdoc for those OTHER functions too? OFTEN the answer is YES. For example, if you've changed the exceptions that a deep, internal function throws, then you MUST either catch it in all higher functions and do something with it, OR let it pass through them upwards. And if you do let it pass through and bubble up then you MUST ALSO update the PHPdoc for THAT higher function to say `"@throws TheNewException"` (or delete something in case you changed a subfunction to no longer throw).
+
+4. If your updates to the affected higher-level functions in steps 2/3 means that THOSE other functions now ALSO behave differently (meaning THEY have new return values/exceptions/parameters), then you MUST also do ANOTHER grep/ag to check for anything that uses THOSE functions, and update all of those code locations TOO. And continue that way up the entire chain of functions that call each other, until you reach the top of the project, so that the entire chain of function calls is caught/documented properly. Otherwise, those unexpected (undocumented) exceptions will terminate PHP, so this is very important!
+
+
+
+## Styleguides
+
+### Namespaces
+
+- Organize all classes into logical namespaces.
+
+- We follow the PSR-4 Autoloading standard, which means that you MUST only have ONE class per source code `.php` file. And the namespace AND classname MUST match BOTH its disk path AND its `.php` filename. In our project, the `src/` folder is the `InstagramAPI` namespace, and everything under that is for its subnamespaces (folders) and our classes (PHP files).
+
+Example of a proper class in our top-level namespace:
+
+```php
+src/Something.php:
+_protectedProperty;
+ }
+
+ public function setProtectedProperty(
+ $protectedProperty)
+ {
+ $this->_protectedProperty = $protectedProperty;
+ }
+
+ public function getPublicProperty()
+ {
+ return $this->_publicProperty;
+ }
+
+ public function setPublicProperty(
+ $publicProperty)
+ {
+ $this->publicProperty = $publicProperty;
+ }
+
+ protected function _somethingInternal()
+ {
+ ...
+ }
+
+ ...
+}
+```
+
+- All functions and variables MUST have descriptive names that document their purpose automatically. Use names like `$videoFilename` and `$deviceInfo` and so on, so that the code documents itself instead of needing tons of comments to explain what each step is doing.
+
+Examples of BAD variable names:
+
+```php
+$x = $po + $py;
+$w = floor($h * $ar);
+```
+
+Examples of GOOD variable names:
+
+```php
+$endpoint = $this->url.'?'.http_build_query($this->params);
+$this->_aspectRatio = (float) ($this->_width / $this->_height);
+$width = floor($this->_height * $this->_maxAspectRatio);
+```
+
+- All functions MUST have occasional comments that explain what they're doing in their various substeps. Look at our codebase and follow our commenting style.
+
+- Our comments start with a capital letter and end in punctuation, and describe the purpose in as few words as possible, such as: `// Default request options (immutable after client creation).`
+
+- All functions MUST do as little work as possible, so that they are easy to maintain and bugfix.
+
+Example of a GOOD function layout:
+
+```php
+function requestVideoURL(...);
+
+function uploadVideoChunks(...);
+
+function configureVideo(...);
+
+function uploadVideo(...)
+{
+ $url = $this->requestVideoURL();
+ if (...handle any errors from the previous function)
+
+ $uploadResult = $this->uploadVideoChunks($url, ...);
+
+ $this->configureVideo($uploadResult, ...);
+}
+```
+
+Example of a BAD function layout:
+
+```php
+function uploadVideo(...)
+{
+ // Request upload URL.
+ // Upload video data to URL.
+ // Configure its location property.
+ // Post it to a timeline.
+ // Call your grandmother.
+ // Make some tea.
+ // ...and 500 other lines of code.
+}
+```
+
+- All function parameter lists MUST be well-thought out so that they list the most important arguments FIRST and so that they are as SIMPLE as possible to EXTEND in the FUTURE, since Instagram's API changes occasionally.
+
+Avoid this kind of function template:
+
+```php
+function uploadVideo($videoFilename, $filter, $url, $caption, $usertags, $hashtags);
+```
+
+Make such multi-argument functions take future-extensible option-arrays instead, especially if you expect that more properties may be added in the future.
+
+So the above would instead be PROPERLY designed as follows:
+
+```php
+function uploadVideo($videoFilename, array $metadata);
+```
+
+Now users can just say `uploadVideo($videoFilename, ['hashtags'=>$hashtags]);`, and we can easily add more metadata fields in the future without ever breaking backwards-compatibility with projects that are using our function!
+
+### Function Documentation
+
+- All functions MUST have _COMPLETE_ PHPdoc doc-blocks. The critically important information is the single-sentence `summary-line` (ALWAYS), then the `detailed description` (if necessary), then the `@param` descriptions (if any), then the `@throws` (one for EVERY type of exception that it throws, even uncaught ones thrown from DEEPER functions called within this function), then the `@return` (if the function returns something), and lastly one or more `@see` if there's any need for a documentation reference to a URL or another function or class.
+
+Example of a properly documented function:
+
+```php
+ /**
+ * Generates a User Agent string from a Device (<< that is the REQUIRED ONE-SENTENCE summary-line).
+ *
+ * [All lines after that are the optional description. This function didn't need any,
+ * but you CAN use this area to provide extra information describing things worth knowing.]
+ *
+ * @param \InstagramAPI\Devices\Device $device The Android device.
+ * @param string[]|null $names (optional) Array of name-strings.
+ *
+ * @throws \InvalidArgumentException If the device parameter is invalid.
+ * @throws \InstagramAPI\Exception\InstagramException In case of invalid or failed API response.
+ *
+ * @return string
+ *
+ * @see otherFunction()
+ * @see http://some-url...
+ */
+ public static function buildUserAgent(
+ Device $device,
+ $names = null)
+ {
+ ...
+ }
+```
+
+- You MUST take EXTREMELY GOOD CARE to ALWAYS _perfectly_ document ALL parameters, the EXACT return-type, and ALL thrown exceptions. All other project developers RELY on the function-documentation ALWAYS being CORRECT! With incorrect documentation, other developers would make incorrect assumptions and _severe_ bugs would be introduced!
+
+### Exceptions
+
+- ALL thrown exceptions that can happen inside a function or in ANY of its SUB-FUNCTION calls MUST be documented as `@throws`, so that we get a COMPLETE OVERVIEW of ALL exceptions that may be thrown when we call the function. YES, that EVEN means exceptions that come from deeper function calls, whose exceptions are NOT being caught by your function and which will therefore bubble up if they're thrown by those deeper sub-functions!
+- Always remember that Exceptions WILL CRITICALLY BREAK ALL OTHER CODE AND STOP PHP'S EXECUTION if not handled or documented properly! They are a LOT of responsibility! So you MUST put a LOT OF TIME AND EFFORT into PROPERLY handling (_catching and doing something_) for ALL exceptions that your function should handle, AND adding PHPdoc _documentation_ about the ones that your function DOESN'T catch/handle internally and which WILL therefore bubble upwards and would possibly BREAK other code (which is EXACTLY what would happen if an exception ISN'T documented by you and someone then uses your bad function and doesn't "catch" your exception since YOU didn't tell them that it can be thrown)!
+- All of our internal exceptions derive from `\InstagramAPI\Exception\InstagramException`, so it's always safe to declare that one as a `@throws \InstagramAPI\Exception\InstagramException` if you're calling anything that throws exceptions based on our internal `src/Exception/*.php` system. But it's even better if you can pinpoint which exact exceptions are thrown, by looking at the functions you're calling and seeing their `@throws` documentation, WHICH OF COURSE DEPENDS ON PEOPLE HAVING WRITTEN PROPER `@throws` FOR THOSE OTHER FUNCTIONS SO THAT _YOU_ KNOW WHAT THE FUNCTIONS YOU'RE CALLING WILL THROW. DO YOU SEE _NOW_ HOW IMPORTANT IT IS TO DECLARE EXCEPTIONS PROPERLY AND TO _ALWAYS_ KEEP THAT LIST UP TO DATE
+- Whenever you are using an EXTERNAL LIBRARY that throws its own custom exceptions (meaning NOT one of the standard PHP ones such as `\Exception` or `\InvalidArgumentException`, etc), then you MUST ALWAYS re-wrap the exception into some appropriate exception from our own library instead, otherwise users will not be able to say `catch (\InstagramAPI\Exception\InstagramException $e)`, since the 3rd party exceptions wouldn't be derived from our base exception and wouldn't be caught, thus breaking the user's program. To solve that, look at the design of our `src/Exception/NetworkException.php`, which we use in `src/Client.php` to re-wrap all Guzzle exceptions into our own exception type instead. Read the source-code of our NetworkException and it will explain how to properly re-wrap 3rd party exceptions and how to ensure that your re-wrapped exception will give users helpful messages and helpful stack traces.
+
+
+# Contributing new endpoints
+
+In order to add endpoints to the API you will need to capture the requests first. For that, you can use any HTTPS proxy you want. You can find a lot of information about this on the internet. Remember that you need to install a root CA (Certificate Authority) in your device so that the proxy can decrypt the requests and show them to you.
+
+Also be aware that you cannot capture Instagram for iPhone's requests. The iPhone API parameters are totally different and they are NOT compatible with this Android API library! You MUST use a real Android APK when capturing requests!
+
+Once you have the endpoint and necessary parameters, how do you add them to this library? Easy, you can follow this example:
+
+```php
+ public function getAwesome(
+ array $userList)
+ {
+ return $this->ig->request('awesome/endpoint/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('user_ids', implode(',', $userList))
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\AwesomeResponse());
+ }
+```
+
+In the example above you can see `('awesome/endpoint/')` which is the endpoint you captured. We are simulating a POST request, so you can add POST parameters easily by doing `->addPost('_uuid', $this->uuid)`.
+
+Which is basically:
+
+```php
+->addPost(key, value)
+```
+
+Where key is the name of the POST param, and value is whatever value the server requires for that parameter.
+
+Some of the requests are signed. This means there is a hash concatenated with the JSON. In order to make a signed request, we can enable or disable signing with the following line:
+
+```php
+->setSignedPost($isSigned)
+```
+
+`$isSigned` is boolean, so if you want a signed request, you simply set it to `true`.
+
+If the request is a GET request, you can add the GET query parameters like this (instead of using `addPost`):
+
+```php
+->addParam(key, value)
+```
+
+And finally, we always end with the `getResponse` function call, which will read the response and return an object with all of the server response values:
+
+```php
+->getResponse(new Response\AwesomeResponse());
+```
+
+Now you might be wondering how to create that response class? But there is nothing to worry about... it's very simple!
+
+Imagine that you have the following response:
+
+```json
+{"items": [{"user": {"is_verified": false, "has_anonymous_profile_picture": false, "is_private": false, "full_name": "awesome", "username": "awesome", "pk": "uid", "profile_pic_url": "profilepic"}, "large_urls": [], "caption": "", "thumbnail_urls": ["thumb1", "thumb2", "thumb3", "thumb4"]}], "status": "ok"}
+```
+
+You can use [http://jsoneditoronline.org](http://jsoneditoronline.org/) for a better visualization:
+
+
+
+(Alternatively, if you're an advanced user, you can create an empty response-class (with no defined properties yet), and then use its `->printJson()` function to look at its contents in a beautifully readable way.)
+
+So your new `src/Response/AwesomeResponse.php` class should contain one `JSON_PROPERTY_MAP` property named `items`. Our magical [LazyJsonMapper](https://github.com/lazyjsonmapper/lazyjsonmapper) object mapping system needs a PHPdoc-style type-definition to tell us if the property is another class, a float, an int, a string, a string array, etc. By default, if you don't specify any type (if you set its value to `''`), it will treat the JSON value as whatever type PHP detected it as internally during JSON decoding (such as a string, int, float, bool, etc).
+
+In this scenario, your `src/Response/AwesomeResponse.php` file and its property definitions should look as follows, since we want to map `items` to an array of `Suggestion` objects:
+
+```php
+ 'Model\Suggestion[]',
+ ];
+}
+```
+
+The `items` property will now expect an array of Suggestion model objects. And `src/Response/Model/Suggestion.php` should look like this:
+
+```php
+ '',
+ 'social_context' => '',
+ 'algorithm' => '',
+ 'thumbnail_urls' => 'string[]',
+ 'value' => '',
+ 'caption' => '',
+ 'user' => 'User',
+ 'large_urls' => 'string[]',
+ 'media_ids' => '',
+ 'icon' => '',
+ ];
+}
+```
+
+Here in this `Suggestion` class you can see many variables that didn't appear in our example endpoint's response, but that's because many other requests _re-use_ the same object, and depending the request, their response contents may differ a bit. Also note that unlike the AwesomeResponse class, the actual Model objects (the files in in `src/Response/Model/`) _don't_ use the "Model\" prefix when referring to other model objects, since they are in the same namespace already.
+
+Also note that many of the properties above are defined as `''` (no type), which means "mixed/untyped". It simply means that the value is not forcibly converted to anything. That's how you should define most properties unless you have a specific reason to force anything to a specific type.
+
+It is also _extremely important_ that any properties relating to Media IDs, PKs, User PKs, etc, _must_ be declared as a `string`, otherwise they may be handled as a float/int which won't fit on 32-bit CPUs and will truncate the number, leading to the wrong data. Just look at all other Model objects that are already in this project, and be sure that any ID/PK fields in your own new Model object are properly tagged as `string` type too!
+
+Now you can test your new endpoint, in order to look at its response object:
+
+```
+$awesome = $i->getAwesome(['123']);
+$awesome->printJson(); // Look at the object data.
+$awesome->printPropertyDescriptions(); // Look at all defined properties.
+```
+
+And finally, how do you access the object's data? Via the magical [LazyJsonMapper](https://github.com/lazyjsonmapper/lazyjsonmapper), which your Response and Model objects inherit from! It automatically creates getters and setters for all properties, and has a million other features!
+
+```php
+$items = $awesome->getItems();
+$user = $items[0]->getUser();
+
+// LazyJsonMapper even lets you look at the JSON of specific sub-objects:
+$user->printJson();
+```
+
+Lastly, you may sometimes be implementing an endpoint which uses a nearly empty response with just the standard `status` and/or `message` (and `_messages`) fields, and no other fields. In that case, there's already a pre-made response which you should use: `GenericResponse`. Search the source code for that word and you'll find many endpoints where we use that basic response. Use it to easily add new endpoint implementations whenever there's no extra data to extract!
+
+We hope that you found this tutorial useful! :smile:
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/Dockerfile b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/Dockerfile
new file mode 100755
index 0000000..2b30c43
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/Dockerfile
@@ -0,0 +1,37 @@
+# The php:7.0-apache Docker image is based on debian:jessie.
+# See: https://github.com/docker-library/php/blob/20b89e64d16dc9310ba6493a38385e36304dded7/7.0/Dockerfile
+
+FROM php:7.1-apache-jessie
+RUN echo "deb http://deb.debian.org/debian jessie main" > /etc/apt/sources.list \
+ && echo "deb http://security.debian.org jessie/updates main" >> /etc/apt/sources.list \
+ && apt-get update \
+ && apt-get install -y \
+ libfreetype6-dev \
+ libjpeg62-turbo-dev \
+ libmcrypt-dev \
+ libpng12-dev \
+ git \
+ libav-tools \
+ unzip \
+ wget \
+ xz-utils \
+ && docker-php-ext-install -j$(nproc) iconv mcrypt \
+ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
+ && docker-php-ext-install -j$(nproc) gd \
+ && docker-php-ext-install -j$(nproc) bcmath \
+ && docker-php-ext-install -j$(nproc) exif
+
+RUN wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz \
+ && tar Jxvf ./ffmpeg-release-amd64-static.tar.xz \
+ && cp ./ffmpeg*amd64-static/ffmpeg /usr/local/bin/
+
+# Install Composer and make it available in the PATH
+RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer
+
+# Set the WORKDIR to /app so all following commands run in /app
+WORKDIR /var/www/html
+
+COPY . ./
+
+# Install dependencies with Composer.
+RUN composer install --prefer-source --no-interaction
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/LICENSE b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/LICENSE
new file mode 100755
index 0000000..b1ba59a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/LICENSE
@@ -0,0 +1,546 @@
+Reciprocal Public License (RPL-1.5)
+
+Version 1.5, July 15, 2007
+
+Copyright (C) 2001-2007
+Technical Pursuit Inc.,
+All Rights Reserved.
+
+
+PREAMBLE
+
+The Reciprocal Public License (RPL) is based on the concept of reciprocity or,
+if you prefer, fairness.
+
+In short, this license grew out of a desire to close loopholes in previous open
+source licenses, loopholes that allowed parties to acquire open source software
+and derive financial benefit from it without having to release their
+improvements or derivatives to the community which enabled them. This occurred
+any time an entity did not release their application to a "third party".
+
+While there is a certain freedom in this model of licensing, it struck the
+authors of the RPL as being unfair to the open source community at large and to
+the original authors of the works in particular. After all, bug fixes,
+extensions, and meaningful and valuable derivatives were not consistently
+finding their way back into the community where they could fuel further, and
+faster, growth and expansion of the overall open source software base.
+
+While you should clearly read and understand the entire license, the essence of
+the RPL is found in two definitions: "Deploy" and "Required Components".
+
+Regarding deployment, under the RPL your changes, bug fixes, extensions, etc.
+must be made available to the open source community at large when you Deploy in
+any form -- either internally or to an outside party. Once you start running
+the software you have to start sharing the software.
+
+Further, under the RPL all components you author including schemas, scripts,
+source code, etc. -- regardless of whether they're compiled into a single
+binary or used as two halves of client/server application -- must be shared.
+You have to share the whole pie, not an isolated slice of it.
+
+In addition to these goals, the RPL was authored to meet the requirements of
+the Open Source Definition as maintained by the Open Source Initiative (OSI).
+
+The specific terms and conditions of the license are defined in the remainder
+of this document.
+
+
+LICENSE TERMS
+
+1.0 General; Applicability & Definitions. This Reciprocal Public License
+Version 1.5 ("License") applies to any programs or other works as well as any
+and all updates or maintenance releases of said programs or works ("Software")
+not already covered by this License which the Software copyright holder
+("Licensor") makes available containing a License Notice (hereinafter defined)
+from the Licensor specifying or allowing use or distribution under the terms of
+this License. As used in this License:
+
+1.1 "Contributor" means any person or entity who created or contributed to the
+creation of an Extension.
+
+1.2 "Deploy" means to use, Serve, sublicense or distribute Licensed Software
+other than for Your internal Research and/or Personal Use, and includes
+without limitation, any and all internal use or distribution of Licensed
+Software within Your business or organization other than for Research and/or
+Personal Use, as well as direct or indirect sublicensing or distribution of
+Licensed Software by You to any third party in any form or manner.
+
+1.3 "Derivative Works" as used in this License is defined under U.S. copyright
+law.
+
+1.4 "Electronic Distribution Mechanism" means a mechanism generally accepted
+in the software development community for the electronic transfer of data such
+as download from an FTP server or web site, where such mechanism is publicly
+accessible.
+
+1.5 "Extensions" means any Modifications, Derivative Works, or Required
+Components as those terms are defined in this License.
+
+1.6 "License" means this Reciprocal Public License.
+
+1.7 "License Notice" means any notice contained in EXHIBIT A.
+
+1.8 "Licensed Software" means any Software licensed pursuant to this License.
+Licensed Software also includes all previous Extensions from any Contributor
+that You receive.
+
+1.9 "Licensor" means the copyright holder of any Software previously not
+covered by this License who releases the Software under the terms of this
+License.
+
+1.10 "Modifications" means any additions to or deletions from the substance or
+structure of (i) a file or other storage containing Licensed Software, or (ii)
+any new file or storage that contains any part of Licensed Software, or (iii)
+any file or storage which replaces or otherwise alters the original
+functionality of Licensed Software at runtime.
+
+1.11 "Personal Use" means use of Licensed Software by an individual solely for
+his or her personal, private and non-commercial purposes. An individual's use
+of Licensed Software in his or her capacity as an officer, employee, member,
+independent contractor or agent of a corporation, business or organization
+(commercial or non-commercial) does not qualify as Personal Use.
+
+1.12 "Required Components" means any text, programs, scripts, schema,
+interface definitions, control files, or other works created by You which are
+required by a third party of average skill to successfully install and run
+Licensed Software containing Your Modifications, or to install and run Your
+Derivative Works.
+
+1.13 "Research" means investigation or experimentation for the purpose of
+understanding the nature and limits of the Licensed Software and its potential
+uses.
+
+1.14 "Serve" means to deliver Licensed Software and/or Your Extensions by
+means of a computer network to one or more computers for purposes of execution
+of Licensed Software and/or Your Extensions.
+
+1.15 "Software" means any computer programs or other works as well as any
+updates or maintenance releases of those programs or works which are
+distributed publicly by Licensor.
+
+1.16 "Source Code" means the preferred form for making modifications to the
+Licensed Software and/or Your Extensions, including all modules contained
+therein, plus any associated text, interface definition files, scripts used to
+control compilation and installation of an executable program or other
+components required by a third party of average skill to build a running
+version of the Licensed Software or Your Extensions.
+
+1.17 "User-Visible Attribution Notice" means any notice contained in EXHIBIT B.
+
+1.18 "You" or "Your" means an individual or a legal entity exercising rights
+under this License. For legal entities, "You" or "Your" includes any entity
+which controls, is controlled by, or is under common control with, You, where
+"control" means (a) the power, direct or indirect, to cause the direction or
+management of such entity, whether by contract or otherwise, or (b) ownership
+of fifty percent (50%) or more of the outstanding shares or beneficial
+ownership of such entity.
+
+2.0 Acceptance Of License. You are not required to accept this License since
+you have not signed it, however nothing else grants you permission to use,
+copy, distribute, modify, or create derivatives of either the Software or any
+Extensions created by a Contributor. These actions are prohibited by law if
+you do not accept this License. Therefore, by performing any of these actions
+You indicate Your acceptance of this License and Your agreement to be bound by
+all its terms and conditions. IF YOU DO NOT AGREE WITH ALL THE TERMS AND
+CONDITIONS OF THIS LICENSE DO NOT USE, MODIFY, CREATE DERIVATIVES, OR
+DISTRIBUTE THE SOFTWARE. IF IT IS IMPOSSIBLE FOR YOU TO COMPLY WITH ALL THE
+TERMS AND CONDITIONS OF THIS LICENSE THEN YOU CAN NOT USE, MODIFY, CREATE
+DERIVATIVES, OR DISTRIBUTE THE SOFTWARE.
+
+3.0 Grant of License From Licensor. Subject to the terms and conditions of
+this License, Licensor hereby grants You a world-wide, royalty-free, non-
+exclusive license, subject to Licensor's intellectual property rights, and any
+third party intellectual property claims derived from the Licensed Software
+under this License, to do the following:
+
+3.1 Use, reproduce, modify, display, perform, sublicense and distribute
+Licensed Software and Your Extensions in both Source Code form or as an
+executable program.
+
+3.2 Create Derivative Works (as that term is defined under U.S. copyright law)
+of Licensed Software by adding to or deleting from the substance or structure
+of said Licensed Software.
+
+3.3 Under claims of patents now or hereafter owned or controlled by Licensor,
+to make, use, have made, and/or otherwise dispose of Licensed Software or
+portions thereof, but solely to the extent that any such claim is necessary to
+enable You to make, use, have made, and/or otherwise dispose of Licensed
+Software or portions thereof.
+
+3.4 Licensor reserves the right to release new versions of the Software with
+different features, specifications, capabilities, functions, licensing terms,
+general availability or other characteristics. Title, ownership rights, and
+intellectual property rights in and to the Licensed Software shall remain in
+Licensor and/or its Contributors.
+
+4.0 Grant of License From Contributor. By application of the provisions in
+Section 6 below, each Contributor hereby grants You a world-wide, royalty-
+free, non-exclusive license, subject to said Contributor's intellectual
+property rights, and any third party intellectual property claims derived from
+the Licensed Software under this License, to do the following:
+
+4.1 Use, reproduce, modify, display, perform, sublicense and distribute any
+Extensions Deployed by such Contributor or portions thereof, in both Source
+Code form or as an executable program, either on an unmodified basis or as
+part of Derivative Works.
+
+4.2 Under claims of patents now or hereafter owned or controlled by
+Contributor, to make, use, have made, and/or otherwise dispose of Extensions
+or portions thereof, but solely to the extent that any such claim is necessary
+to enable You to make, use, have made, and/or otherwise dispose of
+Licensed Software or portions thereof.
+
+5.0 Exclusions From License Grant. Nothing in this License shall be deemed to
+grant any rights to trademarks, copyrights, patents, trade secrets or any
+other intellectual property of Licensor or any Contributor except as expressly
+stated herein. Except as expressly stated in Sections 3 and 4, no other patent
+rights, express or implied, are granted herein. Your Extensions may require
+additional patent licenses from Licensor or Contributors which each may grant
+in its sole discretion. No right is granted to the trademarks of Licensor or
+any Contributor even if such marks are included in the Licensed Software.
+Nothing in this License shall be interpreted to prohibit Licensor from
+licensing under different terms from this License any code that Licensor
+otherwise would have a right to license.
+
+5.1 You expressly acknowledge and agree that although Licensor and each
+Contributor grants the licenses to their respective portions of the Licensed
+Software set forth herein, no assurances are provided by Licensor or any
+Contributor that the Licensed Software does not infringe the patent or other
+intellectual property rights of any other entity. Licensor and each
+Contributor disclaim any liability to You for claims brought by any other
+entity based on infringement of intellectual property rights or otherwise. As
+a condition to exercising the rights and licenses granted hereunder, You
+hereby assume sole responsibility to secure any other intellectual property
+rights needed, if any. For example, if a third party patent license is
+required to allow You to distribute the Licensed Software, it is Your
+responsibility to acquire that license before distributing the Licensed
+Software.
+
+6.0 Your Obligations And Grants. In consideration of, and as an express
+condition to, the licenses granted to You under this License You hereby agree
+that any Modifications, Derivative Works, or Required Components (collectively
+Extensions) that You create or to which You contribute are governed by the
+terms of this License including, without limitation, Section 4. Any Extensions
+that You create or to which You contribute must be Deployed under the terms of
+this License or a future version of this License released under Section 7. You
+hereby grant to Licensor and all third parties a world-wide, non-exclusive,
+royalty-free license under those intellectual property rights You own or
+control to use, reproduce, display, perform, modify, create derivatives,
+sublicense, and distribute Licensed Software, in any form. Any Extensions You
+make and Deploy must have a distinct title so as to readily tell any
+subsequent user or Contributor that the Extensions are by You. You must
+include a copy of this License or directions on how to obtain a copy with
+every copy of the Extensions You distribute. You agree not to offer or impose
+any terms on any Source Code or executable version of the Licensed Software,
+or its Extensions that alter or restrict the applicable version of this
+License or the recipients' rights hereunder.
+
+6.1 Availability of Source Code. You must make available, under the terms of
+this License, the Source Code of any Extensions that You Deploy, via an
+Electronic Distribution Mechanism. The Source Code for any version that You
+Deploy must be made available within one (1) month of when you Deploy and must
+remain available for no less than twelve (12) months after the date You cease
+to Deploy. You are responsible for ensuring that the Source Code to each
+version You Deploy remains available even if the Electronic Distribution
+Mechanism is maintained by a third party. You may not charge a fee for any
+copy of the Source Code distributed under this Section in excess of Your
+actual cost of duplication and distribution of said copy.
+
+6.2 Description of Modifications. You must cause any Modifications that You
+create or to which You contribute to be documented in the Source Code, clearly
+describing the additions, changes or deletions You made. You must include a
+prominent statement that the Modifications are derived, directly or indirectly,
+from the Licensed Software and include the names of the Licensor and any
+Contributor to the Licensed Software in (i) the Source Code and (ii) in any
+notice displayed by the Licensed Software You distribute or in related
+documentation in which You describe the origin or ownership of the Licensed
+Software. You may not modify or delete any pre-existing copyright notices,
+change notices or License text in the Licensed Software without written
+permission of the respective Licensor or Contributor.
+
+6.3 Intellectual Property Matters.
+
+a. Third Party Claims. If You have knowledge that a license to a third party's
+intellectual property right is required to exercise the rights granted by this
+License, You must include a human-readable file with Your distribution that
+describes the claim and the party making the claim in sufficient detail that a
+recipient will know whom to contact.
+
+b. Contributor APIs. If Your Extensions include an application programming
+interface ("API") and You have knowledge of patent licenses that are
+reasonably necessary to implement that API, You must also include this
+information in a human-readable file supplied with Your distribution.
+
+c. Representations. You represent that, except as disclosed pursuant to 6.3(a)
+above, You believe that any Extensions You distribute are Your original
+creations and that You have sufficient rights to grant the rights conveyed by
+this License.
+
+6.4 Required Notices.
+
+a. License Text. You must duplicate this License or instructions on how to
+acquire a copy in any documentation You provide along with the Source Code of
+any Extensions You create or to which You contribute, wherever You describe
+recipients' rights relating to Licensed Software.
+
+b. License Notice. You must duplicate any notice contained in EXHIBIT A (the
+"License Notice") in each file of the Source Code of any copy You distribute
+of the Licensed Software and Your Extensions. If You create an Extension, You
+may add Your name as a Contributor to the Source Code and accompanying
+documentation along with a description of the contribution. If it is not
+possible to put the License Notice in a particular Source Code file due to its
+structure, then You must include such License Notice in a location where a
+user would be likely to look for such a notice.
+
+c. Source Code Availability. You must notify the software community of the
+availability of Source Code to Your Extensions within one (1) month of the date
+You initially Deploy and include in such notification a description of the
+Extensions, and instructions on how to acquire the Source Code. Should such
+instructions change you must notify the software community of revised
+instructions within one (1) month of the date of change. You must provide
+notification by posting to appropriate news groups, mailing lists, weblogs, or
+other sites where a publicly accessible search engine would reasonably be
+expected to index your post in relationship to queries regarding the Licensed
+Software and/or Your Extensions.
+
+d. User-Visible Attribution. You must duplicate any notice contained in
+EXHIBIT B (the "User-Visible Attribution Notice") in each user-visible display
+of the Licensed Software and Your Extensions which delineates copyright,
+ownership, or similar attribution information. If You create an Extension,
+You may add Your name as a Contributor, and add Your attribution notice, as an
+equally visible and functional element of any User-Visible Attribution Notice
+content. To ensure proper attribution, You must also include such User-Visible
+Attribution Notice in at least one location in the Software documentation
+where a user would be likely to look for such notice.
+
+6.5 Additional Terms. You may choose to offer, and charge a fee for, warranty,
+support, indemnity or liability obligations to one or more recipients of
+Licensed Software. However, You may do so only on Your own behalf, and not on
+behalf of the Licensor or any Contributor except as permitted under other
+agreements between you and Licensor or Contributor. You must make it clear that
+any such warranty, support, indemnity or liability obligation is offered by You
+alone, and You hereby agree to indemnify the Licensor and every Contributor for
+any liability plus attorney fees, costs, and related expenses due to any such
+action or claim incurred by the Licensor or such Contributor as a result of
+warranty, support, indemnity or liability terms You offer.
+
+6.6 Conflicts With Other Licenses. Where any portion of Your Extensions, by
+virtue of being Derivative Works of another product or similar circumstance,
+fall under the terms of another license, the terms of that license should be
+honored however You must also make Your Extensions available under this
+License. If the terms of this License continue to conflict with the terms of
+the other license you may write the Licensor for permission to resolve the
+conflict in a fashion that remains consistent with the intent of this License.
+Such permission will be granted at the sole discretion of the Licensor.
+
+7.0 Versions of This License. Licensor may publish from time to time revised
+versions of the License. Once Licensed Software has been published under a
+particular version of the License, You may always continue to use it under the
+terms of that version. You may also choose to use such Licensed Software under
+the terms of any subsequent version of the License published by Licensor. No
+one other than Licensor has the right to modify the terms applicable to
+Licensed Software created under this License.
+
+7.1 If You create or use a modified version of this License, which You may do
+only in order to apply it to software that is not already Licensed Software
+under this License, You must rename Your license so that it is not confusingly
+similar to this License, and must make it clear that Your license contains
+terms that differ from this License. In so naming Your license, You may not
+use any trademark of Licensor or of any Contributor. Should Your modifications
+to this License be limited to alteration of a) Section 13.8 solely to modify
+the legal Jurisdiction or Venue for disputes, b) EXHIBIT A solely to define
+License Notice text, or c) to EXHIBIT B solely to define a User-Visible
+Attribution Notice, You may continue to refer to Your License as the
+Reciprocal Public License or simply the RPL.
+
+8.0 Disclaimer of Warranty. LICENSED SOFTWARE IS PROVIDED UNDER THIS LICENSE
+ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
+INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE LICENSED SOFTWARE IS FREE
+OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+FURTHER THERE IS NO WARRANTY MADE AND ALL IMPLIED WARRANTIES ARE DISCLAIMED
+THAT THE LICENSED SOFTWARE MEETS OR COMPLIES WITH ANY DESCRIPTION OF
+PERFORMANCE OR OPERATION, SAID COMPATIBILITY AND SUITABILITY BEING YOUR
+RESPONSIBILITY. LICENSOR DISCLAIMS ANY WARRANTY, IMPLIED OR EXPRESSED, THAT
+ANY CONTRIBUTOR'S EXTENSIONS MEET ANY STANDARD OF COMPATIBILITY OR DESCRIPTION
+OF PERFORMANCE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LICENSED SOFTWARE IS WITH YOU. SHOULD LICENSED SOFTWARE PROVE DEFECTIVE IN ANY
+RESPECT, YOU (AND NOT THE LICENSOR OR ANY OTHER CONTRIBUTOR) ASSUME THE COST
+OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. UNDER THE TERMS OF THIS
+LICENSOR WILL NOT SUPPORT THIS SOFTWARE AND IS UNDER NO OBLIGATION TO ISSUE
+UPDATES TO THIS SOFTWARE. LICENSOR HAS NO KNOWLEDGE OF ERRANT CODE OR VIRUS IN
+THIS SOFTWARE, BUT DOES NOT WARRANT THAT THE SOFTWARE IS FREE FROM SUCH ERRORS
+OR VIRUSES. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS
+LICENSE. NO USE OF LICENSED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
+DISCLAIMER.
+
+9.0 Limitation of Liability. UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+LICENSOR, ANY CONTRIBUTOR, OR ANY DISTRIBUTOR OF LICENSED SOFTWARE, OR ANY
+SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT,
+SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING,
+WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER
+FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES,
+EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH
+DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH
+OR PERSONAL INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
+APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS
+EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10.0 High Risk Activities. THE LICENSED SOFTWARE IS NOT FAULT-TOLERANT AND IS
+NOT DESIGNED, MANUFACTURED, OR INTENDED FOR USE OR DISTRIBUTION AS ON-LINE
+CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE PERFORMANCE,
+SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT NAVIGATION OR
+COMMUNICATIONS SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE SUPPORT MACHINES, OR
+WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE LICENSED SOFTWARE COULD LEAD
+DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+("HIGH RISK ACTIVITIES"). LICENSOR AND CONTRIBUTORS SPECIFICALLY DISCLAIM ANY
+EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.
+
+11.0 Responsibility for Claims. As between Licensor and Contributors, each
+party is responsible for claims and damages arising, directly or indirectly,
+out of its utilization of rights under this License which specifically
+disclaims warranties and limits any liability of the Licensor. This paragraph
+is to be used in conjunction with and controlled by the Disclaimer Of
+Warranties of Section 8, the Limitation Of Damages in Section 9, and the
+disclaimer against use for High Risk Activities in Section 10. The Licensor
+has thereby disclaimed all warranties and limited any damages that it is or
+may be liable for. You agree to work with Licensor and Contributors to
+distribute such responsibility on an equitable basis consistent with the terms
+of this License including Sections 8, 9, and 10. Nothing herein is intended or
+shall be deemed to constitute any admission of liability.
+
+12.0 Termination. This License and all rights granted hereunder will terminate
+immediately in the event of the circumstances described in Section 13.6 or if
+applicable law prohibits or restricts You from fully and or specifically
+complying with Sections 3, 4 and/or 6, or prevents the enforceability of any
+of those Sections, and You must immediately discontinue any use of Licensed
+Software.
+
+12.1 Automatic Termination Upon Breach. This License and the rights granted
+hereunder will terminate automatically if You fail to comply with the terms
+herein and fail to cure such breach within thirty (30) days of becoming aware
+of the breach. All sublicenses to the Licensed Software that are properly
+granted shall survive any termination of this License. Provisions that, by
+their nature, must remain in effect beyond the termination of this License,
+shall survive.
+
+12.2 Termination Upon Assertion of Patent Infringement. If You initiate
+litigation by asserting a patent infringement claim (excluding declaratory
+judgment actions) against Licensor or a Contributor (Licensor or Contributor
+against whom You file such an action is referred to herein as "Respondent")
+alleging that Licensed Software directly or indirectly infringes any patent,
+then any and all rights granted by such Respondent to You under Sections 3 or
+4 of this License shall terminate prospectively upon sixty (60) days notice
+from Respondent (the "Notice Period") unless within that Notice Period You
+either agree in writing (i) to pay Respondent a mutually agreeable reasonably
+royalty for Your past or future use of Licensed Software made by such
+Respondent, or (ii) withdraw Your litigation claim with respect to Licensed
+Software against such Respondent. If within said Notice Period a reasonable
+royalty and payment arrangement are not mutually agreed upon in writing by the
+parties or the litigation claim is not withdrawn, the rights granted by
+Licensor to You under Sections 3 and 4 automatically terminate at the
+expiration of said Notice Period.
+
+12.3 Reasonable Value of This License. If You assert a patent infringement
+claim against Respondent alleging that Licensed Software directly or
+indirectly infringes any patent where such claim is resolved (such as by
+license or settlement) prior to the initiation of patent infringement
+litigation, then the reasonable value of the licenses granted by said
+Respondent under Sections 3 and 4 shall be taken into account in determining
+the amount or value of any payment or license.
+
+12.4 No Retroactive Effect of Termination. In the event of termination under
+this Section all end user license agreements (excluding licenses to
+distributors and resellers) that have been validly granted by You or any
+distributor hereunder prior to termination shall survive termination.
+
+13.0 Miscellaneous.
+
+13.1 U.S. Government End Users. The Licensed Software is a "commercial item,"
+as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of
+"commercial computer software" and "commercial computer software
+documentation," as such terms are used in 48 C.F.R. 12.212 (Sept. 1995).
+Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4
+(June 1995), all U.S. Government End Users acquire Licensed Software with only
+those rights set forth herein.
+
+13.2 Relationship of Parties. This License will not be construed as creating
+an agency, partnership, joint venture, or any other form of legal association
+between or among You, Licensor, or any Contributor, and You will not represent
+to the contrary, whether expressly, by implication, appearance, or otherwise.
+
+13.3 Independent Development. Nothing in this License will impair Licensor's
+right to acquire, license, develop, subcontract, market, or distribute
+technology or products that perform the same or similar functions as, or
+otherwise compete with, Extensions that You may develop, produce, market, or
+distribute.
+
+13.4 Consent To Breach Not Waiver. Failure by Licensor or Contributor to
+enforce any provision of this License will not be deemed a waiver of future enforcement
+of that or any other provision.
+
+13.5 Severability. This License represents the complete agreement concerning
+the subject matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent necessary
+to make it enforceable.
+
+13.6 Inability to Comply Due to Statute or Regulation. If it is impossible for
+You to comply with any of the terms of this License with respect to some or
+all of the Licensed Software due to statute, judicial order, or regulation,
+then You cannot use, modify, or distribute the software.
+
+13.7 Export Restrictions. You may be restricted with respect to downloading or
+otherwise acquiring, exporting, or reexporting the Licensed Software or any
+underlying information or technology by United States and other applicable
+laws and regulations. By downloading or by otherwise obtaining the Licensed
+Software, You are agreeing to be responsible for compliance with all
+applicable laws and regulations.
+
+13.8 Arbitration, Jurisdiction & Venue. This License shall be governed by
+Colorado law provisions (except to the extent applicable law, if any, provides
+otherwise), excluding its conflict-of-law provisions. You expressly agree that
+any dispute relating to this License shall be submitted to binding arbitration
+under the rules then prevailing of the American Arbitration Association. You
+further agree that Adams County, Colorado USA is proper venue and grant such
+arbitration proceeding jurisdiction as may be appropriate for purposes of
+resolving any dispute under this License. Judgement upon any award made in
+arbitration may be entered and enforced in any court of competent
+jurisdiction. The arbitrator shall award attorney's fees and costs of
+arbitration to the prevailing party. Should either party find it necessary to
+enforce its arbitration award or seek specific performance of such award in a
+civil court of competent jurisdiction, the prevailing party shall be entitled
+to reasonable attorney's fees and costs. The application of the United Nations
+Convention on Contracts for the International Sale of Goods is expressly
+excluded. You and Licensor expressly waive any rights to a jury trial in any
+litigation concerning Licensed Software or this License. Any law or regulation
+that provides that the language of a contract shall be construed against the
+drafter shall not apply to this License.
+
+13.9 Entire Agreement. This License constitutes the entire agreement between
+the parties with respect to the subject matter hereof.
+
+EXHIBIT A
+
+The License Notice below must appear in each file of the Source Code of any
+copy You distribute of the Licensed Software or any Extensions thereto:
+
+ Unless explicitly acquired and licensed from Licensor under another
+ license, the contents of this file are subject to the Reciprocal Public
+ License ("RPL") Version 1.5, or subsequent versions as allowed by the RPL,
+ and You may not copy or use this file in either source code or executable
+ form, except in compliance with the terms and conditions of the RPL.
+
+ All software distributed under the RPL is provided strictly on an "AS
+ IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND
+ LICENSOR HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
+ LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific
+ language governing rights and limitations under the RPL.
+
+
+
+EXHIBIT B
+
+The User-Visible Attribution Notice below, when provided, must appear in each
+user-visible display as defined in Section 6.4 (d):
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/LICENSE_PREMIUM b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/LICENSE_PREMIUM
new file mode 100755
index 0000000..ed45a9e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/LICENSE_PREMIUM
@@ -0,0 +1,16 @@
+The Premium license applies to all entities which have been granted "premium"
+status, and lasts for the duration of their premium status:
+
+All terms of the Reciprocal Public License 1.5 (RPL-1.5) still apply,
+but with the following modifications:
+
+- You are NOT required to disclose your own source code publicly, which
+ means that this premium license is *perfect* for website/app builders.
+
+- You are NOT required to credit this library anywhere in your own projects.
+ (But if you are re-distributing OUR source code, you must of course still
+ retain all of our copyright notices, and not claim our code as your own.)
+
+- You are encouraged (but NOT required) to contribute your library
+ improvements/modifications back to us. We would appreciate it, and may
+ even grant you premium status if the contributions are high-quality!
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/README.md b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/README.md
new file mode 100755
index 0000000..16ab30b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/README.md
@@ -0,0 +1,135 @@
+#  Instagram-API [](https://packagist.org/packages/mgp25/instagram-php) [](https://packagist.org/packages/mgp25/instagram-php)  [](https://packagist.org/packages/mgp25/instagram-php)
+
+This is a PHP library which emulates Instagram's Private API. This library is packed full with almost all the features from the Instagram Android App. This includes media uploads, direct messaging, stories and more.
+
+**Read the [wiki](https://github.com/mgp25/Instagram-API/wiki)** and previous issues before opening a new one! Maybe your issue has already been answered.
+
+**Frequently Asked Questions:** [F.A.Q.](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+
+**Do you like this project? Support it by donating**
+
+**mgp25**
+
+-  Paypal: [Donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5ATYY8H9MC96E)
+-  Bitcoin: 1DCEpC9wYXeUGXS58qSsqKzyy7HLTTXNYe
+
+**stevejobzniak**
+
+-  Paypal: [Donate](https://www.paypal.me/Armindale/0usd)
+-  Bitcoin: 18XF1EmrkpYi4fqkR2XcHkcJxuTMYG4bcv
+
+**jroy**
+
+-  Paypal: [Donate](https://www.paypal.me/JoshuaRoy1/0usd)
+-  Bitcoin: 32J2AqJBDY1VLq6wfZcLrTYS8fCcHHVDKD
+
+----------
+## Installation
+
+### Dependencies
+
+Install/enable the required php extensions and dependencies. You can learn how to do so [here](https://github.com/mgp25/Instagram-API/wiki/Dependencies).
+
+### Install this library
+We use composer to distribute our code effectively and easily. If you do not already have composer installed, you can download and install it here [here](https://getcomposer.org/download/).
+
+Once you have composer installed, you can do the following:
+```sh
+composer require mgp25/instagram-php
+```
+
+```php
+require __DIR__.'/../vendor/autoload.php';
+
+$ig = new \InstagramAPI\Instagram();
+```
+
+If you want to test new and possibly unstable code that is in the master branch, and which hasn't yet been released, then you can do the following (at your own risk):
+
+```sh
+composer require mgp25/instagram-php dev-master
+```
+
+#### _Warning about moving data to a different server_
+
+_Composer checks your system's capabilities and selects libraries based on your **current** machine (where you are running the `composer` command). So if you run Composer on machine `A` to install this library, it will check machine `A`'s capabilities and will install libraries appropriate for that machine (such as installing the PHP 7+ versions of various libraries). If you then move your whole installation to machine `B` instead, it **will not work** unless machine `B` has the **exact** same capabilities (same or higher PHP version and PHP extensions)! Therefore, you should **always** run the Composer-command on your intended target machine instead of your local machine._
+
+## Examples
+
+All examples can be found [here](https://github.com/mgp25/Instagram-API/tree/master/examples).
+
+## Code of Conduct
+
+This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
+By participating, you are expected to uphold this code.
+Please report any unacceptable behavior.
+
+## How do I contribute
+
+If you would like to contribute to this project, please feel free to submit a pull request.
+
+Before you do, take a look at the [contributing guide](https://github.com/mgp25/Instagram-API/blob/master/CONTRIBUTING.md).
+
+## Why did I make this API?
+
+After legal measures, Facebook, WhatsApp and Instagram blocked my accounts.
+In order to use Instagram on my phone I needed a new phone, as they banned my UDID, so that is basically why I made this API.
+
+### What is Instagram?
+According to [the company](https://instagram.com/about/faq/):
+
+> "Instagram is a fun and quirky way to share your life with friends through a series of pictures. Snap a photo with your mobile phone, then choose a filter to transform the image into a memory to keep around forever. We're building Instagram to allow you to experience moments in your friends' lives through pictures as they happen. We imagine a world more connected through photos."
+
+# License
+
+In order help ensure fairness and sharing, this library is dual-licensed. Be
+aware that _all_ usage, unless otherwise specified, is under the **RPL-1.5**
+license!
+
+- Reciprocal Public License 1.5 (RPL-1.5): https://opensource.org/licenses/RPL-1.5
+
+You should read the _entire_ license; especially the `PREAMBLE` at the
+beginning. In short, the word `reciprocal` means "giving something back in
+return for what you are getting". It is _**not** a freeware license_. This
+license _requires_ that you open-source _all_ of your own source code for _any_
+project which uses this library! Creating and maintaining this library is
+endless hard work for us. That's why there is _one_ simple requirement for you:
+Give _something_ back to the world. Whether that's code _or_ financial support
+for this project is entirely up to you, but _nothing else_ grants you _any_
+right to use this library.
+
+Furthermore, the library is _also_ available _to certain entities_ under a
+modified version of the RPL-1.5, which has been modified to allow you to use the
+library _without_ open-sourcing your own project. The modified license
+(see [LICENSE_PREMIUM](https://github.com/mgp25/Instagram-API/blob/master/LICENSE_PREMIUM))
+is granted to certain entities, at _our_ discretion, and for a _limited_ period
+of time (unless otherwise agreed), pursuant to our terms. Currently, we are
+granting this license to all
+"[premium subscribers](https://github.com/mgp25/Instagram-API/issues/2655)" for
+the duration of their subscriptions. You can become a premium subscriber by
+either contributing substantial amounts of high-quality code, or by subscribing
+for a fee. This licensing ensures fairness and stimulates the continued growth
+of this library through both code contributions and the financial support it
+needs.
+
+You are not required to accept this License since you have not signed it,
+however _nothing else_ grants you permission to _use_, copy, distribute, modify,
+or create derivatives of either the Software (this library) or any Extensions
+created by a Contributor. These actions are prohibited by law if you do not
+accept this License. Therefore, by performing any of these actions You indicate
+Your acceptance of this License and Your agreement to be bound by all its terms
+and conditions. IF YOU DO NOT AGREE WITH ALL THE TERMS AND CONDITIONS OF THIS
+LICENSE DO NOT USE, MODIFY, CREATE DERIVATIVES, OR DISTRIBUTE THE SOFTWARE. IF
+IT IS IMPOSSIBLE FOR YOU TO COMPLY WITH ALL THE TERMS AND CONDITIONS OF THIS
+LICENSE THEN YOU CAN NOT USE, MODIFY, CREATE DERIVATIVES, OR DISTRIBUTE THE
+SOFTWARE.
+
+# Terms and conditions
+
+- You will NOT use this API for marketing purposes (spam, botting, harassment, massive bulk messaging...).
+- We do NOT give support to anyone who wants to use this API to send spam or commit other crimes.
+- We reserve the right to block any user of this repository that does not meet these conditions.
+
+## Legal
+
+This code is in no way affiliated with, authorized, maintained, sponsored or endorsed by Instagram or any of its affiliates or subsidiaries. This is an independent and unofficial API. Use at your own risk.
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/composer.json b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/composer.json
new file mode 100755
index 0000000..166032e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/composer.json
@@ -0,0 +1,81 @@
+{
+ "name": "mgp25/instagram-api",
+ "description": "Instagram's private API for PHP",
+ "license": [
+ "RPL-1.5",
+ "proprietary"
+ ],
+ "keywords": [
+ "Instagram",
+ "Private",
+ "API",
+ "PHP"
+ ],
+ "support": {
+ "issues": "https://github.com/mgp25/Instagram-API/issues",
+ "wiki": "https://github.com/mgp25/Instagram-API/wiki",
+ "source": "https://github.com/mgp25/Instagram-API/"
+ },
+ "authors": [
+ {
+ "name": "mgp25",
+ "email": "me@mgp25.com",
+ "role": "Founder"
+ },
+ {
+ "name": "SteveJobzniak",
+ "homepage": "https://github.com/SteveJobzniak",
+ "role": "Developer"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "InstagramAPI\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "InstagramAPI\\Tests\\": "tests/"
+ }
+ },
+ "require": {
+ "php": ">=5.6",
+ "lazyjsonmapper/lazyjsonmapper": "^1.6.1",
+ "guzzlehttp/guzzle": "^6.2",
+ "ext-curl": "*",
+ "ext-mbstring": "*",
+ "ext-gd": "*",
+ "ext-exif": "*",
+ "ext-zlib": "*",
+ "ext-bcmath": "*",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "^2.5",
+ "react/socket": "^0.8",
+ "binsoul/net-mqtt-client-react": "^0.3.2",
+ "clue/socks-react": "^0.8.2",
+ "clue/http-proxy-react": "^1.1.0",
+ "psr/log": "^1.0",
+ "valga/fbns-react": "^0.1.8",
+ "symfony/process": "^3.4|^4.0",
+ "winbox/args": "1.0.0"
+ },
+ "suggest": {
+ "ext-event": "Installing PHP's native Event extension enables faster Realtime class event handling."
+ },
+ "require-dev": {
+ "react/http": "^0.7.2",
+ "friendsofphp/php-cs-fixer": "^2.11.0",
+ "monolog/monolog": "^1.23",
+ "phpunit/phpunit": "^5.7 || ^6.2"
+ },
+ "scripts": {
+ "codestyle": [
+ "lazydoctor -c composer.json -pfo",
+ "php-cs-fixer fix --config=.php_cs.dist --allow-risky yes",
+ "php devtools/checkStyle.php x"
+ ],
+ "test": [
+ "phpunit tests"
+ ]
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/README.md b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/README.md
new file mode 100755
index 0000000..00449d3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/README.md
@@ -0,0 +1,7 @@
+These resources are *ONLY for INTERNAL usage* by the Instagram-API library developer team.
+
+Do *NOT* ask us about them. Do *NOT* make support tickets about them. We will *ban* you.
+
+Using this library is a *privilege* and we do *not* have to explain everything to every newbie who stumbles upon our repository.
+
+If you want to join the team, then read for yourself what they do and try to figure it out on your own.
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/checkDevices.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/checkDevices.php
new file mode 100755
index 0000000..c1a64d1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/checkDevices.php
@@ -0,0 +1,229 @@
+login($username, $password);
+
+// Code for building video lists (uncomment both lines).
+// $userPk = $ig->people->getInfoByName('selenagomez')->getUser()->getPk();
+// buildVideoList($ig, $userPk); exit;
+
+// List of good videos and resolutions that we MUST see with a GOOD user agent.
+// Manually created via buildVideoList() and the code above, by selecting some
+// high resolution videos from random profiles. If these are deleted in the
+// future or if Instagram adds support for wider than 640px videos, we'll need
+// to rebuild this test array with other test videos.
+$highResVideoIDs = [
+ '1451670806714625231_460563723' => [ // Selena Gomez portrait video.
+ ['width'=>640, 'height'=>799, 'url'=>'https://instagram.com/p/BQlXuhNB6zP/'],
+ ['width'=> 480, 'height'=>599, 'url'=>'https://instagram.com/p/BQlXuhNB6zP/'],
+ ['width'=> 480, 'height'=>599, 'url'=>'https://instagram.com/p/BQlXuhNB6zP/'],
+ ],
+ '1264771449117881030_460563723' => [ // Selena Gomez 16:9 landscape video.
+ ['width'=>640, 'height'=>360, 'url'=>'https://instagram.com/p/BGNXu6SujLG/'],
+ ['width'=> 480, 'height'=>270, 'url'=>'https://instagram.com/p/BGNXu6SujLG/'],
+ ['width'=> 480, 'height'=>270, 'url'=>'https://instagram.com/p/BGNXu6SujLG/'],
+ ],
+ '1435631390916900349_460563723' => [ // Selena Gomez max res square video.
+ ['width'=>640, 'height'=>640, 'url'=>'https://instagram.com/p/BPsYyTMAHX9/'],
+ ['width'=> 480, 'height'=>480, 'url'=>'https://instagram.com/p/BPsYyTMAHX9/'],
+ ['width'=> 480, 'height'=>480, 'url'=>'https://instagram.com/p/BPsYyTMAHX9/'],
+ ],
+];
+
+$testDevices = [];
+
+// Test all of our current, "good" devices too? We must do that periodically.
+$testGoodDevices = true;
+if ($testGoodDevices) {
+ foreach (GoodDevices::getAllGoodDevices() as $deviceString) {
+ $testDevices[] = $deviceString;
+ }
+}
+
+// Additional devices to test. Add these new devices in devicestring format.
+$testDevices = array_merge(
+ $testDevices,
+ [
+ // DEMO STRING: This is a low-res device which is NOT capable of
+ // getting HD video URLs in API replies. It's just here for developer
+ // demo purposes. Also note that it's actually caught by the Device()
+ // class' REFUSAL to construct from anything lower than 1920x1080
+ // devices, so we had to FAKE its resolution HERE just to get it to
+ // even be testable! Its real User Agent string is supposed to be:
+ //'17/4.2.2; 240dpi; 480x800; samsung; SM-G350; cs02; hawaii_ss_cs02',
+ // However, Instagram only checks the model identifier, in this case
+ // "SM-G350", and the test shows that this bad device lacks HD videos.
+ '17/4.2.2; 240dpi; 1080x1920; samsung; SM-G350; cs02; hawaii_ss_cs02',
+ ]
+);
+
+// Build all device objects before we even run the tests, just to catch
+// any "bad devicestring" construction errors before wasting time.
+foreach ($testDevices as $key => $deviceString) {
+ // Create the new Device object, without automatic fallbacks.
+ $testDevices[$key] = new Device(Constants::IG_VERSION, Constants::VERSION_CODE, Constants::USER_AGENT_LOCALE, $deviceString, false);
+}
+
+// Test all devices in our list!
+foreach ($testDevices as $thisDevice) {
+ switchDevice($ig, $thisDevice);
+
+ $currentAgent = $ig->device->getUserAgent();
+ echo "\n[{$currentAgent}]\n";
+
+ $isBadDevice = false;
+ foreach ($highResVideoIDs as $videoId => $expectedResolutions) {
+ $bestWidth = $expectedResolutions[0]['width'];
+ $bestHeight = $expectedResolutions[0]['height'];
+ echo "* Checking {$videoId} (should be: {$bestWidth}x{$bestHeight})...";
+
+ // Retrieve video info (with 4 total attempts in case of throttling).
+ for ($attempt = 1; $attempt <= 4; ++$attempt) {
+ try {
+ $mediaInfo = $ig->media->getInfo($videoId)->getItems()[0];
+ break;
+ } catch (\InstagramAPI\Exception\ThrottledException $e) {
+ if ($attempt < 4) {
+ sleep(10);
+ } else {
+ throw $e;
+ }
+ }
+ }
+
+ // Verify all video candidates.
+ $videoVersions = $mediaInfo->getVideoVersions();
+ if (count($videoVersions) !== count($expectedResolutions)) {
+ die('Wrong number of video candidate URLs for media item: '.$videoId);
+ }
+ $isBadVersions = false;
+ foreach ($videoVersions as $idx => $version) {
+ $thisWidth = $version->getWidth();
+ $thisHeight = $version->getHeight();
+ $expectedWidth = $expectedResolutions[$idx]['width'];
+ $expectedHeight = $expectedResolutions[$idx]['height'];
+ if ($thisWidth != $expectedWidth || $thisHeight != $expectedHeight) {
+ $isBadVersions = true;
+ break;
+ }
+ }
+ echo $isBadVersions ? "Bad response: {$thisWidth}x{$thisHeight}.\n" : "OK.\n";
+ if ($isBadVersions) {
+ $isBadDevice = true;
+ }
+ }
+
+ echo $isBadDevice ? "THIS DEVICE IS BAD AND DOES NOT SUPPORT HD VIDEOS!\n" : "DEVICE OK.\n";
+}
+
+// INTERNAL FUNCTIONS...
+
+/**
+ * Changes the user-agent sent by the InstagramAPI library.
+ *
+ * @param \InstagramAPI\Instagram $ig
+ * @param DeviceInterface|string $value
+ */
+function switchDevice(
+ $ig,
+ $value)
+{
+ // Create the new Device object, without automatic fallbacks.
+ $device = ($value instanceof DeviceInterface ? $value : new Device(Constants::IG_VERSION, Constants::VERSION_CODE, Constants::USER_AGENT_LOCALE, $value, false));
+
+ // Update the Instagram Client's User-Agent to the new Device.
+ $ig->device = $device;
+ $ig->client->updateFromCurrentSettings();
+}
+
+/**
+ * Checks a timeline and builds a list of all videos.
+ *
+ * Used for building the internal $highResVideoIDs test array.
+ *
+ * Instagram forces videos (timelines and stories) to be no wider than 640px.
+ *
+ * Max square video: 640x640. Nothing can ever be bigger than that if square.
+ *
+ * Max landscape video: 640xDEPENDS ON PHONE (for example 640x360 (16:9 videos)).
+ * In landscape videos, height is always < 640.
+ *
+ * Max portrait video: 640xDEPENDS ON PHONE (for example 640x799, 640x1136 (16:9)).
+ * In portrait, height IS > 640 (anything less would be a square/landscape).
+ *
+ * So Instagram lets people post HD resolutions in PORTRAIT, but only SD videos
+ * in landscape... This actually leads to a funny realization: To post HD videos
+ * with a high HD resolution, you must rotate them so they're always portrait.
+ *
+ * Also note that Instagram allows UPLOADING videos up to 1080 pixels WIDE. But
+ * they will RESIZE them to no more than 640 pixels WIDE. Perhaps they will
+ * someday allow 1080-width playback, in which case we will have to test and
+ * revise all device identifiers again to make sure they all see the best URLs.
+ *
+ * @param \InstagramAPI\Instagram $ig
+ * @param string $userPk
+ */
+function buildVideoList(
+ $ig,
+ $userPk)
+{
+ // We must use a good device to get answers when scanning for HD videos.
+ switchDevice($ig, GoodDevices::getRandomGoodDevice());
+
+ echo "[\n";
+ $maxId = null;
+ do {
+ $feed = $ig->timeline->getUserFeed($userPk, $maxId);
+ foreach ($feed->getItems() as $item) {
+ if ($item->getMediaType() != \InstagramAPI\Response\Model\Item::VIDEO) {
+ continue;
+ }
+
+ $code = \InstagramAPI\InstagramID::toCode($item->getPk());
+ echo sprintf(" '%s' => [\n", $item->getId());
+ $videoVersions = $item->getVideoVersions();
+ foreach ($videoVersions as $version) {
+ echo sprintf(
+ " ['width'=>%d, 'height'=>%d, 'url'=>'https://instagram.com/p/%s/'],\n",
+ $version->getWidth(), $version->getHeight(), $code
+ );
+ }
+ echo " ],\n";
+ }
+ $maxId = $feed->getNextMaxId();
+ } while ($maxId !== null);
+ echo "]\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/checkExperiments.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/checkExperiments.php
new file mode 100755
index 0000000..b3b8f67
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/checkExperiments.php
@@ -0,0 +1,150 @@
+run();
+if (!empty($problemFiles)) {
+ // Exit with non-zero code to signal that there are problems.
+ exit(1);
+}
+
+class checkExperiments
+{
+ /**
+ * @var string
+ */
+ private $_baseDir;
+
+ /**
+ * @var string[]
+ */
+ private $_inspectFolders;
+
+ /**
+ * @var bool
+ */
+ private $_onlyShowInvalidFiles;
+
+ /**
+ * @var array
+ */
+ private $_experimentWhitelist;
+
+ /**
+ * Constructor.
+ *
+ * @param string $baseDir
+ * @param string[] $inspectFolders
+ * @param bool $onlyShowInvalidFiles
+ */
+ public function __construct(
+ $baseDir,
+ array $inspectFolders,
+ $onlyShowInvalidFiles)
+ {
+ $this->_baseDir = realpath($baseDir);
+ if ($this->_baseDir === false) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid path.', $baseDir));
+ }
+ $this->_inspectFolders = $inspectFolders;
+ $this->_onlyShowInvalidFiles = $onlyShowInvalidFiles;
+ $this->_experimentWhitelist = \InstagramAPI\Settings\StorageHandler::EXPERIMENT_KEYS;
+ }
+
+ /**
+ * Process single file.
+ *
+ * @param string $filePath
+ *
+ * @return bool TRUE if the file uses an un-whitelisted experiment, otherwise FALSE.
+ */
+ private function _processFile(
+ $filePath)
+ {
+ $hasProblems = false;
+ $processedExperiments = [];
+ $inputLines = @file($filePath);
+
+ if ($inputLines === false) {
+ // We were unable to read the input file. Ignore if broken symlink.
+ if (is_link($filePath)) {
+ return false; // File is okay, since the symlink is invalid.
+ } else {
+ echo "- {$filePath}: UNABLE TO READ FILE!".PHP_EOL;
+
+ return true; // File has problems...
+ }
+ }
+
+ if (preg_match_all('/->(?:getExperimentParam|isExperimentEnabled)\((?:\n {1,})?(?:\'|")(\w{1,})(?:\'|")/', implode('', $inputLines), $matches)) {
+ foreach ($matches[1] as $match) {
+ $experimentName = $match;
+ if (!strpos($experimentName, 'Experiment') !== false && !in_array($experimentName, $processedExperiments)) {
+ array_push($processedExperiments, $match);
+ if (in_array($experimentName, $this->_experimentWhitelist)) {
+ if (!$this->_onlyShowInvalidFiles) {
+ echo " {$filePath}: Uses whitelisted experiment: {$experimentName}.\n";
+ }
+ } else {
+ $hasProblems = true;
+ echo "- {$filePath}: Uses un-whitelisted experiment: {$experimentName}.\n";
+ }
+ }
+ }
+ }
+
+ return $hasProblems;
+ }
+
+ /**
+ * Process all *.php files in given path.
+ *
+ * @return string[] An array with all files that have un-whitelisted experiment usage.
+ */
+ public function run()
+ {
+ $filesWithProblems = [];
+ foreach ($this->_inspectFolders as $inspectFolder) {
+ $directoryIterator = new RecursiveDirectoryIterator($this->_baseDir.'/'.$inspectFolder);
+ $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
+ $phpIterator = new RegexIterator($recursiveIterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
+
+ foreach ($phpIterator as $filePath => $dummy) {
+ $hasProblems = $this->_processFile($filePath);
+ if ($hasProblems) {
+ $filesWithProblems[] = $filePath;
+ }
+ }
+ }
+
+ return $filesWithProblems;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/checkStyle.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/checkStyle.php
new file mode 100755
index 0000000..aa97ca6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/checkStyle.php
@@ -0,0 +1,203 @@
+run();
+if (!empty($badFiles)) {
+ // Exit with non-zero code to signal that there are problems.
+ exit(1);
+}
+
+class styleChecker
+{
+ /**
+ * @var string
+ */
+ private $_baseDir;
+
+ /**
+ * @var string[]
+ */
+ private $_inspectFolders;
+
+ /**
+ * @var bool
+ */
+ private $_onlyShowInvalidFiles;
+
+ /**
+ * Constructor.
+ *
+ * @param string $baseDir
+ * @param string[] $inspectFolders
+ * @param bool $onlyShowInvalidFiles
+ */
+ public function __construct(
+ $baseDir,
+ array $inspectFolders,
+ $onlyShowInvalidFiles)
+ {
+ $this->_baseDir = realpath($baseDir);
+ if ($this->_baseDir === false) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid path.', $baseDir));
+ }
+ $this->_inspectFolders = $inspectFolders;
+ $this->_onlyShowInvalidFiles = $onlyShowInvalidFiles;
+ }
+
+ /**
+ * Process single file.
+ *
+ * @param string $filePath
+ *
+ * @return bool TRUE if the file has codestyle problems, otherwise FALSE.
+ */
+ private function _processFile(
+ $filePath)
+ {
+ $hasProblems = false;
+ $hasVisibilityProblems = false;
+ $fileName = basename($filePath);
+ $inputLines = @file($filePath);
+ $outputLines = [];
+
+ if ($inputLines === false) {
+ // We were unable to read the input file. Ignore if broken symlink.
+ if (is_link($filePath)) {
+ return false; // File is okay, since the symlink is invalid.
+ } else {
+ echo "- {$filePath}: UNABLE TO READ FILE!".PHP_EOL;
+
+ return true; // File has problems...
+ }
+ }
+
+ foreach ($inputLines as $line) {
+ // Function arguments on separate lines.
+ if (preg_match('/^(.*?(?:(?:final|static)\s+)*(?:public|private|protected)(?:\s+(?:final|static))*\s+function\s+.+?)\((.+)\)(.*)$/', $line, $matches)) {
+ $hasProblems = true;
+
+ $funcstart = $matches[1];
+ $params = $matches[2];
+ $trail = $matches[3];
+ $params = explode(', ', $params);
+
+ $outputLines[] = $funcstart.'('.PHP_EOL;
+ for ($i = 0, $len = count($params); $i < $len; ++$i) {
+ $newline = ' '.$params[$i];
+ if ($i == ($len - 1)) {
+ $newline .= ')'.PHP_EOL;
+ } else {
+ $newline .= ','.PHP_EOL;
+ }
+ $outputLines[] = $newline;
+ }
+ // } else {
+ // $outputLines[] = $line;
+ }
+
+ // Appropriate public, private and protected member prefixes.
+ if (preg_match('/^\s*(?:(?:final|static)\s+)*(public|private|protected)(?:\s+(?:final|static))*\s+(function|\$)\s*&?([^;\(\s]+)/', $line, $matches)) {
+ $visibility = &$matches[1]; // public, private, protected
+ $type = &$matches[2]; // $, function
+ $name = &$matches[3]; // Member name
+
+ if ($visibility == 'public') {
+ if ($name[0] == '_' && (
+ $name != '__construct'
+ && $name != '__destruct'
+ && $name != '__call'
+ && $name != '__get'
+ && $name != '__set'
+ && $name != '__isset'
+ && $name != '__unset'
+ && $name != '__invoke'
+ && $name != '__toString'
+ )) {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PUBLIC NAME:".trim($matches[0]).PHP_EOL;
+ }
+ } else { // private, protected
+ if ($name[0] != '_') {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PRIVATE/PROTECTED NAME:".trim($matches[0]).PHP_EOL;
+ }
+ }
+ }
+ }
+
+ $newFile = implode('', $outputLines);
+ if (!$hasProblems) {
+ if (!$this->_onlyShowInvalidFiles) {
+ echo " {$filePath}: Already formatted correctly.\n";
+ }
+ } elseif (!$hasVisibilityProblems) {
+ // Has problems, but no visibility problems. Output fixed file.
+ echo "- {$filePath}: Has function parameter problems:\n";
+ echo $newFile;
+ } else {
+ echo "- {$filePath}: Had member visibility problems.\n";
+ }
+
+ return $hasProblems;
+ }
+
+ /**
+ * Process all *.php files in given path.
+ *
+ * @return string[] An array with all files that have codestyle problems.
+ */
+ public function run()
+ {
+ $filesWithProblems = [];
+ foreach ($this->_inspectFolders as $inspectFolder) {
+ $directoryIterator = new RecursiveDirectoryIterator($this->_baseDir.'/'.$inspectFolder);
+ $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
+ $phpIterator = new RegexIterator($recursiveIterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
+
+ foreach ($phpIterator as $filePath => $dummy) {
+ $hasProblems = $this->_processFile($filePath);
+ if ($hasProblems) {
+ $filesWithProblems[] = $filePath;
+ }
+ }
+ }
+
+ return $filesWithProblems;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/testInstagramPhoto.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/testInstagramPhoto.php
new file mode 100755
index 0000000..c0e9bb0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/devtools/testInstagramPhoto.php
@@ -0,0 +1,66 @@
+getFile(), $outputFile)) {
+ throw new \RuntimeException(sprintf('Failed to write to "%s".', $outputFile));
+ }
+}
+
+// Ensure that we have the JPEG orientation test images.
+$testImageFolder = __DIR__.'/exif-orientation-examples/';
+if (!is_dir($testImageFolder)) {
+ echo "* Downloading test images:\n";
+ exec('git clone https://github.com/recurser/exif-orientation-examples.git '.escapeshellarg($testImageFolder));
+}
+
+// Process all of the test images.
+$files = [];
+foreach (['Landscape', 'Portrait'] as $orientation) {
+ for ($imgNum = 1; $imgNum <= 8; ++$imgNum) {
+ $fileName = sprintf('%s_%d.jpg', $orientation, $imgNum);
+ $inputFile = sprintf('%s/%s', $testImageFolder, $fileName);
+ if (!is_file($inputFile)) {
+ die('Error: Missing "'.$inputFile.'".');
+ }
+
+ // Run the cropping test...
+ $outputFile = sprintf('%s/result_crop_%s', $testImageFolder, $fileName);
+ runTest($inputFile, $outputFile, [
+ 'forceAspectRatio' => 1.0, // Square.
+ 'horCropFocus' => -35, // Always use the same focus, to look for orientation errors.
+ 'verCropFocus' => -35, // This combo aims at the upper left corner.
+ 'operation' => InstagramMedia::CROP,
+ ]);
+
+ // Run the expansion test...
+ $outputFile = sprintf('%s/result_expand_%s', $testImageFolder, $fileName);
+ runTest($inputFile, $outputFile, [
+ 'forceAspectRatio' => 1.0, // Square.
+ 'operation' => InstagramMedia::EXPAND,
+ ]);
+ }
+}
+
+echo "\n\nAll images have been processed. Manually review the results to ensure they're all correctly processed.\n";
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/README.md b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/README.md
new file mode 100755
index 0000000..ce3d447
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/README.md
@@ -0,0 +1,17 @@
+## Examples
+
+These examples demonstrate various common tasks and are meant to get you started
+with this library.
+
+Many of these files contain a configuration section at the top, which you must
+edit to provide your own account and media details before running the example.
+
+## WARNING, PLEASE READ!
+
+If you are viewing this examples-folder via the web, they may not work on the
+code version that you have downloaded, since development may evolve quickly in
+the development version of the code tree. Especially if you've installed the
+default, stable version of the library, which can often be severely out of date.
+
+Look in _your own_ local disk's `vendor/mgp25/instagram-php/examples` folder for
+the examples that shipped with the exact library version you have installed!
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/accessingValues.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/accessingValues.php
new file mode 100755
index 0000000..170bf58
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/accessingValues.php
@@ -0,0 +1,74 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ $feed = $ig->discover->getExploreFeed();
+
+ // Let's begin by looking at a beautiful debug output of what's available in
+ // the response! This is very helpful for figuring out what a response has!
+ $feed->printJson();
+
+ // Now let's look at what properties are supported on the $feed object. This
+ // works on ANY object from our library, and will show what functions and
+ // properties are supported, as well as how to call the functions! :-)
+ $feed->printPropertyDescriptions();
+
+ // The getExploreFeed() has an "items" property, which we need. As we saw
+ // above, we should get it via "getItems()". The property list above told us
+ // that it will return an array of "Item" objects. Therefore it's an ARRAY!
+ $items = $feed->getItems();
+
+ // Let's get the media item from the first item of the explore-items array...!
+ $firstItem = $items[0]->getMedia();
+
+ // We can look at that item too, if we want to... Let's do it! Note that
+ // when we list supported properties, it shows everything supported by an
+ // "Item" object. But that DOESN'T mean that every property IS available!
+ // That's why you should always check the JSON to be sure that data exists!
+ $firstItem->printJson(); // Shows its actual JSON contents (available data).
+ $firstItem->printPropertyDescriptions(); // List of supported properties.
+
+ // Let's look specifically at its User object!
+ $firstItem->getUser()->printJson();
+
+ // Okay, so the username of the person who posted the media is easy... And
+ // as you can see, you can even chain multiple function calls in a row here
+ // to get to the data. However, be aware that sometimes Instagram responses
+ // have NULL values, so chaining is sometimes risky. But not in this case,
+ // since we know that "user" and its "username" are always available! :-)
+ $firstItem_username = $firstItem->getUser()->getUsername();
+
+ // Now let's get the "id" of the item too!
+ $firstItem_mediaId = $firstItem->getId();
+
+ // Finally, let's get the highest-quality image URL for the media item!
+ $firstItem_imageUrl = $firstItem->getImageVersions2()->getCandidates()[0]->getUrl();
+
+ // Output some statistics. Well done! :-)
+ echo 'There are '.count($items)." items.\n";
+ echo "The first item has media id: {$firstItem_mediaId}.\n";
+ echo "The first item was uploaded by: {$firstItem_username}.\n";
+ echo "The highest quality image URL is: {$firstItem_imageUrl}.\n";
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/assets/instagram.png b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/assets/instagram.png
new file mode 100755
index 0000000..fc311c0
Binary files /dev/null and b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/assets/instagram.png differ
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/customSettings.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/customSettings.php
new file mode 100755
index 0000000..fdb6f19
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/customSettings.php
@@ -0,0 +1,147 @@
+ 'mysql',
+]);
+
+// 2. You can read src/Settings/Factory.php for valid settings for each backend.
+// Here's an example of how to change the default storage location for "file":
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug, [
+ 'storage' => 'file',
+ 'basefolder' => 'some/path/',
+]);
+
+// 3. And here's an example of how to change the default database filename and
+// the default database table name for the "sqlite" backend:
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug, [
+ 'storage' => 'sqlite',
+ 'dbfilename' => 'some/path/foo.db',
+ 'dbtablename' => 'mysettings',
+]);
+
+// 4. If you read src/Settings/Factory.php, you'll notice that you can choose
+// the storage backends and most of their parameters via the command line or
+// environment variables instead. For example: "SETTINGS_STORAGE=mysql php
+// yourscript.php" would set the "storage" parameter via the environment, and
+// typing "php yourscript.php --settings_storage=mysql" would set it via the
+// command line. The command-line arguments have the highest precedence, then
+// the environment variables, and lastly the code within your script. This
+// precedence order is so that you can easily override your script's code to
+// test other backends or change their parameters without modifying your code.
+
+// 5. Very advanced users can look in src/Settings/StorageHandler.php to read
+// about hasUser(), moveUser() and deleteUser(). Three very, VERY DANGEROUS
+// commands which let you rename or delete account settings in your storage.
+// Carefully read through their descriptions and use them wisely. If you're sure
+// that you dare to use them, then you can access them via $ig->settings->...
+
+// 6. Yet another super advanced topic is the ability to copy data between
+// backends. For example if you want to move all of your data from a File
+// backend to a database, or vice versa. That kind of action is not supported
+// natively by us, but you CAN do it by directly interfacing with the storages!
+
+// First, you MUST manually build a list of all users you want to migrate.
+// You can either hardcode this list. Or get it via something like a directory
+// scan (to look at existing folders in a File backend storage path), or a
+// database query to get all "username" values from the old database. If you're
+// using a database, you will have to connect to it manually and query it
+// yourself! There's no way to do it automatically! Just build this array any
+// way you want to do it!
+$migrateUsers = [
+ 'someuser',
+ 'another.user',
+ 'very_awesome_user123',
+];
+
+// Secondly, you must connect to your old and new storages. These are just
+// example values. The array format is the exact same as what's given to the
+// `Instagram()` constructor! And if you give an empty array, you'll use the
+// same default File backend that the main class uses! So if you want to migrate
+// from that, you should just set oldStorage to `createHandler([])`!
+$oldStorage = \InstagramAPI\Settings\Factory::createHandler([
+ 'storage' => 'sqlite',
+ 'dbfilename' => 'app/instagram.sqlite',
+ 'dbtablename' => 'instagram_sessions',
+]);
+$newStorage = \InstagramAPI\Settings\Factory::createHandler([
+ 'storage' => 'file',
+ 'basefolder' => 'some/path/',
+]);
+
+// Now just run the migration process. This will copy all cookies and settings
+// from the old storage to the new storage, for all of the "migrateUsers".
+foreach ($migrateUsers as $user) {
+ if (!$oldStorage->hasUser($user)) {
+ die("Unable to migrate '{$user}' from old storage (user doesn't exist).\n");
+ }
+
+ echo "Migrating '{$user}'.\n";
+
+ $oldStorage->setActiveUser($user);
+ $newStorage->setActiveUser($user);
+
+ $newStorage->setCookies((string) $oldStorage->getCookies());
+ foreach (\InstagramAPI\Settings\StorageHandler::PERSISTENT_KEYS as $key) {
+ $newStorage->set($key, (string) $oldStorage->get($key));
+ }
+}
+
+// 7. Lastly... if you want to implement your own completely CUSTOM STORAGE,
+// then you simply have to do one thing: Implement the StorageInterface class
+// interface. But be very sure to STRICTLY follow ALL rules for storage backends
+// described in that interface's docs, otherwise your custom backend WON'T work.
+//
+// See the overview in src/Settings/StorageInterface.php, and then read through
+// the various built-in storage backends in src/Settings/Storage/ to see perfect
+// implementations that completely follow the required interface specification.
+//
+// Also note that PDO-based backends should be derived from our "PDOStorage"
+// storage sub-component, so that the logic is perfectly implemented and not
+// duplicated. That's exactly how our "sqlite" and "mysql" PDO backends work!
+//
+// To use your custom storage backend, you would simply create your own class
+// similar to the built-in backends. But do NOT put your own class in our
+// src/Settings/Storage/ folder. Store your class inside your own project.
+//
+// Then simply provide your custom storage class instance as the storage class:
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug, [
+ 'storage' => 'custom',
+ 'class' => new MyCustomStorage(), // Whatever you've named your class.
+]);
+
+// That's it! This should get you started on your journey. :-)
+
+// And please think about contributing your WELL-WRITTEN storage backends to
+// this project! If you had a reason to write your own, then there's probably
+// someone else out there with the same need. Remember to SHARE with the open
+// source community! ;-)
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/exceptionDetailsExample.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/exceptionDetailsExample.php
new file mode 100755
index 0000000..57ae4a2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/exceptionDetailsExample.php
@@ -0,0 +1,83 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+/*
+ * The code below demonstrates how to get the full server response from an
+ * InstagramException object (that's the ONLY type of exception which can
+ * have an Instagram server response attached to it).
+ *
+ * All exceptions thrown by our library are derived from InstagramException.
+ * And MOST of them (but NOT ALL) will have a server response object. So it's
+ * important that you check hasResponse() before trying to use the response.
+ */
+
+// Example 1: Catching EVERYTHING derived from InstagramException so that you
+// can look at the server response (IF one is available).
+
+try {
+ $ig->media->delete('123456'); // Invalid media ID, to trigger a server error.
+} catch (\InstagramAPI\Exception\InstagramException $e) {
+ echo 'Something went wrong (InstagramException): '.$e->getMessage()."\n";
+
+ if ($e->hasResponse()) { // <-- VERY IMPORTANT TO CHECK FIRST!
+ echo "The current exception contains a full server response:\n";
+ $e->getResponse()->printJson();
+ echo "Showing the 'did_delete' and 'message' values of the response:\n";
+ var_dump($e->getResponse()->getDidDelete());
+ var_dump($e->getResponse()->getMessage());
+ }
+}
+
+// Example 2: Catching the SPECIFIC exception you want (which must be derived
+// from InstagramException, otherwise it won't have hasResponse()/getResponse()).
+
+try {
+ $ig->media->delete('123456'); // Invalid media ID, to trigger a server error.
+} catch (\InstagramAPI\Exception\EndpointException $e) {
+ echo 'Something went wrong (EndpointException): '.$e->getMessage()."\n";
+
+ if ($e->hasResponse()) { // <-- VERY IMPORTANT TO CHECK FIRST!
+ echo "The current exception contains a full server response:\n";
+ $e->getResponse()->printJson();
+ echo "Showing the 'did_delete' and 'message' values of the response:\n";
+ var_dump($e->getResponse()->getDidDelete());
+ var_dump($e->getResponse()->getMessage());
+ }
+
+ // Bonus: This shows how to look for specific "error reason" messages in an
+ // EndpointException. There are hundreds or even thousands of different
+ // error messages and we won't add exceptions for them (that would be a
+ // nightmare to maintain). Instead, it's up to the library users to check
+ // for the per-endpoint error messages they care about. Such as this one:
+ if (strpos($e->getMessage(), 'Could not delete') !== false) {
+ echo "* The problem in this generic EndpointException was that Instagram could not delete the media!\n";
+ }
+}
+
+// With this knowledge, you can now go out and catch the exact exceptions you
+// want, and look at specific server details in the reply whenever necessary!
+// Note that the easiest way to handle the generic EndpointException (which is
+// triggered when any of the generic functions fail) is to do a string search in
+// $e->getMessage() to look for a specific error message to know what type of
+// error it was. And if you need more details about the error, you can THEN look
+// at $e->getResponse() to see the contents of the actual server reply.
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/liveBroadcast.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/liveBroadcast.php
new file mode 100755
index 0000000..446c1d8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/liveBroadcast.php
@@ -0,0 +1,137 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // NOTE: This code will create a broadcast, which will give us an RTMP url
+ // where we are supposed to stream-upload the media we want to broadcast.
+ //
+ // The following code is using FFMPEG to broadcast, although other
+ // alternatives are valid too, like OBS (Open Broadcaster Software,
+ // https://obsproject.com).
+ //
+ // For more information on FFMPEG, see:
+ // https://github.com/mgp25/Instagram-API/issues/1488#issuecomment-324271177
+ // and for OBS, see:
+ // https://github.com/mgp25/Instagram-API/issues/1488#issuecomment-333365636
+
+ // Get FFmpeg handler and ensure that the application exists on this system.
+ // NOTE: You can supply custom path to the ffmpeg binary, or just leave NULL
+ // to autodetect it.
+ $ffmpegPath = null;
+ $ffmpeg = \InstagramAPI\Media\Video\FFmpeg::factory($ffmpegPath);
+
+ // Tell Instagram that we want to perform a livestream.
+ $stream = $ig->live->create();
+ $broadcastId = $stream->getBroadcastId();
+ $ig->live->start($broadcastId);
+
+ $streamUploadUrl = $stream->getUploadUrl();
+
+ // Broadcast the entire video file.
+ // NOTE: The video is broadcasted asynchronously (in the background).
+ $broadcastProcess = $ffmpeg->runAsync(sprintf(
+ '-rtbufsize 256M -re -i %s -acodec libmp3lame -ar 44100 -b:a 128k -pix_fmt yuv420p -profile:v baseline -s 720x1280 -bufsize 6000k -vb 400k -maxrate 1500k -deinterlace -vcodec libx264 -preset veryfast -g 30 -r 30 -f flv %s',
+ \Winbox\Args::escape($videoFilename),
+ \Winbox\Args::escape($streamUploadUrl)
+ ));
+
+ // The following loop performs important requests to obtain information
+ // about the broadcast while it is ongoing.
+ // NOTE: This is REQUIRED if you want the comments and likes to appear
+ // in your saved post-live feed.
+ // NOTE: These requests are sent *while* the video is being broadcasted.
+ $lastCommentTs = 0;
+ $lastLikeTs = 0;
+ do {
+ // Get broadcast comments.
+ // - The latest comment timestamp will be required for the next
+ // getComments() request.
+ // - There are two types of comments: System comments and user comments.
+ // We compare both and keep the newest (most recent) timestamp.
+ $commentsResponse = $ig->live->getComments($broadcastId, $lastCommentTs);
+ $systemComments = $commentsResponse->getSystemComments();
+ $comments = $commentsResponse->getComments();
+ if (!empty($systemComments)) {
+ $lastCommentTs = $systemComments[0]->getCreatedAt();
+ }
+ if (!empty($comments) && $comments[0]->getCreatedAt() > $lastCommentTs) {
+ $lastCommentTs = $comments[0]->getCreatedAt();
+ }
+
+ // Get broadcast heartbeat and viewer count.
+ $heartbeatResponse = $ig->live->getHeartbeatAndViewerCount($broadcastId);
+
+ // Check to see if the livestream has been flagged for a policy violation.
+ if ($heartbeatResponse->isIsPolicyViolation() && (int) $heartbeatResponse->getIsPolicyViolation() === 1) {
+ echo 'Instagram has flagged your content as a policy violation with the following reason: '.($heartbeatResponse->getPolicyViolationReason() == null ? 'Unknown' : $heartbeatResponse->getPolicyViolationReason())."\n";
+ // Change this to false if disagree with the policy violation and would would like to continue streaming.
+ // - Note: In this example, the violation is always accepted.
+ // In your use case, you may want to prompt the user if
+ // they would like to accept or refute the policy violation.
+ if (true) {
+ // Get the final viewer list of the broadcast.
+ $ig->live->getFinalViewerList($broadcastId);
+ // End the broadcast stream while acknowledging the copyright warning given.
+ $ig->live->end($broadcastId, true);
+ exit(0);
+ }
+ // Acknowledges the copyright warning and allows you to continue streaming.
+ // - Note: This may allow the copyright holder to view your livestream
+ // regardless of your account privacy or if you archive it.
+ $ig->live->resumeBroadcastAfterContentMatch($broadcastId);
+ }
+
+ // Get broadcast like count.
+ // - The latest like timestamp will be required for the next
+ // getLikeCount() request.
+ $likeCountResponse = $ig->live->getLikeCount($broadcastId, $lastLikeTs);
+ $lastLikeTs = $likeCountResponse->getLikeTs();
+
+ // Get the join request counts.
+ // - This doesn't add support for live-with-friends. Rather,
+ // this is only here to emulate the app's livestream flow.
+ $ig->live->getJoinRequestCounts($broadcastId);
+
+ sleep(2);
+ } while ($broadcastProcess->isRunning());
+
+ // Get the final viewer list of the broadcast.
+ // NOTE: You should only use this after the broadcast has stopped uploading.
+ $ig->live->getFinalViewerList($broadcastId);
+
+ // End the broadcast stream.
+ // NOTE: Instagram will ALSO end the stream if your broadcasting software
+ // itself sends a RTMP signal to end the stream. FFmpeg doesn't do that
+ // (without patching), but OBS sends such a packet. So be aware of that.
+ $ig->live->end($broadcastId);
+
+ // Once the broadcast has ended, you can optionally add the finished
+ // broadcast to your post-live feed (saved replay).
+ $ig->live->addToPostLive($broadcastId);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/paginateWithExclusion.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/paginateWithExclusion.php
new file mode 100755
index 0000000..7bf76d2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/paginateWithExclusion.php
@@ -0,0 +1,57 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Let's list locations that match "milan".
+ $query = 'milan';
+
+ // Initialize the state.
+ $rankToken = null;
+ $excludeList = [];
+ do {
+ // Request the page.
+ $response = $ig->location->findPlaces($query, $excludeList, $rankToken);
+
+ // In this example we're simply printing the IDs of this page's items.
+ foreach ($response->getItems() as $item) {
+ $location = $item->getLocation();
+ // Add the item ID to the exclusion list, to tell Instagram's server
+ // to skip that item on the next pagination request.
+ $excludeList[] = $location->getFacebookPlacesId();
+ // Let's print some details about the item.
+ printf("%s (%.3f, %.3f)\n", $item->getTitle(), $location->getLat(), $location->getLng());
+ }
+
+ // Now we must update the rankToken variable.
+ $rankToken = $response->getRankToken();
+
+ // Sleep for 5 seconds before requesting the next page. This is just an
+ // example of an okay sleep time. It is very important that your scripts
+ // always pause between requests that may run very rapidly, otherwise
+ // Instagram will throttle you temporarily for abusing their API!
+ echo "Sleeping for 5s...\n";
+ sleep(5);
+ } while ($response->getHasMore());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/paginationExample.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/paginationExample.php
new file mode 100755
index 0000000..0dd3f56
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/paginationExample.php
@@ -0,0 +1,53 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Get the UserPK ID for "natgeo" (National Geographic).
+ $userId = $ig->people->getUserIdForName('natgeo');
+
+ // Starting at "null" means starting at the first page.
+ $maxId = null;
+ do {
+ // Request the page corresponding to maxId.
+ $response = $ig->timeline->getUserFeed($userId, $maxId);
+
+ // In this example we're simply printing the IDs of this page's items.
+ foreach ($response->getItems() as $item) {
+ printf("[%s] https://instagram.com/p/%s/\n", $item->getId(), $item->getCode());
+ }
+
+ // Now we must update the maxId variable to the "next page".
+ // This will be a null value again when we've reached the last page!
+ // And we will stop looping through pages as soon as maxId becomes null.
+ $maxId = $response->getNextMaxId();
+
+ // Sleep for 5 seconds before requesting the next page. This is just an
+ // example of an okay sleep time. It is very important that your scripts
+ // always pause between requests that may run very rapidly, otherwise
+ // Instagram will throttle you temporarily for abusing their API!
+ echo "Sleeping for 5s...\n";
+ sleep(5);
+ } while ($maxId !== null); // Must use "!==" for comparison instead of "!=".
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/pushReceiver.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/pushReceiver.php
new file mode 100755
index 0000000..4bf8980
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/pushReceiver.php
@@ -0,0 +1,80 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+$loop = \React\EventLoop\Factory::create();
+if ($debug) {
+ $logger = new \Monolog\Logger('push');
+ $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+} else {
+ $logger = null;
+}
+$push = new \InstagramAPI\Push($loop, $ig, $logger);
+$push->on('incoming', function (\InstagramAPI\Push\Notification $push) {
+ printf('%s%s', $push->getMessage(), PHP_EOL);
+});
+$push->on('like', function (\InstagramAPI\Push\Notification $push) {
+ printf('Media ID: %s%s', $push->getActionParam('id'), PHP_EOL);
+});
+$push->on('comment', function (\InstagramAPI\Push\Notification $push) {
+ switch ($push->getActionPath()) {
+ case 'comments_v2':
+ $mediaId = $push->getActionParam('media_id');
+ $commentId = $push->getActionParam('target_comment_id');
+ break;
+ case 'media':
+ default:
+ $mediaId = $push->getActionParam('id');
+ $commentId = $push->getActionParam('forced_preview_comment_id');
+ }
+ printf(
+ 'Media ID: %s. Comment ID: %s.%s',
+ $mediaId,
+ $commentId,
+ PHP_EOL
+ );
+});
+$push->on('direct_v2_message', function (\InstagramAPI\Push\Notification $push) {
+ printf(
+ 'Thread ID: %s. Thread item ID: %s.%s',
+ $push->getActionParam('id'),
+ $push->getActionParam('x'),
+ PHP_EOL
+ );
+});
+$push->on('error', function (\Exception $e) use ($push, $loop) {
+ printf('[!!!] Got fatal error from FBNS: %s%s', $e->getMessage(), PHP_EOL);
+ $push->stop();
+ $loop->stop();
+});
+$push->start();
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/rankTokenUsage.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/rankTokenUsage.php
new file mode 100755
index 0000000..5321eee
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/rankTokenUsage.php
@@ -0,0 +1,57 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Let's list media for the "nature" hashtag.
+ $tag = 'nature';
+
+ // Generate a random rank token.
+ $rankToken = \InstagramAPI\Signatures::generateUUID();
+
+ // Starting at "null" means starting at the first page.
+ $maxId = null;
+ do {
+ // Request the page corresponding to maxId.
+ // Note that we are using the same rank token for all pages.
+ $response = $ig->hashtag->getFeed($tag, $rankToken, $maxId);
+
+ // In this example we're simply printing the IDs of this page's items.
+ foreach ($response->getItems() as $item) {
+ printf("[%s] https://instagram.com/p/%s/\n", $item->getId(), $item->getCode());
+ }
+
+ // Now we must update the maxId variable to the "next page".
+ // This will be a null value again when we've reached the last page!
+ // And we will stop looping through pages as soon as maxId becomes null.
+ $maxId = $response->getNextMaxId();
+
+ // Sleep for 5 seconds before requesting the next page. This is just an
+ // example of an okay sleep time. It is very important that your scripts
+ // always pause between requests that may run very rapidly, otherwise
+ // Instagram will throttle you temporarily for abusing their API!
+ echo "Sleeping for 5s...\n";
+ sleep(5);
+ } while ($maxId !== null); // Must use "!==" for comparison instead of "!=".
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/realtimeClient.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/realtimeClient.php
new file mode 100755
index 0000000..5abdf06
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/realtimeClient.php
@@ -0,0 +1,100 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+$loop = \React\EventLoop\Factory::create();
+if ($debug) {
+ $logger = new \Monolog\Logger('rtc');
+ $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+} else {
+ $logger = null;
+}
+$rtc = new \InstagramAPI\Realtime($ig, $loop, $logger);
+$rtc->on('live-started', function (\InstagramAPI\Realtime\Payload\LiveBroadcast $live) {
+ printf('[RTC] Live broadcast %s has been started%s', $live->getBroadcastId(), PHP_EOL);
+});
+$rtc->on('live-stopped', function (\InstagramAPI\Realtime\Payload\LiveBroadcast $live) {
+ printf('[RTC] Live broadcast %s has been stopped%s', $live->getBroadcastId(), PHP_EOL);
+});
+$rtc->on('direct-story-created', function (\InstagramAPI\Response\Model\DirectThread $thread) {
+ printf('[RTC] Story %s has been created%s', $thread->getThreadId(), PHP_EOL);
+});
+$rtc->on('direct-story-updated', function ($threadId, $threadItemId, \InstagramAPI\Response\Model\DirectThreadItem $threadItem) {
+ printf('[RTC] Item %s has been created in story %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('direct-story-screenshot', function ($threadId, \InstagramAPI\Realtime\Payload\StoryScreenshot $screenshot) {
+ printf('[RTC] %s has taken screenshot of story %s%s', $screenshot->getActionUserDict()->getUsername(), $threadId, PHP_EOL);
+});
+$rtc->on('direct-story-action', function ($threadId, \InstagramAPI\Response\Model\ActionBadge $storyAction) {
+ printf('[RTC] Story in thread %s has badge %s now%s', $threadId, $storyAction->getActionType(), PHP_EOL);
+});
+$rtc->on('thread-created', function ($threadId, \InstagramAPI\Response\Model\DirectThread $thread) {
+ printf('[RTC] Thread %s has been created%s', $threadId, PHP_EOL);
+});
+$rtc->on('thread-updated', function ($threadId, \InstagramAPI\Response\Model\DirectThread $thread) {
+ printf('[RTC] Thread %s has been updated%s', $threadId, PHP_EOL);
+});
+$rtc->on('thread-notify', function ($threadId, $threadItemId, \InstagramAPI\Realtime\Payload\ThreadAction $notify) {
+ printf('[RTC] Thread %s has notification from %s%s', $threadId, $notify->getUserId(), PHP_EOL);
+});
+$rtc->on('thread-seen', function ($threadId, $userId, \InstagramAPI\Response\Model\DirectThreadLastSeenAt $seenAt) {
+ printf('[RTC] Thread %s has been checked by %s%s', $threadId, $userId, PHP_EOL);
+});
+$rtc->on('thread-activity', function ($threadId, \InstagramAPI\Realtime\Payload\ThreadActivity $activity) {
+ printf('[RTC] Thread %s has some activity made by %s%s', $threadId, $activity->getSenderId(), PHP_EOL);
+});
+$rtc->on('thread-item-created', function ($threadId, $threadItemId, \InstagramAPI\Response\Model\DirectThreadItem $threadItem) {
+ printf('[RTC] Item %s has been created in thread %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('thread-item-updated', function ($threadId, $threadItemId, \InstagramAPI\Response\Model\DirectThreadItem $threadItem) {
+ printf('[RTC] Item %s has been updated in thread %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('thread-item-removed', function ($threadId, $threadItemId) {
+ printf('[RTC] Item %s has been removed from thread %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('client-context-ack', function (\InstagramAPI\Realtime\Payload\Action\AckAction $ack) {
+ printf('[RTC] Received ACK for %s with status %s%s', $ack->getPayload()->getClientContext(), $ack->getStatus(), PHP_EOL);
+});
+$rtc->on('unseen-count-update', function ($inbox, \InstagramAPI\Response\Model\DirectSeenItemPayload $payload) {
+ printf('[RTC] Updating unseen count in %s to %d%s', $inbox, $payload->getCount(), PHP_EOL);
+});
+$rtc->on('presence', function (\InstagramAPI\Response\Model\UserPresence $presence) {
+ $action = $presence->getIsActive() ? 'is now using' : 'just closed';
+ printf('[RTC] User %s %s one of Instagram apps%s', $presence->getUserId(), $action, PHP_EOL);
+});
+$rtc->on('error', function (\Exception $e) use ($rtc, $loop) {
+ printf('[!!!] Got fatal error from Realtime: %s%s', $e->getMessage(), PHP_EOL);
+ $rtc->stop();
+ $loop->stop();
+});
+$rtc->start();
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/realtimeHttp.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/realtimeHttp.php
new file mode 100755
index 0000000..2478262
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/realtimeHttp.php
@@ -0,0 +1,287 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// Create main event loop.
+$loop = \React\EventLoop\Factory::create();
+if ($debug) {
+ $logger = new \Monolog\Logger('rtc');
+ $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+} else {
+ $logger = null;
+}
+// Create HTTP server along with Realtime client.
+$httpServer = new RealtimeHttpServer($loop, $ig, $logger);
+// Run main loop.
+$loop->run();
+
+class RealtimeHttpServer
+{
+ const HOST = '127.0.0.1';
+ const PORT = 1307;
+
+ const TIMEOUT = 5;
+
+ /** @var \React\Promise\Deferred[] */
+ protected $_contexts;
+
+ /** @var \React\EventLoop\LoopInterface */
+ protected $_loop;
+
+ /** @var \InstagramAPI\Instagram */
+ protected $_instagram;
+
+ /** @var \InstagramAPI\Realtime */
+ protected $_rtc;
+
+ /** @var \React\Http\Server */
+ protected $_server;
+
+ /** @var \Psr\Log\LoggerInterface */
+ protected $_logger;
+
+ /**
+ * Constructor.
+ *
+ * @param \React\EventLoop\LoopInterface $loop
+ * @param \InstagramAPI\Instagram $instagram
+ * @param \Psr\Log\LoggerInterface|null $logger
+ */
+ public function __construct(
+ \React\EventLoop\LoopInterface $loop,
+ \InstagramAPI\Instagram $instagram,
+ \Psr\Log\LoggerInterface $logger = null)
+ {
+ $this->_loop = $loop;
+ $this->_instagram = $instagram;
+ if ($logger === null) {
+ $logger = new \Psr\Log\NullLogger();
+ }
+ $this->_logger = $logger;
+ $this->_contexts = [];
+ $this->_rtc = new \InstagramAPI\Realtime($this->_instagram, $this->_loop, $this->_logger);
+ $this->_rtc->on('client-context-ack', [$this, 'onClientContextAck']);
+ $this->_rtc->on('error', [$this, 'onRealtimeFail']);
+ $this->_rtc->start();
+ $this->_startHttpServer();
+ }
+
+ /**
+ * Gracefully stop everything.
+ */
+ protected function _stop()
+ {
+ // Initiate shutdown sequence.
+ $this->_rtc->stop();
+ // Wait 2 seconds for Realtime to shutdown.
+ $this->_loop->addTimer(2, function () {
+ // Stop main loop.
+ $this->_loop->stop();
+ });
+ }
+
+ /**
+ * Called when fatal error has been received from Realtime.
+ *
+ * @param \Exception $e
+ */
+ public function onRealtimeFail(
+ \Exception $e)
+ {
+ $this->_logger->error((string) $e);
+ $this->_stop();
+ }
+
+ /**
+ * Called when ACK has been received.
+ *
+ * @param \InstagramAPI\Realtime\Payload\Action\AckAction $ack
+ */
+ public function onClientContextAck(
+ \InstagramAPI\Realtime\Payload\Action\AckAction $ack)
+ {
+ $context = $ack->getPayload()->getClientContext();
+ $this->_logger->info(sprintf('Received ACK for %s with status %s', $context, $ack->getStatus()));
+ // Check if we have deferred object for this client_context.
+ if (!isset($this->_contexts[$context])) {
+ return;
+ }
+ // Resolve deferred object with $ack.
+ $deferred = $this->_contexts[$context];
+ $deferred->resolve($ack);
+ // Clean up.
+ unset($this->_contexts[$context]);
+ }
+
+ /**
+ * @param string|bool $context
+ *
+ * @return \React\Http\Response|\React\Promise\PromiseInterface
+ */
+ protected function _handleClientContext(
+ $context)
+ {
+ // Reply with 503 Service Unavailable.
+ if ($context === false) {
+ return new \React\Http\Response(503);
+ }
+ // Set up deferred object.
+ $deferred = new \React\Promise\Deferred();
+ $this->_contexts[$context] = $deferred;
+ // Reject deferred after given timeout.
+ $timeout = $this->_loop->addTimer(self::TIMEOUT, function () use ($deferred, $context) {
+ $deferred->reject();
+ unset($this->_contexts[$context]);
+ });
+ // Set up promise.
+ return $deferred->promise()
+ ->then(function (\InstagramAPI\Realtime\Payload\Action\AckAction $ack) use ($timeout) {
+ // Cancel reject timer.
+ $timeout->cancel();
+ // Reply with info from $ack.
+ return new \React\Http\Response($ack->getStatusCode(), ['Content-Type' => 'text/json'], $ack->getPayload()->asJson());
+ })
+ ->otherwise(function () {
+ // Called by reject timer. Reply with 504 Gateway Time-out.
+ return new \React\Http\Response(504);
+ });
+ }
+
+ /**
+ * Handler for incoming HTTP requests.
+ *
+ * @param \Psr\Http\Message\ServerRequestInterface $request
+ *
+ * @return \React\Http\Response|\React\Promise\PromiseInterface
+ */
+ public function onHttpRequest(
+ \Psr\Http\Message\ServerRequestInterface $request)
+ {
+ // Treat request path as command.
+ $command = $request->getUri()->getPath();
+ // Params validation is up to you.
+ $params = $request->getQueryParams();
+ // Log command with its params.
+ $this->_logger->info(sprintf('Received command %s', $command), $params);
+ switch ($command) {
+ case '/ping':
+ return new \React\Http\Response(200, [], 'pong');
+ case '/stop':
+ $this->_stop();
+
+ return new \React\Http\Response(200);
+ case '/seen':
+ $context = $this->_rtc->markDirectItemSeen($params['threadId'], $params['threadItemId']);
+
+ return new \React\Http\Response($context !== false ? 200 : 503);
+ case '/activity':
+ return $this->_handleClientContext($this->_rtc->indicateActivityInDirectThread($params['threadId'], (bool) $params['flag']));
+ case '/message':
+ return $this->_handleClientContext($this->_rtc->sendTextToDirect($params['threadId'], $params['text']));
+ case '/post':
+ return $this->_handleClientContext($this->_rtc->sendPostToDirect($params['threadId'], $params['mediaId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/story':
+ return $this->_handleClientContext($this->_rtc->sendStoryToDirect($params['threadId'], $params['storyId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/profile':
+ return $this->_handleClientContext($this->_rtc->sendProfileToDirect($params['threadId'], $params['userId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/location':
+ return $this->_handleClientContext($this->_rtc->sendLocationToDirect($params['threadId'], $params['locationId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/hashtag':
+ return $this->_handleClientContext($this->_rtc->sendHashtagToDirect($params['threadId'], $params['hashtag'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/like':
+ return $this->_handleClientContext($this->_rtc->sendLikeToDirect($params['threadId']));
+ case '/likeItem':
+ return $this->_handleClientContext($this->_rtc->sendReactionToDirect($params['threadId'], $params['threadItemId'], 'like'));
+ case '/unlikeItem':
+ return $this->_handleClientContext($this->_rtc->deleteReactionFromDirect($params['threadId'], $params['threadItemId'], 'like'));
+ default:
+ $this->_logger->warning(sprintf('Unknown command %s', $command), $params);
+ // If command is unknown, reply with 404 Not Found.
+ return new \React\Http\Response(404);
+ }
+ }
+
+ /**
+ * Init and start HTTP server.
+ */
+ protected function _startHttpServer()
+ {
+ // Create server socket.
+ $socket = new \React\Socket\Server(self::HOST.':'.self::PORT, $this->_loop);
+ $this->_logger->info(sprintf('Listening on http://%s', $socket->getAddress()));
+ // Bind HTTP server on server socket.
+ $this->_server = new \React\Http\Server([$this, 'onHttpRequest']);
+ $this->_server->listen($socket);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/sharePhotoToStoryFeed.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/sharePhotoToStoryFeed.php
new file mode 100755
index 0000000..6ace2a4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/sharePhotoToStoryFeed.php
@@ -0,0 +1,71 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// NOTE: This code will make the credits of the media area 'clickable', but YOU need to
+// manually draw the credit to the user or a sticker-image on top of your image yourself
+// before uploading, if you want the credit to actually be visible on-screen!
+
+// If we want to attach a media, we must find a valid media_id first.
+try {
+ $mediaInfo = $ig->media->getInfo($mediaId)->getItems()[0];
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
+
+// Now create the metadata array:
+$metadata = [
+ 'attached_media' => [
+ [
+ 'media_id' => $mediaId,
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'width' => 0.8, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.6224662, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+$client = new GuzzleHttp\Client();
+$outputFile = Utils::createTempFile(sys_get_temp_dir(), 'IMG');
+
+try {
+ $response = $client->request('GET', $mediaInfo->getImageVersions2()->getCandidates()[0]->getUrl(), ['sink' => $outputFile]);
+
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories.
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($outputFile, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+} finally {
+ @unlink($outputFile);
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/shortcodeConverter.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/shortcodeConverter.php
new file mode 100755
index 0000000..b700109
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/shortcodeConverter.php
@@ -0,0 +1,29 @@
+ code({$code})\n\n";
+
+// From Shortcode to Media ID.
+$id = InstagramID::fromCode($code);
+echo "Instagram code({$code}) -> id({$id})\n\n";
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/twoFactorLogin.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/twoFactorLogin.php
new file mode 100755
index 0000000..6055ec7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/twoFactorLogin.php
@@ -0,0 +1,31 @@
+login($username, $password);
+
+ if ($loginResponse !== null && $loginResponse->isTwoFactorRequired()) {
+ $twoFactorIdentifier = $loginResponse->getTwoFactorInfo()->getTwoFactorIdentifier();
+
+ // The "STDIN" lets you paste the code via terminal for testing.
+ // You should replace this line with the logic you want.
+ // The verification code will be sent by Instagram via SMS.
+ $verificationCode = trim(fgets(STDIN));
+ $ig->finishTwoFactorLogin($username, $password, $twoFactorIdentifier, $verificationCode);
+ }
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadAlbum.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadAlbum.php
new file mode 100755
index 0000000..12872d2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadAlbum.php
@@ -0,0 +1,109 @@
+ 'photo',
+ 'file' => '', // Path to the photo file.
+ ],
+ [
+ 'type' => 'photo',
+ 'file' => '', // Path to the photo file.
+ /* TAGS COMMENTED OUT UNTIL YOU READ THE BELOW:
+ 'usertags' => [ // Optional, lets you tag one or more users in a PHOTO.
+ [
+ 'position' => [0.5, 0.5],
+
+ // WARNING: THE USER ID MUST BE VALID. INSTAGRAM WILL VERIFY IT
+ // AND IF IT'S WRONG THEY WILL SAY "media configure error".
+ 'user_id' => '123456789', // Must be a numerical UserPK ID.
+ ],
+ ],
+ */
+ ],
+ [
+ 'type' => 'video',
+ 'file' => '', // Path to the video file.
+ 'thumbnail_timestamp' => '', // Timestamp of thumbnail
+ ],
+];
+$captionText = ''; // Caption to use for the album.
+//////////////////////
+
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug);
+
+try {
+ $ig->login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+////// NORMALIZE MEDIA //////
+// All album files must have the same aspect ratio.
+// We copy the app's behavior by using the first file
+// as template for all subsequent ones.
+$mediaOptions = [
+ 'targetFeed' => \InstagramAPI\Constants::FEED_TIMELINE_ALBUM,
+ // Uncomment to expand media instead of cropping it.
+ //'operation' => \InstagramAPI\Media\InstagramMedia::EXPAND,
+];
+foreach ($media as &$item) {
+ /** @var \InstagramAPI\Media\InstagramMedia|null $validMedia */
+ $validMedia = null;
+ switch ($item['type']) {
+ case 'photo':
+ $validMedia = new \InstagramAPI\Media\Photo\InstagramPhoto($item['file'], $mediaOptions);
+ break;
+ case 'video':
+ $validMedia = new \InstagramAPI\Media\Video\InstagramVideo($item['file'], $mediaOptions);
+ break;
+ default:
+ // Ignore unknown media type.
+ }
+ if ($validMedia === null) {
+ continue;
+ }
+
+ try {
+ $item['file'] = $validMedia->getFile();
+ // We must prevent the InstagramMedia object from destructing too early,
+ // because the media class auto-deletes the processed file during their
+ // destructor's cleanup (so we wouldn't be able to upload those files).
+ $item['__media'] = $validMedia; // Save object in an unused array key.
+ } catch (\Exception $e) {
+ continue;
+ }
+ if (!isset($mediaOptions['forceAspectRatio'])) {
+ // Use the first media file's aspect ratio for all subsequent files.
+ /** @var \InstagramAPI\Media\MediaDetails $mediaDetails */
+ $mediaDetails = $validMedia instanceof \InstagramAPI\Media\Photo\InstagramPhoto
+ ? new \InstagramAPI\Media\Photo\PhotoDetails($item['file'])
+ : new \InstagramAPI\Media\Video\VideoDetails($item['file']);
+ $mediaOptions['forceAspectRatio'] = $mediaDetails->getAspectRatio();
+ }
+}
+unset($item);
+/////////////////////////////
+
+try {
+ $ig->timeline->uploadAlbum($media, ['caption' => $captionText]);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadPhoto.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadPhoto.php
new file mode 100755
index 0000000..954d960
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadPhoto.php
@@ -0,0 +1,62 @@
+setProxy("http://$proxyUser:$proxyPassword@$proxyIP:$proxyPort"); // SOCKS5 Proxy needing authentication
+try {
+ $ig->login($username, $password);
+} catch (\Exception $e) {
+ echo 'Error: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // The most basic upload command, if you're sure that your photo file is
+ // valid on Instagram (that it fits all requirements), is the following:
+ // $ig->timeline->uploadPhoto($photoFilename, ['caption' => $captionText]);
+
+ // However, if you want to guarantee that the file is valid (correct format,
+ // width, height and aspect ratio), then you can run it through our
+ // automatic photo processing class. It is pretty fast, and only does any
+ // work when the input file is invalid, so you may want to always use it.
+ // You have nothing to worry about, since the class uses temporary files if
+ // the input needs processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename);
+ $ig->timeline->uploadPhoto($photo->getFile(), ['caption' => $captionText]);
+} catch (\Exception $e) {
+ echo 'Error: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStory.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStory.php
new file mode 100755
index 0000000..4eb24a6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStory.php
@@ -0,0 +1,102 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// You don't have to provide hashtags or locations for your story. It is
+// optional! But we will show you how to do both...
+
+// NOTE: This code will make the hashtag area 'clickable', but YOU need to
+// manually draw the hashtag or a sticker-image on top of your image yourself
+// before uploading, if you want the tag to actually be visible on-screen!
+
+// NOTE: The same thing happens when a location sticker is added. And the
+// "location_sticker" WILL ONLY work if you also add the "location" as shown
+// below.
+
+// NOTE: And "caption" will NOT be visible either! Like all the other story
+// metadata described above, YOU must manually draw the caption on your image.
+
+// If we want to attach a location, we must find a valid Location object first:
+try {
+ $location = $ig->location->search('40.7439862', '-73.998511')->getVenues()[0];
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
+
+// Now create the metadata array:
+$metadata = [
+ // (optional) Captions can always be used, like this:
+ 'caption' => '#test This is a great API!',
+
+ // (optional) To add a hashtag, do this:
+ 'hashtags' => [
+ // Note that you can add more than one hashtag in this array.
+ [
+ 'tag_name' => 'test', // Hashtag WITHOUT the '#'! NOTE: This hashtag MUST appear in the caption.
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'width' => 0.24305555, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.07347973, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => false, // Don't change this value.
+ 'use_custom_title' => false, // Don't change this value.
+ ],
+ // ...
+ ],
+
+ // (optional) To add a location, do BOTH of these:
+ 'location_sticker' => [
+ 'width' => 0.89333333333333331,
+ 'height' => 0.071281859070464776,
+ 'x' => 0.5,
+ 'y' => 0.2,
+ 'rotation' => 0.0,
+ 'is_sticker' => true,
+ 'location_id' => $location->getExternalId(),
+ ],
+ 'location' => $location,
+
+ // (optional) You can use story links ONLY if you have a business account with >= 10k followers.
+ // 'link' => 'https://github.com/mgp25/Instagram-API',
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryCountdown.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryCountdown.php
new file mode 100755
index 0000000..251a70f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryCountdown.php
@@ -0,0 +1,67 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// Now create the metadata array:
+$metadata = [
+ 'story_countdowns' => [
+ [
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'z' => 0, // Don't change this value.
+ 'width' => 0.6564815, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.22630993, // ...
+ 'rotation' => 0.0,
+ 'text' => 'NEXT API RELEASE', // Name of the countdown. Make sure it's in all caps!
+ 'text_color' => '#ffffff',
+ 'start_background_color' => '#ca2ee1',
+ 'end_background_color' => '#5eb1ff',
+ 'digit_color' => '#7e0091',
+ 'digit_card_color' => '#ffffff',
+ 'end_ts' => time() + strtotime('1 day', 0), // UNIX Epoch of when the countdown expires.
+ 'following_enabled' => true, // If true, viewers can subscribe to a notification when the countdown expires.
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryFundraiser.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryFundraiser.php
new file mode 100755
index 0000000..2562453
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryFundraiser.php
@@ -0,0 +1,54 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+//Get the first recommended charity.
+$charityUser = $ig->story->getCharities()->getSearchedCharities()[0];
+
+// Now create the metadata array:
+$metadata = [
+ 'story_fundraisers' => $ig->story->createDonateSticker($charityUser),
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryPoll.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryPoll.php
new file mode 100755
index 0000000..eb348dd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryPoll.php
@@ -0,0 +1,78 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// NOTE: This code will make a story poll sticker with the two answers provided,
+// but YOU need to manually draw the question on top of your image yourself
+// before uploading if you want the question to be visible.
+
+// Now create the metadata array:
+$metadata = [
+ 'story_polls' => [
+ // Note that you can only do one story poll in this array.
+ [
+ 'question' => 'Is this API great?', // Story poll question. You need to manually to draw it on top of your image.
+ 'viewer_vote' => 0, // Don't change this value.
+ 'viewer_can_vote' => true, // Don't change this value.
+ 'tallies' => [
+ [
+ 'text' => 'Best API!', // Answer 1.
+ 'count' => 0, // Don't change this value.
+ 'font_size' => 35.0, // Range: 17.5 - 35.0.
+ ],
+ [
+ 'text' => 'The doubt offends', // Answer 2.
+ 'count' => 0, // Don't change this value.
+ 'font_size' => 27.5, // Range: 17.5 - 35.0.
+ ],
+ ],
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'width' => 0.5661107, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.10647108, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryQuestion.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryQuestion.php
new file mode 100755
index 0000000..ac84854
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStoryQuestion.php
@@ -0,0 +1,69 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// Get the profile picture pic url to display on the question.
+$profilePicUrl = $ig->account->getCurrentUser()->getUser()->getProfilePicUrl();
+
+// Now create the metadata array:
+$metadata = [
+ 'story_questions' => [
+ // Note that you can only do one story question in this array.
+ [
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5004223, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'z' => 0, // Don't change this value.
+ 'width' => 0.63118356, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.22212838, // ...
+ 'rotation' => 0.0,
+ 'viewer_can_interact' => false, // Don't change this value.
+ 'background_color' => '#ffffff',
+ 'profile_pic_url' => $profilePicUrl, // Must be the profile pic url of the account you are posting from!
+ 'question_type' => 'text', // Don't change this value.
+ 'question' => 'What do you want to see in the API?', // Story question.
+ 'text_color' => '#000000',
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStorySlider.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStorySlider.php
new file mode 100755
index 0000000..8465b58
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadStorySlider.php
@@ -0,0 +1,71 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// NOTE: This code will make a story poll sticker with the two answers provided,
+// but YOU need to manually draw the question on top of your image yourself
+// before uploading if you want the question to be visible.
+
+// Now create the metadata array:
+$metadata = [
+ 'story_sliders' => [
+ // Note that you can only do one story poll in this array.
+ [
+ 'question' => 'Is this API great?', // Story poll question. You need to manually to draw it on top of your image.
+ 'viewer_vote' => 0, // Don't change this value.
+ 'viewer_can_vote' => false, // Don't change this value.
+ 'slider_vote_count' => 0, // Don't change this value.
+ 'slider_vote_average' => 0, // Don't change this value.
+ 'background_color' => '#ffffff',
+ 'text_color' => '#000000',
+ 'emoji' => '😍',
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5004223, // Also note that X/Y is setting the position of the CENTER of the clickable area
+ 'width' => 0.7777778, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.22212838, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadVideo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadVideo.php
new file mode 100755
index 0000000..b25fc75
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/examples/uploadVideo.php
@@ -0,0 +1,46 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Note that all video upload functions perform some automatic chunk upload
+ // retries, in case of failing to upload all video chunks to Instagram's
+ // server! Uploads therefore take longer when their server is overloaded.
+
+ // If you want to guarantee that the file is valid (correct format, codecs,
+ // width, height and aspect ratio), then you can run it through our
+ // automatic video processing class. It only does any work when the input
+ // video file is invalid, so you may want to always use it. You have nothing
+ // to worry about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $video = new \InstagramAPI\Media\Video\InstagramVideo($videoFilename);
+ $ig->timeline->uploadVideo($video->getFile(), ['caption' => $captionText]);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/extradocs/Push_setPreferences.txt b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/extradocs/Push_setPreferences.txt
new file mode 100755
index 0000000..3a14ed4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/extradocs/Push_setPreferences.txt
@@ -0,0 +1,90 @@
+===================
+Push::setPreferences
+===================
+
+Description
+------------
+Set push preferences.
+
+Params
+------------
+param array $preferences.
+
+Extra Documentation
+--------------------
+
+Each preference can be set either to 'Off', 'Followings' or 'Everyone'/'Enabled'.
+In this context:
+
+'Off' is 1.
+'Followings' is 2.
+'Everyone' is 3.
+
+** Warning **
+
+You can only set preferences if you are eligible for them. See Push::getPreferences()
+to obtain the values for all of your available preferences.
+
+Preferences and possible options
+---------------------------------
+
+Name: likes.
+Options: 1, 2, 3.
+
+Name: comments.
+Options: 1, 2, 3.
+
+Name: comment_likes.
+Options: 1, 2.
+
+Name: like_and_comment_on_photo_user_tagged.
+Options: 1, 2, 3.
+
+Name: live_broadcast.
+Options: 1, 3.
+
+Name: new_follower.
+Options: 1, 3.
+
+Name: follow_request_accepted.
+Options: 1, 3.
+
+Name: contact_joined.
+Options: 1, 3.
+
+Name: pending_direct_share.
+Options: 1, 3.
+
+Name: direct_share_activity.
+Options: 1, 3.
+
+Name: user_tagged.
+Options: 1, 2, 3.
+
+Name: notification_reminders.
+Options: 1, 3.
+
+Name: first_post.
+Options: 1, 2, 3.
+
+Name: announcements.
+Options: 1, 3.
+
+Name: ads.
+Options: 1, 3.
+
+Name: view_count.
+Options: 1, 3.
+
+Name: report_updated.
+Options: 1, 3.
+
+Example
+---------
+
+$preferences = [
+ 'likes' => 3,
+ 'comment_likes' => 2
+ ];
+
+$ig->push->setPreferences($preferences);
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/AutoPropertyMapper.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/AutoPropertyMapper.php
new file mode 100755
index 0000000..9461f25
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/AutoPropertyMapper.php
@@ -0,0 +1,22 @@
+_parent = $parent;
+
+ // Defaults.
+ $this->_verifySSL = true;
+ $this->_proxy = null;
+
+ // Create a default handler stack with Guzzle's auto-selected "best
+ // possible transfer handler for the user's system", and with all of
+ // Guzzle's default middleware (cookie jar support, etc).
+ $stack = HandlerStack::create();
+
+ // Create our cookies middleware and add it to the stack.
+ $this->_fakeCookies = new FakeCookies();
+ $stack->push($this->_fakeCookies, 'fake_cookies');
+
+ $this->_zeroRating = new ZeroRating();
+ $stack->push($this->_zeroRating, 'zero_rewrite');
+
+ // Default request options (immutable after client creation).
+ $this->_guzzleClient = new GuzzleClient([
+ 'handler' => $stack, // Our middleware is now injected.
+ 'allow_redirects' => [
+ 'max' => 8, // Allow up to eight redirects (that's plenty).
+ ],
+ 'connect_timeout' => 30.0, // Give up trying to connect after 30s.
+ 'decode_content' => true, // Decode gzip/deflate/etc HTTP responses.
+ 'timeout' => 240.0, // Maximum per-request time (seconds).
+ // Tells Guzzle to stop throwing exceptions on non-"2xx" HTTP codes,
+ // thus ensuring that it only triggers exceptions on socket errors!
+ // We'll instead MANUALLY be throwing on certain other HTTP codes.
+ 'http_errors' => false,
+ ]);
+
+ $this->_resetConnection = false;
+ }
+
+ /**
+ * Resets certain Client settings via the current Settings storage.
+ *
+ * Used whenever we switch active user, to configure our internal state.
+ *
+ * @param bool $resetCookieJar (optional) Whether to clear current cookies.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function updateFromCurrentSettings(
+ $resetCookieJar = false)
+ {
+ // Update our internal client state from the new user's settings.
+ $this->_userAgent = $this->_parent->device->getUserAgent();
+ $this->loadCookieJar($resetCookieJar);
+
+ // Verify that the jar contains a non-expired csrftoken for the API
+ // domain. Instagram gives us a 1-year csrftoken whenever we log in.
+ // If it's missing, we're definitely NOT logged in! But even if all of
+ // these checks succeed, the cookie may still not be valid. It's just a
+ // preliminary check to detect definitely-invalid session cookies!
+ if ($this->getToken() === null) {
+ $this->_parent->isMaybeLoggedIn = false;
+ }
+
+ // Load rewrite rules (if any).
+ $this->zeroRating()->update($this->_parent->settings->getRewriteRules());
+ }
+
+ /**
+ * Loads all cookies via the current Settings storage.
+ *
+ * @param bool $resetCookieJar (optional) Whether to clear current cookies.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function loadCookieJar(
+ $resetCookieJar = false)
+ {
+ // Mark any previous cookie jar for garbage collection.
+ $this->_cookieJar = null;
+
+ // Delete all current cookies from the storage if this is a reset.
+ if ($resetCookieJar) {
+ $this->_parent->settings->setCookies('');
+ }
+
+ // Get all cookies for the currently active user.
+ $cookieData = $this->_parent->settings->getCookies();
+
+ // Attempt to restore the cookies, otherwise create a new, empty jar.
+ $restoredCookies = is_string($cookieData) ? @json_decode($cookieData, true) : null;
+ if (!is_array($restoredCookies)) {
+ $restoredCookies = [];
+ }
+
+ // Memory-based cookie jar which must be manually saved later.
+ $this->_cookieJar = new CookieJar(false, $restoredCookies);
+
+ // Reset the "last saved" timestamp to the current time to prevent
+ // auto-saving the cookies again immediately after this jar is loaded.
+ $this->_cookieJarLastSaved = time();
+ }
+
+ /**
+ * Retrieve the CSRF token from the current cookie jar.
+ *
+ * Note that Instagram gives you a 1-year token expiration timestamp when
+ * you log in. But if you log out, they set its timestamp to "0" which means
+ * that the cookie is "expired" and invalid. We ignore token cookies if they
+ * have been logged out, or if they have expired naturally.
+ *
+ * @return string|null The token if found and non-expired, otherwise NULL.
+ */
+ public function getToken()
+ {
+ $cookie = $this->getCookie('csrftoken', 'i.instagram.com');
+ if ($cookie === null || $cookie->getValue() === '') {
+ return null;
+ }
+
+ return $cookie->getValue();
+ }
+
+ /**
+ * Searches for a specific cookie in the current jar.
+ *
+ * @param string $name The name of the cookie.
+ * @param string|null $domain (optional) Require a specific domain match.
+ * @param string|null $path (optional) Require a specific path match.
+ *
+ * @return \GuzzleHttp\Cookie\SetCookie|null A cookie if found and non-expired, otherwise NULL.
+ */
+ public function getCookie(
+ $name,
+ $domain = null,
+ $path = null)
+ {
+ $foundCookie = null;
+ if ($this->_cookieJar instanceof CookieJar) {
+ /** @var SetCookie $cookie */
+ foreach ($this->_cookieJar->getIterator() as $cookie) {
+ if ($cookie->getName() === $name
+ && !$cookie->isExpired()
+ && ($domain === null || $cookie->matchesDomain($domain))
+ && ($path === null || $cookie->matchesPath($path))) {
+ // Loop-"break" is omitted intentionally, because we might
+ // have more than one cookie with the same name, so we will
+ // return the LAST one. This is necessary because Instagram
+ // has changed their cookie domain from `i.instagram.com` to
+ // `.instagram.com` and we want the *most recent* cookie.
+ // Guzzle's `CookieJar::setCookie()` always places the most
+ // recently added/modified cookies at the *end* of array.
+ $foundCookie = $cookie;
+ }
+ }
+ }
+
+ return $foundCookie;
+ }
+
+ /**
+ * Gives you all cookies in the Jar encoded as a JSON string.
+ *
+ * This allows custom Settings storages to retrieve all cookies for saving.
+ *
+ * @throws \InvalidArgumentException If the JSON cannot be encoded.
+ *
+ * @return string
+ */
+ public function getCookieJarAsJSON()
+ {
+ if (!$this->_cookieJar instanceof CookieJar) {
+ return '[]';
+ }
+
+ // Gets ALL cookies from the jar, even temporary session-based cookies.
+ $cookies = $this->_cookieJar->toArray();
+
+ // Throws if data can't be encoded as JSON (will never happen).
+ $jsonStr = \GuzzleHttp\json_encode($cookies);
+
+ return $jsonStr;
+ }
+
+ /**
+ * Tells current settings storage to store cookies if necessary.
+ *
+ * NOTE: This Client class is NOT responsible for calling this function!
+ * Instead, our parent "Instagram" instance takes care of it and saves the
+ * cookies "onCloseUser", so that cookies are written to storage in a
+ * single, efficient write when the user's session is finished. We also call
+ * it during some important function calls such as login/logout. Client also
+ * automatically calls it when enough time has elapsed since last save.
+ *
+ * @throws \InvalidArgumentException If the JSON cannot be encoded.
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function saveCookieJar()
+ {
+ // Tell the settings storage to persist the latest cookies.
+ $newCookies = $this->getCookieJarAsJSON();
+ $this->_parent->settings->setCookies($newCookies);
+
+ // Reset the "last saved" timestamp to the current time.
+ $this->_cookieJarLastSaved = time();
+ }
+
+ /**
+ * Controls the SSL verification behavior of the Client.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#verify
+ *
+ * @param bool|string $state TRUE to verify using PHP's default CA bundle,
+ * FALSE to disable SSL verification (this is
+ * insecure!), String to verify using this path to
+ * a custom CA bundle file.
+ */
+ public function setVerifySSL(
+ $state)
+ {
+ $this->_verifySSL = $state;
+ }
+
+ /**
+ * Gets the current SSL verification behavior of the Client.
+ *
+ * @return bool|string
+ */
+ public function getVerifySSL()
+ {
+ return $this->_verifySSL;
+ }
+
+ /**
+ * Set the proxy to use for requests.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#proxy
+ *
+ * @param string|array|null $value String or Array specifying a proxy in
+ * Guzzle format, or NULL to disable proxying.
+ */
+ public function setProxy(
+ $value)
+ {
+ $this->_proxy = $value;
+ $this->_resetConnection = true;
+ }
+
+ /**
+ * Gets the current proxy used for requests.
+ *
+ * @return string|array|null
+ */
+ public function getProxy()
+ {
+ return $this->_proxy;
+ }
+
+ /**
+ * Sets the network interface override to use.
+ *
+ * Only works if Guzzle is using the cURL backend. But that's
+ * almost always the case, on most PHP installations.
+ *
+ * @see http://php.net/curl_setopt CURLOPT_INTERFACE
+ *
+ * @param string|null $value Interface name, IP address or hostname, or NULL to
+ * disable override and let Guzzle use any interface.
+ */
+ public function setOutputInterface(
+ $value)
+ {
+ $this->_outputInterface = $value;
+ $this->_resetConnection = true;
+ }
+
+ /**
+ * Gets the current network interface override used for requests.
+ *
+ * @return string|null
+ */
+ public function getOutputInterface()
+ {
+ return $this->_outputInterface;
+ }
+
+ /**
+ * Output debugging information.
+ *
+ * @param string $method "GET" or "POST".
+ * @param string $url The URL or endpoint used for the request.
+ * @param string|null $uploadedBody What was sent to the server. Use NULL to
+ * avoid displaying it.
+ * @param int|null $uploadedBytes How many bytes were uploaded. Use NULL to
+ * avoid displaying it.
+ * @param HttpResponseInterface $response The Guzzle response object from the request.
+ * @param string $responseBody The actual text-body reply from the server.
+ */
+ protected function _printDebug(
+ $method,
+ $url,
+ $uploadedBody,
+ $uploadedBytes,
+ HttpResponseInterface $response,
+ $responseBody)
+ {
+ Debug::printRequest($method, $url);
+
+ // Display the data body that was uploaded, if provided for debugging.
+ // NOTE: Only provide this from functions that submit meaningful BODY data!
+ if (is_string($uploadedBody)) {
+ Debug::printPostData($uploadedBody);
+ }
+
+ // Display the number of bytes uploaded in the data body, if provided for debugging.
+ // NOTE: Only provide this from functions that actually upload files!
+ if ($uploadedBytes !== null) {
+ Debug::printUpload(Utils::formatBytes($uploadedBytes));
+ }
+
+ // Display the number of bytes received from the response, and status code.
+ if ($response->hasHeader('x-encoded-content-length')) {
+ $bytes = Utils::formatBytes((int) $response->getHeaderLine('x-encoded-content-length'));
+ } elseif ($response->hasHeader('Content-Length')) {
+ $bytes = Utils::formatBytes((int) $response->getHeaderLine('Content-Length'));
+ } else {
+ $bytes = 0;
+ }
+ Debug::printHttpCode($response->getStatusCode(), $bytes);
+
+ // Display the actual API response body.
+ Debug::printResponse($responseBody, $this->_parent->truncatedDebug);
+ }
+
+ /**
+ * Maps a server response onto a specific kind of result object.
+ *
+ * The result is placed directly inside `$responseObject`.
+ *
+ * @param Response $responseObject An instance of a class object whose
+ * properties to fill with the response.
+ * @param string $rawResponse A raw JSON response string
+ * from Instagram's server.
+ * @param HttpResponseInterface $httpResponse HTTP response object.
+ *
+ * @throws InstagramException In case of invalid or failed API response.
+ */
+ public function mapServerResponse(
+ Response $responseObject,
+ $rawResponse,
+ HttpResponseInterface $httpResponse)
+ {
+ // Attempt to decode the raw JSON to an array.
+ // Important: Special JSON decoder which handles 64-bit numbers!
+ $jsonArray = $this->api_body_decode($rawResponse, true);
+
+ // If the server response is not an array, it means that JSON decoding
+ // failed or some other bad thing happened. So analyze the HTTP status
+ // code (if available) to see what really happened.
+ if (!is_array($jsonArray)) {
+ $httpStatusCode = $httpResponse !== null ? $httpResponse->getStatusCode() : null;
+ switch ($httpStatusCode) {
+ case 400:
+ throw new \InstagramAPI\Exception\BadRequestException('Invalid request options.');
+ case 404:
+ throw new \InstagramAPI\Exception\NotFoundException('Requested resource does not exist.');
+ default:
+ throw new \InstagramAPI\Exception\EmptyResponseException('No response from server. Either a connection or configuration error.');
+ }
+ }
+
+ // Perform mapping of all response properties.
+ try {
+ // Assign the new object data. Only throws if custom _init() fails.
+ // NOTE: False = assign data without automatic analysis.
+ $responseObject->assignObjectData($jsonArray, false); // Throws.
+
+ // Use API developer debugging? We'll throw if class lacks property
+ // definitions, or if they can't be mapped as defined in the class
+ // property map. But we'll ignore missing properties in our custom
+ // UnpredictableKeys containers, since those ALWAYS lack keys. ;-)
+ if ($this->_parent->apiDeveloperDebug) {
+ // Perform manual analysis (so that we can intercept its analysis result).
+ $analysis = $responseObject->exportClassAnalysis(); // Never throws.
+
+ // Remove all "missing_definitions" errors for UnpredictableKeys containers.
+ // NOTE: We will keep any "bad_definitions" errors for them.
+ foreach ($analysis->missing_definitions as $className => $x) {
+ if (strpos($className, '\\Response\\Model\\UnpredictableKeys\\') !== false) {
+ unset($analysis->missing_definitions[$className]);
+ }
+ }
+
+ // If any problems remain after that, throw with all combined summaries.
+ if ($analysis->hasProblems()) {
+ throw new LazyJsonMapperException(
+ $analysis->generateNiceSummariesAsString()
+ );
+ }
+ }
+ } catch (LazyJsonMapperException $e) {
+ // Since there was a problem, let's help our developers by
+ // displaying the server's JSON data in a human-readable format,
+ // which makes it easy to see the structure and necessary changes
+ // and speeds up the job of updating responses and models.
+ try {
+ // Decode to stdClass to properly preserve empty objects `{}`,
+ // otherwise they would appear as empty `[]` arrays in output.
+ // NOTE: Large >32-bit numbers will be transformed into strings,
+ // which helps us see which numeric values need "string" type.
+ $jsonObject = $this->api_body_decode($rawResponse, false);
+ if (is_object($jsonObject)) {
+ $prettyJson = @json_encode(
+ $jsonObject,
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
+ );
+ if ($prettyJson !== false) {
+ Debug::printResponse(
+ 'Human-Readable Response:'.PHP_EOL.$prettyJson,
+ false // Not truncated.
+ );
+ }
+ }
+ } catch (\Exception $e) {
+ // Ignore errors.
+ }
+
+ // Exceptions will only be thrown if API developer debugging is
+ // enabled and finds a problem. Either way, we should re-wrap the
+ // exception to our native type instead. The message gives enough
+ // details and we don't need to know the exact Lazy sub-exception.
+ throw new InstagramException($e->getMessage());
+ }
+
+ // Save the HTTP response object as the "getHttpResponse()" value.
+ $responseObject->setHttpResponse($httpResponse);
+
+ // Throw an exception if the API response was unsuccessful.
+ // NOTE: It will contain the full server response object too, which
+ // means that the user can look at the full response details via the
+ // exception itself.
+ if (!$responseObject->isOk()) {
+ if ($responseObject instanceof \InstagramAPI\Response\DirectSendItemResponse && $responseObject->getPayload() !== null) {
+ $message = $responseObject->getPayload()->getMessage();
+ } else {
+ $message = $responseObject->getMessage();
+ }
+
+ try {
+ ServerMessageThrower::autoThrow(
+ get_class($responseObject),
+ $message,
+ $responseObject,
+ $httpResponse
+ );
+ } catch (LoginRequiredException $e) {
+ // Instagram told us that our session is invalid (that we are
+ // not logged in). Update our cached "logged in?" state. This
+ // ensures that users with various retry-algorithms won't hammer
+ // their server. When this flag is false, ALL further attempts
+ // at AUTHENTICATED requests will be aborted by our library.
+ $this->_parent->isMaybeLoggedIn = false;
+
+ throw $e; // Re-throw.
+ }
+ }
+ }
+
+ /**
+ * Helper which builds in the most important Guzzle options.
+ *
+ * Takes care of adding all critical options that we need on every request.
+ * Such as cookies and the user's proxy. But don't call this function
+ * manually. It's automatically called by _guzzleRequest()!
+ *
+ * @param array $guzzleOptions The options specific to the current request.
+ *
+ * @return array A guzzle options array.
+ */
+ protected function _buildGuzzleOptions(
+ array $guzzleOptions = [])
+ {
+ $criticalOptions = [
+ 'cookies' => ($this->_cookieJar instanceof CookieJar ? $this->_cookieJar : false),
+ 'verify' => $this->_verifySSL,
+ 'proxy' => ($this->_proxy !== null ? $this->_proxy : null),
+ ];
+
+ // Critical options always overwrite identical keys in regular opts.
+ // This ensures that we can't screw up the proxy/verify/cookies.
+ $finalOptions = array_merge($guzzleOptions, $criticalOptions);
+
+ // Now merge any specific Guzzle cURL-backend overrides. We must do this
+ // separately since it's in an associative array and we can't just
+ // overwrite that whole array in case the caller had curl options.
+ if (!array_key_exists('curl', $finalOptions)) {
+ $finalOptions['curl'] = [];
+ }
+
+ // Add their network interface override if they want it.
+ // This option MUST be non-empty if set, otherwise it breaks cURL.
+ if (is_string($this->_outputInterface) && $this->_outputInterface !== '') {
+ $finalOptions['curl'][CURLOPT_INTERFACE] = $this->_outputInterface;
+ }
+ if ($this->_resetConnection) {
+ $finalOptions['curl'][CURLOPT_FRESH_CONNECT] = true;
+ $this->_resetConnection = false;
+ }
+
+ return $finalOptions;
+ }
+
+ /**
+ * Wraps Guzzle's request and adds special error handling and options.
+ *
+ * Automatically throws exceptions on certain very serious HTTP errors. And
+ * re-wraps all Guzzle errors to our own internal exceptions instead. You
+ * must ALWAYS use this (or _apiRequest()) instead of the raw Guzzle Client!
+ * However, you can never assume the server response contains what you
+ * wanted. Be sure to validate the API reply too, since Instagram's API
+ * calls themselves may fail with a JSON message explaining what went wrong.
+ *
+ * WARNING: This is a semi-lowlevel handler which only applies critical
+ * options and HTTP connection handling! Most functions will want to call
+ * _apiRequest() instead. An even higher-level handler which takes care of
+ * debugging, server response checking and response decoding!
+ *
+ * @param HttpRequestInterface $request HTTP request to send.
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ *
+ * @throws \InstagramAPI\Exception\NetworkException For any network/socket related errors.
+ * @throws \InstagramAPI\Exception\ThrottledException When we're throttled by server.
+ * @throws \InstagramAPI\Exception\RequestHeadersTooLargeException When request is too large.
+ *
+ * @return HttpResponseInterface
+ */
+ protected function _guzzleRequest(
+ HttpRequestInterface $request,
+ array $guzzleOptions = [])
+ {
+ // Add critically important options for authenticating the request.
+ $guzzleOptions = $this->_buildGuzzleOptions($guzzleOptions);
+
+ // Attempt the request. Will throw in case of socket errors!
+ try {
+ $response = $this->_guzzleClient->send($request, $guzzleOptions);
+ } catch (\Exception $e) {
+ // Re-wrap Guzzle's exception using our own NetworkException.
+ throw new \InstagramAPI\Exception\NetworkException($e);
+ }
+
+ // Detect very serious HTTP status codes in the response.
+ $httpCode = $response->getStatusCode();
+ switch ($httpCode) {
+ case 429: // "429 Too Many Requests"
+ throw new \InstagramAPI\Exception\ThrottledException('Throttled by Instagram because of too many API requests.');
+ break;
+ case 431: // "431 Request Header Fields Too Large"
+ throw new \InstagramAPI\Exception\RequestHeadersTooLargeException('The request start-line and/or headers are too large to process.');
+ break;
+ // WARNING: Do NOT detect 404 and other higher-level HTTP errors here,
+ // since we catch those later during steps like mapServerResponse()
+ // and autoThrow. This is a warning to future contributors!
+ }
+
+ // We'll periodically auto-save our cookies at certain intervals. This
+ // complements the "onCloseUser" and "login()/logout()" force-saving.
+ if ((time() - $this->_cookieJarLastSaved) > self::COOKIE_AUTOSAVE_INTERVAL) {
+ $this->saveCookieJar();
+ }
+
+ // The response may still have serious but "valid response" errors, such
+ // as "400 Bad Request". But it's up to the CALLER to handle those!
+ return $response;
+ }
+
+ /**
+ * Internal wrapper around _guzzleRequest().
+ *
+ * This takes care of many common additional tasks needed by our library,
+ * so you should try to always use this instead of the raw _guzzleRequest()!
+ *
+ * Available library options are:
+ * - 'noDebug': Can be set to TRUE to forcibly hide debugging output for
+ * this request. The user controls debugging globally, but this is an
+ * override that prevents them from seeing certain requests that you may
+ * not want to trigger debugging (such as perhaps individual steps of a
+ * file upload process). However, debugging SHOULD be allowed in MOST cases!
+ * So only use this feature if you have a very good reason.
+ * - 'debugUploadedBody': Set to TRUE to make debugging display the data that
+ * was uploaded in the body of the request. DO NOT use this if your function
+ * uploaded binary data, since printing those bytes would kill the terminal!
+ * - 'debugUploadedBytes': Set to TRUE to make debugging display the size of
+ * the uploaded body data. Should ALWAYS be TRUE when uploading binary data.
+ *
+ * @param HttpRequestInterface $request HTTP request to send.
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ * @param array $libraryOptions Additional options for controlling Library features
+ * such as the debugging output.
+ *
+ * @throws \InstagramAPI\Exception\NetworkException For any network/socket related errors.
+ * @throws \InstagramAPI\Exception\ThrottledException When we're throttled by server.
+ *
+ * @return HttpResponseInterface
+ */
+ protected function _apiRequest(
+ HttpRequestInterface $request,
+ array $guzzleOptions = [],
+ array $libraryOptions = [])
+ {
+ // Perform the API request and retrieve the raw HTTP response body.
+ $guzzleResponse = $this->_guzzleRequest($request, $guzzleOptions);
+
+ // Debugging (must be shown before possible decoding error).
+ if ($this->_parent->debug && (!isset($libraryOptions['noDebug']) || !$libraryOptions['noDebug'])) {
+ // Determine whether we should display the contents of the UPLOADED body.
+ if (isset($libraryOptions['debugUploadedBody']) && $libraryOptions['debugUploadedBody']) {
+ $uploadedBody = (string) $request->getBody();
+ if (!strlen($uploadedBody)) {
+ $uploadedBody = null;
+ }
+ } else {
+ $uploadedBody = null; // Don't display.
+ }
+
+ // Determine whether we should display the size of the UPLOADED body.
+ if (isset($libraryOptions['debugUploadedBytes']) && $libraryOptions['debugUploadedBytes']) {
+ // Calculate the uploaded bytes by looking at request's body size, if it exists.
+ $uploadedBytes = $request->getBody()->getSize();
+ } else {
+ $uploadedBytes = null; // Don't display.
+ }
+
+ $this->_printDebug(
+ $request->getMethod(),
+ $this->_zeroRating->rewrite((string) $request->getUri()),
+ $uploadedBody,
+ $uploadedBytes,
+ $guzzleResponse,
+ (string) $guzzleResponse->getBody());
+ }
+
+ return $guzzleResponse;
+ }
+
+ /**
+ * Perform an Instagram API call.
+ *
+ * @param HttpRequestInterface $request HTTP request to send.
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ *
+ * @throws InstagramException
+ *
+ * @return HttpResponseInterface
+ */
+ public function api(
+ HttpRequestInterface $request,
+ array $guzzleOptions = [])
+ {
+ // Set up headers that are required for every request.
+ $request = modify_request($request, [
+ 'set_headers' => [
+ 'User-Agent' => $this->_userAgent,
+ // Keep the API's HTTPS connection alive in Guzzle for future
+ // re-use, to greatly speed up all further queries after this.
+ 'Connection' => 'Keep-Alive',
+ 'Accept' => '*/*',
+ 'Accept-Encoding' => Constants::ACCEPT_ENCODING,
+ 'Accept-Language' => Constants::ACCEPT_LANGUAGE,
+ ],
+ ]);
+
+ // Check the Content-Type header for debugging.
+ $contentType = $request->getHeader('Content-Type');
+ $isFormData = count($contentType) && reset($contentType) === Constants::CONTENT_TYPE;
+
+ // Perform the API request.
+ $response = $this->_apiRequest($request, $guzzleOptions, [
+ 'debugUploadedBody' => $isFormData,
+ 'debugUploadedBytes' => !$isFormData,
+ ]);
+
+ return $response;
+ }
+
+ /**
+ * Decode a JSON reply from Instagram's API.
+ *
+ * WARNING: EXTREMELY IMPORTANT! NEVER, *EVER* USE THE BASIC "json_decode"
+ * ON API REPLIES! ALWAYS USE THIS METHOD INSTEAD, TO ENSURE PROPER DECODING
+ * OF BIG NUMBERS! OTHERWISE YOU'LL TRUNCATE VARIOUS INSTAGRAM API FIELDS!
+ *
+ * @param string $json The body (JSON string) of the API response.
+ * @param bool $assoc When FALSE, decode to object instead of associative array.
+ *
+ * @return object|array|null Object if assoc false, Array if assoc true,
+ * or NULL if unable to decode JSON.
+ */
+ public static function api_body_decode(
+ $json,
+ $assoc = true)
+ {
+ return @json_decode($json, $assoc, 512, JSON_BIGINT_AS_STRING);
+ }
+
+ /**
+ * Get the cookies middleware instance.
+ *
+ * @return FakeCookies
+ */
+ public function fakeCookies()
+ {
+ return $this->_fakeCookies;
+ }
+
+ /**
+ * Get the zero rating rewrite middleware instance.
+ *
+ * @return ZeroRating
+ */
+ public function zeroRating()
+ {
+ return $this->_zeroRating;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Constants.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Constants.php
new file mode 100755
index 0000000..2795e82
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Constants.php
@@ -0,0 +1,103 @@
+ 'https://i.instagram.com/api/v1/',
+ 2 => 'https://i.instagram.com/api/v2/',
+ ];
+ const IG_VERSION = '107.0.0.27.121';
+ const VERSION_CODE = '168361634';
+ const IG_SIG_KEY = 'c36436a942ea1dbb40d7f2d7d45280a620d991ce8c62fb4ce600f0a048c32c11';
+ const EXPERIMENTS = 'ig_android_push_notifications_settings_redesign_universe,ig_hashtag_display_universe,ig_android_video_ssim_fix_pts_universe,coupon_price_test_ad4ad_instagram_resurrection_universe,ig_android_live_rendering_looper_universe,ig_shopping_checkout_improvements_universe,ig_android_mqtt_cookie_auth_memcache_universe,ig_android_optional_contact_and_preset_universe,ig_android_video_player_memory_leaks,ig_android_stories_seen_state_serialization,ig_stories_photo_time_duration_universe,ig_android_bitmap_cache_executor_size,ig_android_stories_music_search_typeahead,android_ard_ig_use_brotli_effect_universe,ig_android_remove_fb_nux_universe,ig_android_delayed_comments,ig_android_direct_mutation_manager_media_3,ig_smb_ads_holdout_2019_h1_universe,ig_fb_graph_differentiation,ig_android_stories_share_extension_video_segmentation,ig_android_igtv_crop_top,ig_android_stories_create_flow_favorites_tooltip,ig_android_direct_reshare_chaining,ig_android_stories_no_inflation_on_app_start,ig_android_stories_viewer_viewpoint_universe,ig_android_separate_empty_feed_su_universe,ig_android_zero_rating_carrier_signal,ig_direct_holdout_h1_2019,ig_explore_2019_h1_destination_cover,ig_android_direct_stories_in_direct_inbox,ig_android_explore_recyclerview_universe,ig_android_show_muted_accounts_page,ig_android_vc_service_crash_fix_universe,ig_camera_android_subtle_filter_universe,ig_android_lazy_init_live_composer_controller,ig_fb_graph_differentiation_no_fb_data,ig_android_viewpoint_stories_public_testing,ig_camera_android_api_rewrite_universe,ig_android_growth_fci_team_holdout_universe,android_camera_core_cpu_frames_sync,ig_android_video_source_sponsor_fix,ig_android_save_all,ig_android_ttcp_improvements,ig_android_camera_ar_platform_profile_universe,ig_android_separate_sms_n_email_invites_setting_universe,ig_shopping_bag_universe,ig_ar_shopping_camera_android_universe,ig_android_recyclerview_binder_group_enabled_universe,ig_android_stories_viewer_tall_android_cap_media_universe,ig_android_video_exoplayer_2,native_contact_invites_universe,ig_android_stories_seen_state_processing_universe,ig_android_dash_script,ig_android_insights_media_hashtag_insight_universe,ig_android_search_qpl_switch,ig_camera_fast_tti_universe,ig_android_igtv_improved_search,ig_android_stories_music_filters,ig_android_render_thread_memory_leak_holdout,ig_android_automated_logging,ig_android_viewmaster_post_capture_universe,ig_android_2018_h1_hashtag_report_universe,ig_android_camera_effect_gallery_prefetch,ig_share_to_story_toggle_include_shopping_product,ig_android_interactions_verified_badge_on_comment_details,ig_android_fix_ppr_thumbnail_url,ig_android_camera_reduce_file_exif_reads,ig_interactions_project_daisy_creators_universe,ig_payments_billing_address,ig_android_fs_new_gallery_hashtag_prompts,ig_camera_android_gyro_senser_sampling_period_universe,ig_android_xposting_feed_to_stories_reshares_universe,ig_android_combined_consumption,ig_camera_remove_display_rotation_cb_universe,ig_android_interactions_migrate_inline_composer_to_viewpoint_universe,ig_android_ufiv3_holdout,ig_android_neue_igtv_profile_tab_rollout,ig_android_enable_zero_rating,ig_android_story_ads_carousel_performance_universe_1,ig_android_direct_leave_from_group_message_requests,ig_android_import_page_post_after_biz_conversion,ig_camera_ar_effect_attribution_position,ig_promote_add_payment_navigation_universe,ig_android_story_ads_carousel_performance_universe_2,ig_android_main_feed_refresh_style_universe,ig_stories_engagement_holdout_2019_h1_universe,ig_android_story_ads_performance_universe_1,ig_android_stories_viewer_modal_activity,ig_android_story_ads_performance_universe_2,ig_android_publisher_stories_migration,ig_android_story_ads_performance_universe_3,ig_android_quick_conversion_universe,ig_android_story_import_intent,ig_android_story_ads_performance_universe_4,instagram_android_profile_follow_cta_context_feed,ig_biz_graph_connection_universe,ig_android_stories_boomerang_v2_universe,ig_android_ads_profile_cta_feed_universe,ig_android_vc_cowatch_universe,ig_android_nametag,ig_hashtag_creation_universe,ig_android_igtv_chaining,ig_android_live_qa_viewer_v1_universe,ig_shopping_insights_wc_copy_update_android,ig_android_stories_music_lyrics_pre_capture,android_cameracore_fbaudio_integration_ig_universe,ig_android_camera_stopmotion,ig_android_igtv_reshare,ig_android_wellbeing_timeinapp_v1_universe,ig_android_profile_cta_v3,ig_end_of_feed_universe,ig_android_mainfeed_generate_prefetch_background,ig_android_vc_shareable_moments_universe,ig_camera_text_overlay_controller_opt_universe,ig_android_shopping_product_metadata_on_product_tiles_universe,ig_android_video_qp_logger_universe,ig_android_shopping_pdp_cache,ig_android_follow_request_button_improvements_universe,ig_android_vc_start_from_direct_inbox_universe,ig_android_separate_network_executor,ig_perf_android_holdout,ig_fb_graph_differentiation_only_fb_candidates,ig_android_media_streaming_sdk_universe,ig_android_direct_reshares_from_thread,ig_android_stories_video_prefetch_kb,ig_android_wellbeing_timeinapp_v1_migration,ig_android_camera_post_smile_face_first_universe,ig_android_maintabfragment,ig_android_cookie_injection_retry_universe,ig_inventory_connections,ig_stories_injection_tool_enabled_universe,ig_android_canvas_cookie_universe,ig_android_stories_disable_highlights_media_preloading,ig_android_effect_gallery_post_capture_universe,ig_android_shopping_variant_selector_redesign,ig_android_branded_content_ads_universe,ig_promote_lotus_universe,ig_android_video_streaming_upload_universe,ig_camera_android_attribution_bottomsheet_universe,ig_android_product_tag_hint_dots,ig_interactions_h1_2019_team_holdout_universe,ig_camera_android_release_drawing_view_universe,ig_android_music_story_fb_crosspost_universe,ig_android_disable_scroll_listeners,ig_carousel_bumped_organic_impression_client_universe,ig_android_ad_async_ads_universe,ig_biz_post_approval_nux_universe,ig_android_vc_participants_grid_refactor_universe,android_ard_ig_modelmanager_cache_hit,ig_android_persistent_nux,ig_android_crash_fix_detach_from_gl_context,ig_android_branded_content_upsell_keywords_extension,ig_android_vc_ringscreen_timeout_universe,ig_android_edit_location_page_info,ig_android_stories_project_eclipse,ig_camera_android_segmentation_v106_igdjango_universe,ig_android_camera_recents_gallery_modal,ig_promote_are_you_sure_universe,ig_android_li_session_chaining,ig_android_camera_platform_effect_share_universe,ig_android_rate_limit_mediafeedviewablehelper,ig_android_search_empty_state,ig_android_camera_ar_platform_logging,ig_stories_engagement_holdout_2019_h2_universe,ig_android_search_remove_null_state_sections,ig_direct_android_mentions_receiver,ig_camera_android_device_capabilities_experiment,ig_android_stories_viewer_drawable_cache_universe,ig_camera_android_qcc_constructor_opt_universe,ig_android_stories_alignment_guides_universe,ig_android_rn_ads_manager_universe,ig_android_video_visual_quality_score_based_abr,ig_explore_2018_post_chaining_account_recs_dedupe_universe,ig_android_stories_video_seeking_audio_bug_fix,ig_android_insights_holdout,ig_android_do_not_show_social_context_for_likes_page_universe,ig_android_context_feed_recycler_view,ig_fb_notification_universe,ig_android_report_website_universe,ig_android_feed_post_sticker,ig_android_inline_editing_local_prefill,ig_android_commerce_platform_bloks_universe,ig_android_stories_unregister_decor_listener_universe,ig_android_search_condensed_search_icons,ig_android_video_abr_universe,ig_android_blended_inbox_split_button_v2_universe,ig_android_nelson_v0_universe,ig_android_scroll_audio_priority,ig_android_own_profile_sharing_universe,ig_android_vc_cowatch_media_share_universe,ig_biz_graph_unify_assoc_universe,ig_challenge_general_v2,ig_android_place_signature_universe,ig_android_direct_inbox_cache_universe,ig_android_ig_branding_in_fb_universe,ig_android_business_promote_tooltip,ig_android_tap_to_capture_universe,ig_android_follow_requests_ui_improvements,ig_android_video_ssim_fix_compare_frame_index,ig_android_direct_aggregated_media_and_reshares,ig_android_story_camera_share_to_feed_universe,ig_android_fb_follow_server_linkage_universe,ig_android_stories_viewer_reply_box_placeholder_copy,ig_android_biz_reorder_value_props,ig_android_direct_view_more_qe,ig_android_churned_find_friends_redirect_to_discover_people,ig_android_main_feed_new_posts_indicator_universe,ig_vp9_hd_blacklist,ig_camera_android_ar_effect_stories_deeplink,ig_android_client_side_delivery_universe,ig_ios_queue_time_qpl_universe,ig_android_fix_direct_badge_count_universe,ig_android_insights_audience_tab_native_universe,ig_android_stories_send_client_reels_on_tray_fetch_universe,ig_android_felix_prefetch_thumbnail_sprite_sheet,ig_android_live_use_rtc_upload_universe,ig_android_multi_dex_class_loader_v2,ig_android_live_ama_viewer_universe,ig_smb_ads_holdout_2018_h2_universe,ig_android_camera_post_smile_low_end_universe,ig_android_profile_follow_tab_hashtag_row_universe,ig_android_watch_and_more_redesign,igtv_feed_previews,ig_android_live_realtime_comments_universe,ig_android_story_insights_native_universe,ig_smb_ads_holdout_2019_h2_universe,ig_android_purx_native_checkout_universe,ig_camera_android_filter_optmizations,ig_android_integrity_sprint_universe,ig_android_apr_lazy_build_request_infra,ig_android_igds_edit_profile_fields,ig_android_business_transaction_in_stories_creator,ig_android_rounded_corner_framelayout_perf_fix,ig_android_branded_content_appeal_states,android_cameracore_ard_ig_integration,ig_video_experimental_encoding_consumption_universe,ig_android_iab_autofill,ig_android_creator_quick_reply_universe,ig_android_location_page_intent_survey,ig_camera_android_segmentation_async_universe,ig_android_biz_story_to_fb_page_improvement,ig_android_direct_thread_target_queue_universe,ig_android_branded_content_insights_disclosure,ig_camera_android_target_recognition_universe,ig_camera_android_skip_camera_initialization_open_to_post_capture,ig_android_combined_tagging_videos,ig_android_stories_samsung_sharing_integration,ig_android_create_page_on_top_universe,ig_iab_use_default_intent_loading,ig_android_camera_focus_v2,ig_android_biz_conversion_pull_suggest_biz_data,ig_discovery_holdout_2019_h1_universe,ig_android_wellbeing_support_frx_comment_reporting,ig_android_insights_post_dismiss_button,ig_android_user_url_deeplink_fbpage_endpoint,ig_android_ad_holdout_watchandmore_universe,ig_android_follow_request_button_new_ui,ig_iab_dns_prefetch_universe,ig_android_explore_use_shopping_endpoint,ig_android_image_upload_skip_queue_only_on_wifi,ig_android_igtv_pip,ig_android_ad_watchbrowse_carousel_universe,ig_android_camera_new_post_smile_universe,ig_android_shopping_signup_redesign_universe,ig_android_direct_hide_inbox_header,ig_shopping_pdp_more_related_product_section,ig_android_experimental_onetap_dialogs_universe,ig_android_fix_main_feed_su_cards_size_universe,ig_android_direct_multi_upload_universe,ig_camera_text_mode_composer_controller_opt_universe,ig_explore_2019_h1_video_autoplay_resume,ig_android_multi_capture_camera,ig_android_video_upload_quality_qe1,ig_android_follow_requests_copy_improvements,ig_android_save_collaborative_collections,coupon_price_test_boost_instagram_media_acquisition_universe,ig_android_video_outputsurface_handlerthread_universe,ig_android_country_code_fix_universe,ig_perf_android_holdout_2018_h1,ig_android_stories_music_overlay,ig_android_enable_lean_crash_reporting_universe,ig_android_resumable_downloads_logging_universe,ig_android_low_latency_consumption_universe,ig_android_render_output_surface_timeout_universe,ig_android_big_foot_foregroud_reporting,ig_android_unified_iab_logging_universe,ig_threads_app_close_friends_integration,ig_aggregated_quick_reactions,ig_android_shopping_pdp_post_purchase_sharing,ig_android_aggressive_cookie,ig_android_offline_mode_holdout,ig_android_realtime_mqtt_logging,ig_android_rainbow_hashtags,ig_android_no_bg_effect_tray_live_universe,ig_android_direct_block_from_group_message_requests,ig_android_react_native_universe_kill_switch,ig_android_viewpoint_occlusion,ig_android_logged_in_delta_migration,ig_android_push_reliability_universe,ig_android_stories_gallery_video_segmentation,ig_android_direct_business_holdout,ig_android_vc_direct_inbox_ongoing_pill_universe,ig_android_xposting_upsell_directly_after_sharing_to_story,ig_android_direct_sticker_search_upgrade_universe,ig_android_insights_native_post_universe,ig_android_dual_destination_quality_improvement,ig_android_camera_focus_low_end_universe,ig_android_camera_hair_segmentation_universe,ig_android_direct_combine_action_logs,ig_android_leak_detector_upload_universe,ig_android_ads_data_preferences_universe,ig_android_branded_content_access_upsell,ig_android_follow_button_in_story_viewers_list,ig_android_vc_background_call_toast_universe,ig_hashtag_following_holdout_universe,ig_promote_default_destination_universe,ig_android_delay_segmentation_low_end_universe,ig_android_direct_media_latency_optimizations,mi_viewpoint_viewability_universe,android_ard_ig_download_manager_v2,ig_direct_reshare_sharesheet_ranking,ig_music_dash,ig_android_fb_url_universe,ig_android_le_videoautoplay_disabled,ig_android_reel_raven_video_segmented_upload_universe,ig_android_promote_native_migration_universe,invite_friends_by_messenger_in_setting_universe,ig_android_fb_sync_options_universe,ig_android_thread_gesture_refactor,ig_android_stories_skip_seen_state_update_for_direct_stories,ig_android_recommend_accounts_destination_routing_fix,ig_android_fix_prepare_direct_push,ig_android_enable_automated_instruction_text_ar,ig_android_multi_author_story_reshare_universe,ig_android_building_aymf_universe,ig_android_internal_sticker_universe,ig_traffic_routing_universe,ig_android_payments_growth_business_payments_within_payments_universe,ig_camera_async_gallerycontroller_universe,ig_android_direct_state_observer,ig_android_page_claim_deeplink_qe,ig_android_camera_effects_order_universe,ig_android_video_controls_universe,ig_android_video_local_proxy_video_metadata,ig_android_logging_metric_universe_v2,ig_android_network_onbehavior_change_fix,ig_android_xposting_newly_fbc_people,ig_android_visualcomposer_inapp_notification_universe,ig_android_do_not_show_social_context_on_follow_list_universe,ig_android_contact_point_upload_rate_limit_killswitch,ig_android_webrtc_encoder_factory_universe,ig_android_qpl_class_marker,ig_android_fix_profile_pic_from_fb_universe,ig_android_sso_kototoro_app_universe,ig_android_camera_3p_in_post,ig_android_ar_effect_sticker_consumption_universe,ig_android_direct_unread_count_badge,ig_android_profile_thumbnail_impression,ig_android_igtv_autoplay_on_prepare,ig_android_list_adapter_prefetch_infra,ig_file_based_session_handler_2_universe,ig_branded_content_tagging_upsell,ig_android_clear_inflight_image_request,ig_android_main_feed_video_countdown_timer,ig_android_live_ama_universe,ig_android_external_gallery_import_affordance,ig_search_hashtag_content_advisory_remove_snooze,ig_payment_checkout_info,ig_android_optic_new_zoom_controller,ig_android_photos_qpl,ig_stories_ads_delivery_rules,ig_android_downloadable_spark_spot,ig_android_video_upload_iframe_interval,ig_business_new_value_prop_universe,ig_android_power_metrics,ig_android_vio_pipeline_universe,ig_android_show_profile_picture_upsell_in_reel_universe,ig_discovery_holdout_universe,ig_android_direct_import_google_photos2,ig_direct_feed_media_sticker_universe,ig_android_igtv_upload_error_messages,ig_android_stories_collapse_seen_segments,ig_android_self_profile_suggest_business_main,ig_android_suggested_users_background,ig_android_fetch_xpost_setting_in_camera_fully_open,ig_android_hashtag_discover_tab,ig_android_stories_separate_overlay_creation,ig_android_ads_bottom_sheet_report_flow,ig_android_login_onetap_upsell_universe,ig_android_iris_improvements,enable_creator_account_conversion_v0_universe,ig_android_test_not_signing_address_book_unlink_endpoint,ig_android_low_disk_recovery_universe,ig_ei_option_setting_universe,ig_android_account_insights_native_universe,ig_camera_android_ar_platform_universe,ig_android_browser_ads_page_content_width_universe,ig_android_stories_viewer_prefetch_improvements,ig_android_livewith_liveswap_optimization_universe,ig_android_camera_leak,ig_android_feed_core_unified_tags_universe,ig_android_jit,ig_android_optic_camera_warmup,ig_stories_rainbow_ring,ig_android_place_search_profile_image,ig_android_vp8_audio_encoder,android_cameracore_safe_makecurrent_ig,ig_android_analytics_diagnostics_universe,ig_android_ar_effect_sticker_universe,ig_direct_android_mentions_sender,ig_android_whats_app_contact_invite_universe,ig_android_stories_reaction_setup_delay_universe,ig_shopping_visual_product_sticker,ig_android_profile_unified_follow_view,ig_android_video_upload_hevc_encoding_universe,ig_android_mentions_suggestions,ig_android_vc_face_effects_universe,ig_android_fbpage_on_profile_side_tray,ig_android_direct_empty_state,ig_android_shimmering_loading_state,ig_android_igtv_refresh_tv_guide_interval,ig_android_gallery_minimum_video_length,ig_android_notif_improvement_universe,ig_android_hashtag_remove_share_hashtag,ig_android_fb_profile_integration_fbnc_universe,ig_shopping_checkout_2x2_platformization_universe,ig_android_direct_bump_active_threads,ig_fb_graph_differentiation_control,ig_android_show_create_content_pages_universe,ig_android_igsystrace_universe,ig_android_search_register_recent_store,ig_feed_content_universe,ig_android_disk_usage_logging_universe,ig_android_search_without_typed_hashtag_autocomplete,ig_android_video_product_specific_abr,ig_android_vc_interop_latency,ig_android_stories_layout_universe,ig_android_dont_animate_shutter_button_on_open,ig_android_vc_cpu_overuse_universe,ig_android_invite_list_button_redesign_universe,ig_android_react_native_email_sms_settings_universe,ig_hero_player,ag_family_bridges_2018_h2_holdout,ig_promote_net_promoter_score_universe,ig_android_save_auto_sharing_to_fb_option_on_server,aymt_instagram_promote_flow_abandonment_ig_universe,ig_android_whitehat_options_universe,ig_android_keyword_media_serp_page,ig_android_delete_ssim_compare_img_soon,ig_android_felix_video_upload_length,android_cameracore_preview_frame_listener2_ig_universe,ig_android_direct_message_follow_button,ig_android_biz_conversion_suggest_biz_nux,ig_stories_ads_media_based_insertion,ig_android_analytics_background_uploader_schedule,ig_camera_android_boomerang_attribution_universe,ig_android_igtv_browse_long_press,ig_android_profile_neue_infra_rollout_universe,ig_android_profile_ppr_fixes,ig_discovery_2019_h2_holdout_universe,ig_android_stories_weblink_creation,ig_android_blur_image_renderer,ig_profile_company_holdout_h2_2018,ig_android_ads_manager_pause_resume_ads_universe,ig_android_vc_capture_universe,ig_nametag_local_ocr_universe,ig_android_stories_media_seen_batching_universe,ig_android_interactions_nav_to_permalink_followup_universe,ig_camera_discovery_surface_universe,ig_android_save_to_collections_flow,ig_android_direct_segmented_video,instagram_stories_time_fixes,ig_android_le_cold_start_improvements,ig_android_direct_mark_as_read_notif_action,ig_android_stories_async_view_inflation_universe,ig_android_stories_recently_captured_universe,ig_android_direct_inbox_presence_refactor_universe,ig_business_integrity_ipc_universe,ig_android_direct_selfie_stickers,ig_android_vc_missed_call_call_back_action_universe,ig_cameracore_android_new_optic_camera2,ig_fb_graph_differentiation_top_k_fb_coefficients,ig_android_fbc_upsell_on_dp_first_load,ig_android_rename_share_option_in_dialog_menu_universe,ig_android_direct_refactor_inbox_observable_universe,ig_android_business_attribute_sync,ig_camera_android_bg_processor,ig_android_view_and_likes_cta_universe,ig_android_optic_new_focus_controller,ig_android_dropframe_manager,ig_android_direct_default_group_name,ig_android_optic_new_features_implementation,ig_android_search_hashtag_badges,ig_android_stories_reel_interactive_tap_target_size,ig_android_video_live_trace_universe,ig_android_tango_cpu_overuse_universe,ig_android_igtv_browse_with_pip_v2,ig_android_direct_fix_realtime_status,ig_android_unfollow_from_main_feed_v2,ig_android_self_story_setting_option_in_menu,ig_android_story_ads_tap_and_hold_fixes,ig_android_camera_ar_platform_details_view_universe,android_ard_ig_cache_size,ig_android_story_real_time_ad,ig_android_hybrid_bitmap_v4,ig_android_iab_downloadable_strings_universe,ig_android_branded_content_ads_enable_partner_boost,ufi_share,ig_android_direct_remix_visual_messages,ig_quick_story_placement_validation_universe,ig_android_custom_story_import_intent,ig_android_live_qa_broadcaster_v1_universe,ig_android_search_impression_logging_viewpoint,ig_android_downloadable_fonts_universe,ig_android_view_info_universe,ig_android_camera_upsell_dialog,ig_android_business_transaction_in_stories_consumer,ig_android_dead_code_detection,ig_android_promotion_insights_bloks,ig_android_direct_autoplay_videos_automatically,ig_android_ad_watchbrowse_universe,ig_android_pbia_proxy_profile_universe,ig_android_qp_kill_switch,ig_android_new_follower_removal_universe,instagram_android_stories_sticker_tray_redesign,ig_android_branded_content_access_tag,ig_android_gap_rule_enforcer_universe,ig_android_business_cross_post_with_biz_id_infra,ig_android_direct_delete_or_block_from_message_requests,ig_android_photo_invites,ig_interactions_h2_2019_team_holdout_universe,ig_android_reel_tray_item_impression_logging_viewpoint,ig_account_identity_2018_h2_lockdown_phone_global_holdout,ig_android_direct_left_aligned_navigation_bar,ig_android_high_res_gif_stickers,ig_android_feed_load_more_viewpoint_universe,ig_android_stories_reshare_reply_msg,ig_close_friends_v4,ig_android_ads_history_universe,ig_android_pigeon_sampling_runnable_check,ig_promote_media_picker_universe,ig_direct_holdout_h2_2018,ig_android_sidecar_report_ssim,ig_android_pending_media_file_registry,ig_android_wab_adjust_resize_universe,ig_camera_android_facetracker_v12_universe,ig_android_camera_ar_effects_low_storage_universe,ig_android_profile_add_profile_pic_universe,ig_android_ig_to_fb_sync_universe,ig_android_ar_background_effect_universe,ig_android_audience_control,ig_android_fix_recommended_user_impression,ig_android_stories_cross_sharing_to_fb_holdout_universe,shop_home_hscroll_see_all_button_universe,ig_android_refresh_empty_feed_su_universe,ig_android_shopping_parallel_pdp_fetch,ig_android_enable_main_feed_reel_tray_preloading,ig_android_ad_view_ads_native_universe,ig_android_branded_content_tag_redesign_organic,ig_android_profile_neue_universe,ig_android_igtv_whitelisted_for_web,ig_android_viewmaster_dial_ordering_universe,ig_company_profile_holdout,ig_rti_inapp_notifications_universe,ig_android_vc_join_timeout_universe,ig_shop_directory_entrypoint,ig_android_direct_rx_thread_update,ig_android_add_ci_upsell_in_normal_account_chaining_universe,ig_android_feed_core_ads_2019_h1_holdout_universe,ig_close_friends_v4_global,ig_android_share_publish_page_universe,ig_android_new_camera_design_universe,ig_direct_max_participants,ig_promote_hide_local_awareness_universe,ar_engine_audio_service_fba_decoder_ig,ar_engine_audio_fba_integration_instagram,ig_android_igtv_save,ig_android_explore_lru_cache,ig_android_graphql_survey_new_proxy_universe,ig_android_music_browser_redesign,ig_camera_android_try_on_camera_universe,ig_android_follower_following_whatsapp_invite_universe,ig_android_fs_creation_flow_tweaks,ig_direct_blocking_redesign_universe,ig_android_viewmaster_ar_memory_improvements,ig_android_downloadable_vp8_module,ig_android_claim_location_page,ig_android_direct_inbox_recently_active_presence_dot_universe,ig_android_stories_gutter_width_universe,ig_android_story_ads_2019_h1_holdout_universe,ig_android_3pspp,ig_android_cache_timespan_objects,ig_timestamp_public_test,ig_android_fb_profile_integration_universe,ig_android_feed_auto_share_to_facebook_dialog,ig_android_skip_button_content_on_connect_fb_universe,ig_android_network_perf_qpl_ppr,ig_android_post_live,ig_camera_android_focus_attribution_universe,ig_camera_async_space_validation_for_ar,ig_android_core_search_2019_h2,ig_android_prefetch_notification_data,ig_android_stories_music_line_by_line_cube_reveal_lyrics_sticker,ig_android_iab_clickid_universe,ig_android_interactions_hide_keyboard_onscroll,ig_early_friending_holdout_universe,ig_story_camera_reverse_video_experiment,ig_android_profile_lazy_load_carousel_media,ig_android_stories_question_sticker_music_format,ig_android_vpvd_impressions_universe,ig_android_payload_based_scheduling,ig_pacing_overriding_universe,ig_android_ard_ptl_universe,ig_android_q3lc_transparency_control_settings,ig_stories_selfie_sticker,ig_android_sso_use_trustedapp_universe,ig_android_stories_music_lyrics,ig_android_spark_studio_promo,ig_android_stories_music_awareness_universe,ard_ig_broti_effect,ig_android_camera_class_preloading,ig_android_new_fb_page_selection,ig_video_holdout_h2_2017,ig_background_prefetch,ig_camera_android_focus_in_post_universe,ig_android_time_spent_dashboard,ig_android_story_sharing_universe,ig_promote_political_ads_universe,ig_android_camera_effects_initialization_universe,ig_promote_post_insights_entry_universe,ig_android_ad_iab_qpl_kill_switch_universe,ig_android_live_subscribe_user_level_universe,ig_android_igtv_creation_flow,ig_android_vc_sounds_universe,ig_android_video_call_finish_universe,ig_camera_android_cache_format_picker_children,direct_unread_reminder_qe,ig_android_direct_mqtt_send,ig_android_self_story_button_non_fbc_accounts,ig_android_self_profile_suggest_business_gating,ig_feed_video_autoplay_stop_threshold,ig_android_explore_discover_people_entry_point_universe,ig_android_live_webrtc_livewith_params,ig_feed_experience,ig_android_direct_activator_cards,ig_android_vc_codec_settings,ig_promote_prefill_destination_universe,ig_android_appstate_logger,ig_android_profile_leaks_holdouts,ig_android_video_cached_bandwidth_estimate,ig_promote_insights_video_views_universe,ig_android_global_scheduler_offscreen_prefetch,ig_android_discover_interests_universe,ig_android_camera_gallery_upload_we_universe,ig_android_business_category_sticky_header_qe,ig_android_dismiss_recent_searches,ig_android_feed_camera_size_setter,ig_payment_checkout_cvv,ig_android_fb_link_ui_polish_universe,ig_android_tags_unification_universe,ig_android_shopping_lightbox,ig_android_bandwidth_timed_estimator,ig_android_stories_mixed_attribution_universe,ig_iab_tti_holdout_universe,ig_android_ar_button_visibility,ig_android_igtv_crop_top_consumption,ig_android_camera_gyro_universe,ig_android_nametag_effect_deeplink_universe,ig_android_blurred_product_image_previews,ig_android_igtv_ssim_report,ig_android_optic_surface_texture_cleanup,ig_android_business_remove_unowned_fb_pages,ig_android_stories_combined_asset_search,ig_promote_enter_error_screens_universe,ig_stories_allow_camera_actions_while_recording,ig_android_analytics_mark_events_as_offscreen,ig_shopping_checkout_mvp_experiment,ig_android_video_fit_scale_type_igtv,ig_android_direct_pending_media,ig_android_scroll_main_feed,instagram_pcp_activity_feed_following_tab_universe,ig_android_optic_feature_testing,ig_android_igtv_player_follow_button,ig_android_intialization_chunk_410,ig_android_vc_start_call_minimized_universe,ig_android_recognition_tracking_thread_prority_universe,ig_android_stories_music_sticker_position,ig_android_optic_photo_cropping_fixes,ig_camera_regiontracking_use_similarity_tracker_for_scaling,ig_android_interactions_media_breadcrumb,ig_android_vc_cowatch_config_universe,ig_android_nametag_save_experiment_universe,ig_android_refreshable_list_view_check_spring,ig_android_biz_endpoint_switch,ig_android_direct_continuous_capture,ig_android_comments_direct_reply_to_author,ig_android_profile_visits_in_bio,ig_android_fs_new_gallery,ig_android_remove_follow_all_fb_list,ig_android_vc_webrtc_params,ig_android_specific_story_sharing,ig_android_claim_or_connect_page_on_xpost,ig_android_anr,ig_android_story_viewpoint_impression_event_universe,ig_android_image_exif_metadata_ar_effect_id_universe,ig_android_optic_new_architecture,ig_android_stories_viewer_as_modal_high_end_launch,ig_android_local_info_page,ig_new_eof_demarcator_universe';
+ const LOGIN_EXPERIMENTS = 'ig_android_fci_onboarding_friend_search,ig_android_device_detection_info_upload,ig_android_account_linking_upsell_universe,ig_android_direct_main_tab_universe_v2,ig_android_sms_retriever_backtest_universe,ig_android_direct_add_direct_to_android_native_photo_share_sheet,ig_growth_android_profile_pic_prefill_with_fb_pic_2,ig_account_identity_logged_out_signals_global_holdout_universe,ig_android_login_identifier_fuzzy_match,ig_android_video_render_codec_low_memory_gc,ig_android_custom_transitions_universe,ig_android_push_fcm,ig_android_show_login_info_reminder_universe,ig_android_email_fuzzy_matching_universe,ig_android_one_tap_aymh_redesign_universe,ig_android_direct_send_like_from_notification,ig_android_suma_landing_page,ig_android_session_scoped_logger,ig_android_user_session_scoped_class_opt_universe,ig_android_accoun_switch_badge_fix_universe,ig_android_smartlock_hints_universe,ig_android_black_out,ig_activation_global_discretionary_sms_holdout,ig_android_account_switch_infra_universe,ig_android_video_ffmpegutil_pts_fix,ig_android_multi_tap_login_new,ig_android_caption_typeahead_fix_on_o_universe,ig_android_save_pwd_checkbox_reg_universe,ig_android_nux_add_email_device,ig_android_direct_remove_view_mode_stickiness_universe,ig_username_suggestions_on_username_taken,ig_android_ingestion_video_support_hevc_decoding,ig_android_secondary_account_creation_universe,ig_android_account_recovery_auto_login,ig_android_sim_info_upload,ig_android_mobile_http_flow_device_universe,ig_android_hide_fb_button_when_not_installed_universe,ig_android_targeted_one_tap_upsell_universe,ig_android_gmail_oauth_in_reg,ig_android_account_linking_flow_shorten_universe,ig_android_hide_typeahead_for_logged_users,ig_android_vc_interop_use_test_igid_universe,ig_android_log_suggested_users_cache_on_error,ig_android_reg_modularization_universe,ig_android_phone_edit_distance_universe,ig_android_device_verification_separate_endpoint,ig_android_universe_noticiation_channels,ig_smartlock_login,ig_android_igexecutor_sync_optimization_universe,ig_android_account_linking_skip_value_props_universe,ig_android_account_linking_universe,ig_android_hsite_prefill_new_carrier,ig_android_retry_create_account_universe,ig_android_family_apps_user_values_provider_universe,ig_android_reg_nux_headers_cleanup_universe,ig_android_device_info_foreground_reporting,ig_android_shortcuts_2019,ig_android_device_verification_fb_signup,ig_android_onetaplogin_optimization,ig_video_debug_overlay,ig_android_ask_for_permissions_on_reg,ig_assisted_login_universe,ig_android_display_full_country_name_in_reg_universe,ig_android_security_intent_switchoff,ig_android_device_info_job_based_reporting,ig_android_passwordless_auth,ig_android_direct_main_tab_account_switch,ig_android_modularized_dynamic_nux_universe,ig_android_fb_account_linking_sampling_freq_universe,ig_android_fix_sms_read_lollipop,ig_android_access_flow_prefill';
+ const LAUNCHER_CONFIGS = 'ig_android_media_codec_info_collection,stories_gif_sticker,ig_android_felix_release_players,bloks_binding,ig_android_camera_network_activity_logger,ig_android_os_version_blocking_config,ig_android_carrier_signals_killswitch,live_special_codec_size_list,fbns,ig_android_aed,ig_client_config_server_side_retrieval,ig_android_bloks_perf_logging,ig_user_session_operation,ig_user_mismatch_soft_error,ig_android_prerelease_event_counter,fizz_ig_android,ig_android_vc_clear_task_flag_killswitch,ig_android_killswitch_perm_direct_ssim,ig_android_codec_high_profile,ig_android_smart_prefill_killswitch,sonar_prober,action_bar_layout_width,ig_auth_headers_device,always_use_server_recents';
+ const LAUNCHER_LOGIN_CONFIGS = 'ig_camera_ard_use_ig_downloader,ig_android_dogfooding,ig_android_bloks_data_release,ig_donation_sticker_public_thanks,ig_business_profile_donate_cta_android,ig_launcher_ig_android_network_dispatcher_priority_decider_qe2,ig_multi_decode_config,ig_android_improve_segmentation_hint,ig_android_memory_manager_holdout,ig_android_interactions_direct_sharing_comment_launcher,ig_launcher_ig_android_analytics_request_cap_qe,ig_direct_e2e_send_waterfall_sample_rate_config,ig_android_cdn_image_sizes_config,ig_android_critical_path_manager,ig_android_mobileboost_camera,ig_android_pdp_default_sections,ig_android_video_playback,ig_launcher_explore_sfplt_secondary_response_android,ig_android_upload_heap_on_oom,ig_synchronous_account_switch,ig_android_direct_presence_digest_improvements,ig_android_request_compression_launcher,ig_android_feed_attach_report_logs,ig_android_insights_welcome_dialog_tooltip,ig_android_qp_surveys_v1,ig_direct_requests_approval_config,ig_android_react_native_ota_kill_switch,ig_android_video_profiler_loom_traces,video_call_gk,ig_launcher_ig_android_network_stack_cap_video_request_qe,ig_shopping_android_business_new_tagging_flow,ig_android_igtv_bitrate,ig_android_geo_gating,ig_android_explore_startup_prefetch,ig_android_camera_asset_blocker_config,post_user_cache_user_based,ig_android_branded_content_story_partner_promote_rollout,ig_android_quic,ig_android_videolite_uploader,ig_direct_message_type_reporting_config,ig_camera_android_whitelist_all_effects_in_pre,ig_android_shopping_influencer_creator_nux,ig_android_mobileboost_blacklist,ig_android_direct_gifs_killswitch,ig_android_global_scheduler_direct,ig_android_image_display_logging,ig_android_global_scheduler_infra,ig_igtv_branded_content_killswitch,ig_cg_donor_duplicate_sticker,ig_launcher_explore_verified_badge_on_ads,ig_android_cold_start_class_preloading,ig_camera_android_attributed_effects_endpoint_api_query_config,ig_android_highlighted_products_business_option,ig_direct_join_chat_sticker,ig_android_direct_admin_tools_requests,ig_android_rage_shake_whitelist,ig_android_shopping_ads_cta_rollout,ig_android_igtv_segmentation,ig_launcher_force_switch_on_dialog,ig_android_iab_fullscreen_experience_config,ig_android_instacrash,ig_android_specific_story_url_handling_killswitch,ig_mobile_consent_settings_killswitch,ig_android_influencer_monetization_hub_launcher,ig_and roid_scroll_perf_mobile_boost_launcher,ig_android_cx_stories_about_you,ig_android_replay_safe,ig_android_stories_scroll_perf_misc_fixes_h2_2019,ig_android_shopping_django_product_search,ig_direct_giphy_gifs_rating,ig_android_ppr_url_logging_config,ig_canvas_ad_pixel,ig_strongly_referenced_mediacache,ig_android_direct_show_threads_status_in_direct,ig_camera_ard_brotli_model_compression,ig_image_pipeline_skip_disk_config,ig_android_explore_grid_viewpoint,ig_android_iab_persistent_process,ig_android_in_process_iab,ig_android_launcher_value_consistency_checker,ig_launcher_ig_explore_peek_and_sfplt_android,ig_android_skip_photo_finish,ig_biz_android_use_professional_account_term,ig_android_settings_search,ig_android_direct_presence_media_viewer,ig_launcher_explore_navigation_redesign_android,ig_launcher_ig_android_network_stack_cap_api_request_qe,ig_qe_value_consistency_checker,ig_stories_fundraiser_view_payment_address,ig_business_create_donation_android,ig_android_qp_waterfall_logging,ig_android_bloks_demos,ig_redex_dynamic_analysis,ig_android_bug_report_screen_record,ig_shopping_android_carousel_product_ids_fix_killswitch,ig_shopping_android_creators_new_tagging_flow,ig_android_direct_threads_app_dogfooding_flags,ig_shopping_camera_android,ig_android_qp_keep_promotion_during_cooldown,ig_android_qp_slot_cooldown_enabled_universe,ig_android_request_cap_tuning_with_bandwidth,ig_android_client_config_realtime_subscription,ig_launcher_ig_android_network_request_cap_tuning_qe,ig_android_concurrent_coldstart,ig_android_gps_improvements_launcher,ig_android_notification_setting_sync,ig_android_stories_canvas_mode_colour_wheel,ig_android_iab_session_logging_config,ig_android_network_trace_migration,ig_android_extra_native_debugging_info,ig_android_insights_top_account_dialog_tooltip,ig_launcher_ig_android_dispatcher_viewpoint_onscreen_updater_qe,ig_android_disable_browser_multiple_windows,ig_contact_invites_netego_killswitch,ig_android_update_items_header_height_launcher,ig_android_bulk_tag_untag_killswitch,ig_android_employee_options,ig_launcher_ig_android_video_pending_request_store_qe,ig_story_insights_entry,ig_android_creator_multi_select,ig_android_direct_new_media_viewer,ig_android_gps_profile_launcher,ig_android_direct_real_names_launcher,ig_fev_info_launcher,ig_android_remove_request_params_in_network_trace,ig_android_rageshake_redesign,ig_launcher_ig_android_network_stack_queue_undefined_request_qe,ig_cx_promotion_tooltip,ig_text_response_bottom_sheet,ig_android_carrier_signal_timestamp_max_age,ig_android_qp_xshare_to_fb,ig_android_rollout_gating_payment_settings,ig_android_mobile_boost_kill_switch,ig_android_betamap_cold_start,ig_android_media_store,ig_android_async_view_model_launcher,ig_android_newsfeed_recyclerview,ig_android_feed_optimistic_upload,ig_android_fix_render_backtrack_reporting,ig_delink_lasso_accounts,ig_android_feed_report_ranking_issue,ig_android_shopping_insights_events_validator,ig_biz_android_new_logging_architecture,ig_launcher_ig_android_reactnative_realtime_ota,ig_android_boomerang_crash_android_go,ig_android_shopping_influencer_product_sticker_editing,ig_camera_android_max_vertex_texture_launcher,bloks_suggested_hashtag';
+ const SIG_KEY_VERSION = '4';
+
+ // Endpoint Constants.
+ const BLOCK_VERSIONING_ID = 'a4b4b8345a67599efe117ad96b8a9cb357bb51ac3ee00c3a48be37ce10f2bb4c'; // getTimelineFeed()
+ const BATCH_SURFACES = [
+ ['4715', 'instagram_feed_header'],
+ ['5734', 'instagram_feed_prompt'],
+ ['5858', 'instagram_feed_tool_tip'],
+ ];
+ const BATCH_QUERY = 'viewer() {eligible_promotions.trigger_context_v2().ig_parameters().trigger_name().surface_nux_id().external_gating_permitted_qps().supports_client_filters(true).include_holdouts(true) {edges {client_ttl_seconds,log_eligibility_waterfall,is_holdout,priority,time_range {start,end},node {id,promotion_id,logging_data,max_impressions,triggers,contextual_filters {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}},clauses {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}},clauses {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}},clauses {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}}}}}},is_uncancelable,template {name,parameters {name,required,bool_value,string_value,color_value,}},creatives {title {text},content {text},footer {text},social_context {text},social_context_images,primary_action{title {text},url,limit,dismiss_promotion},secondary_action{title {text},url,limit,dismiss_promotion},dismiss_action{title {text},url,limit,dismiss_promotion},image.scale() {uri,width,height}}}}}}';
+ const BATCH_SCALE = 3;
+ const BATCH_VERSION = 1;
+
+ // User-Agent Constants.
+ const USER_AGENT_LOCALE = 'en_US'; // "language_COUNTRY".
+
+ // HTTP Protocol Constants.
+ const ACCEPT_LANGUAGE = 'en-US'; // "language-COUNTRY".
+ const ACCEPT_ENCODING = 'gzip,deflate';
+ const CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=UTF-8';
+ const X_FB_HTTP_Engine = 'Liger';
+ const X_IG_Connection_Type = 'WIFI';
+ const X_IG_Capabilities = '3brTvw==';
+
+ // Supported Capabilities
+ const SUPPORTED_CAPABILITIES = [
+ [
+ 'name' => 'SUPPORTED_SDK_VERSIONS',
+ 'value' => '13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0,25.0,26.0,27.0,28.0,29.0,30.0,31.0,32.0,33.0,34.0,35.0,36.0,37.0,38.0,39.0,40.0,41.0,42.0,43.0,44.0,45.0,46.0,47.0,48.0,49.0,50.0,51.0,52.0,53.0,54.0,55.0,56.0,57.0,58.0,59.0,60.0,61.0,62.0,63.0,64.0,65.0,66.0,67.0,68.0,69.0',
+ ],
+ [
+ 'name' => 'FACE_TRACKER_VERSION',
+ 'value' => '12',
+ ],
+ [
+ 'name' => 'segmentation',
+ 'value' => 'segmentation_enabled',
+ ],
+ [
+ 'name' => 'COMPRESSION',
+ 'value' => 'ETC2_COMPRESSION',
+ ],
+ [
+ 'name' => 'world_tracker',
+ 'value' => 'world_tracker_enabled',
+ ],
+ [
+ 'name' => 'gyroscope',
+ 'value' => 'gyroscope_enabled',
+ ],
+ ];
+
+ // Facebook Constants.
+ const FACEBOOK_OTA_FIELDS = 'update%7Bdownload_uri%2Cdownload_uri_delta_base%2Cversion_code_delta_base%2Cdownload_uri_delta%2Cfallback_to_full_update%2Cfile_size_delta%2Cversion_code%2Cpublished_date%2Cfile_size%2Cota_bundle_type%2Cresources_checksum%2Callowed_networks%2Crelease_id%7D';
+ const FACEBOOK_ORCA_PROTOCOL_VERSION = 20150314;
+ const FACEBOOK_ORCA_APPLICATION_ID = '124024574287414';
+ const FACEBOOK_ANALYTICS_APPLICATION_ID = '567067343352427';
+
+ // MQTT Constants.
+ const PLATFORM = 'android';
+ const FBNS_APPLICATION_NAME = 'MQTT';
+ const INSTAGRAM_APPLICATION_NAME = 'Instagram';
+ const PACKAGE_NAME = 'com.instagram.android';
+
+ // Instagram Quick Promotions.
+ const SURFACE_PARAM = [
+ 4715,
+ 5734,
+ ];
+
+ // Internal Feedtype Constants. CRITICAL: EVERY value here MUST be unique!
+ const FEED_TIMELINE = 1;
+ const FEED_TIMELINE_ALBUM = 2;
+ const FEED_STORY = 3;
+ const FEED_DIRECT = 4;
+ const FEED_DIRECT_STORY = 5;
+ const FEED_TV = 6;
+
+ // General Constants.
+ const SRC_DIR = __DIR__; // Absolute path to the "src" folder.
+
+ // Story view modes.
+ const STORY_VIEW_MODE_ONCE = 'once';
+ const STORY_VIEW_MODE_REPLAYABLE = 'replayable';
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Debug.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Debug.php
new file mode 100755
index 0000000..6d445c7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Debug.php
@@ -0,0 +1,67 @@
+ 1000) {
+ $response = mb_substr($response, 0, 1000, 'utf8').'...';
+ }
+ echo $res.$response."\n\n";
+ }
+
+ public static function printPostData(
+ $post)
+ {
+ $gzip = mb_strpos($post, "\x1f"."\x8b"."\x08", 0, 'US-ASCII') === 0;
+ if (PHP_SAPI === 'cli') {
+ $dat = Utils::colouredString(($gzip ? 'DECODED ' : '').'DATA: ', 'yellow');
+ } else {
+ $dat = 'DATA: ';
+ }
+ echo $dat.urldecode(($gzip ? zlib_decode($post) : $post))."\n";
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Devices/Device.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Devices/Device.php
new file mode 100755
index 0000000..8f4bbf8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Devices/Device.php
@@ -0,0 +1,268 @@
+_appVersion = $appVersion;
+ $this->_versionCode = $versionCode;
+ $this->_userLocale = $userLocale;
+
+ // Use the provided device if a valid good device. Otherwise use random.
+ if ($autoFallback && (!is_string($deviceString) || !GoodDevices::isGoodDevice($deviceString))) {
+ $deviceString = GoodDevices::getRandomGoodDevice();
+ }
+
+ // Initialize ourselves from the device string.
+ $this->_initFromDeviceString($deviceString);
+ }
+
+ /**
+ * Parses a device string into its component parts and sets internal fields.
+ *
+ * Does no validation to make sure the string is one of the good devices.
+ *
+ * @param string $deviceString
+ *
+ * @throws \RuntimeException If the device string is invalid.
+ */
+ protected function _initFromDeviceString(
+ $deviceString)
+ {
+ if (!is_string($deviceString) || empty($deviceString)) {
+ throw new \RuntimeException('Device string is empty.');
+ }
+
+ // Split the device identifier into its components and verify it.
+ $parts = explode('; ', $deviceString);
+ if (count($parts) !== 7) {
+ throw new \RuntimeException(sprintf('Device string "%s" does not conform to the required device format.', $deviceString));
+ }
+
+ // Check the android version.
+ $androidOS = explode('/', $parts[0], 2);
+ if (version_compare($androidOS[1], self::REQUIRED_ANDROID_VERSION, '<')) {
+ throw new \RuntimeException(sprintf('Device string "%s" does not meet the minimum required Android version "%s" for Instagram.', $deviceString, self::REQUIRED_ANDROID_VERSION));
+ }
+
+ // Check the screen resolution.
+ $resolution = explode('x', $parts[2], 2);
+ $pixelCount = (int) $resolution[0] * (int) $resolution[1];
+ if ($pixelCount < 2073600) { // 1920x1080.
+ throw new \RuntimeException(sprintf('Device string "%s" does not meet the minimum resolution requirement of 1920x1080.', $deviceString));
+ }
+
+ // Extract "Manufacturer/Brand" string into separate fields.
+ $manufacturerAndBrand = explode('/', $parts[3], 2);
+
+ // Store all field values.
+ $this->_deviceString = $deviceString;
+ $this->_androidVersion = $androidOS[0]; // "23".
+ $this->_androidRelease = $androidOS[1]; // "6.0.1".
+ $this->_dpi = $parts[1];
+ $this->_resolution = $parts[2];
+ $this->_manufacturer = $manufacturerAndBrand[0];
+ $this->_brand = (isset($manufacturerAndBrand[1])
+ ? $manufacturerAndBrand[1] : null);
+ $this->_model = $parts[4];
+ $this->_device = $parts[5];
+ $this->_cpu = $parts[6];
+
+ // Build our user agent.
+ $this->_userAgent = UserAgent::buildUserAgent($this->_appVersion, $this->_userLocale, $this);
+
+ $this->_fbUserAgents = [];
+ }
+
+ // Getters for all properties...
+
+ /** {@inheritdoc} */
+ public function getDeviceString()
+ {
+ return $this->_deviceString;
+ }
+
+ /** {@inheritdoc} */
+ public function getUserAgent()
+ {
+ return $this->_userAgent;
+ }
+
+ /** {@inheritdoc} */
+ public function getFbUserAgent(
+ $appName)
+ {
+ if (!isset($this->_fbUserAgents[$appName])) {
+ $this->_fbUserAgents[$appName] = UserAgent::buildFbUserAgent(
+ $appName,
+ $this->_appVersion,
+ $this->_versionCode,
+ $this->_userLocale,
+ $this
+ );
+ }
+
+ return $this->_fbUserAgents[$appName];
+ }
+
+ /** {@inheritdoc} */
+ public function getAndroidVersion()
+ {
+ return $this->_androidVersion;
+ }
+
+ /** {@inheritdoc} */
+ public function getAndroidRelease()
+ {
+ return $this->_androidRelease;
+ }
+
+ /** {@inheritdoc} */
+ public function getDPI()
+ {
+ return $this->_dpi;
+ }
+
+ /** {@inheritdoc} */
+ public function getResolution()
+ {
+ return $this->_resolution;
+ }
+
+ /** {@inheritdoc} */
+ public function getManufacturer()
+ {
+ return $this->_manufacturer;
+ }
+
+ /** {@inheritdoc} */
+ public function getBrand()
+ {
+ return $this->_brand;
+ }
+
+ /** {@inheritdoc} */
+ public function getModel()
+ {
+ return $this->_model;
+ }
+
+ /** {@inheritdoc} */
+ public function getDevice()
+ {
+ return $this->_device;
+ }
+
+ /** {@inheritdoc} */
+ public function getCPU()
+ {
+ return $this->_cpu;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Devices/DeviceInterface.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Devices/DeviceInterface.php
new file mode 100755
index 0000000..413946b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Devices/DeviceInterface.php
@@ -0,0 +1,93 @@
+getManufacturer();
+ if ($device->getBrand() !== null) {
+ $manufacturerWithBrand .= '/'.$device->getBrand();
+ }
+
+ // Generate the final User-Agent string.
+ return sprintf(
+ self::USER_AGENT_FORMAT,
+ $appVersion, // App version ("27.0.0.7.97").
+ $device->getAndroidVersion(),
+ $device->getAndroidRelease(),
+ $device->getDPI(),
+ $device->getResolution(),
+ $manufacturerWithBrand,
+ $device->getModel(),
+ $device->getDevice(),
+ $device->getCPU(),
+ $userLocale, // Locale ("en_US").
+ Constants::VERSION_CODE
+ );
+ }
+
+ /**
+ * Escape string for Facebook User-Agent string.
+ *
+ * @param $string
+ *
+ * @return string
+ */
+ protected static function _escapeFbString(
+ $string)
+ {
+ $result = '';
+ for ($i = 0; $i < strlen($string); ++$i) {
+ $char = $string[$i];
+ if ($char === '&') {
+ $result .= '&';
+ } elseif ($char < ' ' || $char > '~') {
+ $result .= sprintf('%d;', ord($char));
+ } else {
+ $result .= $char;
+ }
+ }
+ $result = strtr($result, ['/' => '-', ';' => '-']);
+
+ return $result;
+ }
+
+ /**
+ * Generates a FB User Agent string from a DeviceInterface.
+ *
+ * @param string $appName Application name.
+ * @param string $appVersion Instagram client app version.
+ * @param string $versionCode Instagram client app version code.
+ * @param string $userLocale The user's locale, such as "en_US".
+ * @param DeviceInterface $device
+ *
+ * @throws \InvalidArgumentException If the device parameter is invalid.
+ *
+ * @return string
+ */
+ public static function buildFbUserAgent(
+ $appName,
+ $appVersion,
+ $versionCode,
+ $userLocale,
+ DeviceInterface $device)
+ {
+ list($width, $height) = explode('x', $device->getResolution());
+ $density = round(str_replace('dpi', '', $device->getDPI()) / 160, 1);
+ $result = [
+ 'FBAN' => $appName,
+ 'FBAV' => $appVersion,
+ 'FBBV' => $versionCode,
+ 'FBDM' => sprintf('{density=%.1f,width=%d,height=%d}', $density, $width, $height),
+ 'FBLC' => $userLocale,
+ 'FBCR' => '', // We don't have cellular.
+ 'FBMF' => self::_escapeFbString($device->getManufacturer()),
+ 'FBBD' => self::_escapeFbString($device->getBrand() ? $device->getBrand() : $device->getManufacturer()),
+ 'FBPN' => Constants::PACKAGE_NAME,
+ 'FBDV' => self::_escapeFbString($device->getModel()),
+ 'FBSV' => self::_escapeFbString($device->getAndroidRelease()),
+ 'FBLR' => 0, // android.hardware.ram.low
+ 'FBBK' => 1, // Const (at least in 10.12.0).
+ 'FBCA' => self::_escapeFbString(GoodDevices::CPU_ABI),
+ ];
+ array_walk($result, function (&$value, $key) {
+ $value = sprintf('%s/%s', $key, $value);
+ });
+
+ // Trailing semicolon is essential.
+ return '['.implode(';', $result).';]';
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/AccountDisabledException.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/AccountDisabledException.php
new file mode 100755
index 0000000..c62a056
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/AccountDisabledException.php
@@ -0,0 +1,7 @@
+_response !== null ? true : false;
+ }
+
+ /**
+ * Get the full server response.
+ *
+ * @return Response|null The full response if one exists, otherwise NULL.
+ *
+ * @see InstagramException::hasResponse()
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Internal. Sets the value of the full server response.
+ *
+ * @param Response|null $response The response value.
+ */
+ public function setResponse(
+ Response $response = null)
+ {
+ $this->_response = $response;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/InternalException.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/InternalException.php
new file mode 100755
index 0000000..0c70bdf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/InternalException.php
@@ -0,0 +1,10 @@
+_guzzleException = $guzzleException;
+
+ // Ensure that the message is nicely formatted and follows our standard.
+ $message = 'Network: '.ServerMessageThrower::prettifyMessage($this->_guzzleException->getMessage());
+
+ // Construct with our custom message.
+ // NOTE: We DON'T assign the guzzleException to "$previous", otherwise
+ // the user would still see something like "Uncaught GuzzleHttp\Exception\
+ // RequestException" and Guzzle's stack trace, instead of "Uncaught
+ // InstagramAPI\Exception\NetworkException" and OUR correct stack trace.
+ parent::__construct($message);
+ }
+
+ /**
+ * Gets the original Guzzle exception, which contains much more details.
+ *
+ * @return \Exception The original Guzzle exception.
+ */
+ public function getGuzzleException()
+ {
+ return $this->_guzzleException;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/NotFoundException.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/NotFoundException.php
new file mode 100755
index 0000000..2bcd379
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/NotFoundException.php
@@ -0,0 +1,11 @@
+ ['login_required'],
+ 'CheckpointRequiredException' => [
+ 'checkpoint_required', // message
+ 'checkpoint_challenge_required', // error_type
+ ],
+ 'ChallengeRequiredException' => ['challenge_required'],
+ 'FeedbackRequiredException' => ['feedback_required'],
+ 'ConsentRequiredException' => ['consent_required'],
+ 'IncorrectPasswordException' => [
+ // "The password you entered is incorrect".
+ '/password(.*?)incorrect/', // message
+ 'bad_password', // error_type
+ ],
+ 'InvalidSmsCodeException' => [
+ // "Please check the security code we sent you and try again".
+ '/check(.*?)security(.*?)code/', // message
+ 'sms_code_validation_code_invalid', // error_type
+ ],
+ 'AccountDisabledException' => [
+ // "Your account has been disabled for violating our terms".
+ '/account(.*?)disabled(.*?)violating/',
+ ],
+ 'SentryBlockException' => ['sentry_block'],
+ 'InvalidUserException' => [
+ // "The username you entered doesn't appear to belong to an account"
+ '/username(.*?)doesn\'t(.*?)belong/', // message
+ 'invalid_user', // error_type
+ ],
+ 'ForcedPasswordResetException' => ['/reset(.*?)password/'],
+ ];
+
+ /**
+ * Parses a server message and throws the appropriate exception.
+ *
+ * Uses the generic EndpointException if no other exceptions match.
+ *
+ * @param string|null $prefixString What prefix to use for
+ * the message in the
+ * final exception. Should
+ * be something helpful
+ * such as the name of the
+ * class or function which
+ * threw. Can be `NULL`.
+ * @param string|null $serverMessage The failure string from
+ * Instagram's API (from
+ * `getMessage()`). Might
+ * be empty in some cases.
+ * @param Response|null $serverResponse The complete server
+ * response object, if one
+ * is available
+ * (optional).
+ * @param HttpResponseInterface|null $httpResponse The HTTP response
+ * object (if available).
+ *
+ * @throws InstagramException The appropriate exception.
+ */
+ public static function autoThrow(
+ $prefixString,
+ $serverMessage,
+ Response $serverResponse = null,
+ HttpResponseInterface $httpResponse = null)
+ {
+ // We will analyze both the `message` AND `error_type` (if available).
+ $messages = [$serverMessage];
+ $serverErrorType = null;
+ if ($serverResponse instanceof Response) {
+ // We are reading a property that isn't defined in the class
+ // property map, so we must use "has" first, to ensure it exists.
+ if ($serverResponse->hasErrorType()
+ && is_string($serverResponse->getErrorType())) {
+ $serverErrorType = $serverResponse->getErrorType();
+ $messages[] = $serverErrorType;
+ }
+ }
+
+ $exceptionClass = null;
+
+ // Check if the server message is in our CRITICAL exception table.
+ foreach ($messages as $message) {
+ foreach (self::EXCEPTION_MAP as $className => $patterns) {
+ foreach ($patterns as $pattern) {
+ if ($pattern[0] == '/') {
+ // Regex check.
+ if (preg_match($pattern, $message)) {
+ $exceptionClass = $className;
+ break 3;
+ }
+ } else {
+ // Regular string search.
+ if (strpos($message, $pattern) !== false) {
+ $exceptionClass = $className;
+ break 3;
+ }
+ }
+ }
+ }
+ }
+
+ // Check the HTTP status code if no critical exception has been found.
+ if ($exceptionClass === null) {
+ // NOTE FOR CONTRIBUTORS: All HTTP status exceptions below MUST be
+ // derived from EndpointException, since all HTTP errors are
+ // endpoint-error-related responses and MUST be easily catchable!
+ $httpStatusCode = $httpResponse !== null ? $httpResponse->getStatusCode() : null;
+ switch ($httpStatusCode) {
+ case 400:
+ $exceptionClass = 'BadRequestException';
+ break;
+ case 404:
+ $exceptionClass = 'NotFoundException';
+ break;
+ default:
+ // No critical exceptions and no HTTP code exceptions have
+ // been found, so use the generic "API function exception"!
+ $exceptionClass = 'EndpointException';
+ }
+ }
+
+ // We need to specify the full namespace path to the exception class.
+ $fullClassPath = '\\'.__NAMESPACE__.'\\'.$exceptionClass;
+
+ // Determine which message to display to the user.
+ $displayMessage = is_string($serverMessage) && strlen($serverMessage)
+ ? $serverMessage : $serverErrorType;
+ if (!is_string($displayMessage) || !strlen($displayMessage)) {
+ $displayMessage = 'Request failed.';
+ }
+
+ // Some Instagram messages already have punctuation, and others need it.
+ $displayMessage = self::prettifyMessage($displayMessage);
+
+ // Create an instance of the final exception class, with the pretty msg.
+ $e = new $fullClassPath(
+ $prefixString !== null
+ ? sprintf('%s: %s', $prefixString, $displayMessage)
+ : $displayMessage
+ );
+
+ // Attach the server response to the exception, IF a response exists.
+ // NOTE: Only possible on exceptions derived from InstagramException.
+ if ($serverResponse instanceof Response
+ && $e instanceof \InstagramAPI\Exception\InstagramException) {
+ $e->setResponse($serverResponse);
+ }
+
+ throw $e;
+ }
+
+ /**
+ * Nicely reformats externally generated exception messages.
+ *
+ * This is used for guaranteeing consistent message formatting with full
+ * English sentences, ready for display to the user.
+ *
+ * @param string $message The original message.
+ *
+ * @return string The cleaned-up message.
+ */
+ public static function prettifyMessage(
+ $message)
+ {
+ // Some messages already have punctuation, and others need it. Prettify
+ // the message by ensuring that it ALWAYS ends in punctuation, for
+ // consistency with all of our internal error messages.
+ $lastChar = substr($message, -1);
+ if ($lastChar !== '' && $lastChar !== '.' && $lastChar !== '!' && $lastChar !== '?') {
+ $message .= '.';
+ }
+
+ // Guarantee that the first letter is uppercase.
+ $message = ucfirst($message);
+
+ // Replace all underscores (ie. "Login_required.") with spaces.
+ $message = str_replace('_', ' ', $message);
+
+ return $message;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/SettingsException.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/SettingsException.php
new file mode 100755
index 0000000..6f2c42c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Exception/SettingsException.php
@@ -0,0 +1,10 @@
+If you truly want to enable incorrect website usage by directly embedding this application emulator library in your page, then you can do that AT YOUR OWN RISK by setting the following flag before you create the Instagram() object:
'.PHP_EOL;
+ exit(0); // Exit without error to avoid triggering Error 500.
+ }
+
+ // Prevent people from running this library on ancient PHP versions, and
+ // verify that people have the most critically important PHP extensions.
+ // NOTE: All of these are marked as requirements in composer.json, but
+ // some people install the library at home and then move it somewhere
+ // else without the requirements, and then blame us for their errors.
+ if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50600) {
+ throw new \InstagramAPI\Exception\InternalException(
+ 'You must have PHP 5.6 or higher to use the Instagram API library.'
+ );
+ }
+ static $extensions = ['curl', 'mbstring', 'gd', 'exif', 'zlib'];
+ foreach ($extensions as $ext) {
+ if (!@extension_loaded($ext)) {
+ throw new \InstagramAPI\Exception\InternalException(sprintf(
+ 'You must have the "%s" PHP extension to use the Instagram API library.',
+ $ext
+ ));
+ }
+ }
+
+ // Debugging options.
+ $this->debug = $debug;
+ $this->truncatedDebug = $truncatedDebug;
+
+ // Load all function collections.
+ $this->account = new Request\Account($this);
+ $this->business = new Request\Business($this);
+ $this->collection = new Request\Collection($this);
+ $this->creative = new Request\Creative($this);
+ $this->direct = new Request\Direct($this);
+ $this->discover = new Request\Discover($this);
+ $this->hashtag = new Request\Hashtag($this);
+ $this->highlight = new Request\Highlight($this);
+ $this->tv = new Request\TV($this);
+ $this->internal = new Request\Internal($this);
+ $this->live = new Request\Live($this);
+ $this->location = new Request\Location($this);
+ $this->media = new Request\Media($this);
+ $this->people = new Request\People($this);
+ $this->push = new Request\Push($this);
+ $this->shopping = new Request\Shopping($this);
+ $this->story = new Request\Story($this);
+ $this->timeline = new Request\Timeline($this);
+ $this->usertag = new Request\Usertag($this);
+
+ // Configure the settings storage and network client.
+ $self = $this;
+ $this->settings = Settings\Factory::createHandler(
+ $storageConfig,
+ [
+ // This saves all user session cookies "in bulk" at script exit
+ // or when switching to a different user, so that it only needs
+ // to write cookies to storage a few times per user session:
+ 'onCloseUser' => function ($storage) use ($self) {
+ if ($self->client instanceof Client) {
+ $self->client->saveCookieJar();
+ }
+ },
+ ]
+ );
+ $this->client = new Client($this);
+ $this->experiments = [];
+ }
+
+ /**
+ * Controls the SSL verification behavior of the Client.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#verify
+ *
+ * @param bool|string $state TRUE to verify using PHP's default CA bundle,
+ * FALSE to disable SSL verification (this is
+ * insecure!), String to verify using this path to
+ * a custom CA bundle file.
+ */
+ public function setVerifySSL(
+ $state)
+ {
+ $this->client->setVerifySSL($state);
+ }
+
+ /**
+ * Gets the current SSL verification behavior of the Client.
+ *
+ * @return bool|string
+ */
+ public function getVerifySSL()
+ {
+ return $this->client->getVerifySSL();
+ }
+
+ /**
+ * Set the proxy to use for requests.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#proxy
+ *
+ * @param string|array|null $value String or Array specifying a proxy in
+ * Guzzle format, or NULL to disable
+ * proxying.
+ */
+ public function setProxy(
+ $value)
+ {
+ $this->client->setProxy($value);
+ }
+
+ /**
+ * Gets the current proxy used for requests.
+ *
+ * @return string|array|null
+ */
+ public function getProxy()
+ {
+ return $this->client->getProxy();
+ }
+
+ /**
+ * Sets the network interface override to use.
+ *
+ * Only works if Guzzle is using the cURL backend. But that's
+ * almost always the case, on most PHP installations.
+ *
+ * @see http://php.net/curl_setopt CURLOPT_INTERFACE
+ *
+ * @param string|null $value Interface name, IP address or hostname, or NULL
+ * to disable override and let Guzzle use any
+ * interface.
+ */
+ public function setOutputInterface(
+ $value)
+ {
+ $this->client->setOutputInterface($value);
+ }
+
+ /**
+ * Gets the current network interface override used for requests.
+ *
+ * @return string|null
+ */
+ public function getOutputInterface()
+ {
+ return $this->client->getOutputInterface();
+ }
+
+ /**
+ * Login to Instagram or automatically resume and refresh previous session.
+ *
+ * Sets the active account for the class instance. You can call this
+ * multiple times to switch between multiple Instagram accounts.
+ *
+ * WARNING: You MUST run this function EVERY time your script runs! It
+ * handles automatic session resume and relogin and app session state
+ * refresh and other absolutely *vital* things that are important if you
+ * don't want to be banned from Instagram!
+ *
+ * WARNING: This function MAY return a CHALLENGE telling you that the
+ * account needs two-factor login before letting you log in! Read the
+ * two-factor login example to see how to handle that.
+ *
+ * @param string $username Your Instagram username.
+ * You can also use your email or phone,
+ * but take in mind that they won't work
+ * when you have two factor auth enabled.
+ * @param string $password Your Instagram password.
+ * @param int $appRefreshInterval How frequently `login()` should act
+ * like an Instagram app that's been
+ * closed and reopened and needs to
+ * "refresh its state", by asking for
+ * extended account state details.
+ * Default: After `1800` seconds, meaning
+ * `30` minutes after the last
+ * state-refreshing `login()` call.
+ * This CANNOT be longer than `6` hours.
+ * Read `_sendLoginFlow()`! The shorter
+ * your delay is the BETTER. You may even
+ * want to set it to an even LOWER value
+ * than the default 30 minutes!
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse|null A login response if a
+ * full (re-)login
+ * happens, otherwise
+ * `NULL` if an existing
+ * session is resumed.
+ */
+ public function login(
+ $username,
+ $password,
+ $appRefreshInterval = 1800)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to login().');
+ }
+
+ return $this->_login($username, $password, false, $appRefreshInterval);
+ }
+
+ /**
+ * Internal login handler.
+ *
+ * @param string $username
+ * @param string $password
+ * @param bool $forceLogin Force login to Instagram instead of
+ * resuming previous session. Used
+ * internally to do a new, full relogin
+ * when we detect an expired/invalid
+ * previous session.
+ * @param int $appRefreshInterval
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse|null
+ *
+ * @see Instagram::login() The public login handler with a full description.
+ */
+ protected function _login(
+ $username,
+ $password,
+ $forceLogin = false,
+ $appRefreshInterval = 1800)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to _login().');
+ }
+
+ // Switch the currently active user/pass if the details are different.
+ if ($this->username !== $username || $this->password !== $password) {
+ $this->_setUser($username, $password);
+ }
+
+ // Perform a full relogin if necessary.
+ if (!$this->isMaybeLoggedIn || $forceLogin) {
+ $this->_sendPreLoginFlow();
+
+ try {
+ $response = $this->request('accounts/login/')
+ ->setNeedsAuth(false)
+ ->addPost('country_codes', '[{"country_code":"1","source":["default"]}]')
+ ->addPost('phone_id', $this->phone_id)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->addPost('username', $this->username)
+ ->addPost('adid', $this->advertising_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('password', $this->password)
+ ->addPost('google_tokens', '[]')
+ ->addPost('login_attempt_count', 0)
+ ->getResponse(new Response\LoginResponse());
+ } catch (\InstagramAPI\Exception\InstagramException $e) {
+ if ($e->hasResponse() && $e->getResponse()->isTwoFactorRequired()) {
+ // Login failed because two-factor login is required.
+ // Return server response to tell user they need 2-factor.
+ return $e->getResponse();
+ } else {
+ // Login failed for some other reason... Re-throw error.
+ throw $e;
+ }
+ }
+
+ $this->_updateLoginState($response);
+
+ $this->_sendLoginFlow(true, $appRefreshInterval);
+
+ // Full (re-)login successfully completed. Return server response.
+ return $response;
+ }
+
+ // Attempt to resume an existing session, or full re-login if necessary.
+ // NOTE: The "return" here gives a LoginResponse in case of re-login.
+ return $this->_sendLoginFlow(false, $appRefreshInterval);
+ }
+
+ /**
+ * Finish a two-factor authenticated login.
+ *
+ * This function finishes a two-factor challenge that was provided by the
+ * regular `login()` function. If you successfully answer their challenge,
+ * you will be logged in after this function call.
+ *
+ * @param string $username Your Instagram username used for logging
+ * @param string $password Your Instagram password.
+ * @param string $twoFactorIdentifier Two factor identifier, obtained in
+ * login() response. Format: `123456`.
+ * @param string $verificationCode Verification code you have received
+ * via SMS.
+ * @param string $verificationMethod The verification method for 2FA. 1 is SMS,
+ * 2 is backup codes and 3 is TOTP.
+ * @param int $appRefreshInterval See `login()` for description of this
+ * parameter.
+ * @param string $usernameHandler Instagram username sent in the login response,
+ * Email and phone aren't allowed here.
+ * Default value is the first argument $username
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse
+ */
+ public function finishTwoFactorLogin(
+ $username,
+ $password,
+ $twoFactorIdentifier,
+ $verificationCode,
+ $verificationMethod = '1',
+ $appRefreshInterval = 1800,
+ $usernameHandler = null)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to finishTwoFactorLogin().');
+ }
+ if (empty($verificationCode) || empty($twoFactorIdentifier)) {
+ throw new \InvalidArgumentException('You must provide a verification code and two-factor identifier to finishTwoFactorLogin().');
+ }
+ if (!in_array($verificationMethod, ['1', '2', '3'], true)) {
+ throw new \InvalidArgumentException('You must provide a valid verification method value.');
+ }
+
+ // Switch the currently active user/pass if the details are different.
+ // NOTE: The username and password AREN'T actually necessary for THIS
+ // endpoint, but this extra step helps people who statelessly embed the
+ // library directly into a webpage, so they can `finishTwoFactorLogin()`
+ // on their second page load without having to begin any new `login()`
+ // call (since they did that in their previous webpage's library calls).
+ if ($this->username !== $username || $this->password !== $password) {
+ $this->_setUser($username, $password);
+ }
+
+ $username = ($usernameHandler !== null) ? $usernameHandler : $username;
+
+ // Remove all whitespace from the verification code.
+ $verificationCode = preg_replace('/\s+/', '', $verificationCode);
+
+ $response = $this->request('accounts/two_factor_login/')
+ ->setNeedsAuth(false)
+ // 1 - SMS, 2 - Backup codes, 3 - TOTP, 0 - ??
+ ->addPost('verification_method', $verificationMethod)
+ ->addPost('verification_code', $verificationCode)
+ ->addPost('trust_this_device', 1)
+ ->addPost('two_factor_identifier', $twoFactorIdentifier)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->addPost('username', $username)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->getResponse(new Response\LoginResponse());
+
+ $this->_updateLoginState($response);
+
+ $this->_sendLoginFlow(true, $appRefreshInterval);
+
+ return $response;
+ }
+
+ /**
+ * Request a new security code SMS for a Two Factor login account.
+ *
+ * NOTE: You should first attempt to `login()` which will automatically send
+ * you a two factor SMS. This function is just for asking for a new SMS if
+ * the old code has expired.
+ *
+ * NOTE: Instagram can only send you a new code every 60 seconds.
+ *
+ * @param string $username Your Instagram username.
+ * @param string $password Your Instagram password.
+ * @param string $twoFactorIdentifier Two factor identifier, obtained in
+ * `login()` response.
+ * @param string $usernameHandler Instagram username sent in the login response,
+ * Email and phone aren't allowed here.
+ * Default value is the first argument $username
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TwoFactorLoginSMSResponse
+ */
+ public function sendTwoFactorLoginSMS(
+ $username,
+ $password,
+ $twoFactorIdentifier,
+ $usernameHandler = null)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to sendTwoFactorLoginSMS().');
+ }
+ if (empty($twoFactorIdentifier)) {
+ throw new \InvalidArgumentException('You must provide a two-factor identifier to sendTwoFactorLoginSMS().');
+ }
+
+ // Switch the currently active user/pass if the details are different.
+ // NOTE: The password IS NOT actually necessary for THIS
+ // endpoint, but this extra step helps people who statelessly embed the
+ // library directly into a webpage, so they can `sendTwoFactorLoginSMS()`
+ // on their second page load without having to begin any new `login()`
+ // call (since they did that in their previous webpage's library calls).
+ if ($this->username !== $username || $this->password !== $password) {
+ $this->_setUser($username, $password);
+ }
+
+ $username = ($usernameHandler !== null) ? $usernameHandler : $username;
+
+ return $this->request('accounts/send_two_factor_login_sms/')
+ ->setNeedsAuth(false)
+ ->addPost('two_factor_identifier', $twoFactorIdentifier)
+ ->addPost('username', $username)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\TwoFactorLoginSMSResponse());
+ }
+
+ /**
+ * Request information about available password recovery methods for an account.
+ *
+ * This will tell you things such as whether SMS or EMAIL-based recovery is
+ * available for the given account name.
+ *
+ * `WARNING:` You can call this function without having called `login()`,
+ * but be aware that a user database entry will be created for every
+ * username you try to look up. This is ONLY meant for recovering your OWN
+ * accounts.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UsersLookupResponse
+ */
+ public function userLookup(
+ $username)
+ {
+ // Set active user (without pwd), and create database entry if new user.
+ $this->_setUserWithoutPassword($username);
+
+ return $this->request('users/lookup/')
+ ->setNeedsAuth(false)
+ ->addPost('q', $username)
+ ->addPost('directly_sign_in', true)
+ ->addPost('username', $username)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\UsersLookupResponse());
+ }
+
+ /**
+ * Request a recovery EMAIL to get back into your account.
+ *
+ * `WARNING:` You can call this function without having called `login()`,
+ * but be aware that a user database entry will be created for every
+ * username you try to look up. This is ONLY meant for recovering your OWN
+ * accounts.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RecoveryResponse
+ */
+ public function sendRecoveryEmail(
+ $username)
+ {
+ // Verify that they can use the recovery email option.
+ $userLookup = $this->userLookup($username);
+ if (!$userLookup->getCanEmailReset()) {
+ throw new \InstagramAPI\Exception\InternalException('Email recovery is not available, since your account lacks a verified email address.');
+ }
+
+ return $this->request('accounts/send_recovery_flow_email/')
+ ->setNeedsAuth(false)
+ ->addPost('query', $username)
+ ->addPost('adid', $this->advertising_id)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\RecoveryResponse());
+ }
+
+ /**
+ * Request a recovery SMS to get back into your account.
+ *
+ * `WARNING:` You can call this function without having called `login()`,
+ * but be aware that a user database entry will be created for every
+ * username you try to look up. This is ONLY meant for recovering your OWN
+ * accounts.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RecoveryResponse
+ */
+ public function sendRecoverySMS(
+ $username)
+ {
+ // Verify that they can use the recovery SMS option.
+ $userLookup = $this->userLookup($username);
+ if (!$userLookup->getHasValidPhone() || !$userLookup->getCanSmsReset()) {
+ throw new \InstagramAPI\Exception\InternalException('SMS recovery is not available, since your account lacks a verified phone number.');
+ }
+
+ return $this->request('users/lookup_phone/')
+ ->setNeedsAuth(false)
+ ->addPost('query', $username)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\RecoveryResponse());
+ }
+
+ /**
+ * Set the active account for the class instance.
+ *
+ * We can call this multiple times to switch between multiple accounts.
+ *
+ * @param string $username Your Instagram username.
+ * @param string $password Your Instagram password.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _setUser(
+ $username,
+ $password)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to _setUser().');
+ }
+
+ // Load all settings from the storage and mark as current user.
+ $this->settings->setActiveUser($username);
+
+ // Generate the user's device instance, which will be created from the
+ // user's last-used device IF they've got a valid, good one stored.
+ // But if they've got a BAD/none, this will create a brand-new device.
+ $savedDeviceString = $this->settings->get('devicestring');
+ $this->device = new Devices\Device(
+ Constants::IG_VERSION,
+ Constants::VERSION_CODE,
+ Constants::USER_AGENT_LOCALE,
+ $savedDeviceString
+ );
+
+ // Get active device string so that we can compare it to any saved one.
+ $deviceString = $this->device->getDeviceString();
+
+ // Generate a brand-new device fingerprint if the device wasn't reused
+ // from settings, OR if any of the stored fingerprints are missing.
+ // NOTE: The regeneration when our device model changes is to avoid
+ // dangerously reusing the "previous phone's" unique hardware IDs.
+ // WARNING TO CONTRIBUTORS: Only add new parameter-checks here if they
+ // are CRITICALLY important to the particular device. We don't want to
+ // frivolously force the users to generate new device IDs constantly.
+ $resetCookieJar = false;
+ if ($deviceString !== $savedDeviceString // Brand new device, or missing
+ || empty($this->settings->get('uuid')) // one of the critically...
+ || empty($this->settings->get('phone_id')) // ...important device...
+ || empty($this->settings->get('device_id'))) { // ...parameters.
+ // Erase all previously stored device-specific settings and cookies.
+ $this->settings->eraseDeviceSettings();
+
+ // Save the chosen device string to settings.
+ $this->settings->set('devicestring', $deviceString);
+
+ // Generate hardware fingerprints for the new device.
+ $this->settings->set('device_id', Signatures::generateDeviceId());
+ $this->settings->set('phone_id', Signatures::generateUUID(true));
+ $this->settings->set('uuid', Signatures::generateUUID(true));
+
+ // Erase any stored account ID, to ensure that we detect ourselves
+ // as logged-out. This will force a new relogin from the new device.
+ $this->settings->set('account_id', '');
+
+ // We'll also need to throw out all previous cookies.
+ $resetCookieJar = true;
+ }
+
+ // Generate other missing values. These are for less critical parameters
+ // that don't need to trigger a complete device reset like above. For
+ // example, this is good for new parameters that Instagram introduces
+ // over time, since those can be added one-by-one over time here without
+ // needing to wipe/reset the whole device.
+ if (empty($this->settings->get('advertising_id'))) {
+ $this->settings->set('advertising_id', Signatures::generateUUID(true));
+ }
+ if (empty($this->settings->get('session_id'))) {
+ $this->settings->set('session_id', Signatures::generateUUID(true));
+ }
+
+ // Store various important parameters for easy access.
+ $this->username = $username;
+ $this->password = $password;
+ $this->uuid = $this->settings->get('uuid');
+ $this->advertising_id = $this->settings->get('advertising_id');
+ $this->device_id = $this->settings->get('device_id');
+ $this->phone_id = $this->settings->get('phone_id');
+ $this->session_id = $this->settings->get('session_id');
+ $this->experiments = $this->settings->getExperiments();
+
+ // Load the previous session details if we're possibly logged in.
+ if (!$resetCookieJar && $this->settings->isMaybeLoggedIn()) {
+ $this->isMaybeLoggedIn = true;
+ $this->account_id = $this->settings->get('account_id');
+ } else {
+ $this->isMaybeLoggedIn = false;
+ $this->account_id = null;
+ }
+
+ // Configures Client for current user AND updates isMaybeLoggedIn state
+ // if it fails to load the expected cookies from the user's jar.
+ // Must be done last here, so that isMaybeLoggedIn is properly updated!
+ // NOTE: If we generated a new device we start a new cookie jar.
+ $this->client->updateFromCurrentSettings($resetCookieJar);
+ }
+
+ /**
+ * Set the active account for the class instance, without knowing password.
+ *
+ * This internal function is used by all unauthenticated pre-login functions
+ * whenever they need to perform unauthenticated requests, such as looking
+ * up a user's account recovery options.
+ *
+ * `WARNING:` A user database entry will be created for every username you
+ * set as the active user, exactly like the normal `_setUser()` function.
+ * This is necessary so that we generate a user-device and data storage for
+ * each given username, which gives us necessary data such as a "device ID"
+ * for the new user's virtual device, to use in various API-call parameters.
+ *
+ * `WARNING:` This function CANNOT be used for performing logins, since
+ * Instagram will validate the password and will reject the missing
+ * password. It is ONLY meant to be used for *RECOVERY* PRE-LOGIN calls that
+ * need device parameters when the user DOESN'T KNOW their password yet.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _setUserWithoutPassword(
+ $username)
+ {
+ if (empty($username) || !is_string($username)) {
+ throw new \InvalidArgumentException('You must provide a username.');
+ }
+
+ // Switch the currently active user/pass if the username is different.
+ // NOTE: Creates a user database (device) for the user if they're new!
+ // NOTE: Because we don't know their password, we'll mark the user as
+ // having "NOPASSWORD" as pwd. The user will fix that when/if they call
+ // `login()` with the ACTUAL password, which will tell us what it is.
+ // We CANNOT use an empty string since `_setUser()` will not allow that!
+ // NOTE: If the user tries to look up themselves WHILE they are logged
+ // in, we'll correctly NOT call `_setUser()` since they're already set.
+ if ($this->username !== $username) {
+ $this->_setUser($username, 'NOPASSWORD');
+ }
+ }
+
+ /**
+ * Updates the internal state after a successful login.
+ *
+ * @param Response\LoginResponse $response The login response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _updateLoginState(
+ Response\LoginResponse $response)
+ {
+ // This check is just protection against accidental bugs. It makes sure
+ // that we always call this function with a *successful* login response!
+ if (!$response instanceof Response\LoginResponse
+ || !$response->isOk()) {
+ throw new \InvalidArgumentException('Invalid login response provided to _updateLoginState().');
+ }
+
+ $this->isMaybeLoggedIn = true;
+ $this->account_id = $response->getLoggedInUser()->getPk();
+ $this->settings->set('account_id', $this->account_id);
+ $this->settings->set('last_login', time());
+ }
+
+ /**
+ * Sends pre-login flow. This is required to emulate real device behavior.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _sendPreLoginFlow()
+ {
+ // Reset zero rating rewrite rules.
+ $this->client->zeroRating()->reset();
+ // Calling this non-token API will put a csrftoken in our cookie
+ // jar. We must do this before any functions that require a token.
+ $this->internal->fetchZeroRatingToken();
+ $this->internal->bootstrapMsisdnHeader();
+ $this->internal->readMsisdnHeader('default');
+ $this->internal->syncDeviceFeatures(true);
+ $this->internal->sendLauncherSync(true);
+ $this->internal->bootstrapMsisdnHeader();
+ $this->internal->logAttribution();
+ $this->account->getPrefillCandidates();
+ $this->internal->readMsisdnHeader('default', true);
+ $this->account->setContactPointPrefill('prefill');
+ $this->internal->sendLauncherSync(true, true, true);
+ $this->internal->syncDeviceFeatures(true, true);
+ }
+
+ /**
+ * Registers available Push channels during the login flow.
+ */
+ protected function _registerPushChannels()
+ {
+ // Forcibly remove the stored token value if >24 hours old.
+ // This prevents us from constantly re-registering the user's
+ // "useless" token if they have stopped using the Push features.
+ try {
+ $lastFbnsToken = (int) $this->settings->get('last_fbns_token');
+ } catch (\Exception $e) {
+ $lastFbnsToken = null;
+ }
+ if (!$lastFbnsToken || $lastFbnsToken < strtotime('-24 hours')) {
+ try {
+ $this->settings->set('fbns_token', '');
+ } catch (\Exception $e) {
+ // Ignore storage errors.
+ }
+
+ return;
+ }
+
+ // Read our token from the storage.
+ try {
+ $fbnsToken = $this->settings->get('fbns_token');
+ } catch (\Exception $e) {
+ $fbnsToken = null;
+ }
+ if ($fbnsToken === null) {
+ return;
+ }
+
+ // Register our last token since we had a fresh (age <24 hours) one,
+ // or clear our stored token if we fail to register it again.
+ try {
+ $this->push->register('mqtt', $fbnsToken);
+ } catch (\Exception $e) {
+ try {
+ $this->settings->set('fbns_token', '');
+ } catch (\Exception $e) {
+ // Ignore storage errors.
+ }
+ }
+ }
+
+ /**
+ * Sends login flow. This is required to emulate real device behavior.
+ *
+ * @param bool $justLoggedIn Whether we have just performed a full
+ * relogin (rather than doing a resume).
+ * @param int $appRefreshInterval See `login()` for description of this
+ * parameter.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse|null A login response if a
+ * full (re-)login is
+ * needed during the login
+ * flow attempt, otherwise
+ * `NULL`.
+ */
+ protected function _sendLoginFlow(
+ $justLoggedIn,
+ $appRefreshInterval = 1800)
+ {
+ if (!is_int($appRefreshInterval) || $appRefreshInterval < 0) {
+ throw new \InvalidArgumentException("Instagram's app state refresh interval must be a positive integer.");
+ }
+ if ($appRefreshInterval > 21600) {
+ throw new \InvalidArgumentException("Instagram's app state refresh interval is NOT allowed to be higher than 6 hours, and the lower the better!");
+ }
+
+ // SUPER IMPORTANT:
+ //
+ // STOP trying to ask us to remove this code section!
+ //
+ // EVERY time the user presses their device's home button to leave the
+ // app and then comes back to the app, Instagram does ALL of these things
+ // to refresh its internal app state. We MUST emulate that perfectly,
+ // otherwise Instagram will silently detect you as a "fake" client
+ // after a while!
+ //
+ // You can configure the login's $appRefreshInterval in the function
+ // parameter above, but you should keep it VERY frequent (definitely
+ // NEVER longer than 6 hours), so that Instagram sees you as a real
+ // client that keeps quitting and opening their app like a REAL user!
+ //
+ // Otherwise they WILL detect you as a bot and silently BLOCK features
+ // or even ban you.
+ //
+ // You have been warned.
+ if ($justLoggedIn) {
+ // Reset zero rating rewrite rules.
+ $this->client->zeroRating()->reset();
+ // Perform the "user has just done a full login" API flow.
+ $this->account->getAccountFamily();
+ $this->internal->sendLauncherSync(false, false, true);
+ $this->internal->fetchZeroRatingToken();
+ $this->internal->syncUserFeatures();
+ $this->timeline->getTimelineFeed();
+ $this->story->getReelsTrayFeed('cold_start');
+ $this->internal->sendLauncherSync(false, false, true, true);
+ $this->story->getReelsMediaFeed($this->account_id);
+ $this->people->getRecentActivityInbox();
+ //TODO: Figure out why this isn't sending...
+// $this->internal->logResurrectAttribution();
+ $this->internal->getLoomFetchConfig();
+ $this->internal->getDeviceCapabilitiesDecisions();
+ $this->people->getBootstrapUsers();
+ $this->people->getInfoById($this->account_id);
+ $this->account->getLinkageStatus();
+ $this->creative->sendSupportedCapabilities();
+ $this->media->getBlockedMedia();
+ $this->internal->storeClientPushPermissions();
+ $this->internal->getQPCooldowns();
+ $this->_registerPushChannels();
+ $this->story->getReelsMediaFeed($this->account_id);
+ $this->discover->getExploreFeed(null, null, true);
+ $this->internal->getQPFetch();
+ $this->account->getProcessContactPointSignals();
+ $this->internal->getArlinkDownloadInfo();
+ $this->_registerPushChannels();
+ $this->people->getSharePrefill();
+ $this->direct->getPresences();
+ $this->direct->getInbox();
+ $this->direct->getInbox(null, 20, 10);
+ $this->_registerPushChannels();
+ $this->internal->getFacebookOTA();
+ } else {
+ $lastLoginTime = $this->settings->get('last_login');
+ $isSessionExpired = $lastLoginTime === null || (time() - $lastLoginTime) > $appRefreshInterval;
+
+ // Act like a real logged in app client refreshing its news timeline.
+ // This also lets us detect if we're still logged in with a valid session.
+ if ($isSessionExpired) {
+ // Act like a real logged in app client refreshing its news timeline.
+ // This also lets us detect if we're still logged in with a valid session.
+ try {
+ $this->story->getReelsTrayFeed('cold_start');
+ } catch (\InstagramAPI\Exception\LoginRequiredException $e) {
+ // If our session cookies are expired, we were now told to login,
+ // so handle that by running a forced relogin in that case!
+ return $this->_login($this->username, $this->password, true, $appRefreshInterval);
+ }
+ $this->timeline->getTimelineFeed(null, [
+ 'is_pull_to_refresh' => $isSessionExpired ? null : mt_rand(1, 3) < 3,
+ ]);
+ $this->people->getSharePrefill();
+ $this->people->getRecentActivityInbox();
+
+ $this->people->getSharePrefill();
+ $this->people->getRecentActivityInbox();
+ $this->people->getInfoById($this->account_id);
+ $this->internal->getDeviceCapabilitiesDecisions();
+ $this->direct->getPresences();
+ $this->discover->getExploreFeed();
+ $this->direct->getInbox();
+
+ $this->settings->set('last_login', time());
+ // Generate and save a new application session ID.
+ $this->session_id = Signatures::generateUUID();
+ $this->settings->set('session_id', $this->session_id);
+ // Do the rest of the "user is re-opening the app" API flow...
+ $this->tv->getTvGuide();
+
+ $this->internal->getLoomFetchConfig();
+ $this->direct->getRankedRecipients('reshare', true);
+ $this->direct->getRankedRecipients('raven', true);
+ $this->_registerPushChannels();
+ }
+
+ // Users normally resume their sessions, meaning that their
+ // experiments never get synced and updated. So sync periodically.
+ $lastExperimentsTime = $this->settings->get('last_experiments');
+ if ($lastExperimentsTime === null || (time() - $lastExperimentsTime) > self::EXPERIMENTS_REFRESH) {
+ $this->internal->syncUserFeatures();
+ $this->internal->syncDeviceFeatures();
+ }
+
+ // Update zero rating token when it has been expired.
+ $expired = time() - (int) $this->settings->get('zr_expires');
+ if ($expired > 0) {
+ $this->client->zeroRating()->reset();
+ $this->internal->fetchZeroRatingToken($expired > 7200 ? 'token_stale' : 'token_expired');
+ }
+ }
+
+ // We've now performed a login or resumed a session. Forcibly write our
+ // cookies to the storage, to ensure that the storage doesn't miss them
+ // in case something bad happens to PHP after this moment.
+ $this->client->saveCookieJar();
+
+ return null;
+ }
+
+ /**
+ * Log out of Instagram.
+ *
+ * WARNING: Most people should NEVER call `logout()`! Our library emulates
+ * the Instagram app for Android, where you are supposed to stay logged in
+ * forever. By calling this function, you will tell Instagram that you are
+ * logging out of the APP. But you SHOULDN'T do that! In almost 100% of all
+ * cases you want to *stay logged in* so that `login()` resumes your session!
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LogoutResponse
+ *
+ * @see Instagram::login()
+ */
+ public function logout()
+ {
+ $response = $this->request('accounts/logout/')
+ ->setSignedPost(false)
+ ->addPost('phone_id', $this->phone_id)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->addPost('guid', $this->uuid)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('_uuid', $this->uuid)
+ ->getResponse(new Response\LogoutResponse());
+
+ // We've now logged out. Forcibly write our cookies to the storage, to
+ // ensure that the storage doesn't miss them in case something bad
+ // happens to PHP after this moment.
+ $this->client->saveCookieJar();
+
+ return $response;
+ }
+
+ /**
+ * Checks if a parameter is enabled in the given experiment.
+ *
+ * @param string $experiment
+ * @param string $param
+ * @param bool $default
+ *
+ * @return bool
+ */
+ public function isExperimentEnabled(
+ $experiment,
+ $param,
+ $default = false)
+ {
+ return isset($this->experiments[$experiment][$param])
+ ? in_array($this->experiments[$experiment][$param], ['enabled', 'true', '1'])
+ : $default;
+ }
+
+ /**
+ * Get a parameter value for the given experiment.
+ *
+ * @param string $experiment
+ * @param string $param
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getExperimentParam(
+ $experiment,
+ $param,
+ $default = null)
+ {
+ return isset($this->experiments[$experiment][$param])
+ ? $this->experiments[$experiment][$param]
+ : $default;
+ }
+
+ /**
+ * Create a custom API request.
+ *
+ * Used internally, but can also be used by end-users if they want
+ * to create completely custom API queries without modifying this library.
+ *
+ * @param string $url
+ *
+ * @return \InstagramAPI\Request
+ */
+ public function request(
+ $url)
+ {
+ return new Request($this, $url);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/InstagramID.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/InstagramID.php
new file mode 100755
index 0000000..d26f3c0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/InstagramID.php
@@ -0,0 +1,249 @@
+_width = (int) $width;
+ $this->_height = (int) $height;
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $this->_aspectRatio = (float) ($this->_width / $this->_height);
+ }
+
+ /**
+ * Get stored width for these dimensions.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->_width;
+ }
+
+ /**
+ * Get stored height for these dimensions.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->_height;
+ }
+
+ /**
+ * Get stored aspect ratio for these dimensions.
+ *
+ * @return float
+ */
+ public function getAspectRatio()
+ {
+ return $this->_aspectRatio;
+ }
+
+ /**
+ * Create a new object with swapped axes.
+ *
+ * @return self
+ */
+ public function withSwappedAxes()
+ {
+ return new self($this->_height, $this->_width);
+ }
+
+ /**
+ * Create a new, scale-adjusted object.
+ *
+ * @param float|int $newScale The scale factor to apply.
+ * @param string $roundingFunc One of `round` (default), `floor` or `ceil`.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function withRescaling(
+ $newScale = 1.0,
+ $roundingFunc = 'round')
+ {
+ if (!is_float($newScale) && !is_int($newScale)) {
+ throw new \InvalidArgumentException('The new scale must be a float or integer.');
+ }
+ if ($roundingFunc !== 'round' && $roundingFunc !== 'floor' && $roundingFunc !== 'ceil') {
+ throw new \InvalidArgumentException(sprintf('Invalid rounding function "%s".', $roundingFunc));
+ }
+
+ $newWidth = (int) $roundingFunc($newScale * $this->_width);
+ $newHeight = (int) $roundingFunc($newScale * $this->_height);
+
+ return new self($newWidth, $newHeight);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Geometry/Rectangle.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Geometry/Rectangle.php
new file mode 100755
index 0000000..513a657
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Geometry/Rectangle.php
@@ -0,0 +1,179 @@
+_x = (int) $x;
+ $this->_y = (int) $y;
+ $this->_width = (int) $width;
+ $this->_height = (int) $height;
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $this->_aspectRatio = (float) ($this->_width / $this->_height);
+ }
+
+ /**
+ * Get stored X1 offset for this rectangle.
+ *
+ * @return int
+ */
+ public function getX()
+ {
+ return $this->_x;
+ }
+
+ /**
+ * Get stored Y1 offset for this rectangle.
+ *
+ * @return int
+ */
+ public function getY()
+ {
+ return $this->_y;
+ }
+
+ /**
+ * Get stored X1 offset for this rectangle.
+ *
+ * This does the same thing as `getX()`. It is just a mental
+ * convenience when working in X1/X2 space.
+ *
+ * @return int
+ */
+ public function getX1()
+ {
+ return $this->_x;
+ }
+
+ /**
+ * Get stored Y1 offset for this rectangle.
+ *
+ * This does the same thing as `getY()`. It is just a mental
+ * convenience when working in Y1/Y2 space.
+ *
+ * @return int
+ */
+ public function getY1()
+ {
+ return $this->_y;
+ }
+
+ /**
+ * Get calculated X2 offset (X1+Width) for this rectangle.
+ *
+ * @return int
+ */
+ public function getX2()
+ {
+ return $this->_x + $this->_width;
+ }
+
+ /**
+ * Get calculated Y2 offset (Y1+Height) for this rectangle.
+ *
+ * @return int
+ */
+ public function getY2()
+ {
+ return $this->_y + $this->_height;
+ }
+
+ /**
+ * Get stored width for this rectangle.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->_width;
+ }
+
+ /**
+ * Get stored height for this rectangle.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->_height;
+ }
+
+ /**
+ * Get stored aspect ratio for this rectangle.
+ *
+ * @return float
+ */
+ public function getAspectRatio()
+ {
+ return $this->_aspectRatio;
+ }
+
+ /**
+ * Create a new object with swapped axes.
+ *
+ * @return self
+ */
+ public function withSwappedAxes()
+ {
+ return new self($this->_y, $this->_x, $this->_height, $this->_width);
+ }
+
+ /**
+ * Create a new, scale-adjusted object.
+ *
+ * NOTE: The x1/y1 offsets are not affected. Only the width and height. But
+ * those new dimensions WILL affect the x2/y2 offsets, as you'd expect.
+ *
+ * @param float|int $newScale The scale factor to apply.
+ * @param string $roundingFunc One of `round` (default), `floor` or `ceil`.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function withRescaling(
+ $newScale = 1.0,
+ $roundingFunc = 'round')
+ {
+ if (!is_float($newScale) && !is_int($newScale)) {
+ throw new \InvalidArgumentException('The new scale must be a float or integer.');
+ }
+ if ($roundingFunc !== 'round' && $roundingFunc !== 'floor' && $roundingFunc !== 'ceil') {
+ throw new \InvalidArgumentException(sprintf('Invalid rounding function "%s".', $roundingFunc));
+ }
+
+ $newWidth = (int) $roundingFunc($newScale * $this->_width);
+ $newHeight = (int) $roundingFunc($newScale * $this->_height);
+
+ return new self($this->_x, $this->_y, $newWidth, $newHeight);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/InstagramMedia.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/InstagramMedia.php
new file mode 100755
index 0000000..02ed801
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/InstagramMedia.php
@@ -0,0 +1,1456 @@
+_debug = $debug === true;
+
+ // Input file.
+ if (!is_file($inputFile)) {
+ throw new \InvalidArgumentException(sprintf('Input file "%s" doesn\'t exist.', $inputFile));
+ }
+ $this->_inputFile = $inputFile;
+
+ // Horizontal crop focus.
+ if ($horCropFocus !== null && (!is_int($horCropFocus) || $horCropFocus < -50 || $horCropFocus > 50)) {
+ throw new \InvalidArgumentException('Horizontal crop focus must be between -50 and 50.');
+ }
+ $this->_horCropFocus = $horCropFocus;
+
+ // Vertical crop focus.
+ if ($verCropFocus !== null && (!is_int($verCropFocus) || $verCropFocus < -50 || $verCropFocus > 50)) {
+ throw new \InvalidArgumentException('Vertical crop focus must be between -50 and 50.');
+ }
+ $this->_verCropFocus = $verCropFocus;
+
+ // Minimum and maximum aspect ratio range.
+ if ($minAspectRatio !== null && !is_float($minAspectRatio)) {
+ throw new \InvalidArgumentException('Minimum aspect ratio must be a floating point number.');
+ }
+ if ($maxAspectRatio !== null && !is_float($maxAspectRatio)) {
+ throw new \InvalidArgumentException('Maximum aspect ratio must be a floating point number.');
+ }
+
+ // Does the user want to override (force) the final "target aspect ratio" choice?
+ // NOTE: This will be used to override `$this->_forceTargetAspectRatio`.
+ $this->_hasUserForceTargetAspectRatio = false;
+ if ($userForceTargetAspectRatio !== null) {
+ if (!is_float($userForceTargetAspectRatio) && !is_int($userForceTargetAspectRatio)) {
+ throw new \InvalidArgumentException('Custom target aspect ratio must be a float or integer.');
+ }
+ $userForceTargetAspectRatio = (float) $userForceTargetAspectRatio;
+ $this->_hasUserForceTargetAspectRatio = true;
+ $useRecommendedRatio = false; // We forcibly disable this too, to avoid risk of future bugs.
+ }
+
+ // Create constraints and determine whether to use "recommended target aspect ratio" (if one is available for feed).
+ $this->_constraints = ConstraintsFactory::createFor($targetFeed);
+ if (!$this->_hasUserForceTargetAspectRatio && $useRecommendedRatio === null) {
+ // No value is provided, so let's guess it.
+ if ($minAspectRatio !== null || $maxAspectRatio !== null) {
+ // If we have at least one custom ratio, we must not use recommended ratio.
+ $useRecommendedRatio = false;
+ } else {
+ // Use the recommended value from constraints (either on or off, depending on which target feed).
+ $useRecommendedRatio = $this->_constraints->useRecommendedRatioByDefault();
+ }
+ }
+
+ // Determine the legal min/max aspect ratios for the target feed.
+ if (!$this->_hasUserForceTargetAspectRatio && $useRecommendedRatio === true) {
+ $this->_forceTargetAspectRatio = $this->_constraints->getRecommendedRatio();
+ $deviation = $this->_constraints->getRecommendedRatioDeviation();
+ $minAspectRatio = $this->_forceTargetAspectRatio - $deviation;
+ $maxAspectRatio = $this->_forceTargetAspectRatio + $deviation;
+ } else {
+ // If the user hasn't specified a custom target aspect ratio, this
+ // "force" value will remain NULL (and the target ratio will be
+ // auto-calculated by the canvas generation algorithms instead).
+ $this->_forceTargetAspectRatio = $userForceTargetAspectRatio;
+ $allowedMinRatio = $this->_constraints->getMinAspectRatio();
+ $allowedMaxRatio = $this->_constraints->getMaxAspectRatio();
+
+ // Select allowed aspect ratio range based on defaults and user input.
+ if ($minAspectRatio !== null && ($minAspectRatio < $allowedMinRatio || $minAspectRatio > $allowedMaxRatio)) {
+ throw new \InvalidArgumentException(sprintf('Minimum aspect ratio must be between %.3f and %.3f.',
+ $allowedMinRatio, $allowedMaxRatio));
+ }
+ if ($minAspectRatio === null) {
+ $minAspectRatio = $allowedMinRatio;
+ }
+ if ($maxAspectRatio !== null && ($maxAspectRatio < $allowedMinRatio || $maxAspectRatio > $allowedMaxRatio)) {
+ throw new \InvalidArgumentException(sprintf('Maximum aspect ratio must be between %.3f and %.3f.',
+ $allowedMinRatio, $allowedMaxRatio));
+ }
+ if ($maxAspectRatio === null) {
+ $maxAspectRatio = $allowedMaxRatio;
+ }
+ if ($minAspectRatio !== null && $maxAspectRatio !== null && $minAspectRatio > $maxAspectRatio) {
+ throw new \InvalidArgumentException('Maximum aspect ratio must be greater than or equal to minimum.');
+ }
+
+ // Validate custom target aspect ratio legality if provided by user.
+ if ($this->_hasUserForceTargetAspectRatio) {
+ if ($minAspectRatio !== null && $this->_forceTargetAspectRatio < $minAspectRatio) {
+ throw new \InvalidArgumentException(sprintf('Custom target aspect ratio (%.5f) must be greater than or equal to the minimum aspect ratio (%.5f).',
+ $this->_forceTargetAspectRatio, $minAspectRatio));
+ }
+ if ($maxAspectRatio !== null && $this->_forceTargetAspectRatio > $maxAspectRatio) {
+ throw new \InvalidArgumentException(sprintf('Custom target aspect ratio (%.5f) must be lesser than or equal to the maximum aspect ratio (%.5f).',
+ $this->_forceTargetAspectRatio, $maxAspectRatio));
+ }
+ }
+ }
+ $this->_minAspectRatio = $minAspectRatio;
+ $this->_maxAspectRatio = $maxAspectRatio;
+
+ // Allow the aspect ratio of the final, new canvas to deviate slightly from the min/max range?
+ $this->_allowNewAspectDeviation = $allowNewAspectDeviation;
+
+ // Background color.
+ if ($bgColor !== null && (!is_array($bgColor) || count($bgColor) !== 3 || !isset($bgColor[0]) || !isset($bgColor[1]) || !isset($bgColor[2]))) {
+ throw new \InvalidArgumentException('The background color must be a 3-element array [R, G, B].');
+ } elseif ($bgColor === null) {
+ $bgColor = [255, 255, 255]; // White.
+ }
+ $this->_bgColor = $bgColor;
+
+ //Blurred border
+ $this->_blurredBorder = $blurredBorder;
+
+ // Media operation.
+ if ($operation !== self::CROP && $operation !== self::EXPAND) {
+ throw new \InvalidArgumentException('The operation must be one of the class constants CROP or EXPAND.');
+ }
+ $this->_operation = $operation;
+
+ // Temporary directory path.
+ if ($tmpPath === null) {
+ $tmpPath = self::$defaultTmpPath !== null
+ ? self::$defaultTmpPath
+ : sys_get_temp_dir();
+ }
+ if (!is_dir($tmpPath) || !is_writable($tmpPath)) {
+ throw new \InvalidArgumentException(sprintf('Directory %s does not exist or is not writable.', $tmpPath));
+ }
+ $this->_tmpPath = realpath($tmpPath);
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->deleteFile();
+ }
+
+ /**
+ * Removes the output file if it exists and differs from input file.
+ *
+ * This function is safe and won't delete the original input file.
+ *
+ * Is automatically called when the class instance is destroyed by PHP.
+ * But you can manually call it ahead of time if you want to force cleanup.
+ *
+ * Note that getFile() will still work afterwards, but will have to process
+ * the media again to a new temp file if the input file required processing.
+ *
+ * @return bool
+ */
+ public function deleteFile()
+ {
+ // Only delete if outputfile exists and isn't the same as input file.
+ if ($this->_outputFile !== null && $this->_outputFile !== $this->_inputFile && is_file($this->_outputFile)) {
+ $result = @unlink($this->_outputFile);
+ $this->_outputFile = null; // Reset so getFile() will work again.
+ return $result;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the path to a media file matching the requirements.
+ *
+ * The automatic processing is performed the first time that this function
+ * is called. Which means that no CPU time is wasted if you never call this
+ * function at all.
+ *
+ * Due to the processing, the first call to this function may take a moment.
+ *
+ * If the input file already fits all of the specifications, we simply
+ * return the input path instead, without any need to re-process it.
+ *
+ * @throws \Exception
+ * @throws \RuntimeException
+ *
+ * @return string The path to the media file.
+ *
+ * @see InstagramMedia::_shouldProcess() The criteria that determines processing.
+ */
+ public function getFile()
+ {
+ if ($this->_outputFile === null) {
+ $this->_outputFile = $this->_shouldProcess() ? $this->_process() : $this->_inputFile;
+ }
+
+ return $this->_outputFile;
+ }
+
+ /**
+ * Checks whether we should process the input file.
+ *
+ * @return bool
+ */
+ protected function _shouldProcess()
+ {
+ $inputAspectRatio = $this->_details->getAspectRatio();
+
+ // Process if aspect ratio < minimum allowed.
+ if ($this->_minAspectRatio !== null && $inputAspectRatio < $this->_minAspectRatio) {
+ return true;
+ }
+
+ // Process if aspect ratio > maximum allowed.
+ if ($this->_maxAspectRatio !== null && $inputAspectRatio > $this->_maxAspectRatio) {
+ return true;
+ }
+
+ // Process if USER provided the custom aspect ratio target and input deviates too much.
+ if ($this->_hasUserForceTargetAspectRatio) {
+ if ($this->_forceTargetAspectRatio == 1.0) {
+ // User wants a SQUARE canvas, which can ALWAYS be achieved (by
+ // making both sides equal). Process input if not EXACTLY square.
+ // WARNING: Comparison here and above MUST use `!=` (NOT strict
+ // `!==`) to support both int(1) and float(1.0) values!
+ if ($inputAspectRatio != 1.0) {
+ return true;
+ }
+ } else {
+ // User wants a non-square canvas, which is almost always
+ // IMPOSSIBLE to achieve perfectly. Only process if input
+ // deviates too much from the desired target.
+ $acceptableDeviation = 0.003; // Allow a very narrow range around the user's target.
+ $acceptableMinAspectRatio = $this->_forceTargetAspectRatio - $acceptableDeviation;
+ $acceptableMaxAspectRatio = $this->_forceTargetAspectRatio + $acceptableDeviation;
+ if ($inputAspectRatio < $acceptableMinAspectRatio || $inputAspectRatio > $acceptableMaxAspectRatio) {
+ return true;
+ }
+ }
+ }
+
+ // Process if the media can't be uploaded to Instagram as is.
+ // NOTE: Nobody is allowed to call `isMod2CanvasRequired()` here. That
+ // isn't its purpose. Whether a final Mod2 canvas is required for actual
+ // resizing has NOTHING to do with whether the input file is ok.
+ try {
+ $this->_details->validate($this->_constraints);
+
+ return false;
+ } catch (\Exception $e) {
+ return true;
+ }
+ }
+
+ /**
+ * Whether this processor requires Mod2 width and height canvas dimensions.
+ *
+ * If this returns FALSE, the calculated `InstagramMedia` canvas passed to
+ * this processor _may_ contain uneven width and/or height as the selected
+ * output dimensions.
+ *
+ * Therefore, this function must return TRUE if (and ONLY IF) perfectly even
+ * dimensions are necessary for this particular processor's output format.
+ *
+ * For example, JPEG images accept any dimensions and must therefore return
+ * FALSE. But H264 videos require EVEN dimensions and must return TRUE.
+ *
+ * @return bool
+ */
+ abstract protected function _isMod2CanvasRequired();
+
+ /**
+ * Process the input file and create the new file.
+ *
+ * @throws \RuntimeException
+ *
+ * @return string The path to the new file.
+ */
+ protected function _process()
+ {
+ // Get the dimensions of the original input file.
+ $inputCanvas = new Dimensions($this->_details->getWidth(), $this->_details->getHeight());
+
+ // Create an output canvas with the desired dimensions.
+ // WARNING: This creates a LEGAL canvas which MUST be followed EXACTLY.
+ $canvasInfo = $this->_calculateNewCanvas( // Throws.
+ $this->_operation,
+ $inputCanvas->getWidth(),
+ $inputCanvas->getHeight(),
+ $this->_isMod2CanvasRequired(),
+ $this->_details->getMinAllowedWidth(),
+ $this->_details->getMaxAllowedWidth(),
+ $this->_minAspectRatio,
+ $this->_maxAspectRatio,
+ $this->_forceTargetAspectRatio,
+ $this->_allowNewAspectDeviation
+ );
+ $outputCanvas = $canvasInfo['canvas'];
+
+ // Determine the media operation's resampling parameters and perform it.
+ // NOTE: This section is EXCESSIVELY commented to explain each step. The
+ // algorithm is pretty easy after you understand it. But without the
+ // detailed comments, future contributors may not understand any of it!
+ // "We'd rather have a WaLL oF TeXt for future reference, than bugs due
+ // to future misunderstandings!" - SteveJobzniak ;-)
+ if ($this->_operation === self::CROP) {
+ // Determine the IDEAL canvas dimensions as if Mod2 adjustments were
+ // not applied. That's NECESSARY for calculating an ACCURATE scale-
+ // change compared to the input, so that we can calculate how much
+ // the canvas has rescaled. WARNING: These are 1-dimensional scales,
+ // and only ONE value (the uncropped side) is valid for comparison.
+ $idealCanvas = new Dimensions($outputCanvas->getWidth() - $canvasInfo['mod2WidthDiff'],
+ $outputCanvas->getHeight() - $canvasInfo['mod2HeightDiff']);
+ $idealWidthScale = (float) ($idealCanvas->getWidth() / $inputCanvas->getWidth());
+ $idealHeightScale = (float) ($idealCanvas->getHeight() / $inputCanvas->getHeight());
+ $this->_debugDimensions(
+ $inputCanvas->getWidth(), $inputCanvas->getHeight(),
+ 'CROP: Analyzing Original Input Canvas Size'
+ );
+ $this->_debugDimensions(
+ $idealCanvas->getWidth(), $idealCanvas->getHeight(),
+ 'CROP: Analyzing Ideally Cropped (Non-Mod2-adjusted) Output Canvas Size'
+ );
+ $this->_debugText(
+ 'CROP: Scale of Ideally Cropped Canvas vs Input Canvas',
+ 'width=%.8f, height=%.8f',
+ $idealWidthScale, $idealHeightScale
+ );
+
+ // Now determine HOW the IDEAL canvas has been cropped compared to
+ // the INPUT canvas. But we can't just compare dimensions, since our
+ // algorithms may have cropped and THEN scaled UP the dimensions to
+ // legal values far above the input values, or scaled them DOWN and
+ // then Mod2-cropped at the new scale, etc. There are so many
+ // possibilities. That's also why we couldn't "just keep track of
+ // amount of pixels cropped during main algorithm". We MUST figure
+ // it out ourselves accurately HERE. We can't do it at any earlier
+ // stage, since cumulative rounding errors from width/height
+ // readjustments could drift us away from the target aspect ratio
+ // and could prevent pixel-perfect results UNLESS we calc it HERE.
+ //
+ // There's IS a great way to figure out the cropping. When the WIDTH
+ // of a canvas is reduced (making it more "portraity"), its aspect
+ // ratio number decreases. When the HEIGHT of a canvas is reduced
+ // (making it more "landscapey"), its aspect ratio number increases.
+ //
+ // And our canvas cropping algorithm only crops in ONE DIRECTION
+ // (width or height), so we only need to detect the aspect ratio
+ // change of the IDEAL (non-Mod2-adjusted) canvas, to know what
+ // happened. However, note that this CAN also trigger if the input
+ // had to be up/downscaled (to an imperfect final aspect), but that
+ // doesn't matter since this algorithm will STILL figure out the
+ // proper scale and croppings to use for the canvas. Because uneven,
+ // aspect-affecting scaling basically IS cropping the INPUT canvas!
+ if ($idealCanvas->getAspectRatio() === $inputCanvas->getAspectRatio()) {
+ // No sides have been cropped. So both width and height scales
+ // WILL be IDENTICAL, since NOTHING else would be able to create
+ // an identical aspect ratio again (otherwise the aspect ratio
+ // would have been warped (not equal)). So just pick either one.
+ // NOTE: Identical (uncropped ratio) DOESN'T mean that scale is
+ // going to be 1.0. It MAY be. Or the canvas MAY have been
+ // evenly expanded or evenly shrunk in both dimensions.
+ $hasCropped = 'nothing';
+ $overallRescale = $idealWidthScale; // $idealHeightScale IS identical.
+ } elseif ($idealCanvas->getAspectRatio() < $inputCanvas->getAspectRatio()) {
+ // The horizontal width has been cropped. Grab the height's
+ // scale, since that side is "unaffected" by the main cropping
+ // and should therefore have a scale of 1. Although it may have
+ // had up/down-scaling. In that case, the height scale will
+ // represent the amount of overall rescale change.
+ $hasCropped = 'width';
+ $overallRescale = $idealHeightScale;
+ } else { // Output aspect is > input.
+ // The vertical height has been cropped. Just like above, the
+ // "unaffected" side is what we'll use as our scale reference.
+ $hasCropped = 'height';
+ $overallRescale = $idealWidthScale;
+ }
+ $this->_debugText(
+ 'CROP: Detecting Cropped Direction',
+ 'cropped=%s, overallRescale=%.8f',
+ $hasCropped, $overallRescale
+ );
+
+ // Alright, now calculate the dimensions of the "IDEALLY CROPPED
+ // INPUT canvas", at INPUT canvas scale. These are the scenarios:
+ //
+ // - "hasCropped: nothing, scale is 1.0" = Nothing was cropped, and
+ // nothing was scaled. Treat as "use whole INPUT canvas". This is
+ // pixel-perfect.
+ //
+ // - "hasCropped: nothing, scale NOT 1.0" = Nothing was cropped, but
+ // the whole canvas was up/down-scaled. We don't have to care at
+ // all about that scaling and should treat it as "use whole INPUT
+ // canvas" for crop calculation purposes. The cropped result will
+ // later be scaled/stretched to the canvas size (up or down).
+ //
+ // - "hasCropped: width/height, scale is 1.0" = A single side was
+ // cropped, and nothing was scaled. Treat as "use IDEALLY CROPPED
+ // canvas". This is pixel-perfect.
+ //
+ // - "hasCropped: width/height, scale NOT 1.0" = A single side was
+ // cropped, and then the whole canvas was up/down-scaled. Treat as
+ // "use scale-fixed version of IDEALLY CROPPED canvas". The
+ // cropped result will later be scaled/stretched to the canvas
+ // size (up or down).
+ //
+ // There's an easy way to handle ALL of those scenarios: Just
+ // translate the IDEALLY CROPPED canvas back into INPUT-SCALED
+ // dimensions. Then we'll get a pixel-perfect "input crop" whenever
+ // scale is 1.0, since a scale of 1.0 gives the same result back.
+ // And we'll get a properly re-scaled result in all other cases.
+ //
+ // NOTE: This result CAN deviate from what was "actually cropped"
+ // during the main algorithm. That is TOTALLY INTENTIONAL AND IS THE
+ // INTENDED, PERFECT BEHAVIOR! Do NOT change this code! By always
+ // re-calculating here, we'll actually FIX rounding errors caused by
+ // the main algorithm's multiple steps, and will create better
+ // looking rescaling, and pixel-perfect unscaled croppings and
+ // pixel-perfect unscaled Mod2 adjustments!
+
+ // First calculate the overall IDEAL cropping applied to the INPUT
+ // canvas. If scale is 1.0 it will be used as-is (pixel-perfect).
+ // NOTE: We tell it to use round() so that the rescaled pixels are
+ // as close to the perfect aspect ratio as possible.
+ $croppedInputCanvas = $idealCanvas->withRescaling(1 / $overallRescale, 'round');
+ $this->_debugDimensions(
+ $croppedInputCanvas->getWidth(), $croppedInputCanvas->getHeight(),
+ 'CROP: Rescaled Ideally Cropped Canvas to Input Dimension Space'
+ );
+
+ // Now re-scale the Mod2 adjustments to the INPUT canvas coordinate
+ // space too. If scale is 1.0 they'll be used as-is (pixel-perfect).
+ // If the scale is up/down, they'll be rounded to the next whole
+ // number. The rounding is INTENTIONAL, because if scaling was used
+ // for the IDEAL canvas then it DOESN'T MATTER how many exact pixels
+ // we crop, but round() gives us the BEST APPROXIMATION!
+ $rescaledMod2WidthDiff = (int) round($canvasInfo['mod2WidthDiff'] * (1 / $overallRescale));
+ $rescaledMod2HeightDiff = (int) round($canvasInfo['mod2HeightDiff'] * (1 / $overallRescale));
+ $this->_debugText(
+ 'CROP: Rescaled Mod2 Adjustments to Input Dimension Space',
+ 'width=%s, height=%s, widthRescaled=%s, heightRescaled=%s',
+ $canvasInfo['mod2WidthDiff'], $canvasInfo['mod2HeightDiff'],
+ $rescaledMod2WidthDiff, $rescaledMod2HeightDiff
+ );
+
+ // Apply the Mod2 adjustments to the input cropping that we'll
+ // perform. This ensures that ALL of the Mod2 croppings (in ANY
+ // dimension) will always be pixel-perfect when we're at scale 1.0!
+ $croppedInputCanvas = new Dimensions($croppedInputCanvas->getWidth() + $rescaledMod2WidthDiff,
+ $croppedInputCanvas->getHeight() + $rescaledMod2HeightDiff);
+ $this->_debugDimensions(
+ $croppedInputCanvas->getWidth(), $croppedInputCanvas->getHeight(),
+ 'CROP: Applied Mod2 Adjustments to Final Cropped Input Canvas'
+ );
+
+ // The "CROPPED INPUT canvas" is in the same dimensions/coordinate
+ // space as the "INPUT canvas". So ensure all dimensions are valid
+ // (don't exceed INPUT) and create the final "CROPPED INPUT canvas".
+ // NOTE: This is it... if the media is at scale 1.0, we now have a
+ // pixel-perfect, cropped canvas with ALL of the cropping and Mod2
+ // adjustments applied to it! And if we're at another scale, we have
+ // a perfectly recalculated, cropped canvas which took into account
+ // cropping, scaling and Mod2 adjustments. Advanced stuff! :-)
+ $croppedInputCanvasWidth = $croppedInputCanvas->getWidth() <= $inputCanvas->getWidth()
+ ? $croppedInputCanvas->getWidth() : $inputCanvas->getWidth();
+ $croppedInputCanvasHeight = $croppedInputCanvas->getHeight() <= $inputCanvas->getHeight()
+ ? $croppedInputCanvas->getHeight() : $inputCanvas->getHeight();
+ $croppedInputCanvas = new Dimensions($croppedInputCanvasWidth, $croppedInputCanvasHeight);
+ $this->_debugDimensions(
+ $croppedInputCanvas->getWidth(), $croppedInputCanvas->getHeight(),
+ 'CROP: Clamped to Legal Input Max-Dimensions'
+ );
+
+ // Initialize the crop-shifting variables. They control the range of
+ // X/Y coordinates we'll copy from ORIGINAL INPUT to OUTPUT canvas.
+ // NOTE: This properly selects the entire INPUT media canvas area.
+ $x1 = $y1 = 0;
+ $x2 = $inputCanvas->getWidth();
+ $y2 = $inputCanvas->getHeight();
+ $this->_debugText(
+ 'CROP: Initializing X/Y Variables to Full Input Canvas Size',
+ 'x1=%s, x2=%s, y1=%s, y2=%s',
+ $x1, $x2, $y1, $y2
+ );
+
+ // Calculate the width and height diffs between the original INPUT
+ // canvas and the new CROPPED INPUT canvas. Negative values mean the
+ // output is smaller (which we'll handle by cropping), and larger
+ // values would mean the output is larger (which we'll handle by
+ // letting the OUTPUT canvas stretch the 100% uncropped original
+ // pixels of the INPUT in that direction, to fill the whole canvas).
+ // NOTE: Because of clamping of the CROPPED INPUT canvas above, this
+ // will actually never be a positive ("scale up") number. It will
+ // only be 0 or less. That's good, just be aware of it if editing!
+ $widthDiff = $croppedInputCanvas->getWidth() - $inputCanvas->getWidth();
+ $heightDiff = $croppedInputCanvas->getHeight() - $inputCanvas->getHeight();
+ $this->_debugText(
+ 'CROP: Calculated Input Canvas Crop Amounts',
+ 'width=%s px, height=%s px',
+ $widthDiff, $heightDiff
+ );
+
+ // After ALL of that work... we finally know how to crop the input
+ // canvas! Alright... handle cropping of the INPUT width and height!
+ // NOTE: The main canvas-creation algorithm only crops a single
+ // dimension (width or height), but its Mod2 adjustments may have
+ // caused BOTH to be cropped, which is why we MUST process both.
+ if ($widthDiff < 0) {
+ // Horizontal cropping. Focus on the center by default.
+ $horCropFocus = $this->_horCropFocus !== null ? $this->_horCropFocus : 0;
+ $this->_debugText('CROP: Horizontal Crop Focus', 'focus=%s', $horCropFocus);
+
+ // Invert the focus if this is horizontally flipped media.
+ if ($this->_details->isHorizontallyFlipped()) {
+ $horCropFocus = -$horCropFocus;
+ $this->_debugText(
+ 'CROP: Media is HorFlipped, Flipping Horizontal Crop Focus',
+ 'focus=%s',
+ $horCropFocus
+ );
+ }
+
+ // Calculate amount of pixels to crop and shift them as-focused.
+ // NOTE: Always use floor() to make uneven amounts lean at left.
+ $absWidthDiff = abs($widthDiff);
+ $x1 = (int) floor($absWidthDiff * (50 + $horCropFocus) / 100);
+ $x2 = $x2 - ($absWidthDiff - $x1);
+ $this->_debugText('CROP: Calculated New X Offsets', 'x1=%s, x2=%s', $x1, $x2);
+ }
+ if ($heightDiff < 0) {
+ // Vertical cropping. Focus on top by default (to keep faces).
+ $verCropFocus = $this->_verCropFocus !== null ? $this->_verCropFocus : -50;
+ $this->_debugText('CROP: Vertical Crop Focus', 'focus=%s', $verCropFocus);
+
+ // Invert the focus if this is vertically flipped media.
+ if ($this->_details->isVerticallyFlipped()) {
+ $verCropFocus = -$verCropFocus;
+ $this->_debugText(
+ 'CROP: Media is VerFlipped, Flipping Vertical Crop Focus',
+ 'focus=%s',
+ $verCropFocus
+ );
+ }
+
+ // Calculate amount of pixels to crop and shift them as-focused.
+ // NOTE: Always use floor() to make uneven amounts lean at top.
+ $absHeightDiff = abs($heightDiff);
+ $y1 = (int) floor($absHeightDiff * (50 + $verCropFocus) / 100);
+ $y2 = $y2 - ($absHeightDiff - $y1);
+ $this->_debugText('CROP: Calculated New Y Offsets', 'y1=%s, y2=%s', $y1, $y2);
+ }
+
+ // Create a source rectangle which starts at the start-offsets
+ // (x1/y1) and lasts until the width and height of the desired area.
+ $srcRect = new Rectangle($x1, $y1, $x2 - $x1, $y2 - $y1);
+ $this->_debugText(
+ 'CROP_SRC: Input Canvas Source Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $srcRect->getX1(), $srcRect->getX2(), $srcRect->getY1(), $srcRect->getY2(),
+ $srcRect->getWidth(), $srcRect->getHeight(), $srcRect->getAspectRatio()
+ );
+
+ // Create a destination rectangle which completely fills the entire
+ // output canvas from edge to edge. This ensures that any undersized
+ // or oversized input will be stretched properly in all directions.
+ //
+ // NOTE: Everything about our cropping/canvas algorithms is
+ // optimized so that stretching won't happen unless the media is so
+ // tiny that it's below the minimum width or so wide that it must be
+ // shrunk. Everything else WILL use sharp 1:1 pixels and pure
+ // cropping instead of stretching/shrinking. And when stretch/shrink
+ // is used, the aspect ratio is always perfectly maintained!
+ $dstRect = new Rectangle(0, 0, $outputCanvas->getWidth(), $outputCanvas->getHeight());
+ $this->_debugText(
+ 'CROP_DST: Output Canvas Destination Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $dstRect->getX1(), $dstRect->getX2(), $dstRect->getY1(), $dstRect->getY2(),
+ $dstRect->getWidth(), $dstRect->getHeight(), $dstRect->getAspectRatio()
+ );
+ } elseif ($this->_operation === self::EXPAND) {
+ // We'll copy the entire original input media onto the new canvas.
+ // Always copy from the absolute top left of the original media.
+ $srcRect = new Rectangle(0, 0, $inputCanvas->getWidth(), $inputCanvas->getHeight());
+ $this->_debugText(
+ 'EXPAND_SRC: Input Canvas Source Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $srcRect->getX1(), $srcRect->getX2(), $srcRect->getY1(), $srcRect->getY2(),
+ $srcRect->getWidth(), $srcRect->getHeight(), $srcRect->getAspectRatio()
+ );
+
+ // Determine the target dimensions to fit it on the new canvas,
+ // because the input media's dimensions may have been too large.
+ // This will not scale anything (uses scale=1) if the input fits.
+ $outputWidthScale = (float) ($outputCanvas->getWidth() / $inputCanvas->getWidth());
+ $outputHeightScale = (float) ($outputCanvas->getHeight() / $inputCanvas->getHeight());
+ $scale = min($outputWidthScale, $outputHeightScale);
+ $this->_debugText(
+ 'EXPAND: Calculating Scale to Fit Input on Output Canvas',
+ 'scale=%.8f',
+ $scale
+ );
+
+ // Calculate the scaled destination rectangle. Note that X/Y remain.
+ // NOTE: We tell it to use ceil(), which guarantees that it'll
+ // never scale a side badly and leave a 1px gap between the media
+ // and canvas sides. Also note that ceil will never produce bad
+ // values, since PHP allows the dst_w/dst_h to exceed beyond canvas!
+ $dstRect = $srcRect->withRescaling($scale, 'ceil');
+ $this->_debugDimensions(
+ $dstRect->getWidth(), $dstRect->getHeight(),
+ 'EXPAND: Rescaled Input to Output Dimension Space'
+ );
+
+ // Now calculate the centered destination offset on the canvas.
+ // NOTE: We use floor() to ensure that the result gets left-aligned
+ // perfectly, and prefers to lean towards towards the top as well.
+ $dst_x = (int) floor(($outputCanvas->getWidth() - $dstRect->getWidth()) / 2);
+ $dst_y = (int) floor(($outputCanvas->getHeight() - $dstRect->getHeight()) / 2);
+ $this->_debugText(
+ 'EXPAND: Calculating Centered Destination on Output Canvas',
+ 'dst_x=%s, dst_y=%s',
+ $dst_x, $dst_y
+ );
+
+ // Build the final destination rectangle for the expanded canvas!
+ $dstRect = new Rectangle($dst_x, $dst_y, $dstRect->getWidth(), $dstRect->getHeight());
+ $this->_debugText(
+ 'EXPAND_DST: Output Canvas Destination Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $dstRect->getX1(), $dstRect->getX2(), $dstRect->getY1(), $dstRect->getY2(),
+ $dstRect->getWidth(), $dstRect->getHeight(), $dstRect->getAspectRatio()
+ );
+ } else {
+ throw new \RuntimeException(sprintf('Unsupported operation: %s.', $this->_operation));
+ }
+
+ return $this->_createOutputFile($srcRect, $dstRect, $outputCanvas);
+ }
+
+ /**
+ * Create the new media file.
+ *
+ * @param Rectangle $srcRect Rectangle to copy from the input.
+ * @param Rectangle $dstRect Destination place and scale of copied pixels.
+ * @param Dimensions $canvas The size of the destination canvas.
+ *
+ * @return string The path to the output file.
+ */
+ abstract protected function _createOutputFile(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas);
+
+ /**
+ * Calculate a new canvas based on input size and requested modifications.
+ *
+ * The final canvas will be the same size as the input if everything was
+ * already okay and within the limits. Otherwise it will be a new canvas
+ * representing the _exact_, best-possible size to convert input media to.
+ *
+ * It is up to the caller to perfectly follow these orders, since deviating
+ * by even a SINGLE PIXEL can create illegal media aspect ratios.
+ *
+ * Also note that the resulting canvas can be LARGER than the input in
+ * several cases, such as in EXPAND-mode (obviously), or when the input
+ * isn't wide enough to be legal (and must be scaled up), and whenever Mod2
+ * is requested. In the latter case, the algorithm may have to add a few
+ * pixels to the height to make it valid in a few rare cases. The caller
+ * must be aware of such "enlarged" canvases and should handle them by
+ * stretching the input if necessary.
+ *
+ * @param int $operation
+ * @param int $inputWidth
+ * @param int $inputHeight
+ * @param bool $isMod2CanvasRequired
+ * @param int $minWidth
+ * @param int $maxWidth
+ * @param float|null $minAspectRatio
+ * @param float|null $maxAspectRatio
+ * @param float|null $forceTargetAspectRatio Optional forced aspect ratio
+ * target (ALWAYS applied,
+ * except if input is already
+ * EXACTLY this ratio).
+ * @param bool $allowNewAspectDeviation See constructor arg docs.
+ *
+ * @throws \RuntimeException If requested canvas couldn't be achieved, most
+ * commonly if you have chosen way too narrow
+ * aspect ratio ranges that cannot be perfectly
+ * reached by your input media, and you AREN'T
+ * running with `$allowNewAspectDeviation`.
+ *
+ * @return array An array with `canvas` (`Dimensions`), `mod2WidthDiff` and
+ * `mod2HeightDiff`. The latter are integers representing how
+ * many pixels were cropped (-) or added (+) by the Mod2 step
+ * compared to the ideal canvas.
+ */
+ protected function _calculateNewCanvas(
+ $operation,
+ $inputWidth,
+ $inputHeight,
+ $isMod2CanvasRequired,
+ $minWidth = 1,
+ $maxWidth = 99999,
+ $minAspectRatio = null,
+ $maxAspectRatio = null,
+ $forceTargetAspectRatio = null,
+ $allowNewAspectDeviation = false)
+ {
+ /*
+ * WARNING TO POTENTIAL CONTRIBUTORS:
+ *
+ * THIS right here is the MOST COMPLEX algorithm in the whole project.
+ * Everything is finely tuned to create 100% accurate, pixel-perfect
+ * resizes. A SINGLE PIXEL ERROR in your calculations WILL lead to it
+ * sometimes outputting illegally formatted files that will be rejected
+ * by Instagram. We know this, because we have SEEN IT HAPPEN while we
+ * tweaked and tweaked and tweaked to balance everything perfectly!
+ *
+ * Unfortunately, this file also seems to attract a lot of beginners.
+ * Maybe because a "media processor" seems "fun and easy". But that
+ * would be an incorrect guess. It's the most serious algorithm in the
+ * whole project. If you break it, *YOU* break people's uploads.
+ *
+ * We have had many random, new contributors just jumping in and adding
+ * zero-effort code everywhere in here, and breaking the whole balance,
+ * and then opening pull requests. We have rejected EVERY single one of
+ * those pull requests because they were totally unusable and unsafe.
+ *
+ * We will not accept such pull requests. Ever.
+ *
+ * This warning is here to save your time, and ours.
+ *
+ * If you are interested in helping out with the media algorithms, then
+ * that's GREAT! But in that case we require that you fully read through
+ * the algorithms below and all of its comments about 50 times over a
+ * 3-4 day period - until you understand every single step perfectly.
+ * The comments will help make it clearer the more you read...
+ *
+ * ...and make an effort.
+ *
+ * Then you are ready... and welcome to the team. :-)
+ *
+ * Thank you.
+ */
+
+ if ($forceTargetAspectRatio !== null) {
+ $this->_debugText('SPECIAL_PARAMETERS: Forced Target Aspect Ratio', 'forceTargetAspectRatio=%.5f', $forceTargetAspectRatio);
+ }
+
+ // Initialize target canvas to original input dimensions & aspect ratio.
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $targetWidth = (int) $inputWidth;
+ $targetHeight = (int) $inputHeight;
+ $targetAspectRatio = (float) ($inputWidth / $inputHeight);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_INPUT: Input Canvas Size');
+
+ // Check aspect ratio and crop/expand the canvas to fit aspect if needed.
+ if (
+ ($minAspectRatio !== null && $targetAspectRatio < $minAspectRatio)
+ || ($forceTargetAspectRatio !== null && $targetAspectRatio < $forceTargetAspectRatio)
+ ) {
+ // Determine target ratio; uses forced aspect ratio if set,
+ // otherwise we target the MINIMUM allowed ratio (since we're < it)).
+ $targetAspectRatio = $forceTargetAspectRatio !== null ? $forceTargetAspectRatio : $minAspectRatio;
+
+ if ($operation === self::CROP) {
+ // We need to limit the height, so floor is used intentionally to
+ // AVOID rounding height upwards to a still-too-low aspect ratio.
+ $targetHeight = (int) floor($targetWidth / $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_CROPPED: %s', $forceTargetAspectRatio === null ? 'Aspect Was < MIN' : 'Applying Forced Aspect for INPUT < TARGET'));
+ } elseif ($operation === self::EXPAND) {
+ // We need to expand the width with left/right borders. We use
+ // ceil to guarantee that the final media is wide enough to be
+ // above the minimum allowed aspect ratio.
+ $targetWidth = (int) ceil($targetHeight * $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_EXPANDED: %s', $forceTargetAspectRatio === null ? 'Aspect Was < MIN' : 'Applying Forced Aspect for INPUT < TARGET'));
+ }
+ } elseif (
+ ($maxAspectRatio !== null && $targetAspectRatio > $maxAspectRatio)
+ || ($forceTargetAspectRatio !== null && $targetAspectRatio > $forceTargetAspectRatio)
+ ) {
+ // Determine target ratio; uses forced aspect ratio if set,
+ // otherwise we target the MAXIMUM allowed ratio (since we're > it)).
+ $targetAspectRatio = $forceTargetAspectRatio !== null ? $forceTargetAspectRatio : $maxAspectRatio;
+
+ if ($operation === self::CROP) {
+ // We need to limit the width. We use floor to guarantee cutting
+ // enough pixels, since our width exceeds the maximum allowed ratio.
+ $targetWidth = (int) floor($targetHeight * $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_CROPPED: %s', $forceTargetAspectRatio === null ? 'Aspect Was > MAX' : 'Applying Forced Aspect for INPUT > TARGET'));
+ } elseif ($operation === self::EXPAND) {
+ // We need to expand the height with top/bottom borders. We use
+ // ceil to guarantee that the final media is tall enough to be
+ // below the maximum allowed aspect ratio.
+ $targetHeight = (int) ceil($targetWidth / $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_EXPANDED: %s', $forceTargetAspectRatio === null ? 'Aspect Was > MAX' : 'Applying Forced Aspect for INPUT > TARGET'));
+ }
+ } else {
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS: Aspect Ratio Already Legal');
+ }
+
+ // Determine whether the final target ratio is closest to either the
+ // legal MINIMUM or the legal MAXIMUM aspect ratio limits.
+ // NOTE: The target ratio will actually still be set to the original
+ // input media's ratio in case of no aspect ratio adjustments above.
+ // NOTE: If min and/or max ratios were not provided, we default min to
+ // `0` and max to `9999999` to ensure that we properly detect the "least
+ // distance" direction even when only one (or neither) of the two "range
+ // limit values" were provided.
+ $minAspectDistance = abs(($minAspectRatio !== null
+ ? $minAspectRatio : 0) - $targetAspectRatio);
+ $maxAspectDistance = abs(($maxAspectRatio !== null
+ ? $maxAspectRatio : 9999999) - $targetAspectRatio);
+ $isClosestToMinAspect = ($minAspectDistance <= $maxAspectDistance);
+
+ // We MUST now set up the correct height re-calculation behavior for the
+ // later algorithm steps. This is used whenever our canvas needs to be
+ // re-scaled by any other code below. If our chosen, final target ratio
+ // is closest to the minimum allowed legal ratio, we'll always use
+ // floor() on the height to ensure that the height value becomes as low
+ // as possible (since having LESS height compared to width is what
+ // causes the aspect ratio value to grow), to ensure that the final
+ // result's ratio (after any additional adjustments) will ALWAYS be
+ // ABOVE the minimum legal ratio (minAspectRatio). Otherwise we'll
+ // instead use ceil() on the height (since having more height causes the
+ // aspect ratio value to shrink), to ensure that the result is always
+ // BELOW the maximum ratio (maxAspectRatio).
+ $useFloorHeightRecalc = $isClosestToMinAspect;
+
+ // Verify square target ratios by ensuring canvas is now a square.
+ // NOTE: This is just a sanity check against wrong code above. It will
+ // never execute, since all code above took care of making both
+ // dimensions identical already (if they differed in any way, they had a
+ // non-1 ratio and invoked the aspect ratio cropping/expansion code). It
+ // then made identical thanks to the fact that X / 1 = X, and X * 1 = X.
+ // NOTE: It's worth noting that our squares are always the size of the
+ // shortest side when cropping or the longest side when expanding.
+ // WARNING: Comparison MUST use `==` (NOT strict `===`) to support both
+ // int(1) and float(1.0) values!
+ if ($targetAspectRatio == 1.0 && $targetWidth !== $targetHeight) { // Ratio 1 = Square.
+ $targetWidth = $targetHeight = $operation === self::CROP
+ ? min($targetWidth, $targetHeight)
+ : max($targetWidth, $targetHeight);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_SQUARIFY: Fixed Badly Generated Square');
+ }
+
+ // Lastly, enforce minimum and maximum width limits on our final canvas.
+ // NOTE: Instagram only enforces width & aspect ratio, which in turn
+ // auto-limits height (since we can only use legal height ratios).
+ // NOTE: Yet again, if the target ratio is 1 (square), we'll get
+ // identical width & height, so NO NEED to MANUALLY "fix square" here.
+ if ($targetWidth > $maxWidth) {
+ $targetWidth = $maxWidth;
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Width Was > MAX');
+ $targetHeight = $this->_accurateHeightRecalc($useFloorHeightRecalc, $targetAspectRatio, $targetWidth);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Height Recalc From Width & Aspect');
+ } elseif ($targetWidth < $minWidth) {
+ $targetWidth = $minWidth;
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Width Was < MIN');
+ $targetHeight = $this->_accurateHeightRecalc($useFloorHeightRecalc, $targetAspectRatio, $targetWidth);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Height Recalc From Width & Aspect');
+ }
+
+ // All of the main canvas algorithms are now finished, and we are now
+ // able to check Mod2 compatibility and accurately readjust if needed.
+ $mod2WidthDiff = $mod2HeightDiff = 0;
+ if ($isMod2CanvasRequired
+ && (!$this->_isNumberMod2($targetWidth) || !$this->_isNumberMod2($targetHeight))
+ ) {
+ // Calculate the Mod2-adjusted final canvas size.
+ $mod2Canvas = $this->_calculateAdjustedMod2Canvas(
+ $inputWidth,
+ $inputHeight,
+ $useFloorHeightRecalc,
+ $targetWidth,
+ $targetHeight,
+ $targetAspectRatio,
+ $minWidth,
+ $maxWidth,
+ $minAspectRatio,
+ $maxAspectRatio,
+ $allowNewAspectDeviation
+ );
+
+ // Determine the pixel difference before and after processing.
+ $mod2WidthDiff = $mod2Canvas->getWidth() - $targetWidth;
+ $mod2HeightDiff = $mod2Canvas->getHeight() - $targetHeight;
+ $this->_debugText('CANVAS: Mod2 Difference Stats', 'width=%s, height=%s', $mod2WidthDiff, $mod2HeightDiff);
+
+ // Update the final canvas to the Mod2-adjusted canvas size.
+ // NOTE: If code above failed, the new values are invalid. But so
+ // could our original values have been. We check that further down.
+ $targetWidth = $mod2Canvas->getWidth();
+ $targetHeight = $mod2Canvas->getHeight();
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS: Updated From Mod2 Result');
+ }
+
+ // Create the new canvas Dimensions object.
+ $canvas = new Dimensions($targetWidth, $targetHeight);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_OUTPUT: Final Output Canvas Size');
+
+ // We must now validate the canvas before returning it.
+ // NOTE: Most of these are just strict sanity-checks to protect against
+ // bad code contributions in the future. The canvas won't be able to
+ // pass all of these checks unless the algorithm above remains perfect.
+ $isIllegalRatio = (($minAspectRatio !== null && $canvas->getAspectRatio() < $minAspectRatio)
+ || ($maxAspectRatio !== null && $canvas->getAspectRatio() > $maxAspectRatio));
+ if ($canvas->getWidth() < 1 || $canvas->getHeight() < 1) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Target width (%s) or height (%s) less than one pixel.',
+ $canvas->getWidth(), $canvas->getHeight()
+ ));
+ } elseif ($canvas->getWidth() < $minWidth) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Target width (%s) less than minimum allowed (%s).',
+ $canvas->getWidth(), $minWidth
+ ));
+ } elseif ($canvas->getWidth() > $maxWidth) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Target width (%s) greater than maximum allowed (%s).',
+ $canvas->getWidth(), $maxWidth
+ ));
+ } elseif ($isIllegalRatio) {
+ if (!$allowNewAspectDeviation) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Unable to reach target aspect ratio range during output canvas generation. The range of allowed aspect ratios is too narrow (%.8f - %.8f). We achieved a ratio of %.8f.',
+ $minAspectRatio !== null ? $minAspectRatio : 0.0,
+ $maxAspectRatio !== null ? $maxAspectRatio : INF,
+ $canvas->getAspectRatio()
+ ));
+ } else {
+ // The user wants us to allow "near-misses", so we proceed...
+ $this->_debugDimensions($canvas->getWidth(), $canvas->getHeight(), 'CANVAS_FINAL: Allowing Deviating Aspect Ratio');
+ }
+ }
+
+ return [
+ 'canvas' => $canvas,
+ 'mod2WidthDiff' => $mod2WidthDiff,
+ 'mod2HeightDiff' => $mod2HeightDiff,
+ ];
+ }
+
+ /**
+ * Calculates a new relative height using the target aspect ratio.
+ *
+ * Used internally by `_calculateNewCanvas()`.
+ *
+ * This algorithm aims at the highest-possible or lowest-possible resulting
+ * aspect ratio based on what's needed. It uses either `floor()` or `ceil()`
+ * depending on whether we need the resulting aspect ratio to be >= or <=
+ * the target aspect ratio.
+ *
+ * The principle behind this is the fact that removing height (via floor)
+ * will give us a higher aspect ratio. And adding height (via ceil) will
+ * give us a lower aspect ratio.
+ *
+ * If the target aspect ratio is square (1), height becomes equal to width.
+ *
+ * @param bool $useFloorHeightRecalc
+ * @param float $targetAspectRatio
+ * @param int $targetWidth
+ *
+ * @return int
+ */
+ protected function _accurateHeightRecalc(
+ $useFloorHeightRecalc,
+ $targetAspectRatio,
+ $targetWidth)
+ {
+ // Read the docs above to understand this CRITICALLY IMPORTANT code.
+ $targetHeight = $useFloorHeightRecalc
+ ? (int) floor($targetWidth / $targetAspectRatio) // >=
+ : (int) ceil($targetWidth / $targetAspectRatio); // <=
+
+ return $targetHeight;
+ }
+
+ /**
+ * Adjusts dimensions to create a Mod2-compatible canvas.
+ *
+ * Used internally by `_calculateNewCanvas()`.
+ *
+ * The reason why this function also takes the original input width/height
+ * is because it tries to maximize its usage of the available original pixel
+ * surface area while correcting the dimensions. It uses the extra
+ * information to know when it's safely able to grow the canvas beyond the
+ * given target width/height parameter values.
+ *
+ * @param int $inputWidth
+ * @param int $inputHeight
+ * @param bool $useFloorHeightRecalc
+ * @param int $targetWidth
+ * @param int $targetHeight
+ * @param float $targetAspectRatio
+ * @param int $minWidth
+ * @param int $maxWidth
+ * @param float|null $minAspectRatio
+ * @param float|null $maxAspectRatio
+ * @param bool $allowNewAspectDeviation See constructor arg docs.
+ *
+ * @throws \RuntimeException If requested canvas couldn't be achieved, most
+ * commonly if you have chosen way too narrow
+ * aspect ratio ranges that cannot be perfectly
+ * reached by your input media, and you AREN'T
+ * running with `$allowNewAspectDeviation`.
+ *
+ * @return Dimensions
+ *
+ * @see InstagramMedia::_calculateNewCanvas()
+ */
+ protected function _calculateAdjustedMod2Canvas(
+ $inputWidth,
+ $inputHeight,
+ $useFloorHeightRecalc,
+ $targetWidth,
+ $targetHeight,
+ $targetAspectRatio,
+ $minWidth = 1,
+ $maxWidth = 99999,
+ $minAspectRatio = null,
+ $maxAspectRatio = null,
+ $allowNewAspectDeviation = false)
+ {
+ // Initialize to the calculated canvas size.
+ $mod2Width = $targetWidth;
+ $mod2Height = $targetHeight;
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Current Canvas Size');
+
+ // Determine if we're able to cut an extra pixel from the width if
+ // necessary, or if cutting would take us below the minimum width.
+ $canCutWidth = $mod2Width > $minWidth;
+
+ // To begin, we must correct the width if it's uneven. We'll only do
+ // this once, and then we'll leave the width at its new number. By
+ // keeping it static, we don't risk going over its min/max width
+ // limits. And by only varying one dimension (height) if multiple Mod2
+ // offset adjustments are needed, then we'll properly get a steadily
+ // increasing/decreasing aspect ratio (moving towards the target ratio).
+ if (!$this->_isNumberMod2($mod2Width)) {
+ // Always prefer cutting an extra pixel, rather than stretching
+ // by +1. But use +1 if cutting would take us below minimum width.
+ // NOTE: Another IMPORTANT reason to CUT width rather than extend
+ // is because in narrow cases (canvas close to original input size),
+ // the extra width proportionally increases total area (thus height
+ // too), and gives us less of the original pixels on the height-axis
+ // to play with when attempting to fix the height (and its ratio).
+ $mod2Width += ($canCutWidth ? -1 : 1);
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Width Mod2Fix');
+
+ // Calculate the new relative height based on the new width.
+ $mod2Height = $this->_accurateHeightRecalc($useFloorHeightRecalc, $targetAspectRatio, $mod2Width);
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Height Recalc From Width & Aspect');
+ }
+
+ // Ensure that the calculated height is also Mod2, but totally ignore
+ // the aspect ratio at this moment (we'll fix that later). Instead,
+ // we'll use the same pattern we'd use for width above. That way, if
+ // both width and height were uneven, they both get adjusted equally.
+ if (!$this->_isNumberMod2($mod2Height)) {
+ $mod2Height += ($canCutWidth ? -1 : 1);
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Height Mod2Fix');
+ }
+
+ // We will now analyze multiple different height alternatives to find
+ // which one gives us the best visual quality. This algorithm looks
+ // for the best qualities (with the most pixel area) first. It first
+ // tries the current height (offset 0, which is the closest to the
+ // pre-Mod2 adjusted canvas), then +2 pixels (gives more pixel area if
+ // this is possible), then -2 pixels (cuts but may be our only choice).
+ // After that, it checks 4, -4, 6 and -6 as well.
+ // NOTE: Every increased offset (+/-2, then +/-4, then +/- 6) USUALLY
+ // (but not always) causes more and more deviation from the intended
+ // cropping aspect ratio. So don't add any more steps after 6, since
+ // NOTHING will be THAT far off! Six was chosen as a good balance.
+ // NOTE: Every offset is checked for visual stretching and aspect ratio,
+ // and then rated into one of 3 categories: "perfect" (legal aspect
+ // ratio, no stretching), "stretch" (legal aspect ratio, but stretches),
+ // or "bad" (illegal aspect ratio).
+ $heightAlternatives = ['perfect' => [], 'stretch' => [], 'bad' => []];
+ static $offsetPriorities = [0, 2, -2, 4, -4, 6, -6];
+ foreach ($offsetPriorities as $offset) {
+ // Calculate the new height and its resulting aspect ratio.
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $offsetMod2Height = $mod2Height + $offset;
+ $offsetMod2AspectRatio = (float) ($mod2Width / $offsetMod2Height);
+
+ // Check if the aspect ratio is legal.
+ $isLegalRatio = (($minAspectRatio === null || $offsetMod2AspectRatio >= $minAspectRatio)
+ && ($maxAspectRatio === null || $offsetMod2AspectRatio <= $maxAspectRatio));
+
+ // Detect whether the height would need stretching. Stretching is
+ // defined as "not enough pixels in the input media to reach".
+ // NOTE: If the input media has been upscaled (such as a 64x64 image
+ // being turned into 320x320), then we will ALWAYS detect that media
+ // as needing stretching. That's intentional and correct, because
+ // such media will INDEED need stretching, so there's never going to
+ // be a perfect rating for it (where aspect ratio is legal AND zero
+ // stretching is needed to reach those dimensions).
+ // NOTE: The max() gets rid of negative values (cropping).
+ $stretchAmount = max(0, $offsetMod2Height - $inputHeight);
+
+ // Calculate the deviation from the target aspect ratio. The larger
+ // this number is, the further away from "the ideal canvas". The
+ // "perfect" answers will always deviate by different amount, and
+ // the most perfect one is the one with least deviation.
+ $ratioDeviation = abs($offsetMod2AspectRatio - $targetAspectRatio);
+
+ // Rate this height alternative and store it according to rating.
+ $rating = ($isLegalRatio && !$stretchAmount ? 'perfect' : ($isLegalRatio ? 'stretch' : 'bad'));
+ $heightAlternatives[$rating][] = [
+ 'offset' => $offset,
+ 'height' => $offsetMod2Height,
+ 'ratio' => $offsetMod2AspectRatio,
+ 'isLegalRatio' => $isLegalRatio,
+ 'stretchAmount' => $stretchAmount,
+ 'ratioDeviation' => $ratioDeviation,
+ 'rating' => $rating,
+ ];
+ $this->_debugDimensions($mod2Width, $offsetMod2Height, sprintf(
+ 'MOD2_CANVAS_CHECK: Testing Height Mod2Ratio (h%s%s = %s)',
+ ($offset >= 0 ? '+' : ''), $offset, $rating)
+ );
+ }
+
+ // Now pick the BEST height from our available choices (if any). We will
+ // pick the LEGAL height that has the LEAST amount of deviation from the
+ // ideal aspect ratio. In other words, the BEST-LOOKING aspect ratio!
+ // NOTE: If we find no legal (perfect or stretch) choices, we'll pick
+ // the most accurate (least deviation from ratio) of the bad choices.
+ $bestHeight = null;
+ foreach (['perfect', 'stretch', 'bad'] as $rating) {
+ if (!empty($heightAlternatives[$rating])) {
+ // Sort all alternatives by their amount of ratio deviation.
+ usort($heightAlternatives[$rating], function ($a, $b) {
+ return ($a['ratioDeviation'] < $b['ratioDeviation'])
+ ? -1 : (($a['ratioDeviation'] > $b['ratioDeviation']) ? 1 : 0);
+ });
+
+ // Pick the 1st array element, which has the least deviation!
+ $bestHeight = $heightAlternatives[$rating][0];
+ break;
+ }
+ }
+
+ // Process and apply the best-possible height we found.
+ $mod2Height = $bestHeight['height'];
+ $this->_debugDimensions($mod2Width, $mod2Height, sprintf(
+ 'MOD2_CANVAS: Selected Most Ideal Height Mod2Ratio (h%s%s = %s)',
+ ($bestHeight['offset'] >= 0 ? '+' : ''), $bestHeight['offset'], $bestHeight['rating']
+ ));
+
+ // Decide what to do if there were no legal aspect ratios among our
+ // calculated choices. This can happen if the user gave us an insanely
+ // narrow range (such as "min/max ratio 1.6578" or whatever).
+ if ($bestHeight['rating'] === 'bad') {
+ if (!$allowNewAspectDeviation) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Unable to reach target aspect ratio range during Mod2 canvas conversion. The range of allowed aspect ratios is too narrow (%.8f - %.8f). We achieved a ratio of %.8f.',
+ $minAspectRatio !== null ? $minAspectRatio : 0.0,
+ $maxAspectRatio !== null ? $maxAspectRatio : INF,
+ (float) ($mod2Width / $mod2Height)
+ ));
+ } else {
+ // They WANT us to allow "near-misses", so we'll KEEP our best
+ // possible bad ratio here (the one that was closest to the
+ // target). We didn't find any more ideal aspect ratio (since
+ // all other attempts ALSO FAILED the aspect ratio ranges), so
+ // we have NO idea if they'd prefer any others! ;-)
+ $this->_debugDimensions($mod2Width, $mod2Height, sprintf(
+ 'MOD2_CANVAS: Allowing Deviating Height Mod2Ratio (h%s%s = %s)',
+ ($bestHeight['offset'] >= 0 ? '+' : ''), $bestHeight['offset'], $bestHeight['rating']
+ ));
+ }
+ }
+
+ return new Dimensions($mod2Width, $mod2Height);
+ }
+
+ /**
+ * Checks whether a number is Mod2.
+ *
+ * @param int|float $number
+ *
+ * @return bool
+ */
+ protected function _isNumberMod2(
+ $number)
+ {
+ // NOTE: The modulo operator correctly returns ints even for float input such as 1.999.
+ return $number % 2 === 0;
+ }
+
+ /**
+ * Output debug text.
+ *
+ * @param string $stepDescription
+ * @param string $formatMessage
+ * @param mixed $args,...
+ */
+ protected function _debugText(
+ $stepDescription,
+ $formatMessage,
+ ...$args)
+ {
+ if (!$this->_debug) {
+ return;
+ }
+
+ printf(
+ "[\033[1;33m%s\033[0m] {$formatMessage}\n",
+ $stepDescription,
+ ...$args
+ );
+ }
+
+ /**
+ * Debug current calculation dimensions and their ratio.
+ *
+ * @param int|float $width
+ * @param int|float $height
+ * @param string|null $stepDescription
+ */
+ protected function _debugDimensions(
+ $width,
+ $height,
+ $stepDescription = null)
+ {
+ if (!$this->_debug) {
+ return;
+ }
+
+ printf(
+ // NOTE: This uses 8 decimals for proper debugging, since small
+ // rounding errors can make rejected ratios look valid.
+ "[\033[1;33m%s\033[0m] w=%s h=%s (aspect %.8f)\n",
+ $stepDescription !== null ? $stepDescription : 'DEBUG',
+ $width, $height, (float) ($width / $height)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/MediaDetails.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/MediaDetails.php
new file mode 100755
index 0000000..f7516de
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/MediaDetails.php
@@ -0,0 +1,172 @@
+hasSwappedAxes() ? $this->_height : $this->_width;
+ }
+
+ /**
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->hasSwappedAxes() ? $this->_width : $this->_height;
+ }
+
+ /**
+ * @return float
+ */
+ public function getAspectRatio()
+ {
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ return (float) ($this->getWidth() / $this->getHeight());
+ }
+
+ /**
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->_filename;
+ }
+
+ /**
+ * @return int
+ */
+ public function getFilesize()
+ {
+ return $this->_filesize;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBasename()
+ {
+ // Fix full path disclosure.
+ return basename($this->_filename);
+ }
+
+ /**
+ * Get the minimum allowed media width for this media type.
+ *
+ * @return int
+ */
+ abstract public function getMinAllowedWidth();
+
+ /**
+ * Get the maximum allowed media width for this media type.
+ *
+ * @return int
+ */
+ abstract public function getMaxAllowedWidth();
+
+ /**
+ * Check whether the media has swapped axes.
+ *
+ * @return bool
+ */
+ abstract public function hasSwappedAxes();
+
+ /**
+ * Check whether the media is horizontally flipped.
+ *
+ * ```
+ * ***** *****
+ * * *
+ * *** => ***
+ * * *
+ * * *
+ * ```
+ *
+ * @return bool
+ */
+ abstract public function isHorizontallyFlipped();
+
+ /**
+ * Check whether the media is vertically flipped.
+ *
+ * ```
+ * ***** *
+ * * *
+ * *** => ***
+ * * *
+ * * *****
+ * ```
+ *
+ * @return bool
+ */
+ abstract public function isVerticallyFlipped();
+
+ /**
+ * Constructor.
+ *
+ * @param string $filename
+ * @param int $filesize
+ * @param int $width
+ * @param int $height
+ */
+ public function __construct(
+ $filename,
+ $filesize,
+ $width,
+ $height)
+ {
+ $this->_filename = $filename;
+ $this->_filesize = $filesize;
+ $this->_width = $width;
+ $this->_height = $height;
+ }
+
+ /**
+ * Verifies that a piece of media follows Instagram's rules.
+ *
+ * @param ConstraintsInterface $constraints
+ *
+ * @throws \InvalidArgumentException If Instagram won't allow this file.
+ */
+ public function validate(
+ ConstraintsInterface $constraints)
+ {
+ $mediaFilename = $this->getBasename();
+
+ // Check rotation.
+ if ($this->hasSwappedAxes() || $this->isVerticallyFlipped() || $this->isHorizontallyFlipped()) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts non-rotated media. Your file "%s" is either rotated or flipped or both.',
+ $mediaFilename
+ ));
+ }
+
+ // Check Aspect Ratio.
+ // NOTE: This Instagram rule is the same for both videos and photos.
+ $aspectRatio = $this->getAspectRatio();
+ $minAspectRatio = $constraints->getMinAspectRatio();
+ $maxAspectRatio = $constraints->getMaxAspectRatio();
+ if ($aspectRatio < $minAspectRatio || $aspectRatio > $maxAspectRatio) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts %s media with aspect ratios between %.3f and %.3f. Your file "%s" has a %.4f aspect ratio.',
+ $constraints->getTitle(), $minAspectRatio, $maxAspectRatio, $mediaFilename, $aspectRatio
+ ));
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Photo/InstagramPhoto.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Photo/InstagramPhoto.php
new file mode 100755
index 0000000..57b202d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Photo/InstagramPhoto.php
@@ -0,0 +1,326 @@
+_details = new PhotoDetails($this->_inputFile);
+ }
+
+ /** {@inheritdoc} */
+ protected function _isMod2CanvasRequired()
+ {
+ return false;
+ }
+
+ /** {@inheritdoc} */
+ protected function _createOutputFile(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas)
+ {
+ $outputFile = null;
+
+ try {
+ // Attempt to process the input file.
+ $resource = $this->_loadImage();
+
+ try {
+ $output = $this->_processResource($resource, $srcRect, $dstRect, $canvas);
+ } finally {
+ @imagedestroy($resource);
+ }
+
+ // Write the result to disk.
+ try {
+ // Prepare output file.
+ $outputFile = Utils::createTempFile($this->_tmpPath, 'IMG');
+
+ if (!imagejpeg($output, $outputFile, self::JPEG_QUALITY)) {
+ throw new \RuntimeException('Failed to create JPEG image file.');
+ }
+ } finally {
+ @imagedestroy($output);
+ }
+ } catch (\Exception $e) {
+ if ($outputFile !== null && is_file($outputFile)) {
+ @unlink($outputFile);
+ }
+
+ throw $e; // Re-throw.
+ }
+
+ return $outputFile;
+ }
+
+ /**
+ * Loads image into a resource.
+ *
+ * @throws \RuntimeException
+ *
+ * @return resource
+ */
+ protected function _loadImage()
+ {
+ // Read the correct input file format.
+ switch ($this->_details->getType()) {
+ case IMAGETYPE_JPEG:
+ $resource = imagecreatefromjpeg($this->_inputFile);
+ break;
+ case IMAGETYPE_PNG:
+ $resource = imagecreatefrompng($this->_inputFile);
+ break;
+ case IMAGETYPE_GIF:
+ $resource = imagecreatefromgif($this->_inputFile);
+ break;
+ default:
+ throw new \RuntimeException('Unsupported image type.');
+ }
+ if ($resource === false) {
+ throw new \RuntimeException('Failed to load image.');
+ }
+
+ return $resource;
+ }
+
+ /**
+ * @param resource $source The original image loaded as a resource.
+ * @param Rectangle $srcRect Rectangle to copy from the input.
+ * @param Rectangle $dstRect Destination place and scale of copied pixels.
+ * @param Dimensions $canvas The size of the destination canvas.
+ *
+ * @throws \Exception
+ * @throws \RuntimeException
+ *
+ * @return resource
+ */
+ protected function _processResource(
+ $source,
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas
+ ) {
+ // If our input image pixels are stored rotated, swap all coordinates.
+ if ($this->_details->hasSwappedAxes()) {
+ $srcRect = $srcRect->withSwappedAxes();
+ $dstRect = $dstRect->withSwappedAxes();
+ $canvas = $canvas->withSwappedAxes();
+ }
+
+ // Create an output canvas with our desired size.
+ $output = imagecreatetruecolor($canvas->getWidth(), $canvas->getHeight());
+ if ($output === false) {
+ throw new \RuntimeException('Failed to create output image.');
+ }
+
+ // Fill the output canvas with our background color.
+ // NOTE: If cropping, this is just to have a nice background in
+ // the resulting JPG if a transparent image was used as input.
+ // If expanding, this will be the color of the border as well.
+ $bgColor = imagecolorallocate($output, $this->_bgColor[0], $this->_bgColor[1], $this->_bgColor[2]);
+ if ($bgColor === false) {
+ throw new \RuntimeException('Failed to allocate background color.');
+ }
+
+ // If expanding and blurredBorder, use the photo as a blurred border.
+ if ($this->_blurredBorder && $this->_operation === self::EXPAND) {
+ // Calculate the rectangle
+ $blurImageRect = $this->_calculateBlurImage($srcRect, $canvas);
+ $scaleDownRect = $blurImageRect->withRescaling(self::BLUR_IMAGE_SCALE, 'ceil');
+
+ // Create a canvas for the scaled image
+ $scaledDownImage = imagecreatetruecolor($scaleDownRect->getWidth(), $scaleDownRect->getHeight());
+ if ($scaledDownImage === false) {
+ throw new \RuntimeException('Failed to create scaled down image.');
+ }
+
+ // Copy the image to the scaled canvas
+ if (imagecopyresampled(
+ $scaledDownImage, $source,
+ 0, 0,
+ $blurImageRect->getX(), $blurImageRect->getY(),
+ $scaleDownRect->getWidth(), $scaleDownRect->getHeight(),
+ $blurImageRect->getWidth(), $blurImageRect->getHeight()) === false) {
+ throw new \RuntimeException('Failed to resample blur image.');
+ }
+
+ //Blur the scaled canvas
+ for ($i = 0; $i < 40; ++$i) {
+ imagefilter($scaledDownImage, IMG_FILTER_GAUSSIAN_BLUR, 999);
+ }
+
+ //Copy the blurred image to the output canvas.
+ if (imagecopyresampled(
+ $output, $scaledDownImage,
+ 0, 0,
+ 0, 0,
+ $canvas->getWidth(), $canvas->getHeight(),
+ $scaleDownRect->getWidth(), $scaleDownRect->getHeight()) === false) {
+ throw new \RuntimeException('Failed to resample blurred image.');
+ }
+ } else {
+ if (imagefilledrectangle($output, 0, 0, $canvas->getWidth() - 1, $canvas->getHeight() - 1, $bgColor) === false) {
+ throw new \RuntimeException('Failed to fill image with background color.');
+ }
+ }
+
+ // Copy the resized (and resampled) image onto the new canvas.
+ if (imagecopyresampled(
+ $output, $source,
+ $dstRect->getX(), $dstRect->getY(),
+ $srcRect->getX(), $srcRect->getY(),
+ $dstRect->getWidth(), $dstRect->getHeight(),
+ $srcRect->getWidth(), $srcRect->getHeight()
+ ) === false) {
+ throw new \RuntimeException('Failed to resample image.');
+ }
+
+ // Handle image rotation.
+ $output = $this->_rotateResource($output, $bgColor);
+
+ return $output;
+ }
+
+ /**
+ * Calculates the rectangle of the blur image source.
+ *
+ * @param Rectangle $srcRect
+ * @param Dimensions $canvas
+ *
+ * @return Rectangle
+ */
+ protected function _calculateBlurImage(
+ Rectangle $srcRect,
+ Dimensions $canvas)
+ {
+ $widthScale = (float) ($canvas->getWidth() / $srcRect->getWidth());
+ $heightScale = (float) ($canvas->getHeight() / $srcRect->getHeight());
+ if ($widthScale > $heightScale) {
+ $resX = $srcRect->getX();
+ $resW = $srcRect->getWidth();
+ $resH = (float) $canvas->getHeight() / $widthScale;
+ $resY = (int) floor(($srcRect->getHeight() - $resH) / 2);
+
+ return new Rectangle($resX, $resY, $resW, $resH);
+ } else {
+ $resY = $srcRect->getY();
+ $resH = $srcRect->getHeight();
+ $resW = (float) $canvas->getWidth() / $heightScale;
+ $resX = (int) floor(($srcRect->getWidth() - $resW) / 2);
+
+ return new Rectangle($resX, $resY, $resW, $resH);
+ }
+ }
+
+ /**
+ * Wrapper for PHP's imagerotate function.
+ *
+ * @param resource $original
+ * @param int $bgColor
+ *
+ * @throws \RuntimeException
+ *
+ * @return resource
+ */
+ protected function _rotateResource(
+ $original,
+ $bgColor)
+ {
+ $angle = 0;
+ $flip = null;
+ // Find out angle and flip.
+ if ($this->_details->hasSwappedAxes()) {
+ if ($this->_details->isHorizontallyFlipped() && $this->_details->isVerticallyFlipped()) {
+ $angle = -90;
+ $flip = IMG_FLIP_HORIZONTAL;
+ } elseif ($this->_details->isHorizontallyFlipped()) {
+ $angle = -90;
+ } elseif ($this->_details->isVerticallyFlipped()) {
+ $angle = 90;
+ } else {
+ $angle = -90;
+ $flip = IMG_FLIP_VERTICAL;
+ }
+ } else {
+ if ($this->_details->isHorizontallyFlipped() && $this->_details->isVerticallyFlipped()) {
+ $flip = IMG_FLIP_BOTH;
+ } elseif ($this->_details->isHorizontallyFlipped()) {
+ $flip = IMG_FLIP_HORIZONTAL;
+ } elseif ($this->_details->isVerticallyFlipped()) {
+ $flip = IMG_FLIP_VERTICAL;
+ } else {
+ // Do nothing.
+ }
+ }
+
+ // Flip the image resource if needed. Does not create a new resource.
+ if ($flip !== null && imageflip($original, $flip) === false) {
+ throw new \RuntimeException('Failed to flip image.');
+ }
+
+ // Return original resource if no rotation is needed.
+ if ($angle === 0) {
+ return $original;
+ }
+
+ // Attempt to create a new, rotated image resource.
+ $result = imagerotate($original, $angle, $bgColor);
+ if ($result === false) {
+ throw new \RuntimeException('Failed to rotate image.');
+ }
+
+ // Destroy the original resource since we'll return the new resource.
+ @imagedestroy($original);
+
+ return $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Photo/PhotoDetails.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Photo/PhotoDetails.php
new file mode 100755
index 0000000..497d1ff
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Photo/PhotoDetails.php
@@ -0,0 +1,177 @@
+_type;
+ }
+
+ /** {@inheritdoc} */
+ public function hasSwappedAxes()
+ {
+ return in_array($this->_orientation, [5, 6, 7, 8], true);
+ }
+
+ /** {@inheritdoc} */
+ public function isHorizontallyFlipped()
+ {
+ return in_array($this->_orientation, [2, 3, 6, 7], true);
+ }
+
+ /** {@inheritdoc} */
+ public function isVerticallyFlipped()
+ {
+ return in_array($this->_orientation, [3, 4, 7, 8], true);
+ }
+
+ /** {@inheritdoc} */
+ public function getMinAllowedWidth()
+ {
+ return self::MIN_WIDTH;
+ }
+
+ /** {@inheritdoc} */
+ public function getMaxAllowedWidth()
+ {
+ return self::MAX_WIDTH;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string $filename Path to the photo file.
+ *
+ * @throws \InvalidArgumentException If the photo file is missing or invalid.
+ */
+ public function __construct(
+ $filename)
+ {
+ // Check if input file exists.
+ if (empty($filename) || !is_file($filename)) {
+ throw new \InvalidArgumentException(sprintf('The photo file "%s" does not exist on disk.', $filename));
+ }
+
+ // Determine photo file size and throw when the file is empty.
+ $filesize = filesize($filename);
+ if ($filesize < 1) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The photo file "%s" is empty.',
+ $filename
+ ));
+ }
+
+ // Get image details.
+ $result = @getimagesize($filename);
+ if ($result === false) {
+ throw new \InvalidArgumentException(sprintf('The photo file "%s" is not a valid image.', $filename));
+ }
+ list($width, $height, $this->_type) = $result;
+
+ // Detect JPEG EXIF orientation if it exists.
+ $this->_orientation = $this->_getExifOrientation($filename, $this->_type);
+
+ parent::__construct($filename, $filesize, $width, $height);
+ }
+
+ /** {@inheritdoc} */
+ public function validate(
+ ConstraintsInterface $constraints)
+ {
+ parent::validate($constraints);
+
+ // WARNING TO CONTRIBUTORS: $mediaFilename is for ERROR DISPLAY to
+ // users. Do NOT use it to read from the hard disk!
+ $mediaFilename = $this->getBasename();
+
+ // Validate image type.
+ // NOTE: It is confirmed that Instagram only accepts JPEG files.
+ $type = $this->getType();
+ if ($type !== IMAGETYPE_JPEG) {
+ throw new \InvalidArgumentException(sprintf('The photo file "%s" is not a JPEG file.', $mediaFilename));
+ }
+
+ $width = $this->getWidth();
+ // Validate photo resolution. Instagram allows between 320px-1080px width.
+ if ($width < self::MIN_WIDTH || $width > self::MAX_WIDTH) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts photos that are between %d and %d pixels wide. Your file "%s" is %d pixels wide.',
+ self::MIN_WIDTH, self::MAX_WIDTH, $mediaFilename, $width
+ ));
+ }
+ }
+
+ /**
+ * Get the EXIF orientation from given file.
+ *
+ * @param string $filename
+ * @param int $type
+ *
+ * @return int
+ */
+ protected function _getExifOrientation(
+ $filename,
+ $type)
+ {
+ if ($type !== IMAGETYPE_JPEG || !function_exists('exif_read_data')) {
+ return self::DEFAULT_ORIENTATION;
+ }
+
+ $exif = @exif_read_data($filename);
+ if ($exif === false || !isset($exif['Orientation'])) {
+ return self::DEFAULT_ORIENTATION;
+ }
+
+ return (int) $exif['Orientation'];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/FFmpeg.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/FFmpeg.php
new file mode 100755
index 0000000..7bde900
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/FFmpeg.php
@@ -0,0 +1,244 @@
+_ffmpegBinary = $ffmpegBinary;
+
+ try {
+ $this->version();
+ } catch (\Exception $e) {
+ throw new \RuntimeException(sprintf('It seems that the path to ffmpeg binary is invalid. Please check your path to ensure that it is correct.'));
+ }
+ }
+
+ /**
+ * Create a new instance or use a cached one.
+ *
+ * @param string|null $ffmpegBinary Path to a ffmpeg binary, or NULL to autodetect.
+ *
+ * @return static
+ */
+ public static function factory(
+ $ffmpegBinary = null)
+ {
+ if ($ffmpegBinary === null) {
+ return static::_autoDetectBinary();
+ }
+
+ if (isset(self::$_instances[$ffmpegBinary])) {
+ return self::$_instances[$ffmpegBinary];
+ }
+
+ $instance = new static($ffmpegBinary);
+ self::$_instances[$ffmpegBinary] = $instance;
+
+ return $instance;
+ }
+
+ /**
+ * Run a command and wrap errors into an Exception (if any).
+ *
+ * @param string $command
+ *
+ * @throws \RuntimeException
+ *
+ * @return string[]
+ */
+ public function run(
+ $command)
+ {
+ $process = $this->runAsync($command);
+
+ try {
+ $exitCode = $process->wait();
+ } catch (\Exception $e) {
+ throw new \RuntimeException(sprintf('Failed to run the ffmpeg binary: %s', $e->getMessage()));
+ }
+ if ($exitCode) {
+ $errors = preg_replace('#[\r\n]+#', '"], ["', trim($process->getErrorOutput()));
+ $errorMsg = sprintf('FFmpeg Errors: ["%s"], Command: "%s".', $errors, $command);
+
+ throw new \RuntimeException($errorMsg, $exitCode);
+ }
+
+ return preg_split('#[\r\n]+#', $process->getOutput(), null, PREG_SPLIT_NO_EMPTY);
+ }
+
+ /**
+ * Run a command asynchronously.
+ *
+ * @param string $command
+ *
+ * @return Process
+ */
+ public function runAsync(
+ $command)
+ {
+ $fullCommand = sprintf('%s -v error %s', Args::escape($this->_ffmpegBinary), $command);
+
+ $process = new Process($fullCommand);
+ if (is_int(self::$defaultTimeout) && self::$defaultTimeout > 60) {
+ $process->setTimeout(self::$defaultTimeout);
+ }
+ $process->start();
+
+ return $process;
+ }
+
+ /**
+ * Get the ffmpeg version.
+ *
+ * @throws \RuntimeException
+ *
+ * @return string
+ */
+ public function version()
+ {
+ return $this->run('-version')[0];
+ }
+
+ /**
+ * Get the path to the ffmpeg binary.
+ *
+ * @return string
+ */
+ public function getFFmpegBinary()
+ {
+ return $this->_ffmpegBinary;
+ }
+
+ /**
+ * Check whether ffmpeg has -noautorotate flag.
+ *
+ * @return bool
+ */
+ public function hasNoAutorotate()
+ {
+ if ($this->_hasNoAutorotate === null) {
+ try {
+ $this->run('-noautorotate -f lavfi -i color=color=red -t 1 -f null -');
+ $this->_hasNoAutorotate = true;
+ } catch (\RuntimeException $e) {
+ $this->_hasNoAutorotate = false;
+ }
+ }
+
+ return $this->_hasNoAutorotate;
+ }
+
+ /**
+ * Check whether ffmpeg has libfdk_aac audio encoder.
+ *
+ * @return bool
+ */
+ public function hasLibFdkAac()
+ {
+ if ($this->_hasLibFdkAac === null) {
+ $this->_hasLibFdkAac = $this->_hasAudioEncoder('libfdk_aac');
+ }
+
+ return $this->_hasLibFdkAac;
+ }
+
+ /**
+ * Check whether ffmpeg has specified audio encoder.
+ *
+ * @param string $encoder
+ *
+ * @return bool
+ */
+ protected function _hasAudioEncoder(
+ $encoder)
+ {
+ try {
+ $this->run(sprintf(
+ '-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -c:a %s -t 1 -f null -',
+ Args::escape($encoder)
+ ));
+
+ return true;
+ } catch (\RuntimeException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @throws \RuntimeException
+ *
+ * @return static
+ */
+ protected static function _autoDetectBinary()
+ {
+ $binaries = defined('PHP_WINDOWS_VERSION_MAJOR') ? self::WINDOWS_BINARIES : self::BINARIES;
+ if (self::$defaultBinary !== null) {
+ array_unshift($binaries, self::$defaultBinary);
+ }
+ /* Backwards compatibility. */
+ if (Utils::$ffmpegBin !== null) {
+ array_unshift($binaries, Utils::$ffmpegBin);
+ }
+
+ $instance = null;
+ foreach ($binaries as $binary) {
+ if (isset(self::$_instances[$binary])) {
+ return self::$_instances[$binary];
+ }
+
+ try {
+ $instance = new static($binary);
+ } catch (\Exception $e) {
+ continue;
+ }
+ self::$defaultBinary = $binary;
+ self::$_instances[$binary] = $instance;
+
+ return $instance;
+ }
+
+ throw new \RuntimeException('You must have FFmpeg to process videos. Ensure that its binary-folder exists in your PATH environment variable, or manually set its full path via "\InstagramAPI\Media\Video\FFmpeg::$defaultBinary = \'/home/exampleuser/ffmpeg/bin/ffmpeg\';" at the start of your script.');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/InstagramThumbnail.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/InstagramThumbnail.php
new file mode 100755
index 0000000..882cd3b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/InstagramThumbnail.php
@@ -0,0 +1,155 @@
+_thumbnailTimestamp = 1.0; // Default.
+
+ // Handle per-feed timestamps and custom thumbnail timestamps.
+ if (isset($options['targetFeed'])) {
+ switch ($options['targetFeed']) {
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ // Stories always have the thumbnail at "00:00:00.000" instead.
+ $this->_thumbnailTimestamp = 0.0;
+ break;
+ case Constants::FEED_TIMELINE || Constants::FEED_TIMELINE_ALBUM:
+ // Handle custom timestamp (only supported by timeline media).
+ // NOTE: Matches real app which only customizes timeline covers.
+ if (isset($options['thumbnailTimestamp'])) {
+ $customTimestamp = $options['thumbnailTimestamp'];
+ // If custom timestamp is a number, use as-is. Else assume
+ // a "HH:MM:SS[.000]" string and convert it. Throws if bad.
+ $this->_thumbnailTimestamp = is_int($customTimestamp) || is_float($customTimestamp)
+ ? (float) $customTimestamp
+ : Utils::hmsTimeToSeconds($customTimestamp);
+ }
+ break;
+ default:
+ // Keep the default.
+ }
+ }
+
+ // Ensure the timestamp is 0+ and never longer than the video duration.
+ if ($this->_thumbnailTimestamp > $this->_details->getDuration()) {
+ $this->_thumbnailTimestamp = $this->_details->getDuration();
+ }
+ if ($this->_thumbnailTimestamp < 0.0) {
+ throw new \InvalidArgumentException('Thumbnail timestamp must be a positive number.');
+ }
+ }
+
+ /**
+ * Get thumbnail timestamp as a float.
+ *
+ * @return float Thumbnail offset in secs, with milliseconds (decimals).
+ */
+ public function getTimestamp()
+ {
+ return $this->_thumbnailTimestamp;
+ }
+
+ /**
+ * Get thumbnail timestamp as a formatted string.
+ *
+ * @return string The time formatted as `HH:MM:SS.###` (`###` is millis).
+ */
+ public function getTimestampString()
+ {
+ return Utils::hmsTimeFromSeconds($this->_thumbnailTimestamp);
+ }
+
+ /** {@inheritdoc} */
+ protected function _shouldProcess()
+ {
+ // We must always process the video to get its thumbnail.
+ return true;
+ }
+
+ /** {@inheritdoc} */
+ protected function _ffmpegMustRunAgain(
+ $attempt,
+ array $ffmpegOutput)
+ {
+ // If this was the first run, we must look for the "first frame is no
+ // keyframe" error. It is a rare error which can happen when the user
+ // wants to extract a frame from a timestamp that is before the first
+ // keyframe of the video file. Most files can extract frames even at
+ // `00:00:00.000`, but certain files may have garbage at the start of
+ // the file, and thus extracting a garbage / empty / broken frame and
+ // showing this error. The solution is to omit the `-ss` timestamp for
+ // such files to automatically make ffmpeg extract the 1st VALID frame.
+ // NOTE: We'll only need to retry if timestamp is over 0.0. If it was
+ // zero, then we already executed without `-ss` and shouldn't retry.
+ if ($attempt === 1 && $this->_thumbnailTimestamp > 0.0) {
+ foreach ($ffmpegOutput as $line) {
+ // Example: `[flv @ 0x7fc9cc002e00] warning: first frame is no keyframe`.
+ if (strpos($line, ': first frame is no keyframe') !== false) {
+ return true;
+ }
+ }
+ }
+
+ // If this was the 2nd run or there was no error, accept result as-is.
+ return false;
+ }
+
+ /** {@inheritdoc} */
+ protected function _getInputFlags(
+ $attempt)
+ {
+ // The seektime *must* be specified here, before the input file.
+ // Otherwise ffmpeg will do a slow conversion of the whole file
+ // (but discarding converted frames) until it gets to target time.
+ // See: https://trac.ffmpeg.org/wiki/Seeking
+ // IMPORTANT: WE ONLY APPLY THE SEEK-COMMAND ON THE *FIRST* ATTEMPT. SEE
+ // COMMENTS IN `_ffmpegMustRunAgain()` FOR MORE INFORMATION ABOUT WHY.
+ // AND WE'LL OMIT SEEKING COMPLETELY IF IT'S "0.0" ("EARLIEST POSSIBLE"), TO
+ // GUARANTEE SUCCESS AT GRABBING THE "EARLIEST FRAME" W/O NEEDING RETRIES.
+ return $attempt > 1 || $this->_thumbnailTimestamp === 0.0
+ ? []
+ : [
+ sprintf('-ss %s', $this->getTimestampString()),
+ ];
+ }
+
+ /** {@inheritdoc} */
+ protected function _getOutputFlags(
+ $attempt)
+ {
+ return [
+ '-f mjpeg',
+ '-vframes 1',
+ ];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/InstagramVideo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/InstagramVideo.php
new file mode 100755
index 0000000..c6072a1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/InstagramVideo.php
@@ -0,0 +1,263 @@
+_details = new VideoDetails($this->_inputFile);
+
+ $this->_ffmpeg = $ffmpeg;
+ if ($this->_ffmpeg === null) {
+ $this->_ffmpeg = FFmpeg::factory();
+ }
+ }
+
+ /** {@inheritdoc} */
+ protected function _isMod2CanvasRequired()
+ {
+ return true;
+ }
+
+ /** {@inheritdoc} */
+ protected function _createOutputFile(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas)
+ {
+ $outputFile = null;
+
+ try {
+ // Prepare output file.
+ $outputFile = Utils::createTempFile($this->_tmpPath, 'VID');
+ // Attempt to process the input file.
+ // --------------------------------------------------------------
+ // WARNING: This calls ffmpeg, which can run for a long time. The
+ // user may be running in a CLI. In that case, if they press Ctrl-C
+ // to abort, PHP won't run ANY of our shutdown/destructor handlers!
+ // Therefore they'll still have the temp file if they abort ffmpeg
+ // conversion with Ctrl-C, since our auto-cleanup won't run. There's
+ // absolutely nothing good we can do about that (except a signal
+ // handler to interrupt their Ctrl-C, which is a terrible idea).
+ // Their OS should clear its temp folder periodically. Or if they
+ // use a custom temp folder, it's THEIR own job to clear it!
+ // --------------------------------------------------------------
+ $this->_processVideo($srcRect, $dstRect, $canvas, $outputFile);
+ } catch (\Exception $e) {
+ if ($outputFile !== null && is_file($outputFile)) {
+ @unlink($outputFile);
+ }
+
+ throw $e; // Re-throw.
+ }
+
+ return $outputFile;
+ }
+
+ /**
+ * @param Rectangle $srcRect Rectangle to copy from the input.
+ * @param Rectangle $dstRect Destination place and scale of copied pixels.
+ * @param Dimensions $canvas The size of the destination canvas.
+ * @param string $outputFile
+ *
+ * @throws \RuntimeException
+ */
+ protected function _processVideo(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas,
+ $outputFile)
+ {
+ // ffmpeg has a bug - https://trac.ffmpeg.org/ticket/6370 - it preserves rotate tag
+ // in the output video when the input video has display matrix with rotate
+ // and rotation was overridden manually.
+ $shouldAutorotate = $this->_ffmpeg->hasNoAutorotate() && $this->_details->hasRotationMatrix();
+
+ // Swap to correct dimensions if the video pixels are stored rotated.
+ if (!$shouldAutorotate && $this->_details->hasSwappedAxes()) {
+ $srcRect = $srcRect->withSwappedAxes();
+ $dstRect = $dstRect->withSwappedAxes();
+ $canvas = $canvas->withSwappedAxes();
+ }
+
+ // Prepare filters.
+ $bgColor = sprintf('0x%02X%02X%02X', ...$this->_bgColor);
+ $filters = [
+ sprintf('crop=w=%d:h=%d:x=%d:y=%d', $srcRect->getWidth(), $srcRect->getHeight(), $srcRect->getX(), $srcRect->getY()),
+ sprintf('scale=w=%d:h=%d', $dstRect->getWidth(), $dstRect->getHeight()),
+ sprintf('pad=w=%d:h=%d:x=%d:y=%d:color=%s', $canvas->getWidth(), $canvas->getHeight(), $dstRect->getX(), $dstRect->getY(), $bgColor),
+ ];
+
+ // Rotate the video (if needed to).
+ $rotationFilters = $this->_getRotationFilters();
+ $noAutorotate = false;
+ if (!empty($rotationFilters) && !$shouldAutorotate) {
+ $filters = array_merge($filters, $rotationFilters);
+ $noAutorotate = $this->_ffmpeg->hasNoAutorotate();
+ }
+
+ $attempt = 0;
+ do {
+ ++$attempt;
+
+ // Get the flags to apply to the input file.
+ $inputFlags = $this->_getInputFlags($attempt);
+ if ($noAutorotate) {
+ $inputFlags[] = '-noautorotate';
+ }
+
+ // Video format can't copy since we always need to re-encode due to video filtering.
+ $ffmpegOutput = $this->_ffmpeg->run(sprintf(
+ '-y %s -i %s -vf %s %s %s',
+ implode(' ', $inputFlags),
+ Args::escape($this->_inputFile),
+ Args::escape(implode(',', $filters)),
+ implode(' ', $this->_getOutputFlags($attempt)),
+ Args::escape($outputFile)
+ ));
+ } while ($this->_ffmpegMustRunAgain($attempt, $ffmpegOutput));
+ }
+
+ /**
+ * Internal function to determine whether ffmpeg needs to run again.
+ *
+ * @param int $attempt Which ffmpeg attempt just executed.
+ * @param string[] $ffmpegOutput Array of error strings from the attempt.
+ *
+ * @throws \RuntimeException If this function wants to give up and determines
+ * that we cannot succeed and should throw completely.
+ *
+ * @return bool TRUE to run again, FALSE to accept the current output.
+ */
+ protected function _ffmpegMustRunAgain(
+ $attempt,
+ array $ffmpegOutput)
+ {
+ return false;
+ }
+
+ /**
+ * Get the input flags (placed before the input filename).
+ *
+ * @param int $attempt The current ffmpeg execution attempt.
+ *
+ * @return string[]
+ */
+ protected function _getInputFlags(
+ $attempt)
+ {
+ return [];
+ }
+
+ /**
+ * Get the output flags (placed before the output filename).
+ *
+ * @param int $attempt The current ffmpeg execution attempt.
+ *
+ * @return string[]
+ */
+ protected function _getOutputFlags(
+ $attempt)
+ {
+ $result = [
+ '-metadata:s:v rotate=""', // Strip rotation from metadata.
+ '-f mp4', // Force output format to MP4.
+ ];
+
+ // Force H.264 for the video.
+ $result[] = '-c:v libx264 -preset fast -crf 24';
+
+ // Force AAC for the audio.
+ if ($this->_details->getAudioCodec() !== 'aac') {
+ if ($this->_ffmpeg->hasLibFdkAac()) {
+ $result[] = '-c:a libfdk_aac -vbr 4';
+ } else {
+ // The encoder 'aac' is experimental but experimental codecs are not enabled,
+ // add '-strict -2' if you want to use it.
+ $result[] = '-strict -2 -c:a aac -b:a 96k';
+ }
+ } else {
+ $result[] = '-c:a copy';
+ }
+
+ // Cut too long videos.
+ // FFmpeg cuts video sticking to a closest frame. As a result we might
+ // end with a video that is longer than desired duration. To prevent this
+ // we must use a duration that is somewhat smaller than its maximum allowed
+ // value. 0.1 sec is 1 frame of 10 FPS video.
+ $maxDuration = $this->_constraints->getMaxDuration() - 0.1;
+ if ($this->_details->getDuration() > $maxDuration) {
+ $result[] = sprintf('-t %.2F', $maxDuration);
+ }
+
+ // TODO Loop too short videos.
+ if ($this->_details->getDuration() < $this->_constraints->getMinDuration()) {
+ $times = ceil($this->_constraints->getMinDuration() / $this->_details->getDuration());
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get an array of filters needed to restore video orientation.
+ *
+ * @return array
+ */
+ protected function _getRotationFilters()
+ {
+ $result = [];
+ if ($this->_details->hasSwappedAxes()) {
+ if ($this->_details->isHorizontallyFlipped() && $this->_details->isVerticallyFlipped()) {
+ $result[] = 'transpose=clock';
+ $result[] = 'hflip';
+ } elseif ($this->_details->isHorizontallyFlipped()) {
+ $result[] = 'transpose=clock';
+ } elseif ($this->_details->isVerticallyFlipped()) {
+ $result[] = 'transpose=cclock';
+ } else {
+ $result[] = 'transpose=cclock';
+ $result[] = 'vflip';
+ }
+ } else {
+ if ($this->_details->isHorizontallyFlipped()) {
+ $result[] = 'hflip';
+ }
+ if ($this->_details->isVerticallyFlipped()) {
+ $result[] = 'vflip';
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/VideoDetails.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/VideoDetails.php
new file mode 100755
index 0000000..a1edc96
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Media/Video/VideoDetails.php
@@ -0,0 +1,348 @@
+_duration;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDurationInMsec()
+ {
+ // NOTE: ceil() is to round up and get rid of any MS decimals.
+ return (int) ceil($this->getDuration() * 1000);
+ }
+
+ /**
+ * @return string
+ */
+ public function getVideoCodec()
+ {
+ return $this->_videoCodec;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getAudioCodec()
+ {
+ return $this->_audioCodec;
+ }
+
+ /**
+ * @return string
+ */
+ public function getContainer()
+ {
+ return $this->_container;
+ }
+
+ /** {@inheritdoc} */
+ public function hasSwappedAxes()
+ {
+ return $this->_rotation % 180;
+ }
+
+ /** {@inheritdoc} */
+ public function isHorizontallyFlipped()
+ {
+ return $this->_rotation === 90 || $this->_rotation === 180;
+ }
+
+ /** {@inheritdoc} */
+ public function isVerticallyFlipped()
+ {
+ return $this->_rotation === 180 || $this->_rotation === 270;
+ }
+
+ /**
+ * Check whether the video has display matrix with rotate.
+ *
+ * @return bool
+ */
+ public function hasRotationMatrix()
+ {
+ return $this->_hasRotationMatrix;
+ }
+
+ /** {@inheritdoc} */
+ public function getMinAllowedWidth()
+ {
+ return self::MIN_WIDTH;
+ }
+
+ /** {@inheritdoc} */
+ public function getMaxAllowedWidth()
+ {
+ return self::MAX_WIDTH;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string $filename Path to the video file.
+ *
+ * @throws \InvalidArgumentException If the video file is missing or invalid.
+ * @throws \RuntimeException If FFmpeg isn't working properly.
+ */
+ public function __construct(
+ $filename)
+ {
+ // Check if input file exists.
+ if (empty($filename) || !is_file($filename)) {
+ throw new \InvalidArgumentException(sprintf('The video file "%s" does not exist on disk.', $filename));
+ }
+
+ // Determine video file size and throw when the file is empty.
+ $filesize = filesize($filename);
+ if ($filesize < 1) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The video "%s" is empty.',
+ $filename
+ ));
+ }
+
+ // The user must have FFprobe.
+ $ffprobe = Utils::checkFFPROBE();
+ if ($ffprobe === false) {
+ throw new \RuntimeException('You must have FFprobe to analyze video details. Ensure that its binary-folder exists in your PATH environment variable, or manually set its full path via "\InstagramAPI\Utils::$ffprobeBin = \'/home/exampleuser/ffmpeg/bin/ffprobe\';" at the start of your script.');
+ }
+
+ // Load with FFPROBE. Shows details as JSON and exits.
+ $command = sprintf(
+ '%s -v quiet -print_format json -show_format -show_streams %s',
+ Args::escape($ffprobe),
+ Args::escape($filename)
+ );
+ $jsonInfo = @shell_exec($command);
+
+ // Check for processing errors.
+ if ($jsonInfo === null) {
+ throw new \RuntimeException(sprintf('FFprobe failed to analyze your video file "%s".', $filename));
+ }
+
+ // Attempt to decode the JSON.
+ $probeResult = @json_decode($jsonInfo, true);
+ if ($probeResult === null) {
+ throw new \RuntimeException(sprintf('FFprobe gave us invalid JSON for "%s".', $filename));
+ }
+
+ if (!isset($probeResult['streams']) || !is_array($probeResult['streams'])) {
+ throw new \RuntimeException(sprintf('FFprobe failed to detect any stream. Is "%s" a valid media file?', $filename));
+ }
+
+ // Now analyze all streams to find the first video stream.
+ $width = null;
+ $height = null;
+ $this->_hasRotationMatrix = false;
+ foreach ($probeResult['streams'] as $streamIdx => $streamInfo) {
+ if (!isset($streamInfo['codec_type'])) {
+ continue;
+ }
+ switch ($streamInfo['codec_type']) {
+ case 'video':
+ // TODO Mark media as invalid if more than one video stream found (?)
+ $foundVideoStream = true;
+ $this->_videoCodec = (string) $streamInfo['codec_name']; // string
+ $width = (int) $streamInfo['width'];
+ $height = (int) $streamInfo['height'];
+ if (isset($streamInfo['duration'])) {
+ // NOTE: Duration is a float such as "230.138000".
+ $this->_duration = (float) $streamInfo['duration'];
+ }
+
+ // Read rotation angle from tags.
+ $this->_rotation = 0;
+ if (isset($streamInfo['tags']) && is_array($streamInfo['tags'])) {
+ $tags = array_change_key_case($streamInfo['tags'], CASE_LOWER);
+ if (isset($tags['rotate'])) {
+ $this->_rotation = $this->_normalizeRotation((int) $tags['rotate']);
+ }
+ }
+ if (isset($streamInfo['side_data_list']) && is_array($streamInfo['side_data_list'])) {
+ foreach ($streamInfo['side_data_list'] as $sideData) {
+ if (!isset($sideData['side_data_type'])) {
+ continue;
+ }
+ if (!isset($sideData['rotation'])) {
+ continue;
+ }
+
+ $this->_hasRotationMatrix =
+ strcasecmp($sideData['side_data_type'], 'Display Matrix') === 0
+ && abs($sideData['rotation']) > 0.1;
+ }
+ }
+ break;
+ case 'audio':
+ // TODO Mark media as invalid if more than one audio stream found (?)
+ $this->_audioCodec = (string) $streamInfo['codec_name']; // string
+ break;
+ default:
+ // TODO Mark media as invalid if unknown stream found (?)
+ }
+ }
+
+ // Sometimes there is no duration in stream info, so we should check the format.
+ if ($this->_duration === null && isset($probeResult['format']['duration'])) {
+ $this->_duration = (float) $probeResult['format']['duration'];
+ }
+
+ if (isset($probeResult['format']['format_name'])) {
+ $this->_container = (string) $probeResult['format']['format_name'];
+ }
+
+ // Make sure we have detected the video duration.
+ if ($this->_duration === null) {
+ throw new \RuntimeException(sprintf('FFprobe failed to detect video duration. Is "%s" a valid video file?', $filename));
+ }
+
+ parent::__construct($filename, $filesize, $width, $height);
+ }
+
+ /** {@inheritdoc} */
+ public function validate(
+ ConstraintsInterface $constraints)
+ {
+ parent::validate($constraints);
+
+ // WARNING TO CONTRIBUTORS: $mediaFilename is for ERROR DISPLAY to
+ // users. Do NOT use it to read from the hard disk!
+ $mediaFilename = $this->getBasename();
+
+ // Make sure we have found at least one video stream.
+ if ($this->_videoCodec === null) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram requires video with at least one video stream. Your file "%s" doesn\'t have any.',
+ $mediaFilename
+ ));
+ }
+
+ // Check the video stream. We should have at least one.
+ if ($this->_videoCodec !== 'h264') {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos encoded with the H.264 video codec. Your file "%s" has "%s".',
+ $mediaFilename, $this->_videoCodec
+ ));
+ }
+
+ // Check the audio stream (if available).
+ if ($this->_audioCodec !== null && $this->_audioCodec !== 'aac') {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos encoded with the AAC audio codec. Your file "%s" has "%s".',
+ $mediaFilename, $this->_audioCodec
+ ));
+ }
+
+ // Check the container format.
+ if (strpos($this->_container, 'mp4') === false) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos packed into MP4 containers. Your file "%s" has "%s".',
+ $mediaFilename, $this->_container
+ ));
+ }
+
+ // Validate video resolution. Instagram allows between 480px-720px width.
+ // NOTE: They'll resize 720px wide videos on the server, to 640px instead.
+ // NOTE: Their server CAN receive between 320px-1080px width without
+ // rejecting the video, but the official app would NEVER upload such
+ // resolutions. It's controlled by the "ig_android_universe_video_production"
+ // experiment variable, which currently enforces width of min:480, max:720.
+ // If users want to upload bigger videos, they MUST resize locally first!
+ $width = $this->getWidth();
+ if ($width < self::MIN_WIDTH || $width > self::MAX_WIDTH) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos that are between %d and %d pixels wide. Your file "%s" is %d pixels wide.',
+ self::MIN_WIDTH, self::MAX_WIDTH, $mediaFilename, $width
+ ));
+ }
+
+ // Validate video length.
+ // NOTE: Instagram has no disk size limit, but this length validation
+ // also ensures we can only upload small files exactly as intended.
+ $duration = $this->getDuration();
+ $minDuration = $constraints->getMinDuration();
+ $maxDuration = $constraints->getMaxDuration();
+ if ($duration < $minDuration || $duration > $maxDuration) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts %s videos that are between %.3f and %.3f seconds long. Your video "%s" is %.3f seconds long.',
+ $constraints->getTitle(), $minDuration, $maxDuration, $mediaFilename, $duration
+ ));
+ }
+ }
+
+ /**
+ * @param $rotation
+ *
+ * @return int
+ */
+ private function _normalizeRotation(
+ $rotation)
+ {
+ // The angle must be in 0..359 degrees range.
+ $result = $rotation % 360;
+ // Negative angle can be normalized by adding it to 360:
+ // 360 + (-90) = 270.
+ if ($result < 0) {
+ $result = 360 + $result;
+ }
+ // The final angle must be one of 0, 90, 180 or 270 degrees.
+ // So we are rounding it to the closest one.
+ $result = round($result / 90) * 90;
+
+ return (int) $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Middleware/FakeCookies.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Middleware/FakeCookies.php
new file mode 100755
index 0000000..dc92df4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Middleware/FakeCookies.php
@@ -0,0 +1,172 @@
+clear();
+ }
+
+ /**
+ * Removes all fake cookies so they won't be added to further requests.
+ */
+ public function clear()
+ {
+ $this->_cookies = [];
+ }
+
+ /**
+ * Get all currently used fake cookies.
+ *
+ * @return array
+ */
+ public function cookies()
+ {
+ return $this->_cookies;
+ }
+
+ /**
+ * Adds a fake cookie which will be injected into all requests.
+ *
+ * Remember to clear your fake cookies when you no longer need them.
+ *
+ * Usually you only need fake cookies for a few requests, and care must be
+ * taken to GUARANTEE clearout after that, via something like the following:
+ * "try{...}finally{ ...->clearFakeCookies(); }").
+ *
+ * Otherwise you would FOREVER pollute all other requests!
+ *
+ * If you only need the cookie once, the best way to guarantee clearout is
+ * to leave the "singleUse" parameter in its enabled state.
+ *
+ * @param string $name The name of the cookie. CASE SENSITIVE!
+ * @param string $value The value of the cookie.
+ * @param bool $singleUse If TRUE, the cookie will be deleted after 1 use.
+ */
+ public function add(
+ $name,
+ $value,
+ $singleUse = true)
+ {
+ // This overwrites any existing fake cookie with the same name, which is
+ // intentional since the names of cookies must be unique.
+ $this->_cookies[$name] = [
+ 'value' => $value,
+ 'singleUse' => $singleUse,
+ ];
+ }
+
+ /**
+ * Delete a single fake cookie.
+ *
+ * Useful for selectively removing some fake cookies but keeping the rest.
+ *
+ * @param string $name The name of the cookie. CASE SENSITIVE!
+ */
+ public function delete(
+ $name)
+ {
+ unset($this->_cookies[$name]);
+ }
+
+ /**
+ * Middleware setup function.
+ *
+ * Called by Guzzle when it needs to add an instance of our middleware to
+ * its stack. We simply return a callable which will process all requests.
+ *
+ * @param callable $handler
+ *
+ * @return callable
+ */
+ public function __invoke(
+ callable $handler)
+ {
+ return function (
+ RequestInterface $request,
+ array $options
+ ) use ($handler) {
+ $fakeCookies = $this->cookies();
+
+ // Pass request through unmodified if no work to do (to save CPU).
+ if (count($fakeCookies) === 0) {
+ return $handler($request, $options);
+ }
+
+ $finalCookies = [];
+
+ // Extract all existing cookies in this request's "Cookie:" header.
+ if ($request->hasHeader('Cookie')) {
+ $cookieHeaders = $request->getHeader('Cookie');
+ foreach ($cookieHeaders as $headerLine) {
+ $theseCookies = explode('; ', $headerLine);
+ foreach ($theseCookies as $cookieEntry) {
+ $cookieParts = explode('=', $cookieEntry, 2);
+ if (count($cookieParts) == 2) {
+ // We have the name and value of the cookie!
+ $finalCookies[$cookieParts[0]] = $cookieParts[1];
+ } else {
+ // Unable to find an equals sign, just re-use this
+ // cookie as-is (TRUE="re-use literally").
+ $finalCookies[$cookieEntry] = true;
+ }
+ }
+ }
+ }
+
+ // Inject all of our fake cookies, overwriting any name clashes.
+ // NOTE: The name matching is CASE SENSITIVE!
+ foreach ($fakeCookies as $name => $cookieInfo) {
+ $finalCookies[$name] = $cookieInfo['value'];
+
+ // Delete the cookie now if it was a single-use cookie.
+ if ($cookieInfo['singleUse']) {
+ $this->delete($name);
+ }
+ }
+
+ // Generate all individual cookie strings for the final cookies.
+ $values = [];
+ foreach ($finalCookies as $name => $value) {
+ if ($value === true) {
+ // Cookies to re-use as-is, due to parsing error above.
+ $values[] = $name;
+ } else {
+ $values[] = $name.'='.$value;
+ }
+ }
+
+ // Generate our new, semicolon-separated "Cookie:" header line.
+ // NOTE: This completely replaces the old header. As intended.
+ $finalCookieHeader = implode('; ', $values);
+ $request = $request->withHeader('Cookie', $finalCookieHeader);
+
+ return $handler($request, $options);
+ };
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Middleware/ZeroRating.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Middleware/ZeroRating.php
new file mode 100755
index 0000000..bcf6620
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Middleware/ZeroRating.php
@@ -0,0 +1,120 @@
+ '$1b.$2$3',
+ ];
+
+ /**
+ * Rewrite rules.
+ *
+ * @var array
+ */
+ private $_rules;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->reset();
+ }
+
+ /**
+ * Reset rules to default ones.
+ */
+ public function reset()
+ {
+ $this->update(self::DEFAULT_REWRITE);
+ }
+
+ /**
+ * Update rules.
+ *
+ * @param array $rules
+ */
+ public function update(
+ array $rules = [])
+ {
+ $this->_rules = [];
+ foreach ($rules as $from => $to) {
+ $regex = "#{$from}#";
+ $test = @preg_match($regex, '');
+ if ($test === false) {
+ continue;
+ }
+ $this->_rules[$regex] = strtr($to, [
+ '\.' => '.',
+ ]);
+ }
+ }
+
+ /**
+ * Middleware setup function.
+ *
+ * Called by Guzzle when it needs to add an instance of our middleware to
+ * its stack. We simply return a callable which will process all requests.
+ *
+ * @param callable $handler
+ *
+ * @return callable
+ */
+ public function __invoke(
+ callable $handler)
+ {
+ return function (
+ RequestInterface $request,
+ array $options
+ ) use ($handler) {
+ if (empty($this->_rules)) {
+ return $handler($request, $options);
+ }
+
+ $oldUri = (string) $request->getUri();
+ $uri = $this->rewrite($oldUri);
+ if ($uri !== $oldUri) {
+ $request = $request->withUri(new Uri($uri));
+ }
+
+ return $handler($request, $options);
+ };
+ }
+
+ /**
+ * Do a rewrite.
+ *
+ * @param string $uri
+ *
+ * @return string
+ */
+ public function rewrite(
+ $uri)
+ {
+ foreach ($this->_rules as $from => $to) {
+ $result = @preg_replace($from, $to, $uri);
+ if (!is_string($result)) {
+ continue;
+ }
+ // We must break at the first succeeded replace.
+ if ($result !== $uri) {
+ return $result;
+ }
+ }
+
+ return $uri;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push.php
new file mode 100755
index 0000000..bee6cfd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push.php
@@ -0,0 +1,286 @@
+_instagram = $instagram;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+ if ($this->_logger === null) {
+ $this->_logger = new NullLogger();
+ }
+
+ $this->_fbnsAuth = new Fbns\Auth($this->_instagram);
+ $this->_fbns = $this->_getFbns();
+ }
+
+ /**
+ * Incoming notification callback.
+ *
+ * @param Notification $notification
+ */
+ protected function _onPush(
+ Notification $notification)
+ {
+ $collapseKey = $notification->getCollapseKey();
+ $this->_logger->info(sprintf('Received a push with collapse key "%s"', $collapseKey), [(string) $notification]);
+ $this->emit('incoming', [$notification]);
+ if (!empty($collapseKey)) {
+ $this->emit($collapseKey, [$notification]);
+ }
+ }
+
+ /**
+ * Create a new FBNS receiver.
+ *
+ * @return Fbns
+ */
+ protected function _getFbns()
+ {
+ $fbns = new Fbns(
+ $this,
+ new Connector($this->_instagram, $this->_loop),
+ $this->_fbnsAuth,
+ $this->_instagram->device,
+ $this->_loop,
+ $this->_logger
+ );
+ $fbns->on('fbns_auth', function ($authJson) {
+ try {
+ $this->_fbnsAuth->update($authJson);
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to update FBNS auth: %s', $e->getMessage()), [$authJson]);
+ }
+ });
+ $fbns->on('fbns_token', function ($token) {
+ // Refresh the "last token activity" timestamp.
+ // The age of this timestamp helps us detect when the user
+ // has stopped using the Push features due to inactivity.
+ try {
+ $this->_instagram->settings->set('last_fbns_token', time());
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to write FBNS token timestamp: %s', $e->getMessage()));
+ }
+ // Read our old token. If an identical value exists, then we know
+ // that we've already registered that token during this session.
+ try {
+ $oldToken = $this->_instagram->settings->get('fbns_token');
+ // Do nothing when the new token is equal to the old one.
+ if ($token === $oldToken) {
+ return;
+ }
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to read FBNS token: %s', $e->getMessage()));
+ }
+ // Register the new token.
+ try {
+ $this->_instagram->push->register('mqtt', $token);
+ } catch (\Exception $e) {
+ $this->emit('error', [$e]);
+ }
+ // Save the newly received token to the storage.
+ // NOTE: We save it even if the registration failed, since we now
+ // got it from the server and assume they've given us a good one.
+ // However, it'll always be re-validated during the general login()
+ // flow, and will be cleared there if it fails to register there.
+ try {
+ $this->_instagram->settings->set('fbns_token', $token);
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to update FBNS token: %s', $e->getMessage()), [$token]);
+ }
+ });
+ $fbns->on('push', function (Notification $notification) {
+ $this->_onPush($notification);
+ });
+
+ return $fbns;
+ }
+
+ /**
+ * Start Push receiver.
+ */
+ public function start()
+ {
+ $this->_fbns->start();
+ }
+
+ /**
+ * Stop Push receiver.
+ */
+ public function stop()
+ {
+ $this->_fbns->stop();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Fbns.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Fbns.php
new file mode 100755
index 0000000..2ddf1f2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Fbns.php
@@ -0,0 +1,194 @@
+_target = $target;
+ $this->_connector = $connector;
+ $this->_auth = $auth;
+ $this->_device = $device;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+
+ $this->_client = $this->_getClient();
+ }
+
+ /**
+ * Create a new FBNS client instance.
+ *
+ * @return Lite
+ */
+ protected function _getClient()
+ {
+ $client = new Lite($this->_loop, $this->_connector, $this->_logger);
+
+ // Bind events.
+ $client
+ ->on('connect', function (Lite\ConnectResponsePacket $responsePacket) {
+ // Update auth credentials.
+ $authJson = $responsePacket->getAuth();
+ if (strlen($authJson)) {
+ $this->_logger->info('Received a non-empty auth.', [$authJson]);
+ $this->emit('fbns_auth', [$authJson]);
+ }
+
+ // Register an application.
+ $this->_client->register(Constants::PACKAGE_NAME, Constants::FACEBOOK_ANALYTICS_APPLICATION_ID);
+ })
+ ->on('disconnect', function () {
+ // Try to reconnect.
+ if (!$this->_reconnectInterval) {
+ $this->_connect();
+ }
+ })
+ ->on('register', function (Register $message) {
+ if (!empty($message->getError())) {
+ $this->_target->emit('error', [new \RuntimeException($message->getError())]);
+
+ return;
+ }
+ $this->_logger->info('Received a non-empty token.', [$message->getToken()]);
+ $this->emit('fbns_token', [$message->getToken()]);
+ })
+ ->on('push', function (PushMessage $message) {
+ $payload = $message->getPayload();
+
+ try {
+ $notification = new Notification($payload);
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to decode push: %s', $e->getMessage()), [$payload]);
+
+ return;
+ }
+ $this->emit('push', [$notification]);
+ });
+
+ return $client;
+ }
+
+ /**
+ * Try to establish a connection.
+ */
+ protected function _connect()
+ {
+ $this->_setReconnectTimer(function () {
+ $connection = new Connection(
+ $this->_auth,
+ $this->_device->getFbUserAgent(Constants::FBNS_APPLICATION_NAME)
+ );
+
+ return $this->_client->connect(self::DEFAULT_HOST, self::DEFAULT_PORT, $connection, self::CONNECTION_TIMEOUT);
+ });
+ }
+
+ /**
+ * Start Push receiver.
+ */
+ public function start()
+ {
+ $this->_logger->info('Starting FBNS client...');
+ $this->_isActive = true;
+ $this->_reconnectInterval = 0;
+ $this->_connect();
+ }
+
+ /**
+ * Stop Push receiver.
+ */
+ public function stop()
+ {
+ $this->_logger->info('Stopping FBNS client...');
+ $this->_isActive = false;
+ $this->_cancelReconnectTimer();
+ $this->_client->disconnect();
+ }
+
+ /** {@inheritdoc} */
+ public function isActive()
+ {
+ return $this->_isActive;
+ }
+
+ /** {@inheritdoc} */
+ public function getLogger()
+ {
+ return $this->_logger;
+ }
+
+ /** {@inheritdoc} */
+ public function getLoop()
+ {
+ return $this->_loop;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Fbns/Auth.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Fbns/Auth.php
new file mode 100755
index 0000000..e68b2c3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Fbns/Auth.php
@@ -0,0 +1,89 @@
+_instagram = $instagram;
+ $this->_deviceAuth = $this->_instagram->settings->getFbnsAuth();
+ }
+
+ /** {@inheritdoc} */
+ public function getClientId()
+ {
+ return $this->_deviceAuth->getClientId();
+ }
+
+ /** {@inheritdoc} */
+ public function getClientType()
+ {
+ return $this->_deviceAuth->getClientType();
+ }
+
+ /** {@inheritdoc} */
+ public function getUserId()
+ {
+ return $this->_deviceAuth->getUserId();
+ }
+
+ /** {@inheritdoc} */
+ public function getPassword()
+ {
+ return $this->_deviceAuth->getPassword();
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceId()
+ {
+ return $this->_deviceAuth->getDeviceId();
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceSecret()
+ {
+ return $this->_deviceAuth->getDeviceSecret();
+ }
+
+ /** {@inheritdoc} */
+ public function __toString()
+ {
+ return $this->_deviceAuth->__toString();
+ }
+
+ /**
+ * Update auth data.
+ *
+ * @param string $auth
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function update(
+ $auth)
+ {
+ /* @var DeviceAuth $auth */
+ $this->_deviceAuth->read($auth);
+ $this->_instagram->settings->setFbnsAuth($this->_deviceAuth);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Notification.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Notification.php
new file mode 100755
index 0000000..3709b09
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Notification.php
@@ -0,0 +1,316 @@
+_json = $json;
+
+ if (isset($data->t)) {
+ $this->_title = (string) $data->t;
+ }
+ if (isset($data->m)) {
+ $this->_message = (string) $data->m;
+ }
+ if (isset($data->tt)) {
+ $this->_tickerText = (string) $data->tt;
+ }
+ $this->_actionPath = '';
+ $this->_actionParams = [];
+ if (isset($data->ig)) {
+ $this->_igAction = (string) $data->ig;
+ $parts = parse_url($this->_igAction);
+ if (isset($parts['path'])) {
+ $this->_actionPath = $parts['path'];
+ }
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $this->_actionParams);
+ }
+ }
+ if (isset($data->collapse_key)) {
+ $this->_collapseKey = (string) $data->collapse_key;
+ }
+ if (isset($data->i)) {
+ $this->_optionalImage = (string) $data->i;
+ }
+ if (isset($data->a)) {
+ $this->_optionalAvatarUrl = (string) $data->a;
+ }
+ if (isset($data->sound)) {
+ $this->_sound = (string) $data->sound;
+ }
+ if (isset($data->pi)) {
+ $this->_pushId = (string) $data->pi;
+ }
+ if (isset($data->c)) {
+ $this->_pushCategory = (string) $data->c;
+ }
+ if (isset($data->u)) {
+ $this->_intendedRecipientUserId = (string) $data->u;
+ }
+ if (isset($data->s)) {
+ $this->_sourceUserId = (string) $data->s;
+ }
+ if (isset($data->igo)) {
+ $this->_igActionOverride = (string) $data->igo;
+ }
+ if (isset($data->bc)) {
+ $this->_badgeCount = new BadgeCount((string) $data->bc);
+ }
+ if (isset($data->ia)) {
+ $this->_inAppActors = (string) $data->ia;
+ }
+ }
+
+ /**
+ * Notification constructor.
+ *
+ * @param string $json
+ */
+ public function __construct(
+ $json)
+ {
+ $this->_parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->_json;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->_title;
+ }
+
+ /**
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->_message;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTickerText()
+ {
+ return $this->_tickerText;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIgAction()
+ {
+ return $this->_igAction;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIgActionOverride()
+ {
+ return $this->_igActionOverride;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOptionalImage()
+ {
+ return $this->_optionalImage;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOptionalAvatarUrl()
+ {
+ return $this->_optionalAvatarUrl;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCollapseKey()
+ {
+ return $this->_collapseKey;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSound()
+ {
+ return $this->_sound;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPushId()
+ {
+ return $this->_pushId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPushCategory()
+ {
+ return $this->_pushCategory;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIntendedRecipientUserId()
+ {
+ return $this->_intendedRecipientUserId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSourceUserId()
+ {
+ return $this->_sourceUserId;
+ }
+
+ /**
+ * @return BadgeCount
+ */
+ public function getBadgeCount()
+ {
+ return $this->_badgeCount;
+ }
+
+ /**
+ * @return string
+ */
+ public function getInAppActors()
+ {
+ return $this->_inAppActors;
+ }
+
+ /**
+ * @return string
+ */
+ public function getActionPath()
+ {
+ return $this->_actionPath;
+ }
+
+ /**
+ * @return array
+ */
+ public function getActionParams()
+ {
+ return $this->_actionParams;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return mixed
+ */
+ public function getActionParam(
+ $key)
+ {
+ return isset($this->_actionParams[$key]) ? $this->_actionParams[$key] : null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Payload/BadgeCount.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Payload/BadgeCount.php
new file mode 100755
index 0000000..005debd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Push/Payload/BadgeCount.php
@@ -0,0 +1,89 @@
+_json = $json;
+
+ if (isset($data->di)) {
+ $this->_direct = (int) $data->di;
+ }
+ if (isset($data->ds)) {
+ $this->_ds = (int) $data->ds;
+ }
+ if (isset($data->ac)) {
+ $this->_activities = (int) $data->ac;
+ }
+ }
+
+ /**
+ * Notification constructor.
+ *
+ * @param string $json
+ */
+ public function __construct(
+ $json)
+ {
+ $this->_parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->_json;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDirect()
+ {
+ return $this->_direct;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDs()
+ {
+ return $this->_ds;
+ }
+
+ /**
+ * @return int
+ */
+ public function getActivities()
+ {
+ return $this->_activities;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/React/Connector.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/React/Connector.php
new file mode 100755
index 0000000..4c5ba1a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/React/Connector.php
@@ -0,0 +1,223 @@
+_instagram = $instagram;
+ $this->_loop = $loop;
+
+ $this->_connectors = [];
+ }
+
+ /** {@inheritdoc} */
+ public function connect(
+ $uri)
+ {
+ $uriObj = new Uri($uri);
+ $host = $this->_instagram->client->zeroRating()->rewrite($uriObj->getHost());
+ $uriObj = $uriObj->withHost($host);
+ if (!isset($this->_connectors[$host])) {
+ try {
+ $this->_connectors[$host] = $this->_getSecureConnector(
+ $this->_getSecureContext($this->_instagram->getVerifySSL()),
+ $this->_getProxyForHost($host, $this->_instagram->getProxy())
+ );
+ } catch (\Exception $e) {
+ return new RejectedPromise($e);
+ }
+ }
+ $niceUri = ltrim((string) $uriObj, '/');
+ /** @var PromiseInterface $promise */
+ $promise = $this->_connectors[$host]->connect($niceUri);
+
+ return $promise;
+ }
+
+ /**
+ * Create a secure connector for given configuration.
+ *
+ * @param array $secureContext
+ * @param string|null $proxyAddress
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return ConnectorInterface
+ */
+ protected function _getSecureConnector(
+ array $secureContext = [],
+ $proxyAddress = null)
+ {
+ $connector = new SocketConnector($this->_loop, [
+ 'tcp' => true,
+ 'tls' => false,
+ 'unix' => false,
+ 'dns' => true,
+ 'timeout' => true,
+ ]);
+
+ if ($proxyAddress !== null) {
+ $connector = $this->_wrapConnectorIntoProxy($connector, $proxyAddress, $secureContext);
+ }
+
+ return new SecureConnector($connector, $this->_loop, $secureContext);
+ }
+
+ /**
+ * Get a proxy address (if any) for the host based on the proxy config.
+ *
+ * @param string $host Host.
+ * @param mixed $proxyConfig Proxy config.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string|null
+ *
+ * @see http://docs.guzzlephp.org/en/stable/request-options.html#proxy
+ */
+ protected function _getProxyForHost(
+ $host,
+ $proxyConfig = null)
+ {
+ // Empty config => no proxy.
+ if (empty($proxyConfig)) {
+ return;
+ }
+
+ // Plain string => return it.
+ if (!is_array($proxyConfig)) {
+ return $proxyConfig;
+ }
+
+ // HTTP proxies do not have CONNECT method.
+ if (!isset($proxyConfig['https'])) {
+ throw new \InvalidArgumentException('No proxy with CONNECT method found.');
+ }
+
+ // Check exceptions.
+ if (isset($proxyConfig['no']) && \GuzzleHttp\is_host_in_noproxy($host, $proxyConfig['no'])) {
+ return;
+ }
+
+ return $proxyConfig['https'];
+ }
+
+ /**
+ * Parse given SSL certificate verification and return a secure context.
+ *
+ * @param mixed $config
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return array
+ *
+ * @see http://docs.guzzlephp.org/en/stable/request-options.html#verify
+ */
+ protected function _getSecureContext(
+ $config)
+ {
+ $context = [];
+ if ($config === true) {
+ // PHP 5.6 or greater will find the system cert by default. When
+ // < 5.6, use the Guzzle bundled cacert.
+ if (PHP_VERSION_ID < 50600) {
+ $context['cafile'] = \GuzzleHttp\default_ca_bundle();
+ }
+ } elseif (is_string($config)) {
+ $context['cafile'] = $config;
+ if (!is_file($config)) {
+ throw new \RuntimeException(sprintf('SSL CA bundle not found: "%s".', $config));
+ }
+ } elseif ($config === false) {
+ $context['verify_peer'] = false;
+ $context['verify_peer_name'] = false;
+
+ return $context;
+ } else {
+ throw new \InvalidArgumentException('Invalid verify request option.');
+ }
+ $context['verify_peer'] = true;
+ $context['verify_peer_name'] = true;
+ $context['allow_self_signed'] = false;
+
+ return $context;
+ }
+
+ /**
+ * Wrap the connector into a proxy one for given configuration.
+ *
+ * @param ConnectorInterface $connector
+ * @param string $proxyAddress
+ * @param array $secureContext
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return ConnectorInterface
+ */
+ protected function _wrapConnectorIntoProxy(
+ ConnectorInterface $connector,
+ $proxyAddress,
+ array $secureContext = [])
+ {
+ if (strpos($proxyAddress, '://') === false) {
+ $scheme = 'http';
+ } else {
+ $scheme = parse_url($proxyAddress, PHP_URL_SCHEME);
+ }
+ switch ($scheme) {
+ case 'socks':
+ case 'socks4':
+ case 'socks4a':
+ case 'socks5':
+ $connector = new SocksProxy($proxyAddress, $connector);
+ break;
+ case 'http':
+ $connector = new HttpConnectProxy($proxyAddress, $connector);
+ break;
+ case 'https':
+ $connector = new HttpConnectProxy($proxyAddress, new SecureConnector($connector, $this->_loop, $secureContext));
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unsupported proxy scheme: "%s".', $scheme));
+ }
+
+ return $connector;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/React/PersistentInterface.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/React/PersistentInterface.php
new file mode 100755
index 0000000..8015fcd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/React/PersistentInterface.php
@@ -0,0 +1,49 @@
+_reconnectTimer !== null) {
+ if ($this->_reconnectTimer->isActive()) {
+ $this->getLogger()->info('Existing reconnect timer has been canceled.');
+ $this->_reconnectTimer->cancel();
+ }
+ $this->_reconnectTimer = null;
+ }
+ }
+
+ /**
+ * Set up a new reconnect timer with exponential backoff.
+ *
+ * @param callable $callback
+ */
+ protected function _setReconnectTimer(
+ callable $callback)
+ {
+ $this->_cancelReconnectTimer();
+ if (!$this->isActive()) {
+ return;
+ }
+ $this->_reconnectInterval = min(
+ $this->getMaxReconnectInterval(),
+ max(
+ $this->getMinReconnectInterval(),
+ $this->_reconnectInterval * 2
+ )
+ );
+ $this->getLogger()->info(sprintf('Setting up reconnect timer to %d seconds.', $this->_reconnectInterval));
+ $this->_reconnectTimer = $this->getLoop()->addTimer($this->_reconnectInterval, function () use ($callback) {
+ /** @var PromiseInterface $promise */
+ $promise = $callback();
+ $promise->then(
+ function () {
+ // Reset reconnect interval on successful connection attempt.
+ $this->_reconnectInterval = 0;
+ },
+ function () use ($callback) {
+ $this->_setReconnectTimer($callback);
+ }
+ );
+ });
+ }
+
+ /** {@inheritdoc} */
+ public function getMinReconnectInterval()
+ {
+ return PersistentInterface::MIN_RECONNECT_INTERVAL;
+ }
+
+ /** {@inheritdoc} */
+ public function getMaxReconnectInterval()
+ {
+ return PersistentInterface::MAX_RECONNECT_INTERVAL;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime.php
new file mode 100755
index 0000000..d967d14
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime.php
@@ -0,0 +1,495 @@
+_instagram = $instagram;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+ if ($this->_logger === null) {
+ $this->_logger = new NullLogger();
+ }
+
+ $this->_client = $this->_buildMqttClient();
+ $this->on('region-hint', function ($region) {
+ $this->_instagram->settings->set('datacenter', $region);
+ $this->_client->setAdditionalOption('datacenter', $region);
+ });
+ $this->on('zero-provision', function (ZeroProvisionEvent $event) {
+ if ($event->getZeroProvisionedTime() === null) {
+ return;
+ }
+ if ($event->getProductName() !== 'select') {
+ return;
+ }
+ // TODO check whether we already have a fresh token.
+
+ $this->_instagram->client->zeroRating()->reset();
+ $this->_instagram->internal->fetchZeroRatingToken('mqtt_token_push');
+ });
+ }
+
+ /**
+ * Build a new MQTT client.
+ *
+ * @return Realtime\Mqtt
+ */
+ protected function _buildMqttClient()
+ {
+ $additionalOptions = [
+ 'datacenter' => $this->_instagram->settings->get('datacenter'),
+ 'disable_presence' => (bool) $this->_instagram->settings->get('presence_disabled'),
+ ];
+
+ return new Realtime\Mqtt(
+ $this,
+ new Connector($this->_instagram, $this->_loop),
+ new Auth($this->_instagram),
+ $this->_instagram->device,
+ $this->_instagram,
+ $this->_loop,
+ $this->_logger,
+ $additionalOptions
+ );
+ }
+
+ /**
+ * Starts underlying client.
+ */
+ public function start()
+ {
+ $this->_client->start();
+ }
+
+ /**
+ * Stops underlying client.
+ */
+ public function stop()
+ {
+ $this->_client->stop();
+ }
+
+ /**
+ * Marks thread item as seen.
+ *
+ * @param string $threadId
+ * @param string $threadItemId
+ *
+ * @return bool
+ */
+ public function markDirectItemSeen(
+ $threadId,
+ $threadItemId)
+ {
+ try {
+ $this->_client->sendCommand(new DirectCommand\MarkSeen($threadId, $threadItemId));
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Indicate activity in thread.
+ *
+ * @param string $threadId
+ * @param bool $activityFlag
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function indicateActivityInDirectThread(
+ $threadId,
+ $activityFlag)
+ {
+ try {
+ $command = new DirectCommand\IndicateActivity($threadId, $activityFlag);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Sends text message to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $message Text message.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendTextToDirect(
+ $threadId,
+ $message,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendText($threadId, $message, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Sends like to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendLikeToDirect(
+ $threadId,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendLike($threadId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share an existing media post to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendPostToDirect(
+ $threadId,
+ $mediaId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendPost($threadId, $mediaId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share an existing story to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $storyId The story ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendStoryToDirect(
+ $threadId,
+ $storyId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendStory($threadId, $storyId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share a profile to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $userId Numerical UserPK ID.
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendProfileToDirect(
+ $threadId,
+ $userId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendProfile($threadId, $userId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share a location to a given direct thread.
+ *
+ * You must provide a valid Instagram location ID, which you get via other
+ * functions such as Location::search().
+ *
+ * @param string $threadId Thread ID.
+ * @param string $locationId Instagram's internal ID for the location.
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendLocationToDirect(
+ $threadId,
+ $locationId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendLocation($threadId, $locationId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share a hashtag to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $hashtag Hashtag to share.
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendHashtagToDirect(
+ $threadId,
+ $hashtag,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendHashtag($threadId, $hashtag, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Sends reaction to a given direct thread item.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread ID.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendReactionToDirect(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ DirectCommand\SendReaction::STATUS_CREATED,
+ $options
+ );
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Removes reaction to a given direct thread item.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread ID.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function deleteReactionFromDirect(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ DirectCommand\SendReaction::STATUS_DELETED,
+ $options
+ );
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Receive offline messages starting from the sequence ID.
+ *
+ * @param int $sequenceId
+ */
+ public function receiveOfflineMessages(
+ $sequenceId)
+ {
+ try {
+ $this->_client->sendCommand(new IrisSubscribe($sequenceId));
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+ }
+ }
+
+ /**
+ * Check whether sharing via the realtime client is enabled.
+ *
+ * @return bool
+ */
+ protected function _isRtcReshareEnabled()
+ {
+ return $this->_instagram->isExperimentEnabled('ig_android_rtc_reshare', 'is_rtc_reshare_enabled');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/IndicateActivity.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/IndicateActivity.php
new file mode 100755
index 0000000..c7cc006
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/IndicateActivity.php
@@ -0,0 +1,35 @@
+_data['activity_status'] = $status ? '1' : '0';
+ }
+
+ /** {@inheritdoc} */
+ protected function _isClientContextRequired()
+ {
+ return true;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/MarkSeen.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/MarkSeen.php
new file mode 100755
index 0000000..8ca670f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/MarkSeen.php
@@ -0,0 +1,35 @@
+_data['item_id'] = $this->_validateThreadItemId($threadItemId);
+ }
+
+ /** {@inheritdoc} */
+ protected function _isClientContextRequired()
+ {
+ return false;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendHashtag.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendHashtag.php
new file mode 100755
index 0000000..eae2fcb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendHashtag.php
@@ -0,0 +1,42 @@
+_data['hashtag'] = $hashtag;
+ // Yeah, we need to send the hashtag twice.
+ $this->_data['item_id'] = $hashtag;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendItem.php
new file mode 100755
index 0000000..ba25886
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendItem.php
@@ -0,0 +1,58 @@
+_getSupportedItemTypes(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported item type.', $itemType));
+ }
+ $this->_data['item_type'] = $itemType;
+ }
+
+ /** {@inheritdoc} */
+ protected function _isClientContextRequired()
+ {
+ return true;
+ }
+
+ /**
+ * Get the list of supported item types.
+ *
+ * @return array
+ */
+ protected function _getSupportedItemTypes()
+ {
+ return [
+ SendText::TYPE,
+ SendLike::TYPE,
+ SendReaction::TYPE,
+ SendPost::TYPE,
+ SendStory::TYPE,
+ SendProfile::TYPE,
+ SendLocation::TYPE,
+ SendHashtag::TYPE,
+ ];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendLike.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendLike.php
new file mode 100755
index 0000000..a446d2e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendLike.php
@@ -0,0 +1,23 @@
+_data['venue_id'] = (string) $locationId;
+ // Yeah, we need to send the location ID twice.
+ $this->_data['item_id'] = (string) $locationId;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendPost.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendPost.php
new file mode 100755
index 0000000..a2295eb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendPost.php
@@ -0,0 +1,32 @@
+_data['media_id'] = (string) $mediaId;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendProfile.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendProfile.php
new file mode 100755
index 0000000..ef9dcae
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendProfile.php
@@ -0,0 +1,32 @@
+_data['profile_user_id'] = (string) $userId;
+ // Yeah, we need to send the user ID twice.
+ $this->_data['item_id'] = (string) $userId;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendReaction.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendReaction.php
new file mode 100755
index 0000000..af11354
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendReaction.php
@@ -0,0 +1,75 @@
+_data['item_id'] = $this->_validateThreadItemId($threadItemId);
+ $this->_data['node_type'] = 'item';
+
+ // Handle reaction type.
+ if (!in_array($reaction, $this->_getSupportedReactions(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction.', $reaction));
+ }
+ $this->_data['reaction_type'] = $reaction;
+
+ // Handle reaction status.
+ if (!in_array($status, $this->_getSupportedStatuses(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction status.', $status));
+ }
+ $this->_data['reaction_status'] = $status;
+ }
+
+ /**
+ * Get the list of supported reactions.
+ *
+ * @return array
+ */
+ protected function _getSupportedReactions()
+ {
+ return [
+ self::REACTION_LIKE,
+ ];
+ }
+
+ /**
+ * Get the list of supported statuses.
+ *
+ * @return array
+ */
+ protected function _getSupportedStatuses()
+ {
+ return [
+ self::STATUS_CREATED,
+ self::STATUS_DELETED,
+ ];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendStory.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendStory.php
new file mode 100755
index 0000000..377b3e6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendStory.php
@@ -0,0 +1,32 @@
+_data['item_id'] = (string) $storyId;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendText.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendText.php
new file mode 100755
index 0000000..2386abd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendText.php
@@ -0,0 +1,34 @@
+_data['text'] = $text;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/ShareItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/ShareItem.php
new file mode 100755
index 0000000..e5a7a8d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/ShareItem.php
@@ -0,0 +1,30 @@
+_data['text'] = $options['text'];
+ } else {
+ $this->_data['text'] = '';
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/DirectCommand.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/DirectCommand.php
new file mode 100755
index 0000000..16a05d5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/DirectCommand.php
@@ -0,0 +1,191 @@
+_data = [];
+
+ // Handle action.
+ if (!in_array($action, $this->_getSupportedActions(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported action.', $action));
+ }
+ $this->_data['action'] = $action;
+
+ $this->_data['thread_id'] = $this->_validateThreadId($threadId);
+
+ // Handle client context.
+ if ($this->_isClientContextRequired()) {
+ if (!isset($options['client_context'])) {
+ $this->_data['client_context'] = Signatures::generateUUID();
+ } elseif (!Signatures::isValidUUID($options['client_context'])) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
+ } else {
+ $this->_data['client_context'] = $options['client_context'];
+ }
+ }
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::SEND_MESSAGE;
+ }
+
+ /** {@inheritdoc} */
+ public function getQosLevel()
+ {
+ return Mqtt\QosLevel::FIRE_AND_FORGET;
+ }
+
+ /** {@inheritdoc} */
+ public function jsonSerialize()
+ {
+ return $this->_reorderFieldsByWeight($this->_data, $this->_getFieldsWeights());
+ }
+
+ /**
+ * Get the client context.
+ *
+ * @return string|null
+ */
+ public function getClientContext()
+ {
+ return isset($this->_data['client_context']) ? $this->_data['client_context'] : null;
+ }
+
+ /**
+ * Check whether client_context param is required.
+ *
+ * @return bool
+ */
+ abstract protected function _isClientContextRequired();
+
+ /**
+ * Get the list of supported actions.
+ *
+ * @return array
+ */
+ protected function _getSupportedActions()
+ {
+ return [
+ Direct\SendItem::ACTION,
+ Direct\MarkSeen::ACTION,
+ Direct\IndicateActivity::ACTION,
+ ];
+ }
+
+ /**
+ * Validate given thread identifier.
+ *
+ * @param string $threadId
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ protected function _validateThreadId(
+ $threadId)
+ {
+ if (!ctype_digit($threadId) && (!is_int($threadId) || $threadId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $threadId));
+ }
+
+ return (string) $threadId;
+ }
+
+ /**
+ * Validate given thread item identifier.
+ *
+ * @param string $threadItemId
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ protected function _validateThreadItemId(
+ $threadItemId)
+ {
+ if (!ctype_digit($threadItemId) && (!is_int($threadItemId) || $threadItemId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread item identifier.', $threadItemId));
+ }
+
+ return (string) $threadItemId;
+ }
+
+ /**
+ * Reorders an array of fields by weights to simplify debugging.
+ *
+ * @param array $fields
+ * @param array $weights
+ *
+ * @return array
+ */
+ protected function _reorderFieldsByWeight(
+ array $fields,
+ array $weights)
+ {
+ uksort($fields, function ($a, $b) use ($weights) {
+ $a = isset($weights[$a]) ? $weights[$a] : PHP_INT_MAX;
+ $b = isset($weights[$b]) ? $weights[$b] : PHP_INT_MAX;
+ if ($a < $b) {
+ return -1;
+ }
+ if ($a > $b) {
+ return 1;
+ }
+
+ return 0;
+ });
+
+ return $fields;
+ }
+
+ /**
+ * Get weights for fields.
+ *
+ * @return array
+ */
+ protected function _getFieldsWeights()
+ {
+ return [
+ 'thread_id' => 10,
+ 'item_type' => 15,
+ 'text' => 20,
+ 'client_context' => 25,
+ 'activity_status' => 30,
+ 'reaction_type' => 35,
+ 'reaction_status' => 40,
+ 'item_id' => 45,
+ 'node_type' => 50,
+ 'action' => 55,
+ 'profile_user_id' => 60,
+ 'hashtag' => 65,
+ 'venue_id' => 70,
+ 'media_id' => 75,
+ ];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/IrisSubscribe.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/IrisSubscribe.php
new file mode 100755
index 0000000..180b9c9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/IrisSubscribe.php
@@ -0,0 +1,50 @@
+_sequenceId = $sequenceId;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::IRIS_SUB;
+ }
+
+ /** {@inheritdoc} */
+ public function getQosLevel()
+ {
+ return Mqtt\QosLevel::ACKNOWLEDGED_DELIVERY;
+ }
+
+ /** {@inheritdoc} */
+ public function jsonSerialize()
+ {
+ return [
+ 'seq_id' => $this->_sequenceId,
+ ];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/UpdateSubscriptions.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/UpdateSubscriptions.php
new file mode 100755
index 0000000..5bb648c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Command/UpdateSubscriptions.php
@@ -0,0 +1,93 @@
+_topic = $topic;
+ $this->_subscribe = $subscribe;
+ $this->_unsubscribe = $unsubscribe;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return $this->_topic;
+ }
+
+ /** {@inheritdoc} */
+ public function getQosLevel()
+ {
+ return QosLevel::ACKNOWLEDGED_DELIVERY;
+ }
+
+ /**
+ * Prepare the subscriptions list.
+ *
+ * @param array $subscriptions
+ *
+ * @return array
+ */
+ private function _prepareSubscriptions(
+ array $subscriptions)
+ {
+ $result = [];
+ foreach ($subscriptions as $subscription) {
+ $result[] = (string) $subscription;
+ }
+ usort($result, function ($a, $b) {
+ $hashA = Utils::hashCode($a);
+ $hashB = Utils::hashCode($b);
+
+ if ($hashA > $hashB) {
+ return 1;
+ }
+ if ($hashA < $hashB) {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ return $result;
+ }
+
+ /** {@inheritdoc} */
+ public function jsonSerialize()
+ {
+ $result = [];
+ if (count($this->_subscribe)) {
+ $result['sub'] = $this->_prepareSubscriptions($this->_subscribe);
+ }
+ if (count($this->_unsubscribe)) {
+ $result['unsub'] = $this->_prepareSubscriptions($this->_unsubscribe);
+ }
+
+ return $result;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/CommandInterface.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/CommandInterface.php
new file mode 100755
index 0000000..4dbe91e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/CommandInterface.php
@@ -0,0 +1,20 @@
+_target = $target;
+ }
+
+ /**
+ * Checks if target has at least one listener for specific event.
+ *
+ * @param string $event
+ *
+ * @return bool
+ */
+ protected function _hasListeners(
+ $event)
+ {
+ return (bool) count($this->_target->listeners($event));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/DirectHandler.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/DirectHandler.php
new file mode 100755
index 0000000..f6c52e0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/DirectHandler.php
@@ -0,0 +1,543 @@
+[^/]+)$#D';
+ const ITEM_REGEXP = '#^/direct_v2/threads/(?[^/]+)/items/(?[^/]+)$#D';
+ const ACTIVITY_REGEXP = '#^/direct_v2/threads/(?[^/]+)/activity_indicator_id/(?[^/]+)$#D';
+ const STORY_REGEXP = '#^/direct_v2/visual_threads/(?[^/]+)/items/(?[^/]+)$#D';
+ const SEEN_REGEXP = '#^/direct_v2/threads/(?[^/]+)/participants/(?[^/]+)/has_seen$#D';
+ const SCREENSHOT_REGEXP = '#^/direct_v2/visual_thread/(?[^/]+)/screenshot$#D';
+ const BADGE_REGEXP = '#^/direct_v2/visual_action_badge/(?[^/]+)$#D';
+
+ /** {@inheritdoc} */
+ public function handleMessage(
+ Message $message)
+ {
+ $data = $message->getData();
+
+ if (isset($data['event'])) {
+ $this->_processEvent($data);
+ } elseif (isset($data['action'])) {
+ $this->_processAction($data);
+ } else {
+ throw new HandlerException('Invalid message (both event and action are missing).');
+ }
+ }
+
+ /**
+ * Process incoming event.
+ *
+ * @param array $message
+ *
+ * @throws HandlerException
+ */
+ protected function _processEvent(
+ array $message)
+ {
+ if ($message['event'] === RealtimeEvent::PATCH) {
+ $event = new PatchEvent($message);
+ foreach ($event->getData() as $op) {
+ $this->_handlePatchOp($op);
+ }
+ } else {
+ throw new HandlerException(sprintf('Unknown event type "%s".', $message['event']));
+ }
+ }
+
+ /**
+ * Process incoming action.
+ *
+ * @param array $message
+ *
+ * @throws HandlerException
+ */
+ protected function _processAction(
+ array $message)
+ {
+ if ($message['action'] === RealtimeAction::ACK) {
+ $this->_target->emit('client-context-ack', [new AckAction($message)]);
+ } else {
+ throw new HandlerException(sprintf('Unknown action type "%s".', $message['action']));
+ }
+ }
+
+ /**
+ * Patch op handler.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handlePatchOp(
+ PatchEventOp $op)
+ {
+ switch ($op->getOp()) {
+ case PatchEventOp::ADD:
+ $this->_handleAdd($op);
+ break;
+ case PatchEventOp::REPLACE:
+ $this->_handleReplace($op);
+ break;
+ case PatchEventOp::REMOVE:
+ $this->_handleRemove($op);
+ break;
+ case PatchEventOp::NOTIFY:
+ $this->_handleNotify($op);
+ break;
+ default:
+ throw new HandlerException(sprintf('Unknown patch op "%s".', $op->getOp()));
+ }
+ }
+
+ /**
+ * Handler for the ADD op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleAdd(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2/threads')) {
+ if (strpos($path, 'activity_indicator_id') === false) {
+ $this->_upsertThreadItem($op, true);
+ } else {
+ $this->_updateThreadActivity($op);
+ }
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/inbox/threads')) {
+ $this->_upsertThread($op, true);
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/visual_threads')) {
+ $this->_updateDirectStory($op);
+ } else {
+ throw new HandlerException(sprintf('Unsupported ADD path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for the REPLACE op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleReplace(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2/threads')) {
+ if ($this->_pathEndsWith($path, 'has_seen')) {
+ $this->_updateSeen($op);
+ } else {
+ $this->_upsertThreadItem($op, false);
+ }
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/inbox/threads')) {
+ $this->_upsertThread($op, false);
+ } elseif ($this->_pathEndsWith($path, 'unseen_count')) {
+ if ($this->_pathStartsWith($path, '/direct_v2/inbox')) {
+ $this->_updateUnseenCount('inbox', $op);
+ } else {
+ $this->_updateUnseenCount('visual_inbox', $op);
+ }
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/visual_action_badge')) {
+ $this->_directStoryAction($op);
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/visual_thread')) {
+ if ($this->_pathEndsWith($path, 'screenshot')) {
+ $this->_notifyDirectStoryScreenshot($op);
+ } else {
+ $this->_createDirectStory($op);
+ }
+ } else {
+ throw new HandlerException(sprintf('Unsupported REPLACE path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for the REMOVE op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleRemove(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2')) {
+ $this->_removeThreadItem($op);
+ } else {
+ throw new HandlerException(sprintf('Unsupported REMOVE path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for NOTIFY op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleNotify(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2/threads')) {
+ $this->_notifyThread($op);
+ } else {
+ throw new HandlerException(sprintf('Unsupported NOTIFY path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for thread creation/modification.
+ *
+ * @param PatchEventOp $op
+ * @param bool $insert
+ *
+ * @throws HandlerException
+ */
+ protected function _upsertThread(
+ PatchEventOp $op,
+ $insert)
+ {
+ $event = $insert ? 'thread-created' : 'thread-updated';
+ if (!$this->_hasListeners($event)) {
+ return;
+ }
+
+ if (!preg_match(self::THREAD_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit($event, [$matches['thread_id'], new DirectThread($json)]);
+ }
+
+ /**
+ * Handler for thread item creation/modification.
+ *
+ * @param PatchEventOp $op
+ * @param bool $insert
+ *
+ * @throws HandlerException
+ */
+ protected function _upsertThreadItem(
+ PatchEventOp $op,
+ $insert)
+ {
+ $event = $insert ? 'thread-item-created' : 'thread-item-updated';
+ if (!$this->_hasListeners($event)) {
+ return;
+ }
+
+ if (!preg_match(self::ITEM_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread item regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread item JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit($event, [$matches['thread_id'], $matches['item_id'], new DirectThreadItem($json)]);
+ }
+
+ /**
+ * Handler for thread activity indicator.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _updateThreadActivity(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-activity')) {
+ return;
+ }
+
+ if (!preg_match(self::ACTIVITY_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread activity regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread activity JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit('thread-activity', [$matches['thread_id'], new ThreadActivity($json)]);
+ }
+
+ /**
+ * Handler for story update.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _updateDirectStory(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-updated')) {
+ return;
+ }
+
+ if (!preg_match(self::STORY_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match story item regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode story item JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'direct-story-updated',
+ [$matches['thread_id'], $matches['item_id'], new DirectThreadItem($json)]
+ );
+ }
+
+ /**
+ * Handler for unseen count.
+ *
+ * @param string $inbox
+ * @param PatchEventOp $op
+ */
+ protected function _updateUnseenCount(
+ $inbox,
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('unseen-count-update')) {
+ return;
+ }
+
+ $payload = new DirectSeenItemPayload([
+ 'count' => (int) $op->getValue(),
+ 'timestamp' => $op->getTs(),
+ ]);
+ $this->_target->emit('unseen-count-update', [$inbox, $payload]);
+ }
+
+ /**
+ * Handler for thread seen indicator.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _updateSeen(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-seen')) {
+ return;
+ }
+
+ if (!preg_match(self::SEEN_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread seen regexp.', $op->getPath()));
+ }
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread seen JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'thread-seen',
+ [$matches['thread_id'], $matches['user_id'], new DirectThreadLastSeenAt($json)]
+ );
+ }
+
+ /**
+ * Handler for screenshot notification.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _notifyDirectStoryScreenshot(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-screenshot')) {
+ return;
+ }
+
+ if (!preg_match(self::SCREENSHOT_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread screenshot regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit('direct-story-screenshot', [$matches['thread_id'], new StoryScreenshot($json)]);
+ }
+
+ /**
+ * Handler for direct story creation.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _createDirectStory(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-created')) {
+ return;
+ }
+
+ if ($op->getPath() !== '/direct_v2/visual_thread/create') {
+ throw new HandlerException(sprintf('Path "%s" does not match story create path.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode inbox JSON: %s.', json_last_error_msg()));
+ }
+
+ $inbox = new DirectInbox($json);
+ $allThreads = $inbox->getThreads();
+ if ($allThreads === null || !count($allThreads)) {
+ return;
+ }
+ $this->_target->emit('direct-story-created', [reset($allThreads)]);
+ }
+
+ /**
+ * Handler for story action.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _directStoryAction(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-action')) {
+ return;
+ }
+
+ if (!preg_match(self::BADGE_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match story action regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode story action JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'direct-story-action',
+ [$matches['thread_id'], new ActionBadge($json)]
+ );
+ }
+
+ /**
+ * Handler for thread item removal.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _removeThreadItem(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-item-removed')) {
+ return;
+ }
+
+ if (!preg_match(self::ITEM_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread item regexp.', $op->getPath()));
+ }
+
+ $this->_target->emit('thread-item-removed', [$matches['thread_id'], $matches['item_id']]);
+ }
+
+ /**
+ * Handler for thread notify.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _notifyThread(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-notify')) {
+ return;
+ }
+
+ if (!preg_match(self::ITEM_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread item regexp.', $op->getPath()));
+ }
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread item notify JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'thread-notify',
+ [$matches['thread_id'], $matches['item_id'], new ThreadAction($json)]
+ );
+ }
+
+ /**
+ * Checks if the path starts with specified substring.
+ *
+ * @param string $path
+ * @param string $string
+ *
+ * @return bool
+ */
+ protected function _pathStartsWith(
+ $path,
+ $string)
+ {
+ return strncmp($path, $string, strlen($string)) === 0;
+ }
+
+ /**
+ * Checks if the path ends with specified substring.
+ *
+ * @param string $path
+ * @param string $string
+ *
+ * @return bool
+ */
+ protected function _pathEndsWith(
+ $path,
+ $string)
+ {
+ $length = strlen($string);
+
+ return substr_compare($path, $string, strlen($path) - $length, $length) === 0;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/HandlerException.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/HandlerException.php
new file mode 100755
index 0000000..66819eb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/HandlerException.php
@@ -0,0 +1,7 @@
+getData());
+ if (!$iris->isSucceeded()) {
+ throw new HandlerException(sprintf(
+ 'Failed to subscribe to Iris (%d): %s.',
+ $iris->getErrorType(),
+ $iris->getErrorMessage()
+ ));
+ }
+ $this->_target->emit('iris-subscribed', [$iris]);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/LiveHandler.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/LiveHandler.php
new file mode 100755
index 0000000..aa597bb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/LiveHandler.php
@@ -0,0 +1,81 @@
+getData();
+
+ if (isset($data['event'])) {
+ $this->_processEvent($data);
+ } else {
+ throw new HandlerException('Invalid message (event type is missing).');
+ }
+ }
+
+ /**
+ * Process incoming event.
+ *
+ * @param array $message
+ *
+ * @throws HandlerException
+ */
+ protected function _processEvent(
+ array $message)
+ {
+ if ($message['event'] === RealtimeEvent::PATCH) {
+ $event = new PatchEvent($message);
+ foreach ($event->getData() as $op) {
+ $this->_handlePatchOp($op);
+ }
+ } else {
+ throw new HandlerException(sprintf('Unknown event type "%s".', $message['event']));
+ }
+ }
+
+ /**
+ * Handler for live broadcast creation/removal.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handlePatchOp(
+ PatchEventOp $op)
+ {
+ switch ($op->getOp()) {
+ case PatchEventOp::ADD:
+ $event = 'live-started';
+ break;
+ case PatchEventOp::REMOVE:
+ $event = 'live-stopped';
+ break;
+ default:
+ throw new HandlerException(sprintf('Unsupported live broadcast op: "%s".', $op->getOp()));
+ }
+ if (!$this->_hasListeners($event)) {
+ return;
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode live broadcast JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit($event, [new LiveBroadcast($json)]);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/PresenceHandler.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/PresenceHandler.php
new file mode 100755
index 0000000..d0925df
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/PresenceHandler.php
@@ -0,0 +1,25 @@
+getData();
+ if (!isset($data['presence_event']) || !is_array($data['presence_event'])) {
+ throw new HandlerException('Invalid presence (event data is missing).');
+ }
+ $presence = new UserPresence($data['presence_event']);
+ $this->_target->emit('presence', [$presence]);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/RegionHintHandler.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/RegionHintHandler.php
new file mode 100755
index 0000000..4c529a2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/RegionHintHandler.php
@@ -0,0 +1,22 @@
+getData();
+ if ($region === null || $region === '') {
+ throw new HandlerException('Invalid region hint.');
+ }
+ $this->_target->emit('region-hint', [$message->getData()]);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/ZeroProvisionHandler.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/ZeroProvisionHandler.php
new file mode 100755
index 0000000..009bd59
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Handler/ZeroProvisionHandler.php
@@ -0,0 +1,24 @@
+getData();
+ if (!isset($data['zero_product_provisioning_event']) || !is_array($data['zero_product_provisioning_event'])) {
+ throw new HandlerException('Invalid zero provision (event data is missing).');
+ }
+ $provision = new ZeroProvisionEvent($data['zero_product_provisioning_event']);
+ $this->_target->emit('zero-provision', [$provision]);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/HandlerInterface.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/HandlerInterface.php
new file mode 100755
index 0000000..d7cd9b9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/HandlerInterface.php
@@ -0,0 +1,18 @@
+_module = $module;
+ $this->_data = $payload;
+ }
+
+ /**
+ * @return string
+ */
+ public function getModule()
+ {
+ return $this->_module;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getData()
+ {
+ return $this->_data;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt.php
new file mode 100755
index 0000000..1a29a3e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt.php
@@ -0,0 +1,803 @@
+_target = $target;
+ $this->_connector = $connector;
+ $this->_auth = $auth;
+ $this->_device = $device;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+ $this->_additionalOptions = $additionalOptions;
+
+ $this->_subscriptions = [];
+
+ $this->_loadExperiments($experiments);
+ $this->_initSubscriptions();
+
+ $this->_shutdown = false;
+ $this->_client = $this->_getClient();
+
+ $this->_parsers = [
+ Mqtt\Topics::PUBSUB => new Parser\SkywalkerParser(),
+ Mqtt\Topics::SEND_MESSAGE_RESPONSE => new Parser\JsonParser(Handler\DirectHandler::MODULE),
+ Mqtt\Topics::IRIS_SUB_RESPONSE => new Parser\JsonParser(Handler\IrisHandler::MODULE),
+ Mqtt\Topics::MESSAGE_SYNC => new Parser\IrisParser(),
+ Mqtt\Topics::REALTIME_SUB => new Parser\GraphQlParser(),
+ Mqtt\Topics::GRAPHQL => new Parser\GraphQlParser(),
+ Mqtt\Topics::REGION_HINT => new Parser\RegionHintParser(),
+ ];
+ $this->_handlers = [
+ Handler\DirectHandler::MODULE => new Handler\DirectHandler($this->_target),
+ Handler\LiveHandler::MODULE => new Handler\LiveHandler($this->_target),
+ Handler\IrisHandler::MODULE => new Handler\IrisHandler($this->_target),
+ Handler\PresenceHandler::MODULE => new Handler\PresenceHandler($this->_target),
+ Handler\RegionHintHandler::MODULE => new Handler\RegionHintHandler($this->_target),
+ Handler\ZeroProvisionHandler::MODULE => new Handler\ZeroProvisionHandler($this->_target),
+ ];
+ }
+
+ /** {@inheritdoc} */
+ public function getLoop()
+ {
+ return $this->_loop;
+ }
+
+ /** {@inheritdoc} */
+ public function isActive()
+ {
+ return !$this->_shutdown;
+ }
+
+ /**
+ * Add a subscription to the list.
+ *
+ * @param SubscriptionInterface $subscription
+ */
+ public function addSubscription(
+ SubscriptionInterface $subscription)
+ {
+ $this->_doAddSubscription($subscription, true);
+ }
+
+ /**
+ * Remove a subscription from the list.
+ *
+ * @param SubscriptionInterface $subscription
+ */
+ public function removeSubscription(
+ SubscriptionInterface $subscription)
+ {
+ $this->_doRemoveSubscription($subscription, true);
+ }
+
+ /**
+ * Set an additional option.
+ *
+ * @param string $option
+ * @param mixed $value
+ */
+ public function setAdditionalOption(
+ $option,
+ $value)
+ {
+ $this->_additionalOptions[$option] = $value;
+ }
+
+ /**
+ * Add a subscription to the list and send a command (optional).
+ *
+ * @param SubscriptionInterface $subscription
+ * @param bool $sendCommand
+ */
+ protected function _doAddSubscription(
+ SubscriptionInterface $subscription,
+ $sendCommand)
+ {
+ $topic = $subscription->getTopic();
+ $id = $subscription->getId();
+
+ // Check whether we already subscribed to it.
+ if (isset($this->_subscriptions[$topic][$id])) {
+ return;
+ }
+
+ // Add the subscription to the list.
+ if (!isset($this->_subscriptions[$topic])) {
+ $this->_subscriptions[$topic] = [];
+ }
+ $this->_subscriptions[$topic][$id] = $subscription;
+
+ // Send a command when needed.
+ if (!$sendCommand || $this->_isConnected()) {
+ return;
+ }
+
+ $this->_updateSubscriptions($topic, [$subscription], []);
+ }
+
+ /**
+ * Remove a subscription from the list and send a command (optional).
+ *
+ * @param SubscriptionInterface $subscription
+ * @param bool $sendCommand
+ */
+ protected function _doRemoveSubscription(
+ SubscriptionInterface $subscription,
+ $sendCommand)
+ {
+ $topic = $subscription->getTopic();
+ $id = $subscription->getId();
+
+ // Check whether we are subscribed to it.
+ if (!isset($this->_subscriptions[$topic][$id])) {
+ return;
+ }
+
+ // Remove the subscription from the list.
+ unset($this->_subscriptions[$topic][$id]);
+ if (!count($this->_subscriptions[$topic])) {
+ unset($this->_subscriptions[$topic]);
+ }
+
+ // Send a command when needed.
+ if (!$sendCommand || $this->_isConnected()) {
+ return;
+ }
+
+ $this->_updateSubscriptions($topic, [], [$subscription]);
+ }
+
+ /**
+ * Cancel a keepalive timer (if any).
+ */
+ protected function _cancelKeepaliveTimer()
+ {
+ if ($this->_keepaliveTimer !== null) {
+ if ($this->_keepaliveTimer->isActive()) {
+ $this->_logger->debug('Existing keepalive timer has been canceled.');
+ $this->_keepaliveTimer->cancel();
+ }
+ $this->_keepaliveTimer = null;
+ }
+ }
+
+ /**
+ * Set up a new keepalive timer.
+ */
+ protected function _setKeepaliveTimer()
+ {
+ $this->_cancelKeepaliveTimer();
+ $keepaliveInterval = Mqtt\Config::MQTT_KEEPALIVE;
+ $this->_logger->debug(sprintf('Setting up keepalive timer to %d seconds', $keepaliveInterval));
+ $this->_keepaliveTimer = $this->_loop->addTimer($keepaliveInterval, function () {
+ $this->_logger->info('Keepalive timer has been fired.');
+ $this->_disconnect();
+ });
+ }
+
+ /**
+ * Try to establish a connection.
+ */
+ protected function _connect()
+ {
+ $this->_setReconnectTimer(function () {
+ $this->_logger->info(sprintf('Connecting to %s:%d...', Mqtt\Config::DEFAULT_HOST, Mqtt\Config::DEFAULT_PORT));
+
+ $connection = new DefaultConnection(
+ $this->_getMqttUsername(),
+ $this->_auth->getPassword(),
+ null,
+ $this->_auth->getClientId(),
+ Mqtt\Config::MQTT_KEEPALIVE,
+ Mqtt\Config::MQTT_VERSION,
+ true
+ );
+
+ return $this->_client->connect(Mqtt\Config::DEFAULT_HOST, Mqtt\Config::DEFAULT_PORT, $connection, Mqtt\Config::CONNECTION_TIMEOUT);
+ });
+ }
+
+ /**
+ * Perform first connection in a row.
+ */
+ public function start()
+ {
+ $this->_shutdown = false;
+ $this->_reconnectInterval = 0;
+ $this->_connect();
+ }
+
+ /**
+ * Whether connection is established.
+ *
+ * @return bool
+ */
+ protected function _isConnected()
+ {
+ return $this->_client->isConnected();
+ }
+
+ /**
+ * Disconnect from server.
+ */
+ protected function _disconnect()
+ {
+ $this->_cancelKeepaliveTimer();
+ $this->_client->disconnect();
+ }
+
+ /**
+ * Proxy for _disconnect().
+ */
+ public function stop()
+ {
+ $this->_logger->info('Shutting down...');
+ $this->_shutdown = true;
+ $this->_cancelReconnectTimer();
+ $this->_disconnect();
+ }
+
+ /**
+ * Send the command.
+ *
+ * @param CommandInterface $command
+ *
+ * @throws \LogicException
+ */
+ public function sendCommand(
+ CommandInterface $command)
+ {
+ if (!$this->_isConnected()) {
+ throw new \LogicException('Tried to send the command while offline.');
+ }
+
+ $this->_publish(
+ $command->getTopic(),
+ json_encode($command, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
+ $command->getQosLevel()
+ );
+ }
+
+ /**
+ * Load the experiments.
+ *
+ * @param ExperimentsInterface $experiments
+ */
+ protected function _loadExperiments(
+ ExperimentsInterface $experiments)
+ {
+ $this->_experiments = $experiments;
+
+ // Direct features.
+ $this->_irisEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_realtime_iris',
+ 'is_direct_over_iris_enabled'
+ );
+ $this->_msgTypeBlacklist = $experiments->getExperimentParam(
+ 'ig_android_realtime_iris',
+ 'pubsub_msg_type_blacklist',
+ 'null'
+ );
+
+ // Live features.
+ $this->_mqttLiveEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_skywalker_live_event_start_end',
+ 'is_enabled'
+ );
+
+ // GraphQL features.
+ $this->_graphQlTypingEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_gqls_typing_indicator',
+ 'is_enabled'
+ );
+
+ // Presence features.
+ $this->_inboxPresenceEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_direct_inbox_presence',
+ 'is_enabled'
+ );
+ $this->_threadPresenceEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_direct_thread_presence',
+ 'is_enabled'
+ );
+ }
+
+ protected function _initSubscriptions()
+ {
+ $subscriptionId = Signatures::generateUUID();
+ // Set up PubSub topics.
+ $liveSubscription = new LiveSubscription($this->_auth->getUserId());
+ if ($this->_mqttLiveEnabled) {
+ $this->_doAddSubscription($liveSubscription, false);
+ } else {
+ $this->_doRemoveSubscription($liveSubscription, false);
+ }
+ // Direct subscription is always enabled.
+ $this->_doAddSubscription(new DirectSubscription($this->_auth->getUserId()), false);
+
+ // Set up GraphQL topics.
+ $zeroProvisionSubscription = new ZeroProvisionSubscription($this->_auth->getDeviceId());
+ $this->_doAddSubscription($zeroProvisionSubscription, false);
+ $graphQlTypingSubscription = new DirectTypingSubscription($this->_auth->getUserId());
+ if ($this->_graphQlTypingEnabled) {
+ $this->_doAddSubscription($graphQlTypingSubscription, false);
+ } else {
+ $this->_doRemoveSubscription($graphQlTypingSubscription, false);
+ }
+ $appPresenceSubscription = new AppPresenceSubscription($subscriptionId);
+ if (($this->_inboxPresenceEnabled || $this->_threadPresenceEnabled) && empty($this->_additionalOptions['disable_presence'])) {
+ $this->_doAddSubscription($appPresenceSubscription, false);
+ } else {
+ $this->_doRemoveSubscription($appPresenceSubscription, false);
+ }
+ }
+
+ /**
+ * Returns application specific info.
+ *
+ * @return array
+ */
+ protected function _getAppSpecificInfo()
+ {
+ $result = [
+ 'platform' => Constants::PLATFORM,
+ 'app_version' => Constants::IG_VERSION,
+ 'capabilities' => Constants::X_IG_Capabilities,
+ ];
+ // PubSub message type blacklist.
+ $msgTypeBlacklist = '';
+ if ($this->_msgTypeBlacklist !== null && $this->_msgTypeBlacklist !== '') {
+ $msgTypeBlacklist = $this->_msgTypeBlacklist;
+ }
+ if ($this->_graphQlTypingEnabled) {
+ if ($msgTypeBlacklist !== '') {
+ $msgTypeBlacklist .= ', typing_type';
+ } else {
+ $msgTypeBlacklist = 'typing_type';
+ }
+ }
+ if ($msgTypeBlacklist !== '') {
+ $result['pubsub_msg_type_blacklist'] = $msgTypeBlacklist;
+ }
+ // Everclear subscriptions.
+ $everclearSubscriptions = [];
+ if ($this->_inboxPresenceEnabled) {
+ $everclearSubscriptions[AppPresenceSubscription::ID] = AppPresenceSubscription::QUERY;
+ }
+ if (count($everclearSubscriptions)) {
+ $result['everclear_subscriptions'] = json_encode($everclearSubscriptions);
+ }
+ // Maintaining the order.
+ $result['User-Agent'] = $this->_device->getUserAgent();
+ $result['ig_mqtt_route'] = 'django';
+ // Accept-Language must be the last one.
+ $result['Accept-Language'] = Constants::ACCEPT_LANGUAGE;
+
+ return $result;
+ }
+
+ /**
+ * Get a list of topics to subscribe at connect.
+ *
+ * @return string[]
+ */
+ protected function _getSubscribeTopics()
+ {
+ $topics = [
+ Mqtt\Topics::PUBSUB,
+ ];
+ $topics[] = Mqtt\Topics::REGION_HINT;
+ if ($this->_graphQlTypingEnabled) {
+ $topics[] = Mqtt\Topics::REALTIME_SUB;
+ }
+ $topics[] = Mqtt\Topics::SEND_MESSAGE_RESPONSE;
+ if ($this->_irisEnabled) {
+ $topics[] = Mqtt\Topics::IRIS_SUB_RESPONSE;
+ $topics[] = Mqtt\Topics::MESSAGE_SYNC;
+ }
+
+ return $topics;
+ }
+
+ /**
+ * Returns username for MQTT connection.
+ *
+ * @return string
+ */
+ protected function _getMqttUsername()
+ {
+ // Session ID is uptime in msec.
+ $sessionId = (microtime(true) - strtotime('Last Monday')) * 1000;
+ // Random buster-string to avoid clashing with other data.
+ $randNum = mt_rand(1000000, 9999999);
+ $fields = [
+ // USER_ID
+ 'u' => '%ACCOUNT_ID_'.$randNum.'%',
+ // AGENT
+ 'a' => $this->_device->getFbUserAgent(Constants::INSTAGRAM_APPLICATION_NAME),
+ // CAPABILITIES
+ 'cp' => Mqtt\Capabilities::DEFAULT_SET,
+ // CLIENT_MQTT_SESSION_ID
+ 'mqtt_sid' => '%SESSION_ID_'.$randNum.'%',
+ // NETWORK_TYPE
+ 'nwt' => Mqtt\Config::NETWORK_TYPE_WIFI,
+ // NETWORK_SUBTYPE
+ 'nwst' => 0,
+ // MAKE_USER_AVAILABLE_IN_FOREGROUND
+ 'chat_on' => false,
+ // NO_AUTOMATIC_FOREGROUND
+ 'no_auto_fg' => true,
+ // DEVICE_ID
+ 'd' => $this->_auth->getDeviceId(),
+ // DEVICE_SECRET
+ 'ds' => $this->_auth->getDeviceSecret(),
+ // INITIAL_FOREGROUND_STATE
+ 'fg' => false,
+ // ENDPOINT_CAPABILITIES
+ 'ecp' => 0,
+ // PUBLISH_FORMAT
+ 'pf' => Mqtt\Config::PUBLISH_FORMAT,
+ // CLIENT_TYPE
+ 'ct' => Mqtt\Config::CLIENT_TYPE,
+ // APP_ID
+ 'aid' => Constants::FACEBOOK_ANALYTICS_APPLICATION_ID,
+ // SUBSCRIBE_TOPICS
+ 'st' => $this->_getSubscribeTopics(),
+ ];
+ // REGION_PREFERENCE
+ if (!empty($this->_additionalOptions['datacenter'])) {
+ $fields['dc'] = $this->_additionalOptions['datacenter'];
+ }
+ // Maintaining the order.
+ $fields['clientStack'] = 3;
+ $fields['app_specific_info'] = $this->_getAppSpecificInfo();
+ // Account and session IDs must be a number, but int size is platform dependent in PHP,
+ // so we are replacing JSON strings with plain strings in JSON encoded data.
+ $result = strtr(json_encode($fields), [
+ json_encode('%ACCOUNT_ID_'.$randNum.'%') => $this->_auth->getUserId(),
+ json_encode('%SESSION_ID_'.$randNum.'%') => round($sessionId),
+ ]);
+
+ return $result;
+ }
+
+ /**
+ * Create a new MQTT client.
+ *
+ * @return ReactMqttClient
+ */
+ protected function _getClient()
+ {
+ $client = new ReactMqttClient($this->_connector, $this->_loop, null, new Mqtt\StreamParser());
+
+ $client->on('error', function (\Exception $e) {
+ $this->_logger->error($e->getMessage());
+ });
+ $client->on('warning', function (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+ });
+ $client->on('open', function () {
+ $this->_logger->info('Connection has been established');
+ });
+ $client->on('close', function () {
+ $this->_logger->info('Connection has been closed');
+ $this->_cancelKeepaliveTimer();
+ if (!$this->_reconnectInterval) {
+ $this->_connect();
+ }
+ });
+ $client->on('connect', function () {
+ $this->_logger->info('Connected to a broker');
+ $this->_setKeepaliveTimer();
+ $this->_restoreAllSubscriptions();
+ });
+ $client->on('ping', function () {
+ $this->_logger->debug('Ping flow completed');
+ $this->_setKeepaliveTimer();
+ });
+ $client->on('publish', function () {
+ $this->_logger->debug('Publish flow completed');
+ $this->_setKeepaliveTimer();
+ });
+ $client->on('message', function (MqttMessage $message) {
+ $this->_setKeepaliveTimer();
+ $this->_onReceive($message);
+ });
+ $client->on('disconnect', function () {
+ $this->_logger->info('Disconnected from broker');
+ });
+
+ return $client;
+ }
+
+ /**
+ * Mass update subscriptions statuses.
+ *
+ * @param string $topic
+ * @param SubscriptionInterface[] $subscribe
+ * @param SubscriptionInterface[] $unsubscribe
+ */
+ protected function _updateSubscriptions(
+ $topic,
+ array $subscribe,
+ array $unsubscribe)
+ {
+ if (count($subscribe)) {
+ $this->_logger->info(sprintf('Subscribing to %s topics %s', $topic, implode(', ', $subscribe)));
+ }
+ if (count($unsubscribe)) {
+ $this->_logger->info(sprintf('Unsubscribing from %s topics %s', $topic, implode(', ', $subscribe)));
+ }
+
+ try {
+ $this->sendCommand(new UpdateSubscriptions($topic, $subscribe, $unsubscribe));
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+ }
+ }
+
+ /**
+ * Subscribe to all topics.
+ */
+ protected function _restoreAllSubscriptions()
+ {
+ foreach ($this->_subscriptions as $topic => $subscriptions) {
+ $this->_updateSubscriptions($topic, $subscriptions, []);
+ }
+ }
+
+ /**
+ * Unsubscribe from all topics.
+ */
+ protected function _removeAllSubscriptions()
+ {
+ foreach ($this->_subscriptions as $topic => $subscriptions) {
+ $this->_updateSubscriptions($topic, [], $subscriptions);
+ }
+ }
+
+ /**
+ * Maps human readable topic to its identifier.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ protected function _mapTopic(
+ $topic)
+ {
+ if (array_key_exists($topic, Mqtt\Topics::TOPIC_TO_ID_MAP)) {
+ $result = Mqtt\Topics::TOPIC_TO_ID_MAP[$topic];
+ $this->_logger->debug(sprintf('Topic "%s" has been mapped to "%s"', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->_logger->warning(sprintf('Topic "%s" does not exist in the enum', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Maps topic ID to human readable name.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ protected function _unmapTopic(
+ $topic)
+ {
+ if (array_key_exists($topic, Mqtt\Topics::ID_TO_TOPIC_MAP)) {
+ $result = Mqtt\Topics::ID_TO_TOPIC_MAP[$topic];
+ $this->_logger->debug(sprintf('Topic ID "%s" has been unmapped to "%s"', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->_logger->warning(sprintf('Topic ID "%s" does not exist in the enum', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $topic
+ * @param string $payload
+ * @param int $qosLevel
+ */
+ protected function _publish(
+ $topic,
+ $payload,
+ $qosLevel)
+ {
+ $this->_logger->info(sprintf('Sending message "%s" to topic "%s"', $payload, $topic));
+ $payload = zlib_encode($payload, ZLIB_ENCODING_DEFLATE, 9);
+ // We need to map human readable topic name to its ID because of bandwidth saving.
+ $topic = $this->_mapTopic($topic);
+ $this->_client->publish(new DefaultMessage($topic, $payload, $qosLevel));
+ }
+
+ /**
+ * Incoming message handler.
+ *
+ * @param MqttMessage $msg
+ */
+ protected function _onReceive(
+ MqttMessage $msg)
+ {
+ $payload = @zlib_decode($msg->getPayload());
+ if ($payload === false) {
+ $this->_logger->warning('Failed to inflate the payload');
+
+ return;
+ }
+ $this->_handleMessage($this->_unmapTopic($msg->getTopic()), $payload);
+ }
+
+ /**
+ * @param string $topic
+ * @param string $payload
+ */
+ protected function _handleMessage(
+ $topic,
+ $payload)
+ {
+ $this->_logger->debug(
+ sprintf('Received a message from topic "%s"', $topic),
+ [base64_encode($payload)]
+ );
+ if (!isset($this->_parsers[$topic])) {
+ $this->_logger->warning(
+ sprintf('No parser for topic "%s" found, skipping the message(s)', $topic),
+ [base64_encode($payload)]
+ );
+
+ return;
+ }
+
+ try {
+ $messages = $this->_parsers[$topic]->parseMessage($topic, $payload);
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage(), [$topic, base64_encode($payload)]);
+
+ return;
+ }
+
+ foreach ($messages as $message) {
+ $module = $message->getModule();
+ if (!isset($this->_handlers[$module])) {
+ $this->_logger->warning(
+ sprintf('No handler for module "%s" found, skipping the message', $module),
+ [$message->getData()]
+ );
+
+ continue;
+ }
+
+ $this->_logger->info(
+ sprintf('Processing a message for module "%s"', $module),
+ [$message->getData()]
+ );
+
+ try {
+ $this->_handlers[$module]->handleMessage($message);
+ } catch (Handler\HandlerException $e) {
+ $this->_logger->warning($e->getMessage(), [$message->getData()]);
+ } catch (\Exception $e) {
+ $this->_target->emit('warning', [$e]);
+ }
+ }
+ }
+
+ /** {@inheritdoc} */
+ public function getLogger()
+ {
+ return $this->_logger;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Auth.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Auth.php
new file mode 100755
index 0000000..5f149b6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Auth.php
@@ -0,0 +1,74 @@
+_instagram = $instagram;
+ }
+
+ /** {@inheritdoc} */
+ public function getClientId()
+ {
+ return substr($this->getDeviceId(), 0, 20);
+ }
+
+ /** {@inheritdoc} */
+ public function getClientType()
+ {
+ return self::AUTH_TYPE;
+ }
+
+ /** {@inheritdoc} */
+ public function getUserId()
+ {
+ return $this->_instagram->account_id;
+ }
+
+ /** {@inheritdoc} */
+ public function getPassword()
+ {
+ $cookie = $this->_instagram->client->getCookie('sessionid', 'i.instagram.com');
+ if ($cookie !== null) {
+ return sprintf('%s=%s', $cookie->getName(), $cookie->getValue());
+ }
+
+ throw new \RuntimeException('No session cookie was found.');
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceId()
+ {
+ return $this->_instagram->uuid;
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceSecret()
+ {
+ return '';
+ }
+
+ /** {@inheritdoc} */
+ public function __toString()
+ {
+ return '';
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Capabilities.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Capabilities.php
new file mode 100755
index 0000000..9519c03
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Capabilities.php
@@ -0,0 +1,33 @@
+ ConnectRequestPacket::class,
+ Packet::TYPE_CONNACK => ConnectResponsePacket::class,
+ Packet::TYPE_PUBLISH => PublishRequestPacket::class,
+ Packet::TYPE_PUBACK => PublishAckPacket::class,
+ Packet::TYPE_PUBREC => PublishReceivedPacket::class,
+ Packet::TYPE_PUBREL => PublishReleasePacket::class,
+ Packet::TYPE_PUBCOMP => PublishCompletePacket::class,
+ Packet::TYPE_SUBSCRIBE => SubscribeRequestPacket::class,
+ Packet::TYPE_SUBACK => SubscribeResponsePacket::class,
+ Packet::TYPE_UNSUBSCRIBE => UnsubscribeRequestPacket::class,
+ Packet::TYPE_UNSUBACK => UnsubscribeResponsePacket::class,
+ Packet::TYPE_PINGREQ => PingRequestPacket::class,
+ Packet::TYPE_PINGRESP => PingResponsePacket::class,
+ Packet::TYPE_DISCONNECT => DisconnectRequestPacket::class,
+ ];
+
+ /**
+ * Builds a packet object for the given type.
+ *
+ * @param int $type
+ *
+ * @throws UnknownPacketTypeException
+ *
+ * @return Packet
+ */
+ public function build(
+ $type)
+ {
+ if (!isset(self::$_mapping[$type])) {
+ throw new UnknownPacketTypeException(sprintf('Unknown packet type %d.', $type));
+ }
+
+ $class = self::$_mapping[$type];
+
+ return new $class();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/QosLevel.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/QosLevel.php
new file mode 100755
index 0000000..3d98ae1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/QosLevel.php
@@ -0,0 +1,9 @@
+_buffer = new PacketStream();
+ $this->_factory = new PacketFactory();
+ }
+
+ /**
+ * Registers an error callback.
+ *
+ * @param callable $callback
+ */
+ public function onError(
+ $callback)
+ {
+ $this->_errorCallback = $callback;
+ }
+
+ /**
+ * Appends the given data to the internal buffer and parses it.
+ *
+ * @param string $data
+ *
+ * @return Packet[]
+ */
+ public function push(
+ $data)
+ {
+ $this->_buffer->write($data);
+
+ $result = [];
+ while ($this->_buffer->getRemainingBytes() > 0) {
+ $type = $this->_buffer->readByte() >> 4;
+
+ try {
+ $packet = $this->_factory->build($type);
+ } catch (UnknownPacketTypeException $e) {
+ $this->_handleError($e);
+ continue;
+ }
+
+ $this->_buffer->seek(-1);
+ $position = $this->_buffer->getPosition();
+
+ try {
+ $packet->read($this->_buffer);
+ $result[] = $packet;
+ $this->_buffer->cut();
+ } catch (EndOfStreamException $e) {
+ $this->_buffer->setPosition($position);
+ break;
+ } catch (MalformedPacketException $e) {
+ $this->_handleError($e);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the registered error callback.
+ *
+ * @param \Throwable $exception
+ */
+ private function _handleError(
+ $exception)
+ {
+ if ($this->_errorCallback !== null) {
+ $callback = $this->_errorCallback;
+ $callback($exception);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Topics.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Topics.php
new file mode 100755
index 0000000..3e8dbd4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Topics.php
@@ -0,0 +1,57 @@
+ self::PUBSUB,
+ self::SEND_MESSAGE_ID => self::SEND_MESSAGE,
+ self::SEND_MESSAGE_RESPONSE_ID => self::SEND_MESSAGE_RESPONSE,
+ self::IRIS_SUB_ID => self::IRIS_SUB,
+ self::IRIS_SUB_RESPONSE_ID => self::IRIS_SUB_RESPONSE,
+ self::MESSAGE_SYNC_ID => self::MESSAGE_SYNC,
+ self::REALTIME_SUB_ID => self::REALTIME_SUB,
+ self::GRAPHQL_ID => self::GRAPHQL,
+ self::REGION_HINT_ID => self::REGION_HINT,
+ ];
+
+ const TOPIC_TO_ID_MAP = [
+ self::PUBSUB => self::PUBSUB_ID,
+ self::SEND_MESSAGE => self::SEND_MESSAGE_ID,
+ self::SEND_MESSAGE_RESPONSE => self::SEND_MESSAGE_RESPONSE_ID,
+ self::IRIS_SUB => self::IRIS_SUB_ID,
+ self::IRIS_SUB_RESPONSE => self::IRIS_SUB_RESPONSE_ID,
+ self::MESSAGE_SYNC => self::MESSAGE_SYNC_ID,
+ self::REALTIME_SUB => self::REALTIME_SUB_ID,
+ self::GRAPHQL => self::GRAPHQL_ID,
+ self::REGION_HINT => self::REGION_HINT_ID,
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/GraphQlParser.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/GraphQlParser.php
new file mode 100755
index 0000000..f88e5ff
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/GraphQlParser.php
@@ -0,0 +1,82 @@
+ self::MODULE_DIRECT,
+ AppPresenceSubscription::QUERY => AppPresenceSubscription::ID,
+ ZeroProvisionSubscription::QUERY => ZeroProvisionSubscription::ID,
+ ];
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ */
+ public function parseMessage(
+ $topic,
+ $payload)
+ {
+ $msgTopic = $msgPayload = null;
+ new Reader($payload, function ($context, $field, $value, $type) use (&$msgTopic, &$msgPayload) {
+ if ($type === Compact::TYPE_BINARY) {
+ if ($field === self::FIELD_TOPIC) {
+ $msgTopic = $value;
+ } elseif ($field === self::FIELD_PAYLOAD) {
+ $msgPayload = $value;
+ }
+ }
+ });
+
+ return [$this->_createMessage($msgTopic, $msgPayload)];
+ }
+
+ /**
+ * Create a message from given topic and payload.
+ *
+ * @param string $topic
+ * @param string $payload
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ *
+ * @return Message
+ */
+ protected function _createMessage(
+ $topic,
+ $payload)
+ {
+ if ($topic === null || $payload === null) {
+ throw new \RuntimeException('Incomplete GraphQL message.');
+ }
+
+ if (!array_key_exists($topic, self::TOPIC_TO_MODULE_ENUM)) {
+ throw new \DomainException(sprintf('Unknown GraphQL topic "%s".', $topic));
+ }
+
+ $data = Client::api_body_decode($payload);
+ if (!is_array($data)) {
+ throw new \RuntimeException('Invalid GraphQL payload.');
+ }
+
+ return new Message(self::TOPIC_TO_MODULE_ENUM[$topic], $data);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/IrisParser.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/IrisParser.php
new file mode 100755
index 0000000..590da53
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/IrisParser.php
@@ -0,0 +1,34 @@
+_module = $module;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \RuntimeException
+ */
+ public function parseMessage(
+ $topic,
+ $payload)
+ {
+ $data = Client::api_body_decode($payload);
+ if (!is_array($data)) {
+ throw new \RuntimeException('Invalid JSON payload.');
+ }
+
+ return [new Message($this->_module, $data)];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/RegionHintParser.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/RegionHintParser.php
new file mode 100755
index 0000000..898f686
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/RegionHintParser.php
@@ -0,0 +1,54 @@
+_createMessage($region)];
+ }
+
+ /**
+ * Create a message from given topic and payload.
+ *
+ * @param string $region
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ *
+ * @return Message
+ */
+ protected function _createMessage(
+ $region)
+ {
+ if ($region === null) {
+ throw new \RuntimeException('Incomplete region hint message.');
+ }
+
+ return new Message(RegionHintHandler::MODULE, $region);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/SkywalkerParser.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/SkywalkerParser.php
new file mode 100755
index 0000000..0746531
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Parser/SkywalkerParser.php
@@ -0,0 +1,82 @@
+ self::MODULE_DIRECT,
+ self::TOPIC_LIVE => self::MODULE_LIVE,
+ self::TOPIC_LIVEWITH => self::MODULE_LIVEWITH,
+ ];
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ */
+ public function parseMessage(
+ $topic,
+ $payload)
+ {
+ $msgTopic = $msgPayload = null;
+ new Reader($payload, function ($context, $field, $value, $type) use (&$msgTopic, &$msgPayload) {
+ if ($type === Compact::TYPE_I32 && $field === self::FIELD_TOPIC) {
+ $msgTopic = $value;
+ } elseif ($type === Compact::TYPE_BINARY && $field === self::FIELD_PAYLOAD) {
+ $msgPayload = $value;
+ }
+ });
+
+ return [$this->_createMessage($msgTopic, $msgPayload)];
+ }
+
+ /**
+ * Create a message from given topic and payload.
+ *
+ * @param int $topic
+ * @param string $payload
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ *
+ * @return Message
+ */
+ protected function _createMessage(
+ $topic,
+ $payload)
+ {
+ if ($topic === null || $payload === null) {
+ throw new \RuntimeException('Incomplete Skywalker message.');
+ }
+
+ if (!array_key_exists($topic, self::TOPIC_TO_MODULE_ENUM)) {
+ throw new \DomainException(sprintf('Unknown Skywalker topic "%d".', $topic));
+ }
+
+ $data = Client::api_body_decode($payload);
+ if (!is_array($data)) {
+ throw new \RuntimeException('Invalid Skywalker payload.');
+ }
+
+ return new Message(self::TOPIC_TO_MODULE_ENUM[$topic], $data);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/ParserInterface.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/ParserInterface.php
new file mode 100755
index 0000000..f180332
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/ParserInterface.php
@@ -0,0 +1,19 @@
+ '',
+ 'payload' => '\InstagramAPI\Response\Model\DirectSendItemPayload',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEvent.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEvent.php
new file mode 100755
index 0000000..736355b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEvent.php
@@ -0,0 +1,44 @@
+ 'PatchEventOp[]',
+ 'message_type' => 'int',
+ 'seq_id' => 'int',
+ 'lazy' => 'bool',
+ 'num_endpoints' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEventOp.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEventOp.php
new file mode 100755
index 0000000..7b28155
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEventOp.php
@@ -0,0 +1,45 @@
+ '',
+ 'path' => '',
+ 'value' => '',
+ 'ts' => '',
+ 'doublePublish' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/IrisSubscribeAck.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/IrisSubscribeAck.php
new file mode 100755
index 0000000..3c2016b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/IrisSubscribeAck.php
@@ -0,0 +1,35 @@
+ 'int',
+ 'succeeded' => 'bool',
+ 'error_type' => 'int',
+ 'error_message' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/LiveBroadcast.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/LiveBroadcast.php
new file mode 100755
index 0000000..02d7e66
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/LiveBroadcast.php
@@ -0,0 +1,40 @@
+ '\InstagramAPI\Response\Model\User',
+ 'broadcast_id' => 'string',
+ 'is_periodic' => '',
+ 'broadcast_message' => 'string',
+ 'display_notification' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeAction.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeAction.php
new file mode 100755
index 0000000..77ccf8e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeAction.php
@@ -0,0 +1,29 @@
+ 'string',
+ 'action' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeEvent.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeEvent.php
new file mode 100755
index 0000000..c0861d4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeEvent.php
@@ -0,0 +1,27 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/StoryScreenshot.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/StoryScreenshot.php
new file mode 100755
index 0000000..4d49df3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/StoryScreenshot.php
@@ -0,0 +1,28 @@
+ '\InstagramAPI\Response\Model\User',
+ /*
+ * A number describing what type of media this is.
+ */
+ 'media_type' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadAction.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadAction.php
new file mode 100755
index 0000000..6520222
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadAction.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'action_log' => '\InstagramAPI\Response\Model\ActionLog',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadActivity.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadActivity.php
new file mode 100755
index 0000000..5f0eafa
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadActivity.php
@@ -0,0 +1,35 @@
+ '',
+ 'sender_id' => 'string',
+ 'activity_status' => '',
+ 'ttl' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/ZeroProvisionEvent.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/ZeroProvisionEvent.php
new file mode 100755
index 0000000..96ea4d5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Payload/ZeroProvisionEvent.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'product_name' => 'string',
+ 'zero_provisioned_time' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/AppPresenceSubscription.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/AppPresenceSubscription.php
new file mode 100755
index 0000000..c5bbc23
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/AppPresenceSubscription.php
@@ -0,0 +1,30 @@
+ $subscriptionId,
+ ]);
+ }
+
+ /** {@inheritdoc} */
+ public function getId()
+ {
+ return self::ID;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/DirectTypingSubscription.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/DirectTypingSubscription.php
new file mode 100755
index 0000000..f6ad123
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/DirectTypingSubscription.php
@@ -0,0 +1,29 @@
+ $accountId,
+ ]);
+ }
+
+ /** {@inheritdoc} */
+ public function getId()
+ {
+ return 'direct_typing';
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/ZeroProvisionSubscription.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/ZeroProvisionSubscription.php
new file mode 100755
index 0000000..3fa7ae5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/ZeroProvisionSubscription.php
@@ -0,0 +1,32 @@
+ Signatures::generateUUID(),
+ 'device_id' => $deviceId,
+ ]);
+ }
+
+ /** {@inheritdoc} */
+ public function getId()
+ {
+ return self::ID;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQlSubscription.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQlSubscription.php
new file mode 100755
index 0000000..46580c7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQlSubscription.php
@@ -0,0 +1,46 @@
+_queryId = $queryId;
+ $this->_inputData = $inputData;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::REALTIME_SUB;
+ }
+
+ /** {@inheritdoc} */
+ abstract public function getId();
+
+ /** {@inheritdoc} */
+ public function __toString()
+ {
+ return sprintf(self::TEMPLATE, $this->_queryId, json_encode(['input_data' => $this->_inputData]));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/DirectSubscription.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/DirectSubscription.php
new file mode 100755
index 0000000..99eb563
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/DirectSubscription.php
@@ -0,0 +1,23 @@
+_accountId);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/LiveSubscription.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/LiveSubscription.php
new file mode 100755
index 0000000..7b6fb95
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/LiveSubscription.php
@@ -0,0 +1,23 @@
+_accountId);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/SkywalkerSubscription.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/SkywalkerSubscription.php
new file mode 100755
index 0000000..8cffa52
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/Subscription/SkywalkerSubscription.php
@@ -0,0 +1,35 @@
+_accountId = $accountId;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::PUBSUB;
+ }
+
+ /** {@inheritdoc} */
+ abstract public function getId();
+
+ /** {@inheritdoc} */
+ abstract public function __toString();
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/SubscriptionInterface.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/SubscriptionInterface.php
new file mode 100755
index 0000000..bb90a6d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Realtime/SubscriptionInterface.php
@@ -0,0 +1,27 @@
+_parent = $parent;
+ $this->_url = $url;
+
+ // Set defaults.
+ $this->_apiVersion = 1;
+ $this->_headers = [];
+ $this->_params = [];
+ $this->_posts = [];
+ $this->_files = [];
+ $this->_handles = [];
+ $this->_guzzleOptions = [];
+ $this->_needsAuth = true;
+ $this->_signedPost = true;
+ $this->_signedGet = false;
+ $this->_isMultiResponse = false;
+ $this->_isBodyCompressed = false;
+ $this->_excludeSigned = [];
+ $this->_defaultHeaders = true;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ // Ensure that all opened handles are closed.
+ $this->_closeHandles();
+ }
+
+ /**
+ * Set API version to use.
+ *
+ * @param int $apiVersion
+ *
+ * @throws \InvalidArgumentException In case of unsupported API version.
+ *
+ * @return self
+ */
+ public function setVersion(
+ $apiVersion)
+ {
+ if (!array_key_exists($apiVersion, Constants::API_URLS)) {
+ throw new \InvalidArgumentException(sprintf('"%d" is not a supported API version.', $apiVersion));
+ }
+ $this->_apiVersion = $apiVersion;
+
+ return $this;
+ }
+
+ /**
+ * Add query param to request, overwriting any previous value.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addParam(
+ $key,
+ $value)
+ {
+ if ($value === true) {
+ $value = 'true';
+ } elseif ($value === false) {
+ $value = 'false';
+ }
+ $this->_params[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add POST param to request, overwriting any previous value.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addPost(
+ $key,
+ $value)
+ {
+ if ($value === true) {
+ $value = 'true';
+ } elseif ($value === false) {
+ $value = 'false';
+ }
+ $this->_posts[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add unsigned POST param to request, overwriting any previous value.
+ *
+ * This adds a POST value and marks it as "never sign it", even if this
+ * is a signed request. Instagram sometimes needs a few unsigned values.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addUnsignedPost(
+ $key,
+ $value)
+ {
+ $this->addPost($key, $value);
+ $this->_excludeSigned[] = $key;
+
+ return $this;
+ }
+
+ /**
+ * Add an on-disk file to a POST request, which causes this to become a multipart form request.
+ *
+ * @param string $key Form field name.
+ * @param string $filepath Path to a file.
+ * @param string|null $filename Filename to use in Content-Disposition header.
+ * @param array $headers An associative array of headers.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function addFile(
+ $key,
+ $filepath,
+ $filename = null,
+ array $headers = [])
+ {
+ // Validate
+ if (!is_file($filepath)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" does not exist.', $filepath));
+ }
+ if (!is_readable($filepath)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" is not readable.', $filepath));
+ }
+ // Inherit value from $filepath, if not supplied.
+ if ($filename === null) {
+ $filename = $filepath;
+ }
+ $filename = basename($filename);
+ // Default headers.
+ $headers = $headers + [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Transfer-Encoding' => 'binary',
+ ];
+ $this->_files[$key] = [
+ 'filepath' => $filepath,
+ 'filename' => $filename,
+ 'headers' => $headers,
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Add raw file data to a POST request, which causes this to become a multipart form request.
+ *
+ * @param string $key Form field name.
+ * @param string $data File data.
+ * @param string|null $filename Filename to use in Content-Disposition header.
+ * @param array $headers An associative array of headers.
+ *
+ * @return self
+ */
+ public function addFileData(
+ $key,
+ $data,
+ $filename,
+ array $headers = [])
+ {
+ $filename = basename($filename);
+ // Default headers.
+ $headers = $headers + [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Transfer-Encoding' => 'binary',
+ ];
+ $this->_files[$key] = [
+ 'contents' => $data,
+ 'filename' => $filename,
+ 'headers' => $headers,
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Add custom header to request, overwriting any previous or default value.
+ *
+ * The custom value will even take precedence over the default headers!
+ *
+ * WARNING: If this is called multiple times with the same header "key"
+ * name, it will only keep the LATEST value given for that specific header.
+ * It will NOT keep any of its older values, since you can only have ONE
+ * value per header! If you want multiple values in headers that support
+ * it, you must manually format them properly and send us the final string,
+ * usually by separating the value string entries with a semicolon.
+ *
+ * @param string $key
+ * @param string $value
+ *
+ * @return self
+ */
+ public function addHeader(
+ $key,
+ $value)
+ {
+ $this->_headers[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add headers used by most API requests.
+ *
+ * @return self
+ */
+ protected function _addDefaultHeaders()
+ {
+ if ($this->_defaultHeaders) {
+ $this->_headers['X-IG-App-ID'] = Constants::FACEBOOK_ANALYTICS_APPLICATION_ID;
+ $this->_headers['X-IG-Capabilities'] = Constants::X_IG_Capabilities;
+ $this->_headers['X-IG-Connection-Type'] = Constants::X_IG_Connection_Type;
+ $this->_headers['X-IG-Connection-Speed'] = mt_rand(1000, 3700).'kbps';
+ // TODO: IMPLEMENT PROPER CALCULATION OF THESE HEADERS.
+ $this->_headers['X-IG-Bandwidth-Speed-KBPS'] = '-1.000';
+ $this->_headers['X-IG-Bandwidth-TotalBytes-B'] = '0';
+ $this->_headers['X-IG-Bandwidth-TotalTime-MS'] = '0';
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the "add default headers" flag.
+ *
+ * @param bool $flag
+ *
+ * @return self
+ */
+ public function setAddDefaultHeaders(
+ $flag)
+ {
+ $this->_defaultHeaders = $flag;
+
+ return $this;
+ }
+
+ /**
+ * Set the extra Guzzle options for this request.
+ *
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ *
+ * @return self
+ */
+ public function setGuzzleOptions(
+ array $guzzleOptions)
+ {
+ $this->_guzzleOptions = $guzzleOptions;
+
+ return $this;
+ }
+
+ /**
+ * Set raw request body.
+ *
+ * @param StreamInterface $stream
+ *
+ * @return self
+ */
+ public function setBody(
+ StreamInterface $stream)
+ {
+ $this->_body = $stream;
+
+ return $this;
+ }
+
+ /**
+ * Set authorized request flag.
+ *
+ * @param bool $needsAuth
+ *
+ * @return self
+ */
+ public function setNeedsAuth(
+ $needsAuth)
+ {
+ $this->_needsAuth = $needsAuth;
+
+ return $this;
+ }
+
+ /**
+ * Set signed request data flag.
+ *
+ * @param bool $signedPost
+ *
+ * @return self
+ */
+ public function setSignedPost(
+ $signedPost = true)
+ {
+ $this->_signedPost = $signedPost;
+
+ return $this;
+ }
+
+ /**
+ * Set signed request params flag.
+ *
+ * @param bool $signedGet
+ *
+ * @return self
+ */
+ public function setSignedGet(
+ $signedGet = false)
+ {
+ $this->_signedGet = $signedGet;
+
+ return $this;
+ }
+
+ /**
+ * Set the "this API endpoint responds with multiple JSON objects" flag.
+ *
+ * @param bool $flag
+ *
+ * @return self
+ */
+ public function setIsMultiResponse(
+ $flag = false)
+ {
+ $this->_isMultiResponse = $flag;
+
+ return $this;
+ }
+
+ /**
+ * Set gz-compressed request params flag.
+ *
+ * @param bool $isBodyCompressed
+ *
+ * @return self
+ */
+ public function setIsBodyCompressed(
+ $isBodyCompressed = false)
+ {
+ $this->_isBodyCompressed = $isBodyCompressed;
+
+ if ($isBodyCompressed === true) {
+ $this->_headers['Content-Encoding'] = 'gzip';
+ } elseif (isset($this->_headers['Content-Encoding']) && $this->_headers['Content-Encoding'] === 'gzip') {
+ unset($this->_headers['Content-Encoding']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get a Stream for the given file.
+ *
+ * @param array $file
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return StreamInterface
+ */
+ protected function _getStreamForFile(
+ array $file)
+ {
+ if (isset($file['contents'])) {
+ $result = stream_for($file['contents']); // Throws.
+ } elseif (isset($file['filepath'])) {
+ $handle = fopen($file['filepath'], 'rb');
+ if ($handle === false) {
+ throw new \RuntimeException(sprintf('Could not open file "%s" for reading.', $file['filepath']));
+ }
+ $this->_handles[] = $handle;
+ $result = stream_for($handle); // Throws.
+ } else {
+ throw new \InvalidArgumentException('No data for stream creation.');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Convert the request's data into its HTTP POST multipart body contents.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return MultipartStream
+ */
+ protected function _getMultipartBody()
+ {
+ // Here is a tricky part: all form data (including files) must be ordered by hash code.
+ // So we are creating an index for building POST data.
+ $index = Utils::reorderByHashCode(array_merge($this->_posts, $this->_files));
+ // Build multipart elements using created index.
+ $elements = [];
+ foreach ($index as $key => $value) {
+ if (!isset($this->_files[$key])) {
+ $element = [
+ 'name' => $key,
+ 'contents' => $value,
+ ];
+ } else {
+ $file = $this->_files[$key];
+ $element = [
+ 'name' => $key,
+ 'contents' => $this->_getStreamForFile($file), // Throws.
+ 'filename' => isset($file['filename']) ? $file['filename'] : null,
+ 'headers' => isset($file['headers']) ? $file['headers'] : [],
+ ];
+ }
+ $elements[] = $element;
+ }
+
+ return new MultipartStream( // Throws.
+ $elements,
+ Utils::generateMultipartBoundary()
+ );
+ }
+
+ /**
+ * Close opened file handles.
+ */
+ protected function _closeHandles()
+ {
+ if (!is_array($this->_handles) || !count($this->_handles)) {
+ return;
+ }
+
+ foreach ($this->_handles as $handle) {
+ Utils::safe_fclose($handle);
+ }
+ $this->_resetHandles();
+ }
+
+ /**
+ * Reset opened handles array.
+ */
+ protected function _resetHandles()
+ {
+ $this->_handles = [];
+ }
+
+ /**
+ * Convert the request's data into its HTTP POST urlencoded body contents.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Stream
+ */
+ protected function _getUrlencodedBody()
+ {
+ $this->_headers['Content-Type'] = Constants::CONTENT_TYPE;
+
+ return stream_for( // Throws.
+ http_build_query(Utils::reorderByHashCode($this->_posts))
+ );
+ }
+
+ /**
+ * Convert the request's data into its HTTP POST body contents.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return StreamInterface|null The body stream if POST request; otherwise NULL if GET request.
+ */
+ protected function _getRequestBody()
+ {
+ // Check and return raw body stream if set.
+ if ($this->_body !== null) {
+ if ($this->_isBodyCompressed) {
+ return stream_for(zlib_encode((string) $this->_body, ZLIB_ENCODING_GZIP));
+ }
+
+ return $this->_body;
+ }
+ // We have no POST data and no files.
+ if (!count($this->_posts) && !count($this->_files)) {
+ return;
+ }
+ // Sign POST data if needed.
+ if ($this->_signedPost) {
+ $this->_posts = Signatures::signData($this->_posts, $this->_excludeSigned);
+ }
+ // Switch between multipart (at least one file) or urlencoded body.
+ if (!count($this->_files)) {
+ $result = $this->_getUrlencodedBody(); // Throws.
+ } else {
+ $result = $this->_getMultipartBody(); // Throws.
+ }
+
+ if ($this->_isBodyCompressed) {
+ return stream_for(zlib_encode((string) $result, ZLIB_ENCODING_GZIP));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Build HTTP request object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return HttpRequest
+ */
+ protected function _buildHttpRequest()
+ {
+ $endpoint = $this->_url;
+ // Determine the URI to use (it's either relative to API, or a full URI).
+ if (strncmp($endpoint, 'http:', 5) !== 0 && strncmp($endpoint, 'https:', 6) !== 0) {
+ $endpoint = Constants::API_URLS[$this->_apiVersion].$endpoint;
+ }
+ // Check signed request params flag.
+ if ($this->_signedGet) {
+ $this->_params = Signatures::signData($this->_params);
+ }
+ // Generate the final endpoint URL, by adding any custom query params.
+ if (count($this->_params)) {
+ $endpoint = $endpoint
+ .(strpos($endpoint, '?') === false ? '?' : '&')
+ .http_build_query(Utils::reorderByHashCode($this->_params));
+ }
+ // Add default headers (if enabled).
+ $this->_addDefaultHeaders();
+ /** @var StreamInterface|null $postData The POST body stream; is NULL if GET request instead. */
+ $postData = $this->_getRequestBody(); // Throws.
+ // Determine request method.
+ $method = $postData !== null ? 'POST' : 'GET';
+ // Build HTTP request object.
+ return new HttpRequest( // Throws (they didn't document that properly).
+ $method,
+ $endpoint,
+ $this->_headers,
+ $postData
+ );
+ }
+
+ /**
+ * Helper which throws an error if not logged in.
+ *
+ * Remember to ALWAYS call this function at the top of any API request that
+ * requires the user to be logged in!
+ *
+ * @throws LoginRequiredException
+ */
+ protected function _throwIfNotLoggedIn()
+ {
+ // Check the cached login state. May not reflect what will happen on the
+ // server. But it's the best we can check without trying the actual request!
+ if (!$this->_parent->isMaybeLoggedIn) {
+ throw new LoginRequiredException('User not logged in. Please call login() and then try again.');
+ }
+ }
+
+ /**
+ * Perform the request and get its raw HTTP response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return HttpResponseInterface
+ */
+ public function getHttpResponse()
+ {
+ // Prevent request from sending multiple times.
+ if ($this->_httpResponse === null) {
+ if ($this->_needsAuth) {
+ // Throw if this requires authentication and we're not logged in.
+ $this->_throwIfNotLoggedIn();
+ }
+
+ $this->_resetHandles();
+
+ try {
+ $this->_httpResponse = $this->_parent->client->api( // Throws.
+ $this->_buildHttpRequest(), // Throws.
+ $this->_guzzleOptions
+ );
+ } finally {
+ $this->_closeHandles();
+ }
+ }
+
+ return $this->_httpResponse;
+ }
+
+ /**
+ * Return the raw HTTP response body.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return string
+ */
+ public function getRawResponse()
+ {
+ $httpResponse = $this->getHttpResponse(); // Throws.
+ $body = (string) $httpResponse->getBody();
+
+ // Handle API endpoints that respond with multiple JSON objects.
+ // NOTE: We simply merge all JSON objects into a single object. This
+ // text replacement of "}\r\n{" is safe, because the actual JSON data
+ // objects never contain literal newline characters (http://json.org).
+ // And if we get any duplicate properties, then PHP will simply select
+ // the latest value for that property (ex: a:1,a:2 is treated as a:2).
+ if ($this->_isMultiResponse) {
+ $body = str_replace("}\r\n{", ',', $body);
+ }
+
+ return $body;
+ }
+
+ /**
+ * Return safely JSON-decoded HTTP response.
+ *
+ * This uses a special decoder which handles 64-bit numbers correctly.
+ *
+ * @param bool $assoc When FALSE, decode to object instead of associative array.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return mixed
+ */
+ public function getDecodedResponse(
+ $assoc = true)
+ {
+ // Important: Special JSON decoder.
+ return Client::api_body_decode(
+ $this->getRawResponse(), // Throws.
+ $assoc
+ );
+ }
+
+ /**
+ * Perform the request and map its response data to the provided object.
+ *
+ * @param Response $responseObject An instance of a class object whose properties to fill with the response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return Response The provided responseObject with all JSON properties filled.
+ */
+ public function getResponse(
+ Response $responseObject)
+ {
+ // Check for API response success and put its response in the object.
+ $this->_parent->client->mapServerResponse( // Throws.
+ $responseObject,
+ $this->getRawResponse(), // Throws.
+ $this->getHttpResponse() // Throws.
+ );
+
+ return $responseObject;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Account.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Account.php
new file mode 100755
index 0000000..8cee2aa
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Account.php
@@ -0,0 +1,779 @@
+ig->request('accounts/current_user/')
+ ->addParam('edit', true)
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Edit your biography.
+ *
+ * You are able to add `@mentions` and `#hashtags` to your biography, but
+ * be aware that Instagram disallows certain web URLs and shorteners.
+ *
+ * Also keep in mind that anyone can read your biography (even if your
+ * account is private).
+ *
+ * WARNING: Remember to also call `editProfile()` *after* using this
+ * function, so that you act like the real app!
+ *
+ * @param string $biography Biography text. Use "" for nothing.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see Account::editProfile() should be called after this function!
+ */
+ public function setBiography(
+ $biography)
+ {
+ if (!is_string($biography) || mb_strlen($biography, 'utf8') > 150) {
+ throw new \InvalidArgumentException('Please provide a 0 to 150 character string as biography.');
+ }
+
+ return $this->ig->request('accounts/set_biography/')
+ ->addPost('raw_text', $biography)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Edit your gender.
+ *
+ * WARNING: Remember to also call `editProfile()` *after* using this
+ * function, so that you act like the real app!
+ *
+ * @param string $gender this can be male, female, empty or null for 'prefer not to say' or anything else for custom
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function setGender(
+ $gender = '')
+ {
+ switch (strtolower($gender)) {
+ case 'male':$gender_id = 1; break;
+ case 'female':$gender_id = 2; break;
+ case null:
+ case '':$gender_id = 3; break;
+ default:$gender_id = 4;
+ }
+
+ return $this->ig->request('accounts/set_gender/')
+ ->setSignedPost(false)
+ ->addPost('gender', $gender_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('custom_gender', $gender_id === 4 ? $gender : '')
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Edit your profile.
+ *
+ * Warning: You must provide ALL parameters to this function. The values
+ * which you provide will overwrite all current values on your profile.
+ * You can use getCurrentUser() to see your current values first.
+ *
+ * @param string $url Website URL. Use "" for nothing.
+ * @param string $phone Phone number. Use "" for nothing.
+ * @param string $name Full name. Use "" for nothing.
+ * @param string $biography Biography text. Use "" for nothing.
+ * @param string $email Email. Required!
+ * @param int $gender Gender (1 = male, 2 = female, 3 = unknown). Required!
+ * @param string|null $newUsername (optional) Rename your account to a new username,
+ * which you've already verified with checkUsername().
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see Account::getCurrentUser() to get your current account details.
+ * @see Account::checkUsername() to verify your new username first.
+ */
+ public function editProfile(
+ $url,
+ $phone,
+ $name,
+ $biography,
+ $email,
+ $gender,
+ $newUsername = null)
+ {
+ // We must mark the profile for editing before doing the main request.
+ $userResponse = $this->ig->request('accounts/current_user/')
+ ->addParam('edit', true)
+ ->getResponse(new Response\UserInfoResponse());
+
+ // Get the current user's name from the response.
+ $currentUser = $userResponse->getUser();
+ if (!$currentUser || !is_string($currentUser->getUsername())) {
+ throw new InternalException('Unable to find current account username while preparing profile edit.');
+ }
+ $oldUsername = $currentUser->getUsername();
+
+ // Determine the desired username value.
+ $username = is_string($newUsername) && strlen($newUsername) > 0
+ ? $newUsername
+ : $oldUsername; // Keep current name.
+
+ return $this->ig->request('accounts/edit_profile/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('external_url', $url)
+ ->addPost('phone_number', $phone)
+ ->addPost('username', $username)
+ ->addPost('first_name', $name)
+ ->addPost('biography', $biography)
+ ->addPost('email', $email)
+ ->addPost('gender', $gender)
+ ->addPost('device_id', $this->ig->device_id)
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Changes your account's profile picture.
+ *
+ * @param string $photoFilename The photo filename.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function changeProfilePicture(
+ $photoFilename)
+ {
+ return $this->ig->request('accounts/change_profile_picture/')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addFile('profile_pic', $photoFilename, 'profile_pic')
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Remove your account's profile picture.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function removeProfilePicture()
+ {
+ return $this->ig->request('accounts/remove_profile_picture/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Sets your account to public.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function setPublic()
+ {
+ return $this->ig->request('accounts/set_public/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Sets your account to private.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function setPrivate()
+ {
+ return $this->ig->request('accounts/set_private/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Switches your account to business profile.
+ *
+ * In order to switch your account to Business profile you MUST
+ * call Account::setBusinessInfo().
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SwitchBusinessProfileResponse
+ *
+ * @see Account::setBusinessInfo() sets required data to become a business profile.
+ */
+ public function switchToBusinessProfile()
+ {
+ return $this->ig->request('business_conversion/get_business_convert_social_context/')
+ ->getResponse(new Response\SwitchBusinessProfileResponse());
+ }
+
+ /**
+ * Switches your account to personal profile.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SwitchPersonalProfileResponse
+ */
+ public function switchToPersonalProfile()
+ {
+ return $this->ig->request('accounts/convert_to_personal/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SwitchPersonalProfileResponse());
+ }
+
+ /**
+ * Sets contact information for business profile.
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ * @param string $email Email.
+ * @param string $categoryId TODO: Info.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateBusinessInfoResponse
+ */
+ public function setBusinessInfo(
+ $phoneNumber,
+ $email,
+ $categoryId)
+ {
+ return $this->ig->request('accounts/create_business_info/')
+ ->addPost('set_public', 'true')
+ ->addPost('entry_point', 'setting')
+ ->addPost('public_phone_contact', json_encode([
+ 'public_phone_number' => $phoneNumber,
+ 'business_contact_method' => 'CALL',
+ ]))
+ ->addPost('public_email', $email)
+ ->addPost('category_id', $categoryId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\CreateBusinessInfoResponse());
+ }
+
+ /**
+ * Check if an Instagram username is available (not already registered).
+ *
+ * Use this before trying to rename your Instagram account,
+ * to be sure that the new username is available.
+ *
+ * @param string $username Instagram username to check.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CheckUsernameResponse
+ *
+ * @see Account::editProfile() to rename your account.
+ */
+ public function checkUsername(
+ $username)
+ {
+ return $this->ig->request('users/check_username/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('username', $username)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->getResponse(new Response\CheckUsernameResponse());
+ }
+
+ /**
+ * Get account spam filter status.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterResponse
+ */
+ public function getCommentFilter()
+ {
+ return $this->ig->request('accounts/get_comment_filter/')
+ ->getResponse(new Response\CommentFilterResponse());
+ }
+
+ /**
+ * Set account spam filter status (on/off).
+ *
+ * @param int $config_value Whether spam filter is on (0 or 1).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterSetResponse
+ */
+ public function setCommentFilter(
+ $config_value)
+ {
+ return $this->ig->request('accounts/set_comment_filter/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('config_value', $config_value)
+ ->getResponse(new Response\CommentFilterSetResponse());
+ }
+
+ /**
+ * Get whether the comment category filter is disabled.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentCategoryFilterResponse
+ */
+ public function getCommentCategoryFilterDisabled()
+ {
+ return $this->ig->request('accounts/get_comment_category_filter_disabled/')
+ ->getResponse(new Response\CommentCategoryFilterResponse());
+ }
+
+ /**
+ * Get account spam filter keywords.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterKeywordsResponse
+ */
+ public function getCommentFilterKeywords()
+ {
+ return $this->ig->request('accounts/get_comment_filter_keywords/')
+ ->getResponse(new Response\CommentFilterKeywordsResponse());
+ }
+
+ /**
+ * Set account spam filter keywords.
+ *
+ * @param string $keywords List of blocked words, separated by comma.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterSetResponse
+ */
+ public function setCommentFilterKeywords(
+ $keywords)
+ {
+ return $this->ig->request('accounts/set_comment_filter_keywords/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('keywords', $keywords)
+ ->getResponse(new Response\CommentFilterSetResponse());
+ }
+
+ /**
+ * Change your account's password.
+ *
+ * @param string $oldPassword Old password.
+ * @param string $newPassword New password.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ChangePasswordResponse
+ */
+ public function changePassword(
+ $oldPassword,
+ $newPassword)
+ {
+ return $this->ig->request('accounts/change_password/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('old_password', $oldPassword)
+ ->addPost('new_password1', $newPassword)
+ ->addPost('new_password2', $newPassword)
+ ->getResponse(new Response\ChangePasswordResponse());
+ }
+
+ /**
+ * Get account security info and backup codes.
+ *
+ * WARNING: STORE AND KEEP BACKUP CODES IN A SAFE PLACE. THEY ARE EXTREMELY
+ * IMPORTANT! YOU WILL GET THE CODES IN THE RESPONSE. THE BACKUP
+ * CODES LET YOU REGAIN CONTROL OF YOUR ACCOUNT IF YOU LOSE THE
+ * PHONE NUMBER! WITHOUT THE CODES, YOU RISK LOSING YOUR ACCOUNT!
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\AccountSecurityInfoResponse
+ *
+ * @see Account::enableTwoFactorSMS()
+ */
+ public function getSecurityInfo()
+ {
+ return $this->ig->request('accounts/account_security_info/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\AccountSecurityInfoResponse());
+ }
+
+ /**
+ * Request that Instagram enables two factor SMS authentication.
+ *
+ * The SMS will have a verification code for enabling two factor SMS
+ * authentication. You must then give that code to enableTwoFactorSMS().
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SendTwoFactorEnableSMSResponse
+ *
+ * @see Account::enableTwoFactorSMS()
+ */
+ public function sendTwoFactorEnableSMS(
+ $phoneNumber)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ return $this->ig->request('accounts/send_two_factor_enable_sms/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->getResponse(new Response\SendTwoFactorEnableSMSResponse());
+ }
+
+ /**
+ * Enable Two Factor authentication.
+ *
+ * WARNING: STORE AND KEEP BACKUP CODES IN A SAFE PLACE. THEY ARE EXTREMELY
+ * IMPORTANT! YOU WILL GET THE CODES IN THE RESPONSE. THE BACKUP
+ * CODES LET YOU REGAIN CONTROL OF YOUR ACCOUNT IF YOU LOSE THE
+ * PHONE NUMBER! WITHOUT THE CODES, YOU RISK LOSING YOUR ACCOUNT!
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ * @param string $verificationCode The code sent to your phone via `Account::sendTwoFactorEnableSMS()`.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\AccountSecurityInfoResponse
+ *
+ * @see Account::sendTwoFactorEnableSMS()
+ * @see Account::getSecurityInfo()
+ */
+ public function enableTwoFactorSMS(
+ $phoneNumber,
+ $verificationCode)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ $this->ig->request('accounts/enable_sms_two_factor/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->addPost('verification_code', $verificationCode)
+ ->getResponse(new Response\EnableTwoFactorSMSResponse());
+
+ return $this->getSecurityInfo();
+ }
+
+ /**
+ * Disable Two Factor authentication.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DisableTwoFactorSMSResponse
+ */
+ public function disableTwoFactorSMS()
+ {
+ return $this->ig->request('accounts/disable_sms_two_factor/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\DisableTwoFactorSMSResponse());
+ }
+
+ /**
+ * Save presence status to the storage.
+ *
+ * @param bool $disabled
+ */
+ protected function _savePresenceStatus(
+ $disabled)
+ {
+ try {
+ $this->ig->settings->set('presence_disabled', $disabled ? '1' : '0');
+ } catch (SettingsException $e) {
+ // Ignore storage errors.
+ }
+ }
+
+ /**
+ * Get presence status.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PresenceStatusResponse
+ */
+ public function getPresenceStatus()
+ {
+ /** @var Response\PresenceStatusResponse $result */
+ $result = $this->ig->request('accounts/get_presence_disabled/')
+ ->setSignedGet(true)
+ ->getResponse(new Response\PresenceStatusResponse());
+
+ $this->_savePresenceStatus($result->getDisabled());
+
+ return $result;
+ }
+
+ /**
+ * Enable presence.
+ *
+ * Allow accounts you follow and anyone you message to see when you were
+ * last active on Instagram apps.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function enablePresence()
+ {
+ /** @var Response\GenericResponse $result */
+ $result = $this->ig->request('accounts/set_presence_disabled/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('disabled', '0')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+
+ $this->_savePresenceStatus(false);
+
+ return $result;
+ }
+
+ /**
+ * Disable presence.
+ *
+ * You won't be able to see the activity status of other accounts.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function disablePresence()
+ {
+ /** @var Response\GenericResponse $result */
+ $result = $this->ig->request('accounts/set_presence_disabled/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('disabled', '1')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+
+ $this->_savePresenceStatus(true);
+
+ return $result;
+ }
+
+ /**
+ * Tell Instagram to send you a message to verify your email address.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SendConfirmEmailResponse
+ */
+ public function sendConfirmEmail()
+ {
+ return $this->ig->request('accounts/send_confirm_email/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('send_source', 'edit_profile')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SendConfirmEmailResponse());
+ }
+
+ /**
+ * Tell Instagram to send you an SMS code to verify your phone number.
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SendSMSCodeResponse
+ */
+ public function sendSMSCode(
+ $phoneNumber)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ return $this->ig->request('accounts/send_sms_code/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SendSMSCodeResponse());
+ }
+
+ /**
+ * Submit the SMS code you received to verify your phone number.
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ * @param string $verificationCode The code sent to your phone via `Account::sendSMSCode()`.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\VerifySMSCodeResponse
+ *
+ * @see Account::sendSMSCode()
+ */
+ public function verifySMSCode(
+ $phoneNumber,
+ $verificationCode)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ return $this->ig->request('accounts/verify_sms_code/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->addPost('verification_code', $verificationCode)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\VerifySMSCodeResponse());
+ }
+
+ /**
+ * Set contact point prefill.
+ *
+ * @param string $usage Either "prefill" or "auto_confirmation".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function setContactPointPrefill(
+ $usage)
+ {
+ return $this->ig->request('accounts/contact_point_prefill/')
+ ->setNeedsAuth(false)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('usage', $usage)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get account badge notifications for the "Switch account" menu.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BadgeNotificationsResponse
+ */
+ public function getBadgeNotifications()
+ {
+ return $this->ig->request('notifications/badge/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('users_ids', $this->ig->account_id)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->getResponse(new Response\BadgeNotificationsResponse());
+ }
+
+ /**
+ * TODO.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function getProcessContactPointSignals()
+ {
+ return $this->ig->request('accounts/process_contact_point_signals/')
+ ->addPost('google_tokens', '[]')
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get prefill candidates.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PrefillCandidatesResponse
+ */
+ public function getPrefillCandidates()
+ {
+ return $this->ig->request('accounts/get_prefill_candidates/')
+ ->setNeedsAuth(false)
+ ->addPost('android_device_id', $this->ig->device_id)
+ ->addPost('device_id', $this->ig->uuid)
+ ->addPost('usages', '["account_recovery_omnibox"]')
+ ->getResponse(new Response\PrefillCandidatesResponse());
+ }
+
+ /**
+ * Get details about child and main IG accounts.
+ *
+ * @param bool $useAuth Indicates if auth is required for this request
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function getAccountFamily(
+ $useAuth = true)
+ {
+ return $this->ig->request('multiple_accounts/get_account_family/')
+ ->getResponse(new Response\MultipleAccountFamilyResponse());
+ }
+
+ /**
+ * Get linked accounts status.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LinkageStatusResponse
+ */
+ public function getLinkageStatus()
+ {
+ return $this->ig->request('linked_accounts/get_linkage_status/')
+ ->getResponse(new Response\LinkageStatusResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Business.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Business.php
new file mode 100755
index 0000000..6508a52
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Business.php
@@ -0,0 +1,85 @@
+ig->request('insights/account_organic_insights/')
+ ->addParam('show_promotions_in_landing_page', 'true')
+ ->addParam('first', $day)
+ ->getResponse(new Response\InsightsResponse());
+ }
+
+ /**
+ * Get media insights.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaInsightsResponse
+ */
+ public function getMediaInsights(
+ $mediaId)
+ {
+ return $this->ig->request("insights/media_organic_insights/{$mediaId}/")
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->getResponse(new Response\MediaInsightsResponse());
+ }
+
+ /**
+ * Get account statistics.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GraphqlResponse
+ */
+ public function getStatistics()
+ {
+ return $this->ig->request('ads/graphql/')
+ ->setSignedPost(false)
+ ->setIsMultiResponse(true)
+ ->addParam('locale', Constants::USER_AGENT_LOCALE)
+ ->addParam('vc_policy', 'insights_policy')
+ ->addParam('surface', 'account')
+ ->addPost('access_token', 'undefined')
+ ->addPost('fb_api_caller_class', 'RelayModern')
+ ->addPost('variables', json_encode([
+ 'IgInsightsGridMediaImage_SIZE' => 240,
+ 'timezone' => 'Atlantic/Canary',
+ 'activityTab' => true,
+ 'audienceTab' => true,
+ 'contentTab' => true,
+ 'query_params' => json_encode([
+ 'access_token' => '',
+ 'id' => $this->ig->account_id,
+ ]),
+ ]))
+ ->addPost('doc_id', '1926322010754880')
+ ->getResponse(new Response\GraphqlResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Collection.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Collection.php
new file mode 100755
index 0000000..a58eaba
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Collection.php
@@ -0,0 +1,181 @@
+ig->request('collections/list/')
+ ->addParam('collection_types', '["ALL_MEDIA_AUTO_COLLECTION","MEDIA","PRODUCT_AUTO_COLLECTION"]');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\GetCollectionsListResponse());
+ }
+
+ /**
+ * Get the feed of one of your collections.
+ *
+ * @param string $collectionId The collection ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CollectionFeedResponse
+ */
+ public function getFeed(
+ $collectionId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("feed/collection/{$collectionId}/");
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\CollectionFeedResponse());
+ }
+
+ /**
+ * Create a new collection of your bookmarked (saved) media.
+ *
+ * @param string $name Name of the collection.
+ * @param string $moduleName (optional) From which app module (page) you're performing this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateCollectionResponse
+ */
+ public function create(
+ $name,
+ $moduleName = 'feed_saved_add_to_collection')
+ {
+ return $this->ig->request('collections/create/')
+ ->addPost('module_name', $moduleName)
+ ->addPost('added_media_ids', '[]')
+ ->addPost('collection_visibility', '0') //Instagram is planning for public collections soon
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('name', $name)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\CreateCollectionResponse());
+ }
+
+ /**
+ * Delete a collection.
+ *
+ * @param string $collectionId The collection ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DeleteCollectionResponse
+ */
+ public function delete(
+ $collectionId)
+ {
+ return $this->ig->request("collections/{$collectionId}/delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\DeleteCollectionResponse());
+ }
+
+ /**
+ * Edit the name of a collection or add more saved media to an existing collection.
+ *
+ * @param string $collectionId The collection ID.
+ * @param array $params User-provided key-value pairs:
+ * string 'name',
+ * string 'cover_media_id',
+ * string[] 'add_media',
+ * string 'module_name' (optional).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditCollectionResponse
+ */
+ public function edit(
+ $collectionId,
+ array $params)
+ {
+ $postData = [];
+ if (isset($params['name']) && $params['name'] !== '') {
+ $postData['name'] = $params['name'];
+ }
+ if (!empty($params['cover_media_id'])) {
+ $postData['cover_media_id'] = $params['cover_media_id'];
+ }
+ if (!empty($params['add_media']) && is_array($params['add_media'])) {
+ $postData['added_media_ids'] = json_encode(array_values($params['add_media']));
+ if (isset($params['module_name']) && $params['module_name'] !== '') {
+ $postData['module_name'] = $params['module_name'];
+ } else {
+ $postData['module_name'] = 'feed_saved_add_to_collection';
+ }
+ }
+ if (empty($postData)) {
+ throw new \InvalidArgumentException('You must provide a name or at least one media ID.');
+ }
+ $request = $this->ig->request("collections/{$collectionId}/edit/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ foreach ($postData as $key => $value) {
+ $request->addPost($key, $value);
+ }
+
+ return $request->getResponse(new Response\EditCollectionResponse());
+ }
+
+ /**
+ * Remove a single media item from one or more of your collections.
+ *
+ * Note that you can only remove a single media item per call, since this
+ * function only accepts a single media ID.
+ *
+ * @param string[] $collectionIds Array with one or more collection IDs to remove the item from.
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $moduleName (optional) From which app module (page) you're performing this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditCollectionResponse
+ */
+ public function removeMedia(
+ array $collectionIds,
+ $mediaId,
+ $moduleName = 'feed_contextual_saved_collections')
+ {
+ return $this->ig->request("media/{$mediaId}/save/")
+ ->addPost('module_name', $moduleName)
+ ->addPost('removed_collection_ids', json_encode(array_values($collectionIds)))
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\EditCollectionResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Creative.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Creative.php
new file mode 100755
index 0000000..6aec5b6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Creative.php
@@ -0,0 +1,123 @@
+ig->request('creatives/assets/')
+ ->addPost('type', $stickerType);
+
+ if ($location !== null) {
+ $request
+ ->addPost('lat', $location['lat'])
+ ->addPost('lng', $location['lng'])
+ ->addPost('horizontalAccuracy', $location['horizontalAccuracy']);
+ }
+
+ return $request->getResponse(new Response\StickerAssetsResponse());
+ }
+
+ /**
+ * Get face models that can be used to customize photos or videos.
+ *
+ * NOTE: The files are some strange binary format that only the Instagram
+ * app understands. If anyone figures out the format, please contact us.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FaceModelsResponse
+ */
+ public function getFaceModels()
+ {
+ return $this->ig->request('creatives/face_models/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('aml_facetracker_model_version', 12)
+ ->getResponse(new Response\FaceModelsResponse());
+ }
+
+ /**
+ * Get face overlay effects to customize photos or videos.
+ *
+ * These are effects such as "bunny ears" and similar overlays.
+ *
+ * NOTE: The files are some strange binary format that only the Instagram
+ * app understands. If anyone figures out the format, please contact us.
+ *
+ * @param array|null $location (optional) Array containing lat, lng and horizontalAccuracy.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FaceEffectsResponse
+ */
+ public function getFaceEffects(
+ array $location = null)
+ {
+ $request = $this->ig->request('creatives/face_effects/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES));
+
+ if ($location !== null) {
+ $request
+ ->addPost('lat', $location['lat'])
+ ->addPost('lng', $location['lng'])
+ ->addPost('horizontalAccuracy', $location['horizontalAccuracy']);
+ }
+
+ return $request->getResponse(new Response\FaceEffectsResponse());
+ }
+
+ /**
+ * Send supported capabilities.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\WriteSuppotedCapabilitiesResponse
+ */
+ public function sendSupportedCapabilities()
+ {
+ return $this->ig->request('creatives/write_supported_capabilities/')
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\WriteSuppotedCapabilitiesResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Direct.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Direct.php
new file mode 100755
index 0000000..6051a8f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Direct.php
@@ -0,0 +1,1550 @@
+ 20) {
+ throw new \InvalidArgumentException('Invalid value provided to limit.');
+ }
+ $request = $this->ig->request('direct_v2/inbox/')
+ ->addParam('persistentBadging', 'true')
+ ->addParam('visual_message_return_type', 'unseen')
+ ->addParam('limit', $limit);
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+ if ($prefetch) {
+ $request->addHeader('X-IG-Prefetch-Request', 'foreground');
+ }
+ if ($threadMessageLimit !== null) {
+ $request->addParam('thread_message_limit', $threadMessageLimit);
+ }
+
+ return $request->getResponse(new Response\DirectInboxResponse());
+ }
+
+ /**
+ * Get pending inbox data.
+ *
+ * @param string|null $cursorId Next "cursor ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectPendingInboxResponse
+ */
+ public function getPendingInbox(
+ $cursorId = null)
+ {
+ $request = $this->ig->request('direct_v2/pending_inbox/')
+ ->addParam('persistentBadging', 'true')
+ ->addParam('use_unified_inbox', 'true');
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+
+ return $request->getResponse(new Response\DirectPendingInboxResponse());
+ }
+
+ /**
+ * Approve pending threads by given identifiers.
+ *
+ * @param array $threads One or more thread identifiers.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function approvePendingThreads(
+ array $threads)
+ {
+ if (!count($threads)) {
+ throw new \InvalidArgumentException('Please provide at least one thread to approve.');
+ }
+ // Validate threads.
+ foreach ($threads as &$thread) {
+ if (!is_scalar($thread)) {
+ throw new \InvalidArgumentException('Thread identifier must be scalar.');
+ } elseif (!ctype_digit($thread) && (!is_int($thread) || $thread < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $thread));
+ }
+ $thread = (string) $thread;
+ }
+ unset($thread);
+ // Choose appropriate endpoint.
+ if (count($threads) > 1) {
+ $request = $this->ig->request('direct_v2/threads/approve_multiple/')
+ ->addPost('thread_ids', json_encode($threads));
+ } else {
+ /** @var string $thread */
+ $thread = reset($threads);
+ $request = $this->ig->request("direct_v2/threads/{$thread}/approve/");
+ }
+
+ return $request
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Decline pending threads by given identifiers.
+ *
+ * @param array $threads One or more thread identifiers.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function declinePendingThreads(
+ array $threads)
+ {
+ if (!count($threads)) {
+ throw new \InvalidArgumentException('Please provide at least one thread to decline.');
+ }
+ // Validate threads.
+ foreach ($threads as &$thread) {
+ if (!is_scalar($thread)) {
+ throw new \InvalidArgumentException('Thread identifier must be scalar.');
+ } elseif (!ctype_digit($thread) && (!is_int($thread) || $thread < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $thread));
+ }
+ $thread = (string) $thread;
+ }
+ unset($thread);
+ // Choose appropriate endpoint.
+ if (count($threads) > 1) {
+ $request = $this->ig->request('direct_v2/threads/decline_multiple/')
+ ->addPost('thread_ids', json_encode($threads));
+ } else {
+ /** @var string $thread */
+ $thread = reset($threads);
+ $request = $this->ig->request("direct_v2/threads/{$thread}/decline/");
+ }
+
+ return $request
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Decline all pending threads.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function declineAllPendingThreads()
+ {
+ return $this->ig->request('direct_v2/threads/decline_all/')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get a list of activity statuses for users who you follow or message.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PresencesResponse
+ */
+ public function getPresences()
+ {
+ return $this->ig->request('direct_v2/get_presence/')
+ ->getResponse(new Response\PresencesResponse());
+ }
+
+ /**
+ * Get ranked list of recipients.
+ *
+ * WARNING: This is a special, very heavily throttled API endpoint.
+ * Instagram REQUIRES that you wait several minutes between calls to it.
+ *
+ * @param string $mode Either "reshare" or "raven".
+ * @param bool $showThreads Whether to include existing threads into response.
+ * @param string|null $query (optional) The user to search for.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectRankedRecipientsResponse|null Will be NULL if throttled by Instagram.
+ */
+ public function getRankedRecipients(
+ $mode,
+ $showThreads,
+ $query = null)
+ {
+ try {
+ $request = $this->ig->request('direct_v2/ranked_recipients/')
+ ->addParam('mode', $mode)
+ ->addParam('show_threads', $showThreads ? 'true' : 'false')
+ ->addParam('use_unified_inbox', 'true');
+ if ($query !== null) {
+ $request->addParam('query', $query);
+ }
+
+ return $request
+ ->getResponse(new Response\DirectRankedRecipientsResponse());
+ } catch (ThrottledException $e) {
+ // Throttling is so common that we'll simply return NULL in that case.
+ return null;
+ }
+ }
+
+ /**
+ * Get a thread by the recipients list.
+ *
+ * @param string[]|int[] $users Array of numerical UserPK IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function getThreadByParticipants(
+ array $users)
+ {
+ if (!count($users)) {
+ throw new \InvalidArgumentException('Please provide at least one participant.');
+ }
+ foreach ($users as $user) {
+ if (!is_scalar($user)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ }
+ if (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
+ }
+ }
+ $request = $this->ig->request('direct_v2/threads/get_by_participants/')
+ ->addParam('recipient_users', '['.implode(',', $users).']');
+
+ return $request->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Get direct message thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|null $cursorId Next "cursor ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function getThread(
+ $threadId,
+ $cursorId = null)
+ {
+ $request = $this->ig->request("direct_v2/threads/$threadId/")
+ ->addParam('use_unified_inbox', 'true');
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+
+ return $request->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Get direct visual thread.
+ *
+ * `NOTE:` This "visual" endpoint is only used for Direct stories.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|null $cursorId Next "cursor ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectVisualThreadResponse
+ *
+ * @deprecated Visual inbox has been superseded by the unified inbox.
+ * @see Direct::getThread()
+ */
+ public function getVisualThread(
+ $threadId,
+ $cursorId = null)
+ {
+ $request = $this->ig->request("direct_v2/visual_threads/{$threadId}/");
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+
+ return $request->getResponse(new Response\DirectVisualThreadResponse());
+ }
+
+ /**
+ * Update thread title.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $title New title.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function updateThreadTitle(
+ $threadId,
+ $title)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/update_title/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('title', trim($title))
+ ->setSignedPost(false)
+ ->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Mute direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function muteThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/mute/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unmute direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unmuteThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/unmute/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Create a private story sharing group.
+ *
+ * NOTE: In the official app, when you create a story, you can choose to
+ * send it privately. And from there you can create a new group thread. So
+ * this group creation endpoint is only meant to be used for "direct
+ * stories" at the moment.
+ *
+ * @param string[]|int[] $userIds Array of numerical UserPK IDs.
+ * @param string $threadTitle Name of the group thread.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectCreateGroupThreadResponse
+ */
+ public function createGroupThread(
+ array $userIds,
+ $threadTitle)
+ {
+ if (count($userIds) < 2) {
+ throw new \InvalidArgumentException('You must invite at least 2 users to create a group.');
+ }
+ foreach ($userIds as &$user) {
+ if (!is_scalar($user)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ } elseif (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
+ }
+ $user = (string) $user;
+ }
+
+ $request = $this->ig->request('direct_v2/create_group_thread/')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('recipient_users', json_encode($userIds))
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('thread_title', $threadTitle);
+
+ return $request->getResponse(new Response\DirectCreateGroupThreadResponse());
+ }
+
+ /**
+ * Add users to thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string[]|int[] $users Array of numerical UserPK IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function addUsersToThread(
+ $threadId,
+ array $users)
+ {
+ if (!count($users)) {
+ throw new \InvalidArgumentException('Please provide at least one user.');
+ }
+ foreach ($users as &$user) {
+ if (!is_scalar($user)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ } elseif (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
+ }
+ $user = (string) $user;
+ }
+
+ return $this->ig->request("direct_v2/threads/{$threadId}/add_user/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_ids', json_encode($users))
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Leave direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function leaveThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/leave/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Hide direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function hideThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/hide/")
+ ->addPost('use_unified_inbox', 'true')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Send a direct text message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $text Text message.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendText(
+ array $recipients,
+ $text,
+ array $options = [])
+ {
+ if (!strlen($text)) {
+ throw new \InvalidArgumentException('Text can not be empty.');
+ }
+
+ $urls = Utils::extractURLs($text);
+ if (count($urls)) {
+ /** @var Response\DirectSendItemResponse $result */
+ $result = $this->_sendDirectItem('links', $recipients, array_merge($options, [
+ 'link_urls' => json_encode(array_map(function (array $url) {
+ return $url['fullUrl'];
+ }, $urls)),
+ 'link_text' => $text,
+ ]));
+ } else {
+ /** @var Response\DirectSendItemResponse $result */
+ $result = $this->_sendDirectItem('message', $recipients, array_merge($options, [
+ 'text' => $text,
+ ]));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Share an existing media post via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of additional parameters, including:
+ * "media_type" (required) - either "photo" or "video";
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemsResponse
+ *
+ * @see https://help.instagram.com/1209246439090858 For more information.
+ */
+ public function sendPost(
+ array $recipients,
+ $mediaId,
+ array $options = [])
+ {
+ if (!preg_match('#^\d+_\d+$#D', $mediaId)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media ID.', $mediaId));
+ }
+ if (!isset($options['media_type'])) {
+ throw new \InvalidArgumentException('Please provide media_type in options.');
+ }
+ if ($options['media_type'] !== 'photo' && $options['media_type'] !== 'video') {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media_type.', $options['media_type']));
+ }
+
+ return $this->_sendDirectItems('media_share', $recipients, array_merge($options, [
+ 'media_id' => $mediaId,
+ ]));
+ }
+
+ /**
+ * Send a photo (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $photoFilename The photo filename.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendPhoto(
+ array $recipients,
+ $photoFilename,
+ array $options = [])
+ {
+ if (!is_file($photoFilename) || !is_readable($photoFilename)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" is not available for reading.', $photoFilename));
+ }
+
+ // validate width, height and aspect ratio of photo
+ $photoDetails = new PhotoDetails($photoFilename);
+ $photoDetails->validate(ConstraintsFactory::createFor(Constants::FEED_DIRECT));
+
+ // uplaod it
+ return $this->_sendDirectItem('photo', $recipients, array_merge($options, [
+ 'filepath' => $photoFilename,
+ ]));
+ }
+
+ /**
+ * Send a disappearing photo (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $photoFilename The photo filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSinglePhoto() for available metadata fields.
+ */
+ public function sendDisappearingPhoto(
+ array $recipients,
+ $photoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_ONCE);
+
+ return $this->ig->internal->uploadSinglePhoto(Constants::FEED_DIRECT_STORY, $photoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a replayable photo (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $photoFilename The photo filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSinglePhoto() for available metadata fields.
+ */
+ public function sendReplayablePhoto(
+ array $recipients,
+ $photoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_REPLAYABLE);
+
+ return $this->ig->internal->uploadSinglePhoto(Constants::FEED_DIRECT_STORY, $photoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a video (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $videoFilename The video filename.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendVideo(
+ array $recipients,
+ $videoFilename,
+ array $options = [])
+ {
+ // Direct videos use different upload IDs.
+ $internalMetadata = new InternalMetadata(Utils::generateUploadId(true));
+ // Attempt to upload the video data.
+ $internalMetadata = $this->ig->internal->uploadVideo(Constants::FEED_DIRECT, $videoFilename, $internalMetadata);
+
+ // We must use the same client_context for all attempts to prevent double-posting.
+ if (!isset($options['client_context'])) {
+ $options['client_context'] = Signatures::generateUUID(true);
+ }
+
+ // Send the uploaded video to recipients.
+ try {
+ /** @var \InstagramAPI\Response\DirectSendItemResponse $result */
+ $result = $this->ig->internal->configureWithRetries(
+ function () use ($internalMetadata, $recipients, $options) {
+ $videoUploadResponse = $internalMetadata->getVideoUploadResponse();
+ // Attempt to configure video parameters (which sends it to the thread).
+ return $this->_sendDirectItem('video', $recipients, array_merge($options, [
+ 'upload_id' => $internalMetadata->getUploadId(),
+ 'video_result' => $videoUploadResponse !== null ? $videoUploadResponse->getResult() : '',
+ ]));
+ }
+ );
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf(
+ 'Upload of "%s" failed: %s',
+ $internalMetadata->getPhotoDetails()->getBasename(),
+ $e->getMessage()
+ ),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Send a disappearing video (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function sendDisappearingVideo(
+ array $recipients,
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_ONCE);
+
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_DIRECT_STORY, $videoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a replayable video (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function sendReplayableVideo(
+ array $recipients,
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_REPLAYABLE);
+
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_DIRECT_STORY, $videoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a like to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendLike(
+ array $recipients,
+ array $options = [])
+ {
+ return $this->_sendDirectItem('like', $recipients, $options);
+ }
+
+ /**
+ * Send a hashtag to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $hashtag Hashtag to share.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendHashtag(
+ array $recipients,
+ $hashtag,
+ array $options = [])
+ {
+ if (!strlen($hashtag)) {
+ throw new \InvalidArgumentException('Hashtag can not be empty.');
+ }
+
+ return $this->_sendDirectItem('hashtag', $recipients, array_merge($options, [
+ 'hashtag' => $hashtag,
+ ]));
+ }
+
+ /**
+ * Send a location to a user's inbox.
+ *
+ * You must provide a valid Instagram location ID, which you get via other
+ * functions such as Location::search().
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $locationId Instagram's internal ID for the location.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ *
+ * @see Location::search()
+ */
+ public function sendLocation(
+ array $recipients,
+ $locationId,
+ array $options = [])
+ {
+ if (!ctype_digit($locationId) && (!is_int($locationId) || $locationId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid location ID.', $locationId));
+ }
+
+ return $this->_sendDirectItem('location', $recipients, array_merge($options, [
+ 'venue_id' => $locationId,
+ ]));
+ }
+
+ /**
+ * Send a profile to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $userId Numerical UserPK ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendProfile(
+ array $recipients,
+ $userId,
+ array $options = [])
+ {
+ if (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid numerical UserPK ID.', $userId));
+ }
+
+ return $this->_sendDirectItem('profile', $recipients, array_merge($options, [
+ 'profile_user_id' => $userId,
+ ]));
+ }
+
+ /**
+ * Send a reaction to an existing thread item.
+ *
+ * @param string $threadId Thread identifier.
+ * @param string $threadItemId ThreadItemIdentifier.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ return $this->_handleReaction($threadId, $threadItemId, $reactionType, 'created', $options);
+ }
+
+ /**
+ * Share an existing story post via direct message to a user's inbox.
+ *
+ * You are able to share your own stories, as well as public stories from
+ * other people.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $storyId The story ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $reelId The reel ID in Instagram's internal format (ie "highlight:12970012453081168")
+ * @param array $options An associative array of additional parameters, including:
+ * "media_type" (required) - either "photo" or "video";
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemsResponse
+ *
+ * @see https://help.instagram.com/188382041703187 For more information.
+ */
+ public function sendStory(
+ array $recipients,
+ $storyId,
+ $reelId = null,
+ array $options = [])
+ {
+ if (!preg_match('#^\d+_\d+$#D', $storyId)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid story ID.', $storyId));
+ }
+ if ($reelId !== null) {
+ if (!preg_match('#^highlight:\d+$#D', $reelId)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid reel ID.', $reelId));
+ }
+ $options = array_merge($options,
+ [
+ 'reel_id' => $reelId,
+ ]);
+ }
+ if (!isset($options['media_type'])) {
+ throw new \InvalidArgumentException('Please provide media_type in options.');
+ }
+ if ($options['media_type'] !== 'photo' && $options['media_type'] !== 'video') {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media_type.', $options['media_type']));
+ }
+
+ return $this->_sendDirectItems('story_share', $recipients, array_merge($options, [
+ 'story_media_id' => $storyId,
+ ]));
+ }
+
+ /**
+ * Share an occurring or archived live stream via direct message to a user's inbox.
+ *
+ * You are able to share your own broadcasts, as well as broadcasts from
+ * other people.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response\DirectSendItemResponse
+ */
+ public function sendLive(
+ array $recipients,
+ $broadcastId,
+ array $options = [])
+ {
+ return $this->_sendDirectItem('live', $recipients, array_merge($options, [
+ 'broadcast_id' => $broadcastId,
+ ]));
+ }
+
+ /**
+ * Delete a reaction to an existing thread item.
+ *
+ * @param string $threadId Thread identifier.
+ * @param string $threadItemId ThreadItemIdentifier.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function deleteReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ return $this->_handleReaction($threadId, $threadItemId, $reactionType, 'deleted', $options);
+ }
+
+ /**
+ * Delete an item from given thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread item ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function deleteItem(
+ $threadId,
+ $threadItemId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/items/{$threadItemId}/delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Marks an item from given thread as seen.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread item ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSeenItemResponse
+ */
+ public function markItemSeen(
+ $threadId,
+ $threadItemId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/items/{$threadItemId}/seen/")
+ ->addPost('use_unified_inbox', 'true')
+ ->addPost('action', 'mark_seen')
+ ->addPost('thread_id', $threadId)
+ ->addPost('item_id', $threadItemId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\DirectSeenItemResponse());
+ }
+
+ /**
+ * Marks visual items from given thread as seen.
+ *
+ * `NOTE:` This "visual" endpoint is only used for Direct stories.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|string[] $threadItemIds One or more thread item IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function markVisualItemsSeen(
+ $threadId,
+ $threadItemIds)
+ {
+ if (!is_array($threadItemIds)) {
+ $threadItemIds = [$threadItemIds];
+ } elseif (!count($threadItemIds)) {
+ throw new \InvalidArgumentException('Please provide at least one thread item ID.');
+ }
+
+ return $this->ig->request("direct_v2/visual_threads/{$threadId}/item_seen/")
+ ->addPost('item_ids', '['.implode(',', $threadItemIds).']')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Marks visual items from given thread as replayed.
+ *
+ * `NOTE:` This "visual" endpoint is only used for Direct stories.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|string[] $threadItemIds One or more thread item IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function markVisualItemsReplayed(
+ $threadId,
+ $threadItemIds)
+ {
+ if (!is_array($threadItemIds)) {
+ $threadItemIds = [$threadItemIds];
+ } elseif (!count($threadItemIds)) {
+ throw new \InvalidArgumentException('Please provide at least one thread item ID.');
+ }
+
+ return $this->ig->request("direct_v2/visual_threads/{$threadId}/item_replayed/")
+ ->addPost('item_ids', '['.implode(',', $threadItemIds).']')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Validate and prepare recipients for direct messaging.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param bool $useQuotes Whether to put IDs into quotes.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return array
+ */
+ protected function _prepareRecipients(
+ array $recipients,
+ $useQuotes)
+ {
+ $result = [];
+ // users
+ if (isset($recipients['users'])) {
+ if (!is_array($recipients['users'])) {
+ throw new \InvalidArgumentException('"users" must be an array.');
+ }
+ foreach ($recipients['users'] as $userId) {
+ if (!is_scalar($userId)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ } elseif (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $userId));
+ }
+ }
+ // Although this is an array of groups, you will get "Only one group is supported." error
+ // if you will try to use more than one group here.
+ // We can't use json_encode() here, because each user id must be a number.
+ $result['users'] = '[['.implode(',', $recipients['users']).']]';
+ }
+ // thread
+ if (isset($recipients['thread'])) {
+ if (!is_scalar($recipients['thread'])) {
+ throw new \InvalidArgumentException('Thread identifier must be scalar.');
+ } elseif (!ctype_digit($recipients['thread']) && (!is_int($recipients['thread']) || $recipients['thread'] < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $recipients['thread']));
+ }
+ // Although this is an array, you will get "Need to specify thread ID or recipient users." error
+ // if you will try to use more than one thread identifier here.
+ if (!$useQuotes) {
+ // We can't use json_encode() here, because thread id must be a number.
+ $result['thread'] = '['.$recipients['thread'].']';
+ } else {
+ // We can't use json_encode() here, because thread id must be a string.
+ $result['thread'] = '["'.$recipients['thread'].'"]';
+ }
+ }
+ if (!count($result)) {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ } elseif (isset($result['thread']) && isset($result['users'])) {
+ throw new \InvalidArgumentException('You can not mix "users" with "thread".');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Send a direct message to specific users or thread.
+ *
+ * @param string $type One of: "message", "like", "hashtag", "location", "profile", "photo",
+ * "video", "links", "live".
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param array $options Depends on $type:
+ * "message" uses "client_context" and "text";
+ * "like" uses "client_context";
+ * "hashtag" uses "client_context", "hashtag" and "text";
+ * "location" uses "client_context", "venue_id" and "text";
+ * "profile" uses "client_context", "profile_user_id" and "text";
+ * "photo" uses "client_context" and "filepath";
+ * "video" uses "client_context", "upload_id" and "video_result";
+ * "links" uses "client_context", "link_text" and "link_urls".
+ * "live" uses "client_context" and "text".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ protected function _sendDirectItem(
+ $type,
+ array $recipients,
+ array $options = [])
+ {
+ // Most requests are unsigned, but some use signing by overriding this.
+ $signedPost = false;
+
+ // Handle the request...
+ switch ($type) {
+ case 'message':
+ $request = $this->ig->request('direct_v2/threads/broadcast/text/');
+ // Check and set text.
+ if (!isset($options['text'])) {
+ throw new \InvalidArgumentException('No text message provided.');
+ }
+ $request->addPost('text', $options['text']);
+ break;
+ case 'like':
+ $request = $this->ig->request('direct_v2/threads/broadcast/like/');
+ break;
+ case 'hashtag':
+ $request = $this->ig->request('direct_v2/threads/broadcast/hashtag/');
+ // Check and set hashtag.
+ if (!isset($options['hashtag'])) {
+ throw new \InvalidArgumentException('No hashtag provided.');
+ }
+ $request->addPost('hashtag', $options['hashtag']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ case 'location':
+ $request = $this->ig->request('direct_v2/threads/broadcast/location/');
+ // Check and set venue_id.
+ if (!isset($options['venue_id'])) {
+ throw new \InvalidArgumentException('No venue_id provided.');
+ }
+ $request->addPost('venue_id', $options['venue_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ case 'profile':
+ $request = $this->ig->request('direct_v2/threads/broadcast/profile/');
+ // Check and set profile_user_id.
+ if (!isset($options['profile_user_id'])) {
+ throw new \InvalidArgumentException('No profile_user_id provided.');
+ }
+ $request->addPost('profile_user_id', $options['profile_user_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ case 'photo':
+ $request = $this->ig->request('direct_v2/threads/broadcast/upload_photo/');
+ // Check and set filepath.
+ if (!isset($options['filepath'])) {
+ throw new \InvalidArgumentException('No filepath provided.');
+ }
+ $request->addFile('photo', $options['filepath'], 'direct_temp_photo_'.Utils::generateUploadId().'.jpg');
+ break;
+ case 'video':
+ $request = $this->ig->request('direct_v2/threads/broadcast/configure_video/');
+ // Check and set upload_id.
+ if (!isset($options['upload_id'])) {
+ throw new \InvalidArgumentException('No upload_id provided.');
+ }
+ $request->addPost('upload_id', $options['upload_id']);
+ // Set video_result if provided.
+ if (isset($options['video_result'])) {
+ $request->addPost('video_result', $options['video_result']);
+ }
+ break;
+ case 'links':
+ $request = $this->ig->request('direct_v2/threads/broadcast/link/');
+ // Check and set link_urls.
+ if (!isset($options['link_urls'])) {
+ throw new \InvalidArgumentException('No link_urls provided.');
+ }
+ $request->addPost('link_urls', $options['link_urls']);
+ // Check and set link_text.
+ if (!isset($options['link_text'])) {
+ throw new \InvalidArgumentException('No link_text provided.');
+ }
+ $request->addPost('link_text', $options['link_text']);
+ break;
+ case 'reaction':
+ $request = $this->ig->request('direct_v2/threads/broadcast/reaction/');
+ // Check and set reaction_type.
+ if (!isset($options['reaction_type'])) {
+ throw new \InvalidArgumentException('No reaction_type provided.');
+ }
+ $request->addPost('reaction_type', $options['reaction_type']);
+ // Check and set reaction_status.
+ if (!isset($options['reaction_status'])) {
+ throw new \InvalidArgumentException('No reaction_status provided.');
+ }
+ $request->addPost('reaction_status', $options['reaction_status']);
+ // Check and set item_id.
+ if (!isset($options['item_id'])) {
+ throw new \InvalidArgumentException('No item_id provided.');
+ }
+ $request->addPost('item_id', $options['item_id']);
+ // Check and set node_type.
+ if (!isset($options['node_type'])) {
+ throw new \InvalidArgumentException('No node_type provided.');
+ }
+ $request->addPost('node_type', $options['node_type']);
+ break;
+ case 'live':
+ $request = $this->ig->request('direct_v2/threads/broadcast/live_viewer_invite/');
+ // Check and set broadcast id.
+ if (!isset($options['broadcast_id'])) {
+ throw new \InvalidArgumentException('No broadcast_id provided.');
+ }
+ $request->addPost('broadcast_id', $options['broadcast_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException('Unsupported _sendDirectItem() type.');
+ }
+
+ // Add recipients.
+ $recipients = $this->_prepareRecipients($recipients, false);
+ if (isset($recipients['users'])) {
+ $request->addPost('recipient_users', $recipients['users']);
+ } elseif (isset($recipients['thread'])) {
+ $request->addPost('thread_ids', $recipients['thread']);
+ } else {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ }
+
+ // Handle client_context.
+ if (!isset($options['client_context'])) {
+ // WARNING: Must be random every time otherwise we can only
+ // make a single post per direct-discussion thread.
+ $options['client_context'] = Signatures::generateUUID(true);
+ } elseif (!Signatures::isValidUUID($options['client_context'])) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
+ }
+
+ // Add some additional data if signed post.
+ if ($signedPost) {
+ $request->addPost('_uid', $this->ig->account_id);
+ }
+
+ // Execute the request with all data used by both signed and unsigned.
+ return $request->setSignedPost($signedPost)
+ ->addPost('action', 'send_item')
+ ->addPost('client_context', $options['client_context'])
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\DirectSendItemResponse());
+ }
+
+ /**
+ * Send a direct messages to specific users or thread.
+ *
+ * @param string $type One of: "media_share", "story_share".
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param array $options Depends on $type:
+ * "media_share" uses "client_context", "media_id", "media_type" and "text";
+ * "story_share" uses "client_context", "story_media_id", "media_type" and "text".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemsResponse
+ */
+ protected function _sendDirectItems(
+ $type,
+ array $recipients,
+ array $options = [])
+ {
+ // Most requests are unsigned, but some use signing by overriding this.
+ $signedPost = false;
+
+ // Handle the request...
+ switch ($type) {
+ case 'media_share':
+ $request = $this->ig->request('direct_v2/threads/broadcast/media_share/');
+ // Check and set media_id.
+ if (!isset($options['media_id'])) {
+ throw new \InvalidArgumentException('No media_id provided.');
+ }
+ $request->addPost('media_id', $options['media_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ // Check and set media_type.
+ if (isset($options['media_type']) && $options['media_type'] === 'video') {
+ $request->addParam('media_type', 'video');
+ } else {
+ $request->addParam('media_type', 'photo');
+ }
+ break;
+ case 'story_share':
+ $signedPost = true; // This must be a signed post!
+ $request = $this->ig->request('direct_v2/threads/broadcast/story_share/');
+ // Check and set story_media_id.
+ if (!isset($options['story_media_id'])) {
+ throw new \InvalidArgumentException('No story_media_id provided.');
+ }
+ $request->addPost('story_media_id', $options['story_media_id']);
+ // Set text if provided.
+ if (isset($options['reel_id'])) {
+ $request->addPost('reel_id', $options['reel_id']);
+ }
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ // Check and set media_type.
+ if (isset($options['media_type']) && $options['media_type'] === 'video') {
+ $request->addParam('media_type', 'video');
+ } else {
+ $request->addParam('media_type', 'photo');
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException('Unsupported _sendDirectItems() type.');
+ }
+
+ // Add recipients.
+ $recipients = $this->_prepareRecipients($recipients, false);
+ if (isset($recipients['users'])) {
+ $request->addPost('recipient_users', $recipients['users']);
+ } elseif (isset($recipients['thread'])) {
+ $request->addPost('thread_ids', $recipients['thread']);
+ } else {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ }
+
+ // Handle client_context.
+ if (!isset($options['client_context'])) {
+ // WARNING: Must be random every time otherwise we can only
+ // make a single post per direct-discussion thread.
+ $options['client_context'] = Signatures::generateUUID(true);
+ } elseif (!Signatures::isValidUUID($options['client_context'])) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
+ }
+
+ // Add some additional data if signed post.
+ if ($signedPost) {
+ $request->addPost('_uid', $this->ig->account_id);
+ }
+
+ // Execute the request with all data used by both signed and unsigned.
+ return $request->setSignedPost($signedPost)
+ ->addPost('action', 'send_item')
+ ->addPost('unified_broadcast_format', '1')
+ ->addPost('client_context', $options['client_context'])
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\DirectSendItemsResponse());
+ }
+
+ /**
+ * Handle a reaction to an existing thread item.
+ *
+ * @param string $threadId Thread identifier.
+ * @param string $threadItemId ThreadItemIdentifier.
+ * @param string $reactionType One of: "like".
+ * @param string $reactionStatus One of: "created", "deleted".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ protected function _handleReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ $reactionStatus,
+ array $options = [])
+ {
+ if (!ctype_digit($threadId) && (!is_int($threadId) || $threadId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread ID.', $threadId));
+ }
+ if (!ctype_digit($threadItemId) && (!is_int($threadItemId) || $threadItemId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread item ID.', $threadItemId));
+ }
+ if (!in_array($reactionType, ['like'], true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction type.', $reactionType));
+ }
+
+ return $this->_sendDirectItem('reaction', ['thread' => $threadId], array_merge($options, [
+ 'reaction_type' => $reactionType,
+ 'reaction_status' => $reactionStatus,
+ 'item_id' => $threadItemId,
+ 'node_type' => 'item',
+ ]));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Discover.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Discover.php
new file mode 100755
index 0000000..e404df1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Discover.php
@@ -0,0 +1,192 @@
+ig->request('discover/topical_explore/')
+ ->addParam('is_prefetch', $isPrefetch)
+ ->addParam('omit_cover_media', true)
+ ->addParam('use_sectional_payload', true)
+ ->addParam('timezone_offset', date('Z'))
+ ->addParam('session_id', $this->ig->session_id)
+ ->addParam('include_fixed_destinations', true);
+ if ($clusterId !== null) {
+ $request->addParam('cluster_id', $clusterId);
+ }
+ if (!$isPrefetch) {
+ if ($maxId === null) {
+ $maxId = 0;
+ }
+ $request->addParam('max_id', $maxId);
+ $request->addParam('module', 'explore_popular');
+ }
+
+ return $request->getResponse(new Response\ExploreResponse());
+ }
+
+ /**
+ * Report media in the Explore-feed.
+ *
+ * @param string $exploreSourceToken Token related to the Explore media.
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReportExploreMediaResponse
+ */
+ public function reportExploreMedia(
+ $exploreSourceToken,
+ $userId)
+ {
+ return $this->ig->request('discover/explore_report/')
+ ->addParam('explore_source_token', $exploreSourceToken)
+ ->addParam('m_pk', $this->ig->account_id)
+ ->addParam('a_pk', $userId)
+ ->getResponse(new Response\ReportExploreMediaResponse());
+ }
+
+ /**
+ * Search for Instagram users, hashtags and places via Facebook's algorithm.
+ *
+ * This performs a combined search for "top results" in all 3 areas at once.
+ *
+ * @param string $query The username/full name, hashtag or location to search for.
+ * @param string $latitude (optional) Latitude.
+ * @param string $longitude (optional) Longitude.
+ * @param array $excludeList Array of grouped numerical entity IDs (ie "users" => ["4021088339"])
+ * to exclude from the response, allowing you to skip entities
+ * from a previous call to get more results. The following entities are supported:
+ * "users", "places", "tags".
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FBSearchResponse
+ *
+ * @see FBSearchResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For a rank token example (but with a different type of exclude list).
+ */
+ public function search(
+ $query,
+ $latitude = null,
+ $longitude = null,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation.
+ if (!is_string($query) || $query === '') {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+ $request = $this->_paginateWithMultiExclusion(
+ $this->ig->request('fbsearch/topsearch_flat/')
+ ->addParam('context', 'blended')
+ ->addParam('query', $query)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken
+ );
+
+ if ($latitude !== null && $longitude !== null) {
+ $request
+ ->addParam('lat', $latitude)
+ ->addParam('lng', $longitude);
+ }
+
+ try {
+ /** @var Response\FBSearchResponse $result */
+ $result = $request->getResponse(new Response\FBSearchResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\FBSearchResponse([
+ 'has_more' => false,
+ 'hashtags' => [],
+ 'users' => [],
+ 'places' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get search suggestions via Facebook's algorithm.
+ *
+ * NOTE: In the app, they're listed as the "Suggested" in the "Top" tab at the "Search" screen.
+ *
+ * @param string $type One of: "blended", "users", "hashtags" or "places".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedSearchesResponse
+ */
+ public function getSuggestedSearches(
+ $type)
+ {
+ if (!in_array($type, ['blended', 'users', 'hashtags', 'places'], true)) {
+ throw new \InvalidArgumentException(sprintf('Unknown search type: %s.', $type));
+ }
+
+ return $this->ig->request('fbsearch/suggested_searches/')
+ ->addParam('type', $type)
+ ->getResponse(new Response\SuggestedSearchesResponse());
+ }
+
+ /**
+ * Get recent searches via Facebook's algorithm.
+ *
+ * NOTE: In the app, they're listed as the "Recent" in the "Top" tab at the "Search" screen.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RecentSearchesResponse
+ */
+ public function getRecentSearches()
+ {
+ return $this->ig->request('fbsearch/recent_searches/')
+ ->getResponse(new Response\RecentSearchesResponse());
+ }
+
+ /**
+ * Clear the search history.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function clearSearchHistory()
+ {
+ return $this->ig->request('fbsearch/clear_search_history/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Hashtag.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Hashtag.php
new file mode 100755
index 0000000..0e702eb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Hashtag.php
@@ -0,0 +1,375 @@
+ig->request("tags/{$urlHashtag}/info/")
+ ->getResponse(new Response\TagInfoResponse());
+ }
+
+ /**
+ * Get hashtag story.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagsStoryResponse
+ */
+ public function getStory(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/{$urlHashtag}/story/")
+ ->getResponse(new Response\TagsStoryResponse());
+ }
+
+ /**
+ * Get hashtags from a section.
+ *
+ * Available tab sections: 'top', 'recent' or 'places'.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ * @param string $rankToken The feed UUID. You must use the same value for all pages of the feed.
+ * @param string|null $tab Section tab for hashtags.
+ * @param int[]|null $nextMediaIds Used for pagination.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagFeedResponse
+ */
+ public function getSection(
+ $hashtag,
+ $rankToken,
+ $tab = null,
+ $nextMediaIds = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+
+ $request = $this->ig->request("tags/{$urlHashtag}/sections/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('rank_token', $rankToken)
+ ->addPost('include_persistent', true);
+
+ if ($tab !== null) {
+ if ($tab !== 'top' && $tab !== 'recent' && $tab !== 'places' && $tab !== 'discover') {
+ throw new \InvalidArgumentException('Tab section must be \'top\', \'recent\', \'places\' or \'discover\'.');
+ }
+ $request->addPost('tab', $tab);
+ } else {
+ $request->addPost('supported_tabs', '["top","recent","places","discover"]');
+ }
+
+ if ($nextMediaIds !== null) {
+ if (!is_array($nextMediaIds) || !array_filter($nextMediaIds, 'is_int')) {
+ throw new \InvalidArgumentException('Next media IDs must be an Int[].');
+ }
+ $request->addPost('next_media_ids', json_encode($nextMediaIds));
+ }
+ if ($maxId !== null) {
+ $request->addPost('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\TagFeedResponse());
+ }
+
+ /**
+ * Search for hashtags.
+ *
+ * Gives you search results ordered by best matches first.
+ *
+ * Note that you can get more than one "page" of hashtag search results by
+ * excluding the numerical IDs of all tags from a previous search query.
+ *
+ * Also note that the excludes must be done via Instagram's internal,
+ * numerical IDs for the tags, which you can get from this search-response.
+ *
+ * Lastly, be aware that they will never exclude any tag that perfectly
+ * matches your search query, even if you provide its exact ID too.
+ *
+ * @param string $query Finds hashtags containing this string.
+ * @param string[]|int[] $excludeList Array of numerical hashtag IDs (ie "17841562498105353")
+ * to exclude from the response, allowing you to skip tags
+ * from a previous call to get more results.
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException If invalid query or
+ * trying to exclude too
+ * many hashtags.
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SearchTagResponse
+ *
+ * @see SearchTagResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function search(
+ $query,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation. Do NOT use throwIfInvalidHashtag here.
+ if (!is_string($query) || $query === '') {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+
+ $request = $this->_paginateWithExclusion(
+ $this->ig->request('tags/search/')
+ ->addParam('q', $query)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken
+ );
+
+ try {
+ /** @var Response\SearchTagResponse $result */
+ $result = $request->getResponse(new Response\SearchTagResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\SearchTagResponse([
+ 'has_more' => false,
+ 'results' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Follow hashtag.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagRelatedResponse
+ */
+ public function follow(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/follow/{$urlHashtag}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unfollow hashtag.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagRelatedResponse
+ */
+ public function unfollow(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/unfollow/{$urlHashtag}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get related hashtags.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagRelatedResponse
+ */
+ public function getRelated(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/{$urlHashtag}/related/")
+ ->addParam('visited', '[{"id":"'.$hashtag.'","type":"hashtag"}]')
+ ->addParam('related_types', '["hashtag"]')
+ ->getResponse(new Response\TagRelatedResponse());
+ }
+
+ /**
+ * Get the feed for a hashtag.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ * @param string $rankToken The feed UUID. You must use the same value for all pages of the feed.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagFeedResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFeed(
+ $hashtag,
+ $rankToken,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ Utils::throwIfInvalidRankToken($rankToken);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ $hashtagFeed = $this->ig->request("feed/tag/{$urlHashtag}/")
+ ->addParam('rank_token', $rankToken);
+ if ($maxId !== null) {
+ $hashtagFeed->addParam('max_id', $maxId);
+ }
+
+ return $hashtagFeed->getResponse(new Response\TagFeedResponse());
+ }
+
+ /**
+ * Get list of tags that a user is following.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HashtagsResponse
+ */
+ public function getFollowing(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/following_tags_info/")
+ ->getResponse(new Response\HashtagsResponse());
+ }
+
+ /**
+ * Get list of tags that you are following.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HashtagsResponse
+ */
+ public function getSelfFollowing()
+ {
+ return $this->getFollowing($this->ig->account_id);
+ }
+
+ /**
+ * Get list of tags that are suggested to follow to.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HashtagsResponse
+ */
+ public function getFollowSuggestions()
+ {
+ return $this->ig->request('tags/suggested/')
+ ->getResponse(new Response\HashtagsResponse());
+ }
+
+ /**
+ * Mark TagFeedResponse story media items as seen.
+ *
+ * The "story" property of a `TagFeedResponse` only gives you a list of
+ * story media. It doesn't actually mark any stories as "seen", so the
+ * user doesn't know that you've seen their story. Actually marking the
+ * story as "seen" is done via this endpoint instead. The official app
+ * calls this endpoint periodically (with 1 or more items at a time)
+ * while watching a story.
+ *
+ * This tells the user that you've seen their story, and also helps
+ * Instagram know that it shouldn't give you those seen stories again
+ * if you request the same hashtag feed multiple times.
+ *
+ * Tip: You can pass in the whole "getItems()" array from the hashtag's
+ * "story" property, to easily mark all of the TagFeedResponse's story
+ * media items as seen.
+ *
+ * @param Response\TagFeedResponse $hashtagFeed The hashtag feed response
+ * object which the story media
+ * items came from. The story
+ * items MUST belong to it.
+ * @param Response\Model\Item[] $items Array of one or more story
+ * media Items.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Story::markMediaSeen()
+ * @see Location::markStoryMediaSeen()
+ */
+ public function markStoryMediaSeen(
+ Response\TagFeedResponse $hashtagFeed,
+ array $items)
+ {
+ // Extract the Hashtag Story-Tray ID from the user's hashtag response.
+ // NOTE: This can NEVER fail if the user has properly given us the exact
+ // same hashtag response that they got the story items from!
+ $sourceId = '';
+ if ($hashtagFeed->getStory() instanceof Response\Model\StoryTray) {
+ $sourceId = $hashtagFeed->getStory()->getId();
+ }
+ if (!strlen($sourceId)) {
+ throw new \InvalidArgumentException('Your provided TagFeedResponse is invalid and does not contain any Hashtag Story-Tray ID.');
+ }
+
+ // Ensure they only gave us valid items for this hashtag response.
+ // NOTE: We validate since people cannot be trusted to use their brain.
+ $validIds = [];
+ foreach ($hashtagFeed->getStory()->getItems() as $item) {
+ $validIds[$item->getId()] = true;
+ }
+ foreach ($items as $item) {
+ // NOTE: We only check Items here. Other data is rejected by Internal.
+ if ($item instanceof Response\Model\Item && !isset($validIds[$item->getId()])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The item with ID "%s" does not belong to this TagFeedResponse.',
+ $item->getId()
+ ));
+ }
+ }
+
+ // Mark the story items as seen, with the hashtag as source ID.
+ return $this->ig->internal->markStoryMediaSeen($items, $sourceId);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Highlight.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Highlight.php
new file mode 100755
index 0000000..ba0109e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Highlight.php
@@ -0,0 +1,173 @@
+ig->request("highlights/{$userId}/highlights_tray/")
+ ->addParam('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addParam('phone_id', $this->ig->phone_id)
+ ->addParam('battery_level', '100')
+ ->addParam('is_charging', '1')
+ ->addParam('will_sound_on', '1')
+ ->getResponse(new Response\HighlightFeedResponse());
+ }
+
+ /**
+ * Get self highlight feed.
+ *
+ * NOTE: Sometimes, a highlight doesn't have any `items` property. Read
+ * `Highlight::getUserFeed()` for more information about what to do.
+ * Note 2: if user has a igtv post reponse will include 'tv_channel' property
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HighlightFeedResponse
+ *
+ * @see Highlight::getUserFeed()
+ * @see Story::getReelsMediaFeed() To get highlight items when they aren't included in this response.
+ */
+ public function getSelfUserFeed()
+ {
+ return $this->getUserFeed($this->ig->account_id);
+ }
+
+ /**
+ * Create a highlight reel.
+ *
+ * @param string[] $mediaIds Array with one or more media IDs in Instagram's internal format (ie ["3482384834_43294"]).
+ * @param string $title Title for the highlight.
+ * @param string|null $coverMediaId One media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $module
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateHighlightResponse
+ */
+ public function create(
+ array $mediaIds,
+ $title = 'Highlights',
+ $coverMediaId = null,
+ $module = 'self_profile')
+ {
+ if (empty($mediaIds)) {
+ throw new \InvalidArgumentException('You must provide at least one media ID.');
+ }
+ if ($coverMediaId === null) {
+ $coverMediaId = reset($mediaIds);
+ }
+ if ($title === null || $title === '') {
+ $title = 'Highlights';
+ } elseif (mb_strlen($title, 'utf8') > 16) {
+ throw new \InvalidArgumentException('Title must be between 1 and 16 characters.');
+ }
+
+ $cover = [
+ 'media_id' => $coverMediaId,
+ ];
+
+ return $this->ig->request('highlights/create_reel/')
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('source', $module)
+ ->addPost('creation_id', round(microtime(true) * 1000))
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('cover', json_encode($cover))
+ ->addPost('title', $title)
+ ->addPost('media_ids', json_encode(array_values($mediaIds)))
+ ->getResponse(new Response\CreateHighlightResponse());
+ }
+
+ /**
+ * Edit a highlight reel.
+ *
+ * @param string $highlightReelId Highlight ID, using internal format (ie "highlight:12345678901234567").
+ * @param array $params User-provided highlight key-value pairs. string 'title', string 'cover_media_id', string[] 'add_media', string[] 'remove_media'.
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HighlightFeedResponse
+ */
+ public function edit(
+ $highlightReelId,
+ array $params,
+ $module = 'self_profile')
+ {
+ if (!isset($params['cover_media_id'])) {
+ throw new \InvalidArgumentException('You must provide one media ID for the cover.');
+ }
+ if (!isset($params['title'])) {
+ $params['title'] = 'Highlights';
+ } elseif (mb_strlen($params['title'], 'utf8') > 16) {
+ throw new \InvalidArgumentException('Title length must be between 1 and 16 characters.');
+ }
+ if (!isset($params['add_media']) || !is_array($params['add_media'])) {
+ $params['add_media'] = [];
+ }
+ if (!isset($params['remove_media']) || !is_array($params['remove_media'])) {
+ $params['remove_media'] = [];
+ }
+ $cover = [
+ 'media_id' => $params['cover_media_id'],
+ ];
+
+ return $this->ig->request("highlights/{$highlightReelId}/edit_reel/")
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('source', $module)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('title', $params['title'])
+ ->addPost('cover', json_encode($cover))
+ ->addPost('added_media_ids', json_encode(array_values($params['add_media'])))
+ ->addPost('removed_media_ids', json_encode(array_values($params['remove_media'])))
+ ->getResponse(new Response\HighlightFeedResponse());
+ }
+
+ /**
+ * Delete a highlight reel.
+ *
+ * @param string $highlightReelId Highlight ID, using internal format (ie "highlight:12345678901234567").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function delete(
+ $highlightReelId)
+ {
+ return $this->ig->request("highlights/{$highlightReelId}/delete_reel/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Internal.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Internal.php
new file mode 100755
index 0000000..8b5a031
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Internal.php
@@ -0,0 +1,2596 @@
+getPhotoDetails() === null) {
+ $internalMetadata->setPhotoDetails($targetFeed, $photoFilename);
+ }
+ } catch (\Exception $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Failed to get photo details: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ // Perform the upload.
+ $this->uploadPhotoData($targetFeed, $internalMetadata);
+
+ // Configure the uploaded image and attach it to our timeline/story.
+ $configure = $this->configureSinglePhoto($targetFeed, $internalMetadata, $externalMetadata);
+
+ return $configure;
+ }
+
+ /**
+ * Upload the data for a photo to Instagram.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException
+ */
+ public function uploadPhotoData(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // Make sure we disallow some feeds for this function.
+ if ($targetFeed === Constants::FEED_DIRECT) {
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Make sure we have photo details.
+ if ($internalMetadata->getPhotoDetails() === null) {
+ throw new \InvalidArgumentException('Photo details are missing from the internal metadata.');
+ }
+
+ try {
+ // Upload photo file with one of our photo uploaders.
+ if ($this->_useResumablePhotoUploader($targetFeed, $internalMetadata)) {
+ $this->_uploadResumablePhoto($targetFeed, $internalMetadata);
+ } else {
+ $internalMetadata->setPhotoUploadResponse(
+ $this->_uploadPhotoInOnePiece($targetFeed, $internalMetadata)
+ );
+ }
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf(
+ 'Upload of "%s" failed: %s',
+ $internalMetadata->getPhotoDetails()->getBasename(),
+ $e->getMessage()
+ ),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Configures parameters for a *SINGLE* uploaded photo file.
+ *
+ * WARNING TO CONTRIBUTORS: THIS IS ONLY FOR *TIMELINE* AND *STORY* -PHOTOS-.
+ * USE "configureTimelineAlbum()" FOR ALBUMS and "configureSingleVideo()" FOR VIDEOS.
+ * AND IF FUTURE INSTAGRAM FEATURES NEED CONFIGURATION AND ARE NON-TRIVIAL,
+ * GIVE THEM THEIR OWN FUNCTION LIKE WE DID WITH "configureTimelineAlbum()",
+ * TO AVOID ADDING BUGGY AND UNMAINTAINABLE SPIDERWEB CODE!
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ */
+ public function configureSinglePhoto(
+ $targetFeed,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ // Determine the target endpoint for the photo.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $endpoint = 'media/configure/';
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ case Constants::FEED_STORY:
+ $endpoint = 'media/configure_to_story/';
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Available external metadata parameters:
+ /** @var string Caption to use for the media. */
+ $captionText = isset($externalMetadata['caption']) ? $externalMetadata['caption'] : '';
+ /** @var string Accesibility caption to use for the media. */
+ $altText = isset($externalMetadata['custom_accessibility_caption']) ? $externalMetadata['custom_accessibility_caption'] : null;
+ /** @var Response\Model\Location|null A Location object describing where
+ * the media was taken. */
+ $location = (isset($externalMetadata['location'])) ? $externalMetadata['location'] : null;
+ /** @var array|null Array of story location sticker instructions. ONLY
+ * USED FOR STORY MEDIA! */
+ $locationSticker = (isset($externalMetadata['location_sticker']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['location_sticker'] : null;
+ /** @var array|null Array of usertagging instructions, in the format
+ * [['position'=>[0.5,0.5], 'user_id'=>'123'], ...]. ONLY FOR TIMELINE PHOTOS! */
+ $usertags = (isset($externalMetadata['usertags']) && $targetFeed == Constants::FEED_TIMELINE) ? $externalMetadata['usertags'] : null;
+ /** @var string|null Link to attach to the media. ONLY USED FOR STORY MEDIA,
+ * AND YOU MUST HAVE A BUSINESS INSTAGRAM ACCOUNT TO POST A STORY LINK! */
+ $link = (isset($externalMetadata['link']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['link'] : null;
+ /** @var void Photo filter. THIS DOES NOTHING! All real filters are done in the mobile app. */
+ // $filter = isset($externalMetadata['filter']) ? $externalMetadata['filter'] : null;
+ $filter = null; // COMMENTED OUT SO USERS UNDERSTAND THEY CAN'T USE THIS!
+ /** @var array Hashtags to use for the media. ONLY STORY MEDIA! */
+ $hashtags = (isset($externalMetadata['hashtags']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['hashtags'] : null;
+ /** @var array Mentions to use for the media. ONLY STORY MEDIA! */
+ $storyMentions = (isset($externalMetadata['story_mentions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_mentions'] : null;
+ /** @var array Story poll to use for the media. ONLY STORY MEDIA! */
+ $storyPoll = (isset($externalMetadata['story_polls']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_polls'] : null;
+ /** @var array Story slider to use for the media. ONLY STORY MEDIA! */
+ $storySlider = (isset($externalMetadata['story_sliders']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_sliders'] : null;
+ /** @var array Story question to use for the media. ONLY STORY MEDIA */
+ $storyQuestion = (isset($externalMetadata['story_questions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_questions'] : null;
+ /** @var array Story countdown to use for the media. ONLY STORY MEDIA */
+ $storyCountdown = (isset($externalMetadata['story_countdowns']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_countdowns'] : null;
+ /** @var array Story fundraiser to use for the media. ONLY STORY MEDIA */
+ $storyFundraisers = (isset($externalMetadata['story_fundraisers']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_fundraisers'] : null;
+ /** @var array Attached media used to share media to story feed. ONLY STORY MEDIA! */
+ $attachedMedia = (isset($externalMetadata['attached_media']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['attached_media'] : null;
+ /** @var array Product Tags to use for the media. ONLY FOR TIMELINE PHOTOS! */
+ $productTags = (isset($externalMetadata['product_tags']) && $targetFeed == Constants::FEED_TIMELINE) ? $externalMetadata['product_tags'] : null;
+
+ // Fix very bad external user-metadata values.
+ if (!is_string($captionText)) {
+ $captionText = '';
+ }
+
+ // Critically important internal library-generated metadata parameters:
+ /** @var string The ID of the entry to configure. */
+ $uploadId = $internalMetadata->getUploadId();
+ /** @var int Width of the photo. */
+ $photoWidth = $internalMetadata->getPhotoDetails()->getWidth();
+ /** @var int Height of the photo. */
+ $photoHeight = $internalMetadata->getPhotoDetails()->getHeight();
+
+ // Build the request...
+ $request = $this->ig->request($endpoint)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('edits',
+ [
+ 'crop_original_size' => [(float) $photoWidth, (float) $photoHeight],
+ 'crop_zoom' => 1.0,
+ 'crop_center' => [0.0, -0.0],
+ ])
+ ->addPost('device',
+ [
+ 'manufacturer' => $this->ig->device->getManufacturer(),
+ 'model' => $this->ig->device->getModel(),
+ 'android_version' => $this->ig->device->getAndroidVersion(),
+ 'android_release' => $this->ig->device->getAndroidRelease(),
+ ])
+ ->addPost('extra',
+ [
+ 'source_width' => $photoWidth,
+ 'source_height' => $photoHeight,
+ ]);
+
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $date = date('Y:m:d H:i:s');
+ $request
+ ->addParam('timezone_offset', date('Z'))
+ ->addPost('date_time_original', $date)
+ ->addPost('date_time_digitalized', $date)
+ ->addPost('caption', $captionText)
+ ->addPost('source_type', '4')
+ ->addPost('media_folder', 'Camera')
+ ->addPost('upload_id', $uploadId);
+
+ if ($usertags !== null) {
+ Utils::throwIfInvalidUsertags($usertags);
+ $request->addPost('usertags', json_encode($usertags));
+ }
+ if ($productTags !== null) {
+ Utils::throwIfInvalidProductTags($productTags);
+ $request->addPost('product_tags', json_encode($productTags));
+ }
+ if ($altText !== null) {
+ $request->addPost('custom_accessibility_caption', $altText);
+ }
+ break;
+ case Constants::FEED_STORY:
+ if ($internalMetadata->isBestieMedia()) {
+ $request->addPost('audience', 'besties');
+ }
+
+ $request
+ ->addPost('client_shared_at', (string) time())
+ ->addPost('source_type', '3')
+ ->addPost('configure_mode', '1')
+ ->addPost('client_timestamp', (string) (time() - mt_rand(3, 10)))
+ ->addPost('upload_id', $uploadId);
+
+ if (is_string($link) && Utils::hasValidWebURLSyntax($link)) {
+ $story_cta = '[{"links":[{"linkType": 1, "webUri":'.json_encode($link).', "androidClass": "", "package": "", "deeplinkUri": "", "callToActionTitle": "", "redirectUri": null, "leadGenFormId": "", "igUserId": "", "appInstallObjectiveInvalidationBehavior": null}]}]';
+ $request->addPost('story_cta', $story_cta);
+ }
+ if ($hashtags !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryHashtags($captionText, $hashtags);
+ $request
+ ->addPost('story_hashtags', json_encode($hashtags))
+ ->addPost('caption', $captionText)
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($locationSticker !== null && $location !== null) {
+ Utils::throwIfInvalidStoryLocationSticker($locationSticker);
+ $request
+ ->addPost('story_locations', json_encode([$locationSticker]))
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyMentions !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryMentions($storyMentions);
+ $request
+ ->addPost('reel_mentions', json_encode($storyMentions))
+ ->addPost('caption', str_replace(' ', '+', $captionText).'+')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyPoll !== null) {
+ Utils::throwIfInvalidStoryPoll($storyPoll);
+ $request
+ ->addPost('story_polls', json_encode($storyPoll))
+ ->addPost('internal_features', 'polling_sticker')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storySlider !== null) {
+ Utils::throwIfInvalidStorySlider($storySlider);
+ $request
+ ->addPost('story_sliders', json_encode($storySlider))
+ ->addPost('story_sticker_ids', 'emoji_slider_'.$storySlider[0]['emoji']);
+ }
+ if ($storyQuestion !== null) {
+ Utils::throwIfInvalidStoryQuestion($storyQuestion);
+ $request
+ ->addPost('story_questions', json_encode($storyQuestion))
+ ->addPost('story_sticker_ids', 'question_sticker_ama');
+ }
+ if ($storyCountdown !== null) {
+ Utils::throwIfInvalidStoryCountdown($storyCountdown);
+ $request
+ ->addPost('story_countdowns', json_encode($storyCountdown))
+ ->addPost('story_sticker_ids', 'countdown_sticker_time');
+ }
+ if ($storyFundraisers !== null) {
+ $request
+ ->addPost('story_fundraisers', json_encode($storyFundraisers))
+ ->addPost('story_sticker_ids', 'fundraiser_sticker_id');
+ }
+ if ($attachedMedia !== null) {
+ Utils::throwIfInvalidAttachedMedia($attachedMedia);
+ $request
+ ->addPost('attached_media', json_encode($attachedMedia))
+ ->addPost('story_sticker_ids', 'media_simple_'.reset($attachedMedia)['media_id']);
+ }
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $request
+ ->addPost('recipient_users', $internalMetadata->getDirectUsers());
+
+ if ($internalMetadata->getStoryViewMode() !== null) {
+ $request->addPost('view_mode', $internalMetadata->getStoryViewMode());
+ }
+
+ $request
+ ->addPost('thread_ids', $internalMetadata->getDirectThreads())
+ ->addPost('client_shared_at', (string) time())
+ ->addPost('source_type', '3')
+ ->addPost('configure_mode', '2')
+ ->addPost('client_timestamp', (string) (time() - mt_rand(3, 10)))
+ ->addPost('upload_id', $uploadId);
+ break;
+ }
+
+ if ($location instanceof Response\Model\Location) {
+ if ($targetFeed === Constants::FEED_TIMELINE) {
+ $request->addPost('location', Utils::buildMediaLocationJSON($location));
+ }
+ if ($targetFeed === Constants::FEED_STORY && $locationSticker === null) {
+ throw new \InvalidArgumentException('You must provide a location_sticker together with your story location.');
+ }
+ $request
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $location->getLat())
+ ->addPost('posting_longitude', $location->getLng())
+ ->addPost('media_latitude', $location->getLat())
+ ->addPost('media_longitude', $location->getLng());
+ }
+
+ $configure = $request->getResponse(new Response\ConfigureResponse());
+
+ return $configure;
+ }
+
+ /**
+ * Uploads a raw video file.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $videoFilename The video filename.
+ * @param InternalMetadata|null $internalMetadata (optional) Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return InternalMetadata Updated internal metadata object.
+ */
+ public function uploadVideo(
+ $targetFeed,
+ $videoFilename,
+ InternalMetadata $internalMetadata = null)
+ {
+ if ($internalMetadata === null) {
+ $internalMetadata = new InternalMetadata();
+ }
+
+ try {
+ if ($internalMetadata->getVideoDetails() === null) {
+ $internalMetadata->setVideoDetails($targetFeed, $videoFilename);
+ }
+ } catch (\Exception $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Failed to get photo details: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ try {
+ if ($this->_useSegmentedVideoUploader($targetFeed, $internalMetadata)) {
+ $this->_uploadSegmentedVideo($targetFeed, $internalMetadata);
+ } elseif ($this->_useResumableVideoUploader($targetFeed, $internalMetadata)) {
+ $this->_uploadResumableVideo($targetFeed, $internalMetadata);
+ } else {
+ // Request parameters for uploading a new video.
+ $internalMetadata->setVideoUploadUrls($this->_requestVideoUploadURL($targetFeed, $internalMetadata));
+
+ // Attempt to upload the video data.
+ $internalMetadata->setVideoUploadResponse($this->_uploadVideoChunks($targetFeed, $internalMetadata));
+ }
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of "%s" failed: %s', basename($videoFilename), $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $internalMetadata;
+ }
+
+ /**
+ * UPLOADS A *SINGLE* VIDEO.
+ *
+ * This is NOT used for albums!
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $videoFilename The video filename.
+ * @param InternalMetadata|null $internalMetadata (optional) Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadSingleVideo(
+ $targetFeed,
+ $videoFilename,
+ InternalMetadata $internalMetadata = null,
+ array $externalMetadata = [])
+ {
+ // Make sure we only allow these particular feeds for this function.
+ if ($targetFeed !== Constants::FEED_TIMELINE
+ && $targetFeed !== Constants::FEED_STORY
+ && $targetFeed !== Constants::FEED_DIRECT_STORY
+ && $targetFeed !== Constants::FEED_TV
+ ) {
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Attempt to upload the video.
+ $internalMetadata = $this->uploadVideo($targetFeed, $videoFilename, $internalMetadata);
+
+ // Attempt to upload the thumbnail, associated with our video's ID.
+ $this->uploadVideoThumbnail($targetFeed, $internalMetadata, $externalMetadata);
+
+ // Configure the uploaded video and attach it to our timeline/story.
+ try {
+ /** @var \InstagramAPI\Response\ConfigureResponse $configure */
+ $configure = $this->ig->internal->configureWithRetries(
+ function () use ($targetFeed, $internalMetadata, $externalMetadata) {
+ // Attempt to configure video parameters.
+ return $this->configureSingleVideo($targetFeed, $internalMetadata, $externalMetadata);
+ }
+ );
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of "%s" failed: %s', basename($videoFilename), $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $configure;
+ }
+
+ /**
+ * Performs a resumable upload of a photo file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException
+ */
+ public function uploadVideoThumbnail(
+ $targetFeed,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ if ($internalMetadata->getVideoDetails() === null) {
+ throw new \InvalidArgumentException('Video details are missing from the internal metadata.');
+ }
+
+ try {
+ // Automatically crop&resize the thumbnail to Instagram's requirements.
+ $options = ['targetFeed' => $targetFeed];
+ if (isset($externalMetadata['thumbnail_timestamp'])) {
+ $options['thumbnailTimestamp'] = $externalMetadata['thumbnail_timestamp'];
+ }
+ $videoThumbnail = new InstagramThumbnail(
+ $internalMetadata->getVideoDetails()->getFilename(),
+ $options
+ );
+ // Validate and upload the thumbnail.
+ $internalMetadata->setPhotoDetails($targetFeed, $videoThumbnail->getFile());
+ $this->uploadPhotoData($targetFeed, $internalMetadata);
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of video thumbnail failed: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Asks Instagram for parameters for uploading a new video.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException If the request fails.
+ *
+ * @return \InstagramAPI\Response\UploadJobVideoResponse
+ */
+ protected function _requestVideoUploadURL(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $request = $this->ig->request('upload/video/')
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid);
+
+ foreach ($this->_getVideoUploadParams($targetFeed, $internalMetadata) as $key => $value) {
+ $request->addPost($key, $value);
+ }
+
+ // Perform the "pre-upload" API request.
+ /** @var Response\UploadJobVideoResponse $response */
+ $response = $request->getResponse(new Response\UploadJobVideoResponse());
+
+ return $response;
+ }
+
+ /**
+ * Configures parameters for a *SINGLE* uploaded video file.
+ *
+ * WARNING TO CONTRIBUTORS: THIS IS ONLY FOR *TIMELINE* AND *STORY* -VIDEOS-.
+ * USE "configureTimelineAlbum()" FOR ALBUMS and "configureSinglePhoto()" FOR PHOTOS.
+ * AND IF FUTURE INSTAGRAM FEATURES NEED CONFIGURATION AND ARE NON-TRIVIAL,
+ * GIVE THEM THEIR OWN FUNCTION LIKE WE DID WITH "configureTimelineAlbum()",
+ * TO AVOID ADDING BUGGY AND UNMAINTAINABLE SPIDERWEB CODE!
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ */
+ public function configureSingleVideo(
+ $targetFeed,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ // Determine the target endpoint for the video.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $endpoint = 'media/configure/';
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ case Constants::FEED_STORY:
+ $endpoint = 'media/configure_to_story/';
+ break;
+ case Constants::FEED_TV:
+ $endpoint = 'media/configure_to_igtv/';
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Available external metadata parameters:
+ /** @var string Caption to use for the media. */
+ $captionText = isset($externalMetadata['caption']) ? $externalMetadata['caption'] : '';
+ /** @var string[]|null Array of numerical UserPK IDs of people tagged in
+ * your video. ONLY USED IN STORY VIDEOS! TODO: Actually, it's not even
+ * implemented for stories. */
+ $usertags = (isset($externalMetadata['usertags'])) ? $externalMetadata['usertags'] : null;
+ /** @var Response\Model\Location|null A Location object describing where
+ * the media was taken. */
+ $location = (isset($externalMetadata['location'])) ? $externalMetadata['location'] : null;
+ /** @var array|null Array of story location sticker instructions. ONLY
+ * USED FOR STORY MEDIA! */
+ $locationSticker = (isset($externalMetadata['location_sticker']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['location_sticker'] : null;
+ /** @var string|null Link to attach to the media. ONLY USED FOR STORY MEDIA,
+ * AND YOU MUST HAVE A BUSINESS INSTAGRAM ACCOUNT TO POST A STORY LINK! */
+ $link = (isset($externalMetadata['link']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['link'] : null;
+ /** @var array Hashtags to use for the media. ONLY STORY MEDIA! */
+ $hashtags = (isset($externalMetadata['hashtags']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['hashtags'] : null;
+ /** @var array Mentions to use for the media. ONLY STORY MEDIA! */
+ $storyMentions = (isset($externalMetadata['story_mentions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_mentions'] : null;
+ /** @var array Story poll to use for the media. ONLY STORY MEDIA! */
+ $storyPoll = (isset($externalMetadata['story_polls']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_polls'] : null;
+ /** @var array Attached media used to share media to story feed. ONLY STORY MEDIA! */
+ $storySlider = (isset($externalMetadata['story_sliders']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_sliders'] : null;
+ /** @var array Story question to use for the media. ONLY STORY MEDIA */
+ $storyQuestion = (isset($externalMetadata['story_questions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_questions'] : null;
+ /** @var array Story countdown to use for the media. ONLY STORY MEDIA */
+ $storyCountdown = (isset($externalMetadata['story_countdowns']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_countdowns'] : null;
+ /** @var array Story fundraiser to use for the media. ONLY STORY MEDIA */
+ $storyFundraisers = (isset($externalMetadata['story_fundraisers']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_fundraisers'] : null;
+ /** @var array Attached media used to share media to story feed. ONLY STORY MEDIA! */
+ $attachedMedia = (isset($externalMetadata['attached_media']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['attached_media'] : null;
+ /** @var array Title of the media uploaded to your channel. ONLY TV MEDIA! */
+ $title = (isset($externalMetadata['title']) && $targetFeed == Constants::FEED_TV) ? $externalMetadata['title'] : null;
+ /** @var bool Whether or not a preview should be posted to your feed. ONLY TV MEDIA! */
+ $shareToFeed = (isset($externalMetadata['share_to_feed']) && $targetFeed == Constants::FEED_TV) ? $externalMetadata['share_to_feed'] : false;
+
+ // Fix very bad external user-metadata values.
+ if (!is_string($captionText)) {
+ $captionText = '';
+ }
+
+ $uploadId = $internalMetadata->getUploadId();
+ $videoDetails = $internalMetadata->getVideoDetails();
+
+ // Build the request...
+ $request = $this->ig->request($endpoint)
+ ->addParam('video', 1)
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('video_result', $internalMetadata->getVideoUploadResponse() !== null ? (string) $internalMetadata->getVideoUploadResponse()->getResult() : '')
+ ->addPost('upload_id', $uploadId)
+ ->addPost('poster_frame_index', 0)
+ ->addPost('length', round($videoDetails->getDuration(), 1))
+ ->addPost('audio_muted', $videoDetails->getAudioCodec() === null)
+ ->addPost('filter_type', 0)
+ ->addPost('source_type', 4)
+ ->addPost('device',
+ [
+ 'manufacturer' => $this->ig->device->getManufacturer(),
+ 'model' => $this->ig->device->getModel(),
+ 'android_version' => $this->ig->device->getAndroidVersion(),
+ 'android_release' => $this->ig->device->getAndroidRelease(),
+ ])
+ ->addPost('extra',
+ [
+ 'source_width' => $videoDetails->getWidth(),
+ 'source_height' => $videoDetails->getHeight(),
+ ])
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id);
+
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $request->addPost('caption', $captionText);
+ if ($usertags !== null) {
+ $in = [];
+ foreach ($usertags as $userId) {
+ $in[] = ['user_id' => $userId];
+ }
+ $request->addPost('usertags', $in);
+ }
+ break;
+ case Constants::FEED_STORY:
+ if ($internalMetadata->isBestieMedia()) {
+ $request->addPost('audience', 'besties');
+ }
+
+ $request
+ ->addPost('configure_mode', 1) // 1 - REEL_SHARE
+ ->addPost('story_media_creation_date', time() - mt_rand(10, 20))
+ ->addPost('client_shared_at', time() - mt_rand(3, 10))
+ ->addPost('client_timestamp', time());
+
+ if (is_string($link) && Utils::hasValidWebURLSyntax($link)) {
+ $story_cta = '[{"links":[{"linkType": 1, "webUri":'.json_encode($link).', "androidClass": "", "package": "", "deeplinkUri": "", "callToActionTitle": "", "redirectUri": null, "leadGenFormId": "", "igUserId": "", "appInstallObjectiveInvalidationBehavior": null}]}]';
+ $request->addPost('story_cta', $story_cta);
+ }
+ if ($hashtags !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryHashtags($captionText, $hashtags);
+ $request
+ ->addPost('story_hashtags', json_encode($hashtags))
+ ->addPost('caption', $captionText)
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($locationSticker !== null && $location !== null) {
+ Utils::throwIfInvalidStoryLocationSticker($locationSticker);
+ $request
+ ->addPost('story_locations', json_encode([$locationSticker]))
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyMentions !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryMentions($storyMentions);
+ $request
+ ->addPost('reel_mentions', json_encode($storyMentions))
+ ->addPost('caption', str_replace(' ', '+', $captionText).'+')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyPoll !== null) {
+ Utils::throwIfInvalidStoryPoll($storyPoll);
+ $request
+ ->addPost('story_polls', json_encode($storyPoll))
+ ->addPost('internal_features', 'polling_sticker')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storySlider !== null) {
+ Utils::throwIfInvalidStorySlider($storySlider);
+ $request
+ ->addPost('story_sliders', json_encode($storySlider))
+ ->addPost('story_sticker_ids', 'emoji_slider_'.$storySlider[0]['emoji']);
+ }
+ if ($storyQuestion !== null) {
+ Utils::throwIfInvalidStoryQuestion($storyQuestion);
+ $request
+ ->addPost('story_questions', json_encode($storyQuestion))
+ ->addPost('story_sticker_ids', 'question_sticker_ama');
+ }
+ if ($storyCountdown !== null) {
+ Utils::throwIfInvalidStoryCountdown($storyCountdown);
+ $request
+ ->addPost('story_countdowns', json_encode($storyCountdown))
+ ->addPost('story_sticker_ids', 'countdown_sticker_time');
+ }
+ if ($storyFundraisers !== null) {
+ $request
+ ->addPost('story_fundraisers', json_encode($storyFundraisers))
+ ->addPost('story_sticker_ids', 'fundraiser_sticker_id');
+ }
+ if ($attachedMedia !== null) {
+ Utils::throwIfInvalidAttachedMedia($attachedMedia);
+ $request
+ ->addPost('attached_media', json_encode($attachedMedia))
+ ->addPost('story_sticker_ids', 'media_simple_'.reset($attachedMedia)['media_id']);
+ }
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $request
+ ->addPost('configure_mode', 2) // 2 - DIRECT_STORY_SHARE
+ ->addPost('recipient_users', $internalMetadata->getDirectUsers());
+
+ if ($internalMetadata->getStoryViewMode() !== null) {
+ $request->addPost('view_mode', $internalMetadata->getStoryViewMode());
+ }
+
+ $request
+ ->addPost('thread_ids', $internalMetadata->getDirectThreads())
+ ->addPost('story_media_creation_date', time() - mt_rand(10, 20))
+ ->addPost('client_shared_at', time() - mt_rand(3, 10))
+ ->addPost('client_timestamp', time());
+ break;
+ case Constants::FEED_TV:
+ if ($title === null) {
+ throw new \InvalidArgumentException('You must provide a title for the media.');
+ }
+ if ($shareToFeed) {
+ if ($internalMetadata->getVideoDetails()->getDurationInMsec() < 60000) {
+ throw new \InvalidArgumentException('Your media must be at least a minute long to preview to feed.');
+ }
+ $request->addPost('igtv_share_preview_to_feed', '1');
+ }
+ $request
+ ->addPost('title', $title)
+ ->addPost('caption', $captionText);
+ break;
+ }
+
+ if ($targetFeed == Constants::FEED_STORY) {
+ $request->addPost('story_media_creation_date', time());
+ if ($usertags !== null) {
+ // Reel Mention example:
+ // [{\"y\":0.3407772676161919,\"rotation\":0,\"user_id\":\"USER_ID\",\"x\":0.39892578125,\"width\":0.5619921875,\"height\":0.06011525487256372}]
+ // NOTE: The backslashes are just double JSON encoding, ignore
+ // that and just give us an array with these clean values, don't
+ // try to encode it in any way, we do all encoding to match the above.
+ // This post field will get wrapped in another json_encode call during transfer.
+ $request->addPost('reel_mentions', json_encode($usertags));
+ }
+ }
+
+ if ($location instanceof Response\Model\Location) {
+ if ($targetFeed === Constants::FEED_TIMELINE) {
+ $request->addPost('location', Utils::buildMediaLocationJSON($location));
+ }
+ if ($targetFeed === Constants::FEED_STORY && $locationSticker === null) {
+ throw new \InvalidArgumentException('You must provide a location_sticker together with your story location.');
+ }
+ $request
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $location->getLat())
+ ->addPost('posting_longitude', $location->getLng())
+ ->addPost('media_latitude', $location->getLat())
+ ->addPost('media_longitude', $location->getLng());
+ }
+
+ $configure = $request->getResponse(new Response\ConfigureResponse());
+
+ return $configure;
+ }
+
+ /**
+ * Configures parameters for a whole album of uploaded media files.
+ *
+ * WARNING TO CONTRIBUTORS: THIS IS ONLY FOR *TIMELINE ALBUMS*. DO NOT MAKE
+ * IT DO ANYTHING ELSE, TO AVOID ADDING BUGGY AND UNMAINTAINABLE SPIDERWEB
+ * CODE!
+ *
+ * @param array $media Extended media array coming from Timeline::uploadAlbum(),
+ * containing the user's per-file metadata,
+ * and internally generated per-file metadata.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object for the album itself.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs
+ * for the album itself (its caption, location, etc).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ */
+ public function configureTimelineAlbum(
+ array $media,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ $endpoint = 'media/configure_sidecar/';
+
+ $albumUploadId = $internalMetadata->getUploadId();
+
+ // Available external metadata parameters:
+ /** @var string Caption to use for the album. */
+ $captionText = isset($externalMetadata['caption']) ? $externalMetadata['caption'] : '';
+ /** @var Response\Model\Location|null A Location object describing where
+ * the album was taken. */
+ $location = isset($externalMetadata['location']) ? $externalMetadata['location'] : null;
+
+ // Fix very bad external user-metadata values.
+ if (!is_string($captionText)) {
+ $captionText = '';
+ }
+
+ // Build the album's per-children metadata.
+ $date = date('Y:m:d H:i:s');
+ $childrenMetadata = [];
+ foreach ($media as $item) {
+ /** @var InternalMetadata $itemInternalMetadata */
+ $itemInternalMetadata = $item['internalMetadata'];
+ // Get all of the common, INTERNAL per-file metadata.
+ $uploadId = $itemInternalMetadata->getUploadId();
+
+ switch ($item['type']) {
+ case 'photo':
+ // Build this item's configuration.
+ $photoConfig = [
+ 'date_time_original' => $date,
+ 'scene_type' => 1,
+ 'disable_comments' => false,
+ 'upload_id' => $uploadId,
+ 'source_type' => 0,
+ 'scene_capture_type' => 'standard',
+ 'date_time_digitized' => $date,
+ 'geotag_enabled' => false,
+ 'camera_position' => 'back',
+ 'edits' => [
+ 'filter_strength' => 1,
+ 'filter_name' => 'IGNormalFilter',
+ ],
+ ];
+
+ if (isset($item['usertags'])) {
+ // NOTE: These usertags were validated in Timeline::uploadAlbum.
+ $photoConfig['usertags'] = json_encode(['in' => $item['usertags']]);
+ }
+
+ $childrenMetadata[] = $photoConfig;
+ break;
+ case 'video':
+ // Get all of the INTERNAL per-VIDEO metadata.
+ $videoDetails = $itemInternalMetadata->getVideoDetails();
+
+ // Build this item's configuration.
+ $videoConfig = [
+ 'length' => round($videoDetails->getDuration(), 1),
+ 'date_time_original' => $date,
+ 'scene_type' => 1,
+ 'poster_frame_index' => 0,
+ 'trim_type' => 0,
+ 'disable_comments' => false,
+ 'upload_id' => $uploadId,
+ 'source_type' => 'library',
+ 'geotag_enabled' => false,
+ 'edits' => [
+ 'length' => round($videoDetails->getDuration(), 1),
+ 'cinema' => 'unsupported',
+ 'original_length' => round($videoDetails->getDuration(), 1),
+ 'source_type' => 'library',
+ 'start_time' => 0,
+ 'camera_position' => 'unknown',
+ 'trim_type' => 0,
+ ],
+ ];
+
+ if (isset($item['usertags'])) {
+ // NOTE: These usertags were validated in Timeline::uploadAlbum.
+ $videoConfig['usertags'] = json_encode(['in' => $item['usertags']]);
+ }
+
+ $childrenMetadata[] = $videoConfig;
+ break;
+ }
+ }
+
+ // Build the request...
+ $request = $this->ig->request($endpoint)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('client_sidecar_id', $albumUploadId)
+ ->addPost('caption', $captionText)
+ ->addPost('children_metadata', $childrenMetadata);
+
+ if ($location instanceof Response\Model\Location) {
+ $request
+ ->addPost('location', Utils::buildMediaLocationJSON($location))
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $location->getLat())
+ ->addPost('posting_longitude', $location->getLng())
+ ->addPost('media_latitude', $location->getLat())
+ ->addPost('media_longitude', $location->getLng())
+ ->addPost('exif_latitude', 0.0)
+ ->addPost('exif_longitude', 0.0);
+ }
+
+ $configure = $request->getResponse(new Response\ConfigureResponse());
+
+ return $configure;
+ }
+
+ /**
+ * Saves active experiments.
+ *
+ * @param Response\SyncResponse $syncResponse
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _saveExperiments(
+ Response\SyncResponse $syncResponse)
+ {
+ $experiments = [];
+ foreach ($syncResponse->getExperiments() as $experiment) {
+ $group = $experiment->getName();
+ $params = $experiment->getParams();
+
+ if ($group === null || $params === null) {
+ continue;
+ }
+
+ if (!isset($experiments[$group])) {
+ $experiments[$group] = [];
+ }
+
+ foreach ($params as $param) {
+ $paramName = $param->getName();
+ if ($paramName === null) {
+ continue;
+ }
+
+ $experiments[$group][$paramName] = $param->getValue();
+ }
+ }
+
+ // Save the experiments and the last time we refreshed them.
+ $this->ig->experiments = $this->ig->settings->setExperiments($experiments);
+ $this->ig->settings->set('last_experiments', time());
+ }
+
+ /**
+ * Perform an Instagram "feature synchronization" call for device.
+ *
+ * @param bool $prelogin
+ * @param bool $useCsrfToken
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SyncResponse
+ */
+ public function syncDeviceFeatures(
+ $prelogin = false,
+ $useCsrfToken = false)
+ {
+ $request = $this->ig->request('qe/sync/')
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addPost('id', $this->ig->uuid)
+ ->addPost('experiments', Constants::LOGIN_EXPERIMENTS);
+ if ($useCsrfToken) {
+ $request->addPost('_csrftoken', $this->ig->client->getToken());
+ }
+ if ($prelogin) {
+ $request->setNeedsAuth(false);
+ } else {
+ $request
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id);
+ }
+
+ return $request->getResponse(new Response\SyncResponse());
+ }
+
+ /**
+ * Perform an Instagram "feature synchronization" call for account.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SyncResponse
+ */
+ public function syncUserFeatures()
+ {
+ $result = $this->ig->request('qe/sync/')
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('id', $this->ig->account_id)
+ ->addPost('experiments', Constants::EXPERIMENTS)
+ ->getResponse(new Response\SyncResponse());
+
+ // Save the updated experiments for this user.
+ $this->_saveExperiments($result);
+
+ return $result;
+ }
+
+ /**
+ * Send launcher sync.
+ *
+ * @param bool $prelogin Indicates if the request is done before login request.
+ * @param bool $idIsUuid Indicates if the id parameter is the user's id.
+ * @param bool $useCsrfToken Indicates if a csrf token should be included.
+ * @param bool $loginConfigs Indicates if login configs should be used.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LauncherSyncResponse
+ */
+ public function sendLauncherSync(
+ $prelogin,
+ $idIsUuid = true,
+ $useCsrfToken = false,
+ $loginConfigs = false)
+ {
+ $request = $this->ig->request('launcher/sync/')
+ ->addPost('configs', $loginConfigs ? Constants::LAUNCHER_LOGIN_CONFIGS : Constants::LAUNCHER_CONFIGS)
+ ->addPost('id', ($idIsUuid ? $this->ig->uuid : $this->ig->account_id));
+ if ($useCsrfToken) {
+ $request->addPost('_csrftoken', $this->ig->client->getToken());
+ }
+ if ($prelogin) {
+ $request->setNeedsAuth(false);
+ } else {
+ $request
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id);
+ }
+
+ return $request->getResponse(new Response\LauncherSyncResponse());
+ }
+
+ /**
+ * Get decisions about device capabilities.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CapabilitiesDecisionsResponse
+ */
+ public function getDeviceCapabilitiesDecisions()
+ {
+ return $this->ig->request('device_capabilities/decisions/')
+ ->addParam('signed_body', Signatures::generateSignature(json_encode((object) []).'.{}'))
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->getResponse(new Response\CapabilitiesDecisionsResponse());
+ }
+
+ /**
+ * Registers advertising identifier.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function logAttribution()
+ {
+ return $this->ig->request('attribution/log_attribution/')
+ ->setNeedsAuth(false)
+ ->addPost('adid', $this->ig->advertising_id)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * TODO.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function logResurrectAttribution()
+ {
+ return $this->ig->request('attribution/log_resurrect_attribution/')
+ ->addPost('adid', $this->ig->advertising_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Reads MSISDN header.
+ *
+ * @param string $usage Desired usage, either "ig_select_app" or "default".
+ * @param bool $useCsrfToken (Optional) Decides to include a csrf token in this request.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MsisdnHeaderResponse
+ */
+ public function readMsisdnHeader(
+ $usage,
+ $useCsrfToken = false)
+ {
+ $request = $this->ig->request('accounts/read_msisdn_header/')
+ ->setNeedsAuth(false)
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ // UUID is used as device_id intentionally.
+ ->addPost('device_id', $this->ig->uuid)
+ ->addPost('mobile_subno_usage', $usage);
+ if ($useCsrfToken) {
+ $request->addPost('_csrftoken', $this->ig->client->getToken());
+ }
+
+ return $request->getResponse(new Response\MsisdnHeaderResponse());
+ }
+
+ /**
+ * Bootstraps MSISDN header.
+ *
+ * @param string $usage Mobile subno usage.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MsisdnHeaderResponse
+ *
+ * @since 10.24.0 app version.
+ */
+ public function bootstrapMsisdnHeader(
+ $usage = 'ig_select_app')
+ {
+ $request = $this->ig->request('accounts/msisdn_header_bootstrap/')
+ ->setNeedsAuth(false)
+ ->addPost('mobile_subno_usage', $usage)
+ // UUID is used as device_id intentionally.
+ ->addPost('device_id', $this->ig->uuid);
+
+ return $request->getResponse(new Response\MsisdnHeaderResponse());
+ }
+
+ /**
+ * @param Response\Model\Token|null $token
+ */
+ protected function _saveZeroRatingToken(
+ Response\Model\Token $token = null)
+ {
+ if ($token === null) {
+ return;
+ }
+
+ $rules = [];
+ foreach ($token->getRewriteRules() as $rule) {
+ $rules[$rule->getMatcher()] = $rule->getReplacer();
+ }
+ $this->ig->client->zeroRating()->update($rules);
+
+ try {
+ $this->ig->settings->setRewriteRules($rules);
+ $this->ig->settings->set('zr_token', $token->getTokenHash());
+ $this->ig->settings->set('zr_expires', $token->expiresAt());
+ } catch (SettingsException $e) {
+ // Ignore storage errors.
+ }
+ }
+
+ /**
+ * Get zero rating token hash result.
+ *
+ * @param string $reason One of: "token_expired", "mqtt_token_push", "token_stale", "provisioning_time_mismatch".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TokenResultResponse
+ */
+ public function fetchZeroRatingToken(
+ $reason = 'token_expired')
+ {
+ $request = $this->ig->request('zr/token/result/')
+ ->setNeedsAuth(false)
+ ->addParam('custom_device_id', $this->ig->uuid)
+ ->addParam('device_id', $this->ig->device_id)
+ ->addParam('fetch_reason', $reason)
+ ->addParam('token_hash', (string) $this->ig->settings->get('zr_token'));
+
+ /** @var Response\TokenResultResponse $result */
+ $result = $request->getResponse(new Response\TokenResultResponse());
+ $this->_saveZeroRatingToken($result->getToken());
+
+ return $result;
+ }
+
+ /**
+ * Get megaphone log.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MegaphoneLogResponse
+ */
+ public function getMegaphoneLog()
+ {
+ return $this->ig->request('megaphone/log/')
+ ->setSignedPost(false)
+ ->addPost('type', 'feed_aysf')
+ ->addPost('action', 'seen')
+ ->addPost('reason', '')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('uuid', md5(time()))
+ ->getResponse(new Response\MegaphoneLogResponse());
+ }
+
+ /**
+ * Get hidden entities for users, places and hashtags via Facebook's algorithm.
+ *
+ * TODO: We don't know what this function does. If we ever discover that it
+ * has a useful purpose, then we should move it somewhere else.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FacebookHiddenEntitiesResponse
+ */
+ public function getFacebookHiddenSearchEntities()
+ {
+ return $this->ig->request('fbsearch/get_hidden_search_entities/')
+ ->getResponse(new Response\FacebookHiddenEntitiesResponse());
+ }
+
+ /**
+ * Get Facebook OTA (Over-The-Air) update information.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FacebookOTAResponse
+ */
+ public function getFacebookOTA()
+ {
+ return $this->ig->request('facebook_ota/')
+ ->addParam('fields', Constants::FACEBOOK_OTA_FIELDS)
+ ->addParam('custom_user_id', $this->ig->account_id)
+ ->addParam('signed_body', Signatures::generateSignature('').'.')
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->addParam('version_code', Constants::VERSION_CODE)
+ ->addParam('version_name', Constants::IG_VERSION)
+ ->addParam('custom_app_id', Constants::FACEBOOK_ORCA_APPLICATION_ID)
+ ->addParam('custom_device_id', $this->ig->uuid)
+ ->getResponse(new Response\FacebookOTAResponse());
+ }
+
+ /**
+ * Fetch profiler traces config.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoomFetchConfigResponse
+ *
+ * @see https://github.com/facebookincubator/profilo
+ */
+ public function getLoomFetchConfig()
+ {
+ return $this->ig->request('loom/fetch_config/')
+ ->getResponse(new Response\LoomFetchConfigResponse());
+ }
+
+ /**
+ * Get profile "notices".
+ *
+ * This is just for some internal state information, such as
+ * "has_change_password_megaphone". It's not for public use.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ProfileNoticeResponse
+ */
+ public function getProfileNotice()
+ {
+ return $this->ig->request('users/profile_notice/')
+ ->getResponse(new Response\ProfileNoticeResponse());
+ }
+
+ /**
+ * Fetch quick promotions data.
+ *
+ * This is used by Instagram to fetch internal promotions or changes
+ * about the platform. Latest quick promotion known was the new GDPR
+ * policy where Instagram asks you to accept new policy and accept that
+ * you have 18 years old or more.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FetchQPDataResponse
+ */
+ public function getQPFetch()
+ {
+ return $this->ig->request('qp/batch_fetch/')
+ ->addPost('vc_policy', 'default')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('surfaces_to_queries', json_encode(
+ [
+ Constants::BATCH_SURFACES[0][0] => Constants::BATCH_QUERY,
+ Constants::BATCH_SURFACES[1][0] => Constants::BATCH_QUERY,
+ Constants::BATCH_SURFACES[2][0] => Constants::BATCH_QUERY,
+ ]
+ ))
+ ->addPost('surfaces_to_triggers', json_encode(
+ [
+ Constants::BATCH_SURFACES[0][0] => Constants::BATCH_SURFACES[0][1],
+ Constants::BATCH_SURFACES[1][0] => Constants::BATCH_SURFACES[1][1],
+ Constants::BATCH_SURFACES[2][0] => Constants::BATCH_SURFACES[2][1],
+ ]
+ ))
+ ->addPost('version', Constants::BATCH_VERSION)
+ ->addPost('scale', Constants::BATCH_SCALE)
+ ->getResponse(new Response\FetchQPDataResponse());
+ }
+
+ /**
+ * Get Arlink download info.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ArlinkDownloadInfoResponse
+ */
+ public function getArlinkDownloadInfo()
+ {
+ return $this->ig->request('users/arlink_download_info/')
+ ->addParam('version_override', '2.2.1')
+ ->getResponse(new Response\ArlinkDownloadInfoResponse());
+ }
+
+ /**
+ * Get quick promotions cooldowns.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\QPCooldownsResponse
+ */
+ public function getQPCooldowns()
+ {
+ return $this->ig->request('qp/get_cooldowns/')
+ ->addParam('signed_body', Signatures::generateSignature(json_encode((object) []).'.{}'))
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->getResponse(new Response\QPCooldownsResponse());
+ }
+
+ public function storeClientPushPermissions()
+ {
+ return $this->ig->request('notifications/store_client_push_permissions/')
+ ->setSignedPost(false)
+ ->addPost('enabled', 'true')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Internal helper for marking story media items as seen.
+ *
+ * This is used by story-related functions in other request-collections!
+ *
+ * @param Response\Model\Item[] $items Array of one or more story media Items.
+ * @param string|null $sourceId Where the story was seen from,
+ * such as a location story-tray ID.
+ * If NULL, we automatically use the
+ * user's profile ID from each Item
+ * object as the source ID.
+ * @param string $module Module where the story was found.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Story::markMediaSeen()
+ * @see Location::markStoryMediaSeen()
+ * @see Hashtag::markStoryMediaSeen()
+ */
+ public function markStoryMediaSeen(
+ array $items,
+ $sourceId = null,
+ $module = 'feed_timeline')
+ {
+ // Build the list of seen media, with human randomization of seen-time.
+ $reels = [];
+ $maxSeenAt = time(); // Get current global UTC timestamp.
+ $seenAt = $maxSeenAt - (3 * count($items)); // Start seenAt in the past.
+ foreach ($items as $item) {
+ if (!$item instanceof Response\Model\Item) {
+ throw new \InvalidArgumentException(
+ 'All story items must be instances of \InstagramAPI\Response\Model\Item.'
+ );
+ }
+
+ // Raise "seenAt" if it's somehow older than the item's "takenAt".
+ // NOTE: Can only happen if you see a story instantly when posted.
+ $itemTakenAt = $item->getTakenAt();
+ if ($seenAt < $itemTakenAt) {
+ $seenAt = $itemTakenAt + 2;
+ }
+
+ // Do not let "seenAt" exceed the current global UTC time.
+ if ($seenAt > $maxSeenAt) {
+ $seenAt = $maxSeenAt;
+ }
+
+ // Determine the source ID for this item. This is where the item was
+ // seen from, such as a UserID or a Location-StoryTray ID.
+ $itemSourceId = ($sourceId === null ? $item->getUser()->getPk() : $sourceId);
+
+ // Key Format: "mediaPk_userPk_sourceId".
+ // NOTE: In case of seeing stories on a user's profile, their
+ // userPk is used as the sourceId, as "mediaPk_userPk_userPk".
+ $reelId = $item->getId().'_'.$itemSourceId;
+
+ // Value Format: ["mediaTakenAt_seenAt"] (array with single string).
+ $reels[$reelId] = [$itemTakenAt.'_'.$seenAt];
+
+ // Randomly add 1-3 seconds to next seenAt timestamp, to act human.
+ $seenAt += rand(1, 3);
+ }
+
+ return $this->ig->request('media/seen/')
+ ->setVersion(2)
+ ->setIsBodyCompressed(true)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('container_module', $module)
+ ->addPost('reels', $reels)
+ ->addPost('reel_media_skipped', [])
+ ->addPost('live_vods', [])
+ ->addPost('live_vods_skipped', [])
+ ->addPost('nuxes', [])
+ ->addPost('nuxes_skipped', [])
+// ->addParam('reel', 1)
+// ->addParam('live_vod', 0)
+ ->getResponse(new Response\MediaSeenResponse());
+ }
+
+ /**
+ * Configure media entity (album, video, ...) with retries.
+ *
+ * @param callable $configurator Configurator function.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response
+ */
+ public function configureWithRetries(
+ callable $configurator)
+ {
+ $attempt = 0;
+ $lastError = null;
+ while (true) {
+ // Check for max retry-limit, and throw if we exceeded it.
+ if (++$attempt > self::MAX_CONFIGURE_RETRIES) {
+ if ($lastError === null) {
+ throw new \RuntimeException('All configuration retries have failed.');
+ }
+
+ throw new \RuntimeException(sprintf(
+ 'All configuration retries have failed. Last error: %s',
+ $lastError
+ ));
+ }
+
+ $result = null;
+
+ try {
+ /** @var Response $result */
+ $result = $configurator();
+ } catch (ThrottledException $e) {
+ throw $e;
+ } catch (LoginRequiredException $e) {
+ throw $e;
+ } catch (FeedbackRequiredException $e) {
+ throw $e;
+ } catch (ConsentRequiredException $e) {
+ throw $e;
+ } catch (CheckpointRequiredException $e) {
+ throw $e;
+ } catch (InstagramException $e) {
+ if ($e->hasResponse()) {
+ $result = $e->getResponse();
+ }
+ $lastError = $e;
+ } catch (\Exception $e) {
+ $lastError = $e;
+ // Ignore everything else.
+ }
+
+ // We had a network error or something like that, let's continue to the next attempt.
+ if ($result === null) {
+ sleep(1);
+ continue;
+ }
+
+ $httpResponse = $result->getHttpResponse();
+ $delay = 1;
+ switch ($httpResponse->getStatusCode()) {
+ case 200:
+ // Instagram uses "ok" status for this error, so we need to check it first:
+ // {"message": "media_needs_reupload", "error_title": "staged_position_not_found", "status": "ok"}
+ if (strtolower($result->getMessage()) === 'media_needs_reupload') {
+ throw new \RuntimeException(sprintf(
+ 'You need to reupload the media (%s).',
+ // We are reading a property that isn't defined in the class
+ // property map, so we must use "has" first, to ensure it exists.
+ ($result->hasErrorTitle() && is_string($result->getErrorTitle())
+ ? $result->getErrorTitle()
+ : 'unknown error')
+ ));
+ } elseif ($result->isOk()) {
+ return $result;
+ }
+ // Continue to the next attempt.
+ break;
+ case 202:
+ // We are reading a property that isn't defined in the class
+ // property map, so we must use "has" first, to ensure it exists.
+ if ($result->hasCooldownTimeInSeconds() && $result->getCooldownTimeInSeconds() !== null) {
+ $delay = max((int) $result->getCooldownTimeInSeconds(), 1);
+ }
+ break;
+ default:
+ }
+ sleep($delay);
+ }
+
+ // We are never supposed to get here!
+ throw new \LogicException('Something went wrong during configuration.');
+ }
+
+ /**
+ * Performs a resumable upload of a media file, with support for retries.
+ *
+ * @param MediaDetails $mediaDetails
+ * @param Request $offsetTemplate
+ * @param Request $uploadTemplate
+ * @param bool $skipGet
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response\ResumableUploadResponse
+ */
+ protected function _uploadResumableMedia(
+ MediaDetails $mediaDetails,
+ Request $offsetTemplate,
+ Request $uploadTemplate,
+ $skipGet)
+ {
+ // Open file handle.
+ $handle = fopen($mediaDetails->getFilename(), 'rb');
+ if ($handle === false) {
+ throw new \RuntimeException('Failed to open media file for reading.');
+ }
+
+ try {
+ $length = $mediaDetails->getFilesize();
+
+ // Create a stream for the opened file handle.
+ $stream = new Stream($handle, ['size' => $length]);
+
+ $attempt = 0;
+ while (true) {
+ // Check for max retry-limit, and throw if we exceeded it.
+ if (++$attempt > self::MAX_RESUMABLE_RETRIES) {
+ throw new \RuntimeException('All retries have failed.');
+ }
+
+ try {
+ if ($attempt === 1 && $skipGet) {
+ // It is obvious that the first attempt is always at 0, so we can skip a request.
+ $offset = 0;
+ } else {
+ // Get current offset.
+ $offsetRequest = clone $offsetTemplate;
+ /** @var Response\ResumableOffsetResponse $offsetResponse */
+ $offsetResponse = $offsetRequest->getResponse(new Response\ResumableOffsetResponse());
+ $offset = $offsetResponse->getOffset();
+ }
+
+ // Resume upload from given offset.
+ $uploadRequest = clone $uploadTemplate;
+ $uploadRequest
+ ->addHeader('Offset', $offset)
+ ->setBody(new LimitStream($stream, $length - $offset, $offset));
+ /** @var Response\ResumableUploadResponse $response */
+ $response = $uploadRequest->getResponse(new Response\ResumableUploadResponse());
+
+ return $response;
+ } catch (ThrottledException $e) {
+ throw $e;
+ } catch (LoginRequiredException $e) {
+ throw $e;
+ } catch (FeedbackRequiredException $e) {
+ throw $e;
+ } catch (ConsentRequiredException $e) {
+ throw $e;
+ } catch (CheckpointRequiredException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Ignore everything else.
+ }
+ }
+ } finally {
+ Utils::safe_fclose($handle);
+ }
+
+ // We are never supposed to get here!
+ throw new \LogicException('Something went wrong during media upload.');
+ }
+
+ /**
+ * Performs an upload of a photo file, without support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UploadPhotoResponse
+ */
+ protected function _uploadPhotoInOnePiece(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // Prepare payload for the upload request.
+ $request = $this->ig->request('upload/photo/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addFile(
+ 'photo',
+ $internalMetadata->getPhotoDetails()->getFilename(),
+ 'pending_media_'.Utils::generateUploadId().'.jpg'
+ );
+
+ foreach ($this->_getPhotoUploadParams($targetFeed, $internalMetadata) as $key => $value) {
+ $request->addPost($key, $value);
+ }
+ /** @var Response\UploadPhotoResponse $response */
+ $response = $request->getResponse(new Response\UploadPhotoResponse());
+
+ return $response;
+ }
+
+ /**
+ * Performs a resumable upload of a photo file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ protected function _uploadResumablePhoto(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $photoDetails = $internalMetadata->getPhotoDetails();
+
+ $endpoint = sprintf('https://i.instagram.com/rupload_igphoto/%s_%d_%d',
+ $internalMetadata->getUploadId(),
+ 0,
+ Utils::hashCode($photoDetails->getFilename())
+ );
+
+ $uploadParams = $this->_getPhotoUploadParams($targetFeed, $internalMetadata);
+ $uploadParams = Utils::reorderByHashCode($uploadParams);
+
+ $offsetTemplate = new Request($this->ig, $endpoint);
+ $offsetTemplate
+ ->setAddDefaultHeaders(false)
+ ->addHeader('X_FB_PHOTO_WATERFALL_ID', Signatures::generateUUID(true))
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams));
+
+ $uploadTemplate = clone $offsetTemplate;
+ $uploadTemplate
+ ->addHeader('X-Entity-Type', 'image/jpeg')
+ ->addHeader('X-Entity-Name', basename(parse_url($endpoint, PHP_URL_PATH)))
+ ->addHeader('X-Entity-Length', $photoDetails->getFilesize());
+
+ return $this->_uploadResumableMedia(
+ $photoDetails,
+ $offsetTemplate,
+ $uploadTemplate,
+ $this->ig->isExperimentEnabled(
+ 'ig_android_skip_get_fbupload_photo_universe',
+ 'photo_skip_get'
+ )
+ );
+ }
+
+ /**
+ * Determine whether to use resumable photo uploader based on target feed and internal metadata.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return bool
+ */
+ protected function _useResumablePhotoUploader(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_sidecar_photo_fbupload_universe',
+ 'is_enabled_fbupload_sidecar_photo');
+ break;
+ default:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_photo_fbupload_universe',
+ 'is_enabled_fbupload_photo');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the first missing range (start-end) from a HTTP "Range" header.
+ *
+ * @param string $ranges
+ *
+ * @return array|null
+ */
+ protected function _getFirstMissingRange(
+ $ranges)
+ {
+ preg_match_all('/(?\d+)-(?\d+)\/(?\d+)/', $ranges, $matches, PREG_SET_ORDER);
+ if (!count($matches)) {
+ return;
+ }
+ $pairs = [];
+ $length = 0;
+ foreach ($matches as $match) {
+ $pairs[] = [$match['start'], $match['end']];
+ $length = $match['total'];
+ }
+ // Sort pairs by start.
+ usort($pairs, function (array $pair1, array $pair2) {
+ return $pair1[0] - $pair2[0];
+ });
+ $first = $pairs[0];
+ $second = count($pairs) > 1 ? $pairs[1] : null;
+ if ($first[0] == 0) {
+ $result = [$first[1] + 1, ($second === null ? $length : $second[0]) - 1];
+ } else {
+ $result = [0, $first[0] - 1];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Performs a chunked upload of a video file, with support for retries.
+ *
+ * Note that chunk uploads often get dropped when their server is overloaded
+ * at peak hours, which is why our chunk-retry mechanism exists. We will
+ * try several times to upload all chunks. The retries will only re-upload
+ * the exact chunks that have been dropped from their server, and it won't
+ * waste time with chunks that are already successfully uploaded.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UploadVideoResponse
+ */
+ protected function _uploadVideoChunks(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $videoFilename = $internalMetadata->getVideoDetails()->getFilename();
+
+ // To support video uploads to albums, we MUST fake-inject the
+ // "sessionid" cookie from "i.instagram" into our "upload.instagram"
+ // request, otherwise the server will reply with a "StagedUpload not
+ // found" error when the final chunk has been uploaded.
+ $sessionIDCookie = null;
+ if ($targetFeed === Constants::FEED_TIMELINE_ALBUM) {
+ $foundCookie = $this->ig->client->getCookie('sessionid', 'i.instagram.com');
+ if ($foundCookie !== null) {
+ $sessionIDCookie = $foundCookie->getValue();
+ }
+ if ($sessionIDCookie === null || $sessionIDCookie === '') { // Verify value.
+ throw new \RuntimeException(
+ 'Unable to find the necessary SessionID cookie for uploading video album chunks.'
+ );
+ }
+ }
+
+ // Verify the upload URLs.
+ $uploadUrls = $internalMetadata->getVideoUploadUrls();
+ if (!is_array($uploadUrls) || !count($uploadUrls)) {
+ throw new \RuntimeException('No video upload URLs found.');
+ }
+
+ // Init state.
+ $length = $internalMetadata->getVideoDetails()->getFilesize();
+ $uploadId = $internalMetadata->getUploadId();
+ $sessionId = sprintf('%s-%d', $uploadId, Utils::hashCode($videoFilename));
+ $uploadUrl = array_shift($uploadUrls);
+ $offset = 0;
+ $chunk = min($length, self::MIN_CHUNK_SIZE);
+ $attempt = 0;
+
+ // Open file handle.
+ $handle = fopen($videoFilename, 'rb');
+ if ($handle === false) {
+ throw new \RuntimeException('Failed to open file for reading.');
+ }
+
+ try {
+ // Create a stream for the opened file handle.
+ $stream = new Stream($handle);
+ while (true) {
+ // Check for this server's max retry-limit, and switch server?
+ if (++$attempt > self::MAX_CHUNK_RETRIES) {
+ $uploadUrl = null;
+ }
+
+ // Try to switch to another server.
+ if ($uploadUrl === null) {
+ $uploadUrl = array_shift($uploadUrls);
+ // Fail if there are no upload URLs left.
+ if ($uploadUrl === null) {
+ throw new \RuntimeException('There are no more upload URLs.');
+ }
+ // Reset state.
+ $attempt = 1; // As if "++$attempt" had ran once, above.
+ $offset = 0;
+ $chunk = min($length, self::MIN_CHUNK_SIZE);
+ }
+
+ // Prepare request.
+ $request = new Request($this->ig, $uploadUrl->getUrl());
+ $request
+ ->setAddDefaultHeaders(false)
+ ->addHeader('Content-Type', 'application/octet-stream')
+ ->addHeader('Session-ID', $sessionId)
+ ->addHeader('Content-Disposition', 'attachment; filename="video.mov"')
+ ->addHeader('Content-Range', 'bytes '.$offset.'-'.($offset + $chunk - 1).'/'.$length)
+ ->addHeader('job', $uploadUrl->getJob())
+ ->setBody(new LimitStream($stream, $chunk, $offset));
+
+ // When uploading videos to albums, we must fake-inject the
+ // "sessionid" cookie (the official app fake-injects it too).
+ if ($targetFeed === Constants::FEED_TIMELINE_ALBUM && $sessionIDCookie !== null) {
+ // We'll add it with the default options ("single use")
+ // so the fake cookie is only added to THIS request.
+ $this->ig->client->fakeCookies()->add('sessionid', $sessionIDCookie);
+ }
+
+ // Perform the upload of the current chunk.
+ $start = microtime(true);
+
+ try {
+ $httpResponse = $request->getHttpResponse();
+ } catch (NetworkException $e) {
+ // Ignore network exceptions.
+ continue;
+ }
+
+ // Determine new chunk size based on upload duration.
+ $newChunkSize = (int) ($chunk / (microtime(true) - $start) * 5);
+ // Ensure that the new chunk size is in valid range.
+ $newChunkSize = min(self::MAX_CHUNK_SIZE, max(self::MIN_CHUNK_SIZE, $newChunkSize));
+
+ $result = null;
+
+ try {
+ /** @var Response\UploadVideoResponse $result */
+ $result = $request->getResponse(new Response\UploadVideoResponse());
+ } catch (CheckpointRequiredException $e) {
+ throw $e;
+ } catch (LoginRequiredException $e) {
+ throw $e;
+ } catch (FeedbackRequiredException $e) {
+ throw $e;
+ } catch (ConsentRequiredException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Ignore everything else.
+ }
+
+ // Process the server response...
+ switch ($httpResponse->getStatusCode()) {
+ case 200:
+ // All chunks are uploaded, but if we don't have a
+ // response-result now then we must retry a new server.
+ if ($result === null) {
+ $uploadUrl = null;
+ break;
+ }
+
+ // SUCCESS! :-)
+ return $result;
+ case 201:
+ // The server has given us a regular reply. We expect it
+ // to be a range-reply, such as "0-3912399/23929393".
+ // Their server often drops chunks during peak hours,
+ // and in that case the first range may not start at
+ // zero, or there may be gaps or multiple ranges, such
+ // as "0-4076155/8152310,6114234-8152309/8152310". We'll
+ // handle that by re-uploading whatever they've dropped.
+ if (!$httpResponse->hasHeader('Range')) {
+ $uploadUrl = null;
+ break;
+ }
+ $range = $this->_getFirstMissingRange($httpResponse->getHeaderLine('Range'));
+ if ($range !== null) {
+ $offset = $range[0];
+ $chunk = min($newChunkSize, $range[1] - $range[0] + 1);
+ } else {
+ $chunk = min($newChunkSize, $length - $offset);
+ }
+
+ // Reset attempts count on successful upload.
+ $attempt = 0;
+ break;
+ case 400:
+ case 403:
+ case 511:
+ throw new \RuntimeException(sprintf(
+ 'Instagram\'s server returned HTTP status "%d".',
+ $httpResponse->getStatusCode()
+ ));
+ case 422:
+ throw new \RuntimeException('Instagram\'s server says that the video is corrupt.');
+ default:
+ }
+ }
+ } finally {
+ // Guaranteed to release handle even if something bad happens above!
+ Utils::safe_fclose($handle);
+ }
+
+ // We are never supposed to get here!
+ throw new \LogicException('Something went wrong during video upload.');
+ }
+
+ /**
+ * Performs a segmented upload of a video file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \Exception
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ protected function _uploadSegmentedVideo(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $videoDetails = $internalMetadata->getVideoDetails();
+
+ // We must split the video into segments before running any requests.
+ $segments = $this->_splitVideoIntoSegments($targetFeed, $videoDetails);
+
+ $uploadParams = $this->_getVideoUploadParams($targetFeed, $internalMetadata);
+ $uploadParams = Utils::reorderByHashCode($uploadParams);
+
+ // This request gives us a stream identifier.
+ $startRequest = new Request($this->ig, sprintf(
+ 'https://i.instagram.com/rupload_igvideo/%s?segmented=true&phase=start',
+ Signatures::generateUUID()
+ ));
+ $startRequest
+ ->setAddDefaultHeaders(false)
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams))
+ // Dirty hack to make a POST request.
+ ->setBody(stream_for());
+ /** @var Response\SegmentedStartResponse $startResponse */
+ $startResponse = $startRequest->getResponse(new Response\SegmentedStartResponse());
+ $streamId = $startResponse->getStreamId();
+
+ // Upload the segments.
+ try {
+ $offset = 0;
+ // Yep, no UUID here like in other resumable uploaders. Seems like a bug.
+ $waterfallId = Utils::generateUploadId();
+ foreach ($segments as $segment) {
+ $endpoint = sprintf(
+ 'https://i.instagram.com/rupload_igvideo/%s-%d-%d?segmented=true&phase=transfer',
+ md5($segment->getFilename()),
+ 0,
+ $segment->getFilesize()
+ );
+
+ $offsetTemplate = new Request($this->ig, $endpoint);
+ $offsetTemplate
+ ->setAddDefaultHeaders(false)
+ ->addHeader('Segment-Start-Offset', $offset)
+ // 1 => Audio, 2 => Video, 3 => Mixed.
+ ->addHeader('Segment-Type', $segment->getAudioCodec() !== null ? 1 : 2)
+ ->addHeader('Stream-Id', $streamId)
+ ->addHeader('X_FB_VIDEO_WATERFALL_ID', $waterfallId)
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams));
+
+ $uploadTemplate = clone $offsetTemplate;
+ $uploadTemplate
+ ->addHeader('X-Entity-Type', 'video/mp4')
+ ->addHeader('X-Entity-Name', basename(parse_url($endpoint, PHP_URL_PATH)))
+ ->addHeader('X-Entity-Length', $segment->getFilesize());
+
+ $this->_uploadResumableMedia($segment, $offsetTemplate, $uploadTemplate, false);
+ // Offset seems to be used just for ordering the segments.
+ $offset += $segment->getFilesize();
+ }
+ } finally {
+ // Remove the segments, because we don't need them anymore.
+ foreach ($segments as $segment) {
+ @unlink($segment->getFilename());
+ }
+ }
+
+ // Finalize the upload.
+ $endRequest = new Request($this->ig, sprintf(
+ 'https://i.instagram.com/rupload_igvideo/%s?segmented=true&phase=end',
+ Signatures::generateUUID()
+ ));
+ $endRequest
+ ->setAddDefaultHeaders(false)
+ ->addHeader('Stream-Id', $streamId)
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams))
+ // Dirty hack to make a POST request.
+ ->setBody(stream_for());
+ /** @var Response\GenericResponse $result */
+ $result = $endRequest->getResponse(new Response\GenericResponse());
+
+ return $result;
+ }
+
+ /**
+ * Performs a resumable upload of a video file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ protected function _uploadResumableVideo(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $rurCookie = $this->ig->client->getCookie('rur', 'i.instagram.com');
+ if ($rurCookie === null || $rurCookie->getValue() === '') {
+ throw new \RuntimeException(
+ 'Unable to find the necessary "rur" cookie for uploading video.'
+ );
+ }
+
+ $videoDetails = $internalMetadata->getVideoDetails();
+
+ $endpoint = sprintf('https://i.instagram.com/rupload_igvideo/%s_%d_%d?target=%s',
+ $internalMetadata->getUploadId(),
+ 0,
+ Utils::hashCode($videoDetails->getFilename()),
+ $rurCookie->getValue()
+ );
+
+ $uploadParams = $this->_getVideoUploadParams($targetFeed, $internalMetadata);
+ $uploadParams = Utils::reorderByHashCode($uploadParams);
+
+ $offsetTemplate = new Request($this->ig, $endpoint);
+ $offsetTemplate
+ ->setAddDefaultHeaders(false)
+ ->addHeader('X_FB_VIDEO_WATERFALL_ID', Signatures::generateUUID(true))
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams));
+
+ $uploadTemplate = clone $offsetTemplate;
+ $uploadTemplate
+ ->addHeader('X-Entity-Type', 'video/mp4')
+ ->addHeader('X-Entity-Name', basename(parse_url($endpoint, PHP_URL_PATH)))
+ ->addHeader('X-Entity-Length', $videoDetails->getFilesize());
+
+ return $this->_uploadResumableMedia(
+ $videoDetails,
+ $offsetTemplate,
+ $uploadTemplate,
+ $this->ig->isExperimentEnabled(
+ 'ig_android_skip_get_fbupload_universe',
+ 'video_skip_get'
+ )
+ );
+ }
+
+ /**
+ * Determine whether to use segmented video uploader based on target feed and internal metadata.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return bool
+ */
+ protected function _useSegmentedVideoUploader(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // No segmentation for album video.
+ if ($targetFeed === Constants::FEED_TIMELINE_ALBUM) {
+ return false;
+ }
+
+ // ffmpeg is required for video segmentation.
+ try {
+ FFmpeg::factory();
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ // There is no need to segment short videos.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $minDuration = (int) $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ // NOTE: This typo is intentional. Instagram named it that way.
+ 'segment_duration_threashold_feed',
+ 10
+ );
+ break;
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ $minDuration = (int) $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ // NOTE: This typo is intentional. Instagram named it that way.
+ 'segment_duration_threashold_story_raven',
+ 0
+ );
+ break;
+ case Constants::FEED_TV:
+ $minDuration = 150;
+ break;
+ default:
+ $minDuration = 31536000; // 1 year.
+ }
+ if ((int) $internalMetadata->getVideoDetails()->getDuration() < $minDuration) {
+ return false;
+ }
+
+ // Check experiments for the target feed.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_video_segmented_upload_universe',
+ 'segment_enabled_feed',
+ true);
+ break;
+ case Constants::FEED_DIRECT:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_direct_video_segmented_upload_universe',
+ 'is_enabled_segment_direct');
+ break;
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_reel_raven_video_segmented_upload_universe',
+ 'segment_enabled_story_raven');
+ break;
+ case Constants::FEED_TV:
+ $result = true;
+ break;
+ default:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_video_segmented_upload_universe',
+ 'segment_enabled_unknown',
+ true);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Determine whether to use resumable video uploader based on target feed and internal metadata.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return bool
+ */
+ protected function _useResumableVideoUploader(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_fbupload_sidecar_video_universe',
+ 'is_enabled_fbupload_sidecar_video');
+ break;
+ case Constants::FEED_TIMELINE:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_followers_share');
+ break;
+ case Constants::FEED_DIRECT:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_direct_share');
+ break;
+ case Constants::FEED_STORY:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_reel_share');
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_story_share');
+ break;
+ case Constants::FEED_TV:
+ $result = true;
+ break;
+ default:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_unknown');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get retry context for media upload.
+ *
+ * @return array
+ */
+ protected function _getRetryContext()
+ {
+ return [
+ // TODO increment it with every fail.
+ 'num_step_auto_retry' => 0,
+ 'num_reupload' => 0,
+ 'num_step_manual_retry' => 0,
+ ];
+ }
+
+ /**
+ * Get params for photo upload job.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return array
+ */
+ protected function _getPhotoUploadParams(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // Common params.
+ $result = [
+ 'upload_id' => (string) $internalMetadata->getUploadId(),
+ 'retry_context' => json_encode($this->_getRetryContext()),
+ 'image_compression' => '{"lib_name":"moz","lib_version":"3.1.m","quality":"87"}',
+ 'xsharing_user_ids' => json_encode([]),
+ 'media_type' => $internalMetadata->getVideoDetails() !== null
+ ? (string) Response\Model\Item::VIDEO
+ : (string) Response\Model\Item::PHOTO,
+ ];
+ // Target feed's specific params.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result['is_sidecar'] = '1';
+ break;
+ default:
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get params for video upload job.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return array
+ */
+ protected function _getVideoUploadParams(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $videoDetails = $internalMetadata->getVideoDetails();
+ // Common params.
+ $result = [
+ 'upload_id' => (string) $internalMetadata->getUploadId(),
+ 'retry_context' => json_encode($this->_getRetryContext()),
+ 'xsharing_user_ids' => json_encode([]),
+ 'upload_media_height' => (string) $videoDetails->getHeight(),
+ 'upload_media_width' => (string) $videoDetails->getWidth(),
+ 'upload_media_duration_ms' => (string) $videoDetails->getDurationInMsec(),
+ 'media_type' => (string) Response\Model\Item::VIDEO,
+ // TODO select with targetFeed (?)
+ 'potential_share_types' => json_encode(['not supported type']),
+ ];
+ // Target feed's specific params.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result['is_sidecar'] = '1';
+ break;
+ case Constants::FEED_DIRECT:
+ $result['direct_v2'] = '1';
+ $result['rotate'] = '0';
+ $result['hflip'] = 'false';
+ break;
+ case Constants::FEED_STORY:
+ $result['for_album'] = '1';
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $result['for_direct_story'] = '1';
+ break;
+ case Constants::FEED_TV:
+ $result['is_igtv_video'] = '1';
+ break;
+ default:
+ }
+
+ return $result;
+ }
+
+ /**
+ * Find the segments after ffmpeg processing.
+ *
+ * @param string $outputDirectory The directory to look in.
+ * @param string $prefix The filename prefix.
+ *
+ * @return array
+ */
+ protected function _findSegments(
+ $outputDirectory,
+ $prefix)
+ {
+ // Video segments will be uploaded before the audio one.
+ $result = glob("{$outputDirectory}/{$prefix}.video.*.mp4");
+
+ // Audio always goes into one segment, so we can use is_file() here.
+ $audioTrack = "{$outputDirectory}/{$prefix}.audio.mp4";
+ if (is_file($audioTrack)) {
+ $result[] = $audioTrack;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Split the video file into segments.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param VideoDetails $videoDetails
+ * @param FFmpeg|null $ffmpeg
+ * @param string|null $outputDirectory
+ *
+ * @throws \Exception
+ *
+ * @return VideoDetails[]
+ */
+ protected function _splitVideoIntoSegments(
+ $targetFeed,
+ VideoDetails $videoDetails,
+ FFmpeg $ffmpeg = null,
+ $outputDirectory = null)
+ {
+ if ($ffmpeg === null) {
+ $ffmpeg = FFmpeg::factory();
+ }
+ if ($outputDirectory === null) {
+ $outputDirectory = Utils::$defaultTmpPath === null ? sys_get_temp_dir() : Utils::$defaultTmpPath;
+ }
+ // Check whether the output directory is valid.
+ $targetDirectory = realpath($outputDirectory);
+ if ($targetDirectory === false || !is_dir($targetDirectory) || !is_writable($targetDirectory)) {
+ throw new \RuntimeException(sprintf(
+ 'Directory "%s" is missing or is not writable.',
+ $outputDirectory
+ ));
+ }
+
+ $prefix = sha1($videoDetails->getFilename().uniqid('', true));
+
+ try {
+ // Split the video stream into a multiple segments by time.
+ $ffmpeg->run(sprintf(
+ '-i %s -c:v copy -an -dn -sn -f segment -segment_time %d -segment_format mp4 %s',
+ Args::escape($videoDetails->getFilename()),
+ $this->_getTargetSegmentDuration($targetFeed),
+ Args::escape(sprintf(
+ '%s%s%s.video.%%03d.mp4',
+ $outputDirectory,
+ DIRECTORY_SEPARATOR,
+ $prefix
+ ))
+ ));
+
+ if ($videoDetails->getAudioCodec() !== null) {
+ // Save the audio stream in one segment.
+ $ffmpeg->run(sprintf(
+ '-i %s -c:a copy -vn -dn -sn -f mp4 %s',
+ Args::escape($videoDetails->getFilename()),
+ Args::escape(sprintf(
+ '%s%s%s.audio.mp4',
+ $outputDirectory,
+ DIRECTORY_SEPARATOR,
+ $prefix
+ ))
+ ));
+ }
+ } catch (\RuntimeException $e) {
+ // Find and remove all segments (if any).
+ $files = $this->_findSegments($outputDirectory, $prefix);
+ foreach ($files as $file) {
+ @unlink($file);
+ }
+ // Re-throw the exception.
+ throw $e;
+ }
+
+ // Collect segments.
+ $files = $this->_findSegments($outputDirectory, $prefix);
+ if (empty($files)) {
+ throw new \RuntimeException('Something went wrong while splitting the video into segments.');
+ }
+ $result = [];
+
+ try {
+ // Wrap them into VideoDetails.
+ foreach ($files as $file) {
+ $result[] = new VideoDetails($file);
+ }
+ } catch (\Exception $e) {
+ // Cleanup when something went wrong.
+ foreach ($files as $file) {
+ @unlink($file);
+ }
+
+ throw $e;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get target segment duration in seconds.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return int
+ */
+ protected function _getTargetSegmentDuration(
+ $targetFeed)
+ {
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $duration = $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ 'target_segment_duration_feed',
+ 5
+ );
+ break;
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ $duration = $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ 'target_segment_duration_story_raven',
+ 2
+ );
+ break;
+ case Constants::FEED_TV:
+ $duration = 100;
+ break;
+ default:
+ throw new \InvalidArgumentException("Unsupported feed {$targetFeed}.");
+ }
+
+ return (int) $duration;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Live.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Live.php
new file mode 100755
index 0000000..1e8e318
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Live.php
@@ -0,0 +1,696 @@
+ig->isExperimentEnabled('ig_android_live_suggested_live_expansion', 'is_enabled')) {
+ $endpoint = 'live/get_suggested_live_and_post_live/';
+ }
+
+ return $this->ig->request($endpoint)
+ ->getResponse(new Response\SuggestedBroadcastsResponse());
+ }
+
+ /**
+ * Get broadcast information.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastInfoResponse
+ */
+ public function getInfo(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/info/")
+ ->getResponse(new Response\BroadcastInfoResponse());
+ }
+
+ /**
+ * Get the viewer list of a broadcast.
+ *
+ * WARNING: You MUST be the owner of the broadcast. Otherwise Instagram won't send any API reply!
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ViewerListResponse
+ */
+ public function getViewerList(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_viewer_list/")
+ ->getResponse(new Response\ViewerListResponse());
+ }
+
+ /**
+ * Get the final viewer list of a broadcast after it has ended.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FinalViewerListResponse
+ */
+ public function getFinalViewerList(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_final_viewer_list/")
+ ->getResponse(new Response\FinalViewerListResponse());
+ }
+
+ /**
+ * Get the viewer list of a post-live (saved replay) broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PostLiveViewerListResponse
+ */
+ public function getPostLiveViewerList(
+ $broadcastId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("live/{$broadcastId}/get_post_live_viewers_list/");
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\PostLiveViewerListResponse());
+ }
+
+ /**
+ * Get a live broadcast's heartbeat and viewer count.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param bool $isViewer Indicates if this request is being ran as a viewer (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastHeartbeatAndViewerCountResponse
+ */
+ public function getHeartbeatAndViewerCount(
+ $broadcastId,
+ $isViewer = false)
+ {
+ $request = $this->ig->request("live/{$broadcastId}/heartbeat_and_get_viewer_count/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+ if ($isViewer) {
+ $request->addPost('live_with_eligibility', 1);
+ } else {
+ $request->addPost('offset_to_video_start', 0);
+ }
+
+ return $request->getResponse(new Response\BroadcastHeartbeatAndViewerCountResponse());
+ }
+
+ /**
+ * Get a live broadcast's join request counts.
+ *
+ * Note: This request **will** return null if there have been no pending
+ * join requests have been made. Please have your code check for null.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $lastTotalCount Last join request count (optional).
+ * @param int $lastSeenTs Last seen timestamp (optional).
+ * @param int $lastFetchTs Last fetch timestamp (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastJoinRequestCountResponse|null
+ */
+ public function getJoinRequestCounts(
+ $broadcastId,
+ $lastTotalCount = 0,
+ $lastSeenTs = 0,
+ $lastFetchTs = 0)
+ {
+ try {
+ return $this->ig->request("live/{$broadcastId}/get_join_request_counts/")
+ ->addParam('last_total_count', $lastTotalCount)
+ ->addParam('last_seen_ts', $lastSeenTs)
+ ->addParam('last_fetch_ts', $lastFetchTs)
+ ->getResponse(new Response\BroadcastJoinRequestCountResponse());
+ } catch (\InstagramAPI\Exception\EmptyResponseException $e) {
+ return null;
+ }
+ }
+
+ /**
+ * Show question in a live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function showQuestion(
+ $broadcastId,
+ $questionId)
+ {
+ return $this->ig->request("live/{$broadcastId}/question/{$questionId}/activate/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Hide question in a live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function hideQuestion(
+ $broadcastId,
+ $questionId)
+ {
+ return $this->ig->request("live/{$broadcastId}/question/{$questionId}/deactivate/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Asks a question to the host of the broadcast.
+ *
+ * Note: This function is only used by the viewers of a broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $questionText Your question text.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function question(
+ $broadcastId,
+ $questionText)
+ {
+ return $this->ig->request("live/{$broadcastId}/questions")
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('text', $questionText)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get all received responses from a story question.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastQuestionsResponse
+ */
+ public function getQuestions()
+ {
+ return $this->ig->request('live/get_questions/')
+ ->getResponse(new Response\BroadcastQuestionsResponse());
+ }
+
+ /**
+ * Get all received responses from the current broadcast and a story question.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastQuestionsResponse
+ */
+ public function getLiveBroadcastQuestions(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/questions/")
+ ->addParam('sources', 'story_and_live')
+ ->getResponse(new Response\BroadcastQuestionsResponse());
+ }
+
+ /**
+ * Acknowledges (waves at) a new user after they join.
+ *
+ * Note: This can only be done once to a user, per stream. Additionally, the user must have joined the stream.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $viewerId Numerical UserPK ID of the user to wave to.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function wave(
+ $broadcastId,
+ $viewerId)
+ {
+ return $this->ig->request("live/{$broadcastId}/wave/")
+ ->addPost('viewer_id', $viewerId)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Post a comment to a live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $commentText Your comment text.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentBroadcastResponse
+ */
+ public function comment(
+ $broadcastId,
+ $commentText)
+ {
+ return $this->ig->request("live/{$broadcastId}/comment/")
+ ->addPost('user_breadcrumb', Utils::generateUserBreadcrumb(mb_strlen($commentText)))
+ ->addPost('idempotence_token', Signatures::generateUUID(true))
+ ->addPost('comment_text', $commentText)
+ ->addPost('live_or_vod', 1)
+ ->addPost('offset_to_video_start', 0)
+ ->getResponse(new Response\CommentBroadcastResponse());
+ }
+
+ /**
+ * Pin a comment on live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $commentId Target comment ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PinCommentBroadcastResponse
+ */
+ public function pinComment(
+ $broadcastId,
+ $commentId)
+ {
+ return $this->ig->request("live/{$broadcastId}/pin_comment/")
+ ->addPost('offset_to_video_start', 0)
+ ->addPost('comment_id', $commentId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\PinCommentBroadcastResponse());
+ }
+
+ /**
+ * Unpin a comment on live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $commentId Pinned comment ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UnpinCommentBroadcastResponse
+ */
+ public function unpinComment(
+ $broadcastId,
+ $commentId)
+ {
+ return $this->ig->request("live/{$broadcastId}/unpin_comment/")
+ ->addPost('offset_to_video_start', 0)
+ ->addPost('comment_id', $commentId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UnpinCommentBroadcastResponse());
+ }
+
+ /**
+ * Get broadcast comments.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $lastCommentTs Last comments timestamp (optional).
+ * @param int $commentsRequested Number of comments requested (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastCommentsResponse
+ */
+ public function getComments(
+ $broadcastId,
+ $lastCommentTs = 0,
+ $commentsRequested = 3)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_comment/")
+ ->addParam('last_comment_ts', $lastCommentTs)
+// ->addParam('num_comments_requested', $commentsRequested)
+ ->getResponse(new Response\BroadcastCommentsResponse());
+ }
+
+ /**
+ * Get post-live (saved replay) broadcast comments.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $startingOffset (optional) The time-offset to start at when retrieving the comments.
+ * @param string $encodingTag (optional) TODO: ?.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PostLiveCommentsResponse
+ */
+ public function getPostLiveComments(
+ $broadcastId,
+ $startingOffset = 0,
+ $encodingTag = 'instagram_dash_remuxed')
+ {
+ return $this->ig->request("live/{$broadcastId}/get_post_live_comments/")
+ ->addParam('starting_offset', $startingOffset)
+ ->addParam('encoding_tag', $encodingTag)
+ ->getResponse(new Response\PostLiveCommentsResponse());
+ }
+
+ /**
+ * Enable viewer comments on your live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EnableDisableLiveCommentsResponse
+ */
+ public function enableComments(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/unmute_comment/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\EnableDisableLiveCommentsResponse());
+ }
+
+ /**
+ * Disable viewer comments on your live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EnableDisableLiveCommentsResponse
+ */
+ public function disableComments(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/mute_comment/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\EnableDisableLiveCommentsResponse());
+ }
+
+ /**
+ * Like a broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $likeCount Number of likes ("hearts") to send (optional).
+ * @param int $burstLikeCount Number of burst likes ("hearts") to send (optional).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastLikeResponse
+ */
+ public function like(
+ $broadcastId,
+ $likeCount = 1,
+ $burstLikeCount = 0)
+ {
+ if ($likeCount < 1 || $likeCount > 6) {
+ throw new \InvalidArgumentException('Like count must be a number from 1 to 6.');
+ }
+
+ return $this->ig->request("live/{$broadcastId}/like/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_like_count', $likeCount)
+ ->addPost('user_like_burst_count', $burstLikeCount)
+ ->addPost('offset_to_video_start', 0)
+ ->getResponse(new Response\BroadcastLikeResponse());
+ }
+
+ /**
+ * Get a live broadcast's like count.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $likeTs Like timestamp.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastLikeCountResponse
+ */
+ public function getLikeCount(
+ $broadcastId,
+ $likeTs = 0)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_like_count/")
+ ->addParam('like_ts', $likeTs)
+ ->getResponse(new Response\BroadcastLikeCountResponse());
+ }
+
+ /**
+ * Get post-live (saved replay) broadcast likes.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $startingOffset (optional) The time-offset to start at when retrieving the likes.
+ * @param string $encodingTag (optional) TODO: ?.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PostLiveLikesResponse
+ */
+ public function getPostLiveLikes(
+ $broadcastId,
+ $startingOffset = 0,
+ $encodingTag = 'instagram_dash_remuxed')
+ {
+ return $this->ig->request("live/{$broadcastId}/get_post_live_likes/")
+ ->addParam('starting_offset', $startingOffset)
+ ->addParam('encoding_tag', $encodingTag)
+ ->getResponse(new Response\PostLiveLikesResponse());
+ }
+
+ /**
+ * Create a live broadcast.
+ *
+ * Read the description of `start()` for proper usage.
+ *
+ * @param int $previewWidth (optional) Width.
+ * @param int $previewHeight (optional) Height.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateLiveResponse
+ *
+ * @see Live::start()
+ * @see Live::end()
+ */
+ public function create(
+ $previewWidth = 1080,
+ $previewHeight = 2076)
+ {
+ return $this->ig->request('live/create/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('preview_height', $previewHeight)
+ ->addPost('preview_width', $previewWidth)
+ ->addPost('broadcast_type', 'RTMP_SWAP_ENABLED')
+ ->addPost('internal_only', 0)
+ ->getResponse(new Response\CreateLiveResponse());
+ }
+
+ /**
+ * Start a live broadcast.
+ *
+ * Note that you MUST first call `create()` to get a broadcast-ID and its
+ * RTMP upload-URL. Next, simply begin sending your actual video broadcast
+ * to the stream-upload URL. And then call `start()` with the broadcast-ID
+ * to make the stream available to viewers.
+ *
+ * Also note that broadcasting to the video stream URL must be done via
+ * other software, since it ISN'T (and won't be) handled by this library!
+ *
+ * Lastly, note that stopping the stream is done either via RTMP signals,
+ * which your broadcasting software MUST output properly (FFmpeg DOESN'T do
+ * it without special patching!), OR by calling the `end()` function.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string|null $latitude (optional) Latitude.
+ * @param string|null $longitude (optional) Longitude.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StartLiveResponse
+ *
+ * @see Live::create()
+ * @see Live::end()
+ */
+ public function start(
+ $broadcastId,
+ $latitude = null,
+ $longitude = null)
+ {
+ $response = $this->ig->request("live/{$broadcastId}/start/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ if ($latitude !== null && $longitude !== null) {
+ $response->addPost('latitude', $latitude)
+ ->addPost('longitude', $longitude);
+ }
+
+ $response = $response->getResponse(new Response\StartLiveResponse());
+
+ if ($this->ig->isExperimentEnabled('ig_android_live_qa_broadcaster_v1_universe', 'is_enabled')) {
+ $this->_getQuestionStatus($broadcastId);
+ }
+
+ return $response;
+ }
+
+ /**
+ * Get question status.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ private function _getQuestionStatus(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/question_status/")
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('allow_question_submission', true)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Acknowledges a copyright warning from Instagram after detected via a heartbeat request.
+ *
+ * `NOTE:` It is recommended that you view the `liveBroadcast` example
+ * to see the proper usage of this function.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function resumeBroadcastAfterContentMatch(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/resume_broadcast_after_content_match/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * End a live broadcast.
+ *
+ * `NOTE:` To end your broadcast, you MUST use the `broadcast_id` value
+ * which was assigned to you in the `create()` response.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param bool $copyrightWarning True when broadcast is ended via a copyright notice (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ *
+ * @see Live::create()
+ * @see Live::start()
+ */
+ public function end(
+ $broadcastId,
+ $copyrightWarning = false)
+ {
+ return $this->ig->request("live/{$broadcastId}/end_broadcast/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('end_after_copyright_warning', $copyrightWarning)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Add a finished broadcast to your post-live feed (saved replay).
+ *
+ * The broadcast must have ended before you can call this function.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function addToPostLive(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/add_to_post_live/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Delete a saved post-live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function deletePostLive(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/delete_post_live/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Location.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Location.php
new file mode 100755
index 0000000..16c1d63
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Location.php
@@ -0,0 +1,329 @@
+ig->request('location_search/')
+ ->addParam('rank_token', $this->ig->account_id.'_'.Signatures::generateUUID())
+ ->addParam('latitude', $latitude)
+ ->addParam('longitude', $longitude);
+
+ if ($query === null) {
+ $locations->addParam('timestamp', time());
+ } else {
+ $locations->addParam('search_query', $query);
+ }
+
+ return $locations->getResponse(new Response\LocationResponse());
+ }
+
+ /**
+ * Search for Facebook locations by name.
+ *
+ * WARNING: The locations found by this function DO NOT work for attaching
+ * locations to media uploads. Use Location::search() instead!
+ *
+ * @param string $query Finds locations containing this string.
+ * @param string[]|int[] $excludeList Array of numerical location IDs (ie "17841562498105353")
+ * to exclude from the response, allowing you to skip locations
+ * from a previous call to get more results.
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FBLocationResponse
+ *
+ * @see FBLocationResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function findPlaces(
+ $query,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation. Do NOT use throwIfInvalidHashtag here.
+ if (!is_string($query) || $query === null) {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+ $location = $this->_paginateWithExclusion(
+ $this->ig->request('fbsearch/places/')
+ ->addParam('timezone_offset', date('Z'))
+ ->addParam('query', $query),
+ $excludeList,
+ $rankToken
+ );
+
+ try {
+ /** @var Response\FBLocationResponse $result */
+ $result = $location->getResponse(new Response\FBLocationResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\FBLocationResponse([
+ 'has_more' => false,
+ 'items' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Search for Facebook locations by geographical location.
+ *
+ * WARNING: The locations found by this function DO NOT work for attaching
+ * locations to media uploads. Use Location::search() instead!
+ *
+ * @param string $latitude Latitude.
+ * @param string $longitude Longitude.
+ * @param string|null $query (Optional) Finds locations containing this string.
+ * @param string[]|int[] $excludeList Array of numerical location IDs (ie "17841562498105353")
+ * to exclude from the response, allowing you to skip locations
+ * from a previous call to get more results.
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FBLocationResponse
+ *
+ * @see FBLocationResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function findPlacesNearby(
+ $latitude,
+ $longitude,
+ $query = null,
+ $excludeList = [],
+ $rankToken = null)
+ {
+ $location = $this->_paginateWithExclusion(
+ $this->ig->request('fbsearch/places/')
+ ->addParam('lat', $latitude)
+ ->addParam('lng', $longitude)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken,
+ 50
+ );
+
+ if ($query !== null) {
+ $location->addParam('query', $query);
+ }
+
+ try {
+ /** @var Response\FBLocationResponse() $result */
+ $result = $location->getResponse(new Response\FBLocationResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\FBLocationResponse([
+ 'has_more' => false,
+ 'items' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get related locations by location ID.
+ *
+ * Note that this endpoint almost never succeeds, because most locations do
+ * not have ANY related locations!
+ *
+ * @param string $locationId The internal ID of a location (from a field
+ * such as "pk", "external_id" or "facebook_places_id").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RelatedLocationResponse
+ */
+ public function getRelated(
+ $locationId)
+ {
+ return $this->ig->request("locations/{$locationId}/related/")
+ ->addParam('visited', json_encode(['id' => $locationId, 'type' => 'location']))
+ ->addParam('related_types', json_encode(['location']))
+ ->getResponse(new Response\RelatedLocationResponse());
+ }
+
+ /**
+ * Get the media feed for a location.
+ *
+ * Note that if your location is a "group" (such as a city), the feed will
+ * include media from multiple locations within that area. But if your
+ * location is a very specific place such as a specific night club, it will
+ * usually only include media from that exact location.
+ *
+ * @param string $locationId The internal ID of a location (from a field
+ * such as "pk", "external_id" or "facebook_places_id").
+ * @param string $rankToken The feed UUID. Use must use the same value for all pages of the feed.
+ * @param string|null $tab Section tab for locations. Values: "ranked" and "recent"
+ * @param int[]|null $nextMediaIds Used for pagination.
+ * @param int|null $nextPage Used for pagination.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LocationFeedResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFeed(
+ $locationId,
+ $rankToken,
+ $tab = 'ranked',
+ $nextMediaIds = null,
+ $nextPage = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidRankToken($rankToken);
+ if ($tab !== 'ranked' && $tab !== 'recent') {
+ throw new \InvalidArgumentException('The provided section tab is invalid.');
+ }
+
+ $locationFeed = $this->ig->request("locations/{$locationId}/sections/")
+ ->setSignedPost(false)
+ ->addPost('rank_token', $rankToken)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('session_id', $this->ig->session_id)
+ ->addPost('tab', $tab);
+
+ if ($nextMediaIds !== null) {
+ if (!is_array($nextMediaIds) || !array_filter($nextMediaIds, 'is_int')) {
+ throw new \InvalidArgumentException('Next media IDs must be an Int[].');
+ }
+ $locationFeed->addPost('next_media_ids', json_encode($nextMediaIds));
+ }
+
+ if ($nextPage !== null) {
+ $locationFeed->addPost('page', $nextPage);
+ }
+
+ if ($maxId !== null) {
+ $locationFeed->addPost('max_id', $maxId);
+ }
+
+ return $locationFeed->getResponse(new Response\LocationFeedResponse());
+ }
+
+ /**
+ * Get the story feed for a location.
+ *
+ * @param string $locationId The internal ID of a location (from a field
+ * such as "pk", "external_id" or "facebook_places_id").
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LocationStoryResponse
+ */
+ public function getStoryFeed(
+ $locationId)
+ {
+ return $this->ig->request("locations/{$locationId}/story/")
+ ->getResponse(new Response\LocationStoryResponse());
+ }
+
+ /**
+ * Mark LocationStoryResponse story media items as seen.
+ *
+ * The "story" property of a `LocationStoryResponse` only gives you a
+ * list of story media. It doesn't actually mark any stories as "seen",
+ * so the user doesn't know that you've seen their story. Actually
+ * marking the story as "seen" is done via this endpoint instead. The
+ * official app calls this endpoint periodically (with 1 or more items
+ * at a time) while watching a story.
+ *
+ * This tells the user that you've seen their story, and also helps
+ * Instagram know that it shouldn't give you those seen stories again
+ * if you request the same location feed multiple times.
+ *
+ * Tip: You can pass in the whole "getItems()" array from the location's
+ * "story" property, to easily mark all of the LocationStoryResponse's story
+ * media items as seen.
+ *
+ * @param Response\LocationStoryResponse $locationFeed The location feed
+ * response object which
+ * the story media items
+ * came from. The story
+ * items MUST belong to it.
+ * @param Response\Model\Item[] $items Array of one or more
+ * story media Items.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Story::markMediaSeen()
+ * @see Hashtag::markStoryMediaSeen()
+ */
+ public function markStoryMediaSeen(
+ Response\LocationStoryResponse $locationFeed,
+ array $items)
+ {
+ // Extract the Location Story-Tray ID from the user's location response.
+ // NOTE: This can NEVER fail if the user has properly given us the exact
+ // same location response that they got the story items from!
+ $sourceId = '';
+ if ($locationFeed->getStory() instanceof Response\Model\StoryTray) {
+ $sourceId = $locationFeed->getStory()->getId();
+ }
+ if (!strlen($sourceId)) {
+ throw new \InvalidArgumentException('Your provided LocationStoryResponse is invalid and does not contain any Location Story-Tray ID.');
+ }
+
+ // Ensure they only gave us valid items for this location response.
+ // NOTE: We validate since people cannot be trusted to use their brain.
+ $validIds = [];
+ foreach ($locationFeed->getStory()->getItems() as $item) {
+ $validIds[$item->getId()] = true;
+ }
+ foreach ($items as $item) {
+ // NOTE: We only check Items here. Other data is rejected by Internal.
+ if ($item instanceof Response\Model\Item && !isset($validIds[$item->getId()])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The item with ID "%s" does not belong to this LocationStoryResponse.',
+ $item->getId()
+ ));
+ }
+ }
+
+ // Mark the story items as seen, with the location as source ID.
+ return $this->ig->internal->markStoryMediaSeen($items, $sourceId);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Media.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Media.php
new file mode 100755
index 0000000..ee748e8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Media.php
@@ -0,0 +1,848 @@
+ig->request("media/{$mediaId}/info/")
+ ->getResponse(new Response\MediaInfoResponse());
+ }
+
+ /**
+ * Delete a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string|int $mediaType The type of the media item you are deleting. One of: "PHOTO", "VIDEO"
+ * "CAROUSEL", or the raw value of the Item's "getMediaType()" function.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaDeleteResponse
+ */
+ public function delete(
+ $mediaId,
+ $mediaType = 'PHOTO')
+ {
+ $mediaType = Utils::checkMediaType($mediaType);
+
+ return $this->ig->request("media/{$mediaId}/delete/")
+ ->addParam('media_type', $mediaType)
+ ->addPost('igtv_feed_preview', false)
+ ->addPost('media_id', $mediaId)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\MediaDeleteResponse());
+ }
+
+ /**
+ * Edit media.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $captionText Caption to use for the media.
+ * @param array|null $metadata (optional) Associative array of optional metadata to edit:
+ * "usertags" - special array with user tagging instructions,
+ * if you want to modify the user tags;
+ * "location" - a Location model object to set the media location,
+ * or boolean FALSE to remove any location from the media.
+ * @param string|int $mediaType The type of the media item you are editing. One of: "PHOTO", "VIDEO"
+ * "CAROUSEL", or the raw value of the Item's "getMediaType()" function.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditMediaResponse
+ *
+ * @see Usertag::tagMedia() for an example of proper "usertags" metadata formatting.
+ * @see Usertag::untagMedia() for an example of proper "usertags" metadata formatting.
+ */
+ public function edit(
+ $mediaId,
+ $captionText = '',
+ array $metadata = null,
+ $mediaType = 'PHOTO')
+ {
+ $mediaType = Utils::checkMediaType($mediaType);
+
+ $request = $this->ig->request("media/{$mediaId}/edit_media/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('caption_text', $captionText);
+
+ if (isset($metadata['usertags'])) {
+ Utils::throwIfInvalidUsertags($metadata['usertags']);
+ $request->addPost('usertags', json_encode($metadata['usertags']));
+ }
+
+ if (isset($metadata['location'])) {
+ if ($metadata['location'] === false) {
+ // The user wants to remove the current location from the media.
+ $request->addPost('location', '{}');
+ } else {
+ // The user wants to add/change the location of the media.
+ if (!$metadata['location'] instanceof Response\Model\Location) {
+ throw new \InvalidArgumentException('The "location" metadata value must be an instance of \InstagramAPI\Response\Model\Location.');
+ }
+
+ $request
+ ->addPost('location', Utils::buildMediaLocationJSON($metadata['location']))
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $metadata['location']->getLat())
+ ->addPost('posting_longitude', $metadata['location']->getLng())
+ ->addPost('media_latitude', $metadata['location']->getLat())
+ ->addPost('media_longitude', $metadata['location']->getLng());
+
+ if ($mediaType === 'CAROUSEL') { // Albums need special handling.
+ $request
+ ->addPost('exif_latitude', 0.0)
+ ->addPost('exif_longitude', 0.0);
+ } else { // All other types of media use "av_" instead of "exif_".
+ $request
+ ->addPost('av_latitude', 0.0)
+ ->addPost('av_longitude', 0.0);
+ }
+ }
+ }
+
+ return $request->getResponse(new Response\EditMediaResponse());
+ }
+
+ /**
+ * Like a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param int $feedPosition The position of the media in the feed.
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ * @param bool $carouselBumped (optional) If the media is carousel bumped.
+ * @param array $extraData (optional) Depending on the module name, additional data is required.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ *
+ * @see Media::_parseLikeParameters() For all supported modules and required parameters.
+ */
+ public function like(
+ $mediaId,
+ $feedPosition,
+ $module = 'feed_timeline',
+ $carouselBumped = false,
+ array $extraData = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/like/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('media_id', $mediaId)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('container_module', $module)
+ ->addPost('feed_position', $feedPosition)
+ ->addPost('is_carousel_bumped_post', $carouselBumped)
+ ->addPost('device_id', $this->ig->device_id);
+
+ if (isset($extraData['carousel_media'])) {
+ $request->addPost('carousel_index', $extraData['carousel_index']);
+ }
+
+ $extraData['media_id'] = $mediaId;
+ $this->_parseLikeParameters('like', $request, $module, $extraData);
+
+ return $request->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unlike a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ * @param array $extraData (optional) Depending on the module name, additional data is required.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ *
+ * @see Media::_parseLikeParameters() For all supported modules and required parameters.
+ */
+ public function unlike(
+ $mediaId,
+ $module = 'feed_timeline',
+ array $extraData = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/unlike/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('media_id', $mediaId)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('module_name', $module);
+
+ $this->_parseLikeParameters('unlike', $request, $module, $extraData);
+
+ return $request->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get feed of your liked media.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LikeFeedResponse
+ */
+ public function getLikedFeed(
+ $maxId = null)
+ {
+ $request = $this->ig->request('feed/liked/');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\LikeFeedResponse());
+ }
+
+ /**
+ * Get list of users who liked a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaLikersResponse
+ */
+ public function getLikers(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/likers/")->getResponse(new Response\MediaLikersResponse());
+ }
+
+ /**
+ * Get a simplified, chronological list of users who liked a media item.
+ *
+ * WARNING! DANGEROUS! Although this function works, we don't know
+ * whether it's used by Instagram's app right now. If it isn't used by
+ * the app, then you can easily get BANNED for using this function!
+ *
+ * If you call this function, you do that AT YOUR OWN RISK and you
+ * risk losing your Instagram account! This notice will be removed if
+ * the function is safe to use. Otherwise this whole function will
+ * be removed someday, if it wasn't safe.
+ *
+ * Only use this if you are OK with possibly losing your account!
+ *
+ * TODO: Research when/if the official app calls this function.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaLikersResponse
+ */
+ public function getLikersChrono(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/likers_chrono/")->getResponse(new Response\MediaLikersResponse());
+ }
+
+ /**
+ * Enable comments for a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function enableComments(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/enable_comments/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Disable comments for a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function disableComments(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/disable_comments/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Post a comment on a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentText Your comment text.
+ * @param string|null $replyCommentId (optional) The comment ID you are replying to, if this is a reply (ie "17895795823020906");
+ * when replying, your $commentText MUST contain an @-mention at the start (ie "@theirusername Hello!").
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ * "comments_v2" - In App: clicking on comments button,
+ * "self_comments_v2" - In App: commenting on your own post,
+ * "comments_v2_feed_timeline" - Unknown,
+ * "comments_v2_feed_contextual_hashtag" - Unknown,
+ * "comments_v2_photo_view_profile" - Unknown,
+ * "comments_v2_video_view_profile" - Unknown,
+ * "comments_v2_media_view_profile" - Unknown,
+ * "comments_v2_feed_contextual_location" - Unknown,
+ * "modal_comment_composer_feed_timeline" - In App: clicking on prompt from timeline.
+ * @param int $carouselIndex (optional) The image selected in a carousel while liking an image.
+ * @param int $feedPosition (optional) The position of the media in the feed.
+ * @param bool $feedBumped (optional) If Instagram bumped this post to the top of your feed.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentResponse
+ */
+ public function comment(
+ $mediaId,
+ $commentText,
+ $replyCommentId = null,
+ $module = 'comments_v2',
+ $carouselIndex = 0,
+ $feedPosition = 0,
+ $feedBumped = false)
+ {
+ $request = $this->ig->request("media/{$mediaId}/comment/")
+ ->addPost('user_breadcrumb', Utils::generateUserBreadcrumb(mb_strlen($commentText)))
+ ->addPost('idempotence_token', Signatures::generateUUID())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('comment_text', $commentText)
+ ->addPost('container_module', $module)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('carousel_index', $carouselIndex)
+ ->addPost('feed_position', $feedPosition)
+ ->addPost('is_carousel_bumped_post', $feedBumped);
+ if ($replyCommentId !== null) {
+ $request->addPost('replied_to_comment_id', $replyCommentId);
+ }
+
+ return $request->getResponse(new Response\CommentResponse());
+ }
+
+ /**
+ * Get media comments.
+ *
+ * Note that this endpoint supports both backwards and forwards pagination.
+ * The only one you should really care about is "max_id" for backwards
+ * ("load older comments") pagination in normal cases. By default, if no
+ * parameter is provided, Instagram gives you the latest page of comments
+ * and then paginates backwards via the "max_id" parameter (and the correct
+ * value for it is the "next_max_id" in the response).
+ *
+ * However, if you come to the comments "from a Push notification" (uses the
+ * "target_comment_id" parameter), then the response will ALSO contain a
+ * "next_min_id" value. In that case, you can get newer comments (than the
+ * target comment) by using THAT value and the "min_id" parameter instead.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of optional parameters, including:
+ * "max_id" - next "maximum ID" (get older comments, before this ID), used for backwards pagination;
+ * "min_id" - next "minimum ID" (get newer comments, after this ID), used for forwards pagination;
+ * "target_comment_id" - used by comment Push notifications to retrieve the page with the specific comment.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaCommentsResponse
+ */
+ public function getComments(
+ $mediaId,
+ array $options = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/comments/")
+ ->addParam('can_support_threading', true);
+
+ // Pagination.
+ if (isset($options['min_id']) && isset($options['max_id'])) {
+ throw new \InvalidArgumentException('You can use either "min_id" or "max_id", but not both at the same time.');
+ }
+ if (isset($options['min_id'])) {
+ $request->addParam('min_id', $options['min_id']);
+ }
+ if (isset($options['max_id'])) {
+ $request->addParam('max_id', $options['max_id']);
+ }
+
+ // Request specific comment (does NOT work together with pagination!).
+ // NOTE: If you try pagination params together with this param, then the
+ // server will reject the request completely and give nothing back!
+ if (isset($options['target_comment_id'])) {
+ if (isset($options['min_id']) || isset($options['max_id'])) {
+ throw new \InvalidArgumentException('You cannot use the "target_comment_id" parameter together with the "min_id" or "max_id" parameters.');
+ }
+ $request->addParam('target_comment_id', $options['target_comment_id']);
+ }
+
+ return $request->getResponse(new Response\MediaCommentsResponse());
+ }
+
+ /**
+ * Get the replies to a specific media comment.
+ *
+ * You should be sure that the comment actually HAS more replies before
+ * calling this endpoint! In that case, the comment itself will have a
+ * non-zero "child comment count" value, as well as some "preview comments".
+ *
+ * If the number of preview comments doesn't match the full "child comments"
+ * count, then you are ready to call this endpoint to retrieve the rest of
+ * them. Do NOT call it frivolously for comments that have no child comments
+ * or where you already have all of them via the child comment previews!
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentId The parent comment's ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "max_id" - next "maximum ID" (get older comments, before this ID), used for backwards pagination;
+ * "min_id" - next "minimum ID" (get newer comments, after this ID), used for forwards pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaCommentRepliesResponse
+ */
+ public function getCommentReplies(
+ $mediaId,
+ $commentId,
+ array $options = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/comments/{$commentId}/inline_child_comments/");
+
+ if (isset($options['min_id'], $options['max_id'])) {
+ throw new \InvalidArgumentException('You can use either "min_id" or "max_id", but not both at the same time.');
+ }
+
+ if (isset($options['max_id'])) {
+ $request->addParam('max_id', $options['max_id']);
+ } elseif (isset($options['min_id'])) {
+ $request->addParam('min_id', $options['min_id']);
+ }
+
+ return $request->getResponse(new Response\MediaCommentRepliesResponse());
+ }
+
+ /**
+ * Delete a comment.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentId The comment's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DeleteCommentResponse
+ */
+ public function deleteComment(
+ $mediaId,
+ $commentId)
+ {
+ return $this->ig->request("media/{$mediaId}/comment/{$commentId}/delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\DeleteCommentResponse());
+ }
+
+ /**
+ * Delete multiple comments.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string|string[] $commentIds The IDs of one or more comments to delete.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DeleteCommentResponse
+ */
+ public function deleteComments(
+ $mediaId,
+ $commentIds)
+ {
+ if (is_array($commentIds)) {
+ $commentIds = implode(',', $commentIds);
+ }
+
+ return $this->ig->request("media/{$mediaId}/comment/bulk_delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('comment_ids_to_delete', $commentIds)
+ ->getResponse(new Response\DeleteCommentResponse());
+ }
+
+ /**
+ * Like a comment.
+ *
+ * @param string $commentId The comment's ID.
+ * @param int $feedPosition The position of the media item in the feed.
+ * @param string $module From which module you're preforming this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentLikeUnlikeResponse
+ */
+ public function likeComment(
+ $commentId,
+ $feedPosition,
+ $module = 'self_comments_v2')
+ {
+ return $this->ig->request("media/{$commentId}/comment_like/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('is_carousel_bumped_post', false)
+ ->addPost('container_module', $module)
+ ->addPost('feed_position', $feedPosition)
+ ->getResponse(new Response\CommentLikeUnlikeResponse());
+ }
+
+ /**
+ * Unlike a comment.
+ *
+ * @param string $commentId The comment's ID.
+ * @param int $feedPosition The position of the media item in the feed.
+ * @param string $module From which module you're preforming this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentLikeUnlikeResponse
+ */
+ public function unlikeComment(
+ $commentId,
+ $feedPosition,
+ $module = 'self_comments_v2')
+ {
+ return $this->ig->request("media/{$commentId}/comment_unlike/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('is_carousel_bumped_post', false)
+ ->addPost('container_module', $module)
+ ->addPost('feed_position', $feedPosition)
+ ->getResponse(new Response\CommentLikeUnlikeResponse());
+ }
+
+ /**
+ * Get list of users who liked a comment.
+ *
+ * @param string $commentId The comment's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentLikersResponse
+ */
+ public function getCommentLikers(
+ $commentId)
+ {
+ return $this->ig->request("media/{$commentId}/comment_likers/")->getResponse(new Response\CommentLikersResponse());
+ }
+
+ /**
+ * Translates comments and/or media captions.
+ *
+ * Note that the text will be translated to American English (en-US).
+ *
+ * @param string|string[] $commentIds The IDs of one or more comments and/or media IDs
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TranslateResponse
+ */
+ public function translateComments(
+ $commentIds)
+ {
+ if (is_array($commentIds)) {
+ $commentIds = implode(',', $commentIds);
+ }
+
+ return $this->ig->request("language/bulk_translate/?comment_ids={$commentIds}")
+ ->getResponse(new Response\TranslateResponse());
+ }
+
+ /**
+ * Validate a web URL for acceptable use as external link.
+ *
+ * This endpoint lets you check if the URL is allowed by Instagram, and is
+ * helpful to call before you try to use a web URL in your media links.
+ *
+ * @param string $url The URL you want to validate.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ValidateURLResponse
+ */
+ public function validateURL(
+ $url)
+ {
+ return $this->ig->request('media/validate_reel_url/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('url', $url)
+ ->getResponse(new Response\ValidateURLResponse());
+ }
+
+ /**
+ * Save a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SaveAndUnsaveMedia
+ */
+ public function save(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/save/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SaveAndUnsaveMedia());
+ }
+
+ /**
+ * Unsave a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SaveAndUnsaveMedia
+ */
+ public function unsave(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/unsave/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SaveAndUnsaveMedia());
+ }
+
+ /**
+ * Get saved media items feed.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SavedFeedResponse
+ */
+ public function getSavedFeed(
+ $maxId = null)
+ {
+ $request = $this->ig->request('feed/saved/');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\SavedFeedResponse());
+ }
+
+ /**
+ * Get blocked media.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BlockedMediaResponse
+ */
+ public function getBlockedMedia()
+ {
+ return $this->ig->request('media/blocked/')
+ ->getResponse(new Response\BlockedMediaResponse());
+ }
+
+ /**
+ * Report media as spam.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $sourceName (optional) Source of the media.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function report(
+ $mediaId,
+ $sourceName = 'feed_contextual_chain')
+ {
+ return $this->ig->request("media/{$mediaId}/flag_media/")
+ ->addPost('media_id', $mediaId)
+ ->addPost('source_name', $sourceName)
+ ->addPost('reason_id', '1')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Report a media comment as spam.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentId The comment's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function reportComment(
+ $mediaId,
+ $commentId)
+ {
+ return $this->ig->request("media/{$mediaId}/comment/{$commentId}/flag/")
+ ->addPost('media_id', $mediaId)
+ ->addPost('comment_id', $commentId)
+ ->addPost('reason', '1')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get media permalink.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PermalinkResponse
+ */
+ public function getPermalink(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/permalink/")
+ ->addParam('share_to_app', 'copy_link')
+ ->getResponse(new Response\PermalinkResponse());
+ }
+
+ /**
+ * Validate and update the parameters for a like or unlike request.
+ *
+ * @param string $type What type of request this is (can be "like" or "unlike").
+ * @param Request $request The request to fill with the parsed data.
+ * @param string $module From which app module (page) you're performing this action.
+ * @param array $extraData Depending on the module name, additional data is required.
+ *
+ * @throws \InvalidArgumentException
+ */
+ protected function _parseLikeParameters(
+ $type,
+ Request $request,
+ $module,
+ array $extraData)
+ {
+ // Is this a "double-tap to like"? Note that Instagram doesn't have
+ // "double-tap to unlike". So this can only be "1" if it's a "like".
+ if ($type === 'like' && isset($extraData['double_tap']) && $extraData['double_tap']) {
+ $request->addUnsignedPost('d', 1);
+ } else {
+ $request->addUnsignedPost('d', 0); // Must always be 0 for "unlike".
+ }
+
+ // Now parse the necessary parameters for the selected module.
+ switch ($module) {
+ case 'feed_contextual_post': // "Explore" tab.
+ if (isset($extraData['explore_source_token'])) {
+ // The explore media `Item::getExploreSourceToken()` value.
+ $request->addPost('explore_source_token', $extraData['explore_source_token']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'profile': // LIST VIEW (when posts are shown vertically by the app
+ // one at a time (as in the Timeline tab)): Any media on
+ // a user profile (their timeline) in list view mode.
+ case 'media_view_profile': // GRID VIEW (standard 3x3): Album (carousel)
+ // on a user profile (their timeline).
+ case 'video_view_profile': // GRID VIEW (standard 3x3): Video on a user
+ // profile (their timeline).
+ case 'photo_view_profile': // GRID VIEW (standard 3x3): Photo on a user
+ // profile (their timeline).
+ if (isset($extraData['username']) && isset($extraData['user_id'])) {
+ // Username and id of the media's owner (the profile owner).
+ $request->addPost('username', $extraData['username'])
+ ->addPost('user_id', $extraData['user_id']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'feed_contextual_hashtag': // "Hashtag" search result.
+ if (isset($extraData['hashtag'])) {
+ // The hashtag where the app found this media.
+ Utils::throwIfInvalidHashtag($extraData['hashtag']);
+ $request->addPost('hashtag', $extraData['hashtag']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'feed_contextual_location': // "Location" search result.
+ if (isset($extraData['location_id'])) {
+ // The location ID of this media.
+ $request->addPost('location_id', $extraData['location_id']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'feed_timeline': // "Timeline" tab (the global Home-feed with all
+ // kinds of mixed news).
+ case 'newsfeed': // "Followings Activity" feed tab. Used when
+ // liking/unliking a post that we clicked on from a
+ // single-activity "xyz liked abc's post" entry.
+ case 'feed_contextual_newsfeed_multi_media_liked': // "Followings
+ // Activity" feed
+ // tab. Used when
+ // liking/unliking a
+ // post that we
+ // clicked on from a
+ // multi-activity
+ // "xyz liked 5
+ // posts" entry.
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid module name. %s does not correspond to any of the valid module names.', $module));
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Metadata/Internal.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Metadata/Internal.php
new file mode 100755
index 0000000..d56b9bc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Metadata/Internal.php
@@ -0,0 +1,291 @@
+_uploadId = $uploadId;
+ } else {
+ $this->_uploadId = Utils::generateUploadId();
+ }
+ $this->_bestieMedia = false;
+ }
+
+ /**
+ * Set story view mode.
+ *
+ * @param string $viewMode View mode. Use STORY_VIEW_MODE_ONCE and STORY_VIEW_MODE_REPLAYABLE constants as values.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ public function setStoryViewMode(
+ $viewMode)
+ {
+ if ($viewMode != Constants::STORY_VIEW_MODE_ONCE
+ && $viewMode != Constants::STORY_VIEW_MODE_REPLAYABLE
+ ) {
+ throw new \InvalidArgumentException('Unknown view mode: '.$viewMode);
+ }
+
+ $this->_storyViewMode = $viewMode;
+
+ return $this->_storyViewMode;
+ }
+
+ /**
+ * Get story view mode.
+ *
+ * @return string
+ */
+ public function getStoryViewMode()
+ {
+ return $this->_storyViewMode;
+ }
+
+ /**
+ * @return PhotoDetails
+ */
+ public function getPhotoDetails()
+ {
+ return $this->_photoDetails;
+ }
+
+ /**
+ * @return VideoDetails
+ */
+ public function getVideoDetails()
+ {
+ return $this->_videoDetails;
+ }
+
+ /**
+ * Set video details from the given filename.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $videoFilename
+ *
+ * @throws \InvalidArgumentException If the video file is missing or invalid, or Instagram won't allow this video.
+ * @throws \RuntimeException In case of various processing errors.
+ *
+ * @return VideoDetails
+ */
+ public function setVideoDetails(
+ $targetFeed,
+ $videoFilename)
+ {
+ // Figure out the video file details.
+ // NOTE: We do this first, since it validates whether the video file is
+ // valid and lets us avoid wasting time uploading totally invalid files!
+ $this->_videoDetails = new VideoDetails($videoFilename);
+
+ // Validate the video details and throw if Instagram won't allow it.
+ $this->_videoDetails->validate(ConstraintsFactory::createFor($targetFeed));
+
+ return $this->_videoDetails;
+ }
+
+ /**
+ * Set photo details from the given filename.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $photoFilename
+ *
+ * @throws \InvalidArgumentException If the photo file is missing or invalid, or Instagram won't allow this photo.
+ * @throws \RuntimeException In case of various processing errors.
+ *
+ * @return PhotoDetails
+ */
+ public function setPhotoDetails(
+ $targetFeed,
+ $photoFilename)
+ {
+ // Figure out the photo file details.
+ // NOTE: We do this first, since it validates whether the photo file is
+ // valid and lets us avoid wasting time uploading totally invalid files!
+ $this->_photoDetails = new PhotoDetails($photoFilename);
+
+ // Validate the photo details and throw if Instagram won't allow it.
+ $this->_photoDetails->validate(ConstraintsFactory::createFor($targetFeed));
+
+ return $this->_photoDetails;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUploadId()
+ {
+ return $this->_uploadId;
+ }
+
+ /**
+ * Set upload URLs from a UploadJobVideoResponse response.
+ *
+ * @param UploadJobVideoResponse $response
+ *
+ * @return VideoUploadUrl[]
+ */
+ public function setVideoUploadUrls(
+ UploadJobVideoResponse $response)
+ {
+ $this->_videoUploadUrls = [];
+ if ($response->getVideoUploadUrls() !== null) {
+ $this->_videoUploadUrls = $response->getVideoUploadUrls();
+ }
+
+ return $this->_videoUploadUrls;
+ }
+
+ /**
+ * @return VideoUploadUrl[]
+ */
+ public function getVideoUploadUrls()
+ {
+ return $this->_videoUploadUrls;
+ }
+
+ /**
+ * @return UploadVideoResponse
+ */
+ public function getVideoUploadResponse()
+ {
+ return $this->_videoUploadResponse;
+ }
+
+ /**
+ * @param UploadVideoResponse $videoUploadResponse
+ */
+ public function setVideoUploadResponse(
+ UploadVideoResponse $videoUploadResponse)
+ {
+ $this->_videoUploadResponse = $videoUploadResponse;
+ }
+
+ /**
+ * @return UploadPhotoResponse
+ */
+ public function getPhotoUploadResponse()
+ {
+ return $this->_photoUploadResponse;
+ }
+
+ /**
+ * @param UploadPhotoResponse $photoUploadResponse
+ */
+ public function setPhotoUploadResponse(
+ UploadPhotoResponse $photoUploadResponse)
+ {
+ $this->_photoUploadResponse = $photoUploadResponse;
+ }
+
+ /**
+ * Add Direct recipients to metadata.
+ *
+ * @param array $recipients
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function setDirectRecipients(
+ array $recipients)
+ {
+ if (isset($recipients['users'])) {
+ $this->_directUsers = $recipients['users'];
+ $this->_directThreads = '[]';
+ } elseif (isset($recipients['thread'])) {
+ $this->_directUsers = '[]';
+ $this->_directThreads = $recipients['thread'];
+ } else {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDirectThreads()
+ {
+ return $this->_directThreads;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDirectUsers()
+ {
+ return $this->_directUsers;
+ }
+
+ /**
+ * Set bestie media state.
+ *
+ * @param bool $bestieMedia
+ */
+ public function setBestieMedia(
+ $bestieMedia)
+ {
+ $this->_bestieMedia = $bestieMedia;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isBestieMedia()
+ {
+ return $this->_bestieMedia;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/People.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/People.php
new file mode 100755
index 0000000..fc1aa24
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/People.php
@@ -0,0 +1,1265 @@
+ig->request("users/{$userId}/info/");
+ if ($module !== null) {
+ $request->addParam('from_module', $module);
+ }
+
+ return $request->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Get details about a specific user via their username.
+ *
+ * NOTE: The real app only uses this endpoint for profiles opened via "@mentions".
+ *
+ * @param string $username Username as string (NOT as a numerical ID).
+ * @param string $module From which app module (page) you have opened the profile.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see People::getInfoById() For the list of supported modules.
+ */
+ public function getInfoByName(
+ $username,
+ $module = 'feed_timeline')
+ {
+ return $this->ig->request("users/{$username}/usernameinfo/")
+ ->addParam('from_module', $module)
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Get the numerical UserPK ID for a specific user via their username.
+ *
+ * This is just a convenient helper function. You may prefer to use
+ * People::getInfoByName() instead, which lets you see more details.
+ *
+ * @param string $username Username as string (NOT as a numerical ID).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return string Their numerical UserPK ID.
+ *
+ * @see People::getInfoByName()
+ */
+ public function getUserIdForName(
+ $username)
+ {
+ return $this->getInfoByName($username)->getUser()->getPk();
+ }
+
+ /**
+ * Get user details about your own account.
+ *
+ * Also try Account::getCurrentUser() instead, for account details.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see Account::getCurrentUser()
+ */
+ public function getSelfInfo()
+ {
+ return $this->getInfoById($this->ig->account_id);
+ }
+
+ /**
+ * Get other people's recent activities related to you and your posts.
+ *
+ * This feed has information about when people interact with you, such as
+ * liking your posts, commenting on your posts, tagging you in photos or in
+ * comments, people who started following you, etc.
+ *
+ * @param bool $prefetch Indicates if request is called due to prefetch.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ActivityNewsResponse
+ */
+ public function getRecentActivityInbox(
+ $prefetch = false)
+ {
+ $request = $this->ig->request('news/inbox/');
+ if ($prefetch) {
+ $request->addHeader('X-IG-Prefetch-Request', 'foreground');
+ }
+
+ return $request->getResponse(new Response\ActivityNewsResponse());
+ }
+
+ /**
+ * Get news feed with recent activities by accounts you follow.
+ *
+ * This feed has information about the people you follow, such as what posts
+ * they've liked or that they've started following other people.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowingRecentActivityResponse
+ */
+ public function getFollowingRecentActivity(
+ $maxId = null)
+ {
+ $activity = $this->ig->request('news/');
+ if ($maxId !== null) {
+ $activity->addParam('max_id', $maxId);
+ }
+
+ return $activity->getResponse(new Response\FollowingRecentActivityResponse());
+ }
+
+ /**
+ * Retrieve bootstrap user data (autocompletion user list).
+ *
+ * WARNING: This is a special, very heavily throttled API endpoint.
+ * Instagram REQUIRES that you wait several minutes between calls to it.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BootstrapUsersResponse|null Will be NULL if throttled by Instagram.
+ */
+ public function getBootstrapUsers()
+ {
+ $surfaces = [
+ 'autocomplete_user_list',
+ 'coefficient_besties_list_ranking',
+ 'coefficient_rank_recipient_user_suggestion',
+ 'coefficient_ios_section_test_bootstrap_ranking',
+ 'coefficient_direct_recipients_ranking_variant_2',
+ ];
+
+ try {
+ $request = $this->ig->request('scores/bootstrap/users/')
+ ->addParam('surfaces', json_encode($surfaces));
+
+ return $request->getResponse(new Response\BootstrapUsersResponse());
+ } catch (ThrottledException $e) {
+ // Throttling is so common that we'll simply return NULL in that case.
+ return null;
+ }
+ }
+
+ /**
+ * Show a user's friendship status with you.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipsShowResponse
+ */
+ public function getFriendship(
+ $userId)
+ {
+ return $this->ig->request("friendships/show/{$userId}/")->getResponse(new Response\FriendshipsShowResponse());
+ }
+
+ /**
+ * Show multiple users' friendship status with you.
+ *
+ * @param string|string[] $userList List of numerical UserPK IDs.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipsShowManyResponse
+ */
+ public function getFriendships(
+ $userList)
+ {
+ if (is_array($userList)) {
+ $userList = implode(',', $userList);
+ }
+
+ return $this->ig->request('friendships/show_many/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('user_ids', $userList)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\FriendshipsShowManyResponse());
+ }
+
+ /**
+ * Get list of pending friendship requests.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ */
+ public function getPendingFriendships()
+ {
+ $request = $this->ig->request('friendships/pending/');
+
+ return $request->getResponse(new Response\FollowerAndFollowingResponse());
+ }
+
+ /**
+ * Approve a friendship request.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function approveFriendship(
+ $userId)
+ {
+ return $this->ig->request("friendships/approve/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Reject a friendship request.
+ *
+ * Note that the user can simply send you a new request again, after your
+ * rejection. If they're harassing you, use People::block() instead.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function rejectFriendship(
+ $userId)
+ {
+ return $this->ig->request("friendships/ignore/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Remove one of your followers.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function removeFollower(
+ $userId)
+ {
+ return $this->ig->request("friendships/remove_follower/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Mark user over age in order to see sensitive content.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function markUserOverage(
+ $userId)
+ {
+ return $this->ig->request("friendships/mark_user_overage/{$userId}/feed/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get list of who a user is following.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFollowing(
+ $userId,
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidRankToken($rankToken);
+ $request = $this->ig->request("friendships/{$userId}/following/")
+ ->addParam('includes_hashtags', true)
+ ->addParam('rank_token', $rankToken);
+ if ($searchQuery !== null) {
+ $request->addParam('query', $searchQuery);
+ }
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\FollowerAndFollowingResponse());
+ }
+
+ /**
+ * Get list of who a user is followed by.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFollowers(
+ $userId,
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidRankToken($rankToken);
+ $request = $this->ig->request("friendships/{$userId}/followers/")
+ ->addParam('rank_token', $rankToken);
+ if ($searchQuery !== null) {
+ $request->addParam('query', $searchQuery);
+ }
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\FollowerAndFollowingResponse());
+ }
+
+ /**
+ * Get list of who you are following.
+ *
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getSelfFollowing(
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ return $this->getFollowing($this->ig->account_id, $rankToken, $searchQuery, $maxId);
+ }
+
+ /**
+ * Get list of your own followers.
+ *
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getSelfFollowers(
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ return $this->getFollowers($this->ig->account_id, $rankToken, $searchQuery, $maxId);
+ }
+
+ /**
+ * Search for Instagram users.
+ *
+ * @param string $query The username or full name to search for.
+ * @param string[]|int[] $excludeList Array of numerical user IDs (ie "4021088339")
+ * to exclude from the response, allowing you to skip users
+ * from a previous call to get more results.
+ * @param string|null $rankToken A rank token from a first call response.
+ *
+ * @throws \InvalidArgumentException If invalid query or
+ * trying to exclude too
+ * many user IDs.
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SearchUserResponse
+ *
+ * @see SearchUserResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function search(
+ $query,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation.
+ if (!is_string($query) || $query === '') {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+
+ $request = $this->_paginateWithExclusion(
+ $this->ig->request('users/search/')
+ ->addParam('q', $query)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken
+ );
+
+ try {
+ /** @var Response\SearchUserResponse $result */
+ $result = $request->getResponse(new Response\SearchUserResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\SearchUserResponse([
+ 'has_more' => false,
+ 'num_results' => 0,
+ 'users' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get business account details.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\AccountDetailsResponse
+ */
+ public function getAccountDetails(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/account_details/")
+ ->getResponse(new Response\AccountDetailsResponse());
+ }
+
+ /**
+ * Get a business account's former username(s).
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FormerUsernamesResponse
+ */
+ public function getFormerUsernames(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/former_usernames/")
+ ->getResponse(new Response\FormerUsernamesResponse());
+ }
+
+ /**
+ * Get a business account's shared follower base with similar accounts.
+ *
+ * @param string $userId Numerical UserPk ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SharedFollowersResponse
+ */
+ public function getSharedFollowers(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/shared_follower_accounts/")
+ ->getResponse(new Response\SharedFollowersResponse());
+ }
+
+ /**
+ * Get a business account's active ads on feed.
+ *
+ * @param string $targetUserId Numerical UserPk ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ActiveFeedAdsResponse
+ */
+ public function getActiveFeedAds(
+ $targetUserId,
+ $maxId = null)
+ {
+ return $this->_getActiveAds($targetUserId, '35', $maxId);
+ }
+
+ /**
+ * Get a business account's active ads on stories.
+ *
+ * @param string $targetUserId Numerical UserPk ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ActiveReelAdsResponse
+ */
+ public function getActiveStoryAds(
+ $targetUserId,
+ $maxId = null)
+ {
+ return $this->_getActiveAds($targetUserId, '49', $maxId);
+ }
+
+ /**
+ * Helper function for getting active ads for business accounts.
+ *
+ * @param string $targetUserId Numerical UserPk ID.
+ * @param string $pageType Content-type id(?) of the ad. 35 is feed ads and 49 is story ads.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response
+ */
+ protected function _getActiveAds(
+ $targetUserId,
+ $pageType,
+ $maxId = null)
+ {
+ $request = $this->ig->request('ads/view_ads/')
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('target_user_id', $targetUserId)
+ ->addPost('page_type', $pageType);
+ if ($maxId !== null) {
+ $request->addPost('next_max_id', $maxId);
+ }
+ $request->addPost('ig_user_id', $this->ig->account_id);
+
+ switch ($pageType) {
+ case '35':
+ return $request->getResponse(new Response\ActiveFeedAdsResponse());
+ break;
+ case '49':
+ return $request->getResponse(new Response\ActiveReelAdsResponse());
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid page type.');
+ }
+ }
+
+ /**
+ * Search for users by linking your address book to Instagram.
+ *
+ * WARNING: You must unlink your current address book before you can link
+ * another one to search again, otherwise you will just keep getting the
+ * same response about your currently linked address book every time!
+ *
+ * @param array $contacts
+ * @param string $module
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LinkAddressBookResponse
+ *
+ * @see People::unlinkAddressBook()
+ */
+ public function linkAddressBook(
+ array $contacts,
+ $module = 'find_friends_contacts')
+ {
+ return $this->ig->request('address_book/link/')
+ ->setIsBodyCompressed(true)
+ ->setSignedPost(false)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('module', $module)
+ ->addPost('contacts', json_encode($contacts))
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\LinkAddressBookResponse());
+ }
+
+ /**
+ * Unlink your address book from Instagram.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UnlinkAddressBookResponse
+ */
+ public function unlinkAddressBook()
+ {
+ return $this->ig->request('address_book/unlink/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UnlinkAddressBookResponse());
+ }
+
+ /**
+ * Discover new people via Facebook's algorithm.
+ *
+ * This matches you with other people using multiple algorithms such as
+ * "friends of friends", "location", "people using similar hashtags", etc.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DiscoverPeopleResponse
+ */
+ public function discoverPeople(
+ $maxId = null)
+ {
+ $request = $this->ig->request('discover/ayml/')
+ ->setSignedPost(false)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('module', 'discover_people')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('paginate', true);
+
+ if ($maxId !== null) {
+ $request->addPost('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\DiscoverPeopleResponse());
+ }
+
+ /**
+ * Get suggested users related to a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedUsersResponse
+ */
+ public function getSuggestedUsers(
+ $userId)
+ {
+ return $this->ig->request('discover/chaining/')
+ ->addParam('target_id', $userId)
+ ->getResponse(new Response\SuggestedUsersResponse());
+ }
+
+ /**
+ * Get suggested users via account badge.
+ *
+ * This is the endpoint for when you press the "user icon with the plus
+ * sign" on your own profile in the Instagram app. Its amount of suggestions
+ * matches the number on the badge, and it usually only has a handful (1-4).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedUsersBadgeResponse
+ */
+ public function getSuggestedUsersBadge()
+ {
+ return $this->ig->request('discover/profile_su_badge/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('module', 'discover_people')
+ ->getResponse(new Response\SuggestedUsersBadgeResponse());
+ }
+
+ /**
+ * Hide suggested user, so that they won't be suggested again.
+ *
+ * You must provide the correct algorithm for the user you want to hide,
+ * which can be seen in their "algorithm" value in People::discoverPeople().
+ *
+ * Here is a probably-outdated list of algorithms and their meanings:
+ *
+ * - realtime_chaining_algorithm = ?
+ * - realtime_chaining_ig_coeff_algorithm = ?
+ * - tfidf_city_algorithm = Popular people near you.
+ * - hashtag_interest_algorithm = Popular people on similar hashtags as you.
+ * - second_order_followers_algorithm = Popular.
+ * - super_users_algorithm = Popular.
+ * - followers_algorithm = Follows you.
+ * - ig_friends_of_friends_from_tao_laser_algorithm = ?
+ * - page_rank_algorithm = ?
+ *
+ * TODO: Do more research about this function and document it properly.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $algorithm Which algorithm to hide the suggestion from;
+ * must match that user's "algorithm" value in
+ * functions like People::discoverPeople().
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedUsersResponse
+ */
+ public function hideSuggestedUser(
+ $userId,
+ $algorithm)
+ {
+ return $this->ig->request('discover/aysf_dismiss/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addParam('target_id', $userId)
+ ->addParam('algorithm', $algorithm)
+ ->getResponse(new Response\SuggestedUsersResponse());
+ }
+
+ /**
+ * Follow a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function follow(
+ $userId,
+ $mediaId = null)
+ {
+ $request = $this->ig->request("friendships/create/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('device_id', $this->ig->device_id);
+
+ if ($mediaId !== null) {
+ $request->addPost('media_id_attribution', $mediaId);
+ }
+
+ return $request->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unfollow a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function unfollow(
+ $userId,
+ $mediaId = null)
+ {
+ $request = $this->ig->request("friendships/destroy/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none');
+
+ if ($mediaId !== null) {
+ $request->addPost('media_id_attribution', $mediaId);
+ }
+
+ return $request->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Enable high priority for a user you are following.
+ *
+ * When you mark someone as favorite, you will receive app push
+ * notifications when that user uploads media, and their shared
+ * media will get higher visibility. For instance, their stories
+ * will be placed at the front of your reels-tray, and their
+ * timeline posts will stay visible for longer on your homescreen.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function favorite(
+ $userId)
+ {
+ return $this->ig->request("friendships/favorite/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Disable high priority for a user you are following.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unfavorite(
+ $userId)
+ {
+ return $this->ig->request("friendships/unfavorite/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Turn on story notifications.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function favoriteForStories(
+ $userId)
+ {
+ return $this->ig->request("friendships/favorite_for_stories/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Turn off story notifications.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unfavoriteForStories(
+ $userId)
+ {
+ return $this->ig->request("friendships/unfavorite_for_stories/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Report a user as spam.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $sourceName (optional) Source app-module of the report.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function report(
+ $userId,
+ $sourceName = 'profile')
+ {
+ return $this->ig->request("users/{$userId}/flag_user/")
+ ->addPost('reason_id', 1)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('source_name', $sourceName)
+ ->addPost('is_spam', true)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Block a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function block(
+ $userId)
+ {
+ return $this->ig->request("friendships/block/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Mute stories, posts or both from a user.
+ *
+ * It prevents user media from showing up in the timeline and/or story feed.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $option Selection of what type of media are going to be muted.
+ * Available options: 'story', 'post' or 'all'.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function muteUserMedia(
+ $userId,
+ $option)
+ {
+ return $this->_muteOrUnmuteUserMedia($userId, $option, 'friendships/mute_posts_or_story_from_follow/');
+ }
+
+ /**
+ * Unmute stories, posts or both from a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $option Selection of what type of media are going to be muted.
+ * Available options: 'story', 'post' or 'all'.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function unmuteUserMedia(
+ $userId,
+ $option)
+ {
+ return $this->_muteOrUnmuteUserMedia($userId, $option, 'friendships/unmute_posts_or_story_from_follow/');
+ }
+
+ /**
+ * Helper function to mute user media.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $option Selection of what type of media are going to be muted.
+ * Available options: 'story', 'post' or 'all'.
+ * @param string $endpoint API endpoint for muting/unmuting user media.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::muteUserMedia()
+ * @see People::unmuteUserMedia()
+ */
+ protected function _muteOrUnmuteUserMedia(
+ $userId,
+ $option,
+ $endpoint)
+ {
+ $request = $this->ig->request($endpoint)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ switch ($option) {
+ case 'story':
+ $request->addPost('target_reel_author_id', $userId);
+ break;
+ case 'post':
+ $request->addPost('target_posts_author_id', $userId);
+ break;
+ case 'all':
+ $request->addPost('target_reel_author_id', $userId);
+ $request->addPost('target_posts_author_id', $userId);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid muting option.', $option));
+ }
+
+ return $request->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unblock a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function unblock(
+ $userId)
+ {
+ return $this->ig->request("friendships/unblock/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get a list of all blocked users.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BlockedListResponse
+ */
+ public function getBlockedList(
+ $maxId = null)
+ {
+ $request = $this->ig->request('users/blocked_list/');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\BlockedListResponse());
+ }
+
+ /**
+ * Block a user's ability to see your stories.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::muteFriendStory()
+ */
+ public function blockMyStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/block_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('source', 'profile')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unblock a user so that they can see your stories again.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::unmuteFriendStory()
+ */
+ public function unblockMyStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/unblock_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('source', 'profile')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get the list of users who are blocked from seeing your stories.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BlockedReelsResponse
+ */
+ public function getBlockedStoryList()
+ {
+ return $this->ig->request('friendships/blocked_reels/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\BlockedReelsResponse());
+ }
+
+ /**
+ * Mute a friend's stories, so that you no longer see their stories.
+ *
+ * This hides them from your reels tray (the "latest stories" bar on the
+ * homescreen of the app), but it does not block them from seeing *your*
+ * stories.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::blockMyStory()
+ */
+ public function muteFriendStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/mute_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unmute a friend's stories, so that you see their stories again.
+ *
+ * This does not unblock their ability to see *your* stories.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::unblockMyStory()
+ */
+ public function unmuteFriendStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/unmute_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get the list of users on your close friends list.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CloseFriendsResponse
+ */
+ public function getCloseFriends()
+ {
+ return $this->ig->request('friendships/besties/')
+ ->getResponse(new Response\CloseFriendsResponse());
+ }
+
+ /**
+ * Get the list of suggested users for your close friends list.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CloseFriendsResponse
+ */
+ public function getSuggestedCloseFriends()
+ {
+ return $this->ig->request('friendships/bestie_suggestions/')
+ ->getResponse(new Response\CloseFriendsResponse());
+ }
+
+ /**
+ * Add or Remove users from your close friends list.
+ *
+ * Note: You probably shouldn't touch $module and $source as there is only one way to modify your close friends.
+ *
+ * @param array $add Users to add to your close friends list.
+ * @param array $remove Users to remove from your close friends list.
+ * @param string $module (optional) From which app module (page) you have change your close friends list.
+ * @param string $source (optional) Source page of app-module of where you changed your close friends list.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function setCloseFriends(
+ array $add,
+ array $remove,
+ $module = 'favorites_home_list',
+ $source = 'audience_manager')
+ {
+ return $this->ig->request('friendships/set_besties/')
+ ->setSignedPost(true)
+ ->addPost('module', $module)
+ ->addPost('source', $source)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('remove', $remove)
+ ->addPost('add', $add)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Gets a list of ranked users to display in Android's share UI.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SharePrefillResponse
+ */
+ public function getSharePrefill()
+ {
+ return $this->ig->request('banyan/banyan/')
+ ->addParam('views', '["story_share_sheet","threads_people_picker","reshare_share_sheet"]')
+ ->getResponse(new Response\SharePrefillResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Push.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Push.php
new file mode 100755
index 0000000..16b5bcd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Push.php
@@ -0,0 +1,78 @@
+ig->request('push/register/')
+ ->setSignedPost(false)
+ ->addPost('device_type', $pushChannel === 'mqtt' ? 'android_mqtt' : 'android_fcm')
+ ->addPost('is_main_push_channel', $pushChannel === 'mqtt')
+ ->addPost('device_token', $token)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('guid', $this->ig->uuid)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('device_sub_type', '2')
+ ->addPost('users', $this->ig->account_id);
+
+ return $request->getResponse(new Response\PushRegisterResponse());
+ }
+
+ /**
+ * Get push preferences.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PushPreferencesResponse
+ */
+ public function getPreferences()
+ {
+ return $this->ig->request('push/all_preferences/')
+ ->getResponse(new Response\PushPreferencesResponse());
+ }
+
+ /**
+ * Set push preferences.
+ *
+ * @param array $preferences Described in "extradocs/Push_setPreferences.txt".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PushPreferencesResponse
+ */
+ public function setPreferences(
+ array $preferences)
+ {
+ $request = $this->ig->request('push/preferences/');
+ foreach ($preferences as $key => $value) {
+ $request->addPost($key, $value);
+ }
+
+ return $request->getResponse(new Response\PushPreferencesResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/RequestCollection.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/RequestCollection.php
new file mode 100755
index 0000000..896f09f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/RequestCollection.php
@@ -0,0 +1,104 @@
+ig = $parent;
+ }
+
+ /**
+ * Paginate the request by given exclusion list.
+ *
+ * @param Request $request The request to paginate.
+ * @param array $excludeList Array of numerical entity IDs (ie "4021088339")
+ * to exclude from the response, allowing you to skip entities
+ * from a previous call to get more results.
+ * @param string|null $rankToken The rank token from the previous page's response.
+ * @param int $limit Limit the number of results per page.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Request
+ */
+ protected function _paginateWithExclusion(
+ Request $request,
+ array $excludeList = [],
+ $rankToken = null,
+ $limit = 30)
+ {
+ if (!count($excludeList)) {
+ return $request->addParam('count', (string) $limit);
+ }
+
+ if ($rankToken === null) {
+ throw new \InvalidArgumentException('You must supply the rank token for the pagination.');
+ }
+ Utils::throwIfInvalidRankToken($rankToken);
+
+ return $request
+ ->addParam('count', (string) $limit)
+ ->addParam('exclude_list', '['.implode(', ', $excludeList).']')
+ ->addParam('rank_token', $rankToken);
+ }
+
+ /**
+ * Paginate the request by given multi-exclusion list.
+ *
+ * @param Request $request The request to paginate.
+ * @param array $excludeList Array of grouped numerical entity IDs (ie "users" => ["4021088339"])
+ * to exclude from the response, allowing you to skip entities
+ * from a previous call to get more results.
+ * @param string|null $rankToken The rank token from the previous page's response.
+ * @param int $limit Limit the number of results per page.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Request
+ */
+ protected function _paginateWithMultiExclusion(
+ Request $request,
+ array $excludeList = [],
+ $rankToken = null,
+ $limit = 30)
+ {
+ if (!count($excludeList)) {
+ return $request->addParam('count', (string) $limit);
+ }
+
+ if ($rankToken === null) {
+ throw new \InvalidArgumentException('You must supply the rank token for the pagination.');
+ }
+ Utils::throwIfInvalidRankToken($rankToken);
+
+ $exclude = [];
+ $totalCount = 0;
+ foreach ($excludeList as $group => $ids) {
+ $totalCount += count($ids);
+ $exclude[] = "\"{$group}\":[".implode(', ', $ids).']';
+ }
+
+ return $request
+ ->addParam('count', (string) $limit)
+ ->addParam('exclude_list', '{'.implode(',', $exclude).'}')
+ ->addParam('rank_token', $rankToken);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Shopping.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Shopping.php
new file mode 100755
index 0000000..3bd97be
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Shopping.php
@@ -0,0 +1,123 @@
+ig->request("commerce/products/{$productId}/details/")
+ ->addParam('source_media_id', $mediaId)
+ ->addParam('merchant_id', $merchantId)
+ ->addParam('device_width', $deviceWidth)
+ ->addParam('hero_carousel_enabled', true)
+ ->getResponse(new Response\OnTagProductResponse());
+ }
+
+ /**
+ * Get catalogs.
+ *
+ * @param string $locale The device user's locale, such as "en_US.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GraphqlResponse
+ */
+ public function getCatalogs(
+ $locale = 'en_US')
+ {
+ return $this->ig->request('wwwgraphql/ig/query/')
+ ->addParam('locale', $locale)
+ ->addUnsignedPost('access_token', 'undefined')
+ ->addUnsignedPost('fb_api_caller_class', 'RelayModern')
+ ->addUnsignedPost('variables', ['sources' => null])
+ ->addUnsignedPost('doc_id', '1742970149122229')
+ ->getResponse(new Response\GraphqlResponse());
+ }
+
+ /**
+ * Get catalog items.
+ *
+ * @param string $catalogId The catalog's ID.
+ * @param string $query Finds products containing this string.
+ * @param int $offset Offset, used for pagination. Values must be multiples of 20.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GraphqlResponse
+ */
+ public function getCatalogItems(
+ $catalogId,
+ $query = '',
+ $offset = null)
+ {
+ if ($offset !== null) {
+ if ($offset % 20 !== 0) {
+ throw new \InvalidArgumentException('Offset must be multiple of 20.');
+ }
+ $offset = [
+ 'offset' => $offset,
+ 'tier' => 'products.elasticsearch.thrift.atn',
+ ];
+ }
+
+ $queryParams = [
+ $query,
+ $catalogId,
+ '96',
+ '20',
+ json_encode($offset),
+ ];
+
+ return $this->ig->request('wwwgraphql/ig/query/')
+ ->addUnsignedPost('doc_id', '1747750168640998')
+ ->addUnsignedPost('locale', Constants::ACCEPT_LANGUAGE)
+ ->addUnsignedPost('vc_policy', 'default')
+ ->addUnsignedPost('strip_nulls', true)
+ ->addUnsignedPost('strip_defaults', true)
+ ->addUnsignedPost('query_params', json_encode($queryParams, JSON_FORCE_OBJECT))
+ ->getResponse(new Response\GraphqlResponse());
+ }
+
+ /**
+ * Sets on board catalog.
+ *
+ * @param string $catalogId The catalog's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\OnBoardCatalogResponse
+ */
+ public function setOnBoardCatalog(
+ $catalogId)
+ {
+ return $this->ig->request('commerce/onboard/')
+ ->addPost('current_catalog_id', $catalogId)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\OnBoardCatalogResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Story.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Story.php
new file mode 100755
index 0000000..d8f79c9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Story.php
@@ -0,0 +1,693 @@
+ig->internal->uploadSinglePhoto(Constants::FEED_STORY, $photoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads a photo to your Instagram close friends story.
+ *
+ * @param string $photoFilename The photo filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSinglePhoto() for available metadata fields.
+ * @see https://help.instagram.com/2183694401643300
+ */
+ public function uploadCloseFriendsPhoto(
+ $photoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata(Utils::generateUploadId(true));
+ $internalMetadata->setBestieMedia(true);
+
+ return $this->ig->internal->uploadSinglePhoto(Constants::FEED_STORY, $photoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Uploads a video to your Instagram story.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_STORY, $videoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads a video to your Instagram close friends story.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ * @see https://help.instagram.com/2183694401643300
+ */
+ public function uploadCloseFriendsVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setBestieMedia(true);
+
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_STORY, $videoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Get the global story feed which contains everyone you follow.
+ *
+ * Note that users will eventually drop out of this list even though they
+ * still have stories. So it's always safer to call getUserStoryFeed() if
+ * a specific user's story feed matters to you.
+ *
+ * @param string $reason (optional) Reason for the request.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelsTrayFeedResponse
+ *
+ * @see Story::getUserStoryFeed()
+ */
+ public function getReelsTrayFeed(
+ $reason = 'pull_to_refresh')
+ {
+ return $this->ig->request('feed/reels_tray/')
+ ->setSignedPost(false)
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('reason', $reason)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\ReelsTrayFeedResponse());
+ }
+
+ /**
+ * Get a specific user's story reel feed.
+ *
+ * This function gets the user's story Reel object directly, which always
+ * exists and contains information about the user and their last story even
+ * if that user doesn't have any active story anymore.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserReelMediaFeedResponse
+ *
+ * @see Story::getUserStoryFeed()
+ */
+ public function getUserReelMediaFeed(
+ $userId)
+ {
+ return $this->ig->request("feed/user/{$userId}/reel_media/")
+ ->getResponse(new Response\UserReelMediaFeedResponse());
+ }
+
+ /**
+ * Get a specific user's story feed with broadcast details.
+ *
+ * This function gets the story in a roundabout way, with some extra details
+ * about the "broadcast". But if there is no story available, this endpoint
+ * gives you an empty response.
+ *
+ * NOTE: At least AT THIS MOMENT, this endpoint and the reels-tray endpoint
+ * are the only ones that will give you people's "post_live" fields (their
+ * saved Instagram Live Replays). The other "get user stories" funcs don't!
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserStoryFeedResponse
+ *
+ * @see Story::getUserReelMediaFeed()
+ */
+ public function getUserStoryFeed(
+ $userId)
+ {
+ return $this->ig->request("feed/user/{$userId}/story/")
+ ->addParam('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->getResponse(new Response\UserStoryFeedResponse());
+ }
+
+ /**
+ * Get multiple users' story feeds (or specific highlight-details) at once.
+ *
+ * NOTE: Normally, you would only use this endpoint for stories (by passing
+ * UserPK IDs as the parameter). But if you're looking at people's highlight
+ * feeds (via `Highlight::getUserFeed()`), you may also sometimes discover
+ * highlight entries that don't have any `items` array. In that case, you
+ * are supposed to get the items for those highlights via this endpoint!
+ * Simply pass their `id` values as the argument to this API to get details.
+ *
+ * @param string|string[] $feedList List of numerical UserPK IDs, OR highlight IDs (such as `highlight:123882132324123`).
+ * @param string $source (optional) Source app-module where the request was made.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelsMediaResponse
+ *
+ * @see Highlight::getUserFeed() More info about when to use this API for highlight-details.
+ */
+ public function getReelsMediaFeed(
+ $feedList,
+ $source = 'feed_timeline')
+ {
+ if (!is_array($feedList)) {
+ $feedList = [$feedList];
+ }
+
+ foreach ($feedList as &$value) {
+ $value = (string) $value;
+ }
+ unset($value); // Clear reference.
+
+ return $this->ig->request('feed/reels_media/')
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_ids', $feedList) // Must be string[] array.
+ ->addPost('source', $source)
+ ->getResponse(new Response\ReelsMediaResponse());
+ }
+
+ /**
+ * Get your archived story media feed.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ArchivedStoriesFeedResponse
+ */
+ public function getArchivedStoriesFeed()
+ {
+ return $this->ig->request('archive/reel/day_shells/')
+ ->addParam('include_suggested_highlights', false)
+ ->addParam('is_in_archive_home', true)
+ ->addParam('include_cover', 0)
+ ->addParam('timezone_offset', date('Z'))
+ ->getResponse(new Response\ArchivedStoriesFeedResponse());
+ }
+
+ /**
+ * Get the list of users who have seen one of your story items.
+ *
+ * Note that this only works for your own story items. Instagram doesn't
+ * allow you to see the viewer list for other people's stories!
+ *
+ * @param string $storyPk The story media item's PK in Instagram's internal format (ie "3482384834").
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelMediaViewerResponse
+ */
+ public function getStoryItemViewers(
+ $storyPk,
+ $maxId = null)
+ {
+ $request = $this->ig->request("media/{$storyPk}/list_reel_media_viewer/")
+ ->addParam('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES));
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\ReelMediaViewerResponse());
+ }
+
+ /**
+ * Vote on a story poll.
+ *
+ * Note that once you vote on a story poll, you cannot change your vote.
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $pollId The poll ID in Instagram's internal format (ie "17956159684032257").
+ * @param int $votingOption Value that represents the voting option of the voter. 0 for the first option, 1 for the second option.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelMediaViewerResponse
+ */
+ public function votePollStory(
+ $storyId,
+ $pollId,
+ $votingOption)
+ {
+ if (($votingOption !== 0) && ($votingOption !== 1)) {
+ throw new \InvalidArgumentException('You must provide a valid value for voting option.');
+ }
+
+ return $this->ig->request("media/{$storyId}/{$pollId}/story_poll_vote/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('vote', $votingOption)
+ ->getResponse(new Response\ReelMediaViewerResponse());
+ }
+
+ /**
+ * Vote on a story slider.
+ *
+ * Note that once you vote on a story poll, you cannot change your vote.
+ *
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $sliderId The slider ID in Instagram's internal format (ie "17956159684032257").
+ * @param float $votingOption Value that represents the voting option of the voter. Should be a float from 0 to 1 (ie "0.25").
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelMediaViewerResponse
+ */
+ public function voteSliderStory(
+ $storyId,
+ $sliderId,
+ $votingOption)
+ {
+ if ($votingOption < 0 || $votingOption > 1) {
+ throw new \InvalidArgumentException('You must provide a valid value from 0 to 1 for voting option.');
+ }
+
+ return $this->ig->request("media/{$storyId}/{$sliderId}/story_slider_vote/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('vote', $votingOption)
+ ->getResponse(new Response\ReelMediaViewerResponse());
+ }
+
+ /**
+ * Get the list of users who have voted an option in a story poll.
+ *
+ * Note that this only works for your own story polls. Instagram doesn't
+ * allow you to see the results from other people's polls!
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $pollId The poll ID in Instagram's internal format (ie "17956159684032257").
+ * @param int $votingOption Value that represents the voting option of the voter. 0 for the first option, 1 for the second option.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StoryPollVotersResponse
+ */
+ public function getStoryPollVoters(
+ $storyId,
+ $pollId,
+ $votingOption,
+ $maxId = null)
+ {
+ if (($votingOption !== 0) && ($votingOption !== 1)) {
+ throw new \InvalidArgumentException('You must provide a valid value for voting option.');
+ }
+
+ $request = $this->ig->request("media/{$storyId}/{$pollId}/story_poll_voters/")
+ ->addParam('vote', $votingOption);
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\StoryPollVotersResponse());
+ }
+
+ /**
+ * Respond to a question sticker on a story.
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17956159684032257").
+ * @param string $responseText The text to respond to the question with. (Note: Android App limits this to 94 characters).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function answerStoryQuestion(
+ $storyId,
+ $questionId,
+ $responseText)
+ {
+ return $this->ig->request("media/{$storyId}/{$questionId}/story_question_response/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('response', $responseText)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('type', 'text')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get all responses of a story question.
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17956159684032257").
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StoryAnswersResponse
+ */
+ public function getStoryAnswers(
+ $storyId,
+ $questionId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("media/{$storyId}/{$questionId}/story_question_responses/");
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\StoryAnswersResponse());
+ }
+
+ /**
+ * Gets the created story countdowns of the current account.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StoryCountdownsResponse
+ */
+ public function getStoryCountdowns()
+ {
+ return $this->ig->request('media/story_countdowns/')
+ ->getResponse(new Response\StoryCountdownsResponse());
+ }
+
+ /**
+ * Follows a story countdown to subscribe to a notification when the countdown is finished.
+ *
+ * @param string $countdownId The countdown ID in Instagram's internal format (ie "17956159684032257").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function followStoryCountdown(
+ $countdownId)
+ {
+ return $this->ig->request("media/{$countdownId}/follow_story_countdown/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unfollows a story countdown to unsubscribe from a notification when the countdown is finished.
+ *
+ * @param string $countdownId The countdown ID in Instagram's internal format (ie "17956159684032257").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unfollowStoryCountdown(
+ $countdownId)
+ {
+ return $this->ig->request("media/{$countdownId}/unfollow_story_countdown/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get list of charities for use in the donation sticker on stories.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CharitiesListResponse
+ */
+ public function getCharities(
+ $maxId = null)
+ {
+ $request = $this->ig->request('fundraiser/story_charities_nullstate/');
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\CharitiesListResponse());
+ }
+
+ /**
+ * Searches a list of charities for use in the donation sticker on stories.
+ *
+ * @param string $query Search query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CharitiesListResponse
+ */
+ public function searchCharities(
+ $query,
+ $maxId = null)
+ {
+ $request = $this->ig->request('fundraiser/story_charities_search/')
+ ->addParam('query', $query);
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\CharitiesListResponse());
+ }
+
+ /**
+ * Creates the array for a donation sticker.
+ *
+ * @param \InstagramAPI\Response\Model\User $charityUser The User object of the charity's Instagram account.
+ * @param float $x
+ * @param float $y
+ * @param float $width
+ * @param float $height
+ * @param float $rotation
+ * @param string|null $title The title of the donation sticker.
+ * @param string $titleColor Hex color code for the title color.
+ * @param string $subtitleColor Hex color code for the subtitle color.
+ * @param string $buttonTextColor Hex color code for the button text color.
+ * @param string $startBackgroundColor
+ * @param string $endBackgroundColor
+ *
+ * @return array
+ *
+ * @see Story::getCharities()
+ * @see Story::searchCharities()
+ * @see Story::uploadPhoto()
+ * @see Story::uploadVideo()
+ */
+ public function createDonateSticker(
+ $charityUser,
+ $x = 0.5,
+ $y = 0.5,
+ $width = 0.6805556,
+ $height = 0.254738,
+ $rotation = 0.0,
+ $title = null,
+ $titleColor = '#000000',
+ $subtitleColor = '#999999ff',
+ $buttonTextColor = '#3897f0',
+ $startBackgroundColor = '#fafafa',
+ $endBackgroundColor = '#fafafa')
+ {
+ return [
+ [
+ 'x' => $x,
+ 'y' => $y,
+ 'z' => 0,
+ 'width' => $width,
+ 'height' => $height,
+ 'rotation' => $rotation,
+ 'title' => ($title !== null ? strtoupper($title) : ('HELP SUPPORT '.strtoupper($charityUser->getFullName()))),
+ 'ig_charity_id' => $charityUser->getPk(),
+ 'title_color' => $titleColor,
+ 'subtitle_color' => $subtitleColor,
+ 'button_text_color' => $buttonTextColor,
+ 'start_background_color' => $startBackgroundColor,
+ 'end_background_color' => $endBackgroundColor,
+ 'source_name' => 'sticker_tray',
+ 'user' => [
+ 'username' => $charityUser->getUsername(),
+ 'full_name' => $charityUser->getFullName(),
+ 'profile_pic_url' => $charityUser->getProfilePicUrl(),
+ 'profile_pic_id' => $charityUser->getProfilePicId(),
+ 'has_anonymous_profile_picture' => $charityUser->getHasAnonymousProfilePicture(),
+ 'id' => $charityUser->getPk(),
+ 'usertag_review_enabled' => false,
+ 'mutual_followers_count' => $charityUser->getMutualFollowersCount(),
+ 'show_besties_badge' => false,
+ 'is_private' => $charityUser->getIsPrivate(),
+ 'allowed_commenter_type' => 'any',
+ 'is_verified' => $charityUser->getIsVerified(),
+ 'is_new' => false,
+ 'feed_post_reshare_disabled' => false,
+ ],
+ 'is_sticker' => true,
+ ],
+ ];
+ }
+
+ /**
+ * Mark story media items as seen.
+ *
+ * The various story-related endpoints only give you lists of story media.
+ * They don't actually mark any stories as "seen", so the user doesn't know
+ * that you've seen their story. Actually marking the story as "seen" is
+ * done via this endpoint instead. The official app calls this endpoint
+ * periodically (with 1 or more items at a time) while watching a story.
+ *
+ * Tip: You can pass in the whole "getItems()" array from a user's story
+ * feed (retrieved via any of the other story endpoints), to easily mark
+ * all of that user's story media items as seen.
+ *
+ * WARNING: ONLY USE *THIS* ENDPOINT IF THE STORIES CAME FROM THE ENDPOINTS
+ * IN *THIS* REQUEST-COLLECTION FILE: From "getReelsTrayFeed()" or the
+ * user-specific story endpoints. Do NOT use this endpoint if the stories
+ * came from any OTHER request-collections, such as Location-based stories!
+ * Other request-collections have THEIR OWN special story-marking functions!
+ *
+ * @param Response\Model\Item[] $items Array of one or more story media Items.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Location::markStoryMediaSeen()
+ * @see Hashtag::markStoryMediaSeen()
+ */
+ public function markMediaSeen(
+ array $items)
+ {
+ // NOTE: NULL = Use each item's owner ID as the "source ID".
+ return $this->ig->internal->markStoryMediaSeen($items, null);
+ }
+
+ /**
+ * Get your story settings.
+ *
+ * This has information such as your story messaging mode (who can reply
+ * to your story), and the list of users you have blocked from seeing your
+ * stories.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelSettingsResponse
+ */
+ public function getReelSettings()
+ {
+ return $this->ig->request('users/reel_settings/')
+ ->getResponse(new Response\ReelSettingsResponse());
+ }
+
+ /**
+ * Set your story settings.
+ *
+ * @param string $messagePrefs Who can reply to your story. Valid values are "anyone" (meaning
+ * your followers), "following" (followers that you follow back),
+ * or "off" (meaning that nobody can reply to your story).
+ * @param bool|null $allowStoryReshare Allow story reshare.
+ * @param string|null $autoArchive Auto archive stories for viewing them later. It will appear in your
+ * archive once it has disappeared from your story feed. Valid values
+ * "on" and "off".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelSettingsResponse
+ */
+ public function setReelSettings(
+ $messagePrefs,
+ $allowStoryReshare = null,
+ $autoArchive = null)
+ {
+ if (!in_array($messagePrefs, ['anyone', 'following', 'off'])) {
+ throw new \InvalidArgumentException('You must provide a valid message preference value.');
+ }
+
+ $request = $this->ig->request('users/set_reel_settings/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('message_prefs', $messagePrefs);
+
+ if ($allowStoryReshare !== null) {
+ if (!is_bool($allowStoryReshare)) {
+ throw new \InvalidArgumentException('You must provide a valid value for allowing story reshare.');
+ }
+ $request->addPost('allow_story_reshare', $allowStoryReshare);
+ }
+
+ if ($autoArchive !== null) {
+ if (!in_array($autoArchive, ['on', 'off'])) {
+ throw new \InvalidArgumentException('You must provide a valid value for auto archive.');
+ }
+ $request->addPost('reel_auto_archive', $autoArchive);
+ }
+
+ return $request->getResponse(new Response\ReelSettingsResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/TV.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/TV.php
new file mode 100755
index 0000000..47bc968
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/TV.php
@@ -0,0 +1,171 @@
+ig->request('igtv/tv_guide/')
+ ->addHeader('X-Ads-Opt-Out', '0')
+ ->addHeader('X-Google-AD-ID', $this->ig->advertising_id)
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addParam('prefetch', 1)
+ ->addParam('phone_id', $this->ig->phone_id)
+ ->addParam('battery_level', '100')
+// ->addParam('banner_token', 'OgYA')
+ ->addParam('is_charging', '1')
+ ->addParam('will_sound_on', '1');
+
+ if (isset($options['is_charging'])) {
+ $request->addParam('is_charging', $options['is_charging']);
+ } else {
+ $request->addParam('is_charging', '0');
+ }
+
+ $response = $request->getResponse(new Response\TVGuideResponse());
+
+ return $response;
+ }
+
+ /**
+ * Get channel.
+ *
+ * You can filter the channel with different IDs: 'for_you', 'chrono_following', 'popular', 'continue_watching'
+ * and using a user ID in the following format: 'user_1234567891'.
+ *
+ * @param string $id ID used to filter channels.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TVChannelsResponse
+ */
+ public function getChannel(
+ $id = 'for_you',
+ $maxId = null)
+ {
+ if (!in_array($id, ['for_you', 'chrono_following', 'popular', 'continue_watching'])
+ && !preg_match('/^user_[1-9]\d*$/', $id)) {
+ throw new \InvalidArgumentException('Invalid ID type.');
+ }
+
+ $request = $this->ig->request('igtv/channel/')
+ ->addPost('id', $id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ if ($maxId !== null) {
+ $request->addPost('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\TVChannelsResponse());
+ }
+
+ /**
+ * Uploads a video to your Instagram TV.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_TV, $videoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Searches for channels.
+ *
+ * @param string $query The username or channel you are looking for.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TVSearchResponse
+ */
+ public function search(
+ $query = '')
+ {
+ if ($query !== '') {
+ $endpoint = 'igtv/search/';
+ } else {
+ $endpoint = 'igtv/suggested_searches/';
+ }
+
+ return $this->ig->request($endpoint)
+ ->addParam('query', $query)
+ ->getResponse(new Response\TVSearchResponse());
+ }
+
+ /**
+ * Write seen state on a video.
+ *
+ * @param string $impression Format: 1813637917462151382
+ * @param int $viewProgress Video view progress in seconds.
+ * @param mixed $gridImpressions TODO No info yet.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function writeSeenState(
+ $impression,
+ $viewProgress = 0,
+ $gridImpressions = [])
+ {
+ if (!ctype_digit($viewProgress) && (!is_int($viewProgress) || $viewProgress < 0)) {
+ throw new \InvalidArgumentException('View progress must be a positive integer.');
+ }
+
+ $seenState = json_encode([
+ 'impressions' => [
+ $impression => [
+ 'view_progress_s' => $viewProgress,
+ ],
+ ],
+ 'grid_impressions' => $gridImpressions,
+ ]);
+
+ return $this->ig->request('igtv/write_seen_state/')
+ ->addPost('seen_state', $seenState)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Timeline.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Timeline.php
new file mode 100755
index 0000000..844a6b1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Timeline.php
@@ -0,0 +1,512 @@
+ig->internal->uploadSinglePhoto(Constants::FEED_TIMELINE, $photoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads a video to your Instagram timeline.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_TIMELINE, $videoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads an album to your Instagram timeline.
+ *
+ * An album is also known as a "carousel" and "sidecar". They can contain up
+ * to 10 photos or videos (at the moment).
+ *
+ * @param array $media Array of image/video files and their per-file
+ * metadata (type, file, and optionally
+ * usertags). The "type" must be "photo" or
+ * "video". The "file" must be its disk path.
+ * And the optional "usertags" can only be
+ * used on PHOTOS, never on videos.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs
+ * for the album itself (its caption, location, etc).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureTimelineAlbum() for available album metadata fields.
+ */
+ public function uploadAlbum(
+ array $media,
+ array $externalMetadata = [])
+ {
+ if (empty($media)) {
+ throw new \InvalidArgumentException("List of media to upload can't be empty.");
+ }
+ if (count($media) < 2 || count($media) > 10) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram requires that albums contain 2-10 items. You tried to submit %d.',
+ count($media)
+ ));
+ }
+
+ // Figure out the media file details for ALL media in the album.
+ // NOTE: We do this first, since it validates whether the media files are
+ // valid and lets us avoid wasting time uploading totally invalid albums!
+ foreach ($media as $key => $item) {
+ if (!isset($item['file']) || !isset($item['type'])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Media at index "%s" does not have the required "file" and "type" keys.',
+ $key
+ ));
+ }
+
+ $itemInternalMetadata = new InternalMetadata();
+
+ // If usertags are provided, verify that the entries are valid.
+ if (isset($item['usertags'])) {
+ $item['usertags'] = ['in' => $item['usertags']];
+ Utils::throwIfInvalidUsertags($item['usertags']);
+ }
+
+ // Pre-process media details and throw if not allowed on Instagram.
+ switch ($item['type']) {
+ case 'photo':
+ // Determine the photo details.
+ $itemInternalMetadata->setPhotoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']);
+ break;
+ case 'video':
+ // Determine the video details.
+ $itemInternalMetadata->setVideoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unsupported album media type "%s".', $item['type']));
+ }
+
+ $media[$key]['internalMetadata'] = $itemInternalMetadata;
+ }
+
+ // Perform all media file uploads.
+ foreach ($media as $key => $item) {
+ /** @var InternalMetadata $itemInternalMetadata */
+ $itemInternalMetadata = $media[$key]['internalMetadata'];
+
+ switch ($item['type']) {
+ case 'photo':
+ $this->ig->internal->uploadPhotoData(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata);
+ break;
+ case 'video':
+ // Attempt to upload the video data.
+ $itemInternalMetadata = $this->ig->internal->uploadVideo(Constants::FEED_TIMELINE_ALBUM, $item['file'], $itemInternalMetadata);
+
+ // Attempt to upload the thumbnail, associated with our video's ID.
+ $this->ig->internal->uploadVideoThumbnail(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata, ['thumbnail_timestamp' => (isset($media[$key]['thumbnail_timestamp'])) ? $media[$key]['thumbnail_timestamp'] : 0]);
+ }
+
+ $media[$key]['internalMetadata'] = $itemInternalMetadata;
+ }
+
+ // Generate an uploadId (via internal metadata) for the album.
+ $albumInternalMetadata = new InternalMetadata();
+ // Configure the uploaded album and attach it to our timeline.
+ try {
+ /** @var \InstagramAPI\Response\ConfigureResponse $configure */
+ $configure = $this->ig->internal->configureWithRetries(
+ function () use ($media, $albumInternalMetadata, $externalMetadata) {
+ return $this->ig->internal->configureTimelineAlbum($media, $albumInternalMetadata, $externalMetadata);
+ }
+ );
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of the album failed: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $configure;
+ }
+
+ /**
+ * Get your "home screen" timeline feed.
+ *
+ * This is the feed of recent timeline posts from people you follow.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ * @param array|null $options An associative array with following keys (all
+ * of them are optional):
+ * "latest_story_pk" The media ID in Instagram's
+ * internal format (ie "3482384834_43294");
+ * "seen_posts" One or more seen media IDs;
+ * "unseen_posts" One or more unseen media IDs;
+ * "is_pull_to_refresh" Whether this call was
+ * triggered by a refresh;
+ * "push_disabled" Whether user has disabled
+ * PUSH;
+ * "recovered_from_crash" Whether the app has
+ * recovered from a crash/was killed by Android
+ * memory manager/force closed by user/just
+ * installed for the first time;
+ * "feed_view_info" DON'T USE IT YET.
+ * "is_charging" Wether the device is being charged
+ * or not. Valid values: 0 for not charging, 1 for
+ * charging.
+ * "battery_level" Sets the current device battery
+ * level.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TimelineFeedResponse
+ */
+ public function getTimelineFeed(
+ $maxId = null,
+ array $options = null)
+ {
+ $asyncAds = $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_enabled'
+ );
+
+ $request = $this->ig->request('feed/timeline/')
+ ->setSignedPost(false)
+ ->setIsBodyCompressed(true)
+ //->addHeader('X-CM-Bandwidth-KBPS', '-1.000')
+ //->addHeader('X-CM-Latency', '0.000')
+ ->addHeader('X-Ads-Opt-Out', '0')
+ ->addHeader('X-Google-AD-ID', $this->ig->advertising_id)
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addPost('bloks_versioning_id', Constants::BLOCK_VERSIONING_ID)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('is_prefetch', '0')
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('device_id', $this->ig->uuid)
+ ->addPost('client_session_id', $this->ig->session_id)
+ ->addPost('battery_level', '100')
+ ->addPost('is_charging', '1')
+ ->addPost('will_sound_on', '1')
+ ->addPost('timezone_offset', date('Z'))
+ ->addPost('is_async_ads_in_headload_enabled', (string) (int) ($asyncAds && $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_async_ads_in_headload_enabled'
+ )))
+ ->addPost('is_async_ads_double_request', (string) (int) ($asyncAds && $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_double_request_enabled'
+ )))
+ ->addPost('is_async_ads_rti', (string) (int) ($asyncAds && $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_rti_enabled'
+ )))
+ ->addPost('rti_delivery_backend', (string) (int) $this->ig->getExperimentParam(
+ 'ig_android_ad_async_ads_universe',
+ 'rti_delivery_backend'
+ ));
+
+ if (isset($options['is_charging'])) {
+ $request->addPost('is_charging', $options['is_charging']);
+ } else {
+ $request->addPost('is_charging', '0');
+ }
+
+ if (isset($options['battery_level'])) {
+ $request->addPost('battery_level', $options['battery_level']);
+ } else {
+ $request->addPost('battery_level', mt_rand(25, 100));
+ }
+
+ if (isset($options['latest_story_pk'])) {
+ $request->addPost('latest_story_pk', $options['latest_story_pk']);
+ }
+
+ if ($maxId !== null) {
+ $request->addPost('reason', 'pagination');
+ $request->addPost('max_id', $maxId);
+ $request->addPost('is_pull_to_refresh', '0');
+ } elseif (!empty($options['is_pull_to_refresh'])) {
+ $request->addPost('reason', 'pull_to_refresh');
+ $request->addPost('is_pull_to_refresh', '1');
+ } elseif (isset($options['is_pull_to_refresh'])) {
+ $request->addPost('reason', 'warm_start_fetch');
+ $request->addPost('is_pull_to_refresh', '0');
+ } else {
+ $request->addPost('reason', 'cold_start_fetch');
+ $request->addPost('is_pull_to_refresh', '0');
+ }
+
+ if (isset($options['seen_posts'])) {
+ if (is_array($options['seen_posts'])) {
+ $request->addPost('seen_posts', implode(',', $options['seen_posts']));
+ } else {
+ $request->addPost('seen_posts', $options['seen_posts']);
+ }
+ } elseif ($maxId === null) {
+ $request->addPost('seen_posts', '');
+ }
+
+ if (isset($options['unseen_posts'])) {
+ if (is_array($options['unseen_posts'])) {
+ $request->addPost('unseen_posts', implode(',', $options['unseen_posts']));
+ } else {
+ $request->addPost('unseen_posts', $options['unseen_posts']);
+ }
+ } elseif ($maxId === null) {
+ $request->addPost('unseen_posts', '');
+ }
+
+ if (isset($options['feed_view_info'])) {
+ if (is_array($options['feed_view_info'])) {
+ $request->addPost('feed_view_info', json_encode($options['feed_view_info']));
+ } else {
+ $request->addPost('feed_view_info', json_encode([$options['feed_view_info']]));
+ }
+ } elseif ($maxId === null) {
+ $request->addPost('feed_view_info', '[]');
+ }
+
+ if (!empty($options['push_disabled'])) {
+ $request->addPost('push_disabled', 'true');
+ }
+
+ if (!empty($options['recovered_from_crash'])) {
+ $request->addPost('recovered_from_crash', '1');
+ }
+
+ return $request->getResponse(new Response\TimelineFeedResponse());
+ }
+
+ /**
+ * Get a user's timeline feed.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserFeedResponse
+ */
+ public function getUserFeed(
+ $userId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("feed/user/{$userId}/")
+ ->addParam('exclude_comment', true)
+ ->addParam('only_fetch_first_carousel_media', false);
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\UserFeedResponse());
+ }
+
+ /**
+ * Get your own timeline feed.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserFeedResponse
+ */
+ public function getSelfUserFeed(
+ $maxId = null)
+ {
+ return $this->getUserFeed($this->ig->account_id, $maxId);
+ }
+
+ /**
+ * Get your archived timeline media feed.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserFeedResponse
+ */
+ public function getArchivedMediaFeed()
+ {
+ return $this->ig->request('feed/only_me_feed/')
+ ->getResponse(new Response\UserFeedResponse());
+ }
+
+ /**
+ * Archives or unarchives one of your timeline media items.
+ *
+ * Marking media as "archived" will hide it from everyone except yourself.
+ * You can unmark the media again at any time, to make it public again.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * "ALBUM", or the raw value of the Item's "getMediaType()" function.
+ * @param bool $onlyMe If true, archives your media so that it's only visible to you.
+ * Otherwise, if false, makes the media public to everyone again.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ArchiveMediaResponse
+ */
+ public function archiveMedia(
+ $mediaId,
+ $onlyMe = true)
+ {
+ $endpoint = $onlyMe ? 'only_me' : 'undo_only_me';
+
+ return $this->ig->request("media/{$mediaId}/{$endpoint}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('media_id', $mediaId)
+ ->getResponse(new Response\ArchiveMediaResponse());
+ }
+
+ /**
+ * Backup all of your own uploaded photos and videos. :).
+ *
+ * Note that the backup filenames contain the date and time that the media
+ * was uploaded. It uses PHP's timezone to calculate the local time. So be
+ * sure to use date_default_timezone_set() with your local timezone if you
+ * want correct times in the filenames!
+ *
+ * @param string $baseOutputPath (optional) Base-folder for output.
+ * Uses "backups/" path in lib dir if null.
+ * @param bool $printProgress (optional) Toggles terminal output.
+ *
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ public function backup(
+ $baseOutputPath = null,
+ $printProgress = true)
+ {
+ // Decide which path to use.
+ if ($baseOutputPath === null) {
+ $baseOutputPath = Constants::SRC_DIR.'/../backups/';
+ }
+
+ // Ensure that the whole directory path for the backup exists.
+ $backupFolder = $baseOutputPath.$this->ig->username.'/'.date('Y-m-d').'/';
+ if (!Utils::createFolder($backupFolder)) {
+ throw new \RuntimeException(sprintf(
+ 'The "%s" backup folder is not writable.',
+ $backupFolder
+ ));
+ }
+
+ // Download all media to the output folders.
+ $nextMaxId = null;
+ do {
+ $myTimeline = $this->getSelfUserFeed($nextMaxId);
+
+ // Build a list of all media files on this page.
+ $mediaFiles = []; // Reset queue.
+ foreach ($myTimeline->getItems() as $item) {
+ $itemDate = date('Y-m-d \a\t H.i.s O', $item->getTakenAt());
+ if ($item->getMediaType() == Response\Model\Item::CAROUSEL) {
+ // Albums contain multiple items which must all be queued.
+ // NOTE: We won't name them by their subitem's getIds, since
+ // those Ids have no meaning outside of the album and they
+ // would just mean that the album content is spread out with
+ // wildly varying filenames. Instead, we will name all album
+ // items after their album's Id, with a position offset in
+ // their filename to show their position within the album.
+ $subPosition = 0;
+ foreach ($item->getCarouselMedia() as $subItem) {
+ ++$subPosition;
+ if ($subItem->getMediaType() == Response\Model\CarouselMedia::PHOTO) {
+ $mediaUrl = $subItem->getImageVersions2()->getCandidates()[0]->getUrl();
+ } else {
+ $mediaUrl = $subItem->getVideoVersions()[0]->getUrl();
+ }
+ $subItemId = sprintf('%s [%s-%02d]', $itemDate, $item->getId(), $subPosition);
+ $mediaFiles[$subItemId] = [
+ 'taken_at' => $item->getTakenAt(),
+ 'url' => $mediaUrl,
+ ];
+ }
+ } else {
+ if ($item->getMediaType() == Response\Model\Item::PHOTO) {
+ $mediaUrl = $item->getImageVersions2()->getCandidates()[0]->getUrl();
+ } else {
+ $mediaUrl = $item->getVideoVersions()[0]->getUrl();
+ }
+ $itemId = sprintf('%s [%s]', $itemDate, $item->getId());
+ $mediaFiles[$itemId] = [
+ 'taken_at' => $item->getTakenAt(),
+ 'url' => $mediaUrl,
+ ];
+ }
+ }
+
+ // Download all media files in the current page's file queue.
+ foreach ($mediaFiles as $mediaId => $mediaInfo) {
+ $mediaUrl = $mediaInfo['url'];
+ $fileExtension = pathinfo(parse_url($mediaUrl, PHP_URL_PATH), PATHINFO_EXTENSION);
+ $filePath = $backupFolder.$mediaId.'.'.$fileExtension;
+
+ // Attempt to download the file.
+ if ($printProgress) {
+ echo sprintf("* Downloading \"%s\" to \"%s\".\n", $mediaUrl, $filePath);
+ }
+ copy($mediaUrl, $filePath);
+
+ // Set the file modification time to the taken_at timestamp.
+ if (is_file($filePath)) {
+ touch($filePath, $mediaInfo['taken_at']);
+ }
+ }
+
+ // Update the page ID to point to the next page (if more available).
+ $nextMaxId = $myTimeline->getNextMaxId();
+ } while ($nextMaxId !== null);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Usertag.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Usertag.php
new file mode 100755
index 0000000..4efba3a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Request/Usertag.php
@@ -0,0 +1,144 @@
+ [],
+ 'in' => [
+ ['position' => $position, 'user_id' => $userId],
+ ],
+ ];
+
+ return $this->ig->media->edit($mediaId, $captionText, ['usertags' => $usertags]);
+ }
+
+ /**
+ * Untag a user from a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $userId Numerical UserPK ID.
+ * @param string $captionText Caption to use for the media.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditMediaResponse
+ */
+ public function untagMedia(
+ $mediaId,
+ $userId,
+ $captionText = '')
+ {
+ $usertags = [
+ 'removed' => [
+ $userId,
+ ],
+ 'in' => [],
+ ];
+
+ return $this->ig->media->edit($mediaId, $captionText, ['usertags' => $usertags]);
+ }
+
+ /**
+ * Remove yourself from a tagged media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaInfoResponse
+ */
+ public function removeSelfTag(
+ $mediaId)
+ {
+ return $this->ig->request("usertags/{$mediaId}/remove/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\MediaInfoResponse());
+ }
+
+ /**
+ * Get user taggings for a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UsertagsResponse
+ */
+ public function getUserFeed(
+ $userId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("usertags/{$userId}/feed/");
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\UsertagsResponse());
+ }
+
+ /**
+ * Get user taggings for your own account.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UsertagsResponse
+ */
+ public function getSelfUserFeed(
+ $maxId = null)
+ {
+ return $this->getUserFeed($this->ig->account_id, $maxId);
+ }
+
+ /**
+ * Choose how photos you are tagged in will be added to your profile.
+ *
+ * @param bool $enabled TRUE to manually accept photos, or FALSE to accept automatically.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReviewPreferenceResponse
+ */
+ public function setReviewPreference(
+ $enabled)
+ {
+ return $this->ig->request('usertags/review_preference/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('enabled', (int) $enabled)
+ ->getResponse(new Response\ReviewPreferenceResponse());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response.php
new file mode 100755
index 0000000..abcc39a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response.php
@@ -0,0 +1,141 @@
+ 'string',
+ /*
+ * Instagram's API failure error message(s).
+ *
+ * NOTE: This MUST be marked as 'mixed' since the server can give us
+ * either a single string OR a data structure with multiple messages.
+ * Our custom `getMessage()` will take care of parsing their value.
+ */
+ 'message' => 'mixed',
+ /*
+ * This can exist in any Instagram API response, and carries special
+ * status information.
+ *
+ * Known messages: "fb_needs_reauth", "vkontakte_needs_reauth",
+ * "twitter_needs_reauth", "ameba_needs_reauth", "update_push_token".
+ */
+ '_messages' => 'Response\Model\_Message[]',
+ ];
+
+ /** @var HttpResponseInterface */
+ public $httpResponse;
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ return $this->_getProperty('status') === self::STATUS_OK;
+ }
+
+ /**
+ * Gets the message.
+ *
+ * This function overrides the normal getter with some special processing
+ * to handle unusual multi-error message values in certain responses.
+ *
+ * @throws RuntimeException If the message object is of an unsupported type.
+ *
+ * @return string|null A message string if one exists, otherwise NULL.
+ */
+ public function getMessage()
+ {
+ // Instagram's API usually returns a simple error string. But in some
+ // cases, they instead return a subarray of individual errors, in case
+ // of APIs that can return multiple errors at once.
+ //
+ // Uncomment this if you want to test multiple error handling:
+ // $json = '{"status":"fail","message":{"errors":["Select a valid choice. 0 is not one of the available choices."]}}';
+ // $json = '{"status":"fail","message":{"errors":["Select a valid choice. 0 is not one of the available choices.","Another error.","One more error."]}}';
+ // $data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+ // $this->_setProperty('message', $data['message']);
+
+ $message = $this->_getProperty('message');
+ if ($message === null || is_string($message)) {
+ // Single error string or nothing at all.
+ return $message;
+ } elseif (is_array($message)) {
+ // Multiple errors in an "errors" subarray.
+ if (count($message) === 1 && isset($message['errors']) && is_array($message['errors'])) {
+ // Add "Multiple Errors" prefix if the response contains more than one.
+ // But most of the time, there will only be one error in the array.
+ $str = (count($message['errors']) > 1 ? 'Multiple Errors: ' : '');
+ $str .= implode(' AND ', $message['errors']); // Assumes all errors are strings.
+ return $str;
+ } else {
+ throw new RuntimeException('Unknown message object. Expected errors subarray but found something else. Please submit a ticket about needing an Instagram-API library update!');
+ }
+ } else {
+ throw new RuntimeException('Unknown message type. Please submit a ticket about needing an Instagram-API library update!');
+ }
+ }
+
+ /**
+ * Gets the HTTP response.
+ *
+ * @return HttpResponseInterface
+ */
+ public function getHttpResponse()
+ {
+ return $this->httpResponse;
+ }
+
+ /**
+ * Sets the HTTP response.
+ *
+ * @param HttpResponseInterface $response
+ */
+ public function setHttpResponse(
+ HttpResponseInterface $response)
+ {
+ $this->httpResponse = $response;
+ }
+
+ /**
+ * Checks if an HTTP response value exists.
+ *
+ * @return bool
+ */
+ public function isHttpResponse()
+ {
+ return $this->httpResponse !== null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/AccountCreateResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/AccountCreateResponse.php
new file mode 100755
index 0000000..91cc852
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/AccountCreateResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'created_user' => 'Model\User',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/AccountDetailsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/AccountDetailsResponse.php
new file mode 100755
index 0000000..d67c255
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/AccountDetailsResponse.php
@@ -0,0 +1,52 @@
+ 'string',
+ 'former_username_info' => 'Model\FormerUsernameInfo',
+ 'primary_country_info' => 'Model\PrimaryCountryInfo',
+ 'shared_follower_accounts_info' => 'Model\SharedFollowerAccountsInfo',
+ 'ads_info' => 'Model\AdsInfo',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/AccountSecurityInfoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/AccountSecurityInfoResponse.php
new file mode 100755
index 0000000..8c027e9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/AccountSecurityInfoResponse.php
@@ -0,0 +1,57 @@
+ '',
+ 'is_phone_confirmed' => '',
+ 'country_code' => 'int',
+ 'phone_number' => 'string',
+ 'is_two_factor_enabled' => '',
+ 'national_number' => 'string', // Really int, but may be >32bit.
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ActiveFeedAdsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ActiveFeedAdsResponse.php
new file mode 100755
index 0000000..e87215f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ActiveFeedAdsResponse.php
@@ -0,0 +1,42 @@
+ 'Model\FeedItem[]',
+ 'next_max_id' => 'string',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ActiveReelAdsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ActiveReelAdsResponse.php
new file mode 100755
index 0000000..428279e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ActiveReelAdsResponse.php
@@ -0,0 +1,42 @@
+ 'Model\Reel[]',
+ 'next_max_id' => 'string',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ActivityNewsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ActivityNewsResponse.php
new file mode 100755
index 0000000..db1b061
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ActivityNewsResponse.php
@@ -0,0 +1,77 @@
+ 'Model\Story[]',
+ 'old_stories' => 'Model\Story[]',
+ 'continuation' => '',
+ 'friend_request_stories' => 'Model\Story[]',
+ 'counts' => 'Model\Counts',
+ 'subscription' => 'Model\Subscription',
+ 'partition' => '',
+ 'continuation_token' => '',
+ 'ads_manager' => '',
+ 'aymf' => 'Model\Aymf',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ArchiveMediaResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ArchiveMediaResponse.php
new file mode 100755
index 0000000..97b4bdc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ArchiveMediaResponse.php
@@ -0,0 +1,25 @@
+ 'Model\ArchivedStoriesFeedItem[]',
+ 'num_results' => 'int',
+ 'more_available' => 'bool',
+ 'max_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ArlinkDownloadInfoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ArlinkDownloadInfoResponse.php
new file mode 100755
index 0000000..8ec0d50
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ArlinkDownloadInfoResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'download_url' => 'string',
+ 'file_size' => 'string',
+ 'version' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BadgeNotificationsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BadgeNotificationsResponse.php
new file mode 100755
index 0000000..7bae681
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BadgeNotificationsResponse.php
@@ -0,0 +1,37 @@
+ 'Model\UnpredictableKeys\CoreUnpredictableContainer',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BlockedListResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BlockedListResponse.php
new file mode 100755
index 0000000..f63f309
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BlockedListResponse.php
@@ -0,0 +1,42 @@
+ 'Model\User[]',
+ 'next_max_id' => 'string',
+ 'page_size' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BlockedMediaResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BlockedMediaResponse.php
new file mode 100755
index 0000000..69da2bc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BlockedMediaResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BlockedReelsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BlockedReelsResponse.php
new file mode 100755
index 0000000..40e4e55
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BlockedReelsResponse.php
@@ -0,0 +1,45 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BootstrapUsersResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BootstrapUsersResponse.php
new file mode 100755
index 0000000..e786f4a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BootstrapUsersResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Surface[]',
+ 'users' => 'Model\User[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastCommentsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastCommentsResponse.php
new file mode 100755
index 0000000..6b6d892
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastCommentsResponse.php
@@ -0,0 +1,95 @@
+ 'Model\Comment[]',
+ 'comment_count' => 'int',
+ 'live_seconds_per_comment' => 'int',
+ 'has_more_headload_comments' => 'bool',
+ /*
+ * NOTE: Instagram sends "True" or "False" as a string in this property.
+ */
+ 'is_first_fetch' => 'string',
+ 'comment_likes_enabled' => 'bool',
+ 'pinned_comment' => 'Model\Comment',
+ 'system_comments' => 'Model\Comment[]',
+ 'has_more_comments' => 'bool',
+ 'caption_is_edited' => 'bool',
+ 'caption' => '',
+ 'comment_muted' => 'int',
+ 'media_header_display' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastHeartbeatAndViewerCountResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastHeartbeatAndViewerCountResponse.php
new file mode 100755
index 0000000..1f94d75
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastHeartbeatAndViewerCountResponse.php
@@ -0,0 +1,67 @@
+ 'string',
+ 'viewer_count' => 'int',
+ 'offset_to_video_start' => 'int',
+ 'total_unique_viewer_count' => 'int',
+ 'is_top_live_eligible' => 'int',
+ 'cobroadcaster_ids' => 'string[]',
+ 'is_policy_violation' => 'int',
+ 'policy_violation_reason' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastInfoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastInfoResponse.php
new file mode 100755
index 0000000..5c89ae7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastInfoResponse.php
@@ -0,0 +1,132 @@
+ 'string',
+ 'num_total_requests' => 'int',
+ 'num_new_requests' => 'int',
+ 'users' => 'Model\User[]',
+ 'num_unseen_requests' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastLikeCountResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastLikeCountResponse.php
new file mode 100755
index 0000000..6a2cd45
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastLikeCountResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'likes' => 'int',
+ 'burst_likes' => 'int',
+ 'likers' => 'Model\User[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastLikeResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastLikeResponse.php
new file mode 100755
index 0000000..4385c07
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastLikeResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastQuestionsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastQuestionsResponse.php
new file mode 100755
index 0000000..deb878b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/BroadcastQuestionsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\BroadcastQuestion[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CapabilitiesDecisionsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CapabilitiesDecisionsResponse.php
new file mode 100755
index 0000000..09857e8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CapabilitiesDecisionsResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ChallengeResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ChallengeResponse.php
new file mode 100755
index 0000000..d1af5e8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ChallengeResponse.php
@@ -0,0 +1,25 @@
+ 'Model\User[]',
+ 'suggested_charities' => 'Model\User[]',
+ 'searched_charities' => 'Model\User[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CheckEmailResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CheckEmailResponse.php
new file mode 100755
index 0000000..61978ec
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CheckEmailResponse.php
@@ -0,0 +1,52 @@
+ '',
+ 'available' => '',
+ 'confirmed' => '',
+ 'username_suggestions' => 'string[]',
+ 'error_type' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CheckUsernameResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CheckUsernameResponse.php
new file mode 100755
index 0000000..8a5d9ca
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CheckUsernameResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'available' => '',
+ 'error' => '',
+ 'error_type' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CloseFriendsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CloseFriendsResponse.php
new file mode 100755
index 0000000..4acd195
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CloseFriendsResponse.php
@@ -0,0 +1,49 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CollectionFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CollectionFeedResponse.php
new file mode 100755
index 0000000..c1167b7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CollectionFeedResponse.php
@@ -0,0 +1,67 @@
+ 'string',
+ 'collection_name' => 'string',
+ 'items' => 'Model\SavedFeedItem[]',
+ 'num_results' => 'int',
+ 'more_available' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ 'has_related_media' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentBroadcastResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentBroadcastResponse.php
new file mode 100755
index 0000000..39fb294
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentBroadcastResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Comment',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentCategoryFilterResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentCategoryFilterResponse.php
new file mode 100755
index 0000000..06341d7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentCategoryFilterResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentFilterKeywordsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentFilterKeywordsResponse.php
new file mode 100755
index 0000000..a9c3ad7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentFilterKeywordsResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentFilterResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentFilterResponse.php
new file mode 100755
index 0000000..894907f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentFilterResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentFilterSetResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentFilterSetResponse.php
new file mode 100755
index 0000000..bd9a48c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentFilterSetResponse.php
@@ -0,0 +1,25 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentResponse.php
new file mode 100755
index 0000000..0d2c2a6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CommentResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Comment',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ConfigureResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ConfigureResponse.php
new file mode 100755
index 0000000..5c2e426
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ConfigureResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'media' => 'Model\Item',
+ 'client_sidecar_id' => 'string',
+ 'message_metadata' => 'Model\DirectMessageMetadata[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CreateBusinessInfoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CreateBusinessInfoResponse.php
new file mode 100755
index 0000000..785a8dd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CreateBusinessInfoResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CreateCollectionResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CreateCollectionResponse.php
new file mode 100755
index 0000000..e92fa0b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CreateCollectionResponse.php
@@ -0,0 +1,40 @@
+ 'Model\Reel',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CreateLiveResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CreateLiveResponse.php
new file mode 100755
index 0000000..2560dd5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/CreateLiveResponse.php
@@ -0,0 +1,157 @@
+ 'string',
+ 'upload_url' => 'string',
+ 'max_time_in_seconds' => 'int',
+ 'speed_test_ui_timeout' => 'int',
+ 'stream_network_speed_test_payload_chunk_size_in_bytes' => 'int',
+ 'stream_network_speed_test_payload_size_in_bytes' => 'int',
+ 'stream_network_speed_test_payload_timeout_in_seconds' => 'int',
+ 'speed_test_minimum_bandwidth_threshold' => 'int',
+ 'speed_test_retry_max_count' => 'int',
+ 'speed_test_retry_time_delay' => 'int',
+ 'disable_speed_test' => 'int',
+ 'stream_video_allow_b_frames' => 'int',
+ 'stream_video_width' => 'int',
+ 'stream_video_bit_rate' => 'int',
+ 'stream_video_fps' => 'int',
+ 'stream_audio_bit_rate' => 'int',
+ 'stream_audio_sample_rate' => 'int',
+ 'stream_audio_channels' => 'int',
+ 'heartbeat_interval' => 'int',
+ 'broadcaster_update_frequency' => 'int',
+ 'stream_video_adaptive_bitrate_config' => '',
+ 'stream_network_connection_retry_count' => 'int',
+ 'stream_network_connection_retry_delay_in_seconds' => 'int',
+ 'connect_with_1rtt' => 'int',
+ 'avc_rtmp_payload' => 'int',
+ 'allow_resolution_change' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DeleteCollectionResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DeleteCollectionResponse.php
new file mode 100755
index 0000000..1c7ef6d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DeleteCollectionResponse.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'users' => 'Model\User[]',
+ 'left_users' => 'Model\User[]',
+ 'items' => 'Model\DirectThreadItem[]',
+ 'last_activity_at' => '',
+ 'muted' => '',
+ 'named' => '',
+ 'canonical' => '',
+ 'pending' => '',
+ 'thread_type' => '',
+ 'viewer_id' => 'string',
+ 'thread_title' => '',
+ 'inviter' => 'Model\User',
+ 'has_older' => 'bool',
+ 'has_newer' => 'bool',
+ 'last_seen_at' => '',
+ 'is_pin' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectInboxResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectInboxResponse.php
new file mode 100755
index 0000000..84531d2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectInboxResponse.php
@@ -0,0 +1,67 @@
+ 'Model\User',
+ 'pending_requests_total' => '',
+ 'seq_id' => 'string',
+ 'has_pending_top_requests' => 'bool',
+ 'pending_requests_users' => 'Model\User[]',
+ 'inbox' => 'Model\DirectInbox',
+ 'megaphone' => 'Model\Megaphone',
+ 'snapshot_at_ms' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectPendingInboxResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectPendingInboxResponse.php
new file mode 100755
index 0000000..ab11e2b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectPendingInboxResponse.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'pending_requests_total' => '',
+ 'inbox' => 'Model\DirectInbox',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectRankedRecipientsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectRankedRecipientsResponse.php
new file mode 100755
index 0000000..f1eee00
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectRankedRecipientsResponse.php
@@ -0,0 +1,52 @@
+ '',
+ 'ranked_recipients' => 'Model\DirectRankedRecipient[]',
+ 'filtered' => '',
+ 'request_id' => 'string',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectRecentRecipientsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectRecentRecipientsResponse.php
new file mode 100755
index 0000000..7242b71
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectRecentRecipientsResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'recent_recipients' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectSeenItemResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectSeenItemResponse.php
new file mode 100755
index 0000000..37e9c1d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectSeenItemResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'payload' => 'Model\DirectSeenItemPayload', // The number of unseen items.
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectSendItemResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectSendItemResponse.php
new file mode 100755
index 0000000..a275d2e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectSendItemResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'status_code' => '',
+ 'payload' => 'Model\DirectSendItemPayload',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectSendItemsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectSendItemsResponse.php
new file mode 100755
index 0000000..17a133d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectSendItemsResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'status_code' => '',
+ 'payload' => 'Model\DirectSendItemPayload[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectShareInboxResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectShareInboxResponse.php
new file mode 100755
index 0000000..4c380ed
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectShareInboxResponse.php
@@ -0,0 +1,57 @@
+ '',
+ 'max_id' => 'string',
+ 'new_shares' => '',
+ 'patches' => '',
+ 'last_counted_at' => '',
+ 'new_shares_info' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectThreadResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectThreadResponse.php
new file mode 100755
index 0000000..d62edba
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectThreadResponse.php
@@ -0,0 +1,32 @@
+ 'Model\DirectThread',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectVisualThreadResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectVisualThreadResponse.php
new file mode 100755
index 0000000..32cd4b7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DirectVisualThreadResponse.php
@@ -0,0 +1,192 @@
+ '',
+ 'items' => 'Model\Item[]',
+ 'more_available' => '',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DiscoverPeopleResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DiscoverPeopleResponse.php
new file mode 100755
index 0000000..6cd4976
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DiscoverPeopleResponse.php
@@ -0,0 +1,47 @@
+ 'bool',
+ 'max_id' => 'string',
+ 'suggested_users' => 'Model\SuggestedUsers',
+ 'new_suggested_users' => 'Model\SuggestedUsers',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DiscoverTopLiveResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DiscoverTopLiveResponse.php
new file mode 100755
index 0000000..2195061
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/DiscoverTopLiveResponse.php
@@ -0,0 +1,57 @@
+ 'Model\Broadcast[]',
+ 'post_live_broadcasts' => 'Model\PostLiveItem[]',
+ 'score_map' => '',
+ 'more_available' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/EditCollectionResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/EditCollectionResponse.php
new file mode 100755
index 0000000..d90ef41
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/EditCollectionResponse.php
@@ -0,0 +1,40 @@
+ 'Model\Item',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/EnableDisableLiveCommentsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/EnableDisableLiveCommentsResponse.php
new file mode 100755
index 0000000..415b53a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/EnableDisableLiveCommentsResponse.php
@@ -0,0 +1,32 @@
+ 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/EnableTwoFactorSMSResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/EnableTwoFactorSMSResponse.php
new file mode 100755
index 0000000..0ed8060
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/EnableTwoFactorSMSResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ExploreResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ExploreResponse.php
new file mode 100755
index 0000000..99026ec
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ExploreResponse.php
@@ -0,0 +1,67 @@
+ 'int',
+ 'auto_load_more_enabled' => 'bool',
+ 'items' => 'Model\ExploreItem[]',
+ 'sectional_items' => 'Model\Section[]',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'max_id' => 'string',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FBLocationResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FBLocationResponse.php
new file mode 100755
index 0000000..f2e55c0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FBLocationResponse.php
@@ -0,0 +1,42 @@
+ 'bool',
+ 'items' => 'Model\LocationItem[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FBSearchResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FBSearchResponse.php
new file mode 100755
index 0000000..61793ce
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FBSearchResponse.php
@@ -0,0 +1,48 @@
+ 'bool',
+ 'list' => 'Model\UserList[]',
+ 'clear_client_cache' => 'bool',
+ 'has_more' => 'bool',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FaceEffectsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FaceEffectsResponse.php
new file mode 100755
index 0000000..23496d4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FaceEffectsResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'effects' => 'Model\Effect[]',
+ 'loading_effect' => 'Model\Effect',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FaceModelsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FaceModelsResponse.php
new file mode 100755
index 0000000..8db975f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FaceModelsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\FaceModels',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FacebookHiddenEntitiesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FacebookHiddenEntitiesResponse.php
new file mode 100755
index 0000000..4ada8cd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FacebookHiddenEntitiesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\HiddenEntities',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FacebookOTAResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FacebookOTAResponse.php
new file mode 100755
index 0000000..bbb7302
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FacebookOTAResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'request_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FetchQPDataResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FetchQPDataResponse.php
new file mode 100755
index 0000000..4f8c75f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FetchQPDataResponse.php
@@ -0,0 +1,52 @@
+ 'string',
+ 'extra_info' => 'Model\QPExtraInfo[]',
+ 'qp_data' => 'Model\QPData[]',
+ 'client_cache_ttl_in_sec' => 'int',
+ 'error_msg' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FinalViewerListResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FinalViewerListResponse.php
new file mode 100755
index 0000000..374cc3c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FinalViewerListResponse.php
@@ -0,0 +1,37 @@
+ 'Model\User[]',
+ 'total_unique_viewer_count' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FollowerAndFollowingResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FollowerAndFollowingResponse.php
new file mode 100755
index 0000000..0e51564
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FollowerAndFollowingResponse.php
@@ -0,0 +1,57 @@
+ 'Model\User[]',
+ 'suggested_users' => 'Model\SuggestedUsers',
+ 'truncate_follow_requests_at_index' => 'int',
+ 'next_max_id' => 'string',
+ 'page_size' => '',
+ 'big_list' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FollowingRecentActivityResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FollowingRecentActivityResponse.php
new file mode 100755
index 0000000..691366b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FollowingRecentActivityResponse.php
@@ -0,0 +1,47 @@
+ 'Model\Story[]',
+ 'next_max_id' => 'string',
+ 'auto_load_more_enabled' => '',
+ 'megaphone' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FormerUsernamesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FormerUsernamesResponse.php
new file mode 100755
index 0000000..c8dbe23
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FormerUsernamesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\FormerUsername[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FriendshipResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FriendshipResponse.php
new file mode 100755
index 0000000..5f9c977
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FriendshipResponse.php
@@ -0,0 +1,32 @@
+ 'Model\FriendshipStatus',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FriendshipsShowManyResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FriendshipsShowManyResponse.php
new file mode 100755
index 0000000..bc48bdd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FriendshipsShowManyResponse.php
@@ -0,0 +1,32 @@
+ 'Model\UnpredictableKeys\FriendshipStatusUnpredictableContainer',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FriendshipsShowResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FriendshipsShowResponse.php
new file mode 100755
index 0000000..865e5d4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/FriendshipsShowResponse.php
@@ -0,0 +1,72 @@
+ 'Model\Collection[]',
+ 'more_available' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/GraphqlResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/GraphqlResponse.php
new file mode 100755
index 0000000..7e66914
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/GraphqlResponse.php
@@ -0,0 +1,82 @@
+ 'Model\GraphData',
+ ];
+
+ public function getGenderGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getGenderGraph();
+ }
+
+ public function getAllAgeGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getAllFollowersAgeGraph();
+ }
+
+ public function getMenAgeGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getMenFollowersAgeGraph();
+ }
+
+ public function getWomenAgeGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getWomenFollowersAgeGraph();
+ }
+
+ public function getFollowersTopCities()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getFollowersTopCitiesGraph();
+ }
+
+ public function getFollowersTopCountries()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getFollowersTopCountriesGraph();
+ }
+
+ public function getDailyWeekFollowersGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getWeekDailyFollowersGraph();
+ }
+
+ public function getDaysHourlyFollowersGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getDaysHourlyFollowersGraph();
+ }
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ return true;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/HashtagsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/HashtagsResponse.php
new file mode 100755
index 0000000..64168ba
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/HashtagsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Hashtag[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/HighlightFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/HighlightFeedResponse.php
new file mode 100755
index 0000000..fc80529
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/HighlightFeedResponse.php
@@ -0,0 +1,57 @@
+ 'bool',
+ 'next_max_id' => 'string',
+ 'stories' => 'Model\Story[]',
+ 'show_empty_state' => 'bool',
+ 'tray' => 'Model\StoryTray[]',
+ 'tv_channel' => 'Model\StoryTvChannel',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/InsightsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/InsightsResponse.php
new file mode 100755
index 0000000..4efe919
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/InsightsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Insights',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LauncherSyncResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LauncherSyncResponse.php
new file mode 100755
index 0000000..de6f171
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LauncherSyncResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LikeFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LikeFeedResponse.php
new file mode 100755
index 0000000..506564f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LikeFeedResponse.php
@@ -0,0 +1,62 @@
+ '',
+ 'items' => 'Model\Item[]',
+ 'more_available' => '',
+ 'patches' => '',
+ 'last_counted_at' => '',
+ 'num_results' => 'int',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LinkAddressBookResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LinkAddressBookResponse.php
new file mode 100755
index 0000000..1e192f1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LinkAddressBookResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Suggestion[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LinkageStatusResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LinkageStatusResponse.php
new file mode 100755
index 0000000..a8dea6d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LinkageStatusResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LocationFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LocationFeedResponse.php
new file mode 100755
index 0000000..9f4d5e4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LocationFeedResponse.php
@@ -0,0 +1,52 @@
+ 'Model\Section[]',
+ 'next_page' => 'int',
+ 'more_available' => 'bool',
+ 'next_media_ids' => 'int[]',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LocationResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LocationResponse.php
new file mode 100755
index 0000000..a98c54f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LocationResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Location[]',
+ 'request_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LocationStoryResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LocationStoryResponse.php
new file mode 100755
index 0000000..7f90875
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LocationStoryResponse.php
@@ -0,0 +1,32 @@
+ 'Model\StoryTray',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LoginResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LoginResponse.php
new file mode 100755
index 0000000..3e5428f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LoginResponse.php
@@ -0,0 +1,157 @@
+ 'string',
+ 'has_anonymous_profile_picture' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_id' => 'string',
+ 'full_name' => 'string',
+ 'pk' => 'string',
+ 'is_private' => 'bool',
+ 'is_verified' => 'bool',
+ 'allowed_commenter_type' => 'string',
+ 'reel_auto_archive' => 'string',
+ 'allow_contacts_sync' => 'bool',
+ 'phone_number' => 'string',
+ 'country_code' => 'int',
+ 'national_number' => 'int',
+ 'error_title' => '', // On wrong pass.
+ 'error_type' => '', // On wrong pass.
+ 'buttons' => '', // On wrong pass.
+ 'invalid_credentials' => '', // On wrong pass.
+ 'logged_in_user' => 'Model\User',
+ 'two_factor_required' => '',
+ 'phone_verification_settings' => 'Model\PhoneVerificationSettings',
+ 'two_factor_info' => 'Model\TwoFactorInfo',
+ 'checkpoint_url' => 'string',
+ 'lock' => 'bool',
+ 'help_url' => 'string',
+ 'challenge' => 'Model\Challenge',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LogoutResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LogoutResponse.php
new file mode 100755
index 0000000..184b931
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/LogoutResponse.php
@@ -0,0 +1,25 @@
+ 'Model\SystemControl',
+ 'GATE_APP_VERSION' => 'bool',
+ 'trace_control' => 'Model\TraceControl',
+ 'id' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaCommentRepliesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaCommentRepliesResponse.php
new file mode 100755
index 0000000..0c19039
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaCommentRepliesResponse.php
@@ -0,0 +1,85 @@
+ 'Model\Comment',
+ /*
+ * Number of child comments in this comment thread.
+ */
+ 'child_comment_count' => 'int',
+ 'child_comments' => 'Model\Comment[]',
+ /*
+ * When "has_more_tail_child_comments" is true, you can use the value
+ * in "next_max_child_cursor" as "max_id" parameter to load up to
+ * "num_tail_child_comments" older child-comments.
+ */
+ 'has_more_tail_child_comments' => 'bool',
+ 'next_max_child_cursor' => 'string',
+ 'num_tail_child_comments' => 'int',
+ /*
+ * When "has_more_head_child_comments" is true, you can use the value
+ * in "next_min_child_cursor" as "min_id" parameter to load up to
+ * "num_head_child_comments" newer child-comments.
+ */
+ 'has_more_head_child_comments' => 'bool',
+ 'next_min_child_cursor' => 'string',
+ 'num_head_child_comments' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaCommentsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaCommentsResponse.php
new file mode 100755
index 0000000..4035d2f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaCommentsResponse.php
@@ -0,0 +1,87 @@
+ 'Model\Comment[]',
+ 'comment_count' => 'int',
+ 'comment_likes_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ 'next_min_id' => 'string',
+ 'caption' => 'Model\Caption',
+ 'has_more_comments' => 'bool',
+ 'caption_is_edited' => 'bool',
+ 'preview_comments' => '',
+ 'has_more_headload_comments' => 'bool',
+ 'media_header_display' => 'string',
+ 'threading_enabled' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaDeleteResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaDeleteResponse.php
new file mode 100755
index 0000000..9ae1c54
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaDeleteResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaInfoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaInfoResponse.php
new file mode 100755
index 0000000..725e7e0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaInfoResponse.php
@@ -0,0 +1,47 @@
+ '',
+ 'num_results' => 'int',
+ 'more_available' => '',
+ 'items' => 'Model\Item[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaInsightsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaInsightsResponse.php
new file mode 100755
index 0000000..7d2239f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaInsightsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\MediaInsights',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaLikersResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaLikersResponse.php
new file mode 100755
index 0000000..b45e912
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaLikersResponse.php
@@ -0,0 +1,37 @@
+ 'int',
+ 'users' => 'Model\User[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaSeenResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaSeenResponse.php
new file mode 100755
index 0000000..11b9823
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MediaSeenResponse.php
@@ -0,0 +1,25 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AccountAccessToolConfig.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AccountAccessToolConfig.php
new file mode 100755
index 0000000..78842dc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AccountAccessToolConfig.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'viewer' => 'User',
+ 'viewerId' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AccountSummaryUnit.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AccountSummaryUnit.php
new file mode 100755
index 0000000..de08c44
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AccountSummaryUnit.php
@@ -0,0 +1,20 @@
+ 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Action.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Action.php
new file mode 100755
index 0000000..703af80
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Action.php
@@ -0,0 +1,35 @@
+ 'Text',
+ 'url' => 'string',
+ 'limit' => 'int',
+ 'dismiss_promotion' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php
new file mode 100755
index 0000000..b0e0fa3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php
@@ -0,0 +1,41 @@
+ '',
+ 'action_count' => '',
+ 'action_timestamp' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ActionLog.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ActionLog.php
new file mode 100755
index 0000000..b383647
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ActionLog.php
@@ -0,0 +1,25 @@
+ 'Bold[]',
+ 'description' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Ad4ad.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Ad4ad.php
new file mode 100755
index 0000000..d46b822
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Ad4ad.php
@@ -0,0 +1,45 @@
+ '',
+ 'title' => '',
+ 'media' => 'Item',
+ 'footer' => '',
+ 'id' => 'string',
+ 'tracking_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AdMetadata.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AdMetadata.php
new file mode 100755
index 0000000..d2ea416
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AdMetadata.php
@@ -0,0 +1,25 @@
+ '',
+ 'type' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AdsInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AdsInfo.php
new file mode 100755
index 0000000..863443d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AdsInfo.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'ads_url' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AndroidLinks.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AndroidLinks.php
new file mode 100755
index 0000000..3622ebb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AndroidLinks.php
@@ -0,0 +1,75 @@
+ 'int',
+ 'webUri' => 'string',
+ 'androidClass' => 'string',
+ 'package' => 'string',
+ 'deeplinkUri' => 'string',
+ 'callToActionTitle' => 'string',
+ 'redirectUri' => 'string',
+ 'igUserId' => 'string',
+ 'appInstallObjectiveInvalidationBehavior' => '',
+ 'tapAndHoldContext' => 'string',
+ 'leadGenFormId' => 'string',
+ 'canvasDocId' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMedia.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMedia.php
new file mode 100755
index 0000000..605273c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMedia.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'images' => 'AnimatedMediaImage',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImage.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImage.php
new file mode 100755
index 0000000..5da776d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImage.php
@@ -0,0 +1,20 @@
+ 'AnimatedMediaImageFixedHeigth',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImageFixedHeigth.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImageFixedHeigth.php
new file mode 100755
index 0000000..1a7013d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImageFixedHeigth.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'width' => 'string',
+ 'heigth' => 'string',
+ 'size' => 'string',
+ 'mp4' => 'string',
+ 'mp4_size' => 'string',
+ 'webp' => 'string',
+ 'webp_size' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ArchivedStoriesFeedItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ArchivedStoriesFeedItem.php
new file mode 100755
index 0000000..47b62f5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ArchivedStoriesFeedItem.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'media_count' => 'int',
+ 'id' => 'string',
+ 'reel_type' => 'string',
+ 'latest_reel_media' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Args.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Args.php
new file mode 100755
index 0000000..81ce41a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Args.php
@@ -0,0 +1,135 @@
+ 'string',
+ 'text' => 'string',
+ 'icon_url' => 'string',
+ 'links' => 'Link[]',
+ 'rich_text' => 'string',
+ 'profile_id' => 'string',
+ 'profile_image' => 'string',
+ 'media' => 'Media[]',
+ 'comment_notif_type' => 'string',
+ 'timestamp' => 'string',
+ 'tuuid' => 'string',
+ 'clicked' => 'bool',
+ 'profile_name' => 'string',
+ 'action_url' => 'string',
+ 'destination' => 'string',
+ 'actions' => 'string[]',
+ 'latest_reel_media' => 'string',
+ 'comment_id' => 'string',
+ 'request_count' => '',
+ 'inline_follow' => 'InlineFollow',
+ 'comment_ids' => 'string[]',
+ 'second_profile_id' => 'string',
+ 'second_profile_image' => '',
+ 'profile_image_destination' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AssetModel.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AssetModel.php
new file mode 100755
index 0000000..2623a62
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AssetModel.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Attribution.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Attribution.php
new file mode 100755
index 0000000..c3fe9e0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Attribution.php
@@ -0,0 +1,20 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AudioContext.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AudioContext.php
new file mode 100755
index 0000000..29c14b6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AudioContext.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'duration' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Aymf.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Aymf.php
new file mode 100755
index 0000000..7e57b63
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Aymf.php
@@ -0,0 +1,25 @@
+ 'AymfItem[]',
+ 'more_available' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AymfItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AymfItem.php
new file mode 100755
index 0000000..f9039aa
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/AymfItem.php
@@ -0,0 +1,731 @@
+ 'string',
+ 'uuid' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Badging.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Badging.php
new file mode 100755
index 0000000..2d395b2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Badging.php
@@ -0,0 +1,25 @@
+ '',
+ 'items' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BiographyEntities.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BiographyEntities.php
new file mode 100755
index 0000000..687c8d4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BiographyEntities.php
@@ -0,0 +1,30 @@
+ '',
+ 'raw_text' => 'string',
+ 'nux_type' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BlockedReels.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BlockedReels.php
new file mode 100755
index 0000000..87e3105
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BlockedReels.php
@@ -0,0 +1,30 @@
+ 'User[]',
+ 'page_size' => '',
+ 'big_list' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Bold.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Bold.php
new file mode 100755
index 0000000..9ebca09
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Bold.php
@@ -0,0 +1,25 @@
+ '',
+ 'end' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Broadcast.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Broadcast.php
new file mode 100755
index 0000000..b0a8534
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Broadcast.php
@@ -0,0 +1,151 @@
+ 'User',
+ 'cobroadcasters' => '',
+ /*
+ * A string such as "active" or "post_live".
+ */
+ 'broadcast_status' => 'string',
+ 'is_gaming_content' => 'bool',
+ 'is_player_live_trace_enabled' => 'bool',
+ 'dash_live_predictive_playback_url' => 'string',
+ 'cover_frame_url' => 'string',
+ 'published_time' => 'string',
+ 'hide_from_feed_unit' => 'bool',
+ 'broadcast_message' => 'string',
+ 'muted' => '',
+ 'media_id' => 'string',
+ 'id' => 'string',
+ 'rtmp_playback_url' => 'string',
+ 'dash_abr_playback_url' => 'string',
+ 'dash_playback_url' => 'string',
+ 'ranked_position' => '',
+ 'organic_tracking_token' => 'string',
+ 'seen_ranked_position' => '',
+ 'viewer_count' => 'int',
+ 'dash_manifest' => 'string',
+ /*
+ * Unix timestamp of when the "post_live" will expire.
+ */
+ 'expire_at' => 'string',
+ 'encoding_tag' => 'string',
+ 'total_unique_viewer_count' => 'int',
+ 'internal_only' => 'bool',
+ 'number_of_qualities' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BroadcastQuestion.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BroadcastQuestion.php
new file mode 100755
index 0000000..c9bd7c3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BroadcastQuestion.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'qid' => 'string',
+ 'source' => 'string',
+ 'user' => 'User',
+ 'story_sticker_text' => 'string',
+ 'timestamp' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BroadcastStatusItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BroadcastStatusItem.php
new file mode 100755
index 0000000..f25ace4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BroadcastStatusItem.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'has_reduced_visibility' => 'bool',
+ 'cover_frame_url' => 'string',
+ 'viewer_count' => 'int',
+ 'id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessEdge.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessEdge.php
new file mode 100755
index 0000000..820a1b3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessEdge.php
@@ -0,0 +1,25 @@
+ 'BusinessNode',
+ 'cursor' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessFeed.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessFeed.php
new file mode 100755
index 0000000..2ea0643
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessFeed.php
@@ -0,0 +1,20 @@
+ 'SummaryPromotions',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessManager.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessManager.php
new file mode 100755
index 0000000..4e6799e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessManager.php
@@ -0,0 +1,50 @@
+ 'AccountSummaryUnit',
+ 'account_insights_unit' => 'BusinessNode',
+ 'followers_unit' => 'FollowersUnit',
+ 'top_posts_unit' => 'BusinessNode',
+ 'stories_unit' => 'BusinessNode',
+ 'promotions_unit' => 'PromotionsUnit',
+ 'feed' => 'BusinessFeed',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessManagerStatus.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessManagerStatus.php
new file mode 100755
index 0000000..21803f4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessManagerStatus.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'account_type' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessNode.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessNode.php
new file mode 100755
index 0000000..e36a374
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/BusinessNode.php
@@ -0,0 +1,190 @@
+ '',
+ 'followers_count' => '',
+ 'followers_delta_from_last_week' => '',
+ 'posts_count' => '',
+ 'posts_delta_from_last_week' => '',
+ 'last_week_impressions' => '',
+ 'week_over_week_impressions' => '',
+ 'last_week_reach' => '',
+ 'week_over_week_reach' => '',
+ 'last_week_profile_visits' => '',
+ 'week_over_week_profile_visits' => '',
+ 'last_week_website_visits' => '',
+ 'week_over_week_website_visits' => '',
+ 'last_week_call' => '',
+ 'week_over_week_call' => '',
+ 'last_week_text' => '',
+ 'week_over_week_text' => '',
+ 'last_week_email' => '',
+ 'week_over_week_email' => '',
+ 'last_week_get_direction' => '',
+ 'week_over_week_get_direction' => '',
+ 'average_engagement_count' => '',
+ 'last_week_impressions_day_graph' => '',
+ 'last_week_reach_day_graph' => '',
+ 'last_week_profile_visits_day_graph' => '',
+ 'summary_posts' => '',
+ 'state' => '',
+ 'summary_stories' => '',
+ 'followers_unit_state' => '',
+ 'today_hourly_graph' => '',
+ 'gender_graph' => '',
+ 'all_followers_age_graph' => '',
+ 'followers_top_cities_graph' => '',
+ 'summary_promotions' => '',
+ 'top_posts' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Button.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Button.php
new file mode 100755
index 0000000..f11270a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Button.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'url' => 'string',
+ 'action' => '',
+ 'background_color' => '',
+ 'border_color' => '',
+ 'text_color' => '',
+ 'action_info' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Caption.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Caption.php
new file mode 100755
index 0000000..3d7f06d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Caption.php
@@ -0,0 +1,85 @@
+ '',
+ 'user_id' => 'string',
+ 'created_at_utc' => 'string',
+ 'created_at' => 'string',
+ 'bit_flags' => 'int',
+ 'user' => 'User',
+ 'content_type' => '',
+ 'text' => 'string',
+ 'share_enabled' => 'bool',
+ 'media_id' => 'string',
+ 'pk' => 'string',
+ 'type' => '',
+ 'has_translation' => 'bool',
+ 'did_report_as_spam' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CarouselMedia.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CarouselMedia.php
new file mode 100755
index 0000000..65c0aba
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CarouselMedia.php
@@ -0,0 +1,196 @@
+ 'string',
+ 'id' => 'string',
+ 'carousel_parent_id' => 'string',
+ 'fb_user_tags' => 'Usertag',
+ 'number_of_qualities' => 'int',
+ 'video_codec' => 'string',
+ 'is_dash_eligible' => 'int',
+ 'video_dash_manifest' => 'string',
+ 'image_versions2' => 'Image_Versions2',
+ 'video_versions' => 'VideoVersions[]',
+ 'has_audio' => 'bool',
+ 'video_duration' => 'float',
+ 'video_subtitles_uri' => 'string',
+ 'original_height' => 'int',
+ 'original_width' => 'int',
+ /*
+ * A number describing what type of media this is. Should be compared
+ * against the `CarouselMedia::PHOTO` and `CarouselMedia::VIDEO`
+ * constants!
+ */
+ 'media_type' => 'int',
+ 'dynamic_item_id' => 'string',
+ 'usertags' => 'Usertag',
+ 'preview' => 'string',
+ 'headline' => 'Headline',
+ 'link' => 'string',
+ 'link_text' => 'string',
+ 'link_hint_text' => 'string',
+ 'android_links' => 'AndroidLinks[]',
+ 'ad_metadata' => 'AdMetadata[]',
+ 'ad_action' => 'string',
+ 'ad_link_type' => 'int',
+ 'force_overlay' => 'bool',
+ 'hide_nux_text' => 'bool',
+ 'overlay_text' => 'string',
+ 'overlay_title' => 'string',
+ 'overlay_subtitle' => 'string',
+ 'photo_of_you' => 'bool',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'dominant_color' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CatalogData.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CatalogData.php
new file mode 100755
index 0000000..cf3d8ae
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CatalogData.php
@@ -0,0 +1,25 @@
+ 'PageInfo',
+ 'edges' => 'CatalogEdge[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CatalogEdge.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CatalogEdge.php
new file mode 100755
index 0000000..d6277ff
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CatalogEdge.php
@@ -0,0 +1,20 @@
+ 'CatalogNode',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CatalogNode.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CatalogNode.php
new file mode 100755
index 0000000..a4be371
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CatalogNode.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'full_price' => '',
+ 'current_price' => '',
+ 'name' => 'string',
+ 'description' => 'string',
+ 'main_image_with_safe_fallback' => '',
+ 'retailer_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ChainingInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ChainingInfo.php
new file mode 100755
index 0000000..c75c306
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ChainingInfo.php
@@ -0,0 +1,20 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ChainingSuggestion.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ChainingSuggestion.php
new file mode 100755
index 0000000..19cd7f5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ChainingSuggestion.php
@@ -0,0 +1,587 @@
+ 'ChainingInfo',
+ 'profile_chaining_secondary_label' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Challenge.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Challenge.php
new file mode 100755
index 0000000..f2cc9d0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Challenge.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'api_path' => 'string',
+ 'hide_webview_header' => 'bool',
+ 'lock' => 'bool',
+ 'logout' => 'bool',
+ 'native_flow' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Channel.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Channel.php
new file mode 100755
index 0000000..ee1e56f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Channel.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'channel_type' => 'string',
+ 'title' => 'string',
+ 'header' => 'string',
+ 'media_count' => 'int',
+ 'media' => 'Item',
+ 'context' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CloseFriends.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CloseFriends.php
new file mode 100755
index 0000000..f14028c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CloseFriends.php
@@ -0,0 +1,35 @@
+ '',
+ 'users' => 'User[]',
+ 'big_list' => '',
+ 'page_size' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Collection.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Collection.php
new file mode 100755
index 0000000..c6c2a20
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Collection.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'collection_name' => 'string',
+ 'cover_media' => 'Item',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Comment.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Comment.php
new file mode 100755
index 0000000..daf4834
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Comment.php
@@ -0,0 +1,196 @@
+ 'string',
+ 'user_id' => 'string',
+ /*
+ * Unix timestamp (UTC) of when the comment was posted.
+ * Yes, this is the UTC timestamp even though it's not named "utc"!
+ */
+ 'created_at' => 'string',
+ /*
+ * WARNING: DO NOT USE THIS VALUE! It is NOT a real UTC timestamp.
+ * Instagram has messed up their values of "created_at" vs "created_at_utc".
+ * In `getComments()`, both have identical values. In `getCommentReplies()`,
+ * both are identical too. But in the `getComments()` "reply previews",
+ * their "created_at_utc" values are completely wrong (always +8 hours into
+ * the future, beyond the real UTC time). So just ignore this bad value!
+ * The real app only reads "created_at" for showing comment timestamps!
+ */
+ 'created_at_utc' => 'string',
+ 'bit_flags' => 'int',
+ 'user' => 'User',
+ 'pk' => 'string',
+ 'media_id' => 'string',
+ 'text' => 'string',
+ 'content_type' => 'string',
+ /*
+ * A number describing what type of comment this is. Should be compared
+ * against the `Comment::PARENT` and `Comment::CHILD` constants. All
+ * replies are of type `CHILD`, and all parents are of type `PARENT`.
+ */
+ 'type' => 'int',
+ 'comment_like_count' => 'int',
+ 'has_liked_comment' => 'bool',
+ 'has_translation' => 'bool',
+ 'did_report_as_spam' => 'bool',
+ 'share_enabled' => 'bool',
+ /*
+ * If this is a child in a thread, this is the ID of its parent thread.
+ */
+ 'parent_comment_id' => 'string',
+ /*
+ * Number of child comments in this comment thread.
+ */
+ 'child_comment_count' => 'int',
+ /*
+ * Previews of some of the child comments. Compare it to the child
+ * comment count. If there are more, you must request the comment thread.
+ */
+ 'preview_child_comments' => 'Comment[]',
+ /*
+ * Previews of users in very long comment threads.
+ */
+ 'other_preview_users' => 'User[]',
+ 'inline_composer_display_condition' => 'string',
+ /*
+ * When "has_more_tail_child_comments" is true, you can use the value
+ * in "next_max_child_cursor" as "max_id" parameter to load up to
+ * "num_tail_child_comments" older child-comments.
+ */
+ 'has_more_tail_child_comments' => 'bool',
+ 'next_max_child_cursor' => 'string',
+ 'num_tail_child_comments' => 'int',
+ /*
+ * When "has_more_head_child_comments" is true, you can use the value
+ * in "next_min_child_cursor" as "min_id" parameter to load up to
+ * "num_head_child_comments" newer child-comments.
+ */
+ 'has_more_head_child_comments' => 'bool',
+ 'next_min_child_cursor' => 'string',
+ 'num_head_child_comments' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CommentInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CommentInfo.php
new file mode 100755
index 0000000..d3c5c08
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CommentInfo.php
@@ -0,0 +1,60 @@
+ 'bool',
+ 'comment_threading_enabled' => 'bool',
+ 'has_more_comments' => 'bool',
+ 'max_num_visible_preview_comments' => 'int',
+ 'preview_comments' => '',
+ 'can_view_more_preview_comments' => 'bool',
+ 'comment_count' => 'int',
+ 'inline_composer_display_condition' => 'string',
+ 'inline_composer_imp_trigger_time' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CommentTranslations.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CommentTranslations.php
new file mode 100755
index 0000000..356a1ef
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CommentTranslations.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'translation' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Composer.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Composer.php
new file mode 100755
index 0000000..a43f9bf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Composer.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'aspect_ratio_finished' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ContextualFilters.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ContextualFilters.php
new file mode 100755
index 0000000..e3ed7dc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ContextualFilters.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'filters' => '',
+ 'clauses' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CountdownSticker.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CountdownSticker.php
new file mode 100755
index 0000000..1ad21dd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CountdownSticker.php
@@ -0,0 +1,90 @@
+ 'string',
+ 'end_ts' => 'string',
+ 'text' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'text_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'start_background_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'end_background_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'digit_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'digit_card_color' => 'string',
+ 'following_enabled' => 'bool',
+ 'is_owner' => 'bool',
+ 'attribution' => '',
+ 'viewer_is_following' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Counts.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Counts.php
new file mode 100755
index 0000000..bc608e0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Counts.php
@@ -0,0 +1,55 @@
+ '',
+ 'requests' => '',
+ 'photos_of_you' => '',
+ 'usertags' => '',
+ 'comments' => '',
+ 'likes' => '',
+ 'comment_likes' => '',
+ 'campaign_notification' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CoverMedia.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CoverMedia.php
new file mode 100755
index 0000000..18a3f57
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/CoverMedia.php
@@ -0,0 +1,63 @@
+ 'string',
+ 'media_id' => 'string',
+ /*
+ * A number describing what type of media this is.
+ */
+ 'media_type' => 'int',
+ 'image_versions2' => 'Image_Versions2',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ 'cropped_image_version' => 'ImageCandidate',
+ 'crop_rect' => 'int[]',
+ 'full_image_version' => 'ImageCandidate',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Creative.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Creative.php
new file mode 100755
index 0000000..54dfb13
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Creative.php
@@ -0,0 +1,56 @@
+ 'Text',
+ 'content' => 'Text',
+ 'footer' => 'Text',
+ 'social_context' => 'Text',
+ 'content' => 'Text',
+ 'primary_action' => 'Action',
+ 'secondary_action' => 'Action',
+ 'dismiss_action' => '',
+ 'image' => 'Image',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DataGraph.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DataGraph.php
new file mode 100755
index 0000000..6be10e8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DataGraph.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'data_points' => 'DataPoints[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DataPoints.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DataPoints.php
new file mode 100755
index 0000000..1f7ea06
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DataPoints.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'value' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectCursor.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectCursor.php
new file mode 100755
index 0000000..5e97d19
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectCursor.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'cursor_thread_v2_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectExpiringSummary.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectExpiringSummary.php
new file mode 100755
index 0000000..842a717
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectExpiringSummary.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'timestamp' => 'string',
+ 'count' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectInbox.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectInbox.php
new file mode 100755
index 0000000..cbbf89e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectInbox.php
@@ -0,0 +1,50 @@
+ 'bool',
+ 'unseen_count' => 'int',
+ 'unseen_count_ts' => 'string', // Is a timestamp.
+ 'blended_inbox_enabled' => 'bool',
+ 'threads' => 'DirectThread[]',
+ 'next_cursor' => 'DirectCursor',
+ 'prev_cursor' => 'DirectCursor',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectLink.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectLink.php
new file mode 100755
index 0000000..e7ab3cf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectLink.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'link_context' => 'LinkContext',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectMessageMetadata.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectMessageMetadata.php
new file mode 100755
index 0000000..873f463
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectMessageMetadata.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'item_id' => 'string',
+ 'timestamp' => 'string',
+ 'participant_ids' => 'string[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectRankedRecipient.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectRankedRecipient.php
new file mode 100755
index 0000000..0c1fe2c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectRankedRecipient.php
@@ -0,0 +1,25 @@
+ 'DirectThread',
+ 'user' => 'User',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectReaction.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectReaction.php
new file mode 100755
index 0000000..a85d38a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectReaction.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'timestamp' => 'string',
+ 'sender_id' => 'string',
+ 'client_context' => 'string',
+ 'reaction_status' => 'string',
+ 'node_type' => 'string',
+ 'item_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectReactions.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectReactions.php
new file mode 100755
index 0000000..a627772
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectReactions.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'likes' => 'DirectReaction[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectSeenItemPayload.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectSeenItemPayload.php
new file mode 100755
index 0000000..5971dca
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectSeenItemPayload.php
@@ -0,0 +1,25 @@
+ '',
+ 'timestamp' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectSendItemPayload.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectSendItemPayload.php
new file mode 100755
index 0000000..33541f7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectSendItemPayload.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'client_context' => 'string',
+ 'message' => 'string',
+ 'item_id' => 'string',
+ 'timestamp' => 'string',
+ 'thread_id' => 'string',
+ 'canonical' => 'bool',
+ 'participant_ids' => 'string[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThread.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThread.php
new file mode 100755
index 0000000..23e27f7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThread.php
@@ -0,0 +1,220 @@
+ 'string',
+ 'thread_v2_id' => 'string',
+ 'users' => 'User[]',
+ 'left_users' => 'User[]',
+ 'items' => 'DirectThreadItem[]',
+ 'last_activity_at' => 'string',
+ 'muted' => 'bool',
+ 'is_pin' => 'bool',
+ 'named' => 'bool',
+ 'canonical' => 'bool',
+ 'pending' => 'bool',
+ 'valued_request' => 'bool',
+ 'thread_type' => 'string',
+ 'viewer_id' => 'string',
+ 'thread_title' => 'string',
+ 'pending_score' => 'string',
+ 'vc_muted' => 'bool',
+ 'is_group' => 'bool',
+ 'reshare_send_count' => 'int',
+ 'reshare_receive_count' => 'int',
+ 'expiring_media_send_count' => 'int',
+ 'expiring_media_receive_count' => 'int',
+ 'inviter' => 'User',
+ 'has_older' => 'bool',
+ 'has_newer' => 'bool',
+ 'last_seen_at' => 'UnpredictableKeys\DirectThreadLastSeenAtUnpredictableContainer',
+ 'newest_cursor' => 'string',
+ 'oldest_cursor' => 'string',
+ 'is_spam' => 'bool',
+ 'last_permanent_item' => 'PermanentItem',
+ 'unseen_count' => '',
+ 'action_badge' => 'ActionBadge',
+ 'last_activity_at_secs' => '',
+ 'admin_user_ids' => 'string[]',
+ 'approval_required_for_new_members' => 'bool',
+ 'archived' => 'bool',
+ 'business_thread_folder' => 'int',
+ 'folder' => 'int',
+ 'input_mode' => 'int',
+ 'mentions_muted' => 'bool',
+ 'read_state' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItem.php
new file mode 100755
index 0000000..e840a45
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItem.php
@@ -0,0 +1,185 @@
+ 'string',
+ 'item_type' => '',
+ 'text' => 'string',
+ 'media_share' => 'Item',
+ 'preview_medias' => 'Item[]',
+ 'media' => 'DirectThreadItemMedia',
+ 'user_id' => 'string',
+ 'timestamp' => '',
+ 'client_context' => 'string',
+ 'hide_in_thread' => '',
+ 'action_log' => 'ActionLog',
+ 'link' => 'DirectLink',
+ 'reactions' => 'DirectReactions',
+ 'raven_media' => 'Item',
+ 'seen_user_ids' => 'string[]',
+ 'expiring_media_action_summary' => 'DirectExpiringSummary',
+ 'reel_share' => 'ReelShare',
+ 'placeholder' => 'Placeholder',
+ 'location' => 'Location',
+ 'like' => '',
+ 'live_video_share' => 'LiveVideoShare',
+ 'live_viewer_invite' => 'LiveViewerInvite',
+ 'profile' => 'User',
+ 'story_share' => 'StoryShare',
+ 'direct_media_share' => 'MediaShare',
+ 'video_call_event' => 'VideoCallEvent',
+ 'product_share' => 'ProductShare',
+ 'animated_media' => 'AnimatedMedia',
+ 'felix_share' => 'FelixShare',
+ 'voice_media' => 'VoiceMedia',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItemMedia.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItemMedia.php
new file mode 100755
index 0000000..8239650
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItemMedia.php
@@ -0,0 +1,54 @@
+ 'int',
+ 'image_versions2' => 'Image_Versions2',
+ 'video_versions' => 'VideoVersions[]',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ 'audio' => 'AudioContext',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadLastSeenAt.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadLastSeenAt.php
new file mode 100755
index 0000000..a91cc29
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadLastSeenAt.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'timestamp' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DismissCard.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DismissCard.php
new file mode 100755
index 0000000..b4c427d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/DismissCard.php
@@ -0,0 +1,50 @@
+ '',
+ 'image_url' => 'string',
+ 'title' => '',
+ 'message' => '',
+ 'button_text' => '',
+ 'camera_target' => '',
+ 'face_filter_id' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Edges.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Edges.php
new file mode 100755
index 0000000..3ff10b2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Edges.php
@@ -0,0 +1,30 @@
+ 'int',
+ 'time_range' => 'TimeRange',
+ 'node' => 'QPNode',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Effect.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Effect.php
new file mode 100755
index 0000000..0c83b2b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Effect.php
@@ -0,0 +1,50 @@
+ '',
+ 'id' => 'string',
+ 'effect_id' => 'string',
+ 'effect_file_id' => 'string',
+ 'asset_url' => 'string',
+ 'thumbnail_url' => 'string',
+ 'instructions' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/EligiblePromotions.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/EligiblePromotions.php
new file mode 100755
index 0000000..7f4f1ab
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/EligiblePromotions.php
@@ -0,0 +1,20 @@
+ 'Edges[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/EndOfFeedDemarcator.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/EndOfFeedDemarcator.php
new file mode 100755
index 0000000..2cd11fa
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/EndOfFeedDemarcator.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'title' => 'string',
+ 'subtitle' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Experiment.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Experiment.php
new file mode 100755
index 0000000..f3a1b6f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Experiment.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'group' => 'string',
+ 'additional_params' => '', // TODO: Only seen as [] empty array so far.
+ 'params' => 'Param[]',
+ 'logging_id' => 'string',
+ 'expired' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Explore.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Explore.php
new file mode 100755
index 0000000..4169d29
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Explore.php
@@ -0,0 +1,30 @@
+ '',
+ 'actor_id' => 'string',
+ 'source_token' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ExploreItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ExploreItem.php
new file mode 100755
index 0000000..4592a95
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ExploreItem.php
@@ -0,0 +1,35 @@
+ 'Item',
+ 'stories' => 'Stories',
+ 'channel' => 'Channel',
+ 'explore_item_info' => 'ExploreItemInfo',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ExploreItemInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ExploreItemInfo.php
new file mode 100755
index 0000000..626a3f9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ExploreItemInfo.php
@@ -0,0 +1,40 @@
+ 'int',
+ 'total_num_columns' => 'int',
+ 'aspect_ratio' => 'int',
+ 'autoplay' => 'bool',
+ 'destination_view' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FaceModels.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FaceModels.php
new file mode 100755
index 0000000..8c8fc45
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FaceModels.php
@@ -0,0 +1,30 @@
+ '',
+ 'face_detect_model' => '',
+ 'pdm_multires' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FacebookUser.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FacebookUser.php
new file mode 100755
index 0000000..5c1c470
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FacebookUser.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'name' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FeedAysf.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FeedAysf.php
new file mode 100755
index 0000000..74d124c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FeedAysf.php
@@ -0,0 +1,75 @@
+ '',
+ 'uuid' => 'string',
+ 'view_all_text' => '',
+ 'feed_position' => '',
+ 'landing_site_title' => '',
+ 'is_dismissable' => '',
+ 'suggestions' => 'Suggestion[]',
+ 'should_refill' => '',
+ 'display_new_unit' => '',
+ 'fetch_user_details' => '',
+ 'title' => '',
+ 'activator' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FeedItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FeedItem.php
new file mode 100755
index 0000000..1d94fcd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FeedItem.php
@@ -0,0 +1,45 @@
+ 'Item',
+ 'stories_netego' => 'StoriesNetego',
+ 'ad4ad' => 'Ad4ad',
+ 'suggested_users' => 'SuggestedUsers',
+ 'end_of_feed_demarcator' => '',
+ 'ad_link_type' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FelixShare.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FelixShare.php
new file mode 100755
index 0000000..3832bb5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FelixShare.php
@@ -0,0 +1,25 @@
+ 'Item[]',
+ 'text' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FillItems.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FillItems.php
new file mode 100755
index 0000000..cd3de75
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FillItems.php
@@ -0,0 +1,20 @@
+ 'Item',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FollowersUnit.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FollowersUnit.php
new file mode 100755
index 0000000..5f62fd6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FollowersUnit.php
@@ -0,0 +1,60 @@
+ 'string',
+ 'followers_delta_from_last_week' => 'int',
+ 'gender_graph' => 'DataGraph',
+ 'all_followers_age_graph' => 'DataGraph',
+ 'men_followers_age_graph' => 'DataGraph',
+ 'women_followers_age_graph' => 'DataGraph',
+ 'followers_top_cities_graph' => 'DataGraph',
+ 'followers_top_countries_graph' => 'DataGraph',
+ 'week_daily_followers_graph' => 'DataGraph',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FormerUsername.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FormerUsername.php
new file mode 100755
index 0000000..a6a2a8b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FormerUsername.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'change_timestamp' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FormerUsernameInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FormerUsernameInfo.php
new file mode 100755
index 0000000..9bd2246
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FormerUsernameInfo.php
@@ -0,0 +1,20 @@
+ 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FriendshipStatus.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FriendshipStatus.php
new file mode 100755
index 0000000..7e5b7e0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FriendshipStatus.php
@@ -0,0 +1,70 @@
+ 'bool',
+ 'followed_by' => 'bool',
+ 'incoming_request' => 'bool',
+ 'outgoing_request' => 'bool',
+ 'is_private' => 'bool',
+ 'is_blocking_reel' => 'bool',
+ 'is_muting_reel' => 'bool',
+ 'is_restricted' => 'bool',
+ 'blocking' => 'bool',
+ 'muting' => 'bool',
+ 'is_bestie' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FullItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FullItem.php
new file mode 100755
index 0000000..6a35edb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/FullItem.php
@@ -0,0 +1,20 @@
+ 'Channel',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Gating.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Gating.php
new file mode 100755
index 0000000..fa76988
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Gating.php
@@ -0,0 +1,35 @@
+ '',
+ 'description' => '',
+ 'buttons' => '',
+ 'title' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/GenericMegaphone.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/GenericMegaphone.php
new file mode 100755
index 0000000..b749bdb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/GenericMegaphone.php
@@ -0,0 +1,85 @@
+ '',
+ 'title' => '',
+ 'message' => '',
+ 'dismissible' => '',
+ 'icon' => '',
+ 'buttons' => 'Button[]',
+ 'megaphone_version' => '',
+ 'button_layout' => '',
+ 'action_info' => '',
+ 'button_location' => '',
+ 'background_color' => '',
+ 'title_color' => '',
+ 'message_color' => '',
+ 'uuid' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/GraphData.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/GraphData.php
new file mode 100755
index 0000000..7be4441
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/GraphData.php
@@ -0,0 +1,50 @@
+ 'GraphNode',
+ '__typename' => 'string',
+ 'name' => 'string',
+ 'user' => 'ShadowInstagramUser',
+ 'error' => '',
+ 'catalog_items' => 'CatalogData',
+ 'me' => 'MeGraphData',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/GraphNode.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/GraphNode.php
new file mode 100755
index 0000000..f9ede7a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/GraphNode.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'name' => 'string',
+ 'catalog_items' => 'CatalogData',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Groups.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Groups.php
new file mode 100755
index 0000000..34bbea3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Groups.php
@@ -0,0 +1,25 @@
+ '',
+ 'items' => 'Item[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Hashtag.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Hashtag.php
new file mode 100755
index 0000000..d91d751
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Hashtag.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'name' => 'string',
+ 'media_count' => 'int',
+ 'profile_pic_url' => 'string',
+ 'follow_status' => 'int',
+ 'following' => 'int',
+ 'allow_following' => 'int',
+ 'allow_muting_story' => 'bool',
+ 'related_tags' => '',
+ 'debug_info' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Headline.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Headline.php
new file mode 100755
index 0000000..d8ddc95
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Headline.php
@@ -0,0 +1,70 @@
+ 'string',
+ 'user' => 'User',
+ 'user_id' => 'string',
+ 'pk' => 'string',
+ 'text' => 'string',
+ 'type' => 'int',
+ 'created_at' => 'string',
+ 'created_at_utc' => 'string',
+ 'media_id' => 'string',
+ 'bit_flags' => 'int',
+ 'status' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/HiddenEntities.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/HiddenEntities.php
new file mode 100755
index 0000000..7e7996d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/HiddenEntities.php
@@ -0,0 +1,34 @@
+ '',
+ 'hashtag' => '',
+ 'place' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/HideReason.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/HideReason.php
new file mode 100755
index 0000000..3f84590
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/HideReason.php
@@ -0,0 +1,31 @@
+ 'string',
+ /*
+ * A computer string such as "NOT_RELEVANT" or "KEEP_SEEING_THIS".
+ */
+ 'reason' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/IOSLinks.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/IOSLinks.php
new file mode 100755
index 0000000..c80eb40
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/IOSLinks.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'canvasDocId' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/IabAutofillOptoutInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/IabAutofillOptoutInfo.php
new file mode 100755
index 0000000..70bcffc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/IabAutofillOptoutInfo.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'is_iab_autofill_optout' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Image.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Image.php
new file mode 100755
index 0000000..8219872
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Image.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'width' => 'int',
+ 'height' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ImageCandidate.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ImageCandidate.php
new file mode 100755
index 0000000..d3c2645
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ImageCandidate.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'width' => 'int',
+ 'height' => 'int',
+ 'estimated_scans_sizes' => 'int[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Image_Versions2.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Image_Versions2.php
new file mode 100755
index 0000000..a4e8e0f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Image_Versions2.php
@@ -0,0 +1,25 @@
+ 'ImageCandidate[]',
+ 'trace_token' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/In.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/In.php
new file mode 100755
index 0000000..61a0a80
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/In.php
@@ -0,0 +1,45 @@
+ 'float[]',
+ 'user' => 'User',
+ 'time_in_video' => '',
+ 'start_time_in_video_in_sec' => '',
+ 'duration_in_video_in_sec' => '',
+ 'product' => 'Product',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Injected.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Injected.php
new file mode 100755
index 0000000..00e63eb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Injected.php
@@ -0,0 +1,120 @@
+ 'string',
+ 'show_icon' => 'bool',
+ 'hide_label' => 'string',
+ 'invalidation' => '', // Only encountered as NULL.
+ 'is_demo' => 'bool',
+ 'view_tags' => '', // Only seen as [].
+ 'is_holdout' => 'bool',
+ 'is_leadgen_native_eligible' => 'bool',
+ 'tracking_token' => 'string',
+ 'show_ad_choices' => 'bool',
+ 'ad_title' => 'string',
+ 'about_ad_params' => 'string',
+ 'direct_share' => 'bool',
+ 'ad_id' => 'string',
+ 'display_viewability_eligible' => 'bool',
+ 'fb_page_url' => 'string',
+ 'hide_reasons_v2' => 'HideReason[]',
+ 'hide_flow_type' => 'int',
+ 'cookies' => 'string[]',
+ 'lead_gen_form_id' => 'string',
+ 'ads_debug_info' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/InlineFollow.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/InlineFollow.php
new file mode 100755
index 0000000..1513f10
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/InlineFollow.php
@@ -0,0 +1,30 @@
+ 'User',
+ 'following' => 'bool',
+ 'outgoing_request' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Insights.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Insights.php
new file mode 100755
index 0000000..376ee20
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Insights.php
@@ -0,0 +1,20 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Item.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Item.php
new file mode 100755
index 0000000..8890b89
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Item.php
@@ -0,0 +1,959 @@
+ 'string',
+ 'pk' => 'string',
+ 'id' => 'string',
+ 'device_timestamp' => 'string',
+ /*
+ * A number describing what type of media this is. Should be compared
+ * against the `Item::PHOTO`, `Item::VIDEO` and `Item::CAROUSEL` constants!
+ */
+ 'media_type' => 'int',
+ 'dynamic_item_id' => 'string',
+ 'code' => 'string',
+ 'client_cache_key' => 'string',
+ 'filter_type' => 'int',
+ 'product_type' => 'string',
+ 'nearly_complete_copyright_match' => 'bool',
+ 'media_cropping_info' => 'MediaCroppingInfo',
+ 'image_versions2' => 'Image_Versions2',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ 'caption_position' => 'float',
+ 'is_reel_media' => 'bool',
+ 'video_versions' => 'VideoVersions[]',
+ 'has_audio' => 'bool',
+ 'video_duration' => 'float',
+ 'user' => 'User',
+ 'can_see_insights_as_brand' => 'bool',
+ 'caption' => 'Caption',
+ 'headline' => 'Headline',
+ 'title' => 'string',
+ 'caption_is_edited' => 'bool',
+ 'photo_of_you' => 'bool',
+ 'fb_user_tags' => 'Usertag',
+ 'can_viewer_save' => 'bool',
+ 'has_viewer_saved' => 'bool',
+ 'organic_tracking_token' => 'string',
+ 'follow_hashtag_info' => 'Hashtag',
+ 'expiring_at' => 'string',
+ 'audience' => 'string',
+ 'is_dash_eligible' => 'int',
+ 'video_dash_manifest' => 'string',
+ 'number_of_qualities' => 'int',
+ 'video_codec' => 'string',
+ 'thumbnails' => 'Thumbnail',
+ 'can_reshare' => 'bool',
+ 'can_reply' => 'bool',
+ 'is_pride_media' => 'bool',
+ 'can_viewer_reshare' => 'bool',
+ 'visibility' => '',
+ 'attribution' => 'Attribution',
+ /*
+ * This is actually a float in the reply, but is always `.0`, so we cast
+ * it to an int instead to make the number easier to manage.
+ */
+ 'view_count' => 'int',
+ 'viewer_count' => 'int',
+ 'comment_count' => 'int',
+ 'can_view_more_preview_comments' => 'bool',
+ 'has_more_comments' => 'bool',
+ 'max_num_visible_preview_comments' => 'int',
+ /*
+ * Preview of comments via feed replies.
+ *
+ * If "has_more_comments" is FALSE, then this has ALL of the comments.
+ * Otherwise, you'll need to get all comments by querying the media.
+ */
+ 'preview_comments' => 'Comment[]',
+ /*
+ * Comments for the item.
+ *
+ * TODO: As of mid-2017, this field seems to no longer be used for
+ * timeline feed items? They now use "preview_comments" instead. But we
+ * won't delete it, since some other feed MAY use this property for ITS
+ * Item object.
+ */
+ 'comments' => 'Comment[]',
+ 'comments_disabled' => '',
+ 'reel_mentions' => 'ReelMention[]',
+ 'story_cta' => 'StoryCta[]',
+ 'next_max_id' => 'string',
+ 'carousel_media' => 'CarouselMedia[]',
+ 'carousel_media_type' => '',
+ 'carousel_media_count' => 'int',
+ 'likers' => 'User[]',
+ 'facepile_top_likers' => 'User[]',
+ 'like_count' => 'int',
+ 'preview' => 'string',
+ 'has_liked' => 'bool',
+ 'explore_context' => 'string',
+ 'explore_source_token' => 'string',
+ 'explore_hide_comments' => 'bool',
+ 'explore' => 'Explore',
+ 'impression_token' => 'string',
+ 'usertags' => 'Usertag',
+ 'media' => 'Media',
+ 'stories' => 'Stories',
+ 'top_likers' => 'string[]',
+ 'direct_reply_to_author_enabled' => 'bool',
+ 'suggested_users' => 'SuggestedUsers',
+ 'is_new_suggestion' => 'bool',
+ 'comment_likes_enabled' => 'bool',
+ 'location' => 'Location',
+ 'lat' => 'float',
+ 'lng' => 'float',
+ 'channel' => 'Channel',
+ 'gating' => 'Gating',
+ 'injected' => 'Injected',
+ 'placeholder' => 'Placeholder',
+ 'algorithm' => 'string',
+ 'connection_id' => 'string',
+ 'social_context' => 'string',
+ 'icon' => '',
+ 'media_ids' => 'string[]',
+ 'media_id' => 'string',
+ 'thumbnail_urls' => '',
+ 'large_urls' => '',
+ 'media_infos' => '',
+ 'value' => 'float',
+ 'followed_by' => 'bool',
+ 'collapse_comments' => 'bool',
+ 'link' => 'string',
+ 'link_text' => 'string',
+ 'link_hint_text' => 'string',
+ 'iTunesItem' => '',
+ 'ad_header_style' => 'int',
+ 'ad_metadata' => 'AdMetadata[]',
+ 'ad_action' => 'string',
+ 'ad_link_type' => 'int',
+ 'dr_ad_type' => 'int',
+ 'android_links' => 'AndroidLinks[]',
+ 'ios_links' => 'IOSLinks[]',
+ 'iab_autofill_optout_info' => 'IabAutofillOptoutInfo',
+ 'force_overlay' => 'bool',
+ 'hide_nux_text' => 'bool',
+ 'overlay_text' => 'string',
+ 'overlay_title' => 'string',
+ 'overlay_subtitle' => 'string',
+ 'fb_page_url' => 'string',
+ 'playback_duration_secs' => '',
+ 'url_expire_at_secs' => '',
+ 'is_sidecar_child' => '',
+ 'comment_threading_enabled' => 'bool',
+ 'cover_media' => 'CoverMedia',
+ 'saved_collection_ids' => 'string[]',
+ 'boosted_status' => '',
+ 'boost_unavailable_reason' => '',
+ 'viewers' => 'User[]',
+ 'viewer_cursor' => '',
+ 'total_viewer_count' => 'int',
+ 'multi_author_reel_names' => '',
+ 'screenshotter_user_ids' => '',
+ 'reel_share' => 'ReelShare',
+ 'organic_post_id' => 'string',
+ 'sponsor_tags' => 'User[]',
+ 'story_poll_voter_infos' => '',
+ 'imported_taken_at' => '',
+ 'lead_gen_form_id' => 'string',
+ 'ad_id' => 'string',
+ 'actor_fbid' => 'string',
+ 'is_ad4ad' => '',
+ 'commenting_disabled_for_viewer' => '',
+ 'is_seen' => '',
+ 'story_events' => '',
+ 'story_hashtags' => 'StoryHashtag[]',
+ 'story_polls' => '',
+ 'story_feed_media' => '',
+ 'story_sound_on' => '',
+ 'creative_config' => '',
+ 'story_app_attribution' => 'StoryAppAttribution',
+ 'story_locations' => 'StoryLocation[]',
+ 'story_sliders' => '',
+ 'story_friend_lists' => '',
+ 'story_product_items' => '',
+ 'story_questions' => 'StoryQuestions[]',
+ 'story_question_responder_infos' => 'StoryQuestionResponderInfos[]',
+ 'story_countdowns' => 'StoryCountdowns[]',
+ 'story_music_stickers' => '',
+ 'supports_reel_reactions' => 'bool',
+ 'show_one_tap_fb_share_tooltip' => 'bool',
+ 'has_shared_to_fb' => 'bool',
+ 'main_feed_carousel_starting_media_id' => 'string',
+ 'main_feed_carousel_has_unseen_cover_media' => 'bool',
+ 'inventory_source' => 'string',
+ 'is_eof' => 'bool',
+ 'top_followers' => 'string[]',
+ 'top_followers_count' => 'int',
+ 'follower_count' => 'int',
+ 'post_count' => 'int',
+ 'video_subtitles_uri' => 'string',
+ 'story_is_saved_to_archive' => 'bool',
+ 'timezone_offset' => 'int',
+ 'xpost_deny_reason' => 'string',
+ 'product_tags' => 'ProductTags',
+ 'inline_composer_display_condition' => 'string',
+ 'inline_composer_imp_trigger_time' => 'int',
+ 'highlight_reel_ids' => 'string[]',
+ 'total_screenshot_count' => 'int',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'dominant_color' => 'string',
+ 'audio' => 'AudioContext',
+ 'story_quizs' => 'StoryQuizs[]',
+ 'story_quiz_participant_infos' => 'StoryQuizParticipantInfo[]',
+ ];
+
+ /**
+ * Get the web URL for this media item.
+ *
+ * @return string
+ */
+ public function getItemUrl()
+ {
+ return sprintf('https://www.instagram.com/p/%s/', $this->_getProperty('code'));
+ }
+
+ /**
+ * Checks whether this media item is an advertisement.
+ *
+ * @return bool
+ */
+ public function isAd()
+ {
+ return $this->_getProperty('dr_ad_type') !== null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LayoutContent.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LayoutContent.php
new file mode 100755
index 0000000..f9946c5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LayoutContent.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'related' => 'Tag[]',
+ 'medias' => 'SectionMedia[]',
+ 'feed_type' => 'string',
+ 'fill_items' => 'FillItems[]',
+ 'explore_item_info' => 'ExploreItemInfo',
+ 'tabs_info' => 'TabsInfo',
+ 'full_item' => 'FullItem',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Link.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Link.php
new file mode 100755
index 0000000..3f7ad4c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Link.php
@@ -0,0 +1,45 @@
+ 'int',
+ 'end' => 'int',
+ 'id' => 'string',
+ 'type' => 'string',
+ 'text' => 'string',
+ 'link_context' => 'LinkContext',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LinkContext.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LinkContext.php
new file mode 100755
index 0000000..5b87972
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LinkContext.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'link_title' => 'string',
+ 'link_summary' => 'string',
+ 'link_image_url' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LiveComment.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LiveComment.php
new file mode 100755
index 0000000..9d2350f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LiveComment.php
@@ -0,0 +1,30 @@
+ 'Comment',
+ 'offset' => '',
+ 'event' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LiveVideoShare.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LiveVideoShare.php
new file mode 100755
index 0000000..2812b15
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LiveVideoShare.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'broadcast' => 'Broadcast',
+ 'video_offset' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LiveViewerInvite.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LiveViewerInvite.php
new file mode 100755
index 0000000..97d8ca4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LiveViewerInvite.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'broadcast' => 'Broadcast',
+ 'title' => 'string',
+ 'message' => 'string',
+ 'is_linked' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Location.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Location.php
new file mode 100755
index 0000000..ea2ae08
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Location.php
@@ -0,0 +1,157 @@
+ 'string',
+ 'external_id_source' => 'string',
+ 'external_source' => 'string',
+ 'address' => 'string',
+ 'lat' => 'float',
+ 'lng' => 'float',
+ 'external_id' => 'string',
+ 'facebook_places_id' => 'string',
+ 'city' => 'string',
+ 'pk' => 'string',
+ 'short_name' => 'string',
+ 'facebook_events_id' => 'string',
+ 'start_time' => '',
+ 'end_time' => '',
+ 'location_dict' => 'Location',
+ 'type' => '',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_username' => 'string',
+ 'time_granularity' => '',
+ 'timezone' => '',
+ /*
+ * Country number such as int(398), but it has no relation to actual
+ * country codes, so the number is useless...
+ */
+ 'country' => 'int',
+ /*
+ * Regular unix timestamp of when the location was created.
+ */
+ 'created_at' => 'string',
+ /*
+ * Some kind of internal number to signify what type of event a special
+ * location (such as a festival) is. We've only seen this with int(0).
+ */
+ 'event_category' => 'int',
+ /*
+ * 64-bit integer with the facebook places ID for the location.
+ */
+ 'place_fbid' => 'string',
+ /*
+ * Human-readable name of the facebook place for the location.
+ */
+ 'place_name' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LocationItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LocationItem.php
new file mode 100755
index 0000000..38feb34
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/LocationItem.php
@@ -0,0 +1,35 @@
+ '',
+ 'subtitle' => '',
+ 'location' => 'Location',
+ 'title' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MeGraphData.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MeGraphData.php
new file mode 100755
index 0000000..1dac9dd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MeGraphData.php
@@ -0,0 +1,25 @@
+ 'CatalogData',
+ 'id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Media.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Media.php
new file mode 100755
index 0000000..08797c4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Media.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'id' => 'string',
+ 'user' => 'User',
+ 'expiring_at' => '',
+ 'comment_threading_enabled' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaCroppingInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaCroppingInfo.php
new file mode 100755
index 0000000..ab11ef9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaCroppingInfo.php
@@ -0,0 +1,20 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaData.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaData.php
new file mode 100755
index 0000000..902c9da
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaData.php
@@ -0,0 +1,43 @@
+ 'Image_Versions2',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ /*
+ * A number describing what type of media this is.
+ */
+ 'media_type' => 'int',
+ 'video_versions' => 'VideoVersions[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaInsights.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaInsights.php
new file mode 100755
index 0000000..ac0bf8f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaInsights.php
@@ -0,0 +1,50 @@
+ 'string[]',
+ 'impression_count' => 'int',
+ 'engagement_count' => 'int',
+ 'avg_engagement_count' => 'int',
+ 'comment_count' => 'int',
+ 'save_count' => 'int',
+ 'like_count' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaShare.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaShare.php
new file mode 100755
index 0000000..8201084
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/MediaShare.php
@@ -0,0 +1,25 @@
+ 'Item',
+ 'text' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Megaphone.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Megaphone.php
new file mode 100755
index 0000000..7068983
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Megaphone.php
@@ -0,0 +1,20 @@
+ 'GenericMegaphone',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Nametag.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Nametag.php
new file mode 100755
index 0000000..1e20417
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Nametag.php
@@ -0,0 +1,40 @@
+ 'int',
+ 'gradient' => 'int',
+ 'emoji' => 'string',
+ 'emoji_color' => 'string',
+ 'selfie_sticker' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Owner.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Owner.php
new file mode 100755
index 0000000..51b434f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Owner.php
@@ -0,0 +1,60 @@
+ '',
+ 'pk' => 'string',
+ 'name' => 'string',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_username' => 'string',
+ 'short_name' => 'string',
+ 'lat' => 'float',
+ 'lng' => 'float',
+ 'location_dict' => 'Location',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PageInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PageInfo.php
new file mode 100755
index 0000000..2206725
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PageInfo.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'has_next_page' => 'bool',
+ 'has_previous_page' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Param.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Param.php
new file mode 100755
index 0000000..9e6ad05
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Param.php
@@ -0,0 +1,25 @@
+ '',
+ 'value' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Participants.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Participants.php
new file mode 100755
index 0000000..4474261
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Participants.php
@@ -0,0 +1,30 @@
+ 'User',
+ 'answer' => 'int',
+ 'ts' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PermanentItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PermanentItem.php
new file mode 100755
index 0000000..dea7ca1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PermanentItem.php
@@ -0,0 +1,90 @@
+ 'string',
+ 'user_id' => 'string',
+ 'timestamp' => 'string',
+ 'item_type' => 'string',
+ 'profile' => 'User',
+ 'text' => 'string',
+ 'location' => 'Location',
+ 'like' => '',
+ 'media' => 'MediaData',
+ 'link' => 'Link',
+ 'media_share' => 'Item',
+ 'reel_share' => 'ReelShare',
+ 'client_context' => 'string',
+ 'live_video_share' => 'LiveVideoShare',
+ 'live_viewer_invite' => 'LiveViewerInvite',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PhoneVerificationSettings.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PhoneVerificationSettings.php
new file mode 100755
index 0000000..211b0b1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PhoneVerificationSettings.php
@@ -0,0 +1,35 @@
+ 'int',
+ 'max_sms_count' => 'int',
+ 'robocall_count_down_time_sec' => 'int',
+ 'robocall_after_max_sms' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Placeholder.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Placeholder.php
new file mode 100755
index 0000000..515d0c5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Placeholder.php
@@ -0,0 +1,30 @@
+ 'bool',
+ 'title' => 'string',
+ 'message' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PostLive.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PostLive.php
new file mode 100755
index 0000000..3266000
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PostLive.php
@@ -0,0 +1,20 @@
+ 'PostLiveItem[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PostLiveItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PostLiveItem.php
new file mode 100755
index 0000000..afa9b5f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PostLiveItem.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'user' => 'User',
+ 'broadcasts' => 'Broadcast[]',
+ 'peak_viewer_count' => 'int',
+ 'last_seen_broadcast_ts' => '',
+ 'can_reply' => '',
+ 'ranked_position' => '',
+ 'seen_ranked_position' => '',
+ 'muted' => '',
+ 'can_reshare' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Prefill.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Prefill.php
new file mode 100755
index 0000000..8384250
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Prefill.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'candidates' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PrimaryCountryInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PrimaryCountryInfo.php
new file mode 100755
index 0000000..9ef45f8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PrimaryCountryInfo.php
@@ -0,0 +1,30 @@
+ 'bool',
+ 'has_country' => 'bool',
+ 'country_name' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Product.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Product.php
new file mode 100755
index 0000000..67d5333
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Product.php
@@ -0,0 +1,84 @@
+ 'string',
+ 'price' => 'string',
+ 'current_price' => 'string',
+ 'full_price' => 'string',
+ 'product_id' => 'string',
+ 'has_viewer_saved' => 'bool',
+ 'description' => 'string',
+ 'main_image' => 'ProductImage',
+ 'thumbnail_image' => 'ProductImage',
+ 'product_images' => 'ProductImage[]',
+ 'external_url' => 'string',
+ 'checkout_style' => 'string',
+ 'review_status' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ProductImage.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ProductImage.php
new file mode 100755
index 0000000..1fea73f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ProductImage.php
@@ -0,0 +1,20 @@
+ 'Image_Versions2',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ProductShare.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ProductShare.php
new file mode 100755
index 0000000..df31acf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ProductShare.php
@@ -0,0 +1,30 @@
+ 'Item',
+ 'text' => 'string',
+ 'product' => 'Product',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ProductTags.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ProductTags.php
new file mode 100755
index 0000000..42cab36
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ProductTags.php
@@ -0,0 +1,20 @@
+ 'In[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PromotionsUnit.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PromotionsUnit.php
new file mode 100755
index 0000000..822a013
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PromotionsUnit.php
@@ -0,0 +1,20 @@
+ 'SummaryPromotions',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PushSettings.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PushSettings.php
new file mode 100755
index 0000000..f5a9ac1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/PushSettings.php
@@ -0,0 +1,45 @@
+ '',
+ 'eligible' => '',
+ 'title' => '',
+ 'example' => '',
+ 'options' => '',
+ 'checked' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPData.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPData.php
new file mode 100755
index 0000000..5034e76
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPData.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'data' => 'QPViewerData',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPExtraInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPExtraInfo.php
new file mode 100755
index 0000000..f2e2cc7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPExtraInfo.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'extra_info' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPNode.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPNode.php
new file mode 100755
index 0000000..6ccf4b6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPNode.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'promotion_id' => 'string',
+ 'max_impressions' => 'int',
+ 'triggers' => 'string[]',
+ 'contextual_filters' => 'ContextualFilters',
+ 'template' => 'Template',
+ 'creatives' => 'Creative[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPSurface.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPSurface.php
new file mode 100755
index 0000000..75c50f3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPSurface.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'cooldown' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPViewerData.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPViewerData.php
new file mode 100755
index 0000000..5c2f755
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QPViewerData.php
@@ -0,0 +1,20 @@
+ 'Viewer',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QueryResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QueryResponse.php
new file mode 100755
index 0000000..44eef1b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QueryResponse.php
@@ -0,0 +1,20 @@
+ 'ShadowInstagramUser',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QuestionSticker.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QuestionSticker.php
new file mode 100755
index 0000000..a2718a0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QuestionSticker.php
@@ -0,0 +1,56 @@
+ 'string',
+ 'question' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'text_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'background_color' => 'string',
+ 'viewer_can_interact' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'question_type' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QuizSticker.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QuizSticker.php
new file mode 100755
index 0000000..6bce739
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/QuizSticker.php
@@ -0,0 +1,70 @@
+ 'string',
+ 'quiz_id' => 'string',
+ 'question' => 'string',
+ 'tallies' => 'Tallies[]',
+ 'correct_answer' => 'int',
+ 'viewer_can_answer' => 'bool',
+ 'finished' => 'bool',
+ 'text_color' => 'string',
+ 'start_background_color' => 'string',
+ 'end_background_color' => 'string',
+ 'viewer_answer' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Ranking.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Ranking.php
new file mode 100755
index 0000000..8fc647a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Ranking.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'score_map' => '',
+ 'expiration_ms' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Reel.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Reel.php
new file mode 100755
index 0000000..7dcc069
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Reel.php
@@ -0,0 +1,107 @@
+ 'string',
+ /*
+ * Unix "taken_at" timestamp of the newest item in their story reel.
+ */
+ 'latest_reel_media' => 'string',
+ /*
+ * The "taken_at" timestamp of the last story media you have seen for
+ * that user (the current reel's user). Defaults to `0` (not seen).
+ */
+ 'seen' => 'string',
+ 'can_reply' => 'bool',
+ 'can_reshare' => 'bool',
+ 'reel_type' => 'string',
+ 'cover_media' => 'CoverMedia',
+ 'user' => 'User',
+ 'items' => 'Item[]',
+ 'ranked_position' => 'string',
+ 'title' => 'string',
+ 'seen_ranked_position' => 'string',
+ 'expiring_at' => 'string',
+ 'has_besties_media' => 'bool', // Uses int(0) for false and 1 for true.
+ 'location' => 'Location',
+ 'prefetch_count' => 'int',
+ 'broadcast' => 'Broadcast',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ReelMention.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ReelMention.php
new file mode 100755
index 0000000..d3f0192
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ReelMention.php
@@ -0,0 +1,65 @@
+ 'User',
+ 'is_hidden' => 'int',
+ 'display_type' => 'string',
+ 'is_sticker' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ReelShare.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ReelShare.php
new file mode 100755
index 0000000..8dcceca
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ReelShare.php
@@ -0,0 +1,70 @@
+ 'Item[]',
+ 'story_ranking_token' => 'string',
+ 'broadcasts' => '',
+ 'sticker_version' => 'int',
+ 'text' => 'string',
+ 'type' => 'string',
+ 'is_reel_persisted' => 'bool',
+ 'reel_owner_id' => 'string',
+ 'reel_type' => 'string',
+ 'media' => 'Item',
+ 'mentioned_user_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Related.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Related.php
new file mode 100755
index 0000000..4658f02
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Related.php
@@ -0,0 +1,30 @@
+ '',
+ 'id' => 'string',
+ 'type' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Responder.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Responder.php
new file mode 100755
index 0000000..7d23dfb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Responder.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'has_shared_response' => 'bool',
+ 'id' => 'string',
+ 'user' => 'User',
+ 'ts' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/RewriteRule.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/RewriteRule.php
new file mode 100755
index 0000000..d6a5302
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/RewriteRule.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'replacer' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SavedFeedItem.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SavedFeedItem.php
new file mode 100755
index 0000000..04978b6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SavedFeedItem.php
@@ -0,0 +1,20 @@
+ 'Item',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Section.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Section.php
new file mode 100755
index 0000000..f60678b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Section.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'title' => 'string',
+ 'items' => 'Item[]',
+ 'layout_type' => 'string',
+ 'layout_content' => 'LayoutContent',
+ 'feed_type' => 'string',
+ 'explore_item_info' => 'ExploreItemInfo',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SectionMedia.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SectionMedia.php
new file mode 100755
index 0000000..6c6130d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SectionMedia.php
@@ -0,0 +1,20 @@
+ 'Item',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ServerDataInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ServerDataInfo.php
new file mode 100755
index 0000000..f33a837
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ServerDataInfo.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'nonce' => 'string',
+ 'conferenceName' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ShadowInstagramUser.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ShadowInstagramUser.php
new file mode 100755
index 0000000..c4553eb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/ShadowInstagramUser.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'instagram_user_id' => 'string',
+ 'followers_count' => 'int',
+ 'username' => 'string',
+ 'profile_picture' => 'Image',
+ 'business_manager' => 'BusinessManager',
+ 'error' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SharePrefillEntities.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SharePrefillEntities.php
new file mode 100755
index 0000000..4f4ec65
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SharePrefillEntities.php
@@ -0,0 +1,20 @@
+ 'DirectThread',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SharedFollower.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SharedFollower.php
new file mode 100755
index 0000000..f505d33
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SharedFollower.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'username' => 'string',
+ 'full_name' => 'string',
+ 'is_private' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_id' => 'string',
+ 'is_verified' => 'bool',
+ 'has_anonymous_profile_picture' => 'bool',
+ 'reel_auto_archive' => 'string',
+ 'overlap_score' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SharedFollowerAccountsInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SharedFollowerAccountsInfo.php
new file mode 100755
index 0000000..f239746
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SharedFollowerAccountsInfo.php
@@ -0,0 +1,20 @@
+ 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Slot.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Slot.php
new file mode 100755
index 0000000..cfae147
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Slot.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'cooldown' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StaticStickers.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StaticStickers.php
new file mode 100755
index 0000000..479622d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StaticStickers.php
@@ -0,0 +1,30 @@
+ '',
+ 'id' => 'string',
+ 'stickers' => 'Stickers[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StepData.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StepData.php
new file mode 100755
index 0000000..1082861
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StepData.php
@@ -0,0 +1,80 @@
+ 'string',
+ 'phone_number' => 'string',
+ 'phone_number_formatted' => 'string',
+ 'email' => 'string',
+ 'fb_access_token' => 'string',
+ 'big_blue_token' => 'string',
+ 'google_oauth_token' => 'string',
+ 'security_code' => 'string',
+ 'sms_resend_delay' => 'int',
+ 'resend_delay' => 'int',
+ 'contact_point' => 'string',
+ 'form_type' => 'string',
+ 'phone_number_preview' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Stickers.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Stickers.php
new file mode 100755
index 0000000..fdc47e5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Stickers.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'tray_image_width_ratio' => '',
+ 'image_height' => '',
+ 'image_width_ratio' => '',
+ 'type' => '',
+ 'image_width' => '',
+ 'name' => '',
+ 'image_url' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Stories.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Stories.php
new file mode 100755
index 0000000..382b5d6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Stories.php
@@ -0,0 +1,35 @@
+ '',
+ 'tray' => 'StoryTray[]',
+ 'id' => 'string',
+ 'top_live' => 'TopLive',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoriesNetego.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoriesNetego.php
new file mode 100755
index 0000000..e4f0ac7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoriesNetego.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'hide_unit_if_seen' => 'string',
+ 'id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Story.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Story.php
new file mode 100755
index 0000000..3250e2c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Story.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'counts' => 'Counts',
+ 'args' => 'Args',
+ 'type' => 'int',
+ 'story_type' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryAppAttribution.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryAppAttribution.php
new file mode 100755
index 0000000..04df311
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryAppAttribution.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'app_icon_url' => 'string',
+ 'content_url' => 'string',
+ 'id' => 'string',
+ 'link' => 'string',
+ 'name' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryCountdowns.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryCountdowns.php
new file mode 100755
index 0000000..6c79803
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryCountdowns.php
@@ -0,0 +1,60 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float',
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ 'is_hidden' => 'int',
+ 'countdown_sticker' => 'CountdownSticker',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryCta.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryCta.php
new file mode 100755
index 0000000..56884e9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryCta.php
@@ -0,0 +1,25 @@
+ 'AndroidLinks[]',
+ 'felix_deep_link' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryHashtag.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryHashtag.php
new file mode 100755
index 0000000..f0c50a1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryHashtag.php
@@ -0,0 +1,70 @@
+ 'Hashtag',
+ 'attribution' => 'string',
+ 'custom_title' => 'string',
+ 'is_hidden' => 'int',
+ 'is_sticker' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryLocation.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryLocation.php
new file mode 100755
index 0000000..bc6bdfc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryLocation.php
@@ -0,0 +1,65 @@
+ 'Location',
+ 'attribution' => 'string',
+ 'is_hidden' => 'int',
+ 'is_sticker' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestionResponderInfos.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestionResponderInfos.php
new file mode 100755
index 0000000..0a24469
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestionResponderInfos.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'question' => 'string',
+ 'question_type' => 'string',
+ 'background_color' => 'string',
+ 'text_color' => 'string',
+ 'responders' => 'Responder[]',
+ 'max_id' => '',
+ 'more_available' => 'bool',
+ 'question_response_count' => 'int',
+ 'latest_question_response_time' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestions.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestions.php
new file mode 100755
index 0000000..83fbdcc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestions.php
@@ -0,0 +1,60 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float',
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ 'is_hidden' => 'int',
+ 'question_sticker' => 'QuestionSticker',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizParticipantInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizParticipantInfo.php
new file mode 100755
index 0000000..3f68f45
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizParticipantInfo.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'participants' => 'Participants[]',
+ 'max_id' => '',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizs.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizs.php
new file mode 100755
index 0000000..192aec5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizs.php
@@ -0,0 +1,65 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float',
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ 'is_hidden' => 'int',
+ 'is_sticker' => 'int',
+ 'quiz_sticker' => 'QuizSticker',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryShare.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryShare.php
new file mode 100755
index 0000000..6b1724d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryShare.php
@@ -0,0 +1,40 @@
+ 'Item',
+ 'text' => 'string',
+ 'title' => 'string',
+ 'message' => 'string',
+ 'is_linked' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryTray.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryTray.php
new file mode 100755
index 0000000..d7a2ff1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryTray.php
@@ -0,0 +1,162 @@
+ 'string',
+ 'items' => 'Item[]',
+ 'hide_from_feed_unit' => 'bool',
+ 'media_ids' => 'string[]',
+ 'has_pride_media' => 'bool',
+ 'user' => 'User',
+ 'can_reply' => '',
+ 'expiring_at' => '',
+ 'seen_ranked_position' => 'string',
+ /*
+ * The "taken_at" timestamp of the last story media you have seen for
+ * that user (the current tray's user). Defaults to `0` (not seen).
+ */
+ 'seen' => 'string',
+ /*
+ * Unix "taken_at" timestamp of the newest item in their story reel.
+ */
+ 'latest_reel_media' => 'string',
+ 'ranked_position' => 'string',
+ 'is_nux' => '',
+ 'show_nux_tooltip' => '',
+ 'muted' => '',
+ 'prefetch_count' => 'int',
+ 'location' => 'Location',
+ 'source_token' => '',
+ 'owner' => 'Owner',
+ 'nux_id' => 'string',
+ 'dismiss_card' => 'DismissCard',
+ 'can_reshare' => '',
+ 'has_besties_media' => 'bool',
+ 'reel_type' => 'string',
+ 'unique_integer_reel_id' => 'string',
+ 'cover_media' => 'CoverMedia',
+ 'title' => 'string',
+ 'media_count' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryTvChannel.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryTvChannel.php
new file mode 100755
index 0000000..99d2248
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/StoryTvChannel.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'items' => 'Item[]',
+ 'title' => 'string',
+ 'type' => 'string',
+ 'max_id' => 'string',
+ 'more_available' => 'bool',
+ 'seen_state' => 'mixed',
+ 'user_dict' => 'User',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Subscription.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Subscription.php
new file mode 100755
index 0000000..5a63e28
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Subscription.php
@@ -0,0 +1,35 @@
+ '',
+ 'url' => 'string',
+ 'sequence' => '',
+ 'auth' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Suggested.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Suggested.php
new file mode 100755
index 0000000..e854bea
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Suggested.php
@@ -0,0 +1,40 @@
+ 'int',
+ 'hashtag' => 'Hashtag',
+ 'user' => 'User',
+ 'place' => 'LocationItem',
+ 'client_time' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SuggestedUsers.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SuggestedUsers.php
new file mode 100755
index 0000000..862f46b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SuggestedUsers.php
@@ -0,0 +1,75 @@
+ 'string',
+ 'view_all_text' => '',
+ 'title' => '',
+ 'auto_dvance' => '',
+ 'type' => '',
+ 'tracking_token' => 'string',
+ 'landing_site_type' => '',
+ 'landing_site_title' => '',
+ 'upsell_fb_pos' => '',
+ 'suggestions' => 'Suggestion[]',
+ 'suggestion_cards' => 'SuggestionCard[]',
+ 'netego_type' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Suggestion.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Suggestion.php
new file mode 100755
index 0000000..0ee7a32
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Suggestion.php
@@ -0,0 +1,75 @@
+ '',
+ 'social_context' => 'string',
+ 'algorithm' => 'string',
+ 'thumbnail_urls' => 'string[]',
+ 'value' => 'float',
+ 'caption' => '',
+ 'user' => 'User',
+ 'large_urls' => 'string[]',
+ 'media_ids' => '',
+ 'icon' => '',
+ 'is_new_suggestion' => 'bool',
+ 'uuid' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SuggestionCard.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SuggestionCard.php
new file mode 100755
index 0000000..78903ba
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SuggestionCard.php
@@ -0,0 +1,30 @@
+ 'UserCard',
+ 'upsell_ci_card' => '',
+ 'upsell_fbc_card' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SummaryPromotions.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SummaryPromotions.php
new file mode 100755
index 0000000..33947ad
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SummaryPromotions.php
@@ -0,0 +1,25 @@
+ 'BusinessEdge[]',
+ 'page_info' => 'PageInfo',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SupportedCapabilities.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SupportedCapabilities.php
new file mode 100755
index 0000000..2c682d6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SupportedCapabilities.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'value' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Surface.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Surface.php
new file mode 100755
index 0000000..2bfbdc1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Surface.php
@@ -0,0 +1,39 @@
+ '',
+ 'rank_token' => 'string',
+ 'ttl_secs' => 'int',
+ 'name' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SystemControl.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SystemControl.php
new file mode 100755
index 0000000..b5c5da0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/SystemControl.php
@@ -0,0 +1,30 @@
+ 'int',
+ 'upload_time_period_sec' => 'int',
+ 'upload_bytes_per_update' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TVChannel.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TVChannel.php
new file mode 100755
index 0000000..5824b72
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TVChannel.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'title' => 'string',
+ 'id' => 'string',
+ 'items' => 'Item[]',
+ 'more_available' => 'bool',
+ 'max_id' => 'string',
+ 'seen_state' => '',
+ 'user_dict' => 'User',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TVSearchResult.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TVSearchResult.php
new file mode 100755
index 0000000..a512938
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TVSearchResult.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'User' => 'User',
+ 'channel' => 'TVChannel',
+ 'num_results' => 'int',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Tab.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Tab.php
new file mode 100755
index 0000000..975b258
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Tab.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'title' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TabsInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TabsInfo.php
new file mode 100755
index 0000000..b636fc4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TabsInfo.php
@@ -0,0 +1,25 @@
+ 'Tab[]',
+ 'selected' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Tag.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Tag.php
new file mode 100755
index 0000000..42af922
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Tag.php
@@ -0,0 +1,105 @@
+ 'string',
+ 'name' => 'string',
+ 'media_count' => 'int',
+ 'type' => 'string',
+ 'follow_status' => '',
+ 'following' => '',
+ 'allow_following' => '',
+ 'allow_muting_story' => '',
+ 'profile_pic_url' => '',
+ 'non_violating' => '',
+ 'related_tags' => '',
+ 'subtitle' => '',
+ 'social_context' => '',
+ 'social_context_profile_links' => '',
+ 'show_follow_drop_down' => '',
+ 'follow_button_text' => '',
+ 'debug_info' => '',
+ 'search_result_subtitle' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Tallies.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Tallies.php
new file mode 100755
index 0000000..58ddedd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Tallies.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'count' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Template.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Template.php
new file mode 100755
index 0000000..750250d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Template.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'parameters' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Text.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Text.php
new file mode 100755
index 0000000..9515d3a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Text.php
@@ -0,0 +1,20 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Thumbnail.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Thumbnail.php
new file mode 100755
index 0000000..4dc41a6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Thumbnail.php
@@ -0,0 +1,65 @@
+ 'float',
+ 'thumbnail_width' => 'int',
+ 'thumbnail_height' => 'int',
+ 'thumbnail_duration' => 'float',
+ 'sprite_urls' => 'string[]',
+ 'thumbnails_per_row' => 'int',
+ 'max_thumbnails_per_sprite' => 'int',
+ 'sprite_width' => 'int',
+ 'sprite_height' => 'int',
+ 'rendered_width' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TimeRange.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TimeRange.php
new file mode 100755
index 0000000..f00baef
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TimeRange.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'end' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Token.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Token.php
new file mode 100755
index 0000000..6b1ef68
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Token.php
@@ -0,0 +1,82 @@
+ 'string',
+ 'carrier_id' => 'int',
+ 'ttl' => 'int',
+ 'features' => '',
+ 'request_time' => 'string',
+ 'token_hash' => 'string',
+ 'rewrite_rules' => 'RewriteRule[]',
+ 'enabled_wallet_defs_keys' => '',
+ 'deadline' => 'string',
+ 'zero_cms_fetch_interval_seconds' => 'int',
+ ];
+
+ const DEFAULT_TTL = 3600;
+
+ /**
+ * Get token expiration timestamp.
+ *
+ * @return int
+ */
+ public function expiresAt()
+ {
+ $ttl = (int) $this->getTtl();
+ if ($ttl === 0) {
+ $ttl = self::DEFAULT_TTL;
+ }
+
+ return time() + $ttl;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TopLive.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TopLive.php
new file mode 100755
index 0000000..d1f0b78
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TopLive.php
@@ -0,0 +1,25 @@
+ 'User[]',
+ 'ranked_position' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TraceControl.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TraceControl.php
new file mode 100755
index 0000000..54c4b6a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TraceControl.php
@@ -0,0 +1,35 @@
+ 'int',
+ 'cold_start' => '',
+ 'timed_out_upload_sample_rate' => 'int',
+ 'qpl' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TraySuggestions.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TraySuggestions.php
new file mode 100755
index 0000000..9518eff
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TraySuggestions.php
@@ -0,0 +1,40 @@
+ 'StoryTray[]',
+ 'tray_title' => 'string',
+ 'banner_title' => 'string',
+ 'banner_subtitle' => 'string',
+ 'suggestion_type' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TwoFactorInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TwoFactorInfo.php
new file mode 100755
index 0000000..bf8385f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/TwoFactorInfo.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'two_factor_identifier' => 'string',
+ 'phone_verification_settings' => 'PhoneVerificationSettings',
+ 'obfuscated_phone_number' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/CoreUnpredictableContainer.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/CoreUnpredictableContainer.php
new file mode 100755
index 0000000..497944b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/CoreUnpredictableContainer.php
@@ -0,0 +1,95 @@
+_cache === null) {
+ $this->_cache = $this->asArray(); // Throws.
+ }
+
+ if ($this->_type !== null) {
+ foreach ($this->_cache as &$value) {
+ if (is_array($value)) {
+ $value = new $this->_type($value); // Throws.
+ }
+ }
+ }
+
+ return $this->_cache;
+ }
+
+ /**
+ * Set the data array of this unpredictable container.
+ *
+ * @param array $value The new data array.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setData(
+ array $value)
+ {
+ $this->_cache = $value;
+
+ $newObjectData = [];
+ foreach ($this->_cache as $k => $v) {
+ $newObjectData[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ $this->assignObjectData($newObjectData); // Throws.
+
+ return $this;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/DirectThreadLastSeenAtUnpredictableContainer.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/DirectThreadLastSeenAtUnpredictableContainer.php
new file mode 100755
index 0000000..5647e45
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/DirectThreadLastSeenAtUnpredictableContainer.php
@@ -0,0 +1,13 @@
+ 'string',
+ 'has_anonymous_profile_picture' => 'bool',
+ 'has_highlight_reels' => 'bool',
+ 'is_eligible_to_show_fb_cross_sharing_nux' => 'bool',
+ 'page_id_for_new_suma_biz_account' => 'bool',
+ 'eligible_shopping_signup_entrypoints' => 'string[]',
+ 'is_favorite' => 'bool',
+ 'is_favorite_for_stories' => 'bool',
+ 'is_favorite_for_highlights' => 'bool',
+ 'is_interest_account' => 'bool',
+ 'can_be_reported_as_fraud' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_id' => 'string',
+ 'permission' => 'bool',
+ 'full_name' => 'string',
+ 'user_id' => 'string',
+ 'pk' => 'string',
+ 'id' => 'string',
+ 'is_verified' => 'bool',
+ 'is_private' => 'bool',
+ 'coeff_weight' => '',
+ 'friendship_status' => 'FriendshipStatus',
+ 'hd_profile_pic_versions' => 'ImageCandidate[]',
+ 'byline' => '',
+ 'search_social_context' => '',
+ 'unseen_count' => '',
+ 'mutual_followers_count' => 'int',
+ 'follower_count' => 'int',
+ 'search_subtitle' => 'string',
+ 'social_context' => '',
+ 'media_count' => 'int',
+ 'following_count' => 'int',
+ 'following_tag_count' => 'int',
+ 'instagram_location_id' => '',
+ 'is_business' => 'bool',
+ 'usertags_count' => 'int',
+ 'profile_context' => '',
+ 'biography' => 'string',
+ 'geo_media_count' => 'int',
+ 'is_unpublished' => 'bool',
+ 'allow_contacts_sync' => '',
+ 'show_feed_biz_conversion_icon' => '',
+ 'auto_expand_chaining' => '',
+ 'can_boost_post' => '',
+ 'is_profile_action_needed' => 'bool',
+ 'has_chaining' => 'bool',
+ 'has_recommend_accounts' => 'bool',
+ 'chaining_suggestions' => 'ChainingSuggestion[]',
+ 'include_direct_blacklist_status' => '',
+ 'can_see_organic_insights' => 'bool',
+ 'has_placed_orders' => 'bool',
+ 'can_convert_to_business' => 'bool',
+ 'convert_from_pages' => '',
+ 'show_business_conversion_icon' => 'bool',
+ 'show_conversion_edit_entry' => 'bool',
+ 'show_insights_terms' => 'bool',
+ 'can_create_sponsor_tags' => '',
+ 'hd_profile_pic_url_info' => 'ImageCandidate',
+ 'usertag_review_enabled' => '',
+ 'profile_context_mutual_follow_ids' => 'string[]',
+ 'profile_context_links_with_user_ids' => 'Link[]',
+ 'has_biography_translation' => 'bool',
+ 'total_igtv_videos' => 'int',
+ 'total_ar_effects' => 'int',
+ 'can_link_entities_in_bio' => 'bool',
+ 'biography_with_entities' => 'BiographyEntities',
+ 'max_num_linked_entities_in_bio' => 'int',
+ 'business_contact_method' => 'string',
+ 'highlight_reshare_disabled' => 'bool',
+ /*
+ * Business category.
+ */
+ 'category' => 'string',
+ 'direct_messaging' => 'string',
+ 'page_name' => 'string',
+ 'is_attribute_sync_enabled' => 'bool',
+ 'has_business_presence_node' => 'bool',
+ 'profile_visits_count' => 'int',
+ 'profile_visits_num_days' => 'int',
+ 'is_call_to_action_enabled' => 'bool',
+ 'linked_fb_user' => 'FacebookUser',
+ 'account_type' => 'int',
+ 'should_show_category' => 'bool',
+ 'should_show_public_contacts' => 'bool',
+ 'can_hide_category' => 'bool',
+ 'can_hide_public_contacts' => 'bool',
+ 'can_tag_products_from_merchants' => 'bool',
+ 'public_phone_country_code' => 'string',
+ 'public_phone_number' => 'string',
+ 'contact_phone_number' => 'string',
+ 'latitude' => 'float',
+ 'longitude' => 'float',
+ 'address_street' => 'string',
+ 'zip' => 'string',
+ 'city_id' => 'string', // 64-bit number.
+ 'city_name' => 'string',
+ 'public_email' => 'string',
+ 'is_needy' => 'bool',
+ 'external_url' => 'string',
+ 'external_lynx_url' => 'string',
+ 'email' => 'string',
+ 'country_code' => 'int',
+ 'birthday' => '',
+ 'national_number' => 'string', // Really int, but may be >32bit.
+ 'gender' => 'int',
+ 'phone_number' => 'string',
+ 'needs_email_confirm' => '',
+ 'is_active' => 'bool',
+ 'block_at' => '',
+ 'aggregate_promote_engagement' => '',
+ 'fbuid' => '',
+ 'page_id' => 'string',
+ 'can_claim_page' => 'bool',
+ 'fb_page_call_to_action_id' => 'string',
+ 'fb_page_call_to_action_ix_app_id' => 'int',
+ 'fb_page_call_to_action_ix_label_bundle' => '',
+ 'fb_page_call_to_action_ix_url' => 'string',
+ 'fb_page_call_to_action_ix_partner' => 'string',
+ 'is_call_to_action_enabled_by_surface' => 'bool',
+ 'can_crosspost_without_fb_token' => 'bool',
+ 'num_of_admined_pages' => 'int',
+ 'shoppable_posts_count' => 'int',
+ 'show_shoppable_feed' => 'bool',
+ 'show_account_transparency_details' => 'bool',
+ /*
+ * Unix "taken_at" timestamp of the newest item in their story reel.
+ */
+ 'latest_reel_media' => 'string',
+ 'has_unseen_besties_media' => 'bool',
+ 'allowed_commenter_type' => 'string',
+ 'reel_auto_archive' => 'string',
+ 'is_directapp_installed' => 'bool',
+ 'is_using_unified_inbox_for_direct' => 'int',
+ 'feed_post_reshare_disabled' => 'bool',
+ 'besties_count' => 'int',
+ 'can_be_tagged_as_sponsor' => 'bool',
+ 'can_follow_hashtag' => 'bool',
+ 'is_potential_business' => 'bool',
+ 'has_profile_video_feed' => 'bool',
+ 'is_video_creator' => 'bool',
+ 'show_besties_badge' => 'bool',
+ 'recently_bestied_by_count' => 'int',
+ 'screenshotted' => 'bool',
+ 'nametag' => 'Nametag',
+ 'school' => '',
+ 'is_bestie' => 'bool',
+ 'live_subscription_status' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UserCard.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UserCard.php
new file mode 100755
index 0000000..5cab10b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UserCard.php
@@ -0,0 +1,80 @@
+ 'User',
+ 'algorithm' => 'string',
+ 'social_context' => 'string',
+ 'caption' => '',
+ 'icon' => '',
+ 'media_ids' => '',
+ 'thumbnail_urls' => '',
+ 'large_urls' => '',
+ 'media_infos' => '',
+ 'value' => 'float',
+ 'is_new_suggestion' => 'bool',
+ 'uuid' => 'string',
+ 'followed_by' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UserList.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UserList.php
new file mode 100755
index 0000000..6952967
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UserList.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'user' => 'User',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UserPresence.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UserPresence.php
new file mode 100755
index 0000000..d9e9aa9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/UserPresence.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'last_activity_at_ms' => 'string',
+ 'is_active' => 'bool',
+ 'in_threads' => 'string[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Usertag.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Usertag.php
new file mode 100755
index 0000000..f1bff83
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Usertag.php
@@ -0,0 +1,25 @@
+ 'In[]',
+ 'photo_of_you' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VideoCallEvent.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VideoCallEvent.php
new file mode 100755
index 0000000..57e3645
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VideoCallEvent.php
@@ -0,0 +1,31 @@
+ 'string',
+ 'vc_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VideoUploadUrl.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VideoUploadUrl.php
new file mode 100755
index 0000000..cb572f7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VideoUploadUrl.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'job' => 'string',
+ 'expires' => 'float',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VideoVersions.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VideoVersions.php
new file mode 100755
index 0000000..057e909
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VideoVersions.php
@@ -0,0 +1,40 @@
+ 'int', // Some kinda internal type ID, such as int(102).
+ 'width' => 'int',
+ 'height' => 'int',
+ 'url' => 'string',
+ 'id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Viewer.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Viewer.php
new file mode 100755
index 0000000..9692734
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Viewer.php
@@ -0,0 +1,20 @@
+ 'EligiblePromotions',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VoiceMedia.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VoiceMedia.php
new file mode 100755
index 0000000..b6237dd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VoiceMedia.php
@@ -0,0 +1,20 @@
+ 'DirectThreadItemMedia',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Voter.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Voter.php
new file mode 100755
index 0000000..129936d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/Voter.php
@@ -0,0 +1,25 @@
+ 'User',
+ 'vote' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VoterInfo.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VoterInfo.php
new file mode 100755
index 0000000..37f0aab
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/VoterInfo.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'voters' => 'Voter[]',
+ 'max_id' => 'string',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/_Message.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/_Message.php
new file mode 100755
index 0000000..9b40e81
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/Model/_Message.php
@@ -0,0 +1,25 @@
+ '',
+ 'time' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MsisdnHeaderResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MsisdnHeaderResponse.php
new file mode 100755
index 0000000..481f9b9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MsisdnHeaderResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'url' => 'string',
+ 'remaining_ttl_seconds' => 'int',
+ 'ttl' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MultipleAccountFamilyResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MultipleAccountFamilyResponse.php
new file mode 100755
index 0000000..d93d034
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MultipleAccountFamilyResponse.php
@@ -0,0 +1,37 @@
+ 'string[]',
+ 'main_accounts' => 'string[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MutedReelsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MutedReelsResponse.php
new file mode 100755
index 0000000..3ef5370
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/MutedReelsResponse.php
@@ -0,0 +1,47 @@
+ 'Model\User[]',
+ 'next_max_id' => 'string',
+ 'page_size' => '',
+ 'big_list' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/OnBoardCatalogResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/OnBoardCatalogResponse.php
new file mode 100755
index 0000000..0766763
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/OnBoardCatalogResponse.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'current_catalog_id' => 'string',
+ 'is_business_targeted_for_shopping' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/OnTagProductResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/OnTagProductResponse.php
new file mode 100755
index 0000000..e7bd30a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/OnTagProductResponse.php
@@ -0,0 +1,42 @@
+ 'Model\Product',
+ 'merchant' => 'Model\User',
+ 'other_product_items' => 'Model\Product[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PermalinkResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PermalinkResponse.php
new file mode 100755
index 0000000..e436246
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PermalinkResponse.php
@@ -0,0 +1,32 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PinCommentBroadcastResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PinCommentBroadcastResponse.php
new file mode 100755
index 0000000..6ef84e3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PinCommentBroadcastResponse.php
@@ -0,0 +1,32 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PopularFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PopularFeedResponse.php
new file mode 100755
index 0000000..465bf20
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PopularFeedResponse.php
@@ -0,0 +1,57 @@
+ 'string',
+ 'more_available' => '',
+ 'auto_load_more_enabled' => '',
+ 'items' => 'Model\Item[]',
+ 'num_results' => 'int',
+ 'max_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PostLiveCommentsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PostLiveCommentsResponse.php
new file mode 100755
index 0000000..2b91274
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PostLiveCommentsResponse.php
@@ -0,0 +1,52 @@
+ '',
+ 'ending_offset' => '',
+ 'next_fetch_offset' => '',
+ 'comments' => 'Model\LiveComment[]',
+ 'pinned_comments' => 'Model\LiveComment[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PostLiveLikesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PostLiveLikesResponse.php
new file mode 100755
index 0000000..b7781cf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PostLiveLikesResponse.php
@@ -0,0 +1,47 @@
+ '',
+ 'ending_offset' => '',
+ 'next_fetch_offset' => '',
+ 'time_series' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PostLiveViewerListResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PostLiveViewerListResponse.php
new file mode 100755
index 0000000..5e6ca51
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PostLiveViewerListResponse.php
@@ -0,0 +1,42 @@
+ 'Model\User[]',
+ 'next_max_id' => '',
+ 'total_viewer_count' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PrefillCandidatesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PrefillCandidatesResponse.php
new file mode 100755
index 0000000..2aae401
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PrefillCandidatesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Prefill[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PresenceStatusResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PresenceStatusResponse.php
new file mode 100755
index 0000000..ec4d0b7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PresenceStatusResponse.php
@@ -0,0 +1,37 @@
+ 'bool',
+ 'thread_presence_disabled' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PresencesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PresencesResponse.php
new file mode 100755
index 0000000..feac8db
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PresencesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\UnpredictableKeys\PresenceUnpredictableContainer',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ProfileNoticeResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ProfileNoticeResponse.php
new file mode 100755
index 0000000..19ef64b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ProfileNoticeResponse.php
@@ -0,0 +1,32 @@
+ 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PropertyCollection/Sticker.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PropertyCollection/Sticker.php
new file mode 100755
index 0000000..a436f2d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PropertyCollection/Sticker.php
@@ -0,0 +1,53 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float', // Unused by IG for now. So far it's always int(0).
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PushPreferencesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PushPreferencesResponse.php
new file mode 100755
index 0000000..4a99442
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PushPreferencesResponse.php
@@ -0,0 +1,117 @@
+ 'Model\PushSettings[]',
+ 'likes' => '',
+ 'comments' => '',
+ 'comment_likes' => '',
+ 'like_and_comment_on_photo_user_tagged' => '',
+ 'live_broadcast' => '',
+ 'new_follower' => '',
+ 'follow_request_accepted' => '',
+ 'contact_joined' => '',
+ 'pending_direct_share' => '',
+ 'direct_share_activity' => '',
+ 'user_tagged' => '',
+ 'notification_reminders' => '',
+ 'first_post' => '',
+ 'announcements' => '',
+ 'ads' => '',
+ 'view_count' => '',
+ 'report_updated' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PushRegisterResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PushRegisterResponse.php
new file mode 100755
index 0000000..b999f5f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/PushRegisterResponse.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'global' => 'int',
+ 'default' => 'int',
+ 'surfaces' => 'Model\QPSurface[]',
+ 'slots' => 'Model\Slot[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/RecentSearchesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/RecentSearchesResponse.php
new file mode 100755
index 0000000..d49da4a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/RecentSearchesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Suggested[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/RecoveryResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/RecoveryResponse.php
new file mode 100755
index 0000000..646fef8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/RecoveryResponse.php
@@ -0,0 +1,42 @@
+ 'bool',
+ 'title' => 'string',
+ 'body' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelMediaViewerResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelMediaViewerResponse.php
new file mode 100755
index 0000000..99d83ed
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelMediaViewerResponse.php
@@ -0,0 +1,62 @@
+ 'Model\User[]',
+ 'next_max_id' => 'string',
+ 'user_count' => 'int',
+ 'total_viewer_count' => 'int',
+ 'screenshotter_user_ids' => '',
+ 'total_screenshot_count' => 'int',
+ 'updated_media' => 'Model\Item',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelSettingsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelSettingsResponse.php
new file mode 100755
index 0000000..7151269
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelSettingsResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'blocked_reels' => 'Model\BlockedReels',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelsMediaResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelsMediaResponse.php
new file mode 100755
index 0000000..eeff797
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelsMediaResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Reel[]',
+ 'reels' => 'Model\UnpredictableKeys\ReelUnpredictableContainer',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelsTrayFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelsTrayFeedResponse.php
new file mode 100755
index 0000000..a02fe85
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReelsTrayFeedResponse.php
@@ -0,0 +1,72 @@
+ 'string',
+ 'broadcasts' => 'Model\Broadcast[]',
+ 'tray' => 'Model\StoryTray[]',
+ 'post_live' => 'Model\PostLive',
+ 'sticker_version' => 'int',
+ 'face_filter_nux_version' => 'int',
+ 'stories_viewer_gestures_nux_eligible' => 'bool',
+ 'has_new_nux_story' => 'bool',
+ 'suggestions' => 'Model\TraySuggestions[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/RelatedLocationResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/RelatedLocationResponse.php
new file mode 100755
index 0000000..089eb60
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/RelatedLocationResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Location[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReportExploreMediaResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReportExploreMediaResponse.php
new file mode 100755
index 0000000..a341e4f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReportExploreMediaResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ResumableOffsetResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ResumableOffsetResponse.php
new file mode 100755
index 0000000..42793cd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ResumableOffsetResponse.php
@@ -0,0 +1,52 @@
+ 'int',
+ ];
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ $offset = $this->_getProperty('offset');
+ if ($offset !== null && $offset >= 0) {
+ return true;
+ } else {
+ // Set a nice message for exceptions.
+ if ($this->getMessage() === null) {
+ $this->setMessage('Offset for resumable uploader is missing or invalid.');
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ResumableUploadResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ResumableUploadResponse.php
new file mode 100755
index 0000000..db9b2d0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ResumableUploadResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'upload_id' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReviewPreferenceResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReviewPreferenceResponse.php
new file mode 100755
index 0000000..dee7ea6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ReviewPreferenceResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SaveAndUnsaveMedia.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SaveAndUnsaveMedia.php
new file mode 100755
index 0000000..cc193e5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SaveAndUnsaveMedia.php
@@ -0,0 +1,25 @@
+ 'Model\SavedFeedItem[]',
+ 'more_available' => '',
+ 'next_max_id' => 'string',
+ 'auto_load_more_enabled' => '',
+ 'num_results' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SearchTagResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SearchTagResponse.php
new file mode 100755
index 0000000..d43b569
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SearchTagResponse.php
@@ -0,0 +1,42 @@
+ 'bool',
+ 'results' => 'Model\Tag[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SearchUserResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SearchUserResponse.php
new file mode 100755
index 0000000..2cdb215
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SearchUserResponse.php
@@ -0,0 +1,47 @@
+ 'bool',
+ 'num_results' => 'int',
+ 'users' => 'Model\User[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SegmentedStartResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SegmentedStartResponse.php
new file mode 100755
index 0000000..eb17072
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SegmentedStartResponse.php
@@ -0,0 +1,52 @@
+ 'string',
+ ];
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ $streamId = $this->_getProperty('stream_id');
+ if ($streamId !== null && $streamId !== '') {
+ return true;
+ } else {
+ // Set a nice message for exceptions.
+ if ($this->getMessage() === null) {
+ $this->setMessage('Stream ID for segmented uploader is missing or invalid.');
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SendConfirmEmailResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SendConfirmEmailResponse.php
new file mode 100755
index 0000000..ed19156
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SendConfirmEmailResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'is_email_legit' => '',
+ 'body' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SendSMSCodeResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SendSMSCodeResponse.php
new file mode 100755
index 0000000..9d532b8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SendSMSCodeResponse.php
@@ -0,0 +1,37 @@
+ 'bool',
+ 'phone_verification_settings' => 'Model\PhoneVerificationSettings',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SendTwoFactorEnableSMSResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SendTwoFactorEnableSMSResponse.php
new file mode 100755
index 0000000..8105873
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SendTwoFactorEnableSMSResponse.php
@@ -0,0 +1,37 @@
+ 'Model\PhoneVerificationSettings',
+ 'obfuscated_phone_number' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SharePrefillResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SharePrefillResponse.php
new file mode 100755
index 0000000..c32054e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SharePrefillResponse.php
@@ -0,0 +1,42 @@
+ 'Model\Ranking',
+ 'entities' => 'Model\ServerDataInfo',
+ 'failed_view_names' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SharedFollowersResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SharedFollowersResponse.php
new file mode 100755
index 0000000..6e428eb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SharedFollowersResponse.php
@@ -0,0 +1,32 @@
+ 'Model\SharedFollower[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StartLiveResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StartLiveResponse.php
new file mode 100755
index 0000000..c5bea53
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StartLiveResponse.php
@@ -0,0 +1,32 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StickerAssetsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StickerAssetsResponse.php
new file mode 100755
index 0000000..73d8f12
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StickerAssetsResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'static_stickers' => 'Model\StaticStickers[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StoryAnswersResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StoryAnswersResponse.php
new file mode 100755
index 0000000..cae9c24
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StoryAnswersResponse.php
@@ -0,0 +1,32 @@
+ 'Model\StoryQuestionResponderInfos',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StoryCountdownsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StoryCountdownsResponse.php
new file mode 100755
index 0000000..0733391
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StoryCountdownsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\CountdownSticker[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StoryPollVotersResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StoryPollVotersResponse.php
new file mode 100755
index 0000000..6a250dc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/StoryPollVotersResponse.php
@@ -0,0 +1,32 @@
+ 'Model\VoterInfo',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedBroadcastsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedBroadcastsResponse.php
new file mode 100755
index 0000000..2d28366
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedBroadcastsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Broadcast[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedSearchesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedSearchesResponse.php
new file mode 100755
index 0000000..98bca9d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedSearchesResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Suggested[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedUsersBadgeResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedUsersBadgeResponse.php
new file mode 100755
index 0000000..2c22f97
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedUsersBadgeResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'new_suggestion_ids' => 'string[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedUsersResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedUsersResponse.php
new file mode 100755
index 0000000..0c60221
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SuggestedUsersResponse.php
@@ -0,0 +1,37 @@
+ 'Model\User[]',
+ 'is_backup' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SwitchBusinessProfileResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SwitchBusinessProfileResponse.php
new file mode 100755
index 0000000..77dab4c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SwitchBusinessProfileResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SwitchPersonalProfileResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SwitchPersonalProfileResponse.php
new file mode 100755
index 0000000..60c25c1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SwitchPersonalProfileResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SyncResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SyncResponse.php
new file mode 100755
index 0000000..23f9c73
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/SyncResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Experiment[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TVChannelsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TVChannelsResponse.php
new file mode 100755
index 0000000..a3cde18
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TVChannelsResponse.php
@@ -0,0 +1,67 @@
+ 'string',
+ 'title' => 'string',
+ 'id' => 'string',
+ 'items' => 'Model\Item[]',
+ 'more_available' => 'bool',
+ 'max_id' => 'string',
+ 'seen_state' => '',
+ 'user_dict' => 'Model\User',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TVGuideResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TVGuideResponse.php
new file mode 100755
index 0000000..0a6bee0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TVGuideResponse.php
@@ -0,0 +1,52 @@
+ 'Model\TVChannel[]',
+ 'my_channel' => 'Model\TVChannel',
+ 'badging' => 'Model\Badging',
+ 'composer' => 'Model\Composer',
+ 'banner_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TVSearchResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TVSearchResponse.php
new file mode 100755
index 0000000..cc80d89
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TVSearchResponse.php
@@ -0,0 +1,42 @@
+ 'Model\TVSearchResult[]',
+ 'num_results' => 'int',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TagFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TagFeedResponse.php
new file mode 100755
index 0000000..4b21e6c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TagFeedResponse.php
@@ -0,0 +1,77 @@
+ 'Model\Section[]',
+ 'num_results' => 'int',
+ 'ranked_items' => 'Model\Item[]',
+ 'auto_load_more_enabled' => 'bool',
+ 'items' => 'Model\Item[]',
+ 'story' => 'Model\StoryTray',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'next_media_ids' => '',
+ 'next_page' => 'int',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TagInfoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TagInfoResponse.php
new file mode 100755
index 0000000..12afa93
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TagInfoResponse.php
@@ -0,0 +1,68 @@
+ 'Model\Related[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TagsStoryResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TagsStoryResponse.php
new file mode 100755
index 0000000..4adc673
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TagsStoryResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Reel',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TimelineFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TimelineFeedResponse.php
new file mode 100755
index 0000000..3d23206
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TimelineFeedResponse.php
@@ -0,0 +1,98 @@
+ 'int',
+ 'client_gap_enforcer_matrix' => '',
+ 'is_direct_v2_enabled' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'pagination_info' => '',
+ 'feed_items' => 'Model\FeedItem[]',
+ 'megaphone' => 'Model\FeedAysf',
+ 'client_feed_changelist_applied' => 'bool',
+ 'view_state_version' => 'string',
+ 'feed_pill_text' => 'string',
+ 'client_gap_enforcer_matrix' => '',
+ 'client_session_id' => 'string',
+ 'startup_prefetch_configs' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TokenResultResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TokenResultResponse.php
new file mode 100755
index 0000000..d22596f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TokenResultResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Token',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TopLiveStatusResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TopLiveStatusResponse.php
new file mode 100755
index 0000000..a7e908c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TopLiveStatusResponse.php
@@ -0,0 +1,32 @@
+ 'Model\BroadcastStatusItem[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TranslateResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TranslateResponse.php
new file mode 100755
index 0000000..84a0d12
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TranslateResponse.php
@@ -0,0 +1,32 @@
+ 'Model\CommentTranslations[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TwoFactorLoginSMSResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TwoFactorLoginSMSResponse.php
new file mode 100755
index 0000000..254afdf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/TwoFactorLoginSMSResponse.php
@@ -0,0 +1,37 @@
+ 'bool',
+ 'two_factor_info' => 'Model\TwoFactorInfo',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UnlinkAddressBookResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UnlinkAddressBookResponse.php
new file mode 100755
index 0000000..4d44742
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UnlinkAddressBookResponse.php
@@ -0,0 +1,25 @@
+ 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UploadJobVideoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UploadJobVideoResponse.php
new file mode 100755
index 0000000..7d1e3d7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UploadJobVideoResponse.php
@@ -0,0 +1,37 @@
+ 'string',
+ 'video_upload_urls' => 'Model\VideoUploadUrl[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UploadPhotoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UploadPhotoResponse.php
new file mode 100755
index 0000000..6587b2c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UploadPhotoResponse.php
@@ -0,0 +1,37 @@
+ 'string',
+ 'media_id' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UploadVideoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UploadVideoResponse.php
new file mode 100755
index 0000000..0f9db0e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UploadVideoResponse.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'configure_delay_ms' => 'float',
+ 'result' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UserFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UserFeedResponse.php
new file mode 100755
index 0000000..082c94c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UserFeedResponse.php
@@ -0,0 +1,57 @@
+ 'Model\Item[]',
+ 'num_results' => 'int',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'max_id' => 'string',
+ 'auto_load_more_enabled' => 'bool',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UserInfoResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UserInfoResponse.php
new file mode 100755
index 0000000..0f0a7ac
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UserInfoResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'user' => 'Model\User',
+ 'effect_previews' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UserReelMediaFeedResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UserReelMediaFeedResponse.php
new file mode 100755
index 0000000..275bedc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UserReelMediaFeedResponse.php
@@ -0,0 +1,96 @@
+ 'Model\Broadcast',
+ 'reel' => 'Model\Reel',
+ 'post_live_item' => 'Model\PostLiveItem',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UsersLookupResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UsersLookupResponse.php
new file mode 100755
index 0000000..950bfdb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UsersLookupResponse.php
@@ -0,0 +1,77 @@
+ 'Model\User',
+ 'email_sent' => 'bool',
+ 'has_valid_phone' => 'bool',
+ 'can_email_reset' => 'bool',
+ 'can_sms_reset' => 'bool',
+ 'user_id' => 'string',
+ 'lookup_source' => 'string',
+ 'email' => 'string',
+ 'phone_number' => 'string',
+ 'corrected_input' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UsertagsResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UsertagsResponse.php
new file mode 100755
index 0000000..99fd399
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/UsertagsResponse.php
@@ -0,0 +1,67 @@
+ 'int',
+ 'auto_load_more_enabled' => '',
+ 'items' => 'Model\Item[]',
+ 'more_available' => '',
+ 'next_max_id' => 'string',
+ 'total_count' => '',
+ 'requires_review' => '',
+ 'new_photos' => '',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ValidateURLResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ValidateURLResponse.php
new file mode 100755
index 0000000..95406a8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ValidateURLResponse.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'phone_number' => 'string',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ViewerListResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ViewerListResponse.php
new file mode 100755
index 0000000..75cef57
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/ViewerListResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/WriteSuppotedCapabilitiesResponse.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/WriteSuppotedCapabilitiesResponse.php
new file mode 100755
index 0000000..494155c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Response/WriteSuppotedCapabilitiesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\SupportedCapabilities[]',
+ ];
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Factory.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Factory.php
new file mode 100755
index 0000000..9f7aff2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Factory.php
@@ -0,0 +1,262 @@
+ $storage];
+ }
+
+ // Determine the user's final storage configuration.
+ switch ($storageConfig['storage']) {
+ case 'file':
+ // Look for allowed command-line values related to this backend.
+ $cmdOptions = self::getCmdOptions([
+ 'settings_basefolder::',
+ ]);
+
+ // These settings are optional:
+ $baseFolder = self::getUserConfig('basefolder', $storageConfig, $cmdOptions);
+
+ // Generate the final storage location configuration.
+ $locationConfig = [
+ 'basefolder' => $baseFolder,
+ ];
+
+ $storageInstance = new Storage\File();
+ break;
+ case 'mysql':
+ // Look for allowed command-line values related to this backend.
+ $cmdOptions = self::getCmdOptions([
+ 'settings_dbusername::',
+ 'settings_dbpassword::',
+ 'settings_dbhost::',
+ 'settings_dbname::',
+ 'settings_dbtablename::',
+ ]);
+
+ // These settings are optional, and can be provided regardless of
+ // connection method:
+ $locationConfig = [
+ 'dbtablename' => self::getUserConfig('dbtablename', $storageConfig, $cmdOptions),
+ ];
+
+ // These settings are required, but you only have to use one method:
+ if (isset($storageConfig['pdo'])) {
+ // If "pdo" is set in the factory config, assume the user wants
+ // to re-use an existing PDO connection. In that case we ignore
+ // the username/password/host/name parameters and use their PDO.
+ // NOTE: Beware that we WILL change attributes on the PDO
+ // connection to suit our needs! Primarily turning all error
+ // reporting into exceptions, and setting the charset to UTF-8.
+ // If you want to re-use a PDO connection, you MUST accept the
+ // fact that WE NEED exceptions and UTF-8 in our PDO! If that is
+ // not acceptable to you then DO NOT re-use your own PDO object!
+ $locationConfig['pdo'] = $storageConfig['pdo'];
+ } else {
+ // Make a new connection. Optional settings for it:
+ $locationConfig['dbusername'] = self::getUserConfig('dbusername', $storageConfig, $cmdOptions);
+ $locationConfig['dbpassword'] = self::getUserConfig('dbpassword', $storageConfig, $cmdOptions);
+ $locationConfig['dbhost'] = self::getUserConfig('dbhost', $storageConfig, $cmdOptions);
+ $locationConfig['dbname'] = self::getUserConfig('dbname', $storageConfig, $cmdOptions);
+ }
+
+ $storageInstance = new Storage\MySQL();
+ break;
+ case 'sqlite':
+ // Look for allowed command-line values related to this backend.
+ $cmdOptions = self::getCmdOptions([
+ 'settings_dbfilename::',
+ 'settings_dbtablename::',
+ ]);
+
+ // These settings are optional, and can be provided regardless of
+ // connection method:
+ $locationConfig = [
+ 'dbtablename' => self::getUserConfig('dbtablename', $storageConfig, $cmdOptions),
+ ];
+
+ // These settings are required, but you only have to use one method:
+ if (isset($storageConfig['pdo'])) {
+ // If "pdo" is set in the factory config, assume the user wants
+ // to re-use an existing PDO connection. In that case we ignore
+ // the SQLite filename/connection parameters and use their PDO.
+ // NOTE: Beware that we WILL change attributes on the PDO
+ // connection to suit our needs! Primarily turning all error
+ // reporting into exceptions, and setting the charset to UTF-8.
+ // If you want to re-use a PDO connection, you MUST accept the
+ // fact that WE NEED exceptions and UTF-8 in our PDO! If that is
+ // not acceptable to you then DO NOT re-use your own PDO object!
+ $locationConfig['pdo'] = $storageConfig['pdo'];
+ } else {
+ // Make a new connection. Optional settings for it:
+ $locationConfig['dbfilename'] = self::getUserConfig('dbfilename', $storageConfig, $cmdOptions);
+ }
+
+ $storageInstance = new Storage\SQLite();
+ break;
+ case 'memcached':
+ // The memcached storage can only be configured via the factory
+ // configuration array (not via command line or environment vars).
+
+ // These settings are required, but you only have to use one method:
+ if (isset($storageConfig['memcached'])) {
+ // Re-use the user's own Memcached object.
+ $locationConfig = [
+ 'memcached' => $storageConfig['memcached'],
+ ];
+ } else {
+ // Make a new connection. Optional settings for it:
+ $locationConfig = [
+ // This ID will be passed to the \Memcached() constructor.
+ // NOTE: Memcached's "persistent ID" feature makes Memcached
+ // keep the settings even after you disconnect.
+ 'persistent_id' => (isset($storageConfig['persistent_id'])
+ ? $storageConfig['persistent_id']
+ : null),
+ // Array which will be passed to \Memcached::setOptions().
+ 'memcached_options' => (isset($storageConfig['memcached_options'])
+ ? $storageConfig['memcached_options']
+ : null),
+ // Array which will be passed to \Memcached::addServers().
+ // NOTE: Can contain one or multiple servers.
+ 'servers' => (isset($storageConfig['servers'])
+ ? $storageConfig['servers']
+ : null),
+ // SASL username and password to be used for SASL
+ // authentication with all of the Memcached servers.
+ // NOTE: PHP's Memcached API doesn't support individual
+ // authentication credentials per-server, so these values
+ // apply to all of your servers if you use this feature!
+ 'sasl_username' => (isset($storageConfig['sasl_username'])
+ ? $storageConfig['sasl_username']
+ : null),
+ 'sasl_password' => (isset($storageConfig['sasl_password'])
+ ? $storageConfig['sasl_password']
+ : null),
+ ];
+ }
+
+ $storageInstance = new Storage\Memcached();
+ break;
+ case 'custom':
+ // Custom storage classes can only be configured via the main array.
+ // When using a custom class, you must provide a StorageInterface:
+ if (!isset($storageConfig['class'])
+ || !$storageConfig['class'] instanceof StorageInterface) {
+ throw new SettingsException('Invalid custom storage class.');
+ }
+
+ // Create a clean storage location configuration array.
+ $locationConfig = $storageConfig;
+ unset($locationConfig['storage']);
+ unset($locationConfig['class']);
+
+ $storageInstance = $storageConfig['class'];
+ break;
+ default:
+ throw new SettingsException(sprintf(
+ 'Unknown settings storage type "%s".',
+ $storageConfig['storage']
+ ));
+ }
+
+ // Create the storage handler and connect to the storage location.
+ return new StorageHandler(
+ $storageInstance,
+ $locationConfig,
+ $callbacks
+ );
+ }
+
+ /**
+ * Get option values via command-line parameters.
+ *
+ * @param array $longOpts The longnames for the options to look for.
+ *
+ * @return array
+ */
+ public static function getCmdOptions(
+ array $longOpts)
+ {
+ $cmdOptions = getopt('', $longOpts);
+ if (!is_array($cmdOptions)) {
+ $cmdOptions = [];
+ }
+
+ return $cmdOptions;
+ }
+
+ /**
+ * Looks for the highest-priority result for a Storage config value.
+ *
+ * @param string $settingName The name of the setting.
+ * @param array $storageConfig The Factory's configuration array.
+ * @param array $cmdOptions All parsed command-line options.
+ *
+ * @return string|null The value if found, otherwise NULL.
+ */
+ public static function getUserConfig(
+ $settingName,
+ array $storageConfig,
+ array $cmdOptions)
+ {
+ // Command line options have the highest precedence.
+ // NOTE: Settings provided via cmd must have a "settings_" prefix.
+ if (array_key_exists("settings_{$settingName}", $cmdOptions)) {
+ return $cmdOptions["settings_{$settingName}"];
+ }
+
+ // Environment variables have the second highest precedence.
+ // NOTE: Settings provided via env must be UPPERCASED and have
+ // a "SETTINGS_" prefix, for example "SETTINGS_STORAGE".
+ $envValue = getenv('SETTINGS_'.strtoupper($settingName));
+ if ($envValue !== false) {
+ return $envValue;
+ }
+
+ // Our factory config array has the lowest precedence, so that you can
+ // easily override it via the other methods when testing other storage
+ // backends or different connection parameters.
+ if (array_key_exists($settingName, $storageConfig)) {
+ return $storageConfig[$settingName];
+ }
+
+ // Couldn't find any user-provided value. Automatically returns null.
+ // NOTE: Damn you StyleCI for not allowing "return null;" for clarity.
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/Components/PDOStorage.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/Components/PDOStorage.php
new file mode 100755
index 0000000..84730c0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/Components/PDOStorage.php
@@ -0,0 +1,394 @@
+_backendName = $backendName;
+ }
+
+ /**
+ * Connect to a storage location and perform necessary startup preparations.
+ *
+ * {@inheritdoc}
+ */
+ public function openLocation(
+ array $locationConfig)
+ {
+ $this->_dbTableName = (isset($locationConfig['dbtablename'])
+ ? $locationConfig['dbtablename']
+ : 'user_sessions');
+
+ if (isset($locationConfig['pdo'])) {
+ // Pre-provided connection to re-use instead of creating a new one.
+ if (!$locationConfig['pdo'] instanceof PDO) {
+ throw new SettingsException('The custom PDO object is invalid.');
+ }
+ $this->_isSharedPDO = true;
+ $this->_pdo = $locationConfig['pdo'];
+ } else {
+ // We should connect for the user, by creating our own PDO object.
+ $this->_isSharedPDO = false;
+
+ try {
+ $this->_pdo = $this->_createPDO($locationConfig);
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Connection Failed: '.$e->getMessage());
+ }
+ }
+
+ try {
+ $this->_configurePDO();
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Configuration Failed: '.$e->getMessage());
+ }
+
+ try {
+ $this->_autoCreateTable();
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Create a new PDO connection to the database.
+ *
+ * @param array $locationConfig Configuration parameters for the location.
+ *
+ * @throws \Exception
+ *
+ * @return \PDO The database connection.
+ */
+ abstract protected function _createPDO(
+ array $locationConfig);
+
+ /**
+ * Configures the connection for our needs.
+ *
+ * Warning for those who re-used a PDO object: Beware that we WILL change
+ * attributes on the PDO connection to suit our needs! Primarily turning all
+ * error reporting into exceptions, and setting the charset to UTF-8. If you
+ * want to re-use a PDO connection, you MUST accept the fact that WE NEED
+ * exceptions and UTF-8 in our PDO! If that is not acceptable to you then DO
+ * NOT re-use your own PDO object!
+ *
+ * @throws \Exception
+ */
+ protected function _configurePDO()
+ {
+ $this->_pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+ $this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->_enableUTF8();
+ }
+
+ /**
+ * Enable UTF-8 encoding on the connection.
+ *
+ * This is database-specific and usually requires some kind of query.
+ *
+ * @throws \Exception
+ */
+ abstract protected function _enableUTF8();
+
+ /**
+ * Automatically create the database table if necessary.
+ *
+ * @throws \Exception
+ */
+ abstract protected function _autoCreateTable();
+
+ /**
+ * Automatically writes to the correct user's row and caches the new value.
+ *
+ * @param string $column The database column.
+ * @param string $data Data to be written.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _setUserColumn(
+ $column,
+ $data)
+ {
+ if ($column != 'settings' && $column != 'cookies') {
+ throw new SettingsException(sprintf(
+ 'Attempt to write to illegal database column "%s".',
+ $column
+ ));
+ }
+
+ try {
+ // Update if the user row already exists, otherwise insert.
+ $binds = [':data' => $data];
+ if ($this->_cache['id'] !== null) {
+ $sql = "UPDATE `{$this->_dbTableName}` SET {$column}=:data WHERE (id=:id)";
+ $binds[':id'] = $this->_cache['id'];
+ } else {
+ $sql = "INSERT INTO `{$this->_dbTableName}` (username, {$column}) VALUES (:username, :data)";
+ $binds[':username'] = $this->_username;
+ }
+
+ $sth = $this->_pdo->prepare($sql);
+ $sth->execute($binds);
+
+ // Keep track of the database row ID for the user.
+ if ($this->_cache['id'] === null) {
+ $this->_cache['id'] = $this->_pdo->lastinsertid();
+ }
+
+ $sth->closeCursor();
+
+ // Cache the new value.
+ $this->_cache[$column] = $data;
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUser(
+ $username)
+ {
+ // Check whether a row exists for that username.
+ $sth = $this->_pdo->prepare("SELECT EXISTS(SELECT 1 FROM `{$this->_dbTableName}` WHERE (username=:username))");
+ $sth->execute([':username' => $username]);
+ $result = $sth->fetchColumn();
+ $sth->closeCursor();
+
+ return $result > 0 ? true : false;
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * {@inheritdoc}
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ try {
+ // Verify that the old username exists.
+ if (!$this->hasUser($oldUsername)) {
+ throw new SettingsException(sprintf(
+ 'Cannot move non-existent user "%s".',
+ $oldUsername
+ ));
+ }
+
+ // Verify that the new username does not exist.
+ if ($this->hasUser($newUsername)) {
+ throw new SettingsException(sprintf(
+ 'Refusing to overwrite existing user "%s".',
+ $newUsername
+ ));
+ }
+
+ // Now attempt to rename the old username column to the new name.
+ $sth = $this->_pdo->prepare("UPDATE `{$this->_dbTableName}` SET username=:newusername WHERE (username=:oldusername)");
+ $sth->execute([':oldusername' => $oldUsername, ':newusername' => $newUsername]);
+ $sth->closeCursor();
+ } catch (SettingsException $e) {
+ throw $e; // Ugly but necessary to re-throw only our own messages.
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * {@inheritdoc}
+ */
+ public function deleteUser(
+ $username)
+ {
+ try {
+ // Just attempt to delete the row. Doesn't error if already missing.
+ $sth = $this->_pdo->prepare("DELETE FROM `{$this->_dbTableName}` WHERE (username=:username)");
+ $sth->execute([':username' => $username]);
+ $sth->closeCursor();
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Open the data storage for a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function openUser(
+ $username)
+ {
+ $this->_username = $username;
+
+ // Retrieve and cache the existing user data row if available.
+ try {
+ $sth = $this->_pdo->prepare("SELECT id, settings, cookies FROM `{$this->_dbTableName}` WHERE (username=:username)");
+ $sth->execute([':username' => $this->_username]);
+ $result = $sth->fetch(PDO::FETCH_ASSOC);
+ $sth->closeCursor();
+
+ if (is_array($result)) {
+ $this->_cache = $result;
+ } else {
+ $this->_cache = [
+ 'id' => null,
+ 'settings' => null,
+ 'cookies' => null,
+ ];
+ }
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Load all settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserSettings()
+ {
+ $userSettings = [];
+
+ if (!empty($this->_cache['settings'])) {
+ $userSettings = @json_decode($this->_cache['settings'], true, 512, JSON_BIGINT_AS_STRING);
+ if (!is_array($userSettings)) {
+ throw new SettingsException(sprintf(
+ 'Failed to decode corrupt settings for account "%s".',
+ $this->_username
+ ));
+ }
+ }
+
+ return $userSettings;
+ }
+
+ /**
+ * Save the settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserSettings(
+ array $userSettings,
+ $triggerKey)
+ {
+ // Store the settings as a JSON blob.
+ $encodedData = json_encode($userSettings);
+ $this->_setUserColumn('settings', $encodedData);
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUserCookies()
+ {
+ return isset($this->_cache['cookies'])
+ && !empty($this->_cache['cookies']);
+ }
+
+ /**
+ * Get the cookiefile disk path (only if a file-based cookie jar is wanted).
+ *
+ * {@inheritdoc}
+ */
+ public function getUserCookiesFilePath()
+ {
+ // NULL = We (the backend) will handle the cookie loading/saving.
+ return null;
+ }
+
+ /**
+ * (Non-cookiefile) Load all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserCookies()
+ {
+ return isset($this->_cache['cookies'])
+ ? $this->_cache['cookies']
+ : null;
+ }
+
+ /**
+ * (Non-cookiefile) Save all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserCookies(
+ $rawData)
+ {
+ // Store the raw cookie data as-provided.
+ $this->_setUserColumn('cookies', $rawData);
+ }
+
+ /**
+ * Close the settings storage for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function closeUser()
+ {
+ $this->_username = null;
+ $this->_cache = null;
+ }
+
+ /**
+ * Disconnect from a storage location and perform necessary shutdown steps.
+ *
+ * {@inheritdoc}
+ */
+ public function closeLocation()
+ {
+ // Delete our reference to the PDO object. If nobody else references
+ // it, the PDO connection will now be terminated. In case of shared
+ // objects, the original owner still has their reference (as intended).
+ $this->_pdo = null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/File.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/File.php
new file mode 100755
index 0000000..289d04b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/File.php
@@ -0,0 +1,385 @@
+_baseFolder = $this->_createFolder($baseFolder);
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUser(
+ $username)
+ {
+ // Check whether the user's settings-file exists.
+ $hasUser = $this->_generateUserPaths($username);
+
+ return is_file($hasUser['settingsFile']) ? true : false;
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * {@inheritdoc}
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ // Verify the old and new username parameters.
+ $oldUser = $this->_generateUserPaths($oldUsername);
+ $newUser = $this->_generateUserPaths($newUsername);
+ if (!is_dir($oldUser['userFolder'])) {
+ throw new SettingsException(sprintf(
+ 'Cannot move non-existent user folder "%s".',
+ $oldUser['userFolder']
+ ));
+ }
+ if (is_dir($newUser['userFolder'])) {
+ throw new SettingsException(sprintf(
+ 'Refusing to overwrite existing user folder "%s".',
+ $newUser['userFolder']
+ ));
+ }
+
+ // Create the new destination folder and migrate all data.
+ $this->_createFolder($newUser['userFolder']);
+ if (is_file($oldUser['settingsFile'])
+ && !@rename($oldUser['settingsFile'], $newUser['settingsFile'])) {
+ throw new SettingsException(sprintf(
+ 'Failed to move "%s" to "%s".',
+ $oldUser['settingsFile'], $newUser['settingsFile']
+ ));
+ }
+ if (is_file($oldUser['cookiesFile'])
+ && !@rename($oldUser['cookiesFile'], $newUser['cookiesFile'])) {
+ throw new SettingsException(sprintf(
+ 'Failed to move "%s" to "%s".',
+ $oldUser['cookiesFile'], $newUser['cookiesFile']
+ ));
+ }
+
+ // Delete all files in the old folder, and the folder itself.
+ Utils::deleteTree($oldUser['userFolder']);
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * {@inheritdoc}
+ */
+ public function deleteUser(
+ $username)
+ {
+ // Delete all files in the user folder, and the folder itself.
+ $delUser = $this->_generateUserPaths($username);
+ Utils::deleteTree($delUser['userFolder']);
+ }
+
+ /**
+ * Open the data storage for a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function openUser(
+ $username)
+ {
+ $this->_username = $username;
+ $userPaths = $this->_generateUserPaths($username);
+ $this->_userFolder = $userPaths['userFolder'];
+ $this->_settingsFile = $userPaths['settingsFile'];
+ $this->_cookiesFile = $userPaths['cookiesFile'];
+ $this->_createFolder($this->_userFolder);
+ }
+
+ /**
+ * Load all settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserSettings()
+ {
+ $userSettings = [];
+
+ if (!is_file($this->_settingsFile)) {
+ return $userSettings; // Nothing to load.
+ }
+
+ // Read from disk.
+ $rawData = @file_get_contents($this->_settingsFile);
+ if ($rawData === false) {
+ throw new SettingsException(sprintf(
+ 'Unable to read from settings file "%s".',
+ $this->_settingsFile
+ ));
+ }
+
+ // Fetch the data version ("FILESTORAGEv#;") header.
+ $dataVersion = 1; // Assume migration from v1 if no version.
+ if (preg_match('/^FILESTORAGEv(\d+);/', $rawData, $matches)) {
+ $dataVersion = intval($matches[1]);
+ $rawData = substr($rawData, strpos($rawData, ';') + 1);
+ }
+
+ // Decode the key-value pairs regardless of data-storage version.
+ $userSettings = $this->_decodeStorage($dataVersion, $rawData);
+
+ return $userSettings;
+ }
+
+ /**
+ * Save the settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserSettings(
+ array $userSettings,
+ $triggerKey)
+ {
+ // Generate the storage version header.
+ $versionHeader = 'FILESTORAGEv'.self::STORAGE_VERSION.';';
+
+ // Encode a binary representation of all settings.
+ // VERSION 2 STORAGE FORMAT: JSON-encoded blob.
+ $encodedData = $versionHeader.json_encode($userSettings);
+
+ // Perform an atomic diskwrite, which prevents accidental truncation.
+ // NOTE: If we had just written directly to settingsPath, the file would
+ // have become corrupted if the script was killed mid-write. The atomic
+ // write process guarantees that the data is fully written to disk.
+ Utils::atomicWrite($this->_settingsFile, $encodedData);
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUserCookies()
+ {
+ return is_file($this->_cookiesFile)
+ && filesize($this->_cookiesFile) > 0;
+ }
+
+ /**
+ * Get the cookiefile disk path (only if a file-based cookie jar is wanted).
+ *
+ * {@inheritdoc}
+ */
+ public function getUserCookiesFilePath()
+ {
+ // Tell the caller to use a file-based cookie jar.
+ return $this->_cookiesFile;
+ }
+
+ /**
+ * (Non-cookiefile) Load all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserCookies()
+ {
+ // Never called for "cookiefile" format.
+ }
+
+ /**
+ * (Non-cookiefile) Save all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserCookies(
+ $rawData)
+ {
+ // Never called for "cookiefile" format.
+ }
+
+ /**
+ * Close the settings storage for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function closeUser()
+ {
+ $this->_userFolder = null;
+ $this->_settingsFile = null;
+ $this->_cookiesFile = null;
+ $this->_username = null;
+ }
+
+ /**
+ * Disconnect from a storage location and perform necessary shutdown steps.
+ *
+ * {@inheritdoc}
+ */
+ public function closeLocation()
+ {
+ // We don't need to disconnect from anything since we are file-based.
+ }
+
+ /**
+ * Decodes the data from any File storage format version.
+ *
+ * @param int $dataVersion Which data format to decode.
+ * @param string $rawData The raw data, encoded in version's format.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array An array with all current key-value pairs for the user.
+ */
+ private function _decodeStorage(
+ $dataVersion,
+ $rawData)
+ {
+ $loadedSettings = [];
+
+ switch ($dataVersion) {
+ case 1:
+ /**
+ * This is the old format from v1.x of Instagram-API.
+ * Terrible format. Basic "key=value\r\n" and very fragile.
+ */
+
+ // Split by system-independent newlines. Tries \r\n (Win), then \r
+ // (pre-2000s Mac), then \n\r, then \n (Mac OS X, UNIX, Linux).
+ $lines = preg_split('/(\r\n?|\n\r?)/', $rawData, -1, PREG_SPLIT_NO_EMPTY);
+ if ($lines !== false) {
+ foreach ($lines as $line) {
+ // Key must be at least one character. Allows empty values.
+ if (preg_match('/^([^=]+)=(.*)$/', $line, $matches)) {
+ $key = $matches[1];
+ $value = rtrim($matches[2], "\r\n ");
+ $loadedSettings[$key] = $value;
+ }
+ }
+ }
+ break;
+ case 2:
+ /**
+ * Version 2 uses JSON encoding and perfectly stores any value.
+ * And file corruption can't happen, thanks to the atomic writer.
+ */
+ $loadedSettings = @json_decode($rawData, true, 512, JSON_BIGINT_AS_STRING);
+ if (!is_array($loadedSettings)) {
+ throw new SettingsException(sprintf(
+ 'Failed to decode corrupt settings file for account "%s".',
+ $this->_username
+ ));
+ }
+ break;
+ default:
+ throw new SettingsException(sprintf(
+ 'Invalid file settings storage format version "%d".',
+ $dataVersion
+ ));
+ }
+
+ return $loadedSettings;
+ }
+
+ /**
+ * Generates all path strings for a given username.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @return array An array with information about the user's paths.
+ */
+ private function _generateUserPaths(
+ $username)
+ {
+ $userFolder = $this->_baseFolder.'/'.$username;
+ $settingsFile = $userFolder.'/'.sprintf(self::SETTINGSFILE_NAME, $username);
+ $cookiesFile = $userFolder.'/'.sprintf(self::COOKIESFILE_NAME, $username);
+
+ return [
+ 'userFolder' => $userFolder,
+ 'settingsFile' => $settingsFile,
+ 'cookiesFile' => $cookiesFile,
+ ];
+ }
+
+ /**
+ * Creates a folder if missing, or ensures that it is writable.
+ *
+ * @param string $folder The directory path.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string The canonicalized absolute pathname of the folder, without
+ * any trailing slash.
+ */
+ private function _createFolder(
+ $folder)
+ {
+ if (!Utils::createFolder($folder)) {
+ throw new SettingsException(sprintf(
+ 'The "%s" folder is not writable.',
+ $folder
+ ));
+ }
+
+ // Determine the real path of the folder we created/checked.
+ // NOTE: This ensures that the path will work even on stingy systems
+ // such as Windows Server which chokes on multiple slashes in a row.
+ $realPath = @realpath($folder);
+ if (!is_string($realPath)) {
+ throw new SettingsException(sprintf(
+ 'Unable to resolve real path to folder "%s".',
+ $folder
+ ));
+ }
+
+ return $realPath;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/Memcached.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/Memcached.php
new file mode 100755
index 0000000..b14ce3b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/Memcached.php
@@ -0,0 +1,345 @@
+_isSharedMemcached = true;
+ $this->_memcached = $locationConfig['memcached'];
+ } else {
+ try {
+ // Configure memcached with a persistent data retention ID.
+ $this->_isSharedMemcached = false;
+ $this->_memcached = new PHPMemcached(
+ is_string($locationConfig['persistent_id'])
+ ? $locationConfig['persistent_id']
+ : 'instagram'
+ );
+
+ // Enable SASL authentication if credentials were provided.
+ // NOTE: PHP's Memcached API doesn't support individual
+ // authentication credentials per-server!
+ if (isset($locationConfig['sasl_username'])
+ && isset($locationConfig['sasl_password'])) {
+ // When SASL is used, the servers almost always NEED binary
+ // protocol, but if that doesn't work with the user's server
+ // then then the user can manually override this default by
+ // providing the "false" option via "memcached_options".
+ $this->_memcached->setOption(PHPMemcached::OPT_BINARY_PROTOCOL, true);
+ $this->_memcached->setSaslAuthData(
+ $locationConfig['sasl_username'],
+ $locationConfig['sasl_password']
+ );
+ }
+
+ // Apply any custom options the user has provided.
+ // NOTE: This is where "OPT_BINARY_PROTOCOL" can be overridden.
+ if (is_array($locationConfig['memcached_options'])) {
+ $this->_memcached->setOptions($locationConfig['memcached_options']);
+ }
+
+ // Add the provided servers to the pool.
+ if (is_array($locationConfig['servers'])) {
+ $this->_memcached->addServers($locationConfig['servers']);
+ } else {
+ // Use default port on localhost if nothing was provided.
+ $this->_memcached->addServer('localhost', 11211);
+ }
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Connection Failed: '.$e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * Retrieve a memcached key for a particular user.
+ *
+ * @param string $username The Instagram username.
+ * @param string $key Name of the subkey.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string|null The value as a string IF the user's key exists,
+ * otherwise NULL.
+ */
+ private function _getUserKey(
+ $username,
+ $key)
+ {
+ try {
+ $realKey = $username.'_'.$key;
+ $result = $this->_memcached->get($realKey);
+
+ return $this->_memcached->getResultCode() !== PHPMemcached::RES_NOTFOUND
+ ? (string) $result
+ : null;
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Set a memcached key for a particular user.
+ *
+ * @param string $username The Instagram username.
+ * @param string $key Name of the subkey.
+ * @param string|mixed $value The data to store. MUST be castable to string.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ private function _setUserKey(
+ $username,
+ $key,
+ $value)
+ {
+ try {
+ $realKey = $username.'_'.$key;
+ $success = $this->_memcached->set($realKey, (string) $value);
+ if (!$success) {
+ throw new SettingsException(sprintf(
+ 'Memcached failed to write to key "%s".',
+ $realKey
+ ));
+ }
+ } catch (SettingsException $e) {
+ throw $e; // Ugly but necessary to re-throw only our own messages.
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Delete a memcached key for a particular user.
+ *
+ * @param string $username The Instagram username.
+ * @param string $key Name of the subkey.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ private function _delUserKey(
+ $username,
+ $key)
+ {
+ try {
+ $realKey = $username.'_'.$key;
+ $this->_memcached->delete($realKey);
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUser(
+ $username)
+ {
+ // Check whether the user's settings exist (empty string allowed).
+ $hasUser = $this->_getUserKey($username, 'settings');
+
+ return $hasUser !== null ? true : false;
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * {@inheritdoc}
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ // Verify that the old username exists and fetch the old data.
+ $oldSettings = $this->_getUserKey($oldUsername, 'settings');
+ $oldCookies = $this->_getUserKey($oldUsername, 'cookies');
+ if ($oldSettings === null) { // Only settings are vital.
+ throw new SettingsException(sprintf(
+ 'Cannot move non-existent user "%s".',
+ $oldUsername
+ ));
+ }
+
+ // Verify that the new username does not exist.
+ if ($this->hasUser($newUsername)) {
+ throw new SettingsException(sprintf(
+ 'Refusing to overwrite existing user "%s".',
+ $newUsername
+ ));
+ }
+
+ // Now attempt to write all data to the new name.
+ $this->_setUserKey($newUsername, 'settings', $oldSettings);
+ if ($oldCookies !== null) { // Only if cookies existed.
+ $this->_setUserKey($newUsername, 'cookies', $oldCookies);
+ }
+
+ // Delete the previous user keys.
+ $this->deleteUser($oldUsername);
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * {@inheritdoc}
+ */
+ public function deleteUser(
+ $username)
+ {
+ $this->_delUserKey($username, 'settings');
+ $this->_delUserKey($username, 'cookies');
+ }
+
+ /**
+ * Open the data storage for a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function openUser(
+ $username)
+ {
+ // Just cache the username. We'll create storage later if necessary.
+ $this->_username = $username;
+ }
+
+ /**
+ * Load all settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserSettings()
+ {
+ $userSettings = [];
+
+ $encodedData = $this->_getUserKey($this->_username, 'settings');
+ if (!empty($encodedData)) {
+ $userSettings = @json_decode($encodedData, true, 512, JSON_BIGINT_AS_STRING);
+ if (!is_array($userSettings)) {
+ throw new SettingsException(sprintf(
+ 'Failed to decode corrupt settings for account "%s".',
+ $this->_username
+ ));
+ }
+ }
+
+ return $userSettings;
+ }
+
+ /**
+ * Save the settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserSettings(
+ array $userSettings,
+ $triggerKey)
+ {
+ // Store the settings as a JSON blob.
+ $encodedData = json_encode($userSettings);
+ $this->_setUserKey($this->_username, 'settings', $encodedData);
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUserCookies()
+ {
+ // Simply check if the storage key for cookies exists and is non-empty.
+ return !empty($this->loadUserCookies()) ? true : false;
+ }
+
+ /**
+ * Get the cookiefile disk path (only if a file-based cookie jar is wanted).
+ *
+ * {@inheritdoc}
+ */
+ public function getUserCookiesFilePath()
+ {
+ // NULL = We (the backend) will handle the cookie loading/saving.
+ return null;
+ }
+
+ /**
+ * (Non-cookiefile) Load all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserCookies()
+ {
+ return $this->_getUserKey($this->_username, 'cookies');
+ }
+
+ /**
+ * (Non-cookiefile) Save all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserCookies(
+ $rawData)
+ {
+ // Store the raw cookie data as-provided.
+ $this->_setUserKey($this->_username, 'cookies', $rawData);
+ }
+
+ /**
+ * Close the settings storage for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function closeUser()
+ {
+ $this->_username = null;
+ }
+
+ /**
+ * Disconnect from a storage location and perform necessary shutdown steps.
+ *
+ * {@inheritdoc}
+ */
+ public function closeLocation()
+ {
+ // Close all server connections if this was our own Memcached object.
+ if (!$this->_isSharedMemcached) {
+ try {
+ $this->_memcached->quit();
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Disconnection Failed: '.$e->getMessage());
+ }
+ }
+ $this->_memcached = null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/MySQL.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/MySQL.php
new file mode 100755
index 0000000..0d0a030
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/MySQL.php
@@ -0,0 +1,101 @@
+_pdo->query("SET NAMES 'utf8mb4'")->closeCursor();
+ }
+
+ /**
+ * Automatically create the database table if necessary.
+ *
+ * {@inheritdoc}
+ */
+ protected function _autoCreateTable()
+ {
+ // Detect the name of the MySQL database that PDO is connected to.
+ $dbName = $this->_pdo->query('SELECT database()')->fetchColumn();
+
+ // Abort if we already have the necessary table.
+ $sth = $this->_pdo->prepare('SELECT count(*) FROM information_schema.TABLES WHERE (TABLE_SCHEMA = :tableSchema) AND (TABLE_NAME = :tableName)');
+ $sth->execute([':tableSchema' => $dbName, ':tableName' => $this->_dbTableName]);
+ $result = $sth->fetchColumn();
+ $sth->closeCursor();
+ if ($result > 0) {
+ return;
+ }
+
+ // Create the database table. Throws in case of failure.
+ // NOTE: We store all settings as a binary JSON blob so that we support
+ // all current and future data without having to alter the table schema.
+ // NOTE: The username is a 150-character-max varchar, NOT 255! Why?
+ // Because MySQL has a 767-byte max limit for efficient indexes, and
+ // "utf8mb4" uses 4 bytes per character, which means that 191 characters
+ // is the maximum safe amount (191 * 4 = 764)! We chose 150 as a nice
+ // number. Instagram's username limit is 30, so our limit is fine!
+ // NOTE: We use "utf8mb4_general_ci" which performs fast, general
+ // sorting, since we have no need for language-aware "unicode" sorting.
+ // NOTE: Lastly... note that our encoding only affects the "username"
+ // column. All other columns are numbers, binary blobs, etc!
+ $this->_pdo->exec('CREATE TABLE `'.$this->_dbTableName.'` (
+ id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ username VARCHAR(150) NOT NULL,
+ settings MEDIUMBLOB NULL,
+ cookies MEDIUMBLOB NULL,
+ last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY (username)
+ ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ENGINE=InnoDB;');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/SQLite.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/SQLite.php
new file mode 100755
index 0000000..a32e254
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/Storage/SQLite.php
@@ -0,0 +1,115 @@
+_pdo->query('PRAGMA encoding = "UTF-8"')->closeCursor();
+ }
+
+ /**
+ * Automatically create the database table if necessary.
+ *
+ * {@inheritdoc}
+ */
+ protected function _autoCreateTable()
+ {
+ // Abort if we already have the necessary table.
+ $sth = $this->_pdo->prepare('SELECT count(*) FROM sqlite_master WHERE (type = "table") AND (name = :tableName)');
+ $sth->execute([':tableName' => $this->_dbTableName]);
+ $result = $sth->fetchColumn();
+ $sth->closeCursor();
+ if ($result > 0) {
+ return;
+ }
+
+ // Create the database table. Throws in case of failure.
+ // NOTE: We store all settings as a JSON blob so that we support all
+ // current and future data without having to alter the table schema.
+ // NOTE: SQLite automatically increments "integer primary key" cols.
+ $this->_pdo->exec('CREATE TABLE `'.$this->_dbTableName.'` (
+ id INTEGER PRIMARY KEY NOT NULL,
+ username TEXT NOT NULL UNIQUE,
+ settings BLOB,
+ cookies BLOB,
+ last_modified DATETIME DEFAULT CURRENT_TIMESTAMP
+ );');
+
+ // Set up a trigger to automatically update the modification timestamp.
+ // NOTE: The WHEN clause is important to avoid infinite recursive loops,
+ // otherwise you'll get "Error: too many levels of trigger recursion" if
+ // recursive triggers are enabled in SQLite. The WHEN constraint simply
+ // ensures that we only update last_modified automatically after UPDATEs
+ // that did NOT change last_modified. So our own UPDATEs of other fields
+ // will trigger this automatic UPDATE, which does an UPDATE with a NEW
+ // last_modified value, meaning that the trigger won't execute again!
+ $this->_pdo->exec('CREATE TRIGGER IF NOT EXISTS `'.$this->_dbTableName.'_update_last_modified`
+ AFTER UPDATE
+ ON `'.$this->_dbTableName.'`
+ FOR EACH ROW
+ WHEN NEW.last_modified = OLD.last_modified -- Avoids infinite loop.
+ BEGIN
+ UPDATE `'.$this->_dbTableName.'` SET last_modified=CURRENT_TIMESTAMP WHERE (id=OLD.id);
+ END;'
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/StorageHandler.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/StorageHandler.php
new file mode 100755
index 0000000..95b970b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/StorageHandler.php
@@ -0,0 +1,803 @@
+_callbacks = $callbacks;
+
+ // Connect the storage instance to the user's desired storage location.
+ $this->_storage = $storageInstance;
+ $this->_storage->openLocation($locationConfig);
+ }
+
+ /**
+ * Destructor.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function __destruct()
+ {
+ // The storage handler is being killed, so tell the location to close.
+ if ($this->_username !== null) {
+ $this->_triggerCallback('onCloseUser');
+ $this->_storage->closeUser();
+ $this->_username = null;
+ }
+ $this->_storage->closeLocation();
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return bool TRUE if user exists, otherwise FALSE.
+ */
+ public function hasUser(
+ $username)
+ {
+ $this->_throwIfEmptyValue($username);
+
+ return $this->_storage->hasUser($username);
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * This function is important because of the fact that all per-user settings
+ * in all Storage implementations are retrieved and stored via its Instagram
+ * username, since their NAME is literally the ONLY thing we know about a
+ * user before we have loaded their settings or logged in! So if you later
+ * rename that Instagram account, it means that your old device settings
+ * WON'T follow along automatically, since the new login username is seen
+ * as a brand new user that isn't in the settings storage.
+ *
+ * This function conveniently tells your chosen Storage backend to move a
+ * user's settings to a new name, so that they WILL be found again when you
+ * later look for settings for your new name.
+ *
+ * Bonus guide for easily confused people: YOU must manually rename your
+ * user on Instagram.com before you call this function. We don't do that.
+ *
+ * @param string $oldUsername The old name that settings are stored as.
+ * @param string $newUsername The new name to move the settings to.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ $this->_throwIfEmptyValue($oldUsername);
+ $this->_throwIfEmptyValue($newUsername);
+
+ if ($oldUsername === $this->_username
+ || $newUsername === $this->_username) {
+ throw new SettingsException(
+ 'Attempted to move settings to/from the currently active user.'
+ );
+ }
+
+ $this->_storage->moveUser($oldUsername, $newUsername);
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function deleteUser(
+ $username)
+ {
+ $this->_throwIfEmptyValue($username);
+
+ if ($username === $this->_username) {
+ throw new SettingsException(
+ 'Attempted to delete the currently active user.'
+ );
+ }
+
+ $this->_storage->deleteUser($username);
+ }
+
+ /**
+ * Load all settings for a user from the storage and mark as current user.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function setActiveUser(
+ $username)
+ {
+ $this->_throwIfEmptyValue($username);
+
+ // If that user is already loaded, there's no need to do anything.
+ if ($username === $this->_username) {
+ return;
+ }
+
+ // If we're switching away from a user, tell the backend to close the
+ // current user's storage (if it needs to do any special processing).
+ if ($this->_username !== null) {
+ $this->_triggerCallback('onCloseUser');
+ $this->_storage->closeUser();
+ }
+
+ // Set the new user as the current user for this storage instance.
+ $this->_username = $username;
+ $this->_userSettings = [];
+ $this->_storage->openUser($username);
+
+ // Retrieve any existing settings for the user from the backend.
+ $loadedSettings = $this->_storage->loadUserSettings();
+ foreach ($loadedSettings as $key => $value) {
+ // Map renamed old-school keys to new key names.
+ if ($key == 'username_id') {
+ $key = 'account_id';
+ } elseif ($key == 'adid') {
+ $key = 'advertising_id';
+ }
+
+ // Only keep values for keys that are still in use. Discard others.
+ if (in_array($key, self::PERSISTENT_KEYS)) {
+ // Cast all values to strings to ensure we only use strings!
+ // NOTE: THIS CAST IS EXTREMELY IMPORTANT AND *MUST* BE DONE!
+ $this->_userSettings[$key] = (string) $value;
+ }
+ }
+
+ // Determine what type of cookie storage the backend wants for the user.
+ // NOTE: Do NOT validate file existence, since we'll create if missing.
+ $cookiesFilePath = $this->_storage->getUserCookiesFilePath();
+ if ($cookiesFilePath !== null && (!is_string($cookiesFilePath) || !strlen($cookiesFilePath))) {
+ $cookiesFilePath = null; // Disable since it isn't a non-empty string.
+ }
+ $this->_cookiesFilePath = $cookiesFilePath;
+ }
+
+ /**
+ * Does a preliminary guess about whether the current user is logged in.
+ *
+ * Can only be executed after setActiveUser(). And the session it looks
+ * for may be expired, so there's no guarantee that we are still logged in.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return bool TRUE if possibly logged in, otherwise FALSE.
+ */
+ public function isMaybeLoggedIn()
+ {
+ $this->_throwIfNoActiveUser();
+
+ return $this->_storage->hasUserCookies()
+ && !empty($this->get('account_id'));
+ }
+
+ /**
+ * Erase all device-specific settings and all cookies.
+ *
+ * This is useful when assigning a new Android device to the account, upon
+ * which it's very important that we erase all previous, device-specific
+ * settings so that our account still looks natural to Instagram.
+ *
+ * Note that ALL cookies will be erased too, to clear out the old session.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function eraseDeviceSettings()
+ {
+ foreach (self::PERSISTENT_KEYS as $key) {
+ if (!in_array($key, self::KEEP_KEYS_WHEN_ERASING_DEVICE)) {
+ $this->set($key, ''); // Erase the setting.
+ }
+ }
+
+ $this->setCookies(''); // Erase all cookies.
+ }
+
+ /**
+ * Retrieve the value of a setting from the current user's memory cache.
+ *
+ * Can only be executed after setActiveUser().
+ *
+ * @param string $key Name of the setting.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string|null The value as a string IF the setting exists AND is
+ * a NON-EMPTY string. Otherwise NULL.
+ */
+ public function get(
+ $key)
+ {
+ $this->_throwIfNoActiveUser();
+
+ // Reject anything that isn't in our list of VALID persistent keys.
+ if (!in_array($key, self::PERSISTENT_KEYS)) {
+ throw new SettingsException(sprintf(
+ 'The settings key "%s" is not a valid persistent key name.',
+ $key
+ ));
+ }
+
+ // Return value if it's a NON-EMPTY string, otherwise return NULL.
+ // NOTE: All values are cached as strings so no casting is needed.
+ return (isset($this->_userSettings[$key])
+ && $this->_userSettings[$key] !== '')
+ ? $this->_userSettings[$key]
+ : null;
+ }
+
+ /**
+ * Store a setting's value for the current user.
+ *
+ * Can only be executed after setActiveUser(). To clear the value of a
+ * setting, simply pass in an empty string as value.
+ *
+ * @param string $key Name of the setting.
+ * @param string|mixed $value The data to store. MUST be castable to string.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function set(
+ $key,
+ $value)
+ {
+ $this->_throwIfNoActiveUser();
+
+ // Reject anything that isn't in our list of VALID persistent keys.
+ if (!in_array($key, self::PERSISTENT_KEYS)) {
+ throw new SettingsException(sprintf(
+ 'The settings key "%s" is not a valid persistent key name.',
+ $key
+ ));
+ }
+
+ // Reject null values, since they may be accidental. To unset a setting,
+ // the caller must explicitly pass in an empty string instead.
+ if ($value === null) {
+ throw new SettingsException(
+ 'Illegal attempt to store null value in settings storage.'
+ );
+ }
+
+ // Cast the value to string to ensure we don't try writing non-strings.
+ // NOTE: THIS CAST IS EXTREMELY IMPORTANT AND *MUST* ALWAYS BE DONE!
+ $value = (string) $value;
+
+ // Check if the value differs from our storage (cached representation).
+ // NOTE: This optimizes writes by only writing when values change!
+ if (!array_key_exists($key, $this->_userSettings)
+ || $this->_userSettings[$key] !== $value) {
+ // The value differs, so save to memory cache and write to storage.
+ $this->_userSettings[$key] = $value;
+ $this->_storage->saveUserSettings($this->_userSettings, $key);
+ }
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * Can only be executed after setActiveUser().
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return bool TRUE if cookies exist, otherwise FALSE.
+ */
+ public function hasCookies()
+ {
+ $this->_throwIfNoActiveUser();
+
+ return $this->_storage->hasUserCookies();
+ }
+
+ /**
+ * Get all cookies for the currently active user.
+ *
+ * Can only be executed after setActiveUser().
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string|null A previously-stored, raw cookie data string
+ * (non-empty), or NULL if no cookies exist for
+ * the active user.
+ */
+ public function getCookies()
+ {
+ $this->_throwIfNoActiveUser();
+
+ // Read the cookies via the appropriate backend method.
+ $userCookies = null;
+ if ($this->_cookiesFilePath === null) { // Backend storage.
+ $userCookies = $this->_storage->loadUserCookies();
+ } else { // Cookiefile on disk.
+ if (empty($this->_cookiesFilePath)) { // Just for extra safety.
+ throw new SettingsException(
+ 'Cookie file format requested, but no file path provided.'
+ );
+ }
+
+ // Ensure that the cookie file's folder exists and is writable.
+ $this->_createCookiesFileDirectory();
+
+ // Read the existing cookie jar file if it already exists.
+ if (is_file($this->_cookiesFilePath)) {
+ $rawData = file_get_contents($this->_cookiesFilePath);
+ if ($rawData !== false) {
+ $userCookies = $rawData;
+ }
+ }
+ }
+
+ // Ensure that we'll always return NULL if no cookies exist.
+ if ($userCookies !== null && !strlen($userCookies)) {
+ $userCookies = null;
+ }
+
+ return $userCookies;
+ }
+
+ /**
+ * Save all cookies for the currently active user.
+ *
+ * Can only be executed after setActiveUser(). Note that this function is
+ * called frequently!
+ *
+ * NOTE: It is very important that the owner of this SettingsHandler either
+ * continuously calls "setCookies", or better yet listens to the "closeUser"
+ * callback to save all cookies in bulk to storage at the end of a session.
+ *
+ * @param string $rawData An encoded string with all cookie data. Use an
+ * empty string to erase currently stored cookies.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function setCookies(
+ $rawData)
+ {
+ $this->_throwIfNoActiveUser();
+ $this->_throwIfNotString($rawData);
+
+ if ($this->_cookiesFilePath === null) { // Backend storage.
+ $this->_storage->saveUserCookies($rawData);
+ } else { // Cookiefile on disk.
+ if (strlen($rawData)) { // Update cookies (new value is non-empty).
+ // Perform an atomic diskwrite, which prevents accidental
+ // truncation if the script is ever interrupted mid-write.
+ $this->_createCookiesFileDirectory(); // Ensures dir exists.
+ $timeout = 5;
+ $init = time();
+ while (!$written = Utils::atomicWrite($this->_cookiesFilePath, $rawData)) {
+ usleep(mt_rand(400000, 600000)); // 0.4-0.6 sec
+ if (time() - $init > $timeout) {
+ break;
+ }
+ }
+ if ($written === false) {
+ throw new SettingsException(sprintf(
+ 'The "%s" cookie file is not writable.',
+ $this->_cookiesFilePath
+ ));
+ }
+ } else { // Delete cookies (empty string).
+ // Delete any existing cookie jar since the new data is empty.
+ if (is_file($this->_cookiesFilePath) && !@unlink($this->_cookiesFilePath)) {
+ throw new SettingsException(sprintf(
+ 'Unable to delete the "%s" cookie file.',
+ $this->_cookiesFilePath
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Ensures the whole directory path to the cookie file exists/is writable.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _createCookiesFileDirectory()
+ {
+ if ($this->_cookiesFilePath === null) {
+ return;
+ }
+
+ $cookieDir = dirname($this->_cookiesFilePath); // Can be "." in case of CWD.
+ if (!Utils::createFolder($cookieDir)) {
+ throw new SettingsException(sprintf(
+ 'The "%s" cookie folder is not writable.',
+ $cookieDir
+ ));
+ }
+ }
+
+ /**
+ * Internal: Ensures that a parameter is a string.
+ *
+ * @param mixed $value The value to check.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _throwIfNotString(
+ $value)
+ {
+ if (!is_string($value)) {
+ throw new SettingsException('Parameter must be string.');
+ }
+ }
+
+ /**
+ * Internal: Ensures that a parameter is a non-empty string.
+ *
+ * @param mixed $value The value to check.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _throwIfEmptyValue(
+ $value)
+ {
+ if (!is_string($value) || $value === '') {
+ throw new SettingsException('Parameter must be non-empty string.');
+ }
+ }
+
+ /**
+ * Internal: Ensures that there is an active storage user.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _throwIfNoActiveUser()
+ {
+ if ($this->_username === null) {
+ throw new SettingsException(
+ 'Called user-related function before setting the current storage user.'
+ );
+ }
+ }
+
+ /**
+ * Internal: Triggers a callback.
+ *
+ * All callback functions are given the storage handler instance as their
+ * one and only argument.
+ *
+ * @param string $cbName The name of the callback.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _triggerCallback(
+ $cbName)
+ {
+ // Reject anything that isn't in our list of VALID callbacks.
+ if (!in_array($cbName, self::SUPPORTED_CALLBACKS)) {
+ throw new SettingsException(sprintf(
+ 'The string "%s" is not a valid callback name.',
+ $cbName
+ ));
+ }
+
+ // Trigger the callback with a reference to our StorageHandler instance.
+ if (isset($this->_callbacks[$cbName])) {
+ try {
+ $this->_callbacks[$cbName]($this);
+ } catch (\Exception $e) {
+ // Re-wrap anything that isn't already a SettingsException.
+ if (!$e instanceof SettingsException) {
+ $e = new SettingsException($e->getMessage());
+ }
+
+ throw $e; // Re-throw;
+ }
+ }
+ }
+
+ /**
+ * Process and save experiments.
+ *
+ * @param array $experiments
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array A list of "good" experiments.
+ */
+ public function setExperiments(
+ array $experiments)
+ {
+ $filtered = [];
+ foreach (self::EXPERIMENT_KEYS as $key) {
+ if (!isset($experiments[$key])) {
+ continue;
+ }
+ $filtered[$key] = $experiments[$key];
+ }
+ $this->set('experiments', $this->_packJson($filtered));
+
+ return $filtered;
+ }
+
+ /**
+ * Return saved experiments.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array
+ */
+ public function getExperiments()
+ {
+ return $this->_unpackJson($this->get('experiments'), true);
+ }
+
+ /**
+ * Save rewrite rules.
+ *
+ * @param array $rules
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function setRewriteRules(
+ array $rules)
+ {
+ $this->set('zr_rules', $this->_packJson($rules));
+ }
+
+ /**
+ * Return saved rewrite rules.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array
+ */
+ public function getRewriteRules()
+ {
+ return $this->_unpackJson((string) $this->get('zr_rules'), true);
+ }
+
+ /**
+ * Save FBNS authorization.
+ *
+ * @param AuthInterface $auth
+ */
+ public function setFbnsAuth(
+ AuthInterface $auth)
+ {
+ $this->set('fbns_auth', $auth);
+ }
+
+ /**
+ * Get FBNS authorization.
+ *
+ * Will restore previously saved auth details if they exist. Otherwise it
+ * creates random new authorization details.
+ *
+ * @return AuthInterface
+ */
+ public function getFbnsAuth()
+ {
+ $result = new DeviceAuth();
+
+ try {
+ $result->read($this->get('fbns_auth'));
+ } catch (\Exception $e) {
+ }
+
+ return $result;
+ }
+
+ /**
+ * Pack data as JSON, deflating it when it saves some space.
+ *
+ * @param array|object $data
+ *
+ * @return string
+ */
+ protected function _packJson(
+ $data)
+ {
+ $json = json_encode($data);
+ $gzipped = base64_encode(zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9));
+ // We must compare gzipped with double encoded JSON.
+ $doubleJson = json_encode($json);
+ if (strlen($gzipped) < strlen($doubleJson)) {
+ $serialized = 'Z'.$gzipped;
+ } else {
+ $serialized = 'J'.$json;
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * Unpacks data from JSON encoded string, inflating it when necessary.
+ *
+ * @param string $packed
+ * @param bool $assoc
+ *
+ * @return array|object
+ */
+ protected function _unpackJson(
+ $packed,
+ $assoc = true)
+ {
+ if ($packed === null || $packed === '') {
+ return $assoc ? [] : new \stdClass();
+ }
+ $format = $packed[0];
+ $packed = substr($packed, 1);
+
+ try {
+ switch ($format) {
+ case 'Z':
+ $packed = base64_decode($packed, true);
+ if ($packed === false) {
+ throw new \RuntimeException('Invalid Base64 encoded string.');
+ }
+ $json = @zlib_decode($packed);
+ if ($json === false) {
+ throw new \RuntimeException('Invalid zlib encoded string.');
+ }
+ break;
+ case 'J':
+ $json = $packed;
+ break;
+ default:
+ throw new \RuntimeException('Invalid packed type.');
+ }
+ $data = json_decode($json, $assoc);
+ if ($assoc && !is_array($data)) {
+ throw new \RuntimeException('JSON is not an array.');
+ }
+ if (!$assoc && !is_object($data)) {
+ throw new \RuntimeException('JSON is not an object.');
+ }
+ } catch (\RuntimeException $e) {
+ $data = $assoc ? [] : new \stdClass();
+ }
+
+ return $data;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/StorageInterface.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/StorageInterface.php
new file mode 100755
index 0000000..239f356
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/src/Settings/StorageInterface.php
@@ -0,0 +1,264 @@
+ 4) {
+ if ($result > 0x7FFFFFFF) {
+ $result -= 0x100000000;
+ } elseif ($result < -0x80000000) {
+ $result += 0x100000000;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Reorders array by hashCode() of its keys.
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ public static function reorderByHashCode(
+ array $data)
+ {
+ $hashCodes = [];
+ foreach ($data as $key => $value) {
+ $hashCodes[$key] = self::hashCode($key);
+ }
+
+ uksort($data, function ($a, $b) use ($hashCodes) {
+ $a = $hashCodes[$a];
+ $b = $hashCodes[$b];
+ if ($a < $b) {
+ return -1;
+ } elseif ($a > $b) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+
+ return $data;
+ }
+
+ /**
+ * Generates random multipart boundary string.
+ *
+ * @return string
+ */
+ public static function generateMultipartBoundary()
+ {
+ $result = '';
+ $max = strlen(self::BOUNDARY_CHARS) - 1;
+ for ($i = 0; $i < self::BOUNDARY_LENGTH; ++$i) {
+ $result .= self::BOUNDARY_CHARS[mt_rand(0, $max)];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generates user breadcrumb for use when posting a comment.
+ *
+ * @param int $size
+ *
+ * @return string
+ */
+ public static function generateUserBreadcrumb(
+ $size)
+ {
+ $key = 'iN4$aGr0m';
+ $date = (int) (microtime(true) * 1000);
+
+ // typing time
+ $term = rand(2, 3) * 1000 + $size * rand(15, 20) * 100;
+
+ // android EditText change event occur count
+ $text_change_event_count = round($size / rand(2, 3));
+ if ($text_change_event_count == 0) {
+ $text_change_event_count = 1;
+ }
+
+ // generate typing data
+ $data = $size.' '.$term.' '.$text_change_event_count.' '.$date;
+
+ return base64_encode(hash_hmac('sha256', $data, $key, true))."\n".base64_encode($data)."\n";
+ }
+
+ /**
+ * Converts a hours/minutes/seconds timestamp to seconds.
+ *
+ * @param string $timeStr Either `HH:MM:SS[.###]` (24h-clock) or
+ * `MM:SS[.###]` or `SS[.###]`. The `[.###]` is for
+ * optional millisecond precision if wanted, such as
+ * `00:01:01.149`.
+ *
+ * @throws \InvalidArgumentException If any part of the input is invalid.
+ *
+ * @return float The number of seconds, with decimals (milliseconds).
+ */
+ public static function hmsTimeToSeconds(
+ $timeStr)
+ {
+ if (!is_string($timeStr)) {
+ throw new \InvalidArgumentException('Invalid non-string timestamp.');
+ }
+
+ $sec = 0.0;
+ foreach (array_reverse(explode(':', $timeStr)) as $offsetKey => $v) {
+ if ($offsetKey > 2) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid input "%s" with too many components (max 3 is allowed "HH:MM:SS").',
+ $timeStr
+ ));
+ }
+
+ // Parse component (supports "01" or "01.123" (milli-precision)).
+ if ($v === '' || !preg_match('/^\d+(?:\.\d+)?$/', $v)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid non-digit or empty component "%s" in time string "%s".',
+ $v, $timeStr
+ ));
+ }
+ if ($offsetKey !== 0 && strpos($v, '.') !== false) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Unexpected period in time component "%s" in time string "%s". Only the seconds-component supports milliseconds.',
+ $v, $timeStr
+ ));
+ }
+
+ // Convert the value to float and cap minutes/seconds to 60 (but
+ // allow any number of hours).
+ $v = (float) $v;
+ $maxValue = $offsetKey < 2 ? 60 : -1;
+ if ($maxValue >= 0 && $v > $maxValue) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid time component "%d" (its allowed range is 0-%d) in time string "%s".',
+ $v, $maxValue, $timeStr
+ ));
+ }
+
+ // Multiply the current component of the "01:02:03" string with the
+ // power of its offset. Hour-offset will be 2, Minutes 1 and Secs 0;
+ // and "pow(60, 0)" will return 1 which is why seconds work too.
+ $sec += pow(60, $offsetKey) * $v;
+ }
+
+ return $sec;
+ }
+
+ /**
+ * Converts seconds to a hours/minutes/seconds timestamp.
+ *
+ * @param int|float $sec The number of seconds. Can have fractions (millis).
+ *
+ * @throws \InvalidArgumentException If any part of the input is invalid.
+ *
+ * @return string The time formatted as `HH:MM:SS.###` (`###` is millis).
+ */
+ public static function hmsTimeFromSeconds(
+ $sec)
+ {
+ if (!is_int($sec) && !is_float($sec)) {
+ throw new \InvalidArgumentException('Seconds must be a number.');
+ }
+
+ $wasNegative = false;
+ if ($sec < 0) {
+ $wasNegative = true;
+ $sec = abs($sec);
+ }
+
+ $result = sprintf(
+ '%02d:%02d:%06.3f', // "%06f" is because it counts the whole string.
+ floor($sec / 3600),
+ floor(fmod($sec / 60, 60)),
+ fmod($sec, 60)
+ );
+
+ if ($wasNegative) {
+ $result = '-'.$result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Builds an Instagram media location JSON object in the correct format.
+ *
+ * This function is used whenever we need to send a location to Instagram's
+ * API. All endpoints (so far) expect location data in this exact format.
+ *
+ * @param Location $location A model object representing the location.
+ *
+ * @throws \InvalidArgumentException If the location is invalid.
+ *
+ * @return string The final JSON string ready to submit as an API parameter.
+ */
+ public static function buildMediaLocationJSON(
+ $location)
+ {
+ if (!$location instanceof Location) {
+ throw new \InvalidArgumentException('The location must be an instance of \InstagramAPI\Response\Model\Location.');
+ }
+
+ // Forbid locations that came from Location::searchFacebook() and
+ // Location::searchFacebookByPoint()! They have slightly different
+ // properties, and they don't always contain all data we need. The
+ // real application NEVER uses the "Facebook" endpoints for attaching
+ // locations to media, and NEITHER SHOULD WE.
+ if ($location->getFacebookPlacesId() !== null) {
+ throw new \InvalidArgumentException('You are not allowed to use Location model objects from the Facebook-based location search functions. They are not valid media locations!');
+ }
+
+ // Core location keys that always exist.
+ $obj = [
+ 'name' => $location->getName(),
+ 'lat' => $location->getLat(),
+ 'lng' => $location->getLng(),
+ 'address' => $location->getAddress(),
+ 'external_source' => $location->getExternalIdSource(),
+ ];
+
+ // Attach the location ID via a dynamically generated key.
+ // NOTE: This automatically generates a key such as "facebook_places_id".
+ $key = $location->getExternalIdSource().'_id';
+ $obj[$key] = $location->getExternalId();
+
+ // Ensure that all keys are listed in the correct hash order.
+ $obj = self::reorderByHashCode($obj);
+
+ return json_encode($obj);
+ }
+
+ /**
+ * Check for ffprobe dependency.
+ *
+ * TIP: If your binary isn't findable via the PATH environment locations,
+ * you can manually set the correct path to it. Before calling any functions
+ * that need FFprobe, you must simply assign a manual value (ONCE) to tell
+ * us where to find your FFprobe, like this:
+ *
+ * \InstagramAPI\Utils::$ffprobeBin = '/home/exampleuser/ffmpeg/bin/ffprobe';
+ *
+ * @return string|bool Name of the library if present, otherwise FALSE.
+ */
+ public static function checkFFPROBE()
+ {
+ // We only resolve this once per session and then cache the result.
+ if (self::$ffprobeBin === null) {
+ @exec('ffprobe -version 2>&1', $output, $statusCode);
+ if ($statusCode === 0) {
+ self::$ffprobeBin = 'ffprobe';
+ } else {
+ self::$ffprobeBin = false; // Nothing found!
+ }
+ }
+
+ return self::$ffprobeBin;
+ }
+
+ /**
+ * Verifies a user tag.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * user tag, and with proper values for them. We cannot validate that the
+ * user-id actually exists, but that's the job of the library user!
+ *
+ * @param mixed $userTag An array containing the user ID and the tag position.
+ * Example: ['position'=>[0.5,0.5],'user_id'=>'123'].
+ *
+ * @throws \InvalidArgumentException If the tag is invalid.
+ */
+ public static function throwIfInvalidUserTag(
+ $userTag)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($userTag)) {
+ throw new \InvalidArgumentException('User tag must be an array.');
+ }
+
+ // Check for required keys.
+ $requiredKeys = ['position', 'user_id'];
+ $missingKeys = array_diff($requiredKeys, array_keys($userTag));
+ if (!empty($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for user tag array.', implode('", "', $missingKeys)));
+ }
+
+ // Verify this product tag entry, ensuring that the entry is format
+ // ['position'=>[0.0,1.0],'user_id'=>'123'] and nothing else.
+ foreach ($userTag as $key => $value) {
+ switch ($key) {
+ case 'user_id':
+ if (!is_int($value) && !ctype_digit($value)) {
+ throw new \InvalidArgumentException('User ID must be an integer.');
+ }
+ if ($value < 0) {
+ throw new \InvalidArgumentException('User ID must be a positive integer.');
+ }
+ break;
+ case 'position':
+ try {
+ self::throwIfInvalidPosition($value);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(sprintf('Invalid user tag position: %s', $e->getMessage()), $e->getCode(), $e);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in user tag array.', $key));
+ }
+ }
+ }
+
+ /**
+ * Verifies an array of media usertags.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * usertags, and with proper values for them. We cannot validate that the
+ * user-id's actually exist, but that's the job of the library user!
+ *
+ * @param mixed $usertags The array of usertags, optionally with the "in" or
+ * "removed" top-level keys holding the usertags. Example:
+ * ['in'=>[['position'=>[0.5,0.5],'user_id'=>'123'], ...]].
+ *
+ * @throws \InvalidArgumentException If any tags are invalid.
+ */
+ public static function throwIfInvalidUsertags(
+ $usertags)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($usertags)) {
+ throw new \InvalidArgumentException('Usertags must be an array.');
+ }
+
+ if (empty($usertags)) {
+ throw new \InvalidArgumentException('Empty usertags array.');
+ }
+
+ foreach ($usertags as $k => $v) {
+ if (!is_array($v)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid usertags array. The value for key "%s" must be an array.', $k
+ ));
+ }
+ // Skip the section if it's empty.
+ if (empty($v)) {
+ continue;
+ }
+ // Handle ['in'=>[...], 'removed'=>[...]] top-level keys since
+ // this input contained top-level array keys containing the usertags.
+ switch ($k) {
+ case 'in':
+ foreach ($v as $idx => $userTag) {
+ try {
+ self::throwIfInvalidUserTag($userTag);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid usertag at index "%d": %s', $idx, $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+ break;
+ case 'removed':
+ // Check the array of userids to remove.
+ foreach ($v as $userId) {
+ if (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
+ throw new \InvalidArgumentException('Invalid user ID in usertags "removed" array.');
+ }
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in user tags array.', $k));
+ }
+ }
+ }
+
+ /**
+ * Verifies an array of product tags.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * product tags, and with proper values for them. We cannot validate that the
+ * product's-id actually exists, but that's the job of the library user!
+ *
+ * @param mixed $productTags The array of usertags, optionally with the "in" or
+ * "removed" top-level keys holding the usertags. Example:
+ * ['in'=>[['position'=>[0.5,0.5],'product_id'=>'123'], ...]].
+ *
+ * @throws \InvalidArgumentException If any tags are invalid.
+ */
+ public static function throwIfInvalidProductTags(
+ $productTags)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($productTags)) {
+ throw new \InvalidArgumentException('Products tags must be an array.');
+ }
+
+ if (empty($productTags)) {
+ throw new \InvalidArgumentException('Empty product tags array.');
+ }
+
+ foreach ($productTags as $k => $v) {
+ if (!is_array($v)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid product tags array. The value for key "%s" must be an array.', $k
+ ));
+ }
+
+ // Skip the section if it's empty.
+ if (empty($v)) {
+ continue;
+ }
+
+ // Handle ['in'=>[...], 'removed'=>[...]] top-level keys since
+ // this input contained top-level array keys containing the product tags.
+ switch ($k) {
+ case 'in':
+ // Check the array of product tags to insert.
+ foreach ($v as $idx => $productTag) {
+ try {
+ self::throwIfInvalidProductTag($productTag);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid product tag at index "%d": %s', $idx, $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+ break;
+ case 'removed':
+ // Check the array of product_id to remove.
+ foreach ($v as $productId) {
+ if (!ctype_digit($productId) && (!is_int($productId) || $productId < 0)) {
+ throw new \InvalidArgumentException('Invalid product ID in product tags "removed" array.');
+ }
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in product tags array.', $k));
+ }
+ }
+ }
+
+ /**
+ * Verifies a product tag.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * product tag, and with proper values for them. We cannot validate that the
+ * product-id actually exists, but that's the job of the library user!
+ *
+ * @param mixed $productTag An array containing the product ID and the tag position.
+ * Example: ['position'=>[0.5,0.5],'product_id'=>'123'].
+ *
+ * @throws \InvalidArgumentException If any tags are invalid.
+ */
+ public static function throwIfInvalidProductTag(
+ $productTag)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($productTag)) {
+ throw new \InvalidArgumentException('Product tag must be an array.');
+ }
+
+ // Check for required keys.
+ $requiredKeys = ['position', 'product_id'];
+ $missingKeys = array_diff($requiredKeys, array_keys($productTag));
+ if (!empty($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for product tag array.', implode('", "', $missingKeys)));
+ }
+
+ // Verify this product tag entry, ensuring that the entry is format
+ // ['position'=>[0.0,1.0],'product_id'=>'123'] and nothing else.
+ foreach ($productTag as $key => $value) {
+ switch ($key) {
+ case 'product_id':
+ if (!is_int($value) && !ctype_digit($value)) {
+ throw new \InvalidArgumentException('Product ID must be an integer.');
+ }
+ if ($value < 0) {
+ throw new \InvalidArgumentException('Product ID must be a positive integer.');
+ }
+ break;
+ case 'position':
+ try {
+ self::throwIfInvalidPosition($value);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(sprintf('Invalid product tag position: %s', $e->getMessage()), $e->getCode(), $e);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in product tag array.', $key));
+ }
+ }
+ }
+
+ /**
+ * Verifies a position.
+ *
+ * @param mixed $position An array containing a position coordinates.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function throwIfInvalidPosition(
+ $position)
+ {
+ if (!is_array($position)) {
+ throw new \InvalidArgumentException('Position must be an array.');
+ }
+
+ if (!isset($position[0])) {
+ throw new \InvalidArgumentException('X coordinate is required.');
+ }
+ $x = $position[0];
+ if (!is_int($x) && !is_float($x)) {
+ throw new \InvalidArgumentException('X coordinate must be a number.');
+ }
+ if ($x < 0.0 || $x > 1.0) {
+ throw new \InvalidArgumentException('X coordinate must be a float between 0.0 and 1.0.');
+ }
+
+ if (!isset($position[1])) {
+ throw new \InvalidArgumentException('Y coordinate is required.');
+ }
+ $y = $position[1];
+ if (!is_int($y) && !is_float($y)) {
+ throw new \InvalidArgumentException('Y coordinate must be a number.');
+ }
+ if ($y < 0.0 || $y > 1.0) {
+ throw new \InvalidArgumentException('Y coordinate must be a float between 0.0 and 1.0.');
+ }
+ }
+
+ /**
+ * Verifies that a single hashtag is valid.
+ *
+ * This function enforces the following requirements: It must be a string,
+ * at least 1 character long, and cannot contain the "#" character itself.
+ *
+ * @param mixed $hashtag The hashtag to check (should be string but we
+ * accept anything for checking purposes).
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function throwIfInvalidHashtag(
+ $hashtag)
+ {
+ if (!is_string($hashtag) || !strlen($hashtag)) {
+ throw new \InvalidArgumentException('Hashtag must be a non-empty string.');
+ }
+ // Perform an UTF-8 aware search for the illegal "#" symbol (anywhere).
+ // NOTE: We must use mb_strpos() to support international tags.
+ if (mb_strpos($hashtag, '#') !== false) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Hashtag "%s" is not allowed to contain the "#" character.',
+ $hashtag
+ ));
+ }
+ }
+
+ /**
+ * Verifies a rank token.
+ *
+ * @param string $rankToken
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function throwIfInvalidRankToken(
+ $rankToken
+ ) {
+ if (!Signatures::isValidUUID($rankToken)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid rank token.', $rankToken));
+ }
+ }
+
+ /**
+ * Verifies an array of story poll.
+ *
+ * @param array[] $storyPoll Array with story poll key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryPoll(
+ array $storyPoll)
+ {
+ $requiredKeys = ['question', 'viewer_vote', 'viewer_can_vote', 'tallies', 'is_sticker'];
+
+ if (count($storyPoll) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story poll is permitted. You added %d story polls.', count($storyPoll)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['question' => 1, 'viewer_vote' => 1, 'viewer_can_vote' => 1, 'tallies' => 1, 'is_sticker' => 1], $storyPoll[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story poll array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storyPoll[0] as $k => $v) {
+ switch ($k) {
+ case 'question':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_vote':
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_can_vote':
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'tallies':
+ if (!is_array($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ self::_throwIfInvalidStoryPollTallies($v);
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storyPoll[0], array_flip($requiredKeys)), 'polls');
+ }
+
+ /**
+ * Verifies an array of story slider.
+ *
+ * @param array[] $storySlider Array with story slider key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStorySlider(
+ array $storySlider)
+ {
+ $requiredKeys = ['question', 'viewer_vote', 'viewer_can_vote', 'slider_vote_average', 'slider_vote_count', 'emoji', 'background_color', 'text_color', 'is_sticker'];
+
+ if (count($storySlider) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story slider is permitted. You added %d story sliders.', count($storySlider)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['question' => 1, 'viewer_vote' => 1, 'viewer_can_vote' => 1, 'slider_vote_average' => 1, 'slider_vote_count' => 1, 'emoji' => 1, 'background_color' => 1, 'text_color' => 1, 'is_sticker' => 1], $storySlider[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story slider array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storySlider[0] as $k => $v) {
+ switch ($k) {
+ case 'question':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story slider array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_vote':
+ case 'slider_vote_count':
+ case 'slider_vote_average':
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story slider array-key "%s".', $v, $k));
+ }
+ break;
+ case 'background_color':
+ case 'text_color':
+ if (!preg_match('/^[0-9a-fA-F]{6}$/', substr($v, 1))) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story slider array-key "%s".', $v, $k));
+ }
+ break;
+ case 'emoji':
+ //TODO REQUIRES EMOJI VALIDATION
+ break;
+ case 'viewer_can_vote':
+ if (!is_bool($v) && $v !== false) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storySlider[0], array_flip($requiredKeys)), 'sliders');
+ }
+
+ /**
+ * Verifies an array of story question.
+ *
+ * @param array $storyQuestion Array with story question key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryQuestion(
+ array $storyQuestion)
+ {
+ $requiredKeys = ['z', 'viewer_can_interact', 'background_color', 'profile_pic_url', 'question_type', 'question', 'text_color', 'is_sticker'];
+
+ if (count($storyQuestion) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story question is permitted. You added %d story questions.', count($storyQuestion)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['viewer_can_interact' => 1, 'background_color' => 1, 'profile_pic_url' => 1, 'question_type' => 1, 'question' => 1, 'text_color' => 1, 'is_sticker' => 1], $storyQuestion[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story question array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storyQuestion[0] as $k => $v) {
+ switch ($k) {
+ case 'z': // May be used for AR in the future, for now it's always 0.
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_can_interact':
+ if (!is_bool($v) || $v !== false) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'background_color':
+ case 'text_color':
+ if (!preg_match('/^[0-9a-fA-F]{6}$/', substr($v, 1))) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'question_type':
+ // At this time only text questions are supported.
+ if (!is_string($v) || $v !== 'text') {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'question':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'profile_pic_url':
+ if (!self::hasValidWebURLSyntax($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storyQuestion[0], array_flip($requiredKeys)), 'questions');
+ }
+
+ /**
+ * Verifies an array of story countdown.
+ *
+ * @param array $storyCountdown Array with story countdown key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryCountdown(
+ array $storyCountdown)
+ {
+ $requiredKeys = ['z', 'text', 'text_color', 'start_background_color', 'end_background_color', 'digit_color', 'digit_card_color', 'end_ts', 'following_enabled', 'is_sticker'];
+
+ if (count($storyCountdown) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story countdown is permitted. You added %d story countdowns.', count($storyCountdown)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['z' => 1, 'text' => 1, 'text_color' => 1, 'start_background_color' => 1, 'end_background_color' => 1, 'digit_color' => 1, 'digit_card_color' => 1, 'end_ts' => 1, 'following_enabled' => 1, 'is_sticker' => 1], $storyCountdown[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story countdown array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storyCountdown[0] as $k => $v) {
+ switch ($k) {
+ case 'z': // May be used for AR in the future, for now it's always 0.
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'text':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'text_color':
+ case 'start_background_color':
+ case 'end_background_color':
+ case 'digit_color':
+ case 'digit_card_color':
+ if (!preg_match('/^[0-9a-fA-F]{6}$/', substr($v, 1))) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'end_ts':
+ if (!is_int($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'following_enabled':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storyCountdown[0], array_flip($requiredKeys)), 'countdowns');
+ }
+
+ /**
+ * Verifies if tallies are valid.
+ *
+ * @param array[] $tallies Array with story poll key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ protected static function _throwIfInvalidStoryPollTallies(
+ array $tallies)
+ {
+ $requiredKeys = ['text', 'count', 'font_size'];
+ if (count($tallies) !== 2) {
+ throw new \InvalidArgumentException(sprintf('Missing data for tallies.'));
+ }
+
+ foreach ($tallies as $tallie) {
+ $missingKeys = array_keys(array_diff_key(['text' => 1, 'count' => 1, 'font_size' => 1], $tallie));
+
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for location array.', implode(', ', $missingKeys)));
+ }
+ foreach ($tallie as $k => $v) {
+ if (!in_array($k, $requiredKeys, true)) {
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" for story poll tallies.', $k));
+ }
+ switch ($k) {
+ case 'text':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for tallies array-key "%s".', $v, $k));
+ }
+ break;
+ case 'count':
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for tallies array-key "%s".', $v, $k));
+ }
+ break;
+ case 'font_size':
+ if (!is_float($v) || ($v < 17.5 || $v > 35.0)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for tallies array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Verifies an array of story mentions.
+ *
+ * @param array[] $storyMentions The array of all story mentions.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryMentions(
+ array $storyMentions)
+ {
+ $requiredKeys = ['user_id'];
+
+ foreach ($storyMentions as $mention) {
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['user_id' => 1], $mention));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for mention array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($mention as $k => $v) {
+ switch ($k) {
+ case 'user_id':
+ if (!ctype_digit($v) && (!is_int($v) || $v < 0)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story mention array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($mention, array_flip($requiredKeys)), 'story mentions');
+ }
+ }
+
+ /**
+ * Verifies if a story location sticker is valid.
+ *
+ * @param array[] $locationSticker Array with location sticker key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryLocationSticker(
+ array $locationSticker)
+ {
+ $requiredKeys = ['location_id', 'is_sticker'];
+ $missingKeys = array_keys(array_diff_key(['location_id' => 1, 'is_sticker' => 1], $locationSticker));
+
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for location array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($locationSticker as $k => $v) {
+ switch ($k) {
+ case 'location_id':
+ if (!is_string($v) && !is_numeric($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for location array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for hashtag array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($locationSticker, array_flip($requiredKeys)), 'location');
+ }
+
+ /**
+ * Verifies if a caption is valid for a hashtag and verifies an array of hashtags.
+ *
+ * @param string $captionText The caption for the story hashtag to verify.
+ * @param array[] $hashtags The array of all story hashtags.
+ *
+ * @throws \InvalidArgumentException If caption doesn't contain any hashtag,
+ * or if any tags are invalid.
+ */
+ public static function throwIfInvalidStoryHashtags(
+ $captionText,
+ array $hashtags)
+ {
+ $requiredKeys = ['tag_name', 'use_custom_title', 'is_sticker'];
+
+ // Extract all hashtags from the caption using a UTF-8 aware regex.
+ if (!preg_match_all('/#(\w+[^\x00-\x7F]?+)/u', $captionText, $tagsInCaption)) {
+ throw new \InvalidArgumentException('Invalid caption for hashtag.');
+ }
+
+ // Verify all provided hashtags.
+ foreach ($hashtags as $hashtag) {
+ $missingKeys = array_keys(array_diff_key(['tag_name' => 1, 'use_custom_title' => 1, 'is_sticker' => 1], $hashtag));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for hashtag array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($hashtag as $k => $v) {
+ switch ($k) {
+ case 'tag_name':
+ // Ensure that the hashtag format is valid.
+ self::throwIfInvalidHashtag($v);
+ // Verify that this tag exists somewhere in the caption to check.
+ if (!in_array($v, $tagsInCaption[1])) { // NOTE: UTF-8 aware.
+ throw new \InvalidArgumentException(sprintf('Tag name "%s" does not exist in the caption text.', $v));
+ }
+ break;
+ case 'use_custom_title':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for hashtag array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for hashtag array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($hashtag, array_flip($requiredKeys)), 'hashtag');
+ }
+ }
+
+ /**
+ * Verifies an attached media.
+ *
+ * @param array[] $attachedMedia Array containing the attached media data.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidAttachedMedia(
+ array $attachedMedia)
+ {
+ $attachedMedia = reset($attachedMedia);
+ $requiredKeys = ['media_id', 'is_sticker'];
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['media_id' => 1, 'is_sticker' => 1], $attachedMedia));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for attached media.', implode(', ', $missingKeys)));
+ }
+
+ if (!is_string($attachedMedia['media_id']) && !is_numeric($attachedMedia['media_id'])) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for media_id.', $attachedMedia['media_id']));
+ }
+
+ if (!is_bool($attachedMedia['is_sticker']) && $attachedMedia['is_sticker'] !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for attached media.', $attachedMedia['is_sticker']));
+ }
+
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($attachedMedia, array_flip($requiredKeys)), 'attached media');
+ }
+
+ /**
+ * Verifies a story sticker's placement parameters.
+ *
+ * There are many kinds of story stickers, such as hashtags, locations,
+ * mentions, etc. To place them on the media, the user must provide certain
+ * parameters for things like position and size. This function verifies all
+ * of those parameters and ensures that the sticker placement is valid.
+ *
+ * @param array $storySticker The array describing the story sticker placement.
+ * @param string $type What type of sticker this is.
+ *
+ * @throws \InvalidArgumentException If storySticker is missing keys or has invalid values.
+ */
+ protected static function _throwIfInvalidStoryStickerPlacement(
+ array $storySticker,
+ $type)
+ {
+ $requiredKeys = ['x', 'y', 'width', 'height', 'rotation'];
+
+ // Ensure that all required hashtag array keys exist.
+ $missingKeys = array_keys(array_diff_key(['x' => 1, 'y' => 1, 'width' => 1, 'height' => 1, 'rotation' => 0], $storySticker));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for "%s".', implode(', ', $missingKeys), $type));
+ }
+
+ // Check the individual array values.
+ foreach ($storySticker as $k => $v) {
+ if (!in_array($k, $requiredKeys, true)) {
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" for "%s".', $k, $type));
+ }
+ switch ($k) {
+ case 'x':
+ case 'y':
+ case 'width':
+ case 'height':
+ case 'rotation':
+ if (!is_float($v) || $v < 0.0 || $v > 1.0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for "%s" key "%s".', $v, $type, $k));
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Checks and validates a media item's type.
+ *
+ * @param string|int $mediaType The type of the media item. One of: "PHOTO", "VIDEO"
+ * "CAROUSEL", or the raw value of the Item's "getMediaType()" function.
+ *
+ * @throws \InvalidArgumentException If the type is invalid.
+ *
+ * @return string The verified final type; either "PHOTO", "VIDEO" or "CAROUSEL".
+ */
+ public static function checkMediaType(
+ $mediaType)
+ {
+ if (ctype_digit($mediaType) || is_int($mediaType)) {
+ if ($mediaType == Item::PHOTO) {
+ $mediaType = 'PHOTO';
+ } elseif ($mediaType == Item::VIDEO) {
+ $mediaType = 'VIDEO';
+ } elseif ($mediaType == Item::CAROUSEL) {
+ $mediaType = 'CAROUSEL';
+ }
+ }
+ if (!in_array($mediaType, ['PHOTO', 'VIDEO', 'CAROUSEL'], true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media type.', $mediaType));
+ }
+
+ return $mediaType;
+ }
+
+ public static function formatBytes(
+ $bytes,
+ $precision = 2)
+ {
+ $units = ['B', 'kB', 'mB', 'gB', 'tB'];
+
+ $bytes = max($bytes, 0);
+ $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+ $pow = min($pow, count($units) - 1);
+
+ $bytes /= pow(1024, $pow);
+
+ return round($bytes, $precision).''.$units[$pow];
+ }
+
+ public static function colouredString(
+ $string,
+ $colour)
+ {
+ $colours['black'] = '0;30';
+ $colours['dark_gray'] = '1;30';
+ $colours['blue'] = '0;34';
+ $colours['light_blue'] = '1;34';
+ $colours['green'] = '0;32';
+ $colours['light_green'] = '1;32';
+ $colours['cyan'] = '0;36';
+ $colours['light_cyan'] = '1;36';
+ $colours['red'] = '0;31';
+ $colours['light_red'] = '1;31';
+ $colours['purple'] = '0;35';
+ $colours['light_purple'] = '1;35';
+ $colours['brown'] = '0;33';
+ $colours['yellow'] = '1;33';
+ $colours['light_gray'] = '0;37';
+ $colours['white'] = '1;37';
+
+ $colored_string = '';
+
+ if (isset($colours[$colour])) {
+ $colored_string .= "\033[".$colours[$colour].'m';
+ }
+
+ $colored_string .= $string."\033[0m";
+
+ return $colored_string;
+ }
+
+ public static function getFilterCode(
+ $filter)
+ {
+ $filters = [];
+ $filters[0] = 'Normal';
+ $filters[615] = 'Lark';
+ $filters[614] = 'Reyes';
+ $filters[613] = 'Juno';
+ $filters[612] = 'Aden';
+ $filters[608] = 'Perpetua';
+ $filters[603] = 'Ludwig';
+ $filters[605] = 'Slumber';
+ $filters[616] = 'Crema';
+ $filters[24] = 'Amaro';
+ $filters[17] = 'Mayfair';
+ $filters[23] = 'Rise';
+ $filters[26] = 'Hudson';
+ $filters[25] = 'Valencia';
+ $filters[1] = 'X-Pro II';
+ $filters[27] = 'Sierra';
+ $filters[28] = 'Willow';
+ $filters[2] = 'Lo-Fi';
+ $filters[3] = 'Earlybird';
+ $filters[22] = 'Brannan';
+ $filters[10] = 'Inkwell';
+ $filters[21] = 'Hefe';
+ $filters[15] = 'Nashville';
+ $filters[18] = 'Sutro';
+ $filters[19] = 'Toaster';
+ $filters[20] = 'Walden';
+ $filters[14] = '1977';
+ $filters[16] = 'Kelvin';
+ $filters[-2] = 'OES';
+ $filters[-1] = 'YUV';
+ $filters[109] = 'Stinson';
+ $filters[106] = 'Vesper';
+ $filters[112] = 'Clarendon';
+ $filters[118] = 'Maven';
+ $filters[114] = 'Gingham';
+ $filters[107] = 'Ginza';
+ $filters[113] = 'Skyline';
+ $filters[105] = 'Dogpatch';
+ $filters[115] = 'Brooklyn';
+ $filters[111] = 'Moon';
+ $filters[117] = 'Helena';
+ $filters[116] = 'Ashby';
+ $filters[108] = 'Charmes';
+ $filters[640] = 'BrightContrast';
+ $filters[642] = 'CrazyColor';
+ $filters[643] = 'SubtleColor';
+
+ return array_search($filter, $filters);
+ }
+
+ /**
+ * Creates a folder if missing, or ensures that it is writable.
+ *
+ * @param string $folder The directory path.
+ *
+ * @return bool TRUE if folder exists and is writable, otherwise FALSE.
+ */
+ public static function createFolder(
+ $folder)
+ {
+ // Test write-permissions for the folder and create/fix if necessary.
+ if ((is_dir($folder) && is_writable($folder))
+ || (!is_dir($folder) && mkdir($folder, 0755, true))
+ || chmod($folder, 0755)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Recursively deletes a file/directory tree.
+ *
+ * @param string $folder The directory path.
+ * @param bool $keepRootFolder Whether to keep the top-level folder.
+ *
+ * @return bool TRUE on success, otherwise FALSE.
+ */
+ public static function deleteTree(
+ $folder,
+ $keepRootFolder = false)
+ {
+ // Handle bad arguments.
+ if (empty($folder) || !file_exists($folder)) {
+ return true; // No such file/folder exists.
+ } elseif (is_file($folder) || is_link($folder)) {
+ return @unlink($folder); // Delete file/link.
+ }
+
+ // Delete all children.
+ $files = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
+ \RecursiveIteratorIterator::CHILD_FIRST
+ );
+
+ foreach ($files as $fileinfo) {
+ $action = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
+ if (!@$action($fileinfo->getRealPath())) {
+ return false; // Abort due to the failure.
+ }
+ }
+
+ // Delete the root folder itself?
+ return !$keepRootFolder ? @rmdir($folder) : true;
+ }
+
+ /**
+ * Atomic filewriter.
+ *
+ * Safely writes new contents to a file using an atomic two-step process.
+ * If the script is killed before the write is complete, only the temporary
+ * trash file will be corrupted.
+ *
+ * The algorithm also ensures that 100% of the bytes were written to disk.
+ *
+ * @param string $filename Filename to write the data to.
+ * @param string $data Data to write to file.
+ * @param string $atomicSuffix Lets you optionally provide a different
+ * suffix for the temporary file.
+ *
+ * @return int|bool Number of bytes written on success, otherwise `FALSE`.
+ */
+ public static function atomicWrite(
+ $filename,
+ $data,
+ $atomicSuffix = 'atomictmp')
+ {
+ // Perform an exclusive (locked) overwrite to a temporary file.
+ $filenameTmp = sprintf('%s.%s', $filename, $atomicSuffix);
+ $writeResult = @file_put_contents($filenameTmp, $data, LOCK_EX);
+
+ // Only proceed if we wrote 100% of the data bytes to disk.
+ if ($writeResult !== false && $writeResult === strlen($data)) {
+ // Now move the file to its real destination (replaces if exists).
+ $moveResult = @rename($filenameTmp, $filename);
+ if ($moveResult === true) {
+ // Successful write and move. Return number of bytes written.
+ return $writeResult;
+ }
+ }
+
+ // We've failed. Remove the temporary file if it exists.
+ if (is_file($filenameTmp)) {
+ @unlink($filenameTmp);
+ }
+
+ return false; // Failed.
+ }
+
+ /**
+ * Creates an empty temp file with a unique filename.
+ *
+ * @param string $outputDir Folder to place the temp file in.
+ * @param string $namePrefix (optional) What prefix to use for the temp file.
+ *
+ * @throws \RuntimeException If the file cannot be created.
+ *
+ * @return string
+ */
+ public static function createTempFile(
+ $outputDir,
+ $namePrefix = 'TEMP')
+ {
+ // Automatically generates a name like "INSTATEMP_" or "INSTAVID_" etc.
+ $finalPrefix = sprintf('INSTA%s_', $namePrefix);
+
+ // Try to create the file (detects errors).
+ $tmpFile = @tempnam($outputDir, $finalPrefix);
+ if (!is_string($tmpFile)) {
+ throw new \RuntimeException(sprintf(
+ 'Unable to create temporary output file in "%s" (with prefix "%s").',
+ $outputDir, $finalPrefix
+ ));
+ }
+
+ return $tmpFile;
+ }
+
+ /**
+ * Closes a file pointer if it's open.
+ *
+ * Always use this function instead of fclose()!
+ *
+ * Unlike the normal fclose(), this function is safe to call multiple times
+ * since it only attempts to close the pointer if it's actually still open.
+ * The normal fclose() would give an annoying warning in that scenario.
+ *
+ * @param resource $handle A file pointer opened by fopen() or fsockopen().
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
+ public static function safe_fclose(
+ $handle)
+ {
+ if (is_resource($handle)) {
+ return fclose($handle);
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if a URL has valid "web" syntax.
+ *
+ * This function is Unicode-aware.
+ *
+ * Be aware that it only performs URL syntax validation! It doesn't check
+ * if the domain/URL is fully valid and actually reachable!
+ *
+ * It verifies that the URL begins with either the "http://" or "https://"
+ * protocol, and that it must contain a host with at least one period in it,
+ * and at least two characters after the period (in other words, a TLD). The
+ * rest of the string can be any sequence of non-whitespace characters.
+ *
+ * For example, "http://localhost" will not be seen as a valid web URL, and
+ * "http://www.google.com foobar" is not a valid web URL since there's a
+ * space in it. But "https://bing.com" and "https://a.com/foo" are valid.
+ * However, "http://a.abdabdbadbadbsa" is also seen as a valid URL, since
+ * the validation is pretty simple and doesn't verify the TLDs (there are
+ * too many now to catch them all and new ones appear constantly).
+ *
+ * @param string $url
+ *
+ * @return bool TRUE if valid web syntax, otherwise FALSE.
+ */
+ public static function hasValidWebURLSyntax(
+ $url)
+ {
+ return (bool) preg_match('/^https?:\/\/[^\s.\/]+\.[^\s.\/]{2}\S*$/iu', $url);
+ }
+
+ /**
+ * Extract all URLs from a text string.
+ *
+ * This function is Unicode-aware.
+ *
+ * @param string $text The string to scan for URLs.
+ *
+ * @return array An array of URLs and their individual components.
+ */
+ public static function extractURLs(
+ $text)
+ {
+ $urls = [];
+ if (preg_match_all(
+ // NOTE: This disgusting regex comes from the Android SDK, slightly
+ // modified by Instagram and then encoded by us into PHP format. We
+ // are NOT allowed to tweak this regex! It MUST match the official
+ // app so that our link-detection acts *exactly* like the real app!
+ // NOTE: Here is the "to PHP regex" conversion algorithm we used:
+ // https://github.com/mgp25/Instagram-API/issues/1445#issuecomment-318921867
+ '/((?:(http|https|Http|Https|rtsp|Rtsp):\/\/(?:(?:[a-zA-Z0-9$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,64}(?:\:(?:[a-zA-Z0-9$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,25})?\@)?)?((?:(?:[a-zA-Z0-9\x{00A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\_][a-zA-Z0-9\x{00A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\_\-]{0,64}\.)+(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(?:\x{03B4}\x{03BF}\x{03BA}\x{03B9}\x{03BC}\x{03AE}|\x{0438}\x{0441}\x{043F}\x{044B}\x{0442}\x{0430}\x{043D}\x{0438}\x{0435}|\x{0440}\x{0444}|\x{0441}\x{0440}\x{0431}|\x{05D8}\x{05E2}\x{05E1}\x{05D8}|\x{0622}\x{0632}\x{0645}\x{0627}\x{06CC}\x{0634}\x{06CC}|\x{0625}\x{062E}\x{062A}\x{0628}\x{0627}\x{0631}|\x{0627}\x{0644}\x{0627}\x{0631}\x{062F}\x{0646}|\x{0627}\x{0644}\x{062C}\x{0632}\x{0627}\x{0626}\x{0631}|\x{0627}\x{0644}\x{0633}\x{0639}\x{0648}\x{062F}\x{064A}\x{0629}|\x{0627}\x{0644}\x{0645}\x{063A}\x{0631}\x{0628}|\x{0627}\x{0645}\x{0627}\x{0631}\x{0627}\x{062A}|\x{0628}\x{06BE}\x{0627}\x{0631}\x{062A}|\x{062A}\x{0648}\x{0646}\x{0633}|\x{0633}\x{0648}\x{0631}\x{064A}\x{0629}|\x{0641}\x{0644}\x{0633}\x{0637}\x{064A}\x{0646}|\x{0642}\x{0637}\x{0631}|\x{0645}\x{0635}\x{0631}|\x{092A}\x{0930}\x{0940}\x{0915}\x{094D}\x{0937}\x{093E}|\x{092D}\x{093E}\x{0930}\x{0924}|\x{09AD}\x{09BE}\x{09B0}\x{09A4}|\x{0A2D}\x{0A3E}\x{0A30}\x{0A24}|\x{0AAD}\x{0ABE}\x{0AB0}\x{0AA4}|\x{0B87}\x{0BA8}\x{0BCD}\x{0BA4}\x{0BBF}\x{0BAF}\x{0BBE}|\x{0B87}\x{0BB2}\x{0B99}\x{0BCD}\x{0B95}\x{0BC8}|\x{0B9A}\x{0BBF}\x{0B99}\x{0BCD}\x{0B95}\x{0BAA}\x{0BCD}\x{0BAA}\x{0BC2}\x{0BB0}\x{0BCD}|\x{0BAA}\x{0BB0}\x{0BBF}\x{0B9F}\x{0BCD}\x{0B9A}\x{0BC8}|\x{0C2D}\x{0C3E}\x{0C30}\x{0C24}\x{0C4D}|\x{0DBD}\x{0D82}\x{0D9A}\x{0DCF}|\x{0E44}\x{0E17}\x{0E22}|\x{30C6}\x{30B9}\x{30C8}|\x{4E2D}\x{56FD}|\x{4E2D}\x{570B}|\x{53F0}\x{6E7E}|\x{53F0}\x{7063}|\x{65B0}\x{52A0}\x{5761}|\x{6D4B}\x{8BD5}|\x{6E2C}\x{8A66}|\x{9999}\x{6E2F}|\x{D14C}\x{C2A4}\x{D2B8}|\x{D55C}\x{AD6D}|xn\-\-0zwm56d|xn\-\-11b5bs3a9aj6g|xn\-\-3e0b707e|xn\-\-45brj9c|xn\-\-80akhbyknj4f|xn\-\-90a3ac|xn\-\-9t4b11yi5a|xn\-\-clchc0ea0b2g2a9gcd|xn\-\-deba0ad|xn\-\-fiqs8s|xn\-\-fiqz9s|xn\-\-fpcrj9c3d|xn\-\-fzc2c9e2c|xn\-\-g6w251d|xn\-\-gecrj9c|xn\-\-h2brj9c|xn\-\-hgbk6aj7f53bba|xn\-\-hlcj6aya9esc7a|xn\-\-j6w193g|xn\-\-jxalpdlp|xn\-\-kgbechtv|xn\-\-kprw13d|xn\-\-kpry57d|xn\-\-lgbbat1ad8j|xn\-\-mgbaam7a8h|xn\-\-mgbayh7gpa|xn\-\-mgbbh1a71e|xn\-\-mgbc0a9azcg|xn\-\-mgberp4a5d4ar|xn\-\-o3cw4h|xn\-\-ogbpf8fl|xn\-\-p1ai|xn\-\-pgbs0dh|xn\-\-s9brj9c|xn\-\-wgbh1c|xn\-\-wgbl6a|xn\-\-xkc2al3hye2a|xn\-\-xkc2dl3a5ee0h|xn\-\-yfro4i67o|xn\-\-ygbi2ammx|xn\-\-zckzah|xxx)|y[et]|z[amw]))|(?:(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])))(?:\:\d{1,5})?)(\/(?:(?:[a-zA-Z0-9\x{00A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\;\/\?\:\@\&\=\#\~\-\.\+\!\*\'\(\)\,\_])|(?:\%[a-fA-F0-9]{2}))*)?(?:\b|$)/iu',
+ $text,
+ $matches,
+ PREG_SET_ORDER
+ ) !== false) {
+ foreach ($matches as $match) {
+ $urls[] = [
+ 'fullUrl' => $match[0], // "https://foo:bar@www.bing.com/?foo=#test"
+ 'baseUrl' => $match[1], // "https://foo:bar@www.bing.com"
+ 'protocol' => $match[2], // "https" (empty if no protocol)
+ 'domain' => $match[3], // "www.bing.com"
+ 'path' => isset($match[4]) ? $match[4] : '', // "/?foo=#test"
+ ];
+ }
+ }
+
+ return $urls;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/React/ConnectorTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/React/ConnectorTest.php
new file mode 100755
index 0000000..9414d97
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/React/ConnectorTest.php
@@ -0,0 +1,211 @@
+getMethod($method);
+ $reflectionMethod->setAccessible(true);
+
+ return $reflectionMethod->invokeArgs($object, $args);
+ }
+
+ /**
+ * @return Connector
+ */
+ protected function _createConnector()
+ {
+ /** @var Instagram $instagramMock */
+ $instagramMock = $this->createMock(Instagram::class);
+ /** @var LoopInterface $loopMock */
+ $loopMock = $this->createMock(LoopInterface::class);
+
+ return new Connector($instagramMock, $loopMock);
+ }
+
+ public function testEmptyProxyConfigShouldReturnNull()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ null,
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, null)
+ );
+ }
+
+ public function testSingleProxyConfigShouldReturnAsIs()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ '127.0.0.1:3128',
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, '127.0.0.1:3128')
+ );
+ }
+
+ public function testHttpProxyShouldThrow()
+ {
+ $connector = $this->_createConnector();
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('No proxy with CONNECT method found');
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, [
+ 'http' => '127.0.0.1:3128',
+ ]);
+ }
+
+ public function testMustPickHttpsProxy()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ '127.0.0.1:3128',
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, [
+ 'http' => '127.0.0.1:3127',
+ 'https' => '127.0.0.1:3128',
+ ])
+ );
+ }
+
+ public function testShouldReturnNullWhenHostInExceptions()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ null,
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, [
+ 'https' => '127.0.0.1:3128',
+ 'no' => ['.facebook.com'],
+ ])
+ );
+ }
+
+ public function testVerifyPeerEnabled()
+ {
+ $connector = $this->_createConnector();
+
+ $context = $this->_callProtectedMethod($connector, '_getSecureContext', true);
+ $this->assertInternalType('array', $context);
+ $this->assertCount(3, $context);
+ $this->assertArrayHasKey('verify_peer', $context);
+ $this->assertEquals(true, $context['verify_peer']);
+ $this->assertArrayHasKey('verify_peer_name', $context);
+ $this->assertEquals(true, $context['verify_peer_name']);
+ $this->assertArrayHasKey('allow_self_signed', $context);
+ $this->assertEquals(false, $context['allow_self_signed']);
+ }
+
+ public function testVerifyPeerDisabled()
+ {
+ $connector = $this->_createConnector();
+
+ $context = $this->_callProtectedMethod($connector, '_getSecureContext', false);
+ $this->assertInternalType('array', $context);
+ $this->assertCount(2, $context);
+ $this->assertArrayHasKey('verify_peer', $context);
+ $this->assertEquals(false, $context['verify_peer']);
+ $this->assertArrayHasKey('verify_peer_name', $context);
+ $this->assertEquals(false, $context['verify_peer_name']);
+ }
+
+ public function testVerifyPeerEnabledWithCustomCa()
+ {
+ $connector = $this->_createConnector();
+
+ $context = $this->_callProtectedMethod($connector, '_getSecureContext', __FILE__);
+ $this->assertInternalType('array', $context);
+ $this->assertCount(4, $context);
+ $this->assertArrayHasKey('cafile', $context);
+ $this->assertEquals(__FILE__, $context['cafile']);
+ $this->assertArrayHasKey('verify_peer', $context);
+ $this->assertEquals(true, $context['verify_peer']);
+ $this->assertArrayHasKey('verify_peer_name', $context);
+ $this->assertEquals(true, $context['verify_peer_name']);
+ $this->assertArrayHasKey('allow_self_signed', $context);
+ $this->assertEquals(false, $context['allow_self_signed']);
+ }
+
+ public function testVerifyPeerEnabledWithCustomCaMissing()
+ {
+ $connector = $this->_createConnector();
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('CA bundle not found');
+ $this->_callProtectedMethod($connector, '_getSecureContext', __FILE__.'.missing');
+ }
+
+ public function testVerifyPeerWithInvalidConfig()
+ {
+ $connector = $this->_createConnector();
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid verify request option');
+ $this->_callProtectedMethod($connector, '_getSecureContext', [__FILE__]);
+ }
+
+ public function testSecureConnector()
+ {
+ $connector = $this->_createConnector();
+
+ $secureConnector = $this->_callProtectedMethod($connector, '_getSecureConnector', [], null);
+ $this->assertInstanceOf(SecureConnector::class, $secureConnector);
+ }
+
+ public function testProxyWrappers()
+ {
+ $proxies = [
+ 'socks://127.0.0.1:1080' => SocksProxy::class,
+ 'socks4://127.0.0.1:1080' => SocksProxy::class,
+ 'socks4a://127.0.0.1:1080' => SocksProxy::class,
+ 'socks5://127.0.0.1:1080' => SocksProxy::class,
+ 'http://127.0.0.1:3128' => HttpConnectProxy::class,
+ 'https://127.0.0.1:3128' => HttpConnectProxy::class,
+ '127.0.0.1:3128' => HttpConnectProxy::class,
+ ];
+ foreach ($proxies as $proxy => $targetClass) {
+ $connector = $this->_createConnector();
+ /** @var ConnectorInterface $baseConnector */
+ $baseConnector = $this->createMock(ConnectorInterface::class);
+
+ $this->assertInstanceOf(
+ $targetClass,
+ $this->_callProtectedMethod($connector, '_wrapConnectorIntoProxy', $baseConnector, $proxy)
+ );
+ }
+ }
+
+ public function testProxyWithoutWrapperShouldThrow()
+ {
+ $connector = $this->_createConnector();
+ /** @var ConnectorInterface $baseConnector */
+ $baseConnector = $this->createMock(ConnectorInterface::class);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Unsupported proxy scheme');
+ $this->_callProtectedMethod($connector, '_wrapConnectorIntoProxy', $baseConnector, 'tcp://127.0.0.1');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/IndicateActivityTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/IndicateActivityTest.php
new file mode 100755
index 0000000..d68b2f0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/IndicateActivityTest.php
@@ -0,0 +1,57 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new IndicateActivity('abc', '123');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new IndicateActivity('-1', '123');
+ }
+
+ public function testCommandOutputWhenTrue()
+ {
+ $command = new IndicateActivity('123', true);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","activity_status":"1","action":"indicate_activity"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWhenFalse()
+ {
+ $command = new IndicateActivity('123', false);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","activity_status":"0","action":"indicate_activity"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new IndicateActivity('123', true, ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new IndicateActivity('123', true, ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","activity_status":"1","action":"indicate_activity"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/IrisSubscribeTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/IrisSubscribeTest.php
new file mode 100755
index 0000000..009caef
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/IrisSubscribeTest.php
@@ -0,0 +1,22 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid Iris sequence identifier');
+ new IrisSubscribe(-1);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new IrisSubscribe(777);
+ $this->assertEquals('{"seq_id":777}', json_encode($command));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/MarkSeenTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/MarkSeenTest.php
new file mode 100755
index 0000000..704f38d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/MarkSeenTest.php
@@ -0,0 +1,55 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new MarkSeen('abc', '123');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new MarkSeen('-1', '123');
+ }
+
+ public function testNonNumericThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new MarkSeen('123', 'abc');
+ }
+
+ public function testNegativeThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new MarkSeen('123', '-1');
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new MarkSeen('123', '456');
+ $this->assertEquals(
+ '{"thread_id":"123","item_id":"456","action":"mark_seen"}',
+ json_encode($command)
+ );
+ }
+
+ public function testCustomClientContextIsIgnored()
+ {
+ $command = new MarkSeen('123', '456', ['client_context' => 'test']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_id":"456","action":"mark_seen"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendHashtagTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendHashtagTest.php
new file mode 100755
index 0000000..92ed505
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendHashtagTest.php
@@ -0,0 +1,105 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendHashtag('abc', 'somehashtag');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendHashtag('-1', 'somehashtag');
+ }
+
+ public function testNonStringHashtagShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('be a string');
+ new SendHashtag('123', 2.5);
+ }
+
+ public function testEmptyHashtagShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not be empty');
+ new SendHashtag('123', '');
+ }
+
+ public function testNumberSignOnlyShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not be empty');
+ new SendHashtag('123', '#');
+ }
+
+ public function testTwoWordsShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be one word');
+ new SendHashtag('123', '#cool pics');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendHashtag('123', 'somehashtag', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendHashtag('123', 'somehashtag');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"hashtag","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithTrim()
+ {
+ $command = new SendHashtag('123', '#somehashtag ');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"hashtag","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendHashtag('123', 'somehashtag', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"hashtag","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendHashtag('123', 'somehashtag', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendHashtag('123', 'somehashtag', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"hashtag","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLikeTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLikeTest.php
new file mode 100755
index 0000000..26f6b70
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLikeTest.php
@@ -0,0 +1,48 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLike('abc');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLike('-1');
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendLike('123');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"like","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendLike('123', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendLike('123', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"like","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLocationTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLocationTest.php
new file mode 100755
index 0000000..3f87a1b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLocationTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLocation('abc', '123456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLocation('-1', '123456');
+ }
+
+ public function testInvalidLocationIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendLocation('123', 'location');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendLocation('123', '123456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendLocation('123', '123456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"location","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","venue_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendLocation('123', '123456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"location","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","venue_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendLocation('123', '123456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendLocation('123', '123456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"location","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"123456","action":"send_item","venue_id":"123456"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendPostTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendPostTest.php
new file mode 100755
index 0000000..9b5465c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendPostTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendPost('abc', '123_456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendPost('-1', '123_456');
+ }
+
+ public function testInvalidMediaIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendPost('123', 'abc_def');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendPost('123', '123_456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendPost('123', '123_456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"media_share","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item","media_id":"123_456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendPost('123', '123_456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"media_share","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item","media_id":"123_456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendPost('123', '123_456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendPost('123', '123_456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"media_share","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","action":"send_item","media_id":"123_456"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendProfileTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendProfileTest.php
new file mode 100755
index 0000000..912c6e8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendProfileTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendProfile('abc', '123456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendProfile('-1', '123456');
+ }
+
+ public function testInvalidUserIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendProfile('123', 'username');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendProfile('123', '123456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendProfile('123', '123456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"profile","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","profile_user_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendProfile('123', '123456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"profile","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","profile_user_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendProfile('123', '123456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendProfile('123', '123456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"profile","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"123456","action":"send_item","profile_user_id":"123456"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendReactionTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendReactionTest.php
new file mode 100755
index 0000000..b6edbed
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendReactionTest.php
@@ -0,0 +1,85 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendReaction('abc', '123', 'like', 'created');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendReaction('-1', '123', 'like', 'created');
+ }
+
+ public function testNonNumericThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new SendReaction('123', 'abc', 'like', 'created');
+ }
+
+ public function testNegativeThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new SendReaction('123', '-1', 'like', 'created');
+ }
+
+ public function testUnknownReactionTypeShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a supported reaction');
+ new SendReaction('123', '456', 'angry', 'created');
+ }
+
+ public function testUnknownReactionStatusShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a supported reaction status');
+ new SendReaction('123', '456', 'like', 'removed');
+ }
+
+ public function testCommandOutputWhenLikeIsCreated()
+ {
+ $command = new SendReaction('123', '456', 'like', 'created');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"reaction","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","reaction_type":"like","reaction_status":"created","item_id":"456","node_type":"item","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWhenLikeIsDeleted()
+ {
+ $command = new SendReaction('123', '456', 'like', 'deleted');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"reaction","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","reaction_type":"like","reaction_status":"deleted","item_id":"456","node_type":"item","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendReaction('123', '456', 'like', 'created', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendReaction('123', '456', 'like', 'created', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"reaction","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","reaction_type":"like","reaction_status":"created","item_id":"456","node_type":"item","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendStoryTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendStoryTest.php
new file mode 100755
index 0000000..c780482
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendStoryTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendStory('abc', '123_456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendStory('-1', '123_456');
+ }
+
+ public function testInvalidStoryIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendStory('123', 'abc_def');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendStory('123', '123_456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendStory('123', '123_456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"story_share","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123_456","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendStory('123', '123_456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"story_share","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123_456","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendStory('123', '123_456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendStory('123', '123_456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"story_share","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"123_456","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendTextTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendTextTest.php
new file mode 100755
index 0000000..78faf91
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/SendTextTest.php
@@ -0,0 +1,62 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendText('abc', 'test');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendText('-1', 'test');
+ }
+
+ public function testEmptyTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('can not be empty');
+ new SendText('123', '');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendText('123', null);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendText('123', 'test');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"text","text":"test","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendText('123', 'test', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendText('123', 'test', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"text","text":"test","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/UpdateSubscriptionsTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/UpdateSubscriptionsTest.php
new file mode 100755
index 0000000..c5b7a9a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Command/UpdateSubscriptionsTest.php
@@ -0,0 +1,22 @@
+assertEquals(
+ '{"sub":["ig/live_notification_subscribe/1111111111","ig/u/v1/1111111111"]}',
+ json_encode($command, JSON_UNESCAPED_SLASHES)
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/DirectTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/DirectTest.php
new file mode 100755
index 0000000..7014747
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/DirectTest.php
@@ -0,0 +1,308 @@
+createMock(EventEmitterInterface::class);
+ // listeners() call is at index 0.
+ $target->expects($this->at(1))->method('emit')->with(
+ 'thread-item-created',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(3, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('11111111111111111111111111111111111', $arguments[1]);
+ $this->assertArrayHasKey(2, $arguments);
+ $this->assertInstanceOf(DirectThreadItem::class, $arguments[2]);
+ /** @var DirectThreadItem $item */
+ $item = $arguments[2];
+ $this->assertEquals('TEXT', $item->getText());
+
+ return true;
+ })
+ );
+ // listeners() call is at index 2.
+ $target->expects($this->at(3))->method('emit')->with(
+ 'unseen-count-update',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('inbox', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(DirectSeenItemPayload::class, $arguments[1]);
+ /** @var DirectSeenItemPayload $payload */
+ $payload = $arguments[1];
+ $this->assertEquals(1, $payload->getCount());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/direct_v2/threads/111111111111111111111111111111111111111/items/11111111111111111111111111111111111","value":"{\"item_id\": \"11111111111111111111111111111111111\", \"user_id\": 1111111111, \"timestamp\": 1111111111111111, \"item_type\": \"text\", \"text\": \"TEXT\"}","doublePublish":true},{"op":"replace","path":"/direct_v2/inbox/unseen_count","value":"1","ts":"1111111111111111","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadUpdate()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-updated',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(DirectThread::class, $arguments[1]);
+ /** @var DirectThread $thread */
+ $thread = $arguments[1];
+ $this->assertEquals('111111111111111111111111111111111111111', $thread->getThreadId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"replace","path":"/direct_v2/inbox/threads/111111111111111111111111111111111111111","value":"{\"thread_id\": \"111111111111111111111111111111111111111\"}","doublePublish":true}],"publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadItemRemoval()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-item-removed',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('11111111111111111111111111111111111', $arguments[1]);
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"remove","path":"/direct_v2/threads/111111111111111111111111111111111111111/items/11111111111111111111111111111111111","value":"11111111111111111111111111111111111","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadActivity()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-activity',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(ThreadActivity::class, $arguments[1]);
+ /** @var ThreadActivity $activity */
+ $activity = $arguments[1];
+ $this->assertEquals(1, $activity->getActivityStatus());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/direct_v2/threads/111111111111111111111111111111111111111/activity_indicator_id/deadbeef-dead-beef-dead-beefdeadbeef","value":"{\"timestamp\": 1111111111111111, \"sender_id\": \"1111111111\", \"ttl\": 12000, \"activity_status\": 1}","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadHasSeen()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-seen',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(3, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('1111111111', $arguments[1]);
+ $this->assertArrayHasKey(2, $arguments);
+ $this->assertInstanceOf(DirectThreadLastSeenAt::class, $arguments[2]);
+ /** @var DirectThreadLastSeenAt $lastSeen */
+ $lastSeen = $arguments[2];
+ $this->assertEquals('11111111111111111111111111111111111', $lastSeen->getItemId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"replace","path":"/direct_v2/threads/111111111111111111111111111111111111111/participants/1111111111/has_seen","value":"{\"timestamp\": 1111111111111111, \"item_id\": 11111111111111111111111111111111111}","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadActionBadge()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'direct-story-action',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(ActionBadge::class, $arguments[1]);
+ /** @var ActionBadge $actionBadge */
+ $actionBadge = $arguments[1];
+ $this->assertEquals('raven_delivered', $actionBadge->getActionType());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"replace","path":"/direct_v2/visual_action_badge/111111111111111111111111111111111111111","value":"{\"action_type\": \"raven_delivered\", \"action_count\": 1, \"action_timestamp\": 1111111111111111}","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":3}'
+ ));
+ }
+
+ public function testStoryCreation()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ // listeners() call is at index 0.
+ $target->expects($this->at(1))->method('emit')->with(
+ 'direct-story-updated',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(3, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('11111111111111111111111111111111111', $arguments[1]);
+ $this->assertArrayHasKey(2, $arguments);
+ $this->assertInstanceOf(DirectThreadItem::class, $arguments[2]);
+ /** @var DirectThreadItem $item */
+ $item = $arguments[2];
+ $this->assertEquals('raven_media', $item->getItemType());
+
+ return true;
+ })
+ );
+ // listeners() call is at index 2.
+ $target->expects($this->at(3))->method('emit')->with(
+ 'unseen-count-update',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('visual_inbox', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(DirectSeenItemPayload::class, $arguments[1]);
+ /** @var DirectSeenItemPayload $payload */
+ $payload = $arguments[1];
+ $this->assertEquals(1, $payload->getCount());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/direct_v2/visual_threads/111111111111111111111111111111111111111/items/11111111111111111111111111111111111","value":"{\"item_id\": \"11111111111111111111111111111111111\", \"user_id\": 1111111111, \"timestamp\": 1111111111111111, \"item_type\": \"raven_media\", \"seen_user_ids\": [], \"reply_chain_count\": 0, \"view_mode\": \"once\"}","doublePublish":true},{"op":"replace","path":"/direct_v2/visual_inbox/unseen_count","value":"1","ts":"1111111111111111","doublePublish":true}],"version":"9.6.0","publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":3}'
+ ));
+ }
+
+ public function testSendItemAck()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'client-context-ack',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(AckAction::class, $arguments[0]);
+ /** @var AckAction $ack */
+ $ack = $arguments[0];
+ $this->assertEquals('11111111111111111111111111111111111', $ack->getPayload()->getItemId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"action": "item_ack", "status_code": "200", "payload": {"client_context": "deadbeef-dead-beef-dead-beefdeadbeef", "item_id": "11111111111111111111111111111111111", "timestamp": "1111111111111111", "thread_id": "111111111111111111111111111111111111111"}, "status": "ok"}'
+ ));
+ }
+
+ public function testActivityAck()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'client-context-ack',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(AckAction::class, $arguments[0]);
+ /** @var AckAction $ack */
+ $ack = $arguments[0];
+ $this->assertEquals('deadbeef-dead-beef-dead-beefdeadbeef', $ack->getPayload()->getClientContext());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"action": "item_ack", "status_code": "200", "payload": {"activity_status": 1, "indicate_activity_ts": 1111111111111111, "client_context": "deadbeef-dead-beef-dead-beefdeadbeef", "ttl": 10000}, "status": "ok"}'
+ ));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/IrisHandlerTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/IrisHandlerTest.php
new file mode 100755
index 0000000..c8a15d7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/IrisHandlerTest.php
@@ -0,0 +1,77 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'iris-subscribed',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(IrisSubscribeAck::class, $arguments[0]);
+ /** @var IrisSubscribeAck $ack */
+ $ack = $arguments[0];
+ $this->assertTrue($ack->getSucceeded());
+ $this->assertEquals(666, $ack->getSeqId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new IrisHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"succeeded":true,"seq_id":666,"error_type":null,"error_message":null,"subscribed_at_ms":1111111111111}'
+ ));
+ }
+
+ public function testQueueOverlow()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('IrisQueueOverflowException');
+
+ $handler = new IrisHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"succeeded":false,"seq_id":null,"error_type":1,"error_message":"IrisQueueOverflowException","subscribed_at_ms":null}'
+ ));
+ }
+
+ public function testQueueUnderflow()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('IrisQueueUnderflowException');
+
+ $handler = new IrisHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"succeeded":false,"seq_id":null,"error_type":1,"error_message":"IrisQueueUnderflowException","subscribed_at_ms":null}'
+ ));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/LiveTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/LiveTest.php
new file mode 100755
index 0000000..df3663e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/LiveTest.php
@@ -0,0 +1,74 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'live-started',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(LiveBroadcast::class, $arguments[0]);
+ /** @var LiveBroadcast $live */
+ $live = $arguments[0];
+ $this->assertEquals('11111111111111111', $live->getBroadcastId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new LiveHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/broadcast/11111111111111111/started","value":"{\"broadcast_id\": 11111111111111111, \"user\": {\"pk\": 1111111111, \"username\": \"USERNAME\", \"full_name\": \"\", \"is_private\": true, \"profile_pic_url\": \"\", \"profile_pic_id\": \"\", \"is_verified\": false}, \"published_time\": 1234567890, \"is_periodic\": 0, \"broadcast_message\": \"\", \"display_notification\": true}","doublePublish":true}],"lazy":false,"publisher":1111111111,"version":"9.7.0","publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":2}'
+ ));
+ }
+
+ public function testStopBroadcast()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'live-stopped',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(LiveBroadcast::class, $arguments[0]);
+ /** @var LiveBroadcast $live */
+ $live = $arguments[0];
+ $this->assertEquals('11111111111111111', $live->getBroadcastId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new LiveHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"remove","path":"/broadcast/11111111111111111/ended","value":"{\"broadcast_id\": 11111111111111111, \"user\": {\"pk\": 1111111111, \"username\": \"USERNAME\", \"full_name\": \"NAME\", \"is_private\": true, \"profile_pic_url\": \"\", \"profile_pic_id\": \"\", \"is_verified\": false}, \"published_time\": 1234567890, \"is_periodic\": 0, \"broadcast_message\": \"\", \"display_notification\": false}","doublePublish":true}],"lazy":false,"publisher":1111111111,"version":"10.8.0","publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/PresenceTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/PresenceTest.php
new file mode 100755
index 0000000..ea9c2a2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/PresenceTest.php
@@ -0,0 +1,63 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'presence',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(UserPresence::class, $arguments[0]);
+ /** @var UserPresence $payload */
+ $payload = $arguments[0];
+ $this->assertEquals('1111111111', $payload->getUserId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new PresenceHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"presence_event":{"user_id":"1111111111","is_active":true,"last_activity_at_ms":"123456789012","in_threads":null}}'
+ ));
+ }
+
+ public function testInvalidData()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('Invalid presence');
+
+ $handler = new PresenceHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{}'
+ ));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/RegionHintTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/RegionHintTest.php
new file mode 100755
index 0000000..d0f6f58
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Handler/RegionHintTest.php
@@ -0,0 +1,71 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'region-hint',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('ASH', $arguments[0]);
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new RegionHintHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ 'ASH'
+ ));
+ }
+
+ public function testEmptyRegionHint()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('Invalid region hint');
+
+ $handler = new RegionHintHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ ''
+ ));
+ }
+
+ public function testNullRegionHint()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('Invalid region hint');
+
+ $handler = new RegionHintHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ null
+ ));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Subscription/AppPresenceTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Subscription/AppPresenceTest.php
new file mode 100755
index 0000000..64b6d54
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Subscription/AppPresenceTest.php
@@ -0,0 +1,18 @@
+assertEquals(
+ '1/graphqlsubscriptions/17846944882223835/{"input_data":{"client_subscription_id":"deadbeef-dead-beef-dead-beefdeadbeef"}}',
+ (string) $subscription
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Subscription/ZeroProvisionTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Subscription/ZeroProvisionTest.php
new file mode 100755
index 0000000..993e2fa
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Realtime/Subscription/ZeroProvisionTest.php
@@ -0,0 +1,18 @@
+assertRegExp(
+ '#^1/graphqlsubscriptions/17913953740109069/{"input_data":{"client_subscription_id":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","device_id":"deadbeef-dead-beef-dead-beefdeadbeef"}}#',
+ (string) $subscription
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Response/ExceptionsTest.php b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Response/ExceptionsTest.php
new file mode 100755
index 0000000..2b20ace
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/tests/Response/ExceptionsTest.php
@@ -0,0 +1,112 @@
+expectException(LoginRequiredException::class);
+ $this->expectExceptionMessage('Login required');
+ $response = $this->_makeResponse('{"message":"login_required", "logout_reason": 2, "status": "fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testFeedbackRequiredException()
+ {
+ $this->expectException(FeedbackRequiredException::class);
+ $this->expectExceptionMessage('Feedback required');
+ $response = $this->_makeResponse('{"message":"feedback_required","spam":true,"feedback_title":"You\u2019re Temporarily Blocked","feedback_message":"It looks like you were misusing this feature by going too fast. You\u2019ve been blocked from using it.\n\nLearn more about blocks in the Help Center. We restrict certain content and actions to protect our community. Tell us if you think we made a mistake.","feedback_url":"WUT","feedback_appeal_label":"Report problem","feedback_ignore_label":"OK","feedback_action":"report_problem","status":"fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testConsentRequiredException()
+ {
+ $this->expectException(ConsentRequiredException::class);
+ $this->expectExceptionMessage('Consent required');
+ $response = $this->_makeResponse('{"message":"consent_required","consent_data":{"headline":"Updates to Our Terms and Data Policy","content":"We\'ve updated our Terms and made some changes to our Data Policy. Please take a moment to review these changes and let us know that you agree to them.\n\nYou need to finish reviewing this information before you can use Instagram.","button_text":"Review Now"},"status":"fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testCheckpointRequiredException()
+ {
+ $this->expectException(CheckpointRequiredException::class);
+ $this->expectExceptionMessage('Checkpoint required');
+ $response = $this->_makeResponse('{"message":"checkpoint_required","checkpoint_url":"WUT","lock":true,"status":"fail","error_type":"checkpoint_challenge_required"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testChallengeRequiredException()
+ {
+ $this->expectException(ChallengeRequiredException::class);
+ $this->expectExceptionMessage('Challenge required');
+ $response = $this->_makeResponse('{"message":"challenge_required","challenge":{"url":"https://i.instagram.com/challenge/","api_path":"/challenge/","hide_webview_header":false,"lock":true,"logout":false,"native_flow":true},"status":"fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testIncorrectPasswordException()
+ {
+ $this->expectException(IncorrectPasswordException::class);
+ $this->expectExceptionMessageRegExp('/password.*incorrect/i');
+ $response = $this->_makeResponse('{"message":"The password you entered is incorrect. Please try again.","invalid_credentials":true,"error_title":"Incorrect password for WUT","buttons":[{"title":"Try Again","action":"dismiss"}],"status":"fail","error_type":"bad_password"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testAccountDisabledException()
+ {
+ $this->expectException(AccountDisabledException::class);
+ $this->expectExceptionMessageRegExp('/account.*disabled/i');
+ $response = $this->_makeResponse('{"message":"Your account has been disabled for violating our terms. Learn how you may be able to restore your account."}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testInvalidUserException()
+ {
+ $this->expectException(InvalidUserException::class);
+ $this->expectExceptionMessageRegExp('/check.*username/i');
+ $response = $this->_makeResponse('{"message":"The username you entered doesn\'t appear to belong to an account. Please check your username and try again.","invalid_credentials":true,"error_title":"Incorrect Username","buttons":[{"title":"Try Again","action":"dismiss"}],"status":"fail","error_type":"invalid_user"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testSentryBlockException()
+ {
+ $this->expectException(SentryBlockException::class);
+ $this->expectExceptionMessageRegExp('/problem.*request/i');
+ $response = $this->_makeResponse('{"message":"Sorry, there was a problem with your request.","status":"fail","error_type":"sentry_block"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testInvalidSmsCodeException()
+ {
+ $this->expectException(InvalidSmsCodeException::class);
+ $this->expectExceptionMessageRegExp('/check.*code/i');
+ $response = $this->_makeResponse('{"message":"Please check the security code we sent you and try again.","status":"fail","error_type":"sms_code_validation_code_invalid"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/mgp25/instagram-php/webwarning.htm b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/webwarning.htm
new file mode 100755
index 0000000..a908f74
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/mgp25/instagram-php/webwarning.htm
@@ -0,0 +1,9 @@
+
Instagram-API Warning!
+
Can I run this library via a website?
+
No. Don't do it. You cannot safely use this or any other 3rd party Instagram libraries directly via a website!
+
This library (and all other 3rd party reverse engineered Instagram libraries), is made for command line usage in a terminal by running the script like a program (such as php yourscript.php). We do not recommend running this library via a web browser! Because browsers will terminate the PHP process as soon as the user closes their connection to the page (closes the tab, or presses "Stop loading page"), which means that your script can terminate at any moment. This library is not a webpage! It is an Instagram for Android application emulator. It emulates an application. Not a webpage. You cannot just randomly kill this library in the middle of work! (You might kill it while it's writing to disk or calling some important APIs!)
+
And furthermore, if it's used on a webpage then it must waste time logging in to Instagram every time the user refreshes the webpage, since each page load would start a new script (meaning "a new Instagram app emulator") from scratch. And there are also massive risks about concurrent access from multiple web requests to a single user account which can corrupt your settings-storage or cookies. So we really, really do not recommend running this library via a browser! It's a very bad idea!
+
Instead, you should run your Instagram app emulator script as a permanent 24/7 process, and make it dump data to a database which your regular website reads from, or make some kind of permanent localhost daemon that can listen locally on the server and receive queries on a port (localhost queries from the main website's scripts), and then performing those queries via the API and responding to the website script with something like JSON or with serialized src/Response/ objects, which the website script then transforms for final display to the user.
+
You must also be sure that your permanent process periodically calls login() inside your 24/7 PHP process, to emulate the Instagram for Android application being closed/opened and refreshing itself (like a real user would do). Otherwise it will soon get banned or silently limited as a "detected bot" by Instagram due to never calling login. Preferably use the same refresh ranges we use by default, so that you refresh login() within a random range between every 30 minutes, or at max every 6 hours.
+
So, to recap: Make your "Instagram application" part a permanent process, and then make your webpage interact with that permanent process or an intermediary database in some way. That's the proper architecture system if you want to use this library online! Imagine it like your website talking to a phone and telling its permanently running Instagram app to do things. That's the proper design and that's how you have to think about things.
+
Never forget this fact: This library (and all other 3rd party libraries) is an Android application emulator. It is not a website and will never be able to become a website (because the Android application is not a website!). Therefore, we will never give support to anyone who decides to use the library directly inside a webpage. If you do that, it's at your own risk! It is a really terrible and unsupported idea!
diff --git a/src/mpg25-instagram-api/vendor/psr/http-message/CHANGELOG.md b/src/mpg25-instagram-api/vendor/psr/http-message/CHANGELOG.md
new file mode 100755
index 0000000..74b1ef9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/http-message/CHANGELOG.md
@@ -0,0 +1,36 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1 - 2016-08-06
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Updated all `@return self` annotation references in interfaces to use
+ `@return static`, which more closelly follows the semantics of the
+ specification.
+- Updated the `MessageInterface::getHeaders()` return annotation to use the
+ value `string[][]`, indicating the format is a nested array of strings.
+- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
+ to point to the correct section of RFC 7230.
+- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
+ to add the parameter name (`$uploadedFiles`).
+- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
+ method to correctly reference the method parameter (it was referencing an
+ incorrect parameter name previously).
+
+## 1.0.0 - 2016-05-18
+
+Initial stable release; reflects accepted PSR-7 specification.
diff --git a/src/mpg25-instagram-api/vendor/psr/http-message/LICENSE b/src/mpg25-instagram-api/vendor/psr/http-message/LICENSE
new file mode 100755
index 0000000..c2d8e45
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/http-message/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/psr/http-message/README.md b/src/mpg25-instagram-api/vendor/psr/http-message/README.md
new file mode 100755
index 0000000..2818533
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/http-message/README.md
@@ -0,0 +1,13 @@
+PSR Http Message
+================
+
+This repository holds all interfaces/classes/traits related to
+[PSR-7](http://www.php-fig.org/psr/psr-7/).
+
+Note that this is not a HTTP message implementation of its own. It is merely an
+interface that describes a HTTP message. See the specification for more details.
+
+Usage
+-----
+
+We'll certainly need some stuff in here.
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/psr/http-message/composer.json b/src/mpg25-instagram-api/vendor/psr/http-message/composer.json
new file mode 100755
index 0000000..b0d2937
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/http-message/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/http-message",
+ "description": "Common interface for HTTP messages",
+ "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
+ "homepage": "https://github.com/php-fig/http-message",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/http-message/src/MessageInterface.php b/src/mpg25-instagram-api/vendor/psr/http-message/src/MessageInterface.php
new file mode 100755
index 0000000..dd46e5e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/http-message/src/MessageInterface.php
@@ -0,0 +1,187 @@
+getHeaders() as $name => $values) {
+ * echo $name . ": " . implode(", ", $values);
+ * }
+ *
+ * // Emit headers iteratively:
+ * foreach ($message->getHeaders() as $name => $values) {
+ * foreach ($values as $value) {
+ * header(sprintf('%s: %s', $name, $value), false);
+ * }
+ * }
+ *
+ * While header names are not case-sensitive, getHeaders() will preserve the
+ * exact case in which headers were originally specified.
+ *
+ * @return string[][] Returns an associative array of the message's headers. Each
+ * key MUST be a header name, and each value MUST be an array of strings
+ * for that header.
+ */
+ public function getHeaders();
+
+ /**
+ * Checks if a header exists by the given case-insensitive name.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return bool Returns true if any header names match the given header
+ * name using a case-insensitive string comparison. Returns false if
+ * no matching header name is found in the message.
+ */
+ public function hasHeader($name);
+
+ /**
+ * Retrieves a message header value by the given case-insensitive name.
+ *
+ * This method returns an array of all the header values of the given
+ * case-insensitive header name.
+ *
+ * If the header does not appear in the message, this method MUST return an
+ * empty array.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string[] An array of string values as provided for the given
+ * header. If the header does not appear in the message, this method MUST
+ * return an empty array.
+ */
+ public function getHeader($name);
+
+ /**
+ * Retrieves a comma-separated string of the values for a single header.
+ *
+ * This method returns all of the header values of the given
+ * case-insensitive header name as a string concatenated together using
+ * a comma.
+ *
+ * NOTE: Not all header values may be appropriately represented using
+ * comma concatenation. For such headers, use getHeader() instead
+ * and supply your own delimiter when concatenating.
+ *
+ * If the header does not appear in the message, this method MUST return
+ * an empty string.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string A string of values as provided for the given header
+ * concatenated together using a comma. If the header does not appear in
+ * the message, this method MUST return an empty string.
+ */
+ public function getHeaderLine($name);
+
+ /**
+ * Return an instance with the provided value replacing the specified header.
+ *
+ * While header names are case-insensitive, the casing of the header will
+ * be preserved by this function, and returned from getHeaders().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new and/or updated header and value.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withHeader($name, $value);
+
+ /**
+ * Return an instance with the specified header appended with the given value.
+ *
+ * Existing values for the specified header will be maintained. The new
+ * value(s) will be appended to the existing list. If the header did not
+ * exist previously, it will be added.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new header and/or value.
+ *
+ * @param string $name Case-insensitive header field name to add.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withAddedHeader($name, $value);
+
+ /**
+ * Return an instance without the specified header.
+ *
+ * Header resolution MUST be done without case-sensitivity.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the named header.
+ *
+ * @param string $name Case-insensitive header field name to remove.
+ * @return static
+ */
+ public function withoutHeader($name);
+
+ /**
+ * Gets the body of the message.
+ *
+ * @return StreamInterface Returns the body as a stream.
+ */
+ public function getBody();
+
+ /**
+ * Return an instance with the specified message body.
+ *
+ * The body MUST be a StreamInterface object.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return a new instance that has the
+ * new body stream.
+ *
+ * @param StreamInterface $body Body.
+ * @return static
+ * @throws \InvalidArgumentException When the body is not valid.
+ */
+ public function withBody(StreamInterface $body);
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/http-message/src/RequestInterface.php b/src/mpg25-instagram-api/vendor/psr/http-message/src/RequestInterface.php
new file mode 100755
index 0000000..a96d4fd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/http-message/src/RequestInterface.php
@@ -0,0 +1,129 @@
+getQuery()`
+ * or from the `QUERY_STRING` server param.
+ *
+ * @return array
+ */
+ public function getQueryParams();
+
+ /**
+ * Return an instance with the specified query string arguments.
+ *
+ * These values SHOULD remain immutable over the course of the incoming
+ * request. They MAY be injected during instantiation, such as from PHP's
+ * $_GET superglobal, or MAY be derived from some other value such as the
+ * URI. In cases where the arguments are parsed from the URI, the data
+ * MUST be compatible with what PHP's parse_str() would return for
+ * purposes of how duplicate query parameters are handled, and how nested
+ * sets are handled.
+ *
+ * Setting query string arguments MUST NOT change the URI stored by the
+ * request, nor the values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated query string arguments.
+ *
+ * @param array $query Array of query string arguments, typically from
+ * $_GET.
+ * @return static
+ */
+ public function withQueryParams(array $query);
+
+ /**
+ * Retrieve normalized file upload data.
+ *
+ * This method returns upload metadata in a normalized tree, with each leaf
+ * an instance of Psr\Http\Message\UploadedFileInterface.
+ *
+ * These values MAY be prepared from $_FILES or the message body during
+ * instantiation, or MAY be injected via withUploadedFiles().
+ *
+ * @return array An array tree of UploadedFileInterface instances; an empty
+ * array MUST be returned if no data is present.
+ */
+ public function getUploadedFiles();
+
+ /**
+ * Create a new instance with the specified uploaded files.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
+ * @return static
+ * @throws \InvalidArgumentException if an invalid structure is provided.
+ */
+ public function withUploadedFiles(array $uploadedFiles);
+
+ /**
+ * Retrieve any parameters provided in the request body.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, this method MUST
+ * return the contents of $_POST.
+ *
+ * Otherwise, this method may return any results of deserializing
+ * the request body content; as parsing returns structured content, the
+ * potential types MUST be arrays or objects only. A null value indicates
+ * the absence of body content.
+ *
+ * @return null|array|object The deserialized body parameters, if any.
+ * These will typically be an array or object.
+ */
+ public function getParsedBody();
+
+ /**
+ * Return an instance with the specified body parameters.
+ *
+ * These MAY be injected during instantiation.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, use this method
+ * ONLY to inject the contents of $_POST.
+ *
+ * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
+ * deserializing the request body content. Deserialization/parsing returns
+ * structured data, and, as such, this method ONLY accepts arrays or objects,
+ * or a null value if nothing was available to parse.
+ *
+ * As an example, if content negotiation determines that the request data
+ * is a JSON payload, this method could be used to create a request
+ * instance with the deserialized parameters.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param null|array|object $data The deserialized body data. This will
+ * typically be in an array or object.
+ * @return static
+ * @throws \InvalidArgumentException if an unsupported argument type is
+ * provided.
+ */
+ public function withParsedBody($data);
+
+ /**
+ * Retrieve attributes derived from the request.
+ *
+ * The request "attributes" may be used to allow injection of any
+ * parameters derived from the request: e.g., the results of path
+ * match operations; the results of decrypting cookies; the results of
+ * deserializing non-form-encoded message bodies; etc. Attributes
+ * will be application and request specific, and CAN be mutable.
+ *
+ * @return array Attributes derived from the request.
+ */
+ public function getAttributes();
+
+ /**
+ * Retrieve a single derived request attribute.
+ *
+ * Retrieves a single derived request attribute as described in
+ * getAttributes(). If the attribute has not been previously set, returns
+ * the default value as provided.
+ *
+ * This method obviates the need for a hasAttribute() method, as it allows
+ * specifying a default value to return if the attribute is not found.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $default Default value to return if the attribute does not exist.
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null);
+
+ /**
+ * Return an instance with the specified derived request attribute.
+ *
+ * This method allows setting a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $value The value of the attribute.
+ * @return static
+ */
+ public function withAttribute($name, $value);
+
+ /**
+ * Return an instance that removes the specified derived request attribute.
+ *
+ * This method allows removing a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @return static
+ */
+ public function withoutAttribute($name);
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/http-message/src/StreamInterface.php b/src/mpg25-instagram-api/vendor/psr/http-message/src/StreamInterface.php
new file mode 100755
index 0000000..f68f391
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/http-message/src/StreamInterface.php
@@ -0,0 +1,158 @@
+
+ * [user-info@]host[:port]
+ *
+ *
+ * If the port component is not set or is the standard port for the current
+ * scheme, it SHOULD NOT be included.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ * @return string The URI authority, in "[user-info@]host[:port]" format.
+ */
+ public function getAuthority();
+
+ /**
+ * Retrieve the user information component of the URI.
+ *
+ * If no user information is present, this method MUST return an empty
+ * string.
+ *
+ * If a user is present in the URI, this will return that value;
+ * additionally, if the password is also present, it will be appended to the
+ * user value, with a colon (":") separating the values.
+ *
+ * The trailing "@" character is not part of the user information and MUST
+ * NOT be added.
+ *
+ * @return string The URI user information, in "username[:password]" format.
+ */
+ public function getUserInfo();
+
+ /**
+ * Retrieve the host component of the URI.
+ *
+ * If no host is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.2.2.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @return string The URI host.
+ */
+ public function getHost();
+
+ /**
+ * Retrieve the port component of the URI.
+ *
+ * If a port is present, and it is non-standard for the current scheme,
+ * this method MUST return it as an integer. If the port is the standard port
+ * used with the current scheme, this method SHOULD return null.
+ *
+ * If no port is present, and no scheme is present, this method MUST return
+ * a null value.
+ *
+ * If no port is present, but a scheme is present, this method MAY return
+ * the standard port for that scheme, but SHOULD return null.
+ *
+ * @return null|int The URI port.
+ */
+ public function getPort();
+
+ /**
+ * Retrieve the path component of the URI.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Normally, the empty path "" and absolute path "/" are considered equal as
+ * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+ * do this normalization because in contexts with a trimmed base path, e.g.
+ * the front controller, this difference becomes significant. It's the task
+ * of the user to handle both "" and "/".
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.3.
+ *
+ * As an example, if the value should include a slash ("/") not intended as
+ * delimiter between path segments, that value MUST be passed in encoded
+ * form (e.g., "%2F") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.3
+ * @return string The URI path.
+ */
+ public function getPath();
+
+ /**
+ * Retrieve the query string of the URI.
+ *
+ * If no query string is present, this method MUST return an empty string.
+ *
+ * The leading "?" character is not part of the query and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.4.
+ *
+ * As an example, if a value in a key/value pair of the query string should
+ * include an ampersand ("&") not intended as a delimiter between values,
+ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ * @return string The URI query string.
+ */
+ public function getQuery();
+
+ /**
+ * Retrieve the fragment component of the URI.
+ *
+ * If no fragment is present, this method MUST return an empty string.
+ *
+ * The leading "#" character is not part of the fragment and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.5.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.5
+ * @return string The URI fragment.
+ */
+ public function getFragment();
+
+ /**
+ * Return an instance with the specified scheme.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified scheme.
+ *
+ * Implementations MUST support the schemes "http" and "https" case
+ * insensitively, and MAY accommodate other schemes if required.
+ *
+ * An empty scheme is equivalent to removing the scheme.
+ *
+ * @param string $scheme The scheme to use with the new instance.
+ * @return static A new instance with the specified scheme.
+ * @throws \InvalidArgumentException for invalid or unsupported schemes.
+ */
+ public function withScheme($scheme);
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; an empty string for the user is equivalent to removing user
+ * information.
+ *
+ * @param string $user The user name to use for authority.
+ * @param null|string $password The password associated with $user.
+ * @return static A new instance with the specified user information.
+ */
+ public function withUserInfo($user, $password = null);
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * An empty host value is equivalent to removing the host.
+ *
+ * @param string $host The hostname to use with the new instance.
+ * @return static A new instance with the specified host.
+ * @throws \InvalidArgumentException for invalid hostnames.
+ */
+ public function withHost($host);
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * Implementations MUST raise an exception for ports outside the
+ * established TCP and UDP port ranges.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param null|int $port The port to use with the new instance; a null value
+ * removes the port information.
+ * @return static A new instance with the specified port.
+ * @throws \InvalidArgumentException for invalid ports.
+ */
+ public function withPort($port);
+
+ /**
+ * Return an instance with the specified path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified path.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * If the path is intended to be domain-relative rather than path relative then
+ * it must begin with a slash ("/"). Paths not starting with a slash ("/")
+ * are assumed to be relative to some base path known to the application or
+ * consumer.
+ *
+ * Users can provide both encoded and decoded path characters.
+ * Implementations ensure the correct encoding as outlined in getPath().
+ *
+ * @param string $path The path to use with the new instance.
+ * @return static A new instance with the specified path.
+ * @throws \InvalidArgumentException for invalid paths.
+ */
+ public function withPath($path);
+
+ /**
+ * Return an instance with the specified query string.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified query string.
+ *
+ * Users can provide both encoded and decoded query characters.
+ * Implementations ensure the correct encoding as outlined in getQuery().
+ *
+ * An empty query string value is equivalent to removing the query string.
+ *
+ * @param string $query The query string to use with the new instance.
+ * @return static A new instance with the specified query string.
+ * @throws \InvalidArgumentException for invalid query strings.
+ */
+ public function withQuery($query);
+
+ /**
+ * Return an instance with the specified URI fragment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified URI fragment.
+ *
+ * Users can provide both encoded and decoded fragment characters.
+ * Implementations ensure the correct encoding as outlined in getFragment().
+ *
+ * An empty fragment value is equivalent to removing the fragment.
+ *
+ * @param string $fragment The fragment to use with the new instance.
+ * @return static A new instance with the specified fragment.
+ */
+ public function withFragment($fragment);
+
+ /**
+ * Return the string representation as a URI reference.
+ *
+ * Depending on which components of the URI are present, the resulting
+ * string is either a full URI or relative reference according to RFC 3986,
+ * Section 4.1. The method concatenates the various components of the URI,
+ * using the appropriate delimiters:
+ *
+ * - If a scheme is present, it MUST be suffixed by ":".
+ * - If an authority is present, it MUST be prefixed by "//".
+ * - The path can be concatenated without delimiters. But there are two
+ * cases where the path has to be adjusted to make the URI reference
+ * valid as PHP does not allow to throw an exception in __toString():
+ * - If the path is rootless and an authority is present, the path MUST
+ * be prefixed by "/".
+ * - If the path is starting with more than one "/" and no authority is
+ * present, the starting slashes MUST be reduced to one.
+ * - If a query is present, it MUST be prefixed by "?".
+ * - If a fragment is present, it MUST be prefixed by "#".
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ * @return string
+ */
+ public function __toString();
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/log/.gitignore b/src/mpg25-instagram-api/vendor/psr/log/.gitignore
new file mode 100755
index 0000000..22d0d82
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/.gitignore
@@ -0,0 +1 @@
+vendor
diff --git a/src/mpg25-instagram-api/vendor/psr/log/LICENSE b/src/mpg25-instagram-api/vendor/psr/log/LICENSE
new file mode 100755
index 0000000..474c952
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/AbstractLogger.php b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/AbstractLogger.php
new file mode 100755
index 0000000..90e721a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/AbstractLogger.php
@@ -0,0 +1,128 @@
+log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/InvalidArgumentException.php
new file mode 100755
index 0000000..67f852d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+logger = $logger;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/LoggerInterface.php b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/LoggerInterface.php
new file mode 100755
index 0000000..e695046
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/LoggerInterface.php
@@ -0,0 +1,125 @@
+log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ abstract public function log($level, $message, array $context = array());
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/NullLogger.php b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/NullLogger.php
new file mode 100755
index 0000000..c8f7293
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/NullLogger.php
@@ -0,0 +1,30 @@
+logger) { }`
+ * blocks.
+ */
+class NullLogger extends AbstractLogger
+{
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ public function log($level, $message, array $context = array())
+ {
+ // noop
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
new file mode 100755
index 0000000..8e445ee
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
@@ -0,0 +1,145 @@
+ ".
+ *
+ * Example ->error('Foo') would yield "error Foo".
+ *
+ * @return string[]
+ */
+ abstract public function getLogs();
+
+ public function testImplements()
+ {
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @dataProvider provideLevelsAndMessages
+ */
+ public function testLogsAtAllLevels($level, $message)
+ {
+ $logger = $this->getLogger();
+ $logger->{$level}($message, array('user' => 'Bob'));
+ $logger->log($level, $message, array('user' => 'Bob'));
+
+ $expected = array(
+ $level.' message of level '.$level.' with context: Bob',
+ $level.' message of level '.$level.' with context: Bob',
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function provideLevelsAndMessages()
+ {
+ return array(
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+ );
+ }
+
+ /**
+ * @expectedException \Psr\Log\InvalidArgumentException
+ */
+ public function testThrowsOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+ $logger->log('invalid level', 'Foo');
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+ $expected = array('info {Message {nothing} Bob Bar a}');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testObjectCastToString()
+ {
+ if (method_exists($this, 'createPartialMock')) {
+ $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
+ } else {
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+ }
+ $dummy->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('DUMMY'));
+
+ $this->getLogger()->warning($dummy);
+
+ $expected = array('warning DUMMY');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextCanContainAnything()
+ {
+ $closed = fopen('php://memory', 'r');
+ fclose($closed);
+
+ $context = array(
+ 'bool' => true,
+ 'null' => null,
+ 'string' => 'Foo',
+ 'int' => 0,
+ 'float' => 0.5,
+ 'nested' => array('with object' => new DummyTest),
+ 'object' => new \DateTime,
+ 'resource' => fopen('php://memory', 'r'),
+ 'closed' => $closed,
+ );
+
+ $this->getLogger()->warning('Crazy context data', $context);
+
+ $expected = array('warning Crazy context data');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+ {
+ $logger = $this->getLogger();
+ $logger->warning('Random message', array('exception' => 'oops'));
+ $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+
+ $expected = array(
+ 'warning Random message',
+ 'critical Uncaught Exception!'
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+}
+
+class DummyTest
+{
+ public function __toString()
+ {
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/Test/TestLogger.php b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/Test/TestLogger.php
new file mode 100755
index 0000000..1be3230
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/Psr/Log/Test/TestLogger.php
@@ -0,0 +1,147 @@
+ $level,
+ 'message' => $message,
+ 'context' => $context,
+ ];
+
+ $this->recordsByLevel[$record['level']][] = $record;
+ $this->records[] = $record;
+ }
+
+ public function hasRecords($level)
+ {
+ return isset($this->recordsByLevel[$level]);
+ }
+
+ public function hasRecord($record, $level)
+ {
+ if (is_string($record)) {
+ $record = ['message' => $record];
+ }
+ return $this->hasRecordThatPasses(function ($rec) use ($record) {
+ if ($rec['message'] !== $record['message']) {
+ return false;
+ }
+ if (isset($record['context']) && $rec['context'] !== $record['context']) {
+ return false;
+ }
+ return true;
+ }, $level);
+ }
+
+ public function hasRecordThatContains($message, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($message) {
+ return strpos($rec['message'], $message) !== false;
+ }, $level);
+ }
+
+ public function hasRecordThatMatches($regex, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($regex) {
+ return preg_match($regex, $rec['message']) > 0;
+ }, $level);
+ }
+
+ public function hasRecordThatPasses(callable $predicate, $level)
+ {
+ if (!isset($this->recordsByLevel[$level])) {
+ return false;
+ }
+ foreach ($this->recordsByLevel[$level] as $i => $rec) {
+ if (call_user_func($predicate, $rec, $i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function __call($method, $args)
+ {
+ if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
+ $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
+ $level = strtolower($matches[2]);
+ if (method_exists($this, $genericMethod)) {
+ $args[] = $level;
+ return call_user_func_array([$this, $genericMethod], $args);
+ }
+ }
+ throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
+ }
+
+ public function reset()
+ {
+ $this->records = [];
+ $this->recordsByLevel = [];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/psr/log/README.md b/src/mpg25-instagram-api/vendor/psr/log/README.md
new file mode 100755
index 0000000..5571a25
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/README.md
@@ -0,0 +1,52 @@
+PSR Log
+=======
+
+This repository holds all interfaces/classes/traits related to
+[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
+
+Note that this is not a logger of its own. It is merely an interface that
+describes a logger. See the specification for more details.
+
+Installation
+------------
+
+```bash
+composer require psr/log
+```
+
+Usage
+-----
+
+If you need a logger, you can use the interface like this:
+
+```php
+logger = $logger;
+ }
+
+ public function doSomething()
+ {
+ if ($this->logger) {
+ $this->logger->info('Doing work');
+ }
+
+ // do something useful
+ }
+}
+```
+
+You can then pick one of the implementations of the interface to get a logger.
+
+If you want to implement the interface, you can require this package and
+implement `Psr\Log\LoggerInterface` in your code. Please read the
+[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+for details.
diff --git a/src/mpg25-instagram-api/vendor/psr/log/composer.json b/src/mpg25-instagram-api/vendor/psr/log/composer.json
new file mode 100755
index 0000000..3f6d4ee
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/psr/log/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/log",
+ "description": "Common interface for logging libraries",
+ "keywords": ["psr", "psr-3", "log"],
+ "homepage": "https://github.com/php-fig/log",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/LICENSE b/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/LICENSE
new file mode 100755
index 0000000..be5540c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Ralph Khattar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/README.md b/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/README.md
new file mode 100755
index 0000000..9430d76
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/README.md
@@ -0,0 +1,27 @@
+getallheaders
+=============
+
+PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3.
+
+[](https://travis-ci.org/ralouphie/getallheaders)
+[](https://coveralls.io/r/ralouphie/getallheaders?branch=master)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+
+
+This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php).
+
+## Install
+
+For PHP version **`>= 5.6`**:
+
+```
+composer require ralouphie/getallheaders
+```
+
+For PHP version **`< 5.6`**:
+
+```
+composer require ralouphie/getallheaders "^2"
+```
diff --git a/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/composer.json b/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/composer.json
new file mode 100755
index 0000000..de8ce62
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "ralouphie/getallheaders",
+ "description": "A polyfill for getallheaders.",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5 || ^6.5",
+ "php-coveralls/php-coveralls": "^2.1"
+ },
+ "autoload": {
+ "files": ["src/getallheaders.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "getallheaders\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/src/getallheaders.php b/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/src/getallheaders.php
new file mode 100755
index 0000000..c7285a5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ralouphie/getallheaders/src/getallheaders.php
@@ -0,0 +1,46 @@
+ 'Content-Type',
+ 'CONTENT_LENGTH' => 'Content-Length',
+ 'CONTENT_MD5' => 'Content-Md5',
+ );
+
+ foreach ($_SERVER as $key => $value) {
+ if (substr($key, 0, 5) === 'HTTP_') {
+ $key = substr($key, 5);
+ if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) {
+ $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
+ $headers[$key] = $value;
+ }
+ } elseif (isset($copy_server[$key])) {
+ $headers[$copy_server[$key]] = $value;
+ }
+ }
+
+ if (!isset($headers['Authorization'])) {
+ if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
+ $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
+ } elseif (isset($_SERVER['PHP_AUTH_USER'])) {
+ $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
+ $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);
+ } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
+ $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
+ }
+ }
+
+ return $headers;
+ }
+
+}
diff --git a/src/mpg25-instagram-api/vendor/react/cache/.gitignore b/src/mpg25-instagram-api/vendor/react/cache/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/src/mpg25-instagram-api/vendor/react/cache/.travis.yml b/src/mpg25-instagram-api/vendor/react/cache/.travis.yml
new file mode 100755
index 0000000..402a996
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/.travis.yml
@@ -0,0 +1,32 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+# - hhvm # requires legacy phpunit & ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - php: hhvm
+ install: composer require phpunit/phpunit:^5 --dev --no-interaction
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/src/mpg25-instagram-api/vendor/react/cache/CHANGELOG.md b/src/mpg25-instagram-api/vendor/react/cache/CHANGELOG.md
new file mode 100755
index 0000000..99ecd1c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/CHANGELOG.md
@@ -0,0 +1,74 @@
+# Changelog
+
+## 1.0.0 (2019-07-11)
+
+* First stable LTS release, now following [SemVer](https://semver.org/).
+ We'd like to emphasize that this component is production ready and battle-tested.
+ We plan to support all long-term support (LTS) releases for at least 24 months,
+ so you have a rock-solid foundation to build on top of.
+
+> Contains no other changes, so it's actually fully compatible with the v0.6.0 release.
+
+## 0.6.0 (2019-07-04)
+
+* Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()`
+ supporting multiple cache items (inspired by PSR-16).
+ (#32 by @krlv and #37 by @clue)
+
+* Documentation for TTL precision with millisecond accuracy or below and
+ use high-resolution timer for cache TTL on PHP 7.3+.
+ (#35 and #38 by @clue)
+
+* Improve API documentation and allow legacy HHVM to fail in Travis CI config.
+ (#34 and #36 by @clue)
+
+* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
+ (#31 by @WyriHaximus)
+
+## 0.5.0 (2018-06-25)
+
+* Improve documentation by describing what is expected of a class implementing `CacheInterface`.
+ (#21, #22, #23, #27 by @WyriHaximus)
+
+* Implemented (optional) Least Recently Used (LRU) cache algorithm for `ArrayCache`.
+ (#26 by @clue)
+
+* Added support for cache expiration (TTL).
+ (#29 by @clue and @WyriHaximus)
+
+* Renamed `remove` to `delete` making it more in line with `PSR-16`.
+ (#30 by @clue)
+
+## 0.4.2 (2017-12-20)
+
+* Improve documentation with usage and installation instructions
+ (#10 by @clue)
+
+* Improve test suite by adding PHPUnit to `require-dev` and
+ add forward compatibility with PHPUnit 5 and PHPUnit 6 and
+ sanitize Composer autoload paths
+ (#14 by @shaunbramley and #12 and #18 by @clue)
+
+## 0.4.1 (2016-02-25)
+
+* Repository maintenance, split off from main repo, improve test suite and documentation
+* First class support for PHP7 and HHVM (#9 by @clue)
+* Adjust compatibility to 5.3 (#7 by @clue)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.2 (2013-05-10)
+
+* Version bump
+
+## 0.3.0 (2013-04-14)
+
+* Version bump
+
+## 0.2.6 (2012-12-26)
+
+* Feature: New cache component, used by DNS
diff --git a/src/mpg25-instagram-api/vendor/react/cache/LICENSE b/src/mpg25-instagram-api/vendor/react/cache/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/react/cache/README.md b/src/mpg25-instagram-api/vendor/react/cache/README.md
new file mode 100755
index 0000000..74cef54
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/README.md
@@ -0,0 +1,366 @@
+# Cache
+
+[](https://travis-ci.org/reactphp/cache)
+
+Async, [Promise](https://github.com/reactphp/promise)-based cache interface
+for [ReactPHP](https://reactphp.org/).
+
+The cache component provides a
+[Promise](https://github.com/reactphp/promise)-based
+[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache)
+implementation of that.
+This allows consumers to type hint against the interface and third parties to
+provide alternate implementations.
+This project is heavily inspired by
+[PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/),
+but uses an interface more suited for async, non-blocking applications.
+
+**Table of Contents**
+
+* [Usage](#usage)
+ * [CacheInterface](#cacheinterface)
+ * [get()](#get)
+ * [set()](#set)
+ * [delete()](#delete)
+ * [getMultiple()](#getmultiple)
+ * [setMultiple()](#setmultiple)
+ * [deleteMultiple()](#deletemultiple)
+ * [clear()](#clear)
+ * [has()](#has)
+ * [ArrayCache](#arraycache)
+* [Common usage](#common-usage)
+ * [Fallback get](#fallback-get)
+ * [Fallback-get-and-set](#fallback-get-and-set)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Usage
+
+### CacheInterface
+
+The `CacheInterface` describes the main interface of this component.
+This allows consumers to type hint against the interface and third parties to
+provide alternate implementations.
+
+#### get()
+
+The `get(string $key, mixed $default = null): PromiseInterface` method can be used to
+retrieve an item from the cache.
+
+This method will resolve with the cached value on success or with the
+given `$default` value when no item can be found or when an error occurs.
+Similarly, an expired cache item (once the time-to-live is expired) is
+considered a cache miss.
+
+```php
+$cache
+ ->get('foo')
+ ->then('var_dump');
+```
+
+This example fetches the value of the key `foo` and passes it to the
+`var_dump` function. You can use any of the composition provided by
+[promises](https://github.com/reactphp/promise).
+
+#### set()
+
+The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface` method can be used to
+store an item in the cache.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. If the cache implementation has to go over the network to store
+it, it may take a while.
+
+The optional `$ttl` parameter sets the maximum time-to-live in seconds
+for this cache item. If this parameter is omitted (or `null`), the item
+will stay in the cache for as long as the underlying implementation
+supports. Trying to access an expired cache item results in a cache miss,
+see also [`get()`](#get).
+
+```php
+$cache->set('foo', 'bar', 60);
+```
+
+This example eventually sets the value of the key `foo` to `bar`. If it
+already exists, it is overridden.
+
+This interface does not enforce any particular TTL resolution, so special
+care may have to be taken if you rely on very high precision with
+millisecond accuracy or below. Cache implementations SHOULD work on a
+best effort basis and SHOULD provide at least second accuracy unless
+otherwise noted. Many existing cache implementations are known to provide
+microsecond or millisecond accuracy, but it's generally not recommended
+to rely on this high precision.
+
+This interface suggests that cache implementations SHOULD use a monotonic
+time source if available. Given that a monotonic time source is only
+available as of PHP 7.3 by default, cache implementations MAY fall back
+to using wall-clock time.
+While this does not affect many common use cases, this is an important
+distinction for programs that rely on a high time precision or on systems
+that are subject to discontinuous time adjustments (time jumps).
+This means that if you store a cache item with a TTL of 30s and then
+adjust your system time forward by 20s, the cache item SHOULD still
+expire in 30s.
+
+#### delete()
+
+The `delete(string $key): PromiseInterface` method can be used to
+delete an item from the cache.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. When no item for `$key` is found in the cache, it also resolves
+to `true`. If the cache implementation has to go over the network to
+delete it, it may take a while.
+
+```php
+$cache->delete('foo');
+```
+
+This example eventually deletes the key `foo` from the cache. As with
+`set()`, this may not happen instantly and a promise is returned to
+provide guarantees whether or not the item has been removed from cache.
+
+#### getMultiple()
+
+The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface` method can be used to
+retrieve multiple cache items by their unique keys.
+
+This method will resolve with an array of cached values on success or with the
+given `$default` value when an item can not be found or when an error occurs.
+Similarly, an expired cache item (once the time-to-live is expired) is
+considered a cache miss.
+
+```php
+$cache->getMultiple(array('name', 'age'))->then(function (array $values) {
+ $name = $values['name'] ?? 'User';
+ $age = $values['age'] ?? 'n/a';
+
+ echo $name . ' is ' . $age . PHP_EOL;
+});
+```
+
+This example fetches the cache items for the `name` and `age` keys and
+prints some example output. You can use any of the composition provided
+by [promises](https://github.com/reactphp/promise).
+
+#### setMultiple()
+
+The `setMultiple(array $values, ?float $ttl = null): PromiseInterface` method can be used to
+persist a set of key => value pairs in the cache, with an optional TTL.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. If the cache implementation has to go over the network to store
+it, it may take a while.
+
+The optional `$ttl` parameter sets the maximum time-to-live in seconds
+for these cache items. If this parameter is omitted (or `null`), these items
+will stay in the cache for as long as the underlying implementation
+supports. Trying to access an expired cache items results in a cache miss,
+see also [`getMultiple()`](#getmultiple).
+
+```php
+$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
+```
+
+This example eventually sets the list of values - the key `foo` to `1` value
+and the key `bar` to `2`. If some of the keys already exist, they are overridden.
+
+#### deleteMultiple()
+
+The `setMultiple(string[] $keys): PromiseInterface` method can be used to
+delete multiple cache items in a single operation.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. When no items for `$keys` are found in the cache, it also resolves
+to `true`. If the cache implementation has to go over the network to
+delete it, it may take a while.
+
+```php
+$cache->deleteMultiple(array('foo', 'bar, 'baz'));
+```
+
+This example eventually deletes keys `foo`, `bar` and `baz` from the cache.
+As with `setMultiple()`, this may not happen instantly and a promise is returned to
+provide guarantees whether or not the item has been removed from cache.
+
+#### clear()
+
+The `clear(): PromiseInterface` method can be used to
+wipe clean the entire cache.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. If the cache implementation has to go over the network to
+delete it, it may take a while.
+
+```php
+$cache->clear();
+```
+
+This example eventually deletes all keys from the cache. As with `deleteMultiple()`,
+this may not happen instantly and a promise is returned to provide guarantees
+whether or not all the items have been removed from cache.
+
+#### has()
+
+The `has(string $key): PromiseInterface` method can be used to
+determine whether an item is present in the cache.
+
+This method will resolve with `true` on success or `false` when no item can be found
+or when an error occurs. Similarly, an expired cache item (once the time-to-live
+is expired) is considered a cache miss.
+
+```php
+$cache
+ ->has('foo')
+ ->then('var_dump');
+```
+
+This example checks if the value of the key `foo` is set in the cache and passes
+the result to the `var_dump` function. You can use any of the composition provided by
+[promises](https://github.com/reactphp/promise).
+
+NOTE: It is recommended that has() is only to be used for cache warming type purposes
+and not to be used within your live applications operations for get/set, as this method
+is subject to a race condition where your has() will return true and immediately after,
+another script can remove it making the state of your app out of date.
+
+### ArrayCache
+
+The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
+
+```php
+$cache = new ArrayCache();
+
+$cache->set('foo', 'bar');
+```
+
+Its constructor accepts an optional `?int $limit` parameter to limit the
+maximum number of entries to store in the LRU cache. If you add more
+entries to this instance, it will automatically take care of removing
+the one that was least recently used (LRU).
+
+For example, this snippet will overwrite the first value and only store
+the last two entries:
+
+```php
+$cache = new ArrayCache(2);
+
+$cache->set('foo', '1');
+$cache->set('bar', '2');
+$cache->set('baz', '3');
+```
+
+This cache implementation is known to rely on wall-clock time to schedule
+future cache expiration times when using any version before PHP 7.3,
+because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
+While this does not affect many common use cases, this is an important
+distinction for programs that rely on a high time precision or on systems
+that are subject to discontinuous time adjustments (time jumps).
+This means that if you store a cache item with a TTL of 30s on PHP < 7.3
+and then adjust your system time forward by 20s, the cache item may
+expire in 10s. See also [`set()`](#set) for more details.
+
+## Common usage
+
+### Fallback get
+
+A common use case of caches is to attempt fetching a cached value and as a
+fallback retrieve it from the original data source if not found. Here is an
+example of that:
+
+```php
+$cache
+ ->get('foo')
+ ->then(function ($result) {
+ if ($result === null) {
+ return getFooFromDb();
+ }
+
+ return $result;
+ })
+ ->then('var_dump');
+```
+
+First an attempt is made to retrieve the value of `foo`. A callback function is
+registered that will call `getFooFromDb` when the resulting value is null.
+`getFooFromDb` is a function (can be any PHP callable) that will be called if the
+key does not exist in the cache.
+
+`getFooFromDb` can handle the missing key by returning a promise for the
+actual value from the database (or any other data source). As a result, this
+chain will correctly fall back, and provide the value in both cases.
+
+### Fallback get and set
+
+To expand on the fallback get example, often you want to set the value on the
+cache after fetching it from the data source.
+
+```php
+$cache
+ ->get('foo')
+ ->then(function ($result) {
+ if ($result === null) {
+ return $this->getAndCacheFooFromDb();
+ }
+
+ return $result;
+ })
+ ->then('var_dump');
+
+public function getAndCacheFooFromDb()
+{
+ return $this->db
+ ->get('foo')
+ ->then(array($this, 'cacheFooFromDb'));
+}
+
+public function cacheFooFromDb($foo)
+{
+ $this->cache->set('foo', $foo);
+
+ return $foo;
+}
+```
+
+By using chaining you can easily conditionally cache the value if it is
+fetched from the database.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/cache:^1.0
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/src/mpg25-instagram-api/vendor/react/cache/composer.json b/src/mpg25-instagram-api/vendor/react/cache/composer.json
new file mode 100755
index 0000000..51573b6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/composer.json
@@ -0,0 +1,19 @@
+{
+ "name": "react/cache",
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": ["cache", "caching", "promise", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "~2.0|~1.1"
+ },
+ "autoload": {
+ "psr-4": { "React\\Cache\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Cache\\": "tests/" }
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/cache/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/react/cache/phpunit.xml.dist
new file mode 100755
index 0000000..d02182f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/phpunit.xml.dist
@@ -0,0 +1,20 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/react/cache/src/ArrayCache.php b/src/mpg25-instagram-api/vendor/react/cache/src/ArrayCache.php
new file mode 100755
index 0000000..81f25ef
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/src/ArrayCache.php
@@ -0,0 +1,181 @@
+set('foo', 'bar');
+ * ```
+ *
+ * Its constructor accepts an optional `?int $limit` parameter to limit the
+ * maximum number of entries to store in the LRU cache. If you add more
+ * entries to this instance, it will automatically take care of removing
+ * the one that was least recently used (LRU).
+ *
+ * For example, this snippet will overwrite the first value and only store
+ * the last two entries:
+ *
+ * ```php
+ * $cache = new ArrayCache(2);
+ *
+ * $cache->set('foo', '1');
+ * $cache->set('bar', '2');
+ * $cache->set('baz', '3');
+ * ```
+ *
+ * This cache implementation is known to rely on wall-clock time to schedule
+ * future cache expiration times when using any version before PHP 7.3,
+ * because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you store a cache item with a TTL of 30s on PHP < 7.3
+ * and then adjust your system time forward by 20s, the cache item may
+ * expire in 10s. See also [`set()`](#set) for more details.
+ *
+ * @param int|null $limit maximum number of entries to store in the LRU cache
+ */
+ public function __construct($limit = null)
+ {
+ $this->limit = $limit;
+
+ // prefer high-resolution timer, available as of PHP 7.3+
+ $this->supportsHighResolution = \function_exists('hrtime');
+ }
+
+ public function get($key, $default = null)
+ {
+ // delete key if it is already expired => below will detect this as a cache miss
+ if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ if (!\array_key_exists($key, $this->data)) {
+ return Promise\resolve($default);
+ }
+
+ // remove and append to end of array to keep track of LRU info
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ return Promise\resolve($value);
+ }
+
+ public function set($key, $value, $ttl = null)
+ {
+ // unset before setting to ensure this entry will be added to end of array (LRU info)
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ // sort expiration times if TTL is given (first will expire first)
+ unset($this->expires[$key]);
+ if ($ttl !== null) {
+ $this->expires[$key] = $this->now() + $ttl;
+ \asort($this->expires);
+ }
+
+ // ensure size limit is not exceeded or remove first entry from array
+ if ($this->limit !== null && \count($this->data) > $this->limit) {
+ // first try to check if there's any expired entry
+ // expiration times are sorted, so we can simply look at the first one
+ \reset($this->expires);
+ $key = \key($this->expires);
+
+ // check to see if the first in the list of expiring keys is already expired
+ // if the first key is not expired, we have to overwrite by using LRU info
+ if ($key === null || $this->now() - $this->expires[$key] < 0) {
+ \reset($this->data);
+ $key = \key($this->data);
+ }
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function delete($key)
+ {
+ unset($this->data[$key], $this->expires[$key]);
+
+ return Promise\resolve(true);
+ }
+
+ public function getMultiple(array $keys, $default = null)
+ {
+ $values = array();
+
+ foreach ($keys as $key) {
+ $values[$key] = $this->get($key, $default);
+ }
+
+ return Promise\all($values);
+ }
+
+ public function setMultiple(array $values, $ttl = null)
+ {
+ foreach ($values as $key => $value) {
+ $this->set($key, $value, $ttl);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function deleteMultiple(array $keys)
+ {
+ foreach ($keys as $key) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function clear()
+ {
+ $this->data = array();
+ $this->expires = array();
+
+ return Promise\resolve(true);
+ }
+
+ public function has($key)
+ {
+ // delete key if it is already expired
+ if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ if (!\array_key_exists($key, $this->data)) {
+ return Promise\resolve(false);
+ }
+
+ // remove and append to end of array to keep track of LRU info
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ return Promise\resolve(true);
+ }
+
+ /**
+ * @return float
+ */
+ private function now()
+ {
+ return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/cache/src/CacheInterface.php b/src/mpg25-instagram-api/vendor/react/cache/src/CacheInterface.php
new file mode 100755
index 0000000..3d52501
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/src/CacheInterface.php
@@ -0,0 +1,194 @@
+get('foo')
+ * ->then('var_dump');
+ * ```
+ *
+ * This example fetches the value of the key `foo` and passes it to the
+ * `var_dump` function. You can use any of the composition provided by
+ * [promises](https://github.com/reactphp/promise).
+ *
+ * @param string $key
+ * @param mixed $default Default value to return for cache miss or null if not given.
+ * @return PromiseInterface
+ */
+ public function get($key, $default = null);
+
+ /**
+ * Stores an item in the cache.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. If the cache implementation has to go over the network to store
+ * it, it may take a while.
+ *
+ * The optional `$ttl` parameter sets the maximum time-to-live in seconds
+ * for this cache item. If this parameter is omitted (or `null`), the item
+ * will stay in the cache for as long as the underlying implementation
+ * supports. Trying to access an expired cache item results in a cache miss,
+ * see also [`get()`](#get).
+ *
+ * ```php
+ * $cache->set('foo', 'bar', 60);
+ * ```
+ *
+ * This example eventually sets the value of the key `foo` to `bar`. If it
+ * already exists, it is overridden.
+ *
+ * This interface does not enforce any particular TTL resolution, so special
+ * care may have to be taken if you rely on very high precision with
+ * millisecond accuracy or below. Cache implementations SHOULD work on a
+ * best effort basis and SHOULD provide at least second accuracy unless
+ * otherwise noted. Many existing cache implementations are known to provide
+ * microsecond or millisecond accuracy, but it's generally not recommended
+ * to rely on this high precision.
+ *
+ * This interface suggests that cache implementations SHOULD use a monotonic
+ * time source if available. Given that a monotonic time source is only
+ * available as of PHP 7.3 by default, cache implementations MAY fall back
+ * to using wall-clock time.
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you store a cache item with a TTL of 30s and then
+ * adjust your system time forward by 20s, the cache item SHOULD still
+ * expire in 30s.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @param ?float $ttl
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function set($key, $value, $ttl = null);
+
+ /**
+ * Deletes an item from the cache.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. When no item for `$key` is found in the cache, it also resolves
+ * to `true`. If the cache implementation has to go over the network to
+ * delete it, it may take a while.
+ *
+ * ```php
+ * $cache->delete('foo');
+ * ```
+ *
+ * This example eventually deletes the key `foo` from the cache. As with
+ * `set()`, this may not happen instantly and a promise is returned to
+ * provide guarantees whether or not the item has been removed from cache.
+ *
+ * @param string $key
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function delete($key);
+
+ /**
+ * Retrieves multiple cache items by their unique keys.
+ *
+ * This method will resolve with an array of cached values on success or with the
+ * given `$default` value when an item can not be found or when an error occurs.
+ * Similarly, an expired cache item (once the time-to-live is expired) is
+ * considered a cache miss.
+ *
+ * ```php
+ * $cache->getMultiple(array('name', 'age'))->then(function (array $values) {
+ * $name = $values['name'] ?? 'User';
+ * $age = $values['age'] ?? 'n/a';
+ *
+ * echo $name . ' is ' . $age . PHP_EOL;
+ * });
+ * ```
+ *
+ * This example fetches the cache items for the `name` and `age` keys and
+ * prints some example output. You can use any of the composition provided
+ * by [promises](https://github.com/reactphp/promise).
+ *
+ * @param string[] $keys A list of keys that can obtained in a single operation.
+ * @param mixed $default Default value to return for keys that do not exist.
+ * @return PromiseInterface Returns a promise which resolves to an `array` of cached values
+ */
+ public function getMultiple(array $keys, $default = null);
+
+ /**
+ * Persists a set of key => value pairs in the cache, with an optional TTL.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. If the cache implementation has to go over the network to store
+ * it, it may take a while.
+ *
+ * The optional `$ttl` parameter sets the maximum time-to-live in seconds
+ * for these cache items. If this parameter is omitted (or `null`), these items
+ * will stay in the cache for as long as the underlying implementation
+ * supports. Trying to access an expired cache items results in a cache miss,
+ * see also [`get()`](#get).
+ *
+ * ```php
+ * $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
+ * ```
+ *
+ * This example eventually sets the list of values - the key `foo` to 1 value
+ * and the key `bar` to 2. If some of the keys already exist, they are overridden.
+ *
+ * @param array $values A list of key => value pairs for a multiple-set operation.
+ * @param ?float $ttl Optional. The TTL value of this item.
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function setMultiple(array $values, $ttl = null);
+
+ /**
+ * Deletes multiple cache items in a single operation.
+ *
+ * @param string[] $keys A list of string-based keys to be deleted.
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function deleteMultiple(array $keys);
+
+ /**
+ * Wipes clean the entire cache.
+ *
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function clear();
+
+ /**
+ * Determines whether an item is present in the cache.
+ *
+ * This method will resolve with `true` on success or `false` when no item can be found
+ * or when an error occurs. Similarly, an expired cache item (once the time-to-live
+ * is expired) is considered a cache miss.
+ *
+ * ```php
+ * $cache
+ * ->has('foo')
+ * ->then('var_dump');
+ * ```
+ *
+ * This example checks if the value of the key `foo` is set in the cache and passes
+ * the result to the `var_dump` function. You can use any of the composition provided by
+ * [promises](https://github.com/reactphp/promise).
+ *
+ * NOTE: It is recommended that has() is only to be used for cache warming type purposes
+ * and not to be used within your live applications operations for get/set, as this method
+ * is subject to a race condition where your has() will return true and immediately after,
+ * another script can remove it making the state of your app out of date.
+ *
+ * @param string $key The cache item key.
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function has($key);
+}
diff --git a/src/mpg25-instagram-api/vendor/react/cache/tests/ArrayCacheTest.php b/src/mpg25-instagram-api/vendor/react/cache/tests/ArrayCacheTest.php
new file mode 100755
index 0000000..3b5bd8c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/tests/ArrayCacheTest.php
@@ -0,0 +1,322 @@
+cache = new ArrayCache();
+ }
+
+ /** @test */
+ public function getShouldResolvePromiseWithNullForNonExistentKey()
+ {
+ $success = $this->createCallableMock();
+ $success
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(null);
+
+ $this->cache
+ ->get('foo')
+ ->then(
+ $success,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function setShouldSetKey()
+ {
+ $setPromise = $this->cache
+ ->set('foo', 'bar');
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $setPromise->then($mock);
+
+ $success = $this->createCallableMock();
+ $success
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with('bar');
+
+ $this->cache
+ ->get('foo')
+ ->then($success);
+ }
+
+ /** @test */
+ public function deleteShouldDeleteKey()
+ {
+ $this->cache
+ ->set('foo', 'bar');
+
+ $deletePromise = $this->cache
+ ->delete('foo');
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $deletePromise->then($mock);
+
+ $this->cache
+ ->get('foo')
+ ->then(
+ $this->expectCallableOnce(),
+ $this->expectCallableNever()
+ );
+ }
+
+ public function testGetWillResolveWithNullForCacheMiss()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testGetWillResolveWithDefaultValueForCacheMiss()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith('bar'));
+ }
+
+ public function testGetWillResolveWithExplicitNullValueForCacheHit()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->set('foo', null);
+ $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testLimitSizeToZeroDoesNotStoreAnyData()
+ {
+ $this->cache = new ArrayCache(0);
+
+ $this->cache->set('foo', 'bar');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testLimitSizeToOneWillOnlyReturnLastWrite()
+ {
+ $this->cache = new ArrayCache(1);
+
+ $this->cache->set('foo', '1');
+ $this->cache->set('bar', '2');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith('2'));
+ }
+
+ public function testOverwriteWithLimitedSizeWillUpdateLRUInfo()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1');
+ $this->cache->set('bar', '2');
+ $this->cache->set('foo', '3');
+ $this->cache->set('baz', '4');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('3'));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith(null));
+ $this->cache->get('baz')->then($this->expectCallableOnceWith('4'));
+ }
+
+ public function testGetWithLimitedSizeWillUpdateLRUInfo()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1');
+ $this->cache->set('bar', '2');
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ $this->cache->set('baz', '3');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith(null));
+ $this->cache->get('baz')->then($this->expectCallableOnceWith('3'));
+ }
+
+ public function testGetWillResolveWithValueIfItemIsNotExpired()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->set('foo', '1', 10);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ }
+
+ public function testGetWillResolveWithDefaultIfItemIsExpired()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->set('foo', '1', 0);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testSetWillOverwritOldestItemIfNoEntryIsExpired()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1', 10);
+ $this->cache->set('bar', '2', 20);
+ $this->cache->set('baz', '3', 30);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1', 10);
+ $this->cache->set('bar', '2', 0);
+ $this->cache->set('baz', '3', 30);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testGetMultiple()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', '1');
+
+ $this->cache
+ ->getMultiple(array('foo', 'bar'), 'baz')
+ ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => 'baz')));
+ }
+
+ public function testSetMultiple()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->setMultiple(array('foo' => '1', 'bar' => '2'), 10);
+
+ $this->cache
+ ->getMultiple(array('foo', 'bar'))
+ ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => '2')));
+ }
+
+ public function testDeleteMultiple()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3));
+
+ $this->cache
+ ->deleteMultiple(array('foo', 'baz'))
+ ->then($this->expectCallableOnceWith(true));
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+
+ $this->cache
+ ->has('bar')
+ ->then($this->expectCallableOnceWith(true));
+
+ $this->cache
+ ->has('baz')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function testClearShouldClearCache()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3));
+
+ $this->cache->clear();
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+
+ $this->cache
+ ->has('bar')
+ ->then($this->expectCallableOnceWith(false));
+
+ $this->cache
+ ->has('baz')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function hasShouldResolvePromiseForExistingKey()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', 'bar');
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(true));
+ }
+
+ public function hasShouldResolvePromiseForNonExistentKey()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', 'bar');
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function testHasWillResolveIfItemIsNotExpired()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', '1', 10);
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(true));
+ }
+
+ public function testHasWillResolveIfItemIsExpired()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', '1', 0);
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function testHasWillResolveForExplicitNullValue()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', null);
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(true));
+ }
+
+ public function testHasWithLimitedSizeWillUpdateLRUInfo()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', 1);
+ $this->cache->set('bar', 2);
+ $this->cache->has('foo')->then($this->expectCallableOnceWith(true));
+ $this->cache->set('baz', 3);
+
+ $this->cache->has('foo')->then($this->expectCallableOnceWith(1));
+ $this->cache->has('bar')->then($this->expectCallableOnceWith(false));
+ $this->cache->has('baz')->then($this->expectCallableOnceWith(3));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/cache/tests/CallableStub.php b/src/mpg25-instagram-api/vendor/react/cache/tests/CallableStub.php
new file mode 100755
index 0000000..2f547cd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/cache/tests/CallableStub.php
@@ -0,0 +1,10 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($param)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($param);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Cache\CallableStub')->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/.gitignore b/src/mpg25-instagram-api/vendor/react/dns/.gitignore
new file mode 100755
index 0000000..19982ea
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/react/dns/.travis.yml b/src/mpg25-instagram-api/vendor/react/dns/.travis.yml
new file mode 100755
index 0000000..459e852
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/.travis.yml
@@ -0,0 +1,32 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+# - hhvm # requires legacy phpunit & ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - php: hhvm
+ install: composer require phpunit/phpunit:^5 --dev --no-interaction
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/src/mpg25-instagram-api/vendor/react/dns/CHANGELOG.md b/src/mpg25-instagram-api/vendor/react/dns/CHANGELOG.md
new file mode 100755
index 0000000..314f023
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/CHANGELOG.md
@@ -0,0 +1,261 @@
+# Changelog
+
+## 0.4.19 (2019-07-10)
+
+* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6.
+ (#133 by @clue)
+
+## 0.4.18 (2019-09-07)
+
+* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`,
+ respect TTL from response records when caching and do not cache truncated responses.
+ (#129 by @clue)
+
+* Feature: Limit cache size to 256 last responses by default.
+ (#127 by @clue)
+
+* Feature: Cooperatively resolve hosts to avoid running same query concurrently.
+ (#125 by @clue)
+
+## 0.4.17 (2019-04-01)
+
+* Feature: Support parsing `authority` and `additional` records from DNS response.
+ (#123 by @clue)
+
+* Feature: Support dumping records as part of outgoing binary DNS message.
+ (#124 by @clue)
+
+* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0
+ (#121 by @clue)
+
+* Improve test suite to add forward compatibility with PHPUnit 7,
+ test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM.
+ (#122 by @clue)
+
+## 0.4.16 (2018-11-11)
+
+* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references.
+ (#118 by @clue)
+
+* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages,
+ malformed record data or malformed compressed domain name labels.
+ (#115 and #117 by @clue)
+
+* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset.
+ (#116 by @clue)
+
+* Fix: Fix caching advanced MX/SRV/TXT/SOA structures.
+ (#112 by @clue)
+
+## 0.4.15 (2018-07-02)
+
+* Feature: Add `resolveAll()` method to support custom query types in `Resolver`.
+ (#110 by @clue and @WyriHaximus)
+
+ ```php
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ });
+ ```
+
+* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records.
+ (#104, #105, #106, #107 and #108 by @clue)
+
+* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data.
+ (#104 by @clue)
+
+* Feature: Improve error messages for failed queries and improve documentation.
+ (#109 by @clue)
+
+* Feature: Add reverse DNS lookup example.
+ (#111 by @clue)
+
+## 0.4.14 (2018-06-26)
+
+* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages
+ to avoid cache poisoning attacks and deprecate legacy `Executor`.
+ (#101 and #103 by @clue)
+
+* Feature: Forward compatibility with Cache 0.5
+ (#102 by @clue)
+
+* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API.
+ (#99 by @clue)
+
+## 0.4.13 (2018-02-27)
+
+* Add `Config::loadSystemConfigBlocking()` to load default system config
+ and support parsing DNS config on all supported platforms
+ (`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows)
+ (#92, #93, #94 and #95 by @clue)
+
+ ```php
+ $config = Config::loadSystemConfigBlocking();
+ $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+ ```
+
+* Remove unneeded cyclic dependency on react/socket
+ (#96 by @clue)
+
+## 0.4.12 (2018-01-14)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6,
+ test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases,
+ add test group to skip integration tests relying on internet connection
+ and add minor documentation improvements.
+ (#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor)
+
+## 0.4.11 (2017-08-25)
+
+* Feature: Support resolving from default hosts file
+ (#75, #76 and #77 by @clue)
+
+ This means that resolving hosts such as `localhost` will now work as
+ expected across all platforms with no changes required:
+
+ ```php
+ $resolver->resolve('localhost')->then(function ($ip) {
+ echo 'IP: ' . $ip;
+ });
+ ```
+
+ The new `HostsExecutor` exists for advanced usage and is otherwise used
+ internally for this feature.
+
+## 0.4.10 (2017-08-10)
+
+* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and
+ lock minimum dependencies and work around circular dependency for tests
+ (#70 and #71 by @clue)
+
+* Fix: Work around DNS timeout issues for Windows users
+ (#74 by @clue)
+
+* Documentation and examples for advanced usage
+ (#66 by @WyriHaximus)
+
+* Remove broken TCP code, do not retry with invalid TCP query
+ (#73 by @clue)
+
+* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and
+ lock Travis distro so new defaults will not break the build and
+ fix failing tests for PHP 7.1
+ (#68 by @WyriHaximus and #69 and #72 by @clue)
+
+## 0.4.9 (2017-05-01)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#61 by @clue)
+
+## 0.4.8 (2017-04-16)
+
+* Feature: Add support for the AAAA record type to the protocol parser
+ (#58 by @othillo)
+
+* Feature: Add support for the PTR record type to the protocol parser
+ (#59 by @othillo)
+
+## 0.4.7 (2017-03-31)
+
+* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component
+ (#57 by @clue)
+
+## 0.4.6 (2017-03-11)
+
+* Fix: Fix DNS timeout issues for Windows users and add forward compatibility
+ with Stream v0.5 and upcoming v0.6
+ (#53 by @clue)
+
+* Improve test suite by adding PHPUnit to `require-dev`
+ (#54 by @clue)
+
+## 0.4.5 (2017-03-02)
+
+* Fix: Ensure we ignore the case of the answer
+ (#51 by @WyriHaximus)
+
+* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal
+ code re-use for upcoming versions.
+ (#48 and #49 by @clue)
+
+## 0.4.4 (2017-02-13)
+
+* Fix: Fix handling connection and stream errors
+ (#45 by @clue)
+
+* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component
+ (#46 and #47 by @clue)
+
+## 0.4.3 (2016-07-31)
+
+* Feature: Allow for cache adapter injection (#38 by @WyriHaximus)
+
+ ```php
+ $factory = new React\Dns\Resolver\Factory();
+
+ $cache = new MyCustomCacheInstance();
+ $resolver = $factory->createCached('8.8.8.8', $loop, $cache);
+ ```
+
+* Feature: Support Promise cancellation (#35 by @clue)
+
+ ```php
+ $promise = $resolver->resolve('reactphp.org');
+
+ $promise->cancel();
+ ```
+
+## 0.4.2 (2016-02-24)
+
+* Repository maintenance, split off from main repo, improve test suite and documentation
+* First class support for PHP7 and HHVM (#34 by @clue)
+* Adjust compatibility to 5.3 (#30 by @clue)
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* Bug fix: Properly resolve CNAME aliases
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+* Bump React dependencies to v0.4
+
+## 0.3.2 (2013-05-10)
+
+* Feature: Support default port for IPv6 addresses (@clue)
+
+## 0.3.0 (2013-04-14)
+
+* Bump React dependencies to v0.3
+
+## 0.2.6 (2012-12-26)
+
+* Feature: New cache component, used by DNS
+
+## 0.2.5 (2012-11-26)
+
+* Version bump
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Change to promise-based API (@jsor)
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Feature: DNS executor timeout handling (@arnaud-lb)
+* Feature: DNS retry executor (@arnaud-lb)
+
+## 0.2.1 (2012-10-14)
+
+* Minor adjustments to DNS parser
+
+## 0.2.0 (2012-09-10)
+
+* Feature: DNS resolver
diff --git a/src/mpg25-instagram-api/vendor/react/dns/LICENSE b/src/mpg25-instagram-api/vendor/react/dns/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/react/dns/README.md b/src/mpg25-instagram-api/vendor/react/dns/README.md
new file mode 100755
index 0000000..6cdffe9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/README.md
@@ -0,0 +1,344 @@
+# Dns
+
+[](https://travis-ci.org/reactphp/dns)
+
+Async DNS resolver for [ReactPHP](https://reactphp.org/).
+
+The main point of the DNS component is to provide async DNS resolution.
+However, it is really a toolkit for working with DNS messages, and could
+easily be used to create a DNS server.
+
+**Table of contents**
+
+* [Basic usage](#basic-usage)
+* [Caching](#caching)
+ * [Custom cache adapter](#custom-cache-adapter)
+* [Resolver](#resolver)
+ * [resolve()](#resolve)
+ * [resolveAll()](#resolveall)
+* [Advanced usage](#advanced-usage)
+ * [UdpTransportExecutor](#udptransportexecutor)
+ * [HostsFileExecutor](#hostsfileexecutor)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [References](#references)
+
+## Basic usage
+
+The most basic usage is to just create a resolver through the resolver
+factory. All you need to give it is a nameserver, then you can start resolving
+names, baby!
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$config = React\Dns\Config\Config::loadSystemConfigBlocking();
+$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->create($server, $loop);
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+$loop->run();
+```
+
+See also the [first example](examples).
+
+The `Config` class can be used to load the system default config. This is an
+operation that may access the filesystem and block. Ideally, this method should
+thus be executed only once before the loop starts and not repeatedly while it is
+running.
+Note that this class may return an *empty* configuration if the system config
+can not be loaded. As such, you'll likely want to apply a default nameserver
+as above if none can be found.
+
+> Note that the factory loads the hosts file from the filesystem once when
+ creating the resolver instance.
+ Ideally, this method should thus be executed only once before the loop starts
+ and not repeatedly while it is running.
+
+But there's more.
+
+## Caching
+
+You can cache results by configuring the resolver to use a `CachedExecutor`:
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$config = React\Dns\Config\Config::loadSystemConfigBlocking();
+$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->createCached($server, $loop);
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+...
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+$loop->run();
+```
+
+If the first call returns before the second, only one query will be executed.
+The second result will be served from an in memory cache.
+This is particularly useful for long running scripts where the same hostnames
+have to be looked up multiple times.
+
+See also the [third example](examples).
+
+### Custom cache adapter
+
+By default, the above will use an in memory cache.
+
+You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead:
+
+```php
+$cache = new React\Cache\ArrayCache();
+$loop = React\EventLoop\Factory::create();
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->createCached('8.8.8.8', $loop, $cache);
+```
+
+See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
+
+## Resolver
+
+### resolve()
+
+The `resolve(string $domain): PromiseInterface` method can be used to
+resolve the given $domain name to a single IPv4 address (type `A` query).
+
+```php
+$resolver->resolve('reactphp.org')->then(function ($ip) {
+ echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
+});
+```
+
+This is one of the main methods in this package. It sends a DNS query
+for the given $domain name to your DNS server and returns a single IP
+address on success.
+
+If the DNS server sends a DNS response message that contains more than
+one IP address for this query, it will randomly pick one of the IP
+addresses from the response. If you want the full list of IP addresses
+or want to send a different type of query, you should use the
+[`resolveAll()`](#resolveall) method instead.
+
+If the DNS server sends a DNS response message that indicates an error
+code, this method will reject with a `RecordNotFoundException`. Its
+message and code can be used to check for the response code.
+
+If the DNS communication fails and the server does not respond with a
+valid response message, this message will reject with an `Exception`.
+
+Pending DNS queries can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $resolver->resolve('reactphp.org');
+
+$promise->cancel();
+```
+
+### resolveAll()
+
+The `resolveAll(string $host, int $type): PromiseInterface` method can be used to
+resolve all record values for the given $domain name and query $type.
+
+```php
+$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
+ echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+});
+
+$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+});
+```
+
+This is one of the main methods in this package. It sends a DNS query
+for the given $domain name to your DNS server and returns a list with all
+record values on success.
+
+If the DNS server sends a DNS response message that contains one or more
+records for this query, it will return a list with all record values
+from the response. You can use the `Message::TYPE_*` constants to control
+which type of query will be sent. Note that this method always returns a
+list of record values, but each record value type depends on the query
+type. For example, it returns the IPv4 addresses for type `A` queries,
+the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
+`CNAME` and `PTR` queries and structured data for other queries. See also
+the `Record` documentation for more details.
+
+If the DNS server sends a DNS response message that indicates an error
+code, this method will reject with a `RecordNotFoundException`. Its
+message and code can be used to check for the response code.
+
+If the DNS communication fails and the server does not respond with a
+valid response message, this message will reject with an `Exception`.
+
+Pending DNS queries can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
+
+$promise->cancel();
+```
+
+## Advanced Usage
+
+### UdpTransportExecutor
+
+The `UdpTransportExecutor` can be used to
+send DNS queries over a UDP transport.
+
+This is the main class that sends a DNS query to your DNS server and is used
+internally by the `Resolver` for the actual message transport.
+
+For more advanced usages one can utilize this class directly.
+The following example looks up the `IPv6` address for `igor.io`.
+
+```php
+$loop = Factory::create();
+$executor = new UdpTransportExecutor($loop);
+
+$executor->query(
+ '8.8.8.8:53',
+ new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
+)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv6: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
+```
+
+See also the [fourth example](examples).
+
+Note that this executor does not implement a timeout, so you will very likely
+want to use this in combination with a `TimeoutExecutor` like this:
+
+```php
+$executor = new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 3.0,
+ $loop
+);
+```
+
+Also note that this executor uses an unreliable UDP transport and that it
+does not implement any retry logic, so you will likely want to use this in
+combination with a `RetryExecutor` like this:
+
+```php
+$executor = new RetryExecutor(
+ new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 3.0,
+ $loop
+ )
+);
+```
+
+Note that this executor is entirely async and as such allows you to execute
+any number of queries concurrently. You should probably limit the number of
+concurrent queries in your application or you're very likely going to face
+rate limitations and bans on the resolver end. For many common applications,
+you may want to avoid sending the same query multiple times when the first
+one is still pending, so you will likely want to use this in combination with
+a `CoopExecutor` like this:
+
+```php
+$executor = new CoopExecutor(
+ new RetryExecutor(
+ new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 3.0,
+ $loop
+ )
+ )
+);
+```
+
+> Internally, this class uses PHP's UDP sockets and does not take advantage
+ of [react/datagram](https://github.com/reactphp/datagram) purely for
+ organizational reasons to avoid a cyclic dependency between the two
+ packages. Higher-level components should take advantage of the Datagram
+ component instead of reimplementing this socket logic from scratch.
+
+### HostsFileExecutor
+
+Note that the above `UdpTransportExecutor` class always performs an actual DNS query.
+If you also want to take entries from your hosts file into account, you may
+use this code:
+
+```php
+$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();
+
+$executor = new UdpTransportExecutor($loop);
+$executor = new HostsFileExecutor($hosts, $executor);
+
+$executor->query(
+ '8.8.8.8:53',
+ new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
+);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/dns:^0.4.19
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## References
+
+* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities
+* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification
diff --git a/src/mpg25-instagram-api/vendor/react/dns/composer.json b/src/mpg25-instagram-api/vendor/react/dns/composer.json
new file mode 100755
index 0000000..3ddf6e4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "react/dns",
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.1 || ^1.2.1",
+ "react/promise-timer": "^1.2",
+ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": { "React\\Dns\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Dns\\": "tests" }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/examples/01-one.php b/src/mpg25-instagram-api/vendor/react/dns/examples/01-one.php
new file mode 100755
index 0000000..5db164f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/examples/01-one.php
@@ -0,0 +1,22 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/dns/examples/02-concurrent.php b/src/mpg25-instagram-api/vendor/react/dns/examples/02-concurrent.php
new file mode 100755
index 0000000..87e3f5c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/examples/02-concurrent.php
@@ -0,0 +1,27 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$names = array_slice($argv, 1);
+if (!$names) {
+ $names = array('google.com', 'www.google.com', 'gmail.com');
+}
+
+foreach ($names as $name) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+}
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/dns/examples/03-cached.php b/src/mpg25-instagram-api/vendor/react/dns/examples/03-cached.php
new file mode 100755
index 0000000..e76a27c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/examples/03-cached.php
@@ -0,0 +1,40 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->createCached($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+}, 'printf');
+
+$loop->addTimer(1.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->addTimer(2.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->addTimer(3.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/dns/examples/11-all-ips.php b/src/mpg25-instagram-api/vendor/react/dns/examples/11-all-ips.php
new file mode 100755
index 0000000..d118bbb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/examples/11-all-ips.php
@@ -0,0 +1,31 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolveAll($name, Message::TYPE_A)->then(function (array $ips) use ($name) {
+ echo 'IPv4 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
+}, function (Exception $e) use ($name) {
+ echo 'No IPv4 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
+});
+
+$resolver->resolveAll($name, Message::TYPE_AAAA)->then(function (array $ips) use ($name) {
+ echo 'IPv6 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
+}, function (Exception $e) use ($name) {
+ echo 'No IPv6 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
+});
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/dns/examples/12-all-types.php b/src/mpg25-instagram-api/vendor/react/dns/examples/12-all-types.php
new file mode 100755
index 0000000..438ee86
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/examples/12-all-types.php
@@ -0,0 +1,25 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'google.com';
+$type = constant('React\Dns\Model\Message::TYPE_' . (isset($argv[2]) ? $argv[2] : 'TXT'));
+
+$resolver->resolveAll($name, $type)->then(function (array $values) {
+ var_dump($values);
+}, function (Exception $e) {
+ echo $e->getMessage() . PHP_EOL;
+});
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/dns/examples/13-reverse-dns.php b/src/mpg25-instagram-api/vendor/react/dns/examples/13-reverse-dns.php
new file mode 100755
index 0000000..7bc08f5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/examples/13-reverse-dns.php
@@ -0,0 +1,35 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$ip = isset($argv[1]) ? $argv[1] : '8.8.8.8';
+
+if (@inet_pton($ip) === false) {
+ exit('Error: Given argument is not a valid IP' . PHP_EOL);
+}
+
+if (strpos($ip, ':') === false) {
+ $name = inet_ntop(strrev(inet_pton($ip))) . '.in-addr.arpa';
+} else {
+ $name = wordwrap(strrev(bin2hex(inet_pton($ip))), 1, '.', true) . '.ip6.arpa';
+}
+
+$resolver->resolveAll($name, Message::TYPE_PTR)->then(function (array $names) {
+ var_dump($names);
+}, function (Exception $e) {
+ echo $e->getMessage() . PHP_EOL;
+});
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/dns/examples/91-query-a-and-aaaa.php b/src/mpg25-instagram-api/vendor/react/dns/examples/91-query-a-and-aaaa.php
new file mode 100755
index 0000000..e4a3feb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/examples/91-query-a-and-aaaa.php
@@ -0,0 +1,29 @@
+query('8.8.8.8:53', $ipv4Query)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv4: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+$executor->query('8.8.8.8:53', $ipv6Query)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv6: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/dns/examples/92-query-any.php b/src/mpg25-instagram-api/vendor/react/dns/examples/92-query-any.php
new file mode 100755
index 0000000..dcc14ae
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/examples/92-query-any.php
@@ -0,0 +1,71 @@
+query('8.8.8.8:53', $any)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ /* @var $answer Record */
+
+ $data = $answer->data;
+
+ switch ($answer->type) {
+ case Message::TYPE_A:
+ $type = 'A';
+ break;
+ case Message::TYPE_AAAA:
+ $type = 'AAAA';
+ break;
+ case Message::TYPE_NS:
+ $type = 'NS';
+ break;
+ case Message::TYPE_PTR:
+ $type = 'PTR';
+ break;
+ case Message::TYPE_CNAME:
+ $type = 'CNAME';
+ break;
+ case Message::TYPE_TXT:
+ // TXT records can contain a list of (binary) strings for each record.
+ // here, we assume this is printable ASCII and simply concatenate output
+ $type = 'TXT';
+ $data = implode('', $data);
+ break;
+ case Message::TYPE_MX:
+ // MX records contain "priority" and "target", only dump its values here
+ $type = 'MX';
+ $data = implode(' ', $data);
+ break;
+ case Message::TYPE_SRV:
+ // SRV records contains priority, weight, port and target, dump structure here
+ $type = 'SRV';
+ $data = json_encode($data);
+ break;
+ case Message::TYPE_SOA:
+ // SOA records contain structured data, dump structure here
+ $type = 'SOA';
+ $data = json_encode($data);
+ break;
+ default:
+ // unknown type uses HEX format
+ $type = 'Type ' . $answer->type;
+ $data = wordwrap(strtoupper(bin2hex($data)), 2, ' ', true);
+ }
+
+ echo $type . ': ' . $data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/dns/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/react/dns/phpunit.xml.dist
new file mode 100755
index 0000000..04d426b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/phpunit.xml.dist
@@ -0,0 +1,24 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/BadServerException.php b/src/mpg25-instagram-api/vendor/react/dns/src/BadServerException.php
new file mode 100755
index 0000000..3bf50f1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/BadServerException.php
@@ -0,0 +1,7 @@
+nameservers = $matches[1];
+
+ return $config;
+ }
+
+ /**
+ * Loads the DNS configurations from Windows's WMIC (from the given command or default command)
+ *
+ * Note that this method blocks while loading the given command and should
+ * thus be used with care! While this should be relatively fast for normal
+ * WMIC commands, it remains unknown if this may block under certain
+ * circumstances. In particular, this method should only be executed before
+ * the loop starts, not while it is running.
+ *
+ * Note that this method will only try to execute the given command try to
+ * parse its output, irrespective of whether this command exists. In
+ * particular, this command is only available on Windows. Currently, this
+ * will only parse valid nameserver entries from the command output and will
+ * ignore all other output without complaining.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid nameserver entries can be found.
+ *
+ * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
+ * @return self
+ * @link https://ss64.com/nt/wmic.html
+ */
+ public static function loadWmicBlocking($command = null)
+ {
+ $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
+ preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
+
+ $config = new self();
+ $config->nameservers = $matches[1];
+
+ return $config;
+ }
+
+ public $nameservers = array();
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Config/FilesystemFactory.php b/src/mpg25-instagram-api/vendor/react/dns/src/Config/FilesystemFactory.php
new file mode 100755
index 0000000..68cec3e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Config/FilesystemFactory.php
@@ -0,0 +1,73 @@
+loop = $loop;
+ }
+
+ public function create($filename)
+ {
+ return $this
+ ->loadEtcResolvConf($filename)
+ ->then(array($this, 'parseEtcResolvConf'));
+ }
+
+ /**
+ * @param string $contents
+ * @return Promise
+ * @deprecated see Config instead
+ */
+ public function parseEtcResolvConf($contents)
+ {
+ return Promise\resolve(Config::loadResolvConfBlocking(
+ 'data://text/plain;base64,' . base64_encode($contents)
+ ));
+ }
+
+ public function loadEtcResolvConf($filename)
+ {
+ if (!file_exists($filename)) {
+ return Promise\reject(new \InvalidArgumentException("The filename for /etc/resolv.conf given does not exist: $filename"));
+ }
+
+ try {
+ $deferred = new Deferred();
+
+ $fd = fopen($filename, 'r');
+ stream_set_blocking($fd, 0);
+
+ $contents = '';
+
+ $stream = class_exists('React\Stream\ReadableResourceStream') ? new ReadableResourceStream($fd, $this->loop) : new Stream($fd, $this->loop);
+ $stream->on('data', function ($data) use (&$contents) {
+ $contents .= $data;
+ });
+ $stream->on('end', function () use (&$contents, $deferred) {
+ $deferred->resolve($contents);
+ });
+ $stream->on('error', function ($error) use ($deferred) {
+ $deferred->reject($error);
+ });
+
+ return $deferred->promise();
+ } catch (\Exception $e) {
+ return Promise\reject($e);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Config/HostsFile.php b/src/mpg25-instagram-api/vendor/react/dns/src/Config/HostsFile.php
new file mode 100755
index 0000000..5b6277e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Config/HostsFile.php
@@ -0,0 +1,151 @@
+contents = $contents;
+ }
+
+ /**
+ * Returns all IPs for the given hostname
+ *
+ * @param string $name
+ * @return string[]
+ */
+ public function getIpsForHost($name)
+ {
+ $name = strtolower($name);
+
+ $ips = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line);
+ $ip = array_shift($parts);
+ if ($parts && array_search($name, $parts) !== false) {
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
+ $ip = substr($ip, 0, $pos);
+ }
+
+ if (@inet_pton($ip) !== false) {
+ $ips[] = $ip;
+ }
+ }
+ }
+
+ return $ips;
+ }
+
+ /**
+ * Returns all hostnames for the given IPv4 or IPv6 address
+ *
+ * @param string $ip
+ * @return string[]
+ */
+ public function getHostsForIp($ip)
+ {
+ // check binary representation of IP to avoid string case and short notation
+ $ip = @inet_pton($ip);
+ if ($ip === false) {
+ return array();
+ }
+
+ $names = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line, null, PREG_SPLIT_NO_EMPTY);
+ $addr = array_shift($parts);
+
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
+ $addr = substr($addr, 0, $pos);
+ }
+
+ if (@inet_pton($addr) === $ip) {
+ foreach ($parts as $part) {
+ $names[] = $part;
+ }
+ }
+ }
+
+ return $names;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Model/HeaderBag.php b/src/mpg25-instagram-api/vendor/react/dns/src/Model/HeaderBag.php
new file mode 100755
index 0000000..0093bd3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Model/HeaderBag.php
@@ -0,0 +1,59 @@
+ 0,
+ 'anCount' => 0,
+ 'nsCount' => 0,
+ 'arCount' => 0,
+ 'qr' => 0,
+ 'opcode' => Message::OPCODE_QUERY,
+ 'aa' => 0,
+ 'tc' => 0,
+ 'rd' => 0,
+ 'ra' => 0,
+ 'z' => 0,
+ 'rcode' => Message::RCODE_OK,
+ );
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public $data = '';
+
+ public function get($name)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
+ }
+
+ public function set($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ public function isQuery()
+ {
+ return 0 === $this->attributes['qr'];
+ }
+
+ public function isResponse()
+ {
+ return 1 === $this->attributes['qr'];
+ }
+
+ public function isTruncated()
+ {
+ return 1 === $this->attributes['tc'];
+ }
+
+ public function populateCounts(Message $message)
+ {
+ $this->attributes['qdCount'] = count($message->questions);
+ $this->attributes['anCount'] = count($message->answers);
+ $this->attributes['nsCount'] = count($message->authority);
+ $this->attributes['arCount'] = count($message->additional);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Model/Message.php b/src/mpg25-instagram-api/vendor/react/dns/src/Model/Message.php
new file mode 100755
index 0000000..da859e7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Model/Message.php
@@ -0,0 +1,188 @@
+header->set('id', self::generateId());
+ $request->header->set('rd', 1);
+ $request->questions[] = (array) $query;
+ $request->prepare();
+
+ return $request;
+ }
+
+ /**
+ * Creates a new response message for the given query with the given answer records
+ *
+ * @param Query $query
+ * @param Record[] $answers
+ * @return self
+ */
+ public static function createResponseWithAnswersForQuery(Query $query, array $answers)
+ {
+ $response = new Message();
+ $response->header->set('id', self::generateId());
+ $response->header->set('qr', 1);
+ $response->header->set('opcode', Message::OPCODE_QUERY);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = (array) $query;
+
+ foreach ($answers as $record) {
+ $response->answers[] = $record;
+ }
+
+ $response->prepare();
+
+ return $response;
+ }
+
+ /**
+ * generates a random 16 bit message ID
+ *
+ * This uses a CSPRNG so that an outside attacker that is sending spoofed
+ * DNS response messages can not guess the message ID to avoid possible
+ * cache poisoning attacks.
+ *
+ * The `random_int()` function is only available on PHP 7+ or when
+ * https://github.com/paragonie/random_compat is installed. As such, using
+ * the latest supported PHP version is highly recommended. This currently
+ * falls back to a less secure random number generator on older PHP versions
+ * in the hope that this system is properly protected against outside
+ * attackers, for example by using one of the common local DNS proxy stubs.
+ *
+ * @return int
+ * @see self::getId()
+ * @codeCoverageIgnore
+ */
+ private static function generateId()
+ {
+ if (function_exists('random_int')) {
+ return random_int(0, 0xffff);
+ }
+ return mt_rand(0, 0xffff);
+ }
+
+ /**
+ * @var HeaderBag
+ */
+ public $header;
+
+ /**
+ * This should be an array of Query objects. For BC reasons, this currently
+ * references a nested array with a structure that results from casting the
+ * Query objects to an array:
+ *
+ * ```php
+ * $questions = array(
+ * array(
+ * 'name' => 'reactphp.org',
+ * 'type' => Message::TYPE_A,
+ * 'class' => Message::CLASS_IN
+ * )
+ * );
+ * ```
+ *
+ * @var array
+ * @see Query
+ */
+ public $questions = array();
+
+ /**
+ * @var Record[]
+ */
+ public $answers = array();
+
+ /**
+ * @var Record[]
+ */
+ public $authority = array();
+
+ /**
+ * @var Record[]
+ */
+ public $additional = array();
+
+ /**
+ * @deprecated still used internally for BC reasons, should not be used externally.
+ */
+ public $data = '';
+
+ /**
+ * @deprecated still used internally for BC reasons, should not be used externally.
+ */
+ public $consumed = 0;
+
+ public function __construct()
+ {
+ $this->header = new HeaderBag();
+ }
+
+ /**
+ * Returns the 16 bit message ID
+ *
+ * The response message ID has to match the request message ID. This allows
+ * the receiver to verify this is the correct response message. An outside
+ * attacker may try to inject fake responses by "guessing" the message ID,
+ * so this should use a proper CSPRNG to avoid possible cache poisoning.
+ *
+ * @return int
+ * @see self::generateId()
+ */
+ public function getId()
+ {
+ return $this->header->get('id');
+ }
+
+ /**
+ * Returns the response code (RCODE)
+ *
+ * @return int see self::RCODE_* constants
+ */
+ public function getResponseCode()
+ {
+ return $this->header->get('rcode');
+ }
+
+ public function prepare()
+ {
+ $this->header->populateCounts($this);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Model/Record.php b/src/mpg25-instagram-api/vendor/react/dns/src/Model/Record.php
new file mode 100755
index 0000000..2504911
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Model/Record.php
@@ -0,0 +1,105 @@
+name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->ttl = $ttl;
+ $this->data = $data;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Protocol/BinaryDumper.php b/src/mpg25-instagram-api/vendor/react/dns/src/Protocol/BinaryDumper.php
new file mode 100755
index 0000000..0391604
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Protocol/BinaryDumper.php
@@ -0,0 +1,163 @@
+headerToBinary($message->header);
+ $data .= $this->questionToBinary($message->questions);
+ $data .= $this->recordsToBinary($message->answers);
+ $data .= $this->recordsToBinary($message->authority);
+ $data .= $this->recordsToBinary($message->additional);
+
+ return $data;
+ }
+
+ /**
+ * @param HeaderBag $header
+ * @return string
+ */
+ private function headerToBinary(HeaderBag $header)
+ {
+ $data = '';
+
+ $data .= pack('n', $header->get('id'));
+
+ $flags = 0x00;
+ $flags = ($flags << 1) | $header->get('qr');
+ $flags = ($flags << 4) | $header->get('opcode');
+ $flags = ($flags << 1) | $header->get('aa');
+ $flags = ($flags << 1) | $header->get('tc');
+ $flags = ($flags << 1) | $header->get('rd');
+ $flags = ($flags << 1) | $header->get('ra');
+ $flags = ($flags << 3) | $header->get('z');
+ $flags = ($flags << 4) | $header->get('rcode');
+
+ $data .= pack('n', $flags);
+
+ $data .= pack('n', $header->get('qdCount'));
+ $data .= pack('n', $header->get('anCount'));
+ $data .= pack('n', $header->get('nsCount'));
+ $data .= pack('n', $header->get('arCount'));
+
+ return $data;
+ }
+
+ /**
+ * @param array $questions
+ * @return string
+ */
+ private function questionToBinary(array $questions)
+ {
+ $data = '';
+
+ foreach ($questions as $question) {
+ $data .= $this->domainNameToBinary($question['name']);
+ $data .= pack('n*', $question['type'], $question['class']);
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param Record[] $records
+ * @return string
+ */
+ private function recordsToBinary(array $records)
+ {
+ $data = '';
+
+ foreach ($records as $record) {
+ /* @var $record Record */
+ switch ($record->type) {
+ case Message::TYPE_A:
+ case Message::TYPE_AAAA:
+ $binary = \inet_pton($record->data);
+ break;
+ case Message::TYPE_CNAME:
+ case Message::TYPE_NS:
+ case Message::TYPE_PTR:
+ $binary = $this->domainNameToBinary($record->data);
+ break;
+ case Message::TYPE_TXT:
+ $binary = $this->textsToBinary($record->data);
+ break;
+ case Message::TYPE_MX:
+ $binary = \pack(
+ 'n',
+ $record->data['priority']
+ );
+ $binary .= $this->domainNameToBinary($record->data['target']);
+ break;
+ case Message::TYPE_SRV:
+ $binary = \pack(
+ 'n*',
+ $record->data['priority'],
+ $record->data['weight'],
+ $record->data['port']
+ );
+ $binary .= $this->domainNameToBinary($record->data['target']);
+ break;
+ case Message::TYPE_SOA:
+ $binary = $this->domainNameToBinary($record->data['mname']);
+ $binary .= $this->domainNameToBinary($record->data['rname']);
+ $binary .= \pack(
+ 'N*',
+ $record->data['serial'],
+ $record->data['refresh'],
+ $record->data['retry'],
+ $record->data['expire'],
+ $record->data['minimum']
+ );
+ break;
+ default:
+ // RDATA is already stored as binary value for unknown record types
+ $binary = $record->data;
+ }
+
+ $data .= $this->domainNameToBinary($record->name);
+ $data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
+ $data .= $binary;
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param string[] $texts
+ * @return string
+ */
+ private function textsToBinary(array $texts)
+ {
+ $data = '';
+ foreach ($texts as $text) {
+ $data .= \chr(\strlen($text)) . $text;
+ }
+ return $data;
+ }
+
+ /**
+ * @param string $host
+ * @return string
+ */
+ private function domainNameToBinary($host)
+ {
+ if ($host === '') {
+ return "\0";
+ }
+
+ return $this->textsToBinary(\explode('.', $host . '.'));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Protocol/Parser.php b/src/mpg25-instagram-api/vendor/react/dns/src/Protocol/Parser.php
new file mode 100755
index 0000000..ada9db1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Protocol/Parser.php
@@ -0,0 +1,395 @@
+parse($data, $message) !== $message) {
+ throw new InvalidArgumentException('Unable to parse binary message');
+ }
+
+ return $message;
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function parseChunk($data, Message $message)
+ {
+ return $this->parse($data, $message);
+ }
+
+ private function parse($data, Message $message)
+ {
+ $message->data .= $data;
+
+ if (!$message->header->get('id')) {
+ if (!$this->parseHeader($message)) {
+ return;
+ }
+ }
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ if (!$this->parseQuestion($message)) {
+ return;
+ }
+ }
+
+ // parse all answer records
+ for ($i = $message->header->get('anCount'); $i > 0; --$i) {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return;
+ } else {
+ $message->answers[] = $record;
+ }
+ }
+
+ // parse all authority records
+ for ($i = $message->header->get('nsCount'); $i > 0; --$i) {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return;
+ } else {
+ $message->authority[] = $record;
+ }
+ }
+
+ // parse all additional records
+ for ($i = $message->header->get('arCount'); $i > 0; --$i) {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return;
+ } else {
+ $message->additional[] = $record;
+ }
+ }
+
+ return $message;
+ }
+
+ public function parseHeader(Message $message)
+ {
+ if (!isset($message->data[12 - 1])) {
+ return;
+ }
+
+ $header = substr($message->data, 0, 12);
+ $message->consumed += 12;
+
+ list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header));
+
+ $rcode = $fields & bindec('1111');
+ $z = ($fields >> 4) & bindec('111');
+ $ra = ($fields >> 7) & 1;
+ $rd = ($fields >> 8) & 1;
+ $tc = ($fields >> 9) & 1;
+ $aa = ($fields >> 10) & 1;
+ $opcode = ($fields >> 11) & bindec('1111');
+ $qr = ($fields >> 15) & 1;
+
+ $vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount',
+ 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode');
+
+ foreach ($vars as $name => $value) {
+ $message->header->set($name, $value);
+ }
+
+ return $message;
+ }
+
+ public function parseQuestion(Message $message)
+ {
+ $consumed = $message->consumed;
+
+ list($labels, $consumed) = $this->readLabels($message->data, $consumed);
+
+ if ($labels === null || !isset($message->data[$consumed + 4 - 1])) {
+ return;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ $message->consumed = $consumed;
+
+ $message->questions[] = array(
+ 'name' => implode('.', $labels),
+ 'type' => $type,
+ 'class' => $class,
+ );
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ return $this->parseQuestion($message);
+ }
+
+ return $message;
+ }
+
+ /**
+ * recursively parse all answers from the message data into message answer records
+ *
+ * @param Message $message
+ * @return ?Message returns the updated message on success or null if the data is invalid/incomplete
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function parseAnswer(Message $message)
+ {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return null;
+ }
+
+ $message->answers[] = $record;
+
+ if ($message->header->get('anCount') != count($message->answers)) {
+ return $this->parseAnswer($message);
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param Message $message
+ * @return ?Record returns parsed Record on success or null if data is invalid/incomplete
+ */
+ private function parseRecord(Message $message)
+ {
+ $consumed = $message->consumed;
+
+ list($name, $consumed) = $this->readDomain($message->data, $consumed);
+
+ if ($name === null || !isset($message->data[$consumed + 10 - 1])) {
+ return null;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ // TTL is a UINT32 that must not have most significant bit set for BC reasons
+ if ($ttl < 0 || $ttl >= 1 << 31) {
+ $ttl = 0;
+ }
+
+ list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
+ $consumed += 2;
+
+ if (!isset($message->data[$consumed + $rdLength - 1])) {
+ return null;
+ }
+
+ $rdata = null;
+ $expected = $consumed + $rdLength;
+
+ if (Message::TYPE_A === $type) {
+ if ($rdLength === 4) {
+ $rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_AAAA === $type) {
+ if ($rdLength === 16) {
+ $rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
+ list($rdata, $consumed) = $this->readDomain($message->data, $consumed);
+ } elseif (Message::TYPE_TXT === $type) {
+ $rdata = array();
+ while ($consumed < $expected) {
+ $len = ord($message->data[$consumed]);
+ $rdata[] = (string)substr($message->data, $consumed + 1, $len);
+ $consumed += $len + 1;
+ }
+ } elseif (Message::TYPE_MX === $type) {
+ if ($rdLength > 2) {
+ list($priority) = array_values(unpack('n', substr($message->data, $consumed, 2)));
+ list($target, $consumed) = $this->readDomain($message->data, $consumed + 2);
+
+ $rdata = array(
+ 'priority' => $priority,
+ 'target' => $target
+ );
+ }
+ } elseif (Message::TYPE_SRV === $type) {
+ if ($rdLength > 6) {
+ list($priority, $weight, $port) = array_values(unpack('n*', substr($message->data, $consumed, 6)));
+ list($target, $consumed) = $this->readDomain($message->data, $consumed + 6);
+
+ $rdata = array(
+ 'priority' => $priority,
+ 'weight' => $weight,
+ 'port' => $port,
+ 'target' => $target
+ );
+ }
+ } elseif (Message::TYPE_SOA === $type) {
+ list($mname, $consumed) = $this->readDomain($message->data, $consumed);
+ list($rname, $consumed) = $this->readDomain($message->data, $consumed);
+
+ if ($mname !== null && $rname !== null && isset($message->data[$consumed + 20 - 1])) {
+ list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($message->data, $consumed, 20)));
+ $consumed += 20;
+
+ $rdata = array(
+ 'mname' => $mname,
+ 'rname' => $rname,
+ 'serial' => $serial,
+ 'refresh' => $refresh,
+ 'retry' => $retry,
+ 'expire' => $expire,
+ 'minimum' => $minimum
+ );
+ }
+ } else {
+ // unknown types simply parse rdata as an opaque binary string
+ $rdata = substr($message->data, $consumed, $rdLength);
+ $consumed += $rdLength;
+ }
+
+ // ensure parsing record data consumes expact number of bytes indicated in record length
+ if ($consumed !== $expected || $rdata === null) {
+ return null;
+ }
+
+ $message->consumed = $consumed;
+
+ return new Record($name, $type, $class, $ttl, $rdata);
+ }
+
+ private function readDomain($data, $consumed)
+ {
+ list ($labels, $consumed) = $this->readLabels($data, $consumed);
+
+ if ($labels === null) {
+ return array(null, null);
+ }
+
+ return array(implode('.', $labels), $consumed);
+ }
+
+ private function readLabels($data, $consumed)
+ {
+ $labels = array();
+
+ while (true) {
+ if (!isset($data[$consumed])) {
+ return array(null, null);
+ }
+
+ $length = \ord($data[$consumed]);
+
+ // end of labels reached
+ if ($length === 0) {
+ $consumed += 1;
+ break;
+ }
+
+ // first two bits set? this is a compressed label (14 bit pointer offset)
+ if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1])) {
+ $offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
+ if ($offset >= $consumed) {
+ return array(null, null);
+ }
+
+ $consumed += 2;
+ list($newLabels) = $this->readLabels($data, $offset);
+
+ if ($newLabels === null) {
+ return array(null, null);
+ }
+
+ $labels = array_merge($labels, $newLabels);
+ break;
+ }
+
+ // length MUST be 0-63 (6 bits only) and data has to be large enough
+ if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
+ return array(null, null);
+ }
+
+ $labels[] = substr($data, $consumed + 1, $length);
+ $consumed += $length + 1;
+ }
+
+ return array($labels, $consumed);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function isEndOfLabels($data, $consumed)
+ {
+ $length = ord(substr($data, $consumed, 1));
+ return 0 === $length;
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function getCompressedLabel($data, $consumed)
+ {
+ list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed);
+ list($labels) = $this->readLabels($data, $nameOffset);
+
+ return array($labels, $consumed);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function isCompressedLabel($data, $consumed)
+ {
+ $mask = 0xc000; // 1100000000000000
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return (bool) ($peek & $mask);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function getCompressedLabelOffset($data, $consumed)
+ {
+ $mask = 0x3fff; // 0011111111111111
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return array($peek & $mask, $consumed + 2);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function signedLongToUnsignedLong($i)
+ {
+ return $i & 0x80000000 ? $i - 0xffffffff : $i;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/CachedExecutor.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/CachedExecutor.php
new file mode 100755
index 0000000..8b70894
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/CachedExecutor.php
@@ -0,0 +1,59 @@
+executor = $executor;
+ $this->cache = $cache;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $executor = $this->executor;
+ $cache = $this->cache;
+
+ return $this->cache
+ ->lookup($query)
+ ->then(
+ function ($cachedRecords) use ($query) {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ },
+ function () use ($executor, $cache, $nameserver, $query) {
+ return $executor
+ ->query($nameserver, $query)
+ ->then(function ($response) use ($cache, $query) {
+ $cache->storeResponseMessage($query->currentTime, $response);
+ return $response;
+ });
+ }
+ );
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function buildResponse(Query $query, array $cachedRecords)
+ {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/CachingExecutor.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/CachingExecutor.php
new file mode 100755
index 0000000..e6ec3ac
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/CachingExecutor.php
@@ -0,0 +1,88 @@
+executor = $executor;
+ $this->cache = $cache;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $id = $query->name . ':' . $query->type . ':' . $query->class;
+ $cache = $this->cache;
+ $that = $this;
+ $executor = $this->executor;
+
+ $pending = $cache->get($id);
+ return new Promise(function ($resolve, $reject) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
+ $pending->then(
+ function ($message) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
+ // return cached response message on cache hit
+ if ($message !== null) {
+ return $message;
+ }
+
+ // perform DNS lookup if not already cached
+ return $pending = $executor->query($nameserver, $query)->then(
+ function (Message $message) use ($cache, $id, $that) {
+ // DNS response message received => store in cache when not truncated and return
+ if (!$message->header->isTruncated()) {
+ $cache->set($id, $message, $that->ttl($message));
+ }
+
+ return $message;
+ }
+ );
+ }
+ )->then($resolve, function ($e) use ($reject, &$pending) {
+ $reject($e);
+ $pending = null;
+ });
+ }, function ($_, $reject) use (&$pending, $query) {
+ $reject(new \RuntimeException('DNS query for ' . $query->name . ' has been cancelled'));
+ $pending->cancel();
+ $pending = null;
+ });
+ }
+
+ /**
+ * @param Message $message
+ * @return int
+ * @internal
+ */
+ public function ttl(Message $message)
+ {
+ // select TTL from answers (should all be the same), use smallest value if available
+ // @link https://tools.ietf.org/html/rfc2181#section-5.2
+ $ttl = null;
+ foreach ($message->answers as $answer) {
+ if ($ttl === null || $answer->ttl < $ttl) {
+ $ttl = $answer->ttl;
+ }
+ }
+
+ if ($ttl === null) {
+ $ttl = self::TTL;
+ }
+
+ return $ttl;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/CancellationException.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/CancellationException.php
new file mode 100755
index 0000000..ac30f4c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/CancellationException.php
@@ -0,0 +1,7 @@
+executor = $base;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $key = $this->serializeQueryToIdentity($query);
+ if (isset($this->pending[$key])) {
+ // same query is already pending, so use shared reference to pending query
+ $promise = $this->pending[$key];
+ ++$this->counts[$key];
+ } else {
+ // no such query pending, so start new query and keep reference until it's fulfilled or rejected
+ $promise = $this->executor->query($nameserver, $query);
+ $this->pending[$key] = $promise;
+ $this->counts[$key] = 1;
+
+ $pending =& $this->pending;
+ $counts =& $this->counts;
+ $promise->then(function () use ($key, &$pending, &$counts) {
+ unset($pending[$key], $counts[$key]);
+ }, function () use ($key, &$pending, &$counts) {
+ unset($pending[$key], $counts[$key]);
+ });
+ }
+
+ // Return a child promise awaiting the pending query.
+ // Cancelling this child promise should only cancel the pending query
+ // when no other child promise is awaiting the same query.
+ $pending =& $this->pending;
+ $counts =& $this->counts;
+ return new Promise(function ($resolve, $reject) use ($promise) {
+ $promise->then($resolve, $reject);
+ }, function () use (&$promise, $key, $query, &$pending, &$counts) {
+ if (--$counts[$key] < 1) {
+ unset($pending[$key], $counts[$key]);
+ $promise->cancel();
+ $promise = null;
+ }
+ throw new \RuntimeException('DNS query for ' . $query->name . ' has been cancelled');
+ });
+ }
+
+ private function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/Executor.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/Executor.php
new file mode 100755
index 0000000..40f6bb4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/Executor.php
@@ -0,0 +1,160 @@
+loop = $loop;
+ $this->parser = $parser;
+ $this->dumper = $dumper;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ $transport = strlen($queryData) > 512 ? 'tcp' : 'udp';
+
+ return $this->doQuery($nameserver, $transport, $queryData, $query->name);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function prepareRequest(Query $query)
+ {
+ return Message::createRequestForQuery($query);
+ }
+
+ public function doQuery($nameserver, $transport, $queryData, $name)
+ {
+ // we only support UDP right now
+ if ($transport !== 'udp') {
+ return Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $name . ' failed: Requested transport "' . $transport . '" not available, only UDP is supported in this version'
+ ));
+ }
+
+ $that = $this;
+ $parser = $this->parser;
+ $loop = $this->loop;
+
+ // UDP connections are instant, so try this without a timer
+ try {
+ $conn = $this->createConnection($nameserver, $transport);
+ } catch (\Exception $e) {
+ return Promise\reject(new \RuntimeException('DNS query for ' . $name . ' failed: ' . $e->getMessage(), 0, $e));
+ }
+
+ $deferred = new Deferred(function ($resolve, $reject) use (&$timer, $loop, &$conn, $name) {
+ $reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name)));
+
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+ $conn->close();
+ });
+
+ $timer = null;
+ if ($this->timeout !== null) {
+ $timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) {
+ $conn->close();
+ $deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name)));
+ });
+ }
+
+ $conn->on('data', function ($data) use ($conn, $parser, $deferred, $timer, $loop, $name) {
+ $conn->end();
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+
+ try {
+ $response = $parser->parseMessage($data);
+ } catch (\Exception $e) {
+ $deferred->reject($e);
+ return;
+ }
+
+ if ($response->header->isTruncated()) {
+ $deferred->reject(new \RuntimeException('DNS query for ' . $name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+ $conn->write($queryData);
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+
+ /**
+ * @param string $nameserver
+ * @param string $transport
+ * @return \React\Stream\DuplexStreamInterface
+ */
+ protected function createConnection($nameserver, $transport)
+ {
+ $fd = @stream_socket_client("$transport://$nameserver", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
+ if ($fd === false) {
+ throw new \RuntimeException('Unable to connect to DNS server: ' . $errstr, $errno);
+ }
+
+ // Instantiate stream instance around this stream resource.
+ // This ought to be replaced with a datagram socket in the future.
+ // Temporary work around for Windows 10: buffer whole UDP response
+ // @coverageIgnoreStart
+ if (!class_exists('React\Stream\Stream')) {
+ // prefer DuplexResourceStream as of react/stream v0.7.0
+ $conn = new DuplexResourceStream($fd, $this->loop, -1);
+ } else {
+ // use legacy Stream class for react/stream < v0.7.0
+ $conn = new Stream($fd, $this->loop);
+ $conn->bufferSize = null;
+ }
+ // @coverageIgnoreEnd
+
+ return $conn;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/ExecutorInterface.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/ExecutorInterface.php
new file mode 100755
index 0000000..2f7a635
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/ExecutorInterface.php
@@ -0,0 +1,8 @@
+hosts = $hosts;
+ $this->fallback = $fallback;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
+ // forward lookup for type A or AAAA
+ $records = array();
+ $expectsColon = $query->type === Message::TYPE_AAAA;
+ foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
+ // ensure this is an IPv4/IPV6 address according to query type
+ if ((strpos($ip, ':') !== false) === $expectsColon) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
+ }
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ } elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
+ // reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
+ $ip = $this->getIpFromHost($query->name);
+
+ if ($ip !== null) {
+ $records = array();
+ foreach ($this->hosts->getHostsForIp($ip) as $host) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $host);
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ }
+ }
+
+ return $this->fallback->query($nameserver, $query);
+ }
+
+ private function getIpFromHost($host)
+ {
+ if (substr($host, -13) === '.in-addr.arpa') {
+ // IPv4: read as IP and reverse bytes
+ $ip = @inet_pton(substr($host, 0, -13));
+ if ($ip === false || isset($ip[4])) {
+ return null;
+ }
+
+ return inet_ntop(strrev($ip));
+ } elseif (substr($host, -9) === '.ip6.arpa') {
+ // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
+ $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
+ if ($ip === false) {
+ return null;
+ }
+
+ return $ip;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/Query.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/Query.php
new file mode 100755
index 0000000..058a78d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/Query.php
@@ -0,0 +1,33 @@
+name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->currentTime = $currentTime;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/RecordBag.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/RecordBag.php
new file mode 100755
index 0000000..4dc815a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/RecordBag.php
@@ -0,0 +1,30 @@
+records[] = array($currentTime + $record->ttl, $record);
+ }
+
+ public function all()
+ {
+ return array_values(array_map(
+ function ($value) {
+ list($expiresAt, $record) = $value;
+ return $record;
+ },
+ $this->records
+ ));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/RecordCache.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/RecordCache.php
new file mode 100755
index 0000000..c087e5f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/RecordCache.php
@@ -0,0 +1,122 @@
+cache = $cache;
+ }
+
+ /**
+ * Looks up the cache if there's a cached answer for the given query
+ *
+ * @param Query $query
+ * @return PromiseInterface Promise resolves with array of Record objects on sucess
+ * or rejects with mixed values when query is not cached already.
+ */
+ public function lookup(Query $query)
+ {
+ $id = $this->serializeQueryToIdentity($query);
+
+ $expiredAt = $this->expiredAt;
+
+ return $this->cache
+ ->get($id)
+ ->then(function ($value) use ($query, $expiredAt) {
+ // reject on cache miss
+ if ($value === null) {
+ return Promise\reject();
+ }
+
+ /* @var $recordBag RecordBag */
+ $recordBag = unserialize($value);
+
+ // reject this cache hit if the query was started before the time we expired the cache?
+ // todo: this is a legacy left over, this value is never actually set, so this never applies.
+ // todo: this should probably validate the cache time instead.
+ if (null !== $expiredAt && $expiredAt <= $query->currentTime) {
+ return Promise\reject();
+ }
+
+ return $recordBag->all();
+ });
+ }
+
+ /**
+ * Stores all records from this response message in the cache
+ *
+ * @param int $currentTime
+ * @param Message $message
+ * @uses self::storeRecord()
+ */
+ public function storeResponseMessage($currentTime, Message $message)
+ {
+ foreach ($message->answers as $record) {
+ $this->storeRecord($currentTime, $record);
+ }
+ }
+
+ /**
+ * Stores a single record from a response message in the cache
+ *
+ * @param int $currentTime
+ * @param Record $record
+ */
+ public function storeRecord($currentTime, Record $record)
+ {
+ $id = $this->serializeRecordToIdentity($record);
+
+ $cache = $this->cache;
+
+ $this->cache
+ ->get($id)
+ ->then(
+ function ($value) {
+ // return empty bag on cache miss
+ if ($value === null) {
+ return new RecordBag();
+ }
+
+ // reuse existing bag on cache hit to append new record to it
+ return unserialize($value);
+ }
+ )
+ ->then(function (RecordBag $recordBag) use ($id, $currentTime, $record, $cache) {
+ // add a record to the existing (possibly empty) record bag and save to cache
+ $recordBag->set($currentTime, $record);
+ $cache->set($id, serialize($recordBag));
+ });
+ }
+
+ public function expire($currentTime)
+ {
+ $this->expiredAt = $currentTime;
+ }
+
+ public function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+
+ public function serializeRecordToIdentity(Record $record)
+ {
+ return sprintf('%s:%s:%s', $record->name, $record->type, $record->class);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/RetryExecutor.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/RetryExecutor.php
new file mode 100755
index 0000000..46e2ef9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/RetryExecutor.php
@@ -0,0 +1,79 @@
+executor = $executor;
+ $this->retries = $retries;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return $this->tryQuery($nameserver, $query, $this->retries);
+ }
+
+ public function tryQuery($nameserver, Query $query, $retries)
+ {
+ $deferred = new Deferred(function () use (&$promise) {
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ });
+
+ $success = function ($value) use ($deferred, &$errorback) {
+ $errorback = null;
+ $deferred->resolve($value);
+ };
+
+ $executor = $this->executor;
+ $errorback = function ($e) use ($deferred, &$promise, $nameserver, $query, $success, &$errorback, &$retries, $executor) {
+ if (!$e instanceof TimeoutException) {
+ $errorback = null;
+ $deferred->reject($e);
+ } elseif ($retries <= 0) {
+ $errorback = null;
+ $deferred->reject($e = new \RuntimeException(
+ 'DNS query for ' . $query->name . ' failed: too many retries',
+ 0,
+ $e
+ ));
+
+ // avoid garbage references by replacing all closures in call stack.
+ // what a lovely piece of code!
+ $r = new \ReflectionProperty('Exception', 'trace');
+ $r->setAccessible(true);
+ $trace = $r->getValue($e);
+ foreach ($trace as &$one) {
+ foreach ($one['args'] as &$arg) {
+ if ($arg instanceof \Closure) {
+ $arg = 'Object(' . \get_class($arg) . ')';
+ }
+ }
+ }
+ $r->setValue($e, $trace);
+ } else {
+ --$retries;
+ $promise = $executor->query($nameserver, $query)->then(
+ $success,
+ $errorback
+ );
+ }
+ };
+
+ $promise = $this->executor->query($nameserver, $query)->then(
+ $success,
+ $errorback
+ );
+
+ return $deferred->promise();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/TimeoutException.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/TimeoutException.php
new file mode 100755
index 0000000..90bf806
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/TimeoutException.php
@@ -0,0 +1,7 @@
+executor = $executor;
+ $this->loop = $loop;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return Timer\timeout($this->executor->query($nameserver, $query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) {
+ if ($e instanceof Timer\TimeoutException) {
+ $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->name), 0, $e);
+ }
+ throw $e;
+ });
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Query/UdpTransportExecutor.php b/src/mpg25-instagram-api/vendor/react/dns/src/Query/UdpTransportExecutor.php
new file mode 100755
index 0000000..99a3e8a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Query/UdpTransportExecutor.php
@@ -0,0 +1,181 @@
+query(
+ * '8.8.8.8:53',
+ * new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
+ * )->then(function (Message $message) {
+ * foreach ($message->answers as $answer) {
+ * echo 'IPv6: ' . $answer->data . PHP_EOL;
+ * }
+ * }, 'printf');
+ *
+ * $loop->run();
+ * ```
+ *
+ * See also the [fourth example](examples).
+ *
+ * Note that this executor does not implement a timeout, so you will very likely
+ * want to use this in combination with a `TimeoutExecutor` like this:
+ *
+ * ```php
+ * $executor = new TimeoutExecutor(
+ * new UdpTransportExecutor($loop),
+ * 3.0,
+ * $loop
+ * );
+ * ```
+ *
+ * Also note that this executor uses an unreliable UDP transport and that it
+ * does not implement any retry logic, so you will likely want to use this in
+ * combination with a `RetryExecutor` like this:
+ *
+ * ```php
+ * $executor = new RetryExecutor(
+ * new TimeoutExecutor(
+ * new UdpTransportExecutor($loop),
+ * 3.0,
+ * $loop
+ * )
+ * );
+ * ```
+ *
+ * Note that this executor is entirely async and as such allows you to execute
+ * any number of queries concurrently. You should probably limit the number of
+ * concurrent queries in your application or you're very likely going to face
+ * rate limitations and bans on the resolver end. For many common applications,
+ * you may want to avoid sending the same query multiple times when the first
+ * one is still pending, so you will likely want to use this in combination with
+ * a `CoopExecutor` like this:
+ *
+ * ```php
+ * $executor = new CoopExecutor(
+ * new RetryExecutor(
+ * new TimeoutExecutor(
+ * new UdpTransportExecutor($loop),
+ * 3.0,
+ * $loop
+ * )
+ * )
+ * );
+ * ```
+ *
+ * > Internally, this class uses PHP's UDP sockets and does not take advantage
+ * of [react/datagram](https://github.com/reactphp/datagram) purely for
+ * organizational reasons to avoid a cyclic dependency between the two
+ * packages. Higher-level components should take advantage of the Datagram
+ * component instead of reimplementing this socket logic from scratch.
+ */
+class UdpTransportExecutor implements ExecutorInterface
+{
+ private $loop;
+ private $parser;
+ private $dumper;
+
+ /**
+ * @param LoopInterface $loop
+ * @param null|Parser $parser optional/advanced: DNS protocol parser to use
+ * @param null|BinaryDumper $dumper optional/advanced: DNS protocol dumper to use
+ */
+ public function __construct(LoopInterface $loop, Parser $parser = null, BinaryDumper $dumper = null)
+ {
+ if ($parser === null) {
+ $parser = new Parser();
+ }
+ if ($dumper === null) {
+ $dumper = new BinaryDumper();
+ }
+
+ $this->loop = $loop;
+ $this->parser = $parser;
+ $this->dumper = $dumper;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ if (isset($queryData[512])) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->name . ' failed: Query too large for UDP transport'
+ ));
+ }
+
+ // UDP connections are instant, so try connection without a loop or timeout
+ $socket = @\stream_socket_client("udp://$nameserver", $errno, $errstr, 0);
+ if ($socket === false) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->name . ' failed: Unable to connect to DNS server (' . $errstr . ')',
+ $errno
+ ));
+ }
+
+ // set socket to non-blocking and immediately try to send (fill write buffer)
+ \stream_set_blocking($socket, false);
+ \fwrite($socket, $queryData);
+
+ $loop = $this->loop;
+ $deferred = new Deferred(function () use ($loop, $socket, $query) {
+ // cancellation should remove socket from loop and close socket
+ $loop->removeReadStream($socket);
+ \fclose($socket);
+
+ throw new CancellationException('DNS query for ' . $query->name . ' has been cancelled');
+ });
+
+ $parser = $this->parser;
+ $loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request) {
+ // try to read a single data packet from the DNS server
+ // ignoring any errors, this is uses UDP packets and not a stream of data
+ $data = @\fread($socket, 512);
+
+ try {
+ $response = $parser->parseMessage($data);
+ } catch (\Exception $e) {
+ // ignore and await next if we received an invalid message from remote server
+ // this may as well be a fake response from an attacker (possible DOS)
+ return;
+ }
+
+ // ignore and await next if we received an unexpected response ID
+ // this may as well be a fake response from an attacker (possible cache poisoning)
+ if ($response->getId() !== $request->getId()) {
+ return;
+ }
+
+ // we only react to the first valid message, so remove socket from loop and close
+ $loop->removeReadStream($socket);
+ \fclose($socket);
+
+ if ($response->header->isTruncated()) {
+ $deferred->reject(new \RuntimeException('DNS query for ' . $query->name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+
+ return $deferred->promise();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/RecordNotFoundException.php b/src/mpg25-instagram-api/vendor/react/dns/src/RecordNotFoundException.php
new file mode 100755
index 0000000..0028413
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/RecordNotFoundException.php
@@ -0,0 +1,7 @@
+addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($loop));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null)
+ {
+ // default to keeping maximum of 256 responses in cache unless explicitly given
+ if (!($cache instanceof CacheInterface)) {
+ $cache = new ArrayCache(256);
+ }
+
+ $nameserver = $this->addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($loop, $cache));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ /**
+ * Tries to load the hosts file and decorates the given executor on success
+ *
+ * @param ExecutorInterface $executor
+ * @return ExecutorInterface
+ * @codeCoverageIgnore
+ */
+ private function decorateHostsFileExecutor(ExecutorInterface $executor)
+ {
+ try {
+ $executor = new HostsFileExecutor(
+ HostsFile::loadFromPathBlocking(),
+ $executor
+ );
+ } catch (\RuntimeException $e) {
+ // ignore this file if it can not be loaded
+ }
+
+ // Windows does not store localhost in hosts file by default but handles this internally
+ // To compensate for this, we explicitly use hard-coded defaults for localhost
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $executor = new HostsFileExecutor(
+ new HostsFile("127.0.0.1 localhost\n::1 localhost"),
+ $executor
+ );
+ }
+
+ return $executor;
+ }
+
+ protected function createExecutor(LoopInterface $loop)
+ {
+ return new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 5.0,
+ $loop
+ );
+ }
+
+ protected function createRetryExecutor(LoopInterface $loop)
+ {
+ return new CoopExecutor(new RetryExecutor($this->createExecutor($loop)));
+ }
+
+ protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache)
+ {
+ return new CachingExecutor($this->createRetryExecutor($loop), $cache);
+ }
+
+ protected function addPortToServerIfMissing($nameserver)
+ {
+ if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) {
+ // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
+ $nameserver = '[' . $nameserver . ']';
+ }
+ // assume a dummy scheme when checking for the port, otherwise parse_url() fails
+ if (parse_url('dummy://' . $nameserver, PHP_URL_PORT) === null) {
+ $nameserver .= ':53';
+ }
+
+ return $nameserver;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/src/Resolver/Resolver.php b/src/mpg25-instagram-api/vendor/react/dns/src/Resolver/Resolver.php
new file mode 100755
index 0000000..8690972
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/src/Resolver/Resolver.php
@@ -0,0 +1,250 @@
+nameserver = $nameserver;
+ $this->executor = $executor;
+ }
+
+ /**
+ * Resolves the given $domain name to a single IPv4 address (type `A` query).
+ *
+ * ```php
+ * $resolver->resolve('reactphp.org')->then(function ($ip) {
+ * echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
+ * });
+ * ```
+ *
+ * This is one of the main methods in this package. It sends a DNS query
+ * for the given $domain name to your DNS server and returns a single IP
+ * address on success.
+ *
+ * If the DNS server sends a DNS response message that contains more than
+ * one IP address for this query, it will randomly pick one of the IP
+ * addresses from the response. If you want the full list of IP addresses
+ * or want to send a different type of query, you should use the
+ * [`resolveAll()`](#resolveall) method instead.
+ *
+ * If the DNS server sends a DNS response message that indicates an error
+ * code, this method will reject with a `RecordNotFoundException`. Its
+ * message and code can be used to check for the response code.
+ *
+ * If the DNS communication fails and the server does not respond with a
+ * valid response message, this message will reject with an `Exception`.
+ *
+ * Pending DNS queries can be cancelled by cancelling its pending promise like so:
+ *
+ * ```php
+ * $promise = $resolver->resolve('reactphp.org');
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $domain
+ * @return PromiseInterface Returns a promise which resolves with a single IP address on success or
+ * rejects with an Exception on error.
+ */
+ public function resolve($domain)
+ {
+ return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
+ return $ips[array_rand($ips)];
+ });
+ }
+
+ /**
+ * Resolves all record values for the given $domain name and query $type.
+ *
+ * ```php
+ * $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
+ * echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ * });
+ *
+ * $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ * echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ * });
+ * ```
+ *
+ * This is one of the main methods in this package. It sends a DNS query
+ * for the given $domain name to your DNS server and returns a list with all
+ * record values on success.
+ *
+ * If the DNS server sends a DNS response message that contains one or more
+ * records for this query, it will return a list with all record values
+ * from the response. You can use the `Message::TYPE_*` constants to control
+ * which type of query will be sent. Note that this method always returns a
+ * list of record values, but each record value type depends on the query
+ * type. For example, it returns the IPv4 addresses for type `A` queries,
+ * the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
+ * `CNAME` and `PTR` queries and structured data for other queries. See also
+ * the `Record` documentation for more details.
+ *
+ * If the DNS server sends a DNS response message that indicates an error
+ * code, this method will reject with a `RecordNotFoundException`. Its
+ * message and code can be used to check for the response code.
+ *
+ * If the DNS communication fails and the server does not respond with a
+ * valid response message, this message will reject with an `Exception`.
+ *
+ * Pending DNS queries can be cancelled by cancelling its pending promise like so:
+ *
+ * ```php
+ * $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $domain
+ * @return PromiseInterface Returns a promise which resolves with all record values on success or
+ * rejects with an Exception on error.
+ */
+ public function resolveAll($domain, $type)
+ {
+ $query = new Query($domain, $type, Message::CLASS_IN);
+ $that = $this;
+
+ return $this->executor->query(
+ $this->nameserver,
+ $query
+ )->then(function (Message $response) use ($query, $that) {
+ return $that->extractValues($query, $response);
+ });
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function extractAddress(Query $query, Message $response)
+ {
+ $addresses = $this->extractValues($query, $response);
+
+ return $addresses[array_rand($addresses)];
+ }
+
+ /**
+ * [Internal] extract all resource record values from response for this query
+ *
+ * @param Query $query
+ * @param Message $response
+ * @return array
+ * @throws RecordNotFoundException when response indicates an error or contains no data
+ * @internal
+ */
+ public function extractValues(Query $query, Message $response)
+ {
+ // reject if response code indicates this is an error response message
+ $code = $response->getResponseCode();
+ if ($code !== Message::RCODE_OK) {
+ switch ($code) {
+ case Message::RCODE_FORMAT_ERROR:
+ $message = 'Format Error';
+ break;
+ case Message::RCODE_SERVER_FAILURE:
+ $message = 'Server Failure';
+ break;
+ case Message::RCODE_NAME_ERROR:
+ $message = 'Non-Existent Domain / NXDOMAIN';
+ break;
+ case Message::RCODE_NOT_IMPLEMENTED:
+ $message = 'Not Implemented';
+ break;
+ case Message::RCODE_REFUSED:
+ $message = 'Refused';
+ break;
+ default:
+ $message = 'Unknown error response code ' . $code;
+ }
+ throw new RecordNotFoundException(
+ 'DNS query for ' . $query->name . ' returned an error response (' . $message . ')',
+ $code
+ );
+ }
+
+ $answers = $response->answers;
+ $addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
+
+ // reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
+ if (0 === count($addresses)) {
+ throw new RecordNotFoundException(
+ 'DNS query for ' . $query->name . ' did not return a valid answer (NOERROR / NODATA)'
+ );
+ }
+
+ return array_values($addresses);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function resolveAliases(array $answers, $name)
+ {
+ return $this->valuesByNameAndType($answers, $name, Message::TYPE_A);
+ }
+
+ /**
+ * @param \React\Dns\Model\Record[] $answers
+ * @param string $name
+ * @param int $type
+ * @return array
+ */
+ private function valuesByNameAndType(array $answers, $name, $type)
+ {
+ // return all record values for this name and type (if any)
+ $named = $this->filterByName($answers, $name);
+ $records = $this->filterByType($named, $type);
+ if ($records) {
+ return $this->mapRecordData($records);
+ }
+
+ // no matching records found? check if there are any matching CNAMEs instead
+ $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
+ if ($cnameRecords) {
+ $cnames = $this->mapRecordData($cnameRecords);
+ foreach ($cnames as $cname) {
+ $records = array_merge(
+ $records,
+ $this->valuesByNameAndType($answers, $cname, $type)
+ );
+ }
+ }
+
+ return $records;
+ }
+
+ private function filterByName(array $answers, $name)
+ {
+ return $this->filterByField($answers, 'name', $name);
+ }
+
+ private function filterByType(array $answers, $type)
+ {
+ return $this->filterByField($answers, 'type', $type);
+ }
+
+ private function filterByField(array $answers, $field, $value)
+ {
+ $value = strtolower($value);
+ return array_filter($answers, function ($answer) use ($field, $value) {
+ return $value === strtolower($answer->$field);
+ });
+ }
+
+ private function mapRecordData(array $records)
+ {
+ return array_map(function ($record) {
+ return $record->data;
+ }, $records);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/CallableStub.php b/src/mpg25-instagram-api/vendor/react/dns/tests/CallableStub.php
new file mode 100755
index 0000000..a34a263
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/CallableStub.php
@@ -0,0 +1,10 @@
+assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsDefaultPath()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Not supported on Windows');
+ }
+
+ $config = Config::loadResolvConfBlocking();
+
+ $this->assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsFromExplicitPath()
+ {
+ $config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf');
+
+ $this->assertEquals(array('8.8.8.8'), $config->nameservers);
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testLoadThrowsWhenPathIsInvalid()
+ {
+ Config::loadResolvConfBlocking(__DIR__ . '/invalid.conf');
+ }
+
+ public function testParsesSingleEntryFile()
+ {
+ $contents = 'nameserver 8.8.8.8';
+ $expected = array('8.8.8.8');
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesNameserverEntriesFromAverageFileCorrectly()
+ {
+ $contents = '#
+# Mac OS X Notice
+#
+# This file is not used by the host name and address resolution
+# or the DNS query routing mechanisms used by most processes on
+# this Mac OS X system.
+#
+# This file is automatically generated.
+#
+domain v.cablecom.net
+nameserver 127.0.0.1
+nameserver ::1
+';
+ $expected = array('127.0.0.1', '::1');
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesEmptyFileWithoutNameserverEntries()
+ {
+ $contents = '';
+ $expected = array();
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,');
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries()
+ {
+ $contents = '
+# nameserver 1.2.3.4
+; nameserver 2.3.4.5
+
+nameserver 3.4.5.6 # nope
+nameserver 4.5.6.7 5.6.7.8
+ nameserver 6.7.8.9
+NameServer 7.8.9.10
+';
+ $expected = array();
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsFromWmicOnWindows()
+ {
+ if (DIRECTORY_SEPARATOR !== '\\') {
+ $this->markTestSkipped('Only on Windows');
+ }
+
+ $config = Config::loadWmicBlocking();
+
+ $this->assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsSingleEntryFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1}
+ACE,
+';
+ $expected = array('192.168.2.1');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsEmptyListFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+';
+ $expected = array();
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsSingleEntryForMultipleNicsFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1}
+ACE,
+ACE,{192.168.2.2}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1;192.168.2.2}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{"192.168.2.1","192.168.2.2"}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ private function echoCommand($output)
+ {
+ return 'echo ' . escapeshellarg($output);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Config/FilesystemFactoryTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Config/FilesystemFactoryTest.php
new file mode 100755
index 0000000..bb9eac7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Config/FilesystemFactoryTest.php
@@ -0,0 +1,70 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $factory = new FilesystemFactory($loop);
+ $factory->parseEtcResolvConf($contents)->then(function ($config) use (&$capturedConfig) {
+ $capturedConfig = $config;
+ });
+
+ $this->assertNotNull($capturedConfig);
+ $this->assertSame($expected, $capturedConfig->nameservers);
+ }
+
+ /** @test */
+ public function createShouldLoadStuffFromFilesystem()
+ {
+ $this->markTestIncomplete('Filesystem API is incomplete');
+
+ $expected = array('8.8.8.8');
+
+ $triggerListener = null;
+ $capturedConfig = null;
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop
+ ->expects($this->once())
+ ->method('addReadStream')
+ ->will($this->returnCallback(function ($stream, $listener) use (&$triggerListener) {
+ $triggerListener = function () use ($stream, $listener) {
+ call_user_func($listener, $stream);
+ };
+ }));
+
+ $factory = new FilesystemFactory($loop);
+ $factory->create(__DIR__.'/../Fixtures/etc/resolv.conf')->then(function ($config) use (&$capturedConfig) {
+ $capturedConfig = $config;
+ });
+
+ $triggerListener();
+
+ $this->assertNotNull($capturedConfig);
+ $this->assertSame($expected, $capturedConfig->nameservers);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Config/HostsFileTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Config/HostsFileTest.php
new file mode 100755
index 0000000..ff74ad2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Config/HostsFileTest.php
@@ -0,0 +1,170 @@
+assertInstanceOf('React\Dns\Config\HostsFile', $hosts);
+ }
+
+ public function testDefaultShouldHaveLocalhostMapped()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Not supported on Windows');
+ }
+
+ $hosts = HostsFile::loadFromPathBlocking();
+
+ $this->assertContains('127.0.0.1', $hosts->getIpsForHost('localhost'));
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testLoadThrowsForInvalidPath()
+ {
+ HostsFile::loadFromPathBlocking('does/not/exist');
+ }
+
+ public function testContainsSingleLocalhostEntry()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testNonIpReturnsNothingForInvalidHosts()
+ {
+ $hosts = new HostsFile('a b');
+
+ $this->assertEquals(array(), $hosts->getIpsForHost('a'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('b'));
+ }
+
+ public function testIgnoresIpv6ZoneId()
+ {
+ $hosts = new HostsFile('fe80::1%lo0 localhost');
+
+ $this->assertEquals(array('fe80::1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testSkipsComments()
+ {
+ $hosts = new HostsFile('# start' . PHP_EOL .'#127.0.0.1 localhost' . PHP_EOL . '127.0.0.2 localhost # example.com');
+
+ $this->assertEquals(array('127.0.0.2'), $hosts->getIpsForHost('localhost'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testContainsSingleLocalhostEntryWithCaseIgnored()
+ {
+ $hosts = new HostsFile('127.0.0.1 LocalHost');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('LOCALHOST'));
+ }
+
+ public function testEmptyFileContainsNothing()
+ {
+ $hosts = new HostsFile('');
+
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testSingleEntryWithMultipleNames()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost example.com');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('example.com'));
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testMergesEntriesOverMultipleLines()
+ {
+ $hosts = new HostsFile("127.0.0.1 localhost\n127.0.0.2 localhost\n127.0.0.3 a localhost b\n127.0.0.4 a localhost");
+
+ $this->assertEquals(array('127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testMergesIpv4AndIpv6EntriesOverMultipleLines()
+ {
+ $hosts = new HostsFile("127.0.0.1 localhost\n::1 localhost");
+
+ $this->assertEquals(array('127.0.0.1', '::1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testReverseLookup()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('192.168.1.1'));
+ }
+
+ public function testReverseSkipsComments()
+ {
+ $hosts = new HostsFile("# start\n#127.0.0.1 localhosted\n127.0.0.2\tlocalhost\t# example.com\n\t127.0.0.3\t\texample.org\t\t");
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1'));
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.2'));
+ $this->assertEquals(array('example.org'), $hosts->getHostsForIp('127.0.0.3'));
+ }
+
+ public function testReverseNonIpReturnsNothing()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('localhost'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1.1'));
+ }
+
+ public function testReverseNonIpReturnsNothingForInvalidHosts()
+ {
+ $hosts = new HostsFile('a b');
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('a'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('b'));
+ }
+
+ public function testReverseLookupReturnsLowerCaseHost()
+ {
+ $hosts = new HostsFile('127.0.0.1 LocalHost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
+ }
+
+ public function testReverseLookupChecksNormalizedIpv6()
+ {
+ $hosts = new HostsFile('FE80::00a1 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::A1'));
+ }
+
+ public function testReverseLookupIgnoresIpv6ZoneId()
+ {
+ $hosts = new HostsFile('fe80::1%lo0 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::1'));
+ }
+
+ public function testReverseLookupReturnsMultipleHostsOverSingleLine()
+ {
+ $hosts = new HostsFile("::1 ip6-localhost ip6-loopback");
+
+ $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
+ }
+
+ public function testReverseLookupReturnsMultipleHostsOverMultipleLines()
+ {
+ $hosts = new HostsFile("::1 ip6-localhost\n::1 ip6-loopback");
+
+ $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Fixtures/etc/resolv.conf b/src/mpg25-instagram-api/vendor/react/dns/tests/Fixtures/etc/resolv.conf
new file mode 100755
index 0000000..cae093a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Fixtures/etc/resolv.conf
@@ -0,0 +1 @@
+nameserver 8.8.8.8
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/FunctionalResolverTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/FunctionalResolverTest.php
new file mode 100755
index 0000000..a52a3be
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/FunctionalResolverTest.php
@@ -0,0 +1,171 @@
+loop = LoopFactory::create();
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('8.8.8.8', $this->loop);
+ }
+
+ public function testResolveLocalhostResolves()
+ {
+ $promise = $this->resolver->resolve('localhost');
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ public function testResolveAllLocalhostResolvesWithArray()
+ {
+ $promise = $this->resolver->resolveAll('localhost', Message::TYPE_A);
+ $promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveGoogleResolves()
+ {
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveAllGoogleMxResolvesWithCache()
+ {
+ $factory = new Factory();
+ $this->resolver = $factory->createCached('8.8.8.8', $this->loop);
+
+ $promise = $this->resolver->resolveAll('google.com', Message::TYPE_MX);
+ $promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveInvalidRejects()
+ {
+ $ex = $this->callback(function ($param) {
+ return ($param instanceof RecordNotFoundException && $param->getCode() === Message::RCODE_NAME_ERROR);
+ });
+
+ $promise = $this->resolver->resolve('example.invalid');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
+
+ $this->loop->run();
+ }
+
+ public function testResolveCancelledRejectsImmediately()
+ {
+ $ex = $this->callback(function ($param) {
+ return ($param instanceof \RuntimeException && $param->getMessage() === 'DNS query for google.com has been cancelled');
+ });
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
+ $promise->cancel();
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.1, $time);
+ }
+
+ public function testInvalidResolverDoesNotResolveGoogle()
+ {
+ $factory = new Factory();
+ $this->resolver = $factory->create('255.255.255.255', $this->loop);
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('255.255.255.255', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->createCached('255.255.255.255', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancelResolveShouldNotCauseGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('127.0.0.1', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancelResolveCachedShouldNotCauseGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->createCached('127.0.0.1', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Model/MessageTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Model/MessageTest.php
new file mode 100755
index 0000000..cf3d890
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Model/MessageTest.php
@@ -0,0 +1,31 @@
+assertTrue($request->header->isQuery());
+ $this->assertSame(1, $request->header->get('rd'));
+ }
+
+ public function testCreateResponseWithNoAnswers()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $answers = array();
+ $request = Message::createResponseWithAnswersForQuery($query, $answers);
+
+ $this->assertFalse($request->header->isQuery());
+ $this->assertTrue($request->header->isResponse());
+ $this->assertEquals(0, $request->header->get('anCount'));
+ $this->assertEquals(Message::RCODE_OK, $request->getResponseCode());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Protocol/BinaryDumperTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Protocol/BinaryDumperTest.php
new file mode 100755
index 0000000..ee94030
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Protocol/BinaryDumperTest.php
@@ -0,0 +1,278 @@
+formatHexDump($data);
+
+ $request = new Message();
+ $request->header->set('id', 0x7262);
+ $request->header->set('rd', 1);
+
+ $request->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN,
+ );
+
+ $request->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($request);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryRequestMessageWithCustomOptForEdns0()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "00"; // additional: (empty hostname)
+ $data .= "00 29 03 e8 00 00 00 00 00 00 "; // additional: type OPT, class UDP size, TTL 0, no RDATA
+
+ $expected = $this->formatHexDump($data);
+
+ $request = new Message();
+ $request->header->set('id', 0x7262);
+ $request->header->set('rd', 1);
+
+ $request->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN,
+ );
+
+ $request->additional[] = new Record('', 41, 1000, 0, '');
+
+ $request->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($request);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryResponseMessageWithoutRecords()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithSRVRecord()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 21 00 01"; // question: type SRV, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0c"; // answer: rdlength 12
+ $data .= "00 0a 00 14 1f 90 04 74 65 73 74 00"; // answer: rdata priority 10, weight 20, port 8080 test
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_SRV,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_SRV, Message::CLASS_IN, 86400, array(
+ 'priority' => 10,
+ 'weight' => 20,
+ 'port' => 8080,
+ 'target' => 'test'
+ ));
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithSOARecord()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 06 00 01"; // question: type SOA, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 06 00 01"; // answer: type SOA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 27"; // answer: rdlength 39
+ $data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
+ $data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
+ $data .= "78 49 28 d5 00 00 2a 30 00 00 0e 10"; // answer: rdata 2018060501, 10800, 3600
+ $data .= "00 09 3e 68 00 00 0e 10"; // answer: 605800, 3600
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_SOA,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_SOA, Message::CLASS_IN, 86400, array(
+ 'mname' => 'ns.hello',
+ 'rname' => 'e.hello',
+ 'serial' => 2018060501,
+ 'refresh' => 10800,
+ 'retry' => 3600,
+ 'expire' => 605800,
+ 'minimum' => 3600
+ ));
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithMultipleAnswerRecords()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 04 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 ff 00 01"; // question: type ANY, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01 00 00 00 00 00 04"; // answer: type A, class IN, TTL 0, 4 bytes
+ $data .= "7f 00 00 01"; // answer: 127.0.0.1
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 1c 00 01 00 00 00 00 00 10"; // question: type AAAA, class IN, TTL 0, 16 bytes
+ $data .= "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01"; // answer: ::1
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01 00 00 00 00 00 0c"; // answer: type TXT, class IN, TTL 0, 12 bytes
+ $data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: hello, world
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01 00 00 00 00 00 03"; // anwser: type MX, class IN, TTL 0, 3 bytes
+ $data .= "00 00 00"; // priority 0, no target
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_ANY,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
+ $response->answers[] = new Record('igor.io', Message::TYPE_AAAA, Message::CLASS_IN, 0, '::1');
+ $response->answers[] = new Record('igor.io', Message::TYPE_TXT, Message::CLASS_IN, 0, array('hello', 'world'));
+ $response->answers[] = new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 0, array('priority' => 0, 'target' => ''));
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithAnswerAndAdditionalRecord()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 01 00 00 00 01"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 02 00 01"; // question: type NS, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 02 00 01 00 00 00 00 00 0d"; // answer: type NS, class IN, TTL 0, 10 bytes
+ $data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // answer: example.com
+ $data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // additional: example.com
+ $data .= "00 01 00 01 00 00 00 00 00 04"; // additional: type A, class IN, TTL 0, 4 bytes
+ $data .= "7f 00 00 01"; // additional: 127.0.0.1
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_NS,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_NS, Message::CLASS_IN, 0, 'example.com');
+ $response->additional[] = new Record('example.com', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ private function convertBinaryToHexDump($input)
+ {
+ return $this->formatHexDump(implode('', unpack('H*', $input)));
+ }
+
+ private function formatHexDump($input)
+ {
+ return implode(' ', str_split(str_replace(' ', '', $input), 2));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Protocol/ParserTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Protocol/ParserTest.php
new file mode 100755
index 0000000..4086626
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Protocol/ParserTest.php
@@ -0,0 +1,1033 @@
+parser = new Parser();
+ }
+
+ /**
+ * @dataProvider provideConvertTcpDumpToBinary
+ */
+ public function testConvertTcpDumpToBinary($expected, $data)
+ {
+ $this->assertSame($expected, $this->convertTcpDumpToBinary($data));
+ }
+
+ public function provideConvertTcpDumpToBinary()
+ {
+ return array(
+ array(chr(0x72).chr(0x62), "72 62"),
+ array(chr(0x72).chr(0x62).chr(0x01).chr(0x00), "72 62 01 00"),
+ array(chr(0x72).chr(0x62).chr(0x01).chr(0x00).chr(0x00).chr(0x01), "72 62 01 00 00 01"),
+ array(chr(0x01).chr(0x00).chr(0x01), "01 00 01"),
+ );
+ }
+
+ public function testParseRequest()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $request = $this->parser->parseMessage($data);
+
+ $header = $request->header;
+ $this->assertSame(0x7262, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(0, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(0, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(0, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $request->questions);
+ $this->assertSame('igor.io', $request->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']);
+ }
+
+ public function testParseResponse()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0x7262, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('igor.io', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseQuestionWithTwoQuestions()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "03 77 77 77 04 69 67 6f 72 02 69 6f 00"; // question: www.igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $request = new Message();
+ $request->header->set('qdCount', 2);
+ $request->data = $data;
+
+ $this->parser->parseQuestion($request);
+
+ $this->assertCount(2, $request->questions);
+ $this->assertSame('igor.io', $request->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']);
+ $this->assertSame('www.igor.io', $request->questions[1]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[1]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[1]['class']);
+ }
+
+ public function testParseAnswerWithInlineData()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithExcessiveTtlReturnsZeroTtl()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "ff ff ff ff"; // answer: ttl 2^32 - 1
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(0, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithTtlExactlyBoundaryReturnsZeroTtl()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "80 00 00 00"; // answer: ttl 2^31
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(0, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithMaximumTtlReturnsExactTtl()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "7f ff ff ff"; // answer: ttl 2^31 - 1
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(0x7fffffff, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithUnknownType()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "23 28 00 01"; // answer: type 9000, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 05"; // answer: rdlength 5
+ $data .= "68 65 6c 6c 6f"; // answer: rdata "hello"
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(9000, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('hello', $response->answers[0]->data);
+ }
+
+ public function testParseResponseWithCnameAndOffsetPointers()
+ {
+ $data = "";
+ $data .= "9e 8d 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 6d 61 69 6c 06 67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: mail.google.com
+ $data .= "00 05 00 01"; // question: type CNAME, class IN
+ $data .= "c0 0c"; // answer: offset pointer to mail.google.com
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 a8 9c"; // answer: ttl 43164
+ $data .= "00 0f"; // answer: rdlength 15
+ $data .= "0a 67 6f 6f 67 6c 65 6d 61 69 6c 01 6c"; // answer: rdata googlemail.l.
+ $data .= "c0 11"; // answer: rdata offset pointer to google.com
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('mail.google.com', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_CNAME, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('mail.google.com', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(43164, $response->answers[0]->ttl);
+ $this->assertSame('googlemail.l.google.com', $response->answers[0]->data);
+ }
+
+ public function testParseAAAAResponse()
+ {
+ $data = "";
+ $data .= "cd 72 81 80 00 01 00 01 00 00 00 00 06"; // header
+ $data .= "67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: google.com
+ $data .= "00 1c 00 01"; // question: type AAAA, class IN
+ $data .= "c0 0c"; // answer: offset pointer to google.com
+ $data .= "00 1c 00 01"; // answer: type AAAA, class IN
+ $data .= "00 00 01 2b"; // answer: ttl 299
+ $data .= "00 10"; // answer: rdlength 16
+ $data .= "2a 00 14 50 40 09 08 09 00 00 00 00 00 00 20 0e"; // answer: 2a00:1450:4009:809::200e
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0xcd72, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('google.com', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_AAAA, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('google.com', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_AAAA, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(299, $response->answers[0]->ttl);
+ $this->assertSame('2a00:1450:4009:809::200e', $response->answers[0]->data);
+ }
+
+ public function testParseTXTResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01"; // answer: type TXT, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 06"; // answer: rdlength 6
+ $data .= "05 68 65 6c 6c 6f"; // answer: rdata length 5: hello
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_TXT, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(array('hello'), $response->answers[0]->data);
+ }
+
+ public function testParseTXTResponseMultiple()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01"; // answer: type TXT, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0C"; // answer: rdlength 12
+ $data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: rdata length 5: hello, length 5: world
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_TXT, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(array('hello', 'world'), $response->answers[0]->data);
+ }
+
+ public function testParseMXResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01"; // answer: type MX, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 09"; // answer: rdlength 9
+ $data .= "00 0a 05 68 65 6c 6c 6f 00"; // answer: rdata priority 10: hello
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_MX, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(array('priority' => 10, 'target' => 'hello'), $response->answers[0]->data);
+ }
+
+ public function testParseSRVResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0C"; // answer: rdlength 12
+ $data .= "00 0a 00 14 1F 90 04 74 65 73 74 00"; // answer: rdata priority 10, weight 20, port 8080 test
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_SRV, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(
+ array(
+ 'priority' => 10,
+ 'weight' => 20,
+ 'port' => 8080,
+ 'target' => 'test'
+ ),
+ $response->answers[0]->data
+ );
+ }
+
+ public function testParseMessageResponseWithTwoAnswers()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 02 00 00 00 00"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 00 29"; // answer: ttl 41
+ $data .= "00 0e"; // answer: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // answer: rdata whois.nic.io
+ $data .= "c0 32"; // answer: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 0d f7"; // answer: ttl 3575
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "c1 df 4e 98"; // answer: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(2, $response->answers);
+
+ $this->assertSame('io.whois-servers.net', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(41, $response->answers[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->answers[0]->data);
+
+ $this->assertSame('whois.nic.io', $response->answers[1]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[1]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[1]->class);
+ $this->assertSame(3575, $response->answers[1]->ttl);
+ $this->assertSame('193.223.78.152', $response->answers[1]->data);
+ }
+
+ public function testParseMessageResponseWithTwoAuthorityRecords()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 00 00 02 00 00"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // authority: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // authority: type CNAME, class IN
+ $data .= "00 00 00 29"; // authority: ttl 41
+ $data .= "00 0e"; // authority: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // authority: rdata whois.nic.io
+ $data .= "c0 32"; // authority: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // authority: type CNAME, class IN
+ $data .= "00 00 0d f7"; // authority: ttl 3575
+ $data .= "00 04"; // authority: rdlength 4
+ $data .= "c1 df 4e 98"; // authority: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(0, $response->answers);
+
+ $this->assertCount(2, $response->authority);
+
+ $this->assertSame('io.whois-servers.net', $response->authority[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->authority[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->authority[0]->class);
+ $this->assertSame(41, $response->authority[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->authority[0]->data);
+
+ $this->assertSame('whois.nic.io', $response->authority[1]->name);
+ $this->assertSame(Message::TYPE_A, $response->authority[1]->type);
+ $this->assertSame(Message::CLASS_IN, $response->authority[1]->class);
+ $this->assertSame(3575, $response->authority[1]->ttl);
+ $this->assertSame('193.223.78.152', $response->authority[1]->data);
+ }
+
+ public function testParseMessageResponseWithAnswerAndAdditionalRecord()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 01 00 00 00 01"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 00 29"; // answer: ttl 41
+ $data .= "00 0e"; // answer: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // answer: rdata whois.nic.io
+ $data .= "c0 32"; // additional: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // additional: type CNAME, class IN
+ $data .= "00 00 0d f7"; // additional: ttl 3575
+ $data .= "00 04"; // additional: rdlength 4
+ $data .= "c1 df 4e 98"; // additional: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+
+ $this->assertSame('io.whois-servers.net', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(41, $response->answers[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->answers[0]->data);
+
+ $this->assertCount(0, $response->authority);
+ $this->assertCount(1, $response->additional);
+
+ $this->assertSame('whois.nic.io', $response->additional[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->additional[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->additional[0]->class);
+ $this->assertSame(3575, $response->additional[0]->ttl);
+ $this->assertSame('193.223.78.152', $response->additional[0]->data);
+ }
+
+ public function testParseNSResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 02 00 01"; // answer: type NS, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 07"; // answer: rdlength 7
+ $data .= "05 68 65 6c 6c 6f 00"; // answer: rdata hello
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_NS, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('hello', $response->answers[0]->data);
+ }
+
+ public function testParseSOAResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 06 00 01"; // answer: type SOA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 27"; // answer: rdlength 39
+ $data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
+ $data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
+ $data .= "78 49 28 D5 00 00 2a 30 00 00 0e 10"; // answer: rdata 2018060501, 10800, 3600
+ $data .= "00 09 3a 80 00 00 0e 10"; // answer: 605800, 3600
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_SOA, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(
+ array(
+ 'mname' => 'ns.hello',
+ 'rname' => 'e.hello',
+ 'serial' => 2018060501,
+ 'refresh' => 10800,
+ 'retry' => 3600,
+ 'expire' => 604800,
+ 'minimum' => 3600
+ ),
+ $response->answers[0]->data
+ );
+ }
+
+ public function testParsePTRResponse()
+ {
+ $data = "";
+ $data .= "5d d8 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "01 34 01 34 01 38 01 38 07 69 6e"; // question: 4.4.8.8.in-addr.arpa
+ $data .= "2d 61 64 64 72 04 61 72 70 61 00"; // question (continued)
+ $data .= "00 0c 00 01"; // question: type PTR, class IN
+ $data .= "c0 0c"; // answer: offset pointer to rdata
+ $data .= "00 0c 00 01"; // answer: type PTR, class IN
+ $data .= "00 01 51 7f"; // answer: ttl 86399
+ $data .= "00 20"; // answer: rdlength 32
+ $data .= "13 67 6f 6f 67 6c 65 2d 70 75 62 6c 69 63 2d 64"; // answer: rdata google-public-dns-b.google.com.
+ $data .= "6e 73 2d 62 06 67 6f 6f 67 6c 65 03 63 6f 6d 00";
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0x5dd8, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('4.4.8.8.in-addr.arpa', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_PTR, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('4.4.8.8.in-addr.arpa', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_PTR, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86399, $response->answers[0]->ttl);
+ $this->assertSame('google-public-dns-b.google.com', $response->answers[0]->data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteQuestionThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ //$data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteQuestionLabelThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67"; // question: ig …?
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72"; // question: igor. …?
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteOffsetPointerInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "ff"; // question: incomplete offset pointer
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseInvalidOffsetPointerInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "ff ff"; // question: offset pointer to invalid address
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseInvalidOffsetPointerToSameLabelInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "c0 0c"; // question: offset pointer to invalid address
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseInvalidOffsetPointerToStartOfMessageInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "c0 00"; // question: offset pointer to start of message
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteAnswerFieldsThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseMessageResponseWithIncompleteAuthorityRecordThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 00 00 01 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // authority: offset pointer to igor.io
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseMessageResponseWithIncompleteAdditionalRecordThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 00 00 00 00 01"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // additional: offset pointer to igor.io
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteAnswerRecordDataThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ public function testParseInvalidNSResponseWhereDomainNameIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 02 00 01"; // answer: type NS, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 00"; // answer: rdlength 0
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidAResponseWhereIPIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 00"; // answer: rdlength 0
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidAAAAResponseWhereIPIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 1c 00 01"; // answer: type AAAA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 00"; // answer: rdlength 0
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidTXTResponseWhereTxtChunkExceedsLimit()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01"; // answer: type TXT, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 06"; // answer: rdlength 6
+ $data .= "06 68 65 6c 6c 6f 6f"; // answer: rdata length 6: helloo
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidMXResponseWhereDomainNameIsIncomplete()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01"; // answer: type MX, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 08"; // answer: rdlength 8
+ $data .= "00 0a 05 68 65 6c 6c 6f"; // answer: rdata priority 10: hello (missing label end)
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidMXResponseWhereDomainNameIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01"; // answer: type MX, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 02"; // answer: rdlength 2
+ $data .= "00 0a"; // answer: rdata priority 10
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidSRVResponseWhereDomainNameIsIncomplete()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0b"; // answer: rdlength 11
+ $data .= "00 0a 00 14 1F 90 04 74 65 73 74"; // answer: rdata priority 10, weight 20, port 8080 test (missing label end)
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidSRVResponseWhereDomainNameIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 06"; // answer: rdlength 6
+ $data .= "00 0a 00 14 1F 90"; // answer: rdata priority 10, weight 20, port 8080
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidSOAResponseWhereFlagsAreMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 06 00 01"; // answer: type SOA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 13"; // answer: rdlength 19
+ $data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
+ $data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ private function convertTcpDumpToBinary($input)
+ {
+ // sudo ngrep -d en1 -x port 53
+
+ return pack('H*', str_replace(' ', '', $input));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/CachedExecutorTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/CachedExecutorTest.php
new file mode 100755
index 0000000..d08ed05
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/CachedExecutorTest.php
@@ -0,0 +1,100 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue($this->createPromiseMock()));
+
+ $cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cache
+ ->expects($this->once())
+ ->method('lookup')
+ ->will($this->returnValue(Promise\reject()));
+ $cachedExecutor = new CachedExecutor($executor, $cache);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $cachedExecutor->query('8.8.8.8', $query);
+ }
+
+ /**
+ * @covers React\Dns\Query\CachedExecutor
+ * @test
+ */
+ public function callingQueryTwiceShouldUseCachedResult()
+ {
+ $cachedRecords = array(new Record('igor.io', Message::TYPE_A, Message::CLASS_IN));
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->callQueryCallbackWithAddress('178.79.169.131'));
+
+ $cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cache
+ ->expects($this->at(0))
+ ->method('lookup')
+ ->with($this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue(Promise\reject()));
+ $cache
+ ->expects($this->at(1))
+ ->method('storeResponseMessage')
+ ->with($this->isType('integer'), $this->isInstanceOf('React\Dns\Model\Message'));
+ $cache
+ ->expects($this->at(2))
+ ->method('lookup')
+ ->with($this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue(Promise\resolve($cachedRecords)));
+
+ $cachedExecutor = new CachedExecutor($executor, $cache);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
+ $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
+ }
+
+ private function callQueryCallbackWithAddress($address)
+ {
+ return $this->returnCallback(function ($nameserver, $query) use ($address) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, $address);
+
+ return Promise\resolve($response);
+ });
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+
+ private function createPromiseMock()
+ {
+ return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/CachingExecutorTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/CachingExecutorTest.php
new file mode 100755
index 0000000..abd9342
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/CachingExecutorTest.php
@@ -0,0 +1,183 @@
+getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->never())->method('query');
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(new Promise(function () { }));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnPendingPromiseWhenCacheReturnsMissAndWillSendSameQueryToFallbackExecutor()
+ {
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn(new Promise(function () { }));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSendingQueryToFallbackExecutor()
+ {
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->never())->method('query');
+
+ $message = new Message();
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve($message));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithMinimumTtlFromRecord()
+ {
+ $message = new Message();
+ $message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3700, '127.0.0.1');
+ $message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '127.0.0.1');
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
+ $cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 3600);
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithDefaultTtl()
+ {
+ $message = new Message();
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
+ $cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 60);
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesWithTruncatedResponseButShouldNotSaveTruncatedMessageToCache()
+ {
+ $message = new Message();
+ $message->header->set('tc', 1);
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
+ $cache->expects($this->never())->method('set');
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnRejectedPromiseWhenCacheReturnsMissAndFallbackExecutorRejects()
+ {
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\reject(new \RuntimeException()));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
+ }
+
+ public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromCache()
+ {
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->never())->method('query');
+
+ $pending = new Promise(function () { }, $this->expectCallableOnce());
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($pending);
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
+ }
+
+ public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromFallbackExecutorWhenCacheReturnsMiss()
+ {
+ $pending = new Promise(function () { }, $this->expectCallableOnce());
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn($pending);
+
+ $deferred = new Deferred();
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($deferred->promise());
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+ $deferred->resolve(null);
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/CoopExecutorTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/CoopExecutorTest.php
new file mode 100755
index 0000000..d265de2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/CoopExecutorTest.php
@@ -0,0 +1,233 @@
+getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testQueryOnceWillResolveWhenBaseExecutorResolves()
+ {
+ $message = new Message();
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $connector->query('8.8.8.8', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $promise->then($this->expectCallableOnceWith($message));
+ }
+
+ public function testQueryOnceWillRejectWhenBaseExecutorRejects()
+ {
+ $exception = new RuntimeException();
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn(\React\Promise\reject($exception));
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $connector->query('8.8.8.8', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $promise->then(null, $this->expectCallableOnceWith($exception));
+ }
+
+ public function testQueryTwoDifferentQueriesWillPassExactQueryToBaseExecutorTwice()
+ {
+ $pending = new Promise(function () { });
+ $query1 = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $query2 = new Query('reactphp.org', Message::TYPE_AAAA, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->withConsecutive(
+ array('8.8.8.8', $query1),
+ array('8.8.8.8', $query2)
+ )->willReturn($pending);
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query1);
+ $connector->query('8.8.8.8', $query2);
+ }
+
+ public function testQueryTwiceWillPassExactQueryToBaseExecutorOnceWhenQueryIsStillPending()
+ {
+ $pending = new Promise(function () { });
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyResolved()
+ {
+ $deferred = new Deferred();
+ $pending = new Promise(function () { });
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
+
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+
+ $deferred->resolve(new Message());
+
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyRejected()
+ {
+ $deferred = new Deferred();
+ $pending = new Promise(function () { });
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
+
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+
+ $deferred->reject(new RuntimeException());
+
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testCancelQueryWillCancelPromiseFromBaseExecutorAndReject()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $connector->query('8.8.8.8', $query);
+
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testCancelOneQueryWhenOtherQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableNever());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise1->cancel();
+
+ $promise1->then(null, $this->expectCallableOnce());
+ $promise2->then(null, $this->expectCallableNever());
+ }
+
+ public function testCancelSecondQueryWhenFirstQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableNever());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise2->cancel();
+
+ $promise2->then(null, $this->expectCallableOnce());
+ $promise1->then(null, $this->expectCallableNever());
+ }
+
+ public function testCancelAllPendingQueriesWillCancelPromiseFromBaseExecutorAndRejectCancelled()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise1->cancel();
+ $promise2->cancel();
+
+ $promise1->then(null, $this->expectCallableOnce());
+ $promise2->then(null, $this->expectCallableOnce());
+ }
+
+ public function testQueryTwiceWillQueryBaseExecutorTwiceIfFirstQueryHasAlreadyBeenCancelledWhenSecondIsStarted()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+ $pending = new Promise(function () { });
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->willReturnOnConsecutiveCalls($promise, $pending);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise1->cancel();
+
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise1->then(null, $this->expectCallableOnce());
+
+ $promise2->then(null, $this->expectCallableNever());
+ }
+
+ public function testCancelQueryShouldNotCauseGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $deferred = new Deferred(function () {
+ throw new \RuntimeException();
+ });
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($deferred->promise());
+ $connector = new CoopExecutor($base);
+
+ gc_collect_cycles();
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $connector->query('8.8.8.8', $query);
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/ExecutorTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/ExecutorTest.php
new file mode 100755
index 0000000..0d7ac1d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/ExecutorTest.php
@@ -0,0 +1,308 @@
+loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->parser = $this->getMockBuilder('React\Dns\Protocol\Parser')->getMock();
+ $this->dumper = new BinaryDumper();
+
+ $this->executor = new Executor($this->loop, $this->parser, $this->dumper);
+ }
+
+ /** @test */
+ public function queryShouldCreateUdpRequest()
+ {
+ $timer = $this->createTimerMock();
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock(false));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldRejectIfRequestIsLargerThan512Bytes()
+ {
+ $query = new Query(str_repeat('a', 512).'.igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->setExpectedException('RuntimeException', 'DNS query for ' . $query->name . ' failed: Requested transport "tcp" not available, only UDP is supported in this version');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldCloseConnectionWhenCancelled()
+ {
+ $conn = $this->createConnectionMock(false);
+ $conn->expects($this->once())->method('close');
+
+ $timer = $this->createTimerMock();
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnValue($conn));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->cancel();
+
+ $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldNotStartOrCancelTimerWhenCancelledWithTimeoutIsNull()
+ {
+ $this->loop
+ ->expects($this->never())
+ ->method('addTimer');
+
+ $this->executor = new Executor($this->loop, $this->parser, $this->dumper, null);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('127.0.0.1:53', $query);
+
+ $promise->cancel();
+
+ $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldRejectIfResponseIsTruncated()
+ {
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->parser
+ ->expects($this->once())
+ ->method('parseMessage')
+ ->will($this->returnTruncatedResponse());
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock());
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldFailIfUdpThrow()
+ {
+ $this->loop
+ ->expects($this->never())
+ ->method('addTimer');
+
+ $this->parser
+ ->expects($this->never())
+ ->method('parseMessage');
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->throwException(new \Exception('Nope')));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->setExpectedException('RuntimeException', 'DNS query for igor.io failed: Nope');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldCancelTimerWhenFullResponseIsReceived()
+ {
+ $conn = $this->createConnectionMock();
+
+ $this->parser
+ ->expects($this->once())
+ ->method('parseMessage')
+ ->will($this->returnStandardResponse());
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->at(0))
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock());
+
+
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->once())
+ ->method('addTimer')
+ ->with(5, $this->isInstanceOf('Closure'))
+ ->will($this->returnValue($timer));
+
+ $this->loop
+ ->expects($this->once())
+ ->method('cancelTimer')
+ ->with($timer);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldCloseConnectionOnTimeout()
+ {
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->at(0))
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock(false));
+
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->never())
+ ->method('cancelTimer');
+
+ $this->loop
+ ->expects($this->once())
+ ->method('addTimer')
+ ->with(5, $this->isInstanceOf('Closure'))
+ ->will($this->returnCallback(function ($time, $callback) use (&$timerCallback, $timer) {
+ $timerCallback = $callback;
+ return $timer;
+ }));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->assertNotNull($timerCallback);
+ $timerCallback();
+
+ $this->setExpectedException('React\Dns\Query\TimeoutException', 'DNS query for igor.io timed out');
+ Block\await($promise, $this->loop);
+ }
+
+ private function returnStandardResponse()
+ {
+ $that = $this;
+ $callback = function ($data) use ($that) {
+ $response = new Message();
+ $that->convertMessageToStandardResponse($response);
+ return $response;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ private function returnTruncatedResponse()
+ {
+ $that = $this;
+ $callback = function ($data) use ($that) {
+ $response = new Message();
+ $that->convertMessageToTruncatedResponse($response);
+ return $response;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ public function convertMessageToStandardResponse(Message $response)
+ {
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->prepare();
+
+ return $response;
+ }
+
+ public function convertMessageToTruncatedResponse(Message $response)
+ {
+ $this->convertMessageToStandardResponse($response);
+ $response->header->set('tc', 1);
+ $response->prepare();
+
+ return $response;
+ }
+
+ private function returnNewConnectionMock($emitData = true)
+ {
+ $conn = $this->createConnectionMock($emitData);
+
+ $callback = function () use ($conn) {
+ return $conn;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ private function createConnectionMock($emitData = true)
+ {
+ $conn = $this->getMockBuilder('React\Stream\DuplexStreamInterface')->getMock();
+ $conn
+ ->expects($this->any())
+ ->method('on')
+ ->with('data', $this->isInstanceOf('Closure'))
+ ->will($this->returnCallback(function ($name, $callback) use ($emitData) {
+ $emitData && $callback(null);
+ }));
+
+ return $conn;
+ }
+
+ private function createTimerMock()
+ {
+ return $this->getMockBuilder(
+ interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface'
+ )->getMock();
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\Executor')
+ ->setConstructorArgs(array($this->loop, $this->parser, $this->dumper))
+ ->setMethods(array('createConnection'))
+ ->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/HostsFileExecutorTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/HostsFileExecutorTest.php
new file mode 100755
index 0000000..70d877e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/HostsFileExecutorTest.php
@@ -0,0 +1,126 @@
+hosts = $this->getMockBuilder('React\Dns\Config\HostsFile')->disableOriginalConstructor()->getMock();
+ $this->fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $this->executor = new HostsFileExecutor($this->hosts, $this->fallback);
+ }
+
+ public function testDoesNotTryToGetIpsForMxQuery()
+ {
+ $this->hosts->expects($this->never())->method('getIpsForHost');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_MX, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpsWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array());
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testReturnsResponseMessageIfIpsWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpv4Matches()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
+ $this->fallback->expects($this->once())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testReturnsResponseMessageIfIpv6AddressesWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpv6Matches()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
+ $this->fallback->expects($this->once())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
+ }
+
+ public function testDoesReturnReverseIpv4Lookup()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array('localhost'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoReverseIpv4Matches()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array());
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testDoesReturnReverseIpv6Lookup()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(array('ip6-localhost'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.e.f.3.0.0.e.2.0.2.0.a.2.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackForInvalidAddress()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidIpv4Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidLengthIpv6Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidHexIpv6Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('zZz.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/RecordBagTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/RecordBagTest.php
new file mode 100755
index 0000000..c0615be
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/RecordBagTest.php
@@ -0,0 +1,83 @@
+assertSame(array(), $recordBag->all());
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldSetTheValue()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600));
+
+ $records = $recordBag->all();
+ $this->assertCount(1, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_A, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldAcceptMxRecord()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 3600, array('priority' => 10, 'target' => 'igor.io')));
+
+ $records = $recordBag->all();
+ $this->assertCount(1, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_MX, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ $this->assertSame(array('priority' => 10, 'target' => 'igor.io'), $records[0]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldSetManyValues()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
+
+ $records = $recordBag->all();
+ $this->assertCount(2, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_A, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ $this->assertSame('178.79.169.131', $records[0]->data);
+ $this->assertSame('igor.io', $records[1]->name);
+ $this->assertSame(Message::TYPE_A, $records[1]->type);
+ $this->assertSame(Message::CLASS_IN, $records[1]->class);
+ $this->assertSame('178.79.169.132', $records[1]->data);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/RecordCacheTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/RecordCacheTest.php
new file mode 100755
index 0000000..01f0eee
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/RecordCacheTest.php
@@ -0,0 +1,160 @@
+getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+
+ $cache = new RecordCache($base);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordPendingCacheDoesNotSetCache()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $pending = new Promise(function () { });
+
+ $base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $base->expects($this->once())->method('get')->willReturn($pending);
+ $base->expects($this->never())->method('set');
+
+ $cache = new RecordCache($base);
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordOnCacheMissSetsCache()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+ $base->expects($this->once())->method('set')->with($this->isType('string'), $this->isType('string'));
+
+ $cache = new RecordCache($base);
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordShouldMakeLookupSucceed()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(1, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeTwoRecordsShouldReturnBoth()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(2, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ $this->assertSame('178.79.169.132', $cachedRecords[1]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeResponseMessageShouldStoreAllAnswerValues()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $response = new Message();
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132');
+ $response->prepare();
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeResponseMessage($query->currentTime, $response);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(2, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ $this->assertSame('178.79.169.132', $cachedRecords[1]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function expireShouldExpireDeadRecords()
+ {
+ $cachedTime = 1345656451;
+ $currentTime = $cachedTime + 3605;
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($cachedTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $cache->expire($currentTime);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, $currentTime);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
+ }
+
+ private function getPromiseValue(PromiseInterface $promise)
+ {
+ $capturedValue = null;
+
+ $promise->then(function ($value) use (&$capturedValue) {
+ $capturedValue = $value;
+ });
+
+ return $capturedValue;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/RetryExecutorTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/RetryExecutorTest.php
new file mode 100755
index 0000000..7e44a08
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/RetryExecutorTest.php
@@ -0,0 +1,350 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue($this->expectPromiseOnce()));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldRetryQueryOnTimeout()
+ {
+ $response = $this->createStandardResponse();
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(2))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->onConsecutiveCalls(
+ $this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new TimeoutException("timeout"));
+ }),
+ $this->returnCallback(function ($domain, $query) use ($response) {
+ return Promise\resolve($response);
+ })
+ ));
+
+ $callback = $this->createCallableMock();
+ $callback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('React\Dns\Model\Message'));
+
+ $errorback = $this->expectCallableNever();
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldStopRetryingAfterSomeAttempts()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(3))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new TimeoutException("timeout"));
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('RuntimeException'));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldForwardNonTimeoutErrors()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new \Exception);
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('Exception'));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldCancelQueryOnCancel()
+ {
+ $cancelled = 0;
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ })
+ );
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldCancelSecondQueryOnCancel()
+ {
+ $deferred = new Deferred();
+ $cancelled = 0;
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(2))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->onConsecutiveCalls(
+ $this->returnValue($deferred->promise()),
+ $this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ })
+ ));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ // first query will time out after a while and this sends the next query
+ $deferred->reject(new TimeoutException());
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnSuccess()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->willReturn(Promise\resolve($this->createStandardResponse()));
+
+ $retryExecutor = new RetryExecutor($executor, 0);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnTimeoutErrors()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->any())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->willReturn(Promise\reject(new TimeoutException("timeout")));
+
+ $retryExecutor = new RetryExecutor($executor, 0);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnCancellation()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $deferred = new Deferred(function () {
+ throw new \RuntimeException();
+ });
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->willReturn($deferred->promise());
+
+ $retryExecutor = new RetryExecutor($executor, 0);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnNonTimeoutErrors()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new \Exception);
+ }));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ protected function expectPromiseOnce($return = null)
+ {
+ $mock = $this->createPromiseMock();
+ $mock
+ ->expects($this->once())
+ ->method('then')
+ ->will($this->returnValue($return));
+
+ return $mock;
+ }
+
+ protected function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+
+ protected function createPromiseMock()
+ {
+ return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+ }
+
+ protected function createStandardResponse()
+ {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->prepare();
+
+ return $response;
+ }
+}
+
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/TimeoutExecutorTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/TimeoutExecutorTest.php
new file mode 100755
index 0000000..0d37fb4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/TimeoutExecutorTest.php
@@ -0,0 +1,115 @@
+loop = Factory::create();
+
+ $this->wrapped = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+
+ $this->executor = new TimeoutExecutor($this->wrapped, 5.0, $this->loop);
+ }
+
+ public function testCancellingPromiseWillCancelWrapped()
+ {
+ $cancelled = 0;
+
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ }));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testResolvesPromiseWhenWrappedResolves()
+ {
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->willReturn(Promise\resolve('0.0.0.0'));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+ }
+
+ public function testRejectsPromiseWhenWrappedRejects()
+ {
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->willReturn(Promise\reject(new \RuntimeException()));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException()));
+ }
+
+ public function testWrappedWillBeCancelledOnTimeout()
+ {
+ $this->executor = new TimeoutExecutor($this->wrapped, 0, $this->loop);
+
+ $cancelled = 0;
+
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->logicalAnd(
+ $this->isInstanceOf('React\Dns\Query\TimeoutException'),
+ $this->attribute($this->equalTo('DNS query for igor.io timed out'), 'message')
+ ));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query)->then($callback, $errorback);
+
+ $this->assertEquals(0, $cancelled);
+
+ $this->loop->run();
+
+ $this->assertEquals(1, $cancelled);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Query/UdpTransportExecutorTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/UdpTransportExecutorTest.php
new file mode 100755
index 0000000..f7222dc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Query/UdpTransportExecutorTest.php
@@ -0,0 +1,215 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addReadStream');
+
+ $dumper = $this->getMockBuilder('React\Dns\Protocol\BinaryDumper')->getMock();
+ $dumper->expects($this->once())->method('toBinary')->willReturn(str_repeat('.', 513));
+
+ $executor = new UdpTransportExecutor($loop, null, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $executor->query('8.8.8.8:53', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testQueryRejectsIfServerConnectionFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addReadStream');
+
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $executor->query('///', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ /**
+ * @group internet
+ */
+ public function testQueryRejectsOnCancellation()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $executor->query('8.8.8.8:53', $query);
+ $promise->cancel();
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testQueryKeepsPendingIfServerRejectsNetworkPacket()
+ {
+ $loop = Factory::create();
+
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query('127.0.0.1:1', $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ \Clue\React\Block\sleep(0.2, $loop);
+ $this->assertTrue($wait);
+ }
+
+ public function testQueryKeepsPendingIfServerSendInvalidMessage()
+ {
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+ stream_socket_sendto($server, 'invalid', 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query($address, $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ \Clue\React\Block\sleep(0.2, $loop);
+ $this->assertTrue($wait);
+ }
+
+ public function testQueryKeepsPendingIfServerSendInvalidId()
+ {
+ $parser = new Parser();
+ $dumper = new BinaryDumper();
+
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) use ($parser, $dumper) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+
+ $message = $parser->parseMessage($data);
+ $message->header->set('id', 0);
+
+ stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop, $parser, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query($address, $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ \Clue\React\Block\sleep(0.2, $loop);
+ $this->assertTrue($wait);
+ }
+
+ public function testQueryRejectsIfServerSendsTruncatedResponse()
+ {
+ $parser = new Parser();
+ $dumper = new BinaryDumper();
+
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) use ($parser, $dumper) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+
+ $message = $parser->parseMessage($data);
+ $message->header->set('tc', 1);
+
+ stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop, $parser, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query($address, $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect connection ICMP rejection error
+ \Clue\React\Block\sleep(0.01, $loop);
+ if ($wait) {
+ \Clue\React\Block\sleep(0.2, $loop);
+ }
+
+ $this->assertFalse($wait);
+ }
+
+ public function testQueryResolvesIfServerSendsValidResponse()
+ {
+ $parser = new Parser();
+ $dumper = new BinaryDumper();
+
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) use ($parser, $dumper) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+
+ $message = $parser->parseMessage($data);
+
+ stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop, $parser, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query($address, $query);
+ $response = \Clue\React\Block\await($promise, $loop, 0.2);
+
+ $this->assertInstanceOf('React\Dns\Model\Message', $response);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Resolver/FactoryTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Resolver/FactoryTest.php
new file mode 100755
index 0000000..9b1b0f8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Resolver/FactoryTest.php
@@ -0,0 +1,120 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create('8.8.8.8:53', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ }
+
+ /** @test */
+ public function createWithoutPortShouldCreateResolverWithDefaultPort()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create('8.8.8.8', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $this->assertSame('8.8.8.8:53', $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
+ }
+
+ /** @test */
+ public function createCachedShouldCreateResolverWithCachingExecutor()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->createCached('8.8.8.8:53', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $executor = $this->getResolverPrivateExecutor($resolver);
+ $this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
+ $cache = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
+ $this->assertInstanceOf('React\Cache\ArrayCache', $cache);
+ }
+
+ /** @test */
+ public function createCachedShouldCreateResolverWithCachingExecutorWithCustomCache()
+ {
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->createCached('8.8.8.8:53', $loop, $cache);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $executor = $this->getResolverPrivateExecutor($resolver);
+ $this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
+ $cacheProperty = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
+ $this->assertSame($cache, $cacheProperty);
+ }
+
+ /**
+ * @test
+ * @dataProvider factoryShouldAddDefaultPortProvider
+ */
+ public function factoryShouldAddDefaultPort($input, $expected)
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create($input, $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $this->assertSame($expected, $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
+ }
+
+ public static function factoryShouldAddDefaultPortProvider()
+ {
+ return array(
+ array('8.8.8.8', '8.8.8.8:53'),
+ array('1.2.3.4:5', '1.2.3.4:5'),
+ array('localhost', 'localhost:53'),
+ array('localhost:1234', 'localhost:1234'),
+ array('::1', '[::1]:53'),
+ array('[::1]:53', '[::1]:53')
+ );
+ }
+
+ private function getResolverPrivateExecutor($resolver)
+ {
+ $executor = $this->getResolverPrivateMemberValue($resolver, 'executor');
+
+ // extract underlying executor that may be wrapped in multiple layers of hosts file executors
+ while ($executor instanceof HostsFileExecutor) {
+ $reflector = new \ReflectionProperty('React\Dns\Query\HostsFileExecutor', 'fallback');
+ $reflector->setAccessible(true);
+
+ $executor = $reflector->getValue($executor);
+ }
+
+ return $executor;
+ }
+
+ private function getResolverPrivateMemberValue($resolver, $field)
+ {
+ $reflector = new \ReflectionProperty('React\Dns\Resolver\Resolver', $field);
+ $reflector->setAccessible(true);
+ return $reflector->getValue($resolver);
+ }
+
+ private function getCachingExecutorPrivateMemberValue($resolver, $field)
+ {
+ $reflector = new \ReflectionProperty('React\Dns\Query\CachingExecutor', $field);
+ $reflector->setAccessible(true);
+ return $reflector->getValue($resolver);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
new file mode 100755
index 0000000..a9b8608
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
@@ -0,0 +1,101 @@
+createExecutorMock();
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+
+ $answers = $resolver->resolveAliases($answers, $name);
+
+ $this->assertEquals($expectedAnswers, $answers);
+ }
+
+ public function provideAliasedAnswers()
+ {
+ return array(
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array(),
+ array(
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'baz.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'qux.igor.io'),
+ new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
+ new Record('qux.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
+ ),
+ 'igor.io',
+ ),
+ );
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/Resolver/ResolverTest.php b/src/mpg25-instagram-api/vendor/react/dns/tests/Resolver/ResolverTest.php
new file mode 100755
index 0000000..661386d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/Resolver/ResolverTest.php
@@ -0,0 +1,251 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableOnceWith('178.79.169.131'));
+ }
+
+ /** @test */
+ public function resolveAllShouldQueryGivenRecords()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
+ }
+
+ /** @test */
+ public function resolveAllShouldIgnoreRecordsWithOtherTypes()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, Message::TYPE_TXT, $query->class, 3600, array('ignored'));
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
+ }
+
+ /** @test */
+ public function resolveAllShouldReturnMultipleValuesForAlias()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, Message::TYPE_CNAME, $query->class, 3600, 'example.com');
+ $response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::1');
+ $response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::2');
+ $response->prepare();
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(
+ $this->expectCallableOnceWith($this->equalTo(array('::1', '::2')))
+ );
+ }
+
+ /** @test */
+ public function resolveShouldQueryARecordsAndIgnoreCase()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class);
+ $response->answers[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('blog.wyrihaximus.net')->then($this->expectCallableOnceWith('178.79.169.131'));
+ }
+
+ /** @test */
+ public function resolveShouldFilterByName()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record('foo.bar', $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException'));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
+ }
+
+ /**
+ * @test
+ */
+ public function resolveWithNoAnswersShouldCallErrbackIfGiven()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->callback(function ($param) {
+ return ($param instanceof RecordNotFoundException && $param->getCode() === 0 && $param->getMessage() === 'DNS query for igor.io did not return a valid answer (NOERROR / NODATA)');
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
+ }
+
+ public function provideRcodeErrors()
+ {
+ return array(
+ array(
+ Message::RCODE_FORMAT_ERROR,
+ 'DNS query for example.com returned an error response (Format Error)',
+ ),
+ array(
+ Message::RCODE_SERVER_FAILURE,
+ 'DNS query for example.com returned an error response (Server Failure)',
+ ),
+ array(
+ Message::RCODE_NAME_ERROR,
+ 'DNS query for example.com returned an error response (Non-Existent Domain / NXDOMAIN)'
+ ),
+ array(
+ Message::RCODE_NOT_IMPLEMENTED,
+ 'DNS query for example.com returned an error response (Not Implemented)'
+ ),
+ array(
+ Message::RCODE_REFUSED,
+ 'DNS query for example.com returned an error response (Refused)'
+ ),
+ array(
+ 99,
+ 'DNS query for example.com returned an error response (Unknown error response code 99)'
+ )
+ );
+ }
+
+ /**
+ * @test
+ * @dataProvider provideRcodeErrors
+ */
+ public function resolveWithRcodeErrorShouldCallErrbackIfGiven($code, $expectedMessage)
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) use ($code) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->header->set('rcode', $code);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->callback(function ($param) use ($code, $expectedMessage) {
+ return ($param instanceof RecordNotFoundException && $param->getCode() === $code && $param->getMessage() === $expectedMessage);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('example.com')->then($this->expectCallableNever(), $errback);
+ }
+
+ public function testLegacyExtractAddress()
+ {
+ $executor = $this->createExecutorMock();
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $response = Message::createResponseWithAnswersForQuery($query, array(
+ new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '1.2.3.4')
+ ));
+
+ $ret = $resolver->extractAddress($query, $response);
+ $this->assertEquals('1.2.3.4', $ret);
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/dns/tests/TestCase.php b/src/mpg25-instagram-api/vendor/react/dns/tests/TestCase.php
new file mode 100755
index 0000000..a5a22bf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/dns/tests/TestCase.php
@@ -0,0 +1,61 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Dns\CallableStub')->getMock();
+ }
+
+ public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
+ {
+ if (method_exists($this, 'expectException')) {
+ // PHPUnit 5
+ $this->expectException($exception);
+ if ($exceptionMessage !== '') {
+ $this->expectExceptionMessage($exceptionMessage);
+ }
+ if ($exceptionCode !== null) {
+ $this->expectExceptionCode($exceptionCode);
+ }
+ } else {
+ // legacy PHPUnit 4
+ parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/.gitignore b/src/mpg25-instagram-api/vendor/react/event-loop/.gitignore
new file mode 100755
index 0000000..81b9258
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/.travis.yml b/src/mpg25-instagram-api/vendor/react/event-loop/.travis.yml
new file mode 100755
index 0000000..0b7ce2c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/.travis.yml
@@ -0,0 +1,27 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - hhvm
+
+sudo: false
+
+addons:
+ apt:
+ packages:
+ - libevent-dev # Used by 'event' and 'libevent' PHP extensions
+
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+
+install:
+ - ./travis-init.sh
+ - composer install
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/CHANGELOG.md b/src/mpg25-instagram-api/vendor/react/event-loop/CHANGELOG.md
new file mode 100755
index 0000000..dd5d467
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/CHANGELOG.md
@@ -0,0 +1,79 @@
+# Changelog
+
+## 0.4.3 (2017-04-27)
+
+* Bug fix: Bugfix in the usage sample code #57 (@dandelionred)
+* Improvement: Remove branch-alias definition #53 (@WyriHaximus)
+* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd)
+* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder)
+* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley)
+* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue)
+* Improvement: Travis improvements (backported from #74) #75 (@clue)
+* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder)
+* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder)
+* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek)
+* Improvement: Readme cleanup #89 (@jsor)
+* Improvement: Restructure and improve README #90 (@jsor)
+* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor)
+
+## 0.4.2 (2016-03-07)
+
+* Bug fix: No longer error when signals sent to StreamSelectLoop
+* Support HHVM and PHP7 (@ondrejmirtes, @cebe)
+* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades)
+* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino)
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue)
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25)
+
+## 0.4.0 (2014-02-02)
+
+* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc)
+* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc)
+* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc)
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: New method: `EventLoopInterface::nextTick()`
+* BC break: New method: `EventLoopInterface::futureTick()`
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: No error on removing non-existent streams (@clue)
+* Bug fix: Do not silently remove feof listeners in `LibEvLoop`
+
+## 0.3.0 (2013-04-14)
+
+* BC break: New timers API (@nrk)
+* BC break: Remove check on return value from stream callbacks (@nrk)
+
+## 0.2.7 (2013-01-05)
+
+* Bug fix: Fix libevent timers with PHP 5.3
+* Bug fix: Fix libevent timer cancellation (@nrk)
+
+## 0.2.6 (2012-12-26)
+
+* Bug fix: Plug memory issue in libevent timers (@cameronjacobson)
+* Bug fix: Correctly pause LibEvLoop on stop()
+
+## 0.2.3 (2012-11-14)
+
+* Feature: LibEvLoop, integration of `php-libev`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Version bump
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/LICENSE b/src/mpg25-instagram-api/vendor/react/event-loop/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/README.md b/src/mpg25-instagram-api/vendor/react/event-loop/README.md
new file mode 100755
index 0000000..d4d4c97
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/README.md
@@ -0,0 +1,143 @@
+# EventLoop Component
+
+[](https://travis-ci.org/reactphp/event-loop)
+[](https://codeclimate.com/github/reactphp/event-loop)
+
+Event loop abstraction layer that libraries can use for evented I/O.
+
+In order for async based libraries to be interoperable, they need to use the
+same event loop. This component provides a common `LoopInterface` that any
+library can target. This allows them to be used in the same loop, with one
+single `run()` call that is controlled by the user.
+
+**Table of Contents**
+
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+* [Loop implementations](#loop-implementations)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Quickstart example
+
+Here is an async HTTP server built with just the event loop.
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$server = stream_socket_server('tcp://127.0.0.1:8080');
+stream_set_blocking($server, 0);
+
+$loop->addReadStream($server, function ($server) use ($loop) {
+ $conn = stream_socket_accept($server);
+ $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n";
+ $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) {
+ $written = fwrite($conn, $data);
+ if ($written === strlen($data)) {
+ fclose($conn);
+ $loop->removeStream($conn);
+ } else {
+ $data = substr($data, $written);
+ }
+ });
+});
+
+$loop->addPeriodicTimer(5, function () {
+ $memory = memory_get_usage() / 1024;
+ $formatted = number_format($memory, 3).'K';
+ echo "Current memory usage: {$formatted}\n";
+});
+
+$loop->run();
+```
+
+## Usage
+
+Typical applications use a single event loop which is created at the beginning
+and run at the end of the program.
+
+```php
+// [1]
+$loop = React\EventLoop\Factory::create();
+
+// [2]
+$loop->addPeriodicTimer(1, function () {
+ echo "Tick\n";
+});
+
+$stream = new React\Stream\ReadableResourceStream(
+ fopen('file.txt', 'r'),
+ $loop
+);
+
+// [3]
+$loop->run();
+```
+
+1. The loop instance is created at the beginning of the program. A convenience
+ factory `React\EventLoop\Factory::create()` is provided by this library which
+ picks the best available [loop implementation](#loop-implementations).
+2. The loop instance is used directly or passed to library and application code.
+ In this example, a periodic timer is registered with the event loop which
+ simply outputs `Tick` every second and a
+ [readable stream](https://github.com/reactphp/stream#readableresourcestream)
+ is created by using ReactPHP's
+ [stream component](https://github.com/reactphp/stream) for demonstration
+ purposes.
+3. The loop is run with a single `$loop->run()` call at the end of the program.
+
+## Loop implementations
+
+In addition to the interface there are the following implementations provided:
+
+* `StreamSelectLoop`: This is the only implementation which works out of the
+ box with PHP. It does a simple `select` system call. It's not the most
+ performant of loops, but still does the job quite well.
+
+* `LibEventLoop`: This uses the `libevent` pecl extension. `libevent` itself
+ supports a number of system-specific backends (epoll, kqueue).
+
+* `LibEvLoop`: This uses the `libev` pecl extension
+ ([github](https://github.com/m4rw3r/php-libev)). It supports the same
+ backends as libevent.
+
+* `ExtEventLoop`: This uses the `event` pecl extension. It supports the same
+ backends as libevent.
+
+All of the loops support these features:
+
+* File descriptor polling
+* One-off timers
+* Periodic timers
+* Deferred execution of callbacks
+
+## Install
+
+The recommended way to install this library is [through Composer](http://getcomposer.org).
+[New to Composer?](http://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/event-loop
+```
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](http://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/composer.json b/src/mpg25-instagram-api/vendor/react/event-loop/composer.json
new file mode 100755
index 0000000..5001a9c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "react/event-loop",
+ "description": "Event loop abstraction layer that libraries can use for evented I/O.",
+ "keywords": ["event-loop", "asynchronous"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "suggest": {
+ "ext-libevent": ">=0.1.0",
+ "ext-event": "~1.0",
+ "ext-libev": "*"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/react/event-loop/phpunit.xml.dist
new file mode 100755
index 0000000..cba6d4d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/src/ExtEventLoop.php b/src/mpg25-instagram-api/vendor/react/event-loop/src/ExtEventLoop.php
new file mode 100755
index 0000000..4f4b998
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/src/ExtEventLoop.php
@@ -0,0 +1,326 @@
+eventBase = new EventBase($config);
+ $this->nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timerEvents = new SplObjectStorage();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readListeners[$key])) {
+ $this->readListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, Event::READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeListeners[$key])) {
+ $this->writeListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, Event::WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ unset($this->readListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, Event::READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ unset($this->writeListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, Event::WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $this->streamEvents[$key]->free();
+
+ unset(
+ $this->streamFlags[$key],
+ $this->streamEvents[$key],
+ $this->readListeners[$key],
+ $this->writeListeners[$key]
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->isTimerActive($timer)) {
+ $this->timerEvents[$timer]->free();
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timerEvents->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ // @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226
+ @$this->eventBase->loop(EventBase::LOOP_ONCE | EventBase::LOOP_NONBLOCK);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $flags = EventBase::LOOP_ONCE;
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventBase::LOOP_NONBLOCK;
+ } elseif (!$this->streamEvents && !$this->timerEvents->count()) {
+ break;
+ }
+
+ $this->eventBase->loop($flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $flags = Event::TIMEOUT;
+
+ if ($timer->isPeriodic()) {
+ $flags |= Event::PERSIST;
+ }
+
+ $event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
+ $this->timerEvents[$timer] = $event;
+
+ $event->add($timer->getInterval());
+ }
+
+ /**
+ * Create a new ext-event Event object, or update the existing one.
+ *
+ * @param resource $stream
+ * @param integer $flag Event::READ or Event::WRITE
+ */
+ private function subscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $event = $this->streamEvents[$key];
+ $flags = ($this->streamFlags[$key] |= $flag);
+
+ $event->del();
+ $event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
+ } else {
+ $event = new Event($this->eventBase, $stream, Event::PERSIST | $flag, $this->streamCallback);
+
+ $this->streamEvents[$key] = $event;
+ $this->streamFlags[$key] = $flag;
+ }
+
+ $event->add();
+ }
+
+ /**
+ * Update the ext-event Event object for this stream to stop listening to
+ * the given event type, or remove it entirely if it's no longer needed.
+ *
+ * @param resource $stream
+ * @param integer $flag Event::READ or Event::WRITE
+ */
+ private function unsubscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ $flags = $this->streamFlags[$key] &= ~$flag;
+
+ if (0 === $flags) {
+ $this->removeStream($stream);
+
+ return;
+ }
+
+ $event = $this->streamEvents[$key];
+
+ $event->del();
+ $event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
+ $event->add();
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $this->timerCallback = function ($_, $__, $timer) {
+ call_user_func($timer->getCallback(), $timer);
+
+ if (!$timer->isPeriodic() && $this->isTimerActive($timer)) {
+ $this->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $this->streamCallback = function ($stream, $flags) {
+ $key = (int) $stream;
+
+ if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream, $this);
+ }
+
+ if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream, $this);
+ }
+ };
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/src/Factory.php b/src/mpg25-instagram-api/vendor/react/event-loop/src/Factory.php
new file mode 100755
index 0000000..9a481e3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/src/Factory.php
@@ -0,0 +1,21 @@
+loop = new EventLoop();
+ $this->nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timerEvents = new SplObjectStorage();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ if (isset($this->readEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ call_user_func($listener, $stream, $this);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::READ);
+ $this->loop->add($event);
+
+ $this->readEvents[(int) $stream] = $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ if (isset($this->writeEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ call_user_func($listener, $stream, $this);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::WRITE);
+ $this->loop->add($event);
+
+ $this->writeEvents[(int) $stream] = $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readEvents[$key])) {
+ $this->readEvents[$key]->stop();
+ unset($this->readEvents[$key]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeEvents[$key])) {
+ $this->writeEvents[$key]->stop();
+ unset($this->writeEvents[$key]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $this->removeReadStream($stream);
+ $this->removeWriteStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $callback = function () use ($timer) {
+ call_user_func($timer->getCallback(), $timer);
+
+ if ($this->isTimerActive($timer)) {
+ $this->cancelTimer($timer);
+ }
+ };
+
+ $event = new TimerEvent($callback, $timer->getInterval());
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $callback = function () use ($timer) {
+ call_user_func($timer->getCallback(), $timer);
+ };
+
+ $event = new TimerEvent($callback, $interval, $interval);
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if (isset($this->timerEvents[$timer])) {
+ $this->loop->remove($this->timerEvents[$timer]);
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timerEvents->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $this->loop->run(EventLoop::RUN_ONCE | EventLoop::RUN_NOWAIT);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $flags = EventLoop::RUN_ONCE;
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventLoop::RUN_NOWAIT;
+ } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) {
+ break;
+ }
+
+ $this->loop->run($flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/src/LibEventLoop.php b/src/mpg25-instagram-api/vendor/react/event-loop/src/LibEventLoop.php
new file mode 100755
index 0000000..99417a1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/src/LibEventLoop.php
@@ -0,0 +1,343 @@
+eventBase = event_base_new();
+ $this->nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timerEvents = new SplObjectStorage();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readListeners[$key])) {
+ $this->readListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, EV_READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeListeners[$key])) {
+ $this->writeListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, EV_WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ unset($this->readListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, EV_READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ unset($this->writeListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, EV_WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $event = $this->streamEvents[$key];
+
+ event_del($event);
+ event_free($event);
+
+ unset(
+ $this->streamFlags[$key],
+ $this->streamEvents[$key],
+ $this->readListeners[$key],
+ $this->writeListeners[$key]
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->isTimerActive($timer)) {
+ $event = $this->timerEvents[$timer];
+
+ event_del($event);
+ event_free($event);
+
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timerEvents->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ event_base_loop($this->eventBase, EVLOOP_ONCE | EVLOOP_NONBLOCK);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $flags = EVLOOP_ONCE;
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EVLOOP_NONBLOCK;
+ } elseif (!$this->streamEvents && !$this->timerEvents->count()) {
+ break;
+ }
+
+ event_base_loop($this->eventBase, $flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $this->timerEvents[$timer] = $event = event_timer_new();
+
+ event_timer_set($event, $this->timerCallback, $timer);
+ event_base_set($event, $this->eventBase);
+ event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
+ }
+
+ /**
+ * Create a new ext-libevent event resource, or update the existing one.
+ *
+ * @param resource $stream
+ * @param integer $flag EV_READ or EV_WRITE
+ */
+ private function subscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $event = $this->streamEvents[$key];
+ $flags = $this->streamFlags[$key] |= $flag;
+
+ event_del($event);
+ event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
+ } else {
+ $event = event_new();
+
+ event_set($event, $stream, EV_PERSIST | $flag, $this->streamCallback);
+ event_base_set($event, $this->eventBase);
+
+ $this->streamEvents[$key] = $event;
+ $this->streamFlags[$key] = $flag;
+ }
+
+ event_add($event);
+ }
+
+ /**
+ * Update the ext-libevent event resource for this stream to stop listening to
+ * the given event type, or remove it entirely if it's no longer needed.
+ *
+ * @param resource $stream
+ * @param integer $flag EV_READ or EV_WRITE
+ */
+ private function unsubscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ $flags = $this->streamFlags[$key] &= ~$flag;
+
+ if (0 === $flags) {
+ $this->removeStream($stream);
+
+ return;
+ }
+
+ $event = $this->streamEvents[$key];
+
+ event_del($event);
+ event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
+ event_add($event);
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $this->timerCallback = function ($_, $__, $timer) {
+ call_user_func($timer->getCallback(), $timer);
+
+ // Timer already cancelled ...
+ if (!$this->isTimerActive($timer)) {
+ return;
+
+ // Reschedule periodic timers ...
+ } elseif ($timer->isPeriodic()) {
+ event_add(
+ $this->timerEvents[$timer],
+ $timer->getInterval() * self::MICROSECONDS_PER_SECOND
+ );
+
+ // Clean-up one shot timers ...
+ } else {
+ $this->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $this->streamCallback = function ($stream, $flags) {
+ $key = (int) $stream;
+
+ if (EV_READ === (EV_READ & $flags) && isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream, $this);
+ }
+
+ if (EV_WRITE === (EV_WRITE & $flags) && isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream, $this);
+ }
+ };
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/src/LoopInterface.php b/src/mpg25-instagram-api/vendor/react/event-loop/src/LoopInterface.php
new file mode 100755
index 0000000..d046526
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/src/LoopInterface.php
@@ -0,0 +1,121 @@
+nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timers = new Timers();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readStreams[$key])) {
+ $this->readStreams[$key] = $stream;
+ $this->readListeners[$key] = $listener;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeStreams[$key])) {
+ $this->writeStreams[$key] = $stream;
+ $this->writeListeners[$key] = $listener;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->readStreams[$key],
+ $this->readListeners[$key]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->writeStreams[$key],
+ $this->writeListeners[$key]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $this->removeReadStream($stream);
+ $this->removeWriteStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ $this->timers->cancel($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timers->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $this->timers->tick();
+
+ $this->waitForStreamActivity(0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $this->timers->tick();
+
+ // Next-tick or future-tick queues have pending callbacks ...
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $timeout = 0;
+
+ // There is a pending timer, only block until it is due ...
+ } elseif ($scheduledAt = $this->timers->getFirst()) {
+ $timeout = $scheduledAt - $this->timers->getTime();
+ if ($timeout < 0) {
+ $timeout = 0;
+ } else {
+ /*
+ * round() needed to correct float error:
+ * https://github.com/reactphp/event-loop/issues/48
+ */
+ $timeout = round($timeout * self::MICROSECONDS_PER_SECOND);
+ }
+
+ // The only possible event is stream activity, so wait forever ...
+ } elseif ($this->readStreams || $this->writeStreams) {
+ $timeout = null;
+
+ // There's nothing left to do ...
+ } else {
+ break;
+ }
+
+ $this->waitForStreamActivity($timeout);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Wait/check for stream activity, or until the next timer is due.
+ */
+ private function waitForStreamActivity($timeout)
+ {
+ $read = $this->readStreams;
+ $write = $this->writeStreams;
+
+ $available = $this->streamSelect($read, $write, $timeout);
+ if (false === $available) {
+ // if a system call has been interrupted,
+ // we cannot rely on it's outcome
+ return;
+ }
+
+ foreach ($read as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream, $this);
+ }
+ }
+
+ foreach ($write as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream, $this);
+ }
+ }
+ }
+
+ /**
+ * Emulate a stream_select() implementation that does not break when passed
+ * empty stream arrays.
+ *
+ * @param array &$read An array of read streams to select upon.
+ * @param array &$write An array of write streams to select upon.
+ * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
+ *
+ * @return integer|false The total number of streams that are ready for read/write.
+ * Can return false if stream_select() is interrupted by a signal.
+ */
+ protected function streamSelect(array &$read, array &$write, $timeout)
+ {
+ if ($read || $write) {
+ $except = null;
+
+ // suppress warnings that occur, when stream_select is interrupted by a signal
+ return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
+ }
+
+ $timeout && usleep($timeout);
+
+ return 0;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/src/Tick/FutureTickQueue.php b/src/mpg25-instagram-api/vendor/react/event-loop/src/Tick/FutureTickQueue.php
new file mode 100755
index 0000000..eeffd36
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/src/Tick/FutureTickQueue.php
@@ -0,0 +1,59 @@
+eventLoop = $eventLoop;
+ $this->queue = new SplQueue();
+ }
+
+ /**
+ * Add a callback to be invoked on a future tick of the event loop.
+ *
+ * Callbacks are guaranteed to be executed in the order they are enqueued.
+ *
+ * @param callable $listener The callback to invoke.
+ */
+ public function add(callable $listener)
+ {
+ $this->queue->enqueue($listener);
+ }
+
+ /**
+ * Flush the callback queue.
+ */
+ public function tick()
+ {
+ // Only invoke as many callbacks as were on the queue when tick() was called.
+ $count = $this->queue->count();
+
+ while ($count--) {
+ call_user_func(
+ $this->queue->dequeue(),
+ $this->eventLoop
+ );
+ }
+ }
+
+ /**
+ * Check if the next tick queue is empty.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->queue->isEmpty();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/src/Tick/NextTickQueue.php b/src/mpg25-instagram-api/vendor/react/event-loop/src/Tick/NextTickQueue.php
new file mode 100755
index 0000000..5b8e1de
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/src/Tick/NextTickQueue.php
@@ -0,0 +1,57 @@
+eventLoop = $eventLoop;
+ $this->queue = new SplQueue();
+ }
+
+ /**
+ * Add a callback to be invoked on the next tick of the event loop.
+ *
+ * Callbacks are guaranteed to be executed in the order they are enqueued,
+ * before any timer or stream events.
+ *
+ * @param callable $listener The callback to invoke.
+ */
+ public function add(callable $listener)
+ {
+ $this->queue->enqueue($listener);
+ }
+
+ /**
+ * Flush the callback queue.
+ */
+ public function tick()
+ {
+ while (!$this->queue->isEmpty()) {
+ call_user_func(
+ $this->queue->dequeue(),
+ $this->eventLoop
+ );
+ }
+ }
+
+ /**
+ * Check if the next tick queue is empty.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->queue->isEmpty();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/src/Timer/Timer.php b/src/mpg25-instagram-api/vendor/react/event-loop/src/Timer/Timer.php
new file mode 100755
index 0000000..f670ab3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/src/Timer/Timer.php
@@ -0,0 +1,102 @@
+loop = $loop;
+ $this->interval = (float) $interval;
+ $this->callback = $callback;
+ $this->periodic = (bool) $periodic;
+ $this->data = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLoop()
+ {
+ return $this->loop;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInterval()
+ {
+ return $this->interval;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCallback()
+ {
+ return $this->callback;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setData($data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isPeriodic()
+ {
+ return $this->periodic;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isActive()
+ {
+ return $this->loop->isTimerActive($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancel()
+ {
+ $this->loop->cancelTimer($this);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/src/Timer/TimerInterface.php b/src/mpg25-instagram-api/vendor/react/event-loop/src/Timer/TimerInterface.php
new file mode 100755
index 0000000..d066f36
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/src/Timer/TimerInterface.php
@@ -0,0 +1,62 @@
+timers = new SplObjectStorage();
+ $this->scheduler = new SplPriorityQueue();
+ }
+
+ public function updateTime()
+ {
+ return $this->time = microtime(true);
+ }
+
+ public function getTime()
+ {
+ return $this->time ?: $this->updateTime();
+ }
+
+ public function add(TimerInterface $timer)
+ {
+ $interval = $timer->getInterval();
+ $scheduledAt = $interval + microtime(true);
+
+ $this->timers->attach($timer, $scheduledAt);
+ $this->scheduler->insert($timer, -$scheduledAt);
+ }
+
+ public function contains(TimerInterface $timer)
+ {
+ return $this->timers->contains($timer);
+ }
+
+ public function cancel(TimerInterface $timer)
+ {
+ $this->timers->detach($timer);
+ }
+
+ public function getFirst()
+ {
+ while ($this->scheduler->count()) {
+ $timer = $this->scheduler->top();
+
+ if ($this->timers->contains($timer)) {
+ return $this->timers[$timer];
+ }
+
+ $this->scheduler->extract();
+ }
+
+ return null;
+ }
+
+ public function isEmpty()
+ {
+ return count($this->timers) === 0;
+ }
+
+ public function tick()
+ {
+ $time = $this->updateTime();
+ $timers = $this->timers;
+ $scheduler = $this->scheduler;
+
+ while (!$scheduler->isEmpty()) {
+ $timer = $scheduler->top();
+
+ if (!isset($timers[$timer])) {
+ $scheduler->extract();
+ $timers->detach($timer);
+
+ continue;
+ }
+
+ if ($timers[$timer] >= $time) {
+ break;
+ }
+
+ $scheduler->extract();
+ call_user_func($timer->getCallback(), $timer);
+
+ if ($timer->isPeriodic() && isset($timers[$timer])) {
+ $timers[$timer] = $scheduledAt = $timer->getInterval() + $time;
+ $scheduler->insert($timer, -$scheduledAt);
+ } else {
+ $timers->detach($timer);
+ }
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/AbstractLoopTest.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/AbstractLoopTest.php
new file mode 100755
index 0000000..bd6bb83
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/AbstractLoopTest.php
@@ -0,0 +1,539 @@
+tickTimeout = defined('HHVM_VERSION') ? 0.02 : 0.005;
+ $this->loop = $this->createLoop();
+ }
+
+ abstract public function createLoop();
+
+ public function createSocketPair()
+ {
+ $domain = (DIRECTORY_SEPARATOR === '\\') ? STREAM_PF_INET : STREAM_PF_UNIX;
+ $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+
+ foreach ($sockets as $socket) {
+ if (function_exists('stream_set_read_buffer')) {
+ stream_set_read_buffer($socket, 0);
+ }
+ }
+
+ return $sockets;
+ }
+
+ public function testAddReadStream()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testAddReadStreamIgnoresSecondCallable()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testAddWriteStream()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
+ $this->loop->tick();
+ $this->loop->tick();
+ }
+
+ public function testAddWriteStreamIgnoresSecondCallable()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->tick();
+ $this->loop->tick();
+ }
+
+ public function testRemoveReadStreamInstantly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveReadStreamAfterReading()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveWriteStreamInstantly()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->removeWriteStream($input);
+ $this->loop->tick();
+ }
+
+ public function testRemoveWriteStreamAfterWriting()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableOnce());
+ $this->loop->tick();
+
+ $this->loop->removeWriteStream($input);
+ $this->loop->tick();
+ }
+
+ public function testRemoveStreamInstantly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->removeStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveStreamForReadOnly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->addWriteStream($output, $this->expectCallableOnce());
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveStreamForWriteOnly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ fwrite($output, "foo\n");
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+ $this->loop->addWriteStream($output, $this->expectCallableNever());
+ $this->loop->removeWriteStream($output);
+
+ $this->loop->tick();
+ }
+
+ public function testRemoveStream()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+ $this->loop->addWriteStream($input, $this->expectCallableOnce());
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+
+ $this->loop->removeStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveInvalid()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ // remove a valid stream from the event loop that was never added in the first place
+ $this->loop->removeReadStream($stream);
+ $this->loop->removeWriteStream($stream);
+ $this->loop->removeStream($stream);
+ }
+
+ /** @test */
+ public function emptyRunShouldSimplyReturn()
+ {
+ $this->assertRunFasterThan($this->tickTimeout);
+ }
+
+ /** @test */
+ public function runShouldReturnWhenNoMoreFds()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addReadStream($input, function ($stream) use ($loop) {
+ $loop->removeStream($stream);
+ });
+
+ fwrite($output, "foo\n");
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ /** @test */
+ public function stopShouldStopRunningLoop()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addReadStream($input, function ($stream) use ($loop) {
+ $loop->stop();
+ });
+
+ fwrite($output, "foo\n");
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ public function testStopShouldPreventRunFromBlocking()
+ {
+ $this->loop->addTimer(
+ 1,
+ function () {
+ $this->fail('Timer was executed.');
+ }
+ );
+
+ $this->loop->nextTick(
+ function () {
+ $this->loop->stop();
+ }
+ );
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ public function testIgnoreRemovedCallback()
+ {
+ // two independent streams, both should be readable right away
+ list ($input1, $output1) = $this->createSocketPair();
+ list ($input2, $output2) = $this->createSocketPair();
+
+ $called = false;
+
+ $loop = $this->loop;
+ $loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) {
+ // stream1 is readable, remove stream2 as well => this will invalidate its callback
+ $loop->removeReadStream($stream);
+ $loop->removeReadStream($input2);
+
+ $called = true;
+ });
+
+ // this callback would have to be called as well, but the first stream already removed us
+ $loop->addReadStream($input2, function () use (& $called) {
+ if ($called) {
+ $this->fail('Callback 2 must not be called after callback 1 was called');
+ }
+ });
+
+ fwrite($output1, "foo\n");
+ fwrite($output2, "foo\n");
+
+ $loop->run();
+
+ $this->assertTrue($called);
+ }
+
+ public function testNextTick()
+ {
+ $called = false;
+
+ $callback = function ($loop) use (&$called) {
+ $this->assertSame($this->loop, $loop);
+ $called = true;
+ };
+
+ $this->loop->nextTick($callback);
+
+ $this->assertFalse($called);
+
+ $this->loop->tick();
+
+ $this->assertTrue($called);
+ }
+
+ public function testNextTickFiresBeforeIO()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->loop->tick();
+ }
+
+ public function testRecursiveNextTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->nextTick(
+ function () {
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->loop->tick();
+ }
+
+ public function testRunWaitsForNextTickEvents()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream) {
+ $this->loop->removeStream($stream);
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testNextTickEventGeneratedByFutureTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->futureTick(
+ function () {
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testNextTickEventGeneratedByTimer()
+ {
+ $this->loop->addTimer(
+ 0.001,
+ function () {
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTick()
+ {
+ $called = false;
+
+ $callback = function ($loop) use (&$called) {
+ $this->assertSame($this->loop, $loop);
+ $called = true;
+ };
+
+ $this->loop->futureTick($callback);
+
+ $this->assertFalse($called);
+
+ $this->loop->tick();
+
+ $this->assertTrue($called);
+ }
+
+ public function testFutureTickFiresBeforeIO()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->loop->tick();
+ }
+
+ public function testRecursiveFutureTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream) {
+ echo 'stream' . PHP_EOL;
+ $this->loop->removeWriteStream($stream);
+ }
+ );
+
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick-1' . PHP_EOL;
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick-2' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick-1' . PHP_EOL . 'stream' . PHP_EOL . 'future-tick-2' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testRunWaitsForFutureTickEvents()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream) {
+ $this->loop->removeStream($stream);
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTickEventGeneratedByNextTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->nextTick(
+ function () {
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTickEventGeneratedByTimer()
+ {
+ $this->loop->addTimer(
+ 0.001,
+ function () {
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ private function assertRunFasterThan($maxInterval)
+ {
+ $start = microtime(true);
+
+ $this->loop->run();
+
+ $end = microtime(true);
+ $interval = $end - $start;
+
+ $this->assertLessThan($maxInterval, $interval);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/CallableStub.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/CallableStub.php
new file mode 100755
index 0000000..913d403
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/CallableStub.php
@@ -0,0 +1,10 @@
+markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
+ }
+
+ if (!extension_loaded('event')) {
+ $this->markTestSkipped('ext-event tests skipped because ext-event is not installed.');
+ }
+
+ $cfg = null;
+ if ($readStreamCompatible) {
+ $cfg = new \EventConfig();
+ $cfg->requireFeatures(\EventConfig::FEATURE_FDS);
+ }
+
+ return new ExtEventLoop($cfg);
+ }
+
+ public function createStream()
+ {
+ // Use a FIFO on linux to get around lack of support for disk-based file
+ // descriptors when using the EPOLL back-end.
+ if ('Linux' === PHP_OS) {
+ $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
+
+ unlink($this->fifoPath);
+
+ posix_mkfifo($this->fifoPath, 0600);
+
+ $stream = fopen($this->fifoPath, 'r+');
+
+ // ext-event (as of 1.8.1) does not yet support in-memory temporary
+ // streams. Setting maxmemory:0 and performing a write forces PHP to
+ // back this temporary stream with a real file.
+ //
+ // This problem is mentioned at https://bugs.php.net/bug.php?id=64652&edit=3
+ // but remains unresolved (despite that issue being closed).
+ } else {
+ $stream = fopen('php://temp/maxmemory:0', 'r+');
+
+ fwrite($stream, 'x');
+ ftruncate($stream, 0);
+ }
+
+ return $stream;
+ }
+
+ public function writeToStream($stream, $content)
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::writeToStream($stream, $content);
+ }
+
+ fwrite($stream, $content);
+ }
+
+ /**
+ * @group epoll-readable-error
+ */
+ public function testCanUseReadableStreamWithFeatureFds()
+ {
+ if (PHP_VERSION_ID > 70000) {
+ $this->markTestSkipped('Memory stream not supported');
+ }
+
+ $this->loop = $this->createLoop(true);
+
+ $input = fopen('php://temp/maxmemory:0', 'r+');
+
+ fwrite($input, 'x');
+ ftruncate($input, 0);
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+
+ fwrite($input, "foo\n");
+ $this->loop->tick();
+
+ fwrite($input, "bar\n");
+ $this->loop->tick();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/LibEvLoopTest.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/LibEvLoopTest.php
new file mode 100755
index 0000000..5ea98e3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/LibEvLoopTest.php
@@ -0,0 +1,22 @@
+markTestSkipped('libev tests skipped because ext-libev is not installed.');
+ }
+
+ return new LibEvLoop();
+ }
+
+ public function testLibEvConstructor()
+ {
+ $loop = new LibEvLoop();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/LibEventLoopTest.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/LibEventLoopTest.php
new file mode 100755
index 0000000..920b33c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/LibEventLoopTest.php
@@ -0,0 +1,58 @@
+markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
+ }
+
+ if (!function_exists('event_base_new')) {
+ $this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
+ }
+
+ return new LibEventLoop();
+ }
+
+ public function tearDown()
+ {
+ if (file_exists($this->fifoPath)) {
+ unlink($this->fifoPath);
+ }
+ }
+
+ public function createStream()
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::createStream();
+ }
+
+ $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
+
+ unlink($this->fifoPath);
+
+ // Use a FIFO on linux to get around lack of support for disk-based file
+ // descriptors when using the EPOLL back-end.
+ posix_mkfifo($this->fifoPath, 0600);
+
+ $stream = fopen($this->fifoPath, 'r+');
+
+ return $stream;
+ }
+
+ public function writeToStream($stream, $content)
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::writeToStream($stream, $content);
+ }
+
+ fwrite($stream, $content);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/StreamSelectLoopTest.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/StreamSelectLoopTest.php
new file mode 100755
index 0000000..d2e3e07
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/StreamSelectLoopTest.php
@@ -0,0 +1,179 @@
+getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
+ $this->resetSignalHandlers();
+ }
+ }
+
+ public function createLoop()
+ {
+ return new StreamSelectLoop();
+ }
+
+ public function testStreamSelectTimeoutEmulation()
+ {
+ $this->loop->addTimer(
+ 0.05,
+ $this->expectCallableOnce()
+ );
+
+ $start = microtime(true);
+
+ $this->loop->run();
+
+ $end = microtime(true);
+ $interval = $end - $start;
+
+ $this->assertGreaterThan(0.04, $interval);
+ }
+
+ public function signalProvider()
+ {
+ return [
+ ['SIGUSR1'],
+ ['SIGHUP'],
+ ['SIGTERM'],
+ ];
+ }
+
+ private $_signalHandled = false;
+
+ /**
+ * Test signal interrupt when no stream is attached to the loop
+ * @dataProvider signalProvider
+ */
+ public function testSignalInterruptNoStream($signal)
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('"pcntl" extension is required to run this test.');
+ }
+
+ // dispatch signal handler once before signal is sent and once after
+ $this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); });
+ $this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); });
+ if (defined('HHVM_VERSION')) {
+ // hhvm startup is slow so we need to add another handler much later
+ $this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); });
+ }
+
+ $this->setUpSignalHandler($signal);
+
+ // spawn external process to send signal to current process id
+ $this->forkSendSignal($signal);
+ $this->loop->run();
+ $this->assertTrue($this->_signalHandled);
+ }
+
+ /**
+ * Test signal interrupt when a stream is attached to the loop
+ * @dataProvider signalProvider
+ */
+ public function testSignalInterruptWithStream($signal)
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('"pcntl" extension is required to run this test.');
+ }
+
+ // dispatch signal handler every 10ms
+ $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); });
+
+ // add stream to the loop
+ list($writeStream, $readStream) = $this->createSocketPair();
+ $this->loop->addReadStream($readStream, function($stream, $loop) {
+ /** @var $loop LoopInterface */
+ $read = fgets($stream);
+ if ($read === "end loop\n") {
+ $loop->stop();
+ }
+ });
+ $this->loop->addTimer(0.05, function() use ($writeStream) {
+ fwrite($writeStream, "end loop\n");
+ });
+
+ $this->setUpSignalHandler($signal);
+
+ // spawn external process to send signal to current process id
+ $this->forkSendSignal($signal);
+
+ $this->loop->run();
+
+ $this->assertTrue($this->_signalHandled);
+ }
+
+ /**
+ * add signal handler for signal
+ */
+ protected function setUpSignalHandler($signal)
+ {
+ $this->_signalHandled = false;
+ $this->assertTrue(pcntl_signal(constant($signal), function() { $this->_signalHandled = true; }));
+ }
+
+ /**
+ * reset all signal handlers to default
+ */
+ protected function resetSignalHandlers()
+ {
+ foreach($this->signalProvider() as $signal) {
+ pcntl_signal(constant($signal[0]), SIG_DFL);
+ }
+ }
+
+ /**
+ * fork child process to send signal to current process id
+ */
+ protected function forkSendSignal($signal)
+ {
+ $currentPid = posix_getpid();
+ $childPid = pcntl_fork();
+ if ($childPid == -1) {
+ $this->fail("Failed to fork child process!");
+ } else if ($childPid === 0) {
+ // this is executed in the child process
+ usleep(20000);
+ posix_kill($currentPid, constant($signal));
+ die();
+ }
+ }
+
+ /**
+ * https://github.com/reactphp/event-loop/issues/48
+ *
+ * Tests that timer with very small interval uses at least 1 microsecond
+ * timeout.
+ */
+ public function testSmallTimerInterval()
+ {
+ /** @var StreamSelectLoop|\PHPUnit_Framework_MockObject_MockObject $loop */
+ $loop = $this->getMock('React\EventLoop\StreamSelectLoop', ['streamSelect']);
+ $loop
+ ->expects($this->at(0))
+ ->method('streamSelect')
+ ->with([], [], 1);
+ $loop
+ ->expects($this->at(1))
+ ->method('streamSelect')
+ ->with([], [], 0);
+
+ $callsCount = 0;
+ $loop->addPeriodicTimer(Timer::MIN_INTERVAL, function() use (&$loop, &$callsCount) {
+ $callsCount++;
+ if ($callsCount == 2) {
+ $loop->stop();
+ }
+ });
+
+ $loop->run();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/TestCase.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/TestCase.php
new file mode 100755
index 0000000..5114f4e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/TestCase.php
@@ -0,0 +1,41 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\EventLoop\CallableStub')->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php
new file mode 100755
index 0000000..5768965
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php
@@ -0,0 +1,97 @@
+createLoop();
+
+ $loop->addTimer(0.001, $this->expectCallableOnce());
+ usleep(1000);
+ $loop->tick();
+ }
+
+ public function testAddPeriodicTimer()
+ {
+ $loop = $this->createLoop();
+
+ $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(3));
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+ }
+
+ public function testAddPeriodicTimerWithCancel()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(2));
+
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+
+ $timer->cancel();
+
+ usleep(1000);
+ $loop->tick();
+ }
+
+ public function testAddPeriodicTimerCancelsItself()
+ {
+ $i = 0;
+
+ $loop = $this->createLoop();
+
+ $loop->addPeriodicTimer(0.001, function ($timer) use (&$i) {
+ $i++;
+
+ if ($i == 2) {
+ $timer->cancel();
+ }
+ });
+
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+
+ $this->assertSame(2, $i);
+ }
+
+ public function testIsTimerActive()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addPeriodicTimer(0.001, function () {});
+
+ $this->assertTrue($loop->isTimerActive($timer));
+
+ $timer->cancel();
+
+ $this->assertFalse($loop->isTimerActive($timer));
+ }
+
+ public function testMinimumIntervalOneMicrosecond()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addTimer(0, function () {});
+
+ $this->assertEquals(0.000001, $timer->getInterval());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php
new file mode 100755
index 0000000..a7a6d00
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('ext-event tests skipped because ext-event is not installed.');
+ }
+
+ return new ExtEventLoop();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/LibEvTimerTest.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/LibEvTimerTest.php
new file mode 100755
index 0000000..73abe8e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/LibEvTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('libev tests skipped because ext-libev is not installed.');
+ }
+
+ return new LibEvLoop();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/LibEventTimerTest.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/LibEventTimerTest.php
new file mode 100755
index 0000000..3db2035
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/LibEventTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
+ }
+
+ return new LibEventLoop();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php
new file mode 100755
index 0000000..cfe1d7d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php
@@ -0,0 +1,13 @@
+getMockBuilder('React\EventLoop\LoopInterface')
+ ->getMock();
+
+ $timers = new Timers();
+ $timers->tick();
+
+ // simulate a bunch of processing on stream events,
+ // part of which schedules a future timer...
+ sleep(1);
+ $timers->add(new Timer($loop, 0.5, function () {
+ $this->fail("Timer shouldn't be called");
+ }));
+
+ $timers->tick();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/tests/bootstrap.php b/src/mpg25-instagram-api/vendor/react/event-loop/tests/bootstrap.php
new file mode 100755
index 0000000..d97d8b7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/tests/bootstrap.php
@@ -0,0 +1,7 @@
+addPsr4('React\\Tests\\EventLoop\\', __DIR__);
diff --git a/src/mpg25-instagram-api/vendor/react/event-loop/travis-init.sh b/src/mpg25-instagram-api/vendor/react/event-loop/travis-init.sh
new file mode 100755
index 0000000..8745601
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/event-loop/travis-init.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -e
+set -o pipefail
+
+if [[ "$TRAVIS_PHP_VERSION" != "hhvm" &&
+ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then
+
+ # install 'event' PHP extension
+ echo "yes" | pecl install event
+
+ # install 'libevent' PHP extension (does not support php 7)
+ if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
+ "$TRAVIS_PHP_VERSION" != "7.1" ]]; then
+ curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz
+ pushd libevent-0.1.0
+ phpize
+ ./configure
+ make
+ make install
+ popd
+ echo "extension=libevent.so" >> "$(php -r 'echo php_ini_loaded_file();')"
+ fi
+
+ # install 'libev' PHP extension (does not support php 7)
+ if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
+ "$TRAVIS_PHP_VERSION" != "7.1" ]]; then
+ git clone --recursive https://github.com/m4rw3r/php-libev
+ pushd php-libev
+ phpize
+ ./configure --with-libev
+ make
+ make install
+ popd
+ echo "extension=libev.so" >> "$(php -r 'echo php_ini_loaded_file();')"
+ fi
+
+fi
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/.gitignore b/src/mpg25-instagram-api/vendor/react/promise-timer/.gitignore
new file mode 100755
index 0000000..de4a392
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/.gitignore
@@ -0,0 +1,2 @@
+/vendor
+/composer.lock
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/.travis.yml b/src/mpg25-instagram-api/vendor/react/promise-timer/.travis.yml
new file mode 100755
index 0000000..a71864a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/.travis.yml
@@ -0,0 +1,26 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/CHANGELOG.md b/src/mpg25-instagram-api/vendor/react/promise-timer/CHANGELOG.md
new file mode 100755
index 0000000..e5f4ccd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/CHANGELOG.md
@@ -0,0 +1,63 @@
+# Changelog
+
+## 1.5.1 (2019-03-27)
+
+* Fix: Typo in readme
+ (#35 by @aak74)
+
+* Improvement: Only include functions file when functions aren't defined
+ (#36 by @Niko9911)
+
+## 1.5.0 (2018-06-13)
+
+* Feature: Improve memory consumption by cleaning up garbage references to pending promise without canceller.
+ (#34 by @clue)
+
+## 1.4.0 (2018-06-11)
+
+* Feature: Improve memory consumption by cleaning up garbage references.
+ (#33 by @clue)
+
+## 1.3.0 (2018-04-24)
+
+* Feature: Improve memory consumption by cleaning up unneeded references.
+ (#32 by @clue)
+
+## 1.2.1 (2017-12-22)
+
+* README improvements
+ (#28 by @jsor)
+
+* Improve test suite by adding forward compatiblity with PHPUnit 6 and
+ fix test suite forward compatibility with upcoming EventLoop releases
+ (#30 and #31 by @clue)
+
+## 1.2.0 (2017-08-08)
+
+* Feature: Only start timers if input Promise is still pending and
+ return a settled output promise if the input is already settled.
+ (#25 by @clue)
+
+* Feature: Cap minimum timer interval at 1µs across all versions
+ (#23 by @clue)
+
+* Feature: Forward compatibility with EventLoop v1.0 and v0.5
+ (#27 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev and
+ lock Travis distro so new defaults will not break the build
+ (#24 and #26 by @clue)
+
+## 1.1.1 (2016-12-27)
+
+* Improve test suite to use PSR-4 autoloader and proper namespaces.
+ (#21 by @clue)
+
+## 1.1.0 (2016-02-29)
+
+* Feature: Support promise cancellation for all timer primitives
+ (#18 by @clue)
+
+## 1.0.0 (2015-09-29)
+
+* First tagged release
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/LICENSE b/src/mpg25-instagram-api/vendor/react/promise-timer/LICENSE
new file mode 100755
index 0000000..dc09d1e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/README.md b/src/mpg25-instagram-api/vendor/react/promise-timer/README.md
new file mode 100755
index 0000000..419a127
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/README.md
@@ -0,0 +1,372 @@
+# PromiseTimer
+
+[](https://travis-ci.org/reactphp/promise-timer)
+
+A trivial implementation of timeouts for `Promise`s, built on top of [ReactPHP](https://reactphp.org/).
+
+**Table of contents**
+
+* [Usage](#usage)
+ * [timeout()](#timeout)
+ * [Timeout cancellation](#timeout-cancellation)
+ * [Cancellation handler](#cancellation-handler)
+ * [Input cancellation](#input-cancellation)
+ * [Output cancellation](#output-cancellation)
+ * [Collections](#collections)
+ * [resolve()](#resolve)
+ * [Resolve cancellation](#resolve-cancellation)
+ * [reject()](#reject)
+ * [Reject cancellation](#reject-cancellation)
+ * [TimeoutException](#timeoutexception)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Usage
+
+This lightweight library consists only of a few simple functions.
+All functions reside under the `React\Promise\Timer` namespace.
+
+The below examples assume you use an import statement similar to this:
+
+```php
+use React\Promise\Timer;
+
+Timer\timeout(…);
+```
+
+Alternatively, you can also refer to them with their fully-qualified name:
+
+```php
+\React\Promise\Timer\timeout(…);
+```
+
+### timeout()
+
+The `timeout(PromiseInterface $promise, $time, LoopInterface $loop)` function
+can be used to *cancel* operations that take *too long*.
+You need to pass in an input `$promise` that represents a pending operation and timeout parameters.
+It returns a new `Promise` with the following resolution behavior:
+
+* If the input `$promise` resolves before `$time` seconds, resolve the resulting promise with its fulfillment value.
+* If the input `$promise` rejects before `$time` seconds, reject the resulting promise with its rejection value.
+* If the input `$promise` does not settle before `$time` seconds, *cancel* the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).
+
+Internally, the given `$time` value will be used to start a timer that will
+*cancel* the pending operation once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+If the input `$promise` is already settled, then the resulting promise will
+resolve or reject immediately without starting a timer at all.
+
+A common use case for handling only resolved values looks like this:
+
+```php
+$promise = accessSomeRemoteResource();
+Timer\timeout($promise, 10.0, $loop)->then(function ($value) {
+ // the operation finished within 10.0 seconds
+});
+```
+
+A more complete example could look like this:
+
+```php
+$promise = accessSomeRemoteResource();
+Timer\timeout($promise, 10.0, $loop)->then(
+ function ($value) {
+ // the operation finished within 10.0 seconds
+ },
+ function ($error) {
+ if ($error instanceof Timer\TimeoutException) {
+ // the operation has failed due to a timeout
+ } else {
+ // the input operation has failed due to some other error
+ }
+ }
+);
+```
+
+Or if you're using [react/promise v2.2.0](https://github.com/reactphp/promise) or up:
+
+```php
+Timer\timeout($promise, 10.0, $loop)
+ ->then(function ($value) {
+ // the operation finished within 10.0 seconds
+ })
+ ->otherwise(function (Timer\TimeoutException $error) {
+ // the operation has failed due to a timeout
+ })
+ ->otherwise(function ($error) {
+ // the input operation has failed due to some other error
+ })
+;
+```
+
+#### Timeout cancellation
+
+As discussed above, the [`timeout()`](#timeout) function will *cancel* the
+underlying operation if it takes *too long*.
+This means that you can be sure the resulting promise will then be rejected
+with a [`TimeoutException`](#timeoutexception).
+
+However, what happens to the underlying input `$promise` is a bit more tricky:
+Once the timer fires, we will try to call
+[`$promise->cancel()`](https://github.com/reactphp/promise#cancellablepromiseinterfacecancel)
+on the input `$promise` which in turn invokes its [cancellation handler](#cancellation-handler).
+
+This means that it's actually up the input `$promise` to handle
+[cancellation support](https://github.com/reactphp/promise#cancellablepromiseinterface).
+
+* A common use case involves cleaning up any resources like open network sockets or
+ file handles or terminating external processes or timers.
+
+* If the given input `$promise` does not support cancellation, then this is a NO-OP.
+ This means that while the resulting promise will still be rejected, the underlying
+ input `$promise` may still be pending and can hence continue consuming resources.
+
+See the following chapter for more details on the cancellation handler.
+
+#### Cancellation handler
+
+For example, an implementation for the above operation could look like this:
+
+```php
+function accessSomeRemoteResource()
+{
+ return new Promise(
+ function ($resolve, $reject) use (&$socket) {
+ // this will be called once the promise is created
+ // a common use case involves opening any resources and eventually resolving
+ $socket = createSocket();
+ $socket->on('data', function ($data) use ($resolve) {
+ $resolve($data);
+ });
+ },
+ function ($resolve, $reject) use (&$socket) {
+ // this will be called once calling `cancel()` on this promise
+ // a common use case involves cleaning any resources and then rejecting
+ $socket->close();
+ $reject(new \RuntimeException('Operation cancelled'));
+ }
+ );
+}
+```
+
+In this example, calling `$promise->cancel()` will invoke the registered cancellation
+handler which then closes the network socket and rejects the `Promise` instance.
+
+If no cancellation handler is passed to the `Promise` constructor, then invoking
+its `cancel()` method it is effectively a NO-OP.
+This means that it may still be pending and can hence continue consuming resources.
+
+For more details on the promise cancellation, please refer to the
+[Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface).
+
+#### Input cancellation
+
+Irrespective of the timeout handling, you can also explicitly `cancel()` the
+input `$promise` at any time.
+This means that the `timeout()` handling does not affect cancellation of the
+input `$promise`, as demonstrated in the following example:
+
+```php
+$promise = accessSomeRemoteResource();
+$timeout = Timer\timeout($promise, 10.0, $loop);
+
+$promise->cancel();
+```
+
+The registered [cancellation handler](#cancellation-handler) is responsible for
+handling the `cancel()` call:
+
+* A described above, a common use involves resource cleanup and will then *reject*
+ the `Promise`.
+ If the input `$promise` is being rejected, then the timeout will be aborted
+ and the resulting promise will also be rejected.
+* If the input `$promise` is still pending, then the timout will continue
+ running until the timer expires.
+ The same happens if the input `$promise` does not register a
+ [cancellation handler](#cancellation-handler).
+
+#### Output cancellation
+
+Similarily, you can also explicitly `cancel()` the resulting promise like this:
+
+```php
+$promise = accessSomeRemoteResource();
+$timeout = Timer\timeout($promise, 10.0, $loop);
+
+$timeout->cancel();
+```
+
+Note how this looks very similar to the above [input cancellation](#input-cancellation)
+example. Accordingly, it also behaves very similar.
+
+Calling `cancel()` on the resulting promise will merely try
+to `cancel()` the input `$promise`.
+This means that we do not take over responsibility of the outcome and it's
+entirely up to the input `$promise` to handle cancellation support.
+
+The registered [cancellation handler](#cancellation-handler) is responsible for
+handling the `cancel()` call:
+
+* As described above, a common use involves resource cleanup and will then *reject*
+ the `Promise`.
+ If the input `$promise` is being rejected, then the timeout will be aborted
+ and the resulting promise will also be rejected.
+* If the input `$promise` is still pending, then the timout will continue
+ running until the timer expires.
+ The same happens if the input `$promise` does not register a
+ [cancellation handler](#cancellation-handler).
+
+To re-iterate, note that calling `cancel()` on the resulting promise will merely
+try to cancel the input `$promise` only.
+It is then up to the cancellation handler of the input promise to settle the promise.
+If the input promise is still pending when the timeout occurs, then the normal
+[timeout cancellation](#timeout-cancellation) handling will trigger, effectively rejecting
+the output promise with a [`TimeoutException`](#timeoutexception).
+
+This is done for consistency with the [timeout cancellation](#timeout-cancellation)
+handling and also because it is assumed this is often used like this:
+
+```php
+$timeout = Timer\timeout(accessSomeRemoteResource(), 10.0, $loop);
+
+$timeout->cancel();
+```
+
+As described above, this example works as expected and cleans up any resources
+allocated for the input `$promise`.
+
+Note that if the given input `$promise` does not support cancellation, then this
+is a NO-OP.
+This means that while the resulting promise will still be rejected after the
+timeout, the underlying input `$promise` may still be pending and can hence
+continue consuming resources.
+
+#### Collections
+
+If you want to wait for multiple promises to resolve, you can use the normal promise primitives like this:
+
+```php
+$promises = array(
+ accessSomeRemoteResource(),
+ accessSomeRemoteResource(),
+ accessSomeRemoteResource()
+);
+
+$promise = \React\Promise\all($promises);
+
+Timer\timeout($promise, 10, $loop)->then(function ($values) {
+ // *all* promises resolved
+});
+```
+
+The applies to all promise collection primitives alike, i.e. `all()`, `race()`, `any()`, `some()` etc.
+
+For more details on the promise primitives, please refer to the
+[Promise documentation](https://github.com/reactphp/promise#functions).
+
+### resolve()
+
+The `resolve($time, LoopInterface $loop)` function can be used to create a new Promise that
+resolves in `$time` seconds with the `$time` as the fulfillment value.
+
+```php
+Timer\resolve(1.5, $loop)->then(function ($time) {
+ echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL;
+});
+```
+
+Internally, the given `$time` value will be used to start a timer that will
+resolve the promise once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+#### Resolve cancellation
+
+You can explicitly `cancel()` the resulting timer promise at any time:
+
+```php
+$timer = Timer\resolve(2.0, $loop);
+
+$timer->cancel();
+```
+
+This will abort the timer and *reject* with a `RuntimeException`.
+
+### reject()
+
+The `reject($time, LoopInterface $loop)` function can be used to create a new Promise
+which rejects in `$time` seconds with a `TimeoutException`.
+
+```php
+Timer\reject(2.0, $loop)->then(null, function (TimeoutException $e) {
+ echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL;
+});
+```
+
+Internally, the given `$time` value will be used to start a timer that will
+reject the promise once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+This function complements the [`resolve()`](#resolve) function
+and can be used as a basic building block for higher-level promise consumers.
+
+#### Reject cancellation
+
+You can explicitly `cancel()` the resulting timer promise at any time:
+
+```php
+$timer = Timer\reject(2.0, $loop);
+
+$timer->cancel();
+```
+
+This will abort the timer and *reject* with a `RuntimeException`.
+
+### TimeoutException
+
+The `TimeoutException` extends PHP's built-in `RuntimeException`.
+
+The `getTimeout()` method can be used to get the timeout value in seconds.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/promise-timer:^1.5
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/composer.json b/src/mpg25-instagram-api/vendor/react/promise-timer/composer.json
new file mode 100755
index 0000000..1fdaddd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "react/promise-timer",
+ "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
+ "keywords": ["Promise", "timeout", "timer", "event-loop", "ReactPHP", "async"],
+ "homepage": "https://github.com/reactphp/promise-timer",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "React\\Promise\\Timer\\": "src/" },
+ "files": [ "src/functions_include.php" ]
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Promise\\Timer\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.7.0 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/react/promise-timer/phpunit.xml.dist
new file mode 100755
index 0000000..bb79fba
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/phpunit.xml.dist
@@ -0,0 +1,19 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/src/TimeoutException.php b/src/mpg25-instagram-api/vendor/react/promise-timer/src/TimeoutException.php
new file mode 100755
index 0000000..18ea72f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/src/TimeoutException.php
@@ -0,0 +1,22 @@
+timeout = $timeout;
+ }
+
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/src/functions.php b/src/mpg25-instagram-api/vendor/react/promise-timer/src/functions.php
new file mode 100755
index 0000000..123a905
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/src/functions.php
@@ -0,0 +1,81 @@
+cancel();
+ $promise = null;
+ };
+ }
+
+ return new Promise(function ($resolve, $reject) use ($loop, $time, $promise) {
+ $timer = null;
+ $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $resolve($v);
+ }, function ($v) use (&$timer, $loop, $reject) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $reject($v);
+ });
+
+ // promise already resolved => no need to start timer
+ if ($timer === false) {
+ return;
+ }
+
+ // start timeout timer which will cancel the input promise
+ $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject) {
+ $reject(new TimeoutException($time, 'Timed out after ' . $time . ' seconds'));
+
+ // try to invoke cancellation handler of input promise and then clean
+ // reference in order to avoid garbage references in call stack.
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ $promise = null;
+ });
+ }, $canceller);
+}
+
+function resolve($time, LoopInterface $loop)
+{
+ return new Promise(function ($resolve) use ($loop, $time, &$timer) {
+ // resolve the promise when the timer fires in $time seconds
+ $timer = $loop->addTimer($time, function () use ($time, $resolve) {
+ $resolve($time);
+ });
+ }, function () use (&$timer, $loop) {
+ // cancelling this promise will cancel the timer, clean the reference
+ // in order to avoid garbage references in call stack and then reject.
+ $loop->cancelTimer($timer);
+ $timer = null;
+
+ throw new \RuntimeException('Timer cancelled');
+ });
+}
+
+function reject($time, LoopInterface $loop)
+{
+ return resolve($time, $loop)->then(function ($time) {
+ throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds');
+ });
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/src/functions_include.php b/src/mpg25-instagram-api/vendor/react/promise-timer/src/functions_include.php
new file mode 100755
index 0000000..1d5673a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/src/functions_include.php
@@ -0,0 +1,7 @@
+loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseExpiredIsPendingWithoutRunningLoop()
+ {
+ $promise = Timer\reject(-1, $this->loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseWillBeRejectedOnTimeout()
+ {
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testPromiseExpiredWillBeRejectedOnTimeout()
+ {
+ $promise = Timer\reject(-1, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testCancellingPromiseWillRejectTimer()
+ {
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testWaitingForPromiseToRejectDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\reject(0.01, $this->loop);
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPromiseDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\reject(0.01, $this->loop);
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/tests/FunctionResolveTest.php b/src/mpg25-instagram-api/vendor/react/promise-timer/tests/FunctionResolveTest.php
new file mode 100755
index 0000000..c4e2be7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/tests/FunctionResolveTest.php
@@ -0,0 +1,101 @@
+loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseExpiredIsPendingWithoutRunningLoop()
+ {
+ $promise = Timer\resolve(-1, $this->loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseWillBeResolvedOnTimeout()
+ {
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testPromiseExpiredWillBeResolvedOnTimeout()
+ {
+ $promise = Timer\resolve(-1, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testWillStartLoopTimer()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->with($this->equalTo(0.01));
+
+ Timer\resolve(0.01, $loop);
+ }
+
+ public function testCancellingPromiseWillCancelLoopTimer()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $timer = $this->getMockBuilder(interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
+
+ $promise = Timer\resolve(0.01, $loop);
+
+ $loop->expects($this->once())->method('cancelTimer')->with($this->equalTo($timer));
+
+ $promise->cancel();
+ }
+
+ public function testCancellingPromiseWillRejectTimer()
+ {
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testWaitingForPromiseToResolveDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\resolve(0.01, $this->loop);
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPromiseDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\resolve(0.01, $this->loop);
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/tests/FunctionTimeoutTest.php b/src/mpg25-instagram-api/vendor/react/promise-timer/tests/FunctionTimeoutTest.php
new file mode 100755
index 0000000..9652d1a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/tests/FunctionTimeoutTest.php
@@ -0,0 +1,286 @@
+loop);
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testResolvedExpiredWillResolveRightAway()
+ {
+ $promise = Promise\resolve();
+
+ $promise = Timer\timeout($promise, -1, $this->loop);
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testResolvedWillNotStartTimer()
+ {
+ $promise = Promise\resolve();
+
+ Timer\timeout($promise, 3, $this->loop);
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.5, $time);
+ }
+
+ public function testRejectedWillRejectRightAway()
+ {
+ $promise = Promise\reject();
+
+ $promise = Timer\timeout($promise, 3, $this->loop);
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testRejectedWillNotStartTimer()
+ {
+ $promise = Promise\reject();
+
+ Timer\timeout($promise, 3, $this->loop);
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.5, $time);
+ }
+
+ public function testPendingWillRejectOnTimeout()
+ {
+ $promise = $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testPendingCancellableWillBeCancelledThroughFollowerOnTimeout()
+ {
+ $cancellable = $this->getMockBuilder('React\Promise\CancellablePromiseInterface')->getMock();
+ $cancellable->expects($this->once())->method('cancel');
+
+ $promise = $this->getMockBuilder('React\Promise\CancellablePromiseInterface')->getMock();
+ $promise->expects($this->once())->method('then')->willReturn($cancellable);
+
+ Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ }
+
+ public function testCancelTimeoutWithoutCancellationhandlerWillNotCancelTimerAndWillNotReject()
+ {
+ $promise = new \React\Promise\Promise(function () { });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $timer = $this->getMockBuilder('React\EventLoop\Timer\TimerInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
+ $loop->expects($this->never())->method('cancelTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $timeout->cancel();
+
+ $this->expectPromisePending($timeout);
+ }
+
+ public function testResolvedPromiseWillNotStartTimer()
+ {
+ $promise = new \React\Promise\Promise(function ($resolve) { $resolve(true); });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $this->expectPromiseResolved($timeout);
+ }
+
+ public function testRejectedPromiseWillNotStartTimer()
+ {
+ $promise = Promise\reject(new \RuntimeException());
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillCancelGivenPromise()
+ {
+ $promise = new \React\Promise\Promise(function () { }, $this->expectCallableOnce());
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+ }
+
+ public function testCancelGivenPromiseWillReject()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillRejectIfGivenPromiseWillReject()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+
+ $this->expectPromiseRejected($promise);
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillResolveIfGivenPromiseWillResolve()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $resolve(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+
+ $this->expectPromiseResolved($promise);
+ $this->expectPromiseResolved($timeout);
+ }
+
+ public function testWaitingForPromiseToResolveBeforeTimeoutDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $promise = Timer\timeout($promise, 1.0, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToRejectBeforeTimeoutDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $promise = Timer\timeout($promise, 1.0, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToTimeoutDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToTimeoutWithoutCancellerDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { });
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToTimeoutWithNoOpCancellerDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { }, function () {
+ // no-op
+ });
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPromiseDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $promise = Timer\timeout($promise, 0.01, $loop);
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/tests/TestCase.php b/src/mpg25-instagram-api/vendor/react/promise-timer/tests/TestCase.php
new file mode 100755
index 0000000..9d8d49a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/tests/TestCase.php
@@ -0,0 +1,61 @@
+loop = Factory::create();
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ /**
+ * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
+ */
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Promise\Timer\CallableStub')->getMock();
+ }
+
+ protected function expectPromiseRejected($promise)
+ {
+ return $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ protected function expectPromiseResolved($promise)
+ {
+ return $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+ }
+
+ protected function expectPromisePending($promise)
+ {
+ return $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise-timer/tests/TimeoutExceptionTest.php b/src/mpg25-instagram-api/vendor/react/promise-timer/tests/TimeoutExceptionTest.php
new file mode 100755
index 0000000..e9bedd9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise-timer/tests/TimeoutExceptionTest.php
@@ -0,0 +1,15 @@
+assertEquals(10, $e->getTimeout());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/.gitignore b/src/mpg25-instagram-api/vendor/react/promise/.gitignore
new file mode 100755
index 0000000..5241c60
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/.gitignore
@@ -0,0 +1,5 @@
+composer.lock
+composer.phar
+phpunit.xml
+build/
+vendor/
diff --git a/src/mpg25-instagram-api/vendor/react/promise/.travis.yml b/src/mpg25-instagram-api/vendor/react/promise/.travis.yml
new file mode 100755
index 0000000..bcbe642
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/.travis.yml
@@ -0,0 +1,28 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - nightly # ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ allow_failures:
+ - php: hhvm
+ - php: nightly
+
+install:
+ - composer install
+
+script:
+ - ./vendor/bin/phpunit -v --coverage-text --coverage-clover=./build/logs/clover.xml
+
+after_script:
+ - if [ -f ./build/logs/clover.xml ]; then travis_retry composer require satooshi/php-coveralls --no-interaction --update-with-dependencies; fi
+ - if [ -f ./build/logs/clover.xml ]; then php vendor/bin/coveralls -v; fi
diff --git a/src/mpg25-instagram-api/vendor/react/promise/CHANGELOG.md b/src/mpg25-instagram-api/vendor/react/promise/CHANGELOG.md
new file mode 100755
index 0000000..11f007d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/CHANGELOG.md
@@ -0,0 +1,135 @@
+CHANGELOG for 2.x
+=================
+
+* 2.7.1 (2018-01-07)
+
+ * Fix: file_exists warning when resolving with long strings.
+ (#130 by @sbesselsen)
+ * Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
+ (#133 by @WyriHaximus)
+
+* 2.7.0 (2018-06-13)
+
+ * Feature: Improve memory consumption for pending promises by using static internal callbacks without binding to self.
+ (#124 by @clue)
+
+* 2.6.0 (2018-06-11)
+
+ * Feature: Significantly improve memory consumption and performance by only passing resolver args
+ to resolver and canceller if callback requires them. Also use static callbacks without
+ binding to promise, clean up canceller function reference when they are no longer
+ needed and hide resolver and canceller references from call stack on PHP 7+.
+ (#113, #115, #116, #117, #118, #119 and #123 by @clue)
+
+ These changes combined mean that rejecting promises with an `Exception` should
+ no longer cause any internal circular references which could cause some unexpected
+ memory growth in previous versions. By explicitly avoiding and explicitly
+ cleaning up said references, we can avoid relying on PHP's circular garbage collector
+ to kick in which significantly improves performance when rejecting many promises.
+
+ * Mark legacy progress support / notification API as deprecated
+ (#112 by @clue)
+
+ * Recommend rejecting promises by throwing an exception
+ (#114 by @jsor)
+
+ * Improve documentation to properly instantiate LazyPromise
+ (#121 by @holtkamp)
+
+ * Follower cancellation propagation was originally planned for this release
+ but has been reverted for now and is planned for a future release.
+ (#99 by @jsor and #122 by @clue)
+
+* 2.5.1 (2017-03-25)
+
+ * Fix circular references when resolving with a promise which follows
+ itself (#94).
+
+* 2.5.0 (2016-12-22)
+
+ * Revert automatic cancellation of pending collection promises once the
+ output promise resolves. This was introduced in 42d86b7 (PR #36, released
+ in [v2.3.0](https://github.com/reactphp/promise/releases/tag/v2.3.0)) and
+ was both unintended and backward incompatible.
+
+ If you need automatic cancellation, you can use something like:
+
+ ```php
+ function allAndCancel(array $promises)
+ {
+ return \React\Promise\all($promises)
+ ->always(function() use ($promises) {
+ foreach ($promises as $promise) {
+ if ($promise instanceof \React\Promise\CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+ });
+ }
+ ```
+ * `all()` and `map()` functions now preserve the order of the array (#77).
+ * Fix circular references when resolving a promise with itself (#71).
+
+* 2.4.1 (2016-05-03)
+
+ * Fix `some()` not cancelling pending promises when too much input promises
+ reject (16ff799).
+
+* 2.4.0 (2016-03-31)
+
+ * Support foreign thenables in `resolve()`.
+ Any object that provides a `then()` method is now assimilated to a trusted
+ promise that follows the state of this thenable (#52).
+ * Fix `some()` and `any()` for input arrays containing not enough items
+ (#34).
+
+* 2.3.0 (2016-03-24)
+
+ * Allow cancellation of promises returned by functions working on promise
+ collections (#36).
+ * Handle `\Throwable` in the same way as `\Exception` (#51 by @joshdifabio).
+
+* 2.2.2 (2016-02-26)
+
+ * Fix cancellation handlers called multiple times (#47 by @clue).
+
+* 2.2.1 (2015-07-03)
+
+ * Fix stack error when resolving a promise in its own fulfillment or
+ rejection handlers.
+
+* 2.2.0 (2014-12-30)
+
+ * Introduce new `ExtendedPromiseInterface` implemented by all promises.
+ * Add new `done()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `otherwise()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `always()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `progress()` method (part of the `ExtendedPromiseInterface`).
+ * Rename `Deferred::progress` to `Deferred::notify` to avoid confusion with
+ `ExtendedPromiseInterface::progress` (a `Deferred::progress` alias is
+ still available for backward compatibility)
+ * `resolve()` now always returns a `ExtendedPromiseInterface`.
+
+* 2.1.0 (2014-10-15)
+
+ * Introduce new `CancellablePromiseInterface` implemented by all promises.
+ * Add new `cancel()` method (part of the `CancellablePromiseInterface`).
+
+* 2.0.0 (2013-12-10)
+
+ New major release. The goal is to streamline the API and to make it more
+ compliant with other promise libraries and especially with the new upcoming
+ [ES6 promises specification](https://github.com/domenic/promises-unwrapping/).
+
+ * Add standalone Promise class.
+ * Add new `race()` function.
+ * BC break: Bump minimum PHP version to PHP 5.4.
+ * BC break: Remove `ResolverInterface` and `PromiseInterface` from
+ `Deferred`.
+ * BC break: Change signature of `PromiseInterface`.
+ * BC break: Remove `When` and `Util` classes and move static methods to
+ functions.
+ * BC break: `FulfilledPromise` and `RejectedPromise` now throw an exception
+ when initialized with a promise instead of a value/reason.
+ * BC break: `Deferred::resolve()` and `Deferred::reject()` no longer return
+ a promise.
diff --git a/src/mpg25-instagram-api/vendor/react/promise/LICENSE b/src/mpg25-instagram-api/vendor/react/promise/LICENSE
new file mode 100755
index 0000000..5919d20
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012-2016 Jan Sorgalla
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/react/promise/README.md b/src/mpg25-instagram-api/vendor/react/promise/README.md
new file mode 100755
index 0000000..3685566
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/README.md
@@ -0,0 +1,870 @@
+Promise
+=======
+
+A lightweight implementation of
+[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+[](http://travis-ci.org/reactphp/promise)
+[](https://coveralls.io/github/reactphp/promise?branch=master)
+
+Table of Contents
+-----------------
+
+1. [Introduction](#introduction)
+2. [Concepts](#concepts)
+ * [Deferred](#deferred)
+ * [Promise](#promise-1)
+3. [API](#api)
+ * [Deferred](#deferred-1)
+ * [Deferred::promise()](#deferredpromise)
+ * [Deferred::resolve()](#deferredresolve)
+ * [Deferred::reject()](#deferredreject)
+ * [Deferred::notify()](#deferrednotify)
+ * [PromiseInterface](#promiseinterface)
+ * [PromiseInterface::then()](#promiseinterfacethen)
+ * [ExtendedPromiseInterface](#extendedpromiseinterface)
+ * [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
+ * [ExtendedPromiseInterface::otherwise()](#extendedpromiseinterfaceotherwise)
+ * [ExtendedPromiseInterface::always()](#extendedpromiseinterfacealways)
+ * [ExtendedPromiseInterface::progress()](#extendedpromiseinterfaceprogress)
+ * [CancellablePromiseInterface](#cancellablepromiseinterface)
+ * [CancellablePromiseInterface::cancel()](#cancellablepromiseinterfacecancel)
+ * [Promise](#promise-2)
+ * [FulfilledPromise](#fulfilledpromise)
+ * [RejectedPromise](#rejectedpromise)
+ * [LazyPromise](#lazypromise)
+ * [Functions](#functions)
+ * [resolve()](#resolve)
+ * [reject()](#reject)
+ * [all()](#all)
+ * [race()](#race)
+ * [any()](#any)
+ * [some()](#some)
+ * [map()](#map)
+ * [reduce()](#reduce)
+ * [PromisorInterface](#promisorinterface)
+4. [Examples](#examples)
+ * [How to use Deferred](#how-to-use-deferred)
+ * [How promise forwarding works](#how-promise-forwarding-works)
+ * [Resolution forwarding](#resolution-forwarding)
+ * [Rejection forwarding](#rejection-forwarding)
+ * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
+ * [Progress event forwarding](#progress-event-forwarding)
+ * [done() vs. then()](#done-vs-then)
+5. [Install](#install)
+6. [Credits](#credits)
+7. [License](#license)
+
+Introduction
+------------
+
+Promise is a library implementing
+[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+It also provides several other useful promise-related concepts, such as joining
+multiple promises and mapping and reducing collections of promises.
+
+If you've never heard about promises before,
+[read this first](https://gist.github.com/3889970).
+
+Concepts
+--------
+
+### Deferred
+
+A **Deferred** represents a computation or unit of work that may not have
+completed yet. Typically (but not always), that computation will be something
+that executes asynchronously and completes at some point in the future.
+
+### Promise
+
+While a deferred represents the computation itself, a **Promise** represents
+the result of that computation. Thus, each deferred has a promise that acts as
+a placeholder for its actual result.
+
+API
+---
+
+### Deferred
+
+A deferred represents an operation whose resolution is pending. It has separate
+promise and resolver parts.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$promise = $deferred->promise();
+
+$deferred->resolve(mixed $value = null);
+$deferred->reject(mixed $reason = null);
+$deferred->notify(mixed $update = null);
+```
+
+The `promise` method returns the promise of the deferred.
+
+The `resolve` and `reject` methods control the state of the deferred.
+
+The deprecated `notify` method is for progress notification.
+
+The constructor of the `Deferred` accepts an optional `$canceller` argument.
+See [Promise](#promise-2) for more information.
+
+#### Deferred::promise()
+
+```php
+$promise = $deferred->promise();
+```
+
+Returns the promise of the deferred, which you can hand out to others while
+keeping the authority to modify its state to yourself.
+
+#### Deferred::resolve()
+
+```php
+$deferred->resolve(mixed $value = null);
+```
+
+Resolves the promise returned by `promise()`. All consumers are notified by
+having `$onFulfilled` (which they registered via `$promise->then()`) called with
+`$value`.
+
+If `$value` itself is a promise, the promise will transition to the state of
+this promise once it is resolved.
+
+#### Deferred::reject()
+
+```php
+$deferred->reject(mixed $reason = null);
+```
+
+Rejects the promise returned by `promise()`, signalling that the deferred's
+computation failed.
+All consumers are notified by having `$onRejected` (which they registered via
+`$promise->then()`) called with `$reason`.
+
+If `$reason` itself is a promise, the promise will be rejected with the outcome
+of this promise regardless whether it fulfills or rejects.
+
+#### Deferred::notify()
+
+> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
+
+```php
+$deferred->notify(mixed $update = null);
+```
+
+Triggers progress notifications, to indicate to consumers that the computation
+is making progress toward its result.
+
+All consumers are notified by having `$onProgress` (which they registered via
+`$promise->then()`) called with `$update`.
+
+### PromiseInterface
+
+The promise interface provides the common interface for all promise
+implementations.
+
+A promise represents an eventual outcome, which is either fulfillment (success)
+and an associated value, or rejection (failure) and an associated reason.
+
+Once in the fulfilled or rejected state, a promise becomes immutable.
+Neither its state nor its result (or error) can be modified.
+
+#### Implementations
+
+* [Promise](#promise-2)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+#### PromiseInterface::then()
+
+```php
+$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+```
+
+Transforms a promise's value by applying a function to the promise's fulfillment
+or rejection value. Returns a new promise for the transformed result.
+
+The `then()` method registers new fulfilled, rejection and progress handlers
+with a promise (all parameters are optional):
+
+ * `$onFulfilled` will be invoked once the promise is fulfilled and passed
+ the result as the first argument.
+ * `$onRejected` will be invoked once the promise is rejected and passed the
+ reason as the first argument.
+ * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
+ triggers progress notifications and passed a single argument (whatever it
+ wants) to indicate progress.
+
+It returns a new promise that will fulfill with the return value of either
+`$onFulfilled` or `$onRejected`, whichever is called, or will reject with
+the thrown exception if either throws.
+
+A promise makes the following guarantees about handlers registered in
+the same call to `then()`:
+
+ 1. Only one of `$onFulfilled` or `$onRejected` will be called,
+ never both.
+ 2. `$onFulfilled` and `$onRejected` will never be called more
+ than once.
+ 3. `$onProgress` (deprecated) may be called multiple times.
+
+#### See also
+
+* [resolve()](#resolve) - Creating a resolved promise
+* [reject()](#reject) - Creating a rejected promise
+* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
+* [done() vs. then()](#done-vs-then)
+
+### ExtendedPromiseInterface
+
+The ExtendedPromiseInterface extends the PromiseInterface with useful shortcut
+and utility methods which are not part of the Promises/A specification.
+
+#### Implementations
+
+* [Promise](#promise-1)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+#### ExtendedPromiseInterface::done()
+
+```php
+$promise->done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+```
+
+Consumes the promise's ultimate value if the promise fulfills, or handles the
+ultimate error.
+
+It will cause a fatal error if either `$onFulfilled` or `$onRejected` throw or
+return a rejected promise.
+
+Since the purpose of `done()` is consumption rather than transformation,
+`done()` always returns `null`.
+
+#### See also
+
+* [PromiseInterface::then()](#promiseinterfacethen)
+* [done() vs. then()](#done-vs-then)
+
+#### ExtendedPromiseInterface::otherwise()
+
+```php
+$promise->otherwise(callable $onRejected);
+```
+
+Registers a rejection handler for promise. It is a shortcut for:
+
+```php
+$promise->then(null, $onRejected);
+```
+
+Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
+only specific errors.
+
+```php
+$promise
+ ->otherwise(function (\RuntimeException $reason) {
+ // Only catch \RuntimeException instances
+ // All other types of errors will propagate automatically
+ })
+ ->otherwise(function ($reason) {
+ // Catch other errors
+ )};
+```
+
+#### ExtendedPromiseInterface::always()
+
+```php
+$newPromise = $promise->always(callable $onFulfilledOrRejected);
+```
+
+Allows you to execute "cleanup" type tasks in a promise chain.
+
+It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
+when the promise is either fulfilled or rejected.
+
+* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
+ `$newPromise` will fulfill with the same value as `$promise`.
+* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
+ rejected promise, `$newPromise` will reject with the thrown exception or
+ rejected promise's reason.
+* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
+ `$newPromise` will reject with the same reason as `$promise`.
+* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
+ rejected promise, `$newPromise` will reject with the thrown exception or
+ rejected promise's reason.
+
+`always()` behaves similarly to the synchronous finally statement. When combined
+with `otherwise()`, `always()` allows you to write code that is similar to the familiar
+synchronous catch/finally pair.
+
+Consider the following synchronous code:
+
+```php
+try {
+ return doSomething();
+} catch(\Exception $e) {
+ return handleError($e);
+} finally {
+ cleanup();
+}
+```
+
+Similar asynchronous code (with `doSomething()` that returns a promise) can be
+written:
+
+```php
+return doSomething()
+ ->otherwise('handleError')
+ ->always('cleanup');
+```
+
+#### ExtendedPromiseInterface::progress()
+
+> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
+
+```php
+$promise->progress(callable $onProgress);
+```
+
+Registers a handler for progress updates from promise. It is a shortcut for:
+
+```php
+$promise->then(null, null, $onProgress);
+```
+
+### CancellablePromiseInterface
+
+A cancellable promise provides a mechanism for consumers to notify the creator
+of the promise that they are not longer interested in the result of an
+operation.
+
+#### CancellablePromiseInterface::cancel()
+
+``` php
+$promise->cancel();
+```
+
+The `cancel()` method notifies the creator of the promise that there is no
+further interest in the results of the operation.
+
+Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
+a promise has no effect.
+
+#### Implementations
+
+* [Promise](#promise-1)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+### Promise
+
+Creates a promise whose state is controlled by the functions passed to
+`$resolver`.
+
+```php
+$resolver = function (callable $resolve, callable $reject, callable $notify) {
+ // Do some work, possibly asynchronously, and then
+ // resolve or reject. You can notify of progress events (deprecated)
+ // along the way if you want/need.
+
+ $resolve($awesomeResult);
+ // or throw new Exception('Promise rejected');
+ // or $resolve($anotherPromise);
+ // or $reject($nastyError);
+ // or $notify($progressNotification);
+};
+
+$canceller = function () {
+ // Cancel/abort any running operations like network connections, streams etc.
+
+ // Reject promise by throwing an exception
+ throw new Exception('Promise cancelled');
+};
+
+$promise = new React\Promise\Promise($resolver, $canceller);
+```
+
+The promise constructor receives a resolver function and an optional canceller
+function which both will be called with 3 arguments:
+
+ * `$resolve($value)` - Primary function that seals the fate of the
+ returned promise. Accepts either a non-promise value, or another promise.
+ When called with a non-promise value, fulfills promise with that value.
+ When called with another promise, e.g. `$resolve($otherPromise)`, promise's
+ fate will be equivalent to that of `$otherPromise`.
+ * `$reject($reason)` - Function that rejects the promise. It is recommended to
+ just throw an exception instead of using `$reject()`.
+ * `$notify($update)` - Deprecated function that issues progress events for the promise.
+
+If the resolver or canceller throw an exception, the promise will be rejected
+with that thrown exception as the rejection reason.
+
+The resolver function will be called immediately, the canceller function only
+once all consumers called the `cancel()` method of the promise.
+
+### FulfilledPromise
+
+Creates a already fulfilled promise.
+
+```php
+$promise = React\Promise\FulfilledPromise($value);
+```
+
+Note, that `$value` **cannot** be a promise. It's recommended to use
+[resolve()](#resolve) for creating resolved promises.
+
+### RejectedPromise
+
+Creates a already rejected promise.
+
+```php
+$promise = React\Promise\RejectedPromise($reason);
+```
+
+Note, that `$reason` **cannot** be a promise. It's recommended to use
+[reject()](#reject) for creating rejected promises.
+
+### LazyPromise
+
+Creates a promise which will be lazily initialized by `$factory` once a consumer
+calls the `then()` method.
+
+```php
+$factory = function () {
+ $deferred = new React\Promise\Deferred();
+
+ // Do some heavy stuff here and resolve the deferred once completed
+
+ return $deferred->promise();
+};
+
+$promise = new React\Promise\LazyPromise($factory);
+
+// $factory will only be executed once we call then()
+$promise->then(function ($value) {
+});
+```
+
+### Functions
+
+Useful functions for creating, joining, mapping and reducing collections of
+promises.
+
+All functions working on promise collections (like `all()`, `race()`, `some()`
+etc.) support cancellation. This means, if you call `cancel()` on the returned
+promise, all promises in the collection are cancelled. If the collection itself
+is a promise which resolves to an array, this promise is also cancelled.
+
+#### resolve()
+
+```php
+$promise = React\Promise\resolve(mixed $promiseOrValue);
+```
+
+Creates a promise for the supplied `$promiseOrValue`.
+
+If `$promiseOrValue` is a value, it will be the resolution value of the
+returned promise.
+
+If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
+a trusted promise that follows the state of the thenable is returned.
+
+If `$promiseOrValue` is a promise, it will be returned as is.
+
+Note: The promise returned is always a promise implementing
+[ExtendedPromiseInterface](#extendedpromiseinterface). If you pass in a custom
+promise which only implements [PromiseInterface](#promiseinterface), this
+promise will be assimilated to a extended promise following `$promiseOrValue`.
+
+#### reject()
+
+```php
+$promise = React\Promise\reject(mixed $promiseOrValue);
+```
+
+Creates a rejected promise for the supplied `$promiseOrValue`.
+
+If `$promiseOrValue` is a value, it will be the rejection value of the
+returned promise.
+
+If `$promiseOrValue` is a promise, its completion value will be the rejected
+value of the returned promise.
+
+This can be useful in situations where you need to reject a promise without
+throwing an exception. For example, it allows you to propagate a rejection with
+the value of another promise.
+
+#### all()
+
+```php
+$promise = React\Promise\all(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Returns a promise that will resolve only once all the items in
+`$promisesOrValues` have resolved. The resolution value of the returned promise
+will be an array containing the resolution values of each of the items in
+`$promisesOrValues`.
+
+#### race()
+
+```php
+$promise = React\Promise\race(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Initiates a competitive race that allows one winner. Returns a promise which is
+resolved in the same way the first settled promise resolves.
+
+#### any()
+
+```php
+$promise = React\Promise\any(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Returns a promise that will resolve when any one of the items in
+`$promisesOrValues` resolves. The resolution value of the returned promise
+will be the resolution value of the triggering item.
+
+The returned promise will only reject if *all* items in `$promisesOrValues` are
+rejected. The rejection value will be an array of all rejection reasons.
+
+The returned promise will also reject with a `React\Promise\Exception\LengthException`
+if `$promisesOrValues` contains 0 items.
+
+#### some()
+
+```php
+$promise = React\Promise\some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany);
+```
+
+Returns a promise that will resolve when `$howMany` of the supplied items in
+`$promisesOrValues` resolve. The resolution value of the returned promise
+will be an array of length `$howMany` containing the resolution values of the
+triggering items.
+
+The returned promise will reject if it becomes impossible for `$howMany` items
+to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
+reject). The rejection value will be an array of
+`(count($promisesOrValues) - $howMany) + 1` rejection reasons.
+
+The returned promise will also reject with a `React\Promise\Exception\LengthException`
+if `$promisesOrValues` contains less items than `$howMany`.
+
+#### map()
+
+```php
+$promise = React\Promise\map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc);
+```
+
+Traditional map function, similar to `array_map()`, but allows input to contain
+promises and/or values, and `$mapFunc` may return either a value or a promise.
+
+The map function receives each item as argument, where item is a fully resolved
+value of a promise or value in `$promisesOrValues`.
+
+#### reduce()
+
+```php
+$promise = React\Promise\reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null);
+```
+
+Traditional reduce function, similar to `array_reduce()`, but input may contain
+promises and/or values, and `$reduceFunc` may return either a value or a
+promise, *and* `$initialValue` may be a promise or a value for the starting
+value.
+
+### PromisorInterface
+
+The `React\Promise\PromisorInterface` provides a common interface for objects
+that provide a promise. `React\Promise\Deferred` implements it, but since it
+is part of the public API anyone can implement it.
+
+Examples
+--------
+
+### How to use Deferred
+
+```php
+function getAwesomeResultPromise()
+{
+ $deferred = new React\Promise\Deferred();
+
+ // Execute a Node.js-style function using the callback pattern
+ computeAwesomeResultAsynchronously(function ($error, $result) use ($deferred) {
+ if ($error) {
+ $deferred->reject($error);
+ } else {
+ $deferred->resolve($result);
+ }
+ });
+
+ // Return the promise
+ return $deferred->promise();
+}
+
+getAwesomeResultPromise()
+ ->then(
+ function ($value) {
+ // Deferred resolved, do something with $value
+ },
+ function ($reason) {
+ // Deferred rejected, do something with $reason
+ },
+ function ($update) {
+ // Progress notification triggered, do something with $update
+ }
+ );
+```
+
+### How promise forwarding works
+
+A few simple examples to show how the mechanics of Promises/A forwarding works.
+These examples are contrived, of course, and in real usage, promise chains will
+typically be spread across several function calls, or even several levels of
+your application architecture.
+
+#### Resolution forwarding
+
+Resolved promises forward resolution values to the next promise.
+The first promise, `$deferred->promise()`, will resolve with the value passed
+to `$deferred->resolve()` below.
+
+Each call to `then()` returns a new promise that will resolve with the return
+value of the previous handler. This creates a promise "pipeline".
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ // $x will be the value passed to $deferred->resolve() below
+ // and returns a *new promise* for $x + 1
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 2
+ // This handler receives the return value of the
+ // previous handler.
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 3
+ // This handler receives the return value of the
+ // previous handler.
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 4
+ // This handler receives the return value of the
+ // previous handler.
+ echo 'Resolve ' . $x;
+ });
+
+$deferred->resolve(1); // Prints "Resolve 4"
+```
+
+#### Rejection forwarding
+
+Rejected promises behave similarly, and also work similarly to try/catch:
+When you catch an exception, you must rethrow for it to propagate.
+
+Similarly, when you handle a rejected promise, to propagate the rejection,
+"rethrow" it by either returning a rejected promise, or actually throwing
+(since promise translates thrown exceptions into rejections)
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ throw new \Exception($x + 1);
+ })
+ ->otherwise(function (\Exception $x) {
+ // Propagate the rejection
+ throw $x;
+ })
+ ->otherwise(function (\Exception $x) {
+ // Can also propagate by returning another rejection
+ return React\Promise\reject(
+ new \Exception($x->getMessage() + 1)
+ );
+ })
+ ->otherwise(function ($x) {
+ echo 'Reject ' . $x->getMessage(); // 3
+ });
+
+$deferred->resolve(1); // Prints "Reject 3"
+```
+
+#### Mixed resolution and rejection forwarding
+
+Just like try/catch, you can choose to propagate or not. Mixing resolutions and
+rejections will still forward handler results in a predictable way.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ throw new \Exception($x + 1);
+ })
+ ->otherwise(function (\Exception $x) {
+ // Handle the rejection, and don't propagate.
+ // This is like catch without a rethrow
+ return $x->getMessage() + 1;
+ })
+ ->then(function ($x) {
+ echo 'Mixed ' . $x; // 4
+ });
+
+$deferred->resolve(1); // Prints "Mixed 4"
+```
+
+#### Progress event forwarding
+
+> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
+
+In the same way as resolution and rejection handlers, your progress handler
+**MUST** return a progress event to be propagated to the next link in the chain.
+If you return nothing, `null` will be propagated.
+
+Also in the same way as resolutions and rejections, if you don't register a
+progress handler, the update will be propagated through.
+
+If your progress handler throws an exception, the exception will be propagated
+to the next link in the chain. The best thing to do is to ensure your progress
+handlers do not throw exceptions.
+
+This gives you the opportunity to transform progress events at each step in the
+chain so that they are meaningful to the next step. It also allows you to choose
+not to transform them, and simply let them propagate untransformed, by not
+registering a progress handler.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->progress(function ($update) {
+ return $update + 1;
+ })
+ ->progress(function ($update) {
+ echo 'Progress ' . $update; // 2
+ });
+
+$deferred->notify(1); // Prints "Progress 2"
+```
+
+### done() vs. then()
+
+The golden rule is:
+
+ Either return your promise, or call done() on it.
+
+At a first glance, `then()` and `done()` seem very similar. However, there are
+important distinctions.
+
+The intent of `then()` is to transform a promise's value and to pass or return
+a new promise for the transformed value along to other parts of your code.
+
+The intent of `done()` is to consume a promise's value, transferring
+responsibility for the value to your code.
+
+In addition to transforming a value, `then()` allows you to recover from, or
+propagate intermediate errors. Any errors that are not handled will be caught
+by the promise machinery and used to reject the promise returned by `then()`.
+
+Calling `done()` transfers all responsibility for errors to your code. If an
+error (either a thrown exception or returned rejection) escapes the
+`$onFulfilled` or `$onRejected` callbacks you provide to done, it will be
+rethrown in an uncatchable way causing a fatal error.
+
+```php
+function getJsonResult()
+{
+ return queryApi()
+ ->then(
+ // Transform API results to an object
+ function ($jsonResultString) {
+ return json_decode($jsonResultString);
+ },
+ // Transform API errors to an exception
+ function ($jsonErrorString) {
+ $object = json_decode($jsonErrorString);
+ throw new ApiErrorException($object->errorMessage);
+ }
+ );
+}
+
+// Here we provide no rejection handler. If the promise returned has been
+// rejected, the ApiErrorException will be thrown
+getJsonResult()
+ ->done(
+ // Consume transformed object
+ function ($jsonResultObject) {
+ // Do something with $jsonResultObject
+ }
+ );
+
+// Here we provide a rejection handler which will either throw while debugging
+// or log the exception
+getJsonResult()
+ ->done(
+ function ($jsonResultObject) {
+ // Do something with $jsonResultObject
+ },
+ function (ApiErrorException $exception) {
+ if (isDebug()) {
+ throw $exception;
+ } else {
+ logException($exception);
+ }
+ }
+ );
+```
+
+Note that if a rejection value is not an instance of `\Exception`, it will be
+wrapped in an exception of the type `React\Promise\UnhandledRejectionException`.
+
+You can get the original rejection reason by calling `$exception->getReason()`.
+
+Install
+-------
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/promise:^2.7
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.4 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project due to its vast
+performance improvements.
+
+Credits
+-------
+
+Promise is a port of [when.js](https://github.com/cujojs/when)
+by [Brian Cavalier](https://github.com/briancavalier).
+
+Also, large parts of the documentation have been ported from the when.js
+[Wiki](https://github.com/cujojs/when/wiki) and the
+[API docs](https://github.com/cujojs/when/blob/master/docs/api.md).
+
+License
+-------
+
+Released under the [MIT](LICENSE) license.
diff --git a/src/mpg25-instagram-api/vendor/react/promise/composer.json b/src/mpg25-instagram-api/vendor/react/promise/composer.json
new file mode 100755
index 0000000..2fc4809
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "react/promise",
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "license": "MIT",
+ "authors": [
+ {"name": "Jan Sorgalla", "email": "jsorgalla@gmail.com"}
+ ],
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Promise\\": "tests/fixtures"
+ }
+ },
+ "keywords": [
+ "promise",
+ "promises"
+ ]
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/react/promise/phpunit.xml.dist
new file mode 100755
index 0000000..b9a689d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/phpunit.xml.dist
@@ -0,0 +1,28 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+ ./src/functions_include.php
+
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/react/promise/src/CancellablePromiseInterface.php b/src/mpg25-instagram-api/vendor/react/promise/src/CancellablePromiseInterface.php
new file mode 100755
index 0000000..896db2d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/src/CancellablePromiseInterface.php
@@ -0,0 +1,11 @@
+started) {
+ return;
+ }
+
+ $this->started = true;
+ $this->drain();
+ }
+
+ public function enqueue($cancellable)
+ {
+ if (!\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
+ return;
+ }
+
+ $length = \array_push($this->queue, $cancellable);
+
+ if ($this->started && 1 === $length) {
+ $this->drain();
+ }
+ }
+
+ private function drain()
+ {
+ for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
+ $cancellable = $this->queue[$i];
+
+ $exception = null;
+
+ try {
+ $cancellable->cancel();
+ } catch (\Throwable $exception) {
+ } catch (\Exception $exception) {
+ }
+
+ unset($this->queue[$i]);
+
+ if ($exception) {
+ throw $exception;
+ }
+ }
+
+ $this->queue = [];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/src/Deferred.php b/src/mpg25-instagram-api/vendor/react/promise/src/Deferred.php
new file mode 100755
index 0000000..3ca034b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/src/Deferred.php
@@ -0,0 +1,65 @@
+canceller = $canceller;
+ }
+
+ public function promise()
+ {
+ if (null === $this->promise) {
+ $this->promise = new Promise(function ($resolve, $reject, $notify) {
+ $this->resolveCallback = $resolve;
+ $this->rejectCallback = $reject;
+ $this->notifyCallback = $notify;
+ }, $this->canceller);
+ $this->canceller = null;
+ }
+
+ return $this->promise;
+ }
+
+ public function resolve($value = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->resolveCallback, $value);
+ }
+
+ public function reject($reason = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->rejectCallback, $reason);
+ }
+
+ /**
+ * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
+ * @param mixed $update
+ */
+ public function notify($update = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->notifyCallback, $update);
+ }
+
+ /**
+ * @deprecated 2.2.0
+ * @see Deferred::notify()
+ */
+ public function progress($update = null)
+ {
+ $this->notify($update);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/src/Exception/LengthException.php b/src/mpg25-instagram-api/vendor/react/promise/src/Exception/LengthException.php
new file mode 100755
index 0000000..775c48d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/src/Exception/LengthException.php
@@ -0,0 +1,7 @@
+value = $value;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return $this;
+ }
+
+ try {
+ return resolve($onFulfilled($this->value));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return;
+ }
+
+ $result = $onFulfilled($this->value);
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this;
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/src/LazyPromise.php b/src/mpg25-instagram-api/vendor/react/promise/src/LazyPromise.php
new file mode 100755
index 0000000..7546524
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/src/LazyPromise.php
@@ -0,0 +1,63 @@
+factory = $factory;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->promise()->otherwise($onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->promise()->always($onFulfilledOrRejected);
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->promise()->progress($onProgress);
+ }
+
+ public function cancel()
+ {
+ return $this->promise()->cancel();
+ }
+
+ /**
+ * @internal
+ * @see Promise::settle()
+ */
+ public function promise()
+ {
+ if (null === $this->promise) {
+ try {
+ $this->promise = resolve(\call_user_func($this->factory));
+ } catch (\Throwable $exception) {
+ $this->promise = new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ $this->promise = new RejectedPromise($exception);
+ }
+ }
+
+ return $this->promise;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/src/Promise.php b/src/mpg25-instagram-api/vendor/react/promise/src/Promise.php
new file mode 100755
index 0000000..33759e6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/src/Promise.php
@@ -0,0 +1,256 @@
+canceller = $canceller;
+
+ // Explicitly overwrite arguments with null values before invoking
+ // resolver function. This ensure that these arguments do not show up
+ // in the stack trace in PHP 7+ only.
+ $cb = $resolver;
+ $resolver = $canceller = null;
+ $this->call($cb);
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ if (null === $this->canceller) {
+ return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
+ }
+
+ // This promise has a canceller, so we create a new child promise which
+ // has a canceller that invokes the parent canceller if all other
+ // followers are also cancelled. We keep a reference to this promise
+ // instance for the static canceller function and clear this to avoid
+ // keeping a cyclic reference between parent and follower.
+ $parent = $this;
+ ++$parent->requiredCancelRequests;
+
+ return new static(
+ $this->resolver($onFulfilled, $onRejected, $onProgress),
+ static function () use (&$parent) {
+ if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
+ $parent->cancel();
+ }
+
+ $parent = null;
+ }
+ );
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
+ $promise
+ ->done($onFulfilled, $onRejected);
+ };
+
+ if ($onProgress) {
+ $this->progressHandlers[] = $onProgress;
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, static function ($reason) use ($onRejected) {
+ if (!_checkTypehint($onRejected, $reason)) {
+ return new RejectedPromise($reason);
+ }
+
+ return $onRejected($reason);
+ });
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(static function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ }, static function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->then(null, null, $onProgress);
+ }
+
+ public function cancel()
+ {
+ if (null === $this->canceller || null !== $this->result) {
+ return;
+ }
+
+ $canceller = $this->canceller;
+ $this->canceller = null;
+
+ $this->call($canceller);
+ }
+
+ private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
+ if ($onProgress) {
+ $progressHandler = static function ($update) use ($notify, $onProgress) {
+ try {
+ $notify($onProgress($update));
+ } catch (\Throwable $e) {
+ $notify($e);
+ } catch (\Exception $e) {
+ $notify($e);
+ }
+ };
+ } else {
+ $progressHandler = $notify;
+ }
+
+ $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
+ $promise
+ ->then($onFulfilled, $onRejected)
+ ->done($resolve, $reject, $progressHandler);
+ };
+
+ $this->progressHandlers[] = $progressHandler;
+ };
+ }
+
+ private function reject($reason = null)
+ {
+ if (null !== $this->result) {
+ return;
+ }
+
+ $this->settle(reject($reason));
+ }
+
+ private function settle(ExtendedPromiseInterface $promise)
+ {
+ $promise = $this->unwrap($promise);
+
+ if ($promise === $this) {
+ $promise = new RejectedPromise(
+ new \LogicException('Cannot resolve a promise with itself.')
+ );
+ }
+
+ $handlers = $this->handlers;
+
+ $this->progressHandlers = $this->handlers = [];
+ $this->result = $promise;
+ $this->canceller = null;
+
+ foreach ($handlers as $handler) {
+ $handler($promise);
+ }
+ }
+
+ private function unwrap($promise)
+ {
+ $promise = $this->extract($promise);
+
+ while ($promise instanceof self && null !== $promise->result) {
+ $promise = $this->extract($promise->result);
+ }
+
+ return $promise;
+ }
+
+ private function extract($promise)
+ {
+ if ($promise instanceof LazyPromise) {
+ $promise = $promise->promise();
+ }
+
+ return $promise;
+ }
+
+ private function call(callable $cb)
+ {
+ // Explicitly overwrite argument with null value. This ensure that this
+ // argument does not show up in the stack trace in PHP 7+ only.
+ $callback = $cb;
+ $cb = null;
+
+ // Use reflection to inspect number of arguments expected by this callback.
+ // We did some careful benchmarking here: Using reflection to avoid unneeded
+ // function arguments is actually faster than blindly passing them.
+ // Also, this helps avoiding unnecessary function arguments in the call stack
+ // if the callback creates an Exception (creating garbage cycles).
+ if (\is_array($callback)) {
+ $ref = new \ReflectionMethod($callback[0], $callback[1]);
+ } elseif (\is_object($callback) && !$callback instanceof \Closure) {
+ $ref = new \ReflectionMethod($callback, '__invoke');
+ } else {
+ $ref = new \ReflectionFunction($callback);
+ }
+ $args = $ref->getNumberOfParameters();
+
+ try {
+ if ($args === 0) {
+ $callback();
+ } else {
+ // Keep references to this promise instance for the static resolve/reject functions.
+ // By using static callbacks that are not bound to this instance
+ // and passing the target promise instance by reference, we can
+ // still execute its resolving logic and still clear this
+ // reference when settling the promise. This helps avoiding
+ // garbage cycles if any callback creates an Exception.
+ // These assumptions are covered by the test suite, so if you ever feel like
+ // refactoring this, go ahead, any alternative suggestions are welcome!
+ $target =& $this;
+ $progressHandlers =& $this->progressHandlers;
+
+ $callback(
+ static function ($value = null) use (&$target) {
+ if ($target !== null) {
+ $target->settle(resolve($value));
+ $target = null;
+ }
+ },
+ static function ($reason = null) use (&$target) {
+ if ($target !== null) {
+ $target->reject($reason);
+ $target = null;
+ }
+ },
+ static function ($update = null) use (&$progressHandlers) {
+ foreach ($progressHandlers as $handler) {
+ $handler($update);
+ }
+ }
+ );
+ }
+ } catch (\Throwable $e) {
+ $target = null;
+ $this->reject($e);
+ } catch (\Exception $e) {
+ $target = null;
+ $this->reject($e);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/src/PromiseInterface.php b/src/mpg25-instagram-api/vendor/react/promise/src/PromiseInterface.php
new file mode 100755
index 0000000..fcd763d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/src/PromiseInterface.php
@@ -0,0 +1,14 @@
+reason = $reason;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ return $this;
+ }
+
+ try {
+ return resolve($onRejected($this->reason));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ throw UnhandledRejectionException::resolve($this->reason);
+ }
+
+ $result = $onRejected($this->reason);
+
+ if ($result instanceof self) {
+ throw UnhandledRejectionException::resolve($result->reason);
+ }
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ if (!_checkTypehint($onRejected, $this->reason)) {
+ return $this;
+ }
+
+ return $this->then(null, $onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(null, function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/src/UnhandledRejectionException.php b/src/mpg25-instagram-api/vendor/react/promise/src/UnhandledRejectionException.php
new file mode 100755
index 0000000..e7fe2f7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/src/UnhandledRejectionException.php
@@ -0,0 +1,31 @@
+reason = $reason;
+
+ $message = \sprintf('Unhandled Rejection: %s', \json_encode($reason));
+
+ parent::__construct($message, 0);
+ }
+
+ public function getReason()
+ {
+ return $this->reason;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/src/functions.php b/src/mpg25-instagram-api/vendor/react/promise/src/functions.php
new file mode 100755
index 0000000..c549e4e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/src/functions.php
@@ -0,0 +1,246 @@
+then($resolve, $reject, $notify);
+ }, $canceller);
+ }
+
+ return new FulfilledPromise($promiseOrValue);
+}
+
+function reject($promiseOrValue = null)
+{
+ if ($promiseOrValue instanceof PromiseInterface) {
+ return resolve($promiseOrValue)->then(function ($value) {
+ return new RejectedPromise($value);
+ });
+ }
+
+ return new RejectedPromise($promiseOrValue);
+}
+
+function all($promisesOrValues)
+{
+ return map($promisesOrValues, function ($val) {
+ return $val;
+ });
+}
+
+function race($promisesOrValues)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) {
+ if (!is_array($array) || !$array) {
+ $resolve();
+ return;
+ }
+
+ foreach ($array as $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($resolve, $reject, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function any($promisesOrValues)
+{
+ return some($promisesOrValues, 1)
+ ->then(function ($val) {
+ return \array_shift($val);
+ });
+}
+
+function some($promisesOrValues, $howMany)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array) || $howMany < 1) {
+ $resolve([]);
+ return;
+ }
+
+ $len = \count($array);
+
+ if ($len < $howMany) {
+ throw new Exception\LengthException(
+ \sprintf(
+ 'Input array must contain at least %d item%s but contains only %s item%s.',
+ $howMany,
+ 1 === $howMany ? '' : 's',
+ $len,
+ 1 === $len ? '' : 's'
+ )
+ );
+ }
+
+ $toResolve = $howMany;
+ $toReject = ($len - $toResolve) + 1;
+ $values = [];
+ $reasons = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $values[$i] = $val;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ };
+
+ $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $reasons[$i] = $reason;
+
+ if (0 === --$toReject) {
+ $reject($reasons);
+ }
+ };
+
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($fulfiller, $rejecter, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function map($promisesOrValues, callable $mapFunc)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array) || !$array) {
+ $resolve([]);
+ return;
+ }
+
+ $toResolve = \count($array);
+ $values = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+ $values[$i] = null;
+
+ resolve($promiseOrValue)
+ ->then($mapFunc)
+ ->done(
+ function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
+ $values[$i] = $mapped;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ },
+ $reject,
+ $notify
+ );
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array)) {
+ $array = [];
+ }
+
+ $total = \count($array);
+ $i = 0;
+
+ // Wrap the supplied $reduceFunc with one that handles promises and then
+ // delegates to the supplied.
+ $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
+ $cancellationQueue->enqueue($val);
+
+ return $current
+ ->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
+ return resolve($val)
+ ->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
+ return $reduceFunc($c, $value, $i++, $total);
+ });
+ });
+ };
+
+ $cancellationQueue->enqueue($initialValue);
+
+ \array_reduce($array, $wrappedReduceFunc, resolve($initialValue))
+ ->done($resolve, $reject, $notify);
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+// Internal functions
+function _checkTypehint(callable $callback, $object)
+{
+ if (!\is_object($object)) {
+ return true;
+ }
+
+ if (\is_array($callback)) {
+ $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
+ } elseif (\is_object($callback) && !$callback instanceof \Closure) {
+ $callbackReflection = new \ReflectionMethod($callback, '__invoke');
+ } else {
+ $callbackReflection = new \ReflectionFunction($callback);
+ }
+
+ $parameters = $callbackReflection->getParameters();
+
+ if (!isset($parameters[0])) {
+ return true;
+ }
+
+ $expectedException = $parameters[0];
+
+ if (!$expectedException->getClass()) {
+ return true;
+ }
+
+ return $expectedException->getClass()->isInstance($object);
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/src/functions_include.php b/src/mpg25-instagram-api/vendor/react/promise/src/functions_include.php
new file mode 100755
index 0000000..bd0c54f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/src/functions_include.php
@@ -0,0 +1,5 @@
+enqueue($p);
+
+ $cancellationQueue();
+
+ $this->assertTrue($p->cancelCalled);
+ }
+
+ /** @test */
+ public function ignoresSimpleCancellable()
+ {
+ $p = new SimpleTestCancellable();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($p);
+
+ $cancellationQueue();
+
+ $this->assertFalse($p->cancelCalled);
+ }
+
+ /** @test */
+ public function callsCancelOnPromisesEnqueuedBeforeStart()
+ {
+ $d1 = $this->getCancellableDeferred();
+ $d2 = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($d1->promise());
+ $cancellationQueue->enqueue($d2->promise());
+
+ $cancellationQueue();
+ }
+
+ /** @test */
+ public function callsCancelOnPromisesEnqueuedAfterStart()
+ {
+ $d1 = $this->getCancellableDeferred();
+ $d2 = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+
+ $cancellationQueue();
+
+ $cancellationQueue->enqueue($d2->promise());
+ $cancellationQueue->enqueue($d1->promise());
+ }
+
+ /** @test */
+ public function doesNotCallCancelTwiceWhenStartedTwice()
+ {
+ $d = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($d->promise());
+
+ $cancellationQueue();
+ $cancellationQueue();
+ }
+
+ /** @test */
+ public function rethrowsExceptionsThrownFromCancel()
+ {
+ $this->setExpectedException('\Exception', 'test');
+
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel')
+ ->will($this->throwException(new \Exception('test')));
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($mock);
+
+ $cancellationQueue();
+ }
+
+ private function getCancellableDeferred()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return new Deferred($mock);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/DeferredTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/DeferredTest.php
new file mode 100755
index 0000000..8ee40b8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/DeferredTest.php
@@ -0,0 +1,112 @@
+ [$d, 'promise'],
+ 'resolve' => [$d, 'resolve'],
+ 'reject' => [$d, 'reject'],
+ 'notify' => [$d, 'progress'],
+ 'settle' => [$d, 'resolve'],
+ ]);
+ }
+
+ /** @test */
+ public function progressIsAnAliasForNotify()
+ {
+ $deferred = new Deferred();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $deferred->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $deferred->progress($sentinel);
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $deferred->promise()->cancel();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfParentCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $deferred->promise()->then()->cancel();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenceAndExplicitlyRejectWithException()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function () use (&$deferred) { });
+ $deferred->reject(new \Exception('foo'));
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferred()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred();
+ $deferred->promise();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithUnusedCanceller()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function () { });
+ $deferred->promise();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithNoopCanceller()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function () { });
+ $deferred->promise()->cancel();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FulfilledPromiseTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FulfilledPromiseTest.php
new file mode 100755
index 0000000..f5a2da8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FulfilledPromiseTest.php
@@ -0,0 +1,76 @@
+ function () use (&$promise) {
+ if (!$promise) {
+ throw new \LogicException('FulfilledPromise must be resolved before obtaining the promise');
+ }
+
+ return $promise;
+ },
+ 'resolve' => function ($value = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new FulfilledPromise($value);
+ }
+ },
+ 'reject' => function () {
+ throw new \LogicException('You cannot call reject() for React\Promise\FulfilledPromise');
+ },
+ 'notify' => function () {
+ // no-op
+ },
+ 'settle' => function ($value = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new FulfilledPromise($value);
+ }
+ },
+ ]);
+ }
+
+ /** @test */
+ public function shouldThrowExceptionIfConstructedWithAPromise()
+ {
+ $this->setExpectedException('\InvalidArgumentException');
+
+ return new FulfilledPromise(new FulfilledPromise());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithAlwaysFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new FulfilledPromise(1);
+ $promise->always(function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithThenFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new FulfilledPromise(1);
+ $promise = $promise->then(function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionAllTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionAllTest.php
new file mode 100755
index 0000000..74c1d7c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionAllTest.php
@@ -0,0 +1,114 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ all([])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all([1, 2, 3])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all([resolve(1), resolve(2), resolve(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([null, 1, null, 1, 1]));
+
+ all([null, 1, null, 1, 1])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfAnyInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ all([resolve(1), reject(2), resolve(3)])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all(resolve([1, 2, 3]))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ all(resolve(1))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ $deferred = new Deferred();
+
+ all([resolve(1), $deferred->promise(), resolve(3)])
+ ->then($mock);
+
+ $deferred->resolve(2);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionAnyTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionAnyTest.php
new file mode 100755
index 0000000..140b551
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionAnyTest.php
@@ -0,0 +1,204 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage();
+ })
+ );
+
+ any([])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullWithNonArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(null)
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithAnInputValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([1, 2, 3])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithAPromisedInputValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([resolve(1), resolve(2), resolve(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWithAllRejectedInputValuesIfAllInputsAreRejected()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([0 => 1, 1 => 2, 2 => 3]));
+
+ any([reject(1), reject(2), reject(3)])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWhenFirstInputPromiseResolves()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([resolve(1), reject(2), reject(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any(resolve([1, 2, 3]))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(resolve(1))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldNotRelyOnArryIndexesWhenUnwrappingToASingleResolutionValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+
+ any(['abc' => $d1->promise(), 1 => $d2->promise()])
+ ->then($mock);
+
+ $d2->resolve(2);
+ $d1->resolve(1);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(reject())
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ any($mock)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ any([$mock1, $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 1)->cancel();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionCheckTypehintTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionCheckTypehintTest.php
new file mode 100755
index 0000000..8449bc1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionCheckTypehintTest.php
@@ -0,0 +1,118 @@
+assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptFunctionStringCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithTypehint', new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint('React\Promise\testCallbackWithTypehint', new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptInvokableObjectCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint(new TestCallbackWithTypehintClass(), new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(new TestCallbackWithTypehintClass(), new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptObjectMethodCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptStaticClassCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptClosureCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptFunctionStringCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithoutTypehint', new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptInvokableObjectCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(new TestCallbackWithoutTypehintClass(), new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptObjectMethodCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint([new TestCallbackWithoutTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptStaticClassCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithoutTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
+ }
+}
+
+function testCallbackWithTypehint(\InvalidArgumentException $e)
+{
+}
+
+function testCallbackWithoutTypehint()
+{
+}
+
+class TestCallbackWithTypehintClass
+{
+ public function __invoke(\InvalidArgumentException $e)
+ {
+
+ }
+
+ public function testCallback(\InvalidArgumentException $e)
+ {
+
+ }
+
+ public static function testCallbackStatic(\InvalidArgumentException $e)
+ {
+
+ }
+}
+
+class TestCallbackWithoutTypehintClass
+{
+ public function __invoke()
+ {
+
+ }
+
+ public function testCallback()
+ {
+
+ }
+
+ public static function testCallbackStatic()
+ {
+
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionMapTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionMapTest.php
new file mode 100755
index 0000000..1ea560a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionMapTest.php
@@ -0,0 +1,198 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, 2, 3],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapInputPromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapMixedInputArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, resolve(2), 3],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapInputWhenMapperReturnsAPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, 2, 3],
+ $this->promiseMapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ resolve([1, resolve(2), 3]),
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ map(
+ resolve(1),
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ $deferred = new Deferred();
+
+ map(
+ [resolve(1), $deferred->promise(), resolve(3)],
+ $this->mapper()
+ )->then($mock);
+
+ $deferred->resolve(2);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputContainsRejection()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ map(
+ [resolve(1), reject(2), resolve(3)],
+ $this->mapper()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ map(
+ reject(),
+ $this->mapper()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ map(
+ $mock,
+ $this->mapper()
+ )->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ map(
+ [$mock1, $mock2],
+ $this->mapper()
+ )->cancel();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionRaceTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionRaceTest.php
new file mode 100755
index 0000000..83770ec
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionRaceTest.php
@@ -0,0 +1,211 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ []
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ race(
+ [1, 2, 3]
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ race(
+ [$d1->promise(), $d2->promise(), $d3->promise()]
+ )->then($mock);
+
+ $d2->resolve(2);
+
+ $d1->resolve(1);
+ $d3->resolve(3);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ [null, 1, null, 2, 3]
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfFirstSettledPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ race(
+ [$d1->promise(), $d2->promise(), $d3->promise()]
+ )->then($this->expectCallableNever(), $mock);
+
+ $d2->reject(2);
+
+ $d1->resolve(1);
+ $d3->resolve(3);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ race(
+ resolve([1, 2, 3])
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ reject()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ race($mock)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ race([$mock1, $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ race([$deferred->promise(), $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->reject();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ race([$deferred->promise(), $mock2])->cancel();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionReduceTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionReduceTest.php
new file mode 100755
index 0000000..8b43a87
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionReduceTest.php
@@ -0,0 +1,347 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(6));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceValuesWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceValuesWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithoutInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(6));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceEmptyInputWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ [],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceEmptyInputWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ [],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputContainsRejection()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ reduce(
+ [resolve(1), reject(2), resolve(3)],
+ $this->plus(),
+ resolve(1)
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithNullWhenInputIsEmptyAndNoInitialValueOrPromiseProvided()
+ {
+ // Note: this is different from when.js's behavior!
+ // In when.reduce(), this rejects with a TypeError exception (following
+ // JavaScript's [].reduce behavior.
+ // We're following PHP's array_reduce behavior and resolve with NULL.
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ reduce(
+ [],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAllowSparseArrayInputWithoutInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(3));
+
+ reduce(
+ [null, null, 1, null, 1, 1],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAllowSparseArrayInputWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(4));
+
+ reduce(
+ [null, null, 1, null, 1, 1],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceInInputOrder()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('123'));
+
+ reduce(
+ [1, 2, 3],
+ $this->append(),
+ ''
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('123'));
+
+ reduce(
+ resolve([1, 2, 3]),
+ $this->append(),
+ ''
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToInitialValueWhenInputPromiseDoesNotResolveToAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ resolve(1),
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldProvideCorrectBasisValue()
+ {
+ $insertIntoArray = function ($arr, $val, $i) {
+ $arr[$i] = $val;
+
+ return $arr;
+ };
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ reduce(
+ [$d1->promise(), $d2->promise(), $d3->promise()],
+ $insertIntoArray,
+ []
+ )->then($mock);
+
+ $d3->resolve(3);
+ $d1->resolve(1);
+ $d2->resolve(2);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ reduce(
+ reject(),
+ $this->plus(),
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ reduce(
+ $mock,
+ $this->plus(),
+ 1
+ )->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ reduce(
+ [$mock1, $mock2],
+ $this->plus(),
+ 1
+ )->cancel();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionRejectTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionRejectTest.php
new file mode 100755
index 0000000..84b8ec6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionRejectTest.php
@@ -0,0 +1,64 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($expected)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldRejectAFulfilledPromise()
+ {
+ $expected = 123;
+
+ $resolved = new FulfilledPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldRejectARejectedPromise()
+ {
+ $expected = 123;
+
+ $resolved = new RejectedPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionResolveTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionResolveTest.php
new file mode 100755
index 0000000..53126bc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionResolveTest.php
@@ -0,0 +1,171 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($expected)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveAFulfilledPromise()
+ {
+ $expected = 123;
+
+ $resolved = new FulfilledPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($resolved)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveAThenable()
+ {
+ $thenable = new SimpleFulfilledTestThenable();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ resolve($thenable)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveACancellableThenable()
+ {
+ $thenable = new SimpleTestCancellableThenable();
+
+ $promise = resolve($thenable);
+ $promise->cancel();
+
+ $this->assertTrue($thenable->cancelCalled);
+ }
+
+ /** @test */
+ public function shouldRejectARejectedPromise()
+ {
+ $expected = 123;
+
+ $resolved = new RejectedPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldSupportDeepNestingInPromiseChains()
+ {
+ $d = new Deferred();
+ $d->resolve(false);
+
+ $result = resolve(resolve($d->promise()->then(function ($val) {
+ $d = new Deferred();
+ $d->resolve($val);
+
+ $identity = function ($val) {
+ return $val;
+ };
+
+ return resolve($d->promise()->then($identity))->then(
+ function ($val) {
+ return !$val;
+ }
+ );
+ })));
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $result->then($mock);
+ }
+
+ /** @test */
+ public function shouldSupportVeryDeepNestedPromises()
+ {
+ $deferreds = [];
+
+ // @TODO Increase count once global-queue is merged
+ for ($i = 0; $i < 10; $i++) {
+ $deferreds[] = $d = new Deferred();
+ $p = $d->promise();
+
+ $last = $p;
+ for ($j = 0; $j < 10; $j++) {
+ $last = $last->then(function($result) {
+ return $result;
+ });
+ }
+ }
+
+ $p = null;
+ foreach ($deferreds as $d) {
+ if ($p) {
+ $d->resolve($p);
+ }
+
+ $p = $d->promise();
+ }
+
+ $deferreds[0]->resolve(true);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $deferreds[0]->promise()->then($mock);
+ }
+
+ /** @test */
+ public function returnsExtendePromiseForSimplePromise()
+ {
+ $promise = $this
+ ->getMockBuilder('React\Promise\PromiseInterface')
+ ->getMock();
+
+ $this->assertInstanceOf('React\Promise\ExtendedPromiseInterface', resolve($promise));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionSomeTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionSomeTest.php
new file mode 100755
index 0000000..276b54b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/FunctionSomeTest.php
@@ -0,0 +1,258 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage();
+ })
+ );
+
+ some(
+ [],
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldRejectWithLengthExceptionWithInputArrayContainingNotEnoughItems()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 4 items but contains only 3 items.' === $exception->getMessage();
+ })
+ );
+
+ some(
+ [1, 2, 3],
+ 4
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWithNonArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ null,
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ [1, 2, 3],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ [resolve(1), resolve(2), resolve(3)],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([null, 1]));
+
+ some(
+ [null, 1, null, 2, 3],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfAnyInputPromiseRejectsBeforeDesiredNumberOfInputsAreResolved()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1 => 2, 2 => 3]));
+
+ some(
+ [resolve(1), reject(2), reject(3)],
+ 2
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ resolve([1, 2, 3]),
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithEmptyArrayIfHowManyIsLessThanOne()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ [1],
+ 0
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ resolve(1),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ some(
+ reject(),
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ some($mock, 1)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ some([$mock1, $mock2], 1)->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesFulfill()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 1);
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesReject()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->reject();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 2);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/LazyPromiseTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/LazyPromiseTest.php
new file mode 100755
index 0000000..b630881
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/LazyPromiseTest.php
@@ -0,0 +1,107 @@
+promise();
+ };
+
+ return new CallbackPromiseAdapter([
+ 'promise' => function () use ($factory) {
+ return new LazyPromise($factory);
+ },
+ 'resolve' => [$d, 'resolve'],
+ 'reject' => [$d, 'reject'],
+ 'notify' => [$d, 'progress'],
+ 'settle' => [$d, 'resolve'],
+ ]);
+ }
+
+ /** @test */
+ public function shouldNotCallFactoryIfThenIsNotInvoked()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->never())
+ ->method('__invoke');
+
+ new LazyPromise($factory);
+ }
+
+ /** @test */
+ public function shouldCallFactoryIfThenIsInvoked()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke');
+
+ $p = new LazyPromise($factory);
+ $p->then();
+ }
+
+ /** @test */
+ public function shouldReturnPromiseFromFactory()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue(new FulfilledPromise(1)));
+
+ $onFulfilled = $this->createCallableMock();
+ $onFulfilled
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $p = new LazyPromise($factory);
+
+ $p->then($onFulfilled);
+ }
+
+ /** @test */
+ public function shouldReturnPromiseIfFactoryReturnsNull()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue(null));
+
+ $p = new LazyPromise($factory);
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then());
+ }
+
+ /** @test */
+ public function shouldReturnRejectedPromiseIfFactoryThrowsException()
+ {
+ $exception = new \Exception();
+
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $onRejected = $this->createCallableMock();
+ $onRejected
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $p = new LazyPromise($factory);
+
+ $p->then($this->expectCallableNever(), $onRejected);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php
new file mode 100755
index 0000000..bdedf46
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php
@@ -0,0 +1,40 @@
+callbacks = $callbacks;
+ }
+
+ public function promise()
+ {
+ return call_user_func_array($this->callbacks['promise'], func_get_args());
+ }
+
+ public function resolve()
+ {
+ return call_user_func_array($this->callbacks['resolve'], func_get_args());
+ }
+
+ public function reject()
+ {
+ return call_user_func_array($this->callbacks['reject'], func_get_args());
+ }
+
+ public function notify()
+ {
+ return call_user_func_array($this->callbacks['notify'], func_get_args());
+ }
+
+ public function settle()
+ {
+ return call_user_func_array($this->callbacks['settle'], func_get_args());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php
new file mode 100755
index 0000000..9157cd4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php
@@ -0,0 +1,14 @@
+ function () use ($promise) {
+ return $promise;
+ },
+ 'resolve' => $resolveCallback,
+ 'reject' => $rejectCallback,
+ 'notify' => $progressCallback,
+ 'settle' => $resolveCallback,
+ ]);
+ }
+
+ /** @test */
+ public function shouldRejectIfResolverThrowsException()
+ {
+ $exception = new \Exception('foo');
+
+ $promise = new Promise(function () use ($exception) {
+ throw $exception;
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $promise
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithoutCreatingGarbageCyclesIfResolverResolvesWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve) {
+ $resolve(new \Exception('foo'));
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsExceptionWithoutResolver()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () {
+ throw new \Exception('foo');
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverRejectsWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfParentCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $promise->then()->then()->then()->cancel();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) {
+ throw new \Exception('foo');
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /**
+ * Test that checks number of garbage cycles after throwing from a canceller
+ * that explicitly uses a reference to the promise. This is rather synthetic,
+ * actual use cases often have implicit (hidden) references which ought not
+ * to be stored in the stack trace.
+ *
+ * Reassigned arguments only show up in the stack trace in PHP 7, so we can't
+ * avoid this on legacy PHP. As an alternative, consider explicitly unsetting
+ * any references before throwing.
+ *
+ * @test
+ * @requires PHP 7
+ */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () {}, function () use (&$promise) {
+ throw new \Exception('foo');
+ });
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /**
+ * @test
+ * @requires PHP 7
+ * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
+ */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverWithReferenceThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () use (&$promise) {
+ throw new \Exception('foo');
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /**
+ * @test
+ * @requires PHP 7
+ * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
+ */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenceAndResolverThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () {
+ throw new \Exception('foo');
+ }, function () use (&$promise) { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldIgnoreNotifyAfterReject()
+ {
+ $promise = new Promise(function () { }, function ($resolve, $reject, $notify) {
+ $reject(new \Exception('foo'));
+ $notify(42);
+ });
+
+ $promise->then(null, null, $this->expectCallableNever());
+ $promise->cancel();
+ }
+
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromise()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithThenFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->then()->then()->then();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithDoneFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->done();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithOtherwiseFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->otherwise(function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithAlwaysFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->always(function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithProgressFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->then(null, null, function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldFulfillIfFullfilledWithSimplePromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(new SimpleFulfilledTestPromise());
+ }
+
+ /** @test */
+ public function shouldRejectIfRejectedWithSimplePromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->resolve(new SimpleRejectedTestPromise());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php
new file mode 100755
index 0000000..2baab02
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php
@@ -0,0 +1,246 @@
+getPromiseTestAdapter(function ($resolve, $reject, $notify) use (&$args) {
+ $args = func_get_args();
+ });
+
+ $adapter->promise()->cancel();
+
+ $this->assertCount(3, $args);
+ $this->assertTrue(is_callable($args[0]));
+ $this->assertTrue(is_callable($args[1]));
+ $this->assertTrue(is_callable($args[2]));
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerWithoutArgumentsIfNotAccessed()
+ {
+ $args = null;
+ $adapter = $this->getPromiseTestAdapter(function () use (&$args) {
+ $args = func_num_args();
+ });
+
+ $adapter->promise()->cancel();
+
+ $this->assertSame(0, $args);
+ }
+
+ /** @test */
+ public function cancelShouldFulfillPromiseIfCancellerFulfills()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve) {
+ $resolve(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock, $this->expectCallableNever());
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldRejectPromiseIfCancellerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject) {
+ $reject(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldRejectPromiseWithExceptionIfCancellerThrows()
+ {
+ $e = new \Exception();
+
+ $adapter = $this->getPromiseTestAdapter(function () use ($e) {
+ throw $e;
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($e));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldProgressPromiseIfCancellerNotifies()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject, $progress) {
+ $progress(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerOnlyOnceIfCancellerResolves()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnCallback(function ($resolve) {
+ $resolve();
+ }));
+
+ $adapter = $this->getPromiseTestAdapter($mock);
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectIfCancellerDoesNothing()
+ {
+ $adapter = $this->getPromiseTestAdapter(function () {});
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever());
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerFromDeepNestedPromiseChain()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ $adapter = $this->getPromiseTestAdapter($mock);
+
+ $promise = $adapter->promise()
+ ->then(function () {
+ return new Promise\Promise(function () {});
+ })
+ ->then(function () {
+ $d = new Promise\Deferred();
+
+ return $d->promise();
+ })
+ ->then(function () {
+ return new Promise\Promise(function () {});
+ });
+
+ $promise->cancel();
+ }
+
+ /** @test */
+ public function cancelCalledOnChildrenSouldOnlyCancelWhenAllChildrenCancelled()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldTriggerCancellerWhenAllChildrenCancel()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $child2 = $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ $child2->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldNotTriggerCancellerWhenCancellingOneChildrenMultipleTimes()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $child2 = $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ $child1->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldTriggerCancellerOnlyOnceWhenCancellingMultipleTimes()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldAlwaysTriggerCancellerWhenCalledOnRootPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $adapter->promise()
+ ->then()
+ ->then();
+
+ $adapter->promise()
+ ->then();
+
+ $adapter->promise()->cancel();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/FullTestTrait.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/FullTestTrait.php
new file mode 100755
index 0000000..3ce45d6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/FullTestTrait.php
@@ -0,0 +1,15 @@
+getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $adapter->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateProgressToDownstreamPromises()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnArgument(0));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateTransformedProgressToDownstreamPromises()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue($sentinel));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateCaughtExceptionValueAsProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $promise2 = $adapter2->promise();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ // resolve BEFORE attaching progress handler
+ $adapter->resolve();
+
+ $adapter->promise()
+ ->then(function () use ($promise2) {
+ return $promise2;
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $promise2 = $adapter2->promise();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(function () use ($promise2) {
+ return $promise2;
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ // resolve AFTER attaching progress handler
+ $adapter->resolve();
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressWhenResolvedWithAnotherPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue($sentinel));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->resolve($adapter2->promise());
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldAllowResolveAfterProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->at(0))
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+ $mock
+ ->expects($this->at(1))
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->notify(1);
+ $adapter->resolve(2);
+ }
+
+ /** @test */
+ public function notifyShouldAllowRejectAfterProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->at(0))
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+ $mock
+ ->expects($this->at(1))
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock,
+ $mock
+ );
+
+ $adapter->notify(1);
+ $adapter->reject(2);
+ }
+
+ /** @test */
+ public function notifyShouldReturnSilentlyOnProgressWhenAlreadyRejected()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(1);
+
+ $this->assertNull($adapter->notify());
+ }
+
+ /** @test */
+ public function notifyShouldInvokeProgressHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()->progress($mock);
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldInvokeProgressHandlerFromDone()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done(null, null, $mock));
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldThrowExceptionThrownProgressHandlerFromDone()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->notify(1);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php
new file mode 100755
index 0000000..428230b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php
@@ -0,0 +1,351 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $adapter->resolve(2);
+
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function fulfilledPromiseShouldInvokeNewlyAddedCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve(1);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock, $this->expectCallableNever());
+ }
+
+ /** @test */
+ public function thenShouldForwardResultWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ null,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldForwardCallbackResultToNextCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return $val + 1;
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldForwardPromisedCallbackResultValueToNextCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return \React\Promise\resolve($val + 1);
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackReturnsARejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return \React\Promise\reject($val + 1);
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackThrows()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock2
+ );
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->resolve();
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function doneShouldInvokeFulfillmentHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done($mock));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownFulfillmentHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done(function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejectsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done(function () {
+ return \React\Promise\reject();
+ }));
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve(1);
+ $adapter->promise()->otherwise($this->expectCallableNever());
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {})
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromiseForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromiseForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php
new file mode 100755
index 0000000..a4f48ee
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php
@@ -0,0 +1,68 @@
+getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then());
+ }
+
+ /** @test */
+ public function thenShouldReturnAllowNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null));
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function doneShouldReturnNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function doneShouldReturnAllowNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done(null, null, null));
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $adapter->promise()->otherwise($this->expectCallableNever());
+ }
+
+ /** @test */
+ public function alwaysShouldReturnAPromiseForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php
new file mode 100755
index 0000000..98d1dcf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php
@@ -0,0 +1,512 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $adapter->reject(2);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function rejectedPromiseShouldInvokeNewlyAddedCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(1);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldForwardUndefinedRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(null);
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function () {
+ // Presence of rejection handler is enough to switch back
+ // to resolve mode, even though it returns undefined.
+ // The ONLY way to propagate a rejection is to re-throw or
+ // return a rejected promise;
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyPropagate()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return $val + 1;
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return \React\Promise\resolve($val + 1);
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldPropagateRejectionsWhenErrbackThrows()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock2
+ );
+ }
+
+ /** @test */
+ public function shouldPropagateRejectionsWhenErrbackReturnsARejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return \React\Promise\reject($val + 1);
+ }
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function doneShouldInvokeRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, $mock));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownByRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function unhandledRejectionExceptionThrownByDoneHoldsRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $expected = new \stdClass();
+
+ $adapter->reject($expected);
+
+ try {
+ $adapter->promise()->done();
+ } catch (UnhandledRejectionException $e) {
+ $this->assertSame($expected, $e->getReason());
+ return;
+ }
+
+ $this->fail();
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejectsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject();
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionProvidedAsRejectionValueForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function doneShouldThrowWithDeepNestingPromiseChainsForRejectedPromise()
+ {
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $exception = new \Exception('UnhandledRejectionException');
+
+ $d = new Deferred();
+ $d->resolve();
+
+ $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
+ $d = new Deferred();
+ $d->resolve();
+
+ return \React\Promise\resolve($d->promise()->then(function () {}))->then(
+ function () use ($exception) {
+ throw $exception;
+ }
+ );
+ })));
+
+ $result->done();
+ }
+
+ /** @test */
+ public function doneShouldRecoverWhenRejectionHandlerCatchesExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ $this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
+
+ }));
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $adapter->promise()->otherwise($mock);
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeNonTypeHintedRejectionHandlerIfReasonIsAnExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function ($reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeRejectionHandlerIfReasonMatchesTypehintForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \InvalidArgumentException();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerIfReaonsDoesNotMatchTypehintForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->expectCallableNever();
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {})
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromiseForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromiseForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception1 = new \Exception();
+ $exception2 = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception2));
+
+ $adapter->reject($exception1);
+ $adapter->promise()
+ ->always(function () use ($exception2) {
+ throw $exception2;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception1 = new \Exception();
+ $exception2 = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception2));
+
+ $adapter->reject($exception1);
+ $adapter->promise()
+ ->always(function () use ($exception2) {
+ return \React\Promise\reject($exception2);
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->reject();
+
+ $adapter->promise()->cancel();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php
new file mode 100755
index 0000000..e363b6d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php
@@ -0,0 +1,86 @@
+getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then());
+ }
+
+ /** @test */
+ public function thenShouldReturnAllowNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null));
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->settle();
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function doneShouldReturnNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertNull($adapter->promise()->done(null, function () {}));
+ }
+
+ /** @test */
+ public function doneShouldReturnAllowNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertNull($adapter->promise()->done(null, function () {}, null));
+ }
+
+ /** @test */
+ public function progressShouldNotInvokeProgressHandlerForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $adapter->promise()->progress($this->expectCallableNever());
+ $adapter->notify();
+ }
+
+ /** @test */
+ public function alwaysShouldReturnAPromiseForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php
new file mode 100755
index 0000000..063f178
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php
@@ -0,0 +1,368 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function rejectShouldRejectWithFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(Promise\resolve(1));
+ }
+
+ /** @test */
+ public function rejectShouldRejectWithRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(Promise\reject(1));
+ }
+
+ /** @test */
+ public function rejectShouldForwardReasonWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function rejectShouldMakePromiseImmutable()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(null, function ($value) use ($adapter) {
+ $adapter->reject(3);
+
+ return Promise\reject($value);
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->reject(1);
+ $adapter->reject(2);
+ }
+
+ /** @test */
+ public function notifyShouldInvokeOtherwiseHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->otherwise($mock);
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldInvokeRejectionHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done(null, $mock));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownByRejectionHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done());
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject();
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRetunsPendingPromiseWhichRejectsLater()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $d = new Deferred();
+ $promise = $d->promise();
+
+ $this->assertNull($adapter->promise()->done(null, function () use ($promise) {
+ return $promise;
+ }));
+ $adapter->reject(1);
+ $d->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionProvidedAsRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done());
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ }
+
+ /** @test */
+ public function doneShouldThrowWithDeepNestingPromiseChains()
+ {
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $exception = new \Exception('UnhandledRejectionException');
+
+ $d = new Deferred();
+
+ $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
+ $d = new Deferred();
+ $d->resolve();
+
+ return \React\Promise\resolve($d->promise()->then(function () {}))->then(
+ function () use ($exception) {
+ throw $exception;
+ }
+ );
+ })));
+
+ $result->done();
+
+ $d->resolve();
+ }
+
+ /** @test */
+ public function doneShouldRecoverWhenRejectionHandlerCatchesException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
+
+ }));
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {})
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php
new file mode 100755
index 0000000..0736d35
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php
@@ -0,0 +1,312 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function resolveShouldResolveWithPromisedValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(Promise\resolve(1));
+ }
+
+ /** @test */
+ public function resolveShouldRejectWhenResolvedWithRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->resolve(Promise\reject(1));
+ }
+
+ /** @test */
+ public function resolveShouldForwardValueWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(
+ null,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function resolveShouldMakePromiseImmutable()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(function ($value) use ($adapter) {
+ $adapter->resolve(3);
+
+ return $value;
+ })
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+
+ $adapter->resolve(1);
+ $adapter->resolve(2);
+ }
+
+ /**
+ * @test
+ */
+ public function resolveShouldRejectWhenResolvedWithItself()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(new \LogicException('Cannot resolve a promise with itself.'));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->resolve($adapter->promise());
+ }
+
+ /**
+ * @test
+ */
+ public function resolveShouldRejectWhenResolvedWithAPromiseWhichFollowsItself()
+ {
+ $adapter1 = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(new \LogicException('Cannot resolve a promise with itself.'));
+
+ $promise1 = $adapter1->promise();
+
+ $promise2 = $adapter2->promise();
+
+ $promise2->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter1->resolve($promise2);
+ $adapter2->resolve($promise1);
+ }
+
+ /** @test */
+ public function doneShouldInvokeFulfillmentHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done($mock));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownFulfillmentHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(function () {
+ return \React\Promise\reject();
+ }));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {})
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForFulfillment()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForFulfillment()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+
+ $adapter->resolve(1);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/RejectedPromiseTest.php b/src/mpg25-instagram-api/vendor/react/promise/tests/RejectedPromiseTest.php
new file mode 100755
index 0000000..825f56c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/RejectedPromiseTest.php
@@ -0,0 +1,76 @@
+ function () use (&$promise) {
+ if (!$promise) {
+ throw new \LogicException('RejectedPromise must be rejected before obtaining the promise');
+ }
+
+ return $promise;
+ },
+ 'resolve' => function () {
+ throw new \LogicException('You cannot call resolve() for React\Promise\RejectedPromise');
+ },
+ 'reject' => function ($reason = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new RejectedPromise($reason);
+ }
+ },
+ 'notify' => function () {
+ // no-op
+ },
+ 'settle' => function ($reason = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new RejectedPromise($reason);
+ }
+ },
+ ]);
+ }
+
+ /** @test */
+ public function shouldThrowExceptionIfConstructedWithAPromise()
+ {
+ $this->setExpectedException('\InvalidArgumentException');
+
+ return new RejectedPromise(new RejectedPromise());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithAlwaysFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new RejectedPromise(1);
+ $promise->always(function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithThenFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new RejectedPromise(1);
+ $promise = $promise->then(null, function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/Stub/CallableStub.php b/src/mpg25-instagram-api/vendor/react/promise/tests/Stub/CallableStub.php
new file mode 100755
index 0000000..0120893
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/Stub/CallableStub.php
@@ -0,0 +1,10 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function createCallableMock()
+ {
+ return $this
+ ->getMockBuilder('React\\Promise\Stub\CallableStub')
+ ->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/bootstrap.php b/src/mpg25-instagram-api/vendor/react/promise/tests/bootstrap.php
new file mode 100755
index 0000000..9b7f872
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/bootstrap.php
@@ -0,0 +1,7 @@
+addPsr4('React\\Promise\\', __DIR__);
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php b/src/mpg25-instagram-api/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php
new file mode 100755
index 0000000..ef4d530
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php
@@ -0,0 +1,21 @@
+cancelCalled = true;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php b/src/mpg25-instagram-api/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php
new file mode 100755
index 0000000..c0f1593
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php
@@ -0,0 +1,18 @@
+cancelCalled = true;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/.gitignore b/src/mpg25-instagram-api/vendor/react/socket/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/src/mpg25-instagram-api/vendor/react/socket/.travis.yml b/src/mpg25-instagram-api/vendor/react/socket/.travis.yml
new file mode 100755
index 0000000..fd937b3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/.travis.yml
@@ -0,0 +1,48 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+# - 7.0 # Mac OS X, ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - os: osx
+ language: generic
+ php: 7.0 # just to look right on travis
+ env:
+ - PACKAGE: php70
+ allow_failures:
+ - php: hhvm
+ - os: osx
+
+sudo: false
+
+install:
+ # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew tap homebrew/homebrew-php
+ echo "Installing PHP ..."
+ brew install "${PACKAGE}"
+ brew install "${PACKAGE}"-xdebug
+ brew link "${PACKAGE}"
+ echo "Installing composer ..."
+ curl -s http://getcomposer.org/installer | php
+ mv composer.phar /usr/local/bin/composer
+ fi
+ - composer install --no-interaction
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/src/mpg25-instagram-api/vendor/react/socket/CHANGELOG.md b/src/mpg25-instagram-api/vendor/react/socket/CHANGELOG.md
new file mode 100755
index 0000000..926fb46
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/CHANGELOG.md
@@ -0,0 +1,465 @@
+# Changelog
+
+## 0.8.12 (2018-06-11)
+
+* Feature: Improve memory consumption for failed and cancelled connection attempts.
+ (#161 by @clue)
+
+* Improve test suite to fix Travis config to test against legacy PHP 5.3 again.
+ (#162 by @clue)
+
+## 0.8.11 (2018-04-24)
+
+* Feature: Improve memory consumption for cancelled connection attempts and
+ simplify skipping DNS lookup when connecting to IP addresses.
+ (#159 and #160 by @clue)
+
+## 0.8.10 (2018-02-28)
+
+* Feature: Update DNS dependency to support loading system default DNS
+ nameserver config on all supported platforms
+ (`/etc/resolv.conf` on Unix/Linux/Mac/Docker/WSL and WMIC on Windows)
+ (#152 by @clue)
+
+ This means that connecting to hosts that are managed by a local DNS server,
+ such as a corporate DNS server or when using Docker containers, will now
+ work as expected across all platforms with no changes required:
+
+ ```php
+ $connector = new Connector($loop);
+ $connector->connect('intranet.example:80')->then(function ($connection) {
+ // …
+ });
+ ```
+
+## 0.8.9 (2018-01-18)
+
+* Feature: Support explicitly choosing TLS version to negotiate with remote side
+ by respecting `crypto_method` context parameter for all classes.
+ (#149 by @clue)
+
+ By default, all connector and server classes support TLSv1.0+ and exclude
+ support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly
+ choose the TLS version you want to negotiate with the remote side:
+
+ ```php
+ // new: now supports 'crypto_method` context parameter for all classes
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+ )
+ ));
+ ```
+
+* Minor internal clean up to unify class imports
+ (#148 by @clue)
+
+## 0.8.8 (2018-01-06)
+
+* Improve test suite by adding test group to skip integration tests relying on
+ internet connection and fix minor documentation typo.
+ (#146 by @clue and #145 by @cn007b)
+
+## 0.8.7 (2017-12-24)
+
+* Fix: Fix closing socket resource before removing from loop
+ (#141 by @clue)
+
+ This fixes the root cause of an uncaught `Exception` that only manifested
+ itself after the recent Stream v0.7.4 component update and only if you're
+ using `ext-event` (`ExtEventLoop`).
+
+* Improve test suite by testing against PHP 7.2
+ (#140 by @carusogabriel)
+
+## 0.8.6 (2017-11-18)
+
+* Feature: Add Unix domain socket (UDS) support to `Server` with `unix://` URI scheme
+ and add advanced `UnixServer` class.
+ (#120 by @andig)
+
+ ```php
+ // new: Server now supports "unix://" scheme
+ $server = new Server('unix:///tmp/server.sock', $loop);
+
+ // new: advanced usage
+ $server = new UnixServer('/tmp/server.sock', $loop);
+ ```
+
+* Restructure examples to ease getting started
+ (#136 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#133 by @gabriel-caruso and #134 by @clue)
+
+## 0.8.5 (2017-10-23)
+
+* Fix: Work around PHP bug with Unix domain socket (UDS) paths for Mac OS X
+ (#123 by @andig)
+
+* Fix: Fix `SecureServer` to return `null` URI if server socket is already closed
+ (#129 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit v5 and
+ forward compatibility with upcoming EventLoop releases in tests and
+ test Mac OS X on Travis
+ (#122 by @andig and #125, #127 and #130 by @clue)
+
+* Readme improvements
+ (#118 by @jsor)
+
+## 0.8.4 (2017-09-16)
+
+* Feature: Add `FixedUriConnector` decorator to use fixed, preconfigured URI instead
+ (#117 by @clue)
+
+ This can be useful for consumers that do not support certain URIs, such as
+ when you want to explicitly connect to a Unix domain socket (UDS) path
+ instead of connecting to a default address assumed by an higher-level API:
+
+ ```php
+ $connector = new FixedUriConnector(
+ 'unix:///var/run/docker.sock',
+ new UnixConnector($loop)
+ );
+
+ // destination will be ignored, actually connects to Unix domain socket
+ $promise = $connector->connect('localhost:80');
+ ```
+
+## 0.8.3 (2017-09-08)
+
+* Feature: Reduce memory consumption for failed connections
+ (#113 by @valga)
+
+* Fix: Work around write chunk size for TLS streams for PHP < 7.1.14
+ (#114 by @clue)
+
+## 0.8.2 (2017-08-25)
+
+* Feature: Update DNS dependency to support hosts file on all platforms
+ (#112 by @clue)
+
+ This means that connecting to hosts such as `localhost` will now work as
+ expected across all platforms with no changes required:
+
+ ```php
+ $connector = new Connector($loop);
+ $connector->connect('localhost:8080')->then(function ($connection) {
+ // …
+ });
+ ```
+
+## 0.8.1 (2017-08-15)
+
+* Feature: Forward compatibility with upcoming EventLoop v1.0 and v0.5 and
+ target evenement 3.0 a long side 2.0 and 1.0
+ (#104 by @clue and #111 by @WyriHaximus)
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ fix HHVM build for now again and ignore future HHVM build errors
+ (#109 and #110 by @clue)
+
+* Minor documentation fixes
+ (#103 by @christiaan and #108 by @hansott)
+
+## 0.8.0 (2017-05-09)
+
+* Feature: New `Server` class now acts as a facade for existing server classes
+ and renamed old `Server` to `TcpServer` for advanced usage.
+ (#96 and #97 by @clue)
+
+ The `Server` class is now the main class in this package that implements the
+ `ServerInterface` and allows you to accept incoming streaming connections,
+ such as plaintext TCP/IP or secure TLS connection streams.
+
+ > This is not a BC break and consumer code does not have to be updated.
+
+* Feature / BC break: All addresses are now URIs that include the URI scheme
+ (#98 by @clue)
+
+ ```diff
+ - $parts = parse_url('tcp://' . $conn->getRemoteAddress());
+ + $parts = parse_url($conn->getRemoteAddress());
+ ```
+
+* Fix: Fix `unix://` addresses for Unix domain socket (UDS) paths
+ (#100 by @clue)
+
+* Feature: Forward compatibility with Stream v1.0 and v0.7
+ (#99 by @clue)
+
+## 0.7.2 (2017-04-24)
+
+* Fix: Work around latest PHP 7.0.18 and 7.1.4 no longer accepting full URIs
+ (#94 by @clue)
+
+## 0.7.1 (2017-04-10)
+
+* Fix: Ignore HHVM errors when closing connection that is already closing
+ (#91 by @clue)
+
+## 0.7.0 (2017-04-10)
+
+* Feature: Merge SocketClient component into this component
+ (#87 by @clue)
+
+ This means that this package now provides async, streaming plaintext TCP/IP
+ and secure TLS socket server and client connections for ReactPHP.
+
+ ```
+ $connector = new React\Socket\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+ ```
+
+ Accordingly, the `ConnectionInterface` is now used to represent both incoming
+ server side connections as well as outgoing client side connections.
+
+ If you've previously used the SocketClient component to establish outgoing
+ client connections, upgrading should take no longer than a few minutes.
+ All classes have been merged as-is from the latest `v0.7.0` release with no
+ other changes, so you can simply update your code to use the updated namespace
+ like this:
+
+ ```php
+ // old from SocketClient component and namespace
+ $connector = new React\SocketClient\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+
+ // new
+ $connector = new React\Socket\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+ ```
+
+## 0.6.0 (2017-04-04)
+
+* Feature: Add `LimitingServer` to limit and keep track of open connections
+ (#86 by @clue)
+
+ ```php
+ $server = new Server(0, $loop);
+ $server = new LimitingServer($server, 100);
+
+ $server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+ });
+ ```
+
+* Feature / BC break: Add `pause()` and `resume()` methods to limit active
+ connections
+ (#84 by @clue)
+
+ ```php
+ $server = new Server(0, $loop);
+ $server->pause();
+
+ $loop->addTimer(1.0, function() use ($server) {
+ $server->resume();
+ });
+ ```
+
+## 0.5.1 (2017-03-09)
+
+* Feature: Forward compatibility with Stream v0.5 and upcoming v0.6
+ (#79 by @clue)
+
+## 0.5.0 (2017-02-14)
+
+* Feature / BC break: Replace `listen()` call with URIs passed to constructor
+ and reject listening on hostnames with `InvalidArgumentException`
+ and replace `ConnectionException` with `RuntimeException` for consistency
+ (#61, #66 and #72 by @clue)
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080);
+
+ // new
+ $server = new Server(8080, $loop);
+ ```
+
+ Similarly, you can now pass a full listening URI to the constructor to change
+ the listening host:
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080, '127.0.0.1');
+
+ // new
+ $server = new Server('127.0.0.1:8080', $loop);
+ ```
+
+ Trying to start listening on (DNS) host names will now throw an
+ `InvalidArgumentException`, use IP addresses instead:
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080, 'localhost');
+
+ // new
+ $server = new Server('127.0.0.1:8080', $loop);
+ ```
+
+ If trying to listen fails (such as if port is already in use or port below
+ 1024 may require root access etc.), it will now throw a `RuntimeException`,
+ the `ConnectionException` class has been removed:
+
+ ```php
+ // old: throws React\Socket\ConnectionException
+ $server = new Server($loop);
+ $server->listen(80);
+
+ // new: throws RuntimeException
+ $server = new Server(80, $loop);
+ ```
+
+* Feature / BC break: Rename `shutdown()` to `close()` for consistency throughout React
+ (#62 by @clue)
+
+ ```php
+ // old
+ $server->shutdown();
+
+ // new
+ $server->close();
+ ```
+
+* Feature / BC break: Replace `getPort()` with `getAddress()`
+ (#67 by @clue)
+
+ ```php
+ // old
+ echo $server->getPort(); // 8080
+
+ // new
+ echo $server->getAddress(); // 127.0.0.1:8080
+ ```
+
+* Feature / BC break: `getRemoteAddress()` returns full address instead of only IP
+ (#65 by @clue)
+
+ ```php
+ // old
+ echo $connection->getRemoteAddress(); // 192.168.0.1
+
+ // new
+ echo $connection->getRemoteAddress(); // 192.168.0.1:51743
+ ```
+
+* Feature / BC break: Add `getLocalAddress()` method
+ (#68 by @clue)
+
+ ```php
+ echo $connection->getLocalAddress(); // 127.0.0.1:8080
+ ```
+
+* BC break: The `Server` and `SecureServer` class are now marked `final`
+ and you can no longer `extend` them
+ (which was never documented or recommended anyway).
+ Public properties and event handlers are now internal only.
+ Please use composition instead of extension.
+ (#71, #70 and #69 by @clue)
+
+## 0.4.6 (2017-01-26)
+
+* Feature: Support socket context options passed to `Server`
+ (#64 by @clue)
+
+* Fix: Properly return `null` for unknown addresses
+ (#63 by @clue)
+
+* Improve documentation for `ServerInterface` and lock test suite requirements
+ (#60 by @clue, #57 by @shaunbramley)
+
+## 0.4.5 (2017-01-08)
+
+* Feature: Add `SecureServer` for secure TLS connections
+ (#55 by @clue)
+
+* Add functional integration tests
+ (#54 by @clue)
+
+## 0.4.4 (2016-12-19)
+
+* Feature / Fix: `ConnectionInterface` should extend `DuplexStreamInterface` + documentation
+ (#50 by @clue)
+
+* Feature / Fix: Improve test suite and switch to normal stream handler
+ (#51 by @clue)
+
+* Feature: Add examples
+ (#49 by @clue)
+
+## 0.4.3 (2016-03-01)
+
+* Bug fix: Suppress errors on stream_socket_accept to prevent PHP from crashing
+* Support for PHP7 and HHVM
+* Support PHP 5.3 again
+
+## 0.4.2 (2014-05-25)
+
+* Verify stream is a valid resource in Connection
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: Check read buffer for data before shutdown signal and end emit (@ArtyDev)
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: Reset socket to non-blocking after shutting down (PHP bug)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+* Bump React dependencies to v0.4
+
+## 0.3.3 (2013-07-08)
+
+* Version bump
+
+## 0.3.2 (2013-05-10)
+
+* Version bump
+
+## 0.3.1 (2013-04-21)
+
+* Feature: Support binding to IPv6 addresses (@clue)
+
+## 0.3.0 (2013-04-14)
+
+* Bump React dependencies to v0.3
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.0 (2012-09-10)
+
+* Bump React dependencies to v0.2
+
+## 0.1.1 (2012-07-12)
+
+* Version bump
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/src/mpg25-instagram-api/vendor/react/socket/LICENSE b/src/mpg25-instagram-api/vendor/react/socket/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/react/socket/README.md b/src/mpg25-instagram-api/vendor/react/socket/README.md
new file mode 100755
index 0000000..cf9b56b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/README.md
@@ -0,0 +1,1419 @@
+# Socket
+
+[](https://travis-ci.org/reactphp/socket)
+
+Async, streaming plaintext TCP/IP and secure TLS socket server and client
+connections for [ReactPHP](https://reactphp.org/).
+
+The socket library provides re-usable interfaces for a socket-layer
+server and client based on the [`EventLoop`](https://github.com/reactphp/event-loop)
+and [`Stream`](https://github.com/reactphp/stream) components.
+Its server component allows you to build networking servers that accept incoming
+connections from networking clients (such as an HTTP server).
+Its client component allows you to build networking clients that establish
+outgoing connections to networking servers (such as an HTTP or database client).
+This library provides async, streaming means for all of this, so you can
+handle multiple concurrent connections without blocking.
+
+**Table of Contents**
+
+* [Quickstart example](#quickstart-example)
+* [Connection usage](#connection-usage)
+ * [ConnectionInterface](#connectioninterface)
+ * [getRemoteAddress()](#getremoteaddress)
+ * [getLocalAddress()](#getlocaladdress)
+* [Server usage](#server-usage)
+ * [ServerInterface](#serverinterface)
+ * [connection event](#connection-event)
+ * [error event](#error-event)
+ * [getAddress()](#getaddress)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [close()](#close)
+ * [Server](#server)
+ * [Advanced server usage](#advanced-server-usage)
+ * [TcpServer](#tcpserver)
+ * [SecureServer](#secureserver)
+ * [UnixServer](#unixserver)
+ * [LimitingServer](#limitingserver)
+ * [getConnections()](#getconnections)
+* [Client usage](#client-usage)
+ * [ConnectorInterface](#connectorinterface)
+ * [connect()](#connect)
+ * [Connector](#connector)
+ * [Advanced client usage](#advanced-client-usage)
+ * [TcpConnector](#tcpconnector)
+ * [DnsConnector](#dnsconnector)
+ * [SecureConnector](#secureconnector)
+ * [TimeoutConnector](#timeoutconnector)
+ * [UnixConnector](#unixconnector)
+ * [FixUriConnector](#fixeduriconnector)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Quickstart example
+
+Here is a server that closes the connection if you send it anything:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
+
+$socket->on('connection', function (ConnectionInterface $conn) {
+ $conn->write("Hello " . $conn->getRemoteAddress() . "!\n");
+ $conn->write("Welcome to this amazing server!\n");
+ $conn->write("Here's a tip: don't say anything.\n");
+
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Here's a client that outputs the output of said server and then attempts to
+send it a string:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$connector = new React\Socket\Connector($loop);
+
+$connector->connect('127.0.0.1:8080')->then(function (ConnectionInterface $conn) use ($loop) {
+ $conn->pipe(new React\Stream\WritableResourceStream(STDOUT, $loop));
+ $conn->write("Hello World!\n");
+});
+
+$loop->run();
+```
+
+## Connection usage
+
+### ConnectionInterface
+
+The `ConnectionInterface` is used to represent any incoming and outgoing
+connection, such as a normal TCP/IP connection.
+
+An incoming or outgoing connection is a duplex stream (both readable and
+writable) that implements React's
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+It contains additional properties for the local and remote address (client IP)
+where this connection has been established to/from.
+
+Most commonly, instances implementing this `ConnectionInterface` are emitted
+by all classes implementing the [`ServerInterface`](#serverinterface) and
+used by all classes implementing the [`ConnectorInterface`](#connectorinterface).
+
+Because the `ConnectionInterface` implements the underlying
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
+you can use any of its events and methods as usual:
+
+```php
+$connection->on('data', function ($chunk) {
+ echo $chunk;
+});
+
+$connection->on('end', function () {
+ echo 'ended';
+});
+
+$connection->on('error', function (Exception $e) {
+ echo 'error: ' . $e->getMessage();
+});
+
+$connection->on('close', function () {
+ echo 'closed';
+});
+
+$connection->write($data);
+$connection->end($data = null);
+$connection->close();
+// …
+```
+
+For more details, see the
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+
+#### getRemoteAddress()
+
+The `getRemoteAddress(): ?string` method returns the full remote address
+(URI) where this connection has been established with.
+
+```php
+$address = $connection->getRemoteAddress();
+echo 'Connection with ' . $address . PHP_EOL;
+```
+
+If the remote address can not be determined or is unknown at this time (such as
+after the connection has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+If this is a TCP/IP based connection and you only want the remote IP, you may
+use something like this:
+
+```php
+$address = $connection->getRemoteAddress();
+$ip = trim(parse_url($address, PHP_URL_HOST), '[]');
+echo 'Connection with ' . $ip . PHP_EOL;
+```
+
+#### getLocalAddress()
+
+The `getLocalAddress(): ?string` method returns the full local address
+(URI) where this connection has been established with.
+
+```php
+$address = $connection->getLocalAddress();
+echo 'Connection with ' . $address . PHP_EOL;
+```
+
+If the local address can not be determined or is unknown at this time (such as
+after the connection has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
+so they should not be confused.
+
+If your `TcpServer` instance is listening on multiple interfaces (e.g. using
+the address `0.0.0.0`), you can use this method to find out which interface
+actually accepted this connection (such as a public or local interface).
+
+If your system has multiple interfaces (e.g. a WAN and a LAN interface),
+you can use this method to find out which interface was actually
+used for this connection.
+
+## Server usage
+
+### ServerInterface
+
+The `ServerInterface` is responsible for providing an interface for accepting
+incoming streaming connections, such as a normal TCP/IP connection.
+
+Most higher-level components (such as a HTTP server) accept an instance
+implementing this interface to accept incoming streaming connections.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against any other implementation of this interface.
+This means that you SHOULD typehint against this interface instead of a concrete
+implementation of this interface.
+
+Besides defining a few methods, this interface also implements the
+[`EventEmitterInterface`](https://github.com/igorw/evenement)
+which allows you to react to certain events.
+
+#### connection event
+
+The `connection` event will be emitted whenever a new connection has been
+established, i.e. a new client connects to this server socket:
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'new connection' . PHP_EOL;
+});
+```
+
+See also the [`ConnectionInterface`](#connectioninterface) for more details
+about handling the incoming connection.
+
+#### error event
+
+The `error` event will be emitted whenever there's an error accepting a new
+connection from a client.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+Note that this is not a fatal error event, i.e. the server keeps listening for
+new connections even after this event.
+
+
+#### getAddress()
+
+The `getAddress(): ?string` method can be used to
+return the full address (URI) this server is currently listening on.
+
+```php
+$address = $server->getAddress();
+echo 'Server listening on ' . $address . PHP_EOL;
+```
+
+If the address can not be determined or is unknown at this time (such as
+after the socket has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+If this is a TCP/IP based server and you only want the local port, you may
+use something like this:
+
+```php
+$address = $server->getAddress();
+$port = parse_url($address, PHP_URL_PORT);
+echo 'Server listening on port ' . $port . PHP_EOL;
+```
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause accepting new incoming connections.
+
+Removes the socket resource from the EventLoop and thus stop accepting
+new connections. Note that the listening socket stays active and is not
+closed.
+
+This means that new incoming connections will stay pending in the
+operating system backlog until its configurable backlog is filled.
+Once the backlog is filled, the operating system may reject further
+incoming connections until the backlog is drained again by resuming
+to accept new connections.
+
+Once the server is paused, no futher `connection` events SHOULD
+be emitted.
+
+```php
+$server->pause();
+
+$server->on('connection', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+server MAY continue emitting `connection` events.
+
+Unless otherwise noted, a successfully opened server SHOULD NOT start
+in paused state.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+Similarly, calling this after `close()` is a NO-OP.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume accepting new incoming connections.
+
+Re-attach the socket resource to the EventLoop after a previous `pause()`.
+
+```php
+$server->pause();
+
+$loop->addTimer(1.0, function () use ($server) {
+ $server->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+Similarly, calling this after `close()` is a NO-OP.
+
+#### close()
+
+The `close(): void` method can be used to
+shut down this listening socket.
+
+This will stop listening for new incoming connections on this socket.
+
+```php
+echo 'Shutting down server socket' . PHP_EOL;
+$server->close();
+```
+
+Calling this method more than once on the same instance is a NO-OP.
+
+### Server
+
+The `Server` class is the main class in this package that implements the
+[`ServerInterface`](#serverinterface) and allows you to accept incoming
+streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
+Connections can also be accepted on Unix domain sockets.
+
+```php
+$server = new Server(8080, $loop);
+```
+
+As above, the `$uri` parameter can consist of only a port, in which case the
+server will default to listening on the localhost address `127.0.0.1`,
+which means it will not be reachable from outside of this system.
+
+In order to use a random port assignment, you can use the port `0`:
+
+```php
+$server = new Server(0, $loop);
+$address = $server->getAddress();
+```
+
+In order to change the host the socket is listening on, you can provide an IP
+address through the first parameter provided to the constructor, optionally
+preceded by the `tcp://` scheme:
+
+```php
+$server = new Server('192.168.0.1:8080', $loop);
+```
+
+If you want to listen on an IPv6 address, you MUST enclose the host in square
+brackets:
+
+```php
+$server = new Server('[::1]:8080', $loop);
+```
+
+To listen on a Unix domain socket (UDS) path, you MUST prefix the URI with the
+`unix://` scheme:
+
+```php
+$server = new Server('unix:///tmp/server.sock', $loop);
+```
+
+If the given URI is invalid, does not contain a port, any other scheme or if it
+contains a hostname, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException due to missing port
+$server = new Server('127.0.0.1', $loop);
+```
+
+If the given URI appears to be valid, but listening on it fails (such as if port
+is already in use or port below 1024 may require root access etc.), it will
+throw a `RuntimeException`:
+
+```php
+$first = new Server(8080, $loop);
+
+// throws RuntimeException because port is already in use
+$second = new Server(8080, $loop);
+```
+
+> Note that these error conditions may vary depending on your system and/or
+ configuration.
+ See the exception message and code for more details about the actual error
+ condition.
+
+Optionally, you can specify [TCP socket context options](http://php.net/manual/en/context.socket.php)
+for the underlying stream socket resource like this:
+
+```php
+$server = new Server('[::1]:8080', $loop, array(
+ 'tcp' => array(
+ 'backlog' => 200,
+ 'so_reuseport' => true,
+ 'ipv6_v6only' => true
+ )
+));
+```
+
+> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+ their defaults and effects of changing these may vary depending on your system
+ and/or PHP version.
+ Passing unknown context options has no effect.
+ For BC reasons, you can also pass the TCP socket context options as a simple
+ array without wrapping this in another array under the `tcp` key.
+
+You can start a secure TLS (formerly known as SSL) server by simply prepending
+the `tls://` URI scheme.
+Internally, it will wait for plaintext TCP/IP connections and then performs a
+TLS handshake for each connection.
+It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
+which in its most basic form may look something like this if you're using a
+PEM encoded certificate file:
+
+```php
+$server = new Server('tls://127.0.0.1:8080', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem'
+ )
+));
+```
+
+> Note that the certificate file will not be loaded on instantiation but when an
+ incoming connection initializes its TLS context.
+ This implies that any invalid certificate file paths or contents will only cause
+ an `error` event at a later time.
+
+If your private key is encrypted with a passphrase, you have to specify it
+like this:
+
+```php
+$server = new Server('tls://127.0.0.1:8000', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem',
+ 'passphrase' => 'secret'
+ )
+));
+```
+
+By default, this server supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$server = new Server('tls://127.0.0.1:8000', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+ )
+));
+```
+
+> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
+ their defaults and effects of changing these may vary depending on your system
+ and/or PHP version.
+ The outer context array allows you to also use `tcp` (and possibly more)
+ context options at the same time.
+ Passing unknown context options has no effect.
+ If you do not use the `tls://` scheme, then passing `tls` context options
+ has no effect.
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+> Note that the `Server` class is a concrete implementation for TCP/IP sockets.
+ If you want to typehint in your higher-level protocol implementation, you SHOULD
+ use the generic [`ServerInterface`](#serverinterface) instead.
+
+### Advanced server usage
+
+#### TcpServer
+
+The `TcpServer` class implements the [`ServerInterface`](#serverinterface) and
+is responsible for accepting plaintext TCP/IP connections.
+
+```php
+$server = new TcpServer(8080, $loop);
+```
+
+As above, the `$uri` parameter can consist of only a port, in which case the
+server will default to listening on the localhost address `127.0.0.1`,
+which means it will not be reachable from outside of this system.
+
+In order to use a random port assignment, you can use the port `0`:
+
+```php
+$server = new TcpServer(0, $loop);
+$address = $server->getAddress();
+```
+
+In order to change the host the socket is listening on, you can provide an IP
+address through the first parameter provided to the constructor, optionally
+preceded by the `tcp://` scheme:
+
+```php
+$server = new TcpServer('192.168.0.1:8080', $loop);
+```
+
+If you want to listen on an IPv6 address, you MUST enclose the host in square
+brackets:
+
+```php
+$server = new TcpServer('[::1]:8080', $loop);
+```
+
+If the given URI is invalid, does not contain a port, any other scheme or if it
+contains a hostname, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException due to missing port
+$server = new TcpServer('127.0.0.1', $loop);
+```
+
+If the given URI appears to be valid, but listening on it fails (such as if port
+is already in use or port below 1024 may require root access etc.), it will
+throw a `RuntimeException`:
+
+```php
+$first = new TcpServer(8080, $loop);
+
+// throws RuntimeException because port is already in use
+$second = new TcpServer(8080, $loop);
+```
+
+> Note that these error conditions may vary depending on your system and/or
+configuration.
+See the exception message and code for more details about the actual error
+condition.
+
+Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php)
+for the underlying stream socket resource like this:
+
+```php
+$server = new TcpServer('[::1]:8080', $loop, array(
+ 'backlog' => 200,
+ 'so_reuseport' => true,
+ 'ipv6_v6only' => true
+));
+```
+
+> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+their defaults and effects of changing these may vary depending on your system
+and/or PHP version.
+Passing unknown context options has no effect.
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+#### SecureServer
+
+The `SecureServer` class implements the [`ServerInterface`](#serverinterface)
+and is responsible for providing a secure TLS (formerly known as SSL) server.
+
+It does so by wrapping a [`TcpServer`](#tcpserver) instance which waits for plaintext
+TCP/IP connections and then performs a TLS handshake for each connection.
+It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
+which in its most basic form may look something like this if you're using a
+PEM encoded certificate file:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem'
+));
+```
+
+> Note that the certificate file will not be loaded on instantiation but when an
+incoming connection initializes its TLS context.
+This implies that any invalid certificate file paths or contents will only cause
+an `error` event at a later time.
+
+If your private key is encrypted with a passphrase, you have to specify it
+like this:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem',
+ 'passphrase' => 'secret'
+));
+```
+
+By default, this server supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+));
+```
+
+> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
+their defaults and effects of changing these may vary depending on your system
+and/or PHP version.
+Passing unknown context options has no effect.
+
+Whenever a client completes the TLS handshake, it will emit a `connection` event
+with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+Whenever a client fails to perform a successful TLS handshake, it will emit an
+`error` event and then close the underlying TCP/IP connection:
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error' . $e->getMessage() . PHP_EOL;
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+Note that the `SecureServer` class is a concrete implementation for TLS sockets.
+If you want to typehint in your higher-level protocol implementation, you SHOULD
+use the generic [`ServerInterface`](#serverinterface) instead.
+
+> Advanced usage: Despite allowing any `ServerInterface` as first parameter,
+you SHOULD pass a `TcpServer` instance as first parameter, unless you
+know what you're doing.
+Internally, the `SecureServer` has to set the required TLS context options on
+the underlying stream resources.
+These resources are not exposed through any of the interfaces defined in this
+package, but only through the internal `Connection` class.
+The `TcpServer` class is guaranteed to emit connections that implement
+the `ConnectionInterface` and uses the internal `Connection` class in order to
+expose these underlying resources.
+If you use a custom `ServerInterface` and its `connection` event does not
+meet this requirement, the `SecureServer` will emit an `error` event and
+then close the underlying connection.
+
+#### UnixServer
+
+The `UnixServer` class implements the [`ServerInterface`](#serverinterface) and
+is responsible for accepting connections on Unix domain sockets (UDS).
+
+```php
+$server = new UnixServer('/tmp/server.sock', $loop);
+```
+
+As above, the `$uri` parameter can consist of only a socket path or socket path
+prefixed by the `unix://` scheme.
+
+If the given URI appears to be valid, but listening on it fails (such as if the
+socket is already in use or the file not accessible etc.), it will throw a
+`RuntimeException`:
+
+```php
+$first = new UnixServer('/tmp/same.sock', $loop);
+
+// throws RuntimeException because socket is already in use
+$second = new UnixServer('/tmp/same.sock', $loop);
+```
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'New connection' . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+#### LimitingServer
+
+The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
+for limiting and keeping track of open connections to this server instance.
+
+Whenever the underlying server emits a `connection` event, it will check its
+limits and then either
+ - keep track of this connection by adding it to the list of
+ open connections and then forward the `connection` event
+ - or reject (close) the connection when its limits are exceeded and will
+ forward an `error` event instead.
+
+Whenever a connection closes, it will remove this connection from the list of
+open connections.
+
+```php
+$server = new LimitingServer($server, 100);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [second example](examples) for more details.
+
+You have to pass a maximum number of open connections to ensure
+the server will automatically reject (close) connections once this limit
+is exceeded. In this case, it will emit an `error` event to inform about
+this and no `connection` event will be emitted.
+
+```php
+$server = new LimitingServer($server, 100);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+You MAY pass a `null` limit in order to put no limit on the number of
+open connections and keep accepting new connection until you run out of
+operating system resources (such as open file handles). This may be
+useful if you do not want to take care of applying a limit but still want
+to use the `getConnections()` method.
+
+You can optionally configure the server to pause accepting new
+connections once the connection limit is reached. In this case, it will
+pause the underlying server and no longer process any new connections at
+all, thus also no longer closing any excessive connections.
+The underlying operating system is responsible for keeping a backlog of
+pending connections until its limit is reached, at which point it will
+start rejecting further connections.
+Once the server is below the connection limit, it will continue consuming
+connections from the backlog and will process any outstanding data on
+each connection.
+This mode may be useful for some protocols that are designed to wait for
+a response message (such as HTTP), but may be less useful for other
+protocols that demand immediate responses (such as a "welcome" message in
+an interactive chat).
+
+```php
+$server = new LimitingServer($server, 100, true);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+##### getConnections()
+
+The `getConnections(): ConnectionInterface[]` method can be used to
+return an array with all currently active connections.
+
+```php
+foreach ($server->getConnection() as $connection) {
+ $connection->write('Hi!');
+}
+```
+
+## Client usage
+
+### ConnectorInterface
+
+The `ConnectorInterface` is responsible for providing an interface for
+establishing streaming connections, such as a normal TCP/IP connection.
+
+This is the main interface defined in this package and it is used throughout
+React's vast ecosystem.
+
+Most higher-level components (such as HTTP, database or other networking
+service clients) accept an instance implementing this interface to create their
+TCP/IP connection to the underlying networking service.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against any other implementation of this interface.
+
+The interface only offers a single method:
+
+#### connect()
+
+The `connect(string $uri): PromiseInterface` method
+can be used to create a streaming connection to the given remote address.
+
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a stream implementing [`ConnectionInterface`](#connectioninterface)
+on success or rejects with an `Exception` if the connection is not successful:
+
+```php
+$connector->connect('google.com:443')->then(
+ function (ConnectionInterface $connection) {
+ // connection successfully established
+ },
+ function (Exception $error) {
+ // failed to connect due to $error
+ }
+);
+```
+
+See also [`ConnectionInterface`](#connectioninterface) for more details.
+
+The returned Promise MUST be implemented in such a way that it can be
+cancelled when it is still pending. Cancelling a pending promise MUST
+reject its value with an `Exception`. It SHOULD clean up any underlying
+resources and references as applicable:
+
+```php
+$promise = $connector->connect($uri);
+
+$promise->cancel();
+```
+
+### Connector
+
+The `Connector` class is the main class in this package that implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create streaming connections.
+
+You can use this connector to create any kind of streaming connections, such
+as plaintext TCP/IP, secure TLS or local Unix connection streams.
+
+It binds to the main event loop and can be used like this:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$connector = new Connector($loop);
+
+$connector->connect($uri)->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+In order to create a plaintext TCP/IP connection, you can simply pass a host
+and port combination like this:
+
+```php
+$connector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> If you do no specify a URI scheme in the destination URI, it will assume
+ `tcp://` as a default and establish a plaintext TCP/IP connection.
+ Note that TCP/IP connections require a host and port part in the destination
+ URI like above, all other URI components are optional.
+
+In order to create a secure TLS connection, you can use the `tls://` URI scheme
+like this:
+
+```php
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+In order to create a local Unix domain socket connection, you can use the
+`unix://` URI scheme like this:
+
+```php
+$connector->connect('unix:///tmp/demo.sock')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> The [`getRemoteAddress()`](#getremoteaddress) method will return the target
+ Unix domain socket (UDS) path as given to the `connect()` method, including
+ the `unix://` scheme, for example `unix:///tmp/demo.sock`.
+ The [`getLocalAddress()`](#getlocaladdress) method will most likely return a
+ `null` value as this value is not applicable to UDS connections here.
+
+Under the hood, the `Connector` is implemented as a *higher-level facade*
+for the lower-level connectors implemented in this package. This means it
+also shares all of their features and implementation details.
+If you want to typehint in your higher-level protocol implementation, you SHOULD
+use the generic [`ConnectorInterface`](#connectorinterface) instead.
+
+The `Connector` class will try to detect your system DNS settings (and uses
+Google's public DNS server `8.8.8.8` as a fallback if unable to determine your
+system settings) to resolve all public hostnames into underlying IP addresses by
+default.
+If you explicitly want to use a custom DNS server (such as a local DNS relay or
+a company wide DNS server), you can set up the `Connector` like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'dns' => '127.0.1.1'
+));
+
+$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+If you do not want to use a DNS resolver at all and want to connect to IP
+addresses only, you can also set up your `Connector` like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'dns' => false
+));
+
+$connector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+Advanced: If you need a custom DNS `Resolver` instance, you can also set up
+your `Connector` like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);
+
+$connector = new Connector($loop, array(
+ 'dns' => $resolver
+));
+
+$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+By default, the `tcp://` and `tls://` URI schemes will use timeout value that
+repects your `default_socket_timeout` ini setting (which defaults to 60s).
+If you want a custom timeout value, you can simply pass this like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'timeout' => 10.0
+));
+```
+
+Similarly, if you do not want to apply a timeout at all and let the operating
+system handle this, you can pass a boolean flag like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'timeout' => false
+));
+```
+
+By default, the `Connector` supports the `tcp://`, `tls://` and `unix://`
+URI schemes. If you want to explicitly prohibit any of these, you can simply
+pass boolean flags like this:
+
+```php
+// only allow secure TLS connections
+$connector = new Connector($loop, array(
+ 'tcp' => false,
+ 'tls' => true,
+ 'unix' => false,
+));
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+The `tcp://` and `tls://` also accept additional context options passed to
+the underlying connectors.
+If you want to explicitly pass additional context options, you can simply
+pass arrays of context options like this:
+
+```php
+// allow insecure TLS connections
+$connector = new Connector($loop, array(
+ 'tcp' => array(
+ 'bindto' => '192.168.0.1:0'
+ ),
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ),
+));
+
+$connector->connect('tls://localhost:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+By default, this connector supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$connector = new Connector($loop, array(
+ 'tls' => array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+ )
+));
+```
+
+> For more details about context options, please refer to the PHP documentation
+ about [socket context options](http://php.net/manual/en/context.socket.php)
+ and [SSL context options](http://php.net/manual/en/context.ssl.php).
+
+Advanced: By default, the `Connector` supports the `tcp://`, `tls://` and
+`unix://` URI schemes.
+For this, it sets up the required connector classes automatically.
+If you want to explicitly pass custom connectors for any of these, you can simply
+pass an instance implementing the `ConnectorInterface` like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);
+$tcp = new DnsConnector(new TcpConnector($loop), $resolver);
+
+$tls = new SecureConnector($tcp, $loop);
+
+$unix = new UnixConnector($loop);
+
+$connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'tls' => $tls,
+ 'unix' => $unix,
+
+ 'dns' => false,
+ 'timeout' => false,
+));
+
+$connector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> Internally, the `tcp://` connector will always be wrapped by the DNS resolver,
+ unless you disable DNS like in the above example. In this case, the `tcp://`
+ connector receives the actual hostname instead of only the resolved IP address
+ and is thus responsible for performing the lookup.
+ Internally, the automatically created `tls://` connector will always wrap the
+ underlying `tcp://` connector for establishing the underlying plaintext
+ TCP/IP connection before enabling secure TLS mode. If you want to use a custom
+ underlying `tcp://` connector for secure TLS connections only, you may
+ explicitly pass a `tls://` connector like above instead.
+ Internally, the `tcp://` and `tls://` connectors will always be wrapped by
+ `TimeoutConnector`, unless you disable timeouts like in the above example.
+
+### Advanced client usage
+
+#### TcpConnector
+
+The `React\Socket\TcpConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext
+TCP/IP connections to any IP-port-combination:
+
+```php
+$tcpConnector = new React\Socket\TcpConnector($loop);
+
+$tcpConnector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $tcpConnector->connect('127.0.0.1:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will close the underlying socket
+resource, thus cancelling the pending TCP/IP connection, and reject the
+resulting promise.
+
+You can optionally pass additional
+[socket context options](http://php.net/manual/en/context.socket.php)
+to the constructor like this:
+
+```php
+$tcpConnector = new React\Socket\TcpConnector($loop, array(
+ 'bindto' => '192.168.0.1:0'
+));
+```
+
+Note that this class only allows you to connect to IP-port-combinations.
+If the given URI is invalid, does not contain a valid IP address and port
+or contains any other scheme, it will reject with an
+`InvalidArgumentException`:
+
+If the given URI appears to be valid, but connecting to it fails (such as if
+the remote host rejects the connection etc.), it will reject with a
+`RuntimeException`.
+
+If you want to connect to hostname-port-combinations, see also the following chapter.
+
+> Advanced usage: Internally, the `TcpConnector` allocates an empty *context*
+resource for each stream resource.
+If the destination URI contains a `hostname` query parameter, its value will
+be used to set up the TLS peer name.
+This is used by the `SecureConnector` and `DnsConnector` to verify the peer
+name and can also be used if you want a custom TLS peer name.
+
+#### DnsConnector
+
+The `DnsConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext
+TCP/IP connections to any hostname-port-combination.
+
+It does so by decorating a given `TcpConnector` instance so that it first
+looks up the given domain name via DNS (if applicable) and then establishes the
+underlying TCP/IP connection to the resolved target IP address.
+
+Make sure to set up your DNS resolver and underlying TCP connector like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$dns = $dnsResolverFactory->createCached('8.8.8.8', $loop);
+
+$dnsConnector = new React\Socket\DnsConnector($tcpConnector, $dns);
+
+$dnsConnector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $dnsConnector->connect('www.google.com:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying DNS lookup
+and/or the underlying TCP/IP connection and reject the resulting promise.
+
+> Advanced usage: Internally, the `DnsConnector` relies on a `Resolver` to
+look up the IP address for the given hostname.
+It will then replace the hostname in the destination URI with this IP and
+append a `hostname` query parameter and pass this updated URI to the underlying
+connector.
+The underlying connector is thus responsible for creating a connection to the
+target IP address, while this query parameter can be used to check the original
+hostname and is used by the `TcpConnector` to set up the TLS peer name.
+If a `hostname` is given explicitly, this query parameter will not be modified,
+which can be useful if you want a custom TLS peer name.
+
+#### SecureConnector
+
+The `SecureConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create secure
+TLS (formerly known as SSL) connections to any hostname-port-combination.
+
+It does so by decorating a given `DnsConnector` instance so that it first
+creates a plaintext TCP/IP connection and then enables TLS encryption on this
+stream.
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop);
+
+$secureConnector->connect('www.google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
+ ...
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $secureConnector->connect('www.google.com:443');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying TCP/IP
+connection and/or the SSL/TLS negotiation and reject the resulting promise.
+
+You can optionally pass additional
+[SSL context options](http://php.net/manual/en/context.ssl.php)
+to the constructor like this:
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+));
+```
+
+By default, this connector supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+));
+```
+
+> Advanced usage: Internally, the `SecureConnector` relies on setting up the
+required *context options* on the underlying stream resource.
+It should therefor be used with a `TcpConnector` somewhere in the connector
+stack so that it can allocate an empty *context* resource for each stream
+resource and verify the peer name.
+Failing to do so may result in a TLS peer name mismatch error or some hard to
+trace race conditions, because all stream resources will use a single, shared
+*default context* resource otherwise.
+
+#### TimeoutConnector
+
+The `TimeoutConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to add timeout
+handling to any existing connector instance.
+
+It does so by decorating any given [`ConnectorInterface`](#connectorinterface)
+instance and starting a timer that will automatically reject and abort any
+underlying connection attempt if it takes too long.
+
+```php
+$timeoutConnector = new React\Socket\TimeoutConnector($connector, 3.0, $loop);
+
+$timeoutConnector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $timeoutConnector->connect('google.com:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying connection
+attempt, abort the timer and reject the resulting promise.
+
+#### UnixConnector
+
+The `UnixConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to connect to
+Unix domain socket (UDS) paths like this:
+
+```php
+$connector = new React\Socket\UnixConnector($loop);
+
+$connector->connect('/tmp/demo.sock')->then(function (ConnectionInterface $connection) {
+ $connection->write("HELLO\n");
+});
+
+$loop->run();
+```
+
+Connecting to Unix domain sockets is an atomic operation, i.e. its promise will
+settle (either resolve or reject) immediately.
+As such, calling `cancel()` on the resulting promise has no effect.
+
+> The [`getRemoteAddress()`](#getremoteaddress) method will return the target
+ Unix domain socket (UDS) path as given to the `connect()` method, prepended
+ with the `unix://` scheme, for example `unix:///tmp/demo.sock`.
+ The [`getLocalAddress()`](#getlocaladdress) method will most likely return a
+ `null` value as this value is not applicable to UDS connections here.
+
+#### FixedUriConnector
+
+The `FixedUriConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and decorates an existing Connector
+to always use a fixed, preconfigured URI.
+
+This can be useful for consumers that do not support certain URIs, such as
+when you want to explicitly connect to a Unix domain socket (UDS) path
+instead of connecting to a default address assumed by an higher-level API:
+
+```php
+$connector = new FixedUriConnector(
+ 'unix:///var/run/docker.sock',
+ new UnixConnector($loop)
+);
+
+// destination will be ignored, actually connects to Unix domain socket
+$promise = $connector->connect('localhost:80');
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/socket:^0.8.12
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project, partly due to its vast
+performance improvements and partly because legacy PHP versions require several
+workarounds as described below.
+
+Secure TLS connections received some major upgrades starting with PHP 5.6, with
+the defaults now being more secure, while older versions required explicit
+context options.
+This library does not take responsibility over these context options, so it's
+up to consumers of this library to take care of setting appropriate context
+options as described above.
+
+All versions of PHP prior to 5.6.8 suffered from a buffering issue where reading
+from a streaming TLS connection could be one `data` event behind.
+This library implements a work-around to try to flush the complete incoming
+data buffers on these legacy PHP versions, which has a penalty of around 10% of
+throughput on all connections.
+With this work-around, we have not been able to reproduce this issue anymore,
+but we have seen reports of people saying this could still affect some of the
+older PHP versions (`5.5.23`, `5.6.7`, and `5.6.8`).
+Note that this only affects *some* higher-level streaming protocols, such as
+IRC over TLS, but should not affect HTTP over TLS (HTTPS).
+Further investigation of this issue is needed.
+For more insights, this issue is also covered by our test suite.
+
+PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
+chunks of data over TLS streams at once.
+We try to work around this by limiting the write chunk size to 8192
+bytes for older PHP versions only.
+This is only a work-around and has a noticable performance penalty on
+affected versions.
+
+This project also supports running on HHVM.
+Note that really old HHVM < 3.8 does not support secure TLS connections, as it
+lacks the required `stream_socket_enable_crypto()` function.
+As such, trying to create a secure TLS connections on affected versions will
+return a rejected promise instead.
+This issue is also covered by our test suite, which will skip related tests
+on affected versions.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/src/mpg25-instagram-api/vendor/react/socket/composer.json b/src/mpg25-instagram-api/vendor/react/socket/composer.json
new file mode 100755
index 0000000..cad0aef
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "react/socket",
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": ["async", "socket", "stream", "connection", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/dns": "^0.4.13",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/stream": "^1.0 || ^0.7.1",
+ "react/promise": "^2.6.0 || ^1.2.1",
+ "react/promise-timer": "^1.4.0"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Socket\\": "tests"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/01-echo-server.php b/src/mpg25-instagram-api/vendor/react/socket/examples/01-echo-server.php
new file mode 100755
index 0000000..2c0be57
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/01-echo-server.php
@@ -0,0 +1,42 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) {
+ echo '[' . $conn->getRemoteAddress() . ' connected]' . PHP_EOL;
+ $conn->pipe($conn);
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/02-chat-server.php b/src/mpg25-instagram-api/vendor/react/socket/examples/02-chat-server.php
new file mode 100755
index 0000000..46439e0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/02-chat-server.php
@@ -0,0 +1,59 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server = new LimitingServer($server, null);
+
+$server->on('connection', function (ConnectionInterface $client) use ($server) {
+ // whenever a new message comes in
+ $client->on('data', function ($data) use ($client, $server) {
+ // remove any non-word characters (just for the demo)
+ $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data));
+
+ // ignore empty messages
+ if ($data === '') {
+ return;
+ }
+
+ // prefix with client IP and broadcast to all connected clients
+ $data = trim(parse_url($client->getRemoteAddress(), PHP_URL_HOST), '[]') . ': ' . $data . PHP_EOL;
+ foreach ($server->getConnections() as $connection) {
+ $connection->write($data);
+ }
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/03-http-server.php b/src/mpg25-instagram-api/vendor/react/socket/examples/03-http-server.php
new file mode 100755
index 0000000..eb6d454
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/03-http-server.php
@@ -0,0 +1,57 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) {
+ $conn->once('data', function () use ($conn) {
+ $body = "
Hello world!
\r\n";
+ $conn->end("HTTP/1.1 200 OK\r\nContent-Length: " . strlen($body) . "\r\nConnection: close\r\n\r\n" . $body);
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . strtr($server->getAddress(), array('tcp:' => 'http:', 'tls:' => 'https:')) . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/11-http-client.php b/src/mpg25-instagram-api/vendor/react/socket/examples/11-http-client.php
new file mode 100755
index 0000000..2b64a43
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/11-http-client.php
@@ -0,0 +1,36 @@
+connect($host. ':80')->then(function (ConnectionInterface $connection) use ($host) {
+ $connection->on('data', function ($data) {
+ echo $data;
+ });
+ $connection->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+ });
+
+ $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/12-https-client.php b/src/mpg25-instagram-api/vendor/react/socket/examples/12-https-client.php
new file mode 100755
index 0000000..6e3f279
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/12-https-client.php
@@ -0,0 +1,36 @@
+connect('tls://' . $host . ':443')->then(function (ConnectionInterface $connection) use ($host) {
+ $connection->on('data', function ($data) {
+ echo $data;
+ });
+ $connection->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+ });
+
+ $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/21-netcat-client.php b/src/mpg25-instagram-api/vendor/react/socket/examples/21-netcat-client.php
new file mode 100755
index 0000000..9140e2c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/21-netcat-client.php
@@ -0,0 +1,68 @@
+' . PHP_EOL);
+ exit(1);
+}
+
+$loop = Factory::create();
+$connector = new Connector($loop);
+
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdin->pause();
+$stdout = new WritableResourceStream(STDOUT, $loop);
+$stderr = new WritableResourceStream(STDERR, $loop);
+
+$stderr->write('Connecting' . PHP_EOL);
+
+$connector->connect($argv[1])->then(function (ConnectionInterface $connection) use ($stdin, $stdout, $stderr) {
+ // pipe everything from STDIN into connection
+ $stdin->resume();
+ $stdin->pipe($connection);
+
+ // pipe everything from connection to STDOUT
+ $connection->pipe($stdout);
+
+ // report errors to STDERR
+ $connection->on('error', function ($error) use ($stderr) {
+ $stderr->write('Stream ERROR: ' . $error . PHP_EOL);
+ });
+
+ // report closing and stop reading from input
+ $connection->on('close', function () use ($stderr, $stdin) {
+ $stderr->write('[CLOSED]' . PHP_EOL);
+ $stdin->close();
+ });
+
+ $stderr->write('Connected' . PHP_EOL);
+}, function ($error) use ($stderr) {
+ $stderr->write('Connection ERROR: ' . $error . PHP_EOL);
+});
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/22-http-client.php b/src/mpg25-instagram-api/vendor/react/socket/examples/22-http-client.php
new file mode 100755
index 0000000..fcb8107
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/22-http-client.php
@@ -0,0 +1,60 @@
+' . PHP_EOL);
+ exit(1);
+}
+
+$loop = Factory::create();
+$connector = new Connector($loop);
+
+if (!isset($parts['port'])) {
+ $parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
+}
+
+$host = $parts['host'];
+if (($parts['scheme'] === 'http' && $parts['port'] !== 80) || ($parts['scheme'] === 'https' && $parts['port'] !== 443)) {
+ $host .= ':' . $parts['port'];
+}
+$target = ($parts['scheme'] === 'https' ? 'tls' : 'tcp') . '://' . $parts['host'] . ':' . $parts['port'];
+$resource = isset($parts['path']) ? $parts['path'] : '/';
+if (isset($parts['query'])) {
+ $resource .= '?' . $parts['query'];
+}
+
+$stdout = new WritableResourceStream(STDOUT, $loop);
+
+$connector->connect($target)->then(function (ConnectionInterface $connection) use ($resource, $host, $stdout) {
+ $connection->pipe($stdout);
+
+ $connection->write("GET $resource HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/91-benchmark-server.php b/src/mpg25-instagram-api/vendor/react/socket/examples/91-benchmark-server.php
new file mode 100755
index 0000000..420d474
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/91-benchmark-server.php
@@ -0,0 +1,60 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) use ($loop) {
+ echo '[connected]' . PHP_EOL;
+
+ // count the number of bytes received from this connection
+ $bytes = 0;
+ $conn->on('data', function ($chunk) use (&$bytes) {
+ $bytes += strlen($chunk);
+ });
+
+ // report average throughput once client disconnects
+ $t = microtime(true);
+ $conn->on('close', function () use ($conn, $t, &$bytes) {
+ $t = microtime(true) - $t;
+ echo '[disconnected after receiving ' . $bytes . ' bytes in ' . round($t, 3) . 's => ' . round($bytes / $t / 1024 / 1024, 1) . ' MiB/s]' . PHP_EOL;
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/99-generate-self-signed.php b/src/mpg25-instagram-api/vendor/react/socket/examples/99-generate-self-signed.php
new file mode 100755
index 0000000..00f9314
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/99-generate-self-signed.php
@@ -0,0 +1,31 @@
+ secret.pem
+
+// certificate details (Distinguished Name)
+// (OpenSSL applies defaults to missing fields)
+$dn = array(
+ "commonName" => isset($argv[1]) ? $argv[1] : "localhost",
+// "countryName" => "AU",
+// "stateOrProvinceName" => "Some-State",
+// "localityName" => "London",
+// "organizationName" => "Internet Widgits Pty Ltd",
+// "organizationalUnitName" => "R&D",
+// "emailAddress" => "admin@example.com"
+);
+
+// create certificate which is valid for ~10 years
+$privkey = openssl_pkey_new();
+$cert = openssl_csr_new($dn, $privkey);
+$cert = openssl_csr_sign($cert, null, $privkey, 3650);
+
+// export public and (optionally encrypted) private key in PEM format
+openssl_x509_export($cert, $out);
+echo $out;
+
+$passphrase = isset($argv[2]) ? $argv[2] : null;
+openssl_pkey_export($privkey, $out, $passphrase);
+echo $out;
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/localhost.pem b/src/mpg25-instagram-api/vendor/react/socket/examples/localhost.pem
new file mode 100755
index 0000000..be69279
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/localhost.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
+MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
+BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
+0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
+7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
+824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
+V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
+IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
+ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
+g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
+tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
+LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
+tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
+9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
+43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
+pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
+OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
+2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
+li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
+b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
+vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
+XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
+Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
+iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
++EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
+y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
+81oh1uCH1YPLM29hPyaohxL8
+-----END PRIVATE KEY-----
diff --git a/src/mpg25-instagram-api/vendor/react/socket/examples/localhost_swordfish.pem b/src/mpg25-instagram-api/vendor/react/socket/examples/localhost_swordfish.pem
new file mode 100755
index 0000000..7d1ee80
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/examples/localhost_swordfish.pem
@@ -0,0 +1,51 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQxMDQzWhcNMjYx
+MjI4MTQxMDQzWjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRXt83SrKIHr/i
+3lc8O8pz6NHE1DNHJa4xg2xalXWzCEV6m1qLd9VdaLT9cJD1afNmEMBgY6RblNL/
+paJWVoR9MOUeIoYl2PrhUCxsf7h6MRtezQQe3e+n+/0XunF0JUQIZuJqbxfRk5WT
+XmYnphqOZKEcistAYvFBjzl/D+Cl/nYsreADc+t9l5Vni89oTWEuqIrsM4WUZqqB
+VMAakd2nZJLWIrMxq9hbW1XNukOQfcmZVFTC6CUnLq8qzGbtfZYBuMBACnL1k/E/
+yPaAgR46l14VAcndDUJBtMeL2qYuNwvXQhg3KuBmpTUpH+yzxU+4T3lmv0xXmPqu
+ySH3xvW3AgMBAAGjUDBOMB0GA1UdDgQWBBRu68WTI4pVeTB7wuG9QGI3Ie441TAf
+BgNVHSMEGDAWgBRu68WTI4pVeTB7wuG9QGI3Ie441TAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQCc4pEjEHO47VRJkbHgC+c2gAVgxekkaA1czBA1uAvh
+ILRda0NLlvyftbjaG0zZp2ABUCfRfksl/Pf/PzWLUMEuH/9kEW2rgP43z6YgiL6k
+kBPlmAU607UjD726RPGkw8QPSXS/dWiNJ5CBpPWLpxC45pokqItYbY0ijQ5Piq09
+TchYlCX044oSRnPiP394PQ3HVdaGhJB2DnjDq3in5dVivFf8EdgzQSvp/wXy3WQs
+uFSVonSnrZGY/4AgT3psGaQ6fqKb4SBoqtf5bFQvp1XNNRkuEJnS/0dygEya0c+c
+aCe/1gXC2wDjx0/TekY5m1Nyw5SY6z7stOqL/ekwgejt
+-----END CERTIFICATE-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIG7idPRLgiHkCAggA
+MBQGCCqGSIb3DQMHBAg+MLPdepHWSwSCBMgVW9LseCjfTAmF9U1qRnKsq3kIwEnW
+6aERBqs/mnmEhrXgZYgcvRRK7kD12TdHt/Nz46Ymu0h+Lrvuwtl1fHQUARTk/gFh
+onLhc9kjMUhLRIR007vJe3HvWOb/v+SBSDB38OpUxUwJmBVBuSaYLWVuPR6J5kUj
+xOgBS049lN3E9cfrHvb3bF/epIQrU0OgfyyxEvIi5n30y+tlRn3y68PY6Qd46t4Y
+UN5VZUwvJBgoRy9TGxSkiSRjhxC2PWpLYq/HMzDbcRcFF5dVAIioUd/VZ7fdgBfA
+uMW4SFfpFLDUX0aaYe+ZdA5tM0Bc0cOtG8Z0sc9JYDNNcvjmSiGCi646h8F0D3O6
+JKAQMMxQGWiyQeJ979LVjtq4lJESXA8VEKz9rV03y5xunmFCLy6dGt+6GJwXgabn
+OH7nvEv4GqAOqKc6E9je4JM+AF/oUazrfPse1KEEtsPKarazjCB/SKYtHyDJaavD
+GGjtiU9zWwGMOgIDyNmXe3ga7/TWoGOAg5YlTr6Hbq2Y/5ycgjAgPFjuXtvnoT0+
+mF5TnNfMAqTgQsE2gjhonK1pdlOen0lN5FtoUXp3CXU0dOq0J70GiX+1YA7VDn30
+n5WNAgfOXX3l3E95jGN370pHXyli5RUNW0NZVHV+22jlNWCVtQHUh+DVswQZg+i5
++DqaIHz2jUetMo7gWtqGn/wwSopOs87VM1rcALhZL4EsJ+Zy81I/hA32RNnGbuol
+NAiZh+0KrtTcc/fPunpd8vRtOwGphM11dKucozUufuiPG2inR3aEqt5yNx54ec/f
+J6nryWRYiHEA/rCU9MSBM9cqKFtEmy9/8oxV41/SPxhXjHwDlABWTtFuJ3pf2sOF
+ILSYYFwB0ZGvdjE5yAJFBr9efno/L9fafmGk7a3vmVgK2AmUC9VNB5XHw1GjF8OP
+aQAXe4md9Bh0jk/D/iyp7e7IWNssul/7XejhabidWgFj6EXc9YxE59+FlhDqyMhn
+V6houc+QeUXuwsAKgRJJhJtpv/QSZ5BI3esxHHUt3ayGnvhFElpAc0t7C/EiXKIv
+DAFYP2jksBqijM8YtEgPWYzEP5buYxZnf/LK7FDocLsNcdF38UaKBbeF90e7bR8j
+SHspG9aJWICu8Yawnh8zuy/vQv+h9gWyGodd2p9lQzlbRXrutbwfmPf7xP6nzT9i
+9GcugJxTaZgkCfhhHxFk/nRHS2NAzagKVib1xkUlZJg2hX0fIFUdYteL1GGTvOx5
+m3mTOino4T19z9SEdZYb2OHYh29e/T74bJiLCYdXwevSYHxfZc8pYAf0jp4UnMT2
+f7B0ctX1iXuQ2uZVuxh+U1Mcu+v0gDla1jWh7AhcePSi4xBNUCak0kQip6r5e6Oi
+r4MIyMRk/Pc5pzEKo8G6nk26rNvX3aRvECoVfmK7IVdsqZ6IXlt9kOmWx3IeKzrO
+J5DxpzW+9oIRZJgPTkc4/XRb0tFmFQYTiChiQ1AJUEiCX0GpkFf7cq61aLGYtWyn
+vL2lmQhljzjrDo15hKErvk7eBZW7GW/6j/m/PfRdcBI4ceuP9zWQXnDOd9zmaE4b
+q3bJ+IbbyVZA2WwyzN7umCKWghsiPMAolxEnYM9JRf8BcqeqQiwVZlfO5KFuN6Ze
+le4=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/src/mpg25-instagram-api/vendor/react/socket/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/react/socket/phpunit.xml.dist
new file mode 100755
index 0000000..13d3fab
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/Connection.php b/src/mpg25-instagram-api/vendor/react/socket/src/Connection.php
new file mode 100755
index 0000000..c6267cc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/Connection.php
@@ -0,0 +1,178 @@
+= 70100 && PHP_VERSION_ID < 70104));
+
+ $this->input = new DuplexResourceStream(
+ $resource,
+ $loop,
+ $clearCompleteBuffer ? -1 : null,
+ new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null)
+ );
+
+ $this->stream = $resource;
+
+ Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain'));
+
+ $this->input->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->input->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->input->isWritable();
+ }
+
+ public function pause()
+ {
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ $this->input->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return $this->input->pipe($dest, $options);
+ }
+
+ public function write($data)
+ {
+ return $this->input->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->input->end($data);
+ }
+
+ public function close()
+ {
+ $this->input->close();
+ $this->handleClose();
+ $this->removeAllListeners();
+ }
+
+ public function handleClose()
+ {
+ if (!is_resource($this->stream)) {
+ return;
+ }
+
+ // Try to cleanly shut down socket and ignore any errors in case other
+ // side already closed. Shutting down may return to blocking mode on
+ // some legacy versions, so reset to non-blocking just in case before
+ // continuing to close the socket resource.
+ // Underlying Stream implementation will take care of closing file
+ // handle, so we otherwise keep this open here.
+ @stream_socket_shutdown($this->stream, STREAM_SHUT_RDWR);
+ stream_set_blocking($this->stream, false);
+ }
+
+ public function getRemoteAddress()
+ {
+ return $this->parseAddress(@stream_socket_get_name($this->stream, true));
+ }
+
+ public function getLocalAddress()
+ {
+ return $this->parseAddress(@stream_socket_get_name($this->stream, false));
+ }
+
+ private function parseAddress($address)
+ {
+ if ($address === false) {
+ return null;
+ }
+
+ if ($this->unix) {
+ // remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
+ // note that technically ":" is a valid address, so keep this in place otherwise
+ if (substr($address, -1) === ':' && defined('HHVM_VERSION_ID') && HHVM_VERSION_ID < 31900) {
+ $address = (string)substr($address, 0, -1);
+ }
+
+ // work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
+ // PHP uses "\0" string and HHVM uses empty string (colon removed above)
+ if ($address === '' || $address[0] === "\x00" ) {
+ return null;
+ }
+
+ return 'unix://' . $address;
+ }
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = strrpos($address, ':');
+ if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') {
+ $port = substr($address, $pos + 1);
+ $address = '[' . substr($address, 0, $pos) . ']:' . $port;
+ }
+
+ return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/ConnectionInterface.php b/src/mpg25-instagram-api/vendor/react/socket/src/ConnectionInterface.php
new file mode 100755
index 0000000..64613b5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/ConnectionInterface.php
@@ -0,0 +1,119 @@
+on('data', function ($chunk) {
+ * echo $chunk;
+ * });
+ *
+ * $connection->on('end', function () {
+ * echo 'ended';
+ * });
+ *
+ * $connection->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage();
+ * });
+ *
+ * $connection->on('close', function () {
+ * echo 'closed';
+ * });
+ *
+ * $connection->write($data);
+ * $connection->end($data = null);
+ * $connection->close();
+ * // …
+ * ```
+ *
+ * For more details, see the
+ * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+ *
+ * @see DuplexStreamInterface
+ * @see ServerInterface
+ * @see ConnectorInterface
+ */
+interface ConnectionInterface extends DuplexStreamInterface
+{
+ /**
+ * Returns the full remote address (URI) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the remote address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based connection and you only want the remote IP, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * $ip = trim(parse_url($address, PHP_URL_HOST), '[]');
+ * echo 'Connection with ' . $ip . PHP_EOL;
+ * ```
+ *
+ * @return ?string remote address (URI) or null if unknown
+ */
+ public function getRemoteAddress();
+
+ /**
+ * Returns the full local address (full URI with scheme, IP and port) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getLocalAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the local address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
+ * so they should not be confused.
+ *
+ * If your `TcpServer` instance is listening on multiple interfaces (e.g. using
+ * the address `0.0.0.0`), you can use this method to find out which interface
+ * actually accepted this connection (such as a public or local interface).
+ *
+ * If your system has multiple interfaces (e.g. a WAN and a LAN interface),
+ * you can use this method to find out which interface was actually
+ * used for this connection.
+ *
+ * @return ?string local address (URI) or null if unknown
+ * @see self::getRemoteAddress()
+ */
+ public function getLocalAddress();
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/Connector.php b/src/mpg25-instagram-api/vendor/react/socket/src/Connector.php
new file mode 100755
index 0000000..75276bc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/Connector.php
@@ -0,0 +1,136 @@
+ true,
+ 'tls' => true,
+ 'unix' => true,
+
+ 'dns' => true,
+ 'timeout' => true,
+ );
+
+ if ($options['timeout'] === true) {
+ $options['timeout'] = (float)ini_get("default_socket_timeout");
+ }
+
+ if ($options['tcp'] instanceof ConnectorInterface) {
+ $tcp = $options['tcp'];
+ } else {
+ $tcp = new TcpConnector(
+ $loop,
+ is_array($options['tcp']) ? $options['tcp'] : array()
+ );
+ }
+
+ if ($options['dns'] !== false) {
+ if ($options['dns'] instanceof Resolver) {
+ $resolver = $options['dns'];
+ } else {
+ if ($options['dns'] !== true) {
+ $server = $options['dns'];
+ } else {
+ // try to load nameservers from system config or default to Google's public DNS
+ $config = Config::loadSystemConfigBlocking();
+ $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+ }
+
+ $factory = new Factory();
+ $resolver = $factory->create(
+ $server,
+ $loop
+ );
+ }
+
+ $tcp = new DnsConnector($tcp, $resolver);
+ }
+
+ if ($options['tcp'] !== false) {
+ $options['tcp'] = $tcp;
+
+ if ($options['timeout'] !== false) {
+ $options['tcp'] = new TimeoutConnector(
+ $options['tcp'],
+ $options['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tcp'] = $options['tcp'];
+ }
+
+ if ($options['tls'] !== false) {
+ if (!$options['tls'] instanceof ConnectorInterface) {
+ $options['tls'] = new SecureConnector(
+ $tcp,
+ $loop,
+ is_array($options['tls']) ? $options['tls'] : array()
+ );
+ }
+
+ if ($options['timeout'] !== false) {
+ $options['tls'] = new TimeoutConnector(
+ $options['tls'],
+ $options['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tls'] = $options['tls'];
+ }
+
+ if ($options['unix'] !== false) {
+ if (!$options['unix'] instanceof ConnectorInterface) {
+ $options['unix'] = new UnixConnector($loop);
+ }
+ $this->connectors['unix'] = $options['unix'];
+ }
+ }
+
+ public function connect($uri)
+ {
+ $scheme = 'tcp';
+ if (strpos($uri, '://') !== false) {
+ $scheme = (string)substr($uri, 0, strpos($uri, '://'));
+ }
+
+ if (!isset($this->connectors[$scheme])) {
+ return Promise\reject(new RuntimeException(
+ 'No connector available for URI scheme "' . $scheme . '"'
+ ));
+ }
+
+ return $this->connectors[$scheme]->connect($uri);
+ }
+}
+
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/ConnectorInterface.php b/src/mpg25-instagram-api/vendor/react/socket/src/ConnectorInterface.php
new file mode 100755
index 0000000..196d01a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/ConnectorInterface.php
@@ -0,0 +1,58 @@
+connect('google.com:443')->then(
+ * function (ConnectionInterface $connection) {
+ * // connection successfully established
+ * },
+ * function (Exception $error) {
+ * // failed to connect due to $error
+ * }
+ * );
+ * ```
+ *
+ * The returned Promise MUST be implemented in such a way that it can be
+ * cancelled when it is still pending. Cancelling a pending promise MUST
+ * reject its value with an Exception. It SHOULD clean up any underlying
+ * resources and references as applicable.
+ *
+ * ```php
+ * $promise = $connector->connect($uri);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $uri
+ * @return \React\Promise\PromiseInterface resolves with a stream implementing ConnectionInterface on success or rejects with an Exception on error
+ * @see ConnectionInterface
+ */
+ public function connect($uri);
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/DnsConnector.php b/src/mpg25-instagram-api/vendor/react/socket/src/DnsConnector.php
new file mode 100755
index 0000000..0dfd658
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/DnsConnector.php
@@ -0,0 +1,112 @@
+connector = $connector;
+ $this->resolver = $resolver;
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $parts = parse_url('tcp://' . $uri);
+ unset($parts['scheme']);
+ } else {
+ $parts = parse_url($uri);
+ }
+
+ if (!$parts || !isset($parts['host'])) {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $connector = $this->connector;
+
+ // skip DNS lookup / URI manipulation if this URI already contains an IP
+ if (false !== filter_var($host, FILTER_VALIDATE_IP)) {
+ return $connector->connect($uri);
+ }
+
+ return $this
+ ->resolveHostname($host)
+ ->then(function ($ip) use ($connector, $host, $parts) {
+ $uri = '';
+
+ // prepend original scheme if known
+ if (isset($parts['scheme'])) {
+ $uri .= $parts['scheme'] . '://';
+ }
+
+ if (strpos($ip, ':') !== false) {
+ // enclose IPv6 addresses in square brackets before appending port
+ $uri .= '[' . $ip . ']';
+ } else {
+ $uri .= $ip;
+ }
+
+ // append original port if known
+ if (isset($parts['port'])) {
+ $uri .= ':' . $parts['port'];
+ }
+
+ // append orignal path if known
+ if (isset($parts['path'])) {
+ $uri .= $parts['path'];
+ }
+
+ // append original query if known
+ if (isset($parts['query'])) {
+ $uri .= '?' . $parts['query'];
+ }
+
+ // append original hostname as query if resolved via DNS and if
+ // destination URI does not contain "hostname" query param already
+ $args = array();
+ parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
+ if ($host !== $ip && !isset($args['hostname'])) {
+ $uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . rawurlencode($host);
+ }
+
+ // append original fragment if known
+ if (isset($parts['fragment'])) {
+ $uri .= '#' . $parts['fragment'];
+ }
+
+ return $connector->connect($uri);
+ });
+ }
+
+ private function resolveHostname($host)
+ {
+ $promise = $this->resolver->resolve($host);
+
+ return new Promise\Promise(
+ function ($resolve, $reject) use ($promise) {
+ // resolve/reject with result of DNS lookup
+ $promise->then($resolve, $reject);
+ },
+ function ($_, $reject) use ($promise) {
+ // cancellation should reject connection attempt
+ $reject(new RuntimeException('Connection attempt cancelled during DNS lookup'));
+
+ // (try to) cancel pending DNS lookup
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/FixedUriConnector.php b/src/mpg25-instagram-api/vendor/react/socket/src/FixedUriConnector.php
new file mode 100755
index 0000000..057bcdf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/FixedUriConnector.php
@@ -0,0 +1,41 @@
+connect('localhost:80');
+ * ```
+ */
+class FixedUriConnector implements ConnectorInterface
+{
+ private $uri;
+ private $connector;
+
+ /**
+ * @param string $uri
+ * @param ConnectorInterface $connector
+ */
+ public function __construct($uri, ConnectorInterface $connector)
+ {
+ $this->uri = $uri;
+ $this->connector = $connector;
+ }
+
+ public function connect($_)
+ {
+ return $this->connector->connect($this->uri);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/LimitingServer.php b/src/mpg25-instagram-api/vendor/react/socket/src/LimitingServer.php
new file mode 100755
index 0000000..c7874ee
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/LimitingServer.php
@@ -0,0 +1,203 @@
+on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+class LimitingServer extends EventEmitter implements ServerInterface
+{
+ private $connections = array();
+ private $server;
+ private $limit;
+
+ private $pauseOnLimit = false;
+ private $autoPaused = false;
+ private $manuPaused = false;
+
+ /**
+ * Instantiates a new LimitingServer.
+ *
+ * You have to pass a maximum number of open connections to ensure
+ * the server will automatically reject (close) connections once this limit
+ * is exceeded. In this case, it will emit an `error` event to inform about
+ * this and no `connection` event will be emitted.
+ *
+ * ```php
+ * $server = new LimitingServer($server, 100);
+ * $server->on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * You MAY pass a `null` limit in order to put no limit on the number of
+ * open connections and keep accepting new connection until you run out of
+ * operating system resources (such as open file handles). This may be
+ * useful if you do not want to take care of applying a limit but still want
+ * to use the `getConnections()` method.
+ *
+ * You can optionally configure the server to pause accepting new
+ * connections once the connection limit is reached. In this case, it will
+ * pause the underlying server and no longer process any new connections at
+ * all, thus also no longer closing any excessive connections.
+ * The underlying operating system is responsible for keeping a backlog of
+ * pending connections until its limit is reached, at which point it will
+ * start rejecting further connections.
+ * Once the server is below the connection limit, it will continue consuming
+ * connections from the backlog and will process any outstanding data on
+ * each connection.
+ * This mode may be useful for some protocols that are designed to wait for
+ * a response message (such as HTTP), but may be less useful for other
+ * protocols that demand immediate responses (such as a "welcome" message in
+ * an interactive chat).
+ *
+ * ```php
+ * $server = new LimitingServer($server, 100, true);
+ * $server->on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * @param ServerInterface $server
+ * @param int|null $connectionLimit
+ * @param bool $pauseOnLimit
+ */
+ public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false)
+ {
+ $this->server = $server;
+ $this->limit = $connectionLimit;
+ if ($connectionLimit !== null) {
+ $this->pauseOnLimit = $pauseOnLimit;
+ }
+
+ $this->server->on('connection', array($this, 'handleConnection'));
+ $this->server->on('error', array($this, 'handleError'));
+ }
+
+ /**
+ * Returns an array with all currently active connections
+ *
+ * ```php
+ * foreach ($server->getConnection() as $connection) {
+ * $connection->write('Hi!');
+ * }
+ * ```
+ *
+ * @return ConnectionInterface[]
+ */
+ public function getConnections()
+ {
+ return $this->connections;
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ if (!$this->manuPaused) {
+ $this->manuPaused = true;
+
+ if (!$this->autoPaused) {
+ $this->server->pause();
+ }
+ }
+ }
+
+ public function resume()
+ {
+ if ($this->manuPaused) {
+ $this->manuPaused = false;
+
+ if (!$this->autoPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ // close connection if limit exceeded
+ if ($this->limit !== null && count($this->connections) >= $this->limit) {
+ $this->handleError(new OverflowException('Connection closed because server reached connection limit'));
+ $connection->close();
+ return;
+ }
+
+ $this->connections[] = $connection;
+ $that = $this;
+ $connection->on('close', function () use ($that, $connection) {
+ $that->handleDisconnection($connection);
+ });
+
+ // pause accepting new connections if limit exceeded
+ if ($this->pauseOnLimit && !$this->autoPaused && count($this->connections) >= $this->limit) {
+ $this->autoPaused = true;
+
+ if (!$this->manuPaused) {
+ $this->server->pause();
+ }
+ }
+
+ $this->emit('connection', array($connection));
+ }
+
+ /** @internal */
+ public function handleDisconnection(ConnectionInterface $connection)
+ {
+ unset($this->connections[array_search($connection, $this->connections)]);
+
+ // continue accepting new connection if below limit
+ if ($this->autoPaused && count($this->connections) < $this->limit) {
+ $this->autoPaused = false;
+
+ if (!$this->manuPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ /** @internal */
+ public function handleError(Exception $error)
+ {
+ $this->emit('error', array($error));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/SecureConnector.php b/src/mpg25-instagram-api/vendor/react/socket/src/SecureConnector.php
new file mode 100755
index 0000000..f04183d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/SecureConnector.php
@@ -0,0 +1,64 @@
+connector = $connector;
+ $this->streamEncryption = new StreamEncryption($loop, false);
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ return Promise\reject(new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore
+ }
+
+ if (strpos($uri, '://') === false) {
+ $uri = 'tls://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $uri = str_replace('tls://', '', $uri);
+ $context = $this->context;
+
+ $encryption = $this->streamEncryption;
+ return $this->connector->connect($uri)->then(function (ConnectionInterface $connection) use ($context, $encryption) {
+ // (unencrypted) TCP/IP connection succeeded
+
+ if (!$connection instanceof Connection) {
+ $connection->close();
+ throw new UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource');
+ }
+
+ // set required SSL/TLS context options
+ foreach ($context as $name => $value) {
+ stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ // try to enable encryption
+ return $encryption->enable($connection)->then(null, function ($error) use ($connection) {
+ // establishing encryption failed => close invalid connection and return error
+ $connection->close();
+ throw $error;
+ });
+ });
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/SecureServer.php b/src/mpg25-instagram-api/vendor/react/socket/src/SecureServer.php
new file mode 100755
index 0000000..302ae93
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/SecureServer.php
@@ -0,0 +1,192 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
+ *
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * Whenever a client fails to perform a successful TLS handshake, it will emit an
+ * `error` event and then close the underlying TCP/IP connection:
+ *
+ * ```php
+ * $server->on('error', function (Exception $e) {
+ * echo 'Error' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * Note that the `SecureServer` class is a concrete implementation for TLS sockets.
+ * If you want to typehint in your higher-level protocol implementation, you SHOULD
+ * use the generic `ServerInterface` instead.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class SecureServer extends EventEmitter implements ServerInterface
+{
+ private $tcp;
+ private $encryption;
+ private $context;
+
+ /**
+ * Creates a secure TLS server and starts waiting for incoming connections
+ *
+ * It does so by wrapping a `TcpServer` instance which waits for plaintext
+ * TCP/IP connections and then performs a TLS handshake for each connection.
+ * It thus requires valid [TLS context options],
+ * which in its most basic form may look something like this if you're using a
+ * PEM encoded certificate file:
+ *
+ * ```php
+ * $server = new TcpServer(8000, $loop);
+ * $server = new SecureServer($server, $loop, array(
+ * 'local_cert' => 'server.pem'
+ * ));
+ * ```
+ *
+ * Note that the certificate file will not be loaded on instantiation but when an
+ * incoming connection initializes its TLS context.
+ * This implies that any invalid certificate file paths or contents will only cause
+ * an `error` event at a later time.
+ *
+ * If your private key is encrypted with a passphrase, you have to specify it
+ * like this:
+ *
+ * ```php
+ * $server = new TcpServer(8000, $loop);
+ * $server = new SecureServer($server, $loop, array(
+ * 'local_cert' => 'server.pem',
+ * 'passphrase' => 'secret'
+ * ));
+ * ```
+ *
+ * Note that available [TLS context options],
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ *
+ * Advanced usage: Despite allowing any `ServerInterface` as first parameter,
+ * you SHOULD pass a `TcpServer` instance as first parameter, unless you
+ * know what you're doing.
+ * Internally, the `SecureServer` has to set the required TLS context options on
+ * the underlying stream resources.
+ * These resources are not exposed through any of the interfaces defined in this
+ * package, but only through the internal `Connection` class.
+ * The `TcpServer` class is guaranteed to emit connections that implement
+ * the `ConnectionInterface` and uses the internal `Connection` class in order to
+ * expose these underlying resources.
+ * If you use a custom `ServerInterface` and its `connection` event does not
+ * meet this requirement, the `SecureServer` will emit an `error` event and
+ * then close the underlying connection.
+ *
+ * @param ServerInterface|TcpServer $tcp
+ * @param LoopInterface $loop
+ * @param array $context
+ * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
+ * @see TcpServer
+ * @link http://php.net/manual/en/context.ssl.php for TLS context options
+ */
+ public function __construct(ServerInterface $tcp, LoopInterface $loop, array $context)
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ throw new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
+ }
+
+ // default to empty passphrase to suppress blocking passphrase prompt
+ $context += array(
+ 'passphrase' => ''
+ );
+
+ $this->tcp = $tcp;
+ $this->encryption = new StreamEncryption($loop);
+ $this->context = $context;
+
+ $that = $this;
+ $this->tcp->on('connection', function ($connection) use ($that) {
+ $that->handleConnection($connection);
+ });
+ $this->tcp->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ $address = $this->tcp->getAddress();
+ if ($address === null) {
+ return null;
+ }
+
+ return str_replace('tcp://' , 'tls://', $address);
+ }
+
+ public function pause()
+ {
+ $this->tcp->pause();
+ }
+
+ public function resume()
+ {
+ $this->tcp->resume();
+ }
+
+ public function close()
+ {
+ return $this->tcp->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ if (!$connection instanceof Connection) {
+ $this->emit('error', array(new UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
+ $connection->end();
+ return;
+ }
+
+ foreach ($this->context as $name => $value) {
+ stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ $that = $this;
+
+ $this->encryption->enable($connection)->then(
+ function ($conn) use ($that) {
+ $that->emit('connection', array($conn));
+ },
+ function ($error) use ($that, $connection) {
+ $that->emit('error', array($error));
+ $connection->end();
+ }
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/Server.php b/src/mpg25-instagram-api/vendor/react/socket/src/Server.php
new file mode 100755
index 0000000..72712e4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/Server.php
@@ -0,0 +1,73 @@
+ $context);
+ }
+
+ // apply default options if not explicitly given
+ $context += array(
+ 'tcp' => array(),
+ 'tls' => array(),
+ 'unix' => array()
+ );
+
+ $scheme = 'tcp';
+ $pos = strpos($uri, '://');
+ if ($pos !== false) {
+ $scheme = substr($uri, 0, $pos);
+ }
+
+ if ($scheme === 'unix') {
+ $server = new UnixServer($uri, $loop, $context['unix']);
+ } else {
+ $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
+
+ if ($scheme === 'tls') {
+ $server = new SecureServer($server, $loop, $context['tls']);
+ }
+ }
+
+ $this->server = $server;
+
+ $that = $this;
+ $server->on('connection', function (ConnectionInterface $conn) use ($that) {
+ $that->emit('connection', array($conn));
+ });
+ $server->on('error', function (Exception $error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ $this->server->pause();
+ }
+
+ public function resume()
+ {
+ $this->server->resume();
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/ServerInterface.php b/src/mpg25-instagram-api/vendor/react/socket/src/ServerInterface.php
new file mode 100755
index 0000000..5319678
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/ServerInterface.php
@@ -0,0 +1,151 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'new connection' . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ConnectionInterface` for more details about handling the
+ * incoming connection.
+ *
+ * error event:
+ * The `error` event will be emitted whenever there's an error accepting a new
+ * connection from a client.
+ *
+ * ```php
+ * $server->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * Note that this is not a fatal error event, i.e. the server keeps listening for
+ * new connections even after this event.
+ *
+ * @see ConnectionInterface
+ */
+interface ServerInterface extends EventEmitterInterface
+{
+ /**
+ * Returns the full address (URI) this server is currently listening on
+ *
+ * ```php
+ * $address = $server->getAddress();
+ * echo 'Server listening on ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the address can not be determined or is unknown at this time (such as
+ * after the socket has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based server and you only want the local port, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $server->getAddress();
+ * $port = parse_url($address, PHP_URL_PORT);
+ * echo 'Server listening on port ' . $port . PHP_EOL;
+ * ```
+ *
+ * @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed)
+ */
+ public function getAddress();
+
+ /**
+ * Pauses accepting new incoming connections.
+ *
+ * Removes the socket resource from the EventLoop and thus stop accepting
+ * new connections. Note that the listening socket stays active and is not
+ * closed.
+ *
+ * This means that new incoming connections will stay pending in the
+ * operating system backlog until its configurable backlog is filled.
+ * Once the backlog is filled, the operating system may reject further
+ * incoming connections until the backlog is drained again by resuming
+ * to accept new connections.
+ *
+ * Once the server is paused, no futher `connection` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $server->pause();
+ *
+ * $server->on('connection', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * server MAY continue emitting `connection` events.
+ *
+ * Unless otherwise noted, a successfully opened server SHOULD NOT start
+ * in paused state.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes accepting new incoming connections.
+ *
+ * Re-attach the socket resource to the EventLoop after a previous `pause()`.
+ *
+ * ```php
+ * $server->pause();
+ *
+ * $loop->addTimer(1.0, function () use ($server) {
+ * $server->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Shuts down this listening socket
+ *
+ * This will stop listening for new incoming connections on this socket.
+ *
+ * Calling this method more than once on the same instance is a NO-OP.
+ *
+ * @return void
+ */
+ public function close();
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/StreamEncryption.php b/src/mpg25-instagram-api/vendor/react/socket/src/StreamEncryption.php
new file mode 100755
index 0000000..ba5d472
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/StreamEncryption.php
@@ -0,0 +1,146 @@
+loop = $loop;
+ $this->server = $server;
+
+ // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
+ // PHP 5.6+ supports bitmasks, legacy PHP only supports predefined
+ // constants, so apply accordingly below.
+ // Also, since PHP 5.6.7 up until before PHP 7.2.0 the main constant did
+ // only support TLSv1.0, so we explicitly apply all versions.
+ // @link http://php.net/manual/en/migration56.openssl.php#migration56.openssl.crypto-method
+ // @link https://3v4l.org/plbFn
+ if ($server) {
+ $this->method = STREAM_CRYPTO_METHOD_TLS_SERVER;
+
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_SERVER;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_SERVER;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_SERVER;
+ }
+ } else {
+ $this->method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
+
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+ }
+ }
+ }
+
+ public function enable(Connection $stream)
+ {
+ return $this->toggle($stream, true);
+ }
+
+ public function disable(Connection $stream)
+ {
+ return $this->toggle($stream, false);
+ }
+
+ public function toggle(Connection $stream, $toggle)
+ {
+ // pause actual stream instance to continue operation on raw stream socket
+ $stream->pause();
+
+ // TODO: add write() event to make sure we're not sending any excessive data
+
+ $deferred = new Deferred(function ($_, $reject) use ($toggle) {
+ // cancelling this leaves this stream in an inconsistent state…
+ $reject(new RuntimeException('Cancelled toggling encryption ' . $toggle ? 'on' : 'off'));
+ });
+
+ // get actual stream socket from stream instance
+ $socket = $stream->stream;
+
+ // get crypto method from context options or use global setting from constructor
+ $method = $this->method;
+ $context = stream_context_get_options($socket);
+ if (isset($context['ssl']['crypto_method'])) {
+ $method = $context['ssl']['crypto_method'];
+ }
+
+ $that = $this;
+ $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) {
+ $that->toggleCrypto($socket, $deferred, $toggle, $method);
+ };
+
+ $this->loop->addReadStream($socket, $toggleCrypto);
+
+ if (!$this->server) {
+ $toggleCrypto();
+ }
+
+ $loop = $this->loop;
+
+ return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) {
+ $loop->removeReadStream($socket);
+
+ $stream->encryptionEnabled = $toggle;
+ $stream->resume();
+
+ return $stream;
+ }, function($error) use ($stream, $socket, $loop) {
+ $loop->removeReadStream($socket);
+ $stream->resume();
+ throw $error;
+ });
+ }
+
+ public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
+ {
+ set_error_handler(array($this, 'handleError'));
+ $result = stream_socket_enable_crypto($socket, $toggle, $method);
+ restore_error_handler();
+
+ if (true === $result) {
+ $deferred->resolve();
+ } else if (false === $result) {
+ $deferred->reject(new UnexpectedValueException(
+ sprintf("Unable to complete SSL/TLS handshake: %s", $this->errstr),
+ $this->errno
+ ));
+ } else {
+ // need more data, will retry
+ }
+ }
+
+ public function handleError($errno, $errstr)
+ {
+ $this->errstr = str_replace(array("\r", "\n"), ' ', $errstr);
+ $this->errno = $errno;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/TcpConnector.php b/src/mpg25-instagram-api/vendor/react/socket/src/TcpConnector.php
new file mode 100755
index 0000000..53d55a3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/TcpConnector.php
@@ -0,0 +1,129 @@
+loop = $loop;
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $ip = trim($parts['host'], '[]');
+ if (false === filter_var($ip, FILTER_VALIDATE_IP)) {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP'));
+ }
+
+ // use context given in constructor
+ $context = array(
+ 'socket' => $this->context
+ );
+
+ // parse arguments from query component of URI
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // If an original hostname has been given, use this for TLS setup.
+ // This can happen due to layers of nested connectors, such as a
+ // DnsConnector reporting its original hostname.
+ // These context options are here in case TLS is enabled later on this stream.
+ // If TLS is not enabled later, this doesn't hurt either.
+ if (isset($args['hostname'])) {
+ $context['ssl'] = array(
+ 'SNI_enabled' => true,
+ 'peer_name' => $args['hostname']
+ );
+
+ // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead.
+ // The SNI_server_name context option has to be set here during construction,
+ // as legacy PHP ignores any values set later.
+ if (PHP_VERSION_ID < 50600) {
+ $context['ssl'] += array(
+ 'SNI_server_name' => $args['hostname'],
+ 'CN_match' => $args['hostname']
+ );
+ }
+ }
+
+ // latest versions of PHP no longer accept any other URI components and
+ // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here
+ $remote = 'tcp://' . $parts['host'] . ':' . $parts['port'];
+
+ $socket = @stream_socket_client(
+ $remote,
+ $errno,
+ $errstr,
+ 0,
+ STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT,
+ stream_context_create($context)
+ );
+
+ if (false === $socket) {
+ return Promise\reject(new RuntimeException(
+ sprintf("Connection to %s failed: %s", $uri, $errstr),
+ $errno
+ ));
+ }
+
+ stream_set_blocking($socket, 0);
+
+ // wait for connection
+
+ return $this->waitForStreamOnce($socket);
+ }
+
+ private function waitForStreamOnce($stream)
+ {
+ $loop = $this->loop;
+
+ return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) {
+ $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) {
+ $loop->removeWriteStream($stream);
+
+ // The following hack looks like the only way to
+ // detect connection refused errors with PHP's stream sockets.
+ if (false === stream_socket_get_name($stream, true)) {
+ fclose($stream);
+
+ $reject(new RuntimeException('Connection refused'));
+ } else {
+ $resolve(new Connection($stream, $loop));
+ }
+ });
+ }, function () use ($loop, $stream) {
+ $loop->removeWriteStream($stream);
+ fclose($stream);
+
+ // @codeCoverageIgnoreStart
+ // legacy PHP 5.3 sometimes requires a second close call (see tests)
+ if (PHP_VERSION_ID < 50400 && is_resource($stream)) {
+ fclose($stream);
+ }
+ // @codeCoverageIgnoreEnd
+
+ throw new RuntimeException('Cancelled while waiting for TCP/IP connection to be established');
+ });
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/TcpServer.php b/src/mpg25-instagram-api/vendor/react/socket/src/TcpServer.php
new file mode 100755
index 0000000..119e177
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/TcpServer.php
@@ -0,0 +1,236 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class TcpServer extends EventEmitter implements ServerInterface
+{
+ private $master;
+ private $loop;
+ private $listening = false;
+
+ /**
+ * Creates a plaintext TCP/IP socket server and starts listening on the given address
+ *
+ * This starts accepting new incoming connections on the given address.
+ * See also the `connection event` documented in the `ServerInterface`
+ * for more details.
+ *
+ * ```php
+ * $server = new TcpServer(8080, $loop);
+ * ```
+ *
+ * As above, the `$uri` parameter can consist of only a port, in which case the
+ * server will default to listening on the localhost address `127.0.0.1`,
+ * which means it will not be reachable from outside of this system.
+ *
+ * In order to use a random port assignment, you can use the port `0`:
+ *
+ * ```php
+ * $server = new TcpServer(0, $loop);
+ * $address = $server->getAddress();
+ * ```
+ *
+ * In order to change the host the socket is listening on, you can provide an IP
+ * address through the first parameter provided to the constructor, optionally
+ * preceded by the `tcp://` scheme:
+ *
+ * ```php
+ * $server = new TcpServer('192.168.0.1:8080', $loop);
+ * ```
+ *
+ * If you want to listen on an IPv6 address, you MUST enclose the host in square
+ * brackets:
+ *
+ * ```php
+ * $server = new TcpServer('[::1]:8080', $loop);
+ * ```
+ *
+ * If the given URI is invalid, does not contain a port, any other scheme or if it
+ * contains a hostname, it will throw an `InvalidArgumentException`:
+ *
+ * ```php
+ * // throws InvalidArgumentException due to missing port
+ * $server = new TcpServer('127.0.0.1', $loop);
+ * ```
+ *
+ * If the given URI appears to be valid, but listening on it fails (such as if port
+ * is already in use or port below 1024 may require root access etc.), it will
+ * throw a `RuntimeException`:
+ *
+ * ```php
+ * $first = new TcpServer(8080, $loop);
+ *
+ * // throws RuntimeException because port is already in use
+ * $second = new TcpServer(8080, $loop);
+ * ```
+ *
+ * Note that these error conditions may vary depending on your system and/or
+ * configuration.
+ * See the exception message and code for more details about the actual error
+ * condition.
+ *
+ * Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php)
+ * for the underlying stream socket resource like this:
+ *
+ * ```php
+ * $server = new TcpServer('[::1]:8080', $loop, array(
+ * 'backlog' => 200,
+ * 'so_reuseport' => true,
+ * 'ipv6_v6only' => true
+ * ));
+ * ```
+ *
+ * Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ *
+ * @param string|int $uri
+ * @param LoopInterface $loop
+ * @param array $context
+ * @throws InvalidArgumentException if the listening address is invalid
+ * @throws RuntimeException if listening on this address fails (already in use etc.)
+ */
+ public function __construct($uri, LoopInterface $loop, array $context = array())
+ {
+ $this->loop = $loop;
+
+ // a single port has been given => assume localhost
+ if ((string)(int)$uri === (string)$uri) {
+ $uri = '127.0.0.1:' . $uri;
+ }
+
+ // assume default scheme if none has been given
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ // parse_url() does not accept null ports (random port assignment) => manually remove
+ if (substr($uri, -2) === ':0') {
+ $parts = parse_url(substr($uri, 0, -2));
+ if ($parts) {
+ $parts['port'] = 0;
+ }
+ } else {
+ $parts = parse_url($uri);
+ }
+
+ // ensure URI contains TCP scheme, host and port
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ throw new InvalidArgumentException('Invalid URI "' . $uri . '" given');
+ }
+
+ if (false === filter_var(trim($parts['host'], '[]'), FILTER_VALIDATE_IP)) {
+ throw new InvalidArgumentException('Given URI "' . $uri . '" does not contain a valid host IP');
+ }
+
+ $this->master = @stream_socket_server(
+ $uri,
+ $errno,
+ $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
+ stream_context_create(array('socket' => $context))
+ );
+ if (false === $this->master) {
+ throw new RuntimeException('Failed to listen on "' . $uri . '": ' . $errstr, $errno);
+ }
+ stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!is_resource($this->master)) {
+ return null;
+ }
+
+ $address = stream_socket_get_name($this->master, false);
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = strrpos($address, ':');
+ if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') {
+ $port = substr($address, $pos + 1);
+ $address = '[' . substr($address, 0, $pos) . ']:' . $port;
+ }
+
+ return 'tcp://' . $address;
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ $newSocket = @stream_socket_accept($master);
+ if (false === $newSocket) {
+ $that->emit('error', array(new RuntimeException('Error accepting new connection')));
+
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $this->emit('connection', array(
+ new Connection($socket, $this->loop)
+ ));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/TimeoutConnector.php b/src/mpg25-instagram-api/vendor/react/socket/src/TimeoutConnector.php
new file mode 100755
index 0000000..d4eba2e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/TimeoutConnector.php
@@ -0,0 +1,25 @@
+connector = $connector;
+ $this->timeout = $timeout;
+ $this->loop = $loop;
+ }
+
+ public function connect($uri)
+ {
+ return Timer\timeout($this->connector->connect($uri), $this->timeout, $this->loop);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/UnixConnector.php b/src/mpg25-instagram-api/vendor/react/socket/src/UnixConnector.php
new file mode 100755
index 0000000..9b84ab0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/UnixConnector.php
@@ -0,0 +1,44 @@
+loop = $loop;
+ }
+
+ public function connect($path)
+ {
+ if (strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (substr($path, 0, 7) !== 'unix://') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $path . '" is invalid'));
+ }
+
+ $resource = @stream_socket_client($path, $errno, $errstr, 1.0);
+
+ if (!$resource) {
+ return Promise\reject(new RuntimeException('Unable to connect to unix domain socket "' . $path . '": ' . $errstr, $errno));
+ }
+
+ $connection = new Connection($resource, $this->loop);
+ $connection->unix = true;
+
+ return Promise\resolve($connection);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/src/UnixServer.php b/src/mpg25-instagram-api/vendor/react/socket/src/UnixServer.php
new file mode 100755
index 0000000..8f1ed98
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/src/UnixServer.php
@@ -0,0 +1,130 @@
+loop = $loop;
+
+ if (strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (substr($path, 0, 7) !== 'unix://') {
+ throw new InvalidArgumentException('Given URI "' . $path . '" is invalid');
+ }
+
+ $this->master = @stream_socket_server(
+ $path,
+ $errno,
+ $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
+ stream_context_create(array('socket' => $context))
+ );
+ if (false === $this->master) {
+ throw new RuntimeException('Failed to listen on unix domain socket "' . $path . '": ' . $errstr, $errno);
+ }
+ stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!is_resource($this->master)) {
+ return null;
+ }
+
+ return 'unix://' . stream_socket_get_name($this->master, false);
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ $newSocket = @stream_socket_accept($master);
+ if (false === $newSocket) {
+ $that->emit('error', array(new RuntimeException('Error accepting new connection')));
+
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $connection = new Connection($socket, $this->loop);
+ $connection->unix = true;
+
+ $this->emit('connection', array(
+ $connection
+ ));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/ConnectionTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/ConnectionTest.php
new file mode 100755
index 0000000..d3563df
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/ConnectionTest.php
@@ -0,0 +1,47 @@
+markTestSkipped('HHVM does not support socket operation on test memory stream');
+ }
+
+ $resource = fopen('php://memory', 'r+');
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connection = new Connection($resource, $loop);
+ $connection->close();
+
+ $this->assertFalse(is_resource($resource));
+ }
+
+ public function testCloseConnectionWillRemoveResourceFromLoopBeforeClosingResource()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not support socket operation on test memory stream');
+ }
+
+ $resource = fopen('php://memory', 'r+');
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addWriteStream')->with($resource);
+
+ $onRemove = null;
+ $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($param) use (&$onRemove) {
+ $onRemove = is_resource($param);
+ return true;
+ }));
+
+ $connection = new Connection($resource, $loop);
+ $connection->write('test');
+ $connection->close();
+
+ $this->assertTrue($onRemove);
+ $this->assertFalse(is_resource($resource));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/ConnectorTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/ConnectorTest.php
new file mode 100755
index 0000000..c8eb19b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/ConnectorTest.php
@@ -0,0 +1,128 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp
+ ));
+
+ $connector->connect('127.0.0.1:80');
+ }
+
+ public function testConnectorPassedThroughHostnameIfDnsIsDisabled()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('tcp://google.com:80')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'dns' => false
+ ));
+
+ $connector->connect('tcp://google.com:80');
+ }
+
+ public function testConnectorWithUnknownSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop);
+
+ $promise = $connector->connect('unknown://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTcpDefaultSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tcp' => false
+ ));
+
+ $promise = $connector->connect('google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTcpSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tcp' => false
+ ));
+
+ $promise = $connector->connect('tcp://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTlsSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tls' => false
+ ));
+
+ $promise = $connector->connect('tls://google.com:443');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledUnixSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'unix' => false
+ ));
+
+ $promise = $connector->connect('unix://demo.sock');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorUsesGivenResolverInstance()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+ $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'dns' => $resolver
+ ));
+
+ $connector->connect('google.com:80');
+ }
+
+ public function testConnectorUsesResolvedHostnameIfDnsIsUsed()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function ($resolve) { $resolve('127.0.0.1'); });
+ $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+ $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise);
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('tcp://127.0.0.1:80?hostname=google.com')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'dns' => $resolver
+ ));
+
+ $connector->connect('tcp://google.com:80');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/DnsConnectorTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/DnsConnectorTest.php
new file mode 100755
index 0000000..3c94c39
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/DnsConnectorTest.php
@@ -0,0 +1,111 @@
+tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+
+ $this->connector = new DnsConnector($this->tcp, $this->resolver);
+ }
+
+ public function testPassByResolverIfGivenIp()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('127.0.0.1:80');
+ }
+
+ public function testPassThroughResolverIfGivenHost()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=google.com'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('google.com:80');
+ }
+
+ public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('::1')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80?hostname=google.com'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('google.com:80');
+ }
+
+ public function testPassByResolverIfGivenCompleteUri()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://127.0.0.1:80/path?query#fragment');
+ }
+
+ public function testPassThroughResolverIfGivenCompleteUri()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/path?query&hostname=google.com#fragment'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://google.com:80/path?query#fragment');
+ }
+
+ public function testPassThroughResolverIfGivenExplicitHost()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/?hostname=google.de'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://google.com:80/?hostname=google.de');
+ }
+
+ public function testRejectsImmediatelyIfUriIsInvalid()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('////');
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testSkipConnectionIfDnsFails()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.invalid'))->will($this->returnValue(Promise\reject()));
+ $this->tcp->expects($this->never())->method('connect');
+
+ $this->connector->connect('example.invalid:80');
+ }
+
+ public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, $this->expectCallableOnce());
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue($pending));
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testCancelDuringTcpConnectionCancelsTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/FixedUriConnectorTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/FixedUriConnectorTest.php
new file mode 100755
index 0000000..f42d74f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/FixedUriConnectorTest.php
@@ -0,0 +1,19 @@
+getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $base->expects($this->once())->method('connect')->with('test')->willReturn('ret');
+
+ $connector = new FixedUriConnector('test', $base);
+
+ $this->assertEquals('ret', $connector->connect('ignored'));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/FunctionalConnectorTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/FunctionalConnectorTest.php
new file mode 100755
index 0000000..6611352
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/FunctionalConnectorTest.php
@@ -0,0 +1,32 @@
+on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new Connector($loop);
+
+ $connection = Block\await($connector->connect('localhost:9998'), $loop, self::TIMEOUT);
+
+ $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
+
+ $connection->close();
+ $server->close();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/FunctionalSecureServerTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/FunctionalSecureServerTest.php
new file mode 100755
index 0000000..78a59d0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/FunctionalSecureServerTest.php
@@ -0,0 +1,438 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+ }
+
+ public function testEmitsConnectionForNewConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testWritesDataToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write('foo');
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local ConnectionInterface */
+
+ $local->on('data', $this->expectCallableOnceWith('foo'));
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testWritesDataInMultipleChunksToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write(str_repeat('*', 400000));
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ public function testWritesMoreDataInMultipleChunksToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write(str_repeat('*', 2000000));
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(2000000, $received);
+ }
+
+ public function testEmitsDataFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $once = $this->expectCallableOnceWith('foo');
+ $server->on('connection', function (ConnectionInterface $conn) use ($once) {
+ $conn->on('data', $once);
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $local->write("foo");
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsDataInMultipleChunksFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $received = 0;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$received) {
+ $conn->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $local->write(str_repeat('*', 400000));
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ public function testPipesDataBackInMultipleChunksFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) use (&$received) {
+ $conn->pipe($conn);
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ $local->write(str_repeat('*', 400000));
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ /**
+ * @requires PHP 5.6
+ */
+ public function testEmitsConnectionForNewTlsv11Connection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false,
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ /**
+ * @requires PHP 5.6
+ */
+ public function testEmitsErrorForClientWithTlsVersionMismatch()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER|STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false,
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsConnectionForNewConnectionWithEncryptedCertificate()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem',
+ 'passphrase' => 'swordfish'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithInvalidCertificate()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'invalid.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithEncryptedCertificateMissingPassphrase()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithEncryptedCertificateWithInvalidPassphrase()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem',
+ 'passphrase' => 'nope'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForConnectionWithPeerVerification()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => true
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then(null, $this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsErrorIfConnectionIsCancelled()
+ {
+ if (PHP_OS !== 'Linux') {
+ $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsNothingIfConnectionIsIdle()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableNever());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect(str_replace('tls://', '', $server->getAddress()));
+
+ $promise->then($this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsErrorIfConnectionIsNotSecureHandshake()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect(str_replace('tls://', '', $server->getAddress()));
+
+ $promise->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/FunctionalTcpServerTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/FunctionalTcpServerTest.php
new file mode 100755
index 0000000..ec7855e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/FunctionalTcpServerTest.php
@@ -0,0 +1,324 @@
+on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsNoConnectionForNewConnectionWhenPaused()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+ $server->pause();
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionForNewConnectionWhenResumedAfterPause()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->pause();
+ $server->resume();
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionWithRemoteIp()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $peer);
+ }
+
+ public function testEmitsConnectionWithLocalIp()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $local);
+ $this->assertEquals($server->getAddress(), $local);
+ }
+
+ public function testEmitsConnectionWithLocalIpDespiteListeningOnAll()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer('0.0.0.0:0', $loop);
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $local);
+ }
+
+ public function testEmitsConnectionWithRemoteIpAfterConnectionIsClosedByPeer()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $conn->on('close', function () use ($conn, &$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $client = Block\await($promise, $loop, 0.1);
+ $client->end();
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $peer);
+ }
+
+ public function testEmitsConnectionWithRemoteNullAddressAfterConnectionIsClosedLocally()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $conn->close();
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertNull($peer);
+ }
+
+ public function testEmitsConnectionEvenIfConnectionIsCancelled()
+ {
+ if (PHP_OS !== 'Linux') {
+ $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionForNewIpv6Connection()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionWithRemoteIpv6()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('[::1]:', $peer);
+ }
+
+ public function testEmitsConnectionWithLocalIpv6()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('[::1]:', $local);
+ $this->assertEquals($server->getAddress(), $local);
+ }
+
+ public function testEmitsConnectionWithInheritedContextOptions()
+ {
+ if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
+ // https://3v4l.org/hB4Tc
+ $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop, array(
+ 'backlog' => 4
+ ));
+
+ $all = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$all) {
+ $all = stream_context_get_options($conn->stream);
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnInvalidUri()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('///', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWithoutPort()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('127.0.0.1', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWithWrongScheme()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('udp://127.0.0.1:0', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWIthHostname()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('localhost:8080', $loop);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/IntegrationTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/IntegrationTest.php
new file mode 100755
index 0000000..59dff4f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/IntegrationTest.php
@@ -0,0 +1,328 @@
+connect('google.com:80'), $loop);
+
+ $this->assertContains(':80', $conn->getRemoteAddress());
+ $this->assertNotEquals('google.com:80', $conn->getRemoteAddress());
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingEncryptedStuffFromGoogleShouldWork()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+ $secureConnector = new Connector($loop);
+
+ $conn = Block\await($secureConnector->connect('tls://google.com:443'), $loop);
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingEncryptedStuffFromGoogleShouldWorkIfHostIsResolvedFirst()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $factory = new ResolverFactory();
+ $dns = $factory->create('8.8.8.8', $loop);
+
+ $connector = new DnsConnector(
+ new SecureConnector(
+ new TcpConnector($loop),
+ $loop
+ ),
+ $dns
+ );
+
+ $conn = Block\await($connector->connect('google.com:443'), $loop);
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingPlaintextStuffFromEncryptedGoogleShouldNotWork()
+ {
+ $loop = Factory::create();
+ $connector = new Connector($loop);
+
+ $conn = Block\await($connector->connect('google.com:443'), $loop);
+
+ $this->assertContains(':443', $conn->getRemoteAddress());
+ $this->assertNotEquals('google.com:443', $conn->getRemoteAddress());
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertNotRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ public function testConnectingFailsIfDnsUsesInvalidResolver()
+ {
+ $loop = Factory::create();
+
+ $factory = new ResolverFactory();
+ $dns = $factory->create('demo.invalid', $loop);
+
+ $connector = new Connector($loop, array(
+ 'dns' => $dns
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
+ }
+
+ public function testCancellingPendingConnectionWithoutTimeoutShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+ $promise = $connector->connect('8.8.8.8:80');
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPendingConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop);
+
+ gc_collect_cycles();
+ $promise = $connector->connect('8.8.8.8:80');
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForRejectedConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+
+ $wait = true;
+ $promise = $connector->connect('127.0.0.1:1')->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect connection refused error
+ Block\sleep(0.01, $loop);
+ if ($wait) {
+ Block\sleep(0.2, $loop);
+ if ($wait) {
+ $this->fail('Connection attempt did not fail');
+ }
+ }
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @requires PHP 7
+ */
+ public function testWaitingForConnectionTimeoutShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => 0.001));
+
+ gc_collect_cycles();
+
+ $wait = true;
+ $promise = $connector->connect('google.com:80')->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect connection timeout error
+ Block\sleep(0.01, $loop);
+ if ($wait) {
+ Block\sleep(0.2, $loop);
+ if ($wait) {
+ $this->fail('Connection attempt did not fail');
+ }
+ }
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForInvalidDnsConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+
+ $wait = true;
+ $promise = $connector->connect('example.invalid:80')->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect DNS error
+ Block\sleep(0.01, $loop);
+ if ($wait) {
+ Block\sleep(0.2, $loop);
+ if ($wait) {
+ $this->fail('Connection attempt did not fail');
+ }
+ }
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForSuccessfullyClosedConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+ $promise = $connector->connect('google.com:80')->then(
+ function ($conn) {
+ $conn->close();
+ }
+ );
+ Block\await($promise, $loop, self::TIMEOUT);
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testConnectingFailsIfTimeoutIsTooSmall()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'timeout' => 0.001
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
+ }
+
+ public function testSelfSignedRejectsIfVerificationIsEnabled()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'verify_peer' => true
+ )
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
+ }
+
+ public function testSelfSignedResolvesIfVerificationIsDisabled()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'verify_peer' => false
+ )
+ ));
+
+ $conn = Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
+ $conn->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/LimitingServerTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/LimitingServerTest.php
new file mode 100755
index 0000000..2cc9a58
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/LimitingServerTest.php
@@ -0,0 +1,195 @@
+getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn('127.0.0.1:1234');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $this->assertEquals('127.0.0.1:1234', $server->getAddress());
+ }
+
+ public function testPauseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ }
+
+ public function testPauseTwiceWillBePassedThroughToTcpServerOnce()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testResumeWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->resume();
+ }
+
+ public function testResumeTwiceWillBePassedThroughToTcpServerOnce()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->resume();
+ $server->resume();
+ }
+
+ public function testCloseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('close');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->close();
+ }
+
+ public function testSocketErrorWillBeForwarded()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('error', array(new \RuntimeException('test')));
+ }
+
+ public function testSocketConnectionWillBeForwarded()
+ {
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 100);
+ $server->on('connection', $this->expectCallableOnceWith($connection));
+ $server->on('error', $this->expectCallableNever());
+
+ $tcp->emit('connection', array($connection));
+
+ $this->assertEquals(array($connection), $server->getConnections());
+ }
+
+ public function testSocketConnectionWillBeClosedOnceLimitIsReached()
+ {
+ $first = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $first->expects($this->never())->method('close');
+ $second = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $second->expects($this->once())->method('close');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 1);
+ $server->on('connection', $this->expectCallableOnceWith($first));
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('connection', array($first));
+ $tcp->emit('connection', array($second));
+ }
+
+ public function testPausingServerWillBePausedOnceLimitIsReached()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $tcp = new TcpServer(0, $loop);
+
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $server = new LimitingServer($tcp, 1, true);
+
+ $tcp->emit('connection', array($connection));
+ }
+
+ public function testSocketDisconnectionWillRemoveFromList()
+ {
+ $loop = Factory::create();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $socket = stream_socket_client($tcp->getAddress());
+ fclose($socket);
+
+ $server = new LimitingServer($tcp, 100);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('error', $this->expectCallableNever());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array(), $server->getConnections());
+ }
+
+ public function testPausingServerWillEmitOnlyOneButAcceptTwoConnectionsDueToOperatingSystem()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new LimitingServer($server, 1, true);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('error', $this->expectCallableNever());
+
+ $first = stream_socket_client($server->getAddress());
+ $second = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ fclose($first);
+ fclose($second);
+ }
+
+ public function testPausingServerWillEmitTwoConnectionsFromBacklog()
+ {
+ $loop = Factory::create();
+
+ $twice = $this->createCallableMock();
+ $twice->expects($this->exactly(2))->method('__invoke');
+
+ $server = new TcpServer(0, $loop);
+ $server = new LimitingServer($server, 1, true);
+ $server->on('connection', $twice);
+ $server->on('error', $this->expectCallableNever());
+
+ $first = stream_socket_client($server->getAddress());
+ fclose($first);
+ $second = stream_socket_client($server->getAddress());
+ fclose($second);
+
+ Block\sleep(0.1, $loop);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/SecureConnectorTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/SecureConnectorTest.php
new file mode 100755
index 0000000..0b3a702
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/SecureConnectorTest.php
@@ -0,0 +1,74 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->connector = new SecureConnector($this->tcp, $this->loop);
+ }
+
+ public function testConnectionWillWaitForTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectionWithCompleteUriWillBePassedThroughExpectForScheme()
+ {
+ $pending = new Promise\Promise(function () { });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80/path?query#fragment'))->will($this->returnValue($pending));
+
+ $this->connector->connect('tls://example.com:80/path?query#fragment');
+ }
+
+ public function testConnectionToInvalidSchemeWillReject()
+ {
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('tcp://example.com:80');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testCancelDuringTcpConnectionCancelsTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testConnectionWillBeClosedAndRejectedIfConnectioIsNoStream()
+ {
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $connection->expects($this->once())->method('close');
+
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->willReturn(Promise\resolve($connection));
+
+ $promise = $this->connector->connect('example.com:80');
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/SecureIntegrationTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/SecureIntegrationTest.php
new file mode 100755
index 0000000..8c9ba14
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/SecureIntegrationTest.php
@@ -0,0 +1,204 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $this->loop = LoopFactory::create();
+ $this->server = new TcpServer(0, $this->loop);
+ $this->server = new SecureServer($this->server, $this->loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $this->address = $this->server->getAddress();
+ $this->connector = new SecureConnector(new TcpConnector($this->loop), $this->loop, array('verify_peer' => false));
+ }
+
+ public function tearDown()
+ {
+ if ($this->server !== null) {
+ $this->server->close();
+ $this->server = null;
+ }
+ }
+
+ public function testConnectToServer()
+ {
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testConnectToServerEmitsConnection()
+ {
+ $promiseServer = $this->createPromiseForEvent($this->server, 'connection', $this->expectCallableOnce());
+
+ $promiseClient = $this->connector->connect($this->address);
+
+ list($_, $client) = Block\awaitAll(array($promiseServer, $promiseClient), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->close();
+ }
+
+ public function testSendSmallDataToServerReceivesOneChunk()
+ {
+ // server expects one connection which emits one data event
+ $received = new Deferred();
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($received) {
+ $peer->on('data', function ($chunk) use ($received) {
+ $received->resolve($chunk);
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->write('hello');
+
+ // await server to report one "data" event
+ $data = Block\await($received->promise(), $this->loop, self::TIMEOUT);
+
+ $client->close();
+
+ $this->assertEquals('hello', $data);
+ }
+
+ public function testSendDataWithEndToServerReceivesAllData()
+ {
+ $disconnected = new Deferred();
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($disconnected) {
+ $received = '';
+ $peer->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ $peer->on('close', function () use (&$received, $disconnected) {
+ $disconnected->resolve($received);
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $data = str_repeat('a', 200000);
+ $client->end($data);
+
+ // await server to report connection "close" event
+ $received = Block\await($disconnected->promise(), $this->loop, self::TIMEOUT);
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testSendDataWithoutEndingToServerReceivesAllData()
+ {
+ $received = '';
+ $this->server->on('connection', function (ConnectionInterface $peer) use (&$received) {
+ $peer->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $data = str_repeat('d', 200000);
+ $client->write($data);
+
+ // buffer incoming data for 0.1s (should be plenty of time)
+ Block\sleep(0.1, $this->loop);
+
+ $client->close();
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testConnectToServerWhichSendsSmallDataReceivesOneChunk()
+ {
+ $this->server->on('connection', function (ConnectionInterface $peer) {
+ $peer->write('hello');
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // await client to report one "data" event
+ $receive = $this->createPromiseForEvent($client, 'data', $this->expectCallableOnceWith('hello'));
+ Block\await($receive, $this->loop, self::TIMEOUT);
+
+ $client->close();
+ }
+
+ public function testConnectToServerWhichSendsDataWithEndReceivesAllData()
+ {
+ $data = str_repeat('b', 100000);
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
+ $peer->end($data);
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // await data from client until it closes
+ $received = $this->buffer($client, $this->loop, self::TIMEOUT);
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testConnectToServerWhichSendsDataWithoutEndingReceivesAllData()
+ {
+ $data = str_repeat('c', 100000);
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
+ $peer->write($data);
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // buffer incoming data for 0.1s (should be plenty of time)
+ $received = '';
+ $client->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ Block\sleep(0.1, $this->loop);
+
+ $client->close();
+
+ $this->assertEquals($data, $received);
+ }
+
+ private function createPromiseForEvent(EventEmitterInterface $emitter, $event, $fn)
+ {
+ return new Promise(function ($resolve) use ($emitter, $event, $fn) {
+ $emitter->on($event, function () use ($resolve, $fn) {
+ $resolve(call_user_func_array($fn, func_get_args()));
+ });
+ });
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/SecureServerTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/SecureServerTest.php
new file mode 100755
index 0000000..92c641f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/SecureServerTest.php
@@ -0,0 +1,105 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+ }
+
+ public function testGetAddressWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn('tcp://127.0.0.1:1234');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $this->assertEquals('tls://127.0.0.1:1234', $server->getAddress());
+ }
+
+ public function testGetAddressWillReturnNullIfTcpServerReturnsNull()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn(null);
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $this->assertNull($server->getAddress());
+ }
+
+ public function testPauseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->pause();
+ }
+
+ public function testResumeWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->resume();
+ }
+
+ public function testCloseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('close');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->close();
+ }
+
+ public function testConnectionWillBeEndedWithErrorIfItIsNotAStream()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $connection->expects($this->once())->method('end');
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('connection', array($connection));
+ }
+
+ public function testSocketErrorWillBeForwarded()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('error', array(new \RuntimeException('test')));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/ServerTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/ServerTest.php
new file mode 100755
index 0000000..14fdb2c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/ServerTest.php
@@ -0,0 +1,173 @@
+assertNotEquals(0, $server->getAddress());
+ $server->close();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsForInvalidUri()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new Server('invalid URI', $loop);
+ }
+
+ public function testConstructorCreatesExpectedTcpServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+
+ $connector = new TcpConnector($loop);
+ $connector->connect($server->getAddress())
+ ->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT);
+
+ $connection->close();
+ $server->close();
+ }
+
+ public function testConstructorCreatesExpectedUnixServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server($this->getRandomSocketUri(), $loop);
+
+ $connector = new UnixConnector($loop);
+ $connector->connect($server->getAddress())
+ ->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT);
+
+ $connection->close();
+ $server->close();
+ }
+
+ public function testEmitsConnectionForNewConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesNotEmitConnectionForNewConnectionToPausedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->pause();
+ $server->on('connection', $this->expectCallableNever());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesEmitConnectionForNewConnectionToResumedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->pause();
+ $server->on('connection', $this->expectCallableOnce());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ $server->resume();
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesNotAllowConnectionToClosedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+ $address = $server->getAddress();
+ $server->close();
+
+ $client = @stream_socket_client($address);
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertFalse($client);
+ }
+
+ public function testEmitsConnectionWithInheritedContextOptions()
+ {
+ if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
+ // https://3v4l.org/hB4Tc
+ $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
+ }
+
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop, array(
+ 'backlog' => 4
+ ));
+
+ $all = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$all) {
+ $all = stream_context_get_options($conn->stream);
+ });
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
+ }
+
+ public function testDoesNotEmitSecureConnectionForNewPlainConnection()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $server = new Server('tls://127.0.0.1:0', $loop, array(
+ 'tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ )
+ ));
+ $server->on('connection', $this->expectCallableNever());
+
+ $client = stream_socket_client(str_replace('tls://', '', $server->getAddress()));
+
+ Block\sleep(0.1, $loop);
+ }
+
+ private function getRandomSocketUri()
+ {
+ return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock';
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/Stub/CallableStub.php b/src/mpg25-instagram-api/vendor/react/socket/tests/Stub/CallableStub.php
new file mode 100755
index 0000000..1b197eb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/Stub/CallableStub.php
@@ -0,0 +1,10 @@
+data .= $data;
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ }
+
+ public function close()
+ {
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function getRemoteAddress()
+ {
+ return '127.0.0.1';
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/Stub/ServerStub.php b/src/mpg25-instagram-api/vendor/react/socket/tests/Stub/ServerStub.php
new file mode 100755
index 0000000..d9e74f4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/Stub/ServerStub.php
@@ -0,0 +1,18 @@
+connect('127.0.0.1:9999')
+ ->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldAddResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+
+ $valid = false;
+ $loop->expects($this->once())->method('addWriteStream')->with($this->callback(function ($arg) use (&$valid) {
+ $valid = is_resource($arg);
+ return true;
+ }));
+ $connector->connect($server->getAddress());
+
+ $this->assertTrue($valid);
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceed()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+
+ $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithRemoteAdressSameAsTarget()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertEquals('tcp://127.0.0.1:9999', $connection->getRemoteAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithLocalAdressOnLocalhost()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertContains('tcp://127.0.0.1:', $connection->getLocalAddress());
+ $this->assertNotEquals('tcp://127.0.0.1:9999', $connection->getLocalAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithNullAddressesAfterConnectionClosed()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $connection->close();
+
+ $this->assertNull($connection->getRemoteAddress());
+ $this->assertNull($connection->getLocalAddress());
+ }
+
+ /** @test */
+ public function connectionToTcpServerWillCloseWhenOtherSideCloses()
+ {
+ $loop = Factory::create();
+
+ // immediately close connection and server once connection is in
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', function (ConnectionInterface $conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $once = $this->expectCallableOnce();
+ $connector = new TcpConnector($loop);
+ $connector->connect($server->getAddress())->then(function (ConnectionInterface $conn) use ($once) {
+ $conn->write('hello');
+ $conn->on('close', $once);
+ });
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToEmptyIp6PortShouldFail()
+ {
+ $loop = Factory::create();
+
+ $connector = new TcpConnector($loop);
+ $connector
+ ->connect('[::1]:9999')
+ ->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToIp6TcpServerShouldSucceed()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:9999', $loop);
+ } catch (\Exception $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (IPv6 not supported on this system?)');
+ }
+
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('[::1]:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertEquals('tcp://[::1]:9999', $connection->getRemoteAddress());
+
+ $this->assertContains('tcp://[::1]:', $connection->getLocalAddress());
+ $this->assertNotEquals('tcp://[::1]:9999', $connection->getLocalAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToHostnameShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('www.google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function connectionToInvalidPortShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('255.255.255.255:12345678')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function connectionToInvalidSchemeShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('tls://google.com:443')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function cancellingConnectionShouldRemoveResourceFromLoopAndCloseResource()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+
+ $loop->expects($this->once())->method('addWriteStream');
+ $promise = $connector->connect($server->getAddress());
+
+ $resource = null;
+ $valid = false;
+ $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($arg) use (&$resource, &$valid) {
+ $resource = $arg;
+ $valid = is_resource($arg);
+ return true;
+ }));
+ $promise->cancel();
+
+ // ensure that this was a valid resource during the removeWriteStream() call
+ $this->assertTrue($valid);
+
+ // ensure that this resource should now be closed after the cancel() call
+ $this->assertInternalType('resource', $resource);
+ $this->assertFalse(is_resource($resource));
+ }
+
+ /** @test */
+ public function cancellingConnectionShouldRejectPromise()
+ {
+ $loop = Factory::create();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $this->setExpectedException('RuntimeException', 'Cancelled');
+ Block\await($promise, $loop);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/TcpServerTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/TcpServerTest.php
new file mode 100755
index 0000000..72b3c28
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/TcpServerTest.php
@@ -0,0 +1,285 @@
+loop = $this->createLoop();
+ $this->server = new TcpServer(0, $this->loop);
+
+ $this->port = parse_url($this->server->getAddress(), PHP_URL_PORT);
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::handleConnection
+ */
+ public function testConnection()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $this->server->on('connection', $this->expectCallableOnce());
+
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::handleConnection
+ */
+ public function testConnectionWithManyClients()
+ {
+ $client1 = stream_socket_client('tcp://localhost:'.$this->port);
+ $client2 = stream_socket_client('tcp://localhost:'.$this->port);
+ $client3 = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $this->server->on('connection', $this->expectCallableExactly(3));
+ $this->tick();
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataEventWillNotBeEmittedWhenClientSendsNoData()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedWithDataClientSends()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ fwrite($client, "foo\n");
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+ fwrite($client, "foo\n");
+ stream_socket_shutdown($client, STREAM_SHUT_WR);
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testLoopWillEndWhenServerIsClosed()
+ {
+ // explicitly unset server because we already call close()
+ $this->server->close();
+ $this->server = null;
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testCloseTwiceIsNoOp()
+ {
+ $this->server->close();
+ $this->server->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testGetAddressAfterCloseReturnsNull()
+ {
+ $this->server->close();
+ $this->assertNull($this->server->getAddress());
+ }
+
+ public function testLoopWillEndWhenServerIsClosedAfterSingleConnection()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $server->on('connection', function ($conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+ $stream = new DuplexResourceStream($client, $this->loop);
+
+ $bytes = 1024 * 1024;
+ $stream->end(str_repeat('*', $bytes));
+
+ $mock = $this->expectCallableOnce();
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $received = 0;
+ $server->on('connection', function ($conn) use ($mock, &$received, $server) {
+ // count number of bytes received
+ $conn->on('data', function ($data) use (&$received) {
+ $received += strlen($data);
+ });
+
+ $conn->on('end', $mock);
+
+ // do not await any further connections in order to let the loop terminate
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ $this->assertEquals($bytes, $received);
+ }
+
+ public function testConnectionDoesNotEndWhenClientDoesNotClose()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\Connection::end
+ */
+ public function testConnectionDoesEndWhenClientCloses()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ fclose($client);
+
+ $mock = $this->expectCallableOnce();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testCtorAddsResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new TcpServer(0, $loop);
+ }
+
+ public function testResumeWithoutPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->resume();
+ }
+
+ public function testPauseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->pause();
+ }
+
+ public function testPauseAfterPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testCloseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testListenOnBusyPortThrows()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Windows supports listening on same port multiple times');
+ }
+
+ $another = new TcpServer($this->port, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::close
+ */
+ public function tearDown()
+ {
+ if ($this->server) {
+ $this->server->close();
+ }
+ }
+
+ private function tick()
+ {
+ Block\sleep(0, $this->loop);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/TestCase.php b/src/mpg25-instagram-api/vendor/react/socket/tests/TestCase.php
new file mode 100755
index 0000000..e87fc2f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/TestCase.php
@@ -0,0 +1,101 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Socket\Stub\CallableStub')->getMock();
+ }
+
+ protected function buffer(ReadableStreamInterface $stream, LoopInterface $loop, $timeout)
+ {
+ if (!$stream->isReadable()) {
+ return '';
+ }
+
+ return Block\await(new Promise(
+ function ($resolve, $reject) use ($stream) {
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $reject);
+
+ $stream->on('close', function () use (&$buffer, $resolve) {
+ $resolve($buffer);
+ });
+ },
+ function () use ($stream) {
+ $stream->close();
+ throw new \RuntimeException();
+ }
+ ), $loop, $timeout);
+ }
+
+ public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
+ {
+ if (method_exists($this, 'expectException')) {
+ // PHPUnit 5+
+ $this->expectException($exception);
+ if ($exceptionMessage !== '') {
+ $this->expectExceptionMessage($exceptionMessage);
+ }
+ if ($exceptionCode !== null) {
+ $this->expectExceptionCode($exceptionCode);
+ }
+ } else {
+ // legacy PHPUnit 4
+ parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/TimeoutConnectorTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/TimeoutConnectorTest.php
new file mode 100755
index 0000000..64787d9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/TimeoutConnectorTest.php
@@ -0,0 +1,103 @@
+getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testRejectsWhenConnectorRejects()
+ {
+ $promise = Promise\reject(new \RuntimeException());
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 5.0, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testResolvesWhenConnectorResolves()
+ {
+ $promise = Promise\resolve();
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 5.0, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableOnce(),
+ $this->expectCallableNever()
+ );
+
+ $loop->run();
+ }
+
+ public function testRejectsAndCancelsPendingPromiseOnTimeout()
+ {
+ $promise = new Promise\Promise(function () { }, $this->expectCallableOnce());
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testCancelsPendingPromiseOnCancel()
+ {
+ $promise = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $out = $timeout->connect('google.com:80');
+ $out->cancel();
+
+ $out->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/UnixConnectorTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/UnixConnectorTest.php
new file mode 100755
index 0000000..1564064
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/UnixConnectorTest.php
@@ -0,0 +1,64 @@
+loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->connector = new UnixConnector($this->loop);
+ }
+
+ public function testInvalid()
+ {
+ $promise = $this->connector->connect('google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testInvalidScheme()
+ {
+ $promise = $this->connector->connect('tcp://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testValid()
+ {
+ // random unix domain socket path
+ $path = sys_get_temp_dir() . '/test' . uniqid() . '.sock';
+
+ // temporarily create unix domain socket server to connect to
+ $server = stream_socket_server('unix://' . $path, $errno, $errstr);
+
+ // skip test if we can not create a test server (Windows etc.)
+ if (!$server) {
+ $this->markTestSkipped('Unable to create socket "' . $path . '": ' . $errstr . '(' . $errno .')');
+ return;
+ }
+
+ // tests succeeds if we get notified of successful connection
+ $promise = $this->connector->connect($path);
+ $promise->then($this->expectCallableOnce());
+
+ // remember remote and local address of this connection and close again
+ $remote = $local = false;
+ $promise->then(function(ConnectionInterface $conn) use (&$remote, &$local) {
+ $remote = $conn->getRemoteAddress();
+ $local = $conn->getLocalAddress();
+ $conn->close();
+ });
+
+ // clean up server
+ fclose($server);
+ unlink($path);
+
+ $this->assertNull($local);
+ $this->assertEquals('unix://' . $path, $remote);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/socket/tests/UnixServerTest.php b/src/mpg25-instagram-api/vendor/react/socket/tests/UnixServerTest.php
new file mode 100755
index 0000000..10f7e4f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/socket/tests/UnixServerTest.php
@@ -0,0 +1,283 @@
+loop = Factory::create();
+ $this->uds = $this->getRandomSocketUri();
+ $this->server = new UnixServer($this->uds, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::handleConnection
+ */
+ public function testConnection()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $this->server->on('connection', $this->expectCallableOnce());
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::handleConnection
+ */
+ public function testConnectionWithManyClients()
+ {
+ $client1 = stream_socket_client($this->uds);
+ $client2 = stream_socket_client($this->uds);
+ $client3 = stream_socket_client($this->uds);
+
+ $this->server->on('connection', $this->expectCallableExactly(3));
+ $this->tick();
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataEventWillNotBeEmittedWhenClientSendsNoData()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedWithDataClientSends()
+ {
+ $client = stream_socket_client($this->uds);
+
+ fwrite($client, "foo\n");
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending()
+ {
+ $client = stream_socket_client($this->uds);
+ fwrite($client, "foo\n");
+ stream_socket_shutdown($client, STREAM_SHUT_WR);
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testLoopWillEndWhenServerIsClosed()
+ {
+ // explicitly unset server because we already call close()
+ $this->server->close();
+ $this->server = null;
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testCloseTwiceIsNoOp()
+ {
+ $this->server->close();
+ $this->server->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testGetAddressAfterCloseReturnsNull()
+ {
+ $this->server->close();
+ $this->assertNull($this->server->getAddress());
+ }
+
+ public function testLoopWillEndWhenServerIsClosedAfterSingleConnection()
+ {
+ $client = stream_socket_client($this->uds);
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $server->on('connection', function ($conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts()
+ {
+ $client = stream_socket_client($this->uds);
+ $stream = new DuplexResourceStream($client, $this->loop);
+
+ $bytes = 1024 * 1024;
+ $stream->end(str_repeat('*', $bytes));
+
+ $mock = $this->expectCallableOnce();
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $received = 0;
+ $server->on('connection', function ($conn) use ($mock, &$received, $server) {
+ // count number of bytes received
+ $conn->on('data', function ($data) use (&$received) {
+ $received += strlen($data);
+ });
+
+ $conn->on('end', $mock);
+
+ // do not await any further connections in order to let the loop terminate
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ $this->assertEquals($bytes, $received);
+ }
+
+ public function testConnectionDoesNotEndWhenClientDoesNotClose()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\Connection::end
+ */
+ public function testConnectionDoesEndWhenClientCloses()
+ {
+ $client = stream_socket_client($this->uds);
+
+ fclose($client);
+
+ $mock = $this->expectCallableOnce();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testCtorAddsResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ }
+
+ public function testResumeWithoutPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->resume();
+ }
+
+ public function testPauseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->pause();
+ }
+
+ public function testPauseAfterPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testCloseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testListenOnBusyPortThrows()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Windows supports listening on same port multiple times');
+ }
+
+ $another = new UnixServer($this->uds, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::close
+ */
+ public function tearDown()
+ {
+ if ($this->server) {
+ $this->server->close();
+ }
+ }
+
+ private function getRandomSocketUri()
+ {
+ return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock';
+ }
+
+ private function tick()
+ {
+ Block\sleep(0, $this->loop);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/.gitignore b/src/mpg25-instagram-api/vendor/react/stream/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/src/mpg25-instagram-api/vendor/react/stream/.travis.yml b/src/mpg25-instagram-api/vendor/react/stream/.travis.yml
new file mode 100755
index 0000000..9ff354b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/.travis.yml
@@ -0,0 +1,51 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+# - 7.0 # Mac OS X test setup, ignore errors, see below
+ - 7.1
+ - 7.2
+ - 7.3
+ - nightly # ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ include:
+ - os: osx
+ language: generic
+ php: 7.0 # just to look right on travis
+ env:
+ - PACKAGE: php70
+ allow_failures:
+ - php: nightly
+ - php: hhvm
+ - os: osx
+
+install:
+ # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew tap homebrew/homebrew-php
+ echo "Installing PHP ..."
+ brew install "${PACKAGE}"
+ brew install "${PACKAGE}"-xdebug
+ brew link "${PACKAGE}"
+ echo "Installing composer ..."
+ curl -s http://getcomposer.org/installer | php
+ mv composer.phar /usr/local/bin/composer
+ fi
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
+ - time php examples/91-benchmark-throughput.php
diff --git a/src/mpg25-instagram-api/vendor/react/stream/CHANGELOG.md b/src/mpg25-instagram-api/vendor/react/stream/CHANGELOG.md
new file mode 100755
index 0000000..c5a7dd3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/CHANGELOG.md
@@ -0,0 +1,398 @@
+# Changelog
+
+## 1.1.0 (2018-01-01)
+
+* Improvement: Increase performance by optimizing global function and constant look ups
+ (#137 by @WyriHaximus)
+* Travis: Test against PHP 7.3
+ (#138 by @WyriHaximus)
+* Fix: Ignore empty reads
+ (#139 by @WyriHaximus)
+
+## 1.0.0 (2018-07-11)
+
+* First stable LTS release, now following [SemVer](https://semver.org/).
+ We'd like to emphasize that this component is production ready and battle-tested.
+ We plan to support all long-term support (LTS) releases for at least 24 months,
+ so you have a rock-solid foundation to build on top of.
+
+> Contains no other changes, so it's actually fully compatible with the v0.7.7 release.
+
+## 0.7.7 (2018-01-19)
+
+* Improve test suite by fixing forward compatibility with upcoming EventLoop
+ releases, avoid risky tests and add test group to skip integration tests
+ relying on internet connection and apply appropriate test timeouts.
+ (#128, #131 and #132 by @clue)
+
+## 0.7.6 (2017-12-21)
+
+* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12
+ (#126 by @clue)
+
+* Improve test suite by simplifying test bootstrapping logic via Composer and
+ test against PHP 7.2
+ (#127 by @clue and #124 by @carusogabriel)
+
+## 0.7.5 (2017-11-20)
+
+* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream`
+ (#119 by @clue)
+
+* Fix: Fix forward compatibility with upcoming EventLoop releases
+ (#121 by @clue)
+
+* Restructure examples to ease getting started
+ (#123 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#122 by @gabriel-caruso and #120 by @clue)
+
+## 0.7.4 (2017-10-11)
+
+* Fix: Remove event listeners from `CompositeStream` once closed and
+ remove undocumented left-over `close` event argument
+ (#116 by @clue)
+
+* Minor documentation improvements: Fix wrong class name in example,
+ fix typos in README and
+ fix forward compatibility with upcoming EventLoop releases in example
+ (#113 by @docteurklein and #114 and #115 by @clue)
+
+* Improve test suite by running against Mac OS X on Travis
+ (#112 by @clue)
+
+## 0.7.3 (2017-08-05)
+
+* Improvement: Support Événement 3.0 a long side 2.0 and 1.0
+ (#108 by @WyriHaximus)
+
+* Readme: Corrected loop initialization in usage example
+ (#109 by @pulyavin)
+
+* Travis: Lock linux distribution preventing future builds from breaking
+ (#110 by @clue)
+
+## 0.7.2 (2017-06-15)
+
+* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream.
+ (#107 by @WyriHaximus)
+
+## 0.7.1 (2017-05-20)
+
+* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of
+ bytes to write at once.
+ (#105 by @clue)
+
+ ```php
+ $stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+ ```
+
+* Ignore HHVM test failures for now until Travis tests work again
+ (#106 by @clue)
+
+## 0.7.0 (2017-05-04)
+
+* Removed / BC break: Remove deprecated and unneeded functionality
+ (#45, #87, #90, #91 and #93 by @clue)
+
+ * Remove deprecated `Stream` class, use `DuplexResourceStream` instead
+ (#87 by @clue)
+
+ * Remove public `$buffer` property, use new constructor parameters instead
+ (#91 by @clue)
+
+ * Remove public `$stream` property from all resource streams
+ (#90 by @clue)
+
+ * Remove undocumented and now unused `ReadableStream` and `WritableStream`
+ (#93 by @clue)
+
+ * Remove `BufferedSink`
+ (#45 by @clue)
+
+* Feature / BC break: Simplify `ThroughStream` by using data callback instead of
+ inheritance. It is now a direct implementation of `DuplexStreamInterface`.
+ (#88 and #89 by @clue)
+
+ ```php
+ $through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+ });
+ $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+ $through->write(array(2, true));
+ ```
+
+* Feature / BC break: The `CompositeStream` starts closed if either side is
+ already closed and forwards pause to pipe source on first write attempt.
+ (#96 and #103 by @clue)
+
+ If either side of the composite stream closes, it will also close the other
+ side. We now also ensure that if either side is already closed during
+ instantiation, it will also close the other side.
+
+* BC break: Mark all classes as `final` and
+ mark internal API as `private` to discourage inheritance
+ (#95 and #99 by @clue)
+
+* Feature / BC break: Only emit `error` event for fatal errors
+ (#92 by @clue)
+
+ > The `error` event was previously also allowed to be emitted for non-fatal
+ errors, but our implementations actually only ever emitted this as a fatal
+ error and then closed the stream.
+
+* Feature: Explicitly allow custom events and exclude any semantics
+ (#97 by @clue)
+
+* Strict definition for event callback functions
+ (#101 by @clue)
+
+* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation
+ (#100 and #102 by @clue)
+
+* Actually require all dependencies so this is self-contained and improve
+ forward compatibility with EventLoop v1.0 and v0.5
+ (#94 and #98 by @clue)
+
+## 0.6.0 (2017-03-26)
+
+* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream`
+ (#85 by @clue)
+
+ ```php
+ // old (does still work for BC reasons)
+ $stream = new Stream($connection, $loop);
+
+ // new
+ $stream = new DuplexResourceStream($connection, $loop);
+ ```
+
+ Note that the `DuplexResourceStream` now rejects read-only or write-only
+ streams, so this may affect BC. If you want a read-only or write-only
+ resource, use `ReadableResourceStream` or `WritableResourceStream` instead of
+ `DuplexResourceStream`.
+
+ > BC note: This class was previously called `Stream`. The `Stream` class still
+ exists for BC reasons and will be removed in future versions of this package.
+
+* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`)
+ (#84 by @clue)
+
+ ```php
+ // old
+ $stream = new Buffer(STDOUT, $loop);
+
+ // new
+ $stream = new WritableResourceStream(STDOUT, $loop);
+ ```
+
+* Feature: Add `ReadableResourceStream`
+ (#83 by @clue)
+
+ ```php
+ $stream = new ReadableResourceStream(STDIN, $loop);
+ ```
+
+* Fix / BC Break: Enforce using non-blocking I/O
+ (#46 by @clue)
+
+ > BC note: This is known to affect process pipes on Windows which do not
+ support non-blocking I/O and could thus block the whole EventLoop previously.
+
+* Feature / Fix / BC break: Consistent semantics for
+ `DuplexStreamInterface::end()` to ensure it SHOULD also end readable side
+ (#86 by @clue)
+
+* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4
+ (#80 by @clue)
+
+## 0.5.0 (2017-03-08)
+
+* Feature / BC break: Consistent `end` event semantics (EOF)
+ (#70 by @clue)
+
+ The `end` event will now only be emitted for a *successful* end, not if the
+ stream closes due to an unrecoverable `error` event or if you call `close()`
+ explicitly.
+ If you want to detect when the stream closes (terminates), use the `close`
+ event instead.
+
+* BC break: Remove custom (undocumented) `full-drain` event from `Buffer`
+ (#63 and #68 by @clue)
+
+ > The `full-drain` event was undocumented and mostly used internally.
+ Relying on this event has attracted some low-quality code in the past, so
+ we've removed this from the public API in order to work out a better
+ solution instead.
+ If you want to detect when the buffer finishes flushing data to the stream,
+ you may want to look into its `end()` method or the `close` event instead.
+
+* Feature / BC break: Consistent event semantics and documentation,
+ explicitly state *when* events will be emitted and *which* arguments they
+ receive.
+ (#73 and #69 by @clue)
+
+ The documentation now explicitly defines each event and its arguments.
+ Custom events and event arguments are still supported.
+ Most notably, all defined events only receive inherently required event
+ arguments and no longer transmit the instance they are emitted on for
+ consistency and performance reasons.
+
+ ```php
+ // old (inconsistent and not supported by all implementations)
+ $stream->on('data', function ($data, $stream) {
+ // process $data
+ });
+
+ // new (consistent throughout the whole ecosystem)
+ $stream->on('data', function ($data) use ($stream) {
+ // process $data
+ });
+ ```
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature / BC break: Consistent method semantics and documentation
+ (#72 by @clue)
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature: Consistent `pipe()` semantics for closed and closing streams
+ (#71 from @clue)
+
+ The source stream will now always be paused via `pause()` when the
+ destination stream closes. Also, properly stop piping if the source
+ stream closes and remove all event forwarding.
+
+* Improve test suite by adding PHPUnit to `require-dev` and improving coverage.
+ (#74 and #75 by @clue, #66 by @nawarian)
+
+## 0.4.6 (2017-01-25)
+
+* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone)
+ (#62 by @clue)
+
+* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream`
+ (#60 by @clue)
+
+* Fix: Consistent `close` event behavior for `Buffer`
+ (#61 by @clue)
+
+## 0.4.5 (2016-11-13)
+
+* Feature: Support setting read buffer size to `null` (infinite)
+ (#42 by @clue)
+
+* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event
+ (#55 by @clue)
+
+* Vastly improved performance by factor of 10x to 20x.
+ Raise default buffer sizes to 64 KiB and simplify and improve error handling
+ and unneeded function calls.
+ (#53, #55, #56 by @clue)
+
+## 0.4.4 (2016-08-22)
+
+* Bug fix: Emit `error` event and close `Stream` when accessing the underlying
+ stream resource fails with a permanent error.
+ (#52 and #40 by @clue, #25 by @lysenkobv)
+
+* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF)
+ (#39 by @clue)
+
+* Bug fix: Ignore empty writes to `Buffer`
+ (#51 by @clue)
+
+* Add benchmarking script to measure throughput in CI
+ (#41 by @clue)
+
+## 0.4.3 (2015-10-07)
+
+* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau)
+* Bug fix: No double-write during drain call (@arnaud-lb)
+* Bug fix: Support HHVM (@clue)
+* Adjust compatibility to 5.3 (@clue)
+
+## 0.4.2 (2014-09-09)
+
+* Added DuplexStreamInterface
+* Stream sets stream resources to non-blocking
+* Fixed potential race condition in pipe
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: [Stream] Correctly detect closed connections
+
+## 0.3.2 (2013-05-10)
+
+* Bug fix: [Stream] Make sure CompositeStream is closed properly
+
+## 0.3.1 (2013-04-21)
+
+* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()`
+
+## 0.3.0 (2013-04-14)
+
+* Feature: [Stream] Factory method for BufferedSink
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.5 (2012-11-26)
+
+* Feature: Make BufferedSink trigger progress events on the promise (@jsor)
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream
+* Feature: Added BufferedSink
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Version bump
+
+## 0.2.1 (2012-10-14)
+
+* Bug fix: Check for EOF in `Buffer::write()`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/src/mpg25-instagram-api/vendor/react/stream/LICENSE b/src/mpg25-instagram-api/vendor/react/stream/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/react/stream/README.md b/src/mpg25-instagram-api/vendor/react/stream/README.md
new file mode 100755
index 0000000..b5bc907
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/README.md
@@ -0,0 +1,1225 @@
+# Stream
+
+[](https://travis-ci.org/reactphp/stream)
+
+Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/).
+
+In order to make the [EventLoop](https://github.com/reactphp/event-loop)
+easier to use, this component introduces the powerful concept of "streams".
+Streams allow you to efficiently process huge amounts of data (such as a multi
+Gigabyte file download) in small chunks without having to store everything in
+memory at once.
+They are very similar to the streams found in PHP itself,
+but have an interface more suited for async, non-blocking I/O.
+
+**Table of contents**
+
+* [Stream usage](#stream-usage)
+ * [ReadableStreamInterface](#readablestreaminterface)
+ * [data event](#data-event)
+ * [end event](#end-event)
+ * [error event](#error-event)
+ * [close event](#close-event)
+ * [isReadable()](#isreadable)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [pipe()](#pipe)
+ * [close()](#close)
+ * [WritableStreamInterface](#writablestreaminterface)
+ * [drain event](#drain-event)
+ * [pipe event](#pipe-event)
+ * [error event](#error-event-1)
+ * [close event](#close-event-1)
+ * [isWritable()](#iswritable)
+ * [write()](#write)
+ * [end()](#end)
+ * [close()](#close-1)
+ * [DuplexStreamInterface](#duplexstreaminterface)
+* [Creating streams](#creating-streams)
+ * [ReadableResourceStream](#readableresourcestream)
+ * [WritableResourceStream](#writableresourcestream)
+ * [DuplexResourceStream](#duplexresourcestream)
+ * [ThroughStream](#throughstream)
+ * [CompositeStream](#compositestream)
+* [Usage](#usage)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Stream usage
+
+ReactPHP uses the concept of "streams" throughout its ecosystem to provide a
+consistent higher-level abstraction for processing streams of arbitrary data
+contents and size.
+While a stream itself is a quite low-level concept, it can be used as a powerful
+abstraction to build higher-level components and protocols on top.
+
+If you're new to this concept, it helps to think of them as a water pipe:
+You can consume water from a source or you can produce water and forward (pipe)
+it to any destination (sink).
+
+Similarly, streams can either be
+
+* readable (such as `STDIN` terminal input) or
+* writable (such as `STDOUT` terminal output) or
+* duplex (both readable *and* writable, such as a TCP/IP connection)
+
+Accordingly, this package defines the following three interfaces
+
+* [`ReadableStreamInterface`](#readablestreaminterface)
+* [`WritableStreamInterface`](#writablestreaminterface)
+* [`DuplexStreamInterface`](#duplexstreaminterface)
+
+### ReadableStreamInterface
+
+The `ReadableStreamInterface` is responsible for providing an interface for
+read-only streams and the readable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### data event
+
+The `data` event will be emitted whenever some data was read/received
+from this source stream.
+The event receives a single mixed argument for incoming data.
+
+```php
+$stream->on('data', function ($data) {
+ echo $data;
+});
+```
+
+This event MAY be emitted any number of times, which may be zero times if
+this stream does not send any data at all.
+It SHOULD not be emitted after an `end` or `close` event.
+
+The given `$data` argument may be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit the raw (binary) payload data that is received over the wire as
+chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end event
+
+The `end` event will be emitted once the source stream has successfully
+reached the end of the stream (EOF).
+
+```php
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+a successful end was detected.
+It SHOULD NOT be emitted after a previous `end` or `close` event.
+It MUST NOT be emitted if the stream closes due to a non-successful
+end, such as after a previous `error` event.
+
+After the stream is ended, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+This event will only be emitted if the *end* was reached successfully,
+not if the stream was interrupted by an unrecoverable error or explicitly
+closed. Not all streams know this concept of a "successful end".
+Many use-cases involve detecting when the stream closes (terminates)
+instead, in this case you should use the `close` event.
+After the stream emits an `end` event, it SHOULD usually be followed by a
+`close` event.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit this event if either the remote side closes the connection or
+a file handle was successfully read until reaching its end (EOF).
+
+Note that this event should not be confused with the `end()` method.
+This event defines a successful end *reading* from a source stream, while
+the `end()` method defines *writing* a successful end to a destination
+stream.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to read from this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error or after an unexpected `data` or premature
+`end` event.
+It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-readable mode, see
+also `close()` and `isReadable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and do not make assumption about data
+boundaries (such as unexpected `data` or premature `end` events).
+In other words, many lower-level protocols (such as TCP/IP) may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+Unlike the `end` event, this event SHOULD be emitted whenever the stream
+closes, irrespective of whether this happens implicitly due to an
+unrecoverable error or explicitly when either side closes the stream.
+If you only want to detect a *successful* end, you should use the `end`
+event instead.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after reading a *successful* `end`
+event or after a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isReadable()
+
+The `isReadable(): bool` method can be used to
+check whether this stream is in a readable state (not closed already).
+
+This method can be used to check if the stream still accepts incoming
+data events or if it is ended or closed already.
+Once the stream is non-readable, no further `data` or `end` events SHOULD
+be emitted.
+
+```php
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+A successfully opened stream always MUST start in readable mode.
+
+Once the stream ends or closes, it MUST switch to non-readable mode.
+This can happen any time, explicitly through `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-readable mode, it MUST NOT transition
+back to readable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `isWritable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause reading incoming data events.
+
+Removes the data source file descriptor from the event loop. This
+allows you to throttle incoming data.
+
+Unless otherwise noted, a successfully opened stream SHOULD NOT start
+in paused state.
+
+Once the stream is paused, no futher `data` or `end` events SHOULD
+be emitted.
+
+```php
+$stream->pause();
+
+$stream->on('data', assertShouldNeverCalled());
+$stream->on('end', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+stream MAY continue emitting `data` events.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+
+See also `resume()`.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume reading incoming data events.
+
+Re-attach the data source after a previous `pause()`.
+
+```php
+$stream->pause();
+
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+
+See also `pause()`.
+
+#### pipe()
+
+The `pipe(WritableStreamInterface $dest, array $options = [])` method can be used to
+pipe all the data from this readable source into the given writable destination.
+
+Automatically sends all incoming data to the destination.
+Automatically throttles the source based on what the destination can handle.
+
+```php
+$source->pipe($dest);
+```
+
+Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+into itself in order to write back all the data that is received.
+This may be a useful feature for a TCP/IP echo service:
+
+```php
+$connection->pipe($connection);
+```
+
+This method returns the destination stream as-is, which can be used to
+set up chains of piped streams:
+
+```php
+$source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+```
+
+By default, this will call `end()` on the destination stream once the
+source stream emits an `end` event. This can be disabled like this:
+
+```php
+$source->pipe($dest, array('end' => false));
+```
+
+Note that this only applies to the `end` event.
+If an `error` or explicit `close` event happens on the source stream,
+you'll have to manually close the destination stream:
+
+```php
+$source->pipe($dest);
+$source->on('close', function () use ($dest) {
+ $dest->end('BYE!');
+});
+```
+
+If the source stream is not readable (closed state), then this is a NO-OP.
+
+```php
+$source->close();
+$source->pipe($dest); // NO-OP
+```
+
+If the destinantion stream is not writable (closed state), then this will simply
+throttle (pause) the source stream:
+
+```php
+$dest->close();
+$source->pipe($dest); // calls $source->pause()
+```
+
+Similarly, if the destination stream is closed while the pipe is still
+active, it will also throttle (pause) the source stream:
+
+```php
+$source->pipe($dest);
+$dest->close(); // calls $source->pause()
+```
+
+Once the pipe is set up successfully, the destination stream MUST emit
+a `pipe` event with this source stream an event argument.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to (forcefully) close the stream.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-readable
+mode, see also `isReadable()`.
+This means that no further `data` or `end` events SHOULD be emitted.
+
+```php
+$stream->close();
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this method should not be confused with the `end()` method.
+
+### WritableStreamInterface
+
+The `WritableStreamInterface` is responsible for providing an interface for
+write-only streams and the writable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### drain event
+
+The `drain` event will be emitted whenever the write buffer became full
+previously and is now ready to accept more data.
+
+```php
+$stream->on('drain', function () use ($stream) {
+ echo 'Stream is now ready to accept more data';
+});
+```
+
+This event SHOULD be emitted once every time the buffer became full
+previously and is now ready to accept more data.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if the buffer never became full in the first place.
+This event SHOULD NOT be emitted if the buffer has not become full
+previously.
+
+This event is mostly used internally, see also `write()` for more details.
+
+#### pipe event
+
+The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+into this stream.
+The event receives a single `ReadableStreamInterface` argument for the
+source stream.
+
+```php
+$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ echo 'Now receiving piped data';
+
+ // explicitly close target if source emits an error
+ $source->on('error', function () use ($stream) {
+ $stream->close();
+ });
+});
+
+$source->pipe($stream);
+```
+
+This event MUST be emitted once for each readable stream that is
+successfully piped into this destination stream.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if no stream is ever piped into this stream.
+This event MUST NOT be emitted if either the source is not readable
+(closed already) or this destination is not writable (closed already).
+
+This event is mostly used internally, see also `pipe()` for more details.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to write to this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$stream->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error.
+It SHOULD NOT be emitted after a previous `error` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-writable mode, see
+also `close()` and `isWritable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-writable mode,
+see also `isWritable()`.
+
+This event SHOULD be emitted whenever the stream closes, irrespective of
+whether this happens implicitly due to an unrecoverable error or
+explicitly when either side closes the stream.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after flushing the buffer from
+the `end()` method, after receiving a *successful* `end` event or after
+a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isWritable()
+
+The `isWritable(): bool` method can be used to
+check whether this stream is in a writable state (not closed already).
+
+This method can be used to check if the stream still accepts writing
+any data or if it is ended or closed already.
+Writing any data to a non-writable stream is a NO-OP:
+
+```php
+assert($stream->isWritable() === false);
+
+$stream->write('end'); // NO-OP
+$stream->end('end'); // NO-OP
+```
+
+A successfully opened stream always MUST start in writable mode.
+
+Once the stream ends or closes, it MUST switch to non-writable mode.
+This can happen any time, explicitly through `end()` or `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-writable mode, it MUST NOT transition
+back to writable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `isReadable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### write()
+
+The `write(mixed $data): bool` method can be used to
+write some data into the stream.
+
+A successful write MUST be confirmed with a boolean `true`, which means
+that either the data was written (flushed) immediately or is buffered and
+scheduled for a future write. Note that this interface gives you no
+control over explicitly flushing the buffered data, as finding the
+appropriate time for this is beyond the scope of this interface and left
+up to the implementation of this interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+an `error` event and MAY `close()` the stream if it can not recover from
+this error.
+
+If the internal buffer is full after adding `$data`, then `write()`
+SHOULD return `false`, indicating that the caller should stop sending
+data until the buffer drains.
+The stream SHOULD send a `drain` event once the buffer is ready to accept
+more data.
+
+Similarly, if the the stream is not writable (already in a closed state)
+it MUST NOT process the given `$data` and SHOULD return `false`,
+indicating that the caller should stop sending data.
+
+The given `$data` argument MAY be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will only accept the raw (binary) payload data that is transferred over
+the wire as chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end()
+
+The `end(mixed $data = null): void` method can be used to
+successfully end the stream (after optionally sending some final data).
+
+This method can be used to successfully end the stream, i.e. close
+the stream after sending out all data that is currently buffered.
+
+```php
+$stream->write('hello');
+$stream->write('world');
+$stream->end();
+```
+
+If there's no data currently buffered and nothing to be flushed, then
+this method MAY `close()` the stream immediately.
+
+If there's still data in the buffer that needs to be flushed first, then
+this method SHOULD try to write out this data and only then `close()`
+the stream.
+Once the stream is closed, it SHOULD emit a `close` event.
+
+Note that this interface gives you no control over explicitly flushing
+the buffered data, as finding the appropriate time for this is beyond the
+scope of this interface and left up to the implementation of this
+interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+You can optionally pass some final data that is written to the stream
+before ending the stream. If a non-`null` value is given as `$data`, then
+this method will behave just like calling `write($data)` before ending
+with no data.
+
+```php
+// shorter version
+$stream->end('bye');
+
+// same as longer version
+$stream->write('bye');
+$stream->end();
+```
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->end();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+also end its readable side, unless the stream supports half-open mode.
+In other words, after calling this method, these streams SHOULD switch
+into non-writable AND non-readable mode, see also `isReadable()`.
+This implies that in this case, the stream SHOULD NOT emit any `data`
+or `end` events anymore.
+Streams MAY choose to use the `pause()` method logic for this, but
+special care may have to be taken to ensure a following call to the
+`resume()` method SHOULD NOT continue emitting readable events.
+
+Note that this method should not be confused with the `close()` method.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to forcefully close the stream, i.e. close
+the stream without waiting for any buffered data to be flushed.
+If there's still data in the buffer, this data SHOULD be discarded.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->close();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+Note that this method should not be confused with the `end()` method.
+Unlike the `end()` method, this method does not take care of any existing
+buffers and simply discards any buffer contents.
+Likewise, this method may also be called after calling `end()` on a
+stream in order to stop waiting for the stream to flush its final data.
+
+```php
+$stream->end();
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->close();
+});
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+
+### DuplexStreamInterface
+
+The `DuplexStreamInterface` is responsible for providing an interface for
+duplex streams (both readable and writable).
+
+It builds on top of the existing interfaces for readable and writable streams
+and follows the exact same method and event semantics.
+If you're new to this concept, you should look into the
+`ReadableStreamInterface` and `WritableStreamInterface` first.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to the same events defined
+on the `ReadbleStreamInterface` and `WritableStreamInterface`.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+## Creating streams
+
+ReactPHP uses the concept of "streams" throughout its ecosystem, so that
+many higher-level consumers of this package only deal with
+[stream usage](#stream-usage).
+This implies that stream instances are most often created within some
+higher-level components and many consumers never actually have to deal with
+creating a stream instance.
+
+* Use [react/socket](https://github.com/reactphp/socket)
+ if you want to accept incoming or establish outgoing plaintext TCP/IP or
+ secure TLS socket connection streams.
+* Use [react/http](https://github.com/reactphp/http)
+ if you want to receive an incoming HTTP request body streams.
+* Use [react/child-process](https://github.com/reactphp/child-process)
+ if you want to communicate with child processes via process pipes such as
+ STDIN, STDOUT, STDERR etc.
+* Use experimental [react/filesystem](https://github.com/reactphp/filesystem)
+ if you want to read from / write to the filesystem.
+* See also the last chapter for [more real-world applications](#more).
+
+However, if you are writing a lower-level component or want to create a stream
+instance from a stream resource, then the following chapter is for you.
+
+> Note that the following examples use `fopen()` and `stream_socket_client()`
+ for illustration purposes only.
+ These functions SHOULD NOT be used in a truly async program because each call
+ may take several seconds to complete and would block the EventLoop otherwise.
+ Additionally, the `fopen()` call will return a file handle on some platforms
+ which may or may not be supported by all EventLoop implementations.
+ As an alternative, you may want to use higher-level libraries listed above.
+
+### ReadableResourceStream
+
+The `ReadableResourceStream` is a concrete implementation of the
+[`ReadableStreamInterface`](#readablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-only resource like a file stream opened in
+readable mode or a stream such as `STDIN`:
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop);
+$stream->on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened in reading mode (e.g. `fopen()` mode `r`).
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new ReadableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDIN etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new ReadableResourceStream(STDIN, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop, 8192);
+```
+
+> PHP bug warning: If the PHP process has explicitly been started without a
+ `STDIN` stream, then trying to read from `STDIN` may return data from
+ another stream resource. This does not happen if you start this with an empty
+ stream like `php test.php < /dev/null` instead of `php test.php <&-`.
+ See [#81](https://github.com/reactphp/stream/issues/81) for more details.
+
+### WritableResourceStream
+
+The `WritableResourceStream` is a concrete implementation of the
+[`WritableStreamInterface`](#writablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a write-only resource like a file stream opened in
+writable mode or a stream such as `STDOUT` or `STDERR`:
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new WritableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new WritableResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls
+this maximum buffer size in bytes.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, 8192);
+```
+
+This class takes an optional `int|null $writeChunkSize` parameter that controls
+this maximum buffer size in bytes to write at once to the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be written
+at once to the underlying stream resource. Note that the actual number
+of bytes written may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "write everything available" to the
+underlying stream resource.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+```
+
+See also [`write()`](#write) for more details.
+
+### DuplexResourceStream
+
+The `DuplexResourceStream` is a concrete implementation of the
+[`DuplexStreamInterface`](#duplexstreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-and-write resource like a file stream opened
+in read and write mode mode or a stream such as a TCP/IP connection:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`DuplexStreamInterface`](#duplexstreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for reading *and* writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new DuplexResourceStream(false, $loop);
+```
+
+See also the [`ReadableResourceStream`](#readableresourcestream) for read-only
+and the [`WritableResourceStream`](#writableresourcestream) for write-only
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new DuplexResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop, 8192);
+```
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes another optional `WritableStreamInterface|null $buffer` parameter
+that controls this write behavior of this stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+If you want to change the write buffer soft limit, you can pass an instance of
+[`WritableResourceStream`](#writableresourcestream) like this:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$buffer = new WritableResourceStream($conn, $loop, 8192);
+$stream = new DuplexResourceStream($conn, $loop, null, $buffer);
+```
+
+See also [`WritableResourceStream`](#writableresourcestream) for more details.
+
+### ThroughStream
+
+The `ThroughStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data
+you write to it through to its readable end.
+
+```php
+$through = new ThroughStream();
+$through->on('data', $this->expectCallableOnceWith('hello'));
+
+$through->write('hello');
+```
+
+Similarly, the [`end()` method](#end) will end the stream and emit an
+[`end` event](#end-event) and then [`close()`](#close-1) the stream.
+The [`close()` method](#close-1) will close the stream and emit a
+[`close` event](#close-event).
+Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+
+```php
+$through = new ThroughStream();
+$source->pipe($through)->pipe($dest);
+```
+
+Optionally, its constructor accepts any callable function which will then be
+used to *filter* any data written to it. This function receives a single data
+argument as passed to the writable side and must return the data as it will be
+passed to its readable end:
+
+```php
+$through = new ThroughStream('strtoupper');
+$source->pipe($through)->pipe($dest);
+```
+
+Note that this class makes no assumptions about any data types. This can be
+used to convert data, for example for transforming any structured data into
+a newline-delimited JSON (NDJSON) stream like this:
+
+```php
+$through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+});
+$through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+$through->write(array(2, true));
+```
+
+The callback function is allowed to throw an `Exception`. In this case,
+the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+
+```php
+$through = new ThroughStream(function ($data) {
+ if (!is_string($data)) {
+ throw new \UnexpectedValueException('Only strings allowed');
+ }
+ return $data;
+});
+$through->on('error', $this->expectCallableOnce()));
+$through->on('close', $this->expectCallableOnce()));
+$through->on('data', $this->expectCallableNever()));
+
+$through->write(2);
+```
+
+### CompositeStream
+
+The `CompositeStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and can be used to create a
+single duplex stream from two individual streams implementing
+[`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) respectively.
+
+This is useful for some APIs which may require a single
+[`DuplexStreamInterface`](#duplexstreaminterface) or simply because it's often
+more convenient to work with a single stream instance like this:
+
+```php
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdout = new WritableResourceStream(STDOUT, $loop);
+
+$stdio = new CompositeStream($stdin, $stdout);
+
+$stdio->on('data', function ($chunk) use ($stdio) {
+ $stdio->write('You said: ' . $chunk);
+});
+```
+
+This is a well-behaving stream which forwards all stream events from the
+underlying streams and forwards all streams calls to the underlying streams.
+
+If you `write()` to the duplex stream, it will simply `write()` to the
+writable side and return its status.
+
+If you `end()` the duplex stream, it will `end()` the writable side and will
+`pause()` the readable side.
+
+If you `close()` the duplex stream, both input streams will be closed.
+If either of the two input streams emits a `close` event, the duplex stream
+will also close.
+If either of the two input streams is already closed while constructing the
+duplex stream, it will `close()` the other side and return a closed stream.
+
+## Usage
+
+The following example can be used to pipe the contents of a source file into
+a destination file without having to ever read the whole file into memory:
+
+```php
+$loop = new React\EventLoop\StreamSelectLoop;
+
+$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r'), $loop);
+$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w'), $loop);
+
+$source->pipe($dest);
+
+$loop->run();
+```
+
+> Note that this example uses `fopen()` for illustration purposes only.
+ This should not be used in a truly async program because the filesystem is
+ inherently blocking and each call could potentially take several seconds.
+ See also [creating streams](#creating-streams) for more sophisticated
+ examples.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/stream:^1.0
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project due to its vast
+performance improvements.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## More
+
+* See [creating streams](#creating-streams) for more information on how streams
+ are created in real-world applications.
+* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
+ [dependents on Packagist](https://packagist.org/packages/react/stream/dependents)
+ for a list of packages that use streams in real-world applications.
diff --git a/src/mpg25-instagram-api/vendor/react/stream/composer.json b/src/mpg25-instagram-api/vendor/react/stream/composer.json
new file mode 100755
index 0000000..f6faa66
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "react/stream",
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35",
+ "clue/stream-filter": "~1.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Stream\\": "tests"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/examples/01-http.php b/src/mpg25-instagram-api/vendor/react/stream/examples/01-http.php
new file mode 100755
index 0000000..3687f7c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/examples/01-http.php
@@ -0,0 +1,40 @@
+on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/stream/examples/02-https.php b/src/mpg25-instagram-api/vendor/react/stream/examples/02-https.php
new file mode 100755
index 0000000..163f7c8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/examples/02-https.php
@@ -0,0 +1,40 @@
+on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/stream/examples/11-cat.php b/src/mpg25-instagram-api/vendor/react/stream/examples/11-cat.php
new file mode 100755
index 0000000..90fadc0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/examples/11-cat.php
@@ -0,0 +1,28 @@
+pipe($stdout);
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/stream/examples/91-benchmark-throughput.php b/src/mpg25-instagram-api/vendor/react/stream/examples/91-benchmark-throughput.php
new file mode 100755
index 0000000..ecf695c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/examples/91-benchmark-throughput.php
@@ -0,0 +1,62 @@
+write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL);
+}
+$info->write('piping from ' . $if . ' to ' . $of . ' (for max ' . $t . ' second(s)) ...'. PHP_EOL);
+
+// setup input and output streams and pipe inbetween
+$fh = fopen($if, 'r');
+$in = new React\Stream\ReadableResourceStream($fh, $loop);
+$out = new React\Stream\WritableResourceStream(fopen($of, 'w'), $loop);
+$in->pipe($out);
+
+// stop input stream in $t seconds
+$start = microtime(true);
+$timeout = $loop->addTimer($t, function () use ($in, &$bytes) {
+ $in->close();
+});
+
+// print stream position once stream closes
+$in->on('close', function () use ($fh, $start, $loop, $timeout, $info) {
+ $t = microtime(true) - $start;
+ $loop->cancelTimer($timeout);
+
+ $bytes = ftell($fh);
+
+ $info->write('read ' . $bytes . ' byte(s) in ' . round($t, 3) . ' second(s) => ' . round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL);
+ $info->write('peak memory usage of ' . round(memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL);
+});
+
+$loop->run();
diff --git a/src/mpg25-instagram-api/vendor/react/stream/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/react/stream/phpunit.xml.dist
new file mode 100755
index 0000000..13d3fab
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/react/stream/src/CompositeStream.php b/src/mpg25-instagram-api/vendor/react/stream/src/CompositeStream.php
new file mode 100755
index 0000000..153f2a3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/src/CompositeStream.php
@@ -0,0 +1,82 @@
+readable = $readable;
+ $this->writable = $writable;
+
+ if (!$readable->isReadable() || !$writable->isWritable()) {
+ return $this->close();
+ }
+
+ Util::forwardEvents($this->readable, $this, array('data', 'end', 'error'));
+ Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe'));
+
+ $this->readable->on('close', array($this, 'close'));
+ $this->writable->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->readable->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->readable->pause();
+ }
+
+ public function resume()
+ {
+ if (!$this->writable->isWritable()) {
+ return;
+ }
+
+ $this->readable->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isWritable()
+ {
+ return $this->writable->isWritable();
+ }
+
+ public function write($data)
+ {
+ return $this->writable->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->readable->pause();
+ $this->writable->end($data);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->readable->close();
+ $this->writable->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/src/DuplexResourceStream.php b/src/mpg25-instagram-api/vendor/react/stream/src/DuplexResourceStream.php
new file mode 100755
index 0000000..5f038c6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/src/DuplexResourceStream.php
@@ -0,0 +1,224 @@
+isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ if ($buffer === null) {
+ $buffer = new WritableResourceStream($stream, $loop);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+ $this->buffer = $buffer;
+
+ $that = $this;
+
+ $this->buffer->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+
+ $this->buffer->on('close', array($this, 'close'));
+
+ $this->buffer->on('drain', function () use ($that) {
+ $that->emit('drain');
+ });
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && $this->readable) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ return $this->buffer->write($data);
+ }
+
+ public function close()
+ {
+ if (!$this->writable && !$this->closing) {
+ return;
+ }
+
+ $this->closing = false;
+
+ $this->readable = false;
+ $this->writable = false;
+
+ $this->emit('close');
+ $this->pause();
+ $this->buffer->close();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ $this->closing = true;
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->pause();
+
+ $this->buffer->end($data);
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ /** @internal */
+ public function handleData($stream)
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/src/DuplexStreamInterface.php b/src/mpg25-instagram-api/vendor/react/stream/src/DuplexStreamInterface.php
new file mode 100755
index 0000000..631ce31
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/src/DuplexStreamInterface.php
@@ -0,0 +1,39 @@
+ Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see ReadableStreamInterface
+ * @see WritableStreamInterface
+ */
+interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/src/ReadableResourceStream.php b/src/mpg25-instagram-api/vendor/react/stream/src/ReadableResourceStream.php
new file mode 100755
index 0000000..461f6e4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/src/ReadableResourceStream.php
@@ -0,0 +1,177 @@
+isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && !$this->closed) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+
+ $this->emit('close');
+ $this->pause();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleData()
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($this->stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/src/ReadableStreamInterface.php b/src/mpg25-instagram-api/vendor/react/stream/src/ReadableStreamInterface.php
new file mode 100755
index 0000000..2b4c3d0
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/src/ReadableStreamInterface.php
@@ -0,0 +1,362 @@
+on('data', function ($data) {
+ * echo $data;
+ * });
+ * ```
+ *
+ * This event MAY be emitted any number of times, which may be zero times if
+ * this stream does not send any data at all.
+ * It SHOULD not be emitted after an `end` or `close` event.
+ *
+ * The given `$data` argument may be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit the raw (binary) payload data that is received over the wire as
+ * chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * end event:
+ * The `end` event will be emitted once the source stream has successfully
+ * reached the end of the stream (EOF).
+ *
+ * ```php
+ * $stream->on('end', function () {
+ * echo 'END';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * a successful end was detected.
+ * It SHOULD NOT be emitted after a previous `end` or `close` event.
+ * It MUST NOT be emitted if the stream closes due to a non-successful
+ * end, such as after a previous `error` event.
+ *
+ * After the stream is ended, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * This event will only be emitted if the *end* was reached successfully,
+ * not if the stream was interrupted by an unrecoverable error or explicitly
+ * closed. Not all streams know this concept of a "successful end".
+ * Many use-cases involve detecting when the stream closes (terminates)
+ * instead, in this case you should use the `close` event.
+ * After the stream emits an `end` event, it SHOULD usually be followed by a
+ * `close` event.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit this event if either the remote side closes the connection or
+ * a file handle was successfully read until reaching its end (EOF).
+ *
+ * Note that this event should not be confused with the `end()` method.
+ * This event defines a successful end *reading* from a source stream, while
+ * the `end()` method defines *writing* a successful end to a destination
+ * stream.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to read from this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error or after an unexpected `data` or premature
+ * `end` event.
+ * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-readable mode, see
+ * also `close()` and `isReadable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and do not make assumption about data
+ * boundaries (such as unexpected `data` or premature `end` events).
+ * In other words, many lower-level protocols (such as TCP/IP) may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * Unlike the `end` event, this event SHOULD be emitted whenever the stream
+ * closes, irrespective of whether this happens implicitly due to an
+ * unrecoverable error or explicitly when either side closes the stream.
+ * If you only want to detect a *successful* end, you should use the `end`
+ * event instead.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after reading a *successful* `end`
+ * event or after a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ */
+interface ReadableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a readable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts incoming
+ * data events or if it is ended or closed already.
+ * Once the stream is non-readable, no further `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * A successfully opened stream always MUST start in readable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-readable mode.
+ * This can happen any time, explicitly through `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-readable mode, it MUST NOT transition
+ * back to readable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `isWritable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Pauses reading incoming data events.
+ *
+ * Removes the data source file descriptor from the event loop. This
+ * allows you to throttle incoming data.
+ *
+ * Unless otherwise noted, a successfully opened stream SHOULD NOT start
+ * in paused state.
+ *
+ * Once the stream is paused, no futher `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $stream->on('data', assertShouldNeverCalled());
+ * $stream->on('end', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * stream MAY continue emitting `data` events.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes reading incoming data events.
+ *
+ * Re-attach the data source after a previous `pause()`.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Pipes all the data from this readable source into the given writable destination.
+ *
+ * Automatically sends all incoming data to the destination.
+ * Automatically throttles the source based on what the destination can handle.
+ *
+ * ```php
+ * $source->pipe($dest);
+ * ```
+ *
+ * Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+ * into itself in order to write back all the data that is received.
+ * This may be a useful feature for a TCP/IP echo service:
+ *
+ * ```php
+ * $connection->pipe($connection);
+ * ```
+ *
+ * This method returns the destination stream as-is, which can be used to
+ * set up chains of piped streams:
+ *
+ * ```php
+ * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+ * ```
+ *
+ * By default, this will call `end()` on the destination stream once the
+ * source stream emits an `end` event. This can be disabled like this:
+ *
+ * ```php
+ * $source->pipe($dest, array('end' => false));
+ * ```
+ *
+ * Note that this only applies to the `end` event.
+ * If an `error` or explicit `close` event happens on the source stream,
+ * you'll have to manually close the destination stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $source->on('close', function () use ($dest) {
+ * $dest->end('BYE!');
+ * });
+ * ```
+ *
+ * If the source stream is not readable (closed state), then this is a NO-OP.
+ *
+ * ```php
+ * $source->close();
+ * $source->pipe($dest); // NO-OP
+ * ```
+ *
+ * If the destinantion stream is not writable (closed state), then this will simply
+ * throttle (pause) the source stream:
+ *
+ * ```php
+ * $dest->close();
+ * $source->pipe($dest); // calls $source->pause()
+ * ```
+ *
+ * Similarly, if the destination stream is closed while the pipe is still
+ * active, it will also throttle (pause) the source stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $dest->close(); // calls $source->pause()
+ * ```
+ *
+ * Once the pipe is set up successfully, the destination stream MUST emit
+ * a `pipe` event with this source stream an event argument.
+ *
+ * @param WritableStreamInterface $dest
+ * @param array $options
+ * @return WritableStreamInterface $dest stream as-is
+ */
+ public function pipe(WritableStreamInterface $dest, array $options = array());
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to (forcefully) close the stream.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-readable
+ * mode, see also `isReadable()`.
+ * This means that no further `data` or `end` events SHOULD be emitted.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this method should not be confused with the `end()` method.
+ *
+ * @return void
+ * @see WritableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/src/ThroughStream.php b/src/mpg25-instagram-api/vendor/react/stream/src/ThroughStream.php
new file mode 100755
index 0000000..6f73fb8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/src/ThroughStream.php
@@ -0,0 +1,190 @@
+on('data', $this->expectCallableOnceWith('hello'));
+ *
+ * $through->write('hello');
+ * ```
+ *
+ * Similarly, the [`end()` method](#end) will end the stream and emit an
+ * [`end` event](#end-event) and then [`close()`](#close-1) the stream.
+ * The [`close()` method](#close-1) will close the stream and emit a
+ * [`close` event](#close-event).
+ * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+ *
+ * ```php
+ * $through = new ThroughStream();
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Optionally, its constructor accepts any callable function which will then be
+ * used to *filter* any data written to it. This function receives a single data
+ * argument as passed to the writable side and must return the data as it will be
+ * passed to its readable end:
+ *
+ * ```php
+ * $through = new ThroughStream('strtoupper');
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Note that this class makes no assumptions about any data types. This can be
+ * used to convert data, for example for transforming any structured data into
+ * a newline-delimited JSON (NDJSON) stream like this:
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * return json_encode($data) . PHP_EOL;
+ * });
+ * $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+ *
+ * $through->write(array(2, true));
+ * ```
+ *
+ * The callback function is allowed to throw an `Exception`. In this case,
+ * the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * if (!is_string($data)) {
+ * throw new \UnexpectedValueException('Only strings allowed');
+ * }
+ * return $data;
+ * });
+ * $through->on('error', $this->expectCallableOnce()));
+ * $through->on('close', $this->expectCallableOnce()));
+ * $through->on('data', $this->expectCallableNever()));
+ *
+ * $through->write(2);
+ * ```
+ *
+ * @see WritableStreamInterface::write()
+ * @see WritableStreamInterface::end()
+ * @see DuplexStreamInterface::close()
+ * @see WritableStreamInterface::pipe()
+ */
+final class ThroughStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $readable = true;
+ private $writable = true;
+ private $closed = false;
+ private $paused = false;
+ private $drain = false;
+ private $callback;
+
+ public function __construct($callback = null)
+ {
+ if ($callback !== null && !\is_callable($callback)) {
+ throw new InvalidArgumentException('Invalid transformation callback given');
+ }
+
+ $this->callback = $callback;
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ if ($this->drain) {
+ $this->drain = false;
+ $this->emit('drain');
+ }
+ $this->paused = false;
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ if ($this->callback !== null) {
+ try {
+ $data = \call_user_func($this->callback, $data);
+ } catch (\Exception $e) {
+ $this->emit('error', array($e));
+ $this->close();
+
+ return false;
+ }
+ }
+
+ $this->emit('data', array($data));
+
+ if ($this->paused) {
+ $this->drain = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ if (null !== $data) {
+ $this->write($data);
+
+ // return if write() already caused the stream to close
+ if (!$this->writable) {
+ return;
+ }
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->paused = true;
+ $this->drain = false;
+
+ $this->emit('end');
+ $this->close();
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->closed = true;
+ $this->paused = true;
+ $this->drain = false;
+ $this->callback = null;
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/src/Util.php b/src/mpg25-instagram-api/vendor/react/stream/src/Util.php
new file mode 100755
index 0000000..056b037
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/src/Util.php
@@ -0,0 +1,75 @@
+ NO-OP
+ if (!$source->isReadable()) {
+ return $dest;
+ }
+
+ // destination not writable => just pause() source
+ if (!$dest->isWritable()) {
+ $source->pause();
+
+ return $dest;
+ }
+
+ $dest->emit('pipe', array($source));
+
+ // forward all source data events as $dest->write()
+ $source->on('data', $dataer = function ($data) use ($source, $dest) {
+ $feedMore = $dest->write($data);
+
+ if (false === $feedMore) {
+ $source->pause();
+ }
+ });
+ $dest->on('close', function () use ($source, $dataer) {
+ $source->removeListener('data', $dataer);
+ $source->pause();
+ });
+
+ // forward destination drain as $source->resume()
+ $dest->on('drain', $drainer = function () use ($source) {
+ $source->resume();
+ });
+ $source->on('close', function () use ($dest, $drainer) {
+ $dest->removeListener('drain', $drainer);
+ });
+
+ // forward end event from source as $dest->end()
+ $end = isset($options['end']) ? $options['end'] : true;
+ if ($end) {
+ $source->on('end', $ender = function () use ($dest) {
+ $dest->end();
+ });
+ $dest->on('close', function () use ($source, $ender) {
+ $source->removeListener('end', $ender);
+ });
+ }
+
+ return $dest;
+ }
+
+ public static function forwardEvents($source, $target, array $events)
+ {
+ foreach ($events as $event) {
+ $source->on($event, function () use ($event, $target) {
+ $target->emit($event, \func_get_args());
+ });
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/src/WritableResourceStream.php b/src/mpg25-instagram-api/vendor/react/stream/src/WritableResourceStream.php
new file mode 100755
index 0000000..57c09b2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/src/WritableResourceStream.php
@@ -0,0 +1,171 @@
+stream = $stream;
+ $this->loop = $loop;
+ $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit;
+ $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ $this->data .= $data;
+
+ if (!$this->listening && $this->data !== '') {
+ $this->listening = true;
+
+ $this->loop->addWriteStream($this->stream, array($this, 'handleWrite'));
+ }
+
+ return !isset($this->data[$this->softLimit - 1]);
+ }
+
+ public function end($data = null)
+ {
+ if (null !== $data) {
+ $this->write($data);
+ }
+
+ $this->writable = false;
+
+ // close immediately if buffer is already empty
+ // otherwise wait for buffer to flush first
+ if ($this->data === '') {
+ $this->close();
+ }
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ if ($this->listening) {
+ $this->listening = false;
+ $this->loop->removeWriteStream($this->stream);
+ }
+
+ $this->closed = true;
+ $this->writable = false;
+ $this->data = '';
+
+ $this->emit('close');
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleWrite()
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = array(
+ 'message' => $errstr,
+ 'number' => $errno,
+ 'file' => $errfile,
+ 'line' => $errline
+ );
+ });
+
+ if ($this->writeChunkSize === -1) {
+ $sent = \fwrite($this->stream, $this->data);
+ } else {
+ $sent = \fwrite($this->stream, $this->data, $this->writeChunkSize);
+ }
+
+ \restore_error_handler();
+
+ // Only report errors if *nothing* could be sent.
+ // Any hard (permanent) error will fail to send any data at all.
+ // Sending excessive amounts of data will only flush *some* data and then
+ // report a temporary error (EAGAIN) which we do not raise here in order
+ // to keep the stream open for further tries to write.
+ // Should this turn out to be a permanent error later, it will eventually
+ // send *nothing* and we can detect this.
+ if ($sent === 0 || $sent === false) {
+ if ($error !== null) {
+ $error = new \ErrorException(
+ $error['message'],
+ 0,
+ $error['number'],
+ $error['file'],
+ $error['line']
+ );
+ }
+
+ $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . ($error !== null ? $error->getMessage() : 'Unknown error'), 0, $error)));
+ $this->close();
+
+ return;
+ }
+
+ $exceeded = isset($this->data[$this->softLimit - 1]);
+ $this->data = (string) \substr($this->data, $sent);
+
+ // buffer has been above limit and is now below limit
+ if ($exceeded && !isset($this->data[$this->softLimit - 1])) {
+ $this->emit('drain');
+ }
+
+ // buffer is now completely empty => stop trying to write
+ if ($this->data === '') {
+ // stop waiting for resource to be writable
+ if ($this->listening) {
+ $this->loop->removeWriteStream($this->stream);
+ $this->listening = false;
+ }
+
+ // buffer is end()ing and now completely empty => close buffer
+ if (!$this->writable) {
+ $this->close();
+ }
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/src/WritableStreamInterface.php b/src/mpg25-instagram-api/vendor/react/stream/src/WritableStreamInterface.php
new file mode 100755
index 0000000..3bc932e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/src/WritableStreamInterface.php
@@ -0,0 +1,347 @@
+on('drain', function () use ($stream) {
+ * echo 'Stream is now ready to accept more data';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once every time the buffer became full
+ * previously and is now ready to accept more data.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if the buffer never became full in the first place.
+ * This event SHOULD NOT be emitted if the buffer has not become full
+ * previously.
+ *
+ * This event is mostly used internally, see also `write()` for more details.
+ *
+ * pipe event:
+ * The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+ * into this stream.
+ * The event receives a single `ReadableStreamInterface` argument for the
+ * source stream.
+ *
+ * ```php
+ * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ * echo 'Now receiving piped data';
+ *
+ * // explicitly close target if source emits an error
+ * $source->on('error', function () use ($stream) {
+ * $stream->close();
+ * });
+ * });
+ *
+ * $source->pipe($stream);
+ * ```
+ *
+ * This event MUST be emitted once for each readable stream that is
+ * successfully piped into this destination stream.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if no stream is ever piped into this stream.
+ * This event MUST NOT be emitted if either the source is not readable
+ * (closed already) or this destination is not writable (closed already).
+ *
+ * This event is mostly used internally, see also `pipe()` for more details.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to write to this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error.
+ * It SHOULD NOT be emitted after a previous `error` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-writable mode, see
+ * also `close()` and `isWritable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-writable mode,
+ * see also `isWritable()`.
+ *
+ * This event SHOULD be emitted whenever the stream closes, irrespective of
+ * whether this happens implicitly due to an unrecoverable error or
+ * explicitly when either side closes the stream.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after flushing the buffer from
+ * the `end()` method, after receiving a *successful* `end` event or after
+ * a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ * @see DuplexStreamInterface
+ */
+interface WritableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a writable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts writing
+ * any data or if it is ended or closed already.
+ * Writing any data to a non-writable stream is a NO-OP:
+ *
+ * ```php
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('end'); // NO-OP
+ * $stream->end('end'); // NO-OP
+ * ```
+ *
+ * A successfully opened stream always MUST start in writable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-writable mode.
+ * This can happen any time, explicitly through `end()` or `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-writable mode, it MUST NOT transition
+ * back to writable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `isReadable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Write some data into the stream.
+ *
+ * A successful write MUST be confirmed with a boolean `true`, which means
+ * that either the data was written (flushed) immediately or is buffered and
+ * scheduled for a future write. Note that this interface gives you no
+ * control over explicitly flushing the buffered data, as finding the
+ * appropriate time for this is beyond the scope of this interface and left
+ * up to the implementation of this interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+ * an `error` event and MAY `close()` the stream if it can not recover from
+ * this error.
+ *
+ * If the internal buffer is full after adding `$data`, then `write()`
+ * SHOULD return `false`, indicating that the caller should stop sending
+ * data until the buffer drains.
+ * The stream SHOULD send a `drain` event once the buffer is ready to accept
+ * more data.
+ *
+ * Similarly, if the the stream is not writable (already in a closed state)
+ * it MUST NOT process the given `$data` and SHOULD return `false`,
+ * indicating that the caller should stop sending data.
+ *
+ * The given `$data` argument MAY be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will only accept the raw (binary) payload data that is transferred over
+ * the wire as chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * @param mixed|string $data
+ * @return bool
+ */
+ public function write($data);
+
+ /**
+ * Successfully ends the stream (after optionally sending some final data).
+ *
+ * This method can be used to successfully end the stream, i.e. close
+ * the stream after sending out all data that is currently buffered.
+ *
+ * ```php
+ * $stream->write('hello');
+ * $stream->write('world');
+ * $stream->end();
+ * ```
+ *
+ * If there's no data currently buffered and nothing to be flushed, then
+ * this method MAY `close()` the stream immediately.
+ *
+ * If there's still data in the buffer that needs to be flushed first, then
+ * this method SHOULD try to write out this data and only then `close()`
+ * the stream.
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ *
+ * Note that this interface gives you no control over explicitly flushing
+ * the buffered data, as finding the appropriate time for this is beyond the
+ * scope of this interface and left up to the implementation of this
+ * interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * You can optionally pass some final data that is written to the stream
+ * before ending the stream. If a non-`null` value is given as `$data`, then
+ * this method will behave just like calling `write($data)` before ending
+ * with no data.
+ *
+ * ```php
+ * // shorter version
+ * $stream->end('bye');
+ *
+ * // same as longer version
+ * $stream->write('bye');
+ * $stream->end();
+ * ```
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->end();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+ * also end its readable side, unless the stream supports half-open mode.
+ * In other words, after calling this method, these streams SHOULD switch
+ * into non-writable AND non-readable mode, see also `isReadable()`.
+ * This implies that in this case, the stream SHOULD NOT emit any `data`
+ * or `end` events anymore.
+ * Streams MAY choose to use the `pause()` method logic for this, but
+ * special care may have to be taken to ensure a following call to the
+ * `resume()` method SHOULD NOT continue emitting readable events.
+ *
+ * Note that this method should not be confused with the `close()` method.
+ *
+ * @param mixed|string|null $data
+ * @return void
+ */
+ public function end($data = null);
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to forcefully close the stream, i.e. close
+ * the stream without waiting for any buffered data to be flushed.
+ * If there's still data in the buffer, this data SHOULD be discarded.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * Note that this method should not be confused with the `end()` method.
+ * Unlike the `end()` method, this method does not take care of any existing
+ * buffers and simply discards any buffer contents.
+ * Likewise, this method may also be called after calling `end()` on a
+ * stream in order to stop waiting for the stream to flush its final data.
+ *
+ * ```php
+ * $stream->end();
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->close();
+ * });
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ *
+ * @return void
+ * @see ReadableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/CallableStub.php b/src/mpg25-instagram-api/vendor/react/stream/tests/CallableStub.php
new file mode 100755
index 0000000..31cc834
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/CallableStub.php
@@ -0,0 +1,10 @@
+getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(false);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldCloseWritableIfNotReadable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardWritableCallsToWritableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->write('foo');
+ $composite->isWritable();
+ }
+
+ /** @test */
+ public function itShouldForwardReadableCallsToReadableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->exactly(2))
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+ $readable
+ ->expects($this->once())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->isReadable();
+ $composite->pause();
+ $composite->resume();
+ }
+
+ /** @test */
+ public function itShouldNotForwardResumeIfStreamIsNotWritable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->never())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturnOnConsecutiveCalls(true, false);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->resume();
+ }
+
+ /** @test */
+ public function endShouldDelegateToWritableWithData()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->end('foo');
+ }
+
+ /** @test */
+ public function closeShouldCloseBothStreams()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseOnlyOnce()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $readable->close();
+ $writable->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseAndRemoveAllListeners()
+ {
+ $in = new ThroughStream();
+
+ $composite = new CompositeStream($in, $in);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($composite->isReadable());
+ $this->assertTrue($composite->isWritable());
+ $this->assertCount(1, $composite->listeners('close'));
+
+ $composite->close();
+
+ $this->assertFalse($composite->isReadable());
+ $this->assertFalse($composite->isWritable());
+ $this->assertCount(0, $composite->listeners('close'));
+ }
+
+ /** @test */
+ public function itShouldReceiveForwardedEvents()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('data', $this->expectCallableOnce());
+ $composite->on('drain', $this->expectCallableOnce());
+
+ $readable->emit('data', array('foo'));
+ $writable->emit('drain');
+ }
+
+ /** @test */
+ public function itShouldHandlePipingCorrectly()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $input = new ThroughStream();
+ $input->pipe($composite);
+ $input->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function itShouldForwardPipeCallsToReadableStream()
+ {
+ $readable = new ThroughStream();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite->pipe($output);
+ $readable->emit('data', array('foo'));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php b/src/mpg25-instagram-api/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
new file mode 100755
index 0000000..7135e15
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
@@ -0,0 +1,390 @@
+markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $bufferSize = 4096;
+ $streamA = new DuplexResourceStream($sockA, $loop, $bufferSize);
+ $streamB = new DuplexResourceStream($sockB, $loop, $bufferSize);
+
+ $testString = str_repeat("*", $bufferSize + 1);
+
+ $buffer = "";
+ $streamB->on('data', function ($data) use (&$buffer) {
+ $buffer .= $data;
+ });
+
+ $streamA->write($testString);
+
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($testString, $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testWriteLargeChunk($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // limit seems to be 192 KiB
+ $size = 256 * 1024;
+
+ // sending side sends and expects clean close with no errors
+ $streamA->end(str_repeat('*', $size));
+ $streamA->on('close', $this->expectCallableOnce());
+ $streamA->on('error', $this->expectCallableNever());
+
+ // receiving side counts bytes and expects clean close with no errors
+ $received = 0;
+ $streamB->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+ $streamB->on('close', $this->expectCallableOnce());
+ $streamB->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($size, $received);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->end();
+
+ // streamB should not emit any data
+ $streamB->on('data', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($client, $loop);
+ $streamB = new DuplexResourceStream($peer, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($peer, $loop);
+ $streamB = new DuplexResourceStream($client, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo test', 'r'), $loop);
+ $stream->on('data', $this->expectCallableOnceWith("test\n"));
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals("a\n" . "b\n" . "c\n", $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsLongChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop);
+
+ $bytes = 0;
+ $stream->on('data', function ($chunk) use (&$bytes) {
+ $bytes += strlen($chunk);
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals(12345 * 1234, $bytes);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('true', 'r'), $loop);
+ $stream->on('data', $this->expectCallableNever());
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ * @dataProvider loopProvider
+ */
+ public function testEmptyReadShouldntFcloseStream($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $stream = stream_socket_accept($server);
+
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return '';
+ }, STREAM_FILTER_READ);
+
+ $loop = $loopFactory();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('end', $this->expectCallableNever());
+
+ fwrite($client, "foobar\n");
+
+ $conn->handleData($stream);
+
+ fclose($stream);
+ fclose($client);
+ fclose($server);
+ }
+
+ private function loopTick(LoopInterface $loop)
+ {
+ $loop->addTimer(0, function () use ($loop) {
+ $loop->stop();
+ });
+ $loop->run();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/DuplexResourceStreamTest.php b/src/mpg25-instagram-api/vendor/react/stream/tests/DuplexResourceStreamTest.php
new file mode 100755
index 0000000..3212ae8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/DuplexResourceStreamTest.php
@@ -0,0 +1,495 @@
+createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new DuplexResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream('breakme', $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException RunTimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorAcceptsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ }
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+ $conn->on('end', $this->expectCallableNever());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testEndShouldEndBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->once())->method('end')->with('foo');
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ $conn->end('foo');
+ }
+
+
+ public function testEndAfterCloseIsNoOp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->never())->method('end');
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->end();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::write
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+
+ rewind($stream);
+ $this->assertSame("foo\n", fgets($stream));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ * @covers React\Stream\DuplexResourceStream::isReadable
+ * @covers React\Stream\DuplexResourceStream::isWritable
+ */
+ public function testEnd()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end();
+
+ $this->assertFalse(is_resource($stream));
+ $this->assertFalse($conn->isReadable());
+ $this->assertFalse($conn->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ */
+ public function testEndRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end('bye');
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ public function testEndedStreamsShouldNotWrite()
+ {
+ $file = tempnam(sys_get_temp_dir(), 'reactphptest_');
+ $stream = fopen($file, 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+ $conn->end();
+
+ $res = $conn->write("bar\n");
+ $stream = fopen($file, 'r');
+
+ $this->assertSame("foo\n", fgets($stream));
+ $this->assertFalse($res);
+
+ unlink($file);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ public function testBufferEventsShouldBubbleUp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+
+ $conn->on('drain', $this->expectCallableOnce());
+ $conn->on('error', $this->expectCallableOnce());
+
+ $buffer->emit('drain');
+ $buffer->emit('error', array(new \RuntimeException('Whoops')));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop
+ ->expects($this->once())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) {
+ call_user_func($listener, $stream);
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/EnforceBlockingWrapper.php b/src/mpg25-instagram-api/vendor/react/stream/tests/EnforceBlockingWrapper.php
new file mode 100755
index 0000000..39c0487
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/EnforceBlockingWrapper.php
@@ -0,0 +1,35 @@
+on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockPlain()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tcp://httpbin.org:80');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadKilobyteSecure()
+ {
+ $size = 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockSecureRequiresSmallerChunkSize()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream(
+ $stream,
+ $loop,
+ null,
+ new WritableResourceStream($stream, $loop, null, 8192)
+ );
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0)
+ {
+ $stream->on('close', function () use ($loop) {
+ $loop->stop();
+ });
+
+ $that = $this;
+ $loop->addTimer($timeout, function () use ($loop, $that) {
+ $loop->stop();
+ $that->fail('Timed out while waiting for stream to close');
+ });
+
+ $loop->run();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/ReadableResourceStreamTest.php b/src/mpg25-instagram-api/vendor/react/stream/tests/ReadableResourceStreamTest.php
new file mode 100755
index 0000000..7566f92
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/ReadableResourceStreamTest.php
@@ -0,0 +1,391 @@
+createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new ReadableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(false, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testCloseTwiceShouldEmitCloseEventOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testEmptyReadShouldntFcloseStream()
+ {
+ list($stream, $_) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('end', $this->expectCallableNever());
+
+ $conn->handleData();
+
+ fclose($stream);
+ fclose($_);
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/Stub/ReadableStreamStub.php b/src/mpg25-instagram-api/vendor/react/stream/tests/Stub/ReadableStreamStub.php
new file mode 100755
index 0000000..6984f24
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/Stub/ReadableStreamStub.php
@@ -0,0 +1,61 @@
+emit('data', array($data));
+ }
+
+ // trigger error event
+ public function error($error)
+ {
+ $this->emit('error', array($error));
+ }
+
+ // trigger end event
+ public function end()
+ {
+ $this->emit('end', array());
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ $this->paused = false;
+ }
+
+ public function close()
+ {
+ $this->readable = false;
+
+ $this->emit('close');
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/TestCase.php b/src/mpg25-instagram-api/vendor/react/stream/tests/TestCase.php
new file mode 100755
index 0000000..c8fc1db
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/TestCase.php
@@ -0,0 +1,54 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $callback = $this->createCallableMock();
+ $callback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $callback;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/ThroughStreamTest.php b/src/mpg25-instagram-api/vendor/react/stream/tests/ThroughStreamTest.php
new file mode 100755
index 0000000..a98badf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/ThroughStreamTest.php
@@ -0,0 +1,267 @@
+write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToIt()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruFunction()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruCallback()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsException()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->write('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $ret = $through->write('foo');
+
+ $this->assertFalse($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->write('foo');
+
+ $through->on('drain', $this->expectCallableOnce());
+ $through->resume();
+ }
+
+ /** @test */
+ public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause()
+ {
+ $through = new ThroughStream();
+ $through->on('drain', $this->expectCallableNever());
+ $through->pause();
+ $through->resume();
+ $ret = $through->write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function pipingStuffIntoItShouldWork()
+ {
+ $readable = new ThroughStream();
+
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+
+ $readable->pipe($through);
+ $readable->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function endShouldEmitEndAndClose()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->end();
+ }
+
+ /** @test */
+ public function endShouldCloseTheStream()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endShouldWriteDataBeforeClosing()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endTwiceShouldOnlyEmitOnce()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnce('first'));
+ $through->end('first');
+ $through->end('ignored');
+ }
+
+ /** @test */
+ public function writeAfterEndShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataWillCloseStreamShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', array($through, 'close'));
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToPausedShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToResumedShouldReturnTrue()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->resume();
+
+ $this->assertTrue($through->write('foo'));
+ }
+
+ /** @test */
+ public function itShouldBeReadableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isReadable());
+ }
+
+ /** @test */
+ public function itShouldBeWritableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isWritable());
+ }
+
+ /** @test */
+ public function closeShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function doubleCloseShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function pipeShouldPipeCorrectly()
+ {
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $through = new ThroughStream();
+ $through->pipe($output);
+ $through->write('foo');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/UtilTest.php b/src/mpg25-instagram-api/vendor/react/stream/tests/UtilTest.php
new file mode 100755
index 0000000..3d113ab
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/UtilTest.php
@@ -0,0 +1,273 @@
+getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $ret = Util::pipe($readable, $writable);
+
+ $this->assertSame($writable, $ret);
+ }
+
+ public function testPipeNonReadableSourceShouldDoNothing()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->never())
+ ->method('isWritable');
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeIntoNonWritableDestinationShouldPauseSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(false);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeClosingDestPausesSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = new ThroughStream();
+
+ Util::pipe($readable, $writable);
+
+ $writable->close();
+ }
+
+ public function testPipeWithEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+
+ $readable->end();
+ }
+
+ public function testPipeWithoutEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable, array('end' => false));
+
+ $readable->end();
+ }
+
+ public function testPipeWithTooSlowWritableShouldPauseReadable()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('some data')
+ ->will($this->returnValue(false));
+
+ $readable->pipe($writable);
+
+ $this->assertFalse($readable->paused);
+ $readable->write('some data');
+ $this->assertTrue($readable->paused);
+ }
+
+ public function testPipeWithTooSlowWritableShouldResumeOnDrain()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $onDrain = null;
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->any())
+ ->method('on')
+ ->will($this->returnCallback(function ($name, $callback) use (&$onDrain) {
+ if ($name === 'drain') {
+ $onDrain = $callback;
+ }
+ }));
+
+ $readable->pipe($writable);
+ $readable->pause();
+
+ $this->assertTrue($readable->paused);
+ $this->assertNotNull($onDrain);
+ $onDrain();
+ $this->assertFalse($readable->paused);
+ }
+
+ public function testPipeWithWritableResourceStream()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $readable->pipe($buffer);
+
+ $readable->write('hello, I am some ');
+ $readable->write('random data');
+
+ $buffer->handleWrite();
+ rewind($stream);
+ $this->assertSame('hello, I am some random data', stream_get_contents($stream));
+ }
+
+ public function testPipeSetsUpListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+
+ Util::pipe($source, $dest);
+
+ $this->assertCount(1, $source->listeners('data'));
+ $this->assertCount(1, $source->listeners('end'));
+ $this->assertCount(1, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingSourceRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $source->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingDestRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $dest->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeDuplexIntoSelfEndsOnEnd()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable->expects($this->any())->method('isReadable')->willReturn(true);
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(true);
+ $duplex = new CompositeStream($readable, $writable);
+
+ Util::pipe($duplex, $duplex);
+
+ $writable->expects($this->once())->method('end');
+
+ $duplex->emit('end');
+ }
+
+ /** @test */
+ public function forwardEventsShouldSetupForwards()
+ {
+ $source = new ThroughStream();
+ $target = new ThroughStream();
+
+ Util::forwardEvents($source, $target, array('data'));
+ $target->on('data', $this->expectCallableOnce());
+ $target->on('foo', $this->expectCallableNever());
+
+ $source->emit('data', array('hello'));
+ $source->emit('foo', array('bar'));
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+
+ private function notEqualTo($value)
+ {
+ return new \PHPUnit_Framework_Constraint_Not($value);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/react/stream/tests/WritableStreamResourceTest.php b/src/mpg25-instagram-api/vendor/react/stream/tests/WritableStreamResourceTest.php
new file mode 100755
index 0000000..05bce9c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/react/stream/tests/WritableStreamResourceTest.php
@@ -0,0 +1,534 @@
+createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'w+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsIfNotAValidStreamResource()
+ {
+ $stream = null;
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStream()
+ {
+ $stream = fopen('php://temp', 'r');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'reANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->write("foobar\n");
+ rewind($stream);
+ $this->assertSame("foobar\n", fread($stream, 1024));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteWithDataDoesAddResourceToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream));
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("foobar\n");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmptyWriteDoesNotAddToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->never())->method('addWriteStream');
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("");
+ $buffer->write(null);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+ $loop->preventWrites = true;
+
+ $buffer = new WritableResourceStream($stream, $loop, 4);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $this->assertTrue($buffer->write("foo"));
+ $loop->preventWrites = false;
+ $this->assertFalse($buffer->write("bar\n"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 3);
+
+ $this->assertFalse($buffer->write("foo"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteDetectsWhenOtherSideIsClosed()
+ {
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($a, $loop, 4);
+ $buffer->on('error', $this->expectCallableOnce());
+
+ fclose($b);
+
+ $buffer->write("foo");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmitsDrainAfterWriteWhichExceedsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteInDrain()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->once('drain', function () use ($buffer) {
+ $buffer->write("bar\n");
+ $buffer->handleWrite();
+ });
+
+ $this->assertFalse($buffer->write("foo\n"));
+ $buffer->handleWrite();
+
+ fseek($stream, 0);
+ $this->assertSame("foo\nbar\n", stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', function () use ($buffer) {
+ $buffer->close();
+ });
+
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ $buffer->handleWrite();
+ $this->assertSame('final words', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ rewind($stream);
+ $this->assertSame('', stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::isWritable
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->close();
+ $this->assertFalse($buffer->isWritable());
+
+ $this->assertEquals(array(), $buffer->listeners('close'));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingAfterWriteRemovesStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer->write('foo');
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->never())->method('removeWriteStream');
+
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testDoubleCloseWillEmitOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->close();
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $buffer->close();
+
+ $buffer->write('foo');
+
+ $buffer->handleWrite();
+ $this->assertSame('', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testErrorWhenStreamResourceIsInvalid()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', function ($message) use (&$error) {
+ $error = $message;
+ });
+
+ // invalidate stream resource
+ fclose($stream);
+
+ $buffer->write('Attempting to write to bad stream');
+
+ $this->assertInstanceOf('Exception', $error);
+
+ // the error messages differ between PHP versions, let's just check substrings
+ $this->assertContains('Unable to write to stream: ', $error->getMessage());
+ $this->assertContains(' not a valid stream resource', $error->getMessage(), '', true);
+ }
+
+ public function testWritingToClosedStream()
+ {
+ if ('Darwin' === PHP_OS) {
+ $this->markTestSkipped('OS X issue with shutting down pair for writing');
+ }
+
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+ $loop = $this->createLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($a, $loop);
+ $buffer->on('error', function($message) use (&$error) {
+ $error = $message;
+ });
+
+ $buffer->write('foo');
+ $buffer->handleWrite();
+ stream_socket_shutdown($b, STREAM_SHUT_RD);
+ stream_socket_shutdown($a, STREAM_SHUT_RD);
+ $buffer->write('bar');
+ $buffer->handleWrite();
+
+ $this->assertInstanceOf('Exception', $error);
+ $this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage());
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop->preventWrites = false;
+ $loop
+ ->expects($this->any())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) use ($loop) {
+ if (!$loop->preventWrites) {
+ call_user_func($listener, $stream);
+ }
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/.gitignore b/src/mpg25-instagram-api/vendor/ringcentral/psr7/.gitignore
new file mode 100755
index 0000000..83ec41e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/.gitignore
@@ -0,0 +1,11 @@
+phpunit.xml
+composer.phar
+composer.lock
+composer-test.lock
+vendor/
+build/artifacts/
+artifacts/
+docs/_build
+docs/*.pyc
+.idea
+.DS_STORE
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/.travis.yml b/src/mpg25-instagram-api/vendor/ringcentral/psr7/.travis.yml
new file mode 100755
index 0000000..08f3721
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/.travis.yml
@@ -0,0 +1,21 @@
+language: php
+
+sudo: false
+
+install:
+ - travis_retry composer install --no-interaction --prefer-source
+
+script: make test
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - php: 5.4
+ - php: 5.5
+ - php: 5.6
+ - php: 7.0
+ - php: hhvm
+ allow_failures:
+ - php: hhvm
+ fast_finish: true
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/CHANGELOG.md b/src/mpg25-instagram-api/vendor/ringcentral/psr7/CHANGELOG.md
new file mode 100755
index 0000000..642dc9a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/CHANGELOG.md
@@ -0,0 +1,28 @@
+# CHANGELOG
+
+## 1.2.0 - 2015-08-15
+
+* Body as `"0"` is now properly added to a response.
+* Now allowing forward seeking in CachingStream.
+* Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+* functions.php is now conditionally required.
+* user-info is no longer dropped when resolving URIs.
+
+## 1.1.0 - 2015-06-24
+
+* URIs can now be relative.
+* `multipart/form-data` headers are now overridden case-insensitively.
+* URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+* A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/Dockerfile b/src/mpg25-instagram-api/vendor/ringcentral/psr7/Dockerfile
new file mode 100755
index 0000000..846e8cf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/Dockerfile
@@ -0,0 +1,5 @@
+FROM greensheep/dockerfiles-php-5.3
+RUN apt-get update -y
+RUN apt-get install -y curl
+RUN curl -sS https://getcomposer.org/installer | php
+RUN mv composer.phar /usr/local/bin/composer
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/LICENSE b/src/mpg25-instagram-api/vendor/ringcentral/psr7/LICENSE
new file mode 100755
index 0000000..581d95f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/Makefile b/src/mpg25-instagram-api/vendor/ringcentral/psr7/Makefile
new file mode 100755
index 0000000..73a5c5b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/Makefile
@@ -0,0 +1,21 @@
+all: clean test
+
+test:
+ vendor/bin/phpunit $(TEST)
+
+coverage:
+ vendor/bin/phpunit --coverage-html=artifacts/coverage $(TEST)
+
+view-coverage:
+ open artifacts/coverage/index.html
+
+clean:
+ rm -rf artifacts/*
+
+.PHONY: docker-login
+docker-login:
+ docker run -t -i -v $(shell pwd):/opt/psr7 ringcentral-psr7 /bin/bash
+
+.PHONY: docker-build
+docker-build:
+ docker build -t ringcentral-psr7 .
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/README.md b/src/mpg25-instagram-api/vendor/ringcentral/psr7/README.md
new file mode 100755
index 0000000..b4a6061
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/README.md
@@ -0,0 +1,587 @@
+# PSR-7 Message Implementation
+
+This repository contains a partial [PSR-7](http://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing. Currently missing
+ServerRequestInterface and UploadedFileInterface; a pull request for these features is welcome.
+
+
+# Stream implementation
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## AppendStream
+
+`RingCentral\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use RingCentral\Psr7;
+
+$a = Psr7\stream_for('abc, ');
+$b = Psr7\stream_for('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\stream_for(' Above all listen to me').
+
+echo $composed(); // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`RingCentral\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use RingCentral\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`RingCentral\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use RingCentral\Psr7;
+
+// Create an empty stream
+$stream = Psr7\stream_for();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$stream->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`RingCentral\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing to
+to create a concrete class for a simple extension point.
+
+```php
+
+use RingCentral\Psr7;
+
+$stream = Psr7\stream_for('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`RingCentral\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
+
+This stream decorator skips the first 10 bytes of the given stream to remove
+the gzip header, converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`RingCentral\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use RingCentral\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`RingCentral\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`RingCentral\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`RingCentral\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`RingCentral\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`RingCentral\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use RingCentral\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ call_user_func($this->callback);
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `RingCentral\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `RingCentral\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use RingCentral\Psr7\StreamWrapper;
+
+$stream = RingCentral\Psr7\stream_for('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Function API
+
+There are various functions available under the `RingCentral\Psr7` namespace.
+
+
+## `function str`
+
+`function str(MessageInterface $message)`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new RingCentral\Psr7\Request('GET', 'http://example.com');
+echo RingCentral\Psr7\str($request);
+```
+
+
+## `function uri_for`
+
+`function uri_for($uri)`
+
+This function accepts a string or `Psr\Http\Message\UriInterface` and returns a
+UriInterface for the given value. If the value is already a `UriInterface`, it
+is returned as-is.
+
+```php
+$uri = RingCentral\Psr7\uri_for('http://example.com');
+assert($uri === RingCentral\Psr7\uri_for($uri));
+```
+
+
+## `function stream_for`
+
+`function stream_for($resource = '', array $options = [])`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+* - metadata: Array of custom metadata.
+* - size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = RingCentral\Psr7\stream_for('foo');
+$stream = RingCentral\Psr7\stream_for(fopen('/path/to/file', 'r'));
+
+$generator function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = RingCentral\Psr7\stream_for($generator(100));
+```
+
+
+## `function parse_header`
+
+`function parse_header($header)`
+
+Parse an array of header values containing ";" separated data into an array of
+associative arrays representing the header key value pair data of the header.
+When a parameter does not contain a value, but just contains a key, this
+function will inject a key with a '' string value.
+
+
+## `function normalize_header`
+
+`function normalize_header($header)`
+
+Converts an array of header values that may contain comma separated headers
+into an array of headers with no comma separated values.
+
+
+## `function modify_request`
+
+`function modify_request(RequestInterface $request, array $changes)`
+
+Clone and modify a request with the given changes. This method is useful for
+reducing the number of clones needed to mutate a message.
+
+The changes can be one of:
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `function rewind_body`
+
+`function rewind_body(MessageInterface $message)`
+
+Attempts to rewind a message body and throws an exception on failure. The body
+of the message will only be rewound if a call to `tell()` returns a value other
+than `0`.
+
+
+## `function try_fopen`
+
+`function try_fopen($filename, $mode)`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an error
+handler that checks for errors and throws an exception instead.
+
+
+## `function copy_to_string`
+
+`function copy_to_string(StreamInterface $stream, $maxLen = -1)`
+
+Copy the contents of a stream into a string until the given number of bytes
+have been read.
+
+
+## `function copy_to_stream`
+
+`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)`
+
+Copy the contents of a stream into another stream until the given number of
+bytes have been read.
+
+
+## `function hash`
+
+`function hash(StreamInterface $stream, $algo, $rawOutput = false)`
+
+Calculate a hash of a Stream. This method reads the entire stream to calculate
+a rolling hash (based on PHP's hash_init functions).
+
+
+## `function readline`
+
+`function readline(StreamInterface $stream, $maxLength = null)`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `function parse_request`
+
+`function parse_request($message)`
+
+Parses a request message string into a request object.
+
+
+## `function parse_server_request`
+
+`function parse_server_request($message, array $serverParams = array())`
+
+Parses a request message string into a server-side request object.
+
+
+## `function parse_response`
+
+`function parse_response($message)`
+
+Parses a response message string into a response object.
+
+
+## `function parse_query`
+
+`function parse_query($str, $urlEncoding = true)`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key value pair
+will become an array. This function does not parse nested PHP style arrays into
+an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
+`['foo[a]' => '1', 'foo[b]' => '2']`).
+
+
+## `function build_query`
+
+`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of parseQuery() to build a query string.
+This function does not modify the provided keys when an array is encountered
+(like http_build_query would).
+
+
+## `function mimetype_from_filename`
+
+`function mimetype_from_filename($filename)`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `function mimetype_from_extension`
+
+`function mimetype_from_extension($extension)`
+
+Maps a file extensions to a mimetype.
+
+
+# Static URI methods
+
+The `RingCentral\Psr7\Uri` class has several static methods to manipulate URIs.
+
+
+## `RingCentral\Psr7\Uri::removeDotSegments`
+
+`public static function removeDotSegments($path) -> UriInterface`
+
+Removes dot segments from a path and returns the new path.
+
+See http://tools.ietf.org/html/rfc3986#section-5.2.4
+
+
+## `RingCentral\Psr7\Uri::resolve`
+
+`public static function resolve(UriInterface $base, $rel) -> UriInterface`
+
+Resolve a base URI with a relative URI and return a new URI.
+
+See http://tools.ietf.org/html/rfc3986#section-5
+
+
+## `RingCentral\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
+
+Create a new URI with a specific query string value.
+
+Any existing query string values that exactly match the provided key are
+removed and replaced with the given key value pair.
+
+Note: this function will convert "=" to "%3D" and "&" to "%26".
+
+
+## `RingCentral\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
+
+Create a new URI with a specific query string value removed.
+
+Any existing query string values that exactly match the provided key are
+removed.
+
+Note: this function will convert "=" to "%3D" and "&" to "%26".
+
+
+## `RingCentral\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts) -> UriInterface`
+
+Create a `RingCentral\Psr7\Uri` object from a hash of `parse_url` parts.
+
+
+# Not Implemented
+
+A few aspects of PSR-7 are not implemented in this project. A pull request for
+any of these features is welcome:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/composer.json b/src/mpg25-instagram-api/vendor/ringcentral/psr7/composer.json
new file mode 100755
index 0000000..4955053
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "ringcentral/psr7",
+ "type": "library",
+ "description": "PSR-7 message implementation",
+ "keywords": ["message", "stream", "http", "uri"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3",
+ "psr/http-message": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "RingCentral\\Psr7\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/ringcentral/psr7/phpunit.xml.dist
new file mode 100755
index 0000000..500cd53
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/phpunit.xml.dist
@@ -0,0 +1,17 @@
+
+
+
+
+ tests
+
+
+
+
+ src
+
+ src/
+
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/AppendStream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/AppendStream.php
new file mode 100755
index 0000000..8b8df6f
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/AppendStream.php
@@ -0,0 +1,233 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream)
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ *
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->pos = $this->current = 0;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = array();
+ }
+
+ /**
+ * Detaches each attached stream
+ *
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $this->close();
+ $this->detached = true;
+ }
+
+ public function tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ *
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof()
+ {
+ return !$this->streams ||
+ ($this->current >= count($this->streams) - 1 &&
+ $this->streams[$this->current]->eof());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ *
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ . $i . ' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ *
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ $this->current++;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ // Using a loose comparison here to match on '', false, and null
+ if ($result == null) {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key ? null : array();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/BufferStream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/BufferStream.php
new file mode 100755
index 0000000..a1e236d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/BufferStream.php
@@ -0,0 +1,137 @@
+hwm = $hwm;
+ }
+
+ public function __toString()
+ {
+ return $this->getContents();
+ }
+
+ public function getContents()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+ }
+
+ public function getSize()
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return true;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof()
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell()
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length)
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string)
+ {
+ $this->buffer .= $string;
+
+ // TODO: What should happen here?
+ if (strlen($this->buffer) >= $this->hwm) {
+ return false;
+ }
+
+ return strlen($string);
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key == 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : array();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/CachingStream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/CachingStream.php
new file mode 100755
index 0000000..ce3aca8
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/CachingStream.php
@@ -0,0 +1,135 @@
+remoteStream = $stream;
+ parent::__construct($target ?: new Stream(fopen('php://temp', 'r+')));
+ }
+
+ public function getSize()
+ {
+ return max($this->stream->getSize(), $this->remoteStream->getSize());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence == SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence == SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence == SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ // Because 0 is the first byte, we seek to size - 1.
+ $byte = $size - 1 - $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // If the seek byte is greater the number of read bytes, then read
+ // the difference of bytes to cache the bytes and inherently seek.
+ $this->read($diff);
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string)
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ $this->remoteStream->close() && $this->stream->close();
+ }
+
+ private function cacheEntireStream()
+ {
+ $target = new FnStream(array('write' => 'strlen'));
+ copy_to_stream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/DroppingStream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/DroppingStream.php
new file mode 100755
index 0000000..3a34d38
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/DroppingStream.php
@@ -0,0 +1,41 @@
+maxLength = $maxLength;
+ }
+
+ public function write($string)
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/FnStream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/FnStream.php
new file mode 100755
index 0000000..f78dc8b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/FnStream.php
@@ -0,0 +1,163 @@
+methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_' . $name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ * @throws \BadMethodCallException
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ . '() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ call_user_func($this->_fn_close);
+ }
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
+ $methods[$diff] = array($stream, $diff);
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString()
+ {
+ return call_user_func($this->_fn___toString);
+ }
+
+ public function close()
+ {
+ return call_user_func($this->_fn_close);
+ }
+
+ public function detach()
+ {
+ return call_user_func($this->_fn_detach);
+ }
+
+ public function getSize()
+ {
+ return call_user_func($this->_fn_getSize);
+ }
+
+ public function tell()
+ {
+ return call_user_func($this->_fn_tell);
+ }
+
+ public function eof()
+ {
+ return call_user_func($this->_fn_eof);
+ }
+
+ public function isSeekable()
+ {
+ return call_user_func($this->_fn_isSeekable);
+ }
+
+ public function rewind()
+ {
+ call_user_func($this->_fn_rewind);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ call_user_func($this->_fn_seek, $offset, $whence);
+ }
+
+ public function isWritable()
+ {
+ return call_user_func($this->_fn_isWritable);
+ }
+
+ public function write($string)
+ {
+ return call_user_func($this->_fn_write, $string);
+ }
+
+ public function isReadable()
+ {
+ return call_user_func($this->_fn_isReadable);
+ }
+
+ public function read($length)
+ {
+ return call_user_func($this->_fn_read, $length);
+ }
+
+ public function getContents()
+ {
+ return call_user_func($this->_fn_getContents);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return call_user_func($this->_fn_getMetadata, $key);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/InflateStream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/InflateStream.php
new file mode 100755
index 0000000..c718002
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/InflateStream.php
@@ -0,0 +1,27 @@
+filename = $filename;
+ $this->mode = $mode;
+ parent::__construct();
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream()
+ {
+ return stream_for(try_fopen($this->filename, $this->mode));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/LimitStream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/LimitStream.php
new file mode 100755
index 0000000..57eeca9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/LimitStream.php
@@ -0,0 +1,154 @@
+setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof()
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit == -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit == -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset % with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset($offset)
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length)
+ {
+ if ($this->limit == -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/MessageTrait.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/MessageTrait.php
new file mode 100755
index 0000000..9330bcb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/MessageTrait.php
@@ -0,0 +1,167 @@
+protocol;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+ return $new;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headerLines;
+ }
+
+ public function hasHeader($header)
+ {
+ return isset($this->headers[strtolower($header)]);
+ }
+
+ public function getHeader($header)
+ {
+ $name = strtolower($header);
+ return isset($this->headers[$name]) ? $this->headers[$name] : array();
+ }
+
+ public function getHeaderLine($header)
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value)
+ {
+ $new = clone $this;
+ $header = trim($header);
+ $name = strtolower($header);
+
+ if (!is_array($value)) {
+ $new->headers[$name] = array(trim($value));
+ } else {
+ $new->headers[$name] = $value;
+ foreach ($new->headers[$name] as &$v) {
+ $v = trim($v);
+ }
+ }
+
+ // Remove the header lines.
+ foreach (array_keys($new->headerLines) as $key) {
+ if (strtolower($key) === $name) {
+ unset($new->headerLines[$key]);
+ }
+ }
+
+ // Add the header line.
+ $new->headerLines[$header] = $new->headers[$name];
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value)
+ {
+ if (!$this->hasHeader($header)) {
+ return $this->withHeader($header, $value);
+ }
+
+ $header = trim($header);
+ $name = strtolower($header);
+
+ $value = (array) $value;
+ foreach ($value as &$v) {
+ $v = trim($v);
+ }
+
+ $new = clone $this;
+ $new->headers[$name] = array_merge($new->headers[$name], $value);
+ $new->headerLines[$header] = array_merge($new->headerLines[$header], $value);
+
+ return $new;
+ }
+
+ public function withoutHeader($header)
+ {
+ if (!$this->hasHeader($header)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $name = strtolower($header);
+ unset($new->headers[$name]);
+
+ foreach (array_keys($new->headerLines) as $key) {
+ if (strtolower($key) === $name) {
+ unset($new->headerLines[$key]);
+ }
+ }
+
+ return $new;
+ }
+
+ public function getBody()
+ {
+ if (!$this->stream) {
+ $this->stream = stream_for('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body)
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+ return $new;
+ }
+
+ protected function setHeaders(array $headers)
+ {
+ $this->headerLines = $this->headers = array();
+ foreach ($headers as $header => $value) {
+ $header = trim($header);
+ $name = strtolower($header);
+ if (!is_array($value)) {
+ $value = trim($value);
+ $this->headers[$name][] = $value;
+ $this->headerLines[$header][] = $value;
+ } else {
+ foreach ($value as $v) {
+ $v = trim($v);
+ $this->headers[$name][] = $v;
+ $this->headerLines[$header][] = $v;
+ }
+ }
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/MultipartStream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/MultipartStream.php
new file mode 100755
index 0000000..8c5e5bc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/MultipartStream.php
@@ -0,0 +1,152 @@
+boundary = $boundary ?: uniqid();
+ parent::__construct($this->createStream($elements));
+ }
+
+ /**
+ * Get the boundary
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ */
+ private function getHeaders(array $headers)
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements)
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(stream_for("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element)
+ {
+ foreach (array('contents', 'name') as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = stream_for($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if (substr($uri, 0, 6) !== 'php://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ list($body, $headers) = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ isset($element['filename']) ? $element['filename'] : null,
+ isset($element['headers']) ? $element['headers'] : array()
+ );
+
+ $stream->addStream(stream_for($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(stream_for("\r\n"));
+ }
+
+ /**
+ * @return array
+ */
+ private function createElement($name, $stream, $filename, array $headers)
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = $this->getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = $filename
+ ? sprintf('form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename))
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = $this->getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = $this->getHeader($headers, 'content-type');
+ if (!$type && $filename) {
+ if ($type = mimetype_from_filename($filename)) {
+ $headers['Content-Type'] = $type;
+ }
+ }
+
+ return array($stream, $headers);
+ }
+
+ private function getHeader(array $headers, $key)
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower($k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/NoSeekStream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/NoSeekStream.php
new file mode 100755
index 0000000..328fdda
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/NoSeekStream.php
@@ -0,0 +1,21 @@
+source = $source;
+ $this->size = isset($options['size']) ? $options['size'] : null;
+ $this->metadata = isset($options['metadata']) ? $options['metadata'] : array();
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString()
+ {
+ try {
+ return copy_to_string($this);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function close()
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = false;
+ $this->source = null;
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ return $this->tellPos;
+ }
+
+ public function eof()
+ {
+ return !$this->source;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents()
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
+ }
+
+ private function pump($length)
+ {
+ if ($this->source) {
+ do {
+ $data = call_user_func($this->source, $length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Request.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Request.php
new file mode 100755
index 0000000..bb0f2fc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Request.php
@@ -0,0 +1,146 @@
+method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $protocolVersion;
+
+ $host = $uri->getHost();
+ if ($host && !$this->hasHeader('Host')) {
+ $this->updateHostFromUri($host);
+ }
+
+ if ($body) {
+ $this->stream = stream_for($body);
+ }
+ }
+
+ public function getRequestTarget()
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target == null) {
+ $target = '/';
+ }
+ if ($this->uri->getQuery()) {
+ $target .= '?' . $this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget)
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+ return $new;
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method)
+ {
+ $new = clone $this;
+ $new->method = strtoupper($method);
+ return $new;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false)
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost) {
+ if ($host = $uri->getHost()) {
+ $new->updateHostFromUri($host);
+ }
+ }
+
+ return $new;
+ }
+
+ public function withHeader($header, $value)
+ {
+ /** @var Request $newInstance */
+ $newInstance = parent::withHeader($header, $value);
+ return $newInstance;
+ }
+
+ private function updateHostFromUri($host)
+ {
+ // Ensure Host is the first header.
+ // See: http://tools.ietf.org/html/rfc7230#section-5.4
+ if ($port = $this->uri->getPort()) {
+ $host .= ':' . $port;
+ }
+
+ $this->headerLines = array('Host' => array($host)) + $this->headerLines;
+ $this->headers = array('host' => array($host)) + $this->headers;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Response.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Response.php
new file mode 100755
index 0000000..a6d9451
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Response.php
@@ -0,0 +1,129 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 511 => 'Network Authentication Required',
+ );
+
+ /** @var null|string */
+ private $reasonPhrase = '';
+
+ /** @var int */
+ private $statusCode = 200;
+
+ /**
+ * @param int $status Status code for the response, if any.
+ * @param array $headers Headers for the response, if any.
+ * @param mixed $body Stream body.
+ * @param string $version Protocol version.
+ * @param string $reason Reason phrase (a default will be used if possible).
+ */
+ public function __construct(
+ $status = 200,
+ array $headers = array(),
+ $body = null,
+ $version = '1.1',
+ $reason = null
+ ) {
+ $this->statusCode = (int) $status;
+
+ if ($body !== null) {
+ $this->stream = stream_for($body);
+ }
+
+ $this->setHeaders($headers);
+ if (!$reason && isset(self::$phrases[$this->statusCode])) {
+ $this->reasonPhrase = self::$phrases[$status];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ $new = clone $this;
+ $new->statusCode = (int) $code;
+ if (!$reasonPhrase && isset(self::$phrases[$new->statusCode])) {
+ $reasonPhrase = self::$phrases[$new->statusCode];
+ }
+ $new->reasonPhrase = $reasonPhrase;
+ return $new;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/ServerRequest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/ServerRequest.php
new file mode 100755
index 0000000..8408a09
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/ServerRequest.php
@@ -0,0 +1,122 @@
+serverParams = $serverParams;
+ }
+
+ public function getServerParams()
+ {
+ return $this->serverParams;
+ }
+
+ public function getCookieParams()
+ {
+ return $this->cookies;
+ }
+
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->cookies = $cookies;
+ return $new;
+ }
+
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+ return $new;
+ }
+
+ public function getUploadedFiles()
+ {
+ return $this->fileParams;
+ }
+
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->fileParams = $uploadedFiles;
+ return $new;
+ }
+
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+ return $new;
+ }
+
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ public function getAttribute($name, $default = null)
+ {
+ if (!array_key_exists($name, $this->attributes)) {
+ return $default;
+ }
+ return $this->attributes[$name];
+ }
+
+ public function withAttribute($name, $value)
+ {
+ $new = clone $this;
+ $new->attributes[$name] = $value;
+ return $new;
+ }
+
+ public function withoutAttribute($name)
+ {
+ $new = clone $this;
+ unset($new->attributes[$name]);
+ return $new;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Stream.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Stream.php
new file mode 100755
index 0000000..0a0157c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Stream.php
@@ -0,0 +1,245 @@
+ array(
+ 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
+ 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
+ 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a+' => true
+ ),
+ 'write' => array(
+ 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
+ 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
+ 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
+ )
+ );
+
+ /**
+ * This constructor accepts an associative array of options.
+ *
+ * - size: (int) If a read stream would otherwise have an indeterminate
+ * size, but the size is known due to foreknownledge, then you can
+ * provide that size, in bytes.
+ * - metadata: (array) Any additional metadata to return when the metadata
+ * of the stream is accessed.
+ *
+ * @param resource $stream Stream resource to wrap.
+ * @param array $options Associative array of options.
+ *
+ * @throws \InvalidArgumentException if the stream is not a stream resource
+ */
+ public function __construct($stream, $options = array())
+ {
+ if (!is_resource($stream)) {
+ throw new \InvalidArgumentException('Stream must be a resource');
+ }
+
+ if (isset($options['size'])) {
+ $this->size = $options['size'];
+ }
+
+ $this->customMetadata = isset($options['metadata'])
+ ? $options['metadata']
+ : array();
+
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
+ $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ throw new \RuntimeException('The stream is detached');
+ }
+
+ throw new \BadMethodCallException('No value for ' . $name);
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return (string) stream_get_contents($this->stream);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ $contents = stream_get_contents($this->stream);
+
+ if ($contents === false) {
+ throw new \RuntimeException('Unable to read stream contents');
+ }
+
+ return $contents;
+ }
+
+ public function close()
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function eof()
+ {
+ return !$this->stream || feof($this->stream);
+ }
+
+ public function tell()
+ {
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ } elseif (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ . $offset . ' with whence ' . var_export($whence, true));
+ }
+ }
+
+ public function read($length)
+ {
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+
+ return fread($this->stream, $length);
+ }
+
+ public function write($string)
+ {
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : array();
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/StreamDecoratorTrait.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/StreamDecoratorTrait.php
new file mode 100755
index 0000000..e22c674
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,139 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @param string $name Name of the property (allows "stream" only).
+ *
+ * @return StreamInterface
+ */
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ $this->stream = $this->createStream();
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString()
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+ return $this->getContents();
+ } catch (\Exception $e) {
+ // Really, PHP? https://bugs.php.net/bug.php?id=53648
+ trigger_error('StreamDecorator::__toString exception: '
+ . (string) $e, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ $result = call_user_func_array(array($this->stream, $method), $args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close()
+ {
+ $this->stream->close();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize()
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable()
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->stream->write($string);
+ }
+
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/StreamWrapper.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/StreamWrapper.php
new file mode 100755
index 0000000..8cc07d7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/StreamWrapper.php
@@ -0,0 +1,121 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ . 'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, null, stream_context_create(array(
+ 'guzzle' => array('stream' => $stream)
+ )));
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register()
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read($count)
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write($data)
+ {
+ return (int) $this->stream->write($data);
+ }
+
+ public function stream_tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ public function stream_stat()
+ {
+ static $modeMap = array(
+ 'r' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188
+ );
+
+ return array(
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Uri.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Uri.php
new file mode 100755
index 0000000..5323cdc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/Uri.php
@@ -0,0 +1,601 @@
+ 80,
+ 'https' => 443,
+ );
+
+ private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
+ private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
+ private static $replaceQuery = array('=' => '%3D', '&' => '%26');
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /**
+ * @param string $uri URI to parse and wrap.
+ */
+ public function __construct($uri = '')
+ {
+ if ($uri != null) {
+ $parts = parse_url($uri);
+ if ($parts === false) {
+ throw new \InvalidArgumentException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ public function __toString()
+ {
+ return self::createUriString(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->getPath(),
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ /**
+ * Removes dot segments from a path and returns the new path.
+ *
+ * @param string $path
+ *
+ * @return string
+ * @link http://tools.ietf.org/html/rfc3986#section-5.2.4
+ */
+ public static function removeDotSegments($path)
+ {
+ static $noopPaths = array('' => true, '/' => true, '*' => true);
+ static $ignoreSegments = array('.' => true, '..' => true);
+
+ if (isset($noopPaths[$path])) {
+ return $path;
+ }
+
+ $results = array();
+ $segments = explode('/', $path);
+ foreach ($segments as $segment) {
+ if ($segment == '..') {
+ array_pop($results);
+ } elseif (!isset($ignoreSegments[$segment])) {
+ $results[] = $segment;
+ }
+ }
+
+ $newPath = implode('/', $results);
+ // Add the leading slash if necessary
+ if (substr($path, 0, 1) === '/' &&
+ substr($newPath, 0, 1) !== '/'
+ ) {
+ $newPath = '/' . $newPath;
+ }
+
+ // Add the trailing slash if necessary
+ if ($newPath != '/' && isset($ignoreSegments[end($segments)])) {
+ $newPath .= '/';
+ }
+
+ return $newPath;
+ }
+
+ /**
+ * Resolve a base URI with a relative URI and return a new URI.
+ *
+ * @param UriInterface $base Base URI
+ * @param string $rel Relative URI
+ *
+ * @return UriInterface
+ */
+ public static function resolve(UriInterface $base, $rel)
+ {
+ if ($rel === null || $rel === '') {
+ return $base;
+ }
+
+ if (!($rel instanceof UriInterface)) {
+ $rel = new self($rel);
+ }
+
+ // Return the relative uri as-is if it has a scheme.
+ if ($rel->getScheme()) {
+ return $rel->withPath(static::removeDotSegments($rel->getPath()));
+ }
+
+ $relParts = array(
+ 'scheme' => $rel->getScheme(),
+ 'authority' => $rel->getAuthority(),
+ 'path' => $rel->getPath(),
+ 'query' => $rel->getQuery(),
+ 'fragment' => $rel->getFragment()
+ );
+
+ $parts = array(
+ 'scheme' => $base->getScheme(),
+ 'authority' => $base->getAuthority(),
+ 'path' => $base->getPath(),
+ 'query' => $base->getQuery(),
+ 'fragment' => $base->getFragment()
+ );
+
+ if (!empty($relParts['authority'])) {
+ $parts['authority'] = $relParts['authority'];
+ $parts['path'] = self::removeDotSegments($relParts['path']);
+ $parts['query'] = $relParts['query'];
+ $parts['fragment'] = $relParts['fragment'];
+ } elseif (!empty($relParts['path'])) {
+ if (substr($relParts['path'], 0, 1) == '/') {
+ $parts['path'] = self::removeDotSegments($relParts['path']);
+ $parts['query'] = $relParts['query'];
+ $parts['fragment'] = $relParts['fragment'];
+ } else {
+ if (!empty($parts['authority']) && empty($parts['path'])) {
+ $mergedPath = '/';
+ } else {
+ $mergedPath = substr($parts['path'], 0, strrpos($parts['path'], '/') + 1);
+ }
+ $parts['path'] = self::removeDotSegments($mergedPath . $relParts['path']);
+ $parts['query'] = $relParts['query'];
+ $parts['fragment'] = $relParts['fragment'];
+ }
+ } elseif (!empty($relParts['query'])) {
+ $parts['query'] = $relParts['query'];
+ } elseif ($relParts['fragment'] != null) {
+ $parts['fragment'] = $relParts['fragment'];
+ }
+
+ return new self(static::createUriString(
+ $parts['scheme'],
+ $parts['authority'],
+ $parts['path'],
+ $parts['query'],
+ $parts['fragment']
+ ));
+ }
+
+ /**
+ * Create a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * Note: this function will convert "=" to "%3D" and "&" to "%26".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key value pair to remove.
+ *
+ * @return UriInterface
+ */
+ public static function withoutQueryValue(UriInterface $uri, $key)
+ {
+ $current = $uri->getQuery();
+ if (!$current) {
+ return $uri;
+ }
+
+ $result = array();
+ foreach (explode('&', $current) as $part) {
+ $subParts = explode('=', $part);
+ if ($subParts[0] !== $key) {
+ $result[] = $part;
+ };
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Create a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * Note: this function will convert "=" to "%3D" and "&" to "%26".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string $value Value to set.
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValue(UriInterface $uri, $key, $value)
+ {
+ $current = $uri->getQuery();
+ $key = strtr($key, self::$replaceQuery);
+
+ if (!$current) {
+ $result = array();
+ } else {
+ $result = array();
+ foreach (explode('&', $current) as $part) {
+ $subParts = explode('=', $part);
+ if ($subParts[0] !== $key) {
+ $result[] = $part;
+ };
+ }
+ }
+
+ if ($value !== null) {
+ $result[] = $key . '=' . strtr($value, self::$replaceQuery);
+ } else {
+ $result[] = $key;
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Create a URI from a hash of parse_url parts.
+ *
+ * @param array $parts
+ *
+ * @return self
+ */
+ public static function fromParts(array $parts)
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ return $uri;
+ }
+
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority()
+ {
+ if (empty($this->host)) {
+ return '';
+ }
+
+ $authority = $this->host;
+ if (!empty($this->userInfo)) {
+ $authority = $this->userInfo . '@' . $authority;
+ }
+
+ if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) {
+ $authority .= ':' . $this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo()
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ public function getPath()
+ {
+ return $this->path == null ? '' : $this->path;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme)
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->port = $new->filterPort($new->scheme, $new->host, $new->port);
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null)
+ {
+ $info = $user;
+ if ($password) {
+ $info .= ':' . $password;
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ return $new;
+ }
+
+ public function withHost($host)
+ {
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ return $new;
+ }
+
+ public function withPort($port)
+ {
+ $port = $this->filterPort($this->scheme, $this->host, $port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ return $new;
+ }
+
+ public function withPath($path)
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException(
+ 'Invalid path provided; must be a string'
+ );
+ }
+
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ return $new;
+ }
+
+ public function withQuery($query)
+ {
+ if (!is_string($query) && !method_exists($query, '__toString')) {
+ throw new \InvalidArgumentException(
+ 'Query string must be a string'
+ );
+ }
+
+ $query = (string) $query;
+ if (substr($query, 0, 1) === '?') {
+ $query = substr($query, 1);
+ }
+
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+ return $new;
+ }
+
+ public function withFragment($fragment)
+ {
+ if (substr($fragment, 0, 1) === '#') {
+ $fragment = substr($fragment, 1);
+ }
+
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+ return $new;
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts)
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
+ $this->host = isset($parts['host']) ? $parts['host'] : '';
+ $this->port = !empty($parts['port'])
+ ? $this->filterPort($this->scheme, $this->host, $parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':' . $parts['pass'];
+ }
+ }
+
+ /**
+ * Create a URI string from its various parts
+ *
+ * @param string $scheme
+ * @param string $authority
+ * @param string $path
+ * @param string $query
+ * @param string $fragment
+ * @return string
+ */
+ private static function createUriString($scheme, $authority, $path, $query, $fragment)
+ {
+ $uri = '';
+
+ if (!empty($scheme)) {
+ $uri .= $scheme . '://';
+ }
+
+ if (!empty($authority)) {
+ $uri .= $authority;
+ }
+
+ if ($path != null) {
+ // Add a leading slash if necessary.
+ if ($uri && substr($path, 0, 1) !== '/') {
+ $uri .= '/';
+ }
+ $uri .= $path;
+ }
+
+ if ($query != null) {
+ $uri .= '?' . $query;
+ }
+
+ if ($fragment != null) {
+ $uri .= '#' . $fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Is a given port non-standard for the current scheme?
+ *
+ * @param string $scheme
+ * @param string $host
+ * @param int $port
+ * @return bool
+ */
+ private static function isNonStandardPort($scheme, $host, $port)
+ {
+ if (!$scheme && $port) {
+ return true;
+ }
+
+ if (!$host || !$port) {
+ return false;
+ }
+
+ return !isset(static::$schemes[$scheme]) || $port !== static::$schemes[$scheme];
+ }
+
+ /**
+ * @param string $scheme
+ *
+ * @return string
+ */
+ private function filterScheme($scheme)
+ {
+ $scheme = strtolower($scheme);
+ $scheme = rtrim($scheme, ':/');
+
+ return $scheme;
+ }
+
+ /**
+ * @param string $scheme
+ * @param string $host
+ * @param int $port
+ *
+ * @return int|null
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($scheme, $host, $port)
+ {
+ if (null !== $port) {
+ $port = (int) $port;
+ if (1 > $port || 0xffff < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
+ );
+ }
+ }
+
+ return $this->isNonStandardPort($scheme, $host, $port) ? $port : null;
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param $path
+ *
+ * @return string
+ */
+ private function filterPath($path)
+ {
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . ':@\/%]+|%(?![A-Fa-f0-9]{2}))/',
+ array($this, 'rawurlencodeMatchZero'),
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param $str
+ *
+ * @return string
+ */
+ private function filterQueryAndFragment($str)
+ {
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
+ array($this, 'rawurlencodeMatchZero'),
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match)
+ {
+ return rawurlencode($match[0]);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/functions.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/functions.php
new file mode 100755
index 0000000..3fc89bd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/functions.php
@@ -0,0 +1,832 @@
+getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+}
+
+/**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
+ * returns a UriInterface for the given value. If the value is already a
+ * `UriInterface`, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @return UriInterface
+ * @throws \InvalidArgumentException
+ */
+function uri_for($uri)
+{
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ } elseif (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+}
+
+/**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * @param resource|string|StreamInterface $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return Stream
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+function stream_for($resource = '', array $options = array())
+{
+ switch (gettype($resource)) {
+ case 'string':
+ $stream = fopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new Stream($stream, $options);
+ case 'resource':
+ return new Stream($resource, $options);
+ case 'object':
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return stream_for((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(fopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
+}
+
+/**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair
+ * data of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ *
+ * @return array Returns the parsed header values.
+ */
+function parse_header($header)
+{
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = array();
+
+ foreach (normalize_header($header) as $val) {
+ $part = array();
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+function normalize_header($header)
+{
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = array();
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Clone and modify a request with the given changes.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+function modify_request(RequestInterface $request, array $changes)
+{
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = _caseless_remove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+}
+
+/**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()` returns a
+ * value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+function rewind_body(MessageInterface $message)
+{
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+}
+
+/**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ * @throws \RuntimeException if the file cannot be opened
+ */
+function try_fopen($filename, $mode)
+{
+ $ex = null;
+ $fargs = func_get_args();
+ set_error_handler(function () use ($filename, $mode, &$ex, $fargs) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open %s using mode %s: %s',
+ $filename,
+ $mode,
+ $fargs[1]
+ ));
+ });
+
+ $handle = fopen($filename, $mode);
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+}
+
+/**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ * @return string
+ * @throws \RuntimeException on error.
+ */
+function copy_to_string(StreamInterface $stream, $maxLen = -1)
+{
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+}
+
+/**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+function copy_to_stream(
+ StreamInterface $source,
+ StreamInterface $dest,
+ $maxLen = -1
+) {
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read(1048576))) {
+ break;
+ }
+ }
+ return;
+ }
+
+ $bytes = 0;
+ while (!$source->eof()) {
+ $buf = $source->read($maxLen - $bytes);
+ if (!($len = strlen($buf))) {
+ break;
+ }
+ $bytes += $len;
+ $dest->write($buf);
+ if ($bytes == $maxLen) {
+ break;
+ }
+ }
+}
+
+/**
+ * Calculate a hash of a Stream
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ * @throws \RuntimeException on error.
+ */
+function hash(
+ StreamInterface $stream,
+ $algo,
+ $rawOutput = false
+) {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+}
+
+/**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string|bool
+ */
+function readline(StreamInterface $stream, $maxLength = null)
+{
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ // Using a loose equality here to match on '' and false.
+ if (null == ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+}
+
+/**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ *
+ * @return Request
+ */
+function parse_request($message)
+{
+ $data = _parse_message($message);
+ $matches = array();
+ if (!preg_match('/^[a-zA-Z]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $subParts = isset($parts[2]) ? explode('/', $parts[2]) : array();
+ $version = isset($parts[2]) ? $subParts[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+}
+
+/**
+ * Parses a request message string into a server request object.
+ *
+ * @param string $message Request message string.
+ * @param array $serverParams Server params that will be added to the
+ * ServerRequest object
+ *
+ * @return ServerRequest
+ */
+function parse_server_request($message, array $serverParams = array())
+{
+ $request = parse_request($message);
+
+ return new ServerRequest(
+ $request->getMethod(),
+ $request->getUri(),
+ $request->getHeaders(),
+ $request->getBody(),
+ $request->getProtocolVersion(),
+ $serverParams
+ );
+}
+
+/**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ *
+ * @return Response
+ */
+function parse_response($message)
+{
+ $data = _parse_message($message);
+ if (!preg_match('/^HTTP\/.* [0-9]{3} .*/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $subParts = explode('/', $parts[0]);
+
+ return new Response(
+ $parts[1],
+ $data['headers'],
+ $data['body'],
+ $subParts[1],
+ isset($parts[2]) ? $parts[2] : null
+ );
+}
+
+/**
+ * Parse a query string into an associative array.
+ *
+ * If multiple values are found for the same key, the value of that key
+ * value pair will become an array. This function does not parse nested
+ * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
+ * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
+ *
+ * @param string $str Query string to parse
+ * @param bool|string $urlEncoding How the query string is encoded
+ *
+ * @return array
+ */
+function parse_query($str, $urlEncoding = true)
+{
+ $result = array();
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', $value));
+ };
+ } elseif ($urlEncoding == PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding == PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) { return $str; };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!isset($result[$key])) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = array($result[$key]);
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of parseQuery() to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like http_build_query would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
+ * to encode using RFC3986, or PHP_QUERY_RFC1738
+ * to encode using RFC1738.
+ * @return string
+ */
+function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
+{
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function ($str) { return $str; };
+ } elseif ($encoding == PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding == PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder($k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ if ($v !== null) {
+ $qs .= '=' . $encoder($v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ if ($vv !== null) {
+ $qs .= '=' . $encoder($vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+}
+
+/**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @param $filename
+ *
+ * @return null|string
+ */
+function mimetype_from_filename($filename)
+{
+ return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
+}
+
+/**
+ * Maps a file extensions to a mimetype.
+ *
+ * @param $extension string The file extension.
+ *
+ * @return string|null
+ * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
+ */
+function mimetype_from_extension($extension)
+{
+ static $mimetypes = array(
+ '7z' => 'application/x-7z-compressed',
+ 'aac' => 'audio/x-aac',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'asc' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'atom' => 'application/atom+xml',
+ 'avi' => 'video/x-msvideo',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip2',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'deb' => 'application/x-debian-package',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dvi' => 'application/x-dvi',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'etx' => 'text/x-setext',
+ 'flac' => 'audio/flac',
+ 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif',
+ 'gz' => 'application/gzip',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ini' => 'text/plain',
+ 'iso' => 'application/x-iso9660-image',
+ 'jar' => 'application/java-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'latex' => 'application/x-latex',
+ 'log' => 'text/plain',
+ 'm4a' => 'audio/mp4',
+ 'm4v' => 'video/mp4',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'qt' => 'video/quicktime',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'torrent' => 'application/x-bittorrent',
+ 'ttf' => 'application/x-font-ttf',
+ 'txt' => 'text/plain',
+ 'wav' => 'audio/x-wav',
+ 'webm' => 'video/webm',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'video/x-ms-wmv',
+ 'woff' => 'application/x-font-woff',
+ 'wsdl' => 'application/wsdl+xml',
+ 'xbm' => 'image/x-xbitmap',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'zip' => 'application/zip',
+ );
+
+ $extension = strtolower($extension);
+
+ return isset($mimetypes[$extension])
+ ? $mimetypes[$extension]
+ : null;
+}
+
+/**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ *
+ * @return array
+ * @internal
+ */
+function _parse_message($message)
+{
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ // Iterate over each line in the message, accounting for line endings
+ $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $result = array('start-line' => array_shift($lines), 'headers' => array(), 'body' => '');
+ array_shift($lines);
+
+ for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
+ $line = $lines[$i];
+ // If two line breaks were encountered, then this is the end of body
+ if (empty($line)) {
+ if ($i < $totalLines - 1) {
+ $result['body'] = implode('', array_slice($lines, $i + 2));
+ }
+ break;
+ }
+ if (strpos($line, ':')) {
+ $parts = explode(':', $line, 2);
+ $key = trim($parts[0]);
+ $value = isset($parts[1]) ? trim($parts[1]) : '';
+ $result['headers'][$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ *
+ * @return string
+ * @internal
+ */
+function _parse_request_uri($path, array $headers)
+{
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme . '://' . $host . '/' . ltrim($path, '/');
+}
+
+/** @internal */
+function _caseless_remove($keys, array $data)
+{
+ $result = array();
+
+ foreach ($keys as &$key) {
+ $key = strtolower($key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower($k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/functions_include.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/functions_include.php
new file mode 100755
index 0000000..252e0cf
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/src/functions_include.php
@@ -0,0 +1,6 @@
+getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $a->addStream($s);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage The AppendStream can only seek with SEEK_SET
+ */
+ public function testValidatesSeekType()
+ {
+ $a = new AppendStream();
+ $a->seek(100, SEEK_CUR);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to seek stream 0 of the AppendStream
+ */
+ public function testTriesToRewindOnSeek()
+ {
+ $a = new AppendStream();
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable', 'rewind', 'isSeekable'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('rewind')
+ ->will($this->throwException(new \RuntimeException()));
+ $a->addStream($s);
+ $a->seek(10);
+ }
+
+ public function testSeeksToPositionByReading()
+ {
+ $a = new AppendStream(array(
+ Psr7\stream_for('foo'),
+ Psr7\stream_for('bar'),
+ Psr7\stream_for('baz'),
+ ));
+
+ $a->seek(3);
+ $this->assertEquals(3, $a->tell());
+ $this->assertEquals('bar', $a->read(3));
+
+ $a->seek(6);
+ $this->assertEquals(6, $a->tell());
+ $this->assertEquals('baz', $a->read(3));
+ }
+
+ public function testDetachesEachStream()
+ {
+ $s1 = Psr7\stream_for('foo');
+ $s2 = Psr7\stream_for('bar');
+ $a = new AppendStream(array($s1, $s2));
+ $this->assertSame('foobar', (string) $a);
+ $a->detach();
+ $this->assertSame('', (string) $a);
+ $this->assertSame(0, $a->getSize());
+ }
+
+ public function testClosesEachStream()
+ {
+ $s1 = Psr7\stream_for('foo');
+ $a = new AppendStream(array($s1));
+ $a->close();
+ $this->assertSame('', (string) $a);
+ }
+
+ /**
+ * @expectedExceptionMessage Cannot write to an AppendStream
+ * @expectedException \RuntimeException
+ */
+ public function testIsNotWritable()
+ {
+ $a = new AppendStream(array(Psr7\stream_for('foo')));
+ $this->assertFalse($a->isWritable());
+ $this->assertTrue($a->isSeekable());
+ $this->assertTrue($a->isReadable());
+ $a->write('foo');
+ }
+
+ public function testDoesNotNeedStreams()
+ {
+ $a = new AppendStream();
+ $this->assertEquals('', (string) $a);
+ }
+
+ public function testCanReadFromMultipleStreams()
+ {
+ $a = new AppendStream(array(
+ Psr7\stream_for('foo'),
+ Psr7\stream_for('bar'),
+ Psr7\stream_for('baz'),
+ ));
+ $this->assertFalse($a->eof());
+ $this->assertSame(0, $a->tell());
+ $this->assertEquals('foo', $a->read(3));
+ $this->assertEquals('bar', $a->read(3));
+ $this->assertEquals('baz', $a->read(3));
+ $this->assertSame('', $a->read(1));
+ $this->assertTrue($a->eof());
+ $this->assertSame(9, $a->tell());
+ $this->assertEquals('foobarbaz', (string) $a);
+ }
+
+ public function testCanDetermineSizeFromMultipleStreams()
+ {
+ $a = new AppendStream(array(
+ Psr7\stream_for('foo'),
+ Psr7\stream_for('bar')
+ ));
+ $this->assertEquals(6, $a->getSize());
+
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isSeekable', 'isReadable'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(null));
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $a->addStream($s);
+ $this->assertNull($a->getSize());
+ }
+
+ public function testCatchesExceptionsWhenCastingToString()
+ {
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isSeekable', 'read', 'isReadable', 'eof'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('read')
+ ->will($this->throwException(new \RuntimeException('foo')));
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $s->expects($this->any())
+ ->method('eof')
+ ->will($this->returnValue(false));
+ $a = new AppendStream(array($s));
+ $this->assertFalse($a->eof());
+ $this->assertSame('', (string) $a);
+ }
+
+ public function testCanDetach()
+ {
+ $s = new AppendStream();
+ $s->detach();
+ }
+
+ public function testReturnsEmptyMetadata()
+ {
+ $s = new AppendStream();
+ $this->assertEquals(array(), $s->getMetadata());
+ $this->assertNull($s->getMetadata('foo'));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/BufferStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/BufferStreamTest.php
new file mode 100755
index 0000000..79f907a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/BufferStreamTest.php
@@ -0,0 +1,63 @@
+assertTrue($b->isReadable());
+ $this->assertTrue($b->isWritable());
+ $this->assertFalse($b->isSeekable());
+ $this->assertEquals(null, $b->getMetadata('foo'));
+ $this->assertEquals(10, $b->getMetadata('hwm'));
+ $this->assertEquals(array(), $b->getMetadata());
+ }
+
+ public function testRemovesReadDataFromBuffer()
+ {
+ $b = new BufferStream();
+ $this->assertEquals(3, $b->write('foo'));
+ $this->assertEquals(3, $b->getSize());
+ $this->assertFalse($b->eof());
+ $this->assertEquals('foo', $b->read(10));
+ $this->assertTrue($b->eof());
+ $this->assertEquals('', $b->read(10));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Cannot determine the position of a BufferStream
+ */
+ public function testCanCastToStringOrGetContents()
+ {
+ $b = new BufferStream();
+ $b->write('foo');
+ $b->write('baz');
+ $this->assertEquals('foo', $b->read(3));
+ $b->write('bar');
+ $this->assertEquals('bazbar', (string) $b);
+ $b->tell();
+ }
+
+ public function testDetachClearsBuffer()
+ {
+ $b = new BufferStream();
+ $b->write('foo');
+ $b->detach();
+ $this->assertTrue($b->eof());
+ $this->assertEquals(3, $b->write('abc'));
+ $this->assertEquals('abc', $b->read(10));
+ }
+
+ public function testExceedingHighwaterMarkReturnsFalseButStillBuffers()
+ {
+ $b = new BufferStream(5);
+ $this->assertEquals(3, $b->write('hi '));
+ $this->assertFalse($b->write('hello'));
+ $this->assertEquals('hi hello', (string) $b);
+ $this->assertEquals(4, $b->write('test'));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/CachingStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/CachingStreamTest.php
new file mode 100755
index 0000000..f394fc9
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/CachingStreamTest.php
@@ -0,0 +1,166 @@
+decorated = Psr7\stream_for('testing');
+ $this->body = new CachingStream($this->decorated);
+ }
+
+ public function tearDown()
+ {
+ $this->decorated->close();
+ $this->body->close();
+ }
+
+ public function testUsesRemoteSizeIfPossible()
+ {
+ $body = Psr7\stream_for('test');
+ $caching = new CachingStream($body);
+ $this->assertEquals(4, $caching->getSize());
+ }
+
+ public function testReadsUntilCachedToByte()
+ {
+ $this->body->seek(5);
+ $this->assertEquals('n', $this->body->read(1));
+ $this->body->seek(0);
+ $this->assertEquals('t', $this->body->read(1));
+ }
+
+ public function testCanSeekNearEndWithSeekEnd()
+ {
+ $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
+ $cached = new CachingStream($baseStream);
+ $cached->seek(1, SEEK_END);
+ $this->assertEquals(24, $baseStream->tell());
+ $this->assertEquals('y', $cached->read(1));
+ $this->assertEquals(26, $cached->getSize());
+ }
+
+ public function testCanSeekToEndWithSeekEnd()
+ {
+ $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
+ $cached = new CachingStream($baseStream);
+ $cached->seek(0, SEEK_END);
+ $this->assertEquals(25, $baseStream->tell());
+ $this->assertEquals('z', $cached->read(1));
+ $this->assertEquals(26, $cached->getSize());
+ }
+
+ public function testCanUseSeekEndWithUnknownSize()
+ {
+ $baseStream = Psr7\stream_for('testing');
+ $decorated = Psr7\FnStream::decorate($baseStream, array(
+ 'getSize' => function () { return null; }
+ ));
+ $cached = new CachingStream($decorated);
+ $cached->seek(1, SEEK_END);
+ $this->assertEquals('ng', $cached->read(2));
+ }
+
+ public function testRewindUsesSeek()
+ {
+ $a = Psr7\stream_for('foo');
+ $d = $this->getMockBuilder('RingCentral\Psr7\CachingStream')
+ ->setMethods(array('seek'))
+ ->setConstructorArgs(array($a))
+ ->getMock();
+ $d->expects($this->once())
+ ->method('seek')
+ ->with(0)
+ ->will($this->returnValue(true));
+ $d->seek(0);
+ }
+
+ public function testCanSeekToReadBytes()
+ {
+ $this->assertEquals('te', $this->body->read(2));
+ $this->body->seek(0);
+ $this->assertEquals('test', $this->body->read(4));
+ $this->assertEquals(4, $this->body->tell());
+ $this->body->seek(2);
+ $this->assertEquals(2, $this->body->tell());
+ $this->body->seek(2, SEEK_CUR);
+ $this->assertEquals(4, $this->body->tell());
+ $this->assertEquals('ing', $this->body->read(3));
+ }
+
+ public function testWritesToBufferStream()
+ {
+ $this->body->read(2);
+ $this->body->write('hi');
+ $this->body->seek(0);
+ $this->assertEquals('tehiing', (string) $this->body);
+ }
+
+ public function testSkipsOverwrittenBytes()
+ {
+ $decorated = Psr7\stream_for(
+ implode("\n", array_map(function ($n) {
+ return str_pad($n, 4, '0', STR_PAD_LEFT);
+ }, range(0, 25)))
+ );
+
+ $body = new CachingStream($decorated);
+
+ $this->assertEquals("0000\n", Psr7\readline($body));
+ $this->assertEquals("0001\n", Psr7\readline($body));
+ // Write over part of the body yet to be read, so skip some bytes
+ $this->assertEquals(5, $body->write("TEST\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+ // Read, which skips bytes, then reads
+ $this->assertEquals("0003\n", Psr7\readline($body));
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("0004\n", Psr7\readline($body));
+ $this->assertEquals("0005\n", Psr7\readline($body));
+
+ // Overwrite part of the cached body (so don't skip any bytes)
+ $body->seek(5);
+ $this->assertEquals(5, $body->write("ABCD\n"));
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("TEST\n", Psr7\readline($body));
+ $this->assertEquals("0003\n", Psr7\readline($body));
+ $this->assertEquals("0004\n", Psr7\readline($body));
+ $this->assertEquals("0005\n", Psr7\readline($body));
+ $this->assertEquals("0006\n", Psr7\readline($body));
+ $this->assertEquals(5, $body->write("1234\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+
+ // Seek to 0 and ensure the overwritten bit is replaced
+ $body->seek(0);
+ $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50));
+
+ // Ensure that casting it to a string does not include the bit that was overwritten
+ $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
+ }
+
+ public function testClosesBothStreams()
+ {
+ $s = fopen('php://temp', 'r');
+ $a = Psr7\stream_for($s);
+ $d = new CachingStream($a);
+ $d->close();
+ $this->assertFalse(is_resource($s));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsuresValidWhence()
+ {
+ $this->body->seek(10, -123456);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/DroppingStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/DroppingStreamTest.php
new file mode 100755
index 0000000..1ae9443
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/DroppingStreamTest.php
@@ -0,0 +1,26 @@
+assertEquals(3, $drop->write('hel'));
+ $this->assertEquals(2, $drop->write('lo'));
+ $this->assertEquals(5, $drop->getSize());
+ $this->assertEquals('hello', $drop->read(5));
+ $this->assertEquals(0, $drop->getSize());
+ $drop->write('12345678910');
+ $this->assertEquals(5, $stream->getSize());
+ $this->assertEquals(5, $drop->getSize());
+ $this->assertEquals('12345', (string) $drop);
+ $this->assertEquals(0, $drop->getSize());
+ $drop->write('hello');
+ $this->assertSame(0, $drop->write('test'));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/FnStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/FnStreamTest.php
new file mode 100755
index 0000000..def436c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/FnStreamTest.php
@@ -0,0 +1,92 @@
+seek(1);
+ }
+
+ public function testProxiesToFunction()
+ {
+ $self = $this;
+ $s = new FnStream(array(
+ 'read' => function ($len) use ($self) {
+ $self->assertEquals(3, $len);
+ return 'foo';
+ }
+ ));
+
+ $this->assertEquals('foo', $s->read(3));
+ }
+
+ public function testCanCloseOnDestruct()
+ {
+ $called = false;
+ $s = new FnStream(array(
+ 'close' => function () use (&$called) {
+ $called = true;
+ }
+ ));
+ unset($s);
+ $this->assertTrue($called);
+ }
+
+ public function testDoesNotRequireClose()
+ {
+ $s = new FnStream(array());
+ unset($s);
+ }
+
+ public function testDecoratesStream()
+ {
+ $a = Psr7\stream_for('foo');
+ $b = FnStream::decorate($a, array());
+ $this->assertEquals(3, $b->getSize());
+ $this->assertEquals($b->isWritable(), true);
+ $this->assertEquals($b->isReadable(), true);
+ $this->assertEquals($b->isSeekable(), true);
+ $this->assertEquals($b->read(3), 'foo');
+ $this->assertEquals($b->tell(), 3);
+ $this->assertEquals($a->tell(), 3);
+ $this->assertSame('', $a->read(1));
+ $this->assertEquals($b->eof(), true);
+ $this->assertEquals($a->eof(), true);
+ $b->seek(0);
+ $this->assertEquals('foo', (string) $b);
+ $b->seek(0);
+ $this->assertEquals('foo', $b->getContents());
+ $this->assertEquals($a->getMetadata(), $b->getMetadata());
+ $b->seek(0, SEEK_END);
+ $b->write('bar');
+ $this->assertEquals('foobar', (string) $b);
+ $this->assertInternalType('resource', $b->detach());
+ $b->close();
+ }
+
+ public function testDecoratesWithCustomizations()
+ {
+ $called = false;
+ $a = Psr7\stream_for('foo');
+ $b = FnStream::decorate($a, array(
+ 'read' => function ($len) use (&$called, $a) {
+ $called = true;
+ return $a->read($len);
+ }
+ ));
+ $this->assertEquals('foo', $b->read(3));
+ $this->assertTrue($called);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/FunctionsTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/FunctionsTest.php
new file mode 100755
index 0000000..032fc56
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/FunctionsTest.php
@@ -0,0 +1,604 @@
+assertEquals('foobaz', Psr7\copy_to_string($s));
+ $s->seek(0);
+ $this->assertEquals('foo', Psr7\copy_to_string($s, 3));
+ $this->assertEquals('baz', Psr7\copy_to_string($s, 3));
+ $this->assertEquals('', Psr7\copy_to_string($s));
+ }
+
+ public function testCopiesToStringStopsWhenReadFails()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s1 = FnStream::decorate($s1, array(
+ 'read' => function () { return ''; }
+ ));
+ $result = Psr7\copy_to_string($s1);
+ $this->assertEquals('', $result);
+ }
+
+ public function testCopiesToStream()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s2 = Psr7\stream_for('');
+ Psr7\copy_to_stream($s1, $s2);
+ $this->assertEquals('foobaz', (string) $s2);
+ $s2 = Psr7\stream_for('');
+ $s1->seek(0);
+ Psr7\copy_to_stream($s1, $s2, 3);
+ $this->assertEquals('foo', (string) $s2);
+ Psr7\copy_to_stream($s1, $s2, 3);
+ $this->assertEquals('foobaz', (string) $s2);
+ }
+
+ public function testStopsCopyToStreamWhenWriteFails()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s2 = Psr7\stream_for('');
+ $s2 = FnStream::decorate($s2, array('write' => function () { return 0; }));
+ Psr7\copy_to_stream($s1, $s2);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testStopsCopyToSteamWhenWriteFailsWithMaxLen()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s2 = Psr7\stream_for('');
+ $s2 = FnStream::decorate($s2, array('write' => function () { return 0; }));
+ Psr7\copy_to_stream($s1, $s2, 10);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testStopsCopyToSteamWhenReadFailsWithMaxLen()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s1 = FnStream::decorate($s1, array('read' => function () { return ''; }));
+ $s2 = Psr7\stream_for('');
+ Psr7\copy_to_stream($s1, $s2, 10);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testReadsLines()
+ {
+ $s = Psr7\stream_for("foo\nbaz\nbar");
+ $this->assertEquals("foo\n", Psr7\readline($s));
+ $this->assertEquals("baz\n", Psr7\readline($s));
+ $this->assertEquals("bar", Psr7\readline($s));
+ }
+
+ public function testReadsLinesUpToMaxLength()
+ {
+ $s = Psr7\stream_for("12345\n");
+ $this->assertEquals("123", Psr7\readline($s, 4));
+ $this->assertEquals("45\n", Psr7\readline($s));
+ }
+
+ public function testReadsLineUntilFalseReturnedFromRead()
+ {
+ $s = $this->getMockBuilder('RingCentral\Psr7\Stream')
+ ->setMethods(array('read', 'eof'))
+ ->disableOriginalConstructor()
+ ->getMock();
+ $s->expects($this->exactly(2))
+ ->method('read')
+ ->will($this->returnCallback(function () {
+ static $c = false;
+ if ($c) {
+ return false;
+ }
+ $c = true;
+ return 'h';
+ }));
+ $s->expects($this->exactly(2))
+ ->method('eof')
+ ->will($this->returnValue(false));
+ $this->assertEquals("h", Psr7\readline($s));
+ }
+
+ public function testCalculatesHash()
+ {
+ $s = Psr7\stream_for('foobazbar');
+ $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testCalculatesHashThrowsWhenSeekFails()
+ {
+ $s = new NoSeekStream(Psr7\stream_for('foobazbar'));
+ $s->read(2);
+ Psr7\hash($s, 'md5');
+ }
+
+ public function testCalculatesHashSeeksToOriginalPosition()
+ {
+ $s = Psr7\stream_for('foobazbar');
+ $s->seek(4);
+ $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
+ $this->assertEquals(4, $s->tell());
+ }
+
+ public function testOpensFilesSuccessfully()
+ {
+ $r = Psr7\try_fopen(__FILE__, 'r');
+ $this->assertInternalType('resource', $r);
+ fclose($r);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r
+ */
+ public function testThrowsExceptionNotWarning()
+ {
+ Psr7\try_fopen('/path/to/does/not/exist', 'r');
+ }
+
+ public function parseQueryProvider()
+ {
+ return array(
+ // Does not need to parse when the string is empty
+ array('', array()),
+ // Can parse mult-values items
+ array('q=a&q=b', array('q' => array('a', 'b'))),
+ // Can parse multi-valued items that use numeric indices
+ array('q[0]=a&q[1]=b', array('q[0]' => 'a', 'q[1]' => 'b')),
+ // Can parse duplicates and does not include numeric indices
+ array('q[]=a&q[]=b', array('q[]' => array('a', 'b'))),
+ // Ensures that the value of "q" is an array even though one value
+ array('q[]=a', array('q[]' => 'a')),
+ // Does not modify "." to "_" like PHP's parse_str()
+ array('q.a=a&q.b=b', array('q.a' => 'a', 'q.b' => 'b')),
+ // Can decode %20 to " "
+ array('q%20a=a%20b', array('q a' => 'a b')),
+ // Can parse funky strings with no values by assigning each to null
+ array('q&a', array('q' => null, 'a' => null)),
+ // Does not strip trailing equal signs
+ array('data=abc=', array('data' => 'abc=')),
+ // Can store duplicates without affecting other values
+ array('foo=a&foo=b&?µ=c', array('foo' => array('a', 'b'), '?µ' => 'c')),
+ // Sets value to null when no "=" is present
+ array('foo', array('foo' => null)),
+ // Preserves "0" keys.
+ array('0', array('0' => null)),
+ // Sets the value to an empty string when "=" is present
+ array('0=', array('0' => '')),
+ // Preserves falsey keys
+ array('var=0', array('var' => '0')),
+ array('a[b][c]=1&a[b][c]=2', array('a[b][c]' => array('1', '2'))),
+ array('a[b]=c&a[d]=e', array('a[b]' => 'c', 'a[d]' => 'e')),
+ // Ensure it doesn't leave things behind with repeated values
+ // Can parse mult-values items
+ array('q=a&q=b&q=c', array('q' => array('a', 'b', 'c'))),
+ );
+ }
+
+ /**
+ * @dataProvider parseQueryProvider
+ */
+ public function testParsesQueries($input, $output)
+ {
+ $result = Psr7\parse_query($input);
+ $this->assertSame($output, $result);
+ }
+
+ public function testDoesNotDecode()
+ {
+ $str = 'foo%20=bar';
+ $data = Psr7\parse_query($str, false);
+ $this->assertEquals(array('foo%20' => 'bar'), $data);
+ }
+
+ /**
+ * @dataProvider parseQueryProvider
+ */
+ public function testParsesAndBuildsQueries($input, $output)
+ {
+ $result = Psr7\parse_query($input, false);
+ $this->assertSame($input, Psr7\build_query($result, false));
+ }
+
+ public function testEncodesWithRfc1738()
+ {
+ $str = Psr7\build_query(array('foo bar' => 'baz+'), PHP_QUERY_RFC1738);
+ $this->assertEquals('foo+bar=baz%2B', $str);
+ }
+
+ public function testEncodesWithRfc3986()
+ {
+ $str = Psr7\build_query(array('foo bar' => 'baz+'), PHP_QUERY_RFC3986);
+ $this->assertEquals('foo%20bar=baz%2B', $str);
+ }
+
+ public function testDoesNotEncode()
+ {
+ $str = Psr7\build_query(array('foo bar' => 'baz+'), false);
+ $this->assertEquals('foo bar=baz+', $str);
+ }
+
+ public function testCanControlDecodingType()
+ {
+ $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC3986);
+ $this->assertEquals('foo+bar', $result['var']);
+ $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC1738);
+ $this->assertEquals('foo bar', $result['var']);
+ }
+
+ public function testParsesRequestMessages()
+ {
+ $req = "GET /abc HTTP/1.0\r\nHost: foo.com\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('GET', $request->getMethod());
+ $this->assertEquals('/abc', $request->getRequestTarget());
+ $this->assertEquals('1.0', $request->getProtocolVersion());
+ $this->assertEquals('foo.com', $request->getHeaderLine('Host'));
+ $this->assertEquals('Bar', $request->getHeaderLine('Foo'));
+ $this->assertEquals('Bam, Qux', $request->getHeaderLine('Baz'));
+ $this->assertEquals('Test', (string) $request->getBody());
+ $this->assertEquals('http://foo.com/abc', (string) $request->getUri());
+ }
+
+ public function testParsesRequestMessagesWithHttpsScheme()
+ {
+ $req = "PUT /abc?baz=bar HTTP/1.1\r\nHost: foo.com:443\r\n\r\n";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('/abc?baz=bar', $request->getRequestTarget());
+ $this->assertEquals('1.1', $request->getProtocolVersion());
+ $this->assertEquals('foo.com:443', $request->getHeaderLine('Host'));
+ $this->assertEquals('', (string) $request->getBody());
+ $this->assertEquals('https://foo.com/abc?baz=bar', (string) $request->getUri());
+ }
+
+ public function testParsesRequestMessagesWithUriWhenHostIsNotFirst()
+ {
+ $req = "PUT / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('/', $request->getRequestTarget());
+ $this->assertEquals('http://foo.com/', (string) $request->getUri());
+ }
+
+ public function testParsesRequestMessagesWithFullUri()
+ {
+ $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('GET', $request->getMethod());
+ $this->assertEquals('https://www.google.com:443/search?q=foobar', $request->getRequestTarget());
+ $this->assertEquals('1.1', $request->getProtocolVersion());
+ $this->assertEquals('www.google.com', $request->getHeaderLine('Host'));
+ $this->assertEquals('', (string) $request->getBody());
+ $this->assertEquals('https://www.google.com/search?q=foobar', (string) $request->getUri());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesRequestMessages()
+ {
+ Psr7\parse_request("HTTP/1.1 200 OK\r\n\r\n");
+ }
+
+ public function testParsesResponseMessages()
+ {
+ $res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
+ $response = Psr7\parse_response($res);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals('OK', $response->getReasonPhrase());
+ $this->assertEquals('1.0', $response->getProtocolVersion());
+ $this->assertEquals('Bar', $response->getHeaderLine('Foo'));
+ $this->assertEquals('Bam, Qux', $response->getHeaderLine('Baz'));
+ $this->assertEquals('Test', (string) $response->getBody());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesResponseMessages()
+ {
+ Psr7\parse_response("GET / HTTP/1.1\r\n\r\n");
+ }
+
+ public function testDetermineMimetype()
+ {
+ $this->assertNull(Psr7\mimetype_from_extension('not-a-real-extension'));
+ $this->assertEquals(
+ 'application/json',
+ Psr7\mimetype_from_extension('json')
+ );
+ $this->assertEquals(
+ 'image/jpeg',
+ Psr7\mimetype_from_filename('/tmp/images/IMG034821.JPEG')
+ );
+ }
+
+ public function testCreatesUriForValue()
+ {
+ $this->assertInstanceOf('RingCentral\Psr7\Uri', Psr7\uri_for('/foo'));
+ $this->assertInstanceOf(
+ 'RingCentral\Psr7\Uri',
+ Psr7\uri_for(new Psr7\Uri('/foo'))
+ );
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesUri()
+ {
+ Psr7\uri_for(array());
+ }
+
+ public function testKeepsPositionOfResource()
+ {
+ $h = fopen(__FILE__, 'r');
+ fseek($h, 10);
+ $stream = Psr7\stream_for($h);
+ $this->assertEquals(10, $stream->tell());
+ $stream->close();
+ }
+
+ public function testCreatesWithFactory()
+ {
+ $stream = Psr7\stream_for('foo');
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $stream);
+ $this->assertEquals('foo', $stream->getContents());
+ $stream->close();
+ }
+
+ public function testFactoryCreatesFromEmptyString()
+ {
+ $s = Psr7\stream_for();
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ }
+
+ public function testFactoryCreatesFromNull()
+ {
+ $s = Psr7\stream_for(null);
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ }
+
+ public function testFactoryCreatesFromResource()
+ {
+ $r = fopen(__FILE__, 'r');
+ $s = Psr7\stream_for($r);
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ $this->assertSame(file_get_contents(__FILE__), (string) $s);
+ }
+
+ public function testFactoryCreatesFromObjectWithToString()
+ {
+ $r = new HasToString();
+ $s = Psr7\stream_for($r);
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ $this->assertEquals('foo', (string) $s);
+ }
+
+ public function testCreatePassesThrough()
+ {
+ $s = Psr7\stream_for('foo');
+ $this->assertSame($s, Psr7\stream_for($s));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testThrowsExceptionForUnknown()
+ {
+ Psr7\stream_for(new \stdClass());
+ }
+
+ public function testReturnsCustomMetadata()
+ {
+ $s = Psr7\stream_for('foo', array('metadata' => array('hwm' => 3)));
+ $this->assertEquals(3, $s->getMetadata('hwm'));
+ $this->assertArrayHasKey('hwm', $s->getMetadata());
+ }
+
+ public function testCanSetSize()
+ {
+ $s = Psr7\stream_for('', array('size' => 10));
+ $this->assertEquals(10, $s->getSize());
+ }
+
+ public function testCanCreateIteratorBasedStream()
+ {
+ $a = new \ArrayIterator(array('foo', 'bar', '123'));
+ $p = Psr7\stream_for($a);
+ $this->assertInstanceOf('RingCentral\Psr7\PumpStream', $p);
+ $this->assertEquals('foo', $p->read(3));
+ $this->assertFalse($p->eof());
+ $this->assertEquals('b', $p->read(1));
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals('r12', $p->read(3));
+ $this->assertFalse($p->eof());
+ $this->assertEquals('3', $p->getContents());
+ $this->assertTrue($p->eof());
+ $this->assertEquals(9, $p->tell());
+ }
+
+ public function testConvertsRequestsToStrings()
+ {
+ $request = new Psr7\Request('PUT', 'http://foo.com/hi?123', array(
+ 'Baz' => 'bar',
+ 'Qux' => ' ipsum'
+ ), 'hello', '1.0');
+ $this->assertEquals(
+ "PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
+ Psr7\str($request)
+ );
+ }
+
+ public function testConvertsResponsesToStrings()
+ {
+ $response = new Psr7\Response(200, array(
+ 'Baz' => 'bar',
+ 'Qux' => ' ipsum'
+ ), 'hello', '1.0', 'FOO');
+ $this->assertEquals(
+ "HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
+ Psr7\str($response)
+ );
+ }
+
+ public function parseParamsProvider()
+ {
+ $res1 = array(
+ array(
+ '',
+ 'rel' => 'front',
+ 'type' => 'image/jpeg',
+ ),
+ array(
+ '',
+ 'rel' => 'back',
+ 'type' => 'image/jpeg',
+ ),
+ );
+ return array(
+ array(
+ '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"',
+ $res1
+ ),
+ array(
+ '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"',
+ $res1
+ ),
+ array(
+ 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"',
+ array(
+ array('foo' => 'baz', 'bar' => '123'),
+ array('boo'),
+ array('test' => '123'),
+ array('foobar' => 'foo;bar')
+ )
+ ),
+ array(
+ '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"',
+ array(
+ array('', 'rel' => 'side', 'type' => 'image/jpeg'),
+ array('', 'rel' => 'side', 'type' => 'image/jpeg')
+ )
+ ),
+ array(
+ '',
+ array()
+ )
+ );
+ }
+ /**
+ * @dataProvider parseParamsProvider
+ */
+ public function testParseParams($header, $result)
+ {
+ $this->assertEquals($result, Psr7\parse_header($header));
+ }
+
+ public function testParsesArrayHeaders()
+ {
+ $header = array('a, b', 'c', 'd, e');
+ $this->assertEquals(array('a', 'b', 'c', 'd', 'e'), Psr7\normalize_header($header));
+ }
+
+ public function testRewindsBody()
+ {
+ $body = Psr7\stream_for('abc');
+ $res = new Psr7\Response(200, array(), $body);
+ Psr7\rewind_body($res);
+ $this->assertEquals(0, $body->tell());
+ $body->rewind(1);
+ Psr7\rewind_body($res);
+ $this->assertEquals(0, $body->tell());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testThrowsWhenBodyCannotBeRewound()
+ {
+ $body = Psr7\stream_for('abc');
+ $body->read(1);
+ $body = FnStream::decorate($body, array(
+ 'rewind' => function () { throw new \RuntimeException('a'); }
+ ));
+ $res = new Psr7\Response(200, array(), $body);
+ Psr7\rewind_body($res);
+ }
+
+ public function testCanModifyRequestWithUri()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com');
+ $r2 = Psr7\modify_request($r1, array(
+ 'uri' => new Psr7\Uri('http://www.foo.com')
+ ));
+ $this->assertEquals('http://www.foo.com', (string) $r2->getUri());
+ $this->assertEquals('www.foo.com', (string) $r2->getHeaderLine('host'));
+ }
+
+ public function testCanModifyRequestWithCaseInsensitiveHeader()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com', array('User-Agent' => 'foo'));
+ $r2 = Psr7\modify_request($r1, array('set_headers' => array('User-agent' => 'bar')));
+ $this->assertEquals('bar', $r2->getHeaderLine('User-Agent'));
+ $this->assertEquals('bar', $r2->getHeaderLine('User-agent'));
+ }
+
+ public function testReturnsAsIsWhenNoChanges()
+ {
+ $request = new Psr7\Request('GET', 'http://foo.com');
+ $this->assertSame($request, Psr7\modify_request($request, array()));
+ }
+
+ public function testReturnsUriAsIsWhenNoChanges()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com');
+ $r2 = Psr7\modify_request($r1, array('set_headers' => array('foo' => 'bar')));
+ $this->assertNotSame($r1, $r2);
+ $this->assertEquals('bar', $r2->getHeaderLine('foo'));
+ }
+
+ public function testRemovesHeadersFromMessage()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com', array('foo' => 'bar'));
+ $r2 = Psr7\modify_request($r1, array('remove_headers' => array('foo')));
+ $this->assertNotSame($r1, $r2);
+ $this->assertFalse($r2->hasHeader('foo'));
+ }
+
+ public function testAddsQueryToUri()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com');
+ $r2 = Psr7\modify_request($r1, array('query' => 'foo=bar'));
+ $this->assertNotSame($r1, $r2);
+ $this->assertEquals('foo=bar', $r2->getUri()->getQuery());
+ }
+
+ public function testServerRequestWithServerParams()
+ {
+ $requestString = "GET /abc HTTP/1.1\r\nHost: foo.com\r\n\r\n";
+ $request = Psr7\parse_server_request($requestString);
+
+ $this->assertEquals(array(), $request->getServerParams());
+ }
+
+ public function testServerRequestWithoutServerParams()
+ {
+ $requestString = "GET /abc HTTP/1.1\r\nHost: foo.com\r\n\r\n";
+ $serverParams = array('server_address' => '127.0.0.1', 'server_port' => 80);
+
+ $request = Psr7\parse_server_request($requestString, $serverParams);
+
+ $this->assertEquals(array('server_address' => '127.0.0.1', 'server_port' => 80), $request->getServerParams());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/InflateStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/InflateStreamTest.php
new file mode 100755
index 0000000..cd699eb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/InflateStreamTest.php
@@ -0,0 +1,21 @@
+assertEquals('test', (string) $b);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/LazyOpenStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/LazyOpenStreamTest.php
new file mode 100755
index 0000000..ca0c18e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/LazyOpenStreamTest.php
@@ -0,0 +1,64 @@
+fname = tempnam('/tmp', 'tfile');
+
+ if (file_exists($this->fname)) {
+ unlink($this->fname);
+ }
+ }
+
+ public function tearDown()
+ {
+ if (file_exists($this->fname)) {
+ unlink($this->fname);
+ }
+ }
+
+ public function testOpensLazily()
+ {
+ $l = new LazyOpenStream($this->fname, 'w+');
+ $l->write('foo');
+ $this->assertInternalType('array', $l->getMetadata());
+ $this->assertFileExists($this->fname);
+ $this->assertEquals('foo', file_get_contents($this->fname));
+ $this->assertEquals('foo', (string) $l);
+ }
+
+ public function testProxiesToFile()
+ {
+ file_put_contents($this->fname, 'foo');
+ $l = new LazyOpenStream($this->fname, 'r');
+ $this->assertEquals('foo', $l->read(4));
+ $this->assertTrue($l->eof());
+ $this->assertEquals(3, $l->tell());
+ $this->assertTrue($l->isReadable());
+ $this->assertTrue($l->isSeekable());
+ $this->assertFalse($l->isWritable());
+ $l->seek(1);
+ $this->assertEquals('oo', $l->getContents());
+ $this->assertEquals('foo', (string) $l);
+ $this->assertEquals(3, $l->getSize());
+ $this->assertInternalType('array', $l->getMetadata());
+ $l->close();
+ }
+
+ public function testDetachesUnderlyingStream()
+ {
+ file_put_contents($this->fname, 'foo');
+ $l = new LazyOpenStream($this->fname, 'r');
+ $r = $l->detach();
+ $this->assertInternalType('resource', $r);
+ fseek($r, 0);
+ $this->assertEquals('foo', stream_get_contents($r));
+ fclose($r);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/LimitStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/LimitStreamTest.php
new file mode 100755
index 0000000..7053300
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/LimitStreamTest.php
@@ -0,0 +1,166 @@
+decorated = Psr7\stream_for(fopen(__FILE__, 'r'));
+ $this->body = new LimitStream($this->decorated, 10, 3);
+ }
+
+ public function testReturnsSubset()
+ {
+ $body = new LimitStream(Psr7\stream_for('foo'), -1, 1);
+ $this->assertEquals('oo', (string) $body);
+ $this->assertTrue($body->eof());
+ $body->seek(0);
+ $this->assertFalse($body->eof());
+ $this->assertEquals('oo', $body->read(100));
+ $this->assertSame('', $body->read(1));
+ $this->assertTrue($body->eof());
+ }
+
+ public function testReturnsSubsetWhenCastToString()
+ {
+ $body = Psr7\stream_for('foo_baz_bar');
+ $limited = new LimitStream($body, 3, 4);
+ $this->assertEquals('baz', (string) $limited);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to seek to stream position 10 with whence 0
+ */
+ public function testEnsuresPositionCanBeekSeekedTo()
+ {
+ new LimitStream(Psr7\stream_for(''), 0, 10);
+ }
+
+ public function testReturnsSubsetOfEmptyBodyWhenCastToString()
+ {
+ $body = Psr7\stream_for('01234567891234');
+ $limited = new LimitStream($body, 0, 10);
+ $this->assertEquals('', (string) $limited);
+ }
+
+ public function testReturnsSpecificSubsetOBodyWhenCastToString()
+ {
+ $body = Psr7\stream_for('0123456789abcdef');
+ $limited = new LimitStream($body, 3, 10);
+ $this->assertEquals('abc', (string) $limited);
+ }
+
+ public function testSeeksWhenConstructed()
+ {
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ }
+
+ public function testAllowsBoundedSeek()
+ {
+ $this->body->seek(100);
+ $this->assertEquals(10, $this->body->tell());
+ $this->assertEquals(13, $this->decorated->tell());
+ $this->body->seek(0);
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ try {
+ $this->body->seek(-10);
+ $this->fail();
+ } catch (\RuntimeException $e) {}
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ $this->body->seek(5);
+ $this->assertEquals(5, $this->body->tell());
+ $this->assertEquals(8, $this->decorated->tell());
+ // Fail
+ try {
+ $this->body->seek(1000, SEEK_END);
+ $this->fail();
+ } catch (\RuntimeException $e) {}
+ }
+
+ public function testReadsOnlySubsetOfData()
+ {
+ $data = $this->body->read(100);
+ $this->assertEquals(10, strlen($data));
+ $this->assertSame('', $this->body->read(1000));
+
+ $this->body->setOffset(10);
+ $newData = $this->body->read(100);
+ $this->assertEquals(10, strlen($newData));
+ $this->assertNotSame($data, $newData);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Could not seek to stream offset 2
+ */
+ public function testThrowsWhenCurrentGreaterThanOffsetSeek()
+ {
+ $a = Psr7\stream_for('foo_bar');
+ $b = new NoSeekStream($a);
+ $c = new LimitStream($b);
+ $a->getContents();
+ $c->setOffset(2);
+ }
+
+ public function testCanGetContentsWithoutSeeking()
+ {
+ $a = Psr7\stream_for('foo_bar');
+ $b = new NoSeekStream($a);
+ $c = new LimitStream($b);
+ $this->assertEquals('foo_bar', $c->getContents());
+ }
+
+ public function testClaimsConsumedWhenReadLimitIsReached()
+ {
+ $this->assertFalse($this->body->eof());
+ $this->body->read(1000);
+ $this->assertTrue($this->body->eof());
+ }
+
+ public function testContentLengthIsBounded()
+ {
+ $this->assertEquals(10, $this->body->getSize());
+ }
+
+ public function testGetContentsIsBasedOnSubset()
+ {
+ $body = new LimitStream(Psr7\stream_for('foobazbar'), 3, 3);
+ $this->assertEquals('baz', $body->getContents());
+ }
+
+ public function testReturnsNullIfSizeCannotBeDetermined()
+ {
+ $a = new FnStream(array(
+ 'getSize' => function () { return null; },
+ 'tell' => function () { return 0; },
+ ));
+ $b = new LimitStream($a);
+ $this->assertNull($b->getSize());
+ }
+
+ public function testLengthLessOffsetWhenNoLimitSize()
+ {
+ $a = Psr7\stream_for('foo_bar');
+ $b = new LimitStream($a, -1, 4);
+ $this->assertEquals(3, $b->getSize());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/MultipartStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/MultipartStreamTest.php
new file mode 100755
index 0000000..22edea4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/MultipartStreamTest.php
@@ -0,0 +1,214 @@
+assertNotEmpty($b->getBoundary());
+ }
+
+ public function testCanProvideBoundary()
+ {
+ $b = new MultipartStream(array(), 'foo');
+ $this->assertEquals('foo', $b->getBoundary());
+ }
+
+ public function testIsNotWritable()
+ {
+ $b = new MultipartStream();
+ $this->assertFalse($b->isWritable());
+ }
+
+ public function testCanCreateEmptyStream()
+ {
+ $b = new MultipartStream();
+ $boundary = $b->getBoundary();
+ $this->assertSame("--{$boundary}--\r\n", $b->getContents());
+ $this->assertSame(strlen($boundary) + 6, $b->getSize());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesFilesArrayElement()
+ {
+ new MultipartStream(array(array('foo' => 'bar')));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsuresFileHasName()
+ {
+ new MultipartStream(array(array('contents' => 'bar')));
+ }
+
+ public function testSerializesFields()
+ {
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => 'bar'
+ ),
+ array(
+ 'name' => 'baz',
+ 'contents' => 'bam'
+ )
+ ), 'boundary');
+ $this->assertEquals(
+ "--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\nContent-Length: 3\r\n\r\n"
+ . "bar\r\n--boundary\r\nContent-Disposition: form-data; name=\"baz\"\r\nContent-Length: 3"
+ . "\r\n\r\nbam\r\n--boundary--\r\n", (string) $b);
+ }
+
+ public function testSerializesFiles()
+ {
+ $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.txt';
+ }
+ ));
+
+ $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), array(
+ 'getMetadata' => function () {
+ return '/foo/baz.jpg';
+ }
+ ));
+
+ $f3 = Psr7\FnStream::decorate(Psr7\stream_for('bar'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.gif';
+ }
+ ));
+
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => $f1
+ ),
+ array(
+ 'name' => 'qux',
+ 'contents' => $f2
+ ),
+ array(
+ 'name' => 'qux',
+ 'contents' => $f3
+ ),
+ ), 'boundary');
+
+ $expected = <<assertEquals($expected, str_replace("\r", '', $b));
+ }
+
+ public function testSerializesFilesWithCustomHeaders()
+ {
+ $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.txt';
+ }
+ ));
+
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => $f1,
+ 'headers' => array(
+ 'x-foo' => 'bar',
+ 'content-disposition' => 'custom'
+ )
+ )
+ ), 'boundary');
+
+ $expected = <<assertEquals($expected, str_replace("\r", '', $b));
+ }
+
+ public function testSerializesFilesWithCustomHeadersAndMultipleValues()
+ {
+ $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.txt';
+ }
+ ));
+
+ $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), array(
+ 'getMetadata' => function () {
+ return '/foo/baz.jpg';
+ }
+ ));
+
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => $f1,
+ 'headers' => array(
+ 'x-foo' => 'bar',
+ 'content-disposition' => 'custom'
+ )
+ ),
+ array(
+ 'name' => 'foo',
+ 'contents' => $f2,
+ 'headers' => array('cOntenT-Type' => 'custom'),
+ )
+ ), 'boundary');
+
+ $expected = <<assertEquals($expected, str_replace("\r", '', $b));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/NoSeekStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/NoSeekStreamTest.php
new file mode 100755
index 0000000..a831789
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/NoSeekStreamTest.php
@@ -0,0 +1,40 @@
+getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isSeekable', 'seek'))
+ ->getMockForAbstractClass();
+ $s->expects($this->never())->method('seek');
+ $s->expects($this->never())->method('isSeekable');
+ $wrapped = new NoSeekStream($s);
+ $this->assertFalse($wrapped->isSeekable());
+ $wrapped->seek(2);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Cannot write to a non-writable stream
+ */
+ public function testHandlesClose()
+ {
+ $s = Psr7\stream_for('foo');
+ $wrapped = new NoSeekStream($s);
+ $wrapped->close();
+ $wrapped->write('foo');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/PumpStreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/PumpStreamTest.php
new file mode 100755
index 0000000..6b146e1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/PumpStreamTest.php
@@ -0,0 +1,72 @@
+ array('foo' => 'bar'),
+ 'size' => 100
+ ));
+
+ $this->assertEquals('bar', $p->getMetadata('foo'));
+ $this->assertEquals(array('foo' => 'bar'), $p->getMetadata());
+ $this->assertEquals(100, $p->getSize());
+ }
+
+ public function testCanReadFromCallable()
+ {
+ $p = Psr7\stream_for(function ($size) {
+ return 'a';
+ });
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals(1, $p->tell());
+ $this->assertEquals('aaaaa', $p->read(5));
+ $this->assertEquals(6, $p->tell());
+ }
+
+ public function testStoresExcessDataInBuffer()
+ {
+ $called = array();
+ $p = Psr7\stream_for(function ($size) use (&$called) {
+ $called[] = $size;
+ return 'abcdef';
+ });
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals('b', $p->read(1));
+ $this->assertEquals('cdef', $p->read(4));
+ $this->assertEquals('abcdefabc', $p->read(9));
+ $this->assertEquals(array(1, 9, 3), $called);
+ }
+
+ public function testInifiniteStreamWrappedInLimitStream()
+ {
+ $p = Psr7\stream_for(function () { return 'a'; });
+ $s = new LimitStream($p, 5);
+ $this->assertEquals('aaaaa', (string) $s);
+ }
+
+ public function testDescribesCapabilities()
+ {
+ $p = Psr7\stream_for(function () {});
+ $this->assertTrue($p->isReadable());
+ $this->assertFalse($p->isSeekable());
+ $this->assertFalse($p->isWritable());
+ $this->assertNull($p->getSize());
+ $this->assertEquals('', $p->getContents());
+ $this->assertEquals('', (string) $p);
+ $p->close();
+ $this->assertEquals('', $p->read(10));
+ $this->assertTrue($p->eof());
+
+ try {
+ $this->assertFalse($p->write('aa'));
+ $this->fail();
+ } catch (\RuntimeException $e) {}
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/RequestTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/RequestTest.php
new file mode 100755
index 0000000..ad6f0cb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/RequestTest.php
@@ -0,0 +1,157 @@
+assertEquals('/', (string) $r->getUri());
+ }
+
+ public function testRequestUriMayBeUri()
+ {
+ $uri = new Uri('/');
+ $r = new Request('GET', $uri);
+ $this->assertSame($uri, $r->getUri());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidateRequestUri()
+ {
+ new Request('GET', true);
+ }
+
+ public function testCanConstructWithBody()
+ {
+ $r = new Request('GET', '/', array(), 'baz');
+ $this->assertEquals('baz', (string) $r->getBody());
+ }
+
+ public function testCapitalizesMethod()
+ {
+ $r = new Request('get', '/');
+ $this->assertEquals('GET', $r->getMethod());
+ }
+
+ public function testCapitalizesWithMethod()
+ {
+ $r = new Request('GET', '/');
+ $this->assertEquals('PUT', $r->withMethod('put')->getMethod());
+ }
+
+ public function testWithUri()
+ {
+ $r1 = new Request('GET', '/');
+ $u1 = $r1->getUri();
+ $u2 = new Uri('http://www.example.com');
+ $r2 = $r1->withUri($u2);
+ $this->assertNotSame($r1, $r2);
+ $this->assertSame($u2, $r2->getUri());
+ $this->assertSame($u1, $r1->getUri());
+ }
+
+ public function testSameInstanceWhenSameUri()
+ {
+ $r1 = new Request('GET', 'http://foo.com');
+ $r2 = $r1->withUri($r1->getUri());
+ $this->assertSame($r1, $r2);
+ }
+
+ public function testWithRequestTarget()
+ {
+ $r1 = new Request('GET', '/');
+ $r2 = $r1->withRequestTarget('*');
+ $this->assertEquals('*', $r2->getRequestTarget());
+ $this->assertEquals('/', $r1->getRequestTarget());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testRequestTargetDoesNotAllowSpaces()
+ {
+ $r1 = new Request('GET', '/');
+ $r1->withRequestTarget('/foo bar');
+ }
+
+ public function testRequestTargetDefaultsToSlash()
+ {
+ $r1 = new Request('GET', '');
+ $this->assertEquals('/', $r1->getRequestTarget());
+ $r2 = new Request('GET', '*');
+ $this->assertEquals('*', $r2->getRequestTarget());
+ $r3 = new Request('GET', 'http://foo.com/bar baz/');
+ $this->assertEquals('/bar%20baz/', $r3->getRequestTarget());
+ }
+
+ public function testBuildsRequestTarget()
+ {
+ $r1 = new Request('GET', 'http://foo.com/baz?bar=bam');
+ $this->assertEquals('/baz?bar=bam', $r1->getRequestTarget());
+ }
+
+ public function testHostIsAddedFirst()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam', array('Foo' => 'Bar'));
+ $this->assertEquals(array(
+ 'Host' => array('foo.com'),
+ 'Foo' => array('Bar')
+ ), $r->getHeaders());
+ }
+
+ public function testCanGetHeaderAsCsv()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam', array(
+ 'Foo' => array('a', 'b', 'c')
+ ));
+ $this->assertEquals('a, b, c', $r->getHeaderLine('Foo'));
+ $this->assertEquals('', $r->getHeaderLine('Bar'));
+ }
+
+ public function testHostIsNotOverwrittenWhenPreservingHost()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam', array('Host' => 'a.com'));
+ $this->assertEquals(array('Host' => array('a.com')), $r->getHeaders());
+ $r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true);
+ $this->assertEquals('a.com', $r2->getHeaderLine('Host'));
+ }
+
+ public function testOverridesHostWithUri()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam');
+ $this->assertEquals(array('Host' => array('foo.com')), $r->getHeaders());
+ $r2 = $r->withUri(new Uri('http://www.baz.com/bar'));
+ $this->assertEquals('www.baz.com', $r2->getHeaderLine('Host'));
+ }
+
+ public function testAggregatesHeaders()
+ {
+ $r = new Request('GET', 'http://foo.com', array(
+ 'ZOO' => 'zoobar',
+ 'zoo' => array('foobar', 'zoobar')
+ ));
+ $this->assertEquals('zoobar, foobar, zoobar', $r->getHeaderLine('zoo'));
+ }
+
+ public function testAddsPortToHeader()
+ {
+ $r = new Request('GET', 'http://foo.com:8124/bar');
+ $this->assertEquals('foo.com:8124', $r->getHeaderLine('host'));
+ }
+
+ public function testAddsPortToHeaderAndReplacePreviousPort()
+ {
+ $r = new Request('GET', 'http://foo.com:8124/bar');
+ $r = $r->withUri(new Uri('http://foo.com:8125/bar'));
+ $this->assertEquals('foo.com:8125', $r->getHeaderLine('host'));
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/ResponseTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/ResponseTest.php
new file mode 100755
index 0000000..52b7ba1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/ResponseTest.php
@@ -0,0 +1,154 @@
+assertSame(200, $r->getStatusCode());
+ $this->assertEquals('OK', $r->getReasonPhrase());
+ }
+
+ public function testCanGiveCustomReason()
+ {
+ $r = new Response(200, array(), null, '1.1', 'bar');
+ $this->assertEquals('bar', $r->getReasonPhrase());
+ }
+
+ public function testCanGiveCustomProtocolVersion()
+ {
+ $r = new Response(200, array(), null, '1000');
+ $this->assertEquals('1000', $r->getProtocolVersion());
+ }
+
+ public function testCanCreateNewResponseWithStatusAndNoReason()
+ {
+ $r = new Response(200);
+ $r2 = $r->withStatus(201);
+ $this->assertEquals(200, $r->getStatusCode());
+ $this->assertEquals('OK', $r->getReasonPhrase());
+ $this->assertEquals(201, $r2->getStatusCode());
+ $this->assertEquals('Created', $r2->getReasonPhrase());
+ }
+
+ public function testCanCreateNewResponseWithStatusAndReason()
+ {
+ $r = new Response(200);
+ $r2 = $r->withStatus(201, 'Foo');
+ $this->assertEquals(200, $r->getStatusCode());
+ $this->assertEquals('OK', $r->getReasonPhrase());
+ $this->assertEquals(201, $r2->getStatusCode());
+ $this->assertEquals('Foo', $r2->getReasonPhrase());
+ }
+
+ public function testCreatesResponseWithAddedHeaderArray()
+ {
+ $r = new Response();
+ $r2 = $r->withAddedHeader('foo', array('baz', 'bar'));
+ $this->assertFalse($r->hasHeader('foo'));
+ $this->assertEquals('baz, bar', $r2->getHeaderLine('foo'));
+ }
+
+ public function testReturnsIdentityWhenRemovingMissingHeader()
+ {
+ $r = new Response();
+ $this->assertSame($r, $r->withoutHeader('foo'));
+ }
+
+ public function testAlwaysReturnsBody()
+ {
+ $r = new Response();
+ $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
+ }
+
+ public function testCanSetHeaderAsArray()
+ {
+ $r = new Response(200, array(
+ 'foo' => array('baz ', ' bar ')
+ ));
+ $this->assertEquals('baz, bar', $r->getHeaderLine('foo'));
+ $this->assertEquals(array('baz', 'bar'), $r->getHeader('foo'));
+ }
+
+ public function testSameInstanceWhenSameBody()
+ {
+ $r = new Response(200, array(), 'foo');
+ $b = $r->getBody();
+ $this->assertSame($r, $r->withBody($b));
+ }
+
+ public function testNewInstanceWhenNewBody()
+ {
+ $r = new Response(200, array(), 'foo');
+ $b2 = Psr7\stream_for('abc');
+ $this->assertNotSame($r, $r->withBody($b2));
+ }
+
+ public function testSameInstanceWhenSameProtocol()
+ {
+ $r = new Response(200);
+ $this->assertSame($r, $r->withProtocolVersion('1.1'));
+ }
+
+ public function testNewInstanceWhenNewProtocol()
+ {
+ $r = new Response(200);
+ $this->assertNotSame($r, $r->withProtocolVersion('1.0'));
+ }
+
+ public function testNewInstanceWhenRemovingHeader()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withoutHeader('Foo');
+ $this->assertNotSame($r, $r2);
+ $this->assertFalse($r2->hasHeader('foo'));
+ }
+
+ public function testNewInstanceWhenAddingHeader()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withAddedHeader('Foo', 'Baz');
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals('Bar, Baz', $r2->getHeaderLine('foo'));
+ }
+
+ public function testNewInstanceWhenAddingHeaderArray()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withAddedHeader('Foo', array('Baz', 'Qux'));
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals(array('Bar', 'Baz', 'Qux'), $r2->getHeader('foo'));
+ }
+
+ public function testNewInstanceWhenAddingHeaderThatWasNotThereBefore()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withAddedHeader('Baz', 'Bam');
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals('Bam', $r2->getHeaderLine('Baz'));
+ $this->assertEquals('Bar', $r2->getHeaderLine('Foo'));
+ }
+
+ public function testRemovesPreviouslyAddedHeaderOfDifferentCase()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withHeader('foo', 'Bam');
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals('Bam', $r2->getHeaderLine('Foo'));
+ }
+
+ public function testBodyConsistent()
+ {
+ $r = new Response(200, array(), '0');
+ $this->assertEquals('0', (string)$r->getBody());
+ }
+
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/ServerRequestTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/ServerRequestTest.php
new file mode 100755
index 0000000..4bdfe8e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/ServerRequestTest.php
@@ -0,0 +1,85 @@
+request = new ServerRequest('GET', 'http://localhost');
+ }
+
+ public function testGetNoAttributes()
+ {
+ $this->assertEquals(array(), $this->request->getAttributes());
+ }
+
+ public function testWithAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('hello' => 'world'), $request->getAttributes());
+ }
+
+ public function testGetAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals('world', $request->getAttribute('hello'));
+ }
+
+ public function testGetDefaultAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(null, $request->getAttribute('hi', null));
+ }
+
+ public function testWithoutAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+ $request = $request->withAttribute('test', 'nice');
+
+ $request = $request->withoutAttribute('hello');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'nice'), $request->getAttributes());
+ }
+
+ public function testWithCookieParams()
+ {
+ $request = $this->request->withCookieParams(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getCookieParams());
+ }
+
+ public function testWithQueryParams()
+ {
+ $request = $this->request->withQueryParams(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getQueryParams());
+ }
+
+ public function testWithUploadedFiles()
+ {
+ $request = $this->request->withUploadedFiles(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getUploadedFiles());
+ }
+
+ public function testWithParsedBody()
+ {
+ $request = $this->request->withParsedBody(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getParsedBody());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/StreamDecoratorTraitTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/StreamDecoratorTraitTest.php
new file mode 100755
index 0000000..b0c3dc5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/StreamDecoratorTraitTest.php
@@ -0,0 +1,123 @@
+c = fopen('php://temp', 'r+');
+ fwrite($this->c, 'foo');
+ fseek($this->c, 0);
+ $this->a = Psr7\stream_for($this->c);
+ $this->b = new Str($this->a);
+ }
+
+ public function testCatchesExceptionsWhenCastingToString()
+ {
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('read'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('read')
+ ->will($this->throwException(new \Exception('foo')));
+ $msg = '';
+ set_error_handler(function ($errNo, $str) use (&$msg) {
+ $msg = $str;
+ });
+ echo new Str($s);
+ restore_error_handler();
+ $this->assertContains('foo', $msg);
+ }
+
+ public function testToString()
+ {
+ $this->assertEquals('foo', (string)$this->b);
+ }
+
+ public function testHasSize()
+ {
+ $this->assertEquals(3, $this->b->getSize());
+ }
+
+ public function testReads()
+ {
+ $this->assertEquals('foo', $this->b->read(10));
+ }
+
+ public function testCheckMethods()
+ {
+ $this->assertEquals($this->a->isReadable(), $this->b->isReadable());
+ $this->assertEquals($this->a->isWritable(), $this->b->isWritable());
+ $this->assertEquals($this->a->isSeekable(), $this->b->isSeekable());
+ }
+
+ public function testSeeksAndTells()
+ {
+ $this->b->seek(1);
+ $this->assertEquals(1, $this->a->tell());
+ $this->assertEquals(1, $this->b->tell());
+ $this->b->seek(0);
+ $this->assertEquals(0, $this->a->tell());
+ $this->assertEquals(0, $this->b->tell());
+ $this->b->seek(0, SEEK_END);
+ $this->assertEquals(3, $this->a->tell());
+ $this->assertEquals(3, $this->b->tell());
+ }
+
+ public function testGetsContents()
+ {
+ $this->assertEquals('foo', $this->b->getContents());
+ $this->assertEquals('', $this->b->getContents());
+ $this->b->seek(1);
+ $this->assertEquals('oo', $this->b->getContents(1));
+ }
+
+ public function testCloses()
+ {
+ $this->b->close();
+ $this->assertFalse(is_resource($this->c));
+ }
+
+ public function testDetaches()
+ {
+ $this->b->detach();
+ $this->assertFalse($this->b->isReadable());
+ }
+
+ public function testWrapsMetadata()
+ {
+ $this->assertSame($this->b->getMetadata(), $this->a->getMetadata());
+ $this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri'));
+ }
+
+ public function testWrapsWrites()
+ {
+ $this->b->seek(0, SEEK_END);
+ $this->b->write('foo');
+ $this->assertEquals('foofoo', (string)$this->a);
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ */
+ public function testThrowsWithInvalidGetter()
+ {
+ $this->b->foo;
+ }
+
+}
\ No newline at end of file
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/StreamTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/StreamTest.php
new file mode 100755
index 0000000..5501805
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/StreamTest.php
@@ -0,0 +1,163 @@
+assertTrue($stream->isReadable());
+ $this->assertTrue($stream->isWritable());
+ $this->assertTrue($stream->isSeekable());
+ $this->assertEquals('php://temp', $stream->getMetadata('uri'));
+ $this->assertInternalType('array', $stream->getMetadata());
+ $this->assertEquals(4, $stream->getSize());
+ $this->assertFalse($stream->eof());
+ $stream->close();
+ }
+
+ public function testStreamClosesHandleOnDestruct()
+ {
+ $handle = fopen('php://temp', 'r');
+ $stream = new Stream($handle);
+ unset($stream);
+ $this->assertFalse(is_resource($handle));
+ }
+
+ public function testConvertsToString()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals('data', (string) $stream);
+ $this->assertEquals('data', (string) $stream);
+ $stream->close();
+ }
+
+ public function testGetsContents()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals('', $stream->getContents());
+ $stream->seek(0);
+ $this->assertEquals('data', $stream->getContents());
+ $this->assertEquals('', $stream->getContents());
+ }
+
+ public function testChecksEof()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertFalse($stream->eof());
+ $stream->read(4);
+ $this->assertTrue($stream->eof());
+ $stream->close();
+ }
+
+ public function testGetSize()
+ {
+ $size = filesize(__FILE__);
+ $handle = fopen(__FILE__, 'r');
+ $stream = new Stream($handle);
+ $this->assertEquals($size, $stream->getSize());
+ // Load from cache
+ $this->assertEquals($size, $stream->getSize());
+ $stream->close();
+ }
+
+ public function testEnsuresSizeIsConsistent()
+ {
+ $h = fopen('php://temp', 'w+');
+ $this->assertEquals(3, fwrite($h, 'foo'));
+ $stream = new Stream($h);
+ $this->assertEquals(3, $stream->getSize());
+ $this->assertEquals(4, $stream->write('test'));
+ $this->assertEquals(7, $stream->getSize());
+ $this->assertEquals(7, $stream->getSize());
+ $stream->close();
+ }
+
+ public function testProvidesStreamPosition()
+ {
+ $handle = fopen('php://temp', 'w+');
+ $stream = new Stream($handle);
+ $this->assertEquals(0, $stream->tell());
+ $stream->write('foo');
+ $this->assertEquals(3, $stream->tell());
+ $stream->seek(1);
+ $this->assertEquals(1, $stream->tell());
+ $this->assertSame(ftell($handle), $stream->tell());
+ $stream->close();
+ }
+
+ public function testCanDetachStream()
+ {
+ $r = fopen('php://temp', 'w+');
+ $stream = new Stream($r);
+ $stream->write('foo');
+ $this->assertTrue($stream->isReadable());
+ $this->assertSame($r, $stream->detach());
+ $stream->detach();
+
+ $this->assertFalse($stream->isReadable());
+ $this->assertFalse($stream->isWritable());
+ $this->assertFalse($stream->isSeekable());
+
+ $self = $this;
+
+ $throws = function ($fn) use ($stream, $self) {
+ try {
+ $fn($stream);
+ $self->fail();
+ } catch (\Exception $e) {}
+ };
+
+ $throws(function ($stream) { $stream->read(10); });
+ $throws(function ($stream) { $stream->write('bar'); });
+ $throws(function ($stream) { $stream->seek(10); });
+ $throws(function ($stream) { $stream->tell(); });
+ $throws(function ($stream) { $stream->eof(); });
+ $throws(function ($stream) { $stream->getSize(); });
+ $throws(function ($stream) { $stream->getContents(); });
+ $this->assertSame('', (string) $stream);
+ $stream->close();
+ }
+
+ public function testCloseClearProperties()
+ {
+ $handle = fopen('php://temp', 'r+');
+ $stream = new Stream($handle);
+ $stream->close();
+
+ $this->assertFalse($stream->isSeekable());
+ $this->assertFalse($stream->isReadable());
+ $this->assertFalse($stream->isWritable());
+ $this->assertNull($stream->getSize());
+ $this->assertEmpty($stream->getMetadata());
+ }
+
+ public function testDoesNotThrowInToString()
+ {
+ $s = \RingCentral\Psr7\stream_for('foo');
+ $s = new NoSeekStream($s);
+ $this->assertEquals('foo', (string) $s);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/StreamWrapperTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/StreamWrapperTest.php
new file mode 100755
index 0000000..dd08a83
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/StreamWrapperTest.php
@@ -0,0 +1,100 @@
+assertSame('foo', fread($handle, 3));
+ $this->assertSame(3, ftell($handle));
+ $this->assertSame(3, fwrite($handle, 'bar'));
+ $this->assertSame(0, fseek($handle, 0));
+ $this->assertSame('foobar', fread($handle, 6));
+ $this->assertSame('', fread($handle, 1));
+ $this->assertTrue(feof($handle));
+
+ // This fails on HHVM for some reason
+ if (!defined('HHVM_VERSION')) {
+ $this->assertEquals(array(
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 33206,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 6,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ 0 => 0,
+ 1 => 0,
+ 2 => 33206,
+ 3 => 0,
+ 4 => 0,
+ 5 => 0,
+ 6 => 0,
+ 7 => 6,
+ 8 => 0,
+ 9 => 0,
+ 10 => 0,
+ 11 => 0,
+ 12 => 0,
+ ), fstat($handle));
+ }
+
+ $this->assertTrue(fclose($handle));
+ $this->assertSame('foobar', (string) $stream);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesStream()
+ {
+ $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable', 'isWritable'))
+ ->getMockForAbstractClass();
+ $stream->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $stream->expects($this->once())
+ ->method('isWritable')
+ ->will($this->returnValue(false));
+ StreamWrapper::getResource($stream);
+ }
+
+ /**
+ * @expectedException \PHPUnit_Framework_Error_Warning
+ */
+ public function testReturnsFalseWhenStreamDoesNotExist()
+ {
+ fopen('guzzle://foo', 'r');
+ }
+
+ public function testCanOpenReadonlyStream()
+ {
+ $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable', 'isWritable'))
+ ->getMockForAbstractClass();
+ $stream->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $stream->expects($this->once())
+ ->method('isWritable')
+ ->will($this->returnValue(true));
+ $r = StreamWrapper::getResource($stream);
+ $this->assertInternalType('resource', $r);
+ fclose($r);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/UriTest.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/UriTest.php
new file mode 100755
index 0000000..b53c97e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/UriTest.php
@@ -0,0 +1,258 @@
+assertEquals(
+ 'https://michael:test@test.com/path/123?q=abc#test',
+ (string) $uri
+ );
+
+ $this->assertEquals('test', $uri->getFragment());
+ $this->assertEquals('test.com', $uri->getHost());
+ $this->assertEquals('/path/123', $uri->getPath());
+ $this->assertEquals(null, $uri->getPort());
+ $this->assertEquals('q=abc', $uri->getQuery());
+ $this->assertEquals('https', $uri->getScheme());
+ $this->assertEquals('michael:test', $uri->getUserInfo());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Unable to parse URI
+ */
+ public function testValidatesUriCanBeParsed()
+ {
+ // Due to 5.4.7 "Fixed host recognition when scheme is omitted and a leading component separator is present" this does not work in 5.3
+ //new Uri('///');
+ throw new \InvalidArgumentException('Unable to parse URI');
+ }
+
+ public function testCanTransformAndRetrievePartsIndividually()
+ {
+ $uri = new Uri('');
+ $uri = $uri->withFragment('#test')
+ ->withHost('example.com')
+ ->withPath('path/123')
+ ->withPort(8080)
+ ->withQuery('?q=abc')
+ ->withScheme('http')
+ ->withUserInfo('user', 'pass');
+
+ // Test getters.
+ $this->assertEquals('user:pass@example.com:8080', $uri->getAuthority());
+ $this->assertEquals('test', $uri->getFragment());
+ $this->assertEquals('example.com', $uri->getHost());
+ $this->assertEquals('path/123', $uri->getPath());
+ $this->assertEquals(8080, $uri->getPort());
+ $this->assertEquals('q=abc', $uri->getQuery());
+ $this->assertEquals('http', $uri->getScheme());
+ $this->assertEquals('user:pass', $uri->getUserInfo());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testPortMustBeValid()
+ {
+ $uri = new Uri('');
+ $uri->withPort(100000);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testPathMustBeValid()
+ {
+ $uri = new Uri('');
+ $uri->withPath(array());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testQueryMustBeValid()
+ {
+ $uri = new Uri('');
+ $uri->withQuery(new \stdClass);
+ }
+
+ public function testAllowsFalseyUrlParts()
+ {
+ $url = new Uri('http://a:1/0?0#0');
+ $this->assertSame('a', $url->getHost());
+ $this->assertEquals(1, $url->getPort());
+ $this->assertSame('/0', $url->getPath());
+ $this->assertEquals('0', (string) $url->getQuery());
+ $this->assertSame('0', $url->getFragment());
+ $this->assertEquals('http://a:1/0?0#0', (string) $url);
+ $url = new Uri('');
+ $this->assertSame('', (string) $url);
+ $url = new Uri('0');
+ $this->assertSame('0', (string) $url);
+ $url = new Uri('/');
+ $this->assertSame('/', (string) $url);
+ }
+
+ /**
+ * @dataProvider getResolveTestCases
+ */
+ public function testResolvesUris($base, $rel, $expected)
+ {
+ $uri = new Uri($base);
+ $actual = Uri::resolve($uri, $rel);
+ $this->assertEquals($expected, (string) $actual);
+ }
+
+ public function getResolveTestCases()
+ {
+ return array(
+ //[self::RFC3986_BASE, 'g:h', 'g:h'],
+ array(self::RFC3986_BASE, 'g', 'http://a/b/c/g'),
+ array(self::RFC3986_BASE, './g', 'http://a/b/c/g'),
+ array(self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'),
+ array(self::RFC3986_BASE, '/g', 'http://a/g'),
+ // Due to 5.4.7 "Fixed host recognition when scheme is omitted and a leading component separator is present" this does not work in 5.3
+ //array(self::RFC3986_BASE, '//g', 'http://g'),
+ array(self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'),
+ array(self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'),
+ array(self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'),
+ array(self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'),
+ array(self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'),
+ array(self::RFC3986_BASE, ';x', 'http://a/b/c/;x'),
+ array(self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'),
+ array(self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'),
+ array(self::RFC3986_BASE, '', self::RFC3986_BASE),
+ array(self::RFC3986_BASE, '.', 'http://a/b/c/'),
+ array(self::RFC3986_BASE, './', 'http://a/b/c/'),
+ array(self::RFC3986_BASE, '..', 'http://a/b/'),
+ array(self::RFC3986_BASE, '../', 'http://a/b/'),
+ array(self::RFC3986_BASE, '../g', 'http://a/b/g'),
+ array(self::RFC3986_BASE, '../..', 'http://a/'),
+ array(self::RFC3986_BASE, '../../', 'http://a/'),
+ array(self::RFC3986_BASE, '../../g', 'http://a/g'),
+ array(self::RFC3986_BASE, '../../../g', 'http://a/g'),
+ array(self::RFC3986_BASE, '../../../../g', 'http://a/g'),
+ array(self::RFC3986_BASE, '/./g', 'http://a/g'),
+ array(self::RFC3986_BASE, '/../g', 'http://a/g'),
+ array(self::RFC3986_BASE, 'g.', 'http://a/b/c/g.'),
+ array(self::RFC3986_BASE, '.g', 'http://a/b/c/.g'),
+ array(self::RFC3986_BASE, 'g..', 'http://a/b/c/g..'),
+ array(self::RFC3986_BASE, '..g', 'http://a/b/c/..g'),
+ array(self::RFC3986_BASE, './../g', 'http://a/b/g'),
+ array(self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'),
+ array(self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'),
+ array(self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'),
+ array(self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'),
+ array(self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'),
+ array(self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'),
+ array('http://u@a/b/c/d;p?q', '.', 'http://u@a/b/c/'),
+ array('http://u:p@a/b/c/d;p?q', '.', 'http://u:p@a/b/c/'),
+ //[self::RFC3986_BASE, 'http:g', 'http:g'],
+ );
+ }
+
+ public function testAddAndRemoveQueryValues()
+ {
+ $uri = new Uri('http://foo.com/bar');
+ $uri = Uri::withQueryValue($uri, 'a', 'b');
+ $uri = Uri::withQueryValue($uri, 'c', 'd');
+ $uri = Uri::withQueryValue($uri, 'e', null);
+ $this->assertEquals('a=b&c=d&e', $uri->getQuery());
+
+ $uri = Uri::withoutQueryValue($uri, 'c');
+ $uri = Uri::withoutQueryValue($uri, 'e');
+ $this->assertEquals('a=b', $uri->getQuery());
+ $uri = Uri::withoutQueryValue($uri, 'a');
+ $uri = Uri::withoutQueryValue($uri, 'a');
+ $this->assertEquals('', $uri->getQuery());
+ }
+
+ public function testGetAuthorityReturnsCorrectPort()
+ {
+ // HTTPS non-standard port
+ $uri = new Uri('https://foo.co:99');
+ $this->assertEquals('foo.co:99', $uri->getAuthority());
+
+ // HTTP non-standard port
+ $uri = new Uri('http://foo.co:99');
+ $this->assertEquals('foo.co:99', $uri->getAuthority());
+
+ // No scheme
+ $uri = new Uri('foo.co:99');
+ $this->assertEquals('foo.co:99', $uri->getAuthority());
+
+ // No host or port
+ $uri = new Uri('http:');
+ $this->assertEquals('', $uri->getAuthority());
+
+ // No host or port
+ $uri = new Uri('http://foo.co');
+ $this->assertEquals('foo.co', $uri->getAuthority());
+ }
+
+ public function pathTestProvider()
+ {
+ return array(
+ // Percent encode spaces.
+ array('http://foo.com/baz bar', 'http://foo.com/baz%20bar'),
+ // Don't encoding something that's already encoded.
+ array('http://foo.com/baz%20bar', 'http://foo.com/baz%20bar'),
+ // Percent encode invalid percent encodings
+ array('http://foo.com/baz%2-bar', 'http://foo.com/baz%252-bar'),
+ // Don't encode path segments
+ array('http://foo.com/baz/bar/bam?a', 'http://foo.com/baz/bar/bam?a'),
+ array('http://foo.com/baz+bar', 'http://foo.com/baz+bar'),
+ array('http://foo.com/baz:bar', 'http://foo.com/baz:bar'),
+ array('http://foo.com/baz@bar', 'http://foo.com/baz@bar'),
+ array('http://foo.com/baz(bar);bam/', 'http://foo.com/baz(bar);bam/'),
+ array('http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@', 'http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@'),
+ );
+ }
+
+ /**
+ * @dataProvider pathTestProvider
+ */
+ public function testUriEncodesPathProperly($input, $output)
+ {
+ $uri = new Uri($input);
+ $this->assertEquals((string) $uri, $output);
+ }
+
+ public function testDoesNotAddPortWhenNoPort()
+ {
+ // Due to 5.4.7 "Fixed host recognition when scheme is omitted and a leading component separator is present" this does not work in 5.3
+ //$uri = new Uri('//bar');
+ //$this->assertEquals('bar', (string) $uri);
+ //$uri = new Uri('//barx');
+ //$this->assertEquals('barx', $uri->getHost());
+ }
+
+ public function testAllowsForRelativeUri()
+ {
+ $uri = new Uri();
+ $uri = $uri->withPath('foo');
+ $this->assertEquals('foo', $uri->getPath());
+ $this->assertEquals('foo', (string) $uri);
+ }
+
+ public function testAddsSlashForRelativeUriStringWithHost()
+ {
+ $uri = new Uri();
+ $uri = $uri->withPath('foo')->withHost('bar.com');
+ $this->assertEquals('foo', $uri->getPath());
+ $this->assertEquals('bar.com/foo', (string) $uri);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/bootstrap.php b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/bootstrap.php
new file mode 100755
index 0000000..ea6a079
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/ringcentral/psr7/tests/bootstrap.php
@@ -0,0 +1,13 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * Marker Interface for the Process Component.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Exception/InvalidArgumentException.php b/src/mpg25-instagram-api/vendor/symfony/process/Exception/InvalidArgumentException.php
new file mode 100755
index 0000000..926ee21
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * InvalidArgumentException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Exception/LogicException.php b/src/mpg25-instagram-api/vendor/symfony/process/Exception/LogicException.php
new file mode 100755
index 0000000..be3d490
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Exception/LogicException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * LogicException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Exception/ProcessFailedException.php b/src/mpg25-instagram-api/vendor/symfony/process/Exception/ProcessFailedException.php
new file mode 100755
index 0000000..328acfd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Exception/ProcessFailedException.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception for failed processes.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessFailedException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ if ($process->isSuccessful()) {
+ throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
+ }
+
+ $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
+ $process->getCommandLine(),
+ $process->getExitCode(),
+ $process->getExitCodeText(),
+ $process->getWorkingDirectory()
+ );
+
+ if (!$process->isOutputDisabled()) {
+ $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
+ $process->getOutput(),
+ $process->getErrorOutput()
+ );
+ }
+
+ parent::__construct($error);
+
+ $this->process = $process;
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Exception/ProcessSignaledException.php b/src/mpg25-instagram-api/vendor/symfony/process/Exception/ProcessSignaledException.php
new file mode 100755
index 0000000..d4d3227
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Exception/ProcessSignaledException.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process has been signaled.
+ *
+ * @author Sullivan Senechal
+ */
+final class ProcessSignaledException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ $this->process = $process;
+
+ parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
+ }
+
+ public function getProcess(): Process
+ {
+ return $this->process;
+ }
+
+ public function getSignal(): int
+ {
+ return $this->getProcess()->getTermSignal();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Exception/ProcessTimedOutException.php b/src/mpg25-instagram-api/vendor/symfony/process/Exception/ProcessTimedOutException.php
new file mode 100755
index 0000000..e1f6445
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Exception/ProcessTimedOutException.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+ const TYPE_GENERAL = 1;
+ const TYPE_IDLE = 2;
+
+ private $process;
+ private $timeoutType;
+
+ public function __construct(Process $process, int $timeoutType)
+ {
+ $this->process = $process;
+ $this->timeoutType = $timeoutType;
+
+ parent::__construct(sprintf(
+ 'The process "%s" exceeded the timeout of %s seconds.',
+ $process->getCommandLine(),
+ $this->getExceededTimeout()
+ ));
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ public function isGeneralTimeout()
+ {
+ return self::TYPE_GENERAL === $this->timeoutType;
+ }
+
+ public function isIdleTimeout()
+ {
+ return self::TYPE_IDLE === $this->timeoutType;
+ }
+
+ public function getExceededTimeout()
+ {
+ switch ($this->timeoutType) {
+ case self::TYPE_GENERAL:
+ return $this->process->getTimeout();
+
+ case self::TYPE_IDLE:
+ return $this->process->getIdleTimeout();
+
+ default:
+ throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Exception/RuntimeException.php b/src/mpg25-instagram-api/vendor/symfony/process/Exception/RuntimeException.php
new file mode 100755
index 0000000..adead25
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Exception/RuntimeException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * RuntimeException for the Process Component.
+ *
+ * @author Johannes M. Schmitt
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/ExecutableFinder.php b/src/mpg25-instagram-api/vendor/symfony/process/ExecutableFinder.php
new file mode 100755
index 0000000..cb4345e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/ExecutableFinder.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * Generic executable finder.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class ExecutableFinder
+{
+ private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
+
+ /**
+ * Replaces default suffixes of executable.
+ */
+ public function setSuffixes(array $suffixes)
+ {
+ $this->suffixes = $suffixes;
+ }
+
+ /**
+ * Adds new possible suffix to check for executable.
+ *
+ * @param string $suffix
+ */
+ public function addSuffix($suffix)
+ {
+ $this->suffixes[] = $suffix;
+ }
+
+ /**
+ * Finds an executable by name.
+ *
+ * @param string $name The executable name (without the extension)
+ * @param string|null $default The default to return if no executable is found
+ * @param array $extraDirs Additional dirs to check into
+ *
+ * @return string|null The executable path or default value
+ */
+ public function find($name, $default = null, array $extraDirs = [])
+ {
+ if (ini_get('open_basedir')) {
+ $searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
+ $dirs = [];
+ foreach ($searchPath as $path) {
+ // Silencing against https://bugs.php.net/69240
+ if (@is_dir($path)) {
+ $dirs[] = $path;
+ } else {
+ if (basename($path) == $name && @is_executable($path)) {
+ return $path;
+ }
+ }
+ }
+ } else {
+ $dirs = array_merge(
+ explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
+ $extraDirs
+ );
+ }
+
+ $suffixes = [''];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $pathExt = getenv('PATHEXT');
+ $suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
+ }
+ foreach ($suffixes as $suffix) {
+ foreach ($dirs as $dir) {
+ if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
+ return $file;
+ }
+ }
+ }
+
+ return $default;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/InputStream.php b/src/mpg25-instagram-api/vendor/symfony/process/InputStream.php
new file mode 100755
index 0000000..426ffa3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/InputStream.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * Provides a way to continuously write to the input of a Process until the InputStream is closed.
+ *
+ * @author Nicolas Grekas
+ */
+class InputStream implements \IteratorAggregate
+{
+ /** @var callable|null */
+ private $onEmpty = null;
+ private $input = [];
+ private $open = true;
+
+ /**
+ * Sets a callback that is called when the write buffer becomes empty.
+ */
+ public function onEmpty(callable $onEmpty = null)
+ {
+ $this->onEmpty = $onEmpty;
+ }
+
+ /**
+ * Appends an input to the write buffer.
+ *
+ * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
+ * stream resource or \Traversable
+ */
+ public function write($input)
+ {
+ if (null === $input) {
+ return;
+ }
+ if ($this->isClosed()) {
+ throw new RuntimeException(sprintf('%s is closed', static::class));
+ }
+ $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
+ }
+
+ /**
+ * Closes the write buffer.
+ */
+ public function close()
+ {
+ $this->open = false;
+ }
+
+ /**
+ * Tells whether the write buffer is closed or not.
+ */
+ public function isClosed()
+ {
+ return !$this->open;
+ }
+
+ public function getIterator()
+ {
+ $this->open = true;
+
+ while ($this->open || $this->input) {
+ if (!$this->input) {
+ yield '';
+ continue;
+ }
+ $current = array_shift($this->input);
+
+ if ($current instanceof \Iterator) {
+ yield from $current;
+ } else {
+ yield $current;
+ }
+ if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
+ $this->write($onEmpty($this));
+ }
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/LICENSE b/src/mpg25-instagram-api/vendor/symfony/process/LICENSE
new file mode 100755
index 0000000..a677f43
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/PhpExecutableFinder.php b/src/mpg25-instagram-api/vendor/symfony/process/PhpExecutableFinder.php
new file mode 100755
index 0000000..461ea13
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/PhpExecutableFinder.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * An executable finder specifically designed for the PHP executable.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class PhpExecutableFinder
+{
+ private $executableFinder;
+
+ public function __construct()
+ {
+ $this->executableFinder = new ExecutableFinder();
+ }
+
+ /**
+ * Finds The PHP executable.
+ *
+ * @param bool $includeArgs Whether or not include command arguments
+ *
+ * @return string|false The PHP executable path or false if it cannot be found
+ */
+ public function find($includeArgs = true)
+ {
+ if ($php = getenv('PHP_BINARY')) {
+ if (!is_executable($php)) {
+ $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
+ if ($php = strtok(exec($command.' '.escapeshellarg($php)), PHP_EOL)) {
+ if (!is_executable($php)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return $php;
+ }
+
+ $args = $this->findArguments();
+ $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
+
+ // PHP_BINARY return the current sapi executable
+ if (PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
+ return PHP_BINARY.$args;
+ }
+
+ if ($php = getenv('PHP_PATH')) {
+ if (!@is_executable($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ if ($php = getenv('PHP_PEAR_PHP_BIN')) {
+ if (@is_executable($php)) {
+ return $php;
+ }
+ }
+
+ if (@is_executable($php = PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
+ return $php;
+ }
+
+ $dirs = [PHP_BINDIR];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $dirs[] = 'C:\xampp\php\\';
+ }
+
+ return $this->executableFinder->find('php', false, $dirs);
+ }
+
+ /**
+ * Finds the PHP executable arguments.
+ *
+ * @return array The PHP executable arguments
+ */
+ public function findArguments()
+ {
+ $arguments = [];
+ if ('phpdbg' === \PHP_SAPI) {
+ $arguments[] = '-qrr';
+ }
+
+ return $arguments;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/PhpProcess.php b/src/mpg25-instagram-api/vendor/symfony/process/PhpProcess.php
new file mode 100755
index 0000000..126d9b7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/PhpProcess.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * PhpProcess runs a PHP script in an independent process.
+ *
+ * $p = new PhpProcess('');
+ * $p->run();
+ * print $p->getOutput()."\n";
+ *
+ * @author Fabien Potencier
+ */
+class PhpProcess extends Process
+{
+ /**
+ * @param string $script The PHP script to run (as a string)
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param int $timeout The timeout in seconds
+ * @param array|null $php Path to the PHP binary to use with any additional arguments
+ */
+ public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null)
+ {
+ if (null === $php) {
+ $executableFinder = new PhpExecutableFinder();
+ $php = $executableFinder->find(false);
+ $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
+ }
+ if ('phpdbg' === \PHP_SAPI) {
+ $file = tempnam(sys_get_temp_dir(), 'dbg');
+ file_put_contents($file, $script);
+ register_shutdown_function('unlink', $file);
+ $php[] = $file;
+ $script = null;
+ }
+
+ parent::__construct($php, $cwd, $env, $script, $timeout);
+ }
+
+ /**
+ * Sets the path to the PHP binary to use.
+ *
+ * @deprecated since Symfony 4.2, use the $php argument of the constructor instead.
+ */
+ public function setPhpBinary($php)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the $php argument of the constructor instead.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->setCommandLine($php);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if (null === $this->getCommandLine()) {
+ throw new RuntimeException('Unable to find the PHP executable.');
+ }
+
+ parent::start($callback, $env);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Pipes/AbstractPipes.php b/src/mpg25-instagram-api/vendor/symfony/process/Pipes/AbstractPipes.php
new file mode 100755
index 0000000..9dd415d
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Pipes/AbstractPipes.php
@@ -0,0 +1,182 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+abstract class AbstractPipes implements PipesInterface
+{
+ public $pipes = [];
+
+ private $inputBuffer = '';
+ private $input;
+ private $blocked = true;
+ private $lastError;
+
+ /**
+ * @param resource|string|int|float|bool|\Iterator|null $input
+ */
+ public function __construct($input)
+ {
+ if (\is_resource($input) || $input instanceof \Iterator) {
+ $this->input = $input;
+ } elseif (\is_string($input)) {
+ $this->inputBuffer = $input;
+ } else {
+ $this->inputBuffer = (string) $input;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ foreach ($this->pipes as $pipe) {
+ fclose($pipe);
+ }
+ $this->pipes = [];
+ }
+
+ /**
+ * Returns true if a system call has been interrupted.
+ *
+ * @return bool
+ */
+ protected function hasSystemCallBeenInterrupted()
+ {
+ $lastError = $this->lastError;
+ $this->lastError = null;
+
+ // stream_select returns false when the `select` system call is interrupted by an incoming signal
+ return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
+ }
+
+ /**
+ * Unblocks streams.
+ */
+ protected function unblock()
+ {
+ if (!$this->blocked) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ stream_set_blocking($pipe, 0);
+ }
+ if (\is_resource($this->input)) {
+ stream_set_blocking($this->input, 0);
+ }
+
+ $this->blocked = false;
+ }
+
+ /**
+ * Writes input to stdin.
+ *
+ * @return array|null
+ *
+ * @throws InvalidArgumentException When an input iterator yields a non supported value
+ */
+ protected function write()
+ {
+ if (!isset($this->pipes[0])) {
+ return null;
+ }
+ $input = $this->input;
+
+ if ($input instanceof \Iterator) {
+ if (!$input->valid()) {
+ $input = null;
+ } elseif (\is_resource($input = $input->current())) {
+ stream_set_blocking($input, 0);
+ } elseif (!isset($this->inputBuffer[0])) {
+ if (!\is_string($input)) {
+ if (!is_scalar($input)) {
+ throw new InvalidArgumentException(sprintf('%s yielded a value of type "%s", but only scalars and stream resources are supported', \get_class($this->input), \gettype($input)));
+ }
+ $input = (string) $input;
+ }
+ $this->inputBuffer = $input;
+ $this->input->next();
+ $input = null;
+ } else {
+ $input = null;
+ }
+ }
+
+ $r = $e = [];
+ $w = [$this->pipes[0]];
+
+ // let's have a look if something changed in streams
+ if (false === @stream_select($r, $w, $e, 0, 0)) {
+ return null;
+ }
+
+ foreach ($w as $stdin) {
+ if (isset($this->inputBuffer[0])) {
+ $written = fwrite($stdin, $this->inputBuffer);
+ $this->inputBuffer = substr($this->inputBuffer, $written);
+ if (isset($this->inputBuffer[0])) {
+ return [$this->pipes[0]];
+ }
+ }
+
+ if ($input) {
+ for (;;) {
+ $data = fread($input, self::CHUNK_SIZE);
+ if (!isset($data[0])) {
+ break;
+ }
+ $written = fwrite($stdin, $data);
+ $data = substr($data, $written);
+ if (isset($data[0])) {
+ $this->inputBuffer = $data;
+
+ return [$this->pipes[0]];
+ }
+ }
+ if (feof($input)) {
+ if ($this->input instanceof \Iterator) {
+ $this->input->next();
+ } else {
+ $this->input = null;
+ }
+ }
+ }
+ }
+
+ // no input to read on resource, buffer is empty
+ if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
+ $this->input = null;
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ } elseif (!$w) {
+ return [$this->pipes[0]];
+ }
+
+ return null;
+ }
+
+ /**
+ * @internal
+ */
+ public function handleError($type, $msg)
+ {
+ $this->lastError = $msg;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Pipes/PipesInterface.php b/src/mpg25-instagram-api/vendor/symfony/process/Pipes/PipesInterface.php
new file mode 100755
index 0000000..52bbe76
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Pipes/PipesInterface.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+/**
+ * PipesInterface manages descriptors and pipes for the use of proc_open.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+interface PipesInterface
+{
+ const CHUNK_SIZE = 16384;
+
+ /**
+ * Returns an array of descriptors for the use of proc_open.
+ *
+ * @return array
+ */
+ public function getDescriptors();
+
+ /**
+ * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
+ *
+ * @return string[]
+ */
+ public function getFiles();
+
+ /**
+ * Reads data in file handles and pipes.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close pipes if they've reached EOF
+ *
+ * @return string[] An array of read data indexed by their fd
+ */
+ public function readAndWrite($blocking, $close = false);
+
+ /**
+ * Returns if the current state has open file handles or pipes.
+ *
+ * @return bool
+ */
+ public function areOpen();
+
+ /**
+ * Returns if pipes are able to read output.
+ *
+ * @return bool
+ */
+ public function haveReadSupport();
+
+ /**
+ * Closes file handles and pipes.
+ */
+ public function close();
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Pipes/UnixPipes.php b/src/mpg25-instagram-api/vendor/symfony/process/Pipes/UnixPipes.php
new file mode 100755
index 0000000..875ee6a
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Pipes/UnixPipes.php
@@ -0,0 +1,153 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * UnixPipes implementation uses unix pipes as handles.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class UnixPipes extends AbstractPipes
+{
+ private $ttyMode;
+ private $ptyMode;
+ private $haveReadSupport;
+
+ public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
+ {
+ $this->ttyMode = $ttyMode;
+ $this->ptyMode = $ptyMode;
+ $this->haveReadSupport = $haveReadSupport;
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('/dev/null', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ if ($this->ttyMode) {
+ return [
+ ['file', '/dev/tty', 'r'],
+ ['file', '/dev/tty', 'w'],
+ ['file', '/dev/tty', 'w'],
+ ];
+ }
+
+ if ($this->ptyMode && Process::isPtySupported()) {
+ return [
+ ['pty'],
+ ['pty'],
+ ['pty'],
+ ];
+ }
+
+ return [
+ ['pipe', 'r'],
+ ['pipe', 'w'], // stdout
+ ['pipe', 'w'], // stderr
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+
+ $read = $e = [];
+ $r = $this->pipes;
+ unset($r[0]);
+
+ // let's have a look if something changed in streams
+ set_error_handler([$this, 'handleError']);
+ if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+ restore_error_handler();
+ // if a system call has been interrupted, forget about it, let's try again
+ // otherwise, an error occurred, let's reset pipes
+ if (!$this->hasSystemCallBeenInterrupted()) {
+ $this->pipes = [];
+ }
+
+ return $read;
+ }
+ restore_error_handler();
+
+ foreach ($r as $pipe) {
+ // prior PHP 5.4 the array passed to stream_select is modified and
+ // lose key association, we have to find back the key
+ $read[$type = array_search($pipe, $this->pipes, true)] = '';
+
+ do {
+ $data = fread($pipe, self::CHUNK_SIZE);
+ $read[$type] .= $data;
+ } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
+
+ if (!isset($read[$type][0])) {
+ unset($read[$type]);
+ }
+
+ if ($close && feof($pipe)) {
+ fclose($pipe);
+ unset($this->pipes[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return (bool) $this->pipes;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Pipes/WindowsPipes.php b/src/mpg25-instagram-api/vendor/symfony/process/Pipes/WindowsPipes.php
new file mode 100755
index 0000000..0e38d72
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Pipes/WindowsPipes.php
@@ -0,0 +1,191 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Process;
+
+/**
+ * WindowsPipes implementation uses temporary files as handles.
+ *
+ * @see https://bugs.php.net/51800
+ * @see https://bugs.php.net/65650
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class WindowsPipes extends AbstractPipes
+{
+ private $files = [];
+ private $fileHandles = [];
+ private $lockHandles = [];
+ private $readBytes = [
+ Process::STDOUT => 0,
+ Process::STDERR => 0,
+ ];
+ private $haveReadSupport;
+
+ public function __construct($input, bool $haveReadSupport)
+ {
+ $this->haveReadSupport = $haveReadSupport;
+
+ if ($this->haveReadSupport) {
+ // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
+ // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
+ //
+ // @see https://bugs.php.net/51800
+ $pipes = [
+ Process::STDOUT => Process::OUT,
+ Process::STDERR => Process::ERR,
+ ];
+ $tmpDir = sys_get_temp_dir();
+ $lastError = 'unknown reason';
+ set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
+ for ($i = 0;; ++$i) {
+ foreach ($pipes as $pipe => $name) {
+ $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
+
+ if (!$h = fopen($file.'.lock', 'w')) {
+ restore_error_handler();
+ throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $lastError));
+ }
+ if (!flock($h, LOCK_EX | LOCK_NB)) {
+ continue 2;
+ }
+ if (isset($this->lockHandles[$pipe])) {
+ flock($this->lockHandles[$pipe], LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ }
+ $this->lockHandles[$pipe] = $h;
+
+ if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
+ flock($this->lockHandles[$pipe], LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ unset($this->lockHandles[$pipe]);
+ continue 2;
+ }
+ $this->fileHandles[$pipe] = $h;
+ $this->files[$pipe] = $file;
+ }
+ break;
+ }
+ restore_error_handler();
+ }
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('NUL', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
+ // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
+ // So we redirect output within the commandline and pass the nul device to the process
+ return [
+ ['pipe', 'r'],
+ ['file', 'NUL', 'w'],
+ ['file', 'NUL', 'w'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return $this->files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+ $read = $r = $e = [];
+
+ if ($blocking) {
+ if ($w) {
+ @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
+ } elseif ($this->fileHandles) {
+ usleep(Process::TIMEOUT_PRECISION * 1E6);
+ }
+ }
+ foreach ($this->fileHandles as $type => $fileHandle) {
+ $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
+
+ if (isset($data[0])) {
+ $this->readBytes[$type] += \strlen($data);
+ $read[$type] = $data;
+ }
+ if ($close) {
+ ftruncate($fileHandle, 0);
+ fclose($fileHandle);
+ flock($this->lockHandles[$type], LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ unset($this->fileHandles[$type], $this->lockHandles[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return $this->pipes && $this->fileHandles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ parent::close();
+ foreach ($this->fileHandles as $type => $handle) {
+ ftruncate($handle, 0);
+ fclose($handle);
+ flock($this->lockHandles[$type], LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ }
+ $this->fileHandles = $this->lockHandles = [];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Process.php b/src/mpg25-instagram-api/vendor/symfony/process/Process.php
new file mode 100755
index 0000000..5d02c60
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Process.php
@@ -0,0 +1,1645 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Exception\ProcessSignaledException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Pipes\UnixPipes;
+use Symfony\Component\Process\Pipes\WindowsPipes;
+
+/**
+ * Process is a thin wrapper around proc_* functions to easily
+ * start independent PHP processes.
+ *
+ * @author Fabien Potencier
+ * @author Romain Neutron
+ */
+class Process implements \IteratorAggregate
+{
+ const ERR = 'err';
+ const OUT = 'out';
+
+ const STATUS_READY = 'ready';
+ const STATUS_STARTED = 'started';
+ const STATUS_TERMINATED = 'terminated';
+
+ const STDIN = 0;
+ const STDOUT = 1;
+ const STDERR = 2;
+
+ // Timeout Precision in seconds.
+ const TIMEOUT_PRECISION = 0.2;
+
+ const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
+ const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
+ const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
+ const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
+
+ private $callback;
+ private $hasCallback = false;
+ private $commandline;
+ private $cwd;
+ private $env;
+ private $input;
+ private $starttime;
+ private $lastOutputTime;
+ private $timeout;
+ private $idleTimeout;
+ private $exitcode;
+ private $fallbackStatus = [];
+ private $processInformation;
+ private $outputDisabled = false;
+ private $stdout;
+ private $stderr;
+ private $process;
+ private $status = self::STATUS_READY;
+ private $incrementalOutputOffset = 0;
+ private $incrementalErrorOutputOffset = 0;
+ private $tty = false;
+ private $pty;
+
+ private $useFileHandles = false;
+ /** @var PipesInterface */
+ private $processPipes;
+
+ private $latestSignal;
+
+ private static $sigchild;
+
+ /**
+ * Exit codes translation table.
+ *
+ * User-defined errors must use exit codes in the 64-113 range.
+ */
+ public static $exitCodes = [
+ 0 => 'OK',
+ 1 => 'General error',
+ 2 => 'Misuse of shell builtins',
+
+ 126 => 'Invoked command cannot execute',
+ 127 => 'Command not found',
+ 128 => 'Invalid exit argument',
+
+ // signals
+ 129 => 'Hangup',
+ 130 => 'Interrupt',
+ 131 => 'Quit and dump core',
+ 132 => 'Illegal instruction',
+ 133 => 'Trace/breakpoint trap',
+ 134 => 'Process aborted',
+ 135 => 'Bus error: "access to undefined portion of memory object"',
+ 136 => 'Floating point exception: "erroneous arithmetic operation"',
+ 137 => 'Kill (terminate immediately)',
+ 138 => 'User-defined 1',
+ 139 => 'Segmentation violation',
+ 140 => 'User-defined 2',
+ 141 => 'Write to pipe with no one reading',
+ 142 => 'Signal raised by alarm',
+ 143 => 'Termination (request to terminate)',
+ // 144 - not defined
+ 145 => 'Child process terminated, stopped (or continued*)',
+ 146 => 'Continue if stopped',
+ 147 => 'Stop executing temporarily',
+ 148 => 'Terminal stop signal',
+ 149 => 'Background process attempting to read from tty ("in")',
+ 150 => 'Background process attempting to write to tty ("out")',
+ 151 => 'Urgent data available on socket',
+ 152 => 'CPU time limit exceeded',
+ 153 => 'File size limit exceeded',
+ 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+ 155 => 'Profiling timer expired',
+ // 156 - not defined
+ 157 => 'Pollable event',
+ // 158 - not defined
+ 159 => 'Bad syscall',
+ ];
+
+ /**
+ * @param array $command The command to run and its arguments listed as separate entries
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public function __construct($command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ if (!\function_exists('proc_open')) {
+ throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
+ }
+
+ if (!\is_array($command)) {
+ @trigger_error(sprintf('Passing a command as string when creating a "%s" instance is deprecated since Symfony 4.2, pass it as an array of its arguments instead, or use the "Process::fromShellCommandline()" constructor if you need features provided by the shell.', __CLASS__), E_USER_DEPRECATED);
+ }
+
+ $this->commandline = $command;
+ $this->cwd = $cwd;
+
+ // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
+ // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
+ // @see : https://bugs.php.net/51800
+ // @see : https://bugs.php.net/50524
+ if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
+ $this->cwd = getcwd();
+ }
+ if (null !== $env) {
+ $this->setEnv($env);
+ }
+
+ $this->setInput($input);
+ $this->setTimeout($timeout);
+ $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
+ $this->pty = false;
+ }
+
+ /**
+ * Creates a Process instance as a command-line to be run in a shell wrapper.
+ *
+ * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
+ * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
+ * shell wrapper and not to your commands.
+ *
+ * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
+ * This will save escaping values, which is not portable nor secure anyway:
+ *
+ * $process = Process::fromShellCommandline('my_command "$MY_VAR"');
+ * $process->run(null, ['MY_VAR' => $theValue]);
+ *
+ * @param string $command The command line to pass to the shell of the OS
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @return static
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ $process = new static([], $cwd, $env, $input, $timeout);
+ $process->commandline = $command;
+
+ return $process;
+ }
+
+ public function __destruct()
+ {
+ $this->stop(0);
+ }
+
+ public function __clone()
+ {
+ $this->resetProcessData();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * The callback receives the type of output (out or err) and
+ * some bytes from the output in real-time. It allows to have feedback
+ * from the independent process during execution.
+ *
+ * The STDOUT and STDERR are also available after the process is finished
+ * via the getOutput() and getErrorOutput() methods.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return int The exit status code
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException In case a callback is provided and output has been disabled
+ *
+ * @final
+ */
+ public function run(callable $callback = null, array $env = []): int
+ {
+ $this->start($callback, $env);
+
+ return $this->wait();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * This is identical to run() except that an exception is thrown if the process
+ * exits with a non-zero exit code.
+ *
+ * @return $this
+ *
+ * @throws ProcessFailedException if the process didn't terminate successfully
+ *
+ * @final
+ */
+ public function mustRun(callable $callback = null, array $env = [])
+ {
+ if (0 !== $this->run($callback, $env)) {
+ throw new ProcessFailedException($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Starts the process and returns after writing the input to STDIN.
+ *
+ * This method blocks until all STDIN data is sent to the process then it
+ * returns while the process runs in the background.
+ *
+ * The termination of the process can be awaited with wait().
+ *
+ * The callback receives the type of output (out or err) and some bytes from
+ * the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ * @throws LogicException In case a callback is provided and output has been disabled
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+
+ $this->resetProcessData();
+ $this->starttime = $this->lastOutputTime = microtime(true);
+ $this->callback = $this->buildCallback($callback);
+ $this->hasCallback = null !== $callback;
+ $descriptors = $this->getDescriptors();
+
+ if (\is_array($commandline = $this->commandline)) {
+ $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
+
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ // exec is mandatory to deal with sending a signal to the process
+ $commandline = 'exec '.$commandline;
+ }
+ }
+
+ if ($this->env) {
+ $env += $this->env;
+ }
+ $env += $this->getDefaultEnv();
+
+ $options = ['suppress_errors' => true];
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $options['bypass_shell'] = true;
+ $commandline = $this->prepareWindowsCommandLine($commandline, $env);
+ } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
+ // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
+ $descriptors[3] = ['pipe', 'w'];
+
+ // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
+ $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
+ $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
+
+ // Workaround for the bug, when PTS functionality is enabled.
+ // @see : https://bugs.php.net/69442
+ $ptsWorkaround = fopen(__FILE__, 'r');
+ }
+
+ $envPairs = [];
+ foreach ($env as $k => $v) {
+ if (false !== $v) {
+ $envPairs[] = $k.'='.$v;
+ }
+ }
+
+ if (!is_dir($this->cwd)) {
+ throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
+ }
+
+ $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options);
+
+ if (!\is_resource($this->process)) {
+ throw new RuntimeException('Unable to launch a new process.');
+ }
+ $this->status = self::STATUS_STARTED;
+
+ if (isset($descriptors[3])) {
+ $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
+ }
+
+ if ($this->tty) {
+ return;
+ }
+
+ $this->updateStatus(false);
+ $this->checkTimeout();
+ }
+
+ /**
+ * Restarts the process.
+ *
+ * Be warned that the process is cloned before being started.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return static
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ *
+ * @see start()
+ *
+ * @final
+ */
+ public function restart(callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+
+ $process = clone $this;
+ $process->start($callback, $env);
+
+ return $process;
+ }
+
+ /**
+ * Waits for the process to terminate.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A valid PHP callback
+ *
+ * @return int The exitcode of the process
+ *
+ * @throws RuntimeException When process timed out
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException When process is not yet started
+ */
+ public function wait(callable $callback = null)
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->updateStatus(false);
+
+ if (null !== $callback) {
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"');
+ }
+ $this->callback = $this->buildCallback($callback);
+ }
+
+ do {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+ } while ($running);
+
+ while ($this->isRunning()) {
+ $this->checkTimeout();
+ usleep(1000);
+ }
+
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+ throw new ProcessSignaledException($this);
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Waits until the callback returns true.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @throws RuntimeException When process timed out
+ * @throws LogicException When process is not yet started
+ */
+ public function waitUntil(callable $callback): bool
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+ $this->updateStatus(false);
+
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
+ }
+ $callback = $this->buildCallback($callback);
+
+ $ready = false;
+ while (true) {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ foreach ($output as $type => $data) {
+ if (3 !== $type) {
+ $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ if ($ready) {
+ return true;
+ }
+ if (!$running) {
+ return false;
+ }
+
+ usleep(1000);
+ }
+ }
+
+ /**
+ * Returns the Pid (process identifier), if applicable.
+ *
+ * @return int|null The process id if running, null otherwise
+ */
+ public function getPid()
+ {
+ return $this->isRunning() ? $this->processInformation['pid'] : null;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ public function signal($signal)
+ {
+ $this->doSignal($signal, true);
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ * @throws LogicException if an idle timeout is set
+ */
+ public function disableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Disabling output while the process is running is not possible.');
+ }
+ if (null !== $this->idleTimeout) {
+ throw new LogicException('Output can not be disabled while an idle timeout is set.');
+ }
+
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ */
+ public function enableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Enabling output while the process is running is not possible.');
+ }
+
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns true in case the output is disabled, false otherwise.
+ *
+ * @return bool
+ */
+ public function isOutputDisabled()
+ {
+ return $this->outputDisabled;
+ }
+
+ /**
+ * Returns the current output of the process (STDOUT).
+ *
+ * @return string The process output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the output incrementally.
+ *
+ * In comparison with the getOutput method which always return the whole
+ * output, this one returns the new output since the last call.
+ *
+ * @return string The process output since the last call
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+ $this->incrementalOutputOffset = ftell($this->stdout);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
+ *
+ * @param int $flags A bit field of Process::ITER_* flags
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ *
+ * @return \Generator
+ */
+ public function getIterator($flags = 0)
+ {
+ $this->readPipesForOutput(__FUNCTION__, false);
+
+ $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
+ $blocking = !(self::ITER_NON_BLOCKING & $flags);
+ $yieldOut = !(self::ITER_SKIP_OUT & $flags);
+ $yieldErr = !(self::ITER_SKIP_ERR & $flags);
+
+ while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
+ if ($yieldOut) {
+ $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+
+ if (isset($out[0])) {
+ if ($clearOutput) {
+ $this->clearOutput();
+ } else {
+ $this->incrementalOutputOffset = ftell($this->stdout);
+ }
+
+ yield self::OUT => $out;
+ }
+ }
+
+ if ($yieldErr) {
+ $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+
+ if (isset($err[0])) {
+ if ($clearOutput) {
+ $this->clearErrorOutput();
+ } else {
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+ }
+
+ yield self::ERR => $err;
+ }
+ }
+
+ if (!$blocking && !isset($out[0]) && !isset($err[0])) {
+ yield self::OUT => '';
+ }
+
+ $this->checkTimeout();
+ $this->readPipesForOutput(__FUNCTION__, $blocking);
+ }
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearOutput()
+ {
+ ftruncate($this->stdout, 0);
+ fseek($this->stdout, 0);
+ $this->incrementalOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current error output of the process (STDERR).
+ *
+ * @return string The process error output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the errorOutput incrementally.
+ *
+ * In comparison with the getErrorOutput method which always return the
+ * whole error output, this one returns the new error output since the last
+ * call.
+ *
+ * @return string The process error output since the last call
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearErrorOutput()
+ {
+ ftruncate($this->stderr, 0);
+ fseek($this->stderr, 0);
+ $this->incrementalErrorOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the exit code returned by the process.
+ *
+ * @return int|null The exit status code, null if the Process is not terminated
+ */
+ public function getExitCode()
+ {
+ $this->updateStatus(false);
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns a string representation for the exit code returned by the process.
+ *
+ * This method relies on the Unix exit code status standardization
+ * and might not be relevant for other operating systems.
+ *
+ * @return string|null A string representation for the exit status code, null if the Process is not terminated
+ *
+ * @see http://tldp.org/LDP/abs/html/exitcodes.html
+ * @see http://en.wikipedia.org/wiki/Unix_signal
+ */
+ public function getExitCodeText()
+ {
+ if (null === $exitcode = $this->getExitCode()) {
+ return null;
+ }
+
+ return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
+ }
+
+ /**
+ * Checks if the process ended successfully.
+ *
+ * @return bool true if the process ended successfully, false otherwise
+ */
+ public function isSuccessful()
+ {
+ return 0 === $this->getExitCode();
+ }
+
+ /**
+ * Returns true if the child process has been terminated by an uncaught signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenSignaled()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['signaled'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to terminate its execution.
+ *
+ * It is only meaningful if hasBeenSignaled() returns true.
+ *
+ * @return int
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getTermSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+ }
+
+ return $this->processInformation['termsig'];
+ }
+
+ /**
+ * Returns true if the child process has been stopped by a signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenStopped()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopped'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to stop its execution.
+ *
+ * It is only meaningful if hasBeenStopped() returns true.
+ *
+ * @return int
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getStopSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopsig'];
+ }
+
+ /**
+ * Checks if the process is currently running.
+ *
+ * @return bool true if the process is currently running, false otherwise
+ */
+ public function isRunning()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return false;
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['running'];
+ }
+
+ /**
+ * Checks if the process has been started with no regard to the current state.
+ *
+ * @return bool true if status is ready, false otherwise
+ */
+ public function isStarted()
+ {
+ return self::STATUS_READY != $this->status;
+ }
+
+ /**
+ * Checks if the process is terminated.
+ *
+ * @return bool true if process is terminated, false otherwise
+ */
+ public function isTerminated()
+ {
+ $this->updateStatus(false);
+
+ return self::STATUS_TERMINATED == $this->status;
+ }
+
+ /**
+ * Gets the process status.
+ *
+ * The status is one of: ready, started, terminated.
+ *
+ * @return string The current process status
+ */
+ public function getStatus()
+ {
+ $this->updateStatus(false);
+
+ return $this->status;
+ }
+
+ /**
+ * Stops the process.
+ *
+ * @param int|float $timeout The timeout in seconds
+ * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
+ *
+ * @return int|null The exit-code of the process or null if it's not running
+ */
+ public function stop($timeout = 10, $signal = null)
+ {
+ $timeoutMicro = microtime(true) + $timeout;
+ if ($this->isRunning()) {
+ // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
+ $this->doSignal(15, false);
+ do {
+ usleep(1000);
+ } while ($this->isRunning() && microtime(true) < $timeoutMicro);
+
+ if ($this->isRunning()) {
+ // Avoid exception here: process is supposed to be running, but it might have stopped just
+ // after this line. In any case, let's silently discard the error, we cannot do anything.
+ $this->doSignal($signal ?: 9, false);
+ }
+ }
+
+ if ($this->isRunning()) {
+ if (isset($this->fallbackStatus['pid'])) {
+ unset($this->fallbackStatus['pid']);
+
+ return $this->stop(0, $signal);
+ }
+ $this->close();
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Adds a line to the STDOUT stream.
+ *
+ * @internal
+ */
+ public function addOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stdout, 0, SEEK_END);
+ fwrite($this->stdout, $line);
+ fseek($this->stdout, $this->incrementalOutputOffset);
+ }
+
+ /**
+ * Adds a line to the STDERR stream.
+ *
+ * @internal
+ */
+ public function addErrorOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stderr, 0, SEEK_END);
+ fwrite($this->stderr, $line);
+ fseek($this->stderr, $this->incrementalErrorOutputOffset);
+ }
+
+ /**
+ * Gets the command line to be executed.
+ *
+ * @return string The command to execute
+ */
+ public function getCommandLine()
+ {
+ return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
+ }
+
+ /**
+ * Sets the command line to be executed.
+ *
+ * @param string|array $commandline The command to execute
+ *
+ * @return $this
+ *
+ * @deprecated since Symfony 4.2.
+ */
+ public function setCommandLine($commandline)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->commandline = $commandline;
+
+ return $this;
+ }
+
+ /**
+ * Gets the process timeout (max. runtime).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Gets the process idle timeout (max. time since last output).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getIdleTimeout()
+ {
+ return $this->idleTimeout;
+ }
+
+ /**
+ * Sets the process timeout (max. runtime) in seconds.
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setTimeout($timeout)
+ {
+ $this->timeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process idle timeout (max. time since last output).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return $this
+ *
+ * @throws LogicException if the output is disabled
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setIdleTimeout($timeout)
+ {
+ if (null !== $timeout && $this->outputDisabled) {
+ throw new LogicException('Idle timeout can not be set while the output is disabled.');
+ }
+
+ $this->idleTimeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Enables or disables the TTY mode.
+ *
+ * @param bool $tty True to enabled and false to disable
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the TTY mode is not supported
+ */
+ public function setTty($tty)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
+ throw new RuntimeException('TTY mode is not supported on Windows platform.');
+ }
+
+ if ($tty && !self::isTtySupported()) {
+ throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
+ }
+
+ $this->tty = (bool) $tty;
+
+ return $this;
+ }
+
+ /**
+ * Checks if the TTY mode is enabled.
+ *
+ * @return bool true if the TTY mode is enabled, false otherwise
+ */
+ public function isTty()
+ {
+ return $this->tty;
+ }
+
+ /**
+ * Sets PTY mode.
+ *
+ * @param bool $bool
+ *
+ * @return $this
+ */
+ public function setPty($bool)
+ {
+ $this->pty = (bool) $bool;
+
+ return $this;
+ }
+
+ /**
+ * Returns PTY state.
+ *
+ * @return bool
+ */
+ public function isPty()
+ {
+ return $this->pty;
+ }
+
+ /**
+ * Gets the working directory.
+ *
+ * @return string|null The current working directory or null on failure
+ */
+ public function getWorkingDirectory()
+ {
+ if (null === $this->cwd) {
+ // getcwd() will return false if any one of the parent directories does not have
+ // the readable or search mode set, even if the current directory does
+ return getcwd() ?: null;
+ }
+
+ return $this->cwd;
+ }
+
+ /**
+ * Sets the current working directory.
+ *
+ * @param string $cwd The new working directory
+ *
+ * @return $this
+ */
+ public function setWorkingDirectory($cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Gets the environment variables.
+ *
+ * @return array The current environment variables
+ */
+ public function getEnv()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Sets the environment variables.
+ *
+ * Each environment variable value should be a string.
+ * If it is an array, the variable is ignored.
+ * If it is false or null, it will be removed when
+ * env vars are otherwise inherited.
+ *
+ * That happens in PHP when 'argv' is registered into
+ * the $_ENV array for instance.
+ *
+ * @param array $env The new environment variables
+ *
+ * @return $this
+ */
+ public function setEnv(array $env)
+ {
+ // Process can not handle env values that are arrays
+ $env = array_filter($env, function ($value) {
+ return !\is_array($value);
+ });
+
+ $this->env = $env;
+
+ return $this;
+ }
+
+ /**
+ * Gets the Process input.
+ *
+ * @return resource|string|\Iterator|null The Process input
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Sets the input.
+ *
+ * This content will be passed to the underlying process standard input.
+ *
+ * @param string|int|float|bool|resource|\Traversable|null $input The content
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is running
+ */
+ public function setInput($input)
+ {
+ if ($this->isRunning()) {
+ throw new LogicException('Input can not be set while the process is running.');
+ }
+
+ $this->input = ProcessUtils::validateInput(__METHOD__, $input);
+
+ return $this;
+ }
+
+ /**
+ * Sets whether environment variables will be inherited or not.
+ *
+ * @param bool $inheritEnv
+ *
+ * @return $this
+ */
+ public function inheritEnvironmentVariables($inheritEnv = true)
+ {
+ if (!$inheritEnv) {
+ throw new InvalidArgumentException('Not inheriting environment variables is not supported.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Performs a check between the timeout definition and the time the process started.
+ *
+ * In case you run a background process (with the start method), you should
+ * trigger this method regularly to ensure the process timeout
+ *
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function checkTimeout()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+ }
+
+ if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
+ }
+ }
+
+ /**
+ * Returns whether TTY is supported on the current operating system.
+ */
+ public static function isTtySupported(): bool
+ {
+ static $isTtySupported;
+
+ if (null === $isTtySupported) {
+ $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
+ }
+
+ return $isTtySupported;
+ }
+
+ /**
+ * Returns whether PTY is supported on the current operating system.
+ *
+ * @return bool
+ */
+ public static function isPtySupported()
+ {
+ static $result;
+
+ if (null !== $result) {
+ return $result;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return $result = false;
+ }
+
+ return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
+ }
+
+ /**
+ * Creates the descriptors needed by the proc_open.
+ */
+ private function getDescriptors(): array
+ {
+ if ($this->input instanceof \Iterator) {
+ $this->input->rewind();
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
+ } else {
+ $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
+ }
+
+ return $this->processPipes->getDescriptors();
+ }
+
+ /**
+ * Builds up the callback used by wait().
+ *
+ * The callbacks adds all occurred output to the specific buffer and calls
+ * the user callback (if present) with the received output.
+ *
+ * @param callable|null $callback The user defined PHP callback
+ *
+ * @return \Closure A PHP closure
+ */
+ protected function buildCallback(callable $callback = null)
+ {
+ if ($this->outputDisabled) {
+ return function ($type, $data) use ($callback): bool {
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ $out = self::OUT;
+
+ return function ($type, $data) use ($callback, $out): bool {
+ if ($out == $type) {
+ $this->addOutput($data);
+ } else {
+ $this->addErrorOutput($data);
+ }
+
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ /**
+ * Updates the status of the process, reads pipes.
+ *
+ * @param bool $blocking Whether to use a blocking read call
+ */
+ protected function updateStatus($blocking)
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ $this->processInformation = proc_get_status($this->process);
+ $running = $this->processInformation['running'];
+
+ $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ if ($this->fallbackStatus && $this->isSigchildEnabled()) {
+ $this->processInformation = $this->fallbackStatus + $this->processInformation;
+ }
+
+ if (!$running) {
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
+ *
+ * @return bool
+ */
+ protected function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!\function_exists('phpinfo')) {
+ return self::$sigchild = false;
+ }
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+
+ return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Reads pipes for the freshest output.
+ *
+ * @param string $caller The name of the method that needs fresh outputs
+ * @param bool $blocking Whether to use blocking calls or not
+ *
+ * @throws LogicException in case output has been disabled or process is not started
+ */
+ private function readPipesForOutput(string $caller, bool $blocking = false)
+ {
+ if ($this->outputDisabled) {
+ throw new LogicException('Output has been disabled.');
+ }
+
+ $this->requireProcessIsStarted($caller);
+
+ $this->updateStatus($blocking);
+ }
+
+ /**
+ * Validates and returns the filtered timeout.
+ *
+ * @throws InvalidArgumentException if the given timeout is a negative number
+ */
+ private function validateTimeout(?float $timeout): ?float
+ {
+ $timeout = (float) $timeout;
+
+ if (0.0 === $timeout) {
+ $timeout = null;
+ } elseif ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ return $timeout;
+ }
+
+ /**
+ * Reads pipes, executes callback.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close file handles or not
+ */
+ private function readPipes(bool $blocking, bool $close)
+ {
+ $result = $this->processPipes->readAndWrite($blocking, $close);
+
+ $callback = $this->callback;
+ foreach ($result as $type => $data) {
+ if (3 !== $type) {
+ $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ }
+
+ /**
+ * Closes process resource, closes file handles, sets the exitcode.
+ *
+ * @return int The exitcode
+ */
+ private function close(): int
+ {
+ $this->processPipes->close();
+ if (\is_resource($this->process)) {
+ proc_close($this->process);
+ }
+ $this->exitcode = $this->processInformation['exitcode'];
+ $this->status = self::STATUS_TERMINATED;
+
+ if (-1 === $this->exitcode) {
+ if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
+ // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
+ $this->exitcode = 128 + $this->processInformation['termsig'];
+ } elseif ($this->isSigchildEnabled()) {
+ $this->processInformation['signaled'] = true;
+ $this->processInformation['termsig'] = -1;
+ }
+ }
+
+ // Free memory from self-reference callback created by buildCallback
+ // Doing so in other contexts like __destruct or by garbage collector is ineffective
+ // Now pipes are closed, so the callback is no longer necessary
+ $this->callback = null;
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Resets data related to the latest run of the process.
+ */
+ private function resetProcessData()
+ {
+ $this->starttime = null;
+ $this->callback = null;
+ $this->exitcode = null;
+ $this->fallbackStatus = [];
+ $this->processInformation = null;
+ $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
+ $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
+ $this->process = null;
+ $this->latestSignal = null;
+ $this->status = self::STATUS_READY;
+ $this->incrementalOutputOffset = 0;
+ $this->incrementalErrorOutputOffset = 0;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ * @param bool $throwException Whether to throw exception in case signal failed
+ *
+ * @return bool True if the signal was sent successfully, false otherwise
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ private function doSignal(int $signal, bool $throwException): bool
+ {
+ if (null === $pid = $this->getPid()) {
+ if ($throwException) {
+ throw new LogicException('Can not send signal on a non running process.');
+ }
+
+ return false;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
+ if ($exitCode && $this->isRunning()) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
+ }
+
+ return false;
+ }
+ } else {
+ if (!$this->isSigchildEnabled()) {
+ $ok = @proc_terminate($this->process, $signal);
+ } elseif (\function_exists('posix_kill')) {
+ $ok = @posix_kill($pid, $signal);
+ } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
+ $ok = false === fgets($pipes[2]);
+ }
+ if (!$ok) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
+ }
+
+ return false;
+ }
+ }
+
+ $this->latestSignal = $signal;
+ $this->fallbackStatus['signaled'] = true;
+ $this->fallbackStatus['exitcode'] = -1;
+ $this->fallbackStatus['termsig'] = $this->latestSignal;
+
+ return true;
+ }
+
+ private function prepareWindowsCommandLine(string $cmd, array &$env)
+ {
+ $uid = uniqid('', true);
+ $varCount = 0;
+ $varCache = [];
+ $cmd = preg_replace_callback(
+ '/"(?:(
+ [^"%!^]*+
+ (?:
+ (?: !LF! | "(?:\^[%!^])?+" )
+ [^"%!^]*+
+ )++
+ ) | [^"]*+ )"/x',
+ function ($m) use (&$env, &$varCache, &$varCount, $uid) {
+ if (!isset($m[1])) {
+ return $m[0];
+ }
+ if (isset($varCache[$m[0]])) {
+ return $varCache[$m[0]];
+ }
+ if (false !== strpos($value = $m[1], "\0")) {
+ $value = str_replace("\0", '?', $value);
+ }
+ if (false === strpbrk($value, "\"%!\n")) {
+ return '"'.$value.'"';
+ }
+
+ $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
+ $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
+ $var = $uid.++$varCount;
+
+ $env[$var] = $value;
+
+ return $varCache[$m[0]] = '!'.$var.'!';
+ },
+ $cmd
+ );
+
+ $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
+ foreach ($this->processPipes->getFiles() as $offset => $filename) {
+ $cmd .= ' '.$offset.'>"'.$filename.'"';
+ }
+
+ return $cmd;
+ }
+
+ /**
+ * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
+ *
+ * @throws LogicException if the process has not run
+ */
+ private function requireProcessIsStarted(string $functionName)
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
+ *
+ * @throws LogicException if the process is not yet terminated
+ */
+ private function requireProcessIsTerminated(string $functionName)
+ {
+ if (!$this->isTerminated()) {
+ throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Escapes a string to be used as a shell argument.
+ */
+ private function escapeArgument(?string $argument): string
+ {
+ if ('' === $argument || null === $argument) {
+ return '""';
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ return "'".str_replace("'", "'\\''", $argument)."'";
+ }
+ if (false !== strpos($argument, "\0")) {
+ $argument = str_replace("\0", '?', $argument);
+ }
+ if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
+ return $argument;
+ }
+ $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
+
+ return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
+ }
+
+ private function getDefaultEnv()
+ {
+ $env = [];
+
+ foreach ($_SERVER as $k => $v) {
+ if (\is_string($v) && false !== $v = getenv($k)) {
+ $env[$k] = $v;
+ }
+ }
+
+ foreach ($_ENV as $k => $v) {
+ if (\is_string($v)) {
+ $env[$k] = $v;
+ }
+ }
+
+ return $env;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/ProcessUtils.php b/src/mpg25-instagram-api/vendor/symfony/process/ProcessUtils.php
new file mode 100755
index 0000000..2f9c4be
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/ProcessUtils.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * ProcessUtils is a bunch of utility methods.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Martin Hasoň
+ */
+class ProcessUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Validates and normalizes a Process input.
+ *
+ * @param string $caller The name of method call that validates the input
+ * @param mixed $input The input to validate
+ *
+ * @return mixed The validated input
+ *
+ * @throws InvalidArgumentException In case the input is not valid
+ */
+ public static function validateInput($caller, $input)
+ {
+ if (null !== $input) {
+ if (\is_resource($input)) {
+ return $input;
+ }
+ if (\is_string($input)) {
+ return $input;
+ }
+ if (is_scalar($input)) {
+ return (string) $input;
+ }
+ if ($input instanceof Process) {
+ return $input->getIterator($input::ITER_SKIP_ERR);
+ }
+ if ($input instanceof \Iterator) {
+ return $input;
+ }
+ if ($input instanceof \Traversable) {
+ return new \IteratorIterator($input);
+ }
+
+ throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller));
+ }
+
+ return $input;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/README.md b/src/mpg25-instagram-api/vendor/symfony/process/README.md
new file mode 100755
index 0000000..b7ca5b4
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/README.md
@@ -0,0 +1,13 @@
+Process Component
+=================
+
+The Process component executes commands in sub-processes.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/process.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/ErrorProcessInitiator.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/ErrorProcessInitiator.php
new file mode 100755
index 0000000..c37aeb5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/ErrorProcessInitiator.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Process;
+
+require \dirname(__DIR__).'/vendor/autoload.php';
+
+list('e' => $php) = getopt('e:') + ['e' => 'php'];
+
+try {
+ $process = new Process("exec $php -r \"echo 'ready'; trigger_error('error', E_USER_ERROR);\"");
+ $process->start();
+ $process->setTimeout(0.5);
+ while (false === strpos($process->getOutput(), 'ready')) {
+ usleep(1000);
+ }
+ $process->signal(SIGSTOP);
+ $process->wait();
+
+ return $process->getExitCode();
+} catch (ProcessTimedOutException $t) {
+ echo $t->getMessage().PHP_EOL;
+
+ return 1;
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/ExecutableFinderTest.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/ExecutableFinderTest.php
new file mode 100755
index 0000000..a400273
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/ExecutableFinderTest.php
@@ -0,0 +1,178 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\ExecutableFinder;
+
+/**
+ * @author Chris Smith
+ */
+class ExecutableFinderTest extends TestCase
+{
+ private $path;
+
+ protected function tearDown(): void
+ {
+ if ($this->path) {
+ // Restore path if it was changed.
+ putenv('PATH='.$this->path);
+ }
+ }
+
+ private function setPath($path)
+ {
+ $this->path = getenv('PATH');
+ putenv('PATH='.$path);
+ }
+
+ public function testFind()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath(\dirname(PHP_BINARY));
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindWithDefault()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $expected = 'defaultValue';
+
+ $this->setPath('');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find('foo', $expected);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testFindWithNullAsDefault()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $finder = new ExecutableFinder();
+
+ $result = $finder->find('foo');
+
+ $this->assertNull($result);
+ }
+
+ public function testFindWithExtraDirs()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $extraDirs = [\dirname(PHP_BINARY)];
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindWithOpenBaseDir()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->iniSet('open_basedir', \dirname(PHP_BINARY).PATH_SEPARATOR.'/');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindProcessInOpenBasedir()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ $this->setPath('');
+ $this->iniSet('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), false);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ /**
+ * @requires PHP 5.4
+ */
+ public function testFindBatchExecutableOnWindows()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Can be only tested on windows');
+ }
+
+ $target = tempnam(sys_get_temp_dir(), 'example-windows-executable');
+
+ touch($target);
+ touch($target.'.BAT');
+
+ $this->assertFalse(is_executable($target));
+
+ $this->setPath(sys_get_temp_dir());
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find(basename($target), false);
+
+ unlink($target);
+ unlink($target.'.BAT');
+
+ $this->assertSamePath($target.'.BAT', $result);
+ }
+
+ private function assertSamePath($expected, $tested)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->assertEquals(strtolower($expected), strtolower($tested));
+ } else {
+ $this->assertEquals($expected, $tested);
+ }
+ }
+
+ private function getPhpBinaryName()
+ {
+ return basename(PHP_BINARY, '\\' === \DIRECTORY_SEPARATOR ? '.exe' : '');
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/KillableProcessWithOutput.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/KillableProcessWithOutput.php
new file mode 100755
index 0000000..28a6a27
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/KillableProcessWithOutput.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+$outputs = [
+ 'First iteration output',
+ 'Second iteration output',
+ 'One more iteration output',
+ 'This took more time',
+];
+
+$iterationTime = 10000;
+
+foreach ($outputs as $output) {
+ usleep($iterationTime);
+ $iterationTime *= 10;
+ echo $output."\n";
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/NonStopableProcess.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/NonStopableProcess.php
new file mode 100755
index 0000000..5643259
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/NonStopableProcess.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds.
+ *
+ * @args duration Run this script with a custom duration
+ *
+ * @example `php NonStopableProcess.php 42` will run the script for 42 seconds
+ */
+function handleSignal($signal)
+{
+ switch ($signal) {
+ case SIGTERM:
+ $name = 'SIGTERM';
+ break;
+ case SIGINT:
+ $name = 'SIGINT';
+ break;
+ default:
+ $name = $signal.' (unknown)';
+ break;
+ }
+
+ echo "signal $name\n";
+}
+
+pcntl_signal(SIGTERM, 'handleSignal');
+pcntl_signal(SIGINT, 'handleSignal');
+
+echo 'received ';
+
+$duration = isset($argv[1]) ? (int) $argv[1] : 3;
+$start = microtime(true);
+
+while ($duration > (microtime(true) - $start)) {
+ usleep(10000);
+ pcntl_signal_dispatch();
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/PhpExecutableFinderTest.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
new file mode 100755
index 0000000..338bb4e
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+
+/**
+ * @author Robert Schönthal
+ */
+class PhpExecutableFinderTest extends TestCase
+{
+ /**
+ * tests find() with the constant PHP_BINARY.
+ */
+ public function testFind()
+ {
+ $f = new PhpExecutableFinder();
+
+ $current = PHP_BINARY;
+ $args = 'phpdbg' === \PHP_SAPI ? ' -qrr' : '';
+
+ $this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP');
+ $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');
+ }
+
+ /**
+ * tests find() with the env var PHP_PATH.
+ */
+ public function testFindArguments()
+ {
+ $f = new PhpExecutableFinder();
+
+ if ('phpdbg' === \PHP_SAPI) {
+ $this->assertEquals($f->findArguments(), ['-qrr'], '::findArguments() returns phpdbg arguments');
+ } else {
+ $this->assertEquals($f->findArguments(), [], '::findArguments() returns no arguments');
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/PhpProcessTest.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/PhpProcessTest.php
new file mode 100755
index 0000000..b7b21eb
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/PhpProcessTest.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\PhpProcess;
+
+class PhpProcessTest extends TestCase
+{
+ public function testNonBlockingWorks()
+ {
+ $expected = 'hello world!';
+ $process = new PhpProcess(<<start();
+ $process->wait();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCommandLine()
+ {
+ $process = new PhpProcess(<<<'PHP'
+getCommandLine();
+
+ $process->start();
+ $this->assertStringContainsString($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');
+
+ $process->wait();
+ $this->assertStringContainsString($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');
+
+ $this->assertSame(PHP_VERSION.\PHP_SAPI, $process->getOutput());
+ }
+
+ public function testPassingPhpExplicitly()
+ {
+ $finder = new PhpExecutableFinder();
+ $php = array_merge([$finder->find(false)], $finder->findArguments());
+
+ $expected = 'hello world!';
+ $script = <<run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
new file mode 100755
index 0000000..9ea8981
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+define('ERR_SELECT_FAILED', 1);
+define('ERR_TIMEOUT', 2);
+define('ERR_READ_FAILED', 3);
+define('ERR_WRITE_FAILED', 4);
+
+$read = [STDIN];
+$write = [STDOUT, STDERR];
+
+stream_set_blocking(STDIN, 0);
+stream_set_blocking(STDOUT, 0);
+stream_set_blocking(STDERR, 0);
+
+$out = $err = '';
+while ($read || $write) {
+ $r = $read;
+ $w = $write;
+ $e = null;
+ $n = stream_select($r, $w, $e, 5);
+
+ if (false === $n) {
+ die(ERR_SELECT_FAILED);
+ } elseif ($n < 1) {
+ die(ERR_TIMEOUT);
+ }
+
+ if (in_array(STDOUT, $w) && strlen($out) > 0) {
+ $written = fwrite(STDOUT, (string) $out, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $out = (string) substr($out, $written);
+ }
+ if (null === $read && '' === $out) {
+ $write = array_diff($write, [STDOUT]);
+ }
+
+ if (in_array(STDERR, $w) && strlen($err) > 0) {
+ $written = fwrite(STDERR, (string) $err, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $err = (string) substr($err, $written);
+ }
+ if (null === $read && '' === $err) {
+ $write = array_diff($write, [STDERR]);
+ }
+
+ if ($r) {
+ $str = fread(STDIN, 32768);
+ if (false !== $str) {
+ $out .= $str;
+ $err .= $str;
+ }
+ if (false === $str || feof(STDIN)) {
+ $read = null;
+ if (!feof(STDIN)) {
+ die(ERR_READ_FAILED);
+ }
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
new file mode 100755
index 0000000..f820430
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
@@ -0,0 +1,133 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+
+/**
+ * @author Sebastian Marek
+ */
+class ProcessFailedExceptionTest extends TestCase
+{
+ /**
+ * tests ProcessFailedException throws exception if the process was successful.
+ */
+ public function testProcessFailedExceptionThrowsException()
+ {
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful'])->setConstructorArgs([['php']])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(true);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Expected a failed process, but the given process was successful.');
+
+ new ProcessFailedException($process);
+ }
+
+ /**
+ * tests ProcessFailedException uses information from process output
+ * to generate exception message.
+ */
+ public function testProcessFailedExceptionPopulatesInformationFromProcessOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $output = 'Command output';
+ $errorOutput = 'FATAL: Unexpected error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(false);
+
+ $process->expects($this->once())
+ ->method('getOutput')
+ ->willReturn($output);
+
+ $process->expects($this->once())
+ ->method('getErrorOutput')
+ ->willReturn($errorOutput);
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->willReturn($exitCode);
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->willReturn($exitText);
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->willReturn(false);
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->willReturn($workingDirectory);
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}",
+ str_replace("'php'", 'php', $exception->getMessage())
+ );
+ }
+
+ /**
+ * Tests that ProcessFailedException does not extract information from
+ * process output if it was previously disabled.
+ */
+ public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(false);
+
+ $process->expects($this->never())
+ ->method('getOutput');
+
+ $process->expects($this->never())
+ ->method('getErrorOutput');
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->willReturn($exitCode);
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->willReturn($exitText);
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->willReturn(true);
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->willReturn($workingDirectory);
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}",
+ str_replace("'php'", 'php', $exception->getMessage())
+ );
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/ProcessTest.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/ProcessTest.php
new file mode 100755
index 0000000..adff6ea
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/ProcessTest.php
@@ -0,0 +1,1515 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\InputStream;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * @author Robert Schönthal
+ */
+class ProcessTest extends TestCase
+{
+ private static $phpBin;
+ private static $process;
+ private static $sigchild;
+
+ public static function setUpBeforeClass(): void
+ {
+ $phpBin = new PhpExecutableFinder();
+ self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === \PHP_SAPI ? 'php' : $phpBin->find());
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ protected function tearDown(): void
+ {
+ if (self::$process) {
+ self::$process->stop(0);
+ self::$process = null;
+ }
+ }
+
+ public function testInvalidCwd()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessageRegExp('/The provided cwd ".*" does not exist\./');
+ try {
+ // Check that it works fine if the CWD exists
+ $cmd = new Process(['echo', 'test'], __DIR__);
+ $cmd->run();
+ } catch (\Exception $e) {
+ $this->fail($e);
+ }
+
+ $cmd = new Process(['echo', 'test'], __DIR__.'/notfound/');
+ $cmd->run();
+ }
+
+ public function testThatProcessDoesNotThrowWarningDuringRun()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is transient on Windows');
+ }
+ @trigger_error('Test Error', E_USER_NOTICE);
+ $process = $this->getProcessForCode('sleep(3)');
+ $process->run();
+ $actualError = error_get_last();
+ $this->assertEquals('Test Error', $actualError['message']);
+ $this->assertEquals(E_USER_NOTICE, $actualError['type']);
+ }
+
+ public function testNegativeTimeoutFromConstructor()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
+ $this->getProcess('', null, null, null, -1);
+ }
+
+ public function testNegativeTimeoutFromSetter()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
+ $p = $this->getProcess('');
+ $p->setTimeout(-1);
+ }
+
+ public function testFloatAndNullTimeout()
+ {
+ $p = $this->getProcess('');
+
+ $p->setTimeout(10);
+ $this->assertSame(10.0, $p->getTimeout());
+
+ $p->setTimeout(null);
+ $this->assertNull($p->getTimeout());
+
+ $p->setTimeout(0.0);
+ $this->assertNull($p->getTimeout());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $p = $this->getProcess([self::$phpBin, __DIR__.'/NonStopableProcess.php', 30]);
+ $p->start();
+
+ while ($p->isRunning() && false === strpos($p->getOutput(), 'received')) {
+ usleep(1000);
+ }
+
+ if (!$p->isRunning()) {
+ throw new \LogicException('Process is not running: '.$p->getErrorOutput());
+ }
+
+ $start = microtime(true);
+ $p->stop(0.1);
+
+ $p->wait();
+
+ $this->assertLessThan(15, microtime(true) - $start);
+ }
+
+ public function testWaitUntilSpecificOutput()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestIncomplete('This test is too transient on Windows, help wanted to improve it');
+ }
+
+ $p = $this->getProcess([self::$phpBin, __DIR__.'/KillableProcessWithOutput.php']);
+ $p->start();
+
+ $start = microtime(true);
+
+ $completeOutput = '';
+ $result = $p->waitUntil(function ($type, $output) use (&$completeOutput) {
+ return false !== strpos($completeOutput .= $output, 'One more');
+ });
+ $this->assertTrue($result);
+ $this->assertLessThan(20, microtime(true) - $start);
+ $this->assertStringStartsWith("First iteration output\nSecond iteration output\nOne more", $completeOutput);
+ $p->stop();
+ }
+
+ public function testWaitUntilCanReturnFalse()
+ {
+ $p = $this->getProcess('echo foo');
+ $p->start();
+ $this->assertFalse($p->waitUntil(function () { return false; }));
+ }
+
+ public function testAllOutputIsActuallyReadOnTermination()
+ {
+ // this code will result in a maximum of 2 reads of 8192 bytes by calling
+ // start() and isRunning(). by the time getOutput() is called the process
+ // has terminated so the internal pipes array is already empty. normally
+ // the call to start() will not read any data as the process will not have
+ // generated output, but this is non-deterministic so we must count it as
+ // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
+ // another byte which will never be read.
+ $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
+
+ $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
+ $p = $this->getProcessForCode($code);
+
+ $p->start();
+
+ // Don't call Process::run nor Process::wait to avoid any read of pipes
+ $h = new \ReflectionProperty($p, 'process');
+ $h->setAccessible(true);
+ $h = $h->getValue($p);
+ $s = @proc_get_status($h);
+
+ while (!empty($s['running'])) {
+ usleep(1000);
+ $s = proc_get_status($h);
+ }
+
+ $o = $p->getOutput();
+
+ $this->assertEquals($expectedOutputSize, \strlen($o));
+ }
+
+ public function testCallbacksAreExecutedWithStart()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->start(function ($type, $buffer) use (&$data) {
+ $data .= $buffer;
+ });
+
+ $process->wait();
+
+ $this->assertSame('foo'.PHP_EOL, $data);
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider responsesCodeProvider
+ */
+ public function testProcessResponses($expected, $getter, $code)
+ {
+ $p = $this->getProcessForCode($code);
+ $p->run();
+
+ $this->assertSame($expected, $p->$getter());
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider pipesCodeProvider
+ */
+ public function testProcessPipes($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($expected);
+ $p->run();
+
+ $this->assertEquals($expectedLength, \strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
+ }
+
+ /**
+ * @dataProvider pipesCodeProvider
+ */
+ public function testSetStreamAsInput($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $stream = fopen('php://temporary', 'w+');
+ fwrite($stream, $expected);
+ rewind($stream);
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($stream);
+ $p->run();
+
+ fclose($stream);
+
+ $this->assertEquals($expectedLength, \strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
+ }
+
+ public function testLiveStreamAsInput()
+ {
+ $stream = fopen('php://memory', 'r+');
+ fwrite($stream, 'hello');
+ rewind($stream);
+
+ $p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p->setInput($stream);
+ $p->start(function ($type, $data) use ($stream) {
+ if ('hello' === $data) {
+ fclose($stream);
+ }
+ });
+ $p->wait();
+
+ $this->assertSame('hello', $p->getOutput());
+ }
+
+ public function testSetInputWhileRunningThrowsAnException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Input can not be set while the process is running.');
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->start();
+ try {
+ $process->setInput('foobar');
+ $process->stop();
+ $this->fail('A LogicException should have been raised.');
+ } catch (LogicException $e) {
+ }
+ $process->stop();
+
+ throw $e;
+ }
+
+ /**
+ * @dataProvider provideInvalidInputValues
+ */
+ public function testInvalidInput($value)
+ {
+ $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
+ $this->expectExceptionMessage('Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources.');
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ }
+
+ public function provideInvalidInputValues()
+ {
+ return [
+ [[]],
+ [new NonStringifiable()],
+ ];
+ }
+
+ /**
+ * @dataProvider provideInputValues
+ */
+ public function testValidInput($expected, $value)
+ {
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ $this->assertSame($expected, $process->getInput());
+ }
+
+ public function provideInputValues()
+ {
+ return [
+ [null, null],
+ ['24.5', 24.5],
+ ['input data', 'input data'],
+ ];
+ }
+
+ public function chainedCommandsOutputProvider()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return [
+ ["2 \r\n2\r\n", '&&', '2'],
+ ];
+ }
+
+ return [
+ ["1\n1\n", ';', '1'],
+ ["2\n2\n", '&&', '2'],
+ ];
+ }
+
+ /**
+ * @dataProvider chainedCommandsOutputProvider
+ */
+ public function testChainedCommandsOutput($expected, $operator, $input)
+ {
+ $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
+ $process->run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCallbackIsExecutedForOutput()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = 'foo' === $buffer;
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+ $p->disableOutput();
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = 'foo' === $buffer;
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testGetErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
+ }
+
+ public function testFlushErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $p->clearErrorOutput();
+ $this->assertEmpty($p->getErrorOutput());
+ }
+
+ /**
+ * @dataProvider provideIncrementalOutput
+ */
+ public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
+ {
+ $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
+
+ $p = $this->getProcessForCode('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');');
+
+ $h = fopen($lock, 'w');
+ flock($h, LOCK_EX);
+
+ $p->start();
+
+ foreach (['foo', 'bar'] as $s) {
+ while (false === strpos($p->$getOutput(), $s)) {
+ usleep(1000);
+ }
+
+ $this->assertSame($s, $p->$getIncrementalOutput());
+ $this->assertSame('', $p->$getIncrementalOutput());
+
+ flock($h, LOCK_UN);
+ }
+
+ fclose($h);
+ }
+
+ public function provideIncrementalOutput()
+ {
+ return [
+ ['getOutput', 'getIncrementalOutput', 'php://stdout'],
+ ['getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'],
+ ];
+ }
+
+ public function testGetOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
+ }
+
+ public function testFlushOutput()
+ {
+ $p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}');
+
+ $p->run();
+ $p->clearOutput();
+ $this->assertEmpty($p->getOutput());
+ }
+
+ public function testZeroAsOutput()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
+ $p = $this->getProcess('echo | set /p dummyName=0');
+ } else {
+ $p = $this->getProcess('printf 0');
+ }
+
+ $p->run();
+ $this->assertSame('0', $p->getOutput());
+ }
+
+ public function testExitCodeCommandFailed()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX exit code');
+ }
+
+ // such command run in bash return an exitcode 127
+ $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
+ $process->run();
+
+ $this->assertGreaterThan(0, $process->getExitCode());
+ }
+
+ public function testTTYCommand()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
+ $process->setTty(true);
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->wait();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testTTYCommandExitCode()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(true);
+ $process->run();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testTTYInWindowsEnvironment()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('TTY mode is not supported on Windows platform.');
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is for Windows platform only');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(false);
+ $process->setTty(true);
+ }
+
+ public function testExitCodeTextIsNullWhenExitCodeIsNull()
+ {
+ $process = $this->getProcess('');
+ $this->assertNull($process->getExitCodeText());
+ }
+
+ public function testPTYCommand()
+ {
+ if (!Process::isPtySupported()) {
+ $this->markTestSkipped('PTY is not supported on this operating system.');
+ }
+
+ $process = $this->getProcess('echo "foo"');
+ $process->setPty(true);
+ $process->run();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ $this->assertEquals("foo\r\n", $process->getOutput());
+ }
+
+ public function testMustRun()
+ {
+ $process = $this->getProcess('echo foo');
+
+ $this->assertSame($process, $process->mustRun());
+ $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
+ }
+
+ public function testSuccessfulMustRunHasCorrectExitCode()
+ {
+ $process = $this->getProcess('echo foo')->mustRun();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testMustRunThrowsException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessFailedException');
+ $process = $this->getProcess('exit 1');
+ $process->mustRun();
+ }
+
+ public function testExitCodeText()
+ {
+ $process = $this->getProcess('');
+ $r = new \ReflectionObject($process);
+ $p = $r->getProperty('exitcode');
+ $p->setAccessible(true);
+
+ $p->setValue($process, 2);
+ $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
+ }
+
+ public function testStartIsNonBlocking()
+ {
+ $process = $this->getProcessForCode('usleep(500000);');
+ $start = microtime(true);
+ $process->start();
+ $end = microtime(true);
+ $this->assertLessThan(0.4, $end - $start);
+ $process->stop();
+ }
+
+ public function testUpdateStatus()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertGreaterThan(0, \strlen($process->getOutput()));
+ }
+
+ public function testGetExitCodeIsNullOnStart()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertNull($process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCodeIsNullOnWhenStartingAgain()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->run();
+ $this->assertEquals(0, $process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCode()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertSame(0, $process->getExitCode());
+ }
+
+ public function testStatus()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertFalse($process->isRunning());
+ $this->assertFalse($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_READY, $process->getStatus());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertTrue($process->isTerminated());
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testStop()
+ {
+ $process = $this->getProcessForCode('sleep(31);');
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop();
+ $this->assertFalse($process->isRunning());
+ }
+
+ public function testIsSuccessful()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsSuccessfulOnlyAfterTerminated()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->start();
+
+ $this->assertFalse($process->isSuccessful());
+
+ $process->wait();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsNotSuccessful()
+ {
+ $process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
+ $process->run();
+ $this->assertFalse($process->isSuccessful());
+ }
+
+ public function testProcessIsNotSignaled()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertFalse($process->hasBeenSignaled());
+ }
+
+ public function testProcessWithoutTermSignal()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertEquals(0, $process->getTermSignal());
+ }
+
+ public function testProcessIsSignaledIfStopped()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcessForCode('sleep(32);');
+ $process->start();
+ $process->stop();
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
+ }
+
+ public function testProcessThrowsExceptionWhenExternallySignaled()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessSignaledException');
+ $this->expectExceptionMessage('The process has been signaled with signal "9".');
+ if (!\function_exists('posix_kill')) {
+ $this->markTestSkipped('Function posix_kill is required.');
+ }
+
+ if (self::$sigchild) {
+ $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
+ }
+
+ $process = $this->getProcessForCode('sleep(32.1);');
+ $process->start();
+ posix_kill($process->getPid(), 9); // SIGKILL
+
+ $process->wait();
+ }
+
+ public function testRestart()
+ {
+ $process1 = $this->getProcessForCode('echo getmypid();');
+ $process1->run();
+ $process2 = $process1->restart();
+
+ $process2->wait(); // wait for output
+
+ // Ensure that both processed finished and the output is numeric
+ $this->assertFalse($process1->isRunning());
+ $this->assertFalse($process2->isRunning());
+ $this->assertIsNumeric($process1->getOutput());
+ $this->assertIsNumeric($process2->getOutput());
+
+ // Ensure that restart returned a new process by check that the output is different
+ $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
+ }
+
+ public function testRunProcessWithTimeout()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->run();
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testIterateOverProcessWithTimeout()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->start();
+ foreach ($process as $buffer);
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testCheckTimeoutOnNonStartedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $this->assertNull($process->checkTimeout());
+ }
+
+ public function testCheckTimeoutOnTerminatedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->checkTimeout());
+ }
+
+ public function testCheckTimeoutOnStartedProcess()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(33);');
+ $process->setTimeout(0.1);
+
+ $process->start();
+ $start = microtime(true);
+
+ try {
+ while ($process->isRunning()) {
+ $process->checkTimeout();
+ usleep(100000);
+ }
+ $this->fail('A ProcessTimedOutException should have been raised');
+ } catch (ProcessTimedOutException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testIdleTimeout()
+ {
+ $process = $this->getProcessForCode('sleep(34);');
+ $process->setTimeout(60);
+ $process->setIdleTimeout(0.1);
+
+ try {
+ $process->run();
+
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isIdleTimeout());
+ $this->assertFalse($e->isGeneralTimeout());
+ $this->assertEquals(0.1, $e->getExceededTimeout());
+ }
+ }
+
+ public function testIdleTimeoutNotExceededWhenOutputIsSent()
+ {
+ $process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
+ $process->setTimeout(1);
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'foo')) {
+ usleep(1000);
+ }
+
+ $process->setIdleTimeout(0.5);
+
+ try {
+ $process->wait();
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
+ $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
+ $this->assertEquals(1, $e->getExceededTimeout());
+ }
+ }
+
+ public function testStartAfterATimeout()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(35);');
+ $process->setTimeout(0.1);
+
+ try {
+ $process->run();
+ $this->fail('A ProcessTimedOutException should have been raised.');
+ } catch (ProcessTimedOutException $e) {
+ }
+ $this->assertFalse($process->isRunning());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function testGetPid()
+ {
+ $process = $this->getProcessForCode('sleep(36);');
+ $process->start();
+ $this->assertGreaterThan(0, $process->getPid());
+ $process->stop(0);
+ }
+
+ public function testGetPidIsNullBeforeStart()
+ {
+ $process = $this->getProcess('foo');
+ $this->assertNull($process->getPid());
+ }
+
+ public function testGetPidIsNullAfterRun()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->getPid());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testSignal()
+ {
+ $process = $this->getProcess([self::$phpBin, __DIR__.'/SignalListener.php']);
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'Caught')) {
+ usleep(1000);
+ }
+ $process->signal(SIGUSR1);
+ $process->wait();
+
+ $this->assertEquals('Caught SIGUSR1', $process->getOutput());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testExitCodeIsAvailableAfterSignal()
+ {
+ $process = $this->getProcess('sleep 4');
+ $process->start();
+ $process->signal(SIGKILL);
+
+ while ($process->isRunning()) {
+ usleep(10000);
+ }
+
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertFalse($process->isSuccessful());
+ $this->assertEquals(137, $process->getExitCode());
+ }
+
+ public function testSignalProcessNotRunning()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Can not send signal on a non running process.');
+ $process = $this->getProcess('foo');
+ $process->signal(1); // SIGHUP
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedARunningProcess
+ */
+ public function testMethodsThatNeedARunningProcess($method)
+ {
+ $process = $this->getProcess('foo');
+
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage(sprintf('Process must be started before calling %s.', $method));
+
+ $process->{$method}();
+ }
+
+ public function provideMethodsThatNeedARunningProcess()
+ {
+ return [
+ ['getOutput'],
+ ['getIncrementalOutput'],
+ ['getErrorOutput'],
+ ['getIncrementalErrorOutput'],
+ ['wait'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedATerminatedProcess
+ */
+ public function testMethodsThatNeedATerminatedProcess($method)
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Process must be terminated before calling');
+ $process = $this->getProcessForCode('sleep(37);');
+ $process->start();
+ try {
+ $process->{$method}();
+ $process->stop(0);
+ $this->fail('A LogicException must have been thrown');
+ } catch (\Exception $e) {
+ }
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function provideMethodsThatNeedATerminatedProcess()
+ {
+ return [
+ ['hasBeenSignaled'],
+ ['getTermSignal'],
+ ['hasBeenStopped'],
+ ['getStopSignal'],
+ ];
+ }
+
+ public function testWrongSignal()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('POSIX signals do not work on Windows');
+ }
+
+ $process = $this->getProcessForCode('sleep(38);');
+ $process->start();
+ try {
+ $process->signal(-4);
+ $this->fail('A RuntimeException must have been thrown');
+ } catch (RuntimeException $e) {
+ $process->stop(0);
+ }
+
+ throw $e;
+ }
+
+ public function testDisableOutputDisablesTheOutput()
+ {
+ $p = $this->getProcess('foo');
+ $this->assertFalse($p->isOutputDisabled());
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ $p->enableOutput();
+ $this->assertFalse($p->isOutputDisabled());
+ }
+
+ public function testDisableOutputWhileRunningThrowsException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('Disabling output while the process is running is not possible.');
+ $p = $this->getProcessForCode('sleep(39);');
+ $p->start();
+ $p->disableOutput();
+ }
+
+ public function testEnableOutputWhileRunningThrowsException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('Enabling output while the process is running is not possible.');
+ $p = $this->getProcessForCode('sleep(40);');
+ $p->disableOutput();
+ $p->start();
+ $p->enableOutput();
+ }
+
+ public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
+ {
+ $p = $this->getProcess('echo foo');
+ $p->disableOutput();
+ $p->run();
+ $p->enableOutput();
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ }
+
+ public function testDisableOutputWhileIdleTimeoutIsSet()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Output can not be disabled while an idle timeout is set.');
+ $process = $this->getProcess('foo');
+ $process->setIdleTimeout(1);
+ $process->disableOutput();
+ }
+
+ public function testSetIdleTimeoutWhileOutputIsDisabled()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('timeout can not be set while the output is disabled.');
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $process->setIdleTimeout(1);
+ }
+
+ public function testSetNullIdleTimeoutWhileOutputIsDisabled()
+ {
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $this->assertSame($process, $process->setIdleTimeout(null));
+ }
+
+ /**
+ * @dataProvider provideOutputFetchingMethods
+ */
+ public function testGetOutputWhileDisabled($fetchMethod)
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Output has been disabled.');
+ $p = $this->getProcessForCode('sleep(41);');
+ $p->disableOutput();
+ $p->start();
+ $p->{$fetchMethod}();
+ }
+
+ public function provideOutputFetchingMethods()
+ {
+ return [
+ ['getOutput'],
+ ['getIncrementalOutput'],
+ ['getErrorOutput'],
+ ['getIncrementalErrorOutput'],
+ ];
+ }
+
+ public function testStopTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(42);');
+ $process->run(function () use ($process) {
+ $process->stop();
+ });
+ $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testKillSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(43);');
+ $process->run(function () use ($process) {
+ $process->signal(9); // SIGKILL
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testTermSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(44);');
+ $process->run(function () use ($process) {
+ $process->signal(15); // SIGTERM
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function responsesCodeProvider()
+ {
+ return [
+ //expected output / getter / code to execute
+ // [1,'getExitCode','exit(1);'],
+ // [true,'isSuccessful','exit();'],
+ ['output', 'getOutput', 'echo \'output\';'],
+ ];
+ }
+
+ public function pipesCodeProvider()
+ {
+ $variations = [
+ 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
+ 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
+ ];
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ // Avoid XL buffers on Windows because of https://bugs.php.net/65650
+ $sizes = [1, 2, 4, 8];
+ } else {
+ $sizes = [1, 16, 64, 1024, 4096];
+ }
+
+ $codes = [];
+ foreach ($sizes as $size) {
+ foreach ($variations as $code) {
+ $codes[] = [$code, $size];
+ }
+ }
+
+ return $codes;
+ }
+
+ /**
+ * @dataProvider provideVariousIncrementals
+ */
+ public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
+ {
+ $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null);
+ $process->start();
+ $result = '';
+ $limit = microtime(true) + 3;
+ $expected = '012';
+
+ while ($result !== $expected && microtime(true) < $limit) {
+ $result .= $process->$method();
+ }
+
+ $this->assertSame($expected, $result);
+ $process->stop();
+ }
+
+ public function provideVariousIncrementals()
+ {
+ return [
+ ['php://stdout', 'getIncrementalOutput'],
+ ['php://stderr', 'getIncrementalErrorOutput'],
+ ];
+ }
+
+ public function testIteratorInput()
+ {
+ $input = function () {
+ yield 'ping';
+ yield 'pong';
+ };
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
+ $process->run();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testSimpleInputStream()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('echo \'ping\'; echo fread(STDIN, 4); echo fread(STDIN, 4);');
+ $process->setInput($input);
+
+ $process->start(function ($type, $data) use ($input) {
+ if ('ping' === $data) {
+ $input->write('pang');
+ } elseif (!$input->isClosed()) {
+ $input->write('pong');
+ $input->close();
+ }
+ });
+
+ $process->wait();
+ $this->assertSame('pingpangpong', $process->getOutput());
+ }
+
+ public function testInputStreamWithCallable()
+ {
+ $i = 0;
+ $stream = fopen('php://memory', 'w+');
+ $stream = function () use ($stream, &$i) {
+ if ($i < 3) {
+ rewind($stream);
+ fwrite($stream, ++$i);
+ rewind($stream);
+
+ return $stream;
+ }
+
+ return null;
+ };
+
+ $input = new InputStream();
+ $input->onEmpty($stream);
+ $input->write($stream());
+
+ $process = $this->getProcessForCode('echo fread(STDIN, 3);');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ $input->close();
+ });
+
+ $process->wait();
+ $this->assertSame('123', $process->getOutput());
+ }
+
+ public function testInputStreamWithGenerator()
+ {
+ $input = new InputStream();
+ $input->onEmpty(function ($input) {
+ yield 'pong';
+ $input->close();
+ });
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $process->setInput($input);
+ $process->start();
+ $input->write('ping');
+ $process->wait();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testInputStreamOnEmpty()
+ {
+ $i = 0;
+ $input = new InputStream();
+ $input->onEmpty(function () use (&$i) { ++$i; });
+
+ $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ if ('123' === $data) {
+ $input->close();
+ }
+ });
+ $process->wait();
+
+ $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
+ $this->assertSame('123456', $process->getOutput());
+ }
+
+ public function testIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');
+ $process->setInput($input);
+ $process->start();
+ $output = [];
+
+ foreach ($process as $type => $data) {
+ $output[] = [$type, $data];
+ break;
+ }
+ $expectedOutput = [
+ [$process::OUT, '123'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(345);
+
+ foreach ($process as $type => $data) {
+ $output[] = [$type, $data];
+ }
+
+ $this->assertSame('', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = [
+ [$process::OUT, '123'],
+ [$process::ERR, '234'],
+ [$process::OUT, '345'],
+ [$process::ERR, '456'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testNonBlockingNorClearingIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
+ $process->setInput($input);
+ $process->start();
+ $output = [];
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ $output[] = [$type, $data];
+ break;
+ }
+ $expectedOutput = [
+ [$process::OUT, ''],
+ ];
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(123);
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ if ('' !== $data) {
+ $output[] = [$type, $data];
+ }
+ }
+
+ $this->assertSame('123', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = [
+ [$process::OUT, ''],
+ [$process::OUT, '123'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testChainedProcesses()
+ {
+ $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
+ $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p2->setInput($p1);
+
+ $p1->start();
+ $p2->run();
+
+ $this->assertSame('123', $p1->getErrorOutput());
+ $this->assertSame('', $p1->getOutput());
+ $this->assertSame('', $p2->getErrorOutput());
+ $this->assertSame('456', $p2->getOutput());
+ }
+
+ public function testSetBadEnv()
+ {
+ $process = $this->getProcess('echo hello');
+ $process->setEnv(['bad%%' => '123']);
+ $process->inheritEnvironmentVariables(true);
+
+ $process->run();
+
+ $this->assertSame('hello'.PHP_EOL, $process->getOutput());
+ $this->assertSame('', $process->getErrorOutput());
+ }
+
+ public function testEnvBackupDoesNotDeleteExistingVars()
+ {
+ putenv('existing_var=foo');
+ $_ENV['existing_var'] = 'foo';
+ $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
+ $process->setEnv(['existing_var' => 'bar', 'new_test_var' => 'foo']);
+ $process->inheritEnvironmentVariables();
+
+ $process->run();
+
+ $this->assertSame('foo', $process->getOutput());
+ $this->assertSame('foo', getenv('existing_var'));
+ $this->assertFalse(getenv('new_test_var'));
+
+ putenv('existing_var');
+ unset($_ENV['existing_var']);
+ }
+
+ public function testEnvIsInherited()
+ {
+ $process = $this->getProcessForCode('echo serialize($_SERVER);', null, ['BAR' => 'BAZ', 'EMPTY' => '']);
+
+ putenv('FOO=BAR');
+ $_ENV['FOO'] = 'BAR';
+
+ $process->run();
+
+ $expected = ['BAR' => 'BAZ', 'EMPTY' => '', 'FOO' => 'BAR'];
+ $env = array_intersect_key(unserialize($process->getOutput()), $expected);
+
+ $this->assertEquals($expected, $env);
+
+ putenv('FOO');
+ unset($_ENV['FOO']);
+ }
+
+ public function testGetCommandLine()
+ {
+ $p = new Process(['/usr/bin/php']);
+
+ $expected = '\\' === \DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'";
+ $this->assertSame($expected, $p->getCommandLine());
+ }
+
+ /**
+ * @dataProvider provideEscapeArgument
+ */
+ public function testEscapeArgument($arg)
+ {
+ $p = new Process([self::$phpBin, '-r', 'echo $argv[1];', $arg]);
+ $p->run();
+
+ $this->assertSame((string) $arg, $p->getOutput());
+ }
+
+ public function testRawCommandLine()
+ {
+ $p = Process::fromShellCommandline(sprintf('"%s" -r %s "a" "" "b"', self::$phpBin, escapeshellarg('print_r($argv);')));
+ $p->run();
+
+ $expected = << -
+ [1] => a
+ [2] =>
+ [3] => b
+)
+
+EOTXT;
+ $this->assertSame($expected, str_replace('Standard input code', '-', $p->getOutput()));
+ }
+
+ public function provideEscapeArgument()
+ {
+ yield ['a"b%c%'];
+ yield ['a"b^c^'];
+ yield ["a\nb'c"];
+ yield ['a^b c!'];
+ yield ["a!b\tc"];
+ yield ['a\\\\"\\"'];
+ yield ['éÉèÈàÀöä'];
+ yield [null];
+ yield [1];
+ yield [1.1];
+ }
+
+ public function testEnvArgument()
+ {
+ $env = ['FOO' => 'Foo', 'BAR' => 'Bar'];
+ $cmd = '\\' === \DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
+ $p = Process::fromShellCommandline($cmd, null, $env);
+ $p->run(null, ['BAR' => 'baR', 'BAZ' => 'baZ']);
+
+ $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
+ $this->assertSame($env, $p->getEnv());
+ }
+
+ public function testWaitStoppedDeadProcess()
+ {
+ $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/ErrorProcessInitiator.php -e '.self::$phpBin);
+ $process->start();
+ $process->setTimeout(2);
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ }
+
+ /**
+ * @param string $commandline
+ * @param string|null $input
+ * @param int $timeout
+ */
+ private function getProcess($commandline, string $cwd = null, array $env = null, $input = null, ?int $timeout = 60): Process
+ {
+ if (\is_string($commandline)) {
+ $process = Process::fromShellCommandline($commandline, $cwd, $env, $input, $timeout);
+ } else {
+ $process = new Process($commandline, $cwd, $env, $input, $timeout);
+ }
+ $process->inheritEnvironmentVariables();
+
+ if (self::$process) {
+ self::$process->stop(0);
+ }
+
+ return self::$process = $process;
+ }
+
+ private function getProcessForCode(string $code, string $cwd = null, array $env = null, $input = null, ?int $timeout = 60): Process
+ {
+ return $this->getProcess([self::$phpBin, '-r', $code], $cwd, $env, $input, $timeout);
+ }
+}
+
+class NonStringifiable
+{
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/Tests/SignalListener.php b/src/mpg25-instagram-api/vendor/symfony/process/Tests/SignalListener.php
new file mode 100755
index 0000000..9e30ce3
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/Tests/SignalListener.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+pcntl_signal(SIGUSR1, function () { echo 'SIGUSR1'; exit; });
+
+echo 'Caught ';
+
+$n = 0;
+
+while ($n++ < 400) {
+ usleep(10000);
+ pcntl_signal_dispatch();
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/composer.json b/src/mpg25-instagram-api/vendor/symfony/process/composer.json
new file mode 100755
index 0000000..d3efd02
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/process",
+ "type": "library",
+ "description": "Symfony Process Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.1.3"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Process\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/symfony/process/phpunit.xml.dist b/src/mpg25-instagram-api/vendor/symfony/process/phpunit.xml.dist
new file mode 100755
index 0000000..c32f251
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/symfony/process/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/.gitignore b/src/mpg25-instagram-api/vendor/valga/fbns-react/.gitignore
new file mode 100755
index 0000000..86cea2c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/.gitignore
@@ -0,0 +1,4 @@
+/.idea/
+/vendor/
+/composer.lock
+/.php_cs.cache
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/.php_cs b/src/mpg25-instagram-api/vendor/valga/fbns-react/.php_cs
new file mode 100755
index 0000000..97d12a6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/.php_cs
@@ -0,0 +1,20 @@
+setFinder(
+ \PhpCsFixer\Finder::create()
+ ->in('src')
+ )
+ ->setRules([
+ '@Symfony' => true,
+ // Override @Symfony rules
+ 'pre_increment' => false,
+ 'blank_line_before_statement' => ['statements' => ['return']],
+ 'phpdoc_align' => ['tags' => ['param', 'throws']],
+ 'phpdoc_annotation_without_dot' => false,
+ // Custom rules
+ 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
+ 'ordered_imports' => true,
+ 'phpdoc_order' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ]);
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/LICENSE b/src/mpg25-instagram-api/vendor/valga/fbns-react/LICENSE
new file mode 100755
index 0000000..112f1c7
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 Abyr Valg
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/README.md b/src/mpg25-instagram-api/vendor/valga/fbns-react/README.md
new file mode 100755
index 0000000..6161376
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/README.md
@@ -0,0 +1,84 @@
+# fbns-react
+
+A PHP client for the FBNS built on top of ReactPHP.
+
+## Requirements
+
+You need to install the [GMP extension](http://php.net/manual/en/book.gmp.php) to be able to run this code on x86 PHP builds.
+
+## Installation
+
+```sh
+composer require valga/fbns-react
+```
+
+## Basic Usage
+
+```php
+// Set up a FBNS client.
+$loop = \React\EventLoop\Factory::create();
+$client = new \Fbns\Client\Lite($loop);
+
+// Read saved credentials from a storage.
+$auth = new \Fbns\Client\Auth\DeviceAuth();
+try {
+ $auth->read($storage->get('fbns_auth'));
+} catch (\Exception $e) {
+}
+
+// Connect to a broker.
+$connection = new \Fbns\Client\Connection($deviceAuth, USER_AGENT);
+$client->connect(HOSTNAME, PORT, $connection);
+
+// Bind events.
+$client
+ ->on('connect', function (\Fbns\Client\Lite\ConnectResponsePacket $responsePacket) use ($client, $auth, $storage) {
+ // Update credentials and save them to a storage for future use.
+ try {
+ $auth->read($responsePacket->getAuth());
+ $storage->set('fbns_auth', $responsePacket->getAuth());
+ } catch (\Exception $e) {
+ }
+
+ // Register an application.
+ $client->register(PACKAGE_NAME, APPLICATION_ID);
+ })
+ ->on('register', function (\Fbns\Client\Message\Register $message) use ($app) {
+ // Register received token with an application.
+ $app->registerPushToken($message->getToken());
+ })
+ ->on('push', function (\Fbns\Client\Message\Push $message) use ($app) {
+ // Handle received notification payload.
+ $app->handlePushNotification($message->getPayload());
+ });
+
+// Run main loop.
+$loop->run();
+```
+
+## Advanced Usage
+
+```php
+// Set up a proxy.
+$connector = new \React\Socket\Connector($loop);
+$proxy = new \Clue\React\HttpProxy('username:password@127.0.0.1:3128', $connector);
+
+// Disable SSL verification.
+$ssl = new \React\Socket\SecureConnector($proxy, $loop, ['verify_peer' => false, 'verify_peer_name' => false]);
+
+// Enable logging to stdout.
+$logger = new \Monolog\Logger('fbns');
+$logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+
+// Set up a client.
+$client = new \Fbns\Client\Lite($loop, $ssl, $logger);
+
+// Persistence.
+$client->on('disconnect', function () {
+ // Network connection has been closed. You can reestablish it if you want to.
+});
+$client->connect(HOSTNAME, PORT, $connection)
+ ->otherwise(function () {
+ // Connection attempt was unsuccessful, retry with an exponential backoff.
+ });
+```
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/bin/thrift_debug b/src/mpg25-instagram-api/vendor/valga/fbns-react/bin/thrift_debug
new file mode 100755
index 0000000..7f00f0b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/bin/thrift_debug
@@ -0,0 +1,33 @@
+#!/usr/bin/env php
+ 1) {
+ $data = @file_get_contents($argv[1]);
+} elseif (!posix_isatty(STDIN)) {
+ $data = @stream_get_contents(STDIN);
+} else {
+ echo 'Usage: ', $argv[0], ' [FILE]', PHP_EOL;
+ echo 'Dump the contents of Thrift FILE.', PHP_EOL;
+ echo PHP_EOL;
+ echo 'With no FILE read standard input.', PHP_EOL;
+
+ exit(2);
+}
+
+if ($data === false) {
+ fwrite(STDERR, 'Failed to read the input.'.PHP_EOL);
+
+ exit(1);
+}
+
+try {
+ new \Fbns\Client\Thrift\Debug($data);
+} catch (\Exception $e) {
+ fwrite(STDERR, $e->getMessage().PHP_EOL);
+
+ exit(1);
+}
+
+exit(0);
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/composer.json b/src/mpg25-instagram-api/vendor/valga/fbns-react/composer.json
new file mode 100755
index 0000000..361799c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "valga/fbns-react",
+ "description": "A PHP client for the FBNS built on top of ReactPHP",
+ "keywords": [
+ "FBNS",
+ "Client",
+ "PHP"
+ ],
+ "type": "library",
+ "minimum-stability": "stable",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Abyr Valg",
+ "email": "valga.github@abyrga.ru"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Fbns\\Client\\": "src/"
+ }
+ },
+ "require": {
+ "php": "~5.6|~7.0",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "evenement/evenement": "~2.0|~3.0",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8",
+ "binsoul/net-mqtt": "~0.2",
+ "psr/log": "~1.0"
+ },
+ "require-dev": {
+ "monolog/monolog": "~1.23",
+ "friendsofphp/php-cs-fixer": "~2.4"
+ },
+ "suggest": {
+ "ext-event": "For more efficient event loop implementation.",
+ "ext-gmp": "To be able to run this code on x86 PHP builds."
+ },
+ "scripts": {
+ "codestyle": "php-cs-fixer fix --config=.php_cs"
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Auth/DeviceAuth.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Auth/DeviceAuth.php
new file mode 100755
index 0000000..ae460ae
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Auth/DeviceAuth.php
@@ -0,0 +1,162 @@
+clientId = substr($this->randomUuid(), 0, 20);
+ $this->userId = 0;
+ $this->password = '';
+ $this->deviceSecret = '';
+ $this->deviceId = '';
+ }
+
+ /**
+ * @param string $json
+ */
+ public function read($json)
+ {
+ $data = Json::decode($json);
+ $this->json = $json;
+
+ if (isset($data->ck)) {
+ $this->userId = $data->ck;
+ } else {
+ $this->userId = 0;
+ }
+ if (isset($data->cs)) {
+ $this->password = $data->cs;
+ } else {
+ $this->password = '';
+ }
+ if (isset($data->di)) {
+ $this->deviceId = $data->di;
+ $this->clientId = substr($this->deviceId, 0, 20);
+ } else {
+ $this->deviceId = '';
+ $this->clientId = substr($this->randomUuid(), 0, 20);
+ }
+ if (isset($data->ds)) {
+ $this->deviceSecret = $data->ds;
+ } else {
+ $this->deviceSecret = '';
+ }
+
+ // TODO: sr ?
+ // TODO: rc ?
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->json !== null ? $this->json : '';
+ }
+
+ /**
+ * @return int
+ */
+ public function getUserId()
+ {
+ return $this->userId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDeviceId()
+ {
+ return $this->deviceId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDeviceSecret()
+ {
+ return $this->deviceSecret;
+ }
+
+ /**
+ * @return string
+ */
+ public function getClientType()
+ {
+ return self::TYPE;
+ }
+
+ /**
+ * @return string
+ */
+ public function getClientId()
+ {
+ return $this->clientId;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/AuthInterface.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/AuthInterface.php
new file mode 100755
index 0000000..7b40a93
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/AuthInterface.php
@@ -0,0 +1,41 @@
+assertPacketFlags($this->getExpectedPacketFlags());
+ $this->assertRemainingPacketLength(2);
+
+ $this->identifier = $stream->readWord();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Connection.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Connection.php
new file mode 100755
index 0000000..9ea5a39
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Connection.php
@@ -0,0 +1,339 @@
+auth = $auth;
+ $this->userAgent = $userAgent;
+
+ $this->clientCapabilities = self::FBNS_CLIENT_CAPABILITIES;
+ $this->endpointCapabilities = self::FBNS_ENDPOINT_CAPABILITIES;
+ $this->publishFormat = self::FBNS_PUBLISH_FORMAT;
+ $this->noAutomaticForeground = true;
+ $this->makeUserAvailableInForeground = false;
+ $this->isInitiallyForeground = false;
+ $this->networkType = 1;
+ $this->networkSubtype = 0;
+ $this->subscribeTopics = [(int) Lite::MESSAGE_TOPIC_ID, (int) Lite::REG_RESP_TOPIC_ID];
+ $this->appId = self::FBNS_APP_ID;
+ $this->clientStack = self::FBNS_CLIENT_STACK;
+ }
+
+ /**
+ * @return string
+ */
+ public function toThrift()
+ {
+ $writer = new Writer();
+
+ $writer->writeString(self::CLIENT_ID, $this->auth->getClientId());
+
+ $writer->writeStruct(self::CLIENT_INFO);
+ $writer->writeInt64(self::USER_ID, $this->auth->getUserId());
+ $writer->writeString(self::USER_AGENT, $this->userAgent);
+ $writer->writeInt64(self::CLIENT_CAPABILITIES, $this->clientCapabilities);
+ $writer->writeInt64(self::ENDPOINT_CAPABILITIES, $this->endpointCapabilities);
+ $writer->writeInt32(self::PUBLISH_FORMAT, $this->publishFormat);
+ $writer->writeBool(self::NO_AUTOMATIC_FOREGROUND, $this->noAutomaticForeground);
+ $writer->writeBool(self::MAKE_USER_AVAILABLE_IN_FOREGROUND, $this->makeUserAvailableInForeground);
+ $writer->writeString(self::DEVICE_ID, $this->auth->getDeviceId());
+ $writer->writeBool(self::IS_INITIALLY_FOREGROUND, $this->isInitiallyForeground);
+ $writer->writeInt32(self::NETWORK_TYPE, $this->networkType);
+ $writer->writeInt32(self::NETWORK_SUBTYPE, $this->networkSubtype);
+ if ($this->clientMqttSessionId === null) {
+ $sessionId = (int) ((microtime(true) - strtotime('Last Monday')) * 1000);
+ } else {
+ $sessionId = $this->clientMqttSessionId;
+ }
+ $writer->writeInt64(self::CLIENT_MQTT_SESSION_ID, $sessionId);
+ $writer->writeList(self::SUBSCRIBE_TOPICS, Compact::TYPE_I32, $this->subscribeTopics);
+ $writer->writeString(self::CLIENT_TYPE, $this->auth->getClientType());
+ $writer->writeInt64(self::APP_ID, $this->appId);
+ $writer->writeString(self::DEVICE_SECRET, $this->auth->getDeviceSecret());
+ $writer->writeInt8(self::CLIENT_STACK, $this->clientStack);
+ $writer->writeStop();
+
+ $writer->writeString(self::PASSWORD, $this->auth->getPassword());
+ $writer->writeStop();
+
+ return (string) $writer;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUserAgent()
+ {
+ return $this->userAgent;
+ }
+
+ /**
+ * @param string $userAgent
+ */
+ public function setUserAgent($userAgent)
+ {
+ $this->userAgent = $userAgent;
+ }
+
+ /**
+ * @return int
+ */
+ public function getClientCapabilities()
+ {
+ return $this->clientCapabilities;
+ }
+
+ /**
+ * @param int $clientCapabilities
+ */
+ public function setClientCapabilities($clientCapabilities)
+ {
+ $this->clientCapabilities = $clientCapabilities;
+ }
+
+ /**
+ * @return int
+ */
+ public function getEndpointCapabilities()
+ {
+ return $this->endpointCapabilities;
+ }
+
+ /**
+ * @param int $endpointCapabilities
+ */
+ public function setEndpointCapabilities($endpointCapabilities)
+ {
+ $this->endpointCapabilities = $endpointCapabilities;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isNoAutomaticForeground()
+ {
+ return $this->noAutomaticForeground;
+ }
+
+ /**
+ * @param bool $noAutomaticForeground
+ */
+ public function setNoAutomaticForeground($noAutomaticForeground)
+ {
+ $this->noAutomaticForeground = $noAutomaticForeground;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isMakeUserAvailableInForeground()
+ {
+ return $this->makeUserAvailableInForeground;
+ }
+
+ /**
+ * @param bool $makeUserAvailableInForeground
+ */
+ public function setMakeUserAvailableInForeground($makeUserAvailableInForeground)
+ {
+ $this->makeUserAvailableInForeground = $makeUserAvailableInForeground;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isInitiallyForeground()
+ {
+ return $this->isInitiallyForeground;
+ }
+
+ /**
+ * @param bool $isInitiallyForeground
+ */
+ public function setIsInitiallyForeground($isInitiallyForeground)
+ {
+ $this->isInitiallyForeground = $isInitiallyForeground;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNetworkType()
+ {
+ return $this->networkType;
+ }
+
+ /**
+ * @param int $networkType
+ */
+ public function setNetworkType($networkType)
+ {
+ $this->networkType = $networkType;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNetworkSubtype()
+ {
+ return $this->networkSubtype;
+ }
+
+ /**
+ * @param int $networkSubtype
+ */
+ public function setNetworkSubtype($networkSubtype)
+ {
+ $this->networkSubtype = $networkSubtype;
+ }
+
+ /**
+ * @return int
+ */
+ public function getClientMqttSessionId()
+ {
+ return $this->clientMqttSessionId;
+ }
+
+ /**
+ * @param int $clientMqttSessionId
+ */
+ public function setClientMqttSessionId($clientMqttSessionId)
+ {
+ $this->clientMqttSessionId = $clientMqttSessionId;
+ }
+
+ /**
+ * @return int[]
+ */
+ public function getSubscribeTopics()
+ {
+ return $this->subscribeTopics;
+ }
+
+ /**
+ * @param int[] $subscribeTopics
+ */
+ public function setSubscribeTopics($subscribeTopics)
+ {
+ $this->subscribeTopics = $subscribeTopics;
+ }
+
+ /**
+ * @return int
+ */
+ public function getAppId()
+ {
+ return $this->appId;
+ }
+
+ /**
+ * @param int $appId
+ */
+ public function setAppId($appId)
+ {
+ $this->appId = $appId;
+ }
+
+ /**
+ * @return int
+ */
+ public function getClientStack()
+ {
+ return $this->clientStack;
+ }
+
+ /**
+ * @param int $clientStack
+ */
+ public function setClientStack($clientStack)
+ {
+ $this->clientStack = $clientStack;
+ }
+
+ /**
+ * @return AuthInterface
+ */
+ public function getAuth()
+ {
+ return $this->auth;
+ }
+
+ /**
+ * @param AuthInterface $auth
+ */
+ public function setAuth(AuthInterface $auth)
+ {
+ $this->auth = $auth;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Json.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Json.php
new file mode 100755
index 0000000..24bc1dc
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Json.php
@@ -0,0 +1,28 @@
+ self::MESSAGE_TOPIC,
+ self::REG_REQ_TOPIC_ID => self::REG_REQ_TOPIC,
+ self::REG_RESP_TOPIC_ID => self::REG_RESP_TOPIC,
+ ];
+
+ const TOPIC_TO_ID_ENUM = [
+ self::MESSAGE_TOPIC => self::MESSAGE_TOPIC_ID,
+ self::REG_REQ_TOPIC => self::REG_REQ_TOPIC_ID,
+ self::REG_RESP_TOPIC => self::REG_RESP_TOPIC_ID,
+ ];
+
+ /**
+ * @var LoopInterface
+ */
+ private $loop;
+
+ /**
+ * @var ConnectorInterface
+ */
+ private $connector;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @var ReactMqttClient
+ */
+ private $client;
+
+ /**
+ * @var TimerInterface
+ */
+ private $keepaliveTimer;
+
+ /**
+ * Constructor.
+ *
+ * @param LoopInterface $loop
+ * @param ConnectorInterface|null $connector
+ * @param LoggerInterface|null $logger
+ */
+ public function __construct(LoopInterface $loop, ConnectorInterface $connector = null, LoggerInterface $logger = null)
+ {
+ $this->loop = $loop;
+ if ($connector === null) {
+ $this->connector = new SecureConnector(new Connector($loop), $loop);
+ } else {
+ $this->connector = $connector;
+ }
+ if ($logger !== null) {
+ $this->logger = $logger;
+ } else {
+ $this->logger = new NullLogger();
+ }
+ $this->client = new ReactMqttClient($this->connector, $this->loop, null, new StreamParser());
+
+ $this->client
+ ->on('open', function () {
+ $this->logger->info('Connection has been established.');
+ })
+ ->on('close', function () {
+ $this->logger->info('Network connection has been closed.');
+ $this->cancelKeepaliveTimer();
+ $this->emit('disconnect', [$this]);
+ })
+ ->on('warning', function (\Exception $e) {
+ $this->logger->warning($e->getMessage());
+ })
+ ->on('error', function (\Exception $e) {
+ $this->logger->error($e->getMessage());
+ $this->emit('error', [$e]);
+ })
+ ->on('connect', function (ConnectResponsePacket $responsePacket) {
+ $this->logger->info('Connected to a broker.');
+ $this->setKeepaliveTimer();
+ $this->emit('connect', [$responsePacket]);
+ })
+ ->on('disconnect', function () {
+ $this->logger->info('Disconnected from the broker.');
+ })
+ ->on('message', function (Message $message) {
+ $this->setKeepaliveTimer();
+ $this->onMessage($message);
+ })
+ ->on('publish', function () {
+ $this->logger->info('Publish flow has been completed.');
+ $this->setKeepaliveTimer();
+ })
+ ->on('ping', function () {
+ $this->logger->info('Ping flow has been completed.');
+ $this->setKeepaliveTimer();
+ });
+ }
+
+ private function cancelKeepaliveTimer()
+ {
+ if ($this->keepaliveTimer !== null) {
+ if ($this->keepaliveTimer->isActive()) {
+ $this->logger->info('Existing keepalive timer has been canceled.');
+ $this->keepaliveTimer->cancel();
+ }
+ $this->keepaliveTimer = null;
+ }
+ }
+
+ private function onKeepalive()
+ {
+ $this->logger->info('Keepalive timer has been fired.');
+ $this->cancelKeepaliveTimer();
+ $this->disconnect();
+ }
+
+ private function setKeepaliveTimer()
+ {
+ $this->cancelKeepaliveTimer();
+ $keepaliveInterval = OutgoingConnectFlow::KEEPALIVE;
+ $this->logger->info(sprintf('Setting up keepalive timer to %d seconds', $keepaliveInterval));
+ $this->keepaliveTimer = $this->loop->addTimer($keepaliveInterval, function () {
+ $this->onKeepalive();
+ });
+ }
+
+ /**
+ * @param string $payload
+ */
+ private function onRegister($payload)
+ {
+ try {
+ $message = new Register($payload);
+ } catch (\Exception $e) {
+ $this->logger->warning(sprintf('Failed to decode register message: %s', $e->getMessage()), [$payload]);
+
+ return;
+ }
+
+ $this->emit('register', [$message]);
+ }
+
+ /**
+ * @param string $payload
+ */
+ private function onPush($payload)
+ {
+ try {
+ $message = new Push($payload);
+ } catch (\Exception $e) {
+ $this->logger->warning(sprintf('Failed to decode push message: %s', $e->getMessage()), [$payload]);
+
+ return;
+ }
+
+ $this->emit('push', [$message]);
+ }
+
+ /**
+ * @param Message $message
+ */
+ private function onMessage(Message $message)
+ {
+ $payload = @zlib_decode($message->getPayload());
+ if ($payload === false) {
+ $this->logger->warning('Failed to inflate a payload.');
+
+ return;
+ }
+
+ $topic = $this->unmapTopic($message->getTopic());
+ $this->logger->info(sprintf('Received a message from topic "%s".', $topic), [$payload]);
+
+ switch ($topic) {
+ case self::MESSAGE_TOPIC:
+ $this->onPush($payload);
+ break;
+ case self::REG_RESP_TOPIC:
+ $this->onRegister($payload);
+ break;
+ default:
+ $this->logger->warning(sprintf('Received a message from unknown topic "%s".', $topic), [$payload]);
+ }
+ }
+
+ /**
+ * Establishes a connection to the FBNS server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return PromiseInterface
+ */
+ private function establishConnection($host, $port, Connection $connection, $timeout)
+ {
+ $this->logger->info(sprintf('Connecting to %s:%d...', $host, $port));
+
+ return $this->client->connect($host, $port, $connection, $timeout);
+ }
+
+ /**
+ * Connects to a FBNS server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return PromiseInterface
+ */
+ public function connect($host, $port, Connection $connection, $timeout = 5)
+ {
+ $deferred = new Deferred();
+ $this->disconnect()
+ ->then(function () use ($deferred, $host, $port, $connection, $timeout) {
+ $this->establishConnection($host, $port, $connection, $timeout)
+ ->then(function () use ($deferred) {
+ $deferred->resolve($this);
+ })
+ ->otherwise(function (\Exception $error) use ($deferred) {
+ $deferred->reject($error);
+ });
+ })
+ ->otherwise(function () use ($deferred) {
+ $deferred->reject($this);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @return PromiseInterface
+ */
+ public function disconnect()
+ {
+ if ($this->client->isConnected()) {
+ $deferred = new Deferred();
+ $this->client->disconnect()
+ ->then(function () use ($deferred) {
+ $deferred->resolve($this);
+ })
+ ->otherwise(function () use ($deferred) {
+ $deferred->reject($this);
+ });
+
+ return $deferred->promise();
+ } else {
+ return new FulfilledPromise($this);
+ }
+ }
+
+ /**
+ * Maps human readable topic to its ID.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ private function mapTopic($topic)
+ {
+ if (array_key_exists($topic, self::TOPIC_TO_ID_ENUM)) {
+ $result = self::TOPIC_TO_ID_ENUM[$topic];
+ $this->logger->debug(sprintf('Topic "%s" has been mapped to "%s".', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->logger->debug(sprintf('Topic "%s" does not exist in enum.', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Maps topic ID to human readable name.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ private function unmapTopic($topic)
+ {
+ if (array_key_exists($topic, self::ID_TO_TOPIC_ENUM)) {
+ $result = self::ID_TO_TOPIC_ENUM[$topic];
+ $this->logger->debug(sprintf('Topic ID "%s" has been unmapped to "%s".', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->logger->debug(sprintf('Topic ID "%s" does not exist in enum.', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Publish a message to a topic.
+ *
+ * @param string $topic
+ * @param string $message
+ * @param int $qosLevel
+ *
+ * @return \React\Promise\ExtendedPromiseInterface
+ */
+ private function publish($topic, $message, $qosLevel)
+ {
+ $this->logger->info(sprintf('Sending message to topic "%s".', $topic), [$message]);
+ $topic = $this->mapTopic($topic);
+ $payload = zlib_encode($message, ZLIB_ENCODING_DEFLATE, 9);
+
+ return $this->client->publish(new DefaultMessage($topic, $payload, $qosLevel));
+ }
+
+ /**
+ * Registers an application.
+ *
+ * @param string $packageName
+ * @param string|int $appId
+ *
+ * @return PromiseInterface
+ */
+ public function register($packageName, $appId)
+ {
+ $this->logger->info(sprintf('Registering application "%s" (%s).', $packageName, $appId));
+ $message = json_encode([
+ 'pkg_name' => (string) $packageName,
+ 'appid' => (string) $appId,
+ ]);
+
+ return $this->publish(self::REG_REQ_TOPIC, $message, self::QOS_LEVEL);
+ }
+
+ /**
+ * Checks whether underlying client is connected.
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->client->isConnected();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ConnectRequestPacket.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ConnectRequestPacket.php
new file mode 100755
index 0000000..d47e7c6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ConnectRequestPacket.php
@@ -0,0 +1,198 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+ $this->protocolName = $stream->readString();
+ $this->protocolLevel = $stream->readByte();
+ $this->flags = $stream->readByte();
+ $this->keepAlive = $stream->readWord();
+
+ $payloadLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
+ $this->payload = $stream->read($payloadLength);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeString($this->protocolName);
+ $data->writeByte($this->protocolLevel);
+ $data->writeByte($this->flags);
+ $data->writeWord($this->keepAlive);
+ $data->write($this->payload);
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the protocol level.
+ *
+ * @return int
+ */
+ public function getProtocolLevel()
+ {
+ return $this->protocolLevel;
+ }
+
+ /**
+ * Sets the protocol level.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setProtocolLevel($value)
+ {
+ if ($value != 3) {
+ throw new \InvalidArgumentException(sprintf('Unknown protocol level %d.', $value));
+ }
+
+ $this->protocolLevel = $value;
+ }
+
+ /**
+ * Returns the payload.
+ *
+ * @return string
+ */
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ /**
+ * Sets the payload.
+ *
+ * @param string $value
+ */
+ public function setPayload($value)
+ {
+ $this->payload = $value;
+ }
+
+ /**
+ * Returns the flags.
+ *
+ * @return int
+ */
+ public function getFlags()
+ {
+ return $this->flags;
+ }
+
+ /**
+ * Sets the flags.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setFlags($value)
+ {
+ if ($value > 255) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected a flags lower than 255 but got %d.',
+ $value
+ )
+ );
+ }
+
+ $this->flags = $value;
+ }
+
+ /**
+ * Returns the keep alive time in seconds.
+ *
+ * @return int
+ */
+ public function getKeepAlive()
+ {
+ return $this->keepAlive;
+ }
+
+ /**
+ * Sets the keep alive time in seconds.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setKeepAlive($value)
+ {
+ if ($value > 65535) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected a keep alive time lower than 65535 but got %d.',
+ $value
+ )
+ );
+ }
+
+ $this->keepAlive = $value;
+ }
+
+ /**
+ * Returns the protocol name.
+ *
+ * @return string
+ */
+ public function getProtocolName()
+ {
+ return $this->protocolName;
+ }
+
+ /**
+ * Sets the protocol name.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setProtocolName($value)
+ {
+ $this->assertValidStringLength($value, false);
+
+ $this->protocolName = $value;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ConnectResponsePacket.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ConnectResponsePacket.php
new file mode 100755
index 0000000..da4f025
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ConnectResponsePacket.php
@@ -0,0 +1,145 @@
+ [
+ 'Connection accepted',
+ '',
+ ],
+ 1 => [
+ 'Unacceptable protocol version',
+ 'The Server does not support the level of the MQTT protocol requested by the client.',
+ ],
+ 2 => [
+ 'Identifier rejected',
+ 'The client identifier is correct UTF-8 but not allowed by the server.',
+ ],
+ 3 => [
+ 'Server unavailable',
+ 'The network connection has been made but the MQTT service is unavailable',
+ ],
+ 4 => [
+ 'Bad user name or password',
+ 'The data in the user name or password is malformed.',
+ ],
+ 5 => [
+ 'Not authorized',
+ 'The client is not authorized to connect.',
+ ],
+ ];
+
+ /** @var int */
+ private $flags = 0;
+ /** @var int */
+ private $returnCode;
+ /** @var string */
+ private $auth;
+
+ protected static $packetType = Packet::TYPE_CONNACK;
+
+ public function read(PacketStream $stream)
+ {
+ parent::read($stream);
+ $this->assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+ $this->flags = $stream->readByte();
+ $this->returnCode = $stream->readByte();
+
+ $authLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
+ if ($authLength) {
+ $this->auth = $stream->readString();
+ } else {
+ $this->auth = '';
+ }
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeByte($this->flags);
+ $data->writeByte($this->returnCode);
+
+ if ($this->auth !== null && strlen($this->auth)) {
+ $data->writeString($this->auth);
+ }
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the return code.
+ *
+ * @return int
+ */
+ public function getReturnCode()
+ {
+ return $this->returnCode;
+ }
+
+ /**
+ * Indicates if the connection was successful.
+ *
+ * @return bool
+ */
+ public function isSuccess()
+ {
+ return $this->returnCode === 0;
+ }
+
+ /**
+ * Indicates if the connection failed.
+ *
+ * @return bool
+ */
+ public function isError()
+ {
+ return $this->returnCode > 0;
+ }
+
+ /**
+ * Returns a string representation of the returned error code.
+ *
+ * @return int
+ */
+ public function getErrorName()
+ {
+ if (isset(self::$returnCodes[$this->returnCode])) {
+ return self::$returnCodes[$this->returnCode][0];
+ }
+
+ return 'Error '.$this->returnCode;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAuth()
+ {
+ return $this->auth;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/OutgoingConnectFlow.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/OutgoingConnectFlow.php
new file mode 100755
index 0000000..eeeffbd
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/OutgoingConnectFlow.php
@@ -0,0 +1,74 @@
+connection = $connection;
+ }
+
+ public function getCode()
+ {
+ return 'connect';
+ }
+
+ public function start()
+ {
+ $packet = new ConnectRequestPacket();
+ $packet->setProtocolLevel(self::PROTOCOL_LEVEL);
+ $packet->setProtocolName(self::PROTOCOL_NAME);
+ $packet->setKeepAlive(self::KEEPALIVE);
+ $packet->setFlags(194);
+ $packet->setPayload(zlib_encode($this->connection->toThrift(), ZLIB_ENCODING_DEFLATE, 9));
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $packet->getPacketType() === Packet::TYPE_CONNACK;
+ }
+
+ public function next(Packet $packet)
+ {
+ /** @var ConnectResponsePacket $packet */
+ if ($packet->isSuccess()) {
+ $this->succeed($packet);
+ } else {
+ $this->fail($packet->getErrorName());
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/PacketFactory.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/PacketFactory.php
new file mode 100755
index 0000000..c824b8b
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/PacketFactory.php
@@ -0,0 +1,74 @@
+ ConnectRequestPacket::class,
+ Packet::TYPE_CONNACK => ConnectResponsePacket::class,
+ Packet::TYPE_PUBLISH => PublishRequestPacket::class,
+ Packet::TYPE_PUBACK => PublishAckPacket::class,
+ Packet::TYPE_PUBREC => PublishReceivedPacket::class,
+ Packet::TYPE_PUBREL => PublishReleasePacket::class,
+ Packet::TYPE_PUBCOMP => PublishCompletePacket::class,
+ Packet::TYPE_SUBSCRIBE => SubscribeRequestPacket::class,
+ Packet::TYPE_SUBACK => SubscribeResponsePacket::class,
+ Packet::TYPE_UNSUBSCRIBE => UnsubscribeRequestPacket::class,
+ Packet::TYPE_UNSUBACK => UnsubscribeResponsePacket::class,
+ Packet::TYPE_PINGREQ => PingRequestPacket::class,
+ Packet::TYPE_PINGRESP => PingResponsePacket::class,
+ Packet::TYPE_DISCONNECT => DisconnectRequestPacket::class,
+ ];
+
+ /**
+ * Builds a packet object for the given type.
+ *
+ * @param int $type
+ *
+ * @throws UnknownPacketTypeException
+ *
+ * @return Packet
+ */
+ public function build($type)
+ {
+ if (!isset(self::$mapping[$type])) {
+ throw new UnknownPacketTypeException(sprintf('Unknown packet type %d.', $type));
+ }
+
+ $class = self::$mapping[$type];
+
+ return new $class();
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ReactFlow.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ReactFlow.php
new file mode 100755
index 0000000..91d7095
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ReactFlow.php
@@ -0,0 +1,120 @@
+decorated = $decorated;
+ $this->deferred = $deferred;
+ $this->packet = $packet;
+ $this->isSilent = $isSilent;
+ }
+
+ public function getCode()
+ {
+ return $this->decorated->getCode();
+ }
+
+ public function start()
+ {
+ $this->packet = $this->decorated->start();
+
+ return $this->packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $this->decorated->accept($packet);
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->packet = $this->decorated->next($packet);
+
+ return $this->packet;
+ }
+
+ public function isFinished()
+ {
+ return $this->decorated->isFinished();
+ }
+
+ public function isSuccess()
+ {
+ return $this->decorated->isSuccess();
+ }
+
+ public function getResult()
+ {
+ return $this->decorated->getResult();
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->decorated->getErrorMessage();
+ }
+
+ /**
+ * Returns the associated deferred.
+ *
+ * @return Deferred
+ */
+ public function getDeferred()
+ {
+ return $this->deferred;
+ }
+
+ /**
+ * Returns the current packet.
+ *
+ * @return Packet
+ */
+ public function getPacket()
+ {
+ return $this->packet;
+ }
+
+ /**
+ * Indicates if the flow should emit events.
+ *
+ * @return bool
+ */
+ public function isSilent()
+ {
+ return $this->isSilent;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ReactMqttClient.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ReactMqttClient.php
new file mode 100755
index 0000000..49f8e23
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/ReactMqttClient.php
@@ -0,0 +1,695 @@
+connector = $connector;
+ $this->loop = $loop;
+
+ $this->parser = $parser;
+ if ($this->parser === null) {
+ $this->parser = new StreamParser();
+ }
+
+ $this->parser->onError(function (\Exception $e) {
+ $this->emitWarning($e);
+ });
+
+ $this->identifierGenerator = $identifierGenerator;
+ if ($this->identifierGenerator === null) {
+ $this->identifierGenerator = new DefaultIdentifierGenerator();
+ }
+ }
+
+ /**
+ * Return the host.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Return the port.
+ *
+ * @return string
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Indicates if the client is connected.
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->isConnected;
+ }
+
+ /**
+ * Returns the underlying stream or null if the client is not connected.
+ *
+ * @return DuplexStreamInterface|null
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+
+ /**
+ * Connects to a broker.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function connect($host, $port, Connection $connection, $timeout = 5)
+ {
+ if ($this->isConnected || $this->isConnecting) {
+ return new RejectedPromise(new \LogicException('The client is already connected.'));
+ }
+
+ $this->isConnecting = true;
+ $this->isConnected = false;
+
+ $this->host = $host;
+ $this->port = $port;
+
+ $deferred = new Deferred();
+
+ $this->establishConnection($this->host, $this->port, $timeout)
+ ->then(function (DuplexStreamInterface $stream) use ($connection, $deferred, $timeout) {
+ $this->stream = $stream;
+
+ $this->emit('open', [$connection, $this]);
+
+ $this->registerClient($connection, $timeout)
+ ->then(function (ConnectResponsePacket $responsePacket) use ($deferred, $connection) {
+ $this->isConnecting = false;
+ $this->isConnected = true;
+ $this->connection = $connection;
+
+ $this->emit('connect', [$responsePacket, $this]);
+ $deferred->resolve($responsePacket);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred, $connection) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+
+ $this->emit('close', [$connection, $this]);
+ });
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Disconnects from a broker.
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function disconnect()
+ {
+ if (!$this->isConnected || $this->isDisconnecting) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $this->isDisconnecting = true;
+
+ $deferred = new Deferred();
+
+ $connection = new DefaultConnection();
+ $this->startFlow(new OutgoingDisconnectFlow($connection), true)
+ ->then(function () use ($connection, $deferred) {
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+
+ $this->emit('disconnect', [$connection, $this]);
+ $deferred->resolve($connection);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+ })
+ ->otherwise(function () use ($deferred) {
+ $this->isDisconnecting = false;
+ $deferred->reject($this->connection);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Subscribes to a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function subscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingSubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Unsubscribes from a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function unsubscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingUnsubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Publishes a message.
+ *
+ * @param Message $message
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publish(Message $message)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingPublishFlow($message, $this->identifierGenerator));
+ }
+
+ /**
+ * Calls the given generator periodically and publishes the return value.
+ *
+ * @param int $interval
+ * @param Message $message
+ * @param callable $generator
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publishPeriodically($interval, Message $message, callable $generator)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $deferred = new Deferred();
+
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ $interval,
+ function () use ($message, $generator, $deferred) {
+ $this->publish($message->withPayload($generator($message->getTopic())))->then(
+ function ($value) use ($deferred) {
+ $deferred->notify($value);
+ },
+ function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ }
+ );
+ }
+ );
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Emits warnings.
+ *
+ * @param \Exception $e
+ */
+ private function emitWarning(\Exception $e)
+ {
+ $this->emit('warning', [$e, $this]);
+ }
+
+ /**
+ * Emits errors.
+ *
+ * @param \Exception $e
+ */
+ private function emitError(\Exception $e)
+ {
+ $this->emit('error', [$e, $this]);
+ }
+
+ /**
+ * Establishes a network connection to a server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function establishConnection($host, $port, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $timer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('Connection timed out after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->connector->connect($host.':'.$port)
+ ->always(function () use ($timer) {
+ $this->loop->cancelTimer($timer);
+ })
+ ->then(function (DuplexStreamInterface $stream) use ($deferred) {
+ $stream->on('data', function ($data) {
+ $this->handleReceive($data);
+ });
+
+ $stream->on('close', function () {
+ $this->handleClose();
+ });
+
+ $stream->on('error', function (\Exception $e) {
+ $this->handleError($e);
+ });
+
+ $deferred->resolve($stream);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Registers a new client with the broker.
+ *
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function registerClient(Connection $connection, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $responseTimer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('No response after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->startFlow(new OutgoingConnectFlow($connection), true)
+ ->always(function () use ($responseTimer) {
+ $this->loop->cancelTimer($responseTimer);
+ })->then(function (ConnectResponsePacket $responsePacket) use ($deferred) {
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ OutgoingConnectFlow::KEEPALIVE - OutgoingConnectFlow::KEEPALIVE_TIMEOUT,
+ function () {
+ $this->startFlow(new OutgoingPingFlow());
+ }
+ );
+
+ $deferred->resolve($responsePacket);
+ })->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Handles incoming data.
+ *
+ * @param string $data
+ */
+ private function handleReceive($data)
+ {
+ if (!$this->isConnected && !$this->isConnecting) {
+ return;
+ }
+
+ $flowCount = count($this->receivingFlows);
+
+ $packets = $this->parser->push($data);
+ foreach ($packets as $packet) {
+ $this->handlePacket($packet);
+ }
+
+ if ($flowCount > count($this->receivingFlows)) {
+ $this->receivingFlows = array_values($this->receivingFlows);
+ }
+
+ $this->handleSend();
+ }
+
+ /**
+ * Handles an incoming packet.
+ *
+ * @param Packet $packet
+ */
+ private function handlePacket(Packet $packet)
+ {
+ switch ($packet->getPacketType()) {
+ case Packet::TYPE_PUBLISH:
+ /* @var PublishRequestPacket $packet */
+ $message = new DefaultMessage(
+ $packet->getTopic(),
+ $packet->getPayload(),
+ $packet->getQosLevel(),
+ $packet->isRetained(),
+ $packet->isDuplicate()
+ );
+
+ $this->startFlow(new IncomingPublishFlow($message, $packet->getIdentifier()));
+ break;
+ case Packet::TYPE_CONNACK:
+ case Packet::TYPE_PINGRESP:
+ case Packet::TYPE_SUBACK:
+ case Packet::TYPE_UNSUBACK:
+ case Packet::TYPE_PUBREL:
+ case Packet::TYPE_PUBACK:
+ case Packet::TYPE_PUBREC:
+ case Packet::TYPE_PUBCOMP:
+ $flowFound = false;
+ foreach ($this->receivingFlows as $index => $flow) {
+ if ($flow->accept($packet)) {
+ $flowFound = true;
+
+ unset($this->receivingFlows[$index]);
+ $this->continueFlow($flow, $packet);
+
+ break;
+ }
+ }
+
+ if (!$flowFound) {
+ $this->emitWarning(
+ new \LogicException(sprintf('Received unexpected packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ break;
+ default:
+ $this->emitWarning(
+ new \LogicException(sprintf('Cannot handle packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ }
+
+ /**
+ * Handles outgoing packets.
+ */
+ private function handleSend()
+ {
+ $flow = null;
+ if ($this->writtenFlow !== null) {
+ $flow = $this->writtenFlow;
+ $this->writtenFlow = null;
+ }
+
+ if (count($this->sendingFlows) > 0) {
+ $this->writtenFlow = array_shift($this->sendingFlows);
+ $this->stream->write($this->writtenFlow->getPacket());
+ }
+
+ if ($flow !== null) {
+ if ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ } else {
+ $this->receivingFlows[] = $flow;
+ }
+ }
+ }
+
+ /**
+ * Handles closing of the stream.
+ */
+ private function handleClose()
+ {
+ foreach ($this->timer as $timer) {
+ $this->loop->cancelTimer($timer);
+ }
+ $this->timer = [];
+
+ $this->cleanPreviousSession();
+
+ $connection = $this->connection;
+
+ $this->isConnecting = false;
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+ $this->connection = null;
+ $this->stream = null;
+
+ if ($connection !== null) {
+ $this->emit('close', [$connection, $this]);
+ }
+ }
+
+ /**
+ * Handles errors of the stream.
+ *
+ * @param \Exception $e
+ */
+ private function handleError(\Exception $e)
+ {
+ $this->emitError($e);
+ }
+
+ /**
+ * Starts the given flow.
+ *
+ * @param Flow $flow
+ * @param bool $isSilent
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function startFlow(Flow $flow, $isSilent = false)
+ {
+ try {
+ $packet = $flow->start();
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return new RejectedPromise($e);
+ }
+
+ $deferred = new Deferred();
+ $internalFlow = new ReactFlow($flow, $deferred, $packet, $isSilent);
+
+ if ($packet !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $internalFlow;
+ } else {
+ $this->stream->write($packet);
+ $this->writtenFlow = $internalFlow;
+ $this->handleSend();
+ }
+ } else {
+ $this->loop->nextTick(function () use ($internalFlow) {
+ $this->finishFlow($internalFlow);
+ });
+ }
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Continues the given flow.
+ *
+ * @param ReactFlow $flow
+ * @param Packet $packet
+ */
+ private function continueFlow(ReactFlow $flow, Packet $packet)
+ {
+ try {
+ $response = $flow->next($packet);
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return;
+ }
+
+ if ($response !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $flow;
+ } else {
+ $this->stream->write($response);
+ $this->writtenFlow = $flow;
+ $this->handleSend();
+ }
+ } elseif ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ }
+ }
+
+ /**
+ * Finishes the given flow.
+ *
+ * @param ReactFlow $flow
+ */
+ private function finishFlow(ReactFlow $flow)
+ {
+ if ($flow->isSuccess()) {
+ if (!$flow->isSilent()) {
+ $this->emit($flow->getCode(), [$flow->getResult(), $this]);
+ }
+
+ $flow->getDeferred()->resolve($flow->getResult());
+ } else {
+ $result = new \RuntimeException($flow->getErrorMessage());
+ $this->emitWarning($result);
+
+ $flow->getDeferred()->reject($result);
+ }
+ }
+
+ /**
+ * Cleans previous session by rejecting all pending flows.
+ */
+ private function cleanPreviousSession()
+ {
+ $error = new \RuntimeException('Connection has been closed.');
+ foreach ($this->receivingFlows as $receivingFlow) {
+ $receivingFlow->getDeferred()->reject($error);
+ }
+ $this->receivingFlows = [];
+ foreach ($this->sendingFlows as $sendingFlow) {
+ $sendingFlow->getDeferred()->reject($error);
+ }
+ $this->sendingFlows = [];
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/StreamParser.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/StreamParser.php
new file mode 100755
index 0000000..edfabf6
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Lite/StreamParser.php
@@ -0,0 +1,102 @@
+buffer = new PacketStream();
+ $this->factory = new PacketFactory();
+ }
+
+ /**
+ * Registers an error callback.
+ *
+ * @param callable $callback
+ */
+ public function onError($callback)
+ {
+ $this->errorCallback = $callback;
+ }
+
+ /**
+ * Appends the given data to the internal buffer and parses it.
+ *
+ * @param string $data
+ *
+ * @return Packet[]
+ */
+ public function push($data)
+ {
+ $this->buffer->write($data);
+
+ $result = [];
+ while ($this->buffer->getRemainingBytes() > 0) {
+ $type = $this->buffer->readByte() >> 4;
+ try {
+ $packet = $this->factory->build($type);
+ } catch (UnknownPacketTypeException $e) {
+ $this->handleError($e);
+ continue;
+ }
+
+ $this->buffer->seek(-1);
+ $position = $this->buffer->getPosition();
+ try {
+ $packet->read($this->buffer);
+ $result[] = $packet;
+ $this->buffer->cut();
+ } catch (EndOfStreamException $e) {
+ $this->buffer->setPosition($position);
+ break;
+ } catch (MalformedPacketException $e) {
+ $this->handleError($e);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the registered error callback.
+ *
+ * @param \Throwable $exception
+ */
+ private function handleError($exception)
+ {
+ if ($this->errorCallback !== null) {
+ $callback = $this->errorCallback;
+ $callback($exception);
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Message/Push.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Message/Push.php
new file mode 100755
index 0000000..9a04ea2
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Message/Push.php
@@ -0,0 +1,153 @@
+json = $json;
+
+ if (isset($data->token)) {
+ $this->token = (string) $data->token;
+ }
+ if (isset($data->ck)) {
+ $this->connectionKey = (string) $data->ck;
+ }
+ if (isset($data->pn)) {
+ $this->packageName = (string) $data->pn;
+ }
+ if (isset($data->cp)) {
+ $this->collapseKey = (string) $data->cp;
+ }
+ if (isset($data->fbpushnotif)) {
+ $this->payload = (string) $data->fbpushnotif;
+ }
+ if (isset($data->nid)) {
+ $this->notificationId = (string) $data->nid;
+ }
+ if (isset($data->bu)) {
+ $this->isBuffered = (string) $data->bu;
+ }
+ }
+
+ /**
+ * Message constructor.
+ *
+ * @param string $json
+ */
+ public function __construct($json)
+ {
+ $this->parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->json;
+ }
+
+ /**
+ * @return string
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+
+ /**
+ * @return string
+ */
+ public function getConnectionKey()
+ {
+ return $this->connectionKey;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPackageName()
+ {
+ return $this->packageName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCollapseKey()
+ {
+ return $this->collapseKey;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ /**
+ * @return string
+ */
+ public function getNotificationId()
+ {
+ return $this->notificationId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIsBuffered()
+ {
+ return $this->isBuffered;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Message/Register.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Message/Register.php
new file mode 100755
index 0000000..6fb9cf5
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Message/Register.php
@@ -0,0 +1,89 @@
+json = $json;
+
+ if (isset($data->pkg_name)) {
+ $this->packageName = (string) $data->pkg_name;
+ }
+ if (isset($data->token)) {
+ $this->token = (string) $data->token;
+ }
+ if (isset($data->error)) {
+ $this->error = (string) $data->error;
+ }
+ }
+
+ /**
+ * Message constructor.
+ *
+ * @param string $json
+ */
+ public function __construct($json)
+ {
+ $this->parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->json;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPackageName()
+ {
+ return $this->packageName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+
+ /**
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Thrift/Compact.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Thrift/Compact.php
new file mode 100755
index 0000000..f045449
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Thrift/Compact.php
@@ -0,0 +1,24 @@
+handler($context, $field, $value, $type);
+ });
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Thrift/Reader.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Thrift/Reader.php
new file mode 100755
index 0000000..0b48175
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Thrift/Reader.php
@@ -0,0 +1,263 @@
+buffer = $buffer;
+ $this->position = 0;
+ $this->length = strlen($buffer);
+ $this->field = 0;
+ $this->stack = [];
+ $this->handler = $handler;
+ $this->parse();
+ }
+
+ /**
+ * Parser.
+ */
+ private function parse()
+ {
+ $context = '';
+ while ($this->position < $this->length) {
+ $type = $this->readField();
+ switch ($type) {
+ case Compact::TYPE_STRUCT:
+ array_push($this->stack, $this->field);
+ $this->field = 0;
+ $context = implode('/', $this->stack);
+ break;
+ case Compact::TYPE_STOP:
+ if (!count($this->stack)) {
+ return;
+ }
+ $this->field = array_pop($this->stack);
+ $context = implode('/', $this->stack);
+ break;
+ case Compact::TYPE_LIST:
+ $sizeAndType = $this->readUnsignedByte();
+ $size = $sizeAndType >> 4;
+ $listType = $sizeAndType & 0x0f;
+ if ($size === 0x0f) {
+ $size = $this->readVarint();
+ }
+ $this->handleField($context, $this->field, $this->readList($size, $listType), $listType);
+ break;
+ case Compact::TYPE_TRUE:
+ case Compact::TYPE_FALSE:
+ $this->handleField($context, $this->field, $type === Compact::TYPE_TRUE, $type);
+ break;
+ case Compact::TYPE_BYTE:
+ $this->handleField($context, $this->field, $this->readSignedByte(), $type);
+ break;
+ case Compact::TYPE_I16:
+ case Compact::TYPE_I32:
+ case Compact::TYPE_I64:
+ $this->handleField($context, $this->field, $this->fromZigZag($this->readVarint()), $type);
+ break;
+ case Compact::TYPE_BINARY:
+ $this->handleField($context, $this->field, $this->readString($this->readVarint()), $type);
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param int $size
+ * @param int $type
+ *
+ * @return array
+ */
+ private function readList($size, $type)
+ {
+ $result = [];
+ switch ($type) {
+ case Compact::TYPE_TRUE:
+ case Compact::TYPE_FALSE:
+ for ($i = 0; $i < $size; $i++) {
+ $result[] = $this->readSignedByte() === Compact::TYPE_TRUE;
+ }
+ break;
+ case Compact::TYPE_BYTE:
+ for ($i = 0; $i < $size; $i++) {
+ $result[] = $this->readSignedByte();
+ }
+ break;
+ case Compact::TYPE_I16:
+ case Compact::TYPE_I32:
+ case Compact::TYPE_I64:
+ for ($i = 0; $i < $size; $i++) {
+ $result[] = $this->fromZigZag($this->readVarint());
+ }
+ break;
+ case Compact::TYPE_BINARY:
+ $result[] = $this->readString($this->readVarint());
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return int
+ */
+ private function readField()
+ {
+ $typeAndDelta = ord($this->buffer[$this->position++]);
+ if ($typeAndDelta === Compact::TYPE_STOP) {
+ return Compact::TYPE_STOP;
+ }
+ $delta = $typeAndDelta >> 4;
+ if ($delta === 0) {
+ $this->field = $this->fromZigZag($this->readVarint());
+ } else {
+ $this->field += $delta;
+ }
+ $type = $typeAndDelta & 0x0f;
+
+ return $type;
+ }
+
+ /**
+ * @return int
+ */
+ private function readSignedByte()
+ {
+ $result = $this->readUnsignedByte();
+ if ($result > 0x7f) {
+ $result = 0 - (($result - 1) ^ 0xff);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return int
+ */
+ private function readUnsignedByte()
+ {
+ return ord($this->buffer[$this->position++]);
+ }
+
+ /**
+ * @return int
+ */
+ private function readVarint()
+ {
+ $shift = 0;
+ $result = 0;
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_init($result, 10);
+ }
+ while ($this->position < $this->length) {
+ $byte = ord($this->buffer[$this->position++]);
+ if (PHP_INT_SIZE === 4) {
+ $byte = gmp_init($byte, 10);
+ }
+ $result |= ($byte & 0x7f) << $shift;
+ if (PHP_INT_SIZE === 4) {
+ $byte = (int) gmp_strval($byte, 10);
+ }
+ if ($byte >> 7 === 0) {
+ break;
+ }
+ $shift += 7;
+ }
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_strval($result, 10);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $n
+ *
+ * @return int
+ */
+ private function fromZigZag($n)
+ {
+ if (PHP_INT_SIZE === 4) {
+ $n = gmp_init($n, 10);
+ }
+ $result = ($n >> 1) ^ -($n & 1);
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_strval($result, 10);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $length
+ *
+ * @return string
+ */
+ private function readString($length)
+ {
+ $result = substr($this->buffer, $this->position, $length);
+ $this->position += $length;
+
+ return $result;
+ }
+
+ /**
+ * @param string $context
+ * @param int $field
+ * @param mixed $value
+ * @param int $type
+ */
+ private function handleField($context, $field, $value, $type)
+ {
+ if (!is_callable($this->handler)) {
+ return;
+ }
+ call_user_func($this->handler, $context, $field, $value, $type);
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Thrift/Writer.php b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Thrift/Writer.php
new file mode 100755
index 0000000..229bdf1
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/valga/fbns-react/src/Thrift/Writer.php
@@ -0,0 +1,279 @@
+> ($bits - 1));
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_strval($result, 10);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeByte($number)
+ {
+ $this->buffer .= chr($number);
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeWord($number)
+ {
+ $this->writeVarint($this->toZigZag($number, 16));
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeInt($number)
+ {
+ $this->writeVarint($this->toZigZag($number, 32));
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeLongInt($number)
+ {
+ $this->writeVarint($this->toZigZag($number, 64));
+ }
+
+ /**
+ * @param int $field
+ * @param int $type
+ */
+ private function writeField($field, $type)
+ {
+ $delta = $field - $this->field;
+ if ((0 < $delta) && ($delta <= 15)) {
+ $this->writeByte(($delta << 4) | $type);
+ } else {
+ $this->writeByte($type);
+ $this->writeWord($field);
+ }
+ $this->field = $field;
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeVarint($number)
+ {
+ if (PHP_INT_SIZE === 4) {
+ $number = gmp_init($number, 10);
+ }
+ while (true) {
+ $byte = $number & (~0x7f);
+ if (PHP_INT_SIZE === 4) {
+ $byte = (int) gmp_strval($byte, 10);
+ }
+ if ($byte === 0) {
+ if (PHP_INT_SIZE === 4) {
+ $number = (int) gmp_strval($number, 10);
+ }
+ $this->buffer .= chr($number);
+ break;
+ } else {
+ $byte = ($number & 0xff) | 0x80;
+ if (PHP_INT_SIZE === 4) {
+ $byte = (int) gmp_strval($byte, 10);
+ }
+ $this->buffer .= chr($byte);
+ $number = $number >> 7;
+ }
+ }
+ }
+
+ /**
+ * @param string $data
+ */
+ private function writeBinary($data)
+ {
+ $this->buffer .= $data;
+ }
+
+ /**
+ * @param int $field
+ * @param bool $value
+ */
+ public function writeBool($field, $value)
+ {
+ $this->writeField($field, $value ? Compact::TYPE_TRUE : Compact::TYPE_FALSE);
+ }
+
+ /**
+ * @param int $field
+ * @param string $string
+ */
+ public function writeString($field, $string)
+ {
+ $this->writeField($field, Compact::TYPE_BINARY);
+ $this->writeVarint(strlen($string));
+ $this->writeBinary($string);
+ }
+
+ public function writeStop()
+ {
+ $this->buffer .= chr(Compact::TYPE_STOP);
+ if (count($this->stack)) {
+ $this->field = array_pop($this->stack);
+ }
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt8($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_BYTE);
+ $this->writeByte($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt16($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_I16);
+ $this->writeWord($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt32($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_I32);
+ $this->writeInt($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt64($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_I64);
+ $this->writeLongInt($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $type
+ * @param array $list
+ */
+ public function writeList($field, $type, array $list)
+ {
+ $this->writeField($field, Compact::TYPE_LIST);
+ $size = count($list);
+ if ($size < 0x0f) {
+ $this->writeByte(($size << 4) | $type);
+ } else {
+ $this->writeByte(0xf0 | $type);
+ $this->writeVarint($size);
+ }
+
+ switch ($type) {
+ case Compact::TYPE_TRUE:
+ case Compact::TYPE_FALSE:
+ foreach ($list as $value) {
+ $this->writeByte($value ? Compact::TYPE_TRUE : Compact::TYPE_FALSE);
+ }
+ break;
+ case Compact::TYPE_BYTE:
+ foreach ($list as $number) {
+ $this->writeByte($number);
+ }
+ break;
+ case Compact::TYPE_I16:
+ foreach ($list as $number) {
+ $this->writeWord($number);
+ }
+ break;
+ case Compact::TYPE_I32:
+ foreach ($list as $number) {
+ $this->writeInt($number);
+ }
+ break;
+ case Compact::TYPE_I64:
+ foreach ($list as $number) {
+ $this->writeLongInt($number);
+ }
+ break;
+ case Compact::TYPE_BINARY:
+ foreach ($list as $string) {
+ $this->writeVarint(strlen($string));
+ $this->writeBinary($string);
+ }
+ break;
+ }
+ }
+
+ /**
+ * @param int $field
+ */
+ public function writeStruct($field)
+ {
+ $this->writeField($field, Compact::TYPE_STRUCT);
+ $this->stack[] = $this->field;
+ $this->field = 0;
+ }
+
+ public function __construct()
+ {
+ if (PHP_INT_SIZE === 4 && !extension_loaded('gmp')) {
+ throw new \RuntimeException('You need to install GMP extension to run this code with x86 PHP build.');
+ }
+ $this->buffer = '';
+ $this->field = 0;
+ $this->stack = [];
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->buffer;
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/winbox/args/LICENSE b/src/mpg25-instagram-api/vendor/winbox/args/LICENSE
new file mode 100755
index 0000000..1f5c051
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/winbox/args/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 John Stevenson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/mpg25-instagram-api/vendor/winbox/args/README.md b/src/mpg25-instagram-api/vendor/winbox/args/README.md
new file mode 100755
index 0000000..9daab8c
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/winbox/args/README.md
@@ -0,0 +1,50 @@
+Winbox-Args
+===========
+
+[](https://travis-ci.org/johnstevenson/winbox-args)
+[](https://ci.appveyor.com/project/johnstevenson/winbox-args)
+
+A PHP function to escape command-line arguments, which on Windows replaces `escapeshellarg` with a more robust method. Install from [Packagist][packagist] and use it like this:
+
+```php
+$escaped = Winbox\Args::escape($argument);
+```
+
+Alternatively, you can just [copy the code][function] into your own project (but please keep the license attribution and documentation link).
+
+### What it does
+The following transformations are made:
+
+* Double-quotes are escaped with a backslash, with any preceeding backslashes doubled up.
+* The argument is only enclosed in double-quotes if it contains whitespace or is empty.
+* Trailing backslashes are doubled up if the argument is enclosed in double-quotes.
+
+See [How Windows parses the command-line](https://github.com/johnstevenson/winbox-args/wiki/How-Windows-parses-the-command-line) if you would like to know why.
+
+By default, _cmd.exe_ meta characters are also escaped:
+
+* by caret-escaping the transformed argument (if it contains internal double-quotes or `%...%` syntax).
+* or by enclosing the argument in double-quotes.
+
+There are a couple limitations:
+
+1. If _cmd_ is started with _DelayedExpansion_ enabled, `!...!` syntax could expand environment variables.
+2. If the program name requires caret-escaping and contains whitespace, _cmd_ will not recognize it.
+
+See [How cmd.exe parses a command](https://github.com/johnstevenson/winbox-args/wiki/How-cmd.exe-parses-a-command) and [Implementing a solution](https://github.com/johnstevenson/winbox-args/wiki/Implementing-a-solution) for more information.
+
+### Is that it?
+Yup. An entire repo for a tiny function. However, it needs quite a lot of explanation because:
+
+* the command-line parsing rules in Windows are not immediately obvious.
+* PHP generally uses _cmd.exe_ to execute programs and this applies a different set of rules.
+* there is no simple solution.
+
+Full details explaining the different parsing rules, potential pitfalls and limitations can be found in the [Wiki][wiki].
+
+## License
+Winbox-Args is licensed under the MIT License - see the LICENSE file for details.
+
+[function]: https://github.com/johnstevenson/winbox-args/blob/master/src/Args.php#L15
+[wiki]:https://github.com/johnstevenson/winbox-args/wiki/Home
+[packagist]: https://packagist.org/packages/winbox/args
diff --git a/src/mpg25-instagram-api/vendor/winbox/args/appveyor.yml b/src/mpg25-instagram-api/vendor/winbox/args/appveyor.yml
new file mode 100755
index 0000000..3f90587
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/winbox/args/appveyor.yml
@@ -0,0 +1,22 @@
+build: false
+shallow_clone: false
+platform: 'x86'
+clone_folder: C:\projects\winbox-args
+init:
+ - cinst php
+ - SET PATH=C:\tools\php\;%PATH%
+install:
+ - cd c:\tools\php
+ - copy php.ini-production php.ini
+ - echo date.timezone="UTC" >> php.ini
+ - echo extension_dir=ext >> php.ini
+ - echo extension=php_openssl.dll >> php.ini
+ - echo extension=php_intl.dll >> php.ini
+ - echo extension=php_mbstring.dll >> php.ini
+ - echo extension=php_fileinfo.dll >> php.ini
+ - cd C:\projects\winbox-args
+ - php -r "readfile('https://getcomposer.org/installer');" | php
+ - php composer.phar require phpunit/phpunit:4.* --prefer-dist --dev --no-interaction
+test_script:
+ - cd C:\projects\winbox-args
+ - vendor\bin\phpunit.bat
diff --git a/src/mpg25-instagram-api/vendor/winbox/args/composer.json b/src/mpg25-instagram-api/vendor/winbox/args/composer.json
new file mode 100755
index 0000000..0491911
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/winbox/args/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "winbox/args",
+ "description": "Windows command-line formatter",
+ "keywords": ["windows", "escape", "command"],
+ "homepage": "http://github.com/johnstevenson/winbox-args",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "autoload": {
+ "psr-4": {
+ "Winbox\\": "src/"
+ }
+ }
+}
diff --git a/src/mpg25-instagram-api/vendor/winbox/args/src/Args.php b/src/mpg25-instagram-api/vendor/winbox/args/src/Args.php
new file mode 100755
index 0000000..7f3dd89
--- /dev/null
+++ b/src/mpg25-instagram-api/vendor/winbox/args/src/Args.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Winbox;
+
+class Args
+{
+ /**
+ * Escapes a string to be used as a shell argument
+ *
+ * Provides a more robust method on Windows than escapeshellarg.
+ *
+ * Feel free to copy this function, but please keep the following notice:
+ * MIT Licensed (c) John Stevenson
+ * See https://github.com/johnstevenson/winbox-args for more information.
+ *
+ * @param string $arg The argument to be escaped
+ * @param bool $meta Additionally escape cmd.exe meta characters
+ *
+ * @return string The escaped argument
+ */
+ public static function escape($arg, $meta = true)
+ {
+ if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
+ return escapeshellarg($arg);
+ }
+
+ $quote = strpbrk($arg, " \t") !== false || $arg === '';
+ $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes);
+
+ if ($meta) {
+ $meta = $dquotes || preg_match('/%[^%]+%/', $arg);
+
+ if (!$meta && !$quote) {
+ $quote = strpbrk($arg, '^&|<>()') !== false;
+ }
+ }
+
+ if ($quote) {
+ $arg = preg_replace('/(\\\\*)$/', '$1$1', $arg);
+ $arg = '"'.$arg.'"';
+ }
+
+ if ($meta) {
+ $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg);
+ }
+
+ return $arg;
+ }
+}
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100755
index 0000000..f626d9e
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,7 @@
+
+
+> Permission is hereby granted, free of charge, to any person obtaining a copy
+> of this software and associated documentation files (the "Software"), to deal
+> in the Software without restriction, including without limitation the rights
+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+> copies of the Software, and to permit persons to whom the Software is
+> furnished to do so, subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in
+> all copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+> THE SOFTWARE.
diff --git a/vendor/binsoul/net-mqtt-client-react/README.md b/vendor/binsoul/net-mqtt-client-react/README.md
new file mode 100755
index 0000000..ca3099d
--- /dev/null
+++ b/vendor/binsoul/net-mqtt-client-react/README.md
@@ -0,0 +1,145 @@
+# net-mqtt-client-react
+
+[![Latest Version on Packagist][ico-version]][link-packagist]
+[![Software License][ico-license]](LICENSE.md)
+[![Total Downloads][ico-downloads]][link-downloads]
+
+This package provides an asynchronous MQTT client built on the [React socket](https://github.com/reactphp/socket) library. All client methods return a promise which is fulfilled if the operation succeeded or rejected if the operation failed. Incoming messages of subscribed topics are delivered via the "message" event.
+
+## Install
+
+Via composer:
+
+``` bash
+$ composer require binsoul/net-mqtt-client-react
+```
+
+## Example
+
+Connect to a public broker and run forever.
+
+``` php
+createCached('8.8.8.8', $loop));
+$client = new ReactMqttClient($connector, $loop);
+
+// Bind to events
+$client->on('open', function () use ($client) {
+ // Network connection established
+ echo sprintf("Open: %s:%s\n", $client->getHost(), $client->getPort());
+});
+
+$client->on('close', function () use ($client, $loop) {
+ // Network connection closed
+ echo sprintf("Close: %s:%s\n", $client->getHost(), $client->getPort());
+
+ $loop->stop();
+});
+
+$client->on('connect', function (Connection $connection) {
+ // Broker connected
+ echo sprintf("Connect: client=%s\n", $connection->getClientID());
+});
+
+$client->on('disconnect', function (Connection $connection) {
+ // Broker disconnected
+ echo sprintf("Disconnect: client=%s\n", $connection->getClientID());
+});
+
+$client->on('message', function (Message $message) {
+ // Incoming message
+ echo 'Message';
+
+ if ($message->isDuplicate()) {
+ echo ' (duplicate)';
+ }
+
+ if ($message->isRetained()) {
+ echo ' (retained)';
+ }
+
+ echo ': '.$message->getTopic().' => '.mb_strimwidth($message->getPayload(), 0, 50, '...');
+ echo "\n";
+});
+
+$client->on('warning', function (\Exception $e) {
+ echo sprintf("Warning: %s\n", $e->getMessage());
+});
+
+$client->on('error', function (\Exception $e) use ($loop) {
+ echo sprintf("Error: %s\n", $e->getMessage());
+
+ $loop->stop();
+});
+
+// Connect to broker
+$client->connect('test.mosquitto.org')->then(
+ function () use ($client) {
+ // Subscribe to all topics
+ $client->subscribe(new DefaultSubscription('#'))
+ ->then(function (Subscription $subscription) {
+ echo sprintf("Subscribe: %s\n", $subscription->getFilter());
+ })
+ ->otherwise(function (\Exception $e) {
+ echo sprintf("Error: %s\n", $e->getMessage());
+ });
+
+ // Publish humidity once
+ $client->publish(new DefaultMessage('sensors/humidity', '55%'))
+ ->then(function (Message $message) {
+ echo sprintf("Publish: %s => %s\n", $message->getTopic(), $message->getPayload());
+ })
+ ->otherwise(function (\Exception $e) {
+ echo sprintf("Error: %s\n", $e->getMessage());
+ });
+
+ // Publish a random temperature every 10 seconds
+ $generator = function () {
+ return mt_rand(-20, 30);
+ };
+
+ $client->publishPeriodically(10, new DefaultMessage('sensors/temperature'), $generator)
+ ->progress(function (Message $message) {
+ echo sprintf("Publish: %s => %s\n", $message->getTopic(), $message->getPayload());
+ })
+ ->otherwise(function (\Exception $e) {
+ echo sprintf("Error: %s\n", $e->getMessage());
+ });
+ }
+);
+
+$loop->run();
+```
+
+## Testing
+
+``` bash
+$ composer test
+```
+
+## License
+
+The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
+
+[ico-version]: https://img.shields.io/packagist/v/binsoul/net-mqtt-client-react.svg?style=flat-square
+[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
+[ico-downloads]: https://img.shields.io/packagist/dt/binsoul/net-mqtt-client-react.svg?style=flat-square
+
+[link-packagist]: https://packagist.org/packages/binsoul/net-mqtt-client-react
+[link-downloads]: https://packagist.org/packages/binsoul/net-mqtt-client-react
+[link-author]: https://github.com/binsoul
diff --git a/vendor/binsoul/net-mqtt-client-react/composer.json b/vendor/binsoul/net-mqtt-client-react/composer.json
new file mode 100755
index 0000000..e9dcc25
--- /dev/null
+++ b/vendor/binsoul/net-mqtt-client-react/composer.json
@@ -0,0 +1,51 @@
+{
+ "name": "binsoul/net-mqtt-client-react",
+ "description": "Asynchronous MQTT client built on React",
+ "keywords": [
+ "net",
+ "mqtt",
+ "client"
+ ],
+ "homepage": "https://github.com/binsoul/net-mqtt-client-react",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": "~5.6|~7.0",
+ "binsoul/net-mqtt": "~0.2",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0||~5.0",
+ "friendsofphp/php-cs-fixer": "~1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\Client\\React\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "BinSoul\\Test\\Net\\Mqtt\\Client\\React\\": "tests"
+ }
+ },
+ "scripts": {
+ "test": "phpunit",
+ "fix-style": [
+ "php-cs-fixer fix src",
+ "php-cs-fixer fix tests"
+ ]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ }
+}
diff --git a/vendor/binsoul/net-mqtt-client-react/src/ReactFlow.php b/vendor/binsoul/net-mqtt-client-react/src/ReactFlow.php
new file mode 100755
index 0000000..d404145
--- /dev/null
+++ b/vendor/binsoul/net-mqtt-client-react/src/ReactFlow.php
@@ -0,0 +1,112 @@
+decorated = $decorated;
+ $this->deferred = $deferred;
+ $this->packet = $packet;
+ $this->isSilent = $isSilent;
+ }
+
+ public function getCode()
+ {
+ return $this->decorated->getCode();
+ }
+
+ public function start()
+ {
+ $this->packet = $this->decorated->start();
+
+ return $this->packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $this->decorated->accept($packet);
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->packet = $this->decorated->next($packet);
+
+ return $this->packet;
+ }
+
+ public function isFinished()
+ {
+ return $this->decorated->isFinished();
+ }
+
+ public function isSuccess()
+ {
+ return $this->decorated->isSuccess();
+ }
+
+ public function getResult()
+ {
+ return $this->decorated->getResult();
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->decorated->getErrorMessage();
+ }
+
+ /**
+ * Returns the associated deferred.
+ *
+ * @return Deferred
+ */
+ public function getDeferred()
+ {
+ return $this->deferred;
+ }
+
+ /**
+ * Returns the current packet.
+ *
+ * @return Packet
+ */
+ public function getPacket()
+ {
+ return $this->packet;
+ }
+
+ /**
+ * Indicates if the flow should emit events.
+ *
+ * @return bool
+ */
+ public function isSilent()
+ {
+ return $this->isSilent;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt-client-react/src/ReactMqttClient.php b/vendor/binsoul/net-mqtt-client-react/src/ReactMqttClient.php
new file mode 100755
index 0000000..f56b191
--- /dev/null
+++ b/vendor/binsoul/net-mqtt-client-react/src/ReactMqttClient.php
@@ -0,0 +1,701 @@
+connector = $connector;
+ $this->loop = $loop;
+
+ $this->parser = $parser;
+ if ($this->parser === null) {
+ $this->parser = new StreamParser();
+ }
+
+ $this->parser->onError(function (\Exception $e) {
+ $this->emitWarning($e);
+ });
+
+ $this->identifierGenerator = $identifierGenerator;
+ if ($this->identifierGenerator === null) {
+ $this->identifierGenerator = new DefaultIdentifierGenerator();
+ }
+ }
+
+ /**
+ * Return the host.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Return the port.
+ *
+ * @return string
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Indicates if the client is connected.
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->isConnected;
+ }
+
+ /**
+ * Returns the underlying stream or null if the client is not connected.
+ *
+ * @return DuplexStreamInterface|null
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+
+ /**
+ * Connects to a broker.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function connect($host, $port = 1883, Connection $connection = null, $timeout = 5)
+ {
+ if ($this->isConnected || $this->isConnecting) {
+ return new RejectedPromise(new \LogicException('The client is already connected.'));
+ }
+
+ $this->isConnecting = true;
+ $this->isConnected = false;
+
+ $this->host = $host;
+ $this->port = $port;
+
+ if ($connection === null) {
+ $connection = new DefaultConnection();
+ }
+
+ if ($connection->isCleanSession()) {
+ $this->cleanPreviousSession();
+ }
+
+ if ($connection->getClientID() === '') {
+ $connection = $connection->withClientID($this->identifierGenerator->generateClientID());
+ }
+
+ $deferred = new Deferred();
+
+ $this->establishConnection($this->host, $this->port, $timeout)
+ ->then(function (DuplexStreamInterface $stream) use ($connection, $deferred, $timeout) {
+ $this->stream = $stream;
+
+ $this->emit('open', [$connection, $this]);
+
+ $this->registerClient($connection, $timeout)
+ ->then(function (Connection $connection) use ($deferred) {
+ $this->isConnecting = false;
+ $this->isConnected = true;
+ $this->connection = $connection;
+
+ $this->emit('connect', [$connection, $this]);
+ $deferred->resolve($this->connection);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred, $connection) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+
+ $this->emit('close', [$connection, $this]);
+ });
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Disconnects from a broker.
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function disconnect()
+ {
+ if (!$this->isConnected || $this->isDisconnecting) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $this->isDisconnecting = true;
+
+ $deferred = new Deferred();
+
+ $this->startFlow(new OutgoingDisconnectFlow($this->connection), true)
+ ->then(function (Connection $connection) use ($deferred) {
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+
+ $this->emit('disconnect', [$connection, $this]);
+ $deferred->resolve($connection);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+ })
+ ->otherwise(function () use ($deferred) {
+ $this->isDisconnecting = false;
+ $deferred->reject($this->connection);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Subscribes to a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function subscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingSubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Unsubscribes from a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function unsubscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingUnsubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Publishes a message.
+ *
+ * @param Message $message
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publish(Message $message)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingPublishFlow($message, $this->identifierGenerator));
+ }
+
+ /**
+ * Calls the given generator periodically and publishes the return value.
+ *
+ * @param int $interval
+ * @param Message $message
+ * @param callable $generator
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publishPeriodically($interval, Message $message, callable $generator)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $deferred = new Deferred();
+
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ $interval,
+ function () use ($message, $generator, $deferred) {
+ $this->publish($message->withPayload($generator($message->getTopic())))->then(
+ function ($value) use ($deferred) {
+ $deferred->notify($value);
+ },
+ function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ }
+ );
+ }
+ );
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Emits warnings.
+ *
+ * @param \Exception $e
+ */
+ private function emitWarning(\Exception $e)
+ {
+ $this->emit('warning', [$e, $this]);
+ }
+
+ /**
+ * Emits errors.
+ *
+ * @param \Exception $e
+ */
+ private function emitError(\Exception $e)
+ {
+ $this->emit('error', [$e, $this]);
+ }
+
+ /**
+ * Establishes a network connection to a server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function establishConnection($host, $port, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $timer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('Connection timed out after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->connector->connect($host.':'.$port)
+ ->always(function () use ($timer) {
+ $this->loop->cancelTimer($timer);
+ })
+ ->then(function (DuplexStreamInterface $stream) use ($deferred) {
+ $stream->on('data', function ($data) {
+ $this->handleReceive($data);
+ });
+
+ $stream->on('close', function () {
+ $this->handleClose();
+ });
+
+ $stream->on('error', function (\Exception $e) {
+ $this->handleError($e);
+ });
+
+ $deferred->resolve($stream);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Registers a new client with the broker.
+ *
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function registerClient(Connection $connection, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $responseTimer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('No response after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->startFlow(new OutgoingConnectFlow($connection, $this->identifierGenerator), true)
+ ->always(function () use ($responseTimer) {
+ $this->loop->cancelTimer($responseTimer);
+ })->then(function (Connection $connection) use ($deferred) {
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ floor($connection->getKeepAlive() * 0.75),
+ function () {
+ $this->startFlow(new OutgoingPingFlow());
+ }
+ );
+
+ $deferred->resolve($connection);
+ })->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Handles incoming data.
+ *
+ * @param string $data
+ */
+ private function handleReceive($data)
+ {
+ if (!$this->isConnected && !$this->isConnecting) {
+ return;
+ }
+
+ $flowCount = count($this->receivingFlows);
+
+ $packets = $this->parser->push($data);
+ foreach ($packets as $packet) {
+ $this->handlePacket($packet);
+ }
+
+ if ($flowCount > count($this->receivingFlows)) {
+ $this->receivingFlows = array_values($this->receivingFlows);
+ }
+
+ $this->handleSend();
+ }
+
+ /**
+ * Handles an incoming packet.
+ *
+ * @param Packet $packet
+ */
+ private function handlePacket(Packet $packet)
+ {
+ switch ($packet->getPacketType()) {
+ case Packet::TYPE_PUBLISH:
+ /* @var PublishRequestPacket $packet */
+ $message = new DefaultMessage(
+ $packet->getTopic(),
+ $packet->getPayload(),
+ $packet->getQosLevel(),
+ $packet->isRetained(),
+ $packet->isDuplicate()
+ );
+
+ $this->startFlow(new IncomingPublishFlow($message, $packet->getIdentifier()));
+ break;
+ case Packet::TYPE_CONNACK:
+ case Packet::TYPE_PINGRESP:
+ case Packet::TYPE_SUBACK:
+ case Packet::TYPE_UNSUBACK:
+ case Packet::TYPE_PUBREL:
+ case Packet::TYPE_PUBACK:
+ case Packet::TYPE_PUBREC:
+ case Packet::TYPE_PUBCOMP:
+ $flowFound = false;
+ foreach ($this->receivingFlows as $index => $flow) {
+ if ($flow->accept($packet)) {
+ $flowFound = true;
+
+ unset($this->receivingFlows[$index]);
+ $this->continueFlow($flow, $packet);
+
+ break;
+ }
+ }
+
+ if (!$flowFound) {
+ $this->emitWarning(
+ new \LogicException(sprintf('Received unexpected packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ break;
+ default:
+ $this->emitWarning(
+ new \LogicException(sprintf('Cannot handle packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ }
+
+ /**
+ * Handles outgoing packets.
+ */
+ private function handleSend()
+ {
+ $flow = null;
+ if ($this->writtenFlow !== null) {
+ $flow = $this->writtenFlow;
+ $this->writtenFlow = null;
+ }
+
+ if (count($this->sendingFlows) > 0) {
+ $this->writtenFlow = array_shift($this->sendingFlows);
+ $this->stream->write($this->writtenFlow->getPacket());
+ }
+
+ if ($flow !== null) {
+ if ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ } else {
+ $this->receivingFlows[] = $flow;
+ }
+ }
+ }
+
+ /**
+ * Handles closing of the stream.
+ */
+ private function handleClose()
+ {
+ foreach ($this->timer as $timer) {
+ $this->loop->cancelTimer($timer);
+ }
+
+ $this->timer = [];
+
+ $connection = $this->connection;
+
+ $this->isConnecting = false;
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+ $this->connection = null;
+ $this->stream = null;
+
+ if ($connection !== null) {
+ $this->emit('close', [$connection, $this]);
+ }
+ }
+
+ /**
+ * Handles errors of the stream.
+ *
+ * @param \Exception $e
+ */
+ private function handleError(\Exception $e)
+ {
+ $this->emitError($e);
+ }
+
+ /**
+ * Starts the given flow.
+ *
+ * @param Flow $flow
+ * @param bool $isSilent
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function startFlow(Flow $flow, $isSilent = false)
+ {
+ try {
+ $packet = $flow->start();
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return new RejectedPromise($e);
+ }
+
+ $deferred = new Deferred();
+ $internalFlow = new ReactFlow($flow, $deferred, $packet, $isSilent);
+
+ if ($packet !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $internalFlow;
+ } else {
+ $this->stream->write($packet);
+ $this->writtenFlow = $internalFlow;
+ $this->handleSend();
+ }
+ } else {
+ $this->loop->nextTick(function () use ($internalFlow) {
+ $this->finishFlow($internalFlow);
+ });
+ }
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Continues the given flow.
+ *
+ * @param ReactFlow $flow
+ * @param Packet $packet
+ */
+ private function continueFlow(ReactFlow $flow, Packet $packet)
+ {
+ try {
+ $response = $flow->next($packet);
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return;
+ }
+
+ if ($response !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $flow;
+ } else {
+ $this->stream->write($response);
+ $this->writtenFlow = $flow;
+ $this->handleSend();
+ }
+ } elseif ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ }
+ }
+
+ /**
+ * Finishes the given flow.
+ *
+ * @param ReactFlow $flow
+ */
+ private function finishFlow(ReactFlow $flow)
+ {
+ if ($flow->isSuccess()) {
+ if (!$flow->isSilent()) {
+ $this->emit($flow->getCode(), [$flow->getResult(), $this]);
+ }
+
+ $flow->getDeferred()->resolve($flow->getResult());
+ } else {
+ $result = new \RuntimeException($flow->getErrorMessage());
+ $this->emitWarning($result);
+
+ $flow->getDeferred()->reject($result);
+ }
+ }
+
+ /**
+ * Cleans previous session by rejecting all pending flows.
+ */
+ private function cleanPreviousSession()
+ {
+ $error = new \RuntimeException('Connection has been closed.');
+
+ foreach ($this->receivingFlows as $receivingFlow) {
+ $receivingFlow->getDeferred()->reject($error);
+ }
+
+ foreach ($this->sendingFlows as $sendingFlow) {
+ $sendingFlow->getDeferred()->reject($error);
+ }
+
+ $this->receivingFlows = [];
+ $this->sendingFlows = [];
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/LICENSE.md b/vendor/binsoul/net-mqtt/LICENSE.md
new file mode 100755
index 0000000..6d19c5c
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/LICENSE.md
@@ -0,0 +1,21 @@
+# The MIT License (MIT)
+
+Copyright (c) 2015 Sebastian Mößler
+
+> Permission is hereby granted, free of charge, to any person obtaining a copy
+> of this software and associated documentation files (the "Software"), to deal
+> in the Software without restriction, including without limitation the rights
+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+> copies of the Software, and to permit persons to whom the Software is
+> furnished to do so, subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in
+> all copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+> THE SOFTWARE.
diff --git a/vendor/binsoul/net-mqtt/README.md b/vendor/binsoul/net-mqtt/README.md
new file mode 100755
index 0000000..9f341da
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/README.md
@@ -0,0 +1,36 @@
+# net-mqtt
+
+[![Latest Version on Packagist][ico-version]][link-packagist]
+[![Software License][ico-license]](LICENSE.md)
+[![Total Downloads][ico-downloads]][link-downloads]
+
+MQTT is a machine-to-machine (M2M) / Internet of Things (IoT) connectivity protocol. It provides a lightweight method of carrying out messaging using a publish/subscribe model.
+
+This package implements the MQTT protocol versions 3.1 and 3.1.1.
+
+
+## Install
+
+Via composer:
+
+``` bash
+$ composer require binsoul/net-mqtt
+```
+
+## Testing
+
+``` bash
+$ composer test
+```
+
+## License
+
+The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
+
+[ico-version]: https://img.shields.io/packagist/v/binsoul/net-mqtt.svg?style=flat-square
+[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
+[ico-downloads]: https://img.shields.io/packagist/dt/binsoul/net-mqtt.svg?style=flat-square
+
+[link-packagist]: https://packagist.org/packages/binsoul/net-mqtt
+[link-downloads]: https://packagist.org/packages/binsoul/net-mqtt
+[link-author]: https://github.com/binsoul
diff --git a/vendor/binsoul/net-mqtt/composer.json b/vendor/binsoul/net-mqtt/composer.json
new file mode 100755
index 0000000..035ad9f
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/composer.json
@@ -0,0 +1,47 @@
+{
+ "name": "binsoul/net-mqtt",
+ "description": "MQTT protocol implementation",
+ "keywords": [
+ "net",
+ "mqtt"
+ ],
+ "homepage": "https://github.com/binsoul/net-mqtt",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": "~5.6|~7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0||~5.0",
+ "friendsofphp/php-cs-fixer": "~1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "BinSoul\\Test\\Net\\Mqtt\\": "tests"
+ }
+ },
+ "scripts": {
+ "test": "phpunit",
+ "fix-style": [
+ "php-cs-fixer fix src",
+ "php-cs-fixer fix tests"
+ ]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Connection.php b/vendor/binsoul/net-mqtt/src/Connection.php
new file mode 100755
index 0000000..5297b74
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Connection.php
@@ -0,0 +1,90 @@
+username = $username;
+ $this->password = $password;
+ $this->will = $will;
+ $this->clientID = $clientID;
+ $this->keepAlive = $keepAlive;
+ $this->protocol = $protocol;
+ $this->clean = $clean;
+ }
+
+ public function getProtocol()
+ {
+ return $this->protocol;
+ }
+
+ public function getClientID()
+ {
+ return $this->clientID;
+ }
+
+ public function isCleanSession()
+ {
+ return $this->clean;
+ }
+
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ public function getWill()
+ {
+ return $this->will;
+ }
+
+ public function getKeepAlive()
+ {
+ return $this->keepAlive;
+ }
+
+ public function withProtocol($protocol)
+ {
+ $result = clone $this;
+ $result->protocol = $protocol;
+
+ return $result;
+ }
+
+ public function withClientID($clientID)
+ {
+ $result = clone $this;
+ $result->clientID = $clientID;
+
+ return $result;
+ }
+
+ public function withCredentials($username, $password)
+ {
+ $result = clone $this;
+ $result->username = $username;
+ $result->password = $password;
+
+ return $result;
+ }
+
+ public function withWill(Message $will = null)
+ {
+ $result = clone $this;
+ $result->will = $will;
+
+ return $result;
+ }
+
+ public function withKeepAlive($timeout)
+ {
+ $result = clone $this;
+ $result->keepAlive = $timeout;
+
+ return $result;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/DefaultIdentifierGenerator.php b/vendor/binsoul/net-mqtt/src/DefaultIdentifierGenerator.php
new file mode 100755
index 0000000..4c21211
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/DefaultIdentifierGenerator.php
@@ -0,0 +1,38 @@
+currentIdentifier;
+ if ($this->currentIdentifier > 0xFFFF) {
+ $this->currentIdentifier = 1;
+ }
+
+ return $this->currentIdentifier;
+ }
+
+ public function generateClientID()
+ {
+ if (function_exists('random_bytes')) {
+ $data = random_bytes(9);
+ } elseif (function_exists('openssl_random_pseudo_bytes')) {
+ $data = openssl_random_pseudo_bytes(9);
+ } else {
+ $data = '';
+ for ($i = 1; $i <= 8; ++$i) {
+ $data = chr(mt_rand(0, 255)).$data;
+ }
+ }
+
+ return 'BNMCR'.bin2hex($data);
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/DefaultMessage.php b/vendor/binsoul/net-mqtt/src/DefaultMessage.php
new file mode 100755
index 0000000..bc659e9
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/DefaultMessage.php
@@ -0,0 +1,119 @@
+topic = $topic;
+ $this->payload = $payload;
+ $this->isRetained = $retain;
+ $this->qosLevel = $qosLevel;
+ $this->isDuplicate = $isDuplicate;
+ }
+
+ public function getTopic()
+ {
+ return $this->topic;
+ }
+
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ public function getQosLevel()
+ {
+ return $this->qosLevel;
+ }
+
+ public function isDuplicate()
+ {
+ return $this->isDuplicate;
+ }
+
+ public function isRetained()
+ {
+ return $this->isRetained;
+ }
+
+ public function withTopic($topic)
+ {
+ $result = clone $this;
+ $result->topic = $topic;
+
+ return $result;
+ }
+
+ public function withPayload($payload)
+ {
+ $result = clone $this;
+ $result->payload = $payload;
+
+ return $result;
+ }
+
+ public function withQosLevel($level)
+ {
+ $result = clone $this;
+ $result->qosLevel = $level;
+
+ return $result;
+ }
+
+ public function retain()
+ {
+ $result = clone $this;
+ $result->isRetained = true;
+
+ return $result;
+ }
+
+ public function release()
+ {
+ $result = clone $this;
+ $result->isRetained = false;
+
+ return $result;
+ }
+
+ public function duplicate()
+ {
+ $result = clone $this;
+ $result->isDuplicate = true;
+
+ return $result;
+ }
+
+ public function original()
+ {
+ $result = clone $this;
+ $result->isDuplicate = false;
+
+ return $result;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/DefaultSubscription.php b/vendor/binsoul/net-mqtt/src/DefaultSubscription.php
new file mode 100755
index 0000000..2fa71ab
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/DefaultSubscription.php
@@ -0,0 +1,52 @@
+filter = $filter;
+ $this->qosLevel = $qosLevel;
+ }
+
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+
+ public function getQosLevel()
+ {
+ return $this->qosLevel;
+ }
+
+ public function withFilter($filter)
+ {
+ $result = clone $this;
+ $result->filter = $filter;
+
+ return $result;
+ }
+
+ public function withQosLevel($level)
+ {
+ $result = clone $this;
+ $result->qosLevel = $level;
+
+ return $result;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Exception/EndOfStreamException.php b/vendor/binsoul/net-mqtt/src/Exception/EndOfStreamException.php
new file mode 100755
index 0000000..3ffc71c
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Exception/EndOfStreamException.php
@@ -0,0 +1,10 @@
+isFinished;
+ }
+
+ public function isSuccess()
+ {
+ return $this->isFinished && $this->isSuccess;
+ }
+
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Marks the flow as successful and sets the result.
+ *
+ * @param mixed|null $result
+ */
+ protected function succeed($result = null)
+ {
+ $this->isFinished = true;
+ $this->isSuccess = true;
+ $this->result = $result;
+ }
+
+ /**
+ * Marks the flow as failed and sets the error message.
+ *
+ * @param string $error
+ */
+ protected function fail($error = '')
+ {
+ $this->isFinished = true;
+ $this->isSuccess = false;
+ $this->error = $error;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Flow/IncomingPingFlow.php b/vendor/binsoul/net-mqtt/src/Flow/IncomingPingFlow.php
new file mode 100755
index 0000000..a19a1b0
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Flow/IncomingPingFlow.php
@@ -0,0 +1,23 @@
+succeed();
+
+ return new PingResponsePacket();
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Flow/IncomingPublishFlow.php b/vendor/binsoul/net-mqtt/src/Flow/IncomingPublishFlow.php
new file mode 100755
index 0000000..b992c38
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Flow/IncomingPublishFlow.php
@@ -0,0 +1,80 @@
+message = $message;
+ $this->identifier = $identifier;
+ }
+
+ public function getCode()
+ {
+ return 'message';
+ }
+
+ public function start()
+ {
+ $packet = null;
+ $emit = true;
+ if ($this->message->getQosLevel() === 1) {
+ $packet = new PublishAckPacket();
+ } elseif ($this->message->getQosLevel() === 2) {
+ $packet = new PublishReceivedPacket();
+ $emit = false;
+ }
+
+ if ($packet !== null) {
+ $packet->setIdentifier($this->identifier);
+ }
+
+ if ($emit) {
+ $this->succeed($this->message);
+ }
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ if ($this->message->getQosLevel() !== 2 || $packet->getPacketType() !== Packet::TYPE_PUBREL) {
+ return false;
+ }
+
+ /* @var PublishReleasePacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->succeed($this->message);
+
+ $response = new PublishCompletePacket();
+ $response->setIdentifier($this->identifier);
+
+ return $response;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Flow/OutgoingConnectFlow.php b/vendor/binsoul/net-mqtt/src/Flow/OutgoingConnectFlow.php
new file mode 100755
index 0000000..f439e79
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Flow/OutgoingConnectFlow.php
@@ -0,0 +1,70 @@
+connection = $connection;
+
+ if ($this->connection->getClientID() === '') {
+ $this->connection = $this->connection->withClientID($generator->generateClientID());
+ }
+ }
+
+ public function getCode()
+ {
+ return 'connect';
+ }
+
+ public function start()
+ {
+ $packet = new ConnectRequestPacket();
+ $packet->setProtocolLevel($this->connection->getProtocol());
+ $packet->setKeepAlive($this->connection->getKeepAlive());
+ $packet->setClientID($this->connection->getClientID());
+ $packet->setCleanSession($this->connection->isCleanSession());
+ $packet->setUsername($this->connection->getUsername());
+ $packet->setPassword($this->connection->getPassword());
+ $will = $this->connection->getWill();
+ if ($will !== null && $will->getTopic() !== '' && $will->getPayload() !== '') {
+ $packet->setWill($will->getTopic(), $will->getPayload(), $will->getQosLevel(), $will->isRetained());
+ }
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $packet->getPacketType() === Packet::TYPE_CONNACK;
+ }
+
+ public function next(Packet $packet)
+ {
+ /** @var ConnectResponsePacket $packet */
+ if ($packet->isSuccess()) {
+ $this->succeed($this->connection);
+ } else {
+ $this->fail($packet->getErrorName());
+ }
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Flow/OutgoingDisconnectFlow.php b/vendor/binsoul/net-mqtt/src/Flow/OutgoingDisconnectFlow.php
new file mode 100755
index 0000000..fe04c33
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Flow/OutgoingDisconnectFlow.php
@@ -0,0 +1,37 @@
+connection = $connection;
+ }
+
+ public function getCode()
+ {
+ return 'disconnect';
+ }
+
+ public function start()
+ {
+ $this->succeed($this->connection);
+
+ return new DisconnectRequestPacket();
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Flow/OutgoingPingFlow.php b/vendor/binsoul/net-mqtt/src/Flow/OutgoingPingFlow.php
new file mode 100755
index 0000000..8cf79ab
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Flow/OutgoingPingFlow.php
@@ -0,0 +1,32 @@
+getPacketType() === Packet::TYPE_PINGRESP;
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->succeed();
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Flow/OutgoingPublishFlow.php b/vendor/binsoul/net-mqtt/src/Flow/OutgoingPublishFlow.php
new file mode 100755
index 0000000..63fe264
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Flow/OutgoingPublishFlow.php
@@ -0,0 +1,102 @@
+message = $message;
+ if ($this->message->getQosLevel() > 0) {
+ $this->identifier = $generator->generatePacketID();
+ }
+ }
+
+ public function getCode()
+ {
+ return 'publish';
+ }
+
+ public function start()
+ {
+ $packet = new PublishRequestPacket();
+ $packet->setTopic($this->message->getTopic());
+ $packet->setPayload($this->message->getPayload());
+ $packet->setRetained($this->message->isRetained());
+ $packet->setDuplicate($this->message->isDuplicate());
+ $packet->setQosLevel($this->message->getQosLevel());
+
+ if ($this->message->getQosLevel() === 0) {
+ $this->succeed($this->message);
+ } else {
+ $packet->setIdentifier($this->identifier);
+ }
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ if ($this->message->getQosLevel() === 0) {
+ return false;
+ }
+
+ $packetType = $packet->getPacketType();
+
+ if ($packetType === Packet::TYPE_PUBACK && $this->message->getQosLevel() === 1) {
+ /* @var PublishAckPacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ } elseif ($this->message->getQosLevel() === 2) {
+ if ($packetType === Packet::TYPE_PUBREC) {
+ /* @var PublishReceivedPacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ } elseif ($this->receivedPubRec && $packetType === Packet::TYPE_PUBCOMP) {
+ /* @var PublishCompletePacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ }
+ }
+
+ return false;
+ }
+
+ public function next(Packet $packet)
+ {
+ $packetType = $packet->getPacketType();
+
+ if ($packetType === Packet::TYPE_PUBACK || $packetType === Packet::TYPE_PUBCOMP) {
+ $this->succeed($this->message);
+ } elseif ($packetType === Packet::TYPE_PUBREC) {
+ $this->receivedPubRec = true;
+
+ $response = new PublishReleasePacket();
+ $response->setIdentifier($this->identifier);
+
+ return $response;
+ }
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Flow/OutgoingSubscribeFlow.php b/vendor/binsoul/net-mqtt/src/Flow/OutgoingSubscribeFlow.php
new file mode 100755
index 0000000..4a25517
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Flow/OutgoingSubscribeFlow.php
@@ -0,0 +1,82 @@
+subscriptions = array_values($subscriptions);
+ $this->identifier = $generator->generatePacketID();
+ }
+
+ public function getCode()
+ {
+ return 'subscribe';
+ }
+
+ public function start()
+ {
+ $packet = new SubscribeRequestPacket();
+ $packet->setTopic($this->subscriptions[0]->getFilter());
+ $packet->setQosLevel($this->subscriptions[0]->getQosLevel());
+ $packet->setIdentifier($this->identifier);
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ if ($packet->getPacketType() !== Packet::TYPE_SUBACK) {
+ return false;
+ }
+
+ /* @var SubscribeResponsePacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ }
+
+ public function next(Packet $packet)
+ {
+ /* @var SubscribeResponsePacket $packet */
+ $returnCodes = $packet->getReturnCodes();
+ if (count($returnCodes) !== count($this->subscriptions)) {
+ throw new \LogicException(
+ sprintf(
+ 'SUBACK: Expected %d return codes but got %d.',
+ count($this->subscriptions),
+ count($returnCodes)
+ )
+ );
+ }
+
+ foreach ($returnCodes as $index => $code) {
+ if ($packet->isError($code)) {
+ $this->fail(sprintf('Failed to subscribe to "%s".', $this->subscriptions[$index]->getFilter()));
+
+ return;
+ }
+ }
+
+ $this->succeed($this->subscriptions[0]);
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Flow/OutgoingUnsubscribeFlow.php b/vendor/binsoul/net-mqtt/src/Flow/OutgoingUnsubscribeFlow.php
new file mode 100755
index 0000000..4949ff3
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Flow/OutgoingUnsubscribeFlow.php
@@ -0,0 +1,61 @@
+subscriptions = array_values($subscriptions);
+ $this->identifier = $generator->generatePacketID();
+ }
+
+ public function getCode()
+ {
+ return 'unsubscribe';
+ }
+
+ public function start()
+ {
+ $packet = new UnsubscribeRequestPacket();
+ $packet->setTopic($this->subscriptions[0]->getFilter());
+ $packet->setIdentifier($this->identifier);
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ if ($packet->getPacketType() !== Packet::TYPE_UNSUBACK) {
+ return false;
+ }
+
+ /* @var UnsubscribeResponsePacket $packet */
+ return $packet->getIdentifier() === $this->identifier;
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->succeed($this->subscriptions[0]);
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/IdentifierGenerator.php b/vendor/binsoul/net-mqtt/src/IdentifierGenerator.php
new file mode 100755
index 0000000..94c7c78
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/IdentifierGenerator.php
@@ -0,0 +1,23 @@
+write($output);
+
+ return $output->getData();
+ }
+
+ public function read(PacketStream $stream)
+ {
+ $byte = $stream->readByte();
+
+ if ($byte >> 4 !== static::$packetType) {
+ throw new MalformedPacketException(
+ sprintf(
+ 'Expected packet type %02x but got %02x.',
+ $byte >> 4,
+ static::$packetType
+ )
+ );
+ }
+
+ $this->packetFlags = $byte & 0x0F;
+ $this->readRemainingLength($stream);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $stream->writeByte(((static::$packetType & 0x0F) << 4) + ($this->packetFlags & 0x0F));
+ $this->writeRemainingLength($stream);
+ }
+
+ /**
+ * Reads the remaining length from the given stream.
+ *
+ * @param PacketStream $stream
+ *
+ * @throws MalformedPacketException
+ */
+ private function readRemainingLength(PacketStream $stream)
+ {
+ $this->remainingPacketLength = 0;
+ $multiplier = 1;
+
+ do {
+ $encodedByte = $stream->readByte();
+
+ $this->remainingPacketLength += ($encodedByte & 127) * $multiplier;
+ $multiplier *= 128;
+
+ if ($multiplier > 128 * 128 * 128 * 128) {
+ throw new MalformedPacketException('Malformed remaining length.');
+ }
+ } while (($encodedByte & 128) !== 0);
+ }
+
+ /**
+ * Writes the remaining length to the given stream.
+ *
+ * @param PacketStream $stream
+ */
+ private function writeRemainingLength(PacketStream $stream)
+ {
+ $x = $this->remainingPacketLength;
+ do {
+ $encodedByte = $x % 128;
+ $x = (int) ($x / 128);
+ if ($x > 0) {
+ $encodedByte |= 128;
+ }
+
+ $stream->writeByte($encodedByte);
+ } while ($x > 0);
+ }
+
+ public function getPacketType()
+ {
+ return static::$packetType;
+ }
+
+ /**
+ * Returns the packet flags.
+ *
+ * @return int
+ */
+ public function getPacketFlags()
+ {
+ return $this->packetFlags;
+ }
+
+ /**
+ * Returns the remaining length.
+ *
+ * @return int
+ */
+ public function getRemainingPacketLength()
+ {
+ return $this->remainingPacketLength;
+ }
+
+ /**
+ * Asserts that the packet flags have a specific value.
+ *
+ * @param int $value
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertPacketFlags($value, $fromPacket = true)
+ {
+ if ($this->packetFlags !== $value) {
+ $this->throwException(
+ sprintf(
+ 'Expected flags %02x but got %02x.',
+ $value,
+ $this->packetFlags
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Asserts that the remaining length is greater than zero and has a specific value.
+ *
+ * @param int|null $value value to test or null if any value greater than zero is valid
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertRemainingPacketLength($value = null, $fromPacket = true)
+ {
+ if ($value === null && $this->remainingPacketLength === 0) {
+ $this->throwException('Expected payload but remaining packet length is zero.', $fromPacket);
+ }
+
+ if ($value !== null && $this->remainingPacketLength !== $value) {
+ $this->throwException(
+ sprintf(
+ 'Expected remaining packet length of %d bytes but got %d.',
+ $value,
+ $this->remainingPacketLength
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Asserts that the given string is a well-formed MQTT string.
+ *
+ * @param string $value
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertValidStringLength($value, $fromPacket = true)
+ {
+ if (strlen($value) > 0xFFFF) {
+ $this->throwException(
+ sprintf(
+ 'The string "%s" is longer than 65535 byte.',
+ substr($value, 0, 50)
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Asserts that the given string is a well-formed MQTT string.
+ *
+ * @param string $value
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertValidString($value, $fromPacket = true)
+ {
+ $this->assertValidStringLength($value, $fromPacket);
+
+ if (!mb_check_encoding($value, 'UTF-8')) {
+ $this->throwException(
+ sprintf(
+ 'The string "%s" is not well-formed UTF-8.',
+ substr($value, 0, 50)
+ ),
+ $fromPacket
+ );
+ }
+
+ if (preg_match('/[\xD8-\xDF][\x00-\xFF]|\x00\x00/x', $value)) {
+ $this->throwException(
+ sprintf(
+ 'The string "%s" contains invalid characters.',
+ substr($value, 0, 50)
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Asserts that the given quality of service level is valid.
+ *
+ * @param int $level
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function assertValidQosLevel($level, $fromPacket = true)
+ {
+ if ($level < 0 || $level > 2) {
+ $this->throwException(
+ sprintf(
+ 'Expected a quality of service level between 0 and 2 but got %d.',
+ $level
+ ),
+ $fromPacket
+ );
+ }
+ }
+
+ /**
+ * Throws a MalformedPacketException for packet validation and an InvalidArgumentException otherwise.
+ *
+ * @param string $message
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ protected function throwException($message, $fromPacket)
+ {
+ if ($fromPacket) {
+ throw new MalformedPacketException($message);
+ }
+
+ throw new \InvalidArgumentException($message);
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/ConnectRequestPacket.php b/vendor/binsoul/net-mqtt/src/Packet/ConnectRequestPacket.php
new file mode 100755
index 0000000..7abce1f
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/ConnectRequestPacket.php
@@ -0,0 +1,405 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $this->protocolName = $stream->readString();
+ $this->protocolLevel = $stream->readByte();
+ $this->flags = $stream->readByte();
+ $this->keepAlive = $stream->readWord();
+ $this->clientID = $stream->readString();
+
+ if ($this->hasWill()) {
+ $this->willTopic = $stream->readString();
+ $this->willMessage = $stream->readString();
+ }
+
+ if ($this->hasUsername()) {
+ $this->username = $stream->readString();
+ }
+
+ if ($this->hasPassword()) {
+ $this->password = $stream->readString();
+ }
+
+ $this->assertValidWill();
+ $this->assertValidString($this->clientID);
+ $this->assertValidString($this->willTopic);
+ $this->assertValidString($this->username);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ if ($this->clientID === '') {
+ $this->clientID = 'BinSoul'.mt_rand(100000, 999999);
+ }
+
+ $data = new PacketStream();
+
+ $data->writeString($this->protocolName);
+ $data->writeByte($this->protocolLevel);
+ $data->writeByte($this->flags);
+ $data->writeWord($this->keepAlive);
+ $data->writeString($this->clientID);
+
+ if ($this->hasWill()) {
+ $data->writeString($this->willTopic);
+ $data->writeString($this->willMessage);
+ }
+
+ if ($this->hasUsername()) {
+ $data->writeString($this->username);
+ }
+
+ if ($this->hasPassword()) {
+ $data->writeString($this->password);
+ }
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the protocol level.
+ *
+ * @return int
+ */
+ public function getProtocolLevel()
+ {
+ return $this->protocolLevel;
+ }
+
+ /**
+ * Sets the protocol level.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setProtocolLevel($value)
+ {
+ if ($value < 3 || $value > 4) {
+ throw new \InvalidArgumentException(sprintf('Unknown protocol level %d.', $value));
+ }
+
+ $this->protocolLevel = $value;
+ if ($this->protocolLevel === 3) {
+ $this->protocolName = 'MQIsdp';
+ } elseif ($this->protocolLevel === 4) {
+ $this->protocolName = 'MQTT';
+ }
+ }
+
+ /**
+ * Returns the client id.
+ *
+ * @return string
+ */
+ public function getClientID()
+ {
+ return $this->clientID;
+ }
+
+ /**
+ * Sets the client id.
+ *
+ * @param string $value
+ */
+ public function setClientID($value)
+ {
+ $this->clientID = $value;
+ }
+
+ /**
+ * Returns the keep alive time in seconds.
+ *
+ * @return int
+ */
+ public function getKeepAlive()
+ {
+ return $this->keepAlive;
+ }
+
+ /**
+ * Sets the keep alive time in seconds.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setKeepAlive($value)
+ {
+ if ($value > 65535) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected a keep alive time lower than 65535 but got %d.',
+ $value
+ )
+ );
+ }
+
+ $this->keepAlive = $value;
+ }
+
+ /**
+ * Indicates if the clean session flag is set.
+ *
+ * @return bool
+ */
+ public function isCleanSession()
+ {
+ return ($this->flags & 2) === 2;
+ }
+
+ /**
+ * Changes the clean session flag.
+ *
+ * @param bool $value
+ */
+ public function setCleanSession($value)
+ {
+ if ($value) {
+ $this->flags |= 2;
+ } else {
+ $this->flags &= ~2;
+ }
+ }
+
+ /**
+ * Indicates if a will is set.
+ *
+ * @return bool
+ */
+ public function hasWill()
+ {
+ return ($this->flags & 4) === 4;
+ }
+
+ /**
+ * Returns the desired quality of service level of the will.
+ *
+ * @return int
+ */
+ public function getWillQosLevel()
+ {
+ return ($this->flags & 24) >> 3;
+ }
+
+ /**
+ * Indicates if the will should be retained.
+ *
+ * @return bool
+ */
+ public function isWillRetained()
+ {
+ return ($this->flags & 32) === 32;
+ }
+
+ /**
+ * Returns the will topic.
+ *
+ * @return string
+ */
+ public function getWillTopic()
+ {
+ return $this->willTopic;
+ }
+
+ /**
+ * Returns the will message.
+ *
+ * @return string
+ */
+ public function getWillMessage()
+ {
+ return $this->willMessage;
+ }
+
+ /**
+ * Sets the will.
+ *
+ * @param string $topic
+ * @param string $message
+ * @param int $qosLevel
+ * @param bool $isRetained
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setWill($topic, $message, $qosLevel = 0, $isRetained = false)
+ {
+ $this->assertValidString($topic, false);
+ if ($topic === '') {
+ throw new \InvalidArgumentException('The topic must not be empty.');
+ }
+
+ $this->assertValidStringLength($message, false);
+ if ($message === '') {
+ throw new \InvalidArgumentException('The message must not be empty.');
+ }
+
+ $this->assertValidQosLevel($qosLevel, false);
+
+ $this->willTopic = $topic;
+ $this->willMessage = $message;
+
+ $this->flags |= 4;
+ $this->flags |= ($qosLevel << 3);
+
+ if ($isRetained) {
+ $this->flags |= 32;
+ } else {
+ $this->flags &= ~32;
+ }
+ }
+
+ /**
+ * Removes the will.
+ */
+ public function removeWill()
+ {
+ $this->flags &= ~60;
+ $this->willTopic = '';
+ $this->willMessage = '';
+ }
+
+ /**
+ * Indicates if a username is set.
+ *
+ * @return bool
+ */
+ public function hasUsername()
+ {
+ return $this->flags & 64;
+ }
+
+ /**
+ * Returns the username.
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ /**
+ * Sets the username.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setUsername($value)
+ {
+ $this->assertValidString($value, false);
+
+ $this->username = $value;
+ if ($this->username !== '') {
+ $this->flags |= 64;
+ } else {
+ $this->flags &= ~64;
+ }
+ }
+
+ /**
+ * Indicates if a password is set.
+ *
+ * @return bool
+ */
+ public function hasPassword()
+ {
+ return $this->flags & 128;
+ }
+
+ /**
+ * Returns the password.
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Sets the password.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPassword($value)
+ {
+ $this->assertValidStringLength($value, false);
+
+ $this->password = $value;
+ if ($this->password !== '') {
+ $this->flags |= 128;
+ } else {
+ $this->flags &= ~128;
+ }
+ }
+
+ /**
+ * Asserts that all will flags and quality of service are correct.
+ *
+ * @throws MalformedPacketException
+ */
+ private function assertValidWill()
+ {
+ if ($this->hasWill()) {
+ $this->assertValidQosLevel($this->getWillQosLevel(), true);
+ } else {
+ if ($this->getWillQosLevel() > 0) {
+ $this->throwException(
+ sprintf(
+ 'Expected a will quality of service level of zero but got %d.',
+ $this->getWillQosLevel()
+ ),
+ true
+ );
+ }
+
+ if ($this->isWillRetained()) {
+ $this->throwException('There is not will but the will retain flag is set.', true);
+ }
+ }
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/ConnectResponsePacket.php b/vendor/binsoul/net-mqtt/src/Packet/ConnectResponsePacket.php
new file mode 100755
index 0000000..f896d22
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/ConnectResponsePacket.php
@@ -0,0 +1,111 @@
+ [
+ 'Connection accepted',
+ '',
+ ],
+ 1 => [
+ 'Unacceptable protocol version',
+ 'The Server does not support the level of the MQTT protocol requested by the client.',
+ ],
+ 2 => [
+ 'Identifier rejected',
+ 'The client identifier is correct UTF-8 but not allowed by the server.',
+ ],
+ 3 => [
+ 'Server unavailable',
+ 'The network connection has been made but the MQTT service is unavailable',
+ ],
+ 4 => [
+ 'Bad user name or password',
+ 'The data in the user name or password is malformed.',
+ ],
+ 5 => [
+ 'Not authorized',
+ 'The client is not authorized to connect.',
+ ],
+ ];
+
+ /** @var int */
+ private $flags = 0;
+ /** @var int */
+ private $returnCode;
+
+ protected static $packetType = Packet::TYPE_CONNACK;
+ protected $remainingPacketLength = 2;
+
+ public function read(PacketStream $stream)
+ {
+ parent::read($stream);
+ $this->assertPacketFlags(0);
+ $this->assertRemainingPacketLength(2);
+
+ $this->flags = $stream->readByte();
+ $this->returnCode = $stream->readByte();
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $this->remainingPacketLength = 2;
+ parent::write($stream);
+
+ $stream->writeByte($this->flags);
+ $stream->writeByte($this->returnCode);
+ }
+
+ /**
+ * Returns the return code.
+ *
+ * @return int
+ */
+ public function getReturnCode()
+ {
+ return $this->returnCode;
+ }
+
+ /**
+ * Indicates if the connection was successful.
+ *
+ * @return bool
+ */
+ public function isSuccess()
+ {
+ return $this->returnCode === 0;
+ }
+
+ /**
+ * Indicates if the connection failed.
+ *
+ * @return bool
+ */
+ public function isError()
+ {
+ return $this->returnCode > 0;
+ }
+
+ /**
+ * Returns a string representation of the returned error code.
+ *
+ * @return int
+ */
+ public function getErrorName()
+ {
+ if (isset(self::$returnCodes[$this->returnCode])) {
+ return self::$returnCodes[$this->returnCode][0];
+ }
+
+ return 'Error '.$this->returnCode;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/DisconnectRequestPacket.php b/vendor/binsoul/net-mqtt/src/Packet/DisconnectRequestPacket.php
new file mode 100755
index 0000000..0cda785
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/DisconnectRequestPacket.php
@@ -0,0 +1,22 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength(0);
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/IdentifiablePacket.php b/vendor/binsoul/net-mqtt/src/Packet/IdentifiablePacket.php
new file mode 100755
index 0000000..a44ae1c
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/IdentifiablePacket.php
@@ -0,0 +1,62 @@
+identifier === null) {
+ ++self::$nextIdentifier;
+ self::$nextIdentifier &= 0xFFFF;
+
+ $this->identifier = self::$nextIdentifier;
+ }
+
+ return $this->identifier;
+ }
+
+ /**
+ * Returns the identifier.
+ *
+ * @return int|null
+ */
+ public function getIdentifier()
+ {
+ return $this->identifier;
+ }
+
+ /**
+ * Sets the identifier.
+ *
+ * @param int|null $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setIdentifier($value)
+ {
+ if ($value !== null && ($value < 0 || $value > 0xFFFF)) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected an identifier between 0x0000 and 0xFFFF but got %x',
+ $value
+ )
+ );
+ }
+
+ $this->identifier = $value;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/IdentifierOnlyPacket.php b/vendor/binsoul/net-mqtt/src/Packet/IdentifierOnlyPacket.php
new file mode 100755
index 0000000..01dffa5
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/IdentifierOnlyPacket.php
@@ -0,0 +1,42 @@
+assertPacketFlags($this->getExpectedPacketFlags());
+ $this->assertRemainingPacketLength(2);
+
+ $this->identifier = $stream->readWord();
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $this->remainingPacketLength = 2;
+ parent::write($stream);
+
+ $stream->writeWord($this->generateIdentifier());
+ }
+
+ /**
+ * Returns the expected packet flags.
+ *
+ * @return int
+ */
+ protected function getExpectedPacketFlags()
+ {
+ return 0;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/PingRequestPacket.php b/vendor/binsoul/net-mqtt/src/Packet/PingRequestPacket.php
new file mode 100755
index 0000000..cb0fc58
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/PingRequestPacket.php
@@ -0,0 +1,22 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength(0);
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/PingResponsePacket.php b/vendor/binsoul/net-mqtt/src/Packet/PingResponsePacket.php
new file mode 100755
index 0000000..79eaeb3
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/PingResponsePacket.php
@@ -0,0 +1,22 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength(0);
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/PublishAckPacket.php b/vendor/binsoul/net-mqtt/src/Packet/PublishAckPacket.php
new file mode 100755
index 0000000..fc4937e
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/PublishAckPacket.php
@@ -0,0 +1,13 @@
+assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+ $this->topic = $stream->readString();
+ $this->identifier = null;
+ if ($this->getQosLevel() > 0) {
+ $this->identifier = $stream->readWord();
+ }
+
+ $payloadLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
+ $this->payload = $stream->read($payloadLength);
+
+ $this->assertValidQosLevel($this->getQosLevel());
+ $this->assertValidString($this->topic);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeString($this->topic);
+ if ($this->getQosLevel() > 0) {
+ $data->writeWord($this->generateIdentifier());
+ }
+
+ $data->write($this->payload);
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the topic.
+ *
+ * @return string
+ */
+ public function getTopic()
+ {
+ return $this->topic;
+ }
+
+ /**
+ * Sets the topic.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setTopic($value)
+ {
+ $this->assertValidString($value, false);
+ if ($value === '') {
+ throw new \InvalidArgumentException('The topic must not be empty.');
+ }
+
+ $this->topic = $value;
+ }
+
+ /**
+ * Returns the payload.
+ *
+ * @return string
+ */
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ /**
+ * Sets the payload.
+ *
+ * @param string $value
+ */
+ public function setPayload($value)
+ {
+ $this->payload = $value;
+ }
+
+ /**
+ * Indicates if the packet is a duplicate.
+ *
+ * @return bool
+ */
+ public function isDuplicate()
+ {
+ return ($this->packetFlags & 8) === 8;
+ }
+
+ /**
+ * Marks the packet as duplicate.
+ *
+ * @param bool $value
+ */
+ public function setDuplicate($value)
+ {
+ if ($value) {
+ $this->packetFlags |= 8;
+ } else {
+ $this->packetFlags &= ~8;
+ }
+ }
+
+ /**
+ * Indicates if the packet is retained.
+ *
+ * @return bool
+ */
+ public function isRetained()
+ {
+ return ($this->packetFlags & 1) === 1;
+ }
+
+ /**
+ * Marks the packet as retained.
+ *
+ * @param bool $value
+ */
+ public function setRetained($value)
+ {
+ if ($value) {
+ $this->packetFlags |= 1;
+ } else {
+ $this->packetFlags &= ~1;
+ }
+ }
+
+ /**
+ * Returns the quality of service level.
+ *
+ * @return int
+ */
+ public function getQosLevel()
+ {
+ return ($this->packetFlags & 6) >> 1;
+ }
+
+ /**
+ * Sets the quality of service level.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setQosLevel($value)
+ {
+ $this->assertValidQosLevel($value, false);
+
+ $this->packetFlags |= ($value & 3) << 1;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/StrictConnectRequestPacket.php b/vendor/binsoul/net-mqtt/src/Packet/StrictConnectRequestPacket.php
new file mode 100755
index 0000000..71e8c78
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/StrictConnectRequestPacket.php
@@ -0,0 +1,66 @@
+assertValidClientID($this->clientID, true);
+ }
+
+ /**
+ * Sets the client id.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setClientID($value)
+ {
+ $this->assertValidClientID($value, false);
+
+ $this->clientID = $value;
+ }
+
+ /**
+ * Asserts that a client id is shorter than 24 bytes and only contains characters 0-9, a-z or A-Z.
+ *
+ * @param string $value
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ private function assertValidClientID($value, $fromPacket)
+ {
+
+ if (strlen($value) > 23) {
+ $this->throwException(
+ sprintf(
+ 'Expected client id shorter than 24 bytes but got "%s".',
+ $value
+ ),
+ $fromPacket
+ );
+ }
+
+ if ($value !== '' && !ctype_alnum($value)) {
+ $this->throwException(
+ sprintf(
+ 'Expected a client id containing characters 0-9, a-z or A-Z but got "%s".',
+ $value
+ ),
+ $fromPacket
+ );
+ }
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/SubscribeRequestPacket.php b/vendor/binsoul/net-mqtt/src/Packet/SubscribeRequestPacket.php
new file mode 100755
index 0000000..55aa831
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/SubscribeRequestPacket.php
@@ -0,0 +1,101 @@
+assertPacketFlags(2);
+ $this->assertRemainingPacketLength();
+
+ $this->identifier = $stream->readWord();
+ $this->topic = $stream->readString();
+ $this->qosLevel = $stream->readByte();
+
+ $this->assertValidQosLevel($this->qosLevel);
+ $this->assertValidString($this->topic);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeWord($this->generateIdentifier());
+ $data->writeString($this->topic);
+ $data->writeByte($this->qosLevel);
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the topic.
+ *
+ * @return string
+ */
+ public function getTopic()
+ {
+ return $this->topic;
+ }
+
+ /**
+ * Sets the topic.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setTopic($value)
+ {
+ $this->assertValidString($value, false);
+ if ($value === '') {
+ throw new \InvalidArgumentException('The topic must not be empty.');
+ }
+
+ $this->topic = $value;
+ }
+
+ /**
+ * Returns the quality of service level.
+ *
+ * @return int
+ */
+ public function getQosLevel()
+ {
+ return $this->qosLevel;
+ }
+
+ /**
+ * Sets the quality of service level.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setQosLevel($value)
+ {
+ $this->assertValidQosLevel($value, false);
+
+ $this->qosLevel = $value;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/SubscribeResponsePacket.php b/vendor/binsoul/net-mqtt/src/Packet/SubscribeResponsePacket.php
new file mode 100755
index 0000000..35c78e4
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/SubscribeResponsePacket.php
@@ -0,0 +1,132 @@
+ ['Maximum QoS 0'],
+ 1 => ['Maximum QoS 1'],
+ 2 => ['Maximum QoS 2'],
+ 128 => ['Failure'],
+ ];
+
+ /** @var int[] */
+ private $returnCodes;
+
+ protected static $packetType = Packet::TYPE_SUBACK;
+
+ public function read(PacketStream $stream)
+ {
+ parent::read($stream);
+ $this->assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $this->identifier = $stream->readWord();
+
+ $returnCodeLength = $this->remainingPacketLength - 2;
+ for ($n = 0; $n < $returnCodeLength; ++$n) {
+ $returnCode = $stream->readByte();
+ $this->assertValidReturnCode($returnCode);
+
+ $this->returnCodes[] = $returnCode;
+ }
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeWord($this->generateIdentifier());
+ foreach ($this->returnCodes as $returnCode) {
+ $data->writeByte($returnCode);
+ }
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Indicates if the given return code is an error.
+ *
+ * @param int $returnCode
+ *
+ * @return bool
+ */
+ public function isError($returnCode)
+ {
+ return $returnCode === 128;
+ }
+
+ /**
+ * Indicates if the given return code is an error.
+ *
+ * @param int $returnCode
+ *
+ * @return bool
+ */
+ public function getReturnCodeName($returnCode)
+ {
+ if (isset(self::$qosLevels[$returnCode])) {
+ return self::$qosLevels[$returnCode][0];
+ }
+
+ return 'Unknown '.$returnCode;
+ }
+
+ /**
+ * Returns the return codes.
+ *
+ * @return int[]
+ */
+ public function getReturnCodes()
+ {
+ return $this->returnCodes;
+ }
+
+ /**
+ * Sets the return codes.
+ *
+ * @param int[] $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setReturnCodes(array $value)
+ {
+ foreach ($value as $returnCode) {
+ $this->assertValidReturnCode($returnCode, false);
+ }
+
+ $this->returnCodes = $value;
+ }
+
+ /**
+ * Asserts that a return code is valid.
+ *
+ * @param int $returnCode
+ * @param bool $fromPacket
+ *
+ * @throws MalformedPacketException
+ * @throws \InvalidArgumentException
+ */
+ private function assertValidReturnCode($returnCode, $fromPacket = true)
+ {
+ if (!in_array($returnCode, [0, 1, 2, 128])) {
+ $this->throwException(
+ sprintf('Malformed return code %02x.', $returnCode),
+ $fromPacket
+ );
+ }
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeRequestPacket.php b/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeRequestPacket.php
new file mode 100755
index 0000000..4bce38c
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeRequestPacket.php
@@ -0,0 +1,68 @@
+assertPacketFlags(2);
+ $this->assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+
+ do {
+ $this->identifier = $stream->readWord();
+ $this->topic = $stream->readString();
+ } while (($stream->getPosition() - $originalPosition) <= $this->remainingPacketLength);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeWord($this->generateIdentifier());
+ $data->writeString($this->topic);
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the topic.
+ *
+ * @return string
+ */
+ public function getTopic()
+ {
+ return $this->topic;
+ }
+
+ /**
+ * Sets the topic.
+ *
+ * @param string $value
+ */
+ public function setTopic($value)
+ {
+ $this->topic = $value;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeResponsePacket.php b/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeResponsePacket.php
new file mode 100755
index 0000000..385b91d
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Packet/UnsubscribeResponsePacket.php
@@ -0,0 +1,13 @@
+ ConnectRequestPacket::class,
+ Packet::TYPE_CONNACK => ConnectResponsePacket::class,
+ Packet::TYPE_PUBLISH => PublishRequestPacket::class,
+ Packet::TYPE_PUBACK => PublishAckPacket::class,
+ Packet::TYPE_PUBREC => PublishReceivedPacket::class,
+ Packet::TYPE_PUBREL => PublishReleasePacket::class,
+ Packet::TYPE_PUBCOMP => PublishCompletePacket::class,
+ Packet::TYPE_SUBSCRIBE => SubscribeRequestPacket::class,
+ Packet::TYPE_SUBACK => SubscribeResponsePacket::class,
+ Packet::TYPE_UNSUBSCRIBE => UnsubscribeRequestPacket::class,
+ Packet::TYPE_UNSUBACK => UnsubscribeResponsePacket::class,
+ Packet::TYPE_PINGREQ => PingRequestPacket::class,
+ Packet::TYPE_PINGRESP => PingResponsePacket::class,
+ Packet::TYPE_DISCONNECT => DisconnectRequestPacket::class,
+ ];
+
+ /**
+ * Builds a packet object for the given type.
+ *
+ * @param int $type
+ *
+ * @throws UnknownPacketTypeException
+ *
+ * @return Packet
+ */
+ public function build($type)
+ {
+ if (!isset(self::$mapping[$type])) {
+ throw new UnknownPacketTypeException(sprintf('Unknown packet type %d.', $type));
+ }
+
+ $class = self::$mapping[$type];
+
+ return new $class();
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/PacketStream.php b/vendor/binsoul/net-mqtt/src/PacketStream.php
new file mode 100755
index 0000000..eb241d6
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/PacketStream.php
@@ -0,0 +1,216 @@
+data = $data;
+ $this->position = 0;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Returns the desired number of bytes.
+ *
+ * @param int $count
+ *
+ * @throws EndOfStreamException
+ *
+ * @return string
+ */
+ public function read($count)
+ {
+ $contentLength = strlen($this->data);
+ if ($this->position > $contentLength || $count > $contentLength - $this->position) {
+ throw new EndOfStreamException(
+ sprintf(
+ 'End of stream reached when trying to read %d bytes. content length=%d, position=%d',
+ $count,
+ $contentLength,
+ $this->position
+ )
+ );
+ }
+
+ $chunk = substr($this->data, $this->position, $count);
+ if ($chunk === false) {
+ $chunk = '';
+ }
+
+ $readBytes = strlen($chunk);
+ $this->position += $readBytes;
+
+ return $chunk;
+ }
+
+ /**
+ * Returns a single byte.
+ *
+ * @return int
+ */
+ public function readByte()
+ {
+ return ord($this->read(1));
+ }
+
+ /**
+ * Returns a single word.
+ *
+ * @return int
+ */
+ public function readWord()
+ {
+ return ($this->readByte() << 8) + $this->readByte();
+ }
+
+ /**
+ * Returns a length prefixed string.
+ *
+ * @return string
+ */
+ public function readString()
+ {
+ $length = $this->readWord();
+
+ return $this->read($length);
+ }
+
+ /**
+ * Appends the given value.
+ *
+ * @param string $value
+ */
+ public function write($value)
+ {
+ $this->data .= $value;
+ }
+
+ /**
+ * Appends a single byte.
+ *
+ * @param int $value
+ */
+ public function writeByte($value)
+ {
+ $this->write(chr($value));
+ }
+
+ /**
+ * Appends a single word.
+ *
+ * @param int $value
+ */
+ public function writeWord($value)
+ {
+ $this->write(chr(($value & 0xFFFF) >> 8));
+ $this->write(chr($value & 0xFF));
+ }
+
+ /**
+ * Appends a length prefixed string.
+ *
+ * @param string $string
+ */
+ public function writeString($string)
+ {
+ $this->writeWord(strlen($string));
+ $this->write($string);
+ }
+
+ /**
+ * Returns the length of the stream.
+ *
+ * @return int
+ */
+ public function length()
+ {
+ return strlen($this->data);
+ }
+
+ /**
+ * Returns the number of bytes until the end of the stream.
+ *
+ * @return int
+ */
+ public function getRemainingBytes()
+ {
+ return $this->length() - $this->position;
+ }
+
+ /**
+ * Returns the whole content of the stream.
+ *
+ * @return string
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Changes the internal position of the stream relative to the current position.
+ *
+ * @param int $offset
+ */
+ public function seek($offset)
+ {
+ $this->position += $offset;
+ }
+
+ /**
+ * Returns the internal position of the stream.
+ *
+ * @return int
+ */
+ public function getPosition()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Sets the internal position of the stream.
+ *
+ * @param int $value
+ */
+ public function setPosition($value)
+ {
+ $this->position = $value;
+ }
+
+ /**
+ * Removes all bytes from the beginning to the current position.
+ */
+ public function cut()
+ {
+ $this->data = substr($this->data, $this->position);
+ if ($this->data === false) {
+ $this->data = '';
+ }
+
+ $this->position = 0;
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/StreamParser.php b/vendor/binsoul/net-mqtt/src/StreamParser.php
new file mode 100755
index 0000000..398f3e6
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/StreamParser.php
@@ -0,0 +1,90 @@
+buffer = new PacketStream();
+ $this->factory = new PacketFactory();
+ }
+
+ /**
+ * Registers an error callback.
+ *
+ * @param callable $callback
+ */
+ public function onError($callback)
+ {
+ $this->errorCallback = $callback;
+ }
+
+ /**
+ * Appends the given data to the internal buffer and parses it.
+ *
+ * @param string $data
+ *
+ * @return Packet[]
+ */
+ public function push($data)
+ {
+ $this->buffer->write($data);
+
+ $result = [];
+ while ($this->buffer->getRemainingBytes() > 0) {
+ $type = $this->buffer->readByte() >> 4;
+ try {
+ $packet = $this->factory->build($type);
+ } catch (UnknownPacketTypeException $e) {
+ $this->handleError($e);
+ continue;
+ }
+
+ $this->buffer->seek(-1);
+ $position = $this->buffer->getPosition();
+ try {
+ $packet->read($this->buffer);
+ $result[] = $packet;
+ $this->buffer->cut();
+ } catch (EndOfStreamException $e) {
+ $this->buffer->setPosition($position);
+ break;
+ } catch (MalformedPacketException $e) {
+ $this->handleError($e);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the registered error callback.
+ *
+ * @param \Throwable $exception
+ */
+ private function handleError($exception)
+ {
+ if ($this->errorCallback !== null) {
+ $callback = $this->errorCallback;
+ $callback($exception);
+ }
+ }
+}
diff --git a/vendor/binsoul/net-mqtt/src/Subscription.php b/vendor/binsoul/net-mqtt/src/Subscription.php
new file mode 100755
index 0000000..b870533
--- /dev/null
+++ b/vendor/binsoul/net-mqtt/src/Subscription.php
@@ -0,0 +1,41 @@
+
+ */
+class TopicMatcher
+{
+ /**
+ * Check if the given topic matches the filter.
+ *
+ * @param string $filter e.g. A/B/+, A/B/#
+ * @param string $topic e.g. A/B/C, A/B/foo/bar/baz
+ *
+ * @return bool true if topic matches the pattern
+ */
+ public function matches($filter, $topic)
+ {
+ // Created by Steffen (https://github.com/kernelguy)
+ $tokens = explode('/', $filter);
+ $parts = [];
+ for ($i = 0, $count = count($tokens); $i < $count; ++$i) {
+ $token = $tokens[$i];
+ switch ($token) {
+ case '+':
+ $parts[] = '[^/#\+]*';
+
+ break;
+ case '#':
+ if ($i === 0) {
+ $parts[] = '[^\+\$]*';
+ } else {
+ $parts[] = '[^\+]*';
+ }
+
+ break;
+ default:
+ $parts[] = str_replace('+', '\+', $token);
+
+ break;
+ }
+ }
+
+ $regex = implode('/', $parts);
+ $regex = str_replace('$', '\$', $regex);
+ $regex = ';^'.$regex.'$;';
+
+ return preg_match($regex, $topic) === 1;
+ }
+}
diff --git a/vendor/clue/http-proxy-react/.gitignore b/vendor/clue/http-proxy-react/.gitignore
new file mode 100755
index 0000000..4fbb073
--- /dev/null
+++ b/vendor/clue/http-proxy-react/.gitignore
@@ -0,0 +1,2 @@
+/vendor/
+/composer.lock
diff --git a/vendor/clue/http-proxy-react/.travis.yml b/vendor/clue/http-proxy-react/.travis.yml
new file mode 100755
index 0000000..04f51ad
--- /dev/null
+++ b/vendor/clue/http-proxy-react/.travis.yml
@@ -0,0 +1,27 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/vendor/clue/http-proxy-react/CHANGELOG.md b/vendor/clue/http-proxy-react/CHANGELOG.md
new file mode 100755
index 0000000..3d25812
--- /dev/null
+++ b/vendor/clue/http-proxy-react/CHANGELOG.md
@@ -0,0 +1,103 @@
+# Changelog
+
+## 1.3.0 (2018-02-13)
+
+* Feature: Support communication over Unix domain sockets (UDS)
+ (#20 by @clue)
+
+ ```php
+ // new: now supports communication over Unix domain sockets (UDS)
+ $proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
+ ```
+
+* Reduce memory consumption by avoiding circular reference from stream reader
+ (#18 by @valga)
+
+* Improve documentation
+ (#19 by @clue)
+
+## 1.2.0 (2017-08-30)
+
+* Feature: Use socket error codes for connection rejections
+ (#17 by @clue)
+
+ ```php
+ $promise = $proxy->connect('imap.example.com:143');
+ $promise->then(null, function (Exeption $e) {
+ if ($e->getCode() === SOCKET_EACCES) {
+ echo 'Failed to authenticate with proxy!';
+ }
+ throw $e;
+ });
+ ```
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ optionally exclude tests that rely on working internet connection
+ (#15 and #16 by @clue)
+
+## 1.1.0 (2017-06-11)
+
+* Feature: Support proxy authentication if proxy URL contains username/password
+ (#14 by @clue)
+
+ ```php
+ // new: username/password will now be passed to HTTP proxy server
+ $proxy = new ProxyConnector('user:pass@127.0.0.1:8080', $connector);
+ ```
+
+## 1.0.0 (2017-06-10)
+
+* First stable release, now following SemVer
+
+> Contains no other changes, so it's actually fully compatible with the v0.3.2 release.
+
+## 0.3.2 (2017-06-10)
+
+* Fix: Fix rejecting invalid URIs and unexpected URI schemes
+ (#13 by @clue)
+
+* Fix HHVM build for now again and ignore future HHVM build errors
+ (#12 by @clue)
+
+* Documentation for Connector concepts (TCP/TLS, timeouts, DNS resolution)
+ (#11 by @clue)
+
+## 0.3.1 (2017-05-10)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#10 by @clue)
+
+## 0.3.0 (2017-04-10)
+
+* Feature / BC break: Replace deprecated SocketClient with new Socket component
+ (#9 by @clue)
+
+ This implies that the `ProxyConnector` from this package now implements the
+ `React\Socket\ConnectorInterface` instead of the legacy
+ `React\SocketClient\ConnectorInterface`.
+
+## 0.2.0 (2017-04-10)
+
+* Feature / BC break: Update SocketClient to v0.7 or v0.6 and
+ use `connect($uri)` instead of `create($host, $port)`
+ (#8 by @clue)
+
+ ```php
+ // old
+ $connector->create($host, $port)->then(function (Stream $conn) {
+ $conn->write("…");
+ });
+
+ // new
+ $connector->connect($uri)->then(function (ConnectionInterface $conn) {
+ $conn->write("…");
+ });
+ ```
+
+* Improve test suite by adding PHPUnit to require-dev
+ (#7 by @clue)
+
+
+## 0.1.0 (2016-11-01)
+
+* First tagged release
diff --git a/vendor/clue/http-proxy-react/LICENSE b/vendor/clue/http-proxy-react/LICENSE
new file mode 100755
index 0000000..7baae8e
--- /dev/null
+++ b/vendor/clue/http-proxy-react/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/clue/http-proxy-react/README.md b/vendor/clue/http-proxy-react/README.md
new file mode 100755
index 0000000..442b7f9
--- /dev/null
+++ b/vendor/clue/http-proxy-react/README.md
@@ -0,0 +1,422 @@
+# clue/http-proxy-react [](https://travis-ci.org/clue/php-http-proxy-react)
+
+Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP
+CONNECT proxy server, built on top of [ReactPHP](https://reactphp.org).
+
+HTTP CONNECT proxy servers (also commonly known as "HTTPS proxy" or "SSL proxy")
+are commonly used to tunnel HTTPS traffic through an intermediary ("proxy"), to
+conceal the origin address (anonymity) or to circumvent address blocking
+(geoblocking). While many (public) HTTP CONNECT proxy servers often limit this
+to HTTPS port `443` only, this can technically be used to tunnel any
+TCP/IP-based protocol (HTTP, SMTP, IMAP etc.).
+This library provides a simple API to create these tunneled connection for you.
+Because it implements ReactPHP's standard
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
+it can simply be used in place of a normal connector.
+This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
+existing higher-level protocol implementation.
+
+* **Async execution of connections** -
+ Send any number of HTTP CONNECT requests in parallel and process their
+ responses as soon as results come in.
+ The Promise-based design provides a *sane* interface to working with out of
+ bound responses and possible connection errors.
+* **Standard interfaces** -
+ Allows easy integration with existing higher-level components by implementing
+ ReactPHP's standard
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
+* **Lightweight, SOLID design** -
+ Provides a thin abstraction that is [*just good enough*](http://en.wikipedia.org/wiki/Principle_of_good_enough)
+ and does not get in your way.
+ Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
+* **Good test coverage** -
+ Comes with an automated tests suite and is regularly tested against actual proxy servers in the wild
+
+**Table of contents**
+
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [ProxyConnector](#proxyconnector)
+ * [Plain TCP connections](#plain-tcp-connections)
+ * [Secure TLS connections](#secure-tls-connections)
+ * [Connection timeout](#connection-timeout)
+ * [DNS resolution](#dns-resolution)
+ * [Authentication](#authentication)
+ * [Advanced secure proxy connections](#advanced-secure-proxy-connections)
+ * [Advanced Unix domain sockets](#advanced-unix-domain-sockets)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+### Quickstart example
+
+The following example code demonstrates how this library can be used to send a
+secure HTTPS request to google.com through a local HTTP proxy server:
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$proxy = new ProxyConnector('127.0.0.1:8080', new Connector($loop));
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
+ $stream->on('data', function ($chunk) {
+ echo $chunk;
+ });
+}, 'printf');
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### ProxyConnector
+
+The `ProxyConnector` is responsible for creating plain TCP/IP connections to
+any destination by using an intermediary HTTP CONNECT proxy.
+
+```
+[you] -> [proxy] -> [destination]
+```
+
+Its constructor simply accepts an HTTP proxy URL and a connector used to connect
+to the proxy server address:
+
+```php
+$connector = new Connector($loop);
+$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
+```
+
+The proxy URL may or may not contain a scheme and port definition. The default
+port will be `80` for HTTP (or `443` for HTTPS), but many common HTTP proxy
+servers use custom ports (often the alternative HTTP port `8080`).
+In its most simple form, the given connector will be a
+[`\React\Socket\Connector`](https://github.com/reactphp/socket#connector) if you
+want to connect to a given IP address as above.
+
+This is the main class in this package.
+Because it implements ReactPHP's standard
+[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
+it can simply be used in place of a normal connector.
+Accordingly, it provides only a single public method, the
+[`connect()`](https://github.com/reactphp/socket#connect) method.
+The `connect(string $uri): PromiseInterface`
+method can be used to establish a streaming connection.
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface)
+on success or rejects with an `Exception` on error.
+
+This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
+higher-level component:
+
+```diff
+- $client = new SomeClient($connector);
++ $proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
++ $client = new SomeClient($proxy);
+```
+
+#### Plain TCP connections
+
+HTTP CONNECT proxies are most frequently used to issue HTTPS requests to your destination.
+However, this is actually performed on a higher protocol layer and this
+connector is actually inherently a general-purpose plain TCP/IP connector.
+As documented above, you can simply invoke its `connect()` method to establish
+a streaming plain TCP/IP connection and use any higher level protocol like so:
+
+```php
+$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
+
+$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ });
+});
+```
+
+You can either use the `ProxyConnector` directly or you may want to wrap this connector
+in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
+
+```php
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ });
+});
+```
+
+Note that HTTP CONNECT proxies often restrict which ports one may connect to.
+Many (public) proxy servers do in fact limit this to HTTPS (443) only.
+
+#### Secure TLS connections
+
+This class can also be used if you want to establish a secure TLS connection
+(formerly known as SSL) between you and your destination, such as when using
+secure HTTPS to your destination site. You can simply wrap this connector in
+ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector) or the
+low-level [`SecureConnector`](https://github.com/reactphp/socket#secureconnector):
+
+```php
+$proxy = new ProxyConnector('http://127.0.0.1:8080', $connector);
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+
+$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ });
+});
+```
+
+> Note how secure TLS connections are in fact entirely handled outside of
+ this HTTP CONNECT client implementation.
+
+#### Connection timeout
+
+By default, the `ProxyConnector` does not implement any timeouts for establishing remote
+connections.
+Your underlying operating system may impose limits on pending and/or idle TCP/IP
+connections, anywhere in a range of a few minutes to several hours.
+
+Many use cases require more control over the timeout and likely values much
+smaller, usually in the range of a few seconds only.
+
+You can use ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector)
+or the low-level
+[`TimeoutConnector`](https://github.com/reactphp/socket#timeoutconnector)
+to decorate any given `ConnectorInterface` instance.
+It provides the same `connect()` method, but will automatically reject the
+underlying connection attempt if it takes too long:
+
+```php
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => false,
+ 'timeout' => 3.0
+));
+
+$connector->connect('tcp://google.com:80')->then(function ($stream) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+> Note how connection timeout is in fact entirely handled outside of this
+ HTTP CONNECT client implementation.
+
+#### DNS resolution
+
+By default, the `ProxyConnector` does not perform any DNS resolution at all and simply
+forwards any hostname you're trying to connect to the remote proxy server.
+The remote proxy server is thus responsible for looking up any hostnames via DNS
+(this default mode is thus called *remote DNS resolution*).
+
+As an alternative, you can also send the destination IP to the remote proxy
+server.
+In this mode you either have to stick to using IPs only (which is ofen unfeasable)
+or perform any DNS lookups locally and only transmit the resolved destination IPs
+(this mode is thus called *local DNS resolution*).
+
+The default *remote DNS resolution* is useful if your local `ProxyConnector` either can
+not resolve target hostnames because it has no direct access to the internet or
+if it should not resolve target hostnames because its outgoing DNS traffic might
+be intercepted.
+
+As noted above, the `ProxyConnector` defaults to using remote DNS resolution.
+However, wrapping the `ProxyConnector` in ReactPHP's
+[`Connector`](https://github.com/reactphp/socket#connector) actually
+performs local DNS resolution unless explicitly defined otherwise.
+Given that remote DNS resolution is assumed to be the preferred mode, all
+other examples explicitly disable DNS resoltion like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => false
+));
+```
+
+If you want to explicitly use *local DNS resolution*, you can use the following code:
+
+```php
+// set up Connector which uses Google's public DNS (8.8.8.8)
+$connector = Connector($loop, array(
+ 'tcp' => $proxy,
+ 'dns' => '8.8.8.8'
+));
+```
+
+> Note how local DNS resolution is in fact entirely handled outside of this
+ HTTP CONNECT client implementation.
+
+#### Authentication
+
+If your HTTP proxy server requires authentication, you may pass the username and
+password as part of the HTTP proxy URL like this:
+
+```php
+$proxy = new ProxyConnector('http://user:pass@127.0.0.1:8080', $connector);
+```
+
+Note that both the username and password must be percent-encoded if they contain
+special characters:
+
+```php
+$user = 'he:llo';
+$pass = 'p@ss';
+
+$proxy = new ProxyConnector(
+ rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:8080',
+ $connector
+);
+```
+
+> The authentication details will be used for basic authentication and will be
+ transferred in the `Proxy-Authorization` HTTP request header for each
+ connection attempt.
+ If the authentication details are missing or not accepted by the remote HTTP
+ proxy server, it is expected to reject each connection attempt with a
+ `407` (Proxy Authentication Required) response status code and an exception
+ error code of `SOCKET_EACCES` (13).
+
+#### Advanced secure proxy connections
+
+Note that communication between the client and the proxy is usually via an
+unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
+setup, because you can still establish a TLS connection between you and the
+destination host as above.
+
+If you want to connect to a (rather rare) HTTPS proxy, you may want use the
+`https://` scheme (HTTPS default port 443) and use ReactPHP's
+[`Connector`](https://github.com/reactphp/socket#connector) or the low-level
+[`SecureConnector`](https://github.com/reactphp/socket#secureconnector)
+instance to create a secure connection to the proxy:
+
+```php
+$connector = new Connector($loop);
+$proxy = new ProxyConnector('https://127.0.0.1:443', $connector);
+
+$proxy->connect('tcp://smtp.googlemail.com:587');
+```
+
+#### Advanced Unix domain sockets
+
+HTTP CONNECT proxy servers support forwarding TCP/IP based connections and
+higher level protocols.
+In some advanced cases, it may be useful to let your HTTP CONNECT proxy server
+listen on a Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#authentication).
+
+You can simply use the `http+unix://` URI scheme like this:
+
+```php
+$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
+
+$proxy->connect('tcp://google.com:80')->then(function (ConnectionInterface $stream) {
+ // connected…
+});
+```
+
+Similarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $connector);
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
+ has limited support for this.
+ In particular, enabling [secure TLS](#secure-tls-connections) may not be
+ supported.
+
+> Note that the HTTP CONNECT protocol does not support the notion of UDS paths.
+ The above works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does not support connecting to UDS destination paths.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](http://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/http-proxy-react:^1.3
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite contains tests that rely on a working internet connection,
+alternatively you can also run it like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT
+
+## More
+
+* If you want to learn more about how the
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ and its usual implementations look like, refer to the documentation of the underlying
+ [react/socket](https://github.com/reactphp/socket) component.
+* If you want to learn more about processing streams of data, refer to the
+ documentation of the underlying
+ [react/stream](https://github.com/reactphp/stream) component.
+* As an alternative to an HTTP CONNECT proxy, you may also want to look into
+ using a SOCKS (SOCKS4/SOCKS5) proxy instead.
+ You may want to use [clue/socks-react](https://github.com/clue/php-socks-react)
+ which also provides an implementation of the same
+ [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
+ so that supporting either proxy protocol should be fairly trivial.
+* If you're dealing with public proxies, you'll likely have to work with mixed
+ quality and unreliable proxies. You may want to look into using
+ [clue/connection-manager-extra](https://github.com/clue/php-connection-manager-extra)
+ which allows retrying unreliable ones, implying connection timeouts,
+ concurrently working with multiple connectors and more.
+* If you're looking for an end-user HTTP CONNECT proxy server daemon, you may
+ want to use [LeProxy](https://leproxy.org/).
diff --git a/vendor/clue/http-proxy-react/composer.json b/vendor/clue/http-proxy-react/composer.json
new file mode 100755
index 0000000..23801c9
--- /dev/null
+++ b/vendor/clue/http-proxy-react/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "clue/http-proxy-react",
+ "description": "Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
+ "keywords": ["HTTP", "CONNECT", "proxy", "ReactPHP", "async"],
+ "homepage": "https://github.com/clue/php-http-proxy-react",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "Clue\\React\\HttpProxy\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Tests\\Clue\\React\\HttpProxy\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/promise": " ^2.1 || ^1.2.1",
+ "react/socket": "^1.0 || ^0.8.4",
+ "ringcentral/psr7": "^1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.0 || ^4.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
+ "clue/block-react": "^1.1"
+ }
+}
diff --git a/vendor/clue/http-proxy-react/examples/01-proxy-https.php b/vendor/clue/http-proxy-react/examples/01-proxy-https.php
new file mode 100755
index 0000000..c07ea0d
--- /dev/null
+++ b/vendor/clue/http-proxy-react/examples/01-proxy-https.php
@@ -0,0 +1,30 @@
+ $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
+ $stream->on('data', function ($chunk) {
+ echo $chunk;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php b/vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php
new file mode 100755
index 0000000..c65e69a
--- /dev/null
+++ b/vendor/clue/http-proxy-react/examples/02-optional-proxy-https.php
@@ -0,0 +1,37 @@
+ $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+ ));
+}
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
+ $stream->on('data', function ($chunk) {
+ echo $chunk;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/clue/http-proxy-react/examples/11-proxy-smtp.php b/vendor/clue/http-proxy-react/examples/11-proxy-smtp.php
new file mode 100755
index 0000000..3225491
--- /dev/null
+++ b/vendor/clue/http-proxy-react/examples/11-proxy-smtp.php
@@ -0,0 +1,32 @@
+ $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ $stream->write("QUIT\r\n");
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/clue/http-proxy-react/examples/12-proxy-smtps.php b/vendor/clue/http-proxy-react/examples/12-proxy-smtps.php
new file mode 100755
index 0000000..462dba3
--- /dev/null
+++ b/vendor/clue/http-proxy-react/examples/12-proxy-smtps.php
@@ -0,0 +1,35 @@
+ $proxy,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) {
+ $stream->write("EHLO local\r\n");
+ $stream->on('data', function ($chunk) use ($stream) {
+ echo $chunk;
+ $stream->write("QUIT\r\n");
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/clue/http-proxy-react/phpunit.xml.dist b/vendor/clue/http-proxy-react/phpunit.xml.dist
new file mode 100755
index 0000000..a6e2430
--- /dev/null
+++ b/vendor/clue/http-proxy-react/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
diff --git a/vendor/clue/http-proxy-react/src/ProxyConnector.php b/vendor/clue/http-proxy-react/src/ProxyConnector.php
new file mode 100755
index 0000000..69eb5ee
--- /dev/null
+++ b/vendor/clue/http-proxy-react/src/ProxyConnector.php
@@ -0,0 +1,213 @@
+ [proxy] -> [destination]
+ *
+ * This is most frequently used to issue HTTPS requests to your destination.
+ * However, this is actually performed on a higher protocol layer and this
+ * connector is actually inherently a general-purpose plain TCP/IP connector.
+ *
+ * Note that HTTP CONNECT proxies often restrict which ports one may connect to.
+ * Many (public) proxy servers do in fact limit this to HTTPS (443) only.
+ *
+ * If you want to establish a TLS connection (such as HTTPS) between you and
+ * your destination, you may want to wrap this connector in a SecureConnector
+ * instance.
+ *
+ * Note that communication between the client and the proxy is usually via an
+ * unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
+ * setup, because you can still establish a TLS connection between you and the
+ * destination host as above.
+ *
+ * If you want to connect to a (rather rare) HTTPS proxy, you may want use its
+ * HTTPS port (443) and use a SecureConnector instance to create a secure
+ * connection to the proxy.
+ *
+ * @link https://tools.ietf.org/html/rfc7231#section-4.3.6
+ */
+class ProxyConnector implements ConnectorInterface
+{
+ private $connector;
+ private $proxyUri;
+ private $proxyAuth = '';
+
+ /**
+ * Instantiate a new ProxyConnector which uses the given $proxyUrl
+ *
+ * @param string $proxyUrl The proxy URL may or may not contain a scheme and
+ * port definition. The default port will be `80` for HTTP (or `443` for
+ * HTTPS), but many common HTTP proxy servers use custom ports.
+ * @param ConnectorInterface $connector In its most simple form, the given
+ * connector will be a \React\Socket\Connector if you want to connect to
+ * a given IP address.
+ * @throws InvalidArgumentException if the proxy URL is invalid
+ */
+ public function __construct($proxyUrl, ConnectorInterface $connector)
+ {
+ // support `http+unix://` scheme for Unix domain socket (UDS) paths
+ if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) {
+ // rewrite URI to parse authentication from dummy host
+ $proxyUrl = 'http://' . $match[1] . 'localhost';
+
+ // connector uses Unix transport scheme and explicit path given
+ $connector = new FixedUriConnector(
+ 'unix://' . $match[2],
+ $connector
+ );
+ }
+
+ if (strpos($proxyUrl, '://') === false) {
+ $proxyUrl = 'http://' . $proxyUrl;
+ }
+
+ $parts = parse_url($proxyUrl);
+ if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) {
+ throw new InvalidArgumentException('Invalid proxy URL "' . $proxyUrl . '"');
+ }
+
+ // apply default port and TCP/TLS transport for given scheme
+ if (!isset($parts['port'])) {
+ $parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
+ }
+ $parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp';
+
+ $this->connector = $connector;
+ $this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'];
+
+ // prepare Proxy-Authorization header if URI contains username/password
+ if (isset($parts['user']) || isset($parts['pass'])) {
+ $this->proxyAuth = 'Proxy-Authorization: Basic ' . base64_encode(
+ rawurldecode($parts['user'] . ':' . (isset($parts['pass']) ? $parts['pass'] : ''))
+ ) . "\r\n";
+ }
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $port = $parts['port'];
+
+ // construct URI to HTTP CONNECT proxy server to connect to
+ $proxyUri = $this->proxyUri;
+
+ // append path from URI if given
+ if (isset($parts['path'])) {
+ $proxyUri .= $parts['path'];
+ }
+
+ // parse query args
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // append hostname from URI to query string unless explicitly given
+ if (!isset($args['hostname'])) {
+ $args['hostname'] = $parts['host'];
+ }
+
+ // append query string
+ $proxyUri .= '?' . http_build_query($args, '', '&');;
+
+ // append fragment from URI if given
+ if (isset($parts['fragment'])) {
+ $proxyUri .= '#' . $parts['fragment'];
+ }
+
+ $auth = $this->proxyAuth;
+
+ return $this->connector->connect($proxyUri)->then(function (ConnectionInterface $stream) use ($host, $port, $auth) {
+ $deferred = new Deferred(function ($_, $reject) use ($stream) {
+ $reject(new RuntimeException('Connection canceled while waiting for response from proxy (ECONNABORTED)', defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103));
+ $stream->close();
+ });
+
+ // keep buffering data until headers are complete
+ $buffer = '';
+ $fn = function ($chunk) use (&$buffer, $deferred, $stream) {
+ $buffer .= $chunk;
+
+ $pos = strpos($buffer, "\r\n\r\n");
+ if ($pos !== false) {
+ // try to parse headers as response message
+ try {
+ $response = Psr7\parse_response(substr($buffer, 0, $pos));
+ } catch (Exception $e) {
+ $deferred->reject(new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $e));
+ $stream->close();
+ return;
+ }
+
+ if ($response->getStatusCode() === 407) {
+ // map status code 407 (Proxy Authentication Required) to EACCES
+ $deferred->reject(new RuntimeException('Proxy denied connection due to invalid authentication ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13));
+ return $stream->close();
+ } elseif ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
+ // map non-2xx status code to ECONNREFUSED
+ $deferred->reject(new RuntimeException('Proxy refused connection with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111));
+ return $stream->close();
+ }
+
+ // all okay, resolve with stream instance
+ $deferred->resolve($stream);
+
+ // emit remaining incoming as data event
+ $buffer = (string)substr($buffer, $pos + 4);
+ if ($buffer !== '') {
+ $stream->emit('data', array($buffer));
+ $buffer = '';
+ }
+ return;
+ }
+
+ // stop buffering when 8 KiB have been read
+ if (isset($buffer[8192])) {
+ $deferred->reject(new RuntimeException('Proxy must not send more than 8 KiB of headers (EMSGSIZE)', defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90));
+ $stream->close();
+ }
+ };
+ $stream->on('data', $fn);
+
+ $stream->on('error', function (Exception $e) use ($deferred) {
+ $deferred->reject(new RuntimeException('Stream error while waiting for response from proxy (EIO)', defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e));
+ });
+
+ $stream->on('close', function () use ($deferred) {
+ $deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
+ });
+
+ $stream->write("CONNECT " . $host . ":" . $port . " HTTP/1.1\r\nHost: " . $host . ":" . $port . "\r\n" . $auth . "\r\n");
+
+ return $deferred->promise()->then(function (ConnectionInterface $stream) use ($fn) {
+ // Stop buffering when connection has been established.
+ $stream->removeListener('data', $fn);
+ return new Promise\FulfilledPromise($stream);
+ });
+ }, function (Exception $e) use ($proxyUri) {
+ throw new RuntimeException('Unable to connect to proxy (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $e);
+ });
+ }
+}
diff --git a/vendor/clue/http-proxy-react/tests/AbstractTestCase.php b/vendor/clue/http-proxy-react/tests/AbstractTestCase.php
new file mode 100755
index 0000000..632b314
--- /dev/null
+++ b/vendor/clue/http-proxy-react/tests/AbstractTestCase.php
@@ -0,0 +1,80 @@
+createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->equalTo($value));
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWithExceptionCode($code)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->callback(function ($e) use ($code) {
+ return $e->getCode() === $code;
+ }));
+
+ return $mock;
+ }
+
+
+ protected function expectCallableOnceParameter($type)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf($type));
+
+ return $mock;
+ }
+
+ /**
+ * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
+ */
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('Tests\\Clue\\React\\HttpProxy\\CallableStub')->getMock();
+ }
+}
+
+class CallableStub
+{
+ public function __invoke()
+ {
+ }
+}
+
diff --git a/vendor/clue/http-proxy-react/tests/FunctionalTest.php b/vendor/clue/http-proxy-react/tests/FunctionalTest.php
new file mode 100755
index 0000000..23273cf
--- /dev/null
+++ b/vendor/clue/http-proxy-react/tests/FunctionalTest.php
@@ -0,0 +1,75 @@
+loop = Factory::create();
+
+ $this->tcpConnector = new TcpConnector($this->loop);
+
+ $f = new \React\Dns\Resolver\Factory();
+ $resolver = $f->create('8.8.8.8', $this->loop);
+
+ $this->dnsConnector = new DnsConnector($this->tcpConnector, $resolver);
+ }
+
+ public function testNonListeningSocketRejectsConnection()
+ {
+ $proxy = new ProxyConnector('127.0.0.1:9999', $this->dnsConnector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->setExpectedException('RuntimeException', 'Unable to connect to proxy', SOCKET_ECONNREFUSED);
+ Block\await($promise, $this->loop, 3.0);
+ }
+
+ public function testPlainGoogleDoesNotAcceptConnectMethod()
+ {
+ $proxy = new ProxyConnector('google.com', $this->dnsConnector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->setExpectedException('RuntimeException', '405 (Method Not Allowed)', SOCKET_ECONNREFUSED);
+ Block\await($promise, $this->loop, 3.0);
+ }
+
+ public function testSecureGoogleDoesNotAcceptConnectMethod()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('TLS not supported on really old platforms (HHVM < 3.8)');
+ }
+
+ $secure = new SecureConnector($this->dnsConnector, $this->loop);
+ $proxy = new ProxyConnector('https://google.com:443', $secure);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->setExpectedException('RuntimeException', '405 (Method Not Allowed)', SOCKET_ECONNREFUSED);
+ Block\await($promise, $this->loop, 3.0);
+ }
+
+ public function testSecureGoogleDoesNotAcceptPlainStream()
+ {
+ $proxy = new ProxyConnector('google.com:443', $this->dnsConnector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->setExpectedException('RuntimeException', 'Connection to proxy lost', SOCKET_ECONNRESET);
+ Block\await($promise, $this->loop, 3.0);
+ }
+}
diff --git a/vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php b/vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php
new file mode 100755
index 0000000..13d6e42
--- /dev/null
+++ b/vendor/clue/http-proxy-react/tests/ProxyConnectorTest.php
@@ -0,0 +1,333 @@
+connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidProxy()
+ {
+ new ProxyConnector('///', $this->connector);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidProxyScheme()
+ {
+ new ProxyConnector('ftp://example.com', $this->connector);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidHttpsUnixScheme()
+ {
+ new ProxyConnector('https+unix:///tmp/proxy.sock', $this->connector);
+ }
+
+ public function testCreatesConnectionToHttpPort()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=google.com')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testCreatesConnectionToHttpPortAndPassesThroughUriComponents()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80/path?foo=bar&hostname=google.com#segment')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80/path?foo=bar#segment');
+ }
+
+ public function testCreatesConnectionToHttpPortAndObeysExplicitHostname()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('tcp://proxy.example.com:80?hostname=www.google.com')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80?hostname=www.google.com');
+ }
+
+ public function testCreatesConnectionToHttpsPort()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('tls://proxy.example.com:443?hostname=google.com')->willReturn($promise);
+
+ $proxy = new ProxyConnector('https://proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testCreatesConnectionToUnixPath()
+ {
+ $promise = new Promise(function () { });
+ $this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
+
+ $proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testCancelPromiseWillCancelPendingConnection()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->assertInstanceOf('React\Promise\CancellablePromiseInterface', $promise);
+
+ $promise->cancel();
+ }
+
+ public function testWillWriteToOpenConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testWillProxyAuthorizationHeaderIfProxyUriContainsAuthentication()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('user:pass@proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testWillProxyAuthorizationHeaderIfProxyUriContainsOnlyUsernameWithoutPassword()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjo=\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('user@proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testWillProxyAuthorizationHeaderIfProxyUriContainsAuthenticationWithPercentEncoding()
+ {
+ $user = 'h@llÖ';
+ $pass = '%secret?';
+
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic " . base64_encode($user . ':' . $pass) . "\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector(rawurlencode($user) . ':' . rawurlencode($pass) . '@proxy.example.com', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testWillProxyAuthorizationHeaderIfUnixProxyUriContainsAuthentication()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
+
+ $proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $this->connector);
+
+ $proxy->connect('google.com:80');
+ }
+
+ public function testRejectsInvalidUri()
+ {
+ $this->connector->expects($this->never())->method('connect');
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('///');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testRejectsUriWithNonTcpScheme()
+ {
+ $this->connector->expects($this->never())->method('connect');
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('tls://google.com:80');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testRejectsIfConnectorRejects()
+ {
+ $promise = \React\Promise\reject(new \RuntimeException());
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testRejectsAndClosesIfStreamWritesNonHttp()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $stream->expects($this->once())->method('close');
+ $stream->emit('data', array("invalid\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testRejectsAndClosesIfStreamWritesTooMuchData()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $stream->expects($this->once())->method('close');
+ $stream->emit('data', array(str_repeat('*', 100000)));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EMSGSIZE));
+ }
+
+ public function testRejectsAndClosesIfStreamReturnsProyAuthenticationRequired()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $stream->expects($this->once())->method('close');
+ $stream->emit('data', array("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EACCES));
+ }
+
+ public function testRejectsAndClosesIfStreamReturnsNonSuccess()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $stream->expects($this->once())->method('close');
+ $stream->emit('data', array("HTTP/1.1 403 Not allowed\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
+ }
+
+ public function testResolvesIfStreamReturnsSuccess()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $promise->then($this->expectCallableOnce('React\Stream\Stream'));
+ $never = $this->expectCallableNever();
+ $promise->then(function (ConnectionInterface $stream) use ($never) {
+ $stream->on('data', $never);
+ });
+
+ $stream->emit('data', array("HTTP/1.1 200 OK\r\n\r\n"));
+ }
+
+ public function testResolvesIfStreamReturnsSuccessAndEmitsExcessiveData()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $once = $this->expectCallableOnceWith('hello!');
+ $promise->then(function (ConnectionInterface $stream) use ($once) {
+ $stream->on('data', $once);
+ });
+
+ $stream->emit('data', array("HTTP/1.1 200 OK\r\n\r\nhello!"));
+ }
+
+ public function testCancelPromiseWillCloseOpenConnectionAndReject()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $proxy = new ProxyConnector('proxy.example.com', $this->connector);
+
+ $promise = $proxy->connect('google.com:80');
+
+ $this->assertInstanceOf('React\Promise\CancellablePromiseInterface', $promise);
+
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNABORTED));
+ }
+}
diff --git a/vendor/clue/socks-react/.gitignore b/vendor/clue/socks-react/.gitignore
new file mode 100755
index 0000000..de4a392
--- /dev/null
+++ b/vendor/clue/socks-react/.gitignore
@@ -0,0 +1,2 @@
+/vendor
+/composer.lock
diff --git a/vendor/clue/socks-react/.travis.yml b/vendor/clue/socks-react/.travis.yml
new file mode 100755
index 0000000..04f51ad
--- /dev/null
+++ b/vendor/clue/socks-react/.travis.yml
@@ -0,0 +1,27 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/vendor/clue/socks-react/CHANGELOG.md b/vendor/clue/socks-react/CHANGELOG.md
new file mode 100755
index 0000000..c1eddb3
--- /dev/null
+++ b/vendor/clue/socks-react/CHANGELOG.md
@@ -0,0 +1,338 @@
+# Changelog
+
+## 0.8.7 (2017-12-17)
+
+* Feature: Support SOCKS over TLS (`sockss://` URI scheme)
+ (#70 and #71 by @clue)
+
+ ```php
+ // new: now supports SOCKS over TLS
+ $client = new Client('socks5s://localhost', $connector);
+ ```
+
+* Feature: Support communication over Unix domain sockets (UDS)
+ (#69 by @clue)
+
+ ```php
+ // new: now supports SOCKS over Unix domain sockets (UDS)
+ $client = new Client('socks5+unix:///tmp/proxy.sock', $connector);
+ ```
+
+* Improve test suite by adding forward compatibility with PHPUnit 6
+ (#68 by @clue)
+
+## 0.8.6 (2017-09-17)
+
+* Feature: Forward compatibility with Evenement v3.0
+ (#67 by @WyriHaximus)
+
+## 0.8.5 (2017-09-01)
+
+* Feature: Use socket error codes for connection rejections
+ (#63 by @clue)
+
+ ```php
+ $promise = $proxy->connect('imap.example.com:143');
+ $promise->then(null, function (Exeption $e) {
+ if ($e->getCode() === SOCKET_EACCES) {
+ echo 'Failed to authenticate with proxy!';
+ }
+ throw $e;
+ });
+ ```
+
+* Feature: Report matching SOCKS5 error codes for server side connection errors
+ (#62 by @clue)
+
+* Fix: Fix SOCKS5 client receiving destination hostnames and
+ fix IPv6 addresses as hostnames for TLS certificates
+ (#64 and #65 by @clue)
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ optionally exclude tests that rely on working internet connection
+ (#61 and #66 by @clue)
+
+## 0.8.4 (2017-07-27)
+
+* Feature: Server now passes client source address to Connector
+ (#60 by @clue)
+
+## 0.8.3 (2017-07-18)
+
+* Feature: Pass full remote URI as parameter to authentication callback
+ (#58 by @clue)
+
+ ```php
+ // new third parameter passed to authentication callback
+ $server->setAuth(function ($user, $pass, $remote) {
+ $ip = parse_url($remote, PHP_URL_HOST);
+
+ return ($ip === '127.0.0.1');
+ });
+ ```
+
+* Fix: Fix connecting to IPv6 address via SOCKS5 server and validate target
+ URI so hostname can not contain excessive URI components
+ (#59 by @clue)
+
+* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors
+ (#57 by @clue)
+
+## 0.8.2 (2017-05-09)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#56 by @clue)
+
+## 0.8.1 (2017-04-21)
+
+* Update examples to use URIs with default port 1080 and accept proxy URI arguments
+ (#54 by @clue)
+
+* Remove now unneeded dependency on `react/stream`
+ (#55 by @clue)
+
+## 0.8.0 (2017-04-18)
+
+* Feature: Merge `Server` class from clue/socks-server
+ (#52 by @clue)
+
+ ```php
+ $socket = new React\Socket\Server(1080, $loop);
+ $server = new Clue\React\Socks\Server($loop, $socket);
+ ```
+
+ > Upgrading from [clue/socks-server](https://github.com/clue/php-socks-server)?
+ The classes have been moved as-is, so you can simply start using the new
+ class name `Clue\React\Socks\Server` with no other changes required.
+
+## 0.7.0 (2017-04-14)
+
+* Feature / BC break: Replace depreacted SocketClient with Socket v0.7 and
+ use `connect($uri)` instead of `create($host, $port)`
+ (#51 by @clue)
+
+ ```php
+ // old
+ $connector = new React\SocketClient\TcpConnector($loop);
+ $client = new Client(1080, $connector);
+ $client->create('google.com', 80)->then(function (Stream $conn) {
+ $conn->write("…");
+ });
+
+ // new
+ $connector = new React\Socket\TcpConnector($loop);
+ $client = new Client(1080, $connector);
+ $client->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $conn->write("…");
+ });
+ ```
+
+* Improve test suite by adding PHPUnit to require-dev
+ (#50 by @clue)
+
+## 0.6.0 (2016-11-29)
+
+* Feature / BC break: Pass connector into `Client` instead of loop, remove unneeded deps
+ (#49 by @clue)
+
+ ```php
+ // old (connector is create implicitly)
+ $client = new Client('127.0.0.1', $loop);
+
+ // old (connector can optionally be passed)
+ $client = new Client('127.0.0.1', $loop, $connector);
+
+ // new (connector is now mandatory)
+ $connector = new React\SocketClient\TcpConnector($loop);
+ $client = new Client('127.0.0.1', $connector);
+ ```
+
+* Feature / BC break: `Client` now implements `ConnectorInterface`, remove `Connector` adapter
+ (#47 by @clue)
+
+ ```php
+ // old (explicit connector functions as an adapter)
+ $connector = $client->createConnector();
+ $promise = $connector->create('google.com', 80);
+
+ // new (client can be used as connector right away)
+ $promise = $client->create('google.com', 80);
+ ```
+
+* Feature / BC break: Remove `createSecureConnector()`, use `SecureConnector` instead
+ (#47 by @clue)
+
+ ```php
+ // old (tight coupling and hidden dependency)
+ $tls = $client->createSecureConnector();
+ $promise = $tls->create('google.com', 443);
+
+ // new (more explicit, loose coupling)
+ $tls = new React\SocketClient\SecureConnector($client, $loop);
+ $promise = $tls->create('google.com', 443);
+ ```
+
+* Feature / BC break: Remove `setResolveLocal()` and local DNS resolution and default to remote DNS resolution, use `DnsConnector` instead
+ (#44 by @clue)
+
+ ```php
+ // old (implicitly defaults to true, can be disabled)
+ $client->setResolveLocal(false);
+ $tcp = $client->createConnector();
+ $promise = $tcp->create('google.com', 80);
+
+ // new (always disabled, can be re-enabled like this)
+ $factory = new React\Dns\Resolver\Factory();
+ $resolver = $factory->createCached('8.8.8.8', $loop);
+ $tcp = new React\SocketClient\DnsConnector($client, $resolver);
+ $promise = $tcp->create('google.com', 80);
+ ```
+
+* Feature / BC break: Remove `setTimeout()`, use `TimeoutConnector` instead
+ (#45 by @clue)
+
+ ```php
+ // old (timeout only applies to TCP/IP connection)
+ $client = new Client('127.0.0.1', …);
+ $client->setTimeout(3.0);
+ $tcp = $client->createConnector();
+ $promise = $tcp->create('google.com', 80);
+
+ // new (timeout can be added to any layer)
+ $client = new Client('127.0.0.1', …);
+ $tcp = new React\SocketClient\TimeoutConnector($client, 3.0, $loop);
+ $promise = $tcp->create('google.com', 80);
+ ```
+
+* Feature / BC break: Remove `setProtocolVersion()` and `setAuth()` mutators, only support SOCKS URI for protocol version and authentication (immutable API)
+ (#46 by @clue)
+
+ ```php
+ // old (state can be mutated after instantiation)
+ $client = new Client('127.0.0.1', …);
+ $client->setProtocolVersion('5');
+ $client->setAuth('user', 'pass');
+
+ // new (immutable after construction, already supported as of v0.5.2 - now mandatory)
+ $client = new Client('socks5://user:pass@127.0.0.1', …);
+ ```
+
+## 0.5.2 (2016-11-25)
+
+* Feature: Apply protocol version and username/password auth from SOCKS URI
+ (#43 by @clue)
+
+ ```php
+ // explicitly use SOCKS5
+ $client = new Client('socks5://127.0.0.1', $loop);
+
+ // use authentication (automatically SOCKS5)
+ $client = new Client('user:pass@127.0.0.1', $loop);
+ ```
+
+* More explicit client examples, including proxy chaining
+ (#42 by @clue)
+
+## 0.5.1 (2016-11-21)
+
+* Feature: Support Promise cancellation
+ (#39 by @clue)
+
+ ```php
+ $promise = $connector->create($host, $port);
+
+ $promise->cancel();
+ ```
+
+* Feature: Timeout now cancels pending connection attempt
+ (#39, #22 by @clue)
+
+## 0.5.0 (2016-11-07)
+
+* Remove / BC break: Split off Server to clue/socks-server
+ (#35 by @clue)
+
+ > Upgrading? Check [clue/socks-server](https://github.com/clue/php-socks-server) for details.
+
+* Improve documentation and project structure
+
+## 0.4.0 (2016-03-19)
+
+* Feature: Support proper SSL/TLS connections with additional SSL context options
+ (#31, #33 by @clue)
+
+* Documentation for advanced Connector setups (bindto, multihop)
+ (#32 by @clue)
+
+## 0.3.0 (2015-06-20)
+
+* BC break / Feature: Client ctor now accepts a SOCKS server URI
+ ([#24](https://github.com/clue/php-socks-react/pull/24))
+
+ ```php
+// old
+$client = new Client($loop, 'localhost', 9050);
+
+// new
+$client = new Client('localhost:9050', $loop);
+```
+
+* Feature: Automatically assume default SOCKS port (1080) if not given explicitly
+ ([#26](https://github.com/clue/php-socks-react/pull/26))
+
+* Improve documentation and test suite
+
+## 0.2.1 (2014-11-13)
+
+* Support React PHP v0.4 (while preserving BC with React PHP v0.3)
+ ([#16](https://github.com/clue/php-socks-react/pull/16))
+
+* Improve examples and add first class support for HHVM
+ ([#15](https://github.com/clue/php-socks-react/pull/15) and [#17](https://github.com/clue/php-socks-react/pull/17))
+
+## 0.2.0 (2014-09-27)
+
+* BC break / Feature: Simplify constructors by making parameters optional.
+ ([#10](https://github.com/clue/php-socks-react/pull/10))
+
+ The `Factory` has been removed, you can now create instances of the `Client`
+ and `Server` yourself:
+
+ ```php
+ // old
+ $factory = new Factory($loop, $dns);
+ $client = $factory->createClient('localhost', 9050);
+ $server = $factory->createSever($socket);
+
+ // new
+ $client = new Client($loop, 'localhost', 9050);
+ $server = new Server($loop, $socket);
+ ```
+
+* BC break: Remove HTTP support and link to [clue/buzz-react](https://github.com/clue/php-buzz-react) instead.
+ ([#9](https://github.com/clue/php-socks-react/pull/9))
+
+ HTTP operates on a different layer than this low-level SOCKS library.
+ Removing this reduces the footprint of this library.
+
+ > Upgrading? Check the [README](https://github.com/clue/php-socks-react#http-requests) for details.
+
+* Fix: Refactored to support other, faster loops (libev/libevent)
+ ([#12](https://github.com/clue/php-socks-react/pull/12))
+
+* Explicitly list dependencies, clean up examples and extend test suite significantly
+
+## 0.1.0 (2014-05-19)
+
+* First stable release
+* Async SOCKS `Client` and `Server` implementation
+* Project was originally part of [clue/socks](https://github.com/clue/php-socks)
+ and was split off from its latest releave v0.4.0
+ ([#1](https://github.com/clue/reactphp-socks/issues/1))
+
+> Upgrading from clue/socks v0.4.0? Use namespace `Clue\React\Socks` instead of `Socks` and you're ready to go!
+
+## 0.0.0 (2011-04-26)
+
+* Initial concept, originally tracked as part of
+ [clue/socks](https://github.com/clue/php-socks)
diff --git a/vendor/clue/socks-react/LICENSE b/vendor/clue/socks-react/LICENSE
new file mode 100755
index 0000000..8efa9aa
--- /dev/null
+++ b/vendor/clue/socks-react/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2011 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/clue/socks-react/README.md b/vendor/clue/socks-react/README.md
new file mode 100755
index 0000000..a18d797
--- /dev/null
+++ b/vendor/clue/socks-react/README.md
@@ -0,0 +1,1017 @@
+# clue/socks-react [](https://travis-ci.org/clue/php-socks-react)
+
+Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of [ReactPHP](http://reactphp.org).
+
+The SOCKS protocol family can be used to easily tunnel TCP connections independent
+of the actual application level protocol, such as HTTP, SMTP, IMAP, Telnet etc.
+
+**Table of contents**
+
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+ * [ConnectorInterface](#connectorinterface)
+ * [connect()](#connect)
+ * [Client](#client)
+ * [Plain TCP connections](#plain-tcp-connections)
+ * [Secure TLS connections](#secure-tls-connections)
+ * [HTTP requests](#http-requests)
+ * [Protocol version](#protocol-version)
+ * [DNS resolution](#dns-resolution)
+ * [Authentication](#authentication)
+ * [Proxy chaining](#proxy-chaining)
+ * [Connection timeout](#connection-timeout)
+ * [SOCKS over TLS](#socks-over-tls)
+ * [Unix domain sockets](#unix-domain-sockets)
+ * [Server](#server)
+ * [Server connector](#server-connector)
+ * [Protocol version](#server-protocol-version)
+ * [Authentication](#server-authentication)
+ * [Proxy chaining](#server-proxy-chaining)
+ * [SOCKS over TLS](#server-socks-over-tls)
+ * [Unix domain sockets](#server-unix-domain-sockets)
+* [Servers](#servers)
+ * [Using a PHP SOCKS server](#using-a-php-socks-server)
+ * [Using SSH as a SOCKS server](#using-ssh-as-a-socks-server)
+ * [Using the Tor (anonymity network) to tunnel SOCKS connections](#using-the-tor-anonymity-network-to-tunnel-socks-connections)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Quickstart example
+
+Once [installed](#install), you can use the following code to create a connection
+to google.com via a local SOCKS proxy server:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$client = new Client('127.0.0.1:1080', new Connector($loop));
+
+$client->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+});
+
+$loop->run();
+```
+
+If you're not already running any other [SOCKS proxy server](#servers),
+you can use the following code to create a SOCKS
+proxy server listening for connections on `localhost:1080`:
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+// listen on localhost:1080
+$socket = new Socket('127.0.0.1:1080', $loop);
+
+// start a new server listening for incoming connection on the given socket
+$server = new Server($loop, $socket);
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+## Usage
+
+### ConnectorInterface
+
+The `ConnectorInterface` is responsible for providing an interface for
+establishing streaming connections, such as a normal TCP/IP connection.
+
+In order to use this library, you should understand how this integrates with its
+ecosystem.
+This base interface is actually defined in React's
+[Socket component](https://github.com/reactphp/socket) and used
+throughout React's ecosystem.
+
+Most higher-level components (such as HTTP, database or other networking
+service clients) accept an instance implementing this interface to create their
+TCP/IP connection to the underlying networking service.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against this library in order to connect through a
+SOCKS proxy server.
+
+The interface only offers a single method:
+
+#### connect()
+
+The `connect(string $uri): PromiseInterface` method
+can be used to establish a streaming connection.
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface) or
+rejects with an `Exception`:
+
+```php
+$connector->connect('tcp://google.com:80')->then(
+ function (ConnectionInterface $stream) {
+ // connection successfully established
+ },
+ function (Exception $error) {
+ // failed to connect due to $error
+ }
+);
+```
+
+### Client
+
+The `Client` is responsible for communication with your SOCKS server instance.
+Its constructor simply accepts an SOCKS proxy URI and a connector used to
+connect to the SOCKS proxy server address.
+
+In its most simple form, you can simply pass React's
+[`Connector`](https://github.com/reactphp/socket#connector)
+like this:
+
+```php
+$connector = new React\Socket\Connector($loop);
+$client = new Client('127.0.0.1:1080', $connector);
+```
+
+You can omit the port if you're using the default SOCKS port 1080:
+
+```php
+$client = new Client('127.0.0.1', $connector);
+```
+
+If you need custom connector settings (DNS resolution, timeouts etc.), you can explicitly pass a
+custom instance of the [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
+
+```php
+// use local DNS server
+$dnsResolverFactory = new DnsFactory();
+$resolver = $dnsResolverFactory->createCached('127.0.0.1', $loop);
+
+// outgoing connections to SOCKS server via interface 192.168.10.1
+// this is not to be confused with local DNS resolution (see further below)
+$connector = new DnsConnector(
+ new TcpConnector($loop, array('bindto' => '192.168.10.1:0')),
+ $resolver
+);
+
+$client = new Client('my-socks-server.local:1080', $connector);
+```
+
+This is the main class in this package.
+Because it implements the the [`ConnectorInterface`](#connectorinterface), it
+can simply be used in place of a normal connector.
+This makes it fairly simple to add SOCKS proxy support to pretty much any
+higher-level component:
+
+```diff
+- $client = new SomeClient($connector);
++ $proxy = new Client('127.0.0.1:1080', $connector);
++ $client = new SomeClient($proxy);
+```
+
+#### Plain TCP connections
+
+The `Client` implements the [`ConnectorInterface`](#connectorinterface) and
+hence provides a single public method, the [`connect()`](#connect) method.
+Let's open up a streaming TCP/IP connection and write some data:
+
+```php
+$client->connect('tcp://www.google.com:80')->then(function (ConnectonInterface $stream) {
+ echo 'connected to www.google.com:80';
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ // ...
+});
+```
+
+You can either use the `Client` directly or you may want to wrap this connector
+in React's [`Connector`](https://github.com/reactphp/socket#connector):
+
+```php
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false
+));
+
+$connector->connect('tcp://www.google.com:80')->then(function (ConnectonInterface $stream) {
+ echo 'connected to www.google.com:80';
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ // ...
+});
+
+```
+
+See also the [first example](examples).
+
+The `tcp://` scheme can also be omitted.
+Passing any other scheme will reject the promise.
+
+Pending connection attempts can be canceled by canceling its pending promise like so:
+
+```php
+$promise = $connector->connect($uri);
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying TCP/IP
+connection to the SOCKS server and/or the SOCKS protocol negotiation and reject
+the resulting promise.
+
+#### Secure TLS connections
+
+If you want to establish a secure TLS connection (such as HTTPS) between you and
+your destination, you may want to wrap this connector in React's
+[`Connector`](https://github.com/reactphp/socket#connector) or the low-level
+[`SecureConnector`](https://github.com/reactphp/socket#secureconnector):
+
+```php
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false
+));
+
+// now create an SSL encrypted connection (notice the $ssl instead of $tcp)
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ // proceed with just the plain text data
+ // everything is encrypted/decrypted automatically
+ echo 'connected to SSL encrypted www.google.com';
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ // ...
+});
+```
+
+See also the [second example](examples).
+
+If you use the low-level `SecureConnector`, then the `tls://` scheme can also
+be omitted.
+Passing any other scheme will reject the promise.
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+> Also note how secure TLS connections are in fact entirely handled outside of
+ this SOCKS client implementation.
+
+You can optionally pass additional
+[SSL context options](http://php.net/manual/en/context.ssl.php)
+to the constructor like this:
+
+```php
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ),
+ 'dns' => false
+));
+```
+
+#### HTTP requests
+
+HTTP operates on a higher layer than this low-level SOCKS implementation.
+If you want to issue HTTP requests, you can add a dependency for
+[clue/buzz-react](https://github.com/clue/php-buzz-react).
+It can interact with this library by issuing all
+[http requests through a SOCKS server](https://github.com/clue/php-buzz-react#socks-proxy).
+This works for both plain HTTP and SSL encrypted HTTPS requests.
+
+#### Protocol version
+
+This library supports the SOCKS4, SOCKS4a and SOCKS5 protocol versions.
+
+While SOCKS4 already had (a somewhat limited) support for `SOCKS BIND` requests
+and SOCKS5 added generic UDP support (`SOCKS UDPASSOCIATE`), this library
+focuses on the most commonly used core feature of `SOCKS CONNECT`.
+In this mode, a SOCKS server acts as a generic proxy allowing higher level
+application protocols to work through it.
+
+
+
+Note, this is __not__ a full SOCKS5 implementation due to missing GSSAPI
+authentication (but it's unlikely you're going to miss it anyway).
+
+By default, the `Client` communicates via SOCKS4a with the SOCKS server
+– unless you enable [authentication](#authentication), in which case it will
+default to SOCKS5.
+This is done because SOCKS4a incurs less overhead than SOCKS5 (see above) and
+is equivalent with SOCKS4 if you use [local DNS resolution](#dns-resolution).
+
+If want to explicitly set the protocol version, use the supported values URI
+schemes `socks4`, `socks4a` or `socks5` as part of the SOCKS URI:
+
+```php
+$client = new Client('socks5://127.0.0.1', $connector);
+```
+
+As seen above, both SOCKS5 and SOCKS4a support remote and local DNS resolution.
+If you've explicitly set this to SOCKS4, then you may want to check the following
+chapter about local DNS resolution or you may only connect to IPv4 addresses.
+
+#### DNS resolution
+
+By default, the `Client` does not perform any DNS resolution at all and simply
+forwards any hostname you're trying to connect to to the SOCKS server.
+The remote SOCKS server is thus responsible for looking up any hostnames via DNS
+(this default mode is thus called *remote DNS resolution*).
+As seen above, this mode is supported by the SOCKS5 and SOCKS4a protocols, but
+not the SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
+
+On the other hand, all SOCKS protocol versions support sending destination IP
+addresses to the SOCKS server.
+In this mode you either have to stick to using IPs only (which is ofen unfeasable)
+or perform any DNS lookups locally and only transmit the resolved destination IPs
+(this mode is thus called *local DNS resolution*).
+
+The default *remote DNS resolution* is useful if your local `Client` either can
+not resolve target hostnames because it has no direct access to the internet or
+if it should not resolve target hostnames because its outgoing DNS traffic might
+be intercepted (in particular when using the
+[Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections)).
+
+As noted above, the `Client` defaults to using remote DNS resolution.
+However, wrapping the `Client` in React's
+[`Connector`](https://github.com/reactphp/socket#connector) actually
+performs local DNS resolution unless explicitly defined otherwise.
+Given that remote DNS resolution is assumed to be the preferred mode, all
+other examples explicitly disable DNS resoltion like this:
+
+```php
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false
+));
+```
+
+If you want to explicitly use *local DNS resolution* (such as when explicitly
+using SOCKS4), you can use the following code:
+
+```php
+// set up Connector which uses Google's public DNS (8.8.8.8)
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => '8.8.8.8'
+));
+```
+
+See also the [fourth example](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+> Also note how local DNS resolution is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+If you've explicitly set the client to SOCKS4 and stick to the default
+*remote DNS resolution*, then you may only connect to IPv4 addresses because
+the protocol lacks a way to communicate hostnames.
+If you try to connect to a hostname despite, the resulting promise will be
+rejected right away.
+
+#### Authentication
+
+This library supports username/password authentication for SOCKS5 servers as
+defined in [RFC 1929](http://tools.ietf.org/html/rfc1929).
+
+On the client side, simply pass your username and password to use for
+authentication (see below).
+For each further connection the client will merely send a flag to the server
+indicating authentication information is available.
+Only if the server requests authentication during the initial handshake,
+the actual authentication credentials will be transmitted to the server.
+
+Note that the password is transmitted in cleartext to the SOCKS proxy server,
+so this methods should not be used on a network where you have to worry about eavesdropping.
+
+You can simply pass the authentication information as part of the SOCKS URI:
+
+```php
+$client = new Client('username:password@127.0.0.1', $connector);
+```
+
+Note that both the username and password must be percent-encoded if they contain
+special characters:
+
+```php
+$user = 'he:llo';
+$pass = 'p@ss';
+
+$client = new Client(
+ rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1',
+ $connector
+);
+```
+
+> The authentication details will be transmitted in cleartext to the SOCKS proxy
+ server only if it requires username/password authentication.
+ If the authentication details are missing or not accepted by the remote SOCKS
+ proxy server, it is expected to reject each connection attempt with an
+ exception error code of `SOCKET_EACCES` (13).
+
+Authentication is only supported by protocol version 5 (SOCKS5),
+so passing authentication to the `Client` enforces communication with protocol
+version 5 and complains if you have explicitly set anything else:
+
+```php
+// throws InvalidArgumentException
+new Client('socks4://user:pass@127.0.0.1', $connector);
+```
+
+#### Proxy chaining
+
+The `Client` is responsible for creating connections to the SOCKS server which
+then connects to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if you want to conceal your origin address.
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Client` uses any instance of the `ConnectorInterface` to establish
+outgoing connections.
+In order to connect through another SOCKS server, you can simply use another
+SOCKS connector from another SOCKS client like this:
+
+```php
+// https via the proxy chain "MiddlemanSocksServer -> TargetSocksServer -> TargetHost"
+// please note how the client uses TargetSocksServer (not MiddlemanSocksServer!),
+// which in turn then uses MiddlemanSocksServer.
+// this creates a TCP/IP connection to MiddlemanSocksServer, which then connects
+// to TargetSocksServer, which then connects to the TargetHost
+$middle = new Client('127.0.0.1:1080', new Connector($loop));
+$target = new Client('example.com:1080', $middle);
+
+$connector = new React\Socket\Connector($loop, array(
+ 'tcp' => $target,
+ 'dns' => false
+));
+
+$connector->connect('tls://www.google.com:443')->then(function ($stream) {
+ // …
+});
+```
+
+See also the [third example](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and not part of this class.
+ For example, you can find this in the below [`Server`](#server-proxy-chaining)
+ class or somewhat similar when you're using the
+ [Tor network](#using-the-tor-anonymity-network-to-tunnel-socks-connections).
+
+#### Connection timeout
+
+By default, the `Client` does not implement any timeouts for establishing remote
+connections.
+Your underlying operating system may impose limits on pending and/or idle TCP/IP
+connections, anywhere in a range of a few minutes to several hours.
+
+Many use cases require more control over the timeout and likely values much
+smaller, usually in the range of a few seconds only.
+
+You can use React's [`Connector`](https://github.com/reactphp/socket#connector)
+or the low-level
+[`TimeoutConnector`](https://github.com/reactphp/socket#timeoutconnector)
+to decorate any given `ConnectorInterface` instance.
+It provides the same `connect()` method, but will automatically reject the
+underlying connection attempt if it takes too long:
+
+```php
+$connector = new Connector($loop, array(
+ 'tcp' => $client,
+ 'dns' => false,
+ 'timeout' => 3.0
+));
+
+$connector->connect('tcp://google.com:80')->then(function ($stream) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+Pending connection attempts can be canceled by canceling its pending promise
+as usual.
+
+> Also note how connection timeout is in fact entirely handled outside of this
+ SOCKS client implementation.
+
+#### SOCKS over TLS
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* you connect to and
+may also be able to see your [SOCKS authentication](#authentication) details
+in cleartext.
+
+As an alternative, you may establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+you want to connect to or your [SOCKS authentication](#authentication) details.
+
+You can use the `sockss://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$client = new Client('sockss://127.0.0.1:1080', new Connector($loop));
+
+$client = new Client('socks5s://127.0.0.1:1080', new Connector($loop));
+```
+
+See also [example 32](examples).
+
+Simiarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$client = new Client('sockss://user:pass@127.0.0.1:1080', new Connector($loop));
+```
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+ In particular, the SOCKS server has to accept secure TLS connections, see
+ also [Server SOCKS over TLS](#server-socks-over-tls) for more details.
+ Also, PHP does not support "double encryption" over a single connection.
+ This means that enabling [secure TLS connections](#secure-tls-connections)
+ over a communication channel that has been opened with SOCKS over TLS
+ may not be supported.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this may also have only limited support for
+ [proxy chaining](#proxy-chaining) over multiple TLS paths.
+
+#### Unix domain sockets
+
+All [SOCKS protocol versions](#protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS server listen on a
+Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#authentication).
+
+You can use the `socks+unix://` URI scheme or use an explicit
+[SOCKS protocol version](#protocol-version) like this:
+
+```php
+$client = new Client('socks+unix:///tmp/proxy.sock', new Connector($loop));
+
+$client = new Client('socks5+unix:///tmp/proxy.sock', new Connector($loop));
+```
+
+Simiarly, you can also combine this with [authentication](#authentication)
+like this:
+
+```php
+$client = new Client('socks+unix://user:pass@/tmp/proxy.sock', new Connector($loop));
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
+ has limited support for this.
+ In particular, enabling [secure TLS](#secure-tls-connections) may not be
+ supported.
+
+> Note that SOCKS protocol does not support the notion of UDS paths. The above
+ works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does also not support [proxy chaining](#proxy-chaining)
+ over multiple UDS paths.
+
+### Server
+
+The `Server` is responsible for accepting incoming communication from SOCKS clients
+and forwarding the requested connection to the target host.
+It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
+and an underlying TCP/IP socket server like this:
+
+```php
+$loop = \React\EventLoop\Factory::create();
+
+// listen on localhost:$port
+$socket = new Socket($port, $loop);
+
+$server = new Server($loop, $socket);
+```
+
+#### Server connector
+
+The `Server` uses an instance of the [`ConnectorInterface`](#connectorinterface)
+to establish outgoing connections for each incoming connection request.
+
+If you need custom connector settings (DNS resolution, timeouts etc.), you can explicitly pass a
+custom instance of the [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
+
+```php
+// use local DNS server
+$dnsResolverFactory = new DnsFactory();
+$resolver = $dnsResolverFactory->createCached('127.0.0.1', $loop);
+
+// outgoing connections to target host via interface 192.168.10.1
+$connector = new DnsConnector(
+ new TcpConnector($loop, array('bindto' => '192.168.10.1:0')),
+ $resolver
+);
+
+$server = new Server($loop, $socket, $connector);
+```
+
+If you want to forward the outgoing connection through another SOCKS proxy, you
+may also pass a [`Client`](#client) instance as a connector, see also
+[server proxy chaining](#server-proxy-chaining) for more details.
+
+Internally, the `Server` uses the normal [`connect()`](#connect) method, but
+it also passes the original client IP as the `?source={remote}` parameter.
+The `source` parameter contains the full remote URI, including the protocol
+and any authentication details, for example `socks5://user:pass@1.2.3.4:5678`.
+You can use this parameter for logging purposes or to restrict connection
+requests for certain clients by providing a custom implementation of the
+[`ConnectorInterface`](#connectorinterface).
+
+#### Server protocol version
+
+The `Server` supports all protocol versions (SOCKS4, SOCKS4a and SOCKS5) by default.
+
+If want to explicitly set the protocol version, use the supported values `4`, `4a` or `5`:
+
+```PHP
+$server->setProtocolVersion(5);
+```
+
+In order to reset the protocol version to its default (i.e. automatic detection),
+use `null` as protocol version.
+
+```PHP
+$server->setProtocolVersion(null);
+```
+
+#### Server authentication
+
+By default, the `Server` does not require any authentication from the clients.
+You can enable authentication support so that clients need to pass a valid
+username and password before forwarding any connections.
+
+Setting authentication on the `Server` enforces each further connected client
+to use protocol version 5 (SOCKS5).
+If a client tries to use any other protocol version, does not send along
+authentication details or if authentication details can not be verified,
+the connection will be rejected.
+
+Because your authentication mechanism might take some time to actually check
+the provided authentication credentials (like querying a remote database or webservice),
+the server side uses a [Promise](https://github.com/reactphp/promise) based interface.
+While this might seem complex at first, it actually provides a very simple way
+to handle simultanous connections in a non-blocking fashion and increases overall performance.
+
+```PHP
+$server->setAuth(function ($username, $password, $remote) {
+ // either return a boolean success value right away
+ // or use promises for delayed authentication
+
+ // $remote is a full URI à la socks5://user:pass@192.168.1.1:1234
+ // or socks5s://user:pass@192.168.1.1:1234 for SOCKS over TLS
+ // useful for logging or extracting parts, such as the remote IP
+ $ip = parse_url($remote, PHP_URL_HOST);
+
+ return ($username === 'root' && $ip === '127.0.0.1');
+});
+```
+
+Or if you only accept static authentication details, you can use the simple
+array-based authentication method as a shortcut:
+
+```PHP
+$server->setAuthArray(array(
+ 'tom' => 'password',
+ 'admin' => 'root'
+));
+```
+
+See also [example #12](examples).
+
+If you do not want to use authentication anymore:
+
+```PHP
+$server->unsetAuth();
+```
+
+#### Server proxy chaining
+
+The `Server` is responsible for creating connections to the target host.
+
+```
+Client -> SocksServer -> TargetHost
+```
+
+Sometimes it may be required to establish outgoing connections via another SOCKS
+server.
+For example, this can be useful if your target SOCKS server requires
+authentication, but your client does not support sending authentication
+information (e.g. like most webbrowser).
+
+```
+Client -> MiddlemanSocksServer -> TargetSocksServer -> TargetHost
+```
+
+The `Server` uses any instance of the `ConnectorInterface` to establish outgoing
+connections.
+In order to connect through another SOCKS server, you can simply use the
+[`Client`](#client) SOCKS connector from above.
+You can create a SOCKS `Client` instance like this:
+
+```php
+// set next SOCKS server example.com:1080 as target
+$connector = new React\Socket\Connector($loop);
+$client = new Client('user:pass@example.com:1080', $connector);
+
+// listen on localhost:1080
+$socket = new Socket('127.0.0.1:1080', $loop);
+
+// start a new server which forwards all connections to the other SOCKS server
+$server = new Server($loop, $socket, $client);
+```
+
+See also [example #21](examples).
+
+Proxy chaining can happen on the server side and/or the client side:
+
+* If you ask your client to chain through multiple proxies, then each proxy
+ server does not really know anything about chaining at all.
+ This means that this is a client-only property and not part of this class.
+ For example, you can find this in the above [`Client`](#proxy-chaining) class.
+
+* If you ask your server to chain through another proxy, then your client does
+ not really know anything about chaining at all.
+ This means that this is a server-only property and can be implemented as above.
+
+#### Server SOCKS over TLS
+
+All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+This implies that you can also use [secure TLS connections](#secure-tls-connections)
+to transfer sensitive data across SOCKS proxy servers.
+This means that no eavesdropper nor the proxy server will be able to decrypt
+your data.
+
+However, the initial SOCKS communication between the client and the proxy is
+usually via an unencrypted, plain TCP/IP connection.
+This means that an eavesdropper may be able to see *where* the client connects
+to and may also be able to see the [SOCKS authentication](#authentication)
+details in cleartext.
+
+As an alternative, you may listen for SOCKS over TLS connections so
+that the client has to establish a secure TLS connection to your SOCKS
+proxy before starting the initial SOCKS communication.
+This means that no eavesdroppper will be able to see the destination address
+the client wants to connect to or their [SOCKS authentication](#authentication)
+details.
+
+You can simply start your listening socket on the `tls://` URI scheme like this:
+
+```php
+$loop = \React\EventLoop\Factory::create();
+
+// listen on tls://127.0.0.1:1080 with the given server certificate
+$socket = new React\Socket\Server('tls://127.0.0.1:1080', $loop, array(
+ 'tls' => array(
+ 'local_cert' => __DIR__ . '/localhost.pem',
+ )
+));
+$server = new Server($loop, $socket);
+```
+
+See also [example 31](examples).
+
+> Note that for most use cases, [secure TLS connections](#secure-tls-connections)
+ should be used instead. SOCKS over TLS is considered advanced usage and is
+ used very rarely in practice.
+
+> Note that the SOCKS protocol does not support the notion of TLS. The above
+ works reasonably well because TLS is only used for the connection between
+ client and proxy server and the SOCKS protocol data is otherwise identical.
+ This implies that this does also not support [proxy chaining](#server-proxy-chaining)
+ over multiple TLS paths.
+
+#### Server Unix domain sockets
+
+All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
+based connections and higher level protocols.
+In some advanced cases, it may be useful to let your SOCKS server listen on a
+Unix domain socket (UDS) path instead of a IP:port combination.
+For example, this allows you to rely on file system permissions instead of
+having to rely on explicit [authentication](#server-authentication).
+
+You can simply start your listening socket on the `unix://` URI scheme like this:
+
+```php
+$loop = \React\EventLoop\Factory::create();
+
+// listen on /tmp/proxy.sock
+$socket = new React\Socket\Server('unix:///tmp/proxy.sock', $loop);
+$server = new Server($loop, $socket);
+```
+
+> Note that Unix domain sockets (UDS) are considered advanced usage and that
+ the SOCKS protocol does not support the notion of UDS paths. The above
+ works reasonably well because UDS is only used for the connection between
+ client and proxy server and the path will not actually passed over the protocol.
+ This implies that this does also not support [proxy chaining](#server-proxy-chaining)
+ over multiple UDS paths.
+
+## Servers
+
+### Using a PHP SOCKS server
+
+* If you're looking for an end-user SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
+* If you're looking for a SOCKS server implementation, consider using
+ the above [`Server`](#server) class.
+
+### Using SSH as a SOCKS server
+
+If you already have an SSH server set up, you can easily use it as a SOCKS
+tunnel end point. On your client, simply start your SSH client and use
+the `-D ` option to start a local SOCKS server (quoting the man page:
+a `local "dynamic" application-level port forwarding`).
+
+You can start a local SOCKS server by creating a loopback connection to your
+local system if you already run an SSH daemon:
+
+```bash
+$ ssh -D 1080 localhost
+```
+
+Alternatively, you can start a local SOCKS server tunneling through a given
+remote host that runs an SSH daemon:
+
+```bash
+$ ssh -D 1080 example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$client = new Client('127.0.0.1:1080', $connector);
+```
+
+Note that the above will allow all users on the local system to connect over
+your SOCKS server without authentication which may or may not be what you need.
+As an alternative, recent OpenSSH client versions also support
+[Unix domain sockets](#unix-domain-sockets) (UDS) paths so that you can rely
+on Unix file system permissions instead:
+
+```bash
+$ ssh -D/tmp/proxy.sock example.com
+```
+
+Now you can simply use this SSH SOCKS server like this:
+
+```PHP
+$client = new Client('socks+unix:///tmp/proxy.sock', $connector);
+```
+
+### Using the Tor (anonymity network) to tunnel SOCKS connections
+
+The [Tor anonymity network](http://www.torproject.org) client software is designed
+to encrypt your traffic and route it over a network of several nodes to conceal its origin.
+It presents a SOCKS4 and SOCKS5 interface on TCP port 9050 by default
+which allows you to tunnel any traffic through the anonymity network.
+In most scenarios you probably don't want your client to resolve the target hostnames,
+because you would leak DNS information to anybody observing your local traffic.
+Also, Tor provides hidden services through an `.onion` pseudo top-level domain
+which have to be resolved by Tor.
+
+```PHP
+$client = new Client('127.0.0.1:9050', $connector);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require clue/socks-react:^0.8.7
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite contains a number of tests that rely on a working internet
+connection, alternatively you can also run it like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see LICENSE
+
+## More
+
+* If you want to learn more about processing streams of data, refer to the
+ documentation of the underlying
+ [react/stream](https://github.com/reactphp/stream) component.
+* If you want to learn more about how the
+ [`ConnectorInterface`](#connectorinterface) and its usual implementations look
+ like, refer to the documentation of the underlying
+ [react/socket component](https://github.com/reactphp/socket).
+* As an alternative to a SOCKS (SOCKS4/SOCKS5) proxy, you may also want to look into
+ using an HTTP CONNECT proxy instead.
+ You may want to use [clue/http-proxy-react](https://github.com/clue/php-http-proxy-react)
+ which also provides an implementation of the
+ [`ConnectorInterface`](#connectorinterface) so that supporting either proxy
+ protocol should be fairly trivial.
+* If you're dealing with public proxies, you'll likely have to work with mixed
+ quality and unreliable proxies. You may want to look into using
+ [clue/connection-manager-extra](https://github.com/clue/php-connection-manager-extra)
+ which allows retrying unreliable ones, implying connection timeouts,
+ concurrently working with multiple connectors and more.
+* If you're looking for an end-user SOCKS server daemon, you may want to use
+ [LeProxy](https://leproxy.org/) or [clue/psocksd](https://github.com/clue/psocksd).
diff --git a/vendor/clue/socks-react/composer.json b/vendor/clue/socks-react/composer.json
new file mode 100755
index 0000000..50ef83a
--- /dev/null
+++ b/vendor/clue/socks-react/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "clue/socks-react",
+ "description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
+ "keywords": ["socks client", "socks server", "proxy", "tcp tunnel", "socks protocol", "async", "ReactPHP"],
+ "homepage": "https://github.com/clue/php-socks-react",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "autoload": {
+ "psr-4": {"Clue\\React\\Socks\\": "src/"}
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/socket": "^1.0 || ^0.8.6",
+ "react/promise": "^2.1 || ^1.2",
+ "evenement/evenement": "~3.0|~1.0|~2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
+ "clue/connection-manager-extra": "^1.0 || ^0.7",
+ "clue/block-react": "^1.1"
+ }
+}
diff --git a/vendor/clue/socks-react/examples/01-http.php b/vendor/clue/socks-react/examples/01-http.php
new file mode 100755
index 0000000..584b6c4
--- /dev/null
+++ b/vendor/clue/socks-react/examples/01-http.php
@@ -0,0 +1,30 @@
+ $client,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/02-https.php b/vendor/clue/socks-react/examples/02-https.php
new file mode 100755
index 0000000..f763fc2
--- /dev/null
+++ b/vendor/clue/socks-react/examples/02-https.php
@@ -0,0 +1,30 @@
+ $client,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/03-proxy-chaining.php b/vendor/clue/socks-react/examples/03-proxy-chaining.php
new file mode 100755
index 0000000..5278d2c
--- /dev/null
+++ b/vendor/clue/socks-react/examples/03-proxy-chaining.php
@@ -0,0 +1,46 @@
+ [...]' . PHP_EOL;
+ echo 'You can add 1..n proxies in the path' . PHP_EOL;
+ exit(1);
+}
+
+$path = array_slice($argv, 1);
+
+// Alternatively, you can also hard-code this value like this:
+//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
+
+$loop = React\EventLoop\Factory::create();
+
+// set next SOCKS server chain via p1 -> p2 -> p3 -> destination
+$connector = new Connector($loop);
+foreach ($path as $proxy) {
+ $connector = new Client($proxy, $connector);
+}
+
+// please note how the client uses p3 (not p1!), which in turn then uses the complete chain
+// this creates a TCP/IP connection to p1, which then connects to p2, then to p3, which then connects to the target
+$connector = new Connector($loop, array(
+ 'tcp' => $connector,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS client connecting to SOCKS proxy server chain ' . implode(' -> ', $path) . PHP_EOL;
+
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/04-local-dns.php b/vendor/clue/socks-react/examples/04-local-dns.php
new file mode 100755
index 0000000..2ea39dd
--- /dev/null
+++ b/vendor/clue/socks-react/examples/04-local-dns.php
@@ -0,0 +1,31 @@
+ $client,
+ 'timeout' => 3.0,
+ 'dns' => '8.8.8.8'
+));
+
+echo 'Demo SOCKS client connecting to SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/11-server.php b/vendor/clue/socks-react/examples/11-server.php
new file mode 100755
index 0000000..da0a1a1
--- /dev/null
+++ b/vendor/clue/socks-react/examples/11-server.php
@@ -0,0 +1,19 @@
+getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/12-server-with-password.php b/vendor/clue/socks-react/examples/12-server-with-password.php
new file mode 100755
index 0000000..55cc30b
--- /dev/null
+++ b/vendor/clue/socks-react/examples/12-server-with-password.php
@@ -0,0 +1,24 @@
+setAuthArray(array(
+ 'tom' => 'god',
+ 'user' => 'p@ssw0rd'
+));
+
+echo 'SOCKS5 server requiring authentication listening on ' . $socket->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/13-server-blacklist.php b/vendor/clue/socks-react/examples/13-server-blacklist.php
new file mode 100755
index 0000000..c153049
--- /dev/null
+++ b/vendor/clue/socks-react/examples/13-server-blacklist.php
@@ -0,0 +1,40 @@
+ $reject,
+ 'www.google.com:80' => $reject,
+ '*' => $permit
+));
+
+// listen on 127.0.0.1:1080 or first argument
+$listen = isset($argv[1]) ? $argv[1] : '127.0.0.1:1080';
+$socket = new Socket($listen, $loop);
+
+// start the actual socks server on the given server socket and using our connection manager for outgoing connections
+$server = new Server($loop, $socket, $connector);
+
+echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/21-server-proxy-chaining.php b/vendor/clue/socks-react/examples/21-server-proxy-chaining.php
new file mode 100755
index 0000000..0d0dee2
--- /dev/null
+++ b/vendor/clue/socks-react/examples/21-server-proxy-chaining.php
@@ -0,0 +1,42 @@
+ [...]' . PHP_EOL;
+ echo 'You can add 1..n proxies in the path' . PHP_EOL;
+ exit(1);
+}
+
+$listen = $argv[1];
+$path = array_slice($argv, 2);
+
+// Alternatively, you can also hard-code these values like this:
+//$listen = '127.0.0.1:9050';
+//$path = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
+
+$loop = React\EventLoop\Factory::create();
+
+// set next SOCKS server chain -> p1 -> p2 -> p3 -> destination
+$connector = new Connector($loop);
+foreach ($path as $proxy) {
+ $connector = new Client($proxy, $connector);
+}
+
+// listen on 127.0.0.1:1080 or first argument
+$socket = new Socket($listen, $loop);
+
+// start a new server which forwards all connections to the other SOCKS server
+$server = new Server($loop, $socket, $connector);
+
+echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
+echo 'Forwarding via: ' . implode(' -> ', $path) . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php b/vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
new file mode 100755
index 0000000..39245c9
--- /dev/null
+++ b/vendor/clue/socks-react/examples/22-server-proxy-chaining-from-random-pool.php
@@ -0,0 +1,46 @@
+ ...' . PHP_EOL;
+ echo 'You can add 2..n proxies in the pool' . PHP_EOL;
+ exit(1);
+}
+
+$listen = $argv[1];
+$pool = array_slice($argv, 2);
+
+// Alternatively, you can also hard-code these values like this:
+//$listen = '127.0.0.1:9050';
+//$pool = array('127.0.0.1:9051', '127.0.0.1:9052', '127.0.0.1:9053');
+
+$loop = LoopFactory::create();
+
+// forward to socks server listening on 127.0.0.1:9051-9053
+// this connector randomly picks one of the the attached connectors from the pool
+$connector = new Connector($loop);
+$clients = array();
+foreach ($pool as $proxy) {
+ $clients []= new Client($proxy, $connector);
+}
+$connector = new ConnectionManagerRandom($clients);
+
+$socket = new Socket($listen, $loop);
+
+// start the actual socks server on the given server socket and using our connection manager for outgoing connections
+$server = new Server($loop, $socket, $connector);
+
+echo 'SOCKS server listening on ' . $socket->getAddress() . PHP_EOL;
+echo 'Randomly picking from: ' . implode(', ', $pool) . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/31-server-secure.php b/vendor/clue/socks-react/examples/31-server-secure.php
new file mode 100755
index 0000000..b4b2109
--- /dev/null
+++ b/vendor/clue/socks-react/examples/31-server-secure.php
@@ -0,0 +1,21 @@
+ array(
+ 'local_cert' => __DIR__ . '/localhost.pem',
+)));
+
+// start a new server listening for incoming connection on the given socket
+$server = new Server($loop, $socket);
+
+echo 'SOCKS over TLS server listening on ' . str_replace('tls:', 'sockss:', $socket->getAddress()) . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/32-http-secure.php b/vendor/clue/socks-react/examples/32-http-secure.php
new file mode 100755
index 0000000..d304bd4
--- /dev/null
+++ b/vendor/clue/socks-react/examples/32-http-secure.php
@@ -0,0 +1,33 @@
+ array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+))));
+$connector = new Connector($loop, array(
+ 'tcp' => $client,
+ 'timeout' => 3.0,
+ 'dns' => false
+));
+
+echo 'Demo SOCKS over TLS client connecting to secure SOCKS server ' . $proxy . PHP_EOL;
+
+$connector->connect('tcp://www.google.com:80')->then(function (ConnectionInterface $stream) {
+ echo 'connected' . PHP_EOL;
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ $stream->on('data', function ($data) {
+ echo $data;
+ });
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/clue/socks-react/examples/localhost.pem b/vendor/clue/socks-react/examples/localhost.pem
new file mode 100755
index 0000000..be69279
--- /dev/null
+++ b/vendor/clue/socks-react/examples/localhost.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
+MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
+BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
+0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
+7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
+824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
+V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
+IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
+ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
+g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
+tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
+LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
+tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
+9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
+43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
+pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
+OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
+2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
+li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
+b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
+vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
+XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
+Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
+iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
++EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
+y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
+81oh1uCH1YPLM29hPyaohxL8
+-----END PRIVATE KEY-----
diff --git a/vendor/clue/socks-react/phpunit.xml.dist b/vendor/clue/socks-react/phpunit.xml.dist
new file mode 100755
index 0000000..d451dff
--- /dev/null
+++ b/vendor/clue/socks-react/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
diff --git a/vendor/clue/socks-react/src/Client.php b/vendor/clue/socks-react/src/Client.php
new file mode 100755
index 0000000..ee643b6
--- /dev/null
+++ b/vendor/clue/socks-react/src/Client.php
@@ -0,0 +1,382 @@
+ SOCKS5 authentication
+ if (isset($parts['user']) || isset($parts['pass'])) {
+ if ($parts['scheme'] === 'socks') {
+ // default to using SOCKS5 if not given explicitly
+ $parts['scheme'] = 'socks5';
+ } elseif ($parts['scheme'] !== 'socks5') {
+ // fail if any other protocol version given explicitly
+ throw new InvalidArgumentException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
+ }
+ $parts += array('user' => '', 'pass' => '');
+ $this->setAuth(rawurldecode($parts['user']), rawurldecode($parts['pass']));
+ }
+
+ // check for valid protocol version from URI scheme
+ $this->setProtocolVersionFromScheme($parts['scheme']);
+
+ $this->socksUri = $parts['host'] . ':' . $parts['port'];
+ $this->connector = $connector;
+ }
+
+ private function setProtocolVersionFromScheme($scheme)
+ {
+ if ($scheme === 'socks' || $scheme === 'socks4a') {
+ $this->protocolVersion = '4a';
+ } elseif ($scheme === 'socks5') {
+ $this->protocolVersion = '5';
+ } elseif ($scheme === 'socks4') {
+ $this->protocolVersion = '4';
+ } else {
+ throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
+ }
+ }
+
+ /**
+ * set login data for username/password authentication method (RFC1929)
+ *
+ * @param string $username
+ * @param string $password
+ * @link http://tools.ietf.org/html/rfc1929
+ */
+ private function setAuth($username, $password)
+ {
+ if (strlen($username) > 255 || strlen($password) > 255) {
+ throw new InvalidArgumentException('Both username and password MUST NOT exceed a length of 255 bytes each');
+ }
+ $this->auth = pack('C2', 0x01, strlen($username)) . $username . pack('C', strlen($password)) . $password;
+ }
+
+ /**
+ * Establish a TCP/IP connection to the given target URI through the SOCKS server
+ *
+ * Many higher-level networking protocols build on top of TCP. It you're dealing
+ * with one such client implementation, it probably uses/accepts an instance
+ * implementing React's `ConnectorInterface` (and usually its default `Connector`
+ * instance). In this case you can also pass this `Connector` instance instead
+ * to make this client implementation SOCKS-aware. That's it.
+ *
+ * @param string $uri
+ * @return PromiseInterface Promise
+ */
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $port = $parts['port'];
+
+ if ($this->protocolVersion === '4' && false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ return Promise\reject(new InvalidArgumentException('Requires an IPv4 address for SOCKS4'));
+ }
+
+ if (strlen($host) > 255 || $port > 65535 || $port < 0 || (string)$port !== (string)(int)$port) {
+ return Promise\reject(new InvalidArgumentException('Invalid target specified'));
+ }
+
+ // construct URI to SOCKS server to connect to
+ $socksUri = $this->socksUri;
+
+ // append path from URI if given
+ if (isset($parts['path'])) {
+ $socksUri .= $parts['path'];
+ }
+
+ // parse query args
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // append hostname from URI to query string unless explicitly given
+ if (!isset($args['hostname'])) {
+ $args['hostname'] = $host;
+ }
+
+ // append query string
+ $socksUri .= '?' . http_build_query($args, '', '&');
+
+ // append fragment from URI if given
+ if (isset($parts['fragment'])) {
+ $socksUri .= '#' . $parts['fragment'];
+ }
+
+ $that = $this;
+
+ // start TCP/IP connection to SOCKS server and then
+ // handle SOCKS protocol once connection is ready
+ // resolve plain connection once SOCKS protocol is completed
+ return $this->connector->connect($socksUri)->then(
+ function (ConnectionInterface $stream) use ($that, $host, $port) {
+ return $that->handleConnectedSocks($stream, $host, $port);
+ },
+ function (Exception $e) {
+ throw new RuntimeException('Unable to connect to proxy (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111, $e);
+ }
+ );
+ }
+
+ /**
+ * Internal helper used to handle the communication with the SOCKS server
+ *
+ * @param ConnectionInterface $stream
+ * @param string $host
+ * @param int $port
+ * @return Promise Promise
+ * @internal
+ */
+ public function handleConnectedSocks(ConnectionInterface $stream, $host, $port)
+ {
+ $deferred = new Deferred(function ($_, $reject) {
+ $reject(new RuntimeException('Connection canceled while establishing SOCKS session (ECONNABORTED)', defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103));
+ });
+
+ $reader = new StreamReader();
+ $stream->on('data', array($reader, 'write'));
+
+ $stream->on('error', $onError = function (Exception $e) use ($deferred) {
+ $deferred->reject(new RuntimeException('Stream error while waiting for response from proxy (EIO)', defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e));
+ });
+
+ $stream->on('close', $onClose = function () use ($deferred) {
+ $deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
+ });
+
+ if ($this->protocolVersion === '5') {
+ $promise = $this->handleSocks5($stream, $host, $port, $reader);
+ } else {
+ $promise = $this->handleSocks4($stream, $host, $port, $reader);
+ }
+ $promise->then(function () use ($deferred, $stream) {
+ $deferred->resolve($stream);
+ }, function (Exception $error) use ($deferred) {
+ // pass custom RuntimeException through as-is, otherwise wrap in protocol error
+ if (!$error instanceof RuntimeException) {
+ $error = new RuntimeException('Invalid response received from proxy (EBADMSG)', defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71, $error);
+ }
+
+ $deferred->reject($error);
+ });
+
+ return $deferred->promise()->then(
+ function (ConnectionInterface $stream) use ($reader, $onError, $onClose) {
+ $stream->removeListener('data', array($reader, 'write'));
+ $stream->removeListener('error', $onError);
+ $stream->removeListener('close', $onClose);
+
+ return $stream;
+ },
+ function ($error) use ($stream, $onClose) {
+ $stream->removeListener('close', $onClose);
+ $stream->close();
+
+ throw $error;
+ }
+ );
+ }
+
+ private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader)
+ {
+ // do not resolve hostname. only try to convert to IP
+ $ip = ip2long($host);
+
+ // send IP or (0.0.0.1) if invalid
+ $data = pack('C2nNC', 0x04, 0x01, $port, $ip === false ? 1 : $ip, 0x00);
+
+ if ($ip === false) {
+ // host is not a valid IP => send along hostname (SOCKS4a)
+ $data .= $host . pack('C', 0x00);
+ }
+
+ $stream->write($data);
+
+ return $reader->readBinary(array(
+ 'null' => 'C',
+ 'status' => 'C',
+ 'port' => 'n',
+ 'ip' => 'N'
+ ))->then(function ($data) {
+ if ($data['null'] !== 0x00) {
+ throw new Exception('Invalid SOCKS response');
+ }
+ if ($data['status'] !== 0x5a) {
+ throw new RuntimeException('Proxy refused connection with SOCKS error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ }
+ });
+ }
+
+ private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader)
+ {
+ // protocol version 5
+ $data = pack('C', 0x05);
+
+ $auth = $this->auth;
+ if ($auth === null) {
+ // one method, no authentication
+ $data .= pack('C2', 0x01, 0x00);
+ } else {
+ // two methods, username/password and no authentication
+ $data .= pack('C3', 0x02, 0x02, 0x00);
+ }
+ $stream->write($data);
+
+ $that = $this;
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'method' => 'C'
+ ))->then(function ($data) use ($auth, $stream, $reader) {
+ if ($data['version'] !== 0x05) {
+ throw new Exception('Version/Protocol mismatch');
+ }
+
+ if ($data['method'] === 0x02 && $auth !== null) {
+ // username/password authentication requested and provided
+ $stream->write($auth);
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'status' => 'C'
+ ))->then(function ($data) {
+ if ($data['version'] !== 0x01 || $data['status'] !== 0x00) {
+ throw new RuntimeException('Username/Password authentication failed (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ }
+ });
+ } else if ($data['method'] !== 0x00) {
+ // any other method than "no authentication"
+ throw new RuntimeException('No acceptable authentication method found (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ }
+ })->then(function () use ($stream, $reader, $host, $port) {
+ // do not resolve hostname. only try to convert to (binary/packed) IP
+ $ip = @inet_pton($host);
+
+ $data = pack('C3', 0x05, 0x01, 0x00);
+ if ($ip === false) {
+ // not an IP, send as hostname
+ $data .= pack('C2', 0x03, strlen($host)) . $host;
+ } else {
+ // send as IPv4 / IPv6
+ $data .= pack('C', (strpos($host, ':') === false) ? 0x01 : 0x04) . $ip;
+ }
+ $data .= pack('n', $port);
+
+ $stream->write($data);
+
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'status' => 'C',
+ 'null' => 'C',
+ 'type' => 'C'
+ ));
+ })->then(function ($data) use ($reader) {
+ if ($data['version'] !== 0x05 || $data['null'] !== 0x00) {
+ throw new Exception('Invalid SOCKS response');
+ }
+ if ($data['status'] !== 0x00) {
+ // map limited list of SOCKS error codes to common socket error conditions
+ // @link https://tools.ietf.org/html/rfc1928#section-6
+ if ($data['status'] === Server::ERROR_GENERAL) {
+ throw new RuntimeException('SOCKS server reported a general server failure (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ } elseif ($data['status'] === Server::ERROR_NOT_ALLOWED_BY_RULESET) {
+ throw new RuntimeException('SOCKS server reported connection is not allowed by ruleset (EACCES)', defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ } elseif ($data['status'] === Server::ERROR_NETWORK_UNREACHABLE) {
+ throw new RuntimeException('SOCKS server reported network unreachable (ENETUNREACH)', defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101);
+ } elseif ($data['status'] === Server::ERROR_HOST_UNREACHABLE) {
+ throw new RuntimeException('SOCKS server reported host unreachable (EHOSTUNREACH)', defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113);
+ } elseif ($data['status'] === Server::ERROR_CONNECTION_REFUSED) {
+ throw new RuntimeException('SOCKS server reported connection refused (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ } elseif ($data['status'] === Server::ERROR_TTL) {
+ throw new RuntimeException('SOCKS server reported TTL/timeout expired (ETIMEDOUT)', defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110);
+ } elseif ($data['status'] === Server::ERROR_COMMAND_UNSUPPORTED) {
+ throw new RuntimeException('SOCKS server does not support the CONNECT command (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
+ } elseif ($data['status'] === Server::ERROR_ADDRESS_UNSUPPORTED) {
+ throw new RuntimeException('SOCKS server does not support this address type (EPROTO)', defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71);
+ }
+
+ throw new RuntimeException('SOCKS server reported an unassigned error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)', defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111);
+ }
+ if ($data['type'] === 0x01) {
+ // IPv4 address => skip IP and port
+ return $reader->readLength(6);
+ } elseif ($data['type'] === 0x03) {
+ // domain name => read domain name length
+ return $reader->readBinary(array(
+ 'length' => 'C'
+ ))->then(function ($data) use ($reader) {
+ // skip domain name and port
+ return $reader->readLength($data['length'] + 2);
+ });
+ } elseif ($data['type'] === 0x04) {
+ // IPv6 address => skip IP and port
+ return $reader->readLength(18);
+ } else {
+ throw new Exception('Invalid SOCKS reponse: Invalid address type');
+ }
+ });
+ }
+}
diff --git a/vendor/clue/socks-react/src/Server.php b/vendor/clue/socks-react/src/Server.php
new file mode 100755
index 0000000..0a069e2
--- /dev/null
+++ b/vendor/clue/socks-react/src/Server.php
@@ -0,0 +1,422 @@
+loop = $loop;
+ $this->connector = $connector;
+
+ $that = $this;
+ $serverInterface->on('connection', function ($connection) use ($that) {
+ $that->emit('connection', array($connection));
+ $that->onConnection($connection);
+ });
+ }
+
+ public function setProtocolVersion($version)
+ {
+ if ($version !== null) {
+ $version = (string)$version;
+ if (!in_array($version, array('4', '4a', '5'), true)) {
+ throw new InvalidArgumentException('Invalid protocol version given');
+ }
+ if ($version !== '5' && $this->auth !== null){
+ throw new UnexpectedValueException('Unable to change protocol version to anything but SOCKS5 while authentication is used. Consider removing authentication info or sticking to SOCKS5');
+ }
+ }
+ $this->protocolVersion = $version;
+ }
+
+ public function setAuth($auth)
+ {
+ if (!is_callable($auth)) {
+ throw new InvalidArgumentException('Given authenticator is not a valid callable');
+ }
+ if ($this->protocolVersion !== null && $this->protocolVersion !== '5') {
+ throw new UnexpectedValueException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
+ }
+ // wrap authentication callback in order to cast its return value to a promise
+ $this->auth = function($username, $password, $remote) use ($auth) {
+ $ret = call_user_func($auth, $username, $password, $remote);
+ if ($ret instanceof PromiseInterface) {
+ return $ret;
+ }
+ $deferred = new Deferred();
+ $ret ? $deferred->resolve() : $deferred->reject();
+ return $deferred->promise();
+ };
+ }
+
+ public function setAuthArray(array $login)
+ {
+ $this->setAuth(function ($username, $password) use ($login) {
+ return (isset($login[$username]) && (string)$login[$username] === $password);
+ });
+ }
+
+ public function unsetAuth()
+ {
+ $this->auth = null;
+ }
+
+ public function onConnection(ConnectionInterface $connection)
+ {
+ $that = $this;
+ $handling = $this->handleSocks($connection)->then(function($remote) use ($connection){
+ $connection->emit('ready',array($remote));
+ }, function ($error) use ($connection, $that) {
+ if (!($error instanceof \Exception)) {
+ $error = new \Exception($error);
+ }
+ $connection->emit('error', array($error));
+ $that->endConnection($connection);
+ });
+
+ $connection->on('close', function () use ($handling) {
+ $handling->cancel();
+ });
+ }
+
+ /**
+ * gracefully shutdown connection by flushing all remaining data and closing stream
+ */
+ public function endConnection(ConnectionInterface $stream)
+ {
+ $tid = true;
+ $loop = $this->loop;
+
+ // cancel below timer in case connection is closed in time
+ $stream->once('close', function () use (&$tid, $loop) {
+ // close event called before the timer was set up, so everything is okay
+ if ($tid === true) {
+ // make sure to not start a useless timer
+ $tid = false;
+ } else {
+ $loop->cancelTimer($tid);
+ }
+ });
+
+ // shut down connection by pausing input data, flushing outgoing buffer and then exit
+ $stream->pause();
+ $stream->end();
+
+ // check if connection is not already closed
+ if ($tid === true) {
+ // fall back to forcefully close connection in 3 seconds if buffer can not be flushed
+ $tid = $loop->addTimer(3.0, array($stream,'close'));
+ }
+ }
+
+ private function handleSocks(ConnectionInterface $stream)
+ {
+ $reader = new StreamReader();
+ $stream->on('data', array($reader, 'write'));
+
+ $that = $this;
+ $that = $this;
+
+ $auth = $this->auth;
+ $protocolVersion = $this->protocolVersion;
+
+ // authentication requires SOCKS5
+ if ($auth !== null) {
+ $protocolVersion = '5';
+ }
+
+ return $reader->readByte()->then(function ($version) use ($stream, $that, $protocolVersion, $auth, $reader){
+ if ($version === 0x04) {
+ if ($protocolVersion === '5') {
+ throw new UnexpectedValueException('SOCKS4 not allowed due to configuration');
+ }
+ return $that->handleSocks4($stream, $protocolVersion, $reader);
+ } else if ($version === 0x05) {
+ if ($protocolVersion !== null && $protocolVersion !== '5') {
+ throw new UnexpectedValueException('SOCKS5 not allowed due to configuration');
+ }
+ return $that->handleSocks5($stream, $auth, $reader);
+ }
+ throw new UnexpectedValueException('Unexpected/unknown version number');
+ });
+ }
+
+ public function handleSocks4(ConnectionInterface $stream, $protocolVersion, StreamReader $reader)
+ {
+ // suppliying hostnames is only allowed for SOCKS4a (or automatically detected version)
+ $supportsHostname = ($protocolVersion === null || $protocolVersion === '4a');
+
+ $remote = $stream->getRemoteAddress();
+ if ($remote !== null) {
+ // remove transport scheme and prefix socks4:// instead
+ $secure = strpos($remote, 'tls://') === 0;
+ if (($pos = strpos($remote, '://')) !== false) {
+ $remote = substr($remote, $pos + 3);
+ }
+ $remote = 'socks4' . ($secure ? 's' : '') . '://' . $remote;
+ }
+
+ $that = $this;
+ return $reader->readByteAssert(0x01)->then(function () use ($reader) {
+ return $reader->readBinary(array(
+ 'port' => 'n',
+ 'ipLong' => 'N',
+ 'null' => 'C'
+ ));
+ })->then(function ($data) use ($reader, $supportsHostname, $remote) {
+ if ($data['null'] !== 0x00) {
+ throw new Exception('Not a null byte');
+ }
+ if ($data['ipLong'] === 0) {
+ throw new Exception('Invalid IP');
+ }
+ if ($data['port'] === 0) {
+ throw new Exception('Invalid port');
+ }
+ if ($data['ipLong'] < 256 && $supportsHostname) {
+ // invalid IP => probably a SOCKS4a request which appends the hostname
+ return $reader->readStringNull()->then(function ($string) use ($data, $remote){
+ return array($string, $data['port'], $remote);
+ });
+ } else {
+ $ip = long2ip($data['ipLong']);
+ return array($ip, $data['port'], $remote);
+ }
+ })->then(function ($target) use ($stream, $that) {
+ return $that->connectTarget($stream, $target)->then(function (ConnectionInterface $remote) use ($stream){
+ $stream->write(pack('C8', 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+
+ return $remote;
+ }, function($error) use ($stream){
+ $stream->end(pack('C8', 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
+
+ throw $error;
+ });
+ }, function($error) {
+ throw new UnexpectedValueException('SOCKS4 protocol error',0,$error);
+ });
+ }
+
+ public function handleSocks5(ConnectionInterface $stream, $auth=null, StreamReader $reader)
+ {
+ $remote = $stream->getRemoteAddress();
+ if ($remote !== null) {
+ // remove transport scheme and prefix socks5:// instead
+ $secure = strpos($remote, 'tls://') === 0;
+ if (($pos = strpos($remote, '://')) !== false) {
+ $remote = substr($remote, $pos + 3);
+ }
+ $remote = 'socks5' . ($secure ? 's' : '') . '://' . $remote;
+ }
+
+ $that = $this;
+ return $reader->readByte()->then(function ($num) use ($reader) {
+ // $num different authentication mechanisms offered
+ return $reader->readLength($num);
+ })->then(function ($methods) use ($reader, $stream, $auth, &$remote) {
+ if ($auth === null && strpos($methods,"\x00") !== false) {
+ // accept "no authentication"
+ $stream->write(pack('C2', 0x05, 0x00));
+
+ return 0x00;
+ } else if ($auth !== null && strpos($methods,"\x02") !== false) {
+ // username/password authentication (RFC 1929) sub negotiation
+ $stream->write(pack('C2', 0x05, 0x02));
+ return $reader->readByteAssert(0x01)->then(function () use ($reader) {
+ return $reader->readByte();
+ })->then(function ($length) use ($reader) {
+ return $reader->readLength($length);
+ })->then(function ($username) use ($reader, $auth, $stream, &$remote) {
+ return $reader->readByte()->then(function ($length) use ($reader) {
+ return $reader->readLength($length);
+ })->then(function ($password) use ($username, $auth, $stream, &$remote) {
+ // username and password given => authenticate
+
+ // prefix username/password to remote URI
+ if ($remote !== null) {
+ $remote = str_replace('://', '://' . rawurlencode($username) . ':' . rawurlencode($password) . '@', $remote);
+ }
+
+ return $auth($username, $password, $remote)->then(function () use ($stream, $username) {
+ // accept
+ $stream->emit('auth', array($username));
+ $stream->write(pack('C2', 0x01, 0x00));
+ }, function() use ($stream) {
+ // reject => send any code but 0x00
+ $stream->end(pack('C2', 0x01, 0xFF));
+ throw new UnexpectedValueException('Unable to authenticate');
+ });
+ });
+ });
+ } else {
+ // reject all offered authentication methods
+ $stream->write(pack('C2', 0x05, 0xFF));
+ throw new UnexpectedValueException('No acceptable authentication mechanism found');
+ }
+ })->then(function ($method) use ($reader, $stream) {
+ return $reader->readBinary(array(
+ 'version' => 'C',
+ 'command' => 'C',
+ 'null' => 'C',
+ 'type' => 'C'
+ ));
+ })->then(function ($data) use ($reader) {
+ if ($data['version'] !== 0x05) {
+ throw new UnexpectedValueException('Invalid SOCKS version');
+ }
+ if ($data['command'] !== 0x01) {
+ throw new UnexpectedValueException('Only CONNECT requests supported', Server::ERROR_COMMAND_UNSUPPORTED);
+ }
+// if ($data['null'] !== 0x00) {
+// throw new UnexpectedValueException('Reserved byte has to be NULL');
+// }
+ if ($data['type'] === 0x03) {
+ // target hostname string
+ return $reader->readByte()->then(function ($len) use ($reader) {
+ return $reader->readLength($len);
+ });
+ } else if ($data['type'] === 0x01) {
+ // target IPv4
+ return $reader->readLength(4)->then(function ($addr) {
+ return inet_ntop($addr);
+ });
+ } else if ($data['type'] === 0x04) {
+ // target IPv6
+ return $reader->readLength(16)->then(function ($addr) {
+ return inet_ntop($addr);
+ });
+ } else {
+ throw new UnexpectedValueException('Invalid address type', Server::ERROR_ADDRESS_UNSUPPORTED);
+ }
+ })->then(function ($host) use ($reader, &$remote) {
+ return $reader->readBinary(array('port'=>'n'))->then(function ($data) use ($host, &$remote) {
+ return array($host, $data['port'], $remote);
+ });
+ })->then(function ($target) use ($that, $stream) {
+ return $that->connectTarget($stream, $target);
+ }, function($error) use ($stream) {
+ throw new UnexpectedValueException('SOCKS5 protocol error', $error->getCode(), $error);
+ })->then(function (ConnectionInterface $remote) use ($stream) {
+ $stream->write(pack('C4Nn', 0x05, 0x00, 0x00, 0x01, 0, 0));
+
+ return $remote;
+ }, function(Exception $error) use ($stream){
+ $stream->write(pack('C4Nn', 0x05, $error->getCode() === 0 ? Server::ERROR_GENERAL : $error->getCode(), 0x00, 0x01, 0, 0));
+
+ throw $error;
+ });
+ }
+
+ public function connectTarget(ConnectionInterface $stream, array $target)
+ {
+ $uri = $target[0];
+ if (strpos($uri, ':') !== false) {
+ $uri = '[' . $uri . ']';
+ }
+ $uri .= ':' . $target[1];
+
+ // validate URI so a string hostname can not pass excessive URI parts
+ $parts = parse_url('tcp://' . $uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || count($parts) !== 3) {
+ return Promise\reject(new InvalidArgumentException('Invalid target URI given'));
+ }
+
+ if (isset($target[2])) {
+ $uri .= '?source=' . rawurlencode($target[2]);
+ }
+
+ $stream->emit('target', $target);
+ $that = $this;
+ $connecting = $this->connector->connect($uri);
+
+ $stream->on('close', function () use ($connecting) {
+ $connecting->cancel();
+ });
+
+ return $connecting->then(function (ConnectionInterface $remote) use ($stream, $that) {
+ $stream->pipe($remote, array('end'=>false));
+ $remote->pipe($stream, array('end'=>false));
+
+ // remote end closes connection => stop reading from local end, try to flush buffer to local and disconnect local
+ $remote->on('end', function() use ($stream, $that) {
+ $stream->emit('shutdown', array('remote', null));
+ $that->endConnection($stream);
+ });
+
+ // local end closes connection => stop reading from remote end, try to flush buffer to remote and disconnect remote
+ $stream->on('end', function() use ($remote, $that) {
+ $that->endConnection($remote);
+ });
+
+ // set bigger buffer size of 100k to improve performance
+ $stream->bufferSize = $remote->bufferSize = 100 * 1024 * 1024;
+
+ return $remote;
+ }, function(Exception $error) {
+ // default to general/unknown error
+ $code = Server::ERROR_GENERAL;
+
+ // map common socket error conditions to limited list of SOCKS error codes
+ if ((defined('SOCKET_EACCES') && $error->getCode() === SOCKET_EACCES) || $error->getCode() === 13) {
+ $code = Server::ERROR_NOT_ALLOWED_BY_RULESET;
+ } elseif ((defined('SOCKET_EHOSTUNREACH') && $error->getCode() === SOCKET_EHOSTUNREACH) || $error->getCode() === 113) {
+ $code = Server::ERROR_HOST_UNREACHABLE;
+ } elseif ((defined('SOCKET_ENETUNREACH') && $error->getCode() === SOCKET_ENETUNREACH) || $error->getCode() === 101) {
+ $code = Server::ERROR_NETWORK_UNREACHABLE;
+ } elseif ((defined('SOCKET_ECONNREFUSED') && $error->getCode() === SOCKET_ECONNREFUSED) || $error->getCode() === 111 || $error->getMessage() === 'Connection refused') {
+ // Socket component does not currently assign an error code for this, so we have to resort to checking the exception message
+ $code = Server::ERROR_CONNECTION_REFUSED;
+ } elseif ((defined('SOCKET_ETIMEDOUT') && $error->getCode() === SOCKET_ETIMEDOUT) || $error->getCode() === 110 || $error instanceof TimeoutException) {
+ // Socket component does not currently assign an error code for this, but we can rely on the TimeoutException
+ $code = Server::ERROR_TTL;
+ }
+
+ throw new UnexpectedValueException('Unable to connect to remote target', $code, $error);
+ });
+ }
+}
diff --git a/vendor/clue/socks-react/src/StreamReader.php b/vendor/clue/socks-react/src/StreamReader.php
new file mode 100755
index 0000000..6dbf51c
--- /dev/null
+++ b/vendor/clue/socks-react/src/StreamReader.php
@@ -0,0 +1,149 @@
+buffer .= $data;
+
+ do {
+ $current = reset($this->queue);
+
+ if ($current === false) {
+ break;
+ }
+
+ /* @var $current Closure */
+
+ $ret = $current($this->buffer);
+
+ if ($ret === self::RET_INCOMPLETE) {
+ // current is incomplete, so wait for further data to arrive
+ break;
+ } else {
+ // current is done, remove from list and continue with next
+ array_shift($this->queue);
+ }
+ } while (true);
+ }
+
+ public function readBinary($structure)
+ {
+ $length = 0;
+ $unpack = '';
+ foreach ($structure as $name=>$format) {
+ if ($length !== 0) {
+ $unpack .= '/';
+ }
+ $unpack .= $format . $name;
+
+ if ($format === 'C') {
+ ++$length;
+ } else if ($format === 'n') {
+ $length += 2;
+ } else if ($format === 'N') {
+ $length += 4;
+ } else {
+ throw new InvalidArgumentException('Invalid format given');
+ }
+ }
+
+ return $this->readLength($length)->then(function ($response) use ($unpack) {
+ return unpack($unpack, $response);
+ });
+ }
+
+ public function readLength($bytes)
+ {
+ $deferred = new Deferred();
+
+ $this->readBufferCallback(function (&$buffer) use ($bytes, $deferred) {
+ if (strlen($buffer) >= $bytes) {
+ $deferred->resolve((string)substr($buffer, 0, $bytes));
+ $buffer = (string)substr($buffer, $bytes);
+
+ return StreamReader::RET_DONE;
+ }
+ });
+
+ return $deferred->promise();
+ }
+
+ public function readByte()
+ {
+ return $this->readBinary(array(
+ 'byte' => 'C'
+ ))->then(function ($data) {
+ return $data['byte'];
+ });
+ }
+
+ public function readByteAssert($expect)
+ {
+ return $this->readByte()->then(function ($byte) use ($expect) {
+ if ($byte !== $expect) {
+ throw new UnexpectedValueException('Unexpected byte encountered');
+ }
+ return $byte;
+ });
+ }
+
+ public function readStringNull()
+ {
+ $deferred = new Deferred();
+ $string = '';
+
+ $that = $this;
+ $readOne = function () use (&$readOne, $that, $deferred, &$string) {
+ $that->readByte()->then(function ($byte) use ($deferred, &$string, $readOne) {
+ if ($byte === 0x00) {
+ $deferred->resolve($string);
+ } else {
+ $string .= chr($byte);
+ $readOne();
+ }
+ });
+ };
+ $readOne();
+
+ return $deferred->promise();
+ }
+
+ public function readBufferCallback(/* callable */ $callable)
+ {
+ if (!is_callable($callable)) {
+ throw new InvalidArgumentException('Given function must be callable');
+ }
+
+ if ($this->queue) {
+ $this->queue []= $callable;
+ } else {
+ $this->queue = array($callable);
+
+ if ($this->buffer !== '') {
+ // this is the first element in the queue and the buffer is filled => trigger write procedure
+ $this->write('');
+ }
+ }
+ }
+
+ public function getBuffer()
+ {
+ return $this->buffer;
+ }
+}
diff --git a/vendor/clue/socks-react/tests/ClientTest.php b/vendor/clue/socks-react/tests/ClientTest.php
new file mode 100755
index 0000000..e304901
--- /dev/null
+++ b/vendor/clue/socks-react/tests/ClientTest.php
@@ -0,0 +1,403 @@
+loop = React\EventLoop\Factory::create();
+ $this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->client = new Client('127.0.0.1:1080', $this->connector);
+ }
+
+ public function testCtorAcceptsUriWithHostAndPort()
+ {
+ $client = new Client('127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithScheme()
+ {
+ $client = new Client('socks://127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithHostOnlyAssumesDefaultPort()
+ {
+ $client = new Client('127.0.0.1', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSecureScheme()
+ {
+ $client = new Client('sockss://127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSecureVersionScheme()
+ {
+ $client = new Client('socks5s://127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSocksUnixScheme()
+ {
+ $client = new Client('socks+unix:///tmp/socks.socket', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ public function testCtorAcceptsUriWithSocks5UnixScheme()
+ {
+ $client = new Client('socks5+unix:///tmp/socks.socket', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testCtorThrowsForInvalidUri()
+ {
+ new Client('////', $this->connector);
+ }
+
+ public function testValidAuthFromUri()
+ {
+ $this->client = new Client('username:password@127.0.0.1', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidAuthInformation()
+ {
+ new Client(str_repeat('a', 256) . ':test@127.0.0.1', $this->connector);
+ }
+
+ public function testValidAuthAndVersionFromUri()
+ {
+ $this->client = new Client('socks5://username:password@127.0.0.1:9050', $this->connector);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidCanNotSetAuthenticationForSocks4Uri()
+ {
+ $this->client = new Client('socks4://username:password@127.0.0.1:9050', $this->connector);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidProtocolVersion()
+ {
+ $this->client = new Client('socks3://127.0.0.1:9050', $this->connector);
+ }
+
+ public function testCreateWillConnectToProxy()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=localhost')->willReturn($promise);
+
+ $promise = $this->client->connect('localhost:80');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testCreateWillConnectToProxyWithFullUri()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080/?hostname=test#fragment')->willReturn($promise);
+
+ $promise = $this->client->connect('localhost:80/?hostname=test#fragment');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testCreateWithInvalidHostDoesNotConnect()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $promise = $this->client->connect(str_repeat('a', '256') . ':80');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testCreateWithInvalidPortDoesNotConnect()
+ {
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $promise = $this->client->connect('some-random-site:some-random-port');
+
+ $this->assertInstanceOf('\React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectorRejectsWillRejectConnection()
+ {
+ $promise = \React\Promise\reject(new RuntimeException());
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
+ }
+
+ public function testCancelConnectionDuringConnectionWillCancelConnection()
+ {
+ $promise = new Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+ $promise->cancel();
+
+ $this->expectPromiseReject($promise);
+ }
+
+ public function testCancelConnectionDuringSessionWillCloseStream()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = new Promise(function ($resolve) use ($stream) { $resolve($stream); });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNABORTED));
+ }
+
+ public function testEmitConnectionCloseDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('close');
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNRESET));
+ }
+
+ public function testEmitConnectionErrorDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('error', array(new RuntimeException()));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EIO));
+ }
+
+ public function testEmitInvalidSocks4DataDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testEmitInvalidSocks5DataDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("HTTP/1.1 400 Bad Request\r\n\r\n"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testEmitSocks5DataErrorDuringSessionWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x01\x00\x00"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_ECONNREFUSED));
+ }
+
+ public function testEmitSocks5DataInvalidAddressTypeWillRejectConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x00"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode(SOCKET_EBADMSG));
+ }
+
+ public function testEmitSocks5DataIpv6AddressWillResolveConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->never())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=%3A%3A1')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('[::1]:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x04" . inet_pton('::1') . "\x00\x50"));
+
+ $promise->then($this->expectCallableOnce());
+ }
+
+ public function testEmitSocks5DataHostnameAddressWillResolveConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->never())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05\x00\x00\x03\x0Agoogle.com\x00\x50"));
+
+ $promise->then($this->expectCallableOnce());
+ }
+
+ public function provideConnectionErrors()
+ {
+ return array(
+ array(
+ Server::ERROR_GENERAL,
+ SOCKET_ECONNREFUSED
+ ),
+ array(
+ Server::ERROR_NOT_ALLOWED_BY_RULESET,
+ SOCKET_EACCES
+ ),
+ array(
+ Server::ERROR_NETWORK_UNREACHABLE,
+ SOCKET_ENETUNREACH
+ ),
+ array(
+ Server::ERROR_HOST_UNREACHABLE,
+ SOCKET_EHOSTUNREACH
+ ),
+ array(
+ Server::ERROR_CONNECTION_REFUSED,
+ SOCKET_ECONNREFUSED
+ ),
+ array(
+ Server::ERROR_TTL,
+ SOCKET_ETIMEDOUT
+ ),
+ array(
+ Server::ERROR_COMMAND_UNSUPPORTED,
+ SOCKET_EPROTO
+ ),
+ array(
+ Server::ERROR_ADDRESS_UNSUPPORTED,
+ SOCKET_EPROTO
+ ),
+ array(
+ 200,
+ SOCKET_ECONNREFUSED
+ )
+ );
+ }
+
+ /**
+ * @dataProvider provideConnectionErrors
+ * @param int $error
+ * @param int $expectedCode
+ */
+ public function testEmitSocks5DataErrorMapsToExceptionCode($error, $expectedCode)
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock();
+ $stream->expects($this->once())->method('close');
+
+ $promise = \React\Promise\resolve($stream);
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:1080?hostname=google.com')->willReturn($promise);
+
+ $this->client = new Client('socks5://127.0.0.1:1080', $this->connector);
+
+ $promise = $this->client->connect('google.com:80');
+
+ $stream->emit('data', array("\x05\x00" . "\x05" . chr($error) . "\x00\x00"));
+
+ $promise->then(null, $this->expectCallableOnceWithExceptionCode($expectedCode));
+ }
+}
diff --git a/vendor/clue/socks-react/tests/FunctionalTest.php b/vendor/clue/socks-react/tests/FunctionalTest.php
new file mode 100755
index 0000000..2f1190e
--- /dev/null
+++ b/vendor/clue/socks-react/tests/FunctionalTest.php
@@ -0,0 +1,437 @@
+loop = React\EventLoop\Factory::create();
+
+ $socket = new React\Socket\Server(0, $this->loop);
+ $address = $socket->getAddress();
+ if (strpos($address, '://') === false) {
+ $address = 'tcp://' . $address;
+ }
+ $this->port = parse_url($address, PHP_URL_PORT);
+ $this->assertNotEquals(0, $this->port);
+
+ $this->server = new Server($this->loop, $socket);
+ $this->connector = new TcpConnector($this->loop);
+ $this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
+ }
+
+ /** @group internet */
+ public function testConnection()
+ {
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionInvalid()
+ {
+ $this->assertRejectPromise($this->client->connect('www.google.com.invalid:80'));
+ }
+
+ public function testConnectionWithIpViaSocks4()
+ {
+ $this->server->setProtocolVersion('4');
+
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('127.0.0.1:' . $this->port));
+ }
+
+ /** @group internet */
+ public function testConnectionWithHostnameViaSocks4Fails()
+ {
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionWithInvalidPortFails()
+ {
+ $this->assertRejectPromise($this->client->connect('www.google.com:100000'));
+ }
+
+ public function testConnectionWithIpv6ViaSocks4Fails()
+ {
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('[::1]:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocks4a()
+ {
+ $this->server->setProtocolVersion('4a');
+ $this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocks5()
+ {
+ $this->server->setProtocolVersion(5);
+ $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocksOverTls()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ )));
+ $this->server = new Server($this->loop, $socket);
+
+ $this->connector = new Connector($this->loop, array('tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ )));
+ $this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /**
+ * @group internet
+ * @requires PHP 5.6
+ */
+ public function testConnectionSocksOverTlsUsesPeerNameFromSocksUri()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $socket = new \React\Socket\Server('tls://127.0.0.1:0', $this->loop, array('tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ )));
+ $this->server = new Server($this->loop, $socket);
+
+ $this->connector = new Connector($this->loop, array('tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => true
+ )));
+ $this->client = new Client(str_replace('tls:', 'sockss:', $socket->getAddress()), $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionSocksOverUnix()
+ {
+ if (!in_array('unix', stream_get_transports())) {
+ $this->markTestSkipped('System does not support unix:// scheme');
+ }
+
+ $path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
+ $socket = new UnixServer($path, $this->loop);
+ $this->server = new Server($this->loop, $socket);
+
+ $this->connector = new Connector($this->loop);
+ $this->client = new Client('socks+unix://' . $path, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+
+ unlink($path);
+ }
+
+ /** @group internet */
+ public function testConnectionSocks5OverUnix()
+ {
+ if (!in_array('unix', stream_get_transports())) {
+ $this->markTestSkipped('System does not support unix:// scheme');
+ }
+
+ $path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
+ $socket = new UnixServer($path, $this->loop);
+ $this->server = new Server($this->loop, $socket);
+ $this->server->setProtocolVersion(5);
+
+ $this->connector = new Connector($this->loop);
+ $this->client = new Client('socks5+unix://' . $path, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+
+ unlink($path);
+ }
+
+ /** @group internet */
+ public function testConnectionSocksWithAuthenticationOverUnix()
+ {
+ if (!in_array('unix', stream_get_transports())) {
+ $this->markTestSkipped('System does not support unix:// scheme');
+ }
+
+ $path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
+ $socket = new UnixServer($path, $this->loop);
+ $this->server = new Server($this->loop, $socket);
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->connector = new Connector($this->loop);
+ $this->client = new Client('socks+unix://name:pass@' . $path, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+
+ unlink($path);
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationFromUri()
+ {
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationCallback()
+ {
+ $called = 0;
+ $that = $this;
+ $this->server->setAuth(function ($name, $pass, $remote) use ($that, &$called) {
+ ++$called;
+ $that->assertEquals('name', $name);
+ $that->assertEquals('pass', $pass);
+ $that->assertStringStartsWith('socks5://name:pass@127.0.0.1:', $remote);
+
+ return true;
+ });
+
+ $this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ $this->assertEquals(1, $called);
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationCallbackWillNotBeInvokedIfClientsSendsNoAuth()
+ {
+ $called = 0;
+ $this->server->setAuth(function () use (&$called) {
+ ++$called;
+
+ return true;
+ });
+
+ $this->client = new Client('127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'));
+ $this->assertEquals(0, $called);
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationFromUriEncoded()
+ {
+ $this->server->setAuthArray(array('name' => 'p@ss:w0rd'));
+
+ $this->client = new Client(rawurlencode('name') . ':' . rawurlencode('p@ss:w0rd') . '@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationFromUriWithOnlyUserAndNoPassword()
+ {
+ $this->server->setAuthArray(array('empty' => ''));
+
+ $this->client = new Client('empty@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationEmptyPassword()
+ {
+ $this->server->setAuthArray(array('user' => ''));
+ $this->client = new Client('user@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectionAuthenticationUnused()
+ {
+ $this->client = new Client('name:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ public function testConnectionInvalidProtocolDoesNotMatchSocks5()
+ {
+ $this->server->setProtocolVersion(5);
+ $this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
+ }
+
+ public function testConnectionInvalidProtocolDoesNotMatchSocks4()
+ {
+ $this->server->setProtocolVersion(4);
+ $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
+ }
+
+ public function testConnectionInvalidNoAuthentication()
+ {
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
+ }
+
+ public function testConnectionInvalidAuthenticationMismatch()
+ {
+ $this->server->setAuthArray(array('name' => 'pass'));
+
+ $this->client = new Client('user:pass@127.0.0.1:' . $this->port, $this->connector);
+
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_EACCES);
+ }
+
+ /** @group internet */
+ public function testConnectorOkay()
+ {
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testConnectorInvalidDomain()
+ {
+ $this->assertRejectPromise($this->client->connect('www.google.commm:80'));
+ }
+
+ /** @group internet */
+ public function testConnectorCancelConnection()
+ {
+ $promise = $this->client->connect('www.google.com:80');
+ $promise->cancel();
+
+ $this->assertRejectPromise($promise);
+ }
+
+ /** @group internet */
+ public function testConnectorInvalidUnboundPortTimeout()
+ {
+ // time out the connection attempt in 0.1s (as expected)
+ $tcp = new TimeoutConnector($this->client, 0.1, $this->loop);
+
+ $this->assertRejectPromise($tcp->connect('www.google.com:8080'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorOkay()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop);
+
+ $this->assertResolveStream($ssl->connect('www.google.com:443'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorToBadSslWithVerifyFails()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => true));
+
+ $this->assertRejectPromise($ssl->connect('self-signed.badssl.com:443'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorToBadSslWithoutVerifyWorks()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop, array('verify_peer' => false));
+
+ $this->assertResolveStream($ssl->connect('self-signed.badssl.com:443'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorInvalidPlaintextIsNotSsl()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Required function does not exist in your environment (HHVM?)');
+ }
+
+ $ssl = new SecureConnector($this->client, $this->loop);
+
+ $this->assertRejectPromise($ssl->connect('www.google.com:80'));
+ }
+
+ /** @group internet */
+ public function testSecureConnectorInvalidUnboundPortTimeout()
+ {
+ $ssl = new SecureConnector($this->client, $this->loop);
+
+ // time out the connection attempt in 0.1s (as expected)
+ $ssl = new TimeoutConnector($ssl, 0.1, $this->loop);
+
+ $this->assertRejectPromise($ssl->connect('www.google.com:8080'));
+ }
+
+ private function assertResolveStream($promise)
+ {
+ $this->expectPromiseResolve($promise);
+
+ $promise->then(function ($stream) {
+ $stream->close();
+ });
+
+ Block\await($promise, $this->loop, 2.0);
+ }
+
+ private function assertRejectPromise($promise, $message = null, $code = null)
+ {
+ $this->expectPromiseReject($promise);
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Exception');
+ if ($message !== null) {
+ $this->expectExceptionMessage($message);
+ }
+ if ($code !== null) {
+ $this->expectExceptionCode($code);
+ }
+ } else {
+ $this->setExpectedException('Exception', $message, $code);
+ }
+
+ Block\await($promise, $this->loop, 2.0);
+ }
+}
diff --git a/vendor/clue/socks-react/tests/ServerTest.php b/vendor/clue/socks-react/tests/ServerTest.php
new file mode 100755
index 0000000..5c73845
--- /dev/null
+++ b/vendor/clue/socks-react/tests/ServerTest.php
@@ -0,0 +1,428 @@
+getMockBuilder('React\Socket\ServerInterface')
+ ->getMock();
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')
+ ->getMock();
+
+ $this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')
+ ->getMock();
+
+ $this->server = new Server($loop, $socket, $this->connector);
+ }
+
+ public function testSetProtocolVersion()
+ {
+ $this->server->setProtocolVersion(4);
+ $this->server->setProtocolVersion('4a');
+ $this->server->setProtocolVersion(5);
+ $this->server->setProtocolVersion(null);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetInvalidProtocolVersion()
+ {
+ $this->server->setProtocolVersion(6);
+ }
+
+ public function testSetAuthArray()
+ {
+ $this->server->setAuthArray(array());
+
+ $this->server->setAuthArray(array(
+ 'name1' => 'password1',
+ 'name2' => 'password2'
+ ));
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testSetAuthInvalid()
+ {
+ $this->server->setAuth(true);
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testUnableToSetAuthIfProtocolDoesNotSupportAuth()
+ {
+ $this->server->setProtocolVersion(4);
+
+ $this->server->setAuthArray(array());
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testUnableToSetProtocolWhichDoesNotSupportAuth()
+ {
+ $this->server->setAuthArray(array());
+
+ // this is okay
+ $this->server->setProtocolVersion(5);
+
+ $this->server->setProtocolVersion(4);
+ }
+
+ public function testConnectWillCreateConnection()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectWillCreateConnectionWithSourceUri()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80, 'socks5://10.20.30.40:5060'));
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectWillRejectIfConnectionFails()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function ($_, $reject) { $reject(new \RuntimeException()); });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectWillCancelConnectionIfStreamCloses()
+ {
+ $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close'))->getMock();
+
+ $promise = new Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $stream->emit('close');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectWillAbortIfPromiseIsCanceled()
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = new Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $this->connector->expects($this->once())->method('connect')->with('google.com:80')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function provideConnectionErrors()
+ {
+ return array(
+ array(
+ new RuntimeException('', SOCKET_EACCES),
+ Server::ERROR_NOT_ALLOWED_BY_RULESET
+ ),
+ array(
+ new RuntimeException('', SOCKET_ENETUNREACH),
+ Server::ERROR_NETWORK_UNREACHABLE
+ ),
+ array(
+ new RuntimeException('', SOCKET_EHOSTUNREACH),
+ Server::ERROR_HOST_UNREACHABLE,
+ ),
+ array(
+ new RuntimeException('', SOCKET_ECONNREFUSED),
+ Server::ERROR_CONNECTION_REFUSED
+ ),
+ array(
+ new RuntimeException('Connection refused'),
+ Server::ERROR_CONNECTION_REFUSED
+ ),
+ array(
+ new RuntimeException('', SOCKET_ETIMEDOUT),
+ Server::ERROR_TTL
+ ),
+ array(
+ new TimeoutException(1.0),
+ Server::ERROR_TTL
+ ),
+ array(
+ new RuntimeException(),
+ Server::ERROR_GENERAL
+ )
+ );
+ }
+
+ /**
+ * @dataProvider provideConnectionErrors
+ * @param Exception $error
+ * @param int $expectedCode
+ */
+ public function testConnectWillReturnMappedSocks5ErrorCodeFromConnector($error, $expectedCode)
+ {
+ $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $promise = \React\Promise\reject($error);
+
+ $this->connector->expects($this->once())->method('connect')->willReturn($promise);
+
+ $promise = $this->server->connectTarget($stream, array('google.com', 80));
+
+ $code = null;
+ $promise->then(null, function ($error) use (&$code) {
+ $code = $error->getCode();
+ });
+
+ $this->assertEquals($expectedCode, $code);
+ }
+
+ public function testHandleSocksConnectionWillEndOnInvalidData()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+ $connection->expects($this->once())->method('pause');
+ $connection->expects($this->once())->method('end');
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array('asdasdasdasdasd'));
+ }
+
+ public function testHandleSocks4ConnectionWithIpv4WillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithHostnameWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithHostnameAndSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithSecureTlsSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80?source=socks4s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "example.com" . "\x00"));
+ }
+
+ public function testHandleSocks4aConnectionWithInvalidHostnameWillNotEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . "\x00\x00\x00\x01" . "\x00" . "tls://example.com:80?" . "\x00"));
+ }
+
+ public function testHandleSocks5ConnectionWithIpv4WillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithIpv4AndSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithSecureTlsIpv4AndSourceAddressWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write', 'getRemoteAddress'))->getMock();
+ $connection->expects($this->once())->method('getRemoteAddress')->willReturn('tls://10.20.30.40:5060');
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80?source=socks5s%3A%2F%2F10.20.30.40%3A5060')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x01" . pack('N', ip2long('127.0.0.1')) . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithIpv6WillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('[::1]:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x04" . inet_pton('::1') . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithHostnameWillEstablishOutgoingConnection()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = new Promise(function () { });
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithConnectorRefusedWillReturnReturnRefusedError()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $promise = \React\Promise\reject(new RuntimeException('Connection refused'));
+
+ $this->connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x05" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x0B" . "example.com" . "\x00\x50"));
+ }
+
+ public function testHandleSocks5UdpCommandWillNotEstablishOutgoingConnectionAndReturnCommandError()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $this->server->onConnection($connection);
+
+ $connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x07" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x03\x00\x03\x0B" . "example.com" . "\x00\x50"));
+ }
+
+ public function testHandleSocks5ConnectionWithInvalidHostnameWillNotEstablishOutgoingConnectionAndReturnGeneralError()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end', 'write'))->getMock();
+
+ $this->connector->expects($this->never())->method('connect');
+
+ $this->server->onConnection($connection);
+
+ $connection->expects($this->exactly(2))->method('write')->withConsecutive(array("\x05\x00"), array("\x05\x01" . "\x00\x01\x00\x00\x00\x00\x00\x00"));
+
+ $connection->emit('data', array("\x05\x01\x00" . "\x05\x01\x00\x03\x15" . "tls://example.com:80?" . "\x00\x50"));
+ }
+
+ public function testHandleSocksConnectionWillCancelOutputConnectionIfIncomingCloses()
+ {
+ $connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('pause', 'end'))->getMock();
+
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+
+ $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $this->server->onConnection($connection);
+
+ $connection->emit('data', array("\x04\x01" . "\x00\x50" . pack('N', ip2long('127.0.0.1')) . "\x00"));
+ $connection->emit('close');
+ }
+
+ public function testUnsetAuth()
+ {
+ $this->server->unsetAuth();
+ $this->server->unsetAuth();
+
+ $this->assertTrue(true);
+ }
+}
diff --git a/vendor/clue/socks-react/tests/StreamReaderTest.php b/vendor/clue/socks-react/tests/StreamReaderTest.php
new file mode 100755
index 0000000..a2a0d95
--- /dev/null
+++ b/vendor/clue/socks-react/tests/StreamReaderTest.php
@@ -0,0 +1,82 @@
+reader = new StreamReader();
+ }
+
+ public function testReadByteAssertCorrect()
+ {
+ $this->reader->readByteAssert(0x01)->then($this->expectCallableOnce(0x01));
+
+ $this->reader->write("\x01");
+ }
+
+ public function testReadByteAssertInvalid()
+ {
+ $this->reader->readByteAssert(0x02)->then(null, $this->expectCallableOnce());
+
+ $this->reader->write("\x03");
+ }
+
+ public function testReadStringNull()
+ {
+ $this->reader->readStringNull()->then($this->expectCallableOnce('hello'));
+
+ $this->reader->write("hello\x00");
+ }
+
+ public function testReadStringLength()
+ {
+ $this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
+
+ $this->reader->write('he');
+ $this->reader->write('ll');
+ $this->reader->write('o ');
+
+ $this->assertEquals(' ', $this->reader->getBuffer());
+ }
+
+ public function testReadBuffered()
+ {
+ $this->reader->write('hello');
+
+ $this->reader->readLength(5)->then($this->expectCallableOnce('hello'));
+
+ $this->assertEquals('', $this->reader->getBuffer());
+ }
+
+ public function testSequence()
+ {
+ $this->reader->readByte()->then($this->expectCallableOnce(ord('h')));
+ $this->reader->readByteAssert(ord('e'))->then($this->expectCallableOnce(ord('e')));
+ $this->reader->readLength(4)->then($this->expectCallableOnce('llo '));
+ $this->reader->readBinary(array('w'=>'C', 'o' => 'C'))->then($this->expectCallableOnce(array('w' => ord('w'), 'o' => ord('o'))));
+
+ $this->reader->write('hello world');
+
+ $this->assertEquals('rld', $this->reader->getBuffer());
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidStructure()
+ {
+ $this->reader->readBinary(array('invalid' => 'y'));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testInvalidCallback()
+ {
+ $this->reader->readBufferCallback(array());
+ }
+}
diff --git a/vendor/clue/socks-react/tests/bootstrap.php b/vendor/clue/socks-react/tests/bootstrap.php
new file mode 100755
index 0000000..029c6b9
--- /dev/null
+++ b/vendor/clue/socks-react/tests/bootstrap.php
@@ -0,0 +1,103 @@
+createCallableMock();
+
+
+ if (func_num_args() > 0) {
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->equalTo(func_get_arg(0)));
+ } else {
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+ }
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWithExceptionCode($code)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->callback(function ($e) use ($code) {
+ return $e->getCode() === $code;
+ }));
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceParameter($type)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf($type));
+
+ return $mock;
+ }
+
+ /**
+ * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
+ */
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('CallableStub')->getMock();
+ }
+
+ protected function expectPromiseResolve($promise)
+ {
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $that = $this;
+ $promise->then(null, function($error) use ($that) {
+ $that->assertNull($error);
+ $that->fail('promise rejected');
+ });
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ return $promise;
+ }
+
+ protected function expectPromiseReject($promise)
+ {
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $that = $this;
+ $promise->then(function($value) use ($that) {
+ $that->assertNull($value);
+ $that->fail('promise resolved');
+ });
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ return $promise;
+ }
+}
+
+class CallableStub
+{
+ public function __invoke()
+ {
+ }
+}
diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php
new file mode 100755
index 0000000..fce8549
--- /dev/null
+++ b/vendor/composer/ClassLoader.php
@@ -0,0 +1,445 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+ private $apcuPrefix;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100755
index 0000000..4b615a3
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,56 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: Composer
+Upstream-Contact: Jordi Boggiano
+Source: https://github.com/composer/composer
+
+Files: *
+Copyright: 2016, Nils Adermann
+ 2016, Jordi Boggiano
+License: Expat
+
+Files: src/Composer/Util/TlsHelper.php
+Copyright: 2016, Nils Adermann
+ 2016, Jordi Boggiano
+ 2013, Evan Coury
+License: Expat and BSD-2-Clause
+
+License: BSD-2-Clause
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+ .
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ .
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is furnished
+ to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100755
index 0000000..7a91153
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+ $vendorDir . '/react/promise/src/functions_include.php',
+ '972fda704d680a3a53c68e34e193cb22' => $vendorDir . '/react/promise-timer/src/functions_include.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
+ 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
+ 'ebf8799635f67b5d7248946fe2154f4a' => $vendorDir . '/ringcentral/psr7/src/functions_include.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100755
index 0000000..02066fb
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,10 @@
+ array($vendorDir . '/evenement/evenement/src'),
+);
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
new file mode 100755
index 0000000..5d4f0c7
--- /dev/null
+++ b/vendor/composer/autoload_psr4.php
@@ -0,0 +1,32 @@
+ array($vendorDir . '/winbox/args/src'),
+ 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
+ 'RingCentral\\Psr7\\' => array($vendorDir . '/ringcentral/psr7/src'),
+ 'React\\Stream\\' => array($vendorDir . '/react/stream/src'),
+ 'React\\Socket\\' => array($vendorDir . '/react/socket/src'),
+ 'React\\Promise\\Timer\\' => array($vendorDir . '/react/promise-timer/src'),
+ 'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
+ 'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
+ 'React\\Dns\\' => array($vendorDir . '/react/dns/src'),
+ 'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
+ 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
+ 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
+ 'LazyJsonMapper\\' => array($vendorDir . '/lazyjsonmapper/lazyjsonmapper/src'),
+ 'InstagramAPI\\' => array($vendorDir . '/mgp25/instagram-php/src'),
+ 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
+ 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
+ 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
+ 'GetOptionKit\\' => array($vendorDir . '/corneltek/getoptionkit/src'),
+ 'Fbns\\Client\\' => array($vendorDir . '/valga/fbns-react/src'),
+ 'Clue\\React\\Socks\\' => array($vendorDir . '/clue/socks-react/src'),
+ 'Clue\\React\\HttpProxy\\' => array($vendorDir . '/clue/http-proxy-react/src'),
+ 'BinSoul\\Net\\Mqtt\\Client\\React\\' => array($vendorDir . '/binsoul/net-mqtt-client-react/src'),
+ 'BinSoul\\Net\\Mqtt\\' => array($vendorDir . '/binsoul/net-mqtt/src'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100755
index 0000000..6a318f4
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,70 @@
+= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require_once __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInit331ae81537359437b02a96bc74f11b80::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ if ($useStaticLoader) {
+ $includeFiles = Composer\Autoload\ComposerStaticInit331ae81537359437b02a96bc74f11b80::$files;
+ } else {
+ $includeFiles = require __DIR__ . '/autoload_files.php';
+ }
+ foreach ($includeFiles as $fileIdentifier => $file) {
+ composerRequire331ae81537359437b02a96bc74f11b80($fileIdentifier, $file);
+ }
+
+ return $loader;
+ }
+}
+
+function composerRequire331ae81537359437b02a96bc74f11b80($fileIdentifier, $file)
+{
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ require $file;
+
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+ }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100755
index 0000000..5c67b20
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,189 @@
+ __DIR__ . '/..' . '/react/promise/src/functions_include.php',
+ '972fda704d680a3a53c68e34e193cb22' => __DIR__ . '/..' . '/react/promise-timer/src/functions_include.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
+ 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
+ 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
+ 'ebf8799635f67b5d7248946fe2154f4a' => __DIR__ . '/..' . '/ringcentral/psr7/src/functions_include.php',
+ '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
+ );
+
+ public static $prefixLengthsPsr4 = array (
+ 'W' =>
+ array (
+ 'Winbox\\' => 7,
+ ),
+ 'S' =>
+ array (
+ 'Symfony\\Component\\Process\\' => 26,
+ ),
+ 'R' =>
+ array (
+ 'RingCentral\\Psr7\\' => 17,
+ 'React\\Stream\\' => 13,
+ 'React\\Socket\\' => 13,
+ 'React\\Promise\\Timer\\' => 20,
+ 'React\\Promise\\' => 14,
+ 'React\\EventLoop\\' => 16,
+ 'React\\Dns\\' => 10,
+ 'React\\Cache\\' => 12,
+ ),
+ 'P' =>
+ array (
+ 'Psr\\Log\\' => 8,
+ 'Psr\\Http\\Message\\' => 17,
+ ),
+ 'L' =>
+ array (
+ 'LazyJsonMapper\\' => 15,
+ ),
+ 'I' =>
+ array (
+ 'InstagramAPI\\' => 13,
+ ),
+ 'G' =>
+ array (
+ 'GuzzleHttp\\Psr7\\' => 16,
+ 'GuzzleHttp\\Promise\\' => 19,
+ 'GuzzleHttp\\' => 11,
+ 'GetOptionKit\\' => 13,
+ ),
+ 'F' =>
+ array (
+ 'Fbns\\Client\\' => 12,
+ ),
+ 'C' =>
+ array (
+ 'Clue\\React\\Socks\\' => 17,
+ 'Clue\\React\\HttpProxy\\' => 21,
+ ),
+ 'B' =>
+ array (
+ 'BinSoul\\Net\\Mqtt\\Client\\React\\' => 30,
+ 'BinSoul\\Net\\Mqtt\\' => 17,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Winbox\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/winbox/args/src',
+ ),
+ 'Symfony\\Component\\Process\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/process',
+ ),
+ 'RingCentral\\Psr7\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ringcentral/psr7/src',
+ ),
+ 'React\\Stream\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/stream/src',
+ ),
+ 'React\\Socket\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/socket/src',
+ ),
+ 'React\\Promise\\Timer\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/promise-timer/src',
+ ),
+ 'React\\Promise\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/promise/src',
+ ),
+ 'React\\EventLoop\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/event-loop/src',
+ ),
+ 'React\\Dns\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/dns/src',
+ ),
+ 'React\\Cache\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/cache/src',
+ ),
+ 'Psr\\Log\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
+ ),
+ 'Psr\\Http\\Message\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/http-message/src',
+ ),
+ 'LazyJsonMapper\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/lazyjsonmapper/lazyjsonmapper/src',
+ ),
+ 'InstagramAPI\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/mgp25/instagram-php/src',
+ ),
+ 'GuzzleHttp\\Psr7\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
+ ),
+ 'GuzzleHttp\\Promise\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
+ ),
+ 'GuzzleHttp\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
+ ),
+ 'GetOptionKit\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/corneltek/getoptionkit/src',
+ ),
+ 'Fbns\\Client\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/valga/fbns-react/src',
+ ),
+ 'Clue\\React\\Socks\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/clue/socks-react/src',
+ ),
+ 'Clue\\React\\HttpProxy\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/clue/http-proxy-react/src',
+ ),
+ 'BinSoul\\Net\\Mqtt\\Client\\React\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/binsoul/net-mqtt-client-react/src',
+ ),
+ 'BinSoul\\Net\\Mqtt\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/binsoul/net-mqtt/src',
+ ),
+ );
+
+ public static $prefixesPsr0 = array (
+ 'E' =>
+ array (
+ 'Evenement' =>
+ array (
+ 0 => __DIR__ . '/..' . '/evenement/evenement/src',
+ ),
+ ),
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit331ae81537359437b02a96bc74f11b80::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit331ae81537359437b02a96bc74f11b80::$prefixDirsPsr4;
+ $loader->prefixesPsr0 = ComposerStaticInit331ae81537359437b02a96bc74f11b80::$prefixesPsr0;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100755
index 0000000..46699b3
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,1328 @@
+[
+ {
+ "name": "binsoul/net-mqtt",
+ "version": "0.2.1",
+ "version_normalized": "0.2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/binsoul/net-mqtt.git",
+ "reference": "286b28e6014739b19e0e7ce0cd5871cdd0cef9b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/binsoul/net-mqtt/zipball/286b28e6014739b19e0e7ce0cd5871cdd0cef9b3",
+ "reference": "286b28e6014739b19e0e7ce0cd5871cdd0cef9b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~5.6|~7.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~1.0",
+ "phpunit/phpunit": "~4.0||~5.0"
+ },
+ "time": "2017-04-03T20:17:02+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "description": "MQTT protocol implementation",
+ "homepage": "https://github.com/binsoul/net-mqtt",
+ "keywords": [
+ "mqtt",
+ "net"
+ ]
+ },
+ {
+ "name": "binsoul/net-mqtt-client-react",
+ "version": "0.3.2",
+ "version_normalized": "0.3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/binsoul/net-mqtt-client-react.git",
+ "reference": "6a80fea50e927ebb8bb8a631ea7903c22742ded5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/binsoul/net-mqtt-client-react/zipball/6a80fea50e927ebb8bb8a631ea7903c22742ded5",
+ "reference": "6a80fea50e927ebb8bb8a631ea7903c22742ded5",
+ "shasum": ""
+ },
+ "require": {
+ "binsoul/net-mqtt": "~0.2",
+ "php": "~5.6|~7.0",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~1.0",
+ "phpunit/phpunit": "~4.0||~5.0"
+ },
+ "time": "2017-08-20T08:06:53+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "BinSoul\\Net\\Mqtt\\Client\\React\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Mößler",
+ "email": "code@binsoul.de",
+ "homepage": "https://github.com/binsoul",
+ "role": "Developer"
+ }
+ ],
+ "description": "Asynchronous MQTT client built on React",
+ "homepage": "https://github.com/binsoul/net-mqtt-client-react",
+ "keywords": [
+ "client",
+ "mqtt",
+ "net"
+ ]
+ },
+ {
+ "name": "clue/http-proxy-react",
+ "version": "v1.3.0",
+ "version_normalized": "1.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/clue/php-http-proxy-react.git",
+ "reference": "eeff725640ed53386a6adb05ffdbfc2837404fdf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/clue/php-http-proxy-react/zipball/eeff725640ed53386a6adb05ffdbfc2837404fdf",
+ "reference": "eeff725640ed53386a6adb05ffdbfc2837404fdf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/promise": " ^2.1 || ^1.2.1",
+ "react/socket": "^1.0 || ^0.8.4",
+ "ringcentral/psr7": "^1.2"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.1",
+ "phpunit/phpunit": "^5.0 || ^4.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
+ },
+ "time": "2018-02-13T16:31:32+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Clue\\React\\HttpProxy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "description": "Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
+ "homepage": "https://github.com/clue/php-http-proxy-react",
+ "keywords": [
+ "async",
+ "connect",
+ "http",
+ "proxy",
+ "reactphp"
+ ]
+ },
+ {
+ "name": "clue/socks-react",
+ "version": "v0.8.7",
+ "version_normalized": "0.8.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/clue/php-socks-react.git",
+ "reference": "0fcd6f2f506918ff003f1b995c6e78443f26e8ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/clue/php-socks-react/zipball/0fcd6f2f506918ff003f1b995c6e78443f26e8ea",
+ "reference": "0fcd6f2f506918ff003f1b995c6e78443f26e8ea",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "~3.0|~1.0|~2.0",
+ "php": ">=5.3",
+ "react/promise": "^2.1 || ^1.2",
+ "react/socket": "^1.0 || ^0.8.6"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.1",
+ "clue/connection-manager-extra": "^1.0 || ^0.7",
+ "phpunit/phpunit": "^6.0 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
+ },
+ "time": "2017-12-17T14:47:58+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Clue\\React\\Socks\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
+ "homepage": "https://github.com/clue/php-socks-react",
+ "keywords": [
+ "async",
+ "proxy",
+ "reactphp",
+ "socks client",
+ "socks protocol",
+ "socks server",
+ "tcp tunnel"
+ ]
+ },
+ {
+ "name": "corneltek/getoptionkit",
+ "version": "2.6.0",
+ "version_normalized": "2.6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/c9s/GetOptionKit.git",
+ "reference": "995607ddf4fc90ebdb4a7d58fe972d581ad8495f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/c9s/GetOptionKit/zipball/995607ddf4fc90ebdb4a7d58fe972d581ad8495f",
+ "reference": "995607ddf4fc90ebdb4a7d58fe972d581ad8495f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2017-06-30T14:54:48+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GetOptionKit\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Yo-An Lin",
+ "email": "yoanlin93@gmail.com"
+ }
+ ],
+ "description": "Powerful command-line option toolkit",
+ "homepage": "http://github.com/c9s/GetOptionKit"
+ },
+ {
+ "name": "evenement/evenement",
+ "version": "v3.0.1",
+ "version_normalized": "3.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/igorw/evenement.git",
+ "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
+ "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "time": "2017-07-23T21:35:13+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Evenement": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": [
+ "event-dispatcher",
+ "event-emitter"
+ ]
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "6.4.1",
+ "version_normalized": "6.4.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "0895c932405407fd3a7368b6910c09a24d26db11"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11",
+ "reference": "0895c932405407fd3a7368b6910c09a24d26db11",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.6.1",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "time": "2019-10-23T15:58:00+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.3-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ]
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "v1.3.1",
+ "version_normalized": "1.3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "time": "2016-12-20T10:07:11+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ]
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.6.1",
+ "version_normalized": "1.6.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+ "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "ext-zlib": "*",
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+ },
+ "suggest": {
+ "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "time": "2019-07-01T23:21:34+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ]
+ },
+ {
+ "name": "lazyjsonmapper/lazyjsonmapper",
+ "version": "v1.6.3",
+ "version_normalized": "1.6.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/lazyjsonmapper/lazyjsonmapper.git",
+ "reference": "51e093b50f4de15d2d64548b3ca743713eed6ee9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/lazyjsonmapper/lazyjsonmapper/zipball/51e093b50f4de15d2d64548b3ca743713eed6ee9",
+ "reference": "51e093b50f4de15d2d64548b3ca743713eed6ee9",
+ "shasum": ""
+ },
+ "require": {
+ "corneltek/getoptionkit": "2.*",
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.7.1",
+ "phpunit/phpunit": "6.*"
+ },
+ "time": "2018-05-02T16:57:09+00:00",
+ "bin": [
+ "bin/lazydoctor"
+ ],
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "LazyJsonMapper\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "SteveJobzniak",
+ "role": "Developer",
+ "homepage": "https://github.com/SteveJobzniak"
+ }
+ ],
+ "description": "Advanced, intelligent & automatic object-oriented JSON containers for PHP.",
+ "homepage": "https://github.com/SteveJobzniak/LazyJsonMapper",
+ "keywords": [
+ "development",
+ "json"
+ ]
+ },
+ {
+ "name": "mgp25/instagram-php",
+ "version": "v7.0.1",
+ "version_normalized": "7.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mgp25/Instagram-API.git",
+ "reference": "53421f90b9ef7743f1c6221c4963f2b9f7a592e8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mgp25/Instagram-API/zipball/53421f90b9ef7743f1c6221c4963f2b9f7a592e8",
+ "reference": "53421f90b9ef7743f1c6221c4963f2b9f7a592e8",
+ "shasum": ""
+ },
+ "require": {
+ "binsoul/net-mqtt-client-react": "^0.3.2",
+ "clue/http-proxy-react": "^1.1.0",
+ "clue/socks-react": "^0.8.2",
+ "ext-bcmath": "*",
+ "ext-curl": "*",
+ "ext-exif": "*",
+ "ext-gd": "*",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "guzzlehttp/guzzle": "^6.2",
+ "lazyjsonmapper/lazyjsonmapper": "^1.6.1",
+ "php": ">=5.6",
+ "psr/log": "^1.0",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "^2.5",
+ "react/socket": "^0.8",
+ "symfony/process": "^3.4|^4.0",
+ "valga/fbns-react": "^0.1.8",
+ "winbox/args": "1.0.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.11.0",
+ "monolog/monolog": "^1.23",
+ "phpunit/phpunit": "^5.7 || ^6.2",
+ "react/http": "^0.7.2"
+ },
+ "suggest": {
+ "ext-event": "Installing PHP's native Event extension enables faster Realtime class event handling."
+ },
+ "time": "2019-09-17T00:56:42+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "InstagramAPI\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "RPL-1.5",
+ "proprietary"
+ ],
+ "authors": [
+ {
+ "name": "mgp25",
+ "email": "me@mgp25.com",
+ "role": "Founder"
+ },
+ {
+ "name": "SteveJobzniak",
+ "homepage": "https://github.com/SteveJobzniak",
+ "role": "Developer"
+ }
+ ],
+ "description": "Instagram's private API for PHP",
+ "keywords": [
+ "api",
+ "instagram",
+ "php",
+ "private"
+ ]
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2016-08-06T14:39:51+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ]
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.1",
+ "version_normalized": "1.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
+ "reference": "bf73deb2b3b896a9d9c75f3f0d88185d2faa27e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2019-10-25T08:06:51+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ]
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "version_normalized": "3.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "time": "2019-03-08T08:55:37+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders."
+ },
+ {
+ "name": "react/cache",
+ "version": "v1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/cache.git",
+ "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/cache/zipball/aa10d63a1b40a36a486bdf527f28bac607ee6466",
+ "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "~2.0|~1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-07-11T13:45:28+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Cache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": [
+ "cache",
+ "caching",
+ "promise",
+ "reactphp"
+ ]
+ },
+ {
+ "name": "react/dns",
+ "version": "v0.4.19",
+ "version_normalized": "0.4.19.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/dns.git",
+ "reference": "6852fb98e22d2e5bb35fe5aeeaa96551b120e7c9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/dns/zipball/6852fb98e22d2e5bb35fe5aeeaa96551b120e7c9",
+ "reference": "6852fb98e22d2e5bb35fe5aeeaa96551b120e7c9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.1 || ^1.2.1",
+ "react/promise-timer": "^1.2",
+ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-07-10T21:00:53+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Dns\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": [
+ "async",
+ "dns",
+ "dns-resolver",
+ "reactphp"
+ ]
+ },
+ {
+ "name": "react/event-loop",
+ "version": "v0.4.3",
+ "version_normalized": "0.4.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/event-loop.git",
+ "reference": "8bde03488ee897dc6bb3d91e4e17c353f9c5252f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/event-loop/zipball/8bde03488ee897dc6bb3d91e4e17c353f9c5252f",
+ "reference": "8bde03488ee897dc6bb3d91e4e17c353f9c5252f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "suggest": {
+ "ext-event": "~1.0",
+ "ext-libev": "*",
+ "ext-libevent": ">=0.1.0"
+ },
+ "time": "2017-04-27T10:56:23+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Event loop abstraction layer that libraries can use for evented I/O.",
+ "keywords": [
+ "asynchronous",
+ "event-loop"
+ ]
+ },
+ {
+ "name": "react/promise",
+ "version": "v2.7.1",
+ "version_normalized": "2.7.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise.git",
+ "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
+ "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "time": "2019-01-07T21:25:54+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com"
+ }
+ ],
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "keywords": [
+ "promise",
+ "promises"
+ ]
+ },
+ {
+ "name": "react/promise-timer",
+ "version": "v1.5.1",
+ "version_normalized": "1.5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise-timer.git",
+ "reference": "35fb910604fd86b00023fc5cda477c8074ad0abc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/35fb910604fd86b00023fc5cda477c8074ad0abc",
+ "reference": "35fb910604fd86b00023fc5cda477c8074ad0abc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.7.0 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-03-27T18:10:32+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\Timer\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
+ "homepage": "https://github.com/reactphp/promise-timer",
+ "keywords": [
+ "async",
+ "event-loop",
+ "promise",
+ "reactphp",
+ "timeout",
+ "timer"
+ ]
+ },
+ {
+ "name": "react/socket",
+ "version": "v0.8.12",
+ "version_normalized": "0.8.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/socket.git",
+ "reference": "7f7e6c56ccda7418a1a264892a625f38a5bdee0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/socket/zipball/7f7e6c56ccda7418a1a264892a625f38a5bdee0c",
+ "reference": "7f7e6c56ccda7418a1a264892a625f38a5bdee0c",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.0",
+ "react/dns": "^0.4.13",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.6.0 || ^1.2.1",
+ "react/promise-timer": "^1.4.0",
+ "react/stream": "^1.0 || ^0.7.1"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2018-06-11T14:33:43+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": [
+ "Connection",
+ "Socket",
+ "async",
+ "reactphp",
+ "stream"
+ ]
+ },
+ {
+ "name": "react/stream",
+ "version": "v1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/stream.git",
+ "reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/stream/zipball/50426855f7a77ddf43b9266c22320df5bf6c6ce6",
+ "reference": "50426855f7a77ddf43b9266c22320df5bf6c6ce6",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5"
+ },
+ "require-dev": {
+ "clue/stream-filter": "~1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "time": "2019-01-01T16:15:09+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": [
+ "event-driven",
+ "io",
+ "non-blocking",
+ "pipe",
+ "reactphp",
+ "readable",
+ "stream",
+ "writable"
+ ]
+ },
+ {
+ "name": "ringcentral/psr7",
+ "version": "1.2.2",
+ "version_normalized": "1.2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ringcentral/psr7.git",
+ "reference": "dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ringcentral/psr7/zipball/dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c",
+ "reference": "dcd84bbb49b96c616d1dcc8bfb9bef3f2cd53d1c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "psr/http-message": "~1.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "time": "2018-01-15T21:00:49+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "RingCentral\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "PSR-7 message implementation",
+ "keywords": [
+ "http",
+ "message",
+ "stream",
+ "uri"
+ ]
+ },
+ {
+ "name": "symfony/process",
+ "version": "v4.3.5",
+ "version_normalized": "4.3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/50556892f3cc47d4200bfd1075314139c4c9ff4b",
+ "reference": "50556892f3cc47d4200bfd1075314139c4c9ff4b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "time": "2019-09-26T21:17:10+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com"
+ },
+ {
+ "name": "valga/fbns-react",
+ "version": "0.1.8",
+ "version_normalized": "0.1.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/valga/fbns-react.git",
+ "reference": "4bbf513a8ffed7e0c9ca10776033d34515bb8b37"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/valga/fbns-react/zipball/4bbf513a8ffed7e0c9ca10776033d34515bb8b37",
+ "reference": "4bbf513a8ffed7e0c9ca10776033d34515bb8b37",
+ "shasum": ""
+ },
+ "require": {
+ "binsoul/net-mqtt": "~0.2",
+ "evenement/evenement": "~2.0|~3.0",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "php": "~5.6|~7.0",
+ "psr/log": "~1.0",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.4",
+ "monolog/monolog": "~1.23"
+ },
+ "suggest": {
+ "ext-event": "For more efficient event loop implementation.",
+ "ext-gmp": "To be able to run this code on x86 PHP builds."
+ },
+ "time": "2017-10-09T07:54:13+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Fbns\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Abyr Valg",
+ "email": "valga.github@abyrga.ru"
+ }
+ ],
+ "description": "A PHP client for the FBNS built on top of ReactPHP",
+ "keywords": [
+ "FBNS",
+ "client",
+ "php"
+ ]
+ },
+ {
+ "name": "winbox/args",
+ "version": "v1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/johnstevenson/winbox-args.git",
+ "reference": "389a9ed9410e6f422b1031b3e55a402ace716296"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/johnstevenson/winbox-args/zipball/389a9ed9410e6f422b1031b3e55a402ace716296",
+ "reference": "389a9ed9410e6f422b1031b3e55a402ace716296",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "time": "2016-08-04T14:30:27+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Winbox\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Windows command-line formatter",
+ "homepage": "http://github.com/johnstevenson/winbox-args",
+ "keywords": [
+ "Escape",
+ "command",
+ "windows"
+ ]
+ }
+]
diff --git a/vendor/corneltek/getoptionkit/.gitignore b/vendor/corneltek/getoptionkit/.gitignore
new file mode 100755
index 0000000..f32e02a
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/.gitignore
@@ -0,0 +1,3 @@
+/build
+/vendor
+*.tgz
diff --git a/vendor/corneltek/getoptionkit/.travis.yml b/vendor/corneltek/getoptionkit/.travis.yml
new file mode 100755
index 0000000..147e77c
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/.travis.yml
@@ -0,0 +1,29 @@
+language: php
+php:
+- 5.6
+- 7.0
+- 7.1
+- hhvm
+install:
+- phpenv rehash
+- composer require "satooshi/php-coveralls" "^1" --dev --no-update
+- composer install --no-interaction
+script:
+- phpunit -c phpunit.xml.dist
+after_success:
+- php vendor/bin/coveralls -v
+matrix:
+ fast_finish: true
+ allow_failures:
+ - php: hhvm
+ - php: '5.6'
+cache:
+ apt: true
+ directories:
+ - vendor
+notifications:
+ email:
+ on_success: change
+ on_failure: change
+ slack:
+ secure: dKH3qw9myjwDO+OIz6qBqn5vJJqoFyD2frS4eH68jI5gtxHX2PJVwaP9waXTSu+FFq9wILsHGQezrawWHcMkbEo4gxbri2Ne7gFT4CJD0DAOyf02JgQ1A/cJaqQ5XrLO9CwjP0/8PKaMCHiND4SLEWgmtlFRoB7gr33QjbQjBvc=
diff --git a/vendor/corneltek/getoptionkit/CHANGELOG.md b/vendor/corneltek/getoptionkit/CHANGELOG.md
new file mode 100755
index 0000000..2446ddb
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/CHANGELOG.md
@@ -0,0 +1,56 @@
+CHANGELOG
+==================
+
+## v2.3.0 - Thu May 12 10:29:19 2016
+
+- Fixed bugs for multiple value parsing with arguments.
+- OptionParser::parse(argv) now expects the first argument to be the program name,
+ so you can pass argv directly to the parser.
+
+## v2.2.5-6 - Wed May 11 2016
+
+- Fixed bugs for ContinuousOptionParser.
+
+## v2.2.4 - Fri Oct 2 15:53:33 2015
+
+- ContinuousOptionParser improvements.
+
+## v2.2.2 - Tue Jul 14 00:15:26 2015
+
+- Added PathType.
+
+## v2.2.1 - Tue Jul 14 00:17:19 2015
+
+Merged PRs:
+
+- Commit 7bbb91b: Merge pull request #34 from 1Franck/master
+
+ added value type option(s) support, added class RegexType
+
+- Commit 3722992: Merge pull request #33 from 1Franck/patch-1
+
+ Clarified InvalidOptionValue exception message
+
+
+## v2.2.0 - Thu Jun 4 13:51:47 2015
+
+- Added more types for type constraint option. url, ip, ipv4, ipv6, email by @1Franck++
+- Several bug fixes by @1Franck++
+
+
+
+## v2.1.0 - Fri Apr 24 16:43:00 2015
+
+- Added incremental option value support.
+- Fixed #21 for negative value.
+- Used autoloading with PSR-4
+
+## v2.0.12 - Tue Apr 21 18:51:12 2015
+
+- Improved hinting text for default value
+- Some coding style fix
+- Added default value support
+- Updated default value support for ContinuousOptionParser
+- Added getValue() accessor on OptionSpec class
+- Merged pull request #22 from Gasol/zero-option. @Gasol++
+ - Fix option that can't not be 0
diff --git a/vendor/corneltek/getoptionkit/CONTRIBUTORS.txt b/vendor/corneltek/getoptionkit/CONTRIBUTORS.txt
new file mode 100755
index 0000000..59cb935
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/CONTRIBUTORS.txt
@@ -0,0 +1,11 @@
+Bitdeli Chef
+Dlussky Kirill
+Francois Lajoie
+Gasol Wu
+Igor Santos
+Jevon Wright
+MartyIX
+Michał Kniotek
+Robbert Klarenbeek
+Yo-An Lin
+Yo-An Lin
diff --git a/vendor/corneltek/getoptionkit/LICENSE b/vendor/corneltek/getoptionkit/LICENSE
new file mode 100755
index 0000000..30a0180
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Yo-An Lin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/corneltek/getoptionkit/README.md b/vendor/corneltek/getoptionkit/README.md
new file mode 100755
index 0000000..1e5794b
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/README.md
@@ -0,0 +1,370 @@
+GetOptionKit
+============
+
+Code Quality
+
+[](https://travis-ci.org/c9s/GetOptionKit)
+[](https://coveralls.io/github/c9s/GetOptionKit?branch=master)
+
+Versions & Stats
+
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+[](https://packagist.org/packages/corneltek/getoptionkit)
+
+A powerful option parser toolkit for PHP, supporting type constraints,
+flag, multiple flag, multiple values and required value checking.
+
+GetOptionKit supports PHP5.3, with fine unit testing with PHPUnit testing
+framework.
+
+GetOptionKit is object-oriented, it's flexible and extendable.
+
+Powering PHPBrew , CLIFramework and AssetKit
+
+
+
+## Features
+
+- Simple format.
+- Type constrant.
+- Multiple value, requried value, optional value checking.
+- Auto-generated help text from defined options.
+- Support app/subcommand option parsing.
+- Option Value Validator
+- Option Suggestions
+- SPL library.
+- HHVM support.
+
+## Requirements
+
+* PHP 5.3+
+
+## Install From Composer
+
+```sh
+composer require corneltek/getoptionkit
+```
+
+
+## Supported Option Formats
+
+simple flags:
+
+```sh
+program.php -a -b -c
+program.php -abc
+program.php -vvv # incremental flag v=3
+program.php -a -bc
+```
+
+with multiple values:
+
+```sh
+program.php -a foo -a bar -a zoo -b -b -b
+```
+
+specify value with equal sign:
+
+```sh
+program.php -a=foo
+program.php --long=foo
+```
+
+with normal arguments:
+
+```
+program.php -a=foo -b=bar arg1 arg2 arg3
+program.php arg1 arg2 arg3 -a=foo -b=bar
+```
+
+## Option SPEC
+
+ v|verbose flag option (with boolean value true)
+ d|dir: option require a value (MUST require)
+ d|dir+ option with multiple values.
+ d|dir? option with optional value
+ dir:=string option with type constraint of string
+ dir:=number option with type constraint of number
+ dir:=file option with type constraint of file
+ dir:=date option with type constraint of date
+ dir:=boolean option with type constraint of boolean
+ d single character only option
+ dir long option name
+
+## Command Line Forms
+
+ app [app-opts] [app arguments]
+
+ app [app-opts] subcommand [subcommand-opts] [subcommand-args]
+
+ app [app-opts] subcmd1 [subcmd-opts1] subcmd2 [subcmd-opts] subcmd3 [subcmd-opts3] [subcommand arguments....]
+
+
+## Documentation
+
+See more details in the [documentation](https://github.com/c9s/GetOptionKit/wiki)
+
+## Demo
+
+Please check `examples/demo.php`.
+
+Run:
+
+```sh
+% php examples/demo.php -f test -b 123 -b 333
+```
+
+Print:
+
+ * Available options:
+ -f, --foo option requires a value.
+ -b, --bar + option with multiple value.
+ -z, --zoo [] option with optional value.
+ -v, --verbose verbose message.
+ -d, --debug debug message.
+ --long long option name only.
+ -s short option name only.
+ Enabled options:
+ * key:foo spec:-f, --foo desc:option requires a value.
+ value => test
+
+ * key:bar spec:-b, --bar + desc:option with multiple value.
+ Array
+ (
+ [0] => 123
+ [1] => 333
+ )
+
+## Synopsis
+
+```php
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
+
+$specs = new OptionCollection;
+$specs->add('f|foo:', 'option requires a value.' )
+ ->isa('String');
+
+$specs->add('b|bar+', 'option with multiple value.' )
+ ->isa('Number');
+
+$specs->add('ip+', 'Ip constraint' )
+ ->isa('Ip');
+
+$specs->add('email+', 'Email address constraint' )
+ ->isa('Email');
+
+$specs->add('z|zoo?', 'option with optional value.' )
+ ->isa('Boolean');
+
+$specs->add('file:', 'option value should be a file.' )
+ ->isa('File');
+
+$specs->add('v|verbose', 'verbose message.' );
+$specs->add('d|debug', 'debug message.' );
+$specs->add('long', 'long option name only.' );
+$specs->add('s', 'short option name only.' );
+
+$printer = new ConsoleOptionPrinter();
+echo $printer->render($specs);
+
+$parser = new OptionParser($specs);
+
+echo "Enabled options: \n";
+try {
+ $result = $parser->parse( $argv );
+ foreach ($result->keys as $key => $spec) {
+ print_r($spec);
+ }
+
+ $opt = $result->keys['foo']; // return the option object.
+ $str = $result->keys['foo']->value; // return the option value
+
+ print_r($opt);
+ var_dump($str);
+
+} catch( Exception $e ) {
+ echo $e->getMessage();
+}
+```
+
+
+## Documentation
+
+See for more details.
+
+### Option Value Type
+
+The option value type help you validate the input,
+the following list is the current supported types:
+
+- `string`
+- `number`
+- `boolean`
+- `file`
+- `date`
+- `url`
+- `email`
+- `ip`
+- `ipv4`
+- `ipv6`
+- `regex`
+
+And here is the related sample code:
+
+```php
+$opt->add( 'f|foo:' , 'with string type value' )
+ ->isa('string');
+
+$opt->add( 'b|bar+' , 'with number type value' )
+ ->isa('number');
+
+$opt->add( 'z|zoo?' , 'with boolean type value' )
+ ->isa('boolean');
+
+$opt->add( 'file:' , 'with file type value' )
+ ->isa('file');
+
+$opt->add( 'date:' , 'with date type value' )
+ ->isa('date');
+
+$opt->add( 'url:' , 'with url type value' )
+ ->isa('url');
+
+$opt->add( 'email:' , 'with email type value' )
+ ->isa('email');
+
+$opt->add( 'ip:' , 'with ip(v4/v6) type value' )
+ ->isa('ip');
+
+$opt->add( 'ipv4:' , 'with ipv4 type value' )
+ ->isa('ipv4');
+
+$opt->add( 'ipv6:' , 'with ipv6 type value' )
+ ->isa('ipv6');
+
+$specs->add('r|regex:', 'with custom regex type value')
+ ->isa('Regex', '/^([a-z]+)$/');
+```
+
+> Please note that currently only `string`, `number`, `boolean` types can be validated.
+
+### ContinuousOptionParser
+
+```php
+$specs = new OptionCollection;
+$spec_verbose = $specs->add('v|verbose');
+$spec_color = $specs->add('c|color');
+$spec_debug = $specs->add('d|debug');
+$spec_verbose->description = 'verbose flag';
+
+// ContinuousOptionParser
+$parser = new ContinuousOptionParser( $specs );
+$result = $parser->parse(explode(' ','program -v -d test -a -b -c subcommand -e -f -g subcommand2'));
+$result2 = $parser->continueParse();
+```
+
+### OptionPrinter
+
+GetOptionKit\OptionPrinter can print options for you:
+
+ * Available options:
+ -f, --foo option requires a value.
+ -b, --bar option with multiple value.
+ -z, --zoo option with optional value.
+ -v, --verbose verbose message.
+ -d, --debug debug message.
+ --long long option name only.
+ -s short option name only.
+
+
+## Command-line app with subcommands
+
+For application with subcommands is designed by following form:
+
+
+ [app name] [app opts]
+ [subcommand1] [subcommand-opts]
+ [subcommand2] [subcommand-opts]
+ [subcommand3] [subcommand-opts]
+ [arguments]
+
+You can check the `tests/GetOptionKit/ContinuousOptionParserTest.php` unit test file:
+
+```php
+// subcommand stack
+$subcommands = array('subcommand1','subcommand2','subcommand3');
+
+// different command has its own options
+$subcommandSpecs = array(
+ 'subcommand1' => $cmdspecs,
+ 'subcommand2' => $cmdspecs,
+ 'subcommand3' => $cmdspecs,
+);
+
+// for saved options
+$subcommandOptions = array();
+
+// command arguments
+$arguments = array();
+
+$argv = explode(' ','program -v -d -c subcommand1 -a -b -c subcommand2 -c subcommand3 arg1 arg2 arg3');
+
+// parse application options first
+$parser = new ContinuousOptionParser( $appspecs );
+$app_options = $parser->parse( $argv );
+while (! $parser->isEnd()) {
+ if (@$subcommands[0] && $parser->getCurrentArgument() == $subcommands[0]) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs( $subcommandSpecs[$subcommand] );
+ $subcommandOptions[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+}
+```
+
+## Todo
+
+* Option Spec group.
+* option valid value checking.
+* custom command mapping.
+
+## Command Line Utility Design Concept
+
+* main program name should be easy to type, easy to remember.
+* subcommand should be easy to type, easy to remember. length should be shorter than 7 characters.
+* options should always have long descriptive name
+* a program should be easy to check usage.
+
+## General command interface
+
+To list usage of all subcommands or the program itself:
+
+ $ prog help
+
+To list the subcommand usage
+
+ $ prog help subcommand subcommand2 subcommand3
+
+## Hacking
+
+Fork this repository and clone it:
+
+ $ git clone git://github.com/c9s/GetOptionKit.git
+ $ cd GetOptionKit
+ $ composer install
+
+Run PHPUnit to test:
+
+ $ phpunit
+
+## License
+
+This project is released under MIT License.
diff --git a/vendor/corneltek/getoptionkit/build.xml b/vendor/corneltek/getoptionkit/build.xml
new file mode 100755
index 0000000..f53f06d
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/build.xml
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vendor/corneltek/getoptionkit/composer.json b/vendor/corneltek/getoptionkit/composer.json
new file mode 100755
index 0000000..e6577d5
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/composer.json
@@ -0,0 +1,16 @@
+{
+ "name": "corneltek/getoptionkit",
+ "homepage": "http://github.com/c9s/GetOptionKit",
+ "description": "Powerful command-line option toolkit",
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "license": "MIT",
+ "authors": [ { "name": "Yo-An Lin", "email": "yoanlin93@gmail.com" } ],
+ "autoload": {
+ "psr-4": {
+ "GetOptionKit\\": "src/"
+ }
+ },
+ "extra": { "branch-alias": { "dev-master": "2.6.x-dev" } }
+}
diff --git a/vendor/corneltek/getoptionkit/composer.lock b/vendor/corneltek/getoptionkit/composer.lock
new file mode 100755
index 0000000..1dd27fe
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/composer.lock
@@ -0,0 +1,19 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "5a9b6e9fccaf89d121650d35313e842b",
+ "packages": [],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.3.0"
+ },
+ "platform-dev": []
+}
diff --git a/vendor/corneltek/getoptionkit/examples/demo.php b/vendor/corneltek/getoptionkit/examples/demo.php
new file mode 100755
index 0000000..905f5c3
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/examples/demo.php
@@ -0,0 +1,76 @@
+#!/usr/bin/env php
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+require 'vendor/autoload.php';
+
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
+
+$specs = new OptionCollection;
+$specs->add('f|foo:', 'option requires a value.' )
+ ->isa('String');
+
+$specs->add('b|bar+', 'option with multiple value.' )
+ ->isa('Number');
+
+$specs->add('z|zoo?', 'option with optional value.' )
+ ->isa('Boolean')
+ ;
+
+$specs->add('o|output?', 'option with optional value.' )
+ ->isa('File')
+ ->defaultValue('output.txt')
+ ;
+
+// works for -vvv => verbose = 3
+$specs->add('v|verbose', 'verbose')
+ ->isa('Number')
+ ->incremental();
+
+$specs->add('file:', 'option value should be a file.' )
+ ->trigger(function($value) {
+ echo "Set value to :";
+ var_dump($value);
+ })
+ ->isa('File');
+
+$specs->add('r|regex:', 'with custom regex type value')
+ ->isa('Regex', '/^([a-z]+)$/');
+
+$specs->add('d|debug', 'debug message.' );
+$specs->add('long', 'long option name only.' );
+$specs->add('s', 'short option name only.' );
+$specs->add('m', 'short option m');
+$specs->add('4', 'short option with digit');
+
+$printer = new ConsoleOptionPrinter;
+echo $printer->render($specs);
+
+$parser = new OptionParser($specs);
+
+echo "Enabled options: \n";
+try {
+ $result = $parser->parse( $argv );
+ foreach ($result->keys as $key => $spec) {
+ print_r($spec);
+ }
+
+ $opt = $result->keys['foo']; // return the option object.
+ $str = $result->keys['foo']->value; // return the option value
+
+ print_r($opt);
+ var_dump($str);
+
+} catch( Exception $e ) {
+ echo $e->getMessage();
+}
+
diff --git a/vendor/corneltek/getoptionkit/package.ini b/vendor/corneltek/getoptionkit/package.ini
new file mode 100755
index 0000000..4d71095
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/package.ini
@@ -0,0 +1,15 @@
+[package]
+name = GetOptionKit
+version = 1.2.2
+desc = "A powerful GetOpt toolkit for PHP, which supports type constraints, flag,
+multiple flag, multiple values, required value checking."
+
+author = "Yo-An Lin (c9s) "
+channel = pear.corneltek.com
+stability = stable
+
+[require]
+php = 5.3
+pearinstaller = 1.4.1
+pear.corneltek.com/Universal =
+pear.corneltek.com/PHPUnit_TestMore =
diff --git a/vendor/corneltek/getoptionkit/phpdox.xml b/vendor/corneltek/getoptionkit/phpdox.xml
new file mode 100755
index 0000000..c727483
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/phpdox.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vendor/corneltek/getoptionkit/phprelease.ini b/vendor/corneltek/getoptionkit/phprelease.ini
new file mode 100755
index 0000000..a5c5cc9
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/phprelease.ini
@@ -0,0 +1,2 @@
+Steps = PHPUnit, BumpVersion, GitCommit, GitTag, GitPush, GitPushTags
+; VersionFrom = src/PHPRelease/Console.php
\ No newline at end of file
diff --git a/vendor/corneltek/getoptionkit/phpunit-ci.xml b/vendor/corneltek/getoptionkit/phpunit-ci.xml
new file mode 100755
index 0000000..0fed2fa
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/phpunit-ci.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
+
diff --git a/vendor/corneltek/getoptionkit/phpunit.xml.dist b/vendor/corneltek/getoptionkit/phpunit.xml.dist
new file mode 100755
index 0000000..87bb483
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/phpunit.xml.dist
@@ -0,0 +1,29 @@
+
+
+
+
+
+ src
+
+
+
+
+
+ tests
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vendor/corneltek/getoptionkit/src/Argument.php b/vendor/corneltek/getoptionkit/src/Argument.php
new file mode 100755
index 0000000..2a92717
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/Argument.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+class Argument
+{
+ public $arg;
+
+ public function __construct($arg)
+ {
+ $this->arg = $arg;
+ }
+
+ public function isLongOption()
+ {
+ return substr($this->arg, 0, 2) === '--';
+ }
+
+ public function isShortOption()
+ {
+ return (substr($this->arg, 0, 1) === '-')
+ && (substr($this->arg, 1, 1) !== '-');
+ }
+
+ public function isEmpty()
+ {
+ return $this->arg === null || empty($this->arg) && ('0' !== $this->arg);
+ }
+
+ /**
+ * Check if an option is one of the option in the collection.
+ */
+ public function anyOfOptions(OptionCollection $options)
+ {
+ $name = $this->getOptionName();
+ $keys = $options->keys();
+
+ return in_array($name, $keys);
+ }
+
+ /**
+ * Check current argument is an option by the preceding dash.
+ * note this method does not work for string with negative value.
+ *
+ * -a
+ * --foo
+ */
+ public function isOption()
+ {
+ return $this->isShortOption() || $this->isLongOption();
+ }
+
+ /**
+ * Parse option and return the name after dash. e.g.,
+ * '--foo' returns 'foo'
+ * '-f' returns 'f'.
+ *
+ * @return string
+ */
+ public function getOptionName()
+ {
+ if (preg_match('/^[-]+([a-zA-Z0-9-]+)/', $this->arg, $regs)) {
+ return $regs[1];
+ }
+ }
+
+ public function splitAsOption()
+ {
+ return explode('=', $this->arg, 2);
+ }
+
+ public function containsOptionValue()
+ {
+ return preg_match('/=.+/', $this->arg);
+ }
+
+ public function getOptionValue()
+ {
+ if (preg_match('/=(.+)/', $this->arg, $regs)) {
+ return $regs[1];
+ }
+
+ return;
+ }
+
+ /**
+ * Check combined short flags for "-abc" or "-vvv".
+ *
+ * like: -abc
+ */
+ public function withExtraFlagOptions()
+ {
+ return preg_match('/^-[a-zA-Z0-9]{2,}/', $this->arg);
+ }
+
+ public function extractExtraFlagOptions()
+ {
+ $args = array();
+ for ($i = 2;$i < strlen($this->arg); ++$i) {
+ $args[] = '-'.$this->arg[$i];
+ }
+ $this->arg = substr($this->arg, 0, 2); # -[a-z]
+ return $args;
+ }
+
+ public function __toString()
+ {
+ return $this->arg;
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/ContinuousOptionParser.php b/vendor/corneltek/getoptionkit/src/ContinuousOptionParser.php
new file mode 100755
index 0000000..fa592fd
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/ContinuousOptionParser.php
@@ -0,0 +1,201 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+use Exception;
+use LogicException;
+use GetOptionKit\Exception\InvalidOptionException;
+use GetOptionKit\Exception\RequireValueException;
+
+/**
+ * A common command line argument format:.
+ *
+ * app.php
+ * [--app-options]
+ *
+ * [subcommand
+ * --subcommand-options]
+ * [subcommand
+ * --subcommand-options]
+ * [subcommand
+ * --subcommand-options]
+ *
+ * [arguments]
+ *
+ * ContinuousOptionParser is for the process flow:
+ *
+ * init app options,
+ * parse app options
+ *
+ *
+ *
+ * while not end
+ * if stop at command
+ * shift command
+ * parse command options
+ * else if stop at arguments
+ * shift arguments
+ * execute current command with the arguments.
+ *
+ * Example code:
+ *
+ *
+ * // subcommand stack
+ * $subcommands = array('subcommand1','subcommand2','subcommand3');
+ *
+ * // different command has its own options
+ * $subcommand_specs = array(
+ * 'subcommand1' => $cmdspecs,
+ * 'subcommand2' => $cmdspecs,
+ * 'subcommand3' => $cmdspecs,
+ * );
+ *
+ * // for saved options
+ * $subcommand_options = array();
+ *
+ * // command arguments
+ * $arguments = array();
+ *
+ * $argv = explode(' ','-v -d -c subcommand1 -a -b -c subcommand2 -c subcommand3 arg1 arg2 arg3');
+ *
+ * // parse application options first
+ * $parser = new ContinuousOptionParser( $appspecs );
+ * $app_options = $parser->parse( $argv );
+ * while (! $parser->isEnd()) {
+ * if( $parser->getCurrentArgument() == $subcommands[0] ) {
+ * $parser->advance();
+ * $subcommand = array_shift( $subcommands );
+ * $parser->setSpecs( $subcommand_specs[$subcommand] );
+ * $subcommand_options[ $subcommand ] = $parser->continueParse();
+ * } else {
+ * $arguments[] = $parser->advance();
+ * }
+ * }
+ **/
+class ContinuousOptionParser extends OptionParser
+{
+ public $index;
+ public $length;
+ public $argv;
+
+ /* for the constructor , the option specs is application options */
+ public function __construct(OptionCollection $specs)
+ {
+ parent::__construct($specs);
+ $this->index = 1;
+ }
+
+ /**
+ * @codeCoverageIgnore
+ */
+ public function startFrom($index)
+ {
+ $this->index = $index;
+ }
+
+ public function isEnd()
+ {
+ # echo "!! {$this->index} >= {$this->length}\n";
+ return $this->index >= $this->length;
+ }
+
+ /**
+ * Return the current argument and advance to the next position.
+ *
+ * @return string
+ */
+ public function advance()
+ {
+ if ($this->index >= $this->length) {
+ throw new LogicException("Argument index out of bounds.");
+ }
+ return $this->argv[$this->index++];
+ }
+
+ /**
+ * Return the current argument that the index pointed to.
+ *
+ * @return string
+ */
+ public function getCurrentArgument()
+ {
+ return $this->argv[$this->index];
+ }
+
+ public function continueParse()
+ {
+ return $this->parse($this->argv);
+ }
+
+
+ protected function fillDefaultValues(OptionCollection $opts, OptionResult $result)
+ {
+ // register option result from options with default value
+ foreach ($opts as $opt) {
+ if ($opt->value === null && $opt->defaultValue !== null) {
+ $opt->setValue($opt->getDefaultValue());
+ $result->set($opt->getId(), $opt);
+ }
+ }
+ }
+
+
+ public function parse(array $argv)
+ {
+ // create new Result object.
+ $result = new OptionResult();
+ list($this->argv, $extra) = $this->preprocessingArguments($argv);
+ $this->length = count($this->argv);
+
+ // from last parse index
+ for (; $this->index < $this->length; ++$this->index) {
+ $arg = new Argument($this->argv[$this->index]);
+
+ /* let the application decide for: command or arguments */
+ if (!$arg->isOption()) {
+ # echo "stop at {$this->index}\n";
+ $this->fillDefaultValues($this->specs, $result);
+ return $result;
+ }
+
+ // if the option is with extra flags,
+ // split it out, and insert into the argv array
+ //
+ // like -abc
+ if ($arg->withExtraFlagOptions()) {
+ $extra = $arg->extractExtraFlagOptions();
+ array_splice($this->argv, $this->index + 1, 0, $extra);
+ $this->argv[$this->index] = $arg->arg; // update argument to current argv list.
+ $this->length = count($this->argv); // update argv list length
+ }
+
+ $next = null;
+ if ($this->index + 1 < count($this->argv)) {
+ $next = new Argument($this->argv[$this->index + 1]);
+ }
+
+ $spec = $this->specs->get($arg->getOptionName());
+ if (!$spec) {
+ throw new InvalidOptionException('Invalid option: '.$arg);
+ }
+
+ // This if block is unnecessary
+ // if ($spec->isRequired() || $spec->isMultiple() || $spec->isOptional() || $spec->isFlag()) {
+ $this->index += $this->consumeOptionToken($spec, $arg, $next);
+ $result->set($spec->getId(), $spec);
+ }
+
+ $this->fillDefaultValues($this->specs, $result);
+
+ return $result;
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionException.php b/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionException.php
new file mode 100755
index 0000000..dea01de
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class InvalidOptionException extends Exception
+{
+}
diff --git a/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionValueException.php b/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionValueException.php
new file mode 100755
index 0000000..fa4b163
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/Exception/InvalidOptionValueException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class InvalidOptionValueException extends Exception
+{
+}
diff --git a/vendor/corneltek/getoptionkit/src/Exception/NonNumericException.php b/vendor/corneltek/getoptionkit/src/Exception/NonNumericException.php
new file mode 100755
index 0000000..757d214
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/Exception/NonNumericException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class NonNumericException extends Exception
+{
+}
diff --git a/vendor/corneltek/getoptionkit/src/Exception/OptionConflictException.php b/vendor/corneltek/getoptionkit/src/Exception/OptionConflictException.php
new file mode 100755
index 0000000..6a3c9e1
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/Exception/OptionConflictException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class OptionConflictException extends Exception
+{
+}
diff --git a/vendor/corneltek/getoptionkit/src/Exception/RequireValueException.php b/vendor/corneltek/getoptionkit/src/Exception/RequireValueException.php
new file mode 100755
index 0000000..c9e69ef
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/Exception/RequireValueException.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit\Exception;
+
+use Exception;
+
+class RequireValueException extends Exception
+{
+}
diff --git a/vendor/corneltek/getoptionkit/src/Option.php b/vendor/corneltek/getoptionkit/src/Option.php
new file mode 100755
index 0000000..f11a93a
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/Option.php
@@ -0,0 +1,557 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+
+use Exception;
+use LogicException;
+use InvalidArgumentException;
+use GetOptionKit\Exception\InvalidOptionValueException;
+
+class Option
+{
+ public $short;
+
+ public $long;
+
+ /**
+ * @var string the description of this option
+ */
+ public $desc;
+
+ /**
+ * @var string The option key
+ */
+ public $key; /* key to store values */
+
+ public $value;
+
+ public $type;
+
+ public $valueName; /* name for the value place holder, for printing */
+
+ public $isa;
+
+ public $isaOption;
+
+ public $validValues;
+
+ public $suggestions;
+
+ public $defaultValue;
+
+ public $incremental = false;
+
+ /**
+ * @var Closure The filter closure of the option value.
+ */
+ public $filter;
+
+ public $validator;
+
+ public $multiple = false;
+
+ public $optional = false;
+
+ public $required = false;
+
+ public $flag = false;
+
+ /**
+ * @var callable trigger callback after value is set.
+ */
+ protected $trigger;
+
+ public function __construct($spec)
+ {
+ $this->initFromSpecString($spec);
+ }
+
+ /**
+ * Build spec attributes from spec string.
+ *
+ * @param string $specString
+ */
+ protected function initFromSpecString($specString)
+ {
+ $pattern = '/
+ (
+ (?:[a-zA-Z0-9-]+)
+ (?:
+ \|
+ (?:[a-zA-Z0-9-]+)
+ )?
+ )
+
+ # option attribute operators
+ ([:+?])?
+
+ # value types
+ (?:=(boolean|string|number|date|file|dir|url|email|ip|ipv6|ipv4))?
+ /x';
+ $ret = preg_match($pattern, $specString, $regs);
+ if ($ret === false || $ret === 0) {
+ throw new Exception('Incorrect spec string');
+ }
+
+ $orig = $regs[0];
+ $name = $regs[1];
+ $attributes = isset($regs[2]) ? $regs[2] : null;
+ $type = isset($regs[3]) ? $regs[3] : null;
+
+ $short = null;
+ $long = null;
+
+ // check long,short option name.
+ if (strpos($name, '|') !== false) {
+ list($short, $long) = explode('|', $name);
+ } else if (strlen($name) === 1) {
+ $short = $name;
+ } else if (strlen($name) > 1) {
+ $long = $name;
+ }
+
+ $this->short = $short;
+ $this->long = $long;
+
+ // option is required.
+ if (strpos($attributes, ':') !== false) {
+ $this->required();
+ } else if (strpos($attributes, '+') !== false) {
+ // option with multiple value
+ $this->multiple();
+ } else if (strpos($attributes, '?') !== false) {
+ // option is optional.(zero or one value)
+ $this->optional();
+ } else {
+ $this->flag();
+ }
+ if ($type) {
+ $this->isa($type);
+ }
+ }
+
+ /*
+ * get the option key for result key mapping.
+ */
+ public function getId()
+ {
+ return $this->key ?: $this->long ?: $this->short;
+ }
+
+ /**
+ * To make -v, -vv, -vvv works.
+ */
+ public function incremental()
+ {
+ $this->incremental = true;
+
+ return $this;
+ }
+
+ public function required()
+ {
+ $this->required = true;
+
+ return $this;
+ }
+
+ /**
+ * Set default value
+ *
+ * @param mixed|Closure $value
+ */
+ public function defaultValue($value)
+ {
+ $this->defaultValue = $value;
+
+ return $this;
+ }
+
+ public function multiple()
+ {
+ $this->multiple = true;
+ $this->value = array(); # for value pushing
+ return $this;
+ }
+
+ public function optional()
+ {
+ $this->optional = true;
+
+ return $this;
+ }
+
+ public function flag()
+ {
+ $this->flag = true;
+
+ return $this;
+ }
+
+ public function trigger(callable $trigger)
+ {
+ $this->trigger = $trigger;
+
+ return $this;
+ }
+
+ public function isIncremental()
+ {
+ return $this->incremental;
+ }
+
+ public function isFlag()
+ {
+ return $this->flag;
+ }
+
+ public function isMultiple()
+ {
+ return $this->multiple;
+ }
+
+ public function isRequired()
+ {
+ return $this->required;
+ }
+
+ public function isOptional()
+ {
+ return $this->optional;
+ }
+
+ public function isTypeNumber()
+ {
+ return $this->isa == 'number';
+ }
+
+ public function isType($type)
+ {
+ return $this->isa === $type;
+ }
+
+ public function getTypeClass()
+ {
+ $class = 'GetOptionKit\\ValueType\\'.ucfirst($this->isa).'Type';
+ if (class_exists($class, true)) {
+ return new $class($this->isaOption);
+ }
+ throw new Exception("Type class '$class' not found.");
+ }
+
+ public function testValue($value)
+ {
+ $type = $this->getTypeClass();
+ return $type->test($value);
+ }
+
+ protected function _preprocessValue($value)
+ {
+ $val = $value;
+
+ if ($isa = ucfirst($this->isa)) {
+ $type = $this->getTypeClass();
+ if ($type->test($value)) {
+ $val = $type->parse($value);
+ } else {
+ if (strtolower($isa) === 'regex') {
+ $isa .= '('.$this->isaOption.')';
+ }
+ throw new InvalidOptionValueException("Invalid value for {$this->renderReadableSpec(false)}. Requires a type $isa.");
+ }
+ }
+
+ // check pre-filter for option value
+ if ($this->filter) {
+ $val = call_user_func($this->filter, $val);
+ }
+
+ // check validValues
+ if ($validValues = $this->getValidValues()) {
+ if (!in_array($value, $validValues)) {
+ throw new InvalidOptionValueException('valid values are: '.implode(', ', $validValues));
+ }
+ }
+
+ if (!$this->validate($value)[0]) {
+ throw new InvalidOptionValueException('option is invalid');
+ }
+
+ return $val;
+ }
+
+ protected function callTrigger()
+ {
+ if ($this->trigger) {
+ if ($ret = call_user_func($this->trigger, $this->value)) {
+ $this->value = $ret;
+ }
+ }
+ }
+
+ /*
+ * set option value
+ */
+ public function setValue($value)
+ {
+ $this->value = $this->_preprocessValue($value);
+ $this->callTrigger();
+ }
+
+ /**
+ * This method is for incremental option.
+ */
+ public function increaseValue()
+ {
+ if (!$this->value) {
+ $this->value = 1;
+ } else {
+ ++$this->value;
+ }
+ $this->callTrigger();
+ }
+
+ /**
+ * push option value, when the option accept multiple values.
+ *
+ * @param mixed
+ */
+ public function pushValue($value)
+ {
+ $value = $this->_preprocessValue($value);
+ $this->value[] = $value;
+ $this->callTrigger();
+ }
+
+ public function desc($desc)
+ {
+ $this->desc = $desc;
+ }
+
+ /**
+ * valueName is for option value hinting:.
+ *
+ * --name=
+ */
+ public function valueName($name)
+ {
+ $this->valueName = $name;
+
+ return $this;
+ }
+
+ public function renderValueHint()
+ {
+ $n = null;
+ if ($this->valueName) {
+ $n = $this->valueName;
+ } else if ($values = $this->getValidValues()) {
+ $n = '('.implode(',', $values).')';
+ } else if ($values = $this->getSuggestions()) {
+ $n = '['.implode(',', $values).']';
+ } else if ($val = $this->getDefaultValue()) {
+ // This allows for `0` and `false` values to be displayed also.
+ if ((is_scalar($val) && strlen((string) $val)) || is_bool($val)) {
+ if (is_bool($val)) {
+ $n = ($val ? 'true' : 'false');
+ } else {
+ $n = $val;
+ }
+ }
+ }
+
+ if (!$n && $this->isa !== null) {
+ $n = '<'.$this->isa.'>';
+ }
+ if ($this->isRequired()) {
+ return sprintf('=%s', $n);
+ } else if ($this->isOptional() || $this->defaultValue) {
+ return sprintf('[=%s]', $n);
+ } else if ($n) {
+ return '='.$n;
+ }
+
+ return '';
+ }
+
+ public function getDefaultValue()
+ {
+ if (is_callable($this->defaultValue)) {
+ return $this->defaultValue;
+ }
+
+ return $this->defaultValue;
+ }
+
+ public function getValue()
+ {
+ if (null !== $this->value) {
+ if (is_callable($this->value)) {
+ return call_user_func($this->value);
+ }
+ return $this->value;
+ }
+
+ return $this->getDefaultValue();
+ }
+
+ /**
+ * get readable spec for printing.
+ *
+ * @param string $renderHint render also value hint
+ */
+ public function renderReadableSpec($renderHint = true)
+ {
+ $c1 = '';
+ if ($this->short && $this->long) {
+ $c1 = sprintf('-%s, --%s', $this->short, $this->long);
+ } else if ($this->short) {
+ $c1 = sprintf('-%s', $this->short);
+ } else if ($this->long) {
+ $c1 = sprintf('--%s', $this->long);
+ }
+ if ($renderHint) {
+ return $c1.$this->renderValueHint();
+ }
+
+ return $c1;
+ }
+
+ public function __toString()
+ {
+ $c1 = $this->renderReadableSpec();
+ $return = '';
+ $return .= sprintf('* key:%-8s spec:%s desc:%s', $this->getId(), $c1, $this->desc)."\n";
+ $val = $this->getValue();
+ if (is_array($val)) {
+ $return .= ' value => ' . join(',', array_map(function($v) { return var_export($v, true); }, $val))."\n";
+ } else {
+ $return .= sprintf(' value => %s', $val)."\n";
+ }
+
+ return $return;
+ }
+
+ /**
+ * Value Type Setters.
+ *
+ * @param string $type the value type, valid values are 'number', 'string',
+ * 'file', 'boolean', you can also use your own value type name.
+ * @param mixed $option option(s) for value type class (optionnal)
+ */
+ public function isa($type, $option = null)
+ {
+ // "bool" was kept for backward compatibility
+ if ($type === 'bool') {
+ $type = 'boolean';
+ }
+ $this->isa = $type;
+ $this->isaOption = $option;
+
+ return $this;
+ }
+
+ /**
+ * Assign validValues to member value.
+ */
+ public function validValues($values)
+ {
+ $this->validValues = $values;
+
+ return $this;
+ }
+
+ /**
+ * Assign suggestions.
+ *
+ * @param Closure|array
+ */
+ public function suggestions($suggestions)
+ {
+ $this->suggestions = $suggestions;
+
+ return $this;
+ }
+
+ /**
+ * Return valud values array.
+ *
+ * @return string[] or nil
+ */
+ public function getValidValues()
+ {
+ if ($this->validValues) {
+ if (is_callable($this->validValues)) {
+ return call_user_func($this->validValues);
+ }
+
+ return $this->validValues;
+ }
+
+ return;
+ }
+
+ /**
+ * Return suggestions.
+ *
+ * @return string[] or nil
+ */
+ public function getSuggestions()
+ {
+ if ($this->suggestions) {
+ if (is_callable($this->suggestions)) {
+ return call_user_func($this->suggestions);
+ }
+
+ return $this->suggestions;
+ }
+
+ return;
+ }
+
+ public function validate($value)
+ {
+ if ($this->validator) {
+ $ret = call_user_func($this->validator, $value);
+ if (is_array($ret)) {
+ return $ret;
+ } else if ($ret === false) {
+ return array(false, "Invalid value: $value");
+ } else if ($ret === true) {
+ return array(true, 'Successfully validated.');
+ }
+ throw new InvalidArgumentException('Invalid return value from the validator.');
+ }
+
+ return array(true);
+ }
+
+ public function validator($cb)
+ {
+ $this->validator = $cb;
+
+ return $this;
+ }
+
+ /**
+ * Set up a filter function for the option value.
+ *
+ * todo: add "callable" type hint later.
+ */
+ public function filter($cb)
+ {
+ $this->filter = $cb;
+
+ return $this;
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/OptionCollection.php b/vendor/corneltek/getoptionkit/src/OptionCollection.php
new file mode 100755
index 0000000..22cb638
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/OptionCollection.php
@@ -0,0 +1,207 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+
+use ArrayIterator;
+use IteratorAggregate;
+use Countable;
+use Exception;
+use LogicException;
+use GetOptionKit\Exception\OptionConflictException;
+
+class OptionCollection
+ implements IteratorAggregate, Countable
+{
+ public $data = array();
+
+ /**
+ * @var Option[string]
+ *
+ * read-only property
+ */
+ public $longOptions = array();
+
+ /**
+ * @var Option[string]
+ *
+ * read-only property
+ */
+ public $shortOptions = array();
+
+ /**
+ * @var Option[]
+ *
+ * read-only property
+ */
+ public $options = array();
+
+ public function __construct()
+ {
+ $this->data = array();
+ }
+
+ public function __clone()
+ {
+ foreach ($this->data as $k => $v) {
+ $this->data[ $k ] = clone $v;
+ }
+ foreach ($this->longOptions as $k => $v) {
+ $this->longOptions[ $k ] = clone $v;
+ }
+ foreach ($this->shortOptions as $k => $v) {
+ $this->shortOptions[ $k ] = clone $v;
+ }
+ foreach ($this->options as $k => $v) {
+ $this->options[ $k ] = clone $v;
+ }
+ }
+
+ /**
+ * add( [spec string], [desc string] ).
+ *
+ * add( [option object] )
+ */
+ public function add()
+ {
+ $num = func_num_args();
+ $args = func_get_args();
+ $first = $args[0];
+
+ if ($first instanceof Option) {
+
+ $this->addOption($first);
+
+ } else if (is_string($first)) {
+
+ $specString = $args[0];
+ $desc = isset($args[1]) ? $args[1] : null;
+ $key = isset($args[2]) ? $args[2] : null;
+
+ // parse spec string
+ $spec = new Option($specString);
+ if ($desc) {
+ $spec->desc($desc);
+ }
+ if ($key) {
+ $spec->key = $key;
+ }
+ $this->addOption($spec);
+ return $spec;
+
+ } else {
+
+ throw new LogicException('Unknown Spec Type');
+
+ }
+ }
+
+ /**
+ * Add option object.
+ *
+ * @param object $spec the option object.
+ */
+ public function addOption(Option $spec)
+ {
+ $this->data[$spec->getId()] = $spec;
+ if ($spec->long) {
+ if (isset($this->longOptions[$spec->long])) {
+ throw new OptionConflictException('Option conflict: --'.$spec->long.' is already defined.');
+ }
+ $this->longOptions[$spec->long] = $spec;
+ }
+ if ($spec->short) {
+ if (isset($this->shortOptions[$spec->short])) {
+ throw new OptionConflictException('Option conflict: -'.$spec->short.' is already defined.');
+ }
+ $this->shortOptions[$spec->short] = $spec;
+ }
+ $this->options[] = $spec;
+ if (!$spec->long && !$spec->short) {
+ throw new Exception('Neither long option name nor short name is not given.');
+ }
+ }
+
+ public function getLongOption($name)
+ {
+ return isset($this->longOptions[ $name ]) ? $this->longOptions[ $name ] : null;
+ }
+
+ public function getShortOption($name)
+ {
+ return isset($this->shortOptions[ $name ]) ? $this->shortOptions[ $name ] : null;
+ }
+
+ /* Get spec by spec id */
+ public function get($id)
+ {
+ if (isset($this->data[$id])) {
+ return $this->data[$id];
+ } else if (isset($this->longOptions[$id])) {
+ return $this->longOptions[$id];
+ } else if (isset($this->shortOptions[$id])) {
+ return $this->shortOptions[$id];
+ }
+ }
+
+ public function find($name)
+ {
+ foreach ($this->options as $option) {
+ if ($option->short === $name || $option->long === $name) {
+ return $option;
+ }
+ }
+ }
+
+ public function size()
+ {
+ return count($this->data);
+ }
+
+ public function all()
+ {
+ return $this->data;
+ }
+
+ public function toArray()
+ {
+ $array = array();
+ foreach ($this->data as $k => $spec) {
+ $item = array();
+ if ($spec->long) {
+ $item['long'] = $spec->long;
+ }
+ if ($spec->short) {
+ $item['short'] = $spec->short;
+ }
+ $item['desc'] = $spec->desc;
+ $array[] = $item;
+ }
+
+ return $array;
+ }
+
+ public function keys()
+ {
+ return array_merge(array_keys($this->longOptions), array_keys($this->shortOptions));
+ }
+
+ public function count()
+ {
+ return count($this->data);
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->data);
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/OptionParser.php b/vendor/corneltek/getoptionkit/src/OptionParser.php
new file mode 100755
index 0000000..da9cba5
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/OptionParser.php
@@ -0,0 +1,193 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+use Exception;
+use GetOptionKit\Exception\InvalidOptionException;
+use GetOptionKit\Exception\RequireValueException;
+
+class OptionParser
+{
+ public $specs;
+ public $longOptions;
+ public $shortOptions;
+
+ public function __construct(OptionCollection $specs)
+ {
+ $this->specs = $specs;
+ }
+
+ public function setSpecs(OptionCollection $specs)
+ {
+ $this->specs = $specs;
+ }
+
+ /**
+ * consume option value from current argument or from the next argument
+ *
+ * @return boolean next token consumed?
+ */
+ protected function consumeOptionToken(Option $spec, $arg, $next, & $success = false)
+ {
+ // Check options doesn't require next token before
+ // all options that require values.
+ if ($spec->isFlag()) {
+
+ if ($spec->isIncremental()) {
+ $spec->increaseValue();
+ } else {
+ $spec->setValue(true);
+ }
+ return 0;
+
+ } else if ($spec->isRequired()) {
+
+ if ($next && !$next->isEmpty() && !$next->anyOfOptions($this->specs)) {
+ $spec->setValue($next->arg);
+ return 1;
+ } else {
+ throw new RequireValueException("Option '{$arg->getOptionName()}' requires a value.");
+ }
+
+ } else if ($spec->isMultiple()) {
+
+ if ($next && !$next->isEmpty() && !$next->anyOfOptions($this->specs)) {
+ $this->pushOptionValue($spec, $arg, $next);
+ return 1;
+ }
+
+ } else if ($spec->isOptional() && $next && !$next->isEmpty() && !$next->anyOfOptions($this->specs)) {
+
+ $spec->setValue($next->arg);
+ return 1;
+
+ }
+ return 0;
+ }
+
+ /*
+ * push value to multipl value option
+ */
+ protected function pushOptionValue(Option $spec, $arg, $next)
+ {
+ if ($next && !$next->anyOfOptions($this->specs)) {
+ $spec->pushValue($next->arg);
+ }
+ }
+
+ /**
+ * preprocess the argv array
+ *
+ * - split option and option value
+ * - separate arguments after "--"
+ */
+ protected function preprocessingArguments(array $argv)
+ {
+ // preprocessing arguments
+ $newArgv = array();
+ $extra = array();
+ $afterDash = false;
+ foreach ($argv as $arg) {
+ if ($arg === '--') {
+ $afterDash = true;
+ continue;
+ }
+ if ($afterDash) {
+ $extra[] = $arg;
+ continue;
+ }
+
+ $a = new Argument($arg);
+ if ($a->anyOfOptions($this->specs) && $a->containsOptionValue()) {
+ list($opt, $val) = $a->splitAsOption();
+ array_push($newArgv, $opt, $val);
+ } else {
+ $newArgv[] = $arg;
+ }
+ }
+ return array($newArgv, $extra);
+ }
+
+ protected function fillDefaultValues(OptionCollection $opts, OptionResult $result)
+ {
+ // register option result from options with default value
+ foreach ($opts as $opt) {
+ if ($opt->value === null && $opt->defaultValue !== null) {
+ $opt->setValue($opt->getDefaultValue());
+ $result->set($opt->getId(), $opt);
+ }
+ }
+ }
+
+ /**
+ * @param array $argv
+ *
+ * @return OptionResult|Option[]
+ *
+ * @throws Exception\RequireValueException
+ * @throws Exception\InvalidOptionException
+ * @throws \Exception
+ */
+ public function parse(array $argv)
+ {
+ $result = new OptionResult();
+
+ list($argv, $extra) = $this->preprocessingArguments($argv);
+
+ $len = count($argv);
+
+ // some people might still pass only the option names here.
+ $first = new Argument($argv[0]);
+ if ($first->isOption()) {
+ throw new Exception('parse(argv) expects the first argument to be the program name.');
+ }
+
+ for ($i = 1; $i < $len; ++$i) {
+ $arg = new Argument($argv[$i]);
+
+ // if looks like not an option, push it to argument list.
+ // TODO: we might want to support argument with preceding dash (?)
+ if (!$arg->isOption()) {
+ $result->addArgument($arg);
+ continue;
+ }
+
+ // if the option is with extra flags,
+ // split the string, and insert into the argv array
+ if ($arg->withExtraFlagOptions()) {
+ $extra = $arg->extractExtraFlagOptions();
+ array_splice($argv, $i + 1, 0, $extra);
+ $argv[$i] = $arg->arg; // update argument to current argv list.
+ $len = count($argv); // update argv list length
+ }
+
+ $next = null;
+ if ($i + 1 < count($argv)) {
+ $next = new Argument($argv[$i + 1]);
+ }
+
+ $spec = $this->specs->get($arg->getOptionName());
+ if (!$spec) {
+ throw new InvalidOptionException('Invalid option: '.$arg);
+ }
+
+ // This if expr might be unnecessary, becase we have default mode - flag
+ // if ($spec->isRequired() || $spec->isMultiple() || $spec->isOptional() || $spec->isFlag()) {
+ $i += $this->consumeOptionToken($spec, $arg, $next);
+ $result->set($spec->getId(), $spec);
+ }
+
+ $this->fillDefaultValues($this->specs, $result);
+
+ return $result;
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/OptionPrinter/ConsoleOptionPrinter.php b/vendor/corneltek/getoptionkit/src/OptionPrinter/ConsoleOptionPrinter.php
new file mode 100755
index 0000000..dba0a50
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/OptionPrinter/ConsoleOptionPrinter.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace GetOptionKit\OptionPrinter;
+
+use GetOptionKit\OptionCollection;
+use GetOptionKit\Option;
+
+class ConsoleOptionPrinter implements OptionPrinter
+{
+ public $screenWidth = 78;
+
+ /**
+ * Render readable spec.
+ */
+ public function renderOption(Option $opt)
+ {
+ $c1 = '';
+ if ($opt->short && $opt->long) {
+ $c1 = sprintf('-%s, --%s', $opt->short, $opt->long);
+ } else if ($opt->short) {
+ $c1 = sprintf('-%s', $opt->short);
+ } else if ($opt->long) {
+ $c1 = sprintf('--%s', $opt->long);
+ }
+ $c1 .= $opt->renderValueHint();
+
+ return $c1;
+ }
+
+ /**
+ * render option descriptions.
+ *
+ * @return string output
+ */
+ public function render(OptionCollection $options)
+ {
+ # echo "* Available options:\n";
+ $lines = array();
+ foreach ($options as $option) {
+ $c1 = $this->renderOption($option);
+ $lines[] = "\t".$c1;
+ $lines[] = wordwrap("\t\t".$option->desc, $this->screenWidth, "\n\t\t"); # wrap text
+ $lines[] = '';
+ }
+
+ return implode("\n", $lines);
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/OptionPrinter/OptionPrinter.php b/vendor/corneltek/getoptionkit/src/OptionPrinter/OptionPrinter.php
new file mode 100755
index 0000000..4ed0c70
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/OptionPrinter/OptionPrinter.php
@@ -0,0 +1,12 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace GetOptionKit;
+
+use ArrayIterator;
+use ArrayAccess;
+use IteratorAggregate;
+use Countable;
+
+/**
+ * Define the getopt parsing result.
+ *
+ * create option result from array()
+ *
+ * OptionResult::create($spec, array(
+ * 'key' => 'value'
+ * ), array( ... arguments ... ) );
+ */
+class OptionResult
+ implements IteratorAggregate, ArrayAccess, Countable
+{
+ /**
+ * @var array option specs, key => Option object
+ * */
+ public $keys = array();
+
+ private $currentKey;
+
+ /* arguments */
+ public $arguments = array();
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->keys);
+ }
+
+ public function count()
+ {
+ return count($this->keys);
+ }
+
+ public function merge(OptionResult $a)
+ {
+ $this->keys = array_merge($this->keys, $a->keys);
+ $this->arguments = array_merge($this->arguments, $a->arguments);
+ }
+
+ public function __isset($key)
+ {
+ return isset($this->keys[$key]);
+ }
+
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ public function get($key)
+ {
+ if (isset($this->keys[$key])) {
+ return $this->keys[$key]->getValue();
+ }
+
+ // verifying if we got a camelCased key: http://stackoverflow.com/a/7599674/102960
+ // get $options->baseDir as $option->{'base-dir'}
+ $parts = preg_split('/(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/', $key);
+ if (sizeof($parts) > 1) {
+ $key = implode('-', array_map('strtolower', $parts));
+ }
+ if (isset($this->keys[$key])) {
+ return $this->keys[$key]->getValue();
+ }
+ }
+
+ public function __set($key, $value)
+ {
+ $this->keys[ $key ] = $value;
+ }
+
+ public function has($key)
+ {
+ return isset($this->keys[ $key ]);
+ }
+
+ public function set($key, Option $value)
+ {
+ $this->keys[ $key ] = $value;
+ }
+
+ public function addArgument(Argument $arg)
+ {
+ $this->arguments[] = $arg;
+ }
+
+ public function getArguments()
+ {
+ return array_map(function ($e) { return $e->__toString(); }, $this->arguments);
+ }
+
+ public function offsetSet($name, $value)
+ {
+ $this->keys[ $name ] = $value;
+ }
+
+ public function offsetExists($name)
+ {
+ return isset($this->keys[ $name ]);
+ }
+
+ public function offsetGet($name)
+ {
+ return $this->keys[ $name ];
+ }
+
+ public function offsetUnset($name)
+ {
+ unset($this->keys[$name]);
+ }
+
+ public function toArray()
+ {
+ $array = array();
+ foreach ($this->keys as $key => $option) {
+ $array[ $key ] = $option->getValue();
+ }
+
+ return $array;
+ }
+
+ public static function create($specs, array $values = array(), array $arguments = null)
+ {
+ $new = new self();
+ foreach ($specs as $spec) {
+ $id = $spec->getId();
+ if (isset($values[$id])) {
+ $new->$id = $spec;
+ $spec->setValue($values[$id]);
+ }
+ if ($arguments) {
+ foreach ($arguments as $arg) {
+ $new->addArgument(new Argument($arg));
+ }
+ }
+ }
+
+ return $new;
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/ValueType/BaseType.php b/vendor/corneltek/getoptionkit/src/ValueType/BaseType.php
new file mode 100755
index 0000000..98477bf
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/ValueType/BaseType.php
@@ -0,0 +1,34 @@
+option = $option;
+ }
+ }
+
+ /**
+ * Test a value to see if it fit the type.
+ *
+ * @param mixed $value
+ */
+ abstract public function test($value);
+
+ /**
+ * Parse a string value into it's type value.
+ *
+ * @param mixed $value
+ */
+ abstract public function parse($value);
+}
diff --git a/vendor/corneltek/getoptionkit/src/ValueType/BoolType.php b/vendor/corneltek/getoptionkit/src/ValueType/BoolType.php
new file mode 100755
index 0000000..e2922d8
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/ValueType/BoolType.php
@@ -0,0 +1,10 @@
+ DateTime::ATOM,
+ );
+
+ public function test($value)
+ {
+ return DateTime::createFromFormat($this->option['format'], $value) !== false;
+ }
+
+ public function parse($value)
+ {
+ return DateTime::createFromFormat($this->option['format'], $value);
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/ValueType/DateType.php b/vendor/corneltek/getoptionkit/src/ValueType/DateType.php
new file mode 100755
index 0000000..d168ef4
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/ValueType/DateType.php
@@ -0,0 +1,20 @@
+ 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public function parse($value)
+ {
+ return date_parse($value);
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/ValueType/DirType.php b/vendor/corneltek/getoptionkit/src/ValueType/DirType.php
new file mode 100755
index 0000000..de28279
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/ValueType/DirType.php
@@ -0,0 +1,18 @@
+option = $option;
+ }
+
+ public function test($value)
+ {
+ return preg_match($this->option, $value) !== 0;
+ }
+
+ public function parse($value)
+ {
+ preg_match($this->option, $value, $this->matches);
+ return strval($value);
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/src/ValueType/StringType.php b/vendor/corneltek/getoptionkit/src/ValueType/StringType.php
new file mode 100755
index 0000000..cf41b7b
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/src/ValueType/StringType.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+use GetOptionKit\Argument;
+class ArgumentTest extends \PHPUnit\Framework\TestCase
+{
+ function test()
+ {
+ $arg = new Argument( '--option' );
+ $this->assertTrue( $arg->isLongOption() );
+ $this->assertFalse( $arg->isShortOption() );
+ $this->assertEquals('option' , $arg->getOptionName());
+
+ $this->assertEquals(null, $arg->getOptionValue());
+ }
+
+ function test2()
+ {
+ $arg = new Argument('--option=value');
+ $this->assertNotNull( $arg->containsOptionValue() );
+ $this->assertEquals('value' , $arg->getOptionValue());
+ $this->assertEquals('option' , $arg->getOptionName());
+ }
+
+ function test3()
+ {
+ $arg = new Argument( '-abc' );
+ $this->assertNotNull( $arg->withExtraFlagOptions() );
+
+ $args = $arg->extractExtraFlagOptions();
+ $this->assertNotNull( $args );
+ $this->assertCount( 2, $args );
+
+ $this->assertEquals( '-b', $args[0] );
+ $this->assertEquals( '-c', $args[1] );
+ $this->assertEquals( '-a', $arg->arg);
+ }
+
+ function testZeroValue()
+ {
+ $arg = new Argument( '0' );
+ $this->assertFalse( $arg->isShortOption() );
+ $this->assertFalse( $arg->isLongOption() );
+ $this->assertFalse( $arg->isEmpty() );
+ }
+}
+
+
diff --git a/vendor/corneltek/getoptionkit/tests/ContinuousOptionParserTest.php b/vendor/corneltek/getoptionkit/tests/ContinuousOptionParserTest.php
new file mode 100755
index 0000000..b1c2ad1
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/tests/ContinuousOptionParserTest.php
@@ -0,0 +1,335 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace tests\GetOptionKit;
+use GetOptionKit\ContinuousOptionParser;
+use GetOptionKit\OptionCollection;
+
+class ContinuousOptionParserTest extends \PHPUnit\Framework\TestCase
+{
+
+ public function testOptionCollection()
+ {
+ $specs = new OptionCollection;
+ $specVerbose = $specs->add('v|verbose');
+ $specColor = $specs->add('c|color');
+ $specDebug = $specs->add('d|debug');
+ }
+
+
+
+ public function argumentProvider()
+ {
+ return [
+ [
+ ['program','subcommand1', 'arg1', 'arg2', 'arg3', 'subcommand2', '-b', 1, 'subcommand3', '-b', 2],
+ [
+ 'args' => ['arg1', 'arg2', 'arg3']
+ ],
+ ],
+ [
+ ['program','-v', '-c', 'subcommand1', '--as', 99, 'arg1', 'arg2', 'arg3'],
+ [
+ 'app' => ['verbose' => true ],
+ 'args' => ['arg1', 'arg2', 'arg3']
+ ],
+ ],
+ [
+ ['program','-v', '-c', 'subcommand1', '--as', 99, 'arg1', 'arg2', 'arg3', '--','zz','xx','vv'],
+ [
+ 'app' => ['verbose' => true],
+ 'args' => ['arg1', 'arg2', 'arg3']
+ ],
+ ],
+ ];
+ }
+
+
+
+ /**
+ * @dataProvider argumentProvider
+ */
+ public function testParseSubCommandOptions($argv, $expected)
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('as:');
+ $cmdspecs->add('b:');
+ $cmdspecs->add('c:');
+ $cmdspecs->add('def:')->isa('number')->defaultValue(3);
+
+ $parser = new ContinuousOptionParser( $appspecs );
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => clone $cmdspecs,
+ 'subcommand2' => clone $cmdspecs,
+ 'subcommand3' => clone $cmdspecs,
+ );
+ $subcommand_options = array();
+
+ // $argv = explode(' ','program -v -c subcommand1 --as 99 arg1 arg2 arg3 -- zz xx vv');
+ // $argv = explode(' ','program subcommand1 -a 1 subcommand2 -a 2 subcommand3 -a 3 arg1 arg2 arg3');
+ $app_options = $parser->parse( $argv );
+ $arguments = array();
+ while (! $parser->isEnd()) {
+ if (!empty($subcommands) && $parser->getCurrentArgument() == $subcommands[0]) {
+ $parser->advance();
+ $subcommand = array_shift($subcommands);
+ $parser->setSpecs($subcommand_specs[$subcommand]);
+ $subcommand_options[$subcommand] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+ $this->assertSame($expected['args'], $arguments);
+ if (isset($expected['app'])) {
+ foreach ($expected['app'] as $k => $v) {
+ $this->assertEquals($v, $app_options->get($k));
+ }
+ }
+
+ // $this->assertEquals(99, $subcommand_options['subcommand1']->as);
+ }
+
+
+
+ public function testParser3()
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('n|name:=string');
+ $cmdspecs->add('p|phone:=string');
+ $cmdspecs->add('a|address:=string');
+
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => $cmdspecs,
+ 'subcommand2' => $cmdspecs,
+ 'subcommand3' => $cmdspecs,
+ );
+ $subcommand_options = array();
+ $arguments = array();
+
+ $argv = explode(' ','program -v -d -c subcommand1 --name=c9s --phone=123123123 --address=somewhere arg1 arg2 arg3');
+ $parser = new ContinuousOptionParser( $appspecs );
+ $app_options = $parser->parse( $argv );
+ while (! $parser->isEnd()) {
+ if (@$subcommands[0] && $parser->getCurrentArgument() == $subcommands[0]) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs( $subcommand_specs[$subcommand] );
+ $subcommand_options[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+
+ $this->assertCount(3, $arguments);
+ $this->assertEquals('arg1', $arguments[0]);
+ $this->assertEquals('arg2', $arguments[1]);
+ $this->assertEquals('arg3', $arguments[2]);
+
+ $this->assertNotNull($subcommand_options['subcommand1']);
+ $this->assertEquals('c9s', $subcommand_options['subcommand1']->name );
+ $this->assertEquals('123123123', $subcommand_options['subcommand1']->phone );
+ $this->assertEquals('somewhere', $subcommand_options['subcommand1']->address );
+ }
+
+
+ /* test parser without options */
+ function testParser4()
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('a:'); // required
+ $cmdspecs->add('b?'); // optional
+ $cmdspecs->add('c+'); // multiple (required)
+
+
+
+ $parser = new ContinuousOptionParser( $appspecs );
+ $this->assertNotNull( $parser );
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => clone $cmdspecs,
+ 'subcommand2' => clone $cmdspecs,
+ 'subcommand3' => clone $cmdspecs,
+ );
+ $subcommand_options = array();
+
+ $argv = explode(' ','program subcommand1 subcommand2 subcommand3 -a a -b b -c c');
+ $app_options = $parser->parse( $argv );
+ $arguments = array();
+ while( ! $parser->isEnd() ) {
+ if( @$subcommands[0] && $parser->getCurrentArgument() == $subcommands[0] ) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs( $subcommand_specs[$subcommand] );
+ $subcommand_options[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+
+ $this->assertNotNull( $subcommand_options );
+ $this->assertNotNull( $subcommand_options['subcommand1'] );
+ $this->assertNotNull( $subcommand_options['subcommand2'] );
+ $this->assertNotNull( $subcommand_options['subcommand3'] );
+
+ $r = $subcommand_options['subcommand3'];
+ $this->assertNotNull( $r );
+
+
+
+ $this->assertNotNull( $r->a , 'option a' );
+ $this->assertNotNull( $r->b , 'option b' );
+ $this->assertNotNull( $r->c , 'option c' );
+
+ $this->assertEquals( 'a', $r->a );
+ $this->assertEquals( 'b', $r->b );
+ $this->assertEquals( 'c', $r->c[0] );
+ }
+
+ /* test parser without options */
+ function testParser5()
+ {
+ $appspecs = new OptionCollection;
+ $appspecs->add('v|verbose');
+ $appspecs->add('c|color');
+ $appspecs->add('d|debug');
+
+ $cmdspecs = new OptionCollection;
+ $cmdspecs->add('a:');
+ $cmdspecs->add('b');
+ $cmdspecs->add('c');
+
+ $parser = new ContinuousOptionParser( $appspecs );
+ $this->assertNotNull( $parser );
+
+ $subcommands = array('subcommand1','subcommand2','subcommand3');
+ $subcommand_specs = array(
+ 'subcommand1' => clone $cmdspecs,
+ 'subcommand2' => clone $cmdspecs,
+ 'subcommand3' => clone $cmdspecs,
+ );
+ $subcommand_options = array();
+
+ $argv = explode(' ','program subcommand1 -a 1 subcommand2 -a 2 subcommand3 -a 3 arg1 arg2 arg3');
+ $app_options = $parser->parse( $argv );
+ $arguments = array();
+ while (! $parser->isEnd()) {
+ if (!empty($subcommands) && $parser->getCurrentArgument() == $subcommands[0] ) {
+ $parser->advance();
+ $subcommand = array_shift( $subcommands );
+ $parser->setSpecs($subcommand_specs[$subcommand]);
+ $subcommand_options[ $subcommand ] = $parser->continueParse();
+ } else {
+ $arguments[] = $parser->advance();
+ }
+ }
+
+ $this->assertEquals( 'arg1', $arguments[0] );
+ $this->assertEquals( 'arg2', $arguments[1] );
+ $this->assertEquals( 'arg3', $arguments[2] );
+ $this->assertNotNull( $subcommand_options );
+
+ $this->assertEquals(1, $subcommand_options['subcommand1']->a);
+ $this->assertNotNull( 2, $subcommand_options['subcommand2']->a );
+ $this->assertNotNull( 3, $subcommand_options['subcommand3']->a );
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionException
+ */
+ public function testParseInvalidOptionException()
+ {
+ $parser = new ContinuousOptionParser(new OptionCollection);
+ $parser->parse(array('app','--foo'));
+ $arguments = array();
+ while (!$parser->isEnd())
+ {
+ $arguments[] = $parser->getCurrentArgument();
+ $parser->advance();
+ }
+ }
+
+
+
+ public function testMultipleShortOption()
+ {
+ $options = new OptionCollection;
+ $options->add("a");
+ $options->add("b");
+ $options->add("c");
+
+ $parser = new ContinuousOptionParser($options);
+
+ $result = $parser->parse(array('app', '-ab', 'foo', 'bar'));
+ while (!$parser->isEnd())
+ {
+ $arguments[] = $parser->getCurrentArgument();
+ $parser->advance();
+ }
+
+ $this->assertTrue($result->keys["a"]->value);
+ $this->assertTrue($result->keys["b"]->value);
+ }
+
+ public function testIncrementalValue()
+ {
+ $options = new OptionCollection;
+ $options->add("v|verbose")->incremental();
+ $parser = new ContinuousOptionParser($options);
+ $result = $parser->parse(array('app', '-vvv'));
+ $this->assertEquals(3, $result->keys["verbose"]->value);
+ }
+
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionException
+ */
+ public function testUnknownOption()
+ {
+ $options = new OptionCollection;
+ $options->add("v|verbose");
+ $parser = new ContinuousOptionParser($options);
+ $result = $parser->parse(array('app', '-b'));
+ }
+
+ /**
+ * @expectedException LogicException
+ */
+ public function testAdvancedOutOfBounds()
+ {
+ $options = new OptionCollection;
+ $options->add("v|verbose");
+ $parser = new ContinuousOptionParser($options);
+ $result = $parser->parse(array('app', '-v'));
+ $parser->advance();
+ }
+
+}
+
diff --git a/vendor/corneltek/getoptionkit/tests/OptionCollectionTest.php b/vendor/corneltek/getoptionkit/tests/OptionCollectionTest.php
new file mode 100755
index 0000000..542b293
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/tests/OptionCollectionTest.php
@@ -0,0 +1,46 @@
+add($o = new Option('v|verbose'));
+ $this->assertSame($o, $opts->getLongOption('verbose'));
+ $this->assertSame($o, $opts->getShortOption('v'));
+ }
+
+
+ /**
+ * @expectedException LogicException
+ */
+ public function testAddInvalidOption()
+ {
+ $opts = new OptionCollection;
+ $opts->add(123);
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\OptionConflictException
+ */
+ public function testOptionConflictShort()
+ {
+ $opts = new OptionCollection;
+ $opts->add('r|repeat');
+ $opts->add('t|time');
+ $opts->add('r|regex');
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\OptionConflictException
+ */
+ public function testOptionConflictLong()
+ {
+ $opts = new OptionCollection;
+ $opts->add('r|repeat');
+ $opts->add('t|time');
+ $opts->add('c|repeat');
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/tests/OptionParserTest.php b/vendor/corneltek/getoptionkit/tests/OptionParserTest.php
new file mode 100755
index 0000000..5d5a9b9
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/tests/OptionParserTest.php
@@ -0,0 +1,477 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+use GetOptionKit\InvalidOptionValue;
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\Option;
+
+class OptionParserTest extends \PHPUnit\Framework\TestCase
+{
+ public $parser;
+ public $specs;
+
+ public function setUp()
+ {
+ $this->specs = new OptionCollection;
+ $this->parser = new OptionParser($this->specs);
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testInvalidOption()
+ {
+ $options = new OptionCollection;
+ $options->addOption(new Option(0));
+ }
+
+
+ public function testResultArrayAccessor()
+ {
+ $options = new OptionCollection;
+ $options->add('n|nice:' , 'I take negative value');
+ $parser = new OptionParser($options);
+ $result = $parser->parse(array('a', '-n', '-1', '--', '......'));
+
+ $this->assertTrue(isset($result->nice));
+ $this->assertTrue($result->has('nice'));
+ $this->assertTrue(isset($result['nice']));
+ $this->assertEquals(-1, $result['nice']->value);
+
+ $res = clone $result['nice'];
+ $res->value = 10;
+ $result['nice'] = $res;
+ $this->assertEquals(10, $result['nice']->value);
+
+ unset($result['nice']);
+ }
+
+ public function testCamelCaseOptionName()
+ {
+ $this->specs->add('base-dir:=dir' , 'I take path');
+ $result = $this->parser->parse(array('a', '--base-dir', 'src'));
+ $this->assertInstanceOf('SplFileInfo', $result->baseDir);
+ }
+
+ public function testOptionWithNegativeValue()
+ {
+ $this->specs->add('n|nice:' , 'I take negative value');
+ $result = $this->parser->parse(array('a', '-n', '-1'));
+ $this->assertEquals(-1, $result->nice);
+ }
+
+ public function testShortOptionName()
+ {
+ $this->specs->add('f:' , 'file');
+ $result = $this->parser->parse(array('a', '-f', 'aaa'));
+ $this->assertEquals('aaa',$result['f']->getValue());
+ }
+
+ public function testOptionWithShortNameAndLongName()
+ {
+ $this->specs->add( 'f|foo' , 'flag' );
+ $result = $this->parser->parse(array('a', '-f'));
+ $this->assertTrue($result->foo);
+
+ $result = $this->parser->parse(array('a', '--foo'));
+ $this->assertTrue($result->foo);
+ }
+
+ public function testSpec()
+ {
+ $options = new OptionCollection;
+ $options->add( 'f|foo:' , 'option require value' );
+ $options->add( 'b|bar+' , 'option with multiple value' );
+ $options->add( 'z|zoo?' , 'option with optional value' );
+ $options->add( 'v|verbose' , 'verbose message' );
+ $options->add( 'd|debug' , 'debug message' );
+ $this->assertEquals(5, $options->size());
+ $this->assertEquals(5, count($options));
+
+
+ $opt = $options->get('foo');
+ $this->assertTrue($opt->isRequired());
+
+ $opt = $options->get('bar');
+ $this->assertTrue( $opt->isMultiple() );
+
+ $opt = $options->get('zoo');
+ $this->assertTrue( $opt->isOptional() );
+
+ $opt = $options->get( 'debug' );
+ $this->assertNotNull( $opt );
+ $this->assertInstanceOf('GetOptionKit\\Option', $opt);
+ $this->assertEquals('debug', $opt->long);
+ $this->assertEquals('d', $opt->short);
+ $this->assertTrue($opt->isFlag());
+
+ return $options;
+ }
+
+ /**
+ * @depends testSpec
+ */
+ public function testOptionFinder($options)
+ {
+ $this->assertNotNull($options->find('f'));
+ $this->assertNotNull($options->find('foo'));
+ $this->assertNull($options->find('xyz'));
+ }
+
+ public function testRequire()
+ {
+ $this->specs->add( 'f|foo:' , 'option require value' );
+ $this->specs->add( 'b|bar+' , 'option with multiple value' );
+ $this->specs->add( 'z|zoo?' , 'option with optional value' );
+ $this->specs->add( 'v|verbose' , 'verbose message' );
+ $this->specs->add( 'd|debug' , 'debug message' );
+
+ $firstExceptionRaised = false;
+ $secondExceptionRaised = false;
+
+ // option required a value should throw an exception
+ try {
+ $result = $this->parser->parse( array('a', '-f' , '-v' , '-d' ) );
+ }
+ catch (Exception $e) {
+ $firstExceptionRaised = true;
+ }
+
+ // even if only one option presented in args array
+ try {
+ $result = $this->parser->parse(array('a','-f'));
+ } catch (Exception $e) {
+ $secondExceptionRaised = true;
+ }
+ if ($firstExceptionRaised && $secondExceptionRaised) {
+ return;
+ }
+ $this->fail('An expected exception has not been raised.');
+ }
+
+ public function testMultiple()
+ {
+ $opt = new OptionCollection;
+ $opt->add( 'b|bar+' , 'option with multiple value' );
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app -b 1 -b 2 --bar 3'));
+ $this->assertNotNull($result->bar);
+ $this->assertCount(3,$result->bar);
+ }
+
+
+ public function testMultipleNumber()
+ {
+ $opt = new OptionCollection;
+ $opt->add('b|bar+=number' , 'option with multiple value');
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app --bar 1 --bar 2 --bar 3'));
+ $this->assertNotNull($result->bar);
+ $this->assertCount(3,$result->bar);
+ $this->assertSame(array(1,2,3),$result->bar);
+ }
+
+ public function testSimpleOptionWithDefaultValue()
+ {
+ $opts = new OptionCollection;
+ $opts->add('p|proc=number' , 'option with required value')
+ ->defaultValue(10)
+ ;
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app'));
+ $this->assertEquals(10, $result['proc']->value);
+ }
+
+ public function testOptionalOptionWithDefaultValue()
+ {
+ $opts = new OptionCollection;
+ $opts->add('p|proc?=number' , 'option with required value')
+ ->defaultValue(10)
+ ;
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app --proc'));
+ $this->assertEquals(10, $result['proc']->value);
+ }
+
+ public function testMultipleString()
+ {
+ $opts = new OptionCollection;
+ $opts->add('b|bar+=string' , 'option with multiple value');
+ $bar = $opts->get('bar');
+ $this->assertNotNull($bar);
+ $this->assertTrue($bar->isMultiple());
+ $this->assertTrue($bar->isType('string'));
+ $this->assertFalse($bar->isType('number'));
+
+
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app --bar lisa --bar mary --bar john a b c'));
+ $this->assertNotNull($result->bar);
+ $this->assertCount(3,$result->bar);
+ $this->assertSame(array('lisa', 'mary', 'john'),$result->bar);
+ $this->assertSame(array('a','b','c'), $result->getArguments());
+ }
+
+ public function testParseIncrementalOption()
+ {
+ $opts = new OptionCollection;
+ $opts->add('v|verbose' , 'verbose')
+ ->isa("number")
+ ->incremental();
+
+ $parser = new OptionParser($opts);
+ $result = $parser->parse(explode(' ','app -vvv arg1 arg2'));
+ $this->assertInstanceOf('GetOptionKit\Option',$result['verbose']);
+ $this->assertNotNull($result['verbose']);
+ $this->assertEquals(3, $result['verbose']->value);
+ }
+
+
+ /**
+ * @expectedException Exception
+ */
+ public function testIntegerTypeNonNumeric()
+ {
+ $opt = new OptionCollection;
+ $opt->add( 'b|bar:=number' , 'option with integer type' );
+
+ $parser = new OptionParser($opt);
+ $spec = $opt->get('bar');
+ $this->assertTrue($spec->isTypeNumber());
+
+ // test non numeric
+ $result = $parser->parse(explode(' ','app -b test'));
+ $this->assertNotNull($result->bar);
+ }
+
+
+ public function testIntegerTypeNumericWithoutEqualSign()
+ {
+ $opt = new OptionCollection;
+ $opt->add('b|bar:=number', 'option with integer type');
+
+ $spec = $opt->get('bar');
+ $this->assertTrue($spec->isTypeNumber());
+
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app -b 123123'));
+ $this->assertNotNull($result);
+ $this->assertEquals(123123, $result->bar);
+ }
+
+ public function testIntegerTypeNumericWithEqualSign()
+ {
+ $opt = new OptionCollection;
+ $opt->add('b|bar:=number' , 'option with integer type');
+
+ $spec = $opt->get('bar');
+ $this->assertTrue($spec->isTypeNumber());
+
+ $parser = new OptionParser($opt);
+ $result = $parser->parse(explode(' ','app -b=123123'));
+ $this->assertNotNull($result);
+ $this->assertNotNull($result->bar);
+ $this->assertEquals(123123, $result->bar);
+ }
+
+ public function testStringType()
+ {
+ $this->specs->add( 'b|bar:=string' , 'option with type' );
+
+ $spec = $this->specs->get('bar');
+
+ $result = $this->parser->parse(explode(' ','app -b text arg1 arg2 arg3'));
+ $this->assertNotNull($result->bar);
+
+ $result = $this->parser->parse(explode(' ','app -b=text arg1 arg2 arg3'));
+ $this->assertNotNull($result->bar);
+
+ $args = $result->getArguments();
+ $this->assertNotEmpty($args);
+ $this->assertCount(3,$args);
+ $this->assertEquals('arg1', $args[0]);
+ $this->assertEquals('arg2', $args[1]);
+ $this->assertEquals('arg3', $args[2]);
+ }
+
+ public function testStringQuoteOptionValue()
+ {
+ $opts = new OptionCollection();
+ $opts->add('f|foo:' , 'option requires a value.');
+ $parser = new OptionParser($opts);
+ $res = $parser->parse(['app','--foo=aa bb cc']);
+ $this->assertEquals('aa bb cc', $res->get('foo'));
+ }
+
+ public function testSpec2()
+ {
+ $this->specs->add('long' , 'long option name only.');
+ $this->specs->add('a' , 'short option name only.');
+ $this->specs->add('b' , 'short option name only.');
+ $this->assertNotNull($this->specs->all());
+ $this->assertNotNull($this->specs);
+ $this->assertNotNull($result = $this->parser->parse(explode(' ','app -a -b --long')) );
+ $this->assertNotNull($result->a);
+ $this->assertNotNull($result->b);
+ }
+
+
+ public function testSpecCollection()
+ {
+ $this->specs->add( 'f|foo:' , 'option requires a value.' );
+ $this->specs->add( 'b|bar+' , 'option with multiple value.' );
+ $this->specs->add( 'z|zoo?' , 'option with optional value.' );
+ $this->specs->add( 'v|verbose' , 'verbose message.' );
+ $this->specs->add( 'd|debug' , 'debug message.' );
+ $this->specs->add( 'long' , 'long option name only.' );
+ $this->specs->add( 's' , 'short option name only.' );
+
+ $this->assertNotNull( $this->specs->all() );
+ $this->assertNotNull( $this->specs );
+
+ $this->assertCount( 7 , $array = $this->specs->toArray() );
+ $this->assertNotEmpty( isset($array[0]['long'] ));
+ $this->assertNotEmpty( isset($array[0]['short'] ));
+ $this->assertNotEmpty( isset($array[0]['desc'] ));
+ }
+
+ public function optionTestProvider()
+ {
+ return array(
+ array( 'foo', 'simple boolean option', 'foo', true,
+ [['a','--foo','a', 'b', 'c']]
+ ),
+ array( 'f|foo', 'simple boolean option', 'foo', true,
+ [['a','--foo'], ['a','-f']]
+ ),
+ array( 'f|foo:=string', 'string option', 'foo', 'xxx',
+ [['a','--foo','xxx'], ['a','-f', 'xxx']]
+ ),
+ array( 'f|foo:=string', 'string option', 'foo', 'xxx',
+ [['a','b', 'c', '--foo','xxx'], ['a', 'a', 'b', 'c', '-f', 'xxx']]
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider optionTestProvider
+ */
+ public function test($specString, $desc, $key, $expectedValue, array $argvList)
+ {
+ $opts = new OptionCollection();
+ $opts->add($specString, $desc);
+ $parser = new OptionParser($opts);
+ foreach ($argvList as $argv) {
+ $res = $parser->parse($argv);
+ $this->assertSame($expectedValue, $res->get($key));
+ }
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testParseWithoutProgramName()
+ {
+ $parser = new OptionParser(new OptionCollection);
+ $parser->parse(array('--foo'));
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionException
+ */
+ public function testParseInvalidOptionException()
+ {
+ $parser = new OptionParser(new OptionCollection);
+ $parser->parse(array('app','--foo'));
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\RequireValueException
+ */
+ public function testParseOptionRequireValueException()
+ {
+ $options = new OptionCollection;
+ $options->add('name:=string', 'name');
+
+ $parser = new OptionParser($options);
+ $parser->parse(array('app','--name'));
+ }
+
+
+
+ public function testMore()
+ {
+ $this->specs->add('f|foo:' , 'option require value' );
+ $this->specs->add('b|bar+' , 'option with multiple value' );
+ $this->specs->add('z|zoo?' , 'option with optional value' );
+ $this->specs->add('v|verbose' , 'verbose message' );
+ $this->specs->add('d|debug' , 'debug message' );
+
+ $result = $this->parser->parse( array('a', '-f' , 'foo value' , '-v' , '-d' ) );
+ $this->assertNotNull($result->foo);
+ $this->assertNotNull($result->verbose);
+ $this->assertNotNull($result->debug);
+ $this->assertEquals( 'foo value', $result->foo );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+
+ foreach ($result as $k => $v) {
+ $this->assertTrue(in_array($k, ['foo','bar','zoo','verbose', 'debug']));
+ $this->assertInstanceOf('GetOptionKit\\Option', $v);
+ }
+ $this->assertSame([
+ 'foo' => 'foo value',
+ 'verbose' => true,
+ 'debug' => true
+ ], $result->toArray());
+
+ $result = $this->parser->parse( array('a', '-f=foo value' , '-v' , '-d' ) );
+ $this->assertNotNull( $result );
+ $this->assertNotNull( $result->foo );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+
+ $this->assertEquals( 'foo value', $result->foo );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+
+ $result = $this->parser->parse( array('a', '-vd' ) );
+ $this->assertNotNull( $result->verbose );
+ $this->assertNotNull( $result->debug );
+ }
+
+ public function testParseAcceptsValidOption()
+ {
+ $this->specs
+ ->add('f:foo', 'test option')
+ ->validator(function($value) {
+ return $value === 'valid-option';
+ });
+
+ $result = $this->parser->parse(array('a', '-f' , 'valid-option'));
+
+ $this->assertArrayHasKey('f', $result);
+ }
+
+ /**
+ * @expectedException GetOptionKit\Exception\InvalidOptionValueException
+ */
+ public function testParseThrowsExceptionOnInvalidOption()
+ {
+ $this->specs
+ ->add('f:foo', 'test option')
+ ->validator(function($value) {
+ return $value === 'valid-option';
+ });
+
+ $this->parser->parse(array('a', '-f' , 'not-a-valid-option'));
+ }
+}
diff --git a/vendor/corneltek/getoptionkit/tests/OptionPrinter/ConsoleOptionPrinterTest.php b/vendor/corneltek/getoptionkit/tests/OptionPrinter/ConsoleOptionPrinterTest.php
new file mode 100755
index 0000000..2f06767
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/tests/OptionPrinter/ConsoleOptionPrinterTest.php
@@ -0,0 +1,32 @@
+add('f|foo:', 'option requires a value.' )
+ ->isa('String');
+
+ $options->add('b|bar+', 'option with multiple value.' )
+ ->isa('Number');
+
+ $options->add('z|zoo?', 'option with optional value.' )
+ ->isa('Boolean')
+ ;
+
+ $options->add('n', 'n flag' );
+
+ $options->add('verbose', 'verbose');
+
+ $options->add('o|output?', 'option with optional value.' )
+ ->isa('File')
+ ->defaultValue('output.txt')
+ ;
+ $printer = new ConsoleOptionPrinter;
+ $output = $printer->render($options);
+ }
+
+}
diff --git a/vendor/corneltek/getoptionkit/tests/OptionResultTest.php b/vendor/corneltek/getoptionkit/tests/OptionResultTest.php
new file mode 100755
index 0000000..01be96d
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/tests/OptionResultTest.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+class OptionResultTest extends \PHPUnit\Framework\TestCase
+{
+
+ function testOption()
+ {
+ $option = new \GetOptionKit\OptionResult;
+ $this->assertNotNull( $option );
+
+ $specs = new \GetOptionKit\OptionCollection;
+ $specs->add('name:','name');
+ $result = \GetOptionKit\OptionResult::create($specs,array( 'name' => 'c9s' ),array( 'arg1' ));
+ $this->assertNotNull( $result );
+ $this->assertNotNull( $result->arguments );
+ $this->assertNotNull( $result->name );
+ $this->assertEquals( 'c9s', $result->name );
+ $this->assertEquals( $result->arguments[0] , 'arg1' );
+ }
+
+}
+
+
diff --git a/vendor/corneltek/getoptionkit/tests/OptionTest.php b/vendor/corneltek/getoptionkit/tests/OptionTest.php
new file mode 100755
index 0000000..7b06f1d
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/tests/OptionTest.php
@@ -0,0 +1,227 @@
+assertNotNull($opt);
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testInvalidOptionSpec()
+ {
+ new Option('....');
+ }
+
+ public function testValueName()
+ {
+ $opt = new Option('z');
+ $opt->defaultValue(10);
+ $opt->valueName('priority');
+ $this->assertEquals('[=priority]', $opt->renderValueHint());
+ $this->assertEquals('-z[=priority]', $opt->renderReadableSpec());
+ }
+
+
+ public function testDefaultValue()
+ {
+ $opt = new Option('z');
+ $opt->defaultValue(10);
+ $this->assertEquals(10, $opt->getValue());
+ $this->assertEquals('-z[=10]',$opt->renderReadableSpec(true));
+ }
+
+ public function testBackwardCompatibleBoolean()
+ {
+ $opt = new Option('scope');
+ $opt->isa('bool');
+ $this->assertEquals('boolean', $opt->isa);
+ $this->assertEquals('--scope=',$opt->renderReadableSpec(true));
+ }
+
+
+
+ public function validatorProvider()
+ {
+ return [
+ [function($a) { return in_array($a, ['public', 'private']); }],
+ [function($a) { return [in_array($a, ['public', 'private']), "message"]; }]
+ ];
+ }
+
+ /**
+ * @dataProvider validatorProvider
+ */
+ public function testValidator($cb)
+ {
+ $opt = new Option('scope');
+ $opt->validator($cb);
+ $ret = $opt->validate('public');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('private');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('foo');
+ $this->assertFalse($ret[0]);
+ $this->assertEquals('--scope', $opt->renderReadableSpec(true));
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testInvalidTypeClass()
+ {
+ $opt = new Option('scope');
+ $opt->isa('SomethingElse');
+ $class = $opt->getTypeClass();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testValidatorReturnValue()
+ {
+ $opt = new Option('scope');
+ $opt->validator(function($val) {
+ return 123454;
+ });
+ $ret = $opt->validate('public');
+ }
+
+ public function testOptionWithoutValidator()
+ {
+ $opt = new Option('scope');
+ $ret = $opt->validate('public');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('private');
+ $this->assertTrue($ret[0]);
+ $ret = $opt->validate('foo');
+ $this->assertTrue($ret[0]);
+ $this->assertEquals('--scope',$opt->renderReadableSpec(true));
+ }
+
+
+
+
+ public function testSuggestionsCallback()
+ {
+ $opt = new Option('scope');
+ $this->assertEmpty($opt->getSuggestions());
+
+ $opt->suggestions(function() {
+ return ['public', 'private'];
+ });
+ $this->assertNotEmpty($opt->getSuggestions());
+ $this->assertSame(['public', 'private'],$opt->getSuggestions());
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+
+ $this->assertEquals('--scope=[public,private]',$opt->renderReadableSpec(true));
+ }
+
+ public function testSuggestions()
+ {
+ $opt = new Option('scope');
+ $opt->suggestions(['public', 'private']);
+ $this->assertNotEmpty($opt->getSuggestions());
+ $this->assertSame(['public', 'private'],$opt->getSuggestions());
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+
+ $this->assertEquals('--scope=[public,private]',$opt->renderReadableSpec(true));
+ }
+
+ public function testValidValuesCallback() {
+ $opt = new Option('scope');
+ $opt->validValues(function() {
+ return ['public', 'private'];
+ });
+ $this->assertNotNull($opt->getValidValues());
+ $this->assertNotEmpty($opt->getValidValues());
+
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+ $this->assertEquals('--scope=(public,private)',$opt->renderReadableSpec(true));
+ }
+
+ public function testTrigger()
+ {
+ $opt = new Option('scope');
+ $opt->validValues([ 'public', 'private' ]);
+
+ $state = 0;
+ $opt->trigger(function($val) use(& $state) {
+ $state++;
+ });
+ $this->assertNotEmpty($opt->getValidValues());
+ $opt->setValue('public');
+
+ $this->assertEquals(1, $state);
+ $opt->setValue('private');
+ $this->assertEquals(2, $state);
+
+ }
+
+
+
+ public function testArrayValueToString()
+ {
+ $opt = new Option('uid');
+ $opt->setValue([1,2,3,4]);
+ $toString = '* key:uid spec:--uid desc:
+ value => 1,2,3,4
+';
+ $this->assertEquals($toString,$opt->__toString());
+ }
+
+ public function testValidValues()
+ {
+ $opt = new Option('scope');
+ $opt->validValues([ 'public', 'private' ])
+ ;
+ $this->assertNotEmpty($opt->getValidValues());
+ $this->assertTrue(is_array($opt->getValidValues()));
+
+ $opt->setValue('public');
+ $opt->setValue('private');
+ $this->assertEquals('private',$opt->value);
+ $this->assertEquals('--scope=(public,private)',$opt->renderReadableSpec(true));
+ $this->assertNotEmpty($opt->__toString());
+ }
+
+
+ public function testFilter() {
+ $opt = new Option('scope');
+ $opt->filter(function($val) {
+ return preg_replace('#a#', 'x', $val);
+ })
+ ;
+ $opt->setValue('aa');
+ $this->assertEquals('xx', $opt->value);
+ }
+}
+
diff --git a/vendor/corneltek/getoptionkit/tests/RegexValueTypeTest.php b/vendor/corneltek/getoptionkit/tests/RegexValueTypeTest.php
new file mode 100755
index 0000000..6917918
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/tests/RegexValueTypeTest.php
@@ -0,0 +1,26 @@
+assertEquals($regex->option, '#^Test$#');
+ }
+
+ public function testValidation()
+ {
+ $regex = new RegexType('#^Test$#');
+ $this->assertTrue($regex->test('Test'));
+ $this->assertFalse($regex->test('test'));
+
+ $regex->option = '/^([a-z]+)$/';
+ $this->assertTrue($regex->test('barfoo'));
+ $this->assertFalse($regex->test('foobar234'));
+ $ret = $regex->parse('foobar234');
+ $this->assertNotNull($ret);
+ }
+}
+
diff --git a/vendor/corneltek/getoptionkit/tests/ValueTypeTest.php b/vendor/corneltek/getoptionkit/tests/ValueTypeTest.php
new file mode 100755
index 0000000..08472c5
--- /dev/null
+++ b/vendor/corneltek/getoptionkit/tests/ValueTypeTest.php
@@ -0,0 +1,200 @@
+assertNotNull( new BooleanType );
+ $this->assertNotNull( new StringType );
+ $this->assertNotNull( new FileType );
+ $this->assertNotNull( new DateType );
+ $this->assertNotNull( new DateTimeType );
+ $this->assertNotNull( new NumberType );
+ $this->assertNotNull( new UrlType );
+ $this->assertNotNull( new IpType );
+ $this->assertNotNull( new Ipv4Type );
+ $this->assertNotNull( new Ipv6Type );
+ $this->assertNotNull( new EmailType );
+ $this->assertNotNull( new PathType );
+ $this->assertNotNull( new RegexType("/[a-z]/"));
+ }
+
+
+ public function testDateTimeType()
+ {
+ $type = new DateTimeType([ 'format' => 'Y-m-d' ]);
+ $this->assertTrue($type->test('2016-12-30'));
+ $a = $type->parse('2016-12-30');
+ $this->assertEquals(2016, $a->format('Y'));
+ $this->assertEquals(12, $a->format('m'));
+ $this->assertEquals(30, $a->format('d'));
+ $this->assertFalse($type->test('foo'));
+ }
+
+ public function testDateType()
+ {
+ $type = new DateType;
+ $this->assertTrue($type->test('2016-12-30'));
+ $a = $type->parse('2016-12-30');
+ $this->assertEquals(2016, $a['year']);
+ $this->assertEquals(12, $a['month']);
+ $this->assertEquals(30, $a['day']);
+ $this->assertFalse($type->test('foo'));
+ }
+
+
+
+ public function booleanTestProvider()
+ {
+ return [
+ [true , true, true],
+ [false , true, false],
+ ['true' , true, true],
+ ['false' , true, false],
+ ['0' , true, false],
+ ['1' , true, true],
+ ['foo' , false, null],
+ ['123' , false, null],
+ ];
+ }
+
+ /**
+ * @dataProvider booleanTestProvider
+ */
+ public function testBooleanType($a, $test, $expected)
+ {
+ $bool = new BooleanType;
+ $this->assertEquals($test, $bool->test($a));
+ if ($bool->test($a)) {
+ $this->assertEquals($expected, $bool->parse($a));
+ }
+ }
+
+ public function testDirType()
+ {
+ $type = new DirType;
+ $this->assertTrue($type->test('tests'));
+ $this->assertFalse($type->test('composer.json'));
+ $this->assertFalse($type->test('foo/bar'));
+ $this->assertInstanceOf('SplFileInfo',$type->parse('tests'));
+ }
+
+ public function testFileType()
+ {
+ $type = new FileType;
+ $this->assertFalse($type->test('tests'));
+ $this->assertTrue($type->test('composer.json'));
+ $this->assertFalse($type->test('foo/bar'));
+ $this->assertInstanceOf('SplFileInfo', $type->parse('composer.json'));
+ }
+
+ public function testPathType()
+ {
+ $type = new PathType;
+ $this->assertTrue($type->test('tests'));
+ $this->assertTrue($type->test('composer.json'));
+ $this->assertFalse($type->test('foo/bar'));
+ $this->assertInstanceOf('SplFileInfo', $type->parse('composer.json'));
+ }
+
+ public function testUrlType()
+ {
+ $url = new UrlType;
+ $this->assertTrue($url->test('http://t'));
+ $this->assertTrue($url->test('http://t.c'));
+ $this->assertFalse($url->test('t.c'));
+ $this->assertEquals('http://t.c', $url->parse('http://t.c'));
+ }
+
+ public function ipV4Provider()
+ {
+ return [
+ ['192.168.25.58', true],
+ ['8.8.8.8', true],
+ ['github.com', false],
+ ];
+ }
+
+ public function ipV6Provider()
+ {
+ return [
+ ['192.168.25.58', false],
+ ['2607:f0d0:1002:51::4', true],
+ ['2607:f0d0:1002:0051:0000:0000:0000:0004', true],
+ ['::1', true],
+ ['10.10.15.10/16', false],
+ ['github.com', false],
+ ];
+ }
+
+ public function ipProvider()
+ {
+ return [
+ ['192.168.25.58', true],
+ ['2607:f0d0:1002:51::4', true],
+ ['::1', true],
+ ['10.10.15.10/16', false],
+ ['github.com', false],
+ ];
+ }
+
+ /**
+ * @dataProvider ipProvider
+ */
+ public function testIpType($ipstr, $pass = true)
+ {
+ $ip = new IpType;
+ $this->assertEquals($pass, $ip->test($ipstr));
+ if ($pass) {
+ $this->assertNotNull($ip->parse($ipstr));
+ }
+ }
+
+ /**
+ * @dataProvider ipV4Provider
+ */
+ public function testIpv4Type($ipstr, $pass = true)
+ {
+ $ipv4 = new Ipv4Type;
+ $this->assertEquals($pass, $ipv4->test($ipstr));
+ if ($pass) {
+ $this->assertNotNull($ipv4->parse($ipstr));
+ }
+ }
+
+ /**
+ * @dataProvider ipV6Provider
+ */
+ public function testIpv6Type($ipstr, $pass = true)
+ {
+ $ipv6 = new Ipv6Type;
+ $this->assertEquals($pass, $ipv6->test($ipstr));
+ if ($pass) {
+ $this->assertNotNull($ipv6->parse($ipstr));
+ }
+ }
+
+ public function testEmailType()
+ {
+ $email = new EmailType;
+ $this->assertTrue($email->test('test@gmail.com'));
+ $this->assertFalse($email->test('test@test'));
+ $email->parse('test@gmail.com');
+ }
+}
+
diff --git a/vendor/evenement/evenement/.gitignore b/vendor/evenement/evenement/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/vendor/evenement/evenement/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/vendor/evenement/evenement/.travis.yml b/vendor/evenement/evenement/.travis.yml
new file mode 100755
index 0000000..65ba0ce
--- /dev/null
+++ b/vendor/evenement/evenement/.travis.yml
@@ -0,0 +1,24 @@
+language: php
+
+php:
+ - 7.0
+ - 7.1
+ - hhvm
+ - nightly
+
+matrix:
+ allow_failures:
+ - php: hhvm
+ - php: nightly
+
+before_script:
+ - wget http://getcomposer.org/composer.phar
+ - php composer.phar install
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
+ - php -n examples/benchmark-emit-no-arguments.php
+ - php -n examples/benchmark-emit-one-argument.php
+ - php -n examples/benchmark-emit.php
+ - php -n examples/benchmark-emit-once.php
+ - php -n examples/benchmark-remove-listener-once.php
diff --git a/vendor/evenement/evenement/CHANGELOG.md b/vendor/evenement/evenement/CHANGELOG.md
new file mode 100755
index 0000000..568f229
--- /dev/null
+++ b/vendor/evenement/evenement/CHANGELOG.md
@@ -0,0 +1,35 @@
+CHANGELOG
+=========
+
+
+* v3.0.1 (2017-07-23)
+
+ * Resolved regression introduced in once listeners in v3.0.0 [#49](https://github.com/igorw/evenement/pull/49)
+
+* v3.0.0 (2017-07-23)
+
+ * Passing null as event name throw exception [#46](https://github.com/igorw/evenement/pull/46), and [#47](https://github.com/igorw/evenement/pull/47)
+ * Performance improvements [#39](https://github.com/igorw/evenement/pull/39), and [#45](https://github.com/igorw/evenement/pull/45)
+ * Remove once listeners [#44](https://github.com/igorw/evenement/pull/44), [#45](https://github.com/igorw/evenement/pull/45)
+
+* v2.1.0 (2017-07-17)
+
+ * Chaining for "on" method [#30](https://github.com/igorw/evenement/pull/30)
+ * Unit tests (on Travis) improvements [#33](https://github.com/igorw/evenement/pull/33), [#36](https://github.com/igorw/evenement/pull/36), and [#37](https://github.com/igorw/evenement/pull/37)
+ * Benchmarks added [#35](https://github.com/igorw/evenement/pull/35), and [#40](https://github.com/igorw/evenement/pull/40)
+ * Minor performance improvements [#42](https://github.com/igorw/evenement/pull/42), and [#38](https://github.com/igorw/evenement/pull/38)
+
+* v2.0.0 (2012-11-02)
+
+ * Require PHP >=5.4.0
+ * Added EventEmitterTrait
+ * Removed EventEmitter2
+
+* v1.1.0 (2017-07-17)
+
+ * Chaining for "on" method [#29](https://github.com/igorw/evenement/pull/29)
+ * Minor performance improvements [#43](https://github.com/igorw/evenement/pull/43)
+
+* v1.0.0 (2012-05-30)
+
+ * Inital stable release
diff --git a/vendor/evenement/evenement/LICENSE b/vendor/evenement/evenement/LICENSE
new file mode 100755
index 0000000..d9a37d0
--- /dev/null
+++ b/vendor/evenement/evenement/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/evenement/evenement/README.md b/vendor/evenement/evenement/README.md
new file mode 100755
index 0000000..9443011
--- /dev/null
+++ b/vendor/evenement/evenement/README.md
@@ -0,0 +1,83 @@
+# Événement
+
+Événement is a very simple event dispatching library for PHP.
+
+It has the same design goals as [Silex](http://silex-project.org) and
+[Pimple](http://pimple-project.org), to empower the user while staying concise
+and simple.
+
+It is very strongly inspired by the EventEmitter API found in
+[node.js](http://nodejs.org).
+
+[](http://travis-ci.org/igorw/evenement)
+
+## Fetch
+
+The recommended way to install Événement is [through composer](http://getcomposer.org).
+
+Just create a composer.json file for your project:
+
+```JSON
+{
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0"
+ }
+}
+```
+
+**Note:** The `3.x` version of Événement requires PHP 7 and the `2.x` version requires PHP 5.4. If you are
+using PHP 5.3, please use the `1.x` version:
+
+```JSON
+{
+ "require": {
+ "evenement/evenement": "^1.0"
+ }
+}
+```
+
+And run these two commands to install it:
+
+ $ curl -s http://getcomposer.org/installer | php
+ $ php composer.phar install
+
+Now you can add the autoloader, and you will have access to the library:
+
+```php
+on('user.created', function (User $user) use ($logger) {
+ $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+});
+```
+
+### Emitting Events
+
+```php
+emit('user.created', [$user]);
+```
+
+Tests
+-----
+
+ $ ./vendor/bin/phpunit
+
+License
+-------
+MIT, see LICENSE.
diff --git a/vendor/evenement/evenement/composer.json b/vendor/evenement/evenement/composer.json
new file mode 100755
index 0000000..cbb4827
--- /dev/null
+++ b/vendor/evenement/evenement/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "evenement/evenement",
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": ["event-dispatcher", "event-emitter"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "autoload": {
+ "psr-0": {
+ "Evenement": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-0": {
+ "Evenement": "tests"
+ },
+ "files": ["tests/Evenement/Tests/functions.php"]
+ }
+}
diff --git a/vendor/evenement/evenement/doc/00-intro.md b/vendor/evenement/evenement/doc/00-intro.md
new file mode 100755
index 0000000..6c28a2a
--- /dev/null
+++ b/vendor/evenement/evenement/doc/00-intro.md
@@ -0,0 +1,28 @@
+# Introduction
+
+Événement is is French and means "event". The événement library aims to
+provide a simple way of subscribing to events and notifying those subscribers
+whenever an event occurs.
+
+The API that it exposes is almost a direct port of the EventEmitter API found
+in node.js. It also includes an "EventEmitter". There are some minor
+differences however.
+
+The EventEmitter is an implementation of the publish-subscribe pattern, which
+is a generalized version of the observer pattern. The observer pattern
+specifies an observable subject, which observers can register themselves to.
+Once something interesting happens, the subject notifies its observers.
+
+Pub/sub takes the same idea but encapsulates the observation logic inside a
+separate object which manages all of its subscribers or listeners. Subscribers
+are bound to an event name, and will only receive notifications of the events
+they subscribed to.
+
+**TLDR: What does evenement do, in short? It provides a mapping from event
+names to a list of listener functions and triggers each listener for a given
+event when it is emitted.**
+
+Why do we do this, you ask? To achieve decoupling.
+
+It allows you to design a system where the core will emit events, and modules
+are able to subscribe to these events. And respond to them.
diff --git a/vendor/evenement/evenement/doc/01-api.md b/vendor/evenement/evenement/doc/01-api.md
new file mode 100755
index 0000000..17ba333
--- /dev/null
+++ b/vendor/evenement/evenement/doc/01-api.md
@@ -0,0 +1,91 @@
+# API
+
+The API that événement exposes is defined by the
+`Evenement\EventEmitterInterface`. The interface is useful if you want to
+define an interface that extends the emitter and implicitly defines certain
+events to be emitted, or if you want to type hint an `EventEmitter` to be
+passed to a method without coupling to the specific implementation.
+
+## on($event, callable $listener)
+
+Allows you to subscribe to an event.
+
+Example:
+
+```php
+$emitter->on('user.created', function (User $user) use ($logger) {
+ $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+});
+```
+
+Since the listener can be any callable, you could also use an instance method
+instead of the anonymous function:
+
+```php
+$loggerSubscriber = new LoggerSubscriber($logger);
+$emitter->on('user.created', array($loggerSubscriber, 'onUserCreated'));
+```
+
+This has the benefit that listener does not even need to know that the emitter
+exists.
+
+You can also accept more than one parameter for the listener:
+
+```php
+$emitter->on('numbers_added', function ($result, $a, $b) {});
+```
+
+## once($event, callable $listener)
+
+Convenience method that adds a listener which is guaranteed to only be called
+once.
+
+Example:
+
+```php
+$conn->once('connected', function () use ($conn, $data) {
+ $conn->send($data);
+});
+```
+
+## emit($event, array $arguments = [])
+
+Emit an event, which will call all listeners.
+
+Example:
+
+```php
+$conn->emit('data', [$data]);
+```
+
+The second argument to emit is an array of listener arguments. This is how you
+specify more args:
+
+```php
+$result = $a + $b;
+$emitter->emit('numbers_added', [$result, $a, $b]);
+```
+
+## listeners($event)
+
+Allows you to inspect the listeners attached to an event. Particularly useful
+to check if there are any listeners at all.
+
+Example:
+
+```php
+$e = new \RuntimeException('Everything is broken!');
+if (0 === count($emitter->listeners('error'))) {
+ throw $e;
+}
+```
+
+## removeListener($event, callable $listener)
+
+Remove a specific listener for a specific event.
+
+## removeAllListeners($event = null)
+
+Remove all listeners for a specific event or all listeners all together. This
+is useful for long-running processes, where you want to remove listeners in
+order to allow them to get garbage collected.
diff --git a/vendor/evenement/evenement/doc/02-plugin-system.md b/vendor/evenement/evenement/doc/02-plugin-system.md
new file mode 100755
index 0000000..6a08371
--- /dev/null
+++ b/vendor/evenement/evenement/doc/02-plugin-system.md
@@ -0,0 +1,155 @@
+# Example: Plugin system
+
+In this example I will show you how to create a generic plugin system with
+événement where plugins can alter the behaviour of the app. The app is a blog.
+Boring, I know. By using the EventEmitter it will be easy to extend this blog
+with additional functionality without modifying the core system.
+
+The blog is quite basic. Users are able to create blog posts when they log in.
+The users are stored in a static config file, so there is no sign up process.
+Once logged in they get a "new post" link which gives them a form where they
+can create a new blog post with plain HTML. That will store the post in a
+document database. The index lists all blog post titles by date descending.
+Clicking on the post title will take you to the full post.
+
+## Plugin structure
+
+The goal of the plugin system is to allow features to be added to the blog
+without modifying any core files of the blog.
+
+The plugins are managed through a config file, `plugins.json`. This JSON file
+contains a JSON-encoded list of class-names for plugin classes. This allows
+you to enable and disable plugins in a central location. The initial
+`plugins.json` is just an empty array:
+```json
+[]
+```
+
+A plugin class must implement the `PluginInterface`:
+```php
+interface PluginInterface
+{
+ function attachEvents(EventEmitterInterface $emitter);
+}
+```
+
+The `attachEvents` method allows the plugin to attach any events to the
+emitter. For example:
+```php
+class FooPlugin implements PluginInterface
+{
+ public function attachEvents(EventEmitterInterface $emitter)
+ {
+ $emitter->on('foo', function () {
+ echo 'bar!';
+ });
+ }
+}
+```
+
+The blog system creates an emitter instance and loads the plugins:
+```php
+$emitter = new EventEmitter();
+
+$pluginClasses = json_decode(file_get_contents('plugins.json'), true);
+foreach ($pluginClasses as $pluginClass) {
+ $plugin = new $pluginClass();
+ $pluginClass->attachEvents($emitter);
+}
+```
+
+This is the base system. There are no plugins yet, and there are no events yet
+either. That's because I don't know which extension points will be needed. I
+will add them on demand.
+
+## Feature: Markdown
+
+Writing blog posts in HTML sucks! Wouldn't it be great if I could write them
+in a nice format such as markdown, and have that be converted to HTML for me?
+
+This feature will need two extension points. I need to be able to mark posts
+as markdown, and I need to be able to hook into the rendering of the post body
+and convert it from markdown to HTML. So the blog needs two new events:
+`post.create` and `post.render`.
+
+In the code that creates the post, I'll insert the `post.create` event:
+```php
+class PostEvent
+{
+ public $post;
+
+ public function __construct(array $post)
+ {
+ $this->post = $post;
+ }
+}
+
+$post = createPostFromRequest($_POST);
+
+$event = new PostEvent($post);
+$emitter->emit('post.create', [$event]);
+$post = $event->post;
+
+$db->save('post', $post);
+```
+
+This shows that you can wrap a value in an event object to make it mutable,
+allowing listeners to change it.
+
+The same thing for the `post.render` event:
+```php
+public function renderPostBody(array $post)
+{
+ $emitter = $this->emitter;
+
+ $event = new PostEvent($post);
+ $emitter->emit('post.render', [$event]);
+ $post = $event->post;
+
+ return $post['body'];
+}
+
+
= $post['title'] %>
+
= renderPostBody($post) %>
+```
+
+Ok, the events are in place. It's time to create the first plugin, woohoo! I
+will call this the `MarkdownPlugin`, so here's `plugins.json`:
+```json
+[
+ "MarkdownPlugin"
+]
+```
+
+The `MarkdownPlugin` class will be autoloaded, so I don't have to worry about
+including any files. I just have to worry about implementing the plugin class.
+The `markdown` function represents a markdown to HTML converter.
+```php
+class MarkdownPlugin implements PluginInterface
+{
+ public function attachEvents(EventEmitterInterface $emitter)
+ {
+ $emitter->on('post.create', function (PostEvent $event) {
+ $event->post['format'] = 'markdown';
+ });
+
+ $emitter->on('post.render', function (PostEvent $event) {
+ if (isset($event->post['format']) && 'markdown' === $event->post['format']) {
+ $event->post['body'] = markdown($event->post['body']);
+ }
+ });
+ }
+}
+```
+
+There you go, the blog now renders posts as markdown. But all of the previous
+posts before the addition of the markdown plugin are still rendered correctly
+as raw HTML.
+
+## Feature: Comments
+
+TODO
+
+## Feature: Comment spam control
+
+TODO
diff --git a/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php b/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php
new file mode 100755
index 0000000..53d7f4b
--- /dev/null
+++ b/vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function () {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->emit('event');
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/vendor/evenement/evenement/examples/benchmark-emit-once.php b/vendor/evenement/evenement/examples/benchmark-emit-once.php
new file mode 100755
index 0000000..74f4d17
--- /dev/null
+++ b/vendor/evenement/evenement/examples/benchmark-emit-once.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+ini_set('memory_limit', '512M');
+
+const ITERATIONS = 100000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->once('event', function ($a, $b, $c) {});
+}
+
+$start = microtime(true);
+$emitter->emit('event', [1, 2, 3]);
+$time = microtime(true) - $start;
+
+echo 'Emitting one event to ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php b/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php
new file mode 100755
index 0000000..39fc4ba
--- /dev/null
+++ b/vendor/evenement/evenement/examples/benchmark-emit-one-argument.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function ($a) {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->emit('event', [1]);
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/vendor/evenement/evenement/examples/benchmark-emit.php b/vendor/evenement/evenement/examples/benchmark-emit.php
new file mode 100755
index 0000000..3ab639e
--- /dev/null
+++ b/vendor/evenement/evenement/examples/benchmark-emit.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function ($a, $b, $c) {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $emitter->emit('event', [1, 2, 3]);
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php b/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php
new file mode 100755
index 0000000..414be3b
--- /dev/null
+++ b/vendor/evenement/evenement/examples/benchmark-remove-listener-once.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+ini_set('memory_limit', '512M');
+
+const ITERATIONS = 100000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$listeners = [];
+for ($i = 0; $i < ITERATIONS; $i++) {
+ $listeners[] = function ($a, $b, $c) {};
+}
+
+$start = microtime(true);
+foreach ($listeners as $listener) {
+ $emitter->once('event', $listener);
+}
+$time = microtime(true) - $start;
+echo 'Adding ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
+
+$start = microtime(true);
+foreach ($listeners as $listener) {
+ $emitter->removeListener('event', $listener);
+}
+$time = microtime(true) - $start;
+echo 'Removing ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;
diff --git a/vendor/evenement/evenement/phpunit.xml.dist b/vendor/evenement/evenement/phpunit.xml.dist
new file mode 100755
index 0000000..70bc693
--- /dev/null
+++ b/vendor/evenement/evenement/phpunit.xml.dist
@@ -0,0 +1,24 @@
+
+
+
+
+
+ ./tests/Evenement/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/vendor/evenement/evenement/src/Evenement/EventEmitter.php b/vendor/evenement/evenement/src/Evenement/EventEmitter.php
new file mode 100755
index 0000000..db189b9
--- /dev/null
+++ b/vendor/evenement/evenement/src/Evenement/EventEmitter.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+class EventEmitter implements EventEmitterInterface
+{
+ use EventEmitterTrait;
+}
diff --git a/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php b/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
new file mode 100755
index 0000000..310631a
--- /dev/null
+++ b/vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+interface EventEmitterInterface
+{
+ public function on($event, callable $listener);
+ public function once($event, callable $listener);
+ public function removeListener($event, callable $listener);
+ public function removeAllListeners($event = null);
+ public function listeners($event = null);
+ public function emit($event, array $arguments = []);
+}
diff --git a/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php b/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
new file mode 100755
index 0000000..a78e65c
--- /dev/null
+++ b/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
@@ -0,0 +1,135 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+use InvalidArgumentException;
+
+trait EventEmitterTrait
+{
+ protected $listeners = [];
+ protected $onceListeners = [];
+
+ public function on($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->listeners[$event])) {
+ $this->listeners[$event] = [];
+ }
+
+ $this->listeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function once($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->onceListeners[$event])) {
+ $this->onceListeners[$event] = [];
+ }
+
+ $this->onceListeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function removeListener($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (isset($this->listeners[$event])) {
+ $index = \array_search($listener, $this->listeners[$event], true);
+ if (false !== $index) {
+ unset($this->listeners[$event][$index]);
+ if (\count($this->listeners[$event]) === 0) {
+ unset($this->listeners[$event]);
+ }
+ }
+ }
+
+ if (isset($this->onceListeners[$event])) {
+ $index = \array_search($listener, $this->onceListeners[$event], true);
+ if (false !== $index) {
+ unset($this->onceListeners[$event][$index]);
+ if (\count($this->onceListeners[$event]) === 0) {
+ unset($this->onceListeners[$event]);
+ }
+ }
+ }
+ }
+
+ public function removeAllListeners($event = null)
+ {
+ if ($event !== null) {
+ unset($this->listeners[$event]);
+ } else {
+ $this->listeners = [];
+ }
+
+ if ($event !== null) {
+ unset($this->onceListeners[$event]);
+ } else {
+ $this->onceListeners = [];
+ }
+ }
+
+ public function listeners($event = null): array
+ {
+ if ($event === null) {
+ $events = [];
+ $eventNames = \array_unique(
+ \array_merge(\array_keys($this->listeners), \array_keys($this->onceListeners))
+ );
+ foreach ($eventNames as $eventName) {
+ $events[$eventName] = \array_merge(
+ isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [],
+ isset($this->onceListeners[$eventName]) ? $this->onceListeners[$eventName] : []
+ );
+ }
+ return $events;
+ }
+
+ return \array_merge(
+ isset($this->listeners[$event]) ? $this->listeners[$event] : [],
+ isset($this->onceListeners[$event]) ? $this->onceListeners[$event] : []
+ );
+ }
+
+ public function emit($event, array $arguments = [])
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (isset($this->listeners[$event])) {
+ foreach ($this->listeners[$event] as $listener) {
+ $listener(...$arguments);
+ }
+ }
+
+ if (isset($this->onceListeners[$event])) {
+ $listeners = $this->onceListeners[$event];
+ unset($this->onceListeners[$event]);
+ foreach ($listeners as $listener) {
+ $listener(...$arguments);
+ }
+ }
+ }
+}
diff --git a/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php b/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php
new file mode 100755
index 0000000..28f3011
--- /dev/null
+++ b/vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php
@@ -0,0 +1,438 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+use Evenement\EventEmitter;
+use InvalidArgumentException;
+use PHPUnit\Framework\TestCase;
+
+class EventEmitterTest extends TestCase
+{
+ private $emitter;
+
+ public function setUp()
+ {
+ $this->emitter = new EventEmitter();
+ }
+
+ public function testAddListenerWithLambda()
+ {
+ $this->emitter->on('foo', function () {});
+ }
+
+ public function testAddListenerWithMethod()
+ {
+ $listener = new Listener();
+ $this->emitter->on('foo', [$listener, 'onFoo']);
+ }
+
+ public function testAddListenerWithStaticMethod()
+ {
+ $this->emitter->on('bar', ['Evenement\Tests\Listener', 'onBar']);
+ }
+
+ public function testAddListenerWithInvalidListener()
+ {
+ try {
+ $this->emitter->on('foo', 'not a callable');
+ $this->fail();
+ } catch (\Exception $e) {
+ } catch (\TypeError $e) {
+ }
+ }
+
+ public function testOnce()
+ {
+ $listenerCalled = 0;
+
+ $this->emitter->once('foo', function () use (&$listenerCalled) {
+ $listenerCalled++;
+ });
+
+ $this->assertSame(0, $listenerCalled);
+
+ $this->emitter->emit('foo');
+
+ $this->assertSame(1, $listenerCalled);
+
+ $this->emitter->emit('foo');
+
+ $this->assertSame(1, $listenerCalled);
+ }
+
+ public function testOnceWithArguments()
+ {
+ $capturedArgs = [];
+
+ $this->emitter->once('foo', function ($a, $b) use (&$capturedArgs) {
+ $capturedArgs = array($a, $b);
+ });
+
+ $this->emitter->emit('foo', array('a', 'b'));
+
+ $this->assertSame(array('a', 'b'), $capturedArgs);
+ }
+
+ public function testEmitWithoutArguments()
+ {
+ $listenerCalled = false;
+
+ $this->emitter->on('foo', function () use (&$listenerCalled) {
+ $listenerCalled = true;
+ });
+
+ $this->assertSame(false, $listenerCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(true, $listenerCalled);
+ }
+
+ public function testEmitWithOneArgument()
+ {
+ $test = $this;
+
+ $listenerCalled = false;
+
+ $this->emitter->on('foo', function ($value) use (&$listenerCalled, $test) {
+ $listenerCalled = true;
+
+ $test->assertSame('bar', $value);
+ });
+
+ $this->assertSame(false, $listenerCalled);
+ $this->emitter->emit('foo', ['bar']);
+ $this->assertSame(true, $listenerCalled);
+ }
+
+ public function testEmitWithTwoArguments()
+ {
+ $test = $this;
+
+ $listenerCalled = false;
+
+ $this->emitter->on('foo', function ($arg1, $arg2) use (&$listenerCalled, $test) {
+ $listenerCalled = true;
+
+ $test->assertSame('bar', $arg1);
+ $test->assertSame('baz', $arg2);
+ });
+
+ $this->assertSame(false, $listenerCalled);
+ $this->emitter->emit('foo', ['bar', 'baz']);
+ $this->assertSame(true, $listenerCalled);
+ }
+
+ public function testEmitWithNoListeners()
+ {
+ $this->emitter->emit('foo');
+ $this->emitter->emit('foo', ['bar']);
+ $this->emitter->emit('foo', ['bar', 'baz']);
+ }
+
+ public function testEmitWithTwoListeners()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(2, $listenersCalled);
+ }
+
+ public function testRemoveListenerMatching()
+ {
+ $listenersCalled = 0;
+
+ $listener = function () use (&$listenersCalled) {
+ $listenersCalled++;
+ };
+
+ $this->emitter->on('foo', $listener);
+ $this->emitter->removeListener('foo', $listener);
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(0, $listenersCalled);
+ }
+
+ public function testRemoveListenerNotMatching()
+ {
+ $listenersCalled = 0;
+
+ $listener = function () use (&$listenersCalled) {
+ $listenersCalled++;
+ };
+
+ $this->emitter->on('foo', $listener);
+ $this->emitter->removeListener('bar', $listener);
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(1, $listenersCalled);
+ }
+
+ public function testRemoveAllListenersMatching()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->removeAllListeners('foo');
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(0, $listenersCalled);
+ }
+
+ public function testRemoveAllListenersNotMatching()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->removeAllListeners('bar');
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->assertSame(1, $listenersCalled);
+ }
+
+ public function testRemoveAllListenersWithoutArguments()
+ {
+ $listenersCalled = 0;
+
+ $this->emitter->on('foo', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->on('bar', function () use (&$listenersCalled) {
+ $listenersCalled++;
+ });
+
+ $this->emitter->removeAllListeners();
+
+ $this->assertSame(0, $listenersCalled);
+ $this->emitter->emit('foo');
+ $this->emitter->emit('bar');
+ $this->assertSame(0, $listenersCalled);
+ }
+
+ public function testCallablesClosure()
+ {
+ $calledWith = null;
+
+ $this->emitter->on('foo', function ($data) use (&$calledWith) {
+ $calledWith = $data;
+ });
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame('bar', $calledWith);
+ }
+
+ public function testCallablesClass()
+ {
+ $listener = new Listener();
+ $this->emitter->on('foo', [$listener, 'onFoo']);
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame(['bar'], $listener->getData());
+ }
+
+
+ public function testCallablesClassInvoke()
+ {
+ $listener = new Listener();
+ $this->emitter->on('foo', $listener);
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame(['bar'], $listener->getMagicData());
+ }
+
+ public function testCallablesStaticClass()
+ {
+ $this->emitter->on('foo', '\Evenement\Tests\Listener::onBar');
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame(['bar'], Listener::getStaticData());
+ }
+
+ public function testCallablesFunction()
+ {
+ $this->emitter->on('foo', '\Evenement\Tests\setGlobalTestData');
+
+ $this->emitter->emit('foo', ['bar']);
+
+ self::assertSame('bar', $GLOBALS['evenement-evenement-test-data']);
+
+ unset($GLOBALS['evenement-evenement-test-data']);
+ }
+
+ public function testListeners()
+ {
+ $onA = function () {};
+ $onB = function () {};
+ $onC = function () {};
+ $onceA = function () {};
+ $onceB = function () {};
+ $onceC = function () {};
+
+ self::assertCount(0, $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onA);
+ self::assertCount(1, $this->emitter->listeners('event'));
+ self::assertSame([$onA], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceA);
+ self::assertCount(2, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceB);
+ self::assertCount(3, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onceA, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onB);
+ self::assertCount(4, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceA, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->removeListener('event', $onceA);
+ self::assertCount(3, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceB], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceC);
+ self::assertCount(4, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onceB, $onceC], $this->emitter->listeners('event'));
+ $this->emitter->on('event', $onC);
+ self::assertCount(5, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onC, $onceB, $onceC], $this->emitter->listeners('event'));
+ $this->emitter->once('event', $onceA);
+ self::assertCount(6, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onB, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->removeListener('event', $onB);
+ self::assertCount(5, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onC, $onceB, $onceC, $onceA], $this->emitter->listeners('event'));
+ $this->emitter->emit('event');
+ self::assertCount(2, $this->emitter->listeners('event'));
+ self::assertSame([$onA, $onC], $this->emitter->listeners('event'));
+ }
+
+ public function testOnceCallIsNotRemovedWhenWorkingOverOnceListeners()
+ {
+ $aCalled = false;
+ $aCallable = function () use (&$aCalled) {
+ $aCalled = true;
+ };
+ $bCalled = false;
+ $bCallable = function () use (&$bCalled, $aCallable) {
+ $bCalled = true;
+ $this->emitter->once('event', $aCallable);
+ };
+ $this->emitter->once('event', $bCallable);
+
+ self::assertFalse($aCalled);
+ self::assertFalse($bCalled);
+ $this->emitter->emit('event');
+
+ self::assertFalse($aCalled);
+ self::assertTrue($bCalled);
+ $this->emitter->emit('event');
+
+ self::assertTrue($aCalled);
+ self::assertTrue($bCalled);
+ }
+
+ public function testEventNameMustBeStringOn()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->on(null, function () {});
+ }
+
+ public function testEventNameMustBeStringOnce()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->once(null, function () {});
+ }
+
+ public function testEventNameMustBeStringRemoveListener()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->removeListener(null, function () {});
+ }
+
+ public function testEventNameMustBeStringEmit()
+ {
+ self::expectException(InvalidArgumentException::class);
+ self::expectExceptionMessage('event name must not be null');
+
+ $this->emitter->emit(null);
+ }
+
+ public function testListenersGetAll()
+ {
+ $a = function () {};
+ $b = function () {};
+ $c = function () {};
+ $d = function () {};
+
+ $this->emitter->once('event2', $c);
+ $this->emitter->on('event', $a);
+ $this->emitter->once('event', $b);
+ $this->emitter->on('event', $c);
+ $this->emitter->once('event', $d);
+
+ self::assertSame(
+ [
+ 'event' => [
+ $a,
+ $c,
+ $b,
+ $d,
+ ],
+ 'event2' => [
+ $c,
+ ],
+ ],
+ $this->emitter->listeners()
+ );
+ }
+
+ public function testOnceNestedCallRegression()
+ {
+ $first = 0;
+ $second = 0;
+
+ $this->emitter->once('event', function () use (&$first, &$second) {
+ $first++;
+ $this->emitter->once('event', function () use (&$second) {
+ $second++;
+ });
+ $this->emitter->emit('event');
+ });
+ $this->emitter->emit('event');
+
+ self::assertSame(1, $first);
+ self::assertSame(1, $second);
+ }
+}
diff --git a/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php b/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php
new file mode 100755
index 0000000..df17424
--- /dev/null
+++ b/vendor/evenement/evenement/tests/Evenement/Tests/Listener.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+class Listener
+{
+ private $data = [];
+
+ private $magicData = [];
+
+ private static $staticData = [];
+
+ public function onFoo($data)
+ {
+ $this->data[] = $data;
+ }
+
+ public function __invoke($data)
+ {
+ $this->magicData[] = $data;
+ }
+
+ public static function onBar($data)
+ {
+ self::$staticData[] = $data;
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function getMagicData()
+ {
+ return $this->magicData;
+ }
+
+ public static function getStaticData()
+ {
+ return self::$staticData;
+ }
+}
diff --git a/vendor/evenement/evenement/tests/Evenement/Tests/functions.php b/vendor/evenement/evenement/tests/Evenement/Tests/functions.php
new file mode 100755
index 0000000..7f11f5b
--- /dev/null
+++ b/vendor/evenement/evenement/tests/Evenement/Tests/functions.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+function setGlobalTestData($data)
+{
+ $GLOBALS['evenement-evenement-test-data'] = $data;
+}
diff --git a/vendor/guzzlehttp/guzzle/.php_cs b/vendor/guzzlehttp/guzzle/.php_cs
new file mode 100755
index 0000000..a8ace8a
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/.php_cs
@@ -0,0 +1,21 @@
+setRiskyAllowed(true)
+ ->setRules([
+ '@PSR2' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'declare_strict_types' => false,
+ 'concat_space' => ['spacing'=>'one'],
+ // 'ordered_imports' => true,
+ // 'phpdoc_align' => ['align'=>'vertical'],
+ // 'native_function_invocation' => true,
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__.'/src')
+ ->name('*.php')
+ )
+;
+
+return $config;
diff --git a/vendor/guzzlehttp/guzzle/CHANGELOG.md b/vendor/guzzlehttp/guzzle/CHANGELOG.md
new file mode 100755
index 0000000..6555749
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/CHANGELOG.md
@@ -0,0 +1,1304 @@
+# Change Log
+
+## 6.4.1 - 2019-10-23
+
+* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that
+* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar`
+
+## 6.4.0 - 2019-10-23
+
+* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108)
+* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081)
+* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161)
+* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163)
+* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242)
+* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284)
+* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273)
+* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335)
+* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362)
+
+## 6.3.3 - 2018-04-22
+
+* Fix: Default headers when decode_content is specified
+
+
+## 6.3.2 - 2018-03-26
+
+* Fix: Release process
+
+
+## 6.3.1 - 2018-03-26
+
+* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014)
+* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012)
+* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999)
+* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998)
+* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953)
+* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915)
+* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916)
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+
+## 6.3.0 - 2017-06-22
+
+* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
+* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
+* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
+* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
+* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
+* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
+* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
+* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
+* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
+* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
+* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
+* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
+* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
+* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
+* Improvement: Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)
+
+
++ Minor code cleanups, documentation fixes and clarifications.
+
+## 6.2.3 - 2017-02-28
+
+* Fix deprecations with guzzle/psr7 version 1.4
+
+## 6.2.2 - 2016-10-08
+
+* Allow to pass nullable Response to delay callable
+* Only add scheme when host is present
+* Fix drain case where content-length is the literal string zero
+* Obfuscate in-URL credentials in exceptions
+
+## 6.2.1 - 2016-07-18
+
+* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
+ https://httpoxy.org/
+* Fixing timeout bug with StreamHandler:
+ https://github.com/guzzle/guzzle/pull/1488
+* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
+ a server does not honor `Connection: close`.
+* Ignore URI fragment when sending requests.
+
+## 6.2.0 - 2016-03-21
+
+* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
+ https://github.com/guzzle/guzzle/pull/1389
+* Bug fix: Fix sleep calculation when waiting for delayed requests.
+ https://github.com/guzzle/guzzle/pull/1324
+* Feature: More flexible history containers.
+ https://github.com/guzzle/guzzle/pull/1373
+* Bug fix: defer sink stream opening in StreamHandler.
+ https://github.com/guzzle/guzzle/pull/1377
+* Bug fix: do not attempt to escape cookie values.
+ https://github.com/guzzle/guzzle/pull/1406
+* Feature: report original content encoding and length on decoded responses.
+ https://github.com/guzzle/guzzle/pull/1409
+* Bug fix: rewind seekable request bodies before dispatching to cURL.
+ https://github.com/guzzle/guzzle/pull/1422
+* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
+ https://github.com/guzzle/guzzle/pull/1367
+
+## 6.1.1 - 2015-11-22
+
+* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
+ https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
+* Feature: HandlerStack is now more generic.
+ https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
+* Bug fix: setting verify to false in the StreamHandler now disables peer
+ verification. https://github.com/guzzle/guzzle/issues/1256
+* Feature: Middleware now uses an exception factory, including more error
+ context. https://github.com/guzzle/guzzle/pull/1282
+* Feature: better support for disabled functions.
+ https://github.com/guzzle/guzzle/pull/1287
+* Bug fix: fixed regression where MockHandler was not using `sink`.
+ https://github.com/guzzle/guzzle/pull/1292
+
+## 6.1.0 - 2015-09-08
+
+* Feature: Added the `on_stats` request option to provide access to transfer
+ statistics for requests. https://github.com/guzzle/guzzle/pull/1202
+* Feature: Added the ability to persist session cookies in CookieJars.
+ https://github.com/guzzle/guzzle/pull/1195
+* Feature: Some compatibility updates for Google APP Engine
+ https://github.com/guzzle/guzzle/pull/1216
+* Feature: Added support for NO_PROXY to prevent the use of a proxy based on
+ a simple set of rules. https://github.com/guzzle/guzzle/pull/1197
+* Feature: Cookies can now contain square brackets.
+ https://github.com/guzzle/guzzle/pull/1237
+* Bug fix: Now correctly parsing `=` inside of quotes in Cookies.
+ https://github.com/guzzle/guzzle/pull/1232
+* Bug fix: Cusotm cURL options now correctly override curl options of the
+ same name. https://github.com/guzzle/guzzle/pull/1221
+* Bug fix: Content-Type header is now added when using an explicitly provided
+ multipart body. https://github.com/guzzle/guzzle/pull/1218
+* Bug fix: Now ignoring Set-Cookie headers that have no name.
+* Bug fix: Reason phrase is no longer cast to an int in some cases in the
+ cURL handler. https://github.com/guzzle/guzzle/pull/1187
+* Bug fix: Remove the Authorization header when redirecting if the Host
+ header changes. https://github.com/guzzle/guzzle/pull/1207
+* Bug fix: Cookie path matching fixes
+ https://github.com/guzzle/guzzle/issues/1129
+* Bug fix: Fixing the cURL `body_as_string` setting
+ https://github.com/guzzle/guzzle/pull/1201
+* Bug fix: quotes are no longer stripped when parsing cookies.
+ https://github.com/guzzle/guzzle/issues/1172
+* Bug fix: `form_params` and `query` now always uses the `&` separator.
+ https://github.com/guzzle/guzzle/pull/1163
+* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set.
+ https://github.com/guzzle/guzzle/pull/1189
+
+## 6.0.2 - 2015-07-04
+
+* Fixed a memory leak in the curl handlers in which references to callbacks
+ were not being removed by `curl_reset`.
+* Cookies are now extracted properly before redirects.
+* Cookies now allow more character ranges.
+* Decoded Content-Encoding responses are now modified to correctly reflect
+ their state if the encoding was automatically removed by a handler. This
+ means that the `Content-Encoding` header may be removed an the
+ `Content-Length` modified to reflect the message size after removing the
+ encoding.
+* Added a more explicit error message when trying to use `form_params` and
+ `multipart` in the same request.
+* Several fixes for HHVM support.
+* Functions are now conditionally required using an additional level of
+ indirection to help with global Composer installations.
+
+## 6.0.1 - 2015-05-27
+
+* Fixed a bug with serializing the `query` request option where the `&`
+ separator was missing.
+* Added a better error message for when `body` is provided as an array. Please
+ use `form_params` or `multipart` instead.
+* Various doc fixes.
+
+## 6.0.0 - 2015-05-26
+
+* See the UPGRADING.md document for more information.
+* Added `multipart` and `form_params` request options.
+* Added `synchronous` request option.
+* Added the `on_headers` request option.
+* Fixed `expect` handling.
+* No longer adding default middlewares in the client ctor. These need to be
+ present on the provided handler in order to work.
+* Requests are no longer initiated when sending async requests with the
+ CurlMultiHandler. This prevents unexpected recursion from requests completing
+ while ticking the cURL loop.
+* Removed the semantics of setting `default` to `true`. This is no longer
+ required now that the cURL loop is not ticked for async requests.
+* Added request and response logging middleware.
+* No longer allowing self signed certificates when using the StreamHandler.
+* Ensuring that `sink` is valid if saving to a file.
+* Request exceptions now include a "handler context" which provides handler
+ specific contextual information.
+* Added `GuzzleHttp\RequestOptions` to allow request options to be applied
+ using constants.
+* `$maxHandles` has been removed from CurlMultiHandler.
+* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package.
+
+## 5.3.0 - 2015-05-19
+
+* Mock now supports `save_to`
+* Marked `AbstractRequestEvent::getTransaction()` as public.
+* Fixed a bug in which multiple headers using different casing would overwrite
+ previous headers in the associative array.
+* Added `Utils::getDefaultHandler()`
+* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated.
+* URL scheme is now always lowercased.
+
+## 6.0.0-beta.1
+
+* Requires PHP >= 5.5
+* Updated to use PSR-7
+ * Requires immutable messages, which basically means an event based system
+ owned by a request instance is no longer possible.
+ * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7).
+ * Removed the dependency on `guzzlehttp/streams`. These stream abstractions
+ are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7`
+ namespace.
+* Added middleware and handler system
+ * Replaced the Guzzle event and subscriber system with a middleware system.
+ * No longer depends on RingPHP, but rather places the HTTP handlers directly
+ in Guzzle, operating on PSR-7 messages.
+ * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which
+ means the `guzzlehttp/retry-subscriber` is now obsolete.
+ * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`.
+* Asynchronous responses
+ * No longer supports the `future` request option to send an async request.
+ Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`,
+ `getAsync`, etc.).
+ * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid
+ recursion required by chaining and forwarding react promises. See
+ https://github.com/guzzle/promises
+ * Added `requestAsync` and `sendAsync` to send request asynchronously.
+ * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests
+ asynchronously.
+* Request options
+ * POST and form updates
+ * Added the `form_fields` and `form_files` request options.
+ * Removed the `GuzzleHttp\Post` namespace.
+ * The `body` request option no longer accepts an array for POST requests.
+ * The `exceptions` request option has been deprecated in favor of the
+ `http_errors` request options.
+ * The `save_to` request option has been deprecated in favor of `sink` request
+ option.
+* Clients no longer accept an array of URI template string and variables for
+ URI variables. You will need to expand URI templates before passing them
+ into a client constructor or request method.
+* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are
+ now magic methods that will send synchronous requests.
+* Replaced `Utils.php` with plain functions in `functions.php`.
+* Removed `GuzzleHttp\Collection`.
+* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as
+ an array.
+* Removed `GuzzleHttp\Query`. Query string handling is now handled using an
+ associative array passed into the `query` request option. The query string
+ is serialized using PHP's `http_build_query`. If you need more control, you
+ can pass the query string in as a string.
+* `GuzzleHttp\QueryParser` has been replaced with the
+ `GuzzleHttp\Psr7\parse_query`.
+
+## 5.2.0 - 2015-01-27
+
+* Added `AppliesHeadersInterface` to make applying headers to a request based
+ on the body more generic and not specific to `PostBodyInterface`.
+* Reduced the number of stack frames needed to send requests.
+* Nested futures are now resolved in the client rather than the RequestFsm
+* Finishing state transitions is now handled in the RequestFsm rather than the
+ RingBridge.
+* Added a guard in the Pool class to not use recursion for request retries.
+
+## 5.1.0 - 2014-12-19
+
+* Pool class no longer uses recursion when a request is intercepted.
+* The size of a Pool can now be dynamically adjusted using a callback.
+ See https://github.com/guzzle/guzzle/pull/943.
+* Setting a request option to `null` when creating a request with a client will
+ ensure that the option is not set. This allows you to overwrite default
+ request options on a per-request basis.
+ See https://github.com/guzzle/guzzle/pull/937.
+* Added the ability to limit which protocols are allowed for redirects by
+ specifying a `protocols` array in the `allow_redirects` request option.
+* Nested futures due to retries are now resolved when waiting for synchronous
+ responses. See https://github.com/guzzle/guzzle/pull/947.
+* `"0"` is now an allowed URI path. See
+ https://github.com/guzzle/guzzle/pull/935.
+* `Query` no longer typehints on the `$query` argument in the constructor,
+ allowing for strings and arrays.
+* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle
+ specific exceptions if necessary.
+
+## 5.0.3 - 2014-11-03
+
+This change updates query strings so that they are treated as un-encoded values
+by default where the value represents an un-encoded value to send over the
+wire. A Query object then encodes the value before sending over the wire. This
+means that even value query string values (e.g., ":") are url encoded. This
+makes the Query class match PHP's http_build_query function. However, if you
+want to send requests over the wire using valid query string characters that do
+not need to be encoded, then you can provide a string to Url::setQuery() and
+pass true as the second argument to specify that the query string is a raw
+string that should not be parsed or encoded (unless a call to getQuery() is
+subsequently made, forcing the query-string to be converted into a Query
+object).
+
+## 5.0.2 - 2014-10-30
+
+* Added a trailing `\r\n` to multipart/form-data payloads. See
+ https://github.com/guzzle/guzzle/pull/871
+* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs.
+* Status codes are now returned as integers. See
+ https://github.com/guzzle/guzzle/issues/881
+* No longer overwriting an existing `application/x-www-form-urlencoded` header
+ when sending POST requests, allowing for customized headers. See
+ https://github.com/guzzle/guzzle/issues/877
+* Improved path URL serialization.
+
+ * No longer double percent-encoding characters in the path or query string if
+ they are already encoded.
+ * Now properly encoding the supplied path to a URL object, instead of only
+ encoding ' ' and '?'.
+ * Note: This has been changed in 5.0.3 to now encode query string values by
+ default unless the `rawString` argument is provided when setting the query
+ string on a URL: Now allowing many more characters to be present in the
+ query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A
+
+## 5.0.1 - 2014-10-16
+
+Bugfix release.
+
+* Fixed an issue where connection errors still returned response object in
+ error and end events event though the response is unusable. This has been
+ corrected so that a response is not returned in the `getResponse` method of
+ these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867
+* Fixed an issue where transfer statistics were not being populated in the
+ RingBridge. https://github.com/guzzle/guzzle/issues/866
+
+## 5.0.0 - 2014-10-12
+
+Adding support for non-blocking responses and some minor API cleanup.
+
+### New Features
+
+* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`.
+* Added a public API for creating a default HTTP adapter.
+* Updated the redirect plugin to be non-blocking so that redirects are sent
+ concurrently. Other plugins like this can now be updated to be non-blocking.
+* Added a "progress" event so that you can get upload and download progress
+ events.
+* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers
+ requests concurrently using a capped pool size as efficiently as possible.
+* Added `hasListeners()` to EmitterInterface.
+* Removed `GuzzleHttp\ClientInterface::sendAll` and marked
+ `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the
+ recommended way).
+
+### Breaking changes
+
+The breaking changes in this release are relatively minor. The biggest thing to
+look out for is that request and response objects no longer implement fluent
+interfaces.
+
+* Removed the fluent interfaces (i.e., `return $this`) from requests,
+ responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`,
+ `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and
+ `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of
+ why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/.
+ This also makes the Guzzle message interfaces compatible with the current
+ PSR-7 message proposal.
+* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except
+ for the HTTP request functions from function.php, these functions are now
+ implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode`
+ moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to
+ `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to
+ `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be
+ `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php
+ caused problems for many users: they aren't PSR-4 compliant, require an
+ explicit include, and needed an if-guard to ensure that the functions are not
+ declared multiple times.
+* Rewrote adapter layer.
+ * Removing all classes from `GuzzleHttp\Adapter`, these are now
+ implemented as callables that are stored in `GuzzleHttp\Ring\Client`.
+ * Removed the concept of "parallel adapters". Sending requests serially or
+ concurrently is now handled using a single adapter.
+ * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The
+ Transaction object now exposes the request, response, and client as public
+ properties. The getters and setters have been removed.
+* Removed the "headers" event. This event was only useful for changing the
+ body a response once the headers of the response were known. You can implement
+ a similar behavior in a number of ways. One example might be to use a
+ FnStream that has access to the transaction being sent. For example, when the
+ first byte is written, you could check if the response headers match your
+ expectations, and if so, change the actual stream body that is being
+ written to.
+* Removed the `asArray` parameter from
+ `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+ value as an array, then use the newly added `getHeaderAsArray()` method of
+ `MessageInterface`. This change makes the Guzzle interfaces compatible with
+ the PSR-7 interfaces.
+* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add
+ custom request options using double-dispatch (this was an implementation
+ detail). Instead, you should now provide an associative array to the
+ constructor which is a mapping of the request option name mapping to a
+ function that applies the option value to a request.
+* Removed the concept of "throwImmediately" from exceptions and error events.
+ This control mechanism was used to stop a transfer of concurrent requests
+ from completing. This can now be handled by throwing the exception or by
+ cancelling a pool of requests or each outstanding future request individually.
+* Updated to "GuzzleHttp\Streams" 3.0.
+ * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a
+ `maxLen` parameter. This update makes the Guzzle streams project
+ compatible with the current PSR-7 proposal.
+ * `GuzzleHttp\Stream\Stream::__construct`,
+ `GuzzleHttp\Stream\Stream::factory`, and
+ `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second
+ argument. They now accept an associative array of options, including the
+ "size" key and "metadata" key which can be used to provide custom metadata.
+
+## 4.2.2 - 2014-09-08
+
+* Fixed a memory leak in the CurlAdapter when reusing cURL handles.
+* No longer using `request_fulluri` in stream adapter proxies.
+* Relative redirects are now based on the last response, not the first response.
+
+## 4.2.1 - 2014-08-19
+
+* Ensuring that the StreamAdapter does not always add a Content-Type header
+* Adding automated github releases with a phar and zip
+
+## 4.2.0 - 2014-08-17
+
+* Now merging in default options using a case-insensitive comparison.
+ Closes https://github.com/guzzle/guzzle/issues/767
+* Added the ability to automatically decode `Content-Encoding` response bodies
+ using the `decode_content` request option. This is set to `true` by default
+ to decode the response body if it comes over the wire with a
+ `Content-Encoding`. Set this value to `false` to disable decoding the
+ response content, and pass a string to provide a request `Accept-Encoding`
+ header and turn on automatic response decoding. This feature now allows you
+ to pass an `Accept-Encoding` header in the headers of a request but still
+ disable automatic response decoding.
+ Closes https://github.com/guzzle/guzzle/issues/764
+* Added the ability to throw an exception immediately when transferring
+ requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
+* Updating guzzlehttp/streams dependency to ~2.1
+* No longer utilizing the now deprecated namespaced methods from the stream
+ package.
+
+## 4.1.8 - 2014-08-14
+
+* Fixed an issue in the CurlFactory that caused setting the `stream=false`
+ request option to throw an exception.
+ See: https://github.com/guzzle/guzzle/issues/769
+* TransactionIterator now calls rewind on the inner iterator.
+ See: https://github.com/guzzle/guzzle/pull/765
+* You can now set the `Content-Type` header to `multipart/form-data`
+ when creating POST requests to force multipart bodies.
+ See https://github.com/guzzle/guzzle/issues/768
+
+## 4.1.7 - 2014-08-07
+
+* Fixed an error in the HistoryPlugin that caused the same request and response
+ to be logged multiple times when an HTTP protocol error occurs.
+* Ensuring that cURL does not add a default Content-Type when no Content-Type
+ has been supplied by the user. This prevents the adapter layer from modifying
+ the request that is sent over the wire after any listeners may have already
+ put the request in a desired state (e.g., signed the request).
+* Throwing an exception when you attempt to send requests that have the
+ "stream" set to true in parallel using the MultiAdapter.
+* Only calling curl_multi_select when there are active cURL handles. This was
+ previously changed and caused performance problems on some systems due to PHP
+ always selecting until the maximum select timeout.
+* Fixed a bug where multipart/form-data POST fields were not correctly
+ aggregated (e.g., values with "&").
+
+## 4.1.6 - 2014-08-03
+
+* Added helper methods to make it easier to represent messages as strings,
+ including getting the start line and getting headers as a string.
+
+## 4.1.5 - 2014-08-02
+
+* Automatically retrying cURL "Connection died, retrying a fresh connect"
+ errors when possible.
+* cURL implementation cleanup
+* Allowing multiple event subscriber listeners to be registered per event by
+ passing an array of arrays of listener configuration.
+
+## 4.1.4 - 2014-07-22
+
+* Fixed a bug that caused multi-part POST requests with more than one field to
+ serialize incorrectly.
+* Paths can now be set to "0"
+* `ResponseInterface::xml` now accepts a `libxml_options` option and added a
+ missing default argument that was required when parsing XML response bodies.
+* A `save_to` stream is now created lazily, which means that files are not
+ created on disk unless a request succeeds.
+
+## 4.1.3 - 2014-07-15
+
+* Various fixes to multipart/form-data POST uploads
+* Wrapping function.php in an if-statement to ensure Guzzle can be used
+ globally and in a Composer install
+* Fixed an issue with generating and merging in events to an event array
+* POST headers are only applied before sending a request to allow you to change
+ the query aggregator used before uploading
+* Added much more robust query string parsing
+* Fixed various parsing and normalization issues with URLs
+* Fixing an issue where multi-valued headers were not being utilized correctly
+ in the StreamAdapter
+
+## 4.1.2 - 2014-06-18
+
+* Added support for sending payloads with GET requests
+
+## 4.1.1 - 2014-06-08
+
+* Fixed an issue related to using custom message factory options in subclasses
+* Fixed an issue with nested form fields in a multi-part POST
+* Fixed an issue with using the `json` request option for POST requests
+* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar`
+
+## 4.1.0 - 2014-05-27
+
+* Added a `json` request option to easily serialize JSON payloads.
+* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON.
+* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`.
+* Added the ability to provide an emitter to a client in the client constructor.
+* Added the ability to persist a cookie session using $_SESSION.
+* Added a trait that can be used to add event listeners to an iterator.
+* Removed request method constants from RequestInterface.
+* Fixed warning when invalid request start-lines are received.
+* Updated MessageFactory to work with custom request option methods.
+* Updated cacert bundle to latest build.
+
+4.0.2 (2014-04-16)
+------------------
+
+* Proxy requests using the StreamAdapter now properly use request_fulluri (#632)
+* Added the ability to set scalars as POST fields (#628)
+
+## 4.0.1 - 2014-04-04
+
+* The HTTP status code of a response is now set as the exception code of
+ RequestException objects.
+* 303 redirects will now correctly switch from POST to GET requests.
+* The default parallel adapter of a client now correctly uses the MultiAdapter.
+* HasDataTrait now initializes the internal data array as an empty array so
+ that the toArray() method always returns an array.
+
+## 4.0.0 - 2014-03-29
+
+* For more information on the 4.0 transition, see:
+ http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/
+* For information on changes and upgrading, see:
+ https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+* Added `GuzzleHttp\batch()` as a convenience function for sending requests in
+ parallel without needing to write asynchronous code.
+* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`.
+ You can now pass a callable or an array of associative arrays where each
+ associative array contains the "fn", "priority", and "once" keys.
+
+## 4.0.0.rc-2 - 2014-03-25
+
+* Removed `getConfig()` and `setConfig()` from clients to avoid confusion
+ around whether things like base_url, message_factory, etc. should be able to
+ be retrieved or modified.
+* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface
+* functions.php functions were renamed using snake_case to match PHP idioms
+* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and
+ `GUZZLE_CURL_SELECT_TIMEOUT` environment variables
+* Added the ability to specify custom `sendAll()` event priorities
+* Added the ability to specify custom stream context options to the stream
+ adapter.
+* Added a functions.php function for `get_path()` and `set_path()`
+* CurlAdapter and MultiAdapter now use a callable to generate curl resources
+* MockAdapter now properly reads a body and emits a `headers` event
+* Updated Url class to check if a scheme and host are set before adding ":"
+ and "//". This allows empty Url (e.g., "") to be serialized as "".
+* Parsing invalid XML no longer emits warnings
+* Curl classes now properly throw AdapterExceptions
+* Various performance optimizations
+* Streams are created with the faster `Stream\create()` function
+* Marked deprecation_proxy() as internal
+* Test server is now a collection of static methods on a class
+
+## 4.0.0-rc.1 - 2014-03-15
+
+* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
+
+## 3.8.1 - 2014-01-28
+
+* Bug: Always using GET requests when redirecting from a 303 response
+* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
+ `Guzzle\Http\ClientInterface::setSslVerification()`
+* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
+* Bug: The body of a request can now be set to `"0"`
+* Sending PHP stream requests no longer forces `HTTP/1.0`
+* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
+ each sub-exception
+* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
+ clobbering everything).
+* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
+* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
+ For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
+* Now properly escaping the regular expression delimiter when matching Cookie domains.
+* Network access is now disabled when loading XML documents
+
+## 3.8.0 - 2013-12-05
+
+* Added the ability to define a POST name for a file
+* JSON response parsing now properly walks additionalProperties
+* cURL error code 18 is now retried automatically in the BackoffPlugin
+* Fixed a cURL error when URLs contain fragments
+* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
+ CurlExceptions
+* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
+* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
+* Fixed a bug that was encountered when parsing empty header parameters
+* UriTemplate now has a `setRegex()` method to match the docs
+* The `debug` request parameter now checks if it is truthy rather than if it exists
+* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
+* Added the ability to combine URLs using strict RFC 3986 compliance
+* Command objects can now return the validation errors encountered by the command
+* Various fixes to cache revalidation (#437 and 29797e5)
+* Various fixes to the AsyncPlugin
+* Cleaned up build scripts
+
+## 3.7.4 - 2013-10-02
+
+* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
+* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
+ (see https://github.com/aws/aws-sdk-php/issues/147)
+* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
+* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
+* Updated the bundled cacert.pem (#419)
+* OauthPlugin now supports adding authentication to headers or query string (#425)
+
+## 3.7.3 - 2013-09-08
+
+* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
+ `CommandTransferException`.
+* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
+* Schemas are only injected into response models when explicitly configured.
+* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
+ an EntityBody.
+* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
+* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
+* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
+* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
+* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests
+* Bug fix: Properly parsing headers that contain commas contained in quotes
+* Bug fix: mimetype guessing based on a filename is now case-insensitive
+
+## 3.7.2 - 2013-08-02
+
+* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
+ See https://github.com/guzzle/guzzle/issues/371
+* Bug fix: Cookie domains are now matched correctly according to RFC 6265
+ See https://github.com/guzzle/guzzle/issues/377
+* Bug fix: GET parameters are now used when calculating an OAuth signature
+* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
+* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
+* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
+ See https://github.com/guzzle/guzzle/issues/379
+* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
+ https://github.com/guzzle/guzzle/pull/380
+* cURL multi cleanup and optimizations
+
+## 3.7.1 - 2013-07-05
+
+* Bug fix: Setting default options on a client now works
+* Bug fix: Setting options on HEAD requests now works. See #352
+* Bug fix: Moving stream factory before send event to before building the stream. See #353
+* Bug fix: Cookies no longer match on IP addresses per RFC 6265
+* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
+* Added `cert` and `ssl_key` as request options
+* `Host` header can now diverge from the host part of a URL if the header is set manually
+* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
+* OAuth parameters are only added via the plugin if they aren't already set
+* Exceptions are now thrown when a URL cannot be parsed
+* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
+* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
+
+## 3.7.0 - 2013-06-10
+
+* See UPGRADING.md for more information on how to upgrade.
+* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
+ request. You can pass a 'request.options' configuration setting to a client to apply default request options to
+ every request created by a client (e.g. default query string variables, headers, curl options, etc.).
+* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
+ See `Guzzle\Http\StaticClient::mount`.
+* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
+ created by a command (e.g. custom headers, query string variables, timeout settings, etc.).
+* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
+ headers of a response
+* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
+ (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
+* ServiceBuilders now support storing and retrieving arbitrary data
+* CachePlugin can now purge all resources for a given URI
+* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
+* CachePlugin now uses the Vary header to determine if a resource is a cache hit
+* `Guzzle\Http\Message\Response` now implements `\Serializable`
+* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
+* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
+* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
+* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
+* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
+* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
+ Symfony users can still use the old version of Monolog.
+* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
+ Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
+* Several performance improvements to `Guzzle\Common\Collection`
+* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+* Added `Guzzle\Stream\StreamInterface::isRepeatable`
+* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
+* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
+* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
+* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
+* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
+* Removed `Guzzle\Http\Message\RequestInterface::canCache`
+* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
+* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
+* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
+ `Guzzle\Common\Version::$emitWarnings` to true.
+* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
+ `$request->getResponseBody()->isRepeatable()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
+ These will work through Guzzle 4.0
+* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
+* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
+* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
+* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+* Marked `Guzzle\Common\Collection::inject()` as deprecated.
+* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
+* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+* Always setting X-cache headers on cached responses
+* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+* Added `CacheStorageInterface::purge($url)`
+* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+## 3.6.0 - 2013-05-29
+
+* ServiceDescription now implements ToArrayInterface
+* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
+* Guzzle can now correctly parse incomplete URLs
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+* Added the ability to cast Model objects to a string to view debug information.
+
+## 3.5.0 - 2013-05-13
+
+* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
+* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove
+ itself from the EventDispatcher)
+* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
+* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
+* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
+ non-existent key
+* Bug: All __call() method arguments are now required (helps with mocking frameworks)
+* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
+ to help with refcount based garbage collection of resources created by sending a request
+* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
+* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
+ HistoryPlugin for a history.
+* Added a `responseBody` alias for the `response_body` location
+* Refactored internals to no longer rely on Response::getRequest()
+* HistoryPlugin can now be cast to a string
+* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
+ and responses that are sent over the wire
+* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
+
+## 3.4.3 - 2013-04-30
+
+* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
+* Added a check to re-extract the temp cacert bundle from the phar before sending each request
+
+## 3.4.2 - 2013-04-29
+
+* Bug fix: Stream objects now work correctly with "a" and "a+" modes
+* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
+* Bug fix: AsyncPlugin no longer forces HEAD requests
+* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
+* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
+* Setting a response on a request will write to the custom request body from the response body if one is specified
+* LogPlugin now writes to php://output when STDERR is undefined
+* Added the ability to set multiple POST files for the same key in a single call
+* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
+* Added the ability to queue CurlExceptions to the MockPlugin
+* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
+* Configuration loading now allows remote files
+
+## 3.4.1 - 2013-04-16
+
+* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
+ handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
+* Exceptions are now properly grouped when sending requests in parallel
+* Redirects are now properly aggregated when a multi transaction fails
+* Redirects now set the response on the original object even in the event of a failure
+* Bug fix: Model names are now properly set even when using $refs
+* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
+* Added support for oauth_callback in OAuth signatures
+* Added support for oauth_verifier in OAuth signatures
+* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
+
+## 3.4.0 - 2013-04-11
+
+* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
+* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
+* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
+* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
+* Bug fix: Added `number` type to service descriptions.
+* Bug fix: empty parameters are removed from an OAuth signature
+* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
+* Bug fix: Fixed "array to string" error when validating a union of types in a service description
+* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
+* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
+* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
+* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
+* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
+ the Content-Type can be determined based on the entity body or the path of the request.
+* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
+* Added support for a PSR-3 LogAdapter.
+* Added a `command.after_prepare` event
+* Added `oauth_callback` parameter to the OauthPlugin
+* Added the ability to create a custom stream class when using a stream factory
+* Added a CachingEntityBody decorator
+* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
+* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
+* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
+* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
+ means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
+ POST fields or files (the latter is only used when emulating a form POST in the browser).
+* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
+
+## 3.3.1 - 2013-03-10
+
+* Added the ability to create PHP streaming responses from HTTP requests
+* Bug fix: Running any filters when parsing response headers with service descriptions
+* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
+* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
+ response location visitors.
+* Bug fix: Removed the possibility of creating configuration files with circular dependencies
+* RequestFactory::create() now uses the key of a POST file when setting the POST file name
+* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
+
+## 3.3.0 - 2013-03-03
+
+* A large number of performance optimizations have been made
+* Bug fix: Added 'wb' as a valid write mode for streams
+* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
+* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
+* BC: Removed `Guzzle\Http\Utils` class
+* BC: Setting a service description on a client will no longer modify the client's command factories.
+* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
+ the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
+ lowercase
+* Operation parameter objects are now lazy loaded internally
+* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
+* Added support for instantiating responseType=class responseClass classes. Classes must implement
+ `Guzzle\Service\Command\ResponseClassInterface`
+* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
+ additional properties also support locations and can be used to parse JSON responses where the outermost part of the
+ JSON is an array
+* Added support for nested renaming of JSON models (rename sentAs to name)
+* CachePlugin
+ * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
+ * Debug headers can now added to cached response in the CachePlugin
+
+## 3.2.0 - 2013-02-14
+
+* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
+* URLs with no path no longer contain a "/" by default
+* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
+* BadResponseException no longer includes the full request and response message
+* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
+* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
+* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
+* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
+* xmlEncoding can now be customized for the XML declaration of a XML service description operation
+* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
+ aggregation and no longer uses callbacks
+* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
+* Bug fix: Filters were not always invoked for array service description parameters
+* Bug fix: Redirects now use a target response body rather than a temporary response body
+* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
+* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
+
+## 3.1.2 - 2013-01-27
+
+* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
+ response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
+* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
+* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
+* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
+* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
+
+## 3.1.1 - 2013-01-20
+
+* Adding wildcard support to Guzzle\Common\Collection::getPath()
+* Adding alias support to ServiceBuilder configs
+* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
+
+## 3.1.0 - 2013-01-12
+
+* BC: CurlException now extends from RequestException rather than BadResponseException
+* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
+* Added getData to ServiceDescriptionInterface
+* Added context array to RequestInterface::setState()
+* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
+* Bug: Adding required content-type when JSON request visitor adds JSON to a command
+* Bug: Fixing the serialization of a service description with custom data
+* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
+ an array of successful and failed responses
+* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
+* Added Guzzle\Http\IoEmittingEntityBody
+* Moved command filtration from validators to location visitors
+* Added `extends` attributes to service description parameters
+* Added getModels to ServiceDescriptionInterface
+
+## 3.0.7 - 2012-12-19
+
+* Fixing phar detection when forcing a cacert to system if null or true
+* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
+* Cleaning up `Guzzle\Common\Collection::inject` method
+* Adding a response_body location to service descriptions
+
+## 3.0.6 - 2012-12-09
+
+* CurlMulti performance improvements
+* Adding setErrorResponses() to Operation
+* composer.json tweaks
+
+## 3.0.5 - 2012-11-18
+
+* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
+* Bug: Response body can now be a string containing "0"
+* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
+* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
+* Added support for XML attributes in service description responses
+* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
+* Added better mimetype guessing to requests and post files
+
+## 3.0.4 - 2012-11-11
+
+* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
+* Bug: Cookies can now be added that have a name, domain, or value set to "0"
+* Bug: Using the system cacert bundle when using the Phar
+* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
+* Enhanced cookie jar de-duplication
+* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
+* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
+* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
+
+## 3.0.3 - 2012-11-04
+
+* Implementing redirects in PHP rather than cURL
+* Added PECL URI template extension and using as default parser if available
+* Bug: Fixed Content-Length parsing of Response factory
+* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
+* Adding ToArrayInterface throughout library
+* Fixing OauthPlugin to create unique nonce values per request
+
+## 3.0.2 - 2012-10-25
+
+* Magic methods are enabled by default on clients
+* Magic methods return the result of a command
+* Service clients no longer require a base_url option in the factory
+* Bug: Fixed an issue with URI templates where null template variables were being expanded
+
+## 3.0.1 - 2012-10-22
+
+* Models can now be used like regular collection objects by calling filter, map, etc.
+* Models no longer require a Parameter structure or initial data in the constructor
+* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
+
+## 3.0.0 - 2012-10-15
+
+* Rewrote service description format to be based on Swagger
+ * Now based on JSON schema
+ * Added nested input structures and nested response models
+ * Support for JSON and XML input and output models
+ * Renamed `commands` to `operations`
+ * Removed dot class notation
+ * Removed custom types
+* Broke the project into smaller top-level namespaces to be more component friendly
+* Removed support for XML configs and descriptions. Use arrays or JSON files.
+* Removed the Validation component and Inspector
+* Moved all cookie code to Guzzle\Plugin\Cookie
+* Magic methods on a Guzzle\Service\Client now return the command un-executed.
+* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
+* Now shipping with cURL's CA certs and using it by default
+* Added previousResponse() method to response objects
+* No longer sending Accept and Accept-Encoding headers on every request
+* Only sending an Expect header by default when a payload is greater than 1MB
+* Added/moved client options:
+ * curl.blacklist to curl.option.blacklist
+ * Added ssl.certificate_authority
+* Added a Guzzle\Iterator component
+* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
+* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
+* Added a more robust caching plugin
+* Added setBody to response objects
+* Updating LogPlugin to use a more flexible MessageFormatter
+* Added a completely revamped build process
+* Cleaning up Collection class and removing default values from the get method
+* Fixed ZF2 cache adapters
+
+## 2.8.8 - 2012-10-15
+
+* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
+
+## 2.8.7 - 2012-09-30
+
+* Bug: Fixed config file aliases for JSON includes
+* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
+* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
+* Bug: Hardening request and response parsing to account for missing parts
+* Bug: Fixed PEAR packaging
+* Bug: Fixed Request::getInfo
+* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
+* Adding the ability for the namespace Iterator factory to look in multiple directories
+* Added more getters/setters/removers from service descriptions
+* Added the ability to remove POST fields from OAuth signatures
+* OAuth plugin now supports 2-legged OAuth
+
+## 2.8.6 - 2012-09-05
+
+* Added the ability to modify and build service descriptions
+* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
+* Added a `json` parameter location
+* Now allowing dot notation for classes in the CacheAdapterFactory
+* Using the union of two arrays rather than an array_merge when extending service builder services and service params
+* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
+ in service builder config files.
+* Services defined in two different config files that include one another will by default replace the previously
+ defined service, but you can now create services that extend themselves and merge their settings over the previous
+* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
+ '_default' with a default JSON configuration file.
+
+## 2.8.5 - 2012-08-29
+
+* Bug: Suppressed empty arrays from URI templates
+* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
+* Added support for HTTP responses that do not contain a reason phrase in the start-line
+* AbstractCommand commands are now invokable
+* Added a way to get the data used when signing an Oauth request before a request is sent
+
+## 2.8.4 - 2012-08-15
+
+* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
+* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
+* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
+* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
+* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
+* Added additional response status codes
+* Removed SSL information from the default User-Agent header
+* DELETE requests can now send an entity body
+* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
+* Added the ability of the MockPlugin to consume mocked request bodies
+* LogPlugin now exposes request and response objects in the extras array
+
+## 2.8.3 - 2012-07-30
+
+* Bug: Fixed a case where empty POST requests were sent as GET requests
+* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
+* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
+* Added multiple inheritance to service description commands
+* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()`
+* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
+* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
+
+## 2.8.2 - 2012-07-24
+
+* Bug: Query string values set to 0 are no longer dropped from the query string
+* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`
+* Bug: `+` is now treated as an encoded space when parsing query strings
+* QueryString and Collection performance improvements
+* Allowing dot notation for class paths in filters attribute of a service descriptions
+
+## 2.8.1 - 2012-07-16
+
+* Loosening Event Dispatcher dependency
+* POST redirects can now be customized using CURLOPT_POSTREDIR
+
+## 2.8.0 - 2012-07-15
+
+* BC: Guzzle\Http\Query
+ * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
+ * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
+ * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
+ * Changed the aggregation functions of QueryString to be static methods
+ * Can now use fromString() with querystrings that have a leading ?
+* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters
+* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
+* Cookies are no longer URL decoded by default
+* Bug: URI template variables set to null are no longer expanded
+
+## 2.7.2 - 2012-07-02
+
+* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
+* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
+* CachePlugin now allows for a custom request parameter function to check if a request can be cached
+* Bug fix: CachePlugin now only caches GET and HEAD requests by default
+* Bug fix: Using header glue when transferring headers over the wire
+* Allowing deeply nested arrays for composite variables in URI templates
+* Batch divisors can now return iterators or arrays
+
+## 2.7.1 - 2012-06-26
+
+* Minor patch to update version number in UA string
+* Updating build process
+
+## 2.7.0 - 2012-06-25
+
+* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
+* BC: Removed magic setX methods from commands
+* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
+* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
+* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
+* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
+* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
+* Added the ability to set POST fields and files in a service description
+* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
+* Adding a command.before_prepare event to clients
+* Added BatchClosureTransfer and BatchClosureDivisor
+* BatchTransferException now includes references to the batch divisor and transfer strategies
+* Fixed some tests so that they pass more reliably
+* Added Guzzle\Common\Log\ArrayLogAdapter
+
+## 2.6.6 - 2012-06-10
+
+* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
+* BC: Removing Guzzle\Service\Command\CommandSet
+* Adding generic batching system (replaces the batch queue plugin and command set)
+* Updating ZF cache and log adapters and now using ZF's composer repository
+* Bug: Setting the name of each ApiParam when creating through an ApiCommand
+* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
+* Bug: Changed the default cookie header casing back to 'Cookie'
+
+## 2.6.5 - 2012-06-03
+
+* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
+* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
+* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
+* BC: Renaming methods in the CookieJarInterface
+* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
+* Making the default glue for HTTP headers ';' instead of ','
+* Adding a removeValue to Guzzle\Http\Message\Header
+* Adding getCookies() to request interface.
+* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
+
+## 2.6.4 - 2012-05-30
+
+* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
+* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
+* Bug: Fixing magic method command calls on clients
+* Bug: Email constraint only validates strings
+* Bug: Aggregate POST fields when POST files are present in curl handle
+* Bug: Fixing default User-Agent header
+* Bug: Only appending or prepending parameters in commands if they are specified
+* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
+* Allowing the use of dot notation for class namespaces when using instance_of constraint
+* Added any_match validation constraint
+* Added an AsyncPlugin
+* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
+* Allowing the result of a command object to be changed
+* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
+
+## 2.6.3 - 2012-05-23
+
+* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
+* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
+* You can now use an array of data when creating PUT request bodies in the request factory.
+* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
+* [Http] Adding support for Content-Type in multipart POST uploads per upload
+* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
+* Adding more POST data operations for easier manipulation of POST data.
+* You can now set empty POST fields.
+* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
+* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
+* CS updates
+
+## 2.6.2 - 2012-05-19
+
+* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
+
+## 2.6.1 - 2012-05-19
+
+* [BC] Removing 'path' support in service descriptions. Use 'uri'.
+* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
+* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it.
+* [BC] Removing Guzzle\Common\XmlElement.
+* All commands, both dynamic and concrete, have ApiCommand objects.
+* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
+* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
+* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
+
+## 2.6.0 - 2012-05-15
+
+* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
+* [BC] Executing a Command returns the result of the command rather than the command
+* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
+* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
+* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
+* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
+* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
+* [BC] Guzzle\Guzzle is now deprecated
+* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
+* Adding Guzzle\Version class to give version information about Guzzle
+* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
+* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
+* ServiceDescription and ServiceBuilder are now cacheable using similar configs
+* Changing the format of XML and JSON service builder configs. Backwards compatible.
+* Cleaned up Cookie parsing
+* Trimming the default Guzzle User-Agent header
+* Adding a setOnComplete() method to Commands that is called when a command completes
+* Keeping track of requests that were mocked in the MockPlugin
+* Fixed a caching bug in the CacheAdapterFactory
+* Inspector objects can be injected into a Command object
+* Refactoring a lot of code and tests to be case insensitive when dealing with headers
+* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
+* Adding the ability to set global option overrides to service builder configs
+* Adding the ability to include other service builder config files from within XML and JSON files
+* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
+
+## 2.5.0 - 2012-05-08
+
+* Major performance improvements
+* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.
+* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
+* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}"
+* Added the ability to passed parameters to all requests created by a client
+* Added callback functionality to the ExponentialBackoffPlugin
+* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
+* Rewinding request stream bodies when retrying requests
+* Exception is thrown when JSON response body cannot be decoded
+* Added configurable magic method calls to clients and commands. This is off by default.
+* Fixed a defect that added a hash to every parsed URL part
+* Fixed duplicate none generation for OauthPlugin.
+* Emitting an event each time a client is generated by a ServiceBuilder
+* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
+* cache.* request parameters should be renamed to params.cache.*
+* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle.
+* Added the ability to disable type validation of service descriptions
+* ServiceDescriptions and ServiceBuilders are now Serializable
diff --git a/vendor/guzzlehttp/guzzle/Dockerfile b/vendor/guzzlehttp/guzzle/Dockerfile
new file mode 100755
index 0000000..f6a0952
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/Dockerfile
@@ -0,0 +1,18 @@
+FROM composer:latest as setup
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+RUN set -xe \
+ && composer init --name=guzzlehttp/test --description="Simple project for testing Guzzle scripts" --author="Márk Sági-Kazár " --no-interaction \
+ && composer require guzzlehttp/guzzle
+
+
+FROM php:7.3
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+COPY --from=setup /guzzle /guzzle
diff --git a/vendor/guzzlehttp/guzzle/LICENSE b/vendor/guzzlehttp/guzzle/LICENSE
new file mode 100755
index 0000000..50a177b
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/guzzlehttp/guzzle/README.md b/vendor/guzzlehttp/guzzle/README.md
new file mode 100755
index 0000000..a5ef18a
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/README.md
@@ -0,0 +1,90 @@
+Guzzle, PHP HTTP client
+=======================
+
+[](https://github.com/guzzle/guzzle/releases)
+[](https://travis-ci.org/guzzle/guzzle)
+[](https://packagist.org/packages/guzzlehttp/guzzle)
+
+Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
+trivial to integrate with web services.
+
+- Simple interface for building query strings, POST requests, streaming large
+ uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
+ etc...
+- Can send both synchronous and asynchronous requests using the same interface.
+- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
+ to utilize other PSR-7 compatible libraries with Guzzle.
+- Abstracts away the underlying HTTP transport, allowing you to write
+ environment and transport agnostic code; i.e., no hard dependency on cURL,
+ PHP streams, sockets, or non-blocking event loops.
+- Middleware system allows you to augment and compose client behavior.
+
+```php
+$client = new \GuzzleHttp\Client();
+$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
+
+echo $response->getStatusCode(); # 200
+echo $response->getHeaderLine('content-type'); # 'application/json; charset=utf8'
+echo $response->getBody(); # '{"id": 1420053, "name": "guzzle", ...}'
+
+# Send an asynchronous request.
+$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
+$promise = $client->sendAsync($request)->then(function ($response) {
+ echo 'I completed! ' . $response->getBody();
+});
+
+$promise->wait();
+```
+
+## Help and docs
+
+- [Documentation](http://guzzlephp.org/)
+- [Stack Overflow](http://stackoverflow.com/questions/tagged/guzzle)
+- [Gitter](https://gitter.im/guzzle/guzzle)
+
+
+## Installing Guzzle
+
+The recommended way to install Guzzle is through
+[Composer](http://getcomposer.org).
+
+```bash
+# Install Composer
+curl -sS https://getcomposer.org/installer | php
+```
+
+Next, run the Composer command to install the latest stable version of Guzzle:
+
+```bash
+composer require guzzlehttp/guzzle
+```
+
+After installing, you need to require Composer's autoloader:
+
+```php
+require 'vendor/autoload.php';
+```
+
+You can then later update Guzzle using composer:
+
+ ```bash
+composer update
+ ```
+
+
+## Version Guidance
+
+| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
+|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------|
+| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 |
+| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 |
+| 5.x | Maintained | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 |
+| 6.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 |
+
+[guzzle-3-repo]: https://github.com/guzzle/guzzle3
+[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
+[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
+[guzzle-6-repo]: https://github.com/guzzle/guzzle
+[guzzle-3-docs]: http://guzzle3.readthedocs.org
+[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/
+[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/
diff --git a/vendor/guzzlehttp/guzzle/UPGRADING.md b/vendor/guzzlehttp/guzzle/UPGRADING.md
new file mode 100755
index 0000000..91d1dcc
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/UPGRADING.md
@@ -0,0 +1,1203 @@
+Guzzle Upgrade Guide
+====================
+
+5.0 to 6.0
+----------
+
+Guzzle now uses [PSR-7](http://www.php-fig.org/psr/psr-7/) for HTTP messages.
+Due to the fact that these messages are immutable, this prompted a refactoring
+of Guzzle to use a middleware based system rather than an event system. Any
+HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be
+updated to work with the new immutable PSR-7 request and response objects. Any
+event listeners or subscribers need to be updated to become middleware
+functions that wrap handlers (or are injected into a
+`GuzzleHttp\HandlerStack`).
+
+- Removed `GuzzleHttp\BatchResults`
+- Removed `GuzzleHttp\Collection`
+- Removed `GuzzleHttp\HasDataTrait`
+- Removed `GuzzleHttp\ToArrayInterface`
+- The `guzzlehttp/streams` dependency has been removed. Stream functionality
+ is now present in the `GuzzleHttp\Psr7` namespace provided by the
+ `guzzlehttp/psr7` package.
+- Guzzle no longer uses ReactPHP promises and now uses the
+ `guzzlehttp/promises` library. We use a custom promise library for three
+ significant reasons:
+ 1. React promises (at the time of writing this) are recursive. Promise
+ chaining and promise resolution will eventually blow the stack. Guzzle
+ promises are not recursive as they use a sort of trampolining technique.
+ Note: there has been movement in the React project to modify promises to
+ no longer utilize recursion.
+ 2. Guzzle needs to have the ability to synchronously block on a promise to
+ wait for a result. Guzzle promises allows this functionality (and does
+ not require the use of recursion).
+ 3. Because we need to be able to wait on a result, doing so using React
+ promises requires wrapping react promises with RingPHP futures. This
+ overhead is no longer needed, reducing stack sizes, reducing complexity,
+ and improving performance.
+- `GuzzleHttp\Mimetypes` has been moved to a function in
+ `GuzzleHttp\Psr7\mimetype_from_extension` and
+ `GuzzleHttp\Psr7\mimetype_from_filename`.
+- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
+ strings must now be passed into request objects as strings, or provided to
+ the `query` request option when creating requests with clients. The `query`
+ option uses PHP's `http_build_query` to convert an array to a string. If you
+ need a different serialization technique, you will need to pass the query
+ string in as a string. There are a couple helper functions that will make
+ working with query strings easier: `GuzzleHttp\Psr7\parse_query` and
+ `GuzzleHttp\Psr7\build_query`.
+- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
+ system based on PSR-7, using RingPHP and it's middleware system as well adds
+ more complexity than the benefits it provides. All HTTP handlers that were
+ present in RingPHP have been modified to work directly with PSR-7 messages
+ and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces
+ complexity in Guzzle, removes a dependency, and improves performance. RingPHP
+ will be maintained for Guzzle 5 support, but will no longer be a part of
+ Guzzle 6.
+- As Guzzle now uses a middleware based systems the event system and RingPHP
+ integration has been removed. Note: while the event system has been removed,
+ it is possible to add your own type of event system that is powered by the
+ middleware system.
+ - Removed the `Event` namespace.
+ - Removed the `Subscriber` namespace.
+ - Removed `Transaction` class
+ - Removed `RequestFsm`
+ - Removed `RingBridge`
+ - `GuzzleHttp\Subscriber\Cookie` is now provided by
+ `GuzzleHttp\Middleware::cookies`
+ - `GuzzleHttp\Subscriber\HttpError` is now provided by
+ `GuzzleHttp\Middleware::httpError`
+ - `GuzzleHttp\Subscriber\History` is now provided by
+ `GuzzleHttp\Middleware::history`
+ - `GuzzleHttp\Subscriber\Mock` is now provided by
+ `GuzzleHttp\Handler\MockHandler`
+ - `GuzzleHttp\Subscriber\Prepare` is now provided by
+ `GuzzleHttp\PrepareBodyMiddleware`
+ - `GuzzleHttp\Subscriber\Redirect` is now provided by
+ `GuzzleHttp\RedirectMiddleware`
+- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
+ `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
+- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
+ functions under the `GuzzleHttp` namespace. This requires either a Composer
+ based autoloader or you to include functions.php.
+- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
+ `GuzzleHttp\ClientInterface::getConfig`.
+- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
+- The `json` and `xml` methods of response objects has been removed. With the
+ migration to strictly adhering to PSR-7 as the interface for Guzzle messages,
+ adding methods to message interfaces would actually require Guzzle messages
+ to extend from PSR-7 messages rather then work with them directly.
+
+## Migrating to middleware
+
+The change to PSR-7 unfortunately required significant refactoring to Guzzle
+due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event
+system from plugins. The event system relied on mutability of HTTP messages and
+side effects in order to work. With immutable messages, you have to change your
+workflow to become more about either returning a value (e.g., functional
+middlewares) or setting a value on an object. Guzzle v6 has chosen the
+functional middleware approach.
+
+Instead of using the event system to listen for things like the `before` event,
+you now create a stack based middleware function that intercepts a request on
+the way in and the promise of the response on the way out. This is a much
+simpler and more predictable approach than the event system and works nicely
+with PSR-7 middleware. Due to the use of promises, the middleware system is
+also asynchronous.
+
+v5:
+
+```php
+use GuzzleHttp\Event\BeforeEvent;
+$client = new GuzzleHttp\Client();
+// Get the emitter and listen to the before event.
+$client->getEmitter()->on('before', function (BeforeEvent $e) {
+ // Guzzle v5 events relied on mutation
+ $e->getRequest()->setHeader('X-Foo', 'Bar');
+});
+```
+
+v6:
+
+In v6, you can modify the request before it is sent using the `mapRequest`
+middleware. The idiomatic way in v6 to modify the request/response lifecycle is
+to setup a handler middleware stack up front and inject the handler into a
+client.
+
+```php
+use GuzzleHttp\Middleware;
+// Create a handler stack that has all of the default middlewares attached
+$handler = GuzzleHttp\HandlerStack::create();
+// Push the handler onto the handler stack
+$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
+ // Notice that we have to return a request object
+ return $request->withHeader('X-Foo', 'Bar');
+}));
+// Inject the handler into the client
+$client = new GuzzleHttp\Client(['handler' => $handler]);
+```
+
+## POST Requests
+
+This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params)
+and `multipart` request options. `form_params` is an associative array of
+strings or array of strings and is used to serialize an
+`application/x-www-form-urlencoded` POST request. The
+[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart)
+option is now used to send a multipart/form-data POST request.
+
+`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
+POST files to a multipart/form-data request.
+
+The `body` option no longer accepts an array to send POST requests. Please use
+`multipart` or `form_params` instead.
+
+The `base_url` option has been renamed to `base_uri`.
+
+4.x to 5.0
+----------
+
+## Rewritten Adapter Layer
+
+Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send
+HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
+is still supported, but it has now been renamed to `handler`. Instead of
+passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
+`callable` that follows the RingPHP specification.
+
+## Removed Fluent Interfaces
+
+[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil)
+from the following classes:
+
+- `GuzzleHttp\Collection`
+- `GuzzleHttp\Url`
+- `GuzzleHttp\Query`
+- `GuzzleHttp\Post\PostBody`
+- `GuzzleHttp\Cookie\SetCookie`
+
+## Removed functions.php
+
+Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following
+functions can be used as replacements.
+
+- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode`
+- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
+- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
+- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
+ deprecated in favor of using `GuzzleHttp\Pool::batch()`.
+
+The "procedural" global client has been removed with no replacement (e.g.,
+`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client`
+object as a replacement.
+
+## `throwImmediately` has been removed
+
+The concept of "throwImmediately" has been removed from exceptions and error
+events. This control mechanism was used to stop a transfer of concurrent
+requests from completing. This can now be handled by throwing the exception or
+by cancelling a pool of requests or each outstanding future request
+individually.
+
+## headers event has been removed
+
+Removed the "headers" event. This event was only useful for changing the
+body a response once the headers of the response were known. You can implement
+a similar behavior in a number of ways. One example might be to use a
+FnStream that has access to the transaction being sent. For example, when the
+first byte is written, you could check if the response headers match your
+expectations, and if so, change the actual stream body that is being
+written to.
+
+## Updates to HTTP Messages
+
+Removed the `asArray` parameter from
+`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
+value as an array, then use the newly added `getHeaderAsArray()` method of
+`MessageInterface`. This change makes the Guzzle interfaces compatible with
+the PSR-7 interfaces.
+
+3.x to 4.0
+----------
+
+## Overarching changes:
+
+- Now requires PHP 5.4 or greater.
+- No longer requires cURL to send requests.
+- Guzzle no longer wraps every exception it throws. Only exceptions that are
+ recoverable are now wrapped by Guzzle.
+- Various namespaces have been removed or renamed.
+- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
+ based on the Symfony EventDispatcher is
+ now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant
+ speed and functionality improvements).
+
+Changes per Guzzle 3.x namespace are described below.
+
+## Batch
+
+The `Guzzle\Batch` namespace has been removed. This is best left to
+third-parties to implement on top of Guzzle's core HTTP library.
+
+## Cache
+
+The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement
+has been implemented yet, but hoping to utilize a PSR cache interface).
+
+## Common
+
+- Removed all of the wrapped exceptions. It's better to use the standard PHP
+ library for unrecoverable exceptions.
+- `FromConfigInterface` has been removed.
+- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
+ at `GuzzleHttp\ClientInterface::VERSION`.
+
+### Collection
+
+- `getAll` has been removed. Use `toArray` to convert a collection to an array.
+- `inject` has been removed.
+- `keySearch` has been removed.
+- `getPath` no longer supports wildcard expressions. Use something better like
+ JMESPath for this.
+- `setPath` now supports appending to an existing array via the `[]` notation.
+
+### Events
+
+Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
+`GuzzleHttp\Event\Emitter`.
+
+- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by
+ `GuzzleHttp\Event\EmitterInterface`.
+- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
+ `GuzzleHttp\Event\Emitter`.
+- `Symfony\Component\EventDispatcher\Event` is replaced by
+ `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
+ `GuzzleHttp\Event\EventInterface`.
+- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
+ `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
+ event emitter of a request, client, etc. now uses the `getEmitter` method
+ rather than the `getDispatcher` method.
+
+#### Emitter
+
+- Use the `once()` method to add a listener that automatically removes itself
+ the first time it is invoked.
+- Use the `listeners()` method to retrieve a list of event listeners rather than
+ the `getListeners()` method.
+- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
+- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
+ `removeSubscriber()`.
+
+```php
+$mock = new Mock();
+// 3.x
+$request->getEventDispatcher()->addSubscriber($mock);
+$request->getEventDispatcher()->removeSubscriber($mock);
+// 4.x
+$request->getEmitter()->attach($mock);
+$request->getEmitter()->detach($mock);
+```
+
+Use the `on()` method to add a listener rather than the `addListener()` method.
+
+```php
+// 3.x
+$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
+// 4.x
+$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
+```
+
+## Http
+
+### General changes
+
+- The cacert.pem certificate has been moved to `src/cacert.pem`.
+- Added the concept of adapters that are used to transfer requests over the
+ wire.
+- Simplified the event system.
+- Sending requests in parallel is still possible, but batching is no longer a
+ concept of the HTTP layer. Instead, you must use the `complete` and `error`
+ events to asynchronously manage parallel request transfers.
+- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
+- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
+- QueryAggregators have been rewritten so that they are simply callable
+ functions.
+- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
+ `functions.php` for an easy to use static client instance.
+- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
+ `GuzzleHttp\Exception\TransferException`.
+
+### Client
+
+Calling methods like `get()`, `post()`, `head()`, etc. no longer create and
+return a request, but rather creates a request, sends the request, and returns
+the response.
+
+```php
+// 3.0
+$request = $client->get('/');
+$response = $request->send();
+
+// 4.0
+$response = $client->get('/');
+
+// or, to mirror the previous behavior
+$request = $client->createRequest('GET', '/');
+$response = $client->send($request);
+```
+
+`GuzzleHttp\ClientInterface` has changed.
+
+- The `send` method no longer accepts more than one request. Use `sendAll` to
+ send multiple requests in parallel.
+- `setUserAgent()` has been removed. Use a default request option instead. You
+ could, for example, do something like:
+ `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`.
+- `setSslVerification()` has been removed. Use default request options instead,
+ like `$client->setConfig('defaults/verify', true)`.
+
+`GuzzleHttp\Client` has changed.
+
+- The constructor now accepts only an associative array. You can include a
+ `base_url` string or array to use a URI template as the base URL of a client.
+ You can also specify a `defaults` key that is an associative array of default
+ request options. You can pass an `adapter` to use a custom adapter,
+ `batch_adapter` to use a custom adapter for sending requests in parallel, or
+ a `message_factory` to change the factory used to create HTTP requests and
+ responses.
+- The client no longer emits a `client.create_request` event.
+- Creating requests with a client no longer automatically utilize a URI
+ template. You must pass an array into a creational method (e.g.,
+ `createRequest`, `get`, `put`, etc.) in order to expand a URI template.
+
+### Messages
+
+Messages no longer have references to their counterparts (i.e., a request no
+longer has a reference to it's response, and a response no loger has a
+reference to its request). This association is now managed through a
+`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to
+these transaction objects using request events that are emitted over the
+lifecycle of a request.
+
+#### Requests with a body
+
+- `GuzzleHttp\Message\EntityEnclosingRequest` and
+ `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
+ separation between requests that contain a body and requests that do not
+ contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
+ handles both use cases.
+- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
+ `GuzzleHttp\Message\ResponseInterface`.
+- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
+ `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
+ both requests and responses and is implemented in
+ `GuzzleHttp\Message\MessageFactory`.
+- POST field and file methods have been removed from the request object. You
+ must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface`
+ to control the format of a POST body. Requests that are created using a
+ standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
+ a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
+ the method is POST and no body is provided.
+
+```php
+$request = $client->createRequest('POST', '/');
+$request->getBody()->setField('foo', 'bar');
+$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
+```
+
+#### Headers
+
+- `GuzzleHttp\Message\Header` has been removed. Header values are now simply
+ represented by an array of values or as a string. Header values are returned
+ as a string by default when retrieving a header value from a message. You can
+ pass an optional argument of `true` to retrieve a header value as an array
+ of strings instead of a single concatenated string.
+- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
+ `GuzzleHttp\Post`. This interface has been simplified and now allows the
+ addition of arbitrary headers.
+- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
+ of the custom headers are now handled separately in specific
+ subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
+ been updated to properly handle headers that contain parameters (like the
+ `Link` header).
+
+#### Responses
+
+- `GuzzleHttp\Message\Response::getInfo()` and
+ `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event
+ system to retrieve this type of information.
+- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed.
+- `GuzzleHttp\Message\Response::getMessage()` has been removed.
+- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific
+ methods have moved to the CacheSubscriber.
+- Header specific helper functions like `getContentMd5()` have been removed.
+ Just use `getHeader('Content-MD5')` instead.
+- `GuzzleHttp\Message\Response::setRequest()` and
+ `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event
+ system to work with request and response objects as a transaction.
+- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the
+ Redirect subscriber instead.
+- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have
+ been removed. Use `getStatusCode()` instead.
+
+#### Streaming responses
+
+Streaming requests can now be created by a client directly, returning a
+`GuzzleHttp\Message\ResponseInterface` object that contains a body stream
+referencing an open PHP HTTP stream.
+
+```php
+// 3.0
+use Guzzle\Stream\PhpStreamRequestFactory;
+$request = $client->get('/');
+$factory = new PhpStreamRequestFactory();
+$stream = $factory->fromRequest($request);
+$data = $stream->read(1024);
+
+// 4.0
+$response = $client->get('/', ['stream' => true]);
+// Read some data off of the stream in the response body
+$data = $response->getBody()->read(1024);
+```
+
+#### Redirects
+
+The `configureRedirects()` method has been removed in favor of a
+`allow_redirects` request option.
+
+```php
+// Standard redirects with a default of a max of 5 redirects
+$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);
+
+// Strict redirects with a custom number of redirects
+$request = $client->createRequest('GET', '/', [
+ 'allow_redirects' => ['max' => 5, 'strict' => true]
+]);
+```
+
+#### EntityBody
+
+EntityBody interfaces and classes have been removed or moved to
+`GuzzleHttp\Stream`. All classes and interfaces that once required
+`GuzzleHttp\EntityBodyInterface` now require
+`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no
+longer uses `GuzzleHttp\EntityBody::factory` but now uses
+`GuzzleHttp\Stream\Stream::factory` or even better:
+`GuzzleHttp\Stream\create()`.
+
+- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
+- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
+- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
+- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
+- `Guzzle\Http\IoEmittyinEntityBody` has been removed.
+
+#### Request lifecycle events
+
+Requests previously submitted a large number of requests. The number of events
+emitted over the lifecycle of a request has been significantly reduced to make
+it easier to understand how to extend the behavior of a request. All events
+emitted during the lifecycle of a request now emit a custom
+`GuzzleHttp\Event\EventInterface` object that contains context providing
+methods and a way in which to modify the transaction at that specific point in
+time (e.g., intercept the request and set a response on the transaction).
+
+- `request.before_send` has been renamed to `before` and now emits a
+ `GuzzleHttp\Event\BeforeEvent`
+- `request.complete` has been renamed to `complete` and now emits a
+ `GuzzleHttp\Event\CompleteEvent`.
+- `request.sent` has been removed. Use `complete`.
+- `request.success` has been removed. Use `complete`.
+- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
+- `request.exception` has been removed. Use `error`.
+- `request.receive.status_line` has been removed.
+- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
+ maintain a status update.
+- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
+ intercept writes.
+- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
+ intercept reads.
+
+`headers` is a new event that is emitted after the response headers of a
+request have been received before the body of the response is downloaded. This
+event emits a `GuzzleHttp\Event\HeadersEvent`.
+
+You can intercept a request and inject a response using the `intercept()` event
+of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
+`GuzzleHttp\Event\ErrorEvent` event.
+
+See: http://docs.guzzlephp.org/en/latest/events.html
+
+## Inflection
+
+The `Guzzle\Inflection` namespace has been removed. This is not a core concern
+of Guzzle.
+
+## Iterator
+
+The `Guzzle\Iterator` namespace has been removed.
+
+- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and
+ `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
+ Guzzle itself.
+- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
+ class is shipped with PHP 5.4.
+- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because
+ it's easier to just wrap an iterator in a generator that maps values.
+
+For a replacement of these iterators, see https://github.com/nikic/iter
+
+## Log
+
+The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
+`Guzzle\Log` namespace has been removed. Guzzle now relies on
+`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been
+moved to `GuzzleHttp\Subscriber\Log\Formatter`.
+
+## Parser
+
+The `Guzzle\Parser` namespace has been removed. This was previously used to
+make it possible to plug in custom parsers for cookies, messages, URI
+templates, and URLs; however, this level of complexity is not needed in Guzzle
+so it has been removed.
+
+- Cookie: Cookie parsing logic has been moved to
+ `GuzzleHttp\Cookie\SetCookie::fromString`.
+- Message: Message parsing logic for both requests and responses has been moved
+ to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only
+ used in debugging or deserializing messages, so it doesn't make sense for
+ Guzzle as a library to add this level of complexity to parsing messages.
+- UriTemplate: URI template parsing has been moved to
+ `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL
+ URI template library if it is installed.
+- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously
+ it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
+ then developers are free to subclass `GuzzleHttp\Url`.
+
+## Plugin
+
+The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`.
+Several plugins are shipping with the core Guzzle library under this namespace.
+
+- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar
+ code has moved to `GuzzleHttp\Cookie`.
+- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin.
+- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is
+ received.
+- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin.
+- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before
+ sending. This subscriber is attached to all requests by default.
+- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin.
+
+The following plugins have been removed (third-parties are free to re-implement
+these if needed):
+
+- `GuzzleHttp\Plugin\Async` has been removed.
+- `GuzzleHttp\Plugin\CurlAuth` has been removed.
+- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This
+ functionality should instead be implemented with event listeners that occur
+ after normal response parsing occurs in the guzzle/command package.
+
+The following plugins are not part of the core Guzzle package, but are provided
+in separate repositories:
+
+- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
+ to build custom retry policies using simple functions rather than various
+ chained classes. See: https://github.com/guzzle/retry-subscriber
+- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
+ https://github.com/guzzle/cache-subscriber
+- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
+ https://github.com/guzzle/log-subscriber
+- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
+ https://github.com/guzzle/message-integrity-subscriber
+- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
+ `GuzzleHttp\Subscriber\MockSubscriber`.
+- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to
+ https://github.com/guzzle/oauth-subscriber
+
+## Service
+
+The service description layer of Guzzle has moved into two separate packages:
+
+- http://github.com/guzzle/command Provides a high level abstraction over web
+ services by representing web service operations using commands.
+- http://github.com/guzzle/guzzle-services Provides an implementation of
+ guzzle/command that provides request serialization and response parsing using
+ Guzzle service descriptions.
+
+## Stream
+
+Stream have moved to a separate package available at
+https://github.com/guzzle/streams.
+
+`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take
+on the responsibilities of `Guzzle\Http\EntityBody` and
+`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number
+of methods implemented by the `StreamInterface` has been drastically reduced to
+allow developers to more easily extend and decorate stream behavior.
+
+## Removed methods from StreamInterface
+
+- `getStream` and `setStream` have been removed to better encapsulate streams.
+- `getMetadata` and `setMetadata` have been removed in favor of
+ `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
+ removed. This data is accessible when
+ using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
+- `rewind` has been removed. Use `seek(0)` for a similar behavior.
+
+## Renamed methods
+
+- `detachStream` has been renamed to `detach`.
+- `feof` has been renamed to `eof`.
+- `ftell` has been renamed to `tell`.
+- `readLine` has moved from an instance method to a static class method of
+ `GuzzleHttp\Stream\Stream`.
+
+## Metadata streams
+
+`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
+that contain additional metadata accessible via `getMetadata()`.
+`GuzzleHttp\Stream\StreamInterface::getMetadata` and
+`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.
+
+## StreamRequestFactory
+
+The entire concept of the StreamRequestFactory has been removed. The way this
+was used in Guzzle 3 broke the actual interface of sending streaming requests
+(instead of getting back a Response, you got a StreamInterface). Streaming
+PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.
+
+3.6 to 3.7
+----------
+
+### Deprecations
+
+- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
+
+```php
+\Guzzle\Common\Version::$emitWarnings = true;
+```
+
+The following APIs and options have been marked as deprecated:
+
+- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+- Marked `Guzzle\Common\Collection::inject()` as deprecated.
+- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
+ `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
+ `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
+
+3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
+request methods. When paired with a client's configuration settings, these options allow you to specify default settings
+for various aspects of a request. Because these options make other previous configuration options redundant, several
+configuration options and methods of a client and AbstractCommand have been deprecated.
+
+- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
+- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
+- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
+- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
+
+ $command = $client->getCommand('foo', array(
+ 'command.headers' => array('Test' => '123'),
+ 'command.response_body' => '/path/to/file'
+ ));
+
+ // Should be changed to:
+
+ $command = $client->getCommand('foo', array(
+ 'command.request_options' => array(
+ 'headers' => array('Test' => '123'),
+ 'save_as' => '/path/to/file'
+ )
+ ));
+
+### Interface changes
+
+Additions and changes (you will need to update any implementations or subclasses you may have created):
+
+- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+- Added `Guzzle\Stream\StreamInterface::isRepeatable`
+- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+
+The following methods were removed from interfaces. All of these methods are still available in the concrete classes
+that implement them, but you should update your code to use alternative methods:
+
+- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
+ `$client->setDefaultOption('headers/{header_name}', 'value')`. or
+ `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
+- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
+- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
+- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
+
+### Cache plugin breaking changes
+
+- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+- Always setting X-cache headers on cached responses
+- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+- Added `CacheStorageInterface::purge($url)`
+- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+3.5 to 3.6
+----------
+
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+ For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
+ Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Moved getLinks() from Response to just be used on a Link header object.
+
+If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
+HeaderInterface (e.g. toArray(), getAll(), etc.).
+
+### Interface changes
+
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+
+### Removed deprecated functions
+
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+
+### Deprecations
+
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+
+### Other changes
+
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+
+3.3 to 3.4
+----------
+
+Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
+
+3.2 to 3.3
+----------
+
+### Response::getEtag() quote stripping removed
+
+`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
+
+### Removed `Guzzle\Http\Utils`
+
+The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
+
+### Stream wrapper and type
+
+`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase.
+
+### curl.emit_io became emit_io
+
+Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
+'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+
+3.1 to 3.2
+----------
+
+### CurlMulti is no longer reused globally
+
+Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
+to a single client can pollute requests dispatched from other clients.
+
+If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
+ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
+created.
+
+```php
+$multi = new Guzzle\Http\Curl\CurlMulti();
+$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
+$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
+ $event['client']->setCurlMulti($multi);
+}
+});
+```
+
+### No default path
+
+URLs no longer have a default path value of '/' if no path was specified.
+
+Before:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com/
+```
+
+After:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com
+```
+
+### Less verbose BadResponseException
+
+The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
+response information. You can, however, get access to the request and response object by calling `getRequest()` or
+`getResponse()` on the exception object.
+
+### Query parameter aggregation
+
+Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
+setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
+responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
+
+2.8 to 3.x
+----------
+
+### Guzzle\Service\Inspector
+
+Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
+
+**Before**
+
+```php
+use Guzzle\Service\Inspector;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Inspector::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+**After**
+
+```php
+use Guzzle\Common\Collection;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Collection::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+### Convert XML Service Descriptions to JSON
+
+**Before**
+
+```xml
+
+
+
+
+
+ Get a list of groups
+
+
+ Uses a search query to get a list of groups
+
+
+
+ Create a group
+
+
+
+
+ Delete a group by ID
+
+
+
+
+
+
+ Update a group
+
+
+
+
+
+
+```
+
+**After**
+
+```json
+{
+ "name": "Zendesk REST API v2",
+ "apiVersion": "2012-12-31",
+ "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
+ "operations": {
+ "list_groups": {
+ "httpMethod":"GET",
+ "uri": "groups.json",
+ "summary": "Get a list of groups"
+ },
+ "search_groups":{
+ "httpMethod":"GET",
+ "uri": "search.json?query=\"{query} type:group\"",
+ "summary": "Uses a search query to get a list of groups",
+ "parameters":{
+ "query":{
+ "location": "uri",
+ "description":"Zendesk Search Query",
+ "type": "string",
+ "required": true
+ }
+ }
+ },
+ "create_group": {
+ "httpMethod":"POST",
+ "uri": "groups.json",
+ "summary": "Create a group",
+ "parameters":{
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ },
+ "delete_group": {
+ "httpMethod":"DELETE",
+ "uri": "groups/{id}.json",
+ "summary": "Delete a group",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to delete by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "get_group": {
+ "httpMethod":"GET",
+ "uri": "groups/{id}.json",
+ "summary": "Get a ticket",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to get by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "update_group": {
+ "httpMethod":"PUT",
+ "uri": "groups/{id}.json",
+ "summary": "Update a group",
+ "parameters":{
+ "id": {
+ "location": "uri",
+ "description":"Group to update by ID",
+ "type": "integer",
+ "required": true
+ },
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ }
+}
+```
+
+### Guzzle\Service\Description\ServiceDescription
+
+Commands are now called Operations
+
+**Before**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getCommands(); // @returns ApiCommandInterface[]
+$sd->hasCommand($name);
+$sd->getCommand($name); // @returns ApiCommandInterface|null
+$sd->addCommand($command); // @param ApiCommandInterface $command
+```
+
+**After**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getOperations(); // @returns OperationInterface[]
+$sd->hasOperation($name);
+$sd->getOperation($name); // @returns OperationInterface|null
+$sd->addOperation($operation); // @param OperationInterface $operation
+```
+
+### Guzzle\Common\Inflection\Inflector
+
+Namespace is now `Guzzle\Inflection\Inflector`
+
+### Guzzle\Http\Plugin
+
+Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
+
+### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
+
+Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
+
+**Before**
+
+```php
+use Guzzle\Common\Log\ClosureLogAdapter;
+use Guzzle\Http\Plugin\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $verbosity is an integer indicating desired message verbosity level
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
+```
+
+**After**
+
+```php
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Log\MessageFormatter;
+use Guzzle\Plugin\Log\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $format is a string indicating desired message format -- @see MessageFormatter
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
+```
+
+### Guzzle\Http\Plugin\CurlAuthPlugin
+
+Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
+
+### Guzzle\Http\Plugin\ExponentialBackoffPlugin
+
+Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
+
+**Before**
+
+```php
+use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
+
+$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
+ ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
+ ));
+
+$client->addSubscriber($backoffPlugin);
+```
+
+**After**
+
+```php
+use Guzzle\Plugin\Backoff\BackoffPlugin;
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+
+// Use convenient factory method instead -- see implementation for ideas of what
+// you can do with chaining backoff strategies
+$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
+ HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
+ ));
+$client->addSubscriber($backoffPlugin);
+```
+
+### Known Issues
+
+#### [BUG] Accept-Encoding header behavior changed unintentionally.
+
+(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
+
+In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
+properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
+See issue #217 for a workaround, or use a version containing the fix.
diff --git a/vendor/guzzlehttp/guzzle/composer.json b/vendor/guzzlehttp/guzzle/composer.json
new file mode 100755
index 0000000..c553257
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/composer.json
@@ -0,0 +1,58 @@
+{
+ "name": "guzzlehttp/guzzle",
+ "type": "library",
+ "description": "Guzzle is a PHP HTTP client library",
+ "keywords": [
+ "framework",
+ "http",
+ "rest",
+ "web service",
+ "curl",
+ "client",
+ "HTTP client"
+ ],
+ "homepage": "http://guzzlephp.org/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.5",
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.6.1"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/phpstan.neon.dist b/vendor/guzzlehttp/guzzle/phpstan.neon.dist
new file mode 100755
index 0000000..4ef4192
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/phpstan.neon.dist
@@ -0,0 +1,9 @@
+parameters:
+ level: 1
+ paths:
+ - src
+
+ ignoreErrors:
+ -
+ message: '#Function uri_template not found#'
+ path: %currentWorkingDirectory%/src/functions.php
diff --git a/vendor/guzzlehttp/guzzle/src/Client.php b/vendor/guzzlehttp/guzzle/src/Client.php
new file mode 100755
index 0000000..0f43c71
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Client.php
@@ -0,0 +1,422 @@
+ 'http://www.foo.com/1.0/',
+ * 'timeout' => 0,
+ * 'allow_redirects' => false,
+ * 'proxy' => '192.168.16.1:10'
+ * ]);
+ *
+ * Client configuration settings include the following options:
+ *
+ * - handler: (callable) Function that transfers HTTP requests over the
+ * wire. The function is called with a Psr7\Http\Message\RequestInterface
+ * and array of transfer options, and must return a
+ * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
+ * Psr7\Http\Message\ResponseInterface on success. "handler" is a
+ * constructor only option that cannot be overridden in per/request
+ * options. If no handler is provided, a default handler will be created
+ * that enables all of the request options below by attaching all of the
+ * default middleware to the handler.
+ * - base_uri: (string|UriInterface) Base URI of the client that is merged
+ * into relative URIs. Can be a string or instance of UriInterface.
+ * - **: any request option
+ *
+ * @param array $config Client configuration settings.
+ *
+ * @see \GuzzleHttp\RequestOptions for a list of available request options.
+ */
+ public function __construct(array $config = [])
+ {
+ if (!isset($config['handler'])) {
+ $config['handler'] = HandlerStack::create();
+ } elseif (!is_callable($config['handler'])) {
+ throw new \InvalidArgumentException('handler must be a callable');
+ }
+
+ // Convert the base_uri to a UriInterface
+ if (isset($config['base_uri'])) {
+ $config['base_uri'] = Psr7\uri_for($config['base_uri']);
+ }
+
+ $this->configureDefaults($config);
+ }
+
+ public function __call($method, $args)
+ {
+ if (count($args) < 1) {
+ throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
+ }
+
+ $uri = $args[0];
+ $opts = isset($args[1]) ? $args[1] : [];
+
+ return substr($method, -5) === 'Async'
+ ? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
+ : $this->request($method, $uri, $opts);
+ }
+
+ public function sendAsync(RequestInterface $request, array $options = [])
+ {
+ // Merge the base URI into the request URI if needed.
+ $options = $this->prepareDefaults($options);
+
+ return $this->transfer(
+ $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
+ $options
+ );
+ }
+
+ public function send(RequestInterface $request, array $options = [])
+ {
+ $options[RequestOptions::SYNCHRONOUS] = true;
+ return $this->sendAsync($request, $options)->wait();
+ }
+
+ public function requestAsync($method, $uri = '', array $options = [])
+ {
+ $options = $this->prepareDefaults($options);
+ // Remove request modifying parameter because it can be done up-front.
+ $headers = isset($options['headers']) ? $options['headers'] : [];
+ $body = isset($options['body']) ? $options['body'] : null;
+ $version = isset($options['version']) ? $options['version'] : '1.1';
+ // Merge the URI into the base URI.
+ $uri = $this->buildUri($uri, $options);
+ if (is_array($body)) {
+ $this->invalidBody();
+ }
+ $request = new Psr7\Request($method, $uri, $headers, $body, $version);
+ // Remove the option so that they are not doubly-applied.
+ unset($options['headers'], $options['body'], $options['version']);
+
+ return $this->transfer($request, $options);
+ }
+
+ public function request($method, $uri = '', array $options = [])
+ {
+ $options[RequestOptions::SYNCHRONOUS] = true;
+ return $this->requestAsync($method, $uri, $options)->wait();
+ }
+
+ public function getConfig($option = null)
+ {
+ return $option === null
+ ? $this->config
+ : (isset($this->config[$option]) ? $this->config[$option] : null);
+ }
+
+ private function buildUri($uri, array $config)
+ {
+ // for BC we accept null which would otherwise fail in uri_for
+ $uri = Psr7\uri_for($uri === null ? '' : $uri);
+
+ if (isset($config['base_uri'])) {
+ $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
+ }
+
+ return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
+ }
+
+ /**
+ * Configures the default options for a client.
+ *
+ * @param array $config
+ */
+ private function configureDefaults(array $config)
+ {
+ $defaults = [
+ 'allow_redirects' => RedirectMiddleware::$defaultSettings,
+ 'http_errors' => true,
+ 'decode_content' => true,
+ 'verify' => true,
+ 'cookies' => false
+ ];
+
+ // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
+
+ // We can only trust the HTTP_PROXY environment variable in a CLI
+ // process due to the fact that PHP has no reliable mechanism to
+ // get environment variables that start with "HTTP_".
+ if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
+ $defaults['proxy']['http'] = getenv('HTTP_PROXY');
+ }
+
+ if ($proxy = getenv('HTTPS_PROXY')) {
+ $defaults['proxy']['https'] = $proxy;
+ }
+
+ if ($noProxy = getenv('NO_PROXY')) {
+ $cleanedNoProxy = str_replace(' ', '', $noProxy);
+ $defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
+ }
+
+ $this->config = $config + $defaults;
+
+ if (!empty($config['cookies']) && $config['cookies'] === true) {
+ $this->config['cookies'] = new CookieJar();
+ }
+
+ // Add the default user-agent header.
+ if (!isset($this->config['headers'])) {
+ $this->config['headers'] = ['User-Agent' => default_user_agent()];
+ } else {
+ // Add the User-Agent header if one was not already set.
+ foreach (array_keys($this->config['headers']) as $name) {
+ if (strtolower($name) === 'user-agent') {
+ return;
+ }
+ }
+ $this->config['headers']['User-Agent'] = default_user_agent();
+ }
+ }
+
+ /**
+ * Merges default options into the array.
+ *
+ * @param array $options Options to modify by reference
+ *
+ * @return array
+ */
+ private function prepareDefaults(array $options)
+ {
+ $defaults = $this->config;
+
+ if (!empty($defaults['headers'])) {
+ // Default headers are only added if they are not present.
+ $defaults['_conditional'] = $defaults['headers'];
+ unset($defaults['headers']);
+ }
+
+ // Special handling for headers is required as they are added as
+ // conditional headers and as headers passed to a request ctor.
+ if (array_key_exists('headers', $options)) {
+ // Allows default headers to be unset.
+ if ($options['headers'] === null) {
+ $defaults['_conditional'] = null;
+ unset($options['headers']);
+ } elseif (!is_array($options['headers'])) {
+ throw new \InvalidArgumentException('headers must be an array');
+ }
+ }
+
+ // Shallow merge defaults underneath options.
+ $result = $options + $defaults;
+
+ // Remove null values.
+ foreach ($result as $k => $v) {
+ if ($v === null) {
+ unset($result[$k]);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Transfers the given request and applies request options.
+ *
+ * The URI of the request is not modified and the request options are used
+ * as-is without merging in default options.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return Promise\PromiseInterface
+ */
+ private function transfer(RequestInterface $request, array $options)
+ {
+ // save_to -> sink
+ if (isset($options['save_to'])) {
+ $options['sink'] = $options['save_to'];
+ unset($options['save_to']);
+ }
+
+ // exceptions -> http_errors
+ if (isset($options['exceptions'])) {
+ $options['http_errors'] = $options['exceptions'];
+ unset($options['exceptions']);
+ }
+
+ $request = $this->applyOptions($request, $options);
+ $handler = $options['handler'];
+
+ try {
+ return Promise\promise_for($handler($request, $options));
+ } catch (\Exception $e) {
+ return Promise\rejection_for($e);
+ }
+ }
+
+ /**
+ * Applies the array of request options to a request.
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return RequestInterface
+ */
+ private function applyOptions(RequestInterface $request, array &$options)
+ {
+ $modify = [
+ 'set_headers' => [],
+ ];
+
+ if (isset($options['headers'])) {
+ $modify['set_headers'] = $options['headers'];
+ unset($options['headers']);
+ }
+
+ if (isset($options['form_params'])) {
+ if (isset($options['multipart'])) {
+ throw new \InvalidArgumentException('You cannot use '
+ . 'form_params and multipart at the same time. Use the '
+ . 'form_params option if you want to send application/'
+ . 'x-www-form-urlencoded requests, and the multipart '
+ . 'option to send multipart/form-data requests.');
+ }
+ $options['body'] = http_build_query($options['form_params'], '', '&');
+ unset($options['form_params']);
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
+ }
+
+ if (isset($options['multipart'])) {
+ $options['body'] = new Psr7\MultipartStream($options['multipart']);
+ unset($options['multipart']);
+ }
+
+ if (isset($options['json'])) {
+ $options['body'] = \GuzzleHttp\json_encode($options['json']);
+ unset($options['json']);
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'application/json';
+ }
+
+ if (!empty($options['decode_content'])
+ && $options['decode_content'] !== true
+ ) {
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Accept-Encoding'], $options['_conditional']);
+ $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
+ }
+
+ if (isset($options['body'])) {
+ if (is_array($options['body'])) {
+ $this->invalidBody();
+ }
+ $modify['body'] = Psr7\stream_for($options['body']);
+ unset($options['body']);
+ }
+
+ if (!empty($options['auth']) && is_array($options['auth'])) {
+ $value = $options['auth'];
+ $type = isset($value[2]) ? strtolower($value[2]) : 'basic';
+ switch ($type) {
+ case 'basic':
+ // Ensure that we don't have the header in different case and set the new value.
+ $modify['set_headers'] = Psr7\_caseless_remove(['Authorization'], $modify['set_headers']);
+ $modify['set_headers']['Authorization'] = 'Basic '
+ . base64_encode("$value[0]:$value[1]");
+ break;
+ case 'digest':
+ // @todo: Do not rely on curl
+ $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
+ $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
+ break;
+ case 'ntlm':
+ $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
+ $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
+ break;
+ }
+ }
+
+ if (isset($options['query'])) {
+ $value = $options['query'];
+ if (is_array($value)) {
+ $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
+ }
+ if (!is_string($value)) {
+ throw new \InvalidArgumentException('query must be a string or array');
+ }
+ $modify['query'] = $value;
+ unset($options['query']);
+ }
+
+ // Ensure that sink is not an invalid value.
+ if (isset($options['sink'])) {
+ // TODO: Add more sink validation?
+ if (is_bool($options['sink'])) {
+ throw new \InvalidArgumentException('sink must not be a boolean');
+ }
+ }
+
+ $request = Psr7\modify_request($request, $modify);
+ if ($request->getBody() instanceof Psr7\MultipartStream) {
+ // Use a multipart/form-data POST if a Content-Type is not set.
+ // Ensure that we don't have the header in different case and set the new value.
+ $options['_conditional'] = Psr7\_caseless_remove(['Content-Type'], $options['_conditional']);
+ $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
+ . $request->getBody()->getBoundary();
+ }
+
+ // Merge in conditional headers if they are not present.
+ if (isset($options['_conditional'])) {
+ // Build up the changes so it's in a single clone of the message.
+ $modify = [];
+ foreach ($options['_conditional'] as $k => $v) {
+ if (!$request->hasHeader($k)) {
+ $modify['set_headers'][$k] = $v;
+ }
+ }
+ $request = Psr7\modify_request($request, $modify);
+ // Don't pass this internal value along to middleware/handlers.
+ unset($options['_conditional']);
+ }
+
+ return $request;
+ }
+
+ private function invalidBody()
+ {
+ throw new \InvalidArgumentException('Passing in the "body" request '
+ . 'option as an array to send a POST request has been deprecated. '
+ . 'Please use the "form_params" request option to send a '
+ . 'application/x-www-form-urlencoded request, or the "multipart" '
+ . 'request option to send a multipart/form-data request.');
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/vendor/guzzlehttp/guzzle/src/ClientInterface.php
new file mode 100755
index 0000000..5b37085
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/ClientInterface.php
@@ -0,0 +1,84 @@
+strictMode = $strictMode;
+
+ foreach ($cookieArray as $cookie) {
+ if (!($cookie instanceof SetCookie)) {
+ $cookie = new SetCookie($cookie);
+ }
+ $this->setCookie($cookie);
+ }
+ }
+
+ /**
+ * Create a new Cookie jar from an associative array and domain.
+ *
+ * @param array $cookies Cookies to create the jar from
+ * @param string $domain Domain to set the cookies to
+ *
+ * @return self
+ */
+ public static function fromArray(array $cookies, $domain)
+ {
+ $cookieJar = new self();
+ foreach ($cookies as $name => $value) {
+ $cookieJar->setCookie(new SetCookie([
+ 'Domain' => $domain,
+ 'Name' => $name,
+ 'Value' => $value,
+ 'Discard' => true
+ ]));
+ }
+
+ return $cookieJar;
+ }
+
+ /**
+ * @deprecated
+ */
+ public static function getCookieValue($value)
+ {
+ return $value;
+ }
+
+ /**
+ * Evaluate if this cookie should be persisted to storage
+ * that survives between requests.
+ *
+ * @param SetCookie $cookie Being evaluated.
+ * @param bool $allowSessionCookies If we should persist session cookies
+ * @return bool
+ */
+ public static function shouldPersist(
+ SetCookie $cookie,
+ $allowSessionCookies = false
+ ) {
+ if ($cookie->getExpires() || $allowSessionCookies) {
+ if (!$cookie->getDiscard()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Finds and returns the cookie based on the name
+ *
+ * @param string $name cookie name to search for
+ * @return SetCookie|null cookie that was found or null if not found
+ */
+ public function getCookieByName($name)
+ {
+ // don't allow a null name
+ if ($name === null) {
+ return null;
+ }
+ foreach ($this->cookies as $cookie) {
+ if ($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) {
+ return $cookie;
+ }
+ }
+ }
+
+ public function toArray()
+ {
+ return array_map(function (SetCookie $cookie) {
+ return $cookie->toArray();
+ }, $this->getIterator()->getArrayCopy());
+ }
+
+ public function clear($domain = null, $path = null, $name = null)
+ {
+ if (!$domain) {
+ $this->cookies = [];
+ return;
+ } elseif (!$path) {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($domain) {
+ return !$cookie->matchesDomain($domain);
+ }
+ );
+ } elseif (!$name) {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($path, $domain) {
+ return !($cookie->matchesPath($path) &&
+ $cookie->matchesDomain($domain));
+ }
+ );
+ } else {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) use ($path, $domain, $name) {
+ return !($cookie->getName() == $name &&
+ $cookie->matchesPath($path) &&
+ $cookie->matchesDomain($domain));
+ }
+ );
+ }
+ }
+
+ public function clearSessionCookies()
+ {
+ $this->cookies = array_filter(
+ $this->cookies,
+ function (SetCookie $cookie) {
+ return !$cookie->getDiscard() && $cookie->getExpires();
+ }
+ );
+ }
+
+ public function setCookie(SetCookie $cookie)
+ {
+ // If the name string is empty (but not 0), ignore the set-cookie
+ // string entirely.
+ $name = $cookie->getName();
+ if (!$name && $name !== '0') {
+ return false;
+ }
+
+ // Only allow cookies with set and valid domain, name, value
+ $result = $cookie->validate();
+ if ($result !== true) {
+ if ($this->strictMode) {
+ throw new \RuntimeException('Invalid cookie: ' . $result);
+ } else {
+ $this->removeCookieIfEmpty($cookie);
+ return false;
+ }
+ }
+
+ // Resolve conflicts with previously set cookies
+ foreach ($this->cookies as $i => $c) {
+
+ // Two cookies are identical, when their path, and domain are
+ // identical.
+ if ($c->getPath() != $cookie->getPath() ||
+ $c->getDomain() != $cookie->getDomain() ||
+ $c->getName() != $cookie->getName()
+ ) {
+ continue;
+ }
+
+ // The previously set cookie is a discard cookie and this one is
+ // not so allow the new cookie to be set
+ if (!$cookie->getDiscard() && $c->getDiscard()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the new cookie's expiration is further into the future, then
+ // replace the old cookie
+ if ($cookie->getExpires() > $c->getExpires()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // If the value has changed, we better change it
+ if ($cookie->getValue() !== $c->getValue()) {
+ unset($this->cookies[$i]);
+ continue;
+ }
+
+ // The cookie exists, so no need to continue
+ return false;
+ }
+
+ $this->cookies[] = $cookie;
+
+ return true;
+ }
+
+ public function count()
+ {
+ return count($this->cookies);
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator(array_values($this->cookies));
+ }
+
+ public function extractCookies(
+ RequestInterface $request,
+ ResponseInterface $response
+ ) {
+ if ($cookieHeader = $response->getHeader('Set-Cookie')) {
+ foreach ($cookieHeader as $cookie) {
+ $sc = SetCookie::fromString($cookie);
+ if (!$sc->getDomain()) {
+ $sc->setDomain($request->getUri()->getHost());
+ }
+ if (0 !== strpos($sc->getPath(), '/')) {
+ $sc->setPath($this->getCookiePathFromRequest($request));
+ }
+ $this->setCookie($sc);
+ }
+ }
+ }
+
+ /**
+ * Computes cookie path following RFC 6265 section 5.1.4
+ *
+ * @link https://tools.ietf.org/html/rfc6265#section-5.1.4
+ *
+ * @param RequestInterface $request
+ * @return string
+ */
+ private function getCookiePathFromRequest(RequestInterface $request)
+ {
+ $uriPath = $request->getUri()->getPath();
+ if ('' === $uriPath) {
+ return '/';
+ }
+ if (0 !== strpos($uriPath, '/')) {
+ return '/';
+ }
+ if ('/' === $uriPath) {
+ return '/';
+ }
+ if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
+ return '/';
+ }
+
+ return substr($uriPath, 0, $lastSlashPos);
+ }
+
+ public function withCookieHeader(RequestInterface $request)
+ {
+ $values = [];
+ $uri = $request->getUri();
+ $scheme = $uri->getScheme();
+ $host = $uri->getHost();
+ $path = $uri->getPath() ?: '/';
+
+ foreach ($this->cookies as $cookie) {
+ if ($cookie->matchesPath($path) &&
+ $cookie->matchesDomain($host) &&
+ !$cookie->isExpired() &&
+ (!$cookie->getSecure() || $scheme === 'https')
+ ) {
+ $values[] = $cookie->getName() . '='
+ . $cookie->getValue();
+ }
+ }
+
+ return $values
+ ? $request->withHeader('Cookie', implode('; ', $values))
+ : $request;
+ }
+
+ /**
+ * If a cookie already exists and the server asks to set it again with a
+ * null value, the cookie must be deleted.
+ *
+ * @param SetCookie $cookie
+ */
+ private function removeCookieIfEmpty(SetCookie $cookie)
+ {
+ $cookieValue = $cookie->getValue();
+ if ($cookieValue === null || $cookieValue === '') {
+ $this->clear(
+ $cookie->getDomain(),
+ $cookie->getPath(),
+ $cookie->getName()
+ );
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
new file mode 100755
index 0000000..2cf298a
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
@@ -0,0 +1,84 @@
+filename = $cookieFile;
+ $this->storeSessionCookies = $storeSessionCookies;
+
+ if (file_exists($cookieFile)) {
+ $this->load($cookieFile);
+ }
+ }
+
+ /**
+ * Saves the file when shutting down
+ */
+ public function __destruct()
+ {
+ $this->save($this->filename);
+ }
+
+ /**
+ * Saves the cookies to a file.
+ *
+ * @param string $filename File to save
+ * @throws \RuntimeException if the file cannot be found or created
+ */
+ public function save($filename)
+ {
+ $json = [];
+ foreach ($this as $cookie) {
+ /** @var SetCookie $cookie */
+ if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
+ $json[] = $cookie->toArray();
+ }
+ }
+
+ $jsonStr = \GuzzleHttp\json_encode($json);
+ if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) {
+ throw new \RuntimeException("Unable to save file {$filename}");
+ }
+ }
+
+ /**
+ * Load cookies from a JSON formatted file.
+ *
+ * Old cookies are kept unless overwritten by newly loaded ones.
+ *
+ * @param string $filename Cookie file to load.
+ * @throws \RuntimeException if the file cannot be loaded.
+ */
+ public function load($filename)
+ {
+ $json = file_get_contents($filename);
+ if (false === $json) {
+ throw new \RuntimeException("Unable to load file {$filename}");
+ } elseif ($json === '') {
+ return;
+ }
+
+ $data = \GuzzleHttp\json_decode($json, true);
+ if (is_array($data)) {
+ foreach (json_decode($json, true) as $cookie) {
+ $this->setCookie(new SetCookie($cookie));
+ }
+ } elseif (strlen($data)) {
+ throw new \RuntimeException("Invalid cookie file: {$filename}");
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php
new file mode 100755
index 0000000..0224a24
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php
@@ -0,0 +1,72 @@
+sessionKey = $sessionKey;
+ $this->storeSessionCookies = $storeSessionCookies;
+ $this->load();
+ }
+
+ /**
+ * Saves cookies to session when shutting down
+ */
+ public function __destruct()
+ {
+ $this->save();
+ }
+
+ /**
+ * Save cookies to the client session
+ */
+ public function save()
+ {
+ $json = [];
+ foreach ($this as $cookie) {
+ /** @var SetCookie $cookie */
+ if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
+ $json[] = $cookie->toArray();
+ }
+ }
+
+ $_SESSION[$this->sessionKey] = json_encode($json);
+ }
+
+ /**
+ * Load the contents of the client session into the data array
+ */
+ protected function load()
+ {
+ if (!isset($_SESSION[$this->sessionKey])) {
+ return;
+ }
+ $data = json_decode($_SESSION[$this->sessionKey], true);
+ if (is_array($data)) {
+ foreach ($data as $cookie) {
+ $this->setCookie(new SetCookie($cookie));
+ }
+ } elseif (strlen($data)) {
+ throw new \RuntimeException("Invalid cookie data");
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php
new file mode 100755
index 0000000..3d776a7
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php
@@ -0,0 +1,403 @@
+ null,
+ 'Value' => null,
+ 'Domain' => null,
+ 'Path' => '/',
+ 'Max-Age' => null,
+ 'Expires' => null,
+ 'Secure' => false,
+ 'Discard' => false,
+ 'HttpOnly' => false
+ ];
+
+ /** @var array Cookie data */
+ private $data;
+
+ /**
+ * Create a new SetCookie object from a string
+ *
+ * @param string $cookie Set-Cookie header string
+ *
+ * @return self
+ */
+ public static function fromString($cookie)
+ {
+ // Create the default return array
+ $data = self::$defaults;
+ // Explode the cookie string using a series of semicolons
+ $pieces = array_filter(array_map('trim', explode(';', $cookie)));
+ // The name of the cookie (first kvp) must exist and include an equal sign.
+ if (empty($pieces[0]) || !strpos($pieces[0], '=')) {
+ return new self($data);
+ }
+
+ // Add the cookie pieces into the parsed data array
+ foreach ($pieces as $part) {
+ $cookieParts = explode('=', $part, 2);
+ $key = trim($cookieParts[0]);
+ $value = isset($cookieParts[1])
+ ? trim($cookieParts[1], " \n\r\t\0\x0B")
+ : true;
+
+ // Only check for non-cookies when cookies have been found
+ if (empty($data['Name'])) {
+ $data['Name'] = $key;
+ $data['Value'] = $value;
+ } else {
+ foreach (array_keys(self::$defaults) as $search) {
+ if (!strcasecmp($search, $key)) {
+ $data[$search] = $value;
+ continue 2;
+ }
+ }
+ $data[$key] = $value;
+ }
+ }
+
+ return new self($data);
+ }
+
+ /**
+ * @param array $data Array of cookie data provided by a Cookie parser
+ */
+ public function __construct(array $data = [])
+ {
+ $this->data = array_replace(self::$defaults, $data);
+ // Extract the Expires value and turn it into a UNIX timestamp if needed
+ if (!$this->getExpires() && $this->getMaxAge()) {
+ // Calculate the Expires date
+ $this->setExpires(time() + $this->getMaxAge());
+ } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
+ $this->setExpires($this->getExpires());
+ }
+ }
+
+ public function __toString()
+ {
+ $str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
+ foreach ($this->data as $k => $v) {
+ if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
+ if ($k === 'Expires') {
+ $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
+ } else {
+ $str .= ($v === true ? $k : "{$k}={$v}") . '; ';
+ }
+ }
+ }
+
+ return rtrim($str, '; ');
+ }
+
+ public function toArray()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Get the cookie name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->data['Name'];
+ }
+
+ /**
+ * Set the cookie name
+ *
+ * @param string $name Cookie name
+ */
+ public function setName($name)
+ {
+ $this->data['Name'] = $name;
+ }
+
+ /**
+ * Get the cookie value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->data['Value'];
+ }
+
+ /**
+ * Set the cookie value
+ *
+ * @param string $value Cookie value
+ */
+ public function setValue($value)
+ {
+ $this->data['Value'] = $value;
+ }
+
+ /**
+ * Get the domain
+ *
+ * @return string|null
+ */
+ public function getDomain()
+ {
+ return $this->data['Domain'];
+ }
+
+ /**
+ * Set the domain of the cookie
+ *
+ * @param string $domain
+ */
+ public function setDomain($domain)
+ {
+ $this->data['Domain'] = $domain;
+ }
+
+ /**
+ * Get the path
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->data['Path'];
+ }
+
+ /**
+ * Set the path of the cookie
+ *
+ * @param string $path Path of the cookie
+ */
+ public function setPath($path)
+ {
+ $this->data['Path'] = $path;
+ }
+
+ /**
+ * Maximum lifetime of the cookie in seconds
+ *
+ * @return int|null
+ */
+ public function getMaxAge()
+ {
+ return $this->data['Max-Age'];
+ }
+
+ /**
+ * Set the max-age of the cookie
+ *
+ * @param int $maxAge Max age of the cookie in seconds
+ */
+ public function setMaxAge($maxAge)
+ {
+ $this->data['Max-Age'] = $maxAge;
+ }
+
+ /**
+ * The UNIX timestamp when the cookie Expires
+ *
+ * @return mixed
+ */
+ public function getExpires()
+ {
+ return $this->data['Expires'];
+ }
+
+ /**
+ * Set the unix timestamp for which the cookie will expire
+ *
+ * @param int $timestamp Unix timestamp
+ */
+ public function setExpires($timestamp)
+ {
+ $this->data['Expires'] = is_numeric($timestamp)
+ ? (int) $timestamp
+ : strtotime($timestamp);
+ }
+
+ /**
+ * Get whether or not this is a secure cookie
+ *
+ * @return bool|null
+ */
+ public function getSecure()
+ {
+ return $this->data['Secure'];
+ }
+
+ /**
+ * Set whether or not the cookie is secure
+ *
+ * @param bool $secure Set to true or false if secure
+ */
+ public function setSecure($secure)
+ {
+ $this->data['Secure'] = $secure;
+ }
+
+ /**
+ * Get whether or not this is a session cookie
+ *
+ * @return bool|null
+ */
+ public function getDiscard()
+ {
+ return $this->data['Discard'];
+ }
+
+ /**
+ * Set whether or not this is a session cookie
+ *
+ * @param bool $discard Set to true or false if this is a session cookie
+ */
+ public function setDiscard($discard)
+ {
+ $this->data['Discard'] = $discard;
+ }
+
+ /**
+ * Get whether or not this is an HTTP only cookie
+ *
+ * @return bool
+ */
+ public function getHttpOnly()
+ {
+ return $this->data['HttpOnly'];
+ }
+
+ /**
+ * Set whether or not this is an HTTP only cookie
+ *
+ * @param bool $httpOnly Set to true or false if this is HTTP only
+ */
+ public function setHttpOnly($httpOnly)
+ {
+ $this->data['HttpOnly'] = $httpOnly;
+ }
+
+ /**
+ * Check if the cookie matches a path value.
+ *
+ * A request-path path-matches a given cookie-path if at least one of
+ * the following conditions holds:
+ *
+ * - The cookie-path and the request-path are identical.
+ * - The cookie-path is a prefix of the request-path, and the last
+ * character of the cookie-path is %x2F ("/").
+ * - The cookie-path is a prefix of the request-path, and the first
+ * character of the request-path that is not included in the cookie-
+ * path is a %x2F ("/") character.
+ *
+ * @param string $requestPath Path to check against
+ *
+ * @return bool
+ */
+ public function matchesPath($requestPath)
+ {
+ $cookiePath = $this->getPath();
+
+ // Match on exact matches or when path is the default empty "/"
+ if ($cookiePath === '/' || $cookiePath == $requestPath) {
+ return true;
+ }
+
+ // Ensure that the cookie-path is a prefix of the request path.
+ if (0 !== strpos($requestPath, $cookiePath)) {
+ return false;
+ }
+
+ // Match if the last character of the cookie-path is "/"
+ if (substr($cookiePath, -1, 1) === '/') {
+ return true;
+ }
+
+ // Match if the first character not included in cookie path is "/"
+ return substr($requestPath, strlen($cookiePath), 1) === '/';
+ }
+
+ /**
+ * Check if the cookie matches a domain value
+ *
+ * @param string $domain Domain to check against
+ *
+ * @return bool
+ */
+ public function matchesDomain($domain)
+ {
+ // Remove the leading '.' as per spec in RFC 6265.
+ // http://tools.ietf.org/html/rfc6265#section-5.2.3
+ $cookieDomain = ltrim($this->getDomain(), '.');
+
+ // Domain not set or exact match.
+ if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
+ return true;
+ }
+
+ // Matching the subdomain according to RFC 6265.
+ // http://tools.ietf.org/html/rfc6265#section-5.1.3
+ if (filter_var($domain, FILTER_VALIDATE_IP)) {
+ return false;
+ }
+
+ return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/', $domain);
+ }
+
+ /**
+ * Check if the cookie is expired
+ *
+ * @return bool
+ */
+ public function isExpired()
+ {
+ return $this->getExpires() !== null && time() > $this->getExpires();
+ }
+
+ /**
+ * Check if the cookie is valid according to RFC 6265
+ *
+ * @return bool|string Returns true if valid or an error message if invalid
+ */
+ public function validate()
+ {
+ // Names must not be empty, but can be 0
+ $name = $this->getName();
+ if (empty($name) && !is_numeric($name)) {
+ return 'The cookie name must not be empty';
+ }
+
+ // Check if any of the invalid characters are present in the cookie name
+ if (preg_match(
+ '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
+ $name
+ )) {
+ return 'Cookie name must not contain invalid characters: ASCII '
+ . 'Control characters (0-31;127), space, tab and the '
+ . 'following characters: ()<>@,;:\"/?={}';
+ }
+
+ // Value must not be empty, but can be 0
+ $value = $this->getValue();
+ if (empty($value) && !is_numeric($value)) {
+ return 'The cookie value must not be empty';
+ }
+
+ // Domains must not be empty, but can be 0
+ // A "0" is not a valid internet domain, but may be used as server name
+ // in a private network.
+ $domain = $this->getDomain();
+ if (empty($domain) && !is_numeric($domain)) {
+ return 'The cookie domain must not be empty';
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php
new file mode 100755
index 0000000..427d896
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php
@@ -0,0 +1,27 @@
+getStatusCode()
+ : 0;
+ parent::__construct($message, $code, $previous);
+ $this->request = $request;
+ $this->response = $response;
+ $this->handlerContext = $handlerContext;
+ }
+
+ /**
+ * Wrap non-RequestExceptions with a RequestException
+ *
+ * @param RequestInterface $request
+ * @param \Exception $e
+ *
+ * @return RequestException
+ */
+ public static function wrapException(RequestInterface $request, \Exception $e)
+ {
+ return $e instanceof RequestException
+ ? $e
+ : new RequestException($e->getMessage(), $request, null, $e);
+ }
+
+ /**
+ * Factory method to create a new exception with a normalized error message
+ *
+ * @param RequestInterface $request Request
+ * @param ResponseInterface $response Response received
+ * @param \Exception $previous Previous exception
+ * @param array $ctx Optional handler context.
+ *
+ * @return self
+ */
+ public static function create(
+ RequestInterface $request,
+ ResponseInterface $response = null,
+ \Exception $previous = null,
+ array $ctx = []
+ ) {
+ if (!$response) {
+ return new self(
+ 'Error completing request',
+ $request,
+ null,
+ $previous,
+ $ctx
+ );
+ }
+
+ $level = (int) floor($response->getStatusCode() / 100);
+ if ($level === 4) {
+ $label = 'Client error';
+ $className = ClientException::class;
+ } elseif ($level === 5) {
+ $label = 'Server error';
+ $className = ServerException::class;
+ } else {
+ $label = 'Unsuccessful request';
+ $className = __CLASS__;
+ }
+
+ $uri = $request->getUri();
+ $uri = static::obfuscateUri($uri);
+
+ // Client Error: `GET /` resulted in a `404 Not Found` response:
+ // ... (truncated)
+ $message = sprintf(
+ '%s: `%s %s` resulted in a `%s %s` response',
+ $label,
+ $request->getMethod(),
+ $uri,
+ $response->getStatusCode(),
+ $response->getReasonPhrase()
+ );
+
+ $summary = static::getResponseBodySummary($response);
+
+ if ($summary !== null) {
+ $message .= ":\n{$summary}\n";
+ }
+
+ return new $className($message, $request, $response, $previous, $ctx);
+ }
+
+ /**
+ * Get a short summary of the response
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param ResponseInterface $response
+ *
+ * @return string|null
+ */
+ public static function getResponseBodySummary(ResponseInterface $response)
+ {
+ $body = $response->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $summary = $body->read(120);
+ $body->rewind();
+
+ if ($size > 120) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
+ return null;
+ }
+
+ return $summary;
+ }
+
+ /**
+ * Obfuscates URI if there is an username and a password present
+ *
+ * @param UriInterface $uri
+ *
+ * @return UriInterface
+ */
+ private static function obfuscateUri($uri)
+ {
+ $userInfo = $uri->getUserInfo();
+
+ if (false !== ($pos = strpos($userInfo, ':'))) {
+ return $uri->withUserInfo(substr($userInfo, 0, $pos), '***');
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Get the request that caused the exception
+ *
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Get the associated response
+ *
+ * @return ResponseInterface|null
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Check if a response was received
+ *
+ * @return bool
+ */
+ public function hasResponse()
+ {
+ return $this->response !== null;
+ }
+
+ /**
+ * Get contextual information about the error from the underlying handler.
+ *
+ * The contents of this array will vary depending on which handler you are
+ * using. It may also be just an empty array. Relying on this data will
+ * couple you to a specific handler, but can give more debug information
+ * when needed.
+ *
+ * @return array
+ */
+ public function getHandlerContext()
+ {
+ return $this->handlerContext;
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php b/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php
new file mode 100755
index 0000000..a77c289
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Exception/SeekException.php
@@ -0,0 +1,27 @@
+stream = $stream;
+ $msg = $msg ?: 'Could not seek the stream to position ' . $pos;
+ parent::__construct($msg);
+ }
+
+ /**
+ * @return StreamInterface
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php
new file mode 100755
index 0000000..127094c
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php
@@ -0,0 +1,9 @@
+maxHandles = $maxHandles;
+ }
+
+ public function create(RequestInterface $request, array $options)
+ {
+ if (isset($options['curl']['body_as_string'])) {
+ $options['_body_as_string'] = $options['curl']['body_as_string'];
+ unset($options['curl']['body_as_string']);
+ }
+
+ $easy = new EasyHandle;
+ $easy->request = $request;
+ $easy->options = $options;
+ $conf = $this->getDefaultConf($easy);
+ $this->applyMethod($easy, $conf);
+ $this->applyHandlerOptions($easy, $conf);
+ $this->applyHeaders($easy, $conf);
+ unset($conf['_headers']);
+
+ // Add handler options from the request configuration options
+ if (isset($options['curl'])) {
+ $conf = array_replace($conf, $options['curl']);
+ }
+
+ $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
+ $easy->handle = $this->handles
+ ? array_pop($this->handles)
+ : curl_init();
+ curl_setopt_array($easy->handle, $conf);
+
+ return $easy;
+ }
+
+ public function release(EasyHandle $easy)
+ {
+ $resource = $easy->handle;
+ unset($easy->handle);
+
+ if (count($this->handles) >= $this->maxHandles) {
+ curl_close($resource);
+ } else {
+ // Remove all callback functions as they can hold onto references
+ // and are not cleaned up by curl_reset. Using curl_setopt_array
+ // does not work for some reason, so removing each one
+ // individually.
+ curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
+ curl_setopt($resource, CURLOPT_READFUNCTION, null);
+ curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
+ curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
+ curl_reset($resource);
+ $this->handles[] = $resource;
+ }
+ }
+
+ /**
+ * Completes a cURL transaction, either returning a response promise or a
+ * rejected promise.
+ *
+ * @param callable $handler
+ * @param EasyHandle $easy
+ * @param CurlFactoryInterface $factory Dictates how the handle is released
+ *
+ * @return \GuzzleHttp\Promise\PromiseInterface
+ */
+ public static function finish(
+ callable $handler,
+ EasyHandle $easy,
+ CurlFactoryInterface $factory
+ ) {
+ if (isset($easy->options['on_stats'])) {
+ self::invokeStats($easy);
+ }
+
+ if (!$easy->response || $easy->errno) {
+ return self::finishError($handler, $easy, $factory);
+ }
+
+ // Return the response if it is present and there is no error.
+ $factory->release($easy);
+
+ // Rewind the body of the response if possible.
+ $body = $easy->response->getBody();
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+
+ return new FulfilledPromise($easy->response);
+ }
+
+ private static function invokeStats(EasyHandle $easy)
+ {
+ $curlStats = curl_getinfo($easy->handle);
+ $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME);
+ $stats = new TransferStats(
+ $easy->request,
+ $easy->response,
+ $curlStats['total_time'],
+ $easy->errno,
+ $curlStats
+ );
+ call_user_func($easy->options['on_stats'], $stats);
+ }
+
+ private static function finishError(
+ callable $handler,
+ EasyHandle $easy,
+ CurlFactoryInterface $factory
+ ) {
+ // Get error information and release the handle to the factory.
+ $ctx = [
+ 'errno' => $easy->errno,
+ 'error' => curl_error($easy->handle),
+ 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME),
+ ] + curl_getinfo($easy->handle);
+ $ctx[self::CURL_VERSION_STR] = curl_version()['version'];
+ $factory->release($easy);
+
+ // Retry when nothing is present or when curl failed to rewind.
+ if (empty($easy->options['_err_message'])
+ && (!$easy->errno || $easy->errno == 65)
+ ) {
+ return self::retryFailedRewind($handler, $easy, $ctx);
+ }
+
+ return self::createRejection($easy, $ctx);
+ }
+
+ private static function createRejection(EasyHandle $easy, array $ctx)
+ {
+ static $connectionErrors = [
+ CURLE_OPERATION_TIMEOUTED => true,
+ CURLE_COULDNT_RESOLVE_HOST => true,
+ CURLE_COULDNT_CONNECT => true,
+ CURLE_SSL_CONNECT_ERROR => true,
+ CURLE_GOT_NOTHING => true,
+ ];
+
+ // If an exception was encountered during the onHeaders event, then
+ // return a rejected promise that wraps that exception.
+ if ($easy->onHeadersException) {
+ return \GuzzleHttp\Promise\rejection_for(
+ new RequestException(
+ 'An error was encountered during the on_headers event',
+ $easy->request,
+ $easy->response,
+ $easy->onHeadersException,
+ $ctx
+ )
+ );
+ }
+ if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) {
+ $message = sprintf(
+ 'cURL error %s: %s (%s)',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
+ );
+ } else {
+ $message = sprintf(
+ 'cURL error %s: %s (%s) for %s',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html',
+ $easy->request->getUri()
+ );
+ }
+
+ // Create a connection exception if it was a specific error code.
+ $error = isset($connectionErrors[$easy->errno])
+ ? new ConnectException($message, $easy->request, null, $ctx)
+ : new RequestException($message, $easy->request, $easy->response, null, $ctx);
+
+ return \GuzzleHttp\Promise\rejection_for($error);
+ }
+
+ private function getDefaultConf(EasyHandle $easy)
+ {
+ $conf = [
+ '_headers' => $easy->request->getHeaders(),
+ CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
+ CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
+ CURLOPT_RETURNTRANSFER => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ ];
+
+ if (defined('CURLOPT_PROTOCOLS')) {
+ $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
+ }
+
+ $version = $easy->request->getProtocolVersion();
+ if ($version == 1.1) {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
+ } elseif ($version == 2.0) {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
+ } else {
+ $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
+ }
+
+ return $conf;
+ }
+
+ private function applyMethod(EasyHandle $easy, array &$conf)
+ {
+ $body = $easy->request->getBody();
+ $size = $body->getSize();
+
+ if ($size === null || $size > 0) {
+ $this->applyBody($easy->request, $easy->options, $conf);
+ return;
+ }
+
+ $method = $easy->request->getMethod();
+ if ($method === 'PUT' || $method === 'POST') {
+ // See http://tools.ietf.org/html/rfc7230#section-3.3.2
+ if (!$easy->request->hasHeader('Content-Length')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
+ }
+ } elseif ($method === 'HEAD') {
+ $conf[CURLOPT_NOBODY] = true;
+ unset(
+ $conf[CURLOPT_WRITEFUNCTION],
+ $conf[CURLOPT_READFUNCTION],
+ $conf[CURLOPT_FILE],
+ $conf[CURLOPT_INFILE]
+ );
+ }
+ }
+
+ private function applyBody(RequestInterface $request, array $options, array &$conf)
+ {
+ $size = $request->hasHeader('Content-Length')
+ ? (int) $request->getHeaderLine('Content-Length')
+ : null;
+
+ // Send the body as a string if the size is less than 1MB OR if the
+ // [curl][body_as_string] request value is set.
+ if (($size !== null && $size < 1000000) ||
+ !empty($options['_body_as_string'])
+ ) {
+ $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
+ // Don't duplicate the Content-Length header
+ $this->removeHeader('Content-Length', $conf);
+ $this->removeHeader('Transfer-Encoding', $conf);
+ } else {
+ $conf[CURLOPT_UPLOAD] = true;
+ if ($size !== null) {
+ $conf[CURLOPT_INFILESIZE] = $size;
+ $this->removeHeader('Content-Length', $conf);
+ }
+ $body = $request->getBody();
+ if ($body->isSeekable()) {
+ $body->rewind();
+ }
+ $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
+ return $body->read($length);
+ };
+ }
+
+ // If the Expect header is not present, prevent curl from adding it
+ if (!$request->hasHeader('Expect')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
+ }
+
+ // cURL sometimes adds a content-type by default. Prevent this.
+ if (!$request->hasHeader('Content-Type')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
+ }
+ }
+
+ private function applyHeaders(EasyHandle $easy, array &$conf)
+ {
+ foreach ($conf['_headers'] as $name => $values) {
+ foreach ($values as $value) {
+ $value = (string) $value;
+ if ($value === '') {
+ // cURL requires a special format for empty headers.
+ // See https://github.com/guzzle/guzzle/issues/1882 for more details.
+ $conf[CURLOPT_HTTPHEADER][] = "$name;";
+ } else {
+ $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
+ }
+ }
+ }
+
+ // Remove the Accept header if one was not set
+ if (!$easy->request->hasHeader('Accept')) {
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
+ }
+ }
+
+ /**
+ * Remove a header from the options array.
+ *
+ * @param string $name Case-insensitive header to remove
+ * @param array $options Array of options to modify
+ */
+ private function removeHeader($name, array &$options)
+ {
+ foreach (array_keys($options['_headers']) as $key) {
+ if (!strcasecmp($key, $name)) {
+ unset($options['_headers'][$key]);
+ return;
+ }
+ }
+ }
+
+ private function applyHandlerOptions(EasyHandle $easy, array &$conf)
+ {
+ $options = $easy->options;
+ if (isset($options['verify'])) {
+ if ($options['verify'] === false) {
+ unset($conf[CURLOPT_CAINFO]);
+ $conf[CURLOPT_SSL_VERIFYHOST] = 0;
+ $conf[CURLOPT_SSL_VERIFYPEER] = false;
+ } else {
+ $conf[CURLOPT_SSL_VERIFYHOST] = 2;
+ $conf[CURLOPT_SSL_VERIFYPEER] = true;
+ if (is_string($options['verify'])) {
+ // Throw an error if the file/folder/link path is not valid or doesn't exist.
+ if (!file_exists($options['verify'])) {
+ throw new \InvalidArgumentException(
+ "SSL CA bundle not found: {$options['verify']}"
+ );
+ }
+ // If it's a directory or a link to a directory use CURLOPT_CAPATH.
+ // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
+ if (is_dir($options['verify']) ||
+ (is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
+ $conf[CURLOPT_CAPATH] = $options['verify'];
+ } else {
+ $conf[CURLOPT_CAINFO] = $options['verify'];
+ }
+ }
+ }
+ }
+
+ if (!empty($options['decode_content'])) {
+ $accept = $easy->request->getHeaderLine('Accept-Encoding');
+ if ($accept) {
+ $conf[CURLOPT_ENCODING] = $accept;
+ } else {
+ $conf[CURLOPT_ENCODING] = '';
+ // Don't let curl send the header over the wire
+ $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
+ }
+ }
+
+ if (isset($options['sink'])) {
+ $sink = $options['sink'];
+ if (!is_string($sink)) {
+ $sink = \GuzzleHttp\Psr7\stream_for($sink);
+ } elseif (!is_dir(dirname($sink))) {
+ // Ensure that the directory exists before failing in curl.
+ throw new \RuntimeException(sprintf(
+ 'Directory %s does not exist for sink value of %s',
+ dirname($sink),
+ $sink
+ ));
+ } else {
+ $sink = new LazyOpenStream($sink, 'w+');
+ }
+ $easy->sink = $sink;
+ $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
+ return $sink->write($write);
+ };
+ } else {
+ // Use a default temp stream if no sink was set.
+ $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
+ $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
+ }
+ $timeoutRequiresNoSignal = false;
+ if (isset($options['timeout'])) {
+ $timeoutRequiresNoSignal |= $options['timeout'] < 1;
+ $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
+ }
+
+ // CURL default value is CURL_IPRESOLVE_WHATEVER
+ if (isset($options['force_ip_resolve'])) {
+ if ('v4' === $options['force_ip_resolve']) {
+ $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
+ } elseif ('v6' === $options['force_ip_resolve']) {
+ $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
+ }
+ }
+
+ if (isset($options['connect_timeout'])) {
+ $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
+ $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
+ }
+
+ if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
+ $conf[CURLOPT_NOSIGNAL] = true;
+ }
+
+ if (isset($options['proxy'])) {
+ if (!is_array($options['proxy'])) {
+ $conf[CURLOPT_PROXY] = $options['proxy'];
+ } else {
+ $scheme = $easy->request->getUri()->getScheme();
+ if (isset($options['proxy'][$scheme])) {
+ $host = $easy->request->getUri()->getHost();
+ if (!isset($options['proxy']['no']) ||
+ !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
+ ) {
+ $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
+ }
+ }
+ }
+ }
+
+ if (isset($options['cert'])) {
+ $cert = $options['cert'];
+ if (is_array($cert)) {
+ $conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
+ $cert = $cert[0];
+ }
+ if (!file_exists($cert)) {
+ throw new \InvalidArgumentException(
+ "SSL certificate not found: {$cert}"
+ );
+ }
+ $conf[CURLOPT_SSLCERT] = $cert;
+ }
+
+ if (isset($options['ssl_key'])) {
+ $sslKey = $options['ssl_key'];
+ if (is_array($sslKey)) {
+ $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1];
+ $sslKey = $sslKey[0];
+ }
+ if (!file_exists($sslKey)) {
+ throw new \InvalidArgumentException(
+ "SSL private key not found: {$sslKey}"
+ );
+ }
+ $conf[CURLOPT_SSLKEY] = $sslKey;
+ }
+
+ if (isset($options['progress'])) {
+ $progress = $options['progress'];
+ if (!is_callable($progress)) {
+ throw new \InvalidArgumentException(
+ 'progress client option must be callable'
+ );
+ }
+ $conf[CURLOPT_NOPROGRESS] = false;
+ $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
+ $args = func_get_args();
+ // PHP 5.5 pushed the handle onto the start of the args
+ if (is_resource($args[0])) {
+ array_shift($args);
+ }
+ call_user_func_array($progress, $args);
+ };
+ }
+
+ if (!empty($options['debug'])) {
+ $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
+ $conf[CURLOPT_VERBOSE] = true;
+ }
+ }
+
+ /**
+ * This function ensures that a response was set on a transaction. If one
+ * was not set, then the request is retried if possible. This error
+ * typically means you are sending a payload, curl encountered a
+ * "Connection died, retrying a fresh connect" error, tried to rewind the
+ * stream, and then encountered a "necessary data rewind wasn't possible"
+ * error, causing the request to be sent through curl_multi_info_read()
+ * without an error status.
+ */
+ private static function retryFailedRewind(
+ callable $handler,
+ EasyHandle $easy,
+ array $ctx
+ ) {
+ try {
+ // Only rewind if the body has been read from.
+ $body = $easy->request->getBody();
+ if ($body->tell() > 0) {
+ $body->rewind();
+ }
+ } catch (\RuntimeException $e) {
+ $ctx['error'] = 'The connection unexpectedly failed without '
+ . 'providing an error. The request would have been retried, '
+ . 'but attempting to rewind the request body failed. '
+ . 'Exception: ' . $e;
+ return self::createRejection($easy, $ctx);
+ }
+
+ // Retry no more than 3 times before giving up.
+ if (!isset($easy->options['_curl_retries'])) {
+ $easy->options['_curl_retries'] = 1;
+ } elseif ($easy->options['_curl_retries'] == 2) {
+ $ctx['error'] = 'The cURL request was retried 3 times '
+ . 'and did not succeed. The most likely reason for the failure '
+ . 'is that cURL was unable to rewind the body of the request '
+ . 'and subsequent retries resulted in the same error. Turn on '
+ . 'the debug option to see what went wrong. See '
+ . 'https://bugs.php.net/bug.php?id=47204 for more information.';
+ return self::createRejection($easy, $ctx);
+ } else {
+ $easy->options['_curl_retries']++;
+ }
+
+ return $handler($easy->request, $easy->options);
+ }
+
+ private function createHeaderFn(EasyHandle $easy)
+ {
+ if (isset($easy->options['on_headers'])) {
+ $onHeaders = $easy->options['on_headers'];
+
+ if (!is_callable($onHeaders)) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+ } else {
+ $onHeaders = null;
+ }
+
+ return function ($ch, $h) use (
+ $onHeaders,
+ $easy,
+ &$startingResponse
+ ) {
+ $value = trim($h);
+ if ($value === '') {
+ $startingResponse = true;
+ $easy->createResponse();
+ if ($onHeaders !== null) {
+ try {
+ $onHeaders($easy->response);
+ } catch (\Exception $e) {
+ // Associate the exception with the handle and trigger
+ // a curl header write error by returning 0.
+ $easy->onHeadersException = $e;
+ return -1;
+ }
+ }
+ } elseif ($startingResponse) {
+ $startingResponse = false;
+ $easy->headers = [$value];
+ } else {
+ $easy->headers[] = $value;
+ }
+ return strlen($h);
+ };
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
new file mode 100755
index 0000000..b0fc236
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php
@@ -0,0 +1,27 @@
+factory = isset($options['handle_factory'])
+ ? $options['handle_factory']
+ : new CurlFactory(3);
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (isset($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ $easy = $this->factory->create($request, $options);
+ curl_exec($easy->handle);
+ $easy->errno = curl_errno($easy->handle);
+
+ return CurlFactory::finish($this, $easy, $this->factory);
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
new file mode 100755
index 0000000..d829762
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
@@ -0,0 +1,205 @@
+factory = isset($options['handle_factory'])
+ ? $options['handle_factory'] : new CurlFactory(50);
+
+ if (isset($options['select_timeout'])) {
+ $this->selectTimeout = $options['select_timeout'];
+ } elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
+ $this->selectTimeout = $selectTimeout;
+ } else {
+ $this->selectTimeout = 1;
+ }
+ }
+
+ public function __get($name)
+ {
+ if ($name === '_mh') {
+ return $this->_mh = curl_multi_init();
+ }
+
+ throw new \BadMethodCallException();
+ }
+
+ public function __destruct()
+ {
+ if (isset($this->_mh)) {
+ curl_multi_close($this->_mh);
+ unset($this->_mh);
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $easy = $this->factory->create($request, $options);
+ $id = (int) $easy->handle;
+
+ $promise = new Promise(
+ [$this, 'execute'],
+ function () use ($id) {
+ return $this->cancel($id);
+ }
+ );
+
+ $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
+
+ return $promise;
+ }
+
+ /**
+ * Ticks the curl event loop.
+ */
+ public function tick()
+ {
+ // Add any delayed handles if needed.
+ if ($this->delays) {
+ $currentTime = \GuzzleHttp\_current_time();
+ foreach ($this->delays as $id => $delay) {
+ if ($currentTime >= $delay) {
+ unset($this->delays[$id]);
+ curl_multi_add_handle(
+ $this->_mh,
+ $this->handles[$id]['easy']->handle
+ );
+ }
+ }
+ }
+
+ // Step through the task queue which may add additional requests.
+ P\queue()->run();
+
+ if ($this->active &&
+ curl_multi_select($this->_mh, $this->selectTimeout) === -1
+ ) {
+ // Perform a usleep if a select returns -1.
+ // See: https://bugs.php.net/bug.php?id=61141
+ usleep(250);
+ }
+
+ while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);
+
+ $this->processMessages();
+ }
+
+ /**
+ * Runs until all outstanding connections have completed.
+ */
+ public function execute()
+ {
+ $queue = P\queue();
+
+ while ($this->handles || !$queue->isEmpty()) {
+ // If there are no transfers, then sleep for the next delay
+ if (!$this->active && $this->delays) {
+ usleep($this->timeToNext());
+ }
+ $this->tick();
+ }
+ }
+
+ private function addRequest(array $entry)
+ {
+ $easy = $entry['easy'];
+ $id = (int) $easy->handle;
+ $this->handles[$id] = $entry;
+ if (empty($easy->options['delay'])) {
+ curl_multi_add_handle($this->_mh, $easy->handle);
+ } else {
+ $this->delays[$id] = \GuzzleHttp\_current_time() + ($easy->options['delay'] / 1000);
+ }
+ }
+
+ /**
+ * Cancels a handle from sending and removes references to it.
+ *
+ * @param int $id Handle ID to cancel and remove.
+ *
+ * @return bool True on success, false on failure.
+ */
+ private function cancel($id)
+ {
+ // Cannot cancel if it has been processed.
+ if (!isset($this->handles[$id])) {
+ return false;
+ }
+
+ $handle = $this->handles[$id]['easy']->handle;
+ unset($this->delays[$id], $this->handles[$id]);
+ curl_multi_remove_handle($this->_mh, $handle);
+ curl_close($handle);
+
+ return true;
+ }
+
+ private function processMessages()
+ {
+ while ($done = curl_multi_info_read($this->_mh)) {
+ $id = (int) $done['handle'];
+ curl_multi_remove_handle($this->_mh, $done['handle']);
+
+ if (!isset($this->handles[$id])) {
+ // Probably was cancelled.
+ continue;
+ }
+
+ $entry = $this->handles[$id];
+ unset($this->handles[$id], $this->delays[$id]);
+ $entry['easy']->errno = $done['result'];
+ $entry['deferred']->resolve(
+ CurlFactory::finish(
+ $this,
+ $entry['easy'],
+ $this->factory
+ )
+ );
+ }
+ }
+
+ private function timeToNext()
+ {
+ $currentTime = \GuzzleHttp\_current_time();
+ $nextTime = PHP_INT_MAX;
+ foreach ($this->delays as $time) {
+ if ($time < $nextTime) {
+ $nextTime = $time;
+ }
+ }
+
+ return max(0, $nextTime - $currentTime) * 1000000;
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
new file mode 100755
index 0000000..7754e91
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php
@@ -0,0 +1,92 @@
+headers)) {
+ throw new \RuntimeException('No headers have been received');
+ }
+
+ // HTTP-version SP status-code SP reason-phrase
+ $startLine = explode(' ', array_shift($this->headers), 3);
+ $headers = \GuzzleHttp\headers_from_lines($this->headers);
+ $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
+
+ if (!empty($this->options['decode_content'])
+ && isset($normalizedKeys['content-encoding'])
+ ) {
+ $headers['x-encoded-content-encoding']
+ = $headers[$normalizedKeys['content-encoding']];
+ unset($headers[$normalizedKeys['content-encoding']]);
+ if (isset($normalizedKeys['content-length'])) {
+ $headers['x-encoded-content-length']
+ = $headers[$normalizedKeys['content-length']];
+
+ $bodyLength = (int) $this->sink->getSize();
+ if ($bodyLength) {
+ $headers[$normalizedKeys['content-length']] = $bodyLength;
+ } else {
+ unset($headers[$normalizedKeys['content-length']]);
+ }
+ }
+ }
+
+ // Attach a response to the easy handle with the parsed headers.
+ $this->response = new Response(
+ $startLine[1],
+ $headers,
+ $this->sink,
+ substr($startLine[0], 5),
+ isset($startLine[2]) ? (string) $startLine[2] : null
+ );
+ }
+
+ public function __get($name)
+ {
+ $msg = $name === 'handle'
+ ? 'The EasyHandle has been released'
+ : 'Invalid property: ' . $name;
+ throw new \BadMethodCallException($msg);
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
new file mode 100755
index 0000000..d5c449c
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
@@ -0,0 +1,190 @@
+onFulfilled = $onFulfilled;
+ $this->onRejected = $onRejected;
+
+ if ($queue) {
+ call_user_func_array([$this, 'append'], $queue);
+ }
+ }
+
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (!$this->queue) {
+ throw new \OutOfBoundsException('Mock queue is empty');
+ }
+
+ if (isset($options['delay'])) {
+ usleep($options['delay'] * 1000);
+ }
+
+ $this->lastRequest = $request;
+ $this->lastOptions = $options;
+ $response = array_shift($this->queue);
+
+ if (isset($options['on_headers'])) {
+ if (!is_callable($options['on_headers'])) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+ try {
+ $options['on_headers']($response);
+ } catch (\Exception $e) {
+ $msg = 'An error was encountered during the on_headers event';
+ $response = new RequestException($msg, $request, $response, $e);
+ }
+ }
+
+ if (is_callable($response)) {
+ $response = call_user_func($response, $request, $options);
+ }
+
+ $response = $response instanceof \Exception
+ ? \GuzzleHttp\Promise\rejection_for($response)
+ : \GuzzleHttp\Promise\promise_for($response);
+
+ return $response->then(
+ function ($value) use ($request, $options) {
+ $this->invokeStats($request, $options, $value);
+ if ($this->onFulfilled) {
+ call_user_func($this->onFulfilled, $value);
+ }
+ if (isset($options['sink'])) {
+ $contents = (string) $value->getBody();
+ $sink = $options['sink'];
+
+ if (is_resource($sink)) {
+ fwrite($sink, $contents);
+ } elseif (is_string($sink)) {
+ file_put_contents($sink, $contents);
+ } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) {
+ $sink->write($contents);
+ }
+ }
+
+ return $value;
+ },
+ function ($reason) use ($request, $options) {
+ $this->invokeStats($request, $options, null, $reason);
+ if ($this->onRejected) {
+ call_user_func($this->onRejected, $reason);
+ }
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ }
+
+ /**
+ * Adds one or more variadic requests, exceptions, callables, or promises
+ * to the queue.
+ */
+ public function append()
+ {
+ foreach (func_get_args() as $value) {
+ if ($value instanceof ResponseInterface
+ || $value instanceof \Exception
+ || $value instanceof PromiseInterface
+ || is_callable($value)
+ ) {
+ $this->queue[] = $value;
+ } else {
+ throw new \InvalidArgumentException('Expected a response or '
+ . 'exception. Found ' . \GuzzleHttp\describe_type($value));
+ }
+ }
+ }
+
+ /**
+ * Get the last received request.
+ *
+ * @return RequestInterface
+ */
+ public function getLastRequest()
+ {
+ return $this->lastRequest;
+ }
+
+ /**
+ * Get the last received request options.
+ *
+ * @return array
+ */
+ public function getLastOptions()
+ {
+ return $this->lastOptions;
+ }
+
+ /**
+ * Returns the number of remaining items in the queue.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->queue);
+ }
+
+ private function invokeStats(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response = null,
+ $reason = null
+ ) {
+ if (isset($options['on_stats'])) {
+ $transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0;
+ $stats = new TransferStats($request, $response, $transferTime, $reason);
+ call_user_func($options['on_stats'], $stats);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
new file mode 100755
index 0000000..f8b00be
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php
@@ -0,0 +1,55 @@
+withoutHeader('Expect');
+
+ // Append a content-length header if body size is zero to match
+ // cURL's behavior.
+ if (0 === $request->getBody()->getSize()) {
+ $request = $request->withHeader('Content-Length', '0');
+ }
+
+ return $this->createResponse(
+ $request,
+ $options,
+ $this->createStream($request, $options),
+ $startTime
+ );
+ } catch (\InvalidArgumentException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Determine if the error was a networking error.
+ $message = $e->getMessage();
+ // This list can probably get more comprehensive.
+ if (strpos($message, 'getaddrinfo') // DNS lookup failed
+ || strpos($message, 'Connection refused')
+ || strpos($message, "couldn't connect to host") // error on HHVM
+ || strpos($message, "connection attempt failed")
+ ) {
+ $e = new ConnectException($e->getMessage(), $request, $e);
+ }
+ $e = RequestException::wrapException($request, $e);
+ $this->invokeStats($options, $request, $startTime, null, $e);
+
+ return \GuzzleHttp\Promise\rejection_for($e);
+ }
+ }
+
+ private function invokeStats(
+ array $options,
+ RequestInterface $request,
+ $startTime,
+ ResponseInterface $response = null,
+ $error = null
+ ) {
+ if (isset($options['on_stats'])) {
+ $stats = new TransferStats(
+ $request,
+ $response,
+ \GuzzleHttp\_current_time() - $startTime,
+ $error,
+ []
+ );
+ call_user_func($options['on_stats'], $stats);
+ }
+ }
+
+ private function createResponse(
+ RequestInterface $request,
+ array $options,
+ $stream,
+ $startTime
+ ) {
+ $hdrs = $this->lastHeaders;
+ $this->lastHeaders = [];
+ $parts = explode(' ', array_shift($hdrs), 3);
+ $ver = explode('/', $parts[0])[1];
+ $status = $parts[1];
+ $reason = isset($parts[2]) ? $parts[2] : null;
+ $headers = \GuzzleHttp\headers_from_lines($hdrs);
+ list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
+ $stream = Psr7\stream_for($stream);
+ $sink = $stream;
+
+ if (strcasecmp('HEAD', $request->getMethod())) {
+ $sink = $this->createSink($stream, $options);
+ }
+
+ $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
+
+ if (isset($options['on_headers'])) {
+ try {
+ $options['on_headers']($response);
+ } catch (\Exception $e) {
+ $msg = 'An error was encountered during the on_headers event';
+ $ex = new RequestException($msg, $request, $response, $e);
+ return \GuzzleHttp\Promise\rejection_for($ex);
+ }
+ }
+
+ // Do not drain when the request is a HEAD request because they have
+ // no body.
+ if ($sink !== $stream) {
+ $this->drain(
+ $stream,
+ $sink,
+ $response->getHeaderLine('Content-Length')
+ );
+ }
+
+ $this->invokeStats($options, $request, $startTime, $response, null);
+
+ return new FulfilledPromise($response);
+ }
+
+ private function createSink(StreamInterface $stream, array $options)
+ {
+ if (!empty($options['stream'])) {
+ return $stream;
+ }
+
+ $sink = isset($options['sink'])
+ ? $options['sink']
+ : fopen('php://temp', 'r+');
+
+ return is_string($sink)
+ ? new Psr7\LazyOpenStream($sink, 'w+')
+ : Psr7\stream_for($sink);
+ }
+
+ private function checkDecode(array $options, array $headers, $stream)
+ {
+ // Automatically decode responses when instructed.
+ if (!empty($options['decode_content'])) {
+ $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
+ if (isset($normalizedKeys['content-encoding'])) {
+ $encoding = $headers[$normalizedKeys['content-encoding']];
+ if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
+ $stream = new Psr7\InflateStream(
+ Psr7\stream_for($stream)
+ );
+ $headers['x-encoded-content-encoding']
+ = $headers[$normalizedKeys['content-encoding']];
+ // Remove content-encoding header
+ unset($headers[$normalizedKeys['content-encoding']]);
+ // Fix content-length header
+ if (isset($normalizedKeys['content-length'])) {
+ $headers['x-encoded-content-length']
+ = $headers[$normalizedKeys['content-length']];
+
+ $length = (int) $stream->getSize();
+ if ($length === 0) {
+ unset($headers[$normalizedKeys['content-length']]);
+ } else {
+ $headers[$normalizedKeys['content-length']] = [$length];
+ }
+ }
+ }
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ /**
+ * Drains the source stream into the "sink" client option.
+ *
+ * @param StreamInterface $source
+ * @param StreamInterface $sink
+ * @param string $contentLength Header specifying the amount of
+ * data to read.
+ *
+ * @return StreamInterface
+ * @throws \RuntimeException when the sink option is invalid.
+ */
+ private function drain(
+ StreamInterface $source,
+ StreamInterface $sink,
+ $contentLength
+ ) {
+ // If a content-length header is provided, then stop reading once
+ // that number of bytes has been read. This can prevent infinitely
+ // reading from a stream when dealing with servers that do not honor
+ // Connection: Close headers.
+ Psr7\copy_to_stream(
+ $source,
+ $sink,
+ (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
+ );
+
+ $sink->seek(0);
+ $source->close();
+
+ return $sink;
+ }
+
+ /**
+ * Create a resource and check to ensure it was created successfully
+ *
+ * @param callable $callback Callable that returns stream resource
+ *
+ * @return resource
+ * @throws \RuntimeException on error
+ */
+ private function createResource(callable $callback)
+ {
+ $errors = null;
+ set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
+ $errors[] = [
+ 'message' => $msg,
+ 'file' => $file,
+ 'line' => $line
+ ];
+ return true;
+ });
+
+ $resource = $callback();
+ restore_error_handler();
+
+ if (!$resource) {
+ $message = 'Error creating resource: ';
+ foreach ($errors as $err) {
+ foreach ($err as $key => $value) {
+ $message .= "[$key] $value" . PHP_EOL;
+ }
+ }
+ throw new \RuntimeException(trim($message));
+ }
+
+ return $resource;
+ }
+
+ private function createStream(RequestInterface $request, array $options)
+ {
+ static $methods;
+ if (!$methods) {
+ $methods = array_flip(get_class_methods(__CLASS__));
+ }
+
+ // HTTP/1.1 streams using the PHP stream wrapper require a
+ // Connection: close header
+ if ($request->getProtocolVersion() == '1.1'
+ && !$request->hasHeader('Connection')
+ ) {
+ $request = $request->withHeader('Connection', 'close');
+ }
+
+ // Ensure SSL is verified by default
+ if (!isset($options['verify'])) {
+ $options['verify'] = true;
+ }
+
+ $params = [];
+ $context = $this->getDefaultContext($request);
+
+ if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
+ throw new \InvalidArgumentException('on_headers must be callable');
+ }
+
+ if (!empty($options)) {
+ foreach ($options as $key => $value) {
+ $method = "add_{$key}";
+ if (isset($methods[$method])) {
+ $this->{$method}($request, $context, $value, $params);
+ }
+ }
+ }
+
+ if (isset($options['stream_context'])) {
+ if (!is_array($options['stream_context'])) {
+ throw new \InvalidArgumentException('stream_context must be an array');
+ }
+ $context = array_replace_recursive(
+ $context,
+ $options['stream_context']
+ );
+ }
+
+ // Microsoft NTLM authentication only supported with curl handler
+ if (isset($options['auth'])
+ && is_array($options['auth'])
+ && isset($options['auth'][2])
+ && 'ntlm' == $options['auth'][2]
+ ) {
+ throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
+ }
+
+ $uri = $this->resolveHost($request, $options);
+
+ $context = $this->createResource(
+ function () use ($context, $params) {
+ return stream_context_create($context, $params);
+ }
+ );
+
+ return $this->createResource(
+ function () use ($uri, &$http_response_header, $context, $options) {
+ $resource = fopen((string) $uri, 'r', null, $context);
+ $this->lastHeaders = $http_response_header;
+
+ if (isset($options['read_timeout'])) {
+ $readTimeout = $options['read_timeout'];
+ $sec = (int) $readTimeout;
+ $usec = ($readTimeout - $sec) * 100000;
+ stream_set_timeout($resource, $sec, $usec);
+ }
+
+ return $resource;
+ }
+ );
+ }
+
+ private function resolveHost(RequestInterface $request, array $options)
+ {
+ $uri = $request->getUri();
+
+ if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
+ if ('v4' === $options['force_ip_resolve']) {
+ $records = dns_get_record($uri->getHost(), DNS_A);
+ if (!isset($records[0]['ip'])) {
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv4 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
+ }
+ $uri = $uri->withHost($records[0]['ip']);
+ } elseif ('v6' === $options['force_ip_resolve']) {
+ $records = dns_get_record($uri->getHost(), DNS_AAAA);
+ if (!isset($records[0]['ipv6'])) {
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv6 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
+ }
+ $uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
+ }
+ }
+
+ return $uri;
+ }
+
+ private function getDefaultContext(RequestInterface $request)
+ {
+ $headers = '';
+ foreach ($request->getHeaders() as $name => $value) {
+ foreach ($value as $val) {
+ $headers .= "$name: $val\r\n";
+ }
+ }
+
+ $context = [
+ 'http' => [
+ 'method' => $request->getMethod(),
+ 'header' => $headers,
+ 'protocol_version' => $request->getProtocolVersion(),
+ 'ignore_errors' => true,
+ 'follow_location' => 0,
+ ],
+ ];
+
+ $body = (string) $request->getBody();
+
+ if (!empty($body)) {
+ $context['http']['content'] = $body;
+ // Prevent the HTTP handler from adding a Content-Type header.
+ if (!$request->hasHeader('Content-Type')) {
+ $context['http']['header'] .= "Content-Type:\r\n";
+ }
+ }
+
+ $context['http']['header'] = rtrim($context['http']['header']);
+
+ return $context;
+ }
+
+ private function add_proxy(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (!is_array($value)) {
+ $options['http']['proxy'] = $value;
+ } else {
+ $scheme = $request->getUri()->getScheme();
+ if (isset($value[$scheme])) {
+ if (!isset($value['no'])
+ || !\GuzzleHttp\is_host_in_noproxy(
+ $request->getUri()->getHost(),
+ $value['no']
+ )
+ ) {
+ $options['http']['proxy'] = $value[$scheme];
+ }
+ }
+ }
+ }
+
+ private function add_timeout(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value > 0) {
+ $options['http']['timeout'] = $value;
+ }
+ }
+
+ private function add_verify(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === true) {
+ // PHP 5.6 or greater will find the system cert by default. When
+ // < 5.6, use the Guzzle bundled cacert.
+ if (PHP_VERSION_ID < 50600) {
+ $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
+ }
+ } elseif (is_string($value)) {
+ $options['ssl']['cafile'] = $value;
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL CA bundle not found: $value");
+ }
+ } elseif ($value === false) {
+ $options['ssl']['verify_peer'] = false;
+ $options['ssl']['verify_peer_name'] = false;
+ return;
+ } else {
+ throw new \InvalidArgumentException('Invalid verify request option');
+ }
+
+ $options['ssl']['verify_peer'] = true;
+ $options['ssl']['verify_peer_name'] = true;
+ $options['ssl']['allow_self_signed'] = false;
+ }
+
+ private function add_cert(RequestInterface $request, &$options, $value, &$params)
+ {
+ if (is_array($value)) {
+ $options['ssl']['passphrase'] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new \RuntimeException("SSL certificate not found: {$value}");
+ }
+
+ $options['ssl']['local_cert'] = $value;
+ }
+
+ private function add_progress(RequestInterface $request, &$options, $value, &$params)
+ {
+ $this->addNotification(
+ $params,
+ function ($code, $a, $b, $c, $transferred, $total) use ($value) {
+ if ($code == STREAM_NOTIFY_PROGRESS) {
+ $value($total, $transferred, null, null);
+ }
+ }
+ );
+ }
+
+ private function add_debug(RequestInterface $request, &$options, $value, &$params)
+ {
+ if ($value === false) {
+ return;
+ }
+
+ static $map = [
+ STREAM_NOTIFY_CONNECT => 'CONNECT',
+ STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
+ STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
+ STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
+ STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
+ STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
+ STREAM_NOTIFY_PROGRESS => 'PROGRESS',
+ STREAM_NOTIFY_FAILURE => 'FAILURE',
+ STREAM_NOTIFY_COMPLETED => 'COMPLETED',
+ STREAM_NOTIFY_RESOLVE => 'RESOLVE',
+ ];
+ static $args = ['severity', 'message', 'message_code',
+ 'bytes_transferred', 'bytes_max'];
+
+ $value = \GuzzleHttp\debug_resource($value);
+ $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
+ $this->addNotification(
+ $params,
+ function () use ($ident, $value, $map, $args) {
+ $passed = func_get_args();
+ $code = array_shift($passed);
+ fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
+ foreach (array_filter($passed) as $i => $v) {
+ fwrite($value, $args[$i] . ': "' . $v . '" ');
+ }
+ fwrite($value, "\n");
+ }
+ );
+ }
+
+ private function addNotification(array &$params, callable $notify)
+ {
+ // Wrap the existing function if needed.
+ if (!isset($params['notification'])) {
+ $params['notification'] = $notify;
+ } else {
+ $params['notification'] = $this->callArray([
+ $params['notification'],
+ $notify
+ ]);
+ }
+ }
+
+ private function callArray(array $functions)
+ {
+ return function () use ($functions) {
+ $args = func_get_args();
+ foreach ($functions as $fn) {
+ call_user_func_array($fn, $args);
+ }
+ };
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/vendor/guzzlehttp/guzzle/src/HandlerStack.php
new file mode 100755
index 0000000..f001686
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/HandlerStack.php
@@ -0,0 +1,273 @@
+push(Middleware::httpErrors(), 'http_errors');
+ $stack->push(Middleware::redirect(), 'allow_redirects');
+ $stack->push(Middleware::cookies(), 'cookies');
+ $stack->push(Middleware::prepareBody(), 'prepare_body');
+
+ return $stack;
+ }
+
+ /**
+ * @param callable $handler Underlying HTTP handler.
+ */
+ public function __construct(callable $handler = null)
+ {
+ $this->handler = $handler;
+ }
+
+ /**
+ * Invokes the handler stack as a composed handler
+ *
+ * @param RequestInterface $request
+ * @param array $options
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $handler = $this->resolve();
+
+ return $handler($request, $options);
+ }
+
+ /**
+ * Dumps a string representation of the stack.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $depth = 0;
+ $stack = [];
+ if ($this->handler) {
+ $stack[] = "0) Handler: " . $this->debugCallable($this->handler);
+ }
+
+ $result = '';
+ foreach (array_reverse($this->stack) as $tuple) {
+ $depth++;
+ $str = "{$depth}) Name: '{$tuple[1]}', ";
+ $str .= "Function: " . $this->debugCallable($tuple[0]);
+ $result = "> {$str}\n{$result}";
+ $stack[] = $str;
+ }
+
+ foreach (array_keys($stack) as $k) {
+ $result .= "< {$stack[$k]}\n";
+ }
+
+ return $result;
+ }
+
+ /**
+ * Set the HTTP handler that actually returns a promise.
+ *
+ * @param callable $handler Accepts a request and array of options and
+ * returns a Promise.
+ */
+ public function setHandler(callable $handler)
+ {
+ $this->handler = $handler;
+ $this->cached = null;
+ }
+
+ /**
+ * Returns true if the builder has a handler.
+ *
+ * @return bool
+ */
+ public function hasHandler()
+ {
+ return (bool) $this->handler;
+ }
+
+ /**
+ * Unshift a middleware to the bottom of the stack.
+ *
+ * @param callable $middleware Middleware function
+ * @param string $name Name to register for this middleware.
+ */
+ public function unshift(callable $middleware, $name = null)
+ {
+ array_unshift($this->stack, [$middleware, $name]);
+ $this->cached = null;
+ }
+
+ /**
+ * Push a middleware to the top of the stack.
+ *
+ * @param callable $middleware Middleware function
+ * @param string $name Name to register for this middleware.
+ */
+ public function push(callable $middleware, $name = '')
+ {
+ $this->stack[] = [$middleware, $name];
+ $this->cached = null;
+ }
+
+ /**
+ * Add a middleware before another middleware by name.
+ *
+ * @param string $findName Middleware to find
+ * @param callable $middleware Middleware function
+ * @param string $withName Name to register for this middleware.
+ */
+ public function before($findName, callable $middleware, $withName = '')
+ {
+ $this->splice($findName, $withName, $middleware, true);
+ }
+
+ /**
+ * Add a middleware after another middleware by name.
+ *
+ * @param string $findName Middleware to find
+ * @param callable $middleware Middleware function
+ * @param string $withName Name to register for this middleware.
+ */
+ public function after($findName, callable $middleware, $withName = '')
+ {
+ $this->splice($findName, $withName, $middleware, false);
+ }
+
+ /**
+ * Remove a middleware by instance or name from the stack.
+ *
+ * @param callable|string $remove Middleware to remove by instance or name.
+ */
+ public function remove($remove)
+ {
+ $this->cached = null;
+ $idx = is_callable($remove) ? 0 : 1;
+ $this->stack = array_values(array_filter(
+ $this->stack,
+ function ($tuple) use ($idx, $remove) {
+ return $tuple[$idx] !== $remove;
+ }
+ ));
+ }
+
+ /**
+ * Compose the middleware and handler into a single callable function.
+ *
+ * @return callable
+ */
+ public function resolve()
+ {
+ if (!$this->cached) {
+ if (!($prev = $this->handler)) {
+ throw new \LogicException('No handler has been specified');
+ }
+
+ foreach (array_reverse($this->stack) as $fn) {
+ $prev = $fn[0]($prev);
+ }
+
+ $this->cached = $prev;
+ }
+
+ return $this->cached;
+ }
+
+ /**
+ * @param string $name
+ * @return int
+ */
+ private function findByName($name)
+ {
+ foreach ($this->stack as $k => $v) {
+ if ($v[1] === $name) {
+ return $k;
+ }
+ }
+
+ throw new \InvalidArgumentException("Middleware not found: $name");
+ }
+
+ /**
+ * Splices a function into the middleware list at a specific position.
+ *
+ * @param string $findName
+ * @param string $withName
+ * @param callable $middleware
+ * @param bool $before
+ */
+ private function splice($findName, $withName, callable $middleware, $before)
+ {
+ $this->cached = null;
+ $idx = $this->findByName($findName);
+ $tuple = [$middleware, $withName];
+
+ if ($before) {
+ if ($idx === 0) {
+ array_unshift($this->stack, $tuple);
+ } else {
+ $replacement = [$tuple, $this->stack[$idx]];
+ array_splice($this->stack, $idx, 1, $replacement);
+ }
+ } elseif ($idx === count($this->stack) - 1) {
+ $this->stack[] = $tuple;
+ } else {
+ $replacement = [$this->stack[$idx], $tuple];
+ array_splice($this->stack, $idx, 1, $replacement);
+ }
+ }
+
+ /**
+ * Provides a debug string for a given callable.
+ *
+ * @param array|callable $fn Function to write as a string.
+ *
+ * @return string
+ */
+ private function debugCallable($fn)
+ {
+ if (is_string($fn)) {
+ return "callable({$fn})";
+ }
+
+ if (is_array($fn)) {
+ return is_string($fn[0])
+ ? "callable({$fn[0]}::{$fn[1]})"
+ : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])";
+ }
+
+ return 'callable(' . spl_object_hash($fn) . ')';
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatter.php b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php
new file mode 100755
index 0000000..663ac73
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php
@@ -0,0 +1,180 @@
+>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
+ const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';
+
+ /** @var string Template used to format log messages */
+ private $template;
+
+ /**
+ * @param string $template Log message template
+ */
+ public function __construct($template = self::CLF)
+ {
+ $this->template = $template ?: self::CLF;
+ }
+
+ /**
+ * Returns a formatted message string.
+ *
+ * @param RequestInterface $request Request that was sent
+ * @param ResponseInterface $response Response that was received
+ * @param \Exception $error Exception that was received
+ *
+ * @return string
+ */
+ public function format(
+ RequestInterface $request,
+ ResponseInterface $response = null,
+ \Exception $error = null
+ ) {
+ $cache = [];
+
+ return preg_replace_callback(
+ '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
+ function (array $matches) use ($request, $response, $error, &$cache) {
+ if (isset($cache[$matches[1]])) {
+ return $cache[$matches[1]];
+ }
+
+ $result = '';
+ switch ($matches[1]) {
+ case 'request':
+ $result = Psr7\str($request);
+ break;
+ case 'response':
+ $result = $response ? Psr7\str($response) : '';
+ break;
+ case 'req_headers':
+ $result = trim($request->getMethod()
+ . ' ' . $request->getRequestTarget())
+ . ' HTTP/' . $request->getProtocolVersion() . "\r\n"
+ . $this->headers($request);
+ break;
+ case 'res_headers':
+ $result = $response ?
+ sprintf(
+ 'HTTP/%s %d %s',
+ $response->getProtocolVersion(),
+ $response->getStatusCode(),
+ $response->getReasonPhrase()
+ ) . "\r\n" . $this->headers($response)
+ : 'NULL';
+ break;
+ case 'req_body':
+ $result = $request->getBody();
+ break;
+ case 'res_body':
+ $result = $response ? $response->getBody() : 'NULL';
+ break;
+ case 'ts':
+ case 'date_iso_8601':
+ $result = gmdate('c');
+ break;
+ case 'date_common_log':
+ $result = date('d/M/Y:H:i:s O');
+ break;
+ case 'method':
+ $result = $request->getMethod();
+ break;
+ case 'version':
+ $result = $request->getProtocolVersion();
+ break;
+ case 'uri':
+ case 'url':
+ $result = $request->getUri();
+ break;
+ case 'target':
+ $result = $request->getRequestTarget();
+ break;
+ case 'req_version':
+ $result = $request->getProtocolVersion();
+ break;
+ case 'res_version':
+ $result = $response
+ ? $response->getProtocolVersion()
+ : 'NULL';
+ break;
+ case 'host':
+ $result = $request->getHeaderLine('Host');
+ break;
+ case 'hostname':
+ $result = gethostname();
+ break;
+ case 'code':
+ $result = $response ? $response->getStatusCode() : 'NULL';
+ break;
+ case 'phrase':
+ $result = $response ? $response->getReasonPhrase() : 'NULL';
+ break;
+ case 'error':
+ $result = $error ? $error->getMessage() : 'NULL';
+ break;
+ default:
+ // handle prefixed dynamic headers
+ if (strpos($matches[1], 'req_header_') === 0) {
+ $result = $request->getHeaderLine(substr($matches[1], 11));
+ } elseif (strpos($matches[1], 'res_header_') === 0) {
+ $result = $response
+ ? $response->getHeaderLine(substr($matches[1], 11))
+ : 'NULL';
+ }
+ }
+
+ $cache[$matches[1]] = $result;
+ return $result;
+ },
+ $this->template
+ );
+ }
+
+ private function headers(MessageInterface $message)
+ {
+ $result = '';
+ foreach ($message->getHeaders() as $name => $values) {
+ $result .= $name . ': ' . implode(', ', $values) . "\r\n";
+ }
+
+ return trim($result);
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Middleware.php b/vendor/guzzlehttp/guzzle/src/Middleware.php
new file mode 100755
index 0000000..bffc197
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Middleware.php
@@ -0,0 +1,254 @@
+withCookieHeader($request);
+ return $handler($request, $options)
+ ->then(
+ function ($response) use ($cookieJar, $request) {
+ $cookieJar->extractCookies($request, $response);
+ return $response;
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that throws exceptions for 4xx or 5xx responses when the
+ * "http_error" request option is set to true.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function httpErrors()
+ {
+ return function (callable $handler) {
+ return function ($request, array $options) use ($handler) {
+ if (empty($options['http_errors'])) {
+ return $handler($request, $options);
+ }
+ return $handler($request, $options)->then(
+ function (ResponseInterface $response) use ($request) {
+ $code = $response->getStatusCode();
+ if ($code < 400) {
+ return $response;
+ }
+ throw RequestException::create($request, $response);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that pushes history data to an ArrayAccess container.
+ *
+ * @param array|\ArrayAccess $container Container to hold the history (by reference).
+ *
+ * @return callable Returns a function that accepts the next handler.
+ * @throws \InvalidArgumentException if container is not an array or ArrayAccess.
+ */
+ public static function history(&$container)
+ {
+ if (!is_array($container) && !$container instanceof \ArrayAccess) {
+ throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
+ }
+
+ return function (callable $handler) use (&$container) {
+ return function ($request, array $options) use ($handler, &$container) {
+ return $handler($request, $options)->then(
+ function ($value) use ($request, &$container, $options) {
+ $container[] = [
+ 'request' => $request,
+ 'response' => $value,
+ 'error' => null,
+ 'options' => $options
+ ];
+ return $value;
+ },
+ function ($reason) use ($request, &$container, $options) {
+ $container[] = [
+ 'request' => $request,
+ 'response' => null,
+ 'error' => $reason,
+ 'options' => $options
+ ];
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * Middleware that invokes a callback before and after sending a request.
+ *
+ * The provided listener cannot modify or alter the response. It simply
+ * "taps" into the chain to be notified before returning the promise. The
+ * before listener accepts a request and options array, and the after
+ * listener accepts a request, options array, and response promise.
+ *
+ * @param callable $before Function to invoke before forwarding the request.
+ * @param callable $after Function invoked after forwarding.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function tap(callable $before = null, callable $after = null)
+ {
+ return function (callable $handler) use ($before, $after) {
+ return function ($request, array $options) use ($handler, $before, $after) {
+ if ($before) {
+ $before($request, $options);
+ }
+ $response = $handler($request, $options);
+ if ($after) {
+ $after($request, $options, $response);
+ }
+ return $response;
+ };
+ };
+ }
+
+ /**
+ * Middleware that handles request redirects.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function redirect()
+ {
+ return function (callable $handler) {
+ return new RedirectMiddleware($handler);
+ };
+ }
+
+ /**
+ * Middleware that retries requests based on the boolean result of
+ * invoking the provided "decider" function.
+ *
+ * If no delay function is provided, a simple implementation of exponential
+ * backoff will be utilized.
+ *
+ * @param callable $decider Function that accepts the number of retries,
+ * a request, [response], and [exception] and
+ * returns true if the request is to be retried.
+ * @param callable $delay Function that accepts the number of retries and
+ * returns the number of milliseconds to delay.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function retry(callable $decider, callable $delay = null)
+ {
+ return function (callable $handler) use ($decider, $delay) {
+ return new RetryMiddleware($decider, $handler, $delay);
+ };
+ }
+
+ /**
+ * Middleware that logs requests, responses, and errors using a message
+ * formatter.
+ *
+ * @param LoggerInterface $logger Logs messages.
+ * @param MessageFormatter $formatter Formatter used to create message strings.
+ * @param string $logLevel Level at which to log requests.
+ *
+ * @return callable Returns a function that accepts the next handler.
+ */
+ public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = 'info' /* \Psr\Log\LogLevel::INFO */)
+ {
+ return function (callable $handler) use ($logger, $formatter, $logLevel) {
+ return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) {
+ return $handler($request, $options)->then(
+ function ($response) use ($logger, $request, $formatter, $logLevel) {
+ $message = $formatter->format($request, $response);
+ $logger->log($logLevel, $message);
+ return $response;
+ },
+ function ($reason) use ($logger, $request, $formatter) {
+ $response = $reason instanceof RequestException
+ ? $reason->getResponse()
+ : null;
+ $message = $formatter->format($request, $response, $reason);
+ $logger->notice($message);
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ );
+ };
+ };
+ }
+
+ /**
+ * This middleware adds a default content-type if possible, a default
+ * content-length or transfer-encoding header, and the expect header.
+ *
+ * @return callable
+ */
+ public static function prepareBody()
+ {
+ return function (callable $handler) {
+ return new PrepareBodyMiddleware($handler);
+ };
+ }
+
+ /**
+ * Middleware that applies a map function to the request before passing to
+ * the next handler.
+ *
+ * @param callable $fn Function that accepts a RequestInterface and returns
+ * a RequestInterface.
+ * @return callable
+ */
+ public static function mapRequest(callable $fn)
+ {
+ return function (callable $handler) use ($fn) {
+ return function ($request, array $options) use ($handler, $fn) {
+ return $handler($fn($request), $options);
+ };
+ };
+ }
+
+ /**
+ * Middleware that applies a map function to the resolved promise's
+ * response.
+ *
+ * @param callable $fn Function that accepts a ResponseInterface and
+ * returns a ResponseInterface.
+ * @return callable
+ */
+ public static function mapResponse(callable $fn)
+ {
+ return function (callable $handler) use ($fn) {
+ return function ($request, array $options) use ($handler, $fn) {
+ return $handler($request, $options)->then($fn);
+ };
+ };
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/Pool.php b/vendor/guzzlehttp/guzzle/src/Pool.php
new file mode 100755
index 0000000..05c854a
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Pool.php
@@ -0,0 +1,123 @@
+ $rfn) {
+ if ($rfn instanceof RequestInterface) {
+ yield $key => $client->sendAsync($rfn, $opts);
+ } elseif (is_callable($rfn)) {
+ yield $key => $rfn($opts);
+ } else {
+ throw new \InvalidArgumentException('Each value yielded by '
+ . 'the iterator must be a Psr7\Http\Message\RequestInterface '
+ . 'or a callable that returns a promise that fulfills '
+ . 'with a Psr7\Message\Http\ResponseInterface object.');
+ }
+ }
+ };
+
+ $this->each = new EachPromise($requests(), $config);
+ }
+
+ public function promise()
+ {
+ return $this->each->promise();
+ }
+
+ /**
+ * Sends multiple requests concurrently and returns an array of responses
+ * and exceptions that uses the same ordering as the provided requests.
+ *
+ * IMPORTANT: This method keeps every request and response in memory, and
+ * as such, is NOT recommended when sending a large number or an
+ * indeterminate number of requests concurrently.
+ *
+ * @param ClientInterface $client Client used to send the requests
+ * @param array|\Iterator $requests Requests to send concurrently.
+ * @param array $options Passes through the options available in
+ * {@see GuzzleHttp\Pool::__construct}
+ *
+ * @return array Returns an array containing the response or an exception
+ * in the same order that the requests were sent.
+ * @throws \InvalidArgumentException if the event format is incorrect.
+ */
+ public static function batch(
+ ClientInterface $client,
+ $requests,
+ array $options = []
+ ) {
+ $res = [];
+ self::cmpCallback($options, 'fulfilled', $res);
+ self::cmpCallback($options, 'rejected', $res);
+ $pool = new static($client, $requests, $options);
+ $pool->promise()->wait();
+ ksort($res);
+
+ return $res;
+ }
+
+ private static function cmpCallback(array &$options, $name, array &$results)
+ {
+ if (!isset($options[$name])) {
+ $options[$name] = function ($v, $k) use (&$results) {
+ $results[$k] = $v;
+ };
+ } else {
+ $currentFn = $options[$name];
+ $options[$name] = function ($v, $k) use (&$results, $currentFn) {
+ $currentFn($v, $k);
+ $results[$k] = $v;
+ };
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php
new file mode 100755
index 0000000..2eb95f9
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php
@@ -0,0 +1,106 @@
+nextHandler = $nextHandler;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $fn = $this->nextHandler;
+
+ // Don't do anything if the request has no body.
+ if ($request->getBody()->getSize() === 0) {
+ return $fn($request, $options);
+ }
+
+ $modify = [];
+
+ // Add a default content-type if possible.
+ if (!$request->hasHeader('Content-Type')) {
+ if ($uri = $request->getBody()->getMetadata('uri')) {
+ if ($type = Psr7\mimetype_from_filename($uri)) {
+ $modify['set_headers']['Content-Type'] = $type;
+ }
+ }
+ }
+
+ // Add a default content-length or transfer-encoding header.
+ if (!$request->hasHeader('Content-Length')
+ && !$request->hasHeader('Transfer-Encoding')
+ ) {
+ $size = $request->getBody()->getSize();
+ if ($size !== null) {
+ $modify['set_headers']['Content-Length'] = $size;
+ } else {
+ $modify['set_headers']['Transfer-Encoding'] = 'chunked';
+ }
+ }
+
+ // Add the expect header if needed.
+ $this->addExpectHeader($request, $options, $modify);
+
+ return $fn(Psr7\modify_request($request, $modify), $options);
+ }
+
+ private function addExpectHeader(
+ RequestInterface $request,
+ array $options,
+ array &$modify
+ ) {
+ // Determine if the Expect header should be used
+ if ($request->hasHeader('Expect')) {
+ return;
+ }
+
+ $expect = isset($options['expect']) ? $options['expect'] : null;
+
+ // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
+ if ($expect === false || $request->getProtocolVersion() < 1.1) {
+ return;
+ }
+
+ // The expect header is unconditionally enabled
+ if ($expect === true) {
+ $modify['set_headers']['Expect'] = '100-Continue';
+ return;
+ }
+
+ // By default, send the expect header when the payload is > 1mb
+ if ($expect === null) {
+ $expect = 1048576;
+ }
+
+ // Always add if the body cannot be rewound, the size cannot be
+ // determined, or the size is greater than the cutoff threshold
+ $body = $request->getBody();
+ $size = $body->getSize();
+
+ if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
+ $modify['set_headers']['Expect'] = '100-Continue';
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php
new file mode 100755
index 0000000..bff4e4e
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php
@@ -0,0 +1,237 @@
+ 5,
+ 'protocols' => ['http', 'https'],
+ 'strict' => false,
+ 'referer' => false,
+ 'track_redirects' => false,
+ ];
+
+ /** @var callable */
+ private $nextHandler;
+
+ /**
+ * @param callable $nextHandler Next handler to invoke.
+ */
+ public function __construct(callable $nextHandler)
+ {
+ $this->nextHandler = $nextHandler;
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ $fn = $this->nextHandler;
+
+ if (empty($options['allow_redirects'])) {
+ return $fn($request, $options);
+ }
+
+ if ($options['allow_redirects'] === true) {
+ $options['allow_redirects'] = self::$defaultSettings;
+ } elseif (!is_array($options['allow_redirects'])) {
+ throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
+ } else {
+ // Merge the default settings with the provided settings
+ $options['allow_redirects'] += self::$defaultSettings;
+ }
+
+ if (empty($options['allow_redirects']['max'])) {
+ return $fn($request, $options);
+ }
+
+ return $fn($request, $options)
+ ->then(function (ResponseInterface $response) use ($request, $options) {
+ return $this->checkRedirect($request, $options, $response);
+ });
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface|PromiseInterface $response
+ *
+ * @return ResponseInterface|PromiseInterface
+ */
+ public function checkRedirect(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ if (substr($response->getStatusCode(), 0, 1) != '3'
+ || !$response->hasHeader('Location')
+ ) {
+ return $response;
+ }
+
+ $this->guardMax($request, $options);
+ $nextRequest = $this->modifyRequest($request, $options, $response);
+
+ if (isset($options['allow_redirects']['on_redirect'])) {
+ call_user_func(
+ $options['allow_redirects']['on_redirect'],
+ $request,
+ $response,
+ $nextRequest->getUri()
+ );
+ }
+
+ /** @var PromiseInterface|ResponseInterface $promise */
+ $promise = $this($nextRequest, $options);
+
+ // Add headers to be able to track history of redirects.
+ if (!empty($options['allow_redirects']['track_redirects'])) {
+ return $this->withTracking(
+ $promise,
+ (string) $nextRequest->getUri(),
+ $response->getStatusCode()
+ );
+ }
+
+ return $promise;
+ }
+
+ private function withTracking(PromiseInterface $promise, $uri, $statusCode)
+ {
+ return $promise->then(
+ function (ResponseInterface $response) use ($uri, $statusCode) {
+ // Note that we are pushing to the front of the list as this
+ // would be an earlier response than what is currently present
+ // in the history header.
+ $historyHeader = $response->getHeader(self::HISTORY_HEADER);
+ $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
+ array_unshift($historyHeader, $uri);
+ array_unshift($statusHeader, $statusCode);
+ return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
+ ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
+ }
+ );
+ }
+
+ private function guardMax(RequestInterface $request, array &$options)
+ {
+ $current = isset($options['__redirect_count'])
+ ? $options['__redirect_count']
+ : 0;
+ $options['__redirect_count'] = $current + 1;
+ $max = $options['allow_redirects']['max'];
+
+ if ($options['__redirect_count'] > $max) {
+ throw new TooManyRedirectsException(
+ "Will not follow more than {$max} redirects",
+ $request
+ );
+ }
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ * @param ResponseInterface $response
+ *
+ * @return RequestInterface
+ */
+ public function modifyRequest(
+ RequestInterface $request,
+ array $options,
+ ResponseInterface $response
+ ) {
+ // Request modifications to apply.
+ $modify = [];
+ $protocols = $options['allow_redirects']['protocols'];
+
+ // Use a GET request if this is an entity enclosing request and we are
+ // not forcing RFC compliance, but rather emulating what all browsers
+ // would do.
+ $statusCode = $response->getStatusCode();
+ if ($statusCode == 303 ||
+ ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict'])
+ ) {
+ $modify['method'] = 'GET';
+ $modify['body'] = '';
+ }
+
+ $modify['uri'] = $this->redirectUri($request, $response, $protocols);
+ Psr7\rewind_body($request);
+
+ // Add the Referer header if it is told to do so and only
+ // add the header if we are not redirecting from https to http.
+ if ($options['allow_redirects']['referer']
+ && $modify['uri']->getScheme() === $request->getUri()->getScheme()
+ ) {
+ $uri = $request->getUri()->withUserInfo('');
+ $modify['set_headers']['Referer'] = (string) $uri;
+ } else {
+ $modify['remove_headers'][] = 'Referer';
+ }
+
+ // Remove Authorization header if host is different.
+ if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
+ $modify['remove_headers'][] = 'Authorization';
+ }
+
+ return Psr7\modify_request($request, $modify);
+ }
+
+ /**
+ * Set the appropriate URL on the request based on the location header
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param array $protocols
+ *
+ * @return UriInterface
+ */
+ private function redirectUri(
+ RequestInterface $request,
+ ResponseInterface $response,
+ array $protocols
+ ) {
+ $location = Psr7\UriResolver::resolve(
+ $request->getUri(),
+ new Psr7\Uri($response->getHeaderLine('Location'))
+ );
+
+ // Ensure that the redirect URI is allowed based on the protocols.
+ if (!in_array($location->getScheme(), $protocols)) {
+ throw new BadResponseException(
+ sprintf(
+ 'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
+ $location,
+ implode(', ', $protocols)
+ ),
+ $request,
+ $response
+ );
+ }
+
+ return $location;
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/RequestOptions.php b/vendor/guzzlehttp/guzzle/src/RequestOptions.php
new file mode 100755
index 0000000..5c0fd19
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/RequestOptions.php
@@ -0,0 +1,255 @@
+decider = $decider;
+ $this->nextHandler = $nextHandler;
+ $this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
+ }
+
+ /**
+ * Default exponential backoff delay function.
+ *
+ * @param int $retries
+ *
+ * @return int
+ */
+ public static function exponentialDelay($retries)
+ {
+ return (int) pow(2, $retries - 1);
+ }
+
+ /**
+ * @param RequestInterface $request
+ * @param array $options
+ *
+ * @return PromiseInterface
+ */
+ public function __invoke(RequestInterface $request, array $options)
+ {
+ if (!isset($options['retries'])) {
+ $options['retries'] = 0;
+ }
+
+ $fn = $this->nextHandler;
+ return $fn($request, $options)
+ ->then(
+ $this->onFulfilled($request, $options),
+ $this->onRejected($request, $options)
+ );
+ }
+
+ private function onFulfilled(RequestInterface $req, array $options)
+ {
+ return function ($value) use ($req, $options) {
+ if (!call_user_func(
+ $this->decider,
+ $options['retries'],
+ $req,
+ $value,
+ null
+ )) {
+ return $value;
+ }
+ return $this->doRetry($req, $options, $value);
+ };
+ }
+
+ private function onRejected(RequestInterface $req, array $options)
+ {
+ return function ($reason) use ($req, $options) {
+ if (!call_user_func(
+ $this->decider,
+ $options['retries'],
+ $req,
+ null,
+ $reason
+ )) {
+ return \GuzzleHttp\Promise\rejection_for($reason);
+ }
+ return $this->doRetry($req, $options);
+ };
+ }
+
+ private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null)
+ {
+ $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response);
+
+ return $this($request, $options);
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/TransferStats.php b/vendor/guzzlehttp/guzzle/src/TransferStats.php
new file mode 100755
index 0000000..23a22a3
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/TransferStats.php
@@ -0,0 +1,126 @@
+request = $request;
+ $this->response = $response;
+ $this->transferTime = $transferTime;
+ $this->handlerErrorData = $handlerErrorData;
+ $this->handlerStats = $handlerStats;
+ }
+
+ /**
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Returns the response that was received (if any).
+ *
+ * @return ResponseInterface|null
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Returns true if a response was received.
+ *
+ * @return bool
+ */
+ public function hasResponse()
+ {
+ return $this->response !== null;
+ }
+
+ /**
+ * Gets handler specific error data.
+ *
+ * This might be an exception, a integer representing an error code, or
+ * anything else. Relying on this value assumes that you know what handler
+ * you are using.
+ *
+ * @return mixed
+ */
+ public function getHandlerErrorData()
+ {
+ return $this->handlerErrorData;
+ }
+
+ /**
+ * Get the effective URI the request was sent to.
+ *
+ * @return UriInterface
+ */
+ public function getEffectiveUri()
+ {
+ return $this->request->getUri();
+ }
+
+ /**
+ * Get the estimated time the request was being transferred by the handler.
+ *
+ * @return float Time in seconds.
+ */
+ public function getTransferTime()
+ {
+ return $this->transferTime;
+ }
+
+ /**
+ * Gets an array of all of the handler specific transfer data.
+ *
+ * @return array
+ */
+ public function getHandlerStats()
+ {
+ return $this->handlerStats;
+ }
+
+ /**
+ * Get a specific handler statistic from the handler by name.
+ *
+ * @param string $stat Handler specific transfer stat to retrieve.
+ *
+ * @return mixed|null
+ */
+ public function getHandlerStat($stat)
+ {
+ return isset($this->handlerStats[$stat])
+ ? $this->handlerStats[$stat]
+ : null;
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/UriTemplate.php b/vendor/guzzlehttp/guzzle/src/UriTemplate.php
new file mode 100755
index 0000000..96dcfd0
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/UriTemplate.php
@@ -0,0 +1,237 @@
+ ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '+' => ['prefix' => '', 'joiner' => ',', 'query' => false],
+ '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
+ '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
+ '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
+ ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
+ '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
+ '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true]
+ ];
+
+ /** @var array Delimiters */
+ private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$',
+ '&', '\'', '(', ')', '*', '+', ',', ';', '='];
+
+ /** @var array Percent encoded delimiters */
+ private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D',
+ '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
+ '%3B', '%3D'];
+
+ public function expand($template, array $variables)
+ {
+ if (false === strpos($template, '{')) {
+ return $template;
+ }
+
+ $this->template = $template;
+ $this->variables = $variables;
+
+ return preg_replace_callback(
+ '/\{([^\}]+)\}/',
+ [$this, 'expandMatch'],
+ $this->template
+ );
+ }
+
+ /**
+ * Parse an expression into parts
+ *
+ * @param string $expression Expression to parse
+ *
+ * @return array Returns an associative array of parts
+ */
+ private function parseExpression($expression)
+ {
+ $result = [];
+
+ if (isset(self::$operatorHash[$expression[0]])) {
+ $result['operator'] = $expression[0];
+ $expression = substr($expression, 1);
+ } else {
+ $result['operator'] = '';
+ }
+
+ foreach (explode(',', $expression) as $value) {
+ $value = trim($value);
+ $varspec = [];
+ if ($colonPos = strpos($value, ':')) {
+ $varspec['value'] = substr($value, 0, $colonPos);
+ $varspec['modifier'] = ':';
+ $varspec['position'] = (int) substr($value, $colonPos + 1);
+ } elseif (substr($value, -1) === '*') {
+ $varspec['modifier'] = '*';
+ $varspec['value'] = substr($value, 0, -1);
+ } else {
+ $varspec['value'] = (string) $value;
+ $varspec['modifier'] = '';
+ }
+ $result['values'][] = $varspec;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Process an expansion
+ *
+ * @param array $matches Matches met in the preg_replace_callback
+ *
+ * @return string Returns the replacement string
+ */
+ private function expandMatch(array $matches)
+ {
+ static $rfc1738to3986 = ['+' => '%20', '%7e' => '~'];
+
+ $replacements = [];
+ $parsed = self::parseExpression($matches[1]);
+ $prefix = self::$operatorHash[$parsed['operator']]['prefix'];
+ $joiner = self::$operatorHash[$parsed['operator']]['joiner'];
+ $useQuery = self::$operatorHash[$parsed['operator']]['query'];
+
+ foreach ($parsed['values'] as $value) {
+ if (!isset($this->variables[$value['value']])) {
+ continue;
+ }
+
+ $variable = $this->variables[$value['value']];
+ $actuallyUseQuery = $useQuery;
+ $expanded = '';
+
+ if (is_array($variable)) {
+ $isAssoc = $this->isAssoc($variable);
+ $kvp = [];
+ foreach ($variable as $key => $var) {
+ if ($isAssoc) {
+ $key = rawurlencode($key);
+ $isNestedArray = is_array($var);
+ } else {
+ $isNestedArray = false;
+ }
+
+ if (!$isNestedArray) {
+ $var = rawurlencode($var);
+ if ($parsed['operator'] === '+' ||
+ $parsed['operator'] === '#'
+ ) {
+ $var = $this->decodeReserved($var);
+ }
+ }
+
+ if ($value['modifier'] === '*') {
+ if ($isAssoc) {
+ if ($isNestedArray) {
+ // Nested arrays must allow for deeply nested
+ // structures.
+ $var = strtr(
+ http_build_query([$key => $var]),
+ $rfc1738to3986
+ );
+ } else {
+ $var = $key . '=' . $var;
+ }
+ } elseif ($key > 0 && $actuallyUseQuery) {
+ $var = $value['value'] . '=' . $var;
+ }
+ }
+
+ $kvp[$key] = $var;
+ }
+
+ if (empty($variable)) {
+ $actuallyUseQuery = false;
+ } elseif ($value['modifier'] === '*') {
+ $expanded = implode($joiner, $kvp);
+ if ($isAssoc) {
+ // Don't prepend the value name when using the explode
+ // modifier with an associative array.
+ $actuallyUseQuery = false;
+ }
+ } else {
+ if ($isAssoc) {
+ // When an associative array is encountered and the
+ // explode modifier is not set, then the result must be
+ // a comma separated list of keys followed by their
+ // respective values.
+ foreach ($kvp as $k => &$v) {
+ $v = $k . ',' . $v;
+ }
+ }
+ $expanded = implode(',', $kvp);
+ }
+ } else {
+ if ($value['modifier'] === ':') {
+ $variable = substr($variable, 0, $value['position']);
+ }
+ $expanded = rawurlencode($variable);
+ if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
+ $expanded = $this->decodeReserved($expanded);
+ }
+ }
+
+ if ($actuallyUseQuery) {
+ if (!$expanded && $joiner !== '&') {
+ $expanded = $value['value'];
+ } else {
+ $expanded = $value['value'] . '=' . $expanded;
+ }
+ }
+
+ $replacements[] = $expanded;
+ }
+
+ $ret = implode($joiner, $replacements);
+ if ($ret && $prefix) {
+ return $prefix . $ret;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Determines if an array is associative.
+ *
+ * This makes the assumption that input arrays are sequences or hashes.
+ * This assumption is a tradeoff for accuracy in favor of speed, but it
+ * should work in almost every case where input is supplied for a URI
+ * template.
+ *
+ * @param array $array Array to check
+ *
+ * @return bool
+ */
+ private function isAssoc(array $array)
+ {
+ return $array && array_keys($array)[0] !== 0;
+ }
+
+ /**
+ * Removes percent encoding on reserved characters (used with + and #
+ * modifiers).
+ *
+ * @param string $string String to fix
+ *
+ * @return string
+ */
+ private function decodeReserved($string)
+ {
+ return str_replace(self::$delimsPct, self::$delims, $string);
+ }
+}
diff --git a/vendor/guzzlehttp/guzzle/src/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php
new file mode 100755
index 0000000..51d736d
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/functions.php
@@ -0,0 +1,346 @@
+expand($template, $variables);
+}
+
+/**
+ * Debug function used to describe the provided value type and class.
+ *
+ * @param mixed $input
+ *
+ * @return string Returns a string containing the type of the variable and
+ * if a class is provided, the class name.
+ */
+function describe_type($input)
+{
+ switch (gettype($input)) {
+ case 'object':
+ return 'object(' . get_class($input) . ')';
+ case 'array':
+ return 'array(' . count($input) . ')';
+ default:
+ ob_start();
+ var_dump($input);
+ // normalize float vs double
+ return str_replace('double(', 'float(', rtrim(ob_get_clean()));
+ }
+}
+
+/**
+ * Parses an array of header lines into an associative array of headers.
+ *
+ * @param array $lines Header lines array of strings in the following
+ * format: "Name: Value"
+ * @return array
+ */
+function headers_from_lines($lines)
+{
+ $headers = [];
+
+ foreach ($lines as $line) {
+ $parts = explode(':', $line, 2);
+ $headers[trim($parts[0])][] = isset($parts[1])
+ ? trim($parts[1])
+ : null;
+ }
+
+ return $headers;
+}
+
+/**
+ * Returns a debug stream based on the provided variable.
+ *
+ * @param mixed $value Optional value
+ *
+ * @return resource
+ */
+function debug_resource($value = null)
+{
+ if (is_resource($value)) {
+ return $value;
+ } elseif (defined('STDOUT')) {
+ return STDOUT;
+ }
+
+ return fopen('php://output', 'w');
+}
+
+/**
+ * Chooses and creates a default handler to use based on the environment.
+ *
+ * The returned handler is not wrapped by any default middlewares.
+ *
+ * @throws \RuntimeException if no viable Handler is available.
+ * @return callable Returns the best handler for the given system.
+ */
+function choose_handler()
+{
+ $handler = null;
+ if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
+ $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
+ } elseif (function_exists('curl_exec')) {
+ $handler = new CurlHandler();
+ } elseif (function_exists('curl_multi_exec')) {
+ $handler = new CurlMultiHandler();
+ }
+
+ if (ini_get('allow_url_fopen')) {
+ $handler = $handler
+ ? Proxy::wrapStreaming($handler, new StreamHandler())
+ : new StreamHandler();
+ } elseif (!$handler) {
+ throw new \RuntimeException('GuzzleHttp requires cURL, the '
+ . 'allow_url_fopen ini setting, or a custom HTTP handler.');
+ }
+
+ return $handler;
+}
+
+/**
+ * Get the default User-Agent string to use with Guzzle
+ *
+ * @return string
+ */
+function default_user_agent()
+{
+ static $defaultAgent = '';
+
+ if (!$defaultAgent) {
+ $defaultAgent = 'GuzzleHttp/' . Client::VERSION;
+ if (extension_loaded('curl') && function_exists('curl_version')) {
+ $defaultAgent .= ' curl/' . \curl_version()['version'];
+ }
+ $defaultAgent .= ' PHP/' . PHP_VERSION;
+ }
+
+ return $defaultAgent;
+}
+
+/**
+ * Returns the default cacert bundle for the current system.
+ *
+ * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
+ * If those settings are not configured, then the common locations for
+ * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
+ * and Windows are checked. If any of these file locations are found on
+ * disk, they will be utilized.
+ *
+ * Note: the result of this function is cached for subsequent calls.
+ *
+ * @return string
+ * @throws \RuntimeException if no bundle can be found.
+ */
+function default_ca_bundle()
+{
+ static $cached = null;
+ static $cafiles = [
+ // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
+ '/etc/pki/tls/certs/ca-bundle.crt',
+ // Ubuntu, Debian (provided by the ca-certificates package)
+ '/etc/ssl/certs/ca-certificates.crt',
+ // FreeBSD (provided by the ca_root_nss package)
+ '/usr/local/share/certs/ca-root-nss.crt',
+ // SLES 12 (provided by the ca-certificates package)
+ '/var/lib/ca-certificates/ca-bundle.pem',
+ // OS X provided by homebrew (using the default path)
+ '/usr/local/etc/openssl/cert.pem',
+ // Google app engine
+ '/etc/ca-certificates.crt',
+ // Windows?
+ 'C:\\windows\\system32\\curl-ca-bundle.crt',
+ 'C:\\windows\\curl-ca-bundle.crt',
+ ];
+
+ if ($cached) {
+ return $cached;
+ }
+
+ if ($ca = ini_get('openssl.cafile')) {
+ return $cached = $ca;
+ }
+
+ if ($ca = ini_get('curl.cainfo')) {
+ return $cached = $ca;
+ }
+
+ foreach ($cafiles as $filename) {
+ if (file_exists($filename)) {
+ return $cached = $filename;
+ }
+ }
+
+ throw new \RuntimeException(
+ <<< EOT
+No system CA bundle could be found in any of the the common system locations.
+PHP versions earlier than 5.6 are not properly configured to use the system's
+CA bundle by default. In order to verify peer certificates, you will need to
+supply the path on disk to a certificate bundle to the 'verify' request
+option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
+need a specific certificate bundle, then Mozilla provides a commonly used CA
+bundle which can be downloaded here (provided by the maintainer of cURL):
+https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
+you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
+ini setting to point to the path to the file, allowing you to omit the 'verify'
+request option. See http://curl.haxx.se/docs/sslcerts.html for more
+information.
+EOT
+ );
+}
+
+/**
+ * Creates an associative array of lowercase header names to the actual
+ * header casing.
+ *
+ * @param array $headers
+ *
+ * @return array
+ */
+function normalize_header_keys(array $headers)
+{
+ $result = [];
+ foreach (array_keys($headers) as $key) {
+ $result[strtolower($key)] = $key;
+ }
+
+ return $result;
+}
+
+/**
+ * Returns true if the provided host matches any of the no proxy areas.
+ *
+ * This method will strip a port from the host if it is present. Each pattern
+ * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
+ * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
+ * "baz.foo.com", but ".foo.com" != "foo.com").
+ *
+ * Areas are matched in the following cases:
+ * 1. "*" (without quotes) always matches any hosts.
+ * 2. An exact match.
+ * 3. The area starts with "." and the area is the last part of the host. e.g.
+ * '.mit.edu' will match any host that ends with '.mit.edu'.
+ *
+ * @param string $host Host to check against the patterns.
+ * @param array $noProxyArray An array of host patterns.
+ *
+ * @return bool
+ */
+function is_host_in_noproxy($host, array $noProxyArray)
+{
+ if (strlen($host) === 0) {
+ throw new \InvalidArgumentException('Empty host provided');
+ }
+
+ // Strip port if present.
+ if (strpos($host, ':')) {
+ $host = explode($host, ':', 2)[0];
+ }
+
+ foreach ($noProxyArray as $area) {
+ // Always match on wildcards.
+ if ($area === '*') {
+ return true;
+ } elseif (empty($area)) {
+ // Don't match on empty values.
+ continue;
+ } elseif ($area === $host) {
+ // Exact matches.
+ return true;
+ } else {
+ // Special match if the area when prefixed with ".". Remove any
+ // existing leading "." and add a new leading ".".
+ $area = '.' . ltrim($area, '.');
+ if (substr($host, -(strlen($area))) === $area) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Wrapper for json_decode that throws when an error occurs.
+ *
+ * @param string $json JSON data to parse
+ * @param bool $assoc When true, returned objects will be converted
+ * into associative arrays.
+ * @param int $depth User specified recursion depth.
+ * @param int $options Bitmask of JSON decode options.
+ *
+ * @return mixed
+ * @throws Exception\InvalidArgumentException if the JSON cannot be decoded.
+ * @link http://www.php.net/manual/en/function.json-decode.php
+ */
+function json_decode($json, $assoc = false, $depth = 512, $options = 0)
+{
+ $data = \json_decode($json, $assoc, $depth, $options);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new Exception\InvalidArgumentException(
+ 'json_decode error: ' . json_last_error_msg()
+ );
+ }
+
+ return $data;
+}
+
+/**
+ * Wrapper for JSON encoding that throws when an error occurs.
+ *
+ * @param mixed $value The value being encoded
+ * @param int $options JSON encode option bitmask
+ * @param int $depth Set the maximum depth. Must be greater than zero.
+ *
+ * @return string
+ * @throws Exception\InvalidArgumentException if the JSON cannot be encoded.
+ * @link http://www.php.net/manual/en/function.json-encode.php
+ */
+function json_encode($value, $options = 0, $depth = 512)
+{
+ $json = \json_encode($value, $options, $depth);
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ throw new Exception\InvalidArgumentException(
+ 'json_encode error: ' . json_last_error_msg()
+ );
+ }
+
+ return $json;
+}
+
+/**
+ * Wrapper for the hrtime() or microtime() functions
+ * (depending on the PHP version, one of the two is used)
+ *
+ * @return float|mixed UNIX timestamp
+ * @internal
+ */
+function _current_time()
+{
+ return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true);
+}
diff --git a/vendor/guzzlehttp/guzzle/src/functions_include.php b/vendor/guzzlehttp/guzzle/src/functions_include.php
new file mode 100755
index 0000000..a93393a
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/functions_include.php
@@ -0,0 +1,6 @@
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/guzzlehttp/promises/Makefile b/vendor/guzzlehttp/promises/Makefile
new file mode 100755
index 0000000..8d5b3ef
--- /dev/null
+++ b/vendor/guzzlehttp/promises/Makefile
@@ -0,0 +1,13 @@
+all: clean test
+
+test:
+ vendor/bin/phpunit
+
+coverage:
+ vendor/bin/phpunit --coverage-html=artifacts/coverage
+
+view-coverage:
+ open artifacts/coverage/index.html
+
+clean:
+ rm -rf artifacts/*
diff --git a/vendor/guzzlehttp/promises/README.md b/vendor/guzzlehttp/promises/README.md
new file mode 100755
index 0000000..7b607e2
--- /dev/null
+++ b/vendor/guzzlehttp/promises/README.md
@@ -0,0 +1,504 @@
+# Guzzle Promises
+
+[Promises/A+](https://promisesaplus.com/) implementation that handles promise
+chaining and resolution iteratively, allowing for "infinite" promise chaining
+while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/)
+for a general introduction to promises.
+
+- [Features](#features)
+- [Quick start](#quick-start)
+- [Synchronous wait](#synchronous-wait)
+- [Cancellation](#cancellation)
+- [API](#api)
+ - [Promise](#promise)
+ - [FulfilledPromise](#fulfilledpromise)
+ - [RejectedPromise](#rejectedpromise)
+- [Promise interop](#promise-interop)
+- [Implementation notes](#implementation-notes)
+
+
+# Features
+
+- [Promises/A+](https://promisesaplus.com/) implementation.
+- Promise resolution and chaining is handled iteratively, allowing for
+ "infinite" promise chaining.
+- Promises have a synchronous `wait` method.
+- Promises can be cancelled.
+- Works with any object that has a `then` function.
+- C# style async/await coroutine promises using
+ `GuzzleHttp\Promise\coroutine()`.
+
+
+# Quick start
+
+A *promise* represents the eventual result of an asynchronous operation. The
+primary way of interacting with a promise is through its `then` method, which
+registers callbacks to receive either a promise's eventual value or the reason
+why the promise cannot be fulfilled.
+
+
+## Callbacks
+
+Callbacks are registered with the `then` method by providing an optional
+`$onFulfilled` followed by an optional `$onRejected` function.
+
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(
+ // $onFulfilled
+ function ($value) {
+ echo 'The promise was fulfilled.';
+ },
+ // $onRejected
+ function ($reason) {
+ echo 'The promise was rejected.';
+ }
+);
+```
+
+*Resolving* a promise means that you either fulfill a promise with a *value* or
+reject a promise with a *reason*. Resolving a promises triggers callbacks
+registered with the promises's `then` method. These callbacks are triggered
+only once and in the order in which they were added.
+
+
+## Resolving a promise
+
+Promises are fulfilled using the `resolve($value)` method. Resolving a promise
+with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
+all of the onFulfilled callbacks (resolving a promise with a rejected promise
+will reject the promise and trigger the `$onRejected` callbacks).
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise
+ ->then(function ($value) {
+ // Return a value and don't break the chain
+ return "Hello, " . $value;
+ })
+ // This then is executed after the first then and receives the value
+ // returned from the first then.
+ ->then(function ($value) {
+ echo $value;
+ });
+
+// Resolving the promise triggers the $onFulfilled callbacks and outputs
+// "Hello, reader".
+$promise->resolve('reader.');
+```
+
+
+## Promise forwarding
+
+Promises can be chained one after the other. Each then in the chain is a new
+promise. The return value of a promise is what's forwarded to the next
+promise in the chain. Returning a promise in a `then` callback will cause the
+subsequent promises in the chain to only be fulfilled when the returned promise
+has been fulfilled. The next promise in the chain will be invoked with the
+resolved value of the promise.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$nextPromise = new Promise();
+
+$promise
+ ->then(function ($value) use ($nextPromise) {
+ echo $value;
+ return $nextPromise;
+ })
+ ->then(function ($value) {
+ echo $value;
+ });
+
+// Triggers the first callback and outputs "A"
+$promise->resolve('A');
+// Triggers the second callback and outputs "B"
+$nextPromise->resolve('B');
+```
+
+## Promise rejection
+
+When a promise is rejected, the `$onRejected` callbacks are invoked with the
+rejection reason.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ echo $reason;
+});
+
+$promise->reject('Error!');
+// Outputs "Error!"
+```
+
+## Rejection forwarding
+
+If an exception is thrown in an `$onRejected` callback, subsequent
+`$onRejected` callbacks are invoked with the thrown exception as the reason.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ throw new \Exception($reason);
+})->then(null, function ($reason) {
+ assert($reason->getMessage() === 'Error!');
+});
+
+$promise->reject('Error!');
+```
+
+You can also forward a rejection down the promise chain by returning a
+`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or
+`$onRejected` callback.
+
+```php
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new Promise();
+$promise->then(null, function ($reason) {
+ return new RejectedPromise($reason);
+})->then(null, function ($reason) {
+ assert($reason === 'Error!');
+});
+
+$promise->reject('Error!');
+```
+
+If an exception is not thrown in a `$onRejected` callback and the callback
+does not return a rejected promise, downstream `$onFulfilled` callbacks are
+invoked using the value returned from the `$onRejected` callback.
+
+```php
+use GuzzleHttp\Promise\Promise;
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new Promise();
+$promise
+ ->then(null, function ($reason) {
+ return "It's ok";
+ })
+ ->then(function ($value) {
+ assert($value === "It's ok");
+ });
+
+$promise->reject('Error!');
+```
+
+# Synchronous wait
+
+You can synchronously force promises to complete using a promise's `wait`
+method. When creating a promise, you can provide a wait function that is used
+to synchronously force a promise to complete. When a wait function is invoked
+it is expected to deliver a value to the promise or reject the promise. If the
+wait function does not deliver a value, then an exception is thrown. The wait
+function provided to a promise constructor is invoked when the `wait` function
+of the promise is called.
+
+```php
+$promise = new Promise(function () use (&$promise) {
+ $promise->resolve('foo');
+});
+
+// Calling wait will return the value of the promise.
+echo $promise->wait(); // outputs "foo"
+```
+
+If an exception is encountered while invoking the wait function of a promise,
+the promise is rejected with the exception and the exception is thrown.
+
+```php
+$promise = new Promise(function () use (&$promise) {
+ throw new \Exception('foo');
+});
+
+$promise->wait(); // throws the exception.
+```
+
+Calling `wait` on a promise that has been fulfilled will not trigger the wait
+function. It will simply return the previously resolved value.
+
+```php
+$promise = new Promise(function () { die('this is not called!'); });
+$promise->resolve('foo');
+echo $promise->wait(); // outputs "foo"
+```
+
+Calling `wait` on a promise that has been rejected will throw an exception. If
+the rejection reason is an instance of `\Exception` the reason is thrown.
+Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
+can be obtained by calling the `getReason` method of the exception.
+
+```php
+$promise = new Promise();
+$promise->reject('foo');
+$promise->wait();
+```
+
+> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'
+
+
+## Unwrapping a promise
+
+When synchronously waiting on a promise, you are joining the state of the
+promise into the current state of execution (i.e., return the value of the
+promise if it was fulfilled or throw an exception if it was rejected). This is
+called "unwrapping" the promise. Waiting on a promise will by default unwrap
+the promise state.
+
+You can force a promise to resolve and *not* unwrap the state of the promise
+by passing `false` to the first argument of the `wait` function:
+
+```php
+$promise = new Promise();
+$promise->reject('foo');
+// This will not throw an exception. It simply ensures the promise has
+// been resolved.
+$promise->wait(false);
+```
+
+When unwrapping a promise, the resolved value of the promise will be waited
+upon until the unwrapped value is not a promise. This means that if you resolve
+promise A with a promise B and unwrap promise A, the value returned by the
+wait function will be the value delivered to promise B.
+
+**Note**: when you do not unwrap the promise, no value is returned.
+
+
+# Cancellation
+
+You can cancel a promise that has not yet been fulfilled using the `cancel()`
+method of a promise. When creating a promise you can provide an optional
+cancel function that when invoked cancels the action of computing a resolution
+of the promise.
+
+
+# API
+
+
+## Promise
+
+When creating a promise object, you can provide an optional `$waitFn` and
+`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
+expected to resolve the promise. `$cancelFn` is a function with no arguments
+that is expected to cancel the computation of a promise. It is invoked when the
+`cancel()` method of a promise is called.
+
+```php
+use GuzzleHttp\Promise\Promise;
+
+$promise = new Promise(
+ function () use (&$promise) {
+ $promise->resolve('waited');
+ },
+ function () {
+ // do something that will cancel the promise computation (e.g., close
+ // a socket, cancel a database query, etc...)
+ }
+);
+
+assert('waited' === $promise->wait());
+```
+
+A promise has the following methods:
+
+- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
+
+ Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.
+
+- `otherwise(callable $onRejected) : PromiseInterface`
+
+ Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
+
+- `wait($unwrap = true) : mixed`
+
+ Synchronously waits on the promise to complete.
+
+ `$unwrap` controls whether or not the value of the promise is returned for a
+ fulfilled promise or if an exception is thrown if the promise is rejected.
+ This is set to `true` by default.
+
+- `cancel()`
+
+ Attempts to cancel the promise if possible. The promise being cancelled and
+ the parent most ancestor that has not yet been resolved will also be
+ cancelled. Any promises waiting on the cancelled promise to resolve will also
+ be cancelled.
+
+- `getState() : string`
+
+ Returns the state of the promise. One of `pending`, `fulfilled`, or
+ `rejected`.
+
+- `resolve($value)`
+
+ Fulfills the promise with the given `$value`.
+
+- `reject($reason)`
+
+ Rejects the promise with the given `$reason`.
+
+
+## FulfilledPromise
+
+A fulfilled promise can be created to represent a promise that has been
+fulfilled.
+
+```php
+use GuzzleHttp\Promise\FulfilledPromise;
+
+$promise = new FulfilledPromise('value');
+
+// Fulfilled callbacks are immediately invoked.
+$promise->then(function ($value) {
+ echo $value;
+});
+```
+
+
+## RejectedPromise
+
+A rejected promise can be created to represent a promise that has been
+rejected.
+
+```php
+use GuzzleHttp\Promise\RejectedPromise;
+
+$promise = new RejectedPromise('Error');
+
+// Rejected callbacks are immediately invoked.
+$promise->then(null, function ($reason) {
+ echo $reason;
+});
+```
+
+
+# Promise interop
+
+This library works with foreign promises that have a `then` method. This means
+you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
+for example. When a foreign promise is returned inside of a then method
+callback, promise resolution will occur recursively.
+
+```php
+// Create a React promise
+$deferred = new React\Promise\Deferred();
+$reactPromise = $deferred->promise();
+
+// Create a Guzzle promise that is fulfilled with a React promise.
+$guzzlePromise = new \GuzzleHttp\Promise\Promise();
+$guzzlePromise->then(function ($value) use ($reactPromise) {
+ // Do something something with the value...
+ // Return the React promise
+ return $reactPromise;
+});
+```
+
+Please note that wait and cancel chaining is no longer possible when forwarding
+a foreign promise. You will need to wrap a third-party promise with a Guzzle
+promise in order to utilize wait and cancel functions with foreign promises.
+
+
+## Event Loop Integration
+
+In order to keep the stack size constant, Guzzle promises are resolved
+asynchronously using a task queue. When waiting on promises synchronously, the
+task queue will be automatically run to ensure that the blocking promise and
+any forwarded promises are resolved. When using promises asynchronously in an
+event loop, you will need to run the task queue on each tick of the loop. If
+you do not run the task queue, then promises will not be resolved.
+
+You can run the task queue using the `run()` method of the global task queue
+instance.
+
+```php
+// Get the global task queue
+$queue = \GuzzleHttp\Promise\queue();
+$queue->run();
+```
+
+For example, you could use Guzzle promises with React using a periodic timer:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$loop->addPeriodicTimer(0, [$queue, 'run']);
+```
+
+*TODO*: Perhaps adding a `futureTick()` on each tick would be faster?
+
+
+# Implementation notes
+
+
+## Promise resolution and chaining is handled iteratively
+
+By shuffling pending handlers from one owner to another, promises are
+resolved iteratively, allowing for "infinite" then chaining.
+
+```php
+then(function ($v) {
+ // The stack size remains constant (a good thing)
+ echo xdebug_get_stack_depth() . ', ';
+ return $v + 1;
+ });
+}
+
+$parent->resolve(0);
+var_dump($p->wait()); // int(1000)
+
+```
+
+When a promise is fulfilled or rejected with a non-promise value, the promise
+then takes ownership of the handlers of each child promise and delivers values
+down the chain without using recursion.
+
+When a promise is resolved with another promise, the original promise transfers
+all of its pending handlers to the new promise. When the new promise is
+eventually resolved, all of the pending handlers are delivered the forwarded
+value.
+
+
+## A promise is the deferred.
+
+Some promise libraries implement promises using a deferred object to represent
+a computation and a promise object to represent the delivery of the result of
+the computation. This is a nice separation of computation and delivery because
+consumers of the promise cannot modify the value that will be eventually
+delivered.
+
+One side effect of being able to implement promise resolution and chaining
+iteratively is that you need to be able for one promise to reach into the state
+of another promise to shuffle around ownership of handlers. In order to achieve
+this without making the handlers of a promise publicly mutable, a promise is
+also the deferred value, allowing promises of the same parent class to reach
+into and modify the private properties of promises of the same type. While this
+does allow consumers of the value to modify the resolution or rejection of the
+deferred, it is a small price to pay for keeping the stack size constant.
+
+```php
+$promise = new Promise();
+$promise->then(function ($value) { echo $value; });
+// The promise is the deferred value, so you can deliver a value to it.
+$promise->resolve('foo');
+// prints "foo"
+```
diff --git a/vendor/guzzlehttp/promises/composer.json b/vendor/guzzlehttp/promises/composer.json
new file mode 100755
index 0000000..ec41a61
--- /dev/null
+++ b/vendor/guzzlehttp/promises/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "guzzlehttp/promises",
+ "description": "Guzzle promises library",
+ "keywords": ["promise"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit",
+ "test-ci": "vendor/bin/phpunit --coverage-text"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/AggregateException.php b/vendor/guzzlehttp/promises/src/AggregateException.php
new file mode 100755
index 0000000..6a5690c
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/AggregateException.php
@@ -0,0 +1,16 @@
+then(function ($v) { echo $v; });
+ *
+ * @param callable $generatorFn Generator function to wrap into a promise.
+ *
+ * @return Promise
+ * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
+ */
+final class Coroutine implements PromiseInterface
+{
+ /**
+ * @var PromiseInterface|null
+ */
+ private $currentPromise;
+
+ /**
+ * @var Generator
+ */
+ private $generator;
+
+ /**
+ * @var Promise
+ */
+ private $result;
+
+ public function __construct(callable $generatorFn)
+ {
+ $this->generator = $generatorFn();
+ $this->result = new Promise(function () {
+ while (isset($this->currentPromise)) {
+ $this->currentPromise->wait();
+ }
+ });
+ $this->nextCoroutine($this->generator->current());
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ return $this->result->then($onFulfilled, $onRejected);
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->result->otherwise($onRejected);
+ }
+
+ public function wait($unwrap = true)
+ {
+ return $this->result->wait($unwrap);
+ }
+
+ public function getState()
+ {
+ return $this->result->getState();
+ }
+
+ public function resolve($value)
+ {
+ $this->result->resolve($value);
+ }
+
+ public function reject($reason)
+ {
+ $this->result->reject($reason);
+ }
+
+ public function cancel()
+ {
+ $this->currentPromise->cancel();
+ $this->result->cancel();
+ }
+
+ private function nextCoroutine($yielded)
+ {
+ $this->currentPromise = promise_for($yielded)
+ ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
+ }
+
+ /**
+ * @internal
+ */
+ public function _handleSuccess($value)
+ {
+ unset($this->currentPromise);
+ try {
+ $next = $this->generator->send($value);
+ if ($this->generator->valid()) {
+ $this->nextCoroutine($next);
+ } else {
+ $this->result->resolve($value);
+ }
+ } catch (Exception $exception) {
+ $this->result->reject($exception);
+ } catch (Throwable $throwable) {
+ $this->result->reject($throwable);
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public function _handleFailure($reason)
+ {
+ unset($this->currentPromise);
+ try {
+ $nextYield = $this->generator->throw(exception_for($reason));
+ // The throw was caught, so keep iterating on the coroutine
+ $this->nextCoroutine($nextYield);
+ } catch (Exception $exception) {
+ $this->result->reject($exception);
+ } catch (Throwable $throwable) {
+ $this->result->reject($throwable);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/EachPromise.php b/vendor/guzzlehttp/promises/src/EachPromise.php
new file mode 100755
index 0000000..d0ddf60
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/EachPromise.php
@@ -0,0 +1,229 @@
+iterable = iter_for($iterable);
+
+ if (isset($config['concurrency'])) {
+ $this->concurrency = $config['concurrency'];
+ }
+
+ if (isset($config['fulfilled'])) {
+ $this->onFulfilled = $config['fulfilled'];
+ }
+
+ if (isset($config['rejected'])) {
+ $this->onRejected = $config['rejected'];
+ }
+ }
+
+ public function promise()
+ {
+ if ($this->aggregate) {
+ return $this->aggregate;
+ }
+
+ try {
+ $this->createPromise();
+ $this->iterable->rewind();
+ $this->refillPending();
+ } catch (\Throwable $e) {
+ $this->aggregate->reject($e);
+ } catch (\Exception $e) {
+ $this->aggregate->reject($e);
+ }
+
+ return $this->aggregate;
+ }
+
+ private function createPromise()
+ {
+ $this->mutex = false;
+ $this->aggregate = new Promise(function () {
+ reset($this->pending);
+ if (empty($this->pending) && !$this->iterable->valid()) {
+ $this->aggregate->resolve(null);
+ return;
+ }
+
+ // Consume a potentially fluctuating list of promises while
+ // ensuring that indexes are maintained (precluding array_shift).
+ while ($promise = current($this->pending)) {
+ next($this->pending);
+ $promise->wait();
+ if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+ }
+ });
+
+ // Clear the references when the promise is resolved.
+ $clearFn = function () {
+ $this->iterable = $this->concurrency = $this->pending = null;
+ $this->onFulfilled = $this->onRejected = null;
+ };
+
+ $this->aggregate->then($clearFn, $clearFn);
+ }
+
+ private function refillPending()
+ {
+ if (!$this->concurrency) {
+ // Add all pending promises.
+ while ($this->addPending() && $this->advanceIterator());
+ return;
+ }
+
+ // Add only up to N pending promises.
+ $concurrency = is_callable($this->concurrency)
+ ? call_user_func($this->concurrency, count($this->pending))
+ : $this->concurrency;
+ $concurrency = max($concurrency - count($this->pending), 0);
+ // Concurrency may be set to 0 to disallow new promises.
+ if (!$concurrency) {
+ return;
+ }
+ // Add the first pending promise.
+ $this->addPending();
+ // Note this is special handling for concurrency=1 so that we do
+ // not advance the iterator after adding the first promise. This
+ // helps work around issues with generators that might not have the
+ // next value to yield until promise callbacks are called.
+ while (--$concurrency
+ && $this->advanceIterator()
+ && $this->addPending());
+ }
+
+ private function addPending()
+ {
+ if (!$this->iterable || !$this->iterable->valid()) {
+ return false;
+ }
+
+ $promise = promise_for($this->iterable->current());
+ $idx = $this->iterable->key();
+
+ $this->pending[$idx] = $promise->then(
+ function ($value) use ($idx) {
+ if ($this->onFulfilled) {
+ call_user_func(
+ $this->onFulfilled, $value, $idx, $this->aggregate
+ );
+ }
+ $this->step($idx);
+ },
+ function ($reason) use ($idx) {
+ if ($this->onRejected) {
+ call_user_func(
+ $this->onRejected, $reason, $idx, $this->aggregate
+ );
+ }
+ $this->step($idx);
+ }
+ );
+
+ return true;
+ }
+
+ private function advanceIterator()
+ {
+ // Place a lock on the iterator so that we ensure to not recurse,
+ // preventing fatal generator errors.
+ if ($this->mutex) {
+ return false;
+ }
+
+ $this->mutex = true;
+
+ try {
+ $this->iterable->next();
+ $this->mutex = false;
+ return true;
+ } catch (\Throwable $e) {
+ $this->aggregate->reject($e);
+ $this->mutex = false;
+ return false;
+ } catch (\Exception $e) {
+ $this->aggregate->reject($e);
+ $this->mutex = false;
+ return false;
+ }
+ }
+
+ private function step($idx)
+ {
+ // If the promise was already resolved, then ignore this step.
+ if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+
+ unset($this->pending[$idx]);
+
+ // Only refill pending promises if we are not locked, preventing the
+ // EachPromise to recursively invoke the provided iterator, which
+ // cause a fatal error: "Cannot resume an already running generator"
+ if ($this->advanceIterator() && !$this->checkIfFinished()) {
+ // Add more pending promises if possible.
+ $this->refillPending();
+ }
+ }
+
+ private function checkIfFinished()
+ {
+ if (!$this->pending && !$this->iterable->valid()) {
+ // Resolve the promise if there's nothing left to do.
+ $this->aggregate->resolve(null);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/vendor/guzzlehttp/promises/src/FulfilledPromise.php
new file mode 100755
index 0000000..dbbeeb9
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/FulfilledPromise.php
@@ -0,0 +1,82 @@
+value = $value;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ // Return itself if there is no onFulfilled function.
+ if (!$onFulfilled) {
+ return $this;
+ }
+
+ $queue = queue();
+ $p = new Promise([$queue, 'run']);
+ $value = $this->value;
+ $queue->add(static function () use ($p, $value, $onFulfilled) {
+ if ($p->getState() === self::PENDING) {
+ try {
+ $p->resolve($onFulfilled($value));
+ } catch (\Throwable $e) {
+ $p->reject($e);
+ } catch (\Exception $e) {
+ $p->reject($e);
+ }
+ }
+ });
+
+ return $p;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true, $defaultDelivery = null)
+ {
+ return $unwrap ? $this->value : null;
+ }
+
+ public function getState()
+ {
+ return self::FULFILLED;
+ }
+
+ public function resolve($value)
+ {
+ if ($value !== $this->value) {
+ throw new \LogicException("Cannot resolve a fulfilled promise");
+ }
+ }
+
+ public function reject($reason)
+ {
+ throw new \LogicException("Cannot reject a fulfilled promise");
+ }
+
+ public function cancel()
+ {
+ // pass
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/Promise.php b/vendor/guzzlehttp/promises/src/Promise.php
new file mode 100755
index 0000000..844ada0
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/Promise.php
@@ -0,0 +1,280 @@
+waitFn = $waitFn;
+ $this->cancelFn = $cancelFn;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ if ($this->state === self::PENDING) {
+ $p = new Promise(null, [$this, 'cancel']);
+ $this->handlers[] = [$p, $onFulfilled, $onRejected];
+ $p->waitList = $this->waitList;
+ $p->waitList[] = $this;
+ return $p;
+ }
+
+ // Return a fulfilled promise and immediately invoke any callbacks.
+ if ($this->state === self::FULFILLED) {
+ return $onFulfilled
+ ? promise_for($this->result)->then($onFulfilled)
+ : promise_for($this->result);
+ }
+
+ // It's either cancelled or rejected, so return a rejected promise
+ // and immediately invoke any callbacks.
+ $rejection = rejection_for($this->result);
+ return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true)
+ {
+ $this->waitIfPending();
+
+ $inner = $this->result instanceof PromiseInterface
+ ? $this->result->wait($unwrap)
+ : $this->result;
+
+ if ($unwrap) {
+ if ($this->result instanceof PromiseInterface
+ || $this->state === self::FULFILLED
+ ) {
+ return $inner;
+ } else {
+ // It's rejected so "unwrap" and throw an exception.
+ throw exception_for($inner);
+ }
+ }
+ }
+
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ public function cancel()
+ {
+ if ($this->state !== self::PENDING) {
+ return;
+ }
+
+ $this->waitFn = $this->waitList = null;
+
+ if ($this->cancelFn) {
+ $fn = $this->cancelFn;
+ $this->cancelFn = null;
+ try {
+ $fn();
+ } catch (\Throwable $e) {
+ $this->reject($e);
+ } catch (\Exception $e) {
+ $this->reject($e);
+ }
+ }
+
+ // Reject the promise only if it wasn't rejected in a then callback.
+ if ($this->state === self::PENDING) {
+ $this->reject(new CancellationException('Promise has been cancelled'));
+ }
+ }
+
+ public function resolve($value)
+ {
+ $this->settle(self::FULFILLED, $value);
+ }
+
+ public function reject($reason)
+ {
+ $this->settle(self::REJECTED, $reason);
+ }
+
+ private function settle($state, $value)
+ {
+ if ($this->state !== self::PENDING) {
+ // Ignore calls with the same resolution.
+ if ($state === $this->state && $value === $this->result) {
+ return;
+ }
+ throw $this->state === $state
+ ? new \LogicException("The promise is already {$state}.")
+ : new \LogicException("Cannot change a {$this->state} promise to {$state}");
+ }
+
+ if ($value === $this) {
+ throw new \LogicException('Cannot fulfill or reject a promise with itself');
+ }
+
+ // Clear out the state of the promise but stash the handlers.
+ $this->state = $state;
+ $this->result = $value;
+ $handlers = $this->handlers;
+ $this->handlers = null;
+ $this->waitList = $this->waitFn = null;
+ $this->cancelFn = null;
+
+ if (!$handlers) {
+ return;
+ }
+
+ // If the value was not a settled promise or a thenable, then resolve
+ // it in the task queue using the correct ID.
+ if (!method_exists($value, 'then')) {
+ $id = $state === self::FULFILLED ? 1 : 2;
+ // It's a success, so resolve the handlers in the queue.
+ queue()->add(static function () use ($id, $value, $handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler($id, $value, $handler);
+ }
+ });
+ } elseif ($value instanceof Promise
+ && $value->getState() === self::PENDING
+ ) {
+ // We can just merge our handlers onto the next promise.
+ $value->handlers = array_merge($value->handlers, $handlers);
+ } else {
+ // Resolve the handlers when the forwarded promise is resolved.
+ $value->then(
+ static function ($value) use ($handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler(1, $value, $handler);
+ }
+ },
+ static function ($reason) use ($handlers) {
+ foreach ($handlers as $handler) {
+ self::callHandler(2, $reason, $handler);
+ }
+ }
+ );
+ }
+ }
+
+ /**
+ * Call a stack of handlers using a specific callback index and value.
+ *
+ * @param int $index 1 (resolve) or 2 (reject).
+ * @param mixed $value Value to pass to the callback.
+ * @param array $handler Array of handler data (promise and callbacks).
+ *
+ * @return array Returns the next group to resolve.
+ */
+ private static function callHandler($index, $value, array $handler)
+ {
+ /** @var PromiseInterface $promise */
+ $promise = $handler[0];
+
+ // The promise may have been cancelled or resolved before placing
+ // this thunk in the queue.
+ if ($promise->getState() !== self::PENDING) {
+ return;
+ }
+
+ try {
+ if (isset($handler[$index])) {
+ $promise->resolve($handler[$index]($value));
+ } elseif ($index === 1) {
+ // Forward resolution values as-is.
+ $promise->resolve($value);
+ } else {
+ // Forward rejections down the chain.
+ $promise->reject($value);
+ }
+ } catch (\Throwable $reason) {
+ $promise->reject($reason);
+ } catch (\Exception $reason) {
+ $promise->reject($reason);
+ }
+ }
+
+ private function waitIfPending()
+ {
+ if ($this->state !== self::PENDING) {
+ return;
+ } elseif ($this->waitFn) {
+ $this->invokeWaitFn();
+ } elseif ($this->waitList) {
+ $this->invokeWaitList();
+ } else {
+ // If there's not wait function, then reject the promise.
+ $this->reject('Cannot wait on a promise that has '
+ . 'no internal wait function. You must provide a wait '
+ . 'function when constructing the promise to be able to '
+ . 'wait on a promise.');
+ }
+
+ queue()->run();
+
+ if ($this->state === self::PENDING) {
+ $this->reject('Invoking the wait callback did not resolve the promise');
+ }
+ }
+
+ private function invokeWaitFn()
+ {
+ try {
+ $wfn = $this->waitFn;
+ $this->waitFn = null;
+ $wfn(true);
+ } catch (\Exception $reason) {
+ if ($this->state === self::PENDING) {
+ // The promise has not been resolved yet, so reject the promise
+ // with the exception.
+ $this->reject($reason);
+ } else {
+ // The promise was already resolved, so there's a problem in
+ // the application.
+ throw $reason;
+ }
+ }
+ }
+
+ private function invokeWaitList()
+ {
+ $waitList = $this->waitList;
+ $this->waitList = null;
+
+ foreach ($waitList as $result) {
+ while (true) {
+ $result->waitIfPending();
+
+ if ($result->result instanceof Promise) {
+ $result = $result->result;
+ } else {
+ if ($result->result instanceof PromiseInterface) {
+ $result->result->wait(false);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/PromiseInterface.php b/vendor/guzzlehttp/promises/src/PromiseInterface.php
new file mode 100755
index 0000000..8f5f4b9
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/PromiseInterface.php
@@ -0,0 +1,93 @@
+reason = $reason;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ // If there's no onRejected callback then just return self.
+ if (!$onRejected) {
+ return $this;
+ }
+
+ $queue = queue();
+ $reason = $this->reason;
+ $p = new Promise([$queue, 'run']);
+ $queue->add(static function () use ($p, $reason, $onRejected) {
+ if ($p->getState() === self::PENDING) {
+ try {
+ // Return a resolved promise if onRejected does not throw.
+ $p->resolve($onRejected($reason));
+ } catch (\Throwable $e) {
+ // onRejected threw, so return a rejected promise.
+ $p->reject($e);
+ } catch (\Exception $e) {
+ // onRejected threw, so return a rejected promise.
+ $p->reject($e);
+ }
+ }
+ });
+
+ return $p;
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, $onRejected);
+ }
+
+ public function wait($unwrap = true, $defaultDelivery = null)
+ {
+ if ($unwrap) {
+ throw exception_for($this->reason);
+ }
+ }
+
+ public function getState()
+ {
+ return self::REJECTED;
+ }
+
+ public function resolve($value)
+ {
+ throw new \LogicException("Cannot resolve a rejected promise");
+ }
+
+ public function reject($reason)
+ {
+ if ($reason !== $this->reason) {
+ throw new \LogicException("Cannot reject a rejected promise");
+ }
+ }
+
+ public function cancel()
+ {
+ // pass
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/RejectionException.php b/vendor/guzzlehttp/promises/src/RejectionException.php
new file mode 100755
index 0000000..07c1136
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/RejectionException.php
@@ -0,0 +1,47 @@
+reason = $reason;
+
+ $message = 'The promise was rejected';
+
+ if ($description) {
+ $message .= ' with reason: ' . $description;
+ } elseif (is_string($reason)
+ || (is_object($reason) && method_exists($reason, '__toString'))
+ ) {
+ $message .= ' with reason: ' . $this->reason;
+ } elseif ($reason instanceof \JsonSerializable) {
+ $message .= ' with reason: '
+ . json_encode($this->reason, JSON_PRETTY_PRINT);
+ }
+
+ parent::__construct($message);
+ }
+
+ /**
+ * Returns the rejection reason.
+ *
+ * @return mixed
+ */
+ public function getReason()
+ {
+ return $this->reason;
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/TaskQueue.php b/vendor/guzzlehttp/promises/src/TaskQueue.php
new file mode 100755
index 0000000..6e8a2a0
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/TaskQueue.php
@@ -0,0 +1,66 @@
+run();
+ */
+class TaskQueue implements TaskQueueInterface
+{
+ private $enableShutdown = true;
+ private $queue = [];
+
+ public function __construct($withShutdown = true)
+ {
+ if ($withShutdown) {
+ register_shutdown_function(function () {
+ if ($this->enableShutdown) {
+ // Only run the tasks if an E_ERROR didn't occur.
+ $err = error_get_last();
+ if (!$err || ($err['type'] ^ E_ERROR)) {
+ $this->run();
+ }
+ }
+ });
+ }
+ }
+
+ public function isEmpty()
+ {
+ return !$this->queue;
+ }
+
+ public function add(callable $task)
+ {
+ $this->queue[] = $task;
+ }
+
+ public function run()
+ {
+ /** @var callable $task */
+ while ($task = array_shift($this->queue)) {
+ $task();
+ }
+ }
+
+ /**
+ * The task queue will be run and exhausted by default when the process
+ * exits IFF the exit is not the result of a PHP E_ERROR error.
+ *
+ * You can disable running the automatic shutdown of the queue by calling
+ * this function. If you disable the task queue shutdown process, then you
+ * MUST either run the task queue (as a result of running your event loop
+ * or manually using the run() method) or wait on each outstanding promise.
+ *
+ * Note: This shutdown will occur before any destructors are triggered.
+ */
+ public function disableShutdown()
+ {
+ $this->enableShutdown = false;
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php
new file mode 100755
index 0000000..ac8306e
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php
@@ -0,0 +1,25 @@
+
+ * while ($eventLoop->isRunning()) {
+ * GuzzleHttp\Promise\queue()->run();
+ * }
+ *
+ *
+ * @param TaskQueueInterface $assign Optionally specify a new queue instance.
+ *
+ * @return TaskQueueInterface
+ */
+function queue(TaskQueueInterface $assign = null)
+{
+ static $queue;
+
+ if ($assign) {
+ $queue = $assign;
+ } elseif (!$queue) {
+ $queue = new TaskQueue();
+ }
+
+ return $queue;
+}
+
+/**
+ * Adds a function to run in the task queue when it is next `run()` and returns
+ * a promise that is fulfilled or rejected with the result.
+ *
+ * @param callable $task Task function to run.
+ *
+ * @return PromiseInterface
+ */
+function task(callable $task)
+{
+ $queue = queue();
+ $promise = new Promise([$queue, 'run']);
+ $queue->add(function () use ($task, $promise) {
+ try {
+ $promise->resolve($task());
+ } catch (\Throwable $e) {
+ $promise->reject($e);
+ } catch (\Exception $e) {
+ $promise->reject($e);
+ }
+ });
+
+ return $promise;
+}
+
+/**
+ * Creates a promise for a value if the value is not a promise.
+ *
+ * @param mixed $value Promise or value.
+ *
+ * @return PromiseInterface
+ */
+function promise_for($value)
+{
+ if ($value instanceof PromiseInterface) {
+ return $value;
+ }
+
+ // Return a Guzzle promise that shadows the given promise.
+ if (method_exists($value, 'then')) {
+ $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
+ $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
+ $promise = new Promise($wfn, $cfn);
+ $value->then([$promise, 'resolve'], [$promise, 'reject']);
+ return $promise;
+ }
+
+ return new FulfilledPromise($value);
+}
+
+/**
+ * Creates a rejected promise for a reason if the reason is not a promise. If
+ * the provided reason is a promise, then it is returned as-is.
+ *
+ * @param mixed $reason Promise or reason.
+ *
+ * @return PromiseInterface
+ */
+function rejection_for($reason)
+{
+ if ($reason instanceof PromiseInterface) {
+ return $reason;
+ }
+
+ return new RejectedPromise($reason);
+}
+
+/**
+ * Create an exception for a rejected promise value.
+ *
+ * @param mixed $reason
+ *
+ * @return \Exception|\Throwable
+ */
+function exception_for($reason)
+{
+ return $reason instanceof \Exception || $reason instanceof \Throwable
+ ? $reason
+ : new RejectionException($reason);
+}
+
+/**
+ * Returns an iterator for the given value.
+ *
+ * @param mixed $value
+ *
+ * @return \Iterator
+ */
+function iter_for($value)
+{
+ if ($value instanceof \Iterator) {
+ return $value;
+ } elseif (is_array($value)) {
+ return new \ArrayIterator($value);
+ } else {
+ return new \ArrayIterator([$value]);
+ }
+}
+
+/**
+ * Synchronously waits on a promise to resolve and returns an inspection state
+ * array.
+ *
+ * Returns a state associative array containing a "state" key mapping to a
+ * valid promise state. If the state of the promise is "fulfilled", the array
+ * will contain a "value" key mapping to the fulfilled value of the promise. If
+ * the promise is rejected, the array will contain a "reason" key mapping to
+ * the rejection reason of the promise.
+ *
+ * @param PromiseInterface $promise Promise or value.
+ *
+ * @return array
+ */
+function inspect(PromiseInterface $promise)
+{
+ try {
+ return [
+ 'state' => PromiseInterface::FULFILLED,
+ 'value' => $promise->wait()
+ ];
+ } catch (RejectionException $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
+ } catch (\Throwable $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ } catch (\Exception $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ }
+}
+
+/**
+ * Waits on all of the provided promises, but does not unwrap rejected promises
+ * as thrown exception.
+ *
+ * Returns an array of inspection state arrays.
+ *
+ * @param PromiseInterface[] $promises Traversable of promises to wait upon.
+ *
+ * @return array
+ * @see GuzzleHttp\Promise\inspect for the inspection state array format.
+ */
+function inspect_all($promises)
+{
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = inspect($promise);
+ }
+
+ return $results;
+}
+
+/**
+ * Waits on all of the provided promises and returns the fulfilled values.
+ *
+ * Returns an array that contains the value of each promise (in the same order
+ * the promises were provided). An exception is thrown if any of the promises
+ * are rejected.
+ *
+ * @param mixed $promises Iterable of PromiseInterface objects to wait on.
+ *
+ * @return array
+ * @throws \Exception on error
+ * @throws \Throwable on error in PHP >=7
+ */
+function unwrap($promises)
+{
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = $promise->wait();
+ }
+
+ return $results;
+}
+
+/**
+ * Given an array of promises, return a promise that is fulfilled when all the
+ * items in the array are fulfilled.
+ *
+ * The promise's fulfillment value is an array with fulfillment values at
+ * respective positions to the original array. If any promise in the array
+ * rejects, the returned promise is rejected with the rejection reason.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function all($promises)
+{
+ $results = [];
+ return each(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = $value;
+ },
+ function ($reason, $idx, Promise $aggregate) {
+ $aggregate->reject($reason);
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+}
+
+/**
+ * Initiate a competitive race between multiple promises or values (values will
+ * become immediately fulfilled promises).
+ *
+ * When count amount of promises have been fulfilled, the returned promise is
+ * fulfilled with an array that contains the fulfillment values of the winners
+ * in order of resolution.
+ *
+ * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException}
+ * if the number of fulfilled promises is less than the desired $count.
+ *
+ * @param int $count Total number of promises.
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function some($count, $promises)
+{
+ $results = [];
+ $rejections = [];
+
+ return each(
+ $promises,
+ function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
+ if ($p->getState() !== PromiseInterface::PENDING) {
+ return;
+ }
+ $results[$idx] = $value;
+ if (count($results) >= $count) {
+ $p->resolve(null);
+ }
+ },
+ function ($reason) use (&$rejections) {
+ $rejections[] = $reason;
+ }
+ )->then(
+ function () use (&$results, &$rejections, $count) {
+ if (count($results) !== $count) {
+ throw new AggregateException(
+ 'Not enough promises to fulfill count',
+ $rejections
+ );
+ }
+ ksort($results);
+ return array_values($results);
+ }
+ );
+}
+
+/**
+ * Like some(), with 1 as count. However, if the promise fulfills, the
+ * fulfillment value is not an array of 1 but the value directly.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+function any($promises)
+{
+ return some(1, $promises)->then(function ($values) { return $values[0]; });
+}
+
+/**
+ * Returns a promise that is fulfilled when all of the provided promises have
+ * been fulfilled or rejected.
+ *
+ * The returned promise is fulfilled with an array of inspection state arrays.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ * @see GuzzleHttp\Promise\inspect for the inspection state array format.
+ */
+function settle($promises)
+{
+ $results = [];
+
+ return each(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
+ },
+ function ($reason, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+}
+
+/**
+ * Given an iterator that yields promises or values, returns a promise that is
+ * fulfilled with a null value when the iterator has been consumed or the
+ * aggregate promise has been fulfilled or rejected.
+ *
+ * $onFulfilled is a function that accepts the fulfilled value, iterator
+ * index, and the aggregate promise. The callback can invoke any necessary side
+ * effects and choose to resolve or reject the aggregate promise if needed.
+ *
+ * $onRejected is a function that accepts the rejection reason, iterator
+ * index, and the aggregate promise. The callback can invoke any necessary side
+ * effects and choose to resolve or reject the aggregate promise if needed.
+ *
+ * @param mixed $iterable Iterator or array to iterate over.
+ * @param callable $onFulfilled
+ * @param callable $onRejected
+ *
+ * @return PromiseInterface
+ */
+function each(
+ $iterable,
+ callable $onFulfilled = null,
+ callable $onRejected = null
+) {
+ return (new EachPromise($iterable, [
+ 'fulfilled' => $onFulfilled,
+ 'rejected' => $onRejected
+ ]))->promise();
+}
+
+/**
+ * Like each, but only allows a certain number of outstanding promises at any
+ * given time.
+ *
+ * $concurrency may be an integer or a function that accepts the number of
+ * pending promises and returns a numeric concurrency limit value to allow for
+ * dynamic a concurrency size.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ * @param callable $onRejected
+ *
+ * @return PromiseInterface
+ */
+function each_limit(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null,
+ callable $onRejected = null
+) {
+ return (new EachPromise($iterable, [
+ 'fulfilled' => $onFulfilled,
+ 'rejected' => $onRejected,
+ 'concurrency' => $concurrency
+ ]))->promise();
+}
+
+/**
+ * Like each_limit, but ensures that no promise in the given $iterable argument
+ * is rejected. If any promise is rejected, then the aggregate promise is
+ * rejected with the encountered rejection.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ *
+ * @return PromiseInterface
+ */
+function each_limit_all(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null
+) {
+ return each_limit(
+ $iterable,
+ $concurrency,
+ $onFulfilled,
+ function ($reason, $idx, PromiseInterface $aggregate) {
+ $aggregate->reject($reason);
+ }
+ );
+}
+
+/**
+ * Returns true if a promise is fulfilled.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_fulfilled(PromiseInterface $promise)
+{
+ return $promise->getState() === PromiseInterface::FULFILLED;
+}
+
+/**
+ * Returns true if a promise is rejected.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_rejected(PromiseInterface $promise)
+{
+ return $promise->getState() === PromiseInterface::REJECTED;
+}
+
+/**
+ * Returns true if a promise is fulfilled or rejected.
+ *
+ * @param PromiseInterface $promise
+ *
+ * @return bool
+ */
+function is_settled(PromiseInterface $promise)
+{
+ return $promise->getState() !== PromiseInterface::PENDING;
+}
+
+/**
+ * @see Coroutine
+ *
+ * @param callable $generatorFn
+ *
+ * @return PromiseInterface
+ */
+function coroutine(callable $generatorFn)
+{
+ return new Coroutine($generatorFn);
+}
diff --git a/vendor/guzzlehttp/promises/src/functions_include.php b/vendor/guzzlehttp/promises/src/functions_include.php
new file mode 100755
index 0000000..34cd171
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/functions_include.php
@@ -0,0 +1,6 @@
+withPath('foo')->withHost('example.com')` will throw an exception
+ because the path of a URI with an authority must start with a slash "/" or be empty
+ - `(new Uri())->withScheme('http')` will return `'http://localhost'`
+
+### Deprecated
+
+- `Uri::resolve` in favor of `UriResolver::resolve`
+- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`
+
+### Fixed
+
+- `Stream::read` when length parameter <= 0.
+- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
+- `ServerRequest::getUriFromGlobals` when `Host` header contains port.
+- Compatibility of URIs with `file` scheme and empty host.
+
+
+## [1.3.1] - 2016-06-25
+
+### Fixed
+
+- `Uri::__toString` for network path references, e.g. `//example.org`.
+- Missing lowercase normalization for host.
+- Handling of URI components in case they are `'0'` in a lot of places,
+ e.g. as a user info password.
+- `Uri::withAddedHeader` to correctly merge headers with different case.
+- Trimming of header values in `Uri::withAddedHeader`. Header values may
+ be surrounded by whitespace which should be ignored according to RFC 7230
+ Section 3.2.4. This does not apply to header names.
+- `Uri::withAddedHeader` with an array of header values.
+- `Uri::resolve` when base path has no slash and handling of fragment.
+- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the
+ key/value both in encoded as well as decoded form to those methods. This is
+ consistent with withPath, withQuery etc.
+- `ServerRequest::withoutAttribute` when attribute value is null.
+
+
+## [1.3.0] - 2016-04-13
+
+### Added
+
+- Remaining interfaces needed for full PSR7 compatibility
+ (ServerRequestInterface, UploadedFileInterface, etc.).
+- Support for stream_for from scalars.
+
+### Changed
+
+- Can now extend Uri.
+
+### Fixed
+- A bug in validating request methods by making it more permissive.
+
+
+## [1.2.3] - 2016-02-18
+
+### Fixed
+
+- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
+ streams, which can sometimes return fewer bytes than requested with `fread`.
+- Handling of gzipped responses with FNAME headers.
+
+
+## [1.2.2] - 2016-01-22
+
+### Added
+
+- Support for URIs without any authority.
+- Support for HTTP 451 'Unavailable For Legal Reasons.'
+- Support for using '0' as a filename.
+- Support for including non-standard ports in Host headers.
+
+
+## [1.2.1] - 2015-11-02
+
+### Changes
+
+- Now supporting negative offsets when seeking to SEEK_END.
+
+
+## [1.2.0] - 2015-08-15
+
+### Changed
+
+- Body as `"0"` is now properly added to a response.
+- Now allowing forward seeking in CachingStream.
+- Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+- functions.php is now conditionally required.
+- user-info is no longer dropped when resolving URIs.
+
+
+## [1.1.0] - 2015-06-24
+
+### Changed
+
+- URIs can now be relative.
+- `multipart/form-data` headers are now overridden case-insensitively.
+- URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+- A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
+
+
+
+[Unreleased]: https://github.com/guzzle/psr7/compare/1.6.0...HEAD
+[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0
+[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2
+[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1
+[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0
+[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2
+[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1
+[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0
+[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1
+[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0
+[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3
+[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2
+[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1
+[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0
+[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0
diff --git a/vendor/guzzlehttp/psr7/LICENSE b/vendor/guzzlehttp/psr7/LICENSE
new file mode 100755
index 0000000..581d95f
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/guzzlehttp/psr7/README.md b/vendor/guzzlehttp/psr7/README.md
new file mode 100755
index 0000000..c60a6a3
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/README.md
@@ -0,0 +1,745 @@
+# PSR-7 Message Implementation
+
+This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing.
+
+
+[](https://travis-ci.org/guzzle/psr7)
+
+
+# Stream implementation
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## AppendStream
+
+`GuzzleHttp\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use GuzzleHttp\Psr7;
+
+$a = Psr7\stream_for('abc, ');
+$b = Psr7\stream_for('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\stream_for(' Above all listen to me'));
+
+echo $composed; // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`GuzzleHttp\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use GuzzleHttp\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`GuzzleHttp\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use GuzzleHttp\Psr7;
+
+// Create an empty stream
+$stream = Psr7\stream_for();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$dropping->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`GuzzleHttp\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing
+to create a concrete class for a simple extension point.
+
+```php
+
+use GuzzleHttp\Psr7;
+
+$stream = Psr7\stream_for('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`GuzzleHttp\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
+
+This stream decorator skips the first 10 bytes of the given stream to remove
+the gzip header, converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`GuzzleHttp\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use GuzzleHttp\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`GuzzleHttp\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`GuzzleHttp\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`GuzzleHttp\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`GuzzleHttp\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use GuzzleHttp\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ call_user_func($this->callback);
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\stream_for('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use GuzzleHttp\Psr7\StreamWrapper;
+
+$stream = GuzzleHttp\Psr7\stream_for('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Function API
+
+There are various functions available under the `GuzzleHttp\Psr7` namespace.
+
+
+## `function str`
+
+`function str(MessageInterface $message)`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com');
+echo GuzzleHttp\Psr7\str($request);
+```
+
+
+## `function uri_for`
+
+`function uri_for($uri)`
+
+This function accepts a string or `Psr\Http\Message\UriInterface` and returns a
+UriInterface for the given value. If the value is already a `UriInterface`, it
+is returned as-is.
+
+```php
+$uri = GuzzleHttp\Psr7\uri_for('http://example.com');
+assert($uri === GuzzleHttp\Psr7\uri_for($uri));
+```
+
+
+## `function stream_for`
+
+`function stream_for($resource = '', array $options = [])`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+* - metadata: Array of custom metadata.
+* - size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = GuzzleHttp\Psr7\stream_for('foo');
+$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r'));
+
+$generator = function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = GuzzleHttp\Psr7\stream_for($generator(100));
+```
+
+
+## `function parse_header`
+
+`function parse_header($header)`
+
+Parse an array of header values containing ";" separated data into an array of
+associative arrays representing the header key value pair data of the header.
+When a parameter does not contain a value, but just contains a key, this
+function will inject a key with a '' string value.
+
+
+## `function normalize_header`
+
+`function normalize_header($header)`
+
+Converts an array of header values that may contain comma separated headers
+into an array of headers with no comma separated values.
+
+
+## `function modify_request`
+
+`function modify_request(RequestInterface $request, array $changes)`
+
+Clone and modify a request with the given changes. This method is useful for
+reducing the number of clones needed to mutate a message.
+
+The changes can be one of:
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `function rewind_body`
+
+`function rewind_body(MessageInterface $message)`
+
+Attempts to rewind a message body and throws an exception on failure. The body
+of the message will only be rewound if a call to `tell()` returns a value other
+than `0`.
+
+
+## `function try_fopen`
+
+`function try_fopen($filename, $mode)`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an error
+handler that checks for errors and throws an exception instead.
+
+
+## `function copy_to_string`
+
+`function copy_to_string(StreamInterface $stream, $maxLen = -1)`
+
+Copy the contents of a stream into a string until the given number of bytes
+have been read.
+
+
+## `function copy_to_stream`
+
+`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)`
+
+Copy the contents of a stream into another stream until the given number of
+bytes have been read.
+
+
+## `function hash`
+
+`function hash(StreamInterface $stream, $algo, $rawOutput = false)`
+
+Calculate a hash of a Stream. This method reads the entire stream to calculate
+a rolling hash (based on PHP's hash_init functions).
+
+
+## `function readline`
+
+`function readline(StreamInterface $stream, $maxLength = null)`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `function parse_request`
+
+`function parse_request($message)`
+
+Parses a request message string into a request object.
+
+
+## `function parse_response`
+
+`function parse_response($message)`
+
+Parses a response message string into a response object.
+
+
+## `function parse_query`
+
+`function parse_query($str, $urlEncoding = true)`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key value pair
+will become an array. This function does not parse nested PHP style arrays into
+an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
+`['foo[a]' => '1', 'foo[b]' => '2']`).
+
+
+## `function build_query`
+
+`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of parse_query() to build a query string.
+This function does not modify the provided keys when an array is encountered
+(like http_build_query would).
+
+
+## `function mimetype_from_filename`
+
+`function mimetype_from_filename($filename)`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `function mimetype_from_extension`
+
+`function mimetype_from_extension($extension)`
+
+Maps a file extensions to a mimetype.
+
+
+# Additional URI Methods
+
+Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
+this library also provides additional functionality when working with URIs as static methods.
+
+## URI Types
+
+An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
+An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
+the base URI. Relative references can be divided into several forms according to
+[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2):
+
+- network-path references, e.g. `//example.com/path`
+- absolute-path references, e.g. `/path`
+- relative-path references, e.g. `subpath`
+
+The following methods can be used to identify the type of the URI.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolute`
+
+`public static function isAbsolute(UriInterface $uri): bool`
+
+Whether the URI is absolute, i.e. it has a scheme.
+
+### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
+
+`public static function isNetworkPathReference(UriInterface $uri): bool`
+
+Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
+termed an network-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
+
+`public static function isAbsolutePathReference(UriInterface $uri): bool`
+
+Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
+termed an absolute-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
+
+`public static function isRelativePathReference(UriInterface $uri): bool`
+
+Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
+termed a relative-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
+
+`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`
+
+Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
+fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
+(apart from its fragment) is considered a same-document reference.
+
+## URI Components
+
+Additional methods to work with URI components.
+
+### `GuzzleHttp\Psr7\Uri::isDefaultPort`
+
+`public static function isDefaultPort(UriInterface $uri): bool`
+
+Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
+or the standard port. This method can be used independently of the implementation.
+
+### `GuzzleHttp\Psr7\Uri::composeComponents`
+
+`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
+
+Composes a URI reference string from its various components according to
+[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called
+manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
+
+### `GuzzleHttp\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts): UriInterface`
+
+Creates a URI from a hash of [`parse_url`](http://php.net/manual/en/function.parse-url.php) components.
+
+
+### `GuzzleHttp\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
+
+Creates a new URI with a specific query string value. Any existing query string values that exactly match the
+provided key are removed and replaced with the given key value pair. A value of null will set the query string
+key without a value, e.g. "key" instead of "key=value".
+
+### `GuzzleHttp\Psr7\Uri::withQueryValues`
+
+`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface`
+
+Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an
+associative array of key => value.
+
+### `GuzzleHttp\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
+
+Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
+provided key are removed.
+
+## Reference Resolution
+
+`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
+to [RFC 3986 Section 5](https://tools.ietf.org/html/rfc3986#section-5). This is for example also what web browsers
+do when resolving a link in a website based on the current request URI.
+
+### `GuzzleHttp\Psr7\UriResolver::resolve`
+
+`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
+
+Converts the relative URI into a new URI that is resolved against the base URI.
+
+### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
+
+`public static function removeDotSegments(string $path): string`
+
+Removes dot segments from a path and returns the new path according to
+[RFC 3986 Section 5.2.4](https://tools.ietf.org/html/rfc3986#section-5.2.4).
+
+### `GuzzleHttp\Psr7\UriResolver::relativize`
+
+`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
+
+Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
+
+```php
+(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+```
+
+One use-case is to use the current request URI as base URI and then generate relative links in your documents
+to reduce the document size or offer self-contained downloadable document archives.
+
+```php
+$base = new Uri('http://example.com/a/b/');
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+```
+
+## Normalization and Comparison
+
+`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
+[RFC 3986 Section 6](https://tools.ietf.org/html/rfc3986#section-6).
+
+### `GuzzleHttp\Psr7\UriNormalizer::normalize`
+
+`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
+
+Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
+This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
+of normalizations to apply. The following normalizations are available:
+
+- `UriNormalizer::PRESERVING_NORMALIZATIONS`
+
+ Default normalizations which only include the ones that preserve semantics.
+
+- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
+
+ All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
+
+ Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b`
+
+- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
+
+ Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
+ ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
+ not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
+ characters by URI normalizers.
+
+ Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/`
+
+- `UriNormalizer::CONVERT_EMPTY_PATH`
+
+ Converts the empty path to "/" for http and https URIs.
+
+ Example: `http://example.org` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DEFAULT_HOST`
+
+ Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
+ "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
+ RFC 3986.
+
+ Example: `file://localhost/myfile` → `file:///myfile`
+
+- `UriNormalizer::REMOVE_DEFAULT_PORT`
+
+ Removes the default port of the given URI scheme from the URI.
+
+ Example: `http://example.org:80/` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DOT_SEGMENTS`
+
+ Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
+ change the semantics of the URI reference.
+
+ Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html`
+
+- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
+
+ Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
+ and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
+ may change the semantics. Encoded slashes (%2F) are not removed.
+
+ Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html`
+
+- `UriNormalizer::SORT_QUERY_PARAMETERS`
+
+ Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
+ significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
+ of the URI.
+
+ Example: `?lang=en&article=fred` → `?article=fred&lang=en`
+
+### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
+
+`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
+
+Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
+`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
+This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
+equivalence or difference of relative references does not mean anything.
diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json
new file mode 100755
index 0000000..168a055
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/composer.json
@@ -0,0 +1,49 @@
+{
+ "name": "guzzlehttp/psr7",
+ "type": "library",
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": ["request", "response", "message", "stream", "http", "uri", "url", "psr-7"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8",
+ "ext-zlib": "*"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "suggest": {
+ "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\Psr7\\": "tests/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6-dev"
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php
new file mode 100755
index 0000000..472a0d6
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/AppendStream.php
@@ -0,0 +1,241 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream)
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ *
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = [];
+ }
+
+ /**
+ * Detaches each attached stream.
+ *
+ * Returns null as it's not clear which underlying stream resource to return.
+ *
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->detach();
+ }
+
+ $this->streams = [];
+ }
+
+ public function tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ *
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof()
+ {
+ return !$this->streams ||
+ ($this->current >= count($this->streams) - 1 &&
+ $this->streams[$this->current]->eof());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ *
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ . $i . ' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ *
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ $this->current++;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ // Using a loose comparison here to match on '', false, and null
+ if ($result == null) {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key ? null : [];
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/BufferStream.php b/vendor/guzzlehttp/psr7/src/BufferStream.php
new file mode 100755
index 0000000..af4d4c2
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/BufferStream.php
@@ -0,0 +1,137 @@
+hwm = $hwm;
+ }
+
+ public function __toString()
+ {
+ return $this->getContents();
+ }
+
+ public function getContents()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+ }
+
+ public function getSize()
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return true;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof()
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell()
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length)
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string)
+ {
+ $this->buffer .= $string;
+
+ // TODO: What should happen here?
+ if (strlen($this->buffer) >= $this->hwm) {
+ return false;
+ }
+
+ return strlen($string);
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key == 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : [];
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/CachingStream.php b/vendor/guzzlehttp/psr7/src/CachingStream.php
new file mode 100755
index 0000000..ed68f08
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/CachingStream.php
@@ -0,0 +1,138 @@
+remoteStream = $stream;
+ $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
+ }
+
+ public function getSize()
+ {
+ return max($this->stream->getSize(), $this->remoteStream->getSize());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence == SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence == SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence == SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ $byte = $size + $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // Read the remoteStream until we have read in at least the amount
+ // of bytes requested, or we reach the end of the file.
+ while ($diff > 0 && !$this->remoteStream->eof()) {
+ $this->read($diff);
+ $diff = $byte - $this->stream->getSize();
+ }
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string)
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ $this->remoteStream->close() && $this->stream->close();
+ }
+
+ private function cacheEntireStream()
+ {
+ $target = new FnStream(['write' => 'strlen']);
+ copy_to_stream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/DroppingStream.php b/vendor/guzzlehttp/psr7/src/DroppingStream.php
new file mode 100755
index 0000000..8935c80
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php
@@ -0,0 +1,42 @@
+stream = $stream;
+ $this->maxLength = $maxLength;
+ }
+
+ public function write($string)
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/FnStream.php b/vendor/guzzlehttp/psr7/src/FnStream.php
new file mode 100755
index 0000000..73daea6
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/FnStream.php
@@ -0,0 +1,158 @@
+methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_' . $name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ * @throws \BadMethodCallException
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ . '() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ call_user_func($this->_fn_close);
+ }
+ }
+
+ /**
+ * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
+ * @throws \LogicException
+ */
+ public function __wakeup()
+ {
+ throw new \LogicException('FnStream should never be unserialized');
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
+ $methods[$diff] = [$stream, $diff];
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString()
+ {
+ return call_user_func($this->_fn___toString);
+ }
+
+ public function close()
+ {
+ return call_user_func($this->_fn_close);
+ }
+
+ public function detach()
+ {
+ return call_user_func($this->_fn_detach);
+ }
+
+ public function getSize()
+ {
+ return call_user_func($this->_fn_getSize);
+ }
+
+ public function tell()
+ {
+ return call_user_func($this->_fn_tell);
+ }
+
+ public function eof()
+ {
+ return call_user_func($this->_fn_eof);
+ }
+
+ public function isSeekable()
+ {
+ return call_user_func($this->_fn_isSeekable);
+ }
+
+ public function rewind()
+ {
+ call_user_func($this->_fn_rewind);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ call_user_func($this->_fn_seek, $offset, $whence);
+ }
+
+ public function isWritable()
+ {
+ return call_user_func($this->_fn_isWritable);
+ }
+
+ public function write($string)
+ {
+ return call_user_func($this->_fn_write, $string);
+ }
+
+ public function isReadable()
+ {
+ return call_user_func($this->_fn_isReadable);
+ }
+
+ public function read($length)
+ {
+ return call_user_func($this->_fn_read, $length);
+ }
+
+ public function getContents()
+ {
+ return call_user_func($this->_fn_getContents);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return call_user_func($this->_fn_getMetadata, $key);
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php
new file mode 100755
index 0000000..5e4f602
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/InflateStream.php
@@ -0,0 +1,52 @@
+read(10);
+ $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header);
+ // Skip the header, that is 10 + length of filename + 1 (nil) bytes
+ $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
+ $resource = StreamWrapper::getResource($stream);
+ stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
+ $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
+ }
+
+ /**
+ * @param StreamInterface $stream
+ * @param $header
+ * @return int
+ */
+ private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
+ {
+ $filename_header_length = 0;
+
+ if (substr(bin2hex($header), 6, 2) === '08') {
+ // we have a filename, read until nil
+ $filename_header_length = 1;
+ while ($stream->read(1) !== chr(0)) {
+ $filename_header_length++;
+ }
+ }
+
+ return $filename_header_length;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
new file mode 100755
index 0000000..02cec3a
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
@@ -0,0 +1,39 @@
+filename = $filename;
+ $this->mode = $mode;
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream()
+ {
+ return stream_for(try_fopen($this->filename, $this->mode));
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/LimitStream.php b/vendor/guzzlehttp/psr7/src/LimitStream.php
new file mode 100755
index 0000000..e4f239e
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/LimitStream.php
@@ -0,0 +1,155 @@
+stream = $stream;
+ $this->setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof()
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit == -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit == -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset %s with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset($offset)
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length)
+ {
+ if ($this->limit == -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php
new file mode 100755
index 0000000..a7966d1
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php
@@ -0,0 +1,213 @@
+ array of values */
+ private $headers = [];
+
+ /** @var array Map of lowercase header name => original name at registration */
+ private $headerNames = [];
+
+ /** @var string */
+ private $protocol = '1.1';
+
+ /** @var StreamInterface */
+ private $stream;
+
+ public function getProtocolVersion()
+ {
+ return $this->protocol;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+ return $new;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ public function hasHeader($header)
+ {
+ return isset($this->headerNames[strtolower($header)]);
+ }
+
+ public function getHeader($header)
+ {
+ $header = strtolower($header);
+
+ if (!isset($this->headerNames[$header])) {
+ return [];
+ }
+
+ $header = $this->headerNames[$header];
+
+ return $this->headers[$header];
+ }
+
+ public function getHeaderLine($header)
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value)
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ unset($new->headers[$new->headerNames[$normalized]]);
+ }
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value)
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $new->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+ }
+
+ return $new;
+ }
+
+ public function withoutHeader($header)
+ {
+ $normalized = strtolower($header);
+
+ if (!isset($this->headerNames[$normalized])) {
+ return $this;
+ }
+
+ $header = $this->headerNames[$normalized];
+
+ $new = clone $this;
+ unset($new->headers[$header], $new->headerNames[$normalized]);
+
+ return $new;
+ }
+
+ public function getBody()
+ {
+ if (!$this->stream) {
+ $this->stream = stream_for('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body)
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+ return $new;
+ }
+
+ private function setHeaders(array $headers)
+ {
+ $this->headerNames = $this->headers = [];
+ foreach ($headers as $header => $value) {
+ if (is_int($header)) {
+ // Numeric array keys are converted to int by PHP but having a header name '123' is not forbidden by the spec
+ // and also allowed in withHeader(). So we need to cast it to string again for the following assertion to pass.
+ $header = (string) $header;
+ }
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+ if (isset($this->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $this->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $this->headerNames[$normalized] = $header;
+ $this->headers[$header] = $value;
+ }
+ }
+ }
+
+ private function normalizeHeaderValue($value)
+ {
+ if (!is_array($value)) {
+ return $this->trimHeaderValues([$value]);
+ }
+
+ if (count($value) === 0) {
+ throw new \InvalidArgumentException('Header value can not be an empty array.');
+ }
+
+ return $this->trimHeaderValues($value);
+ }
+
+ /**
+ * Trims whitespace from the header values.
+ *
+ * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
+ *
+ * header-field = field-name ":" OWS field-value OWS
+ * OWS = *( SP / HTAB )
+ *
+ * @param string[] $values Header values
+ *
+ * @return string[] Trimmed header values
+ *
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ */
+ private function trimHeaderValues(array $values)
+ {
+ return array_map(function ($value) {
+ if (!is_scalar($value) && null !== $value) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header value must be scalar or null but %s provided.',
+ is_object($value) ? get_class($value) : gettype($value)
+ ));
+ }
+
+ return trim((string) $value, " \t");
+ }, $values);
+ }
+
+ private function assertHeader($header)
+ {
+ if (!is_string($header)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header name must be a string but %s provided.',
+ is_object($header) ? get_class($header) : gettype($header)
+ ));
+ }
+
+ if ($header === '') {
+ throw new \InvalidArgumentException('Header name can not be empty.');
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php
new file mode 100755
index 0000000..c0fd584
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php
@@ -0,0 +1,153 @@
+boundary = $boundary ?: sha1(uniqid('', true));
+ $this->stream = $this->createStream($elements);
+ }
+
+ /**
+ * Get the boundary
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ */
+ private function getHeaders(array $headers)
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements)
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(stream_for("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element)
+ {
+ foreach (['contents', 'name'] as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = stream_for($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if (substr($uri, 0, 6) !== 'php://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ list($body, $headers) = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ isset($element['filename']) ? $element['filename'] : null,
+ isset($element['headers']) ? $element['headers'] : []
+ );
+
+ $stream->addStream(stream_for($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(stream_for("\r\n"));
+ }
+
+ /**
+ * @return array
+ */
+ private function createElement($name, StreamInterface $stream, $filename, array $headers)
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = $this->getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = ($filename === '0' || $filename)
+ ? sprintf('form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename))
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = $this->getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = $this->getHeader($headers, 'content-type');
+ if (!$type && ($filename === '0' || $filename)) {
+ if ($type = mimetype_from_filename($filename)) {
+ $headers['Content-Type'] = $type;
+ }
+ }
+
+ return [$stream, $headers];
+ }
+
+ private function getHeader(array $headers, $key)
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower($k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/vendor/guzzlehttp/psr7/src/NoSeekStream.php
new file mode 100755
index 0000000..2332218
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php
@@ -0,0 +1,22 @@
+source = $source;
+ $this->size = isset($options['size']) ? $options['size'] : null;
+ $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString()
+ {
+ try {
+ return copy_to_string($this);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function close()
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = false;
+ $this->source = null;
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ return $this->tellPos;
+ }
+
+ public function eof()
+ {
+ return !$this->source;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents()
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
+ }
+
+ private function pump($length)
+ {
+ if ($this->source) {
+ do {
+ $data = call_user_func($this->source, $length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Request.php b/vendor/guzzlehttp/psr7/src/Request.php
new file mode 100755
index 0000000..59f337d
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Request.php
@@ -0,0 +1,151 @@
+assertMethod($method);
+ if (!($uri instanceof UriInterface)) {
+ $uri = new Uri($uri);
+ }
+
+ $this->method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $version;
+
+ if (!isset($this->headerNames['host'])) {
+ $this->updateHostFromUri();
+ }
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+ }
+
+ public function getRequestTarget()
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target == '') {
+ $target = '/';
+ }
+ if ($this->uri->getQuery() != '') {
+ $target .= '?' . $this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget)
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+ return $new;
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method)
+ {
+ $this->assertMethod($method);
+ $new = clone $this;
+ $new->method = strtoupper($method);
+ return $new;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false)
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost || !isset($this->headerNames['host'])) {
+ $new->updateHostFromUri();
+ }
+
+ return $new;
+ }
+
+ private function updateHostFromUri()
+ {
+ $host = $this->uri->getHost();
+
+ if ($host == '') {
+ return;
+ }
+
+ if (($port = $this->uri->getPort()) !== null) {
+ $host .= ':' . $port;
+ }
+
+ if (isset($this->headerNames['host'])) {
+ $header = $this->headerNames['host'];
+ } else {
+ $header = 'Host';
+ $this->headerNames['host'] = 'Host';
+ }
+ // Ensure Host is the first header.
+ // See: http://tools.ietf.org/html/rfc7230#section-5.4
+ $this->headers = [$header => [$host]] + $this->headers;
+ }
+
+ private function assertMethod($method)
+ {
+ if (!is_string($method) || $method === '') {
+ throw new \InvalidArgumentException('Method must be a non-empty string.');
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Response.php b/vendor/guzzlehttp/psr7/src/Response.php
new file mode 100755
index 0000000..e7e04d8
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Response.php
@@ -0,0 +1,154 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 511 => 'Network Authentication Required',
+ ];
+
+ /** @var string */
+ private $reasonPhrase = '';
+
+ /** @var int */
+ private $statusCode = 200;
+
+ /**
+ * @param int $status Status code
+ * @param array $headers Response headers
+ * @param string|null|resource|StreamInterface $body Response body
+ * @param string $version Protocol version
+ * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
+ */
+ public function __construct(
+ $status = 200,
+ array $headers = [],
+ $body = null,
+ $version = '1.1',
+ $reason = null
+ ) {
+ $this->assertStatusCodeIsInteger($status);
+ $status = (int) $status;
+ $this->assertStatusCodeRange($status);
+
+ $this->statusCode = $status;
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = stream_for($body);
+ }
+
+ $this->setHeaders($headers);
+ if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
+ $this->reasonPhrase = self::$phrases[$this->statusCode];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ $this->assertStatusCodeIsInteger($code);
+ $code = (int) $code;
+ $this->assertStatusCodeRange($code);
+
+ $new = clone $this;
+ $new->statusCode = $code;
+ if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
+ $reasonPhrase = self::$phrases[$new->statusCode];
+ }
+ $new->reasonPhrase = $reasonPhrase;
+ return $new;
+ }
+
+ private function assertStatusCodeIsInteger($statusCode)
+ {
+ if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) {
+ throw new \InvalidArgumentException('Status code must be an integer value.');
+ }
+ }
+
+ private function assertStatusCodeRange($statusCode)
+ {
+ if ($statusCode < 100 || $statusCode >= 600) {
+ throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.');
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Rfc7230.php b/vendor/guzzlehttp/psr7/src/Rfc7230.php
new file mode 100755
index 0000000..505e474
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php
@@ -0,0 +1,18 @@
+@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
+ const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
+}
diff --git a/vendor/guzzlehttp/psr7/src/ServerRequest.php b/vendor/guzzlehttp/psr7/src/ServerRequest.php
new file mode 100755
index 0000000..1a09a6c
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php
@@ -0,0 +1,376 @@
+serverParams = $serverParams;
+
+ parent::__construct($method, $uri, $headers, $body, $version);
+ }
+
+ /**
+ * Return an UploadedFile instance array.
+ *
+ * @param array $files A array which respect $_FILES structure
+ * @throws InvalidArgumentException for unrecognized values
+ * @return array
+ */
+ public static function normalizeFiles(array $files)
+ {
+ $normalized = [];
+
+ foreach ($files as $key => $value) {
+ if ($value instanceof UploadedFileInterface) {
+ $normalized[$key] = $value;
+ } elseif (is_array($value) && isset($value['tmp_name'])) {
+ $normalized[$key] = self::createUploadedFileFromSpec($value);
+ } elseif (is_array($value)) {
+ $normalized[$key] = self::normalizeFiles($value);
+ continue;
+ } else {
+ throw new InvalidArgumentException('Invalid value in files specification');
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Create and return an UploadedFile instance from a $_FILES specification.
+ *
+ * If the specification represents an array of values, this method will
+ * delegate to normalizeNestedFileSpec() and return that return value.
+ *
+ * @param array $value $_FILES struct
+ * @return array|UploadedFileInterface
+ */
+ private static function createUploadedFileFromSpec(array $value)
+ {
+ if (is_array($value['tmp_name'])) {
+ return self::normalizeNestedFileSpec($value);
+ }
+
+ return new UploadedFile(
+ $value['tmp_name'],
+ (int) $value['size'],
+ (int) $value['error'],
+ $value['name'],
+ $value['type']
+ );
+ }
+
+ /**
+ * Normalize an array of file specifications.
+ *
+ * Loops through all nested files and returns a normalized array of
+ * UploadedFileInterface instances.
+ *
+ * @param array $files
+ * @return UploadedFileInterface[]
+ */
+ private static function normalizeNestedFileSpec(array $files = [])
+ {
+ $normalizedFiles = [];
+
+ foreach (array_keys($files['tmp_name']) as $key) {
+ $spec = [
+ 'tmp_name' => $files['tmp_name'][$key],
+ 'size' => $files['size'][$key],
+ 'error' => $files['error'][$key],
+ 'name' => $files['name'][$key],
+ 'type' => $files['type'][$key],
+ ];
+ $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
+ }
+
+ return $normalizedFiles;
+ }
+
+ /**
+ * Return a ServerRequest populated with superglobals:
+ * $_GET
+ * $_POST
+ * $_COOKIE
+ * $_FILES
+ * $_SERVER
+ *
+ * @return ServerRequestInterface
+ */
+ public static function fromGlobals()
+ {
+ $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
+ $headers = getallheaders();
+ $uri = self::getUriFromGlobals();
+ $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+ $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
+
+ $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
+
+ return $serverRequest
+ ->withCookieParams($_COOKIE)
+ ->withQueryParams($_GET)
+ ->withParsedBody($_POST)
+ ->withUploadedFiles(self::normalizeFiles($_FILES));
+ }
+
+ private static function extractHostAndPortFromAuthority($authority)
+ {
+ $uri = 'http://'.$authority;
+ $parts = parse_url($uri);
+ if (false === $parts) {
+ return [null, null];
+ }
+
+ $host = isset($parts['host']) ? $parts['host'] : null;
+ $port = isset($parts['port']) ? $parts['port'] : null;
+
+ return [$host, $port];
+ }
+
+ /**
+ * Get a Uri populated with values from $_SERVER.
+ *
+ * @return UriInterface
+ */
+ public static function getUriFromGlobals()
+ {
+ $uri = new Uri('');
+
+ $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
+
+ $hasPort = false;
+ if (isset($_SERVER['HTTP_HOST'])) {
+ list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
+ if ($host !== null) {
+ $uri = $uri->withHost($host);
+ }
+
+ if ($port !== null) {
+ $hasPort = true;
+ $uri = $uri->withPort($port);
+ }
+ } elseif (isset($_SERVER['SERVER_NAME'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_NAME']);
+ } elseif (isset($_SERVER['SERVER_ADDR'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
+ }
+
+ if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
+ $uri = $uri->withPort($_SERVER['SERVER_PORT']);
+ }
+
+ $hasQuery = false;
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
+ $uri = $uri->withPath($requestUriParts[0]);
+ if (isset($requestUriParts[1])) {
+ $hasQuery = true;
+ $uri = $uri->withQuery($requestUriParts[1]);
+ }
+ }
+
+ if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
+ $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
+ }
+
+ return $uri;
+ }
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getServerParams()
+ {
+ return $this->serverParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getUploadedFiles()
+ {
+ return $this->uploadedFiles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->uploadedFiles = $uploadedFiles;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCookieParams()
+ {
+ return $this->cookieParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->cookieParams = $cookies;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttribute($attribute, $default = null)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $default;
+ }
+
+ return $this->attributes[$attribute];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withAttribute($attribute, $value)
+ {
+ $new = clone $this;
+ $new->attributes[$attribute] = $value;
+
+ return $new;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withoutAttribute($attribute)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ unset($new->attributes[$attribute]);
+
+ return $new;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Stream.php b/vendor/guzzlehttp/psr7/src/Stream.php
new file mode 100755
index 0000000..d9e7409
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Stream.php
@@ -0,0 +1,267 @@
+size = $options['size'];
+ }
+
+ $this->customMetadata = isset($options['metadata'])
+ ? $options['metadata']
+ : [];
+
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']);
+ $this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return (string) stream_get_contents($this->stream);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $contents = stream_get_contents($this->stream);
+
+ if ($contents === false) {
+ throw new \RuntimeException('Unable to read stream contents');
+ }
+
+ return $contents;
+ }
+
+ public function close()
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function eof()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ return feof($this->stream);
+ }
+
+ public function tell()
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $whence = (int) $whence;
+
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ }
+ if (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ . $offset . ' with whence ' . var_export($whence, true));
+ }
+ }
+
+ public function read($length)
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+ if ($length < 0) {
+ throw new \RuntimeException('Length parameter cannot be negative');
+ }
+
+ if (0 === $length) {
+ return '';
+ }
+
+ $string = fread($this->stream, $length);
+ if (false === $string) {
+ throw new \RuntimeException('Unable to read from stream');
+ }
+
+ return $string;
+ }
+
+ public function write($string)
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : [];
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
new file mode 100755
index 0000000..daec6f5
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,149 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @param string $name Name of the property (allows "stream" only).
+ *
+ * @return StreamInterface
+ */
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ $this->stream = $this->createStream();
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString()
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+ return $this->getContents();
+ } catch (\Exception $e) {
+ // Really, PHP? https://bugs.php.net/bug.php?id=53648
+ trigger_error('StreamDecorator::__toString exception: '
+ . (string) $e, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ $result = call_user_func_array([$this->stream, $method], $args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close()
+ {
+ $this->stream->close();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize()
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable()
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->stream->write($string);
+ }
+
+ /**
+ * Implement in subclasses to dynamically create streams when requested.
+ *
+ * @return StreamInterface
+ * @throws \BadMethodCallException
+ */
+ protected function createStream()
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/vendor/guzzlehttp/psr7/src/StreamWrapper.php
new file mode 100755
index 0000000..0f3a285
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php
@@ -0,0 +1,161 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ . 'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, null, self::createStreamContext($stream));
+ }
+
+ /**
+ * Creates a stream context that can be used to open a stream as a php stream resource.
+ *
+ * @param StreamInterface $stream
+ *
+ * @return resource
+ */
+ public static function createStreamContext(StreamInterface $stream)
+ {
+ return stream_context_create([
+ 'guzzle' => ['stream' => $stream]
+ ]);
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register()
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read($count)
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write($data)
+ {
+ return (int) $this->stream->write($data);
+ }
+
+ public function stream_tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ public function stream_cast($cast_as)
+ {
+ $stream = clone($this->stream);
+
+ return $stream->detach();
+ }
+
+ public function stream_stat()
+ {
+ static $modeMap = [
+ 'r' => 33060,
+ 'rb' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188,
+ 'wb' => 33188
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+
+ public function url_stat($path, $flags)
+ {
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UploadedFile.php b/vendor/guzzlehttp/psr7/src/UploadedFile.php
new file mode 100755
index 0000000..e62bd5c
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php
@@ -0,0 +1,316 @@
+setError($errorStatus);
+ $this->setSize($size);
+ $this->setClientFilename($clientFilename);
+ $this->setClientMediaType($clientMediaType);
+
+ if ($this->isOk()) {
+ $this->setStreamOrFile($streamOrFile);
+ }
+ }
+
+ /**
+ * Depending on the value set file or stream variable
+ *
+ * @param mixed $streamOrFile
+ * @throws InvalidArgumentException
+ */
+ private function setStreamOrFile($streamOrFile)
+ {
+ if (is_string($streamOrFile)) {
+ $this->file = $streamOrFile;
+ } elseif (is_resource($streamOrFile)) {
+ $this->stream = new Stream($streamOrFile);
+ } elseif ($streamOrFile instanceof StreamInterface) {
+ $this->stream = $streamOrFile;
+ } else {
+ throw new InvalidArgumentException(
+ 'Invalid stream or file provided for UploadedFile'
+ );
+ }
+ }
+
+ /**
+ * @param int $error
+ * @throws InvalidArgumentException
+ */
+ private function setError($error)
+ {
+ if (false === is_int($error)) {
+ throw new InvalidArgumentException(
+ 'Upload file error status must be an integer'
+ );
+ }
+
+ if (false === in_array($error, UploadedFile::$errors)) {
+ throw new InvalidArgumentException(
+ 'Invalid error status for UploadedFile'
+ );
+ }
+
+ $this->error = $error;
+ }
+
+ /**
+ * @param int $size
+ * @throws InvalidArgumentException
+ */
+ private function setSize($size)
+ {
+ if (false === is_int($size)) {
+ throw new InvalidArgumentException(
+ 'Upload file size must be an integer'
+ );
+ }
+
+ $this->size = $size;
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringOrNull($param)
+ {
+ return in_array(gettype($param), ['string', 'NULL']);
+ }
+
+ /**
+ * @param mixed $param
+ * @return boolean
+ */
+ private function isStringNotEmpty($param)
+ {
+ return is_string($param) && false === empty($param);
+ }
+
+ /**
+ * @param string|null $clientFilename
+ * @throws InvalidArgumentException
+ */
+ private function setClientFilename($clientFilename)
+ {
+ if (false === $this->isStringOrNull($clientFilename)) {
+ throw new InvalidArgumentException(
+ 'Upload file client filename must be a string or null'
+ );
+ }
+
+ $this->clientFilename = $clientFilename;
+ }
+
+ /**
+ * @param string|null $clientMediaType
+ * @throws InvalidArgumentException
+ */
+ private function setClientMediaType($clientMediaType)
+ {
+ if (false === $this->isStringOrNull($clientMediaType)) {
+ throw new InvalidArgumentException(
+ 'Upload file client media type must be a string or null'
+ );
+ }
+
+ $this->clientMediaType = $clientMediaType;
+ }
+
+ /**
+ * Return true if there is no upload error
+ *
+ * @return boolean
+ */
+ private function isOk()
+ {
+ return $this->error === UPLOAD_ERR_OK;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isMoved()
+ {
+ return $this->moved;
+ }
+
+ /**
+ * @throws RuntimeException if is moved or not ok
+ */
+ private function validateActive()
+ {
+ if (false === $this->isOk()) {
+ throw new RuntimeException('Cannot retrieve stream due to upload error');
+ }
+
+ if ($this->isMoved()) {
+ throw new RuntimeException('Cannot retrieve stream after it has already been moved');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws RuntimeException if the upload was not successful.
+ */
+ public function getStream()
+ {
+ $this->validateActive();
+
+ if ($this->stream instanceof StreamInterface) {
+ return $this->stream;
+ }
+
+ return new LazyOpenStream($this->file, 'r+');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/is_uploaded_file
+ * @see http://php.net/move_uploaded_file
+ * @param string $targetPath Path to which to move the uploaded file.
+ * @throws RuntimeException if the upload was not successful.
+ * @throws InvalidArgumentException if the $path specified is invalid.
+ * @throws RuntimeException on any error during the move operation, or on
+ * the second or subsequent call to the method.
+ */
+ public function moveTo($targetPath)
+ {
+ $this->validateActive();
+
+ if (false === $this->isStringNotEmpty($targetPath)) {
+ throw new InvalidArgumentException(
+ 'Invalid path provided for move operation; must be a non-empty string'
+ );
+ }
+
+ if ($this->file) {
+ $this->moved = php_sapi_name() == 'cli'
+ ? rename($this->file, $targetPath)
+ : move_uploaded_file($this->file, $targetPath);
+ } else {
+ copy_to_stream(
+ $this->getStream(),
+ new LazyOpenStream($targetPath, 'w')
+ );
+
+ $this->moved = true;
+ }
+
+ if (false === $this->moved) {
+ throw new RuntimeException(
+ sprintf('Uploaded file could not be moved to %s', $targetPath)
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return int|null The file size in bytes or null if unknown.
+ */
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see http://php.net/manual/en/features.file-upload.errors.php
+ * @return int One of PHP's UPLOAD_ERR_XXX constants.
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return string|null The filename sent by the client or null if none
+ * was provided.
+ */
+ public function getClientFilename()
+ {
+ return $this->clientFilename;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getClientMediaType()
+ {
+ return $this->clientMediaType;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Uri.php b/vendor/guzzlehttp/psr7/src/Uri.php
new file mode 100755
index 0000000..825a25e
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Uri.php
@@ -0,0 +1,760 @@
+ 80,
+ 'https' => 443,
+ 'ftp' => 21,
+ 'gopher' => 70,
+ 'nntp' => 119,
+ 'news' => 119,
+ 'telnet' => 23,
+ 'tn3270' => 23,
+ 'imap' => 143,
+ 'pop' => 110,
+ 'ldap' => 389,
+ ];
+
+ private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
+ private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
+ private static $replaceQuery = ['=' => '%3D', '&' => '%26'];
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /**
+ * @param string $uri URI to parse
+ */
+ public function __construct($uri = '')
+ {
+ // weak type check to also accept null until we can add scalar type hints
+ if ($uri != '') {
+ $parts = parse_url($uri);
+ if ($parts === false) {
+ throw new \InvalidArgumentException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ public function __toString()
+ {
+ return self::composeComponents(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->path,
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ /**
+ * Composes a URI reference string from its various components.
+ *
+ * Usually this method does not need to be called manually but instead is used indirectly via
+ * `Psr\Http\Message\UriInterface::__toString`.
+ *
+ * PSR-7 UriInterface treats an empty component the same as a missing component as
+ * getQuery(), getFragment() etc. always return a string. This explains the slight
+ * difference to RFC 3986 Section 5.3.
+ *
+ * Another adjustment is that the authority separator is added even when the authority is missing/empty
+ * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
+ * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
+ * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
+ * that format).
+ *
+ * @param string $scheme
+ * @param string $authority
+ * @param string $path
+ * @param string $query
+ * @param string $fragment
+ *
+ * @return string
+ *
+ * @link https://tools.ietf.org/html/rfc3986#section-5.3
+ */
+ public static function composeComponents($scheme, $authority, $path, $query, $fragment)
+ {
+ $uri = '';
+
+ // weak type checks to also accept null until we can add scalar type hints
+ if ($scheme != '') {
+ $uri .= $scheme . ':';
+ }
+
+ if ($authority != ''|| $scheme === 'file') {
+ $uri .= '//' . $authority;
+ }
+
+ $uri .= $path;
+
+ if ($query != '') {
+ $uri .= '?' . $query;
+ }
+
+ if ($fragment != '') {
+ $uri .= '#' . $fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether the URI has the default port of the current scheme.
+ *
+ * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
+ * independently of the implementation.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ */
+ public static function isDefaultPort(UriInterface $uri)
+ {
+ return $uri->getPort() === null
+ || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]);
+ }
+
+ /**
+ * Whether the URI is absolute, i.e. it has a scheme.
+ *
+ * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
+ * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
+ * to another URI, the base URI. Relative references can be divided into several forms:
+ * - network-path references, e.g. '//example.com/path'
+ * - absolute-path references, e.g. '/path'
+ * - relative-path references, e.g. 'subpath'
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @see Uri::isNetworkPathReference
+ * @see Uri::isAbsolutePathReference
+ * @see Uri::isRelativePathReference
+ * @link https://tools.ietf.org/html/rfc3986#section-4
+ */
+ public static function isAbsolute(UriInterface $uri)
+ {
+ return $uri->getScheme() !== '';
+ }
+
+ /**
+ * Whether the URI is a network-path reference.
+ *
+ * A relative reference that begins with two slash characters is termed an network-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isNetworkPathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === '' && $uri->getAuthority() !== '';
+ }
+
+ /**
+ * Whether the URI is a absolute-path reference.
+ *
+ * A relative reference that begins with a single slash character is termed an absolute-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isAbsolutePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && isset($uri->getPath()[0])
+ && $uri->getPath()[0] === '/';
+ }
+
+ /**
+ * Whether the URI is a relative-path reference.
+ *
+ * A relative reference that does not begin with a slash character is termed a relative-path reference.
+ *
+ * @param UriInterface $uri
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ public static function isRelativePathReference(UriInterface $uri)
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
+ }
+
+ /**
+ * Whether the URI is a same-document reference.
+ *
+ * A same-document reference refers to a URI that is, aside from its fragment
+ * component, identical to the base URI. When no base URI is given, only an empty
+ * URI reference (apart from its fragment) is considered a same-document reference.
+ *
+ * @param UriInterface $uri The URI to check
+ * @param UriInterface|null $base An optional base URI to compare against
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-4.4
+ */
+ public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
+ {
+ if ($base !== null) {
+ $uri = UriResolver::resolve($base, $uri);
+
+ return ($uri->getScheme() === $base->getScheme())
+ && ($uri->getAuthority() === $base->getAuthority())
+ && ($uri->getPath() === $base->getPath())
+ && ($uri->getQuery() === $base->getQuery());
+ }
+
+ return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
+ }
+
+ /**
+ * Removes dot segments from a path and returns the new path.
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
+ * @see UriResolver::removeDotSegments
+ */
+ public static function removeDotSegments($path)
+ {
+ return UriResolver::removeDotSegments($path);
+ }
+
+ /**
+ * Converts the relative URI into a new URI that is resolved against the base URI.
+ *
+ * @param UriInterface $base Base URI
+ * @param string|UriInterface $rel Relative URI
+ *
+ * @return UriInterface
+ *
+ * @deprecated since version 1.4. Use UriResolver::resolve instead.
+ * @see UriResolver::resolve
+ */
+ public static function resolve(UriInterface $base, $rel)
+ {
+ if (!($rel instanceof UriInterface)) {
+ $rel = new self($rel);
+ }
+
+ return UriResolver::resolve($base, $rel);
+ }
+
+ /**
+ * Creates a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key to remove.
+ *
+ * @return UriInterface
+ */
+ public static function withoutQueryValue(UriInterface $uri, $key)
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * A value of null will set the query string key without a value, e.g. "key"
+ * instead of "key=value".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string|null $value Value to set
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValue(UriInterface $uri, $key, $value)
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ $result[] = self::generateQueryString($key, $value);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with multiple specific query string values.
+ *
+ * It has the same behavior as withQueryValue() but for an associative array of key => value.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param array $keyValueArray Associative array of key and values
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValues(UriInterface $uri, array $keyValueArray)
+ {
+ $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
+
+ foreach ($keyValueArray as $key => $value) {
+ $result[] = self::generateQueryString($key, $value);
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a URI from a hash of `parse_url` components.
+ *
+ * @param array $parts
+ *
+ * @return UriInterface
+ * @link http://php.net/manual/en/function.parse-url.php
+ *
+ * @throws \InvalidArgumentException If the components do not form a valid URI.
+ */
+ public static function fromParts(array $parts)
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ $uri->validateState();
+
+ return $uri;
+ }
+
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority()
+ {
+ $authority = $this->host;
+ if ($this->userInfo !== '') {
+ $authority = $this->userInfo . '@' . $authority;
+ }
+
+ if ($this->port !== null) {
+ $authority .= ':' . $this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo()
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme)
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null)
+ {
+ $info = $this->filterUserInfoComponent($user);
+ if ($password !== null) {
+ $info .= ':' . $this->filterUserInfoComponent($password);
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withHost($host)
+ {
+ $host = $this->filterHost($host);
+
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPort($port)
+ {
+ $port = $this->filterPort($port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPath($path)
+ {
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withQuery($query)
+ {
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+
+ return $new;
+ }
+
+ public function withFragment($fragment)
+ {
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+
+ return $new;
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param array $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts)
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user'])
+ ? $this->filterUserInfoComponent($parts['user'])
+ : '';
+ $this->host = isset($parts['host'])
+ ? $this->filterHost($parts['host'])
+ : '';
+ $this->port = isset($parts['port'])
+ ? $this->filterPort($parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
+ }
+
+ $this->removeDefaultPort();
+ }
+
+ /**
+ * @param string $scheme
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the scheme is invalid.
+ */
+ private function filterScheme($scheme)
+ {
+ if (!is_string($scheme)) {
+ throw new \InvalidArgumentException('Scheme must be a string');
+ }
+
+ return strtolower($scheme);
+ }
+
+ /**
+ * @param string $component
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the user info is invalid.
+ */
+ private function filterUserInfoComponent($component)
+ {
+ if (!is_string($component)) {
+ throw new \InvalidArgumentException('User info must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $component
+ );
+ }
+
+ /**
+ * @param string $host
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the host is invalid.
+ */
+ private function filterHost($host)
+ {
+ if (!is_string($host)) {
+ throw new \InvalidArgumentException('Host must be a string');
+ }
+
+ return strtolower($host);
+ }
+
+ /**
+ * @param int|null $port
+ *
+ * @return int|null
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($port)
+ {
+ if ($port === null) {
+ return null;
+ }
+
+ $port = (int) $port;
+ if (0 > $port || 0xffff < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
+ );
+ }
+
+ return $port;
+ }
+
+ /**
+ * @param UriInterface $uri
+ * @param array $keys
+ *
+ * @return array
+ */
+ private static function getFilteredQueryString(UriInterface $uri, array $keys)
+ {
+ $current = $uri->getQuery();
+
+ if ($current === '') {
+ return [];
+ }
+
+ $decodedKeys = array_map('rawurldecode', $keys);
+
+ return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
+ return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
+ });
+ }
+
+ /**
+ * @param string $key
+ * @param string|null $value
+ *
+ * @return string
+ */
+ private static function generateQueryString($key, $value)
+ {
+ // Query string separators ("=", "&") within the key or value need to be encoded
+ // (while preventing double-encoding) before setting the query string. All other
+ // chars that need percent-encoding will be encoded by withQuery().
+ $queryString = strtr($key, self::$replaceQuery);
+
+ if ($value !== null) {
+ $queryString .= '=' . strtr($value, self::$replaceQuery);
+ }
+
+ return $queryString;
+ }
+
+ private function removeDefaultPort()
+ {
+ if ($this->port !== null && self::isDefaultPort($this)) {
+ $this->port = null;
+ }
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param string $path
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the path is invalid.
+ */
+ private function filterPath($path)
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException('Path must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param string $str
+ *
+ * @return string
+ *
+ * @throws \InvalidArgumentException If the query or fragment is invalid.
+ */
+ private function filterQueryAndFragment($str)
+ {
+ if (!is_string($str)) {
+ throw new \InvalidArgumentException('Query and fragment must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match)
+ {
+ return rawurlencode($match[0]);
+ }
+
+ private function validateState()
+ {
+ if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
+ $this->host = self::HTTP_DEFAULT_HOST;
+ }
+
+ if ($this->getAuthority() === '') {
+ if (0 === strpos($this->path, '//')) {
+ throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
+ }
+ if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
+ throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
+ }
+ } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
+ @trigger_error(
+ 'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
+ 'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
+ E_USER_DEPRECATED
+ );
+ $this->path = '/'. $this->path;
+ //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/vendor/guzzlehttp/psr7/src/UriNormalizer.php
new file mode 100755
index 0000000..384c29e
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php
@@ -0,0 +1,216 @@
+getPath() === '' &&
+ ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
+ ) {
+ $uri = $uri->withPath('/');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
+ $uri = $uri->withHost('');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
+ $uri = $uri->withPort(null);
+ }
+
+ if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
+ $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
+ }
+
+ if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
+ $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
+ }
+
+ if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
+ $queryKeyValues = explode('&', $uri->getQuery());
+ sort($queryKeyValues);
+ $uri = $uri->withQuery(implode('&', $queryKeyValues));
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether two URIs can be considered equivalent.
+ *
+ * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
+ * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
+ * resolved against the same base URI. If this is not the case, determination of equivalence or difference of
+ * relative references does not mean anything.
+ *
+ * @param UriInterface $uri1 An URI to compare
+ * @param UriInterface $uri2 An URI to compare
+ * @param int $normalizations A bitmask of normalizations to apply, see constants
+ *
+ * @return bool
+ * @link https://tools.ietf.org/html/rfc3986#section-6.1
+ */
+ public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS)
+ {
+ return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
+ }
+
+ private static function capitalizePercentEncoding(UriInterface $uri)
+ {
+ $regex = '/(?:%[A-Fa-f0-9]{2})++/';
+
+ $callback = function (array $match) {
+ return strtoupper($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private static function decodeUnreservedCharacters(UriInterface $uri)
+ {
+ $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
+
+ $callback = function (array $match) {
+ return rawurldecode($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UriResolver.php b/vendor/guzzlehttp/psr7/src/UriResolver.php
new file mode 100755
index 0000000..c1cb8a2
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UriResolver.php
@@ -0,0 +1,219 @@
+getScheme() != '') {
+ return $rel->withPath(self::removeDotSegments($rel->getPath()));
+ }
+
+ if ($rel->getAuthority() != '') {
+ $targetAuthority = $rel->getAuthority();
+ $targetPath = self::removeDotSegments($rel->getPath());
+ $targetQuery = $rel->getQuery();
+ } else {
+ $targetAuthority = $base->getAuthority();
+ if ($rel->getPath() === '') {
+ $targetPath = $base->getPath();
+ $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
+ } else {
+ if ($rel->getPath()[0] === '/') {
+ $targetPath = $rel->getPath();
+ } else {
+ if ($targetAuthority != '' && $base->getPath() === '') {
+ $targetPath = '/' . $rel->getPath();
+ } else {
+ $lastSlashPos = strrpos($base->getPath(), '/');
+ if ($lastSlashPos === false) {
+ $targetPath = $rel->getPath();
+ } else {
+ $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
+ }
+ }
+ }
+ $targetPath = self::removeDotSegments($targetPath);
+ $targetQuery = $rel->getQuery();
+ }
+ }
+
+ return new Uri(Uri::composeComponents(
+ $base->getScheme(),
+ $targetAuthority,
+ $targetPath,
+ $targetQuery,
+ $rel->getFragment()
+ ));
+ }
+
+ /**
+ * Returns the target URI as a relative reference from the base URI.
+ *
+ * This method is the counterpart to resolve():
+ *
+ * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+ *
+ * One use-case is to use the current request URI as base URI and then generate relative links in your documents
+ * to reduce the document size or offer self-contained downloadable document archives.
+ *
+ * $base = new Uri('http://example.com/a/b/');
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+ * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+ *
+ * This method also accepts a target that is already relative and will try to relativize it further. Only a
+ * relative-path reference will be returned as-is.
+ *
+ * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
+ *
+ * @param UriInterface $base Base URI
+ * @param UriInterface $target Target URI
+ *
+ * @return UriInterface The relative URI reference
+ */
+ public static function relativize(UriInterface $base, UriInterface $target)
+ {
+ if ($target->getScheme() !== '' &&
+ ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
+ ) {
+ return $target;
+ }
+
+ if (Uri::isRelativePathReference($target)) {
+ // As the target is already highly relative we return it as-is. It would be possible to resolve
+ // the target with `$target = self::resolve($base, $target);` and then try make it more relative
+ // by removing a duplicate query. But let's not do that automatically.
+ return $target;
+ }
+
+ if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
+ return $target->withScheme('');
+ }
+
+ // We must remove the path before removing the authority because if the path starts with two slashes, the URI
+ // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
+ // invalid.
+ $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
+
+ if ($base->getPath() !== $target->getPath()) {
+ return $emptyPathUri->withPath(self::getRelativePath($base, $target));
+ }
+
+ if ($base->getQuery() === $target->getQuery()) {
+ // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
+ return $emptyPathUri->withQuery('');
+ }
+
+ // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
+ // inherit the base query component when resolving.
+ if ($target->getQuery() === '') {
+ $segments = explode('/', $target->getPath());
+ $lastSegment = end($segments);
+
+ return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
+ }
+
+ return $emptyPathUri;
+ }
+
+ private static function getRelativePath(UriInterface $base, UriInterface $target)
+ {
+ $sourceSegments = explode('/', $base->getPath());
+ $targetSegments = explode('/', $target->getPath());
+ array_pop($sourceSegments);
+ $targetLastSegment = array_pop($targetSegments);
+ foreach ($sourceSegments as $i => $segment) {
+ if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
+ unset($sourceSegments[$i], $targetSegments[$i]);
+ } else {
+ break;
+ }
+ }
+ $targetSegments[] = $targetLastSegment;
+ $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments);
+
+ // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
+ if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
+ $relativePath = "./$relativePath";
+ } elseif ('/' === $relativePath[0]) {
+ if ($base->getAuthority() != '' && $base->getPath() === '') {
+ // In this case an extra slash is added by resolve() automatically. So we must not add one here.
+ $relativePath = ".$relativePath";
+ } else {
+ $relativePath = "./$relativePath";
+ }
+ }
+
+ return $relativePath;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/functions.php b/vendor/guzzlehttp/psr7/src/functions.php
new file mode 100755
index 0000000..8e6dafe
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/functions.php
@@ -0,0 +1,899 @@
+getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+}
+
+/**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
+ * returns a UriInterface for the given value. If the value is already a
+ * `UriInterface`, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @return UriInterface
+ * @throws \InvalidArgumentException
+ */
+function uri_for($uri)
+{
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ } elseif (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+}
+
+/**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * @param resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return StreamInterface
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+function stream_for($resource = '', array $options = [])
+{
+ if (is_scalar($resource)) {
+ $stream = fopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new Stream($stream, $options);
+ }
+
+ switch (gettype($resource)) {
+ case 'resource':
+ return new Stream($resource, $options);
+ case 'object':
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return stream_for((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(fopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
+}
+
+/**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair
+ * data of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ *
+ * @return array Returns the parsed header values.
+ */
+function parse_header($header)
+{
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = [];
+
+ foreach (normalize_header($header) as $val) {
+ $part = [];
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+function normalize_header($header)
+{
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = [];
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Clone and modify a request with the given changes.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+function modify_request(RequestInterface $request, array $changes)
+{
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+
+ if ($port = $changes['uri']->getPort()) {
+ $standardPorts = ['http' => 80, 'https' => 443];
+ $scheme = $changes['uri']->getScheme();
+ if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
+ $changes['set_headers']['Host'] .= ':'.$port;
+ }
+ }
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = _caseless_remove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ if ($request instanceof ServerRequestInterface) {
+ return (new ServerRequest(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion(),
+ $request->getServerParams()
+ ))
+ ->withParsedBody($request->getParsedBody())
+ ->withQueryParams($request->getQueryParams())
+ ->withCookieParams($request->getCookieParams())
+ ->withUploadedFiles($request->getUploadedFiles());
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+}
+
+/**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()` returns a
+ * value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+function rewind_body(MessageInterface $message)
+{
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+}
+
+/**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ * @throws \RuntimeException if the file cannot be opened
+ */
+function try_fopen($filename, $mode)
+{
+ $ex = null;
+ set_error_handler(function () use ($filename, $mode, &$ex) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open %s using mode %s: %s',
+ $filename,
+ $mode,
+ func_get_args()[1]
+ ));
+ });
+
+ $handle = fopen($filename, $mode);
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+}
+
+/**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ * @return string
+ * @throws \RuntimeException on error.
+ */
+function copy_to_string(StreamInterface $stream, $maxLen = -1)
+{
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+}
+
+/**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+function copy_to_stream(
+ StreamInterface $source,
+ StreamInterface $dest,
+ $maxLen = -1
+) {
+ $bufferSize = 8192;
+
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read($bufferSize))) {
+ break;
+ }
+ }
+ } else {
+ $remaining = $maxLen;
+ while ($remaining > 0 && !$source->eof()) {
+ $buf = $source->read(min($bufferSize, $remaining));
+ $len = strlen($buf);
+ if (!$len) {
+ break;
+ }
+ $remaining -= $len;
+ $dest->write($buf);
+ }
+ }
+}
+
+/**
+ * Calculate a hash of a Stream
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ * @throws \RuntimeException on error.
+ */
+function hash(
+ StreamInterface $stream,
+ $algo,
+ $rawOutput = false
+) {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+}
+
+/**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string
+ */
+function readline(StreamInterface $stream, $maxLength = null)
+{
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ // Using a loose equality here to match on '' and false.
+ if (null == ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte === "\n" || ++$size === $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+}
+
+/**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ *
+ * @return Request
+ */
+function parse_request($message)
+{
+ $data = _parse_message($message);
+ $matches = [];
+ if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+}
+
+/**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ *
+ * @return Response
+ */
+function parse_response($message)
+{
+ $data = _parse_message($message);
+ // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
+ // between status-code and reason-phrase is required. But browsers accept
+ // responses without space and reason as well.
+ if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+
+ return new Response(
+ $parts[1],
+ $data['headers'],
+ $data['body'],
+ explode('/', $parts[0])[1],
+ isset($parts[2]) ? $parts[2] : null
+ );
+}
+
+/**
+ * Parse a query string into an associative array.
+ *
+ * If multiple values are found for the same key, the value of that key
+ * value pair will become an array. This function does not parse nested
+ * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
+ * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
+ *
+ * @param string $str Query string to parse
+ * @param int|bool $urlEncoding How the query string is encoded
+ *
+ * @return array
+ */
+function parse_query($str, $urlEncoding = true)
+{
+ $result = [];
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', $value));
+ };
+ } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) { return $str; };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!isset($result[$key])) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = [$result[$key]];
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of parse_query() to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like http_build_query would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
+ * to encode using RFC3986, or PHP_QUERY_RFC1738
+ * to encode using RFC1738.
+ * @return string
+ */
+function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
+{
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function ($str) { return $str; };
+ } elseif ($encoding === PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding === PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder($k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ if ($v !== null) {
+ $qs .= '=' . $encoder($v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ if ($vv !== null) {
+ $qs .= '=' . $encoder($vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+}
+
+/**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @param $filename
+ *
+ * @return null|string
+ */
+function mimetype_from_filename($filename)
+{
+ return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
+}
+
+/**
+ * Maps a file extensions to a mimetype.
+ *
+ * @param $extension string The file extension.
+ *
+ * @return string|null
+ * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
+ */
+function mimetype_from_extension($extension)
+{
+ static $mimetypes = [
+ '3gp' => 'video/3gpp',
+ '7z' => 'application/x-7z-compressed',
+ 'aac' => 'audio/x-aac',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'asc' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'atom' => 'application/atom+xml',
+ 'avi' => 'video/x-msvideo',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip2',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'deb' => 'application/x-debian-package',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dvi' => 'application/x-dvi',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'etx' => 'text/x-setext',
+ 'flac' => 'audio/flac',
+ 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif',
+ 'gz' => 'application/gzip',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ini' => 'text/plain',
+ 'iso' => 'application/x-iso9660-image',
+ 'jar' => 'application/java-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'latex' => 'application/x-latex',
+ 'log' => 'text/plain',
+ 'm4a' => 'audio/mp4',
+ 'm4v' => 'video/mp4',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mkv' => 'video/x-matroska',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'qt' => 'video/quicktime',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'torrent' => 'application/x-bittorrent',
+ 'ttf' => 'application/x-font-ttf',
+ 'txt' => 'text/plain',
+ 'wav' => 'audio/x-wav',
+ 'webm' => 'video/webm',
+ 'webp' => 'image/webp',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'video/x-ms-wmv',
+ 'woff' => 'application/x-font-woff',
+ 'wsdl' => 'application/wsdl+xml',
+ 'xbm' => 'image/x-xbitmap',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'zip' => 'application/zip',
+ ];
+
+ $extension = strtolower($extension);
+
+ return isset($mimetypes[$extension])
+ ? $mimetypes[$extension]
+ : null;
+}
+
+/**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ *
+ * @return array
+ * @internal
+ */
+function _parse_message($message)
+{
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ $message = ltrim($message, "\r\n");
+
+ $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
+
+ if ($messageParts === false || count($messageParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
+ }
+
+ list($rawHeaders, $body) = $messageParts;
+ $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
+ $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
+
+ if ($headerParts === false || count($headerParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing status line');
+ }
+
+ list($startLine, $rawHeaders) = $headerParts;
+
+ if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
+ // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
+ $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
+ }
+
+ /** @var array[] $headerLines */
+ $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
+
+ // If these aren't the same, then one line didn't match and there's an invalid header.
+ if ($count !== substr_count($rawHeaders, "\n")) {
+ // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
+ throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
+ }
+
+ throw new \InvalidArgumentException('Invalid header syntax');
+ }
+
+ $headers = [];
+
+ foreach ($headerLines as $headerLine) {
+ $headers[$headerLine[1]][] = $headerLine[2];
+ }
+
+ return [
+ 'start-line' => $startLine,
+ 'headers' => $headers,
+ 'body' => $body,
+ ];
+}
+
+/**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ *
+ * @return string
+ * @internal
+ */
+function _parse_request_uri($path, array $headers)
+{
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme . '://' . $host . '/' . ltrim($path, '/');
+}
+
+/**
+ * Get a short summary of the message body
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param MessageInterface $message The message to get the body summary
+ * @param int $truncateAt The maximum allowed size of the summary
+ *
+ * @return null|string
+ */
+function get_message_body_summary(MessageInterface $message, $truncateAt = 120)
+{
+ $body = $message->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $summary = $body->read($truncateAt);
+ $body->rewind();
+
+ if ($size > $truncateAt) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
+ return null;
+ }
+
+ return $summary;
+}
+
+/** @internal */
+function _caseless_remove($keys, array $data)
+{
+ $result = [];
+
+ foreach ($keys as &$key) {
+ $key = strtolower($key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower($k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+}
diff --git a/vendor/guzzlehttp/psr7/src/functions_include.php b/vendor/guzzlehttp/psr7/src/functions_include.php
new file mode 100755
index 0000000..96a4a83
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/functions_include.php
@@ -0,0 +1,6 @@
+setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__)
+ ->name('/(?:^lazydoctor$|\.php$)/')
+ )
+ ->setIndent(' ')
+ ->setLineEnding("\n")
+ ->setRules([
+ '@Symfony' => true,
+ // Override @Symfony rules
+ 'pre_increment' => false,
+ 'blank_line_before_statement' => ['statements' => ['return', 'try', 'throw']],
+ 'phpdoc_align' => ['tags' => ['param', 'throws']],
+ 'method_argument_space' => ['ensure_fully_multiline' => false],
+ 'binary_operator_spaces' => [
+ 'align_double_arrow' => true,
+ 'align_equals' => false,
+ ],
+ 'phpdoc_annotation_without_dot' => false,
+ 'yoda_style' => [
+ // Symfony writes their conditions backwards; we use normal order.
+ 'equal' => false,
+ 'identical' => false,
+ 'less_and_greater' => false,
+ ],
+ 'is_null' => [
+ // Replaces all is_null() with === null.
+ 'use_yoda_style' => false,
+ ],
+ // Custom rules
+ 'align_multiline_comment' => true,
+ 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
+ 'ordered_imports' => true,
+ 'phpdoc_order' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ]);
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/.pre-commit.hook b/vendor/lazyjsonmapper/lazyjsonmapper/.pre-commit.hook
new file mode 100755
index 0000000..4a87bd2
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/.pre-commit.hook
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright 2017 The LazyJsonMapper Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ----------------------------------------------------------------------------
+#
+# Verifies that all files in the worktree follow our codestyle standards.
+#
+# Note that this script can't check that they're actually committing the nicely
+# formatted code. It just checks that the worktree is clean. So if they've fixed
+# all files but haven't added the codestyle fixes to their commit, they'll still
+# pass this check. But it's still a great protection against most mistakes.
+#
+# To install this hook, just run the following in the project's root folder:
+# ln -fs "../../.pre-commit.hook" .git/hooks/pre-commit
+#
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Git ensures that CWD is always the root of the project folder, so we can just
+# run all tests without verifying what folder we are in...
+
+failed="no"
+echo "[pre-commit] Checking work-tree codestyle..."
+
+# Check the general codestyle format.
+echo "> Verifying php-cs-fixer..."
+vendor/bin/php-cs-fixer fix --config=.php_cs.dist --allow-risky yes --dry-run
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Look for specific problems with the style, related to our project.
+echo "> Verifying checkStyle..."
+/usr/bin/env php devtools/checkStyle.php x
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Refuse to commit if there were problems. Instruct the user about solving it.
+if [ "${failed}" = "yes" ]; then
+ # Yes there are lots of "echo" commands, because "\n" is not cross-platform.
+ echo "[commit failed] There are problems with your code..."
+ echo ""
+ echo "Run 'composer codestyle' to fix the code in your worktree."
+ echo ""
+ echo "But beware that the process is automatic, and that the result"
+ echo "isn't always perfect and won't be automatically staged."
+ echo ""
+ echo "So remember to manually read through the changes, then further"
+ echo "fix them if necessary, and finally stage the updated code"
+ echo "afterwards so that the fixed code gets committed."
+ exit 1
+fi
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/LICENSE b/vendor/lazyjsonmapper/lazyjsonmapper/LICENSE
new file mode 100755
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/NOTICE b/vendor/lazyjsonmapper/lazyjsonmapper/NOTICE
new file mode 100755
index 0000000..27c6183
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/NOTICE
@@ -0,0 +1,5 @@
+LazyJsonMapper
+Copyright 2017 The LazyJsonMapper Project
+
+This product includes software developed at
+The LazyJsonMapper Project (https://github.com/SteveJobzniak/LazyJsonMapper).
\ No newline at end of file
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/README.md b/vendor/lazyjsonmapper/lazyjsonmapper/README.md
new file mode 100755
index 0000000..7d3a997
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/README.md
@@ -0,0 +1,211 @@
+# LazyJsonMapper
+
+## Advanced, intelligent & automatic object-oriented JSON containers for PHP.
+
+Implements a highly efficient, automatic, object-oriented and lightweight
+(memory-wise) JSON data container. It provides intelligent data conversion
+and parsing, to give you a nice, reliable interface to your JSON data,
+without having to worry about doing any of the tedious parsing yourself.
+
+### Features:
+
+- Provides a completely object-oriented interface to all of your JSON data.
+
+- Automatically maps complex, nested JSON data structures onto real PHP
+ objects, with total support for nested objects and multi-level arrays.
+
+- Extremely optimized for very high performance and very low memory usage.
+ Much lower than other PHP JSON mappers that people have used in the past.
+
+ For example, normal PHP objects with manually defined `$properties`, which
+ is what's used by _other_ JSON mappers, will consume memory for every
+ property even if that property wasn't in the JSON data (is a `NULL`). Our
+ system on the other hand takes up ZERO bytes of RAM for any properties
+ that don't exist in the current object's JSON data!
+
+- Automatically provides "direct virtual properties", which lets you
+ interact with the JSON data as if it were regular object properties,
+ such as `echo $item->some_value` and `$item->some_value = 'foo'`.
+
+ The virtual properties can be disabled via an option.
+
+- Automatically provides object-oriented "virtual functions", which let you
+ interact with the data in a fully object-oriented way via functions such
+ as `$item->getSomeValue()` and `$item->setSomeValue('foo')`. We support a
+ large range of different functions for manipulating the JSON data, and you
+ can see a list of all available function names for all of your properties
+ by simply running `$item->printPropertyDescriptions()`.
+
+ The virtual functions can be disabled via an option.
+
+- Includes the `LazyDoctor` tool, which _automatically_ documents all of
+ your `LazyJsonMapper`-based classes so that their virtual properties and
+ functions become _fully_ visible to your IDE and to various intelligent
+ code analysis tools. It also performs class diagnostics by compiling all
+ of your class property maps, which means that you can be 100% sure that
+ all of your maps are valid (compilable) if this tool runs successfully.
+
+- We provide a complete, internal API which your subclasses can use to
+ interact with the data inside of the JSON container. This allows you to
+ easily override the automatic functions or create additional functions
+ for your objects. To override core functions, just define a function with
+ the exact same name on your object and make it do whatever you want to.
+
+ Here are some examples of function overriding:
+
+ ```php
+ public function getFoo()
+ {
+ // try to read property, and handle a special "current_time" value.
+ $value = $this->_getProperty('foo');
+ if ($value === 'current_time') { return time(); }
+ return $value;
+ }
+ public function setFoo(
+ $value)
+ {
+ // if they try to set value to "md5", we use a special value instead
+ if ($value === 'md5') { $value = md5(time()); }
+ return $this->_setProperty('foo', $value);
+ }
+ ```
+
+- All mapping/data conversion is done "lazily", on a per-property basis.
+ When you access a property, that specific property is mapped/converted to
+ the proper type as defined by your class property map. No time or memory
+ is wasted converting properties that you never touch.
+
+- Strong type-system. The class property map controls the exact types and
+ array depths. You can fully trust that the data you get/set will match
+ your specifications. Invalid data that mismatches the spec is impossible.
+
+- Advanced settings system. Everything is easily configured via PHP class
+ constants, which means that your class-settings are stateless (there's no
+ need for any special "settings/mapper object" to keep track of settings),
+ and that all settings are immutable constants (which means that they are
+ reliable and can never mutate at runtime, so that you can fully trust that
+ classes will always behave as-defined in their code).
+
+ If you want to override multiple core settings identically for all of your
+ classes, then simply create a subclass of `LazyJsonMapper` and configure
+ all of your settings on that, and then derive all of your other classes
+ from your re-configured subclass!
+
+- The world's most advanced mapper definition system. Your class property
+ maps are defined in an easy PHPdoc-style format, and support multilevel
+ arrays (such as `int[][]` for "an array of arrays of ints"), relative
+ types (so you can map properties to classes/objects that are relative to
+ the namespace of the class property map), parent inheritance (all of your
+ parent `extends`-hierarchy's maps will be included in your final property
+ map) and even multiple inheritance (you can literally "import" an infinite
+ number of other maps into your class, which don't come from your own
+ parent `extends`-hierarchy).
+
+- Inheriting properties from parent classes or importing properties from
+ other classes is a zero-cost operation thanks to how efficient our
+ property map compiler is. So feel free to import everything you need.
+ You can even use this system to create importable classes that just hold
+ "collections" of shared properties, which you import into other classes.
+
+- The class property maps are compiled a single time per-class at runtime,
+ the first time a class is used. The compilation process fully verifies
+ and compiles all property definitions, all parent maps, all inherited
+ maps, and all maps of all classes you link properties to.
+
+ If there are any compilation problems due to a badly written map anywhere
+ in your hierarchy, you will be shown the exact problem in great detail.
+
+ In case of success, the compiled and verified maps are all stored in an
+ incredibly memory-efficient format in a global cache which is shared by
+ your whole PHP runtime, which means that anything in your code or in any
+ other libraries which accesses the same classes will all share the cached
+ compilations of those classes, for maximum memory efficiency.
+
+- You are also able to access JSON properties that haven't been defined in
+ the class property map. In that case, they are treated as undefined and
+ untyped (`mixed`) and there won't be any automatic type-conversion of such
+ properties, but it can still be handy in a pinch.
+
+- There are lots of data export/output options for your object's JSON data,
+ to get it back out of the object again: As a multi-level array, as nested
+ stdClass objects, or as a JSON string representation of your object.
+
+- We include a whole assortment of incredibly advanced debugging features:
+
+ You can run the constructor with `$requireAnalysis` to ensure that all
+ of your JSON data is successfully mapped according to your class property
+ map, and that you haven't missed defining any properties that exist in the
+ data. In case of any problems, the analysis message will give you a full
+ list of all problems encountered in your entire JSON data hierarchy.
+
+ For your class property maps themselves, you can run functions such as
+ `printPropertyDescriptions()` to see a complete list of all properties and
+ how they are defined. This helps debug your class inheritance and imports
+ to visually see what your final class map looks like, and it also helps
+ users see all available properties and all of their virtual functions.
+
+ And for the JSON data, you can use functions such as `printJson()` to get
+ a beautiful view of all internal JSON data, which is incredibly helpful
+ when you (or your users) need to figure out what's available inside the
+ current object instance's data storage.
+
+- A fine-grained and logical exception-system which ensures that you can
+ always trust the behavior of your objects and can catch problems easily.
+ And everything we throw is _always_ based on `LazyJsonMapperException`,
+ which means that you can simply catch that single "root" exception
+ whenever you don't care about fine-grained differentiation.
+
+- Clean and modular code ensures stability and future extensibility.
+
+- Deep code documentation explains everything you could ever wonder about.
+
+- Lastly, we implement super-efficient object serialization. Everything is
+ stored in a tightly packed format which minimizes data size when you need
+ to transfer your objects between runtimes.
+
+### Installation
+
+You need at least PHP 5.6 or higher. PHP 7+ is also fully supported and is recommended.
+
+Run the following [Composer](https://getcomposer.org/download/) installation command:
+
+```
+composer require lazyjsonmapper/lazyjsonmapper
+```
+
+### Examples
+
+View the contents of the [`examples/`](https://github.com/SteveJobzniak/LazyJsonMapper/tree/master/examples) folder.
+
+### Documentation
+
+Everything is fully documented directly within the source code of this library.
+
+You can also [read the same documentation online](https://mgp25.github.io/lazyjsonmapper-docs/namespaces/LazyJsonMapper.html) as nicely formatted HTML pages.
+
+### LazyDoctor
+
+Our automatic class-documentation and diagnostic utility will be placed within
+your project's `./vendor/bin/` folder. Simply run it without any parameters to
+see a list of all available options. You can also open that file in a regular
+text editor to read some general usage tips and tricks at the top of the
+utility's source code.
+
+### Copyright
+
+Copyright 2017 The LazyJsonMapper Project
+
+### License
+
+[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+
+### Author
+
+SteveJobzniak
+
+### Contributing
+
+If you would like to contribute to this project, please feel free to submit a
+pull request, or perhaps even sending a donation to a team member as a token of
+your appreciation.
+
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/bin/lazydoctor b/vendor/lazyjsonmapper/lazyjsonmapper/bin/lazydoctor
new file mode 100755
index 0000000..c142b53
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/bin/lazydoctor
@@ -0,0 +1,738 @@
+#!/usr/bin/env php
+/dev/null" to send STDOUT to the void...
+ * That way you'll ONLY see critical status messages during the processing.
+ */
+
+set_time_limit(0);
+date_default_timezone_set('UTC');
+
+// Verify minimum PHP version.
+if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50600) {
+ fwrite(STDERR, 'LazyDoctor requires PHP 5.6 or higher.'.PHP_EOL);
+ exit(1);
+}
+
+// Register a simple GetOptionKit autoloader. This is fine because
+// GetOptionKit has no 3rd party library dependencies.
+spl_autoload_register(function ($class) {
+ // Check if this is a "GetOptionKit" load-request.
+ static $prefix = 'GetOptionKit\\';
+ static $len = 13; // strlen($prefix)
+ if (strncmp($prefix, $class, $len) !== 0) {
+ return;
+ }
+
+ // Find the "GetOptionKit" source folder.
+ static $dirs = [
+ __DIR__.'/../../../corneltek/getoptionkit/src',
+ __DIR__.'/../vendor/corneltek/getoptionkit/src',
+ ];
+ $baseDir = null;
+ foreach ($dirs as $dir) {
+ if (is_dir($dir) && ($dir = realpath($dir)) !== false) {
+ $baseDir = $dir;
+ break;
+ }
+ }
+ if ($baseDir === null) {
+ return;
+ }
+
+ // Get the relative class name.
+ $relativeClass = substr($class, $len);
+
+ // Generate PSR-4 file path to the class.
+ $file = sprintf('%s/%s.php', $baseDir, str_replace('\\', '/', $relativeClass));
+ if (is_file($file)) {
+ require $file;
+ }
+});
+
+// Parse command line options...
+use GetOptionKit\OptionCollection;
+use GetOptionKit\OptionParser;
+use GetOptionKit\OptionPrinter\ConsoleOptionPrinter;
+
+$specs = new OptionCollection();
+$specs->add('c|composer:=file', 'Path to your project\'s composer.json file.');
+$specs->add('p|properties?=boolean', 'Document virtual properties (if enabled for the classes).');
+$specs->add('f|functions?=boolean', 'Document virtual functions (if enabled for the classes).');
+$specs->add('o|document-overridden?=boolean', 'Always document virtual functions/properties even when they have been manually overridden by the class (or its parents).');
+$specs->add('w|windows?=boolean', 'Generate Windows-style ("\r\n") documentation line endings instead of the default Unix-style ("\n").');
+$specs->add('validate-only?=boolean', 'Validate current docs for all classes but don\'t write anything to disk.');
+$specs->add('h|help?=boolean', 'Show all available options.');
+
+try {
+ $parser = new OptionParser($specs);
+ $result = $parser->parse($argv);
+ $options = [
+ 'composer' => isset($result->keys['composer']) ? $result->keys['composer']->value : null,
+ 'properties' => isset($result->keys['properties']) && $result->keys['properties']->value !== false,
+ 'functions' => isset($result->keys['functions']) && $result->keys['functions']->value !== false,
+ 'document-overridden' => isset($result->keys['document-overridden']) && $result->keys['document-overridden']->value !== false,
+ 'windows' => isset($result->keys['windows']) && $result->keys['windows']->value !== false,
+ 'validate-only' => isset($result->keys['validate-only']) && $result->keys['validate-only']->value !== false,
+ 'help' => isset($result->keys['help']) && $result->keys['help']->value !== false,
+ ];
+} catch (Exception $e) {
+ // Warns in case of invalid option values.
+ fwrite(STDERR, $e->getMessage().PHP_EOL);
+ exit(1);
+}
+
+// Verify options...
+echo '[ LazyDoctor ]'.PHP_EOL.PHP_EOL;
+if ($options['composer'] === null || $options['help']) {
+ if ($options['composer'] === null) {
+ fwrite(STDERR, 'You must provide the --composer option.'.PHP_EOL.PHP_EOL);
+ }
+ $printer = new ConsoleOptionPrinter();
+ echo 'Available options:'.PHP_EOL.PHP_EOL;
+ echo $printer->render($specs);
+ exit($options['composer'] === null && !$options['help'] ? 1 : 0);
+}
+
+if ($options['composer']->getBasename() !== 'composer.json') {
+ fwrite(STDERR, 'You must point to your project\'s composer.json file.'.PHP_EOL.'You used: "'.$options['composer']->getRealPath().'".'.PHP_EOL);
+ exit(1);
+}
+
+// Decode the composer.json file...
+$json = @json_decode(file_get_contents($options['composer']->getRealPath()), true);
+if ($json === null) {
+ fwrite(STDERR, 'Unable to decode composer.json.'.PHP_EOL);
+ exit(1);
+}
+
+// Determine the project folder's real root path...
+$projectRoot = $options['composer']->getPathInfo()->getRealPath();
+
+// Determine their namespace PSR-4 paths via their project's composer.json...
+$namespaces = [];
+foreach (['autoload', 'autoload-dev'] as $type) {
+ if (!isset($json[$type]['psr-4']) || !is_array($json[$type]['psr-4'])) {
+ continue;
+ }
+
+ foreach ($json[$type]['psr-4'] as $namespace => $dir) {
+ // We don't support composer's empty "fallback" namespaces.
+ if ($namespace === '') {
+ fwrite(STDERR, 'Encountered illegal unnamed PSR-4 autoload namespace in composer.json.'.PHP_EOL);
+ exit(1);
+ }
+
+ // Ensure that the namespace ends in backslash.
+ if (substr_compare($namespace, '\\', strlen($namespace) - 1, 1) !== 0) {
+ fwrite(STDERR, 'Encountered illegal namespace "'.$namespace.'" (does not end in backslash) in composer.json.'.PHP_EOL);
+ exit(1);
+ }
+
+ // Ensure that the value is a string.
+ // NOTE: We allow empty strings, which corresponds to root folder.
+ if (!is_string($dir)) {
+ fwrite(STDERR, 'Encountered illegal non-string value for namespace "'.$namespace.'".'.PHP_EOL);
+ exit(1);
+ }
+
+ // Now resolve the path name...
+ $path = sprintf('%s/%s', $projectRoot, $dir);
+ $realpath = realpath($path);
+ if ($realpath === false) {
+ fwrite(STDERR, 'Unable to resolve real path for "'.$path.'".'.PHP_EOL);
+ exit(1);
+ }
+
+ // We don't allow the same directory to be defined multiple times.
+ if (isset($namespaces[$realpath])) {
+ fwrite(STDERR, 'Encountered duplicate namespace directory "'.$realpath.'" in composer.json.'.PHP_EOL);
+ exit(1);
+ }
+
+ // And we're done! The namespace and its path have been resolved.
+ $namespaces[$realpath] = $namespace;
+ }
+}
+
+// Verify that we found some namespaces...
+if (empty($namespaces)) {
+ fwrite(STDERR, 'There are no PSR-4 autoload namespaces in your composer.json.'.PHP_EOL);
+ exit(1);
+}
+
+// Now load the project's autoload.php file.
+// NOTE: This is necessary so that we can autoload their classes...
+$autoload = sprintf('%s/vendor/autoload.php', $projectRoot);
+$realautoload = realpath($autoload);
+if ($realautoload === false) {
+ fwrite(STDERR, 'Unable to find the project\'s Composer autoloader ("'.$autoload.'").'.PHP_EOL);
+ exit(1);
+}
+require $realautoload;
+
+// Verify that their project's autoloader contains LazyJsonMapper...
+if (!class_exists('\LazyJsonMapper\LazyJsonMapper', true)) { // TRUE = Autoload.
+ fwrite(STDERR, 'Target project doesn\'t contain the LazyJsonMapper library.'.PHP_EOL);
+ exit(1);
+}
+
+// Alright, display the current options...
+echo 'Project: "'.$projectRoot.'".'.PHP_EOL
+ .'- Documentation Line Endings: '.($options['windows'] ? 'Windows ("\r\n")' : 'Unix ("\n")').'.'.PHP_EOL
+ .'- ['.($options['properties'] ? 'X' : ' ').'] Document Virtual Properties ("@property").'.PHP_EOL
+ .'- ['.($options['functions'] ? 'X' : ' ').'] Document Virtual Functions ("@method").'.PHP_EOL
+ .'- ['.($options['document-overridden'] ? 'X' : ' ').'] Document Overridden Properties/Functions.'.PHP_EOL;
+if ($options['validate-only']) {
+ echo '- This is a validation run. Nothing will be written to disk.'.PHP_EOL;
+}
+
+// We can now use our custom classes, since the autoloader has been imported...
+use LazyJsonMapper\Exception\LazyJsonMapperException;
+use LazyJsonMapper\Export\PropertyDescription;
+use LazyJsonMapper\Property\PropertyMapCache;
+use LazyJsonMapper\Property\PropertyMapCompiler;
+use LazyJsonMapper\Utilities;
+
+/**
+ * Automatic LazyJsonMapper-class documentation generator.
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class LazyClassDocumentor
+{
+ /** @var PropertyMapCache */
+ private static $_propertyMapCache;
+
+ /** @var array */
+ private $_compiledPropertyMapLink;
+
+ /** @var ReflectionClass */
+ private $_reflector;
+
+ /** @var array */
+ private $_options;
+
+ /** @var string Newline sequence. */
+ private $_nl;
+
+ /**
+ * Constructor.
+ *
+ * @param string $class
+ * @param array $options
+ *
+ * @throws ReflectionException
+ */
+ public function __construct(
+ $class,
+ array $options)
+ {
+ if (self::$_propertyMapCache === null) {
+ self::$_propertyMapCache = new PropertyMapCache();
+ }
+ $this->_reflector = new ReflectionClass($class);
+ $this->_options = $options;
+ $this->_nl = $options['windows'] ? "\r\n" : "\n";
+ }
+
+ /**
+ * Process the current class.
+ *
+ * @throws ReflectionException
+ * @throws LazyJsonMapperException
+ *
+ * @return bool `TRUE` if on-disk file has correct docs, otherwise `FALSE`.
+ */
+ public function process()
+ {
+ // Only process user-defined classes (never any built-in PHP classes).
+ if (!$this->_reflector->isUserDefined()) {
+ return true;
+ }
+
+ // There's nothing to do if this isn't a LazyJsonMapper subclass.
+ // NOTE: This properly skips "\LazyJsonMapper\LazyJsonMapper" itself.
+ if (!$this->_reflector->isSubclassOf('\LazyJsonMapper\LazyJsonMapper')) {
+ return true;
+ }
+
+ // Compile this class property map if not yet built and cached.
+ $thisClassName = $this->_reflector->getName();
+ if (!isset(self::$_propertyMapCache->classMaps[$thisClassName])) {
+ try {
+ PropertyMapCompiler::compileClassPropertyMap( // Throws.
+ self::$_propertyMapCache,
+ $thisClassName
+ );
+ } catch (Exception $e) {
+ fwrite(STDERR, '> Unable to compile the class property map for "'.$thisClassName.'". Reason: '.$e->getMessage().PHP_EOL);
+
+ return false;
+ }
+ }
+
+ // Now link to the property map cache for our current class.
+ $this->_compiledPropertyMapLink = &self::$_propertyMapCache->classMaps[$thisClassName];
+
+ // Get the current class comment (string if ok, FALSE if none exists).
+ $currentDocComment = $this->_reflector->getDocComment();
+ if (is_string($currentDocComment)) {
+ $currentDocComment = trim($currentDocComment);
+ }
+
+ // Extract all relevant lines from the current comment.
+ $finalDocLines = $this->_extractRelevantLines($currentDocComment);
+
+ // Generate the automatic summary line (classname followed by period).
+ $autoSummaryLine = $this->_reflector->getShortName().'.';
+
+ // If the 1st line is a classname followed by a period, update the name.
+ // NOTE: This ensures that we update all outdated auto-added classnames,
+ // and the risk of false positives is very low since we only document
+ // `LazyJsonMapper`-based classes with a `OneWord.`-style summary line.
+ // NOTE: Regex is from http://php.net/manual/en/language.oop5.basic.php,
+ // and yes we must run it in NON-UNICODE MODE, so that it parses on a
+ // byte by byte basis exactly like the real PHP classname interpreter.
+ if (
+ isset($finalDocLines[0]) // The 1st line MUST exist to proceed.
+ && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\.$/', $finalDocLines[0])
+ ) {
+ $finalDocLines[0] = $autoSummaryLine;
+ }
+
+ // Generate the magic documentation lines for the current class.
+ $magicDocLines = $this->_generateMagicDocs();
+ if (!empty($magicDocLines)) {
+ // If there are no lines already... add the automatic summary line.
+ if (empty($finalDocLines)) {
+ $finalDocLines[] = $autoSummaryLine;
+ }
+
+ // Check the 1st char of the 1st line. If it's an @tag of any kind,
+ // insert automatic summary line at top and empty line after that.
+ elseif ($finalDocLines[0][0] === '@') {
+ array_unshift(
+ $finalDocLines,
+ $autoSummaryLine,
+ ''
+ );
+ }
+
+ $finalDocLines[] = ''; // Add empty line before our magic docs.
+ $finalDocLines = array_merge($finalDocLines, array_values($magicDocLines));
+ }
+ unset($magicDocLines);
+
+ // Generate the final doc-comment that this class is supposed to have.
+ if (!empty($finalDocLines)) {
+ // This will generate even if the class only contained an existing
+ // summary/tags and nothing was added by our magic handler.
+ foreach ($finalDocLines as &$line) {
+ $line = ($line === '' ? ' *' : " * {$line}");
+ }
+ unset($line);
+ $finalDocComment = sprintf(
+ '/**%s%s%s */',
+ $this->_nl,
+ implode($this->_nl, $finalDocLines),
+ $this->_nl
+ );
+ } else {
+ // The FALSE signifies that we want no class doc-block at all...
+ $finalDocComment = false;
+ }
+ unset($finalDocLines);
+
+ // There's nothing to do if the doc-comment is already correct.
+ // NOTE: Both values are FALSE if no doc-comment exists and none wanted.
+ if ($currentDocComment === $finalDocComment) {
+ return true;
+ }
+
+ // The docs mismatch. If this is a validate-run, just return false now.
+ if ($this->_options['validate-only']) {
+ fwrite(STDERR, '> Outdated class docs encountered in "'.$thisClassName.'". Aborting scan...'.PHP_EOL);
+
+ return false;
+ }
+
+ // Load the contents of the file...
+ $classFileName = $this->_reflector->getFileName();
+ $fileLines = @file($classFileName);
+ if ($fileLines === false) {
+ fwrite(STDERR, '> Unable to read class file from disk: "'.$classFileName.'".'.PHP_EOL);
+
+ return false;
+ }
+
+ // Split the file into lines BEFORE the class and lines AFTER the class.
+ $classLine = $this->_reflector->getStartLine();
+ $startLines = array_slice($fileLines, 0, $classLine - 1);
+ $endLines = array_slice($fileLines, $classLine - 1);
+ unset($fileLines);
+
+ // Insert the new class documentation using a very careful algorithm.
+ if ($currentDocComment !== false) {
+ // Since the class already had PHPdoc, remove it and insert new doc.
+ // NOTE: A valid PHPdoc (getDocComment()) always starts with
+ // "/**[whitespace]". If it's just a "/*" or something like
+ // "/**Foo", then it's not detected by getDocComment(). However, the
+ // comment may be several lines above the class. So we'll have to do
+ // an intelligent search to find the old class-comment. As for the
+ // ending tag "*/", PHP doesn't care about whitespace around that.
+ // And it also doesn't let the user escape the "*/", which means
+ // that if we see that sequence we KNOW it's the end of a comment!
+ // NOTE: We'll search for the latest "/**[whitespace]" block and
+ // remove all lines from that until its closest "*/".
+ $deleteFrom = null;
+ $deleteTo = null;
+ for ($i = count($startLines) - 1; $i >= 0; --$i) {
+ if (strpos($startLines[$i], '*/') !== false) {
+ $deleteTo = $i;
+ }
+ if (preg_match('/^\s*\/\*\*\s/u', $startLines[$i])) {
+ $deleteFrom = $i;
+ break;
+ }
+ }
+
+ // Ensure that we have found valid comment-offsets.
+ if ($deleteFrom === null || $deleteTo === null || $deleteTo < $deleteFrom) {
+ fwrite(STDERR, '> Unable to parse current class comment on disk: "'.$classFileName.'".'.PHP_EOL);
+
+ return false;
+ }
+
+ // Now update the startLines array to replace the doc-comment...
+ foreach ($startLines as $k => $v) {
+ if ($k === $deleteFrom && $finalDocComment !== false) {
+ // We've found the first line of the old comment, and we
+ // have a new comment. So replace that array entry.
+ $startLines[$k] = $finalDocComment.$this->_nl;
+ } elseif ($k >= $deleteFrom && $k <= $deleteTo) {
+ // Delete all other comment lines, including the first line
+ // if we had no new doc-comment.
+ unset($startLines[$k]);
+ }
+
+ // Break if we've reached the final line to delete.
+ if ($k >= $deleteTo) {
+ break;
+ }
+ }
+ } elseif ($finalDocComment !== false) {
+ // There's no existing doc-comment. Just add ours above the class.
+ // NOTE: This only does something if we had a new comment to insert,
+ // which we SHOULD have since we came this far in this scenario...
+ $startLines[] = $finalDocComment.$this->_nl;
+ }
+
+ // Generate the new file contents.
+ $newFileContent = implode($startLines).implode($endLines);
+ unset($startLines);
+ unset($endLines);
+
+ // Perform an atomic file-write to disk, which ensures that we will
+ // never be able to corrupt the class-files on disk via partial writes.
+ $written = Utilities::atomicWrite($classFileName, $newFileContent);
+ if ($written !== false) {
+ echo '> Wrote updated class documentation to disk: "'.$classFileName.'".'.PHP_EOL;
+
+ return true;
+ } else {
+ fwrite(STDERR, '> Unable to write new class documentation to disk: "'.$classFileName.'".'.PHP_EOL);
+
+ return false;
+ }
+ }
+
+ /**
+ * Extracts all relevant lines from a doc-comment.
+ *
+ * @param string $currentDocComment
+ *
+ * @return array
+ */
+ private function _extractRelevantLines(
+ $currentDocComment)
+ {
+ if (!is_string($currentDocComment)) {
+ return [];
+ }
+
+ // Remove the leading and trailing doc-comment tags (/** and */).
+ $currentDocComment = preg_replace('/(^\s*\/\*\*\s*|\s*\*\/$)/u', '', $currentDocComment);
+
+ // Process all lines. Skip all @method and @property lines.
+ $relevantLines = [];
+ $lines = preg_split('/\r?\n|\r/u', $currentDocComment);
+ foreach ($lines as $line) {
+ // Remove leading and trailing whitespace, and leading asterisks.
+ $line = trim(preg_replace('/^\s*\*+/u', '', $line));
+
+ // Skip this line if it's a @method or @property line.
+ // NOTE: Removing them is totally safe, because the LazyJsonMapper
+ // class has marked all of its magic property/function handlers as
+ // final, which means that people's subclasses CANNOT override them
+ // to add their own magic methods/properties. So therefore we KNOW
+ // that ALL existing @method/@property class doc lines belong to us!
+ if (preg_match('/^@(?:method|property)/u', $line)) {
+ continue;
+ }
+
+ $relevantLines[] = $line;
+ }
+
+ // Remove trailing empty lines from the relevant lines.
+ for ($i = count($relevantLines) - 1; $i >= 0; --$i) {
+ if ($relevantLines[$i] === '') {
+ unset($relevantLines[$i]);
+ } else {
+ break;
+ }
+ }
+
+ // Remove leading empty lines from the relevant lines.
+ foreach ($relevantLines as $k => $v) {
+ if ($v !== '') {
+ break;
+ }
+
+ unset($relevantLines[$k]);
+ }
+
+ // Return a re-indexed (properly 0-indexed) array.
+ return array_values($relevantLines);
+ }
+
+ /**
+ * Generate PHPdoc lines for all magic properties and functions.
+ *
+ * @throws ReflectionException
+ * @throws LazyJsonMapperException
+ *
+ * @return array
+ */
+ private function _generateMagicDocs()
+ {
+ // Check whether we should (and can) document properties and functions.
+ $documentProperties = $this->_options['properties'] && $this->_reflector->getConstant('ALLOW_VIRTUAL_PROPERTIES');
+ $documentFunctions = $this->_options['functions'] && $this->_reflector->getConstant('ALLOW_VIRTUAL_FUNCTIONS');
+ if (!$documentProperties && !$documentFunctions) {
+ return [];
+ }
+
+ // Export all JSON properties, with RELATIVE class-paths when possible.
+ // NOTE: We will document ALL properties. Even ones inherited from
+ // parents/imported maps. This ensures that users who are manually
+ // reading the source code can see EVERYTHING without needing an IDE.
+ $properties = [];
+ $ownerClassName = $this->_reflector->getName();
+ foreach ($this->_compiledPropertyMapLink as $propName => $propDef) {
+ $properties[$propName] = new PropertyDescription( // Throws.
+ $ownerClassName,
+ $propName,
+ $propDef,
+ true // Use relative class-paths when possible.
+ );
+ }
+
+ // Build the magic documentation...
+ $magicDocLines = [];
+ foreach (['functions', 'properties'] as $docType) {
+ if (($docType === 'functions' && !$documentFunctions)
+ || ($docType === 'properties' && !$documentProperties)) {
+ continue;
+ }
+
+ // Generate all lines for the current magic tag type...
+ $lineStorage = [];
+ foreach ($properties as $property) {
+ if ($docType === 'functions') {
+ // We will only document useful functions (not the "has",
+ // since those are useless for properties that are fully
+ // defined in the class map).
+ foreach (['get', 'set', 'is', 'unset'] as $funcType) {
+ // Generate the function name, ie "getSomething", and
+ // skip this function if it's already defined as a REAL
+ // (overridden) function in this class or its parents.
+ $functionName = $funcType.$property->func_case;
+ if (!$this->_options['document-overridden'] && $this->_reflector->hasMethod($functionName)) {
+ continue;
+ }
+
+ // Alright, the function doesn't exist as a real class
+ // function, or the user wants to document it anyway...
+ // Document it via its calculated signature.
+ // NOTE: Classtypes use paths relative to current class!
+ $functionSignature = $property->{'function_'.$funcType};
+ $lineStorage[$functionName] = sprintf('@method %s', $functionSignature);
+ }
+ } elseif ($docType === 'properties') {
+ // Skip this property if it's already defined as a REAL
+ // (overridden) property in this class or its parents.
+ if (!$this->_options['document-overridden'] && $this->_reflector->hasProperty($property->name)) {
+ continue;
+ }
+
+ // Alright, the property doesn't exist as a real class
+ // property, or the user wants to document it anyway...
+ // Document it via its calculated signature.
+ // NOTE: Classtypes use paths relative to current class!
+ $lineStorage[$property->name] = sprintf(
+ '@property %s $%s',
+ $property->type,
+ $property->name
+ );
+ }
+ }
+
+ // Skip this tag type if there was nothing to document...
+ if (empty($lineStorage)) {
+ continue;
+ }
+
+ // Insert empty line separators between different magic tag types.
+ if (!empty($magicDocLines)) {
+ $magicDocLines[] = '';
+ }
+
+ // Reorder lines by name and add them to the magic doc lines.
+ // NOTE: We use case sensitivity so that "getComments" and
+ // "getCommentThreads" etc aren't placed next to each other.
+ ksort($lineStorage, SORT_NATURAL); // Case-sensitive natural order.
+ $magicDocLines = array_merge($magicDocLines, array_values($lineStorage));
+ }
+
+ return $magicDocLines;
+ }
+}
+
+// Now process all PHP files under all of the project's namespace folders.
+foreach ($namespaces as $realpath => $namespace) {
+ echo PHP_EOL.'Processing namespace "'.$namespace.'".'.PHP_EOL.'- Path: "'.$realpath.'".'.PHP_EOL;
+ $realpathlen = strlen($realpath);
+
+ $iterator = new RegexIterator(
+ new RecursiveIteratorIterator(new RecursiveDirectoryIterator($realpath)),
+ '/\.php$/i', RecursiveRegexIterator::GET_MATCH
+ );
+ foreach ($iterator as $file => $ext) {
+ // Determine the real path to the file (compatible with $realpath).
+ $realfile = realpath($file);
+ if ($realfile === false) {
+ fwrite(STDERR, 'Unable to determine real path to file "'.$file.'".'.PHP_EOL);
+ exit(1);
+ }
+
+ // Now ensure that the file starts with the expected path...
+ if (strncmp($realpath, $realfile, $realpathlen) !== 0) {
+ fwrite(STDERR, 'Unexpected path to file "'.$realfile.'". Does not match project path.'.PHP_EOL);
+ exit(1);
+ }
+ $class = substr($realfile, $realpathlen);
+
+ // Remove the leading slash for the folder...
+ if ($class[0] !== '/' && $class[0] !== '\\') {
+ fwrite(STDERR, 'Unexpected path to file "'.$realfile.'". Does not match project path.'.PHP_EOL);
+ exit(1);
+ }
+ $class = substr($class, 1);
+
+ // And now just generate the final class name...
+ $class = sprintf(
+ '%s%s',
+ $namespace,
+ str_replace('/', '\\', preg_replace('/\.php$/ui', '', $class))
+ );
+
+ // Some files may not contain classes. For example, some people have
+ // functions.php files with functions, etc. So before we proceed, just
+ // ensure that the generated class name actually exists.
+ // NOTE: class_exists() ignores interfaces. Only finds classes. Good.
+ if (!class_exists($class, true)) { // TRUE = Autoload.
+ continue;
+ }
+
+ // Now process the current class.
+ $documentor = new LazyClassDocumentor($class, $options);
+ $result = $documentor->process();
+ if (!$result) {
+ if ($options['validate-only']) {
+ fwrite(STDERR, '> One or more files need updated class documentation or contain other errors.'.PHP_EOL);
+ } else {
+ fwrite(STDERR, '> Error while processing class "'.$class.'". Aborting...'.PHP_EOL);
+ }
+ exit(1);
+ }
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/composer.json b/vendor/lazyjsonmapper/lazyjsonmapper/composer.json
new file mode 100755
index 0000000..3b21d54
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/composer.json
@@ -0,0 +1,48 @@
+{
+ "name": "lazyjsonmapper/lazyjsonmapper",
+ "type": "library",
+ "description": "Advanced, intelligent & automatic object-oriented JSON containers for PHP.",
+ "keywords": ["json", "development"],
+ "homepage": "https://github.com/SteveJobzniak/LazyJsonMapper",
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "SteveJobzniak",
+ "homepage": "https://github.com/SteveJobzniak",
+ "role": "Developer"
+ }
+ ],
+ "require": {
+ "php": ">=5.6",
+ "corneltek/getoptionkit": "2.*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "6.*",
+ "friendsofphp/php-cs-fixer": "^2.7.1"
+ },
+ "bin": [
+ "bin/lazydoctor"
+ ],
+ "autoload": {
+ "psr-4": {
+ "LazyJsonMapper\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "LazyJsonMapper\\Tests\\": "tests/"
+ }
+ },
+ "scripts": {
+ "codestyle": [
+ "php-cs-fixer fix --config=.php_cs.dist --allow-risky yes",
+ "php devtools/checkStyle.php x"
+ ],
+ "generatedocs": [
+ "rm -rf docs/output/ && phpdoc"
+ ],
+ "generatefreshdocs": [
+ "rm -rf docs/ && phpdoc"
+ ]
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/devtools/checkStyle.php b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/checkStyle.php
new file mode 100755
index 0000000..5a0fc6c
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/checkStyle.php
@@ -0,0 +1,225 @@
+run();
+if (!empty($badFiles)) {
+ // Exit with non-zero code to signal that there are problems.
+ exit(1);
+}
+
+/**
+ * Code style checker.
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class styleChecker
+{
+ /**
+ * @var string
+ */
+ private $_baseDir;
+
+ /**
+ * @var string[]
+ */
+ private $_inspectFolders;
+
+ /**
+ * @var bool
+ */
+ private $_onlyShowInvalidFiles;
+
+ /**
+ * Constructor.
+ *
+ * @param string $baseDir
+ * @param string[] $inspectFolders
+ * @param bool $onlyShowInvalidFiles
+ */
+ public function __construct(
+ $baseDir,
+ array $inspectFolders,
+ $onlyShowInvalidFiles)
+ {
+ $this->_baseDir = realpath($baseDir);
+ if ($this->_baseDir === false) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid path.', $baseDir));
+ }
+ $this->_inspectFolders = $inspectFolders;
+ $this->_onlyShowInvalidFiles = $onlyShowInvalidFiles;
+ }
+
+ /**
+ * Process single file.
+ *
+ * @param string $filePath
+ *
+ * @return bool TRUE if the file has codestyle problems, otherwise FALSE.
+ */
+ private function _processFile(
+ $filePath)
+ {
+ $hasProblems = false;
+ $hasVisibilityProblems = false;
+ $fileName = basename($filePath);
+ $inputLines = @file($filePath);
+ $outputLines = [];
+
+ if ($inputLines === false) {
+ // We were unable to read the input file. Ignore if broken symlink.
+ if (is_link($filePath)) {
+ return false; // File is okay, since the symlink is invalid.
+ } else {
+ echo "- {$filePath}: UNABLE TO READ FILE!".PHP_EOL;
+
+ return true; // File has problems...
+ }
+ }
+
+ foreach ($inputLines as $line) {
+ // Function arguments on separate lines.
+ if (preg_match('/^(.*?(?:(?:final|static)\s+)*(?:public|private|protected)(?:\s+(?:final|static))*\s+function\s+.+?)\((.+)\)(.*)$/', $line, $matches)) {
+ $hasProblems = true;
+
+ $funcstart = $matches[1];
+ $params = $matches[2];
+ $trail = $matches[3];
+ $params = explode(', ', $params);
+
+ $outputLines[] = $funcstart.'('.PHP_EOL;
+ for ($i = 0, $len = count($params); $i < $len; ++$i) {
+ $newline = ' '.$params[$i];
+ if ($i == ($len - 1)) {
+ $newline .= ')'.PHP_EOL;
+ } else {
+ $newline .= ','.PHP_EOL;
+ }
+ $outputLines[] = $newline;
+ }
+ // } else {
+ // $outputLines[] = $line;
+ }
+
+ // Appropriate public, private and protected member prefixes.
+ if (preg_match('/^\s*(?:(?:final|static)\s+)*(public|private|protected)(?:\s+(?:final|static))*\s+(function|\$)\s*&?([^;\(\s]+)/', $line, $matches)) {
+ $visibility = &$matches[1]; // public, private, protected
+ $type = &$matches[2]; // $, function
+ $name = &$matches[3]; // Member name
+
+ if ($visibility == 'public') {
+ if ($name[0] == '_' && (
+ $name != '__construct'
+ && $name != '__destruct'
+ && $name != '__call'
+ && $name != '__get'
+ && $name != '__set'
+ && $name != '__isset'
+ && $name != '__unset'
+ && $name != '__invoke'
+ && $name != '__toString'
+ )) {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PUBLIC NAME:".trim($matches[0]).PHP_EOL;
+ }
+ } else { // private, protected
+ if ($name[0] != '_') {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PRIVATE/PROTECTED NAME:".trim($matches[0]).PHP_EOL;
+ }
+ }
+ }
+ }
+
+ $newFile = implode('', $outputLines);
+ if (!$hasProblems) {
+ if (!$this->_onlyShowInvalidFiles) {
+ echo " {$filePath}: Already formatted correctly.\n";
+ }
+ } elseif (!$hasVisibilityProblems) {
+ // Has problems, but no visibility problems. Output fixed file.
+ echo "- {$filePath}: Has function parameter problems:\n";
+ echo $newFile;
+ } else {
+ echo "- {$filePath}: Had member visibility problems.\n";
+ }
+
+ return $hasProblems;
+ }
+
+ /**
+ * Process all *.php files in given path.
+ *
+ * @return string[] An array with all files that have codestyle problems.
+ */
+ public function run()
+ {
+ $filesWithProblems = [];
+ foreach ($this->_inspectFolders as $inspectFolder) {
+ $directoryIterator = new RecursiveDirectoryIterator($this->_baseDir.'/'.$inspectFolder);
+ $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
+ $phpIterator = new RegexIterator($recursiveIterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
+
+ foreach ($phpIterator as $filePath => $dummy) {
+ $hasProblems = $this->_processFile($filePath);
+ if ($hasProblems) {
+ $filesWithProblems[] = $filePath;
+ }
+ }
+ }
+
+ return $filesWithProblems;
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/devtools/funcListData.serialized b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/funcListData.serialized
new file mode 100755
index 0000000..589e5be
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/funcListData.serialized
@@ -0,0 +1 @@
+a:10000:{i:0;s:3:"Pol";i:1;s:3:"Unp";i:2;s:8:"DeDiveSt";i:3;s:2:"Un";i:4;s:5:"Oyste";i:5;s:5:"Postm";i:6;s:2:"Sa";i:7;s:10:"SorroWaddl";i:8;s:6:"ApoCys";i:9;s:10:"GenRevisTe";i:10;s:11:"FarcGenoGoy";i:11;s:3:"Cro";i:12;s:12:"BasDefoHorse";i:13;s:6:"PsycSu";i:14;s:8:"DemPresa";i:15;s:4:"PeSc";i:16;s:2:"Va";i:17;s:6:"AnsKaz";i:18;s:3:"Ove";i:19;s:10:"HanovRaoul";i:20;s:3:"Twi";i:21;s:13:"NonsTortWhapu";i:22;s:5:"Areco";i:23;s:13:"BacilMaladUna";i:24;s:5:"Unflu";i:25;s:13:"CheyHarmSands";i:26;s:11:"OtheOvUltra";i:27;s:2:"Lo";i:28;s:4:"Shos";i:29;s:5:"Scler";i:30;s:5:"Predi";i:31;s:4:"Unre";i:32;s:4:"Punj";i:33;s:9:"HosanUnig";i:34;s:7:"BetaWis";i:35;s:13:"ShalVerteWasp";i:36;s:7:"InhSuWi";i:37;s:5:"Pluto";i:38;s:2:"Ph";i:39;s:9:"IrPhResur";i:40;s:10:"UnconVisco";i:41;s:12:"DrMessaSizzl";i:42;s:6:"LaPrea";i:43;s:2:"Zo";i:44;s:4:"Suck";i:45;s:5:"Monop";i:46;s:5:"Pinci";i:47;s:8:"PatroPha";i:48;s:9:"HogPicrTh";i:49;s:4:"Picn";i:50;s:8:"DisasGib";i:51;s:5:"Popul";i:52;s:10:"ExordPseSh";i:53;s:4:"Unpo";i:54;s:8:"HabiTaci";i:55;s:11:"SalsSanStep";i:56;s:11:"ProaeRepUnd";i:57;s:8:"InflLaSu";i:58;s:6:"ReTens";i:59;s:7:"CarnaHy";i:60;s:3:"Tur";i:61;s:6:"NympRe";i:62;s:5:"PacUp";i:63;s:12:"SpangWritZul";i:64;s:5:"LonPa";i:65;s:9:"SanxScrew";i:66;s:4:"KePo";i:67;s:3:"Eno";i:68;s:4:"Rich";i:69;s:9:"PlushThUn";i:70;s:4:"Seid";i:71;s:4:"GaPi";i:72;s:8:"SupeUpwh";i:73;s:4:"Sest";i:74;s:9:"ResoScler";i:75;s:5:"QuRep";i:76;s:8:"SquSuspe";i:77;s:12:"ExtRefunUnwi";i:78;s:4:"FlUn";i:79;s:6:"RenSar";i:80;s:8:"PierrRet";i:81;s:7:"HarNona";i:82;s:8:"PhanPian";i:83;s:11:"MiasPoachPs";i:84;s:5:"Refor";i:85;s:5:"PrUnf";i:86;s:11:"HedeProarRe";i:87;s:13:"SpieTurnUnesc";i:88;s:13:"EmboOgallSpin";i:89;s:11:"MelanUnstWi";i:90;s:10:"FroPutatUn";i:91;s:4:"PaVa";i:92;s:8:"TanisYot";i:93;s:12:"ConDaddFulgu";i:94;s:2:"Pl";i:95;s:11:"HaKerauProt";i:96;s:12:"DecoHinWhigg";i:97;s:4:"OvTh";i:98;s:5:"Gunat";i:99;s:3:"Pot";i:100;s:5:"Virgi";i:101;s:10:"CopoDiGast";i:102;s:10:"DefHexitUn";i:103;s:14:"InertLeckeNaut";i:104;s:13:"IodomUnreeYac";i:105;s:3:"Tul";i:106;s:9:"BailaPrZe";i:107;s:12:"LaminRecoSpi";i:108;s:6:"CoPoly";i:109;s:5:"Irida";i:110;s:3:"Syl";i:111;s:4:"SuVe";i:112;s:13:"GravMiswiTher";i:113;s:4:"Shem";i:114;s:6:"SolToo";i:115;s:5:"Unsta";i:116;s:10:"ForeReTicx";i:117;s:5:"Outra";i:118;s:12:"InteRoripSph";i:119;s:8:"FroTapst";i:120;s:13:"DislMultWhore";i:121;s:9:"CirInPili";i:122;s:3:"Woo";i:123;s:12:"MustePreceRa";i:124;s:12:"PeavPolRootl";i:125;s:6:"KnopSc";i:126;s:10:"TaTormiTub";i:127;s:8:"OvPrefSu";i:128;s:9:"MetTymVen";i:129;s:6:"PreTri";i:130;s:9:"NemSatUni";i:131;s:4:"DiTe";i:132;s:7:"ManSuUn";i:133;s:10:"CoaxFurRes";i:134;s:3:"Yua";i:135;s:4:"Pygo";i:136;s:4:"Indi";i:137;s:4:"Spon";i:138;s:10:"CrumePurch";i:139;s:8:"HemTerri";i:140;s:5:"ProRe";i:141;s:10:"DispuRoyal";i:142;s:6:"SympUn";i:143;s:4:"Nons";i:144;s:5:"Trime";i:145;s:3:"Non";i:146;s:8:"ReproUnd";i:147;s:10:"ExaPhlPrec";i:148;s:4:"Prot";i:149;s:8:"CurasDev";i:150;s:9:"EchiPlica";i:151;s:11:"FairOrbiUna";i:152;s:15:"GriffPutorRepro";i:153;s:11:"CalmOtolSpo";i:154;s:8:"FigPurUn";i:155;s:9:"GlIgInhum";i:156;s:2:"Tr";i:157;s:8:"HauMetaz";i:158;s:2:"On";i:159;s:11:"PluSnapWoor";i:160;s:11:"EuShosUnbre";i:161;s:15:"FilteRocheSemim";i:162;s:9:"ReviRoUns";i:163;s:2:"Ov";i:164;s:10:"HydrJesUnd";i:165;s:10:"SipSortTur";i:166;s:5:"LixSe";i:167;s:5:"NuUra";i:168;s:8:"MelWhisp";i:169;s:9:"DiJumpyTr";i:170;s:10:"LaMurUpyok";i:171;s:3:"Pty";i:172;s:4:"Ithy";i:173;s:4:"Bloo";i:174;s:4:"UnUn";i:175;s:5:"EsSwa";i:176;s:9:"NeobOvPar";i:177;s:5:"DemLa";i:178;s:8:"NonvoUnm";i:179;s:9:"MutiRimux";i:180;s:2:"Pr";i:181;s:11:"UninVillaWh";i:182;s:3:"Unl";i:183;s:10:"ProscSownx";i:184;s:11:"HiRescUnint";i:185;s:3:"Cau";i:186;s:13:"HittoHustlSub";i:187;s:8:"JeremSer";i:188;s:10:"DeMetagPet";i:189;s:8:"MisexRig";i:190;s:10:"ChildCogRe";i:191;s:11:"HyPennoRech";i:192;s:3:"Lig";i:193;s:7:"OuUnmem";i:194;s:2:"Re";i:195;s:4:"VoWa";i:196;s:8:"UnicUnju";i:197;s:5:"SecSn";i:198;s:9:"NumSaiUnc";i:199;s:13:"DoubtHeteUnse";i:200;s:4:"Smee";i:201;s:4:"PrTh";i:202;s:7:"MoVindh";i:203;s:8:"GasZodia";i:204;s:2:"Do";i:205;s:12:"UllUnfatWord";i:206;s:7:"IconNov";i:207;s:4:"Juda";i:208;s:9:"GrReptSei";i:209;s:14:"OutpPenelPolan";i:210;s:4:"Poly";i:211;s:2:"No";i:212;s:11:"SchiUnaVera";i:213;s:4:"Ther";i:214;s:9:"EbFlooPes";i:215;s:6:"MoOchi";i:216;s:5:"Triol";i:217;s:2:"Ra";i:218;s:5:"ProTe";i:219;s:10:"EuryoGrade";i:220;s:12:"EntOligPulpy";i:221;s:8:"RepRepSa";i:222;s:7:"NeskPap";i:223;s:3:"Unf";i:224;s:9:"FreMeUntr";i:225;s:7:"PeriUlt";i:226;s:5:"Varyi";i:227;s:8:"CorrPhTo";i:228;s:8:"ProfeSmo";i:229;s:6:"SuUncr";i:230;s:4:"Rurb";i:231;s:3:"Lan";i:232;s:9:"HearSuper";i:233;s:10:"AngulTsuga";i:234;s:10:"PertUnhYer";i:235;s:11:"GrandNitNoc";i:236;s:2:"Le";i:237;s:11:"HypeOverWei";i:238;s:11:"MentSoSumle";i:239;s:6:"LaShip";i:240;s:9:"PrenUnawa";i:241;s:13:"ExceKotaPonti";i:242;s:7:"HaMelod";i:243;s:6:"PaTyUn";i:244;s:2:"Fo";i:245;s:5:"Verse";i:246;s:10:"OverProTuk";i:247;s:9:"SubvTitan";i:248;s:10:"PondeSight";i:249;s:4:"Thar";i:250;s:7:"InRedho";i:251;s:10:"ReSynuThou";i:252;s:5:"Unpaw";i:253;s:2:"We";i:254;s:4:"Peco";i:255;s:8:"PlacaRoz";i:256;s:9:"RecoUnfre";i:257;s:9:"GyHaloHyp";i:258;s:3:"Lit";i:259;s:12:"NapOverUnfor";i:260;s:13:"CrosIntraNumi";i:261;s:11:"CreHandeSup";i:262;s:2:"Sc";i:263;s:10:"MucOzVerte";i:264;s:4:"Prim";i:265;s:9:"HomMonPhi";i:266;s:5:"Unene";i:267;s:2:"Sl";i:268;s:6:"LimRec";i:269;s:5:"Trebl";i:270;s:13:"ForehNetmResp";i:271;s:9:"NonprUnbe";i:272;s:9:"CarbuRaRe";i:273;s:10:"LihyOverSt";i:274;s:10:"CrotaPurpu";i:275;s:4:"Inel";i:276;s:7:"CenMast";i:277;s:9:"SlaxTaTit";i:278;s:11:"SuTrimUncha";i:279;s:2:"Dw";i:280;s:7:"EcaudSy";i:281;s:11:"FormHeteVog";i:282;s:4:"Stra";i:283;s:13:"IncuLushePelv";i:284;s:13:"LuthUnmaWiste";i:285;s:8:"ApnSivve";i:286;s:7:"CouUnme";i:287;s:7:"PsTrepa";i:288;s:4:"Wiss";i:289;s:10:"BorHomelRi";i:290;s:7:"ThUtrVe";i:291;s:2:"Te";i:292;s:8:"SaTeaUnd";i:293;s:6:"CoHuSo";i:294;s:2:"Th";i:295;s:9:"FlObSubco";i:296;s:4:"FlPa";i:297;s:9:"OverSeagh";i:298;s:7:"LympYaw";i:299;s:7:"IrreJac";i:300;s:10:"TholUnhUnw";i:301;s:4:"DeEl";i:302;s:9:"MervePrTi";i:303;s:8:"NatPrefa";i:304;s:7:"HumInco";i:305;s:7:"RiRibby";i:306;s:8:"BalosPer";i:307;s:8:"BepluDet";i:308;s:8:"EulogRed";i:309;s:8:"HydroVar";i:310;s:6:"OvePse";i:311;s:4:"FuPa";i:312;s:2:"Da";i:313;s:6:"MaSeSh";i:314;s:7:"PanisPr";i:315;s:4:"Pudi";i:316;s:2:"Su";i:317;s:7:"HyMeSqu";i:318;s:7:"MacPlPu";i:319;s:5:"PtiWo";i:320;s:2:"Ja";i:321;s:9:"FoShutTar";i:322;s:15:"HypoiRoughTrilo";i:323;s:10:"MePhylProc";i:324;s:13:"ExpIncreSugge";i:325;s:7:"PhyllTh";i:326;s:4:"Knoc";i:327;s:12:"CrTilloUnder";i:328;s:5:"CaPen";i:329;s:8:"ItaRequi";i:330;s:10:"NitReaTrop";i:331;s:4:"Kelp";i:332;s:7:"TelepVa";i:333;s:10:"MultiTriph";i:334;s:4:"Synu";i:335;s:13:"PremRobenUnre";i:336;s:6:"OveUnp";i:337;s:5:"DraSu";i:338;s:8:"FluProar";i:339;s:5:"UnjWi";i:340;s:4:"Tume";i:341;s:5:"ApEat";i:342;s:12:"BerEsopHippo";i:343;s:4:"Weig";i:344;s:7:"GloSupe";i:345;s:5:"Morib";i:346;s:7:"HaOzoRu";i:347;s:5:"EfMys";i:348;s:7:"BuoSpot";i:349;s:5:"Unhop";i:350;s:8:"IntNaiXa";i:351;s:8:"ReSerpVi";i:352;s:7:"RaUnVer";i:353;s:6:"CuEndl";i:354;s:8:"PopiWood";i:355;s:8:"LionPrSa";i:356;s:10:"SuppUneWre";i:357;s:5:"Vaing";i:358;s:4:"Pale";i:359;s:7:"OveSpha";i:360;s:7:"MiRossx";i:361;s:4:"High";i:362;s:9:"PlaWhZama";i:363;s:11:"GalacMicPan";i:364;s:5:"Tonop";i:365;s:4:"Hera";i:366;s:8:"DermPrUn";i:367;s:5:"Branc";i:368;s:10:"StchUpVulg";i:369;s:12:"OverPecSerol";i:370;s:5:"QuaRe";i:371;s:13:"SubiSyllaUltr";i:372;s:3:"Tar";i:373;s:8:"RigwiSqu";i:374;s:4:"Wabb";i:375;s:12:"LotifSaZooto";i:376;s:5:"Strat";i:377;s:7:"DisDiLa";i:378;s:3:"Una";i:379;s:10:"LandfMesSu";i:380;s:7:"PeavRol";i:381;s:13:"GrudgPerTeuto";i:382;s:12:"ChiCrypUnflo";i:383;s:5:"Front";i:384;s:4:"Erud";i:385;s:7:"PenSqua";i:386;s:11:"BeyonCheMal";i:387;s:3:"Sli";i:388;s:4:"Uter";i:389;s:8:"CloaPenc";i:390;s:8:"PoShiUnp";i:391;s:5:"Deami";i:392;s:9:"LeucMelli";i:393;s:6:"PresQu";i:394;s:6:"HepUnr";i:395;s:9:"VioloVisc";i:396;s:10:"SeTolyWate";i:397;s:5:"Lestr";i:398;s:4:"Wood";i:399;s:10:"IntOddmeTr";i:400;s:10:"PerioRotul";i:401;s:4:"Stol";i:402;s:11:"EntomSouSyn";i:403;s:4:"Semi";i:404;s:6:"DurrUn";i:405;s:7:"DiscoOv";i:406;s:9:"PangiProl";i:407;s:9:"NapRecSqu";i:408;s:3:"Unr";i:409;s:13:"GamesTetanUns";i:410;s:8:"FalMetRa";i:411;s:14:"BataDenouSaxon";i:412;s:5:"Solvo";i:413;s:14:"ScytaTighUnjoy";i:414;s:4:"Glut";i:415;s:4:"EkFi";i:416;s:6:"ErNoPa";i:417;s:6:"LaSpal";i:418;s:11:"JoRackRepat";i:419;s:10:"ReSorreUnd";i:420;s:12:"MisenRiTextu";i:421;s:4:"Wayb";i:422;s:6:"CampQu";i:423;s:11:"BrNapolSulp";i:424;s:4:"Snow";i:425;s:2:"Mo";i:426;s:5:"Tehua";i:427;s:13:"PastPressUnwi";i:428;s:5:"Entom";i:429;s:4:"SoTr";i:430;s:4:"Soft";i:431;s:10:"ChElephUnd";i:432;s:3:"Whi";i:433;s:6:"UpkZoo";i:434;s:5:"PaPre";i:435;s:7:"LobxVal";i:436;s:2:"Di";i:437;s:7:"NarNonp";i:438;s:9:"HorUnexVe";i:439;s:5:"Summe";i:440;s:10:"CouLocoPli";i:441;s:11:"ReSnortUnas";i:442;s:7:"HeKnPre";i:443;s:5:"Shavi";i:444;s:2:"Fi";i:445;s:7:"KiOstra";i:446;s:6:"HeliJu";i:447;s:7:"MasdeNo";i:448;s:5:"Tapst";i:449;s:5:"NazVe";i:450;s:4:"Sque";i:451;s:6:"FrIcht";i:452;s:3:"Hyd";i:453;s:8:"InirLevi";i:454;s:7:"ElasIna";i:455;s:3:"Inc";i:456;s:8:"UnfonUns";i:457;s:10:"PeuceProma";i:458;s:10:"ParomPeSom";i:459;s:7:"ImpOuUn";i:460;s:7:"PeiseUn";i:461;s:3:"Tri";i:462;s:6:"IntrWa";i:463;s:13:"CapryRemigStr";i:464;s:5:"FulPr";i:465;s:9:"MuncRhomb";i:466;s:14:"LiteNaphtSarco";i:467;s:7:"TelecTr";i:468;s:8:"EngaMePr";i:469;s:5:"Minco";i:470;s:4:"HaMa";i:471;s:5:"Skitt";i:472;s:12:"MidWharWonde";i:473;s:5:"ArSto";i:474;s:5:"OrbPi";i:475;s:5:"Sulfo";i:476;s:11:"PeVoluWhalp";i:477;s:7:"MusToas";i:478;s:3:"Spi";i:479;s:6:"PhilSu";i:480;s:3:"Foc";i:481;s:11:"MansNardxPo";i:482;s:11:"RongaTheUnc";i:483;s:12:"ParsSemiSynt";i:484;s:5:"SuToc";i:485;s:7:"OsiSnib";i:486;s:3:"Dee";i:487;s:5:"Throa";i:488;s:12:"HydroReZealo";i:489;s:9:"MetapUnha";i:490;s:10:"HeHoastPen";i:491;s:3:"Het";i:492;s:4:"CaCo";i:493;s:7:"LimitPr";i:494;s:2:"Ho";i:495;s:8:"RodlVive";i:496;s:8:"UpsWatWe";i:497;s:6:"PerSin";i:498;s:4:"Righ";i:499;s:11:"OstraReUnde";i:500;s:5:"ScSko";i:501;s:3:"End";i:502;s:6:"NimPle";i:503;s:12:"QuadrUndoVet";i:504;s:6:"EkMeMu";i:505;s:12:"DefLacquTrad";i:506;s:7:"PrUndWa";i:507;s:12:"ProtoStroUnf";i:508;s:6:"ReTheo";i:509;s:3:"Pre";i:510;s:6:"OvUnfo";i:511;s:3:"Loi";i:512;s:5:"Cultu";i:513;s:5:"Fabul";i:514;s:4:"Succ";i:515;s:8:"MegaRegr";i:516;s:3:"Rur";i:517;s:3:"Ter";i:518;s:10:"ResisUnire";i:519;s:10:"HeloOveSem";i:520;s:12:"NatrSavoScaw";i:521;s:9:"MusoNeuri";i:522;s:6:"ChClia";i:523;s:5:"ParPs";i:524;s:11:"LabxMetaUnd";i:525;s:10:"SilkStuVux";i:526;s:12:"BroaScoSpouc";i:527;s:6:"PaUnde";i:528;s:10:"HovePanoRe";i:529;s:9:"InsulTric";i:530;s:9:"ExtForUnm";i:531;s:6:"FlatUn";i:532;s:3:"Var";i:533;s:11:"HypoeRevSup";i:534;s:14:"StasSubitUncry";i:535;s:13:"BattoStewSupe";i:536;s:6:"SneThe";i:537;s:6:"ConnSi";i:538;s:2:"Po";i:539;s:7:"BraQuid";i:540;s:11:"StomaUnVana";i:541;s:4:"TrUn";i:542;s:15:"SicyoStinkUnspe";i:543;s:10:"InManOcean";i:544;s:8:"IndLymPr";i:545;s:7:"OsseoVa";i:546;s:10:"HeteHyUnsu";i:547;s:6:"TricUn";i:548;s:7:"PlSeTal";i:549;s:7:"DyEpsom";i:550;s:3:"Stu";i:551;s:3:"Nem";i:552;s:5:"Spina";i:553;s:13:"EusFowlfOleag";i:554;s:8:"HeRadiSt";i:555;s:7:"NonjPar";i:556;s:8:"ChelaDai";i:557;s:10:"CommePeSem";i:558;s:9:"EhxMisOve";i:559;s:6:"FoUret";i:560;s:2:"Pi";i:561;s:4:"Inte";i:562;s:4:"GoUn";i:563;s:7:"PhilaSe";i:564;s:10:"ReaUnprVas";i:565;s:14:"ClapHuzzaWalli";i:566;s:7:"LaconTy";i:567;s:8:"EagThioc";i:568;s:3:"Uns";i:569;s:7:"DingaSe";i:570;s:2:"Ve";i:571;s:11:"ParaPyophTu";i:572;s:10:"KasbaOsteo";i:573;s:4:"Tang";i:574;s:8:"StiStrTo";i:575;s:12:"MiSubdrTurbu";i:576;s:11:"PostPrPreal";i:577;s:9:"EquHypoNi";i:578;s:9:"HiIndZeug";i:579;s:4:"Scat";i:580;s:14:"MesoSloomSpasm";i:581;s:10:"ProcoUnyie";i:582;s:4:"Nonr";i:583;s:6:"CompCo";i:584;s:4:"Kymo";i:585;s:12:"NuanPolygSel";i:586;s:11:"LeafaLegZin";i:587;s:8:"RhomVent";i:588;s:9:"PeteRaSul";i:589;s:12:"DesSemblUret";i:590;s:4:"Zoog";i:591;s:8:"ExamiPre";i:592;s:10:"AmpManoRet";i:593;s:4:"Same";i:594;s:2:"Ty";i:595;s:3:"Spr";i:596;s:4:"Eels";i:597;s:5:"Motor";i:598;s:9:"ScyphUnli";i:599;s:12:"DefGanoUnalo";i:600;s:7:"EpEstRo";i:601;s:4:"Pana";i:602;s:10:"CanaEnlSca";i:603;s:9:"HomRoamRo";i:604;s:7:"HanKeWh";i:605;s:5:"DeIch";i:606;s:8:"CapInSto";i:607;s:7:"PenneVo";i:608;s:11:"ElatTardiVi";i:609;s:7:"ManUnvi";i:610;s:10:"OutbPhPrem";i:611;s:11:"AttrBarDros";i:612;s:8:"InofUrod";i:613;s:3:"Oli";i:614;s:11:"EncImpSchai";i:615;s:4:"Palm";i:616;s:3:"Vit";i:617;s:10:"OphthPyret";i:618;s:2:"So";i:619;s:6:"ExacPr";i:620;s:7:"MonPrTr";i:621;s:12:"MaraMystUnam";i:622;s:4:"Vela";i:623;s:6:"PoSupe";i:624;s:10:"CathCoaThu";i:625;s:5:"ToUpc";i:626;s:3:"Toc";i:627;s:9:"GrePraSli";i:628;s:4:"Sams";i:629;s:8:"ChaHoMon";i:630;s:2:"My";i:631;s:7:"HypItin";i:632;s:11:"GloKoxScrib";i:633;s:9:"SalaUnpro";i:634;s:8:"ImpacSan";i:635;s:3:"Rep";i:636;s:9:"ParaPuggi";i:637;s:8:"RebSheUn";i:638;s:9:"MaObRedis";i:639;s:9:"GuStomTir";i:640;s:4:"Isos";i:641;s:9:"SopoUnUnr";i:642;s:8:"PedroUnd";i:643;s:4:"Ward";i:644;s:3:"Val";i:645;s:4:"Plat";i:646;s:6:"RotUns";i:647;s:7:"PermiRe";i:648;s:6:"ConvSc";i:649;s:4:"Knic";i:650;s:7:"HemImme";i:651;s:10:"HandOutSti";i:652;s:12:"DioEpirrNesi";i:653;s:5:"Yodel";i:654;s:11:"BrangOdUnem";i:655;s:4:"Rebr";i:656;s:3:"Sys";i:657;s:10:"PeRegrTong";i:658;s:8:"GlResiUn";i:659;s:10:"NeapxOsteo";i:660;s:9:"LappPsRev";i:661;s:12:"PrePromTrica";i:662;s:5:"Prota";i:663;s:6:"TablUr";i:664;s:9:"CimbMenta";i:665;s:12:"DesHoaxxUnca";i:666;s:10:"LithoSteat";i:667;s:4:"Goyx";i:668;s:13:"PignoPrehSigx";i:669;s:2:"St";i:670;s:13:"BacDigreMontr";i:671;s:7:"TraUnro";i:672;s:7:"HikRiva";i:673;s:7:"CoPeUnp";i:674;s:5:"Thoro";i:675;s:5:"Vibro";i:676;s:7:"HoRudim";i:677;s:5:"Satis";i:678;s:14:"SulkiSyndTugri";i:679;s:8:"AnEpSius";i:680;s:7:"MyPomWe";i:681;s:11:"PathPepsiPl";i:682;s:7:"PerPlut";i:683;s:8:"MorPrete";i:684;s:8:"PiProUnf";i:685;s:7:"SleUnem";i:686;s:10:"BimPolypVa";i:687;s:6:"DzeThi";i:688;s:6:"DolEmp";i:689;s:5:"Skibb";i:690;s:3:"Pen";i:691;s:12:"SpinSymWaysi";i:692;s:9:"CamHomoTe";i:693;s:7:"HemopRa";i:694;s:13:"ConsParsoReri";i:695;s:7:"HumorKh";i:696;s:3:"Meg";i:697;s:9:"HuckInval";i:698;s:5:"CoSto";i:699;s:5:"Wykeh";i:700;s:8:"PeUnscUn";i:701;s:13:"LaminLeisOvov";i:702;s:4:"Unco";i:703;s:5:"HiUnd";i:704;s:9:"ForMeUnri";i:705;s:6:"RhymUn";i:706;s:8:"UnderUrn";i:707;s:13:"QuadrRuntTort";i:708;s:9:"HemiRepur";i:709;s:9:"EuPiSemin";i:710;s:6:"SubcSu";i:711;s:9:"MorNitcSl";i:712;s:10:"PreteVitel";i:713;s:7:"ReducTr";i:714;s:7:"NonSkew";i:715;s:5:"Rejec";i:716;s:6:"ImNuTe";i:717;s:10:"FlaForeHyp";i:718;s:5:"Stech";i:719;s:11:"CypHasteSpo";i:720;s:5:"Ocell";i:721;s:10:"ObtaiThrim";i:722;s:14:"DihyPhenaSmili";i:723;s:14:"ForwaPeripSius";i:724;s:15:"DropcOlivaPippe";i:725;s:10:"SubbeWhite";i:726;s:5:"Pneum";i:727;s:8:"OpprRaga";i:728;s:5:"DesDi";i:729;s:8:"HelioSup";i:730;s:10:"ParaPiSauc";i:731;s:3:"Sor";i:732;s:6:"SeptSu";i:733;s:5:"DrPot";i:734;s:8:"CoOutWau";i:735;s:6:"CeWold";i:736;s:8:"VivenWhi";i:737;s:10:"BandiQuadr";i:738;s:13:"MesiPlanSunni";i:739;s:3:"Urs";i:740;s:10:"KiUnchUnes";i:741;s:12:"MynaxSeTroch";i:742;s:4:"Shut";i:743;s:7:"ObSpurg";i:744;s:3:"Boy";i:745;s:10:"GuitaUnsub";i:746;s:4:"GrPr";i:747;s:11:"MonStrumSus";i:748;s:9:"DiSenTurr";i:749;s:4:"Unop";i:750;s:10:"CansoPrion";i:751;s:12:"HieMesaParoa";i:752;s:3:"Sil";i:753;s:8:"OpPoScio";i:754;s:9:"TrumpTwUn";i:755;s:14:"DoomsEtymoUnch";i:756;s:5:"HypUn";i:757;s:11:"InterMetOoz";i:758;s:9:"NondOcSta";i:759;s:9:"OinOtorRe";i:760;s:11:"IndiJapaUnd";i:761;s:8:"SnufUnsc";i:762;s:2:"Gr";i:763;s:10:"OpePupilUn";i:764;s:12:"IrreLiqueTra";i:765;s:5:"Uncol";i:766;s:4:"MeVe";i:767;s:6:"BlepVa";i:768;s:8:"PreSubva";i:769;s:6:"MaPrep";i:770;s:7:"PuUnnat";i:771;s:6:"JuriKh";i:772;s:9:"CoprGastr";i:773;s:9:"EuergOvSi";i:774;s:5:"Hyper";i:775;s:9:"StoSulpUn";i:776;s:5:"Unfop";i:777;s:4:"Retr";i:778;s:8:"BunchPos";i:779;s:5:"NorSu";i:780;s:12:"HordRunnUnea";i:781;s:7:"InterLi";i:782;s:11:"EnvOdonStew";i:783;s:10:"DiaMesNonc";i:784;s:8:"HematThe";i:785;s:3:"Ski";i:786;s:5:"Kodak";i:787;s:9:"DoRedouRe";i:788;s:2:"Sp";i:789;s:5:"Unfil";i:790;s:9:"RubicWarb";i:791;s:9:"HemiUinta";i:792;s:7:"OulSupr";i:793;s:10:"HieMuPelop";i:794;s:3:"Dis";i:795;s:5:"Telot";i:796;s:3:"Cza";i:797;s:5:"KidLa";i:798;s:4:"Nase";i:799;s:3:"Sul";i:800;s:8:"CubbyRed";i:801;s:8:"OrPectTo";i:802;s:7:"SeSvaUp";i:803;s:6:"SerWit";i:804;s:3:"Spo";i:805;s:10:"MeProbSmyr";i:806;s:5:"Tunab";i:807;s:9:"HolInVene";i:808;s:10:"ClaInlieRe";i:809;s:4:"Carb";i:810;s:13:"ReseSteeUncon";i:811;s:9:"UnbarWhit";i:812;s:11:"OstPhthiStu";i:813;s:5:"Whenn";i:814;s:4:"Ungl";i:815;s:3:"Sch";i:816;s:9:"PictuReco";i:817;s:10:"LyPremoTyp";i:818;s:7:"PagStal";i:819;s:5:"Sogdi";i:820;s:5:"UnUno";i:821;s:5:"Anast";i:822;s:11:"IsocMasOtos";i:823;s:7:"RayReli";i:824;s:4:"Umbe";i:825;s:7:"CortMil";i:826;s:8:"ProSaddl";i:827;s:6:"LigWob";i:828;s:6:"ReSawi";i:829;s:3:"Inw";i:830;s:5:"Recre";i:831;s:7:"PantaPs";i:832;s:7:"LuNoUna";i:833;s:9:"FeliPoTar";i:834;s:13:"RajaxSoweWhor";i:835;s:6:"HeHorn";i:836;s:2:"Vi";i:837;s:5:"Sulph";i:838;s:7:"ScalSco";i:839;s:6:"SuSwin";i:840;s:12:"PleTempUnspl";i:841;s:4:"Disl";i:842;s:6:"PuTach";i:843;s:5:"Methy";i:844;s:9:"StThreeWa";i:845;s:8:"MetaPeri";i:846;s:12:"KoMuttoPseud";i:847;s:3:"Yot";i:848;s:8:"UnleaUnt";i:849;s:12:"NoTammyZoosp";i:850;s:8:"TendUnre";i:851;s:11:"ChunSiSlate";i:852;s:11:"ReverTheUni";i:853;s:14:"SynoWhipsYokel";i:854;s:4:"Undi";i:855;s:10:"HoLodgeTum";i:856;s:10:"GoralLaWul";i:857;s:7:"OnPrede";i:858;s:6:"LieSac";i:859;s:9:"BemuGyrin";i:860;s:3:"Ung";i:861;s:4:"Retu";i:862;s:9:"DesipSavi";i:863;s:8:"RouSandi";i:864;s:4:"Prec";i:865;s:10:"GastPrStra";i:866;s:4:"Muli";i:867;s:6:"DeFebr";i:868;s:11:"IcacPoltrUn";i:869;s:5:"Trach";i:870;s:7:"MisadUn";i:871;s:15:"GaywiOverdScorp";i:872;s:11:"ChiliIsSono";i:873;s:4:"Than";i:874;s:4:"Tube";i:875;s:5:"Mermi";i:876;s:10:"KuskUbUnwo";i:877;s:9:"GeHeMicro";i:878;s:2:"Py";i:879;s:2:"Vo";i:880;s:9:"FrThTrans";i:881;s:13:"RipiSubuWorme";i:882;s:11:"AssigCoenOu";i:883;s:4:"DiUn";i:884;s:12:"NonsRotiWhat";i:885;s:5:"NoUni";i:886;s:6:"OmPhle";i:887;s:4:"Para";i:888;s:3:"Rev";i:889;s:4:"ShSt";i:890;s:8:"MahaPunn";i:891;s:7:"StatoUn";i:892;s:10:"GalliNecta";i:893;s:12:"JuMintmWouch";i:894;s:9:"SempeSpir";i:895;s:13:"OphthSemidSty";i:896;s:8:"CenTripe";i:897;s:8:"SipunUne";i:898;s:5:"Uvulo";i:899;s:4:"Verb";i:900;s:8:"CuprMaPa";i:901;s:4:"PhUn";i:902;s:7:"PulTher";i:903;s:5:"Suppo";i:904;s:8:"BombCeGl";i:905;s:11:"CoccPaSerot";i:906;s:6:"CoExTr";i:907;s:9:"PaPatPedi";i:908;s:5:"Sillo";i:909;s:10:"DrysPyreSy";i:910;s:7:"NoVermi";i:911;s:8:"SaddVuln";i:912;s:10:"NonTessUns";i:913;s:8:"HeSemiUn";i:914;s:5:"ReUns";i:915;s:10:"IntePiSiki";i:916;s:6:"BoleCo";i:917;s:6:"DisgSa";i:918;s:3:"Rem";i:919;s:9:"ChroOverd";i:920;s:3:"Unt";i:921;s:7:"HeSands";i:922;s:6:"PrShTi";i:923;s:10:"NecroPrere";i:924;s:10:"OoeciPhUns";i:925;s:3:"Ham";i:926;s:7:"GaReful";i:927;s:7:"BunyPed";i:928;s:13:"IntoParalZigz";i:929;s:5:"Jacks";i:930;s:2:"Wr";i:931;s:6:"SalUnc";i:932;s:9:"GeSlovTic";i:933;s:9:"NeighPain";i:934;s:3:"Und";i:935;s:6:"OutTri";i:936;s:4:"Teii";i:937;s:5:"Taint";i:938;s:5:"Guill";i:939;s:10:"AzonxFaMoa";i:940;s:10:"InterSeUnc";i:941;s:7:"OveSobr";i:942;s:8:"ReTrihUn";i:943;s:7:"CyWordi";i:944;s:9:"TurnaWash";i:945;s:8:"RevoUngy";i:946;s:6:"PhoRer";i:947;s:5:"Whizz";i:948;s:13:"ProUndesUnsal";i:949;s:11:"StitcUpWitx";i:950;s:4:"Pert";i:951;s:3:"Tac";i:952;s:9:"EmulsProu";i:953;s:6:"UnwaUs";i:954;s:8:"FelTaxia";i:955;s:8:"ReResScl";i:956;s:6:"SemUnp";i:957;s:8:"OvTopWhi";i:958;s:8:"IntReaWo";i:959;s:4:"PlTo";i:960;s:14:"OveraPositUnpl";i:961;s:5:"Trest";i:962;s:10:"BixaChloVi";i:963;s:6:"CharGr";i:964;s:7:"ImSemUn";i:965;s:5:"Leadw";i:966;s:4:"Ples";i:967;s:11:"PsStepVirul";i:968;s:8:"NoOoSpir";i:969;s:10:"HeraImWhee";i:970;s:6:"SaTrag";i:971;s:10:"ForweSubre";i:972;s:11:"BegloEffHyd";i:973;s:8:"ChatTido";i:974;s:5:"RiSup";i:975;s:5:"Nonex";i:976;s:10:"JonMaParap";i:977;s:10:"ChapeDovEn";i:978;s:5:"Twizz";i:979;s:7:"MuckPra";i:980;s:8:"HurklLus";i:981;s:10:"MortProVal";i:982;s:9:"FroLimiRe";i:983;s:9:"HoliMimTr";i:984;s:5:"Besca";i:985;s:7:"MetroRu";i:986;s:13:"SawfSemibUngu";i:987;s:9:"ForePicco";i:988;s:9:"ParSysTee";i:989;s:4:"Pres";i:990;s:7:"HeJaile";i:991;s:3:"Ref";i:992;s:6:"MusgRe";i:993;s:3:"Her";i:994;s:4:"Hyet";i:995;s:7:"HelioPo";i:996;s:6:"BasiYa";i:997;s:8:"ProtVell";i:998;s:7:"NonoWea";i:999;s:7:"SubrUnm";i:1000;s:11:"ConEboniVer";i:1001;s:3:"Uni";i:1002;s:9:"IdePlasUn";i:1003;s:9:"PronStabi";i:1004;s:14:"QuinSquabUnrig";i:1005;s:2:"Ob";i:1006;s:7:"NatchOx";i:1007;s:5:"IsXan";i:1008;s:6:"JaReel";i:1009;s:8:"ProRhota";i:1010;s:4:"Vell";i:1011;s:5:"Hemop";i:1012;s:5:"Searc";i:1013;s:8:"EntaUnst";i:1014;s:12:"MonolNoRajax";i:1015;s:13:"DanPolynStere";i:1016;s:13:"TubUnattZogan";i:1017;s:5:"Raini";i:1018;s:5:"SpiSp";i:1019;s:8:"PosolPro";i:1020;s:6:"AlysEc";i:1021;s:10:"PoRedetThe";i:1022;s:10:"RecoiUglil";i:1023;s:10:"AntifCrIns";i:1024;s:3:"Pse";i:1025;s:12:"NonuSemSeria";i:1026;s:12:"PanSourUncom";i:1027;s:7:"FiMatan";i:1028;s:6:"RattVe";i:1029;s:6:"SpaTom";i:1030;s:12:"GebbiPasTess";i:1031;s:9:"PhotUncUn";i:1032;s:6:"RicTar";i:1033;s:10:"PlagiSeque";i:1034;s:3:"Zer";i:1035;s:5:"Denom";i:1036;s:14:"LentiPrecaUnpr";i:1037;s:6:"PathSu";i:1038;s:4:"PuSh";i:1039;s:10:"LaLinoPrem";i:1040;s:11:"IsPteryViru";i:1041;s:6:"HerbLo";i:1042;s:10:"FebKobusOv";i:1043;s:8:"ChiloWai";i:1044;s:12:"PosTetraTong";i:1045;s:5:"StUni";i:1046;s:11:"SuccTriliUn";i:1047;s:12:"PertSymmUnsu";i:1048;s:8:"UnshrZea";i:1049;s:6:"KonMar";i:1050;s:8:"LimTidbi";i:1051;s:6:"IsMePe";i:1052;s:7:"MarWher";i:1053;s:8:"OssifPen";i:1054;s:3:"Nig";i:1055;s:9:"NoRevuWar";i:1056;s:14:"CatalNympUnfal";i:1057;s:4:"Prep";i:1058;s:2:"Pe";i:1059;s:13:"InsciProSulph";i:1060;s:5:"DysHa";i:1061;s:2:"Wa";i:1062;s:5:"Goldb";i:1063;s:8:"DiHaWhey";i:1064;s:5:"SpThr";i:1065;s:8:"MesoUncu";i:1066;s:8:"SaUncoVi";i:1067;s:5:"SemUn";i:1068;s:13:"EmplHumanPitt";i:1069;s:5:"AmpHa";i:1070;s:6:"LoPrec";i:1071;s:9:"OnRegleSn";i:1072;s:8:"CurDesUn";i:1073;s:7:"NasUnex";i:1074;s:9:"ChulPrudy";i:1075;s:7:"SacUnUp";i:1076;s:10:"InMesMisca";i:1077;s:5:"StrVe";i:1078;s:10:"ForHymeTap";i:1079;s:4:"Ruin";i:1080;s:9:"KurbaOxhe";i:1081;s:9:"DamScyTet";i:1082;s:3:"Int";i:1083;s:9:"ClypeHair";i:1084;s:10:"DoNicolSpe";i:1085;s:6:"PraUne";i:1086;s:2:"He";i:1087;s:9:"SigniUnbr";i:1088;s:10:"IntePlaTor";i:1089;s:3:"Kol";i:1090;s:11:"MesRomUnshu";i:1091;s:5:"Calfl";i:1092;s:5:"Pyrol";i:1093;s:5:"Trous";i:1094;s:7:"LanSpew";i:1095;s:12:"SylTyranUnre";i:1096;s:8:"PodRetra";i:1097;s:2:"Ox";i:1098;s:6:"PosTel";i:1099;s:5:"SynWa";i:1100;s:6:"DauUnb";i:1101;s:13:"PseuThorUropt";i:1102;s:13:"ScleThermVirt";i:1103;s:3:"Imp";i:1104;s:4:"Repr";i:1105;s:8:"SacriSub";i:1106;s:6:"GrSolo";i:1107;s:8:"OvPoShad";i:1108;s:11:"ConfParisRe";i:1109;s:8:"CnidoUna";i:1110;s:2:"Gl";i:1111;s:3:"Inf";i:1112;s:11:"OxyneRehaWi";i:1113;s:3:"Cou";i:1114;s:4:"PhSh";i:1115;s:7:"ExcSubt";i:1116;s:7:"PlProto";i:1117;s:8:"HelleWor";i:1118;s:10:"MyOvertThu";i:1119;s:4:"Unsu";i:1120;s:10:"UnUnindWor";i:1121;s:4:"Tabo";i:1122;s:8:"SoSphTuz";i:1123;s:7:"SeptSpi";i:1124;s:10:"IsoOcRedic";i:1125;s:5:"Shane";i:1126;s:8:"HydroSem";i:1127;s:14:"MattyNeossSemi";i:1128;s:9:"MetaPeSac";i:1129;s:5:"Under";i:1130;s:11:"MonopNodiSk";i:1131;s:5:"RecSu";i:1132;s:10:"PensiSnipt";i:1133;s:7:"EnchoKe";i:1134;s:8:"DisDrTae";i:1135;s:5:"Indem";i:1136;s:5:"DefUp";i:1137;s:5:"EpiIn";i:1138;s:12:"GastrSephSub";i:1139;s:8:"FosUnlie";i:1140;s:5:"Tekya";i:1141;s:8:"NonfeZib";i:1142;s:12:"StyloSubmTha";i:1143;s:7:"UnhUnst";i:1144;s:12:"DispNontPern";i:1145;s:4:"Cary";i:1146;s:2:"Fr";i:1147;s:2:"Cr";i:1148;s:7:"PhlebTs";i:1149;s:8:"FletcUne";i:1150;s:8:"HypeMaUn";i:1151;s:12:"ImprMalSuper";i:1152;s:5:"SacUn";i:1153;s:11:"RubSchatSup";i:1154;s:7:"HeSurfb";i:1155;s:13:"MagTaipoTript";i:1156;s:7:"LesSubs";i:1157;s:7:"PrTreTr";i:1158;s:7:"IvUnUns";i:1159;s:10:"HidagIntRe";i:1160;s:11:"NoSaleSpinu";i:1161;s:5:"Myalg";i:1162;s:10:"DeLenSubla";i:1163;s:7:"SurgyTr";i:1164;s:5:"OuPes";i:1165;s:7:"MarMuWe";i:1166;s:6:"FestFl";i:1167;s:7:"PhQuiUn";i:1168;s:5:"Scabr";i:1169;s:5:"SpTra";i:1170;s:4:"Dact";i:1171;s:5:"Reorg";i:1172;s:8:"LuPapSab";i:1173;s:3:"Epi";i:1174;s:11:"HairSpStrat";i:1175;s:13:"IlleiSemicSha";i:1176;s:14:"DespPropoRepla";i:1177;s:5:"Chymi";i:1178;s:7:"FoUnwre";i:1179;s:5:"Unper";i:1180;s:3:"Jel";i:1181;s:3:"Ego";i:1182;s:6:"CryPre";i:1183;s:5:"PrUnt";i:1184;s:2:"Rh";i:1185;s:6:"FaVamp";i:1186;s:8:"WesteYel";i:1187;s:4:"Limn";i:1188;s:9:"ClamHeUra";i:1189;s:10:"CrooEupShe";i:1190;s:11:"ChDandiMort";i:1191;s:7:"EsEveNe";i:1192;s:14:"LatitToniVoidl";i:1193;s:10:"HadLitNong";i:1194;s:4:"Psyc";i:1195;s:3:"Oct";i:1196;s:3:"Une";i:1197;s:9:"RaScuseSp";i:1198;s:8:"InherRor";i:1199;s:6:"ParStr";i:1200;s:7:"MetaPau";i:1201;s:9:"SeiUnUnva";i:1202;s:9:"TaUntVerj";i:1203;s:8:"CheTelot";i:1204;s:10:"InceKiMurz";i:1205;s:15:"DomicImoliLiter";i:1206;s:5:"CopDe";i:1207;s:3:"Amo";i:1208;s:10:"ParalUnent";i:1209;s:2:"Ea";i:1210;s:4:"Unbi";i:1211;s:13:"PachPlagiTrep";i:1212;s:4:"LyUn";i:1213;s:2:"Wo";i:1214;s:7:"PlatRom";i:1215;s:12:"ChiQuitSulph";i:1216;s:11:"AuPennoRoll";i:1217;s:13:"LarriPallPron";i:1218;s:8:"MailpPyg";i:1219;s:4:"PiTa";i:1220;s:4:"Unam";i:1221;s:9:"HedgeStra";i:1222;s:6:"GynKit";i:1223;s:2:"Sh";i:1224;s:10:"CubInStati";i:1225;s:2:"Mi";i:1226;s:8:"PeRefSou";i:1227;s:5:"InInf";i:1228;s:5:"OveVi";i:1229;s:10:"ReacUnsVal";i:1230;s:5:"Silve";i:1231;s:5:"GooUn";i:1232;s:6:"ThVict";i:1233;s:10:"MalPerShin";i:1234;s:4:"Chuc";i:1235;s:7:"RiaSerr";i:1236;s:11:"OverPhyUlli";i:1237;s:8:"CepEnvie";i:1238;s:8:"ManSubmi";i:1239;s:10:"CarErysPyr";i:1240;s:8:"PlaSpecu";i:1241;s:9:"DraOveOve";i:1242;s:9:"GiddyHyWe";i:1243;s:4:"Cala";i:1244;s:5:"ChHyr";i:1245;s:10:"HeterPoSyn";i:1246;s:7:"FrMaPer";i:1247;s:7:"SeeUnbe";i:1248;s:5:"Redto";i:1249;s:13:"FonsxIllPreli";i:1250;s:7:"InOvers";i:1251;s:5:"Thril";i:1252;s:10:"DisplMicVo";i:1253;s:9:"ImpeInSem";i:1254;s:4:"Osch";i:1255;s:4:"Duog";i:1256;s:9:"NonPuShre";i:1257;s:14:"OstreSergToolm";i:1258;s:9:"HyakMahPa";i:1259;s:7:"PalRefr";i:1260;s:7:"PoTroch";i:1261;s:12:"GiddyInWhipp";i:1262;s:7:"BroDrys";i:1263;s:6:"IllRef";i:1264;s:10:"CommaLabSt";i:1265;s:5:"Apoll";i:1266;s:2:"Ne";i:1267;s:3:"Kat";i:1268;s:7:"PoTinct";i:1269;s:10:"NasSynosUn";i:1270;s:4:"Pant";i:1271;s:5:"Unmis";i:1272;s:5:"UnVin";i:1273;s:4:"Ordi";i:1274;s:10:"MazocPrebe";i:1275;s:3:"Unw";i:1276;s:4:"TyUn";i:1277;s:6:"MetaUn";i:1278;s:4:"Thri";i:1279;s:11:"OokiSilkwSt";i:1280;s:5:"OvPed";i:1281;s:6:"MesoSq";i:1282;s:3:"Per";i:1283;s:9:"ImTraWrit";i:1284;s:2:"In";i:1285;s:9:"NaSliceWh";i:1286;s:8:"PolTherm";i:1287;s:4:"Topm";i:1288;s:9:"CasPnTita";i:1289;s:5:"Unsur";i:1290;s:6:"HeInPr";i:1291;s:8:"AtGiMisp";i:1292;s:14:"BathuEpicyPhor";i:1293;s:5:"Windb";i:1294;s:7:"DoDomUn";i:1295;s:7:"LetPrer";i:1296;s:4:"Subu";i:1297;s:12:"MacroOrtSwee";i:1298;s:11:"DarapEelHoo";i:1299;s:7:"UnaWeWu";i:1300;s:10:"HydrJoiOve";i:1301;s:4:"Rein";i:1302;s:9:"GuarPedia";i:1303;s:5:"Primo";i:1304;s:5:"Pregn";i:1305;s:2:"Tw";i:1306;s:11:"KinMemorTou";i:1307;s:14:"LandbMantSuper";i:1308;s:4:"Pers";i:1309;s:5:"DiRho";i:1310;s:9:"KabaRinne";i:1311;s:2:"To";i:1312;s:4:"Stom";i:1313;s:5:"Movie";i:1314;s:12:"ProtoSpoUnsy";i:1315;s:12:"LiceSharUpso";i:1316;s:9:"SlStereWo";i:1317;s:4:"Tubb";i:1318;s:7:"EradiPe";i:1319;s:10:"OrthoVolum";i:1320;s:10:"LeaMattNon";i:1321;s:11:"PeSemiThrom";i:1322;s:3:"Yar";i:1323;s:12:"DetraGalvSki";i:1324;s:6:"ThyUna";i:1325;s:8:"DiRainRo";i:1326;s:12:"HarakHerVide";i:1327;s:5:"Plata";i:1328;s:4:"Occi";i:1329;s:7:"PaQuadr";i:1330;s:7:"SanToot";i:1331;s:10:"DecExploIn";i:1332;s:2:"Cl";i:1333;s:7:"UnbeUns";i:1334;s:6:"OphRel";i:1335;s:13:"SangSphenUnco";i:1336;s:4:"Unli";i:1337;s:15:"HorneNonopSnake";i:1338;s:11:"PhiSubjUnim";i:1339;s:6:"SaniSu";i:1340;s:3:"Man";i:1341;s:7:"UnUntre";i:1342;s:10:"DihRecapUn";i:1343;s:9:"IntNonrTr";i:1344;s:10:"CoryLerrWh";i:1345;s:10:"GimMonRetr";i:1346;s:4:"Phry";i:1347;s:6:"MyoWea";i:1348;s:3:"Was";i:1349;s:6:"GrHemi";i:1350;s:9:"InInfePre";i:1351;s:4:"NuPn";i:1352;s:14:"ChlorHeteProca";i:1353;s:7:"FroImpu";i:1354;s:6:"DiStee";i:1355;s:7:"CarnUnd";i:1356;s:11:"SentiTimeUn";i:1357;s:11:"ScrawVeYach";i:1358;s:5:"Utero";i:1359;s:8:"InfOfPho";i:1360;s:10:"MolSulphUn";i:1361;s:6:"CaDePr";i:1362;s:5:"InRea";i:1363;s:4:"Scra";i:1364;s:12:"JaPosteUnder";i:1365;s:7:"MuscVer";i:1366;s:12:"DimerEupaMeg";i:1367;s:4:"Grap";i:1368;s:11:"CadeHectoHo";i:1369;s:6:"SulThi";i:1370;s:4:"Hama";i:1371;s:9:"LymOuRemn";i:1372;s:4:"Mart";i:1373;s:6:"KaTrum";i:1374;s:3:"Way";i:1375;s:11:"KitLatPomon";i:1376;s:14:"IsotLeiomTrich";i:1377;s:9:"SteTarrUn";i:1378;s:10:"DeFoxTurnc";i:1379;s:8:"LeucNoPe";i:1380;s:9:"PeriRelSi";i:1381;s:9:"NoParTien";i:1382;s:6:"PupUna";i:1383;s:5:"PanSa";i:1384;s:9:"DeoKerxTu";i:1385;s:5:"Unwor";i:1386;s:2:"Mu";i:1387;s:14:"IodosOverWrith";i:1388;s:4:"HuPa";i:1389;s:7:"JocLame";i:1390;s:4:"Mame";i:1391;s:5:"Unspi";i:1392;s:11:"IthyLeisPos";i:1393;s:7:"ParapWa";i:1394;s:7:"TriliUn";i:1395;s:8:"TarTheer";i:1396;s:5:"Frust";i:1397;s:6:"GuTrun";i:1398;s:9:"MembrReco";i:1399;s:10:"KwartRheUr";i:1400;s:3:"Sem";i:1401;s:4:"Naph";i:1402;s:11:"ParRecUsuar";i:1403;s:12:"LegaMallPowd";i:1404;s:8:"OveProet";i:1405;s:11:"FinesOvXeno";i:1406;s:10:"PresuRattl";i:1407;s:3:"Dem";i:1408;s:13:"FrienRevSeque";i:1409;s:12:"BriHyperKrem";i:1410;s:13:"HomoMoribTouc";i:1411;s:4:"Wauk";i:1412;s:12:"SupeTonsiUns";i:1413;s:8:"KikuScyt";i:1414;s:5:"Scare";i:1415;s:12:"HippoPlPneum";i:1416;s:8:"GraNonfo";i:1417;s:7:"EuphVel";i:1418;s:5:"Undar";i:1419;s:12:"GampShriSurr";i:1420;s:2:"Wi";i:1421;s:7:"OsmPrUn";i:1422;s:10:"TarahUnabi";i:1423;s:7:"CaDawTy";i:1424;s:10:"GeomeLater";i:1425;s:8:"GelPaSup";i:1426;s:8:"RegreVol";i:1427;s:7:"LepMeio";i:1428;s:12:"HeMesolSpeec";i:1429;s:4:"Pred";i:1430;s:12:"EpParagRedex";i:1431;s:10:"MiPhilTyro";i:1432;s:8:"ParaSaga";i:1433;s:6:"EstNih";i:1434;s:11:"OrdiRevolSe";i:1435;s:3:"Pla";i:1436;s:12:"LepiRetSuper";i:1437;s:12:"HoloOperWild";i:1438;s:6:"InteVe";i:1439;s:4:"Whit";i:1440;s:11:"JuMinkoXant";i:1441;s:10:"BowmHelMal";i:1442;s:11:"KetazPrVeld";i:1443;s:4:"Intr";i:1444;s:7:"LeucSub";i:1445;s:7:"OveUnUn";i:1446;s:7:"MyelPos";i:1447;s:6:"PrSche";i:1448;s:2:"Ze";i:1449;s:5:"Pyrrh";i:1450;s:5:"ChaPh";i:1451;s:8:"RelUltra";i:1452;s:5:"Prere";i:1453;s:7:"FoProSe";i:1454;s:11:"MyoUnyiWeir";i:1455;s:8:"InLiTaro";i:1456;s:8:"QuintSty";i:1457;s:4:"Euch";i:1458;s:5:"Consu";i:1459;s:5:"Incon";i:1460;s:11:"HypoMagnPyc";i:1461;s:5:"PeUbi";i:1462;s:13:"HistoIlokaRas";i:1463;s:8:"ManoMiPl";i:1464;s:11:"LeathRedeSu";i:1465;s:11:"PalStucToba";i:1466;s:13:"HabitPriSaber";i:1467;s:5:"Table";i:1468;s:11:"SeneSubcToo";i:1469;s:11:"DiOffeSuper";i:1470;s:4:"Ruff";i:1471;s:12:"HyperPoiStor";i:1472;s:13:"BigwDirtUnasp";i:1473;s:3:"Pro";i:1474;s:6:"EpiExe";i:1475;s:10:"HypMetNahu";i:1476;s:10:"DimiValeWi";i:1477;s:4:"FiRa";i:1478;s:11:"DoSomnaUnta";i:1479;s:8:"BupMazam";i:1480;s:13:"CurtaOntSuppl";i:1481;s:5:"Waben";i:1482;s:10:"EpicrLymph";i:1483;s:7:"CasUnca";i:1484;s:8:"HatchWhe";i:1485;s:8:"HarmMole";i:1486;s:3:"Thi";i:1487;s:3:"Hal";i:1488;s:10:"CisSpuddVe";i:1489;s:11:"LiMismiPsyc";i:1490;s:11:"PolliSiVign";i:1491;s:10:"SeducShSta";i:1492;s:8:"SaugScab";i:1493;s:6:"IslaTr";i:1494;s:10:"ComImParat";i:1495;s:4:"Peri";i:1496;s:11:"GritLaRambe";i:1497;s:7:"EddZygo";i:1498;s:12:"BumInteMidma";i:1499;s:15:"InterOrnitPreme";i:1500;s:9:"PrSugSulf";i:1501;s:8:"IdePsych";i:1502;s:4:"Proc";i:1503;s:7:"CosmTri";i:1504;s:6:"CommUn";i:1505;s:5:"Sawfl";i:1506;s:5:"CusSe";i:1507;s:7:"TrUlste";i:1508;s:4:"Thyr";i:1509;s:5:"Persp";i:1510;s:9:"PedulSiVi";i:1511;s:6:"AmMyrm";i:1512;s:5:"Physi";i:1513;s:8:"SurcTolx";i:1514;s:5:"Cytop";i:1515;s:5:"GaRav";i:1516;s:13:"CarMelanUnuni";i:1517;s:6:"EdSubt";i:1518;s:7:"DotGeph";i:1519;s:7:"InNativ";i:1520;s:5:"Ozoty";i:1521;s:7:"PriScal";i:1522;s:4:"SeUn";i:1523;s:13:"FlexuIntYoudi";i:1524;s:3:"Tra";i:1525;s:8:"SubbiTap";i:1526;s:11:"ChloDrucGra";i:1527;s:8:"MarrMimi";i:1528;s:9:"DooSeSpec";i:1529;s:7:"GauzySe";i:1530;s:4:"Chio";i:1531;s:10:"RatafThVip";i:1532;s:6:"FruiGy";i:1533;s:15:"FoddeSulfoZeuct";i:1534;s:7:"BromiRe";i:1535;s:8:"OvPhrUlt";i:1536;s:3:"Unv";i:1537;s:5:"Lidfl";i:1538;s:5:"Uncon";i:1539;s:12:"SapodUnblUno";i:1540;s:13:"DomiHakimMola";i:1541;s:4:"Unth";i:1542;s:5:"FoOve";i:1543;s:9:"LendePoUn";i:1544;s:14:"ConfScrofTelle";i:1545;s:11:"MonoOutboTe";i:1546;s:11:"FeckTeUnfea";i:1547;s:5:"Wopsx";i:1548;s:8:"StaSulfa";i:1549;s:9:"CoalInapa";i:1550;s:6:"RisSqu";i:1551;s:12:"SubtWacexZyg";i:1552;s:11:"MonoMorSqua";i:1553;s:7:"DisFrTr";i:1554;s:9:"BonaIllit";i:1555;s:4:"Pron";i:1556;s:13:"LuculThaThutt";i:1557;s:3:"Unm";i:1558;s:9:"FelsOpine";i:1559;s:13:"LumpiStaiUnhu";i:1560;s:12:"HoIneliUnlat";i:1561;s:2:"Or";i:1562;s:11:"GoatrNoUnes";i:1563;s:12:"AlBenthPsych";i:1564;s:4:"Insu";i:1565;s:5:"Unver";i:1566;s:4:"Eulo";i:1567;s:7:"OmThroa";i:1568;s:9:"HyUnsUnwo";i:1569;s:5:"PeRes";i:1570;s:5:"DecTe";i:1571;s:6:"SpoZym";i:1572;s:5:"Satyr";i:1573;s:4:"Subc";i:1574;s:2:"As";i:1575;s:3:"Ret";i:1576;s:7:"JarblMy";i:1577;s:4:"Noni";i:1578;s:10:"BalusCacon";i:1579;s:10:"HazReiWard";i:1580;s:7:"QuisThe";i:1581;s:12:"SawflStrumTr";i:1582;s:4:"Witc";i:1583;s:12:"BenzoCheOver";i:1584;s:4:"Preo";i:1585;s:11:"DisseGaName";i:1586;s:8:"EleReadm";i:1587;s:8:"UnaUnZig";i:1588;s:7:"EskNeur";i:1589;s:9:"DisFanSco";i:1590;s:8:"FesJolRe";i:1591;s:10:"ConfuDoPre";i:1592;s:12:"CaptPseuRush";i:1593;s:12:"ScreXenomYar";i:1594;s:9:"RidiTarVa";i:1595;s:10:"HippMbaSaf";i:1596;s:14:"ElateNonreOver";i:1597;s:6:"MiPara";i:1598;s:10:"PterPuSear";i:1599;s:5:"Marsh";i:1600;s:5:"Stram";i:1601;s:8:"SheWippe";i:1602;s:9:"StreWench";i:1603;s:13:"SubauSycopZoo";i:1604;s:14:"LarunNonaPhyto";i:1605;s:8:"OrdeVigi";i:1606;s:11:"KumOphthPar";i:1607;s:4:"Depe";i:1608;s:10:"DecumPerio";i:1609;s:8:"ArchClMe";i:1610;s:7:"TawieWy";i:1611;s:5:"SupWr";i:1612;s:10:"ReshSaTydi";i:1613;s:8:"BeverDis";i:1614;s:11:"HypNeuroTru";i:1615;s:15:"DrinkFulmiSaper";i:1616;s:14:"GlycMachiNonre";i:1617;s:8:"TapiUrbi";i:1618;s:5:"InNun";i:1619;s:11:"ProTalegTot";i:1620;s:5:"GarUn";i:1621;s:5:"Signa";i:1622;s:10:"ProofUltra";i:1623;s:5:"Parti";i:1624;s:10:"HoWanlWitc";i:1625;s:5:"Ossua";i:1626;s:11:"ImpPreSuper";i:1627;s:8:"CurIsoRi";i:1628;s:4:"Play";i:1629;s:4:"Fore";i:1630;s:11:"DecuExtrInd";i:1631;s:12:"DysanHydPost";i:1632;s:7:"ReTelev";i:1633;s:9:"CionxPrSh";i:1634;s:6:"NatiUn";i:1635;s:4:"Grav";i:1636;s:4:"Tran";i:1637;s:3:"Kan";i:1638;s:12:"LichMakiUnne";i:1639;s:9:"LacMesPer";i:1640;s:7:"CoHidJi";i:1641;s:11:"MahSurUnlam";i:1642;s:10:"DeOutsWauk";i:1643;s:13:"MarPylorRoots";i:1644;s:6:"ExPehu";i:1645;s:5:"PaTak";i:1646;s:5:"Rubas";i:1647;s:8:"PhaseWit";i:1648;s:5:"Inhar";i:1649;s:12:"OsteRhyScapu";i:1650;s:13:"MonotRedeSupe";i:1651;s:10:"BlephDaPer";i:1652;s:8:"SylvTrUn";i:1653;s:6:"MePuSa";i:1654;s:4:"DuUn";i:1655;s:7:"DistSem";i:1656;s:5:"HuPer";i:1657;s:11:"PlPolyUnpea";i:1658;s:5:"FeeNe";i:1659;s:12:"ElegGrinZygo";i:1660;s:13:"PanteUnbloUns";i:1661;s:6:"LifTur";i:1662;s:4:"Unsi";i:1663;s:7:"PeoplUr";i:1664;s:5:"CerMa";i:1665;s:8:"TechnUns";i:1666;s:7:"ScleWhe";i:1667;s:11:"MazanNecPro";i:1668;s:10:"RecovSergi";i:1669;s:5:"Unidi";i:1670;s:8:"SunUninf";i:1671;s:9:"MoSciUnfa";i:1672;s:10:"FelsOstTri";i:1673;s:7:"ConSang";i:1674;s:3:"Unh";i:1675;s:7:"CrHyTri";i:1676;s:3:"Kne";i:1677;s:10:"EpiNonPhoe";i:1678;s:7:"HydNeig";i:1679;s:6:"BoOnse";i:1680;s:7:"UnhorVe";i:1681;s:13:"OtherSirTobex";i:1682;s:8:"LaPrebUn";i:1683;s:12:"MyeNitidRace";i:1684;s:11:"DisDorsPseu";i:1685;s:4:"Unne";i:1686;s:10:"ComatTashr";i:1687;s:5:"Chrom";i:1688;s:8:"ScolTene";i:1689;s:6:"PholTi";i:1690;s:5:"EnoUn";i:1691;s:8:"MaxNonsp";i:1692;s:11:"MisStaphTal";i:1693;s:6:"PsSlUn";i:1694;s:7:"SeUlste";i:1695;s:10:"PreThrepUn";i:1696;s:3:"Min";i:1697;s:6:"SplZoo";i:1698;s:4:"Obit";i:1699;s:9:"LiRicoSar";i:1700;s:9:"PsReReind";i:1701;s:2:"Gu";i:1702;s:11:"PioSemUncha";i:1703;s:10:"DebamInsPl";i:1704;s:4:"CoNo";i:1705;s:3:"Pat";i:1706;s:5:"Rhino";i:1707;s:12:"GladdInUnscr";i:1708;s:5:"Gargo";i:1709;s:3:"Hog";i:1710;s:4:"Tany";i:1711;s:9:"FacepUngr";i:1712;s:6:"DesUnd";i:1713;s:3:"Tap";i:1714;s:6:"ReUnde";i:1715;s:2:"Qu";i:1716;s:7:"IntuPar";i:1717;s:5:"LopPr";i:1718;s:4:"Unta";i:1719;s:3:"Tin";i:1720;s:3:"Mot";i:1721;s:10:"MemorRemic";i:1722;s:11:"FistlPanTyi";i:1723;s:12:"GhoGuarRelbu";i:1724;s:3:"Coo";i:1725;s:6:"NotPig";i:1726;s:4:"Orna";i:1727;s:9:"InNonalTh";i:1728;s:7:"AlConen";i:1729;s:8:"QuiSyncx";i:1730;s:12:"HematLarTear";i:1731;s:4:"Link";i:1732;s:7:"PolarVa";i:1733;s:14:"MacroParvRuina";i:1734;s:7:"KeelRep";i:1735;s:4:"UnWo";i:1736;s:13:"PolygRemTucun";i:1737;s:9:"LoxoReSin";i:1738;s:12:"OcPassaSciss";i:1739;s:9:"CopeCorre";i:1740;s:12:"GawLaborTeta";i:1741;s:8:"CoHeSupe";i:1742;s:7:"ProtTel";i:1743;s:7:"RaUnplu";i:1744;s:3:"Try";i:1745;s:4:"Tols";i:1746;s:2:"Sn";i:1747;s:8:"CincUnco";i:1748;s:11:"CondPytUnre";i:1749;s:7:"CeMePro";i:1750;s:5:"Myrme";i:1751;s:4:"Skew";i:1752;s:3:"Usa";i:1753;s:8:"SabbaSla";i:1754;s:9:"GlePrThyr";i:1755;s:7:"FlRhina";i:1756;s:11:"ReaSilicUnb";i:1757;s:9:"InPahTeer";i:1758;s:4:"Gude";i:1759;s:9:"KaxPyosUn";i:1760;s:5:"Bianc";i:1761;s:10:"SelenSquas";i:1762;s:9:"RhinUntuc";i:1763;s:8:"PesSupUn";i:1764;s:7:"SyTeneb";i:1765;s:3:"Gly";i:1766;s:12:"MethaMucPara";i:1767;s:14:"DemaIcelaWhome";i:1768;s:3:"Pri";i:1769;s:10:"ChapaLiMet";i:1770;s:11:"EhaNonseVot";i:1771;s:3:"Xan";i:1772;s:12:"MerocTuppWal";i:1773;s:3:"Dyn";i:1774;s:11:"MystaPseSem";i:1775;s:3:"Org";i:1776;s:13:"MelanParaWear";i:1777;s:9:"ReissUnVi";i:1778;s:2:"Me";i:1779;s:9:"JokisPrTh";i:1780;s:13:"CraEluviFlood";i:1781;s:7:"NavStee";i:1782;s:7:"PasqRep";i:1783;s:7:"FeMildh";i:1784;s:3:"Sub";i:1785;s:12:"IntMeisTetra";i:1786;s:8:"CherPrec";i:1787;s:7:"MegaSex";i:1788;s:4:"GrSp";i:1789;s:8:"PePinfSc";i:1790;s:9:"FluxTripp";i:1791;s:8:"GaliWeak";i:1792;s:3:"Req";i:1793;s:9:"InOvereRo";i:1794;s:8:"NicRamen";i:1795;s:8:"AlfMaUnd";i:1796;s:7:"NonabPi";i:1797;s:8:"ResiTing";i:1798;s:8:"GoParTub";i:1799;s:4:"Sili";i:1800;s:5:"PhPre";i:1801;s:10:"ApartTuske";i:1802;s:6:"HereRe";i:1803;s:4:"ElPr";i:1804;s:13:"HiruMonoTownw";i:1805;s:11:"ChalGuaWord";i:1806;s:11:"ChrNonpuSub";i:1807;s:11:"LazyxQuUndi";i:1808;s:4:"QuRe";i:1809;s:8:"DiGulfWi";i:1810;s:7:"PunUroc";i:1811;s:9:"CulFurHyp";i:1812;s:5:"Salty";i:1813;s:9:"PitcPoStu";i:1814;s:2:"La";i:1815;s:7:"UropZoo";i:1816;s:11:"HermiKaRoup";i:1817;s:4:"Cons";i:1818;s:10:"RapiSprTri";i:1819;s:8:"DenoMiPi";i:1820;s:8:"NairyPhy";i:1821;s:2:"Um";i:1822;s:10:"JuScopUnwi";i:1823;s:4:"Radi";i:1824;s:8:"CribExtr";i:1825;s:6:"SalThr";i:1826;s:3:"Dil";i:1827;s:7:"DefEwde";i:1828;s:9:"PalliUnac";i:1829;s:9:"SafraUnst";i:1830;s:13:"PrefSidaUnder";i:1831;s:11:"SatSporUnin";i:1832;s:5:"MoSch";i:1833;s:8:"CrRepSym";i:1834;s:2:"Pt";i:1835;s:4:"OvPr";i:1836;s:6:"DeUnab";i:1837;s:4:"Unde";i:1838;s:5:"Negro";i:1839;s:5:"MarPu";i:1840;s:5:"Ortho";i:1841;s:12:"MatroThesTra";i:1842;s:2:"Ma";i:1843;s:8:"ActFaKee";i:1844;s:12:"SalacThecUnd";i:1845;s:10:"MuMurPrein";i:1846;s:9:"PtiloTall";i:1847;s:2:"Oc";i:1848;s:10:"ScattSphen";i:1849;s:5:"MotUn";i:1850;s:7:"CuStuUn";i:1851;s:4:"Unqu";i:1852;s:4:"Decr";i:1853;s:5:"TalTr";i:1854;s:4:"Sexd";i:1855;s:7:"ShaUnmo";i:1856;s:6:"EuoNat";i:1857;s:8:"MahiSemi";i:1858;s:9:"GiglTacho";i:1859;s:10:"GneisMaulx";i:1860;s:11:"HemaLaPrors";i:1861;s:10:"FlubPacTyp";i:1862;s:9:"AlNonjTel";i:1863;s:9:"RecSuiUnc";i:1864;s:8:"ConsiSar";i:1865;s:4:"Prei";i:1866;s:4:"Lami";i:1867;s:11:"ManPelecSau";i:1868;s:5:"Ducat";i:1869;s:7:"PsSauci";i:1870;s:7:"HolTerp";i:1871;s:3:"She";i:1872;s:10:"DisDowerSt";i:1873;s:5:"Timan";i:1874;s:3:"Pal";i:1875;s:8:"QuinSuTo";i:1876;s:12:"HeathNonsuSo";i:1877;s:11:"GenMicrTeno";i:1878;s:5:"RecUn";i:1879;s:4:"Unpr";i:1880;s:7:"FlamIrr";i:1881;s:13:"DonarManaPero";i:1882;s:6:"ShTopi";i:1883;s:4:"Scit";i:1884;s:10:"OmmatScour";i:1885;s:5:"Ethen";i:1886;s:4:"Xylo";i:1887;s:5:"EryTr";i:1888;s:6:"BraHol";i:1889;s:13:"ImpalRummaVar";i:1890;s:4:"Trav";i:1891;s:11:"JotLoxUnfun";i:1892;s:8:"MuscSeme";i:1893;s:8:"GiLiPara";i:1894;s:9:"ViscoVouc";i:1895;s:3:"Scr";i:1896;s:6:"HaMaNo";i:1897;s:6:"StaSwe";i:1898;s:9:"InteRadio";i:1899;s:12:"PrThynnUnvol";i:1900;s:8:"PhilRecr";i:1901;s:9:"OmelTecpa";i:1902;s:9:"PettyUnwe";i:1903;s:5:"Melan";i:1904;s:14:"GasteQuinqTric";i:1905;s:12:"CommuDaInter";i:1906;s:5:"Megam";i:1907;s:12:"MiPickpTakam";i:1908;s:4:"Hydr";i:1909;s:4:"Torv";i:1910;s:7:"ExtiInt";i:1911;s:13:"EuphoFruOutdw";i:1912;s:5:"Tinni";i:1913;s:4:"Subs";i:1914;s:5:"OvPho";i:1915;s:11:"GlyphMonRig";i:1916;s:4:"Pion";i:1917;s:10:"IndagPieTi";i:1918;s:11:"TakiUnprUpa";i:1919;s:5:"LoVar";i:1920;s:5:"FaPla";i:1921;s:7:"MiscOns";i:1922;s:7:"DusStee";i:1923;s:9:"BeetlHang";i:1924;s:12:"FigMarmUnend";i:1925;s:5:"Fordo";i:1926;s:4:"Soci";i:1927;s:12:"HygrHypShopk";i:1928;s:11:"HabPlowWenc";i:1929;s:8:"PerSpent";i:1930;s:4:"Camp";i:1931;s:5:"PatRu";i:1932;s:8:"GhaSuthe";i:1933;s:10:"OuttPaUnim";i:1934;s:9:"StockTacc";i:1935;s:7:"InneUns";i:1936;s:4:"Trac";i:1937;s:4:"Unin";i:1938;s:8:"PolUninv";i:1939;s:9:"PheSterUn";i:1940;s:8:"DysYukia";i:1941;s:5:"PenSc";i:1942;s:7:"DaInShi";i:1943;s:14:"HepaNonshOrbic";i:1944;s:7:"PluPrPr";i:1945;s:4:"Semp";i:1946;s:3:"Unb";i:1947;s:9:"LancNonco";i:1948;s:10:"FiKlockNon";i:1949;s:4:"InSw";i:1950;s:8:"RoUnsuWi";i:1951;s:6:"ObscSe";i:1952;s:8:"ScythSub";i:1953;s:7:"HistMot";i:1954;s:5:"GoUtt";i:1955;s:5:"Inter";i:1956;s:7:"MiThUnp";i:1957;s:8:"BreComus";i:1958;s:8:"HurMemTu";i:1959;s:6:"OdylSn";i:1960;s:5:"Misec";i:1961;s:10:"PolypScler";i:1962;s:10:"OverSintTh";i:1963;s:8:"OverSexu";i:1964;s:12:"DruMagneSant";i:1965;s:10:"ParoPawPri";i:1966;s:7:"MaPrVar";i:1967;s:12:"LancNeolShas";i:1968;s:7:"SelUnsh";i:1969;s:5:"Nephr";i:1970;s:11:"NaSeismSept";i:1971;s:9:"DrivaNySi";i:1972;s:8:"PePheUnt";i:1973;s:9:"BusElSilp";i:1974;s:10:"SarclSever";i:1975;s:10:"ChirpDeEde";i:1976;s:10:"SarSigUndi";i:1977;s:8:"PyxidSup";i:1978;s:4:"Nubb";i:1979;s:8:"HederPne";i:1980;s:10:"PhthiTeneb";i:1981;s:7:"FrFreon";i:1982;s:12:"QuareResiSke";i:1983;s:6:"GangNo";i:1984;s:6:"NoonSp";i:1985;s:5:"StrUn";i:1986;s:11:"CrDarogYera";i:1987;s:9:"PhPogamPr";i:1988;s:11:"EneugInceOs";i:1989;s:5:"Carac";i:1990;s:11:"NaysaOvSupe";i:1991;s:5:"BlaHu";i:1992;s:10:"RoselUnWoo";i:1993;s:10:"CorinGraTe";i:1994;s:7:"InUnobv";i:1995;s:9:"BaChuroIn";i:1996;s:8:"MulSerol";i:1997;s:4:"Cast";i:1998;s:6:"DeQuXy";i:1999;s:8:"MiPnVeri";i:2000;s:7:"MyrStor";i:2001;s:8:"IncavZin";i:2002;s:9:"KayRulaTr";i:2003;s:3:"Ser";i:2004;s:7:"PaucRus";i:2005;s:2:"Tu";i:2006;s:9:"OddmWindb";i:2007;s:8:"UnpWhang";i:2008;s:4:"GaMe";i:2009;s:6:"MaPost";i:2010;s:7:"PomRach";i:2011;s:2:"Wy";i:2012;s:11:"MaQueeTapst";i:2013;s:5:"Denar";i:2014;s:10:"MethoSneer";i:2015;s:9:"CovaSpell";i:2016;s:8:"SlanTuis";i:2017;s:12:"ChiReprTaunt";i:2018;s:9:"LaMeasUne";i:2019;s:5:"PoPre";i:2020;s:5:"SpaSs";i:2021;s:4:"Salm";i:2022;s:5:"Endop";i:2023;s:3:"Tut";i:2024;s:6:"PeriPh";i:2025;s:13:"IrreSodaVeget";i:2026;s:5:"Locri";i:2027;s:7:"UnZooma";i:2028;s:4:"Prou";i:2029;s:5:"Outdw";i:2030;s:12:"MesneReThrou";i:2031;s:2:"Se";i:2032;s:4:"Sate";i:2033;s:4:"AtDi";i:2034;s:5:"OrPhe";i:2035;s:5:"Quond";i:2036;s:5:"Samar";i:2037;s:2:"Ke";i:2038;s:5:"Nonmu";i:2039;s:12:"ForcTaggyTea";i:2040;s:4:"Uncr";i:2041;s:5:"PlaRa";i:2042;s:7:"FeFemPr";i:2043;s:5:"ClDis";i:2044;s:14:"BackbIlluvPseu";i:2045;s:10:"BrDemiLuxu";i:2046;s:10:"GaMyoplOut";i:2047;s:14:"MeteOutwoPeriu";i:2048;s:8:"GaMycSha";i:2049;s:5:"Unapp";i:2050;s:4:"Gibb";i:2051;s:3:"Hem";i:2052;s:5:"Syngn";i:2053;s:5:"Lophi";i:2054;s:7:"OvReUnl";i:2055;s:6:"ProfWo";i:2056;s:11:"BaElasmElop";i:2057;s:2:"Eq";i:2058;s:5:"Workm";i:2059;s:9:"AppelUnic";i:2060;s:4:"Rabb";i:2061;s:8:"DovInspi";i:2062;s:5:"Katip";i:2063;s:12:"MiPortmTecto";i:2064;s:8:"ScSiVola";i:2065;s:3:"Cra";i:2066;s:14:"HexaPassaPhila";i:2067;s:9:"FrHoUndet";i:2068;s:7:"DipLint";i:2069;s:4:"IsPa";i:2070;s:3:"New";i:2071;s:3:"Ton";i:2072;s:11:"ImshMacrScr";i:2073;s:5:"Pampr";i:2074;s:9:"InnocUnch";i:2075;s:13:"LithxUnrZipsx";i:2076;s:13:"MingyUngeUnor";i:2077;s:10:"NonnoUngUn";i:2078;s:4:"Pict";i:2079;s:8:"RemeSpon";i:2080;s:4:"Plas";i:2081;s:5:"IntRu";i:2082;s:3:"Hyb";i:2083;s:3:"Tec";i:2084;s:3:"Dia";i:2085;s:4:"Unme";i:2086;s:11:"CarlDeliVam";i:2087;s:8:"NeUncrUn";i:2088;s:3:"Ker";i:2089;s:10:"ObjuProlTw";i:2090;s:8:"DeOverSu";i:2091;s:2:"An";i:2092;s:7:"GraduUn";i:2093;s:9:"PalaeSyTr";i:2094;s:3:"Sen";i:2095;s:6:"SubTet";i:2096;s:9:"MadMultSu";i:2097;s:10:"GermaUnpre";i:2098;s:10:"GibbNonsVo";i:2099;s:3:"Phr";i:2100;s:4:"Macr";i:2101;s:4:"ExUn";i:2102;s:11:"KaibMeUnpar";i:2103;s:5:"Mutil";i:2104;s:12:"PentaPoVentr";i:2105;s:3:"For";i:2106;s:10:"PedeUnciUp";i:2107;s:9:"FranTheop";i:2108;s:10:"InterJerUn";i:2109;s:6:"CyaUnt";i:2110;s:4:"Chic";i:2111;s:10:"RemTanThri";i:2112;s:4:"BeSa";i:2113;s:3:"Fec";i:2114;s:10:"CozilMoUht";i:2115;s:2:"Pa";i:2116;s:5:"Mispr";i:2117;s:4:"PrSt";i:2118;s:4:"DiHe";i:2119;s:10:"FakoMoQuix";i:2120;s:11:"PtePyrUnarr";i:2121;s:6:"PhRoSt";i:2122;s:4:"BeEn";i:2123;s:8:"GuiHypha";i:2124;s:8:"GuInoPre";i:2125;s:9:"PatRhaUnt";i:2126;s:4:"Holo";i:2127;s:8:"PrioUnme";i:2128;s:11:"GradRubytUn";i:2129;s:10:"CoeCredeSi";i:2130;s:9:"ReifUndis";i:2131;s:6:"LuVaWi";i:2132;s:7:"AnBuDia";i:2133;s:4:"Trad";i:2134;s:11:"CardOvRocks";i:2135;s:7:"UnhaWas";i:2136;s:13:"MaggeOthWilli";i:2137;s:8:"MycNoPol";i:2138;s:9:"FasGriNig";i:2139;s:6:"LanPar";i:2140;s:9:"FeoffPrSu";i:2141;s:11:"GonoGreUnco";i:2142;s:8:"SmotStom";i:2143;s:4:"Tute";i:2144;s:6:"MellRa";i:2145;s:4:"Pter";i:2146;s:9:"ThrThroTu";i:2147;s:5:"InTap";i:2148;s:7:"CollTub";i:2149;s:2:"Fa";i:2150;s:9:"PiRibbiSu";i:2151;s:7:"ExuPaSe";i:2152;s:9:"DownHoriz";i:2153;s:12:"JudicMockUnb";i:2154;s:9:"MeloSemia";i:2155;s:6:"CoFeRa";i:2156;s:5:"Macaw";i:2157;s:6:"NeurSe";i:2158;s:8:"LattSouc";i:2159;s:8:"UninWart";i:2160;s:9:"GiSubTown";i:2161;s:12:"PaveTitheUnl";i:2162;s:10:"DragsSubUn";i:2163;s:9:"SlopUncha";i:2164;s:5:"OrgTh";i:2165;s:8:"MixScole";i:2166;s:2:"Em";i:2167;s:5:"Remon";i:2168;s:2:"Bo";i:2169;s:7:"SabSupe";i:2170;s:4:"Neri";i:2171;s:5:"TuUnd";i:2172;s:9:"FlagSciss";i:2173;s:3:"Mas";i:2174;s:9:"InIsaThar";i:2175;s:11:"SemiStiThio";i:2176;s:3:"Oth";i:2177;s:10:"NeePoUnhel";i:2178;s:9:"CarInMate";i:2179;s:9:"DenuRefRi";i:2180;s:11:"ChiwColleMo";i:2181;s:12:"EquidPrecUnv";i:2182;s:6:"KrSpha";i:2183;s:10:"ShTeeTotte";i:2184;s:3:"Tou";i:2185;s:8:"IncRoUpb";i:2186;s:7:"IlMengx";i:2187;s:6:"CephSa";i:2188;s:11:"InkPhthaTet";i:2189;s:8:"IncuMoco";i:2190;s:7:"OveSeis";i:2191;s:3:"Rub";i:2192;s:12:"ObUnctiVeloc";i:2193;s:9:"OpalPodli";i:2194;s:3:"Dec";i:2195;s:8:"CymMoPyo";i:2196;s:9:"PhSpicaTr";i:2197;s:5:"Nondy";i:2198;s:4:"Roma";i:2199;s:9:"InsitSuTr";i:2200;s:3:"Poi";i:2201;s:8:"ImInhRos";i:2202;s:9:"HemoInsPr";i:2203;s:7:"RecoSan";i:2204;s:9:"IntraLepr";i:2205;s:7:"PartiRo";i:2206;s:7:"PlacPla";i:2207;s:9:"MedieSeSy";i:2208;s:4:"BaSc";i:2209;s:2:"Sk";i:2210;s:5:"UnfUn";i:2211;s:8:"CadavEpi";i:2212;s:5:"LagPh";i:2213;s:5:"MalMo";i:2214;s:8:"SpSupeUn";i:2215;s:8:"HiOgrePo";i:2216;s:2:"Ca";i:2217;s:5:"Infol";i:2218;s:10:"DyassUninu";i:2219;s:6:"UnUnsa";i:2220;s:12:"ChoSchwZarni";i:2221;s:9:"InReUnpay";i:2222;s:9:"UntrYello";i:2223;s:12:"OraOverSland";i:2224;s:6:"HePrSt";i:2225;s:10:"OpPropiQuo";i:2226;s:9:"PeriTheUn";i:2227;s:12:"AbuPhysiRheg";i:2228;s:7:"DisShad";i:2229;s:12:"CheGalyaTeax";i:2230;s:11:"CorrKaUncol";i:2231;s:9:"HypLandLu";i:2232;s:4:"Yegu";i:2233;s:4:"Prig";i:2234;s:12:"MultiTubbaVi";i:2235;s:14:"RehaSchnoStrin";i:2236;s:8:"PelSquat";i:2237;s:8:"HexRacTu";i:2238;s:12:"SemblSomeUpb";i:2239;s:11:"GymnImplOve";i:2240;s:6:"NondUn";i:2241;s:11:"HelIneStasi";i:2242;s:7:"EstelTi";i:2243;s:10:"InterPoThe";i:2244;s:5:"Degge";i:2245;s:3:"Rel";i:2246;s:4:"Rock";i:2247;s:9:"FlaGaSpas";i:2248;s:7:"MayaTel";i:2249;s:7:"EnteFic";i:2250;s:4:"Penn";i:2251;s:7:"OvUndaz";i:2252;s:5:"Spinu";i:2253;s:11:"CoPhotoRake";i:2254;s:13:"HomocUnioUnli";i:2255;s:7:"FidGirl";i:2256;s:6:"FaceSt";i:2257;s:9:"HardePyre";i:2258;s:5:"Brand";i:2259;s:5:"Enqui";i:2260;s:5:"Workl";i:2261;s:7:"HoIncom";i:2262;s:10:"SittUloUnk";i:2263;s:2:"Ps";i:2264;s:8:"PertiTra";i:2265;s:11:"EngOverTins";i:2266;s:5:"Uncom";i:2267;s:3:"Jaw";i:2268;s:7:"PiStrSy";i:2269;s:5:"Sotti";i:2270;s:14:"AnthrNonsSiusl";i:2271;s:5:"PseQu";i:2272;s:5:"Reedl";i:2273;s:8:"DikerPea";i:2274;s:6:"NoSwar";i:2275;s:10:"PonTiTrans";i:2276;s:6:"StomWh";i:2277;s:5:"Prepa";i:2278;s:11:"EctoTraWeap";i:2279;s:7:"CeJuKet";i:2280;s:7:"CaStiTo";i:2281;s:11:"CholaScalSl";i:2282;s:5:"LeSpu";i:2283;s:7:"NonexPa";i:2284;s:5:"CuUnd";i:2285;s:5:"CoHas";i:2286;s:7:"PresVet";i:2287;s:10:"EfEndotSph";i:2288;s:4:"GeHo";i:2289;s:10:"ConEnkraEx";i:2290;s:9:"SeakUrano";i:2291;s:10:"RebScutSta";i:2292;s:11:"AmetFoStaph";i:2293;s:4:"LoPu";i:2294;s:4:"Unmo";i:2295;s:12:"ChamaCufRetr";i:2296;s:9:"CuttiPoun";i:2297;s:7:"OxToote";i:2298;s:4:"Hali";i:2299;s:14:"OverSuperTickt";i:2300;s:9:"DefeElong";i:2301;s:10:"RozumTypif";i:2302;s:11:"OnomReinhUn";i:2303;s:5:"HePat";i:2304;s:6:"PrRaff";i:2305;s:7:"UrWorld";i:2306;s:12:"MacTanyoVoli";i:2307;s:6:"KeUnbe";i:2308;s:11:"FooliPeSoun";i:2309;s:8:"FiMaStra";i:2310;s:10:"PrRevuSubc";i:2311;s:3:"Kri";i:2312;s:6:"EnneMe";i:2313;s:3:"Mar";i:2314;s:10:"PaSubmeUna";i:2315;s:14:"MitiNoncaPelta";i:2316;s:7:"TroTylo";i:2317;s:4:"Curv";i:2318;s:5:"QuaTy";i:2319;s:7:"NonsaPr";i:2320;s:5:"Urete";i:2321;s:5:"Hynex";i:2322;s:5:"Tillo";i:2323;s:4:"EnMa";i:2324;s:10:"IonPresuPr";i:2325;s:9:"OverwTale";i:2326;s:9:"EnmaSenda";i:2327;s:13:"HiodTrihUnvis";i:2328;s:3:"Viz";i:2329;s:9:"NayarSuVa";i:2330;s:9:"VasoWallo";i:2331;s:8:"CrypMans";i:2332;s:9:"CondyEpip";i:2333;s:7:"GlMalle";i:2334;s:3:"Pag";i:2335;s:7:"ScUltra";i:2336;s:11:"ScholTaUnba";i:2337;s:13:"DisarGestNonc";i:2338;s:3:"Sir";i:2339;s:9:"PugmiUltr";i:2340;s:5:"Pangl";i:2341;s:8:"MetMyoge";i:2342;s:7:"HydroSp";i:2343;s:5:"Anaca";i:2344;s:6:"OrthPs";i:2345;s:5:"Nicco";i:2346;s:12:"DactTonnUnbo";i:2347;s:3:"Tox";i:2348;s:14:"ChorExcitPhaco";i:2349;s:3:"Tab";i:2350;s:10:"BasUnUnrui";i:2351;s:6:"RefUne";i:2352;s:3:"Ope";i:2353;s:9:"OverQuWar";i:2354;s:5:"Antid";i:2355;s:9:"ExFinRust";i:2356;s:14:"PuggiSecedStri";i:2357;s:7:"IriRaWa";i:2358;s:11:"MormProrPse";i:2359;s:5:"StUno";i:2360;s:7:"GastrPh";i:2361;s:2:"Li";i:2362;s:12:"NonOndiRocam";i:2363;s:7:"MollUnt";i:2364;s:2:"Ti";i:2365;s:4:"Podu";i:2366;s:5:"SanTe";i:2367;s:13:"ChieDanaiFell";i:2368;s:7:"CentFar";i:2369;s:6:"OutTab";i:2370;s:14:"MitigNoncoPiro";i:2371;s:13:"HondMarsObnun";i:2372;s:4:"Rest";i:2373;s:7:"NostrOb";i:2374;s:7:"IconLob";i:2375;s:5:"Virag";i:2376;s:9:"ImmePhane";i:2377;s:5:"Scran";i:2378;s:10:"LeNonalUnv";i:2379;s:7:"NiParUn";i:2380;s:8:"PhyTosUn";i:2381;s:12:"HydMarinSpec";i:2382;s:8:"LocSuUnv";i:2383;s:11:"LympTreUnse";i:2384;s:10:"CondxTenta";i:2385;s:8:"LerUnmis";i:2386;s:14:"GhanKeratNekto";i:2387;s:9:"InMalPrav";i:2388;s:3:"Mul";i:2389;s:8:"PilRever";i:2390;s:13:"PrepSialaTrum";i:2391;s:10:"DammKatKir";i:2392;s:11:"KnobProloRu";i:2393;s:4:"Thie";i:2394;s:3:"Win";i:2395;s:3:"Def";i:2396;s:8:"StrWanch";i:2397;s:7:"LatriRa";i:2398;s:2:"Si";i:2399;s:6:"DiSlSm";i:2400;s:9:"HoStelaUm";i:2401;s:4:"SwTe";i:2402;s:10:"LanToVaira";i:2403;s:6:"OzonTo";i:2404;s:6:"MirSci";i:2405;s:14:"DaisiMyeloUret";i:2406;s:12:"DigitMounUnt";i:2407;s:6:"UniUnm";i:2408;s:6:"HallUn";i:2409;s:3:"Rea";i:2410;s:13:"FrushLitRecry";i:2411;s:4:"JuNa";i:2412;s:6:"SeSuev";i:2413;s:7:"RunTetr";i:2414;s:12:"NoneRoenThio";i:2415;s:4:"Wolf";i:2416;s:9:"HetaJaPyl";i:2417;s:10:"DiapaUnder";i:2418;s:3:"Wra";i:2419;s:4:"Subp";i:2420;s:3:"Sal";i:2421;s:5:"Unmea";i:2422;s:12:"FittiQuinaSe";i:2423;s:8:"PseWicke";i:2424;s:12:"BlNondePlate";i:2425;s:8:"SigTyUnt";i:2426;s:3:"San";i:2427;s:8:"LocPerRx";i:2428;s:8:"SupUnsup";i:2429;s:6:"GraTri";i:2430;s:7:"BaldnLo";i:2431;s:9:"CruroUnej";i:2432;s:10:"LovesRehoo";i:2433;s:12:"HemeNomaOuth";i:2434;s:5:"Innox";i:2435;s:11:"KierRenTele";i:2436;s:9:"SolitStaw";i:2437;s:8:"CeMamUns";i:2438;s:11:"MysoiNeotQu";i:2439;s:10:"KatakTurns";i:2440;s:10:"DisprHymno";i:2441;s:4:"Regr";i:2442;s:9:"GlobaSynt";i:2443;s:3:"Lut";i:2444;s:3:"The";i:2445;s:3:"Rhi";i:2446;s:3:"Tym";i:2447;s:7:"MeUnman";i:2448;s:9:"ExiOmnTor";i:2449;s:12:"PerfeStiSton";i:2450;s:6:"HoPrUn";i:2451;s:10:"IrSeptaTam";i:2452;s:10:"FoParTechn";i:2453;s:4:"Spha";i:2454;s:12:"StupUnflaVie";i:2455;s:7:"LoneVan";i:2456;s:8:"MonRadio";i:2457;s:10:"HomImpSynd";i:2458;s:2:"Ch";i:2459;s:9:"EnOrPoeta";i:2460;s:10:"CoSeminSen";i:2461;s:7:"ProidVe";i:2462;s:6:"MeShor";i:2463;s:4:"Hois";i:2464;s:9:"EyeMePoli";i:2465;s:3:"Rei";i:2466;s:5:"Zoste";i:2467;s:8:"SithUnwa";i:2468;s:5:"Corre";i:2469;s:9:"SilTriUnv";i:2470;s:9:"CompMysta";i:2471;s:7:"HepMuSu";i:2472;s:6:"OpeQua";i:2473;s:4:"Rema";i:2474;s:11:"DeclMassiUn";i:2475;s:3:"Lep";i:2476;s:8:"NecrPhth";i:2477;s:4:"Trir";i:2478;s:8:"MesoWelc";i:2479;s:4:"Vulp";i:2480;s:7:"LindPhr";i:2481;s:7:"EntirMo";i:2482;s:9:"RiSlaTaun";i:2483;s:8:"PreToufi";i:2484;s:9:"PalaSovra";i:2485;s:5:"Penni";i:2486;s:12:"NonprProcRad";i:2487;s:8:"PantRogu";i:2488;s:8:"ForeScom";i:2489;s:2:"Yo";i:2490;s:9:"LiOutUnti";i:2491;s:6:"PostSt";i:2492;s:10:"SpiSyUntuc";i:2493;s:7:"AlquiRh";i:2494;s:12:"PaloSkeldTir";i:2495;s:8:"BiflaUnr";i:2496;s:9:"LacRhuWin";i:2497;s:5:"Unres";i:2498;s:7:"PoPsilo";i:2499;s:3:"Obe";i:2500;s:5:"Nonin";i:2501;s:7:"UncaUnh";i:2502;s:5:"Undiv";i:2503;s:9:"InIzMorin";i:2504;s:7:"NoPicPi";i:2505;s:13:"PediPoltThiof";i:2506;s:4:"Scap";i:2507;s:11:"CoMesorTact";i:2508;s:10:"DeepPikePr";i:2509;s:8:"OmbroQui";i:2510;s:5:"InePo";i:2511;s:10:"PaPolyzWea";i:2512;s:10:"GhiziPosne";i:2513;s:5:"Nonde";i:2514;s:10:"RachyStaph";i:2515;s:9:"NorProtRh";i:2516;s:11:"ConflElusSt";i:2517;s:9:"OliSaraTa";i:2518;s:5:"Spath";i:2519;s:9:"HetIreUnr";i:2520;s:9:"GigeHoUro";i:2521;s:6:"NonSex";i:2522;s:9:"DrawIreni";i:2523;s:7:"HacMars";i:2524;s:10:"DenRaSoros";i:2525;s:10:"HolHypSupe";i:2526;s:4:"Cred";i:2527;s:5:"StUns";i:2528;s:8:"FictWagw";i:2529;s:3:"Unc";i:2530;s:9:"ShoreWhit";i:2531;s:11:"ProRehaTras";i:2532;s:5:"UnUph";i:2533;s:10:"MoPneumPsi";i:2534;s:12:"RabbSomnUnti";i:2535;s:11:"SplenTheUnb";i:2536;s:3:"Par";i:2537;s:10:"DyaFisQuin";i:2538;s:6:"LoUncl";i:2539;s:11:"DicOvariVar";i:2540;s:8:"PreSciSc";i:2541;s:8:"HarHiMor";i:2542;s:11:"QuadrSawaSi";i:2543;s:6:"SuThim";i:2544;s:5:"Vulva";i:2545;s:3:"Hou";i:2546;s:3:"Mer";i:2547;s:8:"GiInPaga";i:2548;s:2:"Co";i:2549;s:10:"PseudRenun";i:2550;s:13:"MiskMortaTana";i:2551;s:7:"OmTerro";i:2552;s:7:"ExUnbex";i:2553;s:5:"Micro";i:2554;s:11:"DenuObsSemi";i:2555;s:11:"BursDiRenea";i:2556;s:3:"Zym";i:2557;s:9:"BunNoWitt";i:2558;s:5:"Misqu";i:2559;s:4:"LaPl";i:2560;s:6:"IndKin";i:2561;s:10:"TriUnmVent";i:2562;s:10:"MoOvePlant";i:2563;s:5:"Verbe";i:2564;s:5:"CutSc";i:2565;s:9:"UnbeVigor";i:2566;s:9:"ListUnder";i:2567;s:10:"PtilSegrSt";i:2568;s:9:"GruelNaso";i:2569;s:5:"Nudis";i:2570;s:13:"LithPhrasRest";i:2571;s:10:"HyPonUngra";i:2572;s:8:"ParPrese";i:2573;s:2:"Pu";i:2574;s:7:"SeSweUn";i:2575;s:4:"Stop";i:2576;s:11:"EntSynTechn";i:2577;s:14:"OverpTrachUneq";i:2578;s:10:"SteenUtter";i:2579;s:12:"FlorMediUnco";i:2580;s:11:"CoCyancNigg";i:2581;s:4:"Sexu";i:2582;s:11:"BignoErStom";i:2583;s:12:"BiggeMyrSupe";i:2584;s:5:"Tinti";i:2585;s:9:"PhilaSubc";i:2586;s:8:"CouPoeti";i:2587;s:13:"DockeIriMisti";i:2588;s:11:"GramOlPityp";i:2589;s:13:"DisEvoluMycod";i:2590;s:5:"Morph";i:2591;s:2:"Ou";i:2592;s:5:"ProUn";i:2593;s:4:"Dulc";i:2594;s:7:"LongaSp";i:2595;s:8:"PolPulUn";i:2596;s:8:"BuDiInco";i:2597;s:6:"ChaiDe";i:2598;s:11:"PacabUnUnsh";i:2599;s:10:"ArthrUninv";i:2600;s:6:"PyroRo";i:2601;s:7:"VulgaYi";i:2602;s:5:"EcLio";i:2603;s:11:"EranIncouOr";i:2604;s:12:"LunuModulOrd";i:2605;s:5:"Patri";i:2606;s:2:"Dr";i:2607;s:7:"RaTrans";i:2608;s:12:"CompoLipScot";i:2609;s:10:"SardoSensa";i:2610;s:12:"OverlSupWrin";i:2611;s:9:"PhotSperm";i:2612;s:7:"TimekUn";i:2613;s:5:"Pusey";i:2614;s:7:"PoQuSou";i:2615;s:6:"FreeGr";i:2616;s:4:"Nona";i:2617;s:6:"PsilUn";i:2618;s:7:"DiseSta";i:2619;s:5:"Eluso";i:2620;s:7:"EndabFr";i:2621;s:7:"EntMaTe";i:2622;s:5:"UndUn";i:2623;s:8:"FrStavUn";i:2624;s:6:"DisSyn";i:2625;s:3:"Tub";i:2626;s:11:"BescDeRenas";i:2627;s:12:"RabbiReSuper";i:2628;s:4:"Bolo";i:2629;s:10:"NonaPectPh";i:2630;s:12:"PolymRoomtUn";i:2631;s:11:"KolTypWirem";i:2632;s:7:"CroPrae";i:2633;s:7:"SubWarr";i:2634;s:6:"SteTro";i:2635;s:7:"BaForel";i:2636;s:6:"OppiWh";i:2637;s:9:"GleanSacc";i:2638;s:10:"MisliQueru";i:2639;s:8:"ScaWoodb";i:2640;s:3:"Equ";i:2641;s:10:"NeocyUnwov";i:2642;s:13:"CoacElectUnsp";i:2643;s:4:"Unch";i:2644;s:7:"BrUncom";i:2645;s:5:"Tanni";i:2646;s:10:"CuLeiOcclu";i:2647;s:3:"Soa";i:2648;s:11:"MyxoPoUnbeg";i:2649;s:12:"PhysiTrUnexi";i:2650;s:6:"InSice";i:2651;s:7:"NonPauc";i:2652;s:6:"SecuUl";i:2653;s:10:"HornOutOut";i:2654;s:6:"OpprOv";i:2655;s:10:"BaHypStich";i:2656;s:9:"HemoaIcTi";i:2657;s:4:"Zeug";i:2658;s:4:"Sphy";i:2659;s:5:"Shaka";i:2660;s:6:"PaPrSc";i:2661;s:5:"Unrel";i:2662;s:9:"ReSmintTw";i:2663;s:2:"Ku";i:2664;s:4:"Botu";i:2665;s:9:"TrulUnext";i:2666;s:9:"CoglWhite";i:2667;s:5:"Curle";i:2668;s:10:"InMinSince";i:2669;s:11:"NoRachTrans";i:2670;s:11:"RolexUnflZo";i:2671;s:7:"KoSwiUm";i:2672;s:9:"SemiUncUn";i:2673;s:7:"AdBosVe";i:2674;s:12:"CamphPePrein";i:2675;s:7:"PygmoSe";i:2676;s:7:"InMorco";i:2677;s:7:"OcOffen";i:2678;s:5:"Speck";i:2679;s:4:"Till";i:2680;s:11:"BestrUnWorc";i:2681;s:8:"OlOveSyn";i:2682;s:10:"UnsocWound";i:2683;s:6:"ReprSu";i:2684;s:7:"RemigUn";i:2685;s:12:"GeorOutmServ";i:2686;s:3:"Pac";i:2687;s:12:"LePerseSerop";i:2688;s:11:"FeverNonWav";i:2689;s:5:"BaBac";i:2690;s:9:"DragGaSan";i:2691;s:8:"UnforYar";i:2692;s:4:"Vent";i:2693;s:7:"BluSlug";i:2694;s:6:"OverSp";i:2695;s:12:"DiuHypoProto";i:2696;s:7:"ReUndes";i:2697;s:8:"PolUnimp";i:2698;s:5:"Unsmo";i:2699;s:9:"CuriaSulf";i:2700;s:11:"SyntToTreas";i:2701;s:13:"SaproSyceeTen";i:2702;s:5:"Thuoc";i:2703;s:6:"BroHol";i:2704;s:13:"MetaMothwVili";i:2705;s:10:"LeStuUnioc";i:2706;s:12:"PitheTeTrigo";i:2707;s:5:"LowMe";i:2708;s:12:"SoStahlTomme";i:2709;s:8:"PothSpTe";i:2710;s:11:"MultPhytoUn";i:2711;s:4:"Note";i:2712;s:13:"SuluSynanViol";i:2713;s:9:"SeapUnsta";i:2714;s:3:"Nin";i:2715;s:3:"Lat";i:2716;s:11:"BiopDespeEt";i:2717;s:9:"FiSuperVo";i:2718;s:5:"Cereb";i:2719;s:10:"BoNonbUnde";i:2720;s:2:"Lu";i:2721;s:10:"PrerPsyWhe";i:2722;s:11:"MeReferShee";i:2723;s:12:"GrosInvitUna";i:2724;s:8:"SarrSeri";i:2725;s:4:"Rags";i:2726;s:2:"Sm";i:2727;s:5:"Unint";i:2728;s:3:"Gut";i:2729;s:9:"InteMyPyr";i:2730;s:7:"IsoloUn";i:2731;s:5:"SiUnb";i:2732;s:10:"BlCanniEqu";i:2733;s:5:"Konar";i:2734;s:4:"Step";i:2735;s:6:"InteNa";i:2736;s:7:"KnaSubc";i:2737;s:9:"ChaUnaUnc";i:2738;s:6:"LaNeUp";i:2739;s:3:"Reb";i:2740;s:9:"DiscPePot";i:2741;s:8:"NonNonVi";i:2742;s:4:"Tata";i:2743;s:4:"Cent";i:2744;s:7:"HandPho";i:2745;s:11:"GazetMenWoa";i:2746;s:9:"PolyPredi";i:2747;s:12:"LaboPreScase";i:2748;s:7:"PaTheTh";i:2749;s:14:"DeafDiaclSides";i:2750;s:4:"Dixi";i:2751;s:8:"MiscUnpo";i:2752;s:9:"RepShaUpt";i:2753;s:13:"GaberJewdPont";i:2754;s:6:"DyoInc";i:2755;s:11:"FanaOeQuinq";i:2756;s:6:"ChThre";i:2757;s:4:"Orth";i:2758;s:9:"LaborMiSp";i:2759;s:6:"DeNavi";i:2760;s:10:"RioShSwain";i:2761;s:3:"Men";i:2762;s:4:"Ghib";i:2763;s:9:"SunpTrust";i:2764;s:5:"Feloi";i:2765;s:9:"NotorOiko";i:2766;s:5:"Laryn";i:2767;s:5:"Wishf";i:2768;s:10:"DoMultiUne";i:2769;s:4:"Unow";i:2770;s:10:"DumpiEvePy";i:2771;s:14:"OctonTwelUnman";i:2772;s:11:"NasSterXylo";i:2773;s:8:"CompOppu";i:2774;s:9:"CoalEnure";i:2775;s:4:"Suba";i:2776;s:5:"Histo";i:2777;s:12:"PaPsidiTooth";i:2778;s:5:"Tubul";i:2779;s:4:"Outg";i:2780;s:5:"HolNi";i:2781;s:2:"El";i:2782;s:11:"SpurnSuUnst";i:2783;s:3:"Soc";i:2784;s:7:"MachSyn";i:2785;s:12:"LoriPraePren";i:2786;s:7:"ProTric";i:2787;s:5:"Disre";i:2788;s:11:"IntePseuXen";i:2789;s:3:"Sei";i:2790;s:4:"Refl";i:2791;s:11:"MisomSperUn";i:2792;s:9:"RetraVaca";i:2793;s:6:"PolyRe";i:2794;s:12:"GrizzImpSnow";i:2795;s:8:"NumUroxa";i:2796;s:4:"LePa";i:2797;s:3:"Qua";i:2798;s:14:"InvolSearSemio";i:2799;s:6:"LeStan";i:2800;s:10:"SantaStaph";i:2801;s:4:"Imag";i:2802;s:9:"PreRakiSu";i:2803;s:13:"DegeTrigoUnch";i:2804;s:8:"FracUngr";i:2805;s:12:"EzrFlukShast";i:2806;s:10:"PlaneSteth";i:2807;s:8:"PresiUnd";i:2808;s:9:"BroDrivIn";i:2809;s:4:"Vice";i:2810;s:3:"Mah";i:2811;s:4:"AcXy";i:2812;s:10:"IsrMegaUst";i:2813;s:9:"PeriReaTr";i:2814;s:10:"CoOswaZami";i:2815;s:5:"JacUn";i:2816;s:9:"FaLeanPar";i:2817;s:14:"EngauInerrSpha";i:2818;s:5:"Sager";i:2819;s:13:"SteeSubwUnpry";i:2820;s:9:"RhagoStag";i:2821;s:4:"Plag";i:2822;s:4:"Sens";i:2823;s:11:"MonTylosWoo";i:2824;s:5:"Toxop";i:2825;s:4:"MiMn";i:2826;s:6:"PhlePy";i:2827;s:14:"LatoPeramUpval";i:2828;s:10:"PerStTarox";i:2829;s:5:"Thero";i:2830;s:9:"ForHaPras";i:2831;s:6:"PeTubi";i:2832;s:13:"DiffeIneVesic";i:2833;s:10:"NonveTeneb";i:2834;s:6:"TauUna";i:2835;s:14:"JeronUncuUntha";i:2836;s:5:"Telem";i:2837;s:9:"BargHaSul";i:2838;s:11:"InartMelaSt";i:2839;s:5:"Confe";i:2840;s:9:"MobPseTen";i:2841;s:4:"Obst";i:2842;s:9:"IvanPedan";i:2843;s:5:"Noctu";i:2844;s:8:"MythTiUn";i:2845;s:13:"ParlaQuadRash";i:2846;s:10:"CyOpiloPre";i:2847;s:13:"OsteUngyvUnsp";i:2848;s:5:"StZoo";i:2849;s:4:"Slid";i:2850;s:9:"ParRuVibr";i:2851;s:14:"EndoImposSubmi";i:2852;s:7:"SpiUnUn";i:2853;s:7:"PreRais";i:2854;s:9:"EpigMyxVo";i:2855;s:5:"ReUnd";i:2856;s:9:"RusSheUnm";i:2857;s:6:"PretWr";i:2858;s:3:"Hom";i:2859;s:6:"GrSini";i:2860;s:11:"ColeNonvPos";i:2861;s:3:"Tor";i:2862;s:8:"CraMonoc";i:2863;s:15:"MisfoPrestSaliv";i:2864;s:7:"NonWaho";i:2865;s:12:"GlanReliSeax";i:2866;s:10:"BrNonpUnse";i:2867;s:7:"TarocTh";i:2868;s:3:"Ora";i:2869;s:8:"GrudgPra";i:2870;s:6:"PeSnUn";i:2871;s:13:"LigniMicroUnc";i:2872;s:7:"ClogHor";i:2873;s:10:"MiserRebUn";i:2874;s:10:"EntSpeSple";i:2875;s:10:"ColleGuard";i:2876;s:5:"Totte";i:2877;s:8:"IsataTax";i:2878;s:8:"NonWorra";i:2879;s:5:"Unsha";i:2880;s:10:"EuStanxUne";i:2881;s:3:"Pin";i:2882;s:4:"MoSe";i:2883;s:5:"WeaWh";i:2884;s:7:"ProviRa";i:2885;s:11:"PreTibiUnin";i:2886;s:11:"MyeloUnsUnt";i:2887;s:9:"InspiStee";i:2888;s:5:"Sensu";i:2889;s:9:"StufSwerv";i:2890;s:2:"Sy";i:2891;s:11:"GunnHaiTrun";i:2892;s:7:"NeopPre";i:2893;s:11:"BitMooncTip";i:2894;s:4:"Unso";i:2895;s:12:"MajPhlebSpin";i:2896;s:5:"Furil";i:2897;s:5:"Sledf";i:2898;s:10:"LadylMaSac";i:2899;s:10:"ChMylioSen";i:2900;s:9:"IxParaUre";i:2901;s:9:"DoMedalUn";i:2902;s:6:"OuUnsa";i:2903;s:8:"DouNonax";i:2904;s:4:"Univ";i:2905;s:4:"Moly";i:2906;s:9:"MonitPeda";i:2907;s:9:"CelaLunul";i:2908;s:8:"AnDenaMe";i:2909;s:6:"IrTerr";i:2910;s:3:"Usu";i:2911;s:7:"UnfoUnv";i:2912;s:7:"JudMono";i:2913;s:14:"PentRagsoUnimb";i:2914;s:9:"ReTransWh";i:2915;s:14:"EquisHornsInco";i:2916;s:12:"HukxOligiSup";i:2917;s:9:"PhSpSuper";i:2918;s:11:"SemUndaUnpr";i:2919;s:4:"Self";i:2920;s:11:"FleeMatePro";i:2921;s:6:"SupWor";i:2922;s:5:"Unben";i:2923;s:9:"MemorTach";i:2924;s:6:"SegUns";i:2925;s:10:"CoDisplSod";i:2926;s:7:"EleSple";i:2927;s:8:"PolyUror";i:2928;s:3:"Yal";i:2929;s:5:"JiTri";i:2930;s:10:"TaiThyroUn";i:2931;s:4:"Wath";i:2932;s:7:"EquiRep";i:2933;s:10:"ShimSubUnd";i:2934;s:6:"HasSan";i:2935;s:5:"HemUt";i:2936;s:4:"Spiv";i:2937;s:10:"MunycPhary";i:2938;s:8:"LargOver";i:2939;s:6:"OveSab";i:2940;s:14:"AulosLarxTraga";i:2941;s:9:"DePubesZa";i:2942;s:9:"LutSeYaki";i:2943;s:8:"PoSphaTh";i:2944;s:3:"Igl";i:2945;s:5:"Contr";i:2946;s:10:"PolysRamSu";i:2947;s:5:"Unack";i:2948;s:10:"AnCapsHyda";i:2949;s:6:"PoeSli";i:2950;s:6:"SausUn";i:2951;s:12:"PostgStaghWi";i:2952;s:8:"TetrTita";i:2953;s:14:"MendaPostScran";i:2954;s:9:"CharaSeag";i:2955;s:8:"HexSpodi";i:2956;s:5:"Quadr";i:2957;s:8:"SubpSwea";i:2958;s:9:"DoNicoTer";i:2959;s:7:"MilliSl";i:2960;s:13:"HelpPneumSlim";i:2961;s:6:"ToeUrb";i:2962;s:10:"DiMaionVen";i:2963;s:7:"PestePo";i:2964;s:7:"LiyPura";i:2965;s:10:"SparlTrian";i:2966;s:5:"BraGa";i:2967;s:7:"FrontRe";i:2968;s:5:"Unall";i:2969;s:5:"Diape";i:2970;s:8:"EscIdiot";i:2971;s:5:"Salam";i:2972;s:13:"CommHypeInest";i:2973;s:4:"Wors";i:2974;s:7:"ReinfSa";i:2975;s:8:"ResciTre";i:2976;s:10:"PiStaurSup";i:2977;s:8:"PorraUpl";i:2978;s:8:"StocUnki";i:2979;s:4:"IsSe";i:2980;s:11:"SattTaTrans";i:2981;s:3:"Mel";i:2982;s:11:"UnanUnUnexp";i:2983;s:5:"Green";i:2984;s:13:"SokemStulWape";i:2985;s:6:"TacoVe";i:2986;s:5:"Coral";i:2987;s:13:"ChoroDipicShr";i:2988;s:11:"PseSterTepe";i:2989;s:6:"OverRe";i:2990;s:10:"FreehMonPo";i:2991;s:12:"KreiLocaPlas";i:2992;s:9:"HurMorPty";i:2993;s:10:"DaSeqTirre";i:2994;s:13:"HypsiReproUre";i:2995;s:5:"Subno";i:2996;s:5:"Outpo";i:2997;s:10:"AndSevenVo";i:2998;s:5:"Vehmi";i:2999;s:3:"Vet";i:3000;s:5:"Proto";i:3001;s:5:"Unbri";i:3002;s:4:"Spie";i:3003;s:11:"ClaColobWhe";i:3004;s:4:"MeSy";i:3005;s:7:"ForSuXy";i:3006;s:11:"ChaKukPrior";i:3007;s:5:"Draco";i:3008;s:5:"Stair";i:3009;s:3:"Len";i:3010;s:4:"Saga";i:3011;s:9:"PyrroReTy";i:3012;s:4:"Nigg";i:3013;s:6:"ProSki";i:3014;s:8:"AutoPeri";i:3015;s:12:"NitroPieTric";i:3016;s:5:"StaTu";i:3017;s:3:"Rec";i:3018;s:4:"Pall";i:3019;s:12:"PreceShamUpl";i:3020;s:7:"CoviWag";i:3021;s:6:"ImTran";i:3022;s:13:"MandaStageUnf";i:3023;s:9:"SemisXero";i:3024;s:3:"Eso";i:3025;s:7:"FumatTr";i:3026;s:4:"Weev";i:3027;s:8:"IntraOst";i:3028;s:11:"GaNeurUngel";i:3029;s:7:"UnUnUnp";i:3030;s:5:"SiTan";i:3031;s:5:"FlPhy";i:3032;s:10:"GmeRubWarl";i:3033;s:9:"CayapTerm";i:3034;s:10:"MoRefinVau";i:3035;s:5:"Phosp";i:3036;s:12:"EligiHexUnkn";i:3037;s:6:"OvUnor";i:3038;s:5:"Verbi";i:3039;s:4:"Wind";i:3040;s:6:"BoroPh";i:3041;s:12:"MesoRudSinec";i:3042;s:10:"DisHemaJam";i:3043;s:8:"ReSiphSt";i:3044;s:4:"Pret";i:3045;s:4:"Pros";i:3046;s:8:"GlaObstu";i:3047;s:8:"JouPaPip";i:3048;s:8:"PunUndef";i:3049;s:5:"Ultra";i:3050;s:3:"Pur";i:3051;s:12:"MisMockxNomo";i:3052;s:12:"TactThacUnhe";i:3053;s:8:"PostSund";i:3054;s:9:"QuadrSavi";i:3055;s:7:"RecTepi";i:3056;s:12:"RutteTeUnplu";i:3057;s:6:"EncPar";i:3058;s:6:"ElSexu";i:3059;s:5:"MonSe";i:3060;s:4:"Velo";i:3061;s:10:"IthaTropUn";i:3062;s:9:"DuffTaute";i:3063;s:9:"ThesaTrUn";i:3064;s:5:"Unson";i:3065;s:14:"HumisKensiPanp";i:3066;s:9:"NectShTil";i:3067;s:4:"Pais";i:3068;s:8:"FeatSemi";i:3069;s:3:"Scu";i:3070;s:11:"DispePhWavy";i:3071;s:9:"ReSexivSp";i:3072;s:12:"NonfeNonlaPh";i:3073;s:9:"CuForYipx";i:3074;s:5:"DiHap";i:3075;s:11:"OversPhytSp";i:3076;s:8:"OrthScor";i:3077;s:3:"Ofo";i:3078;s:6:"MyoPic";i:3079;s:9:"CarLeNaos";i:3080;s:8:"PalmSati";i:3081;s:9:"SpokUnimo";i:3082;s:5:"Gorin";i:3083;s:9:"DyOligUnc";i:3084;s:9:"PhorSpaeb";i:3085;s:9:"ParaSumpt";i:3086;s:11:"CloConDidym";i:3087;s:11:"MenUnaWanto";i:3088;s:5:"Tetra";i:3089;s:7:"PreTurn";i:3090;s:8:"SliTitiv";i:3091;s:8:"HowisUns";i:3092;s:12:"MemPlantUnce";i:3093;s:11:"OstePindSuc";i:3094;s:12:"NonPhycSpect";i:3095;s:4:"Pogo";i:3096;s:2:"Ex";i:3097;s:4:"SaSt";i:3098;s:14:"SeedxSheexSupe";i:3099;s:5:"Fring";i:3100;s:13:"ExcoUndefUnev";i:3101;s:6:"IrRamx";i:3102;s:3:"Rab";i:3103;s:2:"Oe";i:3104;s:8:"UnevZhmu";i:3105;s:5:"OutPr";i:3106;s:3:"Ger";i:3107;s:6:"MesVer";i:3108;s:9:"FuchTidel";i:3109;s:10:"ChRetUnter";i:3110;s:4:"Mall";i:3111;s:10:"ReddySanda";i:3112;s:3:"Ido";i:3113;s:12:"FonSylviUnha";i:3114;s:10:"SamsoTrans";i:3115;s:6:"OrchTe";i:3116;s:10:"NotSemUnse";i:3117;s:4:"Ultr";i:3118;s:4:"PoUn";i:3119;s:8:"TransVal";i:3120;s:8:"FuciGlob";i:3121;s:7:"ScotTet";i:3122;s:15:"RicheRonsdWindo";i:3123;s:5:"Steel";i:3124;s:5:"Semif";i:3125;s:6:"SupUpw";i:3126;s:4:"Bedp";i:3127;s:13:"DagDispiUnsen";i:3128;s:3:"Coa";i:3129;s:12:"JetPedalSego";i:3130;s:4:"Lion";i:3131;s:11:"CymSnipjTeu";i:3132;s:4:"Prop";i:3133;s:7:"NeuroYi";i:3134;s:9:"MasMeTrom";i:3135;s:10:"EthylLoRef";i:3136;s:10:"CombDogSpi";i:3137;s:8:"FrLoMidm";i:3138;s:4:"Myog";i:3139;s:9:"PrScleSub";i:3140;s:13:"RefTuboaUnsor";i:3141;s:9:"EvasiStei";i:3142;s:9:"SpUnValix";i:3143;s:5:"Spira";i:3144;s:4:"EuPs";i:3145;s:9:"HardPenna";i:3146;s:5:"Wonde";i:3147;s:12:"NeonVocXipho";i:3148;s:10:"ChalJuckRe";i:3149;s:8:"DeopMadd";i:3150;s:4:"Grea";i:3151;s:3:"Tik";i:3152;s:5:"SuTar";i:3153;s:9:"LimacScTo";i:3154;s:13:"OverpPenxUnci";i:3155;s:5:"Rimpi";i:3156;s:4:"PySa";i:3157;s:10:"HydLamaiSq";i:3158;s:12:"HyReserSpiro";i:3159;s:11:"BeaBothDrif";i:3160;s:10:"PlRhiSikar";i:3161;s:4:"Olid";i:3162;s:7:"DiSperm";i:3163;s:4:"Prel";i:3164;s:5:"Mioce";i:3165;s:5:"Nonim";i:3166;s:5:"Prize";i:3167;s:7:"StasVul";i:3168;s:10:"PreliWoods";i:3169;s:3:"Rif";i:3170;s:3:"Bur";i:3171;s:13:"PleurUncluVes";i:3172;s:6:"RefSam";i:3173;s:8:"TickeXer";i:3174;s:4:"Unla";i:3175;s:7:"HyPelec";i:3176;s:12:"ForHumbuMalm";i:3177;s:9:"FuOrbitPh";i:3178;s:7:"FerFoRe";i:3179;s:4:"CoVe";i:3180;s:6:"NuncSu";i:3181;s:7:"CuUnfor";i:3182;s:11:"DemoPerStru";i:3183;s:6:"ReevUn";i:3184;s:9:"UndeWhelk";i:3185;s:4:"FuTu";i:3186;s:5:"Ithom";i:3187;s:5:"Sopex";i:3188;s:7:"InSpeci";i:3189;s:5:"Nontu";i:3190;s:6:"HomPre";i:3191;s:10:"InteMyceSu";i:3192;s:9:"CelLoSlac";i:3193;s:4:"Piny";i:3194;s:13:"HeterSpionTur";i:3195;s:4:"Slot";i:3196;s:8:"MetPresh";i:3197;s:13:"DisiNonnuVert";i:3198;s:9:"ArOdonWhi";i:3199;s:8:"InInteMe";i:3200;s:4:"Weat";i:3201;s:9:"GaUnsupUn";i:3202;s:7:"PrisoSh";i:3203;s:10:"IndiLycMol";i:3204;s:7:"PaRehUn";i:3205;s:7:"PollPro";i:3206;s:3:"Gro";i:3207;s:4:"Past";i:3208;s:10:"LambsPanty";i:3209;s:9:"ImpliProc";i:3210;s:5:"Cried";i:3211;s:6:"CycaUn";i:3212;s:14:"KeaxScreeUnlet";i:3213;s:8:"NundRevo";i:3214;s:12:"MadSherWeebl";i:3215;s:2:"Hi";i:3216;s:11:"RajaUnWokex";i:3217;s:12:"PseudRiviSte";i:3218;s:7:"FlaPaUn";i:3219;s:3:"Tax";i:3220;s:4:"PhPu";i:3221;s:11:"GraPsammStr";i:3222;s:14:"CelluEquiRailb";i:3223;s:7:"CongKer";i:3224;s:7:"MagnePs";i:3225;s:13:"NovelSeqSignl";i:3226;s:10:"DauncFoute";i:3227;s:11:"ArianPucRig";i:3228;s:10:"AlaCapThir";i:3229;s:7:"ConteDe";i:3230;s:12:"RupRuskySpri";i:3231;s:8:"FreUncon";i:3232;s:8:"HxLonYvo";i:3233;s:9:"PeriaStag";i:3234;s:2:"Op";i:3235;s:6:"LibiVa";i:3236;s:7:"PouTumu";i:3237;s:5:"Unerr";i:3238;s:7:"OblOrch";i:3239;s:8:"CountUng";i:3240;s:10:"NordiUneas";i:3241;s:9:"CtOrtSail";i:3242;s:5:"Roomm";i:3243;s:4:"Fant";i:3244;s:3:"Gan";i:3245;s:6:"PeUnle";i:3246;s:8:"MuPolRok";i:3247;s:9:"DrawHyper";i:3248;s:13:"EpiMaywiNotho";i:3249;s:7:"LySaxTe";i:3250;s:6:"NoncRh";i:3251;s:6:"FoUnsh";i:3252;s:3:"Sym";i:3253;s:11:"DiaOveThraw";i:3254;s:8:"CycloSem";i:3255;s:8:"PanjaThe";i:3256;s:2:"Ri";i:3257;s:5:"SuUnm";i:3258;s:9:"ArCoPirop";i:3259;s:9:"GoffLibMa";i:3260;s:10:"InsepTalpa";i:3261;s:4:"Steg";i:3262;s:5:"Renai";i:3263;s:9:"SubUnUnwi";i:3264;s:9:"PerPersQu";i:3265;s:5:"DisHo";i:3266;s:4:"Test";i:3267;s:8:"OpenhShi";i:3268;s:4:"SaTe";i:3269;s:5:"SarVe";i:3270;s:8:"PrSpVisi";i:3271;s:9:"KeLoloxRe";i:3272;s:3:"Exe";i:3273;s:4:"Plet";i:3274;s:3:"Uno";i:3275;s:5:"Overr";i:3276;s:7:"MyQuadr";i:3277;s:8:"MacSemSo";i:3278;s:11:"EgghoFeThur";i:3279;s:9:"GaNeTickl";i:3280;s:10:"CafEnlUnpr";i:3281;s:7:"CroScap";i:3282;s:12:"EspaNonmValv";i:3283;s:7:"ParaUnb";i:3284;s:3:"Tal";i:3285;s:9:"CysDitNon";i:3286;s:5:"Unsan";i:3287;s:5:"GlyRe";i:3288;s:4:"Tent";i:3289;s:3:"Bio";i:3290;s:9:"MeSpielUn";i:3291;s:12:"GrufRewoUnch";i:3292;s:5:"EumSe";i:3293;s:3:"Mog";i:3294;s:7:"SoUnswa";i:3295;s:9:"RidSpXero";i:3296;s:4:"Pock";i:3297;s:5:"Steno";i:3298;s:9:"EndMiliVi";i:3299;s:6:"EvocRh";i:3300;s:8:"JoQuisUn";i:3301;s:4:"Gemm";i:3302;s:7:"PinSoma";i:3303;s:6:"JubMul";i:3304;s:10:"BecivBrent";i:3305;s:9:"JaunSynco";i:3306;s:10:"KenMalSure";i:3307;s:8:"SeTriaWo";i:3308;s:9:"ScTriUpal";i:3309;s:10:"FeMycoZucc";i:3310;s:7:"FeUntru";i:3311;s:10:"ScathWeath";i:3312;s:5:"Knubb";i:3313;s:5:"OvUnu";i:3314;s:11:"DoNonliUnre";i:3315;s:5:"Relie";i:3316;s:4:"Must";i:3317;s:4:"PaRe";i:3318;s:2:"Il";i:3319;s:9:"DartPaTat";i:3320;s:5:"KilPr";i:3321;s:4:"DoMi";i:3322;s:7:"ScoSuff";i:3323;s:5:"PirSi";i:3324;s:4:"Rota";i:3325;s:3:"Wad";i:3326;s:14:"InfaMesolMonoh";i:3327;s:7:"RigidUn";i:3328;s:10:"RosSimilUn";i:3329;s:3:"Thr";i:3330;s:9:"DecolDupl";i:3331;s:11:"DialGlazePr";i:3332;s:6:"FunUnt";i:3333;s:6:"ScSemi";i:3334;s:4:"Elec";i:3335;s:14:"HeauLargeUpsta";i:3336;s:7:"SpUptra";i:3337;s:3:"Sno";i:3338;s:7:"NoNontr";i:3339;s:7:"DubTurb";i:3340;s:9:"MilkSupra";i:3341;s:7:"PhiPySu";i:3342;s:14:"NightPerioRect";i:3343;s:4:"EuMe";i:3344;s:12:"FuliLutePros";i:3345;s:12:"AutDeroEyoty";i:3346;s:10:"HulPrusSpu";i:3347;s:7:"PatWeez";i:3348;s:5:"Fragm";i:3349;s:6:"NuTigx";i:3350;s:6:"InfUlt";i:3351;s:6:"CoSubj";i:3352;s:8:"GreHyLea";i:3353;s:6:"CypsEm";i:3354;s:4:"Unsa";i:3355;s:5:"GuPro";i:3356;s:4:"Rotu";i:3357;s:5:"DoEmb";i:3358;s:3:"Pia";i:3359;s:7:"DrMoroc";i:3360;s:7:"LeOpsim";i:3361;s:5:"Sextu";i:3362;s:13:"EpipRecaShoop";i:3363;s:9:"ExaraSiVe";i:3364;s:12:"PreasSooUndi";i:3365;s:6:"FroInv";i:3366;s:7:"MorthPa";i:3367;s:3:"Wri";i:3368;s:7:"OverTet";i:3369;s:3:"Yea";i:3370;s:4:"Sand";i:3371;s:11:"DemonDiManq";i:3372;s:5:"MoiTi";i:3373;s:8:"ForsHype";i:3374;s:8:"HaloMont";i:3375;s:11:"InvPacUnexh";i:3376;s:12:"SilvUnaggWir";i:3377;s:8:"MuSemSen";i:3378;s:4:"Disp";i:3379;s:6:"ProUnq";i:3380;s:10:"ImploVenet";i:3381;s:4:"Hors";i:3382;s:5:"LucTh";i:3383;s:9:"ObliTibio";i:3384;s:4:"Homo";i:3385;s:12:"MonoSabSelen";i:3386;s:7:"NonSubm";i:3387;s:4:"Seri";i:3388;s:8:"CypriTeh";i:3389;s:8:"LocSewer";i:3390;s:12:"PiliTubUnmor";i:3391;s:5:"Beami";i:3392;s:8:"HyRegRep";i:3393;s:7:"SmUnrou";i:3394;s:7:"HeNoUnd";i:3395;s:8:"CyResSob";i:3396;s:9:"ElecaMocm";i:3397;s:5:"Quini";i:3398;s:10:"TegeaUnsca";i:3399;s:10:"ScorSeTrip";i:3400;s:8:"OligSeTh";i:3401;s:9:"HuRooUnde";i:3402;s:5:"Razor";i:3403;s:4:"Uncl";i:3404;s:6:"ScriSu";i:3405;s:4:"Unpu";i:3406;s:8:"RomneSin";i:3407;s:4:"Supe";i:3408;s:5:"Subor";i:3409;s:10:"PhotoProSt";i:3410;s:5:"MaVer";i:3411;s:8:"GrImToba";i:3412;s:10:"PaRetUnfor";i:3413;s:9:"PanPriaRe";i:3414;s:8:"FlokiPht";i:3415;s:6:"PreSol";i:3416;s:8:"QuiniVar";i:3417;s:12:"BurnRetSphae";i:3418;s:7:"GondSpe";i:3419;s:5:"Pseud";i:3420;s:7:"MeRespe";i:3421;s:5:"OtTur";i:3422;s:7:"OuPedRe";i:3423;s:10:"CeraNoSara";i:3424;s:7:"EpiFiRe";i:3425;s:14:"SulphThallTwee";i:3426;s:7:"OuRhema";i:3427;s:4:"Fanc";i:3428;s:8:"OoscoWay";i:3429;s:14:"ComfoHomodHypo";i:3430;s:12:"ImTapisUnfit";i:3431;s:4:"Laix";i:3432;s:5:"Unfes";i:3433;s:2:"Bu";i:3434;s:7:"EtReoTo";i:3435;s:9:"SnippYard";i:3436;s:5:"SaUnd";i:3437;s:5:"Osteo";i:3438;s:8:"PageaUnd";i:3439;s:8:"InhNonUn";i:3440;s:14:"MiscPilasSilic";i:3441;s:9:"OvePnStyc";i:3442;s:8:"BaNoncTo";i:3443;s:5:"Super";i:3444;s:6:"IrSeUn";i:3445;s:9:"CountEchi";i:3446;s:5:"BliRo";i:3447;s:11:"KeMoveaUnha";i:3448;s:15:"CratcSeamrSuper";i:3449;s:4:"Mist";i:3450;s:4:"Wrec";i:3451;s:7:"CenNons";i:3452;s:5:"Confr";i:3453;s:9:"IndecUnal";i:3454;s:5:"Groov";i:3455;s:5:"Nonre";i:3456;s:12:"OuthiSoSuper";i:3457;s:6:"HeSubc";i:3458;s:15:"MetriScuftTable";i:3459;s:5:"Satie";i:3460;s:12:"IncoOrphaSqu";i:3461;s:4:"HyTh";i:3462;s:10:"DirepGiMoi";i:3463;s:3:"Mor";i:3464;s:5:"PaPho";i:3465;s:6:"TavTol";i:3466;s:9:"IsthmNapp";i:3467;s:6:"CopSpl";i:3468;s:6:"BuTren";i:3469;s:11:"PrepUncrUnp";i:3470;s:3:"Mis";i:3471;s:5:"Twitt";i:3472;s:5:"Koiar";i:3473;s:10:"BuHirTetra";i:3474;s:7:"DipiWis";i:3475;s:5:"BasGh";i:3476;s:11:"DeziPreRune";i:3477;s:5:"PerPr";i:3478;s:5:"Scrol";i:3479;s:9:"CenSapSna";i:3480;s:12:"ObraOchleTyk";i:3481;s:6:"AreLan";i:3482;s:10:"MeniNoProa";i:3483;s:4:"Wadm";i:3484;s:10:"LaMotleUnd";i:3485;s:7:"CoSurfe";i:3486;s:5:"KaUnm";i:3487;s:5:"IlSim";i:3488;s:5:"Plero";i:3489;s:9:"LanceSele";i:3490;s:2:"Ro";i:3491;s:11:"DrawsSecuVe";i:3492;s:4:"Vine";i:3493;s:5:"Mythm";i:3494;s:8:"DiJesuKo";i:3495;s:9:"CorLiScut";i:3496;s:5:"ObeOr";i:3497;s:9:"PreStouTe";i:3498;s:8:"SubUnfor";i:3499;s:11:"DiEtonThala";i:3500;s:13:"FamOrallSuper";i:3501;s:5:"Unrib";i:3502;s:7:"PrReUno";i:3503;s:11:"MichTricWai";i:3504;s:8:"OveUtric";i:3505;s:5:"Propa";i:3506;s:10:"PolycVikin";i:3507;s:9:"DivMeduTh";i:3508;s:13:"LuxuTranViper";i:3509;s:8:"DiFrHypo";i:3510;s:13:"MagniMontWres";i:3511;s:10:"CantDeUnci";i:3512;s:10:"PeScripUnf";i:3513;s:10:"ClaitSpUnc";i:3514;s:10:"DecoPhoSup";i:3515;s:5:"InSys";i:3516;s:13:"RhetoUdoUnder";i:3517;s:4:"Marr";i:3518;s:5:"LaRed";i:3519;s:10:"KilolPiUns";i:3520;s:5:"Monoc";i:3521;s:13:"GalliUreViato";i:3522;s:3:"Mac";i:3523;s:9:"SadhScamm";i:3524;s:11:"PhoenSinUns";i:3525;s:8:"EpiUnjud";i:3526;s:8:"JasMonTu";i:3527;s:8:"ParWangx";i:3528;s:9:"AverCaste";i:3529;s:11:"BindMonoUnm";i:3530;s:3:"Sph";i:3531;s:5:"Postf";i:3532;s:5:"Oligo";i:3533;s:10:"ComexRodin";i:3534;s:4:"ThWe";i:3535;s:5:"Unfra";i:3536;s:10:"PleurUnaug";i:3537;s:8:"DepeNeSe";i:3538;s:10:"DiProRaddl";i:3539;s:7:"FeThere";i:3540;s:10:"HenOverpSi";i:3541;s:5:"Epido";i:3542;s:5:"GarTr";i:3543;s:7:"HypThZi";i:3544;s:4:"ReTw";i:3545;s:5:"Trans";i:3546;s:8:"AfReSchi";i:3547;s:5:"Dacry";i:3548;s:12:"QuartTruncWi";i:3549;s:10:"DishoFooti";i:3550;s:12:"ChasInvLutec";i:3551;s:4:"Whip";i:3552;s:5:"MaWin";i:3553;s:5:"HyUns";i:3554;s:11:"EmEvangPoly";i:3555;s:5:"Submo";i:3556;s:4:"PaSw";i:3557;s:6:"DipLac";i:3558;s:7:"MegRemo";i:3559;s:12:"ExtraHymeSec";i:3560;s:7:"OutlePl";i:3561;s:12:"KhasRanjUnle";i:3562;s:10:"RhRounxSan";i:3563;s:4:"Thea";i:3564;s:12:"GraRelatUnri";i:3565;s:4:"Snar";i:3566;s:4:"SoTh";i:3567;s:5:"Undel";i:3568;s:9:"GazHydSem";i:3569;s:11:"CatCopiUnho";i:3570;s:5:"Cerol";i:3571;s:6:"MisrTa";i:3572;s:11:"IntJouLiqui";i:3573;s:3:"Sec";i:3574;s:8:"UnrowVil";i:3575;s:8:"LocoThTh";i:3576;s:10:"HandmUngue";i:3577;s:2:"Is";i:3578;s:11:"RefUterXant";i:3579;s:5:"Canid";i:3580;s:7:"InIsote";i:3581;s:7:"PeUncor";i:3582;s:5:"HoWin";i:3583;s:9:"DisadNoti";i:3584;s:13:"GuttMaeaRamma";i:3585;s:4:"UnWa";i:3586;s:13:"EpiMerrySexua";i:3587;s:7:"FlunkSu";i:3588;s:4:"Solv";i:3589;s:8:"MiNeOnag";i:3590;s:4:"Nibo";i:3591;s:11:"FlocHydMuri";i:3592;s:9:"CaJozyOle";i:3593;s:9:"FemeResti";i:3594;s:7:"PhRocce";i:3595;s:4:"Urun";i:3596;s:9:"DrakeFrum";i:3597;s:11:"InsaMaOctin";i:3598;s:8:"PertuTea";i:3599;s:9:"MisOuriPh";i:3600;s:12:"HeSeaweWretc";i:3601;s:4:"MaPo";i:3602;s:9:"PancQuidd";i:3603;s:2:"Wh";i:3604;s:7:"SmugSol";i:3605;s:4:"Tris";i:3606;s:5:"CeInf";i:3607;s:7:"OphiTeg";i:3608;s:6:"FoFrem";i:3609;s:10:"HabilStore";i:3610;s:3:"Ste";i:3611;s:3:"Vei";i:3612;s:6:"HetmSy";i:3613;s:13:"RivoSpinoZoop";i:3614;s:7:"GyrMiSo";i:3615;s:11:"MeNoncPathi";i:3616;s:10:"DerOrPobsx";i:3617;s:5:"Volti";i:3618;s:9:"PoacUnrep";i:3619;s:9:"ExacSnaUn";i:3620;s:3:"Sce";i:3621;s:7:"DisSple";i:3622;s:11:"KiMonoUnaro";i:3623;s:4:"Tall";i:3624;s:12:"MyelOldSheet";i:3625;s:3:"Con";i:3626;s:14:"CollExpedPreci";i:3627;s:11:"InfecNevYol";i:3628;s:8:"ExHyTomf";i:3629;s:3:"Shi";i:3630;s:2:"Na";i:3631;s:11:"ErGownsMous";i:3632;s:7:"ScUtsuk";i:3633;s:9:"IsoQuRecr";i:3634;s:6:"ChaHon";i:3635;s:11:"EquIconoNep";i:3636;s:10:"TuitiUnsin";i:3637;s:11:"FoxMooUnpor";i:3638;s:4:"Synt";i:3639;s:7:"HemNerv";i:3640;s:5:"Wroth";i:3641;s:7:"UnZirba";i:3642;s:10:"HeterThUnm";i:3643;s:8:"ReUnraVo";i:3644;s:7:"PissaRo";i:3645;s:9:"DiFretSix";i:3646;s:8:"RabUnpro";i:3647;s:12:"InteIrksLipo";i:3648;s:10:"ByrSumpUnp";i:3649;s:2:"Oo";i:3650;s:13:"AntimDaggPseu";i:3651;s:12:"SuVaginVerbe";i:3652;s:5:"InLar";i:3653;s:5:"Fibro";i:3654;s:10:"FoldMeUnab";i:3655;s:12:"BiPrediProar";i:3656;s:11:"SyphThrasTr";i:3657;s:11:"ExNitroPala";i:3658;s:7:"DegrPre";i:3659;s:13:"ExormFeudxTer";i:3660;s:7:"SuTraUn";i:3661;s:7:"NyaSche";i:3662;s:7:"EntoGer";i:3663;s:9:"MacrTrans";i:3664;s:6:"DenoGn";i:3665;s:12:"MultiUnbarUn";i:3666;s:10:"InsLipPenc";i:3667;s:10:"JanTyroWhi";i:3668;s:10:"HalHydruMo";i:3669;s:3:"Irr";i:3670;s:13:"SkaffSubTriad";i:3671;s:5:"Windr";i:3672;s:5:"Unebb";i:3673;s:5:"RevWi";i:3674;s:7:"MajTend";i:3675;s:7:"PreUnbe";i:3676;s:12:"MyoOutscPala";i:3677;s:10:"DrainIlUnm";i:3678;s:13:"RepeSaxxUndis";i:3679;s:11:"ImidMuPsila";i:3680;s:7:"DopLuci";i:3681;s:7:"DecTezx";i:3682;s:9:"ToothViri";i:3683;s:14:"MagneRaspiTach";i:3684;s:10:"SluSwYalix";i:3685;s:10:"PhanSyUnel";i:3686;s:8:"HuReSpec";i:3687;s:4:"Pitc";i:3688;s:8:"HiHyShor";i:3689;s:12:"LieNonacPseu";i:3690;s:13:"KnaOinomSerri";i:3691;s:6:"EntUph";i:3692;s:9:"OdoSaraSu";i:3693;s:5:"Varis";i:3694;s:6:"CorOrp";i:3695;s:9:"BlNocSpli";i:3696;s:7:"SwomWis";i:3697;s:12:"PanscResWigw";i:3698;s:6:"RepTin";i:3699;s:5:"Chitc";i:3700;s:6:"InquMi";i:3701;s:6:"InWhin";i:3702;s:7:"HerTabo";i:3703;s:7:"RackfTu";i:3704;s:10:"MediRabbUn";i:3705;s:12:"CauloExospIn";i:3706;s:11:"HydroSiUnch";i:3707;s:8:"CyPrRect";i:3708;s:7:"AllBand";i:3709;s:8:"HystMail";i:3710;s:12:"HypLooseSole";i:3711;s:10:"CoFreckNon";i:3712;s:10:"AnEffeTali";i:3713;s:10:"DoctrPerit";i:3714;s:5:"SiUna";i:3715;s:4:"Rhiz";i:3716;s:4:"OuRe";i:3717;s:4:"Hobb";i:3718;s:3:"Cer";i:3719;s:9:"AnOrangRa";i:3720;s:9:"SoulfSpZo";i:3721;s:11:"UnreUnsVeto";i:3722;s:5:"HyTec";i:3723;s:9:"AtPhotUns";i:3724;s:4:"Lune";i:3725;s:3:"Tro";i:3726;s:8:"PhagoPim";i:3727;s:9:"NagNoUnde";i:3728;s:9:"FezzeMike";i:3729;s:10:"RiffxVersa";i:3730;s:8:"SigSusan";i:3731;s:7:"OverRei";i:3732;s:10:"NonOverPer";i:3733;s:11:"CoundSpriTc";i:3734;s:5:"Bismu";i:3735;s:9:"ClefThrea";i:3736;s:9:"PinkTetra";i:3737;s:5:"Therm";i:3738;s:5:"PeXyl";i:3739;s:5:"SaSma";i:3740;s:15:"PerinTransUnart";i:3741;s:5:"Pytha";i:3742;s:7:"SyUngen";i:3743;s:9:"BeMonotPa";i:3744;s:11:"DiDictyMoto";i:3745;s:11:"PolyTrWarbl";i:3746;s:9:"FaForeHor";i:3747;s:4:"Inkl";i:3748;s:5:"Perfr";i:3749;s:8:"FunMeRep";i:3750;s:10:"PrePrSkunk";i:3751;s:6:"DoHepa";i:3752;s:12:"NonParoPhilo";i:3753;s:7:"GroUnre";i:3754;s:9:"PillaUnwa";i:3755;s:10:"CixoxSocTh";i:3756;s:6:"HypPat";i:3757;s:15:"TautoTreacUnhar";i:3758;s:4:"Cant";i:3759;s:7:"DisUnsa";i:3760;s:15:"InconMilleWenon";i:3761;s:13:"EmbryNonacPre";i:3762;s:5:"Nonfl";i:3763;s:12:"RegaTripUngr";i:3764;s:9:"ScratWarn";i:3765;s:6:"BiGluc";i:3766;s:7:"BaPolyp";i:3767;s:11:"NovaReskSto";i:3768;s:4:"Utte";i:3769;s:6:"PlaPle";i:3770;s:9:"HematMona";i:3771;s:8:"OphZoono";i:3772;s:10:"StaidUnpre";i:3773;s:9:"DispoIntr";i:3774;s:7:"JeluJet";i:3775;s:4:"Four";i:3776;s:10:"IntMycoUpl";i:3777;s:5:"Skulk";i:3778;s:11:"LepPoodlSer";i:3779;s:13:"OxyrhSpiSquar";i:3780;s:7:"ExHulPa";i:3781;s:5:"SaSod";i:3782;s:15:"PretySweepSweet";i:3783;s:11:"GryMystSter";i:3784;s:5:"RuSmo";i:3785;s:7:"SuperUn";i:3786;s:8:"PolStorm";i:3787;s:8:"IncliPac";i:3788;s:5:"Prepr";i:3789;s:8:"MeckeMeg";i:3790;s:5:"Unobj";i:3791;s:3:"Gra";i:3792;s:9:"OwerlPrTe";i:3793;s:9:"BrShaVesi";i:3794;s:5:"Thaum";i:3795;s:2:"Ki";i:3796;s:7:"HeUngui";i:3797;s:4:"Sout";i:3798;s:4:"Ross";i:3799;s:8:"EyeWhips";i:3800;s:10:"HeIatroUnp";i:3801;s:5:"MePro";i:3802;s:5:"Fault";i:3803;s:9:"MutaSabba";i:3804;s:3:"Swa";i:3805;s:8:"ReSoWive";i:3806;s:5:"FlIsl";i:3807;s:4:"HiPh";i:3808;s:8:"ChelMerc";i:3809;s:6:"BlaReq";i:3810;s:4:"Codi";i:3811;s:9:"SpTriWote";i:3812;s:9:"LegiPyrSt";i:3813;s:6:"EnLida";i:3814;s:4:"Ungr";i:3815;s:11:"PuSchoSnudg";i:3816;s:8:"HyOsmUnh";i:3817;s:7:"UndeZos";i:3818;s:12:"PhilProvSnap";i:3819;s:11:"OoPeriSangu";i:3820;s:5:"Smutt";i:3821;s:5:"MePac";i:3822;s:5:"Thuri";i:3823;s:14:"ArsenHerseSuba";i:3824;s:4:"Macc";i:3825;s:5:"Untra";i:3826;s:8:"CrNonPil";i:3827;s:4:"CaMe";i:3828;s:8:"PanTherm";i:3829;s:12:"InMesolPatac";i:3830;s:8:"MatriNon";i:3831;s:6:"GeoSex";i:3832;s:9:"NurseShit";i:3833;s:4:"Stro";i:3834;s:11:"BavPreinTug";i:3835;s:11:"KarsMuUnwit";i:3836;s:4:"Meta";i:3837;s:7:"OvPrill";i:3838;s:7:"MiMucos";i:3839;s:10:"CouMobsSha";i:3840;s:4:"Visi";i:3841;s:7:"SmooSub";i:3842;s:6:"CyPaSi";i:3843;s:9:"MyPacUntu";i:3844;s:5:"Oricy";i:3845;s:3:"Sup";i:3846;s:6:"TerUnd";i:3847;s:8:"IsMonoSa";i:3848;s:9:"HidSunrWo";i:3849;s:12:"MolaxSanUnst";i:3850;s:5:"MoPha";i:3851;s:8:"PieSubri";i:3852;s:5:"Watch";i:3853;s:4:"Team";i:3854;s:10:"PrSesaVind";i:3855;s:10:"HexadNaVac";i:3856;s:9:"MelanNoRa";i:3857;s:7:"PrSepSt";i:3858;s:5:"Sridh";i:3859;s:5:"MasPo";i:3860;s:7:"LiStatu";i:3861;s:10:"SprawTrans";i:3862;s:8:"LooPhoto";i:3863;s:9:"CaTaZingi";i:3864;s:3:"Oro";i:3865;s:5:"Dermi";i:3866;s:7:"DrierRe";i:3867;s:10:"MiskPerSup";i:3868;s:3:"Wat";i:3869;s:6:"CoWelc";i:3870;s:11:"NonPediScho";i:3871;s:10:"JemmOuRubo";i:3872;s:5:"Urtic";i:3873;s:5:"FrGal";i:3874;s:6:"LaceVa";i:3875;s:8:"RecSubch";i:3876;s:5:"SaSph";i:3877;s:12:"MarylMnemoOn";i:3878;s:3:"Lie";i:3879;s:8:"FluPrSuf";i:3880;s:5:"Pangx";i:3881;s:13:"MediReoccUnda";i:3882;s:9:"CelFatGou";i:3883;s:4:"Xant";i:3884;s:4:"Moil";i:3885;s:11:"DeltForIned";i:3886;s:7:"RegiUnp";i:3887;s:7:"KabylTh";i:3888;s:8:"ShaTrade";i:3889;s:7:"VilWaWi";i:3890;s:9:"MappiOsph";i:3891;s:5:"DriSl";i:3892;s:5:"Tauto";i:3893;s:5:"Sinia";i:3894;s:5:"PrUnr";i:3895;s:7:"UnsVerb";i:3896;s:13:"DesExploTorsi";i:3897;s:5:"PaUna";i:3898;s:8:"PhotUpis";i:3899;s:12:"FemHalfSubur";i:3900;s:10:"BesoDolMem";i:3901;s:9:"WordZebra";i:3902;s:5:"Sweep";i:3903;s:7:"TrUnpro";i:3904;s:11:"ResSerorWid";i:3905;s:8:"SafrSouv";i:3906;s:4:"Ridg";i:3907;s:11:"CancMessUnd";i:3908;s:6:"OvUnUn";i:3909;s:9:"ShSqueUne";i:3910;s:7:"OratoRo";i:3911;s:5:"GaSpe";i:3912;s:7:"CoOolog";i:3913;s:5:"NonPr";i:3914;s:9:"VanguWaho";i:3915;s:7:"ConseLa";i:3916;s:4:"Zimb";i:3917;s:2:"Im";i:3918;s:11:"AzDiasFonsx";i:3919;s:11:"CrEmbleGale";i:3920;s:7:"DiHonUn";i:3921;s:8:"PerjSphy";i:3922;s:12:"PomonStUnvol";i:3923;s:9:"MiPokoTik";i:3924;s:10:"BikhxConic";i:3925;s:4:"BiFi";i:3926;s:6:"CruFig";i:3927;s:4:"Requ";i:3928;s:5:"Amoeb";i:3929;s:9:"FeathHusb";i:3930;s:8:"ExcrLave";i:3931;s:3:"Obl";i:3932;s:10:"HyperMonSh";i:3933;s:7:"UnerrXe";i:3934;s:8:"HypJibNe";i:3935;s:11:"HousSbUnent";i:3936;s:4:"Pyra";i:3937;s:10:"BacParaSub";i:3938;s:5:"Occam";i:3939;s:12:"MichiRumblTu";i:3940;s:4:"Supr";i:3941;s:7:"FeUnsuf";i:3942;s:7:"DowieSp";i:3943;s:10:"FaceOppoUn";i:3944;s:12:"MetamTopoUbi";i:3945;s:7:"PeriPre";i:3946;s:7:"PsycSep";i:3947;s:8:"MeloUncl";i:3948;s:8:"UndanWau";i:3949;s:11:"AutoChoResy";i:3950;s:7:"OvPegUn";i:3951;s:10:"ArseSoyUns";i:3952;s:5:"SpeUn";i:3953;s:4:"Pinn";i:3954;s:5:"Unspr";i:3955;s:3:"Glo";i:3956;s:5:"BraLe";i:3957;s:10:"IdylNonUnm";i:3958;s:9:"ChilChimp";i:3959;s:2:"Hy";i:3960;s:11:"KharNicomPi";i:3961;s:14:"HemipPariUnpre";i:3962;s:8:"TexUnciv";i:3963;s:12:"MitNaphtRevi";i:3964;s:12:"IraqiKoreTat";i:3965;s:12:"HephInfuThix";i:3966;s:13:"ButtFratcPrer";i:3967;s:11:"BanExtraPro";i:3968;s:10:"PsPulpVomi";i:3969;s:9:"DraffTutw";i:3970;s:10:"NoSoliTheo";i:3971;s:6:"NonvSo";i:3972;s:10:"BeSpurSuba";i:3973;s:3:"Rey";i:3974;s:8:"FadGuTel";i:3975;s:12:"FluGeocPascu";i:3976;s:5:"Punty";i:3977;s:5:"Alcid";i:3978;s:5:"Soggy";i:3979;s:7:"PurpRes";i:3980;s:3:"Por";i:3981;s:7:"MultThw";i:3982;s:2:"Of";i:3983;s:7:"GalicOd";i:3984;s:13:"HyperMilUndis";i:3985;s:7:"ElegiUn";i:3986;s:4:"Inst";i:3987;s:8:"MonkcPan";i:3988;s:4:"Jawx";i:3989;s:12:"CoDonneRewar";i:3990;s:11:"MerkSemidUn";i:3991;s:14:"ErectRomerSero";i:3992;s:4:"Jett";i:3993;s:10:"DeJohRahul";i:3994;s:2:"Ar";i:3995;s:5:"Silic";i:3996;s:10:"PrearPrSem";i:3997;s:4:"Meso";i:3998;s:10:"CysLicheRe";i:3999;s:4:"Snag";i:4000;s:9:"GenecPres";i:4001;s:7:"PropSha";i:4002;s:9:"HamaTraUn";i:4003;s:9:"GyroMatUn";i:4004;s:12:"MisuNonParas";i:4005;s:13:"KindeOvePrein";i:4006;s:13:"IconUnentUnpl";i:4007;s:10:"MyrmeNotel";i:4008;s:10:"KrobPoUnsa";i:4009;s:10:"IndusPalau";i:4010;s:7:"HostPar";i:4011;s:9:"HelPreiSu";i:4012;s:5:"Kacha";i:4013;s:3:"Nac";i:4014;s:7:"SafeWan";i:4015;s:8:"ScrViost";i:4016;s:9:"LoSinnSub";i:4017;s:5:"Vedai";i:4018;s:12:"IdeoPanVaunt";i:4019;s:9:"DeErgxOcy";i:4020;s:8:"HanMePal";i:4021;s:7:"ShValky";i:4022;s:11:"DingIncuTel";i:4023;s:8:"PePillSp";i:4024;s:6:"FocPos";i:4025;s:5:"KnaTr";i:4026;s:4:"Wint";i:4027;s:12:"InsiTheUnmis";i:4028;s:7:"AtmogLe";i:4029;s:8:"MePalRec";i:4030;s:8:"ComKwann";i:4031;s:4:"Over";i:4032;s:12:"NiobiNoneqTo";i:4033;s:11:"KiScytStrop";i:4034;s:4:"Wast";i:4035;s:10:"CasuInteMo";i:4036;s:5:"ShWhi";i:4037;s:3:"Wro";i:4038;s:9:"SwasThTot";i:4039;s:5:"AndSc";i:4040;s:6:"GrapSc";i:4041;s:7:"FirepUn";i:4042;s:13:"BoerBuffaPass";i:4043;s:7:"CoSpTin";i:4044;s:3:"Gor";i:4045;s:7:"DisbeHi";i:4046;s:5:"EleSi";i:4047;s:12:"LympUninkYer";i:4048;s:5:"ShaSl";i:4049;s:9:"IlLiPlagi";i:4050;s:7:"CeOrtho";i:4051;s:7:"AntShel";i:4052;s:7:"DiPeris";i:4053;s:3:"Flo";i:4054;s:10:"GladiNonfa";i:4055;s:4:"Unen";i:4056;s:5:"Myosa";i:4057;s:8:"ReqUnder";i:4058;s:8:"IdyTortr";i:4059;s:10:"CanPrTheri";i:4060;s:2:"Ur";i:4061;s:10:"AtollCorti";i:4062;s:8:"DexGestx";i:4063;s:6:"MerQua";i:4064;s:4:"Usat";i:4065;s:14:"BromLicenNaseb";i:4066;s:11:"GoOrthoSull";i:4067;s:11:"QuinSuperUn";i:4068;s:12:"ConfKoftPass";i:4069;s:8:"OrgaSkTr";i:4070;s:6:"CheaOu";i:4071;s:11:"BafGynRamul";i:4072;s:9:"OpPerZepp";i:4073;s:9:"HypMerTom";i:4074;s:7:"RarelUn";i:4075;s:13:"EpidoPostcSta";i:4076;s:7:"VandyWh";i:4077;s:10:"OutwaRapSo";i:4078;s:11:"OverSouWall";i:4079;s:11:"BeclaChorCo";i:4080;s:3:"Sin";i:4081;s:6:"RaSnak";i:4082;s:10:"FiHomLumbr";i:4083;s:12:"PapaProaThyr";i:4084;s:7:"IntTric";i:4085;s:10:"DisInfrOrl";i:4086;s:10:"GoldeIntVo";i:4087;s:8:"DainOoUl";i:4088;s:9:"FullMulti";i:4089;s:9:"MolRizWoo";i:4090;s:5:"Mimos";i:4091;s:5:"Unfun";i:4092;s:7:"HiSuber";i:4093;s:9:"OpistTrac";i:4094;s:9:"MourPreho";i:4095;s:10:"ProtoRebra";i:4096;s:7:"DiManif";i:4097;s:9:"RepuSubin";i:4098;s:5:"Khald";i:4099;s:5:"SkiSu";i:4100;s:8:"BecloNon";i:4101;s:5:"Mesol";i:4102;s:12:"InnaSmeltTur";i:4103;s:9:"ExtooSidt";i:4104;s:12:"InduTitTrywo";i:4105;s:8:"CoMarsMa";i:4106;s:3:"Wei";i:4107;s:6:"GrSiph";i:4108;s:9:"MelOvPyro";i:4109;s:9:"JovinUnin";i:4110;s:6:"OlPsyc";i:4111;s:10:"GiggeWinly";i:4112;s:5:"Shoem";i:4113;s:8:"CoSyncUn";i:4114;s:10:"NomOverhSp";i:4115;s:10:"TrembUncon";i:4116;s:8:"ReinScSm";i:4117;s:5:"Gager";i:4118;s:11:"TiUnprWorke";i:4119;s:9:"MaParanRe";i:4120;s:9:"StruUnche";i:4121;s:3:"Div";i:4122;s:13:"IndiNeomWoodb";i:4123;s:7:"FiFlaUn";i:4124;s:6:"BryCru";i:4125;s:9:"SubdoSubs";i:4126;s:9:"ReveUnWin";i:4127;s:2:"Ir";i:4128;s:7:"GlSkill";i:4129;s:8:"MagySent";i:4130;s:6:"SarsUn";i:4131;s:14:"SquirVaricVola";i:4132;s:6:"GallWa";i:4133;s:7:"PlumbPr";i:4134;s:10:"EmersHaStr";i:4135;s:10:"EffForSupe";i:4136;s:7:"ReiSpin";i:4137;s:12:"PeacPleTinny";i:4138;s:4:"Tara";i:4139;s:11:"DoghInterSi";i:4140;s:9:"KetPoSpik";i:4141;s:7:"LiMalle";i:4142;s:4:"Unbo";i:4143;s:12:"SharUnfVersa";i:4144;s:7:"UndeWea";i:4145;s:3:"Myx";i:4146;s:7:"DetPlut";i:4147;s:7:"IroqRec";i:4148;s:7:"IntPsha";i:4149;s:5:"PosSe";i:4150;s:10:"SubTimarUn";i:4151;s:5:"MetPa";i:4152;s:9:"HoSnarStr";i:4153;s:5:"Phall";i:4154;s:10:"NecrThUnte";i:4155;s:12:"GemaHortSwam";i:4156;s:4:"Nong";i:4157;s:7:"CerSole";i:4158;s:10:"LatinPenma";i:4159;s:11:"PieriRejSem";i:4160;s:5:"PrPro";i:4161;s:8:"MultiPri";i:4162;s:4:"Twol";i:4163;s:7:"TraTurb";i:4164;s:7:"NaNodRe";i:4165;s:5:"Ouaba";i:4166;s:6:"KerPlu";i:4167;s:4:"Pont";i:4168;s:3:"Gur";i:4169;s:11:"PersSpoliUn";i:4170;s:7:"PlantSl";i:4171;s:2:"Fl";i:4172;s:8:"DiSuUnbo";i:4173;s:8:"IseReque";i:4174;s:11:"GenePtVentr";i:4175;s:4:"Rain";i:4176;s:7:"TrUnUns";i:4177;s:7:"DisUnmi";i:4178;s:10:"OcuReVorti";i:4179;s:8:"AzComSiz";i:4180;s:11:"InterStaVes";i:4181;s:6:"FoPanc";i:4182;s:12:"DeutLifNocta";i:4183;s:4:"HyVo";i:4184;s:7:"OvePres";i:4185;s:6:"SaUnpr";i:4186;s:13:"IzduPostePros";i:4187;s:11:"PePiscoUnri";i:4188;s:9:"FlakePist";i:4189;s:10:"GrappOratr";i:4190;s:9:"OilmaPoly";i:4191;s:4:"Soro";i:4192;s:11:"BellUncloUn";i:4193;s:4:"Ribb";i:4194;s:5:"Encri";i:4195;s:11:"NocuShopSia";i:4196;s:8:"GloTraum";i:4197;s:10:"NondParaPh";i:4198;s:12:"GooxPanWeapo";i:4199;s:8:"ScleWhor";i:4200;s:6:"LokaRa";i:4201;s:9:"SaWeaseWi";i:4202;s:8:"ConUnglo";i:4203;s:6:"TafiWo";i:4204;s:13:"EmpPrayfSubal";i:4205;s:11:"CallCamComp";i:4206;s:8:"CorPaleo";i:4207;s:9:"FrOpiuPal";i:4208;s:7:"CardoHe";i:4209;s:10:"LapMaQuebr";i:4210;s:11:"EncycKupUnh";i:4211;s:3:"Opi";i:4212;s:12:"HydroMeRecti";i:4213;s:4:"Nomo";i:4214;s:5:"Unsub";i:4215;s:7:"HomesRa";i:4216;s:12:"ForksMoStepg";i:4217;s:8:"OctoPopp";i:4218;s:12:"FrazQuernSna";i:4219;s:5:"Serol";i:4220;s:7:"NonelSc";i:4221;s:11:"PepPlaUncom";i:4222;s:11:"NonRetrUnha";i:4223;s:5:"Moril";i:4224;s:4:"CoTo";i:4225;s:7:"EnduLin";i:4226;s:8:"PhoebUng";i:4227;s:11:"HetTalkWrec";i:4228;s:12:"JadisMuUnfor";i:4229;s:5:"BitPu";i:4230;s:5:"EuOle";i:4231;s:10:"UnpUnrarUp";i:4232;s:3:"Vis";i:4233;s:7:"OilskPe";i:4234;s:9:"MaSuVicel";i:4235;s:12:"HypoiIlocaWa";i:4236;s:5:"Figle";i:4237;s:5:"Level";i:4238;s:7:"EntalIn";i:4239;s:7:"PrePrun";i:4240;s:7:"ScolUnf";i:4241;s:3:"Gna";i:4242;s:8:"SepSymph";i:4243;s:5:"LeiSp";i:4244;s:5:"Onrus";i:4245;s:12:"NonOxidoStee";i:4246;s:9:"AsymReiRe";i:4247;s:11:"OvePluRelin";i:4248;s:9:"MePedVege";i:4249;s:12:"HemMarkMasse";i:4250;s:10:"OrogeUnpea";i:4251;s:9:"ForPilThy";i:4252;s:7:"MaPtery";i:4253;s:10:"OraShrUndi";i:4254;s:5:"Bacte";i:4255;s:6:"RefoUn";i:4256;s:13:"ParabPhysaTwa";i:4257;s:13:"ClaDandlItera";i:4258;s:9:"LeucMicOp";i:4259;s:3:"Vip";i:4260;s:7:"TrunkWr";i:4261;s:5:"Vermi";i:4262;s:10:"DupHirUnde";i:4263;s:14:"HoardSalteTaur";i:4264;s:3:"Vio";i:4265;s:5:"Goatb";i:4266;s:12:"DressInMaryx";i:4267;s:4:"Neph";i:4268;s:12:"TanstTrivaWe";i:4269;s:6:"LupeSu";i:4270;s:2:"Us";i:4271;s:6:"PerTab";i:4272;s:9:"FiMerYeas";i:4273;s:4:"OnOt";i:4274;s:10:"AntihSmich";i:4275;s:7:"ObiRoSa";i:4276;s:9:"UnscWelsh";i:4277;s:7:"RhombUn";i:4278;s:5:"Think";i:4279;s:6:"SinWes";i:4280;s:5:"LesSu";i:4281;s:5:"NoObs";i:4282;s:8:"ScarWusp";i:4283;s:8:"HoNaPhot";i:4284;s:5:"Vulca";i:4285;s:12:"MarrOwsYeast";i:4286;s:4:"Togu";i:4287;s:13:"FiloPilgrRuth";i:4288;s:8:"CouPyrTe";i:4289;s:7:"ConUnpa";i:4290;s:4:"Unag";i:4291;s:9:"SaUncUroc";i:4292;s:6:"UnrWhe";i:4293;s:9:"PolyShatt";i:4294;s:5:"ReaSi";i:4295;s:7:"NonOuac";i:4296;s:10:"OlidPsUnex";i:4297;s:6:"SwunUn";i:4298;s:7:"TorvVac";i:4299;s:9:"SoariVall";i:4300;s:9:"EreGaShea";i:4301;s:10:"PospoSemip";i:4302;s:8:"NoSuscUn";i:4303;s:3:"Ost";i:4304;s:9:"ErytMolSa";i:4305;s:5:"SaTre";i:4306;s:7:"PaShaZe";i:4307;s:11:"EnlTaarxTra";i:4308;s:5:"Puppi";i:4309;s:3:"War";i:4310;s:4:"Fera";i:4311;s:13:"CircHandkStal";i:4312;s:6:"PiSubc";i:4313;s:5:"Morrh";i:4314;s:10:"JoiPhytoTe";i:4315;s:8:"DevExcur";i:4316;s:7:"NunsPig";i:4317;s:10:"MarsPiePri";i:4318;s:6:"DuraSa";i:4319;s:5:"FluGe";i:4320;s:4:"RoWi";i:4321;s:5:"Ovine";i:4322;s:10:"PepsiPhVir";i:4323;s:8:"MiSpSupe";i:4324;s:3:"Sel";i:4325;s:5:"Sympt";i:4326;s:11:"IsUndrWhatt";i:4327;s:6:"AnOstr";i:4328;s:4:"IrSa";i:4329;s:4:"Spal";i:4330;s:4:"Sear";i:4331;s:3:"Fiq";i:4332;s:7:"FaOutro";i:4333;s:11:"DamoMePolyd";i:4334;s:13:"HarmfOreTushx";i:4335;s:5:"MaUnt";i:4336;s:7:"DesmoTa";i:4337;s:9:"CossaPhPh";i:4338;s:5:"Notho";i:4339;s:5:"Sowli";i:4340;s:7:"MisesPo";i:4341;s:4:"Uplo";i:4342;s:5:"Recti";i:4343;s:7:"GloHolo";i:4344;s:9:"TyppUnnob";i:4345;s:3:"Euc";i:4346;s:8:"MonNotPr";i:4347;s:7:"LogiSoo";i:4348;s:6:"NoUnde";i:4349;s:4:"Mosa";i:4350;s:3:"Cor";i:4351;s:9:"CinquSupr";i:4352;s:7:"PunkVra";i:4353;s:10:"CarSlWebwo";i:4354;s:5:"CarDe";i:4355;s:7:"TetraUl";i:4356;s:2:"Jo";i:4357;s:4:"OvPo";i:4358;s:4:"Spoi";i:4359;s:8:"IrresTyp";i:4360;s:6:"OvRySo";i:4361;s:6:"MicrPa";i:4362;s:3:"Ral";i:4363;s:5:"PrSem";i:4364;s:8:"OuttrSol";i:4365;s:12:"DisInduSmoke";i:4366;s:7:"PneuSag";i:4367;s:4:"Pstx";i:4368;s:8:"MeOveUnr";i:4369;s:5:"Diaer";i:4370;s:8:"FrorGiUn";i:4371;s:6:"SciSea";i:4372;s:6:"ImOver";i:4373;s:7:"CuLoath";i:4374;s:13:"MatriSorTawgi";i:4375;s:5:"SulWa";i:4376;s:11:"DemiHoodxMu";i:4377;s:9:"GladGuiMu";i:4378;s:10:"MaiMazPeri";i:4379;s:9:"ReedTetra";i:4380;s:8:"MeUnUnpe";i:4381;s:11:"HistoHouTro";i:4382;s:7:"DumGoUn";i:4383;s:3:"Ses";i:4384;s:12:"ReceScincUpg";i:4385;s:9:"ChaDiNonm";i:4386;s:9:"HyraxUnsc";i:4387;s:7:"MiNephr";i:4388;s:5:"PlePr";i:4389;s:5:"OpPre";i:4390;s:8:"AwnlBitt";i:4391;s:12:"JuraMucUnrue";i:4392;s:4:"NoRu";i:4393;s:4:"Rita";i:4394;s:10:"DyStenTetr";i:4395;s:6:"MoPoUn";i:4396;s:11:"ConKokoPapi";i:4397;s:8:"HoUnUnde";i:4398;s:13:"RhiUnsweWapac";i:4399;s:10:"BecLadRema";i:4400;s:13:"JordaSeleWitn";i:4401;s:8:"TramUnwh";i:4402;s:13:"GladePanzoZym";i:4403;s:7:"EvilOli";i:4404;s:11:"LeoUnconWea";i:4405;s:5:"ThToo";i:4406;s:8:"SubvVisi";i:4407;s:11:"IloInaTrogo";i:4408;s:5:"Troch";i:4409;s:5:"TriWo";i:4410;s:10:"IsoJuvVanq";i:4411;s:7:"NaTalon";i:4412;s:11:"CauRenSemin";i:4413;s:11:"HectoMancPa";i:4414;s:5:"HySwa";i:4415;s:6:"CuprLe";i:4416;s:4:"Tain";i:4417;s:4:"Watc";i:4418;s:13:"NegriPosWollo";i:4419;s:10:"DetInterLy";i:4420;s:11:"HamiManSpoa";i:4421;s:6:"FuUran";i:4422;s:11:"EpitLegitSi";i:4423;s:4:"Resi";i:4424;s:9:"FlobIctPh";i:4425;s:13:"QopSpaniTroch";i:4426;s:12:"ReediThirUna";i:4427;s:14:"ItaliNondeRuck";i:4428;s:8:"UntUpsho";i:4429;s:11:"InquStanTri";i:4430;s:10:"PeSebiTarg";i:4431;s:5:"ReUnp";i:4432;s:9:"FlocHecRe";i:4433;s:7:"MogrSum";i:4434;s:5:"ForHi";i:4435;s:6:"NicePe";i:4436;s:4:"Unob";i:4437;s:8:"MnemSupe";i:4438;s:5:"StUnc";i:4439;s:5:"Basop";i:4440;s:4:"Rust";i:4441;s:10:"ChGonVerme";i:4442;s:6:"SigYet";i:4443;s:4:"Trap";i:4444;s:5:"Uproo";i:4445;s:7:"BrSpecu";i:4446;s:5:"DePro";i:4447;s:11:"PrPterUness";i:4448;s:7:"LipPeTi";i:4449;s:8:"RoostTru";i:4450;s:12:"EngarIndeSub";i:4451;s:9:"SipeTaint";i:4452;s:13:"HutxLibelUnem";i:4453;s:10:"DuEtheUnor";i:4454;s:9:"SmStockSw";i:4455;s:7:"TarTeYu";i:4456;s:5:"LoUnr";i:4457;s:4:"Yesx";i:4458;s:9:"SleeStone";i:4459;s:10:"FraGemmPer";i:4460;s:9:"IsocThesm";i:4461;s:10:"DerogEritr";i:4462;s:13:"ConHarasLippy";i:4463;s:5:"UnUnr";i:4464;s:6:"GastSt";i:4465;s:4:"Unsh";i:4466;s:9:"GalHypUnt";i:4467;s:2:"Fe";i:4468;s:13:"HypotIntReali";i:4469;s:4:"Uner";i:4470;s:6:"MoUnid";i:4471;s:11:"HomodMymVil";i:4472;s:9:"FibPrepUn";i:4473;s:9:"SpracWart";i:4474;s:5:"EdxSo";i:4475;s:8:"AxeOverf";i:4476;s:12:"EunucFanhoMo";i:4477;s:4:"DeFl";i:4478;s:8:"ErGnSulp";i:4479;s:9:"MoOvePeri";i:4480;s:11:"ScUnanUnpow";i:4481;s:11:"NautiTaYawn";i:4482;s:6:"QuScSu";i:4483;s:3:"Urf";i:4484;s:4:"Phyt";i:4485;s:7:"GhMyelo";i:4486;s:5:"ClEle";i:4487;s:12:"MetePyrotSne";i:4488;s:7:"MonSpoi";i:4489;s:5:"Operc";i:4490;s:5:"HaHex";i:4491;s:10:"BivFirefPh";i:4492;s:7:"TrabUpt";i:4493;s:5:"Maund";i:4494;s:10:"MalebPrere";i:4495;s:4:"Quar";i:4496;s:9:"MaStarbUn";i:4497;s:11:"PythUnVello";i:4498;s:4:"Domi";i:4499;s:8:"PoiSprew";i:4500;s:9:"MeagrUnwi";i:4501;s:8:"NapRevea";i:4502;s:5:"Unact";i:4503;s:15:"CorniJumboSubur";i:4504;s:6:"JudUnd";i:4505;s:10:"InteMisRow";i:4506;s:9:"OleanSyba";i:4507;s:10:"ElaRedlSup";i:4508;s:4:"Farm";i:4509;s:4:"Hipp";i:4510;s:9:"PangPedPh";i:4511;s:5:"RhiTi";i:4512;s:9:"HomisPaTs";i:4513;s:4:"Reas";i:4514;s:7:"AnaboDi";i:4515;s:9:"JarlxRaTh";i:4516;s:11:"GravSunsVer";i:4517;s:4:"Real";i:4518;s:7:"EfPulta";i:4519;s:9:"GunlxMaRe";i:4520;s:7:"PhaPhre";i:4521;s:5:"PlUnr";i:4522;s:5:"Skipp";i:4523;s:4:"Mura";i:4524;s:14:"SerosUnentWork";i:4525;s:3:"Ske";i:4526;s:11:"AttrFourLac";i:4527;s:8:"PreShrin";i:4528;s:7:"InsProf";i:4529;s:11:"ClSwottWebm";i:4530;s:11:"EndPhylaPle";i:4531;s:8:"MenyaThe";i:4532;s:8:"FissVaul";i:4533;s:7:"HastiOv";i:4534;s:3:"Met";i:4535;s:2:"Ta";i:4536;s:8:"HatbaPan";i:4537;s:6:"SyndTe";i:4538;s:5:"Hoppl";i:4539;s:8:"FerNecro";i:4540;s:4:"Rese";i:4541;s:8:"EnoHiUna";i:4542;s:7:"MaOverh";i:4543;s:11:"ItzNonsPecu";i:4544;s:5:"PhiTh";i:4545;s:3:"Jus";i:4546;s:9:"CoLevoTra";i:4547;s:4:"Sent";i:4548;s:7:"PeUrami";i:4549;s:7:"MuWoman";i:4550;s:4:"Ooph";i:4551;s:10:"InterRocUn";i:4552;s:7:"SaShadd";i:4553;s:14:"PaniTwiteUnirh";i:4554;s:7:"PaPhaRe";i:4555;s:4:"SaSy";i:4556;s:11:"ExarcTestTr";i:4557;s:3:"Vex";i:4558;s:3:"Yuz";i:4559;s:5:"UnWea";i:4560;s:11:"HylPanPapil";i:4561;s:9:"WineWulfe";i:4562;s:7:"PenSpar";i:4563;s:8:"WaveWove";i:4564;s:10:"DisFlStenc";i:4565;s:14:"DelegImpreMult";i:4566;s:7:"DiOpina";i:4567;s:5:"PenPr";i:4568;s:5:"Pinna";i:4569;s:14:"HieroIdylYoick";i:4570;s:6:"CougSt";i:4571;s:5:"RevSt";i:4572;s:10:"IndiMusPro";i:4573;s:4:"SkSu";i:4574;s:4:"Becl";i:4575;s:7:"IndiIns";i:4576;s:9:"NickeOuri";i:4577;s:9:"DeDiHundr";i:4578;s:9:"PerfeSeUn";i:4579;s:5:"MerTh";i:4580;s:7:"LanMann";i:4581;s:13:"FileOtheUndev";i:4582;s:9:"JupaSymme";i:4583;s:5:"Overb";i:4584;s:9:"RelocSnoo";i:4585;s:13:"MckayPetiQuee";i:4586;s:12:"SantaSophUnt";i:4587;s:10:"ConInvinSa";i:4588;s:4:"SyZo";i:4589;s:7:"DessSal";i:4590;s:5:"Septu";i:4591;s:10:"EchenOuTel";i:4592;s:8:"AsPeWith";i:4593;s:5:"Rubli";i:4594;s:11:"DonInspiMor";i:4595;s:12:"CypriDeHorto";i:4596;s:14:"ShravSoureSqua";i:4597;s:11:"IsothUncoYu";i:4598;s:5:"Softe";i:4599;s:8:"FiliTegx";i:4600;s:11:"PreRotteTan";i:4601;s:12:"FulgPitcVulc";i:4602;s:5:"Raphi";i:4603;s:4:"Silk";i:4604;s:7:"IntMilk";i:4605;s:7:"EthExor";i:4606;s:4:"InTe";i:4607;s:12:"HousePetrPre";i:4608;s:5:"Techn";i:4609;s:4:"Spir";i:4610;s:6:"MaThul";i:4611;s:4:"Phae";i:4612;s:9:"MythPipRe";i:4613;s:7:"PottlUn";i:4614;s:10:"FarOverStu";i:4615;s:12:"CranDhawxMis";i:4616;s:14:"ImprOverlTrach";i:4617;s:14:"DoohiGrimiNond";i:4618;s:7:"RedfiSe";i:4619;s:8:"EnteUpra";i:4620;s:13:"IrreKoalPicud";i:4621;s:9:"SaSenSiva";i:4622;s:4:"Quad";i:4623;s:5:"Japon";i:4624;s:9:"SeveSodSt";i:4625;s:13:"CyanhExtMicro";i:4626;s:6:"PavRig";i:4627;s:9:"NaphtProv";i:4628;s:12:"HypovIrreTet";i:4629;s:10:"GiloxLapsi";i:4630;s:5:"ToTri";i:4631;s:8:"GroggPre";i:4632;s:5:"Perco";i:4633;s:5:"Upclo";i:4634;s:8:"GenitLab";i:4635;s:6:"UnXylo";i:4636;s:5:"Devir";i:4637;s:8:"FleNassa";i:4638;s:4:"Salt";i:4639;s:13:"DeliManipUnen";i:4640;s:7:"ShinSho";i:4641;s:6:"IliaOu";i:4642;s:10:"EleImponPu";i:4643;s:9:"ProSuWild";i:4644;s:8:"AnNymRum";i:4645;s:14:"HexamInteUnrou";i:4646;s:11:"CopiHealTem";i:4647;s:14:"PieceSometUnre";i:4648;s:5:"HadRa";i:4649;s:10:"CamacJohan";i:4650;s:9:"OverZeugo";i:4651;s:3:"Sha";i:4652;s:6:"PrReZo";i:4653;s:10:"CoeffPhoUn";i:4654;s:9:"MycoObjec";i:4655;s:4:"MiTa";i:4656;s:9:"BiQuadrSe";i:4657;s:13:"OverPhantSanc";i:4658;s:4:"Poni";i:4659;s:7:"MyeTerr";i:4660;s:4:"Inri";i:4661;s:5:"SemZy";i:4662;s:8:"AuFruKer";i:4663;s:8:"GrPeVasu";i:4664;s:8:"NooQuant";i:4665;s:10:"PlodPtarTa";i:4666;s:8:"SubWistx";i:4667;s:14:"CoccSuperTundx";i:4668;s:5:"Spass";i:4669;s:8:"PuzzSapl";i:4670;s:4:"Snon";i:4671;s:8:"InstPega";i:4672;s:13:"HomoOrthRadia";i:4673;s:5:"Phyll";i:4674;s:8:"ChondRev";i:4675;s:9:"MoroPessi";i:4676;s:5:"Fores";i:4677;s:11:"NeOligPtole";i:4678;s:4:"Serf";i:4679;s:10:"InSymUnacu";i:4680;s:8:"BigemMis";i:4681;s:8:"OvicRaSc";i:4682;s:9:"IsmxUnder";i:4683;s:13:"HaeShoweTesta";i:4684;s:4:"Gram";i:4685;s:7:"TipsUnc";i:4686;s:6:"EuPerm";i:4687;s:9:"EyLoPhone";i:4688;s:7:"RepScat";i:4689;s:4:"MiSu";i:4690;s:11:"ChaucRoriUn";i:4691;s:5:"Slowh";i:4692;s:3:"Osp";i:4693;s:5:"Windo";i:4694;s:3:"Izt";i:4695;s:4:"Hoga";i:4696;s:7:"DrUndis";i:4697;s:9:"NucPyraTh";i:4698;s:5:"Unrum";i:4699;s:10:"BotchGrGue";i:4700;s:9:"LaPreaSem";i:4701;s:7:"OdaxSho";i:4702;s:4:"Outb";i:4703;s:9:"CreeUniun";i:4704;s:12:"JeaniMandSun";i:4705;s:6:"DeDigr";i:4706;s:10:"RotSciurSe";i:4707;s:7:"ConfiUn";i:4708;s:4:"Regi";i:4709;s:11:"DoSterThimb";i:4710;s:4:"Vamp";i:4711;s:3:"Hae";i:4712;s:6:"HyRete";i:4713;s:2:"Up";i:4714;s:9:"FumarRece";i:4715;s:3:"Loc";i:4716;s:5:"Unatt";i:4717;s:7:"OstrVea";i:4718;s:11:"InNettlPres";i:4719;s:7:"BuMilks";i:4720;s:4:"Bone";i:4721;s:10:"MyopPorpSy";i:4722;s:12:"EmmenMeUndis";i:4723;s:9:"DaggeDiGi";i:4724;s:7:"UnVarie";i:4725;s:5:"Perfe";i:4726;s:9:"AzoDaffTa";i:4727;s:10:"IneffWheal";i:4728;s:10:"DeconPinSe";i:4729;s:10:"TeUnmVolut";i:4730;s:10:"SaproTrach";i:4731;s:10:"PlaRhiUrba";i:4732;s:9:"StropSubv";i:4733;s:7:"IcNatur";i:4734;s:8:"FluxaRei";i:4735;s:6:"ChEcTo";i:4736;s:12:"QuadrUnovZoo";i:4737;s:12:"NecroThakuWa";i:4738;s:5:"SeSer";i:4739;s:2:"Go";i:4740;s:12:"FrogxPredSar";i:4741;s:12:"CaFrumpTortr";i:4742;s:7:"LePrakr";i:4743;s:6:"KurtWa";i:4744;s:13:"NymphOthVowma";i:4745;s:14:"InduMurgeTrabe";i:4746;s:10:"CytogUrodi";i:4747;s:12:"CarMarkwPrep";i:4748;s:9:"DyPalatRi";i:4749;s:15:"LazarMimusRemov";i:4750;s:9:"OfPterRef";i:4751;s:10:"PapawTherm";i:4752;s:9:"MoProTrea";i:4753;s:9:"PushfScut";i:4754;s:6:"NonSub";i:4755;s:9:"OcSubWido";i:4756;s:8:"UnchaUng";i:4757;s:9:"IcIntePer";i:4758;s:12:"PurgaReiRepl";i:4759;s:13:"CryLoxiaPutri";i:4760;s:12:"HomolMeaslTa";i:4761;s:4:"Pent";i:4762;s:7:"MisMoRu";i:4763;s:9:"ColorCyMa";i:4764;s:7:"HaemoTr";i:4765;s:5:"ParTa";i:4766;s:8:"MesOrxUn";i:4767;s:5:"Kavix";i:4768;s:3:"Pha";i:4769;s:13:"CaliCatsPenba";i:4770;s:10:"OverRaUnsm";i:4771;s:5:"Psych";i:4772;s:9:"SphaeUnfa";i:4773;s:4:"Wyli";i:4774;s:6:"SuSwan";i:4775;s:10:"DichoElkho";i:4776;s:6:"OsteSt";i:4777;s:5:"Overa";i:4778;s:10:"ReprTeUnpa";i:4779;s:9:"PaTonnZei";i:4780;s:4:"Ostr";i:4781;s:5:"Octob";i:4782;s:3:"Exo";i:4783;s:3:"Res";i:4784;s:3:"But";i:4785;s:8:"RhoTreVi";i:4786;s:6:"PrReda";i:4787;s:4:"IlUn";i:4788;s:7:"MeMonNo";i:4789;s:12:"FluMyrmePaho";i:4790;s:6:"ImprRe";i:4791;s:8:"EpichGla";i:4792;s:12:"RepaRolliUnr";i:4793;s:8:"MininOve";i:4794;s:7:"ProsaUn";i:4795;s:12:"HypNonapYoke";i:4796;s:7:"LalopSc";i:4797;s:8:"HoUneVis";i:4798;s:12:"DorKenmMicro";i:4799;s:15:"ProclResubZoops";i:4800;s:15:"SemirSerraSuper";i:4801;s:7:"SaceUna";i:4802;s:5:"Monoa";i:4803;s:5:"NoUns";i:4804;s:9:"DiEmTauto";i:4805;s:10:"BoehmSupUn";i:4806;s:10:"SuperUnres";i:4807;s:14:"BattDemonVitam";i:4808;s:9:"FilInxRap";i:4809;s:10:"PremiStStr";i:4810;s:10:"DesyGooPer";i:4811;s:4:"CoSo";i:4812;s:8:"HypeTric";i:4813;s:4:"Writ";i:4814;s:8:"PolSangu";i:4815;s:7:"PreUnig";i:4816;s:10:"RubServuTo";i:4817;s:5:"Sider";i:4818;s:9:"UnhauUnpe";i:4819;s:9:"HerniVine";i:4820;s:5:"MaMud";i:4821;s:5:"Nonan";i:4822;s:9:"CurraEnte";i:4823;s:8:"RaTenuTe";i:4824;s:2:"Ba";i:4825;s:5:"CuNeu";i:4826;s:8:"SartShat";i:4827;s:5:"Polyp";i:4828;s:7:"UnreUnv";i:4829;s:12:"InteKarakRhy";i:4830;s:3:"Ven";i:4831;s:3:"Tan";i:4832;s:8:"MeNecrNo";i:4833;s:12:"DioResoUnpre";i:4834;s:4:"Fell";i:4835;s:9:"ExGrafMai";i:4836;s:14:"SanifSuffrUnpr";i:4837;s:12:"GrimmKerUnma";i:4838;s:5:"Subsc";i:4839;s:6:"GilLaw";i:4840;s:10:"PerinPsilo";i:4841;s:8:"RenilRiv";i:4842;s:12:"FuThymoXenop";i:4843;s:7:"CassMen";i:4844;s:6:"LuMoro";i:4845;s:10:"HobPlasmSk";i:4846;s:5:"Rehum";i:4847;s:4:"Piaz";i:4848;s:5:"Defen";i:4849;s:4:"Prea";i:4850;s:9:"RedSaccSu";i:4851;s:9:"StrouUrox";i:4852;s:9:"MakObelSe";i:4853;s:5:"ReoTa";i:4854;s:7:"MiNonvi";i:4855;s:7:"AutZieg";i:4856;s:5:"Oscil";i:4857;s:9:"SuSuperUn";i:4858;s:4:"Slee";i:4859;s:5:"CaaJa";i:4860;s:8:"OvePosit";i:4861;s:7:"LianWel";i:4862;s:11:"FibOvoelTub";i:4863;s:4:"Crou";i:4864;s:5:"Wheed";i:4865;s:5:"Heter";i:4866;s:5:"MaPol";i:4867;s:8:"HundMaUr";i:4868;s:5:"ChGyr";i:4869;s:7:"CrFrPhr";i:4870;s:7:"KiPrima";i:4871;s:13:"DemibSinUnint";i:4872;s:5:"PuTyp";i:4873;s:8:"HooPhysi";i:4874;s:7:"CubitSt";i:4875;s:12:"PaletProWhos";i:4876;s:7:"RaTorme";i:4877;s:15:"LeiotSnortWally";i:4878;s:10:"LymPlaniSc";i:4879;s:5:"SuUni";i:4880;s:10:"BefrGlobRh";i:4881;s:11:"LibeNowaTro";i:4882;s:8:"ArEnerRh";i:4883;s:4:"Scar";i:4884;s:10:"CorpLumSha";i:4885;s:14:"HereaTraiTruan";i:4886;s:5:"Syrin";i:4887;s:6:"ChaInn";i:4888;s:3:"Use";i:4889;s:12:"EkahLusiUnfo";i:4890;s:10:"HelpiPhala";i:4891;s:10:"InterUnpro";i:4892;s:5:"Unred";i:4893;s:9:"ConfPePle";i:4894;s:9:"HoPreReam";i:4895;s:11:"PotTedTimer";i:4896;s:5:"Shiel";i:4897;s:10:"GoOverResc";i:4898;s:6:"NaTyle";i:4899;s:10:"FibGommeLa";i:4900;s:7:"PremoSt";i:4901;s:9:"HydroTale";i:4902;s:7:"PerThig";i:4903;s:8:"AcoelHal";i:4904;s:7:"BocNonr";i:4905;s:8:"PsySnaTe";i:4906;s:5:"Stint";i:4907;s:9:"SectSeeUn";i:4908;s:4:"FlMu";i:4909;s:4:"Reve";i:4910;s:7:"QuestUn";i:4911;s:11:"LibUnsVictu";i:4912;s:8:"SellaTra";i:4913;s:10:"CoMultParu";i:4914;s:3:"See";i:4915;s:7:"MaReTea";i:4916;s:10:"DucTowaTri";i:4917;s:7:"KeeProb";i:4918;s:10:"CitywPenta";i:4919;s:5:"Sprad";i:4920;s:5:"Fugit";i:4921;s:5:"TrVas";i:4922;s:12:"FoozlKlysSta";i:4923;s:10:"HomaMisTur";i:4924;s:8:"MensuVar";i:4925;s:13:"ForLinteNidic";i:4926;s:10:"SteToUpspl";i:4927;s:11:"JoshSuVisco";i:4928;s:5:"Semis";i:4929;s:7:"NovoxTr";i:4930;s:9:"GushxSchi";i:4931;s:10:"CitrPhthSu";i:4932;s:6:"ProUnc";i:4933;s:5:"PaVol";i:4934;s:3:"Lam";i:4935;s:7:"MatPlSo";i:4936;s:2:"Ru";i:4937;s:9:"PerRereTh";i:4938;s:11:"DokPetrSube";i:4939;s:6:"ProtSt";i:4940;s:15:"EidetParroScyph";i:4941;s:9:"OverRechi";i:4942;s:5:"Elsew";i:4943;s:12:"HomoSpecZizz";i:4944;s:5:"PrUnw";i:4945;s:8:"RattlRig";i:4946;s:5:"Helic";i:4947;s:9:"MicrSimSq";i:4948;s:8:"PreUnjil";i:4949;s:7:"InfrRep";i:4950;s:10:"GuasPalRub";i:4951;s:8:"OrgSubWa";i:4952;s:4:"Sept";i:4953;s:8:"BrEyeTri";i:4954;s:7:"NagOver";i:4955;s:12:"ButNonadSill";i:4956;s:11:"LavStympVea";i:4957;s:11:"DispoExRaph";i:4958;s:11:"FlexuMaleSa";i:4959;s:10:"IncraVihar";i:4960;s:6:"ScSkew";i:4961;s:4:"Irre";i:4962;s:8:"PeRecUna";i:4963;s:11:"CoPulviSchi";i:4964;s:10:"InconLyasx";i:4965;s:10:"MonodSparg";i:4966;s:6:"KickPi";i:4967;s:11:"TrabUnrWary";i:4968;s:14:"MuleResorUnsna";i:4969;s:13:"MetapNiggSupe";i:4970;s:9:"GraSterUv";i:4971;s:7:"LasquQu";i:4972;s:6:"UnaUnp";i:4973;s:10:"BipheConWr";i:4974;s:6:"CirUni";i:4975;s:3:"Pon";i:4976;s:5:"ExtRe";i:4977;s:5:"Inact";i:4978;s:7:"TrVendi";i:4979;s:5:"Santa";i:4980;s:9:"OsPhlPoro";i:4981;s:14:"HolidHydroTome";i:4982;s:4:"Incr";i:4983;s:3:"Hyp";i:4984;s:10:"CoOverrSug";i:4985;s:4:"Soap";i:4986;s:4:"Sole";i:4987;s:11:"PompProteSq";i:4988;s:6:"OutySu";i:4989;s:8:"FraFrPha";i:4990;s:7:"ParaPat";i:4991;s:12:"MarcOrobScot";i:4992;s:9:"PhysiTuto";i:4993;s:7:"AzygCit";i:4994;s:11:"UnharUnpUns";i:4995;s:3:"Neg";i:4996;s:6:"PrSupe";i:4997;s:9:"RosehUnso";i:4998;s:8:"NonRodom";i:4999;s:9:"IsothOpto";i:5000;s:8:"ForeFrac";i:5001;s:11:"ShoneTeZymo";i:5002;s:10:"BrKoloUnif";i:5003;s:12:"ImprPremiSup";i:5004;s:9:"EpeisGiIn";i:5005;s:8:"SnatcTes";i:5006;s:7:"GerTrav";i:5007;s:11:"ElSubsuUnsu";i:5008;s:4:"ApPr";i:5009;s:5:"Seapo";i:5010;s:7:"PoTetra";i:5011;s:11:"DeceFraOver";i:5012;s:4:"NoPa";i:5013;s:10:"BaraBiMoul";i:5014;s:8:"SchooZam";i:5015;s:3:"Omp";i:5016;s:12:"NuttiStVotar";i:5017;s:9:"DiSoarSto";i:5018;s:3:"Ges";i:5019;s:12:"OeninSubTher";i:5020;s:5:"DumJi";i:5021;s:9:"ClareDias";i:5022;s:9:"OutglPuUn";i:5023;s:5:"Salel";i:5024;s:10:"HyperOePro";i:5025;s:6:"BegNon";i:5026;s:6:"DemeLa";i:5027;s:11:"AuletCoNonr";i:5028;s:10:"JaNonRegul";i:5029;s:3:"Coc";i:5030;s:3:"Fun";i:5031;s:3:"Sti";i:5032;s:9:"MaineMile";i:5033;s:10:"CahokLikab";i:5034;s:6:"SponSt";i:5035;s:3:"Tai";i:5036;s:11:"MicrOverRef";i:5037;s:9:"SlumSphin";i:5038;s:8:"RivalSho";i:5039;s:13:"ContPredeTurb";i:5040;s:7:"TopUndi";i:5041;s:7:"PluStar";i:5042;s:2:"Gy";i:5043;s:13:"PantPiccSemid";i:5044;s:11:"NoTetraTumm";i:5045;s:7:"DaTheot";i:5046;s:13:"PiaTheorUnawa";i:5047;s:5:"Zilla";i:5048;s:12:"LikenProWhat";i:5049;s:5:"Demod";i:5050;s:2:"Ul";i:5051;s:5:"Sexlo";i:5052;s:4:"Unab";i:5053;s:5:"Tobac";i:5054;s:7:"UnVenti";i:5055;s:14:"HugeoSchuSucci";i:5056;s:4:"Octa";i:5057;s:12:"EcoFritxUncu";i:5058;s:7:"MadhvVa";i:5059;s:10:"UnbewUnive";i:5060;s:8:"PsTreUni";i:5061;s:5:"Overc";i:5062;s:7:"DarryLe";i:5063;s:8:"LateRuin";i:5064;s:8:"ParThYea";i:5065;s:5:"Reapp";i:5066;s:12:"NarProoeRhin";i:5067;s:10:"ThiUnconUn";i:5068;s:7:"OrtUnpe";i:5069;s:5:"Reliq";i:5070;s:4:"Uret";i:5071;s:6:"PsyTer";i:5072;s:3:"Gal";i:5073;s:5:"Rando";i:5074;s:6:"UnnWis";i:5075;s:12:"IndInteOutth";i:5076;s:3:"Fla";i:5077;s:7:"AntiSem";i:5078;s:7:"ConMeRh";i:5079;s:6:"SupTaf";i:5080;s:9:"MaPosScen";i:5081;s:3:"Kae";i:5082;s:8:"EquStrTi";i:5083;s:8:"GawmxOve";i:5084;s:10:"GinglUncap";i:5085;s:14:"DemodHakxSongb";i:5086;s:6:"IracSe";i:5087;s:4:"Squa";i:5088;s:7:"MicrUnp";i:5089;s:6:"LoSzla";i:5090;s:11:"JurorUnhaVe";i:5091;s:8:"ThioaZen";i:5092;s:12:"HerbaHeteIso";i:5093;s:8:"RhizUnve";i:5094;s:3:"Twe";i:5095;s:13:"InnSpraySunbe";i:5096;s:9:"KoLoRecop";i:5097;s:7:"LegaSta";i:5098;s:5:"Uncou";i:5099;s:9:"SemiSkiTh";i:5100;s:6:"BecoUn";i:5101;s:2:"Av";i:5102;s:8:"AssyCoRe";i:5103;s:7:"PrWauch";i:5104;s:8:"RhapStyl";i:5105;s:5:"Parap";i:5106;s:5:"Thrif";i:5107;s:10:"InteLophVi";i:5108;s:11:"GradIntLeas";i:5109;s:8:"AurRandy";i:5110;s:7:"HyOutdw";i:5111;s:2:"Sw";i:5112;s:4:"Pede";i:5113;s:12:"HomoeIllYame";i:5114;s:12:"NoninSclStro";i:5115;s:5:"Nonco";i:5116;s:7:"GeSpeck";i:5117;s:6:"StoUnp";i:5118;s:5:"ExpSk";i:5119;s:10:"DiEthnoPro";i:5120;s:11:"RhynUnconUn";i:5121;s:3:"Tha";i:5122;s:4:"Unst";i:5123;s:10:"MicroUncit";i:5124;s:7:"IrrTymp";i:5125;s:10:"SemUnimaWa";i:5126;s:10:"OverPontSn";i:5127;s:10:"AreLaugLau";i:5128;s:7:"DesTran";i:5129;s:7:"IrMinue";i:5130;s:11:"NoRenteVexx";i:5131;s:8:"GousPySc";i:5132;s:7:"NeOxylx";i:5133;s:5:"Unpen";i:5134;s:10:"KatexUnpat";i:5135;s:5:"PoTra";i:5136;s:4:"Nonf";i:5137;s:5:"Expos";i:5138;s:4:"Repe";i:5139;s:11:"DePisoUnide";i:5140;s:5:"EquZa";i:5141;s:8:"UnUnUnwi";i:5142;s:11:"OffcParaXyl";i:5143;s:10:"RaSemiSurv";i:5144;s:9:"PhacoVesi";i:5145;s:3:"Inv";i:5146;s:14:"EpitrOsteXanth";i:5147;s:5:"Wellb";i:5148;s:4:"CaSu";i:5149;s:11:"SincTrogUna";i:5150;s:7:"InParam";i:5151;s:13:"GabioOverbUnc";i:5152;s:6:"PlowRo";i:5153;s:13:"InsecJeremTel";i:5154;s:3:"Rat";i:5155;s:7:"IncPrYa";i:5156;s:8:"JarTwist";i:5157;s:6:"ElaPre";i:5158;s:9:"MiRopTurk";i:5159;s:11:"PrPremoSept";i:5160;s:13:"EssOverfWycli";i:5161;s:7:"TragiUn";i:5162;s:9:"InOuRatch";i:5163;s:5:"Bolar";i:5164;s:2:"Br";i:5165;s:12:"QuadrRentVol";i:5166;s:11:"OuphUnsleWh";i:5167;s:11:"GalvRachiSt";i:5168;s:12:"DeliFazPutri";i:5169;s:9:"HiHomeRet";i:5170;s:8:"SloSuper";i:5171;s:4:"Mime";i:5172;s:4:"Turb";i:5173;s:10:"MagiOrUnsu";i:5174;s:11:"BlaOrthOutg";i:5175;s:4:"Inal";i:5176;s:8:"SkeVolva";i:5177;s:12:"HexatMunicOn";i:5178;s:8:"OmnQuUnc";i:5179;s:3:"Qui";i:5180;s:8:"BiFacMan";i:5181;s:5:"Uncle";i:5182;s:10:"PalaSpUnco";i:5183;s:5:"Sabba";i:5184;s:5:"NonSe";i:5185;s:14:"JuggeTetryUnbo";i:5186;s:10:"KerniPalRe";i:5187;s:4:"Unma";i:5188;s:4:"Unmu";i:5189;s:5:"Incom";i:5190;s:7:"PinkSca";i:5191;s:11:"NaSletSperm";i:5192;s:3:"Mil";i:5193;s:10:"AnthrBuOve";i:5194;s:11:"NemaPerUnre";i:5195;s:10:"RevisSanto";i:5196;s:7:"GaGunRa";i:5197;s:13:"PhotoPreUnrec";i:5198;s:6:"ReZoop";i:5199;s:12:"DermaMaThorn";i:5200;s:4:"Scor";i:5201;s:3:"Tet";i:5202;s:10:"MesocSeric";i:5203;s:12:"OmenSomnaSub";i:5204;s:10:"FiordJochx";i:5205;s:9:"DissGaQua";i:5206;s:7:"TeTorme";i:5207;s:7:"LarrUnf";i:5208;s:6:"DiesTa";i:5209;s:7:"NonfStr";i:5210;s:7:"DisEpil";i:5211;s:5:"Smoke";i:5212;s:9:"JimSaShut";i:5213;s:11:"BandOxamPsy";i:5214;s:3:"Pul";i:5215;s:4:"Hype";i:5216;s:4:"Scon";i:5217;s:11:"FourpMilUnd";i:5218;s:12:"ChuThundTopo";i:5219;s:9:"ExMuRelev";i:5220;s:4:"Resp";i:5221;s:8:"CorMilks";i:5222;s:11:"HalfInPyrul";i:5223;s:12:"PhotoProsaUn";i:5224;s:4:"Maie";i:5225;s:7:"MaOchUb";i:5226;s:5:"SpWar";i:5227;s:8:"CalPhaTe";i:5228;s:7:"HePrYam";i:5229;s:4:"Yama";i:5230;s:4:"Trib";i:5231;s:6:"InPoUn";i:5232;s:5:"Sciss";i:5233;s:3:"Teg";i:5234;s:12:"CinctJesModi";i:5235;s:14:"FoghToxigWolfh";i:5236;s:10:"LowSuWould";i:5237;s:7:"SpittTe";i:5238;s:8:"KathUnde";i:5239;s:11:"EnanReRight";i:5240;s:4:"Unpe";i:5241;s:8:"ToppVaci";i:5242;s:11:"LymphSwirWh";i:5243;s:9:"MarkPseud";i:5244;s:10:"ElegaTlasc";i:5245;s:10:"NonmTheoWa";i:5246;s:8:"MenSynar";i:5247;s:3:"Pil";i:5248;s:5:"Thala";i:5249;s:11:"OlePreUnado";i:5250;s:2:"Ni";i:5251;s:4:"Unge";i:5252;s:8:"ProSolTo";i:5253;s:4:"Rece";i:5254;s:8:"IsoLitho";i:5255;s:3:"Col";i:5256;s:5:"Intra";i:5257;s:5:"RoUnl";i:5258;s:7:"HeronIn";i:5259;s:4:"Undu";i:5260;s:7:"LimSaVo";i:5261;s:10:"CenPentSto";i:5262;s:8:"IsraTher";i:5263;s:9:"DeperUpge";i:5264;s:9:"DispLeWoo";i:5265;s:4:"Puru";i:5266;s:3:"Rig";i:5267;s:9:"CoGolPred";i:5268;s:3:"Lyr";i:5269;s:9:"PoRediUnl";i:5270;s:12:"SnotTibiaUnt";i:5271;s:5:"Mirac";i:5272;s:4:"HyHy";i:5273;s:5:"Senti";i:5274;s:6:"RadeSe";i:5275;s:12:"LattSpheUnne";i:5276;s:5:"Sperm";i:5277;s:7:"PrStabl";i:5278;s:9:"KieyUnpam";i:5279;s:9:"NeThiocVi";i:5280;s:6:"MicrRe";i:5281;s:4:"Hint";i:5282;s:6:"OxycVi";i:5283;s:12:"TrimUndeUnex";i:5284;s:7:"EncTcUr";i:5285;s:4:"Tomb";i:5286;s:12:"RoritSxTartw";i:5287;s:9:"GametPret";i:5288;s:8:"CoNodPri";i:5289;s:13:"PhosPogrPseud";i:5290;s:6:"PalTab";i:5291;s:7:"DiscGla";i:5292;s:8:"GrHibVen";i:5293;s:14:"LeaveMassUncor";i:5294;s:9:"ReSmiUnin";i:5295;s:5:"Sacch";i:5296;s:7:"MisceSt";i:5297;s:14:"PrissRepoTauch";i:5298;s:9:"CornPeScr";i:5299;s:8:"HepSuper";i:5300;s:11:"HiroSeriUnd";i:5301;s:12:"SuTripaUnpin";i:5302;s:10:"IrPuerViti";i:5303;s:7:"SubVint";i:5304;s:12:"ScenaThWinna";i:5305;s:8:"FlorSupr";i:5306;s:8:"ProUndes";i:5307;s:12:"TranVagiVier";i:5308;s:7:"KerriQu";i:5309;s:12:"PrunQuinSter";i:5310;s:8:"HoUnWeit";i:5311;s:6:"UnUnte";i:5312;s:9:"OthinUnWe";i:5313;s:9:"PlanRouUn";i:5314;s:3:"Sex";i:5315;s:7:"KangPia";i:5316;s:5:"Uncau";i:5317;s:5:"GeJox";i:5318;s:7:"GamalPs";i:5319;s:4:"Mono";i:5320;s:7:"PaleoPa";i:5321;s:6:"KeysMa";i:5322;s:6:"SeUngo";i:5323;s:5:"StWre";i:5324;s:12:"CamelDatiFre";i:5325;s:5:"ParUn";i:5326;s:5:"Phoby";i:5327;s:12:"BrocNotorUnt";i:5328;s:5:"Repan";i:5329;s:7:"EczWeed";i:5330;s:9:"FatKornOv";i:5331;s:8:"PaReSyno";i:5332;s:10:"LeoraPolyc";i:5333;s:8:"PolSalmo";i:5334;s:11:"LeaLillSpea";i:5335;s:7:"PasPrim";i:5336;s:10:"GyPalTachy";i:5337;s:6:"ProTra";i:5338;s:10:"OdontReamy";i:5339;s:9:"PasixZoog";i:5340;s:5:"SleTo";i:5341;s:10:"IcedxPaSom";i:5342;s:3:"Nep";i:5343;s:7:"OofyShe";i:5344;s:3:"Bic";i:5345;s:11:"ExHoovRodne";i:5346;s:5:"IsQui";i:5347;s:10:"MolSkiesUn";i:5348;s:4:"Trop";i:5349;s:8:"EnterTch";i:5350;s:5:"OveSu";i:5351;s:5:"Uncre";i:5352;s:9:"InquPhren";i:5353;s:10:"StaSwUnrun";i:5354;s:9:"MoNontaSt";i:5355;s:12:"LaryUninUnme";i:5356;s:5:"Spiro";i:5357;s:8:"PhylTett";i:5358;s:4:"SaSe";i:5359;s:11:"MultiPerPsy";i:5360;s:8:"DaRemSub";i:5361;s:4:"Trip";i:5362;s:11:"HemLummoUna";i:5363;s:13:"DepigGinIntre";i:5364;s:5:"MiOog";i:5365;s:3:"Mic";i:5366;s:6:"ConExt";i:5367;s:10:"SaSturdSur";i:5368;s:8:"KinsmUle";i:5369;s:9:"ItenMilde";i:5370;s:5:"Urome";i:5371;s:10:"SolUnexVar";i:5372;s:9:"SulphTask";i:5373;s:9:"CasMetaVa";i:5374;s:10:"KyuxTriUnm";i:5375;s:13:"MyocePectuPor";i:5376;s:14:"LardiMiscuToda";i:5377;s:7:"UnfXant";i:5378;s:12:"PhaRackUnind";i:5379;s:3:"Mes";i:5380;s:8:"HepatMed";i:5381;s:8:"EarSomet";i:5382;s:6:"ExemUn";i:5383;s:8:"KeelPuQu";i:5384;s:5:"Raung";i:5385;s:13:"HyogQuadrSili";i:5386;s:8:"AmpLiPat";i:5387;s:8:"ImpTradi";i:5388;s:8:"SergeSph";i:5389;s:8:"CirUnblo";i:5390;s:9:"EvMortZin";i:5391;s:10:"MammaThioz";i:5392;s:5:"PeiPr";i:5393;s:13:"MicrNotoRadic";i:5394;s:10:"PhtPicumPs";i:5395;s:11:"GeocNonbOst";i:5396;s:8:"AvaJagVy";i:5397;s:5:"MaVen";i:5398;s:8:"ParaUnbl";i:5399;s:7:"OdonRen";i:5400;s:5:"Locus";i:5401;s:12:"DecoOrthRach";i:5402;s:9:"BlFolSyna";i:5403;s:6:"TeUnde";i:5404;s:8:"MouUntVe";i:5405;s:8:"CedarIso";i:5406;s:9:"StonTenct";i:5407;s:11:"ReinsRepSil";i:5408;s:8:"QuaSneak";i:5409;s:8:"CakeCont";i:5410;s:5:"Eucon";i:5411;s:4:"Tele";i:5412;s:4:"Nonc";i:5413;s:8:"AeMapSub";i:5414;s:10:"GigmaRebri";i:5415;s:4:"Wany";i:5416;s:5:"UnpUn";i:5417;s:5:"PorRe";i:5418;s:13:"HomotMoonPost";i:5419;s:14:"CommDecorImmed";i:5420;s:4:"LiLu";i:5421;s:11:"ConcHibZygo";i:5422;s:12:"InteSubeUnch";i:5423;s:5:"Bulim";i:5424;s:9:"RecoUnhoi";i:5425;s:11:"ProteSaloVe";i:5426;s:10:"MaMegPrair";i:5427;s:12:"OvereSeralTo";i:5428;s:4:"Saca";i:5429;s:11:"ChipLactaSl";i:5430;s:3:"Nos";i:5431;s:11:"SickSponWhi";i:5432;s:7:"MouVisi";i:5433;s:8:"OvQxUnsh";i:5434;s:12:"GlabeLePromy";i:5435;s:4:"SpUs";i:5436;s:5:"Uncan";i:5437;s:10:"SculpSuTru";i:5438;s:9:"OuStronWa";i:5439;s:9:"ChioMaste";i:5440;s:11:"DownlPrUnfe";i:5441;s:5:"Tinca";i:5442;s:10:"HidPrRoare";i:5443;s:6:"ChrEri";i:5444;s:9:"OlfaSalpi";i:5445;s:6:"PleoPr";i:5446;s:10:"UnmenWalle";i:5447;s:4:"Osop";i:5448;s:10:"SpaSubtUnt";i:5449;s:7:"EmitRou";i:5450;s:8:"GriPatSa";i:5451;s:5:"FoPhe";i:5452;s:12:"RectoSteUnre";i:5453;s:7:"NoUnequ";i:5454;s:7:"EntitRa";i:5455;s:4:"GaMa";i:5456;s:7:"PrStere";i:5457;s:14:"PalisRevieSeac";i:5458;s:10:"KeratScUnc";i:5459;s:5:"Gorra";i:5460;s:4:"Oste";i:5461;s:5:"Snipx";i:5462;s:5:"SwaTe";i:5463;s:13:"SikeUnutVolit";i:5464;s:8:"SeptTaff";i:5465;s:8:"MartPrRu";i:5466;s:12:"CeChopsRever";i:5467;s:12:"InProveVagin";i:5468;s:5:"InTri";i:5469;s:13:"OligaPalVesse";i:5470;s:6:"PhysPr";i:5471;s:14:"GlycoNeurVulva";i:5472;s:7:"BeDrScl";i:5473;s:7:"BraUnsa";i:5474;s:3:"Ory";i:5475;s:5:"Phary";i:5476;s:3:"Ini";i:5477;s:10:"MaResuWitt";i:5478;s:8:"DeMaddMa";i:5479;s:12:"HereSteZygom";i:5480;s:9:"FluHashUn";i:5481;s:5:"ChIni";i:5482;s:5:"SoUng";i:5483;s:6:"MaSpir";i:5484;s:10:"GawnxRaUns";i:5485;s:7:"ScToxic";i:5486;s:6:"LabPla";i:5487;s:4:"RoVo";i:5488;s:12:"CircuDicProt";i:5489;s:14:"MariPhiloThank";i:5490;s:3:"Wid";i:5491;s:12:"PlatTheokTou";i:5492;s:6:"ScTiVi";i:5493;s:5:"Clubm";i:5494;s:9:"PondUnvul";i:5495;s:5:"Salte";i:5496;s:11:"RotTubicUnh";i:5497;s:10:"TheoTitlUn";i:5498;s:8:"NatifRen";i:5499;s:4:"Zygn";i:5500;s:3:"Eut";i:5501;s:4:"Gene";i:5502;s:12:"ProReactSemi";i:5503;s:11:"CartPoPyrex";i:5504;s:5:"Shock";i:5505;s:4:"Zygo";i:5506;s:5:"MiTen";i:5507;s:7:"PhrTorr";i:5508;s:8:"GruSeTau";i:5509;s:7:"SmaSoUr";i:5510;s:12:"HoweMegOverw";i:5511;s:7:"NumPrUn";i:5512;s:7:"CyQuebr";i:5513;s:8:"IzzarSup";i:5514;s:8:"CoDihDor";i:5515;s:9:"MowRosSee";i:5516;s:14:"PerseSabeSalpi";i:5517;s:4:"Hear";i:5518;s:7:"StrTrep";i:5519;s:10:"NumerUnrWe";i:5520;s:8:"SleiUnch";i:5521;s:4:"NoPr";i:5522;s:4:"Buzz";i:5523;s:5:"ChMae";i:5524;s:5:"DiMer";i:5525;s:7:"RebetVe";i:5526;s:8:"RevTonsi";i:5527;s:11:"PilSquirSui";i:5528;s:8:"PaVoluYo";i:5529;s:10:"CalaCalcCr";i:5530;s:12:"HoverImpPerf";i:5531;s:7:"EmbHeZa";i:5532;s:5:"Paran";i:5533;s:8:"ElianWat";i:5534;s:13:"LumbMugfuUnsu";i:5535;s:9:"CitOvUnso";i:5536;s:11:"PreSunZerma";i:5537;s:4:"Plan";i:5538;s:10:"LaugMoPopu";i:5539;s:8:"PerSuppl";i:5540;s:11:"CapMeroThre";i:5541;s:7:"DerUndi";i:5542;s:9:"MuddiPuUn";i:5543;s:6:"PoSnai";i:5544;s:4:"Trun";i:5545;s:13:"ConfiExhauSou";i:5546;s:5:"Idiom";i:5547;s:5:"ChSpa";i:5548;s:12:"PheRectTousc";i:5549;s:11:"HemJokSelen";i:5550;s:10:"MilliPeatm";i:5551;s:8:"InacPseu";i:5552;s:7:"VegeXen";i:5553;s:6:"BarGre";i:5554;s:5:"InWhi";i:5555;s:9:"CounReove";i:5556;s:13:"PalmePlaSwang";i:5557;s:9:"DiPreiRee";i:5558;s:10:"CappaHyper";i:5559;s:9:"PatsTymUn";i:5560;s:4:"PrUn";i:5561;s:8:"BongHeat";i:5562;s:4:"Derm";i:5563;s:7:"HomoeSt";i:5564;s:13:"SignaSpeSquif";i:5565;s:9:"OlofPlaco";i:5566;s:7:"CaStoUn";i:5567;s:7:"GlMossi";i:5568;s:7:"TrUnclo";i:5569;s:14:"GlycPolycTapet";i:5570;s:15:"OchroParasVilit";i:5571;s:6:"BairDi";i:5572;s:9:"AssorJuSp";i:5573;s:10:"LooPhPluto";i:5574;s:8:"CorCroni";i:5575;s:7:"LaTerml";i:5576;s:11:"SponsTraTub";i:5577;s:6:"ConOst";i:5578;s:8:"SambuUnl";i:5579;s:8:"HormOrga";i:5580;s:7:"CytRecl";i:5581;s:6:"PySupe";i:5582;s:3:"Foo";i:5583;s:9:"GeoMatPho";i:5584;s:7:"CattPar";i:5585;s:11:"CacheHomUng";i:5586;s:13:"RefTrucuUnrip";i:5587;s:5:"HusWx";i:5588;s:13:"DumbSollyUnla";i:5589;s:7:"CoIroni";i:5590;s:11:"FiberLeniTc";i:5591;s:11:"ReanaUrduZe";i:5592;s:8:"TwiddVir";i:5593;s:5:"Possi";i:5594;s:5:"ArEus";i:5595;s:8:"NeogZono";i:5596;s:8:"ProdPsTe";i:5597;s:6:"AttSte";i:5598;s:4:"Epiz";i:5599;s:5:"Impot";i:5600;s:8:"PotWerec";i:5601;s:13:"DavidPsePseud";i:5602;s:5:"Semic";i:5603;s:4:"Hell";i:5604;s:8:"EncyRepe";i:5605;s:6:"TorUng";i:5606;s:12:"GlSauroUnlea";i:5607;s:7:"RecoThy";i:5608;s:7:"GiQuiSu";i:5609;s:5:"CrVel";i:5610;s:9:"PeSeUnthr";i:5611;s:12:"SchUnleaWitc";i:5612;s:14:"BiannGenitLevi";i:5613;s:11:"EndoMisNong";i:5614;s:3:"Slu";i:5615;s:11:"FruOordxSyn";i:5616;s:7:"RondVes";i:5617;s:4:"Upmo";i:5618;s:9:"MemorShou";i:5619;s:5:"Preli";i:5620;s:4:"Midd";i:5621;s:6:"PilTom";i:5622;s:5:"Wager";i:5623;s:12:"CollaCosSpor";i:5624;s:12:"SleuTaffyTet";i:5625;s:8:"NaceVagi";i:5626;s:5:"Ondag";i:5627;s:3:"Anc";i:5628;s:5:"Unafi";i:5629;s:5:"Parda";i:5630;s:10:"CrossIndig";i:5631;s:4:"ChCo";i:5632;s:6:"JaSeba";i:5633;s:6:"ForPen";i:5634;s:6:"PlunTr";i:5635;s:9:"OverTrUni";i:5636;s:7:"SupUnmo";i:5637;s:13:"SeeaSemilThos";i:5638;s:4:"Clad";i:5639;s:5:"MiSal";i:5640;s:5:"IntRe";i:5641;s:10:"CopGaSpeec";i:5642;s:7:"MaOaRiz";i:5643;s:9:"ElePoseUn";i:5644;s:5:"Trapf";i:5645;s:4:"Syll";i:5646;s:4:"Unof";i:5647;s:7:"SpSupTr";i:5648;s:7:"CrassOv";i:5649;s:9:"SupeTinke";i:5650;s:8:"NoneYamx";i:5651;s:4:"Jour";i:5652;s:7:"SummeSy";i:5653;s:7:"SwaTwin";i:5654;s:5:"Outse";i:5655;s:5:"Unlic";i:5656;s:12:"HospPlastQua";i:5657;s:7:"UnaYabb";i:5658;s:5:"Holly";i:5659;s:7:"PlaUnre";i:5660;s:11:"DisEngMonum";i:5661;s:3:"Opu";i:5662;s:10:"LogyxPrefe";i:5663;s:5:"OccPr";i:5664;s:7:"DrafPro";i:5665;s:8:"SeleTypi";i:5666;s:4:"SuUr";i:5667;s:4:"Sper";i:5668;s:7:"IrUnret";i:5669;s:7:"GoloSem";i:5670;s:12:"QuadrRevToly";i:5671;s:3:"Ura";i:5672;s:6:"ShopVa";i:5673;s:11:"ChucGentlVi";i:5674;s:8:"HaulmHic";i:5675;s:8:"OversOxe";i:5676;s:15:"IllumTritoUnwoo";i:5677;s:10:"ReThunUnpe";i:5678;s:3:"Phe";i:5679;s:5:"OdySe";i:5680;s:10:"DisIrrMans";i:5681;s:9:"DoEpYttri";i:5682;s:12:"OveOxazSpoof";i:5683;s:5:"Recom";i:5684;s:3:"Spy";i:5685;s:9:"ChiFooUns";i:5686;s:8:"NoSuUnov";i:5687;s:7:"LeTiUnr";i:5688;s:14:"ChromIncusSulp";i:5689;s:12:"EschaGastrRe";i:5690;s:12:"OpaquTechnUn";i:5691;s:9:"CuGasSnee";i:5692;s:9:"BrillShoo";i:5693;s:8:"NotUrete";i:5694;s:13:"SpiroSteYaupo";i:5695;s:8:"IntScyph";i:5696;s:5:"HoUnr";i:5697;s:5:"Silkg";i:5698;s:3:"Khw";i:5699;s:8:"MalSemip";i:5700;s:9:"ChrisNonp";i:5701;s:8:"PosPrefl";i:5702;s:6:"RuffSu";i:5703;s:3:"Sax";i:5704;s:8:"MulTermi";i:5705;s:10:"CooMonsUns";i:5706;s:7:"JiPaUnb";i:5707;s:5:"Thymo";i:5708;s:12:"CurstMimePos";i:5709;s:7:"UnrWood";i:5710;s:5:"Favon";i:5711;s:6:"LeSupp";i:5712;s:2:"Sq";i:5713;s:8:"FireInse";i:5714;s:10:"MonopRaffa";i:5715;s:3:"Pho";i:5716;s:14:"ConvNotwiSuper";i:5717;s:5:"OchSt";i:5718;s:10:"DeDialEogh";i:5719;s:9:"MisoUnwra";i:5720;s:8:"ProvUnad";i:5721;s:2:"Cy";i:5722;s:7:"MisenWo";i:5723;s:7:"FiPansc";i:5724;s:13:"AnomaHianPear";i:5725;s:5:"HySat";i:5726;s:4:"PrVa";i:5727;s:12:"QuicUnwWoneg";i:5728;s:9:"DefraHygr";i:5729;s:8:"CherHoMy";i:5730;s:9:"ChanVined";i:5731;s:7:"RaRavis";i:5732;s:15:"LiquiOverpSuper";i:5733;s:11:"CubPhiloUns";i:5734;s:7:"SleiSpa";i:5735;s:6:"SwaUnp";i:5736;s:10:"CoRefUnrig";i:5737;s:15:"SarciThundWantl";i:5738;s:10:"BubonIncOn";i:5739;s:7:"PlScrup";i:5740;s:7:"DeDiObv";i:5741;s:11:"RaisUnderVe";i:5742;s:12:"ConteHaIndec";i:5743;s:11:"MusPolycSul";i:5744;s:7:"SagaSub";i:5745;s:5:"OcUnr";i:5746;s:4:"Reca";i:5747;s:10:"ImmenWitti";i:5748;s:7:"PhraUnc";i:5749;s:5:"Prein";i:5750;s:10:"LievMetPre";i:5751;s:8:"OggSigil";i:5752;s:12:"ThTripyUnbro";i:5753;s:10:"KraTerebTr";i:5754;s:6:"InteSe";i:5755;s:10:"FoSheThora";i:5756;s:9:"ExiNoniUn";i:5757;s:12:"QuRoentSamsa";i:5758;s:6:"PhTyro";i:5759;s:10:"LazybParap";i:5760;s:8:"CaFissMo";i:5761;s:5:"Ataxi";i:5762;s:14:"JaileMolePrear";i:5763;s:10:"BarytSixha";i:5764;s:4:"SiSo";i:5765;s:12:"DiscoInterRa";i:5766;s:11:"HyposSyVici";i:5767;s:4:"InLu";i:5768;s:10:"FlPolysTot";i:5769;s:10:"PrSlaugThe";i:5770;s:9:"MegThTort";i:5771;s:7:"NodReSy";i:5772;s:9:"ConPoliVi";i:5773;s:5:"Snipp";i:5774;s:13:"CuticGunbSulf";i:5775;s:5:"Parag";i:5776;s:3:"Ili";i:5777;s:4:"Call";i:5778;s:14:"MarmSairvSuper";i:5779;s:8:"MollSeam";i:5780;s:10:"InvalSesba";i:5781;s:5:"EqInd";i:5782;s:6:"CoPodo";i:5783;s:7:"AntiWis";i:5784;s:14:"GestiHomePetra";i:5785;s:3:"Phy";i:5786;s:7:"CaimiIs";i:5787;s:8:"OstUrson";i:5788;s:6:"ReSpha";i:5789;s:10:"DiDisParas";i:5790;s:10:"OtalOvSpha";i:5791;s:7:"SaSpavi";i:5792;s:8:"UndwiUns";i:5793;s:5:"Linse";i:5794;s:7:"StUnadu";i:5795;s:4:"SaWi";i:5796;s:13:"InterPsyTempt";i:5797;s:10:"CrosDiSecl";i:5798;s:6:"CyLeMu";i:5799;s:5:"CryNu";i:5800;s:5:"Yiddi";i:5801;s:5:"StoUn";i:5802;s:9:"EggeOutwa";i:5803;s:10:"MoravVisuo";i:5804;s:5:"Ghurr";i:5805;s:14:"CompHolidToxic";i:5806;s:7:"MyoSoci";i:5807;s:9:"LiPosthUn";i:5808;s:6:"SpUnto";i:5809;s:3:"Sep";i:5810;s:11:"FumatIndiTr";i:5811;s:4:"Nege";i:5812;s:10:"PeResuScra";i:5813;s:12:"ConvLinParas";i:5814;s:6:"HooPal";i:5815;s:6:"SporSt";i:5816;s:11:"DyeForsUnem";i:5817;s:10:"PredrPrStu";i:5818;s:11:"DivHegLutem";i:5819;s:10:"ScornUnhig";i:5820;s:9:"BeneTubWa";i:5821;s:5:"SanSt";i:5822;s:7:"LaSaUnc";i:5823;s:7:"MisaPol";i:5824;s:14:"MungPenetRudol";i:5825;s:8:"NonclSur";i:5826;s:10:"HydLigPres";i:5827;s:6:"QuirSa";i:5828;s:7:"SquirTa";i:5829;s:8:"EnExpPro";i:5830;s:4:"HeUn";i:5831;s:9:"UnstUnthr";i:5832;s:5:"LiZoo";i:5833;s:5:"Somet";i:5834;s:7:"UnZucch";i:5835;s:6:"InSpUn";i:5836;s:12:"GawNeptOutbo";i:5837;s:4:"Weed";i:5838;s:9:"BoPolySig";i:5839;s:8:"HatcNucl";i:5840;s:6:"HypInn";i:5841;s:6:"UlUngr";i:5842;s:10:"MesScUnatt";i:5843;s:5:"UncYo";i:5844;s:12:"BimilFasciMe";i:5845;s:8:"PubiTass";i:5846;s:15:"GaragLouchNonlo";i:5847;s:8:"PhilhTah";i:5848;s:6:"SornWa";i:5849;s:10:"TamilUnhor";i:5850;s:3:"Bra";i:5851;s:12:"HeteParPreco";i:5852;s:15:"DazemTotanUncri";i:5853;s:9:"FahlMilly";i:5854;s:5:"ImOpp";i:5855;s:5:"Sahar";i:5856;s:14:"MelopReceTrira";i:5857;s:5:"Mimly";i:5858;s:13:"LunaSupraUnil";i:5859;s:12:"ResiVerWhipp";i:5860;s:10:"NucTolUnou";i:5861;s:6:"SuTele";i:5862;s:6:"PinShu";i:5863;s:8:"HyphYuzl";i:5864;s:7:"SplinUn";i:5865;s:5:"Stack";i:5866;s:5:"Odont";i:5867;s:11:"MoParamPreo";i:5868;s:7:"LiMonst";i:5869;s:6:"StroUn";i:5870;s:4:"Tica";i:5871;s:7:"CouElec";i:5872;s:8:"EnrKusim";i:5873;s:3:"Sic";i:5874;s:9:"IncoUnali";i:5875;s:11:"RedeSelexUp";i:5876;s:5:"Parat";i:5877;s:9:"NonacWhif";i:5878;s:12:"RoSangaWistl";i:5879;s:8:"RatTranq";i:5880;s:9:"JentLogUp";i:5881;s:3:"Mus";i:5882;s:14:"ServaSparkSper";i:5883;s:5:"Sylvi";i:5884;s:10:"SnackSurge";i:5885;s:11:"AztecHomPou";i:5886;s:4:"PrPs";i:5887;s:4:"Unex";i:5888;s:7:"CeorGon";i:5889;s:8:"DisesWin";i:5890;s:8:"EglesIsl";i:5891;s:5:"Remix";i:5892;s:5:"BuRes";i:5893;s:11:"HemogOvSupe";i:5894;s:14:"MeasMenacSpora";i:5895;s:8:"OsphyTal";i:5896;s:4:"Blan";i:5897;s:6:"EfflHo";i:5898;s:9:"PrSpeceUn";i:5899;s:6:"IscUnt";i:5900;s:4:"InTa";i:5901;s:9:"PrehuTene";i:5902;s:9:"MatRuShak";i:5903;s:11:"NervePetRed";i:5904;s:8:"EpiMoOut";i:5905;s:7:"CuOsUnp";i:5906;s:6:"InfiSu";i:5907;s:8:"DeMnSkid";i:5908;s:5:"Secti";i:5909;s:5:"CurEc";i:5910;s:11:"FlankMiShri";i:5911;s:12:"ReeliRuRusty";i:5912;s:7:"PluviTu";i:5913;s:12:"MonoNonasStr";i:5914;s:5:"PreTo";i:5915;s:7:"StuUnif";i:5916;s:5:"Unord";i:5917;s:7:"DichrNa";i:5918;s:9:"NimmProlo";i:5919;s:3:"Jol";i:5920;s:9:"HadScoSub";i:5921;s:5:"Stoma";i:5922;s:8:"PrSignTe";i:5923;s:10:"RebTubeVul";i:5924;s:8:"DiNoWood";i:5925;s:5:"OrSwa";i:5926;s:9:"GaIntelOr";i:5927;s:5:"Scuti";i:5928;s:4:"Gent";i:5929;s:7:"NoniPte";i:5930;s:6:"CaHeat";i:5931;s:10:"PhalaWarve";i:5932;s:8:"PrPygUra";i:5933;s:5:"Nutcr";i:5934;s:11:"LepidMonUnr";i:5935;s:7:"PalPatr";i:5936;s:9:"ReiSixVes";i:5937;s:6:"MesUnf";i:5938;s:3:"Tsu";i:5939;s:5:"Ununi";i:5940;s:6:"ReSpie";i:5941;s:4:"SuUn";i:5942;s:9:"OrdinRabb";i:5943;s:11:"MiNapuPlebx";i:5944;s:8:"PodogSta";i:5945;s:9:"KuskuNigg";i:5946;s:4:"Capr";i:5947;s:5:"Chlam";i:5948;s:5:"CoSch";i:5949;s:13:"IndisMyseTubx";i:5950;s:10:"MazRhiTran";i:5951;s:4:"Prer";i:5952;s:7:"DoMecRu";i:5953;s:11:"RamaTopeZen";i:5954;s:13:"OverSpicUnmel";i:5955;s:5:"Prata";i:5956;s:6:"NebaSt";i:5957;s:12:"BudmaCritDio";i:5958;s:11:"GyroPaleoUn";i:5959;s:8:"EsMaRede";i:5960;s:8:"SizTetra";i:5961;s:7:"AsStaTa";i:5962;s:12:"NurseSmyrUna";i:5963;s:9:"CoUnmWrea";i:5964;s:10:"SisUnaltUn";i:5965;s:11:"GigarGranPr";i:5966;s:9:"MacabMyop";i:5967;s:7:"CrediGu";i:5968;s:10:"OncRinSlug";i:5969;s:4:"Rose";i:5970;s:5:"MisMo";i:5971;s:11:"ShoeSlUnmem";i:5972;s:10:"PasquPreci";i:5973;s:9:"DedicInIr";i:5974;s:12:"GlovePyjamSa";i:5975;s:9:"CockThroa";i:5976;s:3:"Jau";i:5977;s:7:"LibTrip";i:5978;s:13:"MegaRaceTobac";i:5979;s:5:"Atwix";i:5980;s:12:"PlatyReUnben";i:5981;s:11:"MoPropSulph";i:5982;s:5:"Twere";i:5983;s:4:"Timi";i:5984;s:8:"CoOophUn";i:5985;s:7:"KentiRe";i:5986;s:11:"AgrolKaPter";i:5987;s:5:"Xeres";i:5988;s:14:"ChoriLochiMaga";i:5989;s:12:"FucoxGaoNutt";i:5990;s:9:"PrStrTerp";i:5991;s:7:"ItSciss";i:5992;s:11:"GassiNatUnb";i:5993;s:4:"Unri";i:5994;s:8:"PrSarSir";i:5995;s:4:"HeUs";i:5996;s:11:"CyMatamStan";i:5997;s:9:"MarcSuTub";i:5998;s:7:"UnrVall";i:5999;s:7:"PentaPh";i:6000;s:5:"Knobs";i:6001;s:5:"Poste";i:6002;s:4:"InPr";i:6003;s:12:"DoldrRecUros";i:6004;s:12:"OctodParkeTh";i:6005;s:6:"PlaThe";i:6006;s:2:"En";i:6007;s:13:"PseudReheSpri";i:6008;s:8:"NitPraTo";i:6009;s:6:"PrTetr";i:6010;s:8:"DiscKary";i:6011;s:5:"Rattl";i:6012;s:6:"SpicSu";i:6013;s:7:"NonfTet";i:6014;s:7:"UnUteri";i:6015;s:14:"LesbiNawxPrere";i:6016;s:4:"Thra";i:6017;s:7:"MercThu";i:6018;s:6:"PiSini";i:6019;s:3:"Ten";i:6020;s:10:"ExecuXylos";i:6021;s:6:"IaKoUn";i:6022;s:4:"Coin";i:6023;s:6:"HagsLo";i:6024;s:8:"ProsScha";i:6025;s:5:"Weepx";i:6026;s:7:"MirdaPo";i:6027;s:4:"Pegm";i:6028;s:8:"DemForgo";i:6029;s:5:"Meado";i:6030;s:10:"ArchLimbSk";i:6031;s:8:"NegOrSem";i:6032;s:10:"KonRusseUn";i:6033;s:4:"PrSm";i:6034;s:7:"MuRenUn";i:6035;s:3:"Ras";i:6036;s:10:"InterKaTep";i:6037;s:13:"RedoToseTropi";i:6038;s:6:"LibeTa";i:6039;s:8:"ChQuinRe";i:6040;s:4:"Zoop";i:6041;s:10:"MorSpanUne";i:6042;s:10:"IndicRoUnr";i:6043;s:5:"SarTi";i:6044;s:3:"Mou";i:6045;s:7:"CaSatra";i:6046;s:12:"ForamMoaMura";i:6047;s:8:"MaMoSulp";i:6048;s:13:"PiscaTonnVisi";i:6049;s:14:"IndusMuskPodic";i:6050;s:9:"ResprSeni";i:6051;s:9:"AftHypeOa";i:6052;s:9:"DeDigEuco";i:6053;s:8:"TriUndes";i:6054;s:7:"MoskSik";i:6055;s:9:"FifthLuSu";i:6056;s:5:"Proli";i:6057;s:9:"PolyeQuin";i:6058;s:9:"InIsUnmat";i:6059;s:7:"CrPondo";i:6060;s:9:"LantStrid";i:6061;s:13:"HaveSubpTechn";i:6062;s:8:"MaMusaSc";i:6063;s:7:"MoSeSup";i:6064;s:5:"Timef";i:6065;s:9:"LoPyvuSyn";i:6066;s:3:"Spl";i:6067;s:9:"MouReseUm";i:6068;s:4:"ToWa";i:6069;s:9:"MonocSept";i:6070;s:13:"KanepTransTri";i:6071;s:4:"Enhy";i:6072;s:13:"SpareSubTaran";i:6073;s:8:"PagaSupe";i:6074;s:13:"MaesNaturSero";i:6075;s:6:"ClDere";i:6076;s:6:"BuruSn";i:6077;s:5:"PerPs";i:6078;s:7:"PanmUpw";i:6079;s:6:"GreeRe";i:6080;s:7:"ErUnpro";i:6081;s:7:"CycliHe";i:6082;s:2:"Eu";i:6083;s:7:"HoPandr";i:6084;s:11:"MesNonOscil";i:6085;s:10:"ExtPoTetra";i:6086;s:13:"MastoMonoPyro";i:6087;s:6:"HiNoSp";i:6088;s:10:"GlPraUnhed";i:6089;s:6:"RewTho";i:6090;s:7:"UnaWoor";i:6091;s:14:"ElfhPicknUntes";i:6092;s:4:"Tinw";i:6093;s:6:"CrucEx";i:6094;s:8:"FloInoge";i:6095;s:11:"HadexPremTh";i:6096;s:3:"Mys";i:6097;s:6:"TiUnau";i:6098;s:12:"EurNonsuNonv";i:6099;s:5:"Woonx";i:6100;s:5:"DiUnr";i:6101;s:7:"PoRaUlt";i:6102;s:5:"Ethno";i:6103;s:4:"Heat";i:6104;s:6:"ShacSm";i:6105;s:11:"NonPhantSyn";i:6106;s:4:"Quin";i:6107;s:14:"FewnKieseOptio";i:6108;s:11:"ClypImpaUns";i:6109;s:9:"ParalUnbe";i:6110;s:7:"ParSing";i:6111;s:10:"CleSexuaUn";i:6112;s:10:"SacchSolid";i:6113;s:9:"HorsIncor";i:6114;s:13:"FoodlHusNight";i:6115;s:8:"MonSubUn";i:6116;s:5:"Inkho";i:6117;s:4:"Panz";i:6118;s:4:"NoPe";i:6119;s:7:"PonTatu";i:6120;s:8:"CotVangl";i:6121;s:4:"Xero";i:6122;s:5:"CurPi";i:6123;s:3:"Str";i:6124;s:4:"Sacr";i:6125;s:4:"Quai";i:6126;s:5:"Uncoa";i:6127;s:9:"ChitProsu";i:6128;s:6:"PenuUn";i:6129;s:4:"BrLa";i:6130;s:7:"EnterJu";i:6131;s:11:"CoOutsTroph";i:6132;s:11:"ComNondRele";i:6133;s:12:"PreleScenUnp";i:6134;s:12:"ExHogwaPerip";i:6135;s:10:"UndWaZibet";i:6136;s:12:"GinkxQuinqRa";i:6137;s:6:"EmmVer";i:6138;s:10:"PalReTeasa";i:6139;s:12:"MePrepoSurge";i:6140;s:12:"HomSommUncti";i:6141;s:9:"TheoTinin";i:6142;s:12:"IrrMonoPetio";i:6143;s:13:"MultOxysuThes";i:6144;s:4:"Natc";i:6145;s:11:"DisiEnStoma";i:6146;s:11:"HegarPolTan";i:6147;s:5:"StTra";i:6148;s:5:"Unexc";i:6149;s:12:"AweSyllaUnpr";i:6150;s:6:"CaReci";i:6151;s:12:"SatirUnmiVac";i:6152;s:5:"SeUnc";i:6153;s:8:"ProtTint";i:6154;s:5:"Waikl";i:6155;s:10:"MaMegasSpr";i:6156;s:3:"Gam";i:6157;s:7:"CoVagar";i:6158;s:7:"ToppUre";i:6159;s:7:"ArcImpa";i:6160;s:15:"MokoxOcherSubar";i:6161;s:10:"DeeEtiolTh";i:6162;s:10:"TrottUnUnf";i:6163;s:5:"Bronz";i:6164;s:4:"Tutr";i:6165;s:6:"PisaRi";i:6166;s:6:"PuSuTe";i:6167;s:5:"PoSpi";i:6168;s:7:"LiReefe";i:6169;s:9:"QuadWimex";i:6170;s:11:"GoniSizalYa";i:6171;s:3:"Ins";i:6172;s:15:"GinglSemigSerpe";i:6173;s:13:"GadinSemUnfei";i:6174;s:7:"TinUnne";i:6175;s:5:"Grapt";i:6176;s:7:"PhScaTh";i:6177;s:5:"Preaf";i:6178;s:3:"Kap";i:6179;s:10:"MaywMohaWe";i:6180;s:4:"TaTe";i:6181;s:5:"FiTig";i:6182;s:3:"Jer";i:6183;s:11:"OdobPawneSp";i:6184;s:15:"NonbaTorchUnext";i:6185;s:4:"SlSt";i:6186;s:5:"Weelx";i:6187;s:5:"ExTem";i:6188;s:4:"EnUn";i:6189;s:12:"HurlyPythoTr";i:6190;s:4:"Glos";i:6191;s:13:"SpisuTriceUne";i:6192;s:11:"EpacImpoPro";i:6193;s:12:"MetOakeOuttr";i:6194;s:9:"OrScWoodn";i:6195;s:7:"HoMazum";i:6196;s:9:"MesenSemi";i:6197;s:9:"RegulSand";i:6198;s:5:"Stasi";i:6199;s:6:"UnZoog";i:6200;s:4:"Paro";i:6201;s:11:"SinapStaSup";i:6202;s:9:"TaeTeTheo";i:6203;s:10:"KrapiProrh";i:6204;s:9:"GuarMarxi";i:6205;s:13:"SybarTanUnwed";i:6206;s:4:"JaUn";i:6207;s:6:"SemiSo";i:6208;s:8:"UniUnind";i:6209;s:12:"DulTriuTubul";i:6210;s:5:"Engra";i:6211;s:12:"UnpreZygonZy";i:6212;s:5:"Recko";i:6213;s:9:"GrinnInUn";i:6214;s:7:"EleGeLi";i:6215;s:12:"MolSyconUnst";i:6216;s:4:"Scum";i:6217;s:10:"FlaFostUnd";i:6218;s:5:"Peris";i:6219;s:8:"CophLava";i:6220;s:12:"SmetSpriTran";i:6221;s:8:"NephUnwa";i:6222;s:7:"QueetSc";i:6223;s:10:"FeifGueTab";i:6224;s:10:"OliveUnciv";i:6225;s:10:"BuilCompTa";i:6226;s:7:"RecliRe";i:6227;s:4:"OrUn";i:6228;s:7:"ExRadSa";i:6229;s:3:"Jux";i:6230;s:12:"ShikiSupeUns";i:6231;s:4:"DeUn";i:6232;s:9:"MiNuReine";i:6233;s:8:"TheTrime";i:6234;s:6:"SemWot";i:6235;s:3:"Pyr";i:6236;s:9:"NerthOuts";i:6237;s:6:"DyscGl";i:6238;s:3:"Mag";i:6239;s:8:"LucraPer";i:6240;s:7:"ParUlVi";i:6241;s:3:"Ken";i:6242;s:8:"ScrUnsca";i:6243;s:10:"PewmaToUnp";i:6244;s:13:"MangNeglPolys";i:6245;s:9:"FoInhiSor";i:6246;s:11:"DrunSporTir";i:6247;s:4:"HaLo";i:6248;s:11:"PharyQuUnch";i:6249;s:12:"MiamMilPhary";i:6250;s:4:"Stuc";i:6251;s:10:"DomElectPs";i:6252;s:4:"Kumn";i:6253;s:9:"PreteVaga";i:6254;s:4:"MiPr";i:6255;s:9:"GimGrooPr";i:6256;s:5:"ForSc";i:6257;s:3:"Opa";i:6258;s:5:"Hirud";i:6259;s:6:"OverPo";i:6260;s:9:"MarNomaSt";i:6261;s:4:"Saxo";i:6262;s:6:"HyLept";i:6263;s:7:"MyxaOcy";i:6264;s:3:"Iso";i:6265;s:9:"NonchPeri";i:6266;s:9:"ElytrTric";i:6267;s:6:"MePurb";i:6268;s:6:"ChaOut";i:6269;s:8:"NonTrave";i:6270;s:8:"InsiPent";i:6271;s:6:"PrTern";i:6272;s:4:"Subg";i:6273;s:7:"ImprUnv";i:6274;s:4:"Unha";i:6275;s:8:"ProgUngl";i:6276;s:12:"LaMammiToros";i:6277;s:5:"SeaSu";i:6278;s:5:"Vigon";i:6279;s:9:"AriIdiLog";i:6280;s:8:"NovPsyTh";i:6281;s:8:"KrausTro";i:6282;s:5:"Undup";i:6283;s:10:"ContrMysTe";i:6284;s:4:"Harl";i:6285;s:3:"Upk";i:6286;s:6:"InfThi";i:6287;s:7:"GriMyct";i:6288;s:9:"BleaCoccy";i:6289;s:12:"NeuroSoldiSw";i:6290;s:8:"HeKathRa";i:6291;s:10:"NackeNitro";i:6292;s:14:"CapacPestPsych";i:6293;s:8:"OutlaUnl";i:6294;s:7:"PhrenVe";i:6295;s:10:"PluQuicYum";i:6296;s:5:"Tesse";i:6297;s:5:"Poiki";i:6298;s:6:"AppaWr";i:6299;s:8:"CattChPo";i:6300;s:5:"Unsuc";i:6301;s:10:"JotnOtoUns";i:6302;s:10:"ArgNubiPre";i:6303;s:9:"GastLagga";i:6304;s:3:"Yom";i:6305;s:8:"SpSteVet";i:6306;s:12:"ColPleoSheri";i:6307;s:10:"FrankNeSes";i:6308;s:3:"Kon";i:6309;s:14:"CraigMayorOpul";i:6310;s:8:"UnwUnVer";i:6311;s:7:"NonpeRe";i:6312;s:3:"Ort";i:6313;s:7:"HirsQui";i:6314;s:7:"HurOver";i:6315;s:5:"PrSlo";i:6316;s:5:"Cerem";i:6317;s:10:"PleaToXero";i:6318;s:9:"ProUnemUn";i:6319;s:4:"UnWi";i:6320;s:5:"PrePr";i:6321;s:11:"MigSerUnref";i:6322;s:5:"Progu";i:6323;s:8:"HaMultSe";i:6324;s:6:"RiRoll";i:6325;s:5:"Unthi";i:6326;s:14:"ExtolMisnSperm";i:6327;s:7:"PreTimb";i:6328;s:7:"RickeSh";i:6329;s:10:"PericPyrrh";i:6330;s:7:"TiUnder";i:6331;s:13:"HeaVocalWhewt";i:6332;s:4:"Misw";i:6333;s:3:"Bol";i:6334;s:14:"PlatiPunnSphac";i:6335;s:3:"Vag";i:6336;s:14:"SainxSlowSteep";i:6337;s:10:"MacrPaguTr";i:6338;s:4:"Scut";i:6339;s:8:"ShooUnin";i:6340;s:10:"OenanSayax";i:6341;s:14:"EpigLegioTiara";i:6342;s:11:"MenogStopVa";i:6343;s:6:"PlUniq";i:6344;s:10:"EmOverZepp";i:6345;s:11:"SeTelenWork";i:6346;s:6:"MoReco";i:6347;s:4:"Reda";i:6348;s:8:"PelRucTh";i:6349;s:10:"UngriWitho";i:6350;s:4:"MuOr";i:6351;s:14:"PrincThiopVivi";i:6352;s:6:"NonaPh";i:6353;s:5:"NeZoo";i:6354;s:8:"PuffySup";i:6355;s:4:"Bair";i:6356;s:14:"ManwaSinaiUroc";i:6357;s:6:"AntLux";i:6358;s:9:"FungKokTo";i:6359;s:10:"AquipPhyto";i:6360;s:8:"OrtTuVie";i:6361;s:11:"CaloGudgPie";i:6362;s:4:"Palt";i:6363;s:7:"HubXeno";i:6364;s:9:"KnobSyTra";i:6365;s:10:"MirzaObiUn";i:6366;s:10:"CongeStali";i:6367;s:5:"Pomox";i:6368;s:3:"Vol";i:6369;s:7:"InepMel";i:6370;s:10:"ResiRhynSe";i:6371;s:8:"PrTychUn";i:6372;s:9:"DactyTrut";i:6373;s:13:"BradaCarpHera";i:6374;s:9:"OctRebiSu";i:6375;s:10:"ShTruerVer";i:6376;s:9:"HeMeTellu";i:6377;s:12:"FairyGraveSt";i:6378;s:8:"PergTalp";i:6379;s:3:"Sty";i:6380;s:6:"IntSol";i:6381;s:9:"SpeciToUn";i:6382;s:8:"NutaSkir";i:6383;s:5:"Rhomb";i:6384;s:9:"SharUnsni";i:6385;s:5:"DiInt";i:6386;s:5:"Splen";i:6387;s:6:"OraTer";i:6388;s:5:"Dahli";i:6389;s:8:"ClaOliSo";i:6390;s:8:"PeSangSa";i:6391;s:4:"Tors";i:6392;s:9:"HypopPari";i:6393;s:14:"ThiosTonoUnise";i:6394;s:7:"CatPara";i:6395;s:4:"Sumb";i:6396;s:7:"OsPreSw";i:6397;s:8:"HattUrsi";i:6398;s:6:"DiNotc";i:6399;s:5:"Disso";i:6400;s:8:"ReRenaWh";i:6401;s:12:"MonocSeroWha";i:6402;s:5:"Toade";i:6403;s:4:"CnMo";i:6404;s:8:"CardiSca";i:6405;s:4:"Corn";i:6406;s:14:"ExtrPotorPreve";i:6407;s:13:"HomalRepreSub";i:6408;s:4:"Vers";i:6409;s:5:"Unbla";i:6410;s:12:"PalaPneStodg";i:6411;s:5:"PyTri";i:6412;s:10:"DeyshPlPul";i:6413;s:9:"IvorUndis";i:6414;s:7:"PellTid";i:6415;s:5:"Thatx";i:6416;s:8:"DecaEleu";i:6417;s:4:"Maea";i:6418;s:9:"AuObUnadj";i:6419;s:11:"EntInoJumps";i:6420;s:12:"TreUnnotUnsw";i:6421;s:9:"AtechGeOv";i:6422;s:9:"LecUndeUn";i:6423;s:9:"DimnStrin";i:6424;s:13:"MonopNonsuRep";i:6425;s:3:"Ver";i:6426;s:6:"PesTho";i:6427;s:11:"InPredSemis";i:6428;s:7:"PoPrint";i:6429;s:4:"Coch";i:6430;s:4:"Mopp";i:6431;s:3:"Dra";i:6432;s:7:"DrygoPr";i:6433;s:9:"SulciUnco";i:6434;s:8:"SaboUnUn";i:6435;s:10:"TanqTreUns";i:6436;s:7:"UnjVesp";i:6437;s:4:"Pror";i:6438;s:4:"Love";i:6439;s:10:"EmeHyPlaty";i:6440;s:6:"JucRec";i:6441;s:11:"ConfePhoTra";i:6442;s:9:"ConcoPoss";i:6443;s:12:"GalacIrSemif";i:6444;s:9:"ParaUnawa";i:6445;s:7:"UnUnZam";i:6446;s:7:"PinSabr";i:6447;s:9:"MicOverSu";i:6448;s:8:"PoilViny";i:6449;s:8:"SentiSup";i:6450;s:10:"ParlaProSu";i:6451;s:11:"BipBrancObu";i:6452;s:5:"Uncri";i:6453;s:5:"Suran";i:6454;s:12:"BloClaviTitu";i:6455;s:4:"TwUn";i:6456;s:6:"PiPost";i:6457;s:7:"ElaFeHy";i:6458;s:5:"Unnav";i:6459;s:13:"OphidPenthUni";i:6460;s:3:"Lar";i:6461;s:15:"LongiRisibTopog";i:6462;s:7:"ThamuUn";i:6463;s:11:"LotProUnbla";i:6464;s:7:"TaUnmer";i:6465;s:7:"MilPhyl";i:6466;s:5:"IncNi";i:6467;s:3:"Fix";i:6468;s:6:"ExSwip";i:6469;s:5:"OraUn";i:6470;s:4:"Scle";i:6471;s:9:"HoReparSu";i:6472;s:9:"OvangUnpr";i:6473;s:12:"DoorSnowbUni";i:6474;s:9:"OverPolUn";i:6475;s:3:"Mit";i:6476;s:5:"Unsop";i:6477;s:14:"HeavyHeterJump";i:6478;s:9:"PostpWall";i:6479;s:11:"RepRicTetra";i:6480;s:12:"LiqPalaUnipu";i:6481;s:7:"ClMesPe";i:6482;s:13:"HeterLirParda";i:6483;s:5:"Trieq";i:6484;s:9:"SamarTigr";i:6485;s:5:"Diver";i:6486;s:4:"LiUn";i:6487;s:4:"Beta";i:6488;s:6:"DivuOp";i:6489;s:9:"NoniScruf";i:6490;s:9:"LoafOnchi";i:6491;s:3:"Ele";i:6492;s:7:"TeraUnm";i:6493;s:5:"Palee";i:6494;s:5:"Solid";i:6495;s:4:"Mynp";i:6496;s:10:"FairyPulWo";i:6497;s:4:"Wate";i:6498;s:4:"Rodd";i:6499;s:10:"OffPlUngui";i:6500;s:9:"OvPlayUrd";i:6501;s:7:"SocioUp";i:6502;s:5:"EtPim";i:6503;s:10:"ParonPrVir";i:6504;s:2:"Ju";i:6505;s:10:"EchiSaThuy";i:6506;s:12:"NepNodulRefi";i:6507;s:11:"IncSinecUni";i:6508;s:9:"GarbStThe";i:6509;s:13:"GluttHeadsRun";i:6510;s:8:"EvaKafir";i:6511;s:9:"CoInNonen";i:6512;s:7:"InuOtho";i:6513;s:14:"DeutParenPrere";i:6514;s:10:"EnvMetamSh";i:6515;s:8:"ButadDis";i:6516;s:9:"MendPseud";i:6517;s:6:"PaProv";i:6518;s:3:"Ina";i:6519;s:10:"EcclInfPol";i:6520;s:6:"PrUnde";i:6521;s:14:"IneffPudgPursu";i:6522;s:9:"SchizToVo";i:6523;s:3:"Imb";i:6524;s:4:"ChSt";i:6525;s:9:"LarLecoSt";i:6526;s:5:"UnUnd";i:6527;s:7:"IntReoc";i:6528;s:7:"FixedIm";i:6529;s:9:"CogTabWoo";i:6530;s:8:"SpaciUnp";i:6531;s:6:"BaraBe";i:6532;s:5:"Unbar";i:6533;s:12:"CometDeIntra";i:6534;s:12:"ErePeracWhee";i:6535;s:7:"CounUnw";i:6536;s:8:"NonTormo";i:6537;s:10:"PreiSpeTri";i:6538;s:11:"CerLankWeve";i:6539;s:12:"TractUngelVe";i:6540;s:10:"DepDrUnsol";i:6541;s:4:"Mult";i:6542;s:6:"RewVol";i:6543;s:5:"MorSy";i:6544;s:8:"UndWilkx";i:6545;s:4:"BeUn";i:6546;s:7:"ImpasUn";i:6547;s:10:"IndiaMerca";i:6548;s:4:"Unfo";i:6549;s:8:"ImMiMuti";i:6550;s:13:"NaviPreaUnspa";i:6551;s:5:"Extra";i:6552;s:11:"RenuWhWicke";i:6553;s:7:"TergUnp";i:6554;s:8:"MydTabid";i:6555;s:3:"Lod";i:6556;s:4:"Cruc";i:6557;s:5:"DiaSo";i:6558;s:8:"HetInter";i:6559;s:7:"ClMisap";i:6560;s:3:"Spe";i:6561;s:9:"IllecPant";i:6562;s:5:"Rhabd";i:6563;s:13:"BrushWireYell";i:6564;s:7:"DilaOve";i:6565;s:12:"CyprDegPotma";i:6566;s:8:"TicUtero";i:6567;s:6:"InPtyc";i:6568;s:12:"DelaMaraScul";i:6569;s:7:"AlpCrSt";i:6570;s:12:"HeUnproVagra";i:6571;s:4:"Theo";i:6572;s:12:"RecSplanVenu";i:6573;s:4:"Groc";i:6574;s:14:"BasilNoncoZami";i:6575;s:10:"LifexPasto";i:6576;s:14:"PsychSubdWhipp";i:6577;s:5:"MosTe";i:6578;s:14:"CoplaObsiXyloq";i:6579;s:10:"FatbrStrat";i:6580;s:7:"ToUnpit";i:6581;s:5:"PitSh";i:6582;s:8:"JapRatch";i:6583;s:8:"VarniYod";i:6584;s:11:"HancoRemTen";i:6585;s:10:"KoreiOverf";i:6586;s:9:"TineVimfu";i:6587;s:7:"NoruUne";i:6588;s:3:"Ure";i:6589;s:11:"CultrPlaSun";i:6590;s:10:"ExogaTrowe";i:6591;s:5:"Upcom";i:6592;s:10:"CuneiFePre";i:6593;s:10:"MatPlSnaff";i:6594;s:8:"PhaTarTy";i:6595;s:9:"BetHenrUn";i:6596;s:6:"BruMet";i:6597;s:4:"Peck";i:6598;s:5:"Makes";i:6599;s:5:"Unpit";i:6600;s:8:"ScThewTo";i:6601;s:4:"Spec";i:6602;s:9:"UnplUnres";i:6603;s:11:"OxytoSaftUp";i:6604;s:5:"CiHoo";i:6605;s:9:"InverMoTi";i:6606;s:3:"Upt";i:6607;s:15:"OverfPolymPrein";i:6608;s:9:"CotePhoto";i:6609;s:4:"Crew";i:6610;s:8:"PredPrim";i:6611;s:10:"CephaShrub";i:6612;s:3:"Nuc";i:6613;s:3:"Ouz";i:6614;s:9:"ThereVena";i:6615;s:3:"Sug";i:6616;s:3:"Nam";i:6617;s:7:"QuinoTr";i:6618;s:5:"Reove";i:6619;s:10:"GliomIntSc";i:6620;s:9:"DuchReban";i:6621;s:6:"DisHot";i:6622;s:10:"PhoroRatio";i:6623;s:4:"BrTi";i:6624;s:10:"HyInterTae";i:6625;s:4:"Plic";i:6626;s:6:"IsoSar";i:6627;s:8:"CyaniPos";i:6628;s:7:"JossaTh";i:6629;s:7:"MoTease";i:6630;s:9:"PenciSynt";i:6631;s:12:"TobUnjelUrti";i:6632;s:7:"StorTri";i:6633;s:4:"Squi";i:6634;s:10:"IsoThUnsof";i:6635;s:9:"EpilNonbu";i:6636;s:4:"Recr";i:6637;s:12:"GlossMinTown";i:6638;s:3:"Sid";i:6639;s:10:"MoireNoTri";i:6640;s:9:"FungSkUna";i:6641;s:10:"LewPemmUne";i:6642;s:9:"MetaOveSc";i:6643;s:11:"FlaMiscaOwl";i:6644;s:6:"PrWhis";i:6645;s:7:"SpTesUn";i:6646;s:12:"BroSubjuUnmo";i:6647;s:6:"SupTch";i:6648;s:10:"HeromLodPo";i:6649;s:12:"BeniCheesSur";i:6650;s:9:"HypeOverl";i:6651;s:4:"Sela";i:6652;s:10:"StiTurtlVe";i:6653;s:11:"GreMacrMarr";i:6654;s:8:"AsHanImp";i:6655;s:6:"FaSher";i:6656;s:12:"MicPonerTran";i:6657;s:11:"GigarHyMudd";i:6658;s:11:"AmErytFemin";i:6659;s:5:"Sacer";i:6660;s:9:"OutmUneph";i:6661;s:7:"DissWar";i:6662;s:9:"ObPlScabr";i:6663;s:5:"Inten";i:6664;s:3:"Raj";i:6665;s:8:"LaPipeUn";i:6666;s:8:"PreReUne";i:6667;s:11:"InpMonobPle";i:6668;s:4:"Here";i:6669;s:8:"GasPicWe";i:6670;s:12:"GamoInfiMyos";i:6671;s:8:"MinoPlai";i:6672;s:4:"Unbe";i:6673;s:5:"Outla";i:6674;s:7:"PseuVer";i:6675;s:4:"Wing";i:6676;s:5:"Unrav";i:6677;s:6:"GreTur";i:6678;s:11:"OysStaTrith";i:6679;s:8:"SaSymTur";i:6680;s:13:"MisliOdontPha";i:6681;s:11:"HaysNanomRe";i:6682;s:8:"PasPhySe";i:6683;s:11:"ColoLutOpta";i:6684;s:7:"LyUrtic";i:6685;s:6:"TimbTo";i:6686;s:9:"ShotTeete";i:6687;s:13:"BrushCafCompu";i:6688;s:9:"UncrUnhum";i:6689;s:6:"FibrTa";i:6690;s:7:"GameInc";i:6691;s:5:"Weakh";i:6692;s:10:"ObfusQuave";i:6693;s:13:"FurlIrreUngen";i:6694;s:8:"FoulPant";i:6695;s:11:"DisEuryQuar";i:6696;s:4:"Fati";i:6697;s:5:"Shalt";i:6698;s:9:"GhosMiThe";i:6699;s:12:"BandlKowtoTe";i:6700;s:10:"ImponTabUn";i:6701;s:10:"HexOnsUnpe";i:6702;s:8:"ElegiTem";i:6703;s:7:"PenhRep";i:6704;s:9:"OdonPhosp";i:6705;s:6:"PlWenl";i:6706;s:7:"ReprUns";i:6707;s:4:"Smit";i:6708;s:5:"Terro";i:6709;s:7:"ResusTe";i:6710;s:10:"HoppiPrSop";i:6711;s:5:"Shini";i:6712;s:12:"HeterRoamSal";i:6713;s:6:"OverWo";i:6714;s:5:"ScUnp";i:6715;s:4:"Mead";i:6716;s:5:"Upstr";i:6717;s:5:"Unrep";i:6718;s:4:"Rumi";i:6719;s:7:"SuperTe";i:6720;s:5:"ThVer";i:6721;s:12:"SupeThrTomop";i:6722;s:12:"OugPedanStib";i:6723;s:6:"CiCoun";i:6724;s:9:"HeHobThug";i:6725;s:6:"CohGno";i:6726;s:4:"Styr";i:6727;s:9:"MePresRes";i:6728;s:9:"ExPhThumb";i:6729;s:8:"SheSkipp";i:6730;s:10:"CaFaunaUnq";i:6731;s:4:"Meri";i:6732;s:10:"FerReWaste";i:6733;s:5:"Ninet";i:6734;s:9:"KmetxUnar";i:6735;s:8:"PulsSaxt";i:6736;s:6:"PrepUp";i:6737;s:5:"Marxi";i:6738;s:10:"EpiscSubpr";i:6739;s:8:"KochProh";i:6740;s:5:"GemOr";i:6741;s:15:"HeroiSubcoUnbra";i:6742;s:4:"Scot";i:6743;s:8:"MisSnort";i:6744;s:6:"PhaPha";i:6745;s:8:"RevUnpre";i:6746;s:8:"AnoSupVe";i:6747;s:4:"Stam";i:6748;s:3:"Pru";i:6749;s:7:"BeCatap";i:6750;s:5:"DevSc";i:6751;s:11:"SexiSocUnil";i:6752;s:6:"InNons";i:6753;s:11:"RecaTecnTre";i:6754;s:5:"CiCoa";i:6755;s:11:"CoDiscPachy";i:6756;s:4:"Pleu";i:6757;s:10:"ExtrHesiMu";i:6758;s:6:"TilbTo";i:6759;s:5:"Vacuo";i:6760;s:13:"PlaTheopTrica";i:6761;s:12:"OsirProtRhoe";i:6762;s:8:"MulWeava";i:6763;s:4:"Mort";i:6764;s:10:"QuSchilTan";i:6765;s:7:"SqueTar";i:6766;s:12:"OrthPhaRoent";i:6767;s:9:"MoPifTrib";i:6768;s:15:"SubcaSuperUnbra";i:6769;s:7:"LilaNan";i:6770;s:10:"OrnisPresu";i:6771;s:7:"AndreSo";i:6772;s:5:"Sloug";i:6773;s:9:"HoOxyheSc";i:6774;s:13:"DermLawlMetac";i:6775;s:13:"DirTwelvVirgi";i:6776;s:11:"NuttRatapVi";i:6777;s:4:"Teet";i:6778;s:9:"GardeScWr";i:6779;s:6:"PaPlUn";i:6780;s:8:"PhrUrrad";i:6781;s:4:"OrTh";i:6782;s:10:"LenUnUtopi";i:6783;s:7:"FaOssZi";i:6784;s:9:"NonmTexas";i:6785;s:8:"SuTricUn";i:6786;s:11:"NaOrchiRive";i:6787;s:6:"KleUnp";i:6788;s:6:"PhilUn";i:6789;s:4:"Cade";i:6790;s:13:"MacanOvercUpc";i:6791;s:8:"PinnSpha";i:6792;s:11:"IcMegalPara";i:6793;s:5:"NonTi";i:6794;s:10:"PienxVaria";i:6795;s:9:"EncoNoYal";i:6796;s:4:"Uptr";i:6797;s:8:"AnconSyc";i:6798;s:9:"FeIntoSta";i:6799;s:7:"RelShif";i:6800;s:7:"ExonePr";i:6801;s:6:"PuThio";i:6802;s:6:"HesiKa";i:6803;s:5:"PanTy";i:6804;s:11:"MacuMiRomag";i:6805;s:12:"LearnNotaSti";i:6806;s:4:"Raki";i:6807;s:8:"OrthUnfl";i:6808;s:10:"KurvePhySp";i:6809;s:7:"HisMump";i:6810;s:9:"PanxTreen";i:6811;s:8:"IndifUnc";i:6812;s:5:"ByMus";i:6813;s:11:"OuSuspUnplu";i:6814;s:12:"IsoLoundOutp";i:6815;s:15:"HexaxSinuaUntro";i:6816;s:8:"SiccUnde";i:6817;s:7:"CaprSep";i:6818;s:8:"RetroTet";i:6819;s:9:"InteQuSem";i:6820;s:5:"Imper";i:6821;s:3:"Psy";i:6822;s:12:"GnarLogTogsx";i:6823;s:4:"Unpa";i:6824;s:7:"HorseRe";i:6825;s:12:"PaUndisUndre";i:6826;s:3:"Lus";i:6827;s:2:"Cu";i:6828;s:3:"Sip";i:6829;s:3:"Tom";i:6830;s:7:"MicRask";i:6831;s:4:"Pect";i:6832;s:12:"ThesTrichVir";i:6833;s:5:"Unwas";i:6834;s:5:"Naple";i:6835;s:9:"MegPrTumo";i:6836;s:11:"HastaPeteSu";i:6837;s:4:"Unrh";i:6838;s:7:"PolyTol";i:6839;s:13:"DisaOrnitPlat";i:6840;s:3:"Oes";i:6841;s:5:"Puboi";i:6842;s:5:"Polyt";i:6843;s:11:"NonmPsTetra";i:6844;s:4:"Uniu";i:6845;s:11:"FasPreRamis";i:6846;s:5:"Stylo";i:6847;s:13:"PonciScyUnloc";i:6848;s:9:"MiaoRecol";i:6849;s:11:"SleSuperVig";i:6850;s:6:"RampRe";i:6851;s:5:"Hongx";i:6852;s:9:"ReticUnpo";i:6853;s:9:"LactiTrip";i:6854;s:7:"PolStTr";i:6855;s:10:"AxonoGraci";i:6856;s:12:"LinSuddeVerm";i:6857;s:9:"CataSuste";i:6858;s:7:"GiRussi";i:6859;s:8:"SextoSla";i:6860;s:5:"Suthe";i:6861;s:6:"PreSha";i:6862;s:10:"MoisRaSpir";i:6863;s:5:"Herco";i:6864;s:9:"TepaWalac";i:6865;s:5:"Tempe";i:6866;s:7:"RaisiUn";i:6867;s:9:"PremUnwif";i:6868;s:6:"MacSta";i:6869;s:12:"EphahPaurUnd";i:6870;s:9:"ParafSurv";i:6871;s:10:"FiRelatUns";i:6872;s:10:"OverdSinto";i:6873;s:5:"NoPup";i:6874;s:10:"CounForPty";i:6875;s:6:"MethVi";i:6876;s:11:"DecayKnocPu";i:6877;s:4:"Xena";i:6878;s:8:"ReRetrUn";i:6879;s:5:"KlTwi";i:6880;s:14:"BeechSubpUnmea";i:6881;s:5:"Unpro";i:6882;s:4:"PrSe";i:6883;s:6:"ReStUn";i:6884;s:8:"FeruOcra";i:6885;s:8:"HomTetry";i:6886;s:5:"IneNa";i:6887;s:7:"IndiPro";i:6888;s:11:"GinGuppScle";i:6889;s:9:"FellLight";i:6890;s:6:"LymPro";i:6891;s:5:"HaUnd";i:6892;s:12:"DaboiIntPant";i:6893;s:11:"CyaJangPann";i:6894;s:8:"PulStink";i:6895;s:12:"BolDevicOpis";i:6896;s:8:"HyTempWh";i:6897;s:7:"CheerSe";i:6898;s:5:"OmPyo";i:6899;s:3:"Tit";i:6900;s:7:"RetiSow";i:6901;s:8:"SemShall";i:6902;s:9:"SecStroVa";i:6903;s:10:"PieSemiSub";i:6904;s:4:"Conv";i:6905;s:10:"PiemaUndex";i:6906;s:11:"BreFrankUng";i:6907;s:9:"OkiTenUnw";i:6908;s:6:"PoStav";i:6909;s:2:"Zi";i:6910;s:11:"DiarrDoWind";i:6911;s:8:"AlicPara";i:6912;s:8:"SarcSnuf";i:6913;s:5:"Poetr";i:6914;s:5:"Nonma";i:6915;s:12:"BathMegUnple";i:6916;s:8:"PoSatUnd";i:6917;s:4:"Exor";i:6918;s:5:"Megar";i:6919;s:7:"HyIndMe";i:6920;s:8:"DuPhUnsp";i:6921;s:7:"SugViki";i:6922;s:9:"PreSquUns";i:6923;s:5:"Tiger";i:6924;s:2:"Dy";i:6925;s:8:"DebaRoun";i:6926;s:10:"PrecTicTra";i:6927;s:5:"Myoca";i:6928;s:9:"PinaSundr";i:6929;s:12:"NonioSonoTes";i:6930;s:8:"PersTone";i:6931;s:7:"PloPrel";i:6932;s:13:"EyepNorthVisc";i:6933;s:7:"RatiRou";i:6934;s:5:"Place";i:6935;s:5:"Proch";i:6936;s:9:"AppRepSto";i:6937;s:6:"OnlySe";i:6938;s:8:"DieguOsk";i:6939;s:11:"MatteOptaRa";i:6940;s:12:"SqTricuUncon";i:6941;s:12:"BreaDolpTrea";i:6942;s:10:"SerabUnlec";i:6943;s:4:"TaUn";i:6944;s:5:"HyPro";i:6945;s:12:"HeteIrraLupi";i:6946;s:6:"OverPa";i:6947;s:4:"Pili";i:6948;s:7:"MediVen";i:6949;s:7:"StUnple";i:6950;s:4:"Toxi";i:6951;s:4:"Medi";i:6952;s:11:"RadiiSoTouc";i:6953;s:8:"PeriPhan";i:6954;s:11:"EpopHeadsSh";i:6955;s:2:"Ka";i:6956;s:6:"IchSpe";i:6957;s:6:"ChTeta";i:6958;s:9:"LateSemih";i:6959;s:4:"Lutr";i:6960;s:10:"BedebHysMa";i:6961;s:8:"SardShru";i:6962;s:10:"CoreDePain";i:6963;s:3:"Tsa";i:6964;s:2:"De";i:6965;s:12:"MotRhinoVoll";i:6966;s:5:"Prefa";i:6967;s:3:"Est";i:6968;s:10:"FitOverdUn";i:6969;s:11:"PontaQuaSem";i:6970;s:8:"ForMaMis";i:6971;s:11:"PlumlPraeZi";i:6972;s:4:"Pseu";i:6973;s:8:"PuritTru";i:6974;s:12:"EquSpirWokex";i:6975;s:8:"SleepUnt";i:6976;s:5:"MidPo";i:6977;s:6:"ProtRu";i:6978;s:6:"ProgRe";i:6979;s:12:"PleniRunoUnu";i:6980;s:8:"BorJeann";i:6981;s:4:"Unwo";i:6982;s:8:"PamphSho";i:6983;s:8:"IneSkirt";i:6984;s:10:"PeRyukTrag";i:6985;s:9:"EnnoRonVa";i:6986;s:14:"MastySikarTrir";i:6987;s:3:"Tem";i:6988;s:15:"MozamRepliUncon";i:6989;s:8:"HecKoUnd";i:6990;s:8:"TellxThi";i:6991;s:7:"PseRadi";i:6992;s:8:"MissTrac";i:6993;s:7:"SlTrust";i:6994;s:8:"MacrSpUn";i:6995;s:9:"DaisyGoWh";i:6996;s:7:"PeriSor";i:6997;s:13:"HyposInteOver";i:6998;s:4:"Maza";i:6999;s:11:"QuSubscWeez";i:7000;s:6:"TruUre";i:7001;s:9:"PamlRecus";i:7002;s:10:"CycliStren";i:7003;s:5:"Serpi";i:7004;s:3:"Top";i:7005;s:3:"Moo";i:7006;s:7:"KneMaSe";i:7007;s:7:"IsatPol";i:7008;s:11:"ErytFattPeu";i:7009;s:7:"OsteUnd";i:7010;s:5:"Bulba";i:7011;s:8:"PaSnVerb";i:7012;s:5:"Unsin";i:7013;s:7:"PseuRub";i:7014;s:5:"Exsan";i:7015;s:10:"InterMiPen";i:7016;s:5:"Pleur";i:7017;s:13:"HydrInterOver";i:7018;s:4:"Coag";i:7019;s:7:"SaSicar";i:7020;s:6:"MonPre";i:7021;s:8:"SaSumbWi";i:7022;s:7:"GePreci";i:7023;s:4:"Tymp";i:7024;s:9:"SinuoUbii";i:7025;s:5:"Overw";i:7026;s:7:"HeorUnp";i:7027;s:6:"NeisUn";i:7028;s:10:"FrKikaPrec";i:7029;s:3:"Obt";i:7030;s:11:"BowmCroMedi";i:7031;s:12:"GreenMasUnde";i:7032;s:10:"CathoComed";i:7033;s:8:"SmeStUna";i:7034;s:5:"Undig";i:7035;s:8:"MicProba";i:7036;s:5:"Unvai";i:7037;s:8:"PreStoTo";i:7038;s:6:"LibWar";i:7039;s:9:"SaccVampi";i:7040;s:9:"ScoSqWanl";i:7041;s:13:"IndiKiloZephy";i:7042;s:3:"Rah";i:7043;s:8:"UncoUnUn";i:7044;s:7:"SwartUn";i:7045;s:4:"Sord";i:7046;s:4:"Nasi";i:7047;s:4:"Phil";i:7048;s:4:"Suss";i:7049;s:9:"HomopRiSp";i:7050;s:3:"Sca";i:7051;s:2:"Bi";i:7052;s:5:"Unret";i:7053;s:5:"LocTo";i:7054;s:6:"RubUnd";i:7055;s:4:"Foca";i:7056;s:7:"PePreRu";i:7057;s:9:"BaptiUnpa";i:7058;s:5:"ScSig";i:7059;s:11:"EndGenevUns";i:7060;s:6:"LepSpi";i:7061;s:4:"Vorl";i:7062;s:4:"Thro";i:7063;s:9:"FugHaHete";i:7064;s:8:"InteNote";i:7065;s:6:"PapUnd";i:7066;s:5:"Lands";i:7067;s:11:"FluPseuThel";i:7068;s:5:"Letha";i:7069;s:8:"GomaRidi";i:7070;s:9:"CourtMadd";i:7071;s:9:"NovePenSh";i:7072;s:2:"Ut";i:7073;s:4:"Vatm";i:7074;s:4:"Taxi";i:7075;s:7:"QuaveSp";i:7076;s:9:"MonstPred";i:7077;s:11:"PedTriUntoi";i:7078;s:11:"PlaUpteVivi";i:7079;s:8:"SurrTabl";i:7080;s:4:"Imid";i:7081;s:6:"NonZoo";i:7082;s:9:"PalaTrich";i:7083;s:5:"Thecl";i:7084;s:13:"OphPaleoUnout";i:7085;s:12:"DrumNebQuila";i:7086;s:11:"EpMiniProag";i:7087;s:13:"MuleRotuTriac";i:7088;s:5:"Ostra";i:7089;s:5:"Endoc";i:7090;s:12:"HydrVaiYouwa";i:7091;s:5:"RevSe";i:7092;s:8:"DermSigh";i:7093;s:5:"Splat";i:7094;s:5:"MerUn";i:7095;s:8:"KrisPaPr";i:7096;s:9:"LodgMaPol";i:7097;s:9:"FlMenoVul";i:7098;s:8:"MyelaRab";i:7099;s:5:"Commo";i:7100;s:10:"HaPhyTorcx";i:7101;s:10:"NomiReUnfo";i:7102;s:5:"Unwal";i:7103;s:10:"MucSiniTea";i:7104;s:9:"PsSolUnha";i:7105;s:7:"CraHyPr";i:7106;s:12:"IntRheuSerop";i:7107;s:12:"ManWindYouth";i:7108;s:8:"NotoSemi";i:7109;s:11:"MaPhytPosts";i:7110;s:8:"DeaPeWal";i:7111;s:12:"NotaPrimaSou";i:7112;s:4:"Sera";i:7113;s:7:"PaSaUnt";i:7114;s:6:"SimSup";i:7115;s:9:"MalpPhoto";i:7116;s:11:"AsbConiPred";i:7117;s:10:"MetatSpoon";i:7118;s:3:"Hol";i:7119;s:8:"DilLoTap";i:7120;s:4:"OuSe";i:7121;s:4:"SeYe";i:7122;s:7:"SulpUns";i:7123;s:7:"FliQuak";i:7124;s:8:"OphtRecu";i:7125;s:6:"CheNon";i:7126;s:12:"PandSarcoUns";i:7127;s:9:"MeMuleOxy";i:7128;s:10:"CerGarisMe";i:7129;s:9:"BoDuskPer";i:7130;s:4:"Moos";i:7131;s:13:"LiberPeaRolle";i:7132;s:4:"Unhi";i:7133;s:9:"ReadUnroo";i:7134;s:6:"BeaUnu";i:7135;s:5:"Taget";i:7136;s:8:"CellExPr";i:7137;s:7:"PaoScSu";i:7138;s:11:"DonHaeSubro";i:7139;s:3:"Gun";i:7140;s:8:"HePyraSu";i:7141;s:13:"AscaErythLett";i:7142;s:10:"HypocZoonu";i:7143;s:7:"SpewiUn";i:7144;s:5:"Sylva";i:7145;s:8:"MesSteno";i:7146;s:9:"RhomTiger";i:7147;s:4:"Rumm";i:7148;s:2:"Hu";i:7149;s:9:"DosGeopRe";i:7150;s:5:"GlWot";i:7151;s:7:"PaUnUno";i:7152;s:8:"CapeYenx";i:7153;s:10:"TouchTrUnf";i:7154;s:11:"EnaPreRigwi";i:7155;s:9:"TyphlUnUn";i:7156;s:12:"MecoMuddSpea";i:7157;s:4:"Myel";i:7158;s:10:"ExcoReiSej";i:7159;s:11:"ConjMotorZy";i:7160;s:6:"JoinUn";i:7161;s:10:"ExudSympUn";i:7162;s:10:"DisHorseSp";i:7163;s:8:"PreTherm";i:7164;s:7:"SupeTuc";i:7165;s:9:"CaDemSame";i:7166;s:5:"Zenag";i:7167;s:3:"Rug";i:7168;s:7:"RatioZo";i:7169;s:12:"InconPrQuidd";i:7170;s:8:"DevoiPal";i:7171;s:11:"CaIndaTourn";i:7172;s:5:"Infer";i:7173;s:9:"HydroPrec";i:7174;s:4:"Yout";i:7175;s:3:"Hak";i:7176;s:7:"UntZeph";i:7177;s:4:"Peed";i:7178;s:8:"MetPiTuc";i:7179;s:11:"LiNonsaOsci";i:7180;s:3:"Hea";i:7181;s:11:"PiprPostSen";i:7182;s:10:"MiSniUnmet";i:7183;s:6:"CaiExt";i:7184;s:6:"UnfWom";i:7185;s:7:"NonfUnw";i:7186;s:6:"NajNon";i:7187;s:10:"PaProTruck";i:7188;s:10:"RemoSemiSt";i:7189;s:8:"PerUnsym";i:7190;s:5:"Xyrid";i:7191;s:7:"AnaChun";i:7192;s:7:"HaLepro";i:7193;s:5:"Veuve";i:7194;s:14:"NoaxOverpWanto";i:7195;s:4:"Lith";i:7196;s:7:"FlaTara";i:7197;s:7:"PsychTe";i:7198;s:3:"Fra";i:7199;s:5:"Sitti";i:7200;s:13:"SnottTroTwitt";i:7201;s:9:"LampWreck";i:7202;s:8:"SuperVar";i:7203;s:7:"NondSym";i:7204;s:7:"FeetaSo";i:7205;s:9:"GrafNonfi";i:7206;s:2:"Od";i:7207;s:5:"Butan";i:7208;s:9:"LippLiPhy";i:7209;s:5:"ParPr";i:7210;s:7:"ScuUnUs";i:7211;s:6:"InPsUn";i:7212;s:7:"PipSaye";i:7213;s:9:"MatcStirr";i:7214;s:13:"ParrTenuUnpla";i:7215;s:7:"DurEnMe";i:7216;s:5:"PoPor";i:7217;s:11:"CriCutSauce";i:7218;s:10:"StViceWeax";i:7219;s:9:"FingNeuri";i:7220;s:6:"SubVir";i:7221;s:7:"TrWildi";i:7222;s:6:"ReSeUn";i:7223;s:10:"IsoPuSandl";i:7224;s:6:"SenSpl";i:7225;s:12:"LabryPrUnfel";i:7226;s:10:"LingaOpist";i:7227;s:10:"HydPeUninq";i:7228;s:15:"CloudObeyeUnder";i:7229;s:4:"Post";i:7230;s:7:"CrPhoto";i:7231;s:10:"MoPredePre";i:7232;s:12:"CardSchisWhi";i:7233;s:11:"JapKolVenom";i:7234;s:4:"Unfl";i:7235;s:8:"PhacVers";i:7236;s:3:"Lab";i:7237;s:3:"Jig";i:7238;s:10:"CorybIrrem";i:7239;s:9:"TaguUnimp";i:7240;s:5:"Whipp";i:7241;s:4:"Stoc";i:7242;s:13:"ConveOwregShi";i:7243;s:10:"BromoCaFor";i:7244;s:9:"MoStahlTy";i:7245;s:14:"IndigLoriStaye";i:7246;s:10:"ElProeUnsu";i:7247;s:8:"PanUnrid";i:7248;s:5:"Prehe";i:7249;s:3:"Leg";i:7250;s:9:"BotryPrem";i:7251;s:5:"UnWed";i:7252;s:7:"CraPudd";i:7253;s:12:"HainSnonSulf";i:7254;s:12:"PerSouthUnpo";i:7255;s:8:"ElGrafTh";i:7256;s:8:"CrassWil";i:7257;s:9:"ConNonOly";i:7258;s:5:"Schni";i:7259;s:9:"TertZygom";i:7260;s:11:"ScrVacYawwe";i:7261;s:10:"ButDisceTr";i:7262;s:11:"ChoriEcarVe";i:7263;s:4:"Femi";i:7264;s:7:"NeUnpos";i:7265;s:13:"MurPerveRunol";i:7266;s:5:"PrSig";i:7267;s:10:"EmbMidmTro";i:7268;s:8:"CestInca";i:7269;s:5:"CorPy";i:7270;s:12:"BylSpoonTymp";i:7271;s:9:"NotiSlunk";i:7272;s:5:"Whitr";i:7273;s:4:"Unba";i:7274;s:10:"MonatTheor";i:7275;s:14:"IdioShoalTamac";i:7276;s:7:"PartiTo";i:7277;s:11:"CounCraMill";i:7278;s:4:"Uppe";i:7279;s:9:"PatavPewt";i:7280;s:4:"CoSa";i:7281;s:4:"Vase";i:7282;s:6:"ColcNo";i:7283;s:14:"ThrifTransWate";i:7284;s:10:"NoTacTartu";i:7285;s:12:"ReiUnforUngl";i:7286;s:8:"CrExpaEx";i:7287;s:5:"ParPh";i:7288;s:6:"SkylUn";i:7289;s:5:"StaUn";i:7290;s:10:"FibuSlTric";i:7291;s:10:"KartvMagne";i:7292;s:13:"BotcCatstSenc";i:7293;s:7:"EnduHem";i:7294;s:12:"MammaStrUnfo";i:7295;s:9:"SlidSpUro";i:7296;s:13:"PatSnabbVolat";i:7297;s:5:"Mysti";i:7298;s:4:"OsSc";i:7299;s:5:"Trick";i:7300;s:5:"Cohen";i:7301;s:12:"HypMoralTinh";i:7302;s:10:"PettySacri";i:7303;s:8:"StrowUlt";i:7304;s:5:"Circu";i:7305;s:4:"Rifl";i:7306;s:13:"FlocxLaxUnacc";i:7307;s:10:"HirsMyodPe";i:7308;s:5:"LiMic";i:7309;s:9:"MesRainWa";i:7310;s:8:"BuntiSyp";i:7311;s:12:"AgroConveOrt";i:7312;s:4:"Rais";i:7313;s:8:"NeSemiTr";i:7314;s:7:"CrGenPe";i:7315;s:7:"RecUnch";i:7316;s:10:"LatchPosse";i:7317;s:9:"BavenWeal";i:7318;s:5:"NonSp";i:7319;s:6:"MontVo";i:7320;s:15:"MisfaNarcaNonto";i:7321;s:5:"DeTuc";i:7322;s:8:"CountEuc";i:7323;s:10:"HaiIliocUn";i:7324;s:10:"CoaptLumPa";i:7325;s:5:"Thoms";i:7326;s:5:"TaTop";i:7327;s:8:"PhalaVib";i:7328;s:6:"BoraKa";i:7329;s:12:"TassTempeVir";i:7330;s:8:"MonosNam";i:7331;s:10:"SpiTethThe";i:7332;s:13:"QuerUnchiVare";i:7333;s:6:"BipNon";i:7334;s:9:"JaPolyoSi";i:7335;s:12:"AmbiEntGloam";i:7336;s:13:"MetalRegalSup";i:7337;s:10:"ErotiRetun";i:7338;s:12:"PyraSteprSyn";i:7339;s:9:"StabUngui";i:7340;s:5:"Pates";i:7341;s:12:"CaEnseeRunbo";i:7342;s:13:"NeurPteroSoma";i:7343;s:10:"RitarSquTa";i:7344;s:3:"Hum";i:7345;s:7:"PangePo";i:7346;s:5:"InOut";i:7347;s:11:"CoExtoUncan";i:7348;s:11:"HomoeNoctUn";i:7349;s:7:"BuPoRes";i:7350;s:8:"PhalRect";i:7351;s:3:"Mea";i:7352;s:13:"JoyleNotReplu";i:7353;s:4:"Nonh";i:7354;s:4:"Reco";i:7355;s:2:"Fu";i:7356;s:9:"OctSaSpic";i:7357;s:8:"NinUntes";i:7358;s:5:"NoSpl";i:7359;s:5:"OuSte";i:7360;s:9:"ResurStau";i:7361;s:14:"RevokSclerThan";i:7362;s:5:"Tombl";i:7363;s:10:"IsMarooNaz";i:7364;s:11:"OtotoSeriSo";i:7365;s:8:"CerePleu";i:7366;s:7:"ForaPse";i:7367;s:8:"CentTube";i:7368;s:14:"CebuEkamaSauce";i:7369;s:10:"GlenxHypog";i:7370;s:6:"HyOafx";i:7371;s:9:"RackWillo";i:7372;s:5:"LoOgh";i:7373;s:9:"WigfuWint";i:7374;s:14:"IndiOrthoProdu";i:7375;s:12:"ClCoconUnpre";i:7376;s:12:"ExpFlunkMega";i:7377;s:9:"ChorInsWo";i:7378;s:4:"Hair";i:7379;s:6:"UncWin";i:7380;s:7:"PaPsoro";i:7381;s:9:"TradUndit";i:7382;s:9:"MakeUnlik";i:7383;s:10:"AntiApMeta";i:7384;s:7:"PhoPhyt";i:7385;s:7:"ColMust";i:7386;s:10:"CathaConPh";i:7387;s:8:"NoTromVe";i:7388;s:3:"Cos";i:7389;s:10:"CapDunPres";i:7390;s:14:"HypanIncuLeopa";i:7391;s:6:"UnasWe";i:7392;s:5:"ExpUn";i:7393;s:5:"IntUn";i:7394;s:5:"Overf";i:7395;s:5:"Rameq";i:7396;s:9:"ImmLoxoUn";i:7397;s:5:"EmaGi";i:7398;s:13:"EsoEurasUnlab";i:7399;s:8:"LobaPrZo";i:7400;s:9:"ProaUnrYa";i:7401;s:8:"SubUnleg";i:7402;s:9:"ComaExNeg";i:7403;s:10:"HandwPrUnc";i:7404;s:3:"Bre";i:7405;s:7:"QuatrVo";i:7406;s:3:"Erm";i:7407;s:8:"ParThioc";i:7408;s:7:"NoctiTr";i:7409;s:7:"ElUncha";i:7410;s:11:"PitiRecStri";i:7411;s:4:"OtPa";i:7412;s:9:"GanoMyrrh";i:7413;s:4:"Redu";i:7414;s:8:"HuPifTon";i:7415;s:6:"NeUnmu";i:7416;s:5:"GeHus";i:7417;s:14:"DisafProvQuino";i:7418;s:11:"LoydQuReman";i:7419;s:3:"Sta";i:7420;s:12:"RefTeasUnchi";i:7421;s:6:"ClaTam";i:7422;s:5:"Disho";i:7423;s:11:"BaNowneRein";i:7424;s:7:"CoFocus";i:7425;s:6:"SemiWa";i:7426;s:14:"BottoObsceUltr";i:7427;s:4:"Shoa";i:7428;s:7:"MonoTri";i:7429;s:10:"LackQuadUn";i:7430;s:13:"SarcTetaTrump";i:7431;s:11:"UmbUntawVin";i:7432;s:14:"NewlSkewnUnpar";i:7433;s:4:"Podo";i:7434;s:3:"Wor";i:7435;s:12:"InterKeratMy";i:7436;s:11:"ResStrTesta";i:7437;s:5:"Unfat";i:7438;s:4:"Prof";i:7439;s:5:"Misco";i:7440;s:10:"ChurLimbUn";i:7441;s:5:"Rammi";i:7442;s:10:"CarCeleoSn";i:7443;s:9:"DysaSeven";i:7444;s:4:"Inno";i:7445;s:5:"PhoUn";i:7446;s:7:"QuiReUn";i:7447;s:14:"AlgarBrumEquiv";i:7448;s:4:"FiWo";i:7449;s:5:"Wuthe";i:7450;s:11:"MoguePorSpr";i:7451;s:7:"HiMarRe";i:7452;s:9:"UndeUnder";i:7453;s:13:"PinUndelUpspe";i:7454;s:9:"SomitSwSy";i:7455;s:7:"FeGaMed";i:7456;s:10:"ImmPowSore";i:7457;s:7:"DisUnop";i:7458;s:9:"LactOliUn";i:7459;s:5:"GroVe";i:7460;s:6:"StTele";i:7461;s:3:"Exc";i:7462;s:4:"MePa";i:7463;s:8:"SubcZaca";i:7464;s:9:"SyUnsWast";i:7465;s:2:"Gh";i:7466;s:9:"RecRobTum";i:7467;s:11:"DistPreUnre";i:7468;s:12:"EbHoerxMatro";i:7469;s:13:"BeeCorreGetae";i:7470;s:12:"LariOutflPro";i:7471;s:9:"InSmVocab";i:7472;s:5:"Hecta";i:7473;s:6:"IgnaTw";i:7474;s:5:"Seric";i:7475;s:8:"SaeSeiTi";i:7476;s:6:"OppPat";i:7477;s:5:"MoTha";i:7478;s:6:"NonvSk";i:7479;s:4:"Sarc";i:7480;s:5:"Autho";i:7481;s:13:"BestoStiUncla";i:7482;s:9:"CanToolUn";i:7483;s:9:"FornModio";i:7484;s:9:"RhapWartx";i:7485;s:10:"GynodImKra";i:7486;s:13:"FirepPreShinz";i:7487;s:12:"KroLucuMetem";i:7488;s:6:"ReUnsu";i:7489;s:5:"Rooib";i:7490;s:9:"ExpFiGout";i:7491;s:8:"EuTeYess";i:7492;s:11:"PluRecReins";i:7493;s:3:"Com";i:7494;s:5:"FooUn";i:7495;s:4:"Tort";i:7496;s:4:"Brac";i:7497;s:5:"EquTo";i:7498;s:9:"ExTrUnsmi";i:7499;s:4:"Lobe";i:7500;s:5:"GynSu";i:7501;s:10:"PondSxTric";i:7502;s:9:"FocaUnreq";i:7503;s:6:"IrrePr";i:7504;s:4:"Styt";i:7505;s:4:"FoPi";i:7506;s:6:"NaTenn";i:7507;s:5:"Bookb";i:7508;s:3:"Yet";i:7509;s:10:"MyxedSpinn";i:7510;s:9:"AweeNonco";i:7511;s:8:"PrenRecr";i:7512;s:7:"CoImmod";i:7513;s:8:"ApocEeMe";i:7514;s:11:"GiNilsxVisa";i:7515;s:4:"Taxe";i:7516;s:4:"Epip";i:7517;s:10:"ConduMeSub";i:7518;s:10:"CrouReVerm";i:7519;s:10:"OversWeath";i:7520;s:6:"ChMeso";i:7521;s:4:"Dipl";i:7522;s:6:"PlinTh";i:7523;s:7:"OrPreSu";i:7524;s:10:"HipPrRevis";i:7525;s:7:"SchSter";i:7526;s:8:"PlasPlum";i:7527;s:5:"UnWol";i:7528;s:7:"MightSp";i:7529;s:9:"PseuSynca";i:7530;s:7:"PsUnpur";i:7531;s:7:"LynxxMa";i:7532;s:4:"Clun";i:7533;s:12:"OverPhlResil";i:7534;s:12:"MetSatiStabl";i:7535;s:5:"Scien";i:7536;s:3:"Tee";i:7537;s:6:"NoVago";i:7538;s:8:"HomMortg";i:7539;s:4:"Stic";i:7540;s:9:"CaCongrUn";i:7541;s:4:"Whar";i:7542;s:9:"OverQuadr";i:7543;s:8:"MisPlata";i:7544;s:9:"CaiSquUnh";i:7545;s:10:"TenUncouUn";i:7546;s:7:"OutTrom";i:7547;s:3:"Lec";i:7548;s:7:"MoSpili";i:7549;s:6:"CaUnab";i:7550;s:10:"MuddPsUnwa";i:7551;s:12:"CoseExpUncon";i:7552;s:5:"Verde";i:7553;s:7:"CoMoWes";i:7554;s:7:"SimuSli";i:7555;s:9:"MylodTran";i:7556;s:7:"CymbSub";i:7557;s:10:"ImThyUnfor";i:7558;s:4:"Stev";i:7559;s:7:"HoploTa";i:7560;s:7:"CoRabix";i:7561;s:7:"OdoTurk";i:7562;s:3:"Fre";i:7563;s:7:"JoKorex";i:7564;s:10:"CofNoneUnc";i:7565;s:3:"Ery";i:7566;s:7:"MusResu";i:7567;s:5:"Valid";i:7568;s:7:"OncoShi";i:7569;s:11:"FoKibbUndim";i:7570;s:3:"Tim";i:7571;s:8:"MortThWi";i:7572;s:13:"ReaalUnsedUnt";i:7573;s:4:"Degl";i:7574;s:9:"MiniOutPe";i:7575;s:13:"HelioHyetMemo";i:7576;s:11:"InvePrediSl";i:7577;s:7:"GhostHe";i:7578;s:10:"RevUncUntr";i:7579;s:5:"Bemai";i:7580;s:7:"TrikeWh";i:7581;s:8:"TelTikix";i:7582;s:9:"MaliUncir";i:7583;s:12:"ForksHepTher";i:7584;s:7:"HypNond";i:7585;s:5:"Wintr";i:7586;s:7:"JeeSubm";i:7587;s:9:"InadeOrUn";i:7588;s:11:"MilnOversTr";i:7589;s:4:"Thal";i:7590;s:3:"Duk";i:7591;s:7:"SleUpla";i:7592;s:2:"Ha";i:7593;s:5:"PeUnm";i:7594;s:9:"DeHipOryz";i:7595;s:4:"PhTe";i:7596;s:8:"UnpZygos";i:7597;s:11:"LovRefiUnco";i:7598;s:6:"DwHodd";i:7599;s:3:"Phi";i:7600;s:7:"DonatSu";i:7601;s:4:"Gran";i:7602;s:8:"LaboTurr";i:7603;s:5:"Geode";i:7604;s:3:"Gle";i:7605;s:5:"Hebra";i:7606;s:4:"Crot";i:7607;s:10:"BloPahSupe";i:7608;s:8:"PostmSer";i:7609;s:6:"IncoUn";i:7610;s:4:"Rere";i:7611;s:11:"ObstTetraUn";i:7612;s:13:"ForehSanieUnc";i:7613;s:6:"SpToma";i:7614;s:5:"Triha";i:7615;s:13:"GallProceWeis";i:7616;s:10:"NonpePolys";i:7617;s:8:"PartrPol";i:7618;s:5:"Volle";i:7619;s:7:"BockPle";i:7620;s:9:"DeDichWel";i:7621;s:10:"IncIndTher";i:7622;s:5:"Minor";i:7623;s:5:"Shado";i:7624;s:6:"ShSlav";i:7625;s:12:"MazalVelVera";i:7626;s:12:"EmargSaUnapp";i:7627;s:11:"OveTussoUnc";i:7628;s:13:"NovaToniUnfor";i:7629;s:12:"FormOnaOutba";i:7630;s:7:"LouvReg";i:7631;s:8:"SerShZau";i:7632;s:10:"EbullIncav";i:7633;s:12:"SmirkStryUna";i:7634;s:7:"StraiUn";i:7635;s:4:"Besn";i:7636;s:6:"DuchIm";i:7637;s:4:"Cowp";i:7638;s:8:"GermJuMi";i:7639;s:11:"BurdHydProm";i:7640;s:9:"PeSeTimbe";i:7641;s:3:"Cac";i:7642;s:6:"MortUn";i:7643;s:5:"Straw";i:7644;s:10:"ExterProph";i:7645;s:11:"RetroSwTele";i:7646;s:6:"ThiUnf";i:7647;s:4:"Unsk";i:7648;s:9:"AnnodHoIl";i:7649;s:5:"ErgEt";i:7650;s:13:"JoviPreabSnee";i:7651;s:5:"Gentl";i:7652;s:3:"Bet";i:7653;s:12:"NuPebaxTitan";i:7654;s:4:"Tato";i:7655;s:7:"SulphUn";i:7656;s:5:"DubSt";i:7657;s:9:"PlowwUnmi";i:7658;s:9:"PaPhotPis";i:7659;s:5:"Subex";i:7660;s:11:"OptoProSubc";i:7661;s:4:"Rout";i:7662;s:4:"Vapo";i:7663;s:9:"HyMidriSy";i:7664;s:7:"NulliWi";i:7665;s:9:"FerStoUnt";i:7666;s:7:"LusSaun";i:7667;s:7:"SterUnc";i:7668;s:5:"BlOst";i:7669;s:7:"InMicro";i:7670;s:5:"Induc";i:7671;s:4:"Tope";i:7672;s:4:"Disi";i:7673;s:5:"SuUte";i:7674;s:7:"CohMiRe";i:7675;s:11:"AmpuScroWin";i:7676;s:10:"MilToUncit";i:7677;s:12:"CenTerrVesic";i:7678;s:8:"QuixUnhu";i:7679;s:10:"SupeTifTub";i:7680;s:4:"Tens";i:7681;s:4:"SeSy";i:7682;s:5:"Precu";i:7683;s:4:"LiSe";i:7684;s:5:"HerLe";i:7685;s:8:"PapyPhot";i:7686;s:9:"ShantSooh";i:7687;s:10:"TradeVicto";i:7688;s:10:"GuardHiYex";i:7689;s:7:"AlkSpra";i:7690;s:4:"EmSt";i:7691;s:8:"HalosPor";i:7692;s:12:"GrotReasVerb";i:7693;s:13:"InchoNonprPor";i:7694;s:8:"LievLubr";i:7695;s:8:"PhalWith";i:7696;s:7:"HemerLu";i:7697;s:9:"HaMoldUnr";i:7698;s:8:"SpanUnwi";i:7699;s:9:"ConnStTri";i:7700;s:7:"HypSuki";i:7701;s:4:"None";i:7702;s:14:"OverPartuUncal";i:7703;s:13:"JessSperSpide";i:7704;s:11:"ReSynodUpba";i:7705;s:4:"Unsq";i:7706;s:10:"MystaReint";i:7707;s:8:"SubcTeno";i:7708;s:7:"ImpaSom";i:7709;s:10:"PsychUncom";i:7710;s:8:"OverProc";i:7711;s:8:"OvePrRec";i:7712;s:6:"DaiPri";i:7713;s:9:"HeavThUnp";i:7714;s:9:"MesolUntr";i:7715;s:11:"SulUncUncoq";i:7716;s:7:"OvileUn";i:7717;s:10:"EnthuSiale";i:7718;s:10:"ErythOrYen";i:7719;s:6:"DeUnVa";i:7720;s:14:"SemidUnpitVege";i:7721;s:8:"CannWhee";i:7722;s:8:"FratPhRe";i:7723;s:7:"MuSulph";i:7724;s:9:"GhaNudiSl";i:7725;s:7:"SomSuTr";i:7726;s:5:"Nomen";i:7727;s:2:"Be";i:7728;s:7:"LabPreb";i:7729;s:6:"UnqUns";i:7730;s:10:"IntraVespe";i:7731;s:8:"HoUncVot";i:7732;s:8:"GroinSto";i:7733;s:5:"Cooke";i:7734;s:12:"OligoScarVol";i:7735;s:12:"CarGoldUndel";i:7736;s:9:"ReceSqUnd";i:7737;s:7:"GaliJam";i:7738;s:3:"Ole";i:7739;s:7:"LaSilve";i:7740;s:3:"Pne";i:7741;s:3:"Sig";i:7742;s:12:"HugPseudVert";i:7743;s:12:"IleocPolVera";i:7744;s:6:"PrShak";i:7745;s:5:"HyRie";i:7746;s:4:"Swee";i:7747;s:12:"PetThelWinet";i:7748;s:11:"DongoHeReci";i:7749;s:9:"MonoPaZyg";i:7750;s:8:"FrowzGap";i:7751;s:6:"GuInte";i:7752;s:4:"File";i:7753;s:6:"AsinHy";i:7754;s:8:"StotUnfi";i:7755;s:11:"RetTetUname";i:7756;s:13:"BiriCedarQuir";i:7757;s:6:"LuPlWe";i:7758;s:6:"IndSty";i:7759;s:7:"ForePlo";i:7760;s:8:"IntOvipo";i:7761;s:13:"TubuUncomUpdr";i:7762;s:9:"BrLilOver";i:7763;s:3:"Kow";i:7764;s:3:"Nut";i:7765;s:3:"Yuk";i:7766;s:11:"PePhoenVege";i:7767;s:8:"PerceUns";i:7768;s:13:"TalloUnqVesic";i:7769;s:4:"Elen";i:7770;s:13:"GriSymmeUsuri";i:7771;s:4:"Esqu";i:7772;s:5:"PaPea";i:7773;s:6:"EtheTo";i:7774;s:7:"PolyPse";i:7775;s:5:"FibUr";i:7776;s:9:"PassiReSo";i:7777;s:14:"EspiGnomoPerch";i:7778;s:9:"InePoUnsp";i:7779;s:10:"SmitSquaTe";i:7780;s:5:"Trunc";i:7781;s:6:"ChClum";i:7782;s:4:"Pear";i:7783;s:8:"BromoCic";i:7784;s:8:"ExItPayr";i:7785;s:6:"GaSulb";i:7786;s:15:"DeindDiscoWoodr";i:7787;s:4:"Sple";i:7788;s:7:"UndreVi";i:7789;s:12:"ImpPerobStur";i:7790;s:5:"Moder";i:7791;s:3:"Det";i:7792;s:8:"SubUndec";i:7793;s:9:"ProphTris";i:7794;s:10:"PlPreSedul";i:7795;s:9:"PedaUlotr";i:7796;s:6:"GeGing";i:7797;s:9:"BugfChrom";i:7798;s:8:"IntPerso";i:7799;s:10:"SicexSplay";i:7800;s:3:"Ces";i:7801;s:12:"PusSelenSten";i:7802;s:11:"NurseReWars";i:7803;s:9:"CoNaUnbir";i:7804;s:5:"Sospi";i:7805;s:7:"CirGeol";i:7806;s:9:"SesquUnpr";i:7807;s:13:"BioLaticPsora";i:7808;s:10:"IntOstarPe";i:7809;s:5:"PiTwa";i:7810;s:5:"Pleom";i:7811;s:13:"PerinProSasse";i:7812;s:10:"GuanUnsWat";i:7813;s:7:"RekiUnt";i:7814;s:7:"CarPoly";i:7815;s:13:"BiangImprLeti";i:7816;s:9:"ObUnapVeg";i:7817;s:13:"OxanaRasSulph";i:7818;s:11:"InidoShuVis";i:7819;s:7:"SciUnha";i:7820;s:5:"Nonpr";i:7821;s:5:"Sandm";i:7822;s:10:"PolygTwUnc";i:7823;s:7:"GenScTa";i:7824;s:8:"PreSuVin";i:7825;s:11:"MeShekUpren";i:7826;s:6:"PaPicr";i:7827;s:3:"Oph";i:7828;s:9:"CracPeria";i:7829;s:9:"DiscDownl";i:7830;s:5:"Trias";i:7831;s:13:"SecSkullVeris";i:7832;s:9:"FructTher";i:7833;s:6:"DiaZea";i:7834;s:4:"Tren";i:7835;s:14:"LudgaMastProge";i:7836;s:8:"ExteMock";i:7837;s:3:"Vir";i:7838;s:5:"RegSt";i:7839;s:7:"OccuUdx";i:7840;s:10:"GrNeriUnha";i:7841;s:10:"SpiraTopic";i:7842;s:5:"Oreas";i:7843;s:5:"Unmou";i:7844;s:3:"Wes";i:7845;s:2:"Ec";i:7846;s:5:"Probo";i:7847;s:9:"DiscoGano";i:7848;s:8:"NonaSmok";i:7849;s:8:"RamTraUn";i:7850;s:9:"LameMusic";i:7851;s:8:"CowPopes";i:7852;s:12:"PorcStrUnbio";i:7853;s:4:"Rash";i:7854;s:11:"FielInteWes";i:7855;s:4:"Anth";i:7856;s:9:"CeCubiSaf";i:7857;s:7:"PeaPoRo";i:7858;s:7:"PrPyram";i:7859;s:7:"DiseaDo";i:7860;s:10:"HeterPeSac";i:7861;s:9:"FixiLaSla";i:7862;s:8:"HuaProSa";i:7863;s:13:"JuglJustTrivi";i:7864;s:5:"Selac";i:7865;s:7:"HaWacke";i:7866;s:3:"Pas";i:7867;s:12:"DeInaniPolyt";i:7868;s:9:"UncomUnim";i:7869;s:5:"Slaug";i:7870;s:8:"UnbloUnc";i:7871;s:6:"NonPho";i:7872;s:6:"ProfTh";i:7873;s:11:"EpictJuriPe";i:7874;s:7:"MerPhyt";i:7875;s:12:"PhiloRoVirgi";i:7876;s:12:"SpVasolWhiff";i:7877;s:7:"NonloPa";i:7878;s:7:"AbFaUnm";i:7879;s:5:"Tetar";i:7880;s:8:"ReideUre";i:7881;s:9:"EsoPrevRe";i:7882;s:5:"Plymo";i:7883;s:10:"HandlNaTra";i:7884;s:12:"DisbGlumVipe";i:7885;s:9:"OppreWhea";i:7886;s:5:"GueKo";i:7887;s:8:"MonuNond";i:7888;s:7:"SalUnpi";i:7889;s:4:"Syne";i:7890;s:14:"SluitWeisWhatk";i:7891;s:10:"GinwOaPerg";i:7892;s:11:"PleurRaUnde";i:7893;s:10:"MetMetPtil";i:7894;s:11:"OchPenSuper";i:7895;s:3:"Jar";i:7896;s:9:"PreciSond";i:7897;s:3:"Des";i:7898;s:9:"HeKenoNon";i:7899;s:13:"ConneNonspOdy";i:7900;s:9:"AspBliIna";i:7901;s:9:"JansePhPi";i:7902;s:6:"RhTamm";i:7903;s:9:"PoetTucka";i:7904;s:6:"TricZo";i:7905;s:4:"Furd";i:7906;s:5:"Squee";i:7907;s:13:"HippoSteTrigo";i:7908;s:14:"CombiInseLoine";i:7909;s:12:"HaemImprQuav";i:7910;s:4:"Unad";i:7911;s:4:"Uros";i:7912;s:4:"Palp";i:7913;s:4:"ReUl";i:7914;s:6:"ExcPea";i:7915;s:6:"PodTri";i:7916;s:14:"QuibbRobiUntem";i:7917;s:5:"Mothe";i:7918;s:12:"MansThorWald";i:7919;s:6:"HearUr";i:7920;s:13:"DarkPalaeUnar";i:7921;s:10:"SpeTeUntra";i:7922;s:5:"Ineff";i:7923;s:9:"LazexMisi";i:7924;s:7:"HarPseu";i:7925;s:13:"IntSalesVeloc";i:7926;s:5:"Halop";i:7927;s:7:"HyPyroc";i:7928;s:11:"HymeJeProdu";i:7929;s:8:"PseSinga";i:7930;s:7:"UtopXan";i:7931;s:5:"OvPro";i:7932;s:11:"SalUnsWoman";i:7933;s:10:"PyrTesToxo";i:7934;s:5:"GunPr";i:7935;s:7:"OveraUn";i:7936;s:12:"LaPacktWease";i:7937;s:5:"Lepto";i:7938;s:6:"MeStTr";i:7939;s:7:"OwlVerm";i:7940;s:4:"Spad";i:7941;s:4:"Unap";i:7942;s:12:"CulgeDideMan";i:7943;s:8:"PoPreaPr";i:7944;s:10:"TelauUnhus";i:7945;s:9:"CalInJuba";i:7946;s:9:"MicUnfVol";i:7947;s:7:"EumeFle";i:7948;s:10:"NumiRuWide";i:7949;s:6:"TritTu";i:7950;s:5:"Sanuk";i:7951;s:7:"HazarQu";i:7952;s:8:"LeroQuUg";i:7953;s:3:"Who";i:7954;s:12:"MakefMetScul";i:7955;s:9:"FamiPluri";i:7956;s:5:"Purse";i:7957;s:9:"PhTissuUn";i:7958;s:12:"AloDemopSens";i:7959;s:9:"InteSluic";i:7960;s:4:"Mesi";i:7961;s:8:"ReTuliUn";i:7962;s:14:"CyanLycanSpenc";i:7963;s:4:"NeRo";i:7964;s:7:"AmmocCe";i:7965;s:6:"MiSika";i:7966;s:8:"LongiNoa";i:7967;s:8:"ColMiPla";i:7968;s:11:"CoincMillPn";i:7969;s:11:"MoraePrUmpx";i:7970;s:7:"PromaSh";i:7971;s:7:"TaTorre";i:7972;s:7:"PresPro";i:7973;s:7:"GayVomi";i:7974;s:7:"EncVagi";i:7975;s:3:"Geo";i:7976;s:12:"GroupOverUnh";i:7977;s:8:"CaltMeNo";i:7978;s:10:"IsochTogVi";i:7979;s:5:"Sphae";i:7980;s:6:"HeHoIn";i:7981;s:4:"Plis";i:7982;s:9:"MisjoPhar";i:7983;s:10:"AscCoUnthi";i:7984;s:12:"HemiPannaSha";i:7985;s:9:"RosiScrol";i:7986;s:8:"RepSepia";i:7987;s:9:"RaReagRev";i:7988;s:5:"SpThe";i:7989;s:8:"FilaPiez";i:7990;s:10:"GeoboHeNec";i:7991;s:7:"OverSen";i:7992;s:3:"Har";i:7993;s:4:"Torp";i:7994;s:9:"OpilUnder";i:7995;s:7:"KaSupra";i:7996;s:4:"Ikat";i:7997;s:5:"Forea";i:7998;s:6:"FoRere";i:7999;s:13:"PeridSecluTru";i:8000;s:10:"JeewhPigeo";i:8001;s:5:"DiPer";i:8002;s:8:"IndTheUn";i:8003;s:13:"ChlorHesSacri";i:8004;s:7:"UncoUnk";i:8005;s:6:"ComInc";i:8006;s:10:"HelLimPlet";i:8007;s:3:"Tau";i:8008;s:13:"HexaRepreSidd";i:8009;s:11:"LeptoReliUt";i:8010;s:3:"Din";i:8011;s:10:"ChronCuSay";i:8012;s:15:"CaphtHymenPsych";i:8013;s:4:"Saim";i:8014;s:5:"RenSh";i:8015;s:6:"AntiFo";i:8016;s:9:"RecadUnad";i:8017;s:3:"Ben";i:8018;s:5:"Forma";i:8019;s:4:"OrQu";i:8020;s:11:"AutGuesInnk";i:8021;s:10:"DacryUltZa";i:8022;s:8:"SubcoTes";i:8023;s:4:"Sapp";i:8024;s:14:"EuchPostaRipco";i:8025;s:7:"PatbRej";i:8026;s:5:"Maxil";i:8027;s:10:"HeMalaProv";i:8028;s:14:"RakexStagVomer";i:8029;s:9:"MonosPrRa";i:8030;s:4:"Oket";i:8031;s:7:"PaTrack";i:8032;s:9:"CausUnYaw";i:8033;s:9:"IntraUncl";i:8034;s:5:"Staro";i:8035;s:4:"Walk";i:8036;s:4:"Pitt";i:8037;s:10:"SappSmaTan";i:8038;s:6:"ReUnhu";i:8039;s:5:"Measl";i:8040;s:9:"UrbaWhimb";i:8041;s:7:"JaSucci";i:8042;s:10:"CoPyrTeles";i:8043;s:9:"MelilScle";i:8044;s:8:"IntPrStr";i:8045;s:11:"CatFrogSwif";i:8046;s:7:"DiscPar";i:8047;s:10:"PeRecoSpon";i:8048;s:10:"EpiOvePont";i:8049;s:14:"PrefeScatTrans";i:8050;s:7:"OutTele";i:8051;s:8:"HenKluxe";i:8052;s:14:"GametGrubrMono";i:8053;s:3:"Sho";i:8054;s:8:"EschaIso";i:8055;s:11:"EcliPropUnd";i:8056;s:6:"DePros";i:8057;s:13:"BetocCaecaInt";i:8058;s:7:"PrescPr";i:8059;s:12:"DeGeniiSkiam";i:8060;s:5:"SlTre";i:8061;s:11:"SeSpooTrich";i:8062;s:8:"PteroZor";i:8063;s:3:"Tru";i:8064;s:6:"FlSter";i:8065;s:12:"ReptiTrUndec";i:8066;s:14:"HyperProphWood";i:8067;s:10:"TimbaUnfis";i:8068;s:8:"CaSenUni";i:8069;s:8:"RathUrrh";i:8070;s:5:"ReSte";i:8071;s:8:"AutoTheg";i:8072;s:5:"Unrus";i:8073;s:10:"CorLePeaco";i:8074;s:10:"DepaMeRewh";i:8075;s:5:"HomUp";i:8076;s:7:"DeguFis";i:8077;s:10:"DreyfRefal";i:8078;s:4:"IlIn";i:8079;s:8:"OutsOvPr";i:8080;s:9:"RoteSulfa";i:8081;s:12:"CladuPolyStu";i:8082;s:5:"Susce";i:8083;s:8:"CoHexaHu";i:8084;s:12:"KnaggPsychUg";i:8085;s:5:"Pytho";i:8086;s:5:"FloTa";i:8087;s:6:"OuSple";i:8088;s:8:"CliPiWee";i:8089;s:4:"Unar";i:8090;s:10:"InvoMarPar";i:8091;s:5:"Homoo";i:8092;s:7:"SaSceni";i:8093;s:6:"FreQua";i:8094;s:13:"ArchsArtilCoc";i:8095;s:7:"MalMult";i:8096;s:5:"Scree";i:8097;s:10:"PhaPoisThy";i:8098;s:7:"GeLemTo";i:8099;s:5:"Perip";i:8100;s:9:"MartWineb";i:8101;s:5:"Trise";i:8102;s:10:"HyRatTehue";i:8103;s:9:"ExImsonUn";i:8104;s:4:"Warf";i:8105;s:11:"LarriMinWit";i:8106;s:6:"TriUps";i:8107;s:11:"MangTaTouch";i:8108;s:10:"ReddiTange";i:8109;s:7:"SwirVal";i:8110;s:11:"IliaInfiNab";i:8111;s:10:"BackFrieUn";i:8112;s:8:"ChaThrom";i:8113;s:5:"JeOve";i:8114;s:5:"Royet";i:8115;s:5:"Plian";i:8116;s:12:"ImprePeUnspi";i:8117;s:5:"Kaffi";i:8118;s:4:"Skim";i:8119;s:6:"MuRewa";i:8120;s:5:"Wicex";i:8121;s:8:"PruReSce";i:8122;s:10:"ConEpiUnli";i:8123;s:9:"MoPresaSc";i:8124;s:3:"Wel";i:8125;s:5:"PyrPy";i:8126;s:9:"CordoStTi";i:8127;s:12:"PetrSteUnsmo";i:8128;s:9:"HissOtocr";i:8129;s:8:"EuMeSlas";i:8130;s:4:"Leth";i:8131;s:5:"IrLon";i:8132;s:9:"AuliIsUin";i:8133;s:6:"GameYe";i:8134;s:9:"NoncNonsa";i:8135;s:12:"FamisPoStrew";i:8136;s:4:"Stan";i:8137;s:8:"ShiSulph";i:8138;s:10:"MyogrResUn";i:8139;s:13:"MultUncheUnco";i:8140;s:7:"KasOpUn";i:8141;s:10:"DiploPaTyl";i:8142;s:4:"Mann";i:8143;s:11:"LuffxUnbUne";i:8144;s:6:"OutsSu";i:8145;s:5:"Shape";i:8146;s:9:"ScreTripe";i:8147;s:13:"CrimeNapPreim";i:8148;s:6:"MiZest";i:8149;s:5:"Suito";i:8150;s:5:"Learx";i:8151;s:14:"CursoOracPenna";i:8152;s:6:"EnstRa";i:8153;s:9:"ChowdPref";i:8154;s:9:"ClansThre";i:8155;s:7:"BlCurub";i:8156;s:6:"SaffUn";i:8157;s:8:"OuScUndr";i:8158;s:7:"DeWista";i:8159;s:7:"AnImmMe";i:8160;s:8:"InscUnra";i:8161;s:9:"RanceUnco";i:8162;s:3:"Lob";i:8163;s:8:"HumorPoo";i:8164;s:4:"GoSu";i:8165;s:6:"TalaTe";i:8166;s:10:"ChoirPoVet";i:8167;s:12:"MinNecrNonra";i:8168;s:4:"Unes";i:8169;s:11:"ForOvePrele";i:8170;s:4:"GaPe";i:8171;s:5:"IntTh";i:8172;s:5:"Prece";i:8173;s:7:"PoikiSc";i:8174;s:13:"CloudMedSuper";i:8175;s:5:"PreQu";i:8176;s:7:"SwagSwi";i:8177;s:10:"MorOutliPr";i:8178;s:13:"NaissNuclePre";i:8179;s:4:"Chla";i:8180;s:4:"Zapt";i:8181;s:10:"VentrWeany";i:8182;s:7:"ParabSa";i:8183;s:9:"CenoFillx";i:8184;s:5:"Undul";i:8185;s:5:"Trade";i:8186;s:8:"BizeColc";i:8187;s:6:"PhPugh";i:8188;s:12:"PreSporSport";i:8189;s:8:"GyneZarz";i:8190;s:6:"CalcIp";i:8191;s:8:"UnqVakia";i:8192;s:9:"SeUnViola";i:8193;s:7:"HySnoop";i:8194;s:5:"MaiTo";i:8195;s:12:"GaviaMeliNic";i:8196;s:4:"Prae";i:8197;s:6:"HorrMo";i:8198;s:8:"TauroWag";i:8199;s:5:"Punic";i:8200;s:4:"Matr";i:8201;s:5:"Jimba";i:8202;s:2:"Ya";i:8203;s:10:"JapRemaScr";i:8204;s:12:"PrThiasUnten";i:8205;s:11:"MisdPhanPsy";i:8206;s:5:"Store";i:8207;s:6:"StTigh";i:8208;s:9:"ApChoreSl";i:8209;s:4:"Refo";i:8210;s:14:"BlowlHydraPump";i:8211;s:11:"PalaeUnpWha";i:8212;s:8:"ArBiUnco";i:8213;s:12:"ConvPrevSuet";i:8214;s:9:"HemocStag";i:8215;s:9:"TutuVario";i:8216;s:6:"HeiUns";i:8217;s:5:"InTen";i:8218;s:8:"SeroSphe";i:8219;s:4:"Tarr";i:8220;s:5:"ExKro";i:8221;s:8:"MukdPoly";i:8222;s:10:"SpliVotWas";i:8223;s:8:"RecomSca";i:8224;s:11:"ManaPneumSo";i:8225;s:3:"Cha";i:8226;s:9:"MicrOvRet";i:8227;s:8:"PreyTwad";i:8228;s:7:"MoNoRes";i:8229;s:7:"EpUnboi";i:8230;s:7:"DodgiUn";i:8231;s:8:"NovSotti";i:8232;s:8:"RenSubSu";i:8233;s:10:"JurisUncVe";i:8234;s:12:"SteatSutToxo";i:8235;s:11:"TucUncUnpro";i:8236;s:8:"EpiSciss";i:8237;s:8:"OleUnave";i:8238;s:5:"CaFor";i:8239;s:9:"PostRedSc";i:8240;s:7:"ImpeSav";i:8241;s:7:"LocSupr";i:8242;s:8:"HaddInSu";i:8243;s:13:"PolitVerWorth";i:8244;s:9:"MaxilSymp";i:8245;s:3:"Wha";i:8246;s:5:"Oroba";i:8247;s:10:"HolocPurSe";i:8248;s:12:"ChEnsmaMonia";i:8249;s:14:"SaponToxeUnven";i:8250;s:9:"CriDosPre";i:8251;s:5:"Turbo";i:8252;s:13:"FisnHyponVine";i:8253;s:3:"Sol";i:8254;s:10:"PunctSuspe";i:8255;s:12:"BedeCaninPer";i:8256;s:3:"Uro";i:8257;s:7:"StiUnsu";i:8258;s:9:"PaleShore";i:8259;s:7:"LeQuadx";i:8260;s:8:"CryExcTa";i:8261;s:8:"PaProUnp";i:8262;s:9:"PinelScol";i:8263;s:8:"CoHyLayl";i:8264;s:8:"HeOptPri";i:8265;s:8:"PaSliTry";i:8266;s:6:"HaPumi";i:8267;s:6:"SofTra";i:8268;s:4:"Redh";i:8269;s:5:"Round";i:8270;s:10:"PhyUltrVel";i:8271;s:9:"TyrtaZami";i:8272;s:12:"ProbRumeTins";i:8273;s:6:"FlooLa";i:8274;s:9:"RaroSolei";i:8275;s:11:"GorgeNavSma";i:8276;s:4:"Dicy";i:8277;s:5:"ReSpi";i:8278;s:9:"InOcPastu";i:8279;s:11:"CounMyStell";i:8280;s:3:"Sau";i:8281;s:4:"InUn";i:8282;s:11:"NonPredSpin";i:8283;s:5:"Outpr";i:8284;s:11:"ComMilRebuf";i:8285;s:10:"HyStraTell";i:8286;s:6:"PiTris";i:8287;s:6:"RoTect";i:8288;s:6:"SloSpe";i:8289;s:10:"MuttoUnpre";i:8290;s:8:"ChUnwoVe";i:8291;s:5:"Thirt";i:8292;s:9:"PredUnres";i:8293;s:12:"FreelHydroIn";i:8294;s:4:"SaUn";i:8295;s:8:"ErraSage";i:8296;s:10:"QuadrSeaYa";i:8297;s:5:"PerSo";i:8298;s:12:"AvenNonNookl";i:8299;s:4:"DaHe";i:8300;s:8:"ChiDemUk";i:8301;s:8:"MaUnWind";i:8302;s:6:"CaInWe";i:8303;s:11:"EndeOsseoUn";i:8304;s:8:"MyriPeri";i:8305;s:10:"PestpSonat";i:8306;s:4:"PhRa";i:8307;s:9:"HawxPemph";i:8308;s:6:"ReTrem";i:8309;s:9:"NonciScot";i:8310;s:10:"MicPerSpon";i:8311;s:3:"Chr";i:8312;s:9:"GuttTrico";i:8313;s:7:"HagiScr";i:8314;s:6:"PlesPo";i:8315;s:10:"MesorMinNi";i:8316;s:8:"DisPlano";i:8317;s:8:"HyaInOut";i:8318;s:6:"OmTosh";i:8319;s:6:"SuTrVa";i:8320;s:12:"CombiPyrenUn";i:8321;s:5:"PhaPo";i:8322;s:8:"ImpInInt";i:8323;s:8:"ParWandx";i:8324;s:4:"Robo";i:8325;s:9:"MulQuiSpa";i:8326;s:6:"MicOsm";i:8327;s:6:"OvZoog";i:8328;s:11:"OveRemanSel";i:8329;s:10:"BrickGuLac";i:8330;s:12:"PapaSakSensi";i:8331;s:11:"HistPondTel";i:8332;s:4:"Vert";i:8333;s:7:"OestrRe";i:8334;s:10:"MagStrTetr";i:8335;s:8:"BridChry";i:8336;s:7:"ResubUn";i:8337;s:10:"EqPolyTabl";i:8338;s:5:"Matri";i:8339;s:10:"ElectWater";i:8340;s:7:"AmnesUn";i:8341;s:7:"GeRetro";i:8342;s:10:"OstPicValu";i:8343;s:10:"HolPrSpatt";i:8344;s:7:"BrighSe";i:8345;s:11:"MusiNonUniv";i:8346;s:9:"NonSittVo";i:8347;s:4:"Rewo";i:8348;s:7:"ResoUra";i:8349;s:9:"MaPaTolfr";i:8350;s:8:"BunWicke";i:8351;s:13:"HobParasPindy";i:8352;s:8:"IllfMiOu";i:8353;s:11:"DisrTabiUnd";i:8354;s:10:"KoffxPaRua";i:8355;s:13:"GogoxSunkeTom";i:8356;s:9:"UnbeWafer";i:8357;s:9:"SecreSpUp";i:8358;s:4:"Mend";i:8359;s:11:"HallmHydrWa";i:8360;s:7:"ChInPer";i:8361;s:10:"IsoQuinSte";i:8362;s:7:"RelUnta";i:8363;s:7:"GigbaLo";i:8364;s:13:"DrumPipeVentr";i:8365;s:4:"Unil";i:8366;s:10:"PneProviSh";i:8367;s:6:"InInte";i:8368;s:8:"LiUnhuUn";i:8369;s:12:"MismaNeWrith";i:8370;s:5:"Preor";i:8371;s:8:"GeiPrUnp";i:8372;s:9:"NonsuPoly";i:8373;s:5:"Ivory";i:8374;s:9:"TaleYowlr";i:8375;s:13:"MitosNotTrisi";i:8376;s:11:"MeniNeuTatu";i:8377;s:14:"ImprMetalUncon";i:8378;s:12:"PessReaSilic";i:8379;s:12:"RooUnmatVina";i:8380;s:5:"Pedan";i:8381;s:5:"Hattx";i:8382;s:4:"Sham";i:8383;s:3:"Bum";i:8384;s:5:"Polyn";i:8385;s:8:"BaeSerfd";i:8386;s:11:"InureUnleWe";i:8387;s:5:"Outmi";i:8388;s:10:"PaleoTaVer";i:8389;s:11:"HexasLaddSt";i:8390;s:7:"ChevrNo";i:8391;s:7:"LanMeMe";i:8392;s:6:"PhoUps";i:8393;s:6:"ResWri";i:8394;s:8:"PlatTerr";i:8395;s:15:"BilgyDisedUgand";i:8396;s:4:"CrDe";i:8397;s:12:"CholeSaTommy";i:8398;s:9:"OmniSheep";i:8399;s:9:"PeaPtQuat";i:8400;s:8:"IsocNilx";i:8401;s:6:"CeHete";i:8402;s:4:"Voci";i:8403;s:6:"SamSca";i:8404;s:4:"Lapa";i:8405;s:10:"DeuSimUnfr";i:8406;s:9:"HucMarsSc";i:8407;s:11:"PerPlatSpor";i:8408;s:12:"GratuHeKaemp";i:8409;s:13:"MastiShipTemp";i:8410;s:7:"PsePycn";i:8411;s:9:"NubecPipp";i:8412;s:12:"FrencManiuUn";i:8413;s:3:"Gas";i:8414;s:9:"MesoScSub";i:8415;s:12:"FomenMerobUn";i:8416;s:8:"OverPhre";i:8417;s:7:"IcostMi";i:8418;s:10:"BioscMisPa";i:8419;s:10:"ArcExtWham";i:8420;s:9:"TickTitex";i:8421;s:9:"ForfNiSpe";i:8422;s:5:"Unbig";i:8423;s:10:"IrredLiftm";i:8424;s:12:"AugeDebbyUnr";i:8425;s:11:"BleekImmPax";i:8426;s:5:"Dowdi";i:8427;s:8:"SilThimb";i:8428;s:14:"NonrePantiStre";i:8429;s:13:"IncesLentoMar";i:8430;s:12:"CytoPonSludx";i:8431;s:7:"CoSuper";i:8432;s:4:"RoTh";i:8433;s:6:"NiSupe";i:8434;s:7:"ExpPhlo";i:8435;s:3:"Via";i:8436;s:9:"PurbYiddi";i:8437;s:10:"OrPedaRush";i:8438;s:5:"LaTec";i:8439;s:3:"Sod";i:8440;s:9:"CorvDreIm";i:8441;s:9:"NoProUngr";i:8442;s:8:"ContInIn";i:8443;s:5:"Stupe";i:8444;s:4:"Sync";i:8445;s:4:"Sloo";i:8446;s:3:"Hep";i:8447;s:7:"DesFePh";i:8448;s:4:"Toxo";i:8449;s:11:"ConjHeLater";i:8450;s:5:"Mistu";i:8451;s:8:"KnapPsSi";i:8452;s:7:"ArFacQu";i:8453;s:13:"PneuSemiWoode";i:8454;s:7:"KnNisha";i:8455;s:9:"PoQueStak";i:8456;s:13:"LegitRelicRom";i:8457;s:7:"HaIchno";i:8458;s:10:"EleutScept";i:8459;s:7:"ProSter";i:8460;s:11:"HatbSequUna";i:8461;s:4:"Micr";i:8462;s:6:"GlGlut";i:8463;s:12:"ChannMicrPre";i:8464;s:4:"Tyri";i:8465;s:4:"Supp";i:8466;s:5:"Unpre";i:8467;s:9:"JiggeSong";i:8468;s:5:"Wards";i:8469;s:5:"FeInd";i:8470;s:3:"Spa";i:8471;s:12:"BuoyDecuRedu";i:8472;s:5:"Wolfh";i:8473;s:7:"PolonRe";i:8474;s:5:"Astro";i:8475;s:14:"ClareUnlaUntom";i:8476;s:4:"Tria";i:8477;s:11:"UnjarUnUnva";i:8478;s:10:"LampMyeUnb";i:8479;s:4:"Unho";i:8480;s:11:"FraiRiXebec";i:8481;s:9:"EndoThUnr";i:8482;s:9:"FulgMaMin";i:8483;s:13:"HydrPosolProf";i:8484;s:10:"EcholSaumo";i:8485;s:13:"CopeDozexMona";i:8486;s:7:"FlePort";i:8487;s:5:"Usnin";i:8488;s:4:"PaUn";i:8489;s:6:"MourSa";i:8490;s:9:"ChiLarNon";i:8491;s:3:"Sna";i:8492;s:11:"KioPhotPrep";i:8493;s:9:"OligSubco";i:8494;s:8:"InflPiUn";i:8495;s:9:"GlaHetTre";i:8496;s:10:"ChronEucPh";i:8497;s:6:"RuiUne";i:8498;s:10:"ResumUngla";i:8499;s:4:"Corp";i:8500;s:7:"IndPape";i:8501;s:8:"PalProje";i:8502;s:4:"Sepi";i:8503;s:9:"OvariSpin";i:8504;s:10:"QuiveUncor";i:8505;s:5:"Picra";i:8506;s:14:"MythiOutbWedan";i:8507;s:6:"ShowUl";i:8508;s:11:"ParasRecRev";i:8509;s:7:"EugGall";i:8510;s:3:"Coe";i:8511;s:6:"SurrWr";i:8512;s:12:"FlabHetePsyc";i:8513;s:5:"ChDev";i:8514;s:9:"NoneUnbek";i:8515;s:12:"DiagrMesPopu";i:8516;s:8:"ShSupeUn";i:8517;s:11:"KeysMeVicto";i:8518;s:13:"ShriTirriWhos";i:8519;s:15:"PhilaPrickRutil";i:8520;s:13:"EnfesRortyThe";i:8521;s:3:"Ket";i:8522;s:12:"GreePerniPur";i:8523;s:7:"OoSorce";i:8524;s:7:"SonioUn";i:8525;s:8:"QuadSaum";i:8526;s:7:"CorCrGr";i:8527;s:11:"PistShaThou";i:8528;s:6:"DisePh";i:8529;s:8:"SatinWit";i:8530;s:7:"SchisSp";i:8531;s:4:"Upcu";i:8532;s:13:"MadeUnconWitc";i:8533;s:4:"Antu";i:8534;s:10:"SecatTaqua";i:8535;s:5:"Proco";i:8536;s:7:"UntwiWr";i:8537;s:8:"PiProRep";i:8538;s:13:"OstScrimVadim";i:8539;s:5:"Subsu";i:8540;s:10:"CruttLappa";i:8541;s:6:"ShUnli";i:8542;s:7:"EpitRes";i:8543;s:4:"Kuld";i:8544;s:4:"Stru";i:8545;s:6:"UncUne";i:8546;s:3:"Tel";i:8547;s:4:"MuTu";i:8548;s:12:"FreckRippRoc";i:8549;s:10:"UnbefVilay";i:8550;s:9:"HydrInflo";i:8551;s:9:"NotidPred";i:8552;s:12:"GoniLustfUnd";i:8553;s:12:"ImbosMaOverb";i:8554;s:7:"ShThymo";i:8555;s:6:"KaMuPl";i:8556;s:10:"PyogRecoRe";i:8557;s:10:"NestoTrUnt";i:8558;s:12:"MissMonodNon";i:8559;s:6:"UncoUn";i:8560;s:6:"UnUned";i:8561;s:3:"Nes";i:8562;s:6:"DumHem";i:8563;s:4:"Yawn";i:8564;s:5:"Medie";i:8565;s:7:"MetSubi";i:8566;s:9:"SeltSteSt";i:8567;s:8:"LivOlivi";i:8568;s:4:"LiPa";i:8569;s:4:"Sarr";i:8570;s:5:"Porit";i:8571;s:8:"NebbyRam";i:8572;s:6:"PloTim";i:8573;s:9:"MilliOrri";i:8574;s:9:"GessHomTh";i:8575;s:6:"HighLi";i:8576;s:13:"HetHypogSupra";i:8577;s:5:"Stave";i:8578;s:8:"LingTass";i:8579;s:4:"StTh";i:8580;s:6:"SeSong";i:8581;s:4:"Gyra";i:8582;s:9:"OnaxUnhos";i:8583;s:4:"Noto";i:8584;s:6:"PeVerb";i:8585;s:5:"Opera";i:8586;s:7:"SaSoUni";i:8587;s:10:"OphrPeUrea";i:8588;s:5:"InWin";i:8589;s:13:"NecesNoweQues";i:8590;s:7:"SauteSu";i:8591;s:7:"BloBrac";i:8592;s:9:"ExHebriLu";i:8593;s:7:"MpangRa";i:8594;s:12:"DemagEllipMo";i:8595;s:10:"ImprSkiWat";i:8596;s:8:"LocTende";i:8597;s:7:"PaUnadv";i:8598;s:11:"SavSlagStra";i:8599;s:8:"BlTasUnd";i:8600;s:4:"Riss";i:8601;s:5:"MoTah";i:8602;s:5:"Hypot";i:8603;s:5:"Prali";i:8604;s:11:"ExsOverSola";i:8605;s:4:"Omni";i:8606;s:10:"MacrSeSupe";i:8607;s:7:"NautWin";i:8608;s:7:"UnswWhe";i:8609;s:5:"Untoo";i:8610;s:5:"Mimmo";i:8611;s:10:"NotoRegrSu";i:8612;s:7:"MonaPse";i:8613;s:9:"ChiRetSqu";i:8614;s:11:"CatarEpiIde";i:8615;s:8:"SerUnrev";i:8616;s:7:"UnoWrit";i:8617;s:9:"MisSuTeas";i:8618;s:8:"MonWhare";i:8619;s:14:"ElzevRoughUtri";i:8620;s:5:"Uninf";i:8621;s:4:"Volu";i:8622;s:9:"ExpOdsRet";i:8623;s:10:"DiaFendPhy";i:8624;s:7:"OversPh";i:8625;s:3:"Van";i:8626;s:3:"Pic";i:8627;s:3:"Hoo";i:8628;s:7:"MetPneu";i:8629;s:11:"GranuTeleTe";i:8630;s:2:"Ep";i:8631;s:5:"Verti";i:8632;s:8:"BerdaRei";i:8633;s:11:"PolStylWasa";i:8634;s:10:"TemUnbowUn";i:8635;s:10:"HiSeldToli";i:8636;s:8:"PlaRaTre";i:8637;s:7:"SalUnro";i:8638;s:6:"LipoQu";i:8639;s:5:"Refle";i:8640;s:9:"OutloScUn";i:8641;s:7:"GrMaVat";i:8642;s:7:"ShardTr";i:8643;s:6:"PolStr";i:8644;s:9:"StacTiTub";i:8645;s:10:"GangGrooZi";i:8646;s:4:"Titi";i:8647;s:7:"LocPala";i:8648;s:4:"Skin";i:8649;s:11:"DiaphRapSpu";i:8650;s:8:"GlarMeta";i:8651;s:7:"InOffic";i:8652;s:4:"Hete";i:8653;s:7:"RefeSuc";i:8654;s:5:"MeSie";i:8655;s:4:"DaMe";i:8656;s:4:"Yard";i:8657;s:4:"Vene";i:8658;s:8:"MelaShUn";i:8659;s:9:"EjPiUnnab";i:8660;s:9:"OrnitPayn";i:8661;s:12:"CotarPhohUnd";i:8662;s:9:"HaitiUnUn";i:8663;s:8:"NealxSup";i:8664;s:4:"Tetr";i:8665;s:8:"UnlitZeu";i:8666;s:9:"GammaGuid";i:8667;s:9:"TaTraUndi";i:8668;s:10:"SizSuWahah";i:8669;s:7:"PoliPun";i:8670;s:3:"Sum";i:8671;s:13:"PenthRegrTasi";i:8672;s:9:"RetroUnat";i:8673;s:11:"ElInveNickx";i:8674;s:14:"EbionParmeSpid";i:8675;s:4:"Gurg";i:8676;s:4:"Ente";i:8677;s:9:"IntMilTer";i:8678;s:11:"HomoPiRetar";i:8679;s:8:"InspReco";i:8680;s:14:"RealSanctSepar";i:8681;s:12:"CyclOuttSlag";i:8682;s:12:"ExogNadiSupe";i:8683;s:4:"LeTe";i:8684;s:10:"BenefInter";i:8685;s:10:"HarmRotUpp";i:8686;s:8:"PseTunna";i:8687;s:10:"PreResSati";i:8688;s:6:"FloTra";i:8689;s:5:"Troph";i:8690;s:4:"InMo";i:8691;s:15:"PlaceRaciaRuntx";i:8692;s:9:"CauSrTolu";i:8693;s:12:"IncomXenopXe";i:8694;s:12:"CaeciCaPaleo";i:8695;s:4:"LoPh";i:8696;s:12:"GamIodyXerop";i:8697;s:5:"Chond";i:8698;s:5:"MeTel";i:8699;s:8:"ObediPou";i:8700;s:4:"PhVa";i:8701;s:5:"UndUr";i:8702;s:5:"Rachi";i:8703;s:11:"BrickGreeLe";i:8704;s:5:"Unreb";i:8705;s:7:"EnwiTew";i:8706;s:13:"EleonHearScha";i:8707;s:4:"PrRe";i:8708;s:8:"SchUnabr";i:8709;s:6:"CoEmMi";i:8710;s:6:"HypeSh";i:8711;s:5:"FuUna";i:8712;s:10:"RabiSanjTr";i:8713;s:13:"ExamiPredUnsh";i:8714;s:12:"InflaNaSchop";i:8715;s:13:"BoldDoliUnpla";i:8716;s:6:"PaUntw";i:8717;s:4:"Tasi";i:8718;s:3:"Cat";i:8719;s:15:"CountMerisSpott";i:8720;s:3:"Sco";i:8721;s:9:"ManPuWhau";i:8722;s:9:"BlocCapri";i:8723;s:9:"BakinLaLi";i:8724;s:9:"ScreStran";i:8725;s:5:"Thick";i:8726;s:10:"DunkPePost";i:8727;s:9:"SkippStim";i:8728;s:4:"Subr";i:8729;s:5:"CaDer";i:8730;s:5:"MarTe";i:8731;s:5:"Terna";i:8732;s:10:"TelThUnver";i:8733;s:6:"NuclPn";i:8734;s:3:"Vim";i:8735;s:4:"Teac";i:8736;s:5:"PaUno";i:8737;s:11:"CompoDiShru";i:8738;s:6:"MonUnr";i:8739;s:6:"SeSwee";i:8740;s:7:"ErgaPol";i:8741;s:11:"EmbOveStrid";i:8742;s:12:"ArsonPrRepud";i:8743;s:9:"PaliProvi";i:8744;s:9:"MopinOxyp";i:8745;s:9:"GobKentUn";i:8746;s:12:"HypeOdontSpi";i:8747;s:12:"ChiRefoUnthr";i:8748;s:7:"PePress";i:8749;s:11:"HiPeriUndep";i:8750;s:12:"LieutSeToywo";i:8751;s:4:"Gunp";i:8752;s:9:"GuSchTele";i:8753;s:9:"DrumMulti";i:8754;s:8:"InflPray";i:8755;s:9:"PrebTubVi";i:8756;s:9:"DeturEnfi";i:8757;s:7:"KishaWe";i:8758;s:12:"MicPapuThala";i:8759;s:5:"Univa";i:8760;s:6:"PruUrx";i:8761;s:5:"Meato";i:8762;s:5:"SiUro";i:8763;s:11:"PterSnarTeb";i:8764;s:9:"MiscoMySy";i:8765;s:10:"ScotcSubVe";i:8766;s:4:"Unwe";i:8767;s:6:"MasPol";i:8768;s:3:"Pra";i:8769;s:5:"SheWe";i:8770;s:12:"NandSeasoWat";i:8771;s:8:"ImpSnake";i:8772;s:9:"HaubeOver";i:8773;s:11:"NaRetroUnma";i:8774;s:7:"SlSombr";i:8775;s:4:"ExTe";i:8776;s:10:"ReducSancy";i:8777;s:6:"CrVent";i:8778;s:5:"Death";i:8779;s:5:"EmTri";i:8780;s:4:"Bric";i:8781;s:5:"Paron";i:8782;s:10:"MyzOmmiaRe";i:8783;s:14:"ChirThereWallo";i:8784;s:10:"ProceUnUnt";i:8785;s:10:"ConSchapSu";i:8786;s:7:"GymPrUn";i:8787;s:7:"PrimiTr";i:8788;s:9:"OitiTraff";i:8789;s:4:"Mini";i:8790;s:10:"RegeStoiZy";i:8791;s:15:"CategInhabResth";i:8792;s:10:"DipEnsHexo";i:8793;s:9:"PlaPsTana";i:8794;s:10:"NontrTiUnd";i:8795;s:7:"CutirMe";i:8796;s:9:"JamdSubtu";i:8797;s:7:"PredoUn";i:8798;s:11:"BenzoHyVoca";i:8799;s:7:"ChDuHus";i:8800;s:12:"TsarUntuVuls";i:8801;s:9:"PrStTrich";i:8802;s:8:"UnbudVer";i:8803;s:6:"SanaUn";i:8804;s:7:"MoScint";i:8805;s:7:"CaDaFac";i:8806;s:8:"AmbCarpo";i:8807;s:10:"KukSipinUn";i:8808;s:7:"SaUnVen";i:8809;s:13:"PentSubbUnthi";i:8810;s:5:"Unbea";i:8811;s:6:"OrgiPe";i:8812;s:7:"ReScSle";i:8813;s:4:"Whis";i:8814;s:11:"DexPalUnher";i:8815;s:4:"Vaga";i:8816;s:4:"Sins";i:8817;s:4:"PhRe";i:8818;s:7:"KatUnsw";i:8819;s:8:"PenSynov";i:8820;s:9:"QuerRxUnl";i:8821;s:3:"Rac";i:8822;s:6:"OstrUb";i:8823;s:7:"GiMoMon";i:8824;s:5:"ReUnb";i:8825;s:5:"Moral";i:8826;s:14:"PhantUrostVajr";i:8827;s:13:"NoncUnfriUnto";i:8828;s:6:"HarPin";i:8829;s:9:"PeSpaTrim";i:8830;s:10:"CollIrToby";i:8831;s:10:"StyTroUnsl";i:8832;s:6:"LuckPh";i:8833;s:5:"Resou";i:8834;s:8:"MatriPap";i:8835;s:4:"Sugg";i:8836;s:3:"Pos";i:8837;s:10:"ReSquamUnh";i:8838;s:8:"HonInves";i:8839;s:6:"ScreSu";i:8840;s:4:"Whel";i:8841;s:5:"InWag";i:8842;s:9:"MembUnfil";i:8843;s:6:"PrUnth";i:8844;s:14:"HierSubnuUnent";i:8845;s:10:"PogRetinUn";i:8846;s:8:"TidedWit";i:8847;s:9:"EphorSnob";i:8848;s:12:"KinetLopeMit";i:8849;s:10:"NoRaspaSub";i:8850;s:11:"AmaMisSarga";i:8851;s:7:"SuThUns";i:8852;s:13:"DefleThreYeme";i:8853;s:3:"Fas";i:8854;s:4:"Viha";i:8855;s:2:"Kh";i:8856;s:10:"GirJudiOxy";i:8857;s:10:"NeurNonPro";i:8858;s:9:"HydroUnco";i:8859;s:5:"Forec";i:8860;s:7:"EmbExub";i:8861;s:5:"Unpho";i:8862;s:13:"AristPreseTra";i:8863;s:8:"PaSyntUn";i:8864;s:7:"HiPledg";i:8865;s:8:"PostUnze";i:8866;s:10:"MallaUngui";i:8867;s:12:"DrOceanUncor";i:8868;s:6:"CalTur";i:8869;s:3:"Vul";i:8870;s:3:"Ult";i:8871;s:8:"RecTitul";i:8872;s:6:"DilaEa";i:8873;s:7:"OnesWhe";i:8874;s:10:"PlSeroTolu";i:8875;s:7:"RecoSoc";i:8876;s:4:"Leuc";i:8877;s:10:"CaninEnPro";i:8878;s:11:"DisStrTrans";i:8879;s:4:"Pani";i:8880;s:6:"PrSten";i:8881;s:4:"Misc";i:8882;s:6:"NoSemi";i:8883;s:13:"GannPodoSlipo";i:8884;s:5:"MyrUn";i:8885;s:5:"Helio";i:8886;s:10:"MonVerniWa";i:8887;s:4:"Gyno";i:8888;s:7:"InnWint";i:8889;s:11:"CeroShoUnav";i:8890;s:8:"PushSiUn";i:8891;s:8:"DioNeSup";i:8892;s:4:"AuTh";i:8893;s:7:"FeHyPyr";i:8894;s:9:"MePamiUnl";i:8895;s:7:"MePikey";i:8896;s:5:"MacUn";i:8897;s:9:"PoleUnexc";i:8898;s:9:"GeocViper";i:8899;s:9:"JointUnUr";i:8900;s:9:"BuCheTigx";i:8901;s:5:"Ochra";i:8902;s:9:"NjaNoStro";i:8903;s:12:"CnemiGrUnreq";i:8904;s:9:"MellRamSc";i:8905;s:11:"InPistoSubr";i:8906;s:6:"ElamKe";i:8907;s:9:"CounDiagr";i:8908;s:8:"CorRevil";i:8909;s:11:"PurSuoUrini";i:8910;s:3:"Sma";i:8911;s:8:"GralHudd";i:8912;s:10:"BurgaGossi";i:8913;s:8:"FimbIdeo";i:8914;s:5:"Viril";i:8915;s:13:"JuverPredeRep";i:8916;s:7:"PhysiUn";i:8917;s:7:"NiTrans";i:8918;s:11:"IsPrickTrol";i:8919;s:7:"GoOment";i:8920;s:9:"TectUnfus";i:8921;s:10:"OvProtUnen";i:8922;s:5:"LeTre";i:8923;s:4:"Vaul";i:8924;s:8:"PhenRecu";i:8925;s:7:"SaTwelv";i:8926;s:5:"Trypt";i:8927;s:5:"Schiz";i:8928;s:6:"FeNonc";i:8929;s:12:"PolarScirSer";i:8930;s:8:"PraseTin";i:8931;s:10:"OfTattlWis";i:8932;s:4:"ShUn";i:8933;s:11:"GiNemeVicar";i:8934;s:7:"FotUnab";i:8935;s:6:"HeMiOr";i:8936;s:13:"FanxLegatUnid";i:8937;s:3:"Med";i:8938;s:14:"EarnScoroSlaye";i:8939;s:7:"GujrSci";i:8940;s:5:"VeViv";i:8941;s:5:"MeaUn";i:8942;s:6:"PtSpor";i:8943;s:6:"MoisSp";i:8944;s:10:"MopinOtViv";i:8945;s:12:"ExognPanoSpo";i:8946;s:7:"HedSuff";i:8947;s:7:"FifteRe";i:8948;s:4:"Sack";i:8949;s:9:"PiRasSerr";i:8950;s:3:"Vam";i:8951;s:11:"EnFlinPertu";i:8952;s:5:"Vexed";i:8953;s:5:"HenSu";i:8954;s:5:"Picad";i:8955;s:10:"CuirReTusc";i:8956;s:11:"PinPreaSeco";i:8957;s:14:"LaddePapaiUrey";i:8958;s:11:"GirlMoPhoen";i:8959;s:5:"Subli";i:8960;s:9:"UnjaVerti";i:8961;s:9:"ReUnUndes";i:8962;s:4:"PsTe";i:8963;s:7:"ParaWhi";i:8964;s:9:"LetxRetou";i:8965;s:9:"PinfeRass";i:8966;s:12:"RacReadStyle";i:8967;s:7:"TriclUn";i:8968;s:7:"SauSpre";i:8969;s:10:"IncoRhyUnc";i:8970;s:9:"HeftiUtra";i:8971;s:10:"ForSeasoUn";i:8972;s:15:"KorunPlateSynus";i:8973;s:10:"ForHappePo";i:8974;s:9:"SeSquZoon";i:8975;s:12:"HypocOriPent";i:8976;s:11:"SheTanUltra";i:8977;s:8:"OctaUpca";i:8978;s:13:"MuzhiPleniTip";i:8979;s:10:"MiscPhoeSa";i:8980;s:4:"Quan";i:8981;s:9:"NariTricl";i:8982;s:6:"InsTro";i:8983;s:13:"CantoPontoSub";i:8984;s:12:"MirReciSavan";i:8985;s:9:"LeNoVivif";i:8986;s:4:"Paid";i:8987;s:9:"OversTagu";i:8988;s:7:"MyVaunt";i:8989;s:8:"OversStr";i:8990;s:8:"ThyUndup";i:8991;s:10:"LaOuthoRou";i:8992;s:4:"Lepi";i:8993;s:12:"NoUnkinUnmut";i:8994;s:5:"Sermo";i:8995;s:5:"StiSt";i:8996;s:4:"Neos";i:8997;s:8:"IsothNon";i:8998;s:8:"SluiWind";i:8999;s:7:"FelicPa";i:9000;s:7:"UndisUn";i:9001;s:8:"NeoSupra";i:9002;s:10:"JaLontaTus";i:9003;s:5:"Outpa";i:9004;s:12:"HeteSubSupra";i:9005;s:5:"Nephe";i:9006;s:9:"LeProSqua";i:9007;s:10:"CountLofti";i:9008;s:12:"DiagSpeciTiw";i:9009;s:4:"Wien";i:9010;s:8:"PhoniSup";i:9011;s:10:"HardePolyo";i:9012;s:7:"RaRecha";i:9013;s:11:"CydGalInobs";i:9014;s:3:"Yah";i:9015;s:8:"SiStTorn";i:9016;s:4:"Anab";i:9017;s:7:"HolaWen";i:9018;s:7:"CaGusSt";i:9019;s:8:"BinPhoPl";i:9020;s:12:"TracUnaffUnc";i:9021;s:12:"GuHeremOvige";i:9022;s:12:"StimyUnVirus";i:9023;s:8:"MultUnfa";i:9024;s:8:"ChoComCo";i:9025;s:8:"ErotPres";i:9026;s:6:"HypSul";i:9027;s:8:"PrSwUnen";i:9028;s:9:"PeThamViv";i:9029;s:9:"ThyrUnder";i:9030;s:10:"PorphPrRiv";i:9031;s:10:"SchooSubju";i:9032;s:5:"SnaUn";i:9033;s:10:"LamasOvers";i:9034;s:7:"HyUnwis";i:9035;s:3:"Rho";i:9036;s:12:"FrigPawnSani";i:9037;s:9:"SnUnUntar";i:9038;s:11:"BundEnigWhe";i:9039;s:9:"PrusRebUn";i:9040;s:10:"CuOverPutt";i:9041;s:6:"EphyNo";i:9042;s:4:"Stap";i:9043;s:5:"PasPo";i:9044;s:5:"EnwGa";i:9045;s:5:"ExtZy";i:9046;s:7:"RoofwSm";i:9047;s:6:"InfMal";i:9048;s:9:"BriDeVale";i:9049;s:7:"PhasSna";i:9050;s:7:"CaExSpr";i:9051;s:8:"ObiRiSla";i:9052;s:8:"ThuUnpre";i:9053;s:6:"PleUri";i:9054;s:7:"FoSemei";i:9055;s:5:"ImpPh";i:9056;s:13:"RootShemuSpec";i:9057;s:5:"KoSem";i:9058;s:11:"MetaNaphPer";i:9059;s:10:"PolReTrans";i:9060;s:7:"MitPres";i:9061;s:7:"NonspOn";i:9062;s:12:"PancPoinUnre";i:9063;s:11:"GerLactTrin";i:9064;s:4:"ObQu";i:9065;s:5:"Saliv";i:9066;s:4:"Symb";i:9067;s:7:"LopSlee";i:9068;s:8:"PropTaip";i:9069;s:12:"LacteMediSwa";i:9070;s:13:"PeacPreteStyl";i:9071;s:6:"RaWorr";i:9072;s:10:"InspiSacro";i:9073;s:11:"CouFactoSmo";i:9074;s:7:"CoelFat";i:9075;s:8:"StagnSym";i:9076;s:5:"SchTr";i:9077;s:4:"Myth";i:9078;s:3:"Pap";i:9079;s:11:"ReSubcThumb";i:9080;s:4:"InWa";i:9081;s:4:"Coen";i:9082;s:6:"RecThy";i:9083;s:10:"KumbiPhPos";i:9084;s:10:"StradVedan";i:9085;s:10:"LiturSubTi";i:9086;s:12:"PediSubaSupp";i:9087;s:3:"Eff";i:9088;s:14:"InterNeogPreab";i:9089;s:6:"CoPrUn";i:9090;s:8:"InPacTro";i:9091;s:5:"AlUnd";i:9092;s:6:"IsoPen";i:9093;s:6:"PyWave";i:9094;s:9:"OculPithi";i:9095;s:9:"RecSithUr";i:9096;s:6:"ReTant";i:9097;s:7:"TheraTr";i:9098;s:9:"ReRespeWh";i:9099;s:6:"DvaTri";i:9100;s:7:"MiVolca";i:9101;s:11:"PaniPelvUne";i:9102;s:12:"CoutDonSpoon";i:9103;s:4:"DaIr";i:9104;s:12:"OvervScUnder";i:9105;s:7:"OuPolys";i:9106;s:13:"GratiPhyTitan";i:9107;s:12:"ScStaliTipma";i:9108;s:11:"RoVeheVelif";i:9109;s:12:"NiccoPostWas";i:9110;s:7:"MendiPr";i:9111;s:9:"MareUnpVe";i:9112;s:6:"OliSal";i:9113;s:6:"AmpOct";i:9114;s:6:"HeToad";i:9115;s:10:"NapOutpWyc";i:9116;s:10:"CaMerchSup";i:9117;s:5:"Telod";i:9118;s:3:"Cyc";i:9119;s:8:"PraepVol";i:9120;s:4:"Syre";i:9121;s:3:"Ble";i:9122;s:6:"FlSubj";i:9123;s:13:"DelMaledUpait";i:9124;s:7:"PopinUn";i:9125;s:9:"DemiEntSe";i:9126;s:12:"FiltRamaRheo";i:9127;s:9:"RacUntWit";i:9128;s:2:"Pf";i:9129;s:5:"FloPi";i:9130;s:14:"ProinSalaWinge";i:9131;s:11:"GodSmashUnl";i:9132;s:14:"MartMicroNeuro";i:9133;s:6:"PaikUn";i:9134;s:4:"ErUs";i:9135;s:6:"UnUnch";i:9136;s:7:"ConUred";i:9137;s:7:"TaysUng";i:9138;s:7:"LacMyxa";i:9139;s:10:"GouSerXylo";i:9140;s:11:"DeIpomeUnre";i:9141;s:11:"HomoJuTatsa";i:9142;s:8:"TetraUnd";i:9143;s:8:"BragPici";i:9144;s:13:"CompDissQuart";i:9145;s:10:"FuNonvStra";i:9146;s:5:"RaRid";i:9147;s:9:"RisTheUnd";i:9148;s:11:"DendrHySand";i:9149;s:8:"ChrJuWor";i:9150;s:10:"CalDemicSn";i:9151;s:5:"UnaUn";i:9152;s:8:"OppPolTu";i:9153;s:4:"PrTr";i:9154;s:8:"NoncoWor";i:9155;s:7:"BookHyp";i:9156;s:12:"BakesCrEpaul";i:9157;s:5:"Tarum";i:9158;s:5:"Helli";i:9159;s:7:"ImpSpon";i:9160;s:5:"Unpri";i:9161;s:13:"OversPasVerst";i:9162;s:7:"CaPaUnr";i:9163;s:10:"LuMonNeckm";i:9164;s:9:"InSerdaSt";i:9165;s:11:"SemicSucVej";i:9166;s:5:"Portu";i:9167;s:5:"Whiti";i:9168;s:8:"PyopSann";i:9169;s:4:"GyMa";i:9170;s:13:"InduInterMega";i:9171;s:5:"Mykis";i:9172;s:4:"Phot";i:9173;s:12:"InamOssuaSca";i:9174;s:12:"SuTweenZoeal";i:9175;s:5:"SqWor";i:9176;s:8:"RepTarge";i:9177;s:7:"OpUntri";i:9178;s:8:"QueeStSw";i:9179;s:8:"DiopThul";i:9180;s:7:"MyitStr";i:9181;s:8:"SemiTrap";i:9182;s:9:"SluStaSty";i:9183;s:5:"Neorn";i:9184;s:5:"Socia";i:9185;s:6:"LameUn";i:9186;s:5:"Weigh";i:9187;s:5:"ByMor";i:9188;s:9:"MinceSaun";i:9189;s:11:"OvUnceUnper";i:9190;s:11:"ForecHaemPh";i:9191;s:10:"IndusLoSpi";i:9192;s:5:"PanSc";i:9193;s:5:"Vomit";i:9194;s:9:"MazucPoly";i:9195;s:8:"JeProVil";i:9196;s:5:"Swith";i:9197;s:4:"Wave";i:9198;s:9:"OrgaTramf";i:9199;s:12:"InsePrelSuwa";i:9200;s:8:"LoggiOve";i:9201;s:13:"PetPleurSolpu";i:9202;s:9:"SclerTrif";i:9203;s:4:"Wrea";i:9204;s:4:"Dysc";i:9205;s:13:"LapidNonThack";i:9206;s:10:"CumKinsmSh";i:9207;s:8:"GaIdeKas";i:9208;s:12:"BrokaGuiltSy";i:9209;s:11:"BrothFaLoca";i:9210;s:10:"LabPlUncon";i:9211;s:7:"EquimMa";i:9212;s:11:"DiHomoPinak";i:9213;s:5:"Holop";i:9214;s:7:"OliOxyt";i:9215;s:6:"GoWenr";i:9216;s:11:"BaCompTerna";i:9217;s:6:"LymMyt";i:9218;s:8:"CountMon";i:9219;s:9:"ShramTack";i:9220;s:10:"PreScToher";i:9221;s:4:"Wool";i:9222;s:6:"HaPeSo";i:9223;s:10:"EnHypeMarq";i:9224;s:9:"NonPaResu";i:9225;s:10:"SupThyUntu";i:9226;s:9:"FetteFoPa";i:9227;s:12:"MayanThrUnte";i:9228;s:7:"PeerlUn";i:9229;s:10:"ChrysFruTi";i:9230;s:8:"OverbVej";i:9231;s:6:"CirCri";i:9232;s:10:"PuinaTabet";i:9233;s:5:"Pulkx";i:9234;s:7:"UnpreUn";i:9235;s:4:"JaRa";i:9236;s:4:"Disa";i:9237;s:11:"ItinOculoOp";i:9238;s:8:"SophTeet";i:9239;s:4:"Text";i:9240;s:12:"PrehPunnRivu";i:9241;s:11:"ImpSupSyner";i:9242;s:10:"DiscoKensp";i:9243;s:4:"Nond";i:9244;s:6:"MariSe";i:9245;s:7:"BalafHa";i:9246;s:8:"PoPreTea";i:9247;s:8:"LuxNapSc";i:9248;s:5:"Bookk";i:9249;s:5:"AspVa";i:9250;s:10:"MonoNoSkew";i:9251;s:11:"BuDirgUnenv";i:9252;s:12:"LumbTrochVir";i:9253;s:5:"Escha";i:9254;s:7:"NarPeti";i:9255;s:9:"ThorUnmas";i:9256;s:13:"CollFarleUnqu";i:9257;s:8:"FerroPol";i:9258;s:6:"ReimSa";i:9259;s:5:"Robus";i:9260;s:10:"DorSpSpong";i:9261;s:9:"SubdSuper";i:9262;s:7:"PhSalic";i:9263;s:9:"PaulRecor";i:9264;s:7:"IliacUn";i:9265;s:8:"ExPeSupe";i:9266;s:10:"ForehIntRo";i:9267;s:12:"CoHeterNoset";i:9268;s:9:"ScumlStan";i:9269;s:5:"Bulbo";i:9270;s:9:"PateQuVis";i:9271;s:11:"FaradGhoMes";i:9272;s:12:"MurraNidOste";i:9273;s:10:"NeyanPinke";i:9274;s:10:"BewilDoOve";i:9275;s:5:"Prefe";i:9276;s:10:"GasManaPhr";i:9277;s:7:"DiGaPho";i:9278;s:5:"Saltw";i:9279;s:12:"HypeLaiLeuca";i:9280;s:13:"CenEgomiReocc";i:9281;s:8:"HeOmiRet";i:9282;s:14:"CottoGaumPrima";i:9283;s:4:"Flec";i:9284;s:9:"PowelToUn";i:9285;s:3:"Net";i:9286;s:4:"MoPi";i:9287;s:7:"DidHowa";i:9288;s:4:"View";i:9289;s:7:"HadLuUn";i:9290;s:6:"ConvIr";i:9291;s:4:"Xyri";i:9292;s:5:"MalPe";i:9293;s:11:"ScSperTetra";i:9294;s:7:"PreSigh";i:9295;s:4:"Suan";i:9296;s:6:"EquSta";i:9297;s:11:"CoptMitSubc";i:9298;s:10:"EuhModifRo";i:9299;s:9:"RepinStab";i:9300;s:9:"SpleUnipo";i:9301;s:5:"PhoPh";i:9302;s:8:"SlTriWha";i:9303;s:4:"HoSp";i:9304;s:5:"Bystr";i:9305;s:14:"NonsuOxycScorb";i:9306;s:11:"LympSkaffWa";i:9307;s:12:"MonosMudProt";i:9308;s:10:"KinMenViro";i:9309;s:3:"Enz";i:9310;s:8:"SupUnfis";i:9311;s:10:"MorpUnUret";i:9312;s:12:"OveRevisUnre";i:9313;s:9:"NoPauldRe";i:9314;s:5:"ObOld";i:9315;s:12:"FlyflPrUphol";i:9316;s:9:"PetrTouri";i:9317;s:5:"Vesti";i:9318;s:4:"Xiph";i:9319;s:11:"FlouSuzThia";i:9320;s:5:"Tzapo";i:9321;s:9:"PercyWate";i:9322;s:7:"PiSeSpa";i:9323;s:9:"BlizzSupe";i:9324;s:10:"MyopProPse";i:9325;s:9:"TotUnUnre";i:9326;s:8:"LepSubTh";i:9327;s:5:"NonRe";i:9328;s:9:"SatScSpon";i:9329;s:5:"FlVir";i:9330;s:6:"GoIgPu";i:9331;s:10:"PuiSuUnive";i:9332;s:5:"DyMye";i:9333;s:9:"CarHuKhok";i:9334;s:7:"OxoniWe";i:9335;s:7:"PaPerXa";i:9336;s:12:"AteloChPheno";i:9337;s:4:"Jagr";i:9338;s:12:"NewTheaUndro";i:9339;s:7:"DisInfe";i:9340;s:6:"HemMas";i:9341;s:6:"KhOcel";i:9342;s:4:"TaTw";i:9343;s:12:"RedawStUngro";i:9344;s:14:"BuffwImmeOctog";i:9345;s:12:"PardoPhReste";i:9346;s:9:"OrnitSynd";i:9347;s:5:"Suspi";i:9348;s:9:"PancaProv";i:9349;s:6:"PhylVi";i:9350;s:4:"HoIn";i:9351;s:8:"CheNonbe";i:9352;s:8:"MicPraUn";i:9353;s:6:"SuUnes";i:9354;s:5:"StiTy";i:9355;s:7:"SlUnska";i:9356;s:4:"VeWh";i:9357;s:6:"LupiPr";i:9358;s:10:"KaRufVoidl";i:9359;s:7:"RaRhina";i:9360;s:8:"FungSubm";i:9361;s:10:"PatmiPerdu";i:9362;s:3:"Tog";i:9363;s:12:"CanCoalSlips";i:9364;s:10:"DrDubioTri";i:9365;s:3:"Spu";i:9366;s:5:"There";i:9367;s:10:"HomeLecWoo";i:9368;s:5:"BloFo";i:9369;s:7:"ParsPho";i:9370;s:13:"BlizCeroQuitr";i:9371;s:4:"Surr";i:9372;s:10:"QuakReorSu";i:9373;s:13:"DressRhySteno";i:9374;s:5:"OveTo";i:9375;s:5:"PnPol";i:9376;s:10:"MicroThecl";i:9377;s:11:"EchelRuScan";i:9378;s:7:"FluorPr";i:9379;s:4:"Some";i:9380;s:4:"Rhyn";i:9381;s:6:"ChEuro";i:9382;s:13:"PrenuRoariTri";i:9383;s:8:"GuiPosse";i:9384;s:10:"EnsInlySer";i:9385;s:9:"EffUnUnsp";i:9386;s:10:"DoHaluTang";i:9387;s:12:"LeNomadRootw";i:9388;s:10:"HorSincStr";i:9389;s:8:"IntKePre";i:9390;s:10:"CastrFubLo";i:9391;s:5:"Theur";i:9392;s:10:"LubOmeReto";i:9393;s:11:"PreSubopUss";i:9394;s:7:"OrOtWai";i:9395;s:7:"GoverIn";i:9396;s:6:"DrHowk";i:9397;s:6:"ExigPo";i:9398;s:5:"Teler";i:9399;s:9:"MarSpUnbr";i:9400;s:5:"Unrec";i:9401;s:6:"MuNeot";i:9402;s:8:"LeSeaSom";i:9403;s:6:"GaJaco";i:9404;s:6:"CoDisp";i:9405;s:6:"OceTub";i:9406;s:7:"StomUnl";i:9407;s:4:"Stea";i:9408;s:8:"PamShore";i:9409;s:8:"PolysSof";i:9410;s:9:"OpporPres";i:9411;s:5:"ConUn";i:9412;s:4:"Imbo";i:9413;s:11:"PoliStTelev";i:9414;s:11:"ChrysHemiRa";i:9415;s:9:"DivPopPse";i:9416;s:6:"PhUnid";i:9417;s:12:"MiniSomThlas";i:9418;s:5:"ShaSo";i:9419;s:8:"SataTryp";i:9420;s:5:"Visce";i:9421;s:4:"Decl";i:9422;s:6:"DiRoga";i:9423;s:5:"Leafl";i:9424;s:8:"BalStagn";i:9425;s:6:"PrefSc";i:9426;s:6:"LithUm";i:9427;s:8:"IntTruss";i:9428;s:7:"ChlamEx";i:9429;s:5:"Shedh";i:9430;s:8:"CoelOvan";i:9431;s:8:"ProUnder";i:9432;s:8:"EmbryUnk";i:9433;s:4:"Pedo";i:9434;s:7:"MurksWi";i:9435;s:9:"PhiliShSl";i:9436;s:5:"SupTa";i:9437;s:10:"DropoHaLou";i:9438;s:8:"BrChylUn";i:9439;s:10:"PrTetUnpre";i:9440;s:8:"PreScowb";i:9441;s:13:"LimiTortZygon";i:9442;s:9:"HansSpiWa";i:9443;s:7:"DiNonUl";i:9444;s:7:"MalUnco";i:9445;s:9:"IncoStave";i:9446;s:4:"Muni";i:9447;s:10:"PsychTurbi";i:9448;s:5:"Nonig";i:9449;s:9:"CorParPos";i:9450;s:7:"GrUrrho";i:9451;s:5:"Priva";i:9452;s:3:"Gue";i:9453;s:5:"TreUn";i:9454;s:8:"GirnyNom";i:9455;s:9:"GeInPavag";i:9456;s:5:"Poeta";i:9457;s:7:"PrerUro";i:9458;s:8:"PalSacch";i:9459;s:9:"CutliSesq";i:9460;s:4:"Sine";i:9461;s:8:"EasPedun";i:9462;s:11:"DotaNonpSic";i:9463;s:13:"CotyStudiTher";i:9464;s:7:"InTripu";i:9465;s:5:"SteUr";i:9466;s:8:"ConvSeab";i:9467;s:3:"Yor";i:9468;s:4:"Prov";i:9469;s:5:"ChiHe";i:9470;s:7:"NePaPre";i:9471;s:5:"Seque";i:9472;s:10:"EmeHaUpswa";i:9473;s:9:"SorceUnde";i:9474;s:11:"StrTacklUnw";i:9475;s:5:"Johan";i:9476;s:7:"PaSpira";i:9477;s:11:"UncaUncoUnd";i:9478;s:5:"CoGor";i:9479;s:8:"BarrMaWi";i:9480;s:4:"TuXy";i:9481;s:6:"MinPro";i:9482;s:6:"MetUnm";i:9483;s:5:"PrWei";i:9484;s:14:"TatoUnbedWovex";i:9485;s:15:"HuronOpeidPetas";i:9486;s:8:"ThesTool";i:9487;s:8:"MalUnbro";i:9488;s:4:"Tars";i:9489;s:7:"DiGrain";i:9490;s:9:"OgOvRiver";i:9491;s:10:"NonclVexer";i:9492;s:4:"Morg";i:9493;s:10:"BushiParTo";i:9494;s:5:"CanWh";i:9495;s:14:"KerriLampMetap";i:9496;s:11:"MayhaUnpWil";i:9497;s:8:"CtenoNin";i:9498;s:10:"CoDisepSta";i:9499;s:5:"Vitri";i:9500;s:5:"Lecit";i:9501;s:12:"HaqPatriSulp";i:9502;s:5:"Loyal";i:9503;s:12:"KeraRuniTene";i:9504;s:3:"Sne";i:9505;s:13:"HemaShadZeall";i:9506;s:5:"Undif";i:9507;s:8:"DisfrSqu";i:9508;s:6:"SeTrip";i:9509;s:14:"EumitNetbrSche";i:9510;s:7:"MoWentl";i:9511;s:11:"OveProfUnme";i:9512;s:9:"DetesMyZi";i:9513;s:4:"Napa";i:9514;s:10:"DeprMetUnt";i:9515;s:12:"InduParUnrec";i:9516;s:10:"PeachUnthe";i:9517;s:5:"PssTu";i:9518;s:5:"DoTom";i:9519;s:5:"Gavia";i:9520;s:8:"DifDumEp";i:9521;s:9:"NonwaShor";i:9522;s:9:"NeStaUnde";i:9523;s:6:"TestUn";i:9524;s:8:"DemZonoc";i:9525;s:6:"InmUnl";i:9526;s:8:"RedUrino";i:9527;s:9:"StenUsurp";i:9528;s:4:"TaZi";i:9529;s:4:"Phys";i:9530;s:12:"HemoIxoSulph";i:9531;s:6:"HoonSh";i:9532;s:8:"RendeVet";i:9533;s:7:"ScrStun";i:9534;s:6:"NonPat";i:9535;s:5:"Rundi";i:9536;s:9:"GyOmTankm";i:9537;s:5:"Ravel";i:9538;s:4:"MeSe";i:9539;s:10:"LiSeymShel";i:9540;s:14:"CeltEnsnaJesti";i:9541;s:3:"Wal";i:9542;s:14:"InwrTressUnsap";i:9543;s:5:"Remen";i:9544;s:8:"DaUndWit";i:9545;s:8:"PhalaRec";i:9546;s:4:"Vicx";i:9547;s:3:"Pel";i:9548;s:4:"MoPo";i:9549;s:5:"Profr";i:9550;s:8:"JustiRed";i:9551;s:5:"IdThr";i:9552;s:5:"ExMet";i:9553;s:5:"Gleno";i:9554;s:13:"IndeParaTerte";i:9555;s:4:"Uniq";i:9556;s:15:"FilipSourhUnhur";i:9557;s:12:"EuhyoRopeUnr";i:9558;s:12:"FervScapuTec";i:9559;s:7:"IrrKisl";i:9560;s:6:"DefNon";i:9561;s:7:"OmniShe";i:9562;s:6:"OidSup";i:9563;s:10:"MetanSpWin";i:9564;s:5:"Torso";i:9565;s:7:"PrRoShi";i:9566;s:9:"OutpRicke";i:9567;s:7:"InWarmu";i:9568;s:9:"EpaPosPro";i:9569;s:11:"CroInviOvof";i:9570;s:5:"DemRa";i:9571;s:5:"Sacka";i:9572;s:4:"SiSy";i:9573;s:4:"Movi";i:9574;s:5:"Neore";i:9575;s:6:"AmWatc";i:9576;s:6:"ScleSm";i:9577;s:11:"BipaFreWalk";i:9578;s:5:"ArtSh";i:9579;s:8:"PaPikRoo";i:9580;s:13:"ImporTodeVari";i:9581;s:5:"Upste";i:9582;s:10:"MesVolWugg";i:9583;s:7:"CharmPa";i:9584;s:12:"PennWashwWur";i:9585;s:7:"UnWeath";i:9586;s:4:"TrUr";i:9587;s:9:"NervResti";i:9588;s:7:"CruSeSp";i:9589;s:7:"NonnaTe";i:9590;s:5:"Uncal";i:9591;s:11:"HyKatciUnin";i:9592;s:5:"Katun";i:9593;s:11:"ThemsUnUnde";i:9594;s:5:"UndVa";i:9595;s:14:"GapyxIliaMesen";i:9596;s:5:"StUnj";i:9597;s:4:"TiUn";i:9598;s:12:"CreatDecUnfa";i:9599;s:8:"DrumxIrr";i:9600;s:12:"HawPennSceno";i:9601;s:7:"InLitPr";i:9602;s:11:"MisbRumpxTh";i:9603;s:12:"ForesIaJamni";i:9604;s:6:"QuaTal";i:9605;s:13:"HenhPerrUnsto";i:9606;s:7:"ShStere";i:9607;s:7:"SterVan";i:9608;s:10:"NeotPlSwar";i:9609;s:5:"UnWac";i:9610;s:10:"BrFrostInd";i:9611;s:8:"BarbNoNu";i:9612;s:9:"FleKnoPho";i:9613;s:8:"MayPaSub";i:9614;s:8:"ScrawUns";i:9615;s:6:"SeThev";i:9616;s:11:"FiresFoWood";i:9617;s:10:"OvigePaSub";i:9618;s:13:"HolidIntelLuc";i:9619;s:13:"MonotOximaSen";i:9620;s:14:"GangSawhoWoman";i:9621;s:10:"HoImpTepeh";i:9622;s:5:"ImpMe";i:9623;s:8:"LuvaSpee";i:9624;s:9:"DeFolGoye";i:9625;s:12:"EboniMoXiphi";i:9626;s:5:"Milts";i:9627;s:5:"QuSte";i:9628;s:10:"QuisShepSu";i:9629;s:7:"ScatoSp";i:9630;s:9:"RadiSigge";i:9631;s:9:"LollRampa";i:9632;s:10:"JuluRehTri";i:9633;s:8:"CeliObPa";i:9634;s:3:"Scl";i:9635;s:6:"PhoPro";i:9636;s:7:"IrUnWhi";i:9637;s:4:"Whee";i:9638;s:12:"HepOutgrPost";i:9639;s:5:"TacWa";i:9640;s:9:"DulaPleas";i:9641;s:10:"PrPunkeTet";i:9642;s:5:"Presu";i:9643;s:7:"ReSepte";i:9644;s:11:"MicMorphRet";i:9645;s:6:"IcTurn";i:9646;s:7:"CoSlumb";i:9647;s:3:"Kho";i:9648;s:9:"MajPosSti";i:9649;s:6:"OveTyr";i:9650;s:9:"JuraUnali";i:9651;s:10:"HeterUpbur";i:9652;s:4:"Invo";i:9653;s:7:"ChaRuma";i:9654;s:5:"Otoco";i:9655;s:10:"FoHouUnrab";i:9656;s:7:"FumaRom";i:9657;s:10:"BriOnaxUnj";i:9658;s:7:"QuSecUn";i:9659;s:11:"IodaNaumUnc";i:9660;s:9:"PoRiTonsu";i:9661;s:9:"BridHespe";i:9662;s:7:"HeliSup";i:9663;s:11:"LefPoroReso";i:9664;s:5:"NinUn";i:9665;s:14:"LitasPrebPresb";i:9666;s:6:"SaUnUn";i:9667;s:10:"GrHidPsych";i:9668;s:9:"PreceRaRo";i:9669;s:13:"OverPittSadrx";i:9670;s:5:"Ignif";i:9671;s:8:"LighUnsa";i:9672;s:9:"OvUnYeast";i:9673;s:7:"DumFiMi";i:9674;s:4:"Scou";i:9675;s:7:"SexteWr";i:9676;s:5:"HieNo";i:9677;s:6:"CaMala";i:9678;s:4:"StUn";i:9679;s:6:"GrotUn";i:9680;s:5:"InSpi";i:9681;s:10:"PolycShama";i:9682;s:12:"ConEncepRest";i:9683;s:5:"PlaSm";i:9684;s:5:"Blatt";i:9685;s:14:"BilipCensuCont";i:9686;s:7:"DiscOve";i:9687;s:8:"InsLeaUn";i:9688;s:2:"Id";i:9689;s:6:"ShSqui";i:9690;s:10:"StatuTaxed";i:9691;s:7:"StUnale";i:9692;s:7:"PyVampi";i:9693;s:12:"PsyTranqUnco";i:9694;s:4:"Mise";i:9695;s:4:"Wayw";i:9696;s:7:"FleLoSp";i:9697;s:10:"InLixOvert";i:9698;s:9:"IsfahOrPu";i:9699;s:11:"BeniCacoUlt";i:9700;s:8:"FleTarra";i:9701;s:4:"DiOp";i:9702;s:4:"HyPo";i:9703;s:13:"NaumaOveRolle";i:9704;s:10:"TeartUncon";i:9705;s:5:"CirDa";i:9706;s:13:"ImploNondTurb";i:9707;s:10:"DyaSexTric";i:9708;s:10:"GaImpWeapo";i:9709;s:4:"Myas";i:9710;s:11:"OpSpadVolse";i:9711;s:5:"Olent";i:9712;s:11:"BiopLaevoSn";i:9713;s:8:"TagalWai";i:9714;s:8:"HeOxyVal";i:9715;s:4:"Infr";i:9716;s:9:"MicrUmbel";i:9717;s:9:"FaNeedlPe";i:9718;s:11:"LysisPseuUn";i:9719;s:10:"CadChroSib";i:9720;s:12:"GooSyncaTuft";i:9721;s:10:"QuaShodeUn";i:9722;s:5:"OveSt";i:9723;s:4:"Perl";i:9724;s:4:"HuPo";i:9725;s:13:"IntoParaSedim";i:9726;s:5:"Carce";i:9727;s:9:"KlMegaPyr";i:9728;s:12:"OrthSeeaWind";i:9729;s:9:"InsulRhin";i:9730;s:6:"MicRis";i:9731;s:15:"IsaacMediaUnmed";i:9732;s:8:"DeStSwim";i:9733;s:14:"KadayPhospPrea";i:9734;s:10:"MesaPrProb";i:9735;s:11:"PediPrPromo";i:9736;s:4:"Dawt";i:9737;s:11:"NariOrThean";i:9738;s:4:"Word";i:9739;s:10:"ExcitPassu";i:9740;s:7:"HoPoPre";i:9741;s:5:"Sampl";i:9742;s:9:"LePhilaUn";i:9743;s:8:"MalebOve";i:9744;s:9:"BoyaTrigi";i:9745;s:8:"DaDiGuai";i:9746;s:9:"DeerhLiQu";i:9747;s:9:"EpicSquee";i:9748;s:6:"PiSupp";i:9749;s:6:"SenSub";i:9750;s:9:"RemuSubba";i:9751;s:12:"HighlSophiSu";i:9752;s:9:"OptimSpor";i:9753;s:4:"Sech";i:9754;s:9:"AmCartCys";i:9755;s:9:"MushmOsPa";i:9756;s:5:"Decon";i:9757;s:4:"Sexh";i:9758;s:10:"ResisSaThu";i:9759;s:3:"Zec";i:9760;s:4:"Vill";i:9761;s:3:"Pod";i:9762;s:9:"OweSisWai";i:9763;s:11:"EmWassaWeig";i:9764;s:9:"HaPostTub";i:9765;s:4:"Stel";i:9766;s:8:"PePrScru";i:9767;s:6:"GastPr";i:9768;s:10:"TeretTrich";i:9769;s:7:"ResiSpu";i:9770;s:3:"Gre";i:9771;s:14:"CunjOversPneum";i:9772;s:8:"OdOzoUmi";i:9773;s:10:"ConfuCover";i:9774;s:9:"PaSubuUnb";i:9775;s:3:"Och";i:9776;s:6:"OvPorc";i:9777;s:11:"ReweSubvWin";i:9778;s:7:"FouUnfo";i:9779;s:12:"FluMicrRecre";i:9780;s:14:"RecruUnivUnspr";i:9781;s:10:"UnsavUnsea";i:9782;s:9:"HormoPiSe";i:9783;s:8:"SpheTwis";i:9784;s:5:"Thema";i:9785;s:13:"DidelTailUnwh";i:9786;s:5:"Marsi";i:9787;s:14:"InfrPleurPurre";i:9788;s:12:"MekomPreSync";i:9789;s:8:"EartNota";i:9790;s:6:"SoddWa";i:9791;s:3:"Obv";i:9792;s:14:"MarmNaphtPunis";i:9793;s:9:"PhillTaut";i:9794;s:12:"InomaNaPremi";i:9795;s:6:"PagaSa";i:9796;s:9:"LoMidleRe";i:9797;s:14:"MonotPsalTetar";i:9798;s:3:"Mum";i:9799;s:13:"PopuTheorWone";i:9800;s:5:"Worth";i:9801;s:6:"SkWhiz";i:9802;s:8:"DemEroHo";i:9803;s:5:"Soili";i:9804;s:7:"StoutTa";i:9805;s:11:"InteManShaf";i:9806;s:11:"DeyxSpTouri";i:9807;s:7:"GlagLac";i:9808;s:8:"RedaSauc";i:9809;s:11:"PseuTintiTo";i:9810;s:7:"NoachOa";i:9811;s:11:"HyPacinSnor";i:9812;s:4:"Ghee";i:9813;s:5:"Shive";i:9814;s:3:"Urv";i:9815;s:15:"SupraUnaptUnhis";i:9816;s:11:"BullwCrUnge";i:9817;s:11:"GuitaPredVa";i:9818;s:5:"Tarqu";i:9819;s:10:"InsecQuaTh";i:9820;s:11:"EnOverTrans";i:9821;s:11:"DelPeloPeti";i:9822;s:7:"DiFicLo";i:9823;s:4:"FoMa";i:9824;s:11:"InexObjPara";i:9825;s:5:"Stere";i:9826;s:7:"LaiSail";i:9827;s:10:"PrRiddScir";i:9828;s:7:"UnVinat";i:9829;s:7:"OvPileo";i:9830;s:7:"BliCuTr";i:9831;s:12:"ArchsHormoTo";i:9832;s:7:"ForMaTh";i:9833;s:5:"ConDi";i:9834;s:7:"HabblSy";i:9835;s:11:"BioSetulTra";i:9836;s:5:"Twigl";i:9837;s:12:"DarnHesteMed";i:9838;s:9:"ElFrShagb";i:9839;s:9:"DeSticUnc";i:9840;s:5:"Repug";i:9841;s:12:"PamSacroValo";i:9842;s:10:"ThrasUnmod";i:9843;s:6:"DidEco";i:9844;s:11:"AntiDolefRh";i:9845;s:8:"EucomSer";i:9846;s:6:"OxyhPo";i:9847;s:10:"MononPulmo";i:9848;s:4:"Urba";i:9849;s:11:"HolPhoUnshi";i:9850;s:11:"TiamUnguaUn";i:9851;s:6:"EnRoUn";i:9852;s:5:"Semia";i:9853;s:9:"DisbPyrTe";i:9854;s:4:"Geob";i:9855;s:8:"BeInSupe";i:9856;s:8:"DepreNon";i:9857;s:7:"PoWhoms";i:9858;s:4:"Pori";i:9859;s:12:"MisprPhUnarg";i:9860;s:3:"Oxy";i:9861;s:12:"CephaMaPanti";i:9862;s:9:"HyotPhala";i:9863;s:12:"InexUntVoidl";i:9864;s:13:"HendePolysVan";i:9865;s:11:"FusilInTast";i:9866;s:6:"UngUnv";i:9867;s:13:"CaleInterMors";i:9868;s:5:"HeTri";i:9869;s:8:"ElHaPrer";i:9870;s:9:"EcoUnUnwa";i:9871;s:10:"RefinRhTub";i:9872;s:6:"MallPl";i:9873;s:5:"Overm";i:9874;s:14:"EntraProfSynge";i:9875;s:8:"MonUnemb";i:9876;s:4:"Osci";i:9877;s:8:"SwoThink";i:9878;s:14:"OsiaOveriWimpl";i:9879;s:7:"BuHinny";i:9880;s:7:"SulUpgr";i:9881;s:5:"PlSma";i:9882;s:8:"SacchSir";i:9883;s:9:"GristRift";i:9884;s:8:"UncUppar";i:9885;s:10:"SnSubUntho";i:9886;s:4:"Loaf";i:9887;s:13:"PyrocSteekTax";i:9888;s:3:"Zac";i:9889;s:10:"OyeUnVioli";i:9890;s:12:"GimMyeliPyro";i:9891;s:10:"RhodSeliTe";i:9892;s:12:"FisUnadUnsub";i:9893;s:8:"WudgXant";i:9894;s:10:"NoOinPyrop";i:9895;s:6:"ExItal";i:9896;s:5:"Totty";i:9897;s:5:"Octam";i:9898;s:11:"OverReticUl";i:9899;s:9:"ElabrFenc";i:9900;s:13:"EnantUnnUnoxi";i:9901;s:5:"PeSyr";i:9902;s:9:"EtymPhoto";i:9903;s:5:"LilMi";i:9904;s:12:"GlypMedUnfet";i:9905;s:8:"LiaThrWi";i:9906;s:10:"OrShoaThur";i:9907;s:8:"UnthUpru";i:9908;s:7:"LepNihi";i:9909;s:12:"FiliMesoUpst";i:9910;s:9:"PetaTenni";i:9911;s:5:"PedRe";i:9912;s:12:"ColoProfTher";i:9913;s:10:"KatabOvSyr";i:9914;s:5:"Cequi";i:9915;s:7:"DisDiEn";i:9916;s:9:"DorMyzoWi";i:9917;s:8:"NiphaPol";i:9918;s:4:"BiCo";i:9919;s:12:"CompPolypUls";i:9920;s:8:"HeaveStr";i:9921;s:5:"MaMos";i:9922;s:13:"EudoMacarSore";i:9923;s:4:"OlVa";i:9924;s:4:"Cosw";i:9925;s:11:"MalOverdSub";i:9926;s:10:"DiKilUnder";i:9927;s:4:"MaPe";i:9928;s:10:"MetrPeProm";i:9929;s:6:"TekUnd";i:9930;s:7:"PlotiSu";i:9931;s:11:"PaPetalTrac";i:9932;s:11:"MoneyUndUnv";i:9933;s:7:"InsurYo";i:9934;s:11:"FoosMicTact";i:9935;s:7:"OlOther";i:9936;s:7:"MonMyOn";i:9937;s:11:"EmexPySymbo";i:9938;s:11:"HypaxSpiTar";i:9939;s:8:"AntUmbVi";i:9940;s:7:"OcclSqu";i:9941;s:8:"ExaPlush";i:9942;s:12:"PreSuccuVerv";i:9943;s:3:"Tre";i:9944;s:5:"MerTo";i:9945;s:11:"GlossSeptSo";i:9946;s:4:"Magn";i:9947;s:13:"QuintSourwThu";i:9948;s:5:"Kinet";i:9949;s:4:"NoUn";i:9950;s:4:"MaUn";i:9951;s:12:"MatriSpirSwe";i:9952;s:8:"OnlooUnl";i:9953;s:13:"MorwoSubarUne";i:9954;s:4:"Impo";i:9955;s:4:"Phac";i:9956;s:8:"DeprePri";i:9957;s:10:"CatLakarPr";i:9958;s:14:"GuttLophoMildr";i:9959;s:12:"OnychSmeUnwe";i:9960;s:5:"GroWo";i:9961;s:8:"LiRadTru";i:9962;s:13:"IncomTitVidui";i:9963;s:12:"ImpMerePrein";i:9964;s:7:"MaProto";i:9965;s:12:"RamusShrTibi";i:9966;s:11:"BurwFalciSn";i:9967;s:5:"RodTh";i:9968;s:8:"DoxaOlch";i:9969;s:4:"Tere";i:9970;s:4:"DiPo";i:9971;s:8:"DenucLit";i:9972;s:11:"CountInKusk";i:9973;s:5:"HeKok";i:9974;s:5:"PrSta";i:9975;s:7:"PhrenPr";i:9976;s:8:"MyoneNet";i:9977;s:14:"GeasMisfeRepre";i:9978;s:13:"OphidProveSub";i:9979;s:11:"PolProrSien";i:9980;s:5:"Grudg";i:9981;s:10:"LiReglUnde";i:9982;s:7:"DiMyoSu";i:9983;s:7:"SymTyVu";i:9984;s:11:"FiguHebSpon";i:9985;s:8:"CeDoTele";i:9986;s:10:"OkiTipTyig";i:9987;s:9:"PySphTetr";i:9988;s:3:"Pep";i:9989;s:11:"PiuUncUnson";i:9990;s:9:"OchiPrWor";i:9991;s:8:"RequiRyn";i:9992;s:13:"JimbeReceRegi";i:9993;s:4:"HeMe";i:9994;s:5:"Toffy";i:9995;s:7:"EriodSu";i:9996;s:9:"UnexViola";i:9997;s:11:"OrgaUnUnhos";i:9998;s:3:"Dav";i:9999;s:6:"HydRev";}
\ No newline at end of file
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testLazyJsonMapper.php b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testLazyJsonMapper.php
new file mode 100755
index 0000000..f47480d
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testLazyJsonMapper.php
@@ -0,0 +1,1897 @@
+= 0;
+
+$json = << 'int',
+ 'just_a_string' => 'float[]',
+ 'test_pure_lazymapper_object' => '\LazyJsonMapper\LazyJsonMapper',
+ // test the shortcut to avoid having to write the whole path:
+ 'test_pure_lazymapper_object_shortcut' => 'LazyJsonMapper',
+ 'test_pure_lazymapper_object_shortarr' => 'LazyJsonMapper[][]',
+ // test without strict case-sensitive checking: (MUST fail)
+ // 'test_pure_lazymapper_object_shortcut' => 'lazyJsonMapper',
+ ];
+}
+// var_dump(new TestDeep()); // look at the LazyJsonMapper shortcut success
+class TestMid extends TestDeep
+{
+}
+
+class Test extends TestMid
+{
+ const JSON_PROPERTY_MAP = [
+ 'just_a_string' => 'string',
+ 'camelCaseProp' => 'int',
+ // full namespace path, with case-sensitivity typos on purpose (php
+ // allows it, but LazyJsonMapper compiles this to the proper name
+ // instead so that we have strict names internally):
+ 'self_object' => '\foo\Test',
+ 'string_array' => 'string[]',
+ // relative notation instead of full "\namespace\path":
+ // when relative mode is used, it looks in the defining class' own namespace.
+ 'self_array' => 'Test[]',
+ ];
+}
+
+$jsonData = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+
+var_dump($jsonData);
+
+$x = new Test($jsonData, true);
+
+// begin with basic tests...
+var_dump($x);
+$sub = $x->getSelfObject();
+$sub->setJustAString('modifying nested object and propagating the change to root object $x');
+var_dump($sub);
+var_dump($x->getSelfObject());
+var_dump($x);
+$multi = $x->getSelfArray(); // resolves all objects in array, but avoids doing
+ // it recursively. sub-properties are lazy-converted
+ // when they are actually requested.
+var_dump($multi); // array of objects, with no resolved sub-objects yet
+var_dump($x); // now has array of objects
+$deepsub = $multi[1]->getSelfObject(); // causes nested sub to be resolved
+var_dump($multi);
+var_dump($x);
+$deepsub->setJustAString('wow, propagating change of very deep object!');
+var_dump($multi);
+var_dump($x);
+var_dump($x->getCamelCaseProp());
+var_dump($x->getJustAString());
+var_dump($x->isJustAString());
+var_dump($x->getJustAString());
+var_dump($x);
+var_dump($x->getSelfObject());
+var_dump($x->getSelfObject()->getJustAString());
+var_dump($x->self_object->just_a_string);
+var_dump($x->getStringArray());
+var_dump($x->getSelfArray());
+
+try {
+ echo $x->a_missing_property_not_in_data_or_def;
+} catch (LazyJsonMapperException $e) {
+ printf("Test missing property via property access Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->getAMissingPropertyNotInDataOrDef();
+} catch (LazyJsonMapperException $e) {
+ printf("Test missing property via magic getter Exception: %s\n", $e->getMessage());
+}
+
+$x = new Test($jsonData, true);
+var_dump($x); // no data is resolved yet
+// test deeply nested chain of getters and setters.
+$x->getSelfArray()[1]->getSelfObject()->setJustAString('chained command for deep modification')->setCamelCaseProp(9944);
+var_dump($x); // chain has been resolved and change has propagated
+var_dump($x->getSelfArray()[1]->getSelfObject()->getCamelCaseProp()); // int(9944)
+
+class SubClassOfTest extends Test
+{
+}
+$foo = new SubClassOfTest(); // Test acceptance of subclasses of required class.
+$x->setSelfObject($foo);
+var_dump($x->getSelfObject());
+var_dump($x->getSelfObject()->getJustAString());
+
+try {
+ $x->setSelfObject('x'); // trying to set non-object value for object property
+} catch (LazyJsonMapperException $e) {
+ printf("Test non-object assignment Exception: %s\n", $e->getMessage());
+}
+
+class Bleh
+{
+}
+
+try {
+ $x->setSelfObject(new Bleh()); // trying wrong class for property
+} catch (LazyJsonMapperException $e) {
+ printf("Test wrong object assignment Exception: %s\n", $e->getMessage());
+}
+
+$foo = new Test(['just_a_string' => 'example']);
+$x->setSelfObject($foo);
+var_dump($x->getSelfObject());
+var_dump($x->getSelfObject()->getJustAString());
+$x->printJson();
+var_dump($x->just_a_string);
+var_dump(isset($x->just_a_string));
+unset($x->just_a_string);
+var_dump($x->just_a_string);
+var_dump(isset($x->just_a_string));
+unset($x->self_array);
+unset($x->camelCaseProp);
+$x->printJson();
+
+var_dump('---------------------');
+
+// test creation of objects from empty object-arrays "{}" in JSON
+class EmptyObjTest extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'self' => '\foo\EmptyObjTest',
+ ];
+}
+$x = new EmptyObjTest(json_decode('{"self":null}', true)); // allow null object
+var_dump($x->getSelf());
+// NOTE: the empty-array test is because empty arrays are indistinguishable from
+// objects when decoded from JSON. but if it had been an actual JSON array
+// (which is always non-associative), then we detect that it's non-object data.
+$x = new EmptyObjTest(json_decode('{"self":{}}', true)); // allow empty object
+var_dump($x->getSelf());
+$x = new EmptyObjTest(json_decode('{"self":[]}', true)); // allow empty array
+var_dump($x->getSelf());
+$x = new EmptyObjTest(json_decode('{"self":[1,2]}', true)); // forbid non-object
+try {
+ var_dump($x->getSelf());
+} catch (\Exception $e) {
+ printf("Test converting invalid regular JSON array to object Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestUndefinedProps extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'self' => '\foo\TestUndefinedProps',
+ 'selfArray' => '\foo\TestUndefinedProps[]',
+ 'foo_bar' => 'int[][]',
+ 'property' => 'string',
+ ];
+}
+
+$json = <<getMessage());
+}
+
+// now create the class without analysis enabled... which enables regular
+// operation where the user can access the undefined properties too.
+$y = new TestUndefinedProps($data, false);
+var_dump($y); // look at the internal data and the compiled class map
+
+// now verify what the exported property map says.
+// the only defined properties are foo_bar, self, selfArray and property.
+// the undefined ones are missing_property and another_missing.
+$allowRelativeTypes = true;
+$includeUndefined = true;
+$descriptions = $y->exportPropertyDescriptions($allowRelativeTypes, $includeUndefined);
+var_dump($descriptions);
+foreach ($descriptions as $property) {
+ printf("* Property: '%s'. Defined: %s\n", $property->name,
+ $property->is_defined ? 'Yes!' : 'No.');
+}
+
+// Now just test the automatic printing function too...
+$showFunctions = true;
+$y->printPropertyDescriptions($showFunctions, $allowRelativeTypes, $includeUndefined);
+$showFunctions = true;
+$allowRelativeTypes = false;
+$includeUndefined = false;
+$y->printPropertyDescriptions($showFunctions, $allowRelativeTypes, $includeUndefined);
+$showFunctions = false;
+$y->printPropertyDescriptions($showFunctions, $allowRelativeTypes, $includeUndefined);
+
+// And test it on the main class too:
+$y = new Test();
+$y->printPropertyDescriptions();
+$y->printPropertyDescriptions(false, true); // without functions, with relative
+
+var_dump('---------------------');
+
+// Test the hasX() functions, which are useful when verifying that non-defined
+// (not in class definition) fields exist in data before trying to read, to
+// avoid causing any exceptions in the getter.
+$x = new Test($data);
+var_dump($x->hasReallyMissing()); // false, since it's not in class def or data.
+var_dump($x->hasAnotherMissing()); // true, since it's in data (but not in class def)
+var_dump($x->hasJustAString()); // true, since it's in class def (but not in data)
+var_dump($x->getJustAString()); // null, since it's not in data (but is in class def)
+try {
+ $x->getReallyMissing(); // exception, since it's not in class def or data.
+ // var_dump($x->really_missing); // also exception, "no such object property".
+} catch (LazyJsonMapperException $e) {
+ printf("Test getReallyMissing() Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setReallyMissing('a'); // exception, since it's not in class def or data.
+ // $x->really_missing = 'a'; // also exception, "no such object property".
+} catch (LazyJsonMapperException $e) {
+ printf("Test setReallyMissing() Exception: %s\n", $e->getMessage());
+}
+// intended usage by end-users when accessing undefined values:
+if ($x->hasReallyMissing()) {
+ // this won't run, since ReallyMissing didn't exist. but if it HAD existed
+ // in the JSON data, this function call would now be safe without exceptions:
+ var_dump($x->getReallyMissing());
+} else {
+ var_dump('not running getReallyMissing() since the property is missing');
+}
+
+var_dump('---------------------');
+
+class TestNotSubClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'not_subclass' => '\foo\NotSubClass',
+ ];
+}
+class NotSubClass
+{
+} // Not instance of LazyJsonMapper
+
+$json = <<getNotSubclass();
+} catch (LazyJsonMapperException $e) {
+ printf("TestNotSubClass Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestMissingClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'a_missing_class' => '\foo\Missing',
+ ];
+}
+
+$json = <<getMessage());
+}
+
+var_dump('---------------------');
+
+class TestMissingPropAndMissingClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'a_missing_class' => '\foo\Missing',
+ ];
+}
+
+$json = <<getMessage());
+}
+
+var_dump('---------------------');
+
+// this test checks two things:
+// definitions that do not match the data.
+// properties whose classes cannot be constructed (due to custom _init() fail).
+class TestUnmappableAndFailConstructor extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'bad_definition' => 'int[][][]', // too deep arrays for the data
+ // this one will not be able to construct during the getting of this property...
+ 'impossible_constructor' => '\foo\WithBadConstructor',
+ ];
+}
+
+class WithBadConstructor extends LazyJsonMapper
+{
+ protected function _init()
+ {
+ // Uncomment this other exception to test the "Invalid exception thrown
+ // by _init(). Must use LazyUserException." error when users throw the
+ // wrong exception:
+ // throw new \Exception('test');
+
+ throw new \LazyJsonMapper\Exception\LazyUserException('Hello world! Thrown by a failing constructor.');
+ }
+}
+
+$json = <<getMessage());
+}
+
+var_dump('---------------------');
+
+class TestImpossibleSubPropertyCompile extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ // this one links to a class that exists but isn't compiled yet and
+ // therefore must be sub-compiled. but that particular one actually
+ // failed its own compilation earlier (above) - because the
+ // "TestMissingClass" class CANNOT be compiled... so OUR map compilation
+ // HERE will succeed (it points at TestMissingClass which is a
+ // LazyJsonMapper class which exists), but then WE will fail with a
+ // sub-property class error when we try to ALSO compile OUR property's
+ // uncompiled class ("TestMissingClass") at the end of its own
+ // compilation process.
+ 'impossible_subcompilation' => '\foo\TestMissingClass',
+ ];
+}
+
+try {
+ $x = new TestImpossibleSubPropertyCompile();
+} catch (\Exception $e) {
+ printf("Test impossible sub-property class compilation: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// this test is very similar to the previous test, but it ensures that the
+// validation works for deep hierarchies too, of classes with properties that
+// refer to classes with properties that refer to classes that refer to
+// uncompilable classes.
+class TopFailChainClass extends LazyJsonMapper
+{
+ // successfully compiles since MiddleFailChainClass exists and is based on LazyJsonMapper
+ const JSON_PROPERTY_MAP = [
+ 'middle_fail_chain_class' => '\Foo\MiddleFailChainClass',
+ ];
+}
+
+class MiddleFailChainClass extends LazyJsonMapper
+{
+ // successfully compiles since DeepFailChainBadClass exists and is based on LazyJsonMapper
+ const JSON_PROPERTY_MAP = [
+ 'deep_fail_chain_class' => '\Foo\DeepFailChainBadClass',
+ ];
+}
+
+class DeepFailChainBadClass extends LazyJsonMapper
+{
+ // this map will fail to compile, which should stop the compilation of
+ // whichever class began the compilation process that pointed at us...
+ const JSON_PROPERTY_MAP = [
+ 'not_a_valid_class' => '/What/ever/...',
+ ];
+}
+
+// try starting the compilation with each of the 3 classes:
+// it doesn't matter which one we start with, since the compiler cache will
+// notice the failures in them all and will auto-rollback their compilations.
+// which means that the other classes won't incorrectly see anything in the
+// cache. so each attempt to create any of these classes will be as if it was
+// the first-ever call for compiling the classes in its hierarchy.
+
+try {
+ // fails immediately since this class map cannot be compiled
+ $x = new DeepFailChainBadClass();
+} catch (\Exception $e) {
+ printf("Test compiling DeepFailChainBadClass Exception: %s\n", $e->getMessage());
+}
+
+try {
+ // succeeds at compiling its own map, but then fails when trying to compile
+ // the property classes (DeepFailChainBadClass) it found in the hierarchy.
+ $x = new MiddleFailChainClass();
+} catch (\Exception $e) {
+ printf("Test compiling MiddleFailChainClass Exception: %s\n", $e->getMessage());
+}
+
+try {
+ // succeeds at compiling its own map, then looks at its properties and
+ // succeeds at compiling MiddleFailChainClass, and then looks at that one's
+ // properties and fails at compiling the DeepFailChainBadClass it refers to.
+ $x = new TopFailChainClass();
+} catch (\Exception $e) {
+ printf("Test compiling TopFailChainClass Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestNullValue extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'this_is_null' => '\foo\TestNullValue',
+ ];
+}
+
+$json = <<getThisIsNull());
+} catch (LazyJsonMapperException $e) {
+ printf("TestNullValue Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestNoCastValue extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'no_cast1' => '',
+ 'no_cast2' => 'mixed', // same as ''
+ 'no_cast3' => '',
+ ];
+}
+
+$json = <<getNoCast1());
+ var_dump($x->getNoCast2());
+ var_dump($x->getNoCast3());
+ $x->setNoCast1('should succeed without type-forcing');
+ var_dump($x->getNoCast1());
+} catch (LazyJsonMapperException $e) {
+ printf("TestNoCastValue Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+class TestDepth extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'array_of_arrays_of_arrays_of_int' => 'int[][][]',
+ ];
+}
+$x = new TestDepth([]); // Init with no data.
+try {
+ $x->setArrayOfArraysOfArraysOfInt([[new Test()]]);
+} catch (LazyJsonMapperException $e) {
+ printf("Test non-array value at depth 2 of 3 Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setArrayOfArraysOfArraysOfInt([[[new Test()]]]);
+} catch (LazyJsonMapperException $e) {
+ printf("Test invalid value at depth 3 of 3 Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setArrayOfArraysOfArraysOfInt([[[[]]]]);
+} catch (LazyJsonMapperException $e) {
+ printf("Test invalid array-value at depth 3 of 3 Exception: %s\n", $e->getMessage());
+}
+$x->setArrayOfArraysOfArraysOfInt([[[1, '456', 100, 5.5]], [], null, [[20]]]);
+var_dump($x); // 1, 456, 100, 5, 20
+
+var_dump('---------------------');
+
+// test working with raw properties not defined in the class property map.
+class UndefinedPropertyAccess extends LazyJsonMapper
+{
+}
+$json = '{"some_undefined_prop":null}';
+$data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+
+try {
+ // if we run with VALIDATION (TRUE), this will throw an exception
+ // since the data's "some_undefined_prop" is not in the class property map.
+ // NOTE: We already did this test as TestUndefinedProps earlier...
+ $x = new UndefinedPropertyAccess($data, true);
+} catch (\Exception $e) {
+ printf("Test creating class instance with validation detecting undefined properties Exception: %s\n", $e->getMessage());
+}
+
+$x = new UndefinedPropertyAccess($data);
+var_dump($x);
+var_dump($x->hasSomeUndefinedProp()); // true
+var_dump($x->isSomeUndefinedProp()); // false (the null evaluates to false)
+var_dump($x->getSomeUndefinedProp()); // null
+$x->setSomeUndefinedProp(['no data validation since it is undefined']);
+var_dump($x->isSomeUndefinedProp()); // true (the array evaluates to true)
+var_dump($x->getSomeUndefinedProp()); // array with a string in it
+$x->setSomeUndefinedProp('xyz');
+var_dump($x->hasSomeUndefinedProp()); // true
+var_dump($x->getSomeUndefinedProp()); // "xyz"
+var_dump($x->isSomeUndefinedProp()); // true (the string evaluates to true)
+$x->setSomeUndefinedProp(null);
+var_dump($x->hasSomeUndefinedProp()); // true
+var_dump($x->getSomeUndefinedProp()); // null
+
+var_dump('---------------------');
+
+// test of advanced multi-class inheritance:
+// OurTree* is a set of classes inheriting (extending) each other.
+// Unrelated* are two other classes extending each other.
+// FarClass is a single class without any other parents except LazyJsonMapper.
+//
+// OurTreeThree compiles its own inherited hierarchy, which then imports
+// UnrelatedTwo, which compiles its own hierarchy, which then finally imports
+// FarClass. The result is a final, compiled map which includes all classes.
+//
+// (and as noted in the main source code, memory cost of inheritance is 0 since
+// all classes inherit each other's PropertyDefinition objects; the only cost is
+// the amount of RAM it takes for an array["key"] to link to the borrowed object)
+class FarClass extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'farclass' => '\Foo\FarClass',
+ ];
+}
+class UnrelatedOne extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ FarClass::class,
+ 'unrelated_one' => 'int',
+ ];
+}
+class UnrelatedTwo extends UnrelatedOne
+{
+ const JSON_PROPERTY_MAP = [
+ 'unrelated_two' => 'float',
+ 'conflicting_prop1' => '\Foo\UnrelatedOne',
+ 'conflicting_prop2' => '\Foo\UnrelatedOne',
+ ];
+}
+class OurTreeOne extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'ourtree_one' => 'string',
+ ];
+}
+class OurTreeTwo extends OurTreeOne
+{
+ const JSON_PROPERTY_MAP = [
+ 'ourtree_two' => 'int',
+ 'conflicting_prop1' => '\Foo\OurTreeThree', // will be overwritten
+ UnrelatedTwo::class, // ... by this import
+ 'conflicting_prop2' => '\Foo\OurTreeThree', // will overwrite the import
+ ];
+}
+class OurTreeThree extends OurTreeTwo
+{
+ const JSON_PROPERTY_MAP = [
+ 'ourtree_three' => 'bool[]',
+ ];
+
+ protected function _init()
+ {
+ echo "Hello world from the init function!\n";
+ }
+}
+
+$x = new OurTreeThree();
+var_dump($x);
+
+var_dump('---------------------');
+
+// LOTS OF TESTS OF DIRECT BY-REFERENCE ACCESS, BOTH INTERNALLY AND EXTERNALLY.
+// INTERNALLY: &_getProperty(), EXTERNALLY: &__get()
+class TestGetProperty extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'foo' => '\Foo\TestGetProperty[]',
+ 'bar' => 'int',
+ ];
+
+ protected function _init()
+ {
+ // always set "bar" to a good value after construction (this is just to
+ // avoid code repetition during the testing... and to test the _init function)
+ $this->_setProperty('bar', 1234);
+
+ // uncommenting this will test the "must be LazyUserException" error:
+ // throw new \Exception('x'); // is rejected and replaced with generic
+ // throw new LazyUserException('What'); // okay, message propagates
+ }
+
+ public function runTest()
+ {
+ // just show the current internal data (nothing exists)
+ var_dump($this); // foo is empty
+
+ // test retrieving prop, but not saving missing NULL to _objectData
+ var_dump($this->_getProperty('foo')); // NULL
+ var_dump($this); // foo still empty
+
+ // test saving reference to return-value, but not saving default NULL to _objectData
+ $val = &$this->_getProperty('foo'); // missing important createMissingValue param
+ $val = 'hi'; // does NOT modify the real property, modifies some temp var
+ var_dump($this); // foo still empty
+
+ // test saving reference to return-value, and creating + filling default
+ // inner NULL value so that we can properly trust our references to always
+ // return to real inner data. this is the correct way to save the return
+ // by reference.
+ $val = &$this->_getProperty('foo', true);
+ var_dump($this); // foo now has a NULL value in its object data
+ $val = 'hi, this worked because we are linked to real internal data!';
+ var_dump($this); // overwritten internal value thanks to proper link
+
+ // notice how we have set an invalid STRING value to the internal data?
+ // the "foo" property was specified to need to be an array of object
+ // instances (or NULL is ok too). well, we will detect that the next
+ // time we try to retrieve that property!
+ try {
+ $this->_getProperty('foo');
+ } catch (\Exception $e) {
+ printf("Test _getProperty() with invalid data inserted by reference Exception: %s\n", $e->getMessage());
+ }
+
+ // let's try satisfying its array requirements (but still fail its type req)
+ $val = ['string inside array now fits array req but not type req'];
+
+ try {
+ $this->_getProperty('foo');
+ } catch (\Exception $e) {
+ printf("Test _getProperty() with valid array but invalid type inserted by reference Exception: %s\n", $e->getMessage());
+ }
+
+ // now let's fix the property (set it back to NULL, or we could have
+ // made an array of this class to satisfy the requirement).
+ $val = null;
+ var_dump($this); // foo is now NULL again
+
+ // lastly, let's show that the value is always copy-on-write if the &
+ // operator is omitted from the function call. because then PHP is told
+ // to make $val into copy-on-write.
+ unset($val); // important: break its current reference to avoid assigning to it
+ $val = $this->_getProperty('foo');
+ $val = 'not modified!';
+ var_dump($this); // foo still NULL, since $val is not a reference
+
+ // for completeness sake, also test the "bar" property which has a value
+ // and therefore ignores the "createMissingValue"
+ $bar = &$this->_getProperty('bar', true);
+ var_dump($bar); // int(1234), since a value already existed
+ var_dump($this); // bar still 1234
+ $bar = 456;
+ var_dump($this); // bar is now 456
+ }
+}
+
+// run the internal $this->_getProperty call tests:
+$x = new TestGetProperty();
+$x->runTest();
+
+// run the external __get() call tests used for "virtual property access":
+$x = new TestGetProperty(); // reset the internal data
+var_dump($x); // has no "foo" data, only "bar"
+// accessing ->foo calls __get(), which creates "foo" since virtual prop access
+// requires true internal data references, otherwise they would misbehave. so it
+// creates the missing property and gives it the default NULL value.
+var_dump($x->foo); // null
+var_dump($x); // also has "foo" now
+// accessing ->bar calls __get() which sees that it exists, and gives us its value.
+var_dump($x->bar); // 1234
+// trying to set varibles via equals causes __set() to run, which validates all data:
+try {
+ $x->bar = ['invalid']; // int is expected
+} catch (\Exception $e) {
+ printf("Test __set() with invalid value Exception: %s\n", $e->getMessage());
+}
+$x->bar = '932'; // this is okay, __set() sees it is valid and casts it to int
+var_dump($x); // "bar" is now int(932)
+// now let's do some evil special cases! we will steal a direct reference to the
+// internal _objectData['bar'], and then modify it, thus bypassing all validation.
+$evilRef = &$x->bar; // now holds reference to bar
+$evilRef = ['invalid']; // works, since we're literally modifying internal data
+var_dump($x); // "bar" now has an invalid value (an array with a string)
+try {
+ // luckily, every call to _getProperty() (which __get() uses) will validate
+ // the data to ensure that its internal state is valid and fits the class map.
+ var_dump($x->bar);
+} catch (\Exception $e) {
+ printf("Test detection of injected invalid data during next __get() Exception: %s\n", $e->getMessage());
+}
+$x->bar = 789; // call __set() and give it a new, valid value again.
+var_dump($x->bar); // int(789), it is now fixed!
+// lastly, let's play with direct access to internal arrays. anytime you access
+// an array, it will call __get() to get the array, and then PHP resolves your
+// [] brackets on the returned array. which means that we can modify arrays by
+// reference automatically!
+$x->foo = []; // runs __set(): create empty array for this "\Foo\TestGetProperty[]" property
+var_dump($x->foo); // runs __get() which sees valid empty array and returns it
+$x->foo[] = new TestGetProperty(); // okay data written by ref to "foo" array
+var_dump($x->foo); // runs __get(), which sees valid array of 1 item of right type
+$x->foo[] = 'invalid'; // this is allowed because __get() gets "foo" and then
+ // PHP just directly modifies the array...
+var_dump($x); // the "foo" prop now has invalid data in it
+// but luckily, anything that calls _getProperty() again, such as __get(), will
+// cause validation of the data:
+try {
+ // var_dump($x->foo); // calls __get(), would also throw the error.
+ $x->foo[] = 'x'; // calls __get() again to resolve "->foo" and throws error
+} catch (\Exception $e) {
+ printf("Test detection of invalid injected data via array access Exception: %s\n", $e->getMessage());
+}
+$x->foo = [new TestGetProperty(), new TestGetProperty()]; // run __set() to give okay array again, with 2 entries
+var_dump($x->foo); // shows the array with 2 objects in it
+$x->foo[0] = null; // runs __get(), gets "foo" by reference, and directly modifies element
+var_dump($x->foo); // shows array with 1 NULL in it (this array is valid hence no error)
+// we can also __get() the internal array, then loop over the
+// values-by-reference, to directly modify them without any validation:
+foreach ($x->foo as $k => &$fooVal) {
+ $fooVal = 'invalid';
+}
+var_dump($x); // array with string "invalid".
+try {
+ // if this had been unset($x->foo) it would work, but we try to unset a
+ // sub-element which means it actually calls __get() instead of __unset()
+ unset($x->foo[0]); // calls __get(), sees that the data is now invalid
+} catch (\Exception $e) {
+ printf("Test detection of invalid injected data via array by-reference value loop Exception: %s\n", $e->getMessage());
+}
+var_dump($x); // two "invalid" remains
+$x->foo = [null, new TestGetProperty()]; // let's make it a valid 2-element
+var_dump($x->foo); // array of [null,obj];
+unset($x->foo[0]); // runs __get() on "foo", then unsets the 0th element
+
+// these tests were commented out after adding strict sequence valiation:
+// var_dump($x->foo); // array of [obj];, with the 0 array key missing
+// unset($x->foo[1]->bar); // runs__get() on "foo", gets array, finds 1st elem,
+// // sees object, runs __unset() on that ones "bar"
+// var_dump($x); // reveals that the inner object no longer has any "bar" value
+
+// let's test accessing an object inside an array. and for fun add in regular getters
+// NOTE: there is a subtle difference. getFoo() returns copy-on-write array, but
+// any objects within it are of course objects and can be modified and will propagate.
+$x->foo = [new TestGetProperty()];
+var_dump($x->foo[0]->bar); // int(1234)
+var_dump($x->foo[0]->getBar()); // int(1234)
+var_dump($x->getFoo()[0]->getBar()); // int(1234)
+var_dump($x->getFoo()[0]->bar); // int(1234)
+$x->getFoo()[0]->setBar(10);
+var_dump($x); // the 0th "foo" array element has a bar of int(10) now
+$x->getFoo()[0] = 'xyz'; // this does nothing, since getFoo() is always copy-on-write.
+var_dump($x); // still intact, statement above had no effect, which is as intended
+// now let's modify the array by reference to avoid constant __get() calls...
+$arr = &$x->foo;
+$arr = [1, 2, 'f'=>'bar', [['very invalid data']]];
+$arr[] = 'more invalid stuff...';
+var_dump($x); // very invalid stuff...
+try {
+ var_dump($x->foo);
+} catch (\Exception $e) {
+ printf("Test __get() after lots of bad array edits by reference Exception: %s\n", $e->getMessage());
+}
+$arr = null;
+var_dump($x->foo); // now it is fine again (NULL)
+// let's call a normal array-command on the returned array-by-ref
+$x->foo = []; // first make it into an array
+array_push($x->foo, 'zzz'); // now __get() "foo" and then directly push (same as $x->foo[] = 'bar';)
+var_dump($x); // we have directly added invalid data "zzz" into the array.
+$x = null; // release the object...
+
+var_dump('---------------------');
+
+// Test PropertyDefinition equality:
+$a = new PropertyDefinition('int[]');
+$b = new PropertyDefinition('int');
+$c = new PropertyDefinition('int[]');
+var_dump($a->equals($b)); // false
+var_dump($a->equals($c)); // true
+var_dump($b->equals($a)); // false
+var_dump($b->equals($c)); // false
+var_dump($c->equals($a)); // true
+var_dump($c->equals($b)); // false
+
+var_dump('---------------------');
+
+// Test inheriting from base-class and also importing from other-class
+class OtherBase extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'otherbase' => 'string[]',
+ // ImportMapTest::class, // triggers circular map error IF ImportMapTest
+ // // itself refers to our class hierarchy.
+ ];
+}
+class Other extends OtherBase
+{
+ const JSON_PROPERTY_MAP = [
+ 'identical_key' => 'float',
+ 'other' => 'int',
+ ];
+}
+class Base extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'base' => 'float',
+ ];
+}
+class ImportMapTest extends Base
+{
+ const JSON_PROPERTY_MAP = [
+ // Base::class, // self-hierarchy reference (not allowed)
+ // ImportMapTest::class, // self-class reference (not allowed)
+ C::class, // reference to deeper version of self (not allowed)
+ Other::class, // okay, since it's another class.. but only ok if that
+ // other class doesn't have its circular reference enabled!
+ 'identical_key' => 'string', // should be string, since we add it after
+ // importing Other. but the one in Other should
+ // remain as its own one (float).
+ ];
+}
+class C extends ImportMapTest
+{
+}
+
+try {
+ $x = new C(); // comment in/out various class references above to test
+ // various arrangements of bad circular references.
+ var_dump($x); // if successful inheritance, print the
+ // _compiledPropertyMapLink so we can verify that all values
+ // are properly merged.
+} catch (\Exception $e) {
+ printf("Test resolved-shared-ancestor circular map Exception: %s\n", $e->getMessage());
+}
+
+class AA extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ BB::class,
+ ];
+}
+class BB extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ AA::class,
+ ];
+}
+
+try {
+ $x = new AA();
+} catch (\Exception $e) {
+ printf("Test resolved-shared-ancestor circular map Exception: %s\n", $e->getMessage());
+}
+
+// ensure that the locks are empty after all the construction failures above
+$x = new LazyJsonMapper();
+$reflect = new \ReflectionProperty($x, '_propertyMapCache');
+$reflect->setAccessible(true);
+var_dump($reflect->getValue()->compilerLocks); // should be empty array
+
+var_dump('---------------------');
+
+// this was written to test PropertyDefinition re-use (RAM saving) when a class
+// re-defines a property to the exact same settings that it already inherited.
+// properties in LazyJsonMapper will keep their parent's/imported value if their
+// new value is identical, thus avoiding needless creation of useless objects
+// that just describe the exact same settings. it makes identical re-definitions
+// into a zero-cost operation!
+class HasFoo extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'foo' => 'string[]',
+ ];
+}
+class RedefinesFoo extends HasFoo
+{
+ const JSON_PROPERTY_MAP = [
+ // 'foo' => 'float', // tests non-identical settings (should USE NEW obj)
+ 'foo' => 'string[]', // tests identical settings (should KEEP parent
+ // obj). memory usage should be same as if 'foo'
+ // wasn't re-defined on THIS object at all.
+ // 'foo' => 'string[][]', // tests non-identical settings (should USE NEW obj)
+ 'extra' => '\LazyJsonMapper\LazyJsonMapper',
+ ];
+}
+$mem = memory_get_usage();
+$x = new RedefinesFoo();
+unset($x); // free the object itself, so we only keep the compiled map cache
+printf("Memory increased by %d bytes.\n", memory_get_usage() - $mem);
+
+var_dump('---------------------');
+
+// test function overriding:
+class OverridesFunction extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'foo' => 'string',
+ ];
+
+ public function getFoo()
+ {
+ $value = $this->_getProperty('foo');
+
+ return 'Custom getter: '.var_export($value, true);
+ }
+
+ public function setFoo(
+ $value)
+ {
+ $value = sprintf('Tried "%s" but we will write "%s" instead.', $value, md5(time()));
+ $this->_setProperty('foo', $value);
+
+ return $this;
+ }
+}
+
+$x = new OverridesFunction();
+var_dump($x->getFoo());
+$x->setFoo('ignored');
+var_dump($x->getFoo());
+
+var_dump('---------------------');
+
+// Test rejection of associative array keys in "array of" JSON definition, since
+// those are illegal JSON.
+
+class TestArrayKeyValidation extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'obj_arr' => '\Foo\TestArrayKeyValidation[]',
+ ];
+}
+
+// normal sequence, okay:
+$x = new TestArrayKeyValidation(['obj_arr' => [null, null, null, new TestArrayKeyValidation()]]);
+var_dump($x->getObjArr());
+
+// gap in sequence, not okay:
+$x = new TestArrayKeyValidation(['obj_arr' => [1 => new TestArrayKeyValidation()]]);
+
+try {
+ var_dump($x->getObjArr());
+} catch (\Exception $e) {
+ printf("* Test numeric gap in typed 'array of' sequence: %s\n", $e->getMessage());
+}
+
+// This is ok because of a PHP quirk which converts '0' to 0 if used as array
+// key in certain cases such as this one. The key here is literally int(0):
+$x = new TestArrayKeyValidation(['obj_arr' => ['0' => new TestArrayKeyValidation()]]);
+var_dump($x->getObjArr());
+
+// string key in numerically indexed "array of", not okay:
+$x = new TestArrayKeyValidation(['obj_arr' => ['not_allowed_to_have_key' => new TestArrayKeyValidation()]]);
+
+try {
+ var_dump($x->getObjArr());
+} catch (\Exception $e) {
+ printf("* Test illegal string-based key in typed 'array of' sequence: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// Test validation of mixed data (only allows NULL, int, float, string, bool, or
+// numerically indexed arrays of any of those types). Untyped arrays always do
+// array key validation.
+
+class TestUntypedValidation extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'unt' => '', // behavior would be the same if this property is not
+ // defined in class map but then it would have to exist in
+ // the original input data, so I defined it here as untyped.
+ 'strict_depth' => 'mixed[][]', // enforces mixed non-array data at 2
+ // levels deep within an array
+ ];
+}
+
+$x = new TestUntypedValidation();
+
+try {
+ $x->setUnt(new \stdClass());
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of object \stdClass: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setUnt([new \stdClass()]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of object [\stdClass]: %s\n", $e->getMessage());
+}
+
+$fh = null;
+
+try {
+ $fh = fopen(__DIR__.'/../funcListData.serialized', 'r');
+
+ try {
+ $x->setUnt($fh);
+ } catch (\Exception $e) {
+ printf("* Test set-untyped rejection of Resource: %s\n", $e->getMessage());
+ }
+
+ try {
+ $x->setUnt([$fh]);
+ } catch (\Exception $e) {
+ printf("* Test set-untyped rejection of [Resource]: %s\n", $e->getMessage());
+ }
+} finally {
+ if (is_resource($fh)) {
+ fclose($fh);
+ }
+}
+
+// all other types are allowed in this untyped field:
+$x->setUnt(null);
+$x->setUnt(1);
+$x->setUnt(1.5);
+$x->setUnt('2');
+$x->setUnt(true);
+$x->setUnt([null, 1, [1.5], '2', true]);
+var_dump($x->getUnt());
+
+// we also allow associative keys in untyped fields (which will become JSON
+// objects), since that allows people to access "objects" in json data (arrays)
+// without needing to manually map the properties to actual LazyJsonMapper
+// NOTE: mixing key types can create weird JSON objects. so if people want
+// strict validation they need to use typed fields instead, as seen above.
+$x->setUnt([null, 1, ['foo' => 1.5], '2', true]);
+var_dump($x->getUnt());
+
+// lastly, test the "mixed[]" strict_depth which specified untyped data but
+// exactly 1 array level deep.
+var_dump($x->getStrictDepth());
+$x->setStrictDepth([[null, 1, null, '2', true], null, []]);
+var_dump($x->getStrictDepth());
+
+try {
+ $x->setStrictDepth([[null, 1, null, '2', true], 'not_array', []]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of non-array at array-depth: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setStrictDepth([[null, 1, null, '2', true], 'foo' => 'bar', []]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of associative key in strict array depth: %s\n", $e->getMessage());
+}
+
+try {
+ $x->setStrictDepth([[null, 1, null, '2', true], [['too_deep']], []]);
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of array deeper than max depth: %s\n", $e->getMessage());
+}
+var_dump($x->getStrictDepth());
+$x->setStrictDepth([]); // okay, since we never reach maxdepth
+var_dump($x->getStrictDepth()); //accepted
+$x->setStrictDepth(null); // null is always okay
+var_dump($x->getStrictDepth()); //accepted
+try {
+ $x->setStrictDepth('foo'); // rejected since the value is not at specified depth
+} catch (\Exception $e) {
+ printf("* Test set-untyped rejection of value at not enough depth: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// Test FunctionCase name translations into property names:
+
+$x = new FunctionTranslation('ExportProp');
+var_dump($x);
+
+try {
+ // Invalid single-word lowercase FuncCase name.
+ $x = new FunctionTranslation('somelowercase');
+} catch (\Exception $e) {
+ printf("Test invalid single-word lowercase FuncCase name 'somelowercase' Exception: %s\n", $e->getMessage());
+}
+$x = new FunctionTranslation('_MessageList');
+var_dump($x);
+$x = new FunctionTranslation('Nocamelcase'); // Single uppercase = no camel.
+var_dump($x);
+$x = new FunctionTranslation('WithCamelCase'); // Multiple Ucwords = camel.
+var_dump($x);
+
+var_dump('---------------------');
+
+// Test property name translations into FunctionCase, and back...
+// They must be 100% identical in both directions!
+
+// Test function names to property names, and then ensure that both snake_case
+// and camelCase variants translate back to the same function name via PropertyTranslation.
+$funcList = [
+ 'getSome0XThing',
+ 'getSome0xThing',
+ 'getSomeThing',
+ 'get_Messages',
+ 'get__MessageList',
+ 'get0m__AnUn0x',
+ 'get__Foo_Bar__XBaz__',
+ 'get__Foo_Bar_',
+ 'get___M',
+ 'get_M',
+ 'get_0',
+ 'get_',
+ 'get___',
+ 'get123',
+ 'get123prop',
+ 'get123Prop',
+];
+foreach ($funcList as $f) {
+ echo "---\n";
+ list($functionType, $funcCase) = FunctionTranslation::splitFunctionName($f);
+
+ $x = new FunctionTranslation($funcCase);
+ printf("* Function: '%s'\n- Type: '%s'\n- FuncCase: '%s'\n > snake: '%s',\n > camel: '%s'\n", $f, $functionType, $funcCase, $x->snakePropName, $x->camelPropName);
+
+ $y = new PropertyTranslation($x->snakePropName);
+ $getter = 'get'.$y->propFuncCase;
+ printf("* Property: '%s' (snake)\n > func: '%s' (%s)\n", $x->snakePropName, $getter, $getter === $f ? 'ok' : 'fail');
+ if ($x->camelPropName === null) {
+ echo "* Property: No Camel Property, skipping...\n";
+ } else {
+ $y = new PropertyTranslation($x->camelPropName);
+ $getter = 'get'.$y->propFuncCase;
+ printf("* Property: '%s' (camel)\n > func: '%s' (%s)\n", $x->camelPropName, $getter, $getter === $f ? 'ok' : 'fail');
+ }
+ echo "---\n";
+}
+
+var_dump('---------------------');
+
+// test the special operator translator
+$result = 'A + and - and * and / and finally % symbol... And some ++--**//%% close ones...';
+var_dump($result);
+$result = \LazyJsonMapper\Magic\SpecialOperators::encodeOperators($result);
+var_dump($result);
+$result = \LazyJsonMapper\Magic\SpecialOperators::decodeOperators($result);
+var_dump($result);
+
+class OperatorTest extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'en+US' => 'string',
+ 'en-US' => 'string',
+ 'en/US' => 'string',
+ 'en%US' => 'string',
+ 'en*US' => 'string',
+ 'with;semicolon_and@at' => 'string',
+ ];
+}
+
+$optest = new OperatorTest([
+ 'en+US' => 'plus',
+ 'en-US' => 'minus',
+ 'en/US' => 'divide',
+ 'en%US' => 'modulo',
+ 'en*US' => 'multiply',
+ 'with;semicolon_and@at' => 'complex characters here!',
+]);
+
+$optest->printPropertyDescriptions();
+var_dump($optest->getEn_x2B_US()); // plus
+var_dump($optest->getEn_x2D_US()); // minus
+var_dump($optest->getEn_x2F_US()); // divide
+var_dump($optest->getEn_x25_US()); // modulo
+var_dump($optest->getEn_x2A_US()); // multiply
+var_dump($optest->getWith_x3B_semicolonAnd_x40_at());
+
+var_dump('---------------------');
+
+// Test the property description system (the parameters are so strict that there
+// isn't really anything to test, apart from the relative property param...)
+$ownerClassName = get_class(new Test());
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ new PropertyDefinition('\Foo\Test[][]'),
+ false // do not allow relative paths
+);
+var_dump($desc);
+
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ new PropertyDefinition('\Foo\Test[][]'),
+ true // allow relative paths
+);
+var_dump($desc);
+
+// and now test the is_defined detection of UndefinedProperty:
+
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ \LazyJsonMapper\Property\UndefinedProperty::getInstance(),
+ false // do not allow relative paths
+);
+var_dump($desc);
+
+$desc = new PropertyDescription(
+ $ownerClassName,
+ 'the_property',
+ \LazyJsonMapper\Property\UndefinedProperty::getInstance(),
+ true // allow relative paths
+);
+var_dump($desc);
+
+var_dump('---------------------');
+
+// calculate the memsize of a propertydefinition under various circumstances
+
+// first... force autoloading to find each class if not already loaded, to avoid
+// messing up the measurement.
+$x = new PropertyDefinition();
+$x = new LazyJsonMapper();
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('\LazyJsonMapper\LazyJsonMapper');
+printf("Memory size of a PropertyDefinition object referring to '\\LazyJsonMapper\\LazyJsonMapper': %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition();
+printf("Memory size of a PropertyDefinition object referring to NULL ('mixed'/untyped): %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('int');
+printf("Memory size of a PropertyDefinition object referring to 'int': %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('int[]');
+printf("Memory size of a PropertyDefinition object referring to 'int[]': %d bytes.\n", memory_get_usage() - $mem);
+
+unset($x);
+$mem = memory_get_usage();
+$x = new PropertyDefinition('float[][][]');
+printf("Memory size of a PropertyDefinition object referring to 'float[][][]': %d bytes.\n", memory_get_usage() - $mem);
+
+var_dump('---------------------');
+
+// Test detection of undefined properties:
+$undef = UndefinedProperty::getInstance();
+$def = new PropertyDefinition();
+var_dump($undef instanceof UndefinedProperty); // true
+var_dump($def instanceof UndefinedProperty); // false
+
+var_dump('---------------------');
+
+// the following test analyzes memory usage of FunctionTranslation objects vs
+// storing the cache values as a regular array (without objects). since objects
+// are specialized arrays in PHP, with lower memory needs, we see some pretty
+// great space savings by using objects.
+//
+// these tests will determine the memory needs for the runtime function call
+// translation cache. I'd say normally people use around 100 different
+// properties. this code generates very realistic function names that mimic
+// real-world data.
+
+// if true, re-use pre-built list of 10000 function names and ignore the other
+// params below. that's useful because building the list of names is slow on PHP5.
+// and especially because it guarantees comparable runs between PHP binaries.
+$usePrebuiltFuncList = true;
+
+// how many function names to generate. large sample sizes = accurate averages.
+// this only happens if "useprebuilt" is disabled.
+// $funcCacheCount = 10000; // recommended
+$funcCacheCount = 100; // fast for testing but gives inaccurate memory averages
+
+/*
+ * Here are results for PHP7 and PHP5 with 10000x entries to really demonstrate
+ * the correct averages by having a large enough sample size.
+ *
+ * PHP7: Array of 10000x FunctionTranslation objects: 2485704 bytes total, ~248.6 bytes per entry.
+ * PHP7: Array of 10000x numerically indexed arrays: 5394584 bytes total, ~539.5 bytes per entry.
+ * PHP5: Array of 10000x FunctionTranslation objects: 5104024 bytes total, ~510.4 bytes per entry.
+ * PHP5: Array of 10000x numerically indexed arrays: 6640864 bytes total, ~664.4 bytes per entry.
+ *
+ * Those numbers include the object AND the overhead of their associatively
+ * named string key in the parent (cache) array. The array key is the FuncCase
+ * portion of the name (meaning it lacks the functionType prefix like "get" or
+ * "unset").
+ *
+ * The actual LazyJsonMapper project uses FunctionTranslation objects, and
+ * normal users can be expected to need around 100 cache entries to cover every
+ * property they use in their project.
+ *
+ * That's ~25kb of RAM on PHP7 or ~51kb on PHP5. ;-)
+ */
+
+function randWords(
+ array $allWords)
+{
+ global $allWords;
+
+ // pick 1-3 words
+ $keys = array_rand($allWords, mt_rand(2, 4));
+ array_shift($keys);
+
+ // ensure that they are all lowercase, with an uppercase first letter
+ $words = [];
+ foreach ($keys as $k) {
+ $w = ucfirst(preg_replace('/[^a-z]+/', 'x', strtolower($allWords[$k])));
+ // The wordlist has many insanely long words...
+ // Limit the length to 2-5 chars per word (JSON programmers are terse)
+ $w = substr($w, 0, mt_rand(2, 5));
+ $words[] = $w;
+ }
+
+ return $words;
+}
+function randFunctionName(
+ array $allWords)
+{ // Generates a valid "realistic" function name
+ // Commented out because we no longer use the function type as part of the
+ // parsing of FuncCase names. So we don't need it in the test-data.
+ // $functionType = ['has', 'get', 'set', 'is', 'unset'][mt_rand(0, 4)];
+ // return $functionType.implode(randWords($allWords));
+
+ return implode(randWords($allWords));
+}
+function buildFuncList(
+ array $allWords,
+ $count = 500)
+{ // Generates a list of unique functions.
+ if (count($allWords) < 100) {
+ die('Not enough words...');
+ }
+ $funcList = [];
+ while (count($funcList) < $count) {
+ $funcList[randFunctionName($allWords)] = true;
+ }
+
+ return array_keys($funcList);
+}
+
+if (!$usePrebuiltFuncList) {
+ if (!is_file('/usr/share/dict/words')) {
+ die('Dictionary file missing.');
+ }
+ $allWords = @file('/usr/share/dict/words');
+
+ // build a list of $funcCacheCount amount functions that we'll put in a lookup cache
+ echo "- creating a list of {$funcCacheCount} random function names...\n";
+ $funcList = buildFuncList($allWords, $funcCacheCount);
+
+ // debug/list generation:
+ // var_dump($funcList); // uncomment to see quality of name generation
+ // file_put_contents(__DIR__.'/../funcListData.serialized', serialize($funcList));
+
+ echo "- function list built... running cache test...\n";
+} else {
+ if (!is_file(__DIR__.'/../funcListData.serialized')) {
+ die('No serialized function list.');
+ }
+ $funcList = unserialize(file_get_contents(__DIR__.'/../funcListData.serialized'));
+}
+
+// force autoloading of the class to prevent counting the class itself in mem
+$x = new FunctionTranslation('Example');
+
+// try storing them as FunctionTranslation objects
+$holder = [];
+foreach ($funcList as $funcCase) {
+ $newFuncCase = $funcCase.'x'; // Avoid variable re-use of incoming string.
+ $holder[$newFuncCase] = new FunctionTranslation($newFuncCase);
+ unset($newFuncCase);
+}
+// var_dump($holder); // warning: don't uncomment while testing; increases mem
+$mem = memory_get_usage();
+unset($holder);
+$totalmem = $mem - memory_get_usage();
+$indivmem = $totalmem / count($funcList); // includes the parent array overhead
+printf("PHP%d: Array of %dx FunctionTranslation objects: %d bytes total, ~%.1f bytes per entry.\n", $hasSeven ? 7 : 5, count($funcList), $totalmem, $indivmem);
+
+// try storing them as a regular non-associative array
+$holder = [];
+foreach ($funcList as $funcCase) {
+ $newFuncCase = $funcCase.'y'; // Avoid variable re-use of incoming string.
+ $translation = new FunctionTranslation($newFuncCase);
+ $y = [
+ // paranoid about PHP re-using the object's value, so let's tweak all:
+ substr($translation->snakePropName, 0, -1).'y',
+ $translation->camelPropName === null ? null : substr($translation->camelPropName, 0, -1).'y',
+ ];
+ $holder[$newFuncCase] = $y;
+ unset($translation);
+ unset($newFuncCase);
+ unset($y);
+}
+// var_dump($holder); // warning: don't uncomment while testing; increases mem
+$mem = memory_get_usage();
+unset($holder);
+$totalmem = $mem - memory_get_usage();
+$indivmem = $totalmem / count($funcList); // includes the parent array overhead
+printf("PHP%d: Array of %dx numerically indexed arrays: %d bytes total, ~%.1f bytes per entry.\n", $hasSeven ? 7 : 5, count($funcList), $totalmem, $indivmem);
+
+var_dump('---------------------');
+
+// test cache clearing and the memory usage of each cache from this test-file.
+$mem = memory_get_usage();
+$lookupCount = LazyJsonMapper::clearGlobalMagicLookupCache();
+printf("Saved %d bytes by clearing the magic function lookup cache, which contained %d function name translations.\n", $mem - memory_get_usage(), $lookupCount);
+
+$mem = memory_get_usage();
+$classCount = LazyJsonMapper::clearGlobalPropertyMapCache();
+printf("Saved %d bytes by clearing %d compiled class maps. But not all may have been freed from memory by PHP yet, if any class instance variables are still in scope.\n", $mem - memory_get_usage(), $classCount);
+
+var_dump('---------------------');
+
+// perform lots of tests of the array converter:
+
+// assign the normal json data array, but do not recursively validate (convert)
+// it since we want a mix of converted and unconverted data during this test...
+$x = new Test($jsonData);
+
+//
+// the magic: asArray() CLONES the internal data, then recursively validates all
+// of it and then converts it back to a plain array. the result is therefore
+// fully validated/type-converted as a side-effect of the conversion process.
+//
+// it does not touch the contents of the original object:
+//
+$x->getSelfObject(); // force self_object to evaluate and parse
+var_dump($x); // look at raw data... nothing is parsed except self_object
+
+$asArray = $x->asArray();
+
+// look at the original object... still nothing is parsed except self_object,
+// which remains obj, with the exact same instance number. this verifies that
+// asArray did not manipulate/destroy data in our object.
+var_dump($x);
+
+// validate the asArray result for correctness:
+// $asArray[] = 'x'; // uncomment this to trigger a mismatch below
+// var_dump($asArray); // look at asarray contents
+printf("The asArray() result matches original input array? %s\n",
+ // NOTE: Array === operator checks all keys, keytypes, key order, values,
+ // valuetypes and counts recursively. If true, arrays contain IDENTICAL.
+ ($asArray === $jsonData ? 'YES!' : 'No...'));
+
+// try tweaking the input data so that the class definition no longer matches:
+$jsonData['self_array'] = [$jsonData['self_array']]; // wrap in extra array depth
+$x = new Test($jsonData);
+
+try {
+ $asArray = $x->asArray();
+} catch (\Exception $e) {
+ printf("Trying asArray() with data that mismatches class map Exception: %s\n", $e->getMessage());
+}
+$jsonData['self_array'] = $jsonData['self_array'][0]; // fix data again
+
+// try undefined/untyped (missing) field with acceptable basic non-object data:
+// acceptable basic data is: "int, float, string, bool, NULL" (and arrays of those).
+$jsonData['untyped_field_with_non_object'] = '123456foo';
+$x = new Test($jsonData);
+$asArray = $x->asArray();
+printf("As array with untyped/undefined missing but ok data: %s\n",
+ ($asArray === $jsonData ? 'YES!' : 'No...'));
+
+// try undefined/untyped (missing) field with a LazyJsonMapper object. this will
+// NOT be okay because untyped fields only allow basic PHP types mentioned above.
+// NOTE: This can NEVER happen via real json_decode() data. It is a test against
+// user's custom data arrays with bad values...
+$jsonData['untyped_field_with_lazy_object'] = new LazyJsonMapper(['inner_val' => '123foo']);
+
+try {
+ $x = new Test($jsonData, true); // true = run with validation
+} catch (\Exception $e) {
+ printf("Test construction with validation enabled, and having an illegal value (object) in an undefined property Exception: %s\n", $e->getMessage());
+}
+// try with non-LazyJsonMapper object too in a different property (will fail too
+// since ALL OBJECTS are forbidden in undefined/untyped properties):
+$jsonData['untyped_field_with_bad_object'] = new \stdClass();
+$x = new Test($jsonData); // now construct it WITHOUT validation, so the illegal
+ // value is undetected...
+try {
+ $asArray = $x->asArray();
+} catch (\Exception $e) {
+ // should warn about BOTH the lazy and the "bad" object:
+ printf("Test asArray() on previously unvalidated object containing illegal values in data array Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $x->getUntypedFieldWithBadObject();
+} catch (\Exception $e) {
+ // should warn about BOTH the lazy and the "bad" object:
+ printf("Test getUntypedFieldWithBadObject() on previously unvalidated object with illegal value in that field Exception: %s\n", $e->getMessage());
+}
+
+// now remove the fake data value again to restore the original jsonData...
+unset($jsonData['untyped_field_with_lazy_object']);
+unset($jsonData['untyped_field_with_bad_object']);
+
+$x = new Test($jsonData);
+$asArray = $x->asArray(); // works again since all bad data is gone!
+var_dump($asArray);
+
+// now try type-conversion to ensure that the type-map is followed:
+class ForceType extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'arr' => 'float[]',
+ ];
+}
+$x = new ForceType(['arr' => ['1', '232', '94.2', 123.42]]);
+var_dump($x->asArray()); // all are floats, exactly as the class-map requested
+var_dump($x); // and as usual... internal _objectData remains untouched.
+
+// try non-integer arguments
+try {
+ $x->asJson(false);
+} catch (\Exception $e) {
+ printf("Test asJson() with non-int arg1: %s\n", $e->getMessage());
+}
+
+try {
+ $x->asJson(0, false);
+} catch (\Exception $e) {
+ printf("Test asJson() with non-int arg2: %s\n", $e->getMessage());
+}
+
+// try requesting a json data depth that is way too low for the data:
+try {
+ $x->asJson(0, 1);
+} catch (\Exception $e) {
+ printf("Test asJson() with too low depth parameter Exception: %s\n", $e->getMessage());
+}
+
+// and for fun...
+var_dump($x->asArray());
+var_dump($x->asJson());
+var_dump($x->asJson(JSON_PRETTY_PRINT));
+$x->printJson();
+
+var_dump('---------------------');
+
+$x = new Test($jsonData);
+
+// ensure that "convert object to string" works and properly outputs JSON...
+echo '['.$x."]\n\n";
+echo $x;
+echo PHP_EOL;
+
+// and test invalid data being output as with <> brackets as intended:
+$bad = new Test(['self_object' => 1]);
+echo PHP_EOL.'Test of clearly bad input data error handling as message string (since __toString cannot throw): '.$bad.PHP_EOL;
+
+var_dump('---------------------');
+
+// try unsetting properties from the internal JSON data tree:
+$x = new Test($jsonData);
+$x->printJson();
+$x->unsetSelfArray() // NOTE: This tests the "chained unsetter" feature too.
+ ->unsetCamelCaseProp()
+ ->setSelfObject(new Test(['just_a_string' => '123 new object!'])); // Tests chained setter together with unsetters.
+$x->printJson();
+$x->unsetSelfObject();
+$x->printJson();
+
+// now try reading a property and then unsetting and reading it again:
+var_dump($x->getStringArray());
+$x->unsetStringArray();
+var_dump($x->getStringArray());
+$x->printJson();
+
+// also try using the direct unset() on the remaining values
+unset($x->just_a_string);
+unset($x->untyped_field_with_non_object);
+$x->printJson();
+
+var_dump('---------------------');
+
+// Let's do some serialization tests:
+
+// First, run a recursive analysis to force all unparsed properties to evaluate
+// into creating inner LazyJsonMapper objects.
+$x = new Test($jsonData);
+$x->exportClassAnalysis();
+var_dump($x); // tree of objects
+
+// Now test the secret, internal "tight packing" serialization method which
+// returns the internal data as a plain array instead of as a serialized string:
+$secretArr = $x->serialize($x);
+var_dump($secretArr);
+
+// Now serialize the object into an actual, serialized string. The same way an
+// end-user would do it.
+// NOTE: This resolves all nested objects and serializes the root object with a
+// single serialized, plain array within it.
+$str = serialize($x);
+var_dump($str); // no nested serialized objects
+
+// test the ability to fake "unserialize" into re-constructing objects from any
+// serialized array. NOTE: this is just for testing proper re-building /
+// unserialization in a different way. users should never do this. it's dumb.
+// they should just create their "new TheClass([...])" instead.
+$fakeunserialize = new Test();
+var_dump($fakeunserialize); // empty _objectData
+$fakeunserialize->unserialize(serialize(['my_data' => 'hehe']));
+var_dump($fakeunserialize); // objectdata now has my_data
+
+// test exception when calling the function directly with bad params
+try {
+ $fakeunserialize->unserialize();
+ $fakeunserialize->unserialize(null);
+} catch (\Exception $e) {
+ printf("Test unserialize manual call with bad params Exception: %s\n", $e->getMessage());
+}
+
+// lastly, let's test real unserialization as a new object instance.
+// this creates a brand new object with the data array, and has no links to the
+// original object (except using the same shared, compiled classmap since we are
+// still in the same runtime and have a shared classmap cache entry available).
+$new = unserialize($str);
+// verify that all _objectData is there in the new object, and that unlike the
+// original object (which had exportClassAnalysis() to create inner objects),
+// this unserialized copy just has a plain data array:
+var_dump($new);
+// get a random property to cause it to convert it to its destination format:
+$new->getSelfArray();
+var_dump($new); // self_array is now an array of actual objects
+
+var_dump('---------------------');
+
+// test asArray/asStdClass which are aliases to exportObjectDataCopy
+var_dump($x->exportObjectDataCopy('array'));
+var_dump($x->asArray());
+// var_dump($x->exportObjectDataCopy('Array')); // test invalid type
+var_dump($x->exportObjectDataCopy('stdClass'));
+var_dump($x->asStdClass());
+
+var_dump('---------------------');
+
+// test new data assignment at a later time via assignObjectData():
+$foo = new Test(['just_a_string' => '123']);
+$foo->printPropertyDescriptions();
+$foo->printJson();
+$foo->assignObjectData(['camelCaseProp' => 999, 'string_array' => ['a', 'b', 'c']]);
+$foo->printJson();
+
+var_dump('---------------------');
+
+class TestJSONArrayKeys extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'array_of_strings' => 'string[]',
+ ];
+}
+
+// first, test the fact that objects must always be output as {} notation.
+$test = new TestJSONArrayKeys();
+var_dump($test->asJson());
+$test->printJson(); // {}
+$test->setArrayOfStrings(['a', 'b']);
+var_dump($test->asJson());
+$test->printJson(); // {"array_of_strings":["a","b"]}
+$test = new TestJSONArrayKeys(['a', 'b']);
+var_dump($test->asJson());
+$test->printJson(); // {"0":"a","1":"b"}
+
+// now do a test of the fact that properties defined as "array of" only allow
+// sequential, numerical keys.
+$test->setArrayOfStrings(['a', 'b']);
+$test->setArrayOfStrings(['0' => 'a', 'b']); // works because PHP converts "0" to int(0)
+$test->setArrayOfStrings([0 => 'a', 1 => 'b']); // correct order
+try {
+ $test->setArrayOfStrings([1 => 'a', 0 => 'b']); // bad order
+} catch (\Exception $e) {
+ printf("Test wrong order array keys, Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $test->setArrayOfStrings([4 => 'a', 5 => 'b']); // not starting at 0
+} catch (\Exception $e) {
+ printf("Test array keys not starting at 0, Exception: %s\n", $e->getMessage());
+}
+
+try {
+ $test->setArrayOfStrings(['a', 'b', 'foo' => 'b']); // string-key
+} catch (\Exception $e) {
+ printf("Test non-numeric array key in numeric array, Exception: %s\n", $e->getMessage());
+}
+
+var_dump('---------------------');
+
+// test that our forced-object-notation {} JSON output works in all cases (even
+// when strictly numeric keys or empty arrays).
+// the correct, intended format is: The outer container (the object) is always
+// {}, but any inner arrays in properties are [].
+
+$foo = new Test(); // no internal array assigned, so uses empty default array
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([1, [11, 22, 33], 3]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([0=>1, 1=>[11, 22, 33], 2=>3]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([0=>1, '1'=>[11, 22, 33], 2=>3]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([0=>1, 2=>3, 1=>[11, 22, 33]]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test([1, [11, 22, 33], 3, 'x'=>1]);
+var_dump($foo->asJson());
+$foo->printJson();
+$foo = new Test(['x'=>1, 1, [11, 22, 33], 3]);
+var_dump($foo->asJson());
+$foo->printJson();
+
+var_dump('---------------------');
+
+// now end with some nice data dumping tests on the final, large data object...
+// and let's use the newly unserialized object instance for fun..
+var_dump($new->asJson());
+var_dump($new->asJson(JSON_PRETTY_PRINT)); // manually controlling JSON output options
+$new->printPropertyDescriptions();
+echo str_repeat(PHP_EOL, 5);
+$new->printJson(false); // automatic printing but without pretty-print enabled
+echo str_repeat(PHP_EOL, 5);
+$new->getSelfObject()->printJson(); // printing a sub-object (only safe if obj is non-NULL)
+echo str_repeat(PHP_EOL, 5);
+// $new->getSelfArray()->printJson(); // would not work on PHP arrays, obviously
+$new->getSelfArray()[0]->printJson(); // works, since that array entry is an obj
+echo str_repeat(PHP_EOL, 5);
+$new->printJson(); // <-- Debug heaven! ;-)
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testPropertyDefinitionNamespaces.php b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testPropertyDefinitionNamespaces.php
new file mode 100755
index 0000000..7fc1a39
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testPropertyDefinitionNamespaces.php
@@ -0,0 +1,173 @@
+ 'string',
+ ];
+ }
+}
+
+namespace Foo\Deeper {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+
+ class NoExtendsClass
+ {
+ }
+
+ class MyClass extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = ['foo' => 'string'];
+ }
+}
+
+namespace Other\Space\VerySpace {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+
+ class VeryDeepInSpace extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = [
+ 'deepspacenine' => 'string',
+ ];
+ }
+}
+
+namespace Other\Space {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+ use LazyJsonMapper\Property\PropertyDefinition;
+
+ class OtherClass extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = [
+ 'from_other_class' => 'string',
+ ];
+ }
+
+ class MyClass extends LazyJsonMapper
+ {
+ const JSON_PROPERTY_MAP = [
+ 'foo' => 'int',
+ // tests handling of missing relative classes (the warning will
+ // display the namespace of this class which defined the property):
+ // 'missing_relative' => 'NoSuchClass', // uncomment to test
+ // tests support for relative classes within same namespace:
+ 'relative_class_path' => 'OtherClass',
+ 'relative_sub_class_path' => 'VerySpace\VeryDeepInSpace',
+ // and global overrides (the "\" prefix makes PropertyDefinition
+ // use the global namespace instead):
+ 'global_class_path' => '\Foo\Deeper\MyClass',
+ // just for fun, let's import a class map too, via relative:
+ // (can be done via global or relative paths)
+ VerySpace\VeryDeepInSpace::class,
+ ];
+ }
+
+ $resolved = new MyClass();
+ var_dump($resolved);
+}
+
+namespace Foo\Other\Space {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+
+ // This class is here to show that new $x->propType() construction technique
+ // is a bad idea since it may lead to relative resolving like this one, if
+ // the global path cannot be found.
+ class MyClass extends LazyJsonMapper
+ {
+ }
+}
+
+namespace Foo {
+ require __DIR__.'/../../vendor/autoload.php';
+
+ use LazyJsonMapper\LazyJsonMapper;
+ use LazyJsonMapper\Property\PropertyDefinition;
+
+ var_dump(\Other\Space\MyClass::class);
+ var_dump(class_exists('\Other\Space\MyClass'));
+ var_dump(class_exists('\Other\Space\\\MyClass'));
+ var_dump(Deeper\MyClass::class);
+ var_dump(__NAMESPACE__);
+
+ echo "-----\n";
+
+ // test various combinations of namespaces and class prefixes:
+ // $x = new PropertyDefinition('\MyClass', __NAMESPACE__);
+ // $x = new PropertyDefinition('\MyClass\Deeper', __NAMESPACE__);
+ // $x = new PropertyDefinition('MyClass', __NAMESPACE__);
+ // $x = new PropertyDefinition('MyClass\Deeper', __NAMESPACE__);
+ // $x = new PropertyDefinition('MyClass');
+ // $x = new PropertyDefinition('\MyClass');
+ // $x = new PropertyDefinition('MyClass\Deeper');
+ // var_dump($x);
+
+ // test a valid relative path (and the cleanup/normalization of a bad name).
+ $x = new PropertyDefinition('deePER\MYClass[][]', __NAMESPACE__);
+ var_dump($x);
+ var_dump($x->asString());
+ $y = new $x->propType(); // BAD! WE ALWAYS THE GLOBAL PATH, DO NOT USE THIS
+ // always use getStrictClassPath() instead!
+ var_dump($y); // \Foo\Deeper\MyClass instance
+
+ // test a valid path in other space (via global path)
+ $x = new PropertyDefinition('\Other\SPACe\MYCLASS[][]', __NAMESPACE__);
+ var_dump($x);
+ var_dump($x->asString());
+
+ $type = "Deeper\MyClass"; // PHP would resolve this locally due to no \
+ $y = new $type();
+ var_dump($y);
+
+ $type = "\Deeper\MyClass";
+ $y = new $type();
+ var_dump($y);
+
+ echo "------\n";
+ var_dump($x);
+ $y = new $x->propType(); // BAD IDEA! This field has no "\" prefix and may not
+ // resolve to the intended class in all situations
+ // correct way for extra safety is always:
+ $strictClassPath = $x->getStrictClassPath();
+ var_dump($strictClassPath);
+ $y = new $strictClassPath();
+
+ var_dump($y); // \Other\Space\MyClass instance
+
+ // test bad class warning (no extends)
+ // $x = new PropertyDefinition('deePER\noextendsCLASS', __NAMESPACE__);
+
+ // test bad class warning via mistyped basic typename:
+ // $x = new PropertyDefinition('ints', __NAMESPACE__);
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testUserFeatureToggling.php b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testUserFeatureToggling.php
new file mode 100755
index 0000000..e011523
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/junk/testUserFeatureToggling.php
@@ -0,0 +1,205 @@
+ 'string',
+ 'bar' => 'string',
+ ];
+}
+
+// --------------------------------------------------------
+
+// Test class that disallows functions but allows properties.
+
+class DisallowsFunctions extends CoreMap
+{
+ const ALLOW_VIRTUAL_FUNCTIONS = false;
+
+ public function getBar()
+ {
+ return $this->_getProperty('bar');
+ }
+}
+
+$jsonData = ['foo' => 'hello', 'bar' => 'world'];
+
+$x = new DisallowsFunctions($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+$x->printJson();
+
+// works since we have overridden that function manually
+printf("getBar(): \"%s\"\n", $x->getBar());
+
+// works since we allow direct property access in the class above
+$x->bar = 'changed via direct virtual property access';
+
+// look at the new value
+printf("getBar(): \"%s\"\n", $x->getBar());
+
+// does not work since we have no setter "setBar()" in the class above
+try {
+ $x->setBar('xyzzy');
+} catch (\Exception $e) {
+ printf("setBar(): %s\n", $e->getMessage());
+}
+
+// try all function variations of the "foo" property. none should work.
+try {
+ $x->hasFoo();
+} catch (\Exception $e) {
+ printf("hasFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->isFoo();
+} catch (\Exception $e) {
+ printf("isFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->getFoo();
+} catch (\Exception $e) {
+ printf("getFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->setFoo();
+} catch (\Exception $e) {
+ printf("setFoo(): %s\n", $e->getMessage());
+}
+
+try {
+ $x->unsetFoo();
+} catch (\Exception $e) {
+ printf("unsetFoo(): %s\n", $e->getMessage());
+}
+
+// --------------------------------------------------------
+
+// Test class that disallows properties but allows functions.
+
+class DisallowsProperties extends CoreMap
+{
+ const ALLOW_VIRTUAL_PROPERTIES = false;
+}
+
+$jsonData = ['foo' => 'hello', 'bar' => 'world'];
+
+$x = new DisallowsProperties($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+$x->printJson();
+
+// works since we allow functions
+printf("getBar(): \"%s\"\n", $x->getBar());
+$x->setBar('changed via virtual setBar() function acccess');
+
+// look at the new value
+printf("getBar(): \"%s\"\n", $x->getBar());
+
+// try all property acccess variations of the "foo" property. none should work.
+try {
+ $test = $x->foo;
+} catch (\Exception $e) {
+ printf("__get() via x->foo: %s\n", $e->getMessage());
+}
+
+try {
+ $x->foo[] = 'test'; // this __get()-trigger will fail too
+} catch (\Exception $e) {
+ printf("__get() via x->foo[]: %s\n", $e->getMessage());
+}
+
+try {
+ $x->foo = 'xyz';
+} catch (\Exception $e) {
+ printf("__set() via x->foo = ...: %s\n", $e->getMessage());
+}
+
+try {
+ isset($x->foo);
+} catch (\Exception $e) {
+ printf("__isset() via isset(x->foo): %s\n", $e->getMessage());
+}
+
+try {
+ empty($x->foo);
+} catch (\Exception $e) {
+ printf("__isset() via empty(x->foo): %s\n", $e->getMessage());
+}
+
+try {
+ unset($x->foo);
+} catch (\Exception $e) {
+ printf("__unset() via unset(x->foo): %s\n", $e->getMessage());
+}
+
+// --------------------------------------------------------
+
+// Test class that disallows both.
+
+class DisallowsBoth extends CoreMap
+{
+ const ALLOW_VIRTUAL_PROPERTIES = false;
+ const ALLOW_VIRTUAL_FUNCTIONS = false;
+}
+
+$x = new DisallowsBoth($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+
+try {
+ $test = $x->foo;
+} catch (\Exception $e) {
+ printf("__get() via x->foo: %s\n", $e->getMessage());
+}
+
+try {
+ $x->getFoo();
+} catch (\Exception $e) {
+ printf("getFoo(): %s\n", $e->getMessage());
+}
+
+// --------------------------------------------------------
+
+// Test class that extends "DisallowsBoth" and re-allows both.
+
+class ReallowsBoth extends DisallowsBoth
+{
+ const ALLOW_VIRTUAL_PROPERTIES = true;
+ const ALLOW_VIRTUAL_FUNCTIONS = true;
+}
+
+$x = new ReallowsBoth($jsonData);
+
+echo str_repeat(PHP_EOL, 5);
+$x->printPropertyDescriptions();
+
+printf("getFoo(): \"%s\"\n", $x->getFoo());
+printf("x->bar: \"%s\"\n", $x->bar);
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/devtools/nonRecursiveArrays.php b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/nonRecursiveArrays.php
new file mode 100755
index 0000000..0b07092
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/nonRecursiveArrays.php
@@ -0,0 +1,402 @@
+current());
+ }
+}
+
+// Algorithm v1: The initial idea I had...
+function array_flat_topdown_traverse(
+ array &$input)
+{
+ // Traverse top-down, processing level by level (going deeper and deeper).
+ $workStack = [&$input]; // The stack processes one array level at a time.
+ $nextStack = []; // Next stack with all deeper arrays found on this level.
+ $currentDepth = 1; // First level of input array should count from 1.
+ while (!empty($workStack)) {
+ // Pop a direct reference off the start of our FIFO stack.
+ reset($workStack);
+ $firstKey = key($workStack);
+ $pointer = &$workStack[$firstKey];
+ unset($workStack[$firstKey]);
+
+ // Now we're ready to act on the popped stack element...
+ foreach ($pointer as $k => &$v) {
+ // printf(
+ // "[D] %d %s\"%s\":%s\n",
+ // $currentDepth,
+ // str_repeat('-', $currentDepth),
+ // $k,
+ // is_array($v) ? '[]' : var_export($v, true)
+ // );
+
+ // Analyze the current array-child...
+ if (is_array($v)) {
+ // Add the discovered child-array to the end of the next-stack.
+ $nextStack[] = &$v;
+ } else {
+ // The child is a non-array element... Send it to the callback!
+ // TODO: Give callback key + value ref + array depth
+ }
+ }
+
+ // If the work-stack is finished, switch to the next (deeper) stack.
+ if (empty($workStack)) {
+ $workStack = $nextStack;
+ $nextStack = [];
+ $currentDepth++;
+ }
+ }
+}
+
+// Algorithm v2: Avoids two count() calls per stack-element iteration.
+function array_flat_topdown_traverse2(
+ array &$input)
+{
+ // Traverse top-down, processing level by level (going deeper and deeper).
+ $workStack = [&$input]; // The stack processes one array level at a time.
+ $workStackSize = 1; // Hardcoded result of count($workStack).
+ $nextStack = []; // Next stack with all deeper arrays found on this level.
+ $currentDepth = 1; // First level of input array should count from 1.
+ while ($workStackSize > 0) {
+ // Pop a direct reference off the start of our FIFO stack.
+ reset($workStack);
+ $firstKey = key($workStack);
+ $pointer = &$workStack[$firstKey];
+ unset($workStack[$firstKey]);
+ $workStackSize--;
+
+ // Now we're ready to act on the popped stack element...
+ foreach ($pointer as $k => &$v) {
+ // printf(
+ // "[D] %d %s\"%s\":%s\n",
+ // $currentDepth,
+ // str_repeat('-', $currentDepth),
+ // $k,
+ // is_array($v) ? '[]' : var_export($v, true)
+ // );
+
+ // Analyze the current array-child...
+ if (is_array($v)) {
+ // Add the discovered child-array to the end of the next-stack.
+ $nextStack[] = &$v;
+ } else {
+ // The child is a non-array element... Send it to the callback!
+ // TODO: Give callback key + value ref + array depth
+ }
+ }
+
+ // If the work-stack is finished, switch to the next (deeper) stack.
+ if ($workStackSize <= 0) {
+ // NOTE: There's no need to assign to workStack by reference to
+ // avoid copy-on-write. Because when we set nextStack to a new
+ // value, PHP will realize that workStack is the only instance.
+ // In fact, by-ref is slower it also needs an unset($nextStack)
+ // call to break its own reference before doing $nextStack = [].
+ $workStack = $nextStack;
+ $workStackSize = count($workStack);
+ $nextStack = [];
+ $currentDepth++;
+ }
+ }
+}
+
+// Regular, old-school recursive function calls.
+function array_recursive_traverse(
+ array &$input,
+ $currentDepth = 1)
+{
+ // Recursion adds 1 level to the function call stack
+ // per depth-level of the array:
+ // debug_print_backtrace();
+
+ $nextDepth = $currentDepth + 1;
+ foreach ($input as $k => &$v) {
+ // printf(
+ // "[D] %d %s\"%s\":%s\n",
+ // $currentDepth,
+ // str_repeat('-', $currentDepth),
+ // $k,
+ // is_array($v) ? '[]' : var_export($v, true)
+ // );
+
+ if (is_array($v)) {
+ array_recursive_traverse($v, $nextDepth);
+ }
+ }
+}
+
+// Build an array data tree.
+function generateData(
+ $depth)
+{
+ $data = [];
+ $pointer = &$data;
+ for ($d = 0; $d < $depth; ++$d) {
+ // $subArr = ['x', 'y', ['z'], ['foo'], [['xxyyzzyy']]]; // Harder data.
+ $subArr = ['x', 'y', 'z', ['foo'], 'xxyyzzyy'];
+ $pointer[] = &$subArr;
+ $pointer = &$subArr;
+ unset($subArr); // Unlink, otherwise next assignment overwrites pointer.
+ }
+
+ return $data;
+}
+
+// Run a single test.
+function runTest(
+ $description,
+ $data,
+ $algorithm,
+ $iterations)
+{
+ $start = microtime(true);
+
+ switch ($algorithm) {
+ case 'array_flat_topdown_traverse':
+ for ($i = 0; $i < $iterations; ++$i) {
+ array_flat_topdown_traverse($data);
+ }
+ break;
+ case 'array_flat_topdown_traverse2':
+ for ($i = 0; $i < $iterations; ++$i) {
+ array_flat_topdown_traverse2($data);
+ }
+ break;
+ case 'array_recursive_traverse':
+ for ($i = 0; $i < $iterations; ++$i) {
+ array_recursive_traverse($data);
+ }
+ break;
+ case 'RecursiveIteratorIterator':
+ for ($i = 0; $i < $iterations; ++$i) {
+ $iterator = new \RecursiveIteratorIterator(
+ new RecursiveArrayOnlyIterator($data),
+ \RecursiveIteratorIterator::SELF_FIRST
+ );
+ // foreach ($iterator as $key => $value) {
+ // // echo "$key => $value\n";
+ // }
+ // This iteration method takes 15% longer than foreach,
+ // but it's the only way to get the depth, which we
+ // absolutely need to know in this project.
+ for (; $iterator->valid(); $iterator->next()) {
+ $key = $iterator->key();
+ $value = $iterator->current();
+ $depth = $iterator->getDepth();
+ }
+ }
+ break;
+ }
+
+ printf(
+ "%dx %s %s: %.0f milliseconds.\n",
+ $iterations, $description, $algorithm,
+ 1000 * (microtime(true) - $start)
+ );
+}
+
+// Run all algorithm tests at once.
+function runTestMulti(
+ $description,
+ $data,
+ $iterations,
+ $iteratorTestMode) // Time-saver: -1 off, 0 divide by ten, 1 normal
+{
+ if ($iteratorTestMode > -1) {
+ runTest($description, $data, 'RecursiveIteratorIterator',
+ $iteratorTestMode > 0 ? $iterations : (int) floor($iterations / 10));
+ }
+ runTest($description, $data, 'array_flat_topdown_traverse', $iterations);
+ runTest($description, $data, 'array_flat_topdown_traverse2', $iterations);
+ runTest($description, $data, 'array_recursive_traverse', $iterations);
+}
+
+// Special data test-tree for use together with debug-output (uncomment it in
+// the algorithms), to verify that each algorithm detects the current depth.
+$data = [
+ '1one' => [
+ '1two' => [
+ '1three-nonarr1' => '1',
+ '1three' => [
+ '1four-nonarr1' => '2',
+ '1four' => [
+ '1five' => '3',
+ ],
+ ],
+ ],
+ ],
+ '2one-nonarr1' => null,
+ '3one' => [
+ '3two-1' => [
+ '3three-nonarr1' => '4',
+ '3three-1' => [
+ '3four-1' => [
+ '3five-1' => [
+ '3six-nonarr1' => '5',
+ ],
+ ],
+ ],
+ '3three-nonarr2' => '6',
+ ],
+ '3two-nonarr1' => '7',
+ '3two-2' => [
+ '3three-nonarr3' => '8',
+ ],
+ '3two-nonarr2' => '9',
+ ],
+];
+
+// The "RecursiveIteratorIterator" is ~10x slower, so this setting saves time.
+// Values: -1 off, 0 divide by ten, 1 normal.
+$iteratorTestMode = -1;
+
+// Globally extend/shorten the amount of test iterations, or "1" for no scaling.
+$testScale = 1;
+
+// Output PHP version details.
+printf("[Running %dx tests on PHP version %s]\n", $testScale, PHP_VERSION);
+printf("[RecursiveIteratorIterator Tests: %s]\n", ['Disabled', 'Shortened by /10', 'Enabled'][$iteratorTestMode + 1]);
+
+// Test with normal data (6 levels deep).
+runTestMulti('normal-6', generateData(6), $testScale * 500000, $iteratorTestMode);
+
+// Test unusual data (50 levels deep).
+runTestMulti('rare-50', generateData(50), $testScale * 100000, $iteratorTestMode);
+
+// Now test with insanely deeply nested data.
+runTestMulti('insane-500', generateData(500), $testScale * 10000, $iteratorTestMode);
+
+// Let's do one final test with even more disgustingly deep arrays.
+runTestMulti('hellish-5000', generateData(5000), $testScale * 100, $iteratorTestMode);
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/devtools/prefixSplitAlgorithms.php b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/prefixSplitAlgorithms.php
new file mode 100755
index 0000000..8c0acfc
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/devtools/prefixSplitAlgorithms.php
@@ -0,0 +1,270 @@
+ 'string',
+ 'users' => 'User[]',
+ ];
+}
+
+class User extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'name' => 'string',
+ 'details' => 'UserDetails',
+ ];
+}
+
+class UserDetails extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ 'age' => 'int',
+ 'hired_date' => 'string',
+ ];
+}
+
+/*
+ * Now simply create the root object (a Section), and give it the section JSON:
+ */
+
+$section = new Section(json_decode($section_json, true));
+
+/*
+ * Here's how you would look at the available properties and their functions:
+ */
+
+$section->printPropertyDescriptions();
+
+/*
+ * Now let's access some data, via the functions shown by the previous command.
+ */
+
+printf("\n\nData Output Example:\n\nSection Title: %s\n", $section->getSectionTitle());
+foreach ($section->getUsers() as $user) {
+ // $user->printPropertyDescriptions(); // Uncomment to see User-functions.
+ // $user->printJson(); // Uncomment to look at the JSON data for that user.
+ printf(
+ "- User: %s\n Age: %s\n Hired Date: %s\n",
+ $user->getName(),
+ $user->getDetails()->getAge(),
+ $user->getDetails()->getHiredDate()
+ );
+}
+echo "\n\n";
+
+/*
+ * Lastly, let's demonstrate looking at the actual internal JSON data:
+ */
+
+// var_dump($section->asJson()); // Uncomment to get a JSON data string instead.
+// var_dump($section->getUsers()[0]->asJson()); // Property sub-object encoding.
+// var_dump(json_encode($section->getUsers())); // Property non-object values
+// // solvable via `json_encode()`.
+$section->printJson();
+
+/*
+ * There are a million other functions and features. Have fun exploring!
+ * Simply read the main src/LazyJsonMapper.php file for all documentation!
+ */
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/examples/import_example.php b/vendor/lazyjsonmapper/lazyjsonmapper/examples/import_example.php
new file mode 100755
index 0000000..50d7014
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/examples/import_example.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'age' => 'int',
+ ];
+}
+
+class AdvancedUser extends User
+{
+ const JSON_PROPERTY_MAP = [
+ 'advanced' => 'string',
+ ];
+}
+
+class SomethingElse extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ AdvancedUser::class, // This is an "import class map"-command.
+ 'otherprop' => 'float[][]',
+ ];
+}
+
+/*
+ * This demonstrates that SomethingElse contains all fields from AdvancedUser
+ * (which in turn inherited User's map), as well as having its own "otherprop".
+ */
+
+$somethingelse = new SomethingElse();
+$somethingelse->printPropertyDescriptions();
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/examples/inheritance_example.php b/vendor/lazyjsonmapper/lazyjsonmapper/examples/inheritance_example.php
new file mode 100755
index 0000000..899c187
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/examples/inheritance_example.php
@@ -0,0 +1,51 @@
+ 'string',
+ 'age' => 'int',
+ ];
+}
+
+class AdvancedUser extends User
+{
+ const JSON_PROPERTY_MAP = [
+ 'advanced' => 'string',
+ ];
+}
+
+/*
+ * This demonstrates that AdvancedUser contains all fields from User.
+ */
+
+$advanceduser = new AdvancedUser(json_decode($advanceduser_json, true));
+
+$advanceduser->printPropertyDescriptions();
+printf(
+ "\n\nName: %s\nAge: %s\nAdvanced: %s\n",
+ $advanceduser->getName(),
+ $advanceduser->getAge(),
+ $advanceduser->getAdvanced()
+);
+$advanceduser->printJson();
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/examples/unpredictable_data.php b/vendor/lazyjsonmapper/lazyjsonmapper/examples/unpredictable_data.php
new file mode 100755
index 0000000..2d366a5
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/examples/unpredictable_data.php
@@ -0,0 +1,705 @@
+ 'string',
+ ];
+}
+
+/**
+ * This container supports unpredictable keys.
+ *
+ * Note that it doesn't define any properties in its JSON_PROPERTY_MAP. Instead,
+ * we use a custom getter which reads _all_ JSON properties and processes them!
+ *
+ * In other words, this _entire_ container will hold nothing but unpredictable
+ * data keys. (If you have a mixture, there are other examples further down.)
+ */
+class UnpredictableContainer extends LazyJsonMapper
+{
+ /**
+ * Cached key-value object translations.
+ *
+ * This is optional, but speeds up repeated calls to `getUserList()`, since
+ * it will store all previously converted objects for future re-use.
+ *
+ * @var array
+ */
+ protected $_userList;
+
+ /**
+ * Get the list of users.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return array Associative array of key-value pairs, keyed by userID.
+ */
+ public function getUserList()
+ {
+ // Tell LazyJsonMapper to give us all of our internal data as an array.
+ // NOTE: This creates a COPY of the array. It's not attached to the main
+ // storage, which means that any changes we make to the new array aren't
+ // going to affect the actual LazyJsonMapper object's own data. That
+ // won't matter for pure getter-based objects like this one (which is
+ // all you'll need in 99.9999999% of cases where you'll want to read
+ // unpredictable data). So if the user serializes this LazyJsonMapper
+ // object, or gets it "asJson()", etc, then they'd still be serializing
+ // the original, untouched internal data, which is exactly what we are
+ // retrieving here. So everything works out perfectly as long as we
+ // don't have any setters! (But setters will be demonstrated later.)
+ if ($this->_userList === null) {
+ // Get a copy of the internal data as an array, and cache it.
+ // NOTE: This only throws if there is unmappable data internally.
+ $this->_userList = $this->asArray(); // Throws.
+ }
+
+ // Loop through the list of JSON properties and convert every "array"
+ // value to a User object instead. That's because all of JSON's nested,
+ // not-yet-converted "JSON object values" are associative sub-arrays.
+ foreach ($this->_userList as &$value) {
+ if (is_array($value)) {
+ // NOTE: The User constructor can only throw if a custom _init()
+ // fails or if its class property map can't be compiled.
+ $value = new User($value); // Throws.
+ }
+ }
+
+ // Now just return our key-value array. The inner array values have
+ // been converted to User objects, which makes them easy to work with.
+ return $this->_userList;
+ }
+}
+
+/*
+ * Let's try it out with two sets of data that use unpredictable (numeric) keys!
+ */
+
+/*
+ * This JSON data consists of various numeric keys pointing at "User"-objects.
+ */
+$unpredictable_json1 = <<printJson();
+
+/*
+ * Now let's call our custom getter which retrieves all values and gives them to
+ * us as User objects. As you can see, their contents perfectly match the
+ * printJson() results, since our custom getter doesn't manipulate any data.
+ */
+foreach ($unpredictable1->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Let's do the same for another set of data with other keys...
+ */
+$unpredictable_json2 = <<printJson();
+
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Alright, that's all great... but what if we want to manipulate the data?
+ *
+ * Well, the loop above still has $userId and $userInfo variables that point at
+ * the last element it looped to... so let's try using those to set data! What
+ * can possibly go wrong!? ;-)
+ */
+printf("*** Changing the name of user #%s to 'FOO'.\n", $userId);
+$userInfo->setName('FOO');
+
+/*
+ * Now let's look at the contents of the "getUserList()" call...
+ *
+ * Because the user-list is cached internally in our custom object, it already
+ * refers to the exact same object instances... So it will indeed have updated.
+ */
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * But wait... what about the actual internal object data? Let's look at that!
+ *
+ * The name... is not updated...
+ */
+$unpredictable2->printJson();
+
+/*
+ * Uh oh... since we're using a custom getter which fetches the data totally
+ * detached from the core LazyJsonMapper storage, we will need another solution!
+ *
+ * These extra steps are ONLY needed if you want setters that actually work...
+ */
+
+/**
+ * Extends UnpredictableContainer with a custom setter.
+ *
+ * We could have put these functions on the main UnpredictableContainer, but
+ * this example is clearer by only explaining it here as a separate step for
+ * those who need setters...
+ */
+class UnpredictableContainerWithSetter extends UnpredictableContainer
+{
+ /**
+ * Syncs the user list cache with the LazyJsonMapper core storage.
+ *
+ * Note that we could build these steps into `setUserList()`. But by having
+ * it as a separate function, you are able to just update specific User
+ * objects and then call `syncUserList()` to write them back to the core.
+ *
+ * @throws LazyJsonMapperException
+ */
+ public function syncUserList()
+ {
+ // If no internal cache exists yet, get its value from LazyJsonMapper.
+ if ($this->_userList === null) {
+ $this->getUserList(); // Builds our "_userList" variable. Throws.
+ }
+
+ // Now, we need to create a new, internal LazyJsonMapper data array for
+ // our object. In undefined (unmapped) properties, you are ONLY allowed
+ // to store basic data types. Not objects. So we will need to convert
+ // all User objects to real array data. Otherwise OTHER calls would fail
+ // due to invalid internal data when we try things like `printJson()`.
+ $newObjectData = [];
+ foreach ($this->_userList as $k => $v) {
+ $newObjectData[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ // Now give the new object data to LazyJsonMapper, which ensures that it
+ // contains all of the same values as our updated cache! This replaces
+ // the ENTIRE internal JSON property storage, so be aware of that!
+ $this->assignObjectData($newObjectData); // Throws.
+ }
+
+ /**
+ * Replace the entire user list with another list.
+ *
+ * @param array $userList Associative array of User objects keyed by userID.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setUserList(
+ array $userList)
+ {
+ // First of all, let's instantly save their new value to our own
+ // internal cache, since our cache supports User objects and doesn't
+ // have to do any special transformations...
+ $this->_userList = $userList;
+
+ // Now sync our internal cache with the LazyJsonMapper object...! :-)
+ $this->syncUserList(); // Throws.
+
+ // Setters should always return "$this" to make them chainable!
+ return $this;
+ }
+}
+
+/*
+ * Alright, let's try the example again, with the new container that has proper
+ * support for setters!
+ */
+echo "\n\nUnpredictable data #2, with setter:\n";
+$unpredictable2 = new UnpredictableContainerWithSetter(json_decode($unpredictable_json2, true));
+
+$unpredictable2->printJson();
+
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Now let's try manipulating the data again!
+ */
+printf("*** Changing the name of user #%s to 'FOO'.\n", $userId);
+$userInfo->setName('FOO');
+
+/*
+ * Now let's look at the contents of the "getUserList()" call...
+ *
+ * Just as before, it has been updated since we use an internal cache which
+ * already refers to the exact same object instances... so our update is there!
+ */
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+
+/*
+ * Now let's look at the internal LazyJsonMapper data storage...
+ *
+ * It is NOT updated. As expected...
+ */
+$unpredictable2->printJson();
+
+/*
+ * Now let's SYNC our cache back to the internal LazyJsonMapper data storage!
+ *
+ * And voila...!
+ */
+$unpredictable2->syncUserList();
+$unpredictable2->printJson();
+
+/*
+ * Let's also try our custom setter, which takes a whole array of User objects
+ * and does the syncing automatically!
+ */
+$users = $unpredictable2->getUserList();
+foreach ($users as $userId => $userInfo) {
+ $userInfo->setName('Updated...'.$userId.'!');
+}
+$unpredictable2->setUserList($users); // Replaces entire cache and syncs!
+
+/*
+ * Now let's look at the contents of our cache AND the LazyJsonMapper data!
+ *
+ * Everything has been updated, since our "setUserList()" call replaced the
+ * entire cache contents AND synced the new cache contents to the core storage.
+ */
+foreach ($unpredictable2->getUserList() as $userId => $userInfo) {
+ printf("User ID: %s\n Name: %s\n", $userId, $userInfo->getName());
+}
+$unpredictable2->printJson();
+
+/**
+ * This class handles a mixture of knowable and unknowable data.
+ *
+ * Let's end this with one more example... What if you DON'T want to define a
+ * whole custom container? What if you only want a SPECIFIC value within your
+ * map to be handling unpredictable keys? You could achieve that as follows!
+ *
+ * This class will contain less comments, for brevity. You hopefully understand
+ * all of the major workflow concepts by now! We will only explain new concepts.
+ * And this class won't show any `syncEmployees()` function, since you should
+ * understand how to do that now if you want that feature.
+ */
+class UnpredictableMixtureContainer extends LazyJsonMapper
+{
+ protected $_employees;
+
+ const JSON_PROPERTY_MAP = [
+ // This is a statically named "manager" property, which is a User.
+ 'manager' => 'User',
+ // This is a statically named "employees" property, which consists of
+ // unpredictable key-value pairs, keyed by userID.
+ 'employees' => 'mixed', // Must be 'mixed' (aka '') to allow sub-arrays.
+ ];
+
+ /**
+ * Get the list of employees.
+ *
+ * NOTE: This overrides the normal, automatic LazyJsonMapper getter! By
+ * naming our function identically, we override its behavior!
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return array
+ */
+ public function getEmployees()
+ {
+ if ($this->_employees === null) {
+ // Use the internal _getProperty() API to read the actual property.
+ // NOTE: This function only throws if "employees" contains any
+ // invalid non-basic data. Only basic PHP types are accepted.
+ $this->_employees = $this->_getProperty('employees'); // Throws.
+ }
+
+ foreach ($this->_employees as &$value) {
+ if (is_array($value)) {
+ $value = new User($value); // Throws.
+ }
+ }
+
+ return $this->_employees;
+ }
+
+ /**
+ * Set the list of employees.
+ *
+ * NOTE: This overrides the normal, automatic LazyJsonMapper setter! By
+ * naming our function identically, we override its behavior!
+ *
+ * @param array $employees
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setEmployees(
+ array $employees)
+ {
+ $this->_employees = $employees;
+
+ // We now need to construct a new, inner value for the property. Since
+ // it's a "mixed" property, it only accepts basic PHP types.
+ $newInnerValue = [];
+ foreach ($this->_employees as $k => $v) {
+ $newInnerValue[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ // Now use the internal _setProperty() API to set the new inner value.
+ // NOTE: This function only throws if the new value contains any
+ // invalid non-basic data. Only basic PHP types are accepted.
+ $this->_setProperty('employees', $newInnerValue);
+
+ return $this;
+ }
+}
+
+/*
+ * Let's try it out!
+ */
+$unpredictable_json3 = <<printJson();
+printf("The manager's name is: %s\n", $unpredictable3->getManager()->getName());
+foreach ($unpredictable3->getEmployees() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * Now let's set some new employee data... This time, just for fun, let's give
+ * it a virtual JSON array which we construct manually.
+ */
+$unpredictable3->setEmployees([
+ // Let's provide one value as a User object, since our setter supports
+ // the conversion of User objects back into plain arrays.
+ '10' => new User(['name' => 'Employee Ten']),
+ // However, we could also just provide the data as an array, since that's
+ // the goal that our setter performs... this is for really advanced users.
+ // Don't blame us if you break things by using this shortcut! ;-)
+ '11' => ['name' => 'Employee Eleven'],
+]);
+
+/*
+ * Let's also update the manager, which is done via a normal, automatic
+ * LazyJsonMapper setter, since that property is completely defined. And since
+ * the "manager" object is owned by the LazyJsonMapper core, we're able to chain
+ * its getters and setters to select the manager's User object and set its name!
+ */
+$unpredictable3->getManager()->setName('New Manager!');
+
+/*
+ * Now let's look at all current data again!
+ */
+$unpredictable3->printJson();
+printf("The manager's name is: %s\n", $unpredictable3->getManager()->getName());
+foreach ($unpredictable3->getEmployees() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * But wait... there's yet another way to solve this!
+ *
+ * Since we know that our "mixture container"'s `employees` value is a key-value
+ * storage of User objects, in other words it's "an unpredictable key-value
+ * container", then we CAN just tell LazyJsonMapper to map that property to a
+ * `UnpredictableContainer[WithSetter]` which we defined earlier. That way, the
+ * "employees" values are handled automatically by a neat sub-container.
+ *
+ * In fact, we could do something even better! We could define a basic
+ * "unpredictable keys" container, and then define subclasses of it for various
+ * types of unpredictable containers. Let's do that instead!
+ */
+
+/**
+ * This class defines a core "untyped" container of unpredictable data-keys.
+ *
+ * Unpredictable data is data with keys that cannot be known ahead of time, such
+ * as objects whose values are keyed by things like user IDs.
+ *
+ * Here's an example of such unpredictable data: `{"9323":{"name":"foo"}}`
+ *
+ * The `getData()` function retrieves all key-value pairs, converted to the
+ * optional `$_type` (if one is set via a subclass). And `setData()` writes
+ * the new data back into the core `LazyJsonMapper` container. Most people will
+ * not need to use the setter. It's just provided as an extra feature.
+ *
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class CoreUnpredictableContainer extends LazyJsonMapper
+{
+ // Let's disable direct access to this container via anything other than
+ // the functions that WE define ourselves! That way, people cannot use
+ // virtual properties/functions to manipulate the core data storage.
+ const ALLOW_VIRTUAL_PROPERTIES = false;
+ const ALLOW_VIRTUAL_FUNCTIONS = false;
+
+ /**
+ * Data cache to avoid constant processing every time the getter is used.
+ *
+ * @var array
+ */
+ protected $_cache;
+
+ /**
+ * What class-type to convert all sub-object values into.
+ *
+ * Defaults to no conversion. Override this value via a subclass!
+ *
+ * Always use the FULL path to the target class, with a leading backslash!
+ * The leading backslash ensures that it's found via a strict, global path.
+ *
+ * Example: `\Foo\BarClass`.
+ *
+ * @var string
+ */
+ protected $_type;
+
+ /**
+ * Get the data array of this unpredictable container.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ if ($this->_cache === null) {
+ $this->_cache = $this->asArray(); // Throws.
+ }
+
+ if ($this->_type !== null) {
+ foreach ($this->_cache as &$value) {
+ if (is_array($value)) {
+ $value = new $this->_type($value); // Throws.
+ }
+ }
+ }
+
+ return $this->_cache;
+ }
+
+ /**
+ * Set the data array of this unpredictable container.
+ *
+ * @param array $value The new data array.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setData(
+ array $value)
+ {
+ $this->_cache = $value;
+
+ $newObjectData = [];
+ foreach ($this->_cache as $k => $v) {
+ $newObjectData[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ $this->assignObjectData($newObjectData); // Throws.
+
+ return $this;
+ }
+}
+
+/**
+ * This class defines an "unpredictable container of User objects".
+ *
+ * It's very easy to define other containers. Simply create them like this and
+ * override their `$_type` property to any other valid LazyJsonMapper class.
+ */
+class UserUnpredictableContainer extends CoreUnpredictableContainer
+{
+ // The FULL path to the target class, with leading backslash!
+ // NOTE: The leading backslash ensures it's found via a strict, global path.
+ protected $_type = '\User';
+}
+
+/**
+ * This is our new and improved, final class!
+ *
+ * Here is our final object for mapping our unpredictable "employees" data...
+ * As you can see, it is much easier to create this class now that we have
+ * defined a core, re-usable "unpredictable container" above.
+ */
+class UnpredictableMixtureContainerTwo extends LazyJsonMapper
+{
+ const JSON_PROPERTY_MAP = [
+ // This holds a regular User object.
+ 'manager' => 'User',
+ // This property is an unpredictable container of User objets.
+ 'employees' => 'UserUnpredictableContainer',
+ ];
+}
+
+/*
+ * Let's try it out!
+ */
+$unpredictable_json4 = <<printJson();
+printf("The manager's name is: %s\n", $unpredictable4->getManager()->getName());
+foreach ($unpredictable4->getEmployees()->getData() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * And let's update the value of the inner, unpredictable container!
+ *
+ * The container itself takes care of updating the LazyJsonMapper data storage!
+ */
+$unpredictable4->getEmployees()->setData([
+ '123' => ['name' => 'Final Employee 123'],
+ '456' => ['name' => 'Final Employee 456'],
+]);
+
+/*
+ * Now finish by looking at all of the data again, via the LazyJsonMapper core
+ * object and via the `getEmployees()` object's cache... They are identical!
+ */
+$unpredictable4->printJson();
+printf("The manager's name is: %s\n", $unpredictable4->getManager()->getName());
+foreach ($unpredictable4->getEmployees()->getData() as $employeeId => $employeeInfo) {
+ printf("- Employee #%s, Name: %s\n", $employeeId, $employeeInfo->getName());
+}
+
+/*
+ * And that's it! Hopefully you NEVER have to work with nasty, unpredictable
+ * data like this. If you're able to control the JSON format, you should always
+ * design it properly with known keys instead. But at least you now know about
+ * multiple great methods for working with objects that have unpredictable keys!
+ *
+ * There are other methods too, such as if your class contains a blend of known
+ * (defined) keys and unpredictable keys, in which case you'd need to fetch via
+ * `asArray()` as in the `UnpredictableContainer` example, and then you'd simply
+ * filter out all known keys from your cache, to get _just_ the unpredictable
+ * keys. And if you need setters in that scenario, your setter functions would
+ * be a bit more complex since they would need to use `assignObjectData()` AND
+ * would have to provide BOTH the known data AND the unknown data. One way of
+ * doing that would be to use `_getProperty()` to merge in the CURRENT values of
+ * each core property into your NEW object-data array BEFORE you assign it. But
+ * I leave that extremely rare scenario as an excercise for you, dear reader!
+ *
+ * You should go out and adapt all of this code to fit your own needs! ;-)
+ *
+ * For example, if you don't need the unpredictable values to be converted
+ * to/from a specific object type, then simply skip the conversion code.
+ *
+ * If you don't need caching for performance, then skip the caching code.
+ *
+ * If you don't need setters/syncing, then skip all of that code.
+ *
+ * You may also want to disable the user-options `ALLOW_VIRTUAL_PROPERTIES` and
+ * `ALLOW_VIRTUAL_FUNCTIONS` on your unpredictable containers, so users cannot
+ * manipulate the unpredictable data via LazyJsonMapper's automatic functions!
+ * That's what we did for CoreUnpredictableContainer, to ensure that nobody can
+ * destroy its internal data by touching it directly. They can only manipulate
+ * the data via its safe, public `getData()` and `setData()` functions!
+ *
+ * And you may perhaps prefer to write a custom base-class which has a few other
+ * helper-functions for doing these kinds of data translations, caching and
+ * syncing, to make your own work easier (such as the CoreUnpredictableContainer
+ * example above). That way, your various sub-classes could just call your
+ * internal helper functions to do the required processing automatically! :-)
+ *
+ * The possibilities are endless! Have fun!
+ */
+echo "\n\nHave fun!\n";
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/phpdoc.dist.xml b/vendor/lazyjsonmapper/lazyjsonmapper/phpdoc.dist.xml
new file mode 100755
index 0000000..acd326c
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/phpdoc.dist.xml
@@ -0,0 +1,24 @@
+
+
+ LazyJsonMapper
+
+ docs/cache
+ utf8
+
+ TODO
+ FIXME
+
+
+ php
+
+
+
+ docs/output
+
+
+
+
+
+ src
+
+
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/phpunit.xml.dist b/vendor/lazyjsonmapper/lazyjsonmapper/phpunit.xml.dist
new file mode 100755
index 0000000..2fa7a92
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/phpunit.xml.dist
@@ -0,0 +1,9 @@
+
+
+
+
+ tests
+
+
+
\ No newline at end of file
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/BadPropertyDefinitionException.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/BadPropertyDefinitionException.php
new file mode 100755
index 0000000..24fb2f8
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/BadPropertyDefinitionException.php
@@ -0,0 +1,29 @@
+_badClassNameA = $badClassNameA;
+ if (!is_string($badClassNameB)) {
+ $badClassNameB = null;
+ }
+ $this->_badClassNameB = $badClassNameB;
+
+ if ($badClassNameA !== null && $badClassNameB !== null) {
+ parent::__construct(sprintf(
+ 'Circular reference between classes "%s" and "%s" in JSON property map import instruction.',
+ $badClassNameA, $badClassNameB
+ ));
+ } else {
+ parent::__construct('Circular reference in JSON property map import instruction.');
+ }
+ }
+
+ /**
+ * Get the name of the first class involved in the circular map.
+ *
+ * @return string|null
+ */
+ public function getBadClassNameA()
+ {
+ return $this->_badClassNameA;
+ }
+
+ /**
+ * Get the name of the second class involved in the circular map.
+ *
+ * @return string|null
+ */
+ public function getBadClassNameB()
+ {
+ return $this->_badClassNameB;
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/LazyJsonMapperException.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/LazyJsonMapperException.php
new file mode 100755
index 0000000..f9b6e51
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Exception/LazyJsonMapperException.php
@@ -0,0 +1,29 @@
+bad_definitions = array_merge_recursive(
+ $this->bad_definitions,
+ $other->bad_definitions
+ );
+ $this->missing_definitions = array_merge_recursive(
+ $this->missing_definitions,
+ $other->missing_definitions
+ );
+ }
+
+ /**
+ * Adds a problem description to the internal state.
+ *
+ * @param string $definitionSource The class which has the problem.
+ * @param string $problemType Type of problem. Either `bad_definitions`
+ * or `missing_definitions`.
+ * @param string $problemMessage A message describing the actual problem.
+ *
+ * @throws LazyJsonMapperException If any of the parameters are invalid.
+ *
+ * @see ClassAnalysis::hasProblems()
+ */
+ public function addProblem(
+ $definitionSource,
+ $problemType,
+ $problemMessage)
+ {
+ if (!is_string($definitionSource) || !is_string($problemMessage)) {
+ throw new LazyJsonMapperException('The definitionSource and problemMessage parameters must be strings.');
+ }
+ if ($problemType !== 'bad_definitions' && $problemType !== 'missing_definitions') {
+ throw new LazyJsonMapperException('The problemType parameter must be either "bad_definitions" or "missing_definitions".');
+ }
+
+ if (!isset($this->{$problemType}[$definitionSource])) {
+ $this->{$problemType}[$definitionSource] = [];
+ }
+
+ $this->{$problemType}[$definitionSource][] = $problemMessage;
+ }
+
+ /**
+ * Convert the per-class arrays to sorted lists of missing/bad properties.
+ *
+ * Removes all duplicate messages and sorts everything nicely. It is
+ * recommended to only call this function a single time, on the final
+ * `ClassAnalysis` object (after all other steps are finished).
+ */
+ public function sortProblemLists()
+ {
+ foreach (['bad_definitions', 'missing_definitions'] as $problemType) {
+ // Sort the problem messages within each class.
+ foreach ($this->{$problemType} as $definitionSource => $messages) {
+ $this->{$problemType}[$definitionSource] = array_unique($messages);
+ natcasesort($this->{$problemType}[$definitionSource]);
+ }
+
+ // Sort the outer array (the class names).
+ ksort($this->{$problemType}, SORT_NATURAL | SORT_FLAG_CASE);
+ }
+ }
+
+ /**
+ * Check whether any problems were discovered.
+ *
+ * In that case, it's recommended to use `generateNiceSummaries()` to format
+ * user-readable messages about the problems.
+ *
+ * @return bool
+ *
+ * @see ClassAnalysis::generateNiceSummaries()
+ */
+ public function hasProblems()
+ {
+ return !empty($this->bad_definitions) || !empty($this->missing_definitions);
+ }
+
+ /**
+ * Generates nicely formatted problem summaries for this class analysis.
+ *
+ * @return array An array with formatted messages for every type of analysis
+ * which actually had errors, keyed by the problem type. If no
+ * errors, the returned array will be empty.
+ *
+ * @see ClassAnalysis::hasProblems()
+ * @see ClassAnalysis::generateNiceSummariesAsString()
+ */
+ public function generateNiceSummaries()
+ {
+ $problemSummaries = [];
+ if (!empty($this->bad_definitions)) {
+ // Build a nice string containing all encountered bad definitions.
+ $strSubChunks = [];
+ foreach ($this->bad_definitions as $className => $messages) {
+ $strSubChunks[] = sprintf(
+ '"%s": ([\'%s\'])',
+ $className, implode('\'], and [\'', $messages)
+ );
+ }
+ $problemSummaries['bad_definitions'] = sprintf(
+ 'Bad JSON property definitions in %s.',
+ implode(', and in ', $strSubChunks)
+ );
+ }
+ if (!empty($this->missing_definitions)) {
+ // Build a nice string containing all missing class properties.
+ $strSubChunks = [];
+ foreach ($this->missing_definitions as $className => $messages) {
+ $strSubChunks[] = sprintf(
+ '"%s": ("%s")',
+ $className, implode('", "', $messages)
+ );
+ }
+ $problemSummaries['missing_definitions'] = sprintf(
+ 'Missing JSON property definitions in %s.',
+ implode(', and in ', $strSubChunks)
+ );
+ }
+
+ return $problemSummaries;
+ }
+
+ /**
+ * Generates a nicely formatted problem summary string for this class analysis.
+ *
+ * This helper combines all summaries and returns them as a single string
+ * (rather than as an array), which is very useful when displaying ALL
+ * errors to a user as a message.
+ *
+ * @return string The final string. Is an empty string if no errors exist.
+ *
+ * @see ClassAnalysis::hasProblems()
+ * @see ClassAnalysis::generateNiceSummaries()
+ */
+ public function generateNiceSummariesAsString()
+ {
+ return implode(' ', $this->generateNiceSummaries());
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Export/PropertyDescription.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Export/PropertyDescription.php
new file mode 100755
index 0000000..8212140
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Export/PropertyDescription.php
@@ -0,0 +1,250 @@
+asString();
+ $isRelativeTypePath = false;
+ if ($allowRelativeTypePath && $propDef->isObjectType) {
+ $relativeType = Utilities::createRelativeClassPath(
+ Utilities::splitStrictClassPath($strictOwnerClassPath),
+ // NOTE: This is safe because the asString() value is always a
+ // strict class path with optional [] suffixes (which will not
+ // interfere with the splitting process).
+ Utilities::splitStrictClassPath($finalType)
+ );
+ if ($finalType !== $relativeType) {
+ $isRelativeTypePath = true;
+ $finalType = $relativeType;
+ }
+ }
+
+ // Perform the translation from the property name to its FunctionCase.
+ $translation = new PropertyTranslation($propName); // Throws.
+
+ // Now just store all of the user-friendly descriptions.
+ $this->owner = $strictOwnerClassPath;
+ $this->is_defined = $isDefined;
+ $this->name = $propName;
+ $this->type = $finalType;
+ $this->is_basic_type = !$propDef->isObjectType;
+ $this->is_relative_type_path = $isRelativeTypePath;
+ $this->function_has = sprintf('bool has%s()', $translation->propFuncCase);
+ $this->function_is = sprintf('bool is%s()', $translation->propFuncCase);
+ $this->function_get = sprintf('%s get%s()', $finalType, $translation->propFuncCase);
+ $this->function_set = sprintf('$this set%s(%s $value)', $translation->propFuncCase, $finalType);
+ $this->function_unset = sprintf('$this unset%s()', $translation->propFuncCase);
+ $this->func_case = $translation->propFuncCase;
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/LazyJsonMapper.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/LazyJsonMapper.php
new file mode 100755
index 0000000..6f837a6
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/LazyJsonMapper.php
@@ -0,0 +1,2692 @@
+some_value` and `$item->some_value = 'foo'`.
+ *
+ * The virtual properties can be disabled via an option.
+ *
+ * - Automatically provides object-oriented "virtual functions", which let you
+ * interact with the data in a fully object-oriented way via functions such
+ * as `$item->getSomeValue()` and `$item->setSomeValue('foo')`. We support a
+ * large range of different functions for manipulating the JSON data, and you
+ * can see a list of all available function names for all of your properties
+ * by simply running `$item->printPropertyDescriptions()`.
+ *
+ * The virtual functions can be disabled via an option.
+ *
+ * - Includes the `LazyDoctor` tool, which _automatically_ documents all of
+ * your `LazyJsonMapper`-based classes so that their virtual properties and
+ * functions become _fully_ visible to your IDE and to various intelligent
+ * code analysis tools. It also performs class diagnostics by compiling all
+ * of your class property maps, which means that you can be 100% sure that
+ * all of your maps are valid (compilable) if this tool runs successfully.
+ *
+ * - We provide a complete, internal API which your subclasses can use to
+ * interact with the data inside of the JSON container. This allows you to
+ * easily override the automatic functions or create additional functions
+ * for your objects. To override core functions, just define a function with
+ * the exact same name on your object and make it do whatever you want to.
+ *
+ * Here are some examples of function overriding:
+ *
+ * ```php
+ * public function getFoo()
+ * {
+ * // try to read property, and handle a special "current_time" value.
+ * $value = $this->_getProperty('foo');
+ * if ($value === 'current_time') { return time(); }
+ * return $value;
+ * }
+ * public function setFoo(
+ * $value)
+ * {
+ * // if they try to set value to "md5", use a special value instead.
+ * if ($value === 'md5') { $value = md5(time()); }
+ * return $this->_setProperty('foo', $value);
+ * }
+ * ```
+ *
+ * - All mapping/data conversion is done "lazily", on a per-property basis.
+ * When you access a property, that specific property is mapped/converted to
+ * the proper type as defined by your class property map. No time or memory
+ * is wasted converting properties that you never touch.
+ *
+ * - Strong type-system. The class property map controls the exact types and
+ * array depths. You can fully trust that the data you get/set will match
+ * your specifications. Invalid data that mismatches the spec is impossible.
+ *
+ * - Advanced settings system. Everything is easily configured via PHP class
+ * constants, which means that your class-settings are stateless (there's no
+ * need for any special "settings/mapper object" to keep track of settings),
+ * and that all settings are immutable constants (which means that they are
+ * reliable and can never mutate at runtime, so that you can fully trust that
+ * classes will always behave as-defined in their code).
+ *
+ * If you want to override multiple core settings identically for all of your
+ * classes, then simply create a subclass of `LazyJsonMapper` and configure
+ * all of your settings on that, and then derive all of your other classes
+ * from your re-configured subclass!
+ *
+ * - The world's most advanced mapper definition system. Your class property
+ * maps are defined in an easy PHPdoc-style format, and support multilevel
+ * arrays (such as `int[][]` for "an array of arrays of ints"), relative
+ * types (so you can map properties to classes/objects that are relative to
+ * the namespace of the class property map), parent inheritance (all of your
+ * parent `extends`-hierarchy's maps will be included in your final property
+ * map) and even multiple inheritance (you can literally "import" an infinite
+ * number of other maps into your class, which don't come from your own
+ * parent `extends`-hierarchy).
+ *
+ * - Inheriting properties from parent classes or importing properties from
+ * other classes is a zero-cost operation thanks to how efficient our
+ * property map compiler is. So feel free to import everything you need.
+ * You can even use this system to create importable classes that just hold
+ * "collections" of shared properties, which you import into other classes.
+ *
+ * - The class property maps are compiled a single time per-class at runtime,
+ * the first time a class is used. The compilation process fully verifies
+ * and compiles all property definitions, all parent maps, all inherited
+ * maps, and all maps of all classes you link properties to.
+ *
+ * If there are any compilation problems due to a badly written map anywhere
+ * in your hierarchy, you will be shown the exact problem in great detail.
+ *
+ * In case of success, the compiled and verified maps are all stored in an
+ * incredibly memory-efficient format in a global cache which is shared by
+ * your whole PHP runtime, which means that anything in your code or in any
+ * other libraries which accesses the same classes will all share the cached
+ * compilations of those classes, for maximum memory efficiency.
+ *
+ * - You are also able to access JSON properties that haven't been defined in
+ * the class property map. In that case, they are treated as undefined and
+ * untyped (`mixed`) and there won't be any automatic type-conversion of such
+ * properties, but it can still be handy in a pinch.
+ *
+ * - There are lots of data export/output options for your object's JSON data,
+ * to get it back out of the object again: As a multi-level array, as nested
+ * stdClass objects, or as a JSON string representation of your object.
+ *
+ * - We include a whole assortment of incredibly advanced debugging features:
+ *
+ * You can run the constructor with `$requireAnalysis` to ensure that all
+ * of your JSON data is successfully mapped according to your class property
+ * map, and that you haven't missed defining any properties that exist in the
+ * data. In case of any problems, the analysis message will give you a full
+ * list of all problems encountered in your entire JSON data hierarchy.
+ *
+ * For your class property maps themselves, you can run functions such as
+ * `printPropertyDescriptions()` to see a complete list of all properties and
+ * how they are defined. This helps debug your class inheritance and imports
+ * to visually see what your final class map looks like, and it also helps
+ * users see all available properties and all of their virtual functions.
+ *
+ * And for the JSON data, you can use functions such as `printJson()` to get
+ * a beautiful view of all internal JSON data, which is incredibly helpful
+ * when you (or your users) need to figure out what's available inside the
+ * current object instance's data storage.
+ *
+ * - A fine-grained and logical exception-system which ensures that you can
+ * always trust the behavior of your objects and can catch problems easily.
+ * And everything we throw is _always_ based on `LazyJsonMapperException`,
+ * which means that you can simply catch that single "root" exception
+ * whenever you don't care about fine-grained differentiation.
+ *
+ * - Clean and modular code ensures stability and future extensibility.
+ *
+ * - Deep code documentation explains everything you could ever wonder about.
+ *
+ * - Lastly, we implement super-efficient object serialization. Everything is
+ * stored in a tightly packed format which minimizes data size when you need
+ * to transfer your objects between runtimes.
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ */
+class LazyJsonMapper implements Serializable, JsonSerializable
+{
+ /**
+ * Whether "direct virtual properties" access is enabled.
+ *
+ * This constant can be overridden in your subclasses to toggle the option.
+ *
+ * It is recommended that all of your brand new projects create a subclass
+ * of `LazyJsonMapper` which disables this option, and then you simply
+ * derive all of your other classes from that subclass so that nothing in
+ * your project has direct virtual property access enabled.
+ *
+ * That's because there are many quirks with how PHP implements virtual
+ * properties, which can lead to performance slowdowns and lack of data
+ * validation if you use direct access in CERTAIN scenarios (without writing
+ * proper code). And if users are going to be interacting with YOUR library,
+ * then you SHOULD disable virtual properties so that your users don't fail
+ * to implement their proper usage. PHP's virtual property quirks are all
+ * described in the documentation for `__get()`. Please read it and decide
+ * whether you want to allow "direct virtual properties".
+ *
+ * The only reason why this feature is enabled by default is that it helps
+ * people easily migrate from legacy projects.
+ *
+ * @var bool
+ *
+ * @see LazyJsonMapper::__get() More details about virtual properties.
+ */
+ const ALLOW_VIRTUAL_PROPERTIES = true;
+
+ /**
+ * Whether "virtual functions" access is enabled.
+ *
+ * This constant can be overridden in your subclasses to toggle the option.
+ *
+ * It's the recommended access method. It always ensures complete data
+ * validation and the highest performance. However, some people may want to
+ * disable it in favor of manually defining your object's functions instead.
+ * (This library provides a complete internal API for manipulating the
+ * object's JSON data from your own custom class-functions.)
+ *
+ * @var bool
+ *
+ * @see LazyJsonMapper::__call() More details about virtual functions.
+ */
+ const ALLOW_VIRTUAL_FUNCTIONS = true;
+
+ /**
+ * Whether we should cache all magic virtual function translations.
+ *
+ * This constant can be overridden in your subclasses to toggle caching.
+ *
+ * The cache stores all of the current runtime's encountered magic/virtual
+ * function name translations, such as `SomeProperty` (via `getSomeProperty`
+ * and `setSomeProperty`, etc) and the fact that it refers to a JSON
+ * property that will be named either `some_property` (if snake style)
+ * or `someProperty` (if camel style).
+ *
+ * Entries are added to the cache one-by-one every time you call a function
+ * on an uncached property name for the first time. The next time you call a
+ * function which accesses the same property name, then there's no need to
+ * analyze the function name again (to figure out what JSON data property it
+ * refers to), since the translation is already cached. It is cached on a
+ * per-property name basis. So a SINGLE cached translation of `SomeProperty`
+ * will be shared by ALL function calls related to that property (such as
+ * all of the core functions; `hasSomeProperty`, `isSomeProperty`,
+ * `getSomeProperty`, `setSomeProperty`, and `unsetSomeProperty`).
+ *
+ * At the cost of a very tiny bit of RAM, this caching greatly speeds up
+ * magic function calls so that they only take about 16% as long as they
+ * would without the cache. However, they're still very fast even without a
+ * cache, and you may be accessing so many different properties that you'd
+ * prefer to avoid a cache for memory reasons (if you prefer lower memory
+ * needs over pure speed). The choice is yours.
+ *
+ * To calculate the memory needs of your own project's virtual function
+ * cache, you simply need to know that an average cache entry uses ~248.6
+ * bytes of RAM on PHP 7 and ~510.4 bytes on PHP 5. Note that the size per
+ * name translation is small enough to be measured in BYTES (NOT kilobytes)!
+ *
+ * Those averages were calculated from a huge sample-size of 10000 function
+ * names, and are very accurate for real-world JSON data.
+ *
+ * To calculate your own cache memory size, think about how many different
+ * JSON properties you'll need to access in your project. Most normal
+ * projects would probably only access at most 100 different property names
+ * (such as `getComments`, `setTime`, etc), since you most likely won't
+ * access every property in the JSON data. So only count the properties
+ * you'll access. And also remember that identical property names used
+ * across different classes, and all the different functions for accessing
+ * the same property, will still only require a SINGLE global cache entry
+ * per property name.
+ *
+ * The storage needs for 100 different property name translations would be
+ * as follows. As you can see, the cache is very memory-efficient:
+ *
+ * - PHP 7: 100x ~248.6 bytes = 24 860 bytes (~24.9 kilobytes).
+ * - PHP 5: 100x ~510.4 bytes = 51 040 bytes (~51.1 kilobytes).
+ *
+ * (If you're still using PHP 5, you should really consider upgrading to
+ * PHP 7 with its better memory efficiency and its much faster engine!)
+ *
+ * It is highly recommended to use caching, and it is enabled by default
+ * unless you override the constant in your class, as follows:
+ *
+ * ```php
+ * class MyUncachedLazyJsonMapper extends LazyJsonMapper
+ * {
+ * const USE_MAGIC_LOOKUP_CACHE = false;
+ * }
+ * ```
+ *
+ * With the above code, `MyUncachedLazyJsonMapper` (and anything extending
+ * from it) would run without using the magic function translation cache,
+ * which means that all magic "virtual function" calls on instances of that
+ * class would have to redo their property translations every time.
+ *
+ * @var bool
+ */
+ const USE_MAGIC_LOOKUP_CACHE = true;
+
+ /**
+ * Tells us how to map JSON properties to internal PHP types or objects.
+ *
+ * This constant can be overridden in your subclasses, to add custom JSON
+ * mapping definitions to your class. It is recommended to always do so.
+ *
+ * The value must be an array of key-value pairs, where the key is the name
+ * of a JSON property and the value is a string with the definition for that
+ * specific property in PHPdoc-style. We will then perform automatic, strict
+ * lazy-conversion of the value to the target type whenever you access that
+ * property. (However, be aware that we always allow `NULL` values in any
+ * property, and will never enforce/convert those to the target type.)
+ *
+ * The following built-in types are supported: `bool`, `int`, `float`,
+ * `string`. The JSON data will be type-converted to match that type.
+ *
+ * Note that if the type is set to `mixed` or an empty string `""` instead,
+ * then the property will allow any of the built-in types (`bool`, `int`,
+ * `float`, `string`, `NULL` (as always) and multi-level arrays of any of
+ * those types). There simply won't be any "forced" type-conversion to any
+ * specific basic type for that property since you haven't defined any
+ * strict type.
+ *
+ * Setting up untyped properties is still useful even if you don't know its
+ * type, since defining the property in the class map ensures that you can
+ * always get/set/access/use that property even if it's unavailable in the
+ * current object instance's internal JSON data.
+ *
+ * You can also map values to objects. In the case of objects (classes), the
+ * classes MUST inherit from `LazyJsonMapper` so they support all necessary
+ * mapping features. To assign a class to a property, you can either write a
+ * FULL (global) class path starting with a leading backslash `\`, such as
+ * `\Foo\Bar\Baz`. Alternatively, you can use a RELATIVE path related to the
+ * namespace of the CURRENT MAP'S class, such as `Bar\Baz` (if your current
+ * class is `\Foo\Whatever`, then it'd be interpreted as `\Foo\Bar\Baz`).
+ * Anything that DOESN'T start with a leading backslash is interpreted as a
+ * relative class path! Just be aware that your class `use`-statements are
+ * completely ignored since PHP has no mechanism to let us detect those.
+ *
+ * It's also possible to map properties to the core `LazyJsonMapper` object
+ * if you simply want an object-oriented container for its data without
+ * writing a custom class. You can do that by defining the property type as
+ * either `\LazyJsonMapper\LazyJsonMapper`, or as `LazyJsonMapper` (which is
+ * a special shortcut that ALWAYS resolves to the core class). But it's
+ * always best to write a proper class, so that its properties are reliable.
+ *
+ * Lastly, you can map "arrays of TYPE" as well. Simply add one or more `[]`
+ * brackets to the end of the type. For example, `int[]` means "array of
+ * ints", and `\Foo[][][]` means "array of arrays of arrays of `\Foo`
+ * objects". It may be easier to understand those mentally if you read them
+ * backwards and say the words "array of" every time you see a `[]` bracket.
+ * (Note: You can also use the array notation with `mixed[][]`, which would
+ * then strictly define that the value MUST be mixed data at an exact depth
+ * of 2 arrays, and that no further arrays are allowed deeper than that.)
+ *
+ * The assigned types and array-depths are STRICTLY validated. That's an
+ * integral part of the `LazyJsonMapper` container, since it guarantees your
+ * class property map interface will be strictly followed, and that you can
+ * fully TRUST the data you're interacting with. If your map says that the
+ * array data is at depth 8 and consists of `YourObject` objects, then we'll
+ * make sure the data is indeed at depth 8 and their value type is correct!
+ *
+ * That goes for arrays too. If you define an `int[]` array of ints, then
+ * we'll ensure that the array has sequential numeric keys starting at 0 and
+ * going up without any gaps, exactly as a JSON array is supposed to be. And
+ * if you define an object `YourObject`, then we'll ensure that the input
+ * JSON data consists of an array with associative keys there (which is used
+ * as the object properties), exactly as a JSON object is supposed to be.
+ *
+ * In other words, you can TOTALLY trust your data if you've assigned types!
+ *
+ * Example property map:
+ *
+ * ```php
+ * const JSON_PROPERTY_MAP = [
+ * 'some_string' => 'string',
+ * 'an_object' => '\YourProject\YourObject',
+ * 'array_of_numbers' => 'int[]',
+ * 'array_of_objects' => 'RelativeObject[]',
+ * 'untyped_value' => '', // shorthand for 'mixed' below:
+ * 'another_untyped_value' => 'mixed', // allows multilevel arrays
+ * 'deep_arr_of_arrs_of_int' => 'int[][]',
+ * 'array_of_mixed' => 'mixed[]', // enforces 1-level depth
+ * 'array_of_generic_objects' => 'LazyJsonMapper[]',
+ * ];
+ * ```
+ *
+ * Also note that we automatically inherit all of the class maps from all
+ * parents higher up in your object-inheritance chain. In case of a property
+ * name clash, the deepest child class definition of it takes precedence.
+ *
+ * It is also worth knowing that we support a special "multiple inheritance"
+ * instruction which allows you to "import the map" from one or more other
+ * classes. The imported maps will be merged onto your current class, as if
+ * the entire hierarchy of properties from the target class (including the
+ * target class' inherited parents and own imports) had been "pasted inside"
+ * your class' property map at that point. And you don't have to worry
+ * about carefully writing your inheritance relationships, because we have
+ * full protection against circular references (when two objects try to
+ * import each other) and will safely detect that issue at runtime whenever
+ * we try to compile the maps of either of those bad classes with their
+ * badly written imports.
+ *
+ * The instruction for importing other maps is very simple: You just have to
+ * add an unkeyed array element with a reference to the other class. The
+ * other class must simply refer to the relative or full path of the class,
+ * followed by `::class`.
+ *
+ * Example of importing one or more maps from other classes:
+ *
+ * ```php
+ * const JSON_PROPERTY_MAP = [
+ * 'my_own_prop' => 'string',
+ * OtherClass::class, // relative class path
+ * 'redefined_prop' => float,
+ * \OtherNamespace\SomeClass::class, // full class path
+ * ];
+ * ```
+ *
+ * The imports are resolved in the exact order they're listed in the array.
+ * Any property name clashes will always choose the version from the latest
+ * statement in the list. So in this example, our class would first inherit
+ * all of its own parent (`extends`) class maps. Then it would add/overwrite
+ * `my_own_prop`. Then it imports everything from `OtherClass` (and its
+ * parents/imports). Then it adds/overwrites `redefined_prop` (useful if you
+ * want to re-define some property that was inherited from the other class).
+ * And lastly, it imports everything from `\OtherNamespace\SomeClass` (and
+ * its parents/imports). As long as there are no circular references,
+ * everything will compile successfully and you will end up with a very
+ * advanced final map! And any other class which later inherits from
+ * (extends) or imports YOUR class will inherit the same advanced map from
+ * you! This is REAL "multiple inheritance"! ;-)
+ *
+ * Also note that "relative class path" properties are properly inherited as
+ * pointing to whatever was relative to the original inherited/imported
+ * class where the property was defined. You don't have to worry about that.
+ *
+ * The only thing you should keep in mind is that we ONLY import the maps.
+ * We do NOT import any functions from the imported classes (such as its
+ * overridden functions), since there's no way for us to affect how PHP
+ * resolves function calls to "copy" functions from one class to another. If
+ * you need their functions, then you should simply `extends` from the class
+ * to get true PHP inheritance instead of only importing its map. Or you
+ * could just simply put your functions in PHP Traits and Interfaces so
+ * that they can be re-used by various classes without needing inheritance!
+ *
+ * Lastly, here's an important general note about imports and inheritance:
+ * You DON'T have to worry about "increased memory usage" when importing or
+ * inheriting tons of other classes. The runtime "map compiler" is extremely
+ * efficient and re-uses the already-compiled properties inherited from its
+ * parents/imports, meaning that property inheritance is a ZERO-COST memory
+ * operation! In fact, re-definitions are also zero-cost as long as your
+ * class re-defines a property to the exact same type that it had already
+ * inherited/imported. Such as `Base: foo:string, Child: foo:string`. In
+ * that case, we detect that the definitions are identical and just re-use
+ * the compiled property from `Base` instead. It's only when a class adds
+ * NEW/MODIFIED properties that memory usage increases a little bit! In
+ * other words, inheritance/imports are a very good thing, and are always a
+ * better idea than manually writing similar lists of properties in various
+ * unrelated (not extending each other) classes. In fact, if you have a
+ * situation where many classes need similar properties, then it's a GREAT
+ * idea to create special re-usable "property collection" classes and then
+ * simply importing THOSE into ALL of the different classes that need those
+ * sets of properties! You can even use that technique to get around PHP's
+ * `use`-statement limitations (simply place a "property collection" class
+ * container in the namespace that had all of your `use`-classes, and define
+ * its properties via easy, relative paths there, and then simply make your
+ * other classes import THAT container to get all of its properties).
+ *
+ * As you can see, the mapping and inheritance system is extremely powerful!
+ *
+ * Note that the property maps are analyzed and compiled at runtime, the
+ * first time we encounter each class. After compilation, the compiled maps
+ * become immutable (cannot be modified). So there's no point trying to
+ * modify this variable at runtime. That's also partially the reason why
+ * this is defined through a constant, since they prevent you from modifying
+ * the array at runtime and you believing that it would update your map. The
+ * compiled maps are immutable at runtime for performance & safety reasons!
+ *
+ * Have fun!
+ *
+ * @var array
+ *
+ * @see LazyJsonMapper::printPropertyDescriptions() Easily looking at and
+ * debugging your final
+ * class property map.
+ * @see LazyJsonMapper::printJson() Looking at the JSON data
+ * contents of the current
+ * object instance.
+ * @see LazyJsonMapper::exportClassAnalysis() Looking for problems
+ * with your class map.
+ * However, the same test
+ * can also be achieved
+ * via `$requireAnalysis`
+ * as a constructor flag.
+ */
+ const JSON_PROPERTY_MAP = [];
+
+ /**
+ * Magic virtual function lookup cache.
+ *
+ * Globally shared across all instances of `LazyJsonMapper` classes. That
+ * saves tons of memory, since multiple different components/libraries in
+ * your project can use any functions they need, such as `getComments()`,
+ * and then all OTHER code that uses a `comments` property would share that
+ * cached translation too, which ensures maximum memory efficiency!
+ *
+ * It is private to prevent subclasses from modifying it.
+ *
+ * @var array
+ *
+ * @see LazyJsonMapper::clearGlobalMagicLookupCache()
+ */
+ private static $_magicLookupCache = [];
+
+ /**
+ * Storage container for compiled class property maps.
+ *
+ * Class maps are built at runtime the FIRST time we encounter each class.
+ * This cache is necessary so that we instantly have fully-validated maps
+ * without needing to constantly re-build/validate the class property maps.
+ *
+ * Compilation only happens the first time that we encounter the class
+ * during the current runtime (unless you manually decide to clear the
+ * cache at any point). And the compiled map format is extremely optimized
+ * and very memory-efficient (for example, subclasses and inherited classes
+ * all share the same compiled PropertyDefinition objects, and those objects
+ * themselves are extremely optimized). So you don't have to worry about
+ * memory usage at all.
+ *
+ * You should think about this exactly like PHP's own class loader/compiler.
+ * Whenever you request a class for the first time, PHP reads its .php file
+ * and parses and compiles its code and then keeps that class in memory for
+ * instant re-use. That's exactly like what this "property map cache" does.
+ * It compiles the maps the first time the class is used, and then it just
+ * stays in memory for instant re-use. So again, don't worry about it! :-)
+ *
+ * Globally shared across all instances of `LazyJsonMapper` classes. Which
+ * ensures that classes are truly only compiled ONCE during PHP's runtime,
+ * even when multiple parts of your project or other libraries use the same
+ * classes. And it also means that there's no need for you to manually
+ * maintain any kind of personal "cache-storage class instance". The global
+ * storage takes care of that for you effortlessly!
+ *
+ * It is private to prevent subclasses from modifying it.
+ *
+ * @var PropertyMapCache
+ *
+ * @see LazyJsonMapper::clearGlobalPropertyMapCache()
+ */
+ private static $_propertyMapCache;
+
+ /**
+ * Direct reference to the current object's property definition cache.
+ *
+ * This is a reference. It isn't a copy. So the memory usage of each class
+ * instance stays identical since the same cache is shared among them all.
+ * Therefore, you don't have to worry about seeing this in `var_dump()`.
+ *
+ * It is private to prevent subclasses from modifying it.
+ *
+ * @var array
+ *
+ * @see LazyJsonMapper::printPropertyDescriptions() Easily looking at and
+ * debugging your final
+ * class property map.
+ */
+ private $_compiledPropertyMapLink;
+
+ /**
+ * Container for this specific object instance's JSON data.
+ *
+ * Due to the lazy-conversion of objects, some parts of the tree can be
+ * objects and others can be JSON sub-arrays that have not yet been turned
+ * into objects. That system ensures maximum memory and CPU efficiency.
+ *
+ * This container is private to prevent subclasses from modifying it. Use
+ * the protected functions to indirectly check/access/modify values instead.
+ *
+ * @var array
+ */
+ private $_objectData;
+
+ /**
+ * Constructor.
+ *
+ * You CANNOT override this constructor. That's because the constructor and
+ * its arguments and exact algorithm are too important to allow subclasses
+ * to risk breaking it (especially since you must then perfectly maintain an
+ * identical constructor argument list and types of thrown exceptions, and
+ * would always have to remember to call our constructor first, and so on).
+ *
+ * Furthermore, trying to have custom constructors on "JSON data container
+ * objects" MAKES NO SENSE AT ALL, since your JSON data objects can be
+ * created recursively and automatically from nested JSON object data. So
+ * there's no way you could run your object's custom constructors then!
+ *
+ * Instead, there is a separate `_init()` function which you can override in
+ * your subclasses, for safe custom initialization. And if you NEED to use
+ * some external variables in certain functions in your JSON data classes,
+ * then simply add those variables as arguments to those class-functions!
+ *
+ * Also note that there are many reasons why the JSON data must be provided
+ * as an array instead of as an object. Most importantly, the memory usage
+ * is much lower if you decode to an array (because a \stdClass requires as
+ * much RAM as an array WITH object overhead ON TOP of that), and also
+ * because the processing we do is more efficient when we have an array.
+ *
+ * Lastly, this seems like a prominent enough place to mention something
+ * that's VERY important if you are handling JSON data on 32-bit systems:
+ * PHP running as a 32-bit process does NOT support 64-bit JSON integers
+ * natively. It will cast them to floats instead, which may lose precision,
+ * and fails completely if you then cast those numbers to strings, since you
+ * will get float notation or `INT_MAX` capping depending on what you are
+ * doing with the data (ie `printf('%d')` would give `INT_MAX`, and `%s`
+ * would give scientific notation like `8.472E+22`). All of those situations
+ * can be avoided by simply telling PHP to decode big numbers to strings:
+ *
+ * ```php
+ * json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+ * ```
+ *
+ * If you are ever going to handle 64-bit integers on 32-bit systems, then
+ * you will NEED to do the above, as well as ensuring that all of your class
+ * `JSON_PROPERTY_MAP` definitions never use any `int` field type (since
+ * that would convert the safe strings back into truncated 32-bit integers).
+ * Instead, you should define those fields as `string`-type in your map.
+ *
+ * @param array $objectData Decoded JSON data as an array (NOT object).
+ * @param bool $requireAnalysis Whether to throw an exception if any of the
+ * raw JSON properties aren't defined in the
+ * class property map (are missing), or if any
+ * of the encountered property-classes are bad
+ * (fail to construct with the JSON data;
+ * usually only possible due to a custom
+ * `_init()` or when its raw JSON data value
+ * wasn't actually an object at all), or any
+ * problems with array-depth or type coercion.
+ * This option is very useful for debugging
+ * when creating/updating your custom classes.
+ * But BEWARE that it causes the WHOLE
+ * `$objectData` tree to be recursively parsed
+ * and analyzed, which is really TERRIBLE for
+ * performance. So DON'T use this permanently!
+ *
+ * @throws LazyJsonMapperException If the class hierarchy contains any
+ * invalid JSON property map/definition
+ * which prevents successful class map
+ * compilation, or if JSON data analysis
+ * requested and any of the map's property
+ * definitions are bad/missing. Also if a
+ * custom class `_init()` threw any kind of
+ * exception.
+ *
+ * @see LazyJsonMapper::_init()
+ * @see LazyJsonMapper::assignObjectData()
+ */
+ final public function __construct(
+ array $objectData = [],
+ $requireAnalysis = false)
+ {
+ // Create the global property map cache object if not yet initialized.
+ if (self::$_propertyMapCache === null) {
+ self::$_propertyMapCache = new PropertyMapCache();
+ }
+
+ // Compile this class property map if not yet built and cached.
+ // NOTE: Validates all definitions the first time and throws if invalid
+ // definitions, invalid map, or if there are any circular map imports.
+ // NOTE: This aborts compilation, automatically rolls back the failed
+ // compilation attempt, AND throws - at the FIRST-discovered problem
+ // during map compilation! It will only show the single, specific
+ // problem which caused compilation to fail. So it won't inundate the
+ // user's screen with all individual error message in case there are
+ // more problems. If their class maps have multiple problems that
+ // prevent compilation, they'll have to see and fix them one by one.
+ // But the user will only have to fix their maps once, since compilation
+ // issues are a core problem with their map and aren't a runtime issue!
+ $thisClassName = get_class($this);
+ if (!isset(self::$_propertyMapCache->classMaps[$thisClassName])) {
+ PropertyMapCompiler::compileClassPropertyMap( // Throws.
+ self::$_propertyMapCache,
+ $thisClassName
+ );
+ }
+
+ // Now link this class instance directly to its own property-cache, via
+ // direct REFERENCE for high performance (to avoid map array lookups).
+ // The fact that it's a link also avoids the risk of copy-on-write.
+ $this->_compiledPropertyMapLink = &self::$_propertyMapCache->classMaps[$thisClassName];
+
+ // Assign the JSON data, run optional analysis, and then _init().
+ $this->assignObjectData($objectData, $requireAnalysis); // Throws.
+ }
+
+ /**
+ * Assign a new internal JSON data array for this object.
+ *
+ * This is used by the constructor for assigning the initial internal data
+ * state, but can also be very useful for users who want to manually replace
+ * the contents of their object at a later time.
+ *
+ * For example, it might suit your project design better to first construct
+ * an empty object, and *then* pass it to some other function which actually
+ * fills it with the JSON data. This function allows you to achieve that.
+ *
+ * The entire internal data storage will be replaced with the new data.
+ *
+ * @param array $objectData Decoded JSON data as an array (NOT object).
+ * @param bool $requireAnalysis Whether to analyze the JSON data and throw
+ * if there are problems with mapping. See
+ * `__construct()` for more details.
+ *
+ * @throws LazyJsonMapperException If JSON data analysis requested and any
+ * of the map's property definitions are
+ * bad/missing. Also if a custom class
+ * `_init()` threw any kind of exception.
+ *
+ * @see LazyJsonMapper::__construct()
+ * @see LazyJsonMapper::_init()
+ */
+ final public function assignObjectData(
+ array $objectData = [],
+ $requireAnalysis = false)
+ {
+ // Save the provided JSON data array.
+ $this->_objectData = $objectData;
+
+ // Recursively look for missing/bad JSON properties, if scan requested.
+ // NOTE: "Bad" in this case includes things like fatal mismatches
+ // between the definition of a property and the actual JSON data for it.
+ // NOTE: This analysis includes ALL problems with the class and its
+ // entire hierarchy (recursively), which means that it can produce
+ // really long error messages. It will warn about ALL missing properties
+ // that exist in the data but are not defined in the class, and it will
+ // warn about ALL bad properties that existed in the data but cannot be
+ // mapped in the way that the class map claims.
+ if ($requireAnalysis) {
+ $analysis = $this->exportClassAnalysis(); // Never throws.
+ if ($analysis->hasProblems()) {
+ // Since there were problems, throw with all combined summaries.
+ throw new LazyJsonMapperException(
+ $analysis->generateNiceSummariesAsString()
+ );
+ }
+ }
+
+ // Call the custom initializer, where the subclass can do its own init.
+ // NOTE: This is necessary for safely encapsulating the subclass' code.
+ try {
+ $this->_init();
+ } catch (LazyUserException $e) {
+ throw $e; // Re-throw user-error as is, since it's a proper exception.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY other exception!
+ // Ensure that they didn't throw something dumb from their code.
+ // We'll even swallow the message, to truly discourage misuse.
+ throw new LazyUserException(
+ 'Invalid exception thrown by _init(). Must use LazyUserException.'
+ );
+ }
+ }
+
+ /**
+ * Initializer for custom subclass construction / data updates.
+ *
+ * This is where you can perform your custom subclass initialization, since
+ * you are unable to override the main constructor.
+ *
+ * We automatically run this function at the end of the normal constructor,
+ * as well as every time that you manually use `assignObjectData()` to
+ * replace the object's data. (When new data is assigned, you should treat
+ * yourself as a new object, which is why `_init()` will run again.)
+ *
+ * `WARNING:` Please RESIST the urge to touch ANY of the internal JSON data
+ * during this initialization. All data will always be automatically
+ * validated during actual retrieval and setting, so you can always trust
+ * that the final types WILL match your class property map definitions.
+ *
+ * Remember that any extra work you do in `_init()` will run EVERY time an
+ * instance of your object is created, even when those objects live within a
+ * main object. So if your function is heavy, it will slow down operation of
+ * the class; and your parsing of the properties would be counter-productive
+ * against the goal of "lazy parsing" of JSON data on a when-accessed basis!
+ *
+ * Instead, it's preferable to just override specific property getter and
+ * setter functions, to make your class return different default values if a
+ * certain internal data field is missing an expected value. But in general,
+ * your ultimate USER CODE should be responsible for most of THAT checking,
+ * by simply looking for `NULL` (denoting a missing value in the JSON data
+ * container), and then deciding what to do with that in your final project.
+ *
+ * Lastly, there are a few rules that MUST be followed by your `_init()`:
+ *
+ * 1. As explained above, but worth repeating again: Your `LazyJsonMapper`
+ * classes are supposed to be LIGHT-weight "JSON data containers". So
+ * please DO NOT treat this as a "standard class constructor". The more
+ * work you do in `_init()`, the slower your object creation is. And since
+ * your object is a JSON container which is automatically created from
+ * data, you can expect LOTS of instances of your class to be created
+ * during normal runtime! That's also why your `_init()` function does not
+ * get any parameters! That is to discourage misuse as a normal class.
+ *
+ * Also remember that you may not even need `_init()`, since you can simply
+ * give your properties default values instead of using `_init()`, such as
+ * by writing `public $myproperty = true;`. All instances of that object
+ * would then start with that value set to `TRUE` by default.
+ *
+ * 2. You can ONLY throw `LazyUserException`. All other exceptions will be
+ * blocked and turned into a generic `LazyUserException`. This is done to
+ * ensure that your custom init function cannot break the contract that the
+ * `LazyJsonMapper` constructor guarantees in its listed exceptions.
+ *
+ * (That's also the reason why all built-in functions except `_init()` are
+ * marked `final`: So that subclasses cannot break the `LazyJsonMapper`
+ * API/interface contract. If something IS a `LazyJsonMapper`, it MUST
+ * behave as a `LazyJsonMapper`, and the `final` functions guarantee that!)
+ *
+ * 3. If your class extends from `LazyJsonMapper`, then there's no need to
+ * call `parent::_init()` (since our core `_init()` does nothing). But if
+ * you extend from ANY OTHER CLASS, you MUST call your parent's init BEFORE
+ * doing any work, just to guarantee that your WHOLE parent-class hierarchy
+ * is fully initialized first.
+ *
+ * 4. Understand and accept the fact that your personal class properties
+ * will NOT be serialized if you serialize an object. Only the JSON data
+ * array will be kept. The `_init()` function will be called again when you
+ * unserialize the object data, as if your object had just been created for
+ * the first time. This is done to save space and to discourage misuse
+ * of your `LazyJsonMapper` containers.
+ *
+ * Remember that your classes are supposed to be lightweight JSON data
+ * containers, which give you a strongly typed, automatic, object-oriented
+ * interface to your data. Classes "with advanced constructors and tons of
+ * properties" are NOT suitable as JSON containers and belong ELSEWHERE in
+ * the rest of your project!
+ *
+ * @throws LazyUserException If there is any fatal error which prevents
+ * initialization. This stops object construction
+ * if this is the initial construction. However,
+ * it doesn't affect data-assignment if you throw
+ * during a later `assignObjectData()` call.
+ *
+ * @see LazyJsonMapper::assignObjectData()
+ */
+ protected function _init()
+ {
+ // This standard _init does nothing by default...
+
+ // Always call your parent's init FIRST, to avoid breaking your class
+ // hierarchy's necessary initializers. But that can be skipped if your
+ // direct parent is LazyJsonMapper, since we do nothing in our _init().
+
+ // parent::_init();
+
+ // After your parent hierarchy is initialized, you're welcome to perform
+ // YOUR OWN class initialization, such as setting up state variables...
+
+ // $this->someFlag = true;
+
+ // However, that kind of usage is highly discouraged, since your objects
+ // will lose their state again when serialized and then unserialized.
+ // Remember: Your class is meant to be a "light-weight JSON container",
+ // and NOT some ultra-advanced normal class. Always think about that!
+ }
+
+ /**
+ * Export human-readable descriptions of class/object instance properties.
+ *
+ * The defined (class property map) properties are always included. You can
+ * optionally choose to also include undefined properties that only exist in
+ * the current object instance's JSON data, but which aren't in the class.
+ * Such properties are dangerous, since they only exist in the current data.
+ *
+ * Furthermore, you can choose whether any class-types for properties should
+ * use absolute/global paths (ie `\Foo\Bar\Baz`), or whether they should use
+ * paths relative to the class they are owned by (ie `Baz`) when possible.
+ * And that's ONLY possible whenever the target type lives within the same
+ * namespace as the class. Any properties from other namespaces will still
+ * use absolute paths.
+ *
+ * Note that if relative types are used, they are ALWAYS relative to the
+ * class that the current object IS AN INSTANCE OF. This is true even when
+ * those properties were inherited/imported from another class!
+ *
+ * Relative mode is mostly meant for class-documentation, to be placed
+ * inside each of your class files. That way, the relative paths will be
+ * perfectly understood by your IDE. The absolute, non-relative format is
+ * preferable in other, more general "runtime usage" since it is totally
+ * clear about exactly which final object each class path refers to.
+ *
+ * @param bool $allowRelativeTypes If `TRUE`, object types will use relative
+ * paths (compared to this class) whenever
+ * possible.
+ * @param bool $includeUndefined Whether to also include properties that
+ * only exist in the current object
+ * instance's JSON data, but aren't defined
+ * in the actual class property map.
+ *
+ * @throws LazyJsonMapperException If any properties cannot be described.
+ * But that should never be able to happen.
+ *
+ * @return PropertyDescription[] Associative array of property descriptions,
+ * sorted by property name in case-insensitive
+ * natural order.
+ *
+ * @see LazyJsonMapper::printPropertyDescriptions()
+ */
+ final public function exportPropertyDescriptions(
+ $allowRelativeTypes = false,
+ $includeUndefined = false)
+ {
+ if (!is_bool($allowRelativeTypes) || !is_bool($includeUndefined)) {
+ throw new LazyJsonMapperException('The function arguments must be booleans.');
+ }
+
+ // First include all of the defined properties for the current class.
+ $descriptions = [];
+ $ownerClassName = get_class($this);
+ foreach ($this->_compiledPropertyMapLink as $propName => $propDef) {
+ $descriptions[$propName] = new PropertyDescription( // Throws.
+ $ownerClassName,
+ $propName,
+ $propDef,
+ $allowRelativeTypes
+ );
+ }
+
+ // Also include all undefined, JSON-only data properties if desired.
+ if ($includeUndefined) {
+ $undefinedProperty = UndefinedProperty::getInstance();
+ foreach ($this->_objectData as $propName => $v) {
+ if (!isset($descriptions[$propName])) {
+ $descriptions[$propName] = new PropertyDescription( // Throws.
+ $ownerClassName,
+ $propName,
+ $undefinedProperty,
+ $allowRelativeTypes
+ );
+ }
+ }
+ }
+
+ // Sort the descriptions by the case-insensitive name of each property.
+ ksort($descriptions, SORT_NATURAL | SORT_FLAG_CASE); // Natural order.
+
+ return $descriptions;
+ }
+
+ /**
+ * Print human-readable descriptions of class/object instance properties.
+ *
+ * This helper is provided as a quick and easy debug feature, and helps you
+ * look at your compiled class property maps, or to quickly look up how a
+ * certain property needs to be accessed in its function-name form.
+ *
+ * Please read the description of `exportPropertyDescriptions()` if you want
+ * more information about the various options.
+ *
+ * @param bool $showFunctions Whether to show the list of functions for
+ * each property. Which is very helpful, but
+ * also very long. So you may want to
+ * disable this option.
+ * @param bool $allowRelativeTypes If `TRUE`, object types will use relative
+ * paths (compared to this class) whenever
+ * possible.
+ * @param bool $includeUndefined Whether to also include properties that
+ * only exist in the current object
+ * instance's JSON data, but aren't defined
+ * in the actual class property map.
+ *
+ * @throws LazyJsonMapperException If any properties cannot be described.
+ * But that should never be able to happen.
+ *
+ * @see LazyJsonMapper::exportPropertyDescriptions()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function printPropertyDescriptions(
+ $showFunctions = true,
+ $allowRelativeTypes = false,
+ $includeUndefined = false)
+ {
+ if (!is_bool($showFunctions) || !is_bool($allowRelativeTypes) || !is_bool($includeUndefined)) {
+ throw new LazyJsonMapperException('The function arguments must be booleans.');
+ }
+
+ // Generate the descriptions.
+ $descriptions = $this->exportPropertyDescriptions( // Throws.
+ $allowRelativeTypes,
+ $includeUndefined
+ );
+
+ // Create some bars for output formatting.
+ $equals_bar = str_repeat('=', 60);
+ $dash_bar = str_repeat('-', 60);
+
+ // Header.
+ printf(
+ '%s%s> Class: "%s"%s Supports: [%s] Virtual Functions [%s] Virtual Properties%s%s%s Show Functions: %s.%s Allow Relative Types: %s.%s Include Undefined Properties: %s.%s%s%s',
+ $equals_bar,
+ PHP_EOL,
+ Utilities::createStrictClassPath(get_class($this)),
+ PHP_EOL,
+ static::ALLOW_VIRTUAL_FUNCTIONS ? 'X' : ' ',
+ static::ALLOW_VIRTUAL_PROPERTIES ? 'X' : ' ',
+ PHP_EOL,
+ $dash_bar,
+ PHP_EOL,
+ $showFunctions ? 'Yes' : 'No',
+ PHP_EOL,
+ $allowRelativeTypes ? 'Yes' : 'No',
+ PHP_EOL,
+ $includeUndefined ? 'Yes' : 'No',
+ PHP_EOL,
+ $equals_bar,
+ PHP_EOL
+ );
+
+ // Properties.
+ $lastPropertyNum = count($descriptions);
+ $padNumDigitsTo = strlen($lastPropertyNum);
+ if ($padNumDigitsTo < 2) {
+ $padNumDigitsTo = 2; // Minimum 2-digit padding: "09".
+ }
+ $alignPadding = 4 + (2 * $padNumDigitsTo); // " #/" plus the digits.
+ $thisPropertyNum = 0;
+ foreach ($descriptions as $property) {
+ $thisPropertyNum++;
+
+ // Output core information about the property.
+ printf(
+ ' #%s/%s: "%s"%s%s%s: "%s"%s%s',
+ str_pad($thisPropertyNum, $padNumDigitsTo, '0', STR_PAD_LEFT),
+ str_pad($lastPropertyNum, $padNumDigitsTo, '0', STR_PAD_LEFT),
+ $property->name,
+ !$property->is_defined ? ' (Not in class property map!)' : '',
+ PHP_EOL,
+ str_pad('* Type', $alignPadding, ' ', STR_PAD_LEFT),
+ $property->type,
+ $property->is_basic_type ? ' (Basic PHP type)' : '',
+ PHP_EOL
+ );
+
+ // Optionally output the function list as well.
+ if ($showFunctions) {
+ foreach (['has', 'is', 'get', 'set', 'unset'] as $function) {
+ printf(
+ '%s: %s%s',
+ str_pad($function, $alignPadding, ' ', STR_PAD_LEFT),
+ $property->{"function_{$function}"},
+ PHP_EOL
+ );
+ }
+ }
+
+ // Dividers between properties.
+ if ($thisPropertyNum !== $lastPropertyNum) {
+ echo $dash_bar.PHP_EOL;
+ }
+ }
+
+ // Handle empty property lists.
+ if (empty($descriptions)) {
+ echo '- No properties.'.PHP_EOL;
+ }
+
+ // Footer.
+ echo $equals_bar.PHP_EOL;
+ }
+
+ /**
+ * Get a processed copy of this object instance's internal data contents.
+ *
+ * It is recommended that you save the result if you intend to re-use it
+ * multiple times, since each call to this function will need to perform
+ * copying and data conversion from our internal object representation.
+ *
+ * Note that the conversion process will recursively validate and convert
+ * all properties in the entire internal data hierarchy. You can trust that
+ * the returned result will be perfectly accurate and follow your class map
+ * property type-rules. This does however mean that the data may not be the
+ * same as the input array you gave to `__construct()`. For example, if your
+ * input contained an array `["1", "2"]` and your type definition map says
+ * that you want `int` for that property, then you'd get `[1, 2]` as output.
+ *
+ * The conversion is always done on a temporary COPY of the internal data,
+ * which means that you're welcome to run this function as much as you want
+ * without causing your object to grow from resolving all its sub-objects.
+ *
+ * It also means the returned copy belongs to YOU, and that you're able to
+ * do ANYTHING with it, without risk of affecting any of OUR internal data.
+ *
+ * `WARNING:` If you intend to use the result to `json_encode()` a new JSON
+ * object then please DON'T do that. Look at `asJson()` instead, which gives
+ * you full control over all JSON output parameters and properly handles the
+ * conversion in all scenarios, without you needing to do any manual work.
+ *
+ * Also look at the separate `asStdClass()` and `asArray()` functions, for
+ * handy shortcuts instead of manually having to call this longer function.
+ *
+ * @param string $objectRepresentation What container to use to represent
+ * `LazyJsonMapper` objects. Can be
+ * either `array` (useful if you require
+ * that the result is compatible with
+ * `__construct()`), or `stdClass` (the
+ * best choice if you want to be able to
+ * identify subobjects via
+ * `is_object()`).
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return stdClass|array A processed copy of the internal data, using the
+ * desired container type to represent all objects.
+ *
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function exportObjectDataCopy(
+ $objectRepresentation = 'array')
+ {
+ if (!in_array($objectRepresentation, ['stdClass', 'array'], true)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Invalid object representation type "%s". Must be either "stdClass" or "array".',
+ $objectRepresentation
+ ));
+ }
+
+ // Make a shallow copy (copy-on-write) so that we will avoid affecting
+ // our actual internal object data during the parsing below. Otherwise
+ // we would turn all of OUR internal, still-unconverted sub-object
+ // arrays into real objects on our REAL instance, and permanently cause
+ // our object instance's memory usage to go up after an export call.
+ $copy = clone $this;
+
+ // Perform a NON-RECURSIVE analysis of the copy's top-level (own)
+ // properties. This will CONVERT all of their values to the proper type,
+ // and perform further sub-object creation (convert sub-object arrays
+ // into real objects), etc. It also ensures no illegal values exist.
+ // NOTE: For performance, we DON'T want recursive analysis, since we'll
+ // soon be asking any sub-objects to analyze themselves one-by-one too!
+ $analysis = $copy->exportClassAnalysis(false); // Never throws.
+
+ // Abort if there are any BAD definitions (conversion failure due to
+ // mismatches between defined class map and the actual data).
+ if (!empty($analysis->bad_definitions)) { // Ignore missing_definitions
+ $problemSummaries = $analysis->generateNiceSummaries();
+
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert data to %s: %s',
+ $objectRepresentation,
+ $problemSummaries['bad_definitions'] // Tells them exact error.
+ ));
+ }
+
+ // Now recursively process every other sub-object within the copy's data
+ // via exportObjectDataCopy(), thus forcing all nested sub-objects
+ // (which are still DIRECT REFERENCES to OUR "$this" original versions
+ // of the objects, since objects are always by reference) to copy and
+ // validate/convert THEMSELVES and their data and then return it
+ // within the type of container we need. For a perfect final result.
+ // NOTE: We iterate by &$value reference, but that WON'T affect the
+ // objects they point to. It's a reference to the "_objectData" entry.
+ array_walk_recursive($copy->_objectData, function (&$value, $key) use ($objectRepresentation) {
+ // Only process objects. Everything else is already perfect (either
+ // converted to the target-type if mapped or is valid "mixed" data).
+ if (is_object($value)) {
+ // Verify that the object is an instance of LazyJsonMapper.
+ // NOTE: It SHOULDN'T be able to be anything else, since the
+ // object analysis above has already verified that any "mixed"
+ // (non-LazyJsonMapper) properties only contain basic PHP types.
+ // But this check is a cheap safeguard against FUTURE bugs.
+ if (!$value instanceof self) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert data to %s: Unexpected "%s" object in property/key "%s", but we expected an instance of a LazyJsonMapper object.',
+ $objectRepresentation,
+ Utilities::createStrictClassPath(get_class($value)),
+ $key
+ ));
+ }
+
+ // Now just ask the sub-object to give us a copy of ITS verified
+ // + converted data wrapped in the desired container type.
+ // NOTE: This will be resolved recursively as-necessary for all
+ // objects in the entire data tree.
+ $value = $value->exportObjectDataCopy($objectRepresentation); // Throws.
+ }
+ });
+
+ // Convert the outer data-holder to the object-representation type.
+ if ($objectRepresentation === 'stdClass') {
+ // When they want a stdClass, we MUST create it ourselves and assign
+ // all of the internal data key-value pairs on it, with string-keys.
+ $outputContainer = new stdClass();
+ foreach ($copy->_objectData as $k => $v) {
+ $outputContainer->{(string) $k} = $v;
+ }
+ } else { // 'array'
+ // For an array-representation, we'll simply steal the copy's array.
+ $outputContainer = $copy->_objectData;
+ }
+
+ // Voila, we now have their desired container, with cleaned-up and fully
+ // validated copies of all of our internal data. And we haven't affected
+ // any of our real $this data at all. Clones rock!
+ // NOTE: And since $copy goes out of scope now, there will be no other
+ // references to any of the data inside the container. It is therefore
+ // fully encapsulated, standalone data which is safe to manipulate.
+ return $outputContainer;
+ }
+
+ /**
+ * Get a processed copy of this object instance's data wrapped in stdClass.
+ *
+ * This function is just a shortcut/alias for `exportObjectDataCopy()`.
+ * Please read its documentation to understand the full implications and
+ * explanation for the behavior of this function.
+ *
+ * All objects in the returned value will be represented as `stdClass`
+ * objects, which is important if you need to be able to identify nested
+ * internal sub-objects via `is_object()`.
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return stdClass A processed copy of the internal data, with all objects
+ * represented as stdClass.
+ *
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function asStdClass()
+ {
+ return $this->exportObjectDataCopy('stdClass'); // Throws.
+ }
+
+ /**
+ * Get a processed copy of this object instance's data as an array.
+ *
+ * This function is just a shortcut/alias for `exportObjectDataCopy()`.
+ * Please read its documentation to understand the full implications and
+ * explanation for the behavior of this function.
+ *
+ * All objects in the returned value will be represented as nested arrays,
+ * which is good if you require that the result is compatible with
+ * `__construct()` again.
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return array A processed copy of the internal data, with all objects
+ * represented as arrays.
+ *
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function asArray()
+ {
+ return $this->exportObjectDataCopy('array'); // Throws.
+ }
+
+ /**
+ * Get a processed representation of this object instance's data as JSON.
+ *
+ * This helper gives you a JSON string representation of the object's
+ * internal data, and provides the necessary interface to fully control
+ * the JSON encoding process.
+ *
+ * It handles all steps for you and wraps everything in nice exceptions,
+ * and will even clearly explain any `json_encode()` errors in plain English
+ * (although the default settings will never cause any encoding failures).
+ * And most importantly, this function also guarantees that your data is
+ * ALWAYS properly encoded as a valid JSON object `{}` (rather than risking
+ * getting the result as a JSON array such as `[]` or `["a","b","c"]`).
+ * We accept all of the same parameters as the `json_encode()` function!
+ *
+ * Note that we only encode properties that exist in the actual internal
+ * data. Anything that merely exists in the class property map is omitted.
+ *
+ * `WARNING:` It is worth saving the output of this function if you intend
+ * to use the result multiple times, since each call to this function will
+ * internally use `exportObjectDataCopy()`, which performs quite intensive
+ * work to recursively validate and convert values while creating the final
+ * representation of the object's internal JSON data. Please read the
+ * description of `exportObjectDataCopy()` for more information.
+ *
+ * @param int $options Bitmask to control `json_encode()` behavior.
+ * @param int $depth Maximum JSON depth. Encoding fails if set too low.
+ * Can almost always safely be left at `512` (default).
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return string
+ *
+ * @see http://php.net/json_encode
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::printJson()
+ * @see LazyJsonMapper::jsonSerialize() The native json_encode()
+ * serializer instead.
+ */
+ final public function asJson(
+ $options = 0,
+ $depth = 512)
+ {
+ if (!is_int($options) || !is_int($depth)) {
+ throw new LazyJsonMapperException('Invalid non-integer function argument.');
+ }
+
+ // Create a fully-validated, fully-converted final object-tree.
+ // NOTE: See `jsonSerialize()` for details about why we MUST do this.
+ $objectData = $this->exportObjectDataCopy('stdClass'); // Throws.
+
+ // Gracefully handle JSON encoding and validation.
+ $jsonString = @json_encode($objectData, $options, $depth);
+ if ($jsonString === false) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Failed to encode JSON string (error %d: "%s").',
+ json_last_error(), json_last_error_msg()
+ ));
+ }
+
+ return $jsonString;
+ }
+
+ /**
+ * Print a processed representation of this object instance's data as JSON.
+ *
+ * This helper is provided as a quick and easy debug feature, instead of
+ * having to manually write something like `var_dump($item->asJson())`.
+ *
+ * And it's also a much better alternative than PHP's common but totally
+ * unreadable `var_dump($item->asArray())` or `var_dump($item)` techniques.
+ *
+ * You can even take advantage of chained operations, if you are 100% sure
+ * that NONE of the properties along the way are `NULL`. An example of doing
+ * that would be `$container->getItems()[0]->getUser()->printJson()`.
+ * However, such code is ONLY recommended for quick debug tests, but not
+ * for actual production use, since you should always be handling any `NULL`
+ * properties along the way (unless you enjoy random errors whenever a
+ * particular property along the chain happens to be missing from the
+ * object's internal JSON data value storage).
+ *
+ * Please read the description of `asJson()` if you want more information
+ * about how the internal JSON conversion process works.
+ *
+ * @param bool $prettyPrint Use whitespace to nicely format the data.
+ * Defaults to `TRUE` since we assume that you want
+ * human readable output while debug-printing.
+ * @param int $depth Maximum JSON depth. Encoding fails if set too
+ * low. Can almost always safely be left at `512`
+ * (default).
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asStdClass()
+ * @see LazyJsonMapper::asArray()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printPropertyDescriptions()
+ */
+ final public function printJson(
+ $prettyPrint = true,
+ $depth = 512)
+ {
+ // NOTE: These options are important. For display purposes, we don't
+ // want escaped slashes or `\uXXXX` hex versions of UTF-8 characters.
+ $options = ($prettyPrint ? JSON_PRETTY_PRINT : 0) | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
+ $json = $this->asJson($options, $depth); // Throws.
+ if ($prettyPrint && PHP_EOL !== "\n") {
+ // PHP's JSON pretty-printing uses "\n" line endings, which must be
+ // translated for proper display if that isn't the system's style.
+ $json = str_replace("\n", PHP_EOL, $json);
+ }
+ echo $json.PHP_EOL;
+ }
+
+ /**
+ * Serialize this object to a value that's supported by json_encode().
+ *
+ * You are not supposed to call this directly. It is _automatically_ called
+ * by PHP whenever you attempt to `json_encode()` a `LazyJsonMapper` object
+ * or any data structures (such as arrays) which contain such objects. This
+ * function is then called and provides a proper "object representation"
+ * which can be natively encoded by PHP.
+ *
+ * Having this helper ensures that you can _easily_ encode very advanced
+ * structures (such as a regular PHP array which contains several nested
+ * `LazyJsonMapper`-based objects), _without_ needing to manually fiddle
+ * around with `asJson()` on every individual object within your array.
+ *
+ * You are instead able to simply `json_encode($mainObj->getSubArray())`,
+ * which will properly encode every array-element in that array, regardless
+ * of whether they're pure PHP types or nested `LazyJsonMapper` objects.
+ *
+ * Note that we only export properties that exist in the actual internal
+ * data. Anything that merely exists in the class property map is omitted.
+ *
+ * `WARNING:` It is worth saving the output of `json_encode()` if you intend
+ * to use the result multiple times, since each call to this function will
+ * internally use `exportObjectDataCopy()`, which performs quite intensive
+ * work to recursively validate and convert values while creating the final
+ * representation of the object's internal JSON data. Please read the
+ * description of `exportObjectDataCopy()` for more information.
+ *
+ * `WARNING:` In _most_ cases, you should be using `asJson()` (instead of
+ * `json_encode()`), since it's _far more_ convenient and completely wraps
+ * PHP's JSON encoding problems as exceptions. If you truly want to manually
+ * `json_encode()` the data, you'll still get complete data conversion and
+ * validation, but you _won't_ get any _automatic exceptions_ if PHP fails
+ * to encode the JSON, which means that _you'll_ have to manually check if
+ * the final value was successfully encoded by PHP. Just be aware of that!
+ *
+ * @throws LazyJsonMapperException If there are any conversion problems.
+ *
+ * @return stdClass A processed copy of the internal data, with all objects
+ * represented as stdClass. Usable by `json_encode()`.
+ *
+ * @see http://php.net/json_encode
+ * @see LazyJsonMapper::exportObjectDataCopy()
+ * @see LazyJsonMapper::asJson()
+ * @see LazyJsonMapper::printJson()
+ */
+ final public function jsonSerialize()
+ {
+ // Create a fully-validated, fully-converted final object-tree.
+ // NOTE: It is VERY important that we export the data-copy with objects
+ // represented as stdClass objects. Otherwise `json_encode()` will be
+ // unable to understand which parts are JSON objects (whose keys must
+ // always be encoded as `{}` or `{"0":"a","1":"b"}`) and which parts are
+ // JSON arrays (whose keys must always be encoded as `[]` or
+ // `["a","b"]`).
+ // IMPORTANT NOTE: Any "untyped" objects from the original JSON data
+ // input ("mixed"/undefined data NOT mapped to `LazyJsonMapper` classes)
+ // will be encoded using PHP's own auto-detection for arrays, which
+ // GUESSES based on the keys of the array. It'll be guessing perfectly
+ // in almost 100% of all cases and will encode even those "untyped"
+ // arrays as JSON objects again, EXCEPT if an object from the original
+ // data used SEQUENTIAL numerical keys STARTING AT 0, such as:
+ // `json_encode(json_decode('{"0":"a","1":"b"}', true))` which will
+ // result in `["a","b"]`. But that's incredibly rare (if ever), since it
+ // requires weird objects with numerical keys that START AT 0 and then
+ // sequentially go up from there WITHOUT ANY GAPS or ANY NON-NUMERIC
+ // keys. That should NEVER exist in any user's real JSON data, and it
+ // doesn't warrant internally representing all JSON object data as the
+ // incredibly inefficient stdClass type instead. If users REALLY want to
+ // perfectly encode such data as objects when they export as JSON, EVEN
+ // in the insanely-rare/impossible situation where their objects use
+ // sequential numeric keys, then they should at the very least map them
+ // to "LazyJsonMapper", which will ensure that they'll be preserved as
+ // objects in the final JSON output too.
+ return $this->exportObjectDataCopy('stdClass'); // Throws.
+ }
+
+ /**
+ * Handles automatic string conversion when the object is used as a string.
+ *
+ * Internally runs `asJson()`.
+ *
+ * `WARNING:` It's **dangerous** to rely on the automatic string conversion,
+ * since PHP doesn't allow this handler to throw any error/exceptions (if we
+ * do, PHP would die with a Fatal Error). So we cannot notify about errors.
+ *
+ * Therefore, any warnings and exceptions will silently be placed directly
+ * in the string output instead, enclosed in `<>` brackets to guarantee that
+ * they won't be interpreted as valid JSON.
+ *
+ * Smart programmers will instead call `$item->asJson()` manually. It is
+ * only a few more characters, and it guarantees that you can catch
+ * conversion errors and react to them appropriately.
+ *
+ * There is only ONE scenario where `__toString()` is reliable: That's if
+ * your JSON data was constructed with the `$requireAnalysis` constructor
+ * argument, which recursively ensures that all data type-conversions and
+ * all nested sub-object constructions are successful before your object is
+ * allowed to be created. Furthermore, you must not have manipulated
+ * anything via "direct property access by reference" AFTER that (as
+ * explained in `__get()`), so that you're sure that all of your internal
+ * data is truly valid.
+ *
+ * But that is all moot anyway, because always using the debug-only option
+ * `$requireAnalysis` defeats the purpose of memory-efficient lazy-loading
+ * and slows down the creation of all of your class instances. Don't do it.
+ *
+ * In short: `__toString()` is here as a nice bonus for completeness sake,
+ * but you should never use it in production. Just use `asJson()` instead!
+ *
+ * @var string
+ *
+ * @see LazyJsonMapper::asJson()
+ */
+ final public function __toString()
+ {
+ try {
+ return $this->asJson(); // Throws.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ // __toString() is not allowed to throw, so give generic info in a
+ // way that's definitely going to be invalid JSON data, so that the
+ // user will notice the problem if they use this method to manually
+ // generate JSON data, such as if they're doing '["i":'.$item.']'.
+ // NOTE: "" was chosen since it's illegal JSON.
+ return sprintf('<%s>', $e->getMessage());
+ }
+ }
+
+ /**
+ * Analyze the entire object and check for undefined or bad JSON properties.
+ *
+ * This lets you analyze the object data & map to look for the following:
+ *
+ * 1. Undefined JSON properties that need to be defined in your classes
+ * so that their existence becomes permanent and therefore safer to use
+ * (since defined properties are always retrievable even if they don't
+ * exist in the current object instance's data). And actually defining
+ * the properties is also the ONLY way that you can enforce a specific
+ * data-type, since undefined properties default to untyped (`mixed`).
+ *
+ * 2. Bad class map definitions that don't match the actual JSON data.
+ * It does this by checking all of the encountered classes within the JSON
+ * data to ensure that they construct successfully (which they ALWAYS
+ * will, unless the user has overridden `_init()` and throws from it), as
+ * well as verifying that all basic PHP type coercions work, and that the
+ * data is formatted as-described in the definition (such as having the
+ * correct array-depth, or data defined as objects actually being objects).
+ *
+ * The scan is performed by looking at EVERY item in the object's internal
+ * JSON data array, and checking for a corresponding class map definition
+ * (if nothing exists, it's treated as "missing from the class map"), and
+ * then attempting conversion to its specified type (if that fails, it's
+ * treated as "bad class map definition"). All undefined values (which lack
+ * any class map definition) are ALSO validated as if they were `mixed`,
+ * to ensure that NOTHING can contain illegal values.
+ *
+ * If a recursive scan is requested, then all sub-objects (`LazyJsonMapper`
+ * data containers) will ALSO recursively perform the same analysis on their
+ * own internal data, so that the whole JSON data tree is recursively
+ * verified. Their analysis results will be merged with our return value.
+ *
+ * Note that calling this function will cause the WHOLE object tree to be
+ * constructed and ALL of its mapped properties to be converted/parsed to
+ * verify the class map, which takes some time and permanently increases the
+ * object's memory usage (since conversion causes any internal sub-objects
+ * to be stored as actual objects instead of merely as plain sub-arrays)!
+ *
+ * Therefore, you should ONLY use this function when necessary: When you
+ * need advanced and powerful debugging of your class maps!
+ *
+ * @param bool $recursiveScan Whether to also verify missing/bad properties
+ * in sub-objects in the analysis. Recommended.
+ *
+ * @return ClassAnalysis An object describing the problems with this class.
+ */
+ final public function exportClassAnalysis(
+ $recursiveScan = true)
+ {
+ $result = new ClassAnalysis();
+
+ // All problems with OUR class will get filed under our class name.
+ $definitionSource = get_class($this);
+
+ // Ensure that all object-data properties exist in our class definition,
+ // and that their values can be converted as described in the class map.
+ foreach ($this->_objectData as $propName => $value) {
+ // Check if property exists in the class map and get its definition.
+ // NOTE: Normally, this function can throw, but not with the way we
+ // are calling it, because we KNOW that the property exists in at
+ // least the object data, so we DON'T have to catch any errors!
+ $propDef = $this->_getPropertyDefinition($propName);
+
+ // Regardless of whether it's defined or not, we MUST now "get" it
+ // to validate/convert the property's contents, to ensure it's safe.
+ try {
+ // Trying to "get" the property forces complete validation of
+ // the internal property data and non-recursively creates all
+ // sub-objects (if any). In case of any errors, this call will
+ // throw an exception with an error description.
+ // NOTE: Calling this getter is EXTREMELY important even for
+ // UNDEFINED (untyped) properties, because it ensures that no
+ // illegal data values (such as Resources or non-LazyJsonMapper
+ // objects) exist within the data even in undefined properties.
+ // NOTE: In the case of arrays, it validates the whole
+ // arrayDepth and ensures that the array is well-formed and
+ // contains the exact type at the exact depth specified (but it
+ // accepts NULL values anywhere in the chain of arrays, and it
+ // accepts empty arrays at non-max depth). It also verifies
+ // type-coercion to built-in PHP types. And in the case of
+ // objects, it verifies that they are successfully constructed.
+ $value = $this->_getProperty($propName); // Throws.
+
+ // Recursively check all internal objects to make sure they also
+ // have all properties from their own raw JSON data.
+ // NOTE: Nothing in this sub-block throws any exceptions, since
+ // everything was validated by _getProperty(). And the deeper
+ // exportClassAnalysis() calls don't throw anything either.
+ if ($recursiveScan && $value !== null && $propDef->isObjectType) {
+ // NOTE: We don't have to validate array-depth/type
+ // correctness, since _getProperty() already did that for
+ // us. Nothing will be isObjectType unless it's really a
+ // LazyJsonMapper class. Also, since the $value is not NULL
+ // (see above) and it's an "array of [type]" property, then
+ // we KNOW the $value is_array(), since it validated as OK.
+ if ($propDef->arrayDepth > 0) { // Array of objects.
+ array_walk_recursive($value, function (&$obj) use (&$result) {
+ if (is_object($obj)) { // Could be inner NULL too.
+ $result->mergeAnalysis($obj->exportClassAnalysis());
+ }
+ });
+ } else { // Non-"array of" object property.
+ $result->mergeAnalysis($value->exportClassAnalysis());
+ }
+ }
+ } catch (LazyJsonMapperException $e) {
+ // Unable to get the value of this property... which usually
+ // means that the property cannot be type-coerced as requested,
+ // or that the JSON data doesn't match the definition (such as
+ // having a deeper JSON array than what the definition says), or
+ // that invalid data was encountered (such as encountering an
+ // internal non-Lazy object/resource instead of the expected
+ // data), or that the property's class could not be constructed
+ // (which may really only happen due to a user's custom _init()
+ // function failing, since our own classmap's compilation has
+ // already taken care of successfully verifying the classmap
+ // compilation of ALL classes referred to by our ENTIRE property
+ // hierarchy). All of those are user-caused errors...
+
+ // We'll save the exception details message as-is...
+ // TODO: This also means that undefined properties containing
+ // weird data (objects or resources) would throw above and get
+ // logged as "bad definition" despite NOT having any definition.
+ // We may want to add a check here for UndefinedProperty and
+ // prepend something to the exception message in that case, to
+ // tell the user that their UNDEFINED property contained the bad
+ // data. But seriously, they can NEVER have such invalid data
+ // inside of a real json_decode() input array, so nobody sane is
+ // going to be warned of "bad definition" for their undefined
+ // properties! And we can't just log it as "missing" since we
+ // NEED all "bad data" to be logged as bad_definitions, since
+ // that's what our various data validators look at to detect
+ // SERIOUS data errors! Besides, their missing definition WILL
+ // also get logged under "missing_definitions" in the next step.
+ $result->addProblem(
+ $definitionSource,
+ 'bad_definitions',
+ $e->getMessage()
+ );
+ }
+
+ // Now check if we lacked a user-definition for this JSON property.
+ if ($propDef instanceof UndefinedProperty) {
+ // NOTE: We need the (string) casting in case of int-keys. Which
+ // can happen if the user gives us a manually created, non-JSON
+ // constructor array containing non-associative integer keys.
+ $result->addProblem(
+ $definitionSource,
+ 'missing_definitions',
+ (string) $propName
+ );
+ }
+ }
+
+ // Nicely sort the problems and remove any duplicates.
+ $result->sortProblemLists();
+
+ return $result;
+ }
+
+ /**
+ * Check if a property definition exists.
+ *
+ * These are properties defined in the JSON property map for the class.
+ *
+ * `NOTE:` This is the STRICTEST function, which checks if a property is
+ * defined in the class. It rejects properties that only exist in the data.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ */
+ final protected function _hasPropertyDefinition(
+ $propName)
+ {
+ return isset($this->_compiledPropertyMapLink[$propName]);
+ }
+
+ /**
+ * Check if a property definition or an object instance data value exists.
+ *
+ * These are properties that are either defined in the JSON property map
+ * for the class OR that exist in the object instance's data.
+ *
+ * `NOTE:` This is the RECOMMENDED function for checking if a property is
+ * valid, since properties ARE VALID if they exist in the class definition
+ * OR in the object instance's data.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ */
+ final protected function _hasPropertyDefinitionOrData(
+ $propName)
+ {
+ return isset($this->_compiledPropertyMapLink[$propName])
+ || array_key_exists($propName, $this->_objectData);
+ }
+
+ /**
+ * Check if an object instance data value exists.
+ *
+ * These are properties that currently exist in the object instance's data.
+ *
+ * `NOTE:` This function ISN'T RECOMMENDED unless you know what you're doing.
+ * Because any property that exists in the class definition is valid as
+ * well, and can be retrieved even if it isn't in the current object data.
+ * So `_hasPropertyDefinitionOrData()` is recommended instead, if your goal
+ * is to figure out if a property name is valid. Alternatively, you can use
+ * `_hasPropertyDefinition()` if you want to be even stricter by requiring
+ * that the property is defined in the class map.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ *
+ * @see LazyJsonMapper::_hasPropertyDefinition()
+ * @see LazyJsonMapper::_hasPropertyDefinitionOrData()
+ */
+ final protected function _hasPropertyData(
+ $propName)
+ {
+ return array_key_exists($propName, $this->_objectData);
+ }
+
+ /**
+ * Get the property definition for an object data property.
+ *
+ * If the property doesn't exist in the class definition but exists in the
+ * object instance's data, then it will be treated as an undefined & untyped
+ * property. This ensures that the user can access undefined properties too!
+ *
+ * However, you should always strive to keep your classes up-to-date so that
+ * all properties are defined, otherwise they'll be totally inaccessible
+ * (instead of returning `NULL`) anytime they're missing from the JSON data.
+ *
+ * @param string $propName The property name.
+ * @param bool $allowUndefined Whether to allow default definitions when
+ * the property exists in the data but not in
+ * our actual class property map.
+ *
+ * @throws LazyJsonMapperException If property isn't defined in the class
+ * map and also doesn't exist in the object
+ * instance's data. Note that if undefined
+ * definitions are disallowed, we will ONLY
+ * check in the class definition map.
+ *
+ * @return PropertyDefinition An object describing the property.
+ */
+ final protected function _getPropertyDefinition(
+ $propName,
+ $allowUndefined = true)
+ {
+ if (isset($this->_compiledPropertyMapLink[$propName])) {
+ // Custom class property definition exists, so use that. Properties
+ // defined in the class map are always valid even if not in data!
+ return $this->_compiledPropertyMapLink[$propName];
+ } elseif ($allowUndefined && array_key_exists($propName, $this->_objectData)) {
+ // No property definition exists, but the property exists in the
+ // object instance's data, so treat it as undefined & untyped.
+ return UndefinedProperty::getInstance();
+ } else {
+ // There is no definition & no object instance data (or disallowed)!
+ throw new LazyJsonMapperException(sprintf(
+ 'No such object property "%s".',
+ $propName
+ ));
+ }
+ }
+
+ /**
+ * Get the value of an object data property.
+ *
+ * This function automatically reads the object instance's data and converts
+ * the value to the correct type on-the-fly. If that part of the data-array
+ * is an object, it will be lazy-created the first time it's requested.
+ *
+ * `NOTE:` If the object instance's internal data doesn't contain a value, but
+ * it's listed in the class property definition, then it will be treated as
+ * missing and `NULL` will automatically be returned instead. However, if
+ * it's missing from the class definition AND the object instance's data,
+ * then it's treated as an invalid property and an exception is thrown.
+ *
+ * Because of the fact that the default return value for internally-missing
+ * but "class-valid" properties is `NULL`, it means that you cannot discern
+ * whether the `NULL` came from actual JSON data or from the default return
+ * value. However, if that distinction matters to you, you should simply
+ * call `_hasPropertyData()` before retrieving the value.
+ *
+ * `IMPORTANT:` This function performs return-by-reference, which was done
+ * in order to allow certain ADVANCED programming tricks by the caller. If
+ * you save our return value by reference, you'll then have a direct link to
+ * the internal data for that property and can write directly to it
+ * (including writing invalid data if you want to, since we cannot verify
+ * what you do with your reference). However, you A) don't have to worry
+ * about invalid data, because it will all get validated at the next
+ * `_getProperty()` call again, and B) you have to explicitly bind the
+ * returned value by reference to actually risk affecting the internal
+ * data, as explained below.
+ *
+ * Method 1 (recommended for almost 100% of all usages, safest):
+ *
+ * ```php
+ * $val = $this->_getProperty('foo'); // Copy-on-write assignment to $val.
+ * $val = 'bar'; // Does NOT modify the internal data "foo" property.
+ * ```
+ *
+ * Method 2 (dangerous, and this example even contains an intentional bug):
+ *
+ * ```php
+ * $val = &$this->_getProperty('foo'); // Reference assignment to $val.
+ * $val = 'bar'; // Direct link, thus modifies the internal "foo" property.
+ * ```
+ *
+ * `SERIOUS WARNING:` If you use Method 2, do NOT use the code above. It was
+ * just explained that way to demonstrate a SERIOUS MISTAKE. If you assign
+ * by reference, you obviously intend to link directly to internal data. But
+ * the code above fails if no internal data for that property exists yet. In
+ * that case, the code above will write to a temporary `NULL` variable which
+ * is not linked at all to the object. Instead, you MUST ALWAYS use
+ * `$createMissingValue = true` if you want TRUSTABLE data references.
+ *
+ * Method 2 (the CORRECT way of writing it):
+ *
+ * ```php
+ * $val = &$this->_getProperty('foo', true);
+ * $val = 'bar';
+ * ```
+ *
+ * That FIXED code will create the property and put `NULL` in it if it's
+ * missing from the internal data, and then returns the reference to the
+ * created internal data entry. It's the ONLY way to ensure REAL references.
+ *
+ * Note that there is ZERO PERFORMANCE BENEFIT of assigning our return-value
+ * by reference. ONLY use references if you have an ADVANCED reason for it!
+ * Internally, PHP treats both assignment types identically until the moment
+ * you try to write to/modify the contents of the variable. So if you are
+ * only reading, which is most of the time, then there's no reason to use a
+ * reference. And never forget that modifying via direct reference lets you
+ * write invalid data (which won't be detected until a `_getProperty()`
+ * call attempts to read that bad data again), as mentioned earlier.
+ *
+ * There is one final warning: If YOU have a "return-by-reference" function,
+ * then you will AUTOMATICALLY return a direct link to the inner data if you
+ * use us directly in your return statement. Here's an example of that:
+ *
+ * ```php
+ * public function &myFunction() { // <-- Note the "&" return-by-ref here!
+ * // returns reference to real internal data OR temp var (badly coded)
+ * return $this->_getProperty('foo'); // <-- VERY DANGEROUS BUG!
+ *
+ * // returns reference to real internal data (correctly coded)
+ * return $this->_getProperty('foo', true); // <-- CORRECT!
+ *
+ * // you can also intentionally break the link to the internal data,
+ * // by putting it in a copy-on-write variable and returning THAT:
+ * $copyOnWrite = $this->_getProperty('foo'); // copy-on-write (no ref)
+ * return $copyOnWrite; // ref to $copyOnWrite but not to real internal
+ * }
+ * ```
+ *
+ * @param string $propName The property name.
+ * @param bool $createMissingValue If `TRUE` then we will create missing
+ * internal object data entries (for
+ * class-defined properties) and store a
+ * `NULL` in them. This will "pollute" the
+ * internal data with default `NULL`s for
+ * every missing property that you attempt
+ * to retrieve, but it's totally NECESSARY
+ * if you intend to use the return-value
+ * by-reference, since we MUST store the
+ * value internally when you want the
+ * returned reference to link to our
+ * actual object's internal data storage!
+ *
+ * @throws LazyJsonMapperException If the value can't be turned into its
+ * assigned class or built-in PHP type, or
+ * if the property doesn't exist in either
+ * the class property definition or the
+ * object instance's data.
+ *
+ * @return mixed The value as the correct type, or `NULL` if it's either a
+ * literal `NULL` in the data or if no value currently existed
+ * in the internal data storage at all. Note that this
+ * function returns the value by-reference.
+ */
+ final protected function &_getProperty(
+ $propName,
+ $createMissingValue = false)
+ {
+ // Check if the property exists in class/data and get its definition.
+ $propDef = $this->_getPropertyDefinition($propName); // Throws.
+
+ // Assign the appropriate reference to the "$value" variable.
+ if (array_key_exists($propName, $this->_objectData)) {
+ // The value exists in the data, so refer directly to it.
+ $value = &$this->_objectData[$propName]; // IMPORTANT: By reference!
+ } elseif ($createMissingValue) {
+ // No value exists in data yet, AND the caller wants us to create
+ // a default NULL value for the data property in that situation.
+ // NOTE: This is IMPORTANT so that the returned "default NULL"
+ // by-reference value will refer to the correct internal data
+ // array entry instead of to an unrelated, temporary variable.
+ // NOTE: The downside to this is that we'll fill the object's
+ // internal data storage with a bunch of default "NULL" values,
+ // which increases memory needs and messes with the "to JSON"
+ // output functions later. But it's unavoidable. We obviously
+ // CANNOT return a real reference to an internal data entry
+ // unless we CREATE the entry. So we MUST unfortunately do it!
+ // NOTE: This "pollution" is only a problem if the caller intends
+ // to use the property by reference, which isn't the case for
+ // default __call() (the virtual functions), but IS necessary
+ // in the default __get() (direct property access by reference).
+ $this->_objectData[$propName] = null;
+
+ // Now just link to the data array entry we just added.
+ $value = &$this->_objectData[$propName]; // IMPORTANT: By reference!
+ return $value; // OPTIMIZATION: Skip convert() for missing data.
+ } else {
+ // No value exists in data yet, but the caller didn't want us to set
+ // the missing value. So we'll simply use a default NULL variable.
+ // NOTE: If the caller tries to write to this one by reference,
+ // they'll just modify this temporary variable instead of the
+ // internal data. To link to real they MUST $createMissingValue.
+ // NOTE: We MUST return a variable, since "return null" is illegal.
+ $value = null; // Copy-on-write. Temporary variable to be returned.
+ return $value; // OPTIMIZATION: Skip convert() for missing data.
+ }
+
+ // Map the value to the appropriate type and validate it.
+ ValueConverter::convert( // Throws.
+ ValueConverter::CONVERT_FROM_INTERNAL,
+ $value, $propDef->arrayDepth, $propName, $propDef
+ );
+
+ // Whatever $value points at will now be returned by-reference.
+ return $value; // By-reference due to &function signature.
+ }
+
+ /**
+ * Check if an object data property exists and its value evaluates to TRUE.
+ *
+ * @param string $propName The property name.
+ *
+ * @return bool
+ */
+ final protected function _isProperty(
+ $propName)
+ {
+ // Object instance's data has the property and it evaluates to true?
+ return array_key_exists($propName, $this->_objectData)
+ && (bool) $this->_objectData[$propName];
+ }
+
+ /**
+ * Set an object data property to a new value.
+ *
+ * @param string $propName The property name.
+ * @param mixed $value The new value for the property. `NULL` is always
+ * allowed as the new value, regardless of type.
+ *
+ * @throws LazyJsonMapperException If the new value isn't legal for that
+ * property, or if the property doesn't
+ * exist in either the class property
+ * definition or the object instance's data.
+ *
+ * @return $this The current object instance, to allow chaining setters.
+ */
+ final protected function _setProperty(
+ $propName,
+ $value)
+ {
+ // Check if the property exists in class/data and get its definition.
+ $propDef = $this->_getPropertyDefinition($propName); // Throws.
+
+ // Map the value to the appropriate type and validate it.
+ ValueConverter::convert( // Throws.
+ ValueConverter::CONVERT_TO_INTERNAL,
+ $value, $propDef->arrayDepth, $propName, $propDef
+ );
+
+ // Assign the new value for the property.
+ $this->_objectData[$propName] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Erase the internal value of an object data property.
+ *
+ * This is useful for things like erasing certain parts of the JSON data
+ * tree before you're going to output the object as JSON. You can still
+ * continue to access and modify an "erased" property after unsetting it,
+ * but ONLY if that property is defined in your class property map.
+ *
+ * The current value is simply removed from the internal object data array,
+ * as if the object had been constructed without that part of the array.
+ *
+ * Also note that we act like the real unset() function, meaning that we
+ * don't care whether that property key exists in the object instance's
+ * data array. We'll simply unset it if it exists. Or otherwise gracefully
+ * do absolutely nothing.
+ *
+ * @param string $propName The property name.
+ *
+ * @return $this The current object instance, to allow chaining unsetters.
+ */
+ final protected function _unsetProperty(
+ $propName)
+ {
+ unset($this->_objectData[$propName]);
+
+ return $this;
+ }
+
+ /**
+ * __CALL is invoked when attempting to access missing functions.
+ *
+ * This magic handler auto-maps "virtual function" has-ers, is-ers, getters,
+ * setters and unsetters for all of the object's JSON data properties.
+ *
+ * - `bool hasX()` checks if "x" exists in the class definition and/or the
+ * object instance data. The `has`-functions are the only ones that never
+ * throw even if the property is invalid. This function is totally useless
+ * if you've defined the property in the class map, and will always return
+ * `TRUE` in that case. Its ONLY purpose is to allow you to look for
+ * UNDEFINED properties that may/may not exist in the current data, before
+ * you decide to call any of the other "throwy" functions on that
+ * property. In other words, it's used for working with UNMAPPED
+ * (undefined) properties!
+ * - `bool isX()` checks if "x" exists in the object instance's data and its
+ * current value evaluates to `TRUE`.
+ * - `mixed getX()` retrieves the value of "x". Uses copy-on-write (not a
+ * reference).
+ * - `$this setX(mixed $value)` sets the value of "x" to `$value`. The
+ * setters can be chained.
+ * - `$this unsetX()` erases the internal value from the object instance's
+ * data. The unsetters can be chained.
+ *
+ * @param string $functionName Name of the function being called.
+ * @param array $arguments Array of arguments passed to the function.
+ *
+ * @throws LazyUserOptionException If virtual functions are disabled.
+ * @throws LazyJsonMapperException If the function type or property name is
+ * invalid, or if there's any problem with
+ * the conversion to/from the object
+ * instance's internal data. As well as if
+ * the setter doesn't get exactly 1 arg.
+ *
+ * @return mixed The return value depends on which function is used.
+ *
+ * @see http://php.net/manual/en/language.oop5.magic.php
+ * @see LazyJsonMapper::_hasPropertyDefinitionOrData()
+ * @see LazyJsonMapper::_isProperty()
+ * @see LazyJsonMapper::_getProperty()
+ * @see LazyJsonMapper::_setProperty()
+ * @see LazyJsonMapper::_unsetProperty()
+ */
+ final public function __call(
+ $functionName,
+ $arguments)
+ {
+ if (!static::ALLOW_VIRTUAL_FUNCTIONS) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_FUNCTIONS_DISABLED
+ );
+ }
+
+ // Split the function name into its function-type and FuncCase parts.
+ list($functionType, $funcCase) = FunctionTranslation::splitFunctionName(
+ $functionName
+ );
+
+ // If the function didn't follow the [lower prefix][other] format, such
+ // as "getSomething", then this was an invalid function (type = NULL).
+ if ($functionType === null) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".',
+ $functionName
+ ));
+ }
+
+ // Resolve the FuncCase component into its equivalent property names.
+ if (static::USE_MAGIC_LOOKUP_CACHE && isset(self::$_magicLookupCache[$funcCase])) {
+ // Read the previously processed result from the lookup cache.
+ $translation = self::$_magicLookupCache[$funcCase];
+ } else {
+ // Attempt to parse the newly called function name.
+ try {
+ $translation = new FunctionTranslation($funcCase); // Throws.
+ } catch (MagicTranslationException $e) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".', $functionName
+ ));
+ }
+
+ // Store the processed result in the global lookup cache, if caching
+ // is allowed by the current class instance.
+ // NOTE: We'll store it even if the property doesn't exist on this
+ // particular object, because the user may be querying undefined
+ // data that WILL exist, and we don't want to waste time re-parsing.
+ if (static::USE_MAGIC_LOOKUP_CACHE) {
+ self::$_magicLookupCache[$funcCase] = $translation;
+ }
+ }
+
+ // Check for the existence of the "snake_case" property variant first,
+ // and if that fails then look for a "camelCase" property instead.
+ // NOTE: Checks both the class definition & the object instance's data.
+ if ($this->_hasPropertyDefinitionOrData($translation->snakePropName)) {
+ $propName = $translation->snakePropName; // We found a snake prop!
+ } elseif ($translation->camelPropName !== null
+ && $this->_hasPropertyDefinitionOrData($translation->camelPropName)) {
+ $propName = $translation->camelPropName; // We found camel instead.
+ } else {
+ // This object doesn't have the requested property! If this is a
+ // hasX() call, simply return false. In all other cases, throw!
+ if ($functionType === 'has') {
+ return false; // We don't have the property.
+ } else {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".',
+ $functionName
+ ));
+ }
+ }
+
+ // Return the kind of response expected by their desired function.
+ switch ($functionType) {
+ case 'has':
+ return true; // If we've come this far, we have the property.
+ break;
+ case 'is':
+ return $this->_isProperty($propName);
+ break;
+ case 'get':
+ // NOTE: This will NOT return a reference, since __call() itself
+ // does not support return-by-reference. We return a copy-on-write.
+ return $this->_getProperty($propName); // Throws.
+ break;
+ case 'set':
+ // They must provide exactly 1 argument for a setter call.
+ if (count($arguments) !== 1) {
+ // We know property exists; get its def from class or "undef".
+ $propDef = $this->_getPropertyDefinition($propName); // Throws.
+
+ throw new LazyJsonMapperException(sprintf(
+ 'Property setter requires exactly 1 argument: "%s(%s $value)".',
+ $functionName, $propDef->asString()
+ ));
+ }
+
+ // Returns $this so that the user can chain the setters.
+ return $this->_setProperty($propName, $arguments[0]); // Throws.
+ break;
+ case 'unset':
+ // NOTE: Normal PHP unset() calls would have a VOID return type. But
+ // ours actually returns $this so that the user can chain them.
+ return $this->_unsetProperty($propName);
+ break;
+ default:
+ // Unknown function type prefix...
+ throw new LazyJsonMapperException(sprintf(
+ 'Unknown function "%s".',
+ $functionName
+ ));
+ }
+ }
+
+ /**
+ * __GET is invoked when reading data from inaccessible properties.
+ *
+ * This magic handler takes care of "virtual property" access to the
+ * object's JSON data properties.
+ *
+ * `WARNING:` Note that the `__get()` "virtual property" handling creates
+ * `NULL` values in any missing (but valid in class-map) properties that you
+ * try to access! That is NECESSARY because PHP EXPECTS the `__get()` return
+ * value to be a REFERENCE to real internal data, so we MUST create a value
+ * if no value exists, so that we can link PHP to a true reference to the
+ * internal data. Obviously we can't link to something that doesn't exist,
+ * which is why we MUST create `NULL` values. Unfortunately that means that
+ * "virtual property access" will lead to increased memory usage and worse
+ * JSON output (due to all the added values) if you want to export the
+ * internal data as JSON later.
+ *
+ * The recommended access method, `$obj->getFoo()` ("virtual functions")
+ * doesn't have that problem. It ONLY happens when you decide to use
+ * `$obj->foo` for "direct virtual property" access.
+ *
+ * There are several other quirks with "direct virtual properties", due to
+ * how PHP works. If you don't write your code carefully, you may cause
+ * serious issues with performance or unexpected results.
+ *
+ * All of the important quirks are as follows:
+ *
+ * 1. The aforementioned necessary `NULL` value insertion increases memory
+ * usage and may lead to unexpected problems if you convert back to JSON,
+ * such as if the server does not expect those values to exist with `NULL`.
+ *
+ * 2. Anytime you use the array `[]` access operator on the outer property
+ * name, PHP will trigger a `__get()` call, which of course does its whole
+ * data-validation again. The following would therefore be EXTREMELY SLOW,
+ * and possibly even DANGEROUS (since you're manipulating the array while
+ * looping over its keys while constantly triggering `__get()` re-parsing):
+ *
+ * ```php
+ * foreach ($obj->something as $k => $v) {
+ * // Every assignment here will do __get('something') which parses
+ * // the array over and over again before each element is modified:
+ * $obj->something[$k] = 'foo';
+ * }
+ * ```
+ *
+ * The proper way to solve THAT is to either save a reference to
+ * `$obj->something` as follows:
+ *
+ * ```php
+ * $ref = &$obj->something; // Calls __get('something') a single time.
+ * foreach ($ref as $k => $v) {
+ * $ref[$k] = 'foo'; // Changes array directly via stored reference.
+ * }
+ * ```
+ *
+ * Or to loop through the input values as follows:
+ *
+ * ```php
+ * foreach ($obj->something as &$v) { // Note the "&" symbol.
+ * $v = 'foo'; // Changes array value directly via reference.
+ * }
+ * ```
+ *
+ * 3. Anytime you use a reference, there will be NO DATA VALIDATION of the
+ * new value you are inputting. It will not be checked again UNTIL the next
+ * internal `_getProperty()` call for that property (ie. by the next
+ * `__get()`) so you may therefore unintentionally insert bad data that
+ * doesn't match the class definition map:
+ *
+ * ```php
+ * // Saving a reference to internal data and then changing that variable
+ * // will directly edit the value without letting us validate it:
+ *
+ * $ref = &$obj->some_string; // Make "$ref" into a reference to data.
+ * $ref = new InvalidObject(); // Writes a bad value directly to memory.
+ * var_dump($obj->some_string); // Throws error because of bad data.
+ *
+ * // The same is true about array access. In this case, PHP does
+ * // __get('some_int_array') and finds an array, and then it directly
+ * // manipulates that array's memory without letting us validate it:
+ *
+ * $obj->some_int_array[] = new InvalidObject();
+ * $obj->some_int_array[15] = new InvalidObject();
+ * var_dump($obj->some_int_array); // Throws error because of bad data.
+ * ```
+ *
+ * 4. You can always trust that `__set()` assignments WILL be validated.
+ * But a `__set()` ONLY happens when you assign with the equals operator
+ * (`=`) to a pure property name WITHOUT any array access (`[]`) operators.
+ *
+ * ```php
+ * $obj->some_property = '123'; // Calls __set() and validates new value.
+ * $obj->an_array[] = '123' // Calls __get() and won't validate changes.
+ * ```
+ *
+ * These quirks of "virtual direct property access" are quite easy to deal
+ * with when you know about them, since almost all of them are about array
+ * access, and the rest are about intentional misuse by binding directly to
+ * references. Just avoid making those mistakes.
+ *
+ * However, in general, you should REALLY be using the "virtual function"
+ * access method instead, which allows you to do great things such as
+ * overriding certain class property-functions (ie `getSomething()`) with
+ * your own custom behaviors, so that you can do extra validation, get/set
+ * value transformations, and other fantastic things!
+ *
+ * It is possible (and recommended) to disable virtual properties via the
+ * `ALLOW_VIRTUAL_PROPERTIES` class constant. The feature is only enabled by
+ * default because it helps people easily migrate from legacy projects.
+ *
+ * @param string $propName The property name.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ * @throws LazyJsonMapperException If the value can't be turned into its
+ * assigned class or built-in PHP type, or
+ * if the property doesn't exist in either
+ * the class property definition or the
+ * object instance's data.
+ *
+ * @return mixed The value as the correct type, or `NULL` if it's either a
+ * literal `NULL` in the data or if no value currently existed
+ * in the internal data storage at all. Note that this
+ * function returns the value by-reference.
+ *
+ * @see LazyJsonMapper::_getProperty()
+ * @see LazyJsonMapper::__set()
+ */
+ final public function &__get(
+ $propName)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ // This does the usual validation/parsing of the value to ensure that it
+ // is a valid property. It then creates the property with a default NULL
+ // if it doesn't exist, and finally returns a direct reference to the
+ // internal data entry. That's NECESSARY to allow the user treat the
+ // "virtual property" like a real property, so that they can do things
+ // like array-modification, or binding to it by reference, and so on.
+ // NOTE: Because _getProperty() AND __get() are "return-by-reference"
+ // functions, this return-value is automatically a propagated reference.
+ return $this->_getProperty($propName, true); // Throws.
+ }
+
+ /**
+ * __SET is invoked when writing data to inaccessible properties.
+ *
+ * @param string $propName The property name.
+ * @param mixed $value The new value for the property. `NULL` is always
+ * allowed as the new value, regardless of type.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ * @throws LazyJsonMapperException If the new value isn't legal for that
+ * property, or if the property doesn't
+ * exist in either the class property
+ * definition or the object instance's data.
+ *
+ * @see LazyJsonMapper::_setProperty()
+ * @see LazyJsonMapper::__get()
+ */
+ final public function __set(
+ $propName,
+ $value)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ // NOTE: PHP ignores the return value of __set().
+ $this->_setProperty($propName, $value); // Throws.
+ }
+
+ /**
+ * __ISSET is invoked by calling isset() or empty() on inaccessible properties.
+ *
+ * `NOTE:` When the user calls `empty()`, PHP first calls `__isset()`, and if
+ * that's true it calls `__get()` and ensures the value is really non-empty.
+ *
+ * @param string $propName The property name.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ *
+ * @return bool `TRUE` if the property exists in the object instance's data
+ * and is non-`NULL`.
+ */
+ final public function __isset(
+ $propName)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ return isset($this->_objectData[$propName]);
+ }
+
+ /**
+ * __UNSET is invoked by calling unset() on inaccessible properties.
+ *
+ * @param string $propName The property name.
+ *
+ * @throws LazyUserOptionException If virtual properties are disabled.
+ *
+ * @see LazyJsonMapper::_unsetProperty()
+ */
+ final public function __unset(
+ $propName)
+ {
+ if (!static::ALLOW_VIRTUAL_PROPERTIES) {
+ throw new LazyUserOptionException(
+ $this,
+ LazyUserOptionException::ERR_VIRTUAL_PROPERTIES_DISABLED
+ );
+ }
+
+ // NOTE: PHP ignores the return value of __unset().
+ $this->_unsetProperty($propName);
+ }
+
+ /**
+ * Called during serialization of the object.
+ *
+ * You are not supposed to call this directly. Instead, use PHP's global
+ * `serialize()` call:
+ *
+ * ```php
+ * $savedStr = serialize($obj);
+ * ```
+ *
+ * This serializer is thin and efficient. It simply recursively packs all
+ * nested, internal `LazyJsonMapper` objects as a single, plain data array,
+ * which guarantees the lowest possible serialized data size and the fastest
+ * possible unserialization later (since there will only be a SINGLE parent
+ * object that needs to wake up, rather than a whole tree).
+ *
+ * There is no data conversion/validation of properties (ie to make them
+ * match their "class property map"), since serialization is intended to
+ * quickly save the internal contents of an instance to let you restore it
+ * later, regardless of what was in your internal data property storage.
+ *
+ * Also note that only the internal JSON data is serialized. We will not
+ * serialize any subclass `$properties`, since that would be a misuse of how
+ * your `LazyJsonMapper` subclasses are supposed to be designed. If they
+ * need custom properties, then you can handle those in `_init()` as usual.
+ *
+ * Lastly, you should know that calling `serialize()` will not disrupt any
+ * internal data of the current object instance that you're serializing.
+ * You can therefore continue to work with the object afterwards, or even
+ * `serialize()` the same instance multiple times.
+ *
+ * @throws LazySerializationException If the internal data array cannot be
+ * serialized. But this problem can
+ * literally NEVER happen unless YOU have
+ * intentionally put totally-invalid,
+ * non-serializable sub-objects within
+ * your data array AND those objects in
+ * turn throw exceptions when trying to
+ * `serialize()`. That's NEVER going to
+ * happen with real `json_decode()` data;
+ * so if you constructed our object with
+ * real JSON data then you never have to
+ * look for `serialize()` exceptions.
+ *
+ * @return string The object's internal data as a string representation.
+ * Note that serialization produces strings containing binary
+ * data which cannot be handled as text. It is intended for
+ * storage in binary format (ie a `BLOB` database field).
+ */
+ final public function serialize()
+ {
+ // Tell all of our LJM-properties to pack themselves as plain arrays.
+ // NOTE: We don't do any value-conversion or validation of properties,
+ // since that's not our job. The user wants to SERIALIZE our data, so
+ // translation of array entries to "class-map types" doesn't matter.
+ $objectData = $this->_objectData; // Copy-on-write.
+ array_walk_recursive($objectData, function (&$value) {
+ if (is_object($value) && $value instanceof self) {
+ // This call will recursively detect and take care of nested
+ // objects and return ALL of their data as plain sub-arrays.
+ $value = $value->serialize($value); // Throws.
+ }
+ });
+
+ // If this is not the root object of a nested hierarchy, return raw arr.
+ // NOTE: This efficiently packs all inner, nested LazyJsonMapper objects
+ // by ensuring that their data joins the main root-level array again.
+ $args = func_get_args(); // Check secret argument.
+ $isRootObject = !isset($args[0]) || $args[0] !== $this;
+ if (!$isRootObject) {
+ return $objectData; // Secret, undocumented array return value.
+ }
+
+ // This is the root object that was asked to serialize itself, so finish
+ // the process by serializing the data and validating its success.
+ $serialized = null;
+
+ try {
+ // NOTE: This will ALWAYS succeed if the JSON data array is pure.
+ $serialized = serialize($objectData); // Throws.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ // This can literally ONLY happen if the user has given us (and now
+ // wants to serialize) non-JSON data containing other objects that
+ // attempt (and fail) serialization and throw an exception instead.
+ throw new LazySerializationException(sprintf(
+ 'Unexpected exception encountered while serializing a sub-object. Error: %s',
+ $e->getMessage()
+ ));
+ }
+
+ if (!is_string($serialized)) {
+ // Anything other than a string means that serialize() failed.
+ // NOTE: This should NEVER be able to happen!
+ throw new LazySerializationException(
+ 'The object data could not be serialized.'
+ );
+ }
+
+ // The data is fine. Now just return the string.
+ return $serialized;
+ }
+
+ /**
+ * Called during unserialization of the object.
+ *
+ * You are not supposed to call this directly. Instead, use PHP's global
+ * `unserialize()` call:
+ *
+ * ```php
+ * $restoredObj = unserialize($savedStr);
+ * ```
+ *
+ * This unserializer is thin and efficient. It simply unpacks the serialized
+ * raw data array and uses it as the new object's data, without validating
+ * any of the actual values within the serialized data array. It's intended
+ * to get the new object into the same JSON data state as the serialized
+ * object, which is why we don't re-analyze the data after unserialization.
+ *
+ * Note that the unserialization will call the constructor, with a single
+ * argument (your unserialized JSON data array). Everything that the default
+ * constructor performs will happen during unserialization, EXACTLY as if
+ * you had created a brand new object and given it the data array directly.
+ *
+ * You can therefore fully trust your unserialized objects as much as you
+ * already trust your manually created ones! And you can even store them
+ * somewhere and then unserialize them at any time in the future, during a
+ * completely separate PHP process, and even years from now as long as your
+ * project still contains the specific (sub)-class that you originally
+ * serialized. Have fun!
+ *
+ * @param string $serialized The string representation of the object.
+ *
+ * @throws LazySerializationException If the raw, serialized data cannot be
+ * unserialized at all.
+ * @throws LazyJsonMapperException If there are any problems creating the
+ * class that you are unserializing. See
+ * the regular constructor for error
+ * reasons, but disregard all of its data
+ * validation reasons, since unserialized
+ * JSON data is not validated (analyzed)
+ * when it reaches the constructor. The
+ * most likely reasons why this exception
+ * would be thrown during `unserialize()`
+ * is that your class property map is
+ * invalid and could not be compiled, and
+ * thus the object couldn't be recreated,
+ * OR that a custom `_init()` threw.
+ *
+ * @see LazyJsonMapper::__construct()
+ */
+ final public function unserialize(
+ $serialized = null)
+ {
+ $objectData = null;
+
+ try {
+ // Attempt to unpack the serialized data. Do not @suppress any
+ // syntax errors, since the user needs to know if they've provided
+ // a bad serialized string (or even a non-string value).
+ // NOTE: If the original object only contained perfect JSON data,
+ // then there are no sub-objects. But if any sub-objects existed
+ // within the data, this will recursively unserialize those too.
+ $objectData = unserialize($serialized); // Throws.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ // This can literally ONLY happen if the user had given us (and then
+ // serialized) non-JSON data containing other objects that attempt
+ // (and fail) unserialization now and throw an exception instead.
+ throw new LazySerializationException(sprintf(
+ 'Unexpected exception encountered while unserializing a sub-object. Error: %s',
+ $e->getMessage()
+ ));
+ }
+
+ if (!is_array($objectData)) {
+ // Anything other than an array means that $serialized was invalid.
+ throw new LazySerializationException(
+ 'The serialized object data that you provided could not be unserialized.'
+ );
+ }
+
+ // The data is fine. Now just construct this new object instance.
+ // NOTE: Important since ctor builds/links its necessary property map.
+ // NOTE: The unserialized object (or its data) has no links to the
+ // originally serialized object anymore, apart from the fact that (just
+ // like ANY two identical classes) they would both be linked to the
+ // same compiled class property map ("_compiledPropertyMapLink").
+ $this->__construct($objectData); // Throws.
+ }
+
+ /**
+ * Advanced function: Clear the global "magic function lookup cache".
+ *
+ * This command is NOT RECOMMENDED unless you know exactly what you are
+ * doing and WHY you are doing it. This clears the globally shared cache
+ * of magic function-name to property-name translations.
+ *
+ * It is always safe to clear that cache, since any future magic function
+ * calls (even to existing object instances) will simply re-calculate those
+ * translations and put them back in the global cache again.
+ *
+ * However, it's not recommended to clear the cache if you're sure that
+ * you'll constantly be calling the same functions over and over again.
+ * It's obviously faster to keep cached translations for instant lookups!
+ *
+ * @return int How many unique function names were cached before clearing.
+ */
+ final public static function clearGlobalMagicLookupCache()
+ {
+ $lookupCount = count(self::$_magicLookupCache);
+ self::$_magicLookupCache = [];
+
+ return $lookupCount;
+ }
+
+ /**
+ * Advanced function: Clear the global "compiled property map cache".
+ *
+ * This command is NOT RECOMMENDED unless you know exactly what you are
+ * doing and WHY you are doing it. This clears the globally shared cache of
+ * compiled property maps, which may be useful if you've created tons of
+ * different class objects from lots of different classes, and you no longer
+ * have any of those objects in memory and will never create any of them
+ * again. In that case, clearing the cache would get rid of the memory
+ * consumed by the compiled maps from each class.
+ *
+ * All currently existing object instances will continue to work, since they
+ * retain their own personal links to their own compiled property maps.
+ * Therefore, the memory you free by calling this function may not be the
+ * full amount (until ALL of the object instances of all classes are freed).
+ * PHP will only be able to free their unused parent classes, and their
+ * unused subclasses, and any other unused classes in general. But PHP will
+ * retain memory for the SPECIFIC, per-class maps for all living objects.
+ *
+ * Calling this function is totally harmless, since all future class
+ * instance constructors will simply re-compile their whole map hierarchies
+ * again from scratch, and all class maps will be re-built IDENTICALLY the
+ * next time since all of their map definitions themselves are immutable
+ * class constants and can NEVER be modified at runtime (not even via PHP
+ * 7.1's advanced `ReflectionClassConstant` class).
+ *
+ * However, because of the fact that all existing object instances retain
+ * private links to their own maps from the previous compilation, it means
+ * that you may actually end up using MORE memory after clearing the global
+ * cache, IF you still have a LOT of different living object instances AND
+ * also begin to create new instances of various classes again (and thus
+ * begin re-compiling new, global instances of each of their "cleared" class
+ * property maps).
+ *
+ * In short: Do NOT call this function unless you know why you are doing it!
+ *
+ * @return int How many unique classes were cached before clearing.
+ */
+ final public static function clearGlobalPropertyMapCache()
+ {
+ $classCount = count(self::$_propertyMapCache->classMaps);
+ self::$_propertyMapCache->clearCache();
+
+ return $classCount;
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/FunctionTranslation.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/FunctionTranslation.php
new file mode 100755
index 0000000..89cb92a
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/FunctionTranslation.php
@@ -0,0 +1,314 @@
+ `__foo__bar___x_baz__` (snake)
+ * `__foo_Bar__XBaz__` (camel)
+ * - `0m__AnUn0x` => `0m___an_un0x` (snake) & `0m__AnUn0x` (camel)
+ * - `Some0XThing` => `some0_x_thing` (snake) & `some0XThing` (camel)
+ * - `Some0xThing` => `some0x_thing` (snake) & `some0xThing` (camel)
+ * - `SomeThing` => `some_thing` (snake) & `someThing` (camel)
+ * - `Something` => `something` (snake) & `NULL` (camel)
+ * - `___` => `___` (snake) & `NULL` (camel)
+ * - `_0` => `_0` (snake) & `NULL` (camel)
+ * - `_Messages` => `_messages` (snake) & `NULL` (camel)
+ * - `__MessageList` => `__message_list` (snake) & `__messageList` (camel)
+ * - `123` => `123` (snake) & `NULL` (camel)
+ * - `123prop` => `123prop` (snake) & `NULL` (camel)
+ * - `123Prop` => `123_prop` (snake) & `123Prop` (camel)
+ *
+ * ---------------------------------------------------------------------
+ *
+ * `NOTE:` The class validates all parameters, but provides public properties to
+ * avoid needless function calls. It's therefore your responsibility to never
+ * assign any bad values to the public properties after this object's creation!
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ *
+ * @see PropertyTranslation
+ */
+class FunctionTranslation
+{
+ /**
+ * The property name in underscore "snake_case" style.
+ *
+ * For example `some_example_property`.
+ *
+ * @var string
+ */
+ public $snakePropName;
+
+ /**
+ * The property name in "camelCase" style.
+ *
+ * For example `someExampleProperty`.
+ *
+ * `NOTE:` This is `NULL` if the property consists only of a single word.
+ *
+ * @var string|null
+ */
+ public $camelPropName;
+
+ /**
+ * Constructor.
+ *
+ * @param string $funcCase The "property" portion of the function name to
+ * translate, in "FunctionCase" style, such as
+ * `SomeExampleProperty`.
+ *
+ * @throws MagicTranslationException If the `$funcCase` name is unparseable.
+ *
+ * @see FunctionTranslation::splitFunctionName() A helper to split your
+ * function names into the
+ * necessary parts.
+ */
+ public function __construct(
+ $funcCase)
+ {
+ if (!is_string($funcCase) || $funcCase === '') {
+ throw new MagicTranslationException('The function name must be a non-empty string value.');
+ }
+
+ // Convert the FuncCase name to its snake and camel properties.
+ $result = $this->_funcCaseToProperties($funcCase);
+ if ($result === false) {
+ throw new MagicTranslationException(sprintf(
+ 'The provided input value "%s" is not a valid FunctionCase name.',
+ $funcCase
+ ));
+ }
+
+ // We are done with the conversions.
+ $this->snakePropName = $result['snake'];
+ $this->camelPropName = $result['camel'];
+ }
+
+ /**
+ * Split a function name into its function-type and FuncCase components.
+ *
+ * This helper function takes care of splitting a full function name, such
+ * as `getSomeVariable`, into `get` (its function type) and `SomeVariable`
+ * (the valid input format for the FunctionTranslation constructor).
+ *
+ * Call it from your own code before constructing your `FunctionTranslation`
+ * objects. Don't worry about the extra function call for this splitter.
+ * It can perform its job 2.5 million times per second on a 2010 Core i7
+ * dual-core laptop on PHP7, and 0.64 million times per second on PHP5.
+ * And directly embedding these same steps instead of calling this function
+ * will only gain 5% more speed in PHP7 and 16% more speed in PHP5. But the
+ * numbers are already so astronomically fast that it doesn't matter!
+ *
+ * This splitting into `get` and `SomeVariable` is easy and super efficient.
+ * It is this class' final translation of the FunctionCase `SomeVariable`
+ * part into `some_variable` and `someVariable` properties that's the HARD
+ * step which should be cached.
+ *
+ * Recommended usage:
+ *
+ * ```php
+ * list($functionType, $funcCase) = FunctionTranslation::splitFunctionName($name);
+ * // if $functionType is now NULL, the input was invalid. otherwise it was ok.
+ * ```
+ *
+ * @param string $functionName The function name to split. It's your job to
+ * make sure this is a string-type variable! We
+ * will not validate its type. Empty strings ok.
+ *
+ * @return string[]|null[] Two-element array of `functionType` (element 0) &
+ * `funcCase` (element 1). If the input name wasn't
+ * valid a `doSomething` (function-camelCase), then
+ * both elements are `NULL` instead of strings.
+ */
+ public static function splitFunctionName(
+ $functionName)
+ {
+ // Split the input on the FIRST encountered NON-LOWERCAZE ("a-z")
+ // character. And allow empty splits (that's important). Because of the
+ // fact that it splits on non-lowercase character types, it means that
+ // if there are 2 chunks then we KNOW that there was a non-lowercase
+ // character (such as _, 0-9, A-Z, etc) in the input. And if there are
+ // two chunks but the first chunk is an empty string then we know that
+ // there was no lowercase a-z PREFIX in the input. And if there is only
+ // 1 chunk then we know the entire input was all-lowercase a-z.
+ //
+ // Examples of this preg_split()'s behavior:
+ //
+ // "get_" => 2 chunks: "get" & "_"
+ // "getgetX" => 2 chunks: "getget" & "X"
+ // "eraseSomething" => 2 chunks: "erase" & "Something"
+ // "getgetget" (only lowercase a-z) => 1 chunk: "getgetget"
+ // "GetSomething" (no lowercase prefix) => 2 chunks: "" & "GetSomething"
+ // "G" => 2 chunks: "" & "G"
+ // "GX" => 2 chunks: "" & "GX"
+ // "Gx" => 2 chunks: "" & "Gx"
+ // "0" => 2 chunks: "" & "0"
+ // "gx" => 1 chunk: "gx"
+ // "gX" => 2 chunks: "g" & "X"
+ // "g0" => 2 chunks: "g" & "0"
+ // "" (empty string input) => 1 chunk: ""
+ //
+ // Therefore, we know that the input was valid (a lowercase a-z prefix
+ // followed by at least one non-lowercase a-z after that) if we have two
+ // chunks and the first chunk is non-empty!
+ $chunks = preg_split('/(?=[^a-z])/', $functionName, 2);
+ if (count($chunks) === 2 && $chunks[0] !== '') {
+ // [0] = prefix (functionType), [1] = suffix (FuncCase).
+ return $chunks;
+ }
+
+ // Invalid input. Return NULL prefix and NULL suffix values.
+ static $invalidChunks = [null, null]; // Static=Only created first call.
+
+ return $invalidChunks;
+ }
+
+ /**
+ * Converts a FunctionCase name to snake and camel properties.
+ *
+ * See input/output examples in class documentation above.
+ *
+ * @param string $funcCase
+ *
+ * @return array|bool Associative array with `snake` & `camel` elements if
+ * successful, otherwise `FALSE`.
+ */
+ protected function _funcCaseToProperties(
+ $funcCase)
+ {
+ // This algorithm is the exact inverse of PropertyTranslation.
+ // Read that class for more information.
+
+ // There's nothing to do if the input is empty...
+ if (!strlen($funcCase)) {
+ return false;
+ }
+
+ // First, we must decode our encoded representation of any special PHP
+ // operators, just in case their property name had illegal chars.
+ $funcCase = SpecialOperators::decodeOperators($funcCase);
+
+ // Now remove and count all leading underscores (important!).
+ // Example: "__MessageList" => "MessageList".
+ $result = ltrim($funcCase, '_');
+ $leadingUnderscores = strlen($funcCase) - strlen($result);
+
+ // Verify that the REMAINING input result doesn't contain lowercase a-z
+ // as its FIRST character. In that case, we were given invalid input,
+ // because the FuncCase style REQUIRES that the first character is a
+ // NON-LOWERCASE. Anything else is fine, such as UpperCase, numbers or
+ // special characters, etc, but never lowercase, since our splitter
+ // splitFunctionName() would NEVER give us a FuncName part with a
+ // leading lowercase letter! However, the splitter COULD give us
+ // something like "__m" from "get__m". But our PropertyTranslation would
+ // NEVER create output like "__m". It would have created "__M". So
+ // anything that now remains at the start of the string after stripping
+ // leading underscores MUST be non-lowercase.
+ if (preg_match('/^[a-z]/', $result)) {
+ return false;
+ }
+
+ // Split the input into chunks on all camelcase boundaries.
+ // NOTE: Since all chunks are split on camelcase boundaries below, it
+ // means that each chunk ONLY holds a SINGLE fragment which can ONLY
+ // contain at most a SINGLE capital letter (the chunk's first letter).
+ // NOTE: The "PREG_SPLIT_NO_EMPTY" ensures that we don't get an empty
+ // leading array entry when the input begins with an "Upper" character.
+ // NOTE: If this doesn't match anything (meaning there are no uppercase
+ // characters to split on), the input is returned as-is. Such as "123".
+ // NOTE: If $result is an empty string, this returns an empty array.
+ // Example: "MessageList" => "Message" & "List"
+ $chunks = preg_split('/(?=[A-Z])/', $result, -1, PREG_SPLIT_NO_EMPTY);
+ if ($chunks === false) {
+ return false; // Only happens on regex engine failure, NOT mismatch!
+ }
+ $chunkCount = count($chunks);
+
+ // Handle the scenario where there are no chunks ($result was empty).
+ // NOTE: Thanks to all of the validation above, this can ONLY happen
+ // when input consisted entirely of underscores with nothing after that.
+ if ($chunkCount === 0) {
+ // Insert a fake, empty element to act as the first chunk, to ensure
+ // that we have something to insert the underscores into.
+ $chunks[] = '';
+ $chunkCount++;
+ }
+
+ // Lowercase the leading uppercase of 1st chunk (whatever is in there),
+ // since that chunk needs lowercase in both snake_case and camelCase.
+ // Example: "Message" & "List" => "message" & "List"
+ $chunks[0] = lcfirst($chunks[0]);
+
+ // If there were any leading underscores, prepend all of them to the 1st
+ // chunk, which ensures that they become part of the first chunk.
+ // Example: "__message" & "List"
+ if ($leadingUnderscores > 0) {
+ $chunks[0] = str_repeat('_', $leadingUnderscores).$chunks[0];
+ }
+
+ // Now let's create the snake_case and camelCase variable names.
+
+ // The property name chunks are already in perfect format for camelCase.
+ // The first chunk starts with a lowercase letter, and all other chunks
+ // start with an uppercase letter. So generate the camelCase property
+ // name version first. But only if there are 2+ chunks. Otherwise NULL.
+ // NOTE: Turns "i,Tunes,Item" into "iTunesItem", and "foo" into NULL.
+ $camelPropName = $chunkCount >= 2 ? implode('', $chunks) : null;
+
+ // Now make the second property name chunk and onwards into lowercase,
+ // and then generate the all-lowercase "snake_case" property name.
+ // NOTE: Turns "some,Property,Name" into "some_property_name".
+ for ($i = 1; $i < $chunkCount; ++$i) {
+ $chunks[$i] = lcfirst($chunks[$i]); // Only first letter can be UC.
+ }
+ $snakePropName = implode('_', $chunks);
+
+ // Return the final snake_case and camelCase property names.
+ return [
+ 'snake' => $snakePropName,
+ 'camel' => $camelPropName,
+ ];
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/PropertyTranslation.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/PropertyTranslation.php
new file mode 100755
index 0000000..a6e02e3
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/PropertyTranslation.php
@@ -0,0 +1,191 @@
+ `__foo__bar___x_baz__` (snake)
+ * `__foo_Bar__XBaz__` (camel)
+ * - `0m__AnUn0x` => `0m___an_un0x` (snake) & `0m__AnUn0x` (camel)
+ * - `Some0XThing` => `some0_x_thing` (snake) & `some0XThing` (camel)
+ * - `Some0xThing` => `some0x_thing` (snake) & `some0xThing` (camel)
+ * - `SomeThing` => `some_thing` (snake) & `someThing` (camel)
+ * - `Something` => `something` (snake & camel identical; no ucwords)
+ * - `___` => `___` (snake & camel identical; no ucwords)
+ * - `_0` => `_0` (snake & camel identical; no ucwords)
+ * - `_Messages` => `_messages` (snake & camel identical; no ucwords)
+ * - `__MessageList` => `__message_list` (snake) & `__messageList` (camel)
+ * - `123` => `123` (snake & camel identical; no ucwords)
+ * - `123prop` => `123prop` (snake & camel identical; no ucwords)
+ * - `123Prop` => `123_prop` (snake) & `123Prop` (camel)
+ *
+ * ---------------------------------------------------------------------
+ *
+ * `NOTE:` The class validates all parameters, but provides public properties to
+ * avoid needless function calls. It's therefore your responsibility to never
+ * assign any bad values to the public properties after this object's creation!
+ *
+ * @copyright 2017 The LazyJsonMapper Project
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ * @author SteveJobzniak (https://github.com/SteveJobzniak)
+ *
+ * @see FunctionTranslation
+ */
+class PropertyTranslation
+{
+ /**
+ * The property name in "FunctionCase" style.
+ *
+ * For example `AwesomeProperty`.
+ *
+ * @var string
+ */
+ public $propFuncCase;
+
+ /**
+ * Constructor.
+ *
+ * @param string $propertyName The name of the property to translate, in
+ * either `snake_case` or `camelCase` style,
+ * such as `awesome_property` (snake)
+ * or `awesomeProperty` (camel). The
+ * translations will differ based on
+ * which style is used. (That's intentional.)
+ *
+ * @throws MagicTranslationException If the property name is unparseable.
+ */
+ public function __construct(
+ $propertyName)
+ {
+ if (!is_string($propertyName) || $propertyName === '') {
+ throw new MagicTranslationException('The property name must be a non-empty string value.');
+ }
+
+ $this->propFuncCase = $this->_propToFunctionCase($propertyName);
+ }
+
+ /**
+ * Converts a property name to FunctionCase.
+ *
+ * See input/output examples in class documentation above.
+ *
+ * @param string $propName The property name, as either `snake_case` or
+ * `camelCase`. The translations will differ based
+ * on which style is used. (That's intentional.)
+ *
+ * @return string
+ */
+ protected function _propToFunctionCase(
+ $propName)
+ {
+ // To translate a property name to FunctionCase, we must simply convert
+ // any leading lowercase (a-z) character to uppercase, as well as any
+ // (a-z) that comes after an underscore. In the latter case, the
+ // underscore must be removed during the conversion.
+ // For example: "example_property" = "ExampleProperty".
+ // (If multiple underscores, still just remove ONE: "a__b"="A_B").
+ //
+ // However, there is a special case: All LEADING underscores must be
+ // preserved exactly as-is: "__messages" = "__Messages" (still 2).
+ //
+ // As for camelCasePropertyNames? They're already perfect, except that
+ // the first letter must be made uppercase. And IF they have any inner
+ // "_[a-z]" (lowercase) chunks, those should be translated as they would
+ // for a snake_case string. But any "_[A-Z]" should not be touched.
+ // In other words, the algorithm is exactly the same for camelCase.
+
+ // Begin by removing and counting all leading underscores (important!).
+ // NOTE: The reason why we have to do this is because otherwise a
+ // property named "_messages" would be translated to just "Messages",
+ // which then becomes incredibly hard to guess whether it means
+ // "messages" or "_messages". So by preserving any leading underscores,
+ // we remove that ambiguity. It also make the function names more
+ // logical (they match the property's amount of leading underscores) and
+ // it removes clashes. For example if the user defines both "messages"
+ // and "_messages", they can safely use their "getMessages()" and
+ // "get_Messages()" without any ambiguity about what they refer to.
+ $result = ltrim($propName, '_');
+ $leadingUnderscores = strlen($propName) - strlen($result);
+
+ // Now simply uppercase any lowercase (a-z) character that is either at
+ // the start of the string or appears immediately after an underscore.
+ //
+ // ----------------------------------------
+ // TODO: If someone is using Unicode in their JSON data, we should
+ // simply extend this (and also the FunctionTranslation class) to run
+ // with PHP's slower mb_* multibyte functions and "//u" UTF-8 flags,
+ // so that we can support functions like "getÅngbåt()", for a property
+ // named '{"ångbåt":1}'. But honestly, I doubt that even international
+ // users name their JSON data in anything but pure, highly-compressible
+ // ASCII, such as "angbat" and "getAngbat()" in the example above. So
+ // for now, we can have the efficient ASCII algorithms here. In the
+ // future we may want to provide a class-constant to override the
+ // parsers to enable UTF-8 mode, so that the user can have that slower
+ // parsing behavior in certain classes only. And in that case, the magic
+ // function translations can still be stored in the same global cache
+ // together with all the ASCII entries, since they'd use their UTF-8
+ // names as key and thus never clash with anything from this algorithm.
+ // ----------------------------------------
+ $result = preg_replace_callback('/(?:^|_)([a-z])/', function ($matches) {
+ return ucfirst($matches[1]); // Always contains just 1 character.
+ }, $result);
+
+ // Now just prepend any leading underscores that we removed earlier.
+ if ($leadingUnderscores > 0) {
+ $result = str_repeat('_', $leadingUnderscores).$result;
+ }
+
+ // Lastly, we must now translate special PHP operators to an encoded
+ // representation, just in case their property name has illegal chars.
+ $result = SpecialOperators::encodeOperators($result);
+
+ return $result;
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/SpecialOperators.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/SpecialOperators.php
new file mode 100755
index 0000000..1659137
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Magic/SpecialOperators.php
@@ -0,0 +1,169 @@
+',
+ ];
+
+ $translations = ['encode' => [], 'decode' => []];
+ foreach ($operators as $chr) {
+ $hex = str_pad(strtoupper(dechex(ord($chr))), 2, '0', STR_PAD_LEFT);
+ $encoded = sprintf('_x%s_', $hex);
+ $translations['encode'][$chr] = $encoded;
+ $translations['decode'][$encoded] = $chr;
+ }
+
+ self::$_translations = $translations;
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyDefinition.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyDefinition.php
new file mode 100755
index 0000000..24bed79
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyDefinition.php
@@ -0,0 +1,325 @@
+arrayDepth = 0;
+ $this->propType = null;
+ $this->isObjectType = false;
+
+ return; // Skip the rest of the code.
+ }
+
+ if (!is_string($definitionStr)) {
+ throw new BadPropertyDefinitionException(
+ 'The property definition must be a string value.'
+ );
+ }
+
+ // Clean up and validate any provided base namespace or make it global.
+ // IMPORTANT NOTE: Any provided classnames "relative to a certain
+ // namespace" will NOT know about any "use"-statements in the files
+ // where those classes are defined. Even Reflection cannot detect "use".
+ if (is_string($baseNamespace)) { // Custom namespace.
+ if (strlen($baseNamespace) > 0
+ && ($baseNamespace[0] === '\\' || substr($baseNamespace, -1) === '\\')) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Invalid namespace "%s". The namespace is not allowed to start or end with a backslash. Use __NAMESPACE__ format.',
+ $baseNamespace
+ ));
+ }
+ } else {
+ $baseNamespace = ''; // Global namespace.
+ }
+
+ // Set arrayDepth: Count well-formed array-brackets at end of type.
+ // Example: "int[][][]" or "[][]" (yes, array of untyped is possible.)
+ $this->arrayDepth = 0;
+ if (preg_match('/(?:\[\])+$/', $definitionStr, $matches)) {
+ // $matches[0] is the sequence of valid pairs, ie "[][][]".
+ $this->arrayDepth = strlen($matches[0]) / 2;
+ // Get rid of the pairs from the end of the definition.
+ $definitionStr = substr($definitionStr, 0, -($this->arrayDepth * 2));
+ }
+
+ // Set propType: It's what remains of our definition string.
+ // Example: "" or "mixed" or "int" or "\Foo\Bar" or "Bar"
+ $this->propType = $definitionStr;
+
+ // Always store "" or "mixed" as NULL, to make it easy to check.
+ if ($this->propType === '' || $this->propType === 'mixed') {
+ $this->propType = null;
+ }
+
+ // If there's no type, or if it refers to a basic type, then we're done.
+ // This ensures that our basic non-empty type value is a real PHP type.
+ // NOTE: This is intentionally cAsE-sensitive.
+ // NOTE: The basic types are reserved names in PHP, so there's no risk
+ // they refer to classes, since PHP doesn't allow such class names.
+ if ($this->propType === null || in_array($this->propType, self::BASIC_TYPES)) {
+ $this->isObjectType = false;
+
+ return; // Skip the rest of the code.
+ }
+
+ // They are trying to refer to a class (or they've mistyped a basic
+ // type). Validate the target to ensure that it's fully trustable
+ // to be a reachable LazyJsonMapper-based class.
+ $this->isObjectType = true;
+
+ // Check if they've used the special shortcut for the core class.
+ if ($this->propType === 'LazyJsonMapper') {
+ $this->propType = '\\'.LazyJsonMapper::class;
+ }
+
+ // Begin by copying whatever remaining type value they've provided...
+ $classPath = $this->propType;
+
+ // First check if they want the global namespace instead.
+ if ($classPath[0] === '\\') {
+ // Their class refers to a global path.
+ $baseNamespace = ''; // Set global namespace as base.
+ $classPath = substr($classPath, 1); // Strip leading "\".
+ }
+
+ // Construct the full class-path from their base namespace and class.
+ // NOTE: The leading backslash is super important to ensure that PHP
+ // actually looks in the target namespace instead of a sub-namespace
+ // of its current namespace.
+ $fullClassPath = sprintf(
+ '\\%s%s%s',
+ $baseNamespace,
+ $baseNamespace !== '' ? '\\' : '',
+ $classPath
+ );
+
+ // Ensure that the target class actually exists (via autoloader).
+ if (!class_exists($fullClassPath)) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Class "%s" not found.',
+ $fullClassPath
+ ));
+ }
+
+ // We'll use a reflector for analysis, to ensure the class is valid
+ // for use as a target type. It must be based on LazyJsonMapper.
+ try {
+ // First clean up the case-insensitive class name to become the
+ // EXACT name for the class. So we can trust "propType" in ===.
+ // Example: "\fOO\bAr" to "Foo\Bar". (Without any leading "\".)
+ // NOTE: getName() gets the "NameSpace\Class" without leading "\",
+ // which is PHP's preferred notation. And that is exactly how we
+ // will store the final path internally, so that we can always
+ // trust comparisons of these typenames vs full paths to other
+ // class names retrieved via other methods such as get_class().
+ // It does however mean that propType is NOT the right value for
+ // actually constructing the class safely.
+ $reflector = new ReflectionClass($fullClassPath);
+ $fullClassPath = $reflector->getName();
+
+ // The target class or its parents MUST inherit LazyJsonMapper,
+ // so that it implements the necessary behaviors and can be
+ // trusted to accept our standardized constructor parameters.
+ // NOTE: As you can see, we also allow users to map directly to
+ // plain "LazyJsonMapper" objects. It's a very bad idea, since
+ // they don't get any property definitions, and therefore their
+ // object would be unreliable. But that's the user's choice.
+ if ($fullClassPath !== LazyJsonMapper::class
+ && !$reflector->isSubClassOf('\\'.LazyJsonMapper::class)) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Class "\\%s" must inherit from LazyJsonMapper.',
+ $fullClassPath
+ ));
+ }
+
+ // Alright, the class path has been fully resolved, validated
+ // to be a LazyJsonMapper, and normalized into its correct name.
+ // ... Rock on! ;-)
+ $this->propType = $fullClassPath;
+ } catch (ReflectionException $e) {
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Reflection failed for class "%s". Reason: "%s".',
+ $fullClassPath, $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Get the strict, global path to the target class.
+ *
+ * Always use this function when creating objects or in any other way using
+ * the "property type" class path as argument for PHP's class checking
+ * functions. The strict path that it provides ensures that PHP will find
+ * the global path instead of resolving to a local object.
+ *
+ * @return string|null Strict path if this is an object, otherwise `NULL`.
+ */
+ public function getStrictClassPath()
+ {
+ return $this->isObjectType ? '\\'.$this->propType : null;
+ }
+
+ /**
+ * Check if all values of this property match another property object.
+ *
+ * @param PropertyDefinition $otherObject The object to compare with.
+ *
+ * @return bool `TRUE` if all property values are identical, otherwise `FALSE`.
+ */
+ public function equals(
+ PropertyDefinition $otherObject)
+ {
+ // The "==" operator checks for same class and matching property values.
+ return $this == $otherObject;
+ }
+
+ /**
+ * Get the property definition as its string representation.
+ *
+ * The string perfectly represents the property definition, and can
+ * therefore even be used when constructing other object instances.
+ *
+ * @return string
+ */
+ public function asString()
+ {
+ return sprintf(
+ '%s%s%s',
+ $this->isObjectType ? '\\' : '',
+ $this->propType !== null ? $this->propType : 'mixed',
+ str_repeat('[]', $this->arrayDepth)
+ );
+ }
+
+ /**
+ * Get the property definition as its string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->asString();
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCache.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCache.php
new file mode 100755
index 0000000..3f88c0d
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCache.php
@@ -0,0 +1,73 @@
+classMaps = [];
+ $this->compilerLocks = [];
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCompiler.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCompiler.php
new file mode 100755
index 0000000..6872e80
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/PropertyMapCompiler.php
@@ -0,0 +1,1274 @@
+compile(); // Throws.
+ }
+
+ /**
+ * Private Constructor.
+ *
+ * @param bool $isRootCompiler Whether this is the root-level
+ * compiler in a compile-stack.
+ * @param PropertyMapCache $propertyMapCache The class/lock cache to use.
+ * @param string $solveClassName The full path of the class to
+ * compile, but without any
+ * leading `\` global prefix. To
+ * save time, we assume the caller
+ * has already verified that it is
+ * a valid `LazyJsonMapper` class.
+ *
+ * @throws BadPropertyMapException
+ */
+ private function __construct(
+ $isRootCompiler,
+ PropertyMapCache $propertyMapCache,
+ $solveClassName)
+ {
+ // Sanity-check the untyped arguments to protect against accidents.
+ if (!is_bool($isRootCompiler)) {
+ throw new BadPropertyMapException('The isRootCompiler argument must be a boolean.');
+ }
+ if (!is_string($solveClassName) || $solveClassName === '') {
+ throw new BadPropertyMapException('The class name to solve must be a non-empty string.');
+ }
+
+ // Save user-properties.
+ // NOTE: The compiler uses a concept of "root compiler" and recursive
+ // "sub-compilers". The compilers automatically spawn sub-compilers for
+ // any resources they need. And the ROOT compiler is responsible for
+ // coordinating everything and doing any final post-processing work
+ // AFTER the main hierarchy (extends) and "import" compilation steps.
+ $this->_isRootCompiler = $isRootCompiler;
+ $this->_propertyMapCache = $propertyMapCache;
+ $this->_solveClassName = $solveClassName;
+
+ // Generate a strict name (with a leading "\") to tell PHP to search for
+ // it from the global namespace, in commands where we need that.
+ // NOTE: We index the cache by "Foo\Bar" for easy lookups, since that is
+ // PHP's internal get_class()-style representation. But we use strict
+ // "\Foo\Bar" when we actually interact with a class or throw errors.
+ // That way, both PHP and the user understands that it's a global path.
+ $this->_strictSolveClassName = Utilities::createStrictClassPath($this->_solveClassName);
+
+ // We will keep track of ALL classes compiled by ourselves and our
+ // recursive sub-compilers. In case of errors ANYWHERE in the process,
+ // we MUST ensure that all of those classes are erased from the cache
+ // again, since they may have serious errors. This list of compiled
+ // classes was specifically added to handle the fact that the class map
+ // and its hierarchy/imports themselves often fully compile themselves
+ // successfully, but then THEIR property class compilations (the root-
+ // POST-PROCESSING step which compiles all classes pointed to by the
+ // maps) MAY fail. In that case, or if there are ANY other compilation
+ // problems in any of the class hierarchies, then we will be sure to
+ // erase ALL of our compiled classes from the cache; otherwise it WOULD
+ // look like those classes are fully compiled and reliable, despite the
+ // bad and invalid classes some of their properties point to!
+ $this->compiledClasses = [];
+
+ // During the main compilation phase, we'll ONLY compile the actual
+ // class that we've been told to compile, as well as its parent
+ // hierarchy and any imports that any of them contain. But any of those
+ // compiled maps MAY in turn contain PROPERTIES that point at OTHER
+ // uncompiled classes. The PropertyDefinition construction will always
+ // verify that those properties are pointing at reachable (having a
+ // valid class-path) LazyJsonMapper classes. However, the actual CLASS
+ // MAPS of THOSE classes may contain problems too and may be impossible
+ // to compile. We obviously want to figure that out for the user RIGHT
+ // NOW so that they don't run into issues later when trying to access
+ // such a property someday (which would THEN trigger its map compilation
+ // and throw a compiler failure deep into their runtime environment when
+ // they least expected it). The truth is that normal people will
+ // sometimes write bad classes, and then point properties at those bad
+ // classes. And without us proactively pre-compiling all classes pointed
+ // to by their properties, they would NEVER know about the problem until
+ // someday later when their program suddenly accesses that bad property
+ //
+ // So we SHOULD analyze everything for the user's safety. But... we
+ // unfortunately CANNOT compile them during the main compilation stage.
+ // In fact, we cannot even compile them afterwards EITHER, UNLESS we are
+ // the ABSOLUTE ROOT COMPILE-CALL which STARTED the current compilation
+ // process. Only the root function call is allowed to resolve PROPERTY
+ // references to other classes and ensure that THOSE maps are
+ // successfully are compiled too. Otherwise we'd run into circular
+ // compilation issues where properties fail to compile because they may
+ // be pointing at things which are not compiled yet (such as a class
+ // containing a property that points at itself, or "A" having a property
+ // that points to class "B" which has a property that points to class
+ // "C" which has a property that points back at class "A" and therefore
+ // would cause a circular reference if we'd tried to compile those
+ // property-classes instantly as-they're encountered). We must therefore
+ // defer property-class compilation until ALL core classes in the
+ // current inheritance/import chain are resolved. THEN it's safe to
+ // begin compiling any other encountered classes from the properties...
+ //
+ // The solution is "simple": Build a list of the class-paths of all
+ // encountered UNCOMPILED property classes within our own map tree and
+ // within any imports (which are done via recursive compile-calls). To
+ // handle imports, we simply ensure that our compile-function (this one)
+ // lets the parent retrieve our list of "encountered uncompiled classes
+ // from properties" so that we'll let it bubble up all the way to the
+ // root class/call that began the whole compile-chain. And then just let
+ // that root-level call resolve ALL uncompiled properties from the
+ // ENTIRE tree by simply compiling the list of property-classes one by
+ // one if they're still missing. That way, we'll get full protection
+ // against uncompilable class-properties ANYWHERE in our tree, and we'll
+ // do it at a totally safe time (at the end of the main compilation call
+ // that kicked everything off)!
+ //
+ // NOTE: As an optimization, to avoid constant array-writes and the
+ // creation of huge and rapidly growing "encountered classes" arrays, we
+ // will attempt to ONLY track the encountered classes that are MISSING
+ // from the cache (not yet compiled). And this list will ALSO be checked
+ // one more time at the end, to truly clear everything that's already
+ // done. That's intended to make the list as light-weight as possible
+ // so that our parent-compiler doesn't have to do heavy lifting.
+ //
+ // NOTE: The fact is that most classes that properties point to will
+ // already be pre-compiled, or at least large parts of their
+ // inheritance/import hierarchy will already be compiled, so this
+ // recursive "property-class" compilation isn't very heavy at all. It's
+ // as optimized as it can be. Classes we encounter will ONLY compile the
+ // specific aspects of their class maps which HAVEN'T been compiled yet,
+ // such as a class that is a sub-tree (extends) of an already-compiled
+ // class, which means that the extra class would be very fast to
+ // compile. Either way, we basically spend a tiny bit of extra effort
+ // here during compilation to save users from a TON of pain from THEIR
+ // bad classes later. ;-)
+ $this->uncompiledPropertyClasses = [];
+
+ // Initialize the remaining class properties.
+ $this->_classHierarchy = [];
+ $this->_ourCompilerClassLocks = [];
+ $this->_previousMapConstantValue = null;
+ $this->_currentClassInfo = null;
+ $this->_currentClassPropertyMap = [];
+ }
+
+ /**
+ * Compile the solve-class, and its parent/import hierarchy (as-necessary).
+ *
+ * Intelligently watches for compilation errors, and if so it performs an
+ * auto-rollback of ALL classes compiled by this `compile()`-call AND by all
+ * of its recursive sub-compilations (if any took place).
+ *
+ * It performs the rollback if there were ANY issues with the solve-class or
+ * ANY part of its inheritance hierarchy (extends/imports) OR with the final
+ * post-processing compilation of all of the classes that THEIR properties
+ * were pointing at. And note that the post-processing is ALSO recursive and
+ * will FULLY validate the entire hierarchies and property trees of ALL
+ * classes that IT compiles, and so on... until no more work remains.
+ *
+ * So if this function DOESN'T throw, then you can TRUST that the ENTIRE
+ * hierarchy of property maps related to the requested solve-class has been
+ * compiled. However, note that their final PROPERTY CLASS compilation step
+ * and final success-validation doesn't happen until the top-level
+ * rootCompiler is reached again, since it's the job of the root to handle
+ * the recursive compilation and resolving of all classes encountered in
+ * properties. So it's only the rootCompiler's `compile()`-call that TRULY
+ * matters and will determine whether EVERYTHING was successful or not.
+ *
+ * Basically: If we fail ANY aspect of the request to compile, then we'll
+ * FULLY roll back everything that we've changed during our processing.
+ * Which safely guarantees that the FINAL map compilation cache ONLY
+ * contains fully verified and trustable classes and their COMPLETE class
+ * inheritance and property hierarchies!
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ *
+ * @see PropertyMapCompiler::compileClassPropertyMap() The public, static
+ * class entry point.
+ * @see PropertyMapCompiler::_compile() The internal compiler
+ * core.
+ */
+ public function compile()
+ {
+ try {
+ $this->_compile();
+ } catch (\Exception $e) { // NOTE: Could target exact type, but meh.
+ // Our compilation or one of its sub-compilations has failed... We
+ // MUST now perform a rollback and unset everything in our list of
+ // compiled classes (which also includes everything that our
+ // sub-compilers have compiled).
+ // NOTE: Every compile()-call is responsible for unsetting ITS OWN
+ // list of (sub-)compiled classes. Because the parent-handler that
+ // reads our "compiledClasses" property may not run when an
+ // exception happens. So it's OUR job to clear OUR changes to the
+ // cache before we let the exception bubble up the call-stack.
+ foreach ($this->compiledClasses as $className => $x) {
+ unset($this->_propertyMapCache->classMaps[$className]);
+ unset($this->compiledClasses[$className]);
+ }
+
+ throw $e; // Re-throw. (Keeps its stack trace & line number.)
+ }
+ }
+
+ /**
+ * The real, internal compiler algorithm entry point.
+ *
+ * MUST be wrapped in another function which handles compilation failures!
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ *
+ * @see PropertyMapCompiler::compile() The wrapper entry point.
+ */
+ private function _compile()
+ {
+ // There's nothing to do if the class is already in the cache.
+ if (isset($this->_propertyMapCache->classMaps[$this->_solveClassName])) {
+ return; // Abort.
+ }
+
+ // Let's compile the desired "solve-class", since it wasn't cached.
+ // NOTE: This entire algorithm is EXCESSIVELY commented so that everyone
+ // will understand it, since it's very complex thanks to its support for
+ // inheritance and "multiple inheritance" (imports), which in turn uses
+ // advanced anti-circular dependency protection to detect bad imports.
+ // As well as its automatic end-of-run compilation of any encountered,
+ // current uncompiled classes from any properties in the tree.
+
+ // Prepare the class hierarchy for compilation.
+ $this->_reflectSolveClassHierarchy(); // Throws.
+ $this->_checkCurrentLocks(); // Throws.
+ $this->_lockUncompiledClasses();
+
+ // Traverse the class hierarchy and compile and merge their property
+ // maps, giving precedence to later classes in case of name clashes.
+ //
+ // And because we build the list top-down through the chain of class
+ // inheritance (starting at base and going to the deepest child), and
+ // thanks to the fact that PHP classes can only extend from 1 parent
+ // class, it also means that we're actually able to save the per-class
+ // lists at every step of the way, for each class we encounter along the
+ // way. To avoid needing to process those later.
+ //
+ // This builds a properly inherited class property map and constructs
+ // and validates all property definitions so that we don't get any nasty
+ // surprises during data-parsing later.
+ //
+ // The top-down order also ensures that we re-use our parent's inherited
+ // PropertyDefinition objects, so that the compiled classes all share
+ // memory whenever they inherit from the same parent classes & imports.
+ try {
+ foreach ($this->_classHierarchy as $classInfo) {
+ $this->_currentClassInfo = $classInfo;
+ $this->_processCurrentClass(); // Throws.
+ }
+ } finally {
+ // IMPORTANT: If we've compiled all classes, or if uncaught
+ // exceptions were thrown during processing, then we must simply
+ // ensure that we unlock all of OUR remaining locks before we allow
+ // the exception to keep bubbling upwards OR processing to continue.
+ // NOTE: We AREN'T allowed to unlock classes we didn't lock.
+ foreach ($this->_ourCompilerClassLocks as $lockedClassName => $x) {
+ unset($this->_ourCompilerClassLocks[$lockedClassName]);
+ unset($this->_propertyMapCache->compilerLocks[$lockedClassName]);
+ }
+ }
+
+ // If we've reached this point, it means that our solve-class SHOULD be
+ // in the cache since its entire compilation ran successfully above.
+ // Just verify it to be extra safe against any random future mistakes.
+ // NOTE: This step should never go wrong.
+ if (!isset($this->_propertyMapCache->classMaps[$this->_solveClassName])) {
+ throw new BadPropertyMapException(sprintf(
+ 'Error while compiling class "%s". Could not find the class in the cache afterwards.',
+ $this->_strictSolveClassName
+ ));
+ }
+
+ // If we came this far, it means that NOTHING above threw, which means
+ // that our class and its hierarchy of inheritance (and map-imports), as
+ // well as all PropertyDefinitions, have succeeded for the whole tree...
+ //
+ // Now the only remaining question is whether it's safe for us to check
+ // the list of encountered classes in properties and ensure that those
+ // can all be compiled too... And the answer is NO, unless we are the
+ // absolute ROOT CALL which began the whole compilation process. If not,
+ // then we should merely finish cleaning up our list of encountered
+ // "uncompiled" classes and then let our parent-caller deal with it.
+
+ // If we are a subcall (sub-compilation within a main compilation),
+ // simply let our parent compiler bubble our data to the root call...
+ if (!$this->_isRootCompiler) {
+ // Some already-processed classes may have slipped onto the list due
+ // to being encountered as properties before those classes became
+ // compiled. So before returning to the parent, we'll just ensure
+ // that we remove all keys that refer to classes that have already
+ // been compiled. That saves RAM and processing power during the
+ // array merging in the parent. Basically, we want this list to
+ // always be as short as possible so there's less need for any HUGE
+ // array manipulation at a higher stage while it's bubbling up.
+ foreach ($this->uncompiledPropertyClasses as $uncompiledClassName => $x) {
+ if (isset($this->_propertyMapCache->classMaps[$uncompiledClassName])) {
+ unset($this->uncompiledPropertyClasses[$uncompiledClassName]);
+ }
+ }
+
+ return; // Skip the rest of the code.
+ }
+
+ // We're the root call! So it is now safe (and IMPORTANT!) to compile
+ // all of the not-yet-compiled classes from the inheritance tree, to
+ // ensure that EVERY part of the tree is ready as far as classmaps go.
+ // NOTE: We have no information about how deeply the encountered classes
+ // existed. But it doesn't matter, since they may exist in multiple
+ // places. If there's a problem we'll simply say that "the class (this
+ // initial root/non-subcall one) or one of its parents or imports has a
+ // property which is linked to bad class X". Especially since there may
+ // also be problems within properties of THESE classes that we're going
+ // to compile. So trying to pinpoint exactly which class had the bad
+ // reference to an uncompilable class is overkill. We'll just show the
+ // uncompilable class name plus its regular high-quality compilation
+ // error message which describes why that class failed to compile.
+ // The user should simply fix their code in THAT particular class.
+ while (!empty($this->uncompiledPropertyClasses)) {
+ // IMPORTANT: Create a COPY-ON-WRITE version of the current contents
+ // of the "uncompiledPropertyClasses" array. Because that array will
+ // be changing whenever we sub-compile, so we'll use a stable COPY.
+ $workQueue = $this->uncompiledPropertyClasses;
+
+ // Process all entries (classes to compile) in the current queue.
+ foreach ($workQueue as $uncompiledClassName => $x) {
+ // Skip this class if it's already been successfully compiled.
+ if (isset($this->_propertyMapCache->classMaps[$uncompiledClassName])) {
+ unset($this->uncompiledPropertyClasses[$uncompiledClassName]);
+ continue;
+ }
+
+ // Attempt to compile the missing class. We do this one by one
+ // in isolation, so that each extra class we compile gets its
+ // entirely own competition-free sub-compiler with its own
+ // class-locks (since our own top-level rootCompiler's hierarchy
+ // is already fully compiled by this point). Which means that
+ // the sub-classes are welcome to refer to anything we've
+ // already compiled, exactly as if each of these extra classes
+ // were new top-level compiles "running in isolation". The only
+ // difference is that they aren't marked as the root compiler,
+ // since we still want to be the one to resolve all of THEIR
+ // uncompiled property-classes here during THIS loop. Mainly so
+ // that we can be the one to throw exception messages, referring
+ // to the correct root-level class as the one that failed.
+ try {
+ // NOTE: If this subcompile is successful and encounters any
+ // more uncompiled property-classes within the classes it
+ // compiles, they'll be automatically added to OUR OWN list
+ // of "uncompiledPropertyClasses" and WILL be resolved too.
+ //
+ // This will carry on until ALL of the linked classes are
+ // compiled and no more work remains. In other words, we
+ // will resolve the COMPLETE hierarchies of every linked
+ // class and ALL of their properties too. However, this
+ // subcompilation is still very fast since most end-stage
+ // jobs refer to classes that are already mostly-compiled
+ // due to having most of their own dependencies already
+ // pre-compiled at that point.
+ $this->_subcompile($uncompiledClassName); // Throws.
+ } catch (\Exception $e) { // NOTE: Could target exact type, but meh.
+ // Failed to compile the class we discovered in a property.
+ // It can be due to all kinds of problems, such as circular
+ // property maps or bad imports or bad definitions or
+ // corrupt map variables or anything else. We'll wrap the
+ // error message in a slightly prefixed message just to hint
+ // that this problem is with a property. Because the
+ // compilation error message itself is already very clear
+ // about which class failed to compile and why.
+ // NOTE: This "prefixed" message handling is unlike parents
+ // and imports, where we simply let their original exception
+ // message bubble up as if they were part of the core class,
+ // since imports are a more "integral" part of a class (a
+ // class literally CANNOT be built without its parents and
+ // its imports compiling). But CLASSES IN PROPERTIES are
+ // different and are more numerous and are capable of being
+ // resolved by _getProperty() LATER without having been
+ // pre-compiled when the main class map itself was compiled
+ // (although we just DID that pre-compilation above; we'll
+ // NEVER let them wait to get resolved later).
+ // NOTE: This WILL cause us to lose the specific class-type
+ // of the exception, such as CircularPropertyMapException,
+ // etc. That's intentional, since THIS error is about a bad
+ // map in the PARENT-hierarchy (IT pointing at a bad class).
+ throw new BadPropertyMapException(sprintf(
+ 'Compilation of sub-property hierarchy failed for class "%s". Reason: %s',
+ $this->_strictSolveClassName, $e->getMessage()
+ ));
+ } // End of _subcompile() try-catch.
+
+ // The sub-compile was successful (nothing was thrown), which
+ // means that it's now in the compiled property map cache. Let's
+ // unset its entry from our list of uncompiled classes.
+ unset($this->uncompiledPropertyClasses[$uncompiledClassName]);
+ } // End of work-queue loop.
+ } // End of "handle uncompiled property classes" loop.
+ }
+
+ /**
+ * Reflect all classes in the solve-class hierarchy.
+ *
+ * Builds a list of all classes in the inheritance chain, with the
+ * base-level class as the first element, and the solve-class as the
+ * last element. (The order is important!)
+ *
+ * @throws BadPropertyMapException
+ */
+ private function _reflectSolveClassHierarchy()
+ {
+ if (!empty($this->_classHierarchy)) {
+ throw new BadPropertyMapException('Detected multiple calls to _reflectSolveClassHierarchy().');
+ }
+
+ try {
+ // Begin reflecting the "solve-class". And completely refuse to
+ // proceed if the class name we were asked to solve doesn't match
+ // its EXACT real name. It's just a nice bonus check for safety,
+ // to ensure that our caller will be able to find their expected
+ // result in the correct cache key later.
+ $reflector = new ReflectionClass($this->_strictSolveClassName);
+ if ($this->_solveClassName !== $reflector->getName()) {
+ throw new BadPropertyMapException(sprintf(
+ 'Unable to compile class "%s" due to mismatched class name parameter value (the real class name is: "%s").',
+ $this->_strictSolveClassName, Utilities::createStrictClassPath($reflector->getName())
+ ));
+ }
+
+ // Now resolve all classes in its inheritance ("extends") chain.
+ do {
+ // Store the class in the hierarchy.
+ // NOTE: The key is VERY important because it's used later when
+ // checking if a specific class exists in the current hierarchy.
+ $this->_classHierarchy[$reflector->getName()] = [
+ 'reflector' => $reflector,
+ 'namespace' => $reflector->getNamespaceName(),
+ 'className' => $reflector->getName(), // Includes namespace.
+ 'strictClassName' => Utilities::createStrictClassPath($reflector->getName()),
+ ];
+
+ // Update the reflector variable to point at the next parent
+ // class in its hierarchy (or false if no more exists).
+ $reflector = $reflector->getParentClass();
+ } while ($reflector !== false);
+
+ // Reverse the list to fix the order, since we built it "bottom-up".
+ $this->_classHierarchy = array_reverse($this->_classHierarchy, true);
+ } catch (ReflectionException $e) {
+ // This should only be able to fail if the classname was invalid.
+ throw new BadPropertyMapException(sprintf(
+ 'Reflection of class hierarchy failed for class "%s". Reason: "%s".',
+ $this->_strictSolveClassName, $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Check for already-locked classes in the solve-class hierarchy.
+ *
+ * Analyzes the solve-class hierarchy and verifies that we don't violate
+ * any current anti-circular map locks.
+ *
+ * @throws CircularPropertyMapException
+ */
+ private function _checkCurrentLocks()
+ {
+ foreach ($this->_classHierarchy as $classInfo) {
+ // FATAL: If any part of our class hierarchy is already locked ("is
+ // being compiled right now", higher up the call stack), then it's a
+ // bad map with circular "import"-statements.
+ // NOTE: This specifically protects against the dangerous scenario
+ // of importing classes derived from our hierarchy but whose
+ // classname isn't in the current class hierarchy, so they weren't
+ // blocked by the "import-target sanity checks". So when they get
+ // to their own sub-compilation, their class hierarchy is checked
+ // here and we'll discover their circular inheritance.
+ if (isset($this->_propertyMapCache->compilerLocks[$classInfo['className']])) {
+ // NOTE: strictClassName goes in "arg1" because it's being
+ // compiled earlier than us, so we're definitely the "arg2".
+ throw new CircularPropertyMapException(
+ $classInfo['strictClassName'],
+ $this->_strictSolveClassName
+ );
+ }
+ }
+ }
+
+ /**
+ * Lock all uncompiled classes in the solve-class hierarchy.
+ *
+ * @throws BadPropertyMapException
+ */
+ private function _lockUncompiledClasses()
+ {
+ if (!empty($this->_ourCompilerClassLocks)) {
+ throw new BadPropertyMapException('Detected multiple calls to _lockUncompiledClasses().');
+ }
+
+ foreach ($this->_classHierarchy as $classInfo) {
+ // If the class isn't already compiled, we'll lock it so nothing
+ // else can refer to it. We'll lock every unresolved class, and then
+ // we'll be unlocking them one by one as we resolve them during THIS
+ // exact compile-call. No other sub-compilers are allowed to use
+ // them while we're resolving them!
+ if (!isset($this->_propertyMapCache->classMaps[$classInfo['className']])) {
+ // NOTE: We now know that NONE of these unresolved classes were
+ // in the lock-list before WE added them to it. That means that
+ // we can safely clear ANY of those added locks if there's a
+ // problem. But WE are NOT allowed to clear ANY OTHER LOCKS!
+ $this->_ourCompilerClassLocks[$classInfo['className']] = true;
+ $this->_propertyMapCache->compilerLocks[$classInfo['className']] = true;
+ }
+ }
+ }
+
+ /**
+ * Process the current class in the solve-class hierarchy.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _processCurrentClass()
+ {
+ // We must always begin by reflecting its "JSON_PROPERTY_MAP" class
+ // constant to determine if this particular class in the hierarchy
+ // re-defines its value (compared to inheriting it from its "extends"
+ // parent). This protects against accidentally re-parsing inherited
+ // constants, which would both waste time AND would be VERY dangerous,
+ // since that would re-interpret all inherited relative properties (ones
+ // pointing at classes relative to the class which ACTUALLY declared
+ // that constant) as if they were relative to the INHERITING class
+ // instead, which is VERY wrong (and could lead to classes being either
+ // mismapped or "not found"). Luckily the code below fully protects us
+ // against that scenario, by detecting if our value is "ours" or not.
+ try {
+ // Use different techniques based on what their PHP supports.
+ $foundConstant = false;
+ if (version_compare(PHP_VERSION, '7.1.0') >= 0) {
+ // In PHP 7.1.0 and higher, they've finally added "reflection
+ // constants" which allow us to get accurate extra information.
+ $reflectionConstant = $this->_currentClassInfo['reflector']
+ ->getReflectionConstant('JSON_PROPERTY_MAP');
+ if ($reflectionConstant !== false) {
+ $foundConstant = true;
+
+ // Just read its value. We don't have to care about its
+ // isPrivate() flags etc. It lets us read the value anyway.
+ $rawClassPropertyMap = $reflectionConstant->getValue();
+
+ // Use PHP7.1's ReflectionClassConstant's ability to tell us
+ // EXACTLY which class declared the current (inherited or
+ // new) value for the constant. If OUR class didn't declare
+ // it, then we know that it was inherited and should NOT be
+ // parsed again. But if we DID declare its value, then we
+ // know that we MUST parse it ("has different constant").
+ // NOTE: This method is 100% accurate even when re-declared
+ // to the exact same value as the inherited (parent) value!
+ $hasDifferentConstant = ($reflectionConstant
+ ->getDeclaringClass()->getName()
+ === $this->_currentClassInfo['className']);
+ }
+ } else {
+ // In older PHP versions, we're pretty limited... we don't get
+ // ANY extra information about the constants. We just get their
+ // values. And if we try to query a specific constant, we get
+ // FALSE if it doesn't exist (which is indistinguishable from it
+ // actually having FALSE values). So we MUST get an array of all
+ // constants to be able to check whether it TRULY exists or not.
+ $classConstants = $this->_currentClassInfo['reflector']
+ ->getConstants();
+ if (array_key_exists('JSON_PROPERTY_MAP', $classConstants)) {
+ $foundConstant = true;
+
+ // Read its value. Unfortunately old versions of PHP don't
+ // give us ANY information about which class declared it
+ // (meaning whether it was inherited or re-declared here).
+ $rawClassPropertyMap = $classConstants['JSON_PROPERTY_MAP'];
+
+ // MAGIC: This is the best we can do on old PHP versions...
+ // The "!==" ensures that this constant really differs from
+ // its parent. In case of arrays, they are only treated as
+ // identical if both arrays have the same key & value types
+ // and values in the exact same order and same count(). And
+ // it checks recursively!
+ //
+ // NOTE: This method is great, but it's not as accurate as
+ // the perfect PHP 7.1 method. It actually has a VERY TINY
+ // issue where it will fail to detect a new constant: If you
+ // manually re-declare a constant to EXACTLY the same value
+ // as what you would have inherited from your parent
+ // ("extends") hierarchy, then we won't be able to detect
+ // that you have a new value. Instead, you will inherit the
+ // compiled parent class map as if you hadn't declared any
+ // value of your own at all. In almost all cases, that won't
+ // matter, and will give you the same result. It only
+ // matters in a very tiny case which probably won't happen
+ // to anybody in real-world usage:
+ //
+ // namespace A { class A { "foo":"B[]" } class B {} }
+ // namespace Z { class Z extends \A\A { "foo":"B[]" } class B {} }
+ //
+ // In that situation, Z\Z would inherit the constant from
+ // A\A, which compiled the relative-class "foo" property as
+ // "\A\B". And when we look at Z's own constant, we would
+ // see a 100% identical JSON_PROPERTY_MAP constant compared
+ // to A\A, so we would assume that since all the array keys
+ // and values match exactly, its constant "was inherited".
+ // Therefore the "identical" constant of Z will not be
+ // parsed. And Z's foo will therefore also link to "\A\B",
+ // instead of "\Z\B".
+ //
+ // In reality, I doubt that ANY user would EVER have such a
+ // weird inheritance structure, with "extends" across
+ // namespaces and relative paths to classes that exist in
+ // both namespaces, etc. And the problem above would not be
+ // able to happen as long as their "Z\Z" map has declared
+ // ANY other value too (or even just changed the order of
+ // the declarations), so that we detect that their constant
+ // differs in "Z\Z". So 99.9999% of users will be safe.
+ //
+ // And no... we CAN'T simply always re-interpret it, because
+ // then we'd get a FAR more dangerous bug, which means that
+ // ALL relative properties would re-compile every time they
+ // are inherited, "as if they had been declared by us".
+ //
+ // So no, this method really is the BEST that PHP < 7.1 has.
+ $hasDifferentConstant = ($rawClassPropertyMap
+ !== $this->_previousMapConstantValue);
+
+ // Update the "previous constant value" since we will be the
+ // new "previous" value after this iteration.
+ $this->_previousMapConstantValue = $rawClassPropertyMap;
+ }
+ }
+ if (!$foundConstant) {
+ // The constant doesn't exist. Should never be able to happen
+ // since the class inherits from LazyJsonMapper.
+ throw new ReflectionException(
+ // NOTE: This exception message mimics PHP's own reflection
+ // error message style.
+ 'Constant JSON_PROPERTY_MAP does not exist'
+ );
+ }
+ } catch (ReflectionException $e) {
+ // Unable to read the map constant from the class...
+ throw new BadPropertyMapException(sprintf(
+ 'Reflection of JSON_PROPERTY_MAP constant failed for class "%s". Reason: "%s".',
+ $this->_currentClassInfo['strictClassName'], $e->getMessage()
+ ));
+ }
+
+ // If we've already parsed that class before, it means that we've
+ // already fully parsed it AND its parents. If so, just use the cache.
+ if (isset($this->_propertyMapCache->classMaps[$this->_currentClassInfo['className']])) {
+ // Overwrite our whole "_currentClassPropertyMap" with the one from
+ // our parent instead. That's faster than merging the arrays, and
+ // guarantees PERFECT PropertyDefinition instance re-usage.
+ // NOTE: To explain it, imagine class B extending from A. A has
+ // already been parsed and cached. We then instantiate B for the
+ // first time and we need to build B's definitions. By copying the
+ // cache from A (its parent), we'll re-use all of A's finished
+ // PropertyDefinition objects in B and throughout any other chain
+ // that derives from the same base object (A). This holds true for
+ // ALL objects that inherit (or "import") ANYTHING (such as
+ // something that later refers to "B"), since we ALWAYS resolve the
+ // chains top-down from the base class to the deepest child class,
+ // and therefore cache the base (hierarchically shared) definitions
+ // FIRST.
+ $this->_currentClassPropertyMap = $this->_propertyMapCache->classMaps[
+ $this->_currentClassInfo['className']
+ ];
+ } else {
+ // We have not parsed/encountered this class before...
+
+ // Only parse its class constant if it differs from its inherited
+ // parent's constant value. Otherwise we'll keep the parent's map
+ // (the value that's currently in "_currentClassPropertyMap").
+ if ($hasDifferentConstant) {
+ // Process and validate the JSON_PROPERTY_MAP of the class.
+ if (!is_array($rawClassPropertyMap)) {
+ throw new BadPropertyMapException(sprintf(
+ 'Invalid JSON property map in class "%s". The map must be an array.',
+ $this->_currentClassInfo['strictClassName']
+ ));
+ }
+ foreach ($rawClassPropertyMap as $propName => $propDefStr) {
+ // Process the current entry and add it to the current
+ // class' compiled property map if the entry is new/diff.
+ $this->_processPropertyMapEntry( // Throws.
+ $propName,
+ $propDefStr
+ );
+ }
+ }
+
+ // Mark the fact that we have compiled this class, so that we'll be
+ // able to know which classes we have compiled later. This list will
+ // bubble through the compile-call-hierarchy as-needed to keep track
+ // of EVERY compiled class during the current root compile()-run.
+ $this->compiledClasses[$this->_currentClassInfo['className']] = true;
+
+ // Now cache the final property-map for this particular class in the
+ // inheritance chain... Note that if it had no new/different map
+ // declarations of its own (despite having a different constant),
+ // then we're still simply re-using the exact property-map objects
+ // of its parent class here again.
+ $this->_propertyMapCache->classMaps[
+ $this->_currentClassInfo['className']
+ ] = $this->_currentClassPropertyMap;
+ } // End of class-map "lookup-or-compilation".
+
+ // IMPORTANT: We've finished processing a class. If this was one of the
+ // classes that WE locked, we MUST NOW unlock it, but ONLY if we
+ // successfully processed the class (added it to the cache). Otherwise
+ // we'll unlock it at the end (where we unlock any stragglers) instead.
+ // NOTE: We AREN'T allowed to unlock classes we didn't lock.
+ // NOTE: Unlocking is IMPORTANT, since it's necessary for being able to
+ // import classes from diverging branches with shared inheritance
+ // ancestors. If we don't release the parent-classes one by one as we
+ // resolve them, then we would never be able to import classes from
+ // other branches of our parent/class hierarchy, since the imports would
+ // see that one of their parents (from our hierarchy) is still locked
+ // and would refuse to compile themselves.
+ if (isset($this->_ourCompilerClassLocks[$this->_currentClassInfo['className']])) {
+ unset($this->_ourCompilerClassLocks[$this->_currentClassInfo['className']]);
+ unset($this->_propertyMapCache->compilerLocks[$this->_currentClassInfo['className']]);
+ }
+ }
+
+ /**
+ * Process a property map entry for the current class.
+ *
+ * Validates and compiles a map entry, and merges it with the current class
+ * property map if everything went okay and the entry was new/different.
+ *
+ * @param string|int $propName The property name, or a numeric array key.
+ * @param mixed $propDefStr Should be a string describing the property
+ * or the class to import, but may be
+ * something else if the user has written an
+ * invalid class property map.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _processPropertyMapEntry(
+ $propName,
+ $propDefStr)
+ {
+ if (is_string($propName) && is_string($propDefStr)) {
+ // This is a string key -> string value pair, so let's attempt to
+ // compile it as a regular property definition and then add it to
+ // our current class map if the entry is new/different.
+ $this->_processPropertyDefinitionString( // Throws.
+ $propName,
+ $propDefStr
+ );
+ } else {
+ // It cannot be a regular property definition. Check if this is an
+ // "import class map" command. They can exist in the map and tell us
+ // to import other classes, and their instructions are written as
+ // [OtherClass::class, 'ownfield'=>'string'].
+ $isImportCommand = false;
+ if (is_int($propName) && is_string($propDefStr)) {
+ // This is an int key -> string value pair, so we should treat
+ // it as a potential "import class map" command. We must first
+ // ensure that the class has a strictly global "\" prefix.
+ $strictImportClassName = Utilities::createStrictClassPath($propDefStr);
+
+ // Now check if the target class fits the "import class"
+ // requirements.
+ if (class_exists($strictImportClassName)
+ && is_subclass_of($strictImportClassName, '\\'.LazyJsonMapper::class)) {
+ // This is an "import other class" command! Thanks to the
+ // lack of an associative key, we saw a numeric array key
+ // (non-associative). And we've verified (above) that its
+ // value points to another valid class which is a sub-class
+ // of LazyJsonMapper (we don't bother allowing to import
+ // LazyJsonMapper itself, since it is the lowest possible
+ // base class and has 0 values).
+ $isImportCommand = true;
+
+ // Perform the import, which will compile the target (if
+ // necessary) and then merge its map with our current map.
+ // NOTE: There is no need for us to prevent the user from
+ // doing multiple imports of the same class, because the way
+ // the importing works ensures that we always re-use the
+ // same PropertyDefinition object instances from the other
+ // class every time we import the other class.
+ $this->_importClassMap( // Throws.
+ $strictImportClassName
+ );
+ }
+ }
+ if (!$isImportCommand) {
+ // This map-array value is definitely NOT okay.
+ throw new BadPropertyMapException(sprintf(
+ 'Invalid JSON property map entry "%s" in class "%s".',
+ $propName, $this->_currentClassInfo['strictClassName']
+ ));
+ }
+ }
+ }
+
+ /**
+ * Compile a property definition string and add it to the current class.
+ *
+ * Attempts to compile the definition, and then appends it to the current
+ * class' final compiled map, IF the definition is valid and describes a
+ * new/different value compared to what's already in the current class map.
+ *
+ * @param string $propName The property name.
+ * @param string $propDefStr A string describing the property.
+ *
+ * @throws BadPropertyDefinitionException
+ */
+ private function _processPropertyDefinitionString(
+ $propName,
+ $propDefStr)
+ {
+ try {
+ // Validates the definition and throws if bad.
+ // NOTE: The namespace argument here is INCREDIBLY important. It's
+ // what allows each class to refer to classes relative to its own
+ // namespace, rather than needing to type the full, global class
+ // path. Without that parameter, the PropertyDefinition would only
+ // search in the global namespace.
+ // NOTE: "use" statements are ignored since PHP has no mechanism
+ // for inspecting those. But the user will quickly notice such a
+ // problem, since such classnames will warn as "not found" here.
+ $propDefObj = new PropertyDefinition( // Throws.
+ $propDefStr,
+ $this->_currentClassInfo['namespace']
+ );
+
+ // MEMORY OPTIMIZATION TRICK: If we wanted to be "naive", we could
+ // simply assign this new PropertyDefinition directly to our current
+ // class property map, regardless of whether the property-name key
+ // already exists. It would certainly give us the "right" result...
+ //
+ // But imagine if one of our parent objects or previous imports have
+ // already created a property as "foo":"string[]", and imagine that
+ // we ALSO define a "foo":"string[]", even though we've already
+ // inherited that EXACT compiled instruction. What would happen?
+ //
+ // Well, if we instantly assign our own, newly created object, even
+ // though it's equal to an identically named and identically defined
+ // property that we've already inherited/imported, then we waste RAM
+ // since we've now got two INDEPENDENT "compiled property" object
+ // instances that describe the EXACT same property-settings.
+ //
+ // Therefore, we can save RAM by first checking if the property name
+ // already exists in our currently compiled map; and if so, whether
+ // its pre-existing PropertyDefinition already describes the EXACT
+ // same settings. If so, we can keep the existing object (which we
+ // KNOW is owned by a parent/import and will therefore always remain
+ // in memory). Thus saving us the RAM-size of a PropertyDefinition
+ // object. In fact, the re-use means the RAM is the same as if the
+ // sub-class hadn't even overwritten the inherited/imported property
+ // AT ALL! So this protective algorithm makes class JSON property
+ // re-definitions to identical settings a ZERO-cost act.
+ //
+ // IMPORTANT: This only works because compiled properties are
+ // immutable, meaning we can trust that the borrowed object will
+ // remain the same. We are NEVER going to allow any runtime
+ // modifications of the compiled maps. So re-use is totally ok.
+ if (!isset($this->_currentClassPropertyMap[$propName])
+ || !$propDefObj->equals($this->_currentClassPropertyMap[$propName])) {
+ // Add the unique (new/different) property to our class map.
+ $this->_currentClassPropertyMap[$propName] = $propDefObj;
+
+ // Alright, we've encountered a brand new property, and we know
+ // it's pointing at a LazyJsonMapper if it's an object. But we
+ // DON'T know if the TARGET class map can actually COMPILE. We
+ // therefore need to add the class to the list of encountered
+ // property classes. But only if we haven't already compiled it.
+ if ($propDefObj->isObjectType
+ && !isset($this->_propertyMapCache->classMaps[$propDefObj->propType])) {
+ // NOTE: During early compilations, at the startup of a PHP
+ // runtime, this will pretty much always add classes it sees
+ // since NOTHING is compiled while the first class is being
+ // compiled. Which means that this list will contain classes
+ // that are currently BEING SOLVED as the class hierarchy
+ // keeps resolving/compiling itself. But we'll take care of
+ // that by double-checking the list later before we're done.
+ // TODO: PERHAPS we can safely optimize this HERE by also
+ // checking for the target class in _propertyMapCache's list
+ // of locked classes... But planning would be necessary.
+ $this->uncompiledPropertyClasses[$propDefObj->propType] = true;
+ }
+ }
+ } catch (BadPropertyDefinitionException $e) {
+ // Add details and throw from here instead.
+ throw new BadPropertyDefinitionException(sprintf(
+ 'Bad property definition for "%s" in class "%s" (Error: "%s").',
+ $propName, $this->_currentClassInfo['strictClassName'], $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Import another class map into the current class.
+ *
+ * @param string $strictImportClassName The strict, global path to the
+ * class, with leading backslash `\`.
+ * To save time, we assume the caller
+ * has already verified that it is a
+ * valid `LazyJsonMapper` class.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _importClassMap(
+ $strictImportClassName)
+ {
+ // Begin via reflection to resolve the EXACT name of the class they want
+ // to import, so that we can trust its name completely.
+ try {
+ // NOTE: The strict, global "\"-name is necessary here, to guarantee
+ // that we find the correct target class via the global namespace.
+ $reflector = new ReflectionClass($strictImportClassName);
+ $importClassName = $reflector->getName(); // The clean, real name.
+ $strictImportClassName = Utilities::createStrictClassPath($importClassName);
+ } catch (ReflectionException $e) {
+ // This should never be able to fail (since our caller already
+ // verified that the class exists), but treat failure as a bad map.
+ throw new BadPropertyMapException(sprintf(
+ 'Reflection failed for class "%s", when trying to import it into class "%s". Reason: "%s".',
+ $strictImportClassName, $this->_currentClassInfo['strictClassName'], $e->getMessage()
+ ));
+ }
+
+ // FATAL: If the encountered import-statement literally refers to itself
+ // (the class we're currently compiling in the hierarchy), then it's a
+ // totally ridiculous circular self-reference of the most insane kind.
+ if ($importClassName === $this->_currentClassInfo['className']) {
+ throw new CircularPropertyMapException(
+ $strictImportClassName,
+ $strictImportClassName
+ );
+ }
+
+ // FATAL: If the import-statement refers to ANY class in OUR OWN
+ // current class' hierarchy, then it's a circular self-reference. We
+ // forbid those because they make ZERO sense. For example: "ABCD", in
+ // that case, "D" COULD definitely import "B" because "D" is later in
+ // the chain and "B" is therefore already resolved. But WHY? We already
+ // inherit from it! And we could NEVER do the reverse; "B" could never
+ // import "D", because "D" depends on "B" being fully compiled already.
+ // Likewise, we can't allow stupid things like "B imports B". Doesn't
+ // make sense. Therefore, we disallow ALL self-imports.
+ //
+ // NOTE: In the example of "B" importing "D", that's NOT caught here
+ // since the classname "D" wouldn't be part of B's hierarchy. But it
+ // will be caught during our upcoming attempt to sub-compile "D" to
+ // parse ITS hierarchy, since "D" will begin to compile and check its
+ // own class hierarchy, and will arrive at the "B" class and will then
+ // detect that "B" is locked as "already being resolved", since the
+ // ongoing resolving of "B" was what triggered the compilation of "D".
+ if (isset($this->_classHierarchy[$importClassName])) {
+ // NOTE: strictSolveClassName goes in "arg1" because we're the ones
+ // trying to import an invalid target class that's already part of
+ // our own hierarchy.
+ throw new CircularPropertyMapException(
+ $this->_strictSolveClassName,
+ $strictImportClassName
+ );
+ }
+
+ // FATAL: Also prevent users from importing any class that's in the list
+ // of "locked and currently being resolved", since that means that the
+ // class they're trying to import is ITSELF part of a compilation-chain
+ // that's CURRENTLY trying to resolve itself right now.
+ //
+ // NOTE: This catches scenarios where class-chains have common ancestors
+ // but then diverge and go into a circular reference to each other. For
+ // example, "A extends LazyJsonMapper, imports B", "B extends
+ // LazyJsonMapper, imports A". Imagine that you construct "A", which
+ // locks "A" during its compilation process. The parent "LazyJsonMapper"
+ // class is successfully compiled since it has no dependencies. Then "A"
+ // sees "import B" and sub-compiles B. (Remember that "A" remains locked
+ // when that happens, since "A" is waiting for "B" to resolve itself.)
+ // The compilation code of "B" begins running. It first checks its
+ // inheritance hierarchy and sees nothing locked (LazyJsonMapper is
+ // unlocked because it's already done, and B is unlocked because B has
+ // not locked itself yet). Next, "B" sees an "import A" statement. It
+ // succeeds the "block import of A if it's part of our own hierarchy"
+ // check above, since B is NOT "extended from A".
+ //
+ // So then we're at a crossroads... there IS a circular reference, but
+ // we don't know it yet. We COULD let it proceed to "resolve A by
+ // sub-compiling the map for A", which would give us a call-stack of
+ // "compile A -> compile B -> compile A", but then the 2nd "A" compiler
+ // would run, and would do its early lock-check and notice that itself
+ // (A) is locked, since the initial compilation of "A" is not yet done.
+ // It would then throw a non-sensical error saying that there's a
+ // circular reference between "A and A", which is technically true, but
+ // makes no sense. That's because we waited too long!
+ //
+ // So, what the code BELOW does instead, is that when "B" is trying to
+ // resolve itself (during the "compile A -> import B -> compile B"
+ // phase), the "B" compiler will come across its "import A" statement,
+ // and will now detect that "A" is already locked, and therefore throws
+ // a proper exception now instead of letting "A" attempt to compile
+ // itself a second time as described above. The result of this early
+ // sanity-check is a better exception message (correct class names).
+ foreach ($this->_propertyMapCache->compilerLocks as $lockedClassName => $isLocked) {
+ if ($isLocked && $lockedClassName === $importClassName) {
+ // NOTE: strictSolveClassName goes in "arg2" because we're
+ // trying to import something unresolved that's therefore higher
+ // than us in the call-stack.
+ throw new CircularPropertyMapException(
+ $strictImportClassName,
+ $this->_strictSolveClassName
+ );
+ }
+ }
+
+ // Now check if the target class is missing from our cache, and if so
+ // then we must attempt to compile (and cache) the map of the target
+ // class that we have been told to import.
+ //
+ // NOTE: This sub-compilation will perform the necessary recursive
+ // compilation and validation of the import's WHOLE map hierarchy. When
+ // the compiler runs, it will ensure that nothing in the target class
+ // hierarchy is currently locked (that would be a circular reference),
+ // and then it will lock its own hierarchy too until it has resolved
+ // itself. This will go on recursively if necessary, until ALL parents
+ // and ALL further imports have been resolved. And if there are ANY
+ // circular reference ANYWHERE, or ANY other problems with its
+ // map/definitions, then the sub-compilation(s) will throw appropriate
+ // exceptions.
+ //
+ // We won't catch them; we'll just let them bubble up to abort the
+ // entire chain of compilation, since a single error anywhere in the
+ // whole chain means that whatever root-level compile-call initially
+ // began this compilation process _cannot_ be resolved either. So we'll
+ // let the error bubble up all the way to the original call.
+ if (!isset($this->_propertyMapCache->classMaps[$importClassName])) {
+ // Compile the target class to import.
+ // NOTE: We will not catch the error. So if the import fails to
+ // compile, it'll simply output its own message saying that
+ // something was wrong in THAT class. We don't bother including any
+ // info about our class (the one which imported it). That's still
+ // very clear in this case, since the top-level class compiler and
+ // its hierarchy will be in the stack trace as well as always having
+ // a clearly visible import-command in their class property map.
+ $this->_subcompile($importClassName); // Throws.
+ }
+
+ // Now simply loop through the compiled property map of the imported
+ // class... and add every property to our own compiled property map. In
+ // case of clashes, we use the imported one.
+ // NOTE: We'll directly assign its PD objects as-is, which will assign
+ // the objects by shallow "shared instance", so that we re-use the
+ // PropertyDefinition object instances from the compiled class that
+ // we're importing. That will save memory.
+ // NOTE: There's no point doing an equals() clash-check here, since
+ // we're importing an EXISTING, necessary definition from another class,
+ // so we won't save any memory by avoiding their version even if the
+ // definitions are equal.
+ foreach ($this->_propertyMapCache->classMaps[$importClassName] as $importedPropName => $importedPropDefObj) {
+ $this->_currentClassPropertyMap[$importedPropName] = $importedPropDefObj;
+ }
+ }
+
+ /**
+ * Used internally when this compiler needs to run a sub-compilation.
+ *
+ * Performs the sub-compile. And if it was successful (meaning it had no
+ * auto-rollbacks due to ITS `compile()` call failing), then we'll merge its
+ * compiler state with our own so that we preserve important state details.
+ *
+ * @param string $className The full path of the class to sub-compile, but
+ * without any leading `\` global prefix. To save
+ * time, we assume the caller has already verified
+ * that it is a valid `LazyJsonMapper` class.
+ *
+ * @throws BadPropertyDefinitionException
+ * @throws BadPropertyMapException
+ * @throws CircularPropertyMapException
+ */
+ private function _subcompile(
+ $className)
+ {
+ // Sub-compile the target class. If this DOESN'T throw, we know that the
+ // target class successfully exists in the compilation cache afterwards.
+ // NOTE: If it throws, we know that IT has already rolled back all its
+ // changes itself via its own compile()-catch, so WE don't have to worry
+ // about reading its state on error. We ONLY care when it succeeds.
+ $subCompiler = new self( // Throws.
+ false, // Is NOT the root call!
+ $this->_propertyMapCache, // Use the same cache to share locks.
+ $className
+ );
+ $subCompiler->compile(); // Throws.
+
+ // Add its state of successfully compiled classes to our own list of
+ // compiled classes, so that the info is preserved throughout the stack.
+ // NOTE: This is EXTREMELY important, so that we can clear those classes
+ // from the cache in case ANY other step of the compilation chain fails
+ // unexpectedly. Because in that case, we'll NEED to be able to roll
+ // back ALL of our entire compile-chain's property map cache changes!
+ // NOTE: The merge function de-duplicates keys!
+ $this->compiledClasses = array_merge(
+ $this->compiledClasses,
+ $subCompiler->compiledClasses
+ );
+
+ // Add the sub-compiled class hierarchy's own encountered property-
+ // classes to our list of encountered classes. It gives us everything
+ // from their extends-hierarchy and everything from their own imports.
+ // And in case they had multi-level chained imports (classes that import
+ // classes that import classes), it'll actually be containing a FULLY
+ // recursively resolved and merged sub-import list. We will get them ALL
+ // from their WHOLE sub-hierarchy! Exactly as intended.
+ // NOTE: The merge function de-duplicates keys!
+ $this->uncompiledPropertyClasses = array_merge(
+ $this->uncompiledPropertyClasses,
+ $subCompiler->uncompiledPropertyClasses
+ );
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/UndefinedProperty.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/UndefinedProperty.php
new file mode 100755
index 0000000..5ff159c
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Property/UndefinedProperty.php
@@ -0,0 +1,79 @@
+arrayDepth`.
+ * @param string $propName The name of the property. For
+ * exception messages.
+ * @param PropertyDefinition $propDef An object describing the property.
+ *
+ * @throws LazyJsonMapperException If the value can't be turned into its
+ * assigned class or built-in PHP type.
+ */
+ public static function convert(
+ $direction,
+ &$value,
+ $remArrayDepth,
+ $propName,
+ PropertyDefinition $propDef)
+ {
+ // Do nothing if this particular value is NULL.
+ if ($value === null) {
+ return; // Skip the rest of the code.
+ }
+
+ // ------------
+ // TODO: Improve all error messages below to make them more
+ // human-readable. Perhaps even display $propDef->asString() as a great
+ // hint about what type the property requires, even indicating what array
+ // depth it must be at. However, the current messages about problems at
+ // "at array-depth X of Y" should be kept, since that level of detail is
+ // very helpful for figuring out which part of the array is bad.
+ // ------------
+
+ // Handle "arrays of [type]" by recursively processing all layers down
+ // until the array-depth, and verifying all keys and values.
+ // NOTE: If array depth remains, we ONLY allow arrays (or NULLs above),
+ // and ALL of the arrays until the depth MUST be numerically indexed in
+ // a sequential order starting from element 0, without any gaps. Because
+ // the ONLY way that a value can be a true JSON ARRAY is if the keys are
+ // numeric and sequential. Otherwise the "array" we are looking at was
+ // originally a JSON OBJECT in the original, pre-json_decode()-d string.
+ // NOTE: We even support "arrays of mixed", and in that case will verify
+ // that the mixed data is at the expected depth and has key integrity.
+ // So specifying "mixed[]" requires data like "[1,null,true]", whereas
+ // specifying "mixed" avoids doing any depth validation.
+ if ($remArrayDepth > 0) {
+ if (!is_array($value)) {
+ if ($direction === self::CONVERT_FROM_INTERNAL) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected non-array value for array-property "%s" at array-depth %d of %d.',
+ $propName, $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ } else {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to assign new non-array value for array-property "%s" at array-depth %d of %d.',
+ $propName, $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ }
+ }
+
+ // Subtract 1 from the remaining array-depth and process current layer.
+ $newRemArrayDepth = $remArrayDepth - 1;
+ $nextValidKey = 0; // Must start at 0.
+ foreach ($value as $k => &$v) { // IMPORTANT: By reference!
+ // OPTIMIZATION: We MUST only allow sequential int keys, but we
+ // avoid the is_int() call by using an int counter instead:
+ if ($k !== $nextValidKey++) { // ++ post increment...
+ // We're in an "array of"-typed JSON property structure, but
+ // encountered either an associative key or a gap in the
+ // normal numeric key sequence. The JSON data is invalid!
+ // Display the appropriate error.
+ if (is_int($k)) {
+ // It is numeric, so there was a gap...
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected out-of-sequence numeric array key (expected: %s, found: %s) for array-property "%s" at array-depth %d of %d.',
+ $nextValidKey - 1, $k, $propName,
+ $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ } else {
+ // Invalid associative key in a numeric array.
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected non-numeric array key ("%s") for array-property "%s" at array-depth %d of %d.',
+ $k, $propName, $propDef->arrayDepth - $remArrayDepth, $propDef->arrayDepth
+ ));
+ }
+ }
+
+ // The key was valid, so convert() any sub-values within this
+ // value. Its next depth is either 0 (values) or 1+ (more arrays).
+ if ($v !== null) { // OPTIMIZATION: Avoid useless call if null.
+ if ($direction === self::CONVERT_FROM_INTERNAL) {
+ self::convert($direction, $v, $newRemArrayDepth, $propName, $propDef);
+ } else {
+ self::convert($direction, $v, $newRemArrayDepth, $propName, $propDef);
+ }
+ }
+ }
+
+ // Skip rest of the code, since array depth remained in this call.
+ return;
+ } // End of "remaining array depth" handler.
+
+ // Alright, we now know that we're at the "data depth". However, the
+ // property itself may be untyped ("mixed"). Handle that first.
+ if ($propDef->propType === null) {
+ // This is a non-NULL but "untyped" property, which means that we
+ // only accept three things: NULL, Scalars (int, float, string,
+ // bool) and arrays of those. We will NOT allow any Objects or
+ // external Resources. And arrays at this level will only be
+ // allowed if the "mixed" type didn't specify any depth.
+
+ // We've already checked NULL earlier. Check the most common data
+ // types now. Almost all untyped values will be one of these,
+ // meaning even untyped properties are blazingly fast to process.
+ if (is_scalar($value)) { // int, float, string, bool
+ return; // Scalar accepted. Skip the rest of the code.
+ }
+
+ // Alright... then it's either an array, Object or Resource.
+ try {
+ if (is_array($value)) {
+ // Forbid arrays at this "value-level" IF and only if this
+ // was a "mixed[]" notation untyped property, since the "[]"
+ // specifies the maximum mixed data depth in that case.
+ // ------------------------------------------
+ // TODO: We do not have any PropertyDefinition notation
+ // for specifying "mixed non-array property". Perhaps add
+ // that feature someday, maybe "mixed[-]", since "mixed[]"
+ // is already taken by "1-level deep mixed" and "mixed" is
+ // already taken by "do not check depth of this mixed data".
+ // It would be nice to be able to say that a mixed property
+ // "can contain any basic type but not arrays".
+ // A simple implementation would be that arrayDepth "-1"
+ // for mixed denotes "do not check array depth" ("mixed")
+ // and "0" denotes "check array depth" ("mixed[-]").
+ // Either way, the syntax also needs to be valid PHPdoc so
+ // that the automatic property signatures are valid, so we
+ // actually cannot use "mixed[-]". Perhaps I'm overthinking
+ // all of this, though. Because if the user cares about the
+ // depth of untyped (mixed) properties, they should know
+ // enough to just strongly type-assign something instead.
+ // Although, perhaps this IS a job for PropertyDefinition:
+ // If the user specifies "mixed[-]" we can store it as
+ // untyped with arrayDepth -1, but output it as "mixed" when
+ // converting that PropertyDefinition to signature hints.
+ // ------------------------------------------
+ if ($propDef->arrayDepth > 0) {
+ // This "mixed" type specifies a max-depth, which means
+ // that we've reached it. We cannot allow more arrays.
+ throw new LazyJsonMapperException(sprintf(
+ // Let's try to be REALLY clear so the user understands...
+ // Since I anticipate lots of untyped user properties.
+ '%s non-array inner untyped property of "%s". This untyped property specifies a maximum array depth of %d.',
+ ($direction === self::CONVERT_FROM_INTERNAL
+ ? 'Unexpected inner array value in'
+ : 'Unable to assign new inner array value for'),
+ $propName, $propDef->arrayDepth
+ ));
+ }
+
+ // This mixed property has no max depth. Just verify the
+ // contents recursively to ensure it has no invalid data.
+ array_walk_recursive($value, function ($v) {
+ // NOTE: Mixed properties without max-depth can be
+ // either JSON objects or JSON arrays, and we don't know
+ // which, so we cannot verify their array key-type. If
+ // people want validation of keys, they should set a max
+ // depth for their mixed property OR switch to typed.
+ if ($v !== null && !is_scalar($v)) {
+ // Found bad (non-NULL, non-scalar) inner value.
+ throw new LazyJsonMapperException('bad_inner_type');
+ }
+ });
+ } else {
+ // Their value is an Object or Resource.
+ throw new LazyJsonMapperException('bad_inner_type');
+ }
+ } catch (LazyJsonMapperException $e) {
+ // Automatically select appropriate exception message.
+ if ($e->getMessage() !== 'bad_inner_type') {
+ throw $e; // Re-throw since it already had a message.
+ }
+
+ throw new LazyJsonMapperException(sprintf(
+ // Let's try to be REALLY clear so the user understands...
+ // Since I anticipate lots of untyped user properties.
+ '%s untyped property "%s". Untyped properties can only contain NULL or scalar values (int, float, string, bool), or arrays holding any mixture of those types.',
+ ($direction === self::CONVERT_FROM_INTERNAL
+ ? 'Unexpected value in'
+ : 'Unable to assign invalid new value for'),
+ $propName
+ ));
+ }
+
+ // If we've come this far, their untyped property contained a valid
+ // array with only NULL/scalars (or nothing at all) inside. Done!
+ return; // Skip the rest of the code.
+ }
+
+ // We need the strict classpath for all object comparisons, to ensure
+ // that we always compare against the right class via global namespace.
+ $strictClassPath = $propDef->isObjectType ? $propDef->getStrictClassPath() : null;
+
+ // Alright... we know that we're at the "data depth" and that $value
+ // refers to a single non-NULL, strongly typed value...
+ if ($direction === self::CONVERT_TO_INTERNAL) {
+ // No incoming value is allowed to be array anymore at this depth.
+ if (is_array($value)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to assign new inner array value for non-array inner property of "%s", which must be of type "%s".',
+ $propName, $propDef->isObjectType ? $strictClassPath : $propDef->propType
+ ));
+ }
+
+ // Now convert the provided individual value, as necessary...
+ if (!$propDef->isObjectType) {
+ // Cast the value to the target built-in PHP type. We cannot cast objects.
+ // NOTE: For performance, we don't check is_resource(), because
+ // those should never be able to appear in somebody's data. And
+ // they can actually be cast to any basic PHP type without any
+ // problems at all. It's only objects that are a problem and
+ // cannot have any "settype()" applied to them by PHP.
+ // Furthermore, is_resource() isn't even reliable anyway, since
+ // it returns false if it is a closed resource. So whatever,
+ // just rely on the fact that PHP can convert it to basic types.
+ if (is_object($value) || !@settype($value, $propDef->propType)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to cast new inner value for property "%s" to built-in PHP type "%s".',
+ $propName, $propDef->propType
+ ));
+ }
+ } else {
+ // Check that the new value is an object and that it's an instance
+ // of the exact required class (or at least a subclass of it).
+ // NOTE: Since all PropertyDefinition types are validated to derive
+ // from LazyJsonMapper, we don't need to check "instanceof".
+ if (!is_object($value) || !is_a($value, $strictClassPath)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'The new inner value for property "%s" must be an instance of class "%s".',
+ $propName, $strictClassPath
+ ));
+ }
+ }
+ } else { // CONVERT_FROM_INTERNAL
+ // Now convert the individual internal value, as necessary...
+ // NOTE: We validate and convert all values EVERY time, to protect
+ // against things like being constructed with bad non-JSON input-arrays
+ // with bad objects within it, and (even more importantly) to avoid
+ // problems whenever the user modifies internal data by reference via
+ // _getProperty() or __get() (particularly the latter; direct property
+ // access makes it INCREDIBLY easy to directly modify internal data,
+ // especially if they are arrays and the user does $x->items[] = 'foo').
+ if (!$propDef->isObjectType) {
+ // Basic PHP types are not allowed to have an array as their value.
+ // NOTE: If arr, then the PropertyDefinition doesn't match the JSON!
+ if (is_array($value)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected inner array value in non-array inner property for "%s", where we expect value type "%s".',
+ $propName, $propDef->propType
+ ));
+ }
+
+ // Cast the value to the target built-in PHP type. We cannot cast objects.
+ // NOTE: If resources appear in the data, they'll be castable by settype().
+ if (is_object($value) || !@settype($value, $propDef->propType)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to cast inner value for property "%s" to built-in PHP type "%s".',
+ $propName, $propDef->propType
+ ));
+ }
+ } else {
+ // Only convert the value to object if it isn't already an object.
+ if (!is_object($value)) {
+ // Unconverted JSON objects MUST have an array as their inner
+ // value, which contains their object data property list.
+ if (!is_array($value)) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert non-array inner value for property "%s" into class "%s".',
+ $propName, $strictClassPath
+ ));
+ }
+
+ // The encountered array MUST have string-keys, otherwise it
+ // CANNOT be a JSON object. If the array has numerical keys
+ // instead, it means that our array-depth is wrong and that
+ // we're still looking at normal JSON ["foo"] arrays, instead
+ // of associative key-value object {"foo":"bar"} property pairs.
+ // NOTE: We only need to check the first key of the array,
+ // because JSON data CANNOT mix associative and numerical keys.
+ // In fact, if you try something like '{"a":{"a","foo":"bar"}}'
+ // then PHP actually refuses to decode such invalid JSON.
+ // NOTE: If first key is NULL, it means the array is empty. We
+ // must allow empty arrays, since '{"obj":{}}' is valid JSON.
+ // NOTE: We'll only detect non-object inner arrays if their
+ // first key is a numeric int(0). Because objects allow
+ // random numeric keys, but they don't allow sequential ones.
+ reset($value); // Rewind array pointer to its first element.
+ $firstArrKey = key($value); // Get key without moving pointer.
+ if ($firstArrKey !== null && is_int($firstArrKey) && $firstArrKey === 0) {
+ // Determine whether this is a regular array... If it
+ // consists entirely of numeric keys starting at 0 and
+ // going up sequentially without gaps, it's an array...
+ $isRegularArray = true;
+ $nextValidKey = 0; // Must start at 0.
+ foreach ($value as $k => $x) {
+ if ($k !== $nextValidKey++) { // ++ post increment.
+ $isRegularArray = false;
+ break;
+ }
+ }
+
+ // Only throw if it was a totally plain array. This
+ // check ensures that we still allow objects with
+ // numeric keys as long as they aren't 100%
+ // indistinguishable from a regular array.
+ if ($isRegularArray) {
+ throw new LazyJsonMapperException(sprintf(
+ 'Unable to convert non-object-array inner value for property "%s" into class "%s".',
+ $propName, $strictClassPath
+ ));
+ }
+ }
+
+ // Convert the raw JSON array value to its assigned class object.
+ try {
+ // Attempt creation and catch any construction issues.
+ // NOTE: This won't modify $value if construction fails.
+ $value = new $strictClassPath($value); // Constructs the classname in var.
+ } catch (\Exception $e) { // IMPORTANT: Catch ANY exception!
+ throw new LazyJsonMapperException(sprintf(
+ 'Failed to create an instance of class "%s" for property "%s": %s',
+ // NOTE: No need to format the exception message
+ // since it already has perfect grammar.
+ $strictClassPath, $propName, $e->getMessage()
+ ));
+ }
+ }
+
+ // Validate that the class matches the defined property class type.
+ // NOTE: Since all PropertyDefinition types are validated to derive
+ // from LazyJsonMapper, we don't need to verify "instanceof".
+ if (!is_a($value, $strictClassPath)) { // Exact same class or a subclass of it.
+ throw new LazyJsonMapperException(sprintf(
+ 'Unexpected "%s" object in property "%s", but we expected an instance of "%s".',
+ Utilities::createStrictClassPath(get_class($value)),
+ $propName, $strictClassPath
+ ));
+ }
+ } // End of "Get object from internal".
+ } // End of CONVERT_FROM_INTERNAL.
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/src/Utilities.php b/vendor/lazyjsonmapper/lazyjsonmapper/src/Utilities.php
new file mode 100755
index 0000000..1a31708
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/src/Utilities.php
@@ -0,0 +1,180 @@
+ 0) {
+ // Prepend "\" if missing, to force PHP to use the global namespace.
+ if ($className[0] !== '\\') {
+ $className = '\\'.$className;
+ }
+
+ return $className;
+ }
+
+ return null;
+ }
+
+ /**
+ * Splits a strict class-path into its namespace and class name components.
+ *
+ * To rejoin them later, just use: `'namespace' + '\\' + 'class'`.
+ *
+ * The class path should be in `get_class()` aka `TheClass::class` format.
+ *
+ * @param string $strictClassPath Class output of `createStrictClassPath()`.
+ *
+ * @return array Associative array with keys for `namespace` and `class`.
+ *
+ * @see Utilities::createStrictClassPath()
+ */
+ public static function splitStrictClassPath(
+ $strictClassPath = '')
+ {
+ // Split on the rightmost backslash. In a strict path there's always at
+ // least one backslash, for the leading "global namespace" backslash.
+ $lastDelimPos = strrpos($strictClassPath, '\\');
+ // Global: "". Other: "\Foo" or "\Foo\Bar" (if nested namespaces).
+ $namespace = substr($strictClassPath, 0, $lastDelimPos);
+ // Always: "TheClass".
+ $class = substr($strictClassPath, $lastDelimPos + 1);
+
+ return [
+ 'namespace' => $namespace,
+ 'class' => $class,
+ ];
+ }
+
+ /**
+ * Compare two class paths and generate the shortest path between them.
+ *
+ * @param array $sourceComponents Source class as `splitStrictClassPath()`.
+ * @param array $targetComponents Target class as `splitStrictClassPath()`.
+ *
+ * @return string The final path to reach from the source to the target.
+ *
+ * @see Utilities::splitStrictClassPath()
+ */
+ public static function createRelativeClassPath(
+ array $sourceComponents,
+ array $targetComponents)
+ {
+ $sourceNs = &$sourceComponents['namespace'];
+ $targetNs = &$targetComponents['namespace'];
+ $finalType = null;
+
+ // If either the source or the target lives in the global namespace,
+ // we won't do this processing. (Those need a strict, global path.)
+ if ($sourceNs !== '' && $targetNs !== '') {
+ // Check if the source-class namespace is at pos 0 of target space.
+ $pos = strpos($targetNs, $sourceNs);
+ if ($pos === 0) {
+ // Look at the character after the source-class namespace in the
+ // target namespace. Check for "" (str end) or "\\" (subspace).
+ $sourceNsLen = strlen($sourceNs);
+ $chr = substr($targetNs, $sourceNsLen, 1);
+ if ($chr === '') { // Exact same space, without any subspace.
+ $finalType = $targetComponents['class'];
+ } elseif ($chr === '\\') { // Same space, followed by subspace.
+ $finalType = sprintf(
+ '%s\\%s',
+ substr($targetNs, $sourceNsLen + 1),
+ $targetComponents['class']
+ );
+ } // Else: Was false positive, not in same namespace.
+ }
+ }
+
+ // In case of totally different spaces, or if any of the classes are in
+ // the global namespace, then just use the strict, global target path.
+ if ($finalType === null) {
+ $finalType = sprintf(
+ '%s\\%s',
+ $targetNs,
+ $targetComponents['class']
+ );
+ }
+
+ return $finalType;
+ }
+
+ /**
+ * Atomic filewriter.
+ *
+ * Safely writes new contents to a file using an atomic two-step process.
+ * If the script is killed before the write is complete, only the temporary
+ * trash file will be corrupted.
+ *
+ * The algorithm also ensures that 100% of the bytes were written to disk.
+ *
+ * @param string $filename Filename to write the data to.
+ * @param string $data Data to write to file.
+ * @param string $atomicSuffix Lets you optionally provide a different
+ * suffix for the temporary file.
+ *
+ * @return int|bool Number of bytes written on success, otherwise `FALSE`.
+ */
+ public static function atomicWrite(
+ $filename,
+ $data,
+ $atomicSuffix = 'atomictmp')
+ {
+ // Perform an exclusive (locked) overwrite to a temporary file.
+ $filenameTmp = sprintf('%s.%s', $filename, $atomicSuffix);
+ $writeResult = @file_put_contents($filenameTmp, $data, LOCK_EX);
+
+ // Only proceed if we wrote 100% of the data bytes to disk.
+ if ($writeResult !== false && $writeResult === strlen($data)) {
+ // Now move the file to its real destination (replaces if exists).
+ $moveResult = @rename($filenameTmp, $filename);
+ if ($moveResult === true) {
+ // Successful write and move. Return number of bytes written.
+ return $writeResult;
+ }
+ }
+
+ // We've failed. Remove the temporary file if it exists.
+ if (is_file($filenameTmp)) {
+ @unlink($filenameTmp);
+ }
+
+ return false; // Failed.
+ }
+}
diff --git a/vendor/lazyjsonmapper/lazyjsonmapper/tests/bootstrap.php b/vendor/lazyjsonmapper/lazyjsonmapper/tests/bootstrap.php
new file mode 100755
index 0000000..8676102
--- /dev/null
+++ b/vendor/lazyjsonmapper/lazyjsonmapper/tests/bootstrap.php
@@ -0,0 +1,18 @@
+login('yourusername', 'yourpassword');
+ $result = $ig->media->comment('14123451234567890_1234567890', 'Hello World');
+ var_dump($result);
+} catch (\Exception $e) {
+ echo $e->getMessage()."\n";
+}
+```
+
+Error Log/var_dump:
+
+```php
+// Please provide your error log/dump here, for example:
+
+RESPONSE: {"status": "fail", "message": "Sorry, the comment data may have been corrupted."}
+
+InstagramAPI\Response\CommentResponse: Sorry, the comment data may have been corrupted.
+```
+
+---
+
+### For a new endpoint *feature request*, you should include the *capture of the request and response*.
+
+Request:
+
+```http
+# Please provide your capture below, for example:
+
+GET /api/v1/si/fetch_headers/?guid=123456abcdeff19cc2f123456&challenge_type=signup HTTP/1.1
+Host: i.instagram.com
+Connection: keep-alive
+X-IG-Connection-Type: mobile(UMTS)
+X-IG-Capabilities: 3ToAAA==
+Accept-Language: en-US
+Cookie: csrftoken=g79dofABCDEFGII3LI7YdHei1234567; mid=WFI52QABAAGrbKL-ABCDEFGHIJK
+User-Agent: Instagram 10.3.0 Android (18/4.3; 320dpi; 720x1280; Xiaomi; HM 1SW; armani; qcom; en_US)
+Accept-Encoding: gzip, deflate, sdch
+```
+
+Response:
+
+```http
+# Please provide your capture below, for example:
+
+HTTP/1.1 200 OK
+Content-Language: en
+Expires: Sat, 01 Jan 2000 00:00:00 GMT
+Vary: Cookie, Accept-Language
+Pragma: no-cache
+Cache-Control: private, no-cache, no-store, must-revalidate
+Date: Thu, 15 Dec 2016 08:50:19 GMT
+Content-Type: application/json
+Set-Cookie: csrftoken=g79dofABCDEFGII3LI7YdHei1234567; expires=Thu, 14-Dec-2017 08:50:19 GMT; Max-Age=31449600; Path=/; secure
+Connection: keep-alive
+Content-Length: 16
+
+{"status": "ok"}
+```
+---
+
+### Describe your issue
+
+Explanation of your issue goes here.
+
+Please make sure the description is worded well enough to be understood, and with as much context and examples as possible.
+
+We reserve the right to close your ticket without answer if you can't bother spending a few minutes to write a helpful report for us.
diff --git a/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/bug-report.md b/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100755
index 0000000..6a3bf88
--- /dev/null
+++ b/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,42 @@
+---
+name: Bug Report
+about: Report a bug or an unexpected behavior with the API
+labels: bug
+---
+# Bug Report
+
+---
+
+### Notes:
+We will close your issue, sometimes without answering, if any of the following are met:
+* You have not included your code or your debug log
+* You are asking about `challenge_required`, `checkpoint_required`, `feedback_required` or `sentry_block`. They've already been answered in the Wiki and *countless* closed tickets in the past!
+* You have used the wrong issue template
+* You are posting screenshots, which are too painful to help you with
+
+Please make sure you have done the following before submitting your issue:
+* You have [Searched](https://github.com/mgp25/Instagram-API/search?type=Issues) the bug-tracker for similar issues including **closed** ones
+* You have [Read the FAQ](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+* You have [Read the wiki](https://github.com/mgp25/Instagram-API/wiki)
+* You have [Reviewed the examples](https://github.com/mgp25/Instagram-API/tree/master/examples)
+* You have [Installed the api using ``composer``](https://github.com/mgp25/Instagram-API#installation)
+* You are [Using latest API release](https://github.com/mgp25/Instagram-API/releases)
+
+---
+
+### Description
+Please include a well-worded description of your issue below; We will close your issue if we have to guess what you're talking about, please be as specific as possible:
+
+__INSERT YOUR DESCRIPTION HERE__
+
+### Code
+Please post your code relevant to the bug in the section below:
+```php
+INSERT YOUR CODE HERE
+```
+
+### Debug Log
+Please post your debug log in the section below; You can enable the debug log by using `new Instagram(true)` instead of `new Instagram()`:
+```php
+INSERT YOUR DEBUG LOG HERE
+```
\ No newline at end of file
diff --git a/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/feature-request.md b/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100755
index 0000000..07c3ecc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/feature-request.md
@@ -0,0 +1,26 @@
+---
+name: Feature Request
+about: Suggest a new feature/endpoint or an update to a function
+labels: request
+---
+# Feature Request
+
+---
+
+### Notes:
+We will close your feature request, sometimes without answering, if any of the following are met:
+* You requested a endpoint/function for `challenge_required`, `checkpoint_required`, `feedback_required` or `sentry_block` errors. These are decidedly unsupported, and will not be added
+* You requested a endpoint/function for an iOS feature. This API emulates the Android API, therefore we cannot, *safely*, add endpoints/functions which are not in the Android app.
+* You are using the wrong issue template
+
+Please make sure you have done the following before submitting your feature request:
+* You have [Searched](https://github.com/mgp25/Instagram-API/search?type=Issues) the bug-tracker for the same feature request
+* You have [Read the FAQ](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+* You have [Read the wiki](https://github.com/mgp25/Instagram-API/wiki)
+
+---
+
+### Description
+Please include a well-worded description of your feature request. Please make sure to include where on the app this feature is acceptable so we can reproduce it. Additionally, it would make our lives easier if you are able to post the endpoint, its associated parameters, and response.
+
+__INSERT DESCRIPTION HERE__
\ No newline at end of file
diff --git a/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/question.md b/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/question.md
new file mode 100755
index 0000000..ecddf74
--- /dev/null
+++ b/vendor/mgp25/instagram-php/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,42 @@
+---
+name: Question
+about: Ask a question about the API
+labels: question
+---
+# Question
+
+---
+
+### Notes:
+We will close your question, sometimes without answering, if any of the following are met:
+* You have not included context about your question
+* You are asking about `challenge_required`, `checkpoint_required`, `feedback_required` or `sentry_block`. They've already been answered in the Wiki and *countless* closed tickets in the past!
+* You have used the wrong issue template
+* You are posting screenshots, which are too painful to help you with
+
+Please make sure you have done the following before submitting your question:
+* You have [Searched](https://github.com/mgp25/Instagram-API/search?type=Issues) the bug-tracker for similar questions including **closed** ones
+* You have [Read the FAQ](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+* You have [Read the wiki](https://github.com/mgp25/Instagram-API/wiki)
+* You have [Reviewed the examples](https://github.com/mgp25/Instagram-API/tree/master/examples)
+* You have [Installed the api using ``composer``](https://github.com/mgp25/Instagram-API#installation)
+* You are [Using latest API release](https://github.com/mgp25/Instagram-API/releases)
+
+---
+
+### Description
+Please include a well-worded description of your question below; We will close your issue if we have to guess what you're talking about, please be as specific as possible:
+
+__INSERT YOUR DESCRIPTION HERE__
+
+### Code (Optional)
+Please post your code relevant to the question in the section below:
+```php
+INSERT YOUR CODE HERE
+```
+
+### Debug Log (Optional)
+Please post your debug log in the section below; You can enable the debug log by using `new Instagram(true)` instead of `new Instagram()`:
+```php
+INSERT YOUR DEBUG LOG HERE
+```
\ No newline at end of file
diff --git a/vendor/mgp25/instagram-php/.gitignore b/vendor/mgp25/instagram-php/.gitignore
new file mode 100755
index 0000000..39fc46d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/.gitignore
@@ -0,0 +1,49 @@
+# miscellaneous
+sigs/sigs
+sigs/sigKeys
+
+# ignore the lockfile for this library
+composer.lock
+
+# runtime files
+*.dat
+*.jpg
+*.lock
+backups/
+sessions/
+
+# third party libraries
+vendor/
+.php_cs
+.php_cs.cache
+devtools/exif-orientation-examples/
+
+# temporary editor files
+*.swp
+.\#*
+*.sublime-project
+*.sublime-workspace
+.idea
+
+# operating system cache files
+.DS_Store
+Desktop.ini
+Thumbs.db
+
+# tags created by etags, ctags, gtags (GNU global), cscope and ac-php
+TAGS
+.TAGS
+!TAGS/
+tags
+.tags
+!tags/
+gtags.files
+GTAGS
+GRTAGS
+GPATH
+GSYMS
+cscope.files
+cscope.out
+cscope.in.out
+cscope.po.out
+ac-php-tags/
diff --git a/vendor/mgp25/instagram-php/.php_cs.dist b/vendor/mgp25/instagram-php/.php_cs.dist
new file mode 100755
index 0000000..c01fac1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/.php_cs.dist
@@ -0,0 +1,38 @@
+setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__)
+ )
+ ->setIndent(' ')
+ ->setLineEnding("\n")
+ ->setRules([
+ '@Symfony' => true,
+ // Override @Symfony rules
+ 'pre_increment' => false,
+ 'blank_line_before_statement' => ['statements' => ['return', 'try', 'throw']],
+ 'phpdoc_align' => ['tags' => ['param', 'throws']],
+ 'method_argument_space' => ['ensure_fully_multiline' => false],
+ 'binary_operator_spaces' => [
+ 'align_double_arrow' => true,
+ 'align_equals' => false,
+ ],
+ 'phpdoc_annotation_without_dot' => false,
+ 'yoda_style' => [
+ // Symfony writes their conditions backwards; we use normal order.
+ 'equal' => false,
+ 'identical' => false,
+ 'less_and_greater' => false,
+ ],
+ 'is_null' => [
+ // Replaces all is_null() with === null.
+ 'use_yoda_style' => false,
+ ],
+ // Custom rules
+ 'align_multiline_comment' => true,
+ 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
+ 'ordered_imports' => ['sort_algorithm' => 'alpha'],
+ 'phpdoc_order' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ]);
diff --git a/vendor/mgp25/instagram-php/.pre-commit.hook b/vendor/mgp25/instagram-php/.pre-commit.hook
new file mode 100755
index 0000000..d091646
--- /dev/null
+++ b/vendor/mgp25/instagram-php/.pre-commit.hook
@@ -0,0 +1,58 @@
+#!/bin/sh
+#
+# Verifies that all files in the worktree follow our codestyle standards.
+#
+# Note that this script can't check that they're actually committing the nicely
+# formatted code. It just checks that the worktree is clean. So if they've fixed
+# all files but haven't added the codestyle fixes to their commit, they'll still
+# pass this check. But it's still a great protection against most mistakes.
+#
+# To install this hook, just run the following in the project's root folder:
+# ln -fs "../../.pre-commit.hook" .git/hooks/pre-commit
+#
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Git ensures that CWD is always the root of the project folder, so we can just
+# run all tests without verifying what folder we are in...
+
+failed="no"
+echo "[pre-commit] Checking work-tree codestyle..."
+
+# Check if we need updated LazyJsonMapper class documentation.
+echo "> Verifying class documentation..."
+./vendor/bin/lazydoctor -c composer.json -pfo --validate-only >/dev/null
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Check the general codestyle format.
+echo "> Verifying php-cs-fixer..."
+./vendor/bin/php-cs-fixer fix --config=.php_cs.dist --allow-risky yes --dry-run
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Look for specific problems with the style, related to our project.
+echo "> Verifying checkStyle..."
+/usr/bin/env php devtools/checkStyle.php x
+if [ $? -ne 0 ]; then
+ failed="yes"
+fi
+
+# Refuse to commit if there were problems. Instruct the user about solving it.
+if [ "${failed}" = "yes" ]; then
+ # Yes there are lots of "echo" commands, because "\n" is not cross-platform.
+ echo "[commit failed] There are problems with your code..."
+ echo ""
+ echo "Run 'composer codestyle' to fix the code in your worktree."
+ echo ""
+ echo "But beware that the process is automatic, and that the result"
+ echo "isn't always perfect and won't be automatically staged."
+ echo ""
+ echo "So remember to manually read through the changes, then further"
+ echo "fix them if necessary, and finally stage the updated code"
+ echo "afterwards so that the fixed code gets committed."
+ exit 1
+fi
diff --git a/vendor/mgp25/instagram-php/CODE_OF_CONDUCT.md b/vendor/mgp25/instagram-php/CODE_OF_CONDUCT.md
new file mode 100755
index 0000000..4754ab5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/vendor/mgp25/instagram-php/CONTRIBUTING.md b/vendor/mgp25/instagram-php/CONTRIBUTING.md
new file mode 100755
index 0000000..4173a1d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/CONTRIBUTING.md
@@ -0,0 +1,488 @@
+# Contributing to Instagram API
+
+:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
+
+The following is a set of guidelines for contributing to the Instagram API, which is hosted in the [Instagram API repository](https://github.com/mgp25/Instagram-API) on GitHub.
+
+These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
+
+- [What should I know before I get started?](#what-should-i-know-before-i-get-started)
+ * [Code of Conduct](#code-of-conduct)
+
+- [Basic rules](#basic-rules)
+ * [Git Setup](#git-setup)
+ * [Codestyle Command](#codestyle-command)
+ * [Available Tasks](#available-tasks)
+ * [Commits](#commits)
+ * [Modifying anything in the existing code](#modifying-anything-in-the-existing-code)
+
+- [Styleguides](#styleguide)
+ * [Namespaces](#namespaces)
+ * [Functions and Variables](#functions-and-variables)
+ * [Function Documentation](#function-documentation)
+ * [Exceptions](#exceptions)
+
+- [Contributing-new-endpoints](#contributing-new-endpoints)
+
+
+## What should I know before I get started?
+
+### Code of Conduct
+
+This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
+By participating, you are expected to uphold this code.
+Please report any unacceptable behavior.
+
+## Basic rules
+
+Important! Your contributions must follow all of these rules for a consistent and bug-free project.
+
+This is a document which, among other things, describes PSR-4 class autoloading, clean commits, how to handle/document exceptions, how to structure function arguments, naming clear variables, always adding/updating PHPdoc blocks in all affected functions, and how to verify that your changes don't _break everything_ when performing big changes to existing functions or adding brand new functions and classes.
+
+
+### Git Setup
+
+If you are using a Linux/Mac/Unix system, you **MUST** install our git-hook in your local repository. It will help prevent you from accidentally committing badly formatted code. Please run the following command in the project's root folder (the folder which has files like `README.md` etc), to install the hook in your repository:
+
+```
+ln -fs "../../.pre-commit.hook" .git/hooks/pre-commit
+```
+
+
+### Codestyle Command
+
+Before committing any work, you **MUST** always run the codestyle fixer command in the root folder of the repository:
+
+```
+composer codestyle
+```
+
+That command will automatically fix all codestyle issues as well as generate various documentation, such as the class docs for all of the Responses and Model items.
+
+
+### Available Tasks
+
+Please visit our [Project Task Tracker](https://github.com/mgp25/Instagram-API/projects), where contributors can look at the list of available work. High-quality code contributions are greatly appreciated.
+
+
+### Commits
+
+- You **MUST** try to **separate** work into smaller commits. So if you `"changed Utils.php to fix getSeconds and changed Request.php to fix Request()"`, then make **TWO SEPARATE COMMITS**, so that we can easily find your changes in the project history, and can easily revert any individual broken changes without losing tons of other code too! So remember: 1 commit per task. :smile:
+
+- Use the detailed description field of your commit, to document why you did certain changes if it's not an obvious change (such as a typo fix). Your description gets added to the history of the project so that we can know what you were thinking, if we need to check the change again later.
+
+- **Name** your single-file commits `"AffectedClassName: Change description"` and keep the total length of the summary line at 50 characters or less in total (the Git/GitHub standard).
+
+- If your change NEEDS to affect multiple files, then it is SOMETIMES okay to leave out the classname part from the summary, but in that case you _must_ write a very clear and descriptive summary to explain it. _But_ usually you _should_ still include the classname even for multi-file edits. For example, renaming a function in Utils and having to change the name of it in other files is still a `"Utils: Renamed bleh() to bloop()"` commit.
+
+Examples of BAD commit summaries:
+
+```
+Edit something
+Fix this
+Utils+request changes
+```
+
+Examples of GOOD commit summaries:
+
+```
+Utils: Fix formatting of getSeconds
+Request: Send cropping information
+Changed all number_format() to round() everywhere
+Response: Parse timestamps as floats
+```
+
+
+### Modifying anything in the existing code
+
+- If you want to change a public API function's parameters (particularly in `src/Instagram.php`), think EXTREMELY hard about NOT doing it. And if you absolutely MUST do it (such as adding an important new parameter), then you MUST add it in a BACKWARDS-COMPATIBLE WAY, by adding it as the LAST parameter of the argument list AND providing a sensible DEFAULT VALUE for it so that people's CURRENT projects based on this library continue working and DON'T BREAK due to your function definition changes!
+
+- Do NOT look at your changes in isolation. Look at the BIG PICTURE of what your change now does to the REST of the codebase.
+
+- For example, if you change the returned variable type from a function (such as from an `"int"` to a `"string"`), then you MUST update the function's PHPdoc block to document the new return value (with `@return string`), and you MUST also slowly and carefully check ALL OTHER CODE that relied on the OLD behavior of that function. There's a command-line tool called "grep" (or an even better one especially made for programmers, called ["ag" aka "the silver searcher"](https://github.com/ggreer/the_silver_searcher)) to search for text in files. USE IT to check ALL other code locations that called your modified function, and make sure that you didn't break ANY of them AT ALL!
+
+- In fact, ANY TIME that you change a function's ARGUMENTS, RETURN VALUE or THROWN EXCEPTIONS (new/deleted ones), then you MUST update the function's PHPdoc block to match the new truth. And you MUST check ALL other functions that CALL your function, and update those too. For example let's say they DON'T handle a new exception you're now throwing, in which case you MUST now update THEIR `@throws` documentation to document the fact that THEY now let yet another exception bubble up. And then you MUST search for the functions that called THOSE updated functions and update THEIR documentation TOO, all the way until you've reached the top and have FULLY documented what exceptions are being thrown in the whole chain of function calls.
+
+- You MUST ALWAYS update the PHPdoc blocks EVERYWHERE that's affected by your changes (whenever you've changed a functions ARGUMENTS or RETURN type or what it THROWS (any new/deleted exceptions)), because imagine if we NEVER update the blocks EVERYWHERE that's affected by your change. We would then have something like `/** @param $a, $b, $c */ function foo ($x)` and sudden, critical exceptions that aren't getting handled and thus KILL the program.
+
+Here's a checklist for what you MUST do to ENSURE totally correct documentation EVERY TIME that you make a CHANGE to a function's ARGUMENTS or RETURN TYPE or EXCEPTIONS of an existing function, OR when you introduce a NEW function whose use is being added to any existing functions.
+
+1. You MUST ALWAYS use grep/ag. Find EVERY other code location that uses your new/modified function.
+
+2. Check: Did your changes just BREAK EVERYTHING somewhere else, which now has to be updated to match? Almost GUARANTEED the answer is YES, and you MUST update the other locations that expected the old function behavior/return type/parameters/exceptions.
+
+3. Check: Do you need to also UPDATE the PHPdoc for those OTHER functions too? OFTEN the answer is YES. For example, if you've changed the exceptions that a deep, internal function throws, then you MUST either catch it in all higher functions and do something with it, OR let it pass through them upwards. And if you do let it pass through and bubble up then you MUST ALSO update the PHPdoc for THAT higher function to say `"@throws TheNewException"` (or delete something in case you changed a subfunction to no longer throw).
+
+4. If your updates to the affected higher-level functions in steps 2/3 means that THOSE other functions now ALSO behave differently (meaning THEY have new return values/exceptions/parameters), then you MUST also do ANOTHER grep/ag to check for anything that uses THOSE functions, and update all of those code locations TOO. And continue that way up the entire chain of functions that call each other, until you reach the top of the project, so that the entire chain of function calls is caught/documented properly. Otherwise, those unexpected (undocumented) exceptions will terminate PHP, so this is very important!
+
+
+
+## Styleguides
+
+### Namespaces
+
+- Organize all classes into logical namespaces.
+
+- We follow the PSR-4 Autoloading standard, which means that you MUST only have ONE class per source code `.php` file. And the namespace AND classname MUST match BOTH its disk path AND its `.php` filename. In our project, the `src/` folder is the `InstagramAPI` namespace, and everything under that is for its subnamespaces (folders) and our classes (PHP files).
+
+Example of a proper class in our top-level namespace:
+
+```php
+src/Something.php:
+_protectedProperty;
+ }
+
+ public function setProtectedProperty(
+ $protectedProperty)
+ {
+ $this->_protectedProperty = $protectedProperty;
+ }
+
+ public function getPublicProperty()
+ {
+ return $this->_publicProperty;
+ }
+
+ public function setPublicProperty(
+ $publicProperty)
+ {
+ $this->publicProperty = $publicProperty;
+ }
+
+ protected function _somethingInternal()
+ {
+ ...
+ }
+
+ ...
+}
+```
+
+- All functions and variables MUST have descriptive names that document their purpose automatically. Use names like `$videoFilename` and `$deviceInfo` and so on, so that the code documents itself instead of needing tons of comments to explain what each step is doing.
+
+Examples of BAD variable names:
+
+```php
+$x = $po + $py;
+$w = floor($h * $ar);
+```
+
+Examples of GOOD variable names:
+
+```php
+$endpoint = $this->url.'?'.http_build_query($this->params);
+$this->_aspectRatio = (float) ($this->_width / $this->_height);
+$width = floor($this->_height * $this->_maxAspectRatio);
+```
+
+- All functions MUST have occasional comments that explain what they're doing in their various substeps. Look at our codebase and follow our commenting style.
+
+- Our comments start with a capital letter and end in punctuation, and describe the purpose in as few words as possible, such as: `// Default request options (immutable after client creation).`
+
+- All functions MUST do as little work as possible, so that they are easy to maintain and bugfix.
+
+Example of a GOOD function layout:
+
+```php
+function requestVideoURL(...);
+
+function uploadVideoChunks(...);
+
+function configureVideo(...);
+
+function uploadVideo(...)
+{
+ $url = $this->requestVideoURL();
+ if (...handle any errors from the previous function)
+
+ $uploadResult = $this->uploadVideoChunks($url, ...);
+
+ $this->configureVideo($uploadResult, ...);
+}
+```
+
+Example of a BAD function layout:
+
+```php
+function uploadVideo(...)
+{
+ // Request upload URL.
+ // Upload video data to URL.
+ // Configure its location property.
+ // Post it to a timeline.
+ // Call your grandmother.
+ // Make some tea.
+ // ...and 500 other lines of code.
+}
+```
+
+- All function parameter lists MUST be well-thought out so that they list the most important arguments FIRST and so that they are as SIMPLE as possible to EXTEND in the FUTURE, since Instagram's API changes occasionally.
+
+Avoid this kind of function template:
+
+```php
+function uploadVideo($videoFilename, $filter, $url, $caption, $usertags, $hashtags);
+```
+
+Make such multi-argument functions take future-extensible option-arrays instead, especially if you expect that more properties may be added in the future.
+
+So the above would instead be PROPERLY designed as follows:
+
+```php
+function uploadVideo($videoFilename, array $metadata);
+```
+
+Now users can just say `uploadVideo($videoFilename, ['hashtags'=>$hashtags]);`, and we can easily add more metadata fields in the future without ever breaking backwards-compatibility with projects that are using our function!
+
+### Function Documentation
+
+- All functions MUST have _COMPLETE_ PHPdoc doc-blocks. The critically important information is the single-sentence `summary-line` (ALWAYS), then the `detailed description` (if necessary), then the `@param` descriptions (if any), then the `@throws` (one for EVERY type of exception that it throws, even uncaught ones thrown from DEEPER functions called within this function), then the `@return` (if the function returns something), and lastly one or more `@see` if there's any need for a documentation reference to a URL or another function or class.
+
+Example of a properly documented function:
+
+```php
+ /**
+ * Generates a User Agent string from a Device (<< that is the REQUIRED ONE-SENTENCE summary-line).
+ *
+ * [All lines after that are the optional description. This function didn't need any,
+ * but you CAN use this area to provide extra information describing things worth knowing.]
+ *
+ * @param \InstagramAPI\Devices\Device $device The Android device.
+ * @param string[]|null $names (optional) Array of name-strings.
+ *
+ * @throws \InvalidArgumentException If the device parameter is invalid.
+ * @throws \InstagramAPI\Exception\InstagramException In case of invalid or failed API response.
+ *
+ * @return string
+ *
+ * @see otherFunction()
+ * @see http://some-url...
+ */
+ public static function buildUserAgent(
+ Device $device,
+ $names = null)
+ {
+ ...
+ }
+```
+
+- You MUST take EXTREMELY GOOD CARE to ALWAYS _perfectly_ document ALL parameters, the EXACT return-type, and ALL thrown exceptions. All other project developers RELY on the function-documentation ALWAYS being CORRECT! With incorrect documentation, other developers would make incorrect assumptions and _severe_ bugs would be introduced!
+
+### Exceptions
+
+- ALL thrown exceptions that can happen inside a function or in ANY of its SUB-FUNCTION calls MUST be documented as `@throws`, so that we get a COMPLETE OVERVIEW of ALL exceptions that may be thrown when we call the function. YES, that EVEN means exceptions that come from deeper function calls, whose exceptions are NOT being caught by your function and which will therefore bubble up if they're thrown by those deeper sub-functions!
+- Always remember that Exceptions WILL CRITICALLY BREAK ALL OTHER CODE AND STOP PHP'S EXECUTION if not handled or documented properly! They are a LOT of responsibility! So you MUST put a LOT OF TIME AND EFFORT into PROPERLY handling (_catching and doing something_) for ALL exceptions that your function should handle, AND adding PHPdoc _documentation_ about the ones that your function DOESN'T catch/handle internally and which WILL therefore bubble upwards and would possibly BREAK other code (which is EXACTLY what would happen if an exception ISN'T documented by you and someone then uses your bad function and doesn't "catch" your exception since YOU didn't tell them that it can be thrown)!
+- All of our internal exceptions derive from `\InstagramAPI\Exception\InstagramException`, so it's always safe to declare that one as a `@throws \InstagramAPI\Exception\InstagramException` if you're calling anything that throws exceptions based on our internal `src/Exception/*.php` system. But it's even better if you can pinpoint which exact exceptions are thrown, by looking at the functions you're calling and seeing their `@throws` documentation, WHICH OF COURSE DEPENDS ON PEOPLE HAVING WRITTEN PROPER `@throws` FOR THOSE OTHER FUNCTIONS SO THAT _YOU_ KNOW WHAT THE FUNCTIONS YOU'RE CALLING WILL THROW. DO YOU SEE _NOW_ HOW IMPORTANT IT IS TO DECLARE EXCEPTIONS PROPERLY AND TO _ALWAYS_ KEEP THAT LIST UP TO DATE
+- Whenever you are using an EXTERNAL LIBRARY that throws its own custom exceptions (meaning NOT one of the standard PHP ones such as `\Exception` or `\InvalidArgumentException`, etc), then you MUST ALWAYS re-wrap the exception into some appropriate exception from our own library instead, otherwise users will not be able to say `catch (\InstagramAPI\Exception\InstagramException $e)`, since the 3rd party exceptions wouldn't be derived from our base exception and wouldn't be caught, thus breaking the user's program. To solve that, look at the design of our `src/Exception/NetworkException.php`, which we use in `src/Client.php` to re-wrap all Guzzle exceptions into our own exception type instead. Read the source-code of our NetworkException and it will explain how to properly re-wrap 3rd party exceptions and how to ensure that your re-wrapped exception will give users helpful messages and helpful stack traces.
+
+
+# Contributing new endpoints
+
+In order to add endpoints to the API you will need to capture the requests first. For that, you can use any HTTPS proxy you want. You can find a lot of information about this on the internet. Remember that you need to install a root CA (Certificate Authority) in your device so that the proxy can decrypt the requests and show them to you.
+
+Also be aware that you cannot capture Instagram for iPhone's requests. The iPhone API parameters are totally different and they are NOT compatible with this Android API library! You MUST use a real Android APK when capturing requests!
+
+Once you have the endpoint and necessary parameters, how do you add them to this library? Easy, you can follow this example:
+
+```php
+ public function getAwesome(
+ array $userList)
+ {
+ return $this->ig->request('awesome/endpoint/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('user_ids', implode(',', $userList))
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\AwesomeResponse());
+ }
+```
+
+In the example above you can see `('awesome/endpoint/')` which is the endpoint you captured. We are simulating a POST request, so you can add POST parameters easily by doing `->addPost('_uuid', $this->uuid)`.
+
+Which is basically:
+
+```php
+->addPost(key, value)
+```
+
+Where key is the name of the POST param, and value is whatever value the server requires for that parameter.
+
+Some of the requests are signed. This means there is a hash concatenated with the JSON. In order to make a signed request, we can enable or disable signing with the following line:
+
+```php
+->setSignedPost($isSigned)
+```
+
+`$isSigned` is boolean, so if you want a signed request, you simply set it to `true`.
+
+If the request is a GET request, you can add the GET query parameters like this (instead of using `addPost`):
+
+```php
+->addParam(key, value)
+```
+
+And finally, we always end with the `getResponse` function call, which will read the response and return an object with all of the server response values:
+
+```php
+->getResponse(new Response\AwesomeResponse());
+```
+
+Now you might be wondering how to create that response class? But there is nothing to worry about... it's very simple!
+
+Imagine that you have the following response:
+
+```json
+{"items": [{"user": {"is_verified": false, "has_anonymous_profile_picture": false, "is_private": false, "full_name": "awesome", "username": "awesome", "pk": "uid", "profile_pic_url": "profilepic"}, "large_urls": [], "caption": "", "thumbnail_urls": ["thumb1", "thumb2", "thumb3", "thumb4"]}], "status": "ok"}
+```
+
+You can use [http://jsoneditoronline.org](http://jsoneditoronline.org/) for a better visualization:
+
+
+
+(Alternatively, if you're an advanced user, you can create an empty response-class (with no defined properties yet), and then use its `->printJson()` function to look at its contents in a beautifully readable way.)
+
+So your new `src/Response/AwesomeResponse.php` class should contain one `JSON_PROPERTY_MAP` property named `items`. Our magical [LazyJsonMapper](https://github.com/lazyjsonmapper/lazyjsonmapper) object mapping system needs a PHPdoc-style type-definition to tell us if the property is another class, a float, an int, a string, a string array, etc. By default, if you don't specify any type (if you set its value to `''`), it will treat the JSON value as whatever type PHP detected it as internally during JSON decoding (such as a string, int, float, bool, etc).
+
+In this scenario, your `src/Response/AwesomeResponse.php` file and its property definitions should look as follows, since we want to map `items` to an array of `Suggestion` objects:
+
+```php
+ 'Model\Suggestion[]',
+ ];
+}
+```
+
+The `items` property will now expect an array of Suggestion model objects. And `src/Response/Model/Suggestion.php` should look like this:
+
+```php
+ '',
+ 'social_context' => '',
+ 'algorithm' => '',
+ 'thumbnail_urls' => 'string[]',
+ 'value' => '',
+ 'caption' => '',
+ 'user' => 'User',
+ 'large_urls' => 'string[]',
+ 'media_ids' => '',
+ 'icon' => '',
+ ];
+}
+```
+
+Here in this `Suggestion` class you can see many variables that didn't appear in our example endpoint's response, but that's because many other requests _re-use_ the same object, and depending the request, their response contents may differ a bit. Also note that unlike the AwesomeResponse class, the actual Model objects (the files in in `src/Response/Model/`) _don't_ use the "Model\" prefix when referring to other model objects, since they are in the same namespace already.
+
+Also note that many of the properties above are defined as `''` (no type), which means "mixed/untyped". It simply means that the value is not forcibly converted to anything. That's how you should define most properties unless you have a specific reason to force anything to a specific type.
+
+It is also _extremely important_ that any properties relating to Media IDs, PKs, User PKs, etc, _must_ be declared as a `string`, otherwise they may be handled as a float/int which won't fit on 32-bit CPUs and will truncate the number, leading to the wrong data. Just look at all other Model objects that are already in this project, and be sure that any ID/PK fields in your own new Model object are properly tagged as `string` type too!
+
+Now you can test your new endpoint, in order to look at its response object:
+
+```
+$awesome = $i->getAwesome(['123']);
+$awesome->printJson(); // Look at the object data.
+$awesome->printPropertyDescriptions(); // Look at all defined properties.
+```
+
+And finally, how do you access the object's data? Via the magical [LazyJsonMapper](https://github.com/lazyjsonmapper/lazyjsonmapper), which your Response and Model objects inherit from! It automatically creates getters and setters for all properties, and has a million other features!
+
+```php
+$items = $awesome->getItems();
+$user = $items[0]->getUser();
+
+// LazyJsonMapper even lets you look at the JSON of specific sub-objects:
+$user->printJson();
+```
+
+Lastly, you may sometimes be implementing an endpoint which uses a nearly empty response with just the standard `status` and/or `message` (and `_messages`) fields, and no other fields. In that case, there's already a pre-made response which you should use: `GenericResponse`. Search the source code for that word and you'll find many endpoints where we use that basic response. Use it to easily add new endpoint implementations whenever there's no extra data to extract!
+
+We hope that you found this tutorial useful! :smile:
diff --git a/vendor/mgp25/instagram-php/Dockerfile b/vendor/mgp25/instagram-php/Dockerfile
new file mode 100755
index 0000000..2b30c43
--- /dev/null
+++ b/vendor/mgp25/instagram-php/Dockerfile
@@ -0,0 +1,37 @@
+# The php:7.0-apache Docker image is based on debian:jessie.
+# See: https://github.com/docker-library/php/blob/20b89e64d16dc9310ba6493a38385e36304dded7/7.0/Dockerfile
+
+FROM php:7.1-apache-jessie
+RUN echo "deb http://deb.debian.org/debian jessie main" > /etc/apt/sources.list \
+ && echo "deb http://security.debian.org jessie/updates main" >> /etc/apt/sources.list \
+ && apt-get update \
+ && apt-get install -y \
+ libfreetype6-dev \
+ libjpeg62-turbo-dev \
+ libmcrypt-dev \
+ libpng12-dev \
+ git \
+ libav-tools \
+ unzip \
+ wget \
+ xz-utils \
+ && docker-php-ext-install -j$(nproc) iconv mcrypt \
+ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
+ && docker-php-ext-install -j$(nproc) gd \
+ && docker-php-ext-install -j$(nproc) bcmath \
+ && docker-php-ext-install -j$(nproc) exif
+
+RUN wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz \
+ && tar Jxvf ./ffmpeg-release-amd64-static.tar.xz \
+ && cp ./ffmpeg*amd64-static/ffmpeg /usr/local/bin/
+
+# Install Composer and make it available in the PATH
+RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer
+
+# Set the WORKDIR to /app so all following commands run in /app
+WORKDIR /var/www/html
+
+COPY . ./
+
+# Install dependencies with Composer.
+RUN composer install --prefer-source --no-interaction
diff --git a/vendor/mgp25/instagram-php/LICENSE b/vendor/mgp25/instagram-php/LICENSE
new file mode 100755
index 0000000..b1ba59a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/LICENSE
@@ -0,0 +1,546 @@
+Reciprocal Public License (RPL-1.5)
+
+Version 1.5, July 15, 2007
+
+Copyright (C) 2001-2007
+Technical Pursuit Inc.,
+All Rights Reserved.
+
+
+PREAMBLE
+
+The Reciprocal Public License (RPL) is based on the concept of reciprocity or,
+if you prefer, fairness.
+
+In short, this license grew out of a desire to close loopholes in previous open
+source licenses, loopholes that allowed parties to acquire open source software
+and derive financial benefit from it without having to release their
+improvements or derivatives to the community which enabled them. This occurred
+any time an entity did not release their application to a "third party".
+
+While there is a certain freedom in this model of licensing, it struck the
+authors of the RPL as being unfair to the open source community at large and to
+the original authors of the works in particular. After all, bug fixes,
+extensions, and meaningful and valuable derivatives were not consistently
+finding their way back into the community where they could fuel further, and
+faster, growth and expansion of the overall open source software base.
+
+While you should clearly read and understand the entire license, the essence of
+the RPL is found in two definitions: "Deploy" and "Required Components".
+
+Regarding deployment, under the RPL your changes, bug fixes, extensions, etc.
+must be made available to the open source community at large when you Deploy in
+any form -- either internally or to an outside party. Once you start running
+the software you have to start sharing the software.
+
+Further, under the RPL all components you author including schemas, scripts,
+source code, etc. -- regardless of whether they're compiled into a single
+binary or used as two halves of client/server application -- must be shared.
+You have to share the whole pie, not an isolated slice of it.
+
+In addition to these goals, the RPL was authored to meet the requirements of
+the Open Source Definition as maintained by the Open Source Initiative (OSI).
+
+The specific terms and conditions of the license are defined in the remainder
+of this document.
+
+
+LICENSE TERMS
+
+1.0 General; Applicability & Definitions. This Reciprocal Public License
+Version 1.5 ("License") applies to any programs or other works as well as any
+and all updates or maintenance releases of said programs or works ("Software")
+not already covered by this License which the Software copyright holder
+("Licensor") makes available containing a License Notice (hereinafter defined)
+from the Licensor specifying or allowing use or distribution under the terms of
+this License. As used in this License:
+
+1.1 "Contributor" means any person or entity who created or contributed to the
+creation of an Extension.
+
+1.2 "Deploy" means to use, Serve, sublicense or distribute Licensed Software
+other than for Your internal Research and/or Personal Use, and includes
+without limitation, any and all internal use or distribution of Licensed
+Software within Your business or organization other than for Research and/or
+Personal Use, as well as direct or indirect sublicensing or distribution of
+Licensed Software by You to any third party in any form or manner.
+
+1.3 "Derivative Works" as used in this License is defined under U.S. copyright
+law.
+
+1.4 "Electronic Distribution Mechanism" means a mechanism generally accepted
+in the software development community for the electronic transfer of data such
+as download from an FTP server or web site, where such mechanism is publicly
+accessible.
+
+1.5 "Extensions" means any Modifications, Derivative Works, or Required
+Components as those terms are defined in this License.
+
+1.6 "License" means this Reciprocal Public License.
+
+1.7 "License Notice" means any notice contained in EXHIBIT A.
+
+1.8 "Licensed Software" means any Software licensed pursuant to this License.
+Licensed Software also includes all previous Extensions from any Contributor
+that You receive.
+
+1.9 "Licensor" means the copyright holder of any Software previously not
+covered by this License who releases the Software under the terms of this
+License.
+
+1.10 "Modifications" means any additions to or deletions from the substance or
+structure of (i) a file or other storage containing Licensed Software, or (ii)
+any new file or storage that contains any part of Licensed Software, or (iii)
+any file or storage which replaces or otherwise alters the original
+functionality of Licensed Software at runtime.
+
+1.11 "Personal Use" means use of Licensed Software by an individual solely for
+his or her personal, private and non-commercial purposes. An individual's use
+of Licensed Software in his or her capacity as an officer, employee, member,
+independent contractor or agent of a corporation, business or organization
+(commercial or non-commercial) does not qualify as Personal Use.
+
+1.12 "Required Components" means any text, programs, scripts, schema,
+interface definitions, control files, or other works created by You which are
+required by a third party of average skill to successfully install and run
+Licensed Software containing Your Modifications, or to install and run Your
+Derivative Works.
+
+1.13 "Research" means investigation or experimentation for the purpose of
+understanding the nature and limits of the Licensed Software and its potential
+uses.
+
+1.14 "Serve" means to deliver Licensed Software and/or Your Extensions by
+means of a computer network to one or more computers for purposes of execution
+of Licensed Software and/or Your Extensions.
+
+1.15 "Software" means any computer programs or other works as well as any
+updates or maintenance releases of those programs or works which are
+distributed publicly by Licensor.
+
+1.16 "Source Code" means the preferred form for making modifications to the
+Licensed Software and/or Your Extensions, including all modules contained
+therein, plus any associated text, interface definition files, scripts used to
+control compilation and installation of an executable program or other
+components required by a third party of average skill to build a running
+version of the Licensed Software or Your Extensions.
+
+1.17 "User-Visible Attribution Notice" means any notice contained in EXHIBIT B.
+
+1.18 "You" or "Your" means an individual or a legal entity exercising rights
+under this License. For legal entities, "You" or "Your" includes any entity
+which controls, is controlled by, or is under common control with, You, where
+"control" means (a) the power, direct or indirect, to cause the direction or
+management of such entity, whether by contract or otherwise, or (b) ownership
+of fifty percent (50%) or more of the outstanding shares or beneficial
+ownership of such entity.
+
+2.0 Acceptance Of License. You are not required to accept this License since
+you have not signed it, however nothing else grants you permission to use,
+copy, distribute, modify, or create derivatives of either the Software or any
+Extensions created by a Contributor. These actions are prohibited by law if
+you do not accept this License. Therefore, by performing any of these actions
+You indicate Your acceptance of this License and Your agreement to be bound by
+all its terms and conditions. IF YOU DO NOT AGREE WITH ALL THE TERMS AND
+CONDITIONS OF THIS LICENSE DO NOT USE, MODIFY, CREATE DERIVATIVES, OR
+DISTRIBUTE THE SOFTWARE. IF IT IS IMPOSSIBLE FOR YOU TO COMPLY WITH ALL THE
+TERMS AND CONDITIONS OF THIS LICENSE THEN YOU CAN NOT USE, MODIFY, CREATE
+DERIVATIVES, OR DISTRIBUTE THE SOFTWARE.
+
+3.0 Grant of License From Licensor. Subject to the terms and conditions of
+this License, Licensor hereby grants You a world-wide, royalty-free, non-
+exclusive license, subject to Licensor's intellectual property rights, and any
+third party intellectual property claims derived from the Licensed Software
+under this License, to do the following:
+
+3.1 Use, reproduce, modify, display, perform, sublicense and distribute
+Licensed Software and Your Extensions in both Source Code form or as an
+executable program.
+
+3.2 Create Derivative Works (as that term is defined under U.S. copyright law)
+of Licensed Software by adding to or deleting from the substance or structure
+of said Licensed Software.
+
+3.3 Under claims of patents now or hereafter owned or controlled by Licensor,
+to make, use, have made, and/or otherwise dispose of Licensed Software or
+portions thereof, but solely to the extent that any such claim is necessary to
+enable You to make, use, have made, and/or otherwise dispose of Licensed
+Software or portions thereof.
+
+3.4 Licensor reserves the right to release new versions of the Software with
+different features, specifications, capabilities, functions, licensing terms,
+general availability or other characteristics. Title, ownership rights, and
+intellectual property rights in and to the Licensed Software shall remain in
+Licensor and/or its Contributors.
+
+4.0 Grant of License From Contributor. By application of the provisions in
+Section 6 below, each Contributor hereby grants You a world-wide, royalty-
+free, non-exclusive license, subject to said Contributor's intellectual
+property rights, and any third party intellectual property claims derived from
+the Licensed Software under this License, to do the following:
+
+4.1 Use, reproduce, modify, display, perform, sublicense and distribute any
+Extensions Deployed by such Contributor or portions thereof, in both Source
+Code form or as an executable program, either on an unmodified basis or as
+part of Derivative Works.
+
+4.2 Under claims of patents now or hereafter owned or controlled by
+Contributor, to make, use, have made, and/or otherwise dispose of Extensions
+or portions thereof, but solely to the extent that any such claim is necessary
+to enable You to make, use, have made, and/or otherwise dispose of
+Licensed Software or portions thereof.
+
+5.0 Exclusions From License Grant. Nothing in this License shall be deemed to
+grant any rights to trademarks, copyrights, patents, trade secrets or any
+other intellectual property of Licensor or any Contributor except as expressly
+stated herein. Except as expressly stated in Sections 3 and 4, no other patent
+rights, express or implied, are granted herein. Your Extensions may require
+additional patent licenses from Licensor or Contributors which each may grant
+in its sole discretion. No right is granted to the trademarks of Licensor or
+any Contributor even if such marks are included in the Licensed Software.
+Nothing in this License shall be interpreted to prohibit Licensor from
+licensing under different terms from this License any code that Licensor
+otherwise would have a right to license.
+
+5.1 You expressly acknowledge and agree that although Licensor and each
+Contributor grants the licenses to their respective portions of the Licensed
+Software set forth herein, no assurances are provided by Licensor or any
+Contributor that the Licensed Software does not infringe the patent or other
+intellectual property rights of any other entity. Licensor and each
+Contributor disclaim any liability to You for claims brought by any other
+entity based on infringement of intellectual property rights or otherwise. As
+a condition to exercising the rights and licenses granted hereunder, You
+hereby assume sole responsibility to secure any other intellectual property
+rights needed, if any. For example, if a third party patent license is
+required to allow You to distribute the Licensed Software, it is Your
+responsibility to acquire that license before distributing the Licensed
+Software.
+
+6.0 Your Obligations And Grants. In consideration of, and as an express
+condition to, the licenses granted to You under this License You hereby agree
+that any Modifications, Derivative Works, or Required Components (collectively
+Extensions) that You create or to which You contribute are governed by the
+terms of this License including, without limitation, Section 4. Any Extensions
+that You create or to which You contribute must be Deployed under the terms of
+this License or a future version of this License released under Section 7. You
+hereby grant to Licensor and all third parties a world-wide, non-exclusive,
+royalty-free license under those intellectual property rights You own or
+control to use, reproduce, display, perform, modify, create derivatives,
+sublicense, and distribute Licensed Software, in any form. Any Extensions You
+make and Deploy must have a distinct title so as to readily tell any
+subsequent user or Contributor that the Extensions are by You. You must
+include a copy of this License or directions on how to obtain a copy with
+every copy of the Extensions You distribute. You agree not to offer or impose
+any terms on any Source Code or executable version of the Licensed Software,
+or its Extensions that alter or restrict the applicable version of this
+License or the recipients' rights hereunder.
+
+6.1 Availability of Source Code. You must make available, under the terms of
+this License, the Source Code of any Extensions that You Deploy, via an
+Electronic Distribution Mechanism. The Source Code for any version that You
+Deploy must be made available within one (1) month of when you Deploy and must
+remain available for no less than twelve (12) months after the date You cease
+to Deploy. You are responsible for ensuring that the Source Code to each
+version You Deploy remains available even if the Electronic Distribution
+Mechanism is maintained by a third party. You may not charge a fee for any
+copy of the Source Code distributed under this Section in excess of Your
+actual cost of duplication and distribution of said copy.
+
+6.2 Description of Modifications. You must cause any Modifications that You
+create or to which You contribute to be documented in the Source Code, clearly
+describing the additions, changes or deletions You made. You must include a
+prominent statement that the Modifications are derived, directly or indirectly,
+from the Licensed Software and include the names of the Licensor and any
+Contributor to the Licensed Software in (i) the Source Code and (ii) in any
+notice displayed by the Licensed Software You distribute or in related
+documentation in which You describe the origin or ownership of the Licensed
+Software. You may not modify or delete any pre-existing copyright notices,
+change notices or License text in the Licensed Software without written
+permission of the respective Licensor or Contributor.
+
+6.3 Intellectual Property Matters.
+
+a. Third Party Claims. If You have knowledge that a license to a third party's
+intellectual property right is required to exercise the rights granted by this
+License, You must include a human-readable file with Your distribution that
+describes the claim and the party making the claim in sufficient detail that a
+recipient will know whom to contact.
+
+b. Contributor APIs. If Your Extensions include an application programming
+interface ("API") and You have knowledge of patent licenses that are
+reasonably necessary to implement that API, You must also include this
+information in a human-readable file supplied with Your distribution.
+
+c. Representations. You represent that, except as disclosed pursuant to 6.3(a)
+above, You believe that any Extensions You distribute are Your original
+creations and that You have sufficient rights to grant the rights conveyed by
+this License.
+
+6.4 Required Notices.
+
+a. License Text. You must duplicate this License or instructions on how to
+acquire a copy in any documentation You provide along with the Source Code of
+any Extensions You create or to which You contribute, wherever You describe
+recipients' rights relating to Licensed Software.
+
+b. License Notice. You must duplicate any notice contained in EXHIBIT A (the
+"License Notice") in each file of the Source Code of any copy You distribute
+of the Licensed Software and Your Extensions. If You create an Extension, You
+may add Your name as a Contributor to the Source Code and accompanying
+documentation along with a description of the contribution. If it is not
+possible to put the License Notice in a particular Source Code file due to its
+structure, then You must include such License Notice in a location where a
+user would be likely to look for such a notice.
+
+c. Source Code Availability. You must notify the software community of the
+availability of Source Code to Your Extensions within one (1) month of the date
+You initially Deploy and include in such notification a description of the
+Extensions, and instructions on how to acquire the Source Code. Should such
+instructions change you must notify the software community of revised
+instructions within one (1) month of the date of change. You must provide
+notification by posting to appropriate news groups, mailing lists, weblogs, or
+other sites where a publicly accessible search engine would reasonably be
+expected to index your post in relationship to queries regarding the Licensed
+Software and/or Your Extensions.
+
+d. User-Visible Attribution. You must duplicate any notice contained in
+EXHIBIT B (the "User-Visible Attribution Notice") in each user-visible display
+of the Licensed Software and Your Extensions which delineates copyright,
+ownership, or similar attribution information. If You create an Extension,
+You may add Your name as a Contributor, and add Your attribution notice, as an
+equally visible and functional element of any User-Visible Attribution Notice
+content. To ensure proper attribution, You must also include such User-Visible
+Attribution Notice in at least one location in the Software documentation
+where a user would be likely to look for such notice.
+
+6.5 Additional Terms. You may choose to offer, and charge a fee for, warranty,
+support, indemnity or liability obligations to one or more recipients of
+Licensed Software. However, You may do so only on Your own behalf, and not on
+behalf of the Licensor or any Contributor except as permitted under other
+agreements between you and Licensor or Contributor. You must make it clear that
+any such warranty, support, indemnity or liability obligation is offered by You
+alone, and You hereby agree to indemnify the Licensor and every Contributor for
+any liability plus attorney fees, costs, and related expenses due to any such
+action or claim incurred by the Licensor or such Contributor as a result of
+warranty, support, indemnity or liability terms You offer.
+
+6.6 Conflicts With Other Licenses. Where any portion of Your Extensions, by
+virtue of being Derivative Works of another product or similar circumstance,
+fall under the terms of another license, the terms of that license should be
+honored however You must also make Your Extensions available under this
+License. If the terms of this License continue to conflict with the terms of
+the other license you may write the Licensor for permission to resolve the
+conflict in a fashion that remains consistent with the intent of this License.
+Such permission will be granted at the sole discretion of the Licensor.
+
+7.0 Versions of This License. Licensor may publish from time to time revised
+versions of the License. Once Licensed Software has been published under a
+particular version of the License, You may always continue to use it under the
+terms of that version. You may also choose to use such Licensed Software under
+the terms of any subsequent version of the License published by Licensor. No
+one other than Licensor has the right to modify the terms applicable to
+Licensed Software created under this License.
+
+7.1 If You create or use a modified version of this License, which You may do
+only in order to apply it to software that is not already Licensed Software
+under this License, You must rename Your license so that it is not confusingly
+similar to this License, and must make it clear that Your license contains
+terms that differ from this License. In so naming Your license, You may not
+use any trademark of Licensor or of any Contributor. Should Your modifications
+to this License be limited to alteration of a) Section 13.8 solely to modify
+the legal Jurisdiction or Venue for disputes, b) EXHIBIT A solely to define
+License Notice text, or c) to EXHIBIT B solely to define a User-Visible
+Attribution Notice, You may continue to refer to Your License as the
+Reciprocal Public License or simply the RPL.
+
+8.0 Disclaimer of Warranty. LICENSED SOFTWARE IS PROVIDED UNDER THIS LICENSE
+ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
+INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE LICENSED SOFTWARE IS FREE
+OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+FURTHER THERE IS NO WARRANTY MADE AND ALL IMPLIED WARRANTIES ARE DISCLAIMED
+THAT THE LICENSED SOFTWARE MEETS OR COMPLIES WITH ANY DESCRIPTION OF
+PERFORMANCE OR OPERATION, SAID COMPATIBILITY AND SUITABILITY BEING YOUR
+RESPONSIBILITY. LICENSOR DISCLAIMS ANY WARRANTY, IMPLIED OR EXPRESSED, THAT
+ANY CONTRIBUTOR'S EXTENSIONS MEET ANY STANDARD OF COMPATIBILITY OR DESCRIPTION
+OF PERFORMANCE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LICENSED SOFTWARE IS WITH YOU. SHOULD LICENSED SOFTWARE PROVE DEFECTIVE IN ANY
+RESPECT, YOU (AND NOT THE LICENSOR OR ANY OTHER CONTRIBUTOR) ASSUME THE COST
+OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. UNDER THE TERMS OF THIS
+LICENSOR WILL NOT SUPPORT THIS SOFTWARE AND IS UNDER NO OBLIGATION TO ISSUE
+UPDATES TO THIS SOFTWARE. LICENSOR HAS NO KNOWLEDGE OF ERRANT CODE OR VIRUS IN
+THIS SOFTWARE, BUT DOES NOT WARRANT THAT THE SOFTWARE IS FREE FROM SUCH ERRORS
+OR VIRUSES. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS
+LICENSE. NO USE OF LICENSED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
+DISCLAIMER.
+
+9.0 Limitation of Liability. UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
+WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE
+LICENSOR, ANY CONTRIBUTOR, OR ANY DISTRIBUTOR OF LICENSED SOFTWARE, OR ANY
+SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT,
+SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING,
+WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER
+FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES,
+EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH
+DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH
+OR PERSONAL INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
+APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS
+EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10.0 High Risk Activities. THE LICENSED SOFTWARE IS NOT FAULT-TOLERANT AND IS
+NOT DESIGNED, MANUFACTURED, OR INTENDED FOR USE OR DISTRIBUTION AS ON-LINE
+CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE PERFORMANCE,
+SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT NAVIGATION OR
+COMMUNICATIONS SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE SUPPORT MACHINES, OR
+WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE LICENSED SOFTWARE COULD LEAD
+DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE
+("HIGH RISK ACTIVITIES"). LICENSOR AND CONTRIBUTORS SPECIFICALLY DISCLAIM ANY
+EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES.
+
+11.0 Responsibility for Claims. As between Licensor and Contributors, each
+party is responsible for claims and damages arising, directly or indirectly,
+out of its utilization of rights under this License which specifically
+disclaims warranties and limits any liability of the Licensor. This paragraph
+is to be used in conjunction with and controlled by the Disclaimer Of
+Warranties of Section 8, the Limitation Of Damages in Section 9, and the
+disclaimer against use for High Risk Activities in Section 10. The Licensor
+has thereby disclaimed all warranties and limited any damages that it is or
+may be liable for. You agree to work with Licensor and Contributors to
+distribute such responsibility on an equitable basis consistent with the terms
+of this License including Sections 8, 9, and 10. Nothing herein is intended or
+shall be deemed to constitute any admission of liability.
+
+12.0 Termination. This License and all rights granted hereunder will terminate
+immediately in the event of the circumstances described in Section 13.6 or if
+applicable law prohibits or restricts You from fully and or specifically
+complying with Sections 3, 4 and/or 6, or prevents the enforceability of any
+of those Sections, and You must immediately discontinue any use of Licensed
+Software.
+
+12.1 Automatic Termination Upon Breach. This License and the rights granted
+hereunder will terminate automatically if You fail to comply with the terms
+herein and fail to cure such breach within thirty (30) days of becoming aware
+of the breach. All sublicenses to the Licensed Software that are properly
+granted shall survive any termination of this License. Provisions that, by
+their nature, must remain in effect beyond the termination of this License,
+shall survive.
+
+12.2 Termination Upon Assertion of Patent Infringement. If You initiate
+litigation by asserting a patent infringement claim (excluding declaratory
+judgment actions) against Licensor or a Contributor (Licensor or Contributor
+against whom You file such an action is referred to herein as "Respondent")
+alleging that Licensed Software directly or indirectly infringes any patent,
+then any and all rights granted by such Respondent to You under Sections 3 or
+4 of this License shall terminate prospectively upon sixty (60) days notice
+from Respondent (the "Notice Period") unless within that Notice Period You
+either agree in writing (i) to pay Respondent a mutually agreeable reasonably
+royalty for Your past or future use of Licensed Software made by such
+Respondent, or (ii) withdraw Your litigation claim with respect to Licensed
+Software against such Respondent. If within said Notice Period a reasonable
+royalty and payment arrangement are not mutually agreed upon in writing by the
+parties or the litigation claim is not withdrawn, the rights granted by
+Licensor to You under Sections 3 and 4 automatically terminate at the
+expiration of said Notice Period.
+
+12.3 Reasonable Value of This License. If You assert a patent infringement
+claim against Respondent alleging that Licensed Software directly or
+indirectly infringes any patent where such claim is resolved (such as by
+license or settlement) prior to the initiation of patent infringement
+litigation, then the reasonable value of the licenses granted by said
+Respondent under Sections 3 and 4 shall be taken into account in determining
+the amount or value of any payment or license.
+
+12.4 No Retroactive Effect of Termination. In the event of termination under
+this Section all end user license agreements (excluding licenses to
+distributors and resellers) that have been validly granted by You or any
+distributor hereunder prior to termination shall survive termination.
+
+13.0 Miscellaneous.
+
+13.1 U.S. Government End Users. The Licensed Software is a "commercial item,"
+as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of
+"commercial computer software" and "commercial computer software
+documentation," as such terms are used in 48 C.F.R. 12.212 (Sept. 1995).
+Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4
+(June 1995), all U.S. Government End Users acquire Licensed Software with only
+those rights set forth herein.
+
+13.2 Relationship of Parties. This License will not be construed as creating
+an agency, partnership, joint venture, or any other form of legal association
+between or among You, Licensor, or any Contributor, and You will not represent
+to the contrary, whether expressly, by implication, appearance, or otherwise.
+
+13.3 Independent Development. Nothing in this License will impair Licensor's
+right to acquire, license, develop, subcontract, market, or distribute
+technology or products that perform the same or similar functions as, or
+otherwise compete with, Extensions that You may develop, produce, market, or
+distribute.
+
+13.4 Consent To Breach Not Waiver. Failure by Licensor or Contributor to
+enforce any provision of this License will not be deemed a waiver of future enforcement
+of that or any other provision.
+
+13.5 Severability. This License represents the complete agreement concerning
+the subject matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent necessary
+to make it enforceable.
+
+13.6 Inability to Comply Due to Statute or Regulation. If it is impossible for
+You to comply with any of the terms of this License with respect to some or
+all of the Licensed Software due to statute, judicial order, or regulation,
+then You cannot use, modify, or distribute the software.
+
+13.7 Export Restrictions. You may be restricted with respect to downloading or
+otherwise acquiring, exporting, or reexporting the Licensed Software or any
+underlying information or technology by United States and other applicable
+laws and regulations. By downloading or by otherwise obtaining the Licensed
+Software, You are agreeing to be responsible for compliance with all
+applicable laws and regulations.
+
+13.8 Arbitration, Jurisdiction & Venue. This License shall be governed by
+Colorado law provisions (except to the extent applicable law, if any, provides
+otherwise), excluding its conflict-of-law provisions. You expressly agree that
+any dispute relating to this License shall be submitted to binding arbitration
+under the rules then prevailing of the American Arbitration Association. You
+further agree that Adams County, Colorado USA is proper venue and grant such
+arbitration proceeding jurisdiction as may be appropriate for purposes of
+resolving any dispute under this License. Judgement upon any award made in
+arbitration may be entered and enforced in any court of competent
+jurisdiction. The arbitrator shall award attorney's fees and costs of
+arbitration to the prevailing party. Should either party find it necessary to
+enforce its arbitration award or seek specific performance of such award in a
+civil court of competent jurisdiction, the prevailing party shall be entitled
+to reasonable attorney's fees and costs. The application of the United Nations
+Convention on Contracts for the International Sale of Goods is expressly
+excluded. You and Licensor expressly waive any rights to a jury trial in any
+litigation concerning Licensed Software or this License. Any law or regulation
+that provides that the language of a contract shall be construed against the
+drafter shall not apply to this License.
+
+13.9 Entire Agreement. This License constitutes the entire agreement between
+the parties with respect to the subject matter hereof.
+
+EXHIBIT A
+
+The License Notice below must appear in each file of the Source Code of any
+copy You distribute of the Licensed Software or any Extensions thereto:
+
+ Unless explicitly acquired and licensed from Licensor under another
+ license, the contents of this file are subject to the Reciprocal Public
+ License ("RPL") Version 1.5, or subsequent versions as allowed by the RPL,
+ and You may not copy or use this file in either source code or executable
+ form, except in compliance with the terms and conditions of the RPL.
+
+ All software distributed under the RPL is provided strictly on an "AS
+ IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND
+ LICENSOR HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
+ LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific
+ language governing rights and limitations under the RPL.
+
+
+
+EXHIBIT B
+
+The User-Visible Attribution Notice below, when provided, must appear in each
+user-visible display as defined in Section 6.4 (d):
\ No newline at end of file
diff --git a/vendor/mgp25/instagram-php/LICENSE_PREMIUM b/vendor/mgp25/instagram-php/LICENSE_PREMIUM
new file mode 100755
index 0000000..ed45a9e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/LICENSE_PREMIUM
@@ -0,0 +1,16 @@
+The Premium license applies to all entities which have been granted "premium"
+status, and lasts for the duration of their premium status:
+
+All terms of the Reciprocal Public License 1.5 (RPL-1.5) still apply,
+but with the following modifications:
+
+- You are NOT required to disclose your own source code publicly, which
+ means that this premium license is *perfect* for website/app builders.
+
+- You are NOT required to credit this library anywhere in your own projects.
+ (But if you are re-distributing OUR source code, you must of course still
+ retain all of our copyright notices, and not claim our code as your own.)
+
+- You are encouraged (but NOT required) to contribute your library
+ improvements/modifications back to us. We would appreciate it, and may
+ even grant you premium status if the contributions are high-quality!
\ No newline at end of file
diff --git a/vendor/mgp25/instagram-php/README.md b/vendor/mgp25/instagram-php/README.md
new file mode 100755
index 0000000..16ab30b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/README.md
@@ -0,0 +1,135 @@
+#  Instagram-API [](https://packagist.org/packages/mgp25/instagram-php) [](https://packagist.org/packages/mgp25/instagram-php)  [](https://packagist.org/packages/mgp25/instagram-php)
+
+This is a PHP library which emulates Instagram's Private API. This library is packed full with almost all the features from the Instagram Android App. This includes media uploads, direct messaging, stories and more.
+
+**Read the [wiki](https://github.com/mgp25/Instagram-API/wiki)** and previous issues before opening a new one! Maybe your issue has already been answered.
+
+**Frequently Asked Questions:** [F.A.Q.](https://github.com/mgp25/Instagram-API/wiki/FAQ)
+
+**Do you like this project? Support it by donating**
+
+**mgp25**
+
+-  Paypal: [Donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5ATYY8H9MC96E)
+-  Bitcoin: 1DCEpC9wYXeUGXS58qSsqKzyy7HLTTXNYe
+
+**stevejobzniak**
+
+-  Paypal: [Donate](https://www.paypal.me/Armindale/0usd)
+-  Bitcoin: 18XF1EmrkpYi4fqkR2XcHkcJxuTMYG4bcv
+
+**jroy**
+
+-  Paypal: [Donate](https://www.paypal.me/JoshuaRoy1/0usd)
+-  Bitcoin: 32J2AqJBDY1VLq6wfZcLrTYS8fCcHHVDKD
+
+----------
+## Installation
+
+### Dependencies
+
+Install/enable the required php extensions and dependencies. You can learn how to do so [here](https://github.com/mgp25/Instagram-API/wiki/Dependencies).
+
+### Install this library
+We use composer to distribute our code effectively and easily. If you do not already have composer installed, you can download and install it here [here](https://getcomposer.org/download/).
+
+Once you have composer installed, you can do the following:
+```sh
+composer require mgp25/instagram-php
+```
+
+```php
+require __DIR__.'/../vendor/autoload.php';
+
+$ig = new \InstagramAPI\Instagram();
+```
+
+If you want to test new and possibly unstable code that is in the master branch, and which hasn't yet been released, then you can do the following (at your own risk):
+
+```sh
+composer require mgp25/instagram-php dev-master
+```
+
+#### _Warning about moving data to a different server_
+
+_Composer checks your system's capabilities and selects libraries based on your **current** machine (where you are running the `composer` command). So if you run Composer on machine `A` to install this library, it will check machine `A`'s capabilities and will install libraries appropriate for that machine (such as installing the PHP 7+ versions of various libraries). If you then move your whole installation to machine `B` instead, it **will not work** unless machine `B` has the **exact** same capabilities (same or higher PHP version and PHP extensions)! Therefore, you should **always** run the Composer-command on your intended target machine instead of your local machine._
+
+## Examples
+
+All examples can be found [here](https://github.com/mgp25/Instagram-API/tree/master/examples).
+
+## Code of Conduct
+
+This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).
+By participating, you are expected to uphold this code.
+Please report any unacceptable behavior.
+
+## How do I contribute
+
+If you would like to contribute to this project, please feel free to submit a pull request.
+
+Before you do, take a look at the [contributing guide](https://github.com/mgp25/Instagram-API/blob/master/CONTRIBUTING.md).
+
+## Why did I make this API?
+
+After legal measures, Facebook, WhatsApp and Instagram blocked my accounts.
+In order to use Instagram on my phone I needed a new phone, as they banned my UDID, so that is basically why I made this API.
+
+### What is Instagram?
+According to [the company](https://instagram.com/about/faq/):
+
+> "Instagram is a fun and quirky way to share your life with friends through a series of pictures. Snap a photo with your mobile phone, then choose a filter to transform the image into a memory to keep around forever. We're building Instagram to allow you to experience moments in your friends' lives through pictures as they happen. We imagine a world more connected through photos."
+
+# License
+
+In order help ensure fairness and sharing, this library is dual-licensed. Be
+aware that _all_ usage, unless otherwise specified, is under the **RPL-1.5**
+license!
+
+- Reciprocal Public License 1.5 (RPL-1.5): https://opensource.org/licenses/RPL-1.5
+
+You should read the _entire_ license; especially the `PREAMBLE` at the
+beginning. In short, the word `reciprocal` means "giving something back in
+return for what you are getting". It is _**not** a freeware license_. This
+license _requires_ that you open-source _all_ of your own source code for _any_
+project which uses this library! Creating and maintaining this library is
+endless hard work for us. That's why there is _one_ simple requirement for you:
+Give _something_ back to the world. Whether that's code _or_ financial support
+for this project is entirely up to you, but _nothing else_ grants you _any_
+right to use this library.
+
+Furthermore, the library is _also_ available _to certain entities_ under a
+modified version of the RPL-1.5, which has been modified to allow you to use the
+library _without_ open-sourcing your own project. The modified license
+(see [LICENSE_PREMIUM](https://github.com/mgp25/Instagram-API/blob/master/LICENSE_PREMIUM))
+is granted to certain entities, at _our_ discretion, and for a _limited_ period
+of time (unless otherwise agreed), pursuant to our terms. Currently, we are
+granting this license to all
+"[premium subscribers](https://github.com/mgp25/Instagram-API/issues/2655)" for
+the duration of their subscriptions. You can become a premium subscriber by
+either contributing substantial amounts of high-quality code, or by subscribing
+for a fee. This licensing ensures fairness and stimulates the continued growth
+of this library through both code contributions and the financial support it
+needs.
+
+You are not required to accept this License since you have not signed it,
+however _nothing else_ grants you permission to _use_, copy, distribute, modify,
+or create derivatives of either the Software (this library) or any Extensions
+created by a Contributor. These actions are prohibited by law if you do not
+accept this License. Therefore, by performing any of these actions You indicate
+Your acceptance of this License and Your agreement to be bound by all its terms
+and conditions. IF YOU DO NOT AGREE WITH ALL THE TERMS AND CONDITIONS OF THIS
+LICENSE DO NOT USE, MODIFY, CREATE DERIVATIVES, OR DISTRIBUTE THE SOFTWARE. IF
+IT IS IMPOSSIBLE FOR YOU TO COMPLY WITH ALL THE TERMS AND CONDITIONS OF THIS
+LICENSE THEN YOU CAN NOT USE, MODIFY, CREATE DERIVATIVES, OR DISTRIBUTE THE
+SOFTWARE.
+
+# Terms and conditions
+
+- You will NOT use this API for marketing purposes (spam, botting, harassment, massive bulk messaging...).
+- We do NOT give support to anyone who wants to use this API to send spam or commit other crimes.
+- We reserve the right to block any user of this repository that does not meet these conditions.
+
+## Legal
+
+This code is in no way affiliated with, authorized, maintained, sponsored or endorsed by Instagram or any of its affiliates or subsidiaries. This is an independent and unofficial API. Use at your own risk.
diff --git a/vendor/mgp25/instagram-php/composer.json b/vendor/mgp25/instagram-php/composer.json
new file mode 100755
index 0000000..166032e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/composer.json
@@ -0,0 +1,81 @@
+{
+ "name": "mgp25/instagram-api",
+ "description": "Instagram's private API for PHP",
+ "license": [
+ "RPL-1.5",
+ "proprietary"
+ ],
+ "keywords": [
+ "Instagram",
+ "Private",
+ "API",
+ "PHP"
+ ],
+ "support": {
+ "issues": "https://github.com/mgp25/Instagram-API/issues",
+ "wiki": "https://github.com/mgp25/Instagram-API/wiki",
+ "source": "https://github.com/mgp25/Instagram-API/"
+ },
+ "authors": [
+ {
+ "name": "mgp25",
+ "email": "me@mgp25.com",
+ "role": "Founder"
+ },
+ {
+ "name": "SteveJobzniak",
+ "homepage": "https://github.com/SteveJobzniak",
+ "role": "Developer"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "InstagramAPI\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "InstagramAPI\\Tests\\": "tests/"
+ }
+ },
+ "require": {
+ "php": ">=5.6",
+ "lazyjsonmapper/lazyjsonmapper": "^1.6.1",
+ "guzzlehttp/guzzle": "^6.2",
+ "ext-curl": "*",
+ "ext-mbstring": "*",
+ "ext-gd": "*",
+ "ext-exif": "*",
+ "ext-zlib": "*",
+ "ext-bcmath": "*",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "^2.5",
+ "react/socket": "^0.8",
+ "binsoul/net-mqtt-client-react": "^0.3.2",
+ "clue/socks-react": "^0.8.2",
+ "clue/http-proxy-react": "^1.1.0",
+ "psr/log": "^1.0",
+ "valga/fbns-react": "^0.1.8",
+ "symfony/process": "^3.4|^4.0",
+ "winbox/args": "1.0.0"
+ },
+ "suggest": {
+ "ext-event": "Installing PHP's native Event extension enables faster Realtime class event handling."
+ },
+ "require-dev": {
+ "react/http": "^0.7.2",
+ "friendsofphp/php-cs-fixer": "^2.11.0",
+ "monolog/monolog": "^1.23",
+ "phpunit/phpunit": "^5.7 || ^6.2"
+ },
+ "scripts": {
+ "codestyle": [
+ "lazydoctor -c composer.json -pfo",
+ "php-cs-fixer fix --config=.php_cs.dist --allow-risky yes",
+ "php devtools/checkStyle.php x"
+ ],
+ "test": [
+ "phpunit tests"
+ ]
+ }
+}
diff --git a/vendor/mgp25/instagram-php/devtools/README.md b/vendor/mgp25/instagram-php/devtools/README.md
new file mode 100755
index 0000000..00449d3
--- /dev/null
+++ b/vendor/mgp25/instagram-php/devtools/README.md
@@ -0,0 +1,7 @@
+These resources are *ONLY for INTERNAL usage* by the Instagram-API library developer team.
+
+Do *NOT* ask us about them. Do *NOT* make support tickets about them. We will *ban* you.
+
+Using this library is a *privilege* and we do *not* have to explain everything to every newbie who stumbles upon our repository.
+
+If you want to join the team, then read for yourself what they do and try to figure it out on your own.
diff --git a/vendor/mgp25/instagram-php/devtools/checkDevices.php b/vendor/mgp25/instagram-php/devtools/checkDevices.php
new file mode 100755
index 0000000..c1a64d1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/devtools/checkDevices.php
@@ -0,0 +1,229 @@
+login($username, $password);
+
+// Code for building video lists (uncomment both lines).
+// $userPk = $ig->people->getInfoByName('selenagomez')->getUser()->getPk();
+// buildVideoList($ig, $userPk); exit;
+
+// List of good videos and resolutions that we MUST see with a GOOD user agent.
+// Manually created via buildVideoList() and the code above, by selecting some
+// high resolution videos from random profiles. If these are deleted in the
+// future or if Instagram adds support for wider than 640px videos, we'll need
+// to rebuild this test array with other test videos.
+$highResVideoIDs = [
+ '1451670806714625231_460563723' => [ // Selena Gomez portrait video.
+ ['width'=>640, 'height'=>799, 'url'=>'https://instagram.com/p/BQlXuhNB6zP/'],
+ ['width'=> 480, 'height'=>599, 'url'=>'https://instagram.com/p/BQlXuhNB6zP/'],
+ ['width'=> 480, 'height'=>599, 'url'=>'https://instagram.com/p/BQlXuhNB6zP/'],
+ ],
+ '1264771449117881030_460563723' => [ // Selena Gomez 16:9 landscape video.
+ ['width'=>640, 'height'=>360, 'url'=>'https://instagram.com/p/BGNXu6SujLG/'],
+ ['width'=> 480, 'height'=>270, 'url'=>'https://instagram.com/p/BGNXu6SujLG/'],
+ ['width'=> 480, 'height'=>270, 'url'=>'https://instagram.com/p/BGNXu6SujLG/'],
+ ],
+ '1435631390916900349_460563723' => [ // Selena Gomez max res square video.
+ ['width'=>640, 'height'=>640, 'url'=>'https://instagram.com/p/BPsYyTMAHX9/'],
+ ['width'=> 480, 'height'=>480, 'url'=>'https://instagram.com/p/BPsYyTMAHX9/'],
+ ['width'=> 480, 'height'=>480, 'url'=>'https://instagram.com/p/BPsYyTMAHX9/'],
+ ],
+];
+
+$testDevices = [];
+
+// Test all of our current, "good" devices too? We must do that periodically.
+$testGoodDevices = true;
+if ($testGoodDevices) {
+ foreach (GoodDevices::getAllGoodDevices() as $deviceString) {
+ $testDevices[] = $deviceString;
+ }
+}
+
+// Additional devices to test. Add these new devices in devicestring format.
+$testDevices = array_merge(
+ $testDevices,
+ [
+ // DEMO STRING: This is a low-res device which is NOT capable of
+ // getting HD video URLs in API replies. It's just here for developer
+ // demo purposes. Also note that it's actually caught by the Device()
+ // class' REFUSAL to construct from anything lower than 1920x1080
+ // devices, so we had to FAKE its resolution HERE just to get it to
+ // even be testable! Its real User Agent string is supposed to be:
+ //'17/4.2.2; 240dpi; 480x800; samsung; SM-G350; cs02; hawaii_ss_cs02',
+ // However, Instagram only checks the model identifier, in this case
+ // "SM-G350", and the test shows that this bad device lacks HD videos.
+ '17/4.2.2; 240dpi; 1080x1920; samsung; SM-G350; cs02; hawaii_ss_cs02',
+ ]
+);
+
+// Build all device objects before we even run the tests, just to catch
+// any "bad devicestring" construction errors before wasting time.
+foreach ($testDevices as $key => $deviceString) {
+ // Create the new Device object, without automatic fallbacks.
+ $testDevices[$key] = new Device(Constants::IG_VERSION, Constants::VERSION_CODE, Constants::USER_AGENT_LOCALE, $deviceString, false);
+}
+
+// Test all devices in our list!
+foreach ($testDevices as $thisDevice) {
+ switchDevice($ig, $thisDevice);
+
+ $currentAgent = $ig->device->getUserAgent();
+ echo "\n[{$currentAgent}]\n";
+
+ $isBadDevice = false;
+ foreach ($highResVideoIDs as $videoId => $expectedResolutions) {
+ $bestWidth = $expectedResolutions[0]['width'];
+ $bestHeight = $expectedResolutions[0]['height'];
+ echo "* Checking {$videoId} (should be: {$bestWidth}x{$bestHeight})...";
+
+ // Retrieve video info (with 4 total attempts in case of throttling).
+ for ($attempt = 1; $attempt <= 4; ++$attempt) {
+ try {
+ $mediaInfo = $ig->media->getInfo($videoId)->getItems()[0];
+ break;
+ } catch (\InstagramAPI\Exception\ThrottledException $e) {
+ if ($attempt < 4) {
+ sleep(10);
+ } else {
+ throw $e;
+ }
+ }
+ }
+
+ // Verify all video candidates.
+ $videoVersions = $mediaInfo->getVideoVersions();
+ if (count($videoVersions) !== count($expectedResolutions)) {
+ die('Wrong number of video candidate URLs for media item: '.$videoId);
+ }
+ $isBadVersions = false;
+ foreach ($videoVersions as $idx => $version) {
+ $thisWidth = $version->getWidth();
+ $thisHeight = $version->getHeight();
+ $expectedWidth = $expectedResolutions[$idx]['width'];
+ $expectedHeight = $expectedResolutions[$idx]['height'];
+ if ($thisWidth != $expectedWidth || $thisHeight != $expectedHeight) {
+ $isBadVersions = true;
+ break;
+ }
+ }
+ echo $isBadVersions ? "Bad response: {$thisWidth}x{$thisHeight}.\n" : "OK.\n";
+ if ($isBadVersions) {
+ $isBadDevice = true;
+ }
+ }
+
+ echo $isBadDevice ? "THIS DEVICE IS BAD AND DOES NOT SUPPORT HD VIDEOS!\n" : "DEVICE OK.\n";
+}
+
+// INTERNAL FUNCTIONS...
+
+/**
+ * Changes the user-agent sent by the InstagramAPI library.
+ *
+ * @param \InstagramAPI\Instagram $ig
+ * @param DeviceInterface|string $value
+ */
+function switchDevice(
+ $ig,
+ $value)
+{
+ // Create the new Device object, without automatic fallbacks.
+ $device = ($value instanceof DeviceInterface ? $value : new Device(Constants::IG_VERSION, Constants::VERSION_CODE, Constants::USER_AGENT_LOCALE, $value, false));
+
+ // Update the Instagram Client's User-Agent to the new Device.
+ $ig->device = $device;
+ $ig->client->updateFromCurrentSettings();
+}
+
+/**
+ * Checks a timeline and builds a list of all videos.
+ *
+ * Used for building the internal $highResVideoIDs test array.
+ *
+ * Instagram forces videos (timelines and stories) to be no wider than 640px.
+ *
+ * Max square video: 640x640. Nothing can ever be bigger than that if square.
+ *
+ * Max landscape video: 640xDEPENDS ON PHONE (for example 640x360 (16:9 videos)).
+ * In landscape videos, height is always < 640.
+ *
+ * Max portrait video: 640xDEPENDS ON PHONE (for example 640x799, 640x1136 (16:9)).
+ * In portrait, height IS > 640 (anything less would be a square/landscape).
+ *
+ * So Instagram lets people post HD resolutions in PORTRAIT, but only SD videos
+ * in landscape... This actually leads to a funny realization: To post HD videos
+ * with a high HD resolution, you must rotate them so they're always portrait.
+ *
+ * Also note that Instagram allows UPLOADING videos up to 1080 pixels WIDE. But
+ * they will RESIZE them to no more than 640 pixels WIDE. Perhaps they will
+ * someday allow 1080-width playback, in which case we will have to test and
+ * revise all device identifiers again to make sure they all see the best URLs.
+ *
+ * @param \InstagramAPI\Instagram $ig
+ * @param string $userPk
+ */
+function buildVideoList(
+ $ig,
+ $userPk)
+{
+ // We must use a good device to get answers when scanning for HD videos.
+ switchDevice($ig, GoodDevices::getRandomGoodDevice());
+
+ echo "[\n";
+ $maxId = null;
+ do {
+ $feed = $ig->timeline->getUserFeed($userPk, $maxId);
+ foreach ($feed->getItems() as $item) {
+ if ($item->getMediaType() != \InstagramAPI\Response\Model\Item::VIDEO) {
+ continue;
+ }
+
+ $code = \InstagramAPI\InstagramID::toCode($item->getPk());
+ echo sprintf(" '%s' => [\n", $item->getId());
+ $videoVersions = $item->getVideoVersions();
+ foreach ($videoVersions as $version) {
+ echo sprintf(
+ " ['width'=>%d, 'height'=>%d, 'url'=>'https://instagram.com/p/%s/'],\n",
+ $version->getWidth(), $version->getHeight(), $code
+ );
+ }
+ echo " ],\n";
+ }
+ $maxId = $feed->getNextMaxId();
+ } while ($maxId !== null);
+ echo "]\n";
+}
diff --git a/vendor/mgp25/instagram-php/devtools/checkExperiments.php b/vendor/mgp25/instagram-php/devtools/checkExperiments.php
new file mode 100755
index 0000000..b3b8f67
--- /dev/null
+++ b/vendor/mgp25/instagram-php/devtools/checkExperiments.php
@@ -0,0 +1,150 @@
+run();
+if (!empty($problemFiles)) {
+ // Exit with non-zero code to signal that there are problems.
+ exit(1);
+}
+
+class checkExperiments
+{
+ /**
+ * @var string
+ */
+ private $_baseDir;
+
+ /**
+ * @var string[]
+ */
+ private $_inspectFolders;
+
+ /**
+ * @var bool
+ */
+ private $_onlyShowInvalidFiles;
+
+ /**
+ * @var array
+ */
+ private $_experimentWhitelist;
+
+ /**
+ * Constructor.
+ *
+ * @param string $baseDir
+ * @param string[] $inspectFolders
+ * @param bool $onlyShowInvalidFiles
+ */
+ public function __construct(
+ $baseDir,
+ array $inspectFolders,
+ $onlyShowInvalidFiles)
+ {
+ $this->_baseDir = realpath($baseDir);
+ if ($this->_baseDir === false) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid path.', $baseDir));
+ }
+ $this->_inspectFolders = $inspectFolders;
+ $this->_onlyShowInvalidFiles = $onlyShowInvalidFiles;
+ $this->_experimentWhitelist = \InstagramAPI\Settings\StorageHandler::EXPERIMENT_KEYS;
+ }
+
+ /**
+ * Process single file.
+ *
+ * @param string $filePath
+ *
+ * @return bool TRUE if the file uses an un-whitelisted experiment, otherwise FALSE.
+ */
+ private function _processFile(
+ $filePath)
+ {
+ $hasProblems = false;
+ $processedExperiments = [];
+ $inputLines = @file($filePath);
+
+ if ($inputLines === false) {
+ // We were unable to read the input file. Ignore if broken symlink.
+ if (is_link($filePath)) {
+ return false; // File is okay, since the symlink is invalid.
+ } else {
+ echo "- {$filePath}: UNABLE TO READ FILE!".PHP_EOL;
+
+ return true; // File has problems...
+ }
+ }
+
+ if (preg_match_all('/->(?:getExperimentParam|isExperimentEnabled)\((?:\n {1,})?(?:\'|")(\w{1,})(?:\'|")/', implode('', $inputLines), $matches)) {
+ foreach ($matches[1] as $match) {
+ $experimentName = $match;
+ if (!strpos($experimentName, 'Experiment') !== false && !in_array($experimentName, $processedExperiments)) {
+ array_push($processedExperiments, $match);
+ if (in_array($experimentName, $this->_experimentWhitelist)) {
+ if (!$this->_onlyShowInvalidFiles) {
+ echo " {$filePath}: Uses whitelisted experiment: {$experimentName}.\n";
+ }
+ } else {
+ $hasProblems = true;
+ echo "- {$filePath}: Uses un-whitelisted experiment: {$experimentName}.\n";
+ }
+ }
+ }
+ }
+
+ return $hasProblems;
+ }
+
+ /**
+ * Process all *.php files in given path.
+ *
+ * @return string[] An array with all files that have un-whitelisted experiment usage.
+ */
+ public function run()
+ {
+ $filesWithProblems = [];
+ foreach ($this->_inspectFolders as $inspectFolder) {
+ $directoryIterator = new RecursiveDirectoryIterator($this->_baseDir.'/'.$inspectFolder);
+ $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
+ $phpIterator = new RegexIterator($recursiveIterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
+
+ foreach ($phpIterator as $filePath => $dummy) {
+ $hasProblems = $this->_processFile($filePath);
+ if ($hasProblems) {
+ $filesWithProblems[] = $filePath;
+ }
+ }
+ }
+
+ return $filesWithProblems;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/devtools/checkStyle.php b/vendor/mgp25/instagram-php/devtools/checkStyle.php
new file mode 100755
index 0000000..aa97ca6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/devtools/checkStyle.php
@@ -0,0 +1,203 @@
+run();
+if (!empty($badFiles)) {
+ // Exit with non-zero code to signal that there are problems.
+ exit(1);
+}
+
+class styleChecker
+{
+ /**
+ * @var string
+ */
+ private $_baseDir;
+
+ /**
+ * @var string[]
+ */
+ private $_inspectFolders;
+
+ /**
+ * @var bool
+ */
+ private $_onlyShowInvalidFiles;
+
+ /**
+ * Constructor.
+ *
+ * @param string $baseDir
+ * @param string[] $inspectFolders
+ * @param bool $onlyShowInvalidFiles
+ */
+ public function __construct(
+ $baseDir,
+ array $inspectFolders,
+ $onlyShowInvalidFiles)
+ {
+ $this->_baseDir = realpath($baseDir);
+ if ($this->_baseDir === false) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a valid path.', $baseDir));
+ }
+ $this->_inspectFolders = $inspectFolders;
+ $this->_onlyShowInvalidFiles = $onlyShowInvalidFiles;
+ }
+
+ /**
+ * Process single file.
+ *
+ * @param string $filePath
+ *
+ * @return bool TRUE if the file has codestyle problems, otherwise FALSE.
+ */
+ private function _processFile(
+ $filePath)
+ {
+ $hasProblems = false;
+ $hasVisibilityProblems = false;
+ $fileName = basename($filePath);
+ $inputLines = @file($filePath);
+ $outputLines = [];
+
+ if ($inputLines === false) {
+ // We were unable to read the input file. Ignore if broken symlink.
+ if (is_link($filePath)) {
+ return false; // File is okay, since the symlink is invalid.
+ } else {
+ echo "- {$filePath}: UNABLE TO READ FILE!".PHP_EOL;
+
+ return true; // File has problems...
+ }
+ }
+
+ foreach ($inputLines as $line) {
+ // Function arguments on separate lines.
+ if (preg_match('/^(.*?(?:(?:final|static)\s+)*(?:public|private|protected)(?:\s+(?:final|static))*\s+function\s+.+?)\((.+)\)(.*)$/', $line, $matches)) {
+ $hasProblems = true;
+
+ $funcstart = $matches[1];
+ $params = $matches[2];
+ $trail = $matches[3];
+ $params = explode(', ', $params);
+
+ $outputLines[] = $funcstart.'('.PHP_EOL;
+ for ($i = 0, $len = count($params); $i < $len; ++$i) {
+ $newline = ' '.$params[$i];
+ if ($i == ($len - 1)) {
+ $newline .= ')'.PHP_EOL;
+ } else {
+ $newline .= ','.PHP_EOL;
+ }
+ $outputLines[] = $newline;
+ }
+ // } else {
+ // $outputLines[] = $line;
+ }
+
+ // Appropriate public, private and protected member prefixes.
+ if (preg_match('/^\s*(?:(?:final|static)\s+)*(public|private|protected)(?:\s+(?:final|static))*\s+(function|\$)\s*&?([^;\(\s]+)/', $line, $matches)) {
+ $visibility = &$matches[1]; // public, private, protected
+ $type = &$matches[2]; // $, function
+ $name = &$matches[3]; // Member name
+
+ if ($visibility == 'public') {
+ if ($name[0] == '_' && (
+ $name != '__construct'
+ && $name != '__destruct'
+ && $name != '__call'
+ && $name != '__get'
+ && $name != '__set'
+ && $name != '__isset'
+ && $name != '__unset'
+ && $name != '__invoke'
+ && $name != '__toString'
+ )) {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PUBLIC NAME:".trim($matches[0]).PHP_EOL;
+ }
+ } else { // private, protected
+ if ($name[0] != '_') {
+ $hasProblems = true;
+ $hasVisibilityProblems = true;
+ echo "- {$filePath}: BAD PRIVATE/PROTECTED NAME:".trim($matches[0]).PHP_EOL;
+ }
+ }
+ }
+ }
+
+ $newFile = implode('', $outputLines);
+ if (!$hasProblems) {
+ if (!$this->_onlyShowInvalidFiles) {
+ echo " {$filePath}: Already formatted correctly.\n";
+ }
+ } elseif (!$hasVisibilityProblems) {
+ // Has problems, but no visibility problems. Output fixed file.
+ echo "- {$filePath}: Has function parameter problems:\n";
+ echo $newFile;
+ } else {
+ echo "- {$filePath}: Had member visibility problems.\n";
+ }
+
+ return $hasProblems;
+ }
+
+ /**
+ * Process all *.php files in given path.
+ *
+ * @return string[] An array with all files that have codestyle problems.
+ */
+ public function run()
+ {
+ $filesWithProblems = [];
+ foreach ($this->_inspectFolders as $inspectFolder) {
+ $directoryIterator = new RecursiveDirectoryIterator($this->_baseDir.'/'.$inspectFolder);
+ $recursiveIterator = new RecursiveIteratorIterator($directoryIterator);
+ $phpIterator = new RegexIterator($recursiveIterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
+
+ foreach ($phpIterator as $filePath => $dummy) {
+ $hasProblems = $this->_processFile($filePath);
+ if ($hasProblems) {
+ $filesWithProblems[] = $filePath;
+ }
+ }
+ }
+
+ return $filesWithProblems;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/devtools/testInstagramPhoto.php b/vendor/mgp25/instagram-php/devtools/testInstagramPhoto.php
new file mode 100755
index 0000000..c0e9bb0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/devtools/testInstagramPhoto.php
@@ -0,0 +1,66 @@
+getFile(), $outputFile)) {
+ throw new \RuntimeException(sprintf('Failed to write to "%s".', $outputFile));
+ }
+}
+
+// Ensure that we have the JPEG orientation test images.
+$testImageFolder = __DIR__.'/exif-orientation-examples/';
+if (!is_dir($testImageFolder)) {
+ echo "* Downloading test images:\n";
+ exec('git clone https://github.com/recurser/exif-orientation-examples.git '.escapeshellarg($testImageFolder));
+}
+
+// Process all of the test images.
+$files = [];
+foreach (['Landscape', 'Portrait'] as $orientation) {
+ for ($imgNum = 1; $imgNum <= 8; ++$imgNum) {
+ $fileName = sprintf('%s_%d.jpg', $orientation, $imgNum);
+ $inputFile = sprintf('%s/%s', $testImageFolder, $fileName);
+ if (!is_file($inputFile)) {
+ die('Error: Missing "'.$inputFile.'".');
+ }
+
+ // Run the cropping test...
+ $outputFile = sprintf('%s/result_crop_%s', $testImageFolder, $fileName);
+ runTest($inputFile, $outputFile, [
+ 'forceAspectRatio' => 1.0, // Square.
+ 'horCropFocus' => -35, // Always use the same focus, to look for orientation errors.
+ 'verCropFocus' => -35, // This combo aims at the upper left corner.
+ 'operation' => InstagramMedia::CROP,
+ ]);
+
+ // Run the expansion test...
+ $outputFile = sprintf('%s/result_expand_%s', $testImageFolder, $fileName);
+ runTest($inputFile, $outputFile, [
+ 'forceAspectRatio' => 1.0, // Square.
+ 'operation' => InstagramMedia::EXPAND,
+ ]);
+ }
+}
+
+echo "\n\nAll images have been processed. Manually review the results to ensure they're all correctly processed.\n";
diff --git a/vendor/mgp25/instagram-php/examples/README.md b/vendor/mgp25/instagram-php/examples/README.md
new file mode 100755
index 0000000..ce3d447
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/README.md
@@ -0,0 +1,17 @@
+## Examples
+
+These examples demonstrate various common tasks and are meant to get you started
+with this library.
+
+Many of these files contain a configuration section at the top, which you must
+edit to provide your own account and media details before running the example.
+
+## WARNING, PLEASE READ!
+
+If you are viewing this examples-folder via the web, they may not work on the
+code version that you have downloaded, since development may evolve quickly in
+the development version of the code tree. Especially if you've installed the
+default, stable version of the library, which can often be severely out of date.
+
+Look in _your own_ local disk's `vendor/mgp25/instagram-php/examples` folder for
+the examples that shipped with the exact library version you have installed!
diff --git a/vendor/mgp25/instagram-php/examples/accessingValues.php b/vendor/mgp25/instagram-php/examples/accessingValues.php
new file mode 100755
index 0000000..170bf58
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/accessingValues.php
@@ -0,0 +1,74 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ $feed = $ig->discover->getExploreFeed();
+
+ // Let's begin by looking at a beautiful debug output of what's available in
+ // the response! This is very helpful for figuring out what a response has!
+ $feed->printJson();
+
+ // Now let's look at what properties are supported on the $feed object. This
+ // works on ANY object from our library, and will show what functions and
+ // properties are supported, as well as how to call the functions! :-)
+ $feed->printPropertyDescriptions();
+
+ // The getExploreFeed() has an "items" property, which we need. As we saw
+ // above, we should get it via "getItems()". The property list above told us
+ // that it will return an array of "Item" objects. Therefore it's an ARRAY!
+ $items = $feed->getItems();
+
+ // Let's get the media item from the first item of the explore-items array...!
+ $firstItem = $items[0]->getMedia();
+
+ // We can look at that item too, if we want to... Let's do it! Note that
+ // when we list supported properties, it shows everything supported by an
+ // "Item" object. But that DOESN'T mean that every property IS available!
+ // That's why you should always check the JSON to be sure that data exists!
+ $firstItem->printJson(); // Shows its actual JSON contents (available data).
+ $firstItem->printPropertyDescriptions(); // List of supported properties.
+
+ // Let's look specifically at its User object!
+ $firstItem->getUser()->printJson();
+
+ // Okay, so the username of the person who posted the media is easy... And
+ // as you can see, you can even chain multiple function calls in a row here
+ // to get to the data. However, be aware that sometimes Instagram responses
+ // have NULL values, so chaining is sometimes risky. But not in this case,
+ // since we know that "user" and its "username" are always available! :-)
+ $firstItem_username = $firstItem->getUser()->getUsername();
+
+ // Now let's get the "id" of the item too!
+ $firstItem_mediaId = $firstItem->getId();
+
+ // Finally, let's get the highest-quality image URL for the media item!
+ $firstItem_imageUrl = $firstItem->getImageVersions2()->getCandidates()[0]->getUrl();
+
+ // Output some statistics. Well done! :-)
+ echo 'There are '.count($items)." items.\n";
+ echo "The first item has media id: {$firstItem_mediaId}.\n";
+ echo "The first item was uploaded by: {$firstItem_username}.\n";
+ echo "The highest quality image URL is: {$firstItem_imageUrl}.\n";
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/assets/instagram.png b/vendor/mgp25/instagram-php/examples/assets/instagram.png
new file mode 100755
index 0000000..fc311c0
Binary files /dev/null and b/vendor/mgp25/instagram-php/examples/assets/instagram.png differ
diff --git a/vendor/mgp25/instagram-php/examples/customSettings.php b/vendor/mgp25/instagram-php/examples/customSettings.php
new file mode 100755
index 0000000..fdb6f19
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/customSettings.php
@@ -0,0 +1,147 @@
+ 'mysql',
+]);
+
+// 2. You can read src/Settings/Factory.php for valid settings for each backend.
+// Here's an example of how to change the default storage location for "file":
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug, [
+ 'storage' => 'file',
+ 'basefolder' => 'some/path/',
+]);
+
+// 3. And here's an example of how to change the default database filename and
+// the default database table name for the "sqlite" backend:
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug, [
+ 'storage' => 'sqlite',
+ 'dbfilename' => 'some/path/foo.db',
+ 'dbtablename' => 'mysettings',
+]);
+
+// 4. If you read src/Settings/Factory.php, you'll notice that you can choose
+// the storage backends and most of their parameters via the command line or
+// environment variables instead. For example: "SETTINGS_STORAGE=mysql php
+// yourscript.php" would set the "storage" parameter via the environment, and
+// typing "php yourscript.php --settings_storage=mysql" would set it via the
+// command line. The command-line arguments have the highest precedence, then
+// the environment variables, and lastly the code within your script. This
+// precedence order is so that you can easily override your script's code to
+// test other backends or change their parameters without modifying your code.
+
+// 5. Very advanced users can look in src/Settings/StorageHandler.php to read
+// about hasUser(), moveUser() and deleteUser(). Three very, VERY DANGEROUS
+// commands which let you rename or delete account settings in your storage.
+// Carefully read through their descriptions and use them wisely. If you're sure
+// that you dare to use them, then you can access them via $ig->settings->...
+
+// 6. Yet another super advanced topic is the ability to copy data between
+// backends. For example if you want to move all of your data from a File
+// backend to a database, or vice versa. That kind of action is not supported
+// natively by us, but you CAN do it by directly interfacing with the storages!
+
+// First, you MUST manually build a list of all users you want to migrate.
+// You can either hardcode this list. Or get it via something like a directory
+// scan (to look at existing folders in a File backend storage path), or a
+// database query to get all "username" values from the old database. If you're
+// using a database, you will have to connect to it manually and query it
+// yourself! There's no way to do it automatically! Just build this array any
+// way you want to do it!
+$migrateUsers = [
+ 'someuser',
+ 'another.user',
+ 'very_awesome_user123',
+];
+
+// Secondly, you must connect to your old and new storages. These are just
+// example values. The array format is the exact same as what's given to the
+// `Instagram()` constructor! And if you give an empty array, you'll use the
+// same default File backend that the main class uses! So if you want to migrate
+// from that, you should just set oldStorage to `createHandler([])`!
+$oldStorage = \InstagramAPI\Settings\Factory::createHandler([
+ 'storage' => 'sqlite',
+ 'dbfilename' => 'app/instagram.sqlite',
+ 'dbtablename' => 'instagram_sessions',
+]);
+$newStorage = \InstagramAPI\Settings\Factory::createHandler([
+ 'storage' => 'file',
+ 'basefolder' => 'some/path/',
+]);
+
+// Now just run the migration process. This will copy all cookies and settings
+// from the old storage to the new storage, for all of the "migrateUsers".
+foreach ($migrateUsers as $user) {
+ if (!$oldStorage->hasUser($user)) {
+ die("Unable to migrate '{$user}' from old storage (user doesn't exist).\n");
+ }
+
+ echo "Migrating '{$user}'.\n";
+
+ $oldStorage->setActiveUser($user);
+ $newStorage->setActiveUser($user);
+
+ $newStorage->setCookies((string) $oldStorage->getCookies());
+ foreach (\InstagramAPI\Settings\StorageHandler::PERSISTENT_KEYS as $key) {
+ $newStorage->set($key, (string) $oldStorage->get($key));
+ }
+}
+
+// 7. Lastly... if you want to implement your own completely CUSTOM STORAGE,
+// then you simply have to do one thing: Implement the StorageInterface class
+// interface. But be very sure to STRICTLY follow ALL rules for storage backends
+// described in that interface's docs, otherwise your custom backend WON'T work.
+//
+// See the overview in src/Settings/StorageInterface.php, and then read through
+// the various built-in storage backends in src/Settings/Storage/ to see perfect
+// implementations that completely follow the required interface specification.
+//
+// Also note that PDO-based backends should be derived from our "PDOStorage"
+// storage sub-component, so that the logic is perfectly implemented and not
+// duplicated. That's exactly how our "sqlite" and "mysql" PDO backends work!
+//
+// To use your custom storage backend, you would simply create your own class
+// similar to the built-in backends. But do NOT put your own class in our
+// src/Settings/Storage/ folder. Store your class inside your own project.
+//
+// Then simply provide your custom storage class instance as the storage class:
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug, [
+ 'storage' => 'custom',
+ 'class' => new MyCustomStorage(), // Whatever you've named your class.
+]);
+
+// That's it! This should get you started on your journey. :-)
+
+// And please think about contributing your WELL-WRITTEN storage backends to
+// this project! If you had a reason to write your own, then there's probably
+// someone else out there with the same need. Remember to SHARE with the open
+// source community! ;-)
diff --git a/vendor/mgp25/instagram-php/examples/exceptionDetailsExample.php b/vendor/mgp25/instagram-php/examples/exceptionDetailsExample.php
new file mode 100755
index 0000000..57ae4a2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/exceptionDetailsExample.php
@@ -0,0 +1,83 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+/*
+ * The code below demonstrates how to get the full server response from an
+ * InstagramException object (that's the ONLY type of exception which can
+ * have an Instagram server response attached to it).
+ *
+ * All exceptions thrown by our library are derived from InstagramException.
+ * And MOST of them (but NOT ALL) will have a server response object. So it's
+ * important that you check hasResponse() before trying to use the response.
+ */
+
+// Example 1: Catching EVERYTHING derived from InstagramException so that you
+// can look at the server response (IF one is available).
+
+try {
+ $ig->media->delete('123456'); // Invalid media ID, to trigger a server error.
+} catch (\InstagramAPI\Exception\InstagramException $e) {
+ echo 'Something went wrong (InstagramException): '.$e->getMessage()."\n";
+
+ if ($e->hasResponse()) { // <-- VERY IMPORTANT TO CHECK FIRST!
+ echo "The current exception contains a full server response:\n";
+ $e->getResponse()->printJson();
+ echo "Showing the 'did_delete' and 'message' values of the response:\n";
+ var_dump($e->getResponse()->getDidDelete());
+ var_dump($e->getResponse()->getMessage());
+ }
+}
+
+// Example 2: Catching the SPECIFIC exception you want (which must be derived
+// from InstagramException, otherwise it won't have hasResponse()/getResponse()).
+
+try {
+ $ig->media->delete('123456'); // Invalid media ID, to trigger a server error.
+} catch (\InstagramAPI\Exception\EndpointException $e) {
+ echo 'Something went wrong (EndpointException): '.$e->getMessage()."\n";
+
+ if ($e->hasResponse()) { // <-- VERY IMPORTANT TO CHECK FIRST!
+ echo "The current exception contains a full server response:\n";
+ $e->getResponse()->printJson();
+ echo "Showing the 'did_delete' and 'message' values of the response:\n";
+ var_dump($e->getResponse()->getDidDelete());
+ var_dump($e->getResponse()->getMessage());
+ }
+
+ // Bonus: This shows how to look for specific "error reason" messages in an
+ // EndpointException. There are hundreds or even thousands of different
+ // error messages and we won't add exceptions for them (that would be a
+ // nightmare to maintain). Instead, it's up to the library users to check
+ // for the per-endpoint error messages they care about. Such as this one:
+ if (strpos($e->getMessage(), 'Could not delete') !== false) {
+ echo "* The problem in this generic EndpointException was that Instagram could not delete the media!\n";
+ }
+}
+
+// With this knowledge, you can now go out and catch the exact exceptions you
+// want, and look at specific server details in the reply whenever necessary!
+// Note that the easiest way to handle the generic EndpointException (which is
+// triggered when any of the generic functions fail) is to do a string search in
+// $e->getMessage() to look for a specific error message to know what type of
+// error it was. And if you need more details about the error, you can THEN look
+// at $e->getResponse() to see the contents of the actual server reply.
diff --git a/vendor/mgp25/instagram-php/examples/liveBroadcast.php b/vendor/mgp25/instagram-php/examples/liveBroadcast.php
new file mode 100755
index 0000000..446c1d8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/liveBroadcast.php
@@ -0,0 +1,137 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // NOTE: This code will create a broadcast, which will give us an RTMP url
+ // where we are supposed to stream-upload the media we want to broadcast.
+ //
+ // The following code is using FFMPEG to broadcast, although other
+ // alternatives are valid too, like OBS (Open Broadcaster Software,
+ // https://obsproject.com).
+ //
+ // For more information on FFMPEG, see:
+ // https://github.com/mgp25/Instagram-API/issues/1488#issuecomment-324271177
+ // and for OBS, see:
+ // https://github.com/mgp25/Instagram-API/issues/1488#issuecomment-333365636
+
+ // Get FFmpeg handler and ensure that the application exists on this system.
+ // NOTE: You can supply custom path to the ffmpeg binary, or just leave NULL
+ // to autodetect it.
+ $ffmpegPath = null;
+ $ffmpeg = \InstagramAPI\Media\Video\FFmpeg::factory($ffmpegPath);
+
+ // Tell Instagram that we want to perform a livestream.
+ $stream = $ig->live->create();
+ $broadcastId = $stream->getBroadcastId();
+ $ig->live->start($broadcastId);
+
+ $streamUploadUrl = $stream->getUploadUrl();
+
+ // Broadcast the entire video file.
+ // NOTE: The video is broadcasted asynchronously (in the background).
+ $broadcastProcess = $ffmpeg->runAsync(sprintf(
+ '-rtbufsize 256M -re -i %s -acodec libmp3lame -ar 44100 -b:a 128k -pix_fmt yuv420p -profile:v baseline -s 720x1280 -bufsize 6000k -vb 400k -maxrate 1500k -deinterlace -vcodec libx264 -preset veryfast -g 30 -r 30 -f flv %s',
+ \Winbox\Args::escape($videoFilename),
+ \Winbox\Args::escape($streamUploadUrl)
+ ));
+
+ // The following loop performs important requests to obtain information
+ // about the broadcast while it is ongoing.
+ // NOTE: This is REQUIRED if you want the comments and likes to appear
+ // in your saved post-live feed.
+ // NOTE: These requests are sent *while* the video is being broadcasted.
+ $lastCommentTs = 0;
+ $lastLikeTs = 0;
+ do {
+ // Get broadcast comments.
+ // - The latest comment timestamp will be required for the next
+ // getComments() request.
+ // - There are two types of comments: System comments and user comments.
+ // We compare both and keep the newest (most recent) timestamp.
+ $commentsResponse = $ig->live->getComments($broadcastId, $lastCommentTs);
+ $systemComments = $commentsResponse->getSystemComments();
+ $comments = $commentsResponse->getComments();
+ if (!empty($systemComments)) {
+ $lastCommentTs = $systemComments[0]->getCreatedAt();
+ }
+ if (!empty($comments) && $comments[0]->getCreatedAt() > $lastCommentTs) {
+ $lastCommentTs = $comments[0]->getCreatedAt();
+ }
+
+ // Get broadcast heartbeat and viewer count.
+ $heartbeatResponse = $ig->live->getHeartbeatAndViewerCount($broadcastId);
+
+ // Check to see if the livestream has been flagged for a policy violation.
+ if ($heartbeatResponse->isIsPolicyViolation() && (int) $heartbeatResponse->getIsPolicyViolation() === 1) {
+ echo 'Instagram has flagged your content as a policy violation with the following reason: '.($heartbeatResponse->getPolicyViolationReason() == null ? 'Unknown' : $heartbeatResponse->getPolicyViolationReason())."\n";
+ // Change this to false if disagree with the policy violation and would would like to continue streaming.
+ // - Note: In this example, the violation is always accepted.
+ // In your use case, you may want to prompt the user if
+ // they would like to accept or refute the policy violation.
+ if (true) {
+ // Get the final viewer list of the broadcast.
+ $ig->live->getFinalViewerList($broadcastId);
+ // End the broadcast stream while acknowledging the copyright warning given.
+ $ig->live->end($broadcastId, true);
+ exit(0);
+ }
+ // Acknowledges the copyright warning and allows you to continue streaming.
+ // - Note: This may allow the copyright holder to view your livestream
+ // regardless of your account privacy or if you archive it.
+ $ig->live->resumeBroadcastAfterContentMatch($broadcastId);
+ }
+
+ // Get broadcast like count.
+ // - The latest like timestamp will be required for the next
+ // getLikeCount() request.
+ $likeCountResponse = $ig->live->getLikeCount($broadcastId, $lastLikeTs);
+ $lastLikeTs = $likeCountResponse->getLikeTs();
+
+ // Get the join request counts.
+ // - This doesn't add support for live-with-friends. Rather,
+ // this is only here to emulate the app's livestream flow.
+ $ig->live->getJoinRequestCounts($broadcastId);
+
+ sleep(2);
+ } while ($broadcastProcess->isRunning());
+
+ // Get the final viewer list of the broadcast.
+ // NOTE: You should only use this after the broadcast has stopped uploading.
+ $ig->live->getFinalViewerList($broadcastId);
+
+ // End the broadcast stream.
+ // NOTE: Instagram will ALSO end the stream if your broadcasting software
+ // itself sends a RTMP signal to end the stream. FFmpeg doesn't do that
+ // (without patching), but OBS sends such a packet. So be aware of that.
+ $ig->live->end($broadcastId);
+
+ // Once the broadcast has ended, you can optionally add the finished
+ // broadcast to your post-live feed (saved replay).
+ $ig->live->addToPostLive($broadcastId);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/paginateWithExclusion.php b/vendor/mgp25/instagram-php/examples/paginateWithExclusion.php
new file mode 100755
index 0000000..7bf76d2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/paginateWithExclusion.php
@@ -0,0 +1,57 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Let's list locations that match "milan".
+ $query = 'milan';
+
+ // Initialize the state.
+ $rankToken = null;
+ $excludeList = [];
+ do {
+ // Request the page.
+ $response = $ig->location->findPlaces($query, $excludeList, $rankToken);
+
+ // In this example we're simply printing the IDs of this page's items.
+ foreach ($response->getItems() as $item) {
+ $location = $item->getLocation();
+ // Add the item ID to the exclusion list, to tell Instagram's server
+ // to skip that item on the next pagination request.
+ $excludeList[] = $location->getFacebookPlacesId();
+ // Let's print some details about the item.
+ printf("%s (%.3f, %.3f)\n", $item->getTitle(), $location->getLat(), $location->getLng());
+ }
+
+ // Now we must update the rankToken variable.
+ $rankToken = $response->getRankToken();
+
+ // Sleep for 5 seconds before requesting the next page. This is just an
+ // example of an okay sleep time. It is very important that your scripts
+ // always pause between requests that may run very rapidly, otherwise
+ // Instagram will throttle you temporarily for abusing their API!
+ echo "Sleeping for 5s...\n";
+ sleep(5);
+ } while ($response->getHasMore());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/paginationExample.php b/vendor/mgp25/instagram-php/examples/paginationExample.php
new file mode 100755
index 0000000..0dd3f56
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/paginationExample.php
@@ -0,0 +1,53 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Get the UserPK ID for "natgeo" (National Geographic).
+ $userId = $ig->people->getUserIdForName('natgeo');
+
+ // Starting at "null" means starting at the first page.
+ $maxId = null;
+ do {
+ // Request the page corresponding to maxId.
+ $response = $ig->timeline->getUserFeed($userId, $maxId);
+
+ // In this example we're simply printing the IDs of this page's items.
+ foreach ($response->getItems() as $item) {
+ printf("[%s] https://instagram.com/p/%s/\n", $item->getId(), $item->getCode());
+ }
+
+ // Now we must update the maxId variable to the "next page".
+ // This will be a null value again when we've reached the last page!
+ // And we will stop looping through pages as soon as maxId becomes null.
+ $maxId = $response->getNextMaxId();
+
+ // Sleep for 5 seconds before requesting the next page. This is just an
+ // example of an okay sleep time. It is very important that your scripts
+ // always pause between requests that may run very rapidly, otherwise
+ // Instagram will throttle you temporarily for abusing their API!
+ echo "Sleeping for 5s...\n";
+ sleep(5);
+ } while ($maxId !== null); // Must use "!==" for comparison instead of "!=".
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/pushReceiver.php b/vendor/mgp25/instagram-php/examples/pushReceiver.php
new file mode 100755
index 0000000..4bf8980
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/pushReceiver.php
@@ -0,0 +1,80 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+$loop = \React\EventLoop\Factory::create();
+if ($debug) {
+ $logger = new \Monolog\Logger('push');
+ $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+} else {
+ $logger = null;
+}
+$push = new \InstagramAPI\Push($loop, $ig, $logger);
+$push->on('incoming', function (\InstagramAPI\Push\Notification $push) {
+ printf('%s%s', $push->getMessage(), PHP_EOL);
+});
+$push->on('like', function (\InstagramAPI\Push\Notification $push) {
+ printf('Media ID: %s%s', $push->getActionParam('id'), PHP_EOL);
+});
+$push->on('comment', function (\InstagramAPI\Push\Notification $push) {
+ switch ($push->getActionPath()) {
+ case 'comments_v2':
+ $mediaId = $push->getActionParam('media_id');
+ $commentId = $push->getActionParam('target_comment_id');
+ break;
+ case 'media':
+ default:
+ $mediaId = $push->getActionParam('id');
+ $commentId = $push->getActionParam('forced_preview_comment_id');
+ }
+ printf(
+ 'Media ID: %s. Comment ID: %s.%s',
+ $mediaId,
+ $commentId,
+ PHP_EOL
+ );
+});
+$push->on('direct_v2_message', function (\InstagramAPI\Push\Notification $push) {
+ printf(
+ 'Thread ID: %s. Thread item ID: %s.%s',
+ $push->getActionParam('id'),
+ $push->getActionParam('x'),
+ PHP_EOL
+ );
+});
+$push->on('error', function (\Exception $e) use ($push, $loop) {
+ printf('[!!!] Got fatal error from FBNS: %s%s', $e->getMessage(), PHP_EOL);
+ $push->stop();
+ $loop->stop();
+});
+$push->start();
+
+$loop->run();
diff --git a/vendor/mgp25/instagram-php/examples/rankTokenUsage.php b/vendor/mgp25/instagram-php/examples/rankTokenUsage.php
new file mode 100755
index 0000000..5321eee
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/rankTokenUsage.php
@@ -0,0 +1,57 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Let's list media for the "nature" hashtag.
+ $tag = 'nature';
+
+ // Generate a random rank token.
+ $rankToken = \InstagramAPI\Signatures::generateUUID();
+
+ // Starting at "null" means starting at the first page.
+ $maxId = null;
+ do {
+ // Request the page corresponding to maxId.
+ // Note that we are using the same rank token for all pages.
+ $response = $ig->hashtag->getFeed($tag, $rankToken, $maxId);
+
+ // In this example we're simply printing the IDs of this page's items.
+ foreach ($response->getItems() as $item) {
+ printf("[%s] https://instagram.com/p/%s/\n", $item->getId(), $item->getCode());
+ }
+
+ // Now we must update the maxId variable to the "next page".
+ // This will be a null value again when we've reached the last page!
+ // And we will stop looping through pages as soon as maxId becomes null.
+ $maxId = $response->getNextMaxId();
+
+ // Sleep for 5 seconds before requesting the next page. This is just an
+ // example of an okay sleep time. It is very important that your scripts
+ // always pause between requests that may run very rapidly, otherwise
+ // Instagram will throttle you temporarily for abusing their API!
+ echo "Sleeping for 5s...\n";
+ sleep(5);
+ } while ($maxId !== null); // Must use "!==" for comparison instead of "!=".
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/realtimeClient.php b/vendor/mgp25/instagram-php/examples/realtimeClient.php
new file mode 100755
index 0000000..5abdf06
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/realtimeClient.php
@@ -0,0 +1,100 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+$loop = \React\EventLoop\Factory::create();
+if ($debug) {
+ $logger = new \Monolog\Logger('rtc');
+ $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+} else {
+ $logger = null;
+}
+$rtc = new \InstagramAPI\Realtime($ig, $loop, $logger);
+$rtc->on('live-started', function (\InstagramAPI\Realtime\Payload\LiveBroadcast $live) {
+ printf('[RTC] Live broadcast %s has been started%s', $live->getBroadcastId(), PHP_EOL);
+});
+$rtc->on('live-stopped', function (\InstagramAPI\Realtime\Payload\LiveBroadcast $live) {
+ printf('[RTC] Live broadcast %s has been stopped%s', $live->getBroadcastId(), PHP_EOL);
+});
+$rtc->on('direct-story-created', function (\InstagramAPI\Response\Model\DirectThread $thread) {
+ printf('[RTC] Story %s has been created%s', $thread->getThreadId(), PHP_EOL);
+});
+$rtc->on('direct-story-updated', function ($threadId, $threadItemId, \InstagramAPI\Response\Model\DirectThreadItem $threadItem) {
+ printf('[RTC] Item %s has been created in story %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('direct-story-screenshot', function ($threadId, \InstagramAPI\Realtime\Payload\StoryScreenshot $screenshot) {
+ printf('[RTC] %s has taken screenshot of story %s%s', $screenshot->getActionUserDict()->getUsername(), $threadId, PHP_EOL);
+});
+$rtc->on('direct-story-action', function ($threadId, \InstagramAPI\Response\Model\ActionBadge $storyAction) {
+ printf('[RTC] Story in thread %s has badge %s now%s', $threadId, $storyAction->getActionType(), PHP_EOL);
+});
+$rtc->on('thread-created', function ($threadId, \InstagramAPI\Response\Model\DirectThread $thread) {
+ printf('[RTC] Thread %s has been created%s', $threadId, PHP_EOL);
+});
+$rtc->on('thread-updated', function ($threadId, \InstagramAPI\Response\Model\DirectThread $thread) {
+ printf('[RTC] Thread %s has been updated%s', $threadId, PHP_EOL);
+});
+$rtc->on('thread-notify', function ($threadId, $threadItemId, \InstagramAPI\Realtime\Payload\ThreadAction $notify) {
+ printf('[RTC] Thread %s has notification from %s%s', $threadId, $notify->getUserId(), PHP_EOL);
+});
+$rtc->on('thread-seen', function ($threadId, $userId, \InstagramAPI\Response\Model\DirectThreadLastSeenAt $seenAt) {
+ printf('[RTC] Thread %s has been checked by %s%s', $threadId, $userId, PHP_EOL);
+});
+$rtc->on('thread-activity', function ($threadId, \InstagramAPI\Realtime\Payload\ThreadActivity $activity) {
+ printf('[RTC] Thread %s has some activity made by %s%s', $threadId, $activity->getSenderId(), PHP_EOL);
+});
+$rtc->on('thread-item-created', function ($threadId, $threadItemId, \InstagramAPI\Response\Model\DirectThreadItem $threadItem) {
+ printf('[RTC] Item %s has been created in thread %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('thread-item-updated', function ($threadId, $threadItemId, \InstagramAPI\Response\Model\DirectThreadItem $threadItem) {
+ printf('[RTC] Item %s has been updated in thread %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('thread-item-removed', function ($threadId, $threadItemId) {
+ printf('[RTC] Item %s has been removed from thread %s%s', $threadItemId, $threadId, PHP_EOL);
+});
+$rtc->on('client-context-ack', function (\InstagramAPI\Realtime\Payload\Action\AckAction $ack) {
+ printf('[RTC] Received ACK for %s with status %s%s', $ack->getPayload()->getClientContext(), $ack->getStatus(), PHP_EOL);
+});
+$rtc->on('unseen-count-update', function ($inbox, \InstagramAPI\Response\Model\DirectSeenItemPayload $payload) {
+ printf('[RTC] Updating unseen count in %s to %d%s', $inbox, $payload->getCount(), PHP_EOL);
+});
+$rtc->on('presence', function (\InstagramAPI\Response\Model\UserPresence $presence) {
+ $action = $presence->getIsActive() ? 'is now using' : 'just closed';
+ printf('[RTC] User %s %s one of Instagram apps%s', $presence->getUserId(), $action, PHP_EOL);
+});
+$rtc->on('error', function (\Exception $e) use ($rtc, $loop) {
+ printf('[!!!] Got fatal error from Realtime: %s%s', $e->getMessage(), PHP_EOL);
+ $rtc->stop();
+ $loop->stop();
+});
+$rtc->start();
+
+$loop->run();
diff --git a/vendor/mgp25/instagram-php/examples/realtimeHttp.php b/vendor/mgp25/instagram-php/examples/realtimeHttp.php
new file mode 100755
index 0000000..2478262
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/realtimeHttp.php
@@ -0,0 +1,287 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// Create main event loop.
+$loop = \React\EventLoop\Factory::create();
+if ($debug) {
+ $logger = new \Monolog\Logger('rtc');
+ $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+} else {
+ $logger = null;
+}
+// Create HTTP server along with Realtime client.
+$httpServer = new RealtimeHttpServer($loop, $ig, $logger);
+// Run main loop.
+$loop->run();
+
+class RealtimeHttpServer
+{
+ const HOST = '127.0.0.1';
+ const PORT = 1307;
+
+ const TIMEOUT = 5;
+
+ /** @var \React\Promise\Deferred[] */
+ protected $_contexts;
+
+ /** @var \React\EventLoop\LoopInterface */
+ protected $_loop;
+
+ /** @var \InstagramAPI\Instagram */
+ protected $_instagram;
+
+ /** @var \InstagramAPI\Realtime */
+ protected $_rtc;
+
+ /** @var \React\Http\Server */
+ protected $_server;
+
+ /** @var \Psr\Log\LoggerInterface */
+ protected $_logger;
+
+ /**
+ * Constructor.
+ *
+ * @param \React\EventLoop\LoopInterface $loop
+ * @param \InstagramAPI\Instagram $instagram
+ * @param \Psr\Log\LoggerInterface|null $logger
+ */
+ public function __construct(
+ \React\EventLoop\LoopInterface $loop,
+ \InstagramAPI\Instagram $instagram,
+ \Psr\Log\LoggerInterface $logger = null)
+ {
+ $this->_loop = $loop;
+ $this->_instagram = $instagram;
+ if ($logger === null) {
+ $logger = new \Psr\Log\NullLogger();
+ }
+ $this->_logger = $logger;
+ $this->_contexts = [];
+ $this->_rtc = new \InstagramAPI\Realtime($this->_instagram, $this->_loop, $this->_logger);
+ $this->_rtc->on('client-context-ack', [$this, 'onClientContextAck']);
+ $this->_rtc->on('error', [$this, 'onRealtimeFail']);
+ $this->_rtc->start();
+ $this->_startHttpServer();
+ }
+
+ /**
+ * Gracefully stop everything.
+ */
+ protected function _stop()
+ {
+ // Initiate shutdown sequence.
+ $this->_rtc->stop();
+ // Wait 2 seconds for Realtime to shutdown.
+ $this->_loop->addTimer(2, function () {
+ // Stop main loop.
+ $this->_loop->stop();
+ });
+ }
+
+ /**
+ * Called when fatal error has been received from Realtime.
+ *
+ * @param \Exception $e
+ */
+ public function onRealtimeFail(
+ \Exception $e)
+ {
+ $this->_logger->error((string) $e);
+ $this->_stop();
+ }
+
+ /**
+ * Called when ACK has been received.
+ *
+ * @param \InstagramAPI\Realtime\Payload\Action\AckAction $ack
+ */
+ public function onClientContextAck(
+ \InstagramAPI\Realtime\Payload\Action\AckAction $ack)
+ {
+ $context = $ack->getPayload()->getClientContext();
+ $this->_logger->info(sprintf('Received ACK for %s with status %s', $context, $ack->getStatus()));
+ // Check if we have deferred object for this client_context.
+ if (!isset($this->_contexts[$context])) {
+ return;
+ }
+ // Resolve deferred object with $ack.
+ $deferred = $this->_contexts[$context];
+ $deferred->resolve($ack);
+ // Clean up.
+ unset($this->_contexts[$context]);
+ }
+
+ /**
+ * @param string|bool $context
+ *
+ * @return \React\Http\Response|\React\Promise\PromiseInterface
+ */
+ protected function _handleClientContext(
+ $context)
+ {
+ // Reply with 503 Service Unavailable.
+ if ($context === false) {
+ return new \React\Http\Response(503);
+ }
+ // Set up deferred object.
+ $deferred = new \React\Promise\Deferred();
+ $this->_contexts[$context] = $deferred;
+ // Reject deferred after given timeout.
+ $timeout = $this->_loop->addTimer(self::TIMEOUT, function () use ($deferred, $context) {
+ $deferred->reject();
+ unset($this->_contexts[$context]);
+ });
+ // Set up promise.
+ return $deferred->promise()
+ ->then(function (\InstagramAPI\Realtime\Payload\Action\AckAction $ack) use ($timeout) {
+ // Cancel reject timer.
+ $timeout->cancel();
+ // Reply with info from $ack.
+ return new \React\Http\Response($ack->getStatusCode(), ['Content-Type' => 'text/json'], $ack->getPayload()->asJson());
+ })
+ ->otherwise(function () {
+ // Called by reject timer. Reply with 504 Gateway Time-out.
+ return new \React\Http\Response(504);
+ });
+ }
+
+ /**
+ * Handler for incoming HTTP requests.
+ *
+ * @param \Psr\Http\Message\ServerRequestInterface $request
+ *
+ * @return \React\Http\Response|\React\Promise\PromiseInterface
+ */
+ public function onHttpRequest(
+ \Psr\Http\Message\ServerRequestInterface $request)
+ {
+ // Treat request path as command.
+ $command = $request->getUri()->getPath();
+ // Params validation is up to you.
+ $params = $request->getQueryParams();
+ // Log command with its params.
+ $this->_logger->info(sprintf('Received command %s', $command), $params);
+ switch ($command) {
+ case '/ping':
+ return new \React\Http\Response(200, [], 'pong');
+ case '/stop':
+ $this->_stop();
+
+ return new \React\Http\Response(200);
+ case '/seen':
+ $context = $this->_rtc->markDirectItemSeen($params['threadId'], $params['threadItemId']);
+
+ return new \React\Http\Response($context !== false ? 200 : 503);
+ case '/activity':
+ return $this->_handleClientContext($this->_rtc->indicateActivityInDirectThread($params['threadId'], (bool) $params['flag']));
+ case '/message':
+ return $this->_handleClientContext($this->_rtc->sendTextToDirect($params['threadId'], $params['text']));
+ case '/post':
+ return $this->_handleClientContext($this->_rtc->sendPostToDirect($params['threadId'], $params['mediaId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/story':
+ return $this->_handleClientContext($this->_rtc->sendStoryToDirect($params['threadId'], $params['storyId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/profile':
+ return $this->_handleClientContext($this->_rtc->sendProfileToDirect($params['threadId'], $params['userId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/location':
+ return $this->_handleClientContext($this->_rtc->sendLocationToDirect($params['threadId'], $params['locationId'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/hashtag':
+ return $this->_handleClientContext($this->_rtc->sendHashtagToDirect($params['threadId'], $params['hashtag'], [
+ 'text' => isset($params['text']) ? $params['text'] : null,
+ ]));
+ case '/like':
+ return $this->_handleClientContext($this->_rtc->sendLikeToDirect($params['threadId']));
+ case '/likeItem':
+ return $this->_handleClientContext($this->_rtc->sendReactionToDirect($params['threadId'], $params['threadItemId'], 'like'));
+ case '/unlikeItem':
+ return $this->_handleClientContext($this->_rtc->deleteReactionFromDirect($params['threadId'], $params['threadItemId'], 'like'));
+ default:
+ $this->_logger->warning(sprintf('Unknown command %s', $command), $params);
+ // If command is unknown, reply with 404 Not Found.
+ return new \React\Http\Response(404);
+ }
+ }
+
+ /**
+ * Init and start HTTP server.
+ */
+ protected function _startHttpServer()
+ {
+ // Create server socket.
+ $socket = new \React\Socket\Server(self::HOST.':'.self::PORT, $this->_loop);
+ $this->_logger->info(sprintf('Listening on http://%s', $socket->getAddress()));
+ // Bind HTTP server on server socket.
+ $this->_server = new \React\Http\Server([$this, 'onHttpRequest']);
+ $this->_server->listen($socket);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/examples/sharePhotoToStoryFeed.php b/vendor/mgp25/instagram-php/examples/sharePhotoToStoryFeed.php
new file mode 100755
index 0000000..6ace2a4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/sharePhotoToStoryFeed.php
@@ -0,0 +1,71 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// NOTE: This code will make the credits of the media area 'clickable', but YOU need to
+// manually draw the credit to the user or a sticker-image on top of your image yourself
+// before uploading, if you want the credit to actually be visible on-screen!
+
+// If we want to attach a media, we must find a valid media_id first.
+try {
+ $mediaInfo = $ig->media->getInfo($mediaId)->getItems()[0];
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
+
+// Now create the metadata array:
+$metadata = [
+ 'attached_media' => [
+ [
+ 'media_id' => $mediaId,
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'width' => 0.8, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.6224662, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+$client = new GuzzleHttp\Client();
+$outputFile = Utils::createTempFile(sys_get_temp_dir(), 'IMG');
+
+try {
+ $response = $client->request('GET', $mediaInfo->getImageVersions2()->getCandidates()[0]->getUrl(), ['sink' => $outputFile]);
+
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories.
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($outputFile, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+} finally {
+ @unlink($outputFile);
+}
diff --git a/vendor/mgp25/instagram-php/examples/shortcodeConverter.php b/vendor/mgp25/instagram-php/examples/shortcodeConverter.php
new file mode 100755
index 0000000..b700109
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/shortcodeConverter.php
@@ -0,0 +1,29 @@
+ code({$code})\n\n";
+
+// From Shortcode to Media ID.
+$id = InstagramID::fromCode($code);
+echo "Instagram code({$code}) -> id({$id})\n\n";
diff --git a/vendor/mgp25/instagram-php/examples/twoFactorLogin.php b/vendor/mgp25/instagram-php/examples/twoFactorLogin.php
new file mode 100755
index 0000000..6055ec7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/twoFactorLogin.php
@@ -0,0 +1,31 @@
+login($username, $password);
+
+ if ($loginResponse !== null && $loginResponse->isTwoFactorRequired()) {
+ $twoFactorIdentifier = $loginResponse->getTwoFactorInfo()->getTwoFactorIdentifier();
+
+ // The "STDIN" lets you paste the code via terminal for testing.
+ // You should replace this line with the logic you want.
+ // The verification code will be sent by Instagram via SMS.
+ $verificationCode = trim(fgets(STDIN));
+ $ig->finishTwoFactorLogin($username, $password, $twoFactorIdentifier, $verificationCode);
+ }
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/uploadAlbum.php b/vendor/mgp25/instagram-php/examples/uploadAlbum.php
new file mode 100755
index 0000000..12872d2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/uploadAlbum.php
@@ -0,0 +1,109 @@
+ 'photo',
+ 'file' => '', // Path to the photo file.
+ ],
+ [
+ 'type' => 'photo',
+ 'file' => '', // Path to the photo file.
+ /* TAGS COMMENTED OUT UNTIL YOU READ THE BELOW:
+ 'usertags' => [ // Optional, lets you tag one or more users in a PHOTO.
+ [
+ 'position' => [0.5, 0.5],
+
+ // WARNING: THE USER ID MUST BE VALID. INSTAGRAM WILL VERIFY IT
+ // AND IF IT'S WRONG THEY WILL SAY "media configure error".
+ 'user_id' => '123456789', // Must be a numerical UserPK ID.
+ ],
+ ],
+ */
+ ],
+ [
+ 'type' => 'video',
+ 'file' => '', // Path to the video file.
+ 'thumbnail_timestamp' => '', // Timestamp of thumbnail
+ ],
+];
+$captionText = ''; // Caption to use for the album.
+//////////////////////
+
+$ig = new \InstagramAPI\Instagram($debug, $truncatedDebug);
+
+try {
+ $ig->login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+////// NORMALIZE MEDIA //////
+// All album files must have the same aspect ratio.
+// We copy the app's behavior by using the first file
+// as template for all subsequent ones.
+$mediaOptions = [
+ 'targetFeed' => \InstagramAPI\Constants::FEED_TIMELINE_ALBUM,
+ // Uncomment to expand media instead of cropping it.
+ //'operation' => \InstagramAPI\Media\InstagramMedia::EXPAND,
+];
+foreach ($media as &$item) {
+ /** @var \InstagramAPI\Media\InstagramMedia|null $validMedia */
+ $validMedia = null;
+ switch ($item['type']) {
+ case 'photo':
+ $validMedia = new \InstagramAPI\Media\Photo\InstagramPhoto($item['file'], $mediaOptions);
+ break;
+ case 'video':
+ $validMedia = new \InstagramAPI\Media\Video\InstagramVideo($item['file'], $mediaOptions);
+ break;
+ default:
+ // Ignore unknown media type.
+ }
+ if ($validMedia === null) {
+ continue;
+ }
+
+ try {
+ $item['file'] = $validMedia->getFile();
+ // We must prevent the InstagramMedia object from destructing too early,
+ // because the media class auto-deletes the processed file during their
+ // destructor's cleanup (so we wouldn't be able to upload those files).
+ $item['__media'] = $validMedia; // Save object in an unused array key.
+ } catch (\Exception $e) {
+ continue;
+ }
+ if (!isset($mediaOptions['forceAspectRatio'])) {
+ // Use the first media file's aspect ratio for all subsequent files.
+ /** @var \InstagramAPI\Media\MediaDetails $mediaDetails */
+ $mediaDetails = $validMedia instanceof \InstagramAPI\Media\Photo\InstagramPhoto
+ ? new \InstagramAPI\Media\Photo\PhotoDetails($item['file'])
+ : new \InstagramAPI\Media\Video\VideoDetails($item['file']);
+ $mediaOptions['forceAspectRatio'] = $mediaDetails->getAspectRatio();
+ }
+}
+unset($item);
+/////////////////////////////
+
+try {
+ $ig->timeline->uploadAlbum($media, ['caption' => $captionText]);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/uploadPhoto.php b/vendor/mgp25/instagram-php/examples/uploadPhoto.php
new file mode 100755
index 0000000..954d960
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/uploadPhoto.php
@@ -0,0 +1,62 @@
+setProxy("http://$proxyUser:$proxyPassword@$proxyIP:$proxyPort"); // SOCKS5 Proxy needing authentication
+try {
+ $ig->login($username, $password);
+} catch (\Exception $e) {
+ echo 'Error: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // The most basic upload command, if you're sure that your photo file is
+ // valid on Instagram (that it fits all requirements), is the following:
+ // $ig->timeline->uploadPhoto($photoFilename, ['caption' => $captionText]);
+
+ // However, if you want to guarantee that the file is valid (correct format,
+ // width, height and aspect ratio), then you can run it through our
+ // automatic photo processing class. It is pretty fast, and only does any
+ // work when the input file is invalid, so you may want to always use it.
+ // You have nothing to worry about, since the class uses temporary files if
+ // the input needs processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename);
+ $ig->timeline->uploadPhoto($photo->getFile(), ['caption' => $captionText]);
+} catch (\Exception $e) {
+ echo 'Error: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/uploadStory.php b/vendor/mgp25/instagram-php/examples/uploadStory.php
new file mode 100755
index 0000000..4eb24a6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/uploadStory.php
@@ -0,0 +1,102 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// You don't have to provide hashtags or locations for your story. It is
+// optional! But we will show you how to do both...
+
+// NOTE: This code will make the hashtag area 'clickable', but YOU need to
+// manually draw the hashtag or a sticker-image on top of your image yourself
+// before uploading, if you want the tag to actually be visible on-screen!
+
+// NOTE: The same thing happens when a location sticker is added. And the
+// "location_sticker" WILL ONLY work if you also add the "location" as shown
+// below.
+
+// NOTE: And "caption" will NOT be visible either! Like all the other story
+// metadata described above, YOU must manually draw the caption on your image.
+
+// If we want to attach a location, we must find a valid Location object first:
+try {
+ $location = $ig->location->search('40.7439862', '-73.998511')->getVenues()[0];
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
+
+// Now create the metadata array:
+$metadata = [
+ // (optional) Captions can always be used, like this:
+ 'caption' => '#test This is a great API!',
+
+ // (optional) To add a hashtag, do this:
+ 'hashtags' => [
+ // Note that you can add more than one hashtag in this array.
+ [
+ 'tag_name' => 'test', // Hashtag WITHOUT the '#'! NOTE: This hashtag MUST appear in the caption.
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'width' => 0.24305555, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.07347973, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => false, // Don't change this value.
+ 'use_custom_title' => false, // Don't change this value.
+ ],
+ // ...
+ ],
+
+ // (optional) To add a location, do BOTH of these:
+ 'location_sticker' => [
+ 'width' => 0.89333333333333331,
+ 'height' => 0.071281859070464776,
+ 'x' => 0.5,
+ 'y' => 0.2,
+ 'rotation' => 0.0,
+ 'is_sticker' => true,
+ 'location_id' => $location->getExternalId(),
+ ],
+ 'location' => $location,
+
+ // (optional) You can use story links ONLY if you have a business account with >= 10k followers.
+ // 'link' => 'https://github.com/mgp25/Instagram-API',
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/uploadStoryCountdown.php b/vendor/mgp25/instagram-php/examples/uploadStoryCountdown.php
new file mode 100755
index 0000000..251a70f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/uploadStoryCountdown.php
@@ -0,0 +1,67 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// Now create the metadata array:
+$metadata = [
+ 'story_countdowns' => [
+ [
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'z' => 0, // Don't change this value.
+ 'width' => 0.6564815, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.22630993, // ...
+ 'rotation' => 0.0,
+ 'text' => 'NEXT API RELEASE', // Name of the countdown. Make sure it's in all caps!
+ 'text_color' => '#ffffff',
+ 'start_background_color' => '#ca2ee1',
+ 'end_background_color' => '#5eb1ff',
+ 'digit_color' => '#7e0091',
+ 'digit_card_color' => '#ffffff',
+ 'end_ts' => time() + strtotime('1 day', 0), // UNIX Epoch of when the countdown expires.
+ 'following_enabled' => true, // If true, viewers can subscribe to a notification when the countdown expires.
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/uploadStoryFundraiser.php b/vendor/mgp25/instagram-php/examples/uploadStoryFundraiser.php
new file mode 100755
index 0000000..2562453
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/uploadStoryFundraiser.php
@@ -0,0 +1,54 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+//Get the first recommended charity.
+$charityUser = $ig->story->getCharities()->getSearchedCharities()[0];
+
+// Now create the metadata array:
+$metadata = [
+ 'story_fundraisers' => $ig->story->createDonateSticker($charityUser),
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/uploadStoryPoll.php b/vendor/mgp25/instagram-php/examples/uploadStoryPoll.php
new file mode 100755
index 0000000..eb348dd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/uploadStoryPoll.php
@@ -0,0 +1,78 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// NOTE: This code will make a story poll sticker with the two answers provided,
+// but YOU need to manually draw the question on top of your image yourself
+// before uploading if you want the question to be visible.
+
+// Now create the metadata array:
+$metadata = [
+ 'story_polls' => [
+ // Note that you can only do one story poll in this array.
+ [
+ 'question' => 'Is this API great?', // Story poll question. You need to manually to draw it on top of your image.
+ 'viewer_vote' => 0, // Don't change this value.
+ 'viewer_can_vote' => true, // Don't change this value.
+ 'tallies' => [
+ [
+ 'text' => 'Best API!', // Answer 1.
+ 'count' => 0, // Don't change this value.
+ 'font_size' => 35.0, // Range: 17.5 - 35.0.
+ ],
+ [
+ 'text' => 'The doubt offends', // Answer 2.
+ 'count' => 0, // Don't change this value.
+ 'font_size' => 27.5, // Range: 17.5 - 35.0.
+ ],
+ ],
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'width' => 0.5661107, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.10647108, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/uploadStoryQuestion.php b/vendor/mgp25/instagram-php/examples/uploadStoryQuestion.php
new file mode 100755
index 0000000..ac84854
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/uploadStoryQuestion.php
@@ -0,0 +1,69 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// Get the profile picture pic url to display on the question.
+$profilePicUrl = $ig->account->getCurrentUser()->getUser()->getProfilePicUrl();
+
+// Now create the metadata array:
+$metadata = [
+ 'story_questions' => [
+ // Note that you can only do one story question in this array.
+ [
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5004223, // Also note that X/Y is setting the position of the CENTER of the clickable area.
+ 'z' => 0, // Don't change this value.
+ 'width' => 0.63118356, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.22212838, // ...
+ 'rotation' => 0.0,
+ 'viewer_can_interact' => false, // Don't change this value.
+ 'background_color' => '#ffffff',
+ 'profile_pic_url' => $profilePicUrl, // Must be the profile pic url of the account you are posting from!
+ 'question_type' => 'text', // Don't change this value.
+ 'question' => 'What do you want to see in the API?', // Story question.
+ 'text_color' => '#000000',
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/uploadStorySlider.php b/vendor/mgp25/instagram-php/examples/uploadStorySlider.php
new file mode 100755
index 0000000..8465b58
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/uploadStorySlider.php
@@ -0,0 +1,71 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+// NOTE: This code will make a story poll sticker with the two answers provided,
+// but YOU need to manually draw the question on top of your image yourself
+// before uploading if you want the question to be visible.
+
+// Now create the metadata array:
+$metadata = [
+ 'story_sliders' => [
+ // Note that you can only do one story poll in this array.
+ [
+ 'question' => 'Is this API great?', // Story poll question. You need to manually to draw it on top of your image.
+ 'viewer_vote' => 0, // Don't change this value.
+ 'viewer_can_vote' => false, // Don't change this value.
+ 'slider_vote_count' => 0, // Don't change this value.
+ 'slider_vote_average' => 0, // Don't change this value.
+ 'background_color' => '#ffffff',
+ 'text_color' => '#000000',
+ 'emoji' => '😍',
+ 'x' => 0.5, // Range: 0.0 - 1.0. Note that x = 0.5 and y = 0.5 is center of screen.
+ 'y' => 0.5004223, // Also note that X/Y is setting the position of the CENTER of the clickable area
+ 'width' => 0.7777778, // Clickable area size, as percentage of image size: 0.0 - 1.0
+ 'height' => 0.22212838, // ...
+ 'rotation' => 0.0,
+ 'is_sticker' => true, // Don't change this value.
+ ],
+ ],
+];
+
+try {
+ // This example will upload the image via our automatic photo processing
+ // class. It will ensure that the story file matches the ~9:16 (portrait)
+ // aspect ratio needed by Instagram stories. You have nothing to worry
+ // about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $photo = new \InstagramAPI\Media\Photo\InstagramPhoto($photoFilename, ['targetFeed' => \InstagramAPI\Constants::FEED_STORY]);
+ $ig->story->uploadPhoto($photo->getFile(), $metadata);
+
+ // NOTE: Providing metadata for story uploads is OPTIONAL. If you just want
+ // to upload it without any tags/location/caption, simply do the following:
+ // $ig->story->uploadPhoto($photo->getFile());
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/examples/uploadVideo.php b/vendor/mgp25/instagram-php/examples/uploadVideo.php
new file mode 100755
index 0000000..b25fc75
--- /dev/null
+++ b/vendor/mgp25/instagram-php/examples/uploadVideo.php
@@ -0,0 +1,46 @@
+login($username, $password);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+ exit(0);
+}
+
+try {
+ // Note that all video upload functions perform some automatic chunk upload
+ // retries, in case of failing to upload all video chunks to Instagram's
+ // server! Uploads therefore take longer when their server is overloaded.
+
+ // If you want to guarantee that the file is valid (correct format, codecs,
+ // width, height and aspect ratio), then you can run it through our
+ // automatic video processing class. It only does any work when the input
+ // video file is invalid, so you may want to always use it. You have nothing
+ // to worry about, since the class uses temporary files if the input needs
+ // processing, and it never overwrites your original file.
+ //
+ // Also note that it has lots of options, so read its class documentation!
+ $video = new \InstagramAPI\Media\Video\InstagramVideo($videoFilename);
+ $ig->timeline->uploadVideo($video->getFile(), ['caption' => $captionText]);
+} catch (\Exception $e) {
+ echo 'Something went wrong: '.$e->getMessage()."\n";
+}
diff --git a/vendor/mgp25/instagram-php/extradocs/Push_setPreferences.txt b/vendor/mgp25/instagram-php/extradocs/Push_setPreferences.txt
new file mode 100755
index 0000000..3a14ed4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/extradocs/Push_setPreferences.txt
@@ -0,0 +1,90 @@
+===================
+Push::setPreferences
+===================
+
+Description
+------------
+Set push preferences.
+
+Params
+------------
+param array $preferences.
+
+Extra Documentation
+--------------------
+
+Each preference can be set either to 'Off', 'Followings' or 'Everyone'/'Enabled'.
+In this context:
+
+'Off' is 1.
+'Followings' is 2.
+'Everyone' is 3.
+
+** Warning **
+
+You can only set preferences if you are eligible for them. See Push::getPreferences()
+to obtain the values for all of your available preferences.
+
+Preferences and possible options
+---------------------------------
+
+Name: likes.
+Options: 1, 2, 3.
+
+Name: comments.
+Options: 1, 2, 3.
+
+Name: comment_likes.
+Options: 1, 2.
+
+Name: like_and_comment_on_photo_user_tagged.
+Options: 1, 2, 3.
+
+Name: live_broadcast.
+Options: 1, 3.
+
+Name: new_follower.
+Options: 1, 3.
+
+Name: follow_request_accepted.
+Options: 1, 3.
+
+Name: contact_joined.
+Options: 1, 3.
+
+Name: pending_direct_share.
+Options: 1, 3.
+
+Name: direct_share_activity.
+Options: 1, 3.
+
+Name: user_tagged.
+Options: 1, 2, 3.
+
+Name: notification_reminders.
+Options: 1, 3.
+
+Name: first_post.
+Options: 1, 2, 3.
+
+Name: announcements.
+Options: 1, 3.
+
+Name: ads.
+Options: 1, 3.
+
+Name: view_count.
+Options: 1, 3.
+
+Name: report_updated.
+Options: 1, 3.
+
+Example
+---------
+
+$preferences = [
+ 'likes' => 3,
+ 'comment_likes' => 2
+ ];
+
+$ig->push->setPreferences($preferences);
diff --git a/vendor/mgp25/instagram-php/src/AutoPropertyMapper.php b/vendor/mgp25/instagram-php/src/AutoPropertyMapper.php
new file mode 100755
index 0000000..9461f25
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/AutoPropertyMapper.php
@@ -0,0 +1,22 @@
+_parent = $parent;
+
+ // Defaults.
+ $this->_verifySSL = true;
+ $this->_proxy = null;
+
+ // Create a default handler stack with Guzzle's auto-selected "best
+ // possible transfer handler for the user's system", and with all of
+ // Guzzle's default middleware (cookie jar support, etc).
+ $stack = HandlerStack::create();
+
+ // Create our cookies middleware and add it to the stack.
+ $this->_fakeCookies = new FakeCookies();
+ $stack->push($this->_fakeCookies, 'fake_cookies');
+
+ $this->_zeroRating = new ZeroRating();
+ $stack->push($this->_zeroRating, 'zero_rewrite');
+
+ // Default request options (immutable after client creation).
+ $this->_guzzleClient = new GuzzleClient([
+ 'handler' => $stack, // Our middleware is now injected.
+ 'allow_redirects' => [
+ 'max' => 8, // Allow up to eight redirects (that's plenty).
+ ],
+ 'connect_timeout' => 30.0, // Give up trying to connect after 30s.
+ 'decode_content' => true, // Decode gzip/deflate/etc HTTP responses.
+ 'timeout' => 240.0, // Maximum per-request time (seconds).
+ // Tells Guzzle to stop throwing exceptions on non-"2xx" HTTP codes,
+ // thus ensuring that it only triggers exceptions on socket errors!
+ // We'll instead MANUALLY be throwing on certain other HTTP codes.
+ 'http_errors' => false,
+ ]);
+
+ $this->_resetConnection = false;
+ }
+
+ /**
+ * Resets certain Client settings via the current Settings storage.
+ *
+ * Used whenever we switch active user, to configure our internal state.
+ *
+ * @param bool $resetCookieJar (optional) Whether to clear current cookies.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function updateFromCurrentSettings(
+ $resetCookieJar = false)
+ {
+ // Update our internal client state from the new user's settings.
+ $this->_userAgent = $this->_parent->device->getUserAgent();
+ $this->loadCookieJar($resetCookieJar);
+
+ // Verify that the jar contains a non-expired csrftoken for the API
+ // domain. Instagram gives us a 1-year csrftoken whenever we log in.
+ // If it's missing, we're definitely NOT logged in! But even if all of
+ // these checks succeed, the cookie may still not be valid. It's just a
+ // preliminary check to detect definitely-invalid session cookies!
+ if ($this->getToken() === null) {
+ $this->_parent->isMaybeLoggedIn = false;
+ }
+
+ // Load rewrite rules (if any).
+ $this->zeroRating()->update($this->_parent->settings->getRewriteRules());
+ }
+
+ /**
+ * Loads all cookies via the current Settings storage.
+ *
+ * @param bool $resetCookieJar (optional) Whether to clear current cookies.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function loadCookieJar(
+ $resetCookieJar = false)
+ {
+ // Mark any previous cookie jar for garbage collection.
+ $this->_cookieJar = null;
+
+ // Delete all current cookies from the storage if this is a reset.
+ if ($resetCookieJar) {
+ $this->_parent->settings->setCookies('');
+ }
+
+ // Get all cookies for the currently active user.
+ $cookieData = $this->_parent->settings->getCookies();
+
+ // Attempt to restore the cookies, otherwise create a new, empty jar.
+ $restoredCookies = is_string($cookieData) ? @json_decode($cookieData, true) : null;
+ if (!is_array($restoredCookies)) {
+ $restoredCookies = [];
+ }
+
+ // Memory-based cookie jar which must be manually saved later.
+ $this->_cookieJar = new CookieJar(false, $restoredCookies);
+
+ // Reset the "last saved" timestamp to the current time to prevent
+ // auto-saving the cookies again immediately after this jar is loaded.
+ $this->_cookieJarLastSaved = time();
+ }
+
+ /**
+ * Retrieve the CSRF token from the current cookie jar.
+ *
+ * Note that Instagram gives you a 1-year token expiration timestamp when
+ * you log in. But if you log out, they set its timestamp to "0" which means
+ * that the cookie is "expired" and invalid. We ignore token cookies if they
+ * have been logged out, or if they have expired naturally.
+ *
+ * @return string|null The token if found and non-expired, otherwise NULL.
+ */
+ public function getToken()
+ {
+ $cookie = $this->getCookie('csrftoken', 'i.instagram.com');
+ if ($cookie === null || $cookie->getValue() === '') {
+ return null;
+ }
+
+ return $cookie->getValue();
+ }
+
+ /**
+ * Searches for a specific cookie in the current jar.
+ *
+ * @param string $name The name of the cookie.
+ * @param string|null $domain (optional) Require a specific domain match.
+ * @param string|null $path (optional) Require a specific path match.
+ *
+ * @return \GuzzleHttp\Cookie\SetCookie|null A cookie if found and non-expired, otherwise NULL.
+ */
+ public function getCookie(
+ $name,
+ $domain = null,
+ $path = null)
+ {
+ $foundCookie = null;
+ if ($this->_cookieJar instanceof CookieJar) {
+ /** @var SetCookie $cookie */
+ foreach ($this->_cookieJar->getIterator() as $cookie) {
+ if ($cookie->getName() === $name
+ && !$cookie->isExpired()
+ && ($domain === null || $cookie->matchesDomain($domain))
+ && ($path === null || $cookie->matchesPath($path))) {
+ // Loop-"break" is omitted intentionally, because we might
+ // have more than one cookie with the same name, so we will
+ // return the LAST one. This is necessary because Instagram
+ // has changed their cookie domain from `i.instagram.com` to
+ // `.instagram.com` and we want the *most recent* cookie.
+ // Guzzle's `CookieJar::setCookie()` always places the most
+ // recently added/modified cookies at the *end* of array.
+ $foundCookie = $cookie;
+ }
+ }
+ }
+
+ return $foundCookie;
+ }
+
+ /**
+ * Gives you all cookies in the Jar encoded as a JSON string.
+ *
+ * This allows custom Settings storages to retrieve all cookies for saving.
+ *
+ * @throws \InvalidArgumentException If the JSON cannot be encoded.
+ *
+ * @return string
+ */
+ public function getCookieJarAsJSON()
+ {
+ if (!$this->_cookieJar instanceof CookieJar) {
+ return '[]';
+ }
+
+ // Gets ALL cookies from the jar, even temporary session-based cookies.
+ $cookies = $this->_cookieJar->toArray();
+
+ // Throws if data can't be encoded as JSON (will never happen).
+ $jsonStr = \GuzzleHttp\json_encode($cookies);
+
+ return $jsonStr;
+ }
+
+ /**
+ * Tells current settings storage to store cookies if necessary.
+ *
+ * NOTE: This Client class is NOT responsible for calling this function!
+ * Instead, our parent "Instagram" instance takes care of it and saves the
+ * cookies "onCloseUser", so that cookies are written to storage in a
+ * single, efficient write when the user's session is finished. We also call
+ * it during some important function calls such as login/logout. Client also
+ * automatically calls it when enough time has elapsed since last save.
+ *
+ * @throws \InvalidArgumentException If the JSON cannot be encoded.
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function saveCookieJar()
+ {
+ // Tell the settings storage to persist the latest cookies.
+ $newCookies = $this->getCookieJarAsJSON();
+ $this->_parent->settings->setCookies($newCookies);
+
+ // Reset the "last saved" timestamp to the current time.
+ $this->_cookieJarLastSaved = time();
+ }
+
+ /**
+ * Controls the SSL verification behavior of the Client.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#verify
+ *
+ * @param bool|string $state TRUE to verify using PHP's default CA bundle,
+ * FALSE to disable SSL verification (this is
+ * insecure!), String to verify using this path to
+ * a custom CA bundle file.
+ */
+ public function setVerifySSL(
+ $state)
+ {
+ $this->_verifySSL = $state;
+ }
+
+ /**
+ * Gets the current SSL verification behavior of the Client.
+ *
+ * @return bool|string
+ */
+ public function getVerifySSL()
+ {
+ return $this->_verifySSL;
+ }
+
+ /**
+ * Set the proxy to use for requests.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#proxy
+ *
+ * @param string|array|null $value String or Array specifying a proxy in
+ * Guzzle format, or NULL to disable proxying.
+ */
+ public function setProxy(
+ $value)
+ {
+ $this->_proxy = $value;
+ $this->_resetConnection = true;
+ }
+
+ /**
+ * Gets the current proxy used for requests.
+ *
+ * @return string|array|null
+ */
+ public function getProxy()
+ {
+ return $this->_proxy;
+ }
+
+ /**
+ * Sets the network interface override to use.
+ *
+ * Only works if Guzzle is using the cURL backend. But that's
+ * almost always the case, on most PHP installations.
+ *
+ * @see http://php.net/curl_setopt CURLOPT_INTERFACE
+ *
+ * @param string|null $value Interface name, IP address or hostname, or NULL to
+ * disable override and let Guzzle use any interface.
+ */
+ public function setOutputInterface(
+ $value)
+ {
+ $this->_outputInterface = $value;
+ $this->_resetConnection = true;
+ }
+
+ /**
+ * Gets the current network interface override used for requests.
+ *
+ * @return string|null
+ */
+ public function getOutputInterface()
+ {
+ return $this->_outputInterface;
+ }
+
+ /**
+ * Output debugging information.
+ *
+ * @param string $method "GET" or "POST".
+ * @param string $url The URL or endpoint used for the request.
+ * @param string|null $uploadedBody What was sent to the server. Use NULL to
+ * avoid displaying it.
+ * @param int|null $uploadedBytes How many bytes were uploaded. Use NULL to
+ * avoid displaying it.
+ * @param HttpResponseInterface $response The Guzzle response object from the request.
+ * @param string $responseBody The actual text-body reply from the server.
+ */
+ protected function _printDebug(
+ $method,
+ $url,
+ $uploadedBody,
+ $uploadedBytes,
+ HttpResponseInterface $response,
+ $responseBody)
+ {
+ Debug::printRequest($method, $url);
+
+ // Display the data body that was uploaded, if provided for debugging.
+ // NOTE: Only provide this from functions that submit meaningful BODY data!
+ if (is_string($uploadedBody)) {
+ Debug::printPostData($uploadedBody);
+ }
+
+ // Display the number of bytes uploaded in the data body, if provided for debugging.
+ // NOTE: Only provide this from functions that actually upload files!
+ if ($uploadedBytes !== null) {
+ Debug::printUpload(Utils::formatBytes($uploadedBytes));
+ }
+
+ // Display the number of bytes received from the response, and status code.
+ if ($response->hasHeader('x-encoded-content-length')) {
+ $bytes = Utils::formatBytes((int) $response->getHeaderLine('x-encoded-content-length'));
+ } elseif ($response->hasHeader('Content-Length')) {
+ $bytes = Utils::formatBytes((int) $response->getHeaderLine('Content-Length'));
+ } else {
+ $bytes = 0;
+ }
+ Debug::printHttpCode($response->getStatusCode(), $bytes);
+
+ // Display the actual API response body.
+ Debug::printResponse($responseBody, $this->_parent->truncatedDebug);
+ }
+
+ /**
+ * Maps a server response onto a specific kind of result object.
+ *
+ * The result is placed directly inside `$responseObject`.
+ *
+ * @param Response $responseObject An instance of a class object whose
+ * properties to fill with the response.
+ * @param string $rawResponse A raw JSON response string
+ * from Instagram's server.
+ * @param HttpResponseInterface $httpResponse HTTP response object.
+ *
+ * @throws InstagramException In case of invalid or failed API response.
+ */
+ public function mapServerResponse(
+ Response $responseObject,
+ $rawResponse,
+ HttpResponseInterface $httpResponse)
+ {
+ // Attempt to decode the raw JSON to an array.
+ // Important: Special JSON decoder which handles 64-bit numbers!
+ $jsonArray = $this->api_body_decode($rawResponse, true);
+
+ // If the server response is not an array, it means that JSON decoding
+ // failed or some other bad thing happened. So analyze the HTTP status
+ // code (if available) to see what really happened.
+ if (!is_array($jsonArray)) {
+ $httpStatusCode = $httpResponse !== null ? $httpResponse->getStatusCode() : null;
+ switch ($httpStatusCode) {
+ case 400:
+ throw new \InstagramAPI\Exception\BadRequestException('Invalid request options.');
+ case 404:
+ throw new \InstagramAPI\Exception\NotFoundException('Requested resource does not exist.');
+ default:
+ throw new \InstagramAPI\Exception\EmptyResponseException('No response from server. Either a connection or configuration error.');
+ }
+ }
+
+ // Perform mapping of all response properties.
+ try {
+ // Assign the new object data. Only throws if custom _init() fails.
+ // NOTE: False = assign data without automatic analysis.
+ $responseObject->assignObjectData($jsonArray, false); // Throws.
+
+ // Use API developer debugging? We'll throw if class lacks property
+ // definitions, or if they can't be mapped as defined in the class
+ // property map. But we'll ignore missing properties in our custom
+ // UnpredictableKeys containers, since those ALWAYS lack keys. ;-)
+ if ($this->_parent->apiDeveloperDebug) {
+ // Perform manual analysis (so that we can intercept its analysis result).
+ $analysis = $responseObject->exportClassAnalysis(); // Never throws.
+
+ // Remove all "missing_definitions" errors for UnpredictableKeys containers.
+ // NOTE: We will keep any "bad_definitions" errors for them.
+ foreach ($analysis->missing_definitions as $className => $x) {
+ if (strpos($className, '\\Response\\Model\\UnpredictableKeys\\') !== false) {
+ unset($analysis->missing_definitions[$className]);
+ }
+ }
+
+ // If any problems remain after that, throw with all combined summaries.
+ if ($analysis->hasProblems()) {
+ throw new LazyJsonMapperException(
+ $analysis->generateNiceSummariesAsString()
+ );
+ }
+ }
+ } catch (LazyJsonMapperException $e) {
+ // Since there was a problem, let's help our developers by
+ // displaying the server's JSON data in a human-readable format,
+ // which makes it easy to see the structure and necessary changes
+ // and speeds up the job of updating responses and models.
+ try {
+ // Decode to stdClass to properly preserve empty objects `{}`,
+ // otherwise they would appear as empty `[]` arrays in output.
+ // NOTE: Large >32-bit numbers will be transformed into strings,
+ // which helps us see which numeric values need "string" type.
+ $jsonObject = $this->api_body_decode($rawResponse, false);
+ if (is_object($jsonObject)) {
+ $prettyJson = @json_encode(
+ $jsonObject,
+ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
+ );
+ if ($prettyJson !== false) {
+ Debug::printResponse(
+ 'Human-Readable Response:'.PHP_EOL.$prettyJson,
+ false // Not truncated.
+ );
+ }
+ }
+ } catch (\Exception $e) {
+ // Ignore errors.
+ }
+
+ // Exceptions will only be thrown if API developer debugging is
+ // enabled and finds a problem. Either way, we should re-wrap the
+ // exception to our native type instead. The message gives enough
+ // details and we don't need to know the exact Lazy sub-exception.
+ throw new InstagramException($e->getMessage());
+ }
+
+ // Save the HTTP response object as the "getHttpResponse()" value.
+ $responseObject->setHttpResponse($httpResponse);
+
+ // Throw an exception if the API response was unsuccessful.
+ // NOTE: It will contain the full server response object too, which
+ // means that the user can look at the full response details via the
+ // exception itself.
+ if (!$responseObject->isOk()) {
+ if ($responseObject instanceof \InstagramAPI\Response\DirectSendItemResponse && $responseObject->getPayload() !== null) {
+ $message = $responseObject->getPayload()->getMessage();
+ } else {
+ $message = $responseObject->getMessage();
+ }
+
+ try {
+ ServerMessageThrower::autoThrow(
+ get_class($responseObject),
+ $message,
+ $responseObject,
+ $httpResponse
+ );
+ } catch (LoginRequiredException $e) {
+ // Instagram told us that our session is invalid (that we are
+ // not logged in). Update our cached "logged in?" state. This
+ // ensures that users with various retry-algorithms won't hammer
+ // their server. When this flag is false, ALL further attempts
+ // at AUTHENTICATED requests will be aborted by our library.
+ $this->_parent->isMaybeLoggedIn = false;
+
+ throw $e; // Re-throw.
+ }
+ }
+ }
+
+ /**
+ * Helper which builds in the most important Guzzle options.
+ *
+ * Takes care of adding all critical options that we need on every request.
+ * Such as cookies and the user's proxy. But don't call this function
+ * manually. It's automatically called by _guzzleRequest()!
+ *
+ * @param array $guzzleOptions The options specific to the current request.
+ *
+ * @return array A guzzle options array.
+ */
+ protected function _buildGuzzleOptions(
+ array $guzzleOptions = [])
+ {
+ $criticalOptions = [
+ 'cookies' => ($this->_cookieJar instanceof CookieJar ? $this->_cookieJar : false),
+ 'verify' => $this->_verifySSL,
+ 'proxy' => ($this->_proxy !== null ? $this->_proxy : null),
+ ];
+
+ // Critical options always overwrite identical keys in regular opts.
+ // This ensures that we can't screw up the proxy/verify/cookies.
+ $finalOptions = array_merge($guzzleOptions, $criticalOptions);
+
+ // Now merge any specific Guzzle cURL-backend overrides. We must do this
+ // separately since it's in an associative array and we can't just
+ // overwrite that whole array in case the caller had curl options.
+ if (!array_key_exists('curl', $finalOptions)) {
+ $finalOptions['curl'] = [];
+ }
+
+ // Add their network interface override if they want it.
+ // This option MUST be non-empty if set, otherwise it breaks cURL.
+ if (is_string($this->_outputInterface) && $this->_outputInterface !== '') {
+ $finalOptions['curl'][CURLOPT_INTERFACE] = $this->_outputInterface;
+ }
+ if ($this->_resetConnection) {
+ $finalOptions['curl'][CURLOPT_FRESH_CONNECT] = true;
+ $this->_resetConnection = false;
+ }
+
+ return $finalOptions;
+ }
+
+ /**
+ * Wraps Guzzle's request and adds special error handling and options.
+ *
+ * Automatically throws exceptions on certain very serious HTTP errors. And
+ * re-wraps all Guzzle errors to our own internal exceptions instead. You
+ * must ALWAYS use this (or _apiRequest()) instead of the raw Guzzle Client!
+ * However, you can never assume the server response contains what you
+ * wanted. Be sure to validate the API reply too, since Instagram's API
+ * calls themselves may fail with a JSON message explaining what went wrong.
+ *
+ * WARNING: This is a semi-lowlevel handler which only applies critical
+ * options and HTTP connection handling! Most functions will want to call
+ * _apiRequest() instead. An even higher-level handler which takes care of
+ * debugging, server response checking and response decoding!
+ *
+ * @param HttpRequestInterface $request HTTP request to send.
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ *
+ * @throws \InstagramAPI\Exception\NetworkException For any network/socket related errors.
+ * @throws \InstagramAPI\Exception\ThrottledException When we're throttled by server.
+ * @throws \InstagramAPI\Exception\RequestHeadersTooLargeException When request is too large.
+ *
+ * @return HttpResponseInterface
+ */
+ protected function _guzzleRequest(
+ HttpRequestInterface $request,
+ array $guzzleOptions = [])
+ {
+ // Add critically important options for authenticating the request.
+ $guzzleOptions = $this->_buildGuzzleOptions($guzzleOptions);
+
+ // Attempt the request. Will throw in case of socket errors!
+ try {
+ $response = $this->_guzzleClient->send($request, $guzzleOptions);
+ } catch (\Exception $e) {
+ // Re-wrap Guzzle's exception using our own NetworkException.
+ throw new \InstagramAPI\Exception\NetworkException($e);
+ }
+
+ // Detect very serious HTTP status codes in the response.
+ $httpCode = $response->getStatusCode();
+ switch ($httpCode) {
+ case 429: // "429 Too Many Requests"
+ throw new \InstagramAPI\Exception\ThrottledException('Throttled by Instagram because of too many API requests.');
+ break;
+ case 431: // "431 Request Header Fields Too Large"
+ throw new \InstagramAPI\Exception\RequestHeadersTooLargeException('The request start-line and/or headers are too large to process.');
+ break;
+ // WARNING: Do NOT detect 404 and other higher-level HTTP errors here,
+ // since we catch those later during steps like mapServerResponse()
+ // and autoThrow. This is a warning to future contributors!
+ }
+
+ // We'll periodically auto-save our cookies at certain intervals. This
+ // complements the "onCloseUser" and "login()/logout()" force-saving.
+ if ((time() - $this->_cookieJarLastSaved) > self::COOKIE_AUTOSAVE_INTERVAL) {
+ $this->saveCookieJar();
+ }
+
+ // The response may still have serious but "valid response" errors, such
+ // as "400 Bad Request". But it's up to the CALLER to handle those!
+ return $response;
+ }
+
+ /**
+ * Internal wrapper around _guzzleRequest().
+ *
+ * This takes care of many common additional tasks needed by our library,
+ * so you should try to always use this instead of the raw _guzzleRequest()!
+ *
+ * Available library options are:
+ * - 'noDebug': Can be set to TRUE to forcibly hide debugging output for
+ * this request. The user controls debugging globally, but this is an
+ * override that prevents them from seeing certain requests that you may
+ * not want to trigger debugging (such as perhaps individual steps of a
+ * file upload process). However, debugging SHOULD be allowed in MOST cases!
+ * So only use this feature if you have a very good reason.
+ * - 'debugUploadedBody': Set to TRUE to make debugging display the data that
+ * was uploaded in the body of the request. DO NOT use this if your function
+ * uploaded binary data, since printing those bytes would kill the terminal!
+ * - 'debugUploadedBytes': Set to TRUE to make debugging display the size of
+ * the uploaded body data. Should ALWAYS be TRUE when uploading binary data.
+ *
+ * @param HttpRequestInterface $request HTTP request to send.
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ * @param array $libraryOptions Additional options for controlling Library features
+ * such as the debugging output.
+ *
+ * @throws \InstagramAPI\Exception\NetworkException For any network/socket related errors.
+ * @throws \InstagramAPI\Exception\ThrottledException When we're throttled by server.
+ *
+ * @return HttpResponseInterface
+ */
+ protected function _apiRequest(
+ HttpRequestInterface $request,
+ array $guzzleOptions = [],
+ array $libraryOptions = [])
+ {
+ // Perform the API request and retrieve the raw HTTP response body.
+ $guzzleResponse = $this->_guzzleRequest($request, $guzzleOptions);
+
+ // Debugging (must be shown before possible decoding error).
+ if ($this->_parent->debug && (!isset($libraryOptions['noDebug']) || !$libraryOptions['noDebug'])) {
+ // Determine whether we should display the contents of the UPLOADED body.
+ if (isset($libraryOptions['debugUploadedBody']) && $libraryOptions['debugUploadedBody']) {
+ $uploadedBody = (string) $request->getBody();
+ if (!strlen($uploadedBody)) {
+ $uploadedBody = null;
+ }
+ } else {
+ $uploadedBody = null; // Don't display.
+ }
+
+ // Determine whether we should display the size of the UPLOADED body.
+ if (isset($libraryOptions['debugUploadedBytes']) && $libraryOptions['debugUploadedBytes']) {
+ // Calculate the uploaded bytes by looking at request's body size, if it exists.
+ $uploadedBytes = $request->getBody()->getSize();
+ } else {
+ $uploadedBytes = null; // Don't display.
+ }
+
+ $this->_printDebug(
+ $request->getMethod(),
+ $this->_zeroRating->rewrite((string) $request->getUri()),
+ $uploadedBody,
+ $uploadedBytes,
+ $guzzleResponse,
+ (string) $guzzleResponse->getBody());
+ }
+
+ return $guzzleResponse;
+ }
+
+ /**
+ * Perform an Instagram API call.
+ *
+ * @param HttpRequestInterface $request HTTP request to send.
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ *
+ * @throws InstagramException
+ *
+ * @return HttpResponseInterface
+ */
+ public function api(
+ HttpRequestInterface $request,
+ array $guzzleOptions = [])
+ {
+ // Set up headers that are required for every request.
+ $request = modify_request($request, [
+ 'set_headers' => [
+ 'User-Agent' => $this->_userAgent,
+ // Keep the API's HTTPS connection alive in Guzzle for future
+ // re-use, to greatly speed up all further queries after this.
+ 'Connection' => 'Keep-Alive',
+ 'Accept' => '*/*',
+ 'Accept-Encoding' => Constants::ACCEPT_ENCODING,
+ 'Accept-Language' => Constants::ACCEPT_LANGUAGE,
+ ],
+ ]);
+
+ // Check the Content-Type header for debugging.
+ $contentType = $request->getHeader('Content-Type');
+ $isFormData = count($contentType) && reset($contentType) === Constants::CONTENT_TYPE;
+
+ // Perform the API request.
+ $response = $this->_apiRequest($request, $guzzleOptions, [
+ 'debugUploadedBody' => $isFormData,
+ 'debugUploadedBytes' => !$isFormData,
+ ]);
+
+ return $response;
+ }
+
+ /**
+ * Decode a JSON reply from Instagram's API.
+ *
+ * WARNING: EXTREMELY IMPORTANT! NEVER, *EVER* USE THE BASIC "json_decode"
+ * ON API REPLIES! ALWAYS USE THIS METHOD INSTEAD, TO ENSURE PROPER DECODING
+ * OF BIG NUMBERS! OTHERWISE YOU'LL TRUNCATE VARIOUS INSTAGRAM API FIELDS!
+ *
+ * @param string $json The body (JSON string) of the API response.
+ * @param bool $assoc When FALSE, decode to object instead of associative array.
+ *
+ * @return object|array|null Object if assoc false, Array if assoc true,
+ * or NULL if unable to decode JSON.
+ */
+ public static function api_body_decode(
+ $json,
+ $assoc = true)
+ {
+ return @json_decode($json, $assoc, 512, JSON_BIGINT_AS_STRING);
+ }
+
+ /**
+ * Get the cookies middleware instance.
+ *
+ * @return FakeCookies
+ */
+ public function fakeCookies()
+ {
+ return $this->_fakeCookies;
+ }
+
+ /**
+ * Get the zero rating rewrite middleware instance.
+ *
+ * @return ZeroRating
+ */
+ public function zeroRating()
+ {
+ return $this->_zeroRating;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Constants.php b/vendor/mgp25/instagram-php/src/Constants.php
new file mode 100755
index 0000000..2795e82
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Constants.php
@@ -0,0 +1,103 @@
+ 'https://i.instagram.com/api/v1/',
+ 2 => 'https://i.instagram.com/api/v2/',
+ ];
+ const IG_VERSION = '107.0.0.27.121';
+ const VERSION_CODE = '168361634';
+ const IG_SIG_KEY = 'c36436a942ea1dbb40d7f2d7d45280a620d991ce8c62fb4ce600f0a048c32c11';
+ const EXPERIMENTS = 'ig_android_push_notifications_settings_redesign_universe,ig_hashtag_display_universe,ig_android_video_ssim_fix_pts_universe,coupon_price_test_ad4ad_instagram_resurrection_universe,ig_android_live_rendering_looper_universe,ig_shopping_checkout_improvements_universe,ig_android_mqtt_cookie_auth_memcache_universe,ig_android_optional_contact_and_preset_universe,ig_android_video_player_memory_leaks,ig_android_stories_seen_state_serialization,ig_stories_photo_time_duration_universe,ig_android_bitmap_cache_executor_size,ig_android_stories_music_search_typeahead,android_ard_ig_use_brotli_effect_universe,ig_android_remove_fb_nux_universe,ig_android_delayed_comments,ig_android_direct_mutation_manager_media_3,ig_smb_ads_holdout_2019_h1_universe,ig_fb_graph_differentiation,ig_android_stories_share_extension_video_segmentation,ig_android_igtv_crop_top,ig_android_stories_create_flow_favorites_tooltip,ig_android_direct_reshare_chaining,ig_android_stories_no_inflation_on_app_start,ig_android_stories_viewer_viewpoint_universe,ig_android_separate_empty_feed_su_universe,ig_android_zero_rating_carrier_signal,ig_direct_holdout_h1_2019,ig_explore_2019_h1_destination_cover,ig_android_direct_stories_in_direct_inbox,ig_android_explore_recyclerview_universe,ig_android_show_muted_accounts_page,ig_android_vc_service_crash_fix_universe,ig_camera_android_subtle_filter_universe,ig_android_lazy_init_live_composer_controller,ig_fb_graph_differentiation_no_fb_data,ig_android_viewpoint_stories_public_testing,ig_camera_android_api_rewrite_universe,ig_android_growth_fci_team_holdout_universe,android_camera_core_cpu_frames_sync,ig_android_video_source_sponsor_fix,ig_android_save_all,ig_android_ttcp_improvements,ig_android_camera_ar_platform_profile_universe,ig_android_separate_sms_n_email_invites_setting_universe,ig_shopping_bag_universe,ig_ar_shopping_camera_android_universe,ig_android_recyclerview_binder_group_enabled_universe,ig_android_stories_viewer_tall_android_cap_media_universe,ig_android_video_exoplayer_2,native_contact_invites_universe,ig_android_stories_seen_state_processing_universe,ig_android_dash_script,ig_android_insights_media_hashtag_insight_universe,ig_android_search_qpl_switch,ig_camera_fast_tti_universe,ig_android_igtv_improved_search,ig_android_stories_music_filters,ig_android_render_thread_memory_leak_holdout,ig_android_automated_logging,ig_android_viewmaster_post_capture_universe,ig_android_2018_h1_hashtag_report_universe,ig_android_camera_effect_gallery_prefetch,ig_share_to_story_toggle_include_shopping_product,ig_android_interactions_verified_badge_on_comment_details,ig_android_fix_ppr_thumbnail_url,ig_android_camera_reduce_file_exif_reads,ig_interactions_project_daisy_creators_universe,ig_payments_billing_address,ig_android_fs_new_gallery_hashtag_prompts,ig_camera_android_gyro_senser_sampling_period_universe,ig_android_xposting_feed_to_stories_reshares_universe,ig_android_combined_consumption,ig_camera_remove_display_rotation_cb_universe,ig_android_interactions_migrate_inline_composer_to_viewpoint_universe,ig_android_ufiv3_holdout,ig_android_neue_igtv_profile_tab_rollout,ig_android_enable_zero_rating,ig_android_story_ads_carousel_performance_universe_1,ig_android_direct_leave_from_group_message_requests,ig_android_import_page_post_after_biz_conversion,ig_camera_ar_effect_attribution_position,ig_promote_add_payment_navigation_universe,ig_android_story_ads_carousel_performance_universe_2,ig_android_main_feed_refresh_style_universe,ig_stories_engagement_holdout_2019_h1_universe,ig_android_story_ads_performance_universe_1,ig_android_stories_viewer_modal_activity,ig_android_story_ads_performance_universe_2,ig_android_publisher_stories_migration,ig_android_story_ads_performance_universe_3,ig_android_quick_conversion_universe,ig_android_story_import_intent,ig_android_story_ads_performance_universe_4,instagram_android_profile_follow_cta_context_feed,ig_biz_graph_connection_universe,ig_android_stories_boomerang_v2_universe,ig_android_ads_profile_cta_feed_universe,ig_android_vc_cowatch_universe,ig_android_nametag,ig_hashtag_creation_universe,ig_android_igtv_chaining,ig_android_live_qa_viewer_v1_universe,ig_shopping_insights_wc_copy_update_android,ig_android_stories_music_lyrics_pre_capture,android_cameracore_fbaudio_integration_ig_universe,ig_android_camera_stopmotion,ig_android_igtv_reshare,ig_android_wellbeing_timeinapp_v1_universe,ig_android_profile_cta_v3,ig_end_of_feed_universe,ig_android_mainfeed_generate_prefetch_background,ig_android_vc_shareable_moments_universe,ig_camera_text_overlay_controller_opt_universe,ig_android_shopping_product_metadata_on_product_tiles_universe,ig_android_video_qp_logger_universe,ig_android_shopping_pdp_cache,ig_android_follow_request_button_improvements_universe,ig_android_vc_start_from_direct_inbox_universe,ig_android_separate_network_executor,ig_perf_android_holdout,ig_fb_graph_differentiation_only_fb_candidates,ig_android_media_streaming_sdk_universe,ig_android_direct_reshares_from_thread,ig_android_stories_video_prefetch_kb,ig_android_wellbeing_timeinapp_v1_migration,ig_android_camera_post_smile_face_first_universe,ig_android_maintabfragment,ig_android_cookie_injection_retry_universe,ig_inventory_connections,ig_stories_injection_tool_enabled_universe,ig_android_canvas_cookie_universe,ig_android_stories_disable_highlights_media_preloading,ig_android_effect_gallery_post_capture_universe,ig_android_shopping_variant_selector_redesign,ig_android_branded_content_ads_universe,ig_promote_lotus_universe,ig_android_video_streaming_upload_universe,ig_camera_android_attribution_bottomsheet_universe,ig_android_product_tag_hint_dots,ig_interactions_h1_2019_team_holdout_universe,ig_camera_android_release_drawing_view_universe,ig_android_music_story_fb_crosspost_universe,ig_android_disable_scroll_listeners,ig_carousel_bumped_organic_impression_client_universe,ig_android_ad_async_ads_universe,ig_biz_post_approval_nux_universe,ig_android_vc_participants_grid_refactor_universe,android_ard_ig_modelmanager_cache_hit,ig_android_persistent_nux,ig_android_crash_fix_detach_from_gl_context,ig_android_branded_content_upsell_keywords_extension,ig_android_vc_ringscreen_timeout_universe,ig_android_edit_location_page_info,ig_android_stories_project_eclipse,ig_camera_android_segmentation_v106_igdjango_universe,ig_android_camera_recents_gallery_modal,ig_promote_are_you_sure_universe,ig_android_li_session_chaining,ig_android_camera_platform_effect_share_universe,ig_android_rate_limit_mediafeedviewablehelper,ig_android_search_empty_state,ig_android_camera_ar_platform_logging,ig_stories_engagement_holdout_2019_h2_universe,ig_android_search_remove_null_state_sections,ig_direct_android_mentions_receiver,ig_camera_android_device_capabilities_experiment,ig_android_stories_viewer_drawable_cache_universe,ig_camera_android_qcc_constructor_opt_universe,ig_android_stories_alignment_guides_universe,ig_android_rn_ads_manager_universe,ig_android_video_visual_quality_score_based_abr,ig_explore_2018_post_chaining_account_recs_dedupe_universe,ig_android_stories_video_seeking_audio_bug_fix,ig_android_insights_holdout,ig_android_do_not_show_social_context_for_likes_page_universe,ig_android_context_feed_recycler_view,ig_fb_notification_universe,ig_android_report_website_universe,ig_android_feed_post_sticker,ig_android_inline_editing_local_prefill,ig_android_commerce_platform_bloks_universe,ig_android_stories_unregister_decor_listener_universe,ig_android_search_condensed_search_icons,ig_android_video_abr_universe,ig_android_blended_inbox_split_button_v2_universe,ig_android_nelson_v0_universe,ig_android_scroll_audio_priority,ig_android_own_profile_sharing_universe,ig_android_vc_cowatch_media_share_universe,ig_biz_graph_unify_assoc_universe,ig_challenge_general_v2,ig_android_place_signature_universe,ig_android_direct_inbox_cache_universe,ig_android_ig_branding_in_fb_universe,ig_android_business_promote_tooltip,ig_android_tap_to_capture_universe,ig_android_follow_requests_ui_improvements,ig_android_video_ssim_fix_compare_frame_index,ig_android_direct_aggregated_media_and_reshares,ig_android_story_camera_share_to_feed_universe,ig_android_fb_follow_server_linkage_universe,ig_android_stories_viewer_reply_box_placeholder_copy,ig_android_biz_reorder_value_props,ig_android_direct_view_more_qe,ig_android_churned_find_friends_redirect_to_discover_people,ig_android_main_feed_new_posts_indicator_universe,ig_vp9_hd_blacklist,ig_camera_android_ar_effect_stories_deeplink,ig_android_client_side_delivery_universe,ig_ios_queue_time_qpl_universe,ig_android_fix_direct_badge_count_universe,ig_android_insights_audience_tab_native_universe,ig_android_stories_send_client_reels_on_tray_fetch_universe,ig_android_felix_prefetch_thumbnail_sprite_sheet,ig_android_live_use_rtc_upload_universe,ig_android_multi_dex_class_loader_v2,ig_android_live_ama_viewer_universe,ig_smb_ads_holdout_2018_h2_universe,ig_android_camera_post_smile_low_end_universe,ig_android_profile_follow_tab_hashtag_row_universe,ig_android_watch_and_more_redesign,igtv_feed_previews,ig_android_live_realtime_comments_universe,ig_android_story_insights_native_universe,ig_smb_ads_holdout_2019_h2_universe,ig_android_purx_native_checkout_universe,ig_camera_android_filter_optmizations,ig_android_integrity_sprint_universe,ig_android_apr_lazy_build_request_infra,ig_android_igds_edit_profile_fields,ig_android_business_transaction_in_stories_creator,ig_android_rounded_corner_framelayout_perf_fix,ig_android_branded_content_appeal_states,android_cameracore_ard_ig_integration,ig_video_experimental_encoding_consumption_universe,ig_android_iab_autofill,ig_android_creator_quick_reply_universe,ig_android_location_page_intent_survey,ig_camera_android_segmentation_async_universe,ig_android_biz_story_to_fb_page_improvement,ig_android_direct_thread_target_queue_universe,ig_android_branded_content_insights_disclosure,ig_camera_android_target_recognition_universe,ig_camera_android_skip_camera_initialization_open_to_post_capture,ig_android_combined_tagging_videos,ig_android_stories_samsung_sharing_integration,ig_android_create_page_on_top_universe,ig_iab_use_default_intent_loading,ig_android_camera_focus_v2,ig_android_biz_conversion_pull_suggest_biz_data,ig_discovery_holdout_2019_h1_universe,ig_android_wellbeing_support_frx_comment_reporting,ig_android_insights_post_dismiss_button,ig_android_user_url_deeplink_fbpage_endpoint,ig_android_ad_holdout_watchandmore_universe,ig_android_follow_request_button_new_ui,ig_iab_dns_prefetch_universe,ig_android_explore_use_shopping_endpoint,ig_android_image_upload_skip_queue_only_on_wifi,ig_android_igtv_pip,ig_android_ad_watchbrowse_carousel_universe,ig_android_camera_new_post_smile_universe,ig_android_shopping_signup_redesign_universe,ig_android_direct_hide_inbox_header,ig_shopping_pdp_more_related_product_section,ig_android_experimental_onetap_dialogs_universe,ig_android_fix_main_feed_su_cards_size_universe,ig_android_direct_multi_upload_universe,ig_camera_text_mode_composer_controller_opt_universe,ig_explore_2019_h1_video_autoplay_resume,ig_android_multi_capture_camera,ig_android_video_upload_quality_qe1,ig_android_follow_requests_copy_improvements,ig_android_save_collaborative_collections,coupon_price_test_boost_instagram_media_acquisition_universe,ig_android_video_outputsurface_handlerthread_universe,ig_android_country_code_fix_universe,ig_perf_android_holdout_2018_h1,ig_android_stories_music_overlay,ig_android_enable_lean_crash_reporting_universe,ig_android_resumable_downloads_logging_universe,ig_android_low_latency_consumption_universe,ig_android_render_output_surface_timeout_universe,ig_android_big_foot_foregroud_reporting,ig_android_unified_iab_logging_universe,ig_threads_app_close_friends_integration,ig_aggregated_quick_reactions,ig_android_shopping_pdp_post_purchase_sharing,ig_android_aggressive_cookie,ig_android_offline_mode_holdout,ig_android_realtime_mqtt_logging,ig_android_rainbow_hashtags,ig_android_no_bg_effect_tray_live_universe,ig_android_direct_block_from_group_message_requests,ig_android_react_native_universe_kill_switch,ig_android_viewpoint_occlusion,ig_android_logged_in_delta_migration,ig_android_push_reliability_universe,ig_android_stories_gallery_video_segmentation,ig_android_direct_business_holdout,ig_android_vc_direct_inbox_ongoing_pill_universe,ig_android_xposting_upsell_directly_after_sharing_to_story,ig_android_direct_sticker_search_upgrade_universe,ig_android_insights_native_post_universe,ig_android_dual_destination_quality_improvement,ig_android_camera_focus_low_end_universe,ig_android_camera_hair_segmentation_universe,ig_android_direct_combine_action_logs,ig_android_leak_detector_upload_universe,ig_android_ads_data_preferences_universe,ig_android_branded_content_access_upsell,ig_android_follow_button_in_story_viewers_list,ig_android_vc_background_call_toast_universe,ig_hashtag_following_holdout_universe,ig_promote_default_destination_universe,ig_android_delay_segmentation_low_end_universe,ig_android_direct_media_latency_optimizations,mi_viewpoint_viewability_universe,android_ard_ig_download_manager_v2,ig_direct_reshare_sharesheet_ranking,ig_music_dash,ig_android_fb_url_universe,ig_android_le_videoautoplay_disabled,ig_android_reel_raven_video_segmented_upload_universe,ig_android_promote_native_migration_universe,invite_friends_by_messenger_in_setting_universe,ig_android_fb_sync_options_universe,ig_android_thread_gesture_refactor,ig_android_stories_skip_seen_state_update_for_direct_stories,ig_android_recommend_accounts_destination_routing_fix,ig_android_fix_prepare_direct_push,ig_android_enable_automated_instruction_text_ar,ig_android_multi_author_story_reshare_universe,ig_android_building_aymf_universe,ig_android_internal_sticker_universe,ig_traffic_routing_universe,ig_android_payments_growth_business_payments_within_payments_universe,ig_camera_async_gallerycontroller_universe,ig_android_direct_state_observer,ig_android_page_claim_deeplink_qe,ig_android_camera_effects_order_universe,ig_android_video_controls_universe,ig_android_video_local_proxy_video_metadata,ig_android_logging_metric_universe_v2,ig_android_network_onbehavior_change_fix,ig_android_xposting_newly_fbc_people,ig_android_visualcomposer_inapp_notification_universe,ig_android_do_not_show_social_context_on_follow_list_universe,ig_android_contact_point_upload_rate_limit_killswitch,ig_android_webrtc_encoder_factory_universe,ig_android_qpl_class_marker,ig_android_fix_profile_pic_from_fb_universe,ig_android_sso_kototoro_app_universe,ig_android_camera_3p_in_post,ig_android_ar_effect_sticker_consumption_universe,ig_android_direct_unread_count_badge,ig_android_profile_thumbnail_impression,ig_android_igtv_autoplay_on_prepare,ig_android_list_adapter_prefetch_infra,ig_file_based_session_handler_2_universe,ig_branded_content_tagging_upsell,ig_android_clear_inflight_image_request,ig_android_main_feed_video_countdown_timer,ig_android_live_ama_universe,ig_android_external_gallery_import_affordance,ig_search_hashtag_content_advisory_remove_snooze,ig_payment_checkout_info,ig_android_optic_new_zoom_controller,ig_android_photos_qpl,ig_stories_ads_delivery_rules,ig_android_downloadable_spark_spot,ig_android_video_upload_iframe_interval,ig_business_new_value_prop_universe,ig_android_power_metrics,ig_android_vio_pipeline_universe,ig_android_show_profile_picture_upsell_in_reel_universe,ig_discovery_holdout_universe,ig_android_direct_import_google_photos2,ig_direct_feed_media_sticker_universe,ig_android_igtv_upload_error_messages,ig_android_stories_collapse_seen_segments,ig_android_self_profile_suggest_business_main,ig_android_suggested_users_background,ig_android_fetch_xpost_setting_in_camera_fully_open,ig_android_hashtag_discover_tab,ig_android_stories_separate_overlay_creation,ig_android_ads_bottom_sheet_report_flow,ig_android_login_onetap_upsell_universe,ig_android_iris_improvements,enable_creator_account_conversion_v0_universe,ig_android_test_not_signing_address_book_unlink_endpoint,ig_android_low_disk_recovery_universe,ig_ei_option_setting_universe,ig_android_account_insights_native_universe,ig_camera_android_ar_platform_universe,ig_android_browser_ads_page_content_width_universe,ig_android_stories_viewer_prefetch_improvements,ig_android_livewith_liveswap_optimization_universe,ig_android_camera_leak,ig_android_feed_core_unified_tags_universe,ig_android_jit,ig_android_optic_camera_warmup,ig_stories_rainbow_ring,ig_android_place_search_profile_image,ig_android_vp8_audio_encoder,android_cameracore_safe_makecurrent_ig,ig_android_analytics_diagnostics_universe,ig_android_ar_effect_sticker_universe,ig_direct_android_mentions_sender,ig_android_whats_app_contact_invite_universe,ig_android_stories_reaction_setup_delay_universe,ig_shopping_visual_product_sticker,ig_android_profile_unified_follow_view,ig_android_video_upload_hevc_encoding_universe,ig_android_mentions_suggestions,ig_android_vc_face_effects_universe,ig_android_fbpage_on_profile_side_tray,ig_android_direct_empty_state,ig_android_shimmering_loading_state,ig_android_igtv_refresh_tv_guide_interval,ig_android_gallery_minimum_video_length,ig_android_notif_improvement_universe,ig_android_hashtag_remove_share_hashtag,ig_android_fb_profile_integration_fbnc_universe,ig_shopping_checkout_2x2_platformization_universe,ig_android_direct_bump_active_threads,ig_fb_graph_differentiation_control,ig_android_show_create_content_pages_universe,ig_android_igsystrace_universe,ig_android_search_register_recent_store,ig_feed_content_universe,ig_android_disk_usage_logging_universe,ig_android_search_without_typed_hashtag_autocomplete,ig_android_video_product_specific_abr,ig_android_vc_interop_latency,ig_android_stories_layout_universe,ig_android_dont_animate_shutter_button_on_open,ig_android_vc_cpu_overuse_universe,ig_android_invite_list_button_redesign_universe,ig_android_react_native_email_sms_settings_universe,ig_hero_player,ag_family_bridges_2018_h2_holdout,ig_promote_net_promoter_score_universe,ig_android_save_auto_sharing_to_fb_option_on_server,aymt_instagram_promote_flow_abandonment_ig_universe,ig_android_whitehat_options_universe,ig_android_keyword_media_serp_page,ig_android_delete_ssim_compare_img_soon,ig_android_felix_video_upload_length,android_cameracore_preview_frame_listener2_ig_universe,ig_android_direct_message_follow_button,ig_android_biz_conversion_suggest_biz_nux,ig_stories_ads_media_based_insertion,ig_android_analytics_background_uploader_schedule,ig_camera_android_boomerang_attribution_universe,ig_android_igtv_browse_long_press,ig_android_profile_neue_infra_rollout_universe,ig_android_profile_ppr_fixes,ig_discovery_2019_h2_holdout_universe,ig_android_stories_weblink_creation,ig_android_blur_image_renderer,ig_profile_company_holdout_h2_2018,ig_android_ads_manager_pause_resume_ads_universe,ig_android_vc_capture_universe,ig_nametag_local_ocr_universe,ig_android_stories_media_seen_batching_universe,ig_android_interactions_nav_to_permalink_followup_universe,ig_camera_discovery_surface_universe,ig_android_save_to_collections_flow,ig_android_direct_segmented_video,instagram_stories_time_fixes,ig_android_le_cold_start_improvements,ig_android_direct_mark_as_read_notif_action,ig_android_stories_async_view_inflation_universe,ig_android_stories_recently_captured_universe,ig_android_direct_inbox_presence_refactor_universe,ig_business_integrity_ipc_universe,ig_android_direct_selfie_stickers,ig_android_vc_missed_call_call_back_action_universe,ig_cameracore_android_new_optic_camera2,ig_fb_graph_differentiation_top_k_fb_coefficients,ig_android_fbc_upsell_on_dp_first_load,ig_android_rename_share_option_in_dialog_menu_universe,ig_android_direct_refactor_inbox_observable_universe,ig_android_business_attribute_sync,ig_camera_android_bg_processor,ig_android_view_and_likes_cta_universe,ig_android_optic_new_focus_controller,ig_android_dropframe_manager,ig_android_direct_default_group_name,ig_android_optic_new_features_implementation,ig_android_search_hashtag_badges,ig_android_stories_reel_interactive_tap_target_size,ig_android_video_live_trace_universe,ig_android_tango_cpu_overuse_universe,ig_android_igtv_browse_with_pip_v2,ig_android_direct_fix_realtime_status,ig_android_unfollow_from_main_feed_v2,ig_android_self_story_setting_option_in_menu,ig_android_story_ads_tap_and_hold_fixes,ig_android_camera_ar_platform_details_view_universe,android_ard_ig_cache_size,ig_android_story_real_time_ad,ig_android_hybrid_bitmap_v4,ig_android_iab_downloadable_strings_universe,ig_android_branded_content_ads_enable_partner_boost,ufi_share,ig_android_direct_remix_visual_messages,ig_quick_story_placement_validation_universe,ig_android_custom_story_import_intent,ig_android_live_qa_broadcaster_v1_universe,ig_android_search_impression_logging_viewpoint,ig_android_downloadable_fonts_universe,ig_android_view_info_universe,ig_android_camera_upsell_dialog,ig_android_business_transaction_in_stories_consumer,ig_android_dead_code_detection,ig_android_promotion_insights_bloks,ig_android_direct_autoplay_videos_automatically,ig_android_ad_watchbrowse_universe,ig_android_pbia_proxy_profile_universe,ig_android_qp_kill_switch,ig_android_new_follower_removal_universe,instagram_android_stories_sticker_tray_redesign,ig_android_branded_content_access_tag,ig_android_gap_rule_enforcer_universe,ig_android_business_cross_post_with_biz_id_infra,ig_android_direct_delete_or_block_from_message_requests,ig_android_photo_invites,ig_interactions_h2_2019_team_holdout_universe,ig_android_reel_tray_item_impression_logging_viewpoint,ig_account_identity_2018_h2_lockdown_phone_global_holdout,ig_android_direct_left_aligned_navigation_bar,ig_android_high_res_gif_stickers,ig_android_feed_load_more_viewpoint_universe,ig_android_stories_reshare_reply_msg,ig_close_friends_v4,ig_android_ads_history_universe,ig_android_pigeon_sampling_runnable_check,ig_promote_media_picker_universe,ig_direct_holdout_h2_2018,ig_android_sidecar_report_ssim,ig_android_pending_media_file_registry,ig_android_wab_adjust_resize_universe,ig_camera_android_facetracker_v12_universe,ig_android_camera_ar_effects_low_storage_universe,ig_android_profile_add_profile_pic_universe,ig_android_ig_to_fb_sync_universe,ig_android_ar_background_effect_universe,ig_android_audience_control,ig_android_fix_recommended_user_impression,ig_android_stories_cross_sharing_to_fb_holdout_universe,shop_home_hscroll_see_all_button_universe,ig_android_refresh_empty_feed_su_universe,ig_android_shopping_parallel_pdp_fetch,ig_android_enable_main_feed_reel_tray_preloading,ig_android_ad_view_ads_native_universe,ig_android_branded_content_tag_redesign_organic,ig_android_profile_neue_universe,ig_android_igtv_whitelisted_for_web,ig_android_viewmaster_dial_ordering_universe,ig_company_profile_holdout,ig_rti_inapp_notifications_universe,ig_android_vc_join_timeout_universe,ig_shop_directory_entrypoint,ig_android_direct_rx_thread_update,ig_android_add_ci_upsell_in_normal_account_chaining_universe,ig_android_feed_core_ads_2019_h1_holdout_universe,ig_close_friends_v4_global,ig_android_share_publish_page_universe,ig_android_new_camera_design_universe,ig_direct_max_participants,ig_promote_hide_local_awareness_universe,ar_engine_audio_service_fba_decoder_ig,ar_engine_audio_fba_integration_instagram,ig_android_igtv_save,ig_android_explore_lru_cache,ig_android_graphql_survey_new_proxy_universe,ig_android_music_browser_redesign,ig_camera_android_try_on_camera_universe,ig_android_follower_following_whatsapp_invite_universe,ig_android_fs_creation_flow_tweaks,ig_direct_blocking_redesign_universe,ig_android_viewmaster_ar_memory_improvements,ig_android_downloadable_vp8_module,ig_android_claim_location_page,ig_android_direct_inbox_recently_active_presence_dot_universe,ig_android_stories_gutter_width_universe,ig_android_story_ads_2019_h1_holdout_universe,ig_android_3pspp,ig_android_cache_timespan_objects,ig_timestamp_public_test,ig_android_fb_profile_integration_universe,ig_android_feed_auto_share_to_facebook_dialog,ig_android_skip_button_content_on_connect_fb_universe,ig_android_network_perf_qpl_ppr,ig_android_post_live,ig_camera_android_focus_attribution_universe,ig_camera_async_space_validation_for_ar,ig_android_core_search_2019_h2,ig_android_prefetch_notification_data,ig_android_stories_music_line_by_line_cube_reveal_lyrics_sticker,ig_android_iab_clickid_universe,ig_android_interactions_hide_keyboard_onscroll,ig_early_friending_holdout_universe,ig_story_camera_reverse_video_experiment,ig_android_profile_lazy_load_carousel_media,ig_android_stories_question_sticker_music_format,ig_android_vpvd_impressions_universe,ig_android_payload_based_scheduling,ig_pacing_overriding_universe,ig_android_ard_ptl_universe,ig_android_q3lc_transparency_control_settings,ig_stories_selfie_sticker,ig_android_sso_use_trustedapp_universe,ig_android_stories_music_lyrics,ig_android_spark_studio_promo,ig_android_stories_music_awareness_universe,ard_ig_broti_effect,ig_android_camera_class_preloading,ig_android_new_fb_page_selection,ig_video_holdout_h2_2017,ig_background_prefetch,ig_camera_android_focus_in_post_universe,ig_android_time_spent_dashboard,ig_android_story_sharing_universe,ig_promote_political_ads_universe,ig_android_camera_effects_initialization_universe,ig_promote_post_insights_entry_universe,ig_android_ad_iab_qpl_kill_switch_universe,ig_android_live_subscribe_user_level_universe,ig_android_igtv_creation_flow,ig_android_vc_sounds_universe,ig_android_video_call_finish_universe,ig_camera_android_cache_format_picker_children,direct_unread_reminder_qe,ig_android_direct_mqtt_send,ig_android_self_story_button_non_fbc_accounts,ig_android_self_profile_suggest_business_gating,ig_feed_video_autoplay_stop_threshold,ig_android_explore_discover_people_entry_point_universe,ig_android_live_webrtc_livewith_params,ig_feed_experience,ig_android_direct_activator_cards,ig_android_vc_codec_settings,ig_promote_prefill_destination_universe,ig_android_appstate_logger,ig_android_profile_leaks_holdouts,ig_android_video_cached_bandwidth_estimate,ig_promote_insights_video_views_universe,ig_android_global_scheduler_offscreen_prefetch,ig_android_discover_interests_universe,ig_android_camera_gallery_upload_we_universe,ig_android_business_category_sticky_header_qe,ig_android_dismiss_recent_searches,ig_android_feed_camera_size_setter,ig_payment_checkout_cvv,ig_android_fb_link_ui_polish_universe,ig_android_tags_unification_universe,ig_android_shopping_lightbox,ig_android_bandwidth_timed_estimator,ig_android_stories_mixed_attribution_universe,ig_iab_tti_holdout_universe,ig_android_ar_button_visibility,ig_android_igtv_crop_top_consumption,ig_android_camera_gyro_universe,ig_android_nametag_effect_deeplink_universe,ig_android_blurred_product_image_previews,ig_android_igtv_ssim_report,ig_android_optic_surface_texture_cleanup,ig_android_business_remove_unowned_fb_pages,ig_android_stories_combined_asset_search,ig_promote_enter_error_screens_universe,ig_stories_allow_camera_actions_while_recording,ig_android_analytics_mark_events_as_offscreen,ig_shopping_checkout_mvp_experiment,ig_android_video_fit_scale_type_igtv,ig_android_direct_pending_media,ig_android_scroll_main_feed,instagram_pcp_activity_feed_following_tab_universe,ig_android_optic_feature_testing,ig_android_igtv_player_follow_button,ig_android_intialization_chunk_410,ig_android_vc_start_call_minimized_universe,ig_android_recognition_tracking_thread_prority_universe,ig_android_stories_music_sticker_position,ig_android_optic_photo_cropping_fixes,ig_camera_regiontracking_use_similarity_tracker_for_scaling,ig_android_interactions_media_breadcrumb,ig_android_vc_cowatch_config_universe,ig_android_nametag_save_experiment_universe,ig_android_refreshable_list_view_check_spring,ig_android_biz_endpoint_switch,ig_android_direct_continuous_capture,ig_android_comments_direct_reply_to_author,ig_android_profile_visits_in_bio,ig_android_fs_new_gallery,ig_android_remove_follow_all_fb_list,ig_android_vc_webrtc_params,ig_android_specific_story_sharing,ig_android_claim_or_connect_page_on_xpost,ig_android_anr,ig_android_story_viewpoint_impression_event_universe,ig_android_image_exif_metadata_ar_effect_id_universe,ig_android_optic_new_architecture,ig_android_stories_viewer_as_modal_high_end_launch,ig_android_local_info_page,ig_new_eof_demarcator_universe';
+ const LOGIN_EXPERIMENTS = 'ig_android_fci_onboarding_friend_search,ig_android_device_detection_info_upload,ig_android_account_linking_upsell_universe,ig_android_direct_main_tab_universe_v2,ig_android_sms_retriever_backtest_universe,ig_android_direct_add_direct_to_android_native_photo_share_sheet,ig_growth_android_profile_pic_prefill_with_fb_pic_2,ig_account_identity_logged_out_signals_global_holdout_universe,ig_android_login_identifier_fuzzy_match,ig_android_video_render_codec_low_memory_gc,ig_android_custom_transitions_universe,ig_android_push_fcm,ig_android_show_login_info_reminder_universe,ig_android_email_fuzzy_matching_universe,ig_android_one_tap_aymh_redesign_universe,ig_android_direct_send_like_from_notification,ig_android_suma_landing_page,ig_android_session_scoped_logger,ig_android_user_session_scoped_class_opt_universe,ig_android_accoun_switch_badge_fix_universe,ig_android_smartlock_hints_universe,ig_android_black_out,ig_activation_global_discretionary_sms_holdout,ig_android_account_switch_infra_universe,ig_android_video_ffmpegutil_pts_fix,ig_android_multi_tap_login_new,ig_android_caption_typeahead_fix_on_o_universe,ig_android_save_pwd_checkbox_reg_universe,ig_android_nux_add_email_device,ig_android_direct_remove_view_mode_stickiness_universe,ig_username_suggestions_on_username_taken,ig_android_ingestion_video_support_hevc_decoding,ig_android_secondary_account_creation_universe,ig_android_account_recovery_auto_login,ig_android_sim_info_upload,ig_android_mobile_http_flow_device_universe,ig_android_hide_fb_button_when_not_installed_universe,ig_android_targeted_one_tap_upsell_universe,ig_android_gmail_oauth_in_reg,ig_android_account_linking_flow_shorten_universe,ig_android_hide_typeahead_for_logged_users,ig_android_vc_interop_use_test_igid_universe,ig_android_log_suggested_users_cache_on_error,ig_android_reg_modularization_universe,ig_android_phone_edit_distance_universe,ig_android_device_verification_separate_endpoint,ig_android_universe_noticiation_channels,ig_smartlock_login,ig_android_igexecutor_sync_optimization_universe,ig_android_account_linking_skip_value_props_universe,ig_android_account_linking_universe,ig_android_hsite_prefill_new_carrier,ig_android_retry_create_account_universe,ig_android_family_apps_user_values_provider_universe,ig_android_reg_nux_headers_cleanup_universe,ig_android_device_info_foreground_reporting,ig_android_shortcuts_2019,ig_android_device_verification_fb_signup,ig_android_onetaplogin_optimization,ig_video_debug_overlay,ig_android_ask_for_permissions_on_reg,ig_assisted_login_universe,ig_android_display_full_country_name_in_reg_universe,ig_android_security_intent_switchoff,ig_android_device_info_job_based_reporting,ig_android_passwordless_auth,ig_android_direct_main_tab_account_switch,ig_android_modularized_dynamic_nux_universe,ig_android_fb_account_linking_sampling_freq_universe,ig_android_fix_sms_read_lollipop,ig_android_access_flow_prefill';
+ const LAUNCHER_CONFIGS = 'ig_android_media_codec_info_collection,stories_gif_sticker,ig_android_felix_release_players,bloks_binding,ig_android_camera_network_activity_logger,ig_android_os_version_blocking_config,ig_android_carrier_signals_killswitch,live_special_codec_size_list,fbns,ig_android_aed,ig_client_config_server_side_retrieval,ig_android_bloks_perf_logging,ig_user_session_operation,ig_user_mismatch_soft_error,ig_android_prerelease_event_counter,fizz_ig_android,ig_android_vc_clear_task_flag_killswitch,ig_android_killswitch_perm_direct_ssim,ig_android_codec_high_profile,ig_android_smart_prefill_killswitch,sonar_prober,action_bar_layout_width,ig_auth_headers_device,always_use_server_recents';
+ const LAUNCHER_LOGIN_CONFIGS = 'ig_camera_ard_use_ig_downloader,ig_android_dogfooding,ig_android_bloks_data_release,ig_donation_sticker_public_thanks,ig_business_profile_donate_cta_android,ig_launcher_ig_android_network_dispatcher_priority_decider_qe2,ig_multi_decode_config,ig_android_improve_segmentation_hint,ig_android_memory_manager_holdout,ig_android_interactions_direct_sharing_comment_launcher,ig_launcher_ig_android_analytics_request_cap_qe,ig_direct_e2e_send_waterfall_sample_rate_config,ig_android_cdn_image_sizes_config,ig_android_critical_path_manager,ig_android_mobileboost_camera,ig_android_pdp_default_sections,ig_android_video_playback,ig_launcher_explore_sfplt_secondary_response_android,ig_android_upload_heap_on_oom,ig_synchronous_account_switch,ig_android_direct_presence_digest_improvements,ig_android_request_compression_launcher,ig_android_feed_attach_report_logs,ig_android_insights_welcome_dialog_tooltip,ig_android_qp_surveys_v1,ig_direct_requests_approval_config,ig_android_react_native_ota_kill_switch,ig_android_video_profiler_loom_traces,video_call_gk,ig_launcher_ig_android_network_stack_cap_video_request_qe,ig_shopping_android_business_new_tagging_flow,ig_android_igtv_bitrate,ig_android_geo_gating,ig_android_explore_startup_prefetch,ig_android_camera_asset_blocker_config,post_user_cache_user_based,ig_android_branded_content_story_partner_promote_rollout,ig_android_quic,ig_android_videolite_uploader,ig_direct_message_type_reporting_config,ig_camera_android_whitelist_all_effects_in_pre,ig_android_shopping_influencer_creator_nux,ig_android_mobileboost_blacklist,ig_android_direct_gifs_killswitch,ig_android_global_scheduler_direct,ig_android_image_display_logging,ig_android_global_scheduler_infra,ig_igtv_branded_content_killswitch,ig_cg_donor_duplicate_sticker,ig_launcher_explore_verified_badge_on_ads,ig_android_cold_start_class_preloading,ig_camera_android_attributed_effects_endpoint_api_query_config,ig_android_highlighted_products_business_option,ig_direct_join_chat_sticker,ig_android_direct_admin_tools_requests,ig_android_rage_shake_whitelist,ig_android_shopping_ads_cta_rollout,ig_android_igtv_segmentation,ig_launcher_force_switch_on_dialog,ig_android_iab_fullscreen_experience_config,ig_android_instacrash,ig_android_specific_story_url_handling_killswitch,ig_mobile_consent_settings_killswitch,ig_android_influencer_monetization_hub_launcher,ig_and roid_scroll_perf_mobile_boost_launcher,ig_android_cx_stories_about_you,ig_android_replay_safe,ig_android_stories_scroll_perf_misc_fixes_h2_2019,ig_android_shopping_django_product_search,ig_direct_giphy_gifs_rating,ig_android_ppr_url_logging_config,ig_canvas_ad_pixel,ig_strongly_referenced_mediacache,ig_android_direct_show_threads_status_in_direct,ig_camera_ard_brotli_model_compression,ig_image_pipeline_skip_disk_config,ig_android_explore_grid_viewpoint,ig_android_iab_persistent_process,ig_android_in_process_iab,ig_android_launcher_value_consistency_checker,ig_launcher_ig_explore_peek_and_sfplt_android,ig_android_skip_photo_finish,ig_biz_android_use_professional_account_term,ig_android_settings_search,ig_android_direct_presence_media_viewer,ig_launcher_explore_navigation_redesign_android,ig_launcher_ig_android_network_stack_cap_api_request_qe,ig_qe_value_consistency_checker,ig_stories_fundraiser_view_payment_address,ig_business_create_donation_android,ig_android_qp_waterfall_logging,ig_android_bloks_demos,ig_redex_dynamic_analysis,ig_android_bug_report_screen_record,ig_shopping_android_carousel_product_ids_fix_killswitch,ig_shopping_android_creators_new_tagging_flow,ig_android_direct_threads_app_dogfooding_flags,ig_shopping_camera_android,ig_android_qp_keep_promotion_during_cooldown,ig_android_qp_slot_cooldown_enabled_universe,ig_android_request_cap_tuning_with_bandwidth,ig_android_client_config_realtime_subscription,ig_launcher_ig_android_network_request_cap_tuning_qe,ig_android_concurrent_coldstart,ig_android_gps_improvements_launcher,ig_android_notification_setting_sync,ig_android_stories_canvas_mode_colour_wheel,ig_android_iab_session_logging_config,ig_android_network_trace_migration,ig_android_extra_native_debugging_info,ig_android_insights_top_account_dialog_tooltip,ig_launcher_ig_android_dispatcher_viewpoint_onscreen_updater_qe,ig_android_disable_browser_multiple_windows,ig_contact_invites_netego_killswitch,ig_android_update_items_header_height_launcher,ig_android_bulk_tag_untag_killswitch,ig_android_employee_options,ig_launcher_ig_android_video_pending_request_store_qe,ig_story_insights_entry,ig_android_creator_multi_select,ig_android_direct_new_media_viewer,ig_android_gps_profile_launcher,ig_android_direct_real_names_launcher,ig_fev_info_launcher,ig_android_remove_request_params_in_network_trace,ig_android_rageshake_redesign,ig_launcher_ig_android_network_stack_queue_undefined_request_qe,ig_cx_promotion_tooltip,ig_text_response_bottom_sheet,ig_android_carrier_signal_timestamp_max_age,ig_android_qp_xshare_to_fb,ig_android_rollout_gating_payment_settings,ig_android_mobile_boost_kill_switch,ig_android_betamap_cold_start,ig_android_media_store,ig_android_async_view_model_launcher,ig_android_newsfeed_recyclerview,ig_android_feed_optimistic_upload,ig_android_fix_render_backtrack_reporting,ig_delink_lasso_accounts,ig_android_feed_report_ranking_issue,ig_android_shopping_insights_events_validator,ig_biz_android_new_logging_architecture,ig_launcher_ig_android_reactnative_realtime_ota,ig_android_boomerang_crash_android_go,ig_android_shopping_influencer_product_sticker_editing,ig_camera_android_max_vertex_texture_launcher,bloks_suggested_hashtag';
+ const SIG_KEY_VERSION = '4';
+
+ // Endpoint Constants.
+ const BLOCK_VERSIONING_ID = 'a4b4b8345a67599efe117ad96b8a9cb357bb51ac3ee00c3a48be37ce10f2bb4c'; // getTimelineFeed()
+ const BATCH_SURFACES = [
+ ['4715', 'instagram_feed_header'],
+ ['5734', 'instagram_feed_prompt'],
+ ['5858', 'instagram_feed_tool_tip'],
+ ];
+ const BATCH_QUERY = 'viewer() {eligible_promotions.trigger_context_v2().ig_parameters().trigger_name().surface_nux_id().external_gating_permitted_qps().supports_client_filters(true).include_holdouts(true) {edges {client_ttl_seconds,log_eligibility_waterfall,is_holdout,priority,time_range {start,end},node {id,promotion_id,logging_data,max_impressions,triggers,contextual_filters {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}},clauses {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}},clauses {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}},clauses {clause_type,filters {filter_type,unknown_action,value {name,required,bool_value,int_value,string_value},extra_datas {name,required,bool_value,int_value,string_value}}}}}},is_uncancelable,template {name,parameters {name,required,bool_value,string_value,color_value,}},creatives {title {text},content {text},footer {text},social_context {text},social_context_images,primary_action{title {text},url,limit,dismiss_promotion},secondary_action{title {text},url,limit,dismiss_promotion},dismiss_action{title {text},url,limit,dismiss_promotion},image.scale() {uri,width,height}}}}}}';
+ const BATCH_SCALE = 3;
+ const BATCH_VERSION = 1;
+
+ // User-Agent Constants.
+ const USER_AGENT_LOCALE = 'en_US'; // "language_COUNTRY".
+
+ // HTTP Protocol Constants.
+ const ACCEPT_LANGUAGE = 'en-US'; // "language-COUNTRY".
+ const ACCEPT_ENCODING = 'gzip,deflate';
+ const CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=UTF-8';
+ const X_FB_HTTP_Engine = 'Liger';
+ const X_IG_Connection_Type = 'WIFI';
+ const X_IG_Capabilities = '3brTvw==';
+
+ // Supported Capabilities
+ const SUPPORTED_CAPABILITIES = [
+ [
+ 'name' => 'SUPPORTED_SDK_VERSIONS',
+ 'value' => '13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0,25.0,26.0,27.0,28.0,29.0,30.0,31.0,32.0,33.0,34.0,35.0,36.0,37.0,38.0,39.0,40.0,41.0,42.0,43.0,44.0,45.0,46.0,47.0,48.0,49.0,50.0,51.0,52.0,53.0,54.0,55.0,56.0,57.0,58.0,59.0,60.0,61.0,62.0,63.0,64.0,65.0,66.0,67.0,68.0,69.0',
+ ],
+ [
+ 'name' => 'FACE_TRACKER_VERSION',
+ 'value' => '12',
+ ],
+ [
+ 'name' => 'segmentation',
+ 'value' => 'segmentation_enabled',
+ ],
+ [
+ 'name' => 'COMPRESSION',
+ 'value' => 'ETC2_COMPRESSION',
+ ],
+ [
+ 'name' => 'world_tracker',
+ 'value' => 'world_tracker_enabled',
+ ],
+ [
+ 'name' => 'gyroscope',
+ 'value' => 'gyroscope_enabled',
+ ],
+ ];
+
+ // Facebook Constants.
+ const FACEBOOK_OTA_FIELDS = 'update%7Bdownload_uri%2Cdownload_uri_delta_base%2Cversion_code_delta_base%2Cdownload_uri_delta%2Cfallback_to_full_update%2Cfile_size_delta%2Cversion_code%2Cpublished_date%2Cfile_size%2Cota_bundle_type%2Cresources_checksum%2Callowed_networks%2Crelease_id%7D';
+ const FACEBOOK_ORCA_PROTOCOL_VERSION = 20150314;
+ const FACEBOOK_ORCA_APPLICATION_ID = '124024574287414';
+ const FACEBOOK_ANALYTICS_APPLICATION_ID = '567067343352427';
+
+ // MQTT Constants.
+ const PLATFORM = 'android';
+ const FBNS_APPLICATION_NAME = 'MQTT';
+ const INSTAGRAM_APPLICATION_NAME = 'Instagram';
+ const PACKAGE_NAME = 'com.instagram.android';
+
+ // Instagram Quick Promotions.
+ const SURFACE_PARAM = [
+ 4715,
+ 5734,
+ ];
+
+ // Internal Feedtype Constants. CRITICAL: EVERY value here MUST be unique!
+ const FEED_TIMELINE = 1;
+ const FEED_TIMELINE_ALBUM = 2;
+ const FEED_STORY = 3;
+ const FEED_DIRECT = 4;
+ const FEED_DIRECT_STORY = 5;
+ const FEED_TV = 6;
+
+ // General Constants.
+ const SRC_DIR = __DIR__; // Absolute path to the "src" folder.
+
+ // Story view modes.
+ const STORY_VIEW_MODE_ONCE = 'once';
+ const STORY_VIEW_MODE_REPLAYABLE = 'replayable';
+}
diff --git a/vendor/mgp25/instagram-php/src/Debug.php b/vendor/mgp25/instagram-php/src/Debug.php
new file mode 100755
index 0000000..6d445c7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Debug.php
@@ -0,0 +1,67 @@
+ 1000) {
+ $response = mb_substr($response, 0, 1000, 'utf8').'...';
+ }
+ echo $res.$response."\n\n";
+ }
+
+ public static function printPostData(
+ $post)
+ {
+ $gzip = mb_strpos($post, "\x1f"."\x8b"."\x08", 0, 'US-ASCII') === 0;
+ if (PHP_SAPI === 'cli') {
+ $dat = Utils::colouredString(($gzip ? 'DECODED ' : '').'DATA: ', 'yellow');
+ } else {
+ $dat = 'DATA: ';
+ }
+ echo $dat.urldecode(($gzip ? zlib_decode($post) : $post))."\n";
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Devices/Device.php b/vendor/mgp25/instagram-php/src/Devices/Device.php
new file mode 100755
index 0000000..8f4bbf8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Devices/Device.php
@@ -0,0 +1,268 @@
+_appVersion = $appVersion;
+ $this->_versionCode = $versionCode;
+ $this->_userLocale = $userLocale;
+
+ // Use the provided device if a valid good device. Otherwise use random.
+ if ($autoFallback && (!is_string($deviceString) || !GoodDevices::isGoodDevice($deviceString))) {
+ $deviceString = GoodDevices::getRandomGoodDevice();
+ }
+
+ // Initialize ourselves from the device string.
+ $this->_initFromDeviceString($deviceString);
+ }
+
+ /**
+ * Parses a device string into its component parts and sets internal fields.
+ *
+ * Does no validation to make sure the string is one of the good devices.
+ *
+ * @param string $deviceString
+ *
+ * @throws \RuntimeException If the device string is invalid.
+ */
+ protected function _initFromDeviceString(
+ $deviceString)
+ {
+ if (!is_string($deviceString) || empty($deviceString)) {
+ throw new \RuntimeException('Device string is empty.');
+ }
+
+ // Split the device identifier into its components and verify it.
+ $parts = explode('; ', $deviceString);
+ if (count($parts) !== 7) {
+ throw new \RuntimeException(sprintf('Device string "%s" does not conform to the required device format.', $deviceString));
+ }
+
+ // Check the android version.
+ $androidOS = explode('/', $parts[0], 2);
+ if (version_compare($androidOS[1], self::REQUIRED_ANDROID_VERSION, '<')) {
+ throw new \RuntimeException(sprintf('Device string "%s" does not meet the minimum required Android version "%s" for Instagram.', $deviceString, self::REQUIRED_ANDROID_VERSION));
+ }
+
+ // Check the screen resolution.
+ $resolution = explode('x', $parts[2], 2);
+ $pixelCount = (int) $resolution[0] * (int) $resolution[1];
+ if ($pixelCount < 2073600) { // 1920x1080.
+ throw new \RuntimeException(sprintf('Device string "%s" does not meet the minimum resolution requirement of 1920x1080.', $deviceString));
+ }
+
+ // Extract "Manufacturer/Brand" string into separate fields.
+ $manufacturerAndBrand = explode('/', $parts[3], 2);
+
+ // Store all field values.
+ $this->_deviceString = $deviceString;
+ $this->_androidVersion = $androidOS[0]; // "23".
+ $this->_androidRelease = $androidOS[1]; // "6.0.1".
+ $this->_dpi = $parts[1];
+ $this->_resolution = $parts[2];
+ $this->_manufacturer = $manufacturerAndBrand[0];
+ $this->_brand = (isset($manufacturerAndBrand[1])
+ ? $manufacturerAndBrand[1] : null);
+ $this->_model = $parts[4];
+ $this->_device = $parts[5];
+ $this->_cpu = $parts[6];
+
+ // Build our user agent.
+ $this->_userAgent = UserAgent::buildUserAgent($this->_appVersion, $this->_userLocale, $this);
+
+ $this->_fbUserAgents = [];
+ }
+
+ // Getters for all properties...
+
+ /** {@inheritdoc} */
+ public function getDeviceString()
+ {
+ return $this->_deviceString;
+ }
+
+ /** {@inheritdoc} */
+ public function getUserAgent()
+ {
+ return $this->_userAgent;
+ }
+
+ /** {@inheritdoc} */
+ public function getFbUserAgent(
+ $appName)
+ {
+ if (!isset($this->_fbUserAgents[$appName])) {
+ $this->_fbUserAgents[$appName] = UserAgent::buildFbUserAgent(
+ $appName,
+ $this->_appVersion,
+ $this->_versionCode,
+ $this->_userLocale,
+ $this
+ );
+ }
+
+ return $this->_fbUserAgents[$appName];
+ }
+
+ /** {@inheritdoc} */
+ public function getAndroidVersion()
+ {
+ return $this->_androidVersion;
+ }
+
+ /** {@inheritdoc} */
+ public function getAndroidRelease()
+ {
+ return $this->_androidRelease;
+ }
+
+ /** {@inheritdoc} */
+ public function getDPI()
+ {
+ return $this->_dpi;
+ }
+
+ /** {@inheritdoc} */
+ public function getResolution()
+ {
+ return $this->_resolution;
+ }
+
+ /** {@inheritdoc} */
+ public function getManufacturer()
+ {
+ return $this->_manufacturer;
+ }
+
+ /** {@inheritdoc} */
+ public function getBrand()
+ {
+ return $this->_brand;
+ }
+
+ /** {@inheritdoc} */
+ public function getModel()
+ {
+ return $this->_model;
+ }
+
+ /** {@inheritdoc} */
+ public function getDevice()
+ {
+ return $this->_device;
+ }
+
+ /** {@inheritdoc} */
+ public function getCPU()
+ {
+ return $this->_cpu;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Devices/DeviceInterface.php b/vendor/mgp25/instagram-php/src/Devices/DeviceInterface.php
new file mode 100755
index 0000000..413946b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Devices/DeviceInterface.php
@@ -0,0 +1,93 @@
+getManufacturer();
+ if ($device->getBrand() !== null) {
+ $manufacturerWithBrand .= '/'.$device->getBrand();
+ }
+
+ // Generate the final User-Agent string.
+ return sprintf(
+ self::USER_AGENT_FORMAT,
+ $appVersion, // App version ("27.0.0.7.97").
+ $device->getAndroidVersion(),
+ $device->getAndroidRelease(),
+ $device->getDPI(),
+ $device->getResolution(),
+ $manufacturerWithBrand,
+ $device->getModel(),
+ $device->getDevice(),
+ $device->getCPU(),
+ $userLocale, // Locale ("en_US").
+ Constants::VERSION_CODE
+ );
+ }
+
+ /**
+ * Escape string for Facebook User-Agent string.
+ *
+ * @param $string
+ *
+ * @return string
+ */
+ protected static function _escapeFbString(
+ $string)
+ {
+ $result = '';
+ for ($i = 0; $i < strlen($string); ++$i) {
+ $char = $string[$i];
+ if ($char === '&') {
+ $result .= '&';
+ } elseif ($char < ' ' || $char > '~') {
+ $result .= sprintf('%d;', ord($char));
+ } else {
+ $result .= $char;
+ }
+ }
+ $result = strtr($result, ['/' => '-', ';' => '-']);
+
+ return $result;
+ }
+
+ /**
+ * Generates a FB User Agent string from a DeviceInterface.
+ *
+ * @param string $appName Application name.
+ * @param string $appVersion Instagram client app version.
+ * @param string $versionCode Instagram client app version code.
+ * @param string $userLocale The user's locale, such as "en_US".
+ * @param DeviceInterface $device
+ *
+ * @throws \InvalidArgumentException If the device parameter is invalid.
+ *
+ * @return string
+ */
+ public static function buildFbUserAgent(
+ $appName,
+ $appVersion,
+ $versionCode,
+ $userLocale,
+ DeviceInterface $device)
+ {
+ list($width, $height) = explode('x', $device->getResolution());
+ $density = round(str_replace('dpi', '', $device->getDPI()) / 160, 1);
+ $result = [
+ 'FBAN' => $appName,
+ 'FBAV' => $appVersion,
+ 'FBBV' => $versionCode,
+ 'FBDM' => sprintf('{density=%.1f,width=%d,height=%d}', $density, $width, $height),
+ 'FBLC' => $userLocale,
+ 'FBCR' => '', // We don't have cellular.
+ 'FBMF' => self::_escapeFbString($device->getManufacturer()),
+ 'FBBD' => self::_escapeFbString($device->getBrand() ? $device->getBrand() : $device->getManufacturer()),
+ 'FBPN' => Constants::PACKAGE_NAME,
+ 'FBDV' => self::_escapeFbString($device->getModel()),
+ 'FBSV' => self::_escapeFbString($device->getAndroidRelease()),
+ 'FBLR' => 0, // android.hardware.ram.low
+ 'FBBK' => 1, // Const (at least in 10.12.0).
+ 'FBCA' => self::_escapeFbString(GoodDevices::CPU_ABI),
+ ];
+ array_walk($result, function (&$value, $key) {
+ $value = sprintf('%s/%s', $key, $value);
+ });
+
+ // Trailing semicolon is essential.
+ return '['.implode(';', $result).';]';
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Exception/AccountDisabledException.php b/vendor/mgp25/instagram-php/src/Exception/AccountDisabledException.php
new file mode 100755
index 0000000..c62a056
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Exception/AccountDisabledException.php
@@ -0,0 +1,7 @@
+_response !== null ? true : false;
+ }
+
+ /**
+ * Get the full server response.
+ *
+ * @return Response|null The full response if one exists, otherwise NULL.
+ *
+ * @see InstagramException::hasResponse()
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Internal. Sets the value of the full server response.
+ *
+ * @param Response|null $response The response value.
+ */
+ public function setResponse(
+ Response $response = null)
+ {
+ $this->_response = $response;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Exception/InternalException.php b/vendor/mgp25/instagram-php/src/Exception/InternalException.php
new file mode 100755
index 0000000..0c70bdf
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Exception/InternalException.php
@@ -0,0 +1,10 @@
+_guzzleException = $guzzleException;
+
+ // Ensure that the message is nicely formatted and follows our standard.
+ $message = 'Network: '.ServerMessageThrower::prettifyMessage($this->_guzzleException->getMessage());
+
+ // Construct with our custom message.
+ // NOTE: We DON'T assign the guzzleException to "$previous", otherwise
+ // the user would still see something like "Uncaught GuzzleHttp\Exception\
+ // RequestException" and Guzzle's stack trace, instead of "Uncaught
+ // InstagramAPI\Exception\NetworkException" and OUR correct stack trace.
+ parent::__construct($message);
+ }
+
+ /**
+ * Gets the original Guzzle exception, which contains much more details.
+ *
+ * @return \Exception The original Guzzle exception.
+ */
+ public function getGuzzleException()
+ {
+ return $this->_guzzleException;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Exception/NotFoundException.php b/vendor/mgp25/instagram-php/src/Exception/NotFoundException.php
new file mode 100755
index 0000000..2bcd379
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Exception/NotFoundException.php
@@ -0,0 +1,11 @@
+ ['login_required'],
+ 'CheckpointRequiredException' => [
+ 'checkpoint_required', // message
+ 'checkpoint_challenge_required', // error_type
+ ],
+ 'ChallengeRequiredException' => ['challenge_required'],
+ 'FeedbackRequiredException' => ['feedback_required'],
+ 'ConsentRequiredException' => ['consent_required'],
+ 'IncorrectPasswordException' => [
+ // "The password you entered is incorrect".
+ '/password(.*?)incorrect/', // message
+ 'bad_password', // error_type
+ ],
+ 'InvalidSmsCodeException' => [
+ // "Please check the security code we sent you and try again".
+ '/check(.*?)security(.*?)code/', // message
+ 'sms_code_validation_code_invalid', // error_type
+ ],
+ 'AccountDisabledException' => [
+ // "Your account has been disabled for violating our terms".
+ '/account(.*?)disabled(.*?)violating/',
+ ],
+ 'SentryBlockException' => ['sentry_block'],
+ 'InvalidUserException' => [
+ // "The username you entered doesn't appear to belong to an account"
+ '/username(.*?)doesn\'t(.*?)belong/', // message
+ 'invalid_user', // error_type
+ ],
+ 'ForcedPasswordResetException' => ['/reset(.*?)password/'],
+ ];
+
+ /**
+ * Parses a server message and throws the appropriate exception.
+ *
+ * Uses the generic EndpointException if no other exceptions match.
+ *
+ * @param string|null $prefixString What prefix to use for
+ * the message in the
+ * final exception. Should
+ * be something helpful
+ * such as the name of the
+ * class or function which
+ * threw. Can be `NULL`.
+ * @param string|null $serverMessage The failure string from
+ * Instagram's API (from
+ * `getMessage()`). Might
+ * be empty in some cases.
+ * @param Response|null $serverResponse The complete server
+ * response object, if one
+ * is available
+ * (optional).
+ * @param HttpResponseInterface|null $httpResponse The HTTP response
+ * object (if available).
+ *
+ * @throws InstagramException The appropriate exception.
+ */
+ public static function autoThrow(
+ $prefixString,
+ $serverMessage,
+ Response $serverResponse = null,
+ HttpResponseInterface $httpResponse = null)
+ {
+ // We will analyze both the `message` AND `error_type` (if available).
+ $messages = [$serverMessage];
+ $serverErrorType = null;
+ if ($serverResponse instanceof Response) {
+ // We are reading a property that isn't defined in the class
+ // property map, so we must use "has" first, to ensure it exists.
+ if ($serverResponse->hasErrorType()
+ && is_string($serverResponse->getErrorType())) {
+ $serverErrorType = $serverResponse->getErrorType();
+ $messages[] = $serverErrorType;
+ }
+ }
+
+ $exceptionClass = null;
+
+ // Check if the server message is in our CRITICAL exception table.
+ foreach ($messages as $message) {
+ foreach (self::EXCEPTION_MAP as $className => $patterns) {
+ foreach ($patterns as $pattern) {
+ if ($pattern[0] == '/') {
+ // Regex check.
+ if (preg_match($pattern, $message)) {
+ $exceptionClass = $className;
+ break 3;
+ }
+ } else {
+ // Regular string search.
+ if (strpos($message, $pattern) !== false) {
+ $exceptionClass = $className;
+ break 3;
+ }
+ }
+ }
+ }
+ }
+
+ // Check the HTTP status code if no critical exception has been found.
+ if ($exceptionClass === null) {
+ // NOTE FOR CONTRIBUTORS: All HTTP status exceptions below MUST be
+ // derived from EndpointException, since all HTTP errors are
+ // endpoint-error-related responses and MUST be easily catchable!
+ $httpStatusCode = $httpResponse !== null ? $httpResponse->getStatusCode() : null;
+ switch ($httpStatusCode) {
+ case 400:
+ $exceptionClass = 'BadRequestException';
+ break;
+ case 404:
+ $exceptionClass = 'NotFoundException';
+ break;
+ default:
+ // No critical exceptions and no HTTP code exceptions have
+ // been found, so use the generic "API function exception"!
+ $exceptionClass = 'EndpointException';
+ }
+ }
+
+ // We need to specify the full namespace path to the exception class.
+ $fullClassPath = '\\'.__NAMESPACE__.'\\'.$exceptionClass;
+
+ // Determine which message to display to the user.
+ $displayMessage = is_string($serverMessage) && strlen($serverMessage)
+ ? $serverMessage : $serverErrorType;
+ if (!is_string($displayMessage) || !strlen($displayMessage)) {
+ $displayMessage = 'Request failed.';
+ }
+
+ // Some Instagram messages already have punctuation, and others need it.
+ $displayMessage = self::prettifyMessage($displayMessage);
+
+ // Create an instance of the final exception class, with the pretty msg.
+ $e = new $fullClassPath(
+ $prefixString !== null
+ ? sprintf('%s: %s', $prefixString, $displayMessage)
+ : $displayMessage
+ );
+
+ // Attach the server response to the exception, IF a response exists.
+ // NOTE: Only possible on exceptions derived from InstagramException.
+ if ($serverResponse instanceof Response
+ && $e instanceof \InstagramAPI\Exception\InstagramException) {
+ $e->setResponse($serverResponse);
+ }
+
+ throw $e;
+ }
+
+ /**
+ * Nicely reformats externally generated exception messages.
+ *
+ * This is used for guaranteeing consistent message formatting with full
+ * English sentences, ready for display to the user.
+ *
+ * @param string $message The original message.
+ *
+ * @return string The cleaned-up message.
+ */
+ public static function prettifyMessage(
+ $message)
+ {
+ // Some messages already have punctuation, and others need it. Prettify
+ // the message by ensuring that it ALWAYS ends in punctuation, for
+ // consistency with all of our internal error messages.
+ $lastChar = substr($message, -1);
+ if ($lastChar !== '' && $lastChar !== '.' && $lastChar !== '!' && $lastChar !== '?') {
+ $message .= '.';
+ }
+
+ // Guarantee that the first letter is uppercase.
+ $message = ucfirst($message);
+
+ // Replace all underscores (ie. "Login_required.") with spaces.
+ $message = str_replace('_', ' ', $message);
+
+ return $message;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Exception/SettingsException.php b/vendor/mgp25/instagram-php/src/Exception/SettingsException.php
new file mode 100755
index 0000000..6f2c42c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Exception/SettingsException.php
@@ -0,0 +1,10 @@
+If you truly want to enable incorrect website usage by directly embedding this application emulator library in your page, then you can do that AT YOUR OWN RISK by setting the following flag before you create the Instagram() object:
'.PHP_EOL;
+ exit(0); // Exit without error to avoid triggering Error 500.
+ }
+
+ // Prevent people from running this library on ancient PHP versions, and
+ // verify that people have the most critically important PHP extensions.
+ // NOTE: All of these are marked as requirements in composer.json, but
+ // some people install the library at home and then move it somewhere
+ // else without the requirements, and then blame us for their errors.
+ if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50600) {
+ throw new \InstagramAPI\Exception\InternalException(
+ 'You must have PHP 5.6 or higher to use the Instagram API library.'
+ );
+ }
+ static $extensions = ['curl', 'mbstring', 'gd', 'exif', 'zlib'];
+ foreach ($extensions as $ext) {
+ if (!@extension_loaded($ext)) {
+ throw new \InstagramAPI\Exception\InternalException(sprintf(
+ 'You must have the "%s" PHP extension to use the Instagram API library.',
+ $ext
+ ));
+ }
+ }
+
+ // Debugging options.
+ $this->debug = $debug;
+ $this->truncatedDebug = $truncatedDebug;
+
+ // Load all function collections.
+ $this->account = new Request\Account($this);
+ $this->business = new Request\Business($this);
+ $this->collection = new Request\Collection($this);
+ $this->creative = new Request\Creative($this);
+ $this->direct = new Request\Direct($this);
+ $this->discover = new Request\Discover($this);
+ $this->hashtag = new Request\Hashtag($this);
+ $this->highlight = new Request\Highlight($this);
+ $this->tv = new Request\TV($this);
+ $this->internal = new Request\Internal($this);
+ $this->live = new Request\Live($this);
+ $this->location = new Request\Location($this);
+ $this->media = new Request\Media($this);
+ $this->people = new Request\People($this);
+ $this->push = new Request\Push($this);
+ $this->shopping = new Request\Shopping($this);
+ $this->story = new Request\Story($this);
+ $this->timeline = new Request\Timeline($this);
+ $this->usertag = new Request\Usertag($this);
+
+ // Configure the settings storage and network client.
+ $self = $this;
+ $this->settings = Settings\Factory::createHandler(
+ $storageConfig,
+ [
+ // This saves all user session cookies "in bulk" at script exit
+ // or when switching to a different user, so that it only needs
+ // to write cookies to storage a few times per user session:
+ 'onCloseUser' => function ($storage) use ($self) {
+ if ($self->client instanceof Client) {
+ $self->client->saveCookieJar();
+ }
+ },
+ ]
+ );
+ $this->client = new Client($this);
+ $this->experiments = [];
+ }
+
+ /**
+ * Controls the SSL verification behavior of the Client.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#verify
+ *
+ * @param bool|string $state TRUE to verify using PHP's default CA bundle,
+ * FALSE to disable SSL verification (this is
+ * insecure!), String to verify using this path to
+ * a custom CA bundle file.
+ */
+ public function setVerifySSL(
+ $state)
+ {
+ $this->client->setVerifySSL($state);
+ }
+
+ /**
+ * Gets the current SSL verification behavior of the Client.
+ *
+ * @return bool|string
+ */
+ public function getVerifySSL()
+ {
+ return $this->client->getVerifySSL();
+ }
+
+ /**
+ * Set the proxy to use for requests.
+ *
+ * @see http://docs.guzzlephp.org/en/latest/request-options.html#proxy
+ *
+ * @param string|array|null $value String or Array specifying a proxy in
+ * Guzzle format, or NULL to disable
+ * proxying.
+ */
+ public function setProxy(
+ $value)
+ {
+ $this->client->setProxy($value);
+ }
+
+ /**
+ * Gets the current proxy used for requests.
+ *
+ * @return string|array|null
+ */
+ public function getProxy()
+ {
+ return $this->client->getProxy();
+ }
+
+ /**
+ * Sets the network interface override to use.
+ *
+ * Only works if Guzzle is using the cURL backend. But that's
+ * almost always the case, on most PHP installations.
+ *
+ * @see http://php.net/curl_setopt CURLOPT_INTERFACE
+ *
+ * @param string|null $value Interface name, IP address or hostname, or NULL
+ * to disable override and let Guzzle use any
+ * interface.
+ */
+ public function setOutputInterface(
+ $value)
+ {
+ $this->client->setOutputInterface($value);
+ }
+
+ /**
+ * Gets the current network interface override used for requests.
+ *
+ * @return string|null
+ */
+ public function getOutputInterface()
+ {
+ return $this->client->getOutputInterface();
+ }
+
+ /**
+ * Login to Instagram or automatically resume and refresh previous session.
+ *
+ * Sets the active account for the class instance. You can call this
+ * multiple times to switch between multiple Instagram accounts.
+ *
+ * WARNING: You MUST run this function EVERY time your script runs! It
+ * handles automatic session resume and relogin and app session state
+ * refresh and other absolutely *vital* things that are important if you
+ * don't want to be banned from Instagram!
+ *
+ * WARNING: This function MAY return a CHALLENGE telling you that the
+ * account needs two-factor login before letting you log in! Read the
+ * two-factor login example to see how to handle that.
+ *
+ * @param string $username Your Instagram username.
+ * You can also use your email or phone,
+ * but take in mind that they won't work
+ * when you have two factor auth enabled.
+ * @param string $password Your Instagram password.
+ * @param int $appRefreshInterval How frequently `login()` should act
+ * like an Instagram app that's been
+ * closed and reopened and needs to
+ * "refresh its state", by asking for
+ * extended account state details.
+ * Default: After `1800` seconds, meaning
+ * `30` minutes after the last
+ * state-refreshing `login()` call.
+ * This CANNOT be longer than `6` hours.
+ * Read `_sendLoginFlow()`! The shorter
+ * your delay is the BETTER. You may even
+ * want to set it to an even LOWER value
+ * than the default 30 minutes!
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse|null A login response if a
+ * full (re-)login
+ * happens, otherwise
+ * `NULL` if an existing
+ * session is resumed.
+ */
+ public function login(
+ $username,
+ $password,
+ $appRefreshInterval = 1800)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to login().');
+ }
+
+ return $this->_login($username, $password, false, $appRefreshInterval);
+ }
+
+ /**
+ * Internal login handler.
+ *
+ * @param string $username
+ * @param string $password
+ * @param bool $forceLogin Force login to Instagram instead of
+ * resuming previous session. Used
+ * internally to do a new, full relogin
+ * when we detect an expired/invalid
+ * previous session.
+ * @param int $appRefreshInterval
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse|null
+ *
+ * @see Instagram::login() The public login handler with a full description.
+ */
+ protected function _login(
+ $username,
+ $password,
+ $forceLogin = false,
+ $appRefreshInterval = 1800)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to _login().');
+ }
+
+ // Switch the currently active user/pass if the details are different.
+ if ($this->username !== $username || $this->password !== $password) {
+ $this->_setUser($username, $password);
+ }
+
+ // Perform a full relogin if necessary.
+ if (!$this->isMaybeLoggedIn || $forceLogin) {
+ $this->_sendPreLoginFlow();
+
+ try {
+ $response = $this->request('accounts/login/')
+ ->setNeedsAuth(false)
+ ->addPost('country_codes', '[{"country_code":"1","source":["default"]}]')
+ ->addPost('phone_id', $this->phone_id)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->addPost('username', $this->username)
+ ->addPost('adid', $this->advertising_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('password', $this->password)
+ ->addPost('google_tokens', '[]')
+ ->addPost('login_attempt_count', 0)
+ ->getResponse(new Response\LoginResponse());
+ } catch (\InstagramAPI\Exception\InstagramException $e) {
+ if ($e->hasResponse() && $e->getResponse()->isTwoFactorRequired()) {
+ // Login failed because two-factor login is required.
+ // Return server response to tell user they need 2-factor.
+ return $e->getResponse();
+ } else {
+ // Login failed for some other reason... Re-throw error.
+ throw $e;
+ }
+ }
+
+ $this->_updateLoginState($response);
+
+ $this->_sendLoginFlow(true, $appRefreshInterval);
+
+ // Full (re-)login successfully completed. Return server response.
+ return $response;
+ }
+
+ // Attempt to resume an existing session, or full re-login if necessary.
+ // NOTE: The "return" here gives a LoginResponse in case of re-login.
+ return $this->_sendLoginFlow(false, $appRefreshInterval);
+ }
+
+ /**
+ * Finish a two-factor authenticated login.
+ *
+ * This function finishes a two-factor challenge that was provided by the
+ * regular `login()` function. If you successfully answer their challenge,
+ * you will be logged in after this function call.
+ *
+ * @param string $username Your Instagram username used for logging
+ * @param string $password Your Instagram password.
+ * @param string $twoFactorIdentifier Two factor identifier, obtained in
+ * login() response. Format: `123456`.
+ * @param string $verificationCode Verification code you have received
+ * via SMS.
+ * @param string $verificationMethod The verification method for 2FA. 1 is SMS,
+ * 2 is backup codes and 3 is TOTP.
+ * @param int $appRefreshInterval See `login()` for description of this
+ * parameter.
+ * @param string $usernameHandler Instagram username sent in the login response,
+ * Email and phone aren't allowed here.
+ * Default value is the first argument $username
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse
+ */
+ public function finishTwoFactorLogin(
+ $username,
+ $password,
+ $twoFactorIdentifier,
+ $verificationCode,
+ $verificationMethod = '1',
+ $appRefreshInterval = 1800,
+ $usernameHandler = null)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to finishTwoFactorLogin().');
+ }
+ if (empty($verificationCode) || empty($twoFactorIdentifier)) {
+ throw new \InvalidArgumentException('You must provide a verification code and two-factor identifier to finishTwoFactorLogin().');
+ }
+ if (!in_array($verificationMethod, ['1', '2', '3'], true)) {
+ throw new \InvalidArgumentException('You must provide a valid verification method value.');
+ }
+
+ // Switch the currently active user/pass if the details are different.
+ // NOTE: The username and password AREN'T actually necessary for THIS
+ // endpoint, but this extra step helps people who statelessly embed the
+ // library directly into a webpage, so they can `finishTwoFactorLogin()`
+ // on their second page load without having to begin any new `login()`
+ // call (since they did that in their previous webpage's library calls).
+ if ($this->username !== $username || $this->password !== $password) {
+ $this->_setUser($username, $password);
+ }
+
+ $username = ($usernameHandler !== null) ? $usernameHandler : $username;
+
+ // Remove all whitespace from the verification code.
+ $verificationCode = preg_replace('/\s+/', '', $verificationCode);
+
+ $response = $this->request('accounts/two_factor_login/')
+ ->setNeedsAuth(false)
+ // 1 - SMS, 2 - Backup codes, 3 - TOTP, 0 - ??
+ ->addPost('verification_method', $verificationMethod)
+ ->addPost('verification_code', $verificationCode)
+ ->addPost('trust_this_device', 1)
+ ->addPost('two_factor_identifier', $twoFactorIdentifier)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->addPost('username', $username)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->getResponse(new Response\LoginResponse());
+
+ $this->_updateLoginState($response);
+
+ $this->_sendLoginFlow(true, $appRefreshInterval);
+
+ return $response;
+ }
+
+ /**
+ * Request a new security code SMS for a Two Factor login account.
+ *
+ * NOTE: You should first attempt to `login()` which will automatically send
+ * you a two factor SMS. This function is just for asking for a new SMS if
+ * the old code has expired.
+ *
+ * NOTE: Instagram can only send you a new code every 60 seconds.
+ *
+ * @param string $username Your Instagram username.
+ * @param string $password Your Instagram password.
+ * @param string $twoFactorIdentifier Two factor identifier, obtained in
+ * `login()` response.
+ * @param string $usernameHandler Instagram username sent in the login response,
+ * Email and phone aren't allowed here.
+ * Default value is the first argument $username
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TwoFactorLoginSMSResponse
+ */
+ public function sendTwoFactorLoginSMS(
+ $username,
+ $password,
+ $twoFactorIdentifier,
+ $usernameHandler = null)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to sendTwoFactorLoginSMS().');
+ }
+ if (empty($twoFactorIdentifier)) {
+ throw new \InvalidArgumentException('You must provide a two-factor identifier to sendTwoFactorLoginSMS().');
+ }
+
+ // Switch the currently active user/pass if the details are different.
+ // NOTE: The password IS NOT actually necessary for THIS
+ // endpoint, but this extra step helps people who statelessly embed the
+ // library directly into a webpage, so they can `sendTwoFactorLoginSMS()`
+ // on their second page load without having to begin any new `login()`
+ // call (since they did that in their previous webpage's library calls).
+ if ($this->username !== $username || $this->password !== $password) {
+ $this->_setUser($username, $password);
+ }
+
+ $username = ($usernameHandler !== null) ? $usernameHandler : $username;
+
+ return $this->request('accounts/send_two_factor_login_sms/')
+ ->setNeedsAuth(false)
+ ->addPost('two_factor_identifier', $twoFactorIdentifier)
+ ->addPost('username', $username)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\TwoFactorLoginSMSResponse());
+ }
+
+ /**
+ * Request information about available password recovery methods for an account.
+ *
+ * This will tell you things such as whether SMS or EMAIL-based recovery is
+ * available for the given account name.
+ *
+ * `WARNING:` You can call this function without having called `login()`,
+ * but be aware that a user database entry will be created for every
+ * username you try to look up. This is ONLY meant for recovering your OWN
+ * accounts.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UsersLookupResponse
+ */
+ public function userLookup(
+ $username)
+ {
+ // Set active user (without pwd), and create database entry if new user.
+ $this->_setUserWithoutPassword($username);
+
+ return $this->request('users/lookup/')
+ ->setNeedsAuth(false)
+ ->addPost('q', $username)
+ ->addPost('directly_sign_in', true)
+ ->addPost('username', $username)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\UsersLookupResponse());
+ }
+
+ /**
+ * Request a recovery EMAIL to get back into your account.
+ *
+ * `WARNING:` You can call this function without having called `login()`,
+ * but be aware that a user database entry will be created for every
+ * username you try to look up. This is ONLY meant for recovering your OWN
+ * accounts.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RecoveryResponse
+ */
+ public function sendRecoveryEmail(
+ $username)
+ {
+ // Verify that they can use the recovery email option.
+ $userLookup = $this->userLookup($username);
+ if (!$userLookup->getCanEmailReset()) {
+ throw new \InstagramAPI\Exception\InternalException('Email recovery is not available, since your account lacks a verified email address.');
+ }
+
+ return $this->request('accounts/send_recovery_flow_email/')
+ ->setNeedsAuth(false)
+ ->addPost('query', $username)
+ ->addPost('adid', $this->advertising_id)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('guid', $this->uuid)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\RecoveryResponse());
+ }
+
+ /**
+ * Request a recovery SMS to get back into your account.
+ *
+ * `WARNING:` You can call this function without having called `login()`,
+ * but be aware that a user database entry will be created for every
+ * username you try to look up. This is ONLY meant for recovering your OWN
+ * accounts.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RecoveryResponse
+ */
+ public function sendRecoverySMS(
+ $username)
+ {
+ // Verify that they can use the recovery SMS option.
+ $userLookup = $this->userLookup($username);
+ if (!$userLookup->getHasValidPhone() || !$userLookup->getCanSmsReset()) {
+ throw new \InstagramAPI\Exception\InternalException('SMS recovery is not available, since your account lacks a verified phone number.');
+ }
+
+ return $this->request('users/lookup_phone/')
+ ->setNeedsAuth(false)
+ ->addPost('query', $username)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->getResponse(new Response\RecoveryResponse());
+ }
+
+ /**
+ * Set the active account for the class instance.
+ *
+ * We can call this multiple times to switch between multiple accounts.
+ *
+ * @param string $username Your Instagram username.
+ * @param string $password Your Instagram password.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _setUser(
+ $username,
+ $password)
+ {
+ if (empty($username) || empty($password)) {
+ throw new \InvalidArgumentException('You must provide a username and password to _setUser().');
+ }
+
+ // Load all settings from the storage and mark as current user.
+ $this->settings->setActiveUser($username);
+
+ // Generate the user's device instance, which will be created from the
+ // user's last-used device IF they've got a valid, good one stored.
+ // But if they've got a BAD/none, this will create a brand-new device.
+ $savedDeviceString = $this->settings->get('devicestring');
+ $this->device = new Devices\Device(
+ Constants::IG_VERSION,
+ Constants::VERSION_CODE,
+ Constants::USER_AGENT_LOCALE,
+ $savedDeviceString
+ );
+
+ // Get active device string so that we can compare it to any saved one.
+ $deviceString = $this->device->getDeviceString();
+
+ // Generate a brand-new device fingerprint if the device wasn't reused
+ // from settings, OR if any of the stored fingerprints are missing.
+ // NOTE: The regeneration when our device model changes is to avoid
+ // dangerously reusing the "previous phone's" unique hardware IDs.
+ // WARNING TO CONTRIBUTORS: Only add new parameter-checks here if they
+ // are CRITICALLY important to the particular device. We don't want to
+ // frivolously force the users to generate new device IDs constantly.
+ $resetCookieJar = false;
+ if ($deviceString !== $savedDeviceString // Brand new device, or missing
+ || empty($this->settings->get('uuid')) // one of the critically...
+ || empty($this->settings->get('phone_id')) // ...important device...
+ || empty($this->settings->get('device_id'))) { // ...parameters.
+ // Erase all previously stored device-specific settings and cookies.
+ $this->settings->eraseDeviceSettings();
+
+ // Save the chosen device string to settings.
+ $this->settings->set('devicestring', $deviceString);
+
+ // Generate hardware fingerprints for the new device.
+ $this->settings->set('device_id', Signatures::generateDeviceId());
+ $this->settings->set('phone_id', Signatures::generateUUID(true));
+ $this->settings->set('uuid', Signatures::generateUUID(true));
+
+ // Erase any stored account ID, to ensure that we detect ourselves
+ // as logged-out. This will force a new relogin from the new device.
+ $this->settings->set('account_id', '');
+
+ // We'll also need to throw out all previous cookies.
+ $resetCookieJar = true;
+ }
+
+ // Generate other missing values. These are for less critical parameters
+ // that don't need to trigger a complete device reset like above. For
+ // example, this is good for new parameters that Instagram introduces
+ // over time, since those can be added one-by-one over time here without
+ // needing to wipe/reset the whole device.
+ if (empty($this->settings->get('advertising_id'))) {
+ $this->settings->set('advertising_id', Signatures::generateUUID(true));
+ }
+ if (empty($this->settings->get('session_id'))) {
+ $this->settings->set('session_id', Signatures::generateUUID(true));
+ }
+
+ // Store various important parameters for easy access.
+ $this->username = $username;
+ $this->password = $password;
+ $this->uuid = $this->settings->get('uuid');
+ $this->advertising_id = $this->settings->get('advertising_id');
+ $this->device_id = $this->settings->get('device_id');
+ $this->phone_id = $this->settings->get('phone_id');
+ $this->session_id = $this->settings->get('session_id');
+ $this->experiments = $this->settings->getExperiments();
+
+ // Load the previous session details if we're possibly logged in.
+ if (!$resetCookieJar && $this->settings->isMaybeLoggedIn()) {
+ $this->isMaybeLoggedIn = true;
+ $this->account_id = $this->settings->get('account_id');
+ } else {
+ $this->isMaybeLoggedIn = false;
+ $this->account_id = null;
+ }
+
+ // Configures Client for current user AND updates isMaybeLoggedIn state
+ // if it fails to load the expected cookies from the user's jar.
+ // Must be done last here, so that isMaybeLoggedIn is properly updated!
+ // NOTE: If we generated a new device we start a new cookie jar.
+ $this->client->updateFromCurrentSettings($resetCookieJar);
+ }
+
+ /**
+ * Set the active account for the class instance, without knowing password.
+ *
+ * This internal function is used by all unauthenticated pre-login functions
+ * whenever they need to perform unauthenticated requests, such as looking
+ * up a user's account recovery options.
+ *
+ * `WARNING:` A user database entry will be created for every username you
+ * set as the active user, exactly like the normal `_setUser()` function.
+ * This is necessary so that we generate a user-device and data storage for
+ * each given username, which gives us necessary data such as a "device ID"
+ * for the new user's virtual device, to use in various API-call parameters.
+ *
+ * `WARNING:` This function CANNOT be used for performing logins, since
+ * Instagram will validate the password and will reject the missing
+ * password. It is ONLY meant to be used for *RECOVERY* PRE-LOGIN calls that
+ * need device parameters when the user DOESN'T KNOW their password yet.
+ *
+ * @param string $username Your Instagram username.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _setUserWithoutPassword(
+ $username)
+ {
+ if (empty($username) || !is_string($username)) {
+ throw new \InvalidArgumentException('You must provide a username.');
+ }
+
+ // Switch the currently active user/pass if the username is different.
+ // NOTE: Creates a user database (device) for the user if they're new!
+ // NOTE: Because we don't know their password, we'll mark the user as
+ // having "NOPASSWORD" as pwd. The user will fix that when/if they call
+ // `login()` with the ACTUAL password, which will tell us what it is.
+ // We CANNOT use an empty string since `_setUser()` will not allow that!
+ // NOTE: If the user tries to look up themselves WHILE they are logged
+ // in, we'll correctly NOT call `_setUser()` since they're already set.
+ if ($this->username !== $username) {
+ $this->_setUser($username, 'NOPASSWORD');
+ }
+ }
+
+ /**
+ * Updates the internal state after a successful login.
+ *
+ * @param Response\LoginResponse $response The login response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _updateLoginState(
+ Response\LoginResponse $response)
+ {
+ // This check is just protection against accidental bugs. It makes sure
+ // that we always call this function with a *successful* login response!
+ if (!$response instanceof Response\LoginResponse
+ || !$response->isOk()) {
+ throw new \InvalidArgumentException('Invalid login response provided to _updateLoginState().');
+ }
+
+ $this->isMaybeLoggedIn = true;
+ $this->account_id = $response->getLoggedInUser()->getPk();
+ $this->settings->set('account_id', $this->account_id);
+ $this->settings->set('last_login', time());
+ }
+
+ /**
+ * Sends pre-login flow. This is required to emulate real device behavior.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ protected function _sendPreLoginFlow()
+ {
+ // Reset zero rating rewrite rules.
+ $this->client->zeroRating()->reset();
+ // Calling this non-token API will put a csrftoken in our cookie
+ // jar. We must do this before any functions that require a token.
+ $this->internal->fetchZeroRatingToken();
+ $this->internal->bootstrapMsisdnHeader();
+ $this->internal->readMsisdnHeader('default');
+ $this->internal->syncDeviceFeatures(true);
+ $this->internal->sendLauncherSync(true);
+ $this->internal->bootstrapMsisdnHeader();
+ $this->internal->logAttribution();
+ $this->account->getPrefillCandidates();
+ $this->internal->readMsisdnHeader('default', true);
+ $this->account->setContactPointPrefill('prefill');
+ $this->internal->sendLauncherSync(true, true, true);
+ $this->internal->syncDeviceFeatures(true, true);
+ }
+
+ /**
+ * Registers available Push channels during the login flow.
+ */
+ protected function _registerPushChannels()
+ {
+ // Forcibly remove the stored token value if >24 hours old.
+ // This prevents us from constantly re-registering the user's
+ // "useless" token if they have stopped using the Push features.
+ try {
+ $lastFbnsToken = (int) $this->settings->get('last_fbns_token');
+ } catch (\Exception $e) {
+ $lastFbnsToken = null;
+ }
+ if (!$lastFbnsToken || $lastFbnsToken < strtotime('-24 hours')) {
+ try {
+ $this->settings->set('fbns_token', '');
+ } catch (\Exception $e) {
+ // Ignore storage errors.
+ }
+
+ return;
+ }
+
+ // Read our token from the storage.
+ try {
+ $fbnsToken = $this->settings->get('fbns_token');
+ } catch (\Exception $e) {
+ $fbnsToken = null;
+ }
+ if ($fbnsToken === null) {
+ return;
+ }
+
+ // Register our last token since we had a fresh (age <24 hours) one,
+ // or clear our stored token if we fail to register it again.
+ try {
+ $this->push->register('mqtt', $fbnsToken);
+ } catch (\Exception $e) {
+ try {
+ $this->settings->set('fbns_token', '');
+ } catch (\Exception $e) {
+ // Ignore storage errors.
+ }
+ }
+ }
+
+ /**
+ * Sends login flow. This is required to emulate real device behavior.
+ *
+ * @param bool $justLoggedIn Whether we have just performed a full
+ * relogin (rather than doing a resume).
+ * @param int $appRefreshInterval See `login()` for description of this
+ * parameter.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoginResponse|null A login response if a
+ * full (re-)login is
+ * needed during the login
+ * flow attempt, otherwise
+ * `NULL`.
+ */
+ protected function _sendLoginFlow(
+ $justLoggedIn,
+ $appRefreshInterval = 1800)
+ {
+ if (!is_int($appRefreshInterval) || $appRefreshInterval < 0) {
+ throw new \InvalidArgumentException("Instagram's app state refresh interval must be a positive integer.");
+ }
+ if ($appRefreshInterval > 21600) {
+ throw new \InvalidArgumentException("Instagram's app state refresh interval is NOT allowed to be higher than 6 hours, and the lower the better!");
+ }
+
+ // SUPER IMPORTANT:
+ //
+ // STOP trying to ask us to remove this code section!
+ //
+ // EVERY time the user presses their device's home button to leave the
+ // app and then comes back to the app, Instagram does ALL of these things
+ // to refresh its internal app state. We MUST emulate that perfectly,
+ // otherwise Instagram will silently detect you as a "fake" client
+ // after a while!
+ //
+ // You can configure the login's $appRefreshInterval in the function
+ // parameter above, but you should keep it VERY frequent (definitely
+ // NEVER longer than 6 hours), so that Instagram sees you as a real
+ // client that keeps quitting and opening their app like a REAL user!
+ //
+ // Otherwise they WILL detect you as a bot and silently BLOCK features
+ // or even ban you.
+ //
+ // You have been warned.
+ if ($justLoggedIn) {
+ // Reset zero rating rewrite rules.
+ $this->client->zeroRating()->reset();
+ // Perform the "user has just done a full login" API flow.
+ $this->account->getAccountFamily();
+ $this->internal->sendLauncherSync(false, false, true);
+ $this->internal->fetchZeroRatingToken();
+ $this->internal->syncUserFeatures();
+ $this->timeline->getTimelineFeed();
+ $this->story->getReelsTrayFeed('cold_start');
+ $this->internal->sendLauncherSync(false, false, true, true);
+ $this->story->getReelsMediaFeed($this->account_id);
+ $this->people->getRecentActivityInbox();
+ //TODO: Figure out why this isn't sending...
+// $this->internal->logResurrectAttribution();
+ $this->internal->getLoomFetchConfig();
+ $this->internal->getDeviceCapabilitiesDecisions();
+ $this->people->getBootstrapUsers();
+ $this->people->getInfoById($this->account_id);
+ $this->account->getLinkageStatus();
+ $this->creative->sendSupportedCapabilities();
+ $this->media->getBlockedMedia();
+ $this->internal->storeClientPushPermissions();
+ $this->internal->getQPCooldowns();
+ $this->_registerPushChannels();
+ $this->story->getReelsMediaFeed($this->account_id);
+ $this->discover->getExploreFeed(null, null, true);
+ $this->internal->getQPFetch();
+ $this->account->getProcessContactPointSignals();
+ $this->internal->getArlinkDownloadInfo();
+ $this->_registerPushChannels();
+ $this->people->getSharePrefill();
+ $this->direct->getPresences();
+ $this->direct->getInbox();
+ $this->direct->getInbox(null, 20, 10);
+ $this->_registerPushChannels();
+ $this->internal->getFacebookOTA();
+ } else {
+ $lastLoginTime = $this->settings->get('last_login');
+ $isSessionExpired = $lastLoginTime === null || (time() - $lastLoginTime) > $appRefreshInterval;
+
+ // Act like a real logged in app client refreshing its news timeline.
+ // This also lets us detect if we're still logged in with a valid session.
+ if ($isSessionExpired) {
+ // Act like a real logged in app client refreshing its news timeline.
+ // This also lets us detect if we're still logged in with a valid session.
+ try {
+ $this->story->getReelsTrayFeed('cold_start');
+ } catch (\InstagramAPI\Exception\LoginRequiredException $e) {
+ // If our session cookies are expired, we were now told to login,
+ // so handle that by running a forced relogin in that case!
+ return $this->_login($this->username, $this->password, true, $appRefreshInterval);
+ }
+ $this->timeline->getTimelineFeed(null, [
+ 'is_pull_to_refresh' => $isSessionExpired ? null : mt_rand(1, 3) < 3,
+ ]);
+ $this->people->getSharePrefill();
+ $this->people->getRecentActivityInbox();
+
+ $this->people->getSharePrefill();
+ $this->people->getRecentActivityInbox();
+ $this->people->getInfoById($this->account_id);
+ $this->internal->getDeviceCapabilitiesDecisions();
+ $this->direct->getPresences();
+ $this->discover->getExploreFeed();
+ $this->direct->getInbox();
+
+ $this->settings->set('last_login', time());
+ // Generate and save a new application session ID.
+ $this->session_id = Signatures::generateUUID();
+ $this->settings->set('session_id', $this->session_id);
+ // Do the rest of the "user is re-opening the app" API flow...
+ $this->tv->getTvGuide();
+
+ $this->internal->getLoomFetchConfig();
+ $this->direct->getRankedRecipients('reshare', true);
+ $this->direct->getRankedRecipients('raven', true);
+ $this->_registerPushChannels();
+ }
+
+ // Users normally resume their sessions, meaning that their
+ // experiments never get synced and updated. So sync periodically.
+ $lastExperimentsTime = $this->settings->get('last_experiments');
+ if ($lastExperimentsTime === null || (time() - $lastExperimentsTime) > self::EXPERIMENTS_REFRESH) {
+ $this->internal->syncUserFeatures();
+ $this->internal->syncDeviceFeatures();
+ }
+
+ // Update zero rating token when it has been expired.
+ $expired = time() - (int) $this->settings->get('zr_expires');
+ if ($expired > 0) {
+ $this->client->zeroRating()->reset();
+ $this->internal->fetchZeroRatingToken($expired > 7200 ? 'token_stale' : 'token_expired');
+ }
+ }
+
+ // We've now performed a login or resumed a session. Forcibly write our
+ // cookies to the storage, to ensure that the storage doesn't miss them
+ // in case something bad happens to PHP after this moment.
+ $this->client->saveCookieJar();
+
+ return null;
+ }
+
+ /**
+ * Log out of Instagram.
+ *
+ * WARNING: Most people should NEVER call `logout()`! Our library emulates
+ * the Instagram app for Android, where you are supposed to stay logged in
+ * forever. By calling this function, you will tell Instagram that you are
+ * logging out of the APP. But you SHOULDN'T do that! In almost 100% of all
+ * cases you want to *stay logged in* so that `login()` resumes your session!
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LogoutResponse
+ *
+ * @see Instagram::login()
+ */
+ public function logout()
+ {
+ $response = $this->request('accounts/logout/')
+ ->setSignedPost(false)
+ ->addPost('phone_id', $this->phone_id)
+ ->addPost('_csrftoken', $this->client->getToken())
+ ->addPost('guid', $this->uuid)
+ ->addPost('device_id', $this->device_id)
+ ->addPost('_uuid', $this->uuid)
+ ->getResponse(new Response\LogoutResponse());
+
+ // We've now logged out. Forcibly write our cookies to the storage, to
+ // ensure that the storage doesn't miss them in case something bad
+ // happens to PHP after this moment.
+ $this->client->saveCookieJar();
+
+ return $response;
+ }
+
+ /**
+ * Checks if a parameter is enabled in the given experiment.
+ *
+ * @param string $experiment
+ * @param string $param
+ * @param bool $default
+ *
+ * @return bool
+ */
+ public function isExperimentEnabled(
+ $experiment,
+ $param,
+ $default = false)
+ {
+ return isset($this->experiments[$experiment][$param])
+ ? in_array($this->experiments[$experiment][$param], ['enabled', 'true', '1'])
+ : $default;
+ }
+
+ /**
+ * Get a parameter value for the given experiment.
+ *
+ * @param string $experiment
+ * @param string $param
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getExperimentParam(
+ $experiment,
+ $param,
+ $default = null)
+ {
+ return isset($this->experiments[$experiment][$param])
+ ? $this->experiments[$experiment][$param]
+ : $default;
+ }
+
+ /**
+ * Create a custom API request.
+ *
+ * Used internally, but can also be used by end-users if they want
+ * to create completely custom API queries without modifying this library.
+ *
+ * @param string $url
+ *
+ * @return \InstagramAPI\Request
+ */
+ public function request(
+ $url)
+ {
+ return new Request($this, $url);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/InstagramID.php b/vendor/mgp25/instagram-php/src/InstagramID.php
new file mode 100755
index 0000000..d26f3c0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/InstagramID.php
@@ -0,0 +1,249 @@
+_width = (int) $width;
+ $this->_height = (int) $height;
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $this->_aspectRatio = (float) ($this->_width / $this->_height);
+ }
+
+ /**
+ * Get stored width for these dimensions.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->_width;
+ }
+
+ /**
+ * Get stored height for these dimensions.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->_height;
+ }
+
+ /**
+ * Get stored aspect ratio for these dimensions.
+ *
+ * @return float
+ */
+ public function getAspectRatio()
+ {
+ return $this->_aspectRatio;
+ }
+
+ /**
+ * Create a new object with swapped axes.
+ *
+ * @return self
+ */
+ public function withSwappedAxes()
+ {
+ return new self($this->_height, $this->_width);
+ }
+
+ /**
+ * Create a new, scale-adjusted object.
+ *
+ * @param float|int $newScale The scale factor to apply.
+ * @param string $roundingFunc One of `round` (default), `floor` or `ceil`.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function withRescaling(
+ $newScale = 1.0,
+ $roundingFunc = 'round')
+ {
+ if (!is_float($newScale) && !is_int($newScale)) {
+ throw new \InvalidArgumentException('The new scale must be a float or integer.');
+ }
+ if ($roundingFunc !== 'round' && $roundingFunc !== 'floor' && $roundingFunc !== 'ceil') {
+ throw new \InvalidArgumentException(sprintf('Invalid rounding function "%s".', $roundingFunc));
+ }
+
+ $newWidth = (int) $roundingFunc($newScale * $this->_width);
+ $newHeight = (int) $roundingFunc($newScale * $this->_height);
+
+ return new self($newWidth, $newHeight);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Media/Geometry/Rectangle.php b/vendor/mgp25/instagram-php/src/Media/Geometry/Rectangle.php
new file mode 100755
index 0000000..513a657
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Media/Geometry/Rectangle.php
@@ -0,0 +1,179 @@
+_x = (int) $x;
+ $this->_y = (int) $y;
+ $this->_width = (int) $width;
+ $this->_height = (int) $height;
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $this->_aspectRatio = (float) ($this->_width / $this->_height);
+ }
+
+ /**
+ * Get stored X1 offset for this rectangle.
+ *
+ * @return int
+ */
+ public function getX()
+ {
+ return $this->_x;
+ }
+
+ /**
+ * Get stored Y1 offset for this rectangle.
+ *
+ * @return int
+ */
+ public function getY()
+ {
+ return $this->_y;
+ }
+
+ /**
+ * Get stored X1 offset for this rectangle.
+ *
+ * This does the same thing as `getX()`. It is just a mental
+ * convenience when working in X1/X2 space.
+ *
+ * @return int
+ */
+ public function getX1()
+ {
+ return $this->_x;
+ }
+
+ /**
+ * Get stored Y1 offset for this rectangle.
+ *
+ * This does the same thing as `getY()`. It is just a mental
+ * convenience when working in Y1/Y2 space.
+ *
+ * @return int
+ */
+ public function getY1()
+ {
+ return $this->_y;
+ }
+
+ /**
+ * Get calculated X2 offset (X1+Width) for this rectangle.
+ *
+ * @return int
+ */
+ public function getX2()
+ {
+ return $this->_x + $this->_width;
+ }
+
+ /**
+ * Get calculated Y2 offset (Y1+Height) for this rectangle.
+ *
+ * @return int
+ */
+ public function getY2()
+ {
+ return $this->_y + $this->_height;
+ }
+
+ /**
+ * Get stored width for this rectangle.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->_width;
+ }
+
+ /**
+ * Get stored height for this rectangle.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->_height;
+ }
+
+ /**
+ * Get stored aspect ratio for this rectangle.
+ *
+ * @return float
+ */
+ public function getAspectRatio()
+ {
+ return $this->_aspectRatio;
+ }
+
+ /**
+ * Create a new object with swapped axes.
+ *
+ * @return self
+ */
+ public function withSwappedAxes()
+ {
+ return new self($this->_y, $this->_x, $this->_height, $this->_width);
+ }
+
+ /**
+ * Create a new, scale-adjusted object.
+ *
+ * NOTE: The x1/y1 offsets are not affected. Only the width and height. But
+ * those new dimensions WILL affect the x2/y2 offsets, as you'd expect.
+ *
+ * @param float|int $newScale The scale factor to apply.
+ * @param string $roundingFunc One of `round` (default), `floor` or `ceil`.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function withRescaling(
+ $newScale = 1.0,
+ $roundingFunc = 'round')
+ {
+ if (!is_float($newScale) && !is_int($newScale)) {
+ throw new \InvalidArgumentException('The new scale must be a float or integer.');
+ }
+ if ($roundingFunc !== 'round' && $roundingFunc !== 'floor' && $roundingFunc !== 'ceil') {
+ throw new \InvalidArgumentException(sprintf('Invalid rounding function "%s".', $roundingFunc));
+ }
+
+ $newWidth = (int) $roundingFunc($newScale * $this->_width);
+ $newHeight = (int) $roundingFunc($newScale * $this->_height);
+
+ return new self($this->_x, $this->_y, $newWidth, $newHeight);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Media/InstagramMedia.php b/vendor/mgp25/instagram-php/src/Media/InstagramMedia.php
new file mode 100755
index 0000000..02ed801
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Media/InstagramMedia.php
@@ -0,0 +1,1456 @@
+_debug = $debug === true;
+
+ // Input file.
+ if (!is_file($inputFile)) {
+ throw new \InvalidArgumentException(sprintf('Input file "%s" doesn\'t exist.', $inputFile));
+ }
+ $this->_inputFile = $inputFile;
+
+ // Horizontal crop focus.
+ if ($horCropFocus !== null && (!is_int($horCropFocus) || $horCropFocus < -50 || $horCropFocus > 50)) {
+ throw new \InvalidArgumentException('Horizontal crop focus must be between -50 and 50.');
+ }
+ $this->_horCropFocus = $horCropFocus;
+
+ // Vertical crop focus.
+ if ($verCropFocus !== null && (!is_int($verCropFocus) || $verCropFocus < -50 || $verCropFocus > 50)) {
+ throw new \InvalidArgumentException('Vertical crop focus must be between -50 and 50.');
+ }
+ $this->_verCropFocus = $verCropFocus;
+
+ // Minimum and maximum aspect ratio range.
+ if ($minAspectRatio !== null && !is_float($minAspectRatio)) {
+ throw new \InvalidArgumentException('Minimum aspect ratio must be a floating point number.');
+ }
+ if ($maxAspectRatio !== null && !is_float($maxAspectRatio)) {
+ throw new \InvalidArgumentException('Maximum aspect ratio must be a floating point number.');
+ }
+
+ // Does the user want to override (force) the final "target aspect ratio" choice?
+ // NOTE: This will be used to override `$this->_forceTargetAspectRatio`.
+ $this->_hasUserForceTargetAspectRatio = false;
+ if ($userForceTargetAspectRatio !== null) {
+ if (!is_float($userForceTargetAspectRatio) && !is_int($userForceTargetAspectRatio)) {
+ throw new \InvalidArgumentException('Custom target aspect ratio must be a float or integer.');
+ }
+ $userForceTargetAspectRatio = (float) $userForceTargetAspectRatio;
+ $this->_hasUserForceTargetAspectRatio = true;
+ $useRecommendedRatio = false; // We forcibly disable this too, to avoid risk of future bugs.
+ }
+
+ // Create constraints and determine whether to use "recommended target aspect ratio" (if one is available for feed).
+ $this->_constraints = ConstraintsFactory::createFor($targetFeed);
+ if (!$this->_hasUserForceTargetAspectRatio && $useRecommendedRatio === null) {
+ // No value is provided, so let's guess it.
+ if ($minAspectRatio !== null || $maxAspectRatio !== null) {
+ // If we have at least one custom ratio, we must not use recommended ratio.
+ $useRecommendedRatio = false;
+ } else {
+ // Use the recommended value from constraints (either on or off, depending on which target feed).
+ $useRecommendedRatio = $this->_constraints->useRecommendedRatioByDefault();
+ }
+ }
+
+ // Determine the legal min/max aspect ratios for the target feed.
+ if (!$this->_hasUserForceTargetAspectRatio && $useRecommendedRatio === true) {
+ $this->_forceTargetAspectRatio = $this->_constraints->getRecommendedRatio();
+ $deviation = $this->_constraints->getRecommendedRatioDeviation();
+ $minAspectRatio = $this->_forceTargetAspectRatio - $deviation;
+ $maxAspectRatio = $this->_forceTargetAspectRatio + $deviation;
+ } else {
+ // If the user hasn't specified a custom target aspect ratio, this
+ // "force" value will remain NULL (and the target ratio will be
+ // auto-calculated by the canvas generation algorithms instead).
+ $this->_forceTargetAspectRatio = $userForceTargetAspectRatio;
+ $allowedMinRatio = $this->_constraints->getMinAspectRatio();
+ $allowedMaxRatio = $this->_constraints->getMaxAspectRatio();
+
+ // Select allowed aspect ratio range based on defaults and user input.
+ if ($minAspectRatio !== null && ($minAspectRatio < $allowedMinRatio || $minAspectRatio > $allowedMaxRatio)) {
+ throw new \InvalidArgumentException(sprintf('Minimum aspect ratio must be between %.3f and %.3f.',
+ $allowedMinRatio, $allowedMaxRatio));
+ }
+ if ($minAspectRatio === null) {
+ $minAspectRatio = $allowedMinRatio;
+ }
+ if ($maxAspectRatio !== null && ($maxAspectRatio < $allowedMinRatio || $maxAspectRatio > $allowedMaxRatio)) {
+ throw new \InvalidArgumentException(sprintf('Maximum aspect ratio must be between %.3f and %.3f.',
+ $allowedMinRatio, $allowedMaxRatio));
+ }
+ if ($maxAspectRatio === null) {
+ $maxAspectRatio = $allowedMaxRatio;
+ }
+ if ($minAspectRatio !== null && $maxAspectRatio !== null && $minAspectRatio > $maxAspectRatio) {
+ throw new \InvalidArgumentException('Maximum aspect ratio must be greater than or equal to minimum.');
+ }
+
+ // Validate custom target aspect ratio legality if provided by user.
+ if ($this->_hasUserForceTargetAspectRatio) {
+ if ($minAspectRatio !== null && $this->_forceTargetAspectRatio < $minAspectRatio) {
+ throw new \InvalidArgumentException(sprintf('Custom target aspect ratio (%.5f) must be greater than or equal to the minimum aspect ratio (%.5f).',
+ $this->_forceTargetAspectRatio, $minAspectRatio));
+ }
+ if ($maxAspectRatio !== null && $this->_forceTargetAspectRatio > $maxAspectRatio) {
+ throw new \InvalidArgumentException(sprintf('Custom target aspect ratio (%.5f) must be lesser than or equal to the maximum aspect ratio (%.5f).',
+ $this->_forceTargetAspectRatio, $maxAspectRatio));
+ }
+ }
+ }
+ $this->_minAspectRatio = $minAspectRatio;
+ $this->_maxAspectRatio = $maxAspectRatio;
+
+ // Allow the aspect ratio of the final, new canvas to deviate slightly from the min/max range?
+ $this->_allowNewAspectDeviation = $allowNewAspectDeviation;
+
+ // Background color.
+ if ($bgColor !== null && (!is_array($bgColor) || count($bgColor) !== 3 || !isset($bgColor[0]) || !isset($bgColor[1]) || !isset($bgColor[2]))) {
+ throw new \InvalidArgumentException('The background color must be a 3-element array [R, G, B].');
+ } elseif ($bgColor === null) {
+ $bgColor = [255, 255, 255]; // White.
+ }
+ $this->_bgColor = $bgColor;
+
+ //Blurred border
+ $this->_blurredBorder = $blurredBorder;
+
+ // Media operation.
+ if ($operation !== self::CROP && $operation !== self::EXPAND) {
+ throw new \InvalidArgumentException('The operation must be one of the class constants CROP or EXPAND.');
+ }
+ $this->_operation = $operation;
+
+ // Temporary directory path.
+ if ($tmpPath === null) {
+ $tmpPath = self::$defaultTmpPath !== null
+ ? self::$defaultTmpPath
+ : sys_get_temp_dir();
+ }
+ if (!is_dir($tmpPath) || !is_writable($tmpPath)) {
+ throw new \InvalidArgumentException(sprintf('Directory %s does not exist or is not writable.', $tmpPath));
+ }
+ $this->_tmpPath = realpath($tmpPath);
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->deleteFile();
+ }
+
+ /**
+ * Removes the output file if it exists and differs from input file.
+ *
+ * This function is safe and won't delete the original input file.
+ *
+ * Is automatically called when the class instance is destroyed by PHP.
+ * But you can manually call it ahead of time if you want to force cleanup.
+ *
+ * Note that getFile() will still work afterwards, but will have to process
+ * the media again to a new temp file if the input file required processing.
+ *
+ * @return bool
+ */
+ public function deleteFile()
+ {
+ // Only delete if outputfile exists and isn't the same as input file.
+ if ($this->_outputFile !== null && $this->_outputFile !== $this->_inputFile && is_file($this->_outputFile)) {
+ $result = @unlink($this->_outputFile);
+ $this->_outputFile = null; // Reset so getFile() will work again.
+ return $result;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the path to a media file matching the requirements.
+ *
+ * The automatic processing is performed the first time that this function
+ * is called. Which means that no CPU time is wasted if you never call this
+ * function at all.
+ *
+ * Due to the processing, the first call to this function may take a moment.
+ *
+ * If the input file already fits all of the specifications, we simply
+ * return the input path instead, without any need to re-process it.
+ *
+ * @throws \Exception
+ * @throws \RuntimeException
+ *
+ * @return string The path to the media file.
+ *
+ * @see InstagramMedia::_shouldProcess() The criteria that determines processing.
+ */
+ public function getFile()
+ {
+ if ($this->_outputFile === null) {
+ $this->_outputFile = $this->_shouldProcess() ? $this->_process() : $this->_inputFile;
+ }
+
+ return $this->_outputFile;
+ }
+
+ /**
+ * Checks whether we should process the input file.
+ *
+ * @return bool
+ */
+ protected function _shouldProcess()
+ {
+ $inputAspectRatio = $this->_details->getAspectRatio();
+
+ // Process if aspect ratio < minimum allowed.
+ if ($this->_minAspectRatio !== null && $inputAspectRatio < $this->_minAspectRatio) {
+ return true;
+ }
+
+ // Process if aspect ratio > maximum allowed.
+ if ($this->_maxAspectRatio !== null && $inputAspectRatio > $this->_maxAspectRatio) {
+ return true;
+ }
+
+ // Process if USER provided the custom aspect ratio target and input deviates too much.
+ if ($this->_hasUserForceTargetAspectRatio) {
+ if ($this->_forceTargetAspectRatio == 1.0) {
+ // User wants a SQUARE canvas, which can ALWAYS be achieved (by
+ // making both sides equal). Process input if not EXACTLY square.
+ // WARNING: Comparison here and above MUST use `!=` (NOT strict
+ // `!==`) to support both int(1) and float(1.0) values!
+ if ($inputAspectRatio != 1.0) {
+ return true;
+ }
+ } else {
+ // User wants a non-square canvas, which is almost always
+ // IMPOSSIBLE to achieve perfectly. Only process if input
+ // deviates too much from the desired target.
+ $acceptableDeviation = 0.003; // Allow a very narrow range around the user's target.
+ $acceptableMinAspectRatio = $this->_forceTargetAspectRatio - $acceptableDeviation;
+ $acceptableMaxAspectRatio = $this->_forceTargetAspectRatio + $acceptableDeviation;
+ if ($inputAspectRatio < $acceptableMinAspectRatio || $inputAspectRatio > $acceptableMaxAspectRatio) {
+ return true;
+ }
+ }
+ }
+
+ // Process if the media can't be uploaded to Instagram as is.
+ // NOTE: Nobody is allowed to call `isMod2CanvasRequired()` here. That
+ // isn't its purpose. Whether a final Mod2 canvas is required for actual
+ // resizing has NOTHING to do with whether the input file is ok.
+ try {
+ $this->_details->validate($this->_constraints);
+
+ return false;
+ } catch (\Exception $e) {
+ return true;
+ }
+ }
+
+ /**
+ * Whether this processor requires Mod2 width and height canvas dimensions.
+ *
+ * If this returns FALSE, the calculated `InstagramMedia` canvas passed to
+ * this processor _may_ contain uneven width and/or height as the selected
+ * output dimensions.
+ *
+ * Therefore, this function must return TRUE if (and ONLY IF) perfectly even
+ * dimensions are necessary for this particular processor's output format.
+ *
+ * For example, JPEG images accept any dimensions and must therefore return
+ * FALSE. But H264 videos require EVEN dimensions and must return TRUE.
+ *
+ * @return bool
+ */
+ abstract protected function _isMod2CanvasRequired();
+
+ /**
+ * Process the input file and create the new file.
+ *
+ * @throws \RuntimeException
+ *
+ * @return string The path to the new file.
+ */
+ protected function _process()
+ {
+ // Get the dimensions of the original input file.
+ $inputCanvas = new Dimensions($this->_details->getWidth(), $this->_details->getHeight());
+
+ // Create an output canvas with the desired dimensions.
+ // WARNING: This creates a LEGAL canvas which MUST be followed EXACTLY.
+ $canvasInfo = $this->_calculateNewCanvas( // Throws.
+ $this->_operation,
+ $inputCanvas->getWidth(),
+ $inputCanvas->getHeight(),
+ $this->_isMod2CanvasRequired(),
+ $this->_details->getMinAllowedWidth(),
+ $this->_details->getMaxAllowedWidth(),
+ $this->_minAspectRatio,
+ $this->_maxAspectRatio,
+ $this->_forceTargetAspectRatio,
+ $this->_allowNewAspectDeviation
+ );
+ $outputCanvas = $canvasInfo['canvas'];
+
+ // Determine the media operation's resampling parameters and perform it.
+ // NOTE: This section is EXCESSIVELY commented to explain each step. The
+ // algorithm is pretty easy after you understand it. But without the
+ // detailed comments, future contributors may not understand any of it!
+ // "We'd rather have a WaLL oF TeXt for future reference, than bugs due
+ // to future misunderstandings!" - SteveJobzniak ;-)
+ if ($this->_operation === self::CROP) {
+ // Determine the IDEAL canvas dimensions as if Mod2 adjustments were
+ // not applied. That's NECESSARY for calculating an ACCURATE scale-
+ // change compared to the input, so that we can calculate how much
+ // the canvas has rescaled. WARNING: These are 1-dimensional scales,
+ // and only ONE value (the uncropped side) is valid for comparison.
+ $idealCanvas = new Dimensions($outputCanvas->getWidth() - $canvasInfo['mod2WidthDiff'],
+ $outputCanvas->getHeight() - $canvasInfo['mod2HeightDiff']);
+ $idealWidthScale = (float) ($idealCanvas->getWidth() / $inputCanvas->getWidth());
+ $idealHeightScale = (float) ($idealCanvas->getHeight() / $inputCanvas->getHeight());
+ $this->_debugDimensions(
+ $inputCanvas->getWidth(), $inputCanvas->getHeight(),
+ 'CROP: Analyzing Original Input Canvas Size'
+ );
+ $this->_debugDimensions(
+ $idealCanvas->getWidth(), $idealCanvas->getHeight(),
+ 'CROP: Analyzing Ideally Cropped (Non-Mod2-adjusted) Output Canvas Size'
+ );
+ $this->_debugText(
+ 'CROP: Scale of Ideally Cropped Canvas vs Input Canvas',
+ 'width=%.8f, height=%.8f',
+ $idealWidthScale, $idealHeightScale
+ );
+
+ // Now determine HOW the IDEAL canvas has been cropped compared to
+ // the INPUT canvas. But we can't just compare dimensions, since our
+ // algorithms may have cropped and THEN scaled UP the dimensions to
+ // legal values far above the input values, or scaled them DOWN and
+ // then Mod2-cropped at the new scale, etc. There are so many
+ // possibilities. That's also why we couldn't "just keep track of
+ // amount of pixels cropped during main algorithm". We MUST figure
+ // it out ourselves accurately HERE. We can't do it at any earlier
+ // stage, since cumulative rounding errors from width/height
+ // readjustments could drift us away from the target aspect ratio
+ // and could prevent pixel-perfect results UNLESS we calc it HERE.
+ //
+ // There's IS a great way to figure out the cropping. When the WIDTH
+ // of a canvas is reduced (making it more "portraity"), its aspect
+ // ratio number decreases. When the HEIGHT of a canvas is reduced
+ // (making it more "landscapey"), its aspect ratio number increases.
+ //
+ // And our canvas cropping algorithm only crops in ONE DIRECTION
+ // (width or height), so we only need to detect the aspect ratio
+ // change of the IDEAL (non-Mod2-adjusted) canvas, to know what
+ // happened. However, note that this CAN also trigger if the input
+ // had to be up/downscaled (to an imperfect final aspect), but that
+ // doesn't matter since this algorithm will STILL figure out the
+ // proper scale and croppings to use for the canvas. Because uneven,
+ // aspect-affecting scaling basically IS cropping the INPUT canvas!
+ if ($idealCanvas->getAspectRatio() === $inputCanvas->getAspectRatio()) {
+ // No sides have been cropped. So both width and height scales
+ // WILL be IDENTICAL, since NOTHING else would be able to create
+ // an identical aspect ratio again (otherwise the aspect ratio
+ // would have been warped (not equal)). So just pick either one.
+ // NOTE: Identical (uncropped ratio) DOESN'T mean that scale is
+ // going to be 1.0. It MAY be. Or the canvas MAY have been
+ // evenly expanded or evenly shrunk in both dimensions.
+ $hasCropped = 'nothing';
+ $overallRescale = $idealWidthScale; // $idealHeightScale IS identical.
+ } elseif ($idealCanvas->getAspectRatio() < $inputCanvas->getAspectRatio()) {
+ // The horizontal width has been cropped. Grab the height's
+ // scale, since that side is "unaffected" by the main cropping
+ // and should therefore have a scale of 1. Although it may have
+ // had up/down-scaling. In that case, the height scale will
+ // represent the amount of overall rescale change.
+ $hasCropped = 'width';
+ $overallRescale = $idealHeightScale;
+ } else { // Output aspect is > input.
+ // The vertical height has been cropped. Just like above, the
+ // "unaffected" side is what we'll use as our scale reference.
+ $hasCropped = 'height';
+ $overallRescale = $idealWidthScale;
+ }
+ $this->_debugText(
+ 'CROP: Detecting Cropped Direction',
+ 'cropped=%s, overallRescale=%.8f',
+ $hasCropped, $overallRescale
+ );
+
+ // Alright, now calculate the dimensions of the "IDEALLY CROPPED
+ // INPUT canvas", at INPUT canvas scale. These are the scenarios:
+ //
+ // - "hasCropped: nothing, scale is 1.0" = Nothing was cropped, and
+ // nothing was scaled. Treat as "use whole INPUT canvas". This is
+ // pixel-perfect.
+ //
+ // - "hasCropped: nothing, scale NOT 1.0" = Nothing was cropped, but
+ // the whole canvas was up/down-scaled. We don't have to care at
+ // all about that scaling and should treat it as "use whole INPUT
+ // canvas" for crop calculation purposes. The cropped result will
+ // later be scaled/stretched to the canvas size (up or down).
+ //
+ // - "hasCropped: width/height, scale is 1.0" = A single side was
+ // cropped, and nothing was scaled. Treat as "use IDEALLY CROPPED
+ // canvas". This is pixel-perfect.
+ //
+ // - "hasCropped: width/height, scale NOT 1.0" = A single side was
+ // cropped, and then the whole canvas was up/down-scaled. Treat as
+ // "use scale-fixed version of IDEALLY CROPPED canvas". The
+ // cropped result will later be scaled/stretched to the canvas
+ // size (up or down).
+ //
+ // There's an easy way to handle ALL of those scenarios: Just
+ // translate the IDEALLY CROPPED canvas back into INPUT-SCALED
+ // dimensions. Then we'll get a pixel-perfect "input crop" whenever
+ // scale is 1.0, since a scale of 1.0 gives the same result back.
+ // And we'll get a properly re-scaled result in all other cases.
+ //
+ // NOTE: This result CAN deviate from what was "actually cropped"
+ // during the main algorithm. That is TOTALLY INTENTIONAL AND IS THE
+ // INTENDED, PERFECT BEHAVIOR! Do NOT change this code! By always
+ // re-calculating here, we'll actually FIX rounding errors caused by
+ // the main algorithm's multiple steps, and will create better
+ // looking rescaling, and pixel-perfect unscaled croppings and
+ // pixel-perfect unscaled Mod2 adjustments!
+
+ // First calculate the overall IDEAL cropping applied to the INPUT
+ // canvas. If scale is 1.0 it will be used as-is (pixel-perfect).
+ // NOTE: We tell it to use round() so that the rescaled pixels are
+ // as close to the perfect aspect ratio as possible.
+ $croppedInputCanvas = $idealCanvas->withRescaling(1 / $overallRescale, 'round');
+ $this->_debugDimensions(
+ $croppedInputCanvas->getWidth(), $croppedInputCanvas->getHeight(),
+ 'CROP: Rescaled Ideally Cropped Canvas to Input Dimension Space'
+ );
+
+ // Now re-scale the Mod2 adjustments to the INPUT canvas coordinate
+ // space too. If scale is 1.0 they'll be used as-is (pixel-perfect).
+ // If the scale is up/down, they'll be rounded to the next whole
+ // number. The rounding is INTENTIONAL, because if scaling was used
+ // for the IDEAL canvas then it DOESN'T MATTER how many exact pixels
+ // we crop, but round() gives us the BEST APPROXIMATION!
+ $rescaledMod2WidthDiff = (int) round($canvasInfo['mod2WidthDiff'] * (1 / $overallRescale));
+ $rescaledMod2HeightDiff = (int) round($canvasInfo['mod2HeightDiff'] * (1 / $overallRescale));
+ $this->_debugText(
+ 'CROP: Rescaled Mod2 Adjustments to Input Dimension Space',
+ 'width=%s, height=%s, widthRescaled=%s, heightRescaled=%s',
+ $canvasInfo['mod2WidthDiff'], $canvasInfo['mod2HeightDiff'],
+ $rescaledMod2WidthDiff, $rescaledMod2HeightDiff
+ );
+
+ // Apply the Mod2 adjustments to the input cropping that we'll
+ // perform. This ensures that ALL of the Mod2 croppings (in ANY
+ // dimension) will always be pixel-perfect when we're at scale 1.0!
+ $croppedInputCanvas = new Dimensions($croppedInputCanvas->getWidth() + $rescaledMod2WidthDiff,
+ $croppedInputCanvas->getHeight() + $rescaledMod2HeightDiff);
+ $this->_debugDimensions(
+ $croppedInputCanvas->getWidth(), $croppedInputCanvas->getHeight(),
+ 'CROP: Applied Mod2 Adjustments to Final Cropped Input Canvas'
+ );
+
+ // The "CROPPED INPUT canvas" is in the same dimensions/coordinate
+ // space as the "INPUT canvas". So ensure all dimensions are valid
+ // (don't exceed INPUT) and create the final "CROPPED INPUT canvas".
+ // NOTE: This is it... if the media is at scale 1.0, we now have a
+ // pixel-perfect, cropped canvas with ALL of the cropping and Mod2
+ // adjustments applied to it! And if we're at another scale, we have
+ // a perfectly recalculated, cropped canvas which took into account
+ // cropping, scaling and Mod2 adjustments. Advanced stuff! :-)
+ $croppedInputCanvasWidth = $croppedInputCanvas->getWidth() <= $inputCanvas->getWidth()
+ ? $croppedInputCanvas->getWidth() : $inputCanvas->getWidth();
+ $croppedInputCanvasHeight = $croppedInputCanvas->getHeight() <= $inputCanvas->getHeight()
+ ? $croppedInputCanvas->getHeight() : $inputCanvas->getHeight();
+ $croppedInputCanvas = new Dimensions($croppedInputCanvasWidth, $croppedInputCanvasHeight);
+ $this->_debugDimensions(
+ $croppedInputCanvas->getWidth(), $croppedInputCanvas->getHeight(),
+ 'CROP: Clamped to Legal Input Max-Dimensions'
+ );
+
+ // Initialize the crop-shifting variables. They control the range of
+ // X/Y coordinates we'll copy from ORIGINAL INPUT to OUTPUT canvas.
+ // NOTE: This properly selects the entire INPUT media canvas area.
+ $x1 = $y1 = 0;
+ $x2 = $inputCanvas->getWidth();
+ $y2 = $inputCanvas->getHeight();
+ $this->_debugText(
+ 'CROP: Initializing X/Y Variables to Full Input Canvas Size',
+ 'x1=%s, x2=%s, y1=%s, y2=%s',
+ $x1, $x2, $y1, $y2
+ );
+
+ // Calculate the width and height diffs between the original INPUT
+ // canvas and the new CROPPED INPUT canvas. Negative values mean the
+ // output is smaller (which we'll handle by cropping), and larger
+ // values would mean the output is larger (which we'll handle by
+ // letting the OUTPUT canvas stretch the 100% uncropped original
+ // pixels of the INPUT in that direction, to fill the whole canvas).
+ // NOTE: Because of clamping of the CROPPED INPUT canvas above, this
+ // will actually never be a positive ("scale up") number. It will
+ // only be 0 or less. That's good, just be aware of it if editing!
+ $widthDiff = $croppedInputCanvas->getWidth() - $inputCanvas->getWidth();
+ $heightDiff = $croppedInputCanvas->getHeight() - $inputCanvas->getHeight();
+ $this->_debugText(
+ 'CROP: Calculated Input Canvas Crop Amounts',
+ 'width=%s px, height=%s px',
+ $widthDiff, $heightDiff
+ );
+
+ // After ALL of that work... we finally know how to crop the input
+ // canvas! Alright... handle cropping of the INPUT width and height!
+ // NOTE: The main canvas-creation algorithm only crops a single
+ // dimension (width or height), but its Mod2 adjustments may have
+ // caused BOTH to be cropped, which is why we MUST process both.
+ if ($widthDiff < 0) {
+ // Horizontal cropping. Focus on the center by default.
+ $horCropFocus = $this->_horCropFocus !== null ? $this->_horCropFocus : 0;
+ $this->_debugText('CROP: Horizontal Crop Focus', 'focus=%s', $horCropFocus);
+
+ // Invert the focus if this is horizontally flipped media.
+ if ($this->_details->isHorizontallyFlipped()) {
+ $horCropFocus = -$horCropFocus;
+ $this->_debugText(
+ 'CROP: Media is HorFlipped, Flipping Horizontal Crop Focus',
+ 'focus=%s',
+ $horCropFocus
+ );
+ }
+
+ // Calculate amount of pixels to crop and shift them as-focused.
+ // NOTE: Always use floor() to make uneven amounts lean at left.
+ $absWidthDiff = abs($widthDiff);
+ $x1 = (int) floor($absWidthDiff * (50 + $horCropFocus) / 100);
+ $x2 = $x2 - ($absWidthDiff - $x1);
+ $this->_debugText('CROP: Calculated New X Offsets', 'x1=%s, x2=%s', $x1, $x2);
+ }
+ if ($heightDiff < 0) {
+ // Vertical cropping. Focus on top by default (to keep faces).
+ $verCropFocus = $this->_verCropFocus !== null ? $this->_verCropFocus : -50;
+ $this->_debugText('CROP: Vertical Crop Focus', 'focus=%s', $verCropFocus);
+
+ // Invert the focus if this is vertically flipped media.
+ if ($this->_details->isVerticallyFlipped()) {
+ $verCropFocus = -$verCropFocus;
+ $this->_debugText(
+ 'CROP: Media is VerFlipped, Flipping Vertical Crop Focus',
+ 'focus=%s',
+ $verCropFocus
+ );
+ }
+
+ // Calculate amount of pixels to crop and shift them as-focused.
+ // NOTE: Always use floor() to make uneven amounts lean at top.
+ $absHeightDiff = abs($heightDiff);
+ $y1 = (int) floor($absHeightDiff * (50 + $verCropFocus) / 100);
+ $y2 = $y2 - ($absHeightDiff - $y1);
+ $this->_debugText('CROP: Calculated New Y Offsets', 'y1=%s, y2=%s', $y1, $y2);
+ }
+
+ // Create a source rectangle which starts at the start-offsets
+ // (x1/y1) and lasts until the width and height of the desired area.
+ $srcRect = new Rectangle($x1, $y1, $x2 - $x1, $y2 - $y1);
+ $this->_debugText(
+ 'CROP_SRC: Input Canvas Source Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $srcRect->getX1(), $srcRect->getX2(), $srcRect->getY1(), $srcRect->getY2(),
+ $srcRect->getWidth(), $srcRect->getHeight(), $srcRect->getAspectRatio()
+ );
+
+ // Create a destination rectangle which completely fills the entire
+ // output canvas from edge to edge. This ensures that any undersized
+ // or oversized input will be stretched properly in all directions.
+ //
+ // NOTE: Everything about our cropping/canvas algorithms is
+ // optimized so that stretching won't happen unless the media is so
+ // tiny that it's below the minimum width or so wide that it must be
+ // shrunk. Everything else WILL use sharp 1:1 pixels and pure
+ // cropping instead of stretching/shrinking. And when stretch/shrink
+ // is used, the aspect ratio is always perfectly maintained!
+ $dstRect = new Rectangle(0, 0, $outputCanvas->getWidth(), $outputCanvas->getHeight());
+ $this->_debugText(
+ 'CROP_DST: Output Canvas Destination Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $dstRect->getX1(), $dstRect->getX2(), $dstRect->getY1(), $dstRect->getY2(),
+ $dstRect->getWidth(), $dstRect->getHeight(), $dstRect->getAspectRatio()
+ );
+ } elseif ($this->_operation === self::EXPAND) {
+ // We'll copy the entire original input media onto the new canvas.
+ // Always copy from the absolute top left of the original media.
+ $srcRect = new Rectangle(0, 0, $inputCanvas->getWidth(), $inputCanvas->getHeight());
+ $this->_debugText(
+ 'EXPAND_SRC: Input Canvas Source Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $srcRect->getX1(), $srcRect->getX2(), $srcRect->getY1(), $srcRect->getY2(),
+ $srcRect->getWidth(), $srcRect->getHeight(), $srcRect->getAspectRatio()
+ );
+
+ // Determine the target dimensions to fit it on the new canvas,
+ // because the input media's dimensions may have been too large.
+ // This will not scale anything (uses scale=1) if the input fits.
+ $outputWidthScale = (float) ($outputCanvas->getWidth() / $inputCanvas->getWidth());
+ $outputHeightScale = (float) ($outputCanvas->getHeight() / $inputCanvas->getHeight());
+ $scale = min($outputWidthScale, $outputHeightScale);
+ $this->_debugText(
+ 'EXPAND: Calculating Scale to Fit Input on Output Canvas',
+ 'scale=%.8f',
+ $scale
+ );
+
+ // Calculate the scaled destination rectangle. Note that X/Y remain.
+ // NOTE: We tell it to use ceil(), which guarantees that it'll
+ // never scale a side badly and leave a 1px gap between the media
+ // and canvas sides. Also note that ceil will never produce bad
+ // values, since PHP allows the dst_w/dst_h to exceed beyond canvas!
+ $dstRect = $srcRect->withRescaling($scale, 'ceil');
+ $this->_debugDimensions(
+ $dstRect->getWidth(), $dstRect->getHeight(),
+ 'EXPAND: Rescaled Input to Output Dimension Space'
+ );
+
+ // Now calculate the centered destination offset on the canvas.
+ // NOTE: We use floor() to ensure that the result gets left-aligned
+ // perfectly, and prefers to lean towards towards the top as well.
+ $dst_x = (int) floor(($outputCanvas->getWidth() - $dstRect->getWidth()) / 2);
+ $dst_y = (int) floor(($outputCanvas->getHeight() - $dstRect->getHeight()) / 2);
+ $this->_debugText(
+ 'EXPAND: Calculating Centered Destination on Output Canvas',
+ 'dst_x=%s, dst_y=%s',
+ $dst_x, $dst_y
+ );
+
+ // Build the final destination rectangle for the expanded canvas!
+ $dstRect = new Rectangle($dst_x, $dst_y, $dstRect->getWidth(), $dstRect->getHeight());
+ $this->_debugText(
+ 'EXPAND_DST: Output Canvas Destination Rectangle',
+ 'x1=%s, x2=%s, y1=%s, y2=%s, width=%s, height=%s, aspect=%.8f',
+ $dstRect->getX1(), $dstRect->getX2(), $dstRect->getY1(), $dstRect->getY2(),
+ $dstRect->getWidth(), $dstRect->getHeight(), $dstRect->getAspectRatio()
+ );
+ } else {
+ throw new \RuntimeException(sprintf('Unsupported operation: %s.', $this->_operation));
+ }
+
+ return $this->_createOutputFile($srcRect, $dstRect, $outputCanvas);
+ }
+
+ /**
+ * Create the new media file.
+ *
+ * @param Rectangle $srcRect Rectangle to copy from the input.
+ * @param Rectangle $dstRect Destination place and scale of copied pixels.
+ * @param Dimensions $canvas The size of the destination canvas.
+ *
+ * @return string The path to the output file.
+ */
+ abstract protected function _createOutputFile(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas);
+
+ /**
+ * Calculate a new canvas based on input size and requested modifications.
+ *
+ * The final canvas will be the same size as the input if everything was
+ * already okay and within the limits. Otherwise it will be a new canvas
+ * representing the _exact_, best-possible size to convert input media to.
+ *
+ * It is up to the caller to perfectly follow these orders, since deviating
+ * by even a SINGLE PIXEL can create illegal media aspect ratios.
+ *
+ * Also note that the resulting canvas can be LARGER than the input in
+ * several cases, such as in EXPAND-mode (obviously), or when the input
+ * isn't wide enough to be legal (and must be scaled up), and whenever Mod2
+ * is requested. In the latter case, the algorithm may have to add a few
+ * pixels to the height to make it valid in a few rare cases. The caller
+ * must be aware of such "enlarged" canvases and should handle them by
+ * stretching the input if necessary.
+ *
+ * @param int $operation
+ * @param int $inputWidth
+ * @param int $inputHeight
+ * @param bool $isMod2CanvasRequired
+ * @param int $minWidth
+ * @param int $maxWidth
+ * @param float|null $minAspectRatio
+ * @param float|null $maxAspectRatio
+ * @param float|null $forceTargetAspectRatio Optional forced aspect ratio
+ * target (ALWAYS applied,
+ * except if input is already
+ * EXACTLY this ratio).
+ * @param bool $allowNewAspectDeviation See constructor arg docs.
+ *
+ * @throws \RuntimeException If requested canvas couldn't be achieved, most
+ * commonly if you have chosen way too narrow
+ * aspect ratio ranges that cannot be perfectly
+ * reached by your input media, and you AREN'T
+ * running with `$allowNewAspectDeviation`.
+ *
+ * @return array An array with `canvas` (`Dimensions`), `mod2WidthDiff` and
+ * `mod2HeightDiff`. The latter are integers representing how
+ * many pixels were cropped (-) or added (+) by the Mod2 step
+ * compared to the ideal canvas.
+ */
+ protected function _calculateNewCanvas(
+ $operation,
+ $inputWidth,
+ $inputHeight,
+ $isMod2CanvasRequired,
+ $minWidth = 1,
+ $maxWidth = 99999,
+ $minAspectRatio = null,
+ $maxAspectRatio = null,
+ $forceTargetAspectRatio = null,
+ $allowNewAspectDeviation = false)
+ {
+ /*
+ * WARNING TO POTENTIAL CONTRIBUTORS:
+ *
+ * THIS right here is the MOST COMPLEX algorithm in the whole project.
+ * Everything is finely tuned to create 100% accurate, pixel-perfect
+ * resizes. A SINGLE PIXEL ERROR in your calculations WILL lead to it
+ * sometimes outputting illegally formatted files that will be rejected
+ * by Instagram. We know this, because we have SEEN IT HAPPEN while we
+ * tweaked and tweaked and tweaked to balance everything perfectly!
+ *
+ * Unfortunately, this file also seems to attract a lot of beginners.
+ * Maybe because a "media processor" seems "fun and easy". But that
+ * would be an incorrect guess. It's the most serious algorithm in the
+ * whole project. If you break it, *YOU* break people's uploads.
+ *
+ * We have had many random, new contributors just jumping in and adding
+ * zero-effort code everywhere in here, and breaking the whole balance,
+ * and then opening pull requests. We have rejected EVERY single one of
+ * those pull requests because they were totally unusable and unsafe.
+ *
+ * We will not accept such pull requests. Ever.
+ *
+ * This warning is here to save your time, and ours.
+ *
+ * If you are interested in helping out with the media algorithms, then
+ * that's GREAT! But in that case we require that you fully read through
+ * the algorithms below and all of its comments about 50 times over a
+ * 3-4 day period - until you understand every single step perfectly.
+ * The comments will help make it clearer the more you read...
+ *
+ * ...and make an effort.
+ *
+ * Then you are ready... and welcome to the team. :-)
+ *
+ * Thank you.
+ */
+
+ if ($forceTargetAspectRatio !== null) {
+ $this->_debugText('SPECIAL_PARAMETERS: Forced Target Aspect Ratio', 'forceTargetAspectRatio=%.5f', $forceTargetAspectRatio);
+ }
+
+ // Initialize target canvas to original input dimensions & aspect ratio.
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $targetWidth = (int) $inputWidth;
+ $targetHeight = (int) $inputHeight;
+ $targetAspectRatio = (float) ($inputWidth / $inputHeight);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_INPUT: Input Canvas Size');
+
+ // Check aspect ratio and crop/expand the canvas to fit aspect if needed.
+ if (
+ ($minAspectRatio !== null && $targetAspectRatio < $minAspectRatio)
+ || ($forceTargetAspectRatio !== null && $targetAspectRatio < $forceTargetAspectRatio)
+ ) {
+ // Determine target ratio; uses forced aspect ratio if set,
+ // otherwise we target the MINIMUM allowed ratio (since we're < it)).
+ $targetAspectRatio = $forceTargetAspectRatio !== null ? $forceTargetAspectRatio : $minAspectRatio;
+
+ if ($operation === self::CROP) {
+ // We need to limit the height, so floor is used intentionally to
+ // AVOID rounding height upwards to a still-too-low aspect ratio.
+ $targetHeight = (int) floor($targetWidth / $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_CROPPED: %s', $forceTargetAspectRatio === null ? 'Aspect Was < MIN' : 'Applying Forced Aspect for INPUT < TARGET'));
+ } elseif ($operation === self::EXPAND) {
+ // We need to expand the width with left/right borders. We use
+ // ceil to guarantee that the final media is wide enough to be
+ // above the minimum allowed aspect ratio.
+ $targetWidth = (int) ceil($targetHeight * $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_EXPANDED: %s', $forceTargetAspectRatio === null ? 'Aspect Was < MIN' : 'Applying Forced Aspect for INPUT < TARGET'));
+ }
+ } elseif (
+ ($maxAspectRatio !== null && $targetAspectRatio > $maxAspectRatio)
+ || ($forceTargetAspectRatio !== null && $targetAspectRatio > $forceTargetAspectRatio)
+ ) {
+ // Determine target ratio; uses forced aspect ratio if set,
+ // otherwise we target the MAXIMUM allowed ratio (since we're > it)).
+ $targetAspectRatio = $forceTargetAspectRatio !== null ? $forceTargetAspectRatio : $maxAspectRatio;
+
+ if ($operation === self::CROP) {
+ // We need to limit the width. We use floor to guarantee cutting
+ // enough pixels, since our width exceeds the maximum allowed ratio.
+ $targetWidth = (int) floor($targetHeight * $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_CROPPED: %s', $forceTargetAspectRatio === null ? 'Aspect Was > MAX' : 'Applying Forced Aspect for INPUT > TARGET'));
+ } elseif ($operation === self::EXPAND) {
+ // We need to expand the height with top/bottom borders. We use
+ // ceil to guarantee that the final media is tall enough to be
+ // below the maximum allowed aspect ratio.
+ $targetHeight = (int) ceil($targetWidth / $targetAspectRatio);
+ $this->_debugDimensions($targetWidth, $targetHeight, sprintf('CANVAS_EXPANDED: %s', $forceTargetAspectRatio === null ? 'Aspect Was > MAX' : 'Applying Forced Aspect for INPUT > TARGET'));
+ }
+ } else {
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS: Aspect Ratio Already Legal');
+ }
+
+ // Determine whether the final target ratio is closest to either the
+ // legal MINIMUM or the legal MAXIMUM aspect ratio limits.
+ // NOTE: The target ratio will actually still be set to the original
+ // input media's ratio in case of no aspect ratio adjustments above.
+ // NOTE: If min and/or max ratios were not provided, we default min to
+ // `0` and max to `9999999` to ensure that we properly detect the "least
+ // distance" direction even when only one (or neither) of the two "range
+ // limit values" were provided.
+ $minAspectDistance = abs(($minAspectRatio !== null
+ ? $minAspectRatio : 0) - $targetAspectRatio);
+ $maxAspectDistance = abs(($maxAspectRatio !== null
+ ? $maxAspectRatio : 9999999) - $targetAspectRatio);
+ $isClosestToMinAspect = ($minAspectDistance <= $maxAspectDistance);
+
+ // We MUST now set up the correct height re-calculation behavior for the
+ // later algorithm steps. This is used whenever our canvas needs to be
+ // re-scaled by any other code below. If our chosen, final target ratio
+ // is closest to the minimum allowed legal ratio, we'll always use
+ // floor() on the height to ensure that the height value becomes as low
+ // as possible (since having LESS height compared to width is what
+ // causes the aspect ratio value to grow), to ensure that the final
+ // result's ratio (after any additional adjustments) will ALWAYS be
+ // ABOVE the minimum legal ratio (minAspectRatio). Otherwise we'll
+ // instead use ceil() on the height (since having more height causes the
+ // aspect ratio value to shrink), to ensure that the result is always
+ // BELOW the maximum ratio (maxAspectRatio).
+ $useFloorHeightRecalc = $isClosestToMinAspect;
+
+ // Verify square target ratios by ensuring canvas is now a square.
+ // NOTE: This is just a sanity check against wrong code above. It will
+ // never execute, since all code above took care of making both
+ // dimensions identical already (if they differed in any way, they had a
+ // non-1 ratio and invoked the aspect ratio cropping/expansion code). It
+ // then made identical thanks to the fact that X / 1 = X, and X * 1 = X.
+ // NOTE: It's worth noting that our squares are always the size of the
+ // shortest side when cropping or the longest side when expanding.
+ // WARNING: Comparison MUST use `==` (NOT strict `===`) to support both
+ // int(1) and float(1.0) values!
+ if ($targetAspectRatio == 1.0 && $targetWidth !== $targetHeight) { // Ratio 1 = Square.
+ $targetWidth = $targetHeight = $operation === self::CROP
+ ? min($targetWidth, $targetHeight)
+ : max($targetWidth, $targetHeight);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_SQUARIFY: Fixed Badly Generated Square');
+ }
+
+ // Lastly, enforce minimum and maximum width limits on our final canvas.
+ // NOTE: Instagram only enforces width & aspect ratio, which in turn
+ // auto-limits height (since we can only use legal height ratios).
+ // NOTE: Yet again, if the target ratio is 1 (square), we'll get
+ // identical width & height, so NO NEED to MANUALLY "fix square" here.
+ if ($targetWidth > $maxWidth) {
+ $targetWidth = $maxWidth;
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Width Was > MAX');
+ $targetHeight = $this->_accurateHeightRecalc($useFloorHeightRecalc, $targetAspectRatio, $targetWidth);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Height Recalc From Width & Aspect');
+ } elseif ($targetWidth < $minWidth) {
+ $targetWidth = $minWidth;
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Width Was < MIN');
+ $targetHeight = $this->_accurateHeightRecalc($useFloorHeightRecalc, $targetAspectRatio, $targetWidth);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_WIDTH: Height Recalc From Width & Aspect');
+ }
+
+ // All of the main canvas algorithms are now finished, and we are now
+ // able to check Mod2 compatibility and accurately readjust if needed.
+ $mod2WidthDiff = $mod2HeightDiff = 0;
+ if ($isMod2CanvasRequired
+ && (!$this->_isNumberMod2($targetWidth) || !$this->_isNumberMod2($targetHeight))
+ ) {
+ // Calculate the Mod2-adjusted final canvas size.
+ $mod2Canvas = $this->_calculateAdjustedMod2Canvas(
+ $inputWidth,
+ $inputHeight,
+ $useFloorHeightRecalc,
+ $targetWidth,
+ $targetHeight,
+ $targetAspectRatio,
+ $minWidth,
+ $maxWidth,
+ $minAspectRatio,
+ $maxAspectRatio,
+ $allowNewAspectDeviation
+ );
+
+ // Determine the pixel difference before and after processing.
+ $mod2WidthDiff = $mod2Canvas->getWidth() - $targetWidth;
+ $mod2HeightDiff = $mod2Canvas->getHeight() - $targetHeight;
+ $this->_debugText('CANVAS: Mod2 Difference Stats', 'width=%s, height=%s', $mod2WidthDiff, $mod2HeightDiff);
+
+ // Update the final canvas to the Mod2-adjusted canvas size.
+ // NOTE: If code above failed, the new values are invalid. But so
+ // could our original values have been. We check that further down.
+ $targetWidth = $mod2Canvas->getWidth();
+ $targetHeight = $mod2Canvas->getHeight();
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS: Updated From Mod2 Result');
+ }
+
+ // Create the new canvas Dimensions object.
+ $canvas = new Dimensions($targetWidth, $targetHeight);
+ $this->_debugDimensions($targetWidth, $targetHeight, 'CANVAS_OUTPUT: Final Output Canvas Size');
+
+ // We must now validate the canvas before returning it.
+ // NOTE: Most of these are just strict sanity-checks to protect against
+ // bad code contributions in the future. The canvas won't be able to
+ // pass all of these checks unless the algorithm above remains perfect.
+ $isIllegalRatio = (($minAspectRatio !== null && $canvas->getAspectRatio() < $minAspectRatio)
+ || ($maxAspectRatio !== null && $canvas->getAspectRatio() > $maxAspectRatio));
+ if ($canvas->getWidth() < 1 || $canvas->getHeight() < 1) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Target width (%s) or height (%s) less than one pixel.',
+ $canvas->getWidth(), $canvas->getHeight()
+ ));
+ } elseif ($canvas->getWidth() < $minWidth) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Target width (%s) less than minimum allowed (%s).',
+ $canvas->getWidth(), $minWidth
+ ));
+ } elseif ($canvas->getWidth() > $maxWidth) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Target width (%s) greater than maximum allowed (%s).',
+ $canvas->getWidth(), $maxWidth
+ ));
+ } elseif ($isIllegalRatio) {
+ if (!$allowNewAspectDeviation) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Unable to reach target aspect ratio range during output canvas generation. The range of allowed aspect ratios is too narrow (%.8f - %.8f). We achieved a ratio of %.8f.',
+ $minAspectRatio !== null ? $minAspectRatio : 0.0,
+ $maxAspectRatio !== null ? $maxAspectRatio : INF,
+ $canvas->getAspectRatio()
+ ));
+ } else {
+ // The user wants us to allow "near-misses", so we proceed...
+ $this->_debugDimensions($canvas->getWidth(), $canvas->getHeight(), 'CANVAS_FINAL: Allowing Deviating Aspect Ratio');
+ }
+ }
+
+ return [
+ 'canvas' => $canvas,
+ 'mod2WidthDiff' => $mod2WidthDiff,
+ 'mod2HeightDiff' => $mod2HeightDiff,
+ ];
+ }
+
+ /**
+ * Calculates a new relative height using the target aspect ratio.
+ *
+ * Used internally by `_calculateNewCanvas()`.
+ *
+ * This algorithm aims at the highest-possible or lowest-possible resulting
+ * aspect ratio based on what's needed. It uses either `floor()` or `ceil()`
+ * depending on whether we need the resulting aspect ratio to be >= or <=
+ * the target aspect ratio.
+ *
+ * The principle behind this is the fact that removing height (via floor)
+ * will give us a higher aspect ratio. And adding height (via ceil) will
+ * give us a lower aspect ratio.
+ *
+ * If the target aspect ratio is square (1), height becomes equal to width.
+ *
+ * @param bool $useFloorHeightRecalc
+ * @param float $targetAspectRatio
+ * @param int $targetWidth
+ *
+ * @return int
+ */
+ protected function _accurateHeightRecalc(
+ $useFloorHeightRecalc,
+ $targetAspectRatio,
+ $targetWidth)
+ {
+ // Read the docs above to understand this CRITICALLY IMPORTANT code.
+ $targetHeight = $useFloorHeightRecalc
+ ? (int) floor($targetWidth / $targetAspectRatio) // >=
+ : (int) ceil($targetWidth / $targetAspectRatio); // <=
+
+ return $targetHeight;
+ }
+
+ /**
+ * Adjusts dimensions to create a Mod2-compatible canvas.
+ *
+ * Used internally by `_calculateNewCanvas()`.
+ *
+ * The reason why this function also takes the original input width/height
+ * is because it tries to maximize its usage of the available original pixel
+ * surface area while correcting the dimensions. It uses the extra
+ * information to know when it's safely able to grow the canvas beyond the
+ * given target width/height parameter values.
+ *
+ * @param int $inputWidth
+ * @param int $inputHeight
+ * @param bool $useFloorHeightRecalc
+ * @param int $targetWidth
+ * @param int $targetHeight
+ * @param float $targetAspectRatio
+ * @param int $minWidth
+ * @param int $maxWidth
+ * @param float|null $minAspectRatio
+ * @param float|null $maxAspectRatio
+ * @param bool $allowNewAspectDeviation See constructor arg docs.
+ *
+ * @throws \RuntimeException If requested canvas couldn't be achieved, most
+ * commonly if you have chosen way too narrow
+ * aspect ratio ranges that cannot be perfectly
+ * reached by your input media, and you AREN'T
+ * running with `$allowNewAspectDeviation`.
+ *
+ * @return Dimensions
+ *
+ * @see InstagramMedia::_calculateNewCanvas()
+ */
+ protected function _calculateAdjustedMod2Canvas(
+ $inputWidth,
+ $inputHeight,
+ $useFloorHeightRecalc,
+ $targetWidth,
+ $targetHeight,
+ $targetAspectRatio,
+ $minWidth = 1,
+ $maxWidth = 99999,
+ $minAspectRatio = null,
+ $maxAspectRatio = null,
+ $allowNewAspectDeviation = false)
+ {
+ // Initialize to the calculated canvas size.
+ $mod2Width = $targetWidth;
+ $mod2Height = $targetHeight;
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Current Canvas Size');
+
+ // Determine if we're able to cut an extra pixel from the width if
+ // necessary, or if cutting would take us below the minimum width.
+ $canCutWidth = $mod2Width > $minWidth;
+
+ // To begin, we must correct the width if it's uneven. We'll only do
+ // this once, and then we'll leave the width at its new number. By
+ // keeping it static, we don't risk going over its min/max width
+ // limits. And by only varying one dimension (height) if multiple Mod2
+ // offset adjustments are needed, then we'll properly get a steadily
+ // increasing/decreasing aspect ratio (moving towards the target ratio).
+ if (!$this->_isNumberMod2($mod2Width)) {
+ // Always prefer cutting an extra pixel, rather than stretching
+ // by +1. But use +1 if cutting would take us below minimum width.
+ // NOTE: Another IMPORTANT reason to CUT width rather than extend
+ // is because in narrow cases (canvas close to original input size),
+ // the extra width proportionally increases total area (thus height
+ // too), and gives us less of the original pixels on the height-axis
+ // to play with when attempting to fix the height (and its ratio).
+ $mod2Width += ($canCutWidth ? -1 : 1);
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Width Mod2Fix');
+
+ // Calculate the new relative height based on the new width.
+ $mod2Height = $this->_accurateHeightRecalc($useFloorHeightRecalc, $targetAspectRatio, $mod2Width);
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Height Recalc From Width & Aspect');
+ }
+
+ // Ensure that the calculated height is also Mod2, but totally ignore
+ // the aspect ratio at this moment (we'll fix that later). Instead,
+ // we'll use the same pattern we'd use for width above. That way, if
+ // both width and height were uneven, they both get adjusted equally.
+ if (!$this->_isNumberMod2($mod2Height)) {
+ $mod2Height += ($canCutWidth ? -1 : 1);
+ $this->_debugDimensions($mod2Width, $mod2Height, 'MOD2_CANVAS: Height Mod2Fix');
+ }
+
+ // We will now analyze multiple different height alternatives to find
+ // which one gives us the best visual quality. This algorithm looks
+ // for the best qualities (with the most pixel area) first. It first
+ // tries the current height (offset 0, which is the closest to the
+ // pre-Mod2 adjusted canvas), then +2 pixels (gives more pixel area if
+ // this is possible), then -2 pixels (cuts but may be our only choice).
+ // After that, it checks 4, -4, 6 and -6 as well.
+ // NOTE: Every increased offset (+/-2, then +/-4, then +/- 6) USUALLY
+ // (but not always) causes more and more deviation from the intended
+ // cropping aspect ratio. So don't add any more steps after 6, since
+ // NOTHING will be THAT far off! Six was chosen as a good balance.
+ // NOTE: Every offset is checked for visual stretching and aspect ratio,
+ // and then rated into one of 3 categories: "perfect" (legal aspect
+ // ratio, no stretching), "stretch" (legal aspect ratio, but stretches),
+ // or "bad" (illegal aspect ratio).
+ $heightAlternatives = ['perfect' => [], 'stretch' => [], 'bad' => []];
+ static $offsetPriorities = [0, 2, -2, 4, -4, 6, -6];
+ foreach ($offsetPriorities as $offset) {
+ // Calculate the new height and its resulting aspect ratio.
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ $offsetMod2Height = $mod2Height + $offset;
+ $offsetMod2AspectRatio = (float) ($mod2Width / $offsetMod2Height);
+
+ // Check if the aspect ratio is legal.
+ $isLegalRatio = (($minAspectRatio === null || $offsetMod2AspectRatio >= $minAspectRatio)
+ && ($maxAspectRatio === null || $offsetMod2AspectRatio <= $maxAspectRatio));
+
+ // Detect whether the height would need stretching. Stretching is
+ // defined as "not enough pixels in the input media to reach".
+ // NOTE: If the input media has been upscaled (such as a 64x64 image
+ // being turned into 320x320), then we will ALWAYS detect that media
+ // as needing stretching. That's intentional and correct, because
+ // such media will INDEED need stretching, so there's never going to
+ // be a perfect rating for it (where aspect ratio is legal AND zero
+ // stretching is needed to reach those dimensions).
+ // NOTE: The max() gets rid of negative values (cropping).
+ $stretchAmount = max(0, $offsetMod2Height - $inputHeight);
+
+ // Calculate the deviation from the target aspect ratio. The larger
+ // this number is, the further away from "the ideal canvas". The
+ // "perfect" answers will always deviate by different amount, and
+ // the most perfect one is the one with least deviation.
+ $ratioDeviation = abs($offsetMod2AspectRatio - $targetAspectRatio);
+
+ // Rate this height alternative and store it according to rating.
+ $rating = ($isLegalRatio && !$stretchAmount ? 'perfect' : ($isLegalRatio ? 'stretch' : 'bad'));
+ $heightAlternatives[$rating][] = [
+ 'offset' => $offset,
+ 'height' => $offsetMod2Height,
+ 'ratio' => $offsetMod2AspectRatio,
+ 'isLegalRatio' => $isLegalRatio,
+ 'stretchAmount' => $stretchAmount,
+ 'ratioDeviation' => $ratioDeviation,
+ 'rating' => $rating,
+ ];
+ $this->_debugDimensions($mod2Width, $offsetMod2Height, sprintf(
+ 'MOD2_CANVAS_CHECK: Testing Height Mod2Ratio (h%s%s = %s)',
+ ($offset >= 0 ? '+' : ''), $offset, $rating)
+ );
+ }
+
+ // Now pick the BEST height from our available choices (if any). We will
+ // pick the LEGAL height that has the LEAST amount of deviation from the
+ // ideal aspect ratio. In other words, the BEST-LOOKING aspect ratio!
+ // NOTE: If we find no legal (perfect or stretch) choices, we'll pick
+ // the most accurate (least deviation from ratio) of the bad choices.
+ $bestHeight = null;
+ foreach (['perfect', 'stretch', 'bad'] as $rating) {
+ if (!empty($heightAlternatives[$rating])) {
+ // Sort all alternatives by their amount of ratio deviation.
+ usort($heightAlternatives[$rating], function ($a, $b) {
+ return ($a['ratioDeviation'] < $b['ratioDeviation'])
+ ? -1 : (($a['ratioDeviation'] > $b['ratioDeviation']) ? 1 : 0);
+ });
+
+ // Pick the 1st array element, which has the least deviation!
+ $bestHeight = $heightAlternatives[$rating][0];
+ break;
+ }
+ }
+
+ // Process and apply the best-possible height we found.
+ $mod2Height = $bestHeight['height'];
+ $this->_debugDimensions($mod2Width, $mod2Height, sprintf(
+ 'MOD2_CANVAS: Selected Most Ideal Height Mod2Ratio (h%s%s = %s)',
+ ($bestHeight['offset'] >= 0 ? '+' : ''), $bestHeight['offset'], $bestHeight['rating']
+ ));
+
+ // Decide what to do if there were no legal aspect ratios among our
+ // calculated choices. This can happen if the user gave us an insanely
+ // narrow range (such as "min/max ratio 1.6578" or whatever).
+ if ($bestHeight['rating'] === 'bad') {
+ if (!$allowNewAspectDeviation) {
+ throw new \RuntimeException(sprintf(
+ 'Canvas calculation failed. Unable to reach target aspect ratio range during Mod2 canvas conversion. The range of allowed aspect ratios is too narrow (%.8f - %.8f). We achieved a ratio of %.8f.',
+ $minAspectRatio !== null ? $minAspectRatio : 0.0,
+ $maxAspectRatio !== null ? $maxAspectRatio : INF,
+ (float) ($mod2Width / $mod2Height)
+ ));
+ } else {
+ // They WANT us to allow "near-misses", so we'll KEEP our best
+ // possible bad ratio here (the one that was closest to the
+ // target). We didn't find any more ideal aspect ratio (since
+ // all other attempts ALSO FAILED the aspect ratio ranges), so
+ // we have NO idea if they'd prefer any others! ;-)
+ $this->_debugDimensions($mod2Width, $mod2Height, sprintf(
+ 'MOD2_CANVAS: Allowing Deviating Height Mod2Ratio (h%s%s = %s)',
+ ($bestHeight['offset'] >= 0 ? '+' : ''), $bestHeight['offset'], $bestHeight['rating']
+ ));
+ }
+ }
+
+ return new Dimensions($mod2Width, $mod2Height);
+ }
+
+ /**
+ * Checks whether a number is Mod2.
+ *
+ * @param int|float $number
+ *
+ * @return bool
+ */
+ protected function _isNumberMod2(
+ $number)
+ {
+ // NOTE: The modulo operator correctly returns ints even for float input such as 1.999.
+ return $number % 2 === 0;
+ }
+
+ /**
+ * Output debug text.
+ *
+ * @param string $stepDescription
+ * @param string $formatMessage
+ * @param mixed $args,...
+ */
+ protected function _debugText(
+ $stepDescription,
+ $formatMessage,
+ ...$args)
+ {
+ if (!$this->_debug) {
+ return;
+ }
+
+ printf(
+ "[\033[1;33m%s\033[0m] {$formatMessage}\n",
+ $stepDescription,
+ ...$args
+ );
+ }
+
+ /**
+ * Debug current calculation dimensions and their ratio.
+ *
+ * @param int|float $width
+ * @param int|float $height
+ * @param string|null $stepDescription
+ */
+ protected function _debugDimensions(
+ $width,
+ $height,
+ $stepDescription = null)
+ {
+ if (!$this->_debug) {
+ return;
+ }
+
+ printf(
+ // NOTE: This uses 8 decimals for proper debugging, since small
+ // rounding errors can make rejected ratios look valid.
+ "[\033[1;33m%s\033[0m] w=%s h=%s (aspect %.8f)\n",
+ $stepDescription !== null ? $stepDescription : 'DEBUG',
+ $width, $height, (float) ($width / $height)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Media/MediaDetails.php b/vendor/mgp25/instagram-php/src/Media/MediaDetails.php
new file mode 100755
index 0000000..f7516de
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Media/MediaDetails.php
@@ -0,0 +1,172 @@
+hasSwappedAxes() ? $this->_height : $this->_width;
+ }
+
+ /**
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->hasSwappedAxes() ? $this->_width : $this->_height;
+ }
+
+ /**
+ * @return float
+ */
+ public function getAspectRatio()
+ {
+ // NOTE: MUST `float`-cast to FORCE float even when dividing EQUAL ints.
+ return (float) ($this->getWidth() / $this->getHeight());
+ }
+
+ /**
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->_filename;
+ }
+
+ /**
+ * @return int
+ */
+ public function getFilesize()
+ {
+ return $this->_filesize;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBasename()
+ {
+ // Fix full path disclosure.
+ return basename($this->_filename);
+ }
+
+ /**
+ * Get the minimum allowed media width for this media type.
+ *
+ * @return int
+ */
+ abstract public function getMinAllowedWidth();
+
+ /**
+ * Get the maximum allowed media width for this media type.
+ *
+ * @return int
+ */
+ abstract public function getMaxAllowedWidth();
+
+ /**
+ * Check whether the media has swapped axes.
+ *
+ * @return bool
+ */
+ abstract public function hasSwappedAxes();
+
+ /**
+ * Check whether the media is horizontally flipped.
+ *
+ * ```
+ * ***** *****
+ * * *
+ * *** => ***
+ * * *
+ * * *
+ * ```
+ *
+ * @return bool
+ */
+ abstract public function isHorizontallyFlipped();
+
+ /**
+ * Check whether the media is vertically flipped.
+ *
+ * ```
+ * ***** *
+ * * *
+ * *** => ***
+ * * *
+ * * *****
+ * ```
+ *
+ * @return bool
+ */
+ abstract public function isVerticallyFlipped();
+
+ /**
+ * Constructor.
+ *
+ * @param string $filename
+ * @param int $filesize
+ * @param int $width
+ * @param int $height
+ */
+ public function __construct(
+ $filename,
+ $filesize,
+ $width,
+ $height)
+ {
+ $this->_filename = $filename;
+ $this->_filesize = $filesize;
+ $this->_width = $width;
+ $this->_height = $height;
+ }
+
+ /**
+ * Verifies that a piece of media follows Instagram's rules.
+ *
+ * @param ConstraintsInterface $constraints
+ *
+ * @throws \InvalidArgumentException If Instagram won't allow this file.
+ */
+ public function validate(
+ ConstraintsInterface $constraints)
+ {
+ $mediaFilename = $this->getBasename();
+
+ // Check rotation.
+ if ($this->hasSwappedAxes() || $this->isVerticallyFlipped() || $this->isHorizontallyFlipped()) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts non-rotated media. Your file "%s" is either rotated or flipped or both.',
+ $mediaFilename
+ ));
+ }
+
+ // Check Aspect Ratio.
+ // NOTE: This Instagram rule is the same for both videos and photos.
+ $aspectRatio = $this->getAspectRatio();
+ $minAspectRatio = $constraints->getMinAspectRatio();
+ $maxAspectRatio = $constraints->getMaxAspectRatio();
+ if ($aspectRatio < $minAspectRatio || $aspectRatio > $maxAspectRatio) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts %s media with aspect ratios between %.3f and %.3f. Your file "%s" has a %.4f aspect ratio.',
+ $constraints->getTitle(), $minAspectRatio, $maxAspectRatio, $mediaFilename, $aspectRatio
+ ));
+ }
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Media/Photo/InstagramPhoto.php b/vendor/mgp25/instagram-php/src/Media/Photo/InstagramPhoto.php
new file mode 100755
index 0000000..57b202d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Media/Photo/InstagramPhoto.php
@@ -0,0 +1,326 @@
+_details = new PhotoDetails($this->_inputFile);
+ }
+
+ /** {@inheritdoc} */
+ protected function _isMod2CanvasRequired()
+ {
+ return false;
+ }
+
+ /** {@inheritdoc} */
+ protected function _createOutputFile(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas)
+ {
+ $outputFile = null;
+
+ try {
+ // Attempt to process the input file.
+ $resource = $this->_loadImage();
+
+ try {
+ $output = $this->_processResource($resource, $srcRect, $dstRect, $canvas);
+ } finally {
+ @imagedestroy($resource);
+ }
+
+ // Write the result to disk.
+ try {
+ // Prepare output file.
+ $outputFile = Utils::createTempFile($this->_tmpPath, 'IMG');
+
+ if (!imagejpeg($output, $outputFile, self::JPEG_QUALITY)) {
+ throw new \RuntimeException('Failed to create JPEG image file.');
+ }
+ } finally {
+ @imagedestroy($output);
+ }
+ } catch (\Exception $e) {
+ if ($outputFile !== null && is_file($outputFile)) {
+ @unlink($outputFile);
+ }
+
+ throw $e; // Re-throw.
+ }
+
+ return $outputFile;
+ }
+
+ /**
+ * Loads image into a resource.
+ *
+ * @throws \RuntimeException
+ *
+ * @return resource
+ */
+ protected function _loadImage()
+ {
+ // Read the correct input file format.
+ switch ($this->_details->getType()) {
+ case IMAGETYPE_JPEG:
+ $resource = imagecreatefromjpeg($this->_inputFile);
+ break;
+ case IMAGETYPE_PNG:
+ $resource = imagecreatefrompng($this->_inputFile);
+ break;
+ case IMAGETYPE_GIF:
+ $resource = imagecreatefromgif($this->_inputFile);
+ break;
+ default:
+ throw new \RuntimeException('Unsupported image type.');
+ }
+ if ($resource === false) {
+ throw new \RuntimeException('Failed to load image.');
+ }
+
+ return $resource;
+ }
+
+ /**
+ * @param resource $source The original image loaded as a resource.
+ * @param Rectangle $srcRect Rectangle to copy from the input.
+ * @param Rectangle $dstRect Destination place and scale of copied pixels.
+ * @param Dimensions $canvas The size of the destination canvas.
+ *
+ * @throws \Exception
+ * @throws \RuntimeException
+ *
+ * @return resource
+ */
+ protected function _processResource(
+ $source,
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas
+ ) {
+ // If our input image pixels are stored rotated, swap all coordinates.
+ if ($this->_details->hasSwappedAxes()) {
+ $srcRect = $srcRect->withSwappedAxes();
+ $dstRect = $dstRect->withSwappedAxes();
+ $canvas = $canvas->withSwappedAxes();
+ }
+
+ // Create an output canvas with our desired size.
+ $output = imagecreatetruecolor($canvas->getWidth(), $canvas->getHeight());
+ if ($output === false) {
+ throw new \RuntimeException('Failed to create output image.');
+ }
+
+ // Fill the output canvas with our background color.
+ // NOTE: If cropping, this is just to have a nice background in
+ // the resulting JPG if a transparent image was used as input.
+ // If expanding, this will be the color of the border as well.
+ $bgColor = imagecolorallocate($output, $this->_bgColor[0], $this->_bgColor[1], $this->_bgColor[2]);
+ if ($bgColor === false) {
+ throw new \RuntimeException('Failed to allocate background color.');
+ }
+
+ // If expanding and blurredBorder, use the photo as a blurred border.
+ if ($this->_blurredBorder && $this->_operation === self::EXPAND) {
+ // Calculate the rectangle
+ $blurImageRect = $this->_calculateBlurImage($srcRect, $canvas);
+ $scaleDownRect = $blurImageRect->withRescaling(self::BLUR_IMAGE_SCALE, 'ceil');
+
+ // Create a canvas for the scaled image
+ $scaledDownImage = imagecreatetruecolor($scaleDownRect->getWidth(), $scaleDownRect->getHeight());
+ if ($scaledDownImage === false) {
+ throw new \RuntimeException('Failed to create scaled down image.');
+ }
+
+ // Copy the image to the scaled canvas
+ if (imagecopyresampled(
+ $scaledDownImage, $source,
+ 0, 0,
+ $blurImageRect->getX(), $blurImageRect->getY(),
+ $scaleDownRect->getWidth(), $scaleDownRect->getHeight(),
+ $blurImageRect->getWidth(), $blurImageRect->getHeight()) === false) {
+ throw new \RuntimeException('Failed to resample blur image.');
+ }
+
+ //Blur the scaled canvas
+ for ($i = 0; $i < 40; ++$i) {
+ imagefilter($scaledDownImage, IMG_FILTER_GAUSSIAN_BLUR, 999);
+ }
+
+ //Copy the blurred image to the output canvas.
+ if (imagecopyresampled(
+ $output, $scaledDownImage,
+ 0, 0,
+ 0, 0,
+ $canvas->getWidth(), $canvas->getHeight(),
+ $scaleDownRect->getWidth(), $scaleDownRect->getHeight()) === false) {
+ throw new \RuntimeException('Failed to resample blurred image.');
+ }
+ } else {
+ if (imagefilledrectangle($output, 0, 0, $canvas->getWidth() - 1, $canvas->getHeight() - 1, $bgColor) === false) {
+ throw new \RuntimeException('Failed to fill image with background color.');
+ }
+ }
+
+ // Copy the resized (and resampled) image onto the new canvas.
+ if (imagecopyresampled(
+ $output, $source,
+ $dstRect->getX(), $dstRect->getY(),
+ $srcRect->getX(), $srcRect->getY(),
+ $dstRect->getWidth(), $dstRect->getHeight(),
+ $srcRect->getWidth(), $srcRect->getHeight()
+ ) === false) {
+ throw new \RuntimeException('Failed to resample image.');
+ }
+
+ // Handle image rotation.
+ $output = $this->_rotateResource($output, $bgColor);
+
+ return $output;
+ }
+
+ /**
+ * Calculates the rectangle of the blur image source.
+ *
+ * @param Rectangle $srcRect
+ * @param Dimensions $canvas
+ *
+ * @return Rectangle
+ */
+ protected function _calculateBlurImage(
+ Rectangle $srcRect,
+ Dimensions $canvas)
+ {
+ $widthScale = (float) ($canvas->getWidth() / $srcRect->getWidth());
+ $heightScale = (float) ($canvas->getHeight() / $srcRect->getHeight());
+ if ($widthScale > $heightScale) {
+ $resX = $srcRect->getX();
+ $resW = $srcRect->getWidth();
+ $resH = (float) $canvas->getHeight() / $widthScale;
+ $resY = (int) floor(($srcRect->getHeight() - $resH) / 2);
+
+ return new Rectangle($resX, $resY, $resW, $resH);
+ } else {
+ $resY = $srcRect->getY();
+ $resH = $srcRect->getHeight();
+ $resW = (float) $canvas->getWidth() / $heightScale;
+ $resX = (int) floor(($srcRect->getWidth() - $resW) / 2);
+
+ return new Rectangle($resX, $resY, $resW, $resH);
+ }
+ }
+
+ /**
+ * Wrapper for PHP's imagerotate function.
+ *
+ * @param resource $original
+ * @param int $bgColor
+ *
+ * @throws \RuntimeException
+ *
+ * @return resource
+ */
+ protected function _rotateResource(
+ $original,
+ $bgColor)
+ {
+ $angle = 0;
+ $flip = null;
+ // Find out angle and flip.
+ if ($this->_details->hasSwappedAxes()) {
+ if ($this->_details->isHorizontallyFlipped() && $this->_details->isVerticallyFlipped()) {
+ $angle = -90;
+ $flip = IMG_FLIP_HORIZONTAL;
+ } elseif ($this->_details->isHorizontallyFlipped()) {
+ $angle = -90;
+ } elseif ($this->_details->isVerticallyFlipped()) {
+ $angle = 90;
+ } else {
+ $angle = -90;
+ $flip = IMG_FLIP_VERTICAL;
+ }
+ } else {
+ if ($this->_details->isHorizontallyFlipped() && $this->_details->isVerticallyFlipped()) {
+ $flip = IMG_FLIP_BOTH;
+ } elseif ($this->_details->isHorizontallyFlipped()) {
+ $flip = IMG_FLIP_HORIZONTAL;
+ } elseif ($this->_details->isVerticallyFlipped()) {
+ $flip = IMG_FLIP_VERTICAL;
+ } else {
+ // Do nothing.
+ }
+ }
+
+ // Flip the image resource if needed. Does not create a new resource.
+ if ($flip !== null && imageflip($original, $flip) === false) {
+ throw new \RuntimeException('Failed to flip image.');
+ }
+
+ // Return original resource if no rotation is needed.
+ if ($angle === 0) {
+ return $original;
+ }
+
+ // Attempt to create a new, rotated image resource.
+ $result = imagerotate($original, $angle, $bgColor);
+ if ($result === false) {
+ throw new \RuntimeException('Failed to rotate image.');
+ }
+
+ // Destroy the original resource since we'll return the new resource.
+ @imagedestroy($original);
+
+ return $result;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Media/Photo/PhotoDetails.php b/vendor/mgp25/instagram-php/src/Media/Photo/PhotoDetails.php
new file mode 100755
index 0000000..497d1ff
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Media/Photo/PhotoDetails.php
@@ -0,0 +1,177 @@
+_type;
+ }
+
+ /** {@inheritdoc} */
+ public function hasSwappedAxes()
+ {
+ return in_array($this->_orientation, [5, 6, 7, 8], true);
+ }
+
+ /** {@inheritdoc} */
+ public function isHorizontallyFlipped()
+ {
+ return in_array($this->_orientation, [2, 3, 6, 7], true);
+ }
+
+ /** {@inheritdoc} */
+ public function isVerticallyFlipped()
+ {
+ return in_array($this->_orientation, [3, 4, 7, 8], true);
+ }
+
+ /** {@inheritdoc} */
+ public function getMinAllowedWidth()
+ {
+ return self::MIN_WIDTH;
+ }
+
+ /** {@inheritdoc} */
+ public function getMaxAllowedWidth()
+ {
+ return self::MAX_WIDTH;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string $filename Path to the photo file.
+ *
+ * @throws \InvalidArgumentException If the photo file is missing or invalid.
+ */
+ public function __construct(
+ $filename)
+ {
+ // Check if input file exists.
+ if (empty($filename) || !is_file($filename)) {
+ throw new \InvalidArgumentException(sprintf('The photo file "%s" does not exist on disk.', $filename));
+ }
+
+ // Determine photo file size and throw when the file is empty.
+ $filesize = filesize($filename);
+ if ($filesize < 1) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The photo file "%s" is empty.',
+ $filename
+ ));
+ }
+
+ // Get image details.
+ $result = @getimagesize($filename);
+ if ($result === false) {
+ throw new \InvalidArgumentException(sprintf('The photo file "%s" is not a valid image.', $filename));
+ }
+ list($width, $height, $this->_type) = $result;
+
+ // Detect JPEG EXIF orientation if it exists.
+ $this->_orientation = $this->_getExifOrientation($filename, $this->_type);
+
+ parent::__construct($filename, $filesize, $width, $height);
+ }
+
+ /** {@inheritdoc} */
+ public function validate(
+ ConstraintsInterface $constraints)
+ {
+ parent::validate($constraints);
+
+ // WARNING TO CONTRIBUTORS: $mediaFilename is for ERROR DISPLAY to
+ // users. Do NOT use it to read from the hard disk!
+ $mediaFilename = $this->getBasename();
+
+ // Validate image type.
+ // NOTE: It is confirmed that Instagram only accepts JPEG files.
+ $type = $this->getType();
+ if ($type !== IMAGETYPE_JPEG) {
+ throw new \InvalidArgumentException(sprintf('The photo file "%s" is not a JPEG file.', $mediaFilename));
+ }
+
+ $width = $this->getWidth();
+ // Validate photo resolution. Instagram allows between 320px-1080px width.
+ if ($width < self::MIN_WIDTH || $width > self::MAX_WIDTH) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts photos that are between %d and %d pixels wide. Your file "%s" is %d pixels wide.',
+ self::MIN_WIDTH, self::MAX_WIDTH, $mediaFilename, $width
+ ));
+ }
+ }
+
+ /**
+ * Get the EXIF orientation from given file.
+ *
+ * @param string $filename
+ * @param int $type
+ *
+ * @return int
+ */
+ protected function _getExifOrientation(
+ $filename,
+ $type)
+ {
+ if ($type !== IMAGETYPE_JPEG || !function_exists('exif_read_data')) {
+ return self::DEFAULT_ORIENTATION;
+ }
+
+ $exif = @exif_read_data($filename);
+ if ($exif === false || !isset($exif['Orientation'])) {
+ return self::DEFAULT_ORIENTATION;
+ }
+
+ return (int) $exif['Orientation'];
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Media/Video/FFmpeg.php b/vendor/mgp25/instagram-php/src/Media/Video/FFmpeg.php
new file mode 100755
index 0000000..7bde900
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Media/Video/FFmpeg.php
@@ -0,0 +1,244 @@
+_ffmpegBinary = $ffmpegBinary;
+
+ try {
+ $this->version();
+ } catch (\Exception $e) {
+ throw new \RuntimeException(sprintf('It seems that the path to ffmpeg binary is invalid. Please check your path to ensure that it is correct.'));
+ }
+ }
+
+ /**
+ * Create a new instance or use a cached one.
+ *
+ * @param string|null $ffmpegBinary Path to a ffmpeg binary, or NULL to autodetect.
+ *
+ * @return static
+ */
+ public static function factory(
+ $ffmpegBinary = null)
+ {
+ if ($ffmpegBinary === null) {
+ return static::_autoDetectBinary();
+ }
+
+ if (isset(self::$_instances[$ffmpegBinary])) {
+ return self::$_instances[$ffmpegBinary];
+ }
+
+ $instance = new static($ffmpegBinary);
+ self::$_instances[$ffmpegBinary] = $instance;
+
+ return $instance;
+ }
+
+ /**
+ * Run a command and wrap errors into an Exception (if any).
+ *
+ * @param string $command
+ *
+ * @throws \RuntimeException
+ *
+ * @return string[]
+ */
+ public function run(
+ $command)
+ {
+ $process = $this->runAsync($command);
+
+ try {
+ $exitCode = $process->wait();
+ } catch (\Exception $e) {
+ throw new \RuntimeException(sprintf('Failed to run the ffmpeg binary: %s', $e->getMessage()));
+ }
+ if ($exitCode) {
+ $errors = preg_replace('#[\r\n]+#', '"], ["', trim($process->getErrorOutput()));
+ $errorMsg = sprintf('FFmpeg Errors: ["%s"], Command: "%s".', $errors, $command);
+
+ throw new \RuntimeException($errorMsg, $exitCode);
+ }
+
+ return preg_split('#[\r\n]+#', $process->getOutput(), null, PREG_SPLIT_NO_EMPTY);
+ }
+
+ /**
+ * Run a command asynchronously.
+ *
+ * @param string $command
+ *
+ * @return Process
+ */
+ public function runAsync(
+ $command)
+ {
+ $fullCommand = sprintf('%s -v error %s', Args::escape($this->_ffmpegBinary), $command);
+
+ $process = new Process($fullCommand);
+ if (is_int(self::$defaultTimeout) && self::$defaultTimeout > 60) {
+ $process->setTimeout(self::$defaultTimeout);
+ }
+ $process->start();
+
+ return $process;
+ }
+
+ /**
+ * Get the ffmpeg version.
+ *
+ * @throws \RuntimeException
+ *
+ * @return string
+ */
+ public function version()
+ {
+ return $this->run('-version')[0];
+ }
+
+ /**
+ * Get the path to the ffmpeg binary.
+ *
+ * @return string
+ */
+ public function getFFmpegBinary()
+ {
+ return $this->_ffmpegBinary;
+ }
+
+ /**
+ * Check whether ffmpeg has -noautorotate flag.
+ *
+ * @return bool
+ */
+ public function hasNoAutorotate()
+ {
+ if ($this->_hasNoAutorotate === null) {
+ try {
+ $this->run('-noautorotate -f lavfi -i color=color=red -t 1 -f null -');
+ $this->_hasNoAutorotate = true;
+ } catch (\RuntimeException $e) {
+ $this->_hasNoAutorotate = false;
+ }
+ }
+
+ return $this->_hasNoAutorotate;
+ }
+
+ /**
+ * Check whether ffmpeg has libfdk_aac audio encoder.
+ *
+ * @return bool
+ */
+ public function hasLibFdkAac()
+ {
+ if ($this->_hasLibFdkAac === null) {
+ $this->_hasLibFdkAac = $this->_hasAudioEncoder('libfdk_aac');
+ }
+
+ return $this->_hasLibFdkAac;
+ }
+
+ /**
+ * Check whether ffmpeg has specified audio encoder.
+ *
+ * @param string $encoder
+ *
+ * @return bool
+ */
+ protected function _hasAudioEncoder(
+ $encoder)
+ {
+ try {
+ $this->run(sprintf(
+ '-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -c:a %s -t 1 -f null -',
+ Args::escape($encoder)
+ ));
+
+ return true;
+ } catch (\RuntimeException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @throws \RuntimeException
+ *
+ * @return static
+ */
+ protected static function _autoDetectBinary()
+ {
+ $binaries = defined('PHP_WINDOWS_VERSION_MAJOR') ? self::WINDOWS_BINARIES : self::BINARIES;
+ if (self::$defaultBinary !== null) {
+ array_unshift($binaries, self::$defaultBinary);
+ }
+ /* Backwards compatibility. */
+ if (Utils::$ffmpegBin !== null) {
+ array_unshift($binaries, Utils::$ffmpegBin);
+ }
+
+ $instance = null;
+ foreach ($binaries as $binary) {
+ if (isset(self::$_instances[$binary])) {
+ return self::$_instances[$binary];
+ }
+
+ try {
+ $instance = new static($binary);
+ } catch (\Exception $e) {
+ continue;
+ }
+ self::$defaultBinary = $binary;
+ self::$_instances[$binary] = $instance;
+
+ return $instance;
+ }
+
+ throw new \RuntimeException('You must have FFmpeg to process videos. Ensure that its binary-folder exists in your PATH environment variable, or manually set its full path via "\InstagramAPI\Media\Video\FFmpeg::$defaultBinary = \'/home/exampleuser/ffmpeg/bin/ffmpeg\';" at the start of your script.');
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Media/Video/InstagramThumbnail.php b/vendor/mgp25/instagram-php/src/Media/Video/InstagramThumbnail.php
new file mode 100755
index 0000000..882cd3b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Media/Video/InstagramThumbnail.php
@@ -0,0 +1,155 @@
+_thumbnailTimestamp = 1.0; // Default.
+
+ // Handle per-feed timestamps and custom thumbnail timestamps.
+ if (isset($options['targetFeed'])) {
+ switch ($options['targetFeed']) {
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ // Stories always have the thumbnail at "00:00:00.000" instead.
+ $this->_thumbnailTimestamp = 0.0;
+ break;
+ case Constants::FEED_TIMELINE || Constants::FEED_TIMELINE_ALBUM:
+ // Handle custom timestamp (only supported by timeline media).
+ // NOTE: Matches real app which only customizes timeline covers.
+ if (isset($options['thumbnailTimestamp'])) {
+ $customTimestamp = $options['thumbnailTimestamp'];
+ // If custom timestamp is a number, use as-is. Else assume
+ // a "HH:MM:SS[.000]" string and convert it. Throws if bad.
+ $this->_thumbnailTimestamp = is_int($customTimestamp) || is_float($customTimestamp)
+ ? (float) $customTimestamp
+ : Utils::hmsTimeToSeconds($customTimestamp);
+ }
+ break;
+ default:
+ // Keep the default.
+ }
+ }
+
+ // Ensure the timestamp is 0+ and never longer than the video duration.
+ if ($this->_thumbnailTimestamp > $this->_details->getDuration()) {
+ $this->_thumbnailTimestamp = $this->_details->getDuration();
+ }
+ if ($this->_thumbnailTimestamp < 0.0) {
+ throw new \InvalidArgumentException('Thumbnail timestamp must be a positive number.');
+ }
+ }
+
+ /**
+ * Get thumbnail timestamp as a float.
+ *
+ * @return float Thumbnail offset in secs, with milliseconds (decimals).
+ */
+ public function getTimestamp()
+ {
+ return $this->_thumbnailTimestamp;
+ }
+
+ /**
+ * Get thumbnail timestamp as a formatted string.
+ *
+ * @return string The time formatted as `HH:MM:SS.###` (`###` is millis).
+ */
+ public function getTimestampString()
+ {
+ return Utils::hmsTimeFromSeconds($this->_thumbnailTimestamp);
+ }
+
+ /** {@inheritdoc} */
+ protected function _shouldProcess()
+ {
+ // We must always process the video to get its thumbnail.
+ return true;
+ }
+
+ /** {@inheritdoc} */
+ protected function _ffmpegMustRunAgain(
+ $attempt,
+ array $ffmpegOutput)
+ {
+ // If this was the first run, we must look for the "first frame is no
+ // keyframe" error. It is a rare error which can happen when the user
+ // wants to extract a frame from a timestamp that is before the first
+ // keyframe of the video file. Most files can extract frames even at
+ // `00:00:00.000`, but certain files may have garbage at the start of
+ // the file, and thus extracting a garbage / empty / broken frame and
+ // showing this error. The solution is to omit the `-ss` timestamp for
+ // such files to automatically make ffmpeg extract the 1st VALID frame.
+ // NOTE: We'll only need to retry if timestamp is over 0.0. If it was
+ // zero, then we already executed without `-ss` and shouldn't retry.
+ if ($attempt === 1 && $this->_thumbnailTimestamp > 0.0) {
+ foreach ($ffmpegOutput as $line) {
+ // Example: `[flv @ 0x7fc9cc002e00] warning: first frame is no keyframe`.
+ if (strpos($line, ': first frame is no keyframe') !== false) {
+ return true;
+ }
+ }
+ }
+
+ // If this was the 2nd run or there was no error, accept result as-is.
+ return false;
+ }
+
+ /** {@inheritdoc} */
+ protected function _getInputFlags(
+ $attempt)
+ {
+ // The seektime *must* be specified here, before the input file.
+ // Otherwise ffmpeg will do a slow conversion of the whole file
+ // (but discarding converted frames) until it gets to target time.
+ // See: https://trac.ffmpeg.org/wiki/Seeking
+ // IMPORTANT: WE ONLY APPLY THE SEEK-COMMAND ON THE *FIRST* ATTEMPT. SEE
+ // COMMENTS IN `_ffmpegMustRunAgain()` FOR MORE INFORMATION ABOUT WHY.
+ // AND WE'LL OMIT SEEKING COMPLETELY IF IT'S "0.0" ("EARLIEST POSSIBLE"), TO
+ // GUARANTEE SUCCESS AT GRABBING THE "EARLIEST FRAME" W/O NEEDING RETRIES.
+ return $attempt > 1 || $this->_thumbnailTimestamp === 0.0
+ ? []
+ : [
+ sprintf('-ss %s', $this->getTimestampString()),
+ ];
+ }
+
+ /** {@inheritdoc} */
+ protected function _getOutputFlags(
+ $attempt)
+ {
+ return [
+ '-f mjpeg',
+ '-vframes 1',
+ ];
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Media/Video/InstagramVideo.php b/vendor/mgp25/instagram-php/src/Media/Video/InstagramVideo.php
new file mode 100755
index 0000000..c6072a1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Media/Video/InstagramVideo.php
@@ -0,0 +1,263 @@
+_details = new VideoDetails($this->_inputFile);
+
+ $this->_ffmpeg = $ffmpeg;
+ if ($this->_ffmpeg === null) {
+ $this->_ffmpeg = FFmpeg::factory();
+ }
+ }
+
+ /** {@inheritdoc} */
+ protected function _isMod2CanvasRequired()
+ {
+ return true;
+ }
+
+ /** {@inheritdoc} */
+ protected function _createOutputFile(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas)
+ {
+ $outputFile = null;
+
+ try {
+ // Prepare output file.
+ $outputFile = Utils::createTempFile($this->_tmpPath, 'VID');
+ // Attempt to process the input file.
+ // --------------------------------------------------------------
+ // WARNING: This calls ffmpeg, which can run for a long time. The
+ // user may be running in a CLI. In that case, if they press Ctrl-C
+ // to abort, PHP won't run ANY of our shutdown/destructor handlers!
+ // Therefore they'll still have the temp file if they abort ffmpeg
+ // conversion with Ctrl-C, since our auto-cleanup won't run. There's
+ // absolutely nothing good we can do about that (except a signal
+ // handler to interrupt their Ctrl-C, which is a terrible idea).
+ // Their OS should clear its temp folder periodically. Or if they
+ // use a custom temp folder, it's THEIR own job to clear it!
+ // --------------------------------------------------------------
+ $this->_processVideo($srcRect, $dstRect, $canvas, $outputFile);
+ } catch (\Exception $e) {
+ if ($outputFile !== null && is_file($outputFile)) {
+ @unlink($outputFile);
+ }
+
+ throw $e; // Re-throw.
+ }
+
+ return $outputFile;
+ }
+
+ /**
+ * @param Rectangle $srcRect Rectangle to copy from the input.
+ * @param Rectangle $dstRect Destination place and scale of copied pixels.
+ * @param Dimensions $canvas The size of the destination canvas.
+ * @param string $outputFile
+ *
+ * @throws \RuntimeException
+ */
+ protected function _processVideo(
+ Rectangle $srcRect,
+ Rectangle $dstRect,
+ Dimensions $canvas,
+ $outputFile)
+ {
+ // ffmpeg has a bug - https://trac.ffmpeg.org/ticket/6370 - it preserves rotate tag
+ // in the output video when the input video has display matrix with rotate
+ // and rotation was overridden manually.
+ $shouldAutorotate = $this->_ffmpeg->hasNoAutorotate() && $this->_details->hasRotationMatrix();
+
+ // Swap to correct dimensions if the video pixels are stored rotated.
+ if (!$shouldAutorotate && $this->_details->hasSwappedAxes()) {
+ $srcRect = $srcRect->withSwappedAxes();
+ $dstRect = $dstRect->withSwappedAxes();
+ $canvas = $canvas->withSwappedAxes();
+ }
+
+ // Prepare filters.
+ $bgColor = sprintf('0x%02X%02X%02X', ...$this->_bgColor);
+ $filters = [
+ sprintf('crop=w=%d:h=%d:x=%d:y=%d', $srcRect->getWidth(), $srcRect->getHeight(), $srcRect->getX(), $srcRect->getY()),
+ sprintf('scale=w=%d:h=%d', $dstRect->getWidth(), $dstRect->getHeight()),
+ sprintf('pad=w=%d:h=%d:x=%d:y=%d:color=%s', $canvas->getWidth(), $canvas->getHeight(), $dstRect->getX(), $dstRect->getY(), $bgColor),
+ ];
+
+ // Rotate the video (if needed to).
+ $rotationFilters = $this->_getRotationFilters();
+ $noAutorotate = false;
+ if (!empty($rotationFilters) && !$shouldAutorotate) {
+ $filters = array_merge($filters, $rotationFilters);
+ $noAutorotate = $this->_ffmpeg->hasNoAutorotate();
+ }
+
+ $attempt = 0;
+ do {
+ ++$attempt;
+
+ // Get the flags to apply to the input file.
+ $inputFlags = $this->_getInputFlags($attempt);
+ if ($noAutorotate) {
+ $inputFlags[] = '-noautorotate';
+ }
+
+ // Video format can't copy since we always need to re-encode due to video filtering.
+ $ffmpegOutput = $this->_ffmpeg->run(sprintf(
+ '-y %s -i %s -vf %s %s %s',
+ implode(' ', $inputFlags),
+ Args::escape($this->_inputFile),
+ Args::escape(implode(',', $filters)),
+ implode(' ', $this->_getOutputFlags($attempt)),
+ Args::escape($outputFile)
+ ));
+ } while ($this->_ffmpegMustRunAgain($attempt, $ffmpegOutput));
+ }
+
+ /**
+ * Internal function to determine whether ffmpeg needs to run again.
+ *
+ * @param int $attempt Which ffmpeg attempt just executed.
+ * @param string[] $ffmpegOutput Array of error strings from the attempt.
+ *
+ * @throws \RuntimeException If this function wants to give up and determines
+ * that we cannot succeed and should throw completely.
+ *
+ * @return bool TRUE to run again, FALSE to accept the current output.
+ */
+ protected function _ffmpegMustRunAgain(
+ $attempt,
+ array $ffmpegOutput)
+ {
+ return false;
+ }
+
+ /**
+ * Get the input flags (placed before the input filename).
+ *
+ * @param int $attempt The current ffmpeg execution attempt.
+ *
+ * @return string[]
+ */
+ protected function _getInputFlags(
+ $attempt)
+ {
+ return [];
+ }
+
+ /**
+ * Get the output flags (placed before the output filename).
+ *
+ * @param int $attempt The current ffmpeg execution attempt.
+ *
+ * @return string[]
+ */
+ protected function _getOutputFlags(
+ $attempt)
+ {
+ $result = [
+ '-metadata:s:v rotate=""', // Strip rotation from metadata.
+ '-f mp4', // Force output format to MP4.
+ ];
+
+ // Force H.264 for the video.
+ $result[] = '-c:v libx264 -preset fast -crf 24';
+
+ // Force AAC for the audio.
+ if ($this->_details->getAudioCodec() !== 'aac') {
+ if ($this->_ffmpeg->hasLibFdkAac()) {
+ $result[] = '-c:a libfdk_aac -vbr 4';
+ } else {
+ // The encoder 'aac' is experimental but experimental codecs are not enabled,
+ // add '-strict -2' if you want to use it.
+ $result[] = '-strict -2 -c:a aac -b:a 96k';
+ }
+ } else {
+ $result[] = '-c:a copy';
+ }
+
+ // Cut too long videos.
+ // FFmpeg cuts video sticking to a closest frame. As a result we might
+ // end with a video that is longer than desired duration. To prevent this
+ // we must use a duration that is somewhat smaller than its maximum allowed
+ // value. 0.1 sec is 1 frame of 10 FPS video.
+ $maxDuration = $this->_constraints->getMaxDuration() - 0.1;
+ if ($this->_details->getDuration() > $maxDuration) {
+ $result[] = sprintf('-t %.2F', $maxDuration);
+ }
+
+ // TODO Loop too short videos.
+ if ($this->_details->getDuration() < $this->_constraints->getMinDuration()) {
+ $times = ceil($this->_constraints->getMinDuration() / $this->_details->getDuration());
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get an array of filters needed to restore video orientation.
+ *
+ * @return array
+ */
+ protected function _getRotationFilters()
+ {
+ $result = [];
+ if ($this->_details->hasSwappedAxes()) {
+ if ($this->_details->isHorizontallyFlipped() && $this->_details->isVerticallyFlipped()) {
+ $result[] = 'transpose=clock';
+ $result[] = 'hflip';
+ } elseif ($this->_details->isHorizontallyFlipped()) {
+ $result[] = 'transpose=clock';
+ } elseif ($this->_details->isVerticallyFlipped()) {
+ $result[] = 'transpose=cclock';
+ } else {
+ $result[] = 'transpose=cclock';
+ $result[] = 'vflip';
+ }
+ } else {
+ if ($this->_details->isHorizontallyFlipped()) {
+ $result[] = 'hflip';
+ }
+ if ($this->_details->isVerticallyFlipped()) {
+ $result[] = 'vflip';
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Media/Video/VideoDetails.php b/vendor/mgp25/instagram-php/src/Media/Video/VideoDetails.php
new file mode 100755
index 0000000..a1edc96
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Media/Video/VideoDetails.php
@@ -0,0 +1,348 @@
+_duration;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDurationInMsec()
+ {
+ // NOTE: ceil() is to round up and get rid of any MS decimals.
+ return (int) ceil($this->getDuration() * 1000);
+ }
+
+ /**
+ * @return string
+ */
+ public function getVideoCodec()
+ {
+ return $this->_videoCodec;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getAudioCodec()
+ {
+ return $this->_audioCodec;
+ }
+
+ /**
+ * @return string
+ */
+ public function getContainer()
+ {
+ return $this->_container;
+ }
+
+ /** {@inheritdoc} */
+ public function hasSwappedAxes()
+ {
+ return $this->_rotation % 180;
+ }
+
+ /** {@inheritdoc} */
+ public function isHorizontallyFlipped()
+ {
+ return $this->_rotation === 90 || $this->_rotation === 180;
+ }
+
+ /** {@inheritdoc} */
+ public function isVerticallyFlipped()
+ {
+ return $this->_rotation === 180 || $this->_rotation === 270;
+ }
+
+ /**
+ * Check whether the video has display matrix with rotate.
+ *
+ * @return bool
+ */
+ public function hasRotationMatrix()
+ {
+ return $this->_hasRotationMatrix;
+ }
+
+ /** {@inheritdoc} */
+ public function getMinAllowedWidth()
+ {
+ return self::MIN_WIDTH;
+ }
+
+ /** {@inheritdoc} */
+ public function getMaxAllowedWidth()
+ {
+ return self::MAX_WIDTH;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string $filename Path to the video file.
+ *
+ * @throws \InvalidArgumentException If the video file is missing or invalid.
+ * @throws \RuntimeException If FFmpeg isn't working properly.
+ */
+ public function __construct(
+ $filename)
+ {
+ // Check if input file exists.
+ if (empty($filename) || !is_file($filename)) {
+ throw new \InvalidArgumentException(sprintf('The video file "%s" does not exist on disk.', $filename));
+ }
+
+ // Determine video file size and throw when the file is empty.
+ $filesize = filesize($filename);
+ if ($filesize < 1) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The video "%s" is empty.',
+ $filename
+ ));
+ }
+
+ // The user must have FFprobe.
+ $ffprobe = Utils::checkFFPROBE();
+ if ($ffprobe === false) {
+ throw new \RuntimeException('You must have FFprobe to analyze video details. Ensure that its binary-folder exists in your PATH environment variable, or manually set its full path via "\InstagramAPI\Utils::$ffprobeBin = \'/home/exampleuser/ffmpeg/bin/ffprobe\';" at the start of your script.');
+ }
+
+ // Load with FFPROBE. Shows details as JSON and exits.
+ $command = sprintf(
+ '%s -v quiet -print_format json -show_format -show_streams %s',
+ Args::escape($ffprobe),
+ Args::escape($filename)
+ );
+ $jsonInfo = @shell_exec($command);
+
+ // Check for processing errors.
+ if ($jsonInfo === null) {
+ throw new \RuntimeException(sprintf('FFprobe failed to analyze your video file "%s".', $filename));
+ }
+
+ // Attempt to decode the JSON.
+ $probeResult = @json_decode($jsonInfo, true);
+ if ($probeResult === null) {
+ throw new \RuntimeException(sprintf('FFprobe gave us invalid JSON for "%s".', $filename));
+ }
+
+ if (!isset($probeResult['streams']) || !is_array($probeResult['streams'])) {
+ throw new \RuntimeException(sprintf('FFprobe failed to detect any stream. Is "%s" a valid media file?', $filename));
+ }
+
+ // Now analyze all streams to find the first video stream.
+ $width = null;
+ $height = null;
+ $this->_hasRotationMatrix = false;
+ foreach ($probeResult['streams'] as $streamIdx => $streamInfo) {
+ if (!isset($streamInfo['codec_type'])) {
+ continue;
+ }
+ switch ($streamInfo['codec_type']) {
+ case 'video':
+ // TODO Mark media as invalid if more than one video stream found (?)
+ $foundVideoStream = true;
+ $this->_videoCodec = (string) $streamInfo['codec_name']; // string
+ $width = (int) $streamInfo['width'];
+ $height = (int) $streamInfo['height'];
+ if (isset($streamInfo['duration'])) {
+ // NOTE: Duration is a float such as "230.138000".
+ $this->_duration = (float) $streamInfo['duration'];
+ }
+
+ // Read rotation angle from tags.
+ $this->_rotation = 0;
+ if (isset($streamInfo['tags']) && is_array($streamInfo['tags'])) {
+ $tags = array_change_key_case($streamInfo['tags'], CASE_LOWER);
+ if (isset($tags['rotate'])) {
+ $this->_rotation = $this->_normalizeRotation((int) $tags['rotate']);
+ }
+ }
+ if (isset($streamInfo['side_data_list']) && is_array($streamInfo['side_data_list'])) {
+ foreach ($streamInfo['side_data_list'] as $sideData) {
+ if (!isset($sideData['side_data_type'])) {
+ continue;
+ }
+ if (!isset($sideData['rotation'])) {
+ continue;
+ }
+
+ $this->_hasRotationMatrix =
+ strcasecmp($sideData['side_data_type'], 'Display Matrix') === 0
+ && abs($sideData['rotation']) > 0.1;
+ }
+ }
+ break;
+ case 'audio':
+ // TODO Mark media as invalid if more than one audio stream found (?)
+ $this->_audioCodec = (string) $streamInfo['codec_name']; // string
+ break;
+ default:
+ // TODO Mark media as invalid if unknown stream found (?)
+ }
+ }
+
+ // Sometimes there is no duration in stream info, so we should check the format.
+ if ($this->_duration === null && isset($probeResult['format']['duration'])) {
+ $this->_duration = (float) $probeResult['format']['duration'];
+ }
+
+ if (isset($probeResult['format']['format_name'])) {
+ $this->_container = (string) $probeResult['format']['format_name'];
+ }
+
+ // Make sure we have detected the video duration.
+ if ($this->_duration === null) {
+ throw new \RuntimeException(sprintf('FFprobe failed to detect video duration. Is "%s" a valid video file?', $filename));
+ }
+
+ parent::__construct($filename, $filesize, $width, $height);
+ }
+
+ /** {@inheritdoc} */
+ public function validate(
+ ConstraintsInterface $constraints)
+ {
+ parent::validate($constraints);
+
+ // WARNING TO CONTRIBUTORS: $mediaFilename is for ERROR DISPLAY to
+ // users. Do NOT use it to read from the hard disk!
+ $mediaFilename = $this->getBasename();
+
+ // Make sure we have found at least one video stream.
+ if ($this->_videoCodec === null) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram requires video with at least one video stream. Your file "%s" doesn\'t have any.',
+ $mediaFilename
+ ));
+ }
+
+ // Check the video stream. We should have at least one.
+ if ($this->_videoCodec !== 'h264') {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos encoded with the H.264 video codec. Your file "%s" has "%s".',
+ $mediaFilename, $this->_videoCodec
+ ));
+ }
+
+ // Check the audio stream (if available).
+ if ($this->_audioCodec !== null && $this->_audioCodec !== 'aac') {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos encoded with the AAC audio codec. Your file "%s" has "%s".',
+ $mediaFilename, $this->_audioCodec
+ ));
+ }
+
+ // Check the container format.
+ if (strpos($this->_container, 'mp4') === false) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos packed into MP4 containers. Your file "%s" has "%s".',
+ $mediaFilename, $this->_container
+ ));
+ }
+
+ // Validate video resolution. Instagram allows between 480px-720px width.
+ // NOTE: They'll resize 720px wide videos on the server, to 640px instead.
+ // NOTE: Their server CAN receive between 320px-1080px width without
+ // rejecting the video, but the official app would NEVER upload such
+ // resolutions. It's controlled by the "ig_android_universe_video_production"
+ // experiment variable, which currently enforces width of min:480, max:720.
+ // If users want to upload bigger videos, they MUST resize locally first!
+ $width = $this->getWidth();
+ if ($width < self::MIN_WIDTH || $width > self::MAX_WIDTH) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts videos that are between %d and %d pixels wide. Your file "%s" is %d pixels wide.',
+ self::MIN_WIDTH, self::MAX_WIDTH, $mediaFilename, $width
+ ));
+ }
+
+ // Validate video length.
+ // NOTE: Instagram has no disk size limit, but this length validation
+ // also ensures we can only upload small files exactly as intended.
+ $duration = $this->getDuration();
+ $minDuration = $constraints->getMinDuration();
+ $maxDuration = $constraints->getMaxDuration();
+ if ($duration < $minDuration || $duration > $maxDuration) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram only accepts %s videos that are between %.3f and %.3f seconds long. Your video "%s" is %.3f seconds long.',
+ $constraints->getTitle(), $minDuration, $maxDuration, $mediaFilename, $duration
+ ));
+ }
+ }
+
+ /**
+ * @param $rotation
+ *
+ * @return int
+ */
+ private function _normalizeRotation(
+ $rotation)
+ {
+ // The angle must be in 0..359 degrees range.
+ $result = $rotation % 360;
+ // Negative angle can be normalized by adding it to 360:
+ // 360 + (-90) = 270.
+ if ($result < 0) {
+ $result = 360 + $result;
+ }
+ // The final angle must be one of 0, 90, 180 or 270 degrees.
+ // So we are rounding it to the closest one.
+ $result = round($result / 90) * 90;
+
+ return (int) $result;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Middleware/FakeCookies.php b/vendor/mgp25/instagram-php/src/Middleware/FakeCookies.php
new file mode 100755
index 0000000..dc92df4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Middleware/FakeCookies.php
@@ -0,0 +1,172 @@
+clear();
+ }
+
+ /**
+ * Removes all fake cookies so they won't be added to further requests.
+ */
+ public function clear()
+ {
+ $this->_cookies = [];
+ }
+
+ /**
+ * Get all currently used fake cookies.
+ *
+ * @return array
+ */
+ public function cookies()
+ {
+ return $this->_cookies;
+ }
+
+ /**
+ * Adds a fake cookie which will be injected into all requests.
+ *
+ * Remember to clear your fake cookies when you no longer need them.
+ *
+ * Usually you only need fake cookies for a few requests, and care must be
+ * taken to GUARANTEE clearout after that, via something like the following:
+ * "try{...}finally{ ...->clearFakeCookies(); }").
+ *
+ * Otherwise you would FOREVER pollute all other requests!
+ *
+ * If you only need the cookie once, the best way to guarantee clearout is
+ * to leave the "singleUse" parameter in its enabled state.
+ *
+ * @param string $name The name of the cookie. CASE SENSITIVE!
+ * @param string $value The value of the cookie.
+ * @param bool $singleUse If TRUE, the cookie will be deleted after 1 use.
+ */
+ public function add(
+ $name,
+ $value,
+ $singleUse = true)
+ {
+ // This overwrites any existing fake cookie with the same name, which is
+ // intentional since the names of cookies must be unique.
+ $this->_cookies[$name] = [
+ 'value' => $value,
+ 'singleUse' => $singleUse,
+ ];
+ }
+
+ /**
+ * Delete a single fake cookie.
+ *
+ * Useful for selectively removing some fake cookies but keeping the rest.
+ *
+ * @param string $name The name of the cookie. CASE SENSITIVE!
+ */
+ public function delete(
+ $name)
+ {
+ unset($this->_cookies[$name]);
+ }
+
+ /**
+ * Middleware setup function.
+ *
+ * Called by Guzzle when it needs to add an instance of our middleware to
+ * its stack. We simply return a callable which will process all requests.
+ *
+ * @param callable $handler
+ *
+ * @return callable
+ */
+ public function __invoke(
+ callable $handler)
+ {
+ return function (
+ RequestInterface $request,
+ array $options
+ ) use ($handler) {
+ $fakeCookies = $this->cookies();
+
+ // Pass request through unmodified if no work to do (to save CPU).
+ if (count($fakeCookies) === 0) {
+ return $handler($request, $options);
+ }
+
+ $finalCookies = [];
+
+ // Extract all existing cookies in this request's "Cookie:" header.
+ if ($request->hasHeader('Cookie')) {
+ $cookieHeaders = $request->getHeader('Cookie');
+ foreach ($cookieHeaders as $headerLine) {
+ $theseCookies = explode('; ', $headerLine);
+ foreach ($theseCookies as $cookieEntry) {
+ $cookieParts = explode('=', $cookieEntry, 2);
+ if (count($cookieParts) == 2) {
+ // We have the name and value of the cookie!
+ $finalCookies[$cookieParts[0]] = $cookieParts[1];
+ } else {
+ // Unable to find an equals sign, just re-use this
+ // cookie as-is (TRUE="re-use literally").
+ $finalCookies[$cookieEntry] = true;
+ }
+ }
+ }
+ }
+
+ // Inject all of our fake cookies, overwriting any name clashes.
+ // NOTE: The name matching is CASE SENSITIVE!
+ foreach ($fakeCookies as $name => $cookieInfo) {
+ $finalCookies[$name] = $cookieInfo['value'];
+
+ // Delete the cookie now if it was a single-use cookie.
+ if ($cookieInfo['singleUse']) {
+ $this->delete($name);
+ }
+ }
+
+ // Generate all individual cookie strings for the final cookies.
+ $values = [];
+ foreach ($finalCookies as $name => $value) {
+ if ($value === true) {
+ // Cookies to re-use as-is, due to parsing error above.
+ $values[] = $name;
+ } else {
+ $values[] = $name.'='.$value;
+ }
+ }
+
+ // Generate our new, semicolon-separated "Cookie:" header line.
+ // NOTE: This completely replaces the old header. As intended.
+ $finalCookieHeader = implode('; ', $values);
+ $request = $request->withHeader('Cookie', $finalCookieHeader);
+
+ return $handler($request, $options);
+ };
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Middleware/ZeroRating.php b/vendor/mgp25/instagram-php/src/Middleware/ZeroRating.php
new file mode 100755
index 0000000..bcf6620
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Middleware/ZeroRating.php
@@ -0,0 +1,120 @@
+ '$1b.$2$3',
+ ];
+
+ /**
+ * Rewrite rules.
+ *
+ * @var array
+ */
+ private $_rules;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->reset();
+ }
+
+ /**
+ * Reset rules to default ones.
+ */
+ public function reset()
+ {
+ $this->update(self::DEFAULT_REWRITE);
+ }
+
+ /**
+ * Update rules.
+ *
+ * @param array $rules
+ */
+ public function update(
+ array $rules = [])
+ {
+ $this->_rules = [];
+ foreach ($rules as $from => $to) {
+ $regex = "#{$from}#";
+ $test = @preg_match($regex, '');
+ if ($test === false) {
+ continue;
+ }
+ $this->_rules[$regex] = strtr($to, [
+ '\.' => '.',
+ ]);
+ }
+ }
+
+ /**
+ * Middleware setup function.
+ *
+ * Called by Guzzle when it needs to add an instance of our middleware to
+ * its stack. We simply return a callable which will process all requests.
+ *
+ * @param callable $handler
+ *
+ * @return callable
+ */
+ public function __invoke(
+ callable $handler)
+ {
+ return function (
+ RequestInterface $request,
+ array $options
+ ) use ($handler) {
+ if (empty($this->_rules)) {
+ return $handler($request, $options);
+ }
+
+ $oldUri = (string) $request->getUri();
+ $uri = $this->rewrite($oldUri);
+ if ($uri !== $oldUri) {
+ $request = $request->withUri(new Uri($uri));
+ }
+
+ return $handler($request, $options);
+ };
+ }
+
+ /**
+ * Do a rewrite.
+ *
+ * @param string $uri
+ *
+ * @return string
+ */
+ public function rewrite(
+ $uri)
+ {
+ foreach ($this->_rules as $from => $to) {
+ $result = @preg_replace($from, $to, $uri);
+ if (!is_string($result)) {
+ continue;
+ }
+ // We must break at the first succeeded replace.
+ if ($result !== $uri) {
+ return $result;
+ }
+ }
+
+ return $uri;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Push.php b/vendor/mgp25/instagram-php/src/Push.php
new file mode 100755
index 0000000..bee6cfd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Push.php
@@ -0,0 +1,286 @@
+_instagram = $instagram;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+ if ($this->_logger === null) {
+ $this->_logger = new NullLogger();
+ }
+
+ $this->_fbnsAuth = new Fbns\Auth($this->_instagram);
+ $this->_fbns = $this->_getFbns();
+ }
+
+ /**
+ * Incoming notification callback.
+ *
+ * @param Notification $notification
+ */
+ protected function _onPush(
+ Notification $notification)
+ {
+ $collapseKey = $notification->getCollapseKey();
+ $this->_logger->info(sprintf('Received a push with collapse key "%s"', $collapseKey), [(string) $notification]);
+ $this->emit('incoming', [$notification]);
+ if (!empty($collapseKey)) {
+ $this->emit($collapseKey, [$notification]);
+ }
+ }
+
+ /**
+ * Create a new FBNS receiver.
+ *
+ * @return Fbns
+ */
+ protected function _getFbns()
+ {
+ $fbns = new Fbns(
+ $this,
+ new Connector($this->_instagram, $this->_loop),
+ $this->_fbnsAuth,
+ $this->_instagram->device,
+ $this->_loop,
+ $this->_logger
+ );
+ $fbns->on('fbns_auth', function ($authJson) {
+ try {
+ $this->_fbnsAuth->update($authJson);
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to update FBNS auth: %s', $e->getMessage()), [$authJson]);
+ }
+ });
+ $fbns->on('fbns_token', function ($token) {
+ // Refresh the "last token activity" timestamp.
+ // The age of this timestamp helps us detect when the user
+ // has stopped using the Push features due to inactivity.
+ try {
+ $this->_instagram->settings->set('last_fbns_token', time());
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to write FBNS token timestamp: %s', $e->getMessage()));
+ }
+ // Read our old token. If an identical value exists, then we know
+ // that we've already registered that token during this session.
+ try {
+ $oldToken = $this->_instagram->settings->get('fbns_token');
+ // Do nothing when the new token is equal to the old one.
+ if ($token === $oldToken) {
+ return;
+ }
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to read FBNS token: %s', $e->getMessage()));
+ }
+ // Register the new token.
+ try {
+ $this->_instagram->push->register('mqtt', $token);
+ } catch (\Exception $e) {
+ $this->emit('error', [$e]);
+ }
+ // Save the newly received token to the storage.
+ // NOTE: We save it even if the registration failed, since we now
+ // got it from the server and assume they've given us a good one.
+ // However, it'll always be re-validated during the general login()
+ // flow, and will be cleared there if it fails to register there.
+ try {
+ $this->_instagram->settings->set('fbns_token', $token);
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to update FBNS token: %s', $e->getMessage()), [$token]);
+ }
+ });
+ $fbns->on('push', function (Notification $notification) {
+ $this->_onPush($notification);
+ });
+
+ return $fbns;
+ }
+
+ /**
+ * Start Push receiver.
+ */
+ public function start()
+ {
+ $this->_fbns->start();
+ }
+
+ /**
+ * Stop Push receiver.
+ */
+ public function stop()
+ {
+ $this->_fbns->stop();
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Push/Fbns.php b/vendor/mgp25/instagram-php/src/Push/Fbns.php
new file mode 100755
index 0000000..2ddf1f2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Push/Fbns.php
@@ -0,0 +1,194 @@
+_target = $target;
+ $this->_connector = $connector;
+ $this->_auth = $auth;
+ $this->_device = $device;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+
+ $this->_client = $this->_getClient();
+ }
+
+ /**
+ * Create a new FBNS client instance.
+ *
+ * @return Lite
+ */
+ protected function _getClient()
+ {
+ $client = new Lite($this->_loop, $this->_connector, $this->_logger);
+
+ // Bind events.
+ $client
+ ->on('connect', function (Lite\ConnectResponsePacket $responsePacket) {
+ // Update auth credentials.
+ $authJson = $responsePacket->getAuth();
+ if (strlen($authJson)) {
+ $this->_logger->info('Received a non-empty auth.', [$authJson]);
+ $this->emit('fbns_auth', [$authJson]);
+ }
+
+ // Register an application.
+ $this->_client->register(Constants::PACKAGE_NAME, Constants::FACEBOOK_ANALYTICS_APPLICATION_ID);
+ })
+ ->on('disconnect', function () {
+ // Try to reconnect.
+ if (!$this->_reconnectInterval) {
+ $this->_connect();
+ }
+ })
+ ->on('register', function (Register $message) {
+ if (!empty($message->getError())) {
+ $this->_target->emit('error', [new \RuntimeException($message->getError())]);
+
+ return;
+ }
+ $this->_logger->info('Received a non-empty token.', [$message->getToken()]);
+ $this->emit('fbns_token', [$message->getToken()]);
+ })
+ ->on('push', function (PushMessage $message) {
+ $payload = $message->getPayload();
+
+ try {
+ $notification = new Notification($payload);
+ } catch (\Exception $e) {
+ $this->_logger->error(sprintf('Failed to decode push: %s', $e->getMessage()), [$payload]);
+
+ return;
+ }
+ $this->emit('push', [$notification]);
+ });
+
+ return $client;
+ }
+
+ /**
+ * Try to establish a connection.
+ */
+ protected function _connect()
+ {
+ $this->_setReconnectTimer(function () {
+ $connection = new Connection(
+ $this->_auth,
+ $this->_device->getFbUserAgent(Constants::FBNS_APPLICATION_NAME)
+ );
+
+ return $this->_client->connect(self::DEFAULT_HOST, self::DEFAULT_PORT, $connection, self::CONNECTION_TIMEOUT);
+ });
+ }
+
+ /**
+ * Start Push receiver.
+ */
+ public function start()
+ {
+ $this->_logger->info('Starting FBNS client...');
+ $this->_isActive = true;
+ $this->_reconnectInterval = 0;
+ $this->_connect();
+ }
+
+ /**
+ * Stop Push receiver.
+ */
+ public function stop()
+ {
+ $this->_logger->info('Stopping FBNS client...');
+ $this->_isActive = false;
+ $this->_cancelReconnectTimer();
+ $this->_client->disconnect();
+ }
+
+ /** {@inheritdoc} */
+ public function isActive()
+ {
+ return $this->_isActive;
+ }
+
+ /** {@inheritdoc} */
+ public function getLogger()
+ {
+ return $this->_logger;
+ }
+
+ /** {@inheritdoc} */
+ public function getLoop()
+ {
+ return $this->_loop;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Push/Fbns/Auth.php b/vendor/mgp25/instagram-php/src/Push/Fbns/Auth.php
new file mode 100755
index 0000000..e68b2c3
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Push/Fbns/Auth.php
@@ -0,0 +1,89 @@
+_instagram = $instagram;
+ $this->_deviceAuth = $this->_instagram->settings->getFbnsAuth();
+ }
+
+ /** {@inheritdoc} */
+ public function getClientId()
+ {
+ return $this->_deviceAuth->getClientId();
+ }
+
+ /** {@inheritdoc} */
+ public function getClientType()
+ {
+ return $this->_deviceAuth->getClientType();
+ }
+
+ /** {@inheritdoc} */
+ public function getUserId()
+ {
+ return $this->_deviceAuth->getUserId();
+ }
+
+ /** {@inheritdoc} */
+ public function getPassword()
+ {
+ return $this->_deviceAuth->getPassword();
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceId()
+ {
+ return $this->_deviceAuth->getDeviceId();
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceSecret()
+ {
+ return $this->_deviceAuth->getDeviceSecret();
+ }
+
+ /** {@inheritdoc} */
+ public function __toString()
+ {
+ return $this->_deviceAuth->__toString();
+ }
+
+ /**
+ * Update auth data.
+ *
+ * @param string $auth
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function update(
+ $auth)
+ {
+ /* @var DeviceAuth $auth */
+ $this->_deviceAuth->read($auth);
+ $this->_instagram->settings->setFbnsAuth($this->_deviceAuth);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Push/Notification.php b/vendor/mgp25/instagram-php/src/Push/Notification.php
new file mode 100755
index 0000000..3709b09
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Push/Notification.php
@@ -0,0 +1,316 @@
+_json = $json;
+
+ if (isset($data->t)) {
+ $this->_title = (string) $data->t;
+ }
+ if (isset($data->m)) {
+ $this->_message = (string) $data->m;
+ }
+ if (isset($data->tt)) {
+ $this->_tickerText = (string) $data->tt;
+ }
+ $this->_actionPath = '';
+ $this->_actionParams = [];
+ if (isset($data->ig)) {
+ $this->_igAction = (string) $data->ig;
+ $parts = parse_url($this->_igAction);
+ if (isset($parts['path'])) {
+ $this->_actionPath = $parts['path'];
+ }
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $this->_actionParams);
+ }
+ }
+ if (isset($data->collapse_key)) {
+ $this->_collapseKey = (string) $data->collapse_key;
+ }
+ if (isset($data->i)) {
+ $this->_optionalImage = (string) $data->i;
+ }
+ if (isset($data->a)) {
+ $this->_optionalAvatarUrl = (string) $data->a;
+ }
+ if (isset($data->sound)) {
+ $this->_sound = (string) $data->sound;
+ }
+ if (isset($data->pi)) {
+ $this->_pushId = (string) $data->pi;
+ }
+ if (isset($data->c)) {
+ $this->_pushCategory = (string) $data->c;
+ }
+ if (isset($data->u)) {
+ $this->_intendedRecipientUserId = (string) $data->u;
+ }
+ if (isset($data->s)) {
+ $this->_sourceUserId = (string) $data->s;
+ }
+ if (isset($data->igo)) {
+ $this->_igActionOverride = (string) $data->igo;
+ }
+ if (isset($data->bc)) {
+ $this->_badgeCount = new BadgeCount((string) $data->bc);
+ }
+ if (isset($data->ia)) {
+ $this->_inAppActors = (string) $data->ia;
+ }
+ }
+
+ /**
+ * Notification constructor.
+ *
+ * @param string $json
+ */
+ public function __construct(
+ $json)
+ {
+ $this->_parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->_json;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->_title;
+ }
+
+ /**
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->_message;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTickerText()
+ {
+ return $this->_tickerText;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIgAction()
+ {
+ return $this->_igAction;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIgActionOverride()
+ {
+ return $this->_igActionOverride;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOptionalImage()
+ {
+ return $this->_optionalImage;
+ }
+
+ /**
+ * @return string
+ */
+ public function getOptionalAvatarUrl()
+ {
+ return $this->_optionalAvatarUrl;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCollapseKey()
+ {
+ return $this->_collapseKey;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSound()
+ {
+ return $this->_sound;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPushId()
+ {
+ return $this->_pushId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPushCategory()
+ {
+ return $this->_pushCategory;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIntendedRecipientUserId()
+ {
+ return $this->_intendedRecipientUserId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSourceUserId()
+ {
+ return $this->_sourceUserId;
+ }
+
+ /**
+ * @return BadgeCount
+ */
+ public function getBadgeCount()
+ {
+ return $this->_badgeCount;
+ }
+
+ /**
+ * @return string
+ */
+ public function getInAppActors()
+ {
+ return $this->_inAppActors;
+ }
+
+ /**
+ * @return string
+ */
+ public function getActionPath()
+ {
+ return $this->_actionPath;
+ }
+
+ /**
+ * @return array
+ */
+ public function getActionParams()
+ {
+ return $this->_actionParams;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return mixed
+ */
+ public function getActionParam(
+ $key)
+ {
+ return isset($this->_actionParams[$key]) ? $this->_actionParams[$key] : null;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Push/Payload/BadgeCount.php b/vendor/mgp25/instagram-php/src/Push/Payload/BadgeCount.php
new file mode 100755
index 0000000..005debd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Push/Payload/BadgeCount.php
@@ -0,0 +1,89 @@
+_json = $json;
+
+ if (isset($data->di)) {
+ $this->_direct = (int) $data->di;
+ }
+ if (isset($data->ds)) {
+ $this->_ds = (int) $data->ds;
+ }
+ if (isset($data->ac)) {
+ $this->_activities = (int) $data->ac;
+ }
+ }
+
+ /**
+ * Notification constructor.
+ *
+ * @param string $json
+ */
+ public function __construct(
+ $json)
+ {
+ $this->_parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->_json;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDirect()
+ {
+ return $this->_direct;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDs()
+ {
+ return $this->_ds;
+ }
+
+ /**
+ * @return int
+ */
+ public function getActivities()
+ {
+ return $this->_activities;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/React/Connector.php b/vendor/mgp25/instagram-php/src/React/Connector.php
new file mode 100755
index 0000000..4c5ba1a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/React/Connector.php
@@ -0,0 +1,223 @@
+_instagram = $instagram;
+ $this->_loop = $loop;
+
+ $this->_connectors = [];
+ }
+
+ /** {@inheritdoc} */
+ public function connect(
+ $uri)
+ {
+ $uriObj = new Uri($uri);
+ $host = $this->_instagram->client->zeroRating()->rewrite($uriObj->getHost());
+ $uriObj = $uriObj->withHost($host);
+ if (!isset($this->_connectors[$host])) {
+ try {
+ $this->_connectors[$host] = $this->_getSecureConnector(
+ $this->_getSecureContext($this->_instagram->getVerifySSL()),
+ $this->_getProxyForHost($host, $this->_instagram->getProxy())
+ );
+ } catch (\Exception $e) {
+ return new RejectedPromise($e);
+ }
+ }
+ $niceUri = ltrim((string) $uriObj, '/');
+ /** @var PromiseInterface $promise */
+ $promise = $this->_connectors[$host]->connect($niceUri);
+
+ return $promise;
+ }
+
+ /**
+ * Create a secure connector for given configuration.
+ *
+ * @param array $secureContext
+ * @param string|null $proxyAddress
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return ConnectorInterface
+ */
+ protected function _getSecureConnector(
+ array $secureContext = [],
+ $proxyAddress = null)
+ {
+ $connector = new SocketConnector($this->_loop, [
+ 'tcp' => true,
+ 'tls' => false,
+ 'unix' => false,
+ 'dns' => true,
+ 'timeout' => true,
+ ]);
+
+ if ($proxyAddress !== null) {
+ $connector = $this->_wrapConnectorIntoProxy($connector, $proxyAddress, $secureContext);
+ }
+
+ return new SecureConnector($connector, $this->_loop, $secureContext);
+ }
+
+ /**
+ * Get a proxy address (if any) for the host based on the proxy config.
+ *
+ * @param string $host Host.
+ * @param mixed $proxyConfig Proxy config.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string|null
+ *
+ * @see http://docs.guzzlephp.org/en/stable/request-options.html#proxy
+ */
+ protected function _getProxyForHost(
+ $host,
+ $proxyConfig = null)
+ {
+ // Empty config => no proxy.
+ if (empty($proxyConfig)) {
+ return;
+ }
+
+ // Plain string => return it.
+ if (!is_array($proxyConfig)) {
+ return $proxyConfig;
+ }
+
+ // HTTP proxies do not have CONNECT method.
+ if (!isset($proxyConfig['https'])) {
+ throw new \InvalidArgumentException('No proxy with CONNECT method found.');
+ }
+
+ // Check exceptions.
+ if (isset($proxyConfig['no']) && \GuzzleHttp\is_host_in_noproxy($host, $proxyConfig['no'])) {
+ return;
+ }
+
+ return $proxyConfig['https'];
+ }
+
+ /**
+ * Parse given SSL certificate verification and return a secure context.
+ *
+ * @param mixed $config
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return array
+ *
+ * @see http://docs.guzzlephp.org/en/stable/request-options.html#verify
+ */
+ protected function _getSecureContext(
+ $config)
+ {
+ $context = [];
+ if ($config === true) {
+ // PHP 5.6 or greater will find the system cert by default. When
+ // < 5.6, use the Guzzle bundled cacert.
+ if (PHP_VERSION_ID < 50600) {
+ $context['cafile'] = \GuzzleHttp\default_ca_bundle();
+ }
+ } elseif (is_string($config)) {
+ $context['cafile'] = $config;
+ if (!is_file($config)) {
+ throw new \RuntimeException(sprintf('SSL CA bundle not found: "%s".', $config));
+ }
+ } elseif ($config === false) {
+ $context['verify_peer'] = false;
+ $context['verify_peer_name'] = false;
+
+ return $context;
+ } else {
+ throw new \InvalidArgumentException('Invalid verify request option.');
+ }
+ $context['verify_peer'] = true;
+ $context['verify_peer_name'] = true;
+ $context['allow_self_signed'] = false;
+
+ return $context;
+ }
+
+ /**
+ * Wrap the connector into a proxy one for given configuration.
+ *
+ * @param ConnectorInterface $connector
+ * @param string $proxyAddress
+ * @param array $secureContext
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return ConnectorInterface
+ */
+ protected function _wrapConnectorIntoProxy(
+ ConnectorInterface $connector,
+ $proxyAddress,
+ array $secureContext = [])
+ {
+ if (strpos($proxyAddress, '://') === false) {
+ $scheme = 'http';
+ } else {
+ $scheme = parse_url($proxyAddress, PHP_URL_SCHEME);
+ }
+ switch ($scheme) {
+ case 'socks':
+ case 'socks4':
+ case 'socks4a':
+ case 'socks5':
+ $connector = new SocksProxy($proxyAddress, $connector);
+ break;
+ case 'http':
+ $connector = new HttpConnectProxy($proxyAddress, $connector);
+ break;
+ case 'https':
+ $connector = new HttpConnectProxy($proxyAddress, new SecureConnector($connector, $this->_loop, $secureContext));
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unsupported proxy scheme: "%s".', $scheme));
+ }
+
+ return $connector;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/React/PersistentInterface.php b/vendor/mgp25/instagram-php/src/React/PersistentInterface.php
new file mode 100755
index 0000000..8015fcd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/React/PersistentInterface.php
@@ -0,0 +1,49 @@
+_reconnectTimer !== null) {
+ if ($this->_reconnectTimer->isActive()) {
+ $this->getLogger()->info('Existing reconnect timer has been canceled.');
+ $this->_reconnectTimer->cancel();
+ }
+ $this->_reconnectTimer = null;
+ }
+ }
+
+ /**
+ * Set up a new reconnect timer with exponential backoff.
+ *
+ * @param callable $callback
+ */
+ protected function _setReconnectTimer(
+ callable $callback)
+ {
+ $this->_cancelReconnectTimer();
+ if (!$this->isActive()) {
+ return;
+ }
+ $this->_reconnectInterval = min(
+ $this->getMaxReconnectInterval(),
+ max(
+ $this->getMinReconnectInterval(),
+ $this->_reconnectInterval * 2
+ )
+ );
+ $this->getLogger()->info(sprintf('Setting up reconnect timer to %d seconds.', $this->_reconnectInterval));
+ $this->_reconnectTimer = $this->getLoop()->addTimer($this->_reconnectInterval, function () use ($callback) {
+ /** @var PromiseInterface $promise */
+ $promise = $callback();
+ $promise->then(
+ function () {
+ // Reset reconnect interval on successful connection attempt.
+ $this->_reconnectInterval = 0;
+ },
+ function () use ($callback) {
+ $this->_setReconnectTimer($callback);
+ }
+ );
+ });
+ }
+
+ /** {@inheritdoc} */
+ public function getMinReconnectInterval()
+ {
+ return PersistentInterface::MIN_RECONNECT_INTERVAL;
+ }
+
+ /** {@inheritdoc} */
+ public function getMaxReconnectInterval()
+ {
+ return PersistentInterface::MAX_RECONNECT_INTERVAL;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime.php b/vendor/mgp25/instagram-php/src/Realtime.php
new file mode 100755
index 0000000..d967d14
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime.php
@@ -0,0 +1,495 @@
+_instagram = $instagram;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+ if ($this->_logger === null) {
+ $this->_logger = new NullLogger();
+ }
+
+ $this->_client = $this->_buildMqttClient();
+ $this->on('region-hint', function ($region) {
+ $this->_instagram->settings->set('datacenter', $region);
+ $this->_client->setAdditionalOption('datacenter', $region);
+ });
+ $this->on('zero-provision', function (ZeroProvisionEvent $event) {
+ if ($event->getZeroProvisionedTime() === null) {
+ return;
+ }
+ if ($event->getProductName() !== 'select') {
+ return;
+ }
+ // TODO check whether we already have a fresh token.
+
+ $this->_instagram->client->zeroRating()->reset();
+ $this->_instagram->internal->fetchZeroRatingToken('mqtt_token_push');
+ });
+ }
+
+ /**
+ * Build a new MQTT client.
+ *
+ * @return Realtime\Mqtt
+ */
+ protected function _buildMqttClient()
+ {
+ $additionalOptions = [
+ 'datacenter' => $this->_instagram->settings->get('datacenter'),
+ 'disable_presence' => (bool) $this->_instagram->settings->get('presence_disabled'),
+ ];
+
+ return new Realtime\Mqtt(
+ $this,
+ new Connector($this->_instagram, $this->_loop),
+ new Auth($this->_instagram),
+ $this->_instagram->device,
+ $this->_instagram,
+ $this->_loop,
+ $this->_logger,
+ $additionalOptions
+ );
+ }
+
+ /**
+ * Starts underlying client.
+ */
+ public function start()
+ {
+ $this->_client->start();
+ }
+
+ /**
+ * Stops underlying client.
+ */
+ public function stop()
+ {
+ $this->_client->stop();
+ }
+
+ /**
+ * Marks thread item as seen.
+ *
+ * @param string $threadId
+ * @param string $threadItemId
+ *
+ * @return bool
+ */
+ public function markDirectItemSeen(
+ $threadId,
+ $threadItemId)
+ {
+ try {
+ $this->_client->sendCommand(new DirectCommand\MarkSeen($threadId, $threadItemId));
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Indicate activity in thread.
+ *
+ * @param string $threadId
+ * @param bool $activityFlag
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function indicateActivityInDirectThread(
+ $threadId,
+ $activityFlag)
+ {
+ try {
+ $command = new DirectCommand\IndicateActivity($threadId, $activityFlag);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Sends text message to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $message Text message.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendTextToDirect(
+ $threadId,
+ $message,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendText($threadId, $message, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Sends like to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendLikeToDirect(
+ $threadId,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendLike($threadId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share an existing media post to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendPostToDirect(
+ $threadId,
+ $mediaId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendPost($threadId, $mediaId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share an existing story to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $storyId The story ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendStoryToDirect(
+ $threadId,
+ $storyId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendStory($threadId, $storyId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share a profile to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $userId Numerical UserPK ID.
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendProfileToDirect(
+ $threadId,
+ $userId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendProfile($threadId, $userId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share a location to a given direct thread.
+ *
+ * You must provide a valid Instagram location ID, which you get via other
+ * functions such as Location::search().
+ *
+ * @param string $threadId Thread ID.
+ * @param string $locationId Instagram's internal ID for the location.
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendLocationToDirect(
+ $threadId,
+ $locationId,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendLocation($threadId, $locationId, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Share a hashtag to a given direct thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $hashtag Hashtag to share.
+ * @param array $options An associative array of additional parameters, including:
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendHashtagToDirect(
+ $threadId,
+ $hashtag,
+ array $options = [])
+ {
+ if (!$this->_isRtcReshareEnabled()) {
+ return false;
+ }
+
+ try {
+ $command = new DirectCommand\SendHashtag($threadId, $hashtag, $options);
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Sends reaction to a given direct thread item.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread ID.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function sendReactionToDirect(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ DirectCommand\SendReaction::STATUS_CREATED,
+ $options
+ );
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Removes reaction to a given direct thread item.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread ID.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ *
+ * @return bool|string Client context or false if sending is unavailable.
+ */
+ public function deleteReactionFromDirect(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ try {
+ $command = new DirectCommand\SendReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ DirectCommand\SendReaction::STATUS_DELETED,
+ $options
+ );
+ $this->_client->sendCommand($command);
+
+ return $command->getClientContext();
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Receive offline messages starting from the sequence ID.
+ *
+ * @param int $sequenceId
+ */
+ public function receiveOfflineMessages(
+ $sequenceId)
+ {
+ try {
+ $this->_client->sendCommand(new IrisSubscribe($sequenceId));
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+ }
+ }
+
+ /**
+ * Check whether sharing via the realtime client is enabled.
+ *
+ * @return bool
+ */
+ protected function _isRtcReshareEnabled()
+ {
+ return $this->_instagram->isExperimentEnabled('ig_android_rtc_reshare', 'is_rtc_reshare_enabled');
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/IndicateActivity.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/IndicateActivity.php
new file mode 100755
index 0000000..c7cc006
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/IndicateActivity.php
@@ -0,0 +1,35 @@
+_data['activity_status'] = $status ? '1' : '0';
+ }
+
+ /** {@inheritdoc} */
+ protected function _isClientContextRequired()
+ {
+ return true;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/MarkSeen.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/MarkSeen.php
new file mode 100755
index 0000000..8ca670f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/MarkSeen.php
@@ -0,0 +1,35 @@
+_data['item_id'] = $this->_validateThreadItemId($threadItemId);
+ }
+
+ /** {@inheritdoc} */
+ protected function _isClientContextRequired()
+ {
+ return false;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendHashtag.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendHashtag.php
new file mode 100755
index 0000000..eae2fcb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendHashtag.php
@@ -0,0 +1,42 @@
+_data['hashtag'] = $hashtag;
+ // Yeah, we need to send the hashtag twice.
+ $this->_data['item_id'] = $hashtag;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendItem.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendItem.php
new file mode 100755
index 0000000..ba25886
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendItem.php
@@ -0,0 +1,58 @@
+_getSupportedItemTypes(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported item type.', $itemType));
+ }
+ $this->_data['item_type'] = $itemType;
+ }
+
+ /** {@inheritdoc} */
+ protected function _isClientContextRequired()
+ {
+ return true;
+ }
+
+ /**
+ * Get the list of supported item types.
+ *
+ * @return array
+ */
+ protected function _getSupportedItemTypes()
+ {
+ return [
+ SendText::TYPE,
+ SendLike::TYPE,
+ SendReaction::TYPE,
+ SendPost::TYPE,
+ SendStory::TYPE,
+ SendProfile::TYPE,
+ SendLocation::TYPE,
+ SendHashtag::TYPE,
+ ];
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendLike.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendLike.php
new file mode 100755
index 0000000..a446d2e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendLike.php
@@ -0,0 +1,23 @@
+_data['venue_id'] = (string) $locationId;
+ // Yeah, we need to send the location ID twice.
+ $this->_data['item_id'] = (string) $locationId;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendPost.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendPost.php
new file mode 100755
index 0000000..a2295eb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendPost.php
@@ -0,0 +1,32 @@
+_data['media_id'] = (string) $mediaId;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendProfile.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendProfile.php
new file mode 100755
index 0000000..ef9dcae
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendProfile.php
@@ -0,0 +1,32 @@
+_data['profile_user_id'] = (string) $userId;
+ // Yeah, we need to send the user ID twice.
+ $this->_data['item_id'] = (string) $userId;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendReaction.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendReaction.php
new file mode 100755
index 0000000..af11354
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendReaction.php
@@ -0,0 +1,75 @@
+_data['item_id'] = $this->_validateThreadItemId($threadItemId);
+ $this->_data['node_type'] = 'item';
+
+ // Handle reaction type.
+ if (!in_array($reaction, $this->_getSupportedReactions(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction.', $reaction));
+ }
+ $this->_data['reaction_type'] = $reaction;
+
+ // Handle reaction status.
+ if (!in_array($status, $this->_getSupportedStatuses(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction status.', $status));
+ }
+ $this->_data['reaction_status'] = $status;
+ }
+
+ /**
+ * Get the list of supported reactions.
+ *
+ * @return array
+ */
+ protected function _getSupportedReactions()
+ {
+ return [
+ self::REACTION_LIKE,
+ ];
+ }
+
+ /**
+ * Get the list of supported statuses.
+ *
+ * @return array
+ */
+ protected function _getSupportedStatuses()
+ {
+ return [
+ self::STATUS_CREATED,
+ self::STATUS_DELETED,
+ ];
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendStory.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendStory.php
new file mode 100755
index 0000000..377b3e6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendStory.php
@@ -0,0 +1,32 @@
+_data['item_id'] = (string) $storyId;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendText.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendText.php
new file mode 100755
index 0000000..2386abd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/SendText.php
@@ -0,0 +1,34 @@
+_data['text'] = $text;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/ShareItem.php b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/ShareItem.php
new file mode 100755
index 0000000..e5a7a8d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/Direct/ShareItem.php
@@ -0,0 +1,30 @@
+_data['text'] = $options['text'];
+ } else {
+ $this->_data['text'] = '';
+ }
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/DirectCommand.php b/vendor/mgp25/instagram-php/src/Realtime/Command/DirectCommand.php
new file mode 100755
index 0000000..16a05d5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/DirectCommand.php
@@ -0,0 +1,191 @@
+_data = [];
+
+ // Handle action.
+ if (!in_array($action, $this->_getSupportedActions(), true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported action.', $action));
+ }
+ $this->_data['action'] = $action;
+
+ $this->_data['thread_id'] = $this->_validateThreadId($threadId);
+
+ // Handle client context.
+ if ($this->_isClientContextRequired()) {
+ if (!isset($options['client_context'])) {
+ $this->_data['client_context'] = Signatures::generateUUID();
+ } elseif (!Signatures::isValidUUID($options['client_context'])) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
+ } else {
+ $this->_data['client_context'] = $options['client_context'];
+ }
+ }
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::SEND_MESSAGE;
+ }
+
+ /** {@inheritdoc} */
+ public function getQosLevel()
+ {
+ return Mqtt\QosLevel::FIRE_AND_FORGET;
+ }
+
+ /** {@inheritdoc} */
+ public function jsonSerialize()
+ {
+ return $this->_reorderFieldsByWeight($this->_data, $this->_getFieldsWeights());
+ }
+
+ /**
+ * Get the client context.
+ *
+ * @return string|null
+ */
+ public function getClientContext()
+ {
+ return isset($this->_data['client_context']) ? $this->_data['client_context'] : null;
+ }
+
+ /**
+ * Check whether client_context param is required.
+ *
+ * @return bool
+ */
+ abstract protected function _isClientContextRequired();
+
+ /**
+ * Get the list of supported actions.
+ *
+ * @return array
+ */
+ protected function _getSupportedActions()
+ {
+ return [
+ Direct\SendItem::ACTION,
+ Direct\MarkSeen::ACTION,
+ Direct\IndicateActivity::ACTION,
+ ];
+ }
+
+ /**
+ * Validate given thread identifier.
+ *
+ * @param string $threadId
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ protected function _validateThreadId(
+ $threadId)
+ {
+ if (!ctype_digit($threadId) && (!is_int($threadId) || $threadId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $threadId));
+ }
+
+ return (string) $threadId;
+ }
+
+ /**
+ * Validate given thread item identifier.
+ *
+ * @param string $threadItemId
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ protected function _validateThreadItemId(
+ $threadItemId)
+ {
+ if (!ctype_digit($threadItemId) && (!is_int($threadItemId) || $threadItemId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread item identifier.', $threadItemId));
+ }
+
+ return (string) $threadItemId;
+ }
+
+ /**
+ * Reorders an array of fields by weights to simplify debugging.
+ *
+ * @param array $fields
+ * @param array $weights
+ *
+ * @return array
+ */
+ protected function _reorderFieldsByWeight(
+ array $fields,
+ array $weights)
+ {
+ uksort($fields, function ($a, $b) use ($weights) {
+ $a = isset($weights[$a]) ? $weights[$a] : PHP_INT_MAX;
+ $b = isset($weights[$b]) ? $weights[$b] : PHP_INT_MAX;
+ if ($a < $b) {
+ return -1;
+ }
+ if ($a > $b) {
+ return 1;
+ }
+
+ return 0;
+ });
+
+ return $fields;
+ }
+
+ /**
+ * Get weights for fields.
+ *
+ * @return array
+ */
+ protected function _getFieldsWeights()
+ {
+ return [
+ 'thread_id' => 10,
+ 'item_type' => 15,
+ 'text' => 20,
+ 'client_context' => 25,
+ 'activity_status' => 30,
+ 'reaction_type' => 35,
+ 'reaction_status' => 40,
+ 'item_id' => 45,
+ 'node_type' => 50,
+ 'action' => 55,
+ 'profile_user_id' => 60,
+ 'hashtag' => 65,
+ 'venue_id' => 70,
+ 'media_id' => 75,
+ ];
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/IrisSubscribe.php b/vendor/mgp25/instagram-php/src/Realtime/Command/IrisSubscribe.php
new file mode 100755
index 0000000..180b9c9
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/IrisSubscribe.php
@@ -0,0 +1,50 @@
+_sequenceId = $sequenceId;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::IRIS_SUB;
+ }
+
+ /** {@inheritdoc} */
+ public function getQosLevel()
+ {
+ return Mqtt\QosLevel::ACKNOWLEDGED_DELIVERY;
+ }
+
+ /** {@inheritdoc} */
+ public function jsonSerialize()
+ {
+ return [
+ 'seq_id' => $this->_sequenceId,
+ ];
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Command/UpdateSubscriptions.php b/vendor/mgp25/instagram-php/src/Realtime/Command/UpdateSubscriptions.php
new file mode 100755
index 0000000..5bb648c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Command/UpdateSubscriptions.php
@@ -0,0 +1,93 @@
+_topic = $topic;
+ $this->_subscribe = $subscribe;
+ $this->_unsubscribe = $unsubscribe;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return $this->_topic;
+ }
+
+ /** {@inheritdoc} */
+ public function getQosLevel()
+ {
+ return QosLevel::ACKNOWLEDGED_DELIVERY;
+ }
+
+ /**
+ * Prepare the subscriptions list.
+ *
+ * @param array $subscriptions
+ *
+ * @return array
+ */
+ private function _prepareSubscriptions(
+ array $subscriptions)
+ {
+ $result = [];
+ foreach ($subscriptions as $subscription) {
+ $result[] = (string) $subscription;
+ }
+ usort($result, function ($a, $b) {
+ $hashA = Utils::hashCode($a);
+ $hashB = Utils::hashCode($b);
+
+ if ($hashA > $hashB) {
+ return 1;
+ }
+ if ($hashA < $hashB) {
+ return -1;
+ }
+
+ return 0;
+ });
+
+ return $result;
+ }
+
+ /** {@inheritdoc} */
+ public function jsonSerialize()
+ {
+ $result = [];
+ if (count($this->_subscribe)) {
+ $result['sub'] = $this->_prepareSubscriptions($this->_subscribe);
+ }
+ if (count($this->_unsubscribe)) {
+ $result['unsub'] = $this->_prepareSubscriptions($this->_unsubscribe);
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/CommandInterface.php b/vendor/mgp25/instagram-php/src/Realtime/CommandInterface.php
new file mode 100755
index 0000000..4dbe91e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/CommandInterface.php
@@ -0,0 +1,20 @@
+_target = $target;
+ }
+
+ /**
+ * Checks if target has at least one listener for specific event.
+ *
+ * @param string $event
+ *
+ * @return bool
+ */
+ protected function _hasListeners(
+ $event)
+ {
+ return (bool) count($this->_target->listeners($event));
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Handler/DirectHandler.php b/vendor/mgp25/instagram-php/src/Realtime/Handler/DirectHandler.php
new file mode 100755
index 0000000..f6c52e0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Handler/DirectHandler.php
@@ -0,0 +1,543 @@
+[^/]+)$#D';
+ const ITEM_REGEXP = '#^/direct_v2/threads/(?[^/]+)/items/(?[^/]+)$#D';
+ const ACTIVITY_REGEXP = '#^/direct_v2/threads/(?[^/]+)/activity_indicator_id/(?[^/]+)$#D';
+ const STORY_REGEXP = '#^/direct_v2/visual_threads/(?[^/]+)/items/(?[^/]+)$#D';
+ const SEEN_REGEXP = '#^/direct_v2/threads/(?[^/]+)/participants/(?[^/]+)/has_seen$#D';
+ const SCREENSHOT_REGEXP = '#^/direct_v2/visual_thread/(?[^/]+)/screenshot$#D';
+ const BADGE_REGEXP = '#^/direct_v2/visual_action_badge/(?[^/]+)$#D';
+
+ /** {@inheritdoc} */
+ public function handleMessage(
+ Message $message)
+ {
+ $data = $message->getData();
+
+ if (isset($data['event'])) {
+ $this->_processEvent($data);
+ } elseif (isset($data['action'])) {
+ $this->_processAction($data);
+ } else {
+ throw new HandlerException('Invalid message (both event and action are missing).');
+ }
+ }
+
+ /**
+ * Process incoming event.
+ *
+ * @param array $message
+ *
+ * @throws HandlerException
+ */
+ protected function _processEvent(
+ array $message)
+ {
+ if ($message['event'] === RealtimeEvent::PATCH) {
+ $event = new PatchEvent($message);
+ foreach ($event->getData() as $op) {
+ $this->_handlePatchOp($op);
+ }
+ } else {
+ throw new HandlerException(sprintf('Unknown event type "%s".', $message['event']));
+ }
+ }
+
+ /**
+ * Process incoming action.
+ *
+ * @param array $message
+ *
+ * @throws HandlerException
+ */
+ protected function _processAction(
+ array $message)
+ {
+ if ($message['action'] === RealtimeAction::ACK) {
+ $this->_target->emit('client-context-ack', [new AckAction($message)]);
+ } else {
+ throw new HandlerException(sprintf('Unknown action type "%s".', $message['action']));
+ }
+ }
+
+ /**
+ * Patch op handler.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handlePatchOp(
+ PatchEventOp $op)
+ {
+ switch ($op->getOp()) {
+ case PatchEventOp::ADD:
+ $this->_handleAdd($op);
+ break;
+ case PatchEventOp::REPLACE:
+ $this->_handleReplace($op);
+ break;
+ case PatchEventOp::REMOVE:
+ $this->_handleRemove($op);
+ break;
+ case PatchEventOp::NOTIFY:
+ $this->_handleNotify($op);
+ break;
+ default:
+ throw new HandlerException(sprintf('Unknown patch op "%s".', $op->getOp()));
+ }
+ }
+
+ /**
+ * Handler for the ADD op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleAdd(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2/threads')) {
+ if (strpos($path, 'activity_indicator_id') === false) {
+ $this->_upsertThreadItem($op, true);
+ } else {
+ $this->_updateThreadActivity($op);
+ }
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/inbox/threads')) {
+ $this->_upsertThread($op, true);
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/visual_threads')) {
+ $this->_updateDirectStory($op);
+ } else {
+ throw new HandlerException(sprintf('Unsupported ADD path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for the REPLACE op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleReplace(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2/threads')) {
+ if ($this->_pathEndsWith($path, 'has_seen')) {
+ $this->_updateSeen($op);
+ } else {
+ $this->_upsertThreadItem($op, false);
+ }
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/inbox/threads')) {
+ $this->_upsertThread($op, false);
+ } elseif ($this->_pathEndsWith($path, 'unseen_count')) {
+ if ($this->_pathStartsWith($path, '/direct_v2/inbox')) {
+ $this->_updateUnseenCount('inbox', $op);
+ } else {
+ $this->_updateUnseenCount('visual_inbox', $op);
+ }
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/visual_action_badge')) {
+ $this->_directStoryAction($op);
+ } elseif ($this->_pathStartsWith($path, '/direct_v2/visual_thread')) {
+ if ($this->_pathEndsWith($path, 'screenshot')) {
+ $this->_notifyDirectStoryScreenshot($op);
+ } else {
+ $this->_createDirectStory($op);
+ }
+ } else {
+ throw new HandlerException(sprintf('Unsupported REPLACE path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for the REMOVE op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleRemove(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2')) {
+ $this->_removeThreadItem($op);
+ } else {
+ throw new HandlerException(sprintf('Unsupported REMOVE path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for NOTIFY op.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handleNotify(
+ PatchEventOp $op)
+ {
+ $path = $op->getPath();
+ if ($this->_pathStartsWith($path, '/direct_v2/threads')) {
+ $this->_notifyThread($op);
+ } else {
+ throw new HandlerException(sprintf('Unsupported NOTIFY path "%s".', $path));
+ }
+ }
+
+ /**
+ * Handler for thread creation/modification.
+ *
+ * @param PatchEventOp $op
+ * @param bool $insert
+ *
+ * @throws HandlerException
+ */
+ protected function _upsertThread(
+ PatchEventOp $op,
+ $insert)
+ {
+ $event = $insert ? 'thread-created' : 'thread-updated';
+ if (!$this->_hasListeners($event)) {
+ return;
+ }
+
+ if (!preg_match(self::THREAD_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit($event, [$matches['thread_id'], new DirectThread($json)]);
+ }
+
+ /**
+ * Handler for thread item creation/modification.
+ *
+ * @param PatchEventOp $op
+ * @param bool $insert
+ *
+ * @throws HandlerException
+ */
+ protected function _upsertThreadItem(
+ PatchEventOp $op,
+ $insert)
+ {
+ $event = $insert ? 'thread-item-created' : 'thread-item-updated';
+ if (!$this->_hasListeners($event)) {
+ return;
+ }
+
+ if (!preg_match(self::ITEM_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread item regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread item JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit($event, [$matches['thread_id'], $matches['item_id'], new DirectThreadItem($json)]);
+ }
+
+ /**
+ * Handler for thread activity indicator.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _updateThreadActivity(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-activity')) {
+ return;
+ }
+
+ if (!preg_match(self::ACTIVITY_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread activity regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread activity JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit('thread-activity', [$matches['thread_id'], new ThreadActivity($json)]);
+ }
+
+ /**
+ * Handler for story update.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _updateDirectStory(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-updated')) {
+ return;
+ }
+
+ if (!preg_match(self::STORY_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match story item regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode story item JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'direct-story-updated',
+ [$matches['thread_id'], $matches['item_id'], new DirectThreadItem($json)]
+ );
+ }
+
+ /**
+ * Handler for unseen count.
+ *
+ * @param string $inbox
+ * @param PatchEventOp $op
+ */
+ protected function _updateUnseenCount(
+ $inbox,
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('unseen-count-update')) {
+ return;
+ }
+
+ $payload = new DirectSeenItemPayload([
+ 'count' => (int) $op->getValue(),
+ 'timestamp' => $op->getTs(),
+ ]);
+ $this->_target->emit('unseen-count-update', [$inbox, $payload]);
+ }
+
+ /**
+ * Handler for thread seen indicator.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _updateSeen(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-seen')) {
+ return;
+ }
+
+ if (!preg_match(self::SEEN_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread seen regexp.', $op->getPath()));
+ }
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread seen JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'thread-seen',
+ [$matches['thread_id'], $matches['user_id'], new DirectThreadLastSeenAt($json)]
+ );
+ }
+
+ /**
+ * Handler for screenshot notification.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _notifyDirectStoryScreenshot(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-screenshot')) {
+ return;
+ }
+
+ if (!preg_match(self::SCREENSHOT_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread screenshot regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit('direct-story-screenshot', [$matches['thread_id'], new StoryScreenshot($json)]);
+ }
+
+ /**
+ * Handler for direct story creation.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _createDirectStory(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-created')) {
+ return;
+ }
+
+ if ($op->getPath() !== '/direct_v2/visual_thread/create') {
+ throw new HandlerException(sprintf('Path "%s" does not match story create path.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode inbox JSON: %s.', json_last_error_msg()));
+ }
+
+ $inbox = new DirectInbox($json);
+ $allThreads = $inbox->getThreads();
+ if ($allThreads === null || !count($allThreads)) {
+ return;
+ }
+ $this->_target->emit('direct-story-created', [reset($allThreads)]);
+ }
+
+ /**
+ * Handler for story action.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _directStoryAction(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('direct-story-action')) {
+ return;
+ }
+
+ if (!preg_match(self::BADGE_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match story action regexp.', $op->getPath()));
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode story action JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'direct-story-action',
+ [$matches['thread_id'], new ActionBadge($json)]
+ );
+ }
+
+ /**
+ * Handler for thread item removal.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _removeThreadItem(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-item-removed')) {
+ return;
+ }
+
+ if (!preg_match(self::ITEM_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread item regexp.', $op->getPath()));
+ }
+
+ $this->_target->emit('thread-item-removed', [$matches['thread_id'], $matches['item_id']]);
+ }
+
+ /**
+ * Handler for thread notify.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _notifyThread(
+ PatchEventOp $op)
+ {
+ if (!$this->_hasListeners('thread-notify')) {
+ return;
+ }
+
+ if (!preg_match(self::ITEM_REGEXP, $op->getPath(), $matches)) {
+ throw new HandlerException(sprintf('Path "%s" does not match thread item regexp.', $op->getPath()));
+ }
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode thread item notify JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit(
+ 'thread-notify',
+ [$matches['thread_id'], $matches['item_id'], new ThreadAction($json)]
+ );
+ }
+
+ /**
+ * Checks if the path starts with specified substring.
+ *
+ * @param string $path
+ * @param string $string
+ *
+ * @return bool
+ */
+ protected function _pathStartsWith(
+ $path,
+ $string)
+ {
+ return strncmp($path, $string, strlen($string)) === 0;
+ }
+
+ /**
+ * Checks if the path ends with specified substring.
+ *
+ * @param string $path
+ * @param string $string
+ *
+ * @return bool
+ */
+ protected function _pathEndsWith(
+ $path,
+ $string)
+ {
+ $length = strlen($string);
+
+ return substr_compare($path, $string, strlen($path) - $length, $length) === 0;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Handler/HandlerException.php b/vendor/mgp25/instagram-php/src/Realtime/Handler/HandlerException.php
new file mode 100755
index 0000000..66819eb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Handler/HandlerException.php
@@ -0,0 +1,7 @@
+getData());
+ if (!$iris->isSucceeded()) {
+ throw new HandlerException(sprintf(
+ 'Failed to subscribe to Iris (%d): %s.',
+ $iris->getErrorType(),
+ $iris->getErrorMessage()
+ ));
+ }
+ $this->_target->emit('iris-subscribed', [$iris]);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Handler/LiveHandler.php b/vendor/mgp25/instagram-php/src/Realtime/Handler/LiveHandler.php
new file mode 100755
index 0000000..aa597bb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Handler/LiveHandler.php
@@ -0,0 +1,81 @@
+getData();
+
+ if (isset($data['event'])) {
+ $this->_processEvent($data);
+ } else {
+ throw new HandlerException('Invalid message (event type is missing).');
+ }
+ }
+
+ /**
+ * Process incoming event.
+ *
+ * @param array $message
+ *
+ * @throws HandlerException
+ */
+ protected function _processEvent(
+ array $message)
+ {
+ if ($message['event'] === RealtimeEvent::PATCH) {
+ $event = new PatchEvent($message);
+ foreach ($event->getData() as $op) {
+ $this->_handlePatchOp($op);
+ }
+ } else {
+ throw new HandlerException(sprintf('Unknown event type "%s".', $message['event']));
+ }
+ }
+
+ /**
+ * Handler for live broadcast creation/removal.
+ *
+ * @param PatchEventOp $op
+ *
+ * @throws HandlerException
+ */
+ protected function _handlePatchOp(
+ PatchEventOp $op)
+ {
+ switch ($op->getOp()) {
+ case PatchEventOp::ADD:
+ $event = 'live-started';
+ break;
+ case PatchEventOp::REMOVE:
+ $event = 'live-stopped';
+ break;
+ default:
+ throw new HandlerException(sprintf('Unsupported live broadcast op: "%s".', $op->getOp()));
+ }
+ if (!$this->_hasListeners($event)) {
+ return;
+ }
+
+ $json = HttpClient::api_body_decode($op->getValue());
+ if (!is_array($json)) {
+ throw new HandlerException(sprintf('Failed to decode live broadcast JSON: %s.', json_last_error_msg()));
+ }
+
+ $this->_target->emit($event, [new LiveBroadcast($json)]);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Handler/PresenceHandler.php b/vendor/mgp25/instagram-php/src/Realtime/Handler/PresenceHandler.php
new file mode 100755
index 0000000..d0925df
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Handler/PresenceHandler.php
@@ -0,0 +1,25 @@
+getData();
+ if (!isset($data['presence_event']) || !is_array($data['presence_event'])) {
+ throw new HandlerException('Invalid presence (event data is missing).');
+ }
+ $presence = new UserPresence($data['presence_event']);
+ $this->_target->emit('presence', [$presence]);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Handler/RegionHintHandler.php b/vendor/mgp25/instagram-php/src/Realtime/Handler/RegionHintHandler.php
new file mode 100755
index 0000000..4c529a2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Handler/RegionHintHandler.php
@@ -0,0 +1,22 @@
+getData();
+ if ($region === null || $region === '') {
+ throw new HandlerException('Invalid region hint.');
+ }
+ $this->_target->emit('region-hint', [$message->getData()]);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Handler/ZeroProvisionHandler.php b/vendor/mgp25/instagram-php/src/Realtime/Handler/ZeroProvisionHandler.php
new file mode 100755
index 0000000..009bd59
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Handler/ZeroProvisionHandler.php
@@ -0,0 +1,24 @@
+getData();
+ if (!isset($data['zero_product_provisioning_event']) || !is_array($data['zero_product_provisioning_event'])) {
+ throw new HandlerException('Invalid zero provision (event data is missing).');
+ }
+ $provision = new ZeroProvisionEvent($data['zero_product_provisioning_event']);
+ $this->_target->emit('zero-provision', [$provision]);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/HandlerInterface.php b/vendor/mgp25/instagram-php/src/Realtime/HandlerInterface.php
new file mode 100755
index 0000000..d7cd9b9
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/HandlerInterface.php
@@ -0,0 +1,18 @@
+_module = $module;
+ $this->_data = $payload;
+ }
+
+ /**
+ * @return string
+ */
+ public function getModule()
+ {
+ return $this->_module;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getData()
+ {
+ return $this->_data;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Mqtt.php b/vendor/mgp25/instagram-php/src/Realtime/Mqtt.php
new file mode 100755
index 0000000..1a29a3e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Mqtt.php
@@ -0,0 +1,803 @@
+_target = $target;
+ $this->_connector = $connector;
+ $this->_auth = $auth;
+ $this->_device = $device;
+ $this->_loop = $loop;
+ $this->_logger = $logger;
+ $this->_additionalOptions = $additionalOptions;
+
+ $this->_subscriptions = [];
+
+ $this->_loadExperiments($experiments);
+ $this->_initSubscriptions();
+
+ $this->_shutdown = false;
+ $this->_client = $this->_getClient();
+
+ $this->_parsers = [
+ Mqtt\Topics::PUBSUB => new Parser\SkywalkerParser(),
+ Mqtt\Topics::SEND_MESSAGE_RESPONSE => new Parser\JsonParser(Handler\DirectHandler::MODULE),
+ Mqtt\Topics::IRIS_SUB_RESPONSE => new Parser\JsonParser(Handler\IrisHandler::MODULE),
+ Mqtt\Topics::MESSAGE_SYNC => new Parser\IrisParser(),
+ Mqtt\Topics::REALTIME_SUB => new Parser\GraphQlParser(),
+ Mqtt\Topics::GRAPHQL => new Parser\GraphQlParser(),
+ Mqtt\Topics::REGION_HINT => new Parser\RegionHintParser(),
+ ];
+ $this->_handlers = [
+ Handler\DirectHandler::MODULE => new Handler\DirectHandler($this->_target),
+ Handler\LiveHandler::MODULE => new Handler\LiveHandler($this->_target),
+ Handler\IrisHandler::MODULE => new Handler\IrisHandler($this->_target),
+ Handler\PresenceHandler::MODULE => new Handler\PresenceHandler($this->_target),
+ Handler\RegionHintHandler::MODULE => new Handler\RegionHintHandler($this->_target),
+ Handler\ZeroProvisionHandler::MODULE => new Handler\ZeroProvisionHandler($this->_target),
+ ];
+ }
+
+ /** {@inheritdoc} */
+ public function getLoop()
+ {
+ return $this->_loop;
+ }
+
+ /** {@inheritdoc} */
+ public function isActive()
+ {
+ return !$this->_shutdown;
+ }
+
+ /**
+ * Add a subscription to the list.
+ *
+ * @param SubscriptionInterface $subscription
+ */
+ public function addSubscription(
+ SubscriptionInterface $subscription)
+ {
+ $this->_doAddSubscription($subscription, true);
+ }
+
+ /**
+ * Remove a subscription from the list.
+ *
+ * @param SubscriptionInterface $subscription
+ */
+ public function removeSubscription(
+ SubscriptionInterface $subscription)
+ {
+ $this->_doRemoveSubscription($subscription, true);
+ }
+
+ /**
+ * Set an additional option.
+ *
+ * @param string $option
+ * @param mixed $value
+ */
+ public function setAdditionalOption(
+ $option,
+ $value)
+ {
+ $this->_additionalOptions[$option] = $value;
+ }
+
+ /**
+ * Add a subscription to the list and send a command (optional).
+ *
+ * @param SubscriptionInterface $subscription
+ * @param bool $sendCommand
+ */
+ protected function _doAddSubscription(
+ SubscriptionInterface $subscription,
+ $sendCommand)
+ {
+ $topic = $subscription->getTopic();
+ $id = $subscription->getId();
+
+ // Check whether we already subscribed to it.
+ if (isset($this->_subscriptions[$topic][$id])) {
+ return;
+ }
+
+ // Add the subscription to the list.
+ if (!isset($this->_subscriptions[$topic])) {
+ $this->_subscriptions[$topic] = [];
+ }
+ $this->_subscriptions[$topic][$id] = $subscription;
+
+ // Send a command when needed.
+ if (!$sendCommand || $this->_isConnected()) {
+ return;
+ }
+
+ $this->_updateSubscriptions($topic, [$subscription], []);
+ }
+
+ /**
+ * Remove a subscription from the list and send a command (optional).
+ *
+ * @param SubscriptionInterface $subscription
+ * @param bool $sendCommand
+ */
+ protected function _doRemoveSubscription(
+ SubscriptionInterface $subscription,
+ $sendCommand)
+ {
+ $topic = $subscription->getTopic();
+ $id = $subscription->getId();
+
+ // Check whether we are subscribed to it.
+ if (!isset($this->_subscriptions[$topic][$id])) {
+ return;
+ }
+
+ // Remove the subscription from the list.
+ unset($this->_subscriptions[$topic][$id]);
+ if (!count($this->_subscriptions[$topic])) {
+ unset($this->_subscriptions[$topic]);
+ }
+
+ // Send a command when needed.
+ if (!$sendCommand || $this->_isConnected()) {
+ return;
+ }
+
+ $this->_updateSubscriptions($topic, [], [$subscription]);
+ }
+
+ /**
+ * Cancel a keepalive timer (if any).
+ */
+ protected function _cancelKeepaliveTimer()
+ {
+ if ($this->_keepaliveTimer !== null) {
+ if ($this->_keepaliveTimer->isActive()) {
+ $this->_logger->debug('Existing keepalive timer has been canceled.');
+ $this->_keepaliveTimer->cancel();
+ }
+ $this->_keepaliveTimer = null;
+ }
+ }
+
+ /**
+ * Set up a new keepalive timer.
+ */
+ protected function _setKeepaliveTimer()
+ {
+ $this->_cancelKeepaliveTimer();
+ $keepaliveInterval = Mqtt\Config::MQTT_KEEPALIVE;
+ $this->_logger->debug(sprintf('Setting up keepalive timer to %d seconds', $keepaliveInterval));
+ $this->_keepaliveTimer = $this->_loop->addTimer($keepaliveInterval, function () {
+ $this->_logger->info('Keepalive timer has been fired.');
+ $this->_disconnect();
+ });
+ }
+
+ /**
+ * Try to establish a connection.
+ */
+ protected function _connect()
+ {
+ $this->_setReconnectTimer(function () {
+ $this->_logger->info(sprintf('Connecting to %s:%d...', Mqtt\Config::DEFAULT_HOST, Mqtt\Config::DEFAULT_PORT));
+
+ $connection = new DefaultConnection(
+ $this->_getMqttUsername(),
+ $this->_auth->getPassword(),
+ null,
+ $this->_auth->getClientId(),
+ Mqtt\Config::MQTT_KEEPALIVE,
+ Mqtt\Config::MQTT_VERSION,
+ true
+ );
+
+ return $this->_client->connect(Mqtt\Config::DEFAULT_HOST, Mqtt\Config::DEFAULT_PORT, $connection, Mqtt\Config::CONNECTION_TIMEOUT);
+ });
+ }
+
+ /**
+ * Perform first connection in a row.
+ */
+ public function start()
+ {
+ $this->_shutdown = false;
+ $this->_reconnectInterval = 0;
+ $this->_connect();
+ }
+
+ /**
+ * Whether connection is established.
+ *
+ * @return bool
+ */
+ protected function _isConnected()
+ {
+ return $this->_client->isConnected();
+ }
+
+ /**
+ * Disconnect from server.
+ */
+ protected function _disconnect()
+ {
+ $this->_cancelKeepaliveTimer();
+ $this->_client->disconnect();
+ }
+
+ /**
+ * Proxy for _disconnect().
+ */
+ public function stop()
+ {
+ $this->_logger->info('Shutting down...');
+ $this->_shutdown = true;
+ $this->_cancelReconnectTimer();
+ $this->_disconnect();
+ }
+
+ /**
+ * Send the command.
+ *
+ * @param CommandInterface $command
+ *
+ * @throws \LogicException
+ */
+ public function sendCommand(
+ CommandInterface $command)
+ {
+ if (!$this->_isConnected()) {
+ throw new \LogicException('Tried to send the command while offline.');
+ }
+
+ $this->_publish(
+ $command->getTopic(),
+ json_encode($command, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
+ $command->getQosLevel()
+ );
+ }
+
+ /**
+ * Load the experiments.
+ *
+ * @param ExperimentsInterface $experiments
+ */
+ protected function _loadExperiments(
+ ExperimentsInterface $experiments)
+ {
+ $this->_experiments = $experiments;
+
+ // Direct features.
+ $this->_irisEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_realtime_iris',
+ 'is_direct_over_iris_enabled'
+ );
+ $this->_msgTypeBlacklist = $experiments->getExperimentParam(
+ 'ig_android_realtime_iris',
+ 'pubsub_msg_type_blacklist',
+ 'null'
+ );
+
+ // Live features.
+ $this->_mqttLiveEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_skywalker_live_event_start_end',
+ 'is_enabled'
+ );
+
+ // GraphQL features.
+ $this->_graphQlTypingEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_gqls_typing_indicator',
+ 'is_enabled'
+ );
+
+ // Presence features.
+ $this->_inboxPresenceEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_direct_inbox_presence',
+ 'is_enabled'
+ );
+ $this->_threadPresenceEnabled = $experiments->isExperimentEnabled(
+ 'ig_android_direct_thread_presence',
+ 'is_enabled'
+ );
+ }
+
+ protected function _initSubscriptions()
+ {
+ $subscriptionId = Signatures::generateUUID();
+ // Set up PubSub topics.
+ $liveSubscription = new LiveSubscription($this->_auth->getUserId());
+ if ($this->_mqttLiveEnabled) {
+ $this->_doAddSubscription($liveSubscription, false);
+ } else {
+ $this->_doRemoveSubscription($liveSubscription, false);
+ }
+ // Direct subscription is always enabled.
+ $this->_doAddSubscription(new DirectSubscription($this->_auth->getUserId()), false);
+
+ // Set up GraphQL topics.
+ $zeroProvisionSubscription = new ZeroProvisionSubscription($this->_auth->getDeviceId());
+ $this->_doAddSubscription($zeroProvisionSubscription, false);
+ $graphQlTypingSubscription = new DirectTypingSubscription($this->_auth->getUserId());
+ if ($this->_graphQlTypingEnabled) {
+ $this->_doAddSubscription($graphQlTypingSubscription, false);
+ } else {
+ $this->_doRemoveSubscription($graphQlTypingSubscription, false);
+ }
+ $appPresenceSubscription = new AppPresenceSubscription($subscriptionId);
+ if (($this->_inboxPresenceEnabled || $this->_threadPresenceEnabled) && empty($this->_additionalOptions['disable_presence'])) {
+ $this->_doAddSubscription($appPresenceSubscription, false);
+ } else {
+ $this->_doRemoveSubscription($appPresenceSubscription, false);
+ }
+ }
+
+ /**
+ * Returns application specific info.
+ *
+ * @return array
+ */
+ protected function _getAppSpecificInfo()
+ {
+ $result = [
+ 'platform' => Constants::PLATFORM,
+ 'app_version' => Constants::IG_VERSION,
+ 'capabilities' => Constants::X_IG_Capabilities,
+ ];
+ // PubSub message type blacklist.
+ $msgTypeBlacklist = '';
+ if ($this->_msgTypeBlacklist !== null && $this->_msgTypeBlacklist !== '') {
+ $msgTypeBlacklist = $this->_msgTypeBlacklist;
+ }
+ if ($this->_graphQlTypingEnabled) {
+ if ($msgTypeBlacklist !== '') {
+ $msgTypeBlacklist .= ', typing_type';
+ } else {
+ $msgTypeBlacklist = 'typing_type';
+ }
+ }
+ if ($msgTypeBlacklist !== '') {
+ $result['pubsub_msg_type_blacklist'] = $msgTypeBlacklist;
+ }
+ // Everclear subscriptions.
+ $everclearSubscriptions = [];
+ if ($this->_inboxPresenceEnabled) {
+ $everclearSubscriptions[AppPresenceSubscription::ID] = AppPresenceSubscription::QUERY;
+ }
+ if (count($everclearSubscriptions)) {
+ $result['everclear_subscriptions'] = json_encode($everclearSubscriptions);
+ }
+ // Maintaining the order.
+ $result['User-Agent'] = $this->_device->getUserAgent();
+ $result['ig_mqtt_route'] = 'django';
+ // Accept-Language must be the last one.
+ $result['Accept-Language'] = Constants::ACCEPT_LANGUAGE;
+
+ return $result;
+ }
+
+ /**
+ * Get a list of topics to subscribe at connect.
+ *
+ * @return string[]
+ */
+ protected function _getSubscribeTopics()
+ {
+ $topics = [
+ Mqtt\Topics::PUBSUB,
+ ];
+ $topics[] = Mqtt\Topics::REGION_HINT;
+ if ($this->_graphQlTypingEnabled) {
+ $topics[] = Mqtt\Topics::REALTIME_SUB;
+ }
+ $topics[] = Mqtt\Topics::SEND_MESSAGE_RESPONSE;
+ if ($this->_irisEnabled) {
+ $topics[] = Mqtt\Topics::IRIS_SUB_RESPONSE;
+ $topics[] = Mqtt\Topics::MESSAGE_SYNC;
+ }
+
+ return $topics;
+ }
+
+ /**
+ * Returns username for MQTT connection.
+ *
+ * @return string
+ */
+ protected function _getMqttUsername()
+ {
+ // Session ID is uptime in msec.
+ $sessionId = (microtime(true) - strtotime('Last Monday')) * 1000;
+ // Random buster-string to avoid clashing with other data.
+ $randNum = mt_rand(1000000, 9999999);
+ $fields = [
+ // USER_ID
+ 'u' => '%ACCOUNT_ID_'.$randNum.'%',
+ // AGENT
+ 'a' => $this->_device->getFbUserAgent(Constants::INSTAGRAM_APPLICATION_NAME),
+ // CAPABILITIES
+ 'cp' => Mqtt\Capabilities::DEFAULT_SET,
+ // CLIENT_MQTT_SESSION_ID
+ 'mqtt_sid' => '%SESSION_ID_'.$randNum.'%',
+ // NETWORK_TYPE
+ 'nwt' => Mqtt\Config::NETWORK_TYPE_WIFI,
+ // NETWORK_SUBTYPE
+ 'nwst' => 0,
+ // MAKE_USER_AVAILABLE_IN_FOREGROUND
+ 'chat_on' => false,
+ // NO_AUTOMATIC_FOREGROUND
+ 'no_auto_fg' => true,
+ // DEVICE_ID
+ 'd' => $this->_auth->getDeviceId(),
+ // DEVICE_SECRET
+ 'ds' => $this->_auth->getDeviceSecret(),
+ // INITIAL_FOREGROUND_STATE
+ 'fg' => false,
+ // ENDPOINT_CAPABILITIES
+ 'ecp' => 0,
+ // PUBLISH_FORMAT
+ 'pf' => Mqtt\Config::PUBLISH_FORMAT,
+ // CLIENT_TYPE
+ 'ct' => Mqtt\Config::CLIENT_TYPE,
+ // APP_ID
+ 'aid' => Constants::FACEBOOK_ANALYTICS_APPLICATION_ID,
+ // SUBSCRIBE_TOPICS
+ 'st' => $this->_getSubscribeTopics(),
+ ];
+ // REGION_PREFERENCE
+ if (!empty($this->_additionalOptions['datacenter'])) {
+ $fields['dc'] = $this->_additionalOptions['datacenter'];
+ }
+ // Maintaining the order.
+ $fields['clientStack'] = 3;
+ $fields['app_specific_info'] = $this->_getAppSpecificInfo();
+ // Account and session IDs must be a number, but int size is platform dependent in PHP,
+ // so we are replacing JSON strings with plain strings in JSON encoded data.
+ $result = strtr(json_encode($fields), [
+ json_encode('%ACCOUNT_ID_'.$randNum.'%') => $this->_auth->getUserId(),
+ json_encode('%SESSION_ID_'.$randNum.'%') => round($sessionId),
+ ]);
+
+ return $result;
+ }
+
+ /**
+ * Create a new MQTT client.
+ *
+ * @return ReactMqttClient
+ */
+ protected function _getClient()
+ {
+ $client = new ReactMqttClient($this->_connector, $this->_loop, null, new Mqtt\StreamParser());
+
+ $client->on('error', function (\Exception $e) {
+ $this->_logger->error($e->getMessage());
+ });
+ $client->on('warning', function (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+ });
+ $client->on('open', function () {
+ $this->_logger->info('Connection has been established');
+ });
+ $client->on('close', function () {
+ $this->_logger->info('Connection has been closed');
+ $this->_cancelKeepaliveTimer();
+ if (!$this->_reconnectInterval) {
+ $this->_connect();
+ }
+ });
+ $client->on('connect', function () {
+ $this->_logger->info('Connected to a broker');
+ $this->_setKeepaliveTimer();
+ $this->_restoreAllSubscriptions();
+ });
+ $client->on('ping', function () {
+ $this->_logger->debug('Ping flow completed');
+ $this->_setKeepaliveTimer();
+ });
+ $client->on('publish', function () {
+ $this->_logger->debug('Publish flow completed');
+ $this->_setKeepaliveTimer();
+ });
+ $client->on('message', function (MqttMessage $message) {
+ $this->_setKeepaliveTimer();
+ $this->_onReceive($message);
+ });
+ $client->on('disconnect', function () {
+ $this->_logger->info('Disconnected from broker');
+ });
+
+ return $client;
+ }
+
+ /**
+ * Mass update subscriptions statuses.
+ *
+ * @param string $topic
+ * @param SubscriptionInterface[] $subscribe
+ * @param SubscriptionInterface[] $unsubscribe
+ */
+ protected function _updateSubscriptions(
+ $topic,
+ array $subscribe,
+ array $unsubscribe)
+ {
+ if (count($subscribe)) {
+ $this->_logger->info(sprintf('Subscribing to %s topics %s', $topic, implode(', ', $subscribe)));
+ }
+ if (count($unsubscribe)) {
+ $this->_logger->info(sprintf('Unsubscribing from %s topics %s', $topic, implode(', ', $subscribe)));
+ }
+
+ try {
+ $this->sendCommand(new UpdateSubscriptions($topic, $subscribe, $unsubscribe));
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage());
+ }
+ }
+
+ /**
+ * Subscribe to all topics.
+ */
+ protected function _restoreAllSubscriptions()
+ {
+ foreach ($this->_subscriptions as $topic => $subscriptions) {
+ $this->_updateSubscriptions($topic, $subscriptions, []);
+ }
+ }
+
+ /**
+ * Unsubscribe from all topics.
+ */
+ protected function _removeAllSubscriptions()
+ {
+ foreach ($this->_subscriptions as $topic => $subscriptions) {
+ $this->_updateSubscriptions($topic, [], $subscriptions);
+ }
+ }
+
+ /**
+ * Maps human readable topic to its identifier.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ protected function _mapTopic(
+ $topic)
+ {
+ if (array_key_exists($topic, Mqtt\Topics::TOPIC_TO_ID_MAP)) {
+ $result = Mqtt\Topics::TOPIC_TO_ID_MAP[$topic];
+ $this->_logger->debug(sprintf('Topic "%s" has been mapped to "%s"', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->_logger->warning(sprintf('Topic "%s" does not exist in the enum', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Maps topic ID to human readable name.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ protected function _unmapTopic(
+ $topic)
+ {
+ if (array_key_exists($topic, Mqtt\Topics::ID_TO_TOPIC_MAP)) {
+ $result = Mqtt\Topics::ID_TO_TOPIC_MAP[$topic];
+ $this->_logger->debug(sprintf('Topic ID "%s" has been unmapped to "%s"', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->_logger->warning(sprintf('Topic ID "%s" does not exist in the enum', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $topic
+ * @param string $payload
+ * @param int $qosLevel
+ */
+ protected function _publish(
+ $topic,
+ $payload,
+ $qosLevel)
+ {
+ $this->_logger->info(sprintf('Sending message "%s" to topic "%s"', $payload, $topic));
+ $payload = zlib_encode($payload, ZLIB_ENCODING_DEFLATE, 9);
+ // We need to map human readable topic name to its ID because of bandwidth saving.
+ $topic = $this->_mapTopic($topic);
+ $this->_client->publish(new DefaultMessage($topic, $payload, $qosLevel));
+ }
+
+ /**
+ * Incoming message handler.
+ *
+ * @param MqttMessage $msg
+ */
+ protected function _onReceive(
+ MqttMessage $msg)
+ {
+ $payload = @zlib_decode($msg->getPayload());
+ if ($payload === false) {
+ $this->_logger->warning('Failed to inflate the payload');
+
+ return;
+ }
+ $this->_handleMessage($this->_unmapTopic($msg->getTopic()), $payload);
+ }
+
+ /**
+ * @param string $topic
+ * @param string $payload
+ */
+ protected function _handleMessage(
+ $topic,
+ $payload)
+ {
+ $this->_logger->debug(
+ sprintf('Received a message from topic "%s"', $topic),
+ [base64_encode($payload)]
+ );
+ if (!isset($this->_parsers[$topic])) {
+ $this->_logger->warning(
+ sprintf('No parser for topic "%s" found, skipping the message(s)', $topic),
+ [base64_encode($payload)]
+ );
+
+ return;
+ }
+
+ try {
+ $messages = $this->_parsers[$topic]->parseMessage($topic, $payload);
+ } catch (\Exception $e) {
+ $this->_logger->warning($e->getMessage(), [$topic, base64_encode($payload)]);
+
+ return;
+ }
+
+ foreach ($messages as $message) {
+ $module = $message->getModule();
+ if (!isset($this->_handlers[$module])) {
+ $this->_logger->warning(
+ sprintf('No handler for module "%s" found, skipping the message', $module),
+ [$message->getData()]
+ );
+
+ continue;
+ }
+
+ $this->_logger->info(
+ sprintf('Processing a message for module "%s"', $module),
+ [$message->getData()]
+ );
+
+ try {
+ $this->_handlers[$module]->handleMessage($message);
+ } catch (Handler\HandlerException $e) {
+ $this->_logger->warning($e->getMessage(), [$message->getData()]);
+ } catch (\Exception $e) {
+ $this->_target->emit('warning', [$e]);
+ }
+ }
+ }
+
+ /** {@inheritdoc} */
+ public function getLogger()
+ {
+ return $this->_logger;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Auth.php b/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Auth.php
new file mode 100755
index 0000000..5f149b6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Auth.php
@@ -0,0 +1,74 @@
+_instagram = $instagram;
+ }
+
+ /** {@inheritdoc} */
+ public function getClientId()
+ {
+ return substr($this->getDeviceId(), 0, 20);
+ }
+
+ /** {@inheritdoc} */
+ public function getClientType()
+ {
+ return self::AUTH_TYPE;
+ }
+
+ /** {@inheritdoc} */
+ public function getUserId()
+ {
+ return $this->_instagram->account_id;
+ }
+
+ /** {@inheritdoc} */
+ public function getPassword()
+ {
+ $cookie = $this->_instagram->client->getCookie('sessionid', 'i.instagram.com');
+ if ($cookie !== null) {
+ return sprintf('%s=%s', $cookie->getName(), $cookie->getValue());
+ }
+
+ throw new \RuntimeException('No session cookie was found.');
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceId()
+ {
+ return $this->_instagram->uuid;
+ }
+
+ /** {@inheritdoc} */
+ public function getDeviceSecret()
+ {
+ return '';
+ }
+
+ /** {@inheritdoc} */
+ public function __toString()
+ {
+ return '';
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Capabilities.php b/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Capabilities.php
new file mode 100755
index 0000000..9519c03
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Capabilities.php
@@ -0,0 +1,33 @@
+ ConnectRequestPacket::class,
+ Packet::TYPE_CONNACK => ConnectResponsePacket::class,
+ Packet::TYPE_PUBLISH => PublishRequestPacket::class,
+ Packet::TYPE_PUBACK => PublishAckPacket::class,
+ Packet::TYPE_PUBREC => PublishReceivedPacket::class,
+ Packet::TYPE_PUBREL => PublishReleasePacket::class,
+ Packet::TYPE_PUBCOMP => PublishCompletePacket::class,
+ Packet::TYPE_SUBSCRIBE => SubscribeRequestPacket::class,
+ Packet::TYPE_SUBACK => SubscribeResponsePacket::class,
+ Packet::TYPE_UNSUBSCRIBE => UnsubscribeRequestPacket::class,
+ Packet::TYPE_UNSUBACK => UnsubscribeResponsePacket::class,
+ Packet::TYPE_PINGREQ => PingRequestPacket::class,
+ Packet::TYPE_PINGRESP => PingResponsePacket::class,
+ Packet::TYPE_DISCONNECT => DisconnectRequestPacket::class,
+ ];
+
+ /**
+ * Builds a packet object for the given type.
+ *
+ * @param int $type
+ *
+ * @throws UnknownPacketTypeException
+ *
+ * @return Packet
+ */
+ public function build(
+ $type)
+ {
+ if (!isset(self::$_mapping[$type])) {
+ throw new UnknownPacketTypeException(sprintf('Unknown packet type %d.', $type));
+ }
+
+ $class = self::$_mapping[$type];
+
+ return new $class();
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Mqtt/QosLevel.php b/vendor/mgp25/instagram-php/src/Realtime/Mqtt/QosLevel.php
new file mode 100755
index 0000000..3d98ae1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Mqtt/QosLevel.php
@@ -0,0 +1,9 @@
+_buffer = new PacketStream();
+ $this->_factory = new PacketFactory();
+ }
+
+ /**
+ * Registers an error callback.
+ *
+ * @param callable $callback
+ */
+ public function onError(
+ $callback)
+ {
+ $this->_errorCallback = $callback;
+ }
+
+ /**
+ * Appends the given data to the internal buffer and parses it.
+ *
+ * @param string $data
+ *
+ * @return Packet[]
+ */
+ public function push(
+ $data)
+ {
+ $this->_buffer->write($data);
+
+ $result = [];
+ while ($this->_buffer->getRemainingBytes() > 0) {
+ $type = $this->_buffer->readByte() >> 4;
+
+ try {
+ $packet = $this->_factory->build($type);
+ } catch (UnknownPacketTypeException $e) {
+ $this->_handleError($e);
+ continue;
+ }
+
+ $this->_buffer->seek(-1);
+ $position = $this->_buffer->getPosition();
+
+ try {
+ $packet->read($this->_buffer);
+ $result[] = $packet;
+ $this->_buffer->cut();
+ } catch (EndOfStreamException $e) {
+ $this->_buffer->setPosition($position);
+ break;
+ } catch (MalformedPacketException $e) {
+ $this->_handleError($e);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the registered error callback.
+ *
+ * @param \Throwable $exception
+ */
+ private function _handleError(
+ $exception)
+ {
+ if ($this->_errorCallback !== null) {
+ $callback = $this->_errorCallback;
+ $callback($exception);
+ }
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Topics.php b/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Topics.php
new file mode 100755
index 0000000..3e8dbd4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Mqtt/Topics.php
@@ -0,0 +1,57 @@
+ self::PUBSUB,
+ self::SEND_MESSAGE_ID => self::SEND_MESSAGE,
+ self::SEND_MESSAGE_RESPONSE_ID => self::SEND_MESSAGE_RESPONSE,
+ self::IRIS_SUB_ID => self::IRIS_SUB,
+ self::IRIS_SUB_RESPONSE_ID => self::IRIS_SUB_RESPONSE,
+ self::MESSAGE_SYNC_ID => self::MESSAGE_SYNC,
+ self::REALTIME_SUB_ID => self::REALTIME_SUB,
+ self::GRAPHQL_ID => self::GRAPHQL,
+ self::REGION_HINT_ID => self::REGION_HINT,
+ ];
+
+ const TOPIC_TO_ID_MAP = [
+ self::PUBSUB => self::PUBSUB_ID,
+ self::SEND_MESSAGE => self::SEND_MESSAGE_ID,
+ self::SEND_MESSAGE_RESPONSE => self::SEND_MESSAGE_RESPONSE_ID,
+ self::IRIS_SUB => self::IRIS_SUB_ID,
+ self::IRIS_SUB_RESPONSE => self::IRIS_SUB_RESPONSE_ID,
+ self::MESSAGE_SYNC => self::MESSAGE_SYNC_ID,
+ self::REALTIME_SUB => self::REALTIME_SUB_ID,
+ self::GRAPHQL => self::GRAPHQL_ID,
+ self::REGION_HINT => self::REGION_HINT_ID,
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Parser/GraphQlParser.php b/vendor/mgp25/instagram-php/src/Realtime/Parser/GraphQlParser.php
new file mode 100755
index 0000000..f88e5ff
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Parser/GraphQlParser.php
@@ -0,0 +1,82 @@
+ self::MODULE_DIRECT,
+ AppPresenceSubscription::QUERY => AppPresenceSubscription::ID,
+ ZeroProvisionSubscription::QUERY => ZeroProvisionSubscription::ID,
+ ];
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ */
+ public function parseMessage(
+ $topic,
+ $payload)
+ {
+ $msgTopic = $msgPayload = null;
+ new Reader($payload, function ($context, $field, $value, $type) use (&$msgTopic, &$msgPayload) {
+ if ($type === Compact::TYPE_BINARY) {
+ if ($field === self::FIELD_TOPIC) {
+ $msgTopic = $value;
+ } elseif ($field === self::FIELD_PAYLOAD) {
+ $msgPayload = $value;
+ }
+ }
+ });
+
+ return [$this->_createMessage($msgTopic, $msgPayload)];
+ }
+
+ /**
+ * Create a message from given topic and payload.
+ *
+ * @param string $topic
+ * @param string $payload
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ *
+ * @return Message
+ */
+ protected function _createMessage(
+ $topic,
+ $payload)
+ {
+ if ($topic === null || $payload === null) {
+ throw new \RuntimeException('Incomplete GraphQL message.');
+ }
+
+ if (!array_key_exists($topic, self::TOPIC_TO_MODULE_ENUM)) {
+ throw new \DomainException(sprintf('Unknown GraphQL topic "%s".', $topic));
+ }
+
+ $data = Client::api_body_decode($payload);
+ if (!is_array($data)) {
+ throw new \RuntimeException('Invalid GraphQL payload.');
+ }
+
+ return new Message(self::TOPIC_TO_MODULE_ENUM[$topic], $data);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Parser/IrisParser.php b/vendor/mgp25/instagram-php/src/Realtime/Parser/IrisParser.php
new file mode 100755
index 0000000..590da53
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Parser/IrisParser.php
@@ -0,0 +1,34 @@
+_module = $module;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \RuntimeException
+ */
+ public function parseMessage(
+ $topic,
+ $payload)
+ {
+ $data = Client::api_body_decode($payload);
+ if (!is_array($data)) {
+ throw new \RuntimeException('Invalid JSON payload.');
+ }
+
+ return [new Message($this->_module, $data)];
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Parser/RegionHintParser.php b/vendor/mgp25/instagram-php/src/Realtime/Parser/RegionHintParser.php
new file mode 100755
index 0000000..898f686
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Parser/RegionHintParser.php
@@ -0,0 +1,54 @@
+_createMessage($region)];
+ }
+
+ /**
+ * Create a message from given topic and payload.
+ *
+ * @param string $region
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ *
+ * @return Message
+ */
+ protected function _createMessage(
+ $region)
+ {
+ if ($region === null) {
+ throw new \RuntimeException('Incomplete region hint message.');
+ }
+
+ return new Message(RegionHintHandler::MODULE, $region);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Parser/SkywalkerParser.php b/vendor/mgp25/instagram-php/src/Realtime/Parser/SkywalkerParser.php
new file mode 100755
index 0000000..0746531
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Parser/SkywalkerParser.php
@@ -0,0 +1,82 @@
+ self::MODULE_DIRECT,
+ self::TOPIC_LIVE => self::MODULE_LIVE,
+ self::TOPIC_LIVEWITH => self::MODULE_LIVEWITH,
+ ];
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ */
+ public function parseMessage(
+ $topic,
+ $payload)
+ {
+ $msgTopic = $msgPayload = null;
+ new Reader($payload, function ($context, $field, $value, $type) use (&$msgTopic, &$msgPayload) {
+ if ($type === Compact::TYPE_I32 && $field === self::FIELD_TOPIC) {
+ $msgTopic = $value;
+ } elseif ($type === Compact::TYPE_BINARY && $field === self::FIELD_PAYLOAD) {
+ $msgPayload = $value;
+ }
+ });
+
+ return [$this->_createMessage($msgTopic, $msgPayload)];
+ }
+
+ /**
+ * Create a message from given topic and payload.
+ *
+ * @param int $topic
+ * @param string $payload
+ *
+ * @throws \RuntimeException
+ * @throws \DomainException
+ *
+ * @return Message
+ */
+ protected function _createMessage(
+ $topic,
+ $payload)
+ {
+ if ($topic === null || $payload === null) {
+ throw new \RuntimeException('Incomplete Skywalker message.');
+ }
+
+ if (!array_key_exists($topic, self::TOPIC_TO_MODULE_ENUM)) {
+ throw new \DomainException(sprintf('Unknown Skywalker topic "%d".', $topic));
+ }
+
+ $data = Client::api_body_decode($payload);
+ if (!is_array($data)) {
+ throw new \RuntimeException('Invalid Skywalker payload.');
+ }
+
+ return new Message(self::TOPIC_TO_MODULE_ENUM[$topic], $data);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/ParserInterface.php b/vendor/mgp25/instagram-php/src/Realtime/ParserInterface.php
new file mode 100755
index 0000000..f180332
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/ParserInterface.php
@@ -0,0 +1,19 @@
+ '',
+ 'payload' => '\InstagramAPI\Response\Model\DirectSendItemPayload',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEvent.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEvent.php
new file mode 100755
index 0000000..736355b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEvent.php
@@ -0,0 +1,44 @@
+ 'PatchEventOp[]',
+ 'message_type' => 'int',
+ 'seq_id' => 'int',
+ 'lazy' => 'bool',
+ 'num_endpoints' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEventOp.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEventOp.php
new file mode 100755
index 0000000..7b28155
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/Event/PatchEventOp.php
@@ -0,0 +1,45 @@
+ '',
+ 'path' => '',
+ 'value' => '',
+ 'ts' => '',
+ 'doublePublish' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/IrisSubscribeAck.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/IrisSubscribeAck.php
new file mode 100755
index 0000000..3c2016b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/IrisSubscribeAck.php
@@ -0,0 +1,35 @@
+ 'int',
+ 'succeeded' => 'bool',
+ 'error_type' => 'int',
+ 'error_message' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/LiveBroadcast.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/LiveBroadcast.php
new file mode 100755
index 0000000..02d7e66
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/LiveBroadcast.php
@@ -0,0 +1,40 @@
+ '\InstagramAPI\Response\Model\User',
+ 'broadcast_id' => 'string',
+ 'is_periodic' => '',
+ 'broadcast_message' => 'string',
+ 'display_notification' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeAction.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeAction.php
new file mode 100755
index 0000000..77ccf8e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeAction.php
@@ -0,0 +1,29 @@
+ 'string',
+ 'action' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeEvent.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeEvent.php
new file mode 100755
index 0000000..c0861d4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/RealtimeEvent.php
@@ -0,0 +1,27 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/StoryScreenshot.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/StoryScreenshot.php
new file mode 100755
index 0000000..4d49df3
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/StoryScreenshot.php
@@ -0,0 +1,28 @@
+ '\InstagramAPI\Response\Model\User',
+ /*
+ * A number describing what type of media this is.
+ */
+ 'media_type' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadAction.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadAction.php
new file mode 100755
index 0000000..6520222
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadAction.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'action_log' => '\InstagramAPI\Response\Model\ActionLog',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadActivity.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadActivity.php
new file mode 100755
index 0000000..5f0eafa
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/ThreadActivity.php
@@ -0,0 +1,35 @@
+ '',
+ 'sender_id' => 'string',
+ 'activity_status' => '',
+ 'ttl' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Payload/ZeroProvisionEvent.php b/vendor/mgp25/instagram-php/src/Realtime/Payload/ZeroProvisionEvent.php
new file mode 100755
index 0000000..96ea4d5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Payload/ZeroProvisionEvent.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'product_name' => 'string',
+ 'zero_provisioned_time' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/AppPresenceSubscription.php b/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/AppPresenceSubscription.php
new file mode 100755
index 0000000..c5bbc23
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/AppPresenceSubscription.php
@@ -0,0 +1,30 @@
+ $subscriptionId,
+ ]);
+ }
+
+ /** {@inheritdoc} */
+ public function getId()
+ {
+ return self::ID;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/DirectTypingSubscription.php b/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/DirectTypingSubscription.php
new file mode 100755
index 0000000..f6ad123
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/DirectTypingSubscription.php
@@ -0,0 +1,29 @@
+ $accountId,
+ ]);
+ }
+
+ /** {@inheritdoc} */
+ public function getId()
+ {
+ return 'direct_typing';
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/ZeroProvisionSubscription.php b/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/ZeroProvisionSubscription.php
new file mode 100755
index 0000000..3fa7ae5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQl/ZeroProvisionSubscription.php
@@ -0,0 +1,32 @@
+ Signatures::generateUUID(),
+ 'device_id' => $deviceId,
+ ]);
+ }
+
+ /** {@inheritdoc} */
+ public function getId()
+ {
+ return self::ID;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQlSubscription.php b/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQlSubscription.php
new file mode 100755
index 0000000..46580c7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Subscription/GraphQlSubscription.php
@@ -0,0 +1,46 @@
+_queryId = $queryId;
+ $this->_inputData = $inputData;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::REALTIME_SUB;
+ }
+
+ /** {@inheritdoc} */
+ abstract public function getId();
+
+ /** {@inheritdoc} */
+ public function __toString()
+ {
+ return sprintf(self::TEMPLATE, $this->_queryId, json_encode(['input_data' => $this->_inputData]));
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/DirectSubscription.php b/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/DirectSubscription.php
new file mode 100755
index 0000000..99eb563
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/DirectSubscription.php
@@ -0,0 +1,23 @@
+_accountId);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/LiveSubscription.php b/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/LiveSubscription.php
new file mode 100755
index 0000000..7b6fb95
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Subscription/Skywalker/LiveSubscription.php
@@ -0,0 +1,23 @@
+_accountId);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/Subscription/SkywalkerSubscription.php b/vendor/mgp25/instagram-php/src/Realtime/Subscription/SkywalkerSubscription.php
new file mode 100755
index 0000000..8cffa52
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/Subscription/SkywalkerSubscription.php
@@ -0,0 +1,35 @@
+_accountId = $accountId;
+ }
+
+ /** {@inheritdoc} */
+ public function getTopic()
+ {
+ return Mqtt\Topics::PUBSUB;
+ }
+
+ /** {@inheritdoc} */
+ abstract public function getId();
+
+ /** {@inheritdoc} */
+ abstract public function __toString();
+}
diff --git a/vendor/mgp25/instagram-php/src/Realtime/SubscriptionInterface.php b/vendor/mgp25/instagram-php/src/Realtime/SubscriptionInterface.php
new file mode 100755
index 0000000..bb90a6d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Realtime/SubscriptionInterface.php
@@ -0,0 +1,27 @@
+_parent = $parent;
+ $this->_url = $url;
+
+ // Set defaults.
+ $this->_apiVersion = 1;
+ $this->_headers = [];
+ $this->_params = [];
+ $this->_posts = [];
+ $this->_files = [];
+ $this->_handles = [];
+ $this->_guzzleOptions = [];
+ $this->_needsAuth = true;
+ $this->_signedPost = true;
+ $this->_signedGet = false;
+ $this->_isMultiResponse = false;
+ $this->_isBodyCompressed = false;
+ $this->_excludeSigned = [];
+ $this->_defaultHeaders = true;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ // Ensure that all opened handles are closed.
+ $this->_closeHandles();
+ }
+
+ /**
+ * Set API version to use.
+ *
+ * @param int $apiVersion
+ *
+ * @throws \InvalidArgumentException In case of unsupported API version.
+ *
+ * @return self
+ */
+ public function setVersion(
+ $apiVersion)
+ {
+ if (!array_key_exists($apiVersion, Constants::API_URLS)) {
+ throw new \InvalidArgumentException(sprintf('"%d" is not a supported API version.', $apiVersion));
+ }
+ $this->_apiVersion = $apiVersion;
+
+ return $this;
+ }
+
+ /**
+ * Add query param to request, overwriting any previous value.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addParam(
+ $key,
+ $value)
+ {
+ if ($value === true) {
+ $value = 'true';
+ } elseif ($value === false) {
+ $value = 'false';
+ }
+ $this->_params[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add POST param to request, overwriting any previous value.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addPost(
+ $key,
+ $value)
+ {
+ if ($value === true) {
+ $value = 'true';
+ } elseif ($value === false) {
+ $value = 'false';
+ }
+ $this->_posts[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add unsigned POST param to request, overwriting any previous value.
+ *
+ * This adds a POST value and marks it as "never sign it", even if this
+ * is a signed request. Instagram sometimes needs a few unsigned values.
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return self
+ */
+ public function addUnsignedPost(
+ $key,
+ $value)
+ {
+ $this->addPost($key, $value);
+ $this->_excludeSigned[] = $key;
+
+ return $this;
+ }
+
+ /**
+ * Add an on-disk file to a POST request, which causes this to become a multipart form request.
+ *
+ * @param string $key Form field name.
+ * @param string $filepath Path to a file.
+ * @param string|null $filename Filename to use in Content-Disposition header.
+ * @param array $headers An associative array of headers.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function addFile(
+ $key,
+ $filepath,
+ $filename = null,
+ array $headers = [])
+ {
+ // Validate
+ if (!is_file($filepath)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" does not exist.', $filepath));
+ }
+ if (!is_readable($filepath)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" is not readable.', $filepath));
+ }
+ // Inherit value from $filepath, if not supplied.
+ if ($filename === null) {
+ $filename = $filepath;
+ }
+ $filename = basename($filename);
+ // Default headers.
+ $headers = $headers + [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Transfer-Encoding' => 'binary',
+ ];
+ $this->_files[$key] = [
+ 'filepath' => $filepath,
+ 'filename' => $filename,
+ 'headers' => $headers,
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Add raw file data to a POST request, which causes this to become a multipart form request.
+ *
+ * @param string $key Form field name.
+ * @param string $data File data.
+ * @param string|null $filename Filename to use in Content-Disposition header.
+ * @param array $headers An associative array of headers.
+ *
+ * @return self
+ */
+ public function addFileData(
+ $key,
+ $data,
+ $filename,
+ array $headers = [])
+ {
+ $filename = basename($filename);
+ // Default headers.
+ $headers = $headers + [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Transfer-Encoding' => 'binary',
+ ];
+ $this->_files[$key] = [
+ 'contents' => $data,
+ 'filename' => $filename,
+ 'headers' => $headers,
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Add custom header to request, overwriting any previous or default value.
+ *
+ * The custom value will even take precedence over the default headers!
+ *
+ * WARNING: If this is called multiple times with the same header "key"
+ * name, it will only keep the LATEST value given for that specific header.
+ * It will NOT keep any of its older values, since you can only have ONE
+ * value per header! If you want multiple values in headers that support
+ * it, you must manually format them properly and send us the final string,
+ * usually by separating the value string entries with a semicolon.
+ *
+ * @param string $key
+ * @param string $value
+ *
+ * @return self
+ */
+ public function addHeader(
+ $key,
+ $value)
+ {
+ $this->_headers[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add headers used by most API requests.
+ *
+ * @return self
+ */
+ protected function _addDefaultHeaders()
+ {
+ if ($this->_defaultHeaders) {
+ $this->_headers['X-IG-App-ID'] = Constants::FACEBOOK_ANALYTICS_APPLICATION_ID;
+ $this->_headers['X-IG-Capabilities'] = Constants::X_IG_Capabilities;
+ $this->_headers['X-IG-Connection-Type'] = Constants::X_IG_Connection_Type;
+ $this->_headers['X-IG-Connection-Speed'] = mt_rand(1000, 3700).'kbps';
+ // TODO: IMPLEMENT PROPER CALCULATION OF THESE HEADERS.
+ $this->_headers['X-IG-Bandwidth-Speed-KBPS'] = '-1.000';
+ $this->_headers['X-IG-Bandwidth-TotalBytes-B'] = '0';
+ $this->_headers['X-IG-Bandwidth-TotalTime-MS'] = '0';
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the "add default headers" flag.
+ *
+ * @param bool $flag
+ *
+ * @return self
+ */
+ public function setAddDefaultHeaders(
+ $flag)
+ {
+ $this->_defaultHeaders = $flag;
+
+ return $this;
+ }
+
+ /**
+ * Set the extra Guzzle options for this request.
+ *
+ * @param array $guzzleOptions Extra Guzzle options for this request.
+ *
+ * @return self
+ */
+ public function setGuzzleOptions(
+ array $guzzleOptions)
+ {
+ $this->_guzzleOptions = $guzzleOptions;
+
+ return $this;
+ }
+
+ /**
+ * Set raw request body.
+ *
+ * @param StreamInterface $stream
+ *
+ * @return self
+ */
+ public function setBody(
+ StreamInterface $stream)
+ {
+ $this->_body = $stream;
+
+ return $this;
+ }
+
+ /**
+ * Set authorized request flag.
+ *
+ * @param bool $needsAuth
+ *
+ * @return self
+ */
+ public function setNeedsAuth(
+ $needsAuth)
+ {
+ $this->_needsAuth = $needsAuth;
+
+ return $this;
+ }
+
+ /**
+ * Set signed request data flag.
+ *
+ * @param bool $signedPost
+ *
+ * @return self
+ */
+ public function setSignedPost(
+ $signedPost = true)
+ {
+ $this->_signedPost = $signedPost;
+
+ return $this;
+ }
+
+ /**
+ * Set signed request params flag.
+ *
+ * @param bool $signedGet
+ *
+ * @return self
+ */
+ public function setSignedGet(
+ $signedGet = false)
+ {
+ $this->_signedGet = $signedGet;
+
+ return $this;
+ }
+
+ /**
+ * Set the "this API endpoint responds with multiple JSON objects" flag.
+ *
+ * @param bool $flag
+ *
+ * @return self
+ */
+ public function setIsMultiResponse(
+ $flag = false)
+ {
+ $this->_isMultiResponse = $flag;
+
+ return $this;
+ }
+
+ /**
+ * Set gz-compressed request params flag.
+ *
+ * @param bool $isBodyCompressed
+ *
+ * @return self
+ */
+ public function setIsBodyCompressed(
+ $isBodyCompressed = false)
+ {
+ $this->_isBodyCompressed = $isBodyCompressed;
+
+ if ($isBodyCompressed === true) {
+ $this->_headers['Content-Encoding'] = 'gzip';
+ } elseif (isset($this->_headers['Content-Encoding']) && $this->_headers['Content-Encoding'] === 'gzip') {
+ unset($this->_headers['Content-Encoding']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get a Stream for the given file.
+ *
+ * @param array $file
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return StreamInterface
+ */
+ protected function _getStreamForFile(
+ array $file)
+ {
+ if (isset($file['contents'])) {
+ $result = stream_for($file['contents']); // Throws.
+ } elseif (isset($file['filepath'])) {
+ $handle = fopen($file['filepath'], 'rb');
+ if ($handle === false) {
+ throw new \RuntimeException(sprintf('Could not open file "%s" for reading.', $file['filepath']));
+ }
+ $this->_handles[] = $handle;
+ $result = stream_for($handle); // Throws.
+ } else {
+ throw new \InvalidArgumentException('No data for stream creation.');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Convert the request's data into its HTTP POST multipart body contents.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return MultipartStream
+ */
+ protected function _getMultipartBody()
+ {
+ // Here is a tricky part: all form data (including files) must be ordered by hash code.
+ // So we are creating an index for building POST data.
+ $index = Utils::reorderByHashCode(array_merge($this->_posts, $this->_files));
+ // Build multipart elements using created index.
+ $elements = [];
+ foreach ($index as $key => $value) {
+ if (!isset($this->_files[$key])) {
+ $element = [
+ 'name' => $key,
+ 'contents' => $value,
+ ];
+ } else {
+ $file = $this->_files[$key];
+ $element = [
+ 'name' => $key,
+ 'contents' => $this->_getStreamForFile($file), // Throws.
+ 'filename' => isset($file['filename']) ? $file['filename'] : null,
+ 'headers' => isset($file['headers']) ? $file['headers'] : [],
+ ];
+ }
+ $elements[] = $element;
+ }
+
+ return new MultipartStream( // Throws.
+ $elements,
+ Utils::generateMultipartBoundary()
+ );
+ }
+
+ /**
+ * Close opened file handles.
+ */
+ protected function _closeHandles()
+ {
+ if (!is_array($this->_handles) || !count($this->_handles)) {
+ return;
+ }
+
+ foreach ($this->_handles as $handle) {
+ Utils::safe_fclose($handle);
+ }
+ $this->_resetHandles();
+ }
+
+ /**
+ * Reset opened handles array.
+ */
+ protected function _resetHandles()
+ {
+ $this->_handles = [];
+ }
+
+ /**
+ * Convert the request's data into its HTTP POST urlencoded body contents.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Stream
+ */
+ protected function _getUrlencodedBody()
+ {
+ $this->_headers['Content-Type'] = Constants::CONTENT_TYPE;
+
+ return stream_for( // Throws.
+ http_build_query(Utils::reorderByHashCode($this->_posts))
+ );
+ }
+
+ /**
+ * Convert the request's data into its HTTP POST body contents.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return StreamInterface|null The body stream if POST request; otherwise NULL if GET request.
+ */
+ protected function _getRequestBody()
+ {
+ // Check and return raw body stream if set.
+ if ($this->_body !== null) {
+ if ($this->_isBodyCompressed) {
+ return stream_for(zlib_encode((string) $this->_body, ZLIB_ENCODING_GZIP));
+ }
+
+ return $this->_body;
+ }
+ // We have no POST data and no files.
+ if (!count($this->_posts) && !count($this->_files)) {
+ return;
+ }
+ // Sign POST data if needed.
+ if ($this->_signedPost) {
+ $this->_posts = Signatures::signData($this->_posts, $this->_excludeSigned);
+ }
+ // Switch between multipart (at least one file) or urlencoded body.
+ if (!count($this->_files)) {
+ $result = $this->_getUrlencodedBody(); // Throws.
+ } else {
+ $result = $this->_getMultipartBody(); // Throws.
+ }
+
+ if ($this->_isBodyCompressed) {
+ return stream_for(zlib_encode((string) $result, ZLIB_ENCODING_GZIP));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Build HTTP request object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ *
+ * @return HttpRequest
+ */
+ protected function _buildHttpRequest()
+ {
+ $endpoint = $this->_url;
+ // Determine the URI to use (it's either relative to API, or a full URI).
+ if (strncmp($endpoint, 'http:', 5) !== 0 && strncmp($endpoint, 'https:', 6) !== 0) {
+ $endpoint = Constants::API_URLS[$this->_apiVersion].$endpoint;
+ }
+ // Check signed request params flag.
+ if ($this->_signedGet) {
+ $this->_params = Signatures::signData($this->_params);
+ }
+ // Generate the final endpoint URL, by adding any custom query params.
+ if (count($this->_params)) {
+ $endpoint = $endpoint
+ .(strpos($endpoint, '?') === false ? '?' : '&')
+ .http_build_query(Utils::reorderByHashCode($this->_params));
+ }
+ // Add default headers (if enabled).
+ $this->_addDefaultHeaders();
+ /** @var StreamInterface|null $postData The POST body stream; is NULL if GET request instead. */
+ $postData = $this->_getRequestBody(); // Throws.
+ // Determine request method.
+ $method = $postData !== null ? 'POST' : 'GET';
+ // Build HTTP request object.
+ return new HttpRequest( // Throws (they didn't document that properly).
+ $method,
+ $endpoint,
+ $this->_headers,
+ $postData
+ );
+ }
+
+ /**
+ * Helper which throws an error if not logged in.
+ *
+ * Remember to ALWAYS call this function at the top of any API request that
+ * requires the user to be logged in!
+ *
+ * @throws LoginRequiredException
+ */
+ protected function _throwIfNotLoggedIn()
+ {
+ // Check the cached login state. May not reflect what will happen on the
+ // server. But it's the best we can check without trying the actual request!
+ if (!$this->_parent->isMaybeLoggedIn) {
+ throw new LoginRequiredException('User not logged in. Please call login() and then try again.');
+ }
+ }
+
+ /**
+ * Perform the request and get its raw HTTP response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return HttpResponseInterface
+ */
+ public function getHttpResponse()
+ {
+ // Prevent request from sending multiple times.
+ if ($this->_httpResponse === null) {
+ if ($this->_needsAuth) {
+ // Throw if this requires authentication and we're not logged in.
+ $this->_throwIfNotLoggedIn();
+ }
+
+ $this->_resetHandles();
+
+ try {
+ $this->_httpResponse = $this->_parent->client->api( // Throws.
+ $this->_buildHttpRequest(), // Throws.
+ $this->_guzzleOptions
+ );
+ } finally {
+ $this->_closeHandles();
+ }
+ }
+
+ return $this->_httpResponse;
+ }
+
+ /**
+ * Return the raw HTTP response body.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return string
+ */
+ public function getRawResponse()
+ {
+ $httpResponse = $this->getHttpResponse(); // Throws.
+ $body = (string) $httpResponse->getBody();
+
+ // Handle API endpoints that respond with multiple JSON objects.
+ // NOTE: We simply merge all JSON objects into a single object. This
+ // text replacement of "}\r\n{" is safe, because the actual JSON data
+ // objects never contain literal newline characters (http://json.org).
+ // And if we get any duplicate properties, then PHP will simply select
+ // the latest value for that property (ex: a:1,a:2 is treated as a:2).
+ if ($this->_isMultiResponse) {
+ $body = str_replace("}\r\n{", ',', $body);
+ }
+
+ return $body;
+ }
+
+ /**
+ * Return safely JSON-decoded HTTP response.
+ *
+ * This uses a special decoder which handles 64-bit numbers correctly.
+ *
+ * @param bool $assoc When FALSE, decode to object instead of associative array.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return mixed
+ */
+ public function getDecodedResponse(
+ $assoc = true)
+ {
+ // Important: Special JSON decoder.
+ return Client::api_body_decode(
+ $this->getRawResponse(), // Throws.
+ $assoc
+ );
+ }
+
+ /**
+ * Perform the request and map its response data to the provided object.
+ *
+ * @param Response $responseObject An instance of a class object whose properties to fill with the response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws InstagramException
+ *
+ * @return Response The provided responseObject with all JSON properties filled.
+ */
+ public function getResponse(
+ Response $responseObject)
+ {
+ // Check for API response success and put its response in the object.
+ $this->_parent->client->mapServerResponse( // Throws.
+ $responseObject,
+ $this->getRawResponse(), // Throws.
+ $this->getHttpResponse() // Throws.
+ );
+
+ return $responseObject;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Account.php b/vendor/mgp25/instagram-php/src/Request/Account.php
new file mode 100755
index 0000000..8cee2aa
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Account.php
@@ -0,0 +1,779 @@
+ig->request('accounts/current_user/')
+ ->addParam('edit', true)
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Edit your biography.
+ *
+ * You are able to add `@mentions` and `#hashtags` to your biography, but
+ * be aware that Instagram disallows certain web URLs and shorteners.
+ *
+ * Also keep in mind that anyone can read your biography (even if your
+ * account is private).
+ *
+ * WARNING: Remember to also call `editProfile()` *after* using this
+ * function, so that you act like the real app!
+ *
+ * @param string $biography Biography text. Use "" for nothing.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see Account::editProfile() should be called after this function!
+ */
+ public function setBiography(
+ $biography)
+ {
+ if (!is_string($biography) || mb_strlen($biography, 'utf8') > 150) {
+ throw new \InvalidArgumentException('Please provide a 0 to 150 character string as biography.');
+ }
+
+ return $this->ig->request('accounts/set_biography/')
+ ->addPost('raw_text', $biography)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Edit your gender.
+ *
+ * WARNING: Remember to also call `editProfile()` *after* using this
+ * function, so that you act like the real app!
+ *
+ * @param string $gender this can be male, female, empty or null for 'prefer not to say' or anything else for custom
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function setGender(
+ $gender = '')
+ {
+ switch (strtolower($gender)) {
+ case 'male':$gender_id = 1; break;
+ case 'female':$gender_id = 2; break;
+ case null:
+ case '':$gender_id = 3; break;
+ default:$gender_id = 4;
+ }
+
+ return $this->ig->request('accounts/set_gender/')
+ ->setSignedPost(false)
+ ->addPost('gender', $gender_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('custom_gender', $gender_id === 4 ? $gender : '')
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Edit your profile.
+ *
+ * Warning: You must provide ALL parameters to this function. The values
+ * which you provide will overwrite all current values on your profile.
+ * You can use getCurrentUser() to see your current values first.
+ *
+ * @param string $url Website URL. Use "" for nothing.
+ * @param string $phone Phone number. Use "" for nothing.
+ * @param string $name Full name. Use "" for nothing.
+ * @param string $biography Biography text. Use "" for nothing.
+ * @param string $email Email. Required!
+ * @param int $gender Gender (1 = male, 2 = female, 3 = unknown). Required!
+ * @param string|null $newUsername (optional) Rename your account to a new username,
+ * which you've already verified with checkUsername().
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see Account::getCurrentUser() to get your current account details.
+ * @see Account::checkUsername() to verify your new username first.
+ */
+ public function editProfile(
+ $url,
+ $phone,
+ $name,
+ $biography,
+ $email,
+ $gender,
+ $newUsername = null)
+ {
+ // We must mark the profile for editing before doing the main request.
+ $userResponse = $this->ig->request('accounts/current_user/')
+ ->addParam('edit', true)
+ ->getResponse(new Response\UserInfoResponse());
+
+ // Get the current user's name from the response.
+ $currentUser = $userResponse->getUser();
+ if (!$currentUser || !is_string($currentUser->getUsername())) {
+ throw new InternalException('Unable to find current account username while preparing profile edit.');
+ }
+ $oldUsername = $currentUser->getUsername();
+
+ // Determine the desired username value.
+ $username = is_string($newUsername) && strlen($newUsername) > 0
+ ? $newUsername
+ : $oldUsername; // Keep current name.
+
+ return $this->ig->request('accounts/edit_profile/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('external_url', $url)
+ ->addPost('phone_number', $phone)
+ ->addPost('username', $username)
+ ->addPost('first_name', $name)
+ ->addPost('biography', $biography)
+ ->addPost('email', $email)
+ ->addPost('gender', $gender)
+ ->addPost('device_id', $this->ig->device_id)
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Changes your account's profile picture.
+ *
+ * @param string $photoFilename The photo filename.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function changeProfilePicture(
+ $photoFilename)
+ {
+ return $this->ig->request('accounts/change_profile_picture/')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addFile('profile_pic', $photoFilename, 'profile_pic')
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Remove your account's profile picture.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function removeProfilePicture()
+ {
+ return $this->ig->request('accounts/remove_profile_picture/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Sets your account to public.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function setPublic()
+ {
+ return $this->ig->request('accounts/set_public/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Sets your account to private.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function setPrivate()
+ {
+ return $this->ig->request('accounts/set_private/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Switches your account to business profile.
+ *
+ * In order to switch your account to Business profile you MUST
+ * call Account::setBusinessInfo().
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SwitchBusinessProfileResponse
+ *
+ * @see Account::setBusinessInfo() sets required data to become a business profile.
+ */
+ public function switchToBusinessProfile()
+ {
+ return $this->ig->request('business_conversion/get_business_convert_social_context/')
+ ->getResponse(new Response\SwitchBusinessProfileResponse());
+ }
+
+ /**
+ * Switches your account to personal profile.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SwitchPersonalProfileResponse
+ */
+ public function switchToPersonalProfile()
+ {
+ return $this->ig->request('accounts/convert_to_personal/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SwitchPersonalProfileResponse());
+ }
+
+ /**
+ * Sets contact information for business profile.
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ * @param string $email Email.
+ * @param string $categoryId TODO: Info.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateBusinessInfoResponse
+ */
+ public function setBusinessInfo(
+ $phoneNumber,
+ $email,
+ $categoryId)
+ {
+ return $this->ig->request('accounts/create_business_info/')
+ ->addPost('set_public', 'true')
+ ->addPost('entry_point', 'setting')
+ ->addPost('public_phone_contact', json_encode([
+ 'public_phone_number' => $phoneNumber,
+ 'business_contact_method' => 'CALL',
+ ]))
+ ->addPost('public_email', $email)
+ ->addPost('category_id', $categoryId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\CreateBusinessInfoResponse());
+ }
+
+ /**
+ * Check if an Instagram username is available (not already registered).
+ *
+ * Use this before trying to rename your Instagram account,
+ * to be sure that the new username is available.
+ *
+ * @param string $username Instagram username to check.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CheckUsernameResponse
+ *
+ * @see Account::editProfile() to rename your account.
+ */
+ public function checkUsername(
+ $username)
+ {
+ return $this->ig->request('users/check_username/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('username', $username)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->getResponse(new Response\CheckUsernameResponse());
+ }
+
+ /**
+ * Get account spam filter status.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterResponse
+ */
+ public function getCommentFilter()
+ {
+ return $this->ig->request('accounts/get_comment_filter/')
+ ->getResponse(new Response\CommentFilterResponse());
+ }
+
+ /**
+ * Set account spam filter status (on/off).
+ *
+ * @param int $config_value Whether spam filter is on (0 or 1).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterSetResponse
+ */
+ public function setCommentFilter(
+ $config_value)
+ {
+ return $this->ig->request('accounts/set_comment_filter/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('config_value', $config_value)
+ ->getResponse(new Response\CommentFilterSetResponse());
+ }
+
+ /**
+ * Get whether the comment category filter is disabled.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentCategoryFilterResponse
+ */
+ public function getCommentCategoryFilterDisabled()
+ {
+ return $this->ig->request('accounts/get_comment_category_filter_disabled/')
+ ->getResponse(new Response\CommentCategoryFilterResponse());
+ }
+
+ /**
+ * Get account spam filter keywords.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterKeywordsResponse
+ */
+ public function getCommentFilterKeywords()
+ {
+ return $this->ig->request('accounts/get_comment_filter_keywords/')
+ ->getResponse(new Response\CommentFilterKeywordsResponse());
+ }
+
+ /**
+ * Set account spam filter keywords.
+ *
+ * @param string $keywords List of blocked words, separated by comma.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentFilterSetResponse
+ */
+ public function setCommentFilterKeywords(
+ $keywords)
+ {
+ return $this->ig->request('accounts/set_comment_filter_keywords/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('keywords', $keywords)
+ ->getResponse(new Response\CommentFilterSetResponse());
+ }
+
+ /**
+ * Change your account's password.
+ *
+ * @param string $oldPassword Old password.
+ * @param string $newPassword New password.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ChangePasswordResponse
+ */
+ public function changePassword(
+ $oldPassword,
+ $newPassword)
+ {
+ return $this->ig->request('accounts/change_password/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('old_password', $oldPassword)
+ ->addPost('new_password1', $newPassword)
+ ->addPost('new_password2', $newPassword)
+ ->getResponse(new Response\ChangePasswordResponse());
+ }
+
+ /**
+ * Get account security info and backup codes.
+ *
+ * WARNING: STORE AND KEEP BACKUP CODES IN A SAFE PLACE. THEY ARE EXTREMELY
+ * IMPORTANT! YOU WILL GET THE CODES IN THE RESPONSE. THE BACKUP
+ * CODES LET YOU REGAIN CONTROL OF YOUR ACCOUNT IF YOU LOSE THE
+ * PHONE NUMBER! WITHOUT THE CODES, YOU RISK LOSING YOUR ACCOUNT!
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\AccountSecurityInfoResponse
+ *
+ * @see Account::enableTwoFactorSMS()
+ */
+ public function getSecurityInfo()
+ {
+ return $this->ig->request('accounts/account_security_info/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\AccountSecurityInfoResponse());
+ }
+
+ /**
+ * Request that Instagram enables two factor SMS authentication.
+ *
+ * The SMS will have a verification code for enabling two factor SMS
+ * authentication. You must then give that code to enableTwoFactorSMS().
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SendTwoFactorEnableSMSResponse
+ *
+ * @see Account::enableTwoFactorSMS()
+ */
+ public function sendTwoFactorEnableSMS(
+ $phoneNumber)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ return $this->ig->request('accounts/send_two_factor_enable_sms/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->getResponse(new Response\SendTwoFactorEnableSMSResponse());
+ }
+
+ /**
+ * Enable Two Factor authentication.
+ *
+ * WARNING: STORE AND KEEP BACKUP CODES IN A SAFE PLACE. THEY ARE EXTREMELY
+ * IMPORTANT! YOU WILL GET THE CODES IN THE RESPONSE. THE BACKUP
+ * CODES LET YOU REGAIN CONTROL OF YOUR ACCOUNT IF YOU LOSE THE
+ * PHONE NUMBER! WITHOUT THE CODES, YOU RISK LOSING YOUR ACCOUNT!
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ * @param string $verificationCode The code sent to your phone via `Account::sendTwoFactorEnableSMS()`.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\AccountSecurityInfoResponse
+ *
+ * @see Account::sendTwoFactorEnableSMS()
+ * @see Account::getSecurityInfo()
+ */
+ public function enableTwoFactorSMS(
+ $phoneNumber,
+ $verificationCode)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ $this->ig->request('accounts/enable_sms_two_factor/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->addPost('verification_code', $verificationCode)
+ ->getResponse(new Response\EnableTwoFactorSMSResponse());
+
+ return $this->getSecurityInfo();
+ }
+
+ /**
+ * Disable Two Factor authentication.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DisableTwoFactorSMSResponse
+ */
+ public function disableTwoFactorSMS()
+ {
+ return $this->ig->request('accounts/disable_sms_two_factor/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\DisableTwoFactorSMSResponse());
+ }
+
+ /**
+ * Save presence status to the storage.
+ *
+ * @param bool $disabled
+ */
+ protected function _savePresenceStatus(
+ $disabled)
+ {
+ try {
+ $this->ig->settings->set('presence_disabled', $disabled ? '1' : '0');
+ } catch (SettingsException $e) {
+ // Ignore storage errors.
+ }
+ }
+
+ /**
+ * Get presence status.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PresenceStatusResponse
+ */
+ public function getPresenceStatus()
+ {
+ /** @var Response\PresenceStatusResponse $result */
+ $result = $this->ig->request('accounts/get_presence_disabled/')
+ ->setSignedGet(true)
+ ->getResponse(new Response\PresenceStatusResponse());
+
+ $this->_savePresenceStatus($result->getDisabled());
+
+ return $result;
+ }
+
+ /**
+ * Enable presence.
+ *
+ * Allow accounts you follow and anyone you message to see when you were
+ * last active on Instagram apps.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function enablePresence()
+ {
+ /** @var Response\GenericResponse $result */
+ $result = $this->ig->request('accounts/set_presence_disabled/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('disabled', '0')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+
+ $this->_savePresenceStatus(false);
+
+ return $result;
+ }
+
+ /**
+ * Disable presence.
+ *
+ * You won't be able to see the activity status of other accounts.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function disablePresence()
+ {
+ /** @var Response\GenericResponse $result */
+ $result = $this->ig->request('accounts/set_presence_disabled/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('disabled', '1')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+
+ $this->_savePresenceStatus(true);
+
+ return $result;
+ }
+
+ /**
+ * Tell Instagram to send you a message to verify your email address.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SendConfirmEmailResponse
+ */
+ public function sendConfirmEmail()
+ {
+ return $this->ig->request('accounts/send_confirm_email/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('send_source', 'edit_profile')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SendConfirmEmailResponse());
+ }
+
+ /**
+ * Tell Instagram to send you an SMS code to verify your phone number.
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SendSMSCodeResponse
+ */
+ public function sendSMSCode(
+ $phoneNumber)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ return $this->ig->request('accounts/send_sms_code/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SendSMSCodeResponse());
+ }
+
+ /**
+ * Submit the SMS code you received to verify your phone number.
+ *
+ * @param string $phoneNumber Phone number with country code. Format: +34123456789.
+ * @param string $verificationCode The code sent to your phone via `Account::sendSMSCode()`.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\VerifySMSCodeResponse
+ *
+ * @see Account::sendSMSCode()
+ */
+ public function verifySMSCode(
+ $phoneNumber,
+ $verificationCode)
+ {
+ $cleanNumber = '+'.preg_replace('/[^0-9]/', '', $phoneNumber);
+
+ return $this->ig->request('accounts/verify_sms_code/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('phone_number', $cleanNumber)
+ ->addPost('verification_code', $verificationCode)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\VerifySMSCodeResponse());
+ }
+
+ /**
+ * Set contact point prefill.
+ *
+ * @param string $usage Either "prefill" or "auto_confirmation".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function setContactPointPrefill(
+ $usage)
+ {
+ return $this->ig->request('accounts/contact_point_prefill/')
+ ->setNeedsAuth(false)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('usage', $usage)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get account badge notifications for the "Switch account" menu.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BadgeNotificationsResponse
+ */
+ public function getBadgeNotifications()
+ {
+ return $this->ig->request('notifications/badge/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('users_ids', $this->ig->account_id)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->getResponse(new Response\BadgeNotificationsResponse());
+ }
+
+ /**
+ * TODO.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function getProcessContactPointSignals()
+ {
+ return $this->ig->request('accounts/process_contact_point_signals/')
+ ->addPost('google_tokens', '[]')
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get prefill candidates.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PrefillCandidatesResponse
+ */
+ public function getPrefillCandidates()
+ {
+ return $this->ig->request('accounts/get_prefill_candidates/')
+ ->setNeedsAuth(false)
+ ->addPost('android_device_id', $this->ig->device_id)
+ ->addPost('device_id', $this->ig->uuid)
+ ->addPost('usages', '["account_recovery_omnibox"]')
+ ->getResponse(new Response\PrefillCandidatesResponse());
+ }
+
+ /**
+ * Get details about child and main IG accounts.
+ *
+ * @param bool $useAuth Indicates if auth is required for this request
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ */
+ public function getAccountFamily(
+ $useAuth = true)
+ {
+ return $this->ig->request('multiple_accounts/get_account_family/')
+ ->getResponse(new Response\MultipleAccountFamilyResponse());
+ }
+
+ /**
+ * Get linked accounts status.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LinkageStatusResponse
+ */
+ public function getLinkageStatus()
+ {
+ return $this->ig->request('linked_accounts/get_linkage_status/')
+ ->getResponse(new Response\LinkageStatusResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Business.php b/vendor/mgp25/instagram-php/src/Request/Business.php
new file mode 100755
index 0000000..6508a52
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Business.php
@@ -0,0 +1,85 @@
+ig->request('insights/account_organic_insights/')
+ ->addParam('show_promotions_in_landing_page', 'true')
+ ->addParam('first', $day)
+ ->getResponse(new Response\InsightsResponse());
+ }
+
+ /**
+ * Get media insights.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaInsightsResponse
+ */
+ public function getMediaInsights(
+ $mediaId)
+ {
+ return $this->ig->request("insights/media_organic_insights/{$mediaId}/")
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->getResponse(new Response\MediaInsightsResponse());
+ }
+
+ /**
+ * Get account statistics.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GraphqlResponse
+ */
+ public function getStatistics()
+ {
+ return $this->ig->request('ads/graphql/')
+ ->setSignedPost(false)
+ ->setIsMultiResponse(true)
+ ->addParam('locale', Constants::USER_AGENT_LOCALE)
+ ->addParam('vc_policy', 'insights_policy')
+ ->addParam('surface', 'account')
+ ->addPost('access_token', 'undefined')
+ ->addPost('fb_api_caller_class', 'RelayModern')
+ ->addPost('variables', json_encode([
+ 'IgInsightsGridMediaImage_SIZE' => 240,
+ 'timezone' => 'Atlantic/Canary',
+ 'activityTab' => true,
+ 'audienceTab' => true,
+ 'contentTab' => true,
+ 'query_params' => json_encode([
+ 'access_token' => '',
+ 'id' => $this->ig->account_id,
+ ]),
+ ]))
+ ->addPost('doc_id', '1926322010754880')
+ ->getResponse(new Response\GraphqlResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Collection.php b/vendor/mgp25/instagram-php/src/Request/Collection.php
new file mode 100755
index 0000000..a58eaba
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Collection.php
@@ -0,0 +1,181 @@
+ig->request('collections/list/')
+ ->addParam('collection_types', '["ALL_MEDIA_AUTO_COLLECTION","MEDIA","PRODUCT_AUTO_COLLECTION"]');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\GetCollectionsListResponse());
+ }
+
+ /**
+ * Get the feed of one of your collections.
+ *
+ * @param string $collectionId The collection ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CollectionFeedResponse
+ */
+ public function getFeed(
+ $collectionId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("feed/collection/{$collectionId}/");
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\CollectionFeedResponse());
+ }
+
+ /**
+ * Create a new collection of your bookmarked (saved) media.
+ *
+ * @param string $name Name of the collection.
+ * @param string $moduleName (optional) From which app module (page) you're performing this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateCollectionResponse
+ */
+ public function create(
+ $name,
+ $moduleName = 'feed_saved_add_to_collection')
+ {
+ return $this->ig->request('collections/create/')
+ ->addPost('module_name', $moduleName)
+ ->addPost('added_media_ids', '[]')
+ ->addPost('collection_visibility', '0') //Instagram is planning for public collections soon
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('name', $name)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\CreateCollectionResponse());
+ }
+
+ /**
+ * Delete a collection.
+ *
+ * @param string $collectionId The collection ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DeleteCollectionResponse
+ */
+ public function delete(
+ $collectionId)
+ {
+ return $this->ig->request("collections/{$collectionId}/delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\DeleteCollectionResponse());
+ }
+
+ /**
+ * Edit the name of a collection or add more saved media to an existing collection.
+ *
+ * @param string $collectionId The collection ID.
+ * @param array $params User-provided key-value pairs:
+ * string 'name',
+ * string 'cover_media_id',
+ * string[] 'add_media',
+ * string 'module_name' (optional).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditCollectionResponse
+ */
+ public function edit(
+ $collectionId,
+ array $params)
+ {
+ $postData = [];
+ if (isset($params['name']) && $params['name'] !== '') {
+ $postData['name'] = $params['name'];
+ }
+ if (!empty($params['cover_media_id'])) {
+ $postData['cover_media_id'] = $params['cover_media_id'];
+ }
+ if (!empty($params['add_media']) && is_array($params['add_media'])) {
+ $postData['added_media_ids'] = json_encode(array_values($params['add_media']));
+ if (isset($params['module_name']) && $params['module_name'] !== '') {
+ $postData['module_name'] = $params['module_name'];
+ } else {
+ $postData['module_name'] = 'feed_saved_add_to_collection';
+ }
+ }
+ if (empty($postData)) {
+ throw new \InvalidArgumentException('You must provide a name or at least one media ID.');
+ }
+ $request = $this->ig->request("collections/{$collectionId}/edit/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ foreach ($postData as $key => $value) {
+ $request->addPost($key, $value);
+ }
+
+ return $request->getResponse(new Response\EditCollectionResponse());
+ }
+
+ /**
+ * Remove a single media item from one or more of your collections.
+ *
+ * Note that you can only remove a single media item per call, since this
+ * function only accepts a single media ID.
+ *
+ * @param string[] $collectionIds Array with one or more collection IDs to remove the item from.
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $moduleName (optional) From which app module (page) you're performing this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditCollectionResponse
+ */
+ public function removeMedia(
+ array $collectionIds,
+ $mediaId,
+ $moduleName = 'feed_contextual_saved_collections')
+ {
+ return $this->ig->request("media/{$mediaId}/save/")
+ ->addPost('module_name', $moduleName)
+ ->addPost('removed_collection_ids', json_encode(array_values($collectionIds)))
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\EditCollectionResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Creative.php b/vendor/mgp25/instagram-php/src/Request/Creative.php
new file mode 100755
index 0000000..6aec5b6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Creative.php
@@ -0,0 +1,123 @@
+ig->request('creatives/assets/')
+ ->addPost('type', $stickerType);
+
+ if ($location !== null) {
+ $request
+ ->addPost('lat', $location['lat'])
+ ->addPost('lng', $location['lng'])
+ ->addPost('horizontalAccuracy', $location['horizontalAccuracy']);
+ }
+
+ return $request->getResponse(new Response\StickerAssetsResponse());
+ }
+
+ /**
+ * Get face models that can be used to customize photos or videos.
+ *
+ * NOTE: The files are some strange binary format that only the Instagram
+ * app understands. If anyone figures out the format, please contact us.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FaceModelsResponse
+ */
+ public function getFaceModels()
+ {
+ return $this->ig->request('creatives/face_models/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('aml_facetracker_model_version', 12)
+ ->getResponse(new Response\FaceModelsResponse());
+ }
+
+ /**
+ * Get face overlay effects to customize photos or videos.
+ *
+ * These are effects such as "bunny ears" and similar overlays.
+ *
+ * NOTE: The files are some strange binary format that only the Instagram
+ * app understands. If anyone figures out the format, please contact us.
+ *
+ * @param array|null $location (optional) Array containing lat, lng and horizontalAccuracy.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FaceEffectsResponse
+ */
+ public function getFaceEffects(
+ array $location = null)
+ {
+ $request = $this->ig->request('creatives/face_effects/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES));
+
+ if ($location !== null) {
+ $request
+ ->addPost('lat', $location['lat'])
+ ->addPost('lng', $location['lng'])
+ ->addPost('horizontalAccuracy', $location['horizontalAccuracy']);
+ }
+
+ return $request->getResponse(new Response\FaceEffectsResponse());
+ }
+
+ /**
+ * Send supported capabilities.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\WriteSuppotedCapabilitiesResponse
+ */
+ public function sendSupportedCapabilities()
+ {
+ return $this->ig->request('creatives/write_supported_capabilities/')
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\WriteSuppotedCapabilitiesResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Direct.php b/vendor/mgp25/instagram-php/src/Request/Direct.php
new file mode 100755
index 0000000..6051a8f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Direct.php
@@ -0,0 +1,1550 @@
+ 20) {
+ throw new \InvalidArgumentException('Invalid value provided to limit.');
+ }
+ $request = $this->ig->request('direct_v2/inbox/')
+ ->addParam('persistentBadging', 'true')
+ ->addParam('visual_message_return_type', 'unseen')
+ ->addParam('limit', $limit);
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+ if ($prefetch) {
+ $request->addHeader('X-IG-Prefetch-Request', 'foreground');
+ }
+ if ($threadMessageLimit !== null) {
+ $request->addParam('thread_message_limit', $threadMessageLimit);
+ }
+
+ return $request->getResponse(new Response\DirectInboxResponse());
+ }
+
+ /**
+ * Get pending inbox data.
+ *
+ * @param string|null $cursorId Next "cursor ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectPendingInboxResponse
+ */
+ public function getPendingInbox(
+ $cursorId = null)
+ {
+ $request = $this->ig->request('direct_v2/pending_inbox/')
+ ->addParam('persistentBadging', 'true')
+ ->addParam('use_unified_inbox', 'true');
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+
+ return $request->getResponse(new Response\DirectPendingInboxResponse());
+ }
+
+ /**
+ * Approve pending threads by given identifiers.
+ *
+ * @param array $threads One or more thread identifiers.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function approvePendingThreads(
+ array $threads)
+ {
+ if (!count($threads)) {
+ throw new \InvalidArgumentException('Please provide at least one thread to approve.');
+ }
+ // Validate threads.
+ foreach ($threads as &$thread) {
+ if (!is_scalar($thread)) {
+ throw new \InvalidArgumentException('Thread identifier must be scalar.');
+ } elseif (!ctype_digit($thread) && (!is_int($thread) || $thread < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $thread));
+ }
+ $thread = (string) $thread;
+ }
+ unset($thread);
+ // Choose appropriate endpoint.
+ if (count($threads) > 1) {
+ $request = $this->ig->request('direct_v2/threads/approve_multiple/')
+ ->addPost('thread_ids', json_encode($threads));
+ } else {
+ /** @var string $thread */
+ $thread = reset($threads);
+ $request = $this->ig->request("direct_v2/threads/{$thread}/approve/");
+ }
+
+ return $request
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Decline pending threads by given identifiers.
+ *
+ * @param array $threads One or more thread identifiers.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function declinePendingThreads(
+ array $threads)
+ {
+ if (!count($threads)) {
+ throw new \InvalidArgumentException('Please provide at least one thread to decline.');
+ }
+ // Validate threads.
+ foreach ($threads as &$thread) {
+ if (!is_scalar($thread)) {
+ throw new \InvalidArgumentException('Thread identifier must be scalar.');
+ } elseif (!ctype_digit($thread) && (!is_int($thread) || $thread < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $thread));
+ }
+ $thread = (string) $thread;
+ }
+ unset($thread);
+ // Choose appropriate endpoint.
+ if (count($threads) > 1) {
+ $request = $this->ig->request('direct_v2/threads/decline_multiple/')
+ ->addPost('thread_ids', json_encode($threads));
+ } else {
+ /** @var string $thread */
+ $thread = reset($threads);
+ $request = $this->ig->request("direct_v2/threads/{$thread}/decline/");
+ }
+
+ return $request
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Decline all pending threads.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function declineAllPendingThreads()
+ {
+ return $this->ig->request('direct_v2/threads/decline_all/')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get a list of activity statuses for users who you follow or message.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PresencesResponse
+ */
+ public function getPresences()
+ {
+ return $this->ig->request('direct_v2/get_presence/')
+ ->getResponse(new Response\PresencesResponse());
+ }
+
+ /**
+ * Get ranked list of recipients.
+ *
+ * WARNING: This is a special, very heavily throttled API endpoint.
+ * Instagram REQUIRES that you wait several minutes between calls to it.
+ *
+ * @param string $mode Either "reshare" or "raven".
+ * @param bool $showThreads Whether to include existing threads into response.
+ * @param string|null $query (optional) The user to search for.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectRankedRecipientsResponse|null Will be NULL if throttled by Instagram.
+ */
+ public function getRankedRecipients(
+ $mode,
+ $showThreads,
+ $query = null)
+ {
+ try {
+ $request = $this->ig->request('direct_v2/ranked_recipients/')
+ ->addParam('mode', $mode)
+ ->addParam('show_threads', $showThreads ? 'true' : 'false')
+ ->addParam('use_unified_inbox', 'true');
+ if ($query !== null) {
+ $request->addParam('query', $query);
+ }
+
+ return $request
+ ->getResponse(new Response\DirectRankedRecipientsResponse());
+ } catch (ThrottledException $e) {
+ // Throttling is so common that we'll simply return NULL in that case.
+ return null;
+ }
+ }
+
+ /**
+ * Get a thread by the recipients list.
+ *
+ * @param string[]|int[] $users Array of numerical UserPK IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function getThreadByParticipants(
+ array $users)
+ {
+ if (!count($users)) {
+ throw new \InvalidArgumentException('Please provide at least one participant.');
+ }
+ foreach ($users as $user) {
+ if (!is_scalar($user)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ }
+ if (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
+ }
+ }
+ $request = $this->ig->request('direct_v2/threads/get_by_participants/')
+ ->addParam('recipient_users', '['.implode(',', $users).']');
+
+ return $request->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Get direct message thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|null $cursorId Next "cursor ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function getThread(
+ $threadId,
+ $cursorId = null)
+ {
+ $request = $this->ig->request("direct_v2/threads/$threadId/")
+ ->addParam('use_unified_inbox', 'true');
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+
+ return $request->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Get direct visual thread.
+ *
+ * `NOTE:` This "visual" endpoint is only used for Direct stories.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|null $cursorId Next "cursor ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectVisualThreadResponse
+ *
+ * @deprecated Visual inbox has been superseded by the unified inbox.
+ * @see Direct::getThread()
+ */
+ public function getVisualThread(
+ $threadId,
+ $cursorId = null)
+ {
+ $request = $this->ig->request("direct_v2/visual_threads/{$threadId}/");
+ if ($cursorId !== null) {
+ $request->addParam('cursor', $cursorId);
+ }
+
+ return $request->getResponse(new Response\DirectVisualThreadResponse());
+ }
+
+ /**
+ * Update thread title.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $title New title.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function updateThreadTitle(
+ $threadId,
+ $title)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/update_title/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('title', trim($title))
+ ->setSignedPost(false)
+ ->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Mute direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function muteThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/mute/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unmute direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unmuteThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/unmute/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Create a private story sharing group.
+ *
+ * NOTE: In the official app, when you create a story, you can choose to
+ * send it privately. And from there you can create a new group thread. So
+ * this group creation endpoint is only meant to be used for "direct
+ * stories" at the moment.
+ *
+ * @param string[]|int[] $userIds Array of numerical UserPK IDs.
+ * @param string $threadTitle Name of the group thread.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectCreateGroupThreadResponse
+ */
+ public function createGroupThread(
+ array $userIds,
+ $threadTitle)
+ {
+ if (count($userIds) < 2) {
+ throw new \InvalidArgumentException('You must invite at least 2 users to create a group.');
+ }
+ foreach ($userIds as &$user) {
+ if (!is_scalar($user)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ } elseif (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
+ }
+ $user = (string) $user;
+ }
+
+ $request = $this->ig->request('direct_v2/create_group_thread/')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('recipient_users', json_encode($userIds))
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('thread_title', $threadTitle);
+
+ return $request->getResponse(new Response\DirectCreateGroupThreadResponse());
+ }
+
+ /**
+ * Add users to thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string[]|int[] $users Array of numerical UserPK IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectThreadResponse
+ */
+ public function addUsersToThread(
+ $threadId,
+ array $users)
+ {
+ if (!count($users)) {
+ throw new \InvalidArgumentException('Please provide at least one user.');
+ }
+ foreach ($users as &$user) {
+ if (!is_scalar($user)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ } elseif (!ctype_digit($user) && (!is_int($user) || $user < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $user));
+ }
+ $user = (string) $user;
+ }
+
+ return $this->ig->request("direct_v2/threads/{$threadId}/add_user/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_ids', json_encode($users))
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\DirectThreadResponse());
+ }
+
+ /**
+ * Leave direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function leaveThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/leave/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Hide direct thread.
+ *
+ * @param string $threadId Thread ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function hideThread(
+ $threadId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/hide/")
+ ->addPost('use_unified_inbox', 'true')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Send a direct text message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $text Text message.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendText(
+ array $recipients,
+ $text,
+ array $options = [])
+ {
+ if (!strlen($text)) {
+ throw new \InvalidArgumentException('Text can not be empty.');
+ }
+
+ $urls = Utils::extractURLs($text);
+ if (count($urls)) {
+ /** @var Response\DirectSendItemResponse $result */
+ $result = $this->_sendDirectItem('links', $recipients, array_merge($options, [
+ 'link_urls' => json_encode(array_map(function (array $url) {
+ return $url['fullUrl'];
+ }, $urls)),
+ 'link_text' => $text,
+ ]));
+ } else {
+ /** @var Response\DirectSendItemResponse $result */
+ $result = $this->_sendDirectItem('message', $recipients, array_merge($options, [
+ 'text' => $text,
+ ]));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Share an existing media post via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of additional parameters, including:
+ * "media_type" (required) - either "photo" or "video";
+ * "client_context" (optional) - predefined UUID used to prevent double-posting;
+ * "text" (optional) - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemsResponse
+ *
+ * @see https://help.instagram.com/1209246439090858 For more information.
+ */
+ public function sendPost(
+ array $recipients,
+ $mediaId,
+ array $options = [])
+ {
+ if (!preg_match('#^\d+_\d+$#D', $mediaId)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media ID.', $mediaId));
+ }
+ if (!isset($options['media_type'])) {
+ throw new \InvalidArgumentException('Please provide media_type in options.');
+ }
+ if ($options['media_type'] !== 'photo' && $options['media_type'] !== 'video') {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media_type.', $options['media_type']));
+ }
+
+ return $this->_sendDirectItems('media_share', $recipients, array_merge($options, [
+ 'media_id' => $mediaId,
+ ]));
+ }
+
+ /**
+ * Send a photo (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $photoFilename The photo filename.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendPhoto(
+ array $recipients,
+ $photoFilename,
+ array $options = [])
+ {
+ if (!is_file($photoFilename) || !is_readable($photoFilename)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" is not available for reading.', $photoFilename));
+ }
+
+ // validate width, height and aspect ratio of photo
+ $photoDetails = new PhotoDetails($photoFilename);
+ $photoDetails->validate(ConstraintsFactory::createFor(Constants::FEED_DIRECT));
+
+ // uplaod it
+ return $this->_sendDirectItem('photo', $recipients, array_merge($options, [
+ 'filepath' => $photoFilename,
+ ]));
+ }
+
+ /**
+ * Send a disappearing photo (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $photoFilename The photo filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSinglePhoto() for available metadata fields.
+ */
+ public function sendDisappearingPhoto(
+ array $recipients,
+ $photoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_ONCE);
+
+ return $this->ig->internal->uploadSinglePhoto(Constants::FEED_DIRECT_STORY, $photoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a replayable photo (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $photoFilename The photo filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSinglePhoto() for available metadata fields.
+ */
+ public function sendReplayablePhoto(
+ array $recipients,
+ $photoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_REPLAYABLE);
+
+ return $this->ig->internal->uploadSinglePhoto(Constants::FEED_DIRECT_STORY, $photoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a video (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $videoFilename The video filename.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendVideo(
+ array $recipients,
+ $videoFilename,
+ array $options = [])
+ {
+ // Direct videos use different upload IDs.
+ $internalMetadata = new InternalMetadata(Utils::generateUploadId(true));
+ // Attempt to upload the video data.
+ $internalMetadata = $this->ig->internal->uploadVideo(Constants::FEED_DIRECT, $videoFilename, $internalMetadata);
+
+ // We must use the same client_context for all attempts to prevent double-posting.
+ if (!isset($options['client_context'])) {
+ $options['client_context'] = Signatures::generateUUID(true);
+ }
+
+ // Send the uploaded video to recipients.
+ try {
+ /** @var \InstagramAPI\Response\DirectSendItemResponse $result */
+ $result = $this->ig->internal->configureWithRetries(
+ function () use ($internalMetadata, $recipients, $options) {
+ $videoUploadResponse = $internalMetadata->getVideoUploadResponse();
+ // Attempt to configure video parameters (which sends it to the thread).
+ return $this->_sendDirectItem('video', $recipients, array_merge($options, [
+ 'upload_id' => $internalMetadata->getUploadId(),
+ 'video_result' => $videoUploadResponse !== null ? $videoUploadResponse->getResult() : '',
+ ]));
+ }
+ );
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf(
+ 'Upload of "%s" failed: %s',
+ $internalMetadata->getPhotoDetails()->getBasename(),
+ $e->getMessage()
+ ),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Send a disappearing video (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function sendDisappearingVideo(
+ array $recipients,
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_ONCE);
+
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_DIRECT_STORY, $videoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a replayable video (upload) via direct message to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function sendReplayableVideo(
+ array $recipients,
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setDirectRecipients($this->_prepareRecipients($recipients, true));
+ $internalMetadata->setStoryViewMode(Constants::STORY_VIEW_MODE_REPLAYABLE);
+
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_DIRECT_STORY, $videoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Send a like to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendLike(
+ array $recipients,
+ array $options = [])
+ {
+ return $this->_sendDirectItem('like', $recipients, $options);
+ }
+
+ /**
+ * Send a hashtag to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $hashtag Hashtag to share.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendHashtag(
+ array $recipients,
+ $hashtag,
+ array $options = [])
+ {
+ if (!strlen($hashtag)) {
+ throw new \InvalidArgumentException('Hashtag can not be empty.');
+ }
+
+ return $this->_sendDirectItem('hashtag', $recipients, array_merge($options, [
+ 'hashtag' => $hashtag,
+ ]));
+ }
+
+ /**
+ * Send a location to a user's inbox.
+ *
+ * You must provide a valid Instagram location ID, which you get via other
+ * functions such as Location::search().
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $locationId Instagram's internal ID for the location.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ *
+ * @see Location::search()
+ */
+ public function sendLocation(
+ array $recipients,
+ $locationId,
+ array $options = [])
+ {
+ if (!ctype_digit($locationId) && (!is_int($locationId) || $locationId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid location ID.', $locationId));
+ }
+
+ return $this->_sendDirectItem('location', $recipients, array_merge($options, [
+ 'venue_id' => $locationId,
+ ]));
+ }
+
+ /**
+ * Send a profile to a user's inbox.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $userId Numerical UserPK ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendProfile(
+ array $recipients,
+ $userId,
+ array $options = [])
+ {
+ if (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid numerical UserPK ID.', $userId));
+ }
+
+ return $this->_sendDirectItem('profile', $recipients, array_merge($options, [
+ 'profile_user_id' => $userId,
+ ]));
+ }
+
+ /**
+ * Send a reaction to an existing thread item.
+ *
+ * @param string $threadId Thread identifier.
+ * @param string $threadItemId ThreadItemIdentifier.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function sendReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ return $this->_handleReaction($threadId, $threadItemId, $reactionType, 'created', $options);
+ }
+
+ /**
+ * Share an existing story post via direct message to a user's inbox.
+ *
+ * You are able to share your own stories, as well as public stories from
+ * other people.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $storyId The story ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $reelId The reel ID in Instagram's internal format (ie "highlight:12970012453081168")
+ * @param array $options An associative array of additional parameters, including:
+ * "media_type" (required) - either "photo" or "video";
+ * "client_context" - predefined UUID used to prevent double-posting;
+ * "text" - text message.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemsResponse
+ *
+ * @see https://help.instagram.com/188382041703187 For more information.
+ */
+ public function sendStory(
+ array $recipients,
+ $storyId,
+ $reelId = null,
+ array $options = [])
+ {
+ if (!preg_match('#^\d+_\d+$#D', $storyId)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid story ID.', $storyId));
+ }
+ if ($reelId !== null) {
+ if (!preg_match('#^highlight:\d+$#D', $reelId)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid reel ID.', $reelId));
+ }
+ $options = array_merge($options,
+ [
+ 'reel_id' => $reelId,
+ ]);
+ }
+ if (!isset($options['media_type'])) {
+ throw new \InvalidArgumentException('Please provide media_type in options.');
+ }
+ if ($options['media_type'] !== 'photo' && $options['media_type'] !== 'video') {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media_type.', $options['media_type']));
+ }
+
+ return $this->_sendDirectItems('story_share', $recipients, array_merge($options, [
+ 'story_media_id' => $storyId,
+ ]));
+ }
+
+ /**
+ * Share an occurring or archived live stream via direct message to a user's inbox.
+ *
+ * You are able to share your own broadcasts, as well as broadcasts from
+ * other people.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response\DirectSendItemResponse
+ */
+ public function sendLive(
+ array $recipients,
+ $broadcastId,
+ array $options = [])
+ {
+ return $this->_sendDirectItem('live', $recipients, array_merge($options, [
+ 'broadcast_id' => $broadcastId,
+ ]));
+ }
+
+ /**
+ * Delete a reaction to an existing thread item.
+ *
+ * @param string $threadId Thread identifier.
+ * @param string $threadItemId ThreadItemIdentifier.
+ * @param string $reactionType One of: "like".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ public function deleteReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ array $options = [])
+ {
+ return $this->_handleReaction($threadId, $threadItemId, $reactionType, 'deleted', $options);
+ }
+
+ /**
+ * Delete an item from given thread.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread item ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function deleteItem(
+ $threadId,
+ $threadItemId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/items/{$threadItemId}/delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Marks an item from given thread as seen.
+ *
+ * @param string $threadId Thread ID.
+ * @param string $threadItemId Thread item ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSeenItemResponse
+ */
+ public function markItemSeen(
+ $threadId,
+ $threadItemId)
+ {
+ return $this->ig->request("direct_v2/threads/{$threadId}/items/{$threadItemId}/seen/")
+ ->addPost('use_unified_inbox', 'true')
+ ->addPost('action', 'mark_seen')
+ ->addPost('thread_id', $threadId)
+ ->addPost('item_id', $threadItemId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\DirectSeenItemResponse());
+ }
+
+ /**
+ * Marks visual items from given thread as seen.
+ *
+ * `NOTE:` This "visual" endpoint is only used for Direct stories.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|string[] $threadItemIds One or more thread item IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function markVisualItemsSeen(
+ $threadId,
+ $threadItemIds)
+ {
+ if (!is_array($threadItemIds)) {
+ $threadItemIds = [$threadItemIds];
+ } elseif (!count($threadItemIds)) {
+ throw new \InvalidArgumentException('Please provide at least one thread item ID.');
+ }
+
+ return $this->ig->request("direct_v2/visual_threads/{$threadId}/item_seen/")
+ ->addPost('item_ids', '['.implode(',', $threadItemIds).']')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Marks visual items from given thread as replayed.
+ *
+ * `NOTE:` This "visual" endpoint is only used for Direct stories.
+ *
+ * @param string $threadId Thread ID.
+ * @param string|string[] $threadItemIds One or more thread item IDs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function markVisualItemsReplayed(
+ $threadId,
+ $threadItemIds)
+ {
+ if (!is_array($threadItemIds)) {
+ $threadItemIds = [$threadItemIds];
+ } elseif (!count($threadItemIds)) {
+ throw new \InvalidArgumentException('Please provide at least one thread item ID.');
+ }
+
+ return $this->ig->request("direct_v2/visual_threads/{$threadId}/item_replayed/")
+ ->addPost('item_ids', '['.implode(',', $threadItemIds).']')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Validate and prepare recipients for direct messaging.
+ *
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param bool $useQuotes Whether to put IDs into quotes.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return array
+ */
+ protected function _prepareRecipients(
+ array $recipients,
+ $useQuotes)
+ {
+ $result = [];
+ // users
+ if (isset($recipients['users'])) {
+ if (!is_array($recipients['users'])) {
+ throw new \InvalidArgumentException('"users" must be an array.');
+ }
+ foreach ($recipients['users'] as $userId) {
+ if (!is_scalar($userId)) {
+ throw new \InvalidArgumentException('User identifier must be scalar.');
+ } elseif (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid user identifier.', $userId));
+ }
+ }
+ // Although this is an array of groups, you will get "Only one group is supported." error
+ // if you will try to use more than one group here.
+ // We can't use json_encode() here, because each user id must be a number.
+ $result['users'] = '[['.implode(',', $recipients['users']).']]';
+ }
+ // thread
+ if (isset($recipients['thread'])) {
+ if (!is_scalar($recipients['thread'])) {
+ throw new \InvalidArgumentException('Thread identifier must be scalar.');
+ } elseif (!ctype_digit($recipients['thread']) && (!is_int($recipients['thread']) || $recipients['thread'] < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread identifier.', $recipients['thread']));
+ }
+ // Although this is an array, you will get "Need to specify thread ID or recipient users." error
+ // if you will try to use more than one thread identifier here.
+ if (!$useQuotes) {
+ // We can't use json_encode() here, because thread id must be a number.
+ $result['thread'] = '['.$recipients['thread'].']';
+ } else {
+ // We can't use json_encode() here, because thread id must be a string.
+ $result['thread'] = '["'.$recipients['thread'].'"]';
+ }
+ }
+ if (!count($result)) {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ } elseif (isset($result['thread']) && isset($result['users'])) {
+ throw new \InvalidArgumentException('You can not mix "users" with "thread".');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Send a direct message to specific users or thread.
+ *
+ * @param string $type One of: "message", "like", "hashtag", "location", "profile", "photo",
+ * "video", "links", "live".
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param array $options Depends on $type:
+ * "message" uses "client_context" and "text";
+ * "like" uses "client_context";
+ * "hashtag" uses "client_context", "hashtag" and "text";
+ * "location" uses "client_context", "venue_id" and "text";
+ * "profile" uses "client_context", "profile_user_id" and "text";
+ * "photo" uses "client_context" and "filepath";
+ * "video" uses "client_context", "upload_id" and "video_result";
+ * "links" uses "client_context", "link_text" and "link_urls".
+ * "live" uses "client_context" and "text".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ protected function _sendDirectItem(
+ $type,
+ array $recipients,
+ array $options = [])
+ {
+ // Most requests are unsigned, but some use signing by overriding this.
+ $signedPost = false;
+
+ // Handle the request...
+ switch ($type) {
+ case 'message':
+ $request = $this->ig->request('direct_v2/threads/broadcast/text/');
+ // Check and set text.
+ if (!isset($options['text'])) {
+ throw new \InvalidArgumentException('No text message provided.');
+ }
+ $request->addPost('text', $options['text']);
+ break;
+ case 'like':
+ $request = $this->ig->request('direct_v2/threads/broadcast/like/');
+ break;
+ case 'hashtag':
+ $request = $this->ig->request('direct_v2/threads/broadcast/hashtag/');
+ // Check and set hashtag.
+ if (!isset($options['hashtag'])) {
+ throw new \InvalidArgumentException('No hashtag provided.');
+ }
+ $request->addPost('hashtag', $options['hashtag']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ case 'location':
+ $request = $this->ig->request('direct_v2/threads/broadcast/location/');
+ // Check and set venue_id.
+ if (!isset($options['venue_id'])) {
+ throw new \InvalidArgumentException('No venue_id provided.');
+ }
+ $request->addPost('venue_id', $options['venue_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ case 'profile':
+ $request = $this->ig->request('direct_v2/threads/broadcast/profile/');
+ // Check and set profile_user_id.
+ if (!isset($options['profile_user_id'])) {
+ throw new \InvalidArgumentException('No profile_user_id provided.');
+ }
+ $request->addPost('profile_user_id', $options['profile_user_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ case 'photo':
+ $request = $this->ig->request('direct_v2/threads/broadcast/upload_photo/');
+ // Check and set filepath.
+ if (!isset($options['filepath'])) {
+ throw new \InvalidArgumentException('No filepath provided.');
+ }
+ $request->addFile('photo', $options['filepath'], 'direct_temp_photo_'.Utils::generateUploadId().'.jpg');
+ break;
+ case 'video':
+ $request = $this->ig->request('direct_v2/threads/broadcast/configure_video/');
+ // Check and set upload_id.
+ if (!isset($options['upload_id'])) {
+ throw new \InvalidArgumentException('No upload_id provided.');
+ }
+ $request->addPost('upload_id', $options['upload_id']);
+ // Set video_result if provided.
+ if (isset($options['video_result'])) {
+ $request->addPost('video_result', $options['video_result']);
+ }
+ break;
+ case 'links':
+ $request = $this->ig->request('direct_v2/threads/broadcast/link/');
+ // Check and set link_urls.
+ if (!isset($options['link_urls'])) {
+ throw new \InvalidArgumentException('No link_urls provided.');
+ }
+ $request->addPost('link_urls', $options['link_urls']);
+ // Check and set link_text.
+ if (!isset($options['link_text'])) {
+ throw new \InvalidArgumentException('No link_text provided.');
+ }
+ $request->addPost('link_text', $options['link_text']);
+ break;
+ case 'reaction':
+ $request = $this->ig->request('direct_v2/threads/broadcast/reaction/');
+ // Check and set reaction_type.
+ if (!isset($options['reaction_type'])) {
+ throw new \InvalidArgumentException('No reaction_type provided.');
+ }
+ $request->addPost('reaction_type', $options['reaction_type']);
+ // Check and set reaction_status.
+ if (!isset($options['reaction_status'])) {
+ throw new \InvalidArgumentException('No reaction_status provided.');
+ }
+ $request->addPost('reaction_status', $options['reaction_status']);
+ // Check and set item_id.
+ if (!isset($options['item_id'])) {
+ throw new \InvalidArgumentException('No item_id provided.');
+ }
+ $request->addPost('item_id', $options['item_id']);
+ // Check and set node_type.
+ if (!isset($options['node_type'])) {
+ throw new \InvalidArgumentException('No node_type provided.');
+ }
+ $request->addPost('node_type', $options['node_type']);
+ break;
+ case 'live':
+ $request = $this->ig->request('direct_v2/threads/broadcast/live_viewer_invite/');
+ // Check and set broadcast id.
+ if (!isset($options['broadcast_id'])) {
+ throw new \InvalidArgumentException('No broadcast_id provided.');
+ }
+ $request->addPost('broadcast_id', $options['broadcast_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException('Unsupported _sendDirectItem() type.');
+ }
+
+ // Add recipients.
+ $recipients = $this->_prepareRecipients($recipients, false);
+ if (isset($recipients['users'])) {
+ $request->addPost('recipient_users', $recipients['users']);
+ } elseif (isset($recipients['thread'])) {
+ $request->addPost('thread_ids', $recipients['thread']);
+ } else {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ }
+
+ // Handle client_context.
+ if (!isset($options['client_context'])) {
+ // WARNING: Must be random every time otherwise we can only
+ // make a single post per direct-discussion thread.
+ $options['client_context'] = Signatures::generateUUID(true);
+ } elseif (!Signatures::isValidUUID($options['client_context'])) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
+ }
+
+ // Add some additional data if signed post.
+ if ($signedPost) {
+ $request->addPost('_uid', $this->ig->account_id);
+ }
+
+ // Execute the request with all data used by both signed and unsigned.
+ return $request->setSignedPost($signedPost)
+ ->addPost('action', 'send_item')
+ ->addPost('client_context', $options['client_context'])
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\DirectSendItemResponse());
+ }
+
+ /**
+ * Send a direct messages to specific users or thread.
+ *
+ * @param string $type One of: "media_share", "story_share".
+ * @param array $recipients An array with "users" or "thread" keys.
+ * To start a new thread, provide "users" as an array
+ * of numerical UserPK IDs. To use an existing thread
+ * instead, provide "thread" with the thread ID.
+ * @param array $options Depends on $type:
+ * "media_share" uses "client_context", "media_id", "media_type" and "text";
+ * "story_share" uses "client_context", "story_media_id", "media_type" and "text".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemsResponse
+ */
+ protected function _sendDirectItems(
+ $type,
+ array $recipients,
+ array $options = [])
+ {
+ // Most requests are unsigned, but some use signing by overriding this.
+ $signedPost = false;
+
+ // Handle the request...
+ switch ($type) {
+ case 'media_share':
+ $request = $this->ig->request('direct_v2/threads/broadcast/media_share/');
+ // Check and set media_id.
+ if (!isset($options['media_id'])) {
+ throw new \InvalidArgumentException('No media_id provided.');
+ }
+ $request->addPost('media_id', $options['media_id']);
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ // Check and set media_type.
+ if (isset($options['media_type']) && $options['media_type'] === 'video') {
+ $request->addParam('media_type', 'video');
+ } else {
+ $request->addParam('media_type', 'photo');
+ }
+ break;
+ case 'story_share':
+ $signedPost = true; // This must be a signed post!
+ $request = $this->ig->request('direct_v2/threads/broadcast/story_share/');
+ // Check and set story_media_id.
+ if (!isset($options['story_media_id'])) {
+ throw new \InvalidArgumentException('No story_media_id provided.');
+ }
+ $request->addPost('story_media_id', $options['story_media_id']);
+ // Set text if provided.
+ if (isset($options['reel_id'])) {
+ $request->addPost('reel_id', $options['reel_id']);
+ }
+ // Set text if provided.
+ if (isset($options['text']) && strlen($options['text'])) {
+ $request->addPost('text', $options['text']);
+ }
+ // Check and set media_type.
+ if (isset($options['media_type']) && $options['media_type'] === 'video') {
+ $request->addParam('media_type', 'video');
+ } else {
+ $request->addParam('media_type', 'photo');
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException('Unsupported _sendDirectItems() type.');
+ }
+
+ // Add recipients.
+ $recipients = $this->_prepareRecipients($recipients, false);
+ if (isset($recipients['users'])) {
+ $request->addPost('recipient_users', $recipients['users']);
+ } elseif (isset($recipients['thread'])) {
+ $request->addPost('thread_ids', $recipients['thread']);
+ } else {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ }
+
+ // Handle client_context.
+ if (!isset($options['client_context'])) {
+ // WARNING: Must be random every time otherwise we can only
+ // make a single post per direct-discussion thread.
+ $options['client_context'] = Signatures::generateUUID(true);
+ } elseif (!Signatures::isValidUUID($options['client_context'])) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid UUID.', $options['client_context']));
+ }
+
+ // Add some additional data if signed post.
+ if ($signedPost) {
+ $request->addPost('_uid', $this->ig->account_id);
+ }
+
+ // Execute the request with all data used by both signed and unsigned.
+ return $request->setSignedPost($signedPost)
+ ->addPost('action', 'send_item')
+ ->addPost('unified_broadcast_format', '1')
+ ->addPost('client_context', $options['client_context'])
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\DirectSendItemsResponse());
+ }
+
+ /**
+ * Handle a reaction to an existing thread item.
+ *
+ * @param string $threadId Thread identifier.
+ * @param string $threadItemId ThreadItemIdentifier.
+ * @param string $reactionType One of: "like".
+ * @param string $reactionStatus One of: "created", "deleted".
+ * @param array $options An associative array of optional parameters, including:
+ * "client_context" - predefined UUID used to prevent double-posting.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DirectSendItemResponse
+ */
+ protected function _handleReaction(
+ $threadId,
+ $threadItemId,
+ $reactionType,
+ $reactionStatus,
+ array $options = [])
+ {
+ if (!ctype_digit($threadId) && (!is_int($threadId) || $threadId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread ID.', $threadId));
+ }
+ if (!ctype_digit($threadItemId) && (!is_int($threadItemId) || $threadItemId < 0)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid thread item ID.', $threadItemId));
+ }
+ if (!in_array($reactionType, ['like'], true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a supported reaction type.', $reactionType));
+ }
+
+ return $this->_sendDirectItem('reaction', ['thread' => $threadId], array_merge($options, [
+ 'reaction_type' => $reactionType,
+ 'reaction_status' => $reactionStatus,
+ 'item_id' => $threadItemId,
+ 'node_type' => 'item',
+ ]));
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Discover.php b/vendor/mgp25/instagram-php/src/Request/Discover.php
new file mode 100755
index 0000000..e404df1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Discover.php
@@ -0,0 +1,192 @@
+ig->request('discover/topical_explore/')
+ ->addParam('is_prefetch', $isPrefetch)
+ ->addParam('omit_cover_media', true)
+ ->addParam('use_sectional_payload', true)
+ ->addParam('timezone_offset', date('Z'))
+ ->addParam('session_id', $this->ig->session_id)
+ ->addParam('include_fixed_destinations', true);
+ if ($clusterId !== null) {
+ $request->addParam('cluster_id', $clusterId);
+ }
+ if (!$isPrefetch) {
+ if ($maxId === null) {
+ $maxId = 0;
+ }
+ $request->addParam('max_id', $maxId);
+ $request->addParam('module', 'explore_popular');
+ }
+
+ return $request->getResponse(new Response\ExploreResponse());
+ }
+
+ /**
+ * Report media in the Explore-feed.
+ *
+ * @param string $exploreSourceToken Token related to the Explore media.
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReportExploreMediaResponse
+ */
+ public function reportExploreMedia(
+ $exploreSourceToken,
+ $userId)
+ {
+ return $this->ig->request('discover/explore_report/')
+ ->addParam('explore_source_token', $exploreSourceToken)
+ ->addParam('m_pk', $this->ig->account_id)
+ ->addParam('a_pk', $userId)
+ ->getResponse(new Response\ReportExploreMediaResponse());
+ }
+
+ /**
+ * Search for Instagram users, hashtags and places via Facebook's algorithm.
+ *
+ * This performs a combined search for "top results" in all 3 areas at once.
+ *
+ * @param string $query The username/full name, hashtag or location to search for.
+ * @param string $latitude (optional) Latitude.
+ * @param string $longitude (optional) Longitude.
+ * @param array $excludeList Array of grouped numerical entity IDs (ie "users" => ["4021088339"])
+ * to exclude from the response, allowing you to skip entities
+ * from a previous call to get more results. The following entities are supported:
+ * "users", "places", "tags".
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FBSearchResponse
+ *
+ * @see FBSearchResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For a rank token example (but with a different type of exclude list).
+ */
+ public function search(
+ $query,
+ $latitude = null,
+ $longitude = null,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation.
+ if (!is_string($query) || $query === '') {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+ $request = $this->_paginateWithMultiExclusion(
+ $this->ig->request('fbsearch/topsearch_flat/')
+ ->addParam('context', 'blended')
+ ->addParam('query', $query)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken
+ );
+
+ if ($latitude !== null && $longitude !== null) {
+ $request
+ ->addParam('lat', $latitude)
+ ->addParam('lng', $longitude);
+ }
+
+ try {
+ /** @var Response\FBSearchResponse $result */
+ $result = $request->getResponse(new Response\FBSearchResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\FBSearchResponse([
+ 'has_more' => false,
+ 'hashtags' => [],
+ 'users' => [],
+ 'places' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get search suggestions via Facebook's algorithm.
+ *
+ * NOTE: In the app, they're listed as the "Suggested" in the "Top" tab at the "Search" screen.
+ *
+ * @param string $type One of: "blended", "users", "hashtags" or "places".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedSearchesResponse
+ */
+ public function getSuggestedSearches(
+ $type)
+ {
+ if (!in_array($type, ['blended', 'users', 'hashtags', 'places'], true)) {
+ throw new \InvalidArgumentException(sprintf('Unknown search type: %s.', $type));
+ }
+
+ return $this->ig->request('fbsearch/suggested_searches/')
+ ->addParam('type', $type)
+ ->getResponse(new Response\SuggestedSearchesResponse());
+ }
+
+ /**
+ * Get recent searches via Facebook's algorithm.
+ *
+ * NOTE: In the app, they're listed as the "Recent" in the "Top" tab at the "Search" screen.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RecentSearchesResponse
+ */
+ public function getRecentSearches()
+ {
+ return $this->ig->request('fbsearch/recent_searches/')
+ ->getResponse(new Response\RecentSearchesResponse());
+ }
+
+ /**
+ * Clear the search history.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function clearSearchHistory()
+ {
+ return $this->ig->request('fbsearch/clear_search_history/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Hashtag.php b/vendor/mgp25/instagram-php/src/Request/Hashtag.php
new file mode 100755
index 0000000..0e702eb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Hashtag.php
@@ -0,0 +1,375 @@
+ig->request("tags/{$urlHashtag}/info/")
+ ->getResponse(new Response\TagInfoResponse());
+ }
+
+ /**
+ * Get hashtag story.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagsStoryResponse
+ */
+ public function getStory(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/{$urlHashtag}/story/")
+ ->getResponse(new Response\TagsStoryResponse());
+ }
+
+ /**
+ * Get hashtags from a section.
+ *
+ * Available tab sections: 'top', 'recent' or 'places'.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ * @param string $rankToken The feed UUID. You must use the same value for all pages of the feed.
+ * @param string|null $tab Section tab for hashtags.
+ * @param int[]|null $nextMediaIds Used for pagination.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagFeedResponse
+ */
+ public function getSection(
+ $hashtag,
+ $rankToken,
+ $tab = null,
+ $nextMediaIds = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+
+ $request = $this->ig->request("tags/{$urlHashtag}/sections/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('rank_token', $rankToken)
+ ->addPost('include_persistent', true);
+
+ if ($tab !== null) {
+ if ($tab !== 'top' && $tab !== 'recent' && $tab !== 'places' && $tab !== 'discover') {
+ throw new \InvalidArgumentException('Tab section must be \'top\', \'recent\', \'places\' or \'discover\'.');
+ }
+ $request->addPost('tab', $tab);
+ } else {
+ $request->addPost('supported_tabs', '["top","recent","places","discover"]');
+ }
+
+ if ($nextMediaIds !== null) {
+ if (!is_array($nextMediaIds) || !array_filter($nextMediaIds, 'is_int')) {
+ throw new \InvalidArgumentException('Next media IDs must be an Int[].');
+ }
+ $request->addPost('next_media_ids', json_encode($nextMediaIds));
+ }
+ if ($maxId !== null) {
+ $request->addPost('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\TagFeedResponse());
+ }
+
+ /**
+ * Search for hashtags.
+ *
+ * Gives you search results ordered by best matches first.
+ *
+ * Note that you can get more than one "page" of hashtag search results by
+ * excluding the numerical IDs of all tags from a previous search query.
+ *
+ * Also note that the excludes must be done via Instagram's internal,
+ * numerical IDs for the tags, which you can get from this search-response.
+ *
+ * Lastly, be aware that they will never exclude any tag that perfectly
+ * matches your search query, even if you provide its exact ID too.
+ *
+ * @param string $query Finds hashtags containing this string.
+ * @param string[]|int[] $excludeList Array of numerical hashtag IDs (ie "17841562498105353")
+ * to exclude from the response, allowing you to skip tags
+ * from a previous call to get more results.
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException If invalid query or
+ * trying to exclude too
+ * many hashtags.
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SearchTagResponse
+ *
+ * @see SearchTagResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function search(
+ $query,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation. Do NOT use throwIfInvalidHashtag here.
+ if (!is_string($query) || $query === '') {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+
+ $request = $this->_paginateWithExclusion(
+ $this->ig->request('tags/search/')
+ ->addParam('q', $query)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken
+ );
+
+ try {
+ /** @var Response\SearchTagResponse $result */
+ $result = $request->getResponse(new Response\SearchTagResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\SearchTagResponse([
+ 'has_more' => false,
+ 'results' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Follow hashtag.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagRelatedResponse
+ */
+ public function follow(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/follow/{$urlHashtag}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unfollow hashtag.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagRelatedResponse
+ */
+ public function unfollow(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/unfollow/{$urlHashtag}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get related hashtags.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagRelatedResponse
+ */
+ public function getRelated(
+ $hashtag)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ return $this->ig->request("tags/{$urlHashtag}/related/")
+ ->addParam('visited', '[{"id":"'.$hashtag.'","type":"hashtag"}]')
+ ->addParam('related_types', '["hashtag"]')
+ ->getResponse(new Response\TagRelatedResponse());
+ }
+
+ /**
+ * Get the feed for a hashtag.
+ *
+ * @param string $hashtag The hashtag, not including the "#".
+ * @param string $rankToken The feed UUID. You must use the same value for all pages of the feed.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TagFeedResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFeed(
+ $hashtag,
+ $rankToken,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidHashtag($hashtag);
+ Utils::throwIfInvalidRankToken($rankToken);
+ $urlHashtag = urlencode($hashtag); // Necessary for non-English chars.
+ $hashtagFeed = $this->ig->request("feed/tag/{$urlHashtag}/")
+ ->addParam('rank_token', $rankToken);
+ if ($maxId !== null) {
+ $hashtagFeed->addParam('max_id', $maxId);
+ }
+
+ return $hashtagFeed->getResponse(new Response\TagFeedResponse());
+ }
+
+ /**
+ * Get list of tags that a user is following.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HashtagsResponse
+ */
+ public function getFollowing(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/following_tags_info/")
+ ->getResponse(new Response\HashtagsResponse());
+ }
+
+ /**
+ * Get list of tags that you are following.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HashtagsResponse
+ */
+ public function getSelfFollowing()
+ {
+ return $this->getFollowing($this->ig->account_id);
+ }
+
+ /**
+ * Get list of tags that are suggested to follow to.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HashtagsResponse
+ */
+ public function getFollowSuggestions()
+ {
+ return $this->ig->request('tags/suggested/')
+ ->getResponse(new Response\HashtagsResponse());
+ }
+
+ /**
+ * Mark TagFeedResponse story media items as seen.
+ *
+ * The "story" property of a `TagFeedResponse` only gives you a list of
+ * story media. It doesn't actually mark any stories as "seen", so the
+ * user doesn't know that you've seen their story. Actually marking the
+ * story as "seen" is done via this endpoint instead. The official app
+ * calls this endpoint periodically (with 1 or more items at a time)
+ * while watching a story.
+ *
+ * This tells the user that you've seen their story, and also helps
+ * Instagram know that it shouldn't give you those seen stories again
+ * if you request the same hashtag feed multiple times.
+ *
+ * Tip: You can pass in the whole "getItems()" array from the hashtag's
+ * "story" property, to easily mark all of the TagFeedResponse's story
+ * media items as seen.
+ *
+ * @param Response\TagFeedResponse $hashtagFeed The hashtag feed response
+ * object which the story media
+ * items came from. The story
+ * items MUST belong to it.
+ * @param Response\Model\Item[] $items Array of one or more story
+ * media Items.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Story::markMediaSeen()
+ * @see Location::markStoryMediaSeen()
+ */
+ public function markStoryMediaSeen(
+ Response\TagFeedResponse $hashtagFeed,
+ array $items)
+ {
+ // Extract the Hashtag Story-Tray ID from the user's hashtag response.
+ // NOTE: This can NEVER fail if the user has properly given us the exact
+ // same hashtag response that they got the story items from!
+ $sourceId = '';
+ if ($hashtagFeed->getStory() instanceof Response\Model\StoryTray) {
+ $sourceId = $hashtagFeed->getStory()->getId();
+ }
+ if (!strlen($sourceId)) {
+ throw new \InvalidArgumentException('Your provided TagFeedResponse is invalid and does not contain any Hashtag Story-Tray ID.');
+ }
+
+ // Ensure they only gave us valid items for this hashtag response.
+ // NOTE: We validate since people cannot be trusted to use their brain.
+ $validIds = [];
+ foreach ($hashtagFeed->getStory()->getItems() as $item) {
+ $validIds[$item->getId()] = true;
+ }
+ foreach ($items as $item) {
+ // NOTE: We only check Items here. Other data is rejected by Internal.
+ if ($item instanceof Response\Model\Item && !isset($validIds[$item->getId()])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The item with ID "%s" does not belong to this TagFeedResponse.',
+ $item->getId()
+ ));
+ }
+ }
+
+ // Mark the story items as seen, with the hashtag as source ID.
+ return $this->ig->internal->markStoryMediaSeen($items, $sourceId);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Highlight.php b/vendor/mgp25/instagram-php/src/Request/Highlight.php
new file mode 100755
index 0000000..ba0109e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Highlight.php
@@ -0,0 +1,173 @@
+ig->request("highlights/{$userId}/highlights_tray/")
+ ->addParam('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addParam('phone_id', $this->ig->phone_id)
+ ->addParam('battery_level', '100')
+ ->addParam('is_charging', '1')
+ ->addParam('will_sound_on', '1')
+ ->getResponse(new Response\HighlightFeedResponse());
+ }
+
+ /**
+ * Get self highlight feed.
+ *
+ * NOTE: Sometimes, a highlight doesn't have any `items` property. Read
+ * `Highlight::getUserFeed()` for more information about what to do.
+ * Note 2: if user has a igtv post reponse will include 'tv_channel' property
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HighlightFeedResponse
+ *
+ * @see Highlight::getUserFeed()
+ * @see Story::getReelsMediaFeed() To get highlight items when they aren't included in this response.
+ */
+ public function getSelfUserFeed()
+ {
+ return $this->getUserFeed($this->ig->account_id);
+ }
+
+ /**
+ * Create a highlight reel.
+ *
+ * @param string[] $mediaIds Array with one or more media IDs in Instagram's internal format (ie ["3482384834_43294"]).
+ * @param string $title Title for the highlight.
+ * @param string|null $coverMediaId One media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $module
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateHighlightResponse
+ */
+ public function create(
+ array $mediaIds,
+ $title = 'Highlights',
+ $coverMediaId = null,
+ $module = 'self_profile')
+ {
+ if (empty($mediaIds)) {
+ throw new \InvalidArgumentException('You must provide at least one media ID.');
+ }
+ if ($coverMediaId === null) {
+ $coverMediaId = reset($mediaIds);
+ }
+ if ($title === null || $title === '') {
+ $title = 'Highlights';
+ } elseif (mb_strlen($title, 'utf8') > 16) {
+ throw new \InvalidArgumentException('Title must be between 1 and 16 characters.');
+ }
+
+ $cover = [
+ 'media_id' => $coverMediaId,
+ ];
+
+ return $this->ig->request('highlights/create_reel/')
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('source', $module)
+ ->addPost('creation_id', round(microtime(true) * 1000))
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('cover', json_encode($cover))
+ ->addPost('title', $title)
+ ->addPost('media_ids', json_encode(array_values($mediaIds)))
+ ->getResponse(new Response\CreateHighlightResponse());
+ }
+
+ /**
+ * Edit a highlight reel.
+ *
+ * @param string $highlightReelId Highlight ID, using internal format (ie "highlight:12345678901234567").
+ * @param array $params User-provided highlight key-value pairs. string 'title', string 'cover_media_id', string[] 'add_media', string[] 'remove_media'.
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\HighlightFeedResponse
+ */
+ public function edit(
+ $highlightReelId,
+ array $params,
+ $module = 'self_profile')
+ {
+ if (!isset($params['cover_media_id'])) {
+ throw new \InvalidArgumentException('You must provide one media ID for the cover.');
+ }
+ if (!isset($params['title'])) {
+ $params['title'] = 'Highlights';
+ } elseif (mb_strlen($params['title'], 'utf8') > 16) {
+ throw new \InvalidArgumentException('Title length must be between 1 and 16 characters.');
+ }
+ if (!isset($params['add_media']) || !is_array($params['add_media'])) {
+ $params['add_media'] = [];
+ }
+ if (!isset($params['remove_media']) || !is_array($params['remove_media'])) {
+ $params['remove_media'] = [];
+ }
+ $cover = [
+ 'media_id' => $params['cover_media_id'],
+ ];
+
+ return $this->ig->request("highlights/{$highlightReelId}/edit_reel/")
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('source', $module)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('title', $params['title'])
+ ->addPost('cover', json_encode($cover))
+ ->addPost('added_media_ids', json_encode(array_values($params['add_media'])))
+ ->addPost('removed_media_ids', json_encode(array_values($params['remove_media'])))
+ ->getResponse(new Response\HighlightFeedResponse());
+ }
+
+ /**
+ * Delete a highlight reel.
+ *
+ * @param string $highlightReelId Highlight ID, using internal format (ie "highlight:12345678901234567").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function delete(
+ $highlightReelId)
+ {
+ return $this->ig->request("highlights/{$highlightReelId}/delete_reel/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Internal.php b/vendor/mgp25/instagram-php/src/Request/Internal.php
new file mode 100755
index 0000000..8b5a031
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Internal.php
@@ -0,0 +1,2596 @@
+getPhotoDetails() === null) {
+ $internalMetadata->setPhotoDetails($targetFeed, $photoFilename);
+ }
+ } catch (\Exception $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Failed to get photo details: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ // Perform the upload.
+ $this->uploadPhotoData($targetFeed, $internalMetadata);
+
+ // Configure the uploaded image and attach it to our timeline/story.
+ $configure = $this->configureSinglePhoto($targetFeed, $internalMetadata, $externalMetadata);
+
+ return $configure;
+ }
+
+ /**
+ * Upload the data for a photo to Instagram.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException
+ */
+ public function uploadPhotoData(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // Make sure we disallow some feeds for this function.
+ if ($targetFeed === Constants::FEED_DIRECT) {
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Make sure we have photo details.
+ if ($internalMetadata->getPhotoDetails() === null) {
+ throw new \InvalidArgumentException('Photo details are missing from the internal metadata.');
+ }
+
+ try {
+ // Upload photo file with one of our photo uploaders.
+ if ($this->_useResumablePhotoUploader($targetFeed, $internalMetadata)) {
+ $this->_uploadResumablePhoto($targetFeed, $internalMetadata);
+ } else {
+ $internalMetadata->setPhotoUploadResponse(
+ $this->_uploadPhotoInOnePiece($targetFeed, $internalMetadata)
+ );
+ }
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf(
+ 'Upload of "%s" failed: %s',
+ $internalMetadata->getPhotoDetails()->getBasename(),
+ $e->getMessage()
+ ),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Configures parameters for a *SINGLE* uploaded photo file.
+ *
+ * WARNING TO CONTRIBUTORS: THIS IS ONLY FOR *TIMELINE* AND *STORY* -PHOTOS-.
+ * USE "configureTimelineAlbum()" FOR ALBUMS and "configureSingleVideo()" FOR VIDEOS.
+ * AND IF FUTURE INSTAGRAM FEATURES NEED CONFIGURATION AND ARE NON-TRIVIAL,
+ * GIVE THEM THEIR OWN FUNCTION LIKE WE DID WITH "configureTimelineAlbum()",
+ * TO AVOID ADDING BUGGY AND UNMAINTAINABLE SPIDERWEB CODE!
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ */
+ public function configureSinglePhoto(
+ $targetFeed,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ // Determine the target endpoint for the photo.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $endpoint = 'media/configure/';
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ case Constants::FEED_STORY:
+ $endpoint = 'media/configure_to_story/';
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Available external metadata parameters:
+ /** @var string Caption to use for the media. */
+ $captionText = isset($externalMetadata['caption']) ? $externalMetadata['caption'] : '';
+ /** @var string Accesibility caption to use for the media. */
+ $altText = isset($externalMetadata['custom_accessibility_caption']) ? $externalMetadata['custom_accessibility_caption'] : null;
+ /** @var Response\Model\Location|null A Location object describing where
+ * the media was taken. */
+ $location = (isset($externalMetadata['location'])) ? $externalMetadata['location'] : null;
+ /** @var array|null Array of story location sticker instructions. ONLY
+ * USED FOR STORY MEDIA! */
+ $locationSticker = (isset($externalMetadata['location_sticker']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['location_sticker'] : null;
+ /** @var array|null Array of usertagging instructions, in the format
+ * [['position'=>[0.5,0.5], 'user_id'=>'123'], ...]. ONLY FOR TIMELINE PHOTOS! */
+ $usertags = (isset($externalMetadata['usertags']) && $targetFeed == Constants::FEED_TIMELINE) ? $externalMetadata['usertags'] : null;
+ /** @var string|null Link to attach to the media. ONLY USED FOR STORY MEDIA,
+ * AND YOU MUST HAVE A BUSINESS INSTAGRAM ACCOUNT TO POST A STORY LINK! */
+ $link = (isset($externalMetadata['link']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['link'] : null;
+ /** @var void Photo filter. THIS DOES NOTHING! All real filters are done in the mobile app. */
+ // $filter = isset($externalMetadata['filter']) ? $externalMetadata['filter'] : null;
+ $filter = null; // COMMENTED OUT SO USERS UNDERSTAND THEY CAN'T USE THIS!
+ /** @var array Hashtags to use for the media. ONLY STORY MEDIA! */
+ $hashtags = (isset($externalMetadata['hashtags']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['hashtags'] : null;
+ /** @var array Mentions to use for the media. ONLY STORY MEDIA! */
+ $storyMentions = (isset($externalMetadata['story_mentions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_mentions'] : null;
+ /** @var array Story poll to use for the media. ONLY STORY MEDIA! */
+ $storyPoll = (isset($externalMetadata['story_polls']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_polls'] : null;
+ /** @var array Story slider to use for the media. ONLY STORY MEDIA! */
+ $storySlider = (isset($externalMetadata['story_sliders']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_sliders'] : null;
+ /** @var array Story question to use for the media. ONLY STORY MEDIA */
+ $storyQuestion = (isset($externalMetadata['story_questions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_questions'] : null;
+ /** @var array Story countdown to use for the media. ONLY STORY MEDIA */
+ $storyCountdown = (isset($externalMetadata['story_countdowns']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_countdowns'] : null;
+ /** @var array Story fundraiser to use for the media. ONLY STORY MEDIA */
+ $storyFundraisers = (isset($externalMetadata['story_fundraisers']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_fundraisers'] : null;
+ /** @var array Attached media used to share media to story feed. ONLY STORY MEDIA! */
+ $attachedMedia = (isset($externalMetadata['attached_media']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['attached_media'] : null;
+ /** @var array Product Tags to use for the media. ONLY FOR TIMELINE PHOTOS! */
+ $productTags = (isset($externalMetadata['product_tags']) && $targetFeed == Constants::FEED_TIMELINE) ? $externalMetadata['product_tags'] : null;
+
+ // Fix very bad external user-metadata values.
+ if (!is_string($captionText)) {
+ $captionText = '';
+ }
+
+ // Critically important internal library-generated metadata parameters:
+ /** @var string The ID of the entry to configure. */
+ $uploadId = $internalMetadata->getUploadId();
+ /** @var int Width of the photo. */
+ $photoWidth = $internalMetadata->getPhotoDetails()->getWidth();
+ /** @var int Height of the photo. */
+ $photoHeight = $internalMetadata->getPhotoDetails()->getHeight();
+
+ // Build the request...
+ $request = $this->ig->request($endpoint)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('edits',
+ [
+ 'crop_original_size' => [(float) $photoWidth, (float) $photoHeight],
+ 'crop_zoom' => 1.0,
+ 'crop_center' => [0.0, -0.0],
+ ])
+ ->addPost('device',
+ [
+ 'manufacturer' => $this->ig->device->getManufacturer(),
+ 'model' => $this->ig->device->getModel(),
+ 'android_version' => $this->ig->device->getAndroidVersion(),
+ 'android_release' => $this->ig->device->getAndroidRelease(),
+ ])
+ ->addPost('extra',
+ [
+ 'source_width' => $photoWidth,
+ 'source_height' => $photoHeight,
+ ]);
+
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $date = date('Y:m:d H:i:s');
+ $request
+ ->addParam('timezone_offset', date('Z'))
+ ->addPost('date_time_original', $date)
+ ->addPost('date_time_digitalized', $date)
+ ->addPost('caption', $captionText)
+ ->addPost('source_type', '4')
+ ->addPost('media_folder', 'Camera')
+ ->addPost('upload_id', $uploadId);
+
+ if ($usertags !== null) {
+ Utils::throwIfInvalidUsertags($usertags);
+ $request->addPost('usertags', json_encode($usertags));
+ }
+ if ($productTags !== null) {
+ Utils::throwIfInvalidProductTags($productTags);
+ $request->addPost('product_tags', json_encode($productTags));
+ }
+ if ($altText !== null) {
+ $request->addPost('custom_accessibility_caption', $altText);
+ }
+ break;
+ case Constants::FEED_STORY:
+ if ($internalMetadata->isBestieMedia()) {
+ $request->addPost('audience', 'besties');
+ }
+
+ $request
+ ->addPost('client_shared_at', (string) time())
+ ->addPost('source_type', '3')
+ ->addPost('configure_mode', '1')
+ ->addPost('client_timestamp', (string) (time() - mt_rand(3, 10)))
+ ->addPost('upload_id', $uploadId);
+
+ if (is_string($link) && Utils::hasValidWebURLSyntax($link)) {
+ $story_cta = '[{"links":[{"linkType": 1, "webUri":'.json_encode($link).', "androidClass": "", "package": "", "deeplinkUri": "", "callToActionTitle": "", "redirectUri": null, "leadGenFormId": "", "igUserId": "", "appInstallObjectiveInvalidationBehavior": null}]}]';
+ $request->addPost('story_cta', $story_cta);
+ }
+ if ($hashtags !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryHashtags($captionText, $hashtags);
+ $request
+ ->addPost('story_hashtags', json_encode($hashtags))
+ ->addPost('caption', $captionText)
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($locationSticker !== null && $location !== null) {
+ Utils::throwIfInvalidStoryLocationSticker($locationSticker);
+ $request
+ ->addPost('story_locations', json_encode([$locationSticker]))
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyMentions !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryMentions($storyMentions);
+ $request
+ ->addPost('reel_mentions', json_encode($storyMentions))
+ ->addPost('caption', str_replace(' ', '+', $captionText).'+')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyPoll !== null) {
+ Utils::throwIfInvalidStoryPoll($storyPoll);
+ $request
+ ->addPost('story_polls', json_encode($storyPoll))
+ ->addPost('internal_features', 'polling_sticker')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storySlider !== null) {
+ Utils::throwIfInvalidStorySlider($storySlider);
+ $request
+ ->addPost('story_sliders', json_encode($storySlider))
+ ->addPost('story_sticker_ids', 'emoji_slider_'.$storySlider[0]['emoji']);
+ }
+ if ($storyQuestion !== null) {
+ Utils::throwIfInvalidStoryQuestion($storyQuestion);
+ $request
+ ->addPost('story_questions', json_encode($storyQuestion))
+ ->addPost('story_sticker_ids', 'question_sticker_ama');
+ }
+ if ($storyCountdown !== null) {
+ Utils::throwIfInvalidStoryCountdown($storyCountdown);
+ $request
+ ->addPost('story_countdowns', json_encode($storyCountdown))
+ ->addPost('story_sticker_ids', 'countdown_sticker_time');
+ }
+ if ($storyFundraisers !== null) {
+ $request
+ ->addPost('story_fundraisers', json_encode($storyFundraisers))
+ ->addPost('story_sticker_ids', 'fundraiser_sticker_id');
+ }
+ if ($attachedMedia !== null) {
+ Utils::throwIfInvalidAttachedMedia($attachedMedia);
+ $request
+ ->addPost('attached_media', json_encode($attachedMedia))
+ ->addPost('story_sticker_ids', 'media_simple_'.reset($attachedMedia)['media_id']);
+ }
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $request
+ ->addPost('recipient_users', $internalMetadata->getDirectUsers());
+
+ if ($internalMetadata->getStoryViewMode() !== null) {
+ $request->addPost('view_mode', $internalMetadata->getStoryViewMode());
+ }
+
+ $request
+ ->addPost('thread_ids', $internalMetadata->getDirectThreads())
+ ->addPost('client_shared_at', (string) time())
+ ->addPost('source_type', '3')
+ ->addPost('configure_mode', '2')
+ ->addPost('client_timestamp', (string) (time() - mt_rand(3, 10)))
+ ->addPost('upload_id', $uploadId);
+ break;
+ }
+
+ if ($location instanceof Response\Model\Location) {
+ if ($targetFeed === Constants::FEED_TIMELINE) {
+ $request->addPost('location', Utils::buildMediaLocationJSON($location));
+ }
+ if ($targetFeed === Constants::FEED_STORY && $locationSticker === null) {
+ throw new \InvalidArgumentException('You must provide a location_sticker together with your story location.');
+ }
+ $request
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $location->getLat())
+ ->addPost('posting_longitude', $location->getLng())
+ ->addPost('media_latitude', $location->getLat())
+ ->addPost('media_longitude', $location->getLng());
+ }
+
+ $configure = $request->getResponse(new Response\ConfigureResponse());
+
+ return $configure;
+ }
+
+ /**
+ * Uploads a raw video file.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $videoFilename The video filename.
+ * @param InternalMetadata|null $internalMetadata (optional) Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return InternalMetadata Updated internal metadata object.
+ */
+ public function uploadVideo(
+ $targetFeed,
+ $videoFilename,
+ InternalMetadata $internalMetadata = null)
+ {
+ if ($internalMetadata === null) {
+ $internalMetadata = new InternalMetadata();
+ }
+
+ try {
+ if ($internalMetadata->getVideoDetails() === null) {
+ $internalMetadata->setVideoDetails($targetFeed, $videoFilename);
+ }
+ } catch (\Exception $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Failed to get photo details: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ try {
+ if ($this->_useSegmentedVideoUploader($targetFeed, $internalMetadata)) {
+ $this->_uploadSegmentedVideo($targetFeed, $internalMetadata);
+ } elseif ($this->_useResumableVideoUploader($targetFeed, $internalMetadata)) {
+ $this->_uploadResumableVideo($targetFeed, $internalMetadata);
+ } else {
+ // Request parameters for uploading a new video.
+ $internalMetadata->setVideoUploadUrls($this->_requestVideoUploadURL($targetFeed, $internalMetadata));
+
+ // Attempt to upload the video data.
+ $internalMetadata->setVideoUploadResponse($this->_uploadVideoChunks($targetFeed, $internalMetadata));
+ }
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of "%s" failed: %s', basename($videoFilename), $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $internalMetadata;
+ }
+
+ /**
+ * UPLOADS A *SINGLE* VIDEO.
+ *
+ * This is NOT used for albums!
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $videoFilename The video filename.
+ * @param InternalMetadata|null $internalMetadata (optional) Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadSingleVideo(
+ $targetFeed,
+ $videoFilename,
+ InternalMetadata $internalMetadata = null,
+ array $externalMetadata = [])
+ {
+ // Make sure we only allow these particular feeds for this function.
+ if ($targetFeed !== Constants::FEED_TIMELINE
+ && $targetFeed !== Constants::FEED_STORY
+ && $targetFeed !== Constants::FEED_DIRECT_STORY
+ && $targetFeed !== Constants::FEED_TV
+ ) {
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Attempt to upload the video.
+ $internalMetadata = $this->uploadVideo($targetFeed, $videoFilename, $internalMetadata);
+
+ // Attempt to upload the thumbnail, associated with our video's ID.
+ $this->uploadVideoThumbnail($targetFeed, $internalMetadata, $externalMetadata);
+
+ // Configure the uploaded video and attach it to our timeline/story.
+ try {
+ /** @var \InstagramAPI\Response\ConfigureResponse $configure */
+ $configure = $this->ig->internal->configureWithRetries(
+ function () use ($targetFeed, $internalMetadata, $externalMetadata) {
+ // Attempt to configure video parameters.
+ return $this->configureSingleVideo($targetFeed, $internalMetadata, $externalMetadata);
+ }
+ );
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of "%s" failed: %s', basename($videoFilename), $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $configure;
+ }
+
+ /**
+ * Performs a resumable upload of a photo file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException
+ */
+ public function uploadVideoThumbnail(
+ $targetFeed,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ if ($internalMetadata->getVideoDetails() === null) {
+ throw new \InvalidArgumentException('Video details are missing from the internal metadata.');
+ }
+
+ try {
+ // Automatically crop&resize the thumbnail to Instagram's requirements.
+ $options = ['targetFeed' => $targetFeed];
+ if (isset($externalMetadata['thumbnail_timestamp'])) {
+ $options['thumbnailTimestamp'] = $externalMetadata['thumbnail_timestamp'];
+ }
+ $videoThumbnail = new InstagramThumbnail(
+ $internalMetadata->getVideoDetails()->getFilename(),
+ $options
+ );
+ // Validate and upload the thumbnail.
+ $internalMetadata->setPhotoDetails($targetFeed, $videoThumbnail->getFile());
+ $this->uploadPhotoData($targetFeed, $internalMetadata);
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of video thumbnail failed: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Asks Instagram for parameters for uploading a new video.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException If the request fails.
+ *
+ * @return \InstagramAPI\Response\UploadJobVideoResponse
+ */
+ protected function _requestVideoUploadURL(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $request = $this->ig->request('upload/video/')
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid);
+
+ foreach ($this->_getVideoUploadParams($targetFeed, $internalMetadata) as $key => $value) {
+ $request->addPost($key, $value);
+ }
+
+ // Perform the "pre-upload" API request.
+ /** @var Response\UploadJobVideoResponse $response */
+ $response = $request->getResponse(new Response\UploadJobVideoResponse());
+
+ return $response;
+ }
+
+ /**
+ * Configures parameters for a *SINGLE* uploaded video file.
+ *
+ * WARNING TO CONTRIBUTORS: THIS IS ONLY FOR *TIMELINE* AND *STORY* -VIDEOS-.
+ * USE "configureTimelineAlbum()" FOR ALBUMS and "configureSinglePhoto()" FOR PHOTOS.
+ * AND IF FUTURE INSTAGRAM FEATURES NEED CONFIGURATION AND ARE NON-TRIVIAL,
+ * GIVE THEM THEIR OWN FUNCTION LIKE WE DID WITH "configureTimelineAlbum()",
+ * TO AVOID ADDING BUGGY AND UNMAINTAINABLE SPIDERWEB CODE!
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ */
+ public function configureSingleVideo(
+ $targetFeed,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ // Determine the target endpoint for the video.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $endpoint = 'media/configure/';
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ case Constants::FEED_STORY:
+ $endpoint = 'media/configure_to_story/';
+ break;
+ case Constants::FEED_TV:
+ $endpoint = 'media/configure_to_igtv/';
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Bad target feed "%s".', $targetFeed));
+ }
+
+ // Available external metadata parameters:
+ /** @var string Caption to use for the media. */
+ $captionText = isset($externalMetadata['caption']) ? $externalMetadata['caption'] : '';
+ /** @var string[]|null Array of numerical UserPK IDs of people tagged in
+ * your video. ONLY USED IN STORY VIDEOS! TODO: Actually, it's not even
+ * implemented for stories. */
+ $usertags = (isset($externalMetadata['usertags'])) ? $externalMetadata['usertags'] : null;
+ /** @var Response\Model\Location|null A Location object describing where
+ * the media was taken. */
+ $location = (isset($externalMetadata['location'])) ? $externalMetadata['location'] : null;
+ /** @var array|null Array of story location sticker instructions. ONLY
+ * USED FOR STORY MEDIA! */
+ $locationSticker = (isset($externalMetadata['location_sticker']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['location_sticker'] : null;
+ /** @var string|null Link to attach to the media. ONLY USED FOR STORY MEDIA,
+ * AND YOU MUST HAVE A BUSINESS INSTAGRAM ACCOUNT TO POST A STORY LINK! */
+ $link = (isset($externalMetadata['link']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['link'] : null;
+ /** @var array Hashtags to use for the media. ONLY STORY MEDIA! */
+ $hashtags = (isset($externalMetadata['hashtags']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['hashtags'] : null;
+ /** @var array Mentions to use for the media. ONLY STORY MEDIA! */
+ $storyMentions = (isset($externalMetadata['story_mentions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_mentions'] : null;
+ /** @var array Story poll to use for the media. ONLY STORY MEDIA! */
+ $storyPoll = (isset($externalMetadata['story_polls']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_polls'] : null;
+ /** @var array Attached media used to share media to story feed. ONLY STORY MEDIA! */
+ $storySlider = (isset($externalMetadata['story_sliders']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_sliders'] : null;
+ /** @var array Story question to use for the media. ONLY STORY MEDIA */
+ $storyQuestion = (isset($externalMetadata['story_questions']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_questions'] : null;
+ /** @var array Story countdown to use for the media. ONLY STORY MEDIA */
+ $storyCountdown = (isset($externalMetadata['story_countdowns']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_countdowns'] : null;
+ /** @var array Story fundraiser to use for the media. ONLY STORY MEDIA */
+ $storyFundraisers = (isset($externalMetadata['story_fundraisers']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['story_fundraisers'] : null;
+ /** @var array Attached media used to share media to story feed. ONLY STORY MEDIA! */
+ $attachedMedia = (isset($externalMetadata['attached_media']) && $targetFeed == Constants::FEED_STORY) ? $externalMetadata['attached_media'] : null;
+ /** @var array Title of the media uploaded to your channel. ONLY TV MEDIA! */
+ $title = (isset($externalMetadata['title']) && $targetFeed == Constants::FEED_TV) ? $externalMetadata['title'] : null;
+ /** @var bool Whether or not a preview should be posted to your feed. ONLY TV MEDIA! */
+ $shareToFeed = (isset($externalMetadata['share_to_feed']) && $targetFeed == Constants::FEED_TV) ? $externalMetadata['share_to_feed'] : false;
+
+ // Fix very bad external user-metadata values.
+ if (!is_string($captionText)) {
+ $captionText = '';
+ }
+
+ $uploadId = $internalMetadata->getUploadId();
+ $videoDetails = $internalMetadata->getVideoDetails();
+
+ // Build the request...
+ $request = $this->ig->request($endpoint)
+ ->addParam('video', 1)
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('video_result', $internalMetadata->getVideoUploadResponse() !== null ? (string) $internalMetadata->getVideoUploadResponse()->getResult() : '')
+ ->addPost('upload_id', $uploadId)
+ ->addPost('poster_frame_index', 0)
+ ->addPost('length', round($videoDetails->getDuration(), 1))
+ ->addPost('audio_muted', $videoDetails->getAudioCodec() === null)
+ ->addPost('filter_type', 0)
+ ->addPost('source_type', 4)
+ ->addPost('device',
+ [
+ 'manufacturer' => $this->ig->device->getManufacturer(),
+ 'model' => $this->ig->device->getModel(),
+ 'android_version' => $this->ig->device->getAndroidVersion(),
+ 'android_release' => $this->ig->device->getAndroidRelease(),
+ ])
+ ->addPost('extra',
+ [
+ 'source_width' => $videoDetails->getWidth(),
+ 'source_height' => $videoDetails->getHeight(),
+ ])
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id);
+
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $request->addPost('caption', $captionText);
+ if ($usertags !== null) {
+ $in = [];
+ foreach ($usertags as $userId) {
+ $in[] = ['user_id' => $userId];
+ }
+ $request->addPost('usertags', $in);
+ }
+ break;
+ case Constants::FEED_STORY:
+ if ($internalMetadata->isBestieMedia()) {
+ $request->addPost('audience', 'besties');
+ }
+
+ $request
+ ->addPost('configure_mode', 1) // 1 - REEL_SHARE
+ ->addPost('story_media_creation_date', time() - mt_rand(10, 20))
+ ->addPost('client_shared_at', time() - mt_rand(3, 10))
+ ->addPost('client_timestamp', time());
+
+ if (is_string($link) && Utils::hasValidWebURLSyntax($link)) {
+ $story_cta = '[{"links":[{"linkType": 1, "webUri":'.json_encode($link).', "androidClass": "", "package": "", "deeplinkUri": "", "callToActionTitle": "", "redirectUri": null, "leadGenFormId": "", "igUserId": "", "appInstallObjectiveInvalidationBehavior": null}]}]';
+ $request->addPost('story_cta', $story_cta);
+ }
+ if ($hashtags !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryHashtags($captionText, $hashtags);
+ $request
+ ->addPost('story_hashtags', json_encode($hashtags))
+ ->addPost('caption', $captionText)
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($locationSticker !== null && $location !== null) {
+ Utils::throwIfInvalidStoryLocationSticker($locationSticker);
+ $request
+ ->addPost('story_locations', json_encode([$locationSticker]))
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyMentions !== null && $captionText !== '') {
+ Utils::throwIfInvalidStoryMentions($storyMentions);
+ $request
+ ->addPost('reel_mentions', json_encode($storyMentions))
+ ->addPost('caption', str_replace(' ', '+', $captionText).'+')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storyPoll !== null) {
+ Utils::throwIfInvalidStoryPoll($storyPoll);
+ $request
+ ->addPost('story_polls', json_encode($storyPoll))
+ ->addPost('internal_features', 'polling_sticker')
+ ->addPost('mas_opt_in', 'NOT_PROMPTED');
+ }
+ if ($storySlider !== null) {
+ Utils::throwIfInvalidStorySlider($storySlider);
+ $request
+ ->addPost('story_sliders', json_encode($storySlider))
+ ->addPost('story_sticker_ids', 'emoji_slider_'.$storySlider[0]['emoji']);
+ }
+ if ($storyQuestion !== null) {
+ Utils::throwIfInvalidStoryQuestion($storyQuestion);
+ $request
+ ->addPost('story_questions', json_encode($storyQuestion))
+ ->addPost('story_sticker_ids', 'question_sticker_ama');
+ }
+ if ($storyCountdown !== null) {
+ Utils::throwIfInvalidStoryCountdown($storyCountdown);
+ $request
+ ->addPost('story_countdowns', json_encode($storyCountdown))
+ ->addPost('story_sticker_ids', 'countdown_sticker_time');
+ }
+ if ($storyFundraisers !== null) {
+ $request
+ ->addPost('story_fundraisers', json_encode($storyFundraisers))
+ ->addPost('story_sticker_ids', 'fundraiser_sticker_id');
+ }
+ if ($attachedMedia !== null) {
+ Utils::throwIfInvalidAttachedMedia($attachedMedia);
+ $request
+ ->addPost('attached_media', json_encode($attachedMedia))
+ ->addPost('story_sticker_ids', 'media_simple_'.reset($attachedMedia)['media_id']);
+ }
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $request
+ ->addPost('configure_mode', 2) // 2 - DIRECT_STORY_SHARE
+ ->addPost('recipient_users', $internalMetadata->getDirectUsers());
+
+ if ($internalMetadata->getStoryViewMode() !== null) {
+ $request->addPost('view_mode', $internalMetadata->getStoryViewMode());
+ }
+
+ $request
+ ->addPost('thread_ids', $internalMetadata->getDirectThreads())
+ ->addPost('story_media_creation_date', time() - mt_rand(10, 20))
+ ->addPost('client_shared_at', time() - mt_rand(3, 10))
+ ->addPost('client_timestamp', time());
+ break;
+ case Constants::FEED_TV:
+ if ($title === null) {
+ throw new \InvalidArgumentException('You must provide a title for the media.');
+ }
+ if ($shareToFeed) {
+ if ($internalMetadata->getVideoDetails()->getDurationInMsec() < 60000) {
+ throw new \InvalidArgumentException('Your media must be at least a minute long to preview to feed.');
+ }
+ $request->addPost('igtv_share_preview_to_feed', '1');
+ }
+ $request
+ ->addPost('title', $title)
+ ->addPost('caption', $captionText);
+ break;
+ }
+
+ if ($targetFeed == Constants::FEED_STORY) {
+ $request->addPost('story_media_creation_date', time());
+ if ($usertags !== null) {
+ // Reel Mention example:
+ // [{\"y\":0.3407772676161919,\"rotation\":0,\"user_id\":\"USER_ID\",\"x\":0.39892578125,\"width\":0.5619921875,\"height\":0.06011525487256372}]
+ // NOTE: The backslashes are just double JSON encoding, ignore
+ // that and just give us an array with these clean values, don't
+ // try to encode it in any way, we do all encoding to match the above.
+ // This post field will get wrapped in another json_encode call during transfer.
+ $request->addPost('reel_mentions', json_encode($usertags));
+ }
+ }
+
+ if ($location instanceof Response\Model\Location) {
+ if ($targetFeed === Constants::FEED_TIMELINE) {
+ $request->addPost('location', Utils::buildMediaLocationJSON($location));
+ }
+ if ($targetFeed === Constants::FEED_STORY && $locationSticker === null) {
+ throw new \InvalidArgumentException('You must provide a location_sticker together with your story location.');
+ }
+ $request
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $location->getLat())
+ ->addPost('posting_longitude', $location->getLng())
+ ->addPost('media_latitude', $location->getLat())
+ ->addPost('media_longitude', $location->getLng());
+ }
+
+ $configure = $request->getResponse(new Response\ConfigureResponse());
+
+ return $configure;
+ }
+
+ /**
+ * Configures parameters for a whole album of uploaded media files.
+ *
+ * WARNING TO CONTRIBUTORS: THIS IS ONLY FOR *TIMELINE ALBUMS*. DO NOT MAKE
+ * IT DO ANYTHING ELSE, TO AVOID ADDING BUGGY AND UNMAINTAINABLE SPIDERWEB
+ * CODE!
+ *
+ * @param array $media Extended media array coming from Timeline::uploadAlbum(),
+ * containing the user's per-file metadata,
+ * and internally generated per-file metadata.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object for the album itself.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs
+ * for the album itself (its caption, location, etc).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ */
+ public function configureTimelineAlbum(
+ array $media,
+ InternalMetadata $internalMetadata,
+ array $externalMetadata = [])
+ {
+ $endpoint = 'media/configure_sidecar/';
+
+ $albumUploadId = $internalMetadata->getUploadId();
+
+ // Available external metadata parameters:
+ /** @var string Caption to use for the album. */
+ $captionText = isset($externalMetadata['caption']) ? $externalMetadata['caption'] : '';
+ /** @var Response\Model\Location|null A Location object describing where
+ * the album was taken. */
+ $location = isset($externalMetadata['location']) ? $externalMetadata['location'] : null;
+
+ // Fix very bad external user-metadata values.
+ if (!is_string($captionText)) {
+ $captionText = '';
+ }
+
+ // Build the album's per-children metadata.
+ $date = date('Y:m:d H:i:s');
+ $childrenMetadata = [];
+ foreach ($media as $item) {
+ /** @var InternalMetadata $itemInternalMetadata */
+ $itemInternalMetadata = $item['internalMetadata'];
+ // Get all of the common, INTERNAL per-file metadata.
+ $uploadId = $itemInternalMetadata->getUploadId();
+
+ switch ($item['type']) {
+ case 'photo':
+ // Build this item's configuration.
+ $photoConfig = [
+ 'date_time_original' => $date,
+ 'scene_type' => 1,
+ 'disable_comments' => false,
+ 'upload_id' => $uploadId,
+ 'source_type' => 0,
+ 'scene_capture_type' => 'standard',
+ 'date_time_digitized' => $date,
+ 'geotag_enabled' => false,
+ 'camera_position' => 'back',
+ 'edits' => [
+ 'filter_strength' => 1,
+ 'filter_name' => 'IGNormalFilter',
+ ],
+ ];
+
+ if (isset($item['usertags'])) {
+ // NOTE: These usertags were validated in Timeline::uploadAlbum.
+ $photoConfig['usertags'] = json_encode(['in' => $item['usertags']]);
+ }
+
+ $childrenMetadata[] = $photoConfig;
+ break;
+ case 'video':
+ // Get all of the INTERNAL per-VIDEO metadata.
+ $videoDetails = $itemInternalMetadata->getVideoDetails();
+
+ // Build this item's configuration.
+ $videoConfig = [
+ 'length' => round($videoDetails->getDuration(), 1),
+ 'date_time_original' => $date,
+ 'scene_type' => 1,
+ 'poster_frame_index' => 0,
+ 'trim_type' => 0,
+ 'disable_comments' => false,
+ 'upload_id' => $uploadId,
+ 'source_type' => 'library',
+ 'geotag_enabled' => false,
+ 'edits' => [
+ 'length' => round($videoDetails->getDuration(), 1),
+ 'cinema' => 'unsupported',
+ 'original_length' => round($videoDetails->getDuration(), 1),
+ 'source_type' => 'library',
+ 'start_time' => 0,
+ 'camera_position' => 'unknown',
+ 'trim_type' => 0,
+ ],
+ ];
+
+ if (isset($item['usertags'])) {
+ // NOTE: These usertags were validated in Timeline::uploadAlbum.
+ $videoConfig['usertags'] = json_encode(['in' => $item['usertags']]);
+ }
+
+ $childrenMetadata[] = $videoConfig;
+ break;
+ }
+ }
+
+ // Build the request...
+ $request = $this->ig->request($endpoint)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('client_sidecar_id', $albumUploadId)
+ ->addPost('caption', $captionText)
+ ->addPost('children_metadata', $childrenMetadata);
+
+ if ($location instanceof Response\Model\Location) {
+ $request
+ ->addPost('location', Utils::buildMediaLocationJSON($location))
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $location->getLat())
+ ->addPost('posting_longitude', $location->getLng())
+ ->addPost('media_latitude', $location->getLat())
+ ->addPost('media_longitude', $location->getLng())
+ ->addPost('exif_latitude', 0.0)
+ ->addPost('exif_longitude', 0.0);
+ }
+
+ $configure = $request->getResponse(new Response\ConfigureResponse());
+
+ return $configure;
+ }
+
+ /**
+ * Saves active experiments.
+ *
+ * @param Response\SyncResponse $syncResponse
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _saveExperiments(
+ Response\SyncResponse $syncResponse)
+ {
+ $experiments = [];
+ foreach ($syncResponse->getExperiments() as $experiment) {
+ $group = $experiment->getName();
+ $params = $experiment->getParams();
+
+ if ($group === null || $params === null) {
+ continue;
+ }
+
+ if (!isset($experiments[$group])) {
+ $experiments[$group] = [];
+ }
+
+ foreach ($params as $param) {
+ $paramName = $param->getName();
+ if ($paramName === null) {
+ continue;
+ }
+
+ $experiments[$group][$paramName] = $param->getValue();
+ }
+ }
+
+ // Save the experiments and the last time we refreshed them.
+ $this->ig->experiments = $this->ig->settings->setExperiments($experiments);
+ $this->ig->settings->set('last_experiments', time());
+ }
+
+ /**
+ * Perform an Instagram "feature synchronization" call for device.
+ *
+ * @param bool $prelogin
+ * @param bool $useCsrfToken
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SyncResponse
+ */
+ public function syncDeviceFeatures(
+ $prelogin = false,
+ $useCsrfToken = false)
+ {
+ $request = $this->ig->request('qe/sync/')
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addPost('id', $this->ig->uuid)
+ ->addPost('experiments', Constants::LOGIN_EXPERIMENTS);
+ if ($useCsrfToken) {
+ $request->addPost('_csrftoken', $this->ig->client->getToken());
+ }
+ if ($prelogin) {
+ $request->setNeedsAuth(false);
+ } else {
+ $request
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id);
+ }
+
+ return $request->getResponse(new Response\SyncResponse());
+ }
+
+ /**
+ * Perform an Instagram "feature synchronization" call for account.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SyncResponse
+ */
+ public function syncUserFeatures()
+ {
+ $result = $this->ig->request('qe/sync/')
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('id', $this->ig->account_id)
+ ->addPost('experiments', Constants::EXPERIMENTS)
+ ->getResponse(new Response\SyncResponse());
+
+ // Save the updated experiments for this user.
+ $this->_saveExperiments($result);
+
+ return $result;
+ }
+
+ /**
+ * Send launcher sync.
+ *
+ * @param bool $prelogin Indicates if the request is done before login request.
+ * @param bool $idIsUuid Indicates if the id parameter is the user's id.
+ * @param bool $useCsrfToken Indicates if a csrf token should be included.
+ * @param bool $loginConfigs Indicates if login configs should be used.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LauncherSyncResponse
+ */
+ public function sendLauncherSync(
+ $prelogin,
+ $idIsUuid = true,
+ $useCsrfToken = false,
+ $loginConfigs = false)
+ {
+ $request = $this->ig->request('launcher/sync/')
+ ->addPost('configs', $loginConfigs ? Constants::LAUNCHER_LOGIN_CONFIGS : Constants::LAUNCHER_CONFIGS)
+ ->addPost('id', ($idIsUuid ? $this->ig->uuid : $this->ig->account_id));
+ if ($useCsrfToken) {
+ $request->addPost('_csrftoken', $this->ig->client->getToken());
+ }
+ if ($prelogin) {
+ $request->setNeedsAuth(false);
+ } else {
+ $request
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id);
+ }
+
+ return $request->getResponse(new Response\LauncherSyncResponse());
+ }
+
+ /**
+ * Get decisions about device capabilities.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CapabilitiesDecisionsResponse
+ */
+ public function getDeviceCapabilitiesDecisions()
+ {
+ return $this->ig->request('device_capabilities/decisions/')
+ ->addParam('signed_body', Signatures::generateSignature(json_encode((object) []).'.{}'))
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->getResponse(new Response\CapabilitiesDecisionsResponse());
+ }
+
+ /**
+ * Registers advertising identifier.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function logAttribution()
+ {
+ return $this->ig->request('attribution/log_attribution/')
+ ->setNeedsAuth(false)
+ ->addPost('adid', $this->ig->advertising_id)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * TODO.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function logResurrectAttribution()
+ {
+ return $this->ig->request('attribution/log_resurrect_attribution/')
+ ->addPost('adid', $this->ig->advertising_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Reads MSISDN header.
+ *
+ * @param string $usage Desired usage, either "ig_select_app" or "default".
+ * @param bool $useCsrfToken (Optional) Decides to include a csrf token in this request.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MsisdnHeaderResponse
+ */
+ public function readMsisdnHeader(
+ $usage,
+ $useCsrfToken = false)
+ {
+ $request = $this->ig->request('accounts/read_msisdn_header/')
+ ->setNeedsAuth(false)
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ // UUID is used as device_id intentionally.
+ ->addPost('device_id', $this->ig->uuid)
+ ->addPost('mobile_subno_usage', $usage);
+ if ($useCsrfToken) {
+ $request->addPost('_csrftoken', $this->ig->client->getToken());
+ }
+
+ return $request->getResponse(new Response\MsisdnHeaderResponse());
+ }
+
+ /**
+ * Bootstraps MSISDN header.
+ *
+ * @param string $usage Mobile subno usage.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MsisdnHeaderResponse
+ *
+ * @since 10.24.0 app version.
+ */
+ public function bootstrapMsisdnHeader(
+ $usage = 'ig_select_app')
+ {
+ $request = $this->ig->request('accounts/msisdn_header_bootstrap/')
+ ->setNeedsAuth(false)
+ ->addPost('mobile_subno_usage', $usage)
+ // UUID is used as device_id intentionally.
+ ->addPost('device_id', $this->ig->uuid);
+
+ return $request->getResponse(new Response\MsisdnHeaderResponse());
+ }
+
+ /**
+ * @param Response\Model\Token|null $token
+ */
+ protected function _saveZeroRatingToken(
+ Response\Model\Token $token = null)
+ {
+ if ($token === null) {
+ return;
+ }
+
+ $rules = [];
+ foreach ($token->getRewriteRules() as $rule) {
+ $rules[$rule->getMatcher()] = $rule->getReplacer();
+ }
+ $this->ig->client->zeroRating()->update($rules);
+
+ try {
+ $this->ig->settings->setRewriteRules($rules);
+ $this->ig->settings->set('zr_token', $token->getTokenHash());
+ $this->ig->settings->set('zr_expires', $token->expiresAt());
+ } catch (SettingsException $e) {
+ // Ignore storage errors.
+ }
+ }
+
+ /**
+ * Get zero rating token hash result.
+ *
+ * @param string $reason One of: "token_expired", "mqtt_token_push", "token_stale", "provisioning_time_mismatch".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TokenResultResponse
+ */
+ public function fetchZeroRatingToken(
+ $reason = 'token_expired')
+ {
+ $request = $this->ig->request('zr/token/result/')
+ ->setNeedsAuth(false)
+ ->addParam('custom_device_id', $this->ig->uuid)
+ ->addParam('device_id', $this->ig->device_id)
+ ->addParam('fetch_reason', $reason)
+ ->addParam('token_hash', (string) $this->ig->settings->get('zr_token'));
+
+ /** @var Response\TokenResultResponse $result */
+ $result = $request->getResponse(new Response\TokenResultResponse());
+ $this->_saveZeroRatingToken($result->getToken());
+
+ return $result;
+ }
+
+ /**
+ * Get megaphone log.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MegaphoneLogResponse
+ */
+ public function getMegaphoneLog()
+ {
+ return $this->ig->request('megaphone/log/')
+ ->setSignedPost(false)
+ ->addPost('type', 'feed_aysf')
+ ->addPost('action', 'seen')
+ ->addPost('reason', '')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('uuid', md5(time()))
+ ->getResponse(new Response\MegaphoneLogResponse());
+ }
+
+ /**
+ * Get hidden entities for users, places and hashtags via Facebook's algorithm.
+ *
+ * TODO: We don't know what this function does. If we ever discover that it
+ * has a useful purpose, then we should move it somewhere else.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FacebookHiddenEntitiesResponse
+ */
+ public function getFacebookHiddenSearchEntities()
+ {
+ return $this->ig->request('fbsearch/get_hidden_search_entities/')
+ ->getResponse(new Response\FacebookHiddenEntitiesResponse());
+ }
+
+ /**
+ * Get Facebook OTA (Over-The-Air) update information.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FacebookOTAResponse
+ */
+ public function getFacebookOTA()
+ {
+ return $this->ig->request('facebook_ota/')
+ ->addParam('fields', Constants::FACEBOOK_OTA_FIELDS)
+ ->addParam('custom_user_id', $this->ig->account_id)
+ ->addParam('signed_body', Signatures::generateSignature('').'.')
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->addParam('version_code', Constants::VERSION_CODE)
+ ->addParam('version_name', Constants::IG_VERSION)
+ ->addParam('custom_app_id', Constants::FACEBOOK_ORCA_APPLICATION_ID)
+ ->addParam('custom_device_id', $this->ig->uuid)
+ ->getResponse(new Response\FacebookOTAResponse());
+ }
+
+ /**
+ * Fetch profiler traces config.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LoomFetchConfigResponse
+ *
+ * @see https://github.com/facebookincubator/profilo
+ */
+ public function getLoomFetchConfig()
+ {
+ return $this->ig->request('loom/fetch_config/')
+ ->getResponse(new Response\LoomFetchConfigResponse());
+ }
+
+ /**
+ * Get profile "notices".
+ *
+ * This is just for some internal state information, such as
+ * "has_change_password_megaphone". It's not for public use.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ProfileNoticeResponse
+ */
+ public function getProfileNotice()
+ {
+ return $this->ig->request('users/profile_notice/')
+ ->getResponse(new Response\ProfileNoticeResponse());
+ }
+
+ /**
+ * Fetch quick promotions data.
+ *
+ * This is used by Instagram to fetch internal promotions or changes
+ * about the platform. Latest quick promotion known was the new GDPR
+ * policy where Instagram asks you to accept new policy and accept that
+ * you have 18 years old or more.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FetchQPDataResponse
+ */
+ public function getQPFetch()
+ {
+ return $this->ig->request('qp/batch_fetch/')
+ ->addPost('vc_policy', 'default')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('surfaces_to_queries', json_encode(
+ [
+ Constants::BATCH_SURFACES[0][0] => Constants::BATCH_QUERY,
+ Constants::BATCH_SURFACES[1][0] => Constants::BATCH_QUERY,
+ Constants::BATCH_SURFACES[2][0] => Constants::BATCH_QUERY,
+ ]
+ ))
+ ->addPost('surfaces_to_triggers', json_encode(
+ [
+ Constants::BATCH_SURFACES[0][0] => Constants::BATCH_SURFACES[0][1],
+ Constants::BATCH_SURFACES[1][0] => Constants::BATCH_SURFACES[1][1],
+ Constants::BATCH_SURFACES[2][0] => Constants::BATCH_SURFACES[2][1],
+ ]
+ ))
+ ->addPost('version', Constants::BATCH_VERSION)
+ ->addPost('scale', Constants::BATCH_SCALE)
+ ->getResponse(new Response\FetchQPDataResponse());
+ }
+
+ /**
+ * Get Arlink download info.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ArlinkDownloadInfoResponse
+ */
+ public function getArlinkDownloadInfo()
+ {
+ return $this->ig->request('users/arlink_download_info/')
+ ->addParam('version_override', '2.2.1')
+ ->getResponse(new Response\ArlinkDownloadInfoResponse());
+ }
+
+ /**
+ * Get quick promotions cooldowns.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\QPCooldownsResponse
+ */
+ public function getQPCooldowns()
+ {
+ return $this->ig->request('qp/get_cooldowns/')
+ ->addParam('signed_body', Signatures::generateSignature(json_encode((object) []).'.{}'))
+ ->addParam('ig_sig_key_version', Constants::SIG_KEY_VERSION)
+ ->getResponse(new Response\QPCooldownsResponse());
+ }
+
+ public function storeClientPushPermissions()
+ {
+ return $this->ig->request('notifications/store_client_push_permissions/')
+ ->setSignedPost(false)
+ ->addPost('enabled', 'true')
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Internal helper for marking story media items as seen.
+ *
+ * This is used by story-related functions in other request-collections!
+ *
+ * @param Response\Model\Item[] $items Array of one or more story media Items.
+ * @param string|null $sourceId Where the story was seen from,
+ * such as a location story-tray ID.
+ * If NULL, we automatically use the
+ * user's profile ID from each Item
+ * object as the source ID.
+ * @param string $module Module where the story was found.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Story::markMediaSeen()
+ * @see Location::markStoryMediaSeen()
+ * @see Hashtag::markStoryMediaSeen()
+ */
+ public function markStoryMediaSeen(
+ array $items,
+ $sourceId = null,
+ $module = 'feed_timeline')
+ {
+ // Build the list of seen media, with human randomization of seen-time.
+ $reels = [];
+ $maxSeenAt = time(); // Get current global UTC timestamp.
+ $seenAt = $maxSeenAt - (3 * count($items)); // Start seenAt in the past.
+ foreach ($items as $item) {
+ if (!$item instanceof Response\Model\Item) {
+ throw new \InvalidArgumentException(
+ 'All story items must be instances of \InstagramAPI\Response\Model\Item.'
+ );
+ }
+
+ // Raise "seenAt" if it's somehow older than the item's "takenAt".
+ // NOTE: Can only happen if you see a story instantly when posted.
+ $itemTakenAt = $item->getTakenAt();
+ if ($seenAt < $itemTakenAt) {
+ $seenAt = $itemTakenAt + 2;
+ }
+
+ // Do not let "seenAt" exceed the current global UTC time.
+ if ($seenAt > $maxSeenAt) {
+ $seenAt = $maxSeenAt;
+ }
+
+ // Determine the source ID for this item. This is where the item was
+ // seen from, such as a UserID or a Location-StoryTray ID.
+ $itemSourceId = ($sourceId === null ? $item->getUser()->getPk() : $sourceId);
+
+ // Key Format: "mediaPk_userPk_sourceId".
+ // NOTE: In case of seeing stories on a user's profile, their
+ // userPk is used as the sourceId, as "mediaPk_userPk_userPk".
+ $reelId = $item->getId().'_'.$itemSourceId;
+
+ // Value Format: ["mediaTakenAt_seenAt"] (array with single string).
+ $reels[$reelId] = [$itemTakenAt.'_'.$seenAt];
+
+ // Randomly add 1-3 seconds to next seenAt timestamp, to act human.
+ $seenAt += rand(1, 3);
+ }
+
+ return $this->ig->request('media/seen/')
+ ->setVersion(2)
+ ->setIsBodyCompressed(true)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('container_module', $module)
+ ->addPost('reels', $reels)
+ ->addPost('reel_media_skipped', [])
+ ->addPost('live_vods', [])
+ ->addPost('live_vods_skipped', [])
+ ->addPost('nuxes', [])
+ ->addPost('nuxes_skipped', [])
+// ->addParam('reel', 1)
+// ->addParam('live_vod', 0)
+ ->getResponse(new Response\MediaSeenResponse());
+ }
+
+ /**
+ * Configure media entity (album, video, ...) with retries.
+ *
+ * @param callable $configurator Configurator function.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response
+ */
+ public function configureWithRetries(
+ callable $configurator)
+ {
+ $attempt = 0;
+ $lastError = null;
+ while (true) {
+ // Check for max retry-limit, and throw if we exceeded it.
+ if (++$attempt > self::MAX_CONFIGURE_RETRIES) {
+ if ($lastError === null) {
+ throw new \RuntimeException('All configuration retries have failed.');
+ }
+
+ throw new \RuntimeException(sprintf(
+ 'All configuration retries have failed. Last error: %s',
+ $lastError
+ ));
+ }
+
+ $result = null;
+
+ try {
+ /** @var Response $result */
+ $result = $configurator();
+ } catch (ThrottledException $e) {
+ throw $e;
+ } catch (LoginRequiredException $e) {
+ throw $e;
+ } catch (FeedbackRequiredException $e) {
+ throw $e;
+ } catch (ConsentRequiredException $e) {
+ throw $e;
+ } catch (CheckpointRequiredException $e) {
+ throw $e;
+ } catch (InstagramException $e) {
+ if ($e->hasResponse()) {
+ $result = $e->getResponse();
+ }
+ $lastError = $e;
+ } catch (\Exception $e) {
+ $lastError = $e;
+ // Ignore everything else.
+ }
+
+ // We had a network error or something like that, let's continue to the next attempt.
+ if ($result === null) {
+ sleep(1);
+ continue;
+ }
+
+ $httpResponse = $result->getHttpResponse();
+ $delay = 1;
+ switch ($httpResponse->getStatusCode()) {
+ case 200:
+ // Instagram uses "ok" status for this error, so we need to check it first:
+ // {"message": "media_needs_reupload", "error_title": "staged_position_not_found", "status": "ok"}
+ if (strtolower($result->getMessage()) === 'media_needs_reupload') {
+ throw new \RuntimeException(sprintf(
+ 'You need to reupload the media (%s).',
+ // We are reading a property that isn't defined in the class
+ // property map, so we must use "has" first, to ensure it exists.
+ ($result->hasErrorTitle() && is_string($result->getErrorTitle())
+ ? $result->getErrorTitle()
+ : 'unknown error')
+ ));
+ } elseif ($result->isOk()) {
+ return $result;
+ }
+ // Continue to the next attempt.
+ break;
+ case 202:
+ // We are reading a property that isn't defined in the class
+ // property map, so we must use "has" first, to ensure it exists.
+ if ($result->hasCooldownTimeInSeconds() && $result->getCooldownTimeInSeconds() !== null) {
+ $delay = max((int) $result->getCooldownTimeInSeconds(), 1);
+ }
+ break;
+ default:
+ }
+ sleep($delay);
+ }
+
+ // We are never supposed to get here!
+ throw new \LogicException('Something went wrong during configuration.');
+ }
+
+ /**
+ * Performs a resumable upload of a media file, with support for retries.
+ *
+ * @param MediaDetails $mediaDetails
+ * @param Request $offsetTemplate
+ * @param Request $uploadTemplate
+ * @param bool $skipGet
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response\ResumableUploadResponse
+ */
+ protected function _uploadResumableMedia(
+ MediaDetails $mediaDetails,
+ Request $offsetTemplate,
+ Request $uploadTemplate,
+ $skipGet)
+ {
+ // Open file handle.
+ $handle = fopen($mediaDetails->getFilename(), 'rb');
+ if ($handle === false) {
+ throw new \RuntimeException('Failed to open media file for reading.');
+ }
+
+ try {
+ $length = $mediaDetails->getFilesize();
+
+ // Create a stream for the opened file handle.
+ $stream = new Stream($handle, ['size' => $length]);
+
+ $attempt = 0;
+ while (true) {
+ // Check for max retry-limit, and throw if we exceeded it.
+ if (++$attempt > self::MAX_RESUMABLE_RETRIES) {
+ throw new \RuntimeException('All retries have failed.');
+ }
+
+ try {
+ if ($attempt === 1 && $skipGet) {
+ // It is obvious that the first attempt is always at 0, so we can skip a request.
+ $offset = 0;
+ } else {
+ // Get current offset.
+ $offsetRequest = clone $offsetTemplate;
+ /** @var Response\ResumableOffsetResponse $offsetResponse */
+ $offsetResponse = $offsetRequest->getResponse(new Response\ResumableOffsetResponse());
+ $offset = $offsetResponse->getOffset();
+ }
+
+ // Resume upload from given offset.
+ $uploadRequest = clone $uploadTemplate;
+ $uploadRequest
+ ->addHeader('Offset', $offset)
+ ->setBody(new LimitStream($stream, $length - $offset, $offset));
+ /** @var Response\ResumableUploadResponse $response */
+ $response = $uploadRequest->getResponse(new Response\ResumableUploadResponse());
+
+ return $response;
+ } catch (ThrottledException $e) {
+ throw $e;
+ } catch (LoginRequiredException $e) {
+ throw $e;
+ } catch (FeedbackRequiredException $e) {
+ throw $e;
+ } catch (ConsentRequiredException $e) {
+ throw $e;
+ } catch (CheckpointRequiredException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Ignore everything else.
+ }
+ }
+ } finally {
+ Utils::safe_fclose($handle);
+ }
+
+ // We are never supposed to get here!
+ throw new \LogicException('Something went wrong during media upload.');
+ }
+
+ /**
+ * Performs an upload of a photo file, without support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UploadPhotoResponse
+ */
+ protected function _uploadPhotoInOnePiece(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // Prepare payload for the upload request.
+ $request = $this->ig->request('upload/photo/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addFile(
+ 'photo',
+ $internalMetadata->getPhotoDetails()->getFilename(),
+ 'pending_media_'.Utils::generateUploadId().'.jpg'
+ );
+
+ foreach ($this->_getPhotoUploadParams($targetFeed, $internalMetadata) as $key => $value) {
+ $request->addPost($key, $value);
+ }
+ /** @var Response\UploadPhotoResponse $response */
+ $response = $request->getResponse(new Response\UploadPhotoResponse());
+
+ return $response;
+ }
+
+ /**
+ * Performs a resumable upload of a photo file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ protected function _uploadResumablePhoto(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $photoDetails = $internalMetadata->getPhotoDetails();
+
+ $endpoint = sprintf('https://i.instagram.com/rupload_igphoto/%s_%d_%d',
+ $internalMetadata->getUploadId(),
+ 0,
+ Utils::hashCode($photoDetails->getFilename())
+ );
+
+ $uploadParams = $this->_getPhotoUploadParams($targetFeed, $internalMetadata);
+ $uploadParams = Utils::reorderByHashCode($uploadParams);
+
+ $offsetTemplate = new Request($this->ig, $endpoint);
+ $offsetTemplate
+ ->setAddDefaultHeaders(false)
+ ->addHeader('X_FB_PHOTO_WATERFALL_ID', Signatures::generateUUID(true))
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams));
+
+ $uploadTemplate = clone $offsetTemplate;
+ $uploadTemplate
+ ->addHeader('X-Entity-Type', 'image/jpeg')
+ ->addHeader('X-Entity-Name', basename(parse_url($endpoint, PHP_URL_PATH)))
+ ->addHeader('X-Entity-Length', $photoDetails->getFilesize());
+
+ return $this->_uploadResumableMedia(
+ $photoDetails,
+ $offsetTemplate,
+ $uploadTemplate,
+ $this->ig->isExperimentEnabled(
+ 'ig_android_skip_get_fbupload_photo_universe',
+ 'photo_skip_get'
+ )
+ );
+ }
+
+ /**
+ * Determine whether to use resumable photo uploader based on target feed and internal metadata.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return bool
+ */
+ protected function _useResumablePhotoUploader(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_sidecar_photo_fbupload_universe',
+ 'is_enabled_fbupload_sidecar_photo');
+ break;
+ default:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_photo_fbupload_universe',
+ 'is_enabled_fbupload_photo');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the first missing range (start-end) from a HTTP "Range" header.
+ *
+ * @param string $ranges
+ *
+ * @return array|null
+ */
+ protected function _getFirstMissingRange(
+ $ranges)
+ {
+ preg_match_all('/(?\d+)-(?\d+)\/(?\d+)/', $ranges, $matches, PREG_SET_ORDER);
+ if (!count($matches)) {
+ return;
+ }
+ $pairs = [];
+ $length = 0;
+ foreach ($matches as $match) {
+ $pairs[] = [$match['start'], $match['end']];
+ $length = $match['total'];
+ }
+ // Sort pairs by start.
+ usort($pairs, function (array $pair1, array $pair2) {
+ return $pair1[0] - $pair2[0];
+ });
+ $first = $pairs[0];
+ $second = count($pairs) > 1 ? $pairs[1] : null;
+ if ($first[0] == 0) {
+ $result = [$first[1] + 1, ($second === null ? $length : $second[0]) - 1];
+ } else {
+ $result = [0, $first[0] - 1];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Performs a chunked upload of a video file, with support for retries.
+ *
+ * Note that chunk uploads often get dropped when their server is overloaded
+ * at peak hours, which is why our chunk-retry mechanism exists. We will
+ * try several times to upload all chunks. The retries will only re-upload
+ * the exact chunks that have been dropped from their server, and it won't
+ * waste time with chunks that are already successfully uploaded.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UploadVideoResponse
+ */
+ protected function _uploadVideoChunks(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $videoFilename = $internalMetadata->getVideoDetails()->getFilename();
+
+ // To support video uploads to albums, we MUST fake-inject the
+ // "sessionid" cookie from "i.instagram" into our "upload.instagram"
+ // request, otherwise the server will reply with a "StagedUpload not
+ // found" error when the final chunk has been uploaded.
+ $sessionIDCookie = null;
+ if ($targetFeed === Constants::FEED_TIMELINE_ALBUM) {
+ $foundCookie = $this->ig->client->getCookie('sessionid', 'i.instagram.com');
+ if ($foundCookie !== null) {
+ $sessionIDCookie = $foundCookie->getValue();
+ }
+ if ($sessionIDCookie === null || $sessionIDCookie === '') { // Verify value.
+ throw new \RuntimeException(
+ 'Unable to find the necessary SessionID cookie for uploading video album chunks.'
+ );
+ }
+ }
+
+ // Verify the upload URLs.
+ $uploadUrls = $internalMetadata->getVideoUploadUrls();
+ if (!is_array($uploadUrls) || !count($uploadUrls)) {
+ throw new \RuntimeException('No video upload URLs found.');
+ }
+
+ // Init state.
+ $length = $internalMetadata->getVideoDetails()->getFilesize();
+ $uploadId = $internalMetadata->getUploadId();
+ $sessionId = sprintf('%s-%d', $uploadId, Utils::hashCode($videoFilename));
+ $uploadUrl = array_shift($uploadUrls);
+ $offset = 0;
+ $chunk = min($length, self::MIN_CHUNK_SIZE);
+ $attempt = 0;
+
+ // Open file handle.
+ $handle = fopen($videoFilename, 'rb');
+ if ($handle === false) {
+ throw new \RuntimeException('Failed to open file for reading.');
+ }
+
+ try {
+ // Create a stream for the opened file handle.
+ $stream = new Stream($handle);
+ while (true) {
+ // Check for this server's max retry-limit, and switch server?
+ if (++$attempt > self::MAX_CHUNK_RETRIES) {
+ $uploadUrl = null;
+ }
+
+ // Try to switch to another server.
+ if ($uploadUrl === null) {
+ $uploadUrl = array_shift($uploadUrls);
+ // Fail if there are no upload URLs left.
+ if ($uploadUrl === null) {
+ throw new \RuntimeException('There are no more upload URLs.');
+ }
+ // Reset state.
+ $attempt = 1; // As if "++$attempt" had ran once, above.
+ $offset = 0;
+ $chunk = min($length, self::MIN_CHUNK_SIZE);
+ }
+
+ // Prepare request.
+ $request = new Request($this->ig, $uploadUrl->getUrl());
+ $request
+ ->setAddDefaultHeaders(false)
+ ->addHeader('Content-Type', 'application/octet-stream')
+ ->addHeader('Session-ID', $sessionId)
+ ->addHeader('Content-Disposition', 'attachment; filename="video.mov"')
+ ->addHeader('Content-Range', 'bytes '.$offset.'-'.($offset + $chunk - 1).'/'.$length)
+ ->addHeader('job', $uploadUrl->getJob())
+ ->setBody(new LimitStream($stream, $chunk, $offset));
+
+ // When uploading videos to albums, we must fake-inject the
+ // "sessionid" cookie (the official app fake-injects it too).
+ if ($targetFeed === Constants::FEED_TIMELINE_ALBUM && $sessionIDCookie !== null) {
+ // We'll add it with the default options ("single use")
+ // so the fake cookie is only added to THIS request.
+ $this->ig->client->fakeCookies()->add('sessionid', $sessionIDCookie);
+ }
+
+ // Perform the upload of the current chunk.
+ $start = microtime(true);
+
+ try {
+ $httpResponse = $request->getHttpResponse();
+ } catch (NetworkException $e) {
+ // Ignore network exceptions.
+ continue;
+ }
+
+ // Determine new chunk size based on upload duration.
+ $newChunkSize = (int) ($chunk / (microtime(true) - $start) * 5);
+ // Ensure that the new chunk size is in valid range.
+ $newChunkSize = min(self::MAX_CHUNK_SIZE, max(self::MIN_CHUNK_SIZE, $newChunkSize));
+
+ $result = null;
+
+ try {
+ /** @var Response\UploadVideoResponse $result */
+ $result = $request->getResponse(new Response\UploadVideoResponse());
+ } catch (CheckpointRequiredException $e) {
+ throw $e;
+ } catch (LoginRequiredException $e) {
+ throw $e;
+ } catch (FeedbackRequiredException $e) {
+ throw $e;
+ } catch (ConsentRequiredException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ // Ignore everything else.
+ }
+
+ // Process the server response...
+ switch ($httpResponse->getStatusCode()) {
+ case 200:
+ // All chunks are uploaded, but if we don't have a
+ // response-result now then we must retry a new server.
+ if ($result === null) {
+ $uploadUrl = null;
+ break;
+ }
+
+ // SUCCESS! :-)
+ return $result;
+ case 201:
+ // The server has given us a regular reply. We expect it
+ // to be a range-reply, such as "0-3912399/23929393".
+ // Their server often drops chunks during peak hours,
+ // and in that case the first range may not start at
+ // zero, or there may be gaps or multiple ranges, such
+ // as "0-4076155/8152310,6114234-8152309/8152310". We'll
+ // handle that by re-uploading whatever they've dropped.
+ if (!$httpResponse->hasHeader('Range')) {
+ $uploadUrl = null;
+ break;
+ }
+ $range = $this->_getFirstMissingRange($httpResponse->getHeaderLine('Range'));
+ if ($range !== null) {
+ $offset = $range[0];
+ $chunk = min($newChunkSize, $range[1] - $range[0] + 1);
+ } else {
+ $chunk = min($newChunkSize, $length - $offset);
+ }
+
+ // Reset attempts count on successful upload.
+ $attempt = 0;
+ break;
+ case 400:
+ case 403:
+ case 511:
+ throw new \RuntimeException(sprintf(
+ 'Instagram\'s server returned HTTP status "%d".',
+ $httpResponse->getStatusCode()
+ ));
+ case 422:
+ throw new \RuntimeException('Instagram\'s server says that the video is corrupt.');
+ default:
+ }
+ }
+ } finally {
+ // Guaranteed to release handle even if something bad happens above!
+ Utils::safe_fclose($handle);
+ }
+
+ // We are never supposed to get here!
+ throw new \LogicException('Something went wrong during video upload.');
+ }
+
+ /**
+ * Performs a segmented upload of a video file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \Exception
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ protected function _uploadSegmentedVideo(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $videoDetails = $internalMetadata->getVideoDetails();
+
+ // We must split the video into segments before running any requests.
+ $segments = $this->_splitVideoIntoSegments($targetFeed, $videoDetails);
+
+ $uploadParams = $this->_getVideoUploadParams($targetFeed, $internalMetadata);
+ $uploadParams = Utils::reorderByHashCode($uploadParams);
+
+ // This request gives us a stream identifier.
+ $startRequest = new Request($this->ig, sprintf(
+ 'https://i.instagram.com/rupload_igvideo/%s?segmented=true&phase=start',
+ Signatures::generateUUID()
+ ));
+ $startRequest
+ ->setAddDefaultHeaders(false)
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams))
+ // Dirty hack to make a POST request.
+ ->setBody(stream_for());
+ /** @var Response\SegmentedStartResponse $startResponse */
+ $startResponse = $startRequest->getResponse(new Response\SegmentedStartResponse());
+ $streamId = $startResponse->getStreamId();
+
+ // Upload the segments.
+ try {
+ $offset = 0;
+ // Yep, no UUID here like in other resumable uploaders. Seems like a bug.
+ $waterfallId = Utils::generateUploadId();
+ foreach ($segments as $segment) {
+ $endpoint = sprintf(
+ 'https://i.instagram.com/rupload_igvideo/%s-%d-%d?segmented=true&phase=transfer',
+ md5($segment->getFilename()),
+ 0,
+ $segment->getFilesize()
+ );
+
+ $offsetTemplate = new Request($this->ig, $endpoint);
+ $offsetTemplate
+ ->setAddDefaultHeaders(false)
+ ->addHeader('Segment-Start-Offset', $offset)
+ // 1 => Audio, 2 => Video, 3 => Mixed.
+ ->addHeader('Segment-Type', $segment->getAudioCodec() !== null ? 1 : 2)
+ ->addHeader('Stream-Id', $streamId)
+ ->addHeader('X_FB_VIDEO_WATERFALL_ID', $waterfallId)
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams));
+
+ $uploadTemplate = clone $offsetTemplate;
+ $uploadTemplate
+ ->addHeader('X-Entity-Type', 'video/mp4')
+ ->addHeader('X-Entity-Name', basename(parse_url($endpoint, PHP_URL_PATH)))
+ ->addHeader('X-Entity-Length', $segment->getFilesize());
+
+ $this->_uploadResumableMedia($segment, $offsetTemplate, $uploadTemplate, false);
+ // Offset seems to be used just for ordering the segments.
+ $offset += $segment->getFilesize();
+ }
+ } finally {
+ // Remove the segments, because we don't need them anymore.
+ foreach ($segments as $segment) {
+ @unlink($segment->getFilename());
+ }
+ }
+
+ // Finalize the upload.
+ $endRequest = new Request($this->ig, sprintf(
+ 'https://i.instagram.com/rupload_igvideo/%s?segmented=true&phase=end',
+ Signatures::generateUUID()
+ ));
+ $endRequest
+ ->setAddDefaultHeaders(false)
+ ->addHeader('Stream-Id', $streamId)
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams))
+ // Dirty hack to make a POST request.
+ ->setBody(stream_for());
+ /** @var Response\GenericResponse $result */
+ $result = $endRequest->getResponse(new Response\GenericResponse());
+
+ return $result;
+ }
+
+ /**
+ * Performs a resumable upload of a video file, with support for retries.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \LogicException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ protected function _uploadResumableVideo(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $rurCookie = $this->ig->client->getCookie('rur', 'i.instagram.com');
+ if ($rurCookie === null || $rurCookie->getValue() === '') {
+ throw new \RuntimeException(
+ 'Unable to find the necessary "rur" cookie for uploading video.'
+ );
+ }
+
+ $videoDetails = $internalMetadata->getVideoDetails();
+
+ $endpoint = sprintf('https://i.instagram.com/rupload_igvideo/%s_%d_%d?target=%s',
+ $internalMetadata->getUploadId(),
+ 0,
+ Utils::hashCode($videoDetails->getFilename()),
+ $rurCookie->getValue()
+ );
+
+ $uploadParams = $this->_getVideoUploadParams($targetFeed, $internalMetadata);
+ $uploadParams = Utils::reorderByHashCode($uploadParams);
+
+ $offsetTemplate = new Request($this->ig, $endpoint);
+ $offsetTemplate
+ ->setAddDefaultHeaders(false)
+ ->addHeader('X_FB_VIDEO_WATERFALL_ID', Signatures::generateUUID(true))
+ ->addHeader('X-Instagram-Rupload-Params', json_encode($uploadParams));
+
+ $uploadTemplate = clone $offsetTemplate;
+ $uploadTemplate
+ ->addHeader('X-Entity-Type', 'video/mp4')
+ ->addHeader('X-Entity-Name', basename(parse_url($endpoint, PHP_URL_PATH)))
+ ->addHeader('X-Entity-Length', $videoDetails->getFilesize());
+
+ return $this->_uploadResumableMedia(
+ $videoDetails,
+ $offsetTemplate,
+ $uploadTemplate,
+ $this->ig->isExperimentEnabled(
+ 'ig_android_skip_get_fbupload_universe',
+ 'video_skip_get'
+ )
+ );
+ }
+
+ /**
+ * Determine whether to use segmented video uploader based on target feed and internal metadata.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return bool
+ */
+ protected function _useSegmentedVideoUploader(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // No segmentation for album video.
+ if ($targetFeed === Constants::FEED_TIMELINE_ALBUM) {
+ return false;
+ }
+
+ // ffmpeg is required for video segmentation.
+ try {
+ FFmpeg::factory();
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ // There is no need to segment short videos.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $minDuration = (int) $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ // NOTE: This typo is intentional. Instagram named it that way.
+ 'segment_duration_threashold_feed',
+ 10
+ );
+ break;
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ $minDuration = (int) $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ // NOTE: This typo is intentional. Instagram named it that way.
+ 'segment_duration_threashold_story_raven',
+ 0
+ );
+ break;
+ case Constants::FEED_TV:
+ $minDuration = 150;
+ break;
+ default:
+ $minDuration = 31536000; // 1 year.
+ }
+ if ((int) $internalMetadata->getVideoDetails()->getDuration() < $minDuration) {
+ return false;
+ }
+
+ // Check experiments for the target feed.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_video_segmented_upload_universe',
+ 'segment_enabled_feed',
+ true);
+ break;
+ case Constants::FEED_DIRECT:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_direct_video_segmented_upload_universe',
+ 'is_enabled_segment_direct');
+ break;
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_reel_raven_video_segmented_upload_universe',
+ 'segment_enabled_story_raven');
+ break;
+ case Constants::FEED_TV:
+ $result = true;
+ break;
+ default:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_video_segmented_upload_universe',
+ 'segment_enabled_unknown',
+ true);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Determine whether to use resumable video uploader based on target feed and internal metadata.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return bool
+ */
+ protected function _useResumableVideoUploader(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_fbupload_sidecar_video_universe',
+ 'is_enabled_fbupload_sidecar_video');
+ break;
+ case Constants::FEED_TIMELINE:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_followers_share');
+ break;
+ case Constants::FEED_DIRECT:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_direct_share');
+ break;
+ case Constants::FEED_STORY:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_reel_share');
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_story_share');
+ break;
+ case Constants::FEED_TV:
+ $result = true;
+ break;
+ default:
+ $result = $this->ig->isExperimentEnabled(
+ 'ig_android_upload_reliability_universe',
+ 'is_enabled_fbupload_unknown');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get retry context for media upload.
+ *
+ * @return array
+ */
+ protected function _getRetryContext()
+ {
+ return [
+ // TODO increment it with every fail.
+ 'num_step_auto_retry' => 0,
+ 'num_reupload' => 0,
+ 'num_step_manual_retry' => 0,
+ ];
+ }
+
+ /**
+ * Get params for photo upload job.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return array
+ */
+ protected function _getPhotoUploadParams(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ // Common params.
+ $result = [
+ 'upload_id' => (string) $internalMetadata->getUploadId(),
+ 'retry_context' => json_encode($this->_getRetryContext()),
+ 'image_compression' => '{"lib_name":"moz","lib_version":"3.1.m","quality":"87"}',
+ 'xsharing_user_ids' => json_encode([]),
+ 'media_type' => $internalMetadata->getVideoDetails() !== null
+ ? (string) Response\Model\Item::VIDEO
+ : (string) Response\Model\Item::PHOTO,
+ ];
+ // Target feed's specific params.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result['is_sidecar'] = '1';
+ break;
+ default:
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get params for video upload job.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param InternalMetadata $internalMetadata Internal library-generated metadata object.
+ *
+ * @return array
+ */
+ protected function _getVideoUploadParams(
+ $targetFeed,
+ InternalMetadata $internalMetadata)
+ {
+ $videoDetails = $internalMetadata->getVideoDetails();
+ // Common params.
+ $result = [
+ 'upload_id' => (string) $internalMetadata->getUploadId(),
+ 'retry_context' => json_encode($this->_getRetryContext()),
+ 'xsharing_user_ids' => json_encode([]),
+ 'upload_media_height' => (string) $videoDetails->getHeight(),
+ 'upload_media_width' => (string) $videoDetails->getWidth(),
+ 'upload_media_duration_ms' => (string) $videoDetails->getDurationInMsec(),
+ 'media_type' => (string) Response\Model\Item::VIDEO,
+ // TODO select with targetFeed (?)
+ 'potential_share_types' => json_encode(['not supported type']),
+ ];
+ // Target feed's specific params.
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE_ALBUM:
+ $result['is_sidecar'] = '1';
+ break;
+ case Constants::FEED_DIRECT:
+ $result['direct_v2'] = '1';
+ $result['rotate'] = '0';
+ $result['hflip'] = 'false';
+ break;
+ case Constants::FEED_STORY:
+ $result['for_album'] = '1';
+ break;
+ case Constants::FEED_DIRECT_STORY:
+ $result['for_direct_story'] = '1';
+ break;
+ case Constants::FEED_TV:
+ $result['is_igtv_video'] = '1';
+ break;
+ default:
+ }
+
+ return $result;
+ }
+
+ /**
+ * Find the segments after ffmpeg processing.
+ *
+ * @param string $outputDirectory The directory to look in.
+ * @param string $prefix The filename prefix.
+ *
+ * @return array
+ */
+ protected function _findSegments(
+ $outputDirectory,
+ $prefix)
+ {
+ // Video segments will be uploaded before the audio one.
+ $result = glob("{$outputDirectory}/{$prefix}.video.*.mp4");
+
+ // Audio always goes into one segment, so we can use is_file() here.
+ $audioTrack = "{$outputDirectory}/{$prefix}.audio.mp4";
+ if (is_file($audioTrack)) {
+ $result[] = $audioTrack;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Split the video file into segments.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param VideoDetails $videoDetails
+ * @param FFmpeg|null $ffmpeg
+ * @param string|null $outputDirectory
+ *
+ * @throws \Exception
+ *
+ * @return VideoDetails[]
+ */
+ protected function _splitVideoIntoSegments(
+ $targetFeed,
+ VideoDetails $videoDetails,
+ FFmpeg $ffmpeg = null,
+ $outputDirectory = null)
+ {
+ if ($ffmpeg === null) {
+ $ffmpeg = FFmpeg::factory();
+ }
+ if ($outputDirectory === null) {
+ $outputDirectory = Utils::$defaultTmpPath === null ? sys_get_temp_dir() : Utils::$defaultTmpPath;
+ }
+ // Check whether the output directory is valid.
+ $targetDirectory = realpath($outputDirectory);
+ if ($targetDirectory === false || !is_dir($targetDirectory) || !is_writable($targetDirectory)) {
+ throw new \RuntimeException(sprintf(
+ 'Directory "%s" is missing or is not writable.',
+ $outputDirectory
+ ));
+ }
+
+ $prefix = sha1($videoDetails->getFilename().uniqid('', true));
+
+ try {
+ // Split the video stream into a multiple segments by time.
+ $ffmpeg->run(sprintf(
+ '-i %s -c:v copy -an -dn -sn -f segment -segment_time %d -segment_format mp4 %s',
+ Args::escape($videoDetails->getFilename()),
+ $this->_getTargetSegmentDuration($targetFeed),
+ Args::escape(sprintf(
+ '%s%s%s.video.%%03d.mp4',
+ $outputDirectory,
+ DIRECTORY_SEPARATOR,
+ $prefix
+ ))
+ ));
+
+ if ($videoDetails->getAudioCodec() !== null) {
+ // Save the audio stream in one segment.
+ $ffmpeg->run(sprintf(
+ '-i %s -c:a copy -vn -dn -sn -f mp4 %s',
+ Args::escape($videoDetails->getFilename()),
+ Args::escape(sprintf(
+ '%s%s%s.audio.mp4',
+ $outputDirectory,
+ DIRECTORY_SEPARATOR,
+ $prefix
+ ))
+ ));
+ }
+ } catch (\RuntimeException $e) {
+ // Find and remove all segments (if any).
+ $files = $this->_findSegments($outputDirectory, $prefix);
+ foreach ($files as $file) {
+ @unlink($file);
+ }
+ // Re-throw the exception.
+ throw $e;
+ }
+
+ // Collect segments.
+ $files = $this->_findSegments($outputDirectory, $prefix);
+ if (empty($files)) {
+ throw new \RuntimeException('Something went wrong while splitting the video into segments.');
+ }
+ $result = [];
+
+ try {
+ // Wrap them into VideoDetails.
+ foreach ($files as $file) {
+ $result[] = new VideoDetails($file);
+ }
+ } catch (\Exception $e) {
+ // Cleanup when something went wrong.
+ foreach ($files as $file) {
+ @unlink($file);
+ }
+
+ throw $e;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get target segment duration in seconds.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return int
+ */
+ protected function _getTargetSegmentDuration(
+ $targetFeed)
+ {
+ switch ($targetFeed) {
+ case Constants::FEED_TIMELINE:
+ $duration = $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ 'target_segment_duration_feed',
+ 5
+ );
+ break;
+ case Constants::FEED_STORY:
+ case Constants::FEED_DIRECT_STORY:
+ $duration = $this->ig->getExperimentParam(
+ 'ig_android_video_segmented_upload_universe',
+ 'target_segment_duration_story_raven',
+ 2
+ );
+ break;
+ case Constants::FEED_TV:
+ $duration = 100;
+ break;
+ default:
+ throw new \InvalidArgumentException("Unsupported feed {$targetFeed}.");
+ }
+
+ return (int) $duration;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Live.php b/vendor/mgp25/instagram-php/src/Request/Live.php
new file mode 100755
index 0000000..1e8e318
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Live.php
@@ -0,0 +1,696 @@
+ig->isExperimentEnabled('ig_android_live_suggested_live_expansion', 'is_enabled')) {
+ $endpoint = 'live/get_suggested_live_and_post_live/';
+ }
+
+ return $this->ig->request($endpoint)
+ ->getResponse(new Response\SuggestedBroadcastsResponse());
+ }
+
+ /**
+ * Get broadcast information.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastInfoResponse
+ */
+ public function getInfo(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/info/")
+ ->getResponse(new Response\BroadcastInfoResponse());
+ }
+
+ /**
+ * Get the viewer list of a broadcast.
+ *
+ * WARNING: You MUST be the owner of the broadcast. Otherwise Instagram won't send any API reply!
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ViewerListResponse
+ */
+ public function getViewerList(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_viewer_list/")
+ ->getResponse(new Response\ViewerListResponse());
+ }
+
+ /**
+ * Get the final viewer list of a broadcast after it has ended.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FinalViewerListResponse
+ */
+ public function getFinalViewerList(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_final_viewer_list/")
+ ->getResponse(new Response\FinalViewerListResponse());
+ }
+
+ /**
+ * Get the viewer list of a post-live (saved replay) broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PostLiveViewerListResponse
+ */
+ public function getPostLiveViewerList(
+ $broadcastId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("live/{$broadcastId}/get_post_live_viewers_list/");
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\PostLiveViewerListResponse());
+ }
+
+ /**
+ * Get a live broadcast's heartbeat and viewer count.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param bool $isViewer Indicates if this request is being ran as a viewer (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastHeartbeatAndViewerCountResponse
+ */
+ public function getHeartbeatAndViewerCount(
+ $broadcastId,
+ $isViewer = false)
+ {
+ $request = $this->ig->request("live/{$broadcastId}/heartbeat_and_get_viewer_count/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+ if ($isViewer) {
+ $request->addPost('live_with_eligibility', 1);
+ } else {
+ $request->addPost('offset_to_video_start', 0);
+ }
+
+ return $request->getResponse(new Response\BroadcastHeartbeatAndViewerCountResponse());
+ }
+
+ /**
+ * Get a live broadcast's join request counts.
+ *
+ * Note: This request **will** return null if there have been no pending
+ * join requests have been made. Please have your code check for null.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $lastTotalCount Last join request count (optional).
+ * @param int $lastSeenTs Last seen timestamp (optional).
+ * @param int $lastFetchTs Last fetch timestamp (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastJoinRequestCountResponse|null
+ */
+ public function getJoinRequestCounts(
+ $broadcastId,
+ $lastTotalCount = 0,
+ $lastSeenTs = 0,
+ $lastFetchTs = 0)
+ {
+ try {
+ return $this->ig->request("live/{$broadcastId}/get_join_request_counts/")
+ ->addParam('last_total_count', $lastTotalCount)
+ ->addParam('last_seen_ts', $lastSeenTs)
+ ->addParam('last_fetch_ts', $lastFetchTs)
+ ->getResponse(new Response\BroadcastJoinRequestCountResponse());
+ } catch (\InstagramAPI\Exception\EmptyResponseException $e) {
+ return null;
+ }
+ }
+
+ /**
+ * Show question in a live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function showQuestion(
+ $broadcastId,
+ $questionId)
+ {
+ return $this->ig->request("live/{$broadcastId}/question/{$questionId}/activate/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Hide question in a live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function hideQuestion(
+ $broadcastId,
+ $questionId)
+ {
+ return $this->ig->request("live/{$broadcastId}/question/{$questionId}/deactivate/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Asks a question to the host of the broadcast.
+ *
+ * Note: This function is only used by the viewers of a broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $questionText Your question text.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function question(
+ $broadcastId,
+ $questionText)
+ {
+ return $this->ig->request("live/{$broadcastId}/questions")
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('text', $questionText)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get all received responses from a story question.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastQuestionsResponse
+ */
+ public function getQuestions()
+ {
+ return $this->ig->request('live/get_questions/')
+ ->getResponse(new Response\BroadcastQuestionsResponse());
+ }
+
+ /**
+ * Get all received responses from the current broadcast and a story question.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastQuestionsResponse
+ */
+ public function getLiveBroadcastQuestions(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/questions/")
+ ->addParam('sources', 'story_and_live')
+ ->getResponse(new Response\BroadcastQuestionsResponse());
+ }
+
+ /**
+ * Acknowledges (waves at) a new user after they join.
+ *
+ * Note: This can only be done once to a user, per stream. Additionally, the user must have joined the stream.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $viewerId Numerical UserPK ID of the user to wave to.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function wave(
+ $broadcastId,
+ $viewerId)
+ {
+ return $this->ig->request("live/{$broadcastId}/wave/")
+ ->addPost('viewer_id', $viewerId)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Post a comment to a live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $commentText Your comment text.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentBroadcastResponse
+ */
+ public function comment(
+ $broadcastId,
+ $commentText)
+ {
+ return $this->ig->request("live/{$broadcastId}/comment/")
+ ->addPost('user_breadcrumb', Utils::generateUserBreadcrumb(mb_strlen($commentText)))
+ ->addPost('idempotence_token', Signatures::generateUUID(true))
+ ->addPost('comment_text', $commentText)
+ ->addPost('live_or_vod', 1)
+ ->addPost('offset_to_video_start', 0)
+ ->getResponse(new Response\CommentBroadcastResponse());
+ }
+
+ /**
+ * Pin a comment on live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $commentId Target comment ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PinCommentBroadcastResponse
+ */
+ public function pinComment(
+ $broadcastId,
+ $commentId)
+ {
+ return $this->ig->request("live/{$broadcastId}/pin_comment/")
+ ->addPost('offset_to_video_start', 0)
+ ->addPost('comment_id', $commentId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\PinCommentBroadcastResponse());
+ }
+
+ /**
+ * Unpin a comment on live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string $commentId Pinned comment ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UnpinCommentBroadcastResponse
+ */
+ public function unpinComment(
+ $broadcastId,
+ $commentId)
+ {
+ return $this->ig->request("live/{$broadcastId}/unpin_comment/")
+ ->addPost('offset_to_video_start', 0)
+ ->addPost('comment_id', $commentId)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UnpinCommentBroadcastResponse());
+ }
+
+ /**
+ * Get broadcast comments.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $lastCommentTs Last comments timestamp (optional).
+ * @param int $commentsRequested Number of comments requested (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastCommentsResponse
+ */
+ public function getComments(
+ $broadcastId,
+ $lastCommentTs = 0,
+ $commentsRequested = 3)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_comment/")
+ ->addParam('last_comment_ts', $lastCommentTs)
+// ->addParam('num_comments_requested', $commentsRequested)
+ ->getResponse(new Response\BroadcastCommentsResponse());
+ }
+
+ /**
+ * Get post-live (saved replay) broadcast comments.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $startingOffset (optional) The time-offset to start at when retrieving the comments.
+ * @param string $encodingTag (optional) TODO: ?.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PostLiveCommentsResponse
+ */
+ public function getPostLiveComments(
+ $broadcastId,
+ $startingOffset = 0,
+ $encodingTag = 'instagram_dash_remuxed')
+ {
+ return $this->ig->request("live/{$broadcastId}/get_post_live_comments/")
+ ->addParam('starting_offset', $startingOffset)
+ ->addParam('encoding_tag', $encodingTag)
+ ->getResponse(new Response\PostLiveCommentsResponse());
+ }
+
+ /**
+ * Enable viewer comments on your live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EnableDisableLiveCommentsResponse
+ */
+ public function enableComments(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/unmute_comment/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\EnableDisableLiveCommentsResponse());
+ }
+
+ /**
+ * Disable viewer comments on your live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EnableDisableLiveCommentsResponse
+ */
+ public function disableComments(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/mute_comment/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\EnableDisableLiveCommentsResponse());
+ }
+
+ /**
+ * Like a broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $likeCount Number of likes ("hearts") to send (optional).
+ * @param int $burstLikeCount Number of burst likes ("hearts") to send (optional).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastLikeResponse
+ */
+ public function like(
+ $broadcastId,
+ $likeCount = 1,
+ $burstLikeCount = 0)
+ {
+ if ($likeCount < 1 || $likeCount > 6) {
+ throw new \InvalidArgumentException('Like count must be a number from 1 to 6.');
+ }
+
+ return $this->ig->request("live/{$broadcastId}/like/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_like_count', $likeCount)
+ ->addPost('user_like_burst_count', $burstLikeCount)
+ ->addPost('offset_to_video_start', 0)
+ ->getResponse(new Response\BroadcastLikeResponse());
+ }
+
+ /**
+ * Get a live broadcast's like count.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $likeTs Like timestamp.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BroadcastLikeCountResponse
+ */
+ public function getLikeCount(
+ $broadcastId,
+ $likeTs = 0)
+ {
+ return $this->ig->request("live/{$broadcastId}/get_like_count/")
+ ->addParam('like_ts', $likeTs)
+ ->getResponse(new Response\BroadcastLikeCountResponse());
+ }
+
+ /**
+ * Get post-live (saved replay) broadcast likes.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param int $startingOffset (optional) The time-offset to start at when retrieving the likes.
+ * @param string $encodingTag (optional) TODO: ?.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PostLiveLikesResponse
+ */
+ public function getPostLiveLikes(
+ $broadcastId,
+ $startingOffset = 0,
+ $encodingTag = 'instagram_dash_remuxed')
+ {
+ return $this->ig->request("live/{$broadcastId}/get_post_live_likes/")
+ ->addParam('starting_offset', $startingOffset)
+ ->addParam('encoding_tag', $encodingTag)
+ ->getResponse(new Response\PostLiveLikesResponse());
+ }
+
+ /**
+ * Create a live broadcast.
+ *
+ * Read the description of `start()` for proper usage.
+ *
+ * @param int $previewWidth (optional) Width.
+ * @param int $previewHeight (optional) Height.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CreateLiveResponse
+ *
+ * @see Live::start()
+ * @see Live::end()
+ */
+ public function create(
+ $previewWidth = 1080,
+ $previewHeight = 2076)
+ {
+ return $this->ig->request('live/create/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('preview_height', $previewHeight)
+ ->addPost('preview_width', $previewWidth)
+ ->addPost('broadcast_type', 'RTMP_SWAP_ENABLED')
+ ->addPost('internal_only', 0)
+ ->getResponse(new Response\CreateLiveResponse());
+ }
+
+ /**
+ * Start a live broadcast.
+ *
+ * Note that you MUST first call `create()` to get a broadcast-ID and its
+ * RTMP upload-URL. Next, simply begin sending your actual video broadcast
+ * to the stream-upload URL. And then call `start()` with the broadcast-ID
+ * to make the stream available to viewers.
+ *
+ * Also note that broadcasting to the video stream URL must be done via
+ * other software, since it ISN'T (and won't be) handled by this library!
+ *
+ * Lastly, note that stopping the stream is done either via RTMP signals,
+ * which your broadcasting software MUST output properly (FFmpeg DOESN'T do
+ * it without special patching!), OR by calling the `end()` function.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param string|null $latitude (optional) Latitude.
+ * @param string|null $longitude (optional) Longitude.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StartLiveResponse
+ *
+ * @see Live::create()
+ * @see Live::end()
+ */
+ public function start(
+ $broadcastId,
+ $latitude = null,
+ $longitude = null)
+ {
+ $response = $this->ig->request("live/{$broadcastId}/start/")
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ if ($latitude !== null && $longitude !== null) {
+ $response->addPost('latitude', $latitude)
+ ->addPost('longitude', $longitude);
+ }
+
+ $response = $response->getResponse(new Response\StartLiveResponse());
+
+ if ($this->ig->isExperimentEnabled('ig_android_live_qa_broadcaster_v1_universe', 'is_enabled')) {
+ $this->_getQuestionStatus($broadcastId);
+ }
+
+ return $response;
+ }
+
+ /**
+ * Get question status.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ private function _getQuestionStatus(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/question_status/")
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('allow_question_submission', true)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Acknowledges a copyright warning from Instagram after detected via a heartbeat request.
+ *
+ * `NOTE:` It is recommended that you view the `liveBroadcast` example
+ * to see the proper usage of this function.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function resumeBroadcastAfterContentMatch(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/resume_broadcast_after_content_match/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * End a live broadcast.
+ *
+ * `NOTE:` To end your broadcast, you MUST use the `broadcast_id` value
+ * which was assigned to you in the `create()` response.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ * @param bool $copyrightWarning True when broadcast is ended via a copyright notice (optional).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ *
+ * @see Live::create()
+ * @see Live::start()
+ */
+ public function end(
+ $broadcastId,
+ $copyrightWarning = false)
+ {
+ return $this->ig->request("live/{$broadcastId}/end_broadcast/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('end_after_copyright_warning', $copyrightWarning)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Add a finished broadcast to your post-live feed (saved replay).
+ *
+ * The broadcast must have ended before you can call this function.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function addToPostLive(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/add_to_post_live/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Delete a saved post-live broadcast.
+ *
+ * @param string $broadcastId The broadcast ID in Instagram's internal format (ie "17854587811139572").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function deletePostLive(
+ $broadcastId)
+ {
+ return $this->ig->request("live/{$broadcastId}/delete_post_live/")
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Location.php b/vendor/mgp25/instagram-php/src/Request/Location.php
new file mode 100755
index 0000000..16c1d63
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Location.php
@@ -0,0 +1,329 @@
+ig->request('location_search/')
+ ->addParam('rank_token', $this->ig->account_id.'_'.Signatures::generateUUID())
+ ->addParam('latitude', $latitude)
+ ->addParam('longitude', $longitude);
+
+ if ($query === null) {
+ $locations->addParam('timestamp', time());
+ } else {
+ $locations->addParam('search_query', $query);
+ }
+
+ return $locations->getResponse(new Response\LocationResponse());
+ }
+
+ /**
+ * Search for Facebook locations by name.
+ *
+ * WARNING: The locations found by this function DO NOT work for attaching
+ * locations to media uploads. Use Location::search() instead!
+ *
+ * @param string $query Finds locations containing this string.
+ * @param string[]|int[] $excludeList Array of numerical location IDs (ie "17841562498105353")
+ * to exclude from the response, allowing you to skip locations
+ * from a previous call to get more results.
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FBLocationResponse
+ *
+ * @see FBLocationResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function findPlaces(
+ $query,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation. Do NOT use throwIfInvalidHashtag here.
+ if (!is_string($query) || $query === null) {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+ $location = $this->_paginateWithExclusion(
+ $this->ig->request('fbsearch/places/')
+ ->addParam('timezone_offset', date('Z'))
+ ->addParam('query', $query),
+ $excludeList,
+ $rankToken
+ );
+
+ try {
+ /** @var Response\FBLocationResponse $result */
+ $result = $location->getResponse(new Response\FBLocationResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\FBLocationResponse([
+ 'has_more' => false,
+ 'items' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Search for Facebook locations by geographical location.
+ *
+ * WARNING: The locations found by this function DO NOT work for attaching
+ * locations to media uploads. Use Location::search() instead!
+ *
+ * @param string $latitude Latitude.
+ * @param string $longitude Longitude.
+ * @param string|null $query (Optional) Finds locations containing this string.
+ * @param string[]|int[] $excludeList Array of numerical location IDs (ie "17841562498105353")
+ * to exclude from the response, allowing you to skip locations
+ * from a previous call to get more results.
+ * @param string|null $rankToken (When paginating) The rank token from the previous page's response.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FBLocationResponse
+ *
+ * @see FBLocationResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function findPlacesNearby(
+ $latitude,
+ $longitude,
+ $query = null,
+ $excludeList = [],
+ $rankToken = null)
+ {
+ $location = $this->_paginateWithExclusion(
+ $this->ig->request('fbsearch/places/')
+ ->addParam('lat', $latitude)
+ ->addParam('lng', $longitude)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken,
+ 50
+ );
+
+ if ($query !== null) {
+ $location->addParam('query', $query);
+ }
+
+ try {
+ /** @var Response\FBLocationResponse() $result */
+ $result = $location->getResponse(new Response\FBLocationResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\FBLocationResponse([
+ 'has_more' => false,
+ 'items' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get related locations by location ID.
+ *
+ * Note that this endpoint almost never succeeds, because most locations do
+ * not have ANY related locations!
+ *
+ * @param string $locationId The internal ID of a location (from a field
+ * such as "pk", "external_id" or "facebook_places_id").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\RelatedLocationResponse
+ */
+ public function getRelated(
+ $locationId)
+ {
+ return $this->ig->request("locations/{$locationId}/related/")
+ ->addParam('visited', json_encode(['id' => $locationId, 'type' => 'location']))
+ ->addParam('related_types', json_encode(['location']))
+ ->getResponse(new Response\RelatedLocationResponse());
+ }
+
+ /**
+ * Get the media feed for a location.
+ *
+ * Note that if your location is a "group" (such as a city), the feed will
+ * include media from multiple locations within that area. But if your
+ * location is a very specific place such as a specific night club, it will
+ * usually only include media from that exact location.
+ *
+ * @param string $locationId The internal ID of a location (from a field
+ * such as "pk", "external_id" or "facebook_places_id").
+ * @param string $rankToken The feed UUID. Use must use the same value for all pages of the feed.
+ * @param string|null $tab Section tab for locations. Values: "ranked" and "recent"
+ * @param int[]|null $nextMediaIds Used for pagination.
+ * @param int|null $nextPage Used for pagination.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LocationFeedResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFeed(
+ $locationId,
+ $rankToken,
+ $tab = 'ranked',
+ $nextMediaIds = null,
+ $nextPage = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidRankToken($rankToken);
+ if ($tab !== 'ranked' && $tab !== 'recent') {
+ throw new \InvalidArgumentException('The provided section tab is invalid.');
+ }
+
+ $locationFeed = $this->ig->request("locations/{$locationId}/sections/")
+ ->setSignedPost(false)
+ ->addPost('rank_token', $rankToken)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('session_id', $this->ig->session_id)
+ ->addPost('tab', $tab);
+
+ if ($nextMediaIds !== null) {
+ if (!is_array($nextMediaIds) || !array_filter($nextMediaIds, 'is_int')) {
+ throw new \InvalidArgumentException('Next media IDs must be an Int[].');
+ }
+ $locationFeed->addPost('next_media_ids', json_encode($nextMediaIds));
+ }
+
+ if ($nextPage !== null) {
+ $locationFeed->addPost('page', $nextPage);
+ }
+
+ if ($maxId !== null) {
+ $locationFeed->addPost('max_id', $maxId);
+ }
+
+ return $locationFeed->getResponse(new Response\LocationFeedResponse());
+ }
+
+ /**
+ * Get the story feed for a location.
+ *
+ * @param string $locationId The internal ID of a location (from a field
+ * such as "pk", "external_id" or "facebook_places_id").
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LocationStoryResponse
+ */
+ public function getStoryFeed(
+ $locationId)
+ {
+ return $this->ig->request("locations/{$locationId}/story/")
+ ->getResponse(new Response\LocationStoryResponse());
+ }
+
+ /**
+ * Mark LocationStoryResponse story media items as seen.
+ *
+ * The "story" property of a `LocationStoryResponse` only gives you a
+ * list of story media. It doesn't actually mark any stories as "seen",
+ * so the user doesn't know that you've seen their story. Actually
+ * marking the story as "seen" is done via this endpoint instead. The
+ * official app calls this endpoint periodically (with 1 or more items
+ * at a time) while watching a story.
+ *
+ * This tells the user that you've seen their story, and also helps
+ * Instagram know that it shouldn't give you those seen stories again
+ * if you request the same location feed multiple times.
+ *
+ * Tip: You can pass in the whole "getItems()" array from the location's
+ * "story" property, to easily mark all of the LocationStoryResponse's story
+ * media items as seen.
+ *
+ * @param Response\LocationStoryResponse $locationFeed The location feed
+ * response object which
+ * the story media items
+ * came from. The story
+ * items MUST belong to it.
+ * @param Response\Model\Item[] $items Array of one or more
+ * story media Items.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Story::markMediaSeen()
+ * @see Hashtag::markStoryMediaSeen()
+ */
+ public function markStoryMediaSeen(
+ Response\LocationStoryResponse $locationFeed,
+ array $items)
+ {
+ // Extract the Location Story-Tray ID from the user's location response.
+ // NOTE: This can NEVER fail if the user has properly given us the exact
+ // same location response that they got the story items from!
+ $sourceId = '';
+ if ($locationFeed->getStory() instanceof Response\Model\StoryTray) {
+ $sourceId = $locationFeed->getStory()->getId();
+ }
+ if (!strlen($sourceId)) {
+ throw new \InvalidArgumentException('Your provided LocationStoryResponse is invalid and does not contain any Location Story-Tray ID.');
+ }
+
+ // Ensure they only gave us valid items for this location response.
+ // NOTE: We validate since people cannot be trusted to use their brain.
+ $validIds = [];
+ foreach ($locationFeed->getStory()->getItems() as $item) {
+ $validIds[$item->getId()] = true;
+ }
+ foreach ($items as $item) {
+ // NOTE: We only check Items here. Other data is rejected by Internal.
+ if ($item instanceof Response\Model\Item && !isset($validIds[$item->getId()])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The item with ID "%s" does not belong to this LocationStoryResponse.',
+ $item->getId()
+ ));
+ }
+ }
+
+ // Mark the story items as seen, with the location as source ID.
+ return $this->ig->internal->markStoryMediaSeen($items, $sourceId);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Media.php b/vendor/mgp25/instagram-php/src/Request/Media.php
new file mode 100755
index 0000000..ee748e8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Media.php
@@ -0,0 +1,848 @@
+ig->request("media/{$mediaId}/info/")
+ ->getResponse(new Response\MediaInfoResponse());
+ }
+
+ /**
+ * Delete a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string|int $mediaType The type of the media item you are deleting. One of: "PHOTO", "VIDEO"
+ * "CAROUSEL", or the raw value of the Item's "getMediaType()" function.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaDeleteResponse
+ */
+ public function delete(
+ $mediaId,
+ $mediaType = 'PHOTO')
+ {
+ $mediaType = Utils::checkMediaType($mediaType);
+
+ return $this->ig->request("media/{$mediaId}/delete/")
+ ->addParam('media_type', $mediaType)
+ ->addPost('igtv_feed_preview', false)
+ ->addPost('media_id', $mediaId)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\MediaDeleteResponse());
+ }
+
+ /**
+ * Edit media.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $captionText Caption to use for the media.
+ * @param array|null $metadata (optional) Associative array of optional metadata to edit:
+ * "usertags" - special array with user tagging instructions,
+ * if you want to modify the user tags;
+ * "location" - a Location model object to set the media location,
+ * or boolean FALSE to remove any location from the media.
+ * @param string|int $mediaType The type of the media item you are editing. One of: "PHOTO", "VIDEO"
+ * "CAROUSEL", or the raw value of the Item's "getMediaType()" function.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditMediaResponse
+ *
+ * @see Usertag::tagMedia() for an example of proper "usertags" metadata formatting.
+ * @see Usertag::untagMedia() for an example of proper "usertags" metadata formatting.
+ */
+ public function edit(
+ $mediaId,
+ $captionText = '',
+ array $metadata = null,
+ $mediaType = 'PHOTO')
+ {
+ $mediaType = Utils::checkMediaType($mediaType);
+
+ $request = $this->ig->request("media/{$mediaId}/edit_media/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('caption_text', $captionText);
+
+ if (isset($metadata['usertags'])) {
+ Utils::throwIfInvalidUsertags($metadata['usertags']);
+ $request->addPost('usertags', json_encode($metadata['usertags']));
+ }
+
+ if (isset($metadata['location'])) {
+ if ($metadata['location'] === false) {
+ // The user wants to remove the current location from the media.
+ $request->addPost('location', '{}');
+ } else {
+ // The user wants to add/change the location of the media.
+ if (!$metadata['location'] instanceof Response\Model\Location) {
+ throw new \InvalidArgumentException('The "location" metadata value must be an instance of \InstagramAPI\Response\Model\Location.');
+ }
+
+ $request
+ ->addPost('location', Utils::buildMediaLocationJSON($metadata['location']))
+ ->addPost('geotag_enabled', '1')
+ ->addPost('posting_latitude', $metadata['location']->getLat())
+ ->addPost('posting_longitude', $metadata['location']->getLng())
+ ->addPost('media_latitude', $metadata['location']->getLat())
+ ->addPost('media_longitude', $metadata['location']->getLng());
+
+ if ($mediaType === 'CAROUSEL') { // Albums need special handling.
+ $request
+ ->addPost('exif_latitude', 0.0)
+ ->addPost('exif_longitude', 0.0);
+ } else { // All other types of media use "av_" instead of "exif_".
+ $request
+ ->addPost('av_latitude', 0.0)
+ ->addPost('av_longitude', 0.0);
+ }
+ }
+ }
+
+ return $request->getResponse(new Response\EditMediaResponse());
+ }
+
+ /**
+ * Like a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param int $feedPosition The position of the media in the feed.
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ * @param bool $carouselBumped (optional) If the media is carousel bumped.
+ * @param array $extraData (optional) Depending on the module name, additional data is required.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ *
+ * @see Media::_parseLikeParameters() For all supported modules and required parameters.
+ */
+ public function like(
+ $mediaId,
+ $feedPosition,
+ $module = 'feed_timeline',
+ $carouselBumped = false,
+ array $extraData = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/like/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('media_id', $mediaId)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('container_module', $module)
+ ->addPost('feed_position', $feedPosition)
+ ->addPost('is_carousel_bumped_post', $carouselBumped)
+ ->addPost('device_id', $this->ig->device_id);
+
+ if (isset($extraData['carousel_media'])) {
+ $request->addPost('carousel_index', $extraData['carousel_index']);
+ }
+
+ $extraData['media_id'] = $mediaId;
+ $this->_parseLikeParameters('like', $request, $module, $extraData);
+
+ return $request->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unlike a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ * @param array $extraData (optional) Depending on the module name, additional data is required.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ *
+ * @see Media::_parseLikeParameters() For all supported modules and required parameters.
+ */
+ public function unlike(
+ $mediaId,
+ $module = 'feed_timeline',
+ array $extraData = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/unlike/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('media_id', $mediaId)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('module_name', $module);
+
+ $this->_parseLikeParameters('unlike', $request, $module, $extraData);
+
+ return $request->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get feed of your liked media.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LikeFeedResponse
+ */
+ public function getLikedFeed(
+ $maxId = null)
+ {
+ $request = $this->ig->request('feed/liked/');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\LikeFeedResponse());
+ }
+
+ /**
+ * Get list of users who liked a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaLikersResponse
+ */
+ public function getLikers(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/likers/")->getResponse(new Response\MediaLikersResponse());
+ }
+
+ /**
+ * Get a simplified, chronological list of users who liked a media item.
+ *
+ * WARNING! DANGEROUS! Although this function works, we don't know
+ * whether it's used by Instagram's app right now. If it isn't used by
+ * the app, then you can easily get BANNED for using this function!
+ *
+ * If you call this function, you do that AT YOUR OWN RISK and you
+ * risk losing your Instagram account! This notice will be removed if
+ * the function is safe to use. Otherwise this whole function will
+ * be removed someday, if it wasn't safe.
+ *
+ * Only use this if you are OK with possibly losing your account!
+ *
+ * TODO: Research when/if the official app calls this function.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaLikersResponse
+ */
+ public function getLikersChrono(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/likers_chrono/")->getResponse(new Response\MediaLikersResponse());
+ }
+
+ /**
+ * Enable comments for a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function enableComments(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/enable_comments/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Disable comments for a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function disableComments(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/disable_comments/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->setSignedPost(false)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Post a comment on a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentText Your comment text.
+ * @param string|null $replyCommentId (optional) The comment ID you are replying to, if this is a reply (ie "17895795823020906");
+ * when replying, your $commentText MUST contain an @-mention at the start (ie "@theirusername Hello!").
+ * @param string $module (optional) From which app module (page) you're performing this action.
+ * "comments_v2" - In App: clicking on comments button,
+ * "self_comments_v2" - In App: commenting on your own post,
+ * "comments_v2_feed_timeline" - Unknown,
+ * "comments_v2_feed_contextual_hashtag" - Unknown,
+ * "comments_v2_photo_view_profile" - Unknown,
+ * "comments_v2_video_view_profile" - Unknown,
+ * "comments_v2_media_view_profile" - Unknown,
+ * "comments_v2_feed_contextual_location" - Unknown,
+ * "modal_comment_composer_feed_timeline" - In App: clicking on prompt from timeline.
+ * @param int $carouselIndex (optional) The image selected in a carousel while liking an image.
+ * @param int $feedPosition (optional) The position of the media in the feed.
+ * @param bool $feedBumped (optional) If Instagram bumped this post to the top of your feed.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentResponse
+ */
+ public function comment(
+ $mediaId,
+ $commentText,
+ $replyCommentId = null,
+ $module = 'comments_v2',
+ $carouselIndex = 0,
+ $feedPosition = 0,
+ $feedBumped = false)
+ {
+ $request = $this->ig->request("media/{$mediaId}/comment/")
+ ->addPost('user_breadcrumb', Utils::generateUserBreadcrumb(mb_strlen($commentText)))
+ ->addPost('idempotence_token', Signatures::generateUUID())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('comment_text', $commentText)
+ ->addPost('container_module', $module)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('carousel_index', $carouselIndex)
+ ->addPost('feed_position', $feedPosition)
+ ->addPost('is_carousel_bumped_post', $feedBumped);
+ if ($replyCommentId !== null) {
+ $request->addPost('replied_to_comment_id', $replyCommentId);
+ }
+
+ return $request->getResponse(new Response\CommentResponse());
+ }
+
+ /**
+ * Get media comments.
+ *
+ * Note that this endpoint supports both backwards and forwards pagination.
+ * The only one you should really care about is "max_id" for backwards
+ * ("load older comments") pagination in normal cases. By default, if no
+ * parameter is provided, Instagram gives you the latest page of comments
+ * and then paginates backwards via the "max_id" parameter (and the correct
+ * value for it is the "next_max_id" in the response).
+ *
+ * However, if you come to the comments "from a Push notification" (uses the
+ * "target_comment_id" parameter), then the response will ALSO contain a
+ * "next_min_id" value. In that case, you can get newer comments (than the
+ * target comment) by using THAT value and the "min_id" parameter instead.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param array $options An associative array of optional parameters, including:
+ * "max_id" - next "maximum ID" (get older comments, before this ID), used for backwards pagination;
+ * "min_id" - next "minimum ID" (get newer comments, after this ID), used for forwards pagination;
+ * "target_comment_id" - used by comment Push notifications to retrieve the page with the specific comment.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaCommentsResponse
+ */
+ public function getComments(
+ $mediaId,
+ array $options = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/comments/")
+ ->addParam('can_support_threading', true);
+
+ // Pagination.
+ if (isset($options['min_id']) && isset($options['max_id'])) {
+ throw new \InvalidArgumentException('You can use either "min_id" or "max_id", but not both at the same time.');
+ }
+ if (isset($options['min_id'])) {
+ $request->addParam('min_id', $options['min_id']);
+ }
+ if (isset($options['max_id'])) {
+ $request->addParam('max_id', $options['max_id']);
+ }
+
+ // Request specific comment (does NOT work together with pagination!).
+ // NOTE: If you try pagination params together with this param, then the
+ // server will reject the request completely and give nothing back!
+ if (isset($options['target_comment_id'])) {
+ if (isset($options['min_id']) || isset($options['max_id'])) {
+ throw new \InvalidArgumentException('You cannot use the "target_comment_id" parameter together with the "min_id" or "max_id" parameters.');
+ }
+ $request->addParam('target_comment_id', $options['target_comment_id']);
+ }
+
+ return $request->getResponse(new Response\MediaCommentsResponse());
+ }
+
+ /**
+ * Get the replies to a specific media comment.
+ *
+ * You should be sure that the comment actually HAS more replies before
+ * calling this endpoint! In that case, the comment itself will have a
+ * non-zero "child comment count" value, as well as some "preview comments".
+ *
+ * If the number of preview comments doesn't match the full "child comments"
+ * count, then you are ready to call this endpoint to retrieve the rest of
+ * them. Do NOT call it frivolously for comments that have no child comments
+ * or where you already have all of them via the child comment previews!
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentId The parent comment's ID.
+ * @param array $options An associative array of optional parameters, including:
+ * "max_id" - next "maximum ID" (get older comments, before this ID), used for backwards pagination;
+ * "min_id" - next "minimum ID" (get newer comments, after this ID), used for forwards pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaCommentRepliesResponse
+ */
+ public function getCommentReplies(
+ $mediaId,
+ $commentId,
+ array $options = [])
+ {
+ $request = $this->ig->request("media/{$mediaId}/comments/{$commentId}/inline_child_comments/");
+
+ if (isset($options['min_id'], $options['max_id'])) {
+ throw new \InvalidArgumentException('You can use either "min_id" or "max_id", but not both at the same time.');
+ }
+
+ if (isset($options['max_id'])) {
+ $request->addParam('max_id', $options['max_id']);
+ } elseif (isset($options['min_id'])) {
+ $request->addParam('min_id', $options['min_id']);
+ }
+
+ return $request->getResponse(new Response\MediaCommentRepliesResponse());
+ }
+
+ /**
+ * Delete a comment.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentId The comment's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DeleteCommentResponse
+ */
+ public function deleteComment(
+ $mediaId,
+ $commentId)
+ {
+ return $this->ig->request("media/{$mediaId}/comment/{$commentId}/delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\DeleteCommentResponse());
+ }
+
+ /**
+ * Delete multiple comments.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string|string[] $commentIds The IDs of one or more comments to delete.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DeleteCommentResponse
+ */
+ public function deleteComments(
+ $mediaId,
+ $commentIds)
+ {
+ if (is_array($commentIds)) {
+ $commentIds = implode(',', $commentIds);
+ }
+
+ return $this->ig->request("media/{$mediaId}/comment/bulk_delete/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('comment_ids_to_delete', $commentIds)
+ ->getResponse(new Response\DeleteCommentResponse());
+ }
+
+ /**
+ * Like a comment.
+ *
+ * @param string $commentId The comment's ID.
+ * @param int $feedPosition The position of the media item in the feed.
+ * @param string $module From which module you're preforming this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentLikeUnlikeResponse
+ */
+ public function likeComment(
+ $commentId,
+ $feedPosition,
+ $module = 'self_comments_v2')
+ {
+ return $this->ig->request("media/{$commentId}/comment_like/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('is_carousel_bumped_post', false)
+ ->addPost('container_module', $module)
+ ->addPost('feed_position', $feedPosition)
+ ->getResponse(new Response\CommentLikeUnlikeResponse());
+ }
+
+ /**
+ * Unlike a comment.
+ *
+ * @param string $commentId The comment's ID.
+ * @param int $feedPosition The position of the media item in the feed.
+ * @param string $module From which module you're preforming this action.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentLikeUnlikeResponse
+ */
+ public function unlikeComment(
+ $commentId,
+ $feedPosition,
+ $module = 'self_comments_v2')
+ {
+ return $this->ig->request("media/{$commentId}/comment_unlike/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('is_carousel_bumped_post', false)
+ ->addPost('container_module', $module)
+ ->addPost('feed_position', $feedPosition)
+ ->getResponse(new Response\CommentLikeUnlikeResponse());
+ }
+
+ /**
+ * Get list of users who liked a comment.
+ *
+ * @param string $commentId The comment's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CommentLikersResponse
+ */
+ public function getCommentLikers(
+ $commentId)
+ {
+ return $this->ig->request("media/{$commentId}/comment_likers/")->getResponse(new Response\CommentLikersResponse());
+ }
+
+ /**
+ * Translates comments and/or media captions.
+ *
+ * Note that the text will be translated to American English (en-US).
+ *
+ * @param string|string[] $commentIds The IDs of one or more comments and/or media IDs
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TranslateResponse
+ */
+ public function translateComments(
+ $commentIds)
+ {
+ if (is_array($commentIds)) {
+ $commentIds = implode(',', $commentIds);
+ }
+
+ return $this->ig->request("language/bulk_translate/?comment_ids={$commentIds}")
+ ->getResponse(new Response\TranslateResponse());
+ }
+
+ /**
+ * Validate a web URL for acceptable use as external link.
+ *
+ * This endpoint lets you check if the URL is allowed by Instagram, and is
+ * helpful to call before you try to use a web URL in your media links.
+ *
+ * @param string $url The URL you want to validate.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ValidateURLResponse
+ */
+ public function validateURL(
+ $url)
+ {
+ return $this->ig->request('media/validate_reel_url/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('url', $url)
+ ->getResponse(new Response\ValidateURLResponse());
+ }
+
+ /**
+ * Save a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SaveAndUnsaveMedia
+ */
+ public function save(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/save/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SaveAndUnsaveMedia());
+ }
+
+ /**
+ * Unsave a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SaveAndUnsaveMedia
+ */
+ public function unsave(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/unsave/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\SaveAndUnsaveMedia());
+ }
+
+ /**
+ * Get saved media items feed.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SavedFeedResponse
+ */
+ public function getSavedFeed(
+ $maxId = null)
+ {
+ $request = $this->ig->request('feed/saved/');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\SavedFeedResponse());
+ }
+
+ /**
+ * Get blocked media.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BlockedMediaResponse
+ */
+ public function getBlockedMedia()
+ {
+ return $this->ig->request('media/blocked/')
+ ->getResponse(new Response\BlockedMediaResponse());
+ }
+
+ /**
+ * Report media as spam.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $sourceName (optional) Source of the media.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function report(
+ $mediaId,
+ $sourceName = 'feed_contextual_chain')
+ {
+ return $this->ig->request("media/{$mediaId}/flag_media/")
+ ->addPost('media_id', $mediaId)
+ ->addPost('source_name', $sourceName)
+ ->addPost('reason_id', '1')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Report a media comment as spam.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $commentId The comment's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function reportComment(
+ $mediaId,
+ $commentId)
+ {
+ return $this->ig->request("media/{$mediaId}/comment/{$commentId}/flag/")
+ ->addPost('media_id', $mediaId)
+ ->addPost('comment_id', $commentId)
+ ->addPost('reason', '1')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get media permalink.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PermalinkResponse
+ */
+ public function getPermalink(
+ $mediaId)
+ {
+ return $this->ig->request("media/{$mediaId}/permalink/")
+ ->addParam('share_to_app', 'copy_link')
+ ->getResponse(new Response\PermalinkResponse());
+ }
+
+ /**
+ * Validate and update the parameters for a like or unlike request.
+ *
+ * @param string $type What type of request this is (can be "like" or "unlike").
+ * @param Request $request The request to fill with the parsed data.
+ * @param string $module From which app module (page) you're performing this action.
+ * @param array $extraData Depending on the module name, additional data is required.
+ *
+ * @throws \InvalidArgumentException
+ */
+ protected function _parseLikeParameters(
+ $type,
+ Request $request,
+ $module,
+ array $extraData)
+ {
+ // Is this a "double-tap to like"? Note that Instagram doesn't have
+ // "double-tap to unlike". So this can only be "1" if it's a "like".
+ if ($type === 'like' && isset($extraData['double_tap']) && $extraData['double_tap']) {
+ $request->addUnsignedPost('d', 1);
+ } else {
+ $request->addUnsignedPost('d', 0); // Must always be 0 for "unlike".
+ }
+
+ // Now parse the necessary parameters for the selected module.
+ switch ($module) {
+ case 'feed_contextual_post': // "Explore" tab.
+ if (isset($extraData['explore_source_token'])) {
+ // The explore media `Item::getExploreSourceToken()` value.
+ $request->addPost('explore_source_token', $extraData['explore_source_token']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'profile': // LIST VIEW (when posts are shown vertically by the app
+ // one at a time (as in the Timeline tab)): Any media on
+ // a user profile (their timeline) in list view mode.
+ case 'media_view_profile': // GRID VIEW (standard 3x3): Album (carousel)
+ // on a user profile (their timeline).
+ case 'video_view_profile': // GRID VIEW (standard 3x3): Video on a user
+ // profile (their timeline).
+ case 'photo_view_profile': // GRID VIEW (standard 3x3): Photo on a user
+ // profile (their timeline).
+ if (isset($extraData['username']) && isset($extraData['user_id'])) {
+ // Username and id of the media's owner (the profile owner).
+ $request->addPost('username', $extraData['username'])
+ ->addPost('user_id', $extraData['user_id']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'feed_contextual_hashtag': // "Hashtag" search result.
+ if (isset($extraData['hashtag'])) {
+ // The hashtag where the app found this media.
+ Utils::throwIfInvalidHashtag($extraData['hashtag']);
+ $request->addPost('hashtag', $extraData['hashtag']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'feed_contextual_location': // "Location" search result.
+ if (isset($extraData['location_id'])) {
+ // The location ID of this media.
+ $request->addPost('location_id', $extraData['location_id']);
+ } else {
+ throw new \InvalidArgumentException(sprintf('Missing extra data for module "%s".', $module));
+ }
+ break;
+ case 'feed_timeline': // "Timeline" tab (the global Home-feed with all
+ // kinds of mixed news).
+ case 'newsfeed': // "Followings Activity" feed tab. Used when
+ // liking/unliking a post that we clicked on from a
+ // single-activity "xyz liked abc's post" entry.
+ case 'feed_contextual_newsfeed_multi_media_liked': // "Followings
+ // Activity" feed
+ // tab. Used when
+ // liking/unliking a
+ // post that we
+ // clicked on from a
+ // multi-activity
+ // "xyz liked 5
+ // posts" entry.
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid module name. %s does not correspond to any of the valid module names.', $module));
+ }
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Metadata/Internal.php b/vendor/mgp25/instagram-php/src/Request/Metadata/Internal.php
new file mode 100755
index 0000000..d56b9bc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Metadata/Internal.php
@@ -0,0 +1,291 @@
+_uploadId = $uploadId;
+ } else {
+ $this->_uploadId = Utils::generateUploadId();
+ }
+ $this->_bestieMedia = false;
+ }
+
+ /**
+ * Set story view mode.
+ *
+ * @param string $viewMode View mode. Use STORY_VIEW_MODE_ONCE and STORY_VIEW_MODE_REPLAYABLE constants as values.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return string
+ */
+ public function setStoryViewMode(
+ $viewMode)
+ {
+ if ($viewMode != Constants::STORY_VIEW_MODE_ONCE
+ && $viewMode != Constants::STORY_VIEW_MODE_REPLAYABLE
+ ) {
+ throw new \InvalidArgumentException('Unknown view mode: '.$viewMode);
+ }
+
+ $this->_storyViewMode = $viewMode;
+
+ return $this->_storyViewMode;
+ }
+
+ /**
+ * Get story view mode.
+ *
+ * @return string
+ */
+ public function getStoryViewMode()
+ {
+ return $this->_storyViewMode;
+ }
+
+ /**
+ * @return PhotoDetails
+ */
+ public function getPhotoDetails()
+ {
+ return $this->_photoDetails;
+ }
+
+ /**
+ * @return VideoDetails
+ */
+ public function getVideoDetails()
+ {
+ return $this->_videoDetails;
+ }
+
+ /**
+ * Set video details from the given filename.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $videoFilename
+ *
+ * @throws \InvalidArgumentException If the video file is missing or invalid, or Instagram won't allow this video.
+ * @throws \RuntimeException In case of various processing errors.
+ *
+ * @return VideoDetails
+ */
+ public function setVideoDetails(
+ $targetFeed,
+ $videoFilename)
+ {
+ // Figure out the video file details.
+ // NOTE: We do this first, since it validates whether the video file is
+ // valid and lets us avoid wasting time uploading totally invalid files!
+ $this->_videoDetails = new VideoDetails($videoFilename);
+
+ // Validate the video details and throw if Instagram won't allow it.
+ $this->_videoDetails->validate(ConstraintsFactory::createFor($targetFeed));
+
+ return $this->_videoDetails;
+ }
+
+ /**
+ * Set photo details from the given filename.
+ *
+ * @param int $targetFeed One of the FEED_X constants.
+ * @param string $photoFilename
+ *
+ * @throws \InvalidArgumentException If the photo file is missing or invalid, or Instagram won't allow this photo.
+ * @throws \RuntimeException In case of various processing errors.
+ *
+ * @return PhotoDetails
+ */
+ public function setPhotoDetails(
+ $targetFeed,
+ $photoFilename)
+ {
+ // Figure out the photo file details.
+ // NOTE: We do this first, since it validates whether the photo file is
+ // valid and lets us avoid wasting time uploading totally invalid files!
+ $this->_photoDetails = new PhotoDetails($photoFilename);
+
+ // Validate the photo details and throw if Instagram won't allow it.
+ $this->_photoDetails->validate(ConstraintsFactory::createFor($targetFeed));
+
+ return $this->_photoDetails;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUploadId()
+ {
+ return $this->_uploadId;
+ }
+
+ /**
+ * Set upload URLs from a UploadJobVideoResponse response.
+ *
+ * @param UploadJobVideoResponse $response
+ *
+ * @return VideoUploadUrl[]
+ */
+ public function setVideoUploadUrls(
+ UploadJobVideoResponse $response)
+ {
+ $this->_videoUploadUrls = [];
+ if ($response->getVideoUploadUrls() !== null) {
+ $this->_videoUploadUrls = $response->getVideoUploadUrls();
+ }
+
+ return $this->_videoUploadUrls;
+ }
+
+ /**
+ * @return VideoUploadUrl[]
+ */
+ public function getVideoUploadUrls()
+ {
+ return $this->_videoUploadUrls;
+ }
+
+ /**
+ * @return UploadVideoResponse
+ */
+ public function getVideoUploadResponse()
+ {
+ return $this->_videoUploadResponse;
+ }
+
+ /**
+ * @param UploadVideoResponse $videoUploadResponse
+ */
+ public function setVideoUploadResponse(
+ UploadVideoResponse $videoUploadResponse)
+ {
+ $this->_videoUploadResponse = $videoUploadResponse;
+ }
+
+ /**
+ * @return UploadPhotoResponse
+ */
+ public function getPhotoUploadResponse()
+ {
+ return $this->_photoUploadResponse;
+ }
+
+ /**
+ * @param UploadPhotoResponse $photoUploadResponse
+ */
+ public function setPhotoUploadResponse(
+ UploadPhotoResponse $photoUploadResponse)
+ {
+ $this->_photoUploadResponse = $photoUploadResponse;
+ }
+
+ /**
+ * Add Direct recipients to metadata.
+ *
+ * @param array $recipients
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return self
+ */
+ public function setDirectRecipients(
+ array $recipients)
+ {
+ if (isset($recipients['users'])) {
+ $this->_directUsers = $recipients['users'];
+ $this->_directThreads = '[]';
+ } elseif (isset($recipients['thread'])) {
+ $this->_directUsers = '[]';
+ $this->_directThreads = $recipients['thread'];
+ } else {
+ throw new \InvalidArgumentException('Please provide at least one recipient.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDirectThreads()
+ {
+ return $this->_directThreads;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDirectUsers()
+ {
+ return $this->_directUsers;
+ }
+
+ /**
+ * Set bestie media state.
+ *
+ * @param bool $bestieMedia
+ */
+ public function setBestieMedia(
+ $bestieMedia)
+ {
+ $this->_bestieMedia = $bestieMedia;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isBestieMedia()
+ {
+ return $this->_bestieMedia;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/People.php b/vendor/mgp25/instagram-php/src/Request/People.php
new file mode 100755
index 0000000..fc1aa24
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/People.php
@@ -0,0 +1,1265 @@
+ig->request("users/{$userId}/info/");
+ if ($module !== null) {
+ $request->addParam('from_module', $module);
+ }
+
+ return $request->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Get details about a specific user via their username.
+ *
+ * NOTE: The real app only uses this endpoint for profiles opened via "@mentions".
+ *
+ * @param string $username Username as string (NOT as a numerical ID).
+ * @param string $module From which app module (page) you have opened the profile.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see People::getInfoById() For the list of supported modules.
+ */
+ public function getInfoByName(
+ $username,
+ $module = 'feed_timeline')
+ {
+ return $this->ig->request("users/{$username}/usernameinfo/")
+ ->addParam('from_module', $module)
+ ->getResponse(new Response\UserInfoResponse());
+ }
+
+ /**
+ * Get the numerical UserPK ID for a specific user via their username.
+ *
+ * This is just a convenient helper function. You may prefer to use
+ * People::getInfoByName() instead, which lets you see more details.
+ *
+ * @param string $username Username as string (NOT as a numerical ID).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return string Their numerical UserPK ID.
+ *
+ * @see People::getInfoByName()
+ */
+ public function getUserIdForName(
+ $username)
+ {
+ return $this->getInfoByName($username)->getUser()->getPk();
+ }
+
+ /**
+ * Get user details about your own account.
+ *
+ * Also try Account::getCurrentUser() instead, for account details.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserInfoResponse
+ *
+ * @see Account::getCurrentUser()
+ */
+ public function getSelfInfo()
+ {
+ return $this->getInfoById($this->ig->account_id);
+ }
+
+ /**
+ * Get other people's recent activities related to you and your posts.
+ *
+ * This feed has information about when people interact with you, such as
+ * liking your posts, commenting on your posts, tagging you in photos or in
+ * comments, people who started following you, etc.
+ *
+ * @param bool $prefetch Indicates if request is called due to prefetch.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ActivityNewsResponse
+ */
+ public function getRecentActivityInbox(
+ $prefetch = false)
+ {
+ $request = $this->ig->request('news/inbox/');
+ if ($prefetch) {
+ $request->addHeader('X-IG-Prefetch-Request', 'foreground');
+ }
+
+ return $request->getResponse(new Response\ActivityNewsResponse());
+ }
+
+ /**
+ * Get news feed with recent activities by accounts you follow.
+ *
+ * This feed has information about the people you follow, such as what posts
+ * they've liked or that they've started following other people.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowingRecentActivityResponse
+ */
+ public function getFollowingRecentActivity(
+ $maxId = null)
+ {
+ $activity = $this->ig->request('news/');
+ if ($maxId !== null) {
+ $activity->addParam('max_id', $maxId);
+ }
+
+ return $activity->getResponse(new Response\FollowingRecentActivityResponse());
+ }
+
+ /**
+ * Retrieve bootstrap user data (autocompletion user list).
+ *
+ * WARNING: This is a special, very heavily throttled API endpoint.
+ * Instagram REQUIRES that you wait several minutes between calls to it.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BootstrapUsersResponse|null Will be NULL if throttled by Instagram.
+ */
+ public function getBootstrapUsers()
+ {
+ $surfaces = [
+ 'autocomplete_user_list',
+ 'coefficient_besties_list_ranking',
+ 'coefficient_rank_recipient_user_suggestion',
+ 'coefficient_ios_section_test_bootstrap_ranking',
+ 'coefficient_direct_recipients_ranking_variant_2',
+ ];
+
+ try {
+ $request = $this->ig->request('scores/bootstrap/users/')
+ ->addParam('surfaces', json_encode($surfaces));
+
+ return $request->getResponse(new Response\BootstrapUsersResponse());
+ } catch (ThrottledException $e) {
+ // Throttling is so common that we'll simply return NULL in that case.
+ return null;
+ }
+ }
+
+ /**
+ * Show a user's friendship status with you.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipsShowResponse
+ */
+ public function getFriendship(
+ $userId)
+ {
+ return $this->ig->request("friendships/show/{$userId}/")->getResponse(new Response\FriendshipsShowResponse());
+ }
+
+ /**
+ * Show multiple users' friendship status with you.
+ *
+ * @param string|string[] $userList List of numerical UserPK IDs.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipsShowManyResponse
+ */
+ public function getFriendships(
+ $userList)
+ {
+ if (is_array($userList)) {
+ $userList = implode(',', $userList);
+ }
+
+ return $this->ig->request('friendships/show_many/')
+ ->setSignedPost(false)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('user_ids', $userList)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\FriendshipsShowManyResponse());
+ }
+
+ /**
+ * Get list of pending friendship requests.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ */
+ public function getPendingFriendships()
+ {
+ $request = $this->ig->request('friendships/pending/');
+
+ return $request->getResponse(new Response\FollowerAndFollowingResponse());
+ }
+
+ /**
+ * Approve a friendship request.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function approveFriendship(
+ $userId)
+ {
+ return $this->ig->request("friendships/approve/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Reject a friendship request.
+ *
+ * Note that the user can simply send you a new request again, after your
+ * rejection. If they're harassing you, use People::block() instead.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function rejectFriendship(
+ $userId)
+ {
+ return $this->ig->request("friendships/ignore/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Remove one of your followers.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function removeFollower(
+ $userId)
+ {
+ return $this->ig->request("friendships/remove_follower/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Mark user over age in order to see sensitive content.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function markUserOverage(
+ $userId)
+ {
+ return $this->ig->request("friendships/mark_user_overage/{$userId}/feed/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get list of who a user is following.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFollowing(
+ $userId,
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidRankToken($rankToken);
+ $request = $this->ig->request("friendships/{$userId}/following/")
+ ->addParam('includes_hashtags', true)
+ ->addParam('rank_token', $rankToken);
+ if ($searchQuery !== null) {
+ $request->addParam('query', $searchQuery);
+ }
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\FollowerAndFollowingResponse());
+ }
+
+ /**
+ * Get list of who a user is followed by.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getFollowers(
+ $userId,
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ Utils::throwIfInvalidRankToken($rankToken);
+ $request = $this->ig->request("friendships/{$userId}/followers/")
+ ->addParam('rank_token', $rankToken);
+ if ($searchQuery !== null) {
+ $request->addParam('query', $searchQuery);
+ }
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\FollowerAndFollowingResponse());
+ }
+
+ /**
+ * Get list of who you are following.
+ *
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getSelfFollowing(
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ return $this->getFollowing($this->ig->account_id, $rankToken, $searchQuery, $maxId);
+ }
+
+ /**
+ * Get list of your own followers.
+ *
+ * @param string $rankToken The list UUID. You must use the same value for all pages of the list.
+ * @param string|null $searchQuery Limit the userlist to ones matching the query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FollowerAndFollowingResponse
+ *
+ * @see Signatures::generateUUID() To create a UUID.
+ * @see examples/rankTokenUsage.php For an example.
+ */
+ public function getSelfFollowers(
+ $rankToken,
+ $searchQuery = null,
+ $maxId = null)
+ {
+ return $this->getFollowers($this->ig->account_id, $rankToken, $searchQuery, $maxId);
+ }
+
+ /**
+ * Search for Instagram users.
+ *
+ * @param string $query The username or full name to search for.
+ * @param string[]|int[] $excludeList Array of numerical user IDs (ie "4021088339")
+ * to exclude from the response, allowing you to skip users
+ * from a previous call to get more results.
+ * @param string|null $rankToken A rank token from a first call response.
+ *
+ * @throws \InvalidArgumentException If invalid query or
+ * trying to exclude too
+ * many user IDs.
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SearchUserResponse
+ *
+ * @see SearchUserResponse::getRankToken() To get a rank token from the response.
+ * @see examples/paginateWithExclusion.php For an example.
+ */
+ public function search(
+ $query,
+ array $excludeList = [],
+ $rankToken = null)
+ {
+ // Do basic query validation.
+ if (!is_string($query) || $query === '') {
+ throw new \InvalidArgumentException('Query must be a non-empty string.');
+ }
+
+ $request = $this->_paginateWithExclusion(
+ $this->ig->request('users/search/')
+ ->addParam('q', $query)
+ ->addParam('timezone_offset', date('Z')),
+ $excludeList,
+ $rankToken
+ );
+
+ try {
+ /** @var Response\SearchUserResponse $result */
+ $result = $request->getResponse(new Response\SearchUserResponse());
+ } catch (RequestHeadersTooLargeException $e) {
+ $result = new Response\SearchUserResponse([
+ 'has_more' => false,
+ 'num_results' => 0,
+ 'users' => [],
+ 'rank_token' => $rankToken,
+ ]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get business account details.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\AccountDetailsResponse
+ */
+ public function getAccountDetails(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/account_details/")
+ ->getResponse(new Response\AccountDetailsResponse());
+ }
+
+ /**
+ * Get a business account's former username(s).
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FormerUsernamesResponse
+ */
+ public function getFormerUsernames(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/former_usernames/")
+ ->getResponse(new Response\FormerUsernamesResponse());
+ }
+
+ /**
+ * Get a business account's shared follower base with similar accounts.
+ *
+ * @param string $userId Numerical UserPk ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SharedFollowersResponse
+ */
+ public function getSharedFollowers(
+ $userId)
+ {
+ return $this->ig->request("users/{$userId}/shared_follower_accounts/")
+ ->getResponse(new Response\SharedFollowersResponse());
+ }
+
+ /**
+ * Get a business account's active ads on feed.
+ *
+ * @param string $targetUserId Numerical UserPk ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ActiveFeedAdsResponse
+ */
+ public function getActiveFeedAds(
+ $targetUserId,
+ $maxId = null)
+ {
+ return $this->_getActiveAds($targetUserId, '35', $maxId);
+ }
+
+ /**
+ * Get a business account's active ads on stories.
+ *
+ * @param string $targetUserId Numerical UserPk ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ActiveReelAdsResponse
+ */
+ public function getActiveStoryAds(
+ $targetUserId,
+ $maxId = null)
+ {
+ return $this->_getActiveAds($targetUserId, '49', $maxId);
+ }
+
+ /**
+ * Helper function for getting active ads for business accounts.
+ *
+ * @param string $targetUserId Numerical UserPk ID.
+ * @param string $pageType Content-type id(?) of the ad. 35 is feed ads and 49 is story ads.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return Response
+ */
+ protected function _getActiveAds(
+ $targetUserId,
+ $pageType,
+ $maxId = null)
+ {
+ $request = $this->ig->request('ads/view_ads/')
+ ->setSignedPost(false)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('target_user_id', $targetUserId)
+ ->addPost('page_type', $pageType);
+ if ($maxId !== null) {
+ $request->addPost('next_max_id', $maxId);
+ }
+ $request->addPost('ig_user_id', $this->ig->account_id);
+
+ switch ($pageType) {
+ case '35':
+ return $request->getResponse(new Response\ActiveFeedAdsResponse());
+ break;
+ case '49':
+ return $request->getResponse(new Response\ActiveReelAdsResponse());
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid page type.');
+ }
+ }
+
+ /**
+ * Search for users by linking your address book to Instagram.
+ *
+ * WARNING: You must unlink your current address book before you can link
+ * another one to search again, otherwise you will just keep getting the
+ * same response about your currently linked address book every time!
+ *
+ * @param array $contacts
+ * @param string $module
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\LinkAddressBookResponse
+ *
+ * @see People::unlinkAddressBook()
+ */
+ public function linkAddressBook(
+ array $contacts,
+ $module = 'find_friends_contacts')
+ {
+ return $this->ig->request('address_book/link/')
+ ->setIsBodyCompressed(true)
+ ->setSignedPost(false)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('module', $module)
+ ->addPost('contacts', json_encode($contacts))
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('device_id', $this->ig->device_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\LinkAddressBookResponse());
+ }
+
+ /**
+ * Unlink your address book from Instagram.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UnlinkAddressBookResponse
+ */
+ public function unlinkAddressBook()
+ {
+ return $this->ig->request('address_book/unlink/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\UnlinkAddressBookResponse());
+ }
+
+ /**
+ * Discover new people via Facebook's algorithm.
+ *
+ * This matches you with other people using multiple algorithms such as
+ * "friends of friends", "location", "people using similar hashtags", etc.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\DiscoverPeopleResponse
+ */
+ public function discoverPeople(
+ $maxId = null)
+ {
+ $request = $this->ig->request('discover/ayml/')
+ ->setSignedPost(false)
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('module', 'discover_people')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('paginate', true);
+
+ if ($maxId !== null) {
+ $request->addPost('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\DiscoverPeopleResponse());
+ }
+
+ /**
+ * Get suggested users related to a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedUsersResponse
+ */
+ public function getSuggestedUsers(
+ $userId)
+ {
+ return $this->ig->request('discover/chaining/')
+ ->addParam('target_id', $userId)
+ ->getResponse(new Response\SuggestedUsersResponse());
+ }
+
+ /**
+ * Get suggested users via account badge.
+ *
+ * This is the endpoint for when you press the "user icon with the plus
+ * sign" on your own profile in the Instagram app. Its amount of suggestions
+ * matches the number on the badge, and it usually only has a handful (1-4).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedUsersBadgeResponse
+ */
+ public function getSuggestedUsersBadge()
+ {
+ return $this->ig->request('discover/profile_su_badge/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('module', 'discover_people')
+ ->getResponse(new Response\SuggestedUsersBadgeResponse());
+ }
+
+ /**
+ * Hide suggested user, so that they won't be suggested again.
+ *
+ * You must provide the correct algorithm for the user you want to hide,
+ * which can be seen in their "algorithm" value in People::discoverPeople().
+ *
+ * Here is a probably-outdated list of algorithms and their meanings:
+ *
+ * - realtime_chaining_algorithm = ?
+ * - realtime_chaining_ig_coeff_algorithm = ?
+ * - tfidf_city_algorithm = Popular people near you.
+ * - hashtag_interest_algorithm = Popular people on similar hashtags as you.
+ * - second_order_followers_algorithm = Popular.
+ * - super_users_algorithm = Popular.
+ * - followers_algorithm = Follows you.
+ * - ig_friends_of_friends_from_tao_laser_algorithm = ?
+ * - page_rank_algorithm = ?
+ *
+ * TODO: Do more research about this function and document it properly.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $algorithm Which algorithm to hide the suggestion from;
+ * must match that user's "algorithm" value in
+ * functions like People::discoverPeople().
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SuggestedUsersResponse
+ */
+ public function hideSuggestedUser(
+ $userId,
+ $algorithm)
+ {
+ return $this->ig->request('discover/aysf_dismiss/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addParam('target_id', $userId)
+ ->addParam('algorithm', $algorithm)
+ ->getResponse(new Response\SuggestedUsersResponse());
+ }
+
+ /**
+ * Follow a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function follow(
+ $userId,
+ $mediaId = null)
+ {
+ $request = $this->ig->request("friendships/create/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('device_id', $this->ig->device_id);
+
+ if ($mediaId !== null) {
+ $request->addPost('media_id_attribution', $mediaId);
+ }
+
+ return $request->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unfollow a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function unfollow(
+ $userId,
+ $mediaId = null)
+ {
+ $request = $this->ig->request("friendships/destroy/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('radio_type', 'wifi-none');
+
+ if ($mediaId !== null) {
+ $request->addPost('media_id_attribution', $mediaId);
+ }
+
+ return $request->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Enable high priority for a user you are following.
+ *
+ * When you mark someone as favorite, you will receive app push
+ * notifications when that user uploads media, and their shared
+ * media will get higher visibility. For instance, their stories
+ * will be placed at the front of your reels-tray, and their
+ * timeline posts will stay visible for longer on your homescreen.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function favorite(
+ $userId)
+ {
+ return $this->ig->request("friendships/favorite/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Disable high priority for a user you are following.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unfavorite(
+ $userId)
+ {
+ return $this->ig->request("friendships/unfavorite/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Turn on story notifications.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function favoriteForStories(
+ $userId)
+ {
+ return $this->ig->request("friendships/favorite_for_stories/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Turn off story notifications.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unfavoriteForStories(
+ $userId)
+ {
+ return $this->ig->request("friendships/unfavorite_for_stories/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Report a user as spam.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $sourceName (optional) Source app-module of the report.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function report(
+ $userId,
+ $sourceName = 'profile')
+ {
+ return $this->ig->request("users/{$userId}/flag_user/")
+ ->addPost('reason_id', 1)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->addPost('source_name', $sourceName)
+ ->addPost('is_spam', true)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Block a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function block(
+ $userId)
+ {
+ return $this->ig->request("friendships/block/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Mute stories, posts or both from a user.
+ *
+ * It prevents user media from showing up in the timeline and/or story feed.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $option Selection of what type of media are going to be muted.
+ * Available options: 'story', 'post' or 'all'.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function muteUserMedia(
+ $userId,
+ $option)
+ {
+ return $this->_muteOrUnmuteUserMedia($userId, $option, 'friendships/mute_posts_or_story_from_follow/');
+ }
+
+ /**
+ * Unmute stories, posts or both from a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $option Selection of what type of media are going to be muted.
+ * Available options: 'story', 'post' or 'all'.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function unmuteUserMedia(
+ $userId,
+ $option)
+ {
+ return $this->_muteOrUnmuteUserMedia($userId, $option, 'friendships/unmute_posts_or_story_from_follow/');
+ }
+
+ /**
+ * Helper function to mute user media.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string $option Selection of what type of media are going to be muted.
+ * Available options: 'story', 'post' or 'all'.
+ * @param string $endpoint API endpoint for muting/unmuting user media.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::muteUserMedia()
+ * @see People::unmuteUserMedia()
+ */
+ protected function _muteOrUnmuteUserMedia(
+ $userId,
+ $option,
+ $endpoint)
+ {
+ $request = $this->ig->request($endpoint)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ switch ($option) {
+ case 'story':
+ $request->addPost('target_reel_author_id', $userId);
+ break;
+ case 'post':
+ $request->addPost('target_posts_author_id', $userId);
+ break;
+ case 'all':
+ $request->addPost('target_reel_author_id', $userId);
+ $request->addPost('target_posts_author_id', $userId);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid muting option.', $option));
+ }
+
+ return $request->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unblock a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ */
+ public function unblock(
+ $userId)
+ {
+ return $this->ig->request("friendships/unblock/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_id', $userId)
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get a list of all blocked users.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BlockedListResponse
+ */
+ public function getBlockedList(
+ $maxId = null)
+ {
+ $request = $this->ig->request('users/blocked_list/');
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\BlockedListResponse());
+ }
+
+ /**
+ * Block a user's ability to see your stories.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::muteFriendStory()
+ */
+ public function blockMyStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/block_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('source', 'profile')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unblock a user so that they can see your stories again.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::unmuteFriendStory()
+ */
+ public function unblockMyStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/unblock_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('source', 'profile')
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get the list of users who are blocked from seeing your stories.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\BlockedReelsResponse
+ */
+ public function getBlockedStoryList()
+ {
+ return $this->ig->request('friendships/blocked_reels/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\BlockedReelsResponse());
+ }
+
+ /**
+ * Mute a friend's stories, so that you no longer see their stories.
+ *
+ * This hides them from your reels tray (the "latest stories" bar on the
+ * homescreen of the app), but it does not block them from seeing *your*
+ * stories.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::blockMyStory()
+ */
+ public function muteFriendStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/mute_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Unmute a friend's stories, so that you see their stories again.
+ *
+ * This does not unblock their ability to see *your* stories.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\FriendshipResponse
+ *
+ * @see People::unblockMyStory()
+ */
+ public function unmuteFriendStory(
+ $userId)
+ {
+ return $this->ig->request("friendships/unmute_friend_reel/{$userId}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\FriendshipResponse());
+ }
+
+ /**
+ * Get the list of users on your close friends list.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CloseFriendsResponse
+ */
+ public function getCloseFriends()
+ {
+ return $this->ig->request('friendships/besties/')
+ ->getResponse(new Response\CloseFriendsResponse());
+ }
+
+ /**
+ * Get the list of suggested users for your close friends list.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CloseFriendsResponse
+ */
+ public function getSuggestedCloseFriends()
+ {
+ return $this->ig->request('friendships/bestie_suggestions/')
+ ->getResponse(new Response\CloseFriendsResponse());
+ }
+
+ /**
+ * Add or Remove users from your close friends list.
+ *
+ * Note: You probably shouldn't touch $module and $source as there is only one way to modify your close friends.
+ *
+ * @param array $add Users to add to your close friends list.
+ * @param array $remove Users to remove from your close friends list.
+ * @param string $module (optional) From which app module (page) you have change your close friends list.
+ * @param string $source (optional) Source page of app-module of where you changed your close friends list.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function setCloseFriends(
+ array $add,
+ array $remove,
+ $module = 'favorites_home_list',
+ $source = 'audience_manager')
+ {
+ return $this->ig->request('friendships/set_besties/')
+ ->setSignedPost(true)
+ ->addPost('module', $module)
+ ->addPost('source', $source)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('remove', $remove)
+ ->addPost('add', $add)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Gets a list of ranked users to display in Android's share UI.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\SharePrefillResponse
+ */
+ public function getSharePrefill()
+ {
+ return $this->ig->request('banyan/banyan/')
+ ->addParam('views', '["story_share_sheet","threads_people_picker","reshare_share_sheet"]')
+ ->getResponse(new Response\SharePrefillResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Push.php b/vendor/mgp25/instagram-php/src/Request/Push.php
new file mode 100755
index 0000000..16b5bcd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Push.php
@@ -0,0 +1,78 @@
+ig->request('push/register/')
+ ->setSignedPost(false)
+ ->addPost('device_type', $pushChannel === 'mqtt' ? 'android_mqtt' : 'android_fcm')
+ ->addPost('is_main_push_channel', $pushChannel === 'mqtt')
+ ->addPost('device_token', $token)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('guid', $this->ig->uuid)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('device_sub_type', '2')
+ ->addPost('users', $this->ig->account_id);
+
+ return $request->getResponse(new Response\PushRegisterResponse());
+ }
+
+ /**
+ * Get push preferences.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PushPreferencesResponse
+ */
+ public function getPreferences()
+ {
+ return $this->ig->request('push/all_preferences/')
+ ->getResponse(new Response\PushPreferencesResponse());
+ }
+
+ /**
+ * Set push preferences.
+ *
+ * @param array $preferences Described in "extradocs/Push_setPreferences.txt".
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\PushPreferencesResponse
+ */
+ public function setPreferences(
+ array $preferences)
+ {
+ $request = $this->ig->request('push/preferences/');
+ foreach ($preferences as $key => $value) {
+ $request->addPost($key, $value);
+ }
+
+ return $request->getResponse(new Response\PushPreferencesResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/RequestCollection.php b/vendor/mgp25/instagram-php/src/Request/RequestCollection.php
new file mode 100755
index 0000000..896f09f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/RequestCollection.php
@@ -0,0 +1,104 @@
+ig = $parent;
+ }
+
+ /**
+ * Paginate the request by given exclusion list.
+ *
+ * @param Request $request The request to paginate.
+ * @param array $excludeList Array of numerical entity IDs (ie "4021088339")
+ * to exclude from the response, allowing you to skip entities
+ * from a previous call to get more results.
+ * @param string|null $rankToken The rank token from the previous page's response.
+ * @param int $limit Limit the number of results per page.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Request
+ */
+ protected function _paginateWithExclusion(
+ Request $request,
+ array $excludeList = [],
+ $rankToken = null,
+ $limit = 30)
+ {
+ if (!count($excludeList)) {
+ return $request->addParam('count', (string) $limit);
+ }
+
+ if ($rankToken === null) {
+ throw new \InvalidArgumentException('You must supply the rank token for the pagination.');
+ }
+ Utils::throwIfInvalidRankToken($rankToken);
+
+ return $request
+ ->addParam('count', (string) $limit)
+ ->addParam('exclude_list', '['.implode(', ', $excludeList).']')
+ ->addParam('rank_token', $rankToken);
+ }
+
+ /**
+ * Paginate the request by given multi-exclusion list.
+ *
+ * @param Request $request The request to paginate.
+ * @param array $excludeList Array of grouped numerical entity IDs (ie "users" => ["4021088339"])
+ * to exclude from the response, allowing you to skip entities
+ * from a previous call to get more results.
+ * @param string|null $rankToken The rank token from the previous page's response.
+ * @param int $limit Limit the number of results per page.
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return Request
+ */
+ protected function _paginateWithMultiExclusion(
+ Request $request,
+ array $excludeList = [],
+ $rankToken = null,
+ $limit = 30)
+ {
+ if (!count($excludeList)) {
+ return $request->addParam('count', (string) $limit);
+ }
+
+ if ($rankToken === null) {
+ throw new \InvalidArgumentException('You must supply the rank token for the pagination.');
+ }
+ Utils::throwIfInvalidRankToken($rankToken);
+
+ $exclude = [];
+ $totalCount = 0;
+ foreach ($excludeList as $group => $ids) {
+ $totalCount += count($ids);
+ $exclude[] = "\"{$group}\":[".implode(', ', $ids).']';
+ }
+
+ return $request
+ ->addParam('count', (string) $limit)
+ ->addParam('exclude_list', '{'.implode(',', $exclude).'}')
+ ->addParam('rank_token', $rankToken);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Shopping.php b/vendor/mgp25/instagram-php/src/Request/Shopping.php
new file mode 100755
index 0000000..3bd97be
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Shopping.php
@@ -0,0 +1,123 @@
+ig->request("commerce/products/{$productId}/details/")
+ ->addParam('source_media_id', $mediaId)
+ ->addParam('merchant_id', $merchantId)
+ ->addParam('device_width', $deviceWidth)
+ ->addParam('hero_carousel_enabled', true)
+ ->getResponse(new Response\OnTagProductResponse());
+ }
+
+ /**
+ * Get catalogs.
+ *
+ * @param string $locale The device user's locale, such as "en_US.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GraphqlResponse
+ */
+ public function getCatalogs(
+ $locale = 'en_US')
+ {
+ return $this->ig->request('wwwgraphql/ig/query/')
+ ->addParam('locale', $locale)
+ ->addUnsignedPost('access_token', 'undefined')
+ ->addUnsignedPost('fb_api_caller_class', 'RelayModern')
+ ->addUnsignedPost('variables', ['sources' => null])
+ ->addUnsignedPost('doc_id', '1742970149122229')
+ ->getResponse(new Response\GraphqlResponse());
+ }
+
+ /**
+ * Get catalog items.
+ *
+ * @param string $catalogId The catalog's ID.
+ * @param string $query Finds products containing this string.
+ * @param int $offset Offset, used for pagination. Values must be multiples of 20.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GraphqlResponse
+ */
+ public function getCatalogItems(
+ $catalogId,
+ $query = '',
+ $offset = null)
+ {
+ if ($offset !== null) {
+ if ($offset % 20 !== 0) {
+ throw new \InvalidArgumentException('Offset must be multiple of 20.');
+ }
+ $offset = [
+ 'offset' => $offset,
+ 'tier' => 'products.elasticsearch.thrift.atn',
+ ];
+ }
+
+ $queryParams = [
+ $query,
+ $catalogId,
+ '96',
+ '20',
+ json_encode($offset),
+ ];
+
+ return $this->ig->request('wwwgraphql/ig/query/')
+ ->addUnsignedPost('doc_id', '1747750168640998')
+ ->addUnsignedPost('locale', Constants::ACCEPT_LANGUAGE)
+ ->addUnsignedPost('vc_policy', 'default')
+ ->addUnsignedPost('strip_nulls', true)
+ ->addUnsignedPost('strip_defaults', true)
+ ->addUnsignedPost('query_params', json_encode($queryParams, JSON_FORCE_OBJECT))
+ ->getResponse(new Response\GraphqlResponse());
+ }
+
+ /**
+ * Sets on board catalog.
+ *
+ * @param string $catalogId The catalog's ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\OnBoardCatalogResponse
+ */
+ public function setOnBoardCatalog(
+ $catalogId)
+ {
+ return $this->ig->request('commerce/onboard/')
+ ->addPost('current_catalog_id', $catalogId)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\OnBoardCatalogResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Story.php b/vendor/mgp25/instagram-php/src/Request/Story.php
new file mode 100755
index 0000000..d8f79c9
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Story.php
@@ -0,0 +1,693 @@
+ig->internal->uploadSinglePhoto(Constants::FEED_STORY, $photoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads a photo to your Instagram close friends story.
+ *
+ * @param string $photoFilename The photo filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSinglePhoto() for available metadata fields.
+ * @see https://help.instagram.com/2183694401643300
+ */
+ public function uploadCloseFriendsPhoto(
+ $photoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata(Utils::generateUploadId(true));
+ $internalMetadata->setBestieMedia(true);
+
+ return $this->ig->internal->uploadSinglePhoto(Constants::FEED_STORY, $photoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Uploads a video to your Instagram story.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_STORY, $videoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads a video to your Instagram close friends story.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ * @see https://help.instagram.com/2183694401643300
+ */
+ public function uploadCloseFriendsVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ $internalMetadata = new InternalMetadata();
+ $internalMetadata->setBestieMedia(true);
+
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_STORY, $videoFilename, $internalMetadata, $externalMetadata);
+ }
+
+ /**
+ * Get the global story feed which contains everyone you follow.
+ *
+ * Note that users will eventually drop out of this list even though they
+ * still have stories. So it's always safer to call getUserStoryFeed() if
+ * a specific user's story feed matters to you.
+ *
+ * @param string $reason (optional) Reason for the request.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelsTrayFeedResponse
+ *
+ * @see Story::getUserStoryFeed()
+ */
+ public function getReelsTrayFeed(
+ $reason = 'pull_to_refresh')
+ {
+ return $this->ig->request('feed/reels_tray/')
+ ->setSignedPost(false)
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('reason', $reason)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\ReelsTrayFeedResponse());
+ }
+
+ /**
+ * Get a specific user's story reel feed.
+ *
+ * This function gets the user's story Reel object directly, which always
+ * exists and contains information about the user and their last story even
+ * if that user doesn't have any active story anymore.
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserReelMediaFeedResponse
+ *
+ * @see Story::getUserStoryFeed()
+ */
+ public function getUserReelMediaFeed(
+ $userId)
+ {
+ return $this->ig->request("feed/user/{$userId}/reel_media/")
+ ->getResponse(new Response\UserReelMediaFeedResponse());
+ }
+
+ /**
+ * Get a specific user's story feed with broadcast details.
+ *
+ * This function gets the story in a roundabout way, with some extra details
+ * about the "broadcast". But if there is no story available, this endpoint
+ * gives you an empty response.
+ *
+ * NOTE: At least AT THIS MOMENT, this endpoint and the reels-tray endpoint
+ * are the only ones that will give you people's "post_live" fields (their
+ * saved Instagram Live Replays). The other "get user stories" funcs don't!
+ *
+ * @param string $userId Numerical UserPK ID.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserStoryFeedResponse
+ *
+ * @see Story::getUserReelMediaFeed()
+ */
+ public function getUserStoryFeed(
+ $userId)
+ {
+ return $this->ig->request("feed/user/{$userId}/story/")
+ ->addParam('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->getResponse(new Response\UserStoryFeedResponse());
+ }
+
+ /**
+ * Get multiple users' story feeds (or specific highlight-details) at once.
+ *
+ * NOTE: Normally, you would only use this endpoint for stories (by passing
+ * UserPK IDs as the parameter). But if you're looking at people's highlight
+ * feeds (via `Highlight::getUserFeed()`), you may also sometimes discover
+ * highlight entries that don't have any `items` array. In that case, you
+ * are supposed to get the items for those highlights via this endpoint!
+ * Simply pass their `id` values as the argument to this API to get details.
+ *
+ * @param string|string[] $feedList List of numerical UserPK IDs, OR highlight IDs (such as `highlight:123882132324123`).
+ * @param string $source (optional) Source app-module where the request was made.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelsMediaResponse
+ *
+ * @see Highlight::getUserFeed() More info about when to use this API for highlight-details.
+ */
+ public function getReelsMediaFeed(
+ $feedList,
+ $source = 'feed_timeline')
+ {
+ if (!is_array($feedList)) {
+ $feedList = [$feedList];
+ }
+
+ foreach ($feedList as &$value) {
+ $value = (string) $value;
+ }
+ unset($value); // Clear reference.
+
+ return $this->ig->request('feed/reels_media/')
+ ->addPost('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES))
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('user_ids', $feedList) // Must be string[] array.
+ ->addPost('source', $source)
+ ->getResponse(new Response\ReelsMediaResponse());
+ }
+
+ /**
+ * Get your archived story media feed.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ArchivedStoriesFeedResponse
+ */
+ public function getArchivedStoriesFeed()
+ {
+ return $this->ig->request('archive/reel/day_shells/')
+ ->addParam('include_suggested_highlights', false)
+ ->addParam('is_in_archive_home', true)
+ ->addParam('include_cover', 0)
+ ->addParam('timezone_offset', date('Z'))
+ ->getResponse(new Response\ArchivedStoriesFeedResponse());
+ }
+
+ /**
+ * Get the list of users who have seen one of your story items.
+ *
+ * Note that this only works for your own story items. Instagram doesn't
+ * allow you to see the viewer list for other people's stories!
+ *
+ * @param string $storyPk The story media item's PK in Instagram's internal format (ie "3482384834").
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelMediaViewerResponse
+ */
+ public function getStoryItemViewers(
+ $storyPk,
+ $maxId = null)
+ {
+ $request = $this->ig->request("media/{$storyPk}/list_reel_media_viewer/")
+ ->addParam('supported_capabilities_new', json_encode(Constants::SUPPORTED_CAPABILITIES));
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\ReelMediaViewerResponse());
+ }
+
+ /**
+ * Vote on a story poll.
+ *
+ * Note that once you vote on a story poll, you cannot change your vote.
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $pollId The poll ID in Instagram's internal format (ie "17956159684032257").
+ * @param int $votingOption Value that represents the voting option of the voter. 0 for the first option, 1 for the second option.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelMediaViewerResponse
+ */
+ public function votePollStory(
+ $storyId,
+ $pollId,
+ $votingOption)
+ {
+ if (($votingOption !== 0) && ($votingOption !== 1)) {
+ throw new \InvalidArgumentException('You must provide a valid value for voting option.');
+ }
+
+ return $this->ig->request("media/{$storyId}/{$pollId}/story_poll_vote/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('vote', $votingOption)
+ ->getResponse(new Response\ReelMediaViewerResponse());
+ }
+
+ /**
+ * Vote on a story slider.
+ *
+ * Note that once you vote on a story poll, you cannot change your vote.
+ *
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $sliderId The slider ID in Instagram's internal format (ie "17956159684032257").
+ * @param float $votingOption Value that represents the voting option of the voter. Should be a float from 0 to 1 (ie "0.25").
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelMediaViewerResponse
+ */
+ public function voteSliderStory(
+ $storyId,
+ $sliderId,
+ $votingOption)
+ {
+ if ($votingOption < 0 || $votingOption > 1) {
+ throw new \InvalidArgumentException('You must provide a valid value from 0 to 1 for voting option.');
+ }
+
+ return $this->ig->request("media/{$storyId}/{$sliderId}/story_slider_vote/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('radio_type', 'wifi-none')
+ ->addPost('vote', $votingOption)
+ ->getResponse(new Response\ReelMediaViewerResponse());
+ }
+
+ /**
+ * Get the list of users who have voted an option in a story poll.
+ *
+ * Note that this only works for your own story polls. Instagram doesn't
+ * allow you to see the results from other people's polls!
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $pollId The poll ID in Instagram's internal format (ie "17956159684032257").
+ * @param int $votingOption Value that represents the voting option of the voter. 0 for the first option, 1 for the second option.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StoryPollVotersResponse
+ */
+ public function getStoryPollVoters(
+ $storyId,
+ $pollId,
+ $votingOption,
+ $maxId = null)
+ {
+ if (($votingOption !== 0) && ($votingOption !== 1)) {
+ throw new \InvalidArgumentException('You must provide a valid value for voting option.');
+ }
+
+ $request = $this->ig->request("media/{$storyId}/{$pollId}/story_poll_voters/")
+ ->addParam('vote', $votingOption);
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\StoryPollVotersResponse());
+ }
+
+ /**
+ * Respond to a question sticker on a story.
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17956159684032257").
+ * @param string $responseText The text to respond to the question with. (Note: Android App limits this to 94 characters).
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function answerStoryQuestion(
+ $storyId,
+ $questionId,
+ $responseText)
+ {
+ return $this->ig->request("media/{$storyId}/{$questionId}/story_question_response/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('response', $responseText)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('type', 'text')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get all responses of a story question.
+ *
+ * @param string $storyId The story media item's ID in Instagram's internal format (ie "1542304813904481224_6112344004").
+ * @param string $questionId The question ID in Instagram's internal format (ie "17956159684032257").
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StoryAnswersResponse
+ */
+ public function getStoryAnswers(
+ $storyId,
+ $questionId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("media/{$storyId}/{$questionId}/story_question_responses/");
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\StoryAnswersResponse());
+ }
+
+ /**
+ * Gets the created story countdowns of the current account.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\StoryCountdownsResponse
+ */
+ public function getStoryCountdowns()
+ {
+ return $this->ig->request('media/story_countdowns/')
+ ->getResponse(new Response\StoryCountdownsResponse());
+ }
+
+ /**
+ * Follows a story countdown to subscribe to a notification when the countdown is finished.
+ *
+ * @param string $countdownId The countdown ID in Instagram's internal format (ie "17956159684032257").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function followStoryCountdown(
+ $countdownId)
+ {
+ return $this->ig->request("media/{$countdownId}/follow_story_countdown/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Unfollows a story countdown to unsubscribe from a notification when the countdown is finished.
+ *
+ * @param string $countdownId The countdown ID in Instagram's internal format (ie "17956159684032257").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function unfollowStoryCountdown(
+ $countdownId)
+ {
+ return $this->ig->request("media/{$countdownId}/unfollow_story_countdown/")
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->getResponse(new Response\GenericResponse());
+ }
+
+ /**
+ * Get list of charities for use in the donation sticker on stories.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CharitiesListResponse
+ */
+ public function getCharities(
+ $maxId = null)
+ {
+ $request = $this->ig->request('fundraiser/story_charities_nullstate/');
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\CharitiesListResponse());
+ }
+
+ /**
+ * Searches a list of charities for use in the donation sticker on stories.
+ *
+ * @param string $query Search query.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\CharitiesListResponse
+ */
+ public function searchCharities(
+ $query,
+ $maxId = null)
+ {
+ $request = $this->ig->request('fundraiser/story_charities_search/')
+ ->addParam('query', $query);
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\CharitiesListResponse());
+ }
+
+ /**
+ * Creates the array for a donation sticker.
+ *
+ * @param \InstagramAPI\Response\Model\User $charityUser The User object of the charity's Instagram account.
+ * @param float $x
+ * @param float $y
+ * @param float $width
+ * @param float $height
+ * @param float $rotation
+ * @param string|null $title The title of the donation sticker.
+ * @param string $titleColor Hex color code for the title color.
+ * @param string $subtitleColor Hex color code for the subtitle color.
+ * @param string $buttonTextColor Hex color code for the button text color.
+ * @param string $startBackgroundColor
+ * @param string $endBackgroundColor
+ *
+ * @return array
+ *
+ * @see Story::getCharities()
+ * @see Story::searchCharities()
+ * @see Story::uploadPhoto()
+ * @see Story::uploadVideo()
+ */
+ public function createDonateSticker(
+ $charityUser,
+ $x = 0.5,
+ $y = 0.5,
+ $width = 0.6805556,
+ $height = 0.254738,
+ $rotation = 0.0,
+ $title = null,
+ $titleColor = '#000000',
+ $subtitleColor = '#999999ff',
+ $buttonTextColor = '#3897f0',
+ $startBackgroundColor = '#fafafa',
+ $endBackgroundColor = '#fafafa')
+ {
+ return [
+ [
+ 'x' => $x,
+ 'y' => $y,
+ 'z' => 0,
+ 'width' => $width,
+ 'height' => $height,
+ 'rotation' => $rotation,
+ 'title' => ($title !== null ? strtoupper($title) : ('HELP SUPPORT '.strtoupper($charityUser->getFullName()))),
+ 'ig_charity_id' => $charityUser->getPk(),
+ 'title_color' => $titleColor,
+ 'subtitle_color' => $subtitleColor,
+ 'button_text_color' => $buttonTextColor,
+ 'start_background_color' => $startBackgroundColor,
+ 'end_background_color' => $endBackgroundColor,
+ 'source_name' => 'sticker_tray',
+ 'user' => [
+ 'username' => $charityUser->getUsername(),
+ 'full_name' => $charityUser->getFullName(),
+ 'profile_pic_url' => $charityUser->getProfilePicUrl(),
+ 'profile_pic_id' => $charityUser->getProfilePicId(),
+ 'has_anonymous_profile_picture' => $charityUser->getHasAnonymousProfilePicture(),
+ 'id' => $charityUser->getPk(),
+ 'usertag_review_enabled' => false,
+ 'mutual_followers_count' => $charityUser->getMutualFollowersCount(),
+ 'show_besties_badge' => false,
+ 'is_private' => $charityUser->getIsPrivate(),
+ 'allowed_commenter_type' => 'any',
+ 'is_verified' => $charityUser->getIsVerified(),
+ 'is_new' => false,
+ 'feed_post_reshare_disabled' => false,
+ ],
+ 'is_sticker' => true,
+ ],
+ ];
+ }
+
+ /**
+ * Mark story media items as seen.
+ *
+ * The various story-related endpoints only give you lists of story media.
+ * They don't actually mark any stories as "seen", so the user doesn't know
+ * that you've seen their story. Actually marking the story as "seen" is
+ * done via this endpoint instead. The official app calls this endpoint
+ * periodically (with 1 or more items at a time) while watching a story.
+ *
+ * Tip: You can pass in the whole "getItems()" array from a user's story
+ * feed (retrieved via any of the other story endpoints), to easily mark
+ * all of that user's story media items as seen.
+ *
+ * WARNING: ONLY USE *THIS* ENDPOINT IF THE STORIES CAME FROM THE ENDPOINTS
+ * IN *THIS* REQUEST-COLLECTION FILE: From "getReelsTrayFeed()" or the
+ * user-specific story endpoints. Do NOT use this endpoint if the stories
+ * came from any OTHER request-collections, such as Location-based stories!
+ * Other request-collections have THEIR OWN special story-marking functions!
+ *
+ * @param Response\Model\Item[] $items Array of one or more story media Items.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaSeenResponse
+ *
+ * @see Location::markStoryMediaSeen()
+ * @see Hashtag::markStoryMediaSeen()
+ */
+ public function markMediaSeen(
+ array $items)
+ {
+ // NOTE: NULL = Use each item's owner ID as the "source ID".
+ return $this->ig->internal->markStoryMediaSeen($items, null);
+ }
+
+ /**
+ * Get your story settings.
+ *
+ * This has information such as your story messaging mode (who can reply
+ * to your story), and the list of users you have blocked from seeing your
+ * stories.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelSettingsResponse
+ */
+ public function getReelSettings()
+ {
+ return $this->ig->request('users/reel_settings/')
+ ->getResponse(new Response\ReelSettingsResponse());
+ }
+
+ /**
+ * Set your story settings.
+ *
+ * @param string $messagePrefs Who can reply to your story. Valid values are "anyone" (meaning
+ * your followers), "following" (followers that you follow back),
+ * or "off" (meaning that nobody can reply to your story).
+ * @param bool|null $allowStoryReshare Allow story reshare.
+ * @param string|null $autoArchive Auto archive stories for viewing them later. It will appear in your
+ * archive once it has disappeared from your story feed. Valid values
+ * "on" and "off".
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReelSettingsResponse
+ */
+ public function setReelSettings(
+ $messagePrefs,
+ $allowStoryReshare = null,
+ $autoArchive = null)
+ {
+ if (!in_array($messagePrefs, ['anyone', 'following', 'off'])) {
+ throw new \InvalidArgumentException('You must provide a valid message preference value.');
+ }
+
+ $request = $this->ig->request('users/set_reel_settings/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('message_prefs', $messagePrefs);
+
+ if ($allowStoryReshare !== null) {
+ if (!is_bool($allowStoryReshare)) {
+ throw new \InvalidArgumentException('You must provide a valid value for allowing story reshare.');
+ }
+ $request->addPost('allow_story_reshare', $allowStoryReshare);
+ }
+
+ if ($autoArchive !== null) {
+ if (!in_array($autoArchive, ['on', 'off'])) {
+ throw new \InvalidArgumentException('You must provide a valid value for auto archive.');
+ }
+ $request->addPost('reel_auto_archive', $autoArchive);
+ }
+
+ return $request->getResponse(new Response\ReelSettingsResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/TV.php b/vendor/mgp25/instagram-php/src/Request/TV.php
new file mode 100755
index 0000000..47bc968
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/TV.php
@@ -0,0 +1,171 @@
+ig->request('igtv/tv_guide/')
+ ->addHeader('X-Ads-Opt-Out', '0')
+ ->addHeader('X-Google-AD-ID', $this->ig->advertising_id)
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addParam('prefetch', 1)
+ ->addParam('phone_id', $this->ig->phone_id)
+ ->addParam('battery_level', '100')
+// ->addParam('banner_token', 'OgYA')
+ ->addParam('is_charging', '1')
+ ->addParam('will_sound_on', '1');
+
+ if (isset($options['is_charging'])) {
+ $request->addParam('is_charging', $options['is_charging']);
+ } else {
+ $request->addParam('is_charging', '0');
+ }
+
+ $response = $request->getResponse(new Response\TVGuideResponse());
+
+ return $response;
+ }
+
+ /**
+ * Get channel.
+ *
+ * You can filter the channel with different IDs: 'for_you', 'chrono_following', 'popular', 'continue_watching'
+ * and using a user ID in the following format: 'user_1234567891'.
+ *
+ * @param string $id ID used to filter channels.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TVChannelsResponse
+ */
+ public function getChannel(
+ $id = 'for_you',
+ $maxId = null)
+ {
+ if (!in_array($id, ['for_you', 'chrono_following', 'popular', 'continue_watching'])
+ && !preg_match('/^user_[1-9]\d*$/', $id)) {
+ throw new \InvalidArgumentException('Invalid ID type.');
+ }
+
+ $request = $this->ig->request('igtv/channel/')
+ ->addPost('id', $id)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken());
+
+ if ($maxId !== null) {
+ $request->addPost('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\TVChannelsResponse());
+ }
+
+ /**
+ * Uploads a video to your Instagram TV.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_TV, $videoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Searches for channels.
+ *
+ * @param string $query The username or channel you are looking for.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TVSearchResponse
+ */
+ public function search(
+ $query = '')
+ {
+ if ($query !== '') {
+ $endpoint = 'igtv/search/';
+ } else {
+ $endpoint = 'igtv/suggested_searches/';
+ }
+
+ return $this->ig->request($endpoint)
+ ->addParam('query', $query)
+ ->getResponse(new Response\TVSearchResponse());
+ }
+
+ /**
+ * Write seen state on a video.
+ *
+ * @param string $impression Format: 1813637917462151382
+ * @param int $viewProgress Video view progress in seconds.
+ * @param mixed $gridImpressions TODO No info yet.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\GenericResponse
+ */
+ public function writeSeenState(
+ $impression,
+ $viewProgress = 0,
+ $gridImpressions = [])
+ {
+ if (!ctype_digit($viewProgress) && (!is_int($viewProgress) || $viewProgress < 0)) {
+ throw new \InvalidArgumentException('View progress must be a positive integer.');
+ }
+
+ $seenState = json_encode([
+ 'impressions' => [
+ $impression => [
+ 'view_progress_s' => $viewProgress,
+ ],
+ ],
+ 'grid_impressions' => $gridImpressions,
+ ]);
+
+ return $this->ig->request('igtv/write_seen_state/')
+ ->addPost('seen_state', $seenState)
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\GenericResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Timeline.php b/vendor/mgp25/instagram-php/src/Request/Timeline.php
new file mode 100755
index 0000000..844a6b1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Timeline.php
@@ -0,0 +1,512 @@
+ig->internal->uploadSinglePhoto(Constants::FEED_TIMELINE, $photoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads a video to your Instagram timeline.
+ *
+ * @param string $videoFilename The video filename.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureSingleVideo() for available metadata fields.
+ */
+ public function uploadVideo(
+ $videoFilename,
+ array $externalMetadata = [])
+ {
+ return $this->ig->internal->uploadSingleVideo(Constants::FEED_TIMELINE, $videoFilename, null, $externalMetadata);
+ }
+
+ /**
+ * Uploads an album to your Instagram timeline.
+ *
+ * An album is also known as a "carousel" and "sidecar". They can contain up
+ * to 10 photos or videos (at the moment).
+ *
+ * @param array $media Array of image/video files and their per-file
+ * metadata (type, file, and optionally
+ * usertags). The "type" must be "photo" or
+ * "video". The "file" must be its disk path.
+ * And the optional "usertags" can only be
+ * used on PHOTOS, never on videos.
+ * @param array $externalMetadata (optional) User-provided metadata key-value pairs
+ * for the album itself (its caption, location, etc).
+ *
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ * @throws \InstagramAPI\Exception\UploadFailedException If the video upload fails.
+ *
+ * @return \InstagramAPI\Response\ConfigureResponse
+ *
+ * @see Internal::configureTimelineAlbum() for available album metadata fields.
+ */
+ public function uploadAlbum(
+ array $media,
+ array $externalMetadata = [])
+ {
+ if (empty($media)) {
+ throw new \InvalidArgumentException("List of media to upload can't be empty.");
+ }
+ if (count($media) < 2 || count($media) > 10) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Instagram requires that albums contain 2-10 items. You tried to submit %d.',
+ count($media)
+ ));
+ }
+
+ // Figure out the media file details for ALL media in the album.
+ // NOTE: We do this first, since it validates whether the media files are
+ // valid and lets us avoid wasting time uploading totally invalid albums!
+ foreach ($media as $key => $item) {
+ if (!isset($item['file']) || !isset($item['type'])) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Media at index "%s" does not have the required "file" and "type" keys.',
+ $key
+ ));
+ }
+
+ $itemInternalMetadata = new InternalMetadata();
+
+ // If usertags are provided, verify that the entries are valid.
+ if (isset($item['usertags'])) {
+ $item['usertags'] = ['in' => $item['usertags']];
+ Utils::throwIfInvalidUsertags($item['usertags']);
+ }
+
+ // Pre-process media details and throw if not allowed on Instagram.
+ switch ($item['type']) {
+ case 'photo':
+ // Determine the photo details.
+ $itemInternalMetadata->setPhotoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']);
+ break;
+ case 'video':
+ // Determine the video details.
+ $itemInternalMetadata->setVideoDetails(Constants::FEED_TIMELINE_ALBUM, $item['file']);
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Unsupported album media type "%s".', $item['type']));
+ }
+
+ $media[$key]['internalMetadata'] = $itemInternalMetadata;
+ }
+
+ // Perform all media file uploads.
+ foreach ($media as $key => $item) {
+ /** @var InternalMetadata $itemInternalMetadata */
+ $itemInternalMetadata = $media[$key]['internalMetadata'];
+
+ switch ($item['type']) {
+ case 'photo':
+ $this->ig->internal->uploadPhotoData(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata);
+ break;
+ case 'video':
+ // Attempt to upload the video data.
+ $itemInternalMetadata = $this->ig->internal->uploadVideo(Constants::FEED_TIMELINE_ALBUM, $item['file'], $itemInternalMetadata);
+
+ // Attempt to upload the thumbnail, associated with our video's ID.
+ $this->ig->internal->uploadVideoThumbnail(Constants::FEED_TIMELINE_ALBUM, $itemInternalMetadata, ['thumbnail_timestamp' => (isset($media[$key]['thumbnail_timestamp'])) ? $media[$key]['thumbnail_timestamp'] : 0]);
+ }
+
+ $media[$key]['internalMetadata'] = $itemInternalMetadata;
+ }
+
+ // Generate an uploadId (via internal metadata) for the album.
+ $albumInternalMetadata = new InternalMetadata();
+ // Configure the uploaded album and attach it to our timeline.
+ try {
+ /** @var \InstagramAPI\Response\ConfigureResponse $configure */
+ $configure = $this->ig->internal->configureWithRetries(
+ function () use ($media, $albumInternalMetadata, $externalMetadata) {
+ return $this->ig->internal->configureTimelineAlbum($media, $albumInternalMetadata, $externalMetadata);
+ }
+ );
+ } catch (InstagramException $e) {
+ // Pass Instagram's error as is.
+ throw $e;
+ } catch (\Exception $e) {
+ // Wrap runtime errors.
+ throw new UploadFailedException(
+ sprintf('Upload of the album failed: %s', $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+
+ return $configure;
+ }
+
+ /**
+ * Get your "home screen" timeline feed.
+ *
+ * This is the feed of recent timeline posts from people you follow.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ * @param array|null $options An associative array with following keys (all
+ * of them are optional):
+ * "latest_story_pk" The media ID in Instagram's
+ * internal format (ie "3482384834_43294");
+ * "seen_posts" One or more seen media IDs;
+ * "unseen_posts" One or more unseen media IDs;
+ * "is_pull_to_refresh" Whether this call was
+ * triggered by a refresh;
+ * "push_disabled" Whether user has disabled
+ * PUSH;
+ * "recovered_from_crash" Whether the app has
+ * recovered from a crash/was killed by Android
+ * memory manager/force closed by user/just
+ * installed for the first time;
+ * "feed_view_info" DON'T USE IT YET.
+ * "is_charging" Wether the device is being charged
+ * or not. Valid values: 0 for not charging, 1 for
+ * charging.
+ * "battery_level" Sets the current device battery
+ * level.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\TimelineFeedResponse
+ */
+ public function getTimelineFeed(
+ $maxId = null,
+ array $options = null)
+ {
+ $asyncAds = $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_enabled'
+ );
+
+ $request = $this->ig->request('feed/timeline/')
+ ->setSignedPost(false)
+ ->setIsBodyCompressed(true)
+ //->addHeader('X-CM-Bandwidth-KBPS', '-1.000')
+ //->addHeader('X-CM-Latency', '0.000')
+ ->addHeader('X-Ads-Opt-Out', '0')
+ ->addHeader('X-Google-AD-ID', $this->ig->advertising_id)
+ ->addHeader('X-DEVICE-ID', $this->ig->uuid)
+ ->addPost('bloks_versioning_id', Constants::BLOCK_VERSIONING_ID)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('is_prefetch', '0')
+ ->addPost('phone_id', $this->ig->phone_id)
+ ->addPost('device_id', $this->ig->uuid)
+ ->addPost('client_session_id', $this->ig->session_id)
+ ->addPost('battery_level', '100')
+ ->addPost('is_charging', '1')
+ ->addPost('will_sound_on', '1')
+ ->addPost('timezone_offset', date('Z'))
+ ->addPost('is_async_ads_in_headload_enabled', (string) (int) ($asyncAds && $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_async_ads_in_headload_enabled'
+ )))
+ ->addPost('is_async_ads_double_request', (string) (int) ($asyncAds && $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_double_request_enabled'
+ )))
+ ->addPost('is_async_ads_rti', (string) (int) ($asyncAds && $this->ig->isExperimentEnabled(
+ 'ig_android_ad_async_ads_universe',
+ 'is_rti_enabled'
+ )))
+ ->addPost('rti_delivery_backend', (string) (int) $this->ig->getExperimentParam(
+ 'ig_android_ad_async_ads_universe',
+ 'rti_delivery_backend'
+ ));
+
+ if (isset($options['is_charging'])) {
+ $request->addPost('is_charging', $options['is_charging']);
+ } else {
+ $request->addPost('is_charging', '0');
+ }
+
+ if (isset($options['battery_level'])) {
+ $request->addPost('battery_level', $options['battery_level']);
+ } else {
+ $request->addPost('battery_level', mt_rand(25, 100));
+ }
+
+ if (isset($options['latest_story_pk'])) {
+ $request->addPost('latest_story_pk', $options['latest_story_pk']);
+ }
+
+ if ($maxId !== null) {
+ $request->addPost('reason', 'pagination');
+ $request->addPost('max_id', $maxId);
+ $request->addPost('is_pull_to_refresh', '0');
+ } elseif (!empty($options['is_pull_to_refresh'])) {
+ $request->addPost('reason', 'pull_to_refresh');
+ $request->addPost('is_pull_to_refresh', '1');
+ } elseif (isset($options['is_pull_to_refresh'])) {
+ $request->addPost('reason', 'warm_start_fetch');
+ $request->addPost('is_pull_to_refresh', '0');
+ } else {
+ $request->addPost('reason', 'cold_start_fetch');
+ $request->addPost('is_pull_to_refresh', '0');
+ }
+
+ if (isset($options['seen_posts'])) {
+ if (is_array($options['seen_posts'])) {
+ $request->addPost('seen_posts', implode(',', $options['seen_posts']));
+ } else {
+ $request->addPost('seen_posts', $options['seen_posts']);
+ }
+ } elseif ($maxId === null) {
+ $request->addPost('seen_posts', '');
+ }
+
+ if (isset($options['unseen_posts'])) {
+ if (is_array($options['unseen_posts'])) {
+ $request->addPost('unseen_posts', implode(',', $options['unseen_posts']));
+ } else {
+ $request->addPost('unseen_posts', $options['unseen_posts']);
+ }
+ } elseif ($maxId === null) {
+ $request->addPost('unseen_posts', '');
+ }
+
+ if (isset($options['feed_view_info'])) {
+ if (is_array($options['feed_view_info'])) {
+ $request->addPost('feed_view_info', json_encode($options['feed_view_info']));
+ } else {
+ $request->addPost('feed_view_info', json_encode([$options['feed_view_info']]));
+ }
+ } elseif ($maxId === null) {
+ $request->addPost('feed_view_info', '[]');
+ }
+
+ if (!empty($options['push_disabled'])) {
+ $request->addPost('push_disabled', 'true');
+ }
+
+ if (!empty($options['recovered_from_crash'])) {
+ $request->addPost('recovered_from_crash', '1');
+ }
+
+ return $request->getResponse(new Response\TimelineFeedResponse());
+ }
+
+ /**
+ * Get a user's timeline feed.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserFeedResponse
+ */
+ public function getUserFeed(
+ $userId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("feed/user/{$userId}/")
+ ->addParam('exclude_comment', true)
+ ->addParam('only_fetch_first_carousel_media', false);
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\UserFeedResponse());
+ }
+
+ /**
+ * Get your own timeline feed.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserFeedResponse
+ */
+ public function getSelfUserFeed(
+ $maxId = null)
+ {
+ return $this->getUserFeed($this->ig->account_id, $maxId);
+ }
+
+ /**
+ * Get your archived timeline media feed.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UserFeedResponse
+ */
+ public function getArchivedMediaFeed()
+ {
+ return $this->ig->request('feed/only_me_feed/')
+ ->getResponse(new Response\UserFeedResponse());
+ }
+
+ /**
+ * Archives or unarchives one of your timeline media items.
+ *
+ * Marking media as "archived" will hide it from everyone except yourself.
+ * You can unmark the media again at any time, to make it public again.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * "ALBUM", or the raw value of the Item's "getMediaType()" function.
+ * @param bool $onlyMe If true, archives your media so that it's only visible to you.
+ * Otherwise, if false, makes the media public to everyone again.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ArchiveMediaResponse
+ */
+ public function archiveMedia(
+ $mediaId,
+ $onlyMe = true)
+ {
+ $endpoint = $onlyMe ? 'only_me' : 'undo_only_me';
+
+ return $this->ig->request("media/{$mediaId}/{$endpoint}/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('media_id', $mediaId)
+ ->getResponse(new Response\ArchiveMediaResponse());
+ }
+
+ /**
+ * Backup all of your own uploaded photos and videos. :).
+ *
+ * Note that the backup filenames contain the date and time that the media
+ * was uploaded. It uses PHP's timezone to calculate the local time. So be
+ * sure to use date_default_timezone_set() with your local timezone if you
+ * want correct times in the filenames!
+ *
+ * @param string $baseOutputPath (optional) Base-folder for output.
+ * Uses "backups/" path in lib dir if null.
+ * @param bool $printProgress (optional) Toggles terminal output.
+ *
+ * @throws \RuntimeException
+ * @throws \InstagramAPI\Exception\InstagramException
+ */
+ public function backup(
+ $baseOutputPath = null,
+ $printProgress = true)
+ {
+ // Decide which path to use.
+ if ($baseOutputPath === null) {
+ $baseOutputPath = Constants::SRC_DIR.'/../backups/';
+ }
+
+ // Ensure that the whole directory path for the backup exists.
+ $backupFolder = $baseOutputPath.$this->ig->username.'/'.date('Y-m-d').'/';
+ if (!Utils::createFolder($backupFolder)) {
+ throw new \RuntimeException(sprintf(
+ 'The "%s" backup folder is not writable.',
+ $backupFolder
+ ));
+ }
+
+ // Download all media to the output folders.
+ $nextMaxId = null;
+ do {
+ $myTimeline = $this->getSelfUserFeed($nextMaxId);
+
+ // Build a list of all media files on this page.
+ $mediaFiles = []; // Reset queue.
+ foreach ($myTimeline->getItems() as $item) {
+ $itemDate = date('Y-m-d \a\t H.i.s O', $item->getTakenAt());
+ if ($item->getMediaType() == Response\Model\Item::CAROUSEL) {
+ // Albums contain multiple items which must all be queued.
+ // NOTE: We won't name them by their subitem's getIds, since
+ // those Ids have no meaning outside of the album and they
+ // would just mean that the album content is spread out with
+ // wildly varying filenames. Instead, we will name all album
+ // items after their album's Id, with a position offset in
+ // their filename to show their position within the album.
+ $subPosition = 0;
+ foreach ($item->getCarouselMedia() as $subItem) {
+ ++$subPosition;
+ if ($subItem->getMediaType() == Response\Model\CarouselMedia::PHOTO) {
+ $mediaUrl = $subItem->getImageVersions2()->getCandidates()[0]->getUrl();
+ } else {
+ $mediaUrl = $subItem->getVideoVersions()[0]->getUrl();
+ }
+ $subItemId = sprintf('%s [%s-%02d]', $itemDate, $item->getId(), $subPosition);
+ $mediaFiles[$subItemId] = [
+ 'taken_at' => $item->getTakenAt(),
+ 'url' => $mediaUrl,
+ ];
+ }
+ } else {
+ if ($item->getMediaType() == Response\Model\Item::PHOTO) {
+ $mediaUrl = $item->getImageVersions2()->getCandidates()[0]->getUrl();
+ } else {
+ $mediaUrl = $item->getVideoVersions()[0]->getUrl();
+ }
+ $itemId = sprintf('%s [%s]', $itemDate, $item->getId());
+ $mediaFiles[$itemId] = [
+ 'taken_at' => $item->getTakenAt(),
+ 'url' => $mediaUrl,
+ ];
+ }
+ }
+
+ // Download all media files in the current page's file queue.
+ foreach ($mediaFiles as $mediaId => $mediaInfo) {
+ $mediaUrl = $mediaInfo['url'];
+ $fileExtension = pathinfo(parse_url($mediaUrl, PHP_URL_PATH), PATHINFO_EXTENSION);
+ $filePath = $backupFolder.$mediaId.'.'.$fileExtension;
+
+ // Attempt to download the file.
+ if ($printProgress) {
+ echo sprintf("* Downloading \"%s\" to \"%s\".\n", $mediaUrl, $filePath);
+ }
+ copy($mediaUrl, $filePath);
+
+ // Set the file modification time to the taken_at timestamp.
+ if (is_file($filePath)) {
+ touch($filePath, $mediaInfo['taken_at']);
+ }
+ }
+
+ // Update the page ID to point to the next page (if more available).
+ $nextMaxId = $myTimeline->getNextMaxId();
+ } while ($nextMaxId !== null);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Request/Usertag.php b/vendor/mgp25/instagram-php/src/Request/Usertag.php
new file mode 100755
index 0000000..4efba3a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Request/Usertag.php
@@ -0,0 +1,144 @@
+ [],
+ 'in' => [
+ ['position' => $position, 'user_id' => $userId],
+ ],
+ ];
+
+ return $this->ig->media->edit($mediaId, $captionText, ['usertags' => $usertags]);
+ }
+
+ /**
+ * Untag a user from a media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ * @param string $userId Numerical UserPK ID.
+ * @param string $captionText Caption to use for the media.
+ *
+ * @throws \InvalidArgumentException
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\EditMediaResponse
+ */
+ public function untagMedia(
+ $mediaId,
+ $userId,
+ $captionText = '')
+ {
+ $usertags = [
+ 'removed' => [
+ $userId,
+ ],
+ 'in' => [],
+ ];
+
+ return $this->ig->media->edit($mediaId, $captionText, ['usertags' => $usertags]);
+ }
+
+ /**
+ * Remove yourself from a tagged media item.
+ *
+ * @param string $mediaId The media ID in Instagram's internal format (ie "3482384834_43294").
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\MediaInfoResponse
+ */
+ public function removeSelfTag(
+ $mediaId)
+ {
+ return $this->ig->request("usertags/{$mediaId}/remove/")
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->getResponse(new Response\MediaInfoResponse());
+ }
+
+ /**
+ * Get user taggings for a user.
+ *
+ * @param string $userId Numerical UserPK ID.
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UsertagsResponse
+ */
+ public function getUserFeed(
+ $userId,
+ $maxId = null)
+ {
+ $request = $this->ig->request("usertags/{$userId}/feed/");
+
+ if ($maxId !== null) {
+ $request->addParam('max_id', $maxId);
+ }
+
+ return $request->getResponse(new Response\UsertagsResponse());
+ }
+
+ /**
+ * Get user taggings for your own account.
+ *
+ * @param string|null $maxId Next "maximum ID", used for pagination.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\UsertagsResponse
+ */
+ public function getSelfUserFeed(
+ $maxId = null)
+ {
+ return $this->getUserFeed($this->ig->account_id, $maxId);
+ }
+
+ /**
+ * Choose how photos you are tagged in will be added to your profile.
+ *
+ * @param bool $enabled TRUE to manually accept photos, or FALSE to accept automatically.
+ *
+ * @throws \InstagramAPI\Exception\InstagramException
+ *
+ * @return \InstagramAPI\Response\ReviewPreferenceResponse
+ */
+ public function setReviewPreference(
+ $enabled)
+ {
+ return $this->ig->request('usertags/review_preference/')
+ ->addPost('_uuid', $this->ig->uuid)
+ ->addPost('_uid', $this->ig->account_id)
+ ->addPost('_csrftoken', $this->ig->client->getToken())
+ ->addPost('enabled', (int) $enabled)
+ ->getResponse(new Response\ReviewPreferenceResponse());
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Response.php b/vendor/mgp25/instagram-php/src/Response.php
new file mode 100755
index 0000000..abcc39a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response.php
@@ -0,0 +1,141 @@
+ 'string',
+ /*
+ * Instagram's API failure error message(s).
+ *
+ * NOTE: This MUST be marked as 'mixed' since the server can give us
+ * either a single string OR a data structure with multiple messages.
+ * Our custom `getMessage()` will take care of parsing their value.
+ */
+ 'message' => 'mixed',
+ /*
+ * This can exist in any Instagram API response, and carries special
+ * status information.
+ *
+ * Known messages: "fb_needs_reauth", "vkontakte_needs_reauth",
+ * "twitter_needs_reauth", "ameba_needs_reauth", "update_push_token".
+ */
+ '_messages' => 'Response\Model\_Message[]',
+ ];
+
+ /** @var HttpResponseInterface */
+ public $httpResponse;
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ return $this->_getProperty('status') === self::STATUS_OK;
+ }
+
+ /**
+ * Gets the message.
+ *
+ * This function overrides the normal getter with some special processing
+ * to handle unusual multi-error message values in certain responses.
+ *
+ * @throws RuntimeException If the message object is of an unsupported type.
+ *
+ * @return string|null A message string if one exists, otherwise NULL.
+ */
+ public function getMessage()
+ {
+ // Instagram's API usually returns a simple error string. But in some
+ // cases, they instead return a subarray of individual errors, in case
+ // of APIs that can return multiple errors at once.
+ //
+ // Uncomment this if you want to test multiple error handling:
+ // $json = '{"status":"fail","message":{"errors":["Select a valid choice. 0 is not one of the available choices."]}}';
+ // $json = '{"status":"fail","message":{"errors":["Select a valid choice. 0 is not one of the available choices.","Another error.","One more error."]}}';
+ // $data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
+ // $this->_setProperty('message', $data['message']);
+
+ $message = $this->_getProperty('message');
+ if ($message === null || is_string($message)) {
+ // Single error string or nothing at all.
+ return $message;
+ } elseif (is_array($message)) {
+ // Multiple errors in an "errors" subarray.
+ if (count($message) === 1 && isset($message['errors']) && is_array($message['errors'])) {
+ // Add "Multiple Errors" prefix if the response contains more than one.
+ // But most of the time, there will only be one error in the array.
+ $str = (count($message['errors']) > 1 ? 'Multiple Errors: ' : '');
+ $str .= implode(' AND ', $message['errors']); // Assumes all errors are strings.
+ return $str;
+ } else {
+ throw new RuntimeException('Unknown message object. Expected errors subarray but found something else. Please submit a ticket about needing an Instagram-API library update!');
+ }
+ } else {
+ throw new RuntimeException('Unknown message type. Please submit a ticket about needing an Instagram-API library update!');
+ }
+ }
+
+ /**
+ * Gets the HTTP response.
+ *
+ * @return HttpResponseInterface
+ */
+ public function getHttpResponse()
+ {
+ return $this->httpResponse;
+ }
+
+ /**
+ * Sets the HTTP response.
+ *
+ * @param HttpResponseInterface $response
+ */
+ public function setHttpResponse(
+ HttpResponseInterface $response)
+ {
+ $this->httpResponse = $response;
+ }
+
+ /**
+ * Checks if an HTTP response value exists.
+ *
+ * @return bool
+ */
+ public function isHttpResponse()
+ {
+ return $this->httpResponse !== null;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/AccountCreateResponse.php b/vendor/mgp25/instagram-php/src/Response/AccountCreateResponse.php
new file mode 100755
index 0000000..91cc852
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/AccountCreateResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'created_user' => 'Model\User',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/AccountDetailsResponse.php b/vendor/mgp25/instagram-php/src/Response/AccountDetailsResponse.php
new file mode 100755
index 0000000..d67c255
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/AccountDetailsResponse.php
@@ -0,0 +1,52 @@
+ 'string',
+ 'former_username_info' => 'Model\FormerUsernameInfo',
+ 'primary_country_info' => 'Model\PrimaryCountryInfo',
+ 'shared_follower_accounts_info' => 'Model\SharedFollowerAccountsInfo',
+ 'ads_info' => 'Model\AdsInfo',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/AccountSecurityInfoResponse.php b/vendor/mgp25/instagram-php/src/Response/AccountSecurityInfoResponse.php
new file mode 100755
index 0000000..8c027e9
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/AccountSecurityInfoResponse.php
@@ -0,0 +1,57 @@
+ '',
+ 'is_phone_confirmed' => '',
+ 'country_code' => 'int',
+ 'phone_number' => 'string',
+ 'is_two_factor_enabled' => '',
+ 'national_number' => 'string', // Really int, but may be >32bit.
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ActiveFeedAdsResponse.php b/vendor/mgp25/instagram-php/src/Response/ActiveFeedAdsResponse.php
new file mode 100755
index 0000000..e87215f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ActiveFeedAdsResponse.php
@@ -0,0 +1,42 @@
+ 'Model\FeedItem[]',
+ 'next_max_id' => 'string',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ActiveReelAdsResponse.php b/vendor/mgp25/instagram-php/src/Response/ActiveReelAdsResponse.php
new file mode 100755
index 0000000..428279e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ActiveReelAdsResponse.php
@@ -0,0 +1,42 @@
+ 'Model\Reel[]',
+ 'next_max_id' => 'string',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ActivityNewsResponse.php b/vendor/mgp25/instagram-php/src/Response/ActivityNewsResponse.php
new file mode 100755
index 0000000..db1b061
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ActivityNewsResponse.php
@@ -0,0 +1,77 @@
+ 'Model\Story[]',
+ 'old_stories' => 'Model\Story[]',
+ 'continuation' => '',
+ 'friend_request_stories' => 'Model\Story[]',
+ 'counts' => 'Model\Counts',
+ 'subscription' => 'Model\Subscription',
+ 'partition' => '',
+ 'continuation_token' => '',
+ 'ads_manager' => '',
+ 'aymf' => 'Model\Aymf',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ArchiveMediaResponse.php b/vendor/mgp25/instagram-php/src/Response/ArchiveMediaResponse.php
new file mode 100755
index 0000000..97b4bdc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ArchiveMediaResponse.php
@@ -0,0 +1,25 @@
+ 'Model\ArchivedStoriesFeedItem[]',
+ 'num_results' => 'int',
+ 'more_available' => 'bool',
+ 'max_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ArlinkDownloadInfoResponse.php b/vendor/mgp25/instagram-php/src/Response/ArlinkDownloadInfoResponse.php
new file mode 100755
index 0000000..8ec0d50
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ArlinkDownloadInfoResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'download_url' => 'string',
+ 'file_size' => 'string',
+ 'version' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BadgeNotificationsResponse.php b/vendor/mgp25/instagram-php/src/Response/BadgeNotificationsResponse.php
new file mode 100755
index 0000000..7bae681
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BadgeNotificationsResponse.php
@@ -0,0 +1,37 @@
+ 'Model\UnpredictableKeys\CoreUnpredictableContainer',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BlockedListResponse.php b/vendor/mgp25/instagram-php/src/Response/BlockedListResponse.php
new file mode 100755
index 0000000..f63f309
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BlockedListResponse.php
@@ -0,0 +1,42 @@
+ 'Model\User[]',
+ 'next_max_id' => 'string',
+ 'page_size' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BlockedMediaResponse.php b/vendor/mgp25/instagram-php/src/Response/BlockedMediaResponse.php
new file mode 100755
index 0000000..69da2bc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BlockedMediaResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BlockedReelsResponse.php b/vendor/mgp25/instagram-php/src/Response/BlockedReelsResponse.php
new file mode 100755
index 0000000..40e4e55
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BlockedReelsResponse.php
@@ -0,0 +1,45 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BootstrapUsersResponse.php b/vendor/mgp25/instagram-php/src/Response/BootstrapUsersResponse.php
new file mode 100755
index 0000000..e786f4a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BootstrapUsersResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Surface[]',
+ 'users' => 'Model\User[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BroadcastCommentsResponse.php b/vendor/mgp25/instagram-php/src/Response/BroadcastCommentsResponse.php
new file mode 100755
index 0000000..6b6d892
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BroadcastCommentsResponse.php
@@ -0,0 +1,95 @@
+ 'Model\Comment[]',
+ 'comment_count' => 'int',
+ 'live_seconds_per_comment' => 'int',
+ 'has_more_headload_comments' => 'bool',
+ /*
+ * NOTE: Instagram sends "True" or "False" as a string in this property.
+ */
+ 'is_first_fetch' => 'string',
+ 'comment_likes_enabled' => 'bool',
+ 'pinned_comment' => 'Model\Comment',
+ 'system_comments' => 'Model\Comment[]',
+ 'has_more_comments' => 'bool',
+ 'caption_is_edited' => 'bool',
+ 'caption' => '',
+ 'comment_muted' => 'int',
+ 'media_header_display' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BroadcastHeartbeatAndViewerCountResponse.php b/vendor/mgp25/instagram-php/src/Response/BroadcastHeartbeatAndViewerCountResponse.php
new file mode 100755
index 0000000..1f94d75
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BroadcastHeartbeatAndViewerCountResponse.php
@@ -0,0 +1,67 @@
+ 'string',
+ 'viewer_count' => 'int',
+ 'offset_to_video_start' => 'int',
+ 'total_unique_viewer_count' => 'int',
+ 'is_top_live_eligible' => 'int',
+ 'cobroadcaster_ids' => 'string[]',
+ 'is_policy_violation' => 'int',
+ 'policy_violation_reason' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BroadcastInfoResponse.php b/vendor/mgp25/instagram-php/src/Response/BroadcastInfoResponse.php
new file mode 100755
index 0000000..5c89ae7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BroadcastInfoResponse.php
@@ -0,0 +1,132 @@
+ 'string',
+ 'num_total_requests' => 'int',
+ 'num_new_requests' => 'int',
+ 'users' => 'Model\User[]',
+ 'num_unseen_requests' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BroadcastLikeCountResponse.php b/vendor/mgp25/instagram-php/src/Response/BroadcastLikeCountResponse.php
new file mode 100755
index 0000000..6a2cd45
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BroadcastLikeCountResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'likes' => 'int',
+ 'burst_likes' => 'int',
+ 'likers' => 'Model\User[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BroadcastLikeResponse.php b/vendor/mgp25/instagram-php/src/Response/BroadcastLikeResponse.php
new file mode 100755
index 0000000..4385c07
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BroadcastLikeResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/BroadcastQuestionsResponse.php b/vendor/mgp25/instagram-php/src/Response/BroadcastQuestionsResponse.php
new file mode 100755
index 0000000..deb878b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/BroadcastQuestionsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\BroadcastQuestion[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CapabilitiesDecisionsResponse.php b/vendor/mgp25/instagram-php/src/Response/CapabilitiesDecisionsResponse.php
new file mode 100755
index 0000000..09857e8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CapabilitiesDecisionsResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ChallengeResponse.php b/vendor/mgp25/instagram-php/src/Response/ChallengeResponse.php
new file mode 100755
index 0000000..d1af5e8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ChallengeResponse.php
@@ -0,0 +1,25 @@
+ 'Model\User[]',
+ 'suggested_charities' => 'Model\User[]',
+ 'searched_charities' => 'Model\User[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CheckEmailResponse.php b/vendor/mgp25/instagram-php/src/Response/CheckEmailResponse.php
new file mode 100755
index 0000000..61978ec
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CheckEmailResponse.php
@@ -0,0 +1,52 @@
+ '',
+ 'available' => '',
+ 'confirmed' => '',
+ 'username_suggestions' => 'string[]',
+ 'error_type' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CheckUsernameResponse.php b/vendor/mgp25/instagram-php/src/Response/CheckUsernameResponse.php
new file mode 100755
index 0000000..8a5d9ca
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CheckUsernameResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'available' => '',
+ 'error' => '',
+ 'error_type' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CloseFriendsResponse.php b/vendor/mgp25/instagram-php/src/Response/CloseFriendsResponse.php
new file mode 100755
index 0000000..4acd195
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CloseFriendsResponse.php
@@ -0,0 +1,49 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CollectionFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/CollectionFeedResponse.php
new file mode 100755
index 0000000..c1167b7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CollectionFeedResponse.php
@@ -0,0 +1,67 @@
+ 'string',
+ 'collection_name' => 'string',
+ 'items' => 'Model\SavedFeedItem[]',
+ 'num_results' => 'int',
+ 'more_available' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ 'has_related_media' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CommentBroadcastResponse.php b/vendor/mgp25/instagram-php/src/Response/CommentBroadcastResponse.php
new file mode 100755
index 0000000..39fb294
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CommentBroadcastResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Comment',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CommentCategoryFilterResponse.php b/vendor/mgp25/instagram-php/src/Response/CommentCategoryFilterResponse.php
new file mode 100755
index 0000000..06341d7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CommentCategoryFilterResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CommentFilterKeywordsResponse.php b/vendor/mgp25/instagram-php/src/Response/CommentFilterKeywordsResponse.php
new file mode 100755
index 0000000..a9c3ad7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CommentFilterKeywordsResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CommentFilterResponse.php b/vendor/mgp25/instagram-php/src/Response/CommentFilterResponse.php
new file mode 100755
index 0000000..894907f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CommentFilterResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CommentFilterSetResponse.php b/vendor/mgp25/instagram-php/src/Response/CommentFilterSetResponse.php
new file mode 100755
index 0000000..bd9a48c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CommentFilterSetResponse.php
@@ -0,0 +1,25 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CommentResponse.php b/vendor/mgp25/instagram-php/src/Response/CommentResponse.php
new file mode 100755
index 0000000..0d2c2a6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CommentResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Comment',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ConfigureResponse.php b/vendor/mgp25/instagram-php/src/Response/ConfigureResponse.php
new file mode 100755
index 0000000..5c2e426
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ConfigureResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'media' => 'Model\Item',
+ 'client_sidecar_id' => 'string',
+ 'message_metadata' => 'Model\DirectMessageMetadata[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CreateBusinessInfoResponse.php b/vendor/mgp25/instagram-php/src/Response/CreateBusinessInfoResponse.php
new file mode 100755
index 0000000..785a8dd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CreateBusinessInfoResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CreateCollectionResponse.php b/vendor/mgp25/instagram-php/src/Response/CreateCollectionResponse.php
new file mode 100755
index 0000000..e92fa0b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CreateCollectionResponse.php
@@ -0,0 +1,40 @@
+ 'Model\Reel',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/CreateLiveResponse.php b/vendor/mgp25/instagram-php/src/Response/CreateLiveResponse.php
new file mode 100755
index 0000000..2560dd5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/CreateLiveResponse.php
@@ -0,0 +1,157 @@
+ 'string',
+ 'upload_url' => 'string',
+ 'max_time_in_seconds' => 'int',
+ 'speed_test_ui_timeout' => 'int',
+ 'stream_network_speed_test_payload_chunk_size_in_bytes' => 'int',
+ 'stream_network_speed_test_payload_size_in_bytes' => 'int',
+ 'stream_network_speed_test_payload_timeout_in_seconds' => 'int',
+ 'speed_test_minimum_bandwidth_threshold' => 'int',
+ 'speed_test_retry_max_count' => 'int',
+ 'speed_test_retry_time_delay' => 'int',
+ 'disable_speed_test' => 'int',
+ 'stream_video_allow_b_frames' => 'int',
+ 'stream_video_width' => 'int',
+ 'stream_video_bit_rate' => 'int',
+ 'stream_video_fps' => 'int',
+ 'stream_audio_bit_rate' => 'int',
+ 'stream_audio_sample_rate' => 'int',
+ 'stream_audio_channels' => 'int',
+ 'heartbeat_interval' => 'int',
+ 'broadcaster_update_frequency' => 'int',
+ 'stream_video_adaptive_bitrate_config' => '',
+ 'stream_network_connection_retry_count' => 'int',
+ 'stream_network_connection_retry_delay_in_seconds' => 'int',
+ 'connect_with_1rtt' => 'int',
+ 'avc_rtmp_payload' => 'int',
+ 'allow_resolution_change' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DeleteCollectionResponse.php b/vendor/mgp25/instagram-php/src/Response/DeleteCollectionResponse.php
new file mode 100755
index 0000000..1c7ef6d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DeleteCollectionResponse.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'users' => 'Model\User[]',
+ 'left_users' => 'Model\User[]',
+ 'items' => 'Model\DirectThreadItem[]',
+ 'last_activity_at' => '',
+ 'muted' => '',
+ 'named' => '',
+ 'canonical' => '',
+ 'pending' => '',
+ 'thread_type' => '',
+ 'viewer_id' => 'string',
+ 'thread_title' => '',
+ 'inviter' => 'Model\User',
+ 'has_older' => 'bool',
+ 'has_newer' => 'bool',
+ 'last_seen_at' => '',
+ 'is_pin' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectInboxResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectInboxResponse.php
new file mode 100755
index 0000000..84531d2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectInboxResponse.php
@@ -0,0 +1,67 @@
+ 'Model\User',
+ 'pending_requests_total' => '',
+ 'seq_id' => 'string',
+ 'has_pending_top_requests' => 'bool',
+ 'pending_requests_users' => 'Model\User[]',
+ 'inbox' => 'Model\DirectInbox',
+ 'megaphone' => 'Model\Megaphone',
+ 'snapshot_at_ms' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectPendingInboxResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectPendingInboxResponse.php
new file mode 100755
index 0000000..ab11e2b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectPendingInboxResponse.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'pending_requests_total' => '',
+ 'inbox' => 'Model\DirectInbox',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectRankedRecipientsResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectRankedRecipientsResponse.php
new file mode 100755
index 0000000..f1eee00
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectRankedRecipientsResponse.php
@@ -0,0 +1,52 @@
+ '',
+ 'ranked_recipients' => 'Model\DirectRankedRecipient[]',
+ 'filtered' => '',
+ 'request_id' => 'string',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectRecentRecipientsResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectRecentRecipientsResponse.php
new file mode 100755
index 0000000..7242b71
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectRecentRecipientsResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'recent_recipients' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectSeenItemResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectSeenItemResponse.php
new file mode 100755
index 0000000..37e9c1d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectSeenItemResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'payload' => 'Model\DirectSeenItemPayload', // The number of unseen items.
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectSendItemResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectSendItemResponse.php
new file mode 100755
index 0000000..a275d2e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectSendItemResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'status_code' => '',
+ 'payload' => 'Model\DirectSendItemPayload',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectSendItemsResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectSendItemsResponse.php
new file mode 100755
index 0000000..17a133d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectSendItemsResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'status_code' => '',
+ 'payload' => 'Model\DirectSendItemPayload[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectShareInboxResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectShareInboxResponse.php
new file mode 100755
index 0000000..4c380ed
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectShareInboxResponse.php
@@ -0,0 +1,57 @@
+ '',
+ 'max_id' => 'string',
+ 'new_shares' => '',
+ 'patches' => '',
+ 'last_counted_at' => '',
+ 'new_shares_info' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectThreadResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectThreadResponse.php
new file mode 100755
index 0000000..d62edba
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectThreadResponse.php
@@ -0,0 +1,32 @@
+ 'Model\DirectThread',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DirectVisualThreadResponse.php b/vendor/mgp25/instagram-php/src/Response/DirectVisualThreadResponse.php
new file mode 100755
index 0000000..32cd4b7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DirectVisualThreadResponse.php
@@ -0,0 +1,192 @@
+ '',
+ 'items' => 'Model\Item[]',
+ 'more_available' => '',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DiscoverPeopleResponse.php b/vendor/mgp25/instagram-php/src/Response/DiscoverPeopleResponse.php
new file mode 100755
index 0000000..6cd4976
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DiscoverPeopleResponse.php
@@ -0,0 +1,47 @@
+ 'bool',
+ 'max_id' => 'string',
+ 'suggested_users' => 'Model\SuggestedUsers',
+ 'new_suggested_users' => 'Model\SuggestedUsers',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/DiscoverTopLiveResponse.php b/vendor/mgp25/instagram-php/src/Response/DiscoverTopLiveResponse.php
new file mode 100755
index 0000000..2195061
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/DiscoverTopLiveResponse.php
@@ -0,0 +1,57 @@
+ 'Model\Broadcast[]',
+ 'post_live_broadcasts' => 'Model\PostLiveItem[]',
+ 'score_map' => '',
+ 'more_available' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/EditCollectionResponse.php b/vendor/mgp25/instagram-php/src/Response/EditCollectionResponse.php
new file mode 100755
index 0000000..d90ef41
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/EditCollectionResponse.php
@@ -0,0 +1,40 @@
+ 'Model\Item',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/EnableDisableLiveCommentsResponse.php b/vendor/mgp25/instagram-php/src/Response/EnableDisableLiveCommentsResponse.php
new file mode 100755
index 0000000..415b53a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/EnableDisableLiveCommentsResponse.php
@@ -0,0 +1,32 @@
+ 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/EnableTwoFactorSMSResponse.php b/vendor/mgp25/instagram-php/src/Response/EnableTwoFactorSMSResponse.php
new file mode 100755
index 0000000..0ed8060
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/EnableTwoFactorSMSResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ExploreResponse.php b/vendor/mgp25/instagram-php/src/Response/ExploreResponse.php
new file mode 100755
index 0000000..99026ec
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ExploreResponse.php
@@ -0,0 +1,67 @@
+ 'int',
+ 'auto_load_more_enabled' => 'bool',
+ 'items' => 'Model\ExploreItem[]',
+ 'sectional_items' => 'Model\Section[]',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'max_id' => 'string',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FBLocationResponse.php b/vendor/mgp25/instagram-php/src/Response/FBLocationResponse.php
new file mode 100755
index 0000000..f2e55c0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FBLocationResponse.php
@@ -0,0 +1,42 @@
+ 'bool',
+ 'items' => 'Model\LocationItem[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FBSearchResponse.php b/vendor/mgp25/instagram-php/src/Response/FBSearchResponse.php
new file mode 100755
index 0000000..61793ce
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FBSearchResponse.php
@@ -0,0 +1,48 @@
+ 'bool',
+ 'list' => 'Model\UserList[]',
+ 'clear_client_cache' => 'bool',
+ 'has_more' => 'bool',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FaceEffectsResponse.php b/vendor/mgp25/instagram-php/src/Response/FaceEffectsResponse.php
new file mode 100755
index 0000000..23496d4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FaceEffectsResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'effects' => 'Model\Effect[]',
+ 'loading_effect' => 'Model\Effect',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FaceModelsResponse.php b/vendor/mgp25/instagram-php/src/Response/FaceModelsResponse.php
new file mode 100755
index 0000000..8db975f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FaceModelsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\FaceModels',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FacebookHiddenEntitiesResponse.php b/vendor/mgp25/instagram-php/src/Response/FacebookHiddenEntitiesResponse.php
new file mode 100755
index 0000000..4ada8cd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FacebookHiddenEntitiesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\HiddenEntities',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FacebookOTAResponse.php b/vendor/mgp25/instagram-php/src/Response/FacebookOTAResponse.php
new file mode 100755
index 0000000..bbb7302
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FacebookOTAResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'request_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FetchQPDataResponse.php b/vendor/mgp25/instagram-php/src/Response/FetchQPDataResponse.php
new file mode 100755
index 0000000..4f8c75f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FetchQPDataResponse.php
@@ -0,0 +1,52 @@
+ 'string',
+ 'extra_info' => 'Model\QPExtraInfo[]',
+ 'qp_data' => 'Model\QPData[]',
+ 'client_cache_ttl_in_sec' => 'int',
+ 'error_msg' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FinalViewerListResponse.php b/vendor/mgp25/instagram-php/src/Response/FinalViewerListResponse.php
new file mode 100755
index 0000000..374cc3c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FinalViewerListResponse.php
@@ -0,0 +1,37 @@
+ 'Model\User[]',
+ 'total_unique_viewer_count' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FollowerAndFollowingResponse.php b/vendor/mgp25/instagram-php/src/Response/FollowerAndFollowingResponse.php
new file mode 100755
index 0000000..0e51564
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FollowerAndFollowingResponse.php
@@ -0,0 +1,57 @@
+ 'Model\User[]',
+ 'suggested_users' => 'Model\SuggestedUsers',
+ 'truncate_follow_requests_at_index' => 'int',
+ 'next_max_id' => 'string',
+ 'page_size' => '',
+ 'big_list' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FollowingRecentActivityResponse.php b/vendor/mgp25/instagram-php/src/Response/FollowingRecentActivityResponse.php
new file mode 100755
index 0000000..691366b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FollowingRecentActivityResponse.php
@@ -0,0 +1,47 @@
+ 'Model\Story[]',
+ 'next_max_id' => 'string',
+ 'auto_load_more_enabled' => '',
+ 'megaphone' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FormerUsernamesResponse.php b/vendor/mgp25/instagram-php/src/Response/FormerUsernamesResponse.php
new file mode 100755
index 0000000..c8dbe23
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FormerUsernamesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\FormerUsername[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FriendshipResponse.php b/vendor/mgp25/instagram-php/src/Response/FriendshipResponse.php
new file mode 100755
index 0000000..5f9c977
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FriendshipResponse.php
@@ -0,0 +1,32 @@
+ 'Model\FriendshipStatus',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FriendshipsShowManyResponse.php b/vendor/mgp25/instagram-php/src/Response/FriendshipsShowManyResponse.php
new file mode 100755
index 0000000..bc48bdd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FriendshipsShowManyResponse.php
@@ -0,0 +1,32 @@
+ 'Model\UnpredictableKeys\FriendshipStatusUnpredictableContainer',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/FriendshipsShowResponse.php b/vendor/mgp25/instagram-php/src/Response/FriendshipsShowResponse.php
new file mode 100755
index 0000000..865e5d4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/FriendshipsShowResponse.php
@@ -0,0 +1,72 @@
+ 'Model\Collection[]',
+ 'more_available' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/GraphqlResponse.php b/vendor/mgp25/instagram-php/src/Response/GraphqlResponse.php
new file mode 100755
index 0000000..7e66914
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/GraphqlResponse.php
@@ -0,0 +1,82 @@
+ 'Model\GraphData',
+ ];
+
+ public function getGenderGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getGenderGraph();
+ }
+
+ public function getAllAgeGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getAllFollowersAgeGraph();
+ }
+
+ public function getMenAgeGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getMenFollowersAgeGraph();
+ }
+
+ public function getWomenAgeGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getWomenFollowersAgeGraph();
+ }
+
+ public function getFollowersTopCities()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getFollowersTopCitiesGraph();
+ }
+
+ public function getFollowersTopCountries()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getFollowersTopCountriesGraph();
+ }
+
+ public function getDailyWeekFollowersGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getWeekDailyFollowersGraph();
+ }
+
+ public function getDaysHourlyFollowersGraphData()
+ {
+ return $this->_getProperty('data')->getUser()->getBusinessManager()->getFollowersUnit()->getDaysHourlyFollowersGraph();
+ }
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ return true;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/HashtagsResponse.php b/vendor/mgp25/instagram-php/src/Response/HashtagsResponse.php
new file mode 100755
index 0000000..64168ba
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/HashtagsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Hashtag[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/HighlightFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/HighlightFeedResponse.php
new file mode 100755
index 0000000..fc80529
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/HighlightFeedResponse.php
@@ -0,0 +1,57 @@
+ 'bool',
+ 'next_max_id' => 'string',
+ 'stories' => 'Model\Story[]',
+ 'show_empty_state' => 'bool',
+ 'tray' => 'Model\StoryTray[]',
+ 'tv_channel' => 'Model\StoryTvChannel',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/InsightsResponse.php b/vendor/mgp25/instagram-php/src/Response/InsightsResponse.php
new file mode 100755
index 0000000..4efe919
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/InsightsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Insights',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/LauncherSyncResponse.php b/vendor/mgp25/instagram-php/src/Response/LauncherSyncResponse.php
new file mode 100755
index 0000000..de6f171
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/LauncherSyncResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/LikeFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/LikeFeedResponse.php
new file mode 100755
index 0000000..506564f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/LikeFeedResponse.php
@@ -0,0 +1,62 @@
+ '',
+ 'items' => 'Model\Item[]',
+ 'more_available' => '',
+ 'patches' => '',
+ 'last_counted_at' => '',
+ 'num_results' => 'int',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/LinkAddressBookResponse.php b/vendor/mgp25/instagram-php/src/Response/LinkAddressBookResponse.php
new file mode 100755
index 0000000..1e192f1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/LinkAddressBookResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Suggestion[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/LinkageStatusResponse.php b/vendor/mgp25/instagram-php/src/Response/LinkageStatusResponse.php
new file mode 100755
index 0000000..a8dea6d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/LinkageStatusResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/LocationFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/LocationFeedResponse.php
new file mode 100755
index 0000000..9f4d5e4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/LocationFeedResponse.php
@@ -0,0 +1,52 @@
+ 'Model\Section[]',
+ 'next_page' => 'int',
+ 'more_available' => 'bool',
+ 'next_media_ids' => 'int[]',
+ 'next_max_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/LocationResponse.php b/vendor/mgp25/instagram-php/src/Response/LocationResponse.php
new file mode 100755
index 0000000..a98c54f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/LocationResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Location[]',
+ 'request_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/LocationStoryResponse.php b/vendor/mgp25/instagram-php/src/Response/LocationStoryResponse.php
new file mode 100755
index 0000000..7f90875
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/LocationStoryResponse.php
@@ -0,0 +1,32 @@
+ 'Model\StoryTray',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/LoginResponse.php b/vendor/mgp25/instagram-php/src/Response/LoginResponse.php
new file mode 100755
index 0000000..3e5428f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/LoginResponse.php
@@ -0,0 +1,157 @@
+ 'string',
+ 'has_anonymous_profile_picture' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_id' => 'string',
+ 'full_name' => 'string',
+ 'pk' => 'string',
+ 'is_private' => 'bool',
+ 'is_verified' => 'bool',
+ 'allowed_commenter_type' => 'string',
+ 'reel_auto_archive' => 'string',
+ 'allow_contacts_sync' => 'bool',
+ 'phone_number' => 'string',
+ 'country_code' => 'int',
+ 'national_number' => 'int',
+ 'error_title' => '', // On wrong pass.
+ 'error_type' => '', // On wrong pass.
+ 'buttons' => '', // On wrong pass.
+ 'invalid_credentials' => '', // On wrong pass.
+ 'logged_in_user' => 'Model\User',
+ 'two_factor_required' => '',
+ 'phone_verification_settings' => 'Model\PhoneVerificationSettings',
+ 'two_factor_info' => 'Model\TwoFactorInfo',
+ 'checkpoint_url' => 'string',
+ 'lock' => 'bool',
+ 'help_url' => 'string',
+ 'challenge' => 'Model\Challenge',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/LogoutResponse.php b/vendor/mgp25/instagram-php/src/Response/LogoutResponse.php
new file mode 100755
index 0000000..184b931
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/LogoutResponse.php
@@ -0,0 +1,25 @@
+ 'Model\SystemControl',
+ 'GATE_APP_VERSION' => 'bool',
+ 'trace_control' => 'Model\TraceControl',
+ 'id' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MediaCommentRepliesResponse.php b/vendor/mgp25/instagram-php/src/Response/MediaCommentRepliesResponse.php
new file mode 100755
index 0000000..0c19039
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MediaCommentRepliesResponse.php
@@ -0,0 +1,85 @@
+ 'Model\Comment',
+ /*
+ * Number of child comments in this comment thread.
+ */
+ 'child_comment_count' => 'int',
+ 'child_comments' => 'Model\Comment[]',
+ /*
+ * When "has_more_tail_child_comments" is true, you can use the value
+ * in "next_max_child_cursor" as "max_id" parameter to load up to
+ * "num_tail_child_comments" older child-comments.
+ */
+ 'has_more_tail_child_comments' => 'bool',
+ 'next_max_child_cursor' => 'string',
+ 'num_tail_child_comments' => 'int',
+ /*
+ * When "has_more_head_child_comments" is true, you can use the value
+ * in "next_min_child_cursor" as "min_id" parameter to load up to
+ * "num_head_child_comments" newer child-comments.
+ */
+ 'has_more_head_child_comments' => 'bool',
+ 'next_min_child_cursor' => 'string',
+ 'num_head_child_comments' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MediaCommentsResponse.php b/vendor/mgp25/instagram-php/src/Response/MediaCommentsResponse.php
new file mode 100755
index 0000000..4035d2f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MediaCommentsResponse.php
@@ -0,0 +1,87 @@
+ 'Model\Comment[]',
+ 'comment_count' => 'int',
+ 'comment_likes_enabled' => 'bool',
+ 'next_max_id' => 'string',
+ 'next_min_id' => 'string',
+ 'caption' => 'Model\Caption',
+ 'has_more_comments' => 'bool',
+ 'caption_is_edited' => 'bool',
+ 'preview_comments' => '',
+ 'has_more_headload_comments' => 'bool',
+ 'media_header_display' => 'string',
+ 'threading_enabled' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MediaDeleteResponse.php b/vendor/mgp25/instagram-php/src/Response/MediaDeleteResponse.php
new file mode 100755
index 0000000..9ae1c54
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MediaDeleteResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MediaInfoResponse.php b/vendor/mgp25/instagram-php/src/Response/MediaInfoResponse.php
new file mode 100755
index 0000000..725e7e0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MediaInfoResponse.php
@@ -0,0 +1,47 @@
+ '',
+ 'num_results' => 'int',
+ 'more_available' => '',
+ 'items' => 'Model\Item[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MediaInsightsResponse.php b/vendor/mgp25/instagram-php/src/Response/MediaInsightsResponse.php
new file mode 100755
index 0000000..7d2239f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MediaInsightsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\MediaInsights',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MediaLikersResponse.php b/vendor/mgp25/instagram-php/src/Response/MediaLikersResponse.php
new file mode 100755
index 0000000..b45e912
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MediaLikersResponse.php
@@ -0,0 +1,37 @@
+ 'int',
+ 'users' => 'Model\User[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MediaSeenResponse.php b/vendor/mgp25/instagram-php/src/Response/MediaSeenResponse.php
new file mode 100755
index 0000000..11b9823
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MediaSeenResponse.php
@@ -0,0 +1,25 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AccountAccessToolConfig.php b/vendor/mgp25/instagram-php/src/Response/Model/AccountAccessToolConfig.php
new file mode 100755
index 0000000..78842dc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AccountAccessToolConfig.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'viewer' => 'User',
+ 'viewerId' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AccountSummaryUnit.php b/vendor/mgp25/instagram-php/src/Response/Model/AccountSummaryUnit.php
new file mode 100755
index 0000000..de08c44
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AccountSummaryUnit.php
@@ -0,0 +1,20 @@
+ 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Action.php b/vendor/mgp25/instagram-php/src/Response/Model/Action.php
new file mode 100755
index 0000000..703af80
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Action.php
@@ -0,0 +1,35 @@
+ 'Text',
+ 'url' => 'string',
+ 'limit' => 'int',
+ 'dismiss_promotion' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php b/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php
new file mode 100755
index 0000000..b0e0fa3
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php
@@ -0,0 +1,41 @@
+ '',
+ 'action_count' => '',
+ 'action_timestamp' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ActionLog.php b/vendor/mgp25/instagram-php/src/Response/Model/ActionLog.php
new file mode 100755
index 0000000..b383647
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ActionLog.php
@@ -0,0 +1,25 @@
+ 'Bold[]',
+ 'description' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Ad4ad.php b/vendor/mgp25/instagram-php/src/Response/Model/Ad4ad.php
new file mode 100755
index 0000000..d46b822
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Ad4ad.php
@@ -0,0 +1,45 @@
+ '',
+ 'title' => '',
+ 'media' => 'Item',
+ 'footer' => '',
+ 'id' => 'string',
+ 'tracking_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AdMetadata.php b/vendor/mgp25/instagram-php/src/Response/Model/AdMetadata.php
new file mode 100755
index 0000000..d2ea416
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AdMetadata.php
@@ -0,0 +1,25 @@
+ '',
+ 'type' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AdsInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/AdsInfo.php
new file mode 100755
index 0000000..863443d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AdsInfo.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'ads_url' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AndroidLinks.php b/vendor/mgp25/instagram-php/src/Response/Model/AndroidLinks.php
new file mode 100755
index 0000000..3622ebb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AndroidLinks.php
@@ -0,0 +1,75 @@
+ 'int',
+ 'webUri' => 'string',
+ 'androidClass' => 'string',
+ 'package' => 'string',
+ 'deeplinkUri' => 'string',
+ 'callToActionTitle' => 'string',
+ 'redirectUri' => 'string',
+ 'igUserId' => 'string',
+ 'appInstallObjectiveInvalidationBehavior' => '',
+ 'tapAndHoldContext' => 'string',
+ 'leadGenFormId' => 'string',
+ 'canvasDocId' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMedia.php b/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMedia.php
new file mode 100755
index 0000000..605273c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMedia.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'images' => 'AnimatedMediaImage',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImage.php b/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImage.php
new file mode 100755
index 0000000..5da776d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImage.php
@@ -0,0 +1,20 @@
+ 'AnimatedMediaImageFixedHeigth',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImageFixedHeigth.php b/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImageFixedHeigth.php
new file mode 100755
index 0000000..1a7013d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AnimatedMediaImageFixedHeigth.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'width' => 'string',
+ 'heigth' => 'string',
+ 'size' => 'string',
+ 'mp4' => 'string',
+ 'mp4_size' => 'string',
+ 'webp' => 'string',
+ 'webp_size' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ArchivedStoriesFeedItem.php b/vendor/mgp25/instagram-php/src/Response/Model/ArchivedStoriesFeedItem.php
new file mode 100755
index 0000000..47b62f5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ArchivedStoriesFeedItem.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'media_count' => 'int',
+ 'id' => 'string',
+ 'reel_type' => 'string',
+ 'latest_reel_media' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Args.php b/vendor/mgp25/instagram-php/src/Response/Model/Args.php
new file mode 100755
index 0000000..81ce41a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Args.php
@@ -0,0 +1,135 @@
+ 'string',
+ 'text' => 'string',
+ 'icon_url' => 'string',
+ 'links' => 'Link[]',
+ 'rich_text' => 'string',
+ 'profile_id' => 'string',
+ 'profile_image' => 'string',
+ 'media' => 'Media[]',
+ 'comment_notif_type' => 'string',
+ 'timestamp' => 'string',
+ 'tuuid' => 'string',
+ 'clicked' => 'bool',
+ 'profile_name' => 'string',
+ 'action_url' => 'string',
+ 'destination' => 'string',
+ 'actions' => 'string[]',
+ 'latest_reel_media' => 'string',
+ 'comment_id' => 'string',
+ 'request_count' => '',
+ 'inline_follow' => 'InlineFollow',
+ 'comment_ids' => 'string[]',
+ 'second_profile_id' => 'string',
+ 'second_profile_image' => '',
+ 'profile_image_destination' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AssetModel.php b/vendor/mgp25/instagram-php/src/Response/Model/AssetModel.php
new file mode 100755
index 0000000..2623a62
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AssetModel.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Attribution.php b/vendor/mgp25/instagram-php/src/Response/Model/Attribution.php
new file mode 100755
index 0000000..c3fe9e0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Attribution.php
@@ -0,0 +1,20 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AudioContext.php b/vendor/mgp25/instagram-php/src/Response/Model/AudioContext.php
new file mode 100755
index 0000000..29c14b6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AudioContext.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'duration' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Aymf.php b/vendor/mgp25/instagram-php/src/Response/Model/Aymf.php
new file mode 100755
index 0000000..7e57b63
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Aymf.php
@@ -0,0 +1,25 @@
+ 'AymfItem[]',
+ 'more_available' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/AymfItem.php b/vendor/mgp25/instagram-php/src/Response/Model/AymfItem.php
new file mode 100755
index 0000000..f9039aa
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/AymfItem.php
@@ -0,0 +1,731 @@
+ 'string',
+ 'uuid' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Badging.php b/vendor/mgp25/instagram-php/src/Response/Model/Badging.php
new file mode 100755
index 0000000..2d395b2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Badging.php
@@ -0,0 +1,25 @@
+ '',
+ 'items' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/BiographyEntities.php b/vendor/mgp25/instagram-php/src/Response/Model/BiographyEntities.php
new file mode 100755
index 0000000..687c8d4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/BiographyEntities.php
@@ -0,0 +1,30 @@
+ '',
+ 'raw_text' => 'string',
+ 'nux_type' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/BlockedReels.php b/vendor/mgp25/instagram-php/src/Response/Model/BlockedReels.php
new file mode 100755
index 0000000..87e3105
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/BlockedReels.php
@@ -0,0 +1,30 @@
+ 'User[]',
+ 'page_size' => '',
+ 'big_list' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Bold.php b/vendor/mgp25/instagram-php/src/Response/Model/Bold.php
new file mode 100755
index 0000000..9ebca09
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Bold.php
@@ -0,0 +1,25 @@
+ '',
+ 'end' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Broadcast.php b/vendor/mgp25/instagram-php/src/Response/Model/Broadcast.php
new file mode 100755
index 0000000..b0a8534
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Broadcast.php
@@ -0,0 +1,151 @@
+ 'User',
+ 'cobroadcasters' => '',
+ /*
+ * A string such as "active" or "post_live".
+ */
+ 'broadcast_status' => 'string',
+ 'is_gaming_content' => 'bool',
+ 'is_player_live_trace_enabled' => 'bool',
+ 'dash_live_predictive_playback_url' => 'string',
+ 'cover_frame_url' => 'string',
+ 'published_time' => 'string',
+ 'hide_from_feed_unit' => 'bool',
+ 'broadcast_message' => 'string',
+ 'muted' => '',
+ 'media_id' => 'string',
+ 'id' => 'string',
+ 'rtmp_playback_url' => 'string',
+ 'dash_abr_playback_url' => 'string',
+ 'dash_playback_url' => 'string',
+ 'ranked_position' => '',
+ 'organic_tracking_token' => 'string',
+ 'seen_ranked_position' => '',
+ 'viewer_count' => 'int',
+ 'dash_manifest' => 'string',
+ /*
+ * Unix timestamp of when the "post_live" will expire.
+ */
+ 'expire_at' => 'string',
+ 'encoding_tag' => 'string',
+ 'total_unique_viewer_count' => 'int',
+ 'internal_only' => 'bool',
+ 'number_of_qualities' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/BroadcastQuestion.php b/vendor/mgp25/instagram-php/src/Response/Model/BroadcastQuestion.php
new file mode 100755
index 0000000..c9bd7c3
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/BroadcastQuestion.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'qid' => 'string',
+ 'source' => 'string',
+ 'user' => 'User',
+ 'story_sticker_text' => 'string',
+ 'timestamp' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/BroadcastStatusItem.php b/vendor/mgp25/instagram-php/src/Response/Model/BroadcastStatusItem.php
new file mode 100755
index 0000000..f25ace4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/BroadcastStatusItem.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'has_reduced_visibility' => 'bool',
+ 'cover_frame_url' => 'string',
+ 'viewer_count' => 'int',
+ 'id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/BusinessEdge.php b/vendor/mgp25/instagram-php/src/Response/Model/BusinessEdge.php
new file mode 100755
index 0000000..820a1b3
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/BusinessEdge.php
@@ -0,0 +1,25 @@
+ 'BusinessNode',
+ 'cursor' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/BusinessFeed.php b/vendor/mgp25/instagram-php/src/Response/Model/BusinessFeed.php
new file mode 100755
index 0000000..2ea0643
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/BusinessFeed.php
@@ -0,0 +1,20 @@
+ 'SummaryPromotions',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/BusinessManager.php b/vendor/mgp25/instagram-php/src/Response/Model/BusinessManager.php
new file mode 100755
index 0000000..4e6799e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/BusinessManager.php
@@ -0,0 +1,50 @@
+ 'AccountSummaryUnit',
+ 'account_insights_unit' => 'BusinessNode',
+ 'followers_unit' => 'FollowersUnit',
+ 'top_posts_unit' => 'BusinessNode',
+ 'stories_unit' => 'BusinessNode',
+ 'promotions_unit' => 'PromotionsUnit',
+ 'feed' => 'BusinessFeed',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/BusinessManagerStatus.php b/vendor/mgp25/instagram-php/src/Response/Model/BusinessManagerStatus.php
new file mode 100755
index 0000000..21803f4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/BusinessManagerStatus.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'account_type' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/BusinessNode.php b/vendor/mgp25/instagram-php/src/Response/Model/BusinessNode.php
new file mode 100755
index 0000000..e36a374
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/BusinessNode.php
@@ -0,0 +1,190 @@
+ '',
+ 'followers_count' => '',
+ 'followers_delta_from_last_week' => '',
+ 'posts_count' => '',
+ 'posts_delta_from_last_week' => '',
+ 'last_week_impressions' => '',
+ 'week_over_week_impressions' => '',
+ 'last_week_reach' => '',
+ 'week_over_week_reach' => '',
+ 'last_week_profile_visits' => '',
+ 'week_over_week_profile_visits' => '',
+ 'last_week_website_visits' => '',
+ 'week_over_week_website_visits' => '',
+ 'last_week_call' => '',
+ 'week_over_week_call' => '',
+ 'last_week_text' => '',
+ 'week_over_week_text' => '',
+ 'last_week_email' => '',
+ 'week_over_week_email' => '',
+ 'last_week_get_direction' => '',
+ 'week_over_week_get_direction' => '',
+ 'average_engagement_count' => '',
+ 'last_week_impressions_day_graph' => '',
+ 'last_week_reach_day_graph' => '',
+ 'last_week_profile_visits_day_graph' => '',
+ 'summary_posts' => '',
+ 'state' => '',
+ 'summary_stories' => '',
+ 'followers_unit_state' => '',
+ 'today_hourly_graph' => '',
+ 'gender_graph' => '',
+ 'all_followers_age_graph' => '',
+ 'followers_top_cities_graph' => '',
+ 'summary_promotions' => '',
+ 'top_posts' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Button.php b/vendor/mgp25/instagram-php/src/Response/Model/Button.php
new file mode 100755
index 0000000..f11270a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Button.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'url' => 'string',
+ 'action' => '',
+ 'background_color' => '',
+ 'border_color' => '',
+ 'text_color' => '',
+ 'action_info' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Caption.php b/vendor/mgp25/instagram-php/src/Response/Model/Caption.php
new file mode 100755
index 0000000..3d7f06d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Caption.php
@@ -0,0 +1,85 @@
+ '',
+ 'user_id' => 'string',
+ 'created_at_utc' => 'string',
+ 'created_at' => 'string',
+ 'bit_flags' => 'int',
+ 'user' => 'User',
+ 'content_type' => '',
+ 'text' => 'string',
+ 'share_enabled' => 'bool',
+ 'media_id' => 'string',
+ 'pk' => 'string',
+ 'type' => '',
+ 'has_translation' => 'bool',
+ 'did_report_as_spam' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/CarouselMedia.php b/vendor/mgp25/instagram-php/src/Response/Model/CarouselMedia.php
new file mode 100755
index 0000000..65c0aba
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/CarouselMedia.php
@@ -0,0 +1,196 @@
+ 'string',
+ 'id' => 'string',
+ 'carousel_parent_id' => 'string',
+ 'fb_user_tags' => 'Usertag',
+ 'number_of_qualities' => 'int',
+ 'video_codec' => 'string',
+ 'is_dash_eligible' => 'int',
+ 'video_dash_manifest' => 'string',
+ 'image_versions2' => 'Image_Versions2',
+ 'video_versions' => 'VideoVersions[]',
+ 'has_audio' => 'bool',
+ 'video_duration' => 'float',
+ 'video_subtitles_uri' => 'string',
+ 'original_height' => 'int',
+ 'original_width' => 'int',
+ /*
+ * A number describing what type of media this is. Should be compared
+ * against the `CarouselMedia::PHOTO` and `CarouselMedia::VIDEO`
+ * constants!
+ */
+ 'media_type' => 'int',
+ 'dynamic_item_id' => 'string',
+ 'usertags' => 'Usertag',
+ 'preview' => 'string',
+ 'headline' => 'Headline',
+ 'link' => 'string',
+ 'link_text' => 'string',
+ 'link_hint_text' => 'string',
+ 'android_links' => 'AndroidLinks[]',
+ 'ad_metadata' => 'AdMetadata[]',
+ 'ad_action' => 'string',
+ 'ad_link_type' => 'int',
+ 'force_overlay' => 'bool',
+ 'hide_nux_text' => 'bool',
+ 'overlay_text' => 'string',
+ 'overlay_title' => 'string',
+ 'overlay_subtitle' => 'string',
+ 'photo_of_you' => 'bool',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'dominant_color' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/CatalogData.php b/vendor/mgp25/instagram-php/src/Response/Model/CatalogData.php
new file mode 100755
index 0000000..cf3d8ae
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/CatalogData.php
@@ -0,0 +1,25 @@
+ 'PageInfo',
+ 'edges' => 'CatalogEdge[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/CatalogEdge.php b/vendor/mgp25/instagram-php/src/Response/Model/CatalogEdge.php
new file mode 100755
index 0000000..d6277ff
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/CatalogEdge.php
@@ -0,0 +1,20 @@
+ 'CatalogNode',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/CatalogNode.php b/vendor/mgp25/instagram-php/src/Response/Model/CatalogNode.php
new file mode 100755
index 0000000..a4be371
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/CatalogNode.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'full_price' => '',
+ 'current_price' => '',
+ 'name' => 'string',
+ 'description' => 'string',
+ 'main_image_with_safe_fallback' => '',
+ 'retailer_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ChainingInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/ChainingInfo.php
new file mode 100755
index 0000000..c75c306
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ChainingInfo.php
@@ -0,0 +1,20 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ChainingSuggestion.php b/vendor/mgp25/instagram-php/src/Response/Model/ChainingSuggestion.php
new file mode 100755
index 0000000..19cd7f5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ChainingSuggestion.php
@@ -0,0 +1,587 @@
+ 'ChainingInfo',
+ 'profile_chaining_secondary_label' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Challenge.php b/vendor/mgp25/instagram-php/src/Response/Model/Challenge.php
new file mode 100755
index 0000000..f2cc9d0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Challenge.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'api_path' => 'string',
+ 'hide_webview_header' => 'bool',
+ 'lock' => 'bool',
+ 'logout' => 'bool',
+ 'native_flow' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Channel.php b/vendor/mgp25/instagram-php/src/Response/Model/Channel.php
new file mode 100755
index 0000000..ee1e56f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Channel.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'channel_type' => 'string',
+ 'title' => 'string',
+ 'header' => 'string',
+ 'media_count' => 'int',
+ 'media' => 'Item',
+ 'context' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/CloseFriends.php b/vendor/mgp25/instagram-php/src/Response/Model/CloseFriends.php
new file mode 100755
index 0000000..f14028c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/CloseFriends.php
@@ -0,0 +1,35 @@
+ '',
+ 'users' => 'User[]',
+ 'big_list' => '',
+ 'page_size' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Collection.php b/vendor/mgp25/instagram-php/src/Response/Model/Collection.php
new file mode 100755
index 0000000..c6c2a20
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Collection.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'collection_name' => 'string',
+ 'cover_media' => 'Item',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Comment.php b/vendor/mgp25/instagram-php/src/Response/Model/Comment.php
new file mode 100755
index 0000000..daf4834
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Comment.php
@@ -0,0 +1,196 @@
+ 'string',
+ 'user_id' => 'string',
+ /*
+ * Unix timestamp (UTC) of when the comment was posted.
+ * Yes, this is the UTC timestamp even though it's not named "utc"!
+ */
+ 'created_at' => 'string',
+ /*
+ * WARNING: DO NOT USE THIS VALUE! It is NOT a real UTC timestamp.
+ * Instagram has messed up their values of "created_at" vs "created_at_utc".
+ * In `getComments()`, both have identical values. In `getCommentReplies()`,
+ * both are identical too. But in the `getComments()` "reply previews",
+ * their "created_at_utc" values are completely wrong (always +8 hours into
+ * the future, beyond the real UTC time). So just ignore this bad value!
+ * The real app only reads "created_at" for showing comment timestamps!
+ */
+ 'created_at_utc' => 'string',
+ 'bit_flags' => 'int',
+ 'user' => 'User',
+ 'pk' => 'string',
+ 'media_id' => 'string',
+ 'text' => 'string',
+ 'content_type' => 'string',
+ /*
+ * A number describing what type of comment this is. Should be compared
+ * against the `Comment::PARENT` and `Comment::CHILD` constants. All
+ * replies are of type `CHILD`, and all parents are of type `PARENT`.
+ */
+ 'type' => 'int',
+ 'comment_like_count' => 'int',
+ 'has_liked_comment' => 'bool',
+ 'has_translation' => 'bool',
+ 'did_report_as_spam' => 'bool',
+ 'share_enabled' => 'bool',
+ /*
+ * If this is a child in a thread, this is the ID of its parent thread.
+ */
+ 'parent_comment_id' => 'string',
+ /*
+ * Number of child comments in this comment thread.
+ */
+ 'child_comment_count' => 'int',
+ /*
+ * Previews of some of the child comments. Compare it to the child
+ * comment count. If there are more, you must request the comment thread.
+ */
+ 'preview_child_comments' => 'Comment[]',
+ /*
+ * Previews of users in very long comment threads.
+ */
+ 'other_preview_users' => 'User[]',
+ 'inline_composer_display_condition' => 'string',
+ /*
+ * When "has_more_tail_child_comments" is true, you can use the value
+ * in "next_max_child_cursor" as "max_id" parameter to load up to
+ * "num_tail_child_comments" older child-comments.
+ */
+ 'has_more_tail_child_comments' => 'bool',
+ 'next_max_child_cursor' => 'string',
+ 'num_tail_child_comments' => 'int',
+ /*
+ * When "has_more_head_child_comments" is true, you can use the value
+ * in "next_min_child_cursor" as "min_id" parameter to load up to
+ * "num_head_child_comments" newer child-comments.
+ */
+ 'has_more_head_child_comments' => 'bool',
+ 'next_min_child_cursor' => 'string',
+ 'num_head_child_comments' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/CommentInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/CommentInfo.php
new file mode 100755
index 0000000..d3c5c08
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/CommentInfo.php
@@ -0,0 +1,60 @@
+ 'bool',
+ 'comment_threading_enabled' => 'bool',
+ 'has_more_comments' => 'bool',
+ 'max_num_visible_preview_comments' => 'int',
+ 'preview_comments' => '',
+ 'can_view_more_preview_comments' => 'bool',
+ 'comment_count' => 'int',
+ 'inline_composer_display_condition' => 'string',
+ 'inline_composer_imp_trigger_time' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/CommentTranslations.php b/vendor/mgp25/instagram-php/src/Response/Model/CommentTranslations.php
new file mode 100755
index 0000000..356a1ef
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/CommentTranslations.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'translation' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Composer.php b/vendor/mgp25/instagram-php/src/Response/Model/Composer.php
new file mode 100755
index 0000000..a43f9bf
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Composer.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'aspect_ratio_finished' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ContextualFilters.php b/vendor/mgp25/instagram-php/src/Response/Model/ContextualFilters.php
new file mode 100755
index 0000000..e3ed7dc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ContextualFilters.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'filters' => '',
+ 'clauses' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/CountdownSticker.php b/vendor/mgp25/instagram-php/src/Response/Model/CountdownSticker.php
new file mode 100755
index 0000000..1ad21dd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/CountdownSticker.php
@@ -0,0 +1,90 @@
+ 'string',
+ 'end_ts' => 'string',
+ 'text' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'text_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'start_background_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'end_background_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'digit_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'digit_card_color' => 'string',
+ 'following_enabled' => 'bool',
+ 'is_owner' => 'bool',
+ 'attribution' => '',
+ 'viewer_is_following' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Counts.php b/vendor/mgp25/instagram-php/src/Response/Model/Counts.php
new file mode 100755
index 0000000..bc608e0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Counts.php
@@ -0,0 +1,55 @@
+ '',
+ 'requests' => '',
+ 'photos_of_you' => '',
+ 'usertags' => '',
+ 'comments' => '',
+ 'likes' => '',
+ 'comment_likes' => '',
+ 'campaign_notification' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/CoverMedia.php b/vendor/mgp25/instagram-php/src/Response/Model/CoverMedia.php
new file mode 100755
index 0000000..18a3f57
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/CoverMedia.php
@@ -0,0 +1,63 @@
+ 'string',
+ 'media_id' => 'string',
+ /*
+ * A number describing what type of media this is.
+ */
+ 'media_type' => 'int',
+ 'image_versions2' => 'Image_Versions2',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ 'cropped_image_version' => 'ImageCandidate',
+ 'crop_rect' => 'int[]',
+ 'full_image_version' => 'ImageCandidate',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Creative.php b/vendor/mgp25/instagram-php/src/Response/Model/Creative.php
new file mode 100755
index 0000000..54dfb13
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Creative.php
@@ -0,0 +1,56 @@
+ 'Text',
+ 'content' => 'Text',
+ 'footer' => 'Text',
+ 'social_context' => 'Text',
+ 'content' => 'Text',
+ 'primary_action' => 'Action',
+ 'secondary_action' => 'Action',
+ 'dismiss_action' => '',
+ 'image' => 'Image',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DataGraph.php b/vendor/mgp25/instagram-php/src/Response/Model/DataGraph.php
new file mode 100755
index 0000000..6be10e8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DataGraph.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'data_points' => 'DataPoints[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DataPoints.php b/vendor/mgp25/instagram-php/src/Response/Model/DataPoints.php
new file mode 100755
index 0000000..1f7ea06
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DataPoints.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'value' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectCursor.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectCursor.php
new file mode 100755
index 0000000..5e97d19
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectCursor.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'cursor_thread_v2_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectExpiringSummary.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectExpiringSummary.php
new file mode 100755
index 0000000..842a717
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectExpiringSummary.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'timestamp' => 'string',
+ 'count' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectInbox.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectInbox.php
new file mode 100755
index 0000000..cbbf89e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectInbox.php
@@ -0,0 +1,50 @@
+ 'bool',
+ 'unseen_count' => 'int',
+ 'unseen_count_ts' => 'string', // Is a timestamp.
+ 'blended_inbox_enabled' => 'bool',
+ 'threads' => 'DirectThread[]',
+ 'next_cursor' => 'DirectCursor',
+ 'prev_cursor' => 'DirectCursor',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectLink.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectLink.php
new file mode 100755
index 0000000..e7ab3cf
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectLink.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'link_context' => 'LinkContext',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectMessageMetadata.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectMessageMetadata.php
new file mode 100755
index 0000000..873f463
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectMessageMetadata.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'item_id' => 'string',
+ 'timestamp' => 'string',
+ 'participant_ids' => 'string[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectRankedRecipient.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectRankedRecipient.php
new file mode 100755
index 0000000..0c1fe2c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectRankedRecipient.php
@@ -0,0 +1,25 @@
+ 'DirectThread',
+ 'user' => 'User',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectReaction.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectReaction.php
new file mode 100755
index 0000000..a85d38a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectReaction.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'timestamp' => 'string',
+ 'sender_id' => 'string',
+ 'client_context' => 'string',
+ 'reaction_status' => 'string',
+ 'node_type' => 'string',
+ 'item_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectReactions.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectReactions.php
new file mode 100755
index 0000000..a627772
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectReactions.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'likes' => 'DirectReaction[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectSeenItemPayload.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectSeenItemPayload.php
new file mode 100755
index 0000000..5971dca
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectSeenItemPayload.php
@@ -0,0 +1,25 @@
+ '',
+ 'timestamp' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectSendItemPayload.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectSendItemPayload.php
new file mode 100755
index 0000000..33541f7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectSendItemPayload.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'client_context' => 'string',
+ 'message' => 'string',
+ 'item_id' => 'string',
+ 'timestamp' => 'string',
+ 'thread_id' => 'string',
+ 'canonical' => 'bool',
+ 'participant_ids' => 'string[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectThread.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectThread.php
new file mode 100755
index 0000000..23e27f7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectThread.php
@@ -0,0 +1,220 @@
+ 'string',
+ 'thread_v2_id' => 'string',
+ 'users' => 'User[]',
+ 'left_users' => 'User[]',
+ 'items' => 'DirectThreadItem[]',
+ 'last_activity_at' => 'string',
+ 'muted' => 'bool',
+ 'is_pin' => 'bool',
+ 'named' => 'bool',
+ 'canonical' => 'bool',
+ 'pending' => 'bool',
+ 'valued_request' => 'bool',
+ 'thread_type' => 'string',
+ 'viewer_id' => 'string',
+ 'thread_title' => 'string',
+ 'pending_score' => 'string',
+ 'vc_muted' => 'bool',
+ 'is_group' => 'bool',
+ 'reshare_send_count' => 'int',
+ 'reshare_receive_count' => 'int',
+ 'expiring_media_send_count' => 'int',
+ 'expiring_media_receive_count' => 'int',
+ 'inviter' => 'User',
+ 'has_older' => 'bool',
+ 'has_newer' => 'bool',
+ 'last_seen_at' => 'UnpredictableKeys\DirectThreadLastSeenAtUnpredictableContainer',
+ 'newest_cursor' => 'string',
+ 'oldest_cursor' => 'string',
+ 'is_spam' => 'bool',
+ 'last_permanent_item' => 'PermanentItem',
+ 'unseen_count' => '',
+ 'action_badge' => 'ActionBadge',
+ 'last_activity_at_secs' => '',
+ 'admin_user_ids' => 'string[]',
+ 'approval_required_for_new_members' => 'bool',
+ 'archived' => 'bool',
+ 'business_thread_folder' => 'int',
+ 'folder' => 'int',
+ 'input_mode' => 'int',
+ 'mentions_muted' => 'bool',
+ 'read_state' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItem.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItem.php
new file mode 100755
index 0000000..e840a45
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItem.php
@@ -0,0 +1,185 @@
+ 'string',
+ 'item_type' => '',
+ 'text' => 'string',
+ 'media_share' => 'Item',
+ 'preview_medias' => 'Item[]',
+ 'media' => 'DirectThreadItemMedia',
+ 'user_id' => 'string',
+ 'timestamp' => '',
+ 'client_context' => 'string',
+ 'hide_in_thread' => '',
+ 'action_log' => 'ActionLog',
+ 'link' => 'DirectLink',
+ 'reactions' => 'DirectReactions',
+ 'raven_media' => 'Item',
+ 'seen_user_ids' => 'string[]',
+ 'expiring_media_action_summary' => 'DirectExpiringSummary',
+ 'reel_share' => 'ReelShare',
+ 'placeholder' => 'Placeholder',
+ 'location' => 'Location',
+ 'like' => '',
+ 'live_video_share' => 'LiveVideoShare',
+ 'live_viewer_invite' => 'LiveViewerInvite',
+ 'profile' => 'User',
+ 'story_share' => 'StoryShare',
+ 'direct_media_share' => 'MediaShare',
+ 'video_call_event' => 'VideoCallEvent',
+ 'product_share' => 'ProductShare',
+ 'animated_media' => 'AnimatedMedia',
+ 'felix_share' => 'FelixShare',
+ 'voice_media' => 'VoiceMedia',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItemMedia.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItemMedia.php
new file mode 100755
index 0000000..8239650
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadItemMedia.php
@@ -0,0 +1,54 @@
+ 'int',
+ 'image_versions2' => 'Image_Versions2',
+ 'video_versions' => 'VideoVersions[]',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ 'audio' => 'AudioContext',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadLastSeenAt.php b/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadLastSeenAt.php
new file mode 100755
index 0000000..a91cc29
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DirectThreadLastSeenAt.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'timestamp' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/DismissCard.php b/vendor/mgp25/instagram-php/src/Response/Model/DismissCard.php
new file mode 100755
index 0000000..b4c427d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/DismissCard.php
@@ -0,0 +1,50 @@
+ '',
+ 'image_url' => 'string',
+ 'title' => '',
+ 'message' => '',
+ 'button_text' => '',
+ 'camera_target' => '',
+ 'face_filter_id' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Edges.php b/vendor/mgp25/instagram-php/src/Response/Model/Edges.php
new file mode 100755
index 0000000..3ff10b2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Edges.php
@@ -0,0 +1,30 @@
+ 'int',
+ 'time_range' => 'TimeRange',
+ 'node' => 'QPNode',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Effect.php b/vendor/mgp25/instagram-php/src/Response/Model/Effect.php
new file mode 100755
index 0000000..0c83b2b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Effect.php
@@ -0,0 +1,50 @@
+ '',
+ 'id' => 'string',
+ 'effect_id' => 'string',
+ 'effect_file_id' => 'string',
+ 'asset_url' => 'string',
+ 'thumbnail_url' => 'string',
+ 'instructions' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/EligiblePromotions.php b/vendor/mgp25/instagram-php/src/Response/Model/EligiblePromotions.php
new file mode 100755
index 0000000..7f4f1ab
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/EligiblePromotions.php
@@ -0,0 +1,20 @@
+ 'Edges[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/EndOfFeedDemarcator.php b/vendor/mgp25/instagram-php/src/Response/Model/EndOfFeedDemarcator.php
new file mode 100755
index 0000000..2cd11fa
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/EndOfFeedDemarcator.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'title' => 'string',
+ 'subtitle' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Experiment.php b/vendor/mgp25/instagram-php/src/Response/Model/Experiment.php
new file mode 100755
index 0000000..f3a1b6f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Experiment.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'group' => 'string',
+ 'additional_params' => '', // TODO: Only seen as [] empty array so far.
+ 'params' => 'Param[]',
+ 'logging_id' => 'string',
+ 'expired' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Explore.php b/vendor/mgp25/instagram-php/src/Response/Model/Explore.php
new file mode 100755
index 0000000..4169d29
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Explore.php
@@ -0,0 +1,30 @@
+ '',
+ 'actor_id' => 'string',
+ 'source_token' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ExploreItem.php b/vendor/mgp25/instagram-php/src/Response/Model/ExploreItem.php
new file mode 100755
index 0000000..4592a95
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ExploreItem.php
@@ -0,0 +1,35 @@
+ 'Item',
+ 'stories' => 'Stories',
+ 'channel' => 'Channel',
+ 'explore_item_info' => 'ExploreItemInfo',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ExploreItemInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/ExploreItemInfo.php
new file mode 100755
index 0000000..626a3f9
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ExploreItemInfo.php
@@ -0,0 +1,40 @@
+ 'int',
+ 'total_num_columns' => 'int',
+ 'aspect_ratio' => 'int',
+ 'autoplay' => 'bool',
+ 'destination_view' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FaceModels.php b/vendor/mgp25/instagram-php/src/Response/Model/FaceModels.php
new file mode 100755
index 0000000..8c8fc45
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FaceModels.php
@@ -0,0 +1,30 @@
+ '',
+ 'face_detect_model' => '',
+ 'pdm_multires' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FacebookUser.php b/vendor/mgp25/instagram-php/src/Response/Model/FacebookUser.php
new file mode 100755
index 0000000..5c1c470
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FacebookUser.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'name' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FeedAysf.php b/vendor/mgp25/instagram-php/src/Response/Model/FeedAysf.php
new file mode 100755
index 0000000..74d124c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FeedAysf.php
@@ -0,0 +1,75 @@
+ '',
+ 'uuid' => 'string',
+ 'view_all_text' => '',
+ 'feed_position' => '',
+ 'landing_site_title' => '',
+ 'is_dismissable' => '',
+ 'suggestions' => 'Suggestion[]',
+ 'should_refill' => '',
+ 'display_new_unit' => '',
+ 'fetch_user_details' => '',
+ 'title' => '',
+ 'activator' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FeedItem.php b/vendor/mgp25/instagram-php/src/Response/Model/FeedItem.php
new file mode 100755
index 0000000..1d94fcd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FeedItem.php
@@ -0,0 +1,45 @@
+ 'Item',
+ 'stories_netego' => 'StoriesNetego',
+ 'ad4ad' => 'Ad4ad',
+ 'suggested_users' => 'SuggestedUsers',
+ 'end_of_feed_demarcator' => '',
+ 'ad_link_type' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FelixShare.php b/vendor/mgp25/instagram-php/src/Response/Model/FelixShare.php
new file mode 100755
index 0000000..3832bb5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FelixShare.php
@@ -0,0 +1,25 @@
+ 'Item[]',
+ 'text' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FillItems.php b/vendor/mgp25/instagram-php/src/Response/Model/FillItems.php
new file mode 100755
index 0000000..cd3de75
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FillItems.php
@@ -0,0 +1,20 @@
+ 'Item',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FollowersUnit.php b/vendor/mgp25/instagram-php/src/Response/Model/FollowersUnit.php
new file mode 100755
index 0000000..5f62fd6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FollowersUnit.php
@@ -0,0 +1,60 @@
+ 'string',
+ 'followers_delta_from_last_week' => 'int',
+ 'gender_graph' => 'DataGraph',
+ 'all_followers_age_graph' => 'DataGraph',
+ 'men_followers_age_graph' => 'DataGraph',
+ 'women_followers_age_graph' => 'DataGraph',
+ 'followers_top_cities_graph' => 'DataGraph',
+ 'followers_top_countries_graph' => 'DataGraph',
+ 'week_daily_followers_graph' => 'DataGraph',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FormerUsername.php b/vendor/mgp25/instagram-php/src/Response/Model/FormerUsername.php
new file mode 100755
index 0000000..a6a2a8b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FormerUsername.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'change_timestamp' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FormerUsernameInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/FormerUsernameInfo.php
new file mode 100755
index 0000000..9bd2246
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FormerUsernameInfo.php
@@ -0,0 +1,20 @@
+ 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FriendshipStatus.php b/vendor/mgp25/instagram-php/src/Response/Model/FriendshipStatus.php
new file mode 100755
index 0000000..7e5b7e0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FriendshipStatus.php
@@ -0,0 +1,70 @@
+ 'bool',
+ 'followed_by' => 'bool',
+ 'incoming_request' => 'bool',
+ 'outgoing_request' => 'bool',
+ 'is_private' => 'bool',
+ 'is_blocking_reel' => 'bool',
+ 'is_muting_reel' => 'bool',
+ 'is_restricted' => 'bool',
+ 'blocking' => 'bool',
+ 'muting' => 'bool',
+ 'is_bestie' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/FullItem.php b/vendor/mgp25/instagram-php/src/Response/Model/FullItem.php
new file mode 100755
index 0000000..6a35edb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/FullItem.php
@@ -0,0 +1,20 @@
+ 'Channel',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Gating.php b/vendor/mgp25/instagram-php/src/Response/Model/Gating.php
new file mode 100755
index 0000000..fa76988
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Gating.php
@@ -0,0 +1,35 @@
+ '',
+ 'description' => '',
+ 'buttons' => '',
+ 'title' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/GenericMegaphone.php b/vendor/mgp25/instagram-php/src/Response/Model/GenericMegaphone.php
new file mode 100755
index 0000000..b749bdb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/GenericMegaphone.php
@@ -0,0 +1,85 @@
+ '',
+ 'title' => '',
+ 'message' => '',
+ 'dismissible' => '',
+ 'icon' => '',
+ 'buttons' => 'Button[]',
+ 'megaphone_version' => '',
+ 'button_layout' => '',
+ 'action_info' => '',
+ 'button_location' => '',
+ 'background_color' => '',
+ 'title_color' => '',
+ 'message_color' => '',
+ 'uuid' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/GraphData.php b/vendor/mgp25/instagram-php/src/Response/Model/GraphData.php
new file mode 100755
index 0000000..7be4441
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/GraphData.php
@@ -0,0 +1,50 @@
+ 'GraphNode',
+ '__typename' => 'string',
+ 'name' => 'string',
+ 'user' => 'ShadowInstagramUser',
+ 'error' => '',
+ 'catalog_items' => 'CatalogData',
+ 'me' => 'MeGraphData',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/GraphNode.php b/vendor/mgp25/instagram-php/src/Response/Model/GraphNode.php
new file mode 100755
index 0000000..f9ede7a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/GraphNode.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'name' => 'string',
+ 'catalog_items' => 'CatalogData',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Groups.php b/vendor/mgp25/instagram-php/src/Response/Model/Groups.php
new file mode 100755
index 0000000..34bbea3
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Groups.php
@@ -0,0 +1,25 @@
+ '',
+ 'items' => 'Item[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Hashtag.php b/vendor/mgp25/instagram-php/src/Response/Model/Hashtag.php
new file mode 100755
index 0000000..d91d751
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Hashtag.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'name' => 'string',
+ 'media_count' => 'int',
+ 'profile_pic_url' => 'string',
+ 'follow_status' => 'int',
+ 'following' => 'int',
+ 'allow_following' => 'int',
+ 'allow_muting_story' => 'bool',
+ 'related_tags' => '',
+ 'debug_info' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Headline.php b/vendor/mgp25/instagram-php/src/Response/Model/Headline.php
new file mode 100755
index 0000000..d8ddc95
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Headline.php
@@ -0,0 +1,70 @@
+ 'string',
+ 'user' => 'User',
+ 'user_id' => 'string',
+ 'pk' => 'string',
+ 'text' => 'string',
+ 'type' => 'int',
+ 'created_at' => 'string',
+ 'created_at_utc' => 'string',
+ 'media_id' => 'string',
+ 'bit_flags' => 'int',
+ 'status' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/HiddenEntities.php b/vendor/mgp25/instagram-php/src/Response/Model/HiddenEntities.php
new file mode 100755
index 0000000..7e7996d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/HiddenEntities.php
@@ -0,0 +1,34 @@
+ '',
+ 'hashtag' => '',
+ 'place' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/HideReason.php b/vendor/mgp25/instagram-php/src/Response/Model/HideReason.php
new file mode 100755
index 0000000..3f84590
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/HideReason.php
@@ -0,0 +1,31 @@
+ 'string',
+ /*
+ * A computer string such as "NOT_RELEVANT" or "KEEP_SEEING_THIS".
+ */
+ 'reason' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/IOSLinks.php b/vendor/mgp25/instagram-php/src/Response/Model/IOSLinks.php
new file mode 100755
index 0000000..c80eb40
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/IOSLinks.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'canvasDocId' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/IabAutofillOptoutInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/IabAutofillOptoutInfo.php
new file mode 100755
index 0000000..70bcffc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/IabAutofillOptoutInfo.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'is_iab_autofill_optout' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Image.php b/vendor/mgp25/instagram-php/src/Response/Model/Image.php
new file mode 100755
index 0000000..8219872
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Image.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'width' => 'int',
+ 'height' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ImageCandidate.php b/vendor/mgp25/instagram-php/src/Response/Model/ImageCandidate.php
new file mode 100755
index 0000000..d3c2645
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ImageCandidate.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'width' => 'int',
+ 'height' => 'int',
+ 'estimated_scans_sizes' => 'int[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Image_Versions2.php b/vendor/mgp25/instagram-php/src/Response/Model/Image_Versions2.php
new file mode 100755
index 0000000..a4e8e0f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Image_Versions2.php
@@ -0,0 +1,25 @@
+ 'ImageCandidate[]',
+ 'trace_token' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/In.php b/vendor/mgp25/instagram-php/src/Response/Model/In.php
new file mode 100755
index 0000000..61a0a80
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/In.php
@@ -0,0 +1,45 @@
+ 'float[]',
+ 'user' => 'User',
+ 'time_in_video' => '',
+ 'start_time_in_video_in_sec' => '',
+ 'duration_in_video_in_sec' => '',
+ 'product' => 'Product',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Injected.php b/vendor/mgp25/instagram-php/src/Response/Model/Injected.php
new file mode 100755
index 0000000..00e63eb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Injected.php
@@ -0,0 +1,120 @@
+ 'string',
+ 'show_icon' => 'bool',
+ 'hide_label' => 'string',
+ 'invalidation' => '', // Only encountered as NULL.
+ 'is_demo' => 'bool',
+ 'view_tags' => '', // Only seen as [].
+ 'is_holdout' => 'bool',
+ 'is_leadgen_native_eligible' => 'bool',
+ 'tracking_token' => 'string',
+ 'show_ad_choices' => 'bool',
+ 'ad_title' => 'string',
+ 'about_ad_params' => 'string',
+ 'direct_share' => 'bool',
+ 'ad_id' => 'string',
+ 'display_viewability_eligible' => 'bool',
+ 'fb_page_url' => 'string',
+ 'hide_reasons_v2' => 'HideReason[]',
+ 'hide_flow_type' => 'int',
+ 'cookies' => 'string[]',
+ 'lead_gen_form_id' => 'string',
+ 'ads_debug_info' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/InlineFollow.php b/vendor/mgp25/instagram-php/src/Response/Model/InlineFollow.php
new file mode 100755
index 0000000..1513f10
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/InlineFollow.php
@@ -0,0 +1,30 @@
+ 'User',
+ 'following' => 'bool',
+ 'outgoing_request' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Insights.php b/vendor/mgp25/instagram-php/src/Response/Model/Insights.php
new file mode 100755
index 0000000..376ee20
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Insights.php
@@ -0,0 +1,20 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Item.php b/vendor/mgp25/instagram-php/src/Response/Model/Item.php
new file mode 100755
index 0000000..8890b89
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Item.php
@@ -0,0 +1,959 @@
+ 'string',
+ 'pk' => 'string',
+ 'id' => 'string',
+ 'device_timestamp' => 'string',
+ /*
+ * A number describing what type of media this is. Should be compared
+ * against the `Item::PHOTO`, `Item::VIDEO` and `Item::CAROUSEL` constants!
+ */
+ 'media_type' => 'int',
+ 'dynamic_item_id' => 'string',
+ 'code' => 'string',
+ 'client_cache_key' => 'string',
+ 'filter_type' => 'int',
+ 'product_type' => 'string',
+ 'nearly_complete_copyright_match' => 'bool',
+ 'media_cropping_info' => 'MediaCroppingInfo',
+ 'image_versions2' => 'Image_Versions2',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ 'caption_position' => 'float',
+ 'is_reel_media' => 'bool',
+ 'video_versions' => 'VideoVersions[]',
+ 'has_audio' => 'bool',
+ 'video_duration' => 'float',
+ 'user' => 'User',
+ 'can_see_insights_as_brand' => 'bool',
+ 'caption' => 'Caption',
+ 'headline' => 'Headline',
+ 'title' => 'string',
+ 'caption_is_edited' => 'bool',
+ 'photo_of_you' => 'bool',
+ 'fb_user_tags' => 'Usertag',
+ 'can_viewer_save' => 'bool',
+ 'has_viewer_saved' => 'bool',
+ 'organic_tracking_token' => 'string',
+ 'follow_hashtag_info' => 'Hashtag',
+ 'expiring_at' => 'string',
+ 'audience' => 'string',
+ 'is_dash_eligible' => 'int',
+ 'video_dash_manifest' => 'string',
+ 'number_of_qualities' => 'int',
+ 'video_codec' => 'string',
+ 'thumbnails' => 'Thumbnail',
+ 'can_reshare' => 'bool',
+ 'can_reply' => 'bool',
+ 'is_pride_media' => 'bool',
+ 'can_viewer_reshare' => 'bool',
+ 'visibility' => '',
+ 'attribution' => 'Attribution',
+ /*
+ * This is actually a float in the reply, but is always `.0`, so we cast
+ * it to an int instead to make the number easier to manage.
+ */
+ 'view_count' => 'int',
+ 'viewer_count' => 'int',
+ 'comment_count' => 'int',
+ 'can_view_more_preview_comments' => 'bool',
+ 'has_more_comments' => 'bool',
+ 'max_num_visible_preview_comments' => 'int',
+ /*
+ * Preview of comments via feed replies.
+ *
+ * If "has_more_comments" is FALSE, then this has ALL of the comments.
+ * Otherwise, you'll need to get all comments by querying the media.
+ */
+ 'preview_comments' => 'Comment[]',
+ /*
+ * Comments for the item.
+ *
+ * TODO: As of mid-2017, this field seems to no longer be used for
+ * timeline feed items? They now use "preview_comments" instead. But we
+ * won't delete it, since some other feed MAY use this property for ITS
+ * Item object.
+ */
+ 'comments' => 'Comment[]',
+ 'comments_disabled' => '',
+ 'reel_mentions' => 'ReelMention[]',
+ 'story_cta' => 'StoryCta[]',
+ 'next_max_id' => 'string',
+ 'carousel_media' => 'CarouselMedia[]',
+ 'carousel_media_type' => '',
+ 'carousel_media_count' => 'int',
+ 'likers' => 'User[]',
+ 'facepile_top_likers' => 'User[]',
+ 'like_count' => 'int',
+ 'preview' => 'string',
+ 'has_liked' => 'bool',
+ 'explore_context' => 'string',
+ 'explore_source_token' => 'string',
+ 'explore_hide_comments' => 'bool',
+ 'explore' => 'Explore',
+ 'impression_token' => 'string',
+ 'usertags' => 'Usertag',
+ 'media' => 'Media',
+ 'stories' => 'Stories',
+ 'top_likers' => 'string[]',
+ 'direct_reply_to_author_enabled' => 'bool',
+ 'suggested_users' => 'SuggestedUsers',
+ 'is_new_suggestion' => 'bool',
+ 'comment_likes_enabled' => 'bool',
+ 'location' => 'Location',
+ 'lat' => 'float',
+ 'lng' => 'float',
+ 'channel' => 'Channel',
+ 'gating' => 'Gating',
+ 'injected' => 'Injected',
+ 'placeholder' => 'Placeholder',
+ 'algorithm' => 'string',
+ 'connection_id' => 'string',
+ 'social_context' => 'string',
+ 'icon' => '',
+ 'media_ids' => 'string[]',
+ 'media_id' => 'string',
+ 'thumbnail_urls' => '',
+ 'large_urls' => '',
+ 'media_infos' => '',
+ 'value' => 'float',
+ 'followed_by' => 'bool',
+ 'collapse_comments' => 'bool',
+ 'link' => 'string',
+ 'link_text' => 'string',
+ 'link_hint_text' => 'string',
+ 'iTunesItem' => '',
+ 'ad_header_style' => 'int',
+ 'ad_metadata' => 'AdMetadata[]',
+ 'ad_action' => 'string',
+ 'ad_link_type' => 'int',
+ 'dr_ad_type' => 'int',
+ 'android_links' => 'AndroidLinks[]',
+ 'ios_links' => 'IOSLinks[]',
+ 'iab_autofill_optout_info' => 'IabAutofillOptoutInfo',
+ 'force_overlay' => 'bool',
+ 'hide_nux_text' => 'bool',
+ 'overlay_text' => 'string',
+ 'overlay_title' => 'string',
+ 'overlay_subtitle' => 'string',
+ 'fb_page_url' => 'string',
+ 'playback_duration_secs' => '',
+ 'url_expire_at_secs' => '',
+ 'is_sidecar_child' => '',
+ 'comment_threading_enabled' => 'bool',
+ 'cover_media' => 'CoverMedia',
+ 'saved_collection_ids' => 'string[]',
+ 'boosted_status' => '',
+ 'boost_unavailable_reason' => '',
+ 'viewers' => 'User[]',
+ 'viewer_cursor' => '',
+ 'total_viewer_count' => 'int',
+ 'multi_author_reel_names' => '',
+ 'screenshotter_user_ids' => '',
+ 'reel_share' => 'ReelShare',
+ 'organic_post_id' => 'string',
+ 'sponsor_tags' => 'User[]',
+ 'story_poll_voter_infos' => '',
+ 'imported_taken_at' => '',
+ 'lead_gen_form_id' => 'string',
+ 'ad_id' => 'string',
+ 'actor_fbid' => 'string',
+ 'is_ad4ad' => '',
+ 'commenting_disabled_for_viewer' => '',
+ 'is_seen' => '',
+ 'story_events' => '',
+ 'story_hashtags' => 'StoryHashtag[]',
+ 'story_polls' => '',
+ 'story_feed_media' => '',
+ 'story_sound_on' => '',
+ 'creative_config' => '',
+ 'story_app_attribution' => 'StoryAppAttribution',
+ 'story_locations' => 'StoryLocation[]',
+ 'story_sliders' => '',
+ 'story_friend_lists' => '',
+ 'story_product_items' => '',
+ 'story_questions' => 'StoryQuestions[]',
+ 'story_question_responder_infos' => 'StoryQuestionResponderInfos[]',
+ 'story_countdowns' => 'StoryCountdowns[]',
+ 'story_music_stickers' => '',
+ 'supports_reel_reactions' => 'bool',
+ 'show_one_tap_fb_share_tooltip' => 'bool',
+ 'has_shared_to_fb' => 'bool',
+ 'main_feed_carousel_starting_media_id' => 'string',
+ 'main_feed_carousel_has_unseen_cover_media' => 'bool',
+ 'inventory_source' => 'string',
+ 'is_eof' => 'bool',
+ 'top_followers' => 'string[]',
+ 'top_followers_count' => 'int',
+ 'follower_count' => 'int',
+ 'post_count' => 'int',
+ 'video_subtitles_uri' => 'string',
+ 'story_is_saved_to_archive' => 'bool',
+ 'timezone_offset' => 'int',
+ 'xpost_deny_reason' => 'string',
+ 'product_tags' => 'ProductTags',
+ 'inline_composer_display_condition' => 'string',
+ 'inline_composer_imp_trigger_time' => 'int',
+ 'highlight_reel_ids' => 'string[]',
+ 'total_screenshot_count' => 'int',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'dominant_color' => 'string',
+ 'audio' => 'AudioContext',
+ 'story_quizs' => 'StoryQuizs[]',
+ 'story_quiz_participant_infos' => 'StoryQuizParticipantInfo[]',
+ ];
+
+ /**
+ * Get the web URL for this media item.
+ *
+ * @return string
+ */
+ public function getItemUrl()
+ {
+ return sprintf('https://www.instagram.com/p/%s/', $this->_getProperty('code'));
+ }
+
+ /**
+ * Checks whether this media item is an advertisement.
+ *
+ * @return bool
+ */
+ public function isAd()
+ {
+ return $this->_getProperty('dr_ad_type') !== null;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/LayoutContent.php b/vendor/mgp25/instagram-php/src/Response/Model/LayoutContent.php
new file mode 100755
index 0000000..f9946c5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/LayoutContent.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'related' => 'Tag[]',
+ 'medias' => 'SectionMedia[]',
+ 'feed_type' => 'string',
+ 'fill_items' => 'FillItems[]',
+ 'explore_item_info' => 'ExploreItemInfo',
+ 'tabs_info' => 'TabsInfo',
+ 'full_item' => 'FullItem',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Link.php b/vendor/mgp25/instagram-php/src/Response/Model/Link.php
new file mode 100755
index 0000000..3f7ad4c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Link.php
@@ -0,0 +1,45 @@
+ 'int',
+ 'end' => 'int',
+ 'id' => 'string',
+ 'type' => 'string',
+ 'text' => 'string',
+ 'link_context' => 'LinkContext',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/LinkContext.php b/vendor/mgp25/instagram-php/src/Response/Model/LinkContext.php
new file mode 100755
index 0000000..5b87972
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/LinkContext.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'link_title' => 'string',
+ 'link_summary' => 'string',
+ 'link_image_url' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/LiveComment.php b/vendor/mgp25/instagram-php/src/Response/Model/LiveComment.php
new file mode 100755
index 0000000..9d2350f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/LiveComment.php
@@ -0,0 +1,30 @@
+ 'Comment',
+ 'offset' => '',
+ 'event' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/LiveVideoShare.php b/vendor/mgp25/instagram-php/src/Response/Model/LiveVideoShare.php
new file mode 100755
index 0000000..2812b15
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/LiveVideoShare.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'broadcast' => 'Broadcast',
+ 'video_offset' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/LiveViewerInvite.php b/vendor/mgp25/instagram-php/src/Response/Model/LiveViewerInvite.php
new file mode 100755
index 0000000..97d8ca4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/LiveViewerInvite.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'broadcast' => 'Broadcast',
+ 'title' => 'string',
+ 'message' => 'string',
+ 'is_linked' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Location.php b/vendor/mgp25/instagram-php/src/Response/Model/Location.php
new file mode 100755
index 0000000..ea2ae08
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Location.php
@@ -0,0 +1,157 @@
+ 'string',
+ 'external_id_source' => 'string',
+ 'external_source' => 'string',
+ 'address' => 'string',
+ 'lat' => 'float',
+ 'lng' => 'float',
+ 'external_id' => 'string',
+ 'facebook_places_id' => 'string',
+ 'city' => 'string',
+ 'pk' => 'string',
+ 'short_name' => 'string',
+ 'facebook_events_id' => 'string',
+ 'start_time' => '',
+ 'end_time' => '',
+ 'location_dict' => 'Location',
+ 'type' => '',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_username' => 'string',
+ 'time_granularity' => '',
+ 'timezone' => '',
+ /*
+ * Country number such as int(398), but it has no relation to actual
+ * country codes, so the number is useless...
+ */
+ 'country' => 'int',
+ /*
+ * Regular unix timestamp of when the location was created.
+ */
+ 'created_at' => 'string',
+ /*
+ * Some kind of internal number to signify what type of event a special
+ * location (such as a festival) is. We've only seen this with int(0).
+ */
+ 'event_category' => 'int',
+ /*
+ * 64-bit integer with the facebook places ID for the location.
+ */
+ 'place_fbid' => 'string',
+ /*
+ * Human-readable name of the facebook place for the location.
+ */
+ 'place_name' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/LocationItem.php b/vendor/mgp25/instagram-php/src/Response/Model/LocationItem.php
new file mode 100755
index 0000000..38feb34
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/LocationItem.php
@@ -0,0 +1,35 @@
+ '',
+ 'subtitle' => '',
+ 'location' => 'Location',
+ 'title' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/MeGraphData.php b/vendor/mgp25/instagram-php/src/Response/Model/MeGraphData.php
new file mode 100755
index 0000000..1dac9dd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/MeGraphData.php
@@ -0,0 +1,25 @@
+ 'CatalogData',
+ 'id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Media.php b/vendor/mgp25/instagram-php/src/Response/Model/Media.php
new file mode 100755
index 0000000..08797c4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Media.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'id' => 'string',
+ 'user' => 'User',
+ 'expiring_at' => '',
+ 'comment_threading_enabled' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/MediaCroppingInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/MediaCroppingInfo.php
new file mode 100755
index 0000000..ab11ef9
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/MediaCroppingInfo.php
@@ -0,0 +1,20 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/MediaData.php b/vendor/mgp25/instagram-php/src/Response/Model/MediaData.php
new file mode 100755
index 0000000..902c9da
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/MediaData.php
@@ -0,0 +1,43 @@
+ 'Image_Versions2',
+ 'original_width' => 'int',
+ 'original_height' => 'int',
+ /*
+ * A number describing what type of media this is.
+ */
+ 'media_type' => 'int',
+ 'video_versions' => 'VideoVersions[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/MediaInsights.php b/vendor/mgp25/instagram-php/src/Response/Model/MediaInsights.php
new file mode 100755
index 0000000..ac0bf8f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/MediaInsights.php
@@ -0,0 +1,50 @@
+ 'string[]',
+ 'impression_count' => 'int',
+ 'engagement_count' => 'int',
+ 'avg_engagement_count' => 'int',
+ 'comment_count' => 'int',
+ 'save_count' => 'int',
+ 'like_count' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/MediaShare.php b/vendor/mgp25/instagram-php/src/Response/Model/MediaShare.php
new file mode 100755
index 0000000..8201084
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/MediaShare.php
@@ -0,0 +1,25 @@
+ 'Item',
+ 'text' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Megaphone.php b/vendor/mgp25/instagram-php/src/Response/Model/Megaphone.php
new file mode 100755
index 0000000..7068983
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Megaphone.php
@@ -0,0 +1,20 @@
+ 'GenericMegaphone',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Nametag.php b/vendor/mgp25/instagram-php/src/Response/Model/Nametag.php
new file mode 100755
index 0000000..1e20417
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Nametag.php
@@ -0,0 +1,40 @@
+ 'int',
+ 'gradient' => 'int',
+ 'emoji' => 'string',
+ 'emoji_color' => 'string',
+ 'selfie_sticker' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Owner.php b/vendor/mgp25/instagram-php/src/Response/Model/Owner.php
new file mode 100755
index 0000000..51b434f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Owner.php
@@ -0,0 +1,60 @@
+ '',
+ 'pk' => 'string',
+ 'name' => 'string',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_username' => 'string',
+ 'short_name' => 'string',
+ 'lat' => 'float',
+ 'lng' => 'float',
+ 'location_dict' => 'Location',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/PageInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/PageInfo.php
new file mode 100755
index 0000000..2206725
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/PageInfo.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'has_next_page' => 'bool',
+ 'has_previous_page' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Param.php b/vendor/mgp25/instagram-php/src/Response/Model/Param.php
new file mode 100755
index 0000000..9e6ad05
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Param.php
@@ -0,0 +1,25 @@
+ '',
+ 'value' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Participants.php b/vendor/mgp25/instagram-php/src/Response/Model/Participants.php
new file mode 100755
index 0000000..4474261
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Participants.php
@@ -0,0 +1,30 @@
+ 'User',
+ 'answer' => 'int',
+ 'ts' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/PermanentItem.php b/vendor/mgp25/instagram-php/src/Response/Model/PermanentItem.php
new file mode 100755
index 0000000..dea7ca1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/PermanentItem.php
@@ -0,0 +1,90 @@
+ 'string',
+ 'user_id' => 'string',
+ 'timestamp' => 'string',
+ 'item_type' => 'string',
+ 'profile' => 'User',
+ 'text' => 'string',
+ 'location' => 'Location',
+ 'like' => '',
+ 'media' => 'MediaData',
+ 'link' => 'Link',
+ 'media_share' => 'Item',
+ 'reel_share' => 'ReelShare',
+ 'client_context' => 'string',
+ 'live_video_share' => 'LiveVideoShare',
+ 'live_viewer_invite' => 'LiveViewerInvite',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/PhoneVerificationSettings.php b/vendor/mgp25/instagram-php/src/Response/Model/PhoneVerificationSettings.php
new file mode 100755
index 0000000..211b0b1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/PhoneVerificationSettings.php
@@ -0,0 +1,35 @@
+ 'int',
+ 'max_sms_count' => 'int',
+ 'robocall_count_down_time_sec' => 'int',
+ 'robocall_after_max_sms' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Placeholder.php b/vendor/mgp25/instagram-php/src/Response/Model/Placeholder.php
new file mode 100755
index 0000000..515d0c5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Placeholder.php
@@ -0,0 +1,30 @@
+ 'bool',
+ 'title' => 'string',
+ 'message' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/PostLive.php b/vendor/mgp25/instagram-php/src/Response/Model/PostLive.php
new file mode 100755
index 0000000..3266000
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/PostLive.php
@@ -0,0 +1,20 @@
+ 'PostLiveItem[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/PostLiveItem.php b/vendor/mgp25/instagram-php/src/Response/Model/PostLiveItem.php
new file mode 100755
index 0000000..afa9b5f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/PostLiveItem.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'user' => 'User',
+ 'broadcasts' => 'Broadcast[]',
+ 'peak_viewer_count' => 'int',
+ 'last_seen_broadcast_ts' => '',
+ 'can_reply' => '',
+ 'ranked_position' => '',
+ 'seen_ranked_position' => '',
+ 'muted' => '',
+ 'can_reshare' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Prefill.php b/vendor/mgp25/instagram-php/src/Response/Model/Prefill.php
new file mode 100755
index 0000000..8384250
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Prefill.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'candidates' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/PrimaryCountryInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/PrimaryCountryInfo.php
new file mode 100755
index 0000000..9ef45f8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/PrimaryCountryInfo.php
@@ -0,0 +1,30 @@
+ 'bool',
+ 'has_country' => 'bool',
+ 'country_name' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Product.php b/vendor/mgp25/instagram-php/src/Response/Model/Product.php
new file mode 100755
index 0000000..67d5333
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Product.php
@@ -0,0 +1,84 @@
+ 'string',
+ 'price' => 'string',
+ 'current_price' => 'string',
+ 'full_price' => 'string',
+ 'product_id' => 'string',
+ 'has_viewer_saved' => 'bool',
+ 'description' => 'string',
+ 'main_image' => 'ProductImage',
+ 'thumbnail_image' => 'ProductImage',
+ 'product_images' => 'ProductImage[]',
+ 'external_url' => 'string',
+ 'checkout_style' => 'string',
+ 'review_status' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ProductImage.php b/vendor/mgp25/instagram-php/src/Response/Model/ProductImage.php
new file mode 100755
index 0000000..1fea73f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ProductImage.php
@@ -0,0 +1,20 @@
+ 'Image_Versions2',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ProductShare.php b/vendor/mgp25/instagram-php/src/Response/Model/ProductShare.php
new file mode 100755
index 0000000..df31acf
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ProductShare.php
@@ -0,0 +1,30 @@
+ 'Item',
+ 'text' => 'string',
+ 'product' => 'Product',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ProductTags.php b/vendor/mgp25/instagram-php/src/Response/Model/ProductTags.php
new file mode 100755
index 0000000..42cab36
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ProductTags.php
@@ -0,0 +1,20 @@
+ 'In[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/PromotionsUnit.php b/vendor/mgp25/instagram-php/src/Response/Model/PromotionsUnit.php
new file mode 100755
index 0000000..822a013
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/PromotionsUnit.php
@@ -0,0 +1,20 @@
+ 'SummaryPromotions',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/PushSettings.php b/vendor/mgp25/instagram-php/src/Response/Model/PushSettings.php
new file mode 100755
index 0000000..f5a9ac1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/PushSettings.php
@@ -0,0 +1,45 @@
+ '',
+ 'eligible' => '',
+ 'title' => '',
+ 'example' => '',
+ 'options' => '',
+ 'checked' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/QPData.php b/vendor/mgp25/instagram-php/src/Response/Model/QPData.php
new file mode 100755
index 0000000..5034e76
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/QPData.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'data' => 'QPViewerData',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/QPExtraInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/QPExtraInfo.php
new file mode 100755
index 0000000..f2e2cc7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/QPExtraInfo.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'extra_info' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/QPNode.php b/vendor/mgp25/instagram-php/src/Response/Model/QPNode.php
new file mode 100755
index 0000000..6ccf4b6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/QPNode.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'promotion_id' => 'string',
+ 'max_impressions' => 'int',
+ 'triggers' => 'string[]',
+ 'contextual_filters' => 'ContextualFilters',
+ 'template' => 'Template',
+ 'creatives' => 'Creative[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/QPSurface.php b/vendor/mgp25/instagram-php/src/Response/Model/QPSurface.php
new file mode 100755
index 0000000..75c50f3
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/QPSurface.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'cooldown' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/QPViewerData.php b/vendor/mgp25/instagram-php/src/Response/Model/QPViewerData.php
new file mode 100755
index 0000000..5c2f755
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/QPViewerData.php
@@ -0,0 +1,20 @@
+ 'Viewer',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/QueryResponse.php b/vendor/mgp25/instagram-php/src/Response/Model/QueryResponse.php
new file mode 100755
index 0000000..44eef1b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/QueryResponse.php
@@ -0,0 +1,20 @@
+ 'ShadowInstagramUser',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/QuestionSticker.php b/vendor/mgp25/instagram-php/src/Response/Model/QuestionSticker.php
new file mode 100755
index 0000000..a2718a0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/QuestionSticker.php
@@ -0,0 +1,56 @@
+ 'string',
+ 'question' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'text_color' => 'string',
+ /*
+ * HTML color string such as "#812A2A".
+ */
+ 'background_color' => 'string',
+ 'viewer_can_interact' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'question_type' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/QuizSticker.php b/vendor/mgp25/instagram-php/src/Response/Model/QuizSticker.php
new file mode 100755
index 0000000..6bce739
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/QuizSticker.php
@@ -0,0 +1,70 @@
+ 'string',
+ 'quiz_id' => 'string',
+ 'question' => 'string',
+ 'tallies' => 'Tallies[]',
+ 'correct_answer' => 'int',
+ 'viewer_can_answer' => 'bool',
+ 'finished' => 'bool',
+ 'text_color' => 'string',
+ 'start_background_color' => 'string',
+ 'end_background_color' => 'string',
+ 'viewer_answer' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Ranking.php b/vendor/mgp25/instagram-php/src/Response/Model/Ranking.php
new file mode 100755
index 0000000..8fc647a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Ranking.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'score_map' => '',
+ 'expiration_ms' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Reel.php b/vendor/mgp25/instagram-php/src/Response/Model/Reel.php
new file mode 100755
index 0000000..7dcc069
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Reel.php
@@ -0,0 +1,107 @@
+ 'string',
+ /*
+ * Unix "taken_at" timestamp of the newest item in their story reel.
+ */
+ 'latest_reel_media' => 'string',
+ /*
+ * The "taken_at" timestamp of the last story media you have seen for
+ * that user (the current reel's user). Defaults to `0` (not seen).
+ */
+ 'seen' => 'string',
+ 'can_reply' => 'bool',
+ 'can_reshare' => 'bool',
+ 'reel_type' => 'string',
+ 'cover_media' => 'CoverMedia',
+ 'user' => 'User',
+ 'items' => 'Item[]',
+ 'ranked_position' => 'string',
+ 'title' => 'string',
+ 'seen_ranked_position' => 'string',
+ 'expiring_at' => 'string',
+ 'has_besties_media' => 'bool', // Uses int(0) for false and 1 for true.
+ 'location' => 'Location',
+ 'prefetch_count' => 'int',
+ 'broadcast' => 'Broadcast',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ReelMention.php b/vendor/mgp25/instagram-php/src/Response/Model/ReelMention.php
new file mode 100755
index 0000000..d3f0192
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ReelMention.php
@@ -0,0 +1,65 @@
+ 'User',
+ 'is_hidden' => 'int',
+ 'display_type' => 'string',
+ 'is_sticker' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ReelShare.php b/vendor/mgp25/instagram-php/src/Response/Model/ReelShare.php
new file mode 100755
index 0000000..8dcceca
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ReelShare.php
@@ -0,0 +1,70 @@
+ 'Item[]',
+ 'story_ranking_token' => 'string',
+ 'broadcasts' => '',
+ 'sticker_version' => 'int',
+ 'text' => 'string',
+ 'type' => 'string',
+ 'is_reel_persisted' => 'bool',
+ 'reel_owner_id' => 'string',
+ 'reel_type' => 'string',
+ 'media' => 'Item',
+ 'mentioned_user_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Related.php b/vendor/mgp25/instagram-php/src/Response/Model/Related.php
new file mode 100755
index 0000000..4658f02
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Related.php
@@ -0,0 +1,30 @@
+ '',
+ 'id' => 'string',
+ 'type' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Responder.php b/vendor/mgp25/instagram-php/src/Response/Model/Responder.php
new file mode 100755
index 0000000..7d23dfb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Responder.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'has_shared_response' => 'bool',
+ 'id' => 'string',
+ 'user' => 'User',
+ 'ts' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/RewriteRule.php b/vendor/mgp25/instagram-php/src/Response/Model/RewriteRule.php
new file mode 100755
index 0000000..d6a5302
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/RewriteRule.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'replacer' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SavedFeedItem.php b/vendor/mgp25/instagram-php/src/Response/Model/SavedFeedItem.php
new file mode 100755
index 0000000..04978b6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SavedFeedItem.php
@@ -0,0 +1,20 @@
+ 'Item',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Section.php b/vendor/mgp25/instagram-php/src/Response/Model/Section.php
new file mode 100755
index 0000000..f60678b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Section.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'title' => 'string',
+ 'items' => 'Item[]',
+ 'layout_type' => 'string',
+ 'layout_content' => 'LayoutContent',
+ 'feed_type' => 'string',
+ 'explore_item_info' => 'ExploreItemInfo',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SectionMedia.php b/vendor/mgp25/instagram-php/src/Response/Model/SectionMedia.php
new file mode 100755
index 0000000..6c6130d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SectionMedia.php
@@ -0,0 +1,20 @@
+ 'Item',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ServerDataInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/ServerDataInfo.php
new file mode 100755
index 0000000..f33a837
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ServerDataInfo.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'nonce' => 'string',
+ 'conferenceName' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/ShadowInstagramUser.php b/vendor/mgp25/instagram-php/src/Response/Model/ShadowInstagramUser.php
new file mode 100755
index 0000000..c4553eb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/ShadowInstagramUser.php
@@ -0,0 +1,50 @@
+ 'string',
+ 'instagram_user_id' => 'string',
+ 'followers_count' => 'int',
+ 'username' => 'string',
+ 'profile_picture' => 'Image',
+ 'business_manager' => 'BusinessManager',
+ 'error' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SharePrefillEntities.php b/vendor/mgp25/instagram-php/src/Response/Model/SharePrefillEntities.php
new file mode 100755
index 0000000..4f4ec65
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SharePrefillEntities.php
@@ -0,0 +1,20 @@
+ 'DirectThread',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SharedFollower.php b/vendor/mgp25/instagram-php/src/Response/Model/SharedFollower.php
new file mode 100755
index 0000000..f505d33
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SharedFollower.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'username' => 'string',
+ 'full_name' => 'string',
+ 'is_private' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_id' => 'string',
+ 'is_verified' => 'bool',
+ 'has_anonymous_profile_picture' => 'bool',
+ 'reel_auto_archive' => 'string',
+ 'overlap_score' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SharedFollowerAccountsInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/SharedFollowerAccountsInfo.php
new file mode 100755
index 0000000..f239746
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SharedFollowerAccountsInfo.php
@@ -0,0 +1,20 @@
+ 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Slot.php b/vendor/mgp25/instagram-php/src/Response/Model/Slot.php
new file mode 100755
index 0000000..cfae147
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Slot.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'cooldown' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StaticStickers.php b/vendor/mgp25/instagram-php/src/Response/Model/StaticStickers.php
new file mode 100755
index 0000000..479622d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StaticStickers.php
@@ -0,0 +1,30 @@
+ '',
+ 'id' => 'string',
+ 'stickers' => 'Stickers[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StepData.php b/vendor/mgp25/instagram-php/src/Response/Model/StepData.php
new file mode 100755
index 0000000..1082861
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StepData.php
@@ -0,0 +1,80 @@
+ 'string',
+ 'phone_number' => 'string',
+ 'phone_number_formatted' => 'string',
+ 'email' => 'string',
+ 'fb_access_token' => 'string',
+ 'big_blue_token' => 'string',
+ 'google_oauth_token' => 'string',
+ 'security_code' => 'string',
+ 'sms_resend_delay' => 'int',
+ 'resend_delay' => 'int',
+ 'contact_point' => 'string',
+ 'form_type' => 'string',
+ 'phone_number_preview' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Stickers.php b/vendor/mgp25/instagram-php/src/Response/Model/Stickers.php
new file mode 100755
index 0000000..fdc47e5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Stickers.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'tray_image_width_ratio' => '',
+ 'image_height' => '',
+ 'image_width_ratio' => '',
+ 'type' => '',
+ 'image_width' => '',
+ 'name' => '',
+ 'image_url' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Stories.php b/vendor/mgp25/instagram-php/src/Response/Model/Stories.php
new file mode 100755
index 0000000..382b5d6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Stories.php
@@ -0,0 +1,35 @@
+ '',
+ 'tray' => 'StoryTray[]',
+ 'id' => 'string',
+ 'top_live' => 'TopLive',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoriesNetego.php b/vendor/mgp25/instagram-php/src/Response/Model/StoriesNetego.php
new file mode 100755
index 0000000..e4f0ac7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoriesNetego.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'hide_unit_if_seen' => 'string',
+ 'id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Story.php b/vendor/mgp25/instagram-php/src/Response/Model/Story.php
new file mode 100755
index 0000000..3250e2c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Story.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'counts' => 'Counts',
+ 'args' => 'Args',
+ 'type' => 'int',
+ 'story_type' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryAppAttribution.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryAppAttribution.php
new file mode 100755
index 0000000..04df311
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryAppAttribution.php
@@ -0,0 +1,45 @@
+ 'string',
+ 'app_icon_url' => 'string',
+ 'content_url' => 'string',
+ 'id' => 'string',
+ 'link' => 'string',
+ 'name' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryCountdowns.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryCountdowns.php
new file mode 100755
index 0000000..6c79803
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryCountdowns.php
@@ -0,0 +1,60 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float',
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ 'is_hidden' => 'int',
+ 'countdown_sticker' => 'CountdownSticker',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryCta.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryCta.php
new file mode 100755
index 0000000..56884e9
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryCta.php
@@ -0,0 +1,25 @@
+ 'AndroidLinks[]',
+ 'felix_deep_link' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryHashtag.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryHashtag.php
new file mode 100755
index 0000000..f0c50a1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryHashtag.php
@@ -0,0 +1,70 @@
+ 'Hashtag',
+ 'attribution' => 'string',
+ 'custom_title' => 'string',
+ 'is_hidden' => 'int',
+ 'is_sticker' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryLocation.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryLocation.php
new file mode 100755
index 0000000..bc6bdfc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryLocation.php
@@ -0,0 +1,65 @@
+ 'Location',
+ 'attribution' => 'string',
+ 'is_hidden' => 'int',
+ 'is_sticker' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestionResponderInfos.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestionResponderInfos.php
new file mode 100755
index 0000000..0a24469
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestionResponderInfos.php
@@ -0,0 +1,65 @@
+ 'string',
+ 'question' => 'string',
+ 'question_type' => 'string',
+ 'background_color' => 'string',
+ 'text_color' => 'string',
+ 'responders' => 'Responder[]',
+ 'max_id' => '',
+ 'more_available' => 'bool',
+ 'question_response_count' => 'int',
+ 'latest_question_response_time' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestions.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestions.php
new file mode 100755
index 0000000..83fbdcc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryQuestions.php
@@ -0,0 +1,60 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float',
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ 'is_hidden' => 'int',
+ 'question_sticker' => 'QuestionSticker',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizParticipantInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizParticipantInfo.php
new file mode 100755
index 0000000..3f68f45
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizParticipantInfo.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'participants' => 'Participants[]',
+ 'max_id' => '',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizs.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizs.php
new file mode 100755
index 0000000..192aec5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryQuizs.php
@@ -0,0 +1,65 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float',
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ 'is_hidden' => 'int',
+ 'is_sticker' => 'int',
+ 'quiz_sticker' => 'QuizSticker',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryShare.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryShare.php
new file mode 100755
index 0000000..6b1724d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryShare.php
@@ -0,0 +1,40 @@
+ 'Item',
+ 'text' => 'string',
+ 'title' => 'string',
+ 'message' => 'string',
+ 'is_linked' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryTray.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryTray.php
new file mode 100755
index 0000000..d7a2ff1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryTray.php
@@ -0,0 +1,162 @@
+ 'string',
+ 'items' => 'Item[]',
+ 'hide_from_feed_unit' => 'bool',
+ 'media_ids' => 'string[]',
+ 'has_pride_media' => 'bool',
+ 'user' => 'User',
+ 'can_reply' => '',
+ 'expiring_at' => '',
+ 'seen_ranked_position' => 'string',
+ /*
+ * The "taken_at" timestamp of the last story media you have seen for
+ * that user (the current tray's user). Defaults to `0` (not seen).
+ */
+ 'seen' => 'string',
+ /*
+ * Unix "taken_at" timestamp of the newest item in their story reel.
+ */
+ 'latest_reel_media' => 'string',
+ 'ranked_position' => 'string',
+ 'is_nux' => '',
+ 'show_nux_tooltip' => '',
+ 'muted' => '',
+ 'prefetch_count' => 'int',
+ 'location' => 'Location',
+ 'source_token' => '',
+ 'owner' => 'Owner',
+ 'nux_id' => 'string',
+ 'dismiss_card' => 'DismissCard',
+ 'can_reshare' => '',
+ 'has_besties_media' => 'bool',
+ 'reel_type' => 'string',
+ 'unique_integer_reel_id' => 'string',
+ 'cover_media' => 'CoverMedia',
+ 'title' => 'string',
+ 'media_count' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/StoryTvChannel.php b/vendor/mgp25/instagram-php/src/Response/Model/StoryTvChannel.php
new file mode 100755
index 0000000..99d2248
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/StoryTvChannel.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'items' => 'Item[]',
+ 'title' => 'string',
+ 'type' => 'string',
+ 'max_id' => 'string',
+ 'more_available' => 'bool',
+ 'seen_state' => 'mixed',
+ 'user_dict' => 'User',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Subscription.php b/vendor/mgp25/instagram-php/src/Response/Model/Subscription.php
new file mode 100755
index 0000000..5a63e28
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Subscription.php
@@ -0,0 +1,35 @@
+ '',
+ 'url' => 'string',
+ 'sequence' => '',
+ 'auth' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Suggested.php b/vendor/mgp25/instagram-php/src/Response/Model/Suggested.php
new file mode 100755
index 0000000..e854bea
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Suggested.php
@@ -0,0 +1,40 @@
+ 'int',
+ 'hashtag' => 'Hashtag',
+ 'user' => 'User',
+ 'place' => 'LocationItem',
+ 'client_time' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SuggestedUsers.php b/vendor/mgp25/instagram-php/src/Response/Model/SuggestedUsers.php
new file mode 100755
index 0000000..862f46b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SuggestedUsers.php
@@ -0,0 +1,75 @@
+ 'string',
+ 'view_all_text' => '',
+ 'title' => '',
+ 'auto_dvance' => '',
+ 'type' => '',
+ 'tracking_token' => 'string',
+ 'landing_site_type' => '',
+ 'landing_site_title' => '',
+ 'upsell_fb_pos' => '',
+ 'suggestions' => 'Suggestion[]',
+ 'suggestion_cards' => 'SuggestionCard[]',
+ 'netego_type' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Suggestion.php b/vendor/mgp25/instagram-php/src/Response/Model/Suggestion.php
new file mode 100755
index 0000000..0ee7a32
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Suggestion.php
@@ -0,0 +1,75 @@
+ '',
+ 'social_context' => 'string',
+ 'algorithm' => 'string',
+ 'thumbnail_urls' => 'string[]',
+ 'value' => 'float',
+ 'caption' => '',
+ 'user' => 'User',
+ 'large_urls' => 'string[]',
+ 'media_ids' => '',
+ 'icon' => '',
+ 'is_new_suggestion' => 'bool',
+ 'uuid' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SuggestionCard.php b/vendor/mgp25/instagram-php/src/Response/Model/SuggestionCard.php
new file mode 100755
index 0000000..78903ba
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SuggestionCard.php
@@ -0,0 +1,30 @@
+ 'UserCard',
+ 'upsell_ci_card' => '',
+ 'upsell_fbc_card' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SummaryPromotions.php b/vendor/mgp25/instagram-php/src/Response/Model/SummaryPromotions.php
new file mode 100755
index 0000000..33947ad
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SummaryPromotions.php
@@ -0,0 +1,25 @@
+ 'BusinessEdge[]',
+ 'page_info' => 'PageInfo',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SupportedCapabilities.php b/vendor/mgp25/instagram-php/src/Response/Model/SupportedCapabilities.php
new file mode 100755
index 0000000..2c682d6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SupportedCapabilities.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'value' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Surface.php b/vendor/mgp25/instagram-php/src/Response/Model/Surface.php
new file mode 100755
index 0000000..2bfbdc1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Surface.php
@@ -0,0 +1,39 @@
+ '',
+ 'rank_token' => 'string',
+ 'ttl_secs' => 'int',
+ 'name' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/SystemControl.php b/vendor/mgp25/instagram-php/src/Response/Model/SystemControl.php
new file mode 100755
index 0000000..b5c5da0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/SystemControl.php
@@ -0,0 +1,30 @@
+ 'int',
+ 'upload_time_period_sec' => 'int',
+ 'upload_bytes_per_update' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/TVChannel.php b/vendor/mgp25/instagram-php/src/Response/Model/TVChannel.php
new file mode 100755
index 0000000..5824b72
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/TVChannel.php
@@ -0,0 +1,55 @@
+ 'string',
+ 'title' => 'string',
+ 'id' => 'string',
+ 'items' => 'Item[]',
+ 'more_available' => 'bool',
+ 'max_id' => 'string',
+ 'seen_state' => '',
+ 'user_dict' => 'User',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/TVSearchResult.php b/vendor/mgp25/instagram-php/src/Response/Model/TVSearchResult.php
new file mode 100755
index 0000000..a512938
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/TVSearchResult.php
@@ -0,0 +1,40 @@
+ 'string',
+ 'User' => 'User',
+ 'channel' => 'TVChannel',
+ 'num_results' => 'int',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Tab.php b/vendor/mgp25/instagram-php/src/Response/Model/Tab.php
new file mode 100755
index 0000000..975b258
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Tab.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'title' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/TabsInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/TabsInfo.php
new file mode 100755
index 0000000..b636fc4
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/TabsInfo.php
@@ -0,0 +1,25 @@
+ 'Tab[]',
+ 'selected' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Tag.php b/vendor/mgp25/instagram-php/src/Response/Model/Tag.php
new file mode 100755
index 0000000..42af922
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Tag.php
@@ -0,0 +1,105 @@
+ 'string',
+ 'name' => 'string',
+ 'media_count' => 'int',
+ 'type' => 'string',
+ 'follow_status' => '',
+ 'following' => '',
+ 'allow_following' => '',
+ 'allow_muting_story' => '',
+ 'profile_pic_url' => '',
+ 'non_violating' => '',
+ 'related_tags' => '',
+ 'subtitle' => '',
+ 'social_context' => '',
+ 'social_context_profile_links' => '',
+ 'show_follow_drop_down' => '',
+ 'follow_button_text' => '',
+ 'debug_info' => '',
+ 'search_result_subtitle' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Tallies.php b/vendor/mgp25/instagram-php/src/Response/Model/Tallies.php
new file mode 100755
index 0000000..58ddedd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Tallies.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'count' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Template.php b/vendor/mgp25/instagram-php/src/Response/Model/Template.php
new file mode 100755
index 0000000..750250d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Template.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'parameters' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Text.php b/vendor/mgp25/instagram-php/src/Response/Model/Text.php
new file mode 100755
index 0000000..9515d3a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Text.php
@@ -0,0 +1,20 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Thumbnail.php b/vendor/mgp25/instagram-php/src/Response/Model/Thumbnail.php
new file mode 100755
index 0000000..4dc41a6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Thumbnail.php
@@ -0,0 +1,65 @@
+ 'float',
+ 'thumbnail_width' => 'int',
+ 'thumbnail_height' => 'int',
+ 'thumbnail_duration' => 'float',
+ 'sprite_urls' => 'string[]',
+ 'thumbnails_per_row' => 'int',
+ 'max_thumbnails_per_sprite' => 'int',
+ 'sprite_width' => 'int',
+ 'sprite_height' => 'int',
+ 'rendered_width' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/TimeRange.php b/vendor/mgp25/instagram-php/src/Response/Model/TimeRange.php
new file mode 100755
index 0000000..f00baef
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/TimeRange.php
@@ -0,0 +1,25 @@
+ 'string',
+ 'end' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Token.php b/vendor/mgp25/instagram-php/src/Response/Model/Token.php
new file mode 100755
index 0000000..6b1ef68
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Token.php
@@ -0,0 +1,82 @@
+ 'string',
+ 'carrier_id' => 'int',
+ 'ttl' => 'int',
+ 'features' => '',
+ 'request_time' => 'string',
+ 'token_hash' => 'string',
+ 'rewrite_rules' => 'RewriteRule[]',
+ 'enabled_wallet_defs_keys' => '',
+ 'deadline' => 'string',
+ 'zero_cms_fetch_interval_seconds' => 'int',
+ ];
+
+ const DEFAULT_TTL = 3600;
+
+ /**
+ * Get token expiration timestamp.
+ *
+ * @return int
+ */
+ public function expiresAt()
+ {
+ $ttl = (int) $this->getTtl();
+ if ($ttl === 0) {
+ $ttl = self::DEFAULT_TTL;
+ }
+
+ return time() + $ttl;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/TopLive.php b/vendor/mgp25/instagram-php/src/Response/Model/TopLive.php
new file mode 100755
index 0000000..d1f0b78
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/TopLive.php
@@ -0,0 +1,25 @@
+ 'User[]',
+ 'ranked_position' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/TraceControl.php b/vendor/mgp25/instagram-php/src/Response/Model/TraceControl.php
new file mode 100755
index 0000000..54c4b6a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/TraceControl.php
@@ -0,0 +1,35 @@
+ 'int',
+ 'cold_start' => '',
+ 'timed_out_upload_sample_rate' => 'int',
+ 'qpl' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/TraySuggestions.php b/vendor/mgp25/instagram-php/src/Response/Model/TraySuggestions.php
new file mode 100755
index 0000000..9518eff
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/TraySuggestions.php
@@ -0,0 +1,40 @@
+ 'StoryTray[]',
+ 'tray_title' => 'string',
+ 'banner_title' => 'string',
+ 'banner_subtitle' => 'string',
+ 'suggestion_type' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/TwoFactorInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/TwoFactorInfo.php
new file mode 100755
index 0000000..bf8385f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/TwoFactorInfo.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'two_factor_identifier' => 'string',
+ 'phone_verification_settings' => 'PhoneVerificationSettings',
+ 'obfuscated_phone_number' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/CoreUnpredictableContainer.php b/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/CoreUnpredictableContainer.php
new file mode 100755
index 0000000..497944b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/CoreUnpredictableContainer.php
@@ -0,0 +1,95 @@
+_cache === null) {
+ $this->_cache = $this->asArray(); // Throws.
+ }
+
+ if ($this->_type !== null) {
+ foreach ($this->_cache as &$value) {
+ if (is_array($value)) {
+ $value = new $this->_type($value); // Throws.
+ }
+ }
+ }
+
+ return $this->_cache;
+ }
+
+ /**
+ * Set the data array of this unpredictable container.
+ *
+ * @param array $value The new data array.
+ *
+ * @throws LazyJsonMapperException
+ *
+ * @return $this
+ */
+ public function setData(
+ array $value)
+ {
+ $this->_cache = $value;
+
+ $newObjectData = [];
+ foreach ($this->_cache as $k => $v) {
+ $newObjectData[$k] = is_object($v) && $v instanceof LazyJsonMapper
+ ? $v->asArray() // Throws.
+ : $v; // Is already a valid value.
+ }
+
+ $this->assignObjectData($newObjectData); // Throws.
+
+ return $this;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/DirectThreadLastSeenAtUnpredictableContainer.php b/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/DirectThreadLastSeenAtUnpredictableContainer.php
new file mode 100755
index 0000000..5647e45
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/UnpredictableKeys/DirectThreadLastSeenAtUnpredictableContainer.php
@@ -0,0 +1,13 @@
+ 'string',
+ 'has_anonymous_profile_picture' => 'bool',
+ 'has_highlight_reels' => 'bool',
+ 'is_eligible_to_show_fb_cross_sharing_nux' => 'bool',
+ 'page_id_for_new_suma_biz_account' => 'bool',
+ 'eligible_shopping_signup_entrypoints' => 'string[]',
+ 'is_favorite' => 'bool',
+ 'is_favorite_for_stories' => 'bool',
+ 'is_favorite_for_highlights' => 'bool',
+ 'is_interest_account' => 'bool',
+ 'can_be_reported_as_fraud' => 'bool',
+ 'profile_pic_url' => 'string',
+ 'profile_pic_id' => 'string',
+ 'permission' => 'bool',
+ 'full_name' => 'string',
+ 'user_id' => 'string',
+ 'pk' => 'string',
+ 'id' => 'string',
+ 'is_verified' => 'bool',
+ 'is_private' => 'bool',
+ 'coeff_weight' => '',
+ 'friendship_status' => 'FriendshipStatus',
+ 'hd_profile_pic_versions' => 'ImageCandidate[]',
+ 'byline' => '',
+ 'search_social_context' => '',
+ 'unseen_count' => '',
+ 'mutual_followers_count' => 'int',
+ 'follower_count' => 'int',
+ 'search_subtitle' => 'string',
+ 'social_context' => '',
+ 'media_count' => 'int',
+ 'following_count' => 'int',
+ 'following_tag_count' => 'int',
+ 'instagram_location_id' => '',
+ 'is_business' => 'bool',
+ 'usertags_count' => 'int',
+ 'profile_context' => '',
+ 'biography' => 'string',
+ 'geo_media_count' => 'int',
+ 'is_unpublished' => 'bool',
+ 'allow_contacts_sync' => '',
+ 'show_feed_biz_conversion_icon' => '',
+ 'auto_expand_chaining' => '',
+ 'can_boost_post' => '',
+ 'is_profile_action_needed' => 'bool',
+ 'has_chaining' => 'bool',
+ 'has_recommend_accounts' => 'bool',
+ 'chaining_suggestions' => 'ChainingSuggestion[]',
+ 'include_direct_blacklist_status' => '',
+ 'can_see_organic_insights' => 'bool',
+ 'has_placed_orders' => 'bool',
+ 'can_convert_to_business' => 'bool',
+ 'convert_from_pages' => '',
+ 'show_business_conversion_icon' => 'bool',
+ 'show_conversion_edit_entry' => 'bool',
+ 'show_insights_terms' => 'bool',
+ 'can_create_sponsor_tags' => '',
+ 'hd_profile_pic_url_info' => 'ImageCandidate',
+ 'usertag_review_enabled' => '',
+ 'profile_context_mutual_follow_ids' => 'string[]',
+ 'profile_context_links_with_user_ids' => 'Link[]',
+ 'has_biography_translation' => 'bool',
+ 'total_igtv_videos' => 'int',
+ 'total_ar_effects' => 'int',
+ 'can_link_entities_in_bio' => 'bool',
+ 'biography_with_entities' => 'BiographyEntities',
+ 'max_num_linked_entities_in_bio' => 'int',
+ 'business_contact_method' => 'string',
+ 'highlight_reshare_disabled' => 'bool',
+ /*
+ * Business category.
+ */
+ 'category' => 'string',
+ 'direct_messaging' => 'string',
+ 'page_name' => 'string',
+ 'is_attribute_sync_enabled' => 'bool',
+ 'has_business_presence_node' => 'bool',
+ 'profile_visits_count' => 'int',
+ 'profile_visits_num_days' => 'int',
+ 'is_call_to_action_enabled' => 'bool',
+ 'linked_fb_user' => 'FacebookUser',
+ 'account_type' => 'int',
+ 'should_show_category' => 'bool',
+ 'should_show_public_contacts' => 'bool',
+ 'can_hide_category' => 'bool',
+ 'can_hide_public_contacts' => 'bool',
+ 'can_tag_products_from_merchants' => 'bool',
+ 'public_phone_country_code' => 'string',
+ 'public_phone_number' => 'string',
+ 'contact_phone_number' => 'string',
+ 'latitude' => 'float',
+ 'longitude' => 'float',
+ 'address_street' => 'string',
+ 'zip' => 'string',
+ 'city_id' => 'string', // 64-bit number.
+ 'city_name' => 'string',
+ 'public_email' => 'string',
+ 'is_needy' => 'bool',
+ 'external_url' => 'string',
+ 'external_lynx_url' => 'string',
+ 'email' => 'string',
+ 'country_code' => 'int',
+ 'birthday' => '',
+ 'national_number' => 'string', // Really int, but may be >32bit.
+ 'gender' => 'int',
+ 'phone_number' => 'string',
+ 'needs_email_confirm' => '',
+ 'is_active' => 'bool',
+ 'block_at' => '',
+ 'aggregate_promote_engagement' => '',
+ 'fbuid' => '',
+ 'page_id' => 'string',
+ 'can_claim_page' => 'bool',
+ 'fb_page_call_to_action_id' => 'string',
+ 'fb_page_call_to_action_ix_app_id' => 'int',
+ 'fb_page_call_to_action_ix_label_bundle' => '',
+ 'fb_page_call_to_action_ix_url' => 'string',
+ 'fb_page_call_to_action_ix_partner' => 'string',
+ 'is_call_to_action_enabled_by_surface' => 'bool',
+ 'can_crosspost_without_fb_token' => 'bool',
+ 'num_of_admined_pages' => 'int',
+ 'shoppable_posts_count' => 'int',
+ 'show_shoppable_feed' => 'bool',
+ 'show_account_transparency_details' => 'bool',
+ /*
+ * Unix "taken_at" timestamp of the newest item in their story reel.
+ */
+ 'latest_reel_media' => 'string',
+ 'has_unseen_besties_media' => 'bool',
+ 'allowed_commenter_type' => 'string',
+ 'reel_auto_archive' => 'string',
+ 'is_directapp_installed' => 'bool',
+ 'is_using_unified_inbox_for_direct' => 'int',
+ 'feed_post_reshare_disabled' => 'bool',
+ 'besties_count' => 'int',
+ 'can_be_tagged_as_sponsor' => 'bool',
+ 'can_follow_hashtag' => 'bool',
+ 'is_potential_business' => 'bool',
+ 'has_profile_video_feed' => 'bool',
+ 'is_video_creator' => 'bool',
+ 'show_besties_badge' => 'bool',
+ 'recently_bestied_by_count' => 'int',
+ 'screenshotted' => 'bool',
+ 'nametag' => 'Nametag',
+ 'school' => '',
+ 'is_bestie' => 'bool',
+ 'live_subscription_status' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/UserCard.php b/vendor/mgp25/instagram-php/src/Response/Model/UserCard.php
new file mode 100755
index 0000000..5cab10b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/UserCard.php
@@ -0,0 +1,80 @@
+ 'User',
+ 'algorithm' => 'string',
+ 'social_context' => 'string',
+ 'caption' => '',
+ 'icon' => '',
+ 'media_ids' => '',
+ 'thumbnail_urls' => '',
+ 'large_urls' => '',
+ 'media_infos' => '',
+ 'value' => 'float',
+ 'is_new_suggestion' => 'bool',
+ 'uuid' => 'string',
+ 'followed_by' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/UserList.php b/vendor/mgp25/instagram-php/src/Response/Model/UserList.php
new file mode 100755
index 0000000..6952967
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/UserList.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'user' => 'User',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/UserPresence.php b/vendor/mgp25/instagram-php/src/Response/Model/UserPresence.php
new file mode 100755
index 0000000..d9e9aa9
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/UserPresence.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'last_activity_at_ms' => 'string',
+ 'is_active' => 'bool',
+ 'in_threads' => 'string[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Usertag.php b/vendor/mgp25/instagram-php/src/Response/Model/Usertag.php
new file mode 100755
index 0000000..f1bff83
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Usertag.php
@@ -0,0 +1,25 @@
+ 'In[]',
+ 'photo_of_you' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/VideoCallEvent.php b/vendor/mgp25/instagram-php/src/Response/Model/VideoCallEvent.php
new file mode 100755
index 0000000..57e3645
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/VideoCallEvent.php
@@ -0,0 +1,31 @@
+ 'string',
+ 'vc_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/VideoUploadUrl.php b/vendor/mgp25/instagram-php/src/Response/Model/VideoUploadUrl.php
new file mode 100755
index 0000000..cb572f7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/VideoUploadUrl.php
@@ -0,0 +1,30 @@
+ 'string',
+ 'job' => 'string',
+ 'expires' => 'float',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/VideoVersions.php b/vendor/mgp25/instagram-php/src/Response/Model/VideoVersions.php
new file mode 100755
index 0000000..057e909
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/VideoVersions.php
@@ -0,0 +1,40 @@
+ 'int', // Some kinda internal type ID, such as int(102).
+ 'width' => 'int',
+ 'height' => 'int',
+ 'url' => 'string',
+ 'id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Viewer.php b/vendor/mgp25/instagram-php/src/Response/Model/Viewer.php
new file mode 100755
index 0000000..9692734
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Viewer.php
@@ -0,0 +1,20 @@
+ 'EligiblePromotions',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/VoiceMedia.php b/vendor/mgp25/instagram-php/src/Response/Model/VoiceMedia.php
new file mode 100755
index 0000000..b6237dd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/VoiceMedia.php
@@ -0,0 +1,20 @@
+ 'DirectThreadItemMedia',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/Voter.php b/vendor/mgp25/instagram-php/src/Response/Model/Voter.php
new file mode 100755
index 0000000..129936d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/Voter.php
@@ -0,0 +1,25 @@
+ 'User',
+ 'vote' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/VoterInfo.php b/vendor/mgp25/instagram-php/src/Response/Model/VoterInfo.php
new file mode 100755
index 0000000..37f0aab
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/VoterInfo.php
@@ -0,0 +1,35 @@
+ 'string',
+ 'voters' => 'Voter[]',
+ 'max_id' => 'string',
+ 'more_available' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/Model/_Message.php b/vendor/mgp25/instagram-php/src/Response/Model/_Message.php
new file mode 100755
index 0000000..9b40e81
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/Model/_Message.php
@@ -0,0 +1,25 @@
+ '',
+ 'time' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MsisdnHeaderResponse.php b/vendor/mgp25/instagram-php/src/Response/MsisdnHeaderResponse.php
new file mode 100755
index 0000000..481f9b9
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MsisdnHeaderResponse.php
@@ -0,0 +1,47 @@
+ 'string',
+ 'url' => 'string',
+ 'remaining_ttl_seconds' => 'int',
+ 'ttl' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MultipleAccountFamilyResponse.php b/vendor/mgp25/instagram-php/src/Response/MultipleAccountFamilyResponse.php
new file mode 100755
index 0000000..d93d034
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MultipleAccountFamilyResponse.php
@@ -0,0 +1,37 @@
+ 'string[]',
+ 'main_accounts' => 'string[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/MutedReelsResponse.php b/vendor/mgp25/instagram-php/src/Response/MutedReelsResponse.php
new file mode 100755
index 0000000..3ef5370
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/MutedReelsResponse.php
@@ -0,0 +1,47 @@
+ 'Model\User[]',
+ 'next_max_id' => 'string',
+ 'page_size' => '',
+ 'big_list' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/OnBoardCatalogResponse.php b/vendor/mgp25/instagram-php/src/Response/OnBoardCatalogResponse.php
new file mode 100755
index 0000000..0766763
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/OnBoardCatalogResponse.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'current_catalog_id' => 'string',
+ 'is_business_targeted_for_shopping' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/OnTagProductResponse.php b/vendor/mgp25/instagram-php/src/Response/OnTagProductResponse.php
new file mode 100755
index 0000000..e7bd30a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/OnTagProductResponse.php
@@ -0,0 +1,42 @@
+ 'Model\Product',
+ 'merchant' => 'Model\User',
+ 'other_product_items' => 'Model\Product[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PermalinkResponse.php b/vendor/mgp25/instagram-php/src/Response/PermalinkResponse.php
new file mode 100755
index 0000000..e436246
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PermalinkResponse.php
@@ -0,0 +1,32 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PinCommentBroadcastResponse.php b/vendor/mgp25/instagram-php/src/Response/PinCommentBroadcastResponse.php
new file mode 100755
index 0000000..6ef84e3
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PinCommentBroadcastResponse.php
@@ -0,0 +1,32 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PopularFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/PopularFeedResponse.php
new file mode 100755
index 0000000..465bf20
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PopularFeedResponse.php
@@ -0,0 +1,57 @@
+ 'string',
+ 'more_available' => '',
+ 'auto_load_more_enabled' => '',
+ 'items' => 'Model\Item[]',
+ 'num_results' => 'int',
+ 'max_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PostLiveCommentsResponse.php b/vendor/mgp25/instagram-php/src/Response/PostLiveCommentsResponse.php
new file mode 100755
index 0000000..2b91274
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PostLiveCommentsResponse.php
@@ -0,0 +1,52 @@
+ '',
+ 'ending_offset' => '',
+ 'next_fetch_offset' => '',
+ 'comments' => 'Model\LiveComment[]',
+ 'pinned_comments' => 'Model\LiveComment[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PostLiveLikesResponse.php b/vendor/mgp25/instagram-php/src/Response/PostLiveLikesResponse.php
new file mode 100755
index 0000000..b7781cf
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PostLiveLikesResponse.php
@@ -0,0 +1,47 @@
+ '',
+ 'ending_offset' => '',
+ 'next_fetch_offset' => '',
+ 'time_series' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PostLiveViewerListResponse.php b/vendor/mgp25/instagram-php/src/Response/PostLiveViewerListResponse.php
new file mode 100755
index 0000000..5e6ca51
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PostLiveViewerListResponse.php
@@ -0,0 +1,42 @@
+ 'Model\User[]',
+ 'next_max_id' => '',
+ 'total_viewer_count' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PrefillCandidatesResponse.php b/vendor/mgp25/instagram-php/src/Response/PrefillCandidatesResponse.php
new file mode 100755
index 0000000..2aae401
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PrefillCandidatesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Prefill[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PresenceStatusResponse.php b/vendor/mgp25/instagram-php/src/Response/PresenceStatusResponse.php
new file mode 100755
index 0000000..ec4d0b7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PresenceStatusResponse.php
@@ -0,0 +1,37 @@
+ 'bool',
+ 'thread_presence_disabled' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PresencesResponse.php b/vendor/mgp25/instagram-php/src/Response/PresencesResponse.php
new file mode 100755
index 0000000..feac8db
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PresencesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\UnpredictableKeys\PresenceUnpredictableContainer',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ProfileNoticeResponse.php b/vendor/mgp25/instagram-php/src/Response/ProfileNoticeResponse.php
new file mode 100755
index 0000000..19ef64b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ProfileNoticeResponse.php
@@ -0,0 +1,32 @@
+ 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PropertyCollection/Sticker.php b/vendor/mgp25/instagram-php/src/Response/PropertyCollection/Sticker.php
new file mode 100755
index 0000000..a436f2d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PropertyCollection/Sticker.php
@@ -0,0 +1,53 @@
+ 'float',
+ 'y' => 'float',
+ 'z' => 'float', // Unused by IG for now. So far it's always int(0).
+ 'width' => 'float',
+ 'height' => 'float',
+ 'rotation' => 'float',
+ 'is_pinned' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PushPreferencesResponse.php b/vendor/mgp25/instagram-php/src/Response/PushPreferencesResponse.php
new file mode 100755
index 0000000..4a99442
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PushPreferencesResponse.php
@@ -0,0 +1,117 @@
+ 'Model\PushSettings[]',
+ 'likes' => '',
+ 'comments' => '',
+ 'comment_likes' => '',
+ 'like_and_comment_on_photo_user_tagged' => '',
+ 'live_broadcast' => '',
+ 'new_follower' => '',
+ 'follow_request_accepted' => '',
+ 'contact_joined' => '',
+ 'pending_direct_share' => '',
+ 'direct_share_activity' => '',
+ 'user_tagged' => '',
+ 'notification_reminders' => '',
+ 'first_post' => '',
+ 'announcements' => '',
+ 'ads' => '',
+ 'view_count' => '',
+ 'report_updated' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/PushRegisterResponse.php b/vendor/mgp25/instagram-php/src/Response/PushRegisterResponse.php
new file mode 100755
index 0000000..b999f5f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/PushRegisterResponse.php
@@ -0,0 +1,25 @@
+ 'int',
+ 'global' => 'int',
+ 'default' => 'int',
+ 'surfaces' => 'Model\QPSurface[]',
+ 'slots' => 'Model\Slot[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/RecentSearchesResponse.php b/vendor/mgp25/instagram-php/src/Response/RecentSearchesResponse.php
new file mode 100755
index 0000000..d49da4a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/RecentSearchesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Suggested[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/RecoveryResponse.php b/vendor/mgp25/instagram-php/src/Response/RecoveryResponse.php
new file mode 100755
index 0000000..646fef8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/RecoveryResponse.php
@@ -0,0 +1,42 @@
+ 'bool',
+ 'title' => 'string',
+ 'body' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ReelMediaViewerResponse.php b/vendor/mgp25/instagram-php/src/Response/ReelMediaViewerResponse.php
new file mode 100755
index 0000000..99d83ed
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ReelMediaViewerResponse.php
@@ -0,0 +1,62 @@
+ 'Model\User[]',
+ 'next_max_id' => 'string',
+ 'user_count' => 'int',
+ 'total_viewer_count' => 'int',
+ 'screenshotter_user_ids' => '',
+ 'total_screenshot_count' => 'int',
+ 'updated_media' => 'Model\Item',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ReelSettingsResponse.php b/vendor/mgp25/instagram-php/src/Response/ReelSettingsResponse.php
new file mode 100755
index 0000000..7151269
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ReelSettingsResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'blocked_reels' => 'Model\BlockedReels',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ReelsMediaResponse.php b/vendor/mgp25/instagram-php/src/Response/ReelsMediaResponse.php
new file mode 100755
index 0000000..eeff797
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ReelsMediaResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Reel[]',
+ 'reels' => 'Model\UnpredictableKeys\ReelUnpredictableContainer',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ReelsTrayFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/ReelsTrayFeedResponse.php
new file mode 100755
index 0000000..a02fe85
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ReelsTrayFeedResponse.php
@@ -0,0 +1,72 @@
+ 'string',
+ 'broadcasts' => 'Model\Broadcast[]',
+ 'tray' => 'Model\StoryTray[]',
+ 'post_live' => 'Model\PostLive',
+ 'sticker_version' => 'int',
+ 'face_filter_nux_version' => 'int',
+ 'stories_viewer_gestures_nux_eligible' => 'bool',
+ 'has_new_nux_story' => 'bool',
+ 'suggestions' => 'Model\TraySuggestions[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/RelatedLocationResponse.php b/vendor/mgp25/instagram-php/src/Response/RelatedLocationResponse.php
new file mode 100755
index 0000000..089eb60
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/RelatedLocationResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Location[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ReportExploreMediaResponse.php b/vendor/mgp25/instagram-php/src/Response/ReportExploreMediaResponse.php
new file mode 100755
index 0000000..a341e4f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ReportExploreMediaResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ResumableOffsetResponse.php b/vendor/mgp25/instagram-php/src/Response/ResumableOffsetResponse.php
new file mode 100755
index 0000000..42793cd
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ResumableOffsetResponse.php
@@ -0,0 +1,52 @@
+ 'int',
+ ];
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ $offset = $this->_getProperty('offset');
+ if ($offset !== null && $offset >= 0) {
+ return true;
+ } else {
+ // Set a nice message for exceptions.
+ if ($this->getMessage() === null) {
+ $this->setMessage('Offset for resumable uploader is missing or invalid.');
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ResumableUploadResponse.php b/vendor/mgp25/instagram-php/src/Response/ResumableUploadResponse.php
new file mode 100755
index 0000000..db9b2d0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ResumableUploadResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'upload_id' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ReviewPreferenceResponse.php b/vendor/mgp25/instagram-php/src/Response/ReviewPreferenceResponse.php
new file mode 100755
index 0000000..dee7ea6
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ReviewPreferenceResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SaveAndUnsaveMedia.php b/vendor/mgp25/instagram-php/src/Response/SaveAndUnsaveMedia.php
new file mode 100755
index 0000000..cc193e5
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SaveAndUnsaveMedia.php
@@ -0,0 +1,25 @@
+ 'Model\SavedFeedItem[]',
+ 'more_available' => '',
+ 'next_max_id' => 'string',
+ 'auto_load_more_enabled' => '',
+ 'num_results' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SearchTagResponse.php b/vendor/mgp25/instagram-php/src/Response/SearchTagResponse.php
new file mode 100755
index 0000000..d43b569
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SearchTagResponse.php
@@ -0,0 +1,42 @@
+ 'bool',
+ 'results' => 'Model\Tag[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SearchUserResponse.php b/vendor/mgp25/instagram-php/src/Response/SearchUserResponse.php
new file mode 100755
index 0000000..2cdb215
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SearchUserResponse.php
@@ -0,0 +1,47 @@
+ 'bool',
+ 'num_results' => 'int',
+ 'users' => 'Model\User[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SegmentedStartResponse.php b/vendor/mgp25/instagram-php/src/Response/SegmentedStartResponse.php
new file mode 100755
index 0000000..eb17072
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SegmentedStartResponse.php
@@ -0,0 +1,52 @@
+ 'string',
+ ];
+
+ /**
+ * Checks if the response was successful.
+ *
+ * @return bool
+ */
+ public function isOk()
+ {
+ $streamId = $this->_getProperty('stream_id');
+ if ($streamId !== null && $streamId !== '') {
+ return true;
+ } else {
+ // Set a nice message for exceptions.
+ if ($this->getMessage() === null) {
+ $this->setMessage('Stream ID for segmented uploader is missing or invalid.');
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SendConfirmEmailResponse.php b/vendor/mgp25/instagram-php/src/Response/SendConfirmEmailResponse.php
new file mode 100755
index 0000000..ed19156
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SendConfirmEmailResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'is_email_legit' => '',
+ 'body' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SendSMSCodeResponse.php b/vendor/mgp25/instagram-php/src/Response/SendSMSCodeResponse.php
new file mode 100755
index 0000000..9d532b8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SendSMSCodeResponse.php
@@ -0,0 +1,37 @@
+ 'bool',
+ 'phone_verification_settings' => 'Model\PhoneVerificationSettings',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SendTwoFactorEnableSMSResponse.php b/vendor/mgp25/instagram-php/src/Response/SendTwoFactorEnableSMSResponse.php
new file mode 100755
index 0000000..8105873
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SendTwoFactorEnableSMSResponse.php
@@ -0,0 +1,37 @@
+ 'Model\PhoneVerificationSettings',
+ 'obfuscated_phone_number' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SharePrefillResponse.php b/vendor/mgp25/instagram-php/src/Response/SharePrefillResponse.php
new file mode 100755
index 0000000..c32054e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SharePrefillResponse.php
@@ -0,0 +1,42 @@
+ 'Model\Ranking',
+ 'entities' => 'Model\ServerDataInfo',
+ 'failed_view_names' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SharedFollowersResponse.php b/vendor/mgp25/instagram-php/src/Response/SharedFollowersResponse.php
new file mode 100755
index 0000000..6e428eb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SharedFollowersResponse.php
@@ -0,0 +1,32 @@
+ 'Model\SharedFollower[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/StartLiveResponse.php b/vendor/mgp25/instagram-php/src/Response/StartLiveResponse.php
new file mode 100755
index 0000000..c5bea53
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/StartLiveResponse.php
@@ -0,0 +1,32 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/StickerAssetsResponse.php b/vendor/mgp25/instagram-php/src/Response/StickerAssetsResponse.php
new file mode 100755
index 0000000..73d8f12
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/StickerAssetsResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'static_stickers' => 'Model\StaticStickers[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/StoryAnswersResponse.php b/vendor/mgp25/instagram-php/src/Response/StoryAnswersResponse.php
new file mode 100755
index 0000000..cae9c24
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/StoryAnswersResponse.php
@@ -0,0 +1,32 @@
+ 'Model\StoryQuestionResponderInfos',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/StoryCountdownsResponse.php b/vendor/mgp25/instagram-php/src/Response/StoryCountdownsResponse.php
new file mode 100755
index 0000000..0733391
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/StoryCountdownsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\CountdownSticker[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/StoryPollVotersResponse.php b/vendor/mgp25/instagram-php/src/Response/StoryPollVotersResponse.php
new file mode 100755
index 0000000..6a250dc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/StoryPollVotersResponse.php
@@ -0,0 +1,32 @@
+ 'Model\VoterInfo',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SuggestedBroadcastsResponse.php b/vendor/mgp25/instagram-php/src/Response/SuggestedBroadcastsResponse.php
new file mode 100755
index 0000000..2d28366
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SuggestedBroadcastsResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Broadcast[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SuggestedSearchesResponse.php b/vendor/mgp25/instagram-php/src/Response/SuggestedSearchesResponse.php
new file mode 100755
index 0000000..98bca9d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SuggestedSearchesResponse.php
@@ -0,0 +1,37 @@
+ 'Model\Suggested[]',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SuggestedUsersBadgeResponse.php b/vendor/mgp25/instagram-php/src/Response/SuggestedUsersBadgeResponse.php
new file mode 100755
index 0000000..2c22f97
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SuggestedUsersBadgeResponse.php
@@ -0,0 +1,37 @@
+ '',
+ 'new_suggestion_ids' => 'string[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SuggestedUsersResponse.php b/vendor/mgp25/instagram-php/src/Response/SuggestedUsersResponse.php
new file mode 100755
index 0000000..0c60221
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SuggestedUsersResponse.php
@@ -0,0 +1,37 @@
+ 'Model\User[]',
+ 'is_backup' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SwitchBusinessProfileResponse.php b/vendor/mgp25/instagram-php/src/Response/SwitchBusinessProfileResponse.php
new file mode 100755
index 0000000..77dab4c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SwitchBusinessProfileResponse.php
@@ -0,0 +1,32 @@
+ '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SwitchPersonalProfileResponse.php b/vendor/mgp25/instagram-php/src/Response/SwitchPersonalProfileResponse.php
new file mode 100755
index 0000000..60c25c1
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SwitchPersonalProfileResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/SyncResponse.php b/vendor/mgp25/instagram-php/src/Response/SyncResponse.php
new file mode 100755
index 0000000..23f9c73
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/SyncResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Experiment[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TVChannelsResponse.php b/vendor/mgp25/instagram-php/src/Response/TVChannelsResponse.php
new file mode 100755
index 0000000..a3cde18
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TVChannelsResponse.php
@@ -0,0 +1,67 @@
+ 'string',
+ 'title' => 'string',
+ 'id' => 'string',
+ 'items' => 'Model\Item[]',
+ 'more_available' => 'bool',
+ 'max_id' => 'string',
+ 'seen_state' => '',
+ 'user_dict' => 'Model\User',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TVGuideResponse.php b/vendor/mgp25/instagram-php/src/Response/TVGuideResponse.php
new file mode 100755
index 0000000..0a6bee0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TVGuideResponse.php
@@ -0,0 +1,52 @@
+ 'Model\TVChannel[]',
+ 'my_channel' => 'Model\TVChannel',
+ 'badging' => 'Model\Badging',
+ 'composer' => 'Model\Composer',
+ 'banner_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TVSearchResponse.php b/vendor/mgp25/instagram-php/src/Response/TVSearchResponse.php
new file mode 100755
index 0000000..cc80d89
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TVSearchResponse.php
@@ -0,0 +1,42 @@
+ 'Model\TVSearchResult[]',
+ 'num_results' => 'int',
+ 'rank_token' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TagFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/TagFeedResponse.php
new file mode 100755
index 0000000..4b21e6c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TagFeedResponse.php
@@ -0,0 +1,77 @@
+ 'Model\Section[]',
+ 'num_results' => 'int',
+ 'ranked_items' => 'Model\Item[]',
+ 'auto_load_more_enabled' => 'bool',
+ 'items' => 'Model\Item[]',
+ 'story' => 'Model\StoryTray',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'next_media_ids' => '',
+ 'next_page' => 'int',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TagInfoResponse.php b/vendor/mgp25/instagram-php/src/Response/TagInfoResponse.php
new file mode 100755
index 0000000..12afa93
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TagInfoResponse.php
@@ -0,0 +1,68 @@
+ 'Model\Related[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TagsStoryResponse.php b/vendor/mgp25/instagram-php/src/Response/TagsStoryResponse.php
new file mode 100755
index 0000000..4adc673
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TagsStoryResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Reel',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TimelineFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/TimelineFeedResponse.php
new file mode 100755
index 0000000..3d23206
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TimelineFeedResponse.php
@@ -0,0 +1,98 @@
+ 'int',
+ 'client_gap_enforcer_matrix' => '',
+ 'is_direct_v2_enabled' => 'bool',
+ 'auto_load_more_enabled' => 'bool',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'pagination_info' => '',
+ 'feed_items' => 'Model\FeedItem[]',
+ 'megaphone' => 'Model\FeedAysf',
+ 'client_feed_changelist_applied' => 'bool',
+ 'view_state_version' => 'string',
+ 'feed_pill_text' => 'string',
+ 'client_gap_enforcer_matrix' => '',
+ 'client_session_id' => 'string',
+ 'startup_prefetch_configs' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TokenResultResponse.php b/vendor/mgp25/instagram-php/src/Response/TokenResultResponse.php
new file mode 100755
index 0000000..d22596f
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TokenResultResponse.php
@@ -0,0 +1,32 @@
+ 'Model\Token',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TopLiveStatusResponse.php b/vendor/mgp25/instagram-php/src/Response/TopLiveStatusResponse.php
new file mode 100755
index 0000000..a7e908c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TopLiveStatusResponse.php
@@ -0,0 +1,32 @@
+ 'Model\BroadcastStatusItem[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TranslateResponse.php b/vendor/mgp25/instagram-php/src/Response/TranslateResponse.php
new file mode 100755
index 0000000..84a0d12
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TranslateResponse.php
@@ -0,0 +1,32 @@
+ 'Model\CommentTranslations[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/TwoFactorLoginSMSResponse.php b/vendor/mgp25/instagram-php/src/Response/TwoFactorLoginSMSResponse.php
new file mode 100755
index 0000000..254afdf
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/TwoFactorLoginSMSResponse.php
@@ -0,0 +1,37 @@
+ 'bool',
+ 'two_factor_info' => 'Model\TwoFactorInfo',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/UnlinkAddressBookResponse.php b/vendor/mgp25/instagram-php/src/Response/UnlinkAddressBookResponse.php
new file mode 100755
index 0000000..4d44742
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/UnlinkAddressBookResponse.php
@@ -0,0 +1,25 @@
+ 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/UploadJobVideoResponse.php b/vendor/mgp25/instagram-php/src/Response/UploadJobVideoResponse.php
new file mode 100755
index 0000000..7d1e3d7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/UploadJobVideoResponse.php
@@ -0,0 +1,37 @@
+ 'string',
+ 'video_upload_urls' => 'Model\VideoUploadUrl[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/UploadPhotoResponse.php b/vendor/mgp25/instagram-php/src/Response/UploadPhotoResponse.php
new file mode 100755
index 0000000..6587b2c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/UploadPhotoResponse.php
@@ -0,0 +1,37 @@
+ 'string',
+ 'media_id' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/UploadVideoResponse.php b/vendor/mgp25/instagram-php/src/Response/UploadVideoResponse.php
new file mode 100755
index 0000000..0f9db0e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/UploadVideoResponse.php
@@ -0,0 +1,42 @@
+ 'string',
+ 'configure_delay_ms' => 'float',
+ 'result' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/UserFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/UserFeedResponse.php
new file mode 100755
index 0000000..082c94c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/UserFeedResponse.php
@@ -0,0 +1,57 @@
+ 'Model\Item[]',
+ 'num_results' => 'int',
+ 'more_available' => 'bool',
+ 'next_max_id' => 'string',
+ 'max_id' => 'string',
+ 'auto_load_more_enabled' => 'bool',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/UserInfoResponse.php b/vendor/mgp25/instagram-php/src/Response/UserInfoResponse.php
new file mode 100755
index 0000000..0f0a7ac
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/UserInfoResponse.php
@@ -0,0 +1,42 @@
+ '',
+ 'user' => 'Model\User',
+ 'effect_previews' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/UserReelMediaFeedResponse.php b/vendor/mgp25/instagram-php/src/Response/UserReelMediaFeedResponse.php
new file mode 100755
index 0000000..275bedc
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/UserReelMediaFeedResponse.php
@@ -0,0 +1,96 @@
+ 'Model\Broadcast',
+ 'reel' => 'Model\Reel',
+ 'post_live_item' => 'Model\PostLiveItem',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/UsersLookupResponse.php b/vendor/mgp25/instagram-php/src/Response/UsersLookupResponse.php
new file mode 100755
index 0000000..950bfdb
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/UsersLookupResponse.php
@@ -0,0 +1,77 @@
+ 'Model\User',
+ 'email_sent' => 'bool',
+ 'has_valid_phone' => 'bool',
+ 'can_email_reset' => 'bool',
+ 'can_sms_reset' => 'bool',
+ 'user_id' => 'string',
+ 'lookup_source' => 'string',
+ 'email' => 'string',
+ 'phone_number' => 'string',
+ 'corrected_input' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/UsertagsResponse.php b/vendor/mgp25/instagram-php/src/Response/UsertagsResponse.php
new file mode 100755
index 0000000..99fd399
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/UsertagsResponse.php
@@ -0,0 +1,67 @@
+ 'int',
+ 'auto_load_more_enabled' => '',
+ 'items' => 'Model\Item[]',
+ 'more_available' => '',
+ 'next_max_id' => 'string',
+ 'total_count' => '',
+ 'requires_review' => '',
+ 'new_photos' => '',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ValidateURLResponse.php b/vendor/mgp25/instagram-php/src/Response/ValidateURLResponse.php
new file mode 100755
index 0000000..95406a8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ValidateURLResponse.php
@@ -0,0 +1,25 @@
+ 'bool',
+ 'phone_number' => 'string',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/ViewerListResponse.php b/vendor/mgp25/instagram-php/src/Response/ViewerListResponse.php
new file mode 100755
index 0000000..75cef57
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/ViewerListResponse.php
@@ -0,0 +1,32 @@
+ 'Model\User[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Response/WriteSuppotedCapabilitiesResponse.php b/vendor/mgp25/instagram-php/src/Response/WriteSuppotedCapabilitiesResponse.php
new file mode 100755
index 0000000..494155c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Response/WriteSuppotedCapabilitiesResponse.php
@@ -0,0 +1,32 @@
+ 'Model\SupportedCapabilities[]',
+ ];
+}
diff --git a/vendor/mgp25/instagram-php/src/Settings/Factory.php b/vendor/mgp25/instagram-php/src/Settings/Factory.php
new file mode 100755
index 0000000..9f7aff2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Settings/Factory.php
@@ -0,0 +1,262 @@
+ $storage];
+ }
+
+ // Determine the user's final storage configuration.
+ switch ($storageConfig['storage']) {
+ case 'file':
+ // Look for allowed command-line values related to this backend.
+ $cmdOptions = self::getCmdOptions([
+ 'settings_basefolder::',
+ ]);
+
+ // These settings are optional:
+ $baseFolder = self::getUserConfig('basefolder', $storageConfig, $cmdOptions);
+
+ // Generate the final storage location configuration.
+ $locationConfig = [
+ 'basefolder' => $baseFolder,
+ ];
+
+ $storageInstance = new Storage\File();
+ break;
+ case 'mysql':
+ // Look for allowed command-line values related to this backend.
+ $cmdOptions = self::getCmdOptions([
+ 'settings_dbusername::',
+ 'settings_dbpassword::',
+ 'settings_dbhost::',
+ 'settings_dbname::',
+ 'settings_dbtablename::',
+ ]);
+
+ // These settings are optional, and can be provided regardless of
+ // connection method:
+ $locationConfig = [
+ 'dbtablename' => self::getUserConfig('dbtablename', $storageConfig, $cmdOptions),
+ ];
+
+ // These settings are required, but you only have to use one method:
+ if (isset($storageConfig['pdo'])) {
+ // If "pdo" is set in the factory config, assume the user wants
+ // to re-use an existing PDO connection. In that case we ignore
+ // the username/password/host/name parameters and use their PDO.
+ // NOTE: Beware that we WILL change attributes on the PDO
+ // connection to suit our needs! Primarily turning all error
+ // reporting into exceptions, and setting the charset to UTF-8.
+ // If you want to re-use a PDO connection, you MUST accept the
+ // fact that WE NEED exceptions and UTF-8 in our PDO! If that is
+ // not acceptable to you then DO NOT re-use your own PDO object!
+ $locationConfig['pdo'] = $storageConfig['pdo'];
+ } else {
+ // Make a new connection. Optional settings for it:
+ $locationConfig['dbusername'] = self::getUserConfig('dbusername', $storageConfig, $cmdOptions);
+ $locationConfig['dbpassword'] = self::getUserConfig('dbpassword', $storageConfig, $cmdOptions);
+ $locationConfig['dbhost'] = self::getUserConfig('dbhost', $storageConfig, $cmdOptions);
+ $locationConfig['dbname'] = self::getUserConfig('dbname', $storageConfig, $cmdOptions);
+ }
+
+ $storageInstance = new Storage\MySQL();
+ break;
+ case 'sqlite':
+ // Look for allowed command-line values related to this backend.
+ $cmdOptions = self::getCmdOptions([
+ 'settings_dbfilename::',
+ 'settings_dbtablename::',
+ ]);
+
+ // These settings are optional, and can be provided regardless of
+ // connection method:
+ $locationConfig = [
+ 'dbtablename' => self::getUserConfig('dbtablename', $storageConfig, $cmdOptions),
+ ];
+
+ // These settings are required, but you only have to use one method:
+ if (isset($storageConfig['pdo'])) {
+ // If "pdo" is set in the factory config, assume the user wants
+ // to re-use an existing PDO connection. In that case we ignore
+ // the SQLite filename/connection parameters and use their PDO.
+ // NOTE: Beware that we WILL change attributes on the PDO
+ // connection to suit our needs! Primarily turning all error
+ // reporting into exceptions, and setting the charset to UTF-8.
+ // If you want to re-use a PDO connection, you MUST accept the
+ // fact that WE NEED exceptions and UTF-8 in our PDO! If that is
+ // not acceptable to you then DO NOT re-use your own PDO object!
+ $locationConfig['pdo'] = $storageConfig['pdo'];
+ } else {
+ // Make a new connection. Optional settings for it:
+ $locationConfig['dbfilename'] = self::getUserConfig('dbfilename', $storageConfig, $cmdOptions);
+ }
+
+ $storageInstance = new Storage\SQLite();
+ break;
+ case 'memcached':
+ // The memcached storage can only be configured via the factory
+ // configuration array (not via command line or environment vars).
+
+ // These settings are required, but you only have to use one method:
+ if (isset($storageConfig['memcached'])) {
+ // Re-use the user's own Memcached object.
+ $locationConfig = [
+ 'memcached' => $storageConfig['memcached'],
+ ];
+ } else {
+ // Make a new connection. Optional settings for it:
+ $locationConfig = [
+ // This ID will be passed to the \Memcached() constructor.
+ // NOTE: Memcached's "persistent ID" feature makes Memcached
+ // keep the settings even after you disconnect.
+ 'persistent_id' => (isset($storageConfig['persistent_id'])
+ ? $storageConfig['persistent_id']
+ : null),
+ // Array which will be passed to \Memcached::setOptions().
+ 'memcached_options' => (isset($storageConfig['memcached_options'])
+ ? $storageConfig['memcached_options']
+ : null),
+ // Array which will be passed to \Memcached::addServers().
+ // NOTE: Can contain one or multiple servers.
+ 'servers' => (isset($storageConfig['servers'])
+ ? $storageConfig['servers']
+ : null),
+ // SASL username and password to be used for SASL
+ // authentication with all of the Memcached servers.
+ // NOTE: PHP's Memcached API doesn't support individual
+ // authentication credentials per-server, so these values
+ // apply to all of your servers if you use this feature!
+ 'sasl_username' => (isset($storageConfig['sasl_username'])
+ ? $storageConfig['sasl_username']
+ : null),
+ 'sasl_password' => (isset($storageConfig['sasl_password'])
+ ? $storageConfig['sasl_password']
+ : null),
+ ];
+ }
+
+ $storageInstance = new Storage\Memcached();
+ break;
+ case 'custom':
+ // Custom storage classes can only be configured via the main array.
+ // When using a custom class, you must provide a StorageInterface:
+ if (!isset($storageConfig['class'])
+ || !$storageConfig['class'] instanceof StorageInterface) {
+ throw new SettingsException('Invalid custom storage class.');
+ }
+
+ // Create a clean storage location configuration array.
+ $locationConfig = $storageConfig;
+ unset($locationConfig['storage']);
+ unset($locationConfig['class']);
+
+ $storageInstance = $storageConfig['class'];
+ break;
+ default:
+ throw new SettingsException(sprintf(
+ 'Unknown settings storage type "%s".',
+ $storageConfig['storage']
+ ));
+ }
+
+ // Create the storage handler and connect to the storage location.
+ return new StorageHandler(
+ $storageInstance,
+ $locationConfig,
+ $callbacks
+ );
+ }
+
+ /**
+ * Get option values via command-line parameters.
+ *
+ * @param array $longOpts The longnames for the options to look for.
+ *
+ * @return array
+ */
+ public static function getCmdOptions(
+ array $longOpts)
+ {
+ $cmdOptions = getopt('', $longOpts);
+ if (!is_array($cmdOptions)) {
+ $cmdOptions = [];
+ }
+
+ return $cmdOptions;
+ }
+
+ /**
+ * Looks for the highest-priority result for a Storage config value.
+ *
+ * @param string $settingName The name of the setting.
+ * @param array $storageConfig The Factory's configuration array.
+ * @param array $cmdOptions All parsed command-line options.
+ *
+ * @return string|null The value if found, otherwise NULL.
+ */
+ public static function getUserConfig(
+ $settingName,
+ array $storageConfig,
+ array $cmdOptions)
+ {
+ // Command line options have the highest precedence.
+ // NOTE: Settings provided via cmd must have a "settings_" prefix.
+ if (array_key_exists("settings_{$settingName}", $cmdOptions)) {
+ return $cmdOptions["settings_{$settingName}"];
+ }
+
+ // Environment variables have the second highest precedence.
+ // NOTE: Settings provided via env must be UPPERCASED and have
+ // a "SETTINGS_" prefix, for example "SETTINGS_STORAGE".
+ $envValue = getenv('SETTINGS_'.strtoupper($settingName));
+ if ($envValue !== false) {
+ return $envValue;
+ }
+
+ // Our factory config array has the lowest precedence, so that you can
+ // easily override it via the other methods when testing other storage
+ // backends or different connection parameters.
+ if (array_key_exists($settingName, $storageConfig)) {
+ return $storageConfig[$settingName];
+ }
+
+ // Couldn't find any user-provided value. Automatically returns null.
+ // NOTE: Damn you StyleCI for not allowing "return null;" for clarity.
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Settings/Storage/Components/PDOStorage.php b/vendor/mgp25/instagram-php/src/Settings/Storage/Components/PDOStorage.php
new file mode 100755
index 0000000..84730c0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Settings/Storage/Components/PDOStorage.php
@@ -0,0 +1,394 @@
+_backendName = $backendName;
+ }
+
+ /**
+ * Connect to a storage location and perform necessary startup preparations.
+ *
+ * {@inheritdoc}
+ */
+ public function openLocation(
+ array $locationConfig)
+ {
+ $this->_dbTableName = (isset($locationConfig['dbtablename'])
+ ? $locationConfig['dbtablename']
+ : 'user_sessions');
+
+ if (isset($locationConfig['pdo'])) {
+ // Pre-provided connection to re-use instead of creating a new one.
+ if (!$locationConfig['pdo'] instanceof PDO) {
+ throw new SettingsException('The custom PDO object is invalid.');
+ }
+ $this->_isSharedPDO = true;
+ $this->_pdo = $locationConfig['pdo'];
+ } else {
+ // We should connect for the user, by creating our own PDO object.
+ $this->_isSharedPDO = false;
+
+ try {
+ $this->_pdo = $this->_createPDO($locationConfig);
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Connection Failed: '.$e->getMessage());
+ }
+ }
+
+ try {
+ $this->_configurePDO();
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Configuration Failed: '.$e->getMessage());
+ }
+
+ try {
+ $this->_autoCreateTable();
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Create a new PDO connection to the database.
+ *
+ * @param array $locationConfig Configuration parameters for the location.
+ *
+ * @throws \Exception
+ *
+ * @return \PDO The database connection.
+ */
+ abstract protected function _createPDO(
+ array $locationConfig);
+
+ /**
+ * Configures the connection for our needs.
+ *
+ * Warning for those who re-used a PDO object: Beware that we WILL change
+ * attributes on the PDO connection to suit our needs! Primarily turning all
+ * error reporting into exceptions, and setting the charset to UTF-8. If you
+ * want to re-use a PDO connection, you MUST accept the fact that WE NEED
+ * exceptions and UTF-8 in our PDO! If that is not acceptable to you then DO
+ * NOT re-use your own PDO object!
+ *
+ * @throws \Exception
+ */
+ protected function _configurePDO()
+ {
+ $this->_pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+ $this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->_enableUTF8();
+ }
+
+ /**
+ * Enable UTF-8 encoding on the connection.
+ *
+ * This is database-specific and usually requires some kind of query.
+ *
+ * @throws \Exception
+ */
+ abstract protected function _enableUTF8();
+
+ /**
+ * Automatically create the database table if necessary.
+ *
+ * @throws \Exception
+ */
+ abstract protected function _autoCreateTable();
+
+ /**
+ * Automatically writes to the correct user's row and caches the new value.
+ *
+ * @param string $column The database column.
+ * @param string $data Data to be written.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _setUserColumn(
+ $column,
+ $data)
+ {
+ if ($column != 'settings' && $column != 'cookies') {
+ throw new SettingsException(sprintf(
+ 'Attempt to write to illegal database column "%s".',
+ $column
+ ));
+ }
+
+ try {
+ // Update if the user row already exists, otherwise insert.
+ $binds = [':data' => $data];
+ if ($this->_cache['id'] !== null) {
+ $sql = "UPDATE `{$this->_dbTableName}` SET {$column}=:data WHERE (id=:id)";
+ $binds[':id'] = $this->_cache['id'];
+ } else {
+ $sql = "INSERT INTO `{$this->_dbTableName}` (username, {$column}) VALUES (:username, :data)";
+ $binds[':username'] = $this->_username;
+ }
+
+ $sth = $this->_pdo->prepare($sql);
+ $sth->execute($binds);
+
+ // Keep track of the database row ID for the user.
+ if ($this->_cache['id'] === null) {
+ $this->_cache['id'] = $this->_pdo->lastinsertid();
+ }
+
+ $sth->closeCursor();
+
+ // Cache the new value.
+ $this->_cache[$column] = $data;
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUser(
+ $username)
+ {
+ // Check whether a row exists for that username.
+ $sth = $this->_pdo->prepare("SELECT EXISTS(SELECT 1 FROM `{$this->_dbTableName}` WHERE (username=:username))");
+ $sth->execute([':username' => $username]);
+ $result = $sth->fetchColumn();
+ $sth->closeCursor();
+
+ return $result > 0 ? true : false;
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * {@inheritdoc}
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ try {
+ // Verify that the old username exists.
+ if (!$this->hasUser($oldUsername)) {
+ throw new SettingsException(sprintf(
+ 'Cannot move non-existent user "%s".',
+ $oldUsername
+ ));
+ }
+
+ // Verify that the new username does not exist.
+ if ($this->hasUser($newUsername)) {
+ throw new SettingsException(sprintf(
+ 'Refusing to overwrite existing user "%s".',
+ $newUsername
+ ));
+ }
+
+ // Now attempt to rename the old username column to the new name.
+ $sth = $this->_pdo->prepare("UPDATE `{$this->_dbTableName}` SET username=:newusername WHERE (username=:oldusername)");
+ $sth->execute([':oldusername' => $oldUsername, ':newusername' => $newUsername]);
+ $sth->closeCursor();
+ } catch (SettingsException $e) {
+ throw $e; // Ugly but necessary to re-throw only our own messages.
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * {@inheritdoc}
+ */
+ public function deleteUser(
+ $username)
+ {
+ try {
+ // Just attempt to delete the row. Doesn't error if already missing.
+ $sth = $this->_pdo->prepare("DELETE FROM `{$this->_dbTableName}` WHERE (username=:username)");
+ $sth->execute([':username' => $username]);
+ $sth->closeCursor();
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Open the data storage for a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function openUser(
+ $username)
+ {
+ $this->_username = $username;
+
+ // Retrieve and cache the existing user data row if available.
+ try {
+ $sth = $this->_pdo->prepare("SELECT id, settings, cookies FROM `{$this->_dbTableName}` WHERE (username=:username)");
+ $sth->execute([':username' => $this->_username]);
+ $result = $sth->fetch(PDO::FETCH_ASSOC);
+ $sth->closeCursor();
+
+ if (is_array($result)) {
+ $this->_cache = $result;
+ } else {
+ $this->_cache = [
+ 'id' => null,
+ 'settings' => null,
+ 'cookies' => null,
+ ];
+ }
+ } catch (\Exception $e) {
+ throw new SettingsException($this->_backendName.' Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Load all settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserSettings()
+ {
+ $userSettings = [];
+
+ if (!empty($this->_cache['settings'])) {
+ $userSettings = @json_decode($this->_cache['settings'], true, 512, JSON_BIGINT_AS_STRING);
+ if (!is_array($userSettings)) {
+ throw new SettingsException(sprintf(
+ 'Failed to decode corrupt settings for account "%s".',
+ $this->_username
+ ));
+ }
+ }
+
+ return $userSettings;
+ }
+
+ /**
+ * Save the settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserSettings(
+ array $userSettings,
+ $triggerKey)
+ {
+ // Store the settings as a JSON blob.
+ $encodedData = json_encode($userSettings);
+ $this->_setUserColumn('settings', $encodedData);
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUserCookies()
+ {
+ return isset($this->_cache['cookies'])
+ && !empty($this->_cache['cookies']);
+ }
+
+ /**
+ * Get the cookiefile disk path (only if a file-based cookie jar is wanted).
+ *
+ * {@inheritdoc}
+ */
+ public function getUserCookiesFilePath()
+ {
+ // NULL = We (the backend) will handle the cookie loading/saving.
+ return null;
+ }
+
+ /**
+ * (Non-cookiefile) Load all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserCookies()
+ {
+ return isset($this->_cache['cookies'])
+ ? $this->_cache['cookies']
+ : null;
+ }
+
+ /**
+ * (Non-cookiefile) Save all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserCookies(
+ $rawData)
+ {
+ // Store the raw cookie data as-provided.
+ $this->_setUserColumn('cookies', $rawData);
+ }
+
+ /**
+ * Close the settings storage for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function closeUser()
+ {
+ $this->_username = null;
+ $this->_cache = null;
+ }
+
+ /**
+ * Disconnect from a storage location and perform necessary shutdown steps.
+ *
+ * {@inheritdoc}
+ */
+ public function closeLocation()
+ {
+ // Delete our reference to the PDO object. If nobody else references
+ // it, the PDO connection will now be terminated. In case of shared
+ // objects, the original owner still has their reference (as intended).
+ $this->_pdo = null;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Settings/Storage/File.php b/vendor/mgp25/instagram-php/src/Settings/Storage/File.php
new file mode 100755
index 0000000..289d04b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Settings/Storage/File.php
@@ -0,0 +1,385 @@
+_baseFolder = $this->_createFolder($baseFolder);
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUser(
+ $username)
+ {
+ // Check whether the user's settings-file exists.
+ $hasUser = $this->_generateUserPaths($username);
+
+ return is_file($hasUser['settingsFile']) ? true : false;
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * {@inheritdoc}
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ // Verify the old and new username parameters.
+ $oldUser = $this->_generateUserPaths($oldUsername);
+ $newUser = $this->_generateUserPaths($newUsername);
+ if (!is_dir($oldUser['userFolder'])) {
+ throw new SettingsException(sprintf(
+ 'Cannot move non-existent user folder "%s".',
+ $oldUser['userFolder']
+ ));
+ }
+ if (is_dir($newUser['userFolder'])) {
+ throw new SettingsException(sprintf(
+ 'Refusing to overwrite existing user folder "%s".',
+ $newUser['userFolder']
+ ));
+ }
+
+ // Create the new destination folder and migrate all data.
+ $this->_createFolder($newUser['userFolder']);
+ if (is_file($oldUser['settingsFile'])
+ && !@rename($oldUser['settingsFile'], $newUser['settingsFile'])) {
+ throw new SettingsException(sprintf(
+ 'Failed to move "%s" to "%s".',
+ $oldUser['settingsFile'], $newUser['settingsFile']
+ ));
+ }
+ if (is_file($oldUser['cookiesFile'])
+ && !@rename($oldUser['cookiesFile'], $newUser['cookiesFile'])) {
+ throw new SettingsException(sprintf(
+ 'Failed to move "%s" to "%s".',
+ $oldUser['cookiesFile'], $newUser['cookiesFile']
+ ));
+ }
+
+ // Delete all files in the old folder, and the folder itself.
+ Utils::deleteTree($oldUser['userFolder']);
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * {@inheritdoc}
+ */
+ public function deleteUser(
+ $username)
+ {
+ // Delete all files in the user folder, and the folder itself.
+ $delUser = $this->_generateUserPaths($username);
+ Utils::deleteTree($delUser['userFolder']);
+ }
+
+ /**
+ * Open the data storage for a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function openUser(
+ $username)
+ {
+ $this->_username = $username;
+ $userPaths = $this->_generateUserPaths($username);
+ $this->_userFolder = $userPaths['userFolder'];
+ $this->_settingsFile = $userPaths['settingsFile'];
+ $this->_cookiesFile = $userPaths['cookiesFile'];
+ $this->_createFolder($this->_userFolder);
+ }
+
+ /**
+ * Load all settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserSettings()
+ {
+ $userSettings = [];
+
+ if (!is_file($this->_settingsFile)) {
+ return $userSettings; // Nothing to load.
+ }
+
+ // Read from disk.
+ $rawData = @file_get_contents($this->_settingsFile);
+ if ($rawData === false) {
+ throw new SettingsException(sprintf(
+ 'Unable to read from settings file "%s".',
+ $this->_settingsFile
+ ));
+ }
+
+ // Fetch the data version ("FILESTORAGEv#;") header.
+ $dataVersion = 1; // Assume migration from v1 if no version.
+ if (preg_match('/^FILESTORAGEv(\d+);/', $rawData, $matches)) {
+ $dataVersion = intval($matches[1]);
+ $rawData = substr($rawData, strpos($rawData, ';') + 1);
+ }
+
+ // Decode the key-value pairs regardless of data-storage version.
+ $userSettings = $this->_decodeStorage($dataVersion, $rawData);
+
+ return $userSettings;
+ }
+
+ /**
+ * Save the settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserSettings(
+ array $userSettings,
+ $triggerKey)
+ {
+ // Generate the storage version header.
+ $versionHeader = 'FILESTORAGEv'.self::STORAGE_VERSION.';';
+
+ // Encode a binary representation of all settings.
+ // VERSION 2 STORAGE FORMAT: JSON-encoded blob.
+ $encodedData = $versionHeader.json_encode($userSettings);
+
+ // Perform an atomic diskwrite, which prevents accidental truncation.
+ // NOTE: If we had just written directly to settingsPath, the file would
+ // have become corrupted if the script was killed mid-write. The atomic
+ // write process guarantees that the data is fully written to disk.
+ Utils::atomicWrite($this->_settingsFile, $encodedData);
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUserCookies()
+ {
+ return is_file($this->_cookiesFile)
+ && filesize($this->_cookiesFile) > 0;
+ }
+
+ /**
+ * Get the cookiefile disk path (only if a file-based cookie jar is wanted).
+ *
+ * {@inheritdoc}
+ */
+ public function getUserCookiesFilePath()
+ {
+ // Tell the caller to use a file-based cookie jar.
+ return $this->_cookiesFile;
+ }
+
+ /**
+ * (Non-cookiefile) Load all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserCookies()
+ {
+ // Never called for "cookiefile" format.
+ }
+
+ /**
+ * (Non-cookiefile) Save all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserCookies(
+ $rawData)
+ {
+ // Never called for "cookiefile" format.
+ }
+
+ /**
+ * Close the settings storage for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function closeUser()
+ {
+ $this->_userFolder = null;
+ $this->_settingsFile = null;
+ $this->_cookiesFile = null;
+ $this->_username = null;
+ }
+
+ /**
+ * Disconnect from a storage location and perform necessary shutdown steps.
+ *
+ * {@inheritdoc}
+ */
+ public function closeLocation()
+ {
+ // We don't need to disconnect from anything since we are file-based.
+ }
+
+ /**
+ * Decodes the data from any File storage format version.
+ *
+ * @param int $dataVersion Which data format to decode.
+ * @param string $rawData The raw data, encoded in version's format.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array An array with all current key-value pairs for the user.
+ */
+ private function _decodeStorage(
+ $dataVersion,
+ $rawData)
+ {
+ $loadedSettings = [];
+
+ switch ($dataVersion) {
+ case 1:
+ /**
+ * This is the old format from v1.x of Instagram-API.
+ * Terrible format. Basic "key=value\r\n" and very fragile.
+ */
+
+ // Split by system-independent newlines. Tries \r\n (Win), then \r
+ // (pre-2000s Mac), then \n\r, then \n (Mac OS X, UNIX, Linux).
+ $lines = preg_split('/(\r\n?|\n\r?)/', $rawData, -1, PREG_SPLIT_NO_EMPTY);
+ if ($lines !== false) {
+ foreach ($lines as $line) {
+ // Key must be at least one character. Allows empty values.
+ if (preg_match('/^([^=]+)=(.*)$/', $line, $matches)) {
+ $key = $matches[1];
+ $value = rtrim($matches[2], "\r\n ");
+ $loadedSettings[$key] = $value;
+ }
+ }
+ }
+ break;
+ case 2:
+ /**
+ * Version 2 uses JSON encoding and perfectly stores any value.
+ * And file corruption can't happen, thanks to the atomic writer.
+ */
+ $loadedSettings = @json_decode($rawData, true, 512, JSON_BIGINT_AS_STRING);
+ if (!is_array($loadedSettings)) {
+ throw new SettingsException(sprintf(
+ 'Failed to decode corrupt settings file for account "%s".',
+ $this->_username
+ ));
+ }
+ break;
+ default:
+ throw new SettingsException(sprintf(
+ 'Invalid file settings storage format version "%d".',
+ $dataVersion
+ ));
+ }
+
+ return $loadedSettings;
+ }
+
+ /**
+ * Generates all path strings for a given username.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @return array An array with information about the user's paths.
+ */
+ private function _generateUserPaths(
+ $username)
+ {
+ $userFolder = $this->_baseFolder.'/'.$username;
+ $settingsFile = $userFolder.'/'.sprintf(self::SETTINGSFILE_NAME, $username);
+ $cookiesFile = $userFolder.'/'.sprintf(self::COOKIESFILE_NAME, $username);
+
+ return [
+ 'userFolder' => $userFolder,
+ 'settingsFile' => $settingsFile,
+ 'cookiesFile' => $cookiesFile,
+ ];
+ }
+
+ /**
+ * Creates a folder if missing, or ensures that it is writable.
+ *
+ * @param string $folder The directory path.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string The canonicalized absolute pathname of the folder, without
+ * any trailing slash.
+ */
+ private function _createFolder(
+ $folder)
+ {
+ if (!Utils::createFolder($folder)) {
+ throw new SettingsException(sprintf(
+ 'The "%s" folder is not writable.',
+ $folder
+ ));
+ }
+
+ // Determine the real path of the folder we created/checked.
+ // NOTE: This ensures that the path will work even on stingy systems
+ // such as Windows Server which chokes on multiple slashes in a row.
+ $realPath = @realpath($folder);
+ if (!is_string($realPath)) {
+ throw new SettingsException(sprintf(
+ 'Unable to resolve real path to folder "%s".',
+ $folder
+ ));
+ }
+
+ return $realPath;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Settings/Storage/Memcached.php b/vendor/mgp25/instagram-php/src/Settings/Storage/Memcached.php
new file mode 100755
index 0000000..b14ce3b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Settings/Storage/Memcached.php
@@ -0,0 +1,345 @@
+_isSharedMemcached = true;
+ $this->_memcached = $locationConfig['memcached'];
+ } else {
+ try {
+ // Configure memcached with a persistent data retention ID.
+ $this->_isSharedMemcached = false;
+ $this->_memcached = new PHPMemcached(
+ is_string($locationConfig['persistent_id'])
+ ? $locationConfig['persistent_id']
+ : 'instagram'
+ );
+
+ // Enable SASL authentication if credentials were provided.
+ // NOTE: PHP's Memcached API doesn't support individual
+ // authentication credentials per-server!
+ if (isset($locationConfig['sasl_username'])
+ && isset($locationConfig['sasl_password'])) {
+ // When SASL is used, the servers almost always NEED binary
+ // protocol, but if that doesn't work with the user's server
+ // then then the user can manually override this default by
+ // providing the "false" option via "memcached_options".
+ $this->_memcached->setOption(PHPMemcached::OPT_BINARY_PROTOCOL, true);
+ $this->_memcached->setSaslAuthData(
+ $locationConfig['sasl_username'],
+ $locationConfig['sasl_password']
+ );
+ }
+
+ // Apply any custom options the user has provided.
+ // NOTE: This is where "OPT_BINARY_PROTOCOL" can be overridden.
+ if (is_array($locationConfig['memcached_options'])) {
+ $this->_memcached->setOptions($locationConfig['memcached_options']);
+ }
+
+ // Add the provided servers to the pool.
+ if (is_array($locationConfig['servers'])) {
+ $this->_memcached->addServers($locationConfig['servers']);
+ } else {
+ // Use default port on localhost if nothing was provided.
+ $this->_memcached->addServer('localhost', 11211);
+ }
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Connection Failed: '.$e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * Retrieve a memcached key for a particular user.
+ *
+ * @param string $username The Instagram username.
+ * @param string $key Name of the subkey.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string|null The value as a string IF the user's key exists,
+ * otherwise NULL.
+ */
+ private function _getUserKey(
+ $username,
+ $key)
+ {
+ try {
+ $realKey = $username.'_'.$key;
+ $result = $this->_memcached->get($realKey);
+
+ return $this->_memcached->getResultCode() !== PHPMemcached::RES_NOTFOUND
+ ? (string) $result
+ : null;
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Set a memcached key for a particular user.
+ *
+ * @param string $username The Instagram username.
+ * @param string $key Name of the subkey.
+ * @param string|mixed $value The data to store. MUST be castable to string.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ private function _setUserKey(
+ $username,
+ $key,
+ $value)
+ {
+ try {
+ $realKey = $username.'_'.$key;
+ $success = $this->_memcached->set($realKey, (string) $value);
+ if (!$success) {
+ throw new SettingsException(sprintf(
+ 'Memcached failed to write to key "%s".',
+ $realKey
+ ));
+ }
+ } catch (SettingsException $e) {
+ throw $e; // Ugly but necessary to re-throw only our own messages.
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Delete a memcached key for a particular user.
+ *
+ * @param string $username The Instagram username.
+ * @param string $key Name of the subkey.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ private function _delUserKey(
+ $username,
+ $key)
+ {
+ try {
+ $realKey = $username.'_'.$key;
+ $this->_memcached->delete($realKey);
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Error: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUser(
+ $username)
+ {
+ // Check whether the user's settings exist (empty string allowed).
+ $hasUser = $this->_getUserKey($username, 'settings');
+
+ return $hasUser !== null ? true : false;
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * {@inheritdoc}
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ // Verify that the old username exists and fetch the old data.
+ $oldSettings = $this->_getUserKey($oldUsername, 'settings');
+ $oldCookies = $this->_getUserKey($oldUsername, 'cookies');
+ if ($oldSettings === null) { // Only settings are vital.
+ throw new SettingsException(sprintf(
+ 'Cannot move non-existent user "%s".',
+ $oldUsername
+ ));
+ }
+
+ // Verify that the new username does not exist.
+ if ($this->hasUser($newUsername)) {
+ throw new SettingsException(sprintf(
+ 'Refusing to overwrite existing user "%s".',
+ $newUsername
+ ));
+ }
+
+ // Now attempt to write all data to the new name.
+ $this->_setUserKey($newUsername, 'settings', $oldSettings);
+ if ($oldCookies !== null) { // Only if cookies existed.
+ $this->_setUserKey($newUsername, 'cookies', $oldCookies);
+ }
+
+ // Delete the previous user keys.
+ $this->deleteUser($oldUsername);
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * {@inheritdoc}
+ */
+ public function deleteUser(
+ $username)
+ {
+ $this->_delUserKey($username, 'settings');
+ $this->_delUserKey($username, 'cookies');
+ }
+
+ /**
+ * Open the data storage for a specific user.
+ *
+ * {@inheritdoc}
+ */
+ public function openUser(
+ $username)
+ {
+ // Just cache the username. We'll create storage later if necessary.
+ $this->_username = $username;
+ }
+
+ /**
+ * Load all settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserSettings()
+ {
+ $userSettings = [];
+
+ $encodedData = $this->_getUserKey($this->_username, 'settings');
+ if (!empty($encodedData)) {
+ $userSettings = @json_decode($encodedData, true, 512, JSON_BIGINT_AS_STRING);
+ if (!is_array($userSettings)) {
+ throw new SettingsException(sprintf(
+ 'Failed to decode corrupt settings for account "%s".',
+ $this->_username
+ ));
+ }
+ }
+
+ return $userSettings;
+ }
+
+ /**
+ * Save the settings for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserSettings(
+ array $userSettings,
+ $triggerKey)
+ {
+ // Store the settings as a JSON blob.
+ $encodedData = json_encode($userSettings);
+ $this->_setUserKey($this->_username, 'settings', $encodedData);
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function hasUserCookies()
+ {
+ // Simply check if the storage key for cookies exists and is non-empty.
+ return !empty($this->loadUserCookies()) ? true : false;
+ }
+
+ /**
+ * Get the cookiefile disk path (only if a file-based cookie jar is wanted).
+ *
+ * {@inheritdoc}
+ */
+ public function getUserCookiesFilePath()
+ {
+ // NULL = We (the backend) will handle the cookie loading/saving.
+ return null;
+ }
+
+ /**
+ * (Non-cookiefile) Load all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function loadUserCookies()
+ {
+ return $this->_getUserKey($this->_username, 'cookies');
+ }
+
+ /**
+ * (Non-cookiefile) Save all cookies for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function saveUserCookies(
+ $rawData)
+ {
+ // Store the raw cookie data as-provided.
+ $this->_setUserKey($this->_username, 'cookies', $rawData);
+ }
+
+ /**
+ * Close the settings storage for the currently active user.
+ *
+ * {@inheritdoc}
+ */
+ public function closeUser()
+ {
+ $this->_username = null;
+ }
+
+ /**
+ * Disconnect from a storage location and perform necessary shutdown steps.
+ *
+ * {@inheritdoc}
+ */
+ public function closeLocation()
+ {
+ // Close all server connections if this was our own Memcached object.
+ if (!$this->_isSharedMemcached) {
+ try {
+ $this->_memcached->quit();
+ } catch (\Exception $e) {
+ throw new SettingsException('Memcached Disconnection Failed: '.$e->getMessage());
+ }
+ }
+ $this->_memcached = null;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Settings/Storage/MySQL.php b/vendor/mgp25/instagram-php/src/Settings/Storage/MySQL.php
new file mode 100755
index 0000000..0d0a030
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Settings/Storage/MySQL.php
@@ -0,0 +1,101 @@
+_pdo->query("SET NAMES 'utf8mb4'")->closeCursor();
+ }
+
+ /**
+ * Automatically create the database table if necessary.
+ *
+ * {@inheritdoc}
+ */
+ protected function _autoCreateTable()
+ {
+ // Detect the name of the MySQL database that PDO is connected to.
+ $dbName = $this->_pdo->query('SELECT database()')->fetchColumn();
+
+ // Abort if we already have the necessary table.
+ $sth = $this->_pdo->prepare('SELECT count(*) FROM information_schema.TABLES WHERE (TABLE_SCHEMA = :tableSchema) AND (TABLE_NAME = :tableName)');
+ $sth->execute([':tableSchema' => $dbName, ':tableName' => $this->_dbTableName]);
+ $result = $sth->fetchColumn();
+ $sth->closeCursor();
+ if ($result > 0) {
+ return;
+ }
+
+ // Create the database table. Throws in case of failure.
+ // NOTE: We store all settings as a binary JSON blob so that we support
+ // all current and future data without having to alter the table schema.
+ // NOTE: The username is a 150-character-max varchar, NOT 255! Why?
+ // Because MySQL has a 767-byte max limit for efficient indexes, and
+ // "utf8mb4" uses 4 bytes per character, which means that 191 characters
+ // is the maximum safe amount (191 * 4 = 764)! We chose 150 as a nice
+ // number. Instagram's username limit is 30, so our limit is fine!
+ // NOTE: We use "utf8mb4_general_ci" which performs fast, general
+ // sorting, since we have no need for language-aware "unicode" sorting.
+ // NOTE: Lastly... note that our encoding only affects the "username"
+ // column. All other columns are numbers, binary blobs, etc!
+ $this->_pdo->exec('CREATE TABLE `'.$this->_dbTableName.'` (
+ id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ username VARCHAR(150) NOT NULL,
+ settings MEDIUMBLOB NULL,
+ cookies MEDIUMBLOB NULL,
+ last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY (username)
+ ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ENGINE=InnoDB;');
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Settings/Storage/SQLite.php b/vendor/mgp25/instagram-php/src/Settings/Storage/SQLite.php
new file mode 100755
index 0000000..a32e254
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Settings/Storage/SQLite.php
@@ -0,0 +1,115 @@
+_pdo->query('PRAGMA encoding = "UTF-8"')->closeCursor();
+ }
+
+ /**
+ * Automatically create the database table if necessary.
+ *
+ * {@inheritdoc}
+ */
+ protected function _autoCreateTable()
+ {
+ // Abort if we already have the necessary table.
+ $sth = $this->_pdo->prepare('SELECT count(*) FROM sqlite_master WHERE (type = "table") AND (name = :tableName)');
+ $sth->execute([':tableName' => $this->_dbTableName]);
+ $result = $sth->fetchColumn();
+ $sth->closeCursor();
+ if ($result > 0) {
+ return;
+ }
+
+ // Create the database table. Throws in case of failure.
+ // NOTE: We store all settings as a JSON blob so that we support all
+ // current and future data without having to alter the table schema.
+ // NOTE: SQLite automatically increments "integer primary key" cols.
+ $this->_pdo->exec('CREATE TABLE `'.$this->_dbTableName.'` (
+ id INTEGER PRIMARY KEY NOT NULL,
+ username TEXT NOT NULL UNIQUE,
+ settings BLOB,
+ cookies BLOB,
+ last_modified DATETIME DEFAULT CURRENT_TIMESTAMP
+ );');
+
+ // Set up a trigger to automatically update the modification timestamp.
+ // NOTE: The WHEN clause is important to avoid infinite recursive loops,
+ // otherwise you'll get "Error: too many levels of trigger recursion" if
+ // recursive triggers are enabled in SQLite. The WHEN constraint simply
+ // ensures that we only update last_modified automatically after UPDATEs
+ // that did NOT change last_modified. So our own UPDATEs of other fields
+ // will trigger this automatic UPDATE, which does an UPDATE with a NEW
+ // last_modified value, meaning that the trigger won't execute again!
+ $this->_pdo->exec('CREATE TRIGGER IF NOT EXISTS `'.$this->_dbTableName.'_update_last_modified`
+ AFTER UPDATE
+ ON `'.$this->_dbTableName.'`
+ FOR EACH ROW
+ WHEN NEW.last_modified = OLD.last_modified -- Avoids infinite loop.
+ BEGIN
+ UPDATE `'.$this->_dbTableName.'` SET last_modified=CURRENT_TIMESTAMP WHERE (id=OLD.id);
+ END;'
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Settings/StorageHandler.php b/vendor/mgp25/instagram-php/src/Settings/StorageHandler.php
new file mode 100755
index 0000000..95b970b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Settings/StorageHandler.php
@@ -0,0 +1,803 @@
+_callbacks = $callbacks;
+
+ // Connect the storage instance to the user's desired storage location.
+ $this->_storage = $storageInstance;
+ $this->_storage->openLocation($locationConfig);
+ }
+
+ /**
+ * Destructor.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function __destruct()
+ {
+ // The storage handler is being killed, so tell the location to close.
+ if ($this->_username !== null) {
+ $this->_triggerCallback('onCloseUser');
+ $this->_storage->closeUser();
+ $this->_username = null;
+ }
+ $this->_storage->closeLocation();
+ }
+
+ /**
+ * Whether the storage backend contains a specific user.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return bool TRUE if user exists, otherwise FALSE.
+ */
+ public function hasUser(
+ $username)
+ {
+ $this->_throwIfEmptyValue($username);
+
+ return $this->_storage->hasUser($username);
+ }
+
+ /**
+ * Move the internal data for a username to a new username.
+ *
+ * This function is important because of the fact that all per-user settings
+ * in all Storage implementations are retrieved and stored via its Instagram
+ * username, since their NAME is literally the ONLY thing we know about a
+ * user before we have loaded their settings or logged in! So if you later
+ * rename that Instagram account, it means that your old device settings
+ * WON'T follow along automatically, since the new login username is seen
+ * as a brand new user that isn't in the settings storage.
+ *
+ * This function conveniently tells your chosen Storage backend to move a
+ * user's settings to a new name, so that they WILL be found again when you
+ * later look for settings for your new name.
+ *
+ * Bonus guide for easily confused people: YOU must manually rename your
+ * user on Instagram.com before you call this function. We don't do that.
+ *
+ * @param string $oldUsername The old name that settings are stored as.
+ * @param string $newUsername The new name to move the settings to.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function moveUser(
+ $oldUsername,
+ $newUsername)
+ {
+ $this->_throwIfEmptyValue($oldUsername);
+ $this->_throwIfEmptyValue($newUsername);
+
+ if ($oldUsername === $this->_username
+ || $newUsername === $this->_username) {
+ throw new SettingsException(
+ 'Attempted to move settings to/from the currently active user.'
+ );
+ }
+
+ $this->_storage->moveUser($oldUsername, $newUsername);
+ }
+
+ /**
+ * Delete all internal data for a given username.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function deleteUser(
+ $username)
+ {
+ $this->_throwIfEmptyValue($username);
+
+ if ($username === $this->_username) {
+ throw new SettingsException(
+ 'Attempted to delete the currently active user.'
+ );
+ }
+
+ $this->_storage->deleteUser($username);
+ }
+
+ /**
+ * Load all settings for a user from the storage and mark as current user.
+ *
+ * @param string $username The Instagram username.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function setActiveUser(
+ $username)
+ {
+ $this->_throwIfEmptyValue($username);
+
+ // If that user is already loaded, there's no need to do anything.
+ if ($username === $this->_username) {
+ return;
+ }
+
+ // If we're switching away from a user, tell the backend to close the
+ // current user's storage (if it needs to do any special processing).
+ if ($this->_username !== null) {
+ $this->_triggerCallback('onCloseUser');
+ $this->_storage->closeUser();
+ }
+
+ // Set the new user as the current user for this storage instance.
+ $this->_username = $username;
+ $this->_userSettings = [];
+ $this->_storage->openUser($username);
+
+ // Retrieve any existing settings for the user from the backend.
+ $loadedSettings = $this->_storage->loadUserSettings();
+ foreach ($loadedSettings as $key => $value) {
+ // Map renamed old-school keys to new key names.
+ if ($key == 'username_id') {
+ $key = 'account_id';
+ } elseif ($key == 'adid') {
+ $key = 'advertising_id';
+ }
+
+ // Only keep values for keys that are still in use. Discard others.
+ if (in_array($key, self::PERSISTENT_KEYS)) {
+ // Cast all values to strings to ensure we only use strings!
+ // NOTE: THIS CAST IS EXTREMELY IMPORTANT AND *MUST* BE DONE!
+ $this->_userSettings[$key] = (string) $value;
+ }
+ }
+
+ // Determine what type of cookie storage the backend wants for the user.
+ // NOTE: Do NOT validate file existence, since we'll create if missing.
+ $cookiesFilePath = $this->_storage->getUserCookiesFilePath();
+ if ($cookiesFilePath !== null && (!is_string($cookiesFilePath) || !strlen($cookiesFilePath))) {
+ $cookiesFilePath = null; // Disable since it isn't a non-empty string.
+ }
+ $this->_cookiesFilePath = $cookiesFilePath;
+ }
+
+ /**
+ * Does a preliminary guess about whether the current user is logged in.
+ *
+ * Can only be executed after setActiveUser(). And the session it looks
+ * for may be expired, so there's no guarantee that we are still logged in.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return bool TRUE if possibly logged in, otherwise FALSE.
+ */
+ public function isMaybeLoggedIn()
+ {
+ $this->_throwIfNoActiveUser();
+
+ return $this->_storage->hasUserCookies()
+ && !empty($this->get('account_id'));
+ }
+
+ /**
+ * Erase all device-specific settings and all cookies.
+ *
+ * This is useful when assigning a new Android device to the account, upon
+ * which it's very important that we erase all previous, device-specific
+ * settings so that our account still looks natural to Instagram.
+ *
+ * Note that ALL cookies will be erased too, to clear out the old session.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function eraseDeviceSettings()
+ {
+ foreach (self::PERSISTENT_KEYS as $key) {
+ if (!in_array($key, self::KEEP_KEYS_WHEN_ERASING_DEVICE)) {
+ $this->set($key, ''); // Erase the setting.
+ }
+ }
+
+ $this->setCookies(''); // Erase all cookies.
+ }
+
+ /**
+ * Retrieve the value of a setting from the current user's memory cache.
+ *
+ * Can only be executed after setActiveUser().
+ *
+ * @param string $key Name of the setting.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string|null The value as a string IF the setting exists AND is
+ * a NON-EMPTY string. Otherwise NULL.
+ */
+ public function get(
+ $key)
+ {
+ $this->_throwIfNoActiveUser();
+
+ // Reject anything that isn't in our list of VALID persistent keys.
+ if (!in_array($key, self::PERSISTENT_KEYS)) {
+ throw new SettingsException(sprintf(
+ 'The settings key "%s" is not a valid persistent key name.',
+ $key
+ ));
+ }
+
+ // Return value if it's a NON-EMPTY string, otherwise return NULL.
+ // NOTE: All values are cached as strings so no casting is needed.
+ return (isset($this->_userSettings[$key])
+ && $this->_userSettings[$key] !== '')
+ ? $this->_userSettings[$key]
+ : null;
+ }
+
+ /**
+ * Store a setting's value for the current user.
+ *
+ * Can only be executed after setActiveUser(). To clear the value of a
+ * setting, simply pass in an empty string as value.
+ *
+ * @param string $key Name of the setting.
+ * @param string|mixed $value The data to store. MUST be castable to string.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function set(
+ $key,
+ $value)
+ {
+ $this->_throwIfNoActiveUser();
+
+ // Reject anything that isn't in our list of VALID persistent keys.
+ if (!in_array($key, self::PERSISTENT_KEYS)) {
+ throw new SettingsException(sprintf(
+ 'The settings key "%s" is not a valid persistent key name.',
+ $key
+ ));
+ }
+
+ // Reject null values, since they may be accidental. To unset a setting,
+ // the caller must explicitly pass in an empty string instead.
+ if ($value === null) {
+ throw new SettingsException(
+ 'Illegal attempt to store null value in settings storage.'
+ );
+ }
+
+ // Cast the value to string to ensure we don't try writing non-strings.
+ // NOTE: THIS CAST IS EXTREMELY IMPORTANT AND *MUST* ALWAYS BE DONE!
+ $value = (string) $value;
+
+ // Check if the value differs from our storage (cached representation).
+ // NOTE: This optimizes writes by only writing when values change!
+ if (!array_key_exists($key, $this->_userSettings)
+ || $this->_userSettings[$key] !== $value) {
+ // The value differs, so save to memory cache and write to storage.
+ $this->_userSettings[$key] = $value;
+ $this->_storage->saveUserSettings($this->_userSettings, $key);
+ }
+ }
+
+ /**
+ * Whether the storage backend has cookies for the currently active user.
+ *
+ * Can only be executed after setActiveUser().
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return bool TRUE if cookies exist, otherwise FALSE.
+ */
+ public function hasCookies()
+ {
+ $this->_throwIfNoActiveUser();
+
+ return $this->_storage->hasUserCookies();
+ }
+
+ /**
+ * Get all cookies for the currently active user.
+ *
+ * Can only be executed after setActiveUser().
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return string|null A previously-stored, raw cookie data string
+ * (non-empty), or NULL if no cookies exist for
+ * the active user.
+ */
+ public function getCookies()
+ {
+ $this->_throwIfNoActiveUser();
+
+ // Read the cookies via the appropriate backend method.
+ $userCookies = null;
+ if ($this->_cookiesFilePath === null) { // Backend storage.
+ $userCookies = $this->_storage->loadUserCookies();
+ } else { // Cookiefile on disk.
+ if (empty($this->_cookiesFilePath)) { // Just for extra safety.
+ throw new SettingsException(
+ 'Cookie file format requested, but no file path provided.'
+ );
+ }
+
+ // Ensure that the cookie file's folder exists and is writable.
+ $this->_createCookiesFileDirectory();
+
+ // Read the existing cookie jar file if it already exists.
+ if (is_file($this->_cookiesFilePath)) {
+ $rawData = file_get_contents($this->_cookiesFilePath);
+ if ($rawData !== false) {
+ $userCookies = $rawData;
+ }
+ }
+ }
+
+ // Ensure that we'll always return NULL if no cookies exist.
+ if ($userCookies !== null && !strlen($userCookies)) {
+ $userCookies = null;
+ }
+
+ return $userCookies;
+ }
+
+ /**
+ * Save all cookies for the currently active user.
+ *
+ * Can only be executed after setActiveUser(). Note that this function is
+ * called frequently!
+ *
+ * NOTE: It is very important that the owner of this SettingsHandler either
+ * continuously calls "setCookies", or better yet listens to the "closeUser"
+ * callback to save all cookies in bulk to storage at the end of a session.
+ *
+ * @param string $rawData An encoded string with all cookie data. Use an
+ * empty string to erase currently stored cookies.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function setCookies(
+ $rawData)
+ {
+ $this->_throwIfNoActiveUser();
+ $this->_throwIfNotString($rawData);
+
+ if ($this->_cookiesFilePath === null) { // Backend storage.
+ $this->_storage->saveUserCookies($rawData);
+ } else { // Cookiefile on disk.
+ if (strlen($rawData)) { // Update cookies (new value is non-empty).
+ // Perform an atomic diskwrite, which prevents accidental
+ // truncation if the script is ever interrupted mid-write.
+ $this->_createCookiesFileDirectory(); // Ensures dir exists.
+ $timeout = 5;
+ $init = time();
+ while (!$written = Utils::atomicWrite($this->_cookiesFilePath, $rawData)) {
+ usleep(mt_rand(400000, 600000)); // 0.4-0.6 sec
+ if (time() - $init > $timeout) {
+ break;
+ }
+ }
+ if ($written === false) {
+ throw new SettingsException(sprintf(
+ 'The "%s" cookie file is not writable.',
+ $this->_cookiesFilePath
+ ));
+ }
+ } else { // Delete cookies (empty string).
+ // Delete any existing cookie jar since the new data is empty.
+ if (is_file($this->_cookiesFilePath) && !@unlink($this->_cookiesFilePath)) {
+ throw new SettingsException(sprintf(
+ 'Unable to delete the "%s" cookie file.',
+ $this->_cookiesFilePath
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Ensures the whole directory path to the cookie file exists/is writable.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _createCookiesFileDirectory()
+ {
+ if ($this->_cookiesFilePath === null) {
+ return;
+ }
+
+ $cookieDir = dirname($this->_cookiesFilePath); // Can be "." in case of CWD.
+ if (!Utils::createFolder($cookieDir)) {
+ throw new SettingsException(sprintf(
+ 'The "%s" cookie folder is not writable.',
+ $cookieDir
+ ));
+ }
+ }
+
+ /**
+ * Internal: Ensures that a parameter is a string.
+ *
+ * @param mixed $value The value to check.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _throwIfNotString(
+ $value)
+ {
+ if (!is_string($value)) {
+ throw new SettingsException('Parameter must be string.');
+ }
+ }
+
+ /**
+ * Internal: Ensures that a parameter is a non-empty string.
+ *
+ * @param mixed $value The value to check.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _throwIfEmptyValue(
+ $value)
+ {
+ if (!is_string($value) || $value === '') {
+ throw new SettingsException('Parameter must be non-empty string.');
+ }
+ }
+
+ /**
+ * Internal: Ensures that there is an active storage user.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _throwIfNoActiveUser()
+ {
+ if ($this->_username === null) {
+ throw new SettingsException(
+ 'Called user-related function before setting the current storage user.'
+ );
+ }
+ }
+
+ /**
+ * Internal: Triggers a callback.
+ *
+ * All callback functions are given the storage handler instance as their
+ * one and only argument.
+ *
+ * @param string $cbName The name of the callback.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ protected function _triggerCallback(
+ $cbName)
+ {
+ // Reject anything that isn't in our list of VALID callbacks.
+ if (!in_array($cbName, self::SUPPORTED_CALLBACKS)) {
+ throw new SettingsException(sprintf(
+ 'The string "%s" is not a valid callback name.',
+ $cbName
+ ));
+ }
+
+ // Trigger the callback with a reference to our StorageHandler instance.
+ if (isset($this->_callbacks[$cbName])) {
+ try {
+ $this->_callbacks[$cbName]($this);
+ } catch (\Exception $e) {
+ // Re-wrap anything that isn't already a SettingsException.
+ if (!$e instanceof SettingsException) {
+ $e = new SettingsException($e->getMessage());
+ }
+
+ throw $e; // Re-throw;
+ }
+ }
+ }
+
+ /**
+ * Process and save experiments.
+ *
+ * @param array $experiments
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array A list of "good" experiments.
+ */
+ public function setExperiments(
+ array $experiments)
+ {
+ $filtered = [];
+ foreach (self::EXPERIMENT_KEYS as $key) {
+ if (!isset($experiments[$key])) {
+ continue;
+ }
+ $filtered[$key] = $experiments[$key];
+ }
+ $this->set('experiments', $this->_packJson($filtered));
+
+ return $filtered;
+ }
+
+ /**
+ * Return saved experiments.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array
+ */
+ public function getExperiments()
+ {
+ return $this->_unpackJson($this->get('experiments'), true);
+ }
+
+ /**
+ * Save rewrite rules.
+ *
+ * @param array $rules
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ */
+ public function setRewriteRules(
+ array $rules)
+ {
+ $this->set('zr_rules', $this->_packJson($rules));
+ }
+
+ /**
+ * Return saved rewrite rules.
+ *
+ * @throws \InstagramAPI\Exception\SettingsException
+ *
+ * @return array
+ */
+ public function getRewriteRules()
+ {
+ return $this->_unpackJson((string) $this->get('zr_rules'), true);
+ }
+
+ /**
+ * Save FBNS authorization.
+ *
+ * @param AuthInterface $auth
+ */
+ public function setFbnsAuth(
+ AuthInterface $auth)
+ {
+ $this->set('fbns_auth', $auth);
+ }
+
+ /**
+ * Get FBNS authorization.
+ *
+ * Will restore previously saved auth details if they exist. Otherwise it
+ * creates random new authorization details.
+ *
+ * @return AuthInterface
+ */
+ public function getFbnsAuth()
+ {
+ $result = new DeviceAuth();
+
+ try {
+ $result->read($this->get('fbns_auth'));
+ } catch (\Exception $e) {
+ }
+
+ return $result;
+ }
+
+ /**
+ * Pack data as JSON, deflating it when it saves some space.
+ *
+ * @param array|object $data
+ *
+ * @return string
+ */
+ protected function _packJson(
+ $data)
+ {
+ $json = json_encode($data);
+ $gzipped = base64_encode(zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9));
+ // We must compare gzipped with double encoded JSON.
+ $doubleJson = json_encode($json);
+ if (strlen($gzipped) < strlen($doubleJson)) {
+ $serialized = 'Z'.$gzipped;
+ } else {
+ $serialized = 'J'.$json;
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * Unpacks data from JSON encoded string, inflating it when necessary.
+ *
+ * @param string $packed
+ * @param bool $assoc
+ *
+ * @return array|object
+ */
+ protected function _unpackJson(
+ $packed,
+ $assoc = true)
+ {
+ if ($packed === null || $packed === '') {
+ return $assoc ? [] : new \stdClass();
+ }
+ $format = $packed[0];
+ $packed = substr($packed, 1);
+
+ try {
+ switch ($format) {
+ case 'Z':
+ $packed = base64_decode($packed, true);
+ if ($packed === false) {
+ throw new \RuntimeException('Invalid Base64 encoded string.');
+ }
+ $json = @zlib_decode($packed);
+ if ($json === false) {
+ throw new \RuntimeException('Invalid zlib encoded string.');
+ }
+ break;
+ case 'J':
+ $json = $packed;
+ break;
+ default:
+ throw new \RuntimeException('Invalid packed type.');
+ }
+ $data = json_decode($json, $assoc);
+ if ($assoc && !is_array($data)) {
+ throw new \RuntimeException('JSON is not an array.');
+ }
+ if (!$assoc && !is_object($data)) {
+ throw new \RuntimeException('JSON is not an object.');
+ }
+ } catch (\RuntimeException $e) {
+ $data = $assoc ? [] : new \stdClass();
+ }
+
+ return $data;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/src/Settings/StorageInterface.php b/vendor/mgp25/instagram-php/src/Settings/StorageInterface.php
new file mode 100755
index 0000000..239f356
--- /dev/null
+++ b/vendor/mgp25/instagram-php/src/Settings/StorageInterface.php
@@ -0,0 +1,264 @@
+ 4) {
+ if ($result > 0x7FFFFFFF) {
+ $result -= 0x100000000;
+ } elseif ($result < -0x80000000) {
+ $result += 0x100000000;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Reorders array by hashCode() of its keys.
+ *
+ * @param array $data
+ *
+ * @return array
+ */
+ public static function reorderByHashCode(
+ array $data)
+ {
+ $hashCodes = [];
+ foreach ($data as $key => $value) {
+ $hashCodes[$key] = self::hashCode($key);
+ }
+
+ uksort($data, function ($a, $b) use ($hashCodes) {
+ $a = $hashCodes[$a];
+ $b = $hashCodes[$b];
+ if ($a < $b) {
+ return -1;
+ } elseif ($a > $b) {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+
+ return $data;
+ }
+
+ /**
+ * Generates random multipart boundary string.
+ *
+ * @return string
+ */
+ public static function generateMultipartBoundary()
+ {
+ $result = '';
+ $max = strlen(self::BOUNDARY_CHARS) - 1;
+ for ($i = 0; $i < self::BOUNDARY_LENGTH; ++$i) {
+ $result .= self::BOUNDARY_CHARS[mt_rand(0, $max)];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generates user breadcrumb for use when posting a comment.
+ *
+ * @param int $size
+ *
+ * @return string
+ */
+ public static function generateUserBreadcrumb(
+ $size)
+ {
+ $key = 'iN4$aGr0m';
+ $date = (int) (microtime(true) * 1000);
+
+ // typing time
+ $term = rand(2, 3) * 1000 + $size * rand(15, 20) * 100;
+
+ // android EditText change event occur count
+ $text_change_event_count = round($size / rand(2, 3));
+ if ($text_change_event_count == 0) {
+ $text_change_event_count = 1;
+ }
+
+ // generate typing data
+ $data = $size.' '.$term.' '.$text_change_event_count.' '.$date;
+
+ return base64_encode(hash_hmac('sha256', $data, $key, true))."\n".base64_encode($data)."\n";
+ }
+
+ /**
+ * Converts a hours/minutes/seconds timestamp to seconds.
+ *
+ * @param string $timeStr Either `HH:MM:SS[.###]` (24h-clock) or
+ * `MM:SS[.###]` or `SS[.###]`. The `[.###]` is for
+ * optional millisecond precision if wanted, such as
+ * `00:01:01.149`.
+ *
+ * @throws \InvalidArgumentException If any part of the input is invalid.
+ *
+ * @return float The number of seconds, with decimals (milliseconds).
+ */
+ public static function hmsTimeToSeconds(
+ $timeStr)
+ {
+ if (!is_string($timeStr)) {
+ throw new \InvalidArgumentException('Invalid non-string timestamp.');
+ }
+
+ $sec = 0.0;
+ foreach (array_reverse(explode(':', $timeStr)) as $offsetKey => $v) {
+ if ($offsetKey > 2) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid input "%s" with too many components (max 3 is allowed "HH:MM:SS").',
+ $timeStr
+ ));
+ }
+
+ // Parse component (supports "01" or "01.123" (milli-precision)).
+ if ($v === '' || !preg_match('/^\d+(?:\.\d+)?$/', $v)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid non-digit or empty component "%s" in time string "%s".',
+ $v, $timeStr
+ ));
+ }
+ if ($offsetKey !== 0 && strpos($v, '.') !== false) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Unexpected period in time component "%s" in time string "%s". Only the seconds-component supports milliseconds.',
+ $v, $timeStr
+ ));
+ }
+
+ // Convert the value to float and cap minutes/seconds to 60 (but
+ // allow any number of hours).
+ $v = (float) $v;
+ $maxValue = $offsetKey < 2 ? 60 : -1;
+ if ($maxValue >= 0 && $v > $maxValue) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid time component "%d" (its allowed range is 0-%d) in time string "%s".',
+ $v, $maxValue, $timeStr
+ ));
+ }
+
+ // Multiply the current component of the "01:02:03" string with the
+ // power of its offset. Hour-offset will be 2, Minutes 1 and Secs 0;
+ // and "pow(60, 0)" will return 1 which is why seconds work too.
+ $sec += pow(60, $offsetKey) * $v;
+ }
+
+ return $sec;
+ }
+
+ /**
+ * Converts seconds to a hours/minutes/seconds timestamp.
+ *
+ * @param int|float $sec The number of seconds. Can have fractions (millis).
+ *
+ * @throws \InvalidArgumentException If any part of the input is invalid.
+ *
+ * @return string The time formatted as `HH:MM:SS.###` (`###` is millis).
+ */
+ public static function hmsTimeFromSeconds(
+ $sec)
+ {
+ if (!is_int($sec) && !is_float($sec)) {
+ throw new \InvalidArgumentException('Seconds must be a number.');
+ }
+
+ $wasNegative = false;
+ if ($sec < 0) {
+ $wasNegative = true;
+ $sec = abs($sec);
+ }
+
+ $result = sprintf(
+ '%02d:%02d:%06.3f', // "%06f" is because it counts the whole string.
+ floor($sec / 3600),
+ floor(fmod($sec / 60, 60)),
+ fmod($sec, 60)
+ );
+
+ if ($wasNegative) {
+ $result = '-'.$result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Builds an Instagram media location JSON object in the correct format.
+ *
+ * This function is used whenever we need to send a location to Instagram's
+ * API. All endpoints (so far) expect location data in this exact format.
+ *
+ * @param Location $location A model object representing the location.
+ *
+ * @throws \InvalidArgumentException If the location is invalid.
+ *
+ * @return string The final JSON string ready to submit as an API parameter.
+ */
+ public static function buildMediaLocationJSON(
+ $location)
+ {
+ if (!$location instanceof Location) {
+ throw new \InvalidArgumentException('The location must be an instance of \InstagramAPI\Response\Model\Location.');
+ }
+
+ // Forbid locations that came from Location::searchFacebook() and
+ // Location::searchFacebookByPoint()! They have slightly different
+ // properties, and they don't always contain all data we need. The
+ // real application NEVER uses the "Facebook" endpoints for attaching
+ // locations to media, and NEITHER SHOULD WE.
+ if ($location->getFacebookPlacesId() !== null) {
+ throw new \InvalidArgumentException('You are not allowed to use Location model objects from the Facebook-based location search functions. They are not valid media locations!');
+ }
+
+ // Core location keys that always exist.
+ $obj = [
+ 'name' => $location->getName(),
+ 'lat' => $location->getLat(),
+ 'lng' => $location->getLng(),
+ 'address' => $location->getAddress(),
+ 'external_source' => $location->getExternalIdSource(),
+ ];
+
+ // Attach the location ID via a dynamically generated key.
+ // NOTE: This automatically generates a key such as "facebook_places_id".
+ $key = $location->getExternalIdSource().'_id';
+ $obj[$key] = $location->getExternalId();
+
+ // Ensure that all keys are listed in the correct hash order.
+ $obj = self::reorderByHashCode($obj);
+
+ return json_encode($obj);
+ }
+
+ /**
+ * Check for ffprobe dependency.
+ *
+ * TIP: If your binary isn't findable via the PATH environment locations,
+ * you can manually set the correct path to it. Before calling any functions
+ * that need FFprobe, you must simply assign a manual value (ONCE) to tell
+ * us where to find your FFprobe, like this:
+ *
+ * \InstagramAPI\Utils::$ffprobeBin = '/home/exampleuser/ffmpeg/bin/ffprobe';
+ *
+ * @return string|bool Name of the library if present, otherwise FALSE.
+ */
+ public static function checkFFPROBE()
+ {
+ // We only resolve this once per session and then cache the result.
+ if (self::$ffprobeBin === null) {
+ @exec('ffprobe -version 2>&1', $output, $statusCode);
+ if ($statusCode === 0) {
+ self::$ffprobeBin = 'ffprobe';
+ } else {
+ self::$ffprobeBin = false; // Nothing found!
+ }
+ }
+
+ return self::$ffprobeBin;
+ }
+
+ /**
+ * Verifies a user tag.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * user tag, and with proper values for them. We cannot validate that the
+ * user-id actually exists, but that's the job of the library user!
+ *
+ * @param mixed $userTag An array containing the user ID and the tag position.
+ * Example: ['position'=>[0.5,0.5],'user_id'=>'123'].
+ *
+ * @throws \InvalidArgumentException If the tag is invalid.
+ */
+ public static function throwIfInvalidUserTag(
+ $userTag)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($userTag)) {
+ throw new \InvalidArgumentException('User tag must be an array.');
+ }
+
+ // Check for required keys.
+ $requiredKeys = ['position', 'user_id'];
+ $missingKeys = array_diff($requiredKeys, array_keys($userTag));
+ if (!empty($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for user tag array.', implode('", "', $missingKeys)));
+ }
+
+ // Verify this product tag entry, ensuring that the entry is format
+ // ['position'=>[0.0,1.0],'user_id'=>'123'] and nothing else.
+ foreach ($userTag as $key => $value) {
+ switch ($key) {
+ case 'user_id':
+ if (!is_int($value) && !ctype_digit($value)) {
+ throw new \InvalidArgumentException('User ID must be an integer.');
+ }
+ if ($value < 0) {
+ throw new \InvalidArgumentException('User ID must be a positive integer.');
+ }
+ break;
+ case 'position':
+ try {
+ self::throwIfInvalidPosition($value);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(sprintf('Invalid user tag position: %s', $e->getMessage()), $e->getCode(), $e);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in user tag array.', $key));
+ }
+ }
+ }
+
+ /**
+ * Verifies an array of media usertags.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * usertags, and with proper values for them. We cannot validate that the
+ * user-id's actually exist, but that's the job of the library user!
+ *
+ * @param mixed $usertags The array of usertags, optionally with the "in" or
+ * "removed" top-level keys holding the usertags. Example:
+ * ['in'=>[['position'=>[0.5,0.5],'user_id'=>'123'], ...]].
+ *
+ * @throws \InvalidArgumentException If any tags are invalid.
+ */
+ public static function throwIfInvalidUsertags(
+ $usertags)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($usertags)) {
+ throw new \InvalidArgumentException('Usertags must be an array.');
+ }
+
+ if (empty($usertags)) {
+ throw new \InvalidArgumentException('Empty usertags array.');
+ }
+
+ foreach ($usertags as $k => $v) {
+ if (!is_array($v)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid usertags array. The value for key "%s" must be an array.', $k
+ ));
+ }
+ // Skip the section if it's empty.
+ if (empty($v)) {
+ continue;
+ }
+ // Handle ['in'=>[...], 'removed'=>[...]] top-level keys since
+ // this input contained top-level array keys containing the usertags.
+ switch ($k) {
+ case 'in':
+ foreach ($v as $idx => $userTag) {
+ try {
+ self::throwIfInvalidUserTag($userTag);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid usertag at index "%d": %s', $idx, $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+ break;
+ case 'removed':
+ // Check the array of userids to remove.
+ foreach ($v as $userId) {
+ if (!ctype_digit($userId) && (!is_int($userId) || $userId < 0)) {
+ throw new \InvalidArgumentException('Invalid user ID in usertags "removed" array.');
+ }
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in user tags array.', $k));
+ }
+ }
+ }
+
+ /**
+ * Verifies an array of product tags.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * product tags, and with proper values for them. We cannot validate that the
+ * product's-id actually exists, but that's the job of the library user!
+ *
+ * @param mixed $productTags The array of usertags, optionally with the "in" or
+ * "removed" top-level keys holding the usertags. Example:
+ * ['in'=>[['position'=>[0.5,0.5],'product_id'=>'123'], ...]].
+ *
+ * @throws \InvalidArgumentException If any tags are invalid.
+ */
+ public static function throwIfInvalidProductTags(
+ $productTags)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($productTags)) {
+ throw new \InvalidArgumentException('Products tags must be an array.');
+ }
+
+ if (empty($productTags)) {
+ throw new \InvalidArgumentException('Empty product tags array.');
+ }
+
+ foreach ($productTags as $k => $v) {
+ if (!is_array($v)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Invalid product tags array. The value for key "%s" must be an array.', $k
+ ));
+ }
+
+ // Skip the section if it's empty.
+ if (empty($v)) {
+ continue;
+ }
+
+ // Handle ['in'=>[...], 'removed'=>[...]] top-level keys since
+ // this input contained top-level array keys containing the product tags.
+ switch ($k) {
+ case 'in':
+ // Check the array of product tags to insert.
+ foreach ($v as $idx => $productTag) {
+ try {
+ self::throwIfInvalidProductTag($productTag);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid product tag at index "%d": %s', $idx, $e->getMessage()),
+ $e->getCode(),
+ $e
+ );
+ }
+ }
+ break;
+ case 'removed':
+ // Check the array of product_id to remove.
+ foreach ($v as $productId) {
+ if (!ctype_digit($productId) && (!is_int($productId) || $productId < 0)) {
+ throw new \InvalidArgumentException('Invalid product ID in product tags "removed" array.');
+ }
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in product tags array.', $k));
+ }
+ }
+ }
+
+ /**
+ * Verifies a product tag.
+ *
+ * Ensures that the input strictly contains the exact keys necessary for
+ * product tag, and with proper values for them. We cannot validate that the
+ * product-id actually exists, but that's the job of the library user!
+ *
+ * @param mixed $productTag An array containing the product ID and the tag position.
+ * Example: ['position'=>[0.5,0.5],'product_id'=>'123'].
+ *
+ * @throws \InvalidArgumentException If any tags are invalid.
+ */
+ public static function throwIfInvalidProductTag(
+ $productTag)
+ {
+ // NOTE: We can use "array" typehint, but it doesn't give us enough freedom.
+ if (!is_array($productTag)) {
+ throw new \InvalidArgumentException('Product tag must be an array.');
+ }
+
+ // Check for required keys.
+ $requiredKeys = ['position', 'product_id'];
+ $missingKeys = array_diff($requiredKeys, array_keys($productTag));
+ if (!empty($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for product tag array.', implode('", "', $missingKeys)));
+ }
+
+ // Verify this product tag entry, ensuring that the entry is format
+ // ['position'=>[0.0,1.0],'product_id'=>'123'] and nothing else.
+ foreach ($productTag as $key => $value) {
+ switch ($key) {
+ case 'product_id':
+ if (!is_int($value) && !ctype_digit($value)) {
+ throw new \InvalidArgumentException('Product ID must be an integer.');
+ }
+ if ($value < 0) {
+ throw new \InvalidArgumentException('Product ID must be a positive integer.');
+ }
+ break;
+ case 'position':
+ try {
+ self::throwIfInvalidPosition($value);
+ } catch (\InvalidArgumentException $e) {
+ throw new \InvalidArgumentException(sprintf('Invalid product tag position: %s', $e->getMessage()), $e->getCode(), $e);
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" in product tag array.', $key));
+ }
+ }
+ }
+
+ /**
+ * Verifies a position.
+ *
+ * @param mixed $position An array containing a position coordinates.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function throwIfInvalidPosition(
+ $position)
+ {
+ if (!is_array($position)) {
+ throw new \InvalidArgumentException('Position must be an array.');
+ }
+
+ if (!isset($position[0])) {
+ throw new \InvalidArgumentException('X coordinate is required.');
+ }
+ $x = $position[0];
+ if (!is_int($x) && !is_float($x)) {
+ throw new \InvalidArgumentException('X coordinate must be a number.');
+ }
+ if ($x < 0.0 || $x > 1.0) {
+ throw new \InvalidArgumentException('X coordinate must be a float between 0.0 and 1.0.');
+ }
+
+ if (!isset($position[1])) {
+ throw new \InvalidArgumentException('Y coordinate is required.');
+ }
+ $y = $position[1];
+ if (!is_int($y) && !is_float($y)) {
+ throw new \InvalidArgumentException('Y coordinate must be a number.');
+ }
+ if ($y < 0.0 || $y > 1.0) {
+ throw new \InvalidArgumentException('Y coordinate must be a float between 0.0 and 1.0.');
+ }
+ }
+
+ /**
+ * Verifies that a single hashtag is valid.
+ *
+ * This function enforces the following requirements: It must be a string,
+ * at least 1 character long, and cannot contain the "#" character itself.
+ *
+ * @param mixed $hashtag The hashtag to check (should be string but we
+ * accept anything for checking purposes).
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function throwIfInvalidHashtag(
+ $hashtag)
+ {
+ if (!is_string($hashtag) || !strlen($hashtag)) {
+ throw new \InvalidArgumentException('Hashtag must be a non-empty string.');
+ }
+ // Perform an UTF-8 aware search for the illegal "#" symbol (anywhere).
+ // NOTE: We must use mb_strpos() to support international tags.
+ if (mb_strpos($hashtag, '#') !== false) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Hashtag "%s" is not allowed to contain the "#" character.',
+ $hashtag
+ ));
+ }
+ }
+
+ /**
+ * Verifies a rank token.
+ *
+ * @param string $rankToken
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function throwIfInvalidRankToken(
+ $rankToken
+ ) {
+ if (!Signatures::isValidUUID($rankToken)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid rank token.', $rankToken));
+ }
+ }
+
+ /**
+ * Verifies an array of story poll.
+ *
+ * @param array[] $storyPoll Array with story poll key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryPoll(
+ array $storyPoll)
+ {
+ $requiredKeys = ['question', 'viewer_vote', 'viewer_can_vote', 'tallies', 'is_sticker'];
+
+ if (count($storyPoll) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story poll is permitted. You added %d story polls.', count($storyPoll)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['question' => 1, 'viewer_vote' => 1, 'viewer_can_vote' => 1, 'tallies' => 1, 'is_sticker' => 1], $storyPoll[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story poll array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storyPoll[0] as $k => $v) {
+ switch ($k) {
+ case 'question':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_vote':
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_can_vote':
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'tallies':
+ if (!is_array($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ self::_throwIfInvalidStoryPollTallies($v);
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storyPoll[0], array_flip($requiredKeys)), 'polls');
+ }
+
+ /**
+ * Verifies an array of story slider.
+ *
+ * @param array[] $storySlider Array with story slider key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStorySlider(
+ array $storySlider)
+ {
+ $requiredKeys = ['question', 'viewer_vote', 'viewer_can_vote', 'slider_vote_average', 'slider_vote_count', 'emoji', 'background_color', 'text_color', 'is_sticker'];
+
+ if (count($storySlider) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story slider is permitted. You added %d story sliders.', count($storySlider)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['question' => 1, 'viewer_vote' => 1, 'viewer_can_vote' => 1, 'slider_vote_average' => 1, 'slider_vote_count' => 1, 'emoji' => 1, 'background_color' => 1, 'text_color' => 1, 'is_sticker' => 1], $storySlider[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story slider array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storySlider[0] as $k => $v) {
+ switch ($k) {
+ case 'question':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story slider array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_vote':
+ case 'slider_vote_count':
+ case 'slider_vote_average':
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story slider array-key "%s".', $v, $k));
+ }
+ break;
+ case 'background_color':
+ case 'text_color':
+ if (!preg_match('/^[0-9a-fA-F]{6}$/', substr($v, 1))) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story slider array-key "%s".', $v, $k));
+ }
+ break;
+ case 'emoji':
+ //TODO REQUIRES EMOJI VALIDATION
+ break;
+ case 'viewer_can_vote':
+ if (!is_bool($v) && $v !== false) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story poll array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storySlider[0], array_flip($requiredKeys)), 'sliders');
+ }
+
+ /**
+ * Verifies an array of story question.
+ *
+ * @param array $storyQuestion Array with story question key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryQuestion(
+ array $storyQuestion)
+ {
+ $requiredKeys = ['z', 'viewer_can_interact', 'background_color', 'profile_pic_url', 'question_type', 'question', 'text_color', 'is_sticker'];
+
+ if (count($storyQuestion) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story question is permitted. You added %d story questions.', count($storyQuestion)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['viewer_can_interact' => 1, 'background_color' => 1, 'profile_pic_url' => 1, 'question_type' => 1, 'question' => 1, 'text_color' => 1, 'is_sticker' => 1], $storyQuestion[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story question array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storyQuestion[0] as $k => $v) {
+ switch ($k) {
+ case 'z': // May be used for AR in the future, for now it's always 0.
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'viewer_can_interact':
+ if (!is_bool($v) || $v !== false) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'background_color':
+ case 'text_color':
+ if (!preg_match('/^[0-9a-fA-F]{6}$/', substr($v, 1))) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'question_type':
+ // At this time only text questions are supported.
+ if (!is_string($v) || $v !== 'text') {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'question':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'profile_pic_url':
+ if (!self::hasValidWebURLSyntax($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story question array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storyQuestion[0], array_flip($requiredKeys)), 'questions');
+ }
+
+ /**
+ * Verifies an array of story countdown.
+ *
+ * @param array $storyCountdown Array with story countdown key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryCountdown(
+ array $storyCountdown)
+ {
+ $requiredKeys = ['z', 'text', 'text_color', 'start_background_color', 'end_background_color', 'digit_color', 'digit_card_color', 'end_ts', 'following_enabled', 'is_sticker'];
+
+ if (count($storyCountdown) !== 1) {
+ throw new \InvalidArgumentException(sprintf('Only one story countdown is permitted. You added %d story countdowns.', count($storyCountdown)));
+ }
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['z' => 1, 'text' => 1, 'text_color' => 1, 'start_background_color' => 1, 'end_background_color' => 1, 'digit_color' => 1, 'digit_card_color' => 1, 'end_ts' => 1, 'following_enabled' => 1, 'is_sticker' => 1], $storyCountdown[0]));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for story countdown array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($storyCountdown[0] as $k => $v) {
+ switch ($k) {
+ case 'z': // May be used for AR in the future, for now it's always 0.
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'text':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'text_color':
+ case 'start_background_color':
+ case 'end_background_color':
+ case 'digit_color':
+ case 'digit_card_color':
+ if (!preg_match('/^[0-9a-fA-F]{6}$/', substr($v, 1))) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'end_ts':
+ if (!is_int($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'following_enabled':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v) && $v !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story countdown array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($storyCountdown[0], array_flip($requiredKeys)), 'countdowns');
+ }
+
+ /**
+ * Verifies if tallies are valid.
+ *
+ * @param array[] $tallies Array with story poll key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ protected static function _throwIfInvalidStoryPollTallies(
+ array $tallies)
+ {
+ $requiredKeys = ['text', 'count', 'font_size'];
+ if (count($tallies) !== 2) {
+ throw new \InvalidArgumentException(sprintf('Missing data for tallies.'));
+ }
+
+ foreach ($tallies as $tallie) {
+ $missingKeys = array_keys(array_diff_key(['text' => 1, 'count' => 1, 'font_size' => 1], $tallie));
+
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for location array.', implode(', ', $missingKeys)));
+ }
+ foreach ($tallie as $k => $v) {
+ if (!in_array($k, $requiredKeys, true)) {
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" for story poll tallies.', $k));
+ }
+ switch ($k) {
+ case 'text':
+ if (!is_string($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for tallies array-key "%s".', $v, $k));
+ }
+ break;
+ case 'count':
+ if ($v !== 0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for tallies array-key "%s".', $v, $k));
+ }
+ break;
+ case 'font_size':
+ if (!is_float($v) || ($v < 17.5 || $v > 35.0)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for tallies array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Verifies an array of story mentions.
+ *
+ * @param array[] $storyMentions The array of all story mentions.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryMentions(
+ array $storyMentions)
+ {
+ $requiredKeys = ['user_id'];
+
+ foreach ($storyMentions as $mention) {
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['user_id' => 1], $mention));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for mention array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($mention as $k => $v) {
+ switch ($k) {
+ case 'user_id':
+ if (!ctype_digit($v) && (!is_int($v) || $v < 0)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for story mention array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($mention, array_flip($requiredKeys)), 'story mentions');
+ }
+ }
+
+ /**
+ * Verifies if a story location sticker is valid.
+ *
+ * @param array[] $locationSticker Array with location sticker key-value pairs.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidStoryLocationSticker(
+ array $locationSticker)
+ {
+ $requiredKeys = ['location_id', 'is_sticker'];
+ $missingKeys = array_keys(array_diff_key(['location_id' => 1, 'is_sticker' => 1], $locationSticker));
+
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for location array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($locationSticker as $k => $v) {
+ switch ($k) {
+ case 'location_id':
+ if (!is_string($v) && !is_numeric($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for location array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for hashtag array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($locationSticker, array_flip($requiredKeys)), 'location');
+ }
+
+ /**
+ * Verifies if a caption is valid for a hashtag and verifies an array of hashtags.
+ *
+ * @param string $captionText The caption for the story hashtag to verify.
+ * @param array[] $hashtags The array of all story hashtags.
+ *
+ * @throws \InvalidArgumentException If caption doesn't contain any hashtag,
+ * or if any tags are invalid.
+ */
+ public static function throwIfInvalidStoryHashtags(
+ $captionText,
+ array $hashtags)
+ {
+ $requiredKeys = ['tag_name', 'use_custom_title', 'is_sticker'];
+
+ // Extract all hashtags from the caption using a UTF-8 aware regex.
+ if (!preg_match_all('/#(\w+[^\x00-\x7F]?+)/u', $captionText, $tagsInCaption)) {
+ throw new \InvalidArgumentException('Invalid caption for hashtag.');
+ }
+
+ // Verify all provided hashtags.
+ foreach ($hashtags as $hashtag) {
+ $missingKeys = array_keys(array_diff_key(['tag_name' => 1, 'use_custom_title' => 1, 'is_sticker' => 1], $hashtag));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for hashtag array.', implode(', ', $missingKeys)));
+ }
+
+ foreach ($hashtag as $k => $v) {
+ switch ($k) {
+ case 'tag_name':
+ // Ensure that the hashtag format is valid.
+ self::throwIfInvalidHashtag($v);
+ // Verify that this tag exists somewhere in the caption to check.
+ if (!in_array($v, $tagsInCaption[1])) { // NOTE: UTF-8 aware.
+ throw new \InvalidArgumentException(sprintf('Tag name "%s" does not exist in the caption text.', $v));
+ }
+ break;
+ case 'use_custom_title':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for hashtag array-key "%s".', $v, $k));
+ }
+ break;
+ case 'is_sticker':
+ if (!is_bool($v)) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for hashtag array-key "%s".', $v, $k));
+ }
+ break;
+ }
+ }
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($hashtag, array_flip($requiredKeys)), 'hashtag');
+ }
+ }
+
+ /**
+ * Verifies an attached media.
+ *
+ * @param array[] $attachedMedia Array containing the attached media data.
+ *
+ * @throws \InvalidArgumentException If it's missing keys or has invalid values.
+ */
+ public static function throwIfInvalidAttachedMedia(
+ array $attachedMedia)
+ {
+ $attachedMedia = reset($attachedMedia);
+ $requiredKeys = ['media_id', 'is_sticker'];
+
+ // Ensure that all keys exist.
+ $missingKeys = array_keys(array_diff_key(['media_id' => 1, 'is_sticker' => 1], $attachedMedia));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for attached media.', implode(', ', $missingKeys)));
+ }
+
+ if (!is_string($attachedMedia['media_id']) && !is_numeric($attachedMedia['media_id'])) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for media_id.', $attachedMedia['media_id']));
+ }
+
+ if (!is_bool($attachedMedia['is_sticker']) && $attachedMedia['is_sticker'] !== true) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for attached media.', $attachedMedia['is_sticker']));
+ }
+
+ self::_throwIfInvalidStoryStickerPlacement(array_diff_key($attachedMedia, array_flip($requiredKeys)), 'attached media');
+ }
+
+ /**
+ * Verifies a story sticker's placement parameters.
+ *
+ * There are many kinds of story stickers, such as hashtags, locations,
+ * mentions, etc. To place them on the media, the user must provide certain
+ * parameters for things like position and size. This function verifies all
+ * of those parameters and ensures that the sticker placement is valid.
+ *
+ * @param array $storySticker The array describing the story sticker placement.
+ * @param string $type What type of sticker this is.
+ *
+ * @throws \InvalidArgumentException If storySticker is missing keys or has invalid values.
+ */
+ protected static function _throwIfInvalidStoryStickerPlacement(
+ array $storySticker,
+ $type)
+ {
+ $requiredKeys = ['x', 'y', 'width', 'height', 'rotation'];
+
+ // Ensure that all required hashtag array keys exist.
+ $missingKeys = array_keys(array_diff_key(['x' => 1, 'y' => 1, 'width' => 1, 'height' => 1, 'rotation' => 0], $storySticker));
+ if (count($missingKeys)) {
+ throw new \InvalidArgumentException(sprintf('Missing keys "%s" for "%s".', implode(', ', $missingKeys), $type));
+ }
+
+ // Check the individual array values.
+ foreach ($storySticker as $k => $v) {
+ if (!in_array($k, $requiredKeys, true)) {
+ throw new \InvalidArgumentException(sprintf('Invalid key "%s" for "%s".', $k, $type));
+ }
+ switch ($k) {
+ case 'x':
+ case 'y':
+ case 'width':
+ case 'height':
+ case 'rotation':
+ if (!is_float($v) || $v < 0.0 || $v > 1.0) {
+ throw new \InvalidArgumentException(sprintf('Invalid value "%s" for "%s" key "%s".', $v, $type, $k));
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Checks and validates a media item's type.
+ *
+ * @param string|int $mediaType The type of the media item. One of: "PHOTO", "VIDEO"
+ * "CAROUSEL", or the raw value of the Item's "getMediaType()" function.
+ *
+ * @throws \InvalidArgumentException If the type is invalid.
+ *
+ * @return string The verified final type; either "PHOTO", "VIDEO" or "CAROUSEL".
+ */
+ public static function checkMediaType(
+ $mediaType)
+ {
+ if (ctype_digit($mediaType) || is_int($mediaType)) {
+ if ($mediaType == Item::PHOTO) {
+ $mediaType = 'PHOTO';
+ } elseif ($mediaType == Item::VIDEO) {
+ $mediaType = 'VIDEO';
+ } elseif ($mediaType == Item::CAROUSEL) {
+ $mediaType = 'CAROUSEL';
+ }
+ }
+ if (!in_array($mediaType, ['PHOTO', 'VIDEO', 'CAROUSEL'], true)) {
+ throw new \InvalidArgumentException(sprintf('"%s" is not a valid media type.', $mediaType));
+ }
+
+ return $mediaType;
+ }
+
+ public static function formatBytes(
+ $bytes,
+ $precision = 2)
+ {
+ $units = ['B', 'kB', 'mB', 'gB', 'tB'];
+
+ $bytes = max($bytes, 0);
+ $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+ $pow = min($pow, count($units) - 1);
+
+ $bytes /= pow(1024, $pow);
+
+ return round($bytes, $precision).''.$units[$pow];
+ }
+
+ public static function colouredString(
+ $string,
+ $colour)
+ {
+ $colours['black'] = '0;30';
+ $colours['dark_gray'] = '1;30';
+ $colours['blue'] = '0;34';
+ $colours['light_blue'] = '1;34';
+ $colours['green'] = '0;32';
+ $colours['light_green'] = '1;32';
+ $colours['cyan'] = '0;36';
+ $colours['light_cyan'] = '1;36';
+ $colours['red'] = '0;31';
+ $colours['light_red'] = '1;31';
+ $colours['purple'] = '0;35';
+ $colours['light_purple'] = '1;35';
+ $colours['brown'] = '0;33';
+ $colours['yellow'] = '1;33';
+ $colours['light_gray'] = '0;37';
+ $colours['white'] = '1;37';
+
+ $colored_string = '';
+
+ if (isset($colours[$colour])) {
+ $colored_string .= "\033[".$colours[$colour].'m';
+ }
+
+ $colored_string .= $string."\033[0m";
+
+ return $colored_string;
+ }
+
+ public static function getFilterCode(
+ $filter)
+ {
+ $filters = [];
+ $filters[0] = 'Normal';
+ $filters[615] = 'Lark';
+ $filters[614] = 'Reyes';
+ $filters[613] = 'Juno';
+ $filters[612] = 'Aden';
+ $filters[608] = 'Perpetua';
+ $filters[603] = 'Ludwig';
+ $filters[605] = 'Slumber';
+ $filters[616] = 'Crema';
+ $filters[24] = 'Amaro';
+ $filters[17] = 'Mayfair';
+ $filters[23] = 'Rise';
+ $filters[26] = 'Hudson';
+ $filters[25] = 'Valencia';
+ $filters[1] = 'X-Pro II';
+ $filters[27] = 'Sierra';
+ $filters[28] = 'Willow';
+ $filters[2] = 'Lo-Fi';
+ $filters[3] = 'Earlybird';
+ $filters[22] = 'Brannan';
+ $filters[10] = 'Inkwell';
+ $filters[21] = 'Hefe';
+ $filters[15] = 'Nashville';
+ $filters[18] = 'Sutro';
+ $filters[19] = 'Toaster';
+ $filters[20] = 'Walden';
+ $filters[14] = '1977';
+ $filters[16] = 'Kelvin';
+ $filters[-2] = 'OES';
+ $filters[-1] = 'YUV';
+ $filters[109] = 'Stinson';
+ $filters[106] = 'Vesper';
+ $filters[112] = 'Clarendon';
+ $filters[118] = 'Maven';
+ $filters[114] = 'Gingham';
+ $filters[107] = 'Ginza';
+ $filters[113] = 'Skyline';
+ $filters[105] = 'Dogpatch';
+ $filters[115] = 'Brooklyn';
+ $filters[111] = 'Moon';
+ $filters[117] = 'Helena';
+ $filters[116] = 'Ashby';
+ $filters[108] = 'Charmes';
+ $filters[640] = 'BrightContrast';
+ $filters[642] = 'CrazyColor';
+ $filters[643] = 'SubtleColor';
+
+ return array_search($filter, $filters);
+ }
+
+ /**
+ * Creates a folder if missing, or ensures that it is writable.
+ *
+ * @param string $folder The directory path.
+ *
+ * @return bool TRUE if folder exists and is writable, otherwise FALSE.
+ */
+ public static function createFolder(
+ $folder)
+ {
+ // Test write-permissions for the folder and create/fix if necessary.
+ if ((is_dir($folder) && is_writable($folder))
+ || (!is_dir($folder) && mkdir($folder, 0755, true))
+ || chmod($folder, 0755)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Recursively deletes a file/directory tree.
+ *
+ * @param string $folder The directory path.
+ * @param bool $keepRootFolder Whether to keep the top-level folder.
+ *
+ * @return bool TRUE on success, otherwise FALSE.
+ */
+ public static function deleteTree(
+ $folder,
+ $keepRootFolder = false)
+ {
+ // Handle bad arguments.
+ if (empty($folder) || !file_exists($folder)) {
+ return true; // No such file/folder exists.
+ } elseif (is_file($folder) || is_link($folder)) {
+ return @unlink($folder); // Delete file/link.
+ }
+
+ // Delete all children.
+ $files = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
+ \RecursiveIteratorIterator::CHILD_FIRST
+ );
+
+ foreach ($files as $fileinfo) {
+ $action = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
+ if (!@$action($fileinfo->getRealPath())) {
+ return false; // Abort due to the failure.
+ }
+ }
+
+ // Delete the root folder itself?
+ return !$keepRootFolder ? @rmdir($folder) : true;
+ }
+
+ /**
+ * Atomic filewriter.
+ *
+ * Safely writes new contents to a file using an atomic two-step process.
+ * If the script is killed before the write is complete, only the temporary
+ * trash file will be corrupted.
+ *
+ * The algorithm also ensures that 100% of the bytes were written to disk.
+ *
+ * @param string $filename Filename to write the data to.
+ * @param string $data Data to write to file.
+ * @param string $atomicSuffix Lets you optionally provide a different
+ * suffix for the temporary file.
+ *
+ * @return int|bool Number of bytes written on success, otherwise `FALSE`.
+ */
+ public static function atomicWrite(
+ $filename,
+ $data,
+ $atomicSuffix = 'atomictmp')
+ {
+ // Perform an exclusive (locked) overwrite to a temporary file.
+ $filenameTmp = sprintf('%s.%s', $filename, $atomicSuffix);
+ $writeResult = @file_put_contents($filenameTmp, $data, LOCK_EX);
+
+ // Only proceed if we wrote 100% of the data bytes to disk.
+ if ($writeResult !== false && $writeResult === strlen($data)) {
+ // Now move the file to its real destination (replaces if exists).
+ $moveResult = @rename($filenameTmp, $filename);
+ if ($moveResult === true) {
+ // Successful write and move. Return number of bytes written.
+ return $writeResult;
+ }
+ }
+
+ // We've failed. Remove the temporary file if it exists.
+ if (is_file($filenameTmp)) {
+ @unlink($filenameTmp);
+ }
+
+ return false; // Failed.
+ }
+
+ /**
+ * Creates an empty temp file with a unique filename.
+ *
+ * @param string $outputDir Folder to place the temp file in.
+ * @param string $namePrefix (optional) What prefix to use for the temp file.
+ *
+ * @throws \RuntimeException If the file cannot be created.
+ *
+ * @return string
+ */
+ public static function createTempFile(
+ $outputDir,
+ $namePrefix = 'TEMP')
+ {
+ // Automatically generates a name like "INSTATEMP_" or "INSTAVID_" etc.
+ $finalPrefix = sprintf('INSTA%s_', $namePrefix);
+
+ // Try to create the file (detects errors).
+ $tmpFile = @tempnam($outputDir, $finalPrefix);
+ if (!is_string($tmpFile)) {
+ throw new \RuntimeException(sprintf(
+ 'Unable to create temporary output file in "%s" (with prefix "%s").',
+ $outputDir, $finalPrefix
+ ));
+ }
+
+ return $tmpFile;
+ }
+
+ /**
+ * Closes a file pointer if it's open.
+ *
+ * Always use this function instead of fclose()!
+ *
+ * Unlike the normal fclose(), this function is safe to call multiple times
+ * since it only attempts to close the pointer if it's actually still open.
+ * The normal fclose() would give an annoying warning in that scenario.
+ *
+ * @param resource $handle A file pointer opened by fopen() or fsockopen().
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
+ public static function safe_fclose(
+ $handle)
+ {
+ if (is_resource($handle)) {
+ return fclose($handle);
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if a URL has valid "web" syntax.
+ *
+ * This function is Unicode-aware.
+ *
+ * Be aware that it only performs URL syntax validation! It doesn't check
+ * if the domain/URL is fully valid and actually reachable!
+ *
+ * It verifies that the URL begins with either the "http://" or "https://"
+ * protocol, and that it must contain a host with at least one period in it,
+ * and at least two characters after the period (in other words, a TLD). The
+ * rest of the string can be any sequence of non-whitespace characters.
+ *
+ * For example, "http://localhost" will not be seen as a valid web URL, and
+ * "http://www.google.com foobar" is not a valid web URL since there's a
+ * space in it. But "https://bing.com" and "https://a.com/foo" are valid.
+ * However, "http://a.abdabdbadbadbsa" is also seen as a valid URL, since
+ * the validation is pretty simple and doesn't verify the TLDs (there are
+ * too many now to catch them all and new ones appear constantly).
+ *
+ * @param string $url
+ *
+ * @return bool TRUE if valid web syntax, otherwise FALSE.
+ */
+ public static function hasValidWebURLSyntax(
+ $url)
+ {
+ return (bool) preg_match('/^https?:\/\/[^\s.\/]+\.[^\s.\/]{2}\S*$/iu', $url);
+ }
+
+ /**
+ * Extract all URLs from a text string.
+ *
+ * This function is Unicode-aware.
+ *
+ * @param string $text The string to scan for URLs.
+ *
+ * @return array An array of URLs and their individual components.
+ */
+ public static function extractURLs(
+ $text)
+ {
+ $urls = [];
+ if (preg_match_all(
+ // NOTE: This disgusting regex comes from the Android SDK, slightly
+ // modified by Instagram and then encoded by us into PHP format. We
+ // are NOT allowed to tweak this regex! It MUST match the official
+ // app so that our link-detection acts *exactly* like the real app!
+ // NOTE: Here is the "to PHP regex" conversion algorithm we used:
+ // https://github.com/mgp25/Instagram-API/issues/1445#issuecomment-318921867
+ '/((?:(http|https|Http|Https|rtsp|Rtsp):\/\/(?:(?:[a-zA-Z0-9$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,64}(?:\:(?:[a-zA-Z0-9$\-\_\.\+\!\*\'\(\)\,\;\?\&\=]|(?:\%[a-fA-F0-9]{2})){1,25})?\@)?)?((?:(?:[a-zA-Z0-9\x{00A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\_][a-zA-Z0-9\x{00A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\_\-]{0,64}\.)+(?:(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(?:biz|b[abdefghijmnorstvwyz])|(?:cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(?:edu|e[cegrstu])|f[ijkmor]|(?:gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(?:info|int|i[delmnoqrst])|(?:jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(?:name|net|n[acefgilopruz])|(?:org|om)|(?:pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(?:tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(?:\x{03B4}\x{03BF}\x{03BA}\x{03B9}\x{03BC}\x{03AE}|\x{0438}\x{0441}\x{043F}\x{044B}\x{0442}\x{0430}\x{043D}\x{0438}\x{0435}|\x{0440}\x{0444}|\x{0441}\x{0440}\x{0431}|\x{05D8}\x{05E2}\x{05E1}\x{05D8}|\x{0622}\x{0632}\x{0645}\x{0627}\x{06CC}\x{0634}\x{06CC}|\x{0625}\x{062E}\x{062A}\x{0628}\x{0627}\x{0631}|\x{0627}\x{0644}\x{0627}\x{0631}\x{062F}\x{0646}|\x{0627}\x{0644}\x{062C}\x{0632}\x{0627}\x{0626}\x{0631}|\x{0627}\x{0644}\x{0633}\x{0639}\x{0648}\x{062F}\x{064A}\x{0629}|\x{0627}\x{0644}\x{0645}\x{063A}\x{0631}\x{0628}|\x{0627}\x{0645}\x{0627}\x{0631}\x{0627}\x{062A}|\x{0628}\x{06BE}\x{0627}\x{0631}\x{062A}|\x{062A}\x{0648}\x{0646}\x{0633}|\x{0633}\x{0648}\x{0631}\x{064A}\x{0629}|\x{0641}\x{0644}\x{0633}\x{0637}\x{064A}\x{0646}|\x{0642}\x{0637}\x{0631}|\x{0645}\x{0635}\x{0631}|\x{092A}\x{0930}\x{0940}\x{0915}\x{094D}\x{0937}\x{093E}|\x{092D}\x{093E}\x{0930}\x{0924}|\x{09AD}\x{09BE}\x{09B0}\x{09A4}|\x{0A2D}\x{0A3E}\x{0A30}\x{0A24}|\x{0AAD}\x{0ABE}\x{0AB0}\x{0AA4}|\x{0B87}\x{0BA8}\x{0BCD}\x{0BA4}\x{0BBF}\x{0BAF}\x{0BBE}|\x{0B87}\x{0BB2}\x{0B99}\x{0BCD}\x{0B95}\x{0BC8}|\x{0B9A}\x{0BBF}\x{0B99}\x{0BCD}\x{0B95}\x{0BAA}\x{0BCD}\x{0BAA}\x{0BC2}\x{0BB0}\x{0BCD}|\x{0BAA}\x{0BB0}\x{0BBF}\x{0B9F}\x{0BCD}\x{0B9A}\x{0BC8}|\x{0C2D}\x{0C3E}\x{0C30}\x{0C24}\x{0C4D}|\x{0DBD}\x{0D82}\x{0D9A}\x{0DCF}|\x{0E44}\x{0E17}\x{0E22}|\x{30C6}\x{30B9}\x{30C8}|\x{4E2D}\x{56FD}|\x{4E2D}\x{570B}|\x{53F0}\x{6E7E}|\x{53F0}\x{7063}|\x{65B0}\x{52A0}\x{5761}|\x{6D4B}\x{8BD5}|\x{6E2C}\x{8A66}|\x{9999}\x{6E2F}|\x{D14C}\x{C2A4}\x{D2B8}|\x{D55C}\x{AD6D}|xn\-\-0zwm56d|xn\-\-11b5bs3a9aj6g|xn\-\-3e0b707e|xn\-\-45brj9c|xn\-\-80akhbyknj4f|xn\-\-90a3ac|xn\-\-9t4b11yi5a|xn\-\-clchc0ea0b2g2a9gcd|xn\-\-deba0ad|xn\-\-fiqs8s|xn\-\-fiqz9s|xn\-\-fpcrj9c3d|xn\-\-fzc2c9e2c|xn\-\-g6w251d|xn\-\-gecrj9c|xn\-\-h2brj9c|xn\-\-hgbk6aj7f53bba|xn\-\-hlcj6aya9esc7a|xn\-\-j6w193g|xn\-\-jxalpdlp|xn\-\-kgbechtv|xn\-\-kprw13d|xn\-\-kpry57d|xn\-\-lgbbat1ad8j|xn\-\-mgbaam7a8h|xn\-\-mgbayh7gpa|xn\-\-mgbbh1a71e|xn\-\-mgbc0a9azcg|xn\-\-mgberp4a5d4ar|xn\-\-o3cw4h|xn\-\-ogbpf8fl|xn\-\-p1ai|xn\-\-pgbs0dh|xn\-\-s9brj9c|xn\-\-wgbh1c|xn\-\-wgbl6a|xn\-\-xkc2al3hye2a|xn\-\-xkc2dl3a5ee0h|xn\-\-yfro4i67o|xn\-\-ygbi2ammx|xn\-\-zckzah|xxx)|y[et]|z[amw]))|(?:(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])))(?:\:\d{1,5})?)(\/(?:(?:[a-zA-Z0-9\x{00A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\;\/\?\:\@\&\=\#\~\-\.\+\!\*\'\(\)\,\_])|(?:\%[a-fA-F0-9]{2}))*)?(?:\b|$)/iu',
+ $text,
+ $matches,
+ PREG_SET_ORDER
+ ) !== false) {
+ foreach ($matches as $match) {
+ $urls[] = [
+ 'fullUrl' => $match[0], // "https://foo:bar@www.bing.com/?foo=#test"
+ 'baseUrl' => $match[1], // "https://foo:bar@www.bing.com"
+ 'protocol' => $match[2], // "https" (empty if no protocol)
+ 'domain' => $match[3], // "www.bing.com"
+ 'path' => isset($match[4]) ? $match[4] : '', // "/?foo=#test"
+ ];
+ }
+ }
+
+ return $urls;
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/React/ConnectorTest.php b/vendor/mgp25/instagram-php/tests/React/ConnectorTest.php
new file mode 100755
index 0000000..9414d97
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/React/ConnectorTest.php
@@ -0,0 +1,211 @@
+getMethod($method);
+ $reflectionMethod->setAccessible(true);
+
+ return $reflectionMethod->invokeArgs($object, $args);
+ }
+
+ /**
+ * @return Connector
+ */
+ protected function _createConnector()
+ {
+ /** @var Instagram $instagramMock */
+ $instagramMock = $this->createMock(Instagram::class);
+ /** @var LoopInterface $loopMock */
+ $loopMock = $this->createMock(LoopInterface::class);
+
+ return new Connector($instagramMock, $loopMock);
+ }
+
+ public function testEmptyProxyConfigShouldReturnNull()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ null,
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, null)
+ );
+ }
+
+ public function testSingleProxyConfigShouldReturnAsIs()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ '127.0.0.1:3128',
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, '127.0.0.1:3128')
+ );
+ }
+
+ public function testHttpProxyShouldThrow()
+ {
+ $connector = $this->_createConnector();
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('No proxy with CONNECT method found');
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, [
+ 'http' => '127.0.0.1:3128',
+ ]);
+ }
+
+ public function testMustPickHttpsProxy()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ '127.0.0.1:3128',
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, [
+ 'http' => '127.0.0.1:3127',
+ 'https' => '127.0.0.1:3128',
+ ])
+ );
+ }
+
+ public function testShouldReturnNullWhenHostInExceptions()
+ {
+ $connector = $this->_createConnector();
+
+ $this->assertEquals(
+ null,
+ $this->_callProtectedMethod($connector, '_getProxyForHost', self::HOST, [
+ 'https' => '127.0.0.1:3128',
+ 'no' => ['.facebook.com'],
+ ])
+ );
+ }
+
+ public function testVerifyPeerEnabled()
+ {
+ $connector = $this->_createConnector();
+
+ $context = $this->_callProtectedMethod($connector, '_getSecureContext', true);
+ $this->assertInternalType('array', $context);
+ $this->assertCount(3, $context);
+ $this->assertArrayHasKey('verify_peer', $context);
+ $this->assertEquals(true, $context['verify_peer']);
+ $this->assertArrayHasKey('verify_peer_name', $context);
+ $this->assertEquals(true, $context['verify_peer_name']);
+ $this->assertArrayHasKey('allow_self_signed', $context);
+ $this->assertEquals(false, $context['allow_self_signed']);
+ }
+
+ public function testVerifyPeerDisabled()
+ {
+ $connector = $this->_createConnector();
+
+ $context = $this->_callProtectedMethod($connector, '_getSecureContext', false);
+ $this->assertInternalType('array', $context);
+ $this->assertCount(2, $context);
+ $this->assertArrayHasKey('verify_peer', $context);
+ $this->assertEquals(false, $context['verify_peer']);
+ $this->assertArrayHasKey('verify_peer_name', $context);
+ $this->assertEquals(false, $context['verify_peer_name']);
+ }
+
+ public function testVerifyPeerEnabledWithCustomCa()
+ {
+ $connector = $this->_createConnector();
+
+ $context = $this->_callProtectedMethod($connector, '_getSecureContext', __FILE__);
+ $this->assertInternalType('array', $context);
+ $this->assertCount(4, $context);
+ $this->assertArrayHasKey('cafile', $context);
+ $this->assertEquals(__FILE__, $context['cafile']);
+ $this->assertArrayHasKey('verify_peer', $context);
+ $this->assertEquals(true, $context['verify_peer']);
+ $this->assertArrayHasKey('verify_peer_name', $context);
+ $this->assertEquals(true, $context['verify_peer_name']);
+ $this->assertArrayHasKey('allow_self_signed', $context);
+ $this->assertEquals(false, $context['allow_self_signed']);
+ }
+
+ public function testVerifyPeerEnabledWithCustomCaMissing()
+ {
+ $connector = $this->_createConnector();
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('CA bundle not found');
+ $this->_callProtectedMethod($connector, '_getSecureContext', __FILE__.'.missing');
+ }
+
+ public function testVerifyPeerWithInvalidConfig()
+ {
+ $connector = $this->_createConnector();
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid verify request option');
+ $this->_callProtectedMethod($connector, '_getSecureContext', [__FILE__]);
+ }
+
+ public function testSecureConnector()
+ {
+ $connector = $this->_createConnector();
+
+ $secureConnector = $this->_callProtectedMethod($connector, '_getSecureConnector', [], null);
+ $this->assertInstanceOf(SecureConnector::class, $secureConnector);
+ }
+
+ public function testProxyWrappers()
+ {
+ $proxies = [
+ 'socks://127.0.0.1:1080' => SocksProxy::class,
+ 'socks4://127.0.0.1:1080' => SocksProxy::class,
+ 'socks4a://127.0.0.1:1080' => SocksProxy::class,
+ 'socks5://127.0.0.1:1080' => SocksProxy::class,
+ 'http://127.0.0.1:3128' => HttpConnectProxy::class,
+ 'https://127.0.0.1:3128' => HttpConnectProxy::class,
+ '127.0.0.1:3128' => HttpConnectProxy::class,
+ ];
+ foreach ($proxies as $proxy => $targetClass) {
+ $connector = $this->_createConnector();
+ /** @var ConnectorInterface $baseConnector */
+ $baseConnector = $this->createMock(ConnectorInterface::class);
+
+ $this->assertInstanceOf(
+ $targetClass,
+ $this->_callProtectedMethod($connector, '_wrapConnectorIntoProxy', $baseConnector, $proxy)
+ );
+ }
+ }
+
+ public function testProxyWithoutWrapperShouldThrow()
+ {
+ $connector = $this->_createConnector();
+ /** @var ConnectorInterface $baseConnector */
+ $baseConnector = $this->createMock(ConnectorInterface::class);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Unsupported proxy scheme');
+ $this->_callProtectedMethod($connector, '_wrapConnectorIntoProxy', $baseConnector, 'tcp://127.0.0.1');
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/IndicateActivityTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/IndicateActivityTest.php
new file mode 100755
index 0000000..d68b2f0
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/IndicateActivityTest.php
@@ -0,0 +1,57 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new IndicateActivity('abc', '123');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new IndicateActivity('-1', '123');
+ }
+
+ public function testCommandOutputWhenTrue()
+ {
+ $command = new IndicateActivity('123', true);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","activity_status":"1","action":"indicate_activity"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWhenFalse()
+ {
+ $command = new IndicateActivity('123', false);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","activity_status":"0","action":"indicate_activity"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new IndicateActivity('123', true, ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new IndicateActivity('123', true, ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","activity_status":"1","action":"indicate_activity"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/IrisSubscribeTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/IrisSubscribeTest.php
new file mode 100755
index 0000000..009caef
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/IrisSubscribeTest.php
@@ -0,0 +1,22 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid Iris sequence identifier');
+ new IrisSubscribe(-1);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new IrisSubscribe(777);
+ $this->assertEquals('{"seq_id":777}', json_encode($command));
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/MarkSeenTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/MarkSeenTest.php
new file mode 100755
index 0000000..704f38d
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/MarkSeenTest.php
@@ -0,0 +1,55 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new MarkSeen('abc', '123');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new MarkSeen('-1', '123');
+ }
+
+ public function testNonNumericThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new MarkSeen('123', 'abc');
+ }
+
+ public function testNegativeThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new MarkSeen('123', '-1');
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new MarkSeen('123', '456');
+ $this->assertEquals(
+ '{"thread_id":"123","item_id":"456","action":"mark_seen"}',
+ json_encode($command)
+ );
+ }
+
+ public function testCustomClientContextIsIgnored()
+ {
+ $command = new MarkSeen('123', '456', ['client_context' => 'test']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_id":"456","action":"mark_seen"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/SendHashtagTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendHashtagTest.php
new file mode 100755
index 0000000..92ed505
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendHashtagTest.php
@@ -0,0 +1,105 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendHashtag('abc', 'somehashtag');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendHashtag('-1', 'somehashtag');
+ }
+
+ public function testNonStringHashtagShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('be a string');
+ new SendHashtag('123', 2.5);
+ }
+
+ public function testEmptyHashtagShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not be empty');
+ new SendHashtag('123', '');
+ }
+
+ public function testNumberSignOnlyShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not be empty');
+ new SendHashtag('123', '#');
+ }
+
+ public function testTwoWordsShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be one word');
+ new SendHashtag('123', '#cool pics');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendHashtag('123', 'somehashtag', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendHashtag('123', 'somehashtag');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"hashtag","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithTrim()
+ {
+ $command = new SendHashtag('123', '#somehashtag ');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"hashtag","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendHashtag('123', 'somehashtag', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"hashtag","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendHashtag('123', 'somehashtag', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendHashtag('123', 'somehashtag', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"hashtag","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"somehashtag","action":"send_item","hashtag":"somehashtag"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLikeTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLikeTest.php
new file mode 100755
index 0000000..26f6b70
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLikeTest.php
@@ -0,0 +1,48 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLike('abc');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLike('-1');
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendLike('123');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"like","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendLike('123', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendLike('123', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"like","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLocationTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLocationTest.php
new file mode 100755
index 0000000..3f87a1b
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendLocationTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLocation('abc', '123456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendLocation('-1', '123456');
+ }
+
+ public function testInvalidLocationIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendLocation('123', 'location');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendLocation('123', '123456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendLocation('123', '123456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"location","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","venue_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendLocation('123', '123456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"location","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","venue_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendLocation('123', '123456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendLocation('123', '123456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"location","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"123456","action":"send_item","venue_id":"123456"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/SendPostTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendPostTest.php
new file mode 100755
index 0000000..9b5465c
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendPostTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendPost('abc', '123_456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendPost('-1', '123_456');
+ }
+
+ public function testInvalidMediaIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendPost('123', 'abc_def');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendPost('123', '123_456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendPost('123', '123_456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"media_share","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item","media_id":"123_456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendPost('123', '123_456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"media_share","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item","media_id":"123_456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendPost('123', '123_456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendPost('123', '123_456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"media_share","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","action":"send_item","media_id":"123_456"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/SendProfileTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendProfileTest.php
new file mode 100755
index 0000000..912c6e8
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendProfileTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendProfile('abc', '123456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendProfile('-1', '123456');
+ }
+
+ public function testInvalidUserIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendProfile('123', 'username');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendProfile('123', '123456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendProfile('123', '123456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"profile","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","profile_user_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendProfile('123', '123456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"profile","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123456","action":"send_item","profile_user_id":"123456"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendProfile('123', '123456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendProfile('123', '123456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"profile","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"123456","action":"send_item","profile_user_id":"123456"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/SendReactionTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendReactionTest.php
new file mode 100755
index 0000000..b6edbed
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendReactionTest.php
@@ -0,0 +1,85 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendReaction('abc', '123', 'like', 'created');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendReaction('-1', '123', 'like', 'created');
+ }
+
+ public function testNonNumericThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new SendReaction('123', 'abc', 'like', 'created');
+ }
+
+ public function testNegativeThreadItemIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread item identifier');
+ new SendReaction('123', '-1', 'like', 'created');
+ }
+
+ public function testUnknownReactionTypeShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a supported reaction');
+ new SendReaction('123', '456', 'angry', 'created');
+ }
+
+ public function testUnknownReactionStatusShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a supported reaction status');
+ new SendReaction('123', '456', 'like', 'removed');
+ }
+
+ public function testCommandOutputWhenLikeIsCreated()
+ {
+ $command = new SendReaction('123', '456', 'like', 'created');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"reaction","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","reaction_type":"like","reaction_status":"created","item_id":"456","node_type":"item","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWhenLikeIsDeleted()
+ {
+ $command = new SendReaction('123', '456', 'like', 'deleted');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"reaction","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","reaction_type":"like","reaction_status":"deleted","item_id":"456","node_type":"item","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendReaction('123', '456', 'like', 'created', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendReaction('123', '456', 'like', 'created', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"reaction","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","reaction_type":"like","reaction_status":"created","item_id":"456","node_type":"item","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/SendStoryTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendStoryTest.php
new file mode 100755
index 0000000..c780482
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendStoryTest.php
@@ -0,0 +1,75 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendStory('abc', '123_456');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendStory('-1', '123_456');
+ }
+
+ public function testInvalidStoryIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('not a valid');
+ new SendStory('123', 'abc_def');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendStory('123', '123_456', [
+ 'text' => [],
+ ]);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendStory('123', '123_456');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"story_share","text":"","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123_456","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testCommandOutputWithText()
+ {
+ $command = new SendStory('123', '123_456', [
+ 'text' => 'Text',
+ ]);
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"story_share","text":"Text","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","item_id":"123_456","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendStory('123', '123_456', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendStory('123', '123_456', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"story_share","text":"","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","item_id":"123_456","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/SendTextTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendTextTest.php
new file mode 100755
index 0000000..78faf91
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/SendTextTest.php
@@ -0,0 +1,62 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendText('abc', 'test');
+ }
+
+ public function testNegativeThreadIdShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid thread identifier');
+ new SendText('-1', 'test');
+ }
+
+ public function testEmptyTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('can not be empty');
+ new SendText('123', '');
+ }
+
+ public function testNonStringTextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('must be a string');
+ new SendText('123', null);
+ }
+
+ public function testCommandOutput()
+ {
+ $command = new SendText('123', 'test');
+ $this->assertRegExp(
+ '#^{"thread_id":"123","item_type":"text","text":"test","client_context":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","action":"send_item"}$#',
+ json_encode($command)
+ );
+ }
+
+ public function testInvalidClientContextShouldThrow()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('is not a valid UUID');
+ new SendText('123', 'test', ['client_context' => 'test']);
+ }
+
+ public function testValidClientContextShouldPass()
+ {
+ $command = new SendText('123', 'test', ['client_context' => 'deadbeef-dead-beef-dead-beefdeadbeef']);
+ $this->assertEquals(
+ '{"thread_id":"123","item_type":"text","text":"test","client_context":"deadbeef-dead-beef-dead-beefdeadbeef","action":"send_item"}',
+ json_encode($command)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Command/UpdateSubscriptionsTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Command/UpdateSubscriptionsTest.php
new file mode 100755
index 0000000..c5b7a9a
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Command/UpdateSubscriptionsTest.php
@@ -0,0 +1,22 @@
+assertEquals(
+ '{"sub":["ig/live_notification_subscribe/1111111111","ig/u/v1/1111111111"]}',
+ json_encode($command, JSON_UNESCAPED_SLASHES)
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Handler/DirectTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Handler/DirectTest.php
new file mode 100755
index 0000000..7014747
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Handler/DirectTest.php
@@ -0,0 +1,308 @@
+createMock(EventEmitterInterface::class);
+ // listeners() call is at index 0.
+ $target->expects($this->at(1))->method('emit')->with(
+ 'thread-item-created',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(3, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('11111111111111111111111111111111111', $arguments[1]);
+ $this->assertArrayHasKey(2, $arguments);
+ $this->assertInstanceOf(DirectThreadItem::class, $arguments[2]);
+ /** @var DirectThreadItem $item */
+ $item = $arguments[2];
+ $this->assertEquals('TEXT', $item->getText());
+
+ return true;
+ })
+ );
+ // listeners() call is at index 2.
+ $target->expects($this->at(3))->method('emit')->with(
+ 'unseen-count-update',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('inbox', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(DirectSeenItemPayload::class, $arguments[1]);
+ /** @var DirectSeenItemPayload $payload */
+ $payload = $arguments[1];
+ $this->assertEquals(1, $payload->getCount());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/direct_v2/threads/111111111111111111111111111111111111111/items/11111111111111111111111111111111111","value":"{\"item_id\": \"11111111111111111111111111111111111\", \"user_id\": 1111111111, \"timestamp\": 1111111111111111, \"item_type\": \"text\", \"text\": \"TEXT\"}","doublePublish":true},{"op":"replace","path":"/direct_v2/inbox/unseen_count","value":"1","ts":"1111111111111111","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadUpdate()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-updated',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(DirectThread::class, $arguments[1]);
+ /** @var DirectThread $thread */
+ $thread = $arguments[1];
+ $this->assertEquals('111111111111111111111111111111111111111', $thread->getThreadId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"replace","path":"/direct_v2/inbox/threads/111111111111111111111111111111111111111","value":"{\"thread_id\": \"111111111111111111111111111111111111111\"}","doublePublish":true}],"publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadItemRemoval()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-item-removed',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('11111111111111111111111111111111111', $arguments[1]);
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"remove","path":"/direct_v2/threads/111111111111111111111111111111111111111/items/11111111111111111111111111111111111","value":"11111111111111111111111111111111111","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadActivity()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-activity',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(ThreadActivity::class, $arguments[1]);
+ /** @var ThreadActivity $activity */
+ $activity = $arguments[1];
+ $this->assertEquals(1, $activity->getActivityStatus());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/direct_v2/threads/111111111111111111111111111111111111111/activity_indicator_id/deadbeef-dead-beef-dead-beefdeadbeef","value":"{\"timestamp\": 1111111111111111, \"sender_id\": \"1111111111\", \"ttl\": 12000, \"activity_status\": 1}","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadHasSeen()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'thread-seen',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(3, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('1111111111', $arguments[1]);
+ $this->assertArrayHasKey(2, $arguments);
+ $this->assertInstanceOf(DirectThreadLastSeenAt::class, $arguments[2]);
+ /** @var DirectThreadLastSeenAt $lastSeen */
+ $lastSeen = $arguments[2];
+ $this->assertEquals('11111111111111111111111111111111111', $lastSeen->getItemId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"replace","path":"/direct_v2/threads/111111111111111111111111111111111111111/participants/1111111111/has_seen","value":"{\"timestamp\": 1111111111111111, \"item_id\": 11111111111111111111111111111111111}","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+
+ public function testThreadActionBadge()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'direct-story-action',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(ActionBadge::class, $arguments[1]);
+ /** @var ActionBadge $actionBadge */
+ $actionBadge = $arguments[1];
+ $this->assertEquals('raven_delivered', $actionBadge->getActionType());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"replace","path":"/direct_v2/visual_action_badge/111111111111111111111111111111111111111","value":"{\"action_type\": \"raven_delivered\", \"action_count\": 1, \"action_timestamp\": 1111111111111111}","doublePublish":true}],"lazy":false,"publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":3}'
+ ));
+ }
+
+ public function testStoryCreation()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ // listeners() call is at index 0.
+ $target->expects($this->at(1))->method('emit')->with(
+ 'direct-story-updated',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(3, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('111111111111111111111111111111111111111', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertEquals('11111111111111111111111111111111111', $arguments[1]);
+ $this->assertArrayHasKey(2, $arguments);
+ $this->assertInstanceOf(DirectThreadItem::class, $arguments[2]);
+ /** @var DirectThreadItem $item */
+ $item = $arguments[2];
+ $this->assertEquals('raven_media', $item->getItemType());
+
+ return true;
+ })
+ );
+ // listeners() call is at index 2.
+ $target->expects($this->at(3))->method('emit')->with(
+ 'unseen-count-update',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(2, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('visual_inbox', $arguments[0]);
+ $this->assertArrayHasKey(1, $arguments);
+ $this->assertInstanceOf(DirectSeenItemPayload::class, $arguments[1]);
+ /** @var DirectSeenItemPayload $payload */
+ $payload = $arguments[1];
+ $this->assertEquals(1, $payload->getCount());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/direct_v2/visual_threads/111111111111111111111111111111111111111/items/11111111111111111111111111111111111","value":"{\"item_id\": \"11111111111111111111111111111111111\", \"user_id\": 1111111111, \"timestamp\": 1111111111111111, \"item_type\": \"raven_media\", \"seen_user_ids\": [], \"reply_chain_count\": 0, \"view_mode\": \"once\"}","doublePublish":true},{"op":"replace","path":"/direct_v2/visual_inbox/unseen_count","value":"1","ts":"1111111111111111","doublePublish":true}],"version":"9.6.0","publish_metadata":{"topic_publish_id":-1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":3}'
+ ));
+ }
+
+ public function testSendItemAck()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'client-context-ack',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(AckAction::class, $arguments[0]);
+ /** @var AckAction $ack */
+ $ack = $arguments[0];
+ $this->assertEquals('11111111111111111111111111111111111', $ack->getPayload()->getItemId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"action": "item_ack", "status_code": "200", "payload": {"client_context": "deadbeef-dead-beef-dead-beefdeadbeef", "item_id": "11111111111111111111111111111111111", "timestamp": "1111111111111111", "thread_id": "111111111111111111111111111111111111111"}, "status": "ok"}'
+ ));
+ }
+
+ public function testActivityAck()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'client-context-ack',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(AckAction::class, $arguments[0]);
+ /** @var AckAction $ack */
+ $ack = $arguments[0];
+ $this->assertEquals('deadbeef-dead-beef-dead-beefdeadbeef', $ack->getPayload()->getClientContext());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new DirectHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"action": "item_ack", "status_code": "200", "payload": {"activity_status": 1, "indicate_activity_ts": 1111111111111111, "client_context": "deadbeef-dead-beef-dead-beefdeadbeef", "ttl": 10000}, "status": "ok"}'
+ ));
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Handler/IrisHandlerTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Handler/IrisHandlerTest.php
new file mode 100755
index 0000000..c8a15d7
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Handler/IrisHandlerTest.php
@@ -0,0 +1,77 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'iris-subscribed',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(IrisSubscribeAck::class, $arguments[0]);
+ /** @var IrisSubscribeAck $ack */
+ $ack = $arguments[0];
+ $this->assertTrue($ack->getSucceeded());
+ $this->assertEquals(666, $ack->getSeqId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new IrisHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"succeeded":true,"seq_id":666,"error_type":null,"error_message":null,"subscribed_at_ms":1111111111111}'
+ ));
+ }
+
+ public function testQueueOverlow()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('IrisQueueOverflowException');
+
+ $handler = new IrisHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"succeeded":false,"seq_id":null,"error_type":1,"error_message":"IrisQueueOverflowException","subscribed_at_ms":null}'
+ ));
+ }
+
+ public function testQueueUnderflow()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('IrisQueueUnderflowException');
+
+ $handler = new IrisHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"succeeded":false,"seq_id":null,"error_type":1,"error_message":"IrisQueueUnderflowException","subscribed_at_ms":null}'
+ ));
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Handler/LiveTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Handler/LiveTest.php
new file mode 100755
index 0000000..df3663e
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Handler/LiveTest.php
@@ -0,0 +1,74 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'live-started',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(LiveBroadcast::class, $arguments[0]);
+ /** @var LiveBroadcast $live */
+ $live = $arguments[0];
+ $this->assertEquals('11111111111111111', $live->getBroadcastId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new LiveHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"add","path":"/broadcast/11111111111111111/started","value":"{\"broadcast_id\": 11111111111111111, \"user\": {\"pk\": 1111111111, \"username\": \"USERNAME\", \"full_name\": \"\", \"is_private\": true, \"profile_pic_url\": \"\", \"profile_pic_id\": \"\", \"is_verified\": false}, \"published_time\": 1234567890, \"is_periodic\": 0, \"broadcast_message\": \"\", \"display_notification\": true}","doublePublish":true}],"lazy":false,"publisher":1111111111,"version":"9.7.0","publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":2}'
+ ));
+ }
+
+ public function testStopBroadcast()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'live-stopped',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(LiveBroadcast::class, $arguments[0]);
+ /** @var LiveBroadcast $live */
+ $live = $arguments[0];
+ $this->assertEquals('11111111111111111', $live->getBroadcastId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new LiveHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"event":"patch","data":[{"op":"remove","path":"/broadcast/11111111111111111/ended","value":"{\"broadcast_id\": 11111111111111111, \"user\": {\"pk\": 1111111111, \"username\": \"USERNAME\", \"full_name\": \"NAME\", \"is_private\": true, \"profile_pic_url\": \"\", \"profile_pic_id\": \"\", \"is_verified\": false}, \"published_time\": 1234567890, \"is_periodic\": 0, \"broadcast_message\": \"\", \"display_notification\": false}","doublePublish":true}],"lazy":false,"publisher":1111111111,"version":"10.8.0","publish_metadata":{"topic_publish_id":1111111111111111111,"publish_time_ms":"1970-01-01 00:00:00.000000"},"num_endpoints":1}'
+ ));
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Handler/PresenceTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Handler/PresenceTest.php
new file mode 100755
index 0000000..ea9c2a2
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Handler/PresenceTest.php
@@ -0,0 +1,63 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'presence',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertInstanceOf(UserPresence::class, $arguments[0]);
+ /** @var UserPresence $payload */
+ $payload = $arguments[0];
+ $this->assertEquals('1111111111', $payload->getUserId());
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new PresenceHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{"presence_event":{"user_id":"1111111111","is_active":true,"last_activity_at_ms":"123456789012","in_threads":null}}'
+ ));
+ }
+
+ public function testInvalidData()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('Invalid presence');
+
+ $handler = new PresenceHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ '{}'
+ ));
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Handler/RegionHintTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Handler/RegionHintTest.php
new file mode 100755
index 0000000..d0f6f58
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Handler/RegionHintTest.php
@@ -0,0 +1,71 @@
+createMock(EventEmitterInterface::class);
+ $target->expects($this->once())->method('emit')->with(
+ 'region-hint',
+ $this->callback(function ($arguments) {
+ $this->assertInternalType('array', $arguments);
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey(0, $arguments);
+ $this->assertEquals('ASH', $arguments[0]);
+
+ return true;
+ })
+ );
+ $target->expects($this->any())->method('listeners')->willReturn([1]);
+
+ $handler = new RegionHintHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ 'ASH'
+ ));
+ }
+
+ public function testEmptyRegionHint()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('Invalid region hint');
+
+ $handler = new RegionHintHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ ''
+ ));
+ }
+
+ public function testNullRegionHint()
+ {
+ $target = $this->createMock(EventEmitterInterface::class);
+
+ $this->expectException(HandlerException::class);
+ $this->expectExceptionMessage('Invalid region hint');
+
+ $handler = new RegionHintHandler($target);
+ $handler->handleMessage($this->_buildMessage(
+ null
+ ));
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Subscription/AppPresenceTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Subscription/AppPresenceTest.php
new file mode 100755
index 0000000..64b6d54
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Subscription/AppPresenceTest.php
@@ -0,0 +1,18 @@
+assertEquals(
+ '1/graphqlsubscriptions/17846944882223835/{"input_data":{"client_subscription_id":"deadbeef-dead-beef-dead-beefdeadbeef"}}',
+ (string) $subscription
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Realtime/Subscription/ZeroProvisionTest.php b/vendor/mgp25/instagram-php/tests/Realtime/Subscription/ZeroProvisionTest.php
new file mode 100755
index 0000000..993e2fa
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Realtime/Subscription/ZeroProvisionTest.php
@@ -0,0 +1,18 @@
+assertRegExp(
+ '#^1/graphqlsubscriptions/17913953740109069/{"input_data":{"client_subscription_id":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}","device_id":"deadbeef-dead-beef-dead-beefdeadbeef"}}#',
+ (string) $subscription
+ );
+ }
+}
diff --git a/vendor/mgp25/instagram-php/tests/Response/ExceptionsTest.php b/vendor/mgp25/instagram-php/tests/Response/ExceptionsTest.php
new file mode 100755
index 0000000..2b20ace
--- /dev/null
+++ b/vendor/mgp25/instagram-php/tests/Response/ExceptionsTest.php
@@ -0,0 +1,112 @@
+expectException(LoginRequiredException::class);
+ $this->expectExceptionMessage('Login required');
+ $response = $this->_makeResponse('{"message":"login_required", "logout_reason": 2, "status": "fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testFeedbackRequiredException()
+ {
+ $this->expectException(FeedbackRequiredException::class);
+ $this->expectExceptionMessage('Feedback required');
+ $response = $this->_makeResponse('{"message":"feedback_required","spam":true,"feedback_title":"You\u2019re Temporarily Blocked","feedback_message":"It looks like you were misusing this feature by going too fast. You\u2019ve been blocked from using it.\n\nLearn more about blocks in the Help Center. We restrict certain content and actions to protect our community. Tell us if you think we made a mistake.","feedback_url":"WUT","feedback_appeal_label":"Report problem","feedback_ignore_label":"OK","feedback_action":"report_problem","status":"fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testConsentRequiredException()
+ {
+ $this->expectException(ConsentRequiredException::class);
+ $this->expectExceptionMessage('Consent required');
+ $response = $this->_makeResponse('{"message":"consent_required","consent_data":{"headline":"Updates to Our Terms and Data Policy","content":"We\'ve updated our Terms and made some changes to our Data Policy. Please take a moment to review these changes and let us know that you agree to them.\n\nYou need to finish reviewing this information before you can use Instagram.","button_text":"Review Now"},"status":"fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testCheckpointRequiredException()
+ {
+ $this->expectException(CheckpointRequiredException::class);
+ $this->expectExceptionMessage('Checkpoint required');
+ $response = $this->_makeResponse('{"message":"checkpoint_required","checkpoint_url":"WUT","lock":true,"status":"fail","error_type":"checkpoint_challenge_required"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testChallengeRequiredException()
+ {
+ $this->expectException(ChallengeRequiredException::class);
+ $this->expectExceptionMessage('Challenge required');
+ $response = $this->_makeResponse('{"message":"challenge_required","challenge":{"url":"https://i.instagram.com/challenge/","api_path":"/challenge/","hide_webview_header":false,"lock":true,"logout":false,"native_flow":true},"status":"fail"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testIncorrectPasswordException()
+ {
+ $this->expectException(IncorrectPasswordException::class);
+ $this->expectExceptionMessageRegExp('/password.*incorrect/i');
+ $response = $this->_makeResponse('{"message":"The password you entered is incorrect. Please try again.","invalid_credentials":true,"error_title":"Incorrect password for WUT","buttons":[{"title":"Try Again","action":"dismiss"}],"status":"fail","error_type":"bad_password"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testAccountDisabledException()
+ {
+ $this->expectException(AccountDisabledException::class);
+ $this->expectExceptionMessageRegExp('/account.*disabled/i');
+ $response = $this->_makeResponse('{"message":"Your account has been disabled for violating our terms. Learn how you may be able to restore your account."}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testInvalidUserException()
+ {
+ $this->expectException(InvalidUserException::class);
+ $this->expectExceptionMessageRegExp('/check.*username/i');
+ $response = $this->_makeResponse('{"message":"The username you entered doesn\'t appear to belong to an account. Please check your username and try again.","invalid_credentials":true,"error_title":"Incorrect Username","buttons":[{"title":"Try Again","action":"dismiss"}],"status":"fail","error_type":"invalid_user"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testSentryBlockException()
+ {
+ $this->expectException(SentryBlockException::class);
+ $this->expectExceptionMessageRegExp('/problem.*request/i');
+ $response = $this->_makeResponse('{"message":"Sorry, there was a problem with your request.","status":"fail","error_type":"sentry_block"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+
+ public function testInvalidSmsCodeException()
+ {
+ $this->expectException(InvalidSmsCodeException::class);
+ $this->expectExceptionMessageRegExp('/check.*code/i');
+ $response = $this->_makeResponse('{"message":"Please check the security code we sent you and try again.","status":"fail","error_type":"sms_code_validation_code_invalid"}');
+ ServerMessageThrower::autoThrow(null, $response->getMessage(), $response);
+ }
+}
diff --git a/vendor/mgp25/instagram-php/webwarning.htm b/vendor/mgp25/instagram-php/webwarning.htm
new file mode 100755
index 0000000..a908f74
--- /dev/null
+++ b/vendor/mgp25/instagram-php/webwarning.htm
@@ -0,0 +1,9 @@
+
Instagram-API Warning!
+
Can I run this library via a website?
+
No. Don't do it. You cannot safely use this or any other 3rd party Instagram libraries directly via a website!
+
This library (and all other 3rd party reverse engineered Instagram libraries), is made for command line usage in a terminal by running the script like a program (such as php yourscript.php). We do not recommend running this library via a web browser! Because browsers will terminate the PHP process as soon as the user closes their connection to the page (closes the tab, or presses "Stop loading page"), which means that your script can terminate at any moment. This library is not a webpage! It is an Instagram for Android application emulator. It emulates an application. Not a webpage. You cannot just randomly kill this library in the middle of work! (You might kill it while it's writing to disk or calling some important APIs!)
+
And furthermore, if it's used on a webpage then it must waste time logging in to Instagram every time the user refreshes the webpage, since each page load would start a new script (meaning "a new Instagram app emulator") from scratch. And there are also massive risks about concurrent access from multiple web requests to a single user account which can corrupt your settings-storage or cookies. So we really, really do not recommend running this library via a browser! It's a very bad idea!
+
Instead, you should run your Instagram app emulator script as a permanent 24/7 process, and make it dump data to a database which your regular website reads from, or make some kind of permanent localhost daemon that can listen locally on the server and receive queries on a port (localhost queries from the main website's scripts), and then performing those queries via the API and responding to the website script with something like JSON or with serialized src/Response/ objects, which the website script then transforms for final display to the user.
+
You must also be sure that your permanent process periodically calls login() inside your 24/7 PHP process, to emulate the Instagram for Android application being closed/opened and refreshing itself (like a real user would do). Otherwise it will soon get banned or silently limited as a "detected bot" by Instagram due to never calling login. Preferably use the same refresh ranges we use by default, so that you refresh login() within a random range between every 30 minutes, or at max every 6 hours.
+
So, to recap: Make your "Instagram application" part a permanent process, and then make your webpage interact with that permanent process or an intermediary database in some way. That's the proper architecture system if you want to use this library online! Imagine it like your website talking to a phone and telling its permanently running Instagram app to do things. That's the proper design and that's how you have to think about things.
+
Never forget this fact: This library (and all other 3rd party libraries) is an Android application emulator. It is not a website and will never be able to become a website (because the Android application is not a website!). Therefore, we will never give support to anyone who decides to use the library directly inside a webpage. If you do that, it's at your own risk! It is a really terrible and unsupported idea!
diff --git a/vendor/psr/http-message/CHANGELOG.md b/vendor/psr/http-message/CHANGELOG.md
new file mode 100755
index 0000000..74b1ef9
--- /dev/null
+++ b/vendor/psr/http-message/CHANGELOG.md
@@ -0,0 +1,36 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.0.1 - 2016-08-06
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Updated all `@return self` annotation references in interfaces to use
+ `@return static`, which more closelly follows the semantics of the
+ specification.
+- Updated the `MessageInterface::getHeaders()` return annotation to use the
+ value `string[][]`, indicating the format is a nested array of strings.
+- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
+ to point to the correct section of RFC 7230.
+- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
+ to add the parameter name (`$uploadedFiles`).
+- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
+ method to correctly reference the method parameter (it was referencing an
+ incorrect parameter name previously).
+
+## 1.0.0 - 2016-05-18
+
+Initial stable release; reflects accepted PSR-7 specification.
diff --git a/vendor/psr/http-message/LICENSE b/vendor/psr/http-message/LICENSE
new file mode 100755
index 0000000..c2d8e45
--- /dev/null
+++ b/vendor/psr/http-message/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/psr/http-message/README.md b/vendor/psr/http-message/README.md
new file mode 100755
index 0000000..2818533
--- /dev/null
+++ b/vendor/psr/http-message/README.md
@@ -0,0 +1,13 @@
+PSR Http Message
+================
+
+This repository holds all interfaces/classes/traits related to
+[PSR-7](http://www.php-fig.org/psr/psr-7/).
+
+Note that this is not a HTTP message implementation of its own. It is merely an
+interface that describes a HTTP message. See the specification for more details.
+
+Usage
+-----
+
+We'll certainly need some stuff in here.
\ No newline at end of file
diff --git a/vendor/psr/http-message/composer.json b/vendor/psr/http-message/composer.json
new file mode 100755
index 0000000..b0d2937
--- /dev/null
+++ b/vendor/psr/http-message/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/http-message",
+ "description": "Common interface for HTTP messages",
+ "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
+ "homepage": "https://github.com/php-fig/http-message",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/vendor/psr/http-message/src/MessageInterface.php b/vendor/psr/http-message/src/MessageInterface.php
new file mode 100755
index 0000000..dd46e5e
--- /dev/null
+++ b/vendor/psr/http-message/src/MessageInterface.php
@@ -0,0 +1,187 @@
+getHeaders() as $name => $values) {
+ * echo $name . ": " . implode(", ", $values);
+ * }
+ *
+ * // Emit headers iteratively:
+ * foreach ($message->getHeaders() as $name => $values) {
+ * foreach ($values as $value) {
+ * header(sprintf('%s: %s', $name, $value), false);
+ * }
+ * }
+ *
+ * While header names are not case-sensitive, getHeaders() will preserve the
+ * exact case in which headers were originally specified.
+ *
+ * @return string[][] Returns an associative array of the message's headers. Each
+ * key MUST be a header name, and each value MUST be an array of strings
+ * for that header.
+ */
+ public function getHeaders();
+
+ /**
+ * Checks if a header exists by the given case-insensitive name.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return bool Returns true if any header names match the given header
+ * name using a case-insensitive string comparison. Returns false if
+ * no matching header name is found in the message.
+ */
+ public function hasHeader($name);
+
+ /**
+ * Retrieves a message header value by the given case-insensitive name.
+ *
+ * This method returns an array of all the header values of the given
+ * case-insensitive header name.
+ *
+ * If the header does not appear in the message, this method MUST return an
+ * empty array.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string[] An array of string values as provided for the given
+ * header. If the header does not appear in the message, this method MUST
+ * return an empty array.
+ */
+ public function getHeader($name);
+
+ /**
+ * Retrieves a comma-separated string of the values for a single header.
+ *
+ * This method returns all of the header values of the given
+ * case-insensitive header name as a string concatenated together using
+ * a comma.
+ *
+ * NOTE: Not all header values may be appropriately represented using
+ * comma concatenation. For such headers, use getHeader() instead
+ * and supply your own delimiter when concatenating.
+ *
+ * If the header does not appear in the message, this method MUST return
+ * an empty string.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @return string A string of values as provided for the given header
+ * concatenated together using a comma. If the header does not appear in
+ * the message, this method MUST return an empty string.
+ */
+ public function getHeaderLine($name);
+
+ /**
+ * Return an instance with the provided value replacing the specified header.
+ *
+ * While header names are case-insensitive, the casing of the header will
+ * be preserved by this function, and returned from getHeaders().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new and/or updated header and value.
+ *
+ * @param string $name Case-insensitive header field name.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withHeader($name, $value);
+
+ /**
+ * Return an instance with the specified header appended with the given value.
+ *
+ * Existing values for the specified header will be maintained. The new
+ * value(s) will be appended to the existing list. If the header did not
+ * exist previously, it will be added.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * new header and/or value.
+ *
+ * @param string $name Case-insensitive header field name to add.
+ * @param string|string[] $value Header value(s).
+ * @return static
+ * @throws \InvalidArgumentException for invalid header names or values.
+ */
+ public function withAddedHeader($name, $value);
+
+ /**
+ * Return an instance without the specified header.
+ *
+ * Header resolution MUST be done without case-sensitivity.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the named header.
+ *
+ * @param string $name Case-insensitive header field name to remove.
+ * @return static
+ */
+ public function withoutHeader($name);
+
+ /**
+ * Gets the body of the message.
+ *
+ * @return StreamInterface Returns the body as a stream.
+ */
+ public function getBody();
+
+ /**
+ * Return an instance with the specified message body.
+ *
+ * The body MUST be a StreamInterface object.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return a new instance that has the
+ * new body stream.
+ *
+ * @param StreamInterface $body Body.
+ * @return static
+ * @throws \InvalidArgumentException When the body is not valid.
+ */
+ public function withBody(StreamInterface $body);
+}
diff --git a/vendor/psr/http-message/src/RequestInterface.php b/vendor/psr/http-message/src/RequestInterface.php
new file mode 100755
index 0000000..a96d4fd
--- /dev/null
+++ b/vendor/psr/http-message/src/RequestInterface.php
@@ -0,0 +1,129 @@
+getQuery()`
+ * or from the `QUERY_STRING` server param.
+ *
+ * @return array
+ */
+ public function getQueryParams();
+
+ /**
+ * Return an instance with the specified query string arguments.
+ *
+ * These values SHOULD remain immutable over the course of the incoming
+ * request. They MAY be injected during instantiation, such as from PHP's
+ * $_GET superglobal, or MAY be derived from some other value such as the
+ * URI. In cases where the arguments are parsed from the URI, the data
+ * MUST be compatible with what PHP's parse_str() would return for
+ * purposes of how duplicate query parameters are handled, and how nested
+ * sets are handled.
+ *
+ * Setting query string arguments MUST NOT change the URI stored by the
+ * request, nor the values in the server params.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated query string arguments.
+ *
+ * @param array $query Array of query string arguments, typically from
+ * $_GET.
+ * @return static
+ */
+ public function withQueryParams(array $query);
+
+ /**
+ * Retrieve normalized file upload data.
+ *
+ * This method returns upload metadata in a normalized tree, with each leaf
+ * an instance of Psr\Http\Message\UploadedFileInterface.
+ *
+ * These values MAY be prepared from $_FILES or the message body during
+ * instantiation, or MAY be injected via withUploadedFiles().
+ *
+ * @return array An array tree of UploadedFileInterface instances; an empty
+ * array MUST be returned if no data is present.
+ */
+ public function getUploadedFiles();
+
+ /**
+ * Create a new instance with the specified uploaded files.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
+ * @return static
+ * @throws \InvalidArgumentException if an invalid structure is provided.
+ */
+ public function withUploadedFiles(array $uploadedFiles);
+
+ /**
+ * Retrieve any parameters provided in the request body.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, this method MUST
+ * return the contents of $_POST.
+ *
+ * Otherwise, this method may return any results of deserializing
+ * the request body content; as parsing returns structured content, the
+ * potential types MUST be arrays or objects only. A null value indicates
+ * the absence of body content.
+ *
+ * @return null|array|object The deserialized body parameters, if any.
+ * These will typically be an array or object.
+ */
+ public function getParsedBody();
+
+ /**
+ * Return an instance with the specified body parameters.
+ *
+ * These MAY be injected during instantiation.
+ *
+ * If the request Content-Type is either application/x-www-form-urlencoded
+ * or multipart/form-data, and the request method is POST, use this method
+ * ONLY to inject the contents of $_POST.
+ *
+ * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
+ * deserializing the request body content. Deserialization/parsing returns
+ * structured data, and, as such, this method ONLY accepts arrays or objects,
+ * or a null value if nothing was available to parse.
+ *
+ * As an example, if content negotiation determines that the request data
+ * is a JSON payload, this method could be used to create a request
+ * instance with the deserialized parameters.
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated body parameters.
+ *
+ * @param null|array|object $data The deserialized body data. This will
+ * typically be in an array or object.
+ * @return static
+ * @throws \InvalidArgumentException if an unsupported argument type is
+ * provided.
+ */
+ public function withParsedBody($data);
+
+ /**
+ * Retrieve attributes derived from the request.
+ *
+ * The request "attributes" may be used to allow injection of any
+ * parameters derived from the request: e.g., the results of path
+ * match operations; the results of decrypting cookies; the results of
+ * deserializing non-form-encoded message bodies; etc. Attributes
+ * will be application and request specific, and CAN be mutable.
+ *
+ * @return array Attributes derived from the request.
+ */
+ public function getAttributes();
+
+ /**
+ * Retrieve a single derived request attribute.
+ *
+ * Retrieves a single derived request attribute as described in
+ * getAttributes(). If the attribute has not been previously set, returns
+ * the default value as provided.
+ *
+ * This method obviates the need for a hasAttribute() method, as it allows
+ * specifying a default value to return if the attribute is not found.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $default Default value to return if the attribute does not exist.
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null);
+
+ /**
+ * Return an instance with the specified derived request attribute.
+ *
+ * This method allows setting a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that has the
+ * updated attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @param mixed $value The value of the attribute.
+ * @return static
+ */
+ public function withAttribute($name, $value);
+
+ /**
+ * Return an instance that removes the specified derived request attribute.
+ *
+ * This method allows removing a single derived request attribute as
+ * described in getAttributes().
+ *
+ * This method MUST be implemented in such a way as to retain the
+ * immutability of the message, and MUST return an instance that removes
+ * the attribute.
+ *
+ * @see getAttributes()
+ * @param string $name The attribute name.
+ * @return static
+ */
+ public function withoutAttribute($name);
+}
diff --git a/vendor/psr/http-message/src/StreamInterface.php b/vendor/psr/http-message/src/StreamInterface.php
new file mode 100755
index 0000000..f68f391
--- /dev/null
+++ b/vendor/psr/http-message/src/StreamInterface.php
@@ -0,0 +1,158 @@
+
+ * [user-info@]host[:port]
+ *
+ *
+ * If the port component is not set or is the standard port for the current
+ * scheme, it SHOULD NOT be included.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-3.2
+ * @return string The URI authority, in "[user-info@]host[:port]" format.
+ */
+ public function getAuthority();
+
+ /**
+ * Retrieve the user information component of the URI.
+ *
+ * If no user information is present, this method MUST return an empty
+ * string.
+ *
+ * If a user is present in the URI, this will return that value;
+ * additionally, if the password is also present, it will be appended to the
+ * user value, with a colon (":") separating the values.
+ *
+ * The trailing "@" character is not part of the user information and MUST
+ * NOT be added.
+ *
+ * @return string The URI user information, in "username[:password]" format.
+ */
+ public function getUserInfo();
+
+ /**
+ * Retrieve the host component of the URI.
+ *
+ * If no host is present, this method MUST return an empty string.
+ *
+ * The value returned MUST be normalized to lowercase, per RFC 3986
+ * Section 3.2.2.
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
+ * @return string The URI host.
+ */
+ public function getHost();
+
+ /**
+ * Retrieve the port component of the URI.
+ *
+ * If a port is present, and it is non-standard for the current scheme,
+ * this method MUST return it as an integer. If the port is the standard port
+ * used with the current scheme, this method SHOULD return null.
+ *
+ * If no port is present, and no scheme is present, this method MUST return
+ * a null value.
+ *
+ * If no port is present, but a scheme is present, this method MAY return
+ * the standard port for that scheme, but SHOULD return null.
+ *
+ * @return null|int The URI port.
+ */
+ public function getPort();
+
+ /**
+ * Retrieve the path component of the URI.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * Normally, the empty path "" and absolute path "/" are considered equal as
+ * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
+ * do this normalization because in contexts with a trimmed base path, e.g.
+ * the front controller, this difference becomes significant. It's the task
+ * of the user to handle both "" and "/".
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.3.
+ *
+ * As an example, if the value should include a slash ("/") not intended as
+ * delimiter between path segments, that value MUST be passed in encoded
+ * form (e.g., "%2F") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.3
+ * @return string The URI path.
+ */
+ public function getPath();
+
+ /**
+ * Retrieve the query string of the URI.
+ *
+ * If no query string is present, this method MUST return an empty string.
+ *
+ * The leading "?" character is not part of the query and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.4.
+ *
+ * As an example, if a value in a key/value pair of the query string should
+ * include an ampersand ("&") not intended as a delimiter between values,
+ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.4
+ * @return string The URI query string.
+ */
+ public function getQuery();
+
+ /**
+ * Retrieve the fragment component of the URI.
+ *
+ * If no fragment is present, this method MUST return an empty string.
+ *
+ * The leading "#" character is not part of the fragment and MUST NOT be
+ * added.
+ *
+ * The value returned MUST be percent-encoded, but MUST NOT double-encode
+ * any characters. To determine what characters to encode, please refer to
+ * RFC 3986, Sections 2 and 3.5.
+ *
+ * @see https://tools.ietf.org/html/rfc3986#section-2
+ * @see https://tools.ietf.org/html/rfc3986#section-3.5
+ * @return string The URI fragment.
+ */
+ public function getFragment();
+
+ /**
+ * Return an instance with the specified scheme.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified scheme.
+ *
+ * Implementations MUST support the schemes "http" and "https" case
+ * insensitively, and MAY accommodate other schemes if required.
+ *
+ * An empty scheme is equivalent to removing the scheme.
+ *
+ * @param string $scheme The scheme to use with the new instance.
+ * @return static A new instance with the specified scheme.
+ * @throws \InvalidArgumentException for invalid or unsupported schemes.
+ */
+ public function withScheme($scheme);
+
+ /**
+ * Return an instance with the specified user information.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified user information.
+ *
+ * Password is optional, but the user information MUST include the
+ * user; an empty string for the user is equivalent to removing user
+ * information.
+ *
+ * @param string $user The user name to use for authority.
+ * @param null|string $password The password associated with $user.
+ * @return static A new instance with the specified user information.
+ */
+ public function withUserInfo($user, $password = null);
+
+ /**
+ * Return an instance with the specified host.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified host.
+ *
+ * An empty host value is equivalent to removing the host.
+ *
+ * @param string $host The hostname to use with the new instance.
+ * @return static A new instance with the specified host.
+ * @throws \InvalidArgumentException for invalid hostnames.
+ */
+ public function withHost($host);
+
+ /**
+ * Return an instance with the specified port.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified port.
+ *
+ * Implementations MUST raise an exception for ports outside the
+ * established TCP and UDP port ranges.
+ *
+ * A null value provided for the port is equivalent to removing the port
+ * information.
+ *
+ * @param null|int $port The port to use with the new instance; a null value
+ * removes the port information.
+ * @return static A new instance with the specified port.
+ * @throws \InvalidArgumentException for invalid ports.
+ */
+ public function withPort($port);
+
+ /**
+ * Return an instance with the specified path.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified path.
+ *
+ * The path can either be empty or absolute (starting with a slash) or
+ * rootless (not starting with a slash). Implementations MUST support all
+ * three syntaxes.
+ *
+ * If the path is intended to be domain-relative rather than path relative then
+ * it must begin with a slash ("/"). Paths not starting with a slash ("/")
+ * are assumed to be relative to some base path known to the application or
+ * consumer.
+ *
+ * Users can provide both encoded and decoded path characters.
+ * Implementations ensure the correct encoding as outlined in getPath().
+ *
+ * @param string $path The path to use with the new instance.
+ * @return static A new instance with the specified path.
+ * @throws \InvalidArgumentException for invalid paths.
+ */
+ public function withPath($path);
+
+ /**
+ * Return an instance with the specified query string.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified query string.
+ *
+ * Users can provide both encoded and decoded query characters.
+ * Implementations ensure the correct encoding as outlined in getQuery().
+ *
+ * An empty query string value is equivalent to removing the query string.
+ *
+ * @param string $query The query string to use with the new instance.
+ * @return static A new instance with the specified query string.
+ * @throws \InvalidArgumentException for invalid query strings.
+ */
+ public function withQuery($query);
+
+ /**
+ * Return an instance with the specified URI fragment.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified URI fragment.
+ *
+ * Users can provide both encoded and decoded fragment characters.
+ * Implementations ensure the correct encoding as outlined in getFragment().
+ *
+ * An empty fragment value is equivalent to removing the fragment.
+ *
+ * @param string $fragment The fragment to use with the new instance.
+ * @return static A new instance with the specified fragment.
+ */
+ public function withFragment($fragment);
+
+ /**
+ * Return the string representation as a URI reference.
+ *
+ * Depending on which components of the URI are present, the resulting
+ * string is either a full URI or relative reference according to RFC 3986,
+ * Section 4.1. The method concatenates the various components of the URI,
+ * using the appropriate delimiters:
+ *
+ * - If a scheme is present, it MUST be suffixed by ":".
+ * - If an authority is present, it MUST be prefixed by "//".
+ * - The path can be concatenated without delimiters. But there are two
+ * cases where the path has to be adjusted to make the URI reference
+ * valid as PHP does not allow to throw an exception in __toString():
+ * - If the path is rootless and an authority is present, the path MUST
+ * be prefixed by "/".
+ * - If the path is starting with more than one "/" and no authority is
+ * present, the starting slashes MUST be reduced to one.
+ * - If a query is present, it MUST be prefixed by "?".
+ * - If a fragment is present, it MUST be prefixed by "#".
+ *
+ * @see http://tools.ietf.org/html/rfc3986#section-4.1
+ * @return string
+ */
+ public function __toString();
+}
diff --git a/vendor/psr/log/.gitignore b/vendor/psr/log/.gitignore
new file mode 100755
index 0000000..22d0d82
--- /dev/null
+++ b/vendor/psr/log/.gitignore
@@ -0,0 +1 @@
+vendor
diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE
new file mode 100755
index 0000000..474c952
--- /dev/null
+++ b/vendor/psr/log/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php
new file mode 100755
index 0000000..90e721a
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/AbstractLogger.php
@@ -0,0 +1,128 @@
+log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php
new file mode 100755
index 0000000..67f852d
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+logger = $logger;
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php
new file mode 100755
index 0000000..e695046
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LoggerInterface.php
@@ -0,0 +1,125 @@
+log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ abstract public function log($level, $message, array $context = array());
+}
diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php
new file mode 100755
index 0000000..c8f7293
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/NullLogger.php
@@ -0,0 +1,30 @@
+logger) { }`
+ * blocks.
+ */
+class NullLogger extends AbstractLogger
+{
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Psr\Log\InvalidArgumentException
+ */
+ public function log($level, $message, array $context = array())
+ {
+ // noop
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
new file mode 100755
index 0000000..8e445ee
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
@@ -0,0 +1,145 @@
+ ".
+ *
+ * Example ->error('Foo') would yield "error Foo".
+ *
+ * @return string[]
+ */
+ abstract public function getLogs();
+
+ public function testImplements()
+ {
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @dataProvider provideLevelsAndMessages
+ */
+ public function testLogsAtAllLevels($level, $message)
+ {
+ $logger = $this->getLogger();
+ $logger->{$level}($message, array('user' => 'Bob'));
+ $logger->log($level, $message, array('user' => 'Bob'));
+
+ $expected = array(
+ $level.' message of level '.$level.' with context: Bob',
+ $level.' message of level '.$level.' with context: Bob',
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function provideLevelsAndMessages()
+ {
+ return array(
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+ );
+ }
+
+ /**
+ * @expectedException \Psr\Log\InvalidArgumentException
+ */
+ public function testThrowsOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+ $logger->log('invalid level', 'Foo');
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+ $expected = array('info {Message {nothing} Bob Bar a}');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testObjectCastToString()
+ {
+ if (method_exists($this, 'createPartialMock')) {
+ $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
+ } else {
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+ }
+ $dummy->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('DUMMY'));
+
+ $this->getLogger()->warning($dummy);
+
+ $expected = array('warning DUMMY');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextCanContainAnything()
+ {
+ $closed = fopen('php://memory', 'r');
+ fclose($closed);
+
+ $context = array(
+ 'bool' => true,
+ 'null' => null,
+ 'string' => 'Foo',
+ 'int' => 0,
+ 'float' => 0.5,
+ 'nested' => array('with object' => new DummyTest),
+ 'object' => new \DateTime,
+ 'resource' => fopen('php://memory', 'r'),
+ 'closed' => $closed,
+ );
+
+ $this->getLogger()->warning('Crazy context data', $context);
+
+ $expected = array('warning Crazy context data');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+ {
+ $logger = $this->getLogger();
+ $logger->warning('Random message', array('exception' => 'oops'));
+ $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+
+ $expected = array(
+ 'warning Random message',
+ 'critical Uncaught Exception!'
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+}
+
+class DummyTest
+{
+ public function __toString()
+ {
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/Test/TestLogger.php b/vendor/psr/log/Psr/Log/Test/TestLogger.php
new file mode 100755
index 0000000..1be3230
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/Test/TestLogger.php
@@ -0,0 +1,147 @@
+ $level,
+ 'message' => $message,
+ 'context' => $context,
+ ];
+
+ $this->recordsByLevel[$record['level']][] = $record;
+ $this->records[] = $record;
+ }
+
+ public function hasRecords($level)
+ {
+ return isset($this->recordsByLevel[$level]);
+ }
+
+ public function hasRecord($record, $level)
+ {
+ if (is_string($record)) {
+ $record = ['message' => $record];
+ }
+ return $this->hasRecordThatPasses(function ($rec) use ($record) {
+ if ($rec['message'] !== $record['message']) {
+ return false;
+ }
+ if (isset($record['context']) && $rec['context'] !== $record['context']) {
+ return false;
+ }
+ return true;
+ }, $level);
+ }
+
+ public function hasRecordThatContains($message, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($message) {
+ return strpos($rec['message'], $message) !== false;
+ }, $level);
+ }
+
+ public function hasRecordThatMatches($regex, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($regex) {
+ return preg_match($regex, $rec['message']) > 0;
+ }, $level);
+ }
+
+ public function hasRecordThatPasses(callable $predicate, $level)
+ {
+ if (!isset($this->recordsByLevel[$level])) {
+ return false;
+ }
+ foreach ($this->recordsByLevel[$level] as $i => $rec) {
+ if (call_user_func($predicate, $rec, $i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function __call($method, $args)
+ {
+ if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
+ $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
+ $level = strtolower($matches[2]);
+ if (method_exists($this, $genericMethod)) {
+ $args[] = $level;
+ return call_user_func_array([$this, $genericMethod], $args);
+ }
+ }
+ throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
+ }
+
+ public function reset()
+ {
+ $this->records = [];
+ $this->recordsByLevel = [];
+ }
+}
diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md
new file mode 100755
index 0000000..5571a25
--- /dev/null
+++ b/vendor/psr/log/README.md
@@ -0,0 +1,52 @@
+PSR Log
+=======
+
+This repository holds all interfaces/classes/traits related to
+[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
+
+Note that this is not a logger of its own. It is merely an interface that
+describes a logger. See the specification for more details.
+
+Installation
+------------
+
+```bash
+composer require psr/log
+```
+
+Usage
+-----
+
+If you need a logger, you can use the interface like this:
+
+```php
+logger = $logger;
+ }
+
+ public function doSomething()
+ {
+ if ($this->logger) {
+ $this->logger->info('Doing work');
+ }
+
+ // do something useful
+ }
+}
+```
+
+You can then pick one of the implementations of the interface to get a logger.
+
+If you want to implement the interface, you can require this package and
+implement `Psr\Log\LoggerInterface` in your code. Please read the
+[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+for details.
diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json
new file mode 100755
index 0000000..3f6d4ee
--- /dev/null
+++ b/vendor/psr/log/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/log",
+ "description": "Common interface for logging libraries",
+ "keywords": ["psr", "psr-3", "log"],
+ "homepage": "https://github.com/php-fig/log",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
diff --git a/vendor/ralouphie/getallheaders/LICENSE b/vendor/ralouphie/getallheaders/LICENSE
new file mode 100755
index 0000000..be5540c
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Ralph Khattar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/ralouphie/getallheaders/README.md b/vendor/ralouphie/getallheaders/README.md
new file mode 100755
index 0000000..9430d76
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/README.md
@@ -0,0 +1,27 @@
+getallheaders
+=============
+
+PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3.
+
+[](https://travis-ci.org/ralouphie/getallheaders)
+[](https://coveralls.io/r/ralouphie/getallheaders?branch=master)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+[](https://packagist.org/packages/ralouphie/getallheaders)
+
+
+This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php).
+
+## Install
+
+For PHP version **`>= 5.6`**:
+
+```
+composer require ralouphie/getallheaders
+```
+
+For PHP version **`< 5.6`**:
+
+```
+composer require ralouphie/getallheaders "^2"
+```
diff --git a/vendor/ralouphie/getallheaders/composer.json b/vendor/ralouphie/getallheaders/composer.json
new file mode 100755
index 0000000..de8ce62
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "ralouphie/getallheaders",
+ "description": "A polyfill for getallheaders.",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5 || ^6.5",
+ "php-coveralls/php-coveralls": "^2.1"
+ },
+ "autoload": {
+ "files": ["src/getallheaders.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "getallheaders\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/vendor/ralouphie/getallheaders/src/getallheaders.php b/vendor/ralouphie/getallheaders/src/getallheaders.php
new file mode 100755
index 0000000..c7285a5
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/src/getallheaders.php
@@ -0,0 +1,46 @@
+ 'Content-Type',
+ 'CONTENT_LENGTH' => 'Content-Length',
+ 'CONTENT_MD5' => 'Content-Md5',
+ );
+
+ foreach ($_SERVER as $key => $value) {
+ if (substr($key, 0, 5) === 'HTTP_') {
+ $key = substr($key, 5);
+ if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) {
+ $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
+ $headers[$key] = $value;
+ }
+ } elseif (isset($copy_server[$key])) {
+ $headers[$copy_server[$key]] = $value;
+ }
+ }
+
+ if (!isset($headers['Authorization'])) {
+ if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
+ $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
+ } elseif (isset($_SERVER['PHP_AUTH_USER'])) {
+ $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
+ $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);
+ } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
+ $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
+ }
+ }
+
+ return $headers;
+ }
+
+}
diff --git a/vendor/react/cache/.gitignore b/vendor/react/cache/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/vendor/react/cache/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/vendor/react/cache/.travis.yml b/vendor/react/cache/.travis.yml
new file mode 100755
index 0000000..402a996
--- /dev/null
+++ b/vendor/react/cache/.travis.yml
@@ -0,0 +1,32 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+# - hhvm # requires legacy phpunit & ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - php: hhvm
+ install: composer require phpunit/phpunit:^5 --dev --no-interaction
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/vendor/react/cache/CHANGELOG.md b/vendor/react/cache/CHANGELOG.md
new file mode 100755
index 0000000..99ecd1c
--- /dev/null
+++ b/vendor/react/cache/CHANGELOG.md
@@ -0,0 +1,74 @@
+# Changelog
+
+## 1.0.0 (2019-07-11)
+
+* First stable LTS release, now following [SemVer](https://semver.org/).
+ We'd like to emphasize that this component is production ready and battle-tested.
+ We plan to support all long-term support (LTS) releases for at least 24 months,
+ so you have a rock-solid foundation to build on top of.
+
+> Contains no other changes, so it's actually fully compatible with the v0.6.0 release.
+
+## 0.6.0 (2019-07-04)
+
+* Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()`
+ supporting multiple cache items (inspired by PSR-16).
+ (#32 by @krlv and #37 by @clue)
+
+* Documentation for TTL precision with millisecond accuracy or below and
+ use high-resolution timer for cache TTL on PHP 7.3+.
+ (#35 and #38 by @clue)
+
+* Improve API documentation and allow legacy HHVM to fail in Travis CI config.
+ (#34 and #36 by @clue)
+
+* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
+ (#31 by @WyriHaximus)
+
+## 0.5.0 (2018-06-25)
+
+* Improve documentation by describing what is expected of a class implementing `CacheInterface`.
+ (#21, #22, #23, #27 by @WyriHaximus)
+
+* Implemented (optional) Least Recently Used (LRU) cache algorithm for `ArrayCache`.
+ (#26 by @clue)
+
+* Added support for cache expiration (TTL).
+ (#29 by @clue and @WyriHaximus)
+
+* Renamed `remove` to `delete` making it more in line with `PSR-16`.
+ (#30 by @clue)
+
+## 0.4.2 (2017-12-20)
+
+* Improve documentation with usage and installation instructions
+ (#10 by @clue)
+
+* Improve test suite by adding PHPUnit to `require-dev` and
+ add forward compatibility with PHPUnit 5 and PHPUnit 6 and
+ sanitize Composer autoload paths
+ (#14 by @shaunbramley and #12 and #18 by @clue)
+
+## 0.4.1 (2016-02-25)
+
+* Repository maintenance, split off from main repo, improve test suite and documentation
+* First class support for PHP7 and HHVM (#9 by @clue)
+* Adjust compatibility to 5.3 (#7 by @clue)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.2 (2013-05-10)
+
+* Version bump
+
+## 0.3.0 (2013-04-14)
+
+* Version bump
+
+## 0.2.6 (2012-12-26)
+
+* Feature: New cache component, used by DNS
diff --git a/vendor/react/cache/LICENSE b/vendor/react/cache/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/vendor/react/cache/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/cache/README.md b/vendor/react/cache/README.md
new file mode 100755
index 0000000..74cef54
--- /dev/null
+++ b/vendor/react/cache/README.md
@@ -0,0 +1,366 @@
+# Cache
+
+[](https://travis-ci.org/reactphp/cache)
+
+Async, [Promise](https://github.com/reactphp/promise)-based cache interface
+for [ReactPHP](https://reactphp.org/).
+
+The cache component provides a
+[Promise](https://github.com/reactphp/promise)-based
+[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache)
+implementation of that.
+This allows consumers to type hint against the interface and third parties to
+provide alternate implementations.
+This project is heavily inspired by
+[PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/),
+but uses an interface more suited for async, non-blocking applications.
+
+**Table of Contents**
+
+* [Usage](#usage)
+ * [CacheInterface](#cacheinterface)
+ * [get()](#get)
+ * [set()](#set)
+ * [delete()](#delete)
+ * [getMultiple()](#getmultiple)
+ * [setMultiple()](#setmultiple)
+ * [deleteMultiple()](#deletemultiple)
+ * [clear()](#clear)
+ * [has()](#has)
+ * [ArrayCache](#arraycache)
+* [Common usage](#common-usage)
+ * [Fallback get](#fallback-get)
+ * [Fallback-get-and-set](#fallback-get-and-set)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Usage
+
+### CacheInterface
+
+The `CacheInterface` describes the main interface of this component.
+This allows consumers to type hint against the interface and third parties to
+provide alternate implementations.
+
+#### get()
+
+The `get(string $key, mixed $default = null): PromiseInterface` method can be used to
+retrieve an item from the cache.
+
+This method will resolve with the cached value on success or with the
+given `$default` value when no item can be found or when an error occurs.
+Similarly, an expired cache item (once the time-to-live is expired) is
+considered a cache miss.
+
+```php
+$cache
+ ->get('foo')
+ ->then('var_dump');
+```
+
+This example fetches the value of the key `foo` and passes it to the
+`var_dump` function. You can use any of the composition provided by
+[promises](https://github.com/reactphp/promise).
+
+#### set()
+
+The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface` method can be used to
+store an item in the cache.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. If the cache implementation has to go over the network to store
+it, it may take a while.
+
+The optional `$ttl` parameter sets the maximum time-to-live in seconds
+for this cache item. If this parameter is omitted (or `null`), the item
+will stay in the cache for as long as the underlying implementation
+supports. Trying to access an expired cache item results in a cache miss,
+see also [`get()`](#get).
+
+```php
+$cache->set('foo', 'bar', 60);
+```
+
+This example eventually sets the value of the key `foo` to `bar`. If it
+already exists, it is overridden.
+
+This interface does not enforce any particular TTL resolution, so special
+care may have to be taken if you rely on very high precision with
+millisecond accuracy or below. Cache implementations SHOULD work on a
+best effort basis and SHOULD provide at least second accuracy unless
+otherwise noted. Many existing cache implementations are known to provide
+microsecond or millisecond accuracy, but it's generally not recommended
+to rely on this high precision.
+
+This interface suggests that cache implementations SHOULD use a monotonic
+time source if available. Given that a monotonic time source is only
+available as of PHP 7.3 by default, cache implementations MAY fall back
+to using wall-clock time.
+While this does not affect many common use cases, this is an important
+distinction for programs that rely on a high time precision or on systems
+that are subject to discontinuous time adjustments (time jumps).
+This means that if you store a cache item with a TTL of 30s and then
+adjust your system time forward by 20s, the cache item SHOULD still
+expire in 30s.
+
+#### delete()
+
+The `delete(string $key): PromiseInterface` method can be used to
+delete an item from the cache.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. When no item for `$key` is found in the cache, it also resolves
+to `true`. If the cache implementation has to go over the network to
+delete it, it may take a while.
+
+```php
+$cache->delete('foo');
+```
+
+This example eventually deletes the key `foo` from the cache. As with
+`set()`, this may not happen instantly and a promise is returned to
+provide guarantees whether or not the item has been removed from cache.
+
+#### getMultiple()
+
+The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface` method can be used to
+retrieve multiple cache items by their unique keys.
+
+This method will resolve with an array of cached values on success or with the
+given `$default` value when an item can not be found or when an error occurs.
+Similarly, an expired cache item (once the time-to-live is expired) is
+considered a cache miss.
+
+```php
+$cache->getMultiple(array('name', 'age'))->then(function (array $values) {
+ $name = $values['name'] ?? 'User';
+ $age = $values['age'] ?? 'n/a';
+
+ echo $name . ' is ' . $age . PHP_EOL;
+});
+```
+
+This example fetches the cache items for the `name` and `age` keys and
+prints some example output. You can use any of the composition provided
+by [promises](https://github.com/reactphp/promise).
+
+#### setMultiple()
+
+The `setMultiple(array $values, ?float $ttl = null): PromiseInterface` method can be used to
+persist a set of key => value pairs in the cache, with an optional TTL.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. If the cache implementation has to go over the network to store
+it, it may take a while.
+
+The optional `$ttl` parameter sets the maximum time-to-live in seconds
+for these cache items. If this parameter is omitted (or `null`), these items
+will stay in the cache for as long as the underlying implementation
+supports. Trying to access an expired cache items results in a cache miss,
+see also [`getMultiple()`](#getmultiple).
+
+```php
+$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
+```
+
+This example eventually sets the list of values - the key `foo` to `1` value
+and the key `bar` to `2`. If some of the keys already exist, they are overridden.
+
+#### deleteMultiple()
+
+The `setMultiple(string[] $keys): PromiseInterface` method can be used to
+delete multiple cache items in a single operation.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. When no items for `$keys` are found in the cache, it also resolves
+to `true`. If the cache implementation has to go over the network to
+delete it, it may take a while.
+
+```php
+$cache->deleteMultiple(array('foo', 'bar, 'baz'));
+```
+
+This example eventually deletes keys `foo`, `bar` and `baz` from the cache.
+As with `setMultiple()`, this may not happen instantly and a promise is returned to
+provide guarantees whether or not the item has been removed from cache.
+
+#### clear()
+
+The `clear(): PromiseInterface` method can be used to
+wipe clean the entire cache.
+
+This method will resolve with `true` on success or `false` when an error
+occurs. If the cache implementation has to go over the network to
+delete it, it may take a while.
+
+```php
+$cache->clear();
+```
+
+This example eventually deletes all keys from the cache. As with `deleteMultiple()`,
+this may not happen instantly and a promise is returned to provide guarantees
+whether or not all the items have been removed from cache.
+
+#### has()
+
+The `has(string $key): PromiseInterface` method can be used to
+determine whether an item is present in the cache.
+
+This method will resolve with `true` on success or `false` when no item can be found
+or when an error occurs. Similarly, an expired cache item (once the time-to-live
+is expired) is considered a cache miss.
+
+```php
+$cache
+ ->has('foo')
+ ->then('var_dump');
+```
+
+This example checks if the value of the key `foo` is set in the cache and passes
+the result to the `var_dump` function. You can use any of the composition provided by
+[promises](https://github.com/reactphp/promise).
+
+NOTE: It is recommended that has() is only to be used for cache warming type purposes
+and not to be used within your live applications operations for get/set, as this method
+is subject to a race condition where your has() will return true and immediately after,
+another script can remove it making the state of your app out of date.
+
+### ArrayCache
+
+The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
+
+```php
+$cache = new ArrayCache();
+
+$cache->set('foo', 'bar');
+```
+
+Its constructor accepts an optional `?int $limit` parameter to limit the
+maximum number of entries to store in the LRU cache. If you add more
+entries to this instance, it will automatically take care of removing
+the one that was least recently used (LRU).
+
+For example, this snippet will overwrite the first value and only store
+the last two entries:
+
+```php
+$cache = new ArrayCache(2);
+
+$cache->set('foo', '1');
+$cache->set('bar', '2');
+$cache->set('baz', '3');
+```
+
+This cache implementation is known to rely on wall-clock time to schedule
+future cache expiration times when using any version before PHP 7.3,
+because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
+While this does not affect many common use cases, this is an important
+distinction for programs that rely on a high time precision or on systems
+that are subject to discontinuous time adjustments (time jumps).
+This means that if you store a cache item with a TTL of 30s on PHP < 7.3
+and then adjust your system time forward by 20s, the cache item may
+expire in 10s. See also [`set()`](#set) for more details.
+
+## Common usage
+
+### Fallback get
+
+A common use case of caches is to attempt fetching a cached value and as a
+fallback retrieve it from the original data source if not found. Here is an
+example of that:
+
+```php
+$cache
+ ->get('foo')
+ ->then(function ($result) {
+ if ($result === null) {
+ return getFooFromDb();
+ }
+
+ return $result;
+ })
+ ->then('var_dump');
+```
+
+First an attempt is made to retrieve the value of `foo`. A callback function is
+registered that will call `getFooFromDb` when the resulting value is null.
+`getFooFromDb` is a function (can be any PHP callable) that will be called if the
+key does not exist in the cache.
+
+`getFooFromDb` can handle the missing key by returning a promise for the
+actual value from the database (or any other data source). As a result, this
+chain will correctly fall back, and provide the value in both cases.
+
+### Fallback get and set
+
+To expand on the fallback get example, often you want to set the value on the
+cache after fetching it from the data source.
+
+```php
+$cache
+ ->get('foo')
+ ->then(function ($result) {
+ if ($result === null) {
+ return $this->getAndCacheFooFromDb();
+ }
+
+ return $result;
+ })
+ ->then('var_dump');
+
+public function getAndCacheFooFromDb()
+{
+ return $this->db
+ ->get('foo')
+ ->then(array($this, 'cacheFooFromDb'));
+}
+
+public function cacheFooFromDb($foo)
+{
+ $this->cache->set('foo', $foo);
+
+ return $foo;
+}
+```
+
+By using chaining you can easily conditionally cache the value if it is
+fetched from the database.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/cache:^1.0
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/vendor/react/cache/composer.json b/vendor/react/cache/composer.json
new file mode 100755
index 0000000..51573b6
--- /dev/null
+++ b/vendor/react/cache/composer.json
@@ -0,0 +1,19 @@
+{
+ "name": "react/cache",
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": ["cache", "caching", "promise", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "~2.0|~1.1"
+ },
+ "autoload": {
+ "psr-4": { "React\\Cache\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Cache\\": "tests/" }
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/vendor/react/cache/phpunit.xml.dist b/vendor/react/cache/phpunit.xml.dist
new file mode 100755
index 0000000..d02182f
--- /dev/null
+++ b/vendor/react/cache/phpunit.xml.dist
@@ -0,0 +1,20 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/vendor/react/cache/src/ArrayCache.php b/vendor/react/cache/src/ArrayCache.php
new file mode 100755
index 0000000..81f25ef
--- /dev/null
+++ b/vendor/react/cache/src/ArrayCache.php
@@ -0,0 +1,181 @@
+set('foo', 'bar');
+ * ```
+ *
+ * Its constructor accepts an optional `?int $limit` parameter to limit the
+ * maximum number of entries to store in the LRU cache. If you add more
+ * entries to this instance, it will automatically take care of removing
+ * the one that was least recently used (LRU).
+ *
+ * For example, this snippet will overwrite the first value and only store
+ * the last two entries:
+ *
+ * ```php
+ * $cache = new ArrayCache(2);
+ *
+ * $cache->set('foo', '1');
+ * $cache->set('bar', '2');
+ * $cache->set('baz', '3');
+ * ```
+ *
+ * This cache implementation is known to rely on wall-clock time to schedule
+ * future cache expiration times when using any version before PHP 7.3,
+ * because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you store a cache item with a TTL of 30s on PHP < 7.3
+ * and then adjust your system time forward by 20s, the cache item may
+ * expire in 10s. See also [`set()`](#set) for more details.
+ *
+ * @param int|null $limit maximum number of entries to store in the LRU cache
+ */
+ public function __construct($limit = null)
+ {
+ $this->limit = $limit;
+
+ // prefer high-resolution timer, available as of PHP 7.3+
+ $this->supportsHighResolution = \function_exists('hrtime');
+ }
+
+ public function get($key, $default = null)
+ {
+ // delete key if it is already expired => below will detect this as a cache miss
+ if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ if (!\array_key_exists($key, $this->data)) {
+ return Promise\resolve($default);
+ }
+
+ // remove and append to end of array to keep track of LRU info
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ return Promise\resolve($value);
+ }
+
+ public function set($key, $value, $ttl = null)
+ {
+ // unset before setting to ensure this entry will be added to end of array (LRU info)
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ // sort expiration times if TTL is given (first will expire first)
+ unset($this->expires[$key]);
+ if ($ttl !== null) {
+ $this->expires[$key] = $this->now() + $ttl;
+ \asort($this->expires);
+ }
+
+ // ensure size limit is not exceeded or remove first entry from array
+ if ($this->limit !== null && \count($this->data) > $this->limit) {
+ // first try to check if there's any expired entry
+ // expiration times are sorted, so we can simply look at the first one
+ \reset($this->expires);
+ $key = \key($this->expires);
+
+ // check to see if the first in the list of expiring keys is already expired
+ // if the first key is not expired, we have to overwrite by using LRU info
+ if ($key === null || $this->now() - $this->expires[$key] < 0) {
+ \reset($this->data);
+ $key = \key($this->data);
+ }
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function delete($key)
+ {
+ unset($this->data[$key], $this->expires[$key]);
+
+ return Promise\resolve(true);
+ }
+
+ public function getMultiple(array $keys, $default = null)
+ {
+ $values = array();
+
+ foreach ($keys as $key) {
+ $values[$key] = $this->get($key, $default);
+ }
+
+ return Promise\all($values);
+ }
+
+ public function setMultiple(array $values, $ttl = null)
+ {
+ foreach ($values as $key => $value) {
+ $this->set($key, $value, $ttl);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function deleteMultiple(array $keys)
+ {
+ foreach ($keys as $key) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ return Promise\resolve(true);
+ }
+
+ public function clear()
+ {
+ $this->data = array();
+ $this->expires = array();
+
+ return Promise\resolve(true);
+ }
+
+ public function has($key)
+ {
+ // delete key if it is already expired
+ if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
+ unset($this->data[$key], $this->expires[$key]);
+ }
+
+ if (!\array_key_exists($key, $this->data)) {
+ return Promise\resolve(false);
+ }
+
+ // remove and append to end of array to keep track of LRU info
+ $value = $this->data[$key];
+ unset($this->data[$key]);
+ $this->data[$key] = $value;
+
+ return Promise\resolve(true);
+ }
+
+ /**
+ * @return float
+ */
+ private function now()
+ {
+ return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
+ }
+}
diff --git a/vendor/react/cache/src/CacheInterface.php b/vendor/react/cache/src/CacheInterface.php
new file mode 100755
index 0000000..3d52501
--- /dev/null
+++ b/vendor/react/cache/src/CacheInterface.php
@@ -0,0 +1,194 @@
+get('foo')
+ * ->then('var_dump');
+ * ```
+ *
+ * This example fetches the value of the key `foo` and passes it to the
+ * `var_dump` function. You can use any of the composition provided by
+ * [promises](https://github.com/reactphp/promise).
+ *
+ * @param string $key
+ * @param mixed $default Default value to return for cache miss or null if not given.
+ * @return PromiseInterface
+ */
+ public function get($key, $default = null);
+
+ /**
+ * Stores an item in the cache.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. If the cache implementation has to go over the network to store
+ * it, it may take a while.
+ *
+ * The optional `$ttl` parameter sets the maximum time-to-live in seconds
+ * for this cache item. If this parameter is omitted (or `null`), the item
+ * will stay in the cache for as long as the underlying implementation
+ * supports. Trying to access an expired cache item results in a cache miss,
+ * see also [`get()`](#get).
+ *
+ * ```php
+ * $cache->set('foo', 'bar', 60);
+ * ```
+ *
+ * This example eventually sets the value of the key `foo` to `bar`. If it
+ * already exists, it is overridden.
+ *
+ * This interface does not enforce any particular TTL resolution, so special
+ * care may have to be taken if you rely on very high precision with
+ * millisecond accuracy or below. Cache implementations SHOULD work on a
+ * best effort basis and SHOULD provide at least second accuracy unless
+ * otherwise noted. Many existing cache implementations are known to provide
+ * microsecond or millisecond accuracy, but it's generally not recommended
+ * to rely on this high precision.
+ *
+ * This interface suggests that cache implementations SHOULD use a monotonic
+ * time source if available. Given that a monotonic time source is only
+ * available as of PHP 7.3 by default, cache implementations MAY fall back
+ * to using wall-clock time.
+ * While this does not affect many common use cases, this is an important
+ * distinction for programs that rely on a high time precision or on systems
+ * that are subject to discontinuous time adjustments (time jumps).
+ * This means that if you store a cache item with a TTL of 30s and then
+ * adjust your system time forward by 20s, the cache item SHOULD still
+ * expire in 30s.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @param ?float $ttl
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function set($key, $value, $ttl = null);
+
+ /**
+ * Deletes an item from the cache.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. When no item for `$key` is found in the cache, it also resolves
+ * to `true`. If the cache implementation has to go over the network to
+ * delete it, it may take a while.
+ *
+ * ```php
+ * $cache->delete('foo');
+ * ```
+ *
+ * This example eventually deletes the key `foo` from the cache. As with
+ * `set()`, this may not happen instantly and a promise is returned to
+ * provide guarantees whether or not the item has been removed from cache.
+ *
+ * @param string $key
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function delete($key);
+
+ /**
+ * Retrieves multiple cache items by their unique keys.
+ *
+ * This method will resolve with an array of cached values on success or with the
+ * given `$default` value when an item can not be found or when an error occurs.
+ * Similarly, an expired cache item (once the time-to-live is expired) is
+ * considered a cache miss.
+ *
+ * ```php
+ * $cache->getMultiple(array('name', 'age'))->then(function (array $values) {
+ * $name = $values['name'] ?? 'User';
+ * $age = $values['age'] ?? 'n/a';
+ *
+ * echo $name . ' is ' . $age . PHP_EOL;
+ * });
+ * ```
+ *
+ * This example fetches the cache items for the `name` and `age` keys and
+ * prints some example output. You can use any of the composition provided
+ * by [promises](https://github.com/reactphp/promise).
+ *
+ * @param string[] $keys A list of keys that can obtained in a single operation.
+ * @param mixed $default Default value to return for keys that do not exist.
+ * @return PromiseInterface Returns a promise which resolves to an `array` of cached values
+ */
+ public function getMultiple(array $keys, $default = null);
+
+ /**
+ * Persists a set of key => value pairs in the cache, with an optional TTL.
+ *
+ * This method will resolve with `true` on success or `false` when an error
+ * occurs. If the cache implementation has to go over the network to store
+ * it, it may take a while.
+ *
+ * The optional `$ttl` parameter sets the maximum time-to-live in seconds
+ * for these cache items. If this parameter is omitted (or `null`), these items
+ * will stay in the cache for as long as the underlying implementation
+ * supports. Trying to access an expired cache items results in a cache miss,
+ * see also [`get()`](#get).
+ *
+ * ```php
+ * $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
+ * ```
+ *
+ * This example eventually sets the list of values - the key `foo` to 1 value
+ * and the key `bar` to 2. If some of the keys already exist, they are overridden.
+ *
+ * @param array $values A list of key => value pairs for a multiple-set operation.
+ * @param ?float $ttl Optional. The TTL value of this item.
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function setMultiple(array $values, $ttl = null);
+
+ /**
+ * Deletes multiple cache items in a single operation.
+ *
+ * @param string[] $keys A list of string-based keys to be deleted.
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function deleteMultiple(array $keys);
+
+ /**
+ * Wipes clean the entire cache.
+ *
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function clear();
+
+ /**
+ * Determines whether an item is present in the cache.
+ *
+ * This method will resolve with `true` on success or `false` when no item can be found
+ * or when an error occurs. Similarly, an expired cache item (once the time-to-live
+ * is expired) is considered a cache miss.
+ *
+ * ```php
+ * $cache
+ * ->has('foo')
+ * ->then('var_dump');
+ * ```
+ *
+ * This example checks if the value of the key `foo` is set in the cache and passes
+ * the result to the `var_dump` function. You can use any of the composition provided by
+ * [promises](https://github.com/reactphp/promise).
+ *
+ * NOTE: It is recommended that has() is only to be used for cache warming type purposes
+ * and not to be used within your live applications operations for get/set, as this method
+ * is subject to a race condition where your has() will return true and immediately after,
+ * another script can remove it making the state of your app out of date.
+ *
+ * @param string $key The cache item key.
+ * @return PromiseInterface Returns a promise which resolves to `true` on success or `false` on error
+ */
+ public function has($key);
+}
diff --git a/vendor/react/cache/tests/ArrayCacheTest.php b/vendor/react/cache/tests/ArrayCacheTest.php
new file mode 100755
index 0000000..3b5bd8c
--- /dev/null
+++ b/vendor/react/cache/tests/ArrayCacheTest.php
@@ -0,0 +1,322 @@
+cache = new ArrayCache();
+ }
+
+ /** @test */
+ public function getShouldResolvePromiseWithNullForNonExistentKey()
+ {
+ $success = $this->createCallableMock();
+ $success
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(null);
+
+ $this->cache
+ ->get('foo')
+ ->then(
+ $success,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function setShouldSetKey()
+ {
+ $setPromise = $this->cache
+ ->set('foo', 'bar');
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $setPromise->then($mock);
+
+ $success = $this->createCallableMock();
+ $success
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with('bar');
+
+ $this->cache
+ ->get('foo')
+ ->then($success);
+ }
+
+ /** @test */
+ public function deleteShouldDeleteKey()
+ {
+ $this->cache
+ ->set('foo', 'bar');
+
+ $deletePromise = $this->cache
+ ->delete('foo');
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $deletePromise->then($mock);
+
+ $this->cache
+ ->get('foo')
+ ->then(
+ $this->expectCallableOnce(),
+ $this->expectCallableNever()
+ );
+ }
+
+ public function testGetWillResolveWithNullForCacheMiss()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testGetWillResolveWithDefaultValueForCacheMiss()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith('bar'));
+ }
+
+ public function testGetWillResolveWithExplicitNullValueForCacheHit()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->set('foo', null);
+ $this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testLimitSizeToZeroDoesNotStoreAnyData()
+ {
+ $this->cache = new ArrayCache(0);
+
+ $this->cache->set('foo', 'bar');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testLimitSizeToOneWillOnlyReturnLastWrite()
+ {
+ $this->cache = new ArrayCache(1);
+
+ $this->cache->set('foo', '1');
+ $this->cache->set('bar', '2');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith('2'));
+ }
+
+ public function testOverwriteWithLimitedSizeWillUpdateLRUInfo()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1');
+ $this->cache->set('bar', '2');
+ $this->cache->set('foo', '3');
+ $this->cache->set('baz', '4');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('3'));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith(null));
+ $this->cache->get('baz')->then($this->expectCallableOnceWith('4'));
+ }
+
+ public function testGetWithLimitedSizeWillUpdateLRUInfo()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1');
+ $this->cache->set('bar', '2');
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ $this->cache->set('baz', '3');
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith(null));
+ $this->cache->get('baz')->then($this->expectCallableOnceWith('3'));
+ }
+
+ public function testGetWillResolveWithValueIfItemIsNotExpired()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->set('foo', '1', 10);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ }
+
+ public function testGetWillResolveWithDefaultIfItemIsExpired()
+ {
+ $this->cache = new ArrayCache();
+
+ $this->cache->set('foo', '1', 0);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testSetWillOverwritOldestItemIfNoEntryIsExpired()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1', 10);
+ $this->cache->set('bar', '2', 20);
+ $this->cache->set('baz', '3', 30);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', '1', 10);
+ $this->cache->set('bar', '2', 0);
+ $this->cache->set('baz', '3', 30);
+
+ $this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
+ $this->cache->get('bar')->then($this->expectCallableOnceWith(null));
+ }
+
+ public function testGetMultiple()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', '1');
+
+ $this->cache
+ ->getMultiple(array('foo', 'bar'), 'baz')
+ ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => 'baz')));
+ }
+
+ public function testSetMultiple()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->setMultiple(array('foo' => '1', 'bar' => '2'), 10);
+
+ $this->cache
+ ->getMultiple(array('foo', 'bar'))
+ ->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => '2')));
+ }
+
+ public function testDeleteMultiple()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3));
+
+ $this->cache
+ ->deleteMultiple(array('foo', 'baz'))
+ ->then($this->expectCallableOnceWith(true));
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+
+ $this->cache
+ ->has('bar')
+ ->then($this->expectCallableOnceWith(true));
+
+ $this->cache
+ ->has('baz')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function testClearShouldClearCache()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3));
+
+ $this->cache->clear();
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+
+ $this->cache
+ ->has('bar')
+ ->then($this->expectCallableOnceWith(false));
+
+ $this->cache
+ ->has('baz')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function hasShouldResolvePromiseForExistingKey()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', 'bar');
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(true));
+ }
+
+ public function hasShouldResolvePromiseForNonExistentKey()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', 'bar');
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function testHasWillResolveIfItemIsNotExpired()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', '1', 10);
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(true));
+ }
+
+ public function testHasWillResolveIfItemIsExpired()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', '1', 0);
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(false));
+ }
+
+ public function testHasWillResolveForExplicitNullValue()
+ {
+ $this->cache = new ArrayCache();
+ $this->cache->set('foo', null);
+
+ $this->cache
+ ->has('foo')
+ ->then($this->expectCallableOnceWith(true));
+ }
+
+ public function testHasWithLimitedSizeWillUpdateLRUInfo()
+ {
+ $this->cache = new ArrayCache(2);
+
+ $this->cache->set('foo', 1);
+ $this->cache->set('bar', 2);
+ $this->cache->has('foo')->then($this->expectCallableOnceWith(true));
+ $this->cache->set('baz', 3);
+
+ $this->cache->has('foo')->then($this->expectCallableOnceWith(1));
+ $this->cache->has('bar')->then($this->expectCallableOnceWith(false));
+ $this->cache->has('baz')->then($this->expectCallableOnceWith(3));
+ }
+}
diff --git a/vendor/react/cache/tests/CallableStub.php b/vendor/react/cache/tests/CallableStub.php
new file mode 100755
index 0000000..2f547cd
--- /dev/null
+++ b/vendor/react/cache/tests/CallableStub.php
@@ -0,0 +1,10 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($param)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($param);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Cache\CallableStub')->getMock();
+ }
+}
diff --git a/vendor/react/dns/.gitignore b/vendor/react/dns/.gitignore
new file mode 100755
index 0000000..19982ea
--- /dev/null
+++ b/vendor/react/dns/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
\ No newline at end of file
diff --git a/vendor/react/dns/.travis.yml b/vendor/react/dns/.travis.yml
new file mode 100755
index 0000000..459e852
--- /dev/null
+++ b/vendor/react/dns/.travis.yml
@@ -0,0 +1,32 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+# - hhvm # requires legacy phpunit & ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - php: hhvm
+ install: composer require phpunit/phpunit:^5 --dev --no-interaction
+ allow_failures:
+ - php: hhvm
+
+sudo: false
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/vendor/react/dns/CHANGELOG.md b/vendor/react/dns/CHANGELOG.md
new file mode 100755
index 0000000..314f023
--- /dev/null
+++ b/vendor/react/dns/CHANGELOG.md
@@ -0,0 +1,261 @@
+# Changelog
+
+## 0.4.19 (2019-07-10)
+
+* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6.
+ (#133 by @clue)
+
+## 0.4.18 (2019-09-07)
+
+* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`,
+ respect TTL from response records when caching and do not cache truncated responses.
+ (#129 by @clue)
+
+* Feature: Limit cache size to 256 last responses by default.
+ (#127 by @clue)
+
+* Feature: Cooperatively resolve hosts to avoid running same query concurrently.
+ (#125 by @clue)
+
+## 0.4.17 (2019-04-01)
+
+* Feature: Support parsing `authority` and `additional` records from DNS response.
+ (#123 by @clue)
+
+* Feature: Support dumping records as part of outgoing binary DNS message.
+ (#124 by @clue)
+
+* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0
+ (#121 by @clue)
+
+* Improve test suite to add forward compatibility with PHPUnit 7,
+ test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM.
+ (#122 by @clue)
+
+## 0.4.16 (2018-11-11)
+
+* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references.
+ (#118 by @clue)
+
+* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages,
+ malformed record data or malformed compressed domain name labels.
+ (#115 and #117 by @clue)
+
+* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset.
+ (#116 by @clue)
+
+* Fix: Fix caching advanced MX/SRV/TXT/SOA structures.
+ (#112 by @clue)
+
+## 0.4.15 (2018-07-02)
+
+* Feature: Add `resolveAll()` method to support custom query types in `Resolver`.
+ (#110 by @clue and @WyriHaximus)
+
+ ```php
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ });
+ ```
+
+* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records.
+ (#104, #105, #106, #107 and #108 by @clue)
+
+* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data.
+ (#104 by @clue)
+
+* Feature: Improve error messages for failed queries and improve documentation.
+ (#109 by @clue)
+
+* Feature: Add reverse DNS lookup example.
+ (#111 by @clue)
+
+## 0.4.14 (2018-06-26)
+
+* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages
+ to avoid cache poisoning attacks and deprecate legacy `Executor`.
+ (#101 and #103 by @clue)
+
+* Feature: Forward compatibility with Cache 0.5
+ (#102 by @clue)
+
+* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API.
+ (#99 by @clue)
+
+## 0.4.13 (2018-02-27)
+
+* Add `Config::loadSystemConfigBlocking()` to load default system config
+ and support parsing DNS config on all supported platforms
+ (`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows)
+ (#92, #93, #94 and #95 by @clue)
+
+ ```php
+ $config = Config::loadSystemConfigBlocking();
+ $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+ ```
+
+* Remove unneeded cyclic dependency on react/socket
+ (#96 by @clue)
+
+## 0.4.12 (2018-01-14)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6,
+ test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases,
+ add test group to skip integration tests relying on internet connection
+ and add minor documentation improvements.
+ (#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor)
+
+## 0.4.11 (2017-08-25)
+
+* Feature: Support resolving from default hosts file
+ (#75, #76 and #77 by @clue)
+
+ This means that resolving hosts such as `localhost` will now work as
+ expected across all platforms with no changes required:
+
+ ```php
+ $resolver->resolve('localhost')->then(function ($ip) {
+ echo 'IP: ' . $ip;
+ });
+ ```
+
+ The new `HostsExecutor` exists for advanced usage and is otherwise used
+ internally for this feature.
+
+## 0.4.10 (2017-08-10)
+
+* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and
+ lock minimum dependencies and work around circular dependency for tests
+ (#70 and #71 by @clue)
+
+* Fix: Work around DNS timeout issues for Windows users
+ (#74 by @clue)
+
+* Documentation and examples for advanced usage
+ (#66 by @WyriHaximus)
+
+* Remove broken TCP code, do not retry with invalid TCP query
+ (#73 by @clue)
+
+* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and
+ lock Travis distro so new defaults will not break the build and
+ fix failing tests for PHP 7.1
+ (#68 by @WyriHaximus and #69 and #72 by @clue)
+
+## 0.4.9 (2017-05-01)
+
+* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
+ (#61 by @clue)
+
+## 0.4.8 (2017-04-16)
+
+* Feature: Add support for the AAAA record type to the protocol parser
+ (#58 by @othillo)
+
+* Feature: Add support for the PTR record type to the protocol parser
+ (#59 by @othillo)
+
+## 0.4.7 (2017-03-31)
+
+* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component
+ (#57 by @clue)
+
+## 0.4.6 (2017-03-11)
+
+* Fix: Fix DNS timeout issues for Windows users and add forward compatibility
+ with Stream v0.5 and upcoming v0.6
+ (#53 by @clue)
+
+* Improve test suite by adding PHPUnit to `require-dev`
+ (#54 by @clue)
+
+## 0.4.5 (2017-03-02)
+
+* Fix: Ensure we ignore the case of the answer
+ (#51 by @WyriHaximus)
+
+* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal
+ code re-use for upcoming versions.
+ (#48 and #49 by @clue)
+
+## 0.4.4 (2017-02-13)
+
+* Fix: Fix handling connection and stream errors
+ (#45 by @clue)
+
+* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component
+ (#46 and #47 by @clue)
+
+## 0.4.3 (2016-07-31)
+
+* Feature: Allow for cache adapter injection (#38 by @WyriHaximus)
+
+ ```php
+ $factory = new React\Dns\Resolver\Factory();
+
+ $cache = new MyCustomCacheInstance();
+ $resolver = $factory->createCached('8.8.8.8', $loop, $cache);
+ ```
+
+* Feature: Support Promise cancellation (#35 by @clue)
+
+ ```php
+ $promise = $resolver->resolve('reactphp.org');
+
+ $promise->cancel();
+ ```
+
+## 0.4.2 (2016-02-24)
+
+* Repository maintenance, split off from main repo, improve test suite and documentation
+* First class support for PHP7 and HHVM (#34 by @clue)
+* Adjust compatibility to 5.3 (#30 by @clue)
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* Bug fix: Properly resolve CNAME aliases
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+* Bump React dependencies to v0.4
+
+## 0.3.2 (2013-05-10)
+
+* Feature: Support default port for IPv6 addresses (@clue)
+
+## 0.3.0 (2013-04-14)
+
+* Bump React dependencies to v0.3
+
+## 0.2.6 (2012-12-26)
+
+* Feature: New cache component, used by DNS
+
+## 0.2.5 (2012-11-26)
+
+* Version bump
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Change to promise-based API (@jsor)
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Feature: DNS executor timeout handling (@arnaud-lb)
+* Feature: DNS retry executor (@arnaud-lb)
+
+## 0.2.1 (2012-10-14)
+
+* Minor adjustments to DNS parser
+
+## 0.2.0 (2012-09-10)
+
+* Feature: DNS resolver
diff --git a/vendor/react/dns/LICENSE b/vendor/react/dns/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/vendor/react/dns/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/dns/README.md b/vendor/react/dns/README.md
new file mode 100755
index 0000000..6cdffe9
--- /dev/null
+++ b/vendor/react/dns/README.md
@@ -0,0 +1,344 @@
+# Dns
+
+[](https://travis-ci.org/reactphp/dns)
+
+Async DNS resolver for [ReactPHP](https://reactphp.org/).
+
+The main point of the DNS component is to provide async DNS resolution.
+However, it is really a toolkit for working with DNS messages, and could
+easily be used to create a DNS server.
+
+**Table of contents**
+
+* [Basic usage](#basic-usage)
+* [Caching](#caching)
+ * [Custom cache adapter](#custom-cache-adapter)
+* [Resolver](#resolver)
+ * [resolve()](#resolve)
+ * [resolveAll()](#resolveall)
+* [Advanced usage](#advanced-usage)
+ * [UdpTransportExecutor](#udptransportexecutor)
+ * [HostsFileExecutor](#hostsfileexecutor)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [References](#references)
+
+## Basic usage
+
+The most basic usage is to just create a resolver through the resolver
+factory. All you need to give it is a nameserver, then you can start resolving
+names, baby!
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$config = React\Dns\Config\Config::loadSystemConfigBlocking();
+$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->create($server, $loop);
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+$loop->run();
+```
+
+See also the [first example](examples).
+
+The `Config` class can be used to load the system default config. This is an
+operation that may access the filesystem and block. Ideally, this method should
+thus be executed only once before the loop starts and not repeatedly while it is
+running.
+Note that this class may return an *empty* configuration if the system config
+can not be loaded. As such, you'll likely want to apply a default nameserver
+as above if none can be found.
+
+> Note that the factory loads the hosts file from the filesystem once when
+ creating the resolver instance.
+ Ideally, this method should thus be executed only once before the loop starts
+ and not repeatedly while it is running.
+
+But there's more.
+
+## Caching
+
+You can cache results by configuring the resolver to use a `CachedExecutor`:
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$config = React\Dns\Config\Config::loadSystemConfigBlocking();
+$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->createCached($server, $loop);
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+...
+
+$dns->resolve('igor.io')->then(function ($ip) {
+ echo "Host: $ip\n";
+});
+
+$loop->run();
+```
+
+If the first call returns before the second, only one query will be executed.
+The second result will be served from an in memory cache.
+This is particularly useful for long running scripts where the same hostnames
+have to be looked up multiple times.
+
+See also the [third example](examples).
+
+### Custom cache adapter
+
+By default, the above will use an in memory cache.
+
+You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead:
+
+```php
+$cache = new React\Cache\ArrayCache();
+$loop = React\EventLoop\Factory::create();
+$factory = new React\Dns\Resolver\Factory();
+$dns = $factory->createCached('8.8.8.8', $loop, $cache);
+```
+
+See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
+
+## Resolver
+
+### resolve()
+
+The `resolve(string $domain): PromiseInterface` method can be used to
+resolve the given $domain name to a single IPv4 address (type `A` query).
+
+```php
+$resolver->resolve('reactphp.org')->then(function ($ip) {
+ echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
+});
+```
+
+This is one of the main methods in this package. It sends a DNS query
+for the given $domain name to your DNS server and returns a single IP
+address on success.
+
+If the DNS server sends a DNS response message that contains more than
+one IP address for this query, it will randomly pick one of the IP
+addresses from the response. If you want the full list of IP addresses
+or want to send a different type of query, you should use the
+[`resolveAll()`](#resolveall) method instead.
+
+If the DNS server sends a DNS response message that indicates an error
+code, this method will reject with a `RecordNotFoundException`. Its
+message and code can be used to check for the response code.
+
+If the DNS communication fails and the server does not respond with a
+valid response message, this message will reject with an `Exception`.
+
+Pending DNS queries can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $resolver->resolve('reactphp.org');
+
+$promise->cancel();
+```
+
+### resolveAll()
+
+The `resolveAll(string $host, int $type): PromiseInterface` method can be used to
+resolve all record values for the given $domain name and query $type.
+
+```php
+$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
+ echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+});
+
+$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+});
+```
+
+This is one of the main methods in this package. It sends a DNS query
+for the given $domain name to your DNS server and returns a list with all
+record values on success.
+
+If the DNS server sends a DNS response message that contains one or more
+records for this query, it will return a list with all record values
+from the response. You can use the `Message::TYPE_*` constants to control
+which type of query will be sent. Note that this method always returns a
+list of record values, but each record value type depends on the query
+type. For example, it returns the IPv4 addresses for type `A` queries,
+the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
+`CNAME` and `PTR` queries and structured data for other queries. See also
+the `Record` documentation for more details.
+
+If the DNS server sends a DNS response message that indicates an error
+code, this method will reject with a `RecordNotFoundException`. Its
+message and code can be used to check for the response code.
+
+If the DNS communication fails and the server does not respond with a
+valid response message, this message will reject with an `Exception`.
+
+Pending DNS queries can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
+
+$promise->cancel();
+```
+
+## Advanced Usage
+
+### UdpTransportExecutor
+
+The `UdpTransportExecutor` can be used to
+send DNS queries over a UDP transport.
+
+This is the main class that sends a DNS query to your DNS server and is used
+internally by the `Resolver` for the actual message transport.
+
+For more advanced usages one can utilize this class directly.
+The following example looks up the `IPv6` address for `igor.io`.
+
+```php
+$loop = Factory::create();
+$executor = new UdpTransportExecutor($loop);
+
+$executor->query(
+ '8.8.8.8:53',
+ new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
+)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv6: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
+```
+
+See also the [fourth example](examples).
+
+Note that this executor does not implement a timeout, so you will very likely
+want to use this in combination with a `TimeoutExecutor` like this:
+
+```php
+$executor = new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 3.0,
+ $loop
+);
+```
+
+Also note that this executor uses an unreliable UDP transport and that it
+does not implement any retry logic, so you will likely want to use this in
+combination with a `RetryExecutor` like this:
+
+```php
+$executor = new RetryExecutor(
+ new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 3.0,
+ $loop
+ )
+);
+```
+
+Note that this executor is entirely async and as such allows you to execute
+any number of queries concurrently. You should probably limit the number of
+concurrent queries in your application or you're very likely going to face
+rate limitations and bans on the resolver end. For many common applications,
+you may want to avoid sending the same query multiple times when the first
+one is still pending, so you will likely want to use this in combination with
+a `CoopExecutor` like this:
+
+```php
+$executor = new CoopExecutor(
+ new RetryExecutor(
+ new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 3.0,
+ $loop
+ )
+ )
+);
+```
+
+> Internally, this class uses PHP's UDP sockets and does not take advantage
+ of [react/datagram](https://github.com/reactphp/datagram) purely for
+ organizational reasons to avoid a cyclic dependency between the two
+ packages. Higher-level components should take advantage of the Datagram
+ component instead of reimplementing this socket logic from scratch.
+
+### HostsFileExecutor
+
+Note that the above `UdpTransportExecutor` class always performs an actual DNS query.
+If you also want to take entries from your hosts file into account, you may
+use this code:
+
+```php
+$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();
+
+$executor = new UdpTransportExecutor($loop);
+$executor = new HostsFileExecutor($hosts, $executor);
+
+$executor->query(
+ '8.8.8.8:53',
+ new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
+);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/dns:^0.4.19
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## References
+
+* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities
+* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification
diff --git a/vendor/react/dns/composer.json b/vendor/react/dns/composer.json
new file mode 100755
index 0000000..3ddf6e4
--- /dev/null
+++ b/vendor/react/dns/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "react/dns",
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.1 || ^1.2.1",
+ "react/promise-timer": "^1.2",
+ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": { "React\\Dns\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Dns\\": "tests" }
+ }
+}
diff --git a/vendor/react/dns/examples/01-one.php b/vendor/react/dns/examples/01-one.php
new file mode 100755
index 0000000..5db164f
--- /dev/null
+++ b/vendor/react/dns/examples/01-one.php
@@ -0,0 +1,22 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/react/dns/examples/02-concurrent.php b/vendor/react/dns/examples/02-concurrent.php
new file mode 100755
index 0000000..87e3f5c
--- /dev/null
+++ b/vendor/react/dns/examples/02-concurrent.php
@@ -0,0 +1,27 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$names = array_slice($argv, 1);
+if (!$names) {
+ $names = array('google.com', 'www.google.com', 'gmail.com');
+}
+
+foreach ($names as $name) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+}
+
+$loop->run();
diff --git a/vendor/react/dns/examples/03-cached.php b/vendor/react/dns/examples/03-cached.php
new file mode 100755
index 0000000..e76a27c
--- /dev/null
+++ b/vendor/react/dns/examples/03-cached.php
@@ -0,0 +1,40 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->createCached($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+}, 'printf');
+
+$loop->addTimer(1.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->addTimer(2.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->addTimer(3.0, function() use ($name, $resolver) {
+ $resolver->resolve($name)->then(function ($ip) use ($name) {
+ echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
+ }, 'printf');
+});
+
+$loop->run();
diff --git a/vendor/react/dns/examples/11-all-ips.php b/vendor/react/dns/examples/11-all-ips.php
new file mode 100755
index 0000000..d118bbb
--- /dev/null
+++ b/vendor/react/dns/examples/11-all-ips.php
@@ -0,0 +1,31 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+$resolver->resolveAll($name, Message::TYPE_A)->then(function (array $ips) use ($name) {
+ echo 'IPv4 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
+}, function (Exception $e) use ($name) {
+ echo 'No IPv4 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
+});
+
+$resolver->resolveAll($name, Message::TYPE_AAAA)->then(function (array $ips) use ($name) {
+ echo 'IPv6 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
+}, function (Exception $e) use ($name) {
+ echo 'No IPv6 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
+});
+
+$loop->run();
diff --git a/vendor/react/dns/examples/12-all-types.php b/vendor/react/dns/examples/12-all-types.php
new file mode 100755
index 0000000..438ee86
--- /dev/null
+++ b/vendor/react/dns/examples/12-all-types.php
@@ -0,0 +1,25 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$name = isset($argv[1]) ? $argv[1] : 'google.com';
+$type = constant('React\Dns\Model\Message::TYPE_' . (isset($argv[2]) ? $argv[2] : 'TXT'));
+
+$resolver->resolveAll($name, $type)->then(function (array $values) {
+ var_dump($values);
+}, function (Exception $e) {
+ echo $e->getMessage() . PHP_EOL;
+});
+
+$loop->run();
diff --git a/vendor/react/dns/examples/13-reverse-dns.php b/vendor/react/dns/examples/13-reverse-dns.php
new file mode 100755
index 0000000..7bc08f5
--- /dev/null
+++ b/vendor/react/dns/examples/13-reverse-dns.php
@@ -0,0 +1,35 @@
+nameservers ? reset($config->nameservers) : '8.8.8.8';
+
+$factory = new Factory();
+$resolver = $factory->create($server, $loop);
+
+$ip = isset($argv[1]) ? $argv[1] : '8.8.8.8';
+
+if (@inet_pton($ip) === false) {
+ exit('Error: Given argument is not a valid IP' . PHP_EOL);
+}
+
+if (strpos($ip, ':') === false) {
+ $name = inet_ntop(strrev(inet_pton($ip))) . '.in-addr.arpa';
+} else {
+ $name = wordwrap(strrev(bin2hex(inet_pton($ip))), 1, '.', true) . '.ip6.arpa';
+}
+
+$resolver->resolveAll($name, Message::TYPE_PTR)->then(function (array $names) {
+ var_dump($names);
+}, function (Exception $e) {
+ echo $e->getMessage() . PHP_EOL;
+});
+
+$loop->run();
diff --git a/vendor/react/dns/examples/91-query-a-and-aaaa.php b/vendor/react/dns/examples/91-query-a-and-aaaa.php
new file mode 100755
index 0000000..e4a3feb
--- /dev/null
+++ b/vendor/react/dns/examples/91-query-a-and-aaaa.php
@@ -0,0 +1,29 @@
+query('8.8.8.8:53', $ipv4Query)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv4: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+$executor->query('8.8.8.8:53', $ipv6Query)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ echo 'IPv6: ' . $answer->data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/react/dns/examples/92-query-any.php b/vendor/react/dns/examples/92-query-any.php
new file mode 100755
index 0000000..dcc14ae
--- /dev/null
+++ b/vendor/react/dns/examples/92-query-any.php
@@ -0,0 +1,71 @@
+query('8.8.8.8:53', $any)->then(function (Message $message) {
+ foreach ($message->answers as $answer) {
+ /* @var $answer Record */
+
+ $data = $answer->data;
+
+ switch ($answer->type) {
+ case Message::TYPE_A:
+ $type = 'A';
+ break;
+ case Message::TYPE_AAAA:
+ $type = 'AAAA';
+ break;
+ case Message::TYPE_NS:
+ $type = 'NS';
+ break;
+ case Message::TYPE_PTR:
+ $type = 'PTR';
+ break;
+ case Message::TYPE_CNAME:
+ $type = 'CNAME';
+ break;
+ case Message::TYPE_TXT:
+ // TXT records can contain a list of (binary) strings for each record.
+ // here, we assume this is printable ASCII and simply concatenate output
+ $type = 'TXT';
+ $data = implode('', $data);
+ break;
+ case Message::TYPE_MX:
+ // MX records contain "priority" and "target", only dump its values here
+ $type = 'MX';
+ $data = implode(' ', $data);
+ break;
+ case Message::TYPE_SRV:
+ // SRV records contains priority, weight, port and target, dump structure here
+ $type = 'SRV';
+ $data = json_encode($data);
+ break;
+ case Message::TYPE_SOA:
+ // SOA records contain structured data, dump structure here
+ $type = 'SOA';
+ $data = json_encode($data);
+ break;
+ default:
+ // unknown type uses HEX format
+ $type = 'Type ' . $answer->type;
+ $data = wordwrap(strtoupper(bin2hex($data)), 2, ' ', true);
+ }
+
+ echo $type . ': ' . $data . PHP_EOL;
+ }
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/react/dns/phpunit.xml.dist b/vendor/react/dns/phpunit.xml.dist
new file mode 100755
index 0000000..04d426b
--- /dev/null
+++ b/vendor/react/dns/phpunit.xml.dist
@@ -0,0 +1,24 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/vendor/react/dns/src/BadServerException.php b/vendor/react/dns/src/BadServerException.php
new file mode 100755
index 0000000..3bf50f1
--- /dev/null
+++ b/vendor/react/dns/src/BadServerException.php
@@ -0,0 +1,7 @@
+nameservers = $matches[1];
+
+ return $config;
+ }
+
+ /**
+ * Loads the DNS configurations from Windows's WMIC (from the given command or default command)
+ *
+ * Note that this method blocks while loading the given command and should
+ * thus be used with care! While this should be relatively fast for normal
+ * WMIC commands, it remains unknown if this may block under certain
+ * circumstances. In particular, this method should only be executed before
+ * the loop starts, not while it is running.
+ *
+ * Note that this method will only try to execute the given command try to
+ * parse its output, irrespective of whether this command exists. In
+ * particular, this command is only available on Windows. Currently, this
+ * will only parse valid nameserver entries from the command output and will
+ * ignore all other output without complaining.
+ *
+ * Note that the previous section implies that this may return an empty
+ * `Config` object if no valid nameserver entries can be found.
+ *
+ * @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
+ * @return self
+ * @link https://ss64.com/nt/wmic.html
+ */
+ public static function loadWmicBlocking($command = null)
+ {
+ $contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
+ preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
+
+ $config = new self();
+ $config->nameservers = $matches[1];
+
+ return $config;
+ }
+
+ public $nameservers = array();
+}
diff --git a/vendor/react/dns/src/Config/FilesystemFactory.php b/vendor/react/dns/src/Config/FilesystemFactory.php
new file mode 100755
index 0000000..68cec3e
--- /dev/null
+++ b/vendor/react/dns/src/Config/FilesystemFactory.php
@@ -0,0 +1,73 @@
+loop = $loop;
+ }
+
+ public function create($filename)
+ {
+ return $this
+ ->loadEtcResolvConf($filename)
+ ->then(array($this, 'parseEtcResolvConf'));
+ }
+
+ /**
+ * @param string $contents
+ * @return Promise
+ * @deprecated see Config instead
+ */
+ public function parseEtcResolvConf($contents)
+ {
+ return Promise\resolve(Config::loadResolvConfBlocking(
+ 'data://text/plain;base64,' . base64_encode($contents)
+ ));
+ }
+
+ public function loadEtcResolvConf($filename)
+ {
+ if (!file_exists($filename)) {
+ return Promise\reject(new \InvalidArgumentException("The filename for /etc/resolv.conf given does not exist: $filename"));
+ }
+
+ try {
+ $deferred = new Deferred();
+
+ $fd = fopen($filename, 'r');
+ stream_set_blocking($fd, 0);
+
+ $contents = '';
+
+ $stream = class_exists('React\Stream\ReadableResourceStream') ? new ReadableResourceStream($fd, $this->loop) : new Stream($fd, $this->loop);
+ $stream->on('data', function ($data) use (&$contents) {
+ $contents .= $data;
+ });
+ $stream->on('end', function () use (&$contents, $deferred) {
+ $deferred->resolve($contents);
+ });
+ $stream->on('error', function ($error) use ($deferred) {
+ $deferred->reject($error);
+ });
+
+ return $deferred->promise();
+ } catch (\Exception $e) {
+ return Promise\reject($e);
+ }
+ }
+}
diff --git a/vendor/react/dns/src/Config/HostsFile.php b/vendor/react/dns/src/Config/HostsFile.php
new file mode 100755
index 0000000..5b6277e
--- /dev/null
+++ b/vendor/react/dns/src/Config/HostsFile.php
@@ -0,0 +1,151 @@
+contents = $contents;
+ }
+
+ /**
+ * Returns all IPs for the given hostname
+ *
+ * @param string $name
+ * @return string[]
+ */
+ public function getIpsForHost($name)
+ {
+ $name = strtolower($name);
+
+ $ips = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line);
+ $ip = array_shift($parts);
+ if ($parts && array_search($name, $parts) !== false) {
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
+ $ip = substr($ip, 0, $pos);
+ }
+
+ if (@inet_pton($ip) !== false) {
+ $ips[] = $ip;
+ }
+ }
+ }
+
+ return $ips;
+ }
+
+ /**
+ * Returns all hostnames for the given IPv4 or IPv6 address
+ *
+ * @param string $ip
+ * @return string[]
+ */
+ public function getHostsForIp($ip)
+ {
+ // check binary representation of IP to avoid string case and short notation
+ $ip = @inet_pton($ip);
+ if ($ip === false) {
+ return array();
+ }
+
+ $names = array();
+ foreach (preg_split('/\r?\n/', $this->contents) as $line) {
+ $parts = preg_split('/\s+/', $line, null, PREG_SPLIT_NO_EMPTY);
+ $addr = array_shift($parts);
+
+ // remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
+ if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
+ $addr = substr($addr, 0, $pos);
+ }
+
+ if (@inet_pton($addr) === $ip) {
+ foreach ($parts as $part) {
+ $names[] = $part;
+ }
+ }
+ }
+
+ return $names;
+ }
+}
diff --git a/vendor/react/dns/src/Model/HeaderBag.php b/vendor/react/dns/src/Model/HeaderBag.php
new file mode 100755
index 0000000..0093bd3
--- /dev/null
+++ b/vendor/react/dns/src/Model/HeaderBag.php
@@ -0,0 +1,59 @@
+ 0,
+ 'anCount' => 0,
+ 'nsCount' => 0,
+ 'arCount' => 0,
+ 'qr' => 0,
+ 'opcode' => Message::OPCODE_QUERY,
+ 'aa' => 0,
+ 'tc' => 0,
+ 'rd' => 0,
+ 'ra' => 0,
+ 'z' => 0,
+ 'rcode' => Message::RCODE_OK,
+ );
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public $data = '';
+
+ public function get($name)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
+ }
+
+ public function set($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ public function isQuery()
+ {
+ return 0 === $this->attributes['qr'];
+ }
+
+ public function isResponse()
+ {
+ return 1 === $this->attributes['qr'];
+ }
+
+ public function isTruncated()
+ {
+ return 1 === $this->attributes['tc'];
+ }
+
+ public function populateCounts(Message $message)
+ {
+ $this->attributes['qdCount'] = count($message->questions);
+ $this->attributes['anCount'] = count($message->answers);
+ $this->attributes['nsCount'] = count($message->authority);
+ $this->attributes['arCount'] = count($message->additional);
+ }
+}
diff --git a/vendor/react/dns/src/Model/Message.php b/vendor/react/dns/src/Model/Message.php
new file mode 100755
index 0000000..da859e7
--- /dev/null
+++ b/vendor/react/dns/src/Model/Message.php
@@ -0,0 +1,188 @@
+header->set('id', self::generateId());
+ $request->header->set('rd', 1);
+ $request->questions[] = (array) $query;
+ $request->prepare();
+
+ return $request;
+ }
+
+ /**
+ * Creates a new response message for the given query with the given answer records
+ *
+ * @param Query $query
+ * @param Record[] $answers
+ * @return self
+ */
+ public static function createResponseWithAnswersForQuery(Query $query, array $answers)
+ {
+ $response = new Message();
+ $response->header->set('id', self::generateId());
+ $response->header->set('qr', 1);
+ $response->header->set('opcode', Message::OPCODE_QUERY);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = (array) $query;
+
+ foreach ($answers as $record) {
+ $response->answers[] = $record;
+ }
+
+ $response->prepare();
+
+ return $response;
+ }
+
+ /**
+ * generates a random 16 bit message ID
+ *
+ * This uses a CSPRNG so that an outside attacker that is sending spoofed
+ * DNS response messages can not guess the message ID to avoid possible
+ * cache poisoning attacks.
+ *
+ * The `random_int()` function is only available on PHP 7+ or when
+ * https://github.com/paragonie/random_compat is installed. As such, using
+ * the latest supported PHP version is highly recommended. This currently
+ * falls back to a less secure random number generator on older PHP versions
+ * in the hope that this system is properly protected against outside
+ * attackers, for example by using one of the common local DNS proxy stubs.
+ *
+ * @return int
+ * @see self::getId()
+ * @codeCoverageIgnore
+ */
+ private static function generateId()
+ {
+ if (function_exists('random_int')) {
+ return random_int(0, 0xffff);
+ }
+ return mt_rand(0, 0xffff);
+ }
+
+ /**
+ * @var HeaderBag
+ */
+ public $header;
+
+ /**
+ * This should be an array of Query objects. For BC reasons, this currently
+ * references a nested array with a structure that results from casting the
+ * Query objects to an array:
+ *
+ * ```php
+ * $questions = array(
+ * array(
+ * 'name' => 'reactphp.org',
+ * 'type' => Message::TYPE_A,
+ * 'class' => Message::CLASS_IN
+ * )
+ * );
+ * ```
+ *
+ * @var array
+ * @see Query
+ */
+ public $questions = array();
+
+ /**
+ * @var Record[]
+ */
+ public $answers = array();
+
+ /**
+ * @var Record[]
+ */
+ public $authority = array();
+
+ /**
+ * @var Record[]
+ */
+ public $additional = array();
+
+ /**
+ * @deprecated still used internally for BC reasons, should not be used externally.
+ */
+ public $data = '';
+
+ /**
+ * @deprecated still used internally for BC reasons, should not be used externally.
+ */
+ public $consumed = 0;
+
+ public function __construct()
+ {
+ $this->header = new HeaderBag();
+ }
+
+ /**
+ * Returns the 16 bit message ID
+ *
+ * The response message ID has to match the request message ID. This allows
+ * the receiver to verify this is the correct response message. An outside
+ * attacker may try to inject fake responses by "guessing" the message ID,
+ * so this should use a proper CSPRNG to avoid possible cache poisoning.
+ *
+ * @return int
+ * @see self::generateId()
+ */
+ public function getId()
+ {
+ return $this->header->get('id');
+ }
+
+ /**
+ * Returns the response code (RCODE)
+ *
+ * @return int see self::RCODE_* constants
+ */
+ public function getResponseCode()
+ {
+ return $this->header->get('rcode');
+ }
+
+ public function prepare()
+ {
+ $this->header->populateCounts($this);
+ }
+}
diff --git a/vendor/react/dns/src/Model/Record.php b/vendor/react/dns/src/Model/Record.php
new file mode 100755
index 0000000..2504911
--- /dev/null
+++ b/vendor/react/dns/src/Model/Record.php
@@ -0,0 +1,105 @@
+name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->ttl = $ttl;
+ $this->data = $data;
+ }
+}
diff --git a/vendor/react/dns/src/Protocol/BinaryDumper.php b/vendor/react/dns/src/Protocol/BinaryDumper.php
new file mode 100755
index 0000000..0391604
--- /dev/null
+++ b/vendor/react/dns/src/Protocol/BinaryDumper.php
@@ -0,0 +1,163 @@
+headerToBinary($message->header);
+ $data .= $this->questionToBinary($message->questions);
+ $data .= $this->recordsToBinary($message->answers);
+ $data .= $this->recordsToBinary($message->authority);
+ $data .= $this->recordsToBinary($message->additional);
+
+ return $data;
+ }
+
+ /**
+ * @param HeaderBag $header
+ * @return string
+ */
+ private function headerToBinary(HeaderBag $header)
+ {
+ $data = '';
+
+ $data .= pack('n', $header->get('id'));
+
+ $flags = 0x00;
+ $flags = ($flags << 1) | $header->get('qr');
+ $flags = ($flags << 4) | $header->get('opcode');
+ $flags = ($flags << 1) | $header->get('aa');
+ $flags = ($flags << 1) | $header->get('tc');
+ $flags = ($flags << 1) | $header->get('rd');
+ $flags = ($flags << 1) | $header->get('ra');
+ $flags = ($flags << 3) | $header->get('z');
+ $flags = ($flags << 4) | $header->get('rcode');
+
+ $data .= pack('n', $flags);
+
+ $data .= pack('n', $header->get('qdCount'));
+ $data .= pack('n', $header->get('anCount'));
+ $data .= pack('n', $header->get('nsCount'));
+ $data .= pack('n', $header->get('arCount'));
+
+ return $data;
+ }
+
+ /**
+ * @param array $questions
+ * @return string
+ */
+ private function questionToBinary(array $questions)
+ {
+ $data = '';
+
+ foreach ($questions as $question) {
+ $data .= $this->domainNameToBinary($question['name']);
+ $data .= pack('n*', $question['type'], $question['class']);
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param Record[] $records
+ * @return string
+ */
+ private function recordsToBinary(array $records)
+ {
+ $data = '';
+
+ foreach ($records as $record) {
+ /* @var $record Record */
+ switch ($record->type) {
+ case Message::TYPE_A:
+ case Message::TYPE_AAAA:
+ $binary = \inet_pton($record->data);
+ break;
+ case Message::TYPE_CNAME:
+ case Message::TYPE_NS:
+ case Message::TYPE_PTR:
+ $binary = $this->domainNameToBinary($record->data);
+ break;
+ case Message::TYPE_TXT:
+ $binary = $this->textsToBinary($record->data);
+ break;
+ case Message::TYPE_MX:
+ $binary = \pack(
+ 'n',
+ $record->data['priority']
+ );
+ $binary .= $this->domainNameToBinary($record->data['target']);
+ break;
+ case Message::TYPE_SRV:
+ $binary = \pack(
+ 'n*',
+ $record->data['priority'],
+ $record->data['weight'],
+ $record->data['port']
+ );
+ $binary .= $this->domainNameToBinary($record->data['target']);
+ break;
+ case Message::TYPE_SOA:
+ $binary = $this->domainNameToBinary($record->data['mname']);
+ $binary .= $this->domainNameToBinary($record->data['rname']);
+ $binary .= \pack(
+ 'N*',
+ $record->data['serial'],
+ $record->data['refresh'],
+ $record->data['retry'],
+ $record->data['expire'],
+ $record->data['minimum']
+ );
+ break;
+ default:
+ // RDATA is already stored as binary value for unknown record types
+ $binary = $record->data;
+ }
+
+ $data .= $this->domainNameToBinary($record->name);
+ $data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
+ $data .= $binary;
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param string[] $texts
+ * @return string
+ */
+ private function textsToBinary(array $texts)
+ {
+ $data = '';
+ foreach ($texts as $text) {
+ $data .= \chr(\strlen($text)) . $text;
+ }
+ return $data;
+ }
+
+ /**
+ * @param string $host
+ * @return string
+ */
+ private function domainNameToBinary($host)
+ {
+ if ($host === '') {
+ return "\0";
+ }
+
+ return $this->textsToBinary(\explode('.', $host . '.'));
+ }
+}
diff --git a/vendor/react/dns/src/Protocol/Parser.php b/vendor/react/dns/src/Protocol/Parser.php
new file mode 100755
index 0000000..ada9db1
--- /dev/null
+++ b/vendor/react/dns/src/Protocol/Parser.php
@@ -0,0 +1,395 @@
+parse($data, $message) !== $message) {
+ throw new InvalidArgumentException('Unable to parse binary message');
+ }
+
+ return $message;
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function parseChunk($data, Message $message)
+ {
+ return $this->parse($data, $message);
+ }
+
+ private function parse($data, Message $message)
+ {
+ $message->data .= $data;
+
+ if (!$message->header->get('id')) {
+ if (!$this->parseHeader($message)) {
+ return;
+ }
+ }
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ if (!$this->parseQuestion($message)) {
+ return;
+ }
+ }
+
+ // parse all answer records
+ for ($i = $message->header->get('anCount'); $i > 0; --$i) {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return;
+ } else {
+ $message->answers[] = $record;
+ }
+ }
+
+ // parse all authority records
+ for ($i = $message->header->get('nsCount'); $i > 0; --$i) {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return;
+ } else {
+ $message->authority[] = $record;
+ }
+ }
+
+ // parse all additional records
+ for ($i = $message->header->get('arCount'); $i > 0; --$i) {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return;
+ } else {
+ $message->additional[] = $record;
+ }
+ }
+
+ return $message;
+ }
+
+ public function parseHeader(Message $message)
+ {
+ if (!isset($message->data[12 - 1])) {
+ return;
+ }
+
+ $header = substr($message->data, 0, 12);
+ $message->consumed += 12;
+
+ list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header));
+
+ $rcode = $fields & bindec('1111');
+ $z = ($fields >> 4) & bindec('111');
+ $ra = ($fields >> 7) & 1;
+ $rd = ($fields >> 8) & 1;
+ $tc = ($fields >> 9) & 1;
+ $aa = ($fields >> 10) & 1;
+ $opcode = ($fields >> 11) & bindec('1111');
+ $qr = ($fields >> 15) & 1;
+
+ $vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount',
+ 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode');
+
+ foreach ($vars as $name => $value) {
+ $message->header->set($name, $value);
+ }
+
+ return $message;
+ }
+
+ public function parseQuestion(Message $message)
+ {
+ $consumed = $message->consumed;
+
+ list($labels, $consumed) = $this->readLabels($message->data, $consumed);
+
+ if ($labels === null || !isset($message->data[$consumed + 4 - 1])) {
+ return;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ $message->consumed = $consumed;
+
+ $message->questions[] = array(
+ 'name' => implode('.', $labels),
+ 'type' => $type,
+ 'class' => $class,
+ );
+
+ if ($message->header->get('qdCount') != count($message->questions)) {
+ return $this->parseQuestion($message);
+ }
+
+ return $message;
+ }
+
+ /**
+ * recursively parse all answers from the message data into message answer records
+ *
+ * @param Message $message
+ * @return ?Message returns the updated message on success or null if the data is invalid/incomplete
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function parseAnswer(Message $message)
+ {
+ $record = $this->parseRecord($message);
+ if ($record === null) {
+ return null;
+ }
+
+ $message->answers[] = $record;
+
+ if ($message->header->get('anCount') != count($message->answers)) {
+ return $this->parseAnswer($message);
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param Message $message
+ * @return ?Record returns parsed Record on success or null if data is invalid/incomplete
+ */
+ private function parseRecord(Message $message)
+ {
+ $consumed = $message->consumed;
+
+ list($name, $consumed) = $this->readDomain($message->data, $consumed);
+
+ if ($name === null || !isset($message->data[$consumed + 10 - 1])) {
+ return null;
+ }
+
+ list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4)));
+ $consumed += 4;
+
+ // TTL is a UINT32 that must not have most significant bit set for BC reasons
+ if ($ttl < 0 || $ttl >= 1 << 31) {
+ $ttl = 0;
+ }
+
+ list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2)));
+ $consumed += 2;
+
+ if (!isset($message->data[$consumed + $rdLength - 1])) {
+ return null;
+ }
+
+ $rdata = null;
+ $expected = $consumed + $rdLength;
+
+ if (Message::TYPE_A === $type) {
+ if ($rdLength === 4) {
+ $rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_AAAA === $type) {
+ if ($rdLength === 16) {
+ $rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
+ $consumed += $rdLength;
+ }
+ } elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
+ list($rdata, $consumed) = $this->readDomain($message->data, $consumed);
+ } elseif (Message::TYPE_TXT === $type) {
+ $rdata = array();
+ while ($consumed < $expected) {
+ $len = ord($message->data[$consumed]);
+ $rdata[] = (string)substr($message->data, $consumed + 1, $len);
+ $consumed += $len + 1;
+ }
+ } elseif (Message::TYPE_MX === $type) {
+ if ($rdLength > 2) {
+ list($priority) = array_values(unpack('n', substr($message->data, $consumed, 2)));
+ list($target, $consumed) = $this->readDomain($message->data, $consumed + 2);
+
+ $rdata = array(
+ 'priority' => $priority,
+ 'target' => $target
+ );
+ }
+ } elseif (Message::TYPE_SRV === $type) {
+ if ($rdLength > 6) {
+ list($priority, $weight, $port) = array_values(unpack('n*', substr($message->data, $consumed, 6)));
+ list($target, $consumed) = $this->readDomain($message->data, $consumed + 6);
+
+ $rdata = array(
+ 'priority' => $priority,
+ 'weight' => $weight,
+ 'port' => $port,
+ 'target' => $target
+ );
+ }
+ } elseif (Message::TYPE_SOA === $type) {
+ list($mname, $consumed) = $this->readDomain($message->data, $consumed);
+ list($rname, $consumed) = $this->readDomain($message->data, $consumed);
+
+ if ($mname !== null && $rname !== null && isset($message->data[$consumed + 20 - 1])) {
+ list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($message->data, $consumed, 20)));
+ $consumed += 20;
+
+ $rdata = array(
+ 'mname' => $mname,
+ 'rname' => $rname,
+ 'serial' => $serial,
+ 'refresh' => $refresh,
+ 'retry' => $retry,
+ 'expire' => $expire,
+ 'minimum' => $minimum
+ );
+ }
+ } else {
+ // unknown types simply parse rdata as an opaque binary string
+ $rdata = substr($message->data, $consumed, $rdLength);
+ $consumed += $rdLength;
+ }
+
+ // ensure parsing record data consumes expact number of bytes indicated in record length
+ if ($consumed !== $expected || $rdata === null) {
+ return null;
+ }
+
+ $message->consumed = $consumed;
+
+ return new Record($name, $type, $class, $ttl, $rdata);
+ }
+
+ private function readDomain($data, $consumed)
+ {
+ list ($labels, $consumed) = $this->readLabels($data, $consumed);
+
+ if ($labels === null) {
+ return array(null, null);
+ }
+
+ return array(implode('.', $labels), $consumed);
+ }
+
+ private function readLabels($data, $consumed)
+ {
+ $labels = array();
+
+ while (true) {
+ if (!isset($data[$consumed])) {
+ return array(null, null);
+ }
+
+ $length = \ord($data[$consumed]);
+
+ // end of labels reached
+ if ($length === 0) {
+ $consumed += 1;
+ break;
+ }
+
+ // first two bits set? this is a compressed label (14 bit pointer offset)
+ if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1])) {
+ $offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
+ if ($offset >= $consumed) {
+ return array(null, null);
+ }
+
+ $consumed += 2;
+ list($newLabels) = $this->readLabels($data, $offset);
+
+ if ($newLabels === null) {
+ return array(null, null);
+ }
+
+ $labels = array_merge($labels, $newLabels);
+ break;
+ }
+
+ // length MUST be 0-63 (6 bits only) and data has to be large enough
+ if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
+ return array(null, null);
+ }
+
+ $labels[] = substr($data, $consumed + 1, $length);
+ $consumed += $length + 1;
+ }
+
+ return array($labels, $consumed);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function isEndOfLabels($data, $consumed)
+ {
+ $length = ord(substr($data, $consumed, 1));
+ return 0 === $length;
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function getCompressedLabel($data, $consumed)
+ {
+ list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed);
+ list($labels) = $this->readLabels($data, $nameOffset);
+
+ return array($labels, $consumed);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function isCompressedLabel($data, $consumed)
+ {
+ $mask = 0xc000; // 1100000000000000
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return (bool) ($peek & $mask);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function getCompressedLabelOffset($data, $consumed)
+ {
+ $mask = 0x3fff; // 0011111111111111
+ list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
+
+ return array($peek & $mask, $consumed + 2);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ * @codeCoverageIgnore
+ */
+ public function signedLongToUnsignedLong($i)
+ {
+ return $i & 0x80000000 ? $i - 0xffffffff : $i;
+ }
+}
diff --git a/vendor/react/dns/src/Query/CachedExecutor.php b/vendor/react/dns/src/Query/CachedExecutor.php
new file mode 100755
index 0000000..8b70894
--- /dev/null
+++ b/vendor/react/dns/src/Query/CachedExecutor.php
@@ -0,0 +1,59 @@
+executor = $executor;
+ $this->cache = $cache;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $executor = $this->executor;
+ $cache = $this->cache;
+
+ return $this->cache
+ ->lookup($query)
+ ->then(
+ function ($cachedRecords) use ($query) {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ },
+ function () use ($executor, $cache, $nameserver, $query) {
+ return $executor
+ ->query($nameserver, $query)
+ ->then(function ($response) use ($cache, $query) {
+ $cache->storeResponseMessage($query->currentTime, $response);
+ return $response;
+ });
+ }
+ );
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function buildResponse(Query $query, array $cachedRecords)
+ {
+ return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+}
diff --git a/vendor/react/dns/src/Query/CachingExecutor.php b/vendor/react/dns/src/Query/CachingExecutor.php
new file mode 100755
index 0000000..e6ec3ac
--- /dev/null
+++ b/vendor/react/dns/src/Query/CachingExecutor.php
@@ -0,0 +1,88 @@
+executor = $executor;
+ $this->cache = $cache;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $id = $query->name . ':' . $query->type . ':' . $query->class;
+ $cache = $this->cache;
+ $that = $this;
+ $executor = $this->executor;
+
+ $pending = $cache->get($id);
+ return new Promise(function ($resolve, $reject) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
+ $pending->then(
+ function ($message) use ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
+ // return cached response message on cache hit
+ if ($message !== null) {
+ return $message;
+ }
+
+ // perform DNS lookup if not already cached
+ return $pending = $executor->query($nameserver, $query)->then(
+ function (Message $message) use ($cache, $id, $that) {
+ // DNS response message received => store in cache when not truncated and return
+ if (!$message->header->isTruncated()) {
+ $cache->set($id, $message, $that->ttl($message));
+ }
+
+ return $message;
+ }
+ );
+ }
+ )->then($resolve, function ($e) use ($reject, &$pending) {
+ $reject($e);
+ $pending = null;
+ });
+ }, function ($_, $reject) use (&$pending, $query) {
+ $reject(new \RuntimeException('DNS query for ' . $query->name . ' has been cancelled'));
+ $pending->cancel();
+ $pending = null;
+ });
+ }
+
+ /**
+ * @param Message $message
+ * @return int
+ * @internal
+ */
+ public function ttl(Message $message)
+ {
+ // select TTL from answers (should all be the same), use smallest value if available
+ // @link https://tools.ietf.org/html/rfc2181#section-5.2
+ $ttl = null;
+ foreach ($message->answers as $answer) {
+ if ($ttl === null || $answer->ttl < $ttl) {
+ $ttl = $answer->ttl;
+ }
+ }
+
+ if ($ttl === null) {
+ $ttl = self::TTL;
+ }
+
+ return $ttl;
+ }
+}
diff --git a/vendor/react/dns/src/Query/CancellationException.php b/vendor/react/dns/src/Query/CancellationException.php
new file mode 100755
index 0000000..ac30f4c
--- /dev/null
+++ b/vendor/react/dns/src/Query/CancellationException.php
@@ -0,0 +1,7 @@
+executor = $base;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $key = $this->serializeQueryToIdentity($query);
+ if (isset($this->pending[$key])) {
+ // same query is already pending, so use shared reference to pending query
+ $promise = $this->pending[$key];
+ ++$this->counts[$key];
+ } else {
+ // no such query pending, so start new query and keep reference until it's fulfilled or rejected
+ $promise = $this->executor->query($nameserver, $query);
+ $this->pending[$key] = $promise;
+ $this->counts[$key] = 1;
+
+ $pending =& $this->pending;
+ $counts =& $this->counts;
+ $promise->then(function () use ($key, &$pending, &$counts) {
+ unset($pending[$key], $counts[$key]);
+ }, function () use ($key, &$pending, &$counts) {
+ unset($pending[$key], $counts[$key]);
+ });
+ }
+
+ // Return a child promise awaiting the pending query.
+ // Cancelling this child promise should only cancel the pending query
+ // when no other child promise is awaiting the same query.
+ $pending =& $this->pending;
+ $counts =& $this->counts;
+ return new Promise(function ($resolve, $reject) use ($promise) {
+ $promise->then($resolve, $reject);
+ }, function () use (&$promise, $key, $query, &$pending, &$counts) {
+ if (--$counts[$key] < 1) {
+ unset($pending[$key], $counts[$key]);
+ $promise->cancel();
+ $promise = null;
+ }
+ throw new \RuntimeException('DNS query for ' . $query->name . ' has been cancelled');
+ });
+ }
+
+ private function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+}
diff --git a/vendor/react/dns/src/Query/Executor.php b/vendor/react/dns/src/Query/Executor.php
new file mode 100755
index 0000000..40f6bb4
--- /dev/null
+++ b/vendor/react/dns/src/Query/Executor.php
@@ -0,0 +1,160 @@
+loop = $loop;
+ $this->parser = $parser;
+ $this->dumper = $dumper;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ $transport = strlen($queryData) > 512 ? 'tcp' : 'udp';
+
+ return $this->doQuery($nameserver, $transport, $queryData, $query->name);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function prepareRequest(Query $query)
+ {
+ return Message::createRequestForQuery($query);
+ }
+
+ public function doQuery($nameserver, $transport, $queryData, $name)
+ {
+ // we only support UDP right now
+ if ($transport !== 'udp') {
+ return Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $name . ' failed: Requested transport "' . $transport . '" not available, only UDP is supported in this version'
+ ));
+ }
+
+ $that = $this;
+ $parser = $this->parser;
+ $loop = $this->loop;
+
+ // UDP connections are instant, so try this without a timer
+ try {
+ $conn = $this->createConnection($nameserver, $transport);
+ } catch (\Exception $e) {
+ return Promise\reject(new \RuntimeException('DNS query for ' . $name . ' failed: ' . $e->getMessage(), 0, $e));
+ }
+
+ $deferred = new Deferred(function ($resolve, $reject) use (&$timer, $loop, &$conn, $name) {
+ $reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name)));
+
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+ $conn->close();
+ });
+
+ $timer = null;
+ if ($this->timeout !== null) {
+ $timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) {
+ $conn->close();
+ $deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name)));
+ });
+ }
+
+ $conn->on('data', function ($data) use ($conn, $parser, $deferred, $timer, $loop, $name) {
+ $conn->end();
+ if ($timer !== null) {
+ $loop->cancelTimer($timer);
+ }
+
+ try {
+ $response = $parser->parseMessage($data);
+ } catch (\Exception $e) {
+ $deferred->reject($e);
+ return;
+ }
+
+ if ($response->header->isTruncated()) {
+ $deferred->reject(new \RuntimeException('DNS query for ' . $name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+ $conn->write($queryData);
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ protected function generateId()
+ {
+ return mt_rand(0, 0xffff);
+ }
+
+ /**
+ * @param string $nameserver
+ * @param string $transport
+ * @return \React\Stream\DuplexStreamInterface
+ */
+ protected function createConnection($nameserver, $transport)
+ {
+ $fd = @stream_socket_client("$transport://$nameserver", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
+ if ($fd === false) {
+ throw new \RuntimeException('Unable to connect to DNS server: ' . $errstr, $errno);
+ }
+
+ // Instantiate stream instance around this stream resource.
+ // This ought to be replaced with a datagram socket in the future.
+ // Temporary work around for Windows 10: buffer whole UDP response
+ // @coverageIgnoreStart
+ if (!class_exists('React\Stream\Stream')) {
+ // prefer DuplexResourceStream as of react/stream v0.7.0
+ $conn = new DuplexResourceStream($fd, $this->loop, -1);
+ } else {
+ // use legacy Stream class for react/stream < v0.7.0
+ $conn = new Stream($fd, $this->loop);
+ $conn->bufferSize = null;
+ }
+ // @coverageIgnoreEnd
+
+ return $conn;
+ }
+}
diff --git a/vendor/react/dns/src/Query/ExecutorInterface.php b/vendor/react/dns/src/Query/ExecutorInterface.php
new file mode 100755
index 0000000..2f7a635
--- /dev/null
+++ b/vendor/react/dns/src/Query/ExecutorInterface.php
@@ -0,0 +1,8 @@
+hosts = $hosts;
+ $this->fallback = $fallback;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
+ // forward lookup for type A or AAAA
+ $records = array();
+ $expectsColon = $query->type === Message::TYPE_AAAA;
+ foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
+ // ensure this is an IPv4/IPV6 address according to query type
+ if ((strpos($ip, ':') !== false) === $expectsColon) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
+ }
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ } elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
+ // reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
+ $ip = $this->getIpFromHost($query->name);
+
+ if ($ip !== null) {
+ $records = array();
+ foreach ($this->hosts->getHostsForIp($ip) as $host) {
+ $records[] = new Record($query->name, $query->type, $query->class, 0, $host);
+ }
+
+ if ($records) {
+ return Promise\resolve(
+ Message::createResponseWithAnswersForQuery($query, $records)
+ );
+ }
+ }
+ }
+
+ return $this->fallback->query($nameserver, $query);
+ }
+
+ private function getIpFromHost($host)
+ {
+ if (substr($host, -13) === '.in-addr.arpa') {
+ // IPv4: read as IP and reverse bytes
+ $ip = @inet_pton(substr($host, 0, -13));
+ if ($ip === false || isset($ip[4])) {
+ return null;
+ }
+
+ return inet_ntop(strrev($ip));
+ } elseif (substr($host, -9) === '.ip6.arpa') {
+ // IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
+ $ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
+ if ($ip === false) {
+ return null;
+ }
+
+ return $ip;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/vendor/react/dns/src/Query/Query.php b/vendor/react/dns/src/Query/Query.php
new file mode 100755
index 0000000..058a78d
--- /dev/null
+++ b/vendor/react/dns/src/Query/Query.php
@@ -0,0 +1,33 @@
+name = $name;
+ $this->type = $type;
+ $this->class = $class;
+ $this->currentTime = $currentTime;
+ }
+}
diff --git a/vendor/react/dns/src/Query/RecordBag.php b/vendor/react/dns/src/Query/RecordBag.php
new file mode 100755
index 0000000..4dc815a
--- /dev/null
+++ b/vendor/react/dns/src/Query/RecordBag.php
@@ -0,0 +1,30 @@
+records[] = array($currentTime + $record->ttl, $record);
+ }
+
+ public function all()
+ {
+ return array_values(array_map(
+ function ($value) {
+ list($expiresAt, $record) = $value;
+ return $record;
+ },
+ $this->records
+ ));
+ }
+}
diff --git a/vendor/react/dns/src/Query/RecordCache.php b/vendor/react/dns/src/Query/RecordCache.php
new file mode 100755
index 0000000..c087e5f
--- /dev/null
+++ b/vendor/react/dns/src/Query/RecordCache.php
@@ -0,0 +1,122 @@
+cache = $cache;
+ }
+
+ /**
+ * Looks up the cache if there's a cached answer for the given query
+ *
+ * @param Query $query
+ * @return PromiseInterface Promise resolves with array of Record objects on sucess
+ * or rejects with mixed values when query is not cached already.
+ */
+ public function lookup(Query $query)
+ {
+ $id = $this->serializeQueryToIdentity($query);
+
+ $expiredAt = $this->expiredAt;
+
+ return $this->cache
+ ->get($id)
+ ->then(function ($value) use ($query, $expiredAt) {
+ // reject on cache miss
+ if ($value === null) {
+ return Promise\reject();
+ }
+
+ /* @var $recordBag RecordBag */
+ $recordBag = unserialize($value);
+
+ // reject this cache hit if the query was started before the time we expired the cache?
+ // todo: this is a legacy left over, this value is never actually set, so this never applies.
+ // todo: this should probably validate the cache time instead.
+ if (null !== $expiredAt && $expiredAt <= $query->currentTime) {
+ return Promise\reject();
+ }
+
+ return $recordBag->all();
+ });
+ }
+
+ /**
+ * Stores all records from this response message in the cache
+ *
+ * @param int $currentTime
+ * @param Message $message
+ * @uses self::storeRecord()
+ */
+ public function storeResponseMessage($currentTime, Message $message)
+ {
+ foreach ($message->answers as $record) {
+ $this->storeRecord($currentTime, $record);
+ }
+ }
+
+ /**
+ * Stores a single record from a response message in the cache
+ *
+ * @param int $currentTime
+ * @param Record $record
+ */
+ public function storeRecord($currentTime, Record $record)
+ {
+ $id = $this->serializeRecordToIdentity($record);
+
+ $cache = $this->cache;
+
+ $this->cache
+ ->get($id)
+ ->then(
+ function ($value) {
+ // return empty bag on cache miss
+ if ($value === null) {
+ return new RecordBag();
+ }
+
+ // reuse existing bag on cache hit to append new record to it
+ return unserialize($value);
+ }
+ )
+ ->then(function (RecordBag $recordBag) use ($id, $currentTime, $record, $cache) {
+ // add a record to the existing (possibly empty) record bag and save to cache
+ $recordBag->set($currentTime, $record);
+ $cache->set($id, serialize($recordBag));
+ });
+ }
+
+ public function expire($currentTime)
+ {
+ $this->expiredAt = $currentTime;
+ }
+
+ public function serializeQueryToIdentity(Query $query)
+ {
+ return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
+ }
+
+ public function serializeRecordToIdentity(Record $record)
+ {
+ return sprintf('%s:%s:%s', $record->name, $record->type, $record->class);
+ }
+}
diff --git a/vendor/react/dns/src/Query/RetryExecutor.php b/vendor/react/dns/src/Query/RetryExecutor.php
new file mode 100755
index 0000000..46e2ef9
--- /dev/null
+++ b/vendor/react/dns/src/Query/RetryExecutor.php
@@ -0,0 +1,79 @@
+executor = $executor;
+ $this->retries = $retries;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return $this->tryQuery($nameserver, $query, $this->retries);
+ }
+
+ public function tryQuery($nameserver, Query $query, $retries)
+ {
+ $deferred = new Deferred(function () use (&$promise) {
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ });
+
+ $success = function ($value) use ($deferred, &$errorback) {
+ $errorback = null;
+ $deferred->resolve($value);
+ };
+
+ $executor = $this->executor;
+ $errorback = function ($e) use ($deferred, &$promise, $nameserver, $query, $success, &$errorback, &$retries, $executor) {
+ if (!$e instanceof TimeoutException) {
+ $errorback = null;
+ $deferred->reject($e);
+ } elseif ($retries <= 0) {
+ $errorback = null;
+ $deferred->reject($e = new \RuntimeException(
+ 'DNS query for ' . $query->name . ' failed: too many retries',
+ 0,
+ $e
+ ));
+
+ // avoid garbage references by replacing all closures in call stack.
+ // what a lovely piece of code!
+ $r = new \ReflectionProperty('Exception', 'trace');
+ $r->setAccessible(true);
+ $trace = $r->getValue($e);
+ foreach ($trace as &$one) {
+ foreach ($one['args'] as &$arg) {
+ if ($arg instanceof \Closure) {
+ $arg = 'Object(' . \get_class($arg) . ')';
+ }
+ }
+ }
+ $r->setValue($e, $trace);
+ } else {
+ --$retries;
+ $promise = $executor->query($nameserver, $query)->then(
+ $success,
+ $errorback
+ );
+ }
+ };
+
+ $promise = $this->executor->query($nameserver, $query)->then(
+ $success,
+ $errorback
+ );
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/react/dns/src/Query/TimeoutException.php b/vendor/react/dns/src/Query/TimeoutException.php
new file mode 100755
index 0000000..90bf806
--- /dev/null
+++ b/vendor/react/dns/src/Query/TimeoutException.php
@@ -0,0 +1,7 @@
+executor = $executor;
+ $this->loop = $loop;
+ $this->timeout = $timeout;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ return Timer\timeout($this->executor->query($nameserver, $query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) {
+ if ($e instanceof Timer\TimeoutException) {
+ $e = new TimeoutException(sprintf("DNS query for %s timed out", $query->name), 0, $e);
+ }
+ throw $e;
+ });
+ }
+}
diff --git a/vendor/react/dns/src/Query/UdpTransportExecutor.php b/vendor/react/dns/src/Query/UdpTransportExecutor.php
new file mode 100755
index 0000000..99a3e8a
--- /dev/null
+++ b/vendor/react/dns/src/Query/UdpTransportExecutor.php
@@ -0,0 +1,181 @@
+query(
+ * '8.8.8.8:53',
+ * new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
+ * )->then(function (Message $message) {
+ * foreach ($message->answers as $answer) {
+ * echo 'IPv6: ' . $answer->data . PHP_EOL;
+ * }
+ * }, 'printf');
+ *
+ * $loop->run();
+ * ```
+ *
+ * See also the [fourth example](examples).
+ *
+ * Note that this executor does not implement a timeout, so you will very likely
+ * want to use this in combination with a `TimeoutExecutor` like this:
+ *
+ * ```php
+ * $executor = new TimeoutExecutor(
+ * new UdpTransportExecutor($loop),
+ * 3.0,
+ * $loop
+ * );
+ * ```
+ *
+ * Also note that this executor uses an unreliable UDP transport and that it
+ * does not implement any retry logic, so you will likely want to use this in
+ * combination with a `RetryExecutor` like this:
+ *
+ * ```php
+ * $executor = new RetryExecutor(
+ * new TimeoutExecutor(
+ * new UdpTransportExecutor($loop),
+ * 3.0,
+ * $loop
+ * )
+ * );
+ * ```
+ *
+ * Note that this executor is entirely async and as such allows you to execute
+ * any number of queries concurrently. You should probably limit the number of
+ * concurrent queries in your application or you're very likely going to face
+ * rate limitations and bans on the resolver end. For many common applications,
+ * you may want to avoid sending the same query multiple times when the first
+ * one is still pending, so you will likely want to use this in combination with
+ * a `CoopExecutor` like this:
+ *
+ * ```php
+ * $executor = new CoopExecutor(
+ * new RetryExecutor(
+ * new TimeoutExecutor(
+ * new UdpTransportExecutor($loop),
+ * 3.0,
+ * $loop
+ * )
+ * )
+ * );
+ * ```
+ *
+ * > Internally, this class uses PHP's UDP sockets and does not take advantage
+ * of [react/datagram](https://github.com/reactphp/datagram) purely for
+ * organizational reasons to avoid a cyclic dependency between the two
+ * packages. Higher-level components should take advantage of the Datagram
+ * component instead of reimplementing this socket logic from scratch.
+ */
+class UdpTransportExecutor implements ExecutorInterface
+{
+ private $loop;
+ private $parser;
+ private $dumper;
+
+ /**
+ * @param LoopInterface $loop
+ * @param null|Parser $parser optional/advanced: DNS protocol parser to use
+ * @param null|BinaryDumper $dumper optional/advanced: DNS protocol dumper to use
+ */
+ public function __construct(LoopInterface $loop, Parser $parser = null, BinaryDumper $dumper = null)
+ {
+ if ($parser === null) {
+ $parser = new Parser();
+ }
+ if ($dumper === null) {
+ $dumper = new BinaryDumper();
+ }
+
+ $this->loop = $loop;
+ $this->parser = $parser;
+ $this->dumper = $dumper;
+ }
+
+ public function query($nameserver, Query $query)
+ {
+ $request = Message::createRequestForQuery($query);
+
+ $queryData = $this->dumper->toBinary($request);
+ if (isset($queryData[512])) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->name . ' failed: Query too large for UDP transport'
+ ));
+ }
+
+ // UDP connections are instant, so try connection without a loop or timeout
+ $socket = @\stream_socket_client("udp://$nameserver", $errno, $errstr, 0);
+ if ($socket === false) {
+ return \React\Promise\reject(new \RuntimeException(
+ 'DNS query for ' . $query->name . ' failed: Unable to connect to DNS server (' . $errstr . ')',
+ $errno
+ ));
+ }
+
+ // set socket to non-blocking and immediately try to send (fill write buffer)
+ \stream_set_blocking($socket, false);
+ \fwrite($socket, $queryData);
+
+ $loop = $this->loop;
+ $deferred = new Deferred(function () use ($loop, $socket, $query) {
+ // cancellation should remove socket from loop and close socket
+ $loop->removeReadStream($socket);
+ \fclose($socket);
+
+ throw new CancellationException('DNS query for ' . $query->name . ' has been cancelled');
+ });
+
+ $parser = $this->parser;
+ $loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request) {
+ // try to read a single data packet from the DNS server
+ // ignoring any errors, this is uses UDP packets and not a stream of data
+ $data = @\fread($socket, 512);
+
+ try {
+ $response = $parser->parseMessage($data);
+ } catch (\Exception $e) {
+ // ignore and await next if we received an invalid message from remote server
+ // this may as well be a fake response from an attacker (possible DOS)
+ return;
+ }
+
+ // ignore and await next if we received an unexpected response ID
+ // this may as well be a fake response from an attacker (possible cache poisoning)
+ if ($response->getId() !== $request->getId()) {
+ return;
+ }
+
+ // we only react to the first valid message, so remove socket from loop and close
+ $loop->removeReadStream($socket);
+ \fclose($socket);
+
+ if ($response->header->isTruncated()) {
+ $deferred->reject(new \RuntimeException('DNS query for ' . $query->name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
+ return;
+ }
+
+ $deferred->resolve($response);
+ });
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/react/dns/src/RecordNotFoundException.php b/vendor/react/dns/src/RecordNotFoundException.php
new file mode 100755
index 0000000..0028413
--- /dev/null
+++ b/vendor/react/dns/src/RecordNotFoundException.php
@@ -0,0 +1,7 @@
+addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($loop));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null)
+ {
+ // default to keeping maximum of 256 responses in cache unless explicitly given
+ if (!($cache instanceof CacheInterface)) {
+ $cache = new ArrayCache(256);
+ }
+
+ $nameserver = $this->addPortToServerIfMissing($nameserver);
+ $executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($loop, $cache));
+
+ return new Resolver($nameserver, $executor);
+ }
+
+ /**
+ * Tries to load the hosts file and decorates the given executor on success
+ *
+ * @param ExecutorInterface $executor
+ * @return ExecutorInterface
+ * @codeCoverageIgnore
+ */
+ private function decorateHostsFileExecutor(ExecutorInterface $executor)
+ {
+ try {
+ $executor = new HostsFileExecutor(
+ HostsFile::loadFromPathBlocking(),
+ $executor
+ );
+ } catch (\RuntimeException $e) {
+ // ignore this file if it can not be loaded
+ }
+
+ // Windows does not store localhost in hosts file by default but handles this internally
+ // To compensate for this, we explicitly use hard-coded defaults for localhost
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $executor = new HostsFileExecutor(
+ new HostsFile("127.0.0.1 localhost\n::1 localhost"),
+ $executor
+ );
+ }
+
+ return $executor;
+ }
+
+ protected function createExecutor(LoopInterface $loop)
+ {
+ return new TimeoutExecutor(
+ new UdpTransportExecutor($loop),
+ 5.0,
+ $loop
+ );
+ }
+
+ protected function createRetryExecutor(LoopInterface $loop)
+ {
+ return new CoopExecutor(new RetryExecutor($this->createExecutor($loop)));
+ }
+
+ protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache)
+ {
+ return new CachingExecutor($this->createRetryExecutor($loop), $cache);
+ }
+
+ protected function addPortToServerIfMissing($nameserver)
+ {
+ if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) {
+ // several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
+ $nameserver = '[' . $nameserver . ']';
+ }
+ // assume a dummy scheme when checking for the port, otherwise parse_url() fails
+ if (parse_url('dummy://' . $nameserver, PHP_URL_PORT) === null) {
+ $nameserver .= ':53';
+ }
+
+ return $nameserver;
+ }
+}
diff --git a/vendor/react/dns/src/Resolver/Resolver.php b/vendor/react/dns/src/Resolver/Resolver.php
new file mode 100755
index 0000000..8690972
--- /dev/null
+++ b/vendor/react/dns/src/Resolver/Resolver.php
@@ -0,0 +1,250 @@
+nameserver = $nameserver;
+ $this->executor = $executor;
+ }
+
+ /**
+ * Resolves the given $domain name to a single IPv4 address (type `A` query).
+ *
+ * ```php
+ * $resolver->resolve('reactphp.org')->then(function ($ip) {
+ * echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
+ * });
+ * ```
+ *
+ * This is one of the main methods in this package. It sends a DNS query
+ * for the given $domain name to your DNS server and returns a single IP
+ * address on success.
+ *
+ * If the DNS server sends a DNS response message that contains more than
+ * one IP address for this query, it will randomly pick one of the IP
+ * addresses from the response. If you want the full list of IP addresses
+ * or want to send a different type of query, you should use the
+ * [`resolveAll()`](#resolveall) method instead.
+ *
+ * If the DNS server sends a DNS response message that indicates an error
+ * code, this method will reject with a `RecordNotFoundException`. Its
+ * message and code can be used to check for the response code.
+ *
+ * If the DNS communication fails and the server does not respond with a
+ * valid response message, this message will reject with an `Exception`.
+ *
+ * Pending DNS queries can be cancelled by cancelling its pending promise like so:
+ *
+ * ```php
+ * $promise = $resolver->resolve('reactphp.org');
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $domain
+ * @return PromiseInterface Returns a promise which resolves with a single IP address on success or
+ * rejects with an Exception on error.
+ */
+ public function resolve($domain)
+ {
+ return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
+ return $ips[array_rand($ips)];
+ });
+ }
+
+ /**
+ * Resolves all record values for the given $domain name and query $type.
+ *
+ * ```php
+ * $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
+ * echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ * });
+ *
+ * $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
+ * echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
+ * });
+ * ```
+ *
+ * This is one of the main methods in this package. It sends a DNS query
+ * for the given $domain name to your DNS server and returns a list with all
+ * record values on success.
+ *
+ * If the DNS server sends a DNS response message that contains one or more
+ * records for this query, it will return a list with all record values
+ * from the response. You can use the `Message::TYPE_*` constants to control
+ * which type of query will be sent. Note that this method always returns a
+ * list of record values, but each record value type depends on the query
+ * type. For example, it returns the IPv4 addresses for type `A` queries,
+ * the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
+ * `CNAME` and `PTR` queries and structured data for other queries. See also
+ * the `Record` documentation for more details.
+ *
+ * If the DNS server sends a DNS response message that indicates an error
+ * code, this method will reject with a `RecordNotFoundException`. Its
+ * message and code can be used to check for the response code.
+ *
+ * If the DNS communication fails and the server does not respond with a
+ * valid response message, this message will reject with an `Exception`.
+ *
+ * Pending DNS queries can be cancelled by cancelling its pending promise like so:
+ *
+ * ```php
+ * $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $domain
+ * @return PromiseInterface Returns a promise which resolves with all record values on success or
+ * rejects with an Exception on error.
+ */
+ public function resolveAll($domain, $type)
+ {
+ $query = new Query($domain, $type, Message::CLASS_IN);
+ $that = $this;
+
+ return $this->executor->query(
+ $this->nameserver,
+ $query
+ )->then(function (Message $response) use ($query, $that) {
+ return $that->extractValues($query, $response);
+ });
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function extractAddress(Query $query, Message $response)
+ {
+ $addresses = $this->extractValues($query, $response);
+
+ return $addresses[array_rand($addresses)];
+ }
+
+ /**
+ * [Internal] extract all resource record values from response for this query
+ *
+ * @param Query $query
+ * @param Message $response
+ * @return array
+ * @throws RecordNotFoundException when response indicates an error or contains no data
+ * @internal
+ */
+ public function extractValues(Query $query, Message $response)
+ {
+ // reject if response code indicates this is an error response message
+ $code = $response->getResponseCode();
+ if ($code !== Message::RCODE_OK) {
+ switch ($code) {
+ case Message::RCODE_FORMAT_ERROR:
+ $message = 'Format Error';
+ break;
+ case Message::RCODE_SERVER_FAILURE:
+ $message = 'Server Failure';
+ break;
+ case Message::RCODE_NAME_ERROR:
+ $message = 'Non-Existent Domain / NXDOMAIN';
+ break;
+ case Message::RCODE_NOT_IMPLEMENTED:
+ $message = 'Not Implemented';
+ break;
+ case Message::RCODE_REFUSED:
+ $message = 'Refused';
+ break;
+ default:
+ $message = 'Unknown error response code ' . $code;
+ }
+ throw new RecordNotFoundException(
+ 'DNS query for ' . $query->name . ' returned an error response (' . $message . ')',
+ $code
+ );
+ }
+
+ $answers = $response->answers;
+ $addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
+
+ // reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
+ if (0 === count($addresses)) {
+ throw new RecordNotFoundException(
+ 'DNS query for ' . $query->name . ' did not return a valid answer (NOERROR / NODATA)'
+ );
+ }
+
+ return array_values($addresses);
+ }
+
+ /**
+ * @deprecated unused, exists for BC only
+ */
+ public function resolveAliases(array $answers, $name)
+ {
+ return $this->valuesByNameAndType($answers, $name, Message::TYPE_A);
+ }
+
+ /**
+ * @param \React\Dns\Model\Record[] $answers
+ * @param string $name
+ * @param int $type
+ * @return array
+ */
+ private function valuesByNameAndType(array $answers, $name, $type)
+ {
+ // return all record values for this name and type (if any)
+ $named = $this->filterByName($answers, $name);
+ $records = $this->filterByType($named, $type);
+ if ($records) {
+ return $this->mapRecordData($records);
+ }
+
+ // no matching records found? check if there are any matching CNAMEs instead
+ $cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
+ if ($cnameRecords) {
+ $cnames = $this->mapRecordData($cnameRecords);
+ foreach ($cnames as $cname) {
+ $records = array_merge(
+ $records,
+ $this->valuesByNameAndType($answers, $cname, $type)
+ );
+ }
+ }
+
+ return $records;
+ }
+
+ private function filterByName(array $answers, $name)
+ {
+ return $this->filterByField($answers, 'name', $name);
+ }
+
+ private function filterByType(array $answers, $type)
+ {
+ return $this->filterByField($answers, 'type', $type);
+ }
+
+ private function filterByField(array $answers, $field, $value)
+ {
+ $value = strtolower($value);
+ return array_filter($answers, function ($answer) use ($field, $value) {
+ return $value === strtolower($answer->$field);
+ });
+ }
+
+ private function mapRecordData(array $records)
+ {
+ return array_map(function ($record) {
+ return $record->data;
+ }, $records);
+ }
+}
diff --git a/vendor/react/dns/tests/CallableStub.php b/vendor/react/dns/tests/CallableStub.php
new file mode 100755
index 0000000..a34a263
--- /dev/null
+++ b/vendor/react/dns/tests/CallableStub.php
@@ -0,0 +1,10 @@
+assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsDefaultPath()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Not supported on Windows');
+ }
+
+ $config = Config::loadResolvConfBlocking();
+
+ $this->assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsFromExplicitPath()
+ {
+ $config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf');
+
+ $this->assertEquals(array('8.8.8.8'), $config->nameservers);
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testLoadThrowsWhenPathIsInvalid()
+ {
+ Config::loadResolvConfBlocking(__DIR__ . '/invalid.conf');
+ }
+
+ public function testParsesSingleEntryFile()
+ {
+ $contents = 'nameserver 8.8.8.8';
+ $expected = array('8.8.8.8');
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesNameserverEntriesFromAverageFileCorrectly()
+ {
+ $contents = '#
+# Mac OS X Notice
+#
+# This file is not used by the host name and address resolution
+# or the DNS query routing mechanisms used by most processes on
+# this Mac OS X system.
+#
+# This file is automatically generated.
+#
+domain v.cablecom.net
+nameserver 127.0.0.1
+nameserver ::1
+';
+ $expected = array('127.0.0.1', '::1');
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesEmptyFileWithoutNameserverEntries()
+ {
+ $contents = '';
+ $expected = array();
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,');
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries()
+ {
+ $contents = '
+# nameserver 1.2.3.4
+; nameserver 2.3.4.5
+
+nameserver 3.4.5.6 # nope
+nameserver 4.5.6.7 5.6.7.8
+ nameserver 6.7.8.9
+NameServer 7.8.9.10
+';
+ $expected = array();
+
+ $config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsFromWmicOnWindows()
+ {
+ if (DIRECTORY_SEPARATOR !== '\\') {
+ $this->markTestSkipped('Only on Windows');
+ }
+
+ $config = Config::loadWmicBlocking();
+
+ $this->assertInstanceOf('React\Dns\Config\Config', $config);
+ }
+
+ public function testLoadsSingleEntryFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1}
+ACE,
+';
+ $expected = array('192.168.2.1');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsEmptyListFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+';
+ $expected = array();
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsSingleEntryForMultipleNicsFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1}
+ACE,
+ACE,{192.168.2.2}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{192.168.2.1;192.168.2.2}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput()
+ {
+ $contents = '
+Node,DNSServerSearchOrder
+ACE,
+ACE,{"192.168.2.1","192.168.2.2"}
+ACE,
+';
+ $expected = array('192.168.2.1', '192.168.2.2');
+
+ $config = Config::loadWmicBlocking($this->echoCommand($contents));
+
+ $this->assertEquals($expected, $config->nameservers);
+ }
+
+ private function echoCommand($output)
+ {
+ return 'echo ' . escapeshellarg($output);
+ }
+}
diff --git a/vendor/react/dns/tests/Config/FilesystemFactoryTest.php b/vendor/react/dns/tests/Config/FilesystemFactoryTest.php
new file mode 100755
index 0000000..bb9eac7
--- /dev/null
+++ b/vendor/react/dns/tests/Config/FilesystemFactoryTest.php
@@ -0,0 +1,70 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $factory = new FilesystemFactory($loop);
+ $factory->parseEtcResolvConf($contents)->then(function ($config) use (&$capturedConfig) {
+ $capturedConfig = $config;
+ });
+
+ $this->assertNotNull($capturedConfig);
+ $this->assertSame($expected, $capturedConfig->nameservers);
+ }
+
+ /** @test */
+ public function createShouldLoadStuffFromFilesystem()
+ {
+ $this->markTestIncomplete('Filesystem API is incomplete');
+
+ $expected = array('8.8.8.8');
+
+ $triggerListener = null;
+ $capturedConfig = null;
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop
+ ->expects($this->once())
+ ->method('addReadStream')
+ ->will($this->returnCallback(function ($stream, $listener) use (&$triggerListener) {
+ $triggerListener = function () use ($stream, $listener) {
+ call_user_func($listener, $stream);
+ };
+ }));
+
+ $factory = new FilesystemFactory($loop);
+ $factory->create(__DIR__.'/../Fixtures/etc/resolv.conf')->then(function ($config) use (&$capturedConfig) {
+ $capturedConfig = $config;
+ });
+
+ $triggerListener();
+
+ $this->assertNotNull($capturedConfig);
+ $this->assertSame($expected, $capturedConfig->nameservers);
+ }
+}
diff --git a/vendor/react/dns/tests/Config/HostsFileTest.php b/vendor/react/dns/tests/Config/HostsFileTest.php
new file mode 100755
index 0000000..ff74ad2
--- /dev/null
+++ b/vendor/react/dns/tests/Config/HostsFileTest.php
@@ -0,0 +1,170 @@
+assertInstanceOf('React\Dns\Config\HostsFile', $hosts);
+ }
+
+ public function testDefaultShouldHaveLocalhostMapped()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Not supported on Windows');
+ }
+
+ $hosts = HostsFile::loadFromPathBlocking();
+
+ $this->assertContains('127.0.0.1', $hosts->getIpsForHost('localhost'));
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testLoadThrowsForInvalidPath()
+ {
+ HostsFile::loadFromPathBlocking('does/not/exist');
+ }
+
+ public function testContainsSingleLocalhostEntry()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testNonIpReturnsNothingForInvalidHosts()
+ {
+ $hosts = new HostsFile('a b');
+
+ $this->assertEquals(array(), $hosts->getIpsForHost('a'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('b'));
+ }
+
+ public function testIgnoresIpv6ZoneId()
+ {
+ $hosts = new HostsFile('fe80::1%lo0 localhost');
+
+ $this->assertEquals(array('fe80::1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testSkipsComments()
+ {
+ $hosts = new HostsFile('# start' . PHP_EOL .'#127.0.0.1 localhost' . PHP_EOL . '127.0.0.2 localhost # example.com');
+
+ $this->assertEquals(array('127.0.0.2'), $hosts->getIpsForHost('localhost'));
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testContainsSingleLocalhostEntryWithCaseIgnored()
+ {
+ $hosts = new HostsFile('127.0.0.1 LocalHost');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('LOCALHOST'));
+ }
+
+ public function testEmptyFileContainsNothing()
+ {
+ $hosts = new HostsFile('');
+
+ $this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
+ }
+
+ public function testSingleEntryWithMultipleNames()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost example.com');
+
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('example.com'));
+ $this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testMergesEntriesOverMultipleLines()
+ {
+ $hosts = new HostsFile("127.0.0.1 localhost\n127.0.0.2 localhost\n127.0.0.3 a localhost b\n127.0.0.4 a localhost");
+
+ $this->assertEquals(array('127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testMergesIpv4AndIpv6EntriesOverMultipleLines()
+ {
+ $hosts = new HostsFile("127.0.0.1 localhost\n::1 localhost");
+
+ $this->assertEquals(array('127.0.0.1', '::1'), $hosts->getIpsForHost('localhost'));
+ }
+
+ public function testReverseLookup()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('192.168.1.1'));
+ }
+
+ public function testReverseSkipsComments()
+ {
+ $hosts = new HostsFile("# start\n#127.0.0.1 localhosted\n127.0.0.2\tlocalhost\t# example.com\n\t127.0.0.3\t\texample.org\t\t");
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1'));
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.2'));
+ $this->assertEquals(array('example.org'), $hosts->getHostsForIp('127.0.0.3'));
+ }
+
+ public function testReverseNonIpReturnsNothing()
+ {
+ $hosts = new HostsFile('127.0.0.1 localhost');
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('localhost'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1.1'));
+ }
+
+ public function testReverseNonIpReturnsNothingForInvalidHosts()
+ {
+ $hosts = new HostsFile('a b');
+
+ $this->assertEquals(array(), $hosts->getHostsForIp('a'));
+ $this->assertEquals(array(), $hosts->getHostsForIp('b'));
+ }
+
+ public function testReverseLookupReturnsLowerCaseHost()
+ {
+ $hosts = new HostsFile('127.0.0.1 LocalHost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
+ }
+
+ public function testReverseLookupChecksNormalizedIpv6()
+ {
+ $hosts = new HostsFile('FE80::00a1 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::A1'));
+ }
+
+ public function testReverseLookupIgnoresIpv6ZoneId()
+ {
+ $hosts = new HostsFile('fe80::1%lo0 localhost');
+
+ $this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::1'));
+ }
+
+ public function testReverseLookupReturnsMultipleHostsOverSingleLine()
+ {
+ $hosts = new HostsFile("::1 ip6-localhost ip6-loopback");
+
+ $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
+ }
+
+ public function testReverseLookupReturnsMultipleHostsOverMultipleLines()
+ {
+ $hosts = new HostsFile("::1 ip6-localhost\n::1 ip6-loopback");
+
+ $this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
+ }
+}
diff --git a/vendor/react/dns/tests/Fixtures/etc/resolv.conf b/vendor/react/dns/tests/Fixtures/etc/resolv.conf
new file mode 100755
index 0000000..cae093a
--- /dev/null
+++ b/vendor/react/dns/tests/Fixtures/etc/resolv.conf
@@ -0,0 +1 @@
+nameserver 8.8.8.8
diff --git a/vendor/react/dns/tests/FunctionalResolverTest.php b/vendor/react/dns/tests/FunctionalResolverTest.php
new file mode 100755
index 0000000..a52a3be
--- /dev/null
+++ b/vendor/react/dns/tests/FunctionalResolverTest.php
@@ -0,0 +1,171 @@
+loop = LoopFactory::create();
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('8.8.8.8', $this->loop);
+ }
+
+ public function testResolveLocalhostResolves()
+ {
+ $promise = $this->resolver->resolve('localhost');
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ public function testResolveAllLocalhostResolvesWithArray()
+ {
+ $promise = $this->resolver->resolveAll('localhost', Message::TYPE_A);
+ $promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveGoogleResolves()
+ {
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveAllGoogleMxResolvesWithCache()
+ {
+ $factory = new Factory();
+ $this->resolver = $factory->createCached('8.8.8.8', $this->loop);
+
+ $promise = $this->resolver->resolveAll('google.com', Message::TYPE_MX);
+ $promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
+
+ $this->loop->run();
+ }
+
+ /**
+ * @group internet
+ */
+ public function testResolveInvalidRejects()
+ {
+ $ex = $this->callback(function ($param) {
+ return ($param instanceof RecordNotFoundException && $param->getCode() === Message::RCODE_NAME_ERROR);
+ });
+
+ $promise = $this->resolver->resolve('example.invalid');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
+
+ $this->loop->run();
+ }
+
+ public function testResolveCancelledRejectsImmediately()
+ {
+ $ex = $this->callback(function ($param) {
+ return ($param instanceof \RuntimeException && $param->getMessage() === 'DNS query for google.com has been cancelled');
+ });
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
+ $promise->cancel();
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.1, $time);
+ }
+
+ public function testInvalidResolverDoesNotResolveGoogle()
+ {
+ $factory = new Factory();
+ $this->resolver = $factory->create('255.255.255.255', $this->loop);
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('255.255.255.255', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->createCached('255.255.255.255', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancelResolveShouldNotCauseGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->create('127.0.0.1', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancelResolveCachedShouldNotCauseGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $factory = new Factory();
+ $this->resolver = $factory->createCached('127.0.0.1', $this->loop);
+
+ gc_collect_cycles();
+
+ $promise = $this->resolver->resolve('google.com');
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/vendor/react/dns/tests/Model/MessageTest.php b/vendor/react/dns/tests/Model/MessageTest.php
new file mode 100755
index 0000000..cf3d890
--- /dev/null
+++ b/vendor/react/dns/tests/Model/MessageTest.php
@@ -0,0 +1,31 @@
+assertTrue($request->header->isQuery());
+ $this->assertSame(1, $request->header->get('rd'));
+ }
+
+ public function testCreateResponseWithNoAnswers()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $answers = array();
+ $request = Message::createResponseWithAnswersForQuery($query, $answers);
+
+ $this->assertFalse($request->header->isQuery());
+ $this->assertTrue($request->header->isResponse());
+ $this->assertEquals(0, $request->header->get('anCount'));
+ $this->assertEquals(Message::RCODE_OK, $request->getResponseCode());
+ }
+}
diff --git a/vendor/react/dns/tests/Protocol/BinaryDumperTest.php b/vendor/react/dns/tests/Protocol/BinaryDumperTest.php
new file mode 100755
index 0000000..ee94030
--- /dev/null
+++ b/vendor/react/dns/tests/Protocol/BinaryDumperTest.php
@@ -0,0 +1,278 @@
+formatHexDump($data);
+
+ $request = new Message();
+ $request->header->set('id', 0x7262);
+ $request->header->set('rd', 1);
+
+ $request->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN,
+ );
+
+ $request->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($request);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryRequestMessageWithCustomOptForEdns0()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "00"; // additional: (empty hostname)
+ $data .= "00 29 03 e8 00 00 00 00 00 00 "; // additional: type OPT, class UDP size, TTL 0, no RDATA
+
+ $expected = $this->formatHexDump($data);
+
+ $request = new Message();
+ $request->header->set('id', 0x7262);
+ $request->header->set('rd', 1);
+
+ $request->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN,
+ );
+
+ $request->additional[] = new Record('', 41, 1000, 0, '');
+
+ $request->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($request);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryResponseMessageWithoutRecords()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_A,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithSRVRecord()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 21 00 01"; // question: type SRV, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0c"; // answer: rdlength 12
+ $data .= "00 0a 00 14 1f 90 04 74 65 73 74 00"; // answer: rdata priority 10, weight 20, port 8080 test
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_SRV,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_SRV, Message::CLASS_IN, 86400, array(
+ 'priority' => 10,
+ 'weight' => 20,
+ 'port' => 8080,
+ 'target' => 'test'
+ ));
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithSOARecord()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 06 00 01"; // question: type SOA, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 06 00 01"; // answer: type SOA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 27"; // answer: rdlength 39
+ $data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
+ $data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
+ $data .= "78 49 28 d5 00 00 2a 30 00 00 0e 10"; // answer: rdata 2018060501, 10800, 3600
+ $data .= "00 09 3e 68 00 00 0e 10"; // answer: 605800, 3600
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_SOA,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_SOA, Message::CLASS_IN, 86400, array(
+ 'mname' => 'ns.hello',
+ 'rname' => 'e.hello',
+ 'serial' => 2018060501,
+ 'refresh' => 10800,
+ 'retry' => 3600,
+ 'expire' => 605800,
+ 'minimum' => 3600
+ ));
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithMultipleAnswerRecords()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 04 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 ff 00 01"; // question: type ANY, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01 00 00 00 00 00 04"; // answer: type A, class IN, TTL 0, 4 bytes
+ $data .= "7f 00 00 01"; // answer: 127.0.0.1
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 1c 00 01 00 00 00 00 00 10"; // question: type AAAA, class IN, TTL 0, 16 bytes
+ $data .= "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01"; // answer: ::1
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01 00 00 00 00 00 0c"; // answer: type TXT, class IN, TTL 0, 12 bytes
+ $data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: hello, world
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01 00 00 00 00 00 03"; // anwser: type MX, class IN, TTL 0, 3 bytes
+ $data .= "00 00 00"; // priority 0, no target
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_ANY,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
+ $response->answers[] = new Record('igor.io', Message::TYPE_AAAA, Message::CLASS_IN, 0, '::1');
+ $response->answers[] = new Record('igor.io', Message::TYPE_TXT, Message::CLASS_IN, 0, array('hello', 'world'));
+ $response->answers[] = new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 0, array('priority' => 0, 'target' => ''));
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ public function testToBinaryForResponseWithAnswerAndAdditionalRecord()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 01 00 00 00 01"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 02 00 01"; // question: type NS, class IN
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 02 00 01 00 00 00 00 00 0d"; // answer: type NS, class IN, TTL 0, 10 bytes
+ $data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // answer: example.com
+ $data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // additional: example.com
+ $data .= "00 01 00 01 00 00 00 00 00 04"; // additional: type A, class IN, TTL 0, 4 bytes
+ $data .= "7f 00 00 01"; // additional: 127.0.0.1
+
+ $expected = $this->formatHexDump($data);
+
+ $response = new Message();
+ $response->header->set('id', 0x7262);
+ $response->header->set('rd', 1);
+ $response->header->set('rcode', Message::RCODE_OK);
+
+ $response->questions[] = array(
+ 'name' => 'igor.io',
+ 'type' => Message::TYPE_NS,
+ 'class' => Message::CLASS_IN
+ );
+
+ $response->answers[] = new Record('igor.io', Message::TYPE_NS, Message::CLASS_IN, 0, 'example.com');
+ $response->additional[] = new Record('example.com', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
+ $response->prepare();
+
+ $dumper = new BinaryDumper();
+ $data = $dumper->toBinary($response);
+ $data = $this->convertBinaryToHexDump($data);
+
+ $this->assertSame($expected, $data);
+ }
+
+ private function convertBinaryToHexDump($input)
+ {
+ return $this->formatHexDump(implode('', unpack('H*', $input)));
+ }
+
+ private function formatHexDump($input)
+ {
+ return implode(' ', str_split(str_replace(' ', '', $input), 2));
+ }
+}
diff --git a/vendor/react/dns/tests/Protocol/ParserTest.php b/vendor/react/dns/tests/Protocol/ParserTest.php
new file mode 100755
index 0000000..4086626
--- /dev/null
+++ b/vendor/react/dns/tests/Protocol/ParserTest.php
@@ -0,0 +1,1033 @@
+parser = new Parser();
+ }
+
+ /**
+ * @dataProvider provideConvertTcpDumpToBinary
+ */
+ public function testConvertTcpDumpToBinary($expected, $data)
+ {
+ $this->assertSame($expected, $this->convertTcpDumpToBinary($data));
+ }
+
+ public function provideConvertTcpDumpToBinary()
+ {
+ return array(
+ array(chr(0x72).chr(0x62), "72 62"),
+ array(chr(0x72).chr(0x62).chr(0x01).chr(0x00), "72 62 01 00"),
+ array(chr(0x72).chr(0x62).chr(0x01).chr(0x00).chr(0x00).chr(0x01), "72 62 01 00 00 01"),
+ array(chr(0x01).chr(0x00).chr(0x01), "01 00 01"),
+ );
+ }
+
+ public function testParseRequest()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $request = $this->parser->parseMessage($data);
+
+ $header = $request->header;
+ $this->assertSame(0x7262, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(0, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(0, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(0, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $request->questions);
+ $this->assertSame('igor.io', $request->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']);
+ }
+
+ public function testParseResponse()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0x7262, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('igor.io', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseQuestionWithTwoQuestions()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "03 77 77 77 04 69 67 6f 72 02 69 6f 00"; // question: www.igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $request = new Message();
+ $request->header->set('qdCount', 2);
+ $request->data = $data;
+
+ $this->parser->parseQuestion($request);
+
+ $this->assertCount(2, $request->questions);
+ $this->assertSame('igor.io', $request->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[0]['class']);
+ $this->assertSame('www.igor.io', $request->questions[1]['name']);
+ $this->assertSame(Message::TYPE_A, $request->questions[1]['type']);
+ $this->assertSame(Message::CLASS_IN, $request->questions[1]['class']);
+ }
+
+ public function testParseAnswerWithInlineData()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithExcessiveTtlReturnsZeroTtl()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "ff ff ff ff"; // answer: ttl 2^32 - 1
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(0, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithTtlExactlyBoundaryReturnsZeroTtl()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "80 00 00 00"; // answer: ttl 2^31
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(0, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithMaximumTtlReturnsExactTtl()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "7f ff ff ff"; // answer: ttl 2^31 - 1
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "b2 4f a9 83"; // answer: rdata 178.79.169.131
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(0x7fffffff, $response->answers[0]->ttl);
+ $this->assertSame('178.79.169.131', $response->answers[0]->data);
+ }
+
+ public function testParseAnswerWithUnknownType()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "23 28 00 01"; // answer: type 9000, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 05"; // answer: rdlength 5
+ $data .= "68 65 6c 6c 6f"; // answer: rdata "hello"
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(9000, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('hello', $response->answers[0]->data);
+ }
+
+ public function testParseResponseWithCnameAndOffsetPointers()
+ {
+ $data = "";
+ $data .= "9e 8d 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 6d 61 69 6c 06 67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: mail.google.com
+ $data .= "00 05 00 01"; // question: type CNAME, class IN
+ $data .= "c0 0c"; // answer: offset pointer to mail.google.com
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 a8 9c"; // answer: ttl 43164
+ $data .= "00 0f"; // answer: rdlength 15
+ $data .= "0a 67 6f 6f 67 6c 65 6d 61 69 6c 01 6c"; // answer: rdata googlemail.l.
+ $data .= "c0 11"; // answer: rdata offset pointer to google.com
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('mail.google.com', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_CNAME, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('mail.google.com', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(43164, $response->answers[0]->ttl);
+ $this->assertSame('googlemail.l.google.com', $response->answers[0]->data);
+ }
+
+ public function testParseAAAAResponse()
+ {
+ $data = "";
+ $data .= "cd 72 81 80 00 01 00 01 00 00 00 00 06"; // header
+ $data .= "67 6f 6f 67 6c 65 03 63 6f 6d 00"; // question: google.com
+ $data .= "00 1c 00 01"; // question: type AAAA, class IN
+ $data .= "c0 0c"; // answer: offset pointer to google.com
+ $data .= "00 1c 00 01"; // answer: type AAAA, class IN
+ $data .= "00 00 01 2b"; // answer: ttl 299
+ $data .= "00 10"; // answer: rdlength 16
+ $data .= "2a 00 14 50 40 09 08 09 00 00 00 00 00 00 20 0e"; // answer: 2a00:1450:4009:809::200e
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0xcd72, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('google.com', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_AAAA, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('google.com', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_AAAA, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(299, $response->answers[0]->ttl);
+ $this->assertSame('2a00:1450:4009:809::200e', $response->answers[0]->data);
+ }
+
+ public function testParseTXTResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01"; // answer: type TXT, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 06"; // answer: rdlength 6
+ $data .= "05 68 65 6c 6c 6f"; // answer: rdata length 5: hello
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_TXT, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(array('hello'), $response->answers[0]->data);
+ }
+
+ public function testParseTXTResponseMultiple()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01"; // answer: type TXT, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0C"; // answer: rdlength 12
+ $data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: rdata length 5: hello, length 5: world
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_TXT, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(array('hello', 'world'), $response->answers[0]->data);
+ }
+
+ public function testParseMXResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01"; // answer: type MX, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 09"; // answer: rdlength 9
+ $data .= "00 0a 05 68 65 6c 6c 6f 00"; // answer: rdata priority 10: hello
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_MX, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(array('priority' => 10, 'target' => 'hello'), $response->answers[0]->data);
+ }
+
+ public function testParseSRVResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0C"; // answer: rdlength 12
+ $data .= "00 0a 00 14 1F 90 04 74 65 73 74 00"; // answer: rdata priority 10, weight 20, port 8080 test
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_SRV, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(
+ array(
+ 'priority' => 10,
+ 'weight' => 20,
+ 'port' => 8080,
+ 'target' => 'test'
+ ),
+ $response->answers[0]->data
+ );
+ }
+
+ public function testParseMessageResponseWithTwoAnswers()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 02 00 00 00 00"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 00 29"; // answer: ttl 41
+ $data .= "00 0e"; // answer: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // answer: rdata whois.nic.io
+ $data .= "c0 32"; // answer: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 0d f7"; // answer: ttl 3575
+ $data .= "00 04"; // answer: rdlength 4
+ $data .= "c1 df 4e 98"; // answer: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(2, $response->answers);
+
+ $this->assertSame('io.whois-servers.net', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(41, $response->answers[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->answers[0]->data);
+
+ $this->assertSame('whois.nic.io', $response->answers[1]->name);
+ $this->assertSame(Message::TYPE_A, $response->answers[1]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[1]->class);
+ $this->assertSame(3575, $response->answers[1]->ttl);
+ $this->assertSame('193.223.78.152', $response->answers[1]->data);
+ }
+
+ public function testParseMessageResponseWithTwoAuthorityRecords()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 00 00 02 00 00"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // authority: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // authority: type CNAME, class IN
+ $data .= "00 00 00 29"; // authority: ttl 41
+ $data .= "00 0e"; // authority: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // authority: rdata whois.nic.io
+ $data .= "c0 32"; // authority: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // authority: type CNAME, class IN
+ $data .= "00 00 0d f7"; // authority: ttl 3575
+ $data .= "00 04"; // authority: rdlength 4
+ $data .= "c1 df 4e 98"; // authority: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(0, $response->answers);
+
+ $this->assertCount(2, $response->authority);
+
+ $this->assertSame('io.whois-servers.net', $response->authority[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->authority[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->authority[0]->class);
+ $this->assertSame(41, $response->authority[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->authority[0]->data);
+
+ $this->assertSame('whois.nic.io', $response->authority[1]->name);
+ $this->assertSame(Message::TYPE_A, $response->authority[1]->type);
+ $this->assertSame(Message::CLASS_IN, $response->authority[1]->class);
+ $this->assertSame(3575, $response->authority[1]->ttl);
+ $this->assertSame('193.223.78.152', $response->authority[1]->data);
+ }
+
+ public function testParseMessageResponseWithAnswerAndAdditionalRecord()
+ {
+ $data = "";
+ $data .= "bc 73 81 80 00 01 00 01 00 00 00 01"; // header
+ $data .= "02 69 6f 0d 77 68 6f 69 73 2d 73 65 72 76 65 72 73 03 6e 65 74 00";
+ // question: io.whois-servers.net
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to io.whois-servers.net
+ $data .= "00 05 00 01"; // answer: type CNAME, class IN
+ $data .= "00 00 00 29"; // answer: ttl 41
+ $data .= "00 0e"; // answer: rdlength 14
+ $data .= "05 77 68 6f 69 73 03 6e 69 63 02 69 6f 00"; // answer: rdata whois.nic.io
+ $data .= "c0 32"; // additional: offset pointer to whois.nic.io
+ $data .= "00 01 00 01"; // additional: type CNAME, class IN
+ $data .= "00 00 0d f7"; // additional: ttl 3575
+ $data .= "00 04"; // additional: rdlength 4
+ $data .= "c1 df 4e 98"; // additional: rdata 193.223.78.152
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_A, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+
+ $this->assertSame('io.whois-servers.net', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_CNAME, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(41, $response->answers[0]->ttl);
+ $this->assertSame('whois.nic.io', $response->answers[0]->data);
+
+ $this->assertCount(0, $response->authority);
+ $this->assertCount(1, $response->additional);
+
+ $this->assertSame('whois.nic.io', $response->additional[0]->name);
+ $this->assertSame(Message::TYPE_A, $response->additional[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->additional[0]->class);
+ $this->assertSame(3575, $response->additional[0]->ttl);
+ $this->assertSame('193.223.78.152', $response->additional[0]->data);
+ }
+
+ public function testParseNSResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 02 00 01"; // answer: type NS, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 07"; // answer: rdlength 7
+ $data .= "05 68 65 6c 6c 6f 00"; // answer: rdata hello
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_NS, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame('hello', $response->answers[0]->data);
+ }
+
+ public function testParseSOAResponse()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 06 00 01"; // answer: type SOA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 27"; // answer: rdlength 39
+ $data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
+ $data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
+ $data .= "78 49 28 D5 00 00 2a 30 00 00 0e 10"; // answer: rdata 2018060501, 10800, 3600
+ $data .= "00 09 3a 80 00 00 0e 10"; // answer: 605800, 3600
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('igor.io', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_SOA, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86400, $response->answers[0]->ttl);
+ $this->assertSame(
+ array(
+ 'mname' => 'ns.hello',
+ 'rname' => 'e.hello',
+ 'serial' => 2018060501,
+ 'refresh' => 10800,
+ 'retry' => 3600,
+ 'expire' => 604800,
+ 'minimum' => 3600
+ ),
+ $response->answers[0]->data
+ );
+ }
+
+ public function testParsePTRResponse()
+ {
+ $data = "";
+ $data .= "5d d8 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "01 34 01 34 01 38 01 38 07 69 6e"; // question: 4.4.8.8.in-addr.arpa
+ $data .= "2d 61 64 64 72 04 61 72 70 61 00"; // question (continued)
+ $data .= "00 0c 00 01"; // question: type PTR, class IN
+ $data .= "c0 0c"; // answer: offset pointer to rdata
+ $data .= "00 0c 00 01"; // answer: type PTR, class IN
+ $data .= "00 01 51 7f"; // answer: ttl 86399
+ $data .= "00 20"; // answer: rdlength 32
+ $data .= "13 67 6f 6f 67 6c 65 2d 70 75 62 6c 69 63 2d 64"; // answer: rdata google-public-dns-b.google.com.
+ $data .= "6e 73 2d 62 06 67 6f 6f 67 6c 65 03 63 6f 6d 00";
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = $this->parser->parseMessage($data);
+
+ $header = $response->header;
+ $this->assertSame(0x5dd8, $header->get('id'));
+ $this->assertSame(1, $header->get('qdCount'));
+ $this->assertSame(1, $header->get('anCount'));
+ $this->assertSame(0, $header->get('nsCount'));
+ $this->assertSame(0, $header->get('arCount'));
+ $this->assertSame(1, $header->get('qr'));
+ $this->assertSame(Message::OPCODE_QUERY, $header->get('opcode'));
+ $this->assertSame(0, $header->get('aa'));
+ $this->assertSame(0, $header->get('tc'));
+ $this->assertSame(1, $header->get('rd'));
+ $this->assertSame(1, $header->get('ra'));
+ $this->assertSame(0, $header->get('z'));
+ $this->assertSame(Message::RCODE_OK, $header->get('rcode'));
+
+ $this->assertCount(1, $response->questions);
+ $this->assertSame('4.4.8.8.in-addr.arpa', $response->questions[0]['name']);
+ $this->assertSame(Message::TYPE_PTR, $response->questions[0]['type']);
+ $this->assertSame(Message::CLASS_IN, $response->questions[0]['class']);
+
+ $this->assertCount(1, $response->answers);
+ $this->assertSame('4.4.8.8.in-addr.arpa', $response->answers[0]->name);
+ $this->assertSame(Message::TYPE_PTR, $response->answers[0]->type);
+ $this->assertSame(Message::CLASS_IN, $response->answers[0]->class);
+ $this->assertSame(86399, $response->answers[0]->ttl);
+ $this->assertSame('google-public-dns-b.google.com', $response->answers[0]->data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteQuestionThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ //$data .= "00 01 00 01"; // question: type A, class IN
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteQuestionLabelThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67"; // question: ig …?
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72"; // question: igor. …?
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteOffsetPointerInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "ff"; // question: incomplete offset pointer
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseInvalidOffsetPointerInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "ff ff"; // question: offset pointer to invalid address
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseInvalidOffsetPointerToSameLabelInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "c0 0c"; // question: offset pointer to invalid address
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseInvalidOffsetPointerToStartOfMessageInQuestionNameThrows()
+ {
+ $data = "";
+ $data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
+ $data .= "c0 00"; // question: offset pointer to start of message
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteAnswerFieldsThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseMessageResponseWithIncompleteAuthorityRecordThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 00 00 01 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // authority: offset pointer to igor.io
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseMessageResponseWithIncompleteAdditionalRecordThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 00 00 00 00 01"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // additional: offset pointer to igor.io
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testParseIncompleteAnswerRecordDataThrows()
+ {
+ $data = "";
+ $data .= "72 62 81 80 00 01 00 01 00 00 00 00"; // header
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
+ $data .= "00 01 00 01"; // question: type A, class IN
+ $data .= "c0 0c"; // answer: offset pointer to igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 04"; // answer: rdlength 4
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $this->parser->parseMessage($data);
+ }
+
+ public function testParseInvalidNSResponseWhereDomainNameIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 02 00 01"; // answer: type NS, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 00"; // answer: rdlength 0
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidAResponseWhereIPIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 01 00 01"; // answer: type A, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 00"; // answer: rdlength 0
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidAAAAResponseWhereIPIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 1c 00 01"; // answer: type AAAA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 00"; // answer: rdlength 0
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidTXTResponseWhereTxtChunkExceedsLimit()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 10 00 01"; // answer: type TXT, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 06"; // answer: rdlength 6
+ $data .= "06 68 65 6c 6c 6f 6f"; // answer: rdata length 6: helloo
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidMXResponseWhereDomainNameIsIncomplete()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01"; // answer: type MX, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 08"; // answer: rdlength 8
+ $data .= "00 0a 05 68 65 6c 6c 6f"; // answer: rdata priority 10: hello (missing label end)
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidMXResponseWhereDomainNameIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 0f 00 01"; // answer: type MX, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 02"; // answer: rdlength 2
+ $data .= "00 0a"; // answer: rdata priority 10
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidSRVResponseWhereDomainNameIsIncomplete()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 0b"; // answer: rdlength 11
+ $data .= "00 0a 00 14 1F 90 04 74 65 73 74"; // answer: rdata priority 10, weight 20, port 8080 test (missing label end)
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidSRVResponseWhereDomainNameIsMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 21 00 01"; // answer: type SRV, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 06"; // answer: rdlength 6
+ $data .= "00 0a 00 14 1F 90"; // answer: rdata priority 10, weight 20, port 8080
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ public function testParseInvalidSOAResponseWhereFlagsAreMissing()
+ {
+ $data = "";
+ $data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
+ $data .= "00 06 00 01"; // answer: type SOA, class IN
+ $data .= "00 01 51 80"; // answer: ttl 86400
+ $data .= "00 13"; // answer: rdlength 19
+ $data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
+ $data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
+
+ $data = $this->convertTcpDumpToBinary($data);
+
+ $response = new Message();
+ $response->header->set('anCount', 1);
+ $response->data = $data;
+
+ $this->parser->parseAnswer($response);
+
+ $this->assertCount(0, $response->answers);
+ }
+
+ private function convertTcpDumpToBinary($input)
+ {
+ // sudo ngrep -d en1 -x port 53
+
+ return pack('H*', str_replace(' ', '', $input));
+ }
+}
diff --git a/vendor/react/dns/tests/Query/CachedExecutorTest.php b/vendor/react/dns/tests/Query/CachedExecutorTest.php
new file mode 100755
index 0000000..d08ed05
--- /dev/null
+++ b/vendor/react/dns/tests/Query/CachedExecutorTest.php
@@ -0,0 +1,100 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue($this->createPromiseMock()));
+
+ $cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cache
+ ->expects($this->once())
+ ->method('lookup')
+ ->will($this->returnValue(Promise\reject()));
+ $cachedExecutor = new CachedExecutor($executor, $cache);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $cachedExecutor->query('8.8.8.8', $query);
+ }
+
+ /**
+ * @covers React\Dns\Query\CachedExecutor
+ * @test
+ */
+ public function callingQueryTwiceShouldUseCachedResult()
+ {
+ $cachedRecords = array(new Record('igor.io', Message::TYPE_A, Message::CLASS_IN));
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->callQueryCallbackWithAddress('178.79.169.131'));
+
+ $cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $cache
+ ->expects($this->at(0))
+ ->method('lookup')
+ ->with($this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue(Promise\reject()));
+ $cache
+ ->expects($this->at(1))
+ ->method('storeResponseMessage')
+ ->with($this->isType('integer'), $this->isInstanceOf('React\Dns\Model\Message'));
+ $cache
+ ->expects($this->at(2))
+ ->method('lookup')
+ ->with($this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue(Promise\resolve($cachedRecords)));
+
+ $cachedExecutor = new CachedExecutor($executor, $cache);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
+ $cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
+ }
+
+ private function callQueryCallbackWithAddress($address)
+ {
+ return $this->returnCallback(function ($nameserver, $query) use ($address) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, $address);
+
+ return Promise\resolve($response);
+ });
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+
+ private function createPromiseMock()
+ {
+ return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+ }
+}
diff --git a/vendor/react/dns/tests/Query/CachingExecutorTest.php b/vendor/react/dns/tests/Query/CachingExecutorTest.php
new file mode 100755
index 0000000..abd9342
--- /dev/null
+++ b/vendor/react/dns/tests/Query/CachingExecutorTest.php
@@ -0,0 +1,183 @@
+getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->never())->method('query');
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(new Promise(function () { }));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnPendingPromiseWhenCacheReturnsMissAndWillSendSameQueryToFallbackExecutor()
+ {
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn(new Promise(function () { }));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSendingQueryToFallbackExecutor()
+ {
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->never())->method('query');
+
+ $message = new Message();
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve($message));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithMinimumTtlFromRecord()
+ {
+ $message = new Message();
+ $message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3700, '127.0.0.1');
+ $message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '127.0.0.1');
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
+ $cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 3600);
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithDefaultTtl()
+ {
+ $message = new Message();
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
+ $cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 60);
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesWithTruncatedResponseButShouldNotSaveTruncatedMessageToCache()
+ {
+ $message = new Message();
+ $message->header->set('tc', 1);
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
+ $cache->expects($this->never())->method('set');
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
+ }
+
+ public function testQueryWillReturnRejectedPromiseWhenCacheReturnsMissAndFallbackExecutorRejects()
+ {
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn(\React\Promise\reject(new \RuntimeException()));
+
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $promise = $executor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
+ }
+
+ public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromCache()
+ {
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->never())->method('query');
+
+ $pending = new Promise(function () { }, $this->expectCallableOnce());
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($pending);
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
+ }
+
+ public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromFallbackExecutorWhenCacheReturnsMiss()
+ {
+ $pending = new Promise(function () { }, $this->expectCallableOnce());
+ $fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $fallback->expects($this->once())->method('query')->willReturn($pending);
+
+ $deferred = new Deferred();
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($deferred->promise());
+
+ $executor = new CachingExecutor($fallback, $cache);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query('8.8.8.8', $query);
+ $deferred->resolve(null);
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
+ }
+}
diff --git a/vendor/react/dns/tests/Query/CoopExecutorTest.php b/vendor/react/dns/tests/Query/CoopExecutorTest.php
new file mode 100755
index 0000000..d265de2
--- /dev/null
+++ b/vendor/react/dns/tests/Query/CoopExecutorTest.php
@@ -0,0 +1,233 @@
+getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testQueryOnceWillResolveWhenBaseExecutorResolves()
+ {
+ $message = new Message();
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $connector->query('8.8.8.8', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $promise->then($this->expectCallableOnceWith($message));
+ }
+
+ public function testQueryOnceWillRejectWhenBaseExecutorRejects()
+ {
+ $exception = new RuntimeException();
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn(\React\Promise\reject($exception));
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $connector->query('8.8.8.8', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+
+ $promise->then(null, $this->expectCallableOnceWith($exception));
+ }
+
+ public function testQueryTwoDifferentQueriesWillPassExactQueryToBaseExecutorTwice()
+ {
+ $pending = new Promise(function () { });
+ $query1 = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $query2 = new Query('reactphp.org', Message::TYPE_AAAA, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->withConsecutive(
+ array('8.8.8.8', $query1),
+ array('8.8.8.8', $query2)
+ )->willReturn($pending);
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query1);
+ $connector->query('8.8.8.8', $query2);
+ }
+
+ public function testQueryTwiceWillPassExactQueryToBaseExecutorOnceWhenQueryIsStillPending()
+ {
+ $pending = new Promise(function () { });
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyResolved()
+ {
+ $deferred = new Deferred();
+ $pending = new Promise(function () { });
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
+
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+
+ $deferred->resolve(new Message());
+
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyRejected()
+ {
+ $deferred = new Deferred();
+ $pending = new Promise(function () { });
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
+
+ $connector = new CoopExecutor($base);
+
+ $connector->query('8.8.8.8', $query);
+
+ $deferred->reject(new RuntimeException());
+
+ $connector->query('8.8.8.8', $query);
+ }
+
+ public function testCancelQueryWillCancelPromiseFromBaseExecutorAndReject()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $connector->query('8.8.8.8', $query);
+
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testCancelOneQueryWhenOtherQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableNever());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise1->cancel();
+
+ $promise1->then(null, $this->expectCallableOnce());
+ $promise2->then(null, $this->expectCallableNever());
+ }
+
+ public function testCancelSecondQueryWhenFirstQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableNever());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise2->cancel();
+
+ $promise2->then(null, $this->expectCallableOnce());
+ $promise1->then(null, $this->expectCallableNever());
+ }
+
+ public function testCancelAllPendingQueriesWillCancelPromiseFromBaseExecutorAndRejectCancelled()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($promise);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise1->cancel();
+ $promise2->cancel();
+
+ $promise1->then(null, $this->expectCallableOnce());
+ $promise2->then(null, $this->expectCallableOnce());
+ }
+
+ public function testQueryTwiceWillQueryBaseExecutorTwiceIfFirstQueryHasAlreadyBeenCancelledWhenSecondIsStarted()
+ {
+ $promise = new Promise(function () { }, $this->expectCallableOnce());
+ $pending = new Promise(function () { });
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->exactly(2))->method('query')->willReturnOnConsecutiveCalls($promise, $pending);
+ $connector = new CoopExecutor($base);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise1 = $connector->query('8.8.8.8', $query);
+ $promise1->cancel();
+
+ $promise2 = $connector->query('8.8.8.8', $query);
+
+ $promise1->then(null, $this->expectCallableOnce());
+
+ $promise2->then(null, $this->expectCallableNever());
+ }
+
+ public function testCancelQueryShouldNotCauseGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $deferred = new Deferred(function () {
+ throw new \RuntimeException();
+ });
+
+ $base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $base->expects($this->once())->method('query')->willReturn($deferred->promise());
+ $connector = new CoopExecutor($base);
+
+ gc_collect_cycles();
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $connector->query('8.8.8.8', $query);
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/vendor/react/dns/tests/Query/ExecutorTest.php b/vendor/react/dns/tests/Query/ExecutorTest.php
new file mode 100755
index 0000000..0d7ac1d
--- /dev/null
+++ b/vendor/react/dns/tests/Query/ExecutorTest.php
@@ -0,0 +1,308 @@
+loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->parser = $this->getMockBuilder('React\Dns\Protocol\Parser')->getMock();
+ $this->dumper = new BinaryDumper();
+
+ $this->executor = new Executor($this->loop, $this->parser, $this->dumper);
+ }
+
+ /** @test */
+ public function queryShouldCreateUdpRequest()
+ {
+ $timer = $this->createTimerMock();
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock(false));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldRejectIfRequestIsLargerThan512Bytes()
+ {
+ $query = new Query(str_repeat('a', 512).'.igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->setExpectedException('RuntimeException', 'DNS query for ' . $query->name . ' failed: Requested transport "tcp" not available, only UDP is supported in this version');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldCloseConnectionWhenCancelled()
+ {
+ $conn = $this->createConnectionMock(false);
+ $conn->expects($this->once())->method('close');
+
+ $timer = $this->createTimerMock();
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnValue($conn));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->cancel();
+
+ $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldNotStartOrCancelTimerWhenCancelledWithTimeoutIsNull()
+ {
+ $this->loop
+ ->expects($this->never())
+ ->method('addTimer');
+
+ $this->executor = new Executor($this->loop, $this->parser, $this->dumper, null);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('127.0.0.1:53', $query);
+
+ $promise->cancel();
+
+ $this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldRejectIfResponseIsTruncated()
+ {
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->any())
+ ->method('addTimer')
+ ->will($this->returnValue($timer));
+
+ $this->parser
+ ->expects($this->once())
+ ->method('parseMessage')
+ ->will($this->returnTruncatedResponse());
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock());
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldFailIfUdpThrow()
+ {
+ $this->loop
+ ->expects($this->never())
+ ->method('addTimer');
+
+ $this->parser
+ ->expects($this->never())
+ ->method('parseMessage');
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->once())
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->throwException(new \Exception('Nope')));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->setExpectedException('RuntimeException', 'DNS query for igor.io failed: Nope');
+ Block\await($promise, $this->loop);
+ }
+
+ /** @test */
+ public function resolveShouldCancelTimerWhenFullResponseIsReceived()
+ {
+ $conn = $this->createConnectionMock();
+
+ $this->parser
+ ->expects($this->once())
+ ->method('parseMessage')
+ ->will($this->returnStandardResponse());
+
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->at(0))
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock());
+
+
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->once())
+ ->method('addTimer')
+ ->with(5, $this->isInstanceOf('Closure'))
+ ->will($this->returnValue($timer));
+
+ $this->loop
+ ->expects($this->once())
+ ->method('cancelTimer')
+ ->with($timer);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query);
+ }
+
+ /** @test */
+ public function resolveShouldCloseConnectionOnTimeout()
+ {
+ $this->executor = $this->createExecutorMock();
+ $this->executor
+ ->expects($this->at(0))
+ ->method('createConnection')
+ ->with('8.8.8.8:53', 'udp')
+ ->will($this->returnNewConnectionMock(false));
+
+ $timer = $this->createTimerMock();
+
+ $this->loop
+ ->expects($this->never())
+ ->method('cancelTimer');
+
+ $this->loop
+ ->expects($this->once())
+ ->method('addTimer')
+ ->with(5, $this->isInstanceOf('Closure'))
+ ->will($this->returnCallback(function ($time, $callback) use (&$timerCallback, $timer) {
+ $timerCallback = $callback;
+ return $timer;
+ }));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->assertNotNull($timerCallback);
+ $timerCallback();
+
+ $this->setExpectedException('React\Dns\Query\TimeoutException', 'DNS query for igor.io timed out');
+ Block\await($promise, $this->loop);
+ }
+
+ private function returnStandardResponse()
+ {
+ $that = $this;
+ $callback = function ($data) use ($that) {
+ $response = new Message();
+ $that->convertMessageToStandardResponse($response);
+ return $response;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ private function returnTruncatedResponse()
+ {
+ $that = $this;
+ $callback = function ($data) use ($that) {
+ $response = new Message();
+ $that->convertMessageToTruncatedResponse($response);
+ return $response;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ public function convertMessageToStandardResponse(Message $response)
+ {
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->prepare();
+
+ return $response;
+ }
+
+ public function convertMessageToTruncatedResponse(Message $response)
+ {
+ $this->convertMessageToStandardResponse($response);
+ $response->header->set('tc', 1);
+ $response->prepare();
+
+ return $response;
+ }
+
+ private function returnNewConnectionMock($emitData = true)
+ {
+ $conn = $this->createConnectionMock($emitData);
+
+ $callback = function () use ($conn) {
+ return $conn;
+ };
+
+ return $this->returnCallback($callback);
+ }
+
+ private function createConnectionMock($emitData = true)
+ {
+ $conn = $this->getMockBuilder('React\Stream\DuplexStreamInterface')->getMock();
+ $conn
+ ->expects($this->any())
+ ->method('on')
+ ->with('data', $this->isInstanceOf('Closure'))
+ ->will($this->returnCallback(function ($name, $callback) use ($emitData) {
+ $emitData && $callback(null);
+ }));
+
+ return $conn;
+ }
+
+ private function createTimerMock()
+ {
+ return $this->getMockBuilder(
+ interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface'
+ )->getMock();
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\Executor')
+ ->setConstructorArgs(array($this->loop, $this->parser, $this->dumper))
+ ->setMethods(array('createConnection'))
+ ->getMock();
+ }
+}
diff --git a/vendor/react/dns/tests/Query/HostsFileExecutorTest.php b/vendor/react/dns/tests/Query/HostsFileExecutorTest.php
new file mode 100755
index 0000000..70d877e
--- /dev/null
+++ b/vendor/react/dns/tests/Query/HostsFileExecutorTest.php
@@ -0,0 +1,126 @@
+hosts = $this->getMockBuilder('React\Dns\Config\HostsFile')->disableOriginalConstructor()->getMock();
+ $this->fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ $this->executor = new HostsFileExecutor($this->hosts, $this->fallback);
+ }
+
+ public function testDoesNotTryToGetIpsForMxQuery()
+ {
+ $this->hosts->expects($this->never())->method('getIpsForHost');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_MX, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpsWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array());
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testReturnsResponseMessageIfIpsWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpv4Matches()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
+ $this->fallback->expects($this->once())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
+ }
+
+ public function testReturnsResponseMessageIfIpv6AddressesWereFound()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoIpv6Matches()
+ {
+ $this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
+ $this->fallback->expects($this->once())->method('query');
+
+ $ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
+ }
+
+ public function testDoesReturnReverseIpv4Lookup()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array('localhost'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackIfNoReverseIpv4Matches()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array());
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testDoesReturnReverseIpv6Lookup()
+ {
+ $this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(array('ip6-localhost'));
+ $this->fallback->expects($this->never())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.e.f.3.0.0.e.2.0.2.0.a.2.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testFallsBackForInvalidAddress()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidIpv4Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidLengthIpv6Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+
+ public function testReverseFallsBackForInvalidHexIpv6Address()
+ {
+ $this->hosts->expects($this->never())->method('getHostsForIp');
+ $this->fallback->expects($this->once())->method('query');
+
+ $this->executor->query('8.8.8.8', new Query('zZz.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
+ }
+}
diff --git a/vendor/react/dns/tests/Query/RecordBagTest.php b/vendor/react/dns/tests/Query/RecordBagTest.php
new file mode 100755
index 0000000..c0615be
--- /dev/null
+++ b/vendor/react/dns/tests/Query/RecordBagTest.php
@@ -0,0 +1,83 @@
+assertSame(array(), $recordBag->all());
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldSetTheValue()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600));
+
+ $records = $recordBag->all();
+ $this->assertCount(1, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_A, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldAcceptMxRecord()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 3600, array('priority' => 10, 'target' => 'igor.io')));
+
+ $records = $recordBag->all();
+ $this->assertCount(1, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_MX, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ $this->assertSame(array('priority' => 10, 'target' => 'igor.io'), $records[0]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordBag
+ * @test
+ */
+ public function setShouldSetManyValues()
+ {
+ $currentTime = 1345656451;
+
+ $recordBag = new RecordBag();
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
+
+ $records = $recordBag->all();
+ $this->assertCount(2, $records);
+ $this->assertSame('igor.io', $records[0]->name);
+ $this->assertSame(Message::TYPE_A, $records[0]->type);
+ $this->assertSame(Message::CLASS_IN, $records[0]->class);
+ $this->assertSame('178.79.169.131', $records[0]->data);
+ $this->assertSame('igor.io', $records[1]->name);
+ $this->assertSame(Message::TYPE_A, $records[1]->type);
+ $this->assertSame(Message::CLASS_IN, $records[1]->class);
+ $this->assertSame('178.79.169.132', $records[1]->data);
+ }
+}
diff --git a/vendor/react/dns/tests/Query/RecordCacheTest.php b/vendor/react/dns/tests/Query/RecordCacheTest.php
new file mode 100755
index 0000000..01f0eee
--- /dev/null
+++ b/vendor/react/dns/tests/Query/RecordCacheTest.php
@@ -0,0 +1,160 @@
+getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+
+ $cache = new RecordCache($base);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordPendingCacheDoesNotSetCache()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $pending = new Promise(function () { });
+
+ $base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $base->expects($this->once())->method('get')->willReturn($pending);
+ $base->expects($this->never())->method('set');
+
+ $cache = new RecordCache($base);
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordOnCacheMissSetsCache()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
+ $base->expects($this->once())->method('set')->with($this->isType('string'), $this->isType('string'));
+
+ $cache = new RecordCache($base);
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeRecordShouldMakeLookupSucceed()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(1, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeTwoRecordsShouldReturnBoth()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(2, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ $this->assertSame('178.79.169.132', $cachedRecords[1]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function storeResponseMessageShouldStoreAllAnswerValues()
+ {
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+
+ $response = new Message();
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132');
+ $response->prepare();
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeResponseMessage($query->currentTime, $response);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
+ $cachedRecords = $this->getPromiseValue($promise);
+
+ $this->assertCount(2, $cachedRecords);
+ $this->assertSame('178.79.169.131', $cachedRecords[0]->data);
+ $this->assertSame('178.79.169.132', $cachedRecords[1]->data);
+ }
+
+ /**
+ * @covers React\Dns\Query\RecordCache
+ * @test
+ */
+ public function expireShouldExpireDeadRecords()
+ {
+ $cachedTime = 1345656451;
+ $currentTime = $cachedTime + 3605;
+
+ $cache = new RecordCache(new ArrayCache());
+ $cache->storeRecord($cachedTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
+ $cache->expire($currentTime);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, $currentTime);
+ $promise = $cache->lookup($query);
+
+ $this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
+ }
+
+ private function getPromiseValue(PromiseInterface $promise)
+ {
+ $capturedValue = null;
+
+ $promise->then(function ($value) use (&$capturedValue) {
+ $capturedValue = $value;
+ });
+
+ return $capturedValue;
+ }
+}
diff --git a/vendor/react/dns/tests/Query/RetryExecutorTest.php b/vendor/react/dns/tests/Query/RetryExecutorTest.php
new file mode 100755
index 0000000..7e44a08
--- /dev/null
+++ b/vendor/react/dns/tests/Query/RetryExecutorTest.php
@@ -0,0 +1,350 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnValue($this->expectPromiseOnce()));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldRetryQueryOnTimeout()
+ {
+ $response = $this->createStandardResponse();
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(2))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->onConsecutiveCalls(
+ $this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new TimeoutException("timeout"));
+ }),
+ $this->returnCallback(function ($domain, $query) use ($response) {
+ return Promise\resolve($response);
+ })
+ ));
+
+ $callback = $this->createCallableMock();
+ $callback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('React\Dns\Model\Message'));
+
+ $errorback = $this->expectCallableNever();
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldStopRetryingAfterSomeAttempts()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(3))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new TimeoutException("timeout"));
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('RuntimeException'));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldForwardNonTimeoutErrors()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new \Exception);
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->isInstanceOf('Exception'));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldCancelQueryOnCancel()
+ {
+ $cancelled = 0;
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ })
+ );
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldCancelSecondQueryOnCancel()
+ {
+ $deferred = new Deferred();
+ $cancelled = 0;
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->exactly(2))
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->onConsecutiveCalls(
+ $this->returnValue($deferred->promise()),
+ $this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ })
+ ));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ // first query will time out after a while and this sends the next query
+ $deferred->reject(new TimeoutException());
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnSuccess()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->willReturn(Promise\resolve($this->createStandardResponse()));
+
+ $retryExecutor = new RetryExecutor($executor, 0);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnTimeoutErrors()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->any())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->willReturn(Promise\reject(new TimeoutException("timeout")));
+
+ $retryExecutor = new RetryExecutor($executor, 0);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnCancellation()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $deferred = new Deferred(function () {
+ throw new \RuntimeException();
+ });
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->willReturn($deferred->promise());
+
+ $retryExecutor = new RetryExecutor($executor, 0);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $retryExecutor->query('8.8.8.8', $query);
+ $promise->cancel();
+ $promise = null;
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @covers React\Dns\Query\RetryExecutor
+ * @test
+ */
+ public function queryShouldNotCauseGarbageReferencesOnNonTimeoutErrors()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($domain, $query) {
+ return Promise\reject(new \Exception);
+ }));
+
+ $retryExecutor = new RetryExecutor($executor, 2);
+
+ gc_collect_cycles();
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $retryExecutor->query('8.8.8.8', $query);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ protected function expectPromiseOnce($return = null)
+ {
+ $mock = $this->createPromiseMock();
+ $mock
+ ->expects($this->once())
+ ->method('then')
+ ->will($this->returnValue($return));
+
+ return $mock;
+ }
+
+ protected function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+
+ protected function createPromiseMock()
+ {
+ return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+ }
+
+ protected function createStandardResponse()
+ {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
+ $response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
+ $response->prepare();
+
+ return $response;
+ }
+}
+
diff --git a/vendor/react/dns/tests/Query/TimeoutExecutorTest.php b/vendor/react/dns/tests/Query/TimeoutExecutorTest.php
new file mode 100755
index 0000000..0d37fb4
--- /dev/null
+++ b/vendor/react/dns/tests/Query/TimeoutExecutorTest.php
@@ -0,0 +1,115 @@
+loop = Factory::create();
+
+ $this->wrapped = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+
+ $this->executor = new TimeoutExecutor($this->wrapped, 5.0, $this->loop);
+ }
+
+ public function testCancellingPromiseWillCancelWrapped()
+ {
+ $cancelled = 0;
+
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ }));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $this->assertEquals(0, $cancelled);
+ $promise->cancel();
+ $this->assertEquals(1, $cancelled);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testResolvesPromiseWhenWrappedResolves()
+ {
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->willReturn(Promise\resolve('0.0.0.0'));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+ }
+
+ public function testRejectsPromiseWhenWrappedRejects()
+ {
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->willReturn(Promise\reject(new \RuntimeException()));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $promise = $this->executor->query('8.8.8.8:53', $query);
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException()));
+ }
+
+ public function testWrappedWillBeCancelledOnTimeout()
+ {
+ $this->executor = new TimeoutExecutor($this->wrapped, 0, $this->loop);
+
+ $cancelled = 0;
+
+ $this->wrapped
+ ->expects($this->once())
+ ->method('query')
+ ->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
+ $deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
+ ++$cancelled;
+ $reject(new CancellationException('Cancelled'));
+ });
+
+ return $deferred->promise();
+ }));
+
+ $callback = $this->expectCallableNever();
+
+ $errorback = $this->createCallableMock();
+ $errorback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->logicalAnd(
+ $this->isInstanceOf('React\Dns\Query\TimeoutException'),
+ $this->attribute($this->equalTo('DNS query for igor.io timed out'), 'message')
+ ));
+
+ $query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
+ $this->executor->query('8.8.8.8:53', $query)->then($callback, $errorback);
+
+ $this->assertEquals(0, $cancelled);
+
+ $this->loop->run();
+
+ $this->assertEquals(1, $cancelled);
+ }
+}
diff --git a/vendor/react/dns/tests/Query/UdpTransportExecutorTest.php b/vendor/react/dns/tests/Query/UdpTransportExecutorTest.php
new file mode 100755
index 0000000..f7222dc
--- /dev/null
+++ b/vendor/react/dns/tests/Query/UdpTransportExecutorTest.php
@@ -0,0 +1,215 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addReadStream');
+
+ $dumper = $this->getMockBuilder('React\Dns\Protocol\BinaryDumper')->getMock();
+ $dumper->expects($this->once())->method('toBinary')->willReturn(str_repeat('.', 513));
+
+ $executor = new UdpTransportExecutor($loop, null, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $executor->query('8.8.8.8:53', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testQueryRejectsIfServerConnectionFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addReadStream');
+
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $executor->query('///', $query);
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ /**
+ * @group internet
+ */
+ public function testQueryRejectsOnCancellation()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+ $promise = $executor->query('8.8.8.8:53', $query);
+ $promise->cancel();
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testQueryKeepsPendingIfServerRejectsNetworkPacket()
+ {
+ $loop = Factory::create();
+
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query('127.0.0.1:1', $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ \Clue\React\Block\sleep(0.2, $loop);
+ $this->assertTrue($wait);
+ }
+
+ public function testQueryKeepsPendingIfServerSendInvalidMessage()
+ {
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+ stream_socket_sendto($server, 'invalid', 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query($address, $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ \Clue\React\Block\sleep(0.2, $loop);
+ $this->assertTrue($wait);
+ }
+
+ public function testQueryKeepsPendingIfServerSendInvalidId()
+ {
+ $parser = new Parser();
+ $dumper = new BinaryDumper();
+
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) use ($parser, $dumper) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+
+ $message = $parser->parseMessage($data);
+ $message->header->set('id', 0);
+
+ stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop, $parser, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query($address, $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ \Clue\React\Block\sleep(0.2, $loop);
+ $this->assertTrue($wait);
+ }
+
+ public function testQueryRejectsIfServerSendsTruncatedResponse()
+ {
+ $parser = new Parser();
+ $dumper = new BinaryDumper();
+
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) use ($parser, $dumper) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+
+ $message = $parser->parseMessage($data);
+ $message->header->set('tc', 1);
+
+ stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop, $parser, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $wait = true;
+ $promise = $executor->query($address, $query)->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect connection ICMP rejection error
+ \Clue\React\Block\sleep(0.01, $loop);
+ if ($wait) {
+ \Clue\React\Block\sleep(0.2, $loop);
+ }
+
+ $this->assertFalse($wait);
+ }
+
+ public function testQueryResolvesIfServerSendsValidResponse()
+ {
+ $parser = new Parser();
+ $dumper = new BinaryDumper();
+
+ $loop = Factory::create();
+
+ $server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
+ $loop->addReadStream($server, function ($server) use ($parser, $dumper) {
+ $data = stream_socket_recvfrom($server, 512, 0, $peer);
+
+ $message = $parser->parseMessage($data);
+
+ stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
+ });
+
+ $address = stream_socket_get_name($server, false);
+ $executor = new UdpTransportExecutor($loop, $parser, $dumper);
+
+ $query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
+
+ $promise = $executor->query($address, $query);
+ $response = \Clue\React\Block\await($promise, $loop, 0.2);
+
+ $this->assertInstanceOf('React\Dns\Model\Message', $response);
+ }
+}
diff --git a/vendor/react/dns/tests/Resolver/FactoryTest.php b/vendor/react/dns/tests/Resolver/FactoryTest.php
new file mode 100755
index 0000000..9b1b0f8
--- /dev/null
+++ b/vendor/react/dns/tests/Resolver/FactoryTest.php
@@ -0,0 +1,120 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create('8.8.8.8:53', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ }
+
+ /** @test */
+ public function createWithoutPortShouldCreateResolverWithDefaultPort()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create('8.8.8.8', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $this->assertSame('8.8.8.8:53', $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
+ }
+
+ /** @test */
+ public function createCachedShouldCreateResolverWithCachingExecutor()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->createCached('8.8.8.8:53', $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $executor = $this->getResolverPrivateExecutor($resolver);
+ $this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
+ $cache = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
+ $this->assertInstanceOf('React\Cache\ArrayCache', $cache);
+ }
+
+ /** @test */
+ public function createCachedShouldCreateResolverWithCachingExecutorWithCustomCache()
+ {
+ $cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->createCached('8.8.8.8:53', $loop, $cache);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $executor = $this->getResolverPrivateExecutor($resolver);
+ $this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
+ $cacheProperty = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
+ $this->assertSame($cache, $cacheProperty);
+ }
+
+ /**
+ * @test
+ * @dataProvider factoryShouldAddDefaultPortProvider
+ */
+ public function factoryShouldAddDefaultPort($input, $expected)
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $factory = new Factory();
+ $resolver = $factory->create($input, $loop);
+
+ $this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
+ $this->assertSame($expected, $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
+ }
+
+ public static function factoryShouldAddDefaultPortProvider()
+ {
+ return array(
+ array('8.8.8.8', '8.8.8.8:53'),
+ array('1.2.3.4:5', '1.2.3.4:5'),
+ array('localhost', 'localhost:53'),
+ array('localhost:1234', 'localhost:1234'),
+ array('::1', '[::1]:53'),
+ array('[::1]:53', '[::1]:53')
+ );
+ }
+
+ private function getResolverPrivateExecutor($resolver)
+ {
+ $executor = $this->getResolverPrivateMemberValue($resolver, 'executor');
+
+ // extract underlying executor that may be wrapped in multiple layers of hosts file executors
+ while ($executor instanceof HostsFileExecutor) {
+ $reflector = new \ReflectionProperty('React\Dns\Query\HostsFileExecutor', 'fallback');
+ $reflector->setAccessible(true);
+
+ $executor = $reflector->getValue($executor);
+ }
+
+ return $executor;
+ }
+
+ private function getResolverPrivateMemberValue($resolver, $field)
+ {
+ $reflector = new \ReflectionProperty('React\Dns\Resolver\Resolver', $field);
+ $reflector->setAccessible(true);
+ return $reflector->getValue($resolver);
+ }
+
+ private function getCachingExecutorPrivateMemberValue($resolver, $field)
+ {
+ $reflector = new \ReflectionProperty('React\Dns\Query\CachingExecutor', $field);
+ $reflector->setAccessible(true);
+ return $reflector->getValue($resolver);
+ }
+}
diff --git a/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php b/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
new file mode 100755
index 0000000..a9b8608
--- /dev/null
+++ b/vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
@@ -0,0 +1,101 @@
+createExecutorMock();
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+
+ $answers = $resolver->resolveAliases($answers, $name);
+
+ $this->assertEquals($expectedAnswers, $answers);
+ }
+
+ public function provideAliasedAnswers()
+ {
+ return array(
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array(),
+ array(
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ ),
+ 'igor.io',
+ ),
+ array(
+ array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
+ array(
+ new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
+ new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'baz.igor.io'),
+ new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'qux.igor.io'),
+ new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
+ new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
+ new Record('qux.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
+ ),
+ 'igor.io',
+ ),
+ );
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+}
diff --git a/vendor/react/dns/tests/Resolver/ResolverTest.php b/vendor/react/dns/tests/Resolver/ResolverTest.php
new file mode 100755
index 0000000..661386d
--- /dev/null
+++ b/vendor/react/dns/tests/Resolver/ResolverTest.php
@@ -0,0 +1,251 @@
+createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableOnceWith('178.79.169.131'));
+ }
+
+ /** @test */
+ public function resolveAllShouldQueryGivenRecords()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
+ }
+
+ /** @test */
+ public function resolveAllShouldIgnoreRecordsWithOtherTypes()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, Message::TYPE_TXT, $query->class, 3600, array('ignored'));
+ $response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
+ }
+
+ /** @test */
+ public function resolveAllShouldReturnMultipleValuesForAlias()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record($query->name, Message::TYPE_CNAME, $query->class, 3600, 'example.com');
+ $response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::1');
+ $response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::2');
+ $response->prepare();
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(
+ $this->expectCallableOnceWith($this->equalTo(array('::1', '::2')))
+ );
+ }
+
+ /** @test */
+ public function resolveShouldQueryARecordsAndIgnoreCase()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class);
+ $response->answers[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('blog.wyrihaximus.net')->then($this->expectCallableOnceWith('178.79.169.131'));
+ }
+
+ /** @test */
+ public function resolveShouldFilterByName()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+ $response->answers[] = new Record('foo.bar', $query->type, $query->class, 3600, '178.79.169.131');
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException'));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
+ }
+
+ /**
+ * @test
+ */
+ public function resolveWithNoAnswersShouldCallErrbackIfGiven()
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->callback(function ($param) {
+ return ($param instanceof RecordNotFoundException && $param->getCode() === 0 && $param->getMessage() === 'DNS query for igor.io did not return a valid answer (NOERROR / NODATA)');
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
+ }
+
+ public function provideRcodeErrors()
+ {
+ return array(
+ array(
+ Message::RCODE_FORMAT_ERROR,
+ 'DNS query for example.com returned an error response (Format Error)',
+ ),
+ array(
+ Message::RCODE_SERVER_FAILURE,
+ 'DNS query for example.com returned an error response (Server Failure)',
+ ),
+ array(
+ Message::RCODE_NAME_ERROR,
+ 'DNS query for example.com returned an error response (Non-Existent Domain / NXDOMAIN)'
+ ),
+ array(
+ Message::RCODE_NOT_IMPLEMENTED,
+ 'DNS query for example.com returned an error response (Not Implemented)'
+ ),
+ array(
+ Message::RCODE_REFUSED,
+ 'DNS query for example.com returned an error response (Refused)'
+ ),
+ array(
+ 99,
+ 'DNS query for example.com returned an error response (Unknown error response code 99)'
+ )
+ );
+ }
+
+ /**
+ * @test
+ * @dataProvider provideRcodeErrors
+ */
+ public function resolveWithRcodeErrorShouldCallErrbackIfGiven($code, $expectedMessage)
+ {
+ $executor = $this->createExecutorMock();
+ $executor
+ ->expects($this->once())
+ ->method('query')
+ ->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
+ ->will($this->returnCallback(function ($nameserver, $query) use ($code) {
+ $response = new Message();
+ $response->header->set('qr', 1);
+ $response->header->set('rcode', $code);
+ $response->questions[] = new Record($query->name, $query->type, $query->class);
+
+ return Promise\resolve($response);
+ }));
+
+ $errback = $this->expectCallableOnceWith($this->callback(function ($param) use ($code, $expectedMessage) {
+ return ($param instanceof RecordNotFoundException && $param->getCode() === $code && $param->getMessage() === $expectedMessage);
+ }));
+
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+ $resolver->resolve('example.com')->then($this->expectCallableNever(), $errback);
+ }
+
+ public function testLegacyExtractAddress()
+ {
+ $executor = $this->createExecutorMock();
+ $resolver = new Resolver('8.8.8.8:53', $executor);
+
+ $query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
+ $response = Message::createResponseWithAnswersForQuery($query, array(
+ new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '1.2.3.4')
+ ));
+
+ $ret = $resolver->extractAddress($query, $response);
+ $this->assertEquals('1.2.3.4', $ret);
+ }
+
+ private function createExecutorMock()
+ {
+ return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
+ }
+}
diff --git a/vendor/react/dns/tests/TestCase.php b/vendor/react/dns/tests/TestCase.php
new file mode 100755
index 0000000..a5a22bf
--- /dev/null
+++ b/vendor/react/dns/tests/TestCase.php
@@ -0,0 +1,61 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Dns\CallableStub')->getMock();
+ }
+
+ public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
+ {
+ if (method_exists($this, 'expectException')) {
+ // PHPUnit 5
+ $this->expectException($exception);
+ if ($exceptionMessage !== '') {
+ $this->expectExceptionMessage($exceptionMessage);
+ }
+ if ($exceptionCode !== null) {
+ $this->expectExceptionCode($exceptionCode);
+ }
+ } else {
+ // legacy PHPUnit 4
+ parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
+ }
+ }
+}
diff --git a/vendor/react/event-loop/.gitignore b/vendor/react/event-loop/.gitignore
new file mode 100755
index 0000000..81b9258
--- /dev/null
+++ b/vendor/react/event-loop/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+phpunit.xml
+vendor
diff --git a/vendor/react/event-loop/.travis.yml b/vendor/react/event-loop/.travis.yml
new file mode 100755
index 0000000..0b7ce2c
--- /dev/null
+++ b/vendor/react/event-loop/.travis.yml
@@ -0,0 +1,27 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - hhvm
+
+sudo: false
+
+addons:
+ apt:
+ packages:
+ - libevent-dev # Used by 'event' and 'libevent' PHP extensions
+
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+
+install:
+ - ./travis-init.sh
+ - composer install
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/vendor/react/event-loop/CHANGELOG.md b/vendor/react/event-loop/CHANGELOG.md
new file mode 100755
index 0000000..dd5d467
--- /dev/null
+++ b/vendor/react/event-loop/CHANGELOG.md
@@ -0,0 +1,79 @@
+# Changelog
+
+## 0.4.3 (2017-04-27)
+
+* Bug fix: Bugfix in the usage sample code #57 (@dandelionred)
+* Improvement: Remove branch-alias definition #53 (@WyriHaximus)
+* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd)
+* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder)
+* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley)
+* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue)
+* Improvement: Travis improvements (backported from #74) #75 (@clue)
+* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder)
+* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder)
+* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek)
+* Improvement: Readme cleanup #89 (@jsor)
+* Improvement: Restructure and improve README #90 (@jsor)
+* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor)
+
+## 0.4.2 (2016-03-07)
+
+* Bug fix: No longer error when signals sent to StreamSelectLoop
+* Support HHVM and PHP7 (@ondrejmirtes, @cebe)
+* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades)
+* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino)
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue)
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25)
+
+## 0.4.0 (2014-02-02)
+
+* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc)
+* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc)
+* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc)
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: New method: `EventLoopInterface::nextTick()`
+* BC break: New method: `EventLoopInterface::futureTick()`
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: No error on removing non-existent streams (@clue)
+* Bug fix: Do not silently remove feof listeners in `LibEvLoop`
+
+## 0.3.0 (2013-04-14)
+
+* BC break: New timers API (@nrk)
+* BC break: Remove check on return value from stream callbacks (@nrk)
+
+## 0.2.7 (2013-01-05)
+
+* Bug fix: Fix libevent timers with PHP 5.3
+* Bug fix: Fix libevent timer cancellation (@nrk)
+
+## 0.2.6 (2012-12-26)
+
+* Bug fix: Plug memory issue in libevent timers (@cameronjacobson)
+* Bug fix: Correctly pause LibEvLoop on stop()
+
+## 0.2.3 (2012-11-14)
+
+* Feature: LibEvLoop, integration of `php-libev`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Version bump
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/vendor/react/event-loop/LICENSE b/vendor/react/event-loop/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/vendor/react/event-loop/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/event-loop/README.md b/vendor/react/event-loop/README.md
new file mode 100755
index 0000000..d4d4c97
--- /dev/null
+++ b/vendor/react/event-loop/README.md
@@ -0,0 +1,143 @@
+# EventLoop Component
+
+[](https://travis-ci.org/reactphp/event-loop)
+[](https://codeclimate.com/github/reactphp/event-loop)
+
+Event loop abstraction layer that libraries can use for evented I/O.
+
+In order for async based libraries to be interoperable, they need to use the
+same event loop. This component provides a common `LoopInterface` that any
+library can target. This allows them to be used in the same loop, with one
+single `run()` call that is controlled by the user.
+
+**Table of Contents**
+
+* [Quickstart example](#quickstart-example)
+* [Usage](#usage)
+* [Loop implementations](#loop-implementations)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Quickstart example
+
+Here is an async HTTP server built with just the event loop.
+
+```php
+$loop = React\EventLoop\Factory::create();
+
+$server = stream_socket_server('tcp://127.0.0.1:8080');
+stream_set_blocking($server, 0);
+
+$loop->addReadStream($server, function ($server) use ($loop) {
+ $conn = stream_socket_accept($server);
+ $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n";
+ $loop->addWriteStream($conn, function ($conn) use (&$data, $loop) {
+ $written = fwrite($conn, $data);
+ if ($written === strlen($data)) {
+ fclose($conn);
+ $loop->removeStream($conn);
+ } else {
+ $data = substr($data, $written);
+ }
+ });
+});
+
+$loop->addPeriodicTimer(5, function () {
+ $memory = memory_get_usage() / 1024;
+ $formatted = number_format($memory, 3).'K';
+ echo "Current memory usage: {$formatted}\n";
+});
+
+$loop->run();
+```
+
+## Usage
+
+Typical applications use a single event loop which is created at the beginning
+and run at the end of the program.
+
+```php
+// [1]
+$loop = React\EventLoop\Factory::create();
+
+// [2]
+$loop->addPeriodicTimer(1, function () {
+ echo "Tick\n";
+});
+
+$stream = new React\Stream\ReadableResourceStream(
+ fopen('file.txt', 'r'),
+ $loop
+);
+
+// [3]
+$loop->run();
+```
+
+1. The loop instance is created at the beginning of the program. A convenience
+ factory `React\EventLoop\Factory::create()` is provided by this library which
+ picks the best available [loop implementation](#loop-implementations).
+2. The loop instance is used directly or passed to library and application code.
+ In this example, a periodic timer is registered with the event loop which
+ simply outputs `Tick` every second and a
+ [readable stream](https://github.com/reactphp/stream#readableresourcestream)
+ is created by using ReactPHP's
+ [stream component](https://github.com/reactphp/stream) for demonstration
+ purposes.
+3. The loop is run with a single `$loop->run()` call at the end of the program.
+
+## Loop implementations
+
+In addition to the interface there are the following implementations provided:
+
+* `StreamSelectLoop`: This is the only implementation which works out of the
+ box with PHP. It does a simple `select` system call. It's not the most
+ performant of loops, but still does the job quite well.
+
+* `LibEventLoop`: This uses the `libevent` pecl extension. `libevent` itself
+ supports a number of system-specific backends (epoll, kqueue).
+
+* `LibEvLoop`: This uses the `libev` pecl extension
+ ([github](https://github.com/m4rw3r/php-libev)). It supports the same
+ backends as libevent.
+
+* `ExtEventLoop`: This uses the `event` pecl extension. It supports the same
+ backends as libevent.
+
+All of the loops support these features:
+
+* File descriptor polling
+* One-off timers
+* Periodic timers
+* Deferred execution of callbacks
+
+## Install
+
+The recommended way to install this library is [through Composer](http://getcomposer.org).
+[New to Composer?](http://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/event-loop
+```
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](http://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/vendor/react/event-loop/composer.json b/vendor/react/event-loop/composer.json
new file mode 100755
index 0000000..5001a9c
--- /dev/null
+++ b/vendor/react/event-loop/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "react/event-loop",
+ "description": "Event loop abstraction layer that libraries can use for evented I/O.",
+ "keywords": ["event-loop", "asynchronous"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "suggest": {
+ "ext-libevent": ">=0.1.0",
+ "ext-event": "~1.0",
+ "ext-libev": "*"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src"
+ }
+ }
+}
diff --git a/vendor/react/event-loop/phpunit.xml.dist b/vendor/react/event-loop/phpunit.xml.dist
new file mode 100755
index 0000000..cba6d4d
--- /dev/null
+++ b/vendor/react/event-loop/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/vendor/react/event-loop/src/ExtEventLoop.php b/vendor/react/event-loop/src/ExtEventLoop.php
new file mode 100755
index 0000000..4f4b998
--- /dev/null
+++ b/vendor/react/event-loop/src/ExtEventLoop.php
@@ -0,0 +1,326 @@
+eventBase = new EventBase($config);
+ $this->nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timerEvents = new SplObjectStorage();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readListeners[$key])) {
+ $this->readListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, Event::READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeListeners[$key])) {
+ $this->writeListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, Event::WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ unset($this->readListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, Event::READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ unset($this->writeListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, Event::WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $this->streamEvents[$key]->free();
+
+ unset(
+ $this->streamFlags[$key],
+ $this->streamEvents[$key],
+ $this->readListeners[$key],
+ $this->writeListeners[$key]
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->isTimerActive($timer)) {
+ $this->timerEvents[$timer]->free();
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timerEvents->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ // @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226
+ @$this->eventBase->loop(EventBase::LOOP_ONCE | EventBase::LOOP_NONBLOCK);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $flags = EventBase::LOOP_ONCE;
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventBase::LOOP_NONBLOCK;
+ } elseif (!$this->streamEvents && !$this->timerEvents->count()) {
+ break;
+ }
+
+ $this->eventBase->loop($flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $flags = Event::TIMEOUT;
+
+ if ($timer->isPeriodic()) {
+ $flags |= Event::PERSIST;
+ }
+
+ $event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
+ $this->timerEvents[$timer] = $event;
+
+ $event->add($timer->getInterval());
+ }
+
+ /**
+ * Create a new ext-event Event object, or update the existing one.
+ *
+ * @param resource $stream
+ * @param integer $flag Event::READ or Event::WRITE
+ */
+ private function subscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $event = $this->streamEvents[$key];
+ $flags = ($this->streamFlags[$key] |= $flag);
+
+ $event->del();
+ $event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
+ } else {
+ $event = new Event($this->eventBase, $stream, Event::PERSIST | $flag, $this->streamCallback);
+
+ $this->streamEvents[$key] = $event;
+ $this->streamFlags[$key] = $flag;
+ }
+
+ $event->add();
+ }
+
+ /**
+ * Update the ext-event Event object for this stream to stop listening to
+ * the given event type, or remove it entirely if it's no longer needed.
+ *
+ * @param resource $stream
+ * @param integer $flag Event::READ or Event::WRITE
+ */
+ private function unsubscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ $flags = $this->streamFlags[$key] &= ~$flag;
+
+ if (0 === $flags) {
+ $this->removeStream($stream);
+
+ return;
+ }
+
+ $event = $this->streamEvents[$key];
+
+ $event->del();
+ $event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
+ $event->add();
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $this->timerCallback = function ($_, $__, $timer) {
+ call_user_func($timer->getCallback(), $timer);
+
+ if (!$timer->isPeriodic() && $this->isTimerActive($timer)) {
+ $this->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $this->streamCallback = function ($stream, $flags) {
+ $key = (int) $stream;
+
+ if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream, $this);
+ }
+
+ if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream, $this);
+ }
+ };
+ }
+}
diff --git a/vendor/react/event-loop/src/Factory.php b/vendor/react/event-loop/src/Factory.php
new file mode 100755
index 0000000..9a481e3
--- /dev/null
+++ b/vendor/react/event-loop/src/Factory.php
@@ -0,0 +1,21 @@
+loop = new EventLoop();
+ $this->nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timerEvents = new SplObjectStorage();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ if (isset($this->readEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ call_user_func($listener, $stream, $this);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::READ);
+ $this->loop->add($event);
+
+ $this->readEvents[(int) $stream] = $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ if (isset($this->writeEvents[(int) $stream])) {
+ return;
+ }
+
+ $callback = function () use ($stream, $listener) {
+ call_user_func($listener, $stream, $this);
+ };
+
+ $event = new IOEvent($callback, $stream, IOEvent::WRITE);
+ $this->loop->add($event);
+
+ $this->writeEvents[(int) $stream] = $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readEvents[$key])) {
+ $this->readEvents[$key]->stop();
+ unset($this->readEvents[$key]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeEvents[$key])) {
+ $this->writeEvents[$key]->stop();
+ unset($this->writeEvents[$key]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $this->removeReadStream($stream);
+ $this->removeWriteStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $callback = function () use ($timer) {
+ call_user_func($timer->getCallback(), $timer);
+
+ if ($this->isTimerActive($timer)) {
+ $this->cancelTimer($timer);
+ }
+ };
+
+ $event = new TimerEvent($callback, $timer->getInterval());
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $callback = function () use ($timer) {
+ call_user_func($timer->getCallback(), $timer);
+ };
+
+ $event = new TimerEvent($callback, $interval, $interval);
+ $this->timerEvents->attach($timer, $event);
+ $this->loop->add($event);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if (isset($this->timerEvents[$timer])) {
+ $this->loop->remove($this->timerEvents[$timer]);
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timerEvents->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $this->loop->run(EventLoop::RUN_ONCE | EventLoop::RUN_NOWAIT);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $flags = EventLoop::RUN_ONCE;
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EventLoop::RUN_NOWAIT;
+ } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) {
+ break;
+ }
+
+ $this->loop->run($flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+}
diff --git a/vendor/react/event-loop/src/LibEventLoop.php b/vendor/react/event-loop/src/LibEventLoop.php
new file mode 100755
index 0000000..99417a1
--- /dev/null
+++ b/vendor/react/event-loop/src/LibEventLoop.php
@@ -0,0 +1,343 @@
+eventBase = event_base_new();
+ $this->nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timerEvents = new SplObjectStorage();
+
+ $this->createTimerCallback();
+ $this->createStreamCallback();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readListeners[$key])) {
+ $this->readListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, EV_READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeListeners[$key])) {
+ $this->writeListeners[$key] = $listener;
+ $this->subscribeStreamEvent($stream, EV_WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ unset($this->readListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, EV_READ);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ unset($this->writeListeners[$key]);
+ $this->unsubscribeStreamEvent($stream, EV_WRITE);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $event = $this->streamEvents[$key];
+
+ event_del($event);
+ event_free($event);
+
+ unset(
+ $this->streamFlags[$key],
+ $this->streamEvents[$key],
+ $this->readListeners[$key],
+ $this->writeListeners[$key]
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $this->scheduleTimer($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ if ($this->isTimerActive($timer)) {
+ $event = $this->timerEvents[$timer];
+
+ event_del($event);
+ event_free($event);
+
+ $this->timerEvents->detach($timer);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timerEvents->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ event_base_loop($this->eventBase, EVLOOP_ONCE | EVLOOP_NONBLOCK);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $flags = EVLOOP_ONCE;
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $flags |= EVLOOP_NONBLOCK;
+ } elseif (!$this->streamEvents && !$this->timerEvents->count()) {
+ break;
+ }
+
+ event_base_loop($this->eventBase, $flags);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Schedule a timer for execution.
+ *
+ * @param TimerInterface $timer
+ */
+ private function scheduleTimer(TimerInterface $timer)
+ {
+ $this->timerEvents[$timer] = $event = event_timer_new();
+
+ event_timer_set($event, $this->timerCallback, $timer);
+ event_base_set($event, $this->eventBase);
+ event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
+ }
+
+ /**
+ * Create a new ext-libevent event resource, or update the existing one.
+ *
+ * @param resource $stream
+ * @param integer $flag EV_READ or EV_WRITE
+ */
+ private function subscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ if (isset($this->streamEvents[$key])) {
+ $event = $this->streamEvents[$key];
+ $flags = $this->streamFlags[$key] |= $flag;
+
+ event_del($event);
+ event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
+ } else {
+ $event = event_new();
+
+ event_set($event, $stream, EV_PERSIST | $flag, $this->streamCallback);
+ event_base_set($event, $this->eventBase);
+
+ $this->streamEvents[$key] = $event;
+ $this->streamFlags[$key] = $flag;
+ }
+
+ event_add($event);
+ }
+
+ /**
+ * Update the ext-libevent event resource for this stream to stop listening to
+ * the given event type, or remove it entirely if it's no longer needed.
+ *
+ * @param resource $stream
+ * @param integer $flag EV_READ or EV_WRITE
+ */
+ private function unsubscribeStreamEvent($stream, $flag)
+ {
+ $key = (int) $stream;
+
+ $flags = $this->streamFlags[$key] &= ~$flag;
+
+ if (0 === $flags) {
+ $this->removeStream($stream);
+
+ return;
+ }
+
+ $event = $this->streamEvents[$key];
+
+ event_del($event);
+ event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
+ event_add($event);
+ }
+
+ /**
+ * Create a callback used as the target of timer events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createTimerCallback()
+ {
+ $this->timerCallback = function ($_, $__, $timer) {
+ call_user_func($timer->getCallback(), $timer);
+
+ // Timer already cancelled ...
+ if (!$this->isTimerActive($timer)) {
+ return;
+
+ // Reschedule periodic timers ...
+ } elseif ($timer->isPeriodic()) {
+ event_add(
+ $this->timerEvents[$timer],
+ $timer->getInterval() * self::MICROSECONDS_PER_SECOND
+ );
+
+ // Clean-up one shot timers ...
+ } else {
+ $this->cancelTimer($timer);
+ }
+ };
+ }
+
+ /**
+ * Create a callback used as the target of stream events.
+ *
+ * A reference is kept to the callback for the lifetime of the loop
+ * to prevent "Cannot destroy active lambda function" fatal error from
+ * the event extension.
+ */
+ private function createStreamCallback()
+ {
+ $this->streamCallback = function ($stream, $flags) {
+ $key = (int) $stream;
+
+ if (EV_READ === (EV_READ & $flags) && isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream, $this);
+ }
+
+ if (EV_WRITE === (EV_WRITE & $flags) && isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream, $this);
+ }
+ };
+ }
+}
diff --git a/vendor/react/event-loop/src/LoopInterface.php b/vendor/react/event-loop/src/LoopInterface.php
new file mode 100755
index 0000000..d046526
--- /dev/null
+++ b/vendor/react/event-loop/src/LoopInterface.php
@@ -0,0 +1,121 @@
+nextTickQueue = new NextTickQueue($this);
+ $this->futureTickQueue = new FutureTickQueue($this);
+ $this->timers = new Timers();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addReadStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->readStreams[$key])) {
+ $this->readStreams[$key] = $stream;
+ $this->readListeners[$key] = $listener;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addWriteStream($stream, callable $listener)
+ {
+ $key = (int) $stream;
+
+ if (!isset($this->writeStreams[$key])) {
+ $this->writeStreams[$key] = $stream;
+ $this->writeListeners[$key] = $listener;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeReadStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->readStreams[$key],
+ $this->readListeners[$key]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeWriteStream($stream)
+ {
+ $key = (int) $stream;
+
+ unset(
+ $this->writeStreams[$key],
+ $this->writeListeners[$key]
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeStream($stream)
+ {
+ $this->removeReadStream($stream);
+ $this->removeWriteStream($stream);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, false);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPeriodicTimer($interval, callable $callback)
+ {
+ $timer = new Timer($this, $interval, $callback, true);
+
+ $this->timers->add($timer);
+
+ return $timer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancelTimer(TimerInterface $timer)
+ {
+ $this->timers->cancel($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTimerActive(TimerInterface $timer)
+ {
+ return $this->timers->contains($timer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextTick(callable $listener)
+ {
+ $this->nextTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function futureTick(callable $listener)
+ {
+ $this->futureTickQueue->add($listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tick()
+ {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $this->timers->tick();
+
+ $this->waitForStreamActivity(0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $this->running = true;
+
+ while ($this->running) {
+ $this->nextTickQueue->tick();
+
+ $this->futureTickQueue->tick();
+
+ $this->timers->tick();
+
+ // Next-tick or future-tick queues have pending callbacks ...
+ if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
+ $timeout = 0;
+
+ // There is a pending timer, only block until it is due ...
+ } elseif ($scheduledAt = $this->timers->getFirst()) {
+ $timeout = $scheduledAt - $this->timers->getTime();
+ if ($timeout < 0) {
+ $timeout = 0;
+ } else {
+ /*
+ * round() needed to correct float error:
+ * https://github.com/reactphp/event-loop/issues/48
+ */
+ $timeout = round($timeout * self::MICROSECONDS_PER_SECOND);
+ }
+
+ // The only possible event is stream activity, so wait forever ...
+ } elseif ($this->readStreams || $this->writeStreams) {
+ $timeout = null;
+
+ // There's nothing left to do ...
+ } else {
+ break;
+ }
+
+ $this->waitForStreamActivity($timeout);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function stop()
+ {
+ $this->running = false;
+ }
+
+ /**
+ * Wait/check for stream activity, or until the next timer is due.
+ */
+ private function waitForStreamActivity($timeout)
+ {
+ $read = $this->readStreams;
+ $write = $this->writeStreams;
+
+ $available = $this->streamSelect($read, $write, $timeout);
+ if (false === $available) {
+ // if a system call has been interrupted,
+ // we cannot rely on it's outcome
+ return;
+ }
+
+ foreach ($read as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->readListeners[$key])) {
+ call_user_func($this->readListeners[$key], $stream, $this);
+ }
+ }
+
+ foreach ($write as $stream) {
+ $key = (int) $stream;
+
+ if (isset($this->writeListeners[$key])) {
+ call_user_func($this->writeListeners[$key], $stream, $this);
+ }
+ }
+ }
+
+ /**
+ * Emulate a stream_select() implementation that does not break when passed
+ * empty stream arrays.
+ *
+ * @param array &$read An array of read streams to select upon.
+ * @param array &$write An array of write streams to select upon.
+ * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
+ *
+ * @return integer|false The total number of streams that are ready for read/write.
+ * Can return false if stream_select() is interrupted by a signal.
+ */
+ protected function streamSelect(array &$read, array &$write, $timeout)
+ {
+ if ($read || $write) {
+ $except = null;
+
+ // suppress warnings that occur, when stream_select is interrupted by a signal
+ return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
+ }
+
+ $timeout && usleep($timeout);
+
+ return 0;
+ }
+}
diff --git a/vendor/react/event-loop/src/Tick/FutureTickQueue.php b/vendor/react/event-loop/src/Tick/FutureTickQueue.php
new file mode 100755
index 0000000..eeffd36
--- /dev/null
+++ b/vendor/react/event-loop/src/Tick/FutureTickQueue.php
@@ -0,0 +1,59 @@
+eventLoop = $eventLoop;
+ $this->queue = new SplQueue();
+ }
+
+ /**
+ * Add a callback to be invoked on a future tick of the event loop.
+ *
+ * Callbacks are guaranteed to be executed in the order they are enqueued.
+ *
+ * @param callable $listener The callback to invoke.
+ */
+ public function add(callable $listener)
+ {
+ $this->queue->enqueue($listener);
+ }
+
+ /**
+ * Flush the callback queue.
+ */
+ public function tick()
+ {
+ // Only invoke as many callbacks as were on the queue when tick() was called.
+ $count = $this->queue->count();
+
+ while ($count--) {
+ call_user_func(
+ $this->queue->dequeue(),
+ $this->eventLoop
+ );
+ }
+ }
+
+ /**
+ * Check if the next tick queue is empty.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->queue->isEmpty();
+ }
+}
diff --git a/vendor/react/event-loop/src/Tick/NextTickQueue.php b/vendor/react/event-loop/src/Tick/NextTickQueue.php
new file mode 100755
index 0000000..5b8e1de
--- /dev/null
+++ b/vendor/react/event-loop/src/Tick/NextTickQueue.php
@@ -0,0 +1,57 @@
+eventLoop = $eventLoop;
+ $this->queue = new SplQueue();
+ }
+
+ /**
+ * Add a callback to be invoked on the next tick of the event loop.
+ *
+ * Callbacks are guaranteed to be executed in the order they are enqueued,
+ * before any timer or stream events.
+ *
+ * @param callable $listener The callback to invoke.
+ */
+ public function add(callable $listener)
+ {
+ $this->queue->enqueue($listener);
+ }
+
+ /**
+ * Flush the callback queue.
+ */
+ public function tick()
+ {
+ while (!$this->queue->isEmpty()) {
+ call_user_func(
+ $this->queue->dequeue(),
+ $this->eventLoop
+ );
+ }
+ }
+
+ /**
+ * Check if the next tick queue is empty.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->queue->isEmpty();
+ }
+}
diff --git a/vendor/react/event-loop/src/Timer/Timer.php b/vendor/react/event-loop/src/Timer/Timer.php
new file mode 100755
index 0000000..f670ab3
--- /dev/null
+++ b/vendor/react/event-loop/src/Timer/Timer.php
@@ -0,0 +1,102 @@
+loop = $loop;
+ $this->interval = (float) $interval;
+ $this->callback = $callback;
+ $this->periodic = (bool) $periodic;
+ $this->data = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getLoop()
+ {
+ return $this->loop;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInterval()
+ {
+ return $this->interval;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCallback()
+ {
+ return $this->callback;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setData($data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isPeriodic()
+ {
+ return $this->periodic;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isActive()
+ {
+ return $this->loop->isTimerActive($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cancel()
+ {
+ $this->loop->cancelTimer($this);
+ }
+}
diff --git a/vendor/react/event-loop/src/Timer/TimerInterface.php b/vendor/react/event-loop/src/Timer/TimerInterface.php
new file mode 100755
index 0000000..d066f36
--- /dev/null
+++ b/vendor/react/event-loop/src/Timer/TimerInterface.php
@@ -0,0 +1,62 @@
+timers = new SplObjectStorage();
+ $this->scheduler = new SplPriorityQueue();
+ }
+
+ public function updateTime()
+ {
+ return $this->time = microtime(true);
+ }
+
+ public function getTime()
+ {
+ return $this->time ?: $this->updateTime();
+ }
+
+ public function add(TimerInterface $timer)
+ {
+ $interval = $timer->getInterval();
+ $scheduledAt = $interval + microtime(true);
+
+ $this->timers->attach($timer, $scheduledAt);
+ $this->scheduler->insert($timer, -$scheduledAt);
+ }
+
+ public function contains(TimerInterface $timer)
+ {
+ return $this->timers->contains($timer);
+ }
+
+ public function cancel(TimerInterface $timer)
+ {
+ $this->timers->detach($timer);
+ }
+
+ public function getFirst()
+ {
+ while ($this->scheduler->count()) {
+ $timer = $this->scheduler->top();
+
+ if ($this->timers->contains($timer)) {
+ return $this->timers[$timer];
+ }
+
+ $this->scheduler->extract();
+ }
+
+ return null;
+ }
+
+ public function isEmpty()
+ {
+ return count($this->timers) === 0;
+ }
+
+ public function tick()
+ {
+ $time = $this->updateTime();
+ $timers = $this->timers;
+ $scheduler = $this->scheduler;
+
+ while (!$scheduler->isEmpty()) {
+ $timer = $scheduler->top();
+
+ if (!isset($timers[$timer])) {
+ $scheduler->extract();
+ $timers->detach($timer);
+
+ continue;
+ }
+
+ if ($timers[$timer] >= $time) {
+ break;
+ }
+
+ $scheduler->extract();
+ call_user_func($timer->getCallback(), $timer);
+
+ if ($timer->isPeriodic() && isset($timers[$timer])) {
+ $timers[$timer] = $scheduledAt = $timer->getInterval() + $time;
+ $scheduler->insert($timer, -$scheduledAt);
+ } else {
+ $timers->detach($timer);
+ }
+ }
+ }
+}
diff --git a/vendor/react/event-loop/tests/AbstractLoopTest.php b/vendor/react/event-loop/tests/AbstractLoopTest.php
new file mode 100755
index 0000000..bd6bb83
--- /dev/null
+++ b/vendor/react/event-loop/tests/AbstractLoopTest.php
@@ -0,0 +1,539 @@
+tickTimeout = defined('HHVM_VERSION') ? 0.02 : 0.005;
+ $this->loop = $this->createLoop();
+ }
+
+ abstract public function createLoop();
+
+ public function createSocketPair()
+ {
+ $domain = (DIRECTORY_SEPARATOR === '\\') ? STREAM_PF_INET : STREAM_PF_UNIX;
+ $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+
+ foreach ($sockets as $socket) {
+ if (function_exists('stream_set_read_buffer')) {
+ stream_set_read_buffer($socket, 0);
+ }
+ }
+
+ return $sockets;
+ }
+
+ public function testAddReadStream()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testAddReadStreamIgnoresSecondCallable()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testAddWriteStream()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
+ $this->loop->tick();
+ $this->loop->tick();
+ }
+
+ public function testAddWriteStreamIgnoresSecondCallable()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->tick();
+ $this->loop->tick();
+ }
+
+ public function testRemoveReadStreamInstantly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveReadStreamAfterReading()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveWriteStreamInstantly()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->removeWriteStream($input);
+ $this->loop->tick();
+ }
+
+ public function testRemoveWriteStreamAfterWriting()
+ {
+ list ($input) = $this->createSocketPair();
+
+ $this->loop->addWriteStream($input, $this->expectCallableOnce());
+ $this->loop->tick();
+
+ $this->loop->removeWriteStream($input);
+ $this->loop->tick();
+ }
+
+ public function testRemoveStreamInstantly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->addWriteStream($input, $this->expectCallableNever());
+ $this->loop->removeStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveStreamForReadOnly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableNever());
+ $this->loop->addWriteStream($output, $this->expectCallableOnce());
+ $this->loop->removeReadStream($input);
+
+ fwrite($output, "foo\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveStreamForWriteOnly()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ fwrite($output, "foo\n");
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+ $this->loop->addWriteStream($output, $this->expectCallableNever());
+ $this->loop->removeWriteStream($output);
+
+ $this->loop->tick();
+ }
+
+ public function testRemoveStream()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $this->loop->addReadStream($input, $this->expectCallableOnce());
+ $this->loop->addWriteStream($input, $this->expectCallableOnce());
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+
+ $this->loop->removeStream($input);
+
+ fwrite($output, "bar\n");
+ $this->loop->tick();
+ }
+
+ public function testRemoveInvalid()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ // remove a valid stream from the event loop that was never added in the first place
+ $this->loop->removeReadStream($stream);
+ $this->loop->removeWriteStream($stream);
+ $this->loop->removeStream($stream);
+ }
+
+ /** @test */
+ public function emptyRunShouldSimplyReturn()
+ {
+ $this->assertRunFasterThan($this->tickTimeout);
+ }
+
+ /** @test */
+ public function runShouldReturnWhenNoMoreFds()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addReadStream($input, function ($stream) use ($loop) {
+ $loop->removeStream($stream);
+ });
+
+ fwrite($output, "foo\n");
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ /** @test */
+ public function stopShouldStopRunningLoop()
+ {
+ list ($input, $output) = $this->createSocketPair();
+
+ $loop = $this->loop;
+ $this->loop->addReadStream($input, function ($stream) use ($loop) {
+ $loop->stop();
+ });
+
+ fwrite($output, "foo\n");
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ public function testStopShouldPreventRunFromBlocking()
+ {
+ $this->loop->addTimer(
+ 1,
+ function () {
+ $this->fail('Timer was executed.');
+ }
+ );
+
+ $this->loop->nextTick(
+ function () {
+ $this->loop->stop();
+ }
+ );
+
+ $this->assertRunFasterThan($this->tickTimeout * 2);
+ }
+
+ public function testIgnoreRemovedCallback()
+ {
+ // two independent streams, both should be readable right away
+ list ($input1, $output1) = $this->createSocketPair();
+ list ($input2, $output2) = $this->createSocketPair();
+
+ $called = false;
+
+ $loop = $this->loop;
+ $loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) {
+ // stream1 is readable, remove stream2 as well => this will invalidate its callback
+ $loop->removeReadStream($stream);
+ $loop->removeReadStream($input2);
+
+ $called = true;
+ });
+
+ // this callback would have to be called as well, but the first stream already removed us
+ $loop->addReadStream($input2, function () use (& $called) {
+ if ($called) {
+ $this->fail('Callback 2 must not be called after callback 1 was called');
+ }
+ });
+
+ fwrite($output1, "foo\n");
+ fwrite($output2, "foo\n");
+
+ $loop->run();
+
+ $this->assertTrue($called);
+ }
+
+ public function testNextTick()
+ {
+ $called = false;
+
+ $callback = function ($loop) use (&$called) {
+ $this->assertSame($this->loop, $loop);
+ $called = true;
+ };
+
+ $this->loop->nextTick($callback);
+
+ $this->assertFalse($called);
+
+ $this->loop->tick();
+
+ $this->assertTrue($called);
+ }
+
+ public function testNextTickFiresBeforeIO()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->loop->tick();
+ }
+
+ public function testRecursiveNextTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->nextTick(
+ function () {
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->loop->tick();
+ }
+
+ public function testRunWaitsForNextTickEvents()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream) {
+ $this->loop->removeStream($stream);
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testNextTickEventGeneratedByFutureTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->futureTick(
+ function () {
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testNextTickEventGeneratedByTimer()
+ {
+ $this->loop->addTimer(
+ 0.001,
+ function () {
+ $this->loop->nextTick(
+ function () {
+ echo 'next-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('next-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTick()
+ {
+ $called = false;
+
+ $callback = function ($loop) use (&$called) {
+ $this->assertSame($this->loop, $loop);
+ $called = true;
+ };
+
+ $this->loop->futureTick($callback);
+
+ $this->assertFalse($called);
+
+ $this->loop->tick();
+
+ $this->assertTrue($called);
+ }
+
+ public function testFutureTickFiresBeforeIO()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () {
+ echo 'stream' . PHP_EOL;
+ }
+ );
+
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL);
+
+ $this->loop->tick();
+ }
+
+ public function testRecursiveFutureTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream) {
+ echo 'stream' . PHP_EOL;
+ $this->loop->removeWriteStream($stream);
+ }
+ );
+
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick-1' . PHP_EOL;
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick-2' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick-1' . PHP_EOL . 'stream' . PHP_EOL . 'future-tick-2' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testRunWaitsForFutureTickEvents()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->addWriteStream(
+ $stream,
+ function () use ($stream) {
+ $this->loop->removeStream($stream);
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTickEventGeneratedByNextTick()
+ {
+ list ($stream) = $this->createSocketPair();
+
+ $this->loop->nextTick(
+ function () {
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ public function testFutureTickEventGeneratedByTimer()
+ {
+ $this->loop->addTimer(
+ 0.001,
+ function () {
+ $this->loop->futureTick(
+ function () {
+ echo 'future-tick' . PHP_EOL;
+ }
+ );
+ }
+ );
+
+ $this->expectOutputString('future-tick' . PHP_EOL);
+
+ $this->loop->run();
+ }
+
+ private function assertRunFasterThan($maxInterval)
+ {
+ $start = microtime(true);
+
+ $this->loop->run();
+
+ $end = microtime(true);
+ $interval = $end - $start;
+
+ $this->assertLessThan($maxInterval, $interval);
+ }
+}
diff --git a/vendor/react/event-loop/tests/CallableStub.php b/vendor/react/event-loop/tests/CallableStub.php
new file mode 100755
index 0000000..913d403
--- /dev/null
+++ b/vendor/react/event-loop/tests/CallableStub.php
@@ -0,0 +1,10 @@
+markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
+ }
+
+ if (!extension_loaded('event')) {
+ $this->markTestSkipped('ext-event tests skipped because ext-event is not installed.');
+ }
+
+ $cfg = null;
+ if ($readStreamCompatible) {
+ $cfg = new \EventConfig();
+ $cfg->requireFeatures(\EventConfig::FEATURE_FDS);
+ }
+
+ return new ExtEventLoop($cfg);
+ }
+
+ public function createStream()
+ {
+ // Use a FIFO on linux to get around lack of support for disk-based file
+ // descriptors when using the EPOLL back-end.
+ if ('Linux' === PHP_OS) {
+ $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
+
+ unlink($this->fifoPath);
+
+ posix_mkfifo($this->fifoPath, 0600);
+
+ $stream = fopen($this->fifoPath, 'r+');
+
+ // ext-event (as of 1.8.1) does not yet support in-memory temporary
+ // streams. Setting maxmemory:0 and performing a write forces PHP to
+ // back this temporary stream with a real file.
+ //
+ // This problem is mentioned at https://bugs.php.net/bug.php?id=64652&edit=3
+ // but remains unresolved (despite that issue being closed).
+ } else {
+ $stream = fopen('php://temp/maxmemory:0', 'r+');
+
+ fwrite($stream, 'x');
+ ftruncate($stream, 0);
+ }
+
+ return $stream;
+ }
+
+ public function writeToStream($stream, $content)
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::writeToStream($stream, $content);
+ }
+
+ fwrite($stream, $content);
+ }
+
+ /**
+ * @group epoll-readable-error
+ */
+ public function testCanUseReadableStreamWithFeatureFds()
+ {
+ if (PHP_VERSION_ID > 70000) {
+ $this->markTestSkipped('Memory stream not supported');
+ }
+
+ $this->loop = $this->createLoop(true);
+
+ $input = fopen('php://temp/maxmemory:0', 'r+');
+
+ fwrite($input, 'x');
+ ftruncate($input, 0);
+
+ $this->loop->addReadStream($input, $this->expectCallableExactly(2));
+
+ fwrite($input, "foo\n");
+ $this->loop->tick();
+
+ fwrite($input, "bar\n");
+ $this->loop->tick();
+ }
+}
diff --git a/vendor/react/event-loop/tests/LibEvLoopTest.php b/vendor/react/event-loop/tests/LibEvLoopTest.php
new file mode 100755
index 0000000..5ea98e3
--- /dev/null
+++ b/vendor/react/event-loop/tests/LibEvLoopTest.php
@@ -0,0 +1,22 @@
+markTestSkipped('libev tests skipped because ext-libev is not installed.');
+ }
+
+ return new LibEvLoop();
+ }
+
+ public function testLibEvConstructor()
+ {
+ $loop = new LibEvLoop();
+ }
+}
diff --git a/vendor/react/event-loop/tests/LibEventLoopTest.php b/vendor/react/event-loop/tests/LibEventLoopTest.php
new file mode 100755
index 0000000..920b33c
--- /dev/null
+++ b/vendor/react/event-loop/tests/LibEventLoopTest.php
@@ -0,0 +1,58 @@
+markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
+ }
+
+ if (!function_exists('event_base_new')) {
+ $this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
+ }
+
+ return new LibEventLoop();
+ }
+
+ public function tearDown()
+ {
+ if (file_exists($this->fifoPath)) {
+ unlink($this->fifoPath);
+ }
+ }
+
+ public function createStream()
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::createStream();
+ }
+
+ $this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
+
+ unlink($this->fifoPath);
+
+ // Use a FIFO on linux to get around lack of support for disk-based file
+ // descriptors when using the EPOLL back-end.
+ posix_mkfifo($this->fifoPath, 0600);
+
+ $stream = fopen($this->fifoPath, 'r+');
+
+ return $stream;
+ }
+
+ public function writeToStream($stream, $content)
+ {
+ if ('Linux' !== PHP_OS) {
+ return parent::writeToStream($stream, $content);
+ }
+
+ fwrite($stream, $content);
+ }
+}
diff --git a/vendor/react/event-loop/tests/StreamSelectLoopTest.php b/vendor/react/event-loop/tests/StreamSelectLoopTest.php
new file mode 100755
index 0000000..d2e3e07
--- /dev/null
+++ b/vendor/react/event-loop/tests/StreamSelectLoopTest.php
@@ -0,0 +1,179 @@
+getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
+ $this->resetSignalHandlers();
+ }
+ }
+
+ public function createLoop()
+ {
+ return new StreamSelectLoop();
+ }
+
+ public function testStreamSelectTimeoutEmulation()
+ {
+ $this->loop->addTimer(
+ 0.05,
+ $this->expectCallableOnce()
+ );
+
+ $start = microtime(true);
+
+ $this->loop->run();
+
+ $end = microtime(true);
+ $interval = $end - $start;
+
+ $this->assertGreaterThan(0.04, $interval);
+ }
+
+ public function signalProvider()
+ {
+ return [
+ ['SIGUSR1'],
+ ['SIGHUP'],
+ ['SIGTERM'],
+ ];
+ }
+
+ private $_signalHandled = false;
+
+ /**
+ * Test signal interrupt when no stream is attached to the loop
+ * @dataProvider signalProvider
+ */
+ public function testSignalInterruptNoStream($signal)
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('"pcntl" extension is required to run this test.');
+ }
+
+ // dispatch signal handler once before signal is sent and once after
+ $this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); });
+ $this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); });
+ if (defined('HHVM_VERSION')) {
+ // hhvm startup is slow so we need to add another handler much later
+ $this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); });
+ }
+
+ $this->setUpSignalHandler($signal);
+
+ // spawn external process to send signal to current process id
+ $this->forkSendSignal($signal);
+ $this->loop->run();
+ $this->assertTrue($this->_signalHandled);
+ }
+
+ /**
+ * Test signal interrupt when a stream is attached to the loop
+ * @dataProvider signalProvider
+ */
+ public function testSignalInterruptWithStream($signal)
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('"pcntl" extension is required to run this test.');
+ }
+
+ // dispatch signal handler every 10ms
+ $this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); });
+
+ // add stream to the loop
+ list($writeStream, $readStream) = $this->createSocketPair();
+ $this->loop->addReadStream($readStream, function($stream, $loop) {
+ /** @var $loop LoopInterface */
+ $read = fgets($stream);
+ if ($read === "end loop\n") {
+ $loop->stop();
+ }
+ });
+ $this->loop->addTimer(0.05, function() use ($writeStream) {
+ fwrite($writeStream, "end loop\n");
+ });
+
+ $this->setUpSignalHandler($signal);
+
+ // spawn external process to send signal to current process id
+ $this->forkSendSignal($signal);
+
+ $this->loop->run();
+
+ $this->assertTrue($this->_signalHandled);
+ }
+
+ /**
+ * add signal handler for signal
+ */
+ protected function setUpSignalHandler($signal)
+ {
+ $this->_signalHandled = false;
+ $this->assertTrue(pcntl_signal(constant($signal), function() { $this->_signalHandled = true; }));
+ }
+
+ /**
+ * reset all signal handlers to default
+ */
+ protected function resetSignalHandlers()
+ {
+ foreach($this->signalProvider() as $signal) {
+ pcntl_signal(constant($signal[0]), SIG_DFL);
+ }
+ }
+
+ /**
+ * fork child process to send signal to current process id
+ */
+ protected function forkSendSignal($signal)
+ {
+ $currentPid = posix_getpid();
+ $childPid = pcntl_fork();
+ if ($childPid == -1) {
+ $this->fail("Failed to fork child process!");
+ } else if ($childPid === 0) {
+ // this is executed in the child process
+ usleep(20000);
+ posix_kill($currentPid, constant($signal));
+ die();
+ }
+ }
+
+ /**
+ * https://github.com/reactphp/event-loop/issues/48
+ *
+ * Tests that timer with very small interval uses at least 1 microsecond
+ * timeout.
+ */
+ public function testSmallTimerInterval()
+ {
+ /** @var StreamSelectLoop|\PHPUnit_Framework_MockObject_MockObject $loop */
+ $loop = $this->getMock('React\EventLoop\StreamSelectLoop', ['streamSelect']);
+ $loop
+ ->expects($this->at(0))
+ ->method('streamSelect')
+ ->with([], [], 1);
+ $loop
+ ->expects($this->at(1))
+ ->method('streamSelect')
+ ->with([], [], 0);
+
+ $callsCount = 0;
+ $loop->addPeriodicTimer(Timer::MIN_INTERVAL, function() use (&$loop, &$callsCount) {
+ $callsCount++;
+ if ($callsCount == 2) {
+ $loop->stop();
+ }
+ });
+
+ $loop->run();
+ }
+}
diff --git a/vendor/react/event-loop/tests/TestCase.php b/vendor/react/event-loop/tests/TestCase.php
new file mode 100755
index 0000000..5114f4e
--- /dev/null
+++ b/vendor/react/event-loop/tests/TestCase.php
@@ -0,0 +1,41 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\EventLoop\CallableStub')->getMock();
+ }
+}
diff --git a/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php b/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php
new file mode 100755
index 0000000..5768965
--- /dev/null
+++ b/vendor/react/event-loop/tests/Timer/AbstractTimerTest.php
@@ -0,0 +1,97 @@
+createLoop();
+
+ $loop->addTimer(0.001, $this->expectCallableOnce());
+ usleep(1000);
+ $loop->tick();
+ }
+
+ public function testAddPeriodicTimer()
+ {
+ $loop = $this->createLoop();
+
+ $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(3));
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+ }
+
+ public function testAddPeriodicTimerWithCancel()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addPeriodicTimer(0.001, $this->expectCallableExactly(2));
+
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+
+ $timer->cancel();
+
+ usleep(1000);
+ $loop->tick();
+ }
+
+ public function testAddPeriodicTimerCancelsItself()
+ {
+ $i = 0;
+
+ $loop = $this->createLoop();
+
+ $loop->addPeriodicTimer(0.001, function ($timer) use (&$i) {
+ $i++;
+
+ if ($i == 2) {
+ $timer->cancel();
+ }
+ });
+
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+ usleep(1000);
+ $loop->tick();
+
+ $this->assertSame(2, $i);
+ }
+
+ public function testIsTimerActive()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addPeriodicTimer(0.001, function () {});
+
+ $this->assertTrue($loop->isTimerActive($timer));
+
+ $timer->cancel();
+
+ $this->assertFalse($loop->isTimerActive($timer));
+ }
+
+ public function testMinimumIntervalOneMicrosecond()
+ {
+ $loop = $this->createLoop();
+
+ $timer = $loop->addTimer(0, function () {});
+
+ $this->assertEquals(0.000001, $timer->getInterval());
+ }
+}
diff --git a/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php b/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php
new file mode 100755
index 0000000..a7a6d00
--- /dev/null
+++ b/vendor/react/event-loop/tests/Timer/ExtEventTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('ext-event tests skipped because ext-event is not installed.');
+ }
+
+ return new ExtEventLoop();
+ }
+}
diff --git a/vendor/react/event-loop/tests/Timer/LibEvTimerTest.php b/vendor/react/event-loop/tests/Timer/LibEvTimerTest.php
new file mode 100755
index 0000000..73abe8e
--- /dev/null
+++ b/vendor/react/event-loop/tests/Timer/LibEvTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('libev tests skipped because ext-libev is not installed.');
+ }
+
+ return new LibEvLoop();
+ }
+}
diff --git a/vendor/react/event-loop/tests/Timer/LibEventTimerTest.php b/vendor/react/event-loop/tests/Timer/LibEventTimerTest.php
new file mode 100755
index 0000000..3db2035
--- /dev/null
+++ b/vendor/react/event-loop/tests/Timer/LibEventTimerTest.php
@@ -0,0 +1,17 @@
+markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
+ }
+
+ return new LibEventLoop();
+ }
+}
diff --git a/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php b/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php
new file mode 100755
index 0000000..cfe1d7d
--- /dev/null
+++ b/vendor/react/event-loop/tests/Timer/StreamSelectTimerTest.php
@@ -0,0 +1,13 @@
+getMockBuilder('React\EventLoop\LoopInterface')
+ ->getMock();
+
+ $timers = new Timers();
+ $timers->tick();
+
+ // simulate a bunch of processing on stream events,
+ // part of which schedules a future timer...
+ sleep(1);
+ $timers->add(new Timer($loop, 0.5, function () {
+ $this->fail("Timer shouldn't be called");
+ }));
+
+ $timers->tick();
+ }
+}
diff --git a/vendor/react/event-loop/tests/bootstrap.php b/vendor/react/event-loop/tests/bootstrap.php
new file mode 100755
index 0000000..d97d8b7
--- /dev/null
+++ b/vendor/react/event-loop/tests/bootstrap.php
@@ -0,0 +1,7 @@
+addPsr4('React\\Tests\\EventLoop\\', __DIR__);
diff --git a/vendor/react/event-loop/travis-init.sh b/vendor/react/event-loop/travis-init.sh
new file mode 100755
index 0000000..8745601
--- /dev/null
+++ b/vendor/react/event-loop/travis-init.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -e
+set -o pipefail
+
+if [[ "$TRAVIS_PHP_VERSION" != "hhvm" &&
+ "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then
+
+ # install 'event' PHP extension
+ echo "yes" | pecl install event
+
+ # install 'libevent' PHP extension (does not support php 7)
+ if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
+ "$TRAVIS_PHP_VERSION" != "7.1" ]]; then
+ curl http://pecl.php.net/get/libevent-0.1.0.tgz | tar -xz
+ pushd libevent-0.1.0
+ phpize
+ ./configure
+ make
+ make install
+ popd
+ echo "extension=libevent.so" >> "$(php -r 'echo php_ini_loaded_file();')"
+ fi
+
+ # install 'libev' PHP extension (does not support php 7)
+ if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
+ "$TRAVIS_PHP_VERSION" != "7.1" ]]; then
+ git clone --recursive https://github.com/m4rw3r/php-libev
+ pushd php-libev
+ phpize
+ ./configure --with-libev
+ make
+ make install
+ popd
+ echo "extension=libev.so" >> "$(php -r 'echo php_ini_loaded_file();')"
+ fi
+
+fi
diff --git a/vendor/react/promise-timer/.gitignore b/vendor/react/promise-timer/.gitignore
new file mode 100755
index 0000000..de4a392
--- /dev/null
+++ b/vendor/react/promise-timer/.gitignore
@@ -0,0 +1,2 @@
+/vendor
+/composer.lock
diff --git a/vendor/react/promise-timer/.travis.yml b/vendor/react/promise-timer/.travis.yml
new file mode 100755
index 0000000..a71864a
--- /dev/null
+++ b/vendor/react/promise-timer/.travis.yml
@@ -0,0 +1,26 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ allow_failures:
+ - php: hhvm
+
+install:
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
diff --git a/vendor/react/promise-timer/CHANGELOG.md b/vendor/react/promise-timer/CHANGELOG.md
new file mode 100755
index 0000000..e5f4ccd
--- /dev/null
+++ b/vendor/react/promise-timer/CHANGELOG.md
@@ -0,0 +1,63 @@
+# Changelog
+
+## 1.5.1 (2019-03-27)
+
+* Fix: Typo in readme
+ (#35 by @aak74)
+
+* Improvement: Only include functions file when functions aren't defined
+ (#36 by @Niko9911)
+
+## 1.5.0 (2018-06-13)
+
+* Feature: Improve memory consumption by cleaning up garbage references to pending promise without canceller.
+ (#34 by @clue)
+
+## 1.4.0 (2018-06-11)
+
+* Feature: Improve memory consumption by cleaning up garbage references.
+ (#33 by @clue)
+
+## 1.3.0 (2018-04-24)
+
+* Feature: Improve memory consumption by cleaning up unneeded references.
+ (#32 by @clue)
+
+## 1.2.1 (2017-12-22)
+
+* README improvements
+ (#28 by @jsor)
+
+* Improve test suite by adding forward compatiblity with PHPUnit 6 and
+ fix test suite forward compatibility with upcoming EventLoop releases
+ (#30 and #31 by @clue)
+
+## 1.2.0 (2017-08-08)
+
+* Feature: Only start timers if input Promise is still pending and
+ return a settled output promise if the input is already settled.
+ (#25 by @clue)
+
+* Feature: Cap minimum timer interval at 1µs across all versions
+ (#23 by @clue)
+
+* Feature: Forward compatibility with EventLoop v1.0 and v0.5
+ (#27 by @clue)
+
+* Improve test suite by adding PHPUnit to require-dev and
+ lock Travis distro so new defaults will not break the build
+ (#24 and #26 by @clue)
+
+## 1.1.1 (2016-12-27)
+
+* Improve test suite to use PSR-4 autoloader and proper namespaces.
+ (#21 by @clue)
+
+## 1.1.0 (2016-02-29)
+
+* Feature: Support promise cancellation for all timer primitives
+ (#18 by @clue)
+
+## 1.0.0 (2015-09-29)
+
+* First tagged release
diff --git a/vendor/react/promise-timer/LICENSE b/vendor/react/promise-timer/LICENSE
new file mode 100755
index 0000000..dc09d1e
--- /dev/null
+++ b/vendor/react/promise-timer/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/promise-timer/README.md b/vendor/react/promise-timer/README.md
new file mode 100755
index 0000000..419a127
--- /dev/null
+++ b/vendor/react/promise-timer/README.md
@@ -0,0 +1,372 @@
+# PromiseTimer
+
+[](https://travis-ci.org/reactphp/promise-timer)
+
+A trivial implementation of timeouts for `Promise`s, built on top of [ReactPHP](https://reactphp.org/).
+
+**Table of contents**
+
+* [Usage](#usage)
+ * [timeout()](#timeout)
+ * [Timeout cancellation](#timeout-cancellation)
+ * [Cancellation handler](#cancellation-handler)
+ * [Input cancellation](#input-cancellation)
+ * [Output cancellation](#output-cancellation)
+ * [Collections](#collections)
+ * [resolve()](#resolve)
+ * [Resolve cancellation](#resolve-cancellation)
+ * [reject()](#reject)
+ * [Reject cancellation](#reject-cancellation)
+ * [TimeoutException](#timeoutexception)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Usage
+
+This lightweight library consists only of a few simple functions.
+All functions reside under the `React\Promise\Timer` namespace.
+
+The below examples assume you use an import statement similar to this:
+
+```php
+use React\Promise\Timer;
+
+Timer\timeout(…);
+```
+
+Alternatively, you can also refer to them with their fully-qualified name:
+
+```php
+\React\Promise\Timer\timeout(…);
+```
+
+### timeout()
+
+The `timeout(PromiseInterface $promise, $time, LoopInterface $loop)` function
+can be used to *cancel* operations that take *too long*.
+You need to pass in an input `$promise` that represents a pending operation and timeout parameters.
+It returns a new `Promise` with the following resolution behavior:
+
+* If the input `$promise` resolves before `$time` seconds, resolve the resulting promise with its fulfillment value.
+* If the input `$promise` rejects before `$time` seconds, reject the resulting promise with its rejection value.
+* If the input `$promise` does not settle before `$time` seconds, *cancel* the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).
+
+Internally, the given `$time` value will be used to start a timer that will
+*cancel* the pending operation once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+If the input `$promise` is already settled, then the resulting promise will
+resolve or reject immediately without starting a timer at all.
+
+A common use case for handling only resolved values looks like this:
+
+```php
+$promise = accessSomeRemoteResource();
+Timer\timeout($promise, 10.0, $loop)->then(function ($value) {
+ // the operation finished within 10.0 seconds
+});
+```
+
+A more complete example could look like this:
+
+```php
+$promise = accessSomeRemoteResource();
+Timer\timeout($promise, 10.0, $loop)->then(
+ function ($value) {
+ // the operation finished within 10.0 seconds
+ },
+ function ($error) {
+ if ($error instanceof Timer\TimeoutException) {
+ // the operation has failed due to a timeout
+ } else {
+ // the input operation has failed due to some other error
+ }
+ }
+);
+```
+
+Or if you're using [react/promise v2.2.0](https://github.com/reactphp/promise) or up:
+
+```php
+Timer\timeout($promise, 10.0, $loop)
+ ->then(function ($value) {
+ // the operation finished within 10.0 seconds
+ })
+ ->otherwise(function (Timer\TimeoutException $error) {
+ // the operation has failed due to a timeout
+ })
+ ->otherwise(function ($error) {
+ // the input operation has failed due to some other error
+ })
+;
+```
+
+#### Timeout cancellation
+
+As discussed above, the [`timeout()`](#timeout) function will *cancel* the
+underlying operation if it takes *too long*.
+This means that you can be sure the resulting promise will then be rejected
+with a [`TimeoutException`](#timeoutexception).
+
+However, what happens to the underlying input `$promise` is a bit more tricky:
+Once the timer fires, we will try to call
+[`$promise->cancel()`](https://github.com/reactphp/promise#cancellablepromiseinterfacecancel)
+on the input `$promise` which in turn invokes its [cancellation handler](#cancellation-handler).
+
+This means that it's actually up the input `$promise` to handle
+[cancellation support](https://github.com/reactphp/promise#cancellablepromiseinterface).
+
+* A common use case involves cleaning up any resources like open network sockets or
+ file handles or terminating external processes or timers.
+
+* If the given input `$promise` does not support cancellation, then this is a NO-OP.
+ This means that while the resulting promise will still be rejected, the underlying
+ input `$promise` may still be pending and can hence continue consuming resources.
+
+See the following chapter for more details on the cancellation handler.
+
+#### Cancellation handler
+
+For example, an implementation for the above operation could look like this:
+
+```php
+function accessSomeRemoteResource()
+{
+ return new Promise(
+ function ($resolve, $reject) use (&$socket) {
+ // this will be called once the promise is created
+ // a common use case involves opening any resources and eventually resolving
+ $socket = createSocket();
+ $socket->on('data', function ($data) use ($resolve) {
+ $resolve($data);
+ });
+ },
+ function ($resolve, $reject) use (&$socket) {
+ // this will be called once calling `cancel()` on this promise
+ // a common use case involves cleaning any resources and then rejecting
+ $socket->close();
+ $reject(new \RuntimeException('Operation cancelled'));
+ }
+ );
+}
+```
+
+In this example, calling `$promise->cancel()` will invoke the registered cancellation
+handler which then closes the network socket and rejects the `Promise` instance.
+
+If no cancellation handler is passed to the `Promise` constructor, then invoking
+its `cancel()` method it is effectively a NO-OP.
+This means that it may still be pending and can hence continue consuming resources.
+
+For more details on the promise cancellation, please refer to the
+[Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface).
+
+#### Input cancellation
+
+Irrespective of the timeout handling, you can also explicitly `cancel()` the
+input `$promise` at any time.
+This means that the `timeout()` handling does not affect cancellation of the
+input `$promise`, as demonstrated in the following example:
+
+```php
+$promise = accessSomeRemoteResource();
+$timeout = Timer\timeout($promise, 10.0, $loop);
+
+$promise->cancel();
+```
+
+The registered [cancellation handler](#cancellation-handler) is responsible for
+handling the `cancel()` call:
+
+* A described above, a common use involves resource cleanup and will then *reject*
+ the `Promise`.
+ If the input `$promise` is being rejected, then the timeout will be aborted
+ and the resulting promise will also be rejected.
+* If the input `$promise` is still pending, then the timout will continue
+ running until the timer expires.
+ The same happens if the input `$promise` does not register a
+ [cancellation handler](#cancellation-handler).
+
+#### Output cancellation
+
+Similarily, you can also explicitly `cancel()` the resulting promise like this:
+
+```php
+$promise = accessSomeRemoteResource();
+$timeout = Timer\timeout($promise, 10.0, $loop);
+
+$timeout->cancel();
+```
+
+Note how this looks very similar to the above [input cancellation](#input-cancellation)
+example. Accordingly, it also behaves very similar.
+
+Calling `cancel()` on the resulting promise will merely try
+to `cancel()` the input `$promise`.
+This means that we do not take over responsibility of the outcome and it's
+entirely up to the input `$promise` to handle cancellation support.
+
+The registered [cancellation handler](#cancellation-handler) is responsible for
+handling the `cancel()` call:
+
+* As described above, a common use involves resource cleanup and will then *reject*
+ the `Promise`.
+ If the input `$promise` is being rejected, then the timeout will be aborted
+ and the resulting promise will also be rejected.
+* If the input `$promise` is still pending, then the timout will continue
+ running until the timer expires.
+ The same happens if the input `$promise` does not register a
+ [cancellation handler](#cancellation-handler).
+
+To re-iterate, note that calling `cancel()` on the resulting promise will merely
+try to cancel the input `$promise` only.
+It is then up to the cancellation handler of the input promise to settle the promise.
+If the input promise is still pending when the timeout occurs, then the normal
+[timeout cancellation](#timeout-cancellation) handling will trigger, effectively rejecting
+the output promise with a [`TimeoutException`](#timeoutexception).
+
+This is done for consistency with the [timeout cancellation](#timeout-cancellation)
+handling and also because it is assumed this is often used like this:
+
+```php
+$timeout = Timer\timeout(accessSomeRemoteResource(), 10.0, $loop);
+
+$timeout->cancel();
+```
+
+As described above, this example works as expected and cleans up any resources
+allocated for the input `$promise`.
+
+Note that if the given input `$promise` does not support cancellation, then this
+is a NO-OP.
+This means that while the resulting promise will still be rejected after the
+timeout, the underlying input `$promise` may still be pending and can hence
+continue consuming resources.
+
+#### Collections
+
+If you want to wait for multiple promises to resolve, you can use the normal promise primitives like this:
+
+```php
+$promises = array(
+ accessSomeRemoteResource(),
+ accessSomeRemoteResource(),
+ accessSomeRemoteResource()
+);
+
+$promise = \React\Promise\all($promises);
+
+Timer\timeout($promise, 10, $loop)->then(function ($values) {
+ // *all* promises resolved
+});
+```
+
+The applies to all promise collection primitives alike, i.e. `all()`, `race()`, `any()`, `some()` etc.
+
+For more details on the promise primitives, please refer to the
+[Promise documentation](https://github.com/reactphp/promise#functions).
+
+### resolve()
+
+The `resolve($time, LoopInterface $loop)` function can be used to create a new Promise that
+resolves in `$time` seconds with the `$time` as the fulfillment value.
+
+```php
+Timer\resolve(1.5, $loop)->then(function ($time) {
+ echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL;
+});
+```
+
+Internally, the given `$time` value will be used to start a timer that will
+resolve the promise once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+#### Resolve cancellation
+
+You can explicitly `cancel()` the resulting timer promise at any time:
+
+```php
+$timer = Timer\resolve(2.0, $loop);
+
+$timer->cancel();
+```
+
+This will abort the timer and *reject* with a `RuntimeException`.
+
+### reject()
+
+The `reject($time, LoopInterface $loop)` function can be used to create a new Promise
+which rejects in `$time` seconds with a `TimeoutException`.
+
+```php
+Timer\reject(2.0, $loop)->then(null, function (TimeoutException $e) {
+ echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL;
+});
+```
+
+Internally, the given `$time` value will be used to start a timer that will
+reject the promise once it triggers.
+This implies that if you pass a really small (or negative) value, it will still
+start a timer and will thus trigger at the earliest possible time in the future.
+
+This function complements the [`resolve()`](#resolve) function
+and can be used as a basic building block for higher-level promise consumers.
+
+#### Reject cancellation
+
+You can explicitly `cancel()` the resulting timer promise at any time:
+
+```php
+$timer = Timer\reject(2.0, $loop);
+
+$timer->cancel();
+```
+
+This will abort the timer and *reject* with a `RuntimeException`.
+
+### TimeoutException
+
+The `TimeoutException` extends PHP's built-in `RuntimeException`.
+
+The `getTimeout()` method can be used to get the timeout value in seconds.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/promise-timer:^1.5
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
+HHVM.
+It's *highly recommended to use PHP 7+* for this project.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/vendor/react/promise-timer/composer.json b/vendor/react/promise-timer/composer.json
new file mode 100755
index 0000000..1fdaddd
--- /dev/null
+++ b/vendor/react/promise-timer/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "react/promise-timer",
+ "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
+ "keywords": ["Promise", "timeout", "timer", "event-loop", "ReactPHP", "async"],
+ "homepage": "https://github.com/reactphp/promise-timer",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@lueck.tv"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "React\\Promise\\Timer\\": "src/" },
+ "files": [ "src/functions_include.php" ]
+ },
+ "autoload-dev": {
+ "psr-4": { "React\\Tests\\Promise\\Timer\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/promise": "^2.7.0 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/vendor/react/promise-timer/phpunit.xml.dist b/vendor/react/promise-timer/phpunit.xml.dist
new file mode 100755
index 0000000..bb79fba
--- /dev/null
+++ b/vendor/react/promise-timer/phpunit.xml.dist
@@ -0,0 +1,19 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
\ No newline at end of file
diff --git a/vendor/react/promise-timer/src/TimeoutException.php b/vendor/react/promise-timer/src/TimeoutException.php
new file mode 100755
index 0000000..18ea72f
--- /dev/null
+++ b/vendor/react/promise-timer/src/TimeoutException.php
@@ -0,0 +1,22 @@
+timeout = $timeout;
+ }
+
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+}
diff --git a/vendor/react/promise-timer/src/functions.php b/vendor/react/promise-timer/src/functions.php
new file mode 100755
index 0000000..123a905
--- /dev/null
+++ b/vendor/react/promise-timer/src/functions.php
@@ -0,0 +1,81 @@
+cancel();
+ $promise = null;
+ };
+ }
+
+ return new Promise(function ($resolve, $reject) use ($loop, $time, $promise) {
+ $timer = null;
+ $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $resolve($v);
+ }, function ($v) use (&$timer, $loop, $reject) {
+ if ($timer) {
+ $loop->cancelTimer($timer);
+ }
+ $timer = false;
+ $reject($v);
+ });
+
+ // promise already resolved => no need to start timer
+ if ($timer === false) {
+ return;
+ }
+
+ // start timeout timer which will cancel the input promise
+ $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject) {
+ $reject(new TimeoutException($time, 'Timed out after ' . $time . ' seconds'));
+
+ // try to invoke cancellation handler of input promise and then clean
+ // reference in order to avoid garbage references in call stack.
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ $promise = null;
+ });
+ }, $canceller);
+}
+
+function resolve($time, LoopInterface $loop)
+{
+ return new Promise(function ($resolve) use ($loop, $time, &$timer) {
+ // resolve the promise when the timer fires in $time seconds
+ $timer = $loop->addTimer($time, function () use ($time, $resolve) {
+ $resolve($time);
+ });
+ }, function () use (&$timer, $loop) {
+ // cancelling this promise will cancel the timer, clean the reference
+ // in order to avoid garbage references in call stack and then reject.
+ $loop->cancelTimer($timer);
+ $timer = null;
+
+ throw new \RuntimeException('Timer cancelled');
+ });
+}
+
+function reject($time, LoopInterface $loop)
+{
+ return resolve($time, $loop)->then(function ($time) {
+ throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds');
+ });
+}
diff --git a/vendor/react/promise-timer/src/functions_include.php b/vendor/react/promise-timer/src/functions_include.php
new file mode 100755
index 0000000..1d5673a
--- /dev/null
+++ b/vendor/react/promise-timer/src/functions_include.php
@@ -0,0 +1,7 @@
+loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseExpiredIsPendingWithoutRunningLoop()
+ {
+ $promise = Timer\reject(-1, $this->loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseWillBeRejectedOnTimeout()
+ {
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testPromiseExpiredWillBeRejectedOnTimeout()
+ {
+ $promise = Timer\reject(-1, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testCancellingPromiseWillRejectTimer()
+ {
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testWaitingForPromiseToRejectDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\reject(0.01, $this->loop);
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPromiseDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\reject(0.01, $this->loop);
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/vendor/react/promise-timer/tests/FunctionResolveTest.php b/vendor/react/promise-timer/tests/FunctionResolveTest.php
new file mode 100755
index 0000000..c4e2be7
--- /dev/null
+++ b/vendor/react/promise-timer/tests/FunctionResolveTest.php
@@ -0,0 +1,101 @@
+loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseExpiredIsPendingWithoutRunningLoop()
+ {
+ $promise = Timer\resolve(-1, $this->loop);
+
+ $this->expectPromisePending($promise);
+ }
+
+ public function testPromiseWillBeResolvedOnTimeout()
+ {
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testPromiseExpiredWillBeResolvedOnTimeout()
+ {
+ $promise = Timer\resolve(-1, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testWillStartLoopTimer()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->with($this->equalTo(0.01));
+
+ Timer\resolve(0.01, $loop);
+ }
+
+ public function testCancellingPromiseWillCancelLoopTimer()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $timer = $this->getMockBuilder(interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
+
+ $promise = Timer\resolve(0.01, $loop);
+
+ $loop->expects($this->once())->method('cancelTimer')->with($this->equalTo($timer));
+
+ $promise->cancel();
+ }
+
+ public function testCancellingPromiseWillRejectTimer()
+ {
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testWaitingForPromiseToResolveDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\resolve(0.01, $this->loop);
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPromiseDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\resolve(0.01, $this->loop);
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/vendor/react/promise-timer/tests/FunctionTimeoutTest.php b/vendor/react/promise-timer/tests/FunctionTimeoutTest.php
new file mode 100755
index 0000000..9652d1a
--- /dev/null
+++ b/vendor/react/promise-timer/tests/FunctionTimeoutTest.php
@@ -0,0 +1,286 @@
+loop);
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testResolvedExpiredWillResolveRightAway()
+ {
+ $promise = Promise\resolve();
+
+ $promise = Timer\timeout($promise, -1, $this->loop);
+
+ $this->expectPromiseResolved($promise);
+ }
+
+ public function testResolvedWillNotStartTimer()
+ {
+ $promise = Promise\resolve();
+
+ Timer\timeout($promise, 3, $this->loop);
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.5, $time);
+ }
+
+ public function testRejectedWillRejectRightAway()
+ {
+ $promise = Promise\reject();
+
+ $promise = Timer\timeout($promise, 3, $this->loop);
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testRejectedWillNotStartTimer()
+ {
+ $promise = Promise\reject();
+
+ Timer\timeout($promise, 3, $this->loop);
+
+ $time = microtime(true);
+ $this->loop->run();
+ $time = microtime(true) - $time;
+
+ $this->assertLessThan(0.5, $time);
+ }
+
+ public function testPendingWillRejectOnTimeout()
+ {
+ $promise = $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+
+ $this->expectPromiseRejected($promise);
+ }
+
+ public function testPendingCancellableWillBeCancelledThroughFollowerOnTimeout()
+ {
+ $cancellable = $this->getMockBuilder('React\Promise\CancellablePromiseInterface')->getMock();
+ $cancellable->expects($this->once())->method('cancel');
+
+ $promise = $this->getMockBuilder('React\Promise\CancellablePromiseInterface')->getMock();
+ $promise->expects($this->once())->method('then')->willReturn($cancellable);
+
+ Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ }
+
+ public function testCancelTimeoutWithoutCancellationhandlerWillNotCancelTimerAndWillNotReject()
+ {
+ $promise = new \React\Promise\Promise(function () { });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $timer = $this->getMockBuilder('React\EventLoop\Timer\TimerInterface')->getMock();
+ $loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
+ $loop->expects($this->never())->method('cancelTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $timeout->cancel();
+
+ $this->expectPromisePending($timeout);
+ }
+
+ public function testResolvedPromiseWillNotStartTimer()
+ {
+ $promise = new \React\Promise\Promise(function ($resolve) { $resolve(true); });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $this->expectPromiseResolved($timeout);
+ }
+
+ public function testRejectedPromiseWillNotStartTimer()
+ {
+ $promise = Promise\reject(new \RuntimeException());
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->never())->method('addTimer');
+
+ $timeout = Timer\timeout($promise, 0.01, $loop);
+
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillCancelGivenPromise()
+ {
+ $promise = new \React\Promise\Promise(function () { }, $this->expectCallableOnce());
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+ }
+
+ public function testCancelGivenPromiseWillReject()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $promise->cancel();
+
+ $this->expectPromiseRejected($promise);
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillRejectIfGivenPromiseWillReject()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $reject(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+
+ $this->expectPromiseRejected($promise);
+ $this->expectPromiseRejected($timeout);
+ }
+
+ public function testCancelTimeoutWillResolveIfGivenPromiseWillResolve()
+ {
+ $promise = new \React\Promise\Promise(function () { }, function ($resolve, $reject) { $resolve(); });
+
+ $timeout = Timer\timeout($promise, 0.01, $this->loop);
+
+ $timeout->cancel();
+
+ $this->expectPromiseResolved($promise);
+ $this->expectPromiseResolved($timeout);
+ }
+
+ public function testWaitingForPromiseToResolveBeforeTimeoutDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\resolve(0.01, $this->loop);
+
+ $promise = Timer\timeout($promise, 1.0, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToRejectBeforeTimeoutDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = Timer\reject(0.01, $this->loop);
+
+ $promise = Timer\timeout($promise, 1.0, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToTimeoutDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToTimeoutWithoutCancellerDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { });
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForPromiseToTimeoutWithNoOpCancellerDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { }, function () {
+ // no-op
+ });
+
+ $promise = Timer\timeout($promise, 0.01, $this->loop);
+
+ $this->loop->run();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPromiseDoesNotLeaveGarbageCycles()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ gc_collect_cycles();
+
+ $promise = new \React\Promise\Promise(function () { }, function () {
+ throw new \RuntimeException();
+ });
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $promise = Timer\timeout($promise, 0.01, $loop);
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+}
diff --git a/vendor/react/promise-timer/tests/TestCase.php b/vendor/react/promise-timer/tests/TestCase.php
new file mode 100755
index 0000000..9d8d49a
--- /dev/null
+++ b/vendor/react/promise-timer/tests/TestCase.php
@@ -0,0 +1,61 @@
+loop = Factory::create();
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ /**
+ * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react)
+ */
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Promise\Timer\CallableStub')->getMock();
+ }
+
+ protected function expectPromiseRejected($promise)
+ {
+ return $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ protected function expectPromiseResolved($promise)
+ {
+ return $promise->then($this->expectCallableOnce(), $this->expectCallableNever());
+ }
+
+ protected function expectPromisePending($promise)
+ {
+ return $promise->then($this->expectCallableNever(), $this->expectCallableNever());
+ }
+}
diff --git a/vendor/react/promise-timer/tests/TimeoutExceptionTest.php b/vendor/react/promise-timer/tests/TimeoutExceptionTest.php
new file mode 100755
index 0000000..e9bedd9
--- /dev/null
+++ b/vendor/react/promise-timer/tests/TimeoutExceptionTest.php
@@ -0,0 +1,15 @@
+assertEquals(10, $e->getTimeout());
+ }
+}
diff --git a/vendor/react/promise/.gitignore b/vendor/react/promise/.gitignore
new file mode 100755
index 0000000..5241c60
--- /dev/null
+++ b/vendor/react/promise/.gitignore
@@ -0,0 +1,5 @@
+composer.lock
+composer.phar
+phpunit.xml
+build/
+vendor/
diff --git a/vendor/react/promise/.travis.yml b/vendor/react/promise/.travis.yml
new file mode 100755
index 0000000..bcbe642
--- /dev/null
+++ b/vendor/react/promise/.travis.yml
@@ -0,0 +1,28 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - nightly # ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ allow_failures:
+ - php: hhvm
+ - php: nightly
+
+install:
+ - composer install
+
+script:
+ - ./vendor/bin/phpunit -v --coverage-text --coverage-clover=./build/logs/clover.xml
+
+after_script:
+ - if [ -f ./build/logs/clover.xml ]; then travis_retry composer require satooshi/php-coveralls --no-interaction --update-with-dependencies; fi
+ - if [ -f ./build/logs/clover.xml ]; then php vendor/bin/coveralls -v; fi
diff --git a/vendor/react/promise/CHANGELOG.md b/vendor/react/promise/CHANGELOG.md
new file mode 100755
index 0000000..11f007d
--- /dev/null
+++ b/vendor/react/promise/CHANGELOG.md
@@ -0,0 +1,135 @@
+CHANGELOG for 2.x
+=================
+
+* 2.7.1 (2018-01-07)
+
+ * Fix: file_exists warning when resolving with long strings.
+ (#130 by @sbesselsen)
+ * Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
+ (#133 by @WyriHaximus)
+
+* 2.7.0 (2018-06-13)
+
+ * Feature: Improve memory consumption for pending promises by using static internal callbacks without binding to self.
+ (#124 by @clue)
+
+* 2.6.0 (2018-06-11)
+
+ * Feature: Significantly improve memory consumption and performance by only passing resolver args
+ to resolver and canceller if callback requires them. Also use static callbacks without
+ binding to promise, clean up canceller function reference when they are no longer
+ needed and hide resolver and canceller references from call stack on PHP 7+.
+ (#113, #115, #116, #117, #118, #119 and #123 by @clue)
+
+ These changes combined mean that rejecting promises with an `Exception` should
+ no longer cause any internal circular references which could cause some unexpected
+ memory growth in previous versions. By explicitly avoiding and explicitly
+ cleaning up said references, we can avoid relying on PHP's circular garbage collector
+ to kick in which significantly improves performance when rejecting many promises.
+
+ * Mark legacy progress support / notification API as deprecated
+ (#112 by @clue)
+
+ * Recommend rejecting promises by throwing an exception
+ (#114 by @jsor)
+
+ * Improve documentation to properly instantiate LazyPromise
+ (#121 by @holtkamp)
+
+ * Follower cancellation propagation was originally planned for this release
+ but has been reverted for now and is planned for a future release.
+ (#99 by @jsor and #122 by @clue)
+
+* 2.5.1 (2017-03-25)
+
+ * Fix circular references when resolving with a promise which follows
+ itself (#94).
+
+* 2.5.0 (2016-12-22)
+
+ * Revert automatic cancellation of pending collection promises once the
+ output promise resolves. This was introduced in 42d86b7 (PR #36, released
+ in [v2.3.0](https://github.com/reactphp/promise/releases/tag/v2.3.0)) and
+ was both unintended and backward incompatible.
+
+ If you need automatic cancellation, you can use something like:
+
+ ```php
+ function allAndCancel(array $promises)
+ {
+ return \React\Promise\all($promises)
+ ->always(function() use ($promises) {
+ foreach ($promises as $promise) {
+ if ($promise instanceof \React\Promise\CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+ });
+ }
+ ```
+ * `all()` and `map()` functions now preserve the order of the array (#77).
+ * Fix circular references when resolving a promise with itself (#71).
+
+* 2.4.1 (2016-05-03)
+
+ * Fix `some()` not cancelling pending promises when too much input promises
+ reject (16ff799).
+
+* 2.4.0 (2016-03-31)
+
+ * Support foreign thenables in `resolve()`.
+ Any object that provides a `then()` method is now assimilated to a trusted
+ promise that follows the state of this thenable (#52).
+ * Fix `some()` and `any()` for input arrays containing not enough items
+ (#34).
+
+* 2.3.0 (2016-03-24)
+
+ * Allow cancellation of promises returned by functions working on promise
+ collections (#36).
+ * Handle `\Throwable` in the same way as `\Exception` (#51 by @joshdifabio).
+
+* 2.2.2 (2016-02-26)
+
+ * Fix cancellation handlers called multiple times (#47 by @clue).
+
+* 2.2.1 (2015-07-03)
+
+ * Fix stack error when resolving a promise in its own fulfillment or
+ rejection handlers.
+
+* 2.2.0 (2014-12-30)
+
+ * Introduce new `ExtendedPromiseInterface` implemented by all promises.
+ * Add new `done()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `otherwise()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `always()` method (part of the `ExtendedPromiseInterface`).
+ * Add new `progress()` method (part of the `ExtendedPromiseInterface`).
+ * Rename `Deferred::progress` to `Deferred::notify` to avoid confusion with
+ `ExtendedPromiseInterface::progress` (a `Deferred::progress` alias is
+ still available for backward compatibility)
+ * `resolve()` now always returns a `ExtendedPromiseInterface`.
+
+* 2.1.0 (2014-10-15)
+
+ * Introduce new `CancellablePromiseInterface` implemented by all promises.
+ * Add new `cancel()` method (part of the `CancellablePromiseInterface`).
+
+* 2.0.0 (2013-12-10)
+
+ New major release. The goal is to streamline the API and to make it more
+ compliant with other promise libraries and especially with the new upcoming
+ [ES6 promises specification](https://github.com/domenic/promises-unwrapping/).
+
+ * Add standalone Promise class.
+ * Add new `race()` function.
+ * BC break: Bump minimum PHP version to PHP 5.4.
+ * BC break: Remove `ResolverInterface` and `PromiseInterface` from
+ `Deferred`.
+ * BC break: Change signature of `PromiseInterface`.
+ * BC break: Remove `When` and `Util` classes and move static methods to
+ functions.
+ * BC break: `FulfilledPromise` and `RejectedPromise` now throw an exception
+ when initialized with a promise instead of a value/reason.
+ * BC break: `Deferred::resolve()` and `Deferred::reject()` no longer return
+ a promise.
diff --git a/vendor/react/promise/LICENSE b/vendor/react/promise/LICENSE
new file mode 100755
index 0000000..5919d20
--- /dev/null
+++ b/vendor/react/promise/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012-2016 Jan Sorgalla
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/react/promise/README.md b/vendor/react/promise/README.md
new file mode 100755
index 0000000..3685566
--- /dev/null
+++ b/vendor/react/promise/README.md
@@ -0,0 +1,870 @@
+Promise
+=======
+
+A lightweight implementation of
+[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+[](http://travis-ci.org/reactphp/promise)
+[](https://coveralls.io/github/reactphp/promise?branch=master)
+
+Table of Contents
+-----------------
+
+1. [Introduction](#introduction)
+2. [Concepts](#concepts)
+ * [Deferred](#deferred)
+ * [Promise](#promise-1)
+3. [API](#api)
+ * [Deferred](#deferred-1)
+ * [Deferred::promise()](#deferredpromise)
+ * [Deferred::resolve()](#deferredresolve)
+ * [Deferred::reject()](#deferredreject)
+ * [Deferred::notify()](#deferrednotify)
+ * [PromiseInterface](#promiseinterface)
+ * [PromiseInterface::then()](#promiseinterfacethen)
+ * [ExtendedPromiseInterface](#extendedpromiseinterface)
+ * [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
+ * [ExtendedPromiseInterface::otherwise()](#extendedpromiseinterfaceotherwise)
+ * [ExtendedPromiseInterface::always()](#extendedpromiseinterfacealways)
+ * [ExtendedPromiseInterface::progress()](#extendedpromiseinterfaceprogress)
+ * [CancellablePromiseInterface](#cancellablepromiseinterface)
+ * [CancellablePromiseInterface::cancel()](#cancellablepromiseinterfacecancel)
+ * [Promise](#promise-2)
+ * [FulfilledPromise](#fulfilledpromise)
+ * [RejectedPromise](#rejectedpromise)
+ * [LazyPromise](#lazypromise)
+ * [Functions](#functions)
+ * [resolve()](#resolve)
+ * [reject()](#reject)
+ * [all()](#all)
+ * [race()](#race)
+ * [any()](#any)
+ * [some()](#some)
+ * [map()](#map)
+ * [reduce()](#reduce)
+ * [PromisorInterface](#promisorinterface)
+4. [Examples](#examples)
+ * [How to use Deferred](#how-to-use-deferred)
+ * [How promise forwarding works](#how-promise-forwarding-works)
+ * [Resolution forwarding](#resolution-forwarding)
+ * [Rejection forwarding](#rejection-forwarding)
+ * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
+ * [Progress event forwarding](#progress-event-forwarding)
+ * [done() vs. then()](#done-vs-then)
+5. [Install](#install)
+6. [Credits](#credits)
+7. [License](#license)
+
+Introduction
+------------
+
+Promise is a library implementing
+[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+It also provides several other useful promise-related concepts, such as joining
+multiple promises and mapping and reducing collections of promises.
+
+If you've never heard about promises before,
+[read this first](https://gist.github.com/3889970).
+
+Concepts
+--------
+
+### Deferred
+
+A **Deferred** represents a computation or unit of work that may not have
+completed yet. Typically (but not always), that computation will be something
+that executes asynchronously and completes at some point in the future.
+
+### Promise
+
+While a deferred represents the computation itself, a **Promise** represents
+the result of that computation. Thus, each deferred has a promise that acts as
+a placeholder for its actual result.
+
+API
+---
+
+### Deferred
+
+A deferred represents an operation whose resolution is pending. It has separate
+promise and resolver parts.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$promise = $deferred->promise();
+
+$deferred->resolve(mixed $value = null);
+$deferred->reject(mixed $reason = null);
+$deferred->notify(mixed $update = null);
+```
+
+The `promise` method returns the promise of the deferred.
+
+The `resolve` and `reject` methods control the state of the deferred.
+
+The deprecated `notify` method is for progress notification.
+
+The constructor of the `Deferred` accepts an optional `$canceller` argument.
+See [Promise](#promise-2) for more information.
+
+#### Deferred::promise()
+
+```php
+$promise = $deferred->promise();
+```
+
+Returns the promise of the deferred, which you can hand out to others while
+keeping the authority to modify its state to yourself.
+
+#### Deferred::resolve()
+
+```php
+$deferred->resolve(mixed $value = null);
+```
+
+Resolves the promise returned by `promise()`. All consumers are notified by
+having `$onFulfilled` (which they registered via `$promise->then()`) called with
+`$value`.
+
+If `$value` itself is a promise, the promise will transition to the state of
+this promise once it is resolved.
+
+#### Deferred::reject()
+
+```php
+$deferred->reject(mixed $reason = null);
+```
+
+Rejects the promise returned by `promise()`, signalling that the deferred's
+computation failed.
+All consumers are notified by having `$onRejected` (which they registered via
+`$promise->then()`) called with `$reason`.
+
+If `$reason` itself is a promise, the promise will be rejected with the outcome
+of this promise regardless whether it fulfills or rejects.
+
+#### Deferred::notify()
+
+> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
+
+```php
+$deferred->notify(mixed $update = null);
+```
+
+Triggers progress notifications, to indicate to consumers that the computation
+is making progress toward its result.
+
+All consumers are notified by having `$onProgress` (which they registered via
+`$promise->then()`) called with `$update`.
+
+### PromiseInterface
+
+The promise interface provides the common interface for all promise
+implementations.
+
+A promise represents an eventual outcome, which is either fulfillment (success)
+and an associated value, or rejection (failure) and an associated reason.
+
+Once in the fulfilled or rejected state, a promise becomes immutable.
+Neither its state nor its result (or error) can be modified.
+
+#### Implementations
+
+* [Promise](#promise-2)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+#### PromiseInterface::then()
+
+```php
+$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+```
+
+Transforms a promise's value by applying a function to the promise's fulfillment
+or rejection value. Returns a new promise for the transformed result.
+
+The `then()` method registers new fulfilled, rejection and progress handlers
+with a promise (all parameters are optional):
+
+ * `$onFulfilled` will be invoked once the promise is fulfilled and passed
+ the result as the first argument.
+ * `$onRejected` will be invoked once the promise is rejected and passed the
+ reason as the first argument.
+ * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
+ triggers progress notifications and passed a single argument (whatever it
+ wants) to indicate progress.
+
+It returns a new promise that will fulfill with the return value of either
+`$onFulfilled` or `$onRejected`, whichever is called, or will reject with
+the thrown exception if either throws.
+
+A promise makes the following guarantees about handlers registered in
+the same call to `then()`:
+
+ 1. Only one of `$onFulfilled` or `$onRejected` will be called,
+ never both.
+ 2. `$onFulfilled` and `$onRejected` will never be called more
+ than once.
+ 3. `$onProgress` (deprecated) may be called multiple times.
+
+#### See also
+
+* [resolve()](#resolve) - Creating a resolved promise
+* [reject()](#reject) - Creating a rejected promise
+* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
+* [done() vs. then()](#done-vs-then)
+
+### ExtendedPromiseInterface
+
+The ExtendedPromiseInterface extends the PromiseInterface with useful shortcut
+and utility methods which are not part of the Promises/A specification.
+
+#### Implementations
+
+* [Promise](#promise-1)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+#### ExtendedPromiseInterface::done()
+
+```php
+$promise->done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
+```
+
+Consumes the promise's ultimate value if the promise fulfills, or handles the
+ultimate error.
+
+It will cause a fatal error if either `$onFulfilled` or `$onRejected` throw or
+return a rejected promise.
+
+Since the purpose of `done()` is consumption rather than transformation,
+`done()` always returns `null`.
+
+#### See also
+
+* [PromiseInterface::then()](#promiseinterfacethen)
+* [done() vs. then()](#done-vs-then)
+
+#### ExtendedPromiseInterface::otherwise()
+
+```php
+$promise->otherwise(callable $onRejected);
+```
+
+Registers a rejection handler for promise. It is a shortcut for:
+
+```php
+$promise->then(null, $onRejected);
+```
+
+Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
+only specific errors.
+
+```php
+$promise
+ ->otherwise(function (\RuntimeException $reason) {
+ // Only catch \RuntimeException instances
+ // All other types of errors will propagate automatically
+ })
+ ->otherwise(function ($reason) {
+ // Catch other errors
+ )};
+```
+
+#### ExtendedPromiseInterface::always()
+
+```php
+$newPromise = $promise->always(callable $onFulfilledOrRejected);
+```
+
+Allows you to execute "cleanup" type tasks in a promise chain.
+
+It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
+when the promise is either fulfilled or rejected.
+
+* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
+ `$newPromise` will fulfill with the same value as `$promise`.
+* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
+ rejected promise, `$newPromise` will reject with the thrown exception or
+ rejected promise's reason.
+* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
+ `$newPromise` will reject with the same reason as `$promise`.
+* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
+ rejected promise, `$newPromise` will reject with the thrown exception or
+ rejected promise's reason.
+
+`always()` behaves similarly to the synchronous finally statement. When combined
+with `otherwise()`, `always()` allows you to write code that is similar to the familiar
+synchronous catch/finally pair.
+
+Consider the following synchronous code:
+
+```php
+try {
+ return doSomething();
+} catch(\Exception $e) {
+ return handleError($e);
+} finally {
+ cleanup();
+}
+```
+
+Similar asynchronous code (with `doSomething()` that returns a promise) can be
+written:
+
+```php
+return doSomething()
+ ->otherwise('handleError')
+ ->always('cleanup');
+```
+
+#### ExtendedPromiseInterface::progress()
+
+> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
+
+```php
+$promise->progress(callable $onProgress);
+```
+
+Registers a handler for progress updates from promise. It is a shortcut for:
+
+```php
+$promise->then(null, null, $onProgress);
+```
+
+### CancellablePromiseInterface
+
+A cancellable promise provides a mechanism for consumers to notify the creator
+of the promise that they are not longer interested in the result of an
+operation.
+
+#### CancellablePromiseInterface::cancel()
+
+``` php
+$promise->cancel();
+```
+
+The `cancel()` method notifies the creator of the promise that there is no
+further interest in the results of the operation.
+
+Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
+a promise has no effect.
+
+#### Implementations
+
+* [Promise](#promise-1)
+* [FulfilledPromise](#fulfilledpromise)
+* [RejectedPromise](#rejectedpromise)
+* [LazyPromise](#lazypromise)
+
+### Promise
+
+Creates a promise whose state is controlled by the functions passed to
+`$resolver`.
+
+```php
+$resolver = function (callable $resolve, callable $reject, callable $notify) {
+ // Do some work, possibly asynchronously, and then
+ // resolve or reject. You can notify of progress events (deprecated)
+ // along the way if you want/need.
+
+ $resolve($awesomeResult);
+ // or throw new Exception('Promise rejected');
+ // or $resolve($anotherPromise);
+ // or $reject($nastyError);
+ // or $notify($progressNotification);
+};
+
+$canceller = function () {
+ // Cancel/abort any running operations like network connections, streams etc.
+
+ // Reject promise by throwing an exception
+ throw new Exception('Promise cancelled');
+};
+
+$promise = new React\Promise\Promise($resolver, $canceller);
+```
+
+The promise constructor receives a resolver function and an optional canceller
+function which both will be called with 3 arguments:
+
+ * `$resolve($value)` - Primary function that seals the fate of the
+ returned promise. Accepts either a non-promise value, or another promise.
+ When called with a non-promise value, fulfills promise with that value.
+ When called with another promise, e.g. `$resolve($otherPromise)`, promise's
+ fate will be equivalent to that of `$otherPromise`.
+ * `$reject($reason)` - Function that rejects the promise. It is recommended to
+ just throw an exception instead of using `$reject()`.
+ * `$notify($update)` - Deprecated function that issues progress events for the promise.
+
+If the resolver or canceller throw an exception, the promise will be rejected
+with that thrown exception as the rejection reason.
+
+The resolver function will be called immediately, the canceller function only
+once all consumers called the `cancel()` method of the promise.
+
+### FulfilledPromise
+
+Creates a already fulfilled promise.
+
+```php
+$promise = React\Promise\FulfilledPromise($value);
+```
+
+Note, that `$value` **cannot** be a promise. It's recommended to use
+[resolve()](#resolve) for creating resolved promises.
+
+### RejectedPromise
+
+Creates a already rejected promise.
+
+```php
+$promise = React\Promise\RejectedPromise($reason);
+```
+
+Note, that `$reason` **cannot** be a promise. It's recommended to use
+[reject()](#reject) for creating rejected promises.
+
+### LazyPromise
+
+Creates a promise which will be lazily initialized by `$factory` once a consumer
+calls the `then()` method.
+
+```php
+$factory = function () {
+ $deferred = new React\Promise\Deferred();
+
+ // Do some heavy stuff here and resolve the deferred once completed
+
+ return $deferred->promise();
+};
+
+$promise = new React\Promise\LazyPromise($factory);
+
+// $factory will only be executed once we call then()
+$promise->then(function ($value) {
+});
+```
+
+### Functions
+
+Useful functions for creating, joining, mapping and reducing collections of
+promises.
+
+All functions working on promise collections (like `all()`, `race()`, `some()`
+etc.) support cancellation. This means, if you call `cancel()` on the returned
+promise, all promises in the collection are cancelled. If the collection itself
+is a promise which resolves to an array, this promise is also cancelled.
+
+#### resolve()
+
+```php
+$promise = React\Promise\resolve(mixed $promiseOrValue);
+```
+
+Creates a promise for the supplied `$promiseOrValue`.
+
+If `$promiseOrValue` is a value, it will be the resolution value of the
+returned promise.
+
+If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
+a trusted promise that follows the state of the thenable is returned.
+
+If `$promiseOrValue` is a promise, it will be returned as is.
+
+Note: The promise returned is always a promise implementing
+[ExtendedPromiseInterface](#extendedpromiseinterface). If you pass in a custom
+promise which only implements [PromiseInterface](#promiseinterface), this
+promise will be assimilated to a extended promise following `$promiseOrValue`.
+
+#### reject()
+
+```php
+$promise = React\Promise\reject(mixed $promiseOrValue);
+```
+
+Creates a rejected promise for the supplied `$promiseOrValue`.
+
+If `$promiseOrValue` is a value, it will be the rejection value of the
+returned promise.
+
+If `$promiseOrValue` is a promise, its completion value will be the rejected
+value of the returned promise.
+
+This can be useful in situations where you need to reject a promise without
+throwing an exception. For example, it allows you to propagate a rejection with
+the value of another promise.
+
+#### all()
+
+```php
+$promise = React\Promise\all(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Returns a promise that will resolve only once all the items in
+`$promisesOrValues` have resolved. The resolution value of the returned promise
+will be an array containing the resolution values of each of the items in
+`$promisesOrValues`.
+
+#### race()
+
+```php
+$promise = React\Promise\race(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Initiates a competitive race that allows one winner. Returns a promise which is
+resolved in the same way the first settled promise resolves.
+
+#### any()
+
+```php
+$promise = React\Promise\any(array|React\Promise\PromiseInterface $promisesOrValues);
+```
+
+Returns a promise that will resolve when any one of the items in
+`$promisesOrValues` resolves. The resolution value of the returned promise
+will be the resolution value of the triggering item.
+
+The returned promise will only reject if *all* items in `$promisesOrValues` are
+rejected. The rejection value will be an array of all rejection reasons.
+
+The returned promise will also reject with a `React\Promise\Exception\LengthException`
+if `$promisesOrValues` contains 0 items.
+
+#### some()
+
+```php
+$promise = React\Promise\some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany);
+```
+
+Returns a promise that will resolve when `$howMany` of the supplied items in
+`$promisesOrValues` resolve. The resolution value of the returned promise
+will be an array of length `$howMany` containing the resolution values of the
+triggering items.
+
+The returned promise will reject if it becomes impossible for `$howMany` items
+to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
+reject). The rejection value will be an array of
+`(count($promisesOrValues) - $howMany) + 1` rejection reasons.
+
+The returned promise will also reject with a `React\Promise\Exception\LengthException`
+if `$promisesOrValues` contains less items than `$howMany`.
+
+#### map()
+
+```php
+$promise = React\Promise\map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc);
+```
+
+Traditional map function, similar to `array_map()`, but allows input to contain
+promises and/or values, and `$mapFunc` may return either a value or a promise.
+
+The map function receives each item as argument, where item is a fully resolved
+value of a promise or value in `$promisesOrValues`.
+
+#### reduce()
+
+```php
+$promise = React\Promise\reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null);
+```
+
+Traditional reduce function, similar to `array_reduce()`, but input may contain
+promises and/or values, and `$reduceFunc` may return either a value or a
+promise, *and* `$initialValue` may be a promise or a value for the starting
+value.
+
+### PromisorInterface
+
+The `React\Promise\PromisorInterface` provides a common interface for objects
+that provide a promise. `React\Promise\Deferred` implements it, but since it
+is part of the public API anyone can implement it.
+
+Examples
+--------
+
+### How to use Deferred
+
+```php
+function getAwesomeResultPromise()
+{
+ $deferred = new React\Promise\Deferred();
+
+ // Execute a Node.js-style function using the callback pattern
+ computeAwesomeResultAsynchronously(function ($error, $result) use ($deferred) {
+ if ($error) {
+ $deferred->reject($error);
+ } else {
+ $deferred->resolve($result);
+ }
+ });
+
+ // Return the promise
+ return $deferred->promise();
+}
+
+getAwesomeResultPromise()
+ ->then(
+ function ($value) {
+ // Deferred resolved, do something with $value
+ },
+ function ($reason) {
+ // Deferred rejected, do something with $reason
+ },
+ function ($update) {
+ // Progress notification triggered, do something with $update
+ }
+ );
+```
+
+### How promise forwarding works
+
+A few simple examples to show how the mechanics of Promises/A forwarding works.
+These examples are contrived, of course, and in real usage, promise chains will
+typically be spread across several function calls, or even several levels of
+your application architecture.
+
+#### Resolution forwarding
+
+Resolved promises forward resolution values to the next promise.
+The first promise, `$deferred->promise()`, will resolve with the value passed
+to `$deferred->resolve()` below.
+
+Each call to `then()` returns a new promise that will resolve with the return
+value of the previous handler. This creates a promise "pipeline".
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ // $x will be the value passed to $deferred->resolve() below
+ // and returns a *new promise* for $x + 1
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 2
+ // This handler receives the return value of the
+ // previous handler.
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 3
+ // This handler receives the return value of the
+ // previous handler.
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ // $x === 4
+ // This handler receives the return value of the
+ // previous handler.
+ echo 'Resolve ' . $x;
+ });
+
+$deferred->resolve(1); // Prints "Resolve 4"
+```
+
+#### Rejection forwarding
+
+Rejected promises behave similarly, and also work similarly to try/catch:
+When you catch an exception, you must rethrow for it to propagate.
+
+Similarly, when you handle a rejected promise, to propagate the rejection,
+"rethrow" it by either returning a rejected promise, or actually throwing
+(since promise translates thrown exceptions into rejections)
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ throw new \Exception($x + 1);
+ })
+ ->otherwise(function (\Exception $x) {
+ // Propagate the rejection
+ throw $x;
+ })
+ ->otherwise(function (\Exception $x) {
+ // Can also propagate by returning another rejection
+ return React\Promise\reject(
+ new \Exception($x->getMessage() + 1)
+ );
+ })
+ ->otherwise(function ($x) {
+ echo 'Reject ' . $x->getMessage(); // 3
+ });
+
+$deferred->resolve(1); // Prints "Reject 3"
+```
+
+#### Mixed resolution and rejection forwarding
+
+Just like try/catch, you can choose to propagate or not. Mixing resolutions and
+rejections will still forward handler results in a predictable way.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->then(function ($x) {
+ return $x + 1;
+ })
+ ->then(function ($x) {
+ throw new \Exception($x + 1);
+ })
+ ->otherwise(function (\Exception $x) {
+ // Handle the rejection, and don't propagate.
+ // This is like catch without a rethrow
+ return $x->getMessage() + 1;
+ })
+ ->then(function ($x) {
+ echo 'Mixed ' . $x; // 4
+ });
+
+$deferred->resolve(1); // Prints "Mixed 4"
+```
+
+#### Progress event forwarding
+
+> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
+
+In the same way as resolution and rejection handlers, your progress handler
+**MUST** return a progress event to be propagated to the next link in the chain.
+If you return nothing, `null` will be propagated.
+
+Also in the same way as resolutions and rejections, if you don't register a
+progress handler, the update will be propagated through.
+
+If your progress handler throws an exception, the exception will be propagated
+to the next link in the chain. The best thing to do is to ensure your progress
+handlers do not throw exceptions.
+
+This gives you the opportunity to transform progress events at each step in the
+chain so that they are meaningful to the next step. It also allows you to choose
+not to transform them, and simply let them propagate untransformed, by not
+registering a progress handler.
+
+```php
+$deferred = new React\Promise\Deferred();
+
+$deferred->promise()
+ ->progress(function ($update) {
+ return $update + 1;
+ })
+ ->progress(function ($update) {
+ echo 'Progress ' . $update; // 2
+ });
+
+$deferred->notify(1); // Prints "Progress 2"
+```
+
+### done() vs. then()
+
+The golden rule is:
+
+ Either return your promise, or call done() on it.
+
+At a first glance, `then()` and `done()` seem very similar. However, there are
+important distinctions.
+
+The intent of `then()` is to transform a promise's value and to pass or return
+a new promise for the transformed value along to other parts of your code.
+
+The intent of `done()` is to consume a promise's value, transferring
+responsibility for the value to your code.
+
+In addition to transforming a value, `then()` allows you to recover from, or
+propagate intermediate errors. Any errors that are not handled will be caught
+by the promise machinery and used to reject the promise returned by `then()`.
+
+Calling `done()` transfers all responsibility for errors to your code. If an
+error (either a thrown exception or returned rejection) escapes the
+`$onFulfilled` or `$onRejected` callbacks you provide to done, it will be
+rethrown in an uncatchable way causing a fatal error.
+
+```php
+function getJsonResult()
+{
+ return queryApi()
+ ->then(
+ // Transform API results to an object
+ function ($jsonResultString) {
+ return json_decode($jsonResultString);
+ },
+ // Transform API errors to an exception
+ function ($jsonErrorString) {
+ $object = json_decode($jsonErrorString);
+ throw new ApiErrorException($object->errorMessage);
+ }
+ );
+}
+
+// Here we provide no rejection handler. If the promise returned has been
+// rejected, the ApiErrorException will be thrown
+getJsonResult()
+ ->done(
+ // Consume transformed object
+ function ($jsonResultObject) {
+ // Do something with $jsonResultObject
+ }
+ );
+
+// Here we provide a rejection handler which will either throw while debugging
+// or log the exception
+getJsonResult()
+ ->done(
+ function ($jsonResultObject) {
+ // Do something with $jsonResultObject
+ },
+ function (ApiErrorException $exception) {
+ if (isDebug()) {
+ throw $exception;
+ } else {
+ logException($exception);
+ }
+ }
+ );
+```
+
+Note that if a rejection value is not an instance of `\Exception`, it will be
+wrapped in an exception of the type `React\Promise\UnhandledRejectionException`.
+
+You can get the original rejection reason by calling `$exception->getReason()`.
+
+Install
+-------
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/promise:^2.7
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.4 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project due to its vast
+performance improvements.
+
+Credits
+-------
+
+Promise is a port of [when.js](https://github.com/cujojs/when)
+by [Brian Cavalier](https://github.com/briancavalier).
+
+Also, large parts of the documentation have been ported from the when.js
+[Wiki](https://github.com/cujojs/when/wiki) and the
+[API docs](https://github.com/cujojs/when/blob/master/docs/api.md).
+
+License
+-------
+
+Released under the [MIT](LICENSE) license.
diff --git a/vendor/react/promise/composer.json b/vendor/react/promise/composer.json
new file mode 100755
index 0000000..2fc4809
--- /dev/null
+++ b/vendor/react/promise/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "react/promise",
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "license": "MIT",
+ "authors": [
+ {"name": "Jan Sorgalla", "email": "jsorgalla@gmail.com"}
+ ],
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Promise\\": "tests/fixtures"
+ }
+ },
+ "keywords": [
+ "promise",
+ "promises"
+ ]
+}
diff --git a/vendor/react/promise/phpunit.xml.dist b/vendor/react/promise/phpunit.xml.dist
new file mode 100755
index 0000000..b9a689d
--- /dev/null
+++ b/vendor/react/promise/phpunit.xml.dist
@@ -0,0 +1,28 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+ ./src/functions_include.php
+
+
+
+
diff --git a/vendor/react/promise/src/CancellablePromiseInterface.php b/vendor/react/promise/src/CancellablePromiseInterface.php
new file mode 100755
index 0000000..896db2d
--- /dev/null
+++ b/vendor/react/promise/src/CancellablePromiseInterface.php
@@ -0,0 +1,11 @@
+started) {
+ return;
+ }
+
+ $this->started = true;
+ $this->drain();
+ }
+
+ public function enqueue($cancellable)
+ {
+ if (!\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
+ return;
+ }
+
+ $length = \array_push($this->queue, $cancellable);
+
+ if ($this->started && 1 === $length) {
+ $this->drain();
+ }
+ }
+
+ private function drain()
+ {
+ for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
+ $cancellable = $this->queue[$i];
+
+ $exception = null;
+
+ try {
+ $cancellable->cancel();
+ } catch (\Throwable $exception) {
+ } catch (\Exception $exception) {
+ }
+
+ unset($this->queue[$i]);
+
+ if ($exception) {
+ throw $exception;
+ }
+ }
+
+ $this->queue = [];
+ }
+}
diff --git a/vendor/react/promise/src/Deferred.php b/vendor/react/promise/src/Deferred.php
new file mode 100755
index 0000000..3ca034b
--- /dev/null
+++ b/vendor/react/promise/src/Deferred.php
@@ -0,0 +1,65 @@
+canceller = $canceller;
+ }
+
+ public function promise()
+ {
+ if (null === $this->promise) {
+ $this->promise = new Promise(function ($resolve, $reject, $notify) {
+ $this->resolveCallback = $resolve;
+ $this->rejectCallback = $reject;
+ $this->notifyCallback = $notify;
+ }, $this->canceller);
+ $this->canceller = null;
+ }
+
+ return $this->promise;
+ }
+
+ public function resolve($value = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->resolveCallback, $value);
+ }
+
+ public function reject($reason = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->rejectCallback, $reason);
+ }
+
+ /**
+ * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
+ * @param mixed $update
+ */
+ public function notify($update = null)
+ {
+ $this->promise();
+
+ \call_user_func($this->notifyCallback, $update);
+ }
+
+ /**
+ * @deprecated 2.2.0
+ * @see Deferred::notify()
+ */
+ public function progress($update = null)
+ {
+ $this->notify($update);
+ }
+}
diff --git a/vendor/react/promise/src/Exception/LengthException.php b/vendor/react/promise/src/Exception/LengthException.php
new file mode 100755
index 0000000..775c48d
--- /dev/null
+++ b/vendor/react/promise/src/Exception/LengthException.php
@@ -0,0 +1,7 @@
+value = $value;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return $this;
+ }
+
+ try {
+ return resolve($onFulfilled($this->value));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onFulfilled) {
+ return;
+ }
+
+ $result = $onFulfilled($this->value);
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this;
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/vendor/react/promise/src/LazyPromise.php b/vendor/react/promise/src/LazyPromise.php
new file mode 100755
index 0000000..7546524
--- /dev/null
+++ b/vendor/react/promise/src/LazyPromise.php
@@ -0,0 +1,63 @@
+factory = $factory;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return $this->promise()->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->promise()->otherwise($onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->promise()->always($onFulfilledOrRejected);
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->promise()->progress($onProgress);
+ }
+
+ public function cancel()
+ {
+ return $this->promise()->cancel();
+ }
+
+ /**
+ * @internal
+ * @see Promise::settle()
+ */
+ public function promise()
+ {
+ if (null === $this->promise) {
+ try {
+ $this->promise = resolve(\call_user_func($this->factory));
+ } catch (\Throwable $exception) {
+ $this->promise = new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ $this->promise = new RejectedPromise($exception);
+ }
+ }
+
+ return $this->promise;
+ }
+}
diff --git a/vendor/react/promise/src/Promise.php b/vendor/react/promise/src/Promise.php
new file mode 100755
index 0000000..33759e6
--- /dev/null
+++ b/vendor/react/promise/src/Promise.php
@@ -0,0 +1,256 @@
+canceller = $canceller;
+
+ // Explicitly overwrite arguments with null values before invoking
+ // resolver function. This ensure that these arguments do not show up
+ // in the stack trace in PHP 7+ only.
+ $cb = $resolver;
+ $resolver = $canceller = null;
+ $this->call($cb);
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ if (null === $this->canceller) {
+ return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
+ }
+
+ // This promise has a canceller, so we create a new child promise which
+ // has a canceller that invokes the parent canceller if all other
+ // followers are also cancelled. We keep a reference to this promise
+ // instance for the static canceller function and clear this to avoid
+ // keeping a cyclic reference between parent and follower.
+ $parent = $this;
+ ++$parent->requiredCancelRequests;
+
+ return new static(
+ $this->resolver($onFulfilled, $onRejected, $onProgress),
+ static function () use (&$parent) {
+ if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
+ $parent->cancel();
+ }
+
+ $parent = null;
+ }
+ );
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null !== $this->result) {
+ return $this->result->done($onFulfilled, $onRejected, $onProgress);
+ }
+
+ $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
+ $promise
+ ->done($onFulfilled, $onRejected);
+ };
+
+ if ($onProgress) {
+ $this->progressHandlers[] = $onProgress;
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ return $this->then(null, static function ($reason) use ($onRejected) {
+ if (!_checkTypehint($onRejected, $reason)) {
+ return new RejectedPromise($reason);
+ }
+
+ return $onRejected($reason);
+ });
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(static function ($value) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+ return $value;
+ });
+ }, static function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this->then(null, null, $onProgress);
+ }
+
+ public function cancel()
+ {
+ if (null === $this->canceller || null !== $this->result) {
+ return;
+ }
+
+ $canceller = $this->canceller;
+ $this->canceller = null;
+
+ $this->call($canceller);
+ }
+
+ private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
+ if ($onProgress) {
+ $progressHandler = static function ($update) use ($notify, $onProgress) {
+ try {
+ $notify($onProgress($update));
+ } catch (\Throwable $e) {
+ $notify($e);
+ } catch (\Exception $e) {
+ $notify($e);
+ }
+ };
+ } else {
+ $progressHandler = $notify;
+ }
+
+ $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
+ $promise
+ ->then($onFulfilled, $onRejected)
+ ->done($resolve, $reject, $progressHandler);
+ };
+
+ $this->progressHandlers[] = $progressHandler;
+ };
+ }
+
+ private function reject($reason = null)
+ {
+ if (null !== $this->result) {
+ return;
+ }
+
+ $this->settle(reject($reason));
+ }
+
+ private function settle(ExtendedPromiseInterface $promise)
+ {
+ $promise = $this->unwrap($promise);
+
+ if ($promise === $this) {
+ $promise = new RejectedPromise(
+ new \LogicException('Cannot resolve a promise with itself.')
+ );
+ }
+
+ $handlers = $this->handlers;
+
+ $this->progressHandlers = $this->handlers = [];
+ $this->result = $promise;
+ $this->canceller = null;
+
+ foreach ($handlers as $handler) {
+ $handler($promise);
+ }
+ }
+
+ private function unwrap($promise)
+ {
+ $promise = $this->extract($promise);
+
+ while ($promise instanceof self && null !== $promise->result) {
+ $promise = $this->extract($promise->result);
+ }
+
+ return $promise;
+ }
+
+ private function extract($promise)
+ {
+ if ($promise instanceof LazyPromise) {
+ $promise = $promise->promise();
+ }
+
+ return $promise;
+ }
+
+ private function call(callable $cb)
+ {
+ // Explicitly overwrite argument with null value. This ensure that this
+ // argument does not show up in the stack trace in PHP 7+ only.
+ $callback = $cb;
+ $cb = null;
+
+ // Use reflection to inspect number of arguments expected by this callback.
+ // We did some careful benchmarking here: Using reflection to avoid unneeded
+ // function arguments is actually faster than blindly passing them.
+ // Also, this helps avoiding unnecessary function arguments in the call stack
+ // if the callback creates an Exception (creating garbage cycles).
+ if (\is_array($callback)) {
+ $ref = new \ReflectionMethod($callback[0], $callback[1]);
+ } elseif (\is_object($callback) && !$callback instanceof \Closure) {
+ $ref = new \ReflectionMethod($callback, '__invoke');
+ } else {
+ $ref = new \ReflectionFunction($callback);
+ }
+ $args = $ref->getNumberOfParameters();
+
+ try {
+ if ($args === 0) {
+ $callback();
+ } else {
+ // Keep references to this promise instance for the static resolve/reject functions.
+ // By using static callbacks that are not bound to this instance
+ // and passing the target promise instance by reference, we can
+ // still execute its resolving logic and still clear this
+ // reference when settling the promise. This helps avoiding
+ // garbage cycles if any callback creates an Exception.
+ // These assumptions are covered by the test suite, so if you ever feel like
+ // refactoring this, go ahead, any alternative suggestions are welcome!
+ $target =& $this;
+ $progressHandlers =& $this->progressHandlers;
+
+ $callback(
+ static function ($value = null) use (&$target) {
+ if ($target !== null) {
+ $target->settle(resolve($value));
+ $target = null;
+ }
+ },
+ static function ($reason = null) use (&$target) {
+ if ($target !== null) {
+ $target->reject($reason);
+ $target = null;
+ }
+ },
+ static function ($update = null) use (&$progressHandlers) {
+ foreach ($progressHandlers as $handler) {
+ $handler($update);
+ }
+ }
+ );
+ }
+ } catch (\Throwable $e) {
+ $target = null;
+ $this->reject($e);
+ } catch (\Exception $e) {
+ $target = null;
+ $this->reject($e);
+ }
+ }
+}
diff --git a/vendor/react/promise/src/PromiseInterface.php b/vendor/react/promise/src/PromiseInterface.php
new file mode 100755
index 0000000..fcd763d
--- /dev/null
+++ b/vendor/react/promise/src/PromiseInterface.php
@@ -0,0 +1,14 @@
+reason = $reason;
+ }
+
+ public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ return $this;
+ }
+
+ try {
+ return resolve($onRejected($this->reason));
+ } catch (\Throwable $exception) {
+ return new RejectedPromise($exception);
+ } catch (\Exception $exception) {
+ return new RejectedPromise($exception);
+ }
+ }
+
+ public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
+ {
+ if (null === $onRejected) {
+ throw UnhandledRejectionException::resolve($this->reason);
+ }
+
+ $result = $onRejected($this->reason);
+
+ if ($result instanceof self) {
+ throw UnhandledRejectionException::resolve($result->reason);
+ }
+
+ if ($result instanceof ExtendedPromiseInterface) {
+ $result->done();
+ }
+ }
+
+ public function otherwise(callable $onRejected)
+ {
+ if (!_checkTypehint($onRejected, $this->reason)) {
+ return $this;
+ }
+
+ return $this->then(null, $onRejected);
+ }
+
+ public function always(callable $onFulfilledOrRejected)
+ {
+ return $this->then(null, function ($reason) use ($onFulfilledOrRejected) {
+ return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
+ return new RejectedPromise($reason);
+ });
+ });
+ }
+
+ public function progress(callable $onProgress)
+ {
+ return $this;
+ }
+
+ public function cancel()
+ {
+ }
+}
diff --git a/vendor/react/promise/src/UnhandledRejectionException.php b/vendor/react/promise/src/UnhandledRejectionException.php
new file mode 100755
index 0000000..e7fe2f7
--- /dev/null
+++ b/vendor/react/promise/src/UnhandledRejectionException.php
@@ -0,0 +1,31 @@
+reason = $reason;
+
+ $message = \sprintf('Unhandled Rejection: %s', \json_encode($reason));
+
+ parent::__construct($message, 0);
+ }
+
+ public function getReason()
+ {
+ return $this->reason;
+ }
+}
diff --git a/vendor/react/promise/src/functions.php b/vendor/react/promise/src/functions.php
new file mode 100755
index 0000000..c549e4e
--- /dev/null
+++ b/vendor/react/promise/src/functions.php
@@ -0,0 +1,246 @@
+then($resolve, $reject, $notify);
+ }, $canceller);
+ }
+
+ return new FulfilledPromise($promiseOrValue);
+}
+
+function reject($promiseOrValue = null)
+{
+ if ($promiseOrValue instanceof PromiseInterface) {
+ return resolve($promiseOrValue)->then(function ($value) {
+ return new RejectedPromise($value);
+ });
+ }
+
+ return new RejectedPromise($promiseOrValue);
+}
+
+function all($promisesOrValues)
+{
+ return map($promisesOrValues, function ($val) {
+ return $val;
+ });
+}
+
+function race($promisesOrValues)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) {
+ if (!is_array($array) || !$array) {
+ $resolve();
+ return;
+ }
+
+ foreach ($array as $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($resolve, $reject, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function any($promisesOrValues)
+{
+ return some($promisesOrValues, 1)
+ ->then(function ($val) {
+ return \array_shift($val);
+ });
+}
+
+function some($promisesOrValues, $howMany)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array) || $howMany < 1) {
+ $resolve([]);
+ return;
+ }
+
+ $len = \count($array);
+
+ if ($len < $howMany) {
+ throw new Exception\LengthException(
+ \sprintf(
+ 'Input array must contain at least %d item%s but contains only %s item%s.',
+ $howMany,
+ 1 === $howMany ? '' : 's',
+ $len,
+ 1 === $len ? '' : 's'
+ )
+ );
+ }
+
+ $toResolve = $howMany;
+ $toReject = ($len - $toResolve) + 1;
+ $values = [];
+ $reasons = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $values[$i] = $val;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ };
+
+ $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
+ if ($toResolve < 1 || $toReject < 1) {
+ return;
+ }
+
+ $reasons[$i] = $reason;
+
+ if (0 === --$toReject) {
+ $reject($reasons);
+ }
+ };
+
+ $cancellationQueue->enqueue($promiseOrValue);
+
+ resolve($promiseOrValue)
+ ->done($fulfiller, $rejecter, $notify);
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function map($promisesOrValues, callable $mapFunc)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array) || !$array) {
+ $resolve([]);
+ return;
+ }
+
+ $toResolve = \count($array);
+ $values = [];
+
+ foreach ($array as $i => $promiseOrValue) {
+ $cancellationQueue->enqueue($promiseOrValue);
+ $values[$i] = null;
+
+ resolve($promiseOrValue)
+ ->then($mapFunc)
+ ->done(
+ function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
+ $values[$i] = $mapped;
+
+ if (0 === --$toResolve) {
+ $resolve($values);
+ }
+ },
+ $reject,
+ $notify
+ );
+ }
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
+{
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($promisesOrValues);
+
+ return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
+ resolve($promisesOrValues)
+ ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) {
+ if (!\is_array($array)) {
+ $array = [];
+ }
+
+ $total = \count($array);
+ $i = 0;
+
+ // Wrap the supplied $reduceFunc with one that handles promises and then
+ // delegates to the supplied.
+ $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
+ $cancellationQueue->enqueue($val);
+
+ return $current
+ ->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
+ return resolve($val)
+ ->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
+ return $reduceFunc($c, $value, $i++, $total);
+ });
+ });
+ };
+
+ $cancellationQueue->enqueue($initialValue);
+
+ \array_reduce($array, $wrappedReduceFunc, resolve($initialValue))
+ ->done($resolve, $reject, $notify);
+ }, $reject, $notify);
+ }, $cancellationQueue);
+}
+
+// Internal functions
+function _checkTypehint(callable $callback, $object)
+{
+ if (!\is_object($object)) {
+ return true;
+ }
+
+ if (\is_array($callback)) {
+ $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
+ } elseif (\is_object($callback) && !$callback instanceof \Closure) {
+ $callbackReflection = new \ReflectionMethod($callback, '__invoke');
+ } else {
+ $callbackReflection = new \ReflectionFunction($callback);
+ }
+
+ $parameters = $callbackReflection->getParameters();
+
+ if (!isset($parameters[0])) {
+ return true;
+ }
+
+ $expectedException = $parameters[0];
+
+ if (!$expectedException->getClass()) {
+ return true;
+ }
+
+ return $expectedException->getClass()->isInstance($object);
+}
diff --git a/vendor/react/promise/src/functions_include.php b/vendor/react/promise/src/functions_include.php
new file mode 100755
index 0000000..bd0c54f
--- /dev/null
+++ b/vendor/react/promise/src/functions_include.php
@@ -0,0 +1,5 @@
+enqueue($p);
+
+ $cancellationQueue();
+
+ $this->assertTrue($p->cancelCalled);
+ }
+
+ /** @test */
+ public function ignoresSimpleCancellable()
+ {
+ $p = new SimpleTestCancellable();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($p);
+
+ $cancellationQueue();
+
+ $this->assertFalse($p->cancelCalled);
+ }
+
+ /** @test */
+ public function callsCancelOnPromisesEnqueuedBeforeStart()
+ {
+ $d1 = $this->getCancellableDeferred();
+ $d2 = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($d1->promise());
+ $cancellationQueue->enqueue($d2->promise());
+
+ $cancellationQueue();
+ }
+
+ /** @test */
+ public function callsCancelOnPromisesEnqueuedAfterStart()
+ {
+ $d1 = $this->getCancellableDeferred();
+ $d2 = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+
+ $cancellationQueue();
+
+ $cancellationQueue->enqueue($d2->promise());
+ $cancellationQueue->enqueue($d1->promise());
+ }
+
+ /** @test */
+ public function doesNotCallCancelTwiceWhenStartedTwice()
+ {
+ $d = $this->getCancellableDeferred();
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($d->promise());
+
+ $cancellationQueue();
+ $cancellationQueue();
+ }
+
+ /** @test */
+ public function rethrowsExceptionsThrownFromCancel()
+ {
+ $this->setExpectedException('\Exception', 'test');
+
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel')
+ ->will($this->throwException(new \Exception('test')));
+
+ $cancellationQueue = new CancellationQueue();
+ $cancellationQueue->enqueue($mock);
+
+ $cancellationQueue();
+ }
+
+ private function getCancellableDeferred()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return new Deferred($mock);
+ }
+}
diff --git a/vendor/react/promise/tests/DeferredTest.php b/vendor/react/promise/tests/DeferredTest.php
new file mode 100755
index 0000000..8ee40b8
--- /dev/null
+++ b/vendor/react/promise/tests/DeferredTest.php
@@ -0,0 +1,112 @@
+ [$d, 'promise'],
+ 'resolve' => [$d, 'resolve'],
+ 'reject' => [$d, 'reject'],
+ 'notify' => [$d, 'progress'],
+ 'settle' => [$d, 'resolve'],
+ ]);
+ }
+
+ /** @test */
+ public function progressIsAnAliasForNotify()
+ {
+ $deferred = new Deferred();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $deferred->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $deferred->progress($sentinel);
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $deferred->promise()->cancel();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfParentCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $deferred->promise()->then()->cancel();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenceAndExplicitlyRejectWithException()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function () use (&$deferred) { });
+ $deferred->reject(new \Exception('foo'));
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferred()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred();
+ $deferred->promise();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithUnusedCanceller()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function () { });
+ $deferred->promise();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingDeferredWithNoopCanceller()
+ {
+ gc_collect_cycles();
+ $deferred = new Deferred(function () { });
+ $deferred->promise()->cancel();
+ unset($deferred);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+}
diff --git a/vendor/react/promise/tests/FulfilledPromiseTest.php b/vendor/react/promise/tests/FulfilledPromiseTest.php
new file mode 100755
index 0000000..f5a2da8
--- /dev/null
+++ b/vendor/react/promise/tests/FulfilledPromiseTest.php
@@ -0,0 +1,76 @@
+ function () use (&$promise) {
+ if (!$promise) {
+ throw new \LogicException('FulfilledPromise must be resolved before obtaining the promise');
+ }
+
+ return $promise;
+ },
+ 'resolve' => function ($value = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new FulfilledPromise($value);
+ }
+ },
+ 'reject' => function () {
+ throw new \LogicException('You cannot call reject() for React\Promise\FulfilledPromise');
+ },
+ 'notify' => function () {
+ // no-op
+ },
+ 'settle' => function ($value = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new FulfilledPromise($value);
+ }
+ },
+ ]);
+ }
+
+ /** @test */
+ public function shouldThrowExceptionIfConstructedWithAPromise()
+ {
+ $this->setExpectedException('\InvalidArgumentException');
+
+ return new FulfilledPromise(new FulfilledPromise());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithAlwaysFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new FulfilledPromise(1);
+ $promise->always(function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToFulfilledPromiseWithThenFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new FulfilledPromise(1);
+ $promise = $promise->then(function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+}
diff --git a/vendor/react/promise/tests/FunctionAllTest.php b/vendor/react/promise/tests/FunctionAllTest.php
new file mode 100755
index 0000000..74c1d7c
--- /dev/null
+++ b/vendor/react/promise/tests/FunctionAllTest.php
@@ -0,0 +1,114 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ all([])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all([1, 2, 3])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all([resolve(1), resolve(2), resolve(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([null, 1, null, 1, 1]));
+
+ all([null, 1, null, 1, 1])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfAnyInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ all([resolve(1), reject(2), resolve(3)])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ all(resolve([1, 2, 3]))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ all(resolve(1))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ $deferred = new Deferred();
+
+ all([resolve(1), $deferred->promise(), resolve(3)])
+ ->then($mock);
+
+ $deferred->resolve(2);
+ }
+}
diff --git a/vendor/react/promise/tests/FunctionAnyTest.php b/vendor/react/promise/tests/FunctionAnyTest.php
new file mode 100755
index 0000000..140b551
--- /dev/null
+++ b/vendor/react/promise/tests/FunctionAnyTest.php
@@ -0,0 +1,204 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage();
+ })
+ );
+
+ any([])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullWithNonArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(null)
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithAnInputValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([1, 2, 3])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithAPromisedInputValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([resolve(1), resolve(2), resolve(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWithAllRejectedInputValuesIfAllInputsAreRejected()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([0 => 1, 1 => 2, 2 => 3]));
+
+ any([reject(1), reject(2), reject(3)])
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWhenFirstInputPromiseResolves()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any([resolve(1), reject(2), reject(3)])
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ any(resolve([1, 2, 3]))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(resolve(1))
+ ->then($mock);
+ }
+
+ /** @test */
+ public function shouldNotRelyOnArryIndexesWhenUnwrappingToASingleResolutionValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+
+ any(['abc' => $d1->promise(), 1 => $d2->promise()])
+ ->then($mock);
+
+ $d2->resolve(2);
+ $d1->resolve(1);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ any(reject())
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ any($mock)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ any([$mock1, $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 1)->cancel();
+ }
+}
diff --git a/vendor/react/promise/tests/FunctionCheckTypehintTest.php b/vendor/react/promise/tests/FunctionCheckTypehintTest.php
new file mode 100755
index 0000000..8449bc1
--- /dev/null
+++ b/vendor/react/promise/tests/FunctionCheckTypehintTest.php
@@ -0,0 +1,118 @@
+assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptFunctionStringCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithTypehint', new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint('React\Promise\testCallbackWithTypehint', new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptInvokableObjectCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint(new TestCallbackWithTypehintClass(), new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(new TestCallbackWithTypehintClass(), new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptObjectMethodCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptStaticClassCallbackWithTypehint()
+ {
+ $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
+ $this->assertfalse(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \Exception()));
+ }
+
+ /** @test */
+ public function shouldAcceptClosureCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
+ }, new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptFunctionStringCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint('React\Promise\testCallbackWithoutTypehint', new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptInvokableObjectCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(new TestCallbackWithoutTypehintClass(), new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptObjectMethodCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint([new TestCallbackWithoutTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
+ }
+
+ /** @test */
+ public function shouldAcceptStaticClassCallbackWithoutTypehint()
+ {
+ $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithoutTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
+ }
+}
+
+function testCallbackWithTypehint(\InvalidArgumentException $e)
+{
+}
+
+function testCallbackWithoutTypehint()
+{
+}
+
+class TestCallbackWithTypehintClass
+{
+ public function __invoke(\InvalidArgumentException $e)
+ {
+
+ }
+
+ public function testCallback(\InvalidArgumentException $e)
+ {
+
+ }
+
+ public static function testCallbackStatic(\InvalidArgumentException $e)
+ {
+
+ }
+}
+
+class TestCallbackWithoutTypehintClass
+{
+ public function __invoke()
+ {
+
+ }
+
+ public function testCallback()
+ {
+
+ }
+
+ public static function testCallbackStatic()
+ {
+
+ }
+}
diff --git a/vendor/react/promise/tests/FunctionMapTest.php b/vendor/react/promise/tests/FunctionMapTest.php
new file mode 100755
index 0000000..1ea560a
--- /dev/null
+++ b/vendor/react/promise/tests/FunctionMapTest.php
@@ -0,0 +1,198 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, 2, 3],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapInputPromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapMixedInputArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, resolve(2), 3],
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldMapInputWhenMapperReturnsAPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ [1, 2, 3],
+ $this->promiseMapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ map(
+ resolve([1, resolve(2), 3]),
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ map(
+ resolve(1),
+ $this->mapper()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([2, 4, 6]));
+
+ $deferred = new Deferred();
+
+ map(
+ [resolve(1), $deferred->promise(), resolve(3)],
+ $this->mapper()
+ )->then($mock);
+
+ $deferred->resolve(2);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputContainsRejection()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ map(
+ [resolve(1), reject(2), resolve(3)],
+ $this->mapper()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ map(
+ reject(),
+ $this->mapper()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ map(
+ $mock,
+ $this->mapper()
+ )->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ map(
+ [$mock1, $mock2],
+ $this->mapper()
+ )->cancel();
+ }
+}
diff --git a/vendor/react/promise/tests/FunctionRaceTest.php b/vendor/react/promise/tests/FunctionRaceTest.php
new file mode 100755
index 0000000..83770ec
--- /dev/null
+++ b/vendor/react/promise/tests/FunctionRaceTest.php
@@ -0,0 +1,211 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ []
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ race(
+ [1, 2, 3]
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ race(
+ [$d1->promise(), $d2->promise(), $d3->promise()]
+ )->then($mock);
+
+ $d2->resolve(2);
+
+ $d1->resolve(1);
+ $d3->resolve(3);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ [null, 1, null, 2, 3]
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfFirstSettledPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ race(
+ [$d1->promise(), $d2->promise(), $d3->promise()]
+ )->then($this->expectCallableNever(), $mock);
+
+ $d2->reject(2);
+
+ $d1->resolve(1);
+ $d3->resolve(3);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ race(
+ resolve([1, 2, 3])
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToNullWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ race(
+ reject()
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ race($mock)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ race([$mock1, $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ race([$deferred->promise(), $mock2])->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->reject();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ race([$deferred->promise(), $mock2])->cancel();
+ }
+}
diff --git a/vendor/react/promise/tests/FunctionReduceTest.php b/vendor/react/promise/tests/FunctionReduceTest.php
new file mode 100755
index 0000000..8b43a87
--- /dev/null
+++ b/vendor/react/promise/tests/FunctionReduceTest.php
@@ -0,0 +1,347 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(6));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceValuesWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceValuesWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [1, 2, 3],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithoutInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(6));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReducePromisedValuesWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(7));
+
+ reduce(
+ [resolve(1), resolve(2), resolve(3)],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceEmptyInputWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ [],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceEmptyInputWithInitialPromise()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ [],
+ $this->plus(),
+ resolve(1)
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputContainsRejection()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ reduce(
+ [resolve(1), reject(2), resolve(3)],
+ $this->plus(),
+ resolve(1)
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithNullWhenInputIsEmptyAndNoInitialValueOrPromiseProvided()
+ {
+ // Note: this is different from when.js's behavior!
+ // In when.reduce(), this rejects with a TypeError exception (following
+ // JavaScript's [].reduce behavior.
+ // We're following PHP's array_reduce behavior and resolve with NULL.
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ reduce(
+ [],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAllowSparseArrayInputWithoutInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(3));
+
+ reduce(
+ [null, null, 1, null, 1, 1],
+ $this->plus()
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAllowSparseArrayInputWithInitialValue()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(4));
+
+ reduce(
+ [null, null, 1, null, 1, 1],
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldReduceInInputOrder()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('123'));
+
+ reduce(
+ [1, 2, 3],
+ $this->append(),
+ ''
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('123'));
+
+ reduce(
+ resolve([1, 2, 3]),
+ $this->append(),
+ ''
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToInitialValueWhenInputPromiseDoesNotResolveToAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ reduce(
+ resolve(1),
+ $this->plus(),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldProvideCorrectBasisValue()
+ {
+ $insertIntoArray = function ($arr, $val, $i) {
+ $arr[$i] = $val;
+
+ return $arr;
+ };
+
+ $d1 = new Deferred();
+ $d2 = new Deferred();
+ $d3 = new Deferred();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2, 3]));
+
+ reduce(
+ [$d1->promise(), $d2->promise(), $d3->promise()],
+ $insertIntoArray,
+ []
+ )->then($mock);
+
+ $d3->resolve(3);
+ $d1->resolve(1);
+ $d2->resolve(2);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ reduce(
+ reject(),
+ $this->plus(),
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ reduce(
+ $mock,
+ $this->plus(),
+ 1
+ )->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ reduce(
+ [$mock1, $mock2],
+ $this->plus(),
+ 1
+ )->cancel();
+ }
+}
diff --git a/vendor/react/promise/tests/FunctionRejectTest.php b/vendor/react/promise/tests/FunctionRejectTest.php
new file mode 100755
index 0000000..84b8ec6
--- /dev/null
+++ b/vendor/react/promise/tests/FunctionRejectTest.php
@@ -0,0 +1,64 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($expected)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldRejectAFulfilledPromise()
+ {
+ $expected = 123;
+
+ $resolved = new FulfilledPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldRejectARejectedPromise()
+ {
+ $expected = 123;
+
+ $resolved = new RejectedPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ reject($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+}
diff --git a/vendor/react/promise/tests/FunctionResolveTest.php b/vendor/react/promise/tests/FunctionResolveTest.php
new file mode 100755
index 0000000..53126bc
--- /dev/null
+++ b/vendor/react/promise/tests/FunctionResolveTest.php
@@ -0,0 +1,171 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($expected)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveAFulfilledPromise()
+ {
+ $expected = 123;
+
+ $resolved = new FulfilledPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($resolved)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveAThenable()
+ {
+ $thenable = new SimpleFulfilledTestThenable();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ resolve($thenable)
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldResolveACancellableThenable()
+ {
+ $thenable = new SimpleTestCancellableThenable();
+
+ $promise = resolve($thenable);
+ $promise->cancel();
+
+ $this->assertTrue($thenable->cancelCalled);
+ }
+
+ /** @test */
+ public function shouldRejectARejectedPromise()
+ {
+ $expected = 123;
+
+ $resolved = new RejectedPromise($expected);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($expected));
+
+ resolve($resolved)
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function shouldSupportDeepNestingInPromiseChains()
+ {
+ $d = new Deferred();
+ $d->resolve(false);
+
+ $result = resolve(resolve($d->promise()->then(function ($val) {
+ $d = new Deferred();
+ $d->resolve($val);
+
+ $identity = function ($val) {
+ return $val;
+ };
+
+ return resolve($d->promise()->then($identity))->then(
+ function ($val) {
+ return !$val;
+ }
+ );
+ })));
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $result->then($mock);
+ }
+
+ /** @test */
+ public function shouldSupportVeryDeepNestedPromises()
+ {
+ $deferreds = [];
+
+ // @TODO Increase count once global-queue is merged
+ for ($i = 0; $i < 10; $i++) {
+ $deferreds[] = $d = new Deferred();
+ $p = $d->promise();
+
+ $last = $p;
+ for ($j = 0; $j < 10; $j++) {
+ $last = $last->then(function($result) {
+ return $result;
+ });
+ }
+ }
+
+ $p = null;
+ foreach ($deferreds as $d) {
+ if ($p) {
+ $d->resolve($p);
+ }
+
+ $p = $d->promise();
+ }
+
+ $deferreds[0]->resolve(true);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(true));
+
+ $deferreds[0]->promise()->then($mock);
+ }
+
+ /** @test */
+ public function returnsExtendePromiseForSimplePromise()
+ {
+ $promise = $this
+ ->getMockBuilder('React\Promise\PromiseInterface')
+ ->getMock();
+
+ $this->assertInstanceOf('React\Promise\ExtendedPromiseInterface', resolve($promise));
+ }
+}
diff --git a/vendor/react/promise/tests/FunctionSomeTest.php b/vendor/react/promise/tests/FunctionSomeTest.php
new file mode 100755
index 0000000..276b54b
--- /dev/null
+++ b/vendor/react/promise/tests/FunctionSomeTest.php
@@ -0,0 +1,258 @@
+createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage();
+ })
+ );
+
+ some(
+ [],
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldRejectWithLengthExceptionWithInputArrayContainingNotEnoughItems()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(
+ $this->callback(function($exception){
+ return $exception instanceof LengthException &&
+ 'Input array must contain at least 4 items but contains only 3 items.' === $exception->getMessage();
+ })
+ );
+
+ some(
+ [1, 2, 3],
+ 4
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWithNonArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ null,
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveValuesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ [1, 2, 3],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolvePromisesArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ [resolve(1), resolve(2), resolve(3)],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveSparseArrayInput()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([null, 1]));
+
+ some(
+ [null, 1, null, 2, 3],
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectIfAnyInputPromiseRejectsBeforeDesiredNumberOfInputsAreResolved()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1 => 2, 2 => 3]));
+
+ some(
+ [resolve(1), reject(2), reject(3)],
+ 2
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldAcceptAPromiseForAnArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([1, 2]));
+
+ some(
+ resolve([1, 2, 3]),
+ 2
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithEmptyArrayIfHowManyIsLessThanOne()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ [1],
+ 0
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldResolveToEmptyArrayWhenInputPromiseDoesNotResolveToArray()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo([]));
+
+ some(
+ resolve(1),
+ 1
+ )->then($mock);
+ }
+
+ /** @test */
+ public function shouldRejectWhenInputPromiseRejects()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(null));
+
+ some(
+ reject(),
+ 1
+ )->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldCancelInputPromise()
+ {
+ $mock = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock
+ ->expects($this->once())
+ ->method('cancel');
+
+ some($mock, 1)->cancel();
+ }
+
+ /** @test */
+ public function shouldCancelInputArrayPromises()
+ {
+ $mock1 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock1
+ ->expects($this->once())
+ ->method('cancel');
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->once())
+ ->method('cancel');
+
+ some([$mock1, $mock2], 1)->cancel();
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesFulfill()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->resolve();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 1);
+ }
+
+ /** @test */
+ public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesReject()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ $deferred = New Deferred($mock);
+ $deferred->reject();
+
+ $mock2 = $this
+ ->getMockBuilder('React\Promise\CancellablePromiseInterface')
+ ->getMock();
+ $mock2
+ ->expects($this->never())
+ ->method('cancel');
+
+ some([$deferred->promise(), $mock2], 2);
+ }
+}
diff --git a/vendor/react/promise/tests/LazyPromiseTest.php b/vendor/react/promise/tests/LazyPromiseTest.php
new file mode 100755
index 0000000..b630881
--- /dev/null
+++ b/vendor/react/promise/tests/LazyPromiseTest.php
@@ -0,0 +1,107 @@
+promise();
+ };
+
+ return new CallbackPromiseAdapter([
+ 'promise' => function () use ($factory) {
+ return new LazyPromise($factory);
+ },
+ 'resolve' => [$d, 'resolve'],
+ 'reject' => [$d, 'reject'],
+ 'notify' => [$d, 'progress'],
+ 'settle' => [$d, 'resolve'],
+ ]);
+ }
+
+ /** @test */
+ public function shouldNotCallFactoryIfThenIsNotInvoked()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->never())
+ ->method('__invoke');
+
+ new LazyPromise($factory);
+ }
+
+ /** @test */
+ public function shouldCallFactoryIfThenIsInvoked()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke');
+
+ $p = new LazyPromise($factory);
+ $p->then();
+ }
+
+ /** @test */
+ public function shouldReturnPromiseFromFactory()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue(new FulfilledPromise(1)));
+
+ $onFulfilled = $this->createCallableMock();
+ $onFulfilled
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $p = new LazyPromise($factory);
+
+ $p->then($onFulfilled);
+ }
+
+ /** @test */
+ public function shouldReturnPromiseIfFactoryReturnsNull()
+ {
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue(null));
+
+ $p = new LazyPromise($factory);
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $p->then());
+ }
+
+ /** @test */
+ public function shouldReturnRejectedPromiseIfFactoryThrowsException()
+ {
+ $exception = new \Exception();
+
+ $factory = $this->createCallableMock();
+ $factory
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $onRejected = $this->createCallableMock();
+ $onRejected
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $p = new LazyPromise($factory);
+
+ $p->then($this->expectCallableNever(), $onRejected);
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php b/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php
new file mode 100755
index 0000000..bdedf46
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php
@@ -0,0 +1,40 @@
+callbacks = $callbacks;
+ }
+
+ public function promise()
+ {
+ return call_user_func_array($this->callbacks['promise'], func_get_args());
+ }
+
+ public function resolve()
+ {
+ return call_user_func_array($this->callbacks['resolve'], func_get_args());
+ }
+
+ public function reject()
+ {
+ return call_user_func_array($this->callbacks['reject'], func_get_args());
+ }
+
+ public function notify()
+ {
+ return call_user_func_array($this->callbacks['notify'], func_get_args());
+ }
+
+ public function settle()
+ {
+ return call_user_func_array($this->callbacks['settle'], func_get_args());
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php b/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php
new file mode 100755
index 0000000..9157cd4
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php
@@ -0,0 +1,14 @@
+ function () use ($promise) {
+ return $promise;
+ },
+ 'resolve' => $resolveCallback,
+ 'reject' => $rejectCallback,
+ 'notify' => $progressCallback,
+ 'settle' => $resolveCallback,
+ ]);
+ }
+
+ /** @test */
+ public function shouldRejectIfResolverThrowsException()
+ {
+ $exception = new \Exception('foo');
+
+ $promise = new Promise(function () use ($exception) {
+ throw $exception;
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $promise
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldResolveWithoutCreatingGarbageCyclesIfResolverResolvesWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve) {
+ $resolve(new \Exception('foo'));
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsExceptionWithoutResolver()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () {
+ throw new \Exception('foo');
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverRejectsWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfParentCancellerRejectsWithException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
+ $reject(new \Exception('foo'));
+ });
+ $promise->then()->then()->then()->cancel();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function ($resolve, $reject) {
+ throw new \Exception('foo');
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /**
+ * Test that checks number of garbage cycles after throwing from a canceller
+ * that explicitly uses a reference to the promise. This is rather synthetic,
+ * actual use cases often have implicit (hidden) references which ought not
+ * to be stored in the stack trace.
+ *
+ * Reassigned arguments only show up in the stack trace in PHP 7, so we can't
+ * avoid this on legacy PHP. As an alternative, consider explicitly unsetting
+ * any references before throwing.
+ *
+ * @test
+ * @requires PHP 7
+ */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () {}, function () use (&$promise) {
+ throw new \Exception('foo');
+ });
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /**
+ * @test
+ * @requires PHP 7
+ * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
+ */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverWithReferenceThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () use (&$promise) {
+ throw new \Exception('foo');
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /**
+ * @test
+ * @requires PHP 7
+ * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
+ */
+ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenceAndResolverThrowsException()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () {
+ throw new \Exception('foo');
+ }, function () use (&$promise) { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldIgnoreNotifyAfterReject()
+ {
+ $promise = new Promise(function () { }, function ($resolve, $reject, $notify) {
+ $reject(new \Exception('foo'));
+ $notify(42);
+ });
+
+ $promise->then(null, null, $this->expectCallableNever());
+ $promise->cancel();
+ }
+
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromise()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithThenFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->then()->then()->then();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithDoneFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->done();
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithOtherwiseFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->otherwise(function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithAlwaysFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->always(function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithProgressFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new Promise(function () { });
+ $promise->then(null, null, function () { });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldFulfillIfFullfilledWithSimplePromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(new SimpleFulfilledTestPromise());
+ }
+
+ /** @test */
+ public function shouldRejectIfRejectedWithSimplePromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo('foo'));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->resolve(new SimpleRejectedTestPromise());
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php b/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php
new file mode 100755
index 0000000..2baab02
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php
@@ -0,0 +1,246 @@
+getPromiseTestAdapter(function ($resolve, $reject, $notify) use (&$args) {
+ $args = func_get_args();
+ });
+
+ $adapter->promise()->cancel();
+
+ $this->assertCount(3, $args);
+ $this->assertTrue(is_callable($args[0]));
+ $this->assertTrue(is_callable($args[1]));
+ $this->assertTrue(is_callable($args[2]));
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerWithoutArgumentsIfNotAccessed()
+ {
+ $args = null;
+ $adapter = $this->getPromiseTestAdapter(function () use (&$args) {
+ $args = func_num_args();
+ });
+
+ $adapter->promise()->cancel();
+
+ $this->assertSame(0, $args);
+ }
+
+ /** @test */
+ public function cancelShouldFulfillPromiseIfCancellerFulfills()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve) {
+ $resolve(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock, $this->expectCallableNever());
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldRejectPromiseIfCancellerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject) {
+ $reject(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldRejectPromiseWithExceptionIfCancellerThrows()
+ {
+ $e = new \Exception();
+
+ $adapter = $this->getPromiseTestAdapter(function () use ($e) {
+ throw $e;
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($e));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldProgressPromiseIfCancellerNotifies()
+ {
+ $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject, $progress) {
+ $progress(1);
+ });
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerOnlyOnceIfCancellerResolves()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnCallback(function ($resolve) {
+ $resolve();
+ }));
+
+ $adapter = $this->getPromiseTestAdapter($mock);
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectIfCancellerDoesNothing()
+ {
+ $adapter = $this->getPromiseTestAdapter(function () {});
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever());
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldCallCancellerFromDeepNestedPromiseChain()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ $adapter = $this->getPromiseTestAdapter($mock);
+
+ $promise = $adapter->promise()
+ ->then(function () {
+ return new Promise\Promise(function () {});
+ })
+ ->then(function () {
+ $d = new Promise\Deferred();
+
+ return $d->promise();
+ })
+ ->then(function () {
+ return new Promise\Promise(function () {});
+ });
+
+ $promise->cancel();
+ }
+
+ /** @test */
+ public function cancelCalledOnChildrenSouldOnlyCancelWhenAllChildrenCancelled()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldTriggerCancellerWhenAllChildrenCancel()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $child2 = $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ $child2->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldNotTriggerCancellerWhenCancellingOneChildrenMultipleTimes()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $child1 = $adapter->promise()
+ ->then()
+ ->then();
+
+ $child2 = $adapter->promise()
+ ->then();
+
+ $child1->cancel();
+ $child1->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldTriggerCancellerOnlyOnceWhenCancellingMultipleTimes()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $adapter->promise()->cancel();
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function cancelShouldAlwaysTriggerCancellerWhenCalledOnRootPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());
+
+ $adapter->promise()
+ ->then()
+ ->then();
+
+ $adapter->promise()
+ ->then();
+
+ $adapter->promise()->cancel();
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseTest/FullTestTrait.php b/vendor/react/promise/tests/PromiseTest/FullTestTrait.php
new file mode 100755
index 0000000..3ce45d6
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseTest/FullTestTrait.php
@@ -0,0 +1,15 @@
+getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
+
+ $adapter->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateProgressToDownstreamPromises()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnArgument(0));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateTransformedProgressToDownstreamPromises()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue($sentinel));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldPropagateCaughtExceptionValueAsProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $promise2 = $adapter2->promise();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ // resolve BEFORE attaching progress handler
+ $adapter->resolve();
+
+ $adapter->promise()
+ ->then(function () use ($promise2) {
+ return $promise2;
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $promise2 = $adapter2->promise();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(function () use ($promise2) {
+ return $promise2;
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ // resolve AFTER attaching progress handler
+ $adapter->resolve();
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldForwardProgressWhenResolvedWithAnotherPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $sentinel = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->returnValue($sentinel));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($sentinel);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $this->expectCallableNever(),
+ $mock2
+ );
+
+ $adapter->resolve($adapter2->promise());
+ $adapter2->notify($sentinel);
+ }
+
+ /** @test */
+ public function notifyShouldAllowResolveAfterProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->at(0))
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+ $mock
+ ->expects($this->at(1))
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->notify(1);
+ $adapter->resolve(2);
+ }
+
+ /** @test */
+ public function notifyShouldAllowRejectAfterProgress()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->at(0))
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+ $mock
+ ->expects($this->at(1))
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock,
+ $mock
+ );
+
+ $adapter->notify(1);
+ $adapter->reject(2);
+ }
+
+ /** @test */
+ public function notifyShouldReturnSilentlyOnProgressWhenAlreadyRejected()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(1);
+
+ $this->assertNull($adapter->notify());
+ }
+
+ /** @test */
+ public function notifyShouldInvokeProgressHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()->progress($mock);
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldInvokeProgressHandlerFromDone()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done(null, null, $mock));
+ $adapter->notify(1);
+ }
+
+ /** @test */
+ public function notifyShouldThrowExceptionThrownProgressHandlerFromDone()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->notify(1);
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php b/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php
new file mode 100755
index 0000000..428230b
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php
@@ -0,0 +1,351 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $adapter->resolve(2);
+
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function fulfilledPromiseShouldInvokeNewlyAddedCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve(1);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock, $this->expectCallableNever());
+ }
+
+ /** @test */
+ public function thenShouldForwardResultWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ null,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldForwardCallbackResultToNextCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return $val + 1;
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldForwardPromisedCallbackResultValueToNextCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return \React\Promise\resolve($val + 1);
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackReturnsARejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ function ($val) {
+ return \React\Promise\reject($val + 1);
+ },
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackThrows()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock2
+ );
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->resolve();
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function doneShouldInvokeFulfillmentHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done($mock));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownFulfillmentHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done(function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejectsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->resolve(1);
+ $this->assertNull($adapter->promise()->done(function () {
+ return \React\Promise\reject();
+ }));
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->resolve(1);
+ $adapter->promise()->otherwise($this->expectCallableNever());
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {})
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromiseForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromiseForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->resolve($value);
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then($mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->resolve(1);
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php b/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php
new file mode 100755
index 0000000..a4f48ee
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php
@@ -0,0 +1,68 @@
+getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then());
+ }
+
+ /** @test */
+ public function thenShouldReturnAllowNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null));
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function doneShouldReturnNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function doneShouldReturnAllowNullForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done(null, null, null));
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $adapter->promise()->otherwise($this->expectCallableNever());
+ }
+
+ /** @test */
+ public function alwaysShouldReturnAPromiseForPendingPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php b/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php
new file mode 100755
index 0000000..98d1dcf
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php
@@ -0,0 +1,512 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $adapter->reject(2);
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function rejectedPromiseShouldInvokeNewlyAddedCallback()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(1);
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+ }
+
+ /** @test */
+ public function shouldForwardUndefinedRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(null);
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function () {
+ // Presence of rejection handler is enough to switch back
+ // to resolve mode, even though it returns undefined.
+ // The ONLY way to propagate a rejection is to re-throw or
+ // return a rejected promise;
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyPropagate()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return $val + 1;
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return \React\Promise\resolve($val + 1);
+ }
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+ }
+
+ /** @test */
+ public function shouldPropagateRejectionsWhenErrbackThrows()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->will($this->throwException($exception));
+
+ $mock2 = $this->createCallableMock();
+ $mock2
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock2
+ );
+ }
+
+ /** @test */
+ public function shouldPropagateRejectionsWhenErrbackReturnsARejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(2));
+
+ $adapter->reject(1);
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ function ($val) {
+ return \React\Promise\reject($val + 1);
+ }
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+ }
+
+ /** @test */
+ public function doneShouldInvokeRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, $mock));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownByRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function unhandledRejectionExceptionThrownByDoneHoldsRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $expected = new \stdClass();
+
+ $adapter->reject($expected);
+
+ try {
+ $adapter->promise()->done();
+ } catch (UnhandledRejectionException $e) {
+ $this->assertSame($expected, $e->getReason());
+ return;
+ }
+
+ $this->fail();
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejectsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject();
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(1);
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
+ }));
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionProvidedAsRejectionValueForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ $this->assertNull($adapter->promise()->done());
+ }
+
+ /** @test */
+ public function doneShouldThrowWithDeepNestingPromiseChainsForRejectedPromise()
+ {
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $exception = new \Exception('UnhandledRejectionException');
+
+ $d = new Deferred();
+ $d->resolve();
+
+ $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
+ $d = new Deferred();
+ $d->resolve();
+
+ return \React\Promise\resolve($d->promise()->then(function () {}))->then(
+ function () use ($exception) {
+ throw $exception;
+ }
+ );
+ })));
+
+ $result->done();
+ }
+
+ /** @test */
+ public function doneShouldRecoverWhenRejectionHandlerCatchesExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ $this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
+
+ }));
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeRejectionHandlerForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->reject(1);
+ $adapter->promise()->otherwise($mock);
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeNonTypeHintedRejectionHandlerIfReasonIsAnExceptionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function ($reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function otherwiseShouldInvokeRejectionHandlerIfReasonMatchesTypehintForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \InvalidArgumentException();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function otherwiseShouldNotInvokeRejectionHandlerIfReaonsDoesNotMatchTypehintForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->expectCallableNever();
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
+ $mock($reason);
+ });
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {})
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromiseForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromiseForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->reject($exception);
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception1 = new \Exception();
+ $exception2 = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception2));
+
+ $adapter->reject($exception1);
+ $adapter->promise()
+ ->always(function () use ($exception2) {
+ throw $exception2;
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception1 = new \Exception();
+ $exception2 = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception2));
+
+ $adapter->reject($exception1);
+ $adapter->promise()
+ ->always(function () use ($exception2) {
+ return \React\Promise\reject($exception2);
+ })
+ ->then(null, $mock);
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->reject();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->reject();
+
+ $adapter->promise()->cancel();
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php b/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php
new file mode 100755
index 0000000..e363b6d
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php
@@ -0,0 +1,86 @@
+getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then());
+ }
+
+ /** @test */
+ public function thenShouldReturnAllowNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->then(null, null, null));
+ }
+
+ /** @test */
+ public function cancelShouldReturnNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+
+ $this->assertNull($adapter->promise()->cancel());
+ }
+
+ /** @test */
+ public function cancelShouldHaveNoEffectForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter($this->expectCallableNever());
+
+ $adapter->settle();
+
+ $adapter->promise()->cancel();
+ }
+
+ /** @test */
+ public function doneShouldReturnNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertNull($adapter->promise()->done(null, function () {}));
+ }
+
+ /** @test */
+ public function doneShouldReturnAllowNullForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertNull($adapter->promise()->done(null, function () {}, null));
+ }
+
+ /** @test */
+ public function progressShouldNotInvokeProgressHandlerForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $adapter->promise()->progress($this->expectCallableNever());
+ $adapter->notify();
+ }
+
+ /** @test */
+ public function alwaysShouldReturnAPromiseForSettledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $adapter->settle();
+ $this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php b/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php
new file mode 100755
index 0000000..063f178
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php
@@ -0,0 +1,368 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function rejectShouldRejectWithFulfilledPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(Promise\resolve(1));
+ }
+
+ /** @test */
+ public function rejectShouldRejectWithRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->reject(Promise\reject(1));
+ }
+
+ /** @test */
+ public function rejectShouldForwardReasonWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever()
+ )
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function rejectShouldMakePromiseImmutable()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(null, function ($value) use ($adapter) {
+ $adapter->reject(3);
+
+ return Promise\reject($value);
+ })
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->reject(1);
+ $adapter->reject(2);
+ }
+
+ /** @test */
+ public function notifyShouldInvokeOtherwiseHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->otherwise($mock);
+
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldInvokeRejectionHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done(null, $mock));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownByRejectionHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done());
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject();
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(null, function () {
+ return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
+ }));
+ $adapter->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRetunsPendingPromiseWhichRejectsLater()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $d = new Deferred();
+ $promise = $d->promise();
+
+ $this->assertNull($adapter->promise()->done(null, function () use ($promise) {
+ return $promise;
+ }));
+ $adapter->reject(1);
+ $d->reject(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionProvidedAsRejectionValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done());
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ }
+
+ /** @test */
+ public function doneShouldThrowWithDeepNestingPromiseChains()
+ {
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $exception = new \Exception('UnhandledRejectionException');
+
+ $d = new Deferred();
+
+ $result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
+ $d = new Deferred();
+ $d->resolve();
+
+ return \React\Promise\resolve($d->promise()->then(function () {}))->then(
+ function () use ($exception) {
+ throw $exception;
+ }
+ );
+ })));
+
+ $result->done();
+
+ $d->resolve();
+ }
+
+ /** @test */
+ public function doneShouldRecoverWhenRejectionHandlerCatchesException()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
+
+ }));
+ $adapter->reject(new \Exception('UnhandledRejectionException'));
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {})
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForRejection()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+
+ $adapter->reject($exception);
+ }
+}
diff --git a/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php b/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php
new file mode 100755
index 0000000..0736d35
--- /dev/null
+++ b/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php
@@ -0,0 +1,312 @@
+getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function resolveShouldResolveWithPromisedValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($mock);
+
+ $adapter->resolve(Promise\resolve(1));
+ }
+
+ /** @test */
+ public function resolveShouldRejectWhenResolvedWithRejectedPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then($this->expectCallableNever(), $mock);
+
+ $adapter->resolve(Promise\reject(1));
+ }
+
+ /** @test */
+ public function resolveShouldForwardValueWhenCallbackIsNull()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(
+ null,
+ $this->expectCallableNever()
+ )
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function resolveShouldMakePromiseImmutable()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $adapter->promise()
+ ->then(function ($value) use ($adapter) {
+ $adapter->resolve(3);
+
+ return $value;
+ })
+ ->then(
+ $mock,
+ $this->expectCallableNever()
+ );
+
+ $adapter->resolve(1);
+ $adapter->resolve(2);
+ }
+
+ /**
+ * @test
+ */
+ public function resolveShouldRejectWhenResolvedWithItself()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(new \LogicException('Cannot resolve a promise with itself.'));
+
+ $adapter->promise()
+ ->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter->resolve($adapter->promise());
+ }
+
+ /**
+ * @test
+ */
+ public function resolveShouldRejectWhenResolvedWithAPromiseWhichFollowsItself()
+ {
+ $adapter1 = $this->getPromiseTestAdapter();
+ $adapter2 = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with(new \LogicException('Cannot resolve a promise with itself.'));
+
+ $promise1 = $adapter1->promise();
+
+ $promise2 = $adapter2->promise();
+
+ $promise2->then(
+ $this->expectCallableNever(),
+ $mock
+ );
+
+ $adapter1->resolve($promise2);
+ $adapter2->resolve($promise1);
+ }
+
+ /** @test */
+ public function doneShouldInvokeFulfillmentHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo(1));
+
+ $this->assertNull($adapter->promise()->done($mock));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowExceptionThrownFulfillmentHandler()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('\Exception', 'UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(function () {
+ throw new \Exception('UnhandledRejectionException');
+ }));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejects()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $this->setExpectedException('React\\Promise\\UnhandledRejectionException');
+
+ $this->assertNull($adapter->promise()->done(function () {
+ return \React\Promise\reject();
+ }));
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValue()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {})
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {
+ return 1;
+ })
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromise()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $value = new \stdClass();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($value));
+
+ $adapter->promise()
+ ->always(function () {
+ return \React\Promise\resolve(1);
+ })
+ ->then($mock);
+
+ $adapter->resolve($value);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerThrowsForFulfillment()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ throw $exception;
+ })
+ ->then(null, $mock);
+
+ $adapter->resolve(1);
+ }
+
+ /** @test */
+ public function alwaysShouldRejectWhenHandlerRejectsForFulfillment()
+ {
+ $adapter = $this->getPromiseTestAdapter();
+
+ $exception = new \Exception();
+
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($this->identicalTo($exception));
+
+ $adapter->promise()
+ ->always(function () use ($exception) {
+ return \React\Promise\reject($exception);
+ })
+ ->then(null, $mock);
+
+ $adapter->resolve(1);
+ }
+}
diff --git a/vendor/react/promise/tests/RejectedPromiseTest.php b/vendor/react/promise/tests/RejectedPromiseTest.php
new file mode 100755
index 0000000..825f56c
--- /dev/null
+++ b/vendor/react/promise/tests/RejectedPromiseTest.php
@@ -0,0 +1,76 @@
+ function () use (&$promise) {
+ if (!$promise) {
+ throw new \LogicException('RejectedPromise must be rejected before obtaining the promise');
+ }
+
+ return $promise;
+ },
+ 'resolve' => function () {
+ throw new \LogicException('You cannot call resolve() for React\Promise\RejectedPromise');
+ },
+ 'reject' => function ($reason = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new RejectedPromise($reason);
+ }
+ },
+ 'notify' => function () {
+ // no-op
+ },
+ 'settle' => function ($reason = null) use (&$promise) {
+ if (!$promise) {
+ $promise = new RejectedPromise($reason);
+ }
+ },
+ ]);
+ }
+
+ /** @test */
+ public function shouldThrowExceptionIfConstructedWithAPromise()
+ {
+ $this->setExpectedException('\InvalidArgumentException');
+
+ return new RejectedPromise(new RejectedPromise());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithAlwaysFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new RejectedPromise(1);
+ $promise->always(function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+
+ /** @test */
+ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToRejectedPromiseWithThenFollowers()
+ {
+ gc_collect_cycles();
+ $promise = new RejectedPromise(1);
+ $promise = $promise->then(null, function () {
+ throw new \RuntimeException();
+ });
+ unset($promise);
+
+ $this->assertSame(0, gc_collect_cycles());
+ }
+}
diff --git a/vendor/react/promise/tests/Stub/CallableStub.php b/vendor/react/promise/tests/Stub/CallableStub.php
new file mode 100755
index 0000000..0120893
--- /dev/null
+++ b/vendor/react/promise/tests/Stub/CallableStub.php
@@ -0,0 +1,10 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ public function createCallableMock()
+ {
+ return $this
+ ->getMockBuilder('React\\Promise\Stub\CallableStub')
+ ->getMock();
+ }
+}
diff --git a/vendor/react/promise/tests/bootstrap.php b/vendor/react/promise/tests/bootstrap.php
new file mode 100755
index 0000000..9b7f872
--- /dev/null
+++ b/vendor/react/promise/tests/bootstrap.php
@@ -0,0 +1,7 @@
+addPsr4('React\\Promise\\', __DIR__);
diff --git a/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php b/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php
new file mode 100755
index 0000000..ef4d530
--- /dev/null
+++ b/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php
@@ -0,0 +1,21 @@
+cancelCalled = true;
+ }
+}
diff --git a/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php b/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php
new file mode 100755
index 0000000..c0f1593
--- /dev/null
+++ b/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php
@@ -0,0 +1,18 @@
+cancelCalled = true;
+ }
+}
diff --git a/vendor/react/socket/.gitignore b/vendor/react/socket/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/vendor/react/socket/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/vendor/react/socket/.travis.yml b/vendor/react/socket/.travis.yml
new file mode 100755
index 0000000..fd937b3
--- /dev/null
+++ b/vendor/react/socket/.travis.yml
@@ -0,0 +1,48 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+# - 7.0 # Mac OS X, ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - os: osx
+ language: generic
+ php: 7.0 # just to look right on travis
+ env:
+ - PACKAGE: php70
+ allow_failures:
+ - php: hhvm
+ - os: osx
+
+sudo: false
+
+install:
+ # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew tap homebrew/homebrew-php
+ echo "Installing PHP ..."
+ brew install "${PACKAGE}"
+ brew install "${PACKAGE}"-xdebug
+ brew link "${PACKAGE}"
+ echo "Installing composer ..."
+ curl -s http://getcomposer.org/installer | php
+ mv composer.phar /usr/local/bin/composer
+ fi
+ - composer install --no-interaction
+
+script:
+ - ./vendor/bin/phpunit --coverage-text
diff --git a/vendor/react/socket/CHANGELOG.md b/vendor/react/socket/CHANGELOG.md
new file mode 100755
index 0000000..926fb46
--- /dev/null
+++ b/vendor/react/socket/CHANGELOG.md
@@ -0,0 +1,465 @@
+# Changelog
+
+## 0.8.12 (2018-06-11)
+
+* Feature: Improve memory consumption for failed and cancelled connection attempts.
+ (#161 by @clue)
+
+* Improve test suite to fix Travis config to test against legacy PHP 5.3 again.
+ (#162 by @clue)
+
+## 0.8.11 (2018-04-24)
+
+* Feature: Improve memory consumption for cancelled connection attempts and
+ simplify skipping DNS lookup when connecting to IP addresses.
+ (#159 and #160 by @clue)
+
+## 0.8.10 (2018-02-28)
+
+* Feature: Update DNS dependency to support loading system default DNS
+ nameserver config on all supported platforms
+ (`/etc/resolv.conf` on Unix/Linux/Mac/Docker/WSL and WMIC on Windows)
+ (#152 by @clue)
+
+ This means that connecting to hosts that are managed by a local DNS server,
+ such as a corporate DNS server or when using Docker containers, will now
+ work as expected across all platforms with no changes required:
+
+ ```php
+ $connector = new Connector($loop);
+ $connector->connect('intranet.example:80')->then(function ($connection) {
+ // …
+ });
+ ```
+
+## 0.8.9 (2018-01-18)
+
+* Feature: Support explicitly choosing TLS version to negotiate with remote side
+ by respecting `crypto_method` context parameter for all classes.
+ (#149 by @clue)
+
+ By default, all connector and server classes support TLSv1.0+ and exclude
+ support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly
+ choose the TLS version you want to negotiate with the remote side:
+
+ ```php
+ // new: now supports 'crypto_method` context parameter for all classes
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+ )
+ ));
+ ```
+
+* Minor internal clean up to unify class imports
+ (#148 by @clue)
+
+## 0.8.8 (2018-01-06)
+
+* Improve test suite by adding test group to skip integration tests relying on
+ internet connection and fix minor documentation typo.
+ (#146 by @clue and #145 by @cn007b)
+
+## 0.8.7 (2017-12-24)
+
+* Fix: Fix closing socket resource before removing from loop
+ (#141 by @clue)
+
+ This fixes the root cause of an uncaught `Exception` that only manifested
+ itself after the recent Stream v0.7.4 component update and only if you're
+ using `ext-event` (`ExtEventLoop`).
+
+* Improve test suite by testing against PHP 7.2
+ (#140 by @carusogabriel)
+
+## 0.8.6 (2017-11-18)
+
+* Feature: Add Unix domain socket (UDS) support to `Server` with `unix://` URI scheme
+ and add advanced `UnixServer` class.
+ (#120 by @andig)
+
+ ```php
+ // new: Server now supports "unix://" scheme
+ $server = new Server('unix:///tmp/server.sock', $loop);
+
+ // new: advanced usage
+ $server = new UnixServer('/tmp/server.sock', $loop);
+ ```
+
+* Restructure examples to ease getting started
+ (#136 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#133 by @gabriel-caruso and #134 by @clue)
+
+## 0.8.5 (2017-10-23)
+
+* Fix: Work around PHP bug with Unix domain socket (UDS) paths for Mac OS X
+ (#123 by @andig)
+
+* Fix: Fix `SecureServer` to return `null` URI if server socket is already closed
+ (#129 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit v5 and
+ forward compatibility with upcoming EventLoop releases in tests and
+ test Mac OS X on Travis
+ (#122 by @andig and #125, #127 and #130 by @clue)
+
+* Readme improvements
+ (#118 by @jsor)
+
+## 0.8.4 (2017-09-16)
+
+* Feature: Add `FixedUriConnector` decorator to use fixed, preconfigured URI instead
+ (#117 by @clue)
+
+ This can be useful for consumers that do not support certain URIs, such as
+ when you want to explicitly connect to a Unix domain socket (UDS) path
+ instead of connecting to a default address assumed by an higher-level API:
+
+ ```php
+ $connector = new FixedUriConnector(
+ 'unix:///var/run/docker.sock',
+ new UnixConnector($loop)
+ );
+
+ // destination will be ignored, actually connects to Unix domain socket
+ $promise = $connector->connect('localhost:80');
+ ```
+
+## 0.8.3 (2017-09-08)
+
+* Feature: Reduce memory consumption for failed connections
+ (#113 by @valga)
+
+* Fix: Work around write chunk size for TLS streams for PHP < 7.1.14
+ (#114 by @clue)
+
+## 0.8.2 (2017-08-25)
+
+* Feature: Update DNS dependency to support hosts file on all platforms
+ (#112 by @clue)
+
+ This means that connecting to hosts such as `localhost` will now work as
+ expected across all platforms with no changes required:
+
+ ```php
+ $connector = new Connector($loop);
+ $connector->connect('localhost:8080')->then(function ($connection) {
+ // …
+ });
+ ```
+
+## 0.8.1 (2017-08-15)
+
+* Feature: Forward compatibility with upcoming EventLoop v1.0 and v0.5 and
+ target evenement 3.0 a long side 2.0 and 1.0
+ (#104 by @clue and #111 by @WyriHaximus)
+
+* Improve test suite by locking Travis distro so new defaults will not break the build and
+ fix HHVM build for now again and ignore future HHVM build errors
+ (#109 and #110 by @clue)
+
+* Minor documentation fixes
+ (#103 by @christiaan and #108 by @hansott)
+
+## 0.8.0 (2017-05-09)
+
+* Feature: New `Server` class now acts as a facade for existing server classes
+ and renamed old `Server` to `TcpServer` for advanced usage.
+ (#96 and #97 by @clue)
+
+ The `Server` class is now the main class in this package that implements the
+ `ServerInterface` and allows you to accept incoming streaming connections,
+ such as plaintext TCP/IP or secure TLS connection streams.
+
+ > This is not a BC break and consumer code does not have to be updated.
+
+* Feature / BC break: All addresses are now URIs that include the URI scheme
+ (#98 by @clue)
+
+ ```diff
+ - $parts = parse_url('tcp://' . $conn->getRemoteAddress());
+ + $parts = parse_url($conn->getRemoteAddress());
+ ```
+
+* Fix: Fix `unix://` addresses for Unix domain socket (UDS) paths
+ (#100 by @clue)
+
+* Feature: Forward compatibility with Stream v1.0 and v0.7
+ (#99 by @clue)
+
+## 0.7.2 (2017-04-24)
+
+* Fix: Work around latest PHP 7.0.18 and 7.1.4 no longer accepting full URIs
+ (#94 by @clue)
+
+## 0.7.1 (2017-04-10)
+
+* Fix: Ignore HHVM errors when closing connection that is already closing
+ (#91 by @clue)
+
+## 0.7.0 (2017-04-10)
+
+* Feature: Merge SocketClient component into this component
+ (#87 by @clue)
+
+ This means that this package now provides async, streaming plaintext TCP/IP
+ and secure TLS socket server and client connections for ReactPHP.
+
+ ```
+ $connector = new React\Socket\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+ ```
+
+ Accordingly, the `ConnectionInterface` is now used to represent both incoming
+ server side connections as well as outgoing client side connections.
+
+ If you've previously used the SocketClient component to establish outgoing
+ client connections, upgrading should take no longer than a few minutes.
+ All classes have been merged as-is from the latest `v0.7.0` release with no
+ other changes, so you can simply update your code to use the updated namespace
+ like this:
+
+ ```php
+ // old from SocketClient component and namespace
+ $connector = new React\SocketClient\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+
+ // new
+ $connector = new React\Socket\Connector($loop);
+ $connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
+ $connection->write('…');
+ });
+ ```
+
+## 0.6.0 (2017-04-04)
+
+* Feature: Add `LimitingServer` to limit and keep track of open connections
+ (#86 by @clue)
+
+ ```php
+ $server = new Server(0, $loop);
+ $server = new LimitingServer($server, 100);
+
+ $server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+ });
+ ```
+
+* Feature / BC break: Add `pause()` and `resume()` methods to limit active
+ connections
+ (#84 by @clue)
+
+ ```php
+ $server = new Server(0, $loop);
+ $server->pause();
+
+ $loop->addTimer(1.0, function() use ($server) {
+ $server->resume();
+ });
+ ```
+
+## 0.5.1 (2017-03-09)
+
+* Feature: Forward compatibility with Stream v0.5 and upcoming v0.6
+ (#79 by @clue)
+
+## 0.5.0 (2017-02-14)
+
+* Feature / BC break: Replace `listen()` call with URIs passed to constructor
+ and reject listening on hostnames with `InvalidArgumentException`
+ and replace `ConnectionException` with `RuntimeException` for consistency
+ (#61, #66 and #72 by @clue)
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080);
+
+ // new
+ $server = new Server(8080, $loop);
+ ```
+
+ Similarly, you can now pass a full listening URI to the constructor to change
+ the listening host:
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080, '127.0.0.1');
+
+ // new
+ $server = new Server('127.0.0.1:8080', $loop);
+ ```
+
+ Trying to start listening on (DNS) host names will now throw an
+ `InvalidArgumentException`, use IP addresses instead:
+
+ ```php
+ // old
+ $server = new Server($loop);
+ $server->listen(8080, 'localhost');
+
+ // new
+ $server = new Server('127.0.0.1:8080', $loop);
+ ```
+
+ If trying to listen fails (such as if port is already in use or port below
+ 1024 may require root access etc.), it will now throw a `RuntimeException`,
+ the `ConnectionException` class has been removed:
+
+ ```php
+ // old: throws React\Socket\ConnectionException
+ $server = new Server($loop);
+ $server->listen(80);
+
+ // new: throws RuntimeException
+ $server = new Server(80, $loop);
+ ```
+
+* Feature / BC break: Rename `shutdown()` to `close()` for consistency throughout React
+ (#62 by @clue)
+
+ ```php
+ // old
+ $server->shutdown();
+
+ // new
+ $server->close();
+ ```
+
+* Feature / BC break: Replace `getPort()` with `getAddress()`
+ (#67 by @clue)
+
+ ```php
+ // old
+ echo $server->getPort(); // 8080
+
+ // new
+ echo $server->getAddress(); // 127.0.0.1:8080
+ ```
+
+* Feature / BC break: `getRemoteAddress()` returns full address instead of only IP
+ (#65 by @clue)
+
+ ```php
+ // old
+ echo $connection->getRemoteAddress(); // 192.168.0.1
+
+ // new
+ echo $connection->getRemoteAddress(); // 192.168.0.1:51743
+ ```
+
+* Feature / BC break: Add `getLocalAddress()` method
+ (#68 by @clue)
+
+ ```php
+ echo $connection->getLocalAddress(); // 127.0.0.1:8080
+ ```
+
+* BC break: The `Server` and `SecureServer` class are now marked `final`
+ and you can no longer `extend` them
+ (which was never documented or recommended anyway).
+ Public properties and event handlers are now internal only.
+ Please use composition instead of extension.
+ (#71, #70 and #69 by @clue)
+
+## 0.4.6 (2017-01-26)
+
+* Feature: Support socket context options passed to `Server`
+ (#64 by @clue)
+
+* Fix: Properly return `null` for unknown addresses
+ (#63 by @clue)
+
+* Improve documentation for `ServerInterface` and lock test suite requirements
+ (#60 by @clue, #57 by @shaunbramley)
+
+## 0.4.5 (2017-01-08)
+
+* Feature: Add `SecureServer` for secure TLS connections
+ (#55 by @clue)
+
+* Add functional integration tests
+ (#54 by @clue)
+
+## 0.4.4 (2016-12-19)
+
+* Feature / Fix: `ConnectionInterface` should extend `DuplexStreamInterface` + documentation
+ (#50 by @clue)
+
+* Feature / Fix: Improve test suite and switch to normal stream handler
+ (#51 by @clue)
+
+* Feature: Add examples
+ (#49 by @clue)
+
+## 0.4.3 (2016-03-01)
+
+* Bug fix: Suppress errors on stream_socket_accept to prevent PHP from crashing
+* Support for PHP7 and HHVM
+* Support PHP 5.3 again
+
+## 0.4.2 (2014-05-25)
+
+* Verify stream is a valid resource in Connection
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: Check read buffer for data before shutdown signal and end emit (@ArtyDev)
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: Reset socket to non-blocking after shutting down (PHP bug)
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to React/Promise 2.0
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+* Bump React dependencies to v0.4
+
+## 0.3.3 (2013-07-08)
+
+* Version bump
+
+## 0.3.2 (2013-05-10)
+
+* Version bump
+
+## 0.3.1 (2013-04-21)
+
+* Feature: Support binding to IPv6 addresses (@clue)
+
+## 0.3.0 (2013-04-14)
+
+* Bump React dependencies to v0.3
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.0 (2012-09-10)
+
+* Bump React dependencies to v0.2
+
+## 0.1.1 (2012-07-12)
+
+* Version bump
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/vendor/react/socket/LICENSE b/vendor/react/socket/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/vendor/react/socket/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/socket/README.md b/vendor/react/socket/README.md
new file mode 100755
index 0000000..cf9b56b
--- /dev/null
+++ b/vendor/react/socket/README.md
@@ -0,0 +1,1419 @@
+# Socket
+
+[](https://travis-ci.org/reactphp/socket)
+
+Async, streaming plaintext TCP/IP and secure TLS socket server and client
+connections for [ReactPHP](https://reactphp.org/).
+
+The socket library provides re-usable interfaces for a socket-layer
+server and client based on the [`EventLoop`](https://github.com/reactphp/event-loop)
+and [`Stream`](https://github.com/reactphp/stream) components.
+Its server component allows you to build networking servers that accept incoming
+connections from networking clients (such as an HTTP server).
+Its client component allows you to build networking clients that establish
+outgoing connections to networking servers (such as an HTTP or database client).
+This library provides async, streaming means for all of this, so you can
+handle multiple concurrent connections without blocking.
+
+**Table of Contents**
+
+* [Quickstart example](#quickstart-example)
+* [Connection usage](#connection-usage)
+ * [ConnectionInterface](#connectioninterface)
+ * [getRemoteAddress()](#getremoteaddress)
+ * [getLocalAddress()](#getlocaladdress)
+* [Server usage](#server-usage)
+ * [ServerInterface](#serverinterface)
+ * [connection event](#connection-event)
+ * [error event](#error-event)
+ * [getAddress()](#getaddress)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [close()](#close)
+ * [Server](#server)
+ * [Advanced server usage](#advanced-server-usage)
+ * [TcpServer](#tcpserver)
+ * [SecureServer](#secureserver)
+ * [UnixServer](#unixserver)
+ * [LimitingServer](#limitingserver)
+ * [getConnections()](#getconnections)
+* [Client usage](#client-usage)
+ * [ConnectorInterface](#connectorinterface)
+ * [connect()](#connect)
+ * [Connector](#connector)
+ * [Advanced client usage](#advanced-client-usage)
+ * [TcpConnector](#tcpconnector)
+ * [DnsConnector](#dnsconnector)
+ * [SecureConnector](#secureconnector)
+ * [TimeoutConnector](#timeoutconnector)
+ * [UnixConnector](#unixconnector)
+ * [FixUriConnector](#fixeduriconnector)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Quickstart example
+
+Here is a server that closes the connection if you send it anything:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
+
+$socket->on('connection', function (ConnectionInterface $conn) {
+ $conn->write("Hello " . $conn->getRemoteAddress() . "!\n");
+ $conn->write("Welcome to this amazing server!\n");
+ $conn->write("Here's a tip: don't say anything.\n");
+
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Here's a client that outputs the output of said server and then attempts to
+send it a string:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$connector = new React\Socket\Connector($loop);
+
+$connector->connect('127.0.0.1:8080')->then(function (ConnectionInterface $conn) use ($loop) {
+ $conn->pipe(new React\Stream\WritableResourceStream(STDOUT, $loop));
+ $conn->write("Hello World!\n");
+});
+
+$loop->run();
+```
+
+## Connection usage
+
+### ConnectionInterface
+
+The `ConnectionInterface` is used to represent any incoming and outgoing
+connection, such as a normal TCP/IP connection.
+
+An incoming or outgoing connection is a duplex stream (both readable and
+writable) that implements React's
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+It contains additional properties for the local and remote address (client IP)
+where this connection has been established to/from.
+
+Most commonly, instances implementing this `ConnectionInterface` are emitted
+by all classes implementing the [`ServerInterface`](#serverinterface) and
+used by all classes implementing the [`ConnectorInterface`](#connectorinterface).
+
+Because the `ConnectionInterface` implements the underlying
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
+you can use any of its events and methods as usual:
+
+```php
+$connection->on('data', function ($chunk) {
+ echo $chunk;
+});
+
+$connection->on('end', function () {
+ echo 'ended';
+});
+
+$connection->on('error', function (Exception $e) {
+ echo 'error: ' . $e->getMessage();
+});
+
+$connection->on('close', function () {
+ echo 'closed';
+});
+
+$connection->write($data);
+$connection->end($data = null);
+$connection->close();
+// …
+```
+
+For more details, see the
+[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+
+#### getRemoteAddress()
+
+The `getRemoteAddress(): ?string` method returns the full remote address
+(URI) where this connection has been established with.
+
+```php
+$address = $connection->getRemoteAddress();
+echo 'Connection with ' . $address . PHP_EOL;
+```
+
+If the remote address can not be determined or is unknown at this time (such as
+after the connection has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+If this is a TCP/IP based connection and you only want the remote IP, you may
+use something like this:
+
+```php
+$address = $connection->getRemoteAddress();
+$ip = trim(parse_url($address, PHP_URL_HOST), '[]');
+echo 'Connection with ' . $ip . PHP_EOL;
+```
+
+#### getLocalAddress()
+
+The `getLocalAddress(): ?string` method returns the full local address
+(URI) where this connection has been established with.
+
+```php
+$address = $connection->getLocalAddress();
+echo 'Connection with ' . $address . PHP_EOL;
+```
+
+If the local address can not be determined or is unknown at this time (such as
+after the connection has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
+so they should not be confused.
+
+If your `TcpServer` instance is listening on multiple interfaces (e.g. using
+the address `0.0.0.0`), you can use this method to find out which interface
+actually accepted this connection (such as a public or local interface).
+
+If your system has multiple interfaces (e.g. a WAN and a LAN interface),
+you can use this method to find out which interface was actually
+used for this connection.
+
+## Server usage
+
+### ServerInterface
+
+The `ServerInterface` is responsible for providing an interface for accepting
+incoming streaming connections, such as a normal TCP/IP connection.
+
+Most higher-level components (such as a HTTP server) accept an instance
+implementing this interface to accept incoming streaming connections.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against any other implementation of this interface.
+This means that you SHOULD typehint against this interface instead of a concrete
+implementation of this interface.
+
+Besides defining a few methods, this interface also implements the
+[`EventEmitterInterface`](https://github.com/igorw/evenement)
+which allows you to react to certain events.
+
+#### connection event
+
+The `connection` event will be emitted whenever a new connection has been
+established, i.e. a new client connects to this server socket:
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'new connection' . PHP_EOL;
+});
+```
+
+See also the [`ConnectionInterface`](#connectioninterface) for more details
+about handling the incoming connection.
+
+#### error event
+
+The `error` event will be emitted whenever there's an error accepting a new
+connection from a client.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+Note that this is not a fatal error event, i.e. the server keeps listening for
+new connections even after this event.
+
+
+#### getAddress()
+
+The `getAddress(): ?string` method can be used to
+return the full address (URI) this server is currently listening on.
+
+```php
+$address = $server->getAddress();
+echo 'Server listening on ' . $address . PHP_EOL;
+```
+
+If the address can not be determined or is unknown at this time (such as
+after the socket has been closed), it MAY return a `NULL` value instead.
+
+Otherwise, it will return the full address (URI) as a string value, such
+as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`
+`unix://example.sock` or `unix:///path/to/example.sock`.
+Note that individual URI components are application specific and depend
+on the underlying transport protocol.
+
+If this is a TCP/IP based server and you only want the local port, you may
+use something like this:
+
+```php
+$address = $server->getAddress();
+$port = parse_url($address, PHP_URL_PORT);
+echo 'Server listening on port ' . $port . PHP_EOL;
+```
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause accepting new incoming connections.
+
+Removes the socket resource from the EventLoop and thus stop accepting
+new connections. Note that the listening socket stays active and is not
+closed.
+
+This means that new incoming connections will stay pending in the
+operating system backlog until its configurable backlog is filled.
+Once the backlog is filled, the operating system may reject further
+incoming connections until the backlog is drained again by resuming
+to accept new connections.
+
+Once the server is paused, no futher `connection` events SHOULD
+be emitted.
+
+```php
+$server->pause();
+
+$server->on('connection', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+server MAY continue emitting `connection` events.
+
+Unless otherwise noted, a successfully opened server SHOULD NOT start
+in paused state.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+Similarly, calling this after `close()` is a NO-OP.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume accepting new incoming connections.
+
+Re-attach the socket resource to the EventLoop after a previous `pause()`.
+
+```php
+$server->pause();
+
+$loop->addTimer(1.0, function () use ($server) {
+ $server->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+Similarly, calling this after `close()` is a NO-OP.
+
+#### close()
+
+The `close(): void` method can be used to
+shut down this listening socket.
+
+This will stop listening for new incoming connections on this socket.
+
+```php
+echo 'Shutting down server socket' . PHP_EOL;
+$server->close();
+```
+
+Calling this method more than once on the same instance is a NO-OP.
+
+### Server
+
+The `Server` class is the main class in this package that implements the
+[`ServerInterface`](#serverinterface) and allows you to accept incoming
+streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
+Connections can also be accepted on Unix domain sockets.
+
+```php
+$server = new Server(8080, $loop);
+```
+
+As above, the `$uri` parameter can consist of only a port, in which case the
+server will default to listening on the localhost address `127.0.0.1`,
+which means it will not be reachable from outside of this system.
+
+In order to use a random port assignment, you can use the port `0`:
+
+```php
+$server = new Server(0, $loop);
+$address = $server->getAddress();
+```
+
+In order to change the host the socket is listening on, you can provide an IP
+address through the first parameter provided to the constructor, optionally
+preceded by the `tcp://` scheme:
+
+```php
+$server = new Server('192.168.0.1:8080', $loop);
+```
+
+If you want to listen on an IPv6 address, you MUST enclose the host in square
+brackets:
+
+```php
+$server = new Server('[::1]:8080', $loop);
+```
+
+To listen on a Unix domain socket (UDS) path, you MUST prefix the URI with the
+`unix://` scheme:
+
+```php
+$server = new Server('unix:///tmp/server.sock', $loop);
+```
+
+If the given URI is invalid, does not contain a port, any other scheme or if it
+contains a hostname, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException due to missing port
+$server = new Server('127.0.0.1', $loop);
+```
+
+If the given URI appears to be valid, but listening on it fails (such as if port
+is already in use or port below 1024 may require root access etc.), it will
+throw a `RuntimeException`:
+
+```php
+$first = new Server(8080, $loop);
+
+// throws RuntimeException because port is already in use
+$second = new Server(8080, $loop);
+```
+
+> Note that these error conditions may vary depending on your system and/or
+ configuration.
+ See the exception message and code for more details about the actual error
+ condition.
+
+Optionally, you can specify [TCP socket context options](http://php.net/manual/en/context.socket.php)
+for the underlying stream socket resource like this:
+
+```php
+$server = new Server('[::1]:8080', $loop, array(
+ 'tcp' => array(
+ 'backlog' => 200,
+ 'so_reuseport' => true,
+ 'ipv6_v6only' => true
+ )
+));
+```
+
+> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+ their defaults and effects of changing these may vary depending on your system
+ and/or PHP version.
+ Passing unknown context options has no effect.
+ For BC reasons, you can also pass the TCP socket context options as a simple
+ array without wrapping this in another array under the `tcp` key.
+
+You can start a secure TLS (formerly known as SSL) server by simply prepending
+the `tls://` URI scheme.
+Internally, it will wait for plaintext TCP/IP connections and then performs a
+TLS handshake for each connection.
+It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
+which in its most basic form may look something like this if you're using a
+PEM encoded certificate file:
+
+```php
+$server = new Server('tls://127.0.0.1:8080', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem'
+ )
+));
+```
+
+> Note that the certificate file will not be loaded on instantiation but when an
+ incoming connection initializes its TLS context.
+ This implies that any invalid certificate file paths or contents will only cause
+ an `error` event at a later time.
+
+If your private key is encrypted with a passphrase, you have to specify it
+like this:
+
+```php
+$server = new Server('tls://127.0.0.1:8000', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem',
+ 'passphrase' => 'secret'
+ )
+));
+```
+
+By default, this server supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$server = new Server('tls://127.0.0.1:8000', $loop, array(
+ 'tls' => array(
+ 'local_cert' => 'server.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+ )
+));
+```
+
+> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
+ their defaults and effects of changing these may vary depending on your system
+ and/or PHP version.
+ The outer context array allows you to also use `tcp` (and possibly more)
+ context options at the same time.
+ Passing unknown context options has no effect.
+ If you do not use the `tls://` scheme, then passing `tls` context options
+ has no effect.
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+> Note that the `Server` class is a concrete implementation for TCP/IP sockets.
+ If you want to typehint in your higher-level protocol implementation, you SHOULD
+ use the generic [`ServerInterface`](#serverinterface) instead.
+
+### Advanced server usage
+
+#### TcpServer
+
+The `TcpServer` class implements the [`ServerInterface`](#serverinterface) and
+is responsible for accepting plaintext TCP/IP connections.
+
+```php
+$server = new TcpServer(8080, $loop);
+```
+
+As above, the `$uri` parameter can consist of only a port, in which case the
+server will default to listening on the localhost address `127.0.0.1`,
+which means it will not be reachable from outside of this system.
+
+In order to use a random port assignment, you can use the port `0`:
+
+```php
+$server = new TcpServer(0, $loop);
+$address = $server->getAddress();
+```
+
+In order to change the host the socket is listening on, you can provide an IP
+address through the first parameter provided to the constructor, optionally
+preceded by the `tcp://` scheme:
+
+```php
+$server = new TcpServer('192.168.0.1:8080', $loop);
+```
+
+If you want to listen on an IPv6 address, you MUST enclose the host in square
+brackets:
+
+```php
+$server = new TcpServer('[::1]:8080', $loop);
+```
+
+If the given URI is invalid, does not contain a port, any other scheme or if it
+contains a hostname, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException due to missing port
+$server = new TcpServer('127.0.0.1', $loop);
+```
+
+If the given URI appears to be valid, but listening on it fails (such as if port
+is already in use or port below 1024 may require root access etc.), it will
+throw a `RuntimeException`:
+
+```php
+$first = new TcpServer(8080, $loop);
+
+// throws RuntimeException because port is already in use
+$second = new TcpServer(8080, $loop);
+```
+
+> Note that these error conditions may vary depending on your system and/or
+configuration.
+See the exception message and code for more details about the actual error
+condition.
+
+Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php)
+for the underlying stream socket resource like this:
+
+```php
+$server = new TcpServer('[::1]:8080', $loop, array(
+ 'backlog' => 200,
+ 'so_reuseport' => true,
+ 'ipv6_v6only' => true
+));
+```
+
+> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+their defaults and effects of changing these may vary depending on your system
+and/or PHP version.
+Passing unknown context options has no effect.
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+#### SecureServer
+
+The `SecureServer` class implements the [`ServerInterface`](#serverinterface)
+and is responsible for providing a secure TLS (formerly known as SSL) server.
+
+It does so by wrapping a [`TcpServer`](#tcpserver) instance which waits for plaintext
+TCP/IP connections and then performs a TLS handshake for each connection.
+It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
+which in its most basic form may look something like this if you're using a
+PEM encoded certificate file:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem'
+));
+```
+
+> Note that the certificate file will not be loaded on instantiation but when an
+incoming connection initializes its TLS context.
+This implies that any invalid certificate file paths or contents will only cause
+an `error` event at a later time.
+
+If your private key is encrypted with a passphrase, you have to specify it
+like this:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem',
+ 'passphrase' => 'secret'
+));
+```
+
+By default, this server supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$server = new TcpServer(8000, $loop);
+$server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'server.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+));
+```
+
+> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
+their defaults and effects of changing these may vary depending on your system
+and/or PHP version.
+Passing unknown context options has no effect.
+
+Whenever a client completes the TLS handshake, it will emit a `connection` event
+with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+Whenever a client fails to perform a successful TLS handshake, it will emit an
+`error` event and then close the underlying TCP/IP connection:
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error' . $e->getMessage() . PHP_EOL;
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+Note that the `SecureServer` class is a concrete implementation for TLS sockets.
+If you want to typehint in your higher-level protocol implementation, you SHOULD
+use the generic [`ServerInterface`](#serverinterface) instead.
+
+> Advanced usage: Despite allowing any `ServerInterface` as first parameter,
+you SHOULD pass a `TcpServer` instance as first parameter, unless you
+know what you're doing.
+Internally, the `SecureServer` has to set the required TLS context options on
+the underlying stream resources.
+These resources are not exposed through any of the interfaces defined in this
+package, but only through the internal `Connection` class.
+The `TcpServer` class is guaranteed to emit connections that implement
+the `ConnectionInterface` and uses the internal `Connection` class in order to
+expose these underlying resources.
+If you use a custom `ServerInterface` and its `connection` event does not
+meet this requirement, the `SecureServer` will emit an `error` event and
+then close the underlying connection.
+
+#### UnixServer
+
+The `UnixServer` class implements the [`ServerInterface`](#serverinterface) and
+is responsible for accepting connections on Unix domain sockets (UDS).
+
+```php
+$server = new UnixServer('/tmp/server.sock', $loop);
+```
+
+As above, the `$uri` parameter can consist of only a socket path or socket path
+prefixed by the `unix://` scheme.
+
+If the given URI appears to be valid, but listening on it fails (such as if the
+socket is already in use or the file not accessible etc.), it will throw a
+`RuntimeException`:
+
+```php
+$first = new UnixServer('/tmp/same.sock', $loop);
+
+// throws RuntimeException because socket is already in use
+$second = new UnixServer('/tmp/same.sock', $loop);
+```
+
+Whenever a client connects, it will emit a `connection` event with a connection
+instance implementing [`ConnectionInterface`](#connectioninterface):
+
+```php
+$server->on('connection', function (ConnectionInterface $connection) {
+ echo 'New connection' . PHP_EOL;
+
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [`ServerInterface`](#serverinterface) for more details.
+
+#### LimitingServer
+
+The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
+for limiting and keeping track of open connections to this server instance.
+
+Whenever the underlying server emits a `connection` event, it will check its
+limits and then either
+ - keep track of this connection by adding it to the list of
+ open connections and then forward the `connection` event
+ - or reject (close) the connection when its limits are exceeded and will
+ forward an `error` event instead.
+
+Whenever a connection closes, it will remove this connection from the list of
+open connections.
+
+```php
+$server = new LimitingServer($server, 100);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+See also the [second example](examples) for more details.
+
+You have to pass a maximum number of open connections to ensure
+the server will automatically reject (close) connections once this limit
+is exceeded. In this case, it will emit an `error` event to inform about
+this and no `connection` event will be emitted.
+
+```php
+$server = new LimitingServer($server, 100);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+You MAY pass a `null` limit in order to put no limit on the number of
+open connections and keep accepting new connection until you run out of
+operating system resources (such as open file handles). This may be
+useful if you do not want to take care of applying a limit but still want
+to use the `getConnections()` method.
+
+You can optionally configure the server to pause accepting new
+connections once the connection limit is reached. In this case, it will
+pause the underlying server and no longer process any new connections at
+all, thus also no longer closing any excessive connections.
+The underlying operating system is responsible for keeping a backlog of
+pending connections until its limit is reached, at which point it will
+start rejecting further connections.
+Once the server is below the connection limit, it will continue consuming
+connections from the backlog and will process any outstanding data on
+each connection.
+This mode may be useful for some protocols that are designed to wait for
+a response message (such as HTTP), but may be less useful for other
+protocols that demand immediate responses (such as a "welcome" message in
+an interactive chat).
+
+```php
+$server = new LimitingServer($server, 100, true);
+$server->on('connection', function (ConnectionInterface $connection) {
+ $connection->write('hello there!' . PHP_EOL);
+ …
+});
+```
+
+##### getConnections()
+
+The `getConnections(): ConnectionInterface[]` method can be used to
+return an array with all currently active connections.
+
+```php
+foreach ($server->getConnection() as $connection) {
+ $connection->write('Hi!');
+}
+```
+
+## Client usage
+
+### ConnectorInterface
+
+The `ConnectorInterface` is responsible for providing an interface for
+establishing streaming connections, such as a normal TCP/IP connection.
+
+This is the main interface defined in this package and it is used throughout
+React's vast ecosystem.
+
+Most higher-level components (such as HTTP, database or other networking
+service clients) accept an instance implementing this interface to create their
+TCP/IP connection to the underlying networking service.
+This is usually done via dependency injection, so it's fairly simple to actually
+swap this implementation against any other implementation of this interface.
+
+The interface only offers a single method:
+
+#### connect()
+
+The `connect(string $uri): PromiseInterface` method
+can be used to create a streaming connection to the given remote address.
+
+It returns a [Promise](https://github.com/reactphp/promise) which either
+fulfills with a stream implementing [`ConnectionInterface`](#connectioninterface)
+on success or rejects with an `Exception` if the connection is not successful:
+
+```php
+$connector->connect('google.com:443')->then(
+ function (ConnectionInterface $connection) {
+ // connection successfully established
+ },
+ function (Exception $error) {
+ // failed to connect due to $error
+ }
+);
+```
+
+See also [`ConnectionInterface`](#connectioninterface) for more details.
+
+The returned Promise MUST be implemented in such a way that it can be
+cancelled when it is still pending. Cancelling a pending promise MUST
+reject its value with an `Exception`. It SHOULD clean up any underlying
+resources and references as applicable:
+
+```php
+$promise = $connector->connect($uri);
+
+$promise->cancel();
+```
+
+### Connector
+
+The `Connector` class is the main class in this package that implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create streaming connections.
+
+You can use this connector to create any kind of streaming connections, such
+as plaintext TCP/IP, secure TLS or local Unix connection streams.
+
+It binds to the main event loop and can be used like this:
+
+```php
+$loop = React\EventLoop\Factory::create();
+$connector = new Connector($loop);
+
+$connector->connect($uri)->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+In order to create a plaintext TCP/IP connection, you can simply pass a host
+and port combination like this:
+
+```php
+$connector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> If you do no specify a URI scheme in the destination URI, it will assume
+ `tcp://` as a default and establish a plaintext TCP/IP connection.
+ Note that TCP/IP connections require a host and port part in the destination
+ URI like above, all other URI components are optional.
+
+In order to create a secure TLS connection, you can use the `tls://` URI scheme
+like this:
+
+```php
+$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+In order to create a local Unix domain socket connection, you can use the
+`unix://` URI scheme like this:
+
+```php
+$connector->connect('unix:///tmp/demo.sock')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> The [`getRemoteAddress()`](#getremoteaddress) method will return the target
+ Unix domain socket (UDS) path as given to the `connect()` method, including
+ the `unix://` scheme, for example `unix:///tmp/demo.sock`.
+ The [`getLocalAddress()`](#getlocaladdress) method will most likely return a
+ `null` value as this value is not applicable to UDS connections here.
+
+Under the hood, the `Connector` is implemented as a *higher-level facade*
+for the lower-level connectors implemented in this package. This means it
+also shares all of their features and implementation details.
+If you want to typehint in your higher-level protocol implementation, you SHOULD
+use the generic [`ConnectorInterface`](#connectorinterface) instead.
+
+The `Connector` class will try to detect your system DNS settings (and uses
+Google's public DNS server `8.8.8.8` as a fallback if unable to determine your
+system settings) to resolve all public hostnames into underlying IP addresses by
+default.
+If you explicitly want to use a custom DNS server (such as a local DNS relay or
+a company wide DNS server), you can set up the `Connector` like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'dns' => '127.0.1.1'
+));
+
+$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+If you do not want to use a DNS resolver at all and want to connect to IP
+addresses only, you can also set up your `Connector` like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'dns' => false
+));
+
+$connector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+Advanced: If you need a custom DNS `Resolver` instance, you can also set up
+your `Connector` like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);
+
+$connector = new Connector($loop, array(
+ 'dns' => $resolver
+));
+
+$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+By default, the `tcp://` and `tls://` URI schemes will use timeout value that
+repects your `default_socket_timeout` ini setting (which defaults to 60s).
+If you want a custom timeout value, you can simply pass this like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'timeout' => 10.0
+));
+```
+
+Similarly, if you do not want to apply a timeout at all and let the operating
+system handle this, you can pass a boolean flag like this:
+
+```php
+$connector = new Connector($loop, array(
+ 'timeout' => false
+));
+```
+
+By default, the `Connector` supports the `tcp://`, `tls://` and `unix://`
+URI schemes. If you want to explicitly prohibit any of these, you can simply
+pass boolean flags like this:
+
+```php
+// only allow secure TLS connections
+$connector = new Connector($loop, array(
+ 'tcp' => false,
+ 'tls' => true,
+ 'unix' => false,
+));
+
+$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+The `tcp://` and `tls://` also accept additional context options passed to
+the underlying connectors.
+If you want to explicitly pass additional context options, you can simply
+pass arrays of context options like this:
+
+```php
+// allow insecure TLS connections
+$connector = new Connector($loop, array(
+ 'tcp' => array(
+ 'bindto' => '192.168.0.1:0'
+ ),
+ 'tls' => array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ),
+));
+
+$connector->connect('tls://localhost:443')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+By default, this connector supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$connector = new Connector($loop, array(
+ 'tls' => array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+ )
+));
+```
+
+> For more details about context options, please refer to the PHP documentation
+ about [socket context options](http://php.net/manual/en/context.socket.php)
+ and [SSL context options](http://php.net/manual/en/context.ssl.php).
+
+Advanced: By default, the `Connector` supports the `tcp://`, `tls://` and
+`unix://` URI schemes.
+For this, it sets up the required connector classes automatically.
+If you want to explicitly pass custom connectors for any of these, you can simply
+pass an instance implementing the `ConnectorInterface` like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);
+$tcp = new DnsConnector(new TcpConnector($loop), $resolver);
+
+$tls = new SecureConnector($tcp, $loop);
+
+$unix = new UnixConnector($loop);
+
+$connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'tls' => $tls,
+ 'unix' => $unix,
+
+ 'dns' => false,
+ 'timeout' => false,
+));
+
+$connector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+```
+
+> Internally, the `tcp://` connector will always be wrapped by the DNS resolver,
+ unless you disable DNS like in the above example. In this case, the `tcp://`
+ connector receives the actual hostname instead of only the resolved IP address
+ and is thus responsible for performing the lookup.
+ Internally, the automatically created `tls://` connector will always wrap the
+ underlying `tcp://` connector for establishing the underlying plaintext
+ TCP/IP connection before enabling secure TLS mode. If you want to use a custom
+ underlying `tcp://` connector for secure TLS connections only, you may
+ explicitly pass a `tls://` connector like above instead.
+ Internally, the `tcp://` and `tls://` connectors will always be wrapped by
+ `TimeoutConnector`, unless you disable timeouts like in the above example.
+
+### Advanced client usage
+
+#### TcpConnector
+
+The `React\Socket\TcpConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext
+TCP/IP connections to any IP-port-combination:
+
+```php
+$tcpConnector = new React\Socket\TcpConnector($loop);
+
+$tcpConnector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $tcpConnector->connect('127.0.0.1:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will close the underlying socket
+resource, thus cancelling the pending TCP/IP connection, and reject the
+resulting promise.
+
+You can optionally pass additional
+[socket context options](http://php.net/manual/en/context.socket.php)
+to the constructor like this:
+
+```php
+$tcpConnector = new React\Socket\TcpConnector($loop, array(
+ 'bindto' => '192.168.0.1:0'
+));
+```
+
+Note that this class only allows you to connect to IP-port-combinations.
+If the given URI is invalid, does not contain a valid IP address and port
+or contains any other scheme, it will reject with an
+`InvalidArgumentException`:
+
+If the given URI appears to be valid, but connecting to it fails (such as if
+the remote host rejects the connection etc.), it will reject with a
+`RuntimeException`.
+
+If you want to connect to hostname-port-combinations, see also the following chapter.
+
+> Advanced usage: Internally, the `TcpConnector` allocates an empty *context*
+resource for each stream resource.
+If the destination URI contains a `hostname` query parameter, its value will
+be used to set up the TLS peer name.
+This is used by the `SecureConnector` and `DnsConnector` to verify the peer
+name and can also be used if you want a custom TLS peer name.
+
+#### DnsConnector
+
+The `DnsConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create plaintext
+TCP/IP connections to any hostname-port-combination.
+
+It does so by decorating a given `TcpConnector` instance so that it first
+looks up the given domain name via DNS (if applicable) and then establishes the
+underlying TCP/IP connection to the resolved target IP address.
+
+Make sure to set up your DNS resolver and underlying TCP connector like this:
+
+```php
+$dnsResolverFactory = new React\Dns\Resolver\Factory();
+$dns = $dnsResolverFactory->createCached('8.8.8.8', $loop);
+
+$dnsConnector = new React\Socket\DnsConnector($tcpConnector, $dns);
+
+$dnsConnector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
+ $connection->write('...');
+ $connection->end();
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $dnsConnector->connect('www.google.com:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying DNS lookup
+and/or the underlying TCP/IP connection and reject the resulting promise.
+
+> Advanced usage: Internally, the `DnsConnector` relies on a `Resolver` to
+look up the IP address for the given hostname.
+It will then replace the hostname in the destination URI with this IP and
+append a `hostname` query parameter and pass this updated URI to the underlying
+connector.
+The underlying connector is thus responsible for creating a connection to the
+target IP address, while this query parameter can be used to check the original
+hostname and is used by the `TcpConnector` to set up the TLS peer name.
+If a `hostname` is given explicitly, this query parameter will not be modified,
+which can be useful if you want a custom TLS peer name.
+
+#### SecureConnector
+
+The `SecureConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to create secure
+TLS (formerly known as SSL) connections to any hostname-port-combination.
+
+It does so by decorating a given `DnsConnector` instance so that it first
+creates a plaintext TCP/IP connection and then enables TLS encryption on this
+stream.
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop);
+
+$secureConnector->connect('www.google.com:443')->then(function (ConnectionInterface $connection) {
+ $connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
+ ...
+});
+
+$loop->run();
+```
+
+See also the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $secureConnector->connect('www.google.com:443');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying TCP/IP
+connection and/or the SSL/TLS negotiation and reject the resulting promise.
+
+You can optionally pass additional
+[SSL context options](http://php.net/manual/en/context.ssl.php)
+to the constructor like this:
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+));
+```
+
+By default, this connector supports TLSv1.0+ and excludes support for legacy
+SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
+want to negotiate with the remote side:
+
+```php
+$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array(
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
+));
+```
+
+> Advanced usage: Internally, the `SecureConnector` relies on setting up the
+required *context options* on the underlying stream resource.
+It should therefor be used with a `TcpConnector` somewhere in the connector
+stack so that it can allocate an empty *context* resource for each stream
+resource and verify the peer name.
+Failing to do so may result in a TLS peer name mismatch error or some hard to
+trace race conditions, because all stream resources will use a single, shared
+*default context* resource otherwise.
+
+#### TimeoutConnector
+
+The `TimeoutConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to add timeout
+handling to any existing connector instance.
+
+It does so by decorating any given [`ConnectorInterface`](#connectorinterface)
+instance and starting a timer that will automatically reject and abort any
+underlying connection attempt if it takes too long.
+
+```php
+$timeoutConnector = new React\Socket\TimeoutConnector($connector, 3.0, $loop);
+
+$timeoutConnector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
+ // connection succeeded within 3.0 seconds
+});
+```
+
+See also any of the [examples](examples).
+
+Pending connection attempts can be cancelled by cancelling its pending promise like so:
+
+```php
+$promise = $timeoutConnector->connect('google.com:80');
+
+$promise->cancel();
+```
+
+Calling `cancel()` on a pending promise will cancel the underlying connection
+attempt, abort the timer and reject the resulting promise.
+
+#### UnixConnector
+
+The `UnixConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and allows you to connect to
+Unix domain socket (UDS) paths like this:
+
+```php
+$connector = new React\Socket\UnixConnector($loop);
+
+$connector->connect('/tmp/demo.sock')->then(function (ConnectionInterface $connection) {
+ $connection->write("HELLO\n");
+});
+
+$loop->run();
+```
+
+Connecting to Unix domain sockets is an atomic operation, i.e. its promise will
+settle (either resolve or reject) immediately.
+As such, calling `cancel()` on the resulting promise has no effect.
+
+> The [`getRemoteAddress()`](#getremoteaddress) method will return the target
+ Unix domain socket (UDS) path as given to the `connect()` method, prepended
+ with the `unix://` scheme, for example `unix:///tmp/demo.sock`.
+ The [`getLocalAddress()`](#getlocaladdress) method will most likely return a
+ `null` value as this value is not applicable to UDS connections here.
+
+#### FixedUriConnector
+
+The `FixedUriConnector` class implements the
+[`ConnectorInterface`](#connectorinterface) and decorates an existing Connector
+to always use a fixed, preconfigured URI.
+
+This can be useful for consumers that do not support certain URIs, such as
+when you want to explicitly connect to a Unix domain socket (UDS) path
+instead of connecting to a default address assumed by an higher-level API:
+
+```php
+$connector = new FixedUriConnector(
+ 'unix:///var/run/docker.sock',
+ new UnixConnector($loop)
+);
+
+// destination will be ignored, actually connects to Unix domain socket
+$promise = $connector->connect('localhost:80');
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/socket:^0.8.12
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project, partly due to its vast
+performance improvements and partly because legacy PHP versions require several
+workarounds as described below.
+
+Secure TLS connections received some major upgrades starting with PHP 5.6, with
+the defaults now being more secure, while older versions required explicit
+context options.
+This library does not take responsibility over these context options, so it's
+up to consumers of this library to take care of setting appropriate context
+options as described above.
+
+All versions of PHP prior to 5.6.8 suffered from a buffering issue where reading
+from a streaming TLS connection could be one `data` event behind.
+This library implements a work-around to try to flush the complete incoming
+data buffers on these legacy PHP versions, which has a penalty of around 10% of
+throughput on all connections.
+With this work-around, we have not been able to reproduce this issue anymore,
+but we have seen reports of people saying this could still affect some of the
+older PHP versions (`5.5.23`, `5.6.7`, and `5.6.8`).
+Note that this only affects *some* higher-level streaming protocols, such as
+IRC over TLS, but should not affect HTTP over TLS (HTTPS).
+Further investigation of this issue is needed.
+For more insights, this issue is also covered by our test suite.
+
+PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
+chunks of data over TLS streams at once.
+We try to work around this by limiting the write chunk size to 8192
+bytes for older PHP versions only.
+This is only a work-around and has a noticable performance penalty on
+affected versions.
+
+This project also supports running on HHVM.
+Note that really old HHVM < 3.8 does not support secure TLS connections, as it
+lacks the required `stream_socket_enable_crypto()` function.
+As such, trying to create a secure TLS connections on affected versions will
+return a rejected promise instead.
+This issue is also covered by our test suite, which will skip related tests
+on affected versions.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
diff --git a/vendor/react/socket/composer.json b/vendor/react/socket/composer.json
new file mode 100755
index 0000000..cad0aef
--- /dev/null
+++ b/vendor/react/socket/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "react/socket",
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": ["async", "socket", "stream", "connection", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.0",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/dns": "^0.4.13",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "react/stream": "^1.0 || ^0.7.1",
+ "react/promise": "^2.6.0 || ^1.2.1",
+ "react/promise-timer": "^1.4.0"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.2",
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Socket\\": "tests"
+ }
+ }
+}
diff --git a/vendor/react/socket/examples/01-echo-server.php b/vendor/react/socket/examples/01-echo-server.php
new file mode 100755
index 0000000..2c0be57
--- /dev/null
+++ b/vendor/react/socket/examples/01-echo-server.php
@@ -0,0 +1,42 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) {
+ echo '[' . $conn->getRemoteAddress() . ' connected]' . PHP_EOL;
+ $conn->pipe($conn);
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/react/socket/examples/02-chat-server.php b/vendor/react/socket/examples/02-chat-server.php
new file mode 100755
index 0000000..46439e0
--- /dev/null
+++ b/vendor/react/socket/examples/02-chat-server.php
@@ -0,0 +1,59 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server = new LimitingServer($server, null);
+
+$server->on('connection', function (ConnectionInterface $client) use ($server) {
+ // whenever a new message comes in
+ $client->on('data', function ($data) use ($client, $server) {
+ // remove any non-word characters (just for the demo)
+ $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data));
+
+ // ignore empty messages
+ if ($data === '') {
+ return;
+ }
+
+ // prefix with client IP and broadcast to all connected clients
+ $data = trim(parse_url($client->getRemoteAddress(), PHP_URL_HOST), '[]') . ': ' . $data . PHP_EOL;
+ foreach ($server->getConnections() as $connection) {
+ $connection->write($data);
+ }
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/react/socket/examples/03-http-server.php b/vendor/react/socket/examples/03-http-server.php
new file mode 100755
index 0000000..eb6d454
--- /dev/null
+++ b/vendor/react/socket/examples/03-http-server.php
@@ -0,0 +1,57 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) {
+ $conn->once('data', function () use ($conn) {
+ $body = "
Hello world!
\r\n";
+ $conn->end("HTTP/1.1 200 OK\r\nContent-Length: " . strlen($body) . "\r\nConnection: close\r\n\r\n" . $body);
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . strtr($server->getAddress(), array('tcp:' => 'http:', 'tls:' => 'https:')) . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/react/socket/examples/11-http-client.php b/vendor/react/socket/examples/11-http-client.php
new file mode 100755
index 0000000..2b64a43
--- /dev/null
+++ b/vendor/react/socket/examples/11-http-client.php
@@ -0,0 +1,36 @@
+connect($host. ':80')->then(function (ConnectionInterface $connection) use ($host) {
+ $connection->on('data', function ($data) {
+ echo $data;
+ });
+ $connection->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+ });
+
+ $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/react/socket/examples/12-https-client.php b/vendor/react/socket/examples/12-https-client.php
new file mode 100755
index 0000000..6e3f279
--- /dev/null
+++ b/vendor/react/socket/examples/12-https-client.php
@@ -0,0 +1,36 @@
+connect('tls://' . $host . ':443')->then(function (ConnectionInterface $connection) use ($host) {
+ $connection->on('data', function ($data) {
+ echo $data;
+ });
+ $connection->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+ });
+
+ $connection->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/react/socket/examples/21-netcat-client.php b/vendor/react/socket/examples/21-netcat-client.php
new file mode 100755
index 0000000..9140e2c
--- /dev/null
+++ b/vendor/react/socket/examples/21-netcat-client.php
@@ -0,0 +1,68 @@
+' . PHP_EOL);
+ exit(1);
+}
+
+$loop = Factory::create();
+$connector = new Connector($loop);
+
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdin->pause();
+$stdout = new WritableResourceStream(STDOUT, $loop);
+$stderr = new WritableResourceStream(STDERR, $loop);
+
+$stderr->write('Connecting' . PHP_EOL);
+
+$connector->connect($argv[1])->then(function (ConnectionInterface $connection) use ($stdin, $stdout, $stderr) {
+ // pipe everything from STDIN into connection
+ $stdin->resume();
+ $stdin->pipe($connection);
+
+ // pipe everything from connection to STDOUT
+ $connection->pipe($stdout);
+
+ // report errors to STDERR
+ $connection->on('error', function ($error) use ($stderr) {
+ $stderr->write('Stream ERROR: ' . $error . PHP_EOL);
+ });
+
+ // report closing and stop reading from input
+ $connection->on('close', function () use ($stderr, $stdin) {
+ $stderr->write('[CLOSED]' . PHP_EOL);
+ $stdin->close();
+ });
+
+ $stderr->write('Connected' . PHP_EOL);
+}, function ($error) use ($stderr) {
+ $stderr->write('Connection ERROR: ' . $error . PHP_EOL);
+});
+
+$loop->run();
diff --git a/vendor/react/socket/examples/22-http-client.php b/vendor/react/socket/examples/22-http-client.php
new file mode 100755
index 0000000..fcb8107
--- /dev/null
+++ b/vendor/react/socket/examples/22-http-client.php
@@ -0,0 +1,60 @@
+' . PHP_EOL);
+ exit(1);
+}
+
+$loop = Factory::create();
+$connector = new Connector($loop);
+
+if (!isset($parts['port'])) {
+ $parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
+}
+
+$host = $parts['host'];
+if (($parts['scheme'] === 'http' && $parts['port'] !== 80) || ($parts['scheme'] === 'https' && $parts['port'] !== 443)) {
+ $host .= ':' . $parts['port'];
+}
+$target = ($parts['scheme'] === 'https' ? 'tls' : 'tcp') . '://' . $parts['host'] . ':' . $parts['port'];
+$resource = isset($parts['path']) ? $parts['path'] : '/';
+if (isset($parts['query'])) {
+ $resource .= '?' . $parts['query'];
+}
+
+$stdout = new WritableResourceStream(STDOUT, $loop);
+
+$connector->connect($target)->then(function (ConnectionInterface $connection) use ($resource, $host, $stdout) {
+ $connection->pipe($stdout);
+
+ $connection->write("GET $resource HTTP/1.0\r\nHost: $host\r\n\r\n");
+}, 'printf');
+
+$loop->run();
diff --git a/vendor/react/socket/examples/91-benchmark-server.php b/vendor/react/socket/examples/91-benchmark-server.php
new file mode 100755
index 0000000..420d474
--- /dev/null
+++ b/vendor/react/socket/examples/91-benchmark-server.php
@@ -0,0 +1,60 @@
+ array(
+ 'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
+ )
+));
+
+$server->on('connection', function (ConnectionInterface $conn) use ($loop) {
+ echo '[connected]' . PHP_EOL;
+
+ // count the number of bytes received from this connection
+ $bytes = 0;
+ $conn->on('data', function ($chunk) use (&$bytes) {
+ $bytes += strlen($chunk);
+ });
+
+ // report average throughput once client disconnects
+ $t = microtime(true);
+ $conn->on('close', function () use ($conn, $t, &$bytes) {
+ $t = microtime(true) - $t;
+ echo '[disconnected after receiving ' . $bytes . ' bytes in ' . round($t, 3) . 's => ' . round($bytes / $t / 1024 / 1024, 1) . ' MiB/s]' . PHP_EOL;
+ });
+});
+
+$server->on('error', 'printf');
+
+echo 'Listening on ' . $server->getAddress() . PHP_EOL;
+
+$loop->run();
diff --git a/vendor/react/socket/examples/99-generate-self-signed.php b/vendor/react/socket/examples/99-generate-self-signed.php
new file mode 100755
index 0000000..00f9314
--- /dev/null
+++ b/vendor/react/socket/examples/99-generate-self-signed.php
@@ -0,0 +1,31 @@
+ secret.pem
+
+// certificate details (Distinguished Name)
+// (OpenSSL applies defaults to missing fields)
+$dn = array(
+ "commonName" => isset($argv[1]) ? $argv[1] : "localhost",
+// "countryName" => "AU",
+// "stateOrProvinceName" => "Some-State",
+// "localityName" => "London",
+// "organizationName" => "Internet Widgits Pty Ltd",
+// "organizationalUnitName" => "R&D",
+// "emailAddress" => "admin@example.com"
+);
+
+// create certificate which is valid for ~10 years
+$privkey = openssl_pkey_new();
+$cert = openssl_csr_new($dn, $privkey);
+$cert = openssl_csr_sign($cert, null, $privkey, 3650);
+
+// export public and (optionally encrypted) private key in PEM format
+openssl_x509_export($cert, $out);
+echo $out;
+
+$passphrase = isset($argv[2]) ? $argv[2] : null;
+openssl_pkey_export($privkey, $out, $passphrase);
+echo $out;
diff --git a/vendor/react/socket/examples/localhost.pem b/vendor/react/socket/examples/localhost.pem
new file mode 100755
index 0000000..be69279
--- /dev/null
+++ b/vendor/react/socket/examples/localhost.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
+MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
+BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
+0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
+7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
+824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
+V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
+IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
+W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
+2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
+zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
+UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
+wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
+YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
+ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
+g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
+tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
+LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
+tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
+9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
+43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
+pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
+OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
+2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
+li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
+b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
+vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
+XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
+Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
+iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
++EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
+y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
+81oh1uCH1YPLM29hPyaohxL8
+-----END PRIVATE KEY-----
diff --git a/vendor/react/socket/examples/localhost_swordfish.pem b/vendor/react/socket/examples/localhost_swordfish.pem
new file mode 100755
index 0000000..7d1ee80
--- /dev/null
+++ b/vendor/react/socket/examples/localhost_swordfish.pem
@@ -0,0 +1,51 @@
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
+MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
+DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQxMDQzWhcNMjYx
+MjI4MTQxMDQzWjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRXt83SrKIHr/i
+3lc8O8pz6NHE1DNHJa4xg2xalXWzCEV6m1qLd9VdaLT9cJD1afNmEMBgY6RblNL/
+paJWVoR9MOUeIoYl2PrhUCxsf7h6MRtezQQe3e+n+/0XunF0JUQIZuJqbxfRk5WT
+XmYnphqOZKEcistAYvFBjzl/D+Cl/nYsreADc+t9l5Vni89oTWEuqIrsM4WUZqqB
+VMAakd2nZJLWIrMxq9hbW1XNukOQfcmZVFTC6CUnLq8qzGbtfZYBuMBACnL1k/E/
+yPaAgR46l14VAcndDUJBtMeL2qYuNwvXQhg3KuBmpTUpH+yzxU+4T3lmv0xXmPqu
+ySH3xvW3AgMBAAGjUDBOMB0GA1UdDgQWBBRu68WTI4pVeTB7wuG9QGI3Ie441TAf
+BgNVHSMEGDAWgBRu68WTI4pVeTB7wuG9QGI3Ie441TAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQCc4pEjEHO47VRJkbHgC+c2gAVgxekkaA1czBA1uAvh
+ILRda0NLlvyftbjaG0zZp2ABUCfRfksl/Pf/PzWLUMEuH/9kEW2rgP43z6YgiL6k
+kBPlmAU607UjD726RPGkw8QPSXS/dWiNJ5CBpPWLpxC45pokqItYbY0ijQ5Piq09
+TchYlCX044oSRnPiP394PQ3HVdaGhJB2DnjDq3in5dVivFf8EdgzQSvp/wXy3WQs
+uFSVonSnrZGY/4AgT3psGaQ6fqKb4SBoqtf5bFQvp1XNNRkuEJnS/0dygEya0c+c
+aCe/1gXC2wDjx0/TekY5m1Nyw5SY6z7stOqL/ekwgejt
+-----END CERTIFICATE-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIG7idPRLgiHkCAggA
+MBQGCCqGSIb3DQMHBAg+MLPdepHWSwSCBMgVW9LseCjfTAmF9U1qRnKsq3kIwEnW
+6aERBqs/mnmEhrXgZYgcvRRK7kD12TdHt/Nz46Ymu0h+Lrvuwtl1fHQUARTk/gFh
+onLhc9kjMUhLRIR007vJe3HvWOb/v+SBSDB38OpUxUwJmBVBuSaYLWVuPR6J5kUj
+xOgBS049lN3E9cfrHvb3bF/epIQrU0OgfyyxEvIi5n30y+tlRn3y68PY6Qd46t4Y
+UN5VZUwvJBgoRy9TGxSkiSRjhxC2PWpLYq/HMzDbcRcFF5dVAIioUd/VZ7fdgBfA
+uMW4SFfpFLDUX0aaYe+ZdA5tM0Bc0cOtG8Z0sc9JYDNNcvjmSiGCi646h8F0D3O6
+JKAQMMxQGWiyQeJ979LVjtq4lJESXA8VEKz9rV03y5xunmFCLy6dGt+6GJwXgabn
+OH7nvEv4GqAOqKc6E9je4JM+AF/oUazrfPse1KEEtsPKarazjCB/SKYtHyDJaavD
+GGjtiU9zWwGMOgIDyNmXe3ga7/TWoGOAg5YlTr6Hbq2Y/5ycgjAgPFjuXtvnoT0+
+mF5TnNfMAqTgQsE2gjhonK1pdlOen0lN5FtoUXp3CXU0dOq0J70GiX+1YA7VDn30
+n5WNAgfOXX3l3E95jGN370pHXyli5RUNW0NZVHV+22jlNWCVtQHUh+DVswQZg+i5
++DqaIHz2jUetMo7gWtqGn/wwSopOs87VM1rcALhZL4EsJ+Zy81I/hA32RNnGbuol
+NAiZh+0KrtTcc/fPunpd8vRtOwGphM11dKucozUufuiPG2inR3aEqt5yNx54ec/f
+J6nryWRYiHEA/rCU9MSBM9cqKFtEmy9/8oxV41/SPxhXjHwDlABWTtFuJ3pf2sOF
+ILSYYFwB0ZGvdjE5yAJFBr9efno/L9fafmGk7a3vmVgK2AmUC9VNB5XHw1GjF8OP
+aQAXe4md9Bh0jk/D/iyp7e7IWNssul/7XejhabidWgFj6EXc9YxE59+FlhDqyMhn
+V6houc+QeUXuwsAKgRJJhJtpv/QSZ5BI3esxHHUt3ayGnvhFElpAc0t7C/EiXKIv
+DAFYP2jksBqijM8YtEgPWYzEP5buYxZnf/LK7FDocLsNcdF38UaKBbeF90e7bR8j
+SHspG9aJWICu8Yawnh8zuy/vQv+h9gWyGodd2p9lQzlbRXrutbwfmPf7xP6nzT9i
+9GcugJxTaZgkCfhhHxFk/nRHS2NAzagKVib1xkUlZJg2hX0fIFUdYteL1GGTvOx5
+m3mTOino4T19z9SEdZYb2OHYh29e/T74bJiLCYdXwevSYHxfZc8pYAf0jp4UnMT2
+f7B0ctX1iXuQ2uZVuxh+U1Mcu+v0gDla1jWh7AhcePSi4xBNUCak0kQip6r5e6Oi
+r4MIyMRk/Pc5pzEKo8G6nk26rNvX3aRvECoVfmK7IVdsqZ6IXlt9kOmWx3IeKzrO
+J5DxpzW+9oIRZJgPTkc4/XRb0tFmFQYTiChiQ1AJUEiCX0GpkFf7cq61aLGYtWyn
+vL2lmQhljzjrDo15hKErvk7eBZW7GW/6j/m/PfRdcBI4ceuP9zWQXnDOd9zmaE4b
+q3bJ+IbbyVZA2WwyzN7umCKWghsiPMAolxEnYM9JRf8BcqeqQiwVZlfO5KFuN6Ze
+le4=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/vendor/react/socket/phpunit.xml.dist b/vendor/react/socket/phpunit.xml.dist
new file mode 100755
index 0000000..13d3fab
--- /dev/null
+++ b/vendor/react/socket/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/vendor/react/socket/src/Connection.php b/vendor/react/socket/src/Connection.php
new file mode 100755
index 0000000..c6267cc
--- /dev/null
+++ b/vendor/react/socket/src/Connection.php
@@ -0,0 +1,178 @@
+= 70100 && PHP_VERSION_ID < 70104));
+
+ $this->input = new DuplexResourceStream(
+ $resource,
+ $loop,
+ $clearCompleteBuffer ? -1 : null,
+ new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null)
+ );
+
+ $this->stream = $resource;
+
+ Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain'));
+
+ $this->input->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->input->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->input->isWritable();
+ }
+
+ public function pause()
+ {
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ $this->input->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return $this->input->pipe($dest, $options);
+ }
+
+ public function write($data)
+ {
+ return $this->input->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->input->end($data);
+ }
+
+ public function close()
+ {
+ $this->input->close();
+ $this->handleClose();
+ $this->removeAllListeners();
+ }
+
+ public function handleClose()
+ {
+ if (!is_resource($this->stream)) {
+ return;
+ }
+
+ // Try to cleanly shut down socket and ignore any errors in case other
+ // side already closed. Shutting down may return to blocking mode on
+ // some legacy versions, so reset to non-blocking just in case before
+ // continuing to close the socket resource.
+ // Underlying Stream implementation will take care of closing file
+ // handle, so we otherwise keep this open here.
+ @stream_socket_shutdown($this->stream, STREAM_SHUT_RDWR);
+ stream_set_blocking($this->stream, false);
+ }
+
+ public function getRemoteAddress()
+ {
+ return $this->parseAddress(@stream_socket_get_name($this->stream, true));
+ }
+
+ public function getLocalAddress()
+ {
+ return $this->parseAddress(@stream_socket_get_name($this->stream, false));
+ }
+
+ private function parseAddress($address)
+ {
+ if ($address === false) {
+ return null;
+ }
+
+ if ($this->unix) {
+ // remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
+ // note that technically ":" is a valid address, so keep this in place otherwise
+ if (substr($address, -1) === ':' && defined('HHVM_VERSION_ID') && HHVM_VERSION_ID < 31900) {
+ $address = (string)substr($address, 0, -1);
+ }
+
+ // work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
+ // PHP uses "\0" string and HHVM uses empty string (colon removed above)
+ if ($address === '' || $address[0] === "\x00" ) {
+ return null;
+ }
+
+ return 'unix://' . $address;
+ }
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = strrpos($address, ':');
+ if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') {
+ $port = substr($address, $pos + 1);
+ $address = '[' . substr($address, 0, $pos) . ']:' . $port;
+ }
+
+ return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address;
+ }
+}
diff --git a/vendor/react/socket/src/ConnectionInterface.php b/vendor/react/socket/src/ConnectionInterface.php
new file mode 100755
index 0000000..64613b5
--- /dev/null
+++ b/vendor/react/socket/src/ConnectionInterface.php
@@ -0,0 +1,119 @@
+on('data', function ($chunk) {
+ * echo $chunk;
+ * });
+ *
+ * $connection->on('end', function () {
+ * echo 'ended';
+ * });
+ *
+ * $connection->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage();
+ * });
+ *
+ * $connection->on('close', function () {
+ * echo 'closed';
+ * });
+ *
+ * $connection->write($data);
+ * $connection->end($data = null);
+ * $connection->close();
+ * // …
+ * ```
+ *
+ * For more details, see the
+ * [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
+ *
+ * @see DuplexStreamInterface
+ * @see ServerInterface
+ * @see ConnectorInterface
+ */
+interface ConnectionInterface extends DuplexStreamInterface
+{
+ /**
+ * Returns the full remote address (URI) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the remote address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based connection and you only want the remote IP, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $connection->getRemoteAddress();
+ * $ip = trim(parse_url($address, PHP_URL_HOST), '[]');
+ * echo 'Connection with ' . $ip . PHP_EOL;
+ * ```
+ *
+ * @return ?string remote address (URI) or null if unknown
+ */
+ public function getRemoteAddress();
+
+ /**
+ * Returns the full local address (full URI with scheme, IP and port) where this connection has been established with
+ *
+ * ```php
+ * $address = $connection->getLocalAddress();
+ * echo 'Connection with ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the local address can not be determined or is unknown at this time (such as
+ * after the connection has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
+ * `unix://example.sock` or `unix:///path/to/example.sock`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
+ * so they should not be confused.
+ *
+ * If your `TcpServer` instance is listening on multiple interfaces (e.g. using
+ * the address `0.0.0.0`), you can use this method to find out which interface
+ * actually accepted this connection (such as a public or local interface).
+ *
+ * If your system has multiple interfaces (e.g. a WAN and a LAN interface),
+ * you can use this method to find out which interface was actually
+ * used for this connection.
+ *
+ * @return ?string local address (URI) or null if unknown
+ * @see self::getRemoteAddress()
+ */
+ public function getLocalAddress();
+}
diff --git a/vendor/react/socket/src/Connector.php b/vendor/react/socket/src/Connector.php
new file mode 100755
index 0000000..75276bc
--- /dev/null
+++ b/vendor/react/socket/src/Connector.php
@@ -0,0 +1,136 @@
+ true,
+ 'tls' => true,
+ 'unix' => true,
+
+ 'dns' => true,
+ 'timeout' => true,
+ );
+
+ if ($options['timeout'] === true) {
+ $options['timeout'] = (float)ini_get("default_socket_timeout");
+ }
+
+ if ($options['tcp'] instanceof ConnectorInterface) {
+ $tcp = $options['tcp'];
+ } else {
+ $tcp = new TcpConnector(
+ $loop,
+ is_array($options['tcp']) ? $options['tcp'] : array()
+ );
+ }
+
+ if ($options['dns'] !== false) {
+ if ($options['dns'] instanceof Resolver) {
+ $resolver = $options['dns'];
+ } else {
+ if ($options['dns'] !== true) {
+ $server = $options['dns'];
+ } else {
+ // try to load nameservers from system config or default to Google's public DNS
+ $config = Config::loadSystemConfigBlocking();
+ $server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
+ }
+
+ $factory = new Factory();
+ $resolver = $factory->create(
+ $server,
+ $loop
+ );
+ }
+
+ $tcp = new DnsConnector($tcp, $resolver);
+ }
+
+ if ($options['tcp'] !== false) {
+ $options['tcp'] = $tcp;
+
+ if ($options['timeout'] !== false) {
+ $options['tcp'] = new TimeoutConnector(
+ $options['tcp'],
+ $options['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tcp'] = $options['tcp'];
+ }
+
+ if ($options['tls'] !== false) {
+ if (!$options['tls'] instanceof ConnectorInterface) {
+ $options['tls'] = new SecureConnector(
+ $tcp,
+ $loop,
+ is_array($options['tls']) ? $options['tls'] : array()
+ );
+ }
+
+ if ($options['timeout'] !== false) {
+ $options['tls'] = new TimeoutConnector(
+ $options['tls'],
+ $options['timeout'],
+ $loop
+ );
+ }
+
+ $this->connectors['tls'] = $options['tls'];
+ }
+
+ if ($options['unix'] !== false) {
+ if (!$options['unix'] instanceof ConnectorInterface) {
+ $options['unix'] = new UnixConnector($loop);
+ }
+ $this->connectors['unix'] = $options['unix'];
+ }
+ }
+
+ public function connect($uri)
+ {
+ $scheme = 'tcp';
+ if (strpos($uri, '://') !== false) {
+ $scheme = (string)substr($uri, 0, strpos($uri, '://'));
+ }
+
+ if (!isset($this->connectors[$scheme])) {
+ return Promise\reject(new RuntimeException(
+ 'No connector available for URI scheme "' . $scheme . '"'
+ ));
+ }
+
+ return $this->connectors[$scheme]->connect($uri);
+ }
+}
+
diff --git a/vendor/react/socket/src/ConnectorInterface.php b/vendor/react/socket/src/ConnectorInterface.php
new file mode 100755
index 0000000..196d01a
--- /dev/null
+++ b/vendor/react/socket/src/ConnectorInterface.php
@@ -0,0 +1,58 @@
+connect('google.com:443')->then(
+ * function (ConnectionInterface $connection) {
+ * // connection successfully established
+ * },
+ * function (Exception $error) {
+ * // failed to connect due to $error
+ * }
+ * );
+ * ```
+ *
+ * The returned Promise MUST be implemented in such a way that it can be
+ * cancelled when it is still pending. Cancelling a pending promise MUST
+ * reject its value with an Exception. It SHOULD clean up any underlying
+ * resources and references as applicable.
+ *
+ * ```php
+ * $promise = $connector->connect($uri);
+ *
+ * $promise->cancel();
+ * ```
+ *
+ * @param string $uri
+ * @return \React\Promise\PromiseInterface resolves with a stream implementing ConnectionInterface on success or rejects with an Exception on error
+ * @see ConnectionInterface
+ */
+ public function connect($uri);
+}
diff --git a/vendor/react/socket/src/DnsConnector.php b/vendor/react/socket/src/DnsConnector.php
new file mode 100755
index 0000000..0dfd658
--- /dev/null
+++ b/vendor/react/socket/src/DnsConnector.php
@@ -0,0 +1,112 @@
+connector = $connector;
+ $this->resolver = $resolver;
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $parts = parse_url('tcp://' . $uri);
+ unset($parts['scheme']);
+ } else {
+ $parts = parse_url($uri);
+ }
+
+ if (!$parts || !isset($parts['host'])) {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $host = trim($parts['host'], '[]');
+ $connector = $this->connector;
+
+ // skip DNS lookup / URI manipulation if this URI already contains an IP
+ if (false !== filter_var($host, FILTER_VALIDATE_IP)) {
+ return $connector->connect($uri);
+ }
+
+ return $this
+ ->resolveHostname($host)
+ ->then(function ($ip) use ($connector, $host, $parts) {
+ $uri = '';
+
+ // prepend original scheme if known
+ if (isset($parts['scheme'])) {
+ $uri .= $parts['scheme'] . '://';
+ }
+
+ if (strpos($ip, ':') !== false) {
+ // enclose IPv6 addresses in square brackets before appending port
+ $uri .= '[' . $ip . ']';
+ } else {
+ $uri .= $ip;
+ }
+
+ // append original port if known
+ if (isset($parts['port'])) {
+ $uri .= ':' . $parts['port'];
+ }
+
+ // append orignal path if known
+ if (isset($parts['path'])) {
+ $uri .= $parts['path'];
+ }
+
+ // append original query if known
+ if (isset($parts['query'])) {
+ $uri .= '?' . $parts['query'];
+ }
+
+ // append original hostname as query if resolved via DNS and if
+ // destination URI does not contain "hostname" query param already
+ $args = array();
+ parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
+ if ($host !== $ip && !isset($args['hostname'])) {
+ $uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . rawurlencode($host);
+ }
+
+ // append original fragment if known
+ if (isset($parts['fragment'])) {
+ $uri .= '#' . $parts['fragment'];
+ }
+
+ return $connector->connect($uri);
+ });
+ }
+
+ private function resolveHostname($host)
+ {
+ $promise = $this->resolver->resolve($host);
+
+ return new Promise\Promise(
+ function ($resolve, $reject) use ($promise) {
+ // resolve/reject with result of DNS lookup
+ $promise->then($resolve, $reject);
+ },
+ function ($_, $reject) use ($promise) {
+ // cancellation should reject connection attempt
+ $reject(new RuntimeException('Connection attempt cancelled during DNS lookup'));
+
+ // (try to) cancel pending DNS lookup
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+ );
+ }
+}
diff --git a/vendor/react/socket/src/FixedUriConnector.php b/vendor/react/socket/src/FixedUriConnector.php
new file mode 100755
index 0000000..057bcdf
--- /dev/null
+++ b/vendor/react/socket/src/FixedUriConnector.php
@@ -0,0 +1,41 @@
+connect('localhost:80');
+ * ```
+ */
+class FixedUriConnector implements ConnectorInterface
+{
+ private $uri;
+ private $connector;
+
+ /**
+ * @param string $uri
+ * @param ConnectorInterface $connector
+ */
+ public function __construct($uri, ConnectorInterface $connector)
+ {
+ $this->uri = $uri;
+ $this->connector = $connector;
+ }
+
+ public function connect($_)
+ {
+ return $this->connector->connect($this->uri);
+ }
+}
diff --git a/vendor/react/socket/src/LimitingServer.php b/vendor/react/socket/src/LimitingServer.php
new file mode 100755
index 0000000..c7874ee
--- /dev/null
+++ b/vendor/react/socket/src/LimitingServer.php
@@ -0,0 +1,203 @@
+on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+class LimitingServer extends EventEmitter implements ServerInterface
+{
+ private $connections = array();
+ private $server;
+ private $limit;
+
+ private $pauseOnLimit = false;
+ private $autoPaused = false;
+ private $manuPaused = false;
+
+ /**
+ * Instantiates a new LimitingServer.
+ *
+ * You have to pass a maximum number of open connections to ensure
+ * the server will automatically reject (close) connections once this limit
+ * is exceeded. In this case, it will emit an `error` event to inform about
+ * this and no `connection` event will be emitted.
+ *
+ * ```php
+ * $server = new LimitingServer($server, 100);
+ * $server->on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * You MAY pass a `null` limit in order to put no limit on the number of
+ * open connections and keep accepting new connection until you run out of
+ * operating system resources (such as open file handles). This may be
+ * useful if you do not want to take care of applying a limit but still want
+ * to use the `getConnections()` method.
+ *
+ * You can optionally configure the server to pause accepting new
+ * connections once the connection limit is reached. In this case, it will
+ * pause the underlying server and no longer process any new connections at
+ * all, thus also no longer closing any excessive connections.
+ * The underlying operating system is responsible for keeping a backlog of
+ * pending connections until its limit is reached, at which point it will
+ * start rejecting further connections.
+ * Once the server is below the connection limit, it will continue consuming
+ * connections from the backlog and will process any outstanding data on
+ * each connection.
+ * This mode may be useful for some protocols that are designed to wait for
+ * a response message (such as HTTP), but may be less useful for other
+ * protocols that demand immediate responses (such as a "welcome" message in
+ * an interactive chat).
+ *
+ * ```php
+ * $server = new LimitingServer($server, 100, true);
+ * $server->on('connection', function (ConnectionInterface $connection) {
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * @param ServerInterface $server
+ * @param int|null $connectionLimit
+ * @param bool $pauseOnLimit
+ */
+ public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false)
+ {
+ $this->server = $server;
+ $this->limit = $connectionLimit;
+ if ($connectionLimit !== null) {
+ $this->pauseOnLimit = $pauseOnLimit;
+ }
+
+ $this->server->on('connection', array($this, 'handleConnection'));
+ $this->server->on('error', array($this, 'handleError'));
+ }
+
+ /**
+ * Returns an array with all currently active connections
+ *
+ * ```php
+ * foreach ($server->getConnection() as $connection) {
+ * $connection->write('Hi!');
+ * }
+ * ```
+ *
+ * @return ConnectionInterface[]
+ */
+ public function getConnections()
+ {
+ return $this->connections;
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ if (!$this->manuPaused) {
+ $this->manuPaused = true;
+
+ if (!$this->autoPaused) {
+ $this->server->pause();
+ }
+ }
+ }
+
+ public function resume()
+ {
+ if ($this->manuPaused) {
+ $this->manuPaused = false;
+
+ if (!$this->autoPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ // close connection if limit exceeded
+ if ($this->limit !== null && count($this->connections) >= $this->limit) {
+ $this->handleError(new OverflowException('Connection closed because server reached connection limit'));
+ $connection->close();
+ return;
+ }
+
+ $this->connections[] = $connection;
+ $that = $this;
+ $connection->on('close', function () use ($that, $connection) {
+ $that->handleDisconnection($connection);
+ });
+
+ // pause accepting new connections if limit exceeded
+ if ($this->pauseOnLimit && !$this->autoPaused && count($this->connections) >= $this->limit) {
+ $this->autoPaused = true;
+
+ if (!$this->manuPaused) {
+ $this->server->pause();
+ }
+ }
+
+ $this->emit('connection', array($connection));
+ }
+
+ /** @internal */
+ public function handleDisconnection(ConnectionInterface $connection)
+ {
+ unset($this->connections[array_search($connection, $this->connections)]);
+
+ // continue accepting new connection if below limit
+ if ($this->autoPaused && count($this->connections) < $this->limit) {
+ $this->autoPaused = false;
+
+ if (!$this->manuPaused) {
+ $this->server->resume();
+ }
+ }
+ }
+
+ /** @internal */
+ public function handleError(Exception $error)
+ {
+ $this->emit('error', array($error));
+ }
+}
diff --git a/vendor/react/socket/src/SecureConnector.php b/vendor/react/socket/src/SecureConnector.php
new file mode 100755
index 0000000..f04183d
--- /dev/null
+++ b/vendor/react/socket/src/SecureConnector.php
@@ -0,0 +1,64 @@
+connector = $connector;
+ $this->streamEncryption = new StreamEncryption($loop, false);
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ return Promise\reject(new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore
+ }
+
+ if (strpos($uri, '://') === false) {
+ $uri = 'tls://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $uri = str_replace('tls://', '', $uri);
+ $context = $this->context;
+
+ $encryption = $this->streamEncryption;
+ return $this->connector->connect($uri)->then(function (ConnectionInterface $connection) use ($context, $encryption) {
+ // (unencrypted) TCP/IP connection succeeded
+
+ if (!$connection instanceof Connection) {
+ $connection->close();
+ throw new UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource');
+ }
+
+ // set required SSL/TLS context options
+ foreach ($context as $name => $value) {
+ stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ // try to enable encryption
+ return $encryption->enable($connection)->then(null, function ($error) use ($connection) {
+ // establishing encryption failed => close invalid connection and return error
+ $connection->close();
+ throw $error;
+ });
+ });
+ }
+}
diff --git a/vendor/react/socket/src/SecureServer.php b/vendor/react/socket/src/SecureServer.php
new file mode 100755
index 0000000..302ae93
--- /dev/null
+++ b/vendor/react/socket/src/SecureServer.php
@@ -0,0 +1,192 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
+ *
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * Whenever a client fails to perform a successful TLS handshake, it will emit an
+ * `error` event and then close the underlying TCP/IP connection:
+ *
+ * ```php
+ * $server->on('error', function (Exception $e) {
+ * echo 'Error' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * Note that the `SecureServer` class is a concrete implementation for TLS sockets.
+ * If you want to typehint in your higher-level protocol implementation, you SHOULD
+ * use the generic `ServerInterface` instead.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class SecureServer extends EventEmitter implements ServerInterface
+{
+ private $tcp;
+ private $encryption;
+ private $context;
+
+ /**
+ * Creates a secure TLS server and starts waiting for incoming connections
+ *
+ * It does so by wrapping a `TcpServer` instance which waits for plaintext
+ * TCP/IP connections and then performs a TLS handshake for each connection.
+ * It thus requires valid [TLS context options],
+ * which in its most basic form may look something like this if you're using a
+ * PEM encoded certificate file:
+ *
+ * ```php
+ * $server = new TcpServer(8000, $loop);
+ * $server = new SecureServer($server, $loop, array(
+ * 'local_cert' => 'server.pem'
+ * ));
+ * ```
+ *
+ * Note that the certificate file will not be loaded on instantiation but when an
+ * incoming connection initializes its TLS context.
+ * This implies that any invalid certificate file paths or contents will only cause
+ * an `error` event at a later time.
+ *
+ * If your private key is encrypted with a passphrase, you have to specify it
+ * like this:
+ *
+ * ```php
+ * $server = new TcpServer(8000, $loop);
+ * $server = new SecureServer($server, $loop, array(
+ * 'local_cert' => 'server.pem',
+ * 'passphrase' => 'secret'
+ * ));
+ * ```
+ *
+ * Note that available [TLS context options],
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ *
+ * Advanced usage: Despite allowing any `ServerInterface` as first parameter,
+ * you SHOULD pass a `TcpServer` instance as first parameter, unless you
+ * know what you're doing.
+ * Internally, the `SecureServer` has to set the required TLS context options on
+ * the underlying stream resources.
+ * These resources are not exposed through any of the interfaces defined in this
+ * package, but only through the internal `Connection` class.
+ * The `TcpServer` class is guaranteed to emit connections that implement
+ * the `ConnectionInterface` and uses the internal `Connection` class in order to
+ * expose these underlying resources.
+ * If you use a custom `ServerInterface` and its `connection` event does not
+ * meet this requirement, the `SecureServer` will emit an `error` event and
+ * then close the underlying connection.
+ *
+ * @param ServerInterface|TcpServer $tcp
+ * @param LoopInterface $loop
+ * @param array $context
+ * @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
+ * @see TcpServer
+ * @link http://php.net/manual/en/context.ssl.php for TLS context options
+ */
+ public function __construct(ServerInterface $tcp, LoopInterface $loop, array $context)
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ throw new BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
+ }
+
+ // default to empty passphrase to suppress blocking passphrase prompt
+ $context += array(
+ 'passphrase' => ''
+ );
+
+ $this->tcp = $tcp;
+ $this->encryption = new StreamEncryption($loop);
+ $this->context = $context;
+
+ $that = $this;
+ $this->tcp->on('connection', function ($connection) use ($that) {
+ $that->handleConnection($connection);
+ });
+ $this->tcp->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ $address = $this->tcp->getAddress();
+ if ($address === null) {
+ return null;
+ }
+
+ return str_replace('tcp://' , 'tls://', $address);
+ }
+
+ public function pause()
+ {
+ $this->tcp->pause();
+ }
+
+ public function resume()
+ {
+ $this->tcp->resume();
+ }
+
+ public function close()
+ {
+ return $this->tcp->close();
+ }
+
+ /** @internal */
+ public function handleConnection(ConnectionInterface $connection)
+ {
+ if (!$connection instanceof Connection) {
+ $this->emit('error', array(new UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
+ $connection->end();
+ return;
+ }
+
+ foreach ($this->context as $name => $value) {
+ stream_context_set_option($connection->stream, 'ssl', $name, $value);
+ }
+
+ $that = $this;
+
+ $this->encryption->enable($connection)->then(
+ function ($conn) use ($that) {
+ $that->emit('connection', array($conn));
+ },
+ function ($error) use ($that, $connection) {
+ $that->emit('error', array($error));
+ $connection->end();
+ }
+ );
+ }
+}
diff --git a/vendor/react/socket/src/Server.php b/vendor/react/socket/src/Server.php
new file mode 100755
index 0000000..72712e4
--- /dev/null
+++ b/vendor/react/socket/src/Server.php
@@ -0,0 +1,73 @@
+ $context);
+ }
+
+ // apply default options if not explicitly given
+ $context += array(
+ 'tcp' => array(),
+ 'tls' => array(),
+ 'unix' => array()
+ );
+
+ $scheme = 'tcp';
+ $pos = strpos($uri, '://');
+ if ($pos !== false) {
+ $scheme = substr($uri, 0, $pos);
+ }
+
+ if ($scheme === 'unix') {
+ $server = new UnixServer($uri, $loop, $context['unix']);
+ } else {
+ $server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
+
+ if ($scheme === 'tls') {
+ $server = new SecureServer($server, $loop, $context['tls']);
+ }
+ }
+
+ $this->server = $server;
+
+ $that = $this;
+ $server->on('connection', function (ConnectionInterface $conn) use ($that) {
+ $that->emit('connection', array($conn));
+ });
+ $server->on('error', function (Exception $error) use ($that) {
+ $that->emit('error', array($error));
+ });
+ }
+
+ public function getAddress()
+ {
+ return $this->server->getAddress();
+ }
+
+ public function pause()
+ {
+ $this->server->pause();
+ }
+
+ public function resume()
+ {
+ $this->server->resume();
+ }
+
+ public function close()
+ {
+ $this->server->close();
+ }
+}
diff --git a/vendor/react/socket/src/ServerInterface.php b/vendor/react/socket/src/ServerInterface.php
new file mode 100755
index 0000000..5319678
--- /dev/null
+++ b/vendor/react/socket/src/ServerInterface.php
@@ -0,0 +1,151 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'new connection' . PHP_EOL;
+ * });
+ * ```
+ *
+ * See also the `ConnectionInterface` for more details about handling the
+ * incoming connection.
+ *
+ * error event:
+ * The `error` event will be emitted whenever there's an error accepting a new
+ * connection from a client.
+ *
+ * ```php
+ * $server->on('error', function (Exception $e) {
+ * echo 'error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * Note that this is not a fatal error event, i.e. the server keeps listening for
+ * new connections even after this event.
+ *
+ * @see ConnectionInterface
+ */
+interface ServerInterface extends EventEmitterInterface
+{
+ /**
+ * Returns the full address (URI) this server is currently listening on
+ *
+ * ```php
+ * $address = $server->getAddress();
+ * echo 'Server listening on ' . $address . PHP_EOL;
+ * ```
+ *
+ * If the address can not be determined or is unknown at this time (such as
+ * after the socket has been closed), it MAY return a `NULL` value instead.
+ *
+ * Otherwise, it will return the full address (URI) as a string value, such
+ * as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`.
+ * Note that individual URI components are application specific and depend
+ * on the underlying transport protocol.
+ *
+ * If this is a TCP/IP based server and you only want the local port, you may
+ * use something like this:
+ *
+ * ```php
+ * $address = $server->getAddress();
+ * $port = parse_url($address, PHP_URL_PORT);
+ * echo 'Server listening on port ' . $port . PHP_EOL;
+ * ```
+ *
+ * @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed)
+ */
+ public function getAddress();
+
+ /**
+ * Pauses accepting new incoming connections.
+ *
+ * Removes the socket resource from the EventLoop and thus stop accepting
+ * new connections. Note that the listening socket stays active and is not
+ * closed.
+ *
+ * This means that new incoming connections will stay pending in the
+ * operating system backlog until its configurable backlog is filled.
+ * Once the backlog is filled, the operating system may reject further
+ * incoming connections until the backlog is drained again by resuming
+ * to accept new connections.
+ *
+ * Once the server is paused, no futher `connection` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $server->pause();
+ *
+ * $server->on('connection', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * server MAY continue emitting `connection` events.
+ *
+ * Unless otherwise noted, a successfully opened server SHOULD NOT start
+ * in paused state.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes accepting new incoming connections.
+ *
+ * Re-attach the socket resource to the EventLoop after a previous `pause()`.
+ *
+ * ```php
+ * $server->pause();
+ *
+ * $loop->addTimer(1.0, function () use ($server) {
+ * $server->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ * Similarly, calling this after `close()` is a NO-OP.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Shuts down this listening socket
+ *
+ * This will stop listening for new incoming connections on this socket.
+ *
+ * Calling this method more than once on the same instance is a NO-OP.
+ *
+ * @return void
+ */
+ public function close();
+}
diff --git a/vendor/react/socket/src/StreamEncryption.php b/vendor/react/socket/src/StreamEncryption.php
new file mode 100755
index 0000000..ba5d472
--- /dev/null
+++ b/vendor/react/socket/src/StreamEncryption.php
@@ -0,0 +1,146 @@
+loop = $loop;
+ $this->server = $server;
+
+ // support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
+ // PHP 5.6+ supports bitmasks, legacy PHP only supports predefined
+ // constants, so apply accordingly below.
+ // Also, since PHP 5.6.7 up until before PHP 7.2.0 the main constant did
+ // only support TLSv1.0, so we explicitly apply all versions.
+ // @link http://php.net/manual/en/migration56.openssl.php#migration56.openssl.crypto-method
+ // @link https://3v4l.org/plbFn
+ if ($server) {
+ $this->method = STREAM_CRYPTO_METHOD_TLS_SERVER;
+
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_SERVER;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_SERVER;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_SERVER;
+ }
+ } else {
+ $this->method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
+
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
+ }
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
+ $this->method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+ }
+ }
+ }
+
+ public function enable(Connection $stream)
+ {
+ return $this->toggle($stream, true);
+ }
+
+ public function disable(Connection $stream)
+ {
+ return $this->toggle($stream, false);
+ }
+
+ public function toggle(Connection $stream, $toggle)
+ {
+ // pause actual stream instance to continue operation on raw stream socket
+ $stream->pause();
+
+ // TODO: add write() event to make sure we're not sending any excessive data
+
+ $deferred = new Deferred(function ($_, $reject) use ($toggle) {
+ // cancelling this leaves this stream in an inconsistent state…
+ $reject(new RuntimeException('Cancelled toggling encryption ' . $toggle ? 'on' : 'off'));
+ });
+
+ // get actual stream socket from stream instance
+ $socket = $stream->stream;
+
+ // get crypto method from context options or use global setting from constructor
+ $method = $this->method;
+ $context = stream_context_get_options($socket);
+ if (isset($context['ssl']['crypto_method'])) {
+ $method = $context['ssl']['crypto_method'];
+ }
+
+ $that = $this;
+ $toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) {
+ $that->toggleCrypto($socket, $deferred, $toggle, $method);
+ };
+
+ $this->loop->addReadStream($socket, $toggleCrypto);
+
+ if (!$this->server) {
+ $toggleCrypto();
+ }
+
+ $loop = $this->loop;
+
+ return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) {
+ $loop->removeReadStream($socket);
+
+ $stream->encryptionEnabled = $toggle;
+ $stream->resume();
+
+ return $stream;
+ }, function($error) use ($stream, $socket, $loop) {
+ $loop->removeReadStream($socket);
+ $stream->resume();
+ throw $error;
+ });
+ }
+
+ public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
+ {
+ set_error_handler(array($this, 'handleError'));
+ $result = stream_socket_enable_crypto($socket, $toggle, $method);
+ restore_error_handler();
+
+ if (true === $result) {
+ $deferred->resolve();
+ } else if (false === $result) {
+ $deferred->reject(new UnexpectedValueException(
+ sprintf("Unable to complete SSL/TLS handshake: %s", $this->errstr),
+ $this->errno
+ ));
+ } else {
+ // need more data, will retry
+ }
+ }
+
+ public function handleError($errno, $errstr)
+ {
+ $this->errstr = str_replace(array("\r", "\n"), ' ', $errstr);
+ $this->errno = $errno;
+ }
+}
diff --git a/vendor/react/socket/src/TcpConnector.php b/vendor/react/socket/src/TcpConnector.php
new file mode 100755
index 0000000..53d55a3
--- /dev/null
+++ b/vendor/react/socket/src/TcpConnector.php
@@ -0,0 +1,129 @@
+loop = $loop;
+ $this->context = $context;
+ }
+
+ public function connect($uri)
+ {
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $uri . '" is invalid'));
+ }
+
+ $ip = trim($parts['host'], '[]');
+ if (false === filter_var($ip, FILTER_VALIDATE_IP)) {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP'));
+ }
+
+ // use context given in constructor
+ $context = array(
+ 'socket' => $this->context
+ );
+
+ // parse arguments from query component of URI
+ $args = array();
+ if (isset($parts['query'])) {
+ parse_str($parts['query'], $args);
+ }
+
+ // If an original hostname has been given, use this for TLS setup.
+ // This can happen due to layers of nested connectors, such as a
+ // DnsConnector reporting its original hostname.
+ // These context options are here in case TLS is enabled later on this stream.
+ // If TLS is not enabled later, this doesn't hurt either.
+ if (isset($args['hostname'])) {
+ $context['ssl'] = array(
+ 'SNI_enabled' => true,
+ 'peer_name' => $args['hostname']
+ );
+
+ // Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead.
+ // The SNI_server_name context option has to be set here during construction,
+ // as legacy PHP ignores any values set later.
+ if (PHP_VERSION_ID < 50600) {
+ $context['ssl'] += array(
+ 'SNI_server_name' => $args['hostname'],
+ 'CN_match' => $args['hostname']
+ );
+ }
+ }
+
+ // latest versions of PHP no longer accept any other URI components and
+ // HHVM fails to parse URIs with a query but no path, so let's simplify our URI here
+ $remote = 'tcp://' . $parts['host'] . ':' . $parts['port'];
+
+ $socket = @stream_socket_client(
+ $remote,
+ $errno,
+ $errstr,
+ 0,
+ STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT,
+ stream_context_create($context)
+ );
+
+ if (false === $socket) {
+ return Promise\reject(new RuntimeException(
+ sprintf("Connection to %s failed: %s", $uri, $errstr),
+ $errno
+ ));
+ }
+
+ stream_set_blocking($socket, 0);
+
+ // wait for connection
+
+ return $this->waitForStreamOnce($socket);
+ }
+
+ private function waitForStreamOnce($stream)
+ {
+ $loop = $this->loop;
+
+ return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream) {
+ $loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject) {
+ $loop->removeWriteStream($stream);
+
+ // The following hack looks like the only way to
+ // detect connection refused errors with PHP's stream sockets.
+ if (false === stream_socket_get_name($stream, true)) {
+ fclose($stream);
+
+ $reject(new RuntimeException('Connection refused'));
+ } else {
+ $resolve(new Connection($stream, $loop));
+ }
+ });
+ }, function () use ($loop, $stream) {
+ $loop->removeWriteStream($stream);
+ fclose($stream);
+
+ // @codeCoverageIgnoreStart
+ // legacy PHP 5.3 sometimes requires a second close call (see tests)
+ if (PHP_VERSION_ID < 50400 && is_resource($stream)) {
+ fclose($stream);
+ }
+ // @codeCoverageIgnoreEnd
+
+ throw new RuntimeException('Cancelled while waiting for TCP/IP connection to be established');
+ });
+ }
+}
diff --git a/vendor/react/socket/src/TcpServer.php b/vendor/react/socket/src/TcpServer.php
new file mode 100755
index 0000000..119e177
--- /dev/null
+++ b/vendor/react/socket/src/TcpServer.php
@@ -0,0 +1,236 @@
+on('connection', function (ConnectionInterface $connection) {
+ * echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
+ * $connection->write('hello there!' . PHP_EOL);
+ * …
+ * });
+ * ```
+ *
+ * See also the `ServerInterface` for more details.
+ *
+ * @see ServerInterface
+ * @see ConnectionInterface
+ */
+final class TcpServer extends EventEmitter implements ServerInterface
+{
+ private $master;
+ private $loop;
+ private $listening = false;
+
+ /**
+ * Creates a plaintext TCP/IP socket server and starts listening on the given address
+ *
+ * This starts accepting new incoming connections on the given address.
+ * See also the `connection event` documented in the `ServerInterface`
+ * for more details.
+ *
+ * ```php
+ * $server = new TcpServer(8080, $loop);
+ * ```
+ *
+ * As above, the `$uri` parameter can consist of only a port, in which case the
+ * server will default to listening on the localhost address `127.0.0.1`,
+ * which means it will not be reachable from outside of this system.
+ *
+ * In order to use a random port assignment, you can use the port `0`:
+ *
+ * ```php
+ * $server = new TcpServer(0, $loop);
+ * $address = $server->getAddress();
+ * ```
+ *
+ * In order to change the host the socket is listening on, you can provide an IP
+ * address through the first parameter provided to the constructor, optionally
+ * preceded by the `tcp://` scheme:
+ *
+ * ```php
+ * $server = new TcpServer('192.168.0.1:8080', $loop);
+ * ```
+ *
+ * If you want to listen on an IPv6 address, you MUST enclose the host in square
+ * brackets:
+ *
+ * ```php
+ * $server = new TcpServer('[::1]:8080', $loop);
+ * ```
+ *
+ * If the given URI is invalid, does not contain a port, any other scheme or if it
+ * contains a hostname, it will throw an `InvalidArgumentException`:
+ *
+ * ```php
+ * // throws InvalidArgumentException due to missing port
+ * $server = new TcpServer('127.0.0.1', $loop);
+ * ```
+ *
+ * If the given URI appears to be valid, but listening on it fails (such as if port
+ * is already in use or port below 1024 may require root access etc.), it will
+ * throw a `RuntimeException`:
+ *
+ * ```php
+ * $first = new TcpServer(8080, $loop);
+ *
+ * // throws RuntimeException because port is already in use
+ * $second = new TcpServer(8080, $loop);
+ * ```
+ *
+ * Note that these error conditions may vary depending on your system and/or
+ * configuration.
+ * See the exception message and code for more details about the actual error
+ * condition.
+ *
+ * Optionally, you can specify [socket context options](http://php.net/manual/en/context.socket.php)
+ * for the underlying stream socket resource like this:
+ *
+ * ```php
+ * $server = new TcpServer('[::1]:8080', $loop, array(
+ * 'backlog' => 200,
+ * 'so_reuseport' => true,
+ * 'ipv6_v6only' => true
+ * ));
+ * ```
+ *
+ * Note that available [socket context options](http://php.net/manual/en/context.socket.php),
+ * their defaults and effects of changing these may vary depending on your system
+ * and/or PHP version.
+ * Passing unknown context options has no effect.
+ *
+ * @param string|int $uri
+ * @param LoopInterface $loop
+ * @param array $context
+ * @throws InvalidArgumentException if the listening address is invalid
+ * @throws RuntimeException if listening on this address fails (already in use etc.)
+ */
+ public function __construct($uri, LoopInterface $loop, array $context = array())
+ {
+ $this->loop = $loop;
+
+ // a single port has been given => assume localhost
+ if ((string)(int)$uri === (string)$uri) {
+ $uri = '127.0.0.1:' . $uri;
+ }
+
+ // assume default scheme if none has been given
+ if (strpos($uri, '://') === false) {
+ $uri = 'tcp://' . $uri;
+ }
+
+ // parse_url() does not accept null ports (random port assignment) => manually remove
+ if (substr($uri, -2) === ':0') {
+ $parts = parse_url(substr($uri, 0, -2));
+ if ($parts) {
+ $parts['port'] = 0;
+ }
+ } else {
+ $parts = parse_url($uri);
+ }
+
+ // ensure URI contains TCP scheme, host and port
+ if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
+ throw new InvalidArgumentException('Invalid URI "' . $uri . '" given');
+ }
+
+ if (false === filter_var(trim($parts['host'], '[]'), FILTER_VALIDATE_IP)) {
+ throw new InvalidArgumentException('Given URI "' . $uri . '" does not contain a valid host IP');
+ }
+
+ $this->master = @stream_socket_server(
+ $uri,
+ $errno,
+ $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
+ stream_context_create(array('socket' => $context))
+ );
+ if (false === $this->master) {
+ throw new RuntimeException('Failed to listen on "' . $uri . '": ' . $errstr, $errno);
+ }
+ stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!is_resource($this->master)) {
+ return null;
+ }
+
+ $address = stream_socket_get_name($this->master, false);
+
+ // check if this is an IPv6 address which includes multiple colons but no square brackets
+ $pos = strrpos($address, ':');
+ if ($pos !== false && strpos($address, ':') < $pos && substr($address, 0, 1) !== '[') {
+ $port = substr($address, $pos + 1);
+ $address = '[' . substr($address, 0, $pos) . ']:' . $port;
+ }
+
+ return 'tcp://' . $address;
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ $newSocket = @stream_socket_accept($master);
+ if (false === $newSocket) {
+ $that->emit('error', array(new RuntimeException('Error accepting new connection')));
+
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $this->emit('connection', array(
+ new Connection($socket, $this->loop)
+ ));
+ }
+}
diff --git a/vendor/react/socket/src/TimeoutConnector.php b/vendor/react/socket/src/TimeoutConnector.php
new file mode 100755
index 0000000..d4eba2e
--- /dev/null
+++ b/vendor/react/socket/src/TimeoutConnector.php
@@ -0,0 +1,25 @@
+connector = $connector;
+ $this->timeout = $timeout;
+ $this->loop = $loop;
+ }
+
+ public function connect($uri)
+ {
+ return Timer\timeout($this->connector->connect($uri), $this->timeout, $this->loop);
+ }
+}
diff --git a/vendor/react/socket/src/UnixConnector.php b/vendor/react/socket/src/UnixConnector.php
new file mode 100755
index 0000000..9b84ab0
--- /dev/null
+++ b/vendor/react/socket/src/UnixConnector.php
@@ -0,0 +1,44 @@
+loop = $loop;
+ }
+
+ public function connect($path)
+ {
+ if (strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (substr($path, 0, 7) !== 'unix://') {
+ return Promise\reject(new InvalidArgumentException('Given URI "' . $path . '" is invalid'));
+ }
+
+ $resource = @stream_socket_client($path, $errno, $errstr, 1.0);
+
+ if (!$resource) {
+ return Promise\reject(new RuntimeException('Unable to connect to unix domain socket "' . $path . '": ' . $errstr, $errno));
+ }
+
+ $connection = new Connection($resource, $this->loop);
+ $connection->unix = true;
+
+ return Promise\resolve($connection);
+ }
+}
diff --git a/vendor/react/socket/src/UnixServer.php b/vendor/react/socket/src/UnixServer.php
new file mode 100755
index 0000000..8f1ed98
--- /dev/null
+++ b/vendor/react/socket/src/UnixServer.php
@@ -0,0 +1,130 @@
+loop = $loop;
+
+ if (strpos($path, '://') === false) {
+ $path = 'unix://' . $path;
+ } elseif (substr($path, 0, 7) !== 'unix://') {
+ throw new InvalidArgumentException('Given URI "' . $path . '" is invalid');
+ }
+
+ $this->master = @stream_socket_server(
+ $path,
+ $errno,
+ $errstr,
+ STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
+ stream_context_create(array('socket' => $context))
+ );
+ if (false === $this->master) {
+ throw new RuntimeException('Failed to listen on unix domain socket "' . $path . '": ' . $errstr, $errno);
+ }
+ stream_set_blocking($this->master, 0);
+
+ $this->resume();
+ }
+
+ public function getAddress()
+ {
+ if (!is_resource($this->master)) {
+ return null;
+ }
+
+ return 'unix://' . stream_socket_get_name($this->master, false);
+ }
+
+ public function pause()
+ {
+ if (!$this->listening) {
+ return;
+ }
+
+ $this->loop->removeReadStream($this->master);
+ $this->listening = false;
+ }
+
+ public function resume()
+ {
+ if ($this->listening || !is_resource($this->master)) {
+ return;
+ }
+
+ $that = $this;
+ $this->loop->addReadStream($this->master, function ($master) use ($that) {
+ $newSocket = @stream_socket_accept($master);
+ if (false === $newSocket) {
+ $that->emit('error', array(new RuntimeException('Error accepting new connection')));
+
+ return;
+ }
+ $that->handleConnection($newSocket);
+ });
+ $this->listening = true;
+ }
+
+ public function close()
+ {
+ if (!is_resource($this->master)) {
+ return;
+ }
+
+ $this->pause();
+ fclose($this->master);
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleConnection($socket)
+ {
+ $connection = new Connection($socket, $this->loop);
+ $connection->unix = true;
+
+ $this->emit('connection', array(
+ $connection
+ ));
+ }
+}
diff --git a/vendor/react/socket/tests/ConnectionTest.php b/vendor/react/socket/tests/ConnectionTest.php
new file mode 100755
index 0000000..d3563df
--- /dev/null
+++ b/vendor/react/socket/tests/ConnectionTest.php
@@ -0,0 +1,47 @@
+markTestSkipped('HHVM does not support socket operation on test memory stream');
+ }
+
+ $resource = fopen('php://memory', 'r+');
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connection = new Connection($resource, $loop);
+ $connection->close();
+
+ $this->assertFalse(is_resource($resource));
+ }
+
+ public function testCloseConnectionWillRemoveResourceFromLoopBeforeClosingResource()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not support socket operation on test memory stream');
+ }
+
+ $resource = fopen('php://memory', 'r+');
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addWriteStream')->with($resource);
+
+ $onRemove = null;
+ $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($param) use (&$onRemove) {
+ $onRemove = is_resource($param);
+ return true;
+ }));
+
+ $connection = new Connection($resource, $loop);
+ $connection->write('test');
+ $connection->close();
+
+ $this->assertTrue($onRemove);
+ $this->assertFalse(is_resource($resource));
+ }
+}
diff --git a/vendor/react/socket/tests/ConnectorTest.php b/vendor/react/socket/tests/ConnectorTest.php
new file mode 100755
index 0000000..c8eb19b
--- /dev/null
+++ b/vendor/react/socket/tests/ConnectorTest.php
@@ -0,0 +1,128 @@
+getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('127.0.0.1:80')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp
+ ));
+
+ $connector->connect('127.0.0.1:80');
+ }
+
+ public function testConnectorPassedThroughHostnameIfDnsIsDisabled()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('tcp://google.com:80')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'dns' => false
+ ));
+
+ $connector->connect('tcp://google.com:80');
+ }
+
+ public function testConnectorWithUnknownSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop);
+
+ $promise = $connector->connect('unknown://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTcpDefaultSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tcp' => false
+ ));
+
+ $promise = $connector->connect('google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTcpSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tcp' => false
+ ));
+
+ $promise = $connector->connect('tcp://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledTlsSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'tls' => false
+ ));
+
+ $promise = $connector->connect('tls://google.com:443');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorWithDisabledUnixSchemeAlwaysFails()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new Connector($loop, array(
+ 'unix' => false
+ ));
+
+ $promise = $connector->connect('unix://demo.sock');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testConnectorUsesGivenResolverInstance()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function () { });
+ $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+ $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'dns' => $resolver
+ ));
+
+ $connector->connect('google.com:80');
+ }
+
+ public function testConnectorUsesResolvedHostnameIfDnsIsUsed()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $promise = new Promise(function ($resolve) { $resolve('127.0.0.1'); });
+ $resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+ $resolver->expects($this->once())->method('resolve')->with('google.com')->willReturn($promise);
+
+ $promise = new Promise(function () { });
+ $tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $tcp->expects($this->once())->method('connect')->with('tcp://127.0.0.1:80?hostname=google.com')->willReturn($promise);
+
+ $connector = new Connector($loop, array(
+ 'tcp' => $tcp,
+ 'dns' => $resolver
+ ));
+
+ $connector->connect('tcp://google.com:80');
+ }
+}
diff --git a/vendor/react/socket/tests/DnsConnectorTest.php b/vendor/react/socket/tests/DnsConnectorTest.php
new file mode 100755
index 0000000..3c94c39
--- /dev/null
+++ b/vendor/react/socket/tests/DnsConnectorTest.php
@@ -0,0 +1,111 @@
+tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->resolver = $this->getMockBuilder('React\Dns\Resolver\Resolver')->disableOriginalConstructor()->getMock();
+
+ $this->connector = new DnsConnector($this->tcp, $this->resolver);
+ }
+
+ public function testPassByResolverIfGivenIp()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('127.0.0.1:80');
+ }
+
+ public function testPassThroughResolverIfGivenHost()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=google.com'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('google.com:80');
+ }
+
+ public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('::1')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80?hostname=google.com'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('google.com:80');
+ }
+
+ public function testPassByResolverIfGivenCompleteUri()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://127.0.0.1:80/path?query#fragment');
+ }
+
+ public function testPassThroughResolverIfGivenCompleteUri()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/path?query&hostname=google.com#fragment'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://google.com:80/path?query#fragment');
+ }
+
+ public function testPassThroughResolverIfGivenExplicitHost()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/?hostname=google.de'))->will($this->returnValue(Promise\reject()));
+
+ $this->connector->connect('scheme://google.com:80/?hostname=google.de');
+ }
+
+ public function testRejectsImmediatelyIfUriIsInvalid()
+ {
+ $this->resolver->expects($this->never())->method('resolve');
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('////');
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testSkipConnectionIfDnsFails()
+ {
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.invalid'))->will($this->returnValue(Promise\reject()));
+ $this->tcp->expects($this->never())->method('connect');
+
+ $this->connector->connect('example.invalid:80');
+ }
+
+ public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, $this->expectCallableOnce());
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue($pending));
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testCancelDuringTcpConnectionCancelsTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+ $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue(Promise\resolve('1.2.3.4')));
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/vendor/react/socket/tests/FixedUriConnectorTest.php b/vendor/react/socket/tests/FixedUriConnectorTest.php
new file mode 100755
index 0000000..f42d74f
--- /dev/null
+++ b/vendor/react/socket/tests/FixedUriConnectorTest.php
@@ -0,0 +1,19 @@
+getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $base->expects($this->once())->method('connect')->with('test')->willReturn('ret');
+
+ $connector = new FixedUriConnector('test', $base);
+
+ $this->assertEquals('ret', $connector->connect('ignored'));
+ }
+}
diff --git a/vendor/react/socket/tests/FunctionalConnectorTest.php b/vendor/react/socket/tests/FunctionalConnectorTest.php
new file mode 100755
index 0000000..6611352
--- /dev/null
+++ b/vendor/react/socket/tests/FunctionalConnectorTest.php
@@ -0,0 +1,32 @@
+on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new Connector($loop);
+
+ $connection = Block\await($connector->connect('localhost:9998'), $loop, self::TIMEOUT);
+
+ $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
+
+ $connection->close();
+ $server->close();
+ }
+}
diff --git a/vendor/react/socket/tests/FunctionalSecureServerTest.php b/vendor/react/socket/tests/FunctionalSecureServerTest.php
new file mode 100755
index 0000000..78a59d0
--- /dev/null
+++ b/vendor/react/socket/tests/FunctionalSecureServerTest.php
@@ -0,0 +1,438 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+ }
+
+ public function testEmitsConnectionForNewConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testWritesDataToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write('foo');
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local ConnectionInterface */
+
+ $local->on('data', $this->expectCallableOnceWith('foo'));
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testWritesDataInMultipleChunksToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write(str_repeat('*', 400000));
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ public function testWritesMoreDataInMultipleChunksToConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) {
+ $conn->write(str_repeat('*', 2000000));
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(2000000, $received);
+ }
+
+ public function testEmitsDataFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $once = $this->expectCallableOnceWith('foo');
+ $server->on('connection', function (ConnectionInterface $conn) use ($once) {
+ $conn->on('data', $once);
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $local->write("foo");
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsDataInMultipleChunksFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $received = 0;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$received) {
+ $conn->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $local->write(str_repeat('*', 400000));
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ public function testPipesDataBackInMultipleChunksFromConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $server->on('connection', function (ConnectionInterface $conn) use (&$received) {
+ $conn->pipe($conn);
+ });
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $local = Block\await($promise, $loop, self::TIMEOUT);
+ /* @var $local React\Stream\Stream */
+
+ $received = 0;
+ $local->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+
+ $local->write(str_repeat('*', 400000));
+
+ Block\sleep(self::TIMEOUT, $loop);
+
+ $this->assertEquals(400000, $received);
+ }
+
+ /**
+ * @requires PHP 5.6
+ */
+ public function testEmitsConnectionForNewTlsv11Connection()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false,
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ /**
+ * @requires PHP 5.6
+ */
+ public function testEmitsErrorForClientWithTlsVersionMismatch()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_1_SERVER|STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false,
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsConnectionForNewConnectionWithEncryptedCertificate()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem',
+ 'passphrase' => 'swordfish'
+ ));
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithInvalidCertificate()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => 'invalid.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithEncryptedCertificateMissingPassphrase()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForServerWithEncryptedCertificateWithInvalidPassphrase()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost_swordfish.pem',
+ 'passphrase' => 'nope'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $this->setExpectedException('RuntimeException', 'handshake');
+ Block\await($promise, $loop, self::TIMEOUT);
+ }
+
+ public function testEmitsErrorForConnectionWithPeerVerification()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => true
+ ));
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then(null, $this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsErrorIfConnectionIsCancelled()
+ {
+ if (PHP_OS !== 'Linux') {
+ $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new SecureConnector(new TcpConnector($loop), $loop, array(
+ 'verify_peer' => false
+ ));
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsNothingIfConnectionIsIdle()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableNever());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect(str_replace('tls://', '', $server->getAddress()));
+
+ $promise->then($this->expectCallableOnce());
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+
+ public function testEmitsErrorIfConnectionIsNotSecureHandshake()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new SecureServer($server, $loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $server->on('connection', $this->expectCallableNever());
+ $server->on('error', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect(str_replace('tls://', '', $server->getAddress()));
+
+ $promise->then(function (ConnectionInterface $stream) {
+ $stream->write("GET / HTTP/1.0\r\n\r\n");
+ });
+
+ Block\sleep(self::TIMEOUT, $loop);
+ }
+}
diff --git a/vendor/react/socket/tests/FunctionalTcpServerTest.php b/vendor/react/socket/tests/FunctionalTcpServerTest.php
new file mode 100755
index 0000000..ec7855e
--- /dev/null
+++ b/vendor/react/socket/tests/FunctionalTcpServerTest.php
@@ -0,0 +1,324 @@
+on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsNoConnectionForNewConnectionWhenPaused()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+ $server->pause();
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionForNewConnectionWhenResumedAfterPause()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->pause();
+ $server->resume();
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionWithRemoteIp()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $peer);
+ }
+
+ public function testEmitsConnectionWithLocalIp()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $local);
+ $this->assertEquals($server->getAddress(), $local);
+ }
+
+ public function testEmitsConnectionWithLocalIpDespiteListeningOnAll()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer('0.0.0.0:0', $loop);
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $local);
+ }
+
+ public function testEmitsConnectionWithRemoteIpAfterConnectionIsClosedByPeer()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $conn->on('close', function () use ($conn, &$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $client = Block\await($promise, $loop, 0.1);
+ $client->end();
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('127.0.0.1:', $peer);
+ }
+
+ public function testEmitsConnectionWithRemoteNullAddressAfterConnectionIsClosedLocally()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $conn->close();
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertNull($peer);
+ }
+
+ public function testEmitsConnectionEvenIfConnectionIsCancelled()
+ {
+ if (PHP_OS !== 'Linux') {
+ $this->markTestSkipped('Linux only (OS is ' . PHP_OS . ')');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $promise->then(null, $this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionForNewIpv6Connection()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $server->on('connection', $this->expectCallableOnce());
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testEmitsConnectionWithRemoteIpv6()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $peer = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$peer) {
+ $peer = $conn->getRemoteAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('[::1]:', $peer);
+ }
+
+ public function testEmitsConnectionWithLocalIpv6()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:0', $loop);
+ } catch (\RuntimeException $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
+ }
+
+ $local = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$local) {
+ $local = $conn->getLocalAddress();
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertContains('[::1]:', $local);
+ $this->assertEquals($server->getAddress(), $local);
+ }
+
+ public function testEmitsConnectionWithInheritedContextOptions()
+ {
+ if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
+ // https://3v4l.org/hB4Tc
+ $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
+ }
+
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop, array(
+ 'backlog' => 4
+ ));
+
+ $all = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$all) {
+ $all = stream_context_get_options($conn->stream);
+ });
+
+ $connector = new TcpConnector($loop);
+ $promise = $connector->connect($server->getAddress());
+
+ $promise->then($this->expectCallableOnce());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnInvalidUri()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('///', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWithoutPort()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('127.0.0.1', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWithWrongScheme()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('udp://127.0.0.1:0', $loop);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testFailsToListenOnUriWIthHostname()
+ {
+ $loop = Factory::create();
+
+ new TcpServer('localhost:8080', $loop);
+ }
+}
diff --git a/vendor/react/socket/tests/IntegrationTest.php b/vendor/react/socket/tests/IntegrationTest.php
new file mode 100755
index 0000000..59dff4f
--- /dev/null
+++ b/vendor/react/socket/tests/IntegrationTest.php
@@ -0,0 +1,328 @@
+connect('google.com:80'), $loop);
+
+ $this->assertContains(':80', $conn->getRemoteAddress());
+ $this->assertNotEquals('google.com:80', $conn->getRemoteAddress());
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingEncryptedStuffFromGoogleShouldWork()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+ $secureConnector = new Connector($loop);
+
+ $conn = Block\await($secureConnector->connect('tls://google.com:443'), $loop);
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingEncryptedStuffFromGoogleShouldWorkIfHostIsResolvedFirst()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $factory = new ResolverFactory();
+ $dns = $factory->create('8.8.8.8', $loop);
+
+ $connector = new DnsConnector(
+ new SecureConnector(
+ new TcpConnector($loop),
+ $loop
+ ),
+ $dns
+ );
+
+ $conn = Block\await($connector->connect('google.com:443'), $loop);
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ /** @test */
+ public function gettingPlaintextStuffFromEncryptedGoogleShouldNotWork()
+ {
+ $loop = Factory::create();
+ $connector = new Connector($loop);
+
+ $conn = Block\await($connector->connect('google.com:443'), $loop);
+
+ $this->assertContains(':443', $conn->getRemoteAddress());
+ $this->assertNotEquals('google.com:443', $conn->getRemoteAddress());
+
+ $conn->write("GET / HTTP/1.0\r\n\r\n");
+
+ $response = $this->buffer($conn, $loop, self::TIMEOUT);
+
+ $this->assertNotRegExp('#^HTTP/1\.0#', $response);
+ }
+
+ public function testConnectingFailsIfDnsUsesInvalidResolver()
+ {
+ $loop = Factory::create();
+
+ $factory = new ResolverFactory();
+ $dns = $factory->create('demo.invalid', $loop);
+
+ $connector = new Connector($loop, array(
+ 'dns' => $dns
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
+ }
+
+ public function testCancellingPendingConnectionWithoutTimeoutShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+ $promise = $connector->connect('8.8.8.8:80');
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testCancellingPendingConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop);
+
+ gc_collect_cycles();
+ $promise = $connector->connect('8.8.8.8:80');
+ $promise->cancel();
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForRejectedConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+
+ $wait = true;
+ $promise = $connector->connect('127.0.0.1:1')->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect connection refused error
+ Block\sleep(0.01, $loop);
+ if ($wait) {
+ Block\sleep(0.2, $loop);
+ if ($wait) {
+ $this->fail('Connection attempt did not fail');
+ }
+ }
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ /**
+ * @requires PHP 7
+ */
+ public function testWaitingForConnectionTimeoutShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => 0.001));
+
+ gc_collect_cycles();
+
+ $wait = true;
+ $promise = $connector->connect('google.com:80')->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect connection timeout error
+ Block\sleep(0.01, $loop);
+ if ($wait) {
+ Block\sleep(0.2, $loop);
+ if ($wait) {
+ $this->fail('Connection attempt did not fail');
+ }
+ }
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForInvalidDnsConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+
+ $wait = true;
+ $promise = $connector->connect('example.invalid:80')->then(
+ null,
+ function ($e) use (&$wait) {
+ $wait = false;
+ throw $e;
+ }
+ );
+
+ // run loop for short period to ensure we detect DNS error
+ Block\sleep(0.01, $loop);
+ if ($wait) {
+ Block\sleep(0.2, $loop);
+ if ($wait) {
+ $this->fail('Connection attempt did not fail');
+ }
+ }
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testWaitingForSuccessfullyClosedConnectionShouldNotCreateAnyGarbageReferences()
+ {
+ if (class_exists('React\Promise\When')) {
+ $this->markTestSkipped('Not supported on legacy Promise v1 API');
+ }
+
+ $loop = Factory::create();
+ $connector = new Connector($loop, array('timeout' => false));
+
+ gc_collect_cycles();
+ $promise = $connector->connect('google.com:80')->then(
+ function ($conn) {
+ $conn->close();
+ }
+ );
+ Block\await($promise, $loop, self::TIMEOUT);
+ unset($promise);
+
+ $this->assertEquals(0, gc_collect_cycles());
+ }
+
+ public function testConnectingFailsIfTimeoutIsTooSmall()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'timeout' => 0.001
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
+ }
+
+ public function testSelfSignedRejectsIfVerificationIsEnabled()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'verify_peer' => true
+ )
+ ));
+
+ $this->setExpectedException('RuntimeException');
+ Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
+ }
+
+ public function testSelfSignedResolvesIfVerificationIsDisabled()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $connector = new Connector($loop, array(
+ 'tls' => array(
+ 'verify_peer' => false
+ )
+ ));
+
+ $conn = Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
+ $conn->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+}
diff --git a/vendor/react/socket/tests/LimitingServerTest.php b/vendor/react/socket/tests/LimitingServerTest.php
new file mode 100755
index 0000000..2cc9a58
--- /dev/null
+++ b/vendor/react/socket/tests/LimitingServerTest.php
@@ -0,0 +1,195 @@
+getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn('127.0.0.1:1234');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $this->assertEquals('127.0.0.1:1234', $server->getAddress());
+ }
+
+ public function testPauseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ }
+
+ public function testPauseTwiceWillBePassedThroughToTcpServerOnce()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testResumeWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->resume();
+ }
+
+ public function testResumeTwiceWillBePassedThroughToTcpServerOnce()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->pause();
+ $server->resume();
+ $server->resume();
+ }
+
+ public function testCloseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('close');
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->close();
+ }
+
+ public function testSocketErrorWillBeForwarded()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 100);
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('error', array(new \RuntimeException('test')));
+ }
+
+ public function testSocketConnectionWillBeForwarded()
+ {
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 100);
+ $server->on('connection', $this->expectCallableOnceWith($connection));
+ $server->on('error', $this->expectCallableNever());
+
+ $tcp->emit('connection', array($connection));
+
+ $this->assertEquals(array($connection), $server->getConnections());
+ }
+
+ public function testSocketConnectionWillBeClosedOnceLimitIsReached()
+ {
+ $first = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $first->expects($this->never())->method('close');
+ $second = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $second->expects($this->once())->method('close');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new LimitingServer($tcp, 1);
+ $server->on('connection', $this->expectCallableOnceWith($first));
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('connection', array($first));
+ $tcp->emit('connection', array($second));
+ }
+
+ public function testPausingServerWillBePausedOnceLimitIsReached()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $tcp = new TcpServer(0, $loop);
+
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+
+ $server = new LimitingServer($tcp, 1, true);
+
+ $tcp->emit('connection', array($connection));
+ }
+
+ public function testSocketDisconnectionWillRemoveFromList()
+ {
+ $loop = Factory::create();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $socket = stream_socket_client($tcp->getAddress());
+ fclose($socket);
+
+ $server = new LimitingServer($tcp, 100);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('error', $this->expectCallableNever());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array(), $server->getConnections());
+ }
+
+ public function testPausingServerWillEmitOnlyOneButAcceptTwoConnectionsDueToOperatingSystem()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(0, $loop);
+ $server = new LimitingServer($server, 1, true);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('error', $this->expectCallableNever());
+
+ $first = stream_socket_client($server->getAddress());
+ $second = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ fclose($first);
+ fclose($second);
+ }
+
+ public function testPausingServerWillEmitTwoConnectionsFromBacklog()
+ {
+ $loop = Factory::create();
+
+ $twice = $this->createCallableMock();
+ $twice->expects($this->exactly(2))->method('__invoke');
+
+ $server = new TcpServer(0, $loop);
+ $server = new LimitingServer($server, 1, true);
+ $server->on('connection', $twice);
+ $server->on('error', $this->expectCallableNever());
+
+ $first = stream_socket_client($server->getAddress());
+ fclose($first);
+ $second = stream_socket_client($server->getAddress());
+ fclose($second);
+
+ Block\sleep(0.1, $loop);
+ }
+}
diff --git a/vendor/react/socket/tests/SecureConnectorTest.php b/vendor/react/socket/tests/SecureConnectorTest.php
new file mode 100755
index 0000000..0b3a702
--- /dev/null
+++ b/vendor/react/socket/tests/SecureConnectorTest.php
@@ -0,0 +1,74 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->tcp = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $this->connector = new SecureConnector($this->tcp, $this->loop);
+ }
+
+ public function testConnectionWillWaitForTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ }
+
+ public function testConnectionWithCompleteUriWillBePassedThroughExpectForScheme()
+ {
+ $pending = new Promise\Promise(function () { });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80/path?query#fragment'))->will($this->returnValue($pending));
+
+ $this->connector->connect('tls://example.com:80/path?query#fragment');
+ }
+
+ public function testConnectionToInvalidSchemeWillReject()
+ {
+ $this->tcp->expects($this->never())->method('connect');
+
+ $promise = $this->connector->connect('tcp://example.com:80');
+
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testCancelDuringTcpConnectionCancelsTcpConnection()
+ {
+ $pending = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending));
+
+ $promise = $this->connector->connect('example.com:80');
+ $promise->cancel();
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+
+ public function testConnectionWillBeClosedAndRejectedIfConnectioIsNoStream()
+ {
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $connection->expects($this->once())->method('close');
+
+ $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->willReturn(Promise\resolve($connection));
+
+ $promise = $this->connector->connect('example.com:80');
+
+ $promise->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/vendor/react/socket/tests/SecureIntegrationTest.php b/vendor/react/socket/tests/SecureIntegrationTest.php
new file mode 100755
index 0000000..8c9ba14
--- /dev/null
+++ b/vendor/react/socket/tests/SecureIntegrationTest.php
@@ -0,0 +1,204 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $this->loop = LoopFactory::create();
+ $this->server = new TcpServer(0, $this->loop);
+ $this->server = new SecureServer($this->server, $this->loop, array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ ));
+ $this->address = $this->server->getAddress();
+ $this->connector = new SecureConnector(new TcpConnector($this->loop), $this->loop, array('verify_peer' => false));
+ }
+
+ public function tearDown()
+ {
+ if ($this->server !== null) {
+ $this->server->close();
+ $this->server = null;
+ }
+ }
+
+ public function testConnectToServer()
+ {
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testConnectToServerEmitsConnection()
+ {
+ $promiseServer = $this->createPromiseForEvent($this->server, 'connection', $this->expectCallableOnce());
+
+ $promiseClient = $this->connector->connect($this->address);
+
+ list($_, $client) = Block\awaitAll(array($promiseServer, $promiseClient), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->close();
+ }
+
+ public function testSendSmallDataToServerReceivesOneChunk()
+ {
+ // server expects one connection which emits one data event
+ $received = new Deferred();
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($received) {
+ $peer->on('data', function ($chunk) use ($received) {
+ $received->resolve($chunk);
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $client->write('hello');
+
+ // await server to report one "data" event
+ $data = Block\await($received->promise(), $this->loop, self::TIMEOUT);
+
+ $client->close();
+
+ $this->assertEquals('hello', $data);
+ }
+
+ public function testSendDataWithEndToServerReceivesAllData()
+ {
+ $disconnected = new Deferred();
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($disconnected) {
+ $received = '';
+ $peer->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ $peer->on('close', function () use (&$received, $disconnected) {
+ $disconnected->resolve($received);
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $data = str_repeat('a', 200000);
+ $client->end($data);
+
+ // await server to report connection "close" event
+ $received = Block\await($disconnected->promise(), $this->loop, self::TIMEOUT);
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testSendDataWithoutEndingToServerReceivesAllData()
+ {
+ $received = '';
+ $this->server->on('connection', function (ConnectionInterface $peer) use (&$received) {
+ $peer->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ $data = str_repeat('d', 200000);
+ $client->write($data);
+
+ // buffer incoming data for 0.1s (should be plenty of time)
+ Block\sleep(0.1, $this->loop);
+
+ $client->close();
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testConnectToServerWhichSendsSmallDataReceivesOneChunk()
+ {
+ $this->server->on('connection', function (ConnectionInterface $peer) {
+ $peer->write('hello');
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // await client to report one "data" event
+ $receive = $this->createPromiseForEvent($client, 'data', $this->expectCallableOnceWith('hello'));
+ Block\await($receive, $this->loop, self::TIMEOUT);
+
+ $client->close();
+ }
+
+ public function testConnectToServerWhichSendsDataWithEndReceivesAllData()
+ {
+ $data = str_repeat('b', 100000);
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
+ $peer->end($data);
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // await data from client until it closes
+ $received = $this->buffer($client, $this->loop, self::TIMEOUT);
+
+ $this->assertEquals($data, $received);
+ }
+
+ public function testConnectToServerWhichSendsDataWithoutEndingReceivesAllData()
+ {
+ $data = str_repeat('c', 100000);
+ $this->server->on('connection', function (ConnectionInterface $peer) use ($data) {
+ $peer->write($data);
+ });
+
+ $client = Block\await($this->connector->connect($this->address), $this->loop, self::TIMEOUT);
+ /* @var $client ConnectionInterface */
+
+ // buffer incoming data for 0.1s (should be plenty of time)
+ $received = '';
+ $client->on('data', function ($chunk) use (&$received) {
+ $received .= $chunk;
+ });
+ Block\sleep(0.1, $this->loop);
+
+ $client->close();
+
+ $this->assertEquals($data, $received);
+ }
+
+ private function createPromiseForEvent(EventEmitterInterface $emitter, $event, $fn)
+ {
+ return new Promise(function ($resolve) use ($emitter, $event, $fn) {
+ $emitter->on($event, function () use ($resolve, $fn) {
+ $resolve(call_user_func_array($fn, func_get_args()));
+ });
+ });
+ }
+}
diff --git a/vendor/react/socket/tests/SecureServerTest.php b/vendor/react/socket/tests/SecureServerTest.php
new file mode 100755
index 0000000..92c641f
--- /dev/null
+++ b/vendor/react/socket/tests/SecureServerTest.php
@@ -0,0 +1,105 @@
+markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+ }
+
+ public function testGetAddressWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn('tcp://127.0.0.1:1234');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $this->assertEquals('tls://127.0.0.1:1234', $server->getAddress());
+ }
+
+ public function testGetAddressWillReturnNullIfTcpServerReturnsNull()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('getAddress')->willReturn(null);
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $this->assertNull($server->getAddress());
+ }
+
+ public function testPauseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('pause');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->pause();
+ }
+
+ public function testResumeWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('resume');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->resume();
+ }
+
+ public function testCloseWillBePassedThroughToTcpServer()
+ {
+ $tcp = $this->getMockBuilder('React\Socket\ServerInterface')->getMock();
+ $tcp->expects($this->once())->method('close');
+
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->close();
+ }
+
+ public function testConnectionWillBeEndedWithErrorIfItIsNotAStream()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
+ $connection->expects($this->once())->method('end');
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('connection', array($connection));
+ }
+
+ public function testSocketErrorWillBeForwarded()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $tcp = new TcpServer(0, $loop);
+
+ $server = new SecureServer($tcp, $loop, array());
+
+ $server->on('error', $this->expectCallableOnce());
+
+ $tcp->emit('error', array(new \RuntimeException('test')));
+ }
+}
diff --git a/vendor/react/socket/tests/ServerTest.php b/vendor/react/socket/tests/ServerTest.php
new file mode 100755
index 0000000..14fdb2c
--- /dev/null
+++ b/vendor/react/socket/tests/ServerTest.php
@@ -0,0 +1,173 @@
+assertNotEquals(0, $server->getAddress());
+ $server->close();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsForInvalidUri()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $server = new Server('invalid URI', $loop);
+ }
+
+ public function testConstructorCreatesExpectedTcpServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+
+ $connector = new TcpConnector($loop);
+ $connector->connect($server->getAddress())
+ ->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT);
+
+ $connection->close();
+ $server->close();
+ }
+
+ public function testConstructorCreatesExpectedUnixServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server($this->getRandomSocketUri(), $loop);
+
+ $connector = new UnixConnector($loop);
+ $connector->connect($server->getAddress())
+ ->then($this->expectCallableOnce(), $this->expectCallableNever());
+
+ $connection = Block\await($connector->connect($server->getAddress()), $loop, self::TIMEOUT);
+
+ $connection->close();
+ $server->close();
+ }
+
+ public function testEmitsConnectionForNewConnection()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesNotEmitConnectionForNewConnectionToPausedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->pause();
+ $server->on('connection', $this->expectCallableNever());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesEmitConnectionForNewConnectionToResumedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->pause();
+ $server->on('connection', $this->expectCallableOnce());
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ $server->resume();
+ Block\sleep(0.1, $loop);
+ }
+
+ public function testDoesNotAllowConnectionToClosedServer()
+ {
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+ $address = $server->getAddress();
+ $server->close();
+
+ $client = @stream_socket_client($address);
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertFalse($client);
+ }
+
+ public function testEmitsConnectionWithInheritedContextOptions()
+ {
+ if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {
+ // https://3v4l.org/hB4Tc
+ $this->markTestSkipped('Not supported on legacy HHVM < 3.13');
+ }
+
+ $loop = Factory::create();
+
+ $server = new Server(0, $loop, array(
+ 'backlog' => 4
+ ));
+
+ $all = null;
+ $server->on('connection', function (ConnectionInterface $conn) use (&$all) {
+ $all = stream_context_get_options($conn->stream);
+ });
+
+ $client = stream_socket_client($server->getAddress());
+
+ Block\sleep(0.1, $loop);
+
+ $this->assertEquals(array('socket' => array('backlog' => 4)), $all);
+ }
+
+ public function testDoesNotEmitSecureConnectionForNewPlainConnection()
+ {
+ if (!function_exists('stream_socket_enable_crypto')) {
+ $this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
+ }
+
+ $loop = Factory::create();
+
+ $server = new Server('tls://127.0.0.1:0', $loop, array(
+ 'tls' => array(
+ 'local_cert' => __DIR__ . '/../examples/localhost.pem'
+ )
+ ));
+ $server->on('connection', $this->expectCallableNever());
+
+ $client = stream_socket_client(str_replace('tls://', '', $server->getAddress()));
+
+ Block\sleep(0.1, $loop);
+ }
+
+ private function getRandomSocketUri()
+ {
+ return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock';
+ }
+}
diff --git a/vendor/react/socket/tests/Stub/CallableStub.php b/vendor/react/socket/tests/Stub/CallableStub.php
new file mode 100755
index 0000000..1b197eb
--- /dev/null
+++ b/vendor/react/socket/tests/Stub/CallableStub.php
@@ -0,0 +1,10 @@
+data .= $data;
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ }
+
+ public function close()
+ {
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function getRemoteAddress()
+ {
+ return '127.0.0.1';
+ }
+}
diff --git a/vendor/react/socket/tests/Stub/ServerStub.php b/vendor/react/socket/tests/Stub/ServerStub.php
new file mode 100755
index 0000000..d9e74f4
--- /dev/null
+++ b/vendor/react/socket/tests/Stub/ServerStub.php
@@ -0,0 +1,18 @@
+connect('127.0.0.1:9999')
+ ->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldAddResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+
+ $valid = false;
+ $loop->expects($this->once())->method('addWriteStream')->with($this->callback(function ($arg) use (&$valid) {
+ $valid = is_resource($arg);
+ return true;
+ }));
+ $connector->connect($server->getAddress());
+
+ $this->assertTrue($valid);
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceed()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+
+ $this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithRemoteAdressSameAsTarget()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertEquals('tcp://127.0.0.1:9999', $connection->getRemoteAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithLocalAdressOnLocalhost()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertContains('tcp://127.0.0.1:', $connection->getLocalAddress());
+ $this->assertNotEquals('tcp://127.0.0.1:9999', $connection->getLocalAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToTcpServerShouldSucceedWithNullAddressesAfterConnectionClosed()
+ {
+ $loop = Factory::create();
+
+ $server = new TcpServer(9999, $loop);
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $connection->close();
+
+ $this->assertNull($connection->getRemoteAddress());
+ $this->assertNull($connection->getLocalAddress());
+ }
+
+ /** @test */
+ public function connectionToTcpServerWillCloseWhenOtherSideCloses()
+ {
+ $loop = Factory::create();
+
+ // immediately close connection and server once connection is in
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', function (ConnectionInterface $conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $once = $this->expectCallableOnce();
+ $connector = new TcpConnector($loop);
+ $connector->connect($server->getAddress())->then(function (ConnectionInterface $conn) use ($once) {
+ $conn->write('hello');
+ $conn->on('close', $once);
+ });
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToEmptyIp6PortShouldFail()
+ {
+ $loop = Factory::create();
+
+ $connector = new TcpConnector($loop);
+ $connector
+ ->connect('[::1]:9999')
+ ->then($this->expectCallableNever(), $this->expectCallableOnce());
+
+ $loop->run();
+ }
+
+ /** @test */
+ public function connectionToIp6TcpServerShouldSucceed()
+ {
+ $loop = Factory::create();
+
+ try {
+ $server = new TcpServer('[::1]:9999', $loop);
+ } catch (\Exception $e) {
+ $this->markTestSkipped('Unable to start IPv6 server socket (IPv6 not supported on this system?)');
+ }
+
+ $server->on('connection', $this->expectCallableOnce());
+ $server->on('connection', array($server, 'close'));
+
+ $connector = new TcpConnector($loop);
+
+ $connection = Block\await($connector->connect('[::1]:9999'), $loop, self::TIMEOUT);
+ /* @var $connection ConnectionInterface */
+
+ $this->assertEquals('tcp://[::1]:9999', $connection->getRemoteAddress());
+
+ $this->assertContains('tcp://[::1]:', $connection->getLocalAddress());
+ $this->assertNotEquals('tcp://[::1]:9999', $connection->getLocalAddress());
+
+ $connection->close();
+ }
+
+ /** @test */
+ public function connectionToHostnameShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('www.google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function connectionToInvalidPortShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('255.255.255.255:12345678')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function connectionToInvalidSchemeShouldFailImmediately()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+
+ $connector = new TcpConnector($loop);
+ $connector->connect('tls://google.com:443')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+ }
+
+ /** @test */
+ public function cancellingConnectionShouldRemoveResourceFromLoopAndCloseResource()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+ $server->on('connection', $this->expectCallableNever());
+
+ $loop->expects($this->once())->method('addWriteStream');
+ $promise = $connector->connect($server->getAddress());
+
+ $resource = null;
+ $valid = false;
+ $loop->expects($this->once())->method('removeWriteStream')->with($this->callback(function ($arg) use (&$resource, &$valid) {
+ $resource = $arg;
+ $valid = is_resource($arg);
+ return true;
+ }));
+ $promise->cancel();
+
+ // ensure that this was a valid resource during the removeWriteStream() call
+ $this->assertTrue($valid);
+
+ // ensure that this resource should now be closed after the cancel() call
+ $this->assertInternalType('resource', $resource);
+ $this->assertFalse(is_resource($resource));
+ }
+
+ /** @test */
+ public function cancellingConnectionShouldRejectPromise()
+ {
+ $loop = Factory::create();
+ $connector = new TcpConnector($loop);
+
+ $server = new TcpServer(0, $loop);
+
+ $promise = $connector->connect($server->getAddress());
+ $promise->cancel();
+
+ $this->setExpectedException('RuntimeException', 'Cancelled');
+ Block\await($promise, $loop);
+ }
+}
diff --git a/vendor/react/socket/tests/TcpServerTest.php b/vendor/react/socket/tests/TcpServerTest.php
new file mode 100755
index 0000000..72b3c28
--- /dev/null
+++ b/vendor/react/socket/tests/TcpServerTest.php
@@ -0,0 +1,285 @@
+loop = $this->createLoop();
+ $this->server = new TcpServer(0, $this->loop);
+
+ $this->port = parse_url($this->server->getAddress(), PHP_URL_PORT);
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::handleConnection
+ */
+ public function testConnection()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $this->server->on('connection', $this->expectCallableOnce());
+
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::handleConnection
+ */
+ public function testConnectionWithManyClients()
+ {
+ $client1 = stream_socket_client('tcp://localhost:'.$this->port);
+ $client2 = stream_socket_client('tcp://localhost:'.$this->port);
+ $client3 = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $this->server->on('connection', $this->expectCallableExactly(3));
+ $this->tick();
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataEventWillNotBeEmittedWhenClientSendsNoData()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedWithDataClientSends()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ fwrite($client, "foo\n");
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+ fwrite($client, "foo\n");
+ stream_socket_shutdown($client, STREAM_SHUT_WR);
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testLoopWillEndWhenServerIsClosed()
+ {
+ // explicitly unset server because we already call close()
+ $this->server->close();
+ $this->server = null;
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testCloseTwiceIsNoOp()
+ {
+ $this->server->close();
+ $this->server->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testGetAddressAfterCloseReturnsNull()
+ {
+ $this->server->close();
+ $this->assertNull($this->server->getAddress());
+ }
+
+ public function testLoopWillEndWhenServerIsClosedAfterSingleConnection()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $server->on('connection', function ($conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts()
+ {
+ $client = stream_socket_client('tcp://localhost:' . $this->port);
+ $stream = new DuplexResourceStream($client, $this->loop);
+
+ $bytes = 1024 * 1024;
+ $stream->end(str_repeat('*', $bytes));
+
+ $mock = $this->expectCallableOnce();
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $received = 0;
+ $server->on('connection', function ($conn) use ($mock, &$received, $server) {
+ // count number of bytes received
+ $conn->on('data', function ($data) use (&$received) {
+ $received += strlen($data);
+ });
+
+ $conn->on('end', $mock);
+
+ // do not await any further connections in order to let the loop terminate
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ $this->assertEquals($bytes, $received);
+ }
+
+ public function testConnectionDoesNotEndWhenClientDoesNotClose()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\Connection::end
+ */
+ public function testConnectionDoesEndWhenClientCloses()
+ {
+ $client = stream_socket_client('tcp://localhost:'.$this->port);
+
+ fclose($client);
+
+ $mock = $this->expectCallableOnce();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testCtorAddsResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new TcpServer(0, $loop);
+ }
+
+ public function testResumeWithoutPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->resume();
+ }
+
+ public function testPauseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->pause();
+ }
+
+ public function testPauseAfterPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testCloseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new TcpServer(0, $loop);
+ $server->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testListenOnBusyPortThrows()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Windows supports listening on same port multiple times');
+ }
+
+ $another = new TcpServer($this->port, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\TcpServer::close
+ */
+ public function tearDown()
+ {
+ if ($this->server) {
+ $this->server->close();
+ }
+ }
+
+ private function tick()
+ {
+ Block\sleep(0, $this->loop);
+ }
+}
diff --git a/vendor/react/socket/tests/TestCase.php b/vendor/react/socket/tests/TestCase.php
new file mode 100755
index 0000000..e87fc2f
--- /dev/null
+++ b/vendor/react/socket/tests/TestCase.php
@@ -0,0 +1,101 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $mock;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Socket\Stub\CallableStub')->getMock();
+ }
+
+ protected function buffer(ReadableStreamInterface $stream, LoopInterface $loop, $timeout)
+ {
+ if (!$stream->isReadable()) {
+ return '';
+ }
+
+ return Block\await(new Promise(
+ function ($resolve, $reject) use ($stream) {
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $reject);
+
+ $stream->on('close', function () use (&$buffer, $resolve) {
+ $resolve($buffer);
+ });
+ },
+ function () use ($stream) {
+ $stream->close();
+ throw new \RuntimeException();
+ }
+ ), $loop, $timeout);
+ }
+
+ public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
+ {
+ if (method_exists($this, 'expectException')) {
+ // PHPUnit 5+
+ $this->expectException($exception);
+ if ($exceptionMessage !== '') {
+ $this->expectExceptionMessage($exceptionMessage);
+ }
+ if ($exceptionCode !== null) {
+ $this->expectExceptionCode($exceptionCode);
+ }
+ } else {
+ // legacy PHPUnit 4
+ parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
+ }
+ }
+}
diff --git a/vendor/react/socket/tests/TimeoutConnectorTest.php b/vendor/react/socket/tests/TimeoutConnectorTest.php
new file mode 100755
index 0000000..64787d9
--- /dev/null
+++ b/vendor/react/socket/tests/TimeoutConnectorTest.php
@@ -0,0 +1,103 @@
+getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testRejectsWhenConnectorRejects()
+ {
+ $promise = Promise\reject(new \RuntimeException());
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 5.0, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testResolvesWhenConnectorResolves()
+ {
+ $promise = Promise\resolve();
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 5.0, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableOnce(),
+ $this->expectCallableNever()
+ );
+
+ $loop->run();
+ }
+
+ public function testRejectsAndCancelsPendingPromiseOnTimeout()
+ {
+ $promise = new Promise\Promise(function () { }, $this->expectCallableOnce());
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $timeout->connect('google.com:80')->then(
+ $this->expectCallableNever(),
+ $this->expectCallableOnce()
+ );
+
+ $loop->run();
+ }
+
+ public function testCancelsPendingPromiseOnCancel()
+ {
+ $promise = new Promise\Promise(function () { }, function () { throw new \Exception(); });
+
+ $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
+ $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
+
+ $loop = Factory::create();
+
+ $timeout = new TimeoutConnector($connector, 0.01, $loop);
+
+ $out = $timeout->connect('google.com:80');
+ $out->cancel();
+
+ $out->then($this->expectCallableNever(), $this->expectCallableOnce());
+ }
+}
diff --git a/vendor/react/socket/tests/UnixConnectorTest.php b/vendor/react/socket/tests/UnixConnectorTest.php
new file mode 100755
index 0000000..1564064
--- /dev/null
+++ b/vendor/react/socket/tests/UnixConnectorTest.php
@@ -0,0 +1,64 @@
+loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $this->connector = new UnixConnector($this->loop);
+ }
+
+ public function testInvalid()
+ {
+ $promise = $this->connector->connect('google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testInvalidScheme()
+ {
+ $promise = $this->connector->connect('tcp://google.com:80');
+ $promise->then(null, $this->expectCallableOnce());
+ }
+
+ public function testValid()
+ {
+ // random unix domain socket path
+ $path = sys_get_temp_dir() . '/test' . uniqid() . '.sock';
+
+ // temporarily create unix domain socket server to connect to
+ $server = stream_socket_server('unix://' . $path, $errno, $errstr);
+
+ // skip test if we can not create a test server (Windows etc.)
+ if (!$server) {
+ $this->markTestSkipped('Unable to create socket "' . $path . '": ' . $errstr . '(' . $errno .')');
+ return;
+ }
+
+ // tests succeeds if we get notified of successful connection
+ $promise = $this->connector->connect($path);
+ $promise->then($this->expectCallableOnce());
+
+ // remember remote and local address of this connection and close again
+ $remote = $local = false;
+ $promise->then(function(ConnectionInterface $conn) use (&$remote, &$local) {
+ $remote = $conn->getRemoteAddress();
+ $local = $conn->getLocalAddress();
+ $conn->close();
+ });
+
+ // clean up server
+ fclose($server);
+ unlink($path);
+
+ $this->assertNull($local);
+ $this->assertEquals('unix://' . $path, $remote);
+ }
+}
diff --git a/vendor/react/socket/tests/UnixServerTest.php b/vendor/react/socket/tests/UnixServerTest.php
new file mode 100755
index 0000000..10f7e4f
--- /dev/null
+++ b/vendor/react/socket/tests/UnixServerTest.php
@@ -0,0 +1,283 @@
+loop = Factory::create();
+ $this->uds = $this->getRandomSocketUri();
+ $this->server = new UnixServer($this->uds, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::handleConnection
+ */
+ public function testConnection()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $this->server->on('connection', $this->expectCallableOnce());
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::handleConnection
+ */
+ public function testConnectionWithManyClients()
+ {
+ $client1 = stream_socket_client($this->uds);
+ $client2 = stream_socket_client($this->uds);
+ $client3 = stream_socket_client($this->uds);
+
+ $this->server->on('connection', $this->expectCallableExactly(3));
+ $this->tick();
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataEventWillNotBeEmittedWhenClientSendsNoData()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedWithDataClientSends()
+ {
+ $client = stream_socket_client($this->uds);
+
+ fwrite($client, "foo\n");
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testDataWillBeEmittedEvenWhenClientShutsDownAfterSending()
+ {
+ $client = stream_socket_client($this->uds);
+ fwrite($client, "foo\n");
+ stream_socket_shutdown($client, STREAM_SHUT_WR);
+
+ $mock = $this->expectCallableOnceWith("foo\n");
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('data', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testLoopWillEndWhenServerIsClosed()
+ {
+ // explicitly unset server because we already call close()
+ $this->server->close();
+ $this->server = null;
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testCloseTwiceIsNoOp()
+ {
+ $this->server->close();
+ $this->server->close();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testGetAddressAfterCloseReturnsNull()
+ {
+ $this->server->close();
+ $this->assertNull($this->server->getAddress());
+ }
+
+ public function testLoopWillEndWhenServerIsClosedAfterSingleConnection()
+ {
+ $client = stream_socket_client($this->uds);
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $server->on('connection', function ($conn) use ($server) {
+ $conn->close();
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ // if we reach this, then everything is good
+ $this->assertNull(null);
+ }
+
+ public function testDataWillBeEmittedInMultipleChunksWhenClientSendsExcessiveAmounts()
+ {
+ $client = stream_socket_client($this->uds);
+ $stream = new DuplexResourceStream($client, $this->loop);
+
+ $bytes = 1024 * 1024;
+ $stream->end(str_repeat('*', $bytes));
+
+ $mock = $this->expectCallableOnce();
+
+ // explicitly unset server because we only accept a single connection
+ // and then already call close()
+ $server = $this->server;
+ $this->server = null;
+
+ $received = 0;
+ $server->on('connection', function ($conn) use ($mock, &$received, $server) {
+ // count number of bytes received
+ $conn->on('data', function ($data) use (&$received) {
+ $received += strlen($data);
+ });
+
+ $conn->on('end', $mock);
+
+ // do not await any further connections in order to let the loop terminate
+ $server->close();
+ });
+
+ $this->loop->run();
+
+ $this->assertEquals($bytes, $received);
+ }
+
+ public function testConnectionDoesNotEndWhenClientDoesNotClose()
+ {
+ $client = stream_socket_client($this->uds);
+
+ $mock = $this->expectCallableNever();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ /**
+ * @covers React\Socket\Connection::end
+ */
+ public function testConnectionDoesEndWhenClientCloses()
+ {
+ $client = stream_socket_client($this->uds);
+
+ fclose($client);
+
+ $mock = $this->expectCallableOnce();
+
+ $this->server->on('connection', function ($conn) use ($mock) {
+ $conn->on('end', $mock);
+ });
+ $this->tick();
+ $this->tick();
+ }
+
+ public function testCtorAddsResourceToLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ }
+
+ public function testResumeWithoutPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('addReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->resume();
+ }
+
+ public function testPauseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->pause();
+ }
+
+ public function testPauseAfterPauseIsNoOp()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->pause();
+ $server->pause();
+ }
+
+ public function testCloseRemovesResourceFromLoop()
+ {
+ $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ $loop->expects($this->once())->method('removeReadStream');
+
+ $server = new UnixServer($this->getRandomSocketUri(), $loop);
+ $server->close();
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testListenOnBusyPortThrows()
+ {
+ if (DIRECTORY_SEPARATOR === '\\') {
+ $this->markTestSkipped('Windows supports listening on same port multiple times');
+ }
+
+ $another = new UnixServer($this->uds, $this->loop);
+ }
+
+ /**
+ * @covers React\Socket\UnixServer::close
+ */
+ public function tearDown()
+ {
+ if ($this->server) {
+ $this->server->close();
+ }
+ }
+
+ private function getRandomSocketUri()
+ {
+ return "unix://" . sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(rand(), true) . '.sock';
+ }
+
+ private function tick()
+ {
+ Block\sleep(0, $this->loop);
+ }
+}
diff --git a/vendor/react/stream/.gitignore b/vendor/react/stream/.gitignore
new file mode 100755
index 0000000..987e2a2
--- /dev/null
+++ b/vendor/react/stream/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/vendor/react/stream/.travis.yml b/vendor/react/stream/.travis.yml
new file mode 100755
index 0000000..9ff354b
--- /dev/null
+++ b/vendor/react/stream/.travis.yml
@@ -0,0 +1,51 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+# - 7.0 # Mac OS X test setup, ignore errors, see below
+ - 7.1
+ - 7.2
+ - 7.3
+ - nightly # ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ include:
+ - os: osx
+ language: generic
+ php: 7.0 # just to look right on travis
+ env:
+ - PACKAGE: php70
+ allow_failures:
+ - php: nightly
+ - php: hhvm
+ - os: osx
+
+install:
+ # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew tap homebrew/homebrew-php
+ echo "Installing PHP ..."
+ brew install "${PACKAGE}"
+ brew install "${PACKAGE}"-xdebug
+ brew link "${PACKAGE}"
+ echo "Installing composer ..."
+ curl -s http://getcomposer.org/installer | php
+ mv composer.phar /usr/local/bin/composer
+ fi
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
+ - time php examples/91-benchmark-throughput.php
diff --git a/vendor/react/stream/CHANGELOG.md b/vendor/react/stream/CHANGELOG.md
new file mode 100755
index 0000000..c5a7dd3
--- /dev/null
+++ b/vendor/react/stream/CHANGELOG.md
@@ -0,0 +1,398 @@
+# Changelog
+
+## 1.1.0 (2018-01-01)
+
+* Improvement: Increase performance by optimizing global function and constant look ups
+ (#137 by @WyriHaximus)
+* Travis: Test against PHP 7.3
+ (#138 by @WyriHaximus)
+* Fix: Ignore empty reads
+ (#139 by @WyriHaximus)
+
+## 1.0.0 (2018-07-11)
+
+* First stable LTS release, now following [SemVer](https://semver.org/).
+ We'd like to emphasize that this component is production ready and battle-tested.
+ We plan to support all long-term support (LTS) releases for at least 24 months,
+ so you have a rock-solid foundation to build on top of.
+
+> Contains no other changes, so it's actually fully compatible with the v0.7.7 release.
+
+## 0.7.7 (2018-01-19)
+
+* Improve test suite by fixing forward compatibility with upcoming EventLoop
+ releases, avoid risky tests and add test group to skip integration tests
+ relying on internet connection and apply appropriate test timeouts.
+ (#128, #131 and #132 by @clue)
+
+## 0.7.6 (2017-12-21)
+
+* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12
+ (#126 by @clue)
+
+* Improve test suite by simplifying test bootstrapping logic via Composer and
+ test against PHP 7.2
+ (#127 by @clue and #124 by @carusogabriel)
+
+## 0.7.5 (2017-11-20)
+
+* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream`
+ (#119 by @clue)
+
+* Fix: Fix forward compatibility with upcoming EventLoop releases
+ (#121 by @clue)
+
+* Restructure examples to ease getting started
+ (#123 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#122 by @gabriel-caruso and #120 by @clue)
+
+## 0.7.4 (2017-10-11)
+
+* Fix: Remove event listeners from `CompositeStream` once closed and
+ remove undocumented left-over `close` event argument
+ (#116 by @clue)
+
+* Minor documentation improvements: Fix wrong class name in example,
+ fix typos in README and
+ fix forward compatibility with upcoming EventLoop releases in example
+ (#113 by @docteurklein and #114 and #115 by @clue)
+
+* Improve test suite by running against Mac OS X on Travis
+ (#112 by @clue)
+
+## 0.7.3 (2017-08-05)
+
+* Improvement: Support Événement 3.0 a long side 2.0 and 1.0
+ (#108 by @WyriHaximus)
+
+* Readme: Corrected loop initialization in usage example
+ (#109 by @pulyavin)
+
+* Travis: Lock linux distribution preventing future builds from breaking
+ (#110 by @clue)
+
+## 0.7.2 (2017-06-15)
+
+* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream.
+ (#107 by @WyriHaximus)
+
+## 0.7.1 (2017-05-20)
+
+* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of
+ bytes to write at once.
+ (#105 by @clue)
+
+ ```php
+ $stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+ ```
+
+* Ignore HHVM test failures for now until Travis tests work again
+ (#106 by @clue)
+
+## 0.7.0 (2017-05-04)
+
+* Removed / BC break: Remove deprecated and unneeded functionality
+ (#45, #87, #90, #91 and #93 by @clue)
+
+ * Remove deprecated `Stream` class, use `DuplexResourceStream` instead
+ (#87 by @clue)
+
+ * Remove public `$buffer` property, use new constructor parameters instead
+ (#91 by @clue)
+
+ * Remove public `$stream` property from all resource streams
+ (#90 by @clue)
+
+ * Remove undocumented and now unused `ReadableStream` and `WritableStream`
+ (#93 by @clue)
+
+ * Remove `BufferedSink`
+ (#45 by @clue)
+
+* Feature / BC break: Simplify `ThroughStream` by using data callback instead of
+ inheritance. It is now a direct implementation of `DuplexStreamInterface`.
+ (#88 and #89 by @clue)
+
+ ```php
+ $through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+ });
+ $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+ $through->write(array(2, true));
+ ```
+
+* Feature / BC break: The `CompositeStream` starts closed if either side is
+ already closed and forwards pause to pipe source on first write attempt.
+ (#96 and #103 by @clue)
+
+ If either side of the composite stream closes, it will also close the other
+ side. We now also ensure that if either side is already closed during
+ instantiation, it will also close the other side.
+
+* BC break: Mark all classes as `final` and
+ mark internal API as `private` to discourage inheritance
+ (#95 and #99 by @clue)
+
+* Feature / BC break: Only emit `error` event for fatal errors
+ (#92 by @clue)
+
+ > The `error` event was previously also allowed to be emitted for non-fatal
+ errors, but our implementations actually only ever emitted this as a fatal
+ error and then closed the stream.
+
+* Feature: Explicitly allow custom events and exclude any semantics
+ (#97 by @clue)
+
+* Strict definition for event callback functions
+ (#101 by @clue)
+
+* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation
+ (#100 and #102 by @clue)
+
+* Actually require all dependencies so this is self-contained and improve
+ forward compatibility with EventLoop v1.0 and v0.5
+ (#94 and #98 by @clue)
+
+## 0.6.0 (2017-03-26)
+
+* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream`
+ (#85 by @clue)
+
+ ```php
+ // old (does still work for BC reasons)
+ $stream = new Stream($connection, $loop);
+
+ // new
+ $stream = new DuplexResourceStream($connection, $loop);
+ ```
+
+ Note that the `DuplexResourceStream` now rejects read-only or write-only
+ streams, so this may affect BC. If you want a read-only or write-only
+ resource, use `ReadableResourceStream` or `WritableResourceStream` instead of
+ `DuplexResourceStream`.
+
+ > BC note: This class was previously called `Stream`. The `Stream` class still
+ exists for BC reasons and will be removed in future versions of this package.
+
+* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`)
+ (#84 by @clue)
+
+ ```php
+ // old
+ $stream = new Buffer(STDOUT, $loop);
+
+ // new
+ $stream = new WritableResourceStream(STDOUT, $loop);
+ ```
+
+* Feature: Add `ReadableResourceStream`
+ (#83 by @clue)
+
+ ```php
+ $stream = new ReadableResourceStream(STDIN, $loop);
+ ```
+
+* Fix / BC Break: Enforce using non-blocking I/O
+ (#46 by @clue)
+
+ > BC note: This is known to affect process pipes on Windows which do not
+ support non-blocking I/O and could thus block the whole EventLoop previously.
+
+* Feature / Fix / BC break: Consistent semantics for
+ `DuplexStreamInterface::end()` to ensure it SHOULD also end readable side
+ (#86 by @clue)
+
+* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4
+ (#80 by @clue)
+
+## 0.5.0 (2017-03-08)
+
+* Feature / BC break: Consistent `end` event semantics (EOF)
+ (#70 by @clue)
+
+ The `end` event will now only be emitted for a *successful* end, not if the
+ stream closes due to an unrecoverable `error` event or if you call `close()`
+ explicitly.
+ If you want to detect when the stream closes (terminates), use the `close`
+ event instead.
+
+* BC break: Remove custom (undocumented) `full-drain` event from `Buffer`
+ (#63 and #68 by @clue)
+
+ > The `full-drain` event was undocumented and mostly used internally.
+ Relying on this event has attracted some low-quality code in the past, so
+ we've removed this from the public API in order to work out a better
+ solution instead.
+ If you want to detect when the buffer finishes flushing data to the stream,
+ you may want to look into its `end()` method or the `close` event instead.
+
+* Feature / BC break: Consistent event semantics and documentation,
+ explicitly state *when* events will be emitted and *which* arguments they
+ receive.
+ (#73 and #69 by @clue)
+
+ The documentation now explicitly defines each event and its arguments.
+ Custom events and event arguments are still supported.
+ Most notably, all defined events only receive inherently required event
+ arguments and no longer transmit the instance they are emitted on for
+ consistency and performance reasons.
+
+ ```php
+ // old (inconsistent and not supported by all implementations)
+ $stream->on('data', function ($data, $stream) {
+ // process $data
+ });
+
+ // new (consistent throughout the whole ecosystem)
+ $stream->on('data', function ($data) use ($stream) {
+ // process $data
+ });
+ ```
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature / BC break: Consistent method semantics and documentation
+ (#72 by @clue)
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature: Consistent `pipe()` semantics for closed and closing streams
+ (#71 from @clue)
+
+ The source stream will now always be paused via `pause()` when the
+ destination stream closes. Also, properly stop piping if the source
+ stream closes and remove all event forwarding.
+
+* Improve test suite by adding PHPUnit to `require-dev` and improving coverage.
+ (#74 and #75 by @clue, #66 by @nawarian)
+
+## 0.4.6 (2017-01-25)
+
+* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone)
+ (#62 by @clue)
+
+* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream`
+ (#60 by @clue)
+
+* Fix: Consistent `close` event behavior for `Buffer`
+ (#61 by @clue)
+
+## 0.4.5 (2016-11-13)
+
+* Feature: Support setting read buffer size to `null` (infinite)
+ (#42 by @clue)
+
+* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event
+ (#55 by @clue)
+
+* Vastly improved performance by factor of 10x to 20x.
+ Raise default buffer sizes to 64 KiB and simplify and improve error handling
+ and unneeded function calls.
+ (#53, #55, #56 by @clue)
+
+## 0.4.4 (2016-08-22)
+
+* Bug fix: Emit `error` event and close `Stream` when accessing the underlying
+ stream resource fails with a permanent error.
+ (#52 and #40 by @clue, #25 by @lysenkobv)
+
+* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF)
+ (#39 by @clue)
+
+* Bug fix: Ignore empty writes to `Buffer`
+ (#51 by @clue)
+
+* Add benchmarking script to measure throughput in CI
+ (#41 by @clue)
+
+## 0.4.3 (2015-10-07)
+
+* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau)
+* Bug fix: No double-write during drain call (@arnaud-lb)
+* Bug fix: Support HHVM (@clue)
+* Adjust compatibility to 5.3 (@clue)
+
+## 0.4.2 (2014-09-09)
+
+* Added DuplexStreamInterface
+* Stream sets stream resources to non-blocking
+* Fixed potential race condition in pipe
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: [Stream] Correctly detect closed connections
+
+## 0.3.2 (2013-05-10)
+
+* Bug fix: [Stream] Make sure CompositeStream is closed properly
+
+## 0.3.1 (2013-04-21)
+
+* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()`
+
+## 0.3.0 (2013-04-14)
+
+* Feature: [Stream] Factory method for BufferedSink
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.5 (2012-11-26)
+
+* Feature: Make BufferedSink trigger progress events on the promise (@jsor)
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream
+* Feature: Added BufferedSink
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Version bump
+
+## 0.2.1 (2012-10-14)
+
+* Bug fix: Check for EOF in `Buffer::write()`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/vendor/react/stream/LICENSE b/vendor/react/stream/LICENSE
new file mode 100755
index 0000000..a808108
--- /dev/null
+++ b/vendor/react/stream/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/react/stream/README.md b/vendor/react/stream/README.md
new file mode 100755
index 0000000..b5bc907
--- /dev/null
+++ b/vendor/react/stream/README.md
@@ -0,0 +1,1225 @@
+# Stream
+
+[](https://travis-ci.org/reactphp/stream)
+
+Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/).
+
+In order to make the [EventLoop](https://github.com/reactphp/event-loop)
+easier to use, this component introduces the powerful concept of "streams".
+Streams allow you to efficiently process huge amounts of data (such as a multi
+Gigabyte file download) in small chunks without having to store everything in
+memory at once.
+They are very similar to the streams found in PHP itself,
+but have an interface more suited for async, non-blocking I/O.
+
+**Table of contents**
+
+* [Stream usage](#stream-usage)
+ * [ReadableStreamInterface](#readablestreaminterface)
+ * [data event](#data-event)
+ * [end event](#end-event)
+ * [error event](#error-event)
+ * [close event](#close-event)
+ * [isReadable()](#isreadable)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [pipe()](#pipe)
+ * [close()](#close)
+ * [WritableStreamInterface](#writablestreaminterface)
+ * [drain event](#drain-event)
+ * [pipe event](#pipe-event)
+ * [error event](#error-event-1)
+ * [close event](#close-event-1)
+ * [isWritable()](#iswritable)
+ * [write()](#write)
+ * [end()](#end)
+ * [close()](#close-1)
+ * [DuplexStreamInterface](#duplexstreaminterface)
+* [Creating streams](#creating-streams)
+ * [ReadableResourceStream](#readableresourcestream)
+ * [WritableResourceStream](#writableresourcestream)
+ * [DuplexResourceStream](#duplexresourcestream)
+ * [ThroughStream](#throughstream)
+ * [CompositeStream](#compositestream)
+* [Usage](#usage)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Stream usage
+
+ReactPHP uses the concept of "streams" throughout its ecosystem to provide a
+consistent higher-level abstraction for processing streams of arbitrary data
+contents and size.
+While a stream itself is a quite low-level concept, it can be used as a powerful
+abstraction to build higher-level components and protocols on top.
+
+If you're new to this concept, it helps to think of them as a water pipe:
+You can consume water from a source or you can produce water and forward (pipe)
+it to any destination (sink).
+
+Similarly, streams can either be
+
+* readable (such as `STDIN` terminal input) or
+* writable (such as `STDOUT` terminal output) or
+* duplex (both readable *and* writable, such as a TCP/IP connection)
+
+Accordingly, this package defines the following three interfaces
+
+* [`ReadableStreamInterface`](#readablestreaminterface)
+* [`WritableStreamInterface`](#writablestreaminterface)
+* [`DuplexStreamInterface`](#duplexstreaminterface)
+
+### ReadableStreamInterface
+
+The `ReadableStreamInterface` is responsible for providing an interface for
+read-only streams and the readable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### data event
+
+The `data` event will be emitted whenever some data was read/received
+from this source stream.
+The event receives a single mixed argument for incoming data.
+
+```php
+$stream->on('data', function ($data) {
+ echo $data;
+});
+```
+
+This event MAY be emitted any number of times, which may be zero times if
+this stream does not send any data at all.
+It SHOULD not be emitted after an `end` or `close` event.
+
+The given `$data` argument may be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit the raw (binary) payload data that is received over the wire as
+chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end event
+
+The `end` event will be emitted once the source stream has successfully
+reached the end of the stream (EOF).
+
+```php
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+a successful end was detected.
+It SHOULD NOT be emitted after a previous `end` or `close` event.
+It MUST NOT be emitted if the stream closes due to a non-successful
+end, such as after a previous `error` event.
+
+After the stream is ended, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+This event will only be emitted if the *end* was reached successfully,
+not if the stream was interrupted by an unrecoverable error or explicitly
+closed. Not all streams know this concept of a "successful end".
+Many use-cases involve detecting when the stream closes (terminates)
+instead, in this case you should use the `close` event.
+After the stream emits an `end` event, it SHOULD usually be followed by a
+`close` event.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit this event if either the remote side closes the connection or
+a file handle was successfully read until reaching its end (EOF).
+
+Note that this event should not be confused with the `end()` method.
+This event defines a successful end *reading* from a source stream, while
+the `end()` method defines *writing* a successful end to a destination
+stream.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to read from this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error or after an unexpected `data` or premature
+`end` event.
+It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-readable mode, see
+also `close()` and `isReadable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and do not make assumption about data
+boundaries (such as unexpected `data` or premature `end` events).
+In other words, many lower-level protocols (such as TCP/IP) may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+Unlike the `end` event, this event SHOULD be emitted whenever the stream
+closes, irrespective of whether this happens implicitly due to an
+unrecoverable error or explicitly when either side closes the stream.
+If you only want to detect a *successful* end, you should use the `end`
+event instead.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after reading a *successful* `end`
+event or after a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isReadable()
+
+The `isReadable(): bool` method can be used to
+check whether this stream is in a readable state (not closed already).
+
+This method can be used to check if the stream still accepts incoming
+data events or if it is ended or closed already.
+Once the stream is non-readable, no further `data` or `end` events SHOULD
+be emitted.
+
+```php
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+A successfully opened stream always MUST start in readable mode.
+
+Once the stream ends or closes, it MUST switch to non-readable mode.
+This can happen any time, explicitly through `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-readable mode, it MUST NOT transition
+back to readable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `isWritable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause reading incoming data events.
+
+Removes the data source file descriptor from the event loop. This
+allows you to throttle incoming data.
+
+Unless otherwise noted, a successfully opened stream SHOULD NOT start
+in paused state.
+
+Once the stream is paused, no futher `data` or `end` events SHOULD
+be emitted.
+
+```php
+$stream->pause();
+
+$stream->on('data', assertShouldNeverCalled());
+$stream->on('end', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+stream MAY continue emitting `data` events.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+
+See also `resume()`.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume reading incoming data events.
+
+Re-attach the data source after a previous `pause()`.
+
+```php
+$stream->pause();
+
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+
+See also `pause()`.
+
+#### pipe()
+
+The `pipe(WritableStreamInterface $dest, array $options = [])` method can be used to
+pipe all the data from this readable source into the given writable destination.
+
+Automatically sends all incoming data to the destination.
+Automatically throttles the source based on what the destination can handle.
+
+```php
+$source->pipe($dest);
+```
+
+Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+into itself in order to write back all the data that is received.
+This may be a useful feature for a TCP/IP echo service:
+
+```php
+$connection->pipe($connection);
+```
+
+This method returns the destination stream as-is, which can be used to
+set up chains of piped streams:
+
+```php
+$source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+```
+
+By default, this will call `end()` on the destination stream once the
+source stream emits an `end` event. This can be disabled like this:
+
+```php
+$source->pipe($dest, array('end' => false));
+```
+
+Note that this only applies to the `end` event.
+If an `error` or explicit `close` event happens on the source stream,
+you'll have to manually close the destination stream:
+
+```php
+$source->pipe($dest);
+$source->on('close', function () use ($dest) {
+ $dest->end('BYE!');
+});
+```
+
+If the source stream is not readable (closed state), then this is a NO-OP.
+
+```php
+$source->close();
+$source->pipe($dest); // NO-OP
+```
+
+If the destinantion stream is not writable (closed state), then this will simply
+throttle (pause) the source stream:
+
+```php
+$dest->close();
+$source->pipe($dest); // calls $source->pause()
+```
+
+Similarly, if the destination stream is closed while the pipe is still
+active, it will also throttle (pause) the source stream:
+
+```php
+$source->pipe($dest);
+$dest->close(); // calls $source->pause()
+```
+
+Once the pipe is set up successfully, the destination stream MUST emit
+a `pipe` event with this source stream an event argument.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to (forcefully) close the stream.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-readable
+mode, see also `isReadable()`.
+This means that no further `data` or `end` events SHOULD be emitted.
+
+```php
+$stream->close();
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this method should not be confused with the `end()` method.
+
+### WritableStreamInterface
+
+The `WritableStreamInterface` is responsible for providing an interface for
+write-only streams and the writable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### drain event
+
+The `drain` event will be emitted whenever the write buffer became full
+previously and is now ready to accept more data.
+
+```php
+$stream->on('drain', function () use ($stream) {
+ echo 'Stream is now ready to accept more data';
+});
+```
+
+This event SHOULD be emitted once every time the buffer became full
+previously and is now ready to accept more data.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if the buffer never became full in the first place.
+This event SHOULD NOT be emitted if the buffer has not become full
+previously.
+
+This event is mostly used internally, see also `write()` for more details.
+
+#### pipe event
+
+The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+into this stream.
+The event receives a single `ReadableStreamInterface` argument for the
+source stream.
+
+```php
+$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ echo 'Now receiving piped data';
+
+ // explicitly close target if source emits an error
+ $source->on('error', function () use ($stream) {
+ $stream->close();
+ });
+});
+
+$source->pipe($stream);
+```
+
+This event MUST be emitted once for each readable stream that is
+successfully piped into this destination stream.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if no stream is ever piped into this stream.
+This event MUST NOT be emitted if either the source is not readable
+(closed already) or this destination is not writable (closed already).
+
+This event is mostly used internally, see also `pipe()` for more details.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to write to this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$stream->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error.
+It SHOULD NOT be emitted after a previous `error` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-writable mode, see
+also `close()` and `isWritable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-writable mode,
+see also `isWritable()`.
+
+This event SHOULD be emitted whenever the stream closes, irrespective of
+whether this happens implicitly due to an unrecoverable error or
+explicitly when either side closes the stream.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after flushing the buffer from
+the `end()` method, after receiving a *successful* `end` event or after
+a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isWritable()
+
+The `isWritable(): bool` method can be used to
+check whether this stream is in a writable state (not closed already).
+
+This method can be used to check if the stream still accepts writing
+any data or if it is ended or closed already.
+Writing any data to a non-writable stream is a NO-OP:
+
+```php
+assert($stream->isWritable() === false);
+
+$stream->write('end'); // NO-OP
+$stream->end('end'); // NO-OP
+```
+
+A successfully opened stream always MUST start in writable mode.
+
+Once the stream ends or closes, it MUST switch to non-writable mode.
+This can happen any time, explicitly through `end()` or `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-writable mode, it MUST NOT transition
+back to writable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `isReadable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### write()
+
+The `write(mixed $data): bool` method can be used to
+write some data into the stream.
+
+A successful write MUST be confirmed with a boolean `true`, which means
+that either the data was written (flushed) immediately or is buffered and
+scheduled for a future write. Note that this interface gives you no
+control over explicitly flushing the buffered data, as finding the
+appropriate time for this is beyond the scope of this interface and left
+up to the implementation of this interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+an `error` event and MAY `close()` the stream if it can not recover from
+this error.
+
+If the internal buffer is full after adding `$data`, then `write()`
+SHOULD return `false`, indicating that the caller should stop sending
+data until the buffer drains.
+The stream SHOULD send a `drain` event once the buffer is ready to accept
+more data.
+
+Similarly, if the the stream is not writable (already in a closed state)
+it MUST NOT process the given `$data` and SHOULD return `false`,
+indicating that the caller should stop sending data.
+
+The given `$data` argument MAY be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will only accept the raw (binary) payload data that is transferred over
+the wire as chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end()
+
+The `end(mixed $data = null): void` method can be used to
+successfully end the stream (after optionally sending some final data).
+
+This method can be used to successfully end the stream, i.e. close
+the stream after sending out all data that is currently buffered.
+
+```php
+$stream->write('hello');
+$stream->write('world');
+$stream->end();
+```
+
+If there's no data currently buffered and nothing to be flushed, then
+this method MAY `close()` the stream immediately.
+
+If there's still data in the buffer that needs to be flushed first, then
+this method SHOULD try to write out this data and only then `close()`
+the stream.
+Once the stream is closed, it SHOULD emit a `close` event.
+
+Note that this interface gives you no control over explicitly flushing
+the buffered data, as finding the appropriate time for this is beyond the
+scope of this interface and left up to the implementation of this
+interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+You can optionally pass some final data that is written to the stream
+before ending the stream. If a non-`null` value is given as `$data`, then
+this method will behave just like calling `write($data)` before ending
+with no data.
+
+```php
+// shorter version
+$stream->end('bye');
+
+// same as longer version
+$stream->write('bye');
+$stream->end();
+```
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->end();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+also end its readable side, unless the stream supports half-open mode.
+In other words, after calling this method, these streams SHOULD switch
+into non-writable AND non-readable mode, see also `isReadable()`.
+This implies that in this case, the stream SHOULD NOT emit any `data`
+or `end` events anymore.
+Streams MAY choose to use the `pause()` method logic for this, but
+special care may have to be taken to ensure a following call to the
+`resume()` method SHOULD NOT continue emitting readable events.
+
+Note that this method should not be confused with the `close()` method.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to forcefully close the stream, i.e. close
+the stream without waiting for any buffered data to be flushed.
+If there's still data in the buffer, this data SHOULD be discarded.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->close();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+Note that this method should not be confused with the `end()` method.
+Unlike the `end()` method, this method does not take care of any existing
+buffers and simply discards any buffer contents.
+Likewise, this method may also be called after calling `end()` on a
+stream in order to stop waiting for the stream to flush its final data.
+
+```php
+$stream->end();
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->close();
+});
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+
+### DuplexStreamInterface
+
+The `DuplexStreamInterface` is responsible for providing an interface for
+duplex streams (both readable and writable).
+
+It builds on top of the existing interfaces for readable and writable streams
+and follows the exact same method and event semantics.
+If you're new to this concept, you should look into the
+`ReadableStreamInterface` and `WritableStreamInterface` first.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to the same events defined
+on the `ReadbleStreamInterface` and `WritableStreamInterface`.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+## Creating streams
+
+ReactPHP uses the concept of "streams" throughout its ecosystem, so that
+many higher-level consumers of this package only deal with
+[stream usage](#stream-usage).
+This implies that stream instances are most often created within some
+higher-level components and many consumers never actually have to deal with
+creating a stream instance.
+
+* Use [react/socket](https://github.com/reactphp/socket)
+ if you want to accept incoming or establish outgoing plaintext TCP/IP or
+ secure TLS socket connection streams.
+* Use [react/http](https://github.com/reactphp/http)
+ if you want to receive an incoming HTTP request body streams.
+* Use [react/child-process](https://github.com/reactphp/child-process)
+ if you want to communicate with child processes via process pipes such as
+ STDIN, STDOUT, STDERR etc.
+* Use experimental [react/filesystem](https://github.com/reactphp/filesystem)
+ if you want to read from / write to the filesystem.
+* See also the last chapter for [more real-world applications](#more).
+
+However, if you are writing a lower-level component or want to create a stream
+instance from a stream resource, then the following chapter is for you.
+
+> Note that the following examples use `fopen()` and `stream_socket_client()`
+ for illustration purposes only.
+ These functions SHOULD NOT be used in a truly async program because each call
+ may take several seconds to complete and would block the EventLoop otherwise.
+ Additionally, the `fopen()` call will return a file handle on some platforms
+ which may or may not be supported by all EventLoop implementations.
+ As an alternative, you may want to use higher-level libraries listed above.
+
+### ReadableResourceStream
+
+The `ReadableResourceStream` is a concrete implementation of the
+[`ReadableStreamInterface`](#readablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-only resource like a file stream opened in
+readable mode or a stream such as `STDIN`:
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop);
+$stream->on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened in reading mode (e.g. `fopen()` mode `r`).
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new ReadableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDIN etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new ReadableResourceStream(STDIN, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop, 8192);
+```
+
+> PHP bug warning: If the PHP process has explicitly been started without a
+ `STDIN` stream, then trying to read from `STDIN` may return data from
+ another stream resource. This does not happen if you start this with an empty
+ stream like `php test.php < /dev/null` instead of `php test.php <&-`.
+ See [#81](https://github.com/reactphp/stream/issues/81) for more details.
+
+### WritableResourceStream
+
+The `WritableResourceStream` is a concrete implementation of the
+[`WritableStreamInterface`](#writablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a write-only resource like a file stream opened in
+writable mode or a stream such as `STDOUT` or `STDERR`:
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new WritableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new WritableResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls
+this maximum buffer size in bytes.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, 8192);
+```
+
+This class takes an optional `int|null $writeChunkSize` parameter that controls
+this maximum buffer size in bytes to write at once to the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be written
+at once to the underlying stream resource. Note that the actual number
+of bytes written may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "write everything available" to the
+underlying stream resource.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+```
+
+See also [`write()`](#write) for more details.
+
+### DuplexResourceStream
+
+The `DuplexResourceStream` is a concrete implementation of the
+[`DuplexStreamInterface`](#duplexstreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-and-write resource like a file stream opened
+in read and write mode mode or a stream such as a TCP/IP connection:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`DuplexStreamInterface`](#duplexstreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for reading *and* writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new DuplexResourceStream(false, $loop);
+```
+
+See also the [`ReadableResourceStream`](#readableresourcestream) for read-only
+and the [`WritableResourceStream`](#writableresourcestream) for write-only
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new DuplexResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop, 8192);
+```
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes another optional `WritableStreamInterface|null $buffer` parameter
+that controls this write behavior of this stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+If you want to change the write buffer soft limit, you can pass an instance of
+[`WritableResourceStream`](#writableresourcestream) like this:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$buffer = new WritableResourceStream($conn, $loop, 8192);
+$stream = new DuplexResourceStream($conn, $loop, null, $buffer);
+```
+
+See also [`WritableResourceStream`](#writableresourcestream) for more details.
+
+### ThroughStream
+
+The `ThroughStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data
+you write to it through to its readable end.
+
+```php
+$through = new ThroughStream();
+$through->on('data', $this->expectCallableOnceWith('hello'));
+
+$through->write('hello');
+```
+
+Similarly, the [`end()` method](#end) will end the stream and emit an
+[`end` event](#end-event) and then [`close()`](#close-1) the stream.
+The [`close()` method](#close-1) will close the stream and emit a
+[`close` event](#close-event).
+Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+
+```php
+$through = new ThroughStream();
+$source->pipe($through)->pipe($dest);
+```
+
+Optionally, its constructor accepts any callable function which will then be
+used to *filter* any data written to it. This function receives a single data
+argument as passed to the writable side and must return the data as it will be
+passed to its readable end:
+
+```php
+$through = new ThroughStream('strtoupper');
+$source->pipe($through)->pipe($dest);
+```
+
+Note that this class makes no assumptions about any data types. This can be
+used to convert data, for example for transforming any structured data into
+a newline-delimited JSON (NDJSON) stream like this:
+
+```php
+$through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+});
+$through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+$through->write(array(2, true));
+```
+
+The callback function is allowed to throw an `Exception`. In this case,
+the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+
+```php
+$through = new ThroughStream(function ($data) {
+ if (!is_string($data)) {
+ throw new \UnexpectedValueException('Only strings allowed');
+ }
+ return $data;
+});
+$through->on('error', $this->expectCallableOnce()));
+$through->on('close', $this->expectCallableOnce()));
+$through->on('data', $this->expectCallableNever()));
+
+$through->write(2);
+```
+
+### CompositeStream
+
+The `CompositeStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and can be used to create a
+single duplex stream from two individual streams implementing
+[`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) respectively.
+
+This is useful for some APIs which may require a single
+[`DuplexStreamInterface`](#duplexstreaminterface) or simply because it's often
+more convenient to work with a single stream instance like this:
+
+```php
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdout = new WritableResourceStream(STDOUT, $loop);
+
+$stdio = new CompositeStream($stdin, $stdout);
+
+$stdio->on('data', function ($chunk) use ($stdio) {
+ $stdio->write('You said: ' . $chunk);
+});
+```
+
+This is a well-behaving stream which forwards all stream events from the
+underlying streams and forwards all streams calls to the underlying streams.
+
+If you `write()` to the duplex stream, it will simply `write()` to the
+writable side and return its status.
+
+If you `end()` the duplex stream, it will `end()` the writable side and will
+`pause()` the readable side.
+
+If you `close()` the duplex stream, both input streams will be closed.
+If either of the two input streams emits a `close` event, the duplex stream
+will also close.
+If either of the two input streams is already closed while constructing the
+duplex stream, it will `close()` the other side and return a closed stream.
+
+## Usage
+
+The following example can be used to pipe the contents of a source file into
+a destination file without having to ever read the whole file into memory:
+
+```php
+$loop = new React\EventLoop\StreamSelectLoop;
+
+$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r'), $loop);
+$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w'), $loop);
+
+$source->pipe($dest);
+
+$loop->run();
+```
+
+> Note that this example uses `fopen()` for illustration purposes only.
+ This should not be used in a truly async program because the filesystem is
+ inherently blocking and each call could potentially take several seconds.
+ See also [creating streams](#creating-streams) for more sophisticated
+ examples.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require react/stream:^1.0
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project due to its vast
+performance improvements.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## More
+
+* See [creating streams](#creating-streams) for more information on how streams
+ are created in real-world applications.
+* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
+ [dependents on Packagist](https://packagist.org/packages/react/stream/dependents)
+ for a list of packages that use streams in real-world applications.
diff --git a/vendor/react/stream/composer.json b/vendor/react/stream/composer.json
new file mode 100755
index 0000000..f6faa66
--- /dev/null
+++ b/vendor/react/stream/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "react/stream",
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35",
+ "clue/stream-filter": "~1.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Stream\\": "tests"
+ }
+ }
+}
diff --git a/vendor/react/stream/examples/01-http.php b/vendor/react/stream/examples/01-http.php
new file mode 100755
index 0000000..3687f7c
--- /dev/null
+++ b/vendor/react/stream/examples/01-http.php
@@ -0,0 +1,40 @@
+on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/vendor/react/stream/examples/02-https.php b/vendor/react/stream/examples/02-https.php
new file mode 100755
index 0000000..163f7c8
--- /dev/null
+++ b/vendor/react/stream/examples/02-https.php
@@ -0,0 +1,40 @@
+on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/vendor/react/stream/examples/11-cat.php b/vendor/react/stream/examples/11-cat.php
new file mode 100755
index 0000000..90fadc0
--- /dev/null
+++ b/vendor/react/stream/examples/11-cat.php
@@ -0,0 +1,28 @@
+pipe($stdout);
+
+$loop->run();
diff --git a/vendor/react/stream/examples/91-benchmark-throughput.php b/vendor/react/stream/examples/91-benchmark-throughput.php
new file mode 100755
index 0000000..ecf695c
--- /dev/null
+++ b/vendor/react/stream/examples/91-benchmark-throughput.php
@@ -0,0 +1,62 @@
+write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL);
+}
+$info->write('piping from ' . $if . ' to ' . $of . ' (for max ' . $t . ' second(s)) ...'. PHP_EOL);
+
+// setup input and output streams and pipe inbetween
+$fh = fopen($if, 'r');
+$in = new React\Stream\ReadableResourceStream($fh, $loop);
+$out = new React\Stream\WritableResourceStream(fopen($of, 'w'), $loop);
+$in->pipe($out);
+
+// stop input stream in $t seconds
+$start = microtime(true);
+$timeout = $loop->addTimer($t, function () use ($in, &$bytes) {
+ $in->close();
+});
+
+// print stream position once stream closes
+$in->on('close', function () use ($fh, $start, $loop, $timeout, $info) {
+ $t = microtime(true) - $start;
+ $loop->cancelTimer($timeout);
+
+ $bytes = ftell($fh);
+
+ $info->write('read ' . $bytes . ' byte(s) in ' . round($t, 3) . ' second(s) => ' . round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL);
+ $info->write('peak memory usage of ' . round(memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL);
+});
+
+$loop->run();
diff --git a/vendor/react/stream/phpunit.xml.dist b/vendor/react/stream/phpunit.xml.dist
new file mode 100755
index 0000000..13d3fab
--- /dev/null
+++ b/vendor/react/stream/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./src/
+
+
+
diff --git a/vendor/react/stream/src/CompositeStream.php b/vendor/react/stream/src/CompositeStream.php
new file mode 100755
index 0000000..153f2a3
--- /dev/null
+++ b/vendor/react/stream/src/CompositeStream.php
@@ -0,0 +1,82 @@
+readable = $readable;
+ $this->writable = $writable;
+
+ if (!$readable->isReadable() || !$writable->isWritable()) {
+ return $this->close();
+ }
+
+ Util::forwardEvents($this->readable, $this, array('data', 'end', 'error'));
+ Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe'));
+
+ $this->readable->on('close', array($this, 'close'));
+ $this->writable->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->readable->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->readable->pause();
+ }
+
+ public function resume()
+ {
+ if (!$this->writable->isWritable()) {
+ return;
+ }
+
+ $this->readable->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isWritable()
+ {
+ return $this->writable->isWritable();
+ }
+
+ public function write($data)
+ {
+ return $this->writable->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->readable->pause();
+ $this->writable->end($data);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->readable->close();
+ $this->writable->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/vendor/react/stream/src/DuplexResourceStream.php b/vendor/react/stream/src/DuplexResourceStream.php
new file mode 100755
index 0000000..5f038c6
--- /dev/null
+++ b/vendor/react/stream/src/DuplexResourceStream.php
@@ -0,0 +1,224 @@
+isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ if ($buffer === null) {
+ $buffer = new WritableResourceStream($stream, $loop);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+ $this->buffer = $buffer;
+
+ $that = $this;
+
+ $this->buffer->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+
+ $this->buffer->on('close', array($this, 'close'));
+
+ $this->buffer->on('drain', function () use ($that) {
+ $that->emit('drain');
+ });
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && $this->readable) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ return $this->buffer->write($data);
+ }
+
+ public function close()
+ {
+ if (!$this->writable && !$this->closing) {
+ return;
+ }
+
+ $this->closing = false;
+
+ $this->readable = false;
+ $this->writable = false;
+
+ $this->emit('close');
+ $this->pause();
+ $this->buffer->close();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ $this->closing = true;
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->pause();
+
+ $this->buffer->end($data);
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ /** @internal */
+ public function handleData($stream)
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/vendor/react/stream/src/DuplexStreamInterface.php b/vendor/react/stream/src/DuplexStreamInterface.php
new file mode 100755
index 0000000..631ce31
--- /dev/null
+++ b/vendor/react/stream/src/DuplexStreamInterface.php
@@ -0,0 +1,39 @@
+ Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see ReadableStreamInterface
+ * @see WritableStreamInterface
+ */
+interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface
+{
+}
diff --git a/vendor/react/stream/src/ReadableResourceStream.php b/vendor/react/stream/src/ReadableResourceStream.php
new file mode 100755
index 0000000..461f6e4
--- /dev/null
+++ b/vendor/react/stream/src/ReadableResourceStream.php
@@ -0,0 +1,177 @@
+isLegacyPipe($stream)) {
+ \stream_set_read_buffer($stream, 0);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && !$this->closed) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+
+ $this->emit('close');
+ $this->pause();
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleData()
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = \stream_get_contents($this->stream, $this->bufferSize);
+
+ \restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } elseif (\feof($this->stream)) {
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (\PHP_VERSION_ID < 50428 || (\PHP_VERSION_ID >= 50500 && \PHP_VERSION_ID < 50512)) {
+ $meta = \stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/vendor/react/stream/src/ReadableStreamInterface.php b/vendor/react/stream/src/ReadableStreamInterface.php
new file mode 100755
index 0000000..2b4c3d0
--- /dev/null
+++ b/vendor/react/stream/src/ReadableStreamInterface.php
@@ -0,0 +1,362 @@
+on('data', function ($data) {
+ * echo $data;
+ * });
+ * ```
+ *
+ * This event MAY be emitted any number of times, which may be zero times if
+ * this stream does not send any data at all.
+ * It SHOULD not be emitted after an `end` or `close` event.
+ *
+ * The given `$data` argument may be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit the raw (binary) payload data that is received over the wire as
+ * chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * end event:
+ * The `end` event will be emitted once the source stream has successfully
+ * reached the end of the stream (EOF).
+ *
+ * ```php
+ * $stream->on('end', function () {
+ * echo 'END';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * a successful end was detected.
+ * It SHOULD NOT be emitted after a previous `end` or `close` event.
+ * It MUST NOT be emitted if the stream closes due to a non-successful
+ * end, such as after a previous `error` event.
+ *
+ * After the stream is ended, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * This event will only be emitted if the *end* was reached successfully,
+ * not if the stream was interrupted by an unrecoverable error or explicitly
+ * closed. Not all streams know this concept of a "successful end".
+ * Many use-cases involve detecting when the stream closes (terminates)
+ * instead, in this case you should use the `close` event.
+ * After the stream emits an `end` event, it SHOULD usually be followed by a
+ * `close` event.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit this event if either the remote side closes the connection or
+ * a file handle was successfully read until reaching its end (EOF).
+ *
+ * Note that this event should not be confused with the `end()` method.
+ * This event defines a successful end *reading* from a source stream, while
+ * the `end()` method defines *writing* a successful end to a destination
+ * stream.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to read from this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error or after an unexpected `data` or premature
+ * `end` event.
+ * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-readable mode, see
+ * also `close()` and `isReadable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and do not make assumption about data
+ * boundaries (such as unexpected `data` or premature `end` events).
+ * In other words, many lower-level protocols (such as TCP/IP) may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * Unlike the `end` event, this event SHOULD be emitted whenever the stream
+ * closes, irrespective of whether this happens implicitly due to an
+ * unrecoverable error or explicitly when either side closes the stream.
+ * If you only want to detect a *successful* end, you should use the `end`
+ * event instead.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after reading a *successful* `end`
+ * event or after a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ */
+interface ReadableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a readable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts incoming
+ * data events or if it is ended or closed already.
+ * Once the stream is non-readable, no further `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * A successfully opened stream always MUST start in readable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-readable mode.
+ * This can happen any time, explicitly through `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-readable mode, it MUST NOT transition
+ * back to readable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `isWritable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Pauses reading incoming data events.
+ *
+ * Removes the data source file descriptor from the event loop. This
+ * allows you to throttle incoming data.
+ *
+ * Unless otherwise noted, a successfully opened stream SHOULD NOT start
+ * in paused state.
+ *
+ * Once the stream is paused, no futher `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $stream->on('data', assertShouldNeverCalled());
+ * $stream->on('end', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * stream MAY continue emitting `data` events.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes reading incoming data events.
+ *
+ * Re-attach the data source after a previous `pause()`.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Pipes all the data from this readable source into the given writable destination.
+ *
+ * Automatically sends all incoming data to the destination.
+ * Automatically throttles the source based on what the destination can handle.
+ *
+ * ```php
+ * $source->pipe($dest);
+ * ```
+ *
+ * Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+ * into itself in order to write back all the data that is received.
+ * This may be a useful feature for a TCP/IP echo service:
+ *
+ * ```php
+ * $connection->pipe($connection);
+ * ```
+ *
+ * This method returns the destination stream as-is, which can be used to
+ * set up chains of piped streams:
+ *
+ * ```php
+ * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+ * ```
+ *
+ * By default, this will call `end()` on the destination stream once the
+ * source stream emits an `end` event. This can be disabled like this:
+ *
+ * ```php
+ * $source->pipe($dest, array('end' => false));
+ * ```
+ *
+ * Note that this only applies to the `end` event.
+ * If an `error` or explicit `close` event happens on the source stream,
+ * you'll have to manually close the destination stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $source->on('close', function () use ($dest) {
+ * $dest->end('BYE!');
+ * });
+ * ```
+ *
+ * If the source stream is not readable (closed state), then this is a NO-OP.
+ *
+ * ```php
+ * $source->close();
+ * $source->pipe($dest); // NO-OP
+ * ```
+ *
+ * If the destinantion stream is not writable (closed state), then this will simply
+ * throttle (pause) the source stream:
+ *
+ * ```php
+ * $dest->close();
+ * $source->pipe($dest); // calls $source->pause()
+ * ```
+ *
+ * Similarly, if the destination stream is closed while the pipe is still
+ * active, it will also throttle (pause) the source stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $dest->close(); // calls $source->pause()
+ * ```
+ *
+ * Once the pipe is set up successfully, the destination stream MUST emit
+ * a `pipe` event with this source stream an event argument.
+ *
+ * @param WritableStreamInterface $dest
+ * @param array $options
+ * @return WritableStreamInterface $dest stream as-is
+ */
+ public function pipe(WritableStreamInterface $dest, array $options = array());
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to (forcefully) close the stream.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-readable
+ * mode, see also `isReadable()`.
+ * This means that no further `data` or `end` events SHOULD be emitted.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this method should not be confused with the `end()` method.
+ *
+ * @return void
+ * @see WritableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/vendor/react/stream/src/ThroughStream.php b/vendor/react/stream/src/ThroughStream.php
new file mode 100755
index 0000000..6f73fb8
--- /dev/null
+++ b/vendor/react/stream/src/ThroughStream.php
@@ -0,0 +1,190 @@
+on('data', $this->expectCallableOnceWith('hello'));
+ *
+ * $through->write('hello');
+ * ```
+ *
+ * Similarly, the [`end()` method](#end) will end the stream and emit an
+ * [`end` event](#end-event) and then [`close()`](#close-1) the stream.
+ * The [`close()` method](#close-1) will close the stream and emit a
+ * [`close` event](#close-event).
+ * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+ *
+ * ```php
+ * $through = new ThroughStream();
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Optionally, its constructor accepts any callable function which will then be
+ * used to *filter* any data written to it. This function receives a single data
+ * argument as passed to the writable side and must return the data as it will be
+ * passed to its readable end:
+ *
+ * ```php
+ * $through = new ThroughStream('strtoupper');
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Note that this class makes no assumptions about any data types. This can be
+ * used to convert data, for example for transforming any structured data into
+ * a newline-delimited JSON (NDJSON) stream like this:
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * return json_encode($data) . PHP_EOL;
+ * });
+ * $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+ *
+ * $through->write(array(2, true));
+ * ```
+ *
+ * The callback function is allowed to throw an `Exception`. In this case,
+ * the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * if (!is_string($data)) {
+ * throw new \UnexpectedValueException('Only strings allowed');
+ * }
+ * return $data;
+ * });
+ * $through->on('error', $this->expectCallableOnce()));
+ * $through->on('close', $this->expectCallableOnce()));
+ * $through->on('data', $this->expectCallableNever()));
+ *
+ * $through->write(2);
+ * ```
+ *
+ * @see WritableStreamInterface::write()
+ * @see WritableStreamInterface::end()
+ * @see DuplexStreamInterface::close()
+ * @see WritableStreamInterface::pipe()
+ */
+final class ThroughStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $readable = true;
+ private $writable = true;
+ private $closed = false;
+ private $paused = false;
+ private $drain = false;
+ private $callback;
+
+ public function __construct($callback = null)
+ {
+ if ($callback !== null && !\is_callable($callback)) {
+ throw new InvalidArgumentException('Invalid transformation callback given');
+ }
+
+ $this->callback = $callback;
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ if ($this->drain) {
+ $this->drain = false;
+ $this->emit('drain');
+ }
+ $this->paused = false;
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ if ($this->callback !== null) {
+ try {
+ $data = \call_user_func($this->callback, $data);
+ } catch (\Exception $e) {
+ $this->emit('error', array($e));
+ $this->close();
+
+ return false;
+ }
+ }
+
+ $this->emit('data', array($data));
+
+ if ($this->paused) {
+ $this->drain = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ if (null !== $data) {
+ $this->write($data);
+
+ // return if write() already caused the stream to close
+ if (!$this->writable) {
+ return;
+ }
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->paused = true;
+ $this->drain = false;
+
+ $this->emit('end');
+ $this->close();
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->closed = true;
+ $this->paused = true;
+ $this->drain = false;
+ $this->callback = null;
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/vendor/react/stream/src/Util.php b/vendor/react/stream/src/Util.php
new file mode 100755
index 0000000..056b037
--- /dev/null
+++ b/vendor/react/stream/src/Util.php
@@ -0,0 +1,75 @@
+ NO-OP
+ if (!$source->isReadable()) {
+ return $dest;
+ }
+
+ // destination not writable => just pause() source
+ if (!$dest->isWritable()) {
+ $source->pause();
+
+ return $dest;
+ }
+
+ $dest->emit('pipe', array($source));
+
+ // forward all source data events as $dest->write()
+ $source->on('data', $dataer = function ($data) use ($source, $dest) {
+ $feedMore = $dest->write($data);
+
+ if (false === $feedMore) {
+ $source->pause();
+ }
+ });
+ $dest->on('close', function () use ($source, $dataer) {
+ $source->removeListener('data', $dataer);
+ $source->pause();
+ });
+
+ // forward destination drain as $source->resume()
+ $dest->on('drain', $drainer = function () use ($source) {
+ $source->resume();
+ });
+ $source->on('close', function () use ($dest, $drainer) {
+ $dest->removeListener('drain', $drainer);
+ });
+
+ // forward end event from source as $dest->end()
+ $end = isset($options['end']) ? $options['end'] : true;
+ if ($end) {
+ $source->on('end', $ender = function () use ($dest) {
+ $dest->end();
+ });
+ $dest->on('close', function () use ($source, $ender) {
+ $source->removeListener('end', $ender);
+ });
+ }
+
+ return $dest;
+ }
+
+ public static function forwardEvents($source, $target, array $events)
+ {
+ foreach ($events as $event) {
+ $source->on($event, function () use ($event, $target) {
+ $target->emit($event, \func_get_args());
+ });
+ }
+ }
+}
diff --git a/vendor/react/stream/src/WritableResourceStream.php b/vendor/react/stream/src/WritableResourceStream.php
new file mode 100755
index 0000000..57c09b2
--- /dev/null
+++ b/vendor/react/stream/src/WritableResourceStream.php
@@ -0,0 +1,171 @@
+stream = $stream;
+ $this->loop = $loop;
+ $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit;
+ $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ $this->data .= $data;
+
+ if (!$this->listening && $this->data !== '') {
+ $this->listening = true;
+
+ $this->loop->addWriteStream($this->stream, array($this, 'handleWrite'));
+ }
+
+ return !isset($this->data[$this->softLimit - 1]);
+ }
+
+ public function end($data = null)
+ {
+ if (null !== $data) {
+ $this->write($data);
+ }
+
+ $this->writable = false;
+
+ // close immediately if buffer is already empty
+ // otherwise wait for buffer to flush first
+ if ($this->data === '') {
+ $this->close();
+ }
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ if ($this->listening) {
+ $this->listening = false;
+ $this->loop->removeWriteStream($this->stream);
+ }
+
+ $this->closed = true;
+ $this->writable = false;
+ $this->data = '';
+
+ $this->emit('close');
+ $this->removeAllListeners();
+
+ if (\is_resource($this->stream)) {
+ \fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleWrite()
+ {
+ $error = null;
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = array(
+ 'message' => $errstr,
+ 'number' => $errno,
+ 'file' => $errfile,
+ 'line' => $errline
+ );
+ });
+
+ if ($this->writeChunkSize === -1) {
+ $sent = \fwrite($this->stream, $this->data);
+ } else {
+ $sent = \fwrite($this->stream, $this->data, $this->writeChunkSize);
+ }
+
+ \restore_error_handler();
+
+ // Only report errors if *nothing* could be sent.
+ // Any hard (permanent) error will fail to send any data at all.
+ // Sending excessive amounts of data will only flush *some* data and then
+ // report a temporary error (EAGAIN) which we do not raise here in order
+ // to keep the stream open for further tries to write.
+ // Should this turn out to be a permanent error later, it will eventually
+ // send *nothing* and we can detect this.
+ if ($sent === 0 || $sent === false) {
+ if ($error !== null) {
+ $error = new \ErrorException(
+ $error['message'],
+ 0,
+ $error['number'],
+ $error['file'],
+ $error['line']
+ );
+ }
+
+ $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . ($error !== null ? $error->getMessage() : 'Unknown error'), 0, $error)));
+ $this->close();
+
+ return;
+ }
+
+ $exceeded = isset($this->data[$this->softLimit - 1]);
+ $this->data = (string) \substr($this->data, $sent);
+
+ // buffer has been above limit and is now below limit
+ if ($exceeded && !isset($this->data[$this->softLimit - 1])) {
+ $this->emit('drain');
+ }
+
+ // buffer is now completely empty => stop trying to write
+ if ($this->data === '') {
+ // stop waiting for resource to be writable
+ if ($this->listening) {
+ $this->loop->removeWriteStream($this->stream);
+ $this->listening = false;
+ }
+
+ // buffer is end()ing and now completely empty => close buffer
+ if (!$this->writable) {
+ $this->close();
+ }
+ }
+ }
+}
diff --git a/vendor/react/stream/src/WritableStreamInterface.php b/vendor/react/stream/src/WritableStreamInterface.php
new file mode 100755
index 0000000..3bc932e
--- /dev/null
+++ b/vendor/react/stream/src/WritableStreamInterface.php
@@ -0,0 +1,347 @@
+on('drain', function () use ($stream) {
+ * echo 'Stream is now ready to accept more data';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once every time the buffer became full
+ * previously and is now ready to accept more data.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if the buffer never became full in the first place.
+ * This event SHOULD NOT be emitted if the buffer has not become full
+ * previously.
+ *
+ * This event is mostly used internally, see also `write()` for more details.
+ *
+ * pipe event:
+ * The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+ * into this stream.
+ * The event receives a single `ReadableStreamInterface` argument for the
+ * source stream.
+ *
+ * ```php
+ * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ * echo 'Now receiving piped data';
+ *
+ * // explicitly close target if source emits an error
+ * $source->on('error', function () use ($stream) {
+ * $stream->close();
+ * });
+ * });
+ *
+ * $source->pipe($stream);
+ * ```
+ *
+ * This event MUST be emitted once for each readable stream that is
+ * successfully piped into this destination stream.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if no stream is ever piped into this stream.
+ * This event MUST NOT be emitted if either the source is not readable
+ * (closed already) or this destination is not writable (closed already).
+ *
+ * This event is mostly used internally, see also `pipe()` for more details.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to write to this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error.
+ * It SHOULD NOT be emitted after a previous `error` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-writable mode, see
+ * also `close()` and `isWritable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-writable mode,
+ * see also `isWritable()`.
+ *
+ * This event SHOULD be emitted whenever the stream closes, irrespective of
+ * whether this happens implicitly due to an unrecoverable error or
+ * explicitly when either side closes the stream.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after flushing the buffer from
+ * the `end()` method, after receiving a *successful* `end` event or after
+ * a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ * @see DuplexStreamInterface
+ */
+interface WritableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a writable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts writing
+ * any data or if it is ended or closed already.
+ * Writing any data to a non-writable stream is a NO-OP:
+ *
+ * ```php
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('end'); // NO-OP
+ * $stream->end('end'); // NO-OP
+ * ```
+ *
+ * A successfully opened stream always MUST start in writable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-writable mode.
+ * This can happen any time, explicitly through `end()` or `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-writable mode, it MUST NOT transition
+ * back to writable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `isReadable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Write some data into the stream.
+ *
+ * A successful write MUST be confirmed with a boolean `true`, which means
+ * that either the data was written (flushed) immediately or is buffered and
+ * scheduled for a future write. Note that this interface gives you no
+ * control over explicitly flushing the buffered data, as finding the
+ * appropriate time for this is beyond the scope of this interface and left
+ * up to the implementation of this interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+ * an `error` event and MAY `close()` the stream if it can not recover from
+ * this error.
+ *
+ * If the internal buffer is full after adding `$data`, then `write()`
+ * SHOULD return `false`, indicating that the caller should stop sending
+ * data until the buffer drains.
+ * The stream SHOULD send a `drain` event once the buffer is ready to accept
+ * more data.
+ *
+ * Similarly, if the the stream is not writable (already in a closed state)
+ * it MUST NOT process the given `$data` and SHOULD return `false`,
+ * indicating that the caller should stop sending data.
+ *
+ * The given `$data` argument MAY be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will only accept the raw (binary) payload data that is transferred over
+ * the wire as chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * @param mixed|string $data
+ * @return bool
+ */
+ public function write($data);
+
+ /**
+ * Successfully ends the stream (after optionally sending some final data).
+ *
+ * This method can be used to successfully end the stream, i.e. close
+ * the stream after sending out all data that is currently buffered.
+ *
+ * ```php
+ * $stream->write('hello');
+ * $stream->write('world');
+ * $stream->end();
+ * ```
+ *
+ * If there's no data currently buffered and nothing to be flushed, then
+ * this method MAY `close()` the stream immediately.
+ *
+ * If there's still data in the buffer that needs to be flushed first, then
+ * this method SHOULD try to write out this data and only then `close()`
+ * the stream.
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ *
+ * Note that this interface gives you no control over explicitly flushing
+ * the buffered data, as finding the appropriate time for this is beyond the
+ * scope of this interface and left up to the implementation of this
+ * interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * You can optionally pass some final data that is written to the stream
+ * before ending the stream. If a non-`null` value is given as `$data`, then
+ * this method will behave just like calling `write($data)` before ending
+ * with no data.
+ *
+ * ```php
+ * // shorter version
+ * $stream->end('bye');
+ *
+ * // same as longer version
+ * $stream->write('bye');
+ * $stream->end();
+ * ```
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->end();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+ * also end its readable side, unless the stream supports half-open mode.
+ * In other words, after calling this method, these streams SHOULD switch
+ * into non-writable AND non-readable mode, see also `isReadable()`.
+ * This implies that in this case, the stream SHOULD NOT emit any `data`
+ * or `end` events anymore.
+ * Streams MAY choose to use the `pause()` method logic for this, but
+ * special care may have to be taken to ensure a following call to the
+ * `resume()` method SHOULD NOT continue emitting readable events.
+ *
+ * Note that this method should not be confused with the `close()` method.
+ *
+ * @param mixed|string|null $data
+ * @return void
+ */
+ public function end($data = null);
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to forcefully close the stream, i.e. close
+ * the stream without waiting for any buffered data to be flushed.
+ * If there's still data in the buffer, this data SHOULD be discarded.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * Note that this method should not be confused with the `end()` method.
+ * Unlike the `end()` method, this method does not take care of any existing
+ * buffers and simply discards any buffer contents.
+ * Likewise, this method may also be called after calling `end()` on a
+ * stream in order to stop waiting for the stream to flush its final data.
+ *
+ * ```php
+ * $stream->end();
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->close();
+ * });
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ *
+ * @return void
+ * @see ReadableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/vendor/react/stream/tests/CallableStub.php b/vendor/react/stream/tests/CallableStub.php
new file mode 100755
index 0000000..31cc834
--- /dev/null
+++ b/vendor/react/stream/tests/CallableStub.php
@@ -0,0 +1,10 @@
+getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(false);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldCloseWritableIfNotReadable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardWritableCallsToWritableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->write('foo');
+ $composite->isWritable();
+ }
+
+ /** @test */
+ public function itShouldForwardReadableCallsToReadableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->exactly(2))
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+ $readable
+ ->expects($this->once())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->isReadable();
+ $composite->pause();
+ $composite->resume();
+ }
+
+ /** @test */
+ public function itShouldNotForwardResumeIfStreamIsNotWritable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->never())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturnOnConsecutiveCalls(true, false);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->resume();
+ }
+
+ /** @test */
+ public function endShouldDelegateToWritableWithData()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->end('foo');
+ }
+
+ /** @test */
+ public function closeShouldCloseBothStreams()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseOnlyOnce()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $readable->close();
+ $writable->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseAndRemoveAllListeners()
+ {
+ $in = new ThroughStream();
+
+ $composite = new CompositeStream($in, $in);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($composite->isReadable());
+ $this->assertTrue($composite->isWritable());
+ $this->assertCount(1, $composite->listeners('close'));
+
+ $composite->close();
+
+ $this->assertFalse($composite->isReadable());
+ $this->assertFalse($composite->isWritable());
+ $this->assertCount(0, $composite->listeners('close'));
+ }
+
+ /** @test */
+ public function itShouldReceiveForwardedEvents()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('data', $this->expectCallableOnce());
+ $composite->on('drain', $this->expectCallableOnce());
+
+ $readable->emit('data', array('foo'));
+ $writable->emit('drain');
+ }
+
+ /** @test */
+ public function itShouldHandlePipingCorrectly()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $input = new ThroughStream();
+ $input->pipe($composite);
+ $input->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function itShouldForwardPipeCallsToReadableStream()
+ {
+ $readable = new ThroughStream();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite->pipe($output);
+ $readable->emit('data', array('foo'));
+ }
+}
diff --git a/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php b/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
new file mode 100755
index 0000000..7135e15
--- /dev/null
+++ b/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
@@ -0,0 +1,390 @@
+markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $bufferSize = 4096;
+ $streamA = new DuplexResourceStream($sockA, $loop, $bufferSize);
+ $streamB = new DuplexResourceStream($sockB, $loop, $bufferSize);
+
+ $testString = str_repeat("*", $bufferSize + 1);
+
+ $buffer = "";
+ $streamB->on('data', function ($data) use (&$buffer) {
+ $buffer .= $data;
+ });
+
+ $streamA->write($testString);
+
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($testString, $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testWriteLargeChunk($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // limit seems to be 192 KiB
+ $size = 256 * 1024;
+
+ // sending side sends and expects clean close with no errors
+ $streamA->end(str_repeat('*', $size));
+ $streamA->on('close', $this->expectCallableOnce());
+ $streamA->on('error', $this->expectCallableNever());
+
+ // receiving side counts bytes and expects clean close with no errors
+ $received = 0;
+ $streamB->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+ $streamB->on('close', $this->expectCallableOnce());
+ $streamB->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($size, $received);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->end();
+
+ // streamB should not emit any data
+ $streamB->on('data', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($client, $loop);
+ $streamB = new DuplexResourceStream($peer, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($peer, $loop);
+ $streamB = new DuplexResourceStream($client, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo test', 'r'), $loop);
+ $stream->on('data', $this->expectCallableOnceWith("test\n"));
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals("a\n" . "b\n" . "c\n", $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsLongChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop);
+
+ $bytes = 0;
+ $stream->on('data', function ($chunk) use (&$bytes) {
+ $bytes += strlen($chunk);
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals(12345 * 1234, $bytes);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('true', 'r'), $loop);
+ $stream->on('data', $this->expectCallableNever());
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ * @dataProvider loopProvider
+ */
+ public function testEmptyReadShouldntFcloseStream($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $stream = stream_socket_accept($server);
+
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return '';
+ }, STREAM_FILTER_READ);
+
+ $loop = $loopFactory();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('end', $this->expectCallableNever());
+
+ fwrite($client, "foobar\n");
+
+ $conn->handleData($stream);
+
+ fclose($stream);
+ fclose($client);
+ fclose($server);
+ }
+
+ private function loopTick(LoopInterface $loop)
+ {
+ $loop->addTimer(0, function () use ($loop) {
+ $loop->stop();
+ });
+ $loop->run();
+ }
+}
diff --git a/vendor/react/stream/tests/DuplexResourceStreamTest.php b/vendor/react/stream/tests/DuplexResourceStreamTest.php
new file mode 100755
index 0000000..3212ae8
--- /dev/null
+++ b/vendor/react/stream/tests/DuplexResourceStreamTest.php
@@ -0,0 +1,495 @@
+createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new DuplexResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream('breakme', $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException RunTimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorAcceptsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ }
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+ $conn->on('end', $this->expectCallableNever());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testEndShouldEndBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->once())->method('end')->with('foo');
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ $conn->end('foo');
+ }
+
+
+ public function testEndAfterCloseIsNoOp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->never())->method('end');
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->end();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::write
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+
+ rewind($stream);
+ $this->assertSame("foo\n", fgets($stream));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ * @covers React\Stream\DuplexResourceStream::isReadable
+ * @covers React\Stream\DuplexResourceStream::isWritable
+ */
+ public function testEnd()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end();
+
+ $this->assertFalse(is_resource($stream));
+ $this->assertFalse($conn->isReadable());
+ $this->assertFalse($conn->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ */
+ public function testEndRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end('bye');
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ public function testEndedStreamsShouldNotWrite()
+ {
+ $file = tempnam(sys_get_temp_dir(), 'reactphptest_');
+ $stream = fopen($file, 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+ $conn->end();
+
+ $res = $conn->write("bar\n");
+ $stream = fopen($file, 'r');
+
+ $this->assertSame("foo\n", fgets($stream));
+ $this->assertFalse($res);
+
+ unlink($file);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ public function testBufferEventsShouldBubbleUp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+
+ $conn->on('drain', $this->expectCallableOnce());
+ $conn->on('error', $this->expectCallableOnce());
+
+ $buffer->emit('drain');
+ $buffer->emit('error', array(new \RuntimeException('Whoops')));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop
+ ->expects($this->once())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) {
+ call_user_func($listener, $stream);
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/vendor/react/stream/tests/EnforceBlockingWrapper.php b/vendor/react/stream/tests/EnforceBlockingWrapper.php
new file mode 100755
index 0000000..39c0487
--- /dev/null
+++ b/vendor/react/stream/tests/EnforceBlockingWrapper.php
@@ -0,0 +1,35 @@
+on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockPlain()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tcp://httpbin.org:80');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadKilobyteSecure()
+ {
+ $size = 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockSecureRequiresSmallerChunkSize()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream(
+ $stream,
+ $loop,
+ null,
+ new WritableResourceStream($stream, $loop, null, 8192)
+ );
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0)
+ {
+ $stream->on('close', function () use ($loop) {
+ $loop->stop();
+ });
+
+ $that = $this;
+ $loop->addTimer($timeout, function () use ($loop, $that) {
+ $loop->stop();
+ $that->fail('Timed out while waiting for stream to close');
+ });
+
+ $loop->run();
+ }
+}
diff --git a/vendor/react/stream/tests/ReadableResourceStreamTest.php b/vendor/react/stream/tests/ReadableResourceStreamTest.php
new file mode 100755
index 0000000..7566f92
--- /dev/null
+++ b/vendor/react/stream/tests/ReadableResourceStreamTest.php
@@ -0,0 +1,391 @@
+createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new ReadableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(false, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testCloseTwiceShouldEmitCloseEventOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testEmptyReadShouldntFcloseStream()
+ {
+ list($stream, $_) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('end', $this->expectCallableNever());
+
+ $conn->handleData();
+
+ fclose($stream);
+ fclose($_);
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/vendor/react/stream/tests/Stub/ReadableStreamStub.php b/vendor/react/stream/tests/Stub/ReadableStreamStub.php
new file mode 100755
index 0000000..6984f24
--- /dev/null
+++ b/vendor/react/stream/tests/Stub/ReadableStreamStub.php
@@ -0,0 +1,61 @@
+emit('data', array($data));
+ }
+
+ // trigger error event
+ public function error($error)
+ {
+ $this->emit('error', array($error));
+ }
+
+ // trigger end event
+ public function end()
+ {
+ $this->emit('end', array());
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ $this->paused = false;
+ }
+
+ public function close()
+ {
+ $this->readable = false;
+
+ $this->emit('close');
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+}
diff --git a/vendor/react/stream/tests/TestCase.php b/vendor/react/stream/tests/TestCase.php
new file mode 100755
index 0000000..c8fc1db
--- /dev/null
+++ b/vendor/react/stream/tests/TestCase.php
@@ -0,0 +1,54 @@
+createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $callback = $this->createCallableMock();
+ $callback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $callback;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock();
+ }
+}
diff --git a/vendor/react/stream/tests/ThroughStreamTest.php b/vendor/react/stream/tests/ThroughStreamTest.php
new file mode 100755
index 0000000..a98badf
--- /dev/null
+++ b/vendor/react/stream/tests/ThroughStreamTest.php
@@ -0,0 +1,267 @@
+write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToIt()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruFunction()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruCallback()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsException()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->write('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $ret = $through->write('foo');
+
+ $this->assertFalse($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->write('foo');
+
+ $through->on('drain', $this->expectCallableOnce());
+ $through->resume();
+ }
+
+ /** @test */
+ public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause()
+ {
+ $through = new ThroughStream();
+ $through->on('drain', $this->expectCallableNever());
+ $through->pause();
+ $through->resume();
+ $ret = $through->write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function pipingStuffIntoItShouldWork()
+ {
+ $readable = new ThroughStream();
+
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+
+ $readable->pipe($through);
+ $readable->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function endShouldEmitEndAndClose()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->end();
+ }
+
+ /** @test */
+ public function endShouldCloseTheStream()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endShouldWriteDataBeforeClosing()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endTwiceShouldOnlyEmitOnce()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnce('first'));
+ $through->end('first');
+ $through->end('ignored');
+ }
+
+ /** @test */
+ public function writeAfterEndShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataWillCloseStreamShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', array($through, 'close'));
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToPausedShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToResumedShouldReturnTrue()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->resume();
+
+ $this->assertTrue($through->write('foo'));
+ }
+
+ /** @test */
+ public function itShouldBeReadableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isReadable());
+ }
+
+ /** @test */
+ public function itShouldBeWritableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isWritable());
+ }
+
+ /** @test */
+ public function closeShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function doubleCloseShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function pipeShouldPipeCorrectly()
+ {
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $through = new ThroughStream();
+ $through->pipe($output);
+ $through->write('foo');
+ }
+}
diff --git a/vendor/react/stream/tests/UtilTest.php b/vendor/react/stream/tests/UtilTest.php
new file mode 100755
index 0000000..3d113ab
--- /dev/null
+++ b/vendor/react/stream/tests/UtilTest.php
@@ -0,0 +1,273 @@
+getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $ret = Util::pipe($readable, $writable);
+
+ $this->assertSame($writable, $ret);
+ }
+
+ public function testPipeNonReadableSourceShouldDoNothing()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->never())
+ ->method('isWritable');
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeIntoNonWritableDestinationShouldPauseSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(false);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeClosingDestPausesSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = new ThroughStream();
+
+ Util::pipe($readable, $writable);
+
+ $writable->close();
+ }
+
+ public function testPipeWithEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+
+ $readable->end();
+ }
+
+ public function testPipeWithoutEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable, array('end' => false));
+
+ $readable->end();
+ }
+
+ public function testPipeWithTooSlowWritableShouldPauseReadable()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('some data')
+ ->will($this->returnValue(false));
+
+ $readable->pipe($writable);
+
+ $this->assertFalse($readable->paused);
+ $readable->write('some data');
+ $this->assertTrue($readable->paused);
+ }
+
+ public function testPipeWithTooSlowWritableShouldResumeOnDrain()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $onDrain = null;
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->any())
+ ->method('on')
+ ->will($this->returnCallback(function ($name, $callback) use (&$onDrain) {
+ if ($name === 'drain') {
+ $onDrain = $callback;
+ }
+ }));
+
+ $readable->pipe($writable);
+ $readable->pause();
+
+ $this->assertTrue($readable->paused);
+ $this->assertNotNull($onDrain);
+ $onDrain();
+ $this->assertFalse($readable->paused);
+ }
+
+ public function testPipeWithWritableResourceStream()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $readable->pipe($buffer);
+
+ $readable->write('hello, I am some ');
+ $readable->write('random data');
+
+ $buffer->handleWrite();
+ rewind($stream);
+ $this->assertSame('hello, I am some random data', stream_get_contents($stream));
+ }
+
+ public function testPipeSetsUpListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+
+ Util::pipe($source, $dest);
+
+ $this->assertCount(1, $source->listeners('data'));
+ $this->assertCount(1, $source->listeners('end'));
+ $this->assertCount(1, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingSourceRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $source->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingDestRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $dest->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeDuplexIntoSelfEndsOnEnd()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable->expects($this->any())->method('isReadable')->willReturn(true);
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(true);
+ $duplex = new CompositeStream($readable, $writable);
+
+ Util::pipe($duplex, $duplex);
+
+ $writable->expects($this->once())->method('end');
+
+ $duplex->emit('end');
+ }
+
+ /** @test */
+ public function forwardEventsShouldSetupForwards()
+ {
+ $source = new ThroughStream();
+ $target = new ThroughStream();
+
+ Util::forwardEvents($source, $target, array('data'));
+ $target->on('data', $this->expectCallableOnce());
+ $target->on('foo', $this->expectCallableNever());
+
+ $source->emit('data', array('hello'));
+ $source->emit('foo', array('bar'));
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+
+ private function notEqualTo($value)
+ {
+ return new \PHPUnit_Framework_Constraint_Not($value);
+ }
+}
diff --git a/vendor/react/stream/tests/WritableStreamResourceTest.php b/vendor/react/stream/tests/WritableStreamResourceTest.php
new file mode 100755
index 0000000..05bce9c
--- /dev/null
+++ b/vendor/react/stream/tests/WritableStreamResourceTest.php
@@ -0,0 +1,534 @@
+createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'w+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsIfNotAValidStreamResource()
+ {
+ $stream = null;
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStream()
+ {
+ $stream = fopen('php://temp', 'r');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'reANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->write("foobar\n");
+ rewind($stream);
+ $this->assertSame("foobar\n", fread($stream, 1024));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteWithDataDoesAddResourceToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream));
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("foobar\n");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmptyWriteDoesNotAddToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->never())->method('addWriteStream');
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("");
+ $buffer->write(null);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+ $loop->preventWrites = true;
+
+ $buffer = new WritableResourceStream($stream, $loop, 4);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $this->assertTrue($buffer->write("foo"));
+ $loop->preventWrites = false;
+ $this->assertFalse($buffer->write("bar\n"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 3);
+
+ $this->assertFalse($buffer->write("foo"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteDetectsWhenOtherSideIsClosed()
+ {
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($a, $loop, 4);
+ $buffer->on('error', $this->expectCallableOnce());
+
+ fclose($b);
+
+ $buffer->write("foo");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmitsDrainAfterWriteWhichExceedsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteInDrain()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->once('drain', function () use ($buffer) {
+ $buffer->write("bar\n");
+ $buffer->handleWrite();
+ });
+
+ $this->assertFalse($buffer->write("foo\n"));
+ $buffer->handleWrite();
+
+ fseek($stream, 0);
+ $this->assertSame("foo\nbar\n", stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', function () use ($buffer) {
+ $buffer->close();
+ });
+
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ $buffer->handleWrite();
+ $this->assertSame('final words', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ rewind($stream);
+ $this->assertSame('', stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::isWritable
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->close();
+ $this->assertFalse($buffer->isWritable());
+
+ $this->assertEquals(array(), $buffer->listeners('close'));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingAfterWriteRemovesStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer->write('foo');
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->never())->method('removeWriteStream');
+
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testDoubleCloseWillEmitOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->close();
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $buffer->close();
+
+ $buffer->write('foo');
+
+ $buffer->handleWrite();
+ $this->assertSame('', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testErrorWhenStreamResourceIsInvalid()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', function ($message) use (&$error) {
+ $error = $message;
+ });
+
+ // invalidate stream resource
+ fclose($stream);
+
+ $buffer->write('Attempting to write to bad stream');
+
+ $this->assertInstanceOf('Exception', $error);
+
+ // the error messages differ between PHP versions, let's just check substrings
+ $this->assertContains('Unable to write to stream: ', $error->getMessage());
+ $this->assertContains(' not a valid stream resource', $error->getMessage(), '', true);
+ }
+
+ public function testWritingToClosedStream()
+ {
+ if ('Darwin' === PHP_OS) {
+ $this->markTestSkipped('OS X issue with shutting down pair for writing');
+ }
+
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+ $loop = $this->createLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($a, $loop);
+ $buffer->on('error', function($message) use (&$error) {
+ $error = $message;
+ });
+
+ $buffer->write('foo');
+ $buffer->handleWrite();
+ stream_socket_shutdown($b, STREAM_SHUT_RD);
+ stream_socket_shutdown($a, STREAM_SHUT_RD);
+ $buffer->write('bar');
+ $buffer->handleWrite();
+
+ $this->assertInstanceOf('Exception', $error);
+ $this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage());
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop->preventWrites = false;
+ $loop
+ ->expects($this->any())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) use ($loop) {
+ if (!$loop->preventWrites) {
+ call_user_func($listener, $stream);
+ }
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/vendor/ringcentral/psr7/.gitignore b/vendor/ringcentral/psr7/.gitignore
new file mode 100755
index 0000000..83ec41e
--- /dev/null
+++ b/vendor/ringcentral/psr7/.gitignore
@@ -0,0 +1,11 @@
+phpunit.xml
+composer.phar
+composer.lock
+composer-test.lock
+vendor/
+build/artifacts/
+artifacts/
+docs/_build
+docs/*.pyc
+.idea
+.DS_STORE
diff --git a/vendor/ringcentral/psr7/.travis.yml b/vendor/ringcentral/psr7/.travis.yml
new file mode 100755
index 0000000..08f3721
--- /dev/null
+++ b/vendor/ringcentral/psr7/.travis.yml
@@ -0,0 +1,21 @@
+language: php
+
+sudo: false
+
+install:
+ - travis_retry composer install --no-interaction --prefer-source
+
+script: make test
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ - php: 5.4
+ - php: 5.5
+ - php: 5.6
+ - php: 7.0
+ - php: hhvm
+ allow_failures:
+ - php: hhvm
+ fast_finish: true
diff --git a/vendor/ringcentral/psr7/CHANGELOG.md b/vendor/ringcentral/psr7/CHANGELOG.md
new file mode 100755
index 0000000..642dc9a
--- /dev/null
+++ b/vendor/ringcentral/psr7/CHANGELOG.md
@@ -0,0 +1,28 @@
+# CHANGELOG
+
+## 1.2.0 - 2015-08-15
+
+* Body as `"0"` is now properly added to a response.
+* Now allowing forward seeking in CachingStream.
+* Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+* functions.php is now conditionally required.
+* user-info is no longer dropped when resolving URIs.
+
+## 1.1.0 - 2015-06-24
+
+* URIs can now be relative.
+* `multipart/form-data` headers are now overridden case-insensitively.
+* URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+* A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
diff --git a/vendor/ringcentral/psr7/Dockerfile b/vendor/ringcentral/psr7/Dockerfile
new file mode 100755
index 0000000..846e8cf
--- /dev/null
+++ b/vendor/ringcentral/psr7/Dockerfile
@@ -0,0 +1,5 @@
+FROM greensheep/dockerfiles-php-5.3
+RUN apt-get update -y
+RUN apt-get install -y curl
+RUN curl -sS https://getcomposer.org/installer | php
+RUN mv composer.phar /usr/local/bin/composer
\ No newline at end of file
diff --git a/vendor/ringcentral/psr7/LICENSE b/vendor/ringcentral/psr7/LICENSE
new file mode 100755
index 0000000..581d95f
--- /dev/null
+++ b/vendor/ringcentral/psr7/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/ringcentral/psr7/Makefile b/vendor/ringcentral/psr7/Makefile
new file mode 100755
index 0000000..73a5c5b
--- /dev/null
+++ b/vendor/ringcentral/psr7/Makefile
@@ -0,0 +1,21 @@
+all: clean test
+
+test:
+ vendor/bin/phpunit $(TEST)
+
+coverage:
+ vendor/bin/phpunit --coverage-html=artifacts/coverage $(TEST)
+
+view-coverage:
+ open artifacts/coverage/index.html
+
+clean:
+ rm -rf artifacts/*
+
+.PHONY: docker-login
+docker-login:
+ docker run -t -i -v $(shell pwd):/opt/psr7 ringcentral-psr7 /bin/bash
+
+.PHONY: docker-build
+docker-build:
+ docker build -t ringcentral-psr7 .
\ No newline at end of file
diff --git a/vendor/ringcentral/psr7/README.md b/vendor/ringcentral/psr7/README.md
new file mode 100755
index 0000000..b4a6061
--- /dev/null
+++ b/vendor/ringcentral/psr7/README.md
@@ -0,0 +1,587 @@
+# PSR-7 Message Implementation
+
+This repository contains a partial [PSR-7](http://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing. Currently missing
+ServerRequestInterface and UploadedFileInterface; a pull request for these features is welcome.
+
+
+# Stream implementation
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## AppendStream
+
+`RingCentral\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use RingCentral\Psr7;
+
+$a = Psr7\stream_for('abc, ');
+$b = Psr7\stream_for('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\stream_for(' Above all listen to me').
+
+echo $composed(); // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`RingCentral\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use RingCentral\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`RingCentral\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use RingCentral\Psr7;
+
+// Create an empty stream
+$stream = Psr7\stream_for();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$stream->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`RingCentral\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing to
+to create a concrete class for a simple extension point.
+
+```php
+
+use RingCentral\Psr7;
+
+$stream = Psr7\stream_for('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`RingCentral\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
+
+This stream decorator skips the first 10 bytes of the given stream to remove
+the gzip header, converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`RingCentral\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use RingCentral\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`RingCentral\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`RingCentral\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`RingCentral\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`RingCentral\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`RingCentral\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use RingCentral\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ call_user_func($this->callback);
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use RingCentral\Psr7;
+
+$original = Psr7\stream_for('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `RingCentral\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `RingCentral\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use RingCentral\Psr7\StreamWrapper;
+
+$stream = RingCentral\Psr7\stream_for('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Function API
+
+There are various functions available under the `RingCentral\Psr7` namespace.
+
+
+## `function str`
+
+`function str(MessageInterface $message)`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new RingCentral\Psr7\Request('GET', 'http://example.com');
+echo RingCentral\Psr7\str($request);
+```
+
+
+## `function uri_for`
+
+`function uri_for($uri)`
+
+This function accepts a string or `Psr\Http\Message\UriInterface` and returns a
+UriInterface for the given value. If the value is already a `UriInterface`, it
+is returned as-is.
+
+```php
+$uri = RingCentral\Psr7\uri_for('http://example.com');
+assert($uri === RingCentral\Psr7\uri_for($uri));
+```
+
+
+## `function stream_for`
+
+`function stream_for($resource = '', array $options = [])`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+* - metadata: Array of custom metadata.
+* - size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = RingCentral\Psr7\stream_for('foo');
+$stream = RingCentral\Psr7\stream_for(fopen('/path/to/file', 'r'));
+
+$generator function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = RingCentral\Psr7\stream_for($generator(100));
+```
+
+
+## `function parse_header`
+
+`function parse_header($header)`
+
+Parse an array of header values containing ";" separated data into an array of
+associative arrays representing the header key value pair data of the header.
+When a parameter does not contain a value, but just contains a key, this
+function will inject a key with a '' string value.
+
+
+## `function normalize_header`
+
+`function normalize_header($header)`
+
+Converts an array of header values that may contain comma separated headers
+into an array of headers with no comma separated values.
+
+
+## `function modify_request`
+
+`function modify_request(RequestInterface $request, array $changes)`
+
+Clone and modify a request with the given changes. This method is useful for
+reducing the number of clones needed to mutate a message.
+
+The changes can be one of:
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `function rewind_body`
+
+`function rewind_body(MessageInterface $message)`
+
+Attempts to rewind a message body and throws an exception on failure. The body
+of the message will only be rewound if a call to `tell()` returns a value other
+than `0`.
+
+
+## `function try_fopen`
+
+`function try_fopen($filename, $mode)`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an error
+handler that checks for errors and throws an exception instead.
+
+
+## `function copy_to_string`
+
+`function copy_to_string(StreamInterface $stream, $maxLen = -1)`
+
+Copy the contents of a stream into a string until the given number of bytes
+have been read.
+
+
+## `function copy_to_stream`
+
+`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)`
+
+Copy the contents of a stream into another stream until the given number of
+bytes have been read.
+
+
+## `function hash`
+
+`function hash(StreamInterface $stream, $algo, $rawOutput = false)`
+
+Calculate a hash of a Stream. This method reads the entire stream to calculate
+a rolling hash (based on PHP's hash_init functions).
+
+
+## `function readline`
+
+`function readline(StreamInterface $stream, $maxLength = null)`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `function parse_request`
+
+`function parse_request($message)`
+
+Parses a request message string into a request object.
+
+
+## `function parse_server_request`
+
+`function parse_server_request($message, array $serverParams = array())`
+
+Parses a request message string into a server-side request object.
+
+
+## `function parse_response`
+
+`function parse_response($message)`
+
+Parses a response message string into a response object.
+
+
+## `function parse_query`
+
+`function parse_query($str, $urlEncoding = true)`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key value pair
+will become an array. This function does not parse nested PHP style arrays into
+an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
+`['foo[a]' => '1', 'foo[b]' => '2']`).
+
+
+## `function build_query`
+
+`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of parseQuery() to build a query string.
+This function does not modify the provided keys when an array is encountered
+(like http_build_query would).
+
+
+## `function mimetype_from_filename`
+
+`function mimetype_from_filename($filename)`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `function mimetype_from_extension`
+
+`function mimetype_from_extension($extension)`
+
+Maps a file extensions to a mimetype.
+
+
+# Static URI methods
+
+The `RingCentral\Psr7\Uri` class has several static methods to manipulate URIs.
+
+
+## `RingCentral\Psr7\Uri::removeDotSegments`
+
+`public static function removeDotSegments($path) -> UriInterface`
+
+Removes dot segments from a path and returns the new path.
+
+See http://tools.ietf.org/html/rfc3986#section-5.2.4
+
+
+## `RingCentral\Psr7\Uri::resolve`
+
+`public static function resolve(UriInterface $base, $rel) -> UriInterface`
+
+Resolve a base URI with a relative URI and return a new URI.
+
+See http://tools.ietf.org/html/rfc3986#section-5
+
+
+## `RingCentral\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
+
+Create a new URI with a specific query string value.
+
+Any existing query string values that exactly match the provided key are
+removed and replaced with the given key value pair.
+
+Note: this function will convert "=" to "%3D" and "&" to "%26".
+
+
+## `RingCentral\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key, $value) -> UriInterface`
+
+Create a new URI with a specific query string value removed.
+
+Any existing query string values that exactly match the provided key are
+removed.
+
+Note: this function will convert "=" to "%3D" and "&" to "%26".
+
+
+## `RingCentral\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts) -> UriInterface`
+
+Create a `RingCentral\Psr7\Uri` object from a hash of `parse_url` parts.
+
+
+# Not Implemented
+
+A few aspects of PSR-7 are not implemented in this project. A pull request for
+any of these features is welcome:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
diff --git a/vendor/ringcentral/psr7/composer.json b/vendor/ringcentral/psr7/composer.json
new file mode 100755
index 0000000..4955053
--- /dev/null
+++ b/vendor/ringcentral/psr7/composer.json
@@ -0,0 +1,35 @@
+{
+ "name": "ringcentral/psr7",
+ "type": "library",
+ "description": "PSR-7 message implementation",
+ "keywords": ["message", "stream", "http", "uri"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.3",
+ "psr/http-message": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "RingCentral\\Psr7\\": "src/"
+ },
+ "files": ["src/functions_include.php"]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ }
+}
diff --git a/vendor/ringcentral/psr7/phpunit.xml.dist b/vendor/ringcentral/psr7/phpunit.xml.dist
new file mode 100755
index 0000000..500cd53
--- /dev/null
+++ b/vendor/ringcentral/psr7/phpunit.xml.dist
@@ -0,0 +1,17 @@
+
+
+
+
+ tests
+
+
+
+
+ src
+
+ src/
+
+
+
+
diff --git a/vendor/ringcentral/psr7/src/AppendStream.php b/vendor/ringcentral/psr7/src/AppendStream.php
new file mode 100755
index 0000000..8b8df6f
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/AppendStream.php
@@ -0,0 +1,233 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream)
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ *
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->pos = $this->current = 0;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = array();
+ }
+
+ /**
+ * Detaches each attached stream
+ *
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $this->close();
+ $this->detached = true;
+ }
+
+ public function tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ *
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof()
+ {
+ return !$this->streams ||
+ ($this->current >= count($this->streams) - 1 &&
+ $this->streams[$this->current]->eof());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ *
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ . $i . ' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ *
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ $this->current++;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ // Using a loose comparison here to match on '', false, and null
+ if ($result == null) {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key ? null : array();
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/BufferStream.php b/vendor/ringcentral/psr7/src/BufferStream.php
new file mode 100755
index 0000000..a1e236d
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/BufferStream.php
@@ -0,0 +1,137 @@
+hwm = $hwm;
+ }
+
+ public function __toString()
+ {
+ return $this->getContents();
+ }
+
+ public function getContents()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+ }
+
+ public function getSize()
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return true;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof()
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell()
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length)
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string)
+ {
+ $this->buffer .= $string;
+
+ // TODO: What should happen here?
+ if (strlen($this->buffer) >= $this->hwm) {
+ return false;
+ }
+
+ return strlen($string);
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key == 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : array();
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/CachingStream.php b/vendor/ringcentral/psr7/src/CachingStream.php
new file mode 100755
index 0000000..ce3aca8
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/CachingStream.php
@@ -0,0 +1,135 @@
+remoteStream = $stream;
+ parent::__construct($target ?: new Stream(fopen('php://temp', 'r+')));
+ }
+
+ public function getSize()
+ {
+ return max($this->stream->getSize(), $this->remoteStream->getSize());
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence == SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence == SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence == SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ // Because 0 is the first byte, we seek to size - 1.
+ $byte = $size - 1 - $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // If the seek byte is greater the number of read bytes, then read
+ // the difference of bytes to cache the bytes and inherently seek.
+ $this->read($diff);
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string)
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ $this->remoteStream->close() && $this->stream->close();
+ }
+
+ private function cacheEntireStream()
+ {
+ $target = new FnStream(array('write' => 'strlen'));
+ copy_to_stream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/DroppingStream.php b/vendor/ringcentral/psr7/src/DroppingStream.php
new file mode 100755
index 0000000..3a34d38
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/DroppingStream.php
@@ -0,0 +1,41 @@
+maxLength = $maxLength;
+ }
+
+ public function write($string)
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/FnStream.php b/vendor/ringcentral/psr7/src/FnStream.php
new file mode 100755
index 0000000..f78dc8b
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/FnStream.php
@@ -0,0 +1,163 @@
+methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_' . $name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ * @throws \BadMethodCallException
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ . '() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ call_user_func($this->_fn_close);
+ }
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
+ $methods[$diff] = array($stream, $diff);
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString()
+ {
+ return call_user_func($this->_fn___toString);
+ }
+
+ public function close()
+ {
+ return call_user_func($this->_fn_close);
+ }
+
+ public function detach()
+ {
+ return call_user_func($this->_fn_detach);
+ }
+
+ public function getSize()
+ {
+ return call_user_func($this->_fn_getSize);
+ }
+
+ public function tell()
+ {
+ return call_user_func($this->_fn_tell);
+ }
+
+ public function eof()
+ {
+ return call_user_func($this->_fn_eof);
+ }
+
+ public function isSeekable()
+ {
+ return call_user_func($this->_fn_isSeekable);
+ }
+
+ public function rewind()
+ {
+ call_user_func($this->_fn_rewind);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ call_user_func($this->_fn_seek, $offset, $whence);
+ }
+
+ public function isWritable()
+ {
+ return call_user_func($this->_fn_isWritable);
+ }
+
+ public function write($string)
+ {
+ return call_user_func($this->_fn_write, $string);
+ }
+
+ public function isReadable()
+ {
+ return call_user_func($this->_fn_isReadable);
+ }
+
+ public function read($length)
+ {
+ return call_user_func($this->_fn_read, $length);
+ }
+
+ public function getContents()
+ {
+ return call_user_func($this->_fn_getContents);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return call_user_func($this->_fn_getMetadata, $key);
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/InflateStream.php b/vendor/ringcentral/psr7/src/InflateStream.php
new file mode 100755
index 0000000..c718002
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/InflateStream.php
@@ -0,0 +1,27 @@
+filename = $filename;
+ $this->mode = $mode;
+ parent::__construct();
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream()
+ {
+ return stream_for(try_fopen($this->filename, $this->mode));
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/LimitStream.php b/vendor/ringcentral/psr7/src/LimitStream.php
new file mode 100755
index 0000000..57eeca9
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/LimitStream.php
@@ -0,0 +1,154 @@
+setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof()
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit == -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit == -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset % with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset($offset)
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length)
+ {
+ if ($this->limit == -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/MessageTrait.php b/vendor/ringcentral/psr7/src/MessageTrait.php
new file mode 100755
index 0000000..9330bcb
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/MessageTrait.php
@@ -0,0 +1,167 @@
+protocol;
+ }
+
+ public function withProtocolVersion($version)
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+ return $new;
+ }
+
+ public function getHeaders()
+ {
+ return $this->headerLines;
+ }
+
+ public function hasHeader($header)
+ {
+ return isset($this->headers[strtolower($header)]);
+ }
+
+ public function getHeader($header)
+ {
+ $name = strtolower($header);
+ return isset($this->headers[$name]) ? $this->headers[$name] : array();
+ }
+
+ public function getHeaderLine($header)
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value)
+ {
+ $new = clone $this;
+ $header = trim($header);
+ $name = strtolower($header);
+
+ if (!is_array($value)) {
+ $new->headers[$name] = array(trim($value));
+ } else {
+ $new->headers[$name] = $value;
+ foreach ($new->headers[$name] as &$v) {
+ $v = trim($v);
+ }
+ }
+
+ // Remove the header lines.
+ foreach (array_keys($new->headerLines) as $key) {
+ if (strtolower($key) === $name) {
+ unset($new->headerLines[$key]);
+ }
+ }
+
+ // Add the header line.
+ $new->headerLines[$header] = $new->headers[$name];
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value)
+ {
+ if (!$this->hasHeader($header)) {
+ return $this->withHeader($header, $value);
+ }
+
+ $header = trim($header);
+ $name = strtolower($header);
+
+ $value = (array) $value;
+ foreach ($value as &$v) {
+ $v = trim($v);
+ }
+
+ $new = clone $this;
+ $new->headers[$name] = array_merge($new->headers[$name], $value);
+ $new->headerLines[$header] = array_merge($new->headerLines[$header], $value);
+
+ return $new;
+ }
+
+ public function withoutHeader($header)
+ {
+ if (!$this->hasHeader($header)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $name = strtolower($header);
+ unset($new->headers[$name]);
+
+ foreach (array_keys($new->headerLines) as $key) {
+ if (strtolower($key) === $name) {
+ unset($new->headerLines[$key]);
+ }
+ }
+
+ return $new;
+ }
+
+ public function getBody()
+ {
+ if (!$this->stream) {
+ $this->stream = stream_for('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body)
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+ return $new;
+ }
+
+ protected function setHeaders(array $headers)
+ {
+ $this->headerLines = $this->headers = array();
+ foreach ($headers as $header => $value) {
+ $header = trim($header);
+ $name = strtolower($header);
+ if (!is_array($value)) {
+ $value = trim($value);
+ $this->headers[$name][] = $value;
+ $this->headerLines[$header][] = $value;
+ } else {
+ foreach ($value as $v) {
+ $v = trim($v);
+ $this->headers[$name][] = $v;
+ $this->headerLines[$header][] = $v;
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/MultipartStream.php b/vendor/ringcentral/psr7/src/MultipartStream.php
new file mode 100755
index 0000000..8c5e5bc
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/MultipartStream.php
@@ -0,0 +1,152 @@
+boundary = $boundary ?: uniqid();
+ parent::__construct($this->createStream($elements));
+ }
+
+ /**
+ * Get the boundary
+ *
+ * @return string
+ */
+ public function getBoundary()
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ */
+ private function getHeaders(array $headers)
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements)
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(stream_for("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element)
+ {
+ foreach (array('contents', 'name') as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = stream_for($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if (substr($uri, 0, 6) !== 'php://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ list($body, $headers) = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ isset($element['filename']) ? $element['filename'] : null,
+ isset($element['headers']) ? $element['headers'] : array()
+ );
+
+ $stream->addStream(stream_for($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(stream_for("\r\n"));
+ }
+
+ /**
+ * @return array
+ */
+ private function createElement($name, $stream, $filename, array $headers)
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = $this->getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = $filename
+ ? sprintf('form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename))
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = $this->getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = $this->getHeader($headers, 'content-type');
+ if (!$type && $filename) {
+ if ($type = mimetype_from_filename($filename)) {
+ $headers['Content-Type'] = $type;
+ }
+ }
+
+ return array($stream, $headers);
+ }
+
+ private function getHeader(array $headers, $key)
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower($k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/NoSeekStream.php b/vendor/ringcentral/psr7/src/NoSeekStream.php
new file mode 100755
index 0000000..328fdda
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/NoSeekStream.php
@@ -0,0 +1,21 @@
+source = $source;
+ $this->size = isset($options['size']) ? $options['size'] : null;
+ $this->metadata = isset($options['metadata']) ? $options['metadata'] : array();
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString()
+ {
+ try {
+ return copy_to_string($this);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function close()
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = false;
+ $this->source = null;
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ return $this->tellPos;
+ }
+
+ public function eof()
+ {
+ return !$this->source;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents()
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
+ }
+
+ private function pump($length)
+ {
+ if ($this->source) {
+ do {
+ $data = call_user_func($this->source, $length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/Request.php b/vendor/ringcentral/psr7/src/Request.php
new file mode 100755
index 0000000..bb0f2fc
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/Request.php
@@ -0,0 +1,146 @@
+method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $protocolVersion;
+
+ $host = $uri->getHost();
+ if ($host && !$this->hasHeader('Host')) {
+ $this->updateHostFromUri($host);
+ }
+
+ if ($body) {
+ $this->stream = stream_for($body);
+ }
+ }
+
+ public function getRequestTarget()
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target == null) {
+ $target = '/';
+ }
+ if ($this->uri->getQuery()) {
+ $target .= '?' . $this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget)
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+ return $new;
+ }
+
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method)
+ {
+ $new = clone $this;
+ $new->method = strtoupper($method);
+ return $new;
+ }
+
+ public function getUri()
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false)
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost) {
+ if ($host = $uri->getHost()) {
+ $new->updateHostFromUri($host);
+ }
+ }
+
+ return $new;
+ }
+
+ public function withHeader($header, $value)
+ {
+ /** @var Request $newInstance */
+ $newInstance = parent::withHeader($header, $value);
+ return $newInstance;
+ }
+
+ private function updateHostFromUri($host)
+ {
+ // Ensure Host is the first header.
+ // See: http://tools.ietf.org/html/rfc7230#section-5.4
+ if ($port = $this->uri->getPort()) {
+ $host .= ':' . $port;
+ }
+
+ $this->headerLines = array('Host' => array($host)) + $this->headerLines;
+ $this->headers = array('host' => array($host)) + $this->headers;
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/Response.php b/vendor/ringcentral/psr7/src/Response.php
new file mode 100755
index 0000000..a6d9451
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/Response.php
@@ -0,0 +1,129 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 511 => 'Network Authentication Required',
+ );
+
+ /** @var null|string */
+ private $reasonPhrase = '';
+
+ /** @var int */
+ private $statusCode = 200;
+
+ /**
+ * @param int $status Status code for the response, if any.
+ * @param array $headers Headers for the response, if any.
+ * @param mixed $body Stream body.
+ * @param string $version Protocol version.
+ * @param string $reason Reason phrase (a default will be used if possible).
+ */
+ public function __construct(
+ $status = 200,
+ array $headers = array(),
+ $body = null,
+ $version = '1.1',
+ $reason = null
+ ) {
+ $this->statusCode = (int) $status;
+
+ if ($body !== null) {
+ $this->stream = stream_for($body);
+ }
+
+ $this->setHeaders($headers);
+ if (!$reason && isset(self::$phrases[$this->statusCode])) {
+ $this->reasonPhrase = self::$phrases[$status];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase()
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = '')
+ {
+ $new = clone $this;
+ $new->statusCode = (int) $code;
+ if (!$reasonPhrase && isset(self::$phrases[$new->statusCode])) {
+ $reasonPhrase = self::$phrases[$new->statusCode];
+ }
+ $new->reasonPhrase = $reasonPhrase;
+ return $new;
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/ServerRequest.php b/vendor/ringcentral/psr7/src/ServerRequest.php
new file mode 100755
index 0000000..8408a09
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/ServerRequest.php
@@ -0,0 +1,122 @@
+serverParams = $serverParams;
+ }
+
+ public function getServerParams()
+ {
+ return $this->serverParams;
+ }
+
+ public function getCookieParams()
+ {
+ return $this->cookies;
+ }
+
+ public function withCookieParams(array $cookies)
+ {
+ $new = clone $this;
+ $new->cookies = $cookies;
+ return $new;
+ }
+
+ public function getQueryParams()
+ {
+ return $this->queryParams;
+ }
+
+ public function withQueryParams(array $query)
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+ return $new;
+ }
+
+ public function getUploadedFiles()
+ {
+ return $this->fileParams;
+ }
+
+ public function withUploadedFiles(array $uploadedFiles)
+ {
+ $new = clone $this;
+ $new->fileParams = $uploadedFiles;
+ return $new;
+ }
+
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ public function withParsedBody($data)
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+ return $new;
+ }
+
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ public function getAttribute($name, $default = null)
+ {
+ if (!array_key_exists($name, $this->attributes)) {
+ return $default;
+ }
+ return $this->attributes[$name];
+ }
+
+ public function withAttribute($name, $value)
+ {
+ $new = clone $this;
+ $new->attributes[$name] = $value;
+ return $new;
+ }
+
+ public function withoutAttribute($name)
+ {
+ $new = clone $this;
+ unset($new->attributes[$name]);
+ return $new;
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/Stream.php b/vendor/ringcentral/psr7/src/Stream.php
new file mode 100755
index 0000000..0a0157c
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/Stream.php
@@ -0,0 +1,245 @@
+ array(
+ 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
+ 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
+ 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a+' => true
+ ),
+ 'write' => array(
+ 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
+ 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
+ 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
+ )
+ );
+
+ /**
+ * This constructor accepts an associative array of options.
+ *
+ * - size: (int) If a read stream would otherwise have an indeterminate
+ * size, but the size is known due to foreknownledge, then you can
+ * provide that size, in bytes.
+ * - metadata: (array) Any additional metadata to return when the metadata
+ * of the stream is accessed.
+ *
+ * @param resource $stream Stream resource to wrap.
+ * @param array $options Associative array of options.
+ *
+ * @throws \InvalidArgumentException if the stream is not a stream resource
+ */
+ public function __construct($stream, $options = array())
+ {
+ if (!is_resource($stream)) {
+ throw new \InvalidArgumentException('Stream must be a resource');
+ }
+
+ if (isset($options['size'])) {
+ $this->size = $options['size'];
+ }
+
+ $this->customMetadata = isset($options['metadata'])
+ ? $options['metadata']
+ : array();
+
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
+ $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ throw new \RuntimeException('The stream is detached');
+ }
+
+ throw new \BadMethodCallException('No value for ' . $name);
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return (string) stream_get_contents($this->stream);
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ $contents = stream_get_contents($this->stream);
+
+ if ($contents === false) {
+ throw new \RuntimeException('Unable to read stream contents');
+ }
+
+ return $contents;
+ }
+
+ public function close()
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function eof()
+ {
+ return !$this->stream || feof($this->stream);
+ }
+
+ public function tell()
+ {
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ } elseif (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ . $offset . ' with whence ' . var_export($whence, true));
+ }
+ }
+
+ public function read($length)
+ {
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+
+ return fread($this->stream, $length);
+ }
+
+ public function write($string)
+ {
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : array();
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/StreamDecoratorTrait.php b/vendor/ringcentral/psr7/src/StreamDecoratorTrait.php
new file mode 100755
index 0000000..e22c674
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,139 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @param string $name Name of the property (allows "stream" only).
+ *
+ * @return StreamInterface
+ */
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ $this->stream = $this->createStream();
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString()
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+ return $this->getContents();
+ } catch (\Exception $e) {
+ // Really, PHP? https://bugs.php.net/bug.php?id=53648
+ trigger_error('StreamDecorator::__toString exception: '
+ . (string) $e, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ return copy_to_string($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ $result = call_user_func_array(array($this->stream, $method), $args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close()
+ {
+ $this->stream->close();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize()
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable()
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind()
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->stream->write($string);
+ }
+
+}
diff --git a/vendor/ringcentral/psr7/src/StreamWrapper.php b/vendor/ringcentral/psr7/src/StreamWrapper.php
new file mode 100755
index 0000000..8cc07d7
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/StreamWrapper.php
@@ -0,0 +1,121 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ . 'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, null, stream_context_create(array(
+ 'guzzle' => array('stream' => $stream)
+ )));
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register()
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read($count)
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write($data)
+ {
+ return (int) $this->stream->write($data);
+ }
+
+ public function stream_tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ public function stream_stat()
+ {
+ static $modeMap = array(
+ 'r' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188
+ );
+
+ return array(
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ );
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/Uri.php b/vendor/ringcentral/psr7/src/Uri.php
new file mode 100755
index 0000000..5323cdc
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/Uri.php
@@ -0,0 +1,601 @@
+ 80,
+ 'https' => 443,
+ );
+
+ private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
+ private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
+ private static $replaceQuery = array('=' => '%3D', '&' => '%26');
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /**
+ * @param string $uri URI to parse and wrap.
+ */
+ public function __construct($uri = '')
+ {
+ if ($uri != null) {
+ $parts = parse_url($uri);
+ if ($parts === false) {
+ throw new \InvalidArgumentException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ public function __toString()
+ {
+ return self::createUriString(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->getPath(),
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ /**
+ * Removes dot segments from a path and returns the new path.
+ *
+ * @param string $path
+ *
+ * @return string
+ * @link http://tools.ietf.org/html/rfc3986#section-5.2.4
+ */
+ public static function removeDotSegments($path)
+ {
+ static $noopPaths = array('' => true, '/' => true, '*' => true);
+ static $ignoreSegments = array('.' => true, '..' => true);
+
+ if (isset($noopPaths[$path])) {
+ return $path;
+ }
+
+ $results = array();
+ $segments = explode('/', $path);
+ foreach ($segments as $segment) {
+ if ($segment == '..') {
+ array_pop($results);
+ } elseif (!isset($ignoreSegments[$segment])) {
+ $results[] = $segment;
+ }
+ }
+
+ $newPath = implode('/', $results);
+ // Add the leading slash if necessary
+ if (substr($path, 0, 1) === '/' &&
+ substr($newPath, 0, 1) !== '/'
+ ) {
+ $newPath = '/' . $newPath;
+ }
+
+ // Add the trailing slash if necessary
+ if ($newPath != '/' && isset($ignoreSegments[end($segments)])) {
+ $newPath .= '/';
+ }
+
+ return $newPath;
+ }
+
+ /**
+ * Resolve a base URI with a relative URI and return a new URI.
+ *
+ * @param UriInterface $base Base URI
+ * @param string $rel Relative URI
+ *
+ * @return UriInterface
+ */
+ public static function resolve(UriInterface $base, $rel)
+ {
+ if ($rel === null || $rel === '') {
+ return $base;
+ }
+
+ if (!($rel instanceof UriInterface)) {
+ $rel = new self($rel);
+ }
+
+ // Return the relative uri as-is if it has a scheme.
+ if ($rel->getScheme()) {
+ return $rel->withPath(static::removeDotSegments($rel->getPath()));
+ }
+
+ $relParts = array(
+ 'scheme' => $rel->getScheme(),
+ 'authority' => $rel->getAuthority(),
+ 'path' => $rel->getPath(),
+ 'query' => $rel->getQuery(),
+ 'fragment' => $rel->getFragment()
+ );
+
+ $parts = array(
+ 'scheme' => $base->getScheme(),
+ 'authority' => $base->getAuthority(),
+ 'path' => $base->getPath(),
+ 'query' => $base->getQuery(),
+ 'fragment' => $base->getFragment()
+ );
+
+ if (!empty($relParts['authority'])) {
+ $parts['authority'] = $relParts['authority'];
+ $parts['path'] = self::removeDotSegments($relParts['path']);
+ $parts['query'] = $relParts['query'];
+ $parts['fragment'] = $relParts['fragment'];
+ } elseif (!empty($relParts['path'])) {
+ if (substr($relParts['path'], 0, 1) == '/') {
+ $parts['path'] = self::removeDotSegments($relParts['path']);
+ $parts['query'] = $relParts['query'];
+ $parts['fragment'] = $relParts['fragment'];
+ } else {
+ if (!empty($parts['authority']) && empty($parts['path'])) {
+ $mergedPath = '/';
+ } else {
+ $mergedPath = substr($parts['path'], 0, strrpos($parts['path'], '/') + 1);
+ }
+ $parts['path'] = self::removeDotSegments($mergedPath . $relParts['path']);
+ $parts['query'] = $relParts['query'];
+ $parts['fragment'] = $relParts['fragment'];
+ }
+ } elseif (!empty($relParts['query'])) {
+ $parts['query'] = $relParts['query'];
+ } elseif ($relParts['fragment'] != null) {
+ $parts['fragment'] = $relParts['fragment'];
+ }
+
+ return new self(static::createUriString(
+ $parts['scheme'],
+ $parts['authority'],
+ $parts['path'],
+ $parts['query'],
+ $parts['fragment']
+ ));
+ }
+
+ /**
+ * Create a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * Note: this function will convert "=" to "%3D" and "&" to "%26".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key value pair to remove.
+ *
+ * @return UriInterface
+ */
+ public static function withoutQueryValue(UriInterface $uri, $key)
+ {
+ $current = $uri->getQuery();
+ if (!$current) {
+ return $uri;
+ }
+
+ $result = array();
+ foreach (explode('&', $current) as $part) {
+ $subParts = explode('=', $part);
+ if ($subParts[0] !== $key) {
+ $result[] = $part;
+ };
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Create a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * Note: this function will convert "=" to "%3D" and "&" to "%26".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string $value Value to set.
+ *
+ * @return UriInterface
+ */
+ public static function withQueryValue(UriInterface $uri, $key, $value)
+ {
+ $current = $uri->getQuery();
+ $key = strtr($key, self::$replaceQuery);
+
+ if (!$current) {
+ $result = array();
+ } else {
+ $result = array();
+ foreach (explode('&', $current) as $part) {
+ $subParts = explode('=', $part);
+ if ($subParts[0] !== $key) {
+ $result[] = $part;
+ };
+ }
+ }
+
+ if ($value !== null) {
+ $result[] = $key . '=' . strtr($value, self::$replaceQuery);
+ } else {
+ $result[] = $key;
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Create a URI from a hash of parse_url parts.
+ *
+ * @param array $parts
+ *
+ * @return self
+ */
+ public static function fromParts(array $parts)
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ return $uri;
+ }
+
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority()
+ {
+ if (empty($this->host)) {
+ return '';
+ }
+
+ $authority = $this->host;
+ if (!empty($this->userInfo)) {
+ $authority = $this->userInfo . '@' . $authority;
+ }
+
+ if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) {
+ $authority .= ':' . $this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo()
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ public function getPath()
+ {
+ return $this->path == null ? '' : $this->path;
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ public function getFragment()
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme)
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->port = $new->filterPort($new->scheme, $new->host, $new->port);
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null)
+ {
+ $info = $user;
+ if ($password) {
+ $info .= ':' . $password;
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ return $new;
+ }
+
+ public function withHost($host)
+ {
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ return $new;
+ }
+
+ public function withPort($port)
+ {
+ $port = $this->filterPort($this->scheme, $this->host, $port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ return $new;
+ }
+
+ public function withPath($path)
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException(
+ 'Invalid path provided; must be a string'
+ );
+ }
+
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ return $new;
+ }
+
+ public function withQuery($query)
+ {
+ if (!is_string($query) && !method_exists($query, '__toString')) {
+ throw new \InvalidArgumentException(
+ 'Query string must be a string'
+ );
+ }
+
+ $query = (string) $query;
+ if (substr($query, 0, 1) === '?') {
+ $query = substr($query, 1);
+ }
+
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+ return $new;
+ }
+
+ public function withFragment($fragment)
+ {
+ if (substr($fragment, 0, 1) === '#') {
+ $fragment = substr($fragment, 1);
+ }
+
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+ return $new;
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts)
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
+ $this->host = isset($parts['host']) ? $parts['host'] : '';
+ $this->port = !empty($parts['port'])
+ ? $this->filterPort($this->scheme, $this->host, $parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':' . $parts['pass'];
+ }
+ }
+
+ /**
+ * Create a URI string from its various parts
+ *
+ * @param string $scheme
+ * @param string $authority
+ * @param string $path
+ * @param string $query
+ * @param string $fragment
+ * @return string
+ */
+ private static function createUriString($scheme, $authority, $path, $query, $fragment)
+ {
+ $uri = '';
+
+ if (!empty($scheme)) {
+ $uri .= $scheme . '://';
+ }
+
+ if (!empty($authority)) {
+ $uri .= $authority;
+ }
+
+ if ($path != null) {
+ // Add a leading slash if necessary.
+ if ($uri && substr($path, 0, 1) !== '/') {
+ $uri .= '/';
+ }
+ $uri .= $path;
+ }
+
+ if ($query != null) {
+ $uri .= '?' . $query;
+ }
+
+ if ($fragment != null) {
+ $uri .= '#' . $fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Is a given port non-standard for the current scheme?
+ *
+ * @param string $scheme
+ * @param string $host
+ * @param int $port
+ * @return bool
+ */
+ private static function isNonStandardPort($scheme, $host, $port)
+ {
+ if (!$scheme && $port) {
+ return true;
+ }
+
+ if (!$host || !$port) {
+ return false;
+ }
+
+ return !isset(static::$schemes[$scheme]) || $port !== static::$schemes[$scheme];
+ }
+
+ /**
+ * @param string $scheme
+ *
+ * @return string
+ */
+ private function filterScheme($scheme)
+ {
+ $scheme = strtolower($scheme);
+ $scheme = rtrim($scheme, ':/');
+
+ return $scheme;
+ }
+
+ /**
+ * @param string $scheme
+ * @param string $host
+ * @param int $port
+ *
+ * @return int|null
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($scheme, $host, $port)
+ {
+ if (null !== $port) {
+ $port = (int) $port;
+ if (1 > $port || 0xffff < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
+ );
+ }
+ }
+
+ return $this->isNonStandardPort($scheme, $host, $port) ? $port : null;
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param $path
+ *
+ * @return string
+ */
+ private function filterPath($path)
+ {
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . ':@\/%]+|%(?![A-Fa-f0-9]{2}))/',
+ array($this, 'rawurlencodeMatchZero'),
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param $str
+ *
+ * @return string
+ */
+ private function filterQueryAndFragment($str)
+ {
+ return preg_replace_callback(
+ '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
+ array($this, 'rawurlencodeMatchZero'),
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match)
+ {
+ return rawurlencode($match[0]);
+ }
+}
diff --git a/vendor/ringcentral/psr7/src/functions.php b/vendor/ringcentral/psr7/src/functions.php
new file mode 100755
index 0000000..3fc89bd
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/functions.php
@@ -0,0 +1,832 @@
+getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+}
+
+/**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
+ * returns a UriInterface for the given value. If the value is already a
+ * `UriInterface`, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @return UriInterface
+ * @throws \InvalidArgumentException
+ */
+function uri_for($uri)
+{
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ } elseif (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+}
+
+/**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * @param resource|string|StreamInterface $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return Stream
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+function stream_for($resource = '', array $options = array())
+{
+ switch (gettype($resource)) {
+ case 'string':
+ $stream = fopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new Stream($stream, $options);
+ case 'resource':
+ return new Stream($resource, $options);
+ case 'object':
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return stream_for((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(fopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
+}
+
+/**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair
+ * data of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ *
+ * @return array Returns the parsed header values.
+ */
+function parse_header($header)
+{
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = array();
+
+ foreach (normalize_header($header) as $val) {
+ $part = array();
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+}
+
+/**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+function normalize_header($header)
+{
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = array();
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Clone and modify a request with the given changes.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+function modify_request(RequestInterface $request, array $changes)
+{
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = _caseless_remove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+}
+
+/**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()` returns a
+ * value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+function rewind_body(MessageInterface $message)
+{
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+}
+
+/**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ * @throws \RuntimeException if the file cannot be opened
+ */
+function try_fopen($filename, $mode)
+{
+ $ex = null;
+ $fargs = func_get_args();
+ set_error_handler(function () use ($filename, $mode, &$ex, $fargs) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open %s using mode %s: %s',
+ $filename,
+ $mode,
+ $fargs[1]
+ ));
+ });
+
+ $handle = fopen($filename, $mode);
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+}
+
+/**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ * @return string
+ * @throws \RuntimeException on error.
+ */
+function copy_to_string(StreamInterface $stream, $maxLen = -1)
+{
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+}
+
+/**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+function copy_to_stream(
+ StreamInterface $source,
+ StreamInterface $dest,
+ $maxLen = -1
+) {
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read(1048576))) {
+ break;
+ }
+ }
+ return;
+ }
+
+ $bytes = 0;
+ while (!$source->eof()) {
+ $buf = $source->read($maxLen - $bytes);
+ if (!($len = strlen($buf))) {
+ break;
+ }
+ $bytes += $len;
+ $dest->write($buf);
+ if ($bytes == $maxLen) {
+ break;
+ }
+ }
+}
+
+/**
+ * Calculate a hash of a Stream
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ * @throws \RuntimeException on error.
+ */
+function hash(
+ StreamInterface $stream,
+ $algo,
+ $rawOutput = false
+) {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+}
+
+/**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string|bool
+ */
+function readline(StreamInterface $stream, $maxLength = null)
+{
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ // Using a loose equality here to match on '' and false.
+ if (null == ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+}
+
+/**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ *
+ * @return Request
+ */
+function parse_request($message)
+{
+ $data = _parse_message($message);
+ $matches = array();
+ if (!preg_match('/^[a-zA-Z]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $subParts = isset($parts[2]) ? explode('/', $parts[2]) : array();
+ $version = isset($parts[2]) ? $subParts[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+}
+
+/**
+ * Parses a request message string into a server request object.
+ *
+ * @param string $message Request message string.
+ * @param array $serverParams Server params that will be added to the
+ * ServerRequest object
+ *
+ * @return ServerRequest
+ */
+function parse_server_request($message, array $serverParams = array())
+{
+ $request = parse_request($message);
+
+ return new ServerRequest(
+ $request->getMethod(),
+ $request->getUri(),
+ $request->getHeaders(),
+ $request->getBody(),
+ $request->getProtocolVersion(),
+ $serverParams
+ );
+}
+
+/**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ *
+ * @return Response
+ */
+function parse_response($message)
+{
+ $data = _parse_message($message);
+ if (!preg_match('/^HTTP\/.* [0-9]{3} .*/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $subParts = explode('/', $parts[0]);
+
+ return new Response(
+ $parts[1],
+ $data['headers'],
+ $data['body'],
+ $subParts[1],
+ isset($parts[2]) ? $parts[2] : null
+ );
+}
+
+/**
+ * Parse a query string into an associative array.
+ *
+ * If multiple values are found for the same key, the value of that key
+ * value pair will become an array. This function does not parse nested
+ * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
+ * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
+ *
+ * @param string $str Query string to parse
+ * @param bool|string $urlEncoding How the query string is encoded
+ *
+ * @return array
+ */
+function parse_query($str, $urlEncoding = true)
+{
+ $result = array();
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', $value));
+ };
+ } elseif ($urlEncoding == PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding == PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) { return $str; };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!isset($result[$key])) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = array($result[$key]);
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of parseQuery() to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like http_build_query would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
+ * to encode using RFC3986, or PHP_QUERY_RFC1738
+ * to encode using RFC1738.
+ * @return string
+ */
+function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
+{
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function ($str) { return $str; };
+ } elseif ($encoding == PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding == PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder($k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ if ($v !== null) {
+ $qs .= '=' . $encoder($v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ if ($vv !== null) {
+ $qs .= '=' . $encoder($vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+}
+
+/**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @param $filename
+ *
+ * @return null|string
+ */
+function mimetype_from_filename($filename)
+{
+ return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
+}
+
+/**
+ * Maps a file extensions to a mimetype.
+ *
+ * @param $extension string The file extension.
+ *
+ * @return string|null
+ * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
+ */
+function mimetype_from_extension($extension)
+{
+ static $mimetypes = array(
+ '7z' => 'application/x-7z-compressed',
+ 'aac' => 'audio/x-aac',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'asc' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'atom' => 'application/atom+xml',
+ 'avi' => 'video/x-msvideo',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip2',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'deb' => 'application/x-debian-package',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dvi' => 'application/x-dvi',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'etx' => 'text/x-setext',
+ 'flac' => 'audio/flac',
+ 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif',
+ 'gz' => 'application/gzip',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ini' => 'text/plain',
+ 'iso' => 'application/x-iso9660-image',
+ 'jar' => 'application/java-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'latex' => 'application/x-latex',
+ 'log' => 'text/plain',
+ 'm4a' => 'audio/mp4',
+ 'm4v' => 'video/mp4',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'qt' => 'video/quicktime',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'torrent' => 'application/x-bittorrent',
+ 'ttf' => 'application/x-font-ttf',
+ 'txt' => 'text/plain',
+ 'wav' => 'audio/x-wav',
+ 'webm' => 'video/webm',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'video/x-ms-wmv',
+ 'woff' => 'application/x-font-woff',
+ 'wsdl' => 'application/wsdl+xml',
+ 'xbm' => 'image/x-xbitmap',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'zip' => 'application/zip',
+ );
+
+ $extension = strtolower($extension);
+
+ return isset($mimetypes[$extension])
+ ? $mimetypes[$extension]
+ : null;
+}
+
+/**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ *
+ * @return array
+ * @internal
+ */
+function _parse_message($message)
+{
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ // Iterate over each line in the message, accounting for line endings
+ $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $result = array('start-line' => array_shift($lines), 'headers' => array(), 'body' => '');
+ array_shift($lines);
+
+ for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
+ $line = $lines[$i];
+ // If two line breaks were encountered, then this is the end of body
+ if (empty($line)) {
+ if ($i < $totalLines - 1) {
+ $result['body'] = implode('', array_slice($lines, $i + 2));
+ }
+ break;
+ }
+ if (strpos($line, ':')) {
+ $parts = explode(':', $line, 2);
+ $key = trim($parts[0]);
+ $value = isset($parts[1]) ? trim($parts[1]) : '';
+ $result['headers'][$key][] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ *
+ * @return string
+ * @internal
+ */
+function _parse_request_uri($path, array $headers)
+{
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme . '://' . $host . '/' . ltrim($path, '/');
+}
+
+/** @internal */
+function _caseless_remove($keys, array $data)
+{
+ $result = array();
+
+ foreach ($keys as &$key) {
+ $key = strtolower($key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower($k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+}
diff --git a/vendor/ringcentral/psr7/src/functions_include.php b/vendor/ringcentral/psr7/src/functions_include.php
new file mode 100755
index 0000000..252e0cf
--- /dev/null
+++ b/vendor/ringcentral/psr7/src/functions_include.php
@@ -0,0 +1,6 @@
+getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $a->addStream($s);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage The AppendStream can only seek with SEEK_SET
+ */
+ public function testValidatesSeekType()
+ {
+ $a = new AppendStream();
+ $a->seek(100, SEEK_CUR);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to seek stream 0 of the AppendStream
+ */
+ public function testTriesToRewindOnSeek()
+ {
+ $a = new AppendStream();
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable', 'rewind', 'isSeekable'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('rewind')
+ ->will($this->throwException(new \RuntimeException()));
+ $a->addStream($s);
+ $a->seek(10);
+ }
+
+ public function testSeeksToPositionByReading()
+ {
+ $a = new AppendStream(array(
+ Psr7\stream_for('foo'),
+ Psr7\stream_for('bar'),
+ Psr7\stream_for('baz'),
+ ));
+
+ $a->seek(3);
+ $this->assertEquals(3, $a->tell());
+ $this->assertEquals('bar', $a->read(3));
+
+ $a->seek(6);
+ $this->assertEquals(6, $a->tell());
+ $this->assertEquals('baz', $a->read(3));
+ }
+
+ public function testDetachesEachStream()
+ {
+ $s1 = Psr7\stream_for('foo');
+ $s2 = Psr7\stream_for('bar');
+ $a = new AppendStream(array($s1, $s2));
+ $this->assertSame('foobar', (string) $a);
+ $a->detach();
+ $this->assertSame('', (string) $a);
+ $this->assertSame(0, $a->getSize());
+ }
+
+ public function testClosesEachStream()
+ {
+ $s1 = Psr7\stream_for('foo');
+ $a = new AppendStream(array($s1));
+ $a->close();
+ $this->assertSame('', (string) $a);
+ }
+
+ /**
+ * @expectedExceptionMessage Cannot write to an AppendStream
+ * @expectedException \RuntimeException
+ */
+ public function testIsNotWritable()
+ {
+ $a = new AppendStream(array(Psr7\stream_for('foo')));
+ $this->assertFalse($a->isWritable());
+ $this->assertTrue($a->isSeekable());
+ $this->assertTrue($a->isReadable());
+ $a->write('foo');
+ }
+
+ public function testDoesNotNeedStreams()
+ {
+ $a = new AppendStream();
+ $this->assertEquals('', (string) $a);
+ }
+
+ public function testCanReadFromMultipleStreams()
+ {
+ $a = new AppendStream(array(
+ Psr7\stream_for('foo'),
+ Psr7\stream_for('bar'),
+ Psr7\stream_for('baz'),
+ ));
+ $this->assertFalse($a->eof());
+ $this->assertSame(0, $a->tell());
+ $this->assertEquals('foo', $a->read(3));
+ $this->assertEquals('bar', $a->read(3));
+ $this->assertEquals('baz', $a->read(3));
+ $this->assertSame('', $a->read(1));
+ $this->assertTrue($a->eof());
+ $this->assertSame(9, $a->tell());
+ $this->assertEquals('foobarbaz', (string) $a);
+ }
+
+ public function testCanDetermineSizeFromMultipleStreams()
+ {
+ $a = new AppendStream(array(
+ Psr7\stream_for('foo'),
+ Psr7\stream_for('bar')
+ ));
+ $this->assertEquals(6, $a->getSize());
+
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isSeekable', 'isReadable'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(null));
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $a->addStream($s);
+ $this->assertNull($a->getSize());
+ }
+
+ public function testCatchesExceptionsWhenCastingToString()
+ {
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isSeekable', 'read', 'isReadable', 'eof'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('read')
+ ->will($this->throwException(new \RuntimeException('foo')));
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $s->expects($this->any())
+ ->method('eof')
+ ->will($this->returnValue(false));
+ $a = new AppendStream(array($s));
+ $this->assertFalse($a->eof());
+ $this->assertSame('', (string) $a);
+ }
+
+ public function testCanDetach()
+ {
+ $s = new AppendStream();
+ $s->detach();
+ }
+
+ public function testReturnsEmptyMetadata()
+ {
+ $s = new AppendStream();
+ $this->assertEquals(array(), $s->getMetadata());
+ $this->assertNull($s->getMetadata('foo'));
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/BufferStreamTest.php b/vendor/ringcentral/psr7/tests/BufferStreamTest.php
new file mode 100755
index 0000000..79f907a
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/BufferStreamTest.php
@@ -0,0 +1,63 @@
+assertTrue($b->isReadable());
+ $this->assertTrue($b->isWritable());
+ $this->assertFalse($b->isSeekable());
+ $this->assertEquals(null, $b->getMetadata('foo'));
+ $this->assertEquals(10, $b->getMetadata('hwm'));
+ $this->assertEquals(array(), $b->getMetadata());
+ }
+
+ public function testRemovesReadDataFromBuffer()
+ {
+ $b = new BufferStream();
+ $this->assertEquals(3, $b->write('foo'));
+ $this->assertEquals(3, $b->getSize());
+ $this->assertFalse($b->eof());
+ $this->assertEquals('foo', $b->read(10));
+ $this->assertTrue($b->eof());
+ $this->assertEquals('', $b->read(10));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Cannot determine the position of a BufferStream
+ */
+ public function testCanCastToStringOrGetContents()
+ {
+ $b = new BufferStream();
+ $b->write('foo');
+ $b->write('baz');
+ $this->assertEquals('foo', $b->read(3));
+ $b->write('bar');
+ $this->assertEquals('bazbar', (string) $b);
+ $b->tell();
+ }
+
+ public function testDetachClearsBuffer()
+ {
+ $b = new BufferStream();
+ $b->write('foo');
+ $b->detach();
+ $this->assertTrue($b->eof());
+ $this->assertEquals(3, $b->write('abc'));
+ $this->assertEquals('abc', $b->read(10));
+ }
+
+ public function testExceedingHighwaterMarkReturnsFalseButStillBuffers()
+ {
+ $b = new BufferStream(5);
+ $this->assertEquals(3, $b->write('hi '));
+ $this->assertFalse($b->write('hello'));
+ $this->assertEquals('hi hello', (string) $b);
+ $this->assertEquals(4, $b->write('test'));
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/CachingStreamTest.php b/vendor/ringcentral/psr7/tests/CachingStreamTest.php
new file mode 100755
index 0000000..f394fc9
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/CachingStreamTest.php
@@ -0,0 +1,166 @@
+decorated = Psr7\stream_for('testing');
+ $this->body = new CachingStream($this->decorated);
+ }
+
+ public function tearDown()
+ {
+ $this->decorated->close();
+ $this->body->close();
+ }
+
+ public function testUsesRemoteSizeIfPossible()
+ {
+ $body = Psr7\stream_for('test');
+ $caching = new CachingStream($body);
+ $this->assertEquals(4, $caching->getSize());
+ }
+
+ public function testReadsUntilCachedToByte()
+ {
+ $this->body->seek(5);
+ $this->assertEquals('n', $this->body->read(1));
+ $this->body->seek(0);
+ $this->assertEquals('t', $this->body->read(1));
+ }
+
+ public function testCanSeekNearEndWithSeekEnd()
+ {
+ $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
+ $cached = new CachingStream($baseStream);
+ $cached->seek(1, SEEK_END);
+ $this->assertEquals(24, $baseStream->tell());
+ $this->assertEquals('y', $cached->read(1));
+ $this->assertEquals(26, $cached->getSize());
+ }
+
+ public function testCanSeekToEndWithSeekEnd()
+ {
+ $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
+ $cached = new CachingStream($baseStream);
+ $cached->seek(0, SEEK_END);
+ $this->assertEquals(25, $baseStream->tell());
+ $this->assertEquals('z', $cached->read(1));
+ $this->assertEquals(26, $cached->getSize());
+ }
+
+ public function testCanUseSeekEndWithUnknownSize()
+ {
+ $baseStream = Psr7\stream_for('testing');
+ $decorated = Psr7\FnStream::decorate($baseStream, array(
+ 'getSize' => function () { return null; }
+ ));
+ $cached = new CachingStream($decorated);
+ $cached->seek(1, SEEK_END);
+ $this->assertEquals('ng', $cached->read(2));
+ }
+
+ public function testRewindUsesSeek()
+ {
+ $a = Psr7\stream_for('foo');
+ $d = $this->getMockBuilder('RingCentral\Psr7\CachingStream')
+ ->setMethods(array('seek'))
+ ->setConstructorArgs(array($a))
+ ->getMock();
+ $d->expects($this->once())
+ ->method('seek')
+ ->with(0)
+ ->will($this->returnValue(true));
+ $d->seek(0);
+ }
+
+ public function testCanSeekToReadBytes()
+ {
+ $this->assertEquals('te', $this->body->read(2));
+ $this->body->seek(0);
+ $this->assertEquals('test', $this->body->read(4));
+ $this->assertEquals(4, $this->body->tell());
+ $this->body->seek(2);
+ $this->assertEquals(2, $this->body->tell());
+ $this->body->seek(2, SEEK_CUR);
+ $this->assertEquals(4, $this->body->tell());
+ $this->assertEquals('ing', $this->body->read(3));
+ }
+
+ public function testWritesToBufferStream()
+ {
+ $this->body->read(2);
+ $this->body->write('hi');
+ $this->body->seek(0);
+ $this->assertEquals('tehiing', (string) $this->body);
+ }
+
+ public function testSkipsOverwrittenBytes()
+ {
+ $decorated = Psr7\stream_for(
+ implode("\n", array_map(function ($n) {
+ return str_pad($n, 4, '0', STR_PAD_LEFT);
+ }, range(0, 25)))
+ );
+
+ $body = new CachingStream($decorated);
+
+ $this->assertEquals("0000\n", Psr7\readline($body));
+ $this->assertEquals("0001\n", Psr7\readline($body));
+ // Write over part of the body yet to be read, so skip some bytes
+ $this->assertEquals(5, $body->write("TEST\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+ // Read, which skips bytes, then reads
+ $this->assertEquals("0003\n", Psr7\readline($body));
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("0004\n", Psr7\readline($body));
+ $this->assertEquals("0005\n", Psr7\readline($body));
+
+ // Overwrite part of the cached body (so don't skip any bytes)
+ $body->seek(5);
+ $this->assertEquals(5, $body->write("ABCD\n"));
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("TEST\n", Psr7\readline($body));
+ $this->assertEquals("0003\n", Psr7\readline($body));
+ $this->assertEquals("0004\n", Psr7\readline($body));
+ $this->assertEquals("0005\n", Psr7\readline($body));
+ $this->assertEquals("0006\n", Psr7\readline($body));
+ $this->assertEquals(5, $body->write("1234\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+
+ // Seek to 0 and ensure the overwritten bit is replaced
+ $body->seek(0);
+ $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50));
+
+ // Ensure that casting it to a string does not include the bit that was overwritten
+ $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
+ }
+
+ public function testClosesBothStreams()
+ {
+ $s = fopen('php://temp', 'r');
+ $a = Psr7\stream_for($s);
+ $d = new CachingStream($a);
+ $d->close();
+ $this->assertFalse(is_resource($s));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsuresValidWhence()
+ {
+ $this->body->seek(10, -123456);
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/DroppingStreamTest.php b/vendor/ringcentral/psr7/tests/DroppingStreamTest.php
new file mode 100755
index 0000000..1ae9443
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/DroppingStreamTest.php
@@ -0,0 +1,26 @@
+assertEquals(3, $drop->write('hel'));
+ $this->assertEquals(2, $drop->write('lo'));
+ $this->assertEquals(5, $drop->getSize());
+ $this->assertEquals('hello', $drop->read(5));
+ $this->assertEquals(0, $drop->getSize());
+ $drop->write('12345678910');
+ $this->assertEquals(5, $stream->getSize());
+ $this->assertEquals(5, $drop->getSize());
+ $this->assertEquals('12345', (string) $drop);
+ $this->assertEquals(0, $drop->getSize());
+ $drop->write('hello');
+ $this->assertSame(0, $drop->write('test'));
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/FnStreamTest.php b/vendor/ringcentral/psr7/tests/FnStreamTest.php
new file mode 100755
index 0000000..def436c
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/FnStreamTest.php
@@ -0,0 +1,92 @@
+seek(1);
+ }
+
+ public function testProxiesToFunction()
+ {
+ $self = $this;
+ $s = new FnStream(array(
+ 'read' => function ($len) use ($self) {
+ $self->assertEquals(3, $len);
+ return 'foo';
+ }
+ ));
+
+ $this->assertEquals('foo', $s->read(3));
+ }
+
+ public function testCanCloseOnDestruct()
+ {
+ $called = false;
+ $s = new FnStream(array(
+ 'close' => function () use (&$called) {
+ $called = true;
+ }
+ ));
+ unset($s);
+ $this->assertTrue($called);
+ }
+
+ public function testDoesNotRequireClose()
+ {
+ $s = new FnStream(array());
+ unset($s);
+ }
+
+ public function testDecoratesStream()
+ {
+ $a = Psr7\stream_for('foo');
+ $b = FnStream::decorate($a, array());
+ $this->assertEquals(3, $b->getSize());
+ $this->assertEquals($b->isWritable(), true);
+ $this->assertEquals($b->isReadable(), true);
+ $this->assertEquals($b->isSeekable(), true);
+ $this->assertEquals($b->read(3), 'foo');
+ $this->assertEquals($b->tell(), 3);
+ $this->assertEquals($a->tell(), 3);
+ $this->assertSame('', $a->read(1));
+ $this->assertEquals($b->eof(), true);
+ $this->assertEquals($a->eof(), true);
+ $b->seek(0);
+ $this->assertEquals('foo', (string) $b);
+ $b->seek(0);
+ $this->assertEquals('foo', $b->getContents());
+ $this->assertEquals($a->getMetadata(), $b->getMetadata());
+ $b->seek(0, SEEK_END);
+ $b->write('bar');
+ $this->assertEquals('foobar', (string) $b);
+ $this->assertInternalType('resource', $b->detach());
+ $b->close();
+ }
+
+ public function testDecoratesWithCustomizations()
+ {
+ $called = false;
+ $a = Psr7\stream_for('foo');
+ $b = FnStream::decorate($a, array(
+ 'read' => function ($len) use (&$called, $a) {
+ $called = true;
+ return $a->read($len);
+ }
+ ));
+ $this->assertEquals('foo', $b->read(3));
+ $this->assertTrue($called);
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/FunctionsTest.php b/vendor/ringcentral/psr7/tests/FunctionsTest.php
new file mode 100755
index 0000000..032fc56
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/FunctionsTest.php
@@ -0,0 +1,604 @@
+assertEquals('foobaz', Psr7\copy_to_string($s));
+ $s->seek(0);
+ $this->assertEquals('foo', Psr7\copy_to_string($s, 3));
+ $this->assertEquals('baz', Psr7\copy_to_string($s, 3));
+ $this->assertEquals('', Psr7\copy_to_string($s));
+ }
+
+ public function testCopiesToStringStopsWhenReadFails()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s1 = FnStream::decorate($s1, array(
+ 'read' => function () { return ''; }
+ ));
+ $result = Psr7\copy_to_string($s1);
+ $this->assertEquals('', $result);
+ }
+
+ public function testCopiesToStream()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s2 = Psr7\stream_for('');
+ Psr7\copy_to_stream($s1, $s2);
+ $this->assertEquals('foobaz', (string) $s2);
+ $s2 = Psr7\stream_for('');
+ $s1->seek(0);
+ Psr7\copy_to_stream($s1, $s2, 3);
+ $this->assertEquals('foo', (string) $s2);
+ Psr7\copy_to_stream($s1, $s2, 3);
+ $this->assertEquals('foobaz', (string) $s2);
+ }
+
+ public function testStopsCopyToStreamWhenWriteFails()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s2 = Psr7\stream_for('');
+ $s2 = FnStream::decorate($s2, array('write' => function () { return 0; }));
+ Psr7\copy_to_stream($s1, $s2);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testStopsCopyToSteamWhenWriteFailsWithMaxLen()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s2 = Psr7\stream_for('');
+ $s2 = FnStream::decorate($s2, array('write' => function () { return 0; }));
+ Psr7\copy_to_stream($s1, $s2, 10);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testStopsCopyToSteamWhenReadFailsWithMaxLen()
+ {
+ $s1 = Psr7\stream_for('foobaz');
+ $s1 = FnStream::decorate($s1, array('read' => function () { return ''; }));
+ $s2 = Psr7\stream_for('');
+ Psr7\copy_to_stream($s1, $s2, 10);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testReadsLines()
+ {
+ $s = Psr7\stream_for("foo\nbaz\nbar");
+ $this->assertEquals("foo\n", Psr7\readline($s));
+ $this->assertEquals("baz\n", Psr7\readline($s));
+ $this->assertEquals("bar", Psr7\readline($s));
+ }
+
+ public function testReadsLinesUpToMaxLength()
+ {
+ $s = Psr7\stream_for("12345\n");
+ $this->assertEquals("123", Psr7\readline($s, 4));
+ $this->assertEquals("45\n", Psr7\readline($s));
+ }
+
+ public function testReadsLineUntilFalseReturnedFromRead()
+ {
+ $s = $this->getMockBuilder('RingCentral\Psr7\Stream')
+ ->setMethods(array('read', 'eof'))
+ ->disableOriginalConstructor()
+ ->getMock();
+ $s->expects($this->exactly(2))
+ ->method('read')
+ ->will($this->returnCallback(function () {
+ static $c = false;
+ if ($c) {
+ return false;
+ }
+ $c = true;
+ return 'h';
+ }));
+ $s->expects($this->exactly(2))
+ ->method('eof')
+ ->will($this->returnValue(false));
+ $this->assertEquals("h", Psr7\readline($s));
+ }
+
+ public function testCalculatesHash()
+ {
+ $s = Psr7\stream_for('foobazbar');
+ $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testCalculatesHashThrowsWhenSeekFails()
+ {
+ $s = new NoSeekStream(Psr7\stream_for('foobazbar'));
+ $s->read(2);
+ Psr7\hash($s, 'md5');
+ }
+
+ public function testCalculatesHashSeeksToOriginalPosition()
+ {
+ $s = Psr7\stream_for('foobazbar');
+ $s->seek(4);
+ $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
+ $this->assertEquals(4, $s->tell());
+ }
+
+ public function testOpensFilesSuccessfully()
+ {
+ $r = Psr7\try_fopen(__FILE__, 'r');
+ $this->assertInternalType('resource', $r);
+ fclose($r);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r
+ */
+ public function testThrowsExceptionNotWarning()
+ {
+ Psr7\try_fopen('/path/to/does/not/exist', 'r');
+ }
+
+ public function parseQueryProvider()
+ {
+ return array(
+ // Does not need to parse when the string is empty
+ array('', array()),
+ // Can parse mult-values items
+ array('q=a&q=b', array('q' => array('a', 'b'))),
+ // Can parse multi-valued items that use numeric indices
+ array('q[0]=a&q[1]=b', array('q[0]' => 'a', 'q[1]' => 'b')),
+ // Can parse duplicates and does not include numeric indices
+ array('q[]=a&q[]=b', array('q[]' => array('a', 'b'))),
+ // Ensures that the value of "q" is an array even though one value
+ array('q[]=a', array('q[]' => 'a')),
+ // Does not modify "." to "_" like PHP's parse_str()
+ array('q.a=a&q.b=b', array('q.a' => 'a', 'q.b' => 'b')),
+ // Can decode %20 to " "
+ array('q%20a=a%20b', array('q a' => 'a b')),
+ // Can parse funky strings with no values by assigning each to null
+ array('q&a', array('q' => null, 'a' => null)),
+ // Does not strip trailing equal signs
+ array('data=abc=', array('data' => 'abc=')),
+ // Can store duplicates without affecting other values
+ array('foo=a&foo=b&?µ=c', array('foo' => array('a', 'b'), '?µ' => 'c')),
+ // Sets value to null when no "=" is present
+ array('foo', array('foo' => null)),
+ // Preserves "0" keys.
+ array('0', array('0' => null)),
+ // Sets the value to an empty string when "=" is present
+ array('0=', array('0' => '')),
+ // Preserves falsey keys
+ array('var=0', array('var' => '0')),
+ array('a[b][c]=1&a[b][c]=2', array('a[b][c]' => array('1', '2'))),
+ array('a[b]=c&a[d]=e', array('a[b]' => 'c', 'a[d]' => 'e')),
+ // Ensure it doesn't leave things behind with repeated values
+ // Can parse mult-values items
+ array('q=a&q=b&q=c', array('q' => array('a', 'b', 'c'))),
+ );
+ }
+
+ /**
+ * @dataProvider parseQueryProvider
+ */
+ public function testParsesQueries($input, $output)
+ {
+ $result = Psr7\parse_query($input);
+ $this->assertSame($output, $result);
+ }
+
+ public function testDoesNotDecode()
+ {
+ $str = 'foo%20=bar';
+ $data = Psr7\parse_query($str, false);
+ $this->assertEquals(array('foo%20' => 'bar'), $data);
+ }
+
+ /**
+ * @dataProvider parseQueryProvider
+ */
+ public function testParsesAndBuildsQueries($input, $output)
+ {
+ $result = Psr7\parse_query($input, false);
+ $this->assertSame($input, Psr7\build_query($result, false));
+ }
+
+ public function testEncodesWithRfc1738()
+ {
+ $str = Psr7\build_query(array('foo bar' => 'baz+'), PHP_QUERY_RFC1738);
+ $this->assertEquals('foo+bar=baz%2B', $str);
+ }
+
+ public function testEncodesWithRfc3986()
+ {
+ $str = Psr7\build_query(array('foo bar' => 'baz+'), PHP_QUERY_RFC3986);
+ $this->assertEquals('foo%20bar=baz%2B', $str);
+ }
+
+ public function testDoesNotEncode()
+ {
+ $str = Psr7\build_query(array('foo bar' => 'baz+'), false);
+ $this->assertEquals('foo bar=baz+', $str);
+ }
+
+ public function testCanControlDecodingType()
+ {
+ $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC3986);
+ $this->assertEquals('foo+bar', $result['var']);
+ $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC1738);
+ $this->assertEquals('foo bar', $result['var']);
+ }
+
+ public function testParsesRequestMessages()
+ {
+ $req = "GET /abc HTTP/1.0\r\nHost: foo.com\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('GET', $request->getMethod());
+ $this->assertEquals('/abc', $request->getRequestTarget());
+ $this->assertEquals('1.0', $request->getProtocolVersion());
+ $this->assertEquals('foo.com', $request->getHeaderLine('Host'));
+ $this->assertEquals('Bar', $request->getHeaderLine('Foo'));
+ $this->assertEquals('Bam, Qux', $request->getHeaderLine('Baz'));
+ $this->assertEquals('Test', (string) $request->getBody());
+ $this->assertEquals('http://foo.com/abc', (string) $request->getUri());
+ }
+
+ public function testParsesRequestMessagesWithHttpsScheme()
+ {
+ $req = "PUT /abc?baz=bar HTTP/1.1\r\nHost: foo.com:443\r\n\r\n";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('/abc?baz=bar', $request->getRequestTarget());
+ $this->assertEquals('1.1', $request->getProtocolVersion());
+ $this->assertEquals('foo.com:443', $request->getHeaderLine('Host'));
+ $this->assertEquals('', (string) $request->getBody());
+ $this->assertEquals('https://foo.com/abc?baz=bar', (string) $request->getUri());
+ }
+
+ public function testParsesRequestMessagesWithUriWhenHostIsNotFirst()
+ {
+ $req = "PUT / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('PUT', $request->getMethod());
+ $this->assertEquals('/', $request->getRequestTarget());
+ $this->assertEquals('http://foo.com/', (string) $request->getUri());
+ }
+
+ public function testParsesRequestMessagesWithFullUri()
+ {
+ $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
+ $request = Psr7\parse_request($req);
+ $this->assertEquals('GET', $request->getMethod());
+ $this->assertEquals('https://www.google.com:443/search?q=foobar', $request->getRequestTarget());
+ $this->assertEquals('1.1', $request->getProtocolVersion());
+ $this->assertEquals('www.google.com', $request->getHeaderLine('Host'));
+ $this->assertEquals('', (string) $request->getBody());
+ $this->assertEquals('https://www.google.com/search?q=foobar', (string) $request->getUri());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesRequestMessages()
+ {
+ Psr7\parse_request("HTTP/1.1 200 OK\r\n\r\n");
+ }
+
+ public function testParsesResponseMessages()
+ {
+ $res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
+ $response = Psr7\parse_response($res);
+ $this->assertEquals(200, $response->getStatusCode());
+ $this->assertEquals('OK', $response->getReasonPhrase());
+ $this->assertEquals('1.0', $response->getProtocolVersion());
+ $this->assertEquals('Bar', $response->getHeaderLine('Foo'));
+ $this->assertEquals('Bam, Qux', $response->getHeaderLine('Baz'));
+ $this->assertEquals('Test', (string) $response->getBody());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesResponseMessages()
+ {
+ Psr7\parse_response("GET / HTTP/1.1\r\n\r\n");
+ }
+
+ public function testDetermineMimetype()
+ {
+ $this->assertNull(Psr7\mimetype_from_extension('not-a-real-extension'));
+ $this->assertEquals(
+ 'application/json',
+ Psr7\mimetype_from_extension('json')
+ );
+ $this->assertEquals(
+ 'image/jpeg',
+ Psr7\mimetype_from_filename('/tmp/images/IMG034821.JPEG')
+ );
+ }
+
+ public function testCreatesUriForValue()
+ {
+ $this->assertInstanceOf('RingCentral\Psr7\Uri', Psr7\uri_for('/foo'));
+ $this->assertInstanceOf(
+ 'RingCentral\Psr7\Uri',
+ Psr7\uri_for(new Psr7\Uri('/foo'))
+ );
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesUri()
+ {
+ Psr7\uri_for(array());
+ }
+
+ public function testKeepsPositionOfResource()
+ {
+ $h = fopen(__FILE__, 'r');
+ fseek($h, 10);
+ $stream = Psr7\stream_for($h);
+ $this->assertEquals(10, $stream->tell());
+ $stream->close();
+ }
+
+ public function testCreatesWithFactory()
+ {
+ $stream = Psr7\stream_for('foo');
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $stream);
+ $this->assertEquals('foo', $stream->getContents());
+ $stream->close();
+ }
+
+ public function testFactoryCreatesFromEmptyString()
+ {
+ $s = Psr7\stream_for();
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ }
+
+ public function testFactoryCreatesFromNull()
+ {
+ $s = Psr7\stream_for(null);
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ }
+
+ public function testFactoryCreatesFromResource()
+ {
+ $r = fopen(__FILE__, 'r');
+ $s = Psr7\stream_for($r);
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ $this->assertSame(file_get_contents(__FILE__), (string) $s);
+ }
+
+ public function testFactoryCreatesFromObjectWithToString()
+ {
+ $r = new HasToString();
+ $s = Psr7\stream_for($r);
+ $this->assertInstanceOf('RingCentral\Psr7\Stream', $s);
+ $this->assertEquals('foo', (string) $s);
+ }
+
+ public function testCreatePassesThrough()
+ {
+ $s = Psr7\stream_for('foo');
+ $this->assertSame($s, Psr7\stream_for($s));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testThrowsExceptionForUnknown()
+ {
+ Psr7\stream_for(new \stdClass());
+ }
+
+ public function testReturnsCustomMetadata()
+ {
+ $s = Psr7\stream_for('foo', array('metadata' => array('hwm' => 3)));
+ $this->assertEquals(3, $s->getMetadata('hwm'));
+ $this->assertArrayHasKey('hwm', $s->getMetadata());
+ }
+
+ public function testCanSetSize()
+ {
+ $s = Psr7\stream_for('', array('size' => 10));
+ $this->assertEquals(10, $s->getSize());
+ }
+
+ public function testCanCreateIteratorBasedStream()
+ {
+ $a = new \ArrayIterator(array('foo', 'bar', '123'));
+ $p = Psr7\stream_for($a);
+ $this->assertInstanceOf('RingCentral\Psr7\PumpStream', $p);
+ $this->assertEquals('foo', $p->read(3));
+ $this->assertFalse($p->eof());
+ $this->assertEquals('b', $p->read(1));
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals('r12', $p->read(3));
+ $this->assertFalse($p->eof());
+ $this->assertEquals('3', $p->getContents());
+ $this->assertTrue($p->eof());
+ $this->assertEquals(9, $p->tell());
+ }
+
+ public function testConvertsRequestsToStrings()
+ {
+ $request = new Psr7\Request('PUT', 'http://foo.com/hi?123', array(
+ 'Baz' => 'bar',
+ 'Qux' => ' ipsum'
+ ), 'hello', '1.0');
+ $this->assertEquals(
+ "PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
+ Psr7\str($request)
+ );
+ }
+
+ public function testConvertsResponsesToStrings()
+ {
+ $response = new Psr7\Response(200, array(
+ 'Baz' => 'bar',
+ 'Qux' => ' ipsum'
+ ), 'hello', '1.0', 'FOO');
+ $this->assertEquals(
+ "HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
+ Psr7\str($response)
+ );
+ }
+
+ public function parseParamsProvider()
+ {
+ $res1 = array(
+ array(
+ '',
+ 'rel' => 'front',
+ 'type' => 'image/jpeg',
+ ),
+ array(
+ '',
+ 'rel' => 'back',
+ 'type' => 'image/jpeg',
+ ),
+ );
+ return array(
+ array(
+ '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"',
+ $res1
+ ),
+ array(
+ '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"',
+ $res1
+ ),
+ array(
+ 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"',
+ array(
+ array('foo' => 'baz', 'bar' => '123'),
+ array('boo'),
+ array('test' => '123'),
+ array('foobar' => 'foo;bar')
+ )
+ ),
+ array(
+ '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"',
+ array(
+ array('', 'rel' => 'side', 'type' => 'image/jpeg'),
+ array('', 'rel' => 'side', 'type' => 'image/jpeg')
+ )
+ ),
+ array(
+ '',
+ array()
+ )
+ );
+ }
+ /**
+ * @dataProvider parseParamsProvider
+ */
+ public function testParseParams($header, $result)
+ {
+ $this->assertEquals($result, Psr7\parse_header($header));
+ }
+
+ public function testParsesArrayHeaders()
+ {
+ $header = array('a, b', 'c', 'd, e');
+ $this->assertEquals(array('a', 'b', 'c', 'd', 'e'), Psr7\normalize_header($header));
+ }
+
+ public function testRewindsBody()
+ {
+ $body = Psr7\stream_for('abc');
+ $res = new Psr7\Response(200, array(), $body);
+ Psr7\rewind_body($res);
+ $this->assertEquals(0, $body->tell());
+ $body->rewind(1);
+ Psr7\rewind_body($res);
+ $this->assertEquals(0, $body->tell());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testThrowsWhenBodyCannotBeRewound()
+ {
+ $body = Psr7\stream_for('abc');
+ $body->read(1);
+ $body = FnStream::decorate($body, array(
+ 'rewind' => function () { throw new \RuntimeException('a'); }
+ ));
+ $res = new Psr7\Response(200, array(), $body);
+ Psr7\rewind_body($res);
+ }
+
+ public function testCanModifyRequestWithUri()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com');
+ $r2 = Psr7\modify_request($r1, array(
+ 'uri' => new Psr7\Uri('http://www.foo.com')
+ ));
+ $this->assertEquals('http://www.foo.com', (string) $r2->getUri());
+ $this->assertEquals('www.foo.com', (string) $r2->getHeaderLine('host'));
+ }
+
+ public function testCanModifyRequestWithCaseInsensitiveHeader()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com', array('User-Agent' => 'foo'));
+ $r2 = Psr7\modify_request($r1, array('set_headers' => array('User-agent' => 'bar')));
+ $this->assertEquals('bar', $r2->getHeaderLine('User-Agent'));
+ $this->assertEquals('bar', $r2->getHeaderLine('User-agent'));
+ }
+
+ public function testReturnsAsIsWhenNoChanges()
+ {
+ $request = new Psr7\Request('GET', 'http://foo.com');
+ $this->assertSame($request, Psr7\modify_request($request, array()));
+ }
+
+ public function testReturnsUriAsIsWhenNoChanges()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com');
+ $r2 = Psr7\modify_request($r1, array('set_headers' => array('foo' => 'bar')));
+ $this->assertNotSame($r1, $r2);
+ $this->assertEquals('bar', $r2->getHeaderLine('foo'));
+ }
+
+ public function testRemovesHeadersFromMessage()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com', array('foo' => 'bar'));
+ $r2 = Psr7\modify_request($r1, array('remove_headers' => array('foo')));
+ $this->assertNotSame($r1, $r2);
+ $this->assertFalse($r2->hasHeader('foo'));
+ }
+
+ public function testAddsQueryToUri()
+ {
+ $r1 = new Psr7\Request('GET', 'http://foo.com');
+ $r2 = Psr7\modify_request($r1, array('query' => 'foo=bar'));
+ $this->assertNotSame($r1, $r2);
+ $this->assertEquals('foo=bar', $r2->getUri()->getQuery());
+ }
+
+ public function testServerRequestWithServerParams()
+ {
+ $requestString = "GET /abc HTTP/1.1\r\nHost: foo.com\r\n\r\n";
+ $request = Psr7\parse_server_request($requestString);
+
+ $this->assertEquals(array(), $request->getServerParams());
+ }
+
+ public function testServerRequestWithoutServerParams()
+ {
+ $requestString = "GET /abc HTTP/1.1\r\nHost: foo.com\r\n\r\n";
+ $serverParams = array('server_address' => '127.0.0.1', 'server_port' => 80);
+
+ $request = Psr7\parse_server_request($requestString, $serverParams);
+
+ $this->assertEquals(array('server_address' => '127.0.0.1', 'server_port' => 80), $request->getServerParams());
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/InflateStreamTest.php b/vendor/ringcentral/psr7/tests/InflateStreamTest.php
new file mode 100755
index 0000000..cd699eb
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/InflateStreamTest.php
@@ -0,0 +1,21 @@
+assertEquals('test', (string) $b);
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/LazyOpenStreamTest.php b/vendor/ringcentral/psr7/tests/LazyOpenStreamTest.php
new file mode 100755
index 0000000..ca0c18e
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/LazyOpenStreamTest.php
@@ -0,0 +1,64 @@
+fname = tempnam('/tmp', 'tfile');
+
+ if (file_exists($this->fname)) {
+ unlink($this->fname);
+ }
+ }
+
+ public function tearDown()
+ {
+ if (file_exists($this->fname)) {
+ unlink($this->fname);
+ }
+ }
+
+ public function testOpensLazily()
+ {
+ $l = new LazyOpenStream($this->fname, 'w+');
+ $l->write('foo');
+ $this->assertInternalType('array', $l->getMetadata());
+ $this->assertFileExists($this->fname);
+ $this->assertEquals('foo', file_get_contents($this->fname));
+ $this->assertEquals('foo', (string) $l);
+ }
+
+ public function testProxiesToFile()
+ {
+ file_put_contents($this->fname, 'foo');
+ $l = new LazyOpenStream($this->fname, 'r');
+ $this->assertEquals('foo', $l->read(4));
+ $this->assertTrue($l->eof());
+ $this->assertEquals(3, $l->tell());
+ $this->assertTrue($l->isReadable());
+ $this->assertTrue($l->isSeekable());
+ $this->assertFalse($l->isWritable());
+ $l->seek(1);
+ $this->assertEquals('oo', $l->getContents());
+ $this->assertEquals('foo', (string) $l);
+ $this->assertEquals(3, $l->getSize());
+ $this->assertInternalType('array', $l->getMetadata());
+ $l->close();
+ }
+
+ public function testDetachesUnderlyingStream()
+ {
+ file_put_contents($this->fname, 'foo');
+ $l = new LazyOpenStream($this->fname, 'r');
+ $r = $l->detach();
+ $this->assertInternalType('resource', $r);
+ fseek($r, 0);
+ $this->assertEquals('foo', stream_get_contents($r));
+ fclose($r);
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/LimitStreamTest.php b/vendor/ringcentral/psr7/tests/LimitStreamTest.php
new file mode 100755
index 0000000..7053300
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/LimitStreamTest.php
@@ -0,0 +1,166 @@
+decorated = Psr7\stream_for(fopen(__FILE__, 'r'));
+ $this->body = new LimitStream($this->decorated, 10, 3);
+ }
+
+ public function testReturnsSubset()
+ {
+ $body = new LimitStream(Psr7\stream_for('foo'), -1, 1);
+ $this->assertEquals('oo', (string) $body);
+ $this->assertTrue($body->eof());
+ $body->seek(0);
+ $this->assertFalse($body->eof());
+ $this->assertEquals('oo', $body->read(100));
+ $this->assertSame('', $body->read(1));
+ $this->assertTrue($body->eof());
+ }
+
+ public function testReturnsSubsetWhenCastToString()
+ {
+ $body = Psr7\stream_for('foo_baz_bar');
+ $limited = new LimitStream($body, 3, 4);
+ $this->assertEquals('baz', (string) $limited);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to seek to stream position 10 with whence 0
+ */
+ public function testEnsuresPositionCanBeekSeekedTo()
+ {
+ new LimitStream(Psr7\stream_for(''), 0, 10);
+ }
+
+ public function testReturnsSubsetOfEmptyBodyWhenCastToString()
+ {
+ $body = Psr7\stream_for('01234567891234');
+ $limited = new LimitStream($body, 0, 10);
+ $this->assertEquals('', (string) $limited);
+ }
+
+ public function testReturnsSpecificSubsetOBodyWhenCastToString()
+ {
+ $body = Psr7\stream_for('0123456789abcdef');
+ $limited = new LimitStream($body, 3, 10);
+ $this->assertEquals('abc', (string) $limited);
+ }
+
+ public function testSeeksWhenConstructed()
+ {
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ }
+
+ public function testAllowsBoundedSeek()
+ {
+ $this->body->seek(100);
+ $this->assertEquals(10, $this->body->tell());
+ $this->assertEquals(13, $this->decorated->tell());
+ $this->body->seek(0);
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ try {
+ $this->body->seek(-10);
+ $this->fail();
+ } catch (\RuntimeException $e) {}
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ $this->body->seek(5);
+ $this->assertEquals(5, $this->body->tell());
+ $this->assertEquals(8, $this->decorated->tell());
+ // Fail
+ try {
+ $this->body->seek(1000, SEEK_END);
+ $this->fail();
+ } catch (\RuntimeException $e) {}
+ }
+
+ public function testReadsOnlySubsetOfData()
+ {
+ $data = $this->body->read(100);
+ $this->assertEquals(10, strlen($data));
+ $this->assertSame('', $this->body->read(1000));
+
+ $this->body->setOffset(10);
+ $newData = $this->body->read(100);
+ $this->assertEquals(10, strlen($newData));
+ $this->assertNotSame($data, $newData);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Could not seek to stream offset 2
+ */
+ public function testThrowsWhenCurrentGreaterThanOffsetSeek()
+ {
+ $a = Psr7\stream_for('foo_bar');
+ $b = new NoSeekStream($a);
+ $c = new LimitStream($b);
+ $a->getContents();
+ $c->setOffset(2);
+ }
+
+ public function testCanGetContentsWithoutSeeking()
+ {
+ $a = Psr7\stream_for('foo_bar');
+ $b = new NoSeekStream($a);
+ $c = new LimitStream($b);
+ $this->assertEquals('foo_bar', $c->getContents());
+ }
+
+ public function testClaimsConsumedWhenReadLimitIsReached()
+ {
+ $this->assertFalse($this->body->eof());
+ $this->body->read(1000);
+ $this->assertTrue($this->body->eof());
+ }
+
+ public function testContentLengthIsBounded()
+ {
+ $this->assertEquals(10, $this->body->getSize());
+ }
+
+ public function testGetContentsIsBasedOnSubset()
+ {
+ $body = new LimitStream(Psr7\stream_for('foobazbar'), 3, 3);
+ $this->assertEquals('baz', $body->getContents());
+ }
+
+ public function testReturnsNullIfSizeCannotBeDetermined()
+ {
+ $a = new FnStream(array(
+ 'getSize' => function () { return null; },
+ 'tell' => function () { return 0; },
+ ));
+ $b = new LimitStream($a);
+ $this->assertNull($b->getSize());
+ }
+
+ public function testLengthLessOffsetWhenNoLimitSize()
+ {
+ $a = Psr7\stream_for('foo_bar');
+ $b = new LimitStream($a, -1, 4);
+ $this->assertEquals(3, $b->getSize());
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/MultipartStreamTest.php b/vendor/ringcentral/psr7/tests/MultipartStreamTest.php
new file mode 100755
index 0000000..22edea4
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/MultipartStreamTest.php
@@ -0,0 +1,214 @@
+assertNotEmpty($b->getBoundary());
+ }
+
+ public function testCanProvideBoundary()
+ {
+ $b = new MultipartStream(array(), 'foo');
+ $this->assertEquals('foo', $b->getBoundary());
+ }
+
+ public function testIsNotWritable()
+ {
+ $b = new MultipartStream();
+ $this->assertFalse($b->isWritable());
+ }
+
+ public function testCanCreateEmptyStream()
+ {
+ $b = new MultipartStream();
+ $boundary = $b->getBoundary();
+ $this->assertSame("--{$boundary}--\r\n", $b->getContents());
+ $this->assertSame(strlen($boundary) + 6, $b->getSize());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesFilesArrayElement()
+ {
+ new MultipartStream(array(array('foo' => 'bar')));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsuresFileHasName()
+ {
+ new MultipartStream(array(array('contents' => 'bar')));
+ }
+
+ public function testSerializesFields()
+ {
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => 'bar'
+ ),
+ array(
+ 'name' => 'baz',
+ 'contents' => 'bam'
+ )
+ ), 'boundary');
+ $this->assertEquals(
+ "--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\nContent-Length: 3\r\n\r\n"
+ . "bar\r\n--boundary\r\nContent-Disposition: form-data; name=\"baz\"\r\nContent-Length: 3"
+ . "\r\n\r\nbam\r\n--boundary--\r\n", (string) $b);
+ }
+
+ public function testSerializesFiles()
+ {
+ $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.txt';
+ }
+ ));
+
+ $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), array(
+ 'getMetadata' => function () {
+ return '/foo/baz.jpg';
+ }
+ ));
+
+ $f3 = Psr7\FnStream::decorate(Psr7\stream_for('bar'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.gif';
+ }
+ ));
+
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => $f1
+ ),
+ array(
+ 'name' => 'qux',
+ 'contents' => $f2
+ ),
+ array(
+ 'name' => 'qux',
+ 'contents' => $f3
+ ),
+ ), 'boundary');
+
+ $expected = <<assertEquals($expected, str_replace("\r", '', $b));
+ }
+
+ public function testSerializesFilesWithCustomHeaders()
+ {
+ $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.txt';
+ }
+ ));
+
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => $f1,
+ 'headers' => array(
+ 'x-foo' => 'bar',
+ 'content-disposition' => 'custom'
+ )
+ )
+ ), 'boundary');
+
+ $expected = <<assertEquals($expected, str_replace("\r", '', $b));
+ }
+
+ public function testSerializesFilesWithCustomHeadersAndMultipleValues()
+ {
+ $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), array(
+ 'getMetadata' => function () {
+ return '/foo/bar.txt';
+ }
+ ));
+
+ $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), array(
+ 'getMetadata' => function () {
+ return '/foo/baz.jpg';
+ }
+ ));
+
+ $b = new MultipartStream(array(
+ array(
+ 'name' => 'foo',
+ 'contents' => $f1,
+ 'headers' => array(
+ 'x-foo' => 'bar',
+ 'content-disposition' => 'custom'
+ )
+ ),
+ array(
+ 'name' => 'foo',
+ 'contents' => $f2,
+ 'headers' => array('cOntenT-Type' => 'custom'),
+ )
+ ), 'boundary');
+
+ $expected = <<assertEquals($expected, str_replace("\r", '', $b));
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/NoSeekStreamTest.php b/vendor/ringcentral/psr7/tests/NoSeekStreamTest.php
new file mode 100755
index 0000000..a831789
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/NoSeekStreamTest.php
@@ -0,0 +1,40 @@
+getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isSeekable', 'seek'))
+ ->getMockForAbstractClass();
+ $s->expects($this->never())->method('seek');
+ $s->expects($this->never())->method('isSeekable');
+ $wrapped = new NoSeekStream($s);
+ $this->assertFalse($wrapped->isSeekable());
+ $wrapped->seek(2);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Cannot write to a non-writable stream
+ */
+ public function testHandlesClose()
+ {
+ $s = Psr7\stream_for('foo');
+ $wrapped = new NoSeekStream($s);
+ $wrapped->close();
+ $wrapped->write('foo');
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/PumpStreamTest.php b/vendor/ringcentral/psr7/tests/PumpStreamTest.php
new file mode 100755
index 0000000..6b146e1
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/PumpStreamTest.php
@@ -0,0 +1,72 @@
+ array('foo' => 'bar'),
+ 'size' => 100
+ ));
+
+ $this->assertEquals('bar', $p->getMetadata('foo'));
+ $this->assertEquals(array('foo' => 'bar'), $p->getMetadata());
+ $this->assertEquals(100, $p->getSize());
+ }
+
+ public function testCanReadFromCallable()
+ {
+ $p = Psr7\stream_for(function ($size) {
+ return 'a';
+ });
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals(1, $p->tell());
+ $this->assertEquals('aaaaa', $p->read(5));
+ $this->assertEquals(6, $p->tell());
+ }
+
+ public function testStoresExcessDataInBuffer()
+ {
+ $called = array();
+ $p = Psr7\stream_for(function ($size) use (&$called) {
+ $called[] = $size;
+ return 'abcdef';
+ });
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals('b', $p->read(1));
+ $this->assertEquals('cdef', $p->read(4));
+ $this->assertEquals('abcdefabc', $p->read(9));
+ $this->assertEquals(array(1, 9, 3), $called);
+ }
+
+ public function testInifiniteStreamWrappedInLimitStream()
+ {
+ $p = Psr7\stream_for(function () { return 'a'; });
+ $s = new LimitStream($p, 5);
+ $this->assertEquals('aaaaa', (string) $s);
+ }
+
+ public function testDescribesCapabilities()
+ {
+ $p = Psr7\stream_for(function () {});
+ $this->assertTrue($p->isReadable());
+ $this->assertFalse($p->isSeekable());
+ $this->assertFalse($p->isWritable());
+ $this->assertNull($p->getSize());
+ $this->assertEquals('', $p->getContents());
+ $this->assertEquals('', (string) $p);
+ $p->close();
+ $this->assertEquals('', $p->read(10));
+ $this->assertTrue($p->eof());
+
+ try {
+ $this->assertFalse($p->write('aa'));
+ $this->fail();
+ } catch (\RuntimeException $e) {}
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/RequestTest.php b/vendor/ringcentral/psr7/tests/RequestTest.php
new file mode 100755
index 0000000..ad6f0cb
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/RequestTest.php
@@ -0,0 +1,157 @@
+assertEquals('/', (string) $r->getUri());
+ }
+
+ public function testRequestUriMayBeUri()
+ {
+ $uri = new Uri('/');
+ $r = new Request('GET', $uri);
+ $this->assertSame($uri, $r->getUri());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidateRequestUri()
+ {
+ new Request('GET', true);
+ }
+
+ public function testCanConstructWithBody()
+ {
+ $r = new Request('GET', '/', array(), 'baz');
+ $this->assertEquals('baz', (string) $r->getBody());
+ }
+
+ public function testCapitalizesMethod()
+ {
+ $r = new Request('get', '/');
+ $this->assertEquals('GET', $r->getMethod());
+ }
+
+ public function testCapitalizesWithMethod()
+ {
+ $r = new Request('GET', '/');
+ $this->assertEquals('PUT', $r->withMethod('put')->getMethod());
+ }
+
+ public function testWithUri()
+ {
+ $r1 = new Request('GET', '/');
+ $u1 = $r1->getUri();
+ $u2 = new Uri('http://www.example.com');
+ $r2 = $r1->withUri($u2);
+ $this->assertNotSame($r1, $r2);
+ $this->assertSame($u2, $r2->getUri());
+ $this->assertSame($u1, $r1->getUri());
+ }
+
+ public function testSameInstanceWhenSameUri()
+ {
+ $r1 = new Request('GET', 'http://foo.com');
+ $r2 = $r1->withUri($r1->getUri());
+ $this->assertSame($r1, $r2);
+ }
+
+ public function testWithRequestTarget()
+ {
+ $r1 = new Request('GET', '/');
+ $r2 = $r1->withRequestTarget('*');
+ $this->assertEquals('*', $r2->getRequestTarget());
+ $this->assertEquals('/', $r1->getRequestTarget());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testRequestTargetDoesNotAllowSpaces()
+ {
+ $r1 = new Request('GET', '/');
+ $r1->withRequestTarget('/foo bar');
+ }
+
+ public function testRequestTargetDefaultsToSlash()
+ {
+ $r1 = new Request('GET', '');
+ $this->assertEquals('/', $r1->getRequestTarget());
+ $r2 = new Request('GET', '*');
+ $this->assertEquals('*', $r2->getRequestTarget());
+ $r3 = new Request('GET', 'http://foo.com/bar baz/');
+ $this->assertEquals('/bar%20baz/', $r3->getRequestTarget());
+ }
+
+ public function testBuildsRequestTarget()
+ {
+ $r1 = new Request('GET', 'http://foo.com/baz?bar=bam');
+ $this->assertEquals('/baz?bar=bam', $r1->getRequestTarget());
+ }
+
+ public function testHostIsAddedFirst()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam', array('Foo' => 'Bar'));
+ $this->assertEquals(array(
+ 'Host' => array('foo.com'),
+ 'Foo' => array('Bar')
+ ), $r->getHeaders());
+ }
+
+ public function testCanGetHeaderAsCsv()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam', array(
+ 'Foo' => array('a', 'b', 'c')
+ ));
+ $this->assertEquals('a, b, c', $r->getHeaderLine('Foo'));
+ $this->assertEquals('', $r->getHeaderLine('Bar'));
+ }
+
+ public function testHostIsNotOverwrittenWhenPreservingHost()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam', array('Host' => 'a.com'));
+ $this->assertEquals(array('Host' => array('a.com')), $r->getHeaders());
+ $r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true);
+ $this->assertEquals('a.com', $r2->getHeaderLine('Host'));
+ }
+
+ public function testOverridesHostWithUri()
+ {
+ $r = new Request('GET', 'http://foo.com/baz?bar=bam');
+ $this->assertEquals(array('Host' => array('foo.com')), $r->getHeaders());
+ $r2 = $r->withUri(new Uri('http://www.baz.com/bar'));
+ $this->assertEquals('www.baz.com', $r2->getHeaderLine('Host'));
+ }
+
+ public function testAggregatesHeaders()
+ {
+ $r = new Request('GET', 'http://foo.com', array(
+ 'ZOO' => 'zoobar',
+ 'zoo' => array('foobar', 'zoobar')
+ ));
+ $this->assertEquals('zoobar, foobar, zoobar', $r->getHeaderLine('zoo'));
+ }
+
+ public function testAddsPortToHeader()
+ {
+ $r = new Request('GET', 'http://foo.com:8124/bar');
+ $this->assertEquals('foo.com:8124', $r->getHeaderLine('host'));
+ }
+
+ public function testAddsPortToHeaderAndReplacePreviousPort()
+ {
+ $r = new Request('GET', 'http://foo.com:8124/bar');
+ $r = $r->withUri(new Uri('http://foo.com:8125/bar'));
+ $this->assertEquals('foo.com:8125', $r->getHeaderLine('host'));
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/ResponseTest.php b/vendor/ringcentral/psr7/tests/ResponseTest.php
new file mode 100755
index 0000000..52b7ba1
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/ResponseTest.php
@@ -0,0 +1,154 @@
+assertSame(200, $r->getStatusCode());
+ $this->assertEquals('OK', $r->getReasonPhrase());
+ }
+
+ public function testCanGiveCustomReason()
+ {
+ $r = new Response(200, array(), null, '1.1', 'bar');
+ $this->assertEquals('bar', $r->getReasonPhrase());
+ }
+
+ public function testCanGiveCustomProtocolVersion()
+ {
+ $r = new Response(200, array(), null, '1000');
+ $this->assertEquals('1000', $r->getProtocolVersion());
+ }
+
+ public function testCanCreateNewResponseWithStatusAndNoReason()
+ {
+ $r = new Response(200);
+ $r2 = $r->withStatus(201);
+ $this->assertEquals(200, $r->getStatusCode());
+ $this->assertEquals('OK', $r->getReasonPhrase());
+ $this->assertEquals(201, $r2->getStatusCode());
+ $this->assertEquals('Created', $r2->getReasonPhrase());
+ }
+
+ public function testCanCreateNewResponseWithStatusAndReason()
+ {
+ $r = new Response(200);
+ $r2 = $r->withStatus(201, 'Foo');
+ $this->assertEquals(200, $r->getStatusCode());
+ $this->assertEquals('OK', $r->getReasonPhrase());
+ $this->assertEquals(201, $r2->getStatusCode());
+ $this->assertEquals('Foo', $r2->getReasonPhrase());
+ }
+
+ public function testCreatesResponseWithAddedHeaderArray()
+ {
+ $r = new Response();
+ $r2 = $r->withAddedHeader('foo', array('baz', 'bar'));
+ $this->assertFalse($r->hasHeader('foo'));
+ $this->assertEquals('baz, bar', $r2->getHeaderLine('foo'));
+ }
+
+ public function testReturnsIdentityWhenRemovingMissingHeader()
+ {
+ $r = new Response();
+ $this->assertSame($r, $r->withoutHeader('foo'));
+ }
+
+ public function testAlwaysReturnsBody()
+ {
+ $r = new Response();
+ $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
+ }
+
+ public function testCanSetHeaderAsArray()
+ {
+ $r = new Response(200, array(
+ 'foo' => array('baz ', ' bar ')
+ ));
+ $this->assertEquals('baz, bar', $r->getHeaderLine('foo'));
+ $this->assertEquals(array('baz', 'bar'), $r->getHeader('foo'));
+ }
+
+ public function testSameInstanceWhenSameBody()
+ {
+ $r = new Response(200, array(), 'foo');
+ $b = $r->getBody();
+ $this->assertSame($r, $r->withBody($b));
+ }
+
+ public function testNewInstanceWhenNewBody()
+ {
+ $r = new Response(200, array(), 'foo');
+ $b2 = Psr7\stream_for('abc');
+ $this->assertNotSame($r, $r->withBody($b2));
+ }
+
+ public function testSameInstanceWhenSameProtocol()
+ {
+ $r = new Response(200);
+ $this->assertSame($r, $r->withProtocolVersion('1.1'));
+ }
+
+ public function testNewInstanceWhenNewProtocol()
+ {
+ $r = new Response(200);
+ $this->assertNotSame($r, $r->withProtocolVersion('1.0'));
+ }
+
+ public function testNewInstanceWhenRemovingHeader()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withoutHeader('Foo');
+ $this->assertNotSame($r, $r2);
+ $this->assertFalse($r2->hasHeader('foo'));
+ }
+
+ public function testNewInstanceWhenAddingHeader()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withAddedHeader('Foo', 'Baz');
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals('Bar, Baz', $r2->getHeaderLine('foo'));
+ }
+
+ public function testNewInstanceWhenAddingHeaderArray()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withAddedHeader('Foo', array('Baz', 'Qux'));
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals(array('Bar', 'Baz', 'Qux'), $r2->getHeader('foo'));
+ }
+
+ public function testNewInstanceWhenAddingHeaderThatWasNotThereBefore()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withAddedHeader('Baz', 'Bam');
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals('Bam', $r2->getHeaderLine('Baz'));
+ $this->assertEquals('Bar', $r2->getHeaderLine('Foo'));
+ }
+
+ public function testRemovesPreviouslyAddedHeaderOfDifferentCase()
+ {
+ $r = new Response(200, array('Foo' => 'Bar'));
+ $r2 = $r->withHeader('foo', 'Bam');
+ $this->assertNotSame($r, $r2);
+ $this->assertEquals('Bam', $r2->getHeaderLine('Foo'));
+ }
+
+ public function testBodyConsistent()
+ {
+ $r = new Response(200, array(), '0');
+ $this->assertEquals('0', (string)$r->getBody());
+ }
+
+}
diff --git a/vendor/ringcentral/psr7/tests/ServerRequestTest.php b/vendor/ringcentral/psr7/tests/ServerRequestTest.php
new file mode 100755
index 0000000..4bdfe8e
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/ServerRequestTest.php
@@ -0,0 +1,85 @@
+request = new ServerRequest('GET', 'http://localhost');
+ }
+
+ public function testGetNoAttributes()
+ {
+ $this->assertEquals(array(), $this->request->getAttributes());
+ }
+
+ public function testWithAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('hello' => 'world'), $request->getAttributes());
+ }
+
+ public function testGetAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals('world', $request->getAttribute('hello'));
+ }
+
+ public function testGetDefaultAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(null, $request->getAttribute('hi', null));
+ }
+
+ public function testWithoutAttribute()
+ {
+ $request = $this->request->withAttribute('hello', 'world');
+ $request = $request->withAttribute('test', 'nice');
+
+ $request = $request->withoutAttribute('hello');
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'nice'), $request->getAttributes());
+ }
+
+ public function testWithCookieParams()
+ {
+ $request = $this->request->withCookieParams(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getCookieParams());
+ }
+
+ public function testWithQueryParams()
+ {
+ $request = $this->request->withQueryParams(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getQueryParams());
+ }
+
+ public function testWithUploadedFiles()
+ {
+ $request = $this->request->withUploadedFiles(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getUploadedFiles());
+ }
+
+ public function testWithParsedBody()
+ {
+ $request = $this->request->withParsedBody(array('test' => 'world'));
+
+ $this->assertNotSame($request, $this->request);
+ $this->assertEquals(array('test' => 'world'), $request->getParsedBody());
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/StreamDecoratorTraitTest.php b/vendor/ringcentral/psr7/tests/StreamDecoratorTraitTest.php
new file mode 100755
index 0000000..b0c3dc5
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/StreamDecoratorTraitTest.php
@@ -0,0 +1,123 @@
+c = fopen('php://temp', 'r+');
+ fwrite($this->c, 'foo');
+ fseek($this->c, 0);
+ $this->a = Psr7\stream_for($this->c);
+ $this->b = new Str($this->a);
+ }
+
+ public function testCatchesExceptionsWhenCastingToString()
+ {
+ $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('read'))
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('read')
+ ->will($this->throwException(new \Exception('foo')));
+ $msg = '';
+ set_error_handler(function ($errNo, $str) use (&$msg) {
+ $msg = $str;
+ });
+ echo new Str($s);
+ restore_error_handler();
+ $this->assertContains('foo', $msg);
+ }
+
+ public function testToString()
+ {
+ $this->assertEquals('foo', (string)$this->b);
+ }
+
+ public function testHasSize()
+ {
+ $this->assertEquals(3, $this->b->getSize());
+ }
+
+ public function testReads()
+ {
+ $this->assertEquals('foo', $this->b->read(10));
+ }
+
+ public function testCheckMethods()
+ {
+ $this->assertEquals($this->a->isReadable(), $this->b->isReadable());
+ $this->assertEquals($this->a->isWritable(), $this->b->isWritable());
+ $this->assertEquals($this->a->isSeekable(), $this->b->isSeekable());
+ }
+
+ public function testSeeksAndTells()
+ {
+ $this->b->seek(1);
+ $this->assertEquals(1, $this->a->tell());
+ $this->assertEquals(1, $this->b->tell());
+ $this->b->seek(0);
+ $this->assertEquals(0, $this->a->tell());
+ $this->assertEquals(0, $this->b->tell());
+ $this->b->seek(0, SEEK_END);
+ $this->assertEquals(3, $this->a->tell());
+ $this->assertEquals(3, $this->b->tell());
+ }
+
+ public function testGetsContents()
+ {
+ $this->assertEquals('foo', $this->b->getContents());
+ $this->assertEquals('', $this->b->getContents());
+ $this->b->seek(1);
+ $this->assertEquals('oo', $this->b->getContents(1));
+ }
+
+ public function testCloses()
+ {
+ $this->b->close();
+ $this->assertFalse(is_resource($this->c));
+ }
+
+ public function testDetaches()
+ {
+ $this->b->detach();
+ $this->assertFalse($this->b->isReadable());
+ }
+
+ public function testWrapsMetadata()
+ {
+ $this->assertSame($this->b->getMetadata(), $this->a->getMetadata());
+ $this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri'));
+ }
+
+ public function testWrapsWrites()
+ {
+ $this->b->seek(0, SEEK_END);
+ $this->b->write('foo');
+ $this->assertEquals('foofoo', (string)$this->a);
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ */
+ public function testThrowsWithInvalidGetter()
+ {
+ $this->b->foo;
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/ringcentral/psr7/tests/StreamTest.php b/vendor/ringcentral/psr7/tests/StreamTest.php
new file mode 100755
index 0000000..5501805
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/StreamTest.php
@@ -0,0 +1,163 @@
+assertTrue($stream->isReadable());
+ $this->assertTrue($stream->isWritable());
+ $this->assertTrue($stream->isSeekable());
+ $this->assertEquals('php://temp', $stream->getMetadata('uri'));
+ $this->assertInternalType('array', $stream->getMetadata());
+ $this->assertEquals(4, $stream->getSize());
+ $this->assertFalse($stream->eof());
+ $stream->close();
+ }
+
+ public function testStreamClosesHandleOnDestruct()
+ {
+ $handle = fopen('php://temp', 'r');
+ $stream = new Stream($handle);
+ unset($stream);
+ $this->assertFalse(is_resource($handle));
+ }
+
+ public function testConvertsToString()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals('data', (string) $stream);
+ $this->assertEquals('data', (string) $stream);
+ $stream->close();
+ }
+
+ public function testGetsContents()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals('', $stream->getContents());
+ $stream->seek(0);
+ $this->assertEquals('data', $stream->getContents());
+ $this->assertEquals('', $stream->getContents());
+ }
+
+ public function testChecksEof()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertFalse($stream->eof());
+ $stream->read(4);
+ $this->assertTrue($stream->eof());
+ $stream->close();
+ }
+
+ public function testGetSize()
+ {
+ $size = filesize(__FILE__);
+ $handle = fopen(__FILE__, 'r');
+ $stream = new Stream($handle);
+ $this->assertEquals($size, $stream->getSize());
+ // Load from cache
+ $this->assertEquals($size, $stream->getSize());
+ $stream->close();
+ }
+
+ public function testEnsuresSizeIsConsistent()
+ {
+ $h = fopen('php://temp', 'w+');
+ $this->assertEquals(3, fwrite($h, 'foo'));
+ $stream = new Stream($h);
+ $this->assertEquals(3, $stream->getSize());
+ $this->assertEquals(4, $stream->write('test'));
+ $this->assertEquals(7, $stream->getSize());
+ $this->assertEquals(7, $stream->getSize());
+ $stream->close();
+ }
+
+ public function testProvidesStreamPosition()
+ {
+ $handle = fopen('php://temp', 'w+');
+ $stream = new Stream($handle);
+ $this->assertEquals(0, $stream->tell());
+ $stream->write('foo');
+ $this->assertEquals(3, $stream->tell());
+ $stream->seek(1);
+ $this->assertEquals(1, $stream->tell());
+ $this->assertSame(ftell($handle), $stream->tell());
+ $stream->close();
+ }
+
+ public function testCanDetachStream()
+ {
+ $r = fopen('php://temp', 'w+');
+ $stream = new Stream($r);
+ $stream->write('foo');
+ $this->assertTrue($stream->isReadable());
+ $this->assertSame($r, $stream->detach());
+ $stream->detach();
+
+ $this->assertFalse($stream->isReadable());
+ $this->assertFalse($stream->isWritable());
+ $this->assertFalse($stream->isSeekable());
+
+ $self = $this;
+
+ $throws = function ($fn) use ($stream, $self) {
+ try {
+ $fn($stream);
+ $self->fail();
+ } catch (\Exception $e) {}
+ };
+
+ $throws(function ($stream) { $stream->read(10); });
+ $throws(function ($stream) { $stream->write('bar'); });
+ $throws(function ($stream) { $stream->seek(10); });
+ $throws(function ($stream) { $stream->tell(); });
+ $throws(function ($stream) { $stream->eof(); });
+ $throws(function ($stream) { $stream->getSize(); });
+ $throws(function ($stream) { $stream->getContents(); });
+ $this->assertSame('', (string) $stream);
+ $stream->close();
+ }
+
+ public function testCloseClearProperties()
+ {
+ $handle = fopen('php://temp', 'r+');
+ $stream = new Stream($handle);
+ $stream->close();
+
+ $this->assertFalse($stream->isSeekable());
+ $this->assertFalse($stream->isReadable());
+ $this->assertFalse($stream->isWritable());
+ $this->assertNull($stream->getSize());
+ $this->assertEmpty($stream->getMetadata());
+ }
+
+ public function testDoesNotThrowInToString()
+ {
+ $s = \RingCentral\Psr7\stream_for('foo');
+ $s = new NoSeekStream($s);
+ $this->assertEquals('foo', (string) $s);
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/StreamWrapperTest.php b/vendor/ringcentral/psr7/tests/StreamWrapperTest.php
new file mode 100755
index 0000000..dd08a83
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/StreamWrapperTest.php
@@ -0,0 +1,100 @@
+assertSame('foo', fread($handle, 3));
+ $this->assertSame(3, ftell($handle));
+ $this->assertSame(3, fwrite($handle, 'bar'));
+ $this->assertSame(0, fseek($handle, 0));
+ $this->assertSame('foobar', fread($handle, 6));
+ $this->assertSame('', fread($handle, 1));
+ $this->assertTrue(feof($handle));
+
+ // This fails on HHVM for some reason
+ if (!defined('HHVM_VERSION')) {
+ $this->assertEquals(array(
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 33206,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 6,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ 0 => 0,
+ 1 => 0,
+ 2 => 33206,
+ 3 => 0,
+ 4 => 0,
+ 5 => 0,
+ 6 => 0,
+ 7 => 6,
+ 8 => 0,
+ 9 => 0,
+ 10 => 0,
+ 11 => 0,
+ 12 => 0,
+ ), fstat($handle));
+ }
+
+ $this->assertTrue(fclose($handle));
+ $this->assertSame('foobar', (string) $stream);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesStream()
+ {
+ $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable', 'isWritable'))
+ ->getMockForAbstractClass();
+ $stream->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $stream->expects($this->once())
+ ->method('isWritable')
+ ->will($this->returnValue(false));
+ StreamWrapper::getResource($stream);
+ }
+
+ /**
+ * @expectedException \PHPUnit_Framework_Error_Warning
+ */
+ public function testReturnsFalseWhenStreamDoesNotExist()
+ {
+ fopen('guzzle://foo', 'r');
+ }
+
+ public function testCanOpenReadonlyStream()
+ {
+ $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
+ ->setMethods(array('isReadable', 'isWritable'))
+ ->getMockForAbstractClass();
+ $stream->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $stream->expects($this->once())
+ ->method('isWritable')
+ ->will($this->returnValue(true));
+ $r = StreamWrapper::getResource($stream);
+ $this->assertInternalType('resource', $r);
+ fclose($r);
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/UriTest.php b/vendor/ringcentral/psr7/tests/UriTest.php
new file mode 100755
index 0000000..b53c97e
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/UriTest.php
@@ -0,0 +1,258 @@
+assertEquals(
+ 'https://michael:test@test.com/path/123?q=abc#test',
+ (string) $uri
+ );
+
+ $this->assertEquals('test', $uri->getFragment());
+ $this->assertEquals('test.com', $uri->getHost());
+ $this->assertEquals('/path/123', $uri->getPath());
+ $this->assertEquals(null, $uri->getPort());
+ $this->assertEquals('q=abc', $uri->getQuery());
+ $this->assertEquals('https', $uri->getScheme());
+ $this->assertEquals('michael:test', $uri->getUserInfo());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Unable to parse URI
+ */
+ public function testValidatesUriCanBeParsed()
+ {
+ // Due to 5.4.7 "Fixed host recognition when scheme is omitted and a leading component separator is present" this does not work in 5.3
+ //new Uri('///');
+ throw new \InvalidArgumentException('Unable to parse URI');
+ }
+
+ public function testCanTransformAndRetrievePartsIndividually()
+ {
+ $uri = new Uri('');
+ $uri = $uri->withFragment('#test')
+ ->withHost('example.com')
+ ->withPath('path/123')
+ ->withPort(8080)
+ ->withQuery('?q=abc')
+ ->withScheme('http')
+ ->withUserInfo('user', 'pass');
+
+ // Test getters.
+ $this->assertEquals('user:pass@example.com:8080', $uri->getAuthority());
+ $this->assertEquals('test', $uri->getFragment());
+ $this->assertEquals('example.com', $uri->getHost());
+ $this->assertEquals('path/123', $uri->getPath());
+ $this->assertEquals(8080, $uri->getPort());
+ $this->assertEquals('q=abc', $uri->getQuery());
+ $this->assertEquals('http', $uri->getScheme());
+ $this->assertEquals('user:pass', $uri->getUserInfo());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testPortMustBeValid()
+ {
+ $uri = new Uri('');
+ $uri->withPort(100000);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testPathMustBeValid()
+ {
+ $uri = new Uri('');
+ $uri->withPath(array());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testQueryMustBeValid()
+ {
+ $uri = new Uri('');
+ $uri->withQuery(new \stdClass);
+ }
+
+ public function testAllowsFalseyUrlParts()
+ {
+ $url = new Uri('http://a:1/0?0#0');
+ $this->assertSame('a', $url->getHost());
+ $this->assertEquals(1, $url->getPort());
+ $this->assertSame('/0', $url->getPath());
+ $this->assertEquals('0', (string) $url->getQuery());
+ $this->assertSame('0', $url->getFragment());
+ $this->assertEquals('http://a:1/0?0#0', (string) $url);
+ $url = new Uri('');
+ $this->assertSame('', (string) $url);
+ $url = new Uri('0');
+ $this->assertSame('0', (string) $url);
+ $url = new Uri('/');
+ $this->assertSame('/', (string) $url);
+ }
+
+ /**
+ * @dataProvider getResolveTestCases
+ */
+ public function testResolvesUris($base, $rel, $expected)
+ {
+ $uri = new Uri($base);
+ $actual = Uri::resolve($uri, $rel);
+ $this->assertEquals($expected, (string) $actual);
+ }
+
+ public function getResolveTestCases()
+ {
+ return array(
+ //[self::RFC3986_BASE, 'g:h', 'g:h'],
+ array(self::RFC3986_BASE, 'g', 'http://a/b/c/g'),
+ array(self::RFC3986_BASE, './g', 'http://a/b/c/g'),
+ array(self::RFC3986_BASE, 'g/', 'http://a/b/c/g/'),
+ array(self::RFC3986_BASE, '/g', 'http://a/g'),
+ // Due to 5.4.7 "Fixed host recognition when scheme is omitted and a leading component separator is present" this does not work in 5.3
+ //array(self::RFC3986_BASE, '//g', 'http://g'),
+ array(self::RFC3986_BASE, '?y', 'http://a/b/c/d;p?y'),
+ array(self::RFC3986_BASE, 'g?y', 'http://a/b/c/g?y'),
+ array(self::RFC3986_BASE, '#s', 'http://a/b/c/d;p?q#s'),
+ array(self::RFC3986_BASE, 'g#s', 'http://a/b/c/g#s'),
+ array(self::RFC3986_BASE, 'g?y#s', 'http://a/b/c/g?y#s'),
+ array(self::RFC3986_BASE, ';x', 'http://a/b/c/;x'),
+ array(self::RFC3986_BASE, 'g;x', 'http://a/b/c/g;x'),
+ array(self::RFC3986_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s'),
+ array(self::RFC3986_BASE, '', self::RFC3986_BASE),
+ array(self::RFC3986_BASE, '.', 'http://a/b/c/'),
+ array(self::RFC3986_BASE, './', 'http://a/b/c/'),
+ array(self::RFC3986_BASE, '..', 'http://a/b/'),
+ array(self::RFC3986_BASE, '../', 'http://a/b/'),
+ array(self::RFC3986_BASE, '../g', 'http://a/b/g'),
+ array(self::RFC3986_BASE, '../..', 'http://a/'),
+ array(self::RFC3986_BASE, '../../', 'http://a/'),
+ array(self::RFC3986_BASE, '../../g', 'http://a/g'),
+ array(self::RFC3986_BASE, '../../../g', 'http://a/g'),
+ array(self::RFC3986_BASE, '../../../../g', 'http://a/g'),
+ array(self::RFC3986_BASE, '/./g', 'http://a/g'),
+ array(self::RFC3986_BASE, '/../g', 'http://a/g'),
+ array(self::RFC3986_BASE, 'g.', 'http://a/b/c/g.'),
+ array(self::RFC3986_BASE, '.g', 'http://a/b/c/.g'),
+ array(self::RFC3986_BASE, 'g..', 'http://a/b/c/g..'),
+ array(self::RFC3986_BASE, '..g', 'http://a/b/c/..g'),
+ array(self::RFC3986_BASE, './../g', 'http://a/b/g'),
+ array(self::RFC3986_BASE, 'foo////g', 'http://a/b/c/foo////g'),
+ array(self::RFC3986_BASE, './g/.', 'http://a/b/c/g/'),
+ array(self::RFC3986_BASE, 'g/./h', 'http://a/b/c/g/h'),
+ array(self::RFC3986_BASE, 'g/../h', 'http://a/b/c/h'),
+ array(self::RFC3986_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y'),
+ array(self::RFC3986_BASE, 'g;x=1/../y', 'http://a/b/c/y'),
+ array('http://u@a/b/c/d;p?q', '.', 'http://u@a/b/c/'),
+ array('http://u:p@a/b/c/d;p?q', '.', 'http://u:p@a/b/c/'),
+ //[self::RFC3986_BASE, 'http:g', 'http:g'],
+ );
+ }
+
+ public function testAddAndRemoveQueryValues()
+ {
+ $uri = new Uri('http://foo.com/bar');
+ $uri = Uri::withQueryValue($uri, 'a', 'b');
+ $uri = Uri::withQueryValue($uri, 'c', 'd');
+ $uri = Uri::withQueryValue($uri, 'e', null);
+ $this->assertEquals('a=b&c=d&e', $uri->getQuery());
+
+ $uri = Uri::withoutQueryValue($uri, 'c');
+ $uri = Uri::withoutQueryValue($uri, 'e');
+ $this->assertEquals('a=b', $uri->getQuery());
+ $uri = Uri::withoutQueryValue($uri, 'a');
+ $uri = Uri::withoutQueryValue($uri, 'a');
+ $this->assertEquals('', $uri->getQuery());
+ }
+
+ public function testGetAuthorityReturnsCorrectPort()
+ {
+ // HTTPS non-standard port
+ $uri = new Uri('https://foo.co:99');
+ $this->assertEquals('foo.co:99', $uri->getAuthority());
+
+ // HTTP non-standard port
+ $uri = new Uri('http://foo.co:99');
+ $this->assertEquals('foo.co:99', $uri->getAuthority());
+
+ // No scheme
+ $uri = new Uri('foo.co:99');
+ $this->assertEquals('foo.co:99', $uri->getAuthority());
+
+ // No host or port
+ $uri = new Uri('http:');
+ $this->assertEquals('', $uri->getAuthority());
+
+ // No host or port
+ $uri = new Uri('http://foo.co');
+ $this->assertEquals('foo.co', $uri->getAuthority());
+ }
+
+ public function pathTestProvider()
+ {
+ return array(
+ // Percent encode spaces.
+ array('http://foo.com/baz bar', 'http://foo.com/baz%20bar'),
+ // Don't encoding something that's already encoded.
+ array('http://foo.com/baz%20bar', 'http://foo.com/baz%20bar'),
+ // Percent encode invalid percent encodings
+ array('http://foo.com/baz%2-bar', 'http://foo.com/baz%252-bar'),
+ // Don't encode path segments
+ array('http://foo.com/baz/bar/bam?a', 'http://foo.com/baz/bar/bam?a'),
+ array('http://foo.com/baz+bar', 'http://foo.com/baz+bar'),
+ array('http://foo.com/baz:bar', 'http://foo.com/baz:bar'),
+ array('http://foo.com/baz@bar', 'http://foo.com/baz@bar'),
+ array('http://foo.com/baz(bar);bam/', 'http://foo.com/baz(bar);bam/'),
+ array('http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@', 'http://foo.com/a-zA-Z0-9.-_~!$&\'()*+,;=:@'),
+ );
+ }
+
+ /**
+ * @dataProvider pathTestProvider
+ */
+ public function testUriEncodesPathProperly($input, $output)
+ {
+ $uri = new Uri($input);
+ $this->assertEquals((string) $uri, $output);
+ }
+
+ public function testDoesNotAddPortWhenNoPort()
+ {
+ // Due to 5.4.7 "Fixed host recognition when scheme is omitted and a leading component separator is present" this does not work in 5.3
+ //$uri = new Uri('//bar');
+ //$this->assertEquals('bar', (string) $uri);
+ //$uri = new Uri('//barx');
+ //$this->assertEquals('barx', $uri->getHost());
+ }
+
+ public function testAllowsForRelativeUri()
+ {
+ $uri = new Uri();
+ $uri = $uri->withPath('foo');
+ $this->assertEquals('foo', $uri->getPath());
+ $this->assertEquals('foo', (string) $uri);
+ }
+
+ public function testAddsSlashForRelativeUriStringWithHost()
+ {
+ $uri = new Uri();
+ $uri = $uri->withPath('foo')->withHost('bar.com');
+ $this->assertEquals('foo', $uri->getPath());
+ $this->assertEquals('bar.com/foo', (string) $uri);
+ }
+}
diff --git a/vendor/ringcentral/psr7/tests/bootstrap.php b/vendor/ringcentral/psr7/tests/bootstrap.php
new file mode 100755
index 0000000..ea6a079
--- /dev/null
+++ b/vendor/ringcentral/psr7/tests/bootstrap.php
@@ -0,0 +1,13 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * Marker Interface for the Process Component.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/vendor/symfony/process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Exception/InvalidArgumentException.php
new file mode 100755
index 0000000..926ee21
--- /dev/null
+++ b/vendor/symfony/process/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * InvalidArgumentException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/Exception/LogicException.php b/vendor/symfony/process/Exception/LogicException.php
new file mode 100755
index 0000000..be3d490
--- /dev/null
+++ b/vendor/symfony/process/Exception/LogicException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * LogicException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/Exception/ProcessFailedException.php b/vendor/symfony/process/Exception/ProcessFailedException.php
new file mode 100755
index 0000000..328acfd
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessFailedException.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception for failed processes.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessFailedException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ if ($process->isSuccessful()) {
+ throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
+ }
+
+ $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
+ $process->getCommandLine(),
+ $process->getExitCode(),
+ $process->getExitCodeText(),
+ $process->getWorkingDirectory()
+ );
+
+ if (!$process->isOutputDisabled()) {
+ $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
+ $process->getOutput(),
+ $process->getErrorOutput()
+ );
+ }
+
+ parent::__construct($error);
+
+ $this->process = $process;
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+}
diff --git a/vendor/symfony/process/Exception/ProcessSignaledException.php b/vendor/symfony/process/Exception/ProcessSignaledException.php
new file mode 100755
index 0000000..d4d3227
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessSignaledException.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process has been signaled.
+ *
+ * @author Sullivan Senechal
+ */
+final class ProcessSignaledException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ $this->process = $process;
+
+ parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
+ }
+
+ public function getProcess(): Process
+ {
+ return $this->process;
+ }
+
+ public function getSignal(): int
+ {
+ return $this->getProcess()->getTermSignal();
+ }
+}
diff --git a/vendor/symfony/process/Exception/ProcessTimedOutException.php b/vendor/symfony/process/Exception/ProcessTimedOutException.php
new file mode 100755
index 0000000..e1f6445
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessTimedOutException.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+ const TYPE_GENERAL = 1;
+ const TYPE_IDLE = 2;
+
+ private $process;
+ private $timeoutType;
+
+ public function __construct(Process $process, int $timeoutType)
+ {
+ $this->process = $process;
+ $this->timeoutType = $timeoutType;
+
+ parent::__construct(sprintf(
+ 'The process "%s" exceeded the timeout of %s seconds.',
+ $process->getCommandLine(),
+ $this->getExceededTimeout()
+ ));
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ public function isGeneralTimeout()
+ {
+ return self::TYPE_GENERAL === $this->timeoutType;
+ }
+
+ public function isIdleTimeout()
+ {
+ return self::TYPE_IDLE === $this->timeoutType;
+ }
+
+ public function getExceededTimeout()
+ {
+ switch ($this->timeoutType) {
+ case self::TYPE_GENERAL:
+ return $this->process->getTimeout();
+
+ case self::TYPE_IDLE:
+ return $this->process->getIdleTimeout();
+
+ default:
+ throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+ }
+ }
+}
diff --git a/vendor/symfony/process/Exception/RuntimeException.php b/vendor/symfony/process/Exception/RuntimeException.php
new file mode 100755
index 0000000..adead25
--- /dev/null
+++ b/vendor/symfony/process/Exception/RuntimeException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * RuntimeException for the Process Component.
+ *
+ * @author Johannes M. Schmitt
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/ExecutableFinder.php b/vendor/symfony/process/ExecutableFinder.php
new file mode 100755
index 0000000..cb4345e
--- /dev/null
+++ b/vendor/symfony/process/ExecutableFinder.php
@@ -0,0 +1,88 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * Generic executable finder.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class ExecutableFinder
+{
+ private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
+
+ /**
+ * Replaces default suffixes of executable.
+ */
+ public function setSuffixes(array $suffixes)
+ {
+ $this->suffixes = $suffixes;
+ }
+
+ /**
+ * Adds new possible suffix to check for executable.
+ *
+ * @param string $suffix
+ */
+ public function addSuffix($suffix)
+ {
+ $this->suffixes[] = $suffix;
+ }
+
+ /**
+ * Finds an executable by name.
+ *
+ * @param string $name The executable name (without the extension)
+ * @param string|null $default The default to return if no executable is found
+ * @param array $extraDirs Additional dirs to check into
+ *
+ * @return string|null The executable path or default value
+ */
+ public function find($name, $default = null, array $extraDirs = [])
+ {
+ if (ini_get('open_basedir')) {
+ $searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
+ $dirs = [];
+ foreach ($searchPath as $path) {
+ // Silencing against https://bugs.php.net/69240
+ if (@is_dir($path)) {
+ $dirs[] = $path;
+ } else {
+ if (basename($path) == $name && @is_executable($path)) {
+ return $path;
+ }
+ }
+ }
+ } else {
+ $dirs = array_merge(
+ explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
+ $extraDirs
+ );
+ }
+
+ $suffixes = [''];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $pathExt = getenv('PATHEXT');
+ $suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
+ }
+ foreach ($suffixes as $suffix) {
+ foreach ($dirs as $dir) {
+ if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
+ return $file;
+ }
+ }
+ }
+
+ return $default;
+ }
+}
diff --git a/vendor/symfony/process/InputStream.php b/vendor/symfony/process/InputStream.php
new file mode 100755
index 0000000..426ffa3
--- /dev/null
+++ b/vendor/symfony/process/InputStream.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * Provides a way to continuously write to the input of a Process until the InputStream is closed.
+ *
+ * @author Nicolas Grekas
+ */
+class InputStream implements \IteratorAggregate
+{
+ /** @var callable|null */
+ private $onEmpty = null;
+ private $input = [];
+ private $open = true;
+
+ /**
+ * Sets a callback that is called when the write buffer becomes empty.
+ */
+ public function onEmpty(callable $onEmpty = null)
+ {
+ $this->onEmpty = $onEmpty;
+ }
+
+ /**
+ * Appends an input to the write buffer.
+ *
+ * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
+ * stream resource or \Traversable
+ */
+ public function write($input)
+ {
+ if (null === $input) {
+ return;
+ }
+ if ($this->isClosed()) {
+ throw new RuntimeException(sprintf('%s is closed', static::class));
+ }
+ $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
+ }
+
+ /**
+ * Closes the write buffer.
+ */
+ public function close()
+ {
+ $this->open = false;
+ }
+
+ /**
+ * Tells whether the write buffer is closed or not.
+ */
+ public function isClosed()
+ {
+ return !$this->open;
+ }
+
+ public function getIterator()
+ {
+ $this->open = true;
+
+ while ($this->open || $this->input) {
+ if (!$this->input) {
+ yield '';
+ continue;
+ }
+ $current = array_shift($this->input);
+
+ if ($current instanceof \Iterator) {
+ yield from $current;
+ } else {
+ yield $current;
+ }
+ if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
+ $this->write($onEmpty($this));
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/process/LICENSE b/vendor/symfony/process/LICENSE
new file mode 100755
index 0000000..a677f43
--- /dev/null
+++ b/vendor/symfony/process/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2019 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/process/PhpExecutableFinder.php b/vendor/symfony/process/PhpExecutableFinder.php
new file mode 100755
index 0000000..461ea13
--- /dev/null
+++ b/vendor/symfony/process/PhpExecutableFinder.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * An executable finder specifically designed for the PHP executable.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class PhpExecutableFinder
+{
+ private $executableFinder;
+
+ public function __construct()
+ {
+ $this->executableFinder = new ExecutableFinder();
+ }
+
+ /**
+ * Finds The PHP executable.
+ *
+ * @param bool $includeArgs Whether or not include command arguments
+ *
+ * @return string|false The PHP executable path or false if it cannot be found
+ */
+ public function find($includeArgs = true)
+ {
+ if ($php = getenv('PHP_BINARY')) {
+ if (!is_executable($php)) {
+ $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
+ if ($php = strtok(exec($command.' '.escapeshellarg($php)), PHP_EOL)) {
+ if (!is_executable($php)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return $php;
+ }
+
+ $args = $this->findArguments();
+ $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
+
+ // PHP_BINARY return the current sapi executable
+ if (PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
+ return PHP_BINARY.$args;
+ }
+
+ if ($php = getenv('PHP_PATH')) {
+ if (!@is_executable($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ if ($php = getenv('PHP_PEAR_PHP_BIN')) {
+ if (@is_executable($php)) {
+ return $php;
+ }
+ }
+
+ if (@is_executable($php = PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
+ return $php;
+ }
+
+ $dirs = [PHP_BINDIR];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $dirs[] = 'C:\xampp\php\\';
+ }
+
+ return $this->executableFinder->find('php', false, $dirs);
+ }
+
+ /**
+ * Finds the PHP executable arguments.
+ *
+ * @return array The PHP executable arguments
+ */
+ public function findArguments()
+ {
+ $arguments = [];
+ if ('phpdbg' === \PHP_SAPI) {
+ $arguments[] = '-qrr';
+ }
+
+ return $arguments;
+ }
+}
diff --git a/vendor/symfony/process/PhpProcess.php b/vendor/symfony/process/PhpProcess.php
new file mode 100755
index 0000000..126d9b7
--- /dev/null
+++ b/vendor/symfony/process/PhpProcess.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * PhpProcess runs a PHP script in an independent process.
+ *
+ * $p = new PhpProcess('');
+ * $p->run();
+ * print $p->getOutput()."\n";
+ *
+ * @author Fabien Potencier
+ */
+class PhpProcess extends Process
+{
+ /**
+ * @param string $script The PHP script to run (as a string)
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param int $timeout The timeout in seconds
+ * @param array|null $php Path to the PHP binary to use with any additional arguments
+ */
+ public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null)
+ {
+ if (null === $php) {
+ $executableFinder = new PhpExecutableFinder();
+ $php = $executableFinder->find(false);
+ $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
+ }
+ if ('phpdbg' === \PHP_SAPI) {
+ $file = tempnam(sys_get_temp_dir(), 'dbg');
+ file_put_contents($file, $script);
+ register_shutdown_function('unlink', $file);
+ $php[] = $file;
+ $script = null;
+ }
+
+ parent::__construct($php, $cwd, $env, $script, $timeout);
+ }
+
+ /**
+ * Sets the path to the PHP binary to use.
+ *
+ * @deprecated since Symfony 4.2, use the $php argument of the constructor instead.
+ */
+ public function setPhpBinary($php)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the $php argument of the constructor instead.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->setCommandLine($php);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if (null === $this->getCommandLine()) {
+ throw new RuntimeException('Unable to find the PHP executable.');
+ }
+
+ parent::start($callback, $env);
+ }
+}
diff --git a/vendor/symfony/process/Pipes/AbstractPipes.php b/vendor/symfony/process/Pipes/AbstractPipes.php
new file mode 100755
index 0000000..9dd415d
--- /dev/null
+++ b/vendor/symfony/process/Pipes/AbstractPipes.php
@@ -0,0 +1,182 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+abstract class AbstractPipes implements PipesInterface
+{
+ public $pipes = [];
+
+ private $inputBuffer = '';
+ private $input;
+ private $blocked = true;
+ private $lastError;
+
+ /**
+ * @param resource|string|int|float|bool|\Iterator|null $input
+ */
+ public function __construct($input)
+ {
+ if (\is_resource($input) || $input instanceof \Iterator) {
+ $this->input = $input;
+ } elseif (\is_string($input)) {
+ $this->inputBuffer = $input;
+ } else {
+ $this->inputBuffer = (string) $input;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ foreach ($this->pipes as $pipe) {
+ fclose($pipe);
+ }
+ $this->pipes = [];
+ }
+
+ /**
+ * Returns true if a system call has been interrupted.
+ *
+ * @return bool
+ */
+ protected function hasSystemCallBeenInterrupted()
+ {
+ $lastError = $this->lastError;
+ $this->lastError = null;
+
+ // stream_select returns false when the `select` system call is interrupted by an incoming signal
+ return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
+ }
+
+ /**
+ * Unblocks streams.
+ */
+ protected function unblock()
+ {
+ if (!$this->blocked) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ stream_set_blocking($pipe, 0);
+ }
+ if (\is_resource($this->input)) {
+ stream_set_blocking($this->input, 0);
+ }
+
+ $this->blocked = false;
+ }
+
+ /**
+ * Writes input to stdin.
+ *
+ * @return array|null
+ *
+ * @throws InvalidArgumentException When an input iterator yields a non supported value
+ */
+ protected function write()
+ {
+ if (!isset($this->pipes[0])) {
+ return null;
+ }
+ $input = $this->input;
+
+ if ($input instanceof \Iterator) {
+ if (!$input->valid()) {
+ $input = null;
+ } elseif (\is_resource($input = $input->current())) {
+ stream_set_blocking($input, 0);
+ } elseif (!isset($this->inputBuffer[0])) {
+ if (!\is_string($input)) {
+ if (!is_scalar($input)) {
+ throw new InvalidArgumentException(sprintf('%s yielded a value of type "%s", but only scalars and stream resources are supported', \get_class($this->input), \gettype($input)));
+ }
+ $input = (string) $input;
+ }
+ $this->inputBuffer = $input;
+ $this->input->next();
+ $input = null;
+ } else {
+ $input = null;
+ }
+ }
+
+ $r = $e = [];
+ $w = [$this->pipes[0]];
+
+ // let's have a look if something changed in streams
+ if (false === @stream_select($r, $w, $e, 0, 0)) {
+ return null;
+ }
+
+ foreach ($w as $stdin) {
+ if (isset($this->inputBuffer[0])) {
+ $written = fwrite($stdin, $this->inputBuffer);
+ $this->inputBuffer = substr($this->inputBuffer, $written);
+ if (isset($this->inputBuffer[0])) {
+ return [$this->pipes[0]];
+ }
+ }
+
+ if ($input) {
+ for (;;) {
+ $data = fread($input, self::CHUNK_SIZE);
+ if (!isset($data[0])) {
+ break;
+ }
+ $written = fwrite($stdin, $data);
+ $data = substr($data, $written);
+ if (isset($data[0])) {
+ $this->inputBuffer = $data;
+
+ return [$this->pipes[0]];
+ }
+ }
+ if (feof($input)) {
+ if ($this->input instanceof \Iterator) {
+ $this->input->next();
+ } else {
+ $this->input = null;
+ }
+ }
+ }
+ }
+
+ // no input to read on resource, buffer is empty
+ if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
+ $this->input = null;
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ } elseif (!$w) {
+ return [$this->pipes[0]];
+ }
+
+ return null;
+ }
+
+ /**
+ * @internal
+ */
+ public function handleError($type, $msg)
+ {
+ $this->lastError = $msg;
+ }
+}
diff --git a/vendor/symfony/process/Pipes/PipesInterface.php b/vendor/symfony/process/Pipes/PipesInterface.php
new file mode 100755
index 0000000..52bbe76
--- /dev/null
+++ b/vendor/symfony/process/Pipes/PipesInterface.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+/**
+ * PipesInterface manages descriptors and pipes for the use of proc_open.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+interface PipesInterface
+{
+ const CHUNK_SIZE = 16384;
+
+ /**
+ * Returns an array of descriptors for the use of proc_open.
+ *
+ * @return array
+ */
+ public function getDescriptors();
+
+ /**
+ * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
+ *
+ * @return string[]
+ */
+ public function getFiles();
+
+ /**
+ * Reads data in file handles and pipes.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close pipes if they've reached EOF
+ *
+ * @return string[] An array of read data indexed by their fd
+ */
+ public function readAndWrite($blocking, $close = false);
+
+ /**
+ * Returns if the current state has open file handles or pipes.
+ *
+ * @return bool
+ */
+ public function areOpen();
+
+ /**
+ * Returns if pipes are able to read output.
+ *
+ * @return bool
+ */
+ public function haveReadSupport();
+
+ /**
+ * Closes file handles and pipes.
+ */
+ public function close();
+}
diff --git a/vendor/symfony/process/Pipes/UnixPipes.php b/vendor/symfony/process/Pipes/UnixPipes.php
new file mode 100755
index 0000000..875ee6a
--- /dev/null
+++ b/vendor/symfony/process/Pipes/UnixPipes.php
@@ -0,0 +1,153 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * UnixPipes implementation uses unix pipes as handles.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class UnixPipes extends AbstractPipes
+{
+ private $ttyMode;
+ private $ptyMode;
+ private $haveReadSupport;
+
+ public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
+ {
+ $this->ttyMode = $ttyMode;
+ $this->ptyMode = $ptyMode;
+ $this->haveReadSupport = $haveReadSupport;
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('/dev/null', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ if ($this->ttyMode) {
+ return [
+ ['file', '/dev/tty', 'r'],
+ ['file', '/dev/tty', 'w'],
+ ['file', '/dev/tty', 'w'],
+ ];
+ }
+
+ if ($this->ptyMode && Process::isPtySupported()) {
+ return [
+ ['pty'],
+ ['pty'],
+ ['pty'],
+ ];
+ }
+
+ return [
+ ['pipe', 'r'],
+ ['pipe', 'w'], // stdout
+ ['pipe', 'w'], // stderr
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+
+ $read = $e = [];
+ $r = $this->pipes;
+ unset($r[0]);
+
+ // let's have a look if something changed in streams
+ set_error_handler([$this, 'handleError']);
+ if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+ restore_error_handler();
+ // if a system call has been interrupted, forget about it, let's try again
+ // otherwise, an error occurred, let's reset pipes
+ if (!$this->hasSystemCallBeenInterrupted()) {
+ $this->pipes = [];
+ }
+
+ return $read;
+ }
+ restore_error_handler();
+
+ foreach ($r as $pipe) {
+ // prior PHP 5.4 the array passed to stream_select is modified and
+ // lose key association, we have to find back the key
+ $read[$type = array_search($pipe, $this->pipes, true)] = '';
+
+ do {
+ $data = fread($pipe, self::CHUNK_SIZE);
+ $read[$type] .= $data;
+ } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
+
+ if (!isset($read[$type][0])) {
+ unset($read[$type]);
+ }
+
+ if ($close && feof($pipe)) {
+ fclose($pipe);
+ unset($this->pipes[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return (bool) $this->pipes;
+ }
+}
diff --git a/vendor/symfony/process/Pipes/WindowsPipes.php b/vendor/symfony/process/Pipes/WindowsPipes.php
new file mode 100755
index 0000000..0e38d72
--- /dev/null
+++ b/vendor/symfony/process/Pipes/WindowsPipes.php
@@ -0,0 +1,191 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Process;
+
+/**
+ * WindowsPipes implementation uses temporary files as handles.
+ *
+ * @see https://bugs.php.net/51800
+ * @see https://bugs.php.net/65650
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class WindowsPipes extends AbstractPipes
+{
+ private $files = [];
+ private $fileHandles = [];
+ private $lockHandles = [];
+ private $readBytes = [
+ Process::STDOUT => 0,
+ Process::STDERR => 0,
+ ];
+ private $haveReadSupport;
+
+ public function __construct($input, bool $haveReadSupport)
+ {
+ $this->haveReadSupport = $haveReadSupport;
+
+ if ($this->haveReadSupport) {
+ // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
+ // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
+ //
+ // @see https://bugs.php.net/51800
+ $pipes = [
+ Process::STDOUT => Process::OUT,
+ Process::STDERR => Process::ERR,
+ ];
+ $tmpDir = sys_get_temp_dir();
+ $lastError = 'unknown reason';
+ set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
+ for ($i = 0;; ++$i) {
+ foreach ($pipes as $pipe => $name) {
+ $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
+
+ if (!$h = fopen($file.'.lock', 'w')) {
+ restore_error_handler();
+ throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $lastError));
+ }
+ if (!flock($h, LOCK_EX | LOCK_NB)) {
+ continue 2;
+ }
+ if (isset($this->lockHandles[$pipe])) {
+ flock($this->lockHandles[$pipe], LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ }
+ $this->lockHandles[$pipe] = $h;
+
+ if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
+ flock($this->lockHandles[$pipe], LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ unset($this->lockHandles[$pipe]);
+ continue 2;
+ }
+ $this->fileHandles[$pipe] = $h;
+ $this->files[$pipe] = $file;
+ }
+ break;
+ }
+ restore_error_handler();
+ }
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('NUL', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
+ // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
+ // So we redirect output within the commandline and pass the nul device to the process
+ return [
+ ['pipe', 'r'],
+ ['file', 'NUL', 'w'],
+ ['file', 'NUL', 'w'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return $this->files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+ $read = $r = $e = [];
+
+ if ($blocking) {
+ if ($w) {
+ @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
+ } elseif ($this->fileHandles) {
+ usleep(Process::TIMEOUT_PRECISION * 1E6);
+ }
+ }
+ foreach ($this->fileHandles as $type => $fileHandle) {
+ $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
+
+ if (isset($data[0])) {
+ $this->readBytes[$type] += \strlen($data);
+ $read[$type] = $data;
+ }
+ if ($close) {
+ ftruncate($fileHandle, 0);
+ fclose($fileHandle);
+ flock($this->lockHandles[$type], LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ unset($this->fileHandles[$type], $this->lockHandles[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return $this->pipes && $this->fileHandles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ parent::close();
+ foreach ($this->fileHandles as $type => $handle) {
+ ftruncate($handle, 0);
+ fclose($handle);
+ flock($this->lockHandles[$type], LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ }
+ $this->fileHandles = $this->lockHandles = [];
+ }
+}
diff --git a/vendor/symfony/process/Process.php b/vendor/symfony/process/Process.php
new file mode 100755
index 0000000..5d02c60
--- /dev/null
+++ b/vendor/symfony/process/Process.php
@@ -0,0 +1,1645 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Exception\ProcessSignaledException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Pipes\UnixPipes;
+use Symfony\Component\Process\Pipes\WindowsPipes;
+
+/**
+ * Process is a thin wrapper around proc_* functions to easily
+ * start independent PHP processes.
+ *
+ * @author Fabien Potencier
+ * @author Romain Neutron
+ */
+class Process implements \IteratorAggregate
+{
+ const ERR = 'err';
+ const OUT = 'out';
+
+ const STATUS_READY = 'ready';
+ const STATUS_STARTED = 'started';
+ const STATUS_TERMINATED = 'terminated';
+
+ const STDIN = 0;
+ const STDOUT = 1;
+ const STDERR = 2;
+
+ // Timeout Precision in seconds.
+ const TIMEOUT_PRECISION = 0.2;
+
+ const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
+ const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
+ const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
+ const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
+
+ private $callback;
+ private $hasCallback = false;
+ private $commandline;
+ private $cwd;
+ private $env;
+ private $input;
+ private $starttime;
+ private $lastOutputTime;
+ private $timeout;
+ private $idleTimeout;
+ private $exitcode;
+ private $fallbackStatus = [];
+ private $processInformation;
+ private $outputDisabled = false;
+ private $stdout;
+ private $stderr;
+ private $process;
+ private $status = self::STATUS_READY;
+ private $incrementalOutputOffset = 0;
+ private $incrementalErrorOutputOffset = 0;
+ private $tty = false;
+ private $pty;
+
+ private $useFileHandles = false;
+ /** @var PipesInterface */
+ private $processPipes;
+
+ private $latestSignal;
+
+ private static $sigchild;
+
+ /**
+ * Exit codes translation table.
+ *
+ * User-defined errors must use exit codes in the 64-113 range.
+ */
+ public static $exitCodes = [
+ 0 => 'OK',
+ 1 => 'General error',
+ 2 => 'Misuse of shell builtins',
+
+ 126 => 'Invoked command cannot execute',
+ 127 => 'Command not found',
+ 128 => 'Invalid exit argument',
+
+ // signals
+ 129 => 'Hangup',
+ 130 => 'Interrupt',
+ 131 => 'Quit and dump core',
+ 132 => 'Illegal instruction',
+ 133 => 'Trace/breakpoint trap',
+ 134 => 'Process aborted',
+ 135 => 'Bus error: "access to undefined portion of memory object"',
+ 136 => 'Floating point exception: "erroneous arithmetic operation"',
+ 137 => 'Kill (terminate immediately)',
+ 138 => 'User-defined 1',
+ 139 => 'Segmentation violation',
+ 140 => 'User-defined 2',
+ 141 => 'Write to pipe with no one reading',
+ 142 => 'Signal raised by alarm',
+ 143 => 'Termination (request to terminate)',
+ // 144 - not defined
+ 145 => 'Child process terminated, stopped (or continued*)',
+ 146 => 'Continue if stopped',
+ 147 => 'Stop executing temporarily',
+ 148 => 'Terminal stop signal',
+ 149 => 'Background process attempting to read from tty ("in")',
+ 150 => 'Background process attempting to write to tty ("out")',
+ 151 => 'Urgent data available on socket',
+ 152 => 'CPU time limit exceeded',
+ 153 => 'File size limit exceeded',
+ 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+ 155 => 'Profiling timer expired',
+ // 156 - not defined
+ 157 => 'Pollable event',
+ // 158 - not defined
+ 159 => 'Bad syscall',
+ ];
+
+ /**
+ * @param array $command The command to run and its arguments listed as separate entries
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public function __construct($command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ if (!\function_exists('proc_open')) {
+ throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
+ }
+
+ if (!\is_array($command)) {
+ @trigger_error(sprintf('Passing a command as string when creating a "%s" instance is deprecated since Symfony 4.2, pass it as an array of its arguments instead, or use the "Process::fromShellCommandline()" constructor if you need features provided by the shell.', __CLASS__), E_USER_DEPRECATED);
+ }
+
+ $this->commandline = $command;
+ $this->cwd = $cwd;
+
+ // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
+ // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
+ // @see : https://bugs.php.net/51800
+ // @see : https://bugs.php.net/50524
+ if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
+ $this->cwd = getcwd();
+ }
+ if (null !== $env) {
+ $this->setEnv($env);
+ }
+
+ $this->setInput($input);
+ $this->setTimeout($timeout);
+ $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
+ $this->pty = false;
+ }
+
+ /**
+ * Creates a Process instance as a command-line to be run in a shell wrapper.
+ *
+ * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
+ * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
+ * shell wrapper and not to your commands.
+ *
+ * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
+ * This will save escaping values, which is not portable nor secure anyway:
+ *
+ * $process = Process::fromShellCommandline('my_command "$MY_VAR"');
+ * $process->run(null, ['MY_VAR' => $theValue]);
+ *
+ * @param string $command The command line to pass to the shell of the OS
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @return static
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ $process = new static([], $cwd, $env, $input, $timeout);
+ $process->commandline = $command;
+
+ return $process;
+ }
+
+ public function __destruct()
+ {
+ $this->stop(0);
+ }
+
+ public function __clone()
+ {
+ $this->resetProcessData();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * The callback receives the type of output (out or err) and
+ * some bytes from the output in real-time. It allows to have feedback
+ * from the independent process during execution.
+ *
+ * The STDOUT and STDERR are also available after the process is finished
+ * via the getOutput() and getErrorOutput() methods.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return int The exit status code
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException In case a callback is provided and output has been disabled
+ *
+ * @final
+ */
+ public function run(callable $callback = null, array $env = []): int
+ {
+ $this->start($callback, $env);
+
+ return $this->wait();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * This is identical to run() except that an exception is thrown if the process
+ * exits with a non-zero exit code.
+ *
+ * @return $this
+ *
+ * @throws ProcessFailedException if the process didn't terminate successfully
+ *
+ * @final
+ */
+ public function mustRun(callable $callback = null, array $env = [])
+ {
+ if (0 !== $this->run($callback, $env)) {
+ throw new ProcessFailedException($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Starts the process and returns after writing the input to STDIN.
+ *
+ * This method blocks until all STDIN data is sent to the process then it
+ * returns while the process runs in the background.
+ *
+ * The termination of the process can be awaited with wait().
+ *
+ * The callback receives the type of output (out or err) and some bytes from
+ * the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ * @throws LogicException In case a callback is provided and output has been disabled
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+
+ $this->resetProcessData();
+ $this->starttime = $this->lastOutputTime = microtime(true);
+ $this->callback = $this->buildCallback($callback);
+ $this->hasCallback = null !== $callback;
+ $descriptors = $this->getDescriptors();
+
+ if (\is_array($commandline = $this->commandline)) {
+ $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
+
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ // exec is mandatory to deal with sending a signal to the process
+ $commandline = 'exec '.$commandline;
+ }
+ }
+
+ if ($this->env) {
+ $env += $this->env;
+ }
+ $env += $this->getDefaultEnv();
+
+ $options = ['suppress_errors' => true];
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $options['bypass_shell'] = true;
+ $commandline = $this->prepareWindowsCommandLine($commandline, $env);
+ } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
+ // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
+ $descriptors[3] = ['pipe', 'w'];
+
+ // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
+ $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
+ $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
+
+ // Workaround for the bug, when PTS functionality is enabled.
+ // @see : https://bugs.php.net/69442
+ $ptsWorkaround = fopen(__FILE__, 'r');
+ }
+
+ $envPairs = [];
+ foreach ($env as $k => $v) {
+ if (false !== $v) {
+ $envPairs[] = $k.'='.$v;
+ }
+ }
+
+ if (!is_dir($this->cwd)) {
+ throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
+ }
+
+ $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options);
+
+ if (!\is_resource($this->process)) {
+ throw new RuntimeException('Unable to launch a new process.');
+ }
+ $this->status = self::STATUS_STARTED;
+
+ if (isset($descriptors[3])) {
+ $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
+ }
+
+ if ($this->tty) {
+ return;
+ }
+
+ $this->updateStatus(false);
+ $this->checkTimeout();
+ }
+
+ /**
+ * Restarts the process.
+ *
+ * Be warned that the process is cloned before being started.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return static
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ *
+ * @see start()
+ *
+ * @final
+ */
+ public function restart(callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+
+ $process = clone $this;
+ $process->start($callback, $env);
+
+ return $process;
+ }
+
+ /**
+ * Waits for the process to terminate.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A valid PHP callback
+ *
+ * @return int The exitcode of the process
+ *
+ * @throws RuntimeException When process timed out
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException When process is not yet started
+ */
+ public function wait(callable $callback = null)
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->updateStatus(false);
+
+ if (null !== $callback) {
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"');
+ }
+ $this->callback = $this->buildCallback($callback);
+ }
+
+ do {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+ } while ($running);
+
+ while ($this->isRunning()) {
+ $this->checkTimeout();
+ usleep(1000);
+ }
+
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+ throw new ProcessSignaledException($this);
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Waits until the callback returns true.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @throws RuntimeException When process timed out
+ * @throws LogicException When process is not yet started
+ */
+ public function waitUntil(callable $callback): bool
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+ $this->updateStatus(false);
+
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
+ }
+ $callback = $this->buildCallback($callback);
+
+ $ready = false;
+ while (true) {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ foreach ($output as $type => $data) {
+ if (3 !== $type) {
+ $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ if ($ready) {
+ return true;
+ }
+ if (!$running) {
+ return false;
+ }
+
+ usleep(1000);
+ }
+ }
+
+ /**
+ * Returns the Pid (process identifier), if applicable.
+ *
+ * @return int|null The process id if running, null otherwise
+ */
+ public function getPid()
+ {
+ return $this->isRunning() ? $this->processInformation['pid'] : null;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ public function signal($signal)
+ {
+ $this->doSignal($signal, true);
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ * @throws LogicException if an idle timeout is set
+ */
+ public function disableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Disabling output while the process is running is not possible.');
+ }
+ if (null !== $this->idleTimeout) {
+ throw new LogicException('Output can not be disabled while an idle timeout is set.');
+ }
+
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ */
+ public function enableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Enabling output while the process is running is not possible.');
+ }
+
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns true in case the output is disabled, false otherwise.
+ *
+ * @return bool
+ */
+ public function isOutputDisabled()
+ {
+ return $this->outputDisabled;
+ }
+
+ /**
+ * Returns the current output of the process (STDOUT).
+ *
+ * @return string The process output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the output incrementally.
+ *
+ * In comparison with the getOutput method which always return the whole
+ * output, this one returns the new output since the last call.
+ *
+ * @return string The process output since the last call
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+ $this->incrementalOutputOffset = ftell($this->stdout);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
+ *
+ * @param int $flags A bit field of Process::ITER_* flags
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ *
+ * @return \Generator
+ */
+ public function getIterator($flags = 0)
+ {
+ $this->readPipesForOutput(__FUNCTION__, false);
+
+ $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
+ $blocking = !(self::ITER_NON_BLOCKING & $flags);
+ $yieldOut = !(self::ITER_SKIP_OUT & $flags);
+ $yieldErr = !(self::ITER_SKIP_ERR & $flags);
+
+ while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
+ if ($yieldOut) {
+ $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+
+ if (isset($out[0])) {
+ if ($clearOutput) {
+ $this->clearOutput();
+ } else {
+ $this->incrementalOutputOffset = ftell($this->stdout);
+ }
+
+ yield self::OUT => $out;
+ }
+ }
+
+ if ($yieldErr) {
+ $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+
+ if (isset($err[0])) {
+ if ($clearOutput) {
+ $this->clearErrorOutput();
+ } else {
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+ }
+
+ yield self::ERR => $err;
+ }
+ }
+
+ if (!$blocking && !isset($out[0]) && !isset($err[0])) {
+ yield self::OUT => '';
+ }
+
+ $this->checkTimeout();
+ $this->readPipesForOutput(__FUNCTION__, $blocking);
+ }
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearOutput()
+ {
+ ftruncate($this->stdout, 0);
+ fseek($this->stdout, 0);
+ $this->incrementalOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current error output of the process (STDERR).
+ *
+ * @return string The process error output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the errorOutput incrementally.
+ *
+ * In comparison with the getErrorOutput method which always return the
+ * whole error output, this one returns the new error output since the last
+ * call.
+ *
+ * @return string The process error output since the last call
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearErrorOutput()
+ {
+ ftruncate($this->stderr, 0);
+ fseek($this->stderr, 0);
+ $this->incrementalErrorOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the exit code returned by the process.
+ *
+ * @return int|null The exit status code, null if the Process is not terminated
+ */
+ public function getExitCode()
+ {
+ $this->updateStatus(false);
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns a string representation for the exit code returned by the process.
+ *
+ * This method relies on the Unix exit code status standardization
+ * and might not be relevant for other operating systems.
+ *
+ * @return string|null A string representation for the exit status code, null if the Process is not terminated
+ *
+ * @see http://tldp.org/LDP/abs/html/exitcodes.html
+ * @see http://en.wikipedia.org/wiki/Unix_signal
+ */
+ public function getExitCodeText()
+ {
+ if (null === $exitcode = $this->getExitCode()) {
+ return null;
+ }
+
+ return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
+ }
+
+ /**
+ * Checks if the process ended successfully.
+ *
+ * @return bool true if the process ended successfully, false otherwise
+ */
+ public function isSuccessful()
+ {
+ return 0 === $this->getExitCode();
+ }
+
+ /**
+ * Returns true if the child process has been terminated by an uncaught signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenSignaled()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['signaled'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to terminate its execution.
+ *
+ * It is only meaningful if hasBeenSignaled() returns true.
+ *
+ * @return int
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getTermSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+ }
+
+ return $this->processInformation['termsig'];
+ }
+
+ /**
+ * Returns true if the child process has been stopped by a signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenStopped()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopped'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to stop its execution.
+ *
+ * It is only meaningful if hasBeenStopped() returns true.
+ *
+ * @return int
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getStopSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopsig'];
+ }
+
+ /**
+ * Checks if the process is currently running.
+ *
+ * @return bool true if the process is currently running, false otherwise
+ */
+ public function isRunning()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return false;
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['running'];
+ }
+
+ /**
+ * Checks if the process has been started with no regard to the current state.
+ *
+ * @return bool true if status is ready, false otherwise
+ */
+ public function isStarted()
+ {
+ return self::STATUS_READY != $this->status;
+ }
+
+ /**
+ * Checks if the process is terminated.
+ *
+ * @return bool true if process is terminated, false otherwise
+ */
+ public function isTerminated()
+ {
+ $this->updateStatus(false);
+
+ return self::STATUS_TERMINATED == $this->status;
+ }
+
+ /**
+ * Gets the process status.
+ *
+ * The status is one of: ready, started, terminated.
+ *
+ * @return string The current process status
+ */
+ public function getStatus()
+ {
+ $this->updateStatus(false);
+
+ return $this->status;
+ }
+
+ /**
+ * Stops the process.
+ *
+ * @param int|float $timeout The timeout in seconds
+ * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
+ *
+ * @return int|null The exit-code of the process or null if it's not running
+ */
+ public function stop($timeout = 10, $signal = null)
+ {
+ $timeoutMicro = microtime(true) + $timeout;
+ if ($this->isRunning()) {
+ // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
+ $this->doSignal(15, false);
+ do {
+ usleep(1000);
+ } while ($this->isRunning() && microtime(true) < $timeoutMicro);
+
+ if ($this->isRunning()) {
+ // Avoid exception here: process is supposed to be running, but it might have stopped just
+ // after this line. In any case, let's silently discard the error, we cannot do anything.
+ $this->doSignal($signal ?: 9, false);
+ }
+ }
+
+ if ($this->isRunning()) {
+ if (isset($this->fallbackStatus['pid'])) {
+ unset($this->fallbackStatus['pid']);
+
+ return $this->stop(0, $signal);
+ }
+ $this->close();
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Adds a line to the STDOUT stream.
+ *
+ * @internal
+ */
+ public function addOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stdout, 0, SEEK_END);
+ fwrite($this->stdout, $line);
+ fseek($this->stdout, $this->incrementalOutputOffset);
+ }
+
+ /**
+ * Adds a line to the STDERR stream.
+ *
+ * @internal
+ */
+ public function addErrorOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stderr, 0, SEEK_END);
+ fwrite($this->stderr, $line);
+ fseek($this->stderr, $this->incrementalErrorOutputOffset);
+ }
+
+ /**
+ * Gets the command line to be executed.
+ *
+ * @return string The command to execute
+ */
+ public function getCommandLine()
+ {
+ return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
+ }
+
+ /**
+ * Sets the command line to be executed.
+ *
+ * @param string|array $commandline The command to execute
+ *
+ * @return $this
+ *
+ * @deprecated since Symfony 4.2.
+ */
+ public function setCommandLine($commandline)
+ {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->commandline = $commandline;
+
+ return $this;
+ }
+
+ /**
+ * Gets the process timeout (max. runtime).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Gets the process idle timeout (max. time since last output).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getIdleTimeout()
+ {
+ return $this->idleTimeout;
+ }
+
+ /**
+ * Sets the process timeout (max. runtime) in seconds.
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setTimeout($timeout)
+ {
+ $this->timeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process idle timeout (max. time since last output).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return $this
+ *
+ * @throws LogicException if the output is disabled
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setIdleTimeout($timeout)
+ {
+ if (null !== $timeout && $this->outputDisabled) {
+ throw new LogicException('Idle timeout can not be set while the output is disabled.');
+ }
+
+ $this->idleTimeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Enables or disables the TTY mode.
+ *
+ * @param bool $tty True to enabled and false to disable
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the TTY mode is not supported
+ */
+ public function setTty($tty)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
+ throw new RuntimeException('TTY mode is not supported on Windows platform.');
+ }
+
+ if ($tty && !self::isTtySupported()) {
+ throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
+ }
+
+ $this->tty = (bool) $tty;
+
+ return $this;
+ }
+
+ /**
+ * Checks if the TTY mode is enabled.
+ *
+ * @return bool true if the TTY mode is enabled, false otherwise
+ */
+ public function isTty()
+ {
+ return $this->tty;
+ }
+
+ /**
+ * Sets PTY mode.
+ *
+ * @param bool $bool
+ *
+ * @return $this
+ */
+ public function setPty($bool)
+ {
+ $this->pty = (bool) $bool;
+
+ return $this;
+ }
+
+ /**
+ * Returns PTY state.
+ *
+ * @return bool
+ */
+ public function isPty()
+ {
+ return $this->pty;
+ }
+
+ /**
+ * Gets the working directory.
+ *
+ * @return string|null The current working directory or null on failure
+ */
+ public function getWorkingDirectory()
+ {
+ if (null === $this->cwd) {
+ // getcwd() will return false if any one of the parent directories does not have
+ // the readable or search mode set, even if the current directory does
+ return getcwd() ?: null;
+ }
+
+ return $this->cwd;
+ }
+
+ /**
+ * Sets the current working directory.
+ *
+ * @param string $cwd The new working directory
+ *
+ * @return $this
+ */
+ public function setWorkingDirectory($cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Gets the environment variables.
+ *
+ * @return array The current environment variables
+ */
+ public function getEnv()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Sets the environment variables.
+ *
+ * Each environment variable value should be a string.
+ * If it is an array, the variable is ignored.
+ * If it is false or null, it will be removed when
+ * env vars are otherwise inherited.
+ *
+ * That happens in PHP when 'argv' is registered into
+ * the $_ENV array for instance.
+ *
+ * @param array $env The new environment variables
+ *
+ * @return $this
+ */
+ public function setEnv(array $env)
+ {
+ // Process can not handle env values that are arrays
+ $env = array_filter($env, function ($value) {
+ return !\is_array($value);
+ });
+
+ $this->env = $env;
+
+ return $this;
+ }
+
+ /**
+ * Gets the Process input.
+ *
+ * @return resource|string|\Iterator|null The Process input
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Sets the input.
+ *
+ * This content will be passed to the underlying process standard input.
+ *
+ * @param string|int|float|bool|resource|\Traversable|null $input The content
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is running
+ */
+ public function setInput($input)
+ {
+ if ($this->isRunning()) {
+ throw new LogicException('Input can not be set while the process is running.');
+ }
+
+ $this->input = ProcessUtils::validateInput(__METHOD__, $input);
+
+ return $this;
+ }
+
+ /**
+ * Sets whether environment variables will be inherited or not.
+ *
+ * @param bool $inheritEnv
+ *
+ * @return $this
+ */
+ public function inheritEnvironmentVariables($inheritEnv = true)
+ {
+ if (!$inheritEnv) {
+ throw new InvalidArgumentException('Not inheriting environment variables is not supported.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Performs a check between the timeout definition and the time the process started.
+ *
+ * In case you run a background process (with the start method), you should
+ * trigger this method regularly to ensure the process timeout
+ *
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function checkTimeout()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+ }
+
+ if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
+ }
+ }
+
+ /**
+ * Returns whether TTY is supported on the current operating system.
+ */
+ public static function isTtySupported(): bool
+ {
+ static $isTtySupported;
+
+ if (null === $isTtySupported) {
+ $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
+ }
+
+ return $isTtySupported;
+ }
+
+ /**
+ * Returns whether PTY is supported on the current operating system.
+ *
+ * @return bool
+ */
+ public static function isPtySupported()
+ {
+ static $result;
+
+ if (null !== $result) {
+ return $result;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return $result = false;
+ }
+
+ return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
+ }
+
+ /**
+ * Creates the descriptors needed by the proc_open.
+ */
+ private function getDescriptors(): array
+ {
+ if ($this->input instanceof \Iterator) {
+ $this->input->rewind();
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
+ } else {
+ $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
+ }
+
+ return $this->processPipes->getDescriptors();
+ }
+
+ /**
+ * Builds up the callback used by wait().
+ *
+ * The callbacks adds all occurred output to the specific buffer and calls
+ * the user callback (if present) with the received output.
+ *
+ * @param callable|null $callback The user defined PHP callback
+ *
+ * @return \Closure A PHP closure
+ */
+ protected function buildCallback(callable $callback = null)
+ {
+ if ($this->outputDisabled) {
+ return function ($type, $data) use ($callback): bool {
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ $out = self::OUT;
+
+ return function ($type, $data) use ($callback, $out): bool {
+ if ($out == $type) {
+ $this->addOutput($data);
+ } else {
+ $this->addErrorOutput($data);
+ }
+
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ /**
+ * Updates the status of the process, reads pipes.
+ *
+ * @param bool $blocking Whether to use a blocking read call
+ */
+ protected function updateStatus($blocking)
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ $this->processInformation = proc_get_status($this->process);
+ $running = $this->processInformation['running'];
+
+ $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ if ($this->fallbackStatus && $this->isSigchildEnabled()) {
+ $this->processInformation = $this->fallbackStatus + $this->processInformation;
+ }
+
+ if (!$running) {
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
+ *
+ * @return bool
+ */
+ protected function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!\function_exists('phpinfo')) {
+ return self::$sigchild = false;
+ }
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+
+ return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Reads pipes for the freshest output.
+ *
+ * @param string $caller The name of the method that needs fresh outputs
+ * @param bool $blocking Whether to use blocking calls or not
+ *
+ * @throws LogicException in case output has been disabled or process is not started
+ */
+ private function readPipesForOutput(string $caller, bool $blocking = false)
+ {
+ if ($this->outputDisabled) {
+ throw new LogicException('Output has been disabled.');
+ }
+
+ $this->requireProcessIsStarted($caller);
+
+ $this->updateStatus($blocking);
+ }
+
+ /**
+ * Validates and returns the filtered timeout.
+ *
+ * @throws InvalidArgumentException if the given timeout is a negative number
+ */
+ private function validateTimeout(?float $timeout): ?float
+ {
+ $timeout = (float) $timeout;
+
+ if (0.0 === $timeout) {
+ $timeout = null;
+ } elseif ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ return $timeout;
+ }
+
+ /**
+ * Reads pipes, executes callback.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close file handles or not
+ */
+ private function readPipes(bool $blocking, bool $close)
+ {
+ $result = $this->processPipes->readAndWrite($blocking, $close);
+
+ $callback = $this->callback;
+ foreach ($result as $type => $data) {
+ if (3 !== $type) {
+ $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ }
+
+ /**
+ * Closes process resource, closes file handles, sets the exitcode.
+ *
+ * @return int The exitcode
+ */
+ private function close(): int
+ {
+ $this->processPipes->close();
+ if (\is_resource($this->process)) {
+ proc_close($this->process);
+ }
+ $this->exitcode = $this->processInformation['exitcode'];
+ $this->status = self::STATUS_TERMINATED;
+
+ if (-1 === $this->exitcode) {
+ if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
+ // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
+ $this->exitcode = 128 + $this->processInformation['termsig'];
+ } elseif ($this->isSigchildEnabled()) {
+ $this->processInformation['signaled'] = true;
+ $this->processInformation['termsig'] = -1;
+ }
+ }
+
+ // Free memory from self-reference callback created by buildCallback
+ // Doing so in other contexts like __destruct or by garbage collector is ineffective
+ // Now pipes are closed, so the callback is no longer necessary
+ $this->callback = null;
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Resets data related to the latest run of the process.
+ */
+ private function resetProcessData()
+ {
+ $this->starttime = null;
+ $this->callback = null;
+ $this->exitcode = null;
+ $this->fallbackStatus = [];
+ $this->processInformation = null;
+ $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
+ $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
+ $this->process = null;
+ $this->latestSignal = null;
+ $this->status = self::STATUS_READY;
+ $this->incrementalOutputOffset = 0;
+ $this->incrementalErrorOutputOffset = 0;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ * @param bool $throwException Whether to throw exception in case signal failed
+ *
+ * @return bool True if the signal was sent successfully, false otherwise
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ private function doSignal(int $signal, bool $throwException): bool
+ {
+ if (null === $pid = $this->getPid()) {
+ if ($throwException) {
+ throw new LogicException('Can not send signal on a non running process.');
+ }
+
+ return false;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
+ if ($exitCode && $this->isRunning()) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
+ }
+
+ return false;
+ }
+ } else {
+ if (!$this->isSigchildEnabled()) {
+ $ok = @proc_terminate($this->process, $signal);
+ } elseif (\function_exists('posix_kill')) {
+ $ok = @posix_kill($pid, $signal);
+ } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
+ $ok = false === fgets($pipes[2]);
+ }
+ if (!$ok) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
+ }
+
+ return false;
+ }
+ }
+
+ $this->latestSignal = $signal;
+ $this->fallbackStatus['signaled'] = true;
+ $this->fallbackStatus['exitcode'] = -1;
+ $this->fallbackStatus['termsig'] = $this->latestSignal;
+
+ return true;
+ }
+
+ private function prepareWindowsCommandLine(string $cmd, array &$env)
+ {
+ $uid = uniqid('', true);
+ $varCount = 0;
+ $varCache = [];
+ $cmd = preg_replace_callback(
+ '/"(?:(
+ [^"%!^]*+
+ (?:
+ (?: !LF! | "(?:\^[%!^])?+" )
+ [^"%!^]*+
+ )++
+ ) | [^"]*+ )"/x',
+ function ($m) use (&$env, &$varCache, &$varCount, $uid) {
+ if (!isset($m[1])) {
+ return $m[0];
+ }
+ if (isset($varCache[$m[0]])) {
+ return $varCache[$m[0]];
+ }
+ if (false !== strpos($value = $m[1], "\0")) {
+ $value = str_replace("\0", '?', $value);
+ }
+ if (false === strpbrk($value, "\"%!\n")) {
+ return '"'.$value.'"';
+ }
+
+ $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
+ $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
+ $var = $uid.++$varCount;
+
+ $env[$var] = $value;
+
+ return $varCache[$m[0]] = '!'.$var.'!';
+ },
+ $cmd
+ );
+
+ $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
+ foreach ($this->processPipes->getFiles() as $offset => $filename) {
+ $cmd .= ' '.$offset.'>"'.$filename.'"';
+ }
+
+ return $cmd;
+ }
+
+ /**
+ * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
+ *
+ * @throws LogicException if the process has not run
+ */
+ private function requireProcessIsStarted(string $functionName)
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
+ *
+ * @throws LogicException if the process is not yet terminated
+ */
+ private function requireProcessIsTerminated(string $functionName)
+ {
+ if (!$this->isTerminated()) {
+ throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Escapes a string to be used as a shell argument.
+ */
+ private function escapeArgument(?string $argument): string
+ {
+ if ('' === $argument || null === $argument) {
+ return '""';
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ return "'".str_replace("'", "'\\''", $argument)."'";
+ }
+ if (false !== strpos($argument, "\0")) {
+ $argument = str_replace("\0", '?', $argument);
+ }
+ if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
+ return $argument;
+ }
+ $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
+
+ return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
+ }
+
+ private function getDefaultEnv()
+ {
+ $env = [];
+
+ foreach ($_SERVER as $k => $v) {
+ if (\is_string($v) && false !== $v = getenv($k)) {
+ $env[$k] = $v;
+ }
+ }
+
+ foreach ($_ENV as $k => $v) {
+ if (\is_string($v)) {
+ $env[$k] = $v;
+ }
+ }
+
+ return $env;
+ }
+}
diff --git a/vendor/symfony/process/ProcessUtils.php b/vendor/symfony/process/ProcessUtils.php
new file mode 100755
index 0000000..2f9c4be
--- /dev/null
+++ b/vendor/symfony/process/ProcessUtils.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * ProcessUtils is a bunch of utility methods.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Martin Hasoň
+ */
+class ProcessUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Validates and normalizes a Process input.
+ *
+ * @param string $caller The name of method call that validates the input
+ * @param mixed $input The input to validate
+ *
+ * @return mixed The validated input
+ *
+ * @throws InvalidArgumentException In case the input is not valid
+ */
+ public static function validateInput($caller, $input)
+ {
+ if (null !== $input) {
+ if (\is_resource($input)) {
+ return $input;
+ }
+ if (\is_string($input)) {
+ return $input;
+ }
+ if (is_scalar($input)) {
+ return (string) $input;
+ }
+ if ($input instanceof Process) {
+ return $input->getIterator($input::ITER_SKIP_ERR);
+ }
+ if ($input instanceof \Iterator) {
+ return $input;
+ }
+ if ($input instanceof \Traversable) {
+ return new \IteratorIterator($input);
+ }
+
+ throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller));
+ }
+
+ return $input;
+ }
+}
diff --git a/vendor/symfony/process/README.md b/vendor/symfony/process/README.md
new file mode 100755
index 0000000..b7ca5b4
--- /dev/null
+++ b/vendor/symfony/process/README.md
@@ -0,0 +1,13 @@
+Process Component
+=================
+
+The Process component executes commands in sub-processes.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/process.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/process/Tests/ErrorProcessInitiator.php b/vendor/symfony/process/Tests/ErrorProcessInitiator.php
new file mode 100755
index 0000000..c37aeb5
--- /dev/null
+++ b/vendor/symfony/process/Tests/ErrorProcessInitiator.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Process;
+
+require \dirname(__DIR__).'/vendor/autoload.php';
+
+list('e' => $php) = getopt('e:') + ['e' => 'php'];
+
+try {
+ $process = new Process("exec $php -r \"echo 'ready'; trigger_error('error', E_USER_ERROR);\"");
+ $process->start();
+ $process->setTimeout(0.5);
+ while (false === strpos($process->getOutput(), 'ready')) {
+ usleep(1000);
+ }
+ $process->signal(SIGSTOP);
+ $process->wait();
+
+ return $process->getExitCode();
+} catch (ProcessTimedOutException $t) {
+ echo $t->getMessage().PHP_EOL;
+
+ return 1;
+}
diff --git a/vendor/symfony/process/Tests/ExecutableFinderTest.php b/vendor/symfony/process/Tests/ExecutableFinderTest.php
new file mode 100755
index 0000000..a400273
--- /dev/null
+++ b/vendor/symfony/process/Tests/ExecutableFinderTest.php
@@ -0,0 +1,178 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\ExecutableFinder;
+
+/**
+ * @author Chris Smith
+ */
+class ExecutableFinderTest extends TestCase
+{
+ private $path;
+
+ protected function tearDown(): void
+ {
+ if ($this->path) {
+ // Restore path if it was changed.
+ putenv('PATH='.$this->path);
+ }
+ }
+
+ private function setPath($path)
+ {
+ $this->path = getenv('PATH');
+ putenv('PATH='.$path);
+ }
+
+ public function testFind()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath(\dirname(PHP_BINARY));
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindWithDefault()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $expected = 'defaultValue';
+
+ $this->setPath('');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find('foo', $expected);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function testFindWithNullAsDefault()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $finder = new ExecutableFinder();
+
+ $result = $finder->find('foo');
+
+ $this->assertNull($result);
+ }
+
+ public function testFindWithExtraDirs()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $extraDirs = [\dirname(PHP_BINARY)];
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindWithOpenBaseDir()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->iniSet('open_basedir', \dirname(PHP_BINARY).PATH_SEPARATOR.'/');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindProcessInOpenBasedir()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ $this->setPath('');
+ $this->iniSet('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), false);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ /**
+ * @requires PHP 5.4
+ */
+ public function testFindBatchExecutableOnWindows()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Can be only tested on windows');
+ }
+
+ $target = tempnam(sys_get_temp_dir(), 'example-windows-executable');
+
+ touch($target);
+ touch($target.'.BAT');
+
+ $this->assertFalse(is_executable($target));
+
+ $this->setPath(sys_get_temp_dir());
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find(basename($target), false);
+
+ unlink($target);
+ unlink($target.'.BAT');
+
+ $this->assertSamePath($target.'.BAT', $result);
+ }
+
+ private function assertSamePath($expected, $tested)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->assertEquals(strtolower($expected), strtolower($tested));
+ } else {
+ $this->assertEquals($expected, $tested);
+ }
+ }
+
+ private function getPhpBinaryName()
+ {
+ return basename(PHP_BINARY, '\\' === \DIRECTORY_SEPARATOR ? '.exe' : '');
+ }
+}
diff --git a/vendor/symfony/process/Tests/KillableProcessWithOutput.php b/vendor/symfony/process/Tests/KillableProcessWithOutput.php
new file mode 100755
index 0000000..28a6a27
--- /dev/null
+++ b/vendor/symfony/process/Tests/KillableProcessWithOutput.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+$outputs = [
+ 'First iteration output',
+ 'Second iteration output',
+ 'One more iteration output',
+ 'This took more time',
+];
+
+$iterationTime = 10000;
+
+foreach ($outputs as $output) {
+ usleep($iterationTime);
+ $iterationTime *= 10;
+ echo $output."\n";
+}
diff --git a/vendor/symfony/process/Tests/NonStopableProcess.php b/vendor/symfony/process/Tests/NonStopableProcess.php
new file mode 100755
index 0000000..5643259
--- /dev/null
+++ b/vendor/symfony/process/Tests/NonStopableProcess.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds.
+ *
+ * @args duration Run this script with a custom duration
+ *
+ * @example `php NonStopableProcess.php 42` will run the script for 42 seconds
+ */
+function handleSignal($signal)
+{
+ switch ($signal) {
+ case SIGTERM:
+ $name = 'SIGTERM';
+ break;
+ case SIGINT:
+ $name = 'SIGINT';
+ break;
+ default:
+ $name = $signal.' (unknown)';
+ break;
+ }
+
+ echo "signal $name\n";
+}
+
+pcntl_signal(SIGTERM, 'handleSignal');
+pcntl_signal(SIGINT, 'handleSignal');
+
+echo 'received ';
+
+$duration = isset($argv[1]) ? (int) $argv[1] : 3;
+$start = microtime(true);
+
+while ($duration > (microtime(true) - $start)) {
+ usleep(10000);
+ pcntl_signal_dispatch();
+}
diff --git a/vendor/symfony/process/Tests/PhpExecutableFinderTest.php b/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
new file mode 100755
index 0000000..338bb4e
--- /dev/null
+++ b/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+
+/**
+ * @author Robert Schönthal
+ */
+class PhpExecutableFinderTest extends TestCase
+{
+ /**
+ * tests find() with the constant PHP_BINARY.
+ */
+ public function testFind()
+ {
+ $f = new PhpExecutableFinder();
+
+ $current = PHP_BINARY;
+ $args = 'phpdbg' === \PHP_SAPI ? ' -qrr' : '';
+
+ $this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP');
+ $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');
+ }
+
+ /**
+ * tests find() with the env var PHP_PATH.
+ */
+ public function testFindArguments()
+ {
+ $f = new PhpExecutableFinder();
+
+ if ('phpdbg' === \PHP_SAPI) {
+ $this->assertEquals($f->findArguments(), ['-qrr'], '::findArguments() returns phpdbg arguments');
+ } else {
+ $this->assertEquals($f->findArguments(), [], '::findArguments() returns no arguments');
+ }
+ }
+}
diff --git a/vendor/symfony/process/Tests/PhpProcessTest.php b/vendor/symfony/process/Tests/PhpProcessTest.php
new file mode 100755
index 0000000..b7b21eb
--- /dev/null
+++ b/vendor/symfony/process/Tests/PhpProcessTest.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\PhpProcess;
+
+class PhpProcessTest extends TestCase
+{
+ public function testNonBlockingWorks()
+ {
+ $expected = 'hello world!';
+ $process = new PhpProcess(<<start();
+ $process->wait();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCommandLine()
+ {
+ $process = new PhpProcess(<<<'PHP'
+getCommandLine();
+
+ $process->start();
+ $this->assertStringContainsString($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');
+
+ $process->wait();
+ $this->assertStringContainsString($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');
+
+ $this->assertSame(PHP_VERSION.\PHP_SAPI, $process->getOutput());
+ }
+
+ public function testPassingPhpExplicitly()
+ {
+ $finder = new PhpExecutableFinder();
+ $php = array_merge([$finder->find(false)], $finder->findArguments());
+
+ $expected = 'hello world!';
+ $script = <<run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+}
diff --git a/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php b/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
new file mode 100755
index 0000000..9ea8981
--- /dev/null
+++ b/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+define('ERR_SELECT_FAILED', 1);
+define('ERR_TIMEOUT', 2);
+define('ERR_READ_FAILED', 3);
+define('ERR_WRITE_FAILED', 4);
+
+$read = [STDIN];
+$write = [STDOUT, STDERR];
+
+stream_set_blocking(STDIN, 0);
+stream_set_blocking(STDOUT, 0);
+stream_set_blocking(STDERR, 0);
+
+$out = $err = '';
+while ($read || $write) {
+ $r = $read;
+ $w = $write;
+ $e = null;
+ $n = stream_select($r, $w, $e, 5);
+
+ if (false === $n) {
+ die(ERR_SELECT_FAILED);
+ } elseif ($n < 1) {
+ die(ERR_TIMEOUT);
+ }
+
+ if (in_array(STDOUT, $w) && strlen($out) > 0) {
+ $written = fwrite(STDOUT, (string) $out, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $out = (string) substr($out, $written);
+ }
+ if (null === $read && '' === $out) {
+ $write = array_diff($write, [STDOUT]);
+ }
+
+ if (in_array(STDERR, $w) && strlen($err) > 0) {
+ $written = fwrite(STDERR, (string) $err, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $err = (string) substr($err, $written);
+ }
+ if (null === $read && '' === $err) {
+ $write = array_diff($write, [STDERR]);
+ }
+
+ if ($r) {
+ $str = fread(STDIN, 32768);
+ if (false !== $str) {
+ $out .= $str;
+ $err .= $str;
+ }
+ if (false === $str || feof(STDIN)) {
+ $read = null;
+ if (!feof(STDIN)) {
+ die(ERR_READ_FAILED);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php b/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
new file mode 100755
index 0000000..f820430
--- /dev/null
+++ b/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
@@ -0,0 +1,133 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+
+/**
+ * @author Sebastian Marek
+ */
+class ProcessFailedExceptionTest extends TestCase
+{
+ /**
+ * tests ProcessFailedException throws exception if the process was successful.
+ */
+ public function testProcessFailedExceptionThrowsException()
+ {
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful'])->setConstructorArgs([['php']])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(true);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Expected a failed process, but the given process was successful.');
+
+ new ProcessFailedException($process);
+ }
+
+ /**
+ * tests ProcessFailedException uses information from process output
+ * to generate exception message.
+ */
+ public function testProcessFailedExceptionPopulatesInformationFromProcessOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $output = 'Command output';
+ $errorOutput = 'FATAL: Unexpected error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(false);
+
+ $process->expects($this->once())
+ ->method('getOutput')
+ ->willReturn($output);
+
+ $process->expects($this->once())
+ ->method('getErrorOutput')
+ ->willReturn($errorOutput);
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->willReturn($exitCode);
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->willReturn($exitText);
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->willReturn(false);
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->willReturn($workingDirectory);
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}",
+ str_replace("'php'", 'php', $exception->getMessage())
+ );
+ }
+
+ /**
+ * Tests that ProcessFailedException does not extract information from
+ * process output if it was previously disabled.
+ */
+ public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->willReturn(false);
+
+ $process->expects($this->never())
+ ->method('getOutput');
+
+ $process->expects($this->never())
+ ->method('getErrorOutput');
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->willReturn($exitCode);
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->willReturn($exitText);
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->willReturn(true);
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->willReturn($workingDirectory);
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}",
+ str_replace("'php'", 'php', $exception->getMessage())
+ );
+ }
+}
diff --git a/vendor/symfony/process/Tests/ProcessTest.php b/vendor/symfony/process/Tests/ProcessTest.php
new file mode 100755
index 0000000..adff6ea
--- /dev/null
+++ b/vendor/symfony/process/Tests/ProcessTest.php
@@ -0,0 +1,1515 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\InputStream;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * @author Robert Schönthal
+ */
+class ProcessTest extends TestCase
+{
+ private static $phpBin;
+ private static $process;
+ private static $sigchild;
+
+ public static function setUpBeforeClass(): void
+ {
+ $phpBin = new PhpExecutableFinder();
+ self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === \PHP_SAPI ? 'php' : $phpBin->find());
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ protected function tearDown(): void
+ {
+ if (self::$process) {
+ self::$process->stop(0);
+ self::$process = null;
+ }
+ }
+
+ public function testInvalidCwd()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessageRegExp('/The provided cwd ".*" does not exist\./');
+ try {
+ // Check that it works fine if the CWD exists
+ $cmd = new Process(['echo', 'test'], __DIR__);
+ $cmd->run();
+ } catch (\Exception $e) {
+ $this->fail($e);
+ }
+
+ $cmd = new Process(['echo', 'test'], __DIR__.'/notfound/');
+ $cmd->run();
+ }
+
+ public function testThatProcessDoesNotThrowWarningDuringRun()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is transient on Windows');
+ }
+ @trigger_error('Test Error', E_USER_NOTICE);
+ $process = $this->getProcessForCode('sleep(3)');
+ $process->run();
+ $actualError = error_get_last();
+ $this->assertEquals('Test Error', $actualError['message']);
+ $this->assertEquals(E_USER_NOTICE, $actualError['type']);
+ }
+
+ public function testNegativeTimeoutFromConstructor()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
+ $this->getProcess('', null, null, null, -1);
+ }
+
+ public function testNegativeTimeoutFromSetter()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
+ $p = $this->getProcess('');
+ $p->setTimeout(-1);
+ }
+
+ public function testFloatAndNullTimeout()
+ {
+ $p = $this->getProcess('');
+
+ $p->setTimeout(10);
+ $this->assertSame(10.0, $p->getTimeout());
+
+ $p->setTimeout(null);
+ $this->assertNull($p->getTimeout());
+
+ $p->setTimeout(0.0);
+ $this->assertNull($p->getTimeout());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $p = $this->getProcess([self::$phpBin, __DIR__.'/NonStopableProcess.php', 30]);
+ $p->start();
+
+ while ($p->isRunning() && false === strpos($p->getOutput(), 'received')) {
+ usleep(1000);
+ }
+
+ if (!$p->isRunning()) {
+ throw new \LogicException('Process is not running: '.$p->getErrorOutput());
+ }
+
+ $start = microtime(true);
+ $p->stop(0.1);
+
+ $p->wait();
+
+ $this->assertLessThan(15, microtime(true) - $start);
+ }
+
+ public function testWaitUntilSpecificOutput()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestIncomplete('This test is too transient on Windows, help wanted to improve it');
+ }
+
+ $p = $this->getProcess([self::$phpBin, __DIR__.'/KillableProcessWithOutput.php']);
+ $p->start();
+
+ $start = microtime(true);
+
+ $completeOutput = '';
+ $result = $p->waitUntil(function ($type, $output) use (&$completeOutput) {
+ return false !== strpos($completeOutput .= $output, 'One more');
+ });
+ $this->assertTrue($result);
+ $this->assertLessThan(20, microtime(true) - $start);
+ $this->assertStringStartsWith("First iteration output\nSecond iteration output\nOne more", $completeOutput);
+ $p->stop();
+ }
+
+ public function testWaitUntilCanReturnFalse()
+ {
+ $p = $this->getProcess('echo foo');
+ $p->start();
+ $this->assertFalse($p->waitUntil(function () { return false; }));
+ }
+
+ public function testAllOutputIsActuallyReadOnTermination()
+ {
+ // this code will result in a maximum of 2 reads of 8192 bytes by calling
+ // start() and isRunning(). by the time getOutput() is called the process
+ // has terminated so the internal pipes array is already empty. normally
+ // the call to start() will not read any data as the process will not have
+ // generated output, but this is non-deterministic so we must count it as
+ // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
+ // another byte which will never be read.
+ $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
+
+ $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
+ $p = $this->getProcessForCode($code);
+
+ $p->start();
+
+ // Don't call Process::run nor Process::wait to avoid any read of pipes
+ $h = new \ReflectionProperty($p, 'process');
+ $h->setAccessible(true);
+ $h = $h->getValue($p);
+ $s = @proc_get_status($h);
+
+ while (!empty($s['running'])) {
+ usleep(1000);
+ $s = proc_get_status($h);
+ }
+
+ $o = $p->getOutput();
+
+ $this->assertEquals($expectedOutputSize, \strlen($o));
+ }
+
+ public function testCallbacksAreExecutedWithStart()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->start(function ($type, $buffer) use (&$data) {
+ $data .= $buffer;
+ });
+
+ $process->wait();
+
+ $this->assertSame('foo'.PHP_EOL, $data);
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider responsesCodeProvider
+ */
+ public function testProcessResponses($expected, $getter, $code)
+ {
+ $p = $this->getProcessForCode($code);
+ $p->run();
+
+ $this->assertSame($expected, $p->$getter());
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider pipesCodeProvider
+ */
+ public function testProcessPipes($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($expected);
+ $p->run();
+
+ $this->assertEquals($expectedLength, \strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
+ }
+
+ /**
+ * @dataProvider pipesCodeProvider
+ */
+ public function testSetStreamAsInput($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $stream = fopen('php://temporary', 'w+');
+ fwrite($stream, $expected);
+ rewind($stream);
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($stream);
+ $p->run();
+
+ fclose($stream);
+
+ $this->assertEquals($expectedLength, \strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
+ }
+
+ public function testLiveStreamAsInput()
+ {
+ $stream = fopen('php://memory', 'r+');
+ fwrite($stream, 'hello');
+ rewind($stream);
+
+ $p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p->setInput($stream);
+ $p->start(function ($type, $data) use ($stream) {
+ if ('hello' === $data) {
+ fclose($stream);
+ }
+ });
+ $p->wait();
+
+ $this->assertSame('hello', $p->getOutput());
+ }
+
+ public function testSetInputWhileRunningThrowsAnException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Input can not be set while the process is running.');
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->start();
+ try {
+ $process->setInput('foobar');
+ $process->stop();
+ $this->fail('A LogicException should have been raised.');
+ } catch (LogicException $e) {
+ }
+ $process->stop();
+
+ throw $e;
+ }
+
+ /**
+ * @dataProvider provideInvalidInputValues
+ */
+ public function testInvalidInput($value)
+ {
+ $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
+ $this->expectExceptionMessage('Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources.');
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ }
+
+ public function provideInvalidInputValues()
+ {
+ return [
+ [[]],
+ [new NonStringifiable()],
+ ];
+ }
+
+ /**
+ * @dataProvider provideInputValues
+ */
+ public function testValidInput($expected, $value)
+ {
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ $this->assertSame($expected, $process->getInput());
+ }
+
+ public function provideInputValues()
+ {
+ return [
+ [null, null],
+ ['24.5', 24.5],
+ ['input data', 'input data'],
+ ];
+ }
+
+ public function chainedCommandsOutputProvider()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return [
+ ["2 \r\n2\r\n", '&&', '2'],
+ ];
+ }
+
+ return [
+ ["1\n1\n", ';', '1'],
+ ["2\n2\n", '&&', '2'],
+ ];
+ }
+
+ /**
+ * @dataProvider chainedCommandsOutputProvider
+ */
+ public function testChainedCommandsOutput($expected, $operator, $input)
+ {
+ $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
+ $process->run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCallbackIsExecutedForOutput()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = 'foo' === $buffer;
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+ $p->disableOutput();
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = 'foo' === $buffer;
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testGetErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
+ }
+
+ public function testFlushErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $p->clearErrorOutput();
+ $this->assertEmpty($p->getErrorOutput());
+ }
+
+ /**
+ * @dataProvider provideIncrementalOutput
+ */
+ public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
+ {
+ $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
+
+ $p = $this->getProcessForCode('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');');
+
+ $h = fopen($lock, 'w');
+ flock($h, LOCK_EX);
+
+ $p->start();
+
+ foreach (['foo', 'bar'] as $s) {
+ while (false === strpos($p->$getOutput(), $s)) {
+ usleep(1000);
+ }
+
+ $this->assertSame($s, $p->$getIncrementalOutput());
+ $this->assertSame('', $p->$getIncrementalOutput());
+
+ flock($h, LOCK_UN);
+ }
+
+ fclose($h);
+ }
+
+ public function provideIncrementalOutput()
+ {
+ return [
+ ['getOutput', 'getIncrementalOutput', 'php://stdout'],
+ ['getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'],
+ ];
+ }
+
+ public function testGetOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
+ }
+
+ public function testFlushOutput()
+ {
+ $p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}');
+
+ $p->run();
+ $p->clearOutput();
+ $this->assertEmpty($p->getOutput());
+ }
+
+ public function testZeroAsOutput()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
+ $p = $this->getProcess('echo | set /p dummyName=0');
+ } else {
+ $p = $this->getProcess('printf 0');
+ }
+
+ $p->run();
+ $this->assertSame('0', $p->getOutput());
+ }
+
+ public function testExitCodeCommandFailed()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX exit code');
+ }
+
+ // such command run in bash return an exitcode 127
+ $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
+ $process->run();
+
+ $this->assertGreaterThan(0, $process->getExitCode());
+ }
+
+ public function testTTYCommand()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
+ $process->setTty(true);
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->wait();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testTTYCommandExitCode()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(true);
+ $process->run();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testTTYInWindowsEnvironment()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('TTY mode is not supported on Windows platform.');
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is for Windows platform only');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(false);
+ $process->setTty(true);
+ }
+
+ public function testExitCodeTextIsNullWhenExitCodeIsNull()
+ {
+ $process = $this->getProcess('');
+ $this->assertNull($process->getExitCodeText());
+ }
+
+ public function testPTYCommand()
+ {
+ if (!Process::isPtySupported()) {
+ $this->markTestSkipped('PTY is not supported on this operating system.');
+ }
+
+ $process = $this->getProcess('echo "foo"');
+ $process->setPty(true);
+ $process->run();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ $this->assertEquals("foo\r\n", $process->getOutput());
+ }
+
+ public function testMustRun()
+ {
+ $process = $this->getProcess('echo foo');
+
+ $this->assertSame($process, $process->mustRun());
+ $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
+ }
+
+ public function testSuccessfulMustRunHasCorrectExitCode()
+ {
+ $process = $this->getProcess('echo foo')->mustRun();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testMustRunThrowsException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessFailedException');
+ $process = $this->getProcess('exit 1');
+ $process->mustRun();
+ }
+
+ public function testExitCodeText()
+ {
+ $process = $this->getProcess('');
+ $r = new \ReflectionObject($process);
+ $p = $r->getProperty('exitcode');
+ $p->setAccessible(true);
+
+ $p->setValue($process, 2);
+ $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
+ }
+
+ public function testStartIsNonBlocking()
+ {
+ $process = $this->getProcessForCode('usleep(500000);');
+ $start = microtime(true);
+ $process->start();
+ $end = microtime(true);
+ $this->assertLessThan(0.4, $end - $start);
+ $process->stop();
+ }
+
+ public function testUpdateStatus()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertGreaterThan(0, \strlen($process->getOutput()));
+ }
+
+ public function testGetExitCodeIsNullOnStart()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertNull($process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCodeIsNullOnWhenStartingAgain()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->run();
+ $this->assertEquals(0, $process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCode()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertSame(0, $process->getExitCode());
+ }
+
+ public function testStatus()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertFalse($process->isRunning());
+ $this->assertFalse($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_READY, $process->getStatus());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertTrue($process->isTerminated());
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testStop()
+ {
+ $process = $this->getProcessForCode('sleep(31);');
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop();
+ $this->assertFalse($process->isRunning());
+ }
+
+ public function testIsSuccessful()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsSuccessfulOnlyAfterTerminated()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->start();
+
+ $this->assertFalse($process->isSuccessful());
+
+ $process->wait();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsNotSuccessful()
+ {
+ $process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
+ $process->run();
+ $this->assertFalse($process->isSuccessful());
+ }
+
+ public function testProcessIsNotSignaled()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertFalse($process->hasBeenSignaled());
+ }
+
+ public function testProcessWithoutTermSignal()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertEquals(0, $process->getTermSignal());
+ }
+
+ public function testProcessIsSignaledIfStopped()
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcessForCode('sleep(32);');
+ $process->start();
+ $process->stop();
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
+ }
+
+ public function testProcessThrowsExceptionWhenExternallySignaled()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessSignaledException');
+ $this->expectExceptionMessage('The process has been signaled with signal "9".');
+ if (!\function_exists('posix_kill')) {
+ $this->markTestSkipped('Function posix_kill is required.');
+ }
+
+ if (self::$sigchild) {
+ $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
+ }
+
+ $process = $this->getProcessForCode('sleep(32.1);');
+ $process->start();
+ posix_kill($process->getPid(), 9); // SIGKILL
+
+ $process->wait();
+ }
+
+ public function testRestart()
+ {
+ $process1 = $this->getProcessForCode('echo getmypid();');
+ $process1->run();
+ $process2 = $process1->restart();
+
+ $process2->wait(); // wait for output
+
+ // Ensure that both processed finished and the output is numeric
+ $this->assertFalse($process1->isRunning());
+ $this->assertFalse($process2->isRunning());
+ $this->assertIsNumeric($process1->getOutput());
+ $this->assertIsNumeric($process2->getOutput());
+
+ // Ensure that restart returned a new process by check that the output is different
+ $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
+ }
+
+ public function testRunProcessWithTimeout()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->run();
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testIterateOverProcessWithTimeout()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->start();
+ foreach ($process as $buffer);
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testCheckTimeoutOnNonStartedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $this->assertNull($process->checkTimeout());
+ }
+
+ public function testCheckTimeoutOnTerminatedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->checkTimeout());
+ }
+
+ public function testCheckTimeoutOnStartedProcess()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(33);');
+ $process->setTimeout(0.1);
+
+ $process->start();
+ $start = microtime(true);
+
+ try {
+ while ($process->isRunning()) {
+ $process->checkTimeout();
+ usleep(100000);
+ }
+ $this->fail('A ProcessTimedOutException should have been raised');
+ } catch (ProcessTimedOutException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testIdleTimeout()
+ {
+ $process = $this->getProcessForCode('sleep(34);');
+ $process->setTimeout(60);
+ $process->setIdleTimeout(0.1);
+
+ try {
+ $process->run();
+
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isIdleTimeout());
+ $this->assertFalse($e->isGeneralTimeout());
+ $this->assertEquals(0.1, $e->getExceededTimeout());
+ }
+ }
+
+ public function testIdleTimeoutNotExceededWhenOutputIsSent()
+ {
+ $process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
+ $process->setTimeout(1);
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'foo')) {
+ usleep(1000);
+ }
+
+ $process->setIdleTimeout(0.5);
+
+ try {
+ $process->wait();
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
+ $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
+ $this->assertEquals(1, $e->getExceededTimeout());
+ }
+ }
+
+ public function testStartAfterATimeout()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
+ $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
+ $process = $this->getProcessForCode('sleep(35);');
+ $process->setTimeout(0.1);
+
+ try {
+ $process->run();
+ $this->fail('A ProcessTimedOutException should have been raised.');
+ } catch (ProcessTimedOutException $e) {
+ }
+ $this->assertFalse($process->isRunning());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function testGetPid()
+ {
+ $process = $this->getProcessForCode('sleep(36);');
+ $process->start();
+ $this->assertGreaterThan(0, $process->getPid());
+ $process->stop(0);
+ }
+
+ public function testGetPidIsNullBeforeStart()
+ {
+ $process = $this->getProcess('foo');
+ $this->assertNull($process->getPid());
+ }
+
+ public function testGetPidIsNullAfterRun()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->getPid());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testSignal()
+ {
+ $process = $this->getProcess([self::$phpBin, __DIR__.'/SignalListener.php']);
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'Caught')) {
+ usleep(1000);
+ }
+ $process->signal(SIGUSR1);
+ $process->wait();
+
+ $this->assertEquals('Caught SIGUSR1', $process->getOutput());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testExitCodeIsAvailableAfterSignal()
+ {
+ $process = $this->getProcess('sleep 4');
+ $process->start();
+ $process->signal(SIGKILL);
+
+ while ($process->isRunning()) {
+ usleep(10000);
+ }
+
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertFalse($process->isSuccessful());
+ $this->assertEquals(137, $process->getExitCode());
+ }
+
+ public function testSignalProcessNotRunning()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Can not send signal on a non running process.');
+ $process = $this->getProcess('foo');
+ $process->signal(1); // SIGHUP
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedARunningProcess
+ */
+ public function testMethodsThatNeedARunningProcess($method)
+ {
+ $process = $this->getProcess('foo');
+
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage(sprintf('Process must be started before calling %s.', $method));
+
+ $process->{$method}();
+ }
+
+ public function provideMethodsThatNeedARunningProcess()
+ {
+ return [
+ ['getOutput'],
+ ['getIncrementalOutput'],
+ ['getErrorOutput'],
+ ['getIncrementalErrorOutput'],
+ ['wait'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedATerminatedProcess
+ */
+ public function testMethodsThatNeedATerminatedProcess($method)
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Process must be terminated before calling');
+ $process = $this->getProcessForCode('sleep(37);');
+ $process->start();
+ try {
+ $process->{$method}();
+ $process->stop(0);
+ $this->fail('A LogicException must have been thrown');
+ } catch (\Exception $e) {
+ }
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function provideMethodsThatNeedATerminatedProcess()
+ {
+ return [
+ ['hasBeenSignaled'],
+ ['getTermSignal'],
+ ['hasBeenStopped'],
+ ['getStopSignal'],
+ ];
+ }
+
+ public function testWrongSignal()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('POSIX signals do not work on Windows');
+ }
+
+ $process = $this->getProcessForCode('sleep(38);');
+ $process->start();
+ try {
+ $process->signal(-4);
+ $this->fail('A RuntimeException must have been thrown');
+ } catch (RuntimeException $e) {
+ $process->stop(0);
+ }
+
+ throw $e;
+ }
+
+ public function testDisableOutputDisablesTheOutput()
+ {
+ $p = $this->getProcess('foo');
+ $this->assertFalse($p->isOutputDisabled());
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ $p->enableOutput();
+ $this->assertFalse($p->isOutputDisabled());
+ }
+
+ public function testDisableOutputWhileRunningThrowsException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('Disabling output while the process is running is not possible.');
+ $p = $this->getProcessForCode('sleep(39);');
+ $p->start();
+ $p->disableOutput();
+ }
+
+ public function testEnableOutputWhileRunningThrowsException()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('Enabling output while the process is running is not possible.');
+ $p = $this->getProcessForCode('sleep(40);');
+ $p->disableOutput();
+ $p->start();
+ $p->enableOutput();
+ }
+
+ public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
+ {
+ $p = $this->getProcess('echo foo');
+ $p->disableOutput();
+ $p->run();
+ $p->enableOutput();
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ }
+
+ public function testDisableOutputWhileIdleTimeoutIsSet()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Output can not be disabled while an idle timeout is set.');
+ $process = $this->getProcess('foo');
+ $process->setIdleTimeout(1);
+ $process->disableOutput();
+ }
+
+ public function testSetIdleTimeoutWhileOutputIsDisabled()
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('timeout can not be set while the output is disabled.');
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $process->setIdleTimeout(1);
+ }
+
+ public function testSetNullIdleTimeoutWhileOutputIsDisabled()
+ {
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $this->assertSame($process, $process->setIdleTimeout(null));
+ }
+
+ /**
+ * @dataProvider provideOutputFetchingMethods
+ */
+ public function testGetOutputWhileDisabled($fetchMethod)
+ {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage('Output has been disabled.');
+ $p = $this->getProcessForCode('sleep(41);');
+ $p->disableOutput();
+ $p->start();
+ $p->{$fetchMethod}();
+ }
+
+ public function provideOutputFetchingMethods()
+ {
+ return [
+ ['getOutput'],
+ ['getIncrementalOutput'],
+ ['getErrorOutput'],
+ ['getIncrementalErrorOutput'],
+ ];
+ }
+
+ public function testStopTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(42);');
+ $process->run(function () use ($process) {
+ $process->stop();
+ });
+ $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testKillSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(43);');
+ $process->run(function () use ($process) {
+ $process->signal(9); // SIGKILL
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testTermSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(44);');
+ $process->run(function () use ($process) {
+ $process->signal(15); // SIGTERM
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function responsesCodeProvider()
+ {
+ return [
+ //expected output / getter / code to execute
+ // [1,'getExitCode','exit(1);'],
+ // [true,'isSuccessful','exit();'],
+ ['output', 'getOutput', 'echo \'output\';'],
+ ];
+ }
+
+ public function pipesCodeProvider()
+ {
+ $variations = [
+ 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
+ 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
+ ];
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ // Avoid XL buffers on Windows because of https://bugs.php.net/65650
+ $sizes = [1, 2, 4, 8];
+ } else {
+ $sizes = [1, 16, 64, 1024, 4096];
+ }
+
+ $codes = [];
+ foreach ($sizes as $size) {
+ foreach ($variations as $code) {
+ $codes[] = [$code, $size];
+ }
+ }
+
+ return $codes;
+ }
+
+ /**
+ * @dataProvider provideVariousIncrementals
+ */
+ public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
+ {
+ $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null);
+ $process->start();
+ $result = '';
+ $limit = microtime(true) + 3;
+ $expected = '012';
+
+ while ($result !== $expected && microtime(true) < $limit) {
+ $result .= $process->$method();
+ }
+
+ $this->assertSame($expected, $result);
+ $process->stop();
+ }
+
+ public function provideVariousIncrementals()
+ {
+ return [
+ ['php://stdout', 'getIncrementalOutput'],
+ ['php://stderr', 'getIncrementalErrorOutput'],
+ ];
+ }
+
+ public function testIteratorInput()
+ {
+ $input = function () {
+ yield 'ping';
+ yield 'pong';
+ };
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
+ $process->run();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testSimpleInputStream()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('echo \'ping\'; echo fread(STDIN, 4); echo fread(STDIN, 4);');
+ $process->setInput($input);
+
+ $process->start(function ($type, $data) use ($input) {
+ if ('ping' === $data) {
+ $input->write('pang');
+ } elseif (!$input->isClosed()) {
+ $input->write('pong');
+ $input->close();
+ }
+ });
+
+ $process->wait();
+ $this->assertSame('pingpangpong', $process->getOutput());
+ }
+
+ public function testInputStreamWithCallable()
+ {
+ $i = 0;
+ $stream = fopen('php://memory', 'w+');
+ $stream = function () use ($stream, &$i) {
+ if ($i < 3) {
+ rewind($stream);
+ fwrite($stream, ++$i);
+ rewind($stream);
+
+ return $stream;
+ }
+
+ return null;
+ };
+
+ $input = new InputStream();
+ $input->onEmpty($stream);
+ $input->write($stream());
+
+ $process = $this->getProcessForCode('echo fread(STDIN, 3);');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ $input->close();
+ });
+
+ $process->wait();
+ $this->assertSame('123', $process->getOutput());
+ }
+
+ public function testInputStreamWithGenerator()
+ {
+ $input = new InputStream();
+ $input->onEmpty(function ($input) {
+ yield 'pong';
+ $input->close();
+ });
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $process->setInput($input);
+ $process->start();
+ $input->write('ping');
+ $process->wait();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testInputStreamOnEmpty()
+ {
+ $i = 0;
+ $input = new InputStream();
+ $input->onEmpty(function () use (&$i) { ++$i; });
+
+ $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ if ('123' === $data) {
+ $input->close();
+ }
+ });
+ $process->wait();
+
+ $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
+ $this->assertSame('123456', $process->getOutput());
+ }
+
+ public function testIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');
+ $process->setInput($input);
+ $process->start();
+ $output = [];
+
+ foreach ($process as $type => $data) {
+ $output[] = [$type, $data];
+ break;
+ }
+ $expectedOutput = [
+ [$process::OUT, '123'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(345);
+
+ foreach ($process as $type => $data) {
+ $output[] = [$type, $data];
+ }
+
+ $this->assertSame('', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = [
+ [$process::OUT, '123'],
+ [$process::ERR, '234'],
+ [$process::OUT, '345'],
+ [$process::ERR, '456'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testNonBlockingNorClearingIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
+ $process->setInput($input);
+ $process->start();
+ $output = [];
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ $output[] = [$type, $data];
+ break;
+ }
+ $expectedOutput = [
+ [$process::OUT, ''],
+ ];
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(123);
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ if ('' !== $data) {
+ $output[] = [$type, $data];
+ }
+ }
+
+ $this->assertSame('123', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = [
+ [$process::OUT, ''],
+ [$process::OUT, '123'],
+ ];
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testChainedProcesses()
+ {
+ $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
+ $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p2->setInput($p1);
+
+ $p1->start();
+ $p2->run();
+
+ $this->assertSame('123', $p1->getErrorOutput());
+ $this->assertSame('', $p1->getOutput());
+ $this->assertSame('', $p2->getErrorOutput());
+ $this->assertSame('456', $p2->getOutput());
+ }
+
+ public function testSetBadEnv()
+ {
+ $process = $this->getProcess('echo hello');
+ $process->setEnv(['bad%%' => '123']);
+ $process->inheritEnvironmentVariables(true);
+
+ $process->run();
+
+ $this->assertSame('hello'.PHP_EOL, $process->getOutput());
+ $this->assertSame('', $process->getErrorOutput());
+ }
+
+ public function testEnvBackupDoesNotDeleteExistingVars()
+ {
+ putenv('existing_var=foo');
+ $_ENV['existing_var'] = 'foo';
+ $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
+ $process->setEnv(['existing_var' => 'bar', 'new_test_var' => 'foo']);
+ $process->inheritEnvironmentVariables();
+
+ $process->run();
+
+ $this->assertSame('foo', $process->getOutput());
+ $this->assertSame('foo', getenv('existing_var'));
+ $this->assertFalse(getenv('new_test_var'));
+
+ putenv('existing_var');
+ unset($_ENV['existing_var']);
+ }
+
+ public function testEnvIsInherited()
+ {
+ $process = $this->getProcessForCode('echo serialize($_SERVER);', null, ['BAR' => 'BAZ', 'EMPTY' => '']);
+
+ putenv('FOO=BAR');
+ $_ENV['FOO'] = 'BAR';
+
+ $process->run();
+
+ $expected = ['BAR' => 'BAZ', 'EMPTY' => '', 'FOO' => 'BAR'];
+ $env = array_intersect_key(unserialize($process->getOutput()), $expected);
+
+ $this->assertEquals($expected, $env);
+
+ putenv('FOO');
+ unset($_ENV['FOO']);
+ }
+
+ public function testGetCommandLine()
+ {
+ $p = new Process(['/usr/bin/php']);
+
+ $expected = '\\' === \DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'";
+ $this->assertSame($expected, $p->getCommandLine());
+ }
+
+ /**
+ * @dataProvider provideEscapeArgument
+ */
+ public function testEscapeArgument($arg)
+ {
+ $p = new Process([self::$phpBin, '-r', 'echo $argv[1];', $arg]);
+ $p->run();
+
+ $this->assertSame((string) $arg, $p->getOutput());
+ }
+
+ public function testRawCommandLine()
+ {
+ $p = Process::fromShellCommandline(sprintf('"%s" -r %s "a" "" "b"', self::$phpBin, escapeshellarg('print_r($argv);')));
+ $p->run();
+
+ $expected = << -
+ [1] => a
+ [2] =>
+ [3] => b
+)
+
+EOTXT;
+ $this->assertSame($expected, str_replace('Standard input code', '-', $p->getOutput()));
+ }
+
+ public function provideEscapeArgument()
+ {
+ yield ['a"b%c%'];
+ yield ['a"b^c^'];
+ yield ["a\nb'c"];
+ yield ['a^b c!'];
+ yield ["a!b\tc"];
+ yield ['a\\\\"\\"'];
+ yield ['éÉèÈàÀöä'];
+ yield [null];
+ yield [1];
+ yield [1.1];
+ }
+
+ public function testEnvArgument()
+ {
+ $env = ['FOO' => 'Foo', 'BAR' => 'Bar'];
+ $cmd = '\\' === \DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
+ $p = Process::fromShellCommandline($cmd, null, $env);
+ $p->run(null, ['BAR' => 'baR', 'BAZ' => 'baZ']);
+
+ $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
+ $this->assertSame($env, $p->getEnv());
+ }
+
+ public function testWaitStoppedDeadProcess()
+ {
+ $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/ErrorProcessInitiator.php -e '.self::$phpBin);
+ $process->start();
+ $process->setTimeout(2);
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ }
+
+ /**
+ * @param string $commandline
+ * @param string|null $input
+ * @param int $timeout
+ */
+ private function getProcess($commandline, string $cwd = null, array $env = null, $input = null, ?int $timeout = 60): Process
+ {
+ if (\is_string($commandline)) {
+ $process = Process::fromShellCommandline($commandline, $cwd, $env, $input, $timeout);
+ } else {
+ $process = new Process($commandline, $cwd, $env, $input, $timeout);
+ }
+ $process->inheritEnvironmentVariables();
+
+ if (self::$process) {
+ self::$process->stop(0);
+ }
+
+ return self::$process = $process;
+ }
+
+ private function getProcessForCode(string $code, string $cwd = null, array $env = null, $input = null, ?int $timeout = 60): Process
+ {
+ return $this->getProcess([self::$phpBin, '-r', $code], $cwd, $env, $input, $timeout);
+ }
+}
+
+class NonStringifiable
+{
+}
diff --git a/vendor/symfony/process/Tests/SignalListener.php b/vendor/symfony/process/Tests/SignalListener.php
new file mode 100755
index 0000000..9e30ce3
--- /dev/null
+++ b/vendor/symfony/process/Tests/SignalListener.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+pcntl_signal(SIGUSR1, function () { echo 'SIGUSR1'; exit; });
+
+echo 'Caught ';
+
+$n = 0;
+
+while ($n++ < 400) {
+ usleep(10000);
+ pcntl_signal_dispatch();
+}
diff --git a/vendor/symfony/process/composer.json b/vendor/symfony/process/composer.json
new file mode 100755
index 0000000..d3efd02
--- /dev/null
+++ b/vendor/symfony/process/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/process",
+ "type": "library",
+ "description": "Symfony Process Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^7.1.3"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Process\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.3-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/process/phpunit.xml.dist b/vendor/symfony/process/phpunit.xml.dist
new file mode 100755
index 0000000..c32f251
--- /dev/null
+++ b/vendor/symfony/process/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/vendor/valga/fbns-react/.gitignore b/vendor/valga/fbns-react/.gitignore
new file mode 100755
index 0000000..86cea2c
--- /dev/null
+++ b/vendor/valga/fbns-react/.gitignore
@@ -0,0 +1,4 @@
+/.idea/
+/vendor/
+/composer.lock
+/.php_cs.cache
diff --git a/vendor/valga/fbns-react/.php_cs b/vendor/valga/fbns-react/.php_cs
new file mode 100755
index 0000000..97d12a6
--- /dev/null
+++ b/vendor/valga/fbns-react/.php_cs
@@ -0,0 +1,20 @@
+setFinder(
+ \PhpCsFixer\Finder::create()
+ ->in('src')
+ )
+ ->setRules([
+ '@Symfony' => true,
+ // Override @Symfony rules
+ 'pre_increment' => false,
+ 'blank_line_before_statement' => ['statements' => ['return']],
+ 'phpdoc_align' => ['tags' => ['param', 'throws']],
+ 'phpdoc_annotation_without_dot' => false,
+ // Custom rules
+ 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
+ 'ordered_imports' => true,
+ 'phpdoc_order' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ ]);
diff --git a/vendor/valga/fbns-react/LICENSE b/vendor/valga/fbns-react/LICENSE
new file mode 100755
index 0000000..112f1c7
--- /dev/null
+++ b/vendor/valga/fbns-react/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 Abyr Valg
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/valga/fbns-react/README.md b/vendor/valga/fbns-react/README.md
new file mode 100755
index 0000000..6161376
--- /dev/null
+++ b/vendor/valga/fbns-react/README.md
@@ -0,0 +1,84 @@
+# fbns-react
+
+A PHP client for the FBNS built on top of ReactPHP.
+
+## Requirements
+
+You need to install the [GMP extension](http://php.net/manual/en/book.gmp.php) to be able to run this code on x86 PHP builds.
+
+## Installation
+
+```sh
+composer require valga/fbns-react
+```
+
+## Basic Usage
+
+```php
+// Set up a FBNS client.
+$loop = \React\EventLoop\Factory::create();
+$client = new \Fbns\Client\Lite($loop);
+
+// Read saved credentials from a storage.
+$auth = new \Fbns\Client\Auth\DeviceAuth();
+try {
+ $auth->read($storage->get('fbns_auth'));
+} catch (\Exception $e) {
+}
+
+// Connect to a broker.
+$connection = new \Fbns\Client\Connection($deviceAuth, USER_AGENT);
+$client->connect(HOSTNAME, PORT, $connection);
+
+// Bind events.
+$client
+ ->on('connect', function (\Fbns\Client\Lite\ConnectResponsePacket $responsePacket) use ($client, $auth, $storage) {
+ // Update credentials and save them to a storage for future use.
+ try {
+ $auth->read($responsePacket->getAuth());
+ $storage->set('fbns_auth', $responsePacket->getAuth());
+ } catch (\Exception $e) {
+ }
+
+ // Register an application.
+ $client->register(PACKAGE_NAME, APPLICATION_ID);
+ })
+ ->on('register', function (\Fbns\Client\Message\Register $message) use ($app) {
+ // Register received token with an application.
+ $app->registerPushToken($message->getToken());
+ })
+ ->on('push', function (\Fbns\Client\Message\Push $message) use ($app) {
+ // Handle received notification payload.
+ $app->handlePushNotification($message->getPayload());
+ });
+
+// Run main loop.
+$loop->run();
+```
+
+## Advanced Usage
+
+```php
+// Set up a proxy.
+$connector = new \React\Socket\Connector($loop);
+$proxy = new \Clue\React\HttpProxy('username:password@127.0.0.1:3128', $connector);
+
+// Disable SSL verification.
+$ssl = new \React\Socket\SecureConnector($proxy, $loop, ['verify_peer' => false, 'verify_peer_name' => false]);
+
+// Enable logging to stdout.
+$logger = new \Monolog\Logger('fbns');
+$logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', \Monolog\Logger::INFO));
+
+// Set up a client.
+$client = new \Fbns\Client\Lite($loop, $ssl, $logger);
+
+// Persistence.
+$client->on('disconnect', function () {
+ // Network connection has been closed. You can reestablish it if you want to.
+});
+$client->connect(HOSTNAME, PORT, $connection)
+ ->otherwise(function () {
+ // Connection attempt was unsuccessful, retry with an exponential backoff.
+ });
+```
diff --git a/vendor/valga/fbns-react/bin/thrift_debug b/vendor/valga/fbns-react/bin/thrift_debug
new file mode 100755
index 0000000..7f00f0b
--- /dev/null
+++ b/vendor/valga/fbns-react/bin/thrift_debug
@@ -0,0 +1,33 @@
+#!/usr/bin/env php
+ 1) {
+ $data = @file_get_contents($argv[1]);
+} elseif (!posix_isatty(STDIN)) {
+ $data = @stream_get_contents(STDIN);
+} else {
+ echo 'Usage: ', $argv[0], ' [FILE]', PHP_EOL;
+ echo 'Dump the contents of Thrift FILE.', PHP_EOL;
+ echo PHP_EOL;
+ echo 'With no FILE read standard input.', PHP_EOL;
+
+ exit(2);
+}
+
+if ($data === false) {
+ fwrite(STDERR, 'Failed to read the input.'.PHP_EOL);
+
+ exit(1);
+}
+
+try {
+ new \Fbns\Client\Thrift\Debug($data);
+} catch (\Exception $e) {
+ fwrite(STDERR, $e->getMessage().PHP_EOL);
+
+ exit(1);
+}
+
+exit(0);
diff --git a/vendor/valga/fbns-react/composer.json b/vendor/valga/fbns-react/composer.json
new file mode 100755
index 0000000..361799c
--- /dev/null
+++ b/vendor/valga/fbns-react/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "valga/fbns-react",
+ "description": "A PHP client for the FBNS built on top of ReactPHP",
+ "keywords": [
+ "FBNS",
+ "Client",
+ "PHP"
+ ],
+ "type": "library",
+ "minimum-stability": "stable",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Abyr Valg",
+ "email": "valga.github@abyrga.ru"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Fbns\\Client\\": "src/"
+ }
+ },
+ "require": {
+ "php": "~5.6|~7.0",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "evenement/evenement": "~2.0|~3.0",
+ "react/event-loop": "^0.4.3",
+ "react/promise": "~2.0",
+ "react/socket": "~0.8",
+ "binsoul/net-mqtt": "~0.2",
+ "psr/log": "~1.0"
+ },
+ "require-dev": {
+ "monolog/monolog": "~1.23",
+ "friendsofphp/php-cs-fixer": "~2.4"
+ },
+ "suggest": {
+ "ext-event": "For more efficient event loop implementation.",
+ "ext-gmp": "To be able to run this code on x86 PHP builds."
+ },
+ "scripts": {
+ "codestyle": "php-cs-fixer fix --config=.php_cs"
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Auth/DeviceAuth.php b/vendor/valga/fbns-react/src/Auth/DeviceAuth.php
new file mode 100755
index 0000000..ae460ae
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Auth/DeviceAuth.php
@@ -0,0 +1,162 @@
+clientId = substr($this->randomUuid(), 0, 20);
+ $this->userId = 0;
+ $this->password = '';
+ $this->deviceSecret = '';
+ $this->deviceId = '';
+ }
+
+ /**
+ * @param string $json
+ */
+ public function read($json)
+ {
+ $data = Json::decode($json);
+ $this->json = $json;
+
+ if (isset($data->ck)) {
+ $this->userId = $data->ck;
+ } else {
+ $this->userId = 0;
+ }
+ if (isset($data->cs)) {
+ $this->password = $data->cs;
+ } else {
+ $this->password = '';
+ }
+ if (isset($data->di)) {
+ $this->deviceId = $data->di;
+ $this->clientId = substr($this->deviceId, 0, 20);
+ } else {
+ $this->deviceId = '';
+ $this->clientId = substr($this->randomUuid(), 0, 20);
+ }
+ if (isset($data->ds)) {
+ $this->deviceSecret = $data->ds;
+ } else {
+ $this->deviceSecret = '';
+ }
+
+ // TODO: sr ?
+ // TODO: rc ?
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->json !== null ? $this->json : '';
+ }
+
+ /**
+ * @return int
+ */
+ public function getUserId()
+ {
+ return $this->userId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDeviceId()
+ {
+ return $this->deviceId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDeviceSecret()
+ {
+ return $this->deviceSecret;
+ }
+
+ /**
+ * @return string
+ */
+ public function getClientType()
+ {
+ return self::TYPE;
+ }
+
+ /**
+ * @return string
+ */
+ public function getClientId()
+ {
+ return $this->clientId;
+ }
+}
diff --git a/vendor/valga/fbns-react/src/AuthInterface.php b/vendor/valga/fbns-react/src/AuthInterface.php
new file mode 100755
index 0000000..7b40a93
--- /dev/null
+++ b/vendor/valga/fbns-react/src/AuthInterface.php
@@ -0,0 +1,41 @@
+assertPacketFlags($this->getExpectedPacketFlags());
+ $this->assertRemainingPacketLength(2);
+
+ $this->identifier = $stream->readWord();
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Connection.php b/vendor/valga/fbns-react/src/Connection.php
new file mode 100755
index 0000000..9ea5a39
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Connection.php
@@ -0,0 +1,339 @@
+auth = $auth;
+ $this->userAgent = $userAgent;
+
+ $this->clientCapabilities = self::FBNS_CLIENT_CAPABILITIES;
+ $this->endpointCapabilities = self::FBNS_ENDPOINT_CAPABILITIES;
+ $this->publishFormat = self::FBNS_PUBLISH_FORMAT;
+ $this->noAutomaticForeground = true;
+ $this->makeUserAvailableInForeground = false;
+ $this->isInitiallyForeground = false;
+ $this->networkType = 1;
+ $this->networkSubtype = 0;
+ $this->subscribeTopics = [(int) Lite::MESSAGE_TOPIC_ID, (int) Lite::REG_RESP_TOPIC_ID];
+ $this->appId = self::FBNS_APP_ID;
+ $this->clientStack = self::FBNS_CLIENT_STACK;
+ }
+
+ /**
+ * @return string
+ */
+ public function toThrift()
+ {
+ $writer = new Writer();
+
+ $writer->writeString(self::CLIENT_ID, $this->auth->getClientId());
+
+ $writer->writeStruct(self::CLIENT_INFO);
+ $writer->writeInt64(self::USER_ID, $this->auth->getUserId());
+ $writer->writeString(self::USER_AGENT, $this->userAgent);
+ $writer->writeInt64(self::CLIENT_CAPABILITIES, $this->clientCapabilities);
+ $writer->writeInt64(self::ENDPOINT_CAPABILITIES, $this->endpointCapabilities);
+ $writer->writeInt32(self::PUBLISH_FORMAT, $this->publishFormat);
+ $writer->writeBool(self::NO_AUTOMATIC_FOREGROUND, $this->noAutomaticForeground);
+ $writer->writeBool(self::MAKE_USER_AVAILABLE_IN_FOREGROUND, $this->makeUserAvailableInForeground);
+ $writer->writeString(self::DEVICE_ID, $this->auth->getDeviceId());
+ $writer->writeBool(self::IS_INITIALLY_FOREGROUND, $this->isInitiallyForeground);
+ $writer->writeInt32(self::NETWORK_TYPE, $this->networkType);
+ $writer->writeInt32(self::NETWORK_SUBTYPE, $this->networkSubtype);
+ if ($this->clientMqttSessionId === null) {
+ $sessionId = (int) ((microtime(true) - strtotime('Last Monday')) * 1000);
+ } else {
+ $sessionId = $this->clientMqttSessionId;
+ }
+ $writer->writeInt64(self::CLIENT_MQTT_SESSION_ID, $sessionId);
+ $writer->writeList(self::SUBSCRIBE_TOPICS, Compact::TYPE_I32, $this->subscribeTopics);
+ $writer->writeString(self::CLIENT_TYPE, $this->auth->getClientType());
+ $writer->writeInt64(self::APP_ID, $this->appId);
+ $writer->writeString(self::DEVICE_SECRET, $this->auth->getDeviceSecret());
+ $writer->writeInt8(self::CLIENT_STACK, $this->clientStack);
+ $writer->writeStop();
+
+ $writer->writeString(self::PASSWORD, $this->auth->getPassword());
+ $writer->writeStop();
+
+ return (string) $writer;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUserAgent()
+ {
+ return $this->userAgent;
+ }
+
+ /**
+ * @param string $userAgent
+ */
+ public function setUserAgent($userAgent)
+ {
+ $this->userAgent = $userAgent;
+ }
+
+ /**
+ * @return int
+ */
+ public function getClientCapabilities()
+ {
+ return $this->clientCapabilities;
+ }
+
+ /**
+ * @param int $clientCapabilities
+ */
+ public function setClientCapabilities($clientCapabilities)
+ {
+ $this->clientCapabilities = $clientCapabilities;
+ }
+
+ /**
+ * @return int
+ */
+ public function getEndpointCapabilities()
+ {
+ return $this->endpointCapabilities;
+ }
+
+ /**
+ * @param int $endpointCapabilities
+ */
+ public function setEndpointCapabilities($endpointCapabilities)
+ {
+ $this->endpointCapabilities = $endpointCapabilities;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isNoAutomaticForeground()
+ {
+ return $this->noAutomaticForeground;
+ }
+
+ /**
+ * @param bool $noAutomaticForeground
+ */
+ public function setNoAutomaticForeground($noAutomaticForeground)
+ {
+ $this->noAutomaticForeground = $noAutomaticForeground;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isMakeUserAvailableInForeground()
+ {
+ return $this->makeUserAvailableInForeground;
+ }
+
+ /**
+ * @param bool $makeUserAvailableInForeground
+ */
+ public function setMakeUserAvailableInForeground($makeUserAvailableInForeground)
+ {
+ $this->makeUserAvailableInForeground = $makeUserAvailableInForeground;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isInitiallyForeground()
+ {
+ return $this->isInitiallyForeground;
+ }
+
+ /**
+ * @param bool $isInitiallyForeground
+ */
+ public function setIsInitiallyForeground($isInitiallyForeground)
+ {
+ $this->isInitiallyForeground = $isInitiallyForeground;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNetworkType()
+ {
+ return $this->networkType;
+ }
+
+ /**
+ * @param int $networkType
+ */
+ public function setNetworkType($networkType)
+ {
+ $this->networkType = $networkType;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNetworkSubtype()
+ {
+ return $this->networkSubtype;
+ }
+
+ /**
+ * @param int $networkSubtype
+ */
+ public function setNetworkSubtype($networkSubtype)
+ {
+ $this->networkSubtype = $networkSubtype;
+ }
+
+ /**
+ * @return int
+ */
+ public function getClientMqttSessionId()
+ {
+ return $this->clientMqttSessionId;
+ }
+
+ /**
+ * @param int $clientMqttSessionId
+ */
+ public function setClientMqttSessionId($clientMqttSessionId)
+ {
+ $this->clientMqttSessionId = $clientMqttSessionId;
+ }
+
+ /**
+ * @return int[]
+ */
+ public function getSubscribeTopics()
+ {
+ return $this->subscribeTopics;
+ }
+
+ /**
+ * @param int[] $subscribeTopics
+ */
+ public function setSubscribeTopics($subscribeTopics)
+ {
+ $this->subscribeTopics = $subscribeTopics;
+ }
+
+ /**
+ * @return int
+ */
+ public function getAppId()
+ {
+ return $this->appId;
+ }
+
+ /**
+ * @param int $appId
+ */
+ public function setAppId($appId)
+ {
+ $this->appId = $appId;
+ }
+
+ /**
+ * @return int
+ */
+ public function getClientStack()
+ {
+ return $this->clientStack;
+ }
+
+ /**
+ * @param int $clientStack
+ */
+ public function setClientStack($clientStack)
+ {
+ $this->clientStack = $clientStack;
+ }
+
+ /**
+ * @return AuthInterface
+ */
+ public function getAuth()
+ {
+ return $this->auth;
+ }
+
+ /**
+ * @param AuthInterface $auth
+ */
+ public function setAuth(AuthInterface $auth)
+ {
+ $this->auth = $auth;
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Json.php b/vendor/valga/fbns-react/src/Json.php
new file mode 100755
index 0000000..24bc1dc
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Json.php
@@ -0,0 +1,28 @@
+ self::MESSAGE_TOPIC,
+ self::REG_REQ_TOPIC_ID => self::REG_REQ_TOPIC,
+ self::REG_RESP_TOPIC_ID => self::REG_RESP_TOPIC,
+ ];
+
+ const TOPIC_TO_ID_ENUM = [
+ self::MESSAGE_TOPIC => self::MESSAGE_TOPIC_ID,
+ self::REG_REQ_TOPIC => self::REG_REQ_TOPIC_ID,
+ self::REG_RESP_TOPIC => self::REG_RESP_TOPIC_ID,
+ ];
+
+ /**
+ * @var LoopInterface
+ */
+ private $loop;
+
+ /**
+ * @var ConnectorInterface
+ */
+ private $connector;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @var ReactMqttClient
+ */
+ private $client;
+
+ /**
+ * @var TimerInterface
+ */
+ private $keepaliveTimer;
+
+ /**
+ * Constructor.
+ *
+ * @param LoopInterface $loop
+ * @param ConnectorInterface|null $connector
+ * @param LoggerInterface|null $logger
+ */
+ public function __construct(LoopInterface $loop, ConnectorInterface $connector = null, LoggerInterface $logger = null)
+ {
+ $this->loop = $loop;
+ if ($connector === null) {
+ $this->connector = new SecureConnector(new Connector($loop), $loop);
+ } else {
+ $this->connector = $connector;
+ }
+ if ($logger !== null) {
+ $this->logger = $logger;
+ } else {
+ $this->logger = new NullLogger();
+ }
+ $this->client = new ReactMqttClient($this->connector, $this->loop, null, new StreamParser());
+
+ $this->client
+ ->on('open', function () {
+ $this->logger->info('Connection has been established.');
+ })
+ ->on('close', function () {
+ $this->logger->info('Network connection has been closed.');
+ $this->cancelKeepaliveTimer();
+ $this->emit('disconnect', [$this]);
+ })
+ ->on('warning', function (\Exception $e) {
+ $this->logger->warning($e->getMessage());
+ })
+ ->on('error', function (\Exception $e) {
+ $this->logger->error($e->getMessage());
+ $this->emit('error', [$e]);
+ })
+ ->on('connect', function (ConnectResponsePacket $responsePacket) {
+ $this->logger->info('Connected to a broker.');
+ $this->setKeepaliveTimer();
+ $this->emit('connect', [$responsePacket]);
+ })
+ ->on('disconnect', function () {
+ $this->logger->info('Disconnected from the broker.');
+ })
+ ->on('message', function (Message $message) {
+ $this->setKeepaliveTimer();
+ $this->onMessage($message);
+ })
+ ->on('publish', function () {
+ $this->logger->info('Publish flow has been completed.');
+ $this->setKeepaliveTimer();
+ })
+ ->on('ping', function () {
+ $this->logger->info('Ping flow has been completed.');
+ $this->setKeepaliveTimer();
+ });
+ }
+
+ private function cancelKeepaliveTimer()
+ {
+ if ($this->keepaliveTimer !== null) {
+ if ($this->keepaliveTimer->isActive()) {
+ $this->logger->info('Existing keepalive timer has been canceled.');
+ $this->keepaliveTimer->cancel();
+ }
+ $this->keepaliveTimer = null;
+ }
+ }
+
+ private function onKeepalive()
+ {
+ $this->logger->info('Keepalive timer has been fired.');
+ $this->cancelKeepaliveTimer();
+ $this->disconnect();
+ }
+
+ private function setKeepaliveTimer()
+ {
+ $this->cancelKeepaliveTimer();
+ $keepaliveInterval = OutgoingConnectFlow::KEEPALIVE;
+ $this->logger->info(sprintf('Setting up keepalive timer to %d seconds', $keepaliveInterval));
+ $this->keepaliveTimer = $this->loop->addTimer($keepaliveInterval, function () {
+ $this->onKeepalive();
+ });
+ }
+
+ /**
+ * @param string $payload
+ */
+ private function onRegister($payload)
+ {
+ try {
+ $message = new Register($payload);
+ } catch (\Exception $e) {
+ $this->logger->warning(sprintf('Failed to decode register message: %s', $e->getMessage()), [$payload]);
+
+ return;
+ }
+
+ $this->emit('register', [$message]);
+ }
+
+ /**
+ * @param string $payload
+ */
+ private function onPush($payload)
+ {
+ try {
+ $message = new Push($payload);
+ } catch (\Exception $e) {
+ $this->logger->warning(sprintf('Failed to decode push message: %s', $e->getMessage()), [$payload]);
+
+ return;
+ }
+
+ $this->emit('push', [$message]);
+ }
+
+ /**
+ * @param Message $message
+ */
+ private function onMessage(Message $message)
+ {
+ $payload = @zlib_decode($message->getPayload());
+ if ($payload === false) {
+ $this->logger->warning('Failed to inflate a payload.');
+
+ return;
+ }
+
+ $topic = $this->unmapTopic($message->getTopic());
+ $this->logger->info(sprintf('Received a message from topic "%s".', $topic), [$payload]);
+
+ switch ($topic) {
+ case self::MESSAGE_TOPIC:
+ $this->onPush($payload);
+ break;
+ case self::REG_RESP_TOPIC:
+ $this->onRegister($payload);
+ break;
+ default:
+ $this->logger->warning(sprintf('Received a message from unknown topic "%s".', $topic), [$payload]);
+ }
+ }
+
+ /**
+ * Establishes a connection to the FBNS server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return PromiseInterface
+ */
+ private function establishConnection($host, $port, Connection $connection, $timeout)
+ {
+ $this->logger->info(sprintf('Connecting to %s:%d...', $host, $port));
+
+ return $this->client->connect($host, $port, $connection, $timeout);
+ }
+
+ /**
+ * Connects to a FBNS server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return PromiseInterface
+ */
+ public function connect($host, $port, Connection $connection, $timeout = 5)
+ {
+ $deferred = new Deferred();
+ $this->disconnect()
+ ->then(function () use ($deferred, $host, $port, $connection, $timeout) {
+ $this->establishConnection($host, $port, $connection, $timeout)
+ ->then(function () use ($deferred) {
+ $deferred->resolve($this);
+ })
+ ->otherwise(function (\Exception $error) use ($deferred) {
+ $deferred->reject($error);
+ });
+ })
+ ->otherwise(function () use ($deferred) {
+ $deferred->reject($this);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @return PromiseInterface
+ */
+ public function disconnect()
+ {
+ if ($this->client->isConnected()) {
+ $deferred = new Deferred();
+ $this->client->disconnect()
+ ->then(function () use ($deferred) {
+ $deferred->resolve($this);
+ })
+ ->otherwise(function () use ($deferred) {
+ $deferred->reject($this);
+ });
+
+ return $deferred->promise();
+ } else {
+ return new FulfilledPromise($this);
+ }
+ }
+
+ /**
+ * Maps human readable topic to its ID.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ private function mapTopic($topic)
+ {
+ if (array_key_exists($topic, self::TOPIC_TO_ID_ENUM)) {
+ $result = self::TOPIC_TO_ID_ENUM[$topic];
+ $this->logger->debug(sprintf('Topic "%s" has been mapped to "%s".', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->logger->debug(sprintf('Topic "%s" does not exist in enum.', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Maps topic ID to human readable name.
+ *
+ * @param string $topic
+ *
+ * @return string
+ */
+ private function unmapTopic($topic)
+ {
+ if (array_key_exists($topic, self::ID_TO_TOPIC_ENUM)) {
+ $result = self::ID_TO_TOPIC_ENUM[$topic];
+ $this->logger->debug(sprintf('Topic ID "%s" has been unmapped to "%s".', $topic, $result));
+ } else {
+ $result = $topic;
+ $this->logger->debug(sprintf('Topic ID "%s" does not exist in enum.', $topic));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Publish a message to a topic.
+ *
+ * @param string $topic
+ * @param string $message
+ * @param int $qosLevel
+ *
+ * @return \React\Promise\ExtendedPromiseInterface
+ */
+ private function publish($topic, $message, $qosLevel)
+ {
+ $this->logger->info(sprintf('Sending message to topic "%s".', $topic), [$message]);
+ $topic = $this->mapTopic($topic);
+ $payload = zlib_encode($message, ZLIB_ENCODING_DEFLATE, 9);
+
+ return $this->client->publish(new DefaultMessage($topic, $payload, $qosLevel));
+ }
+
+ /**
+ * Registers an application.
+ *
+ * @param string $packageName
+ * @param string|int $appId
+ *
+ * @return PromiseInterface
+ */
+ public function register($packageName, $appId)
+ {
+ $this->logger->info(sprintf('Registering application "%s" (%s).', $packageName, $appId));
+ $message = json_encode([
+ 'pkg_name' => (string) $packageName,
+ 'appid' => (string) $appId,
+ ]);
+
+ return $this->publish(self::REG_REQ_TOPIC, $message, self::QOS_LEVEL);
+ }
+
+ /**
+ * Checks whether underlying client is connected.
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->client->isConnected();
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Lite/ConnectRequestPacket.php b/vendor/valga/fbns-react/src/Lite/ConnectRequestPacket.php
new file mode 100755
index 0000000..d47e7c6
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Lite/ConnectRequestPacket.php
@@ -0,0 +1,198 @@
+assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+ $this->protocolName = $stream->readString();
+ $this->protocolLevel = $stream->readByte();
+ $this->flags = $stream->readByte();
+ $this->keepAlive = $stream->readWord();
+
+ $payloadLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
+ $this->payload = $stream->read($payloadLength);
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeString($this->protocolName);
+ $data->writeByte($this->protocolLevel);
+ $data->writeByte($this->flags);
+ $data->writeWord($this->keepAlive);
+ $data->write($this->payload);
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the protocol level.
+ *
+ * @return int
+ */
+ public function getProtocolLevel()
+ {
+ return $this->protocolLevel;
+ }
+
+ /**
+ * Sets the protocol level.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setProtocolLevel($value)
+ {
+ if ($value != 3) {
+ throw new \InvalidArgumentException(sprintf('Unknown protocol level %d.', $value));
+ }
+
+ $this->protocolLevel = $value;
+ }
+
+ /**
+ * Returns the payload.
+ *
+ * @return string
+ */
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ /**
+ * Sets the payload.
+ *
+ * @param string $value
+ */
+ public function setPayload($value)
+ {
+ $this->payload = $value;
+ }
+
+ /**
+ * Returns the flags.
+ *
+ * @return int
+ */
+ public function getFlags()
+ {
+ return $this->flags;
+ }
+
+ /**
+ * Sets the flags.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setFlags($value)
+ {
+ if ($value > 255) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected a flags lower than 255 but got %d.',
+ $value
+ )
+ );
+ }
+
+ $this->flags = $value;
+ }
+
+ /**
+ * Returns the keep alive time in seconds.
+ *
+ * @return int
+ */
+ public function getKeepAlive()
+ {
+ return $this->keepAlive;
+ }
+
+ /**
+ * Sets the keep alive time in seconds.
+ *
+ * @param int $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setKeepAlive($value)
+ {
+ if ($value > 65535) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Expected a keep alive time lower than 65535 but got %d.',
+ $value
+ )
+ );
+ }
+
+ $this->keepAlive = $value;
+ }
+
+ /**
+ * Returns the protocol name.
+ *
+ * @return string
+ */
+ public function getProtocolName()
+ {
+ return $this->protocolName;
+ }
+
+ /**
+ * Sets the protocol name.
+ *
+ * @param string $value
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setProtocolName($value)
+ {
+ $this->assertValidStringLength($value, false);
+
+ $this->protocolName = $value;
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Lite/ConnectResponsePacket.php b/vendor/valga/fbns-react/src/Lite/ConnectResponsePacket.php
new file mode 100755
index 0000000..da4f025
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Lite/ConnectResponsePacket.php
@@ -0,0 +1,145 @@
+ [
+ 'Connection accepted',
+ '',
+ ],
+ 1 => [
+ 'Unacceptable protocol version',
+ 'The Server does not support the level of the MQTT protocol requested by the client.',
+ ],
+ 2 => [
+ 'Identifier rejected',
+ 'The client identifier is correct UTF-8 but not allowed by the server.',
+ ],
+ 3 => [
+ 'Server unavailable',
+ 'The network connection has been made but the MQTT service is unavailable',
+ ],
+ 4 => [
+ 'Bad user name or password',
+ 'The data in the user name or password is malformed.',
+ ],
+ 5 => [
+ 'Not authorized',
+ 'The client is not authorized to connect.',
+ ],
+ ];
+
+ /** @var int */
+ private $flags = 0;
+ /** @var int */
+ private $returnCode;
+ /** @var string */
+ private $auth;
+
+ protected static $packetType = Packet::TYPE_CONNACK;
+
+ public function read(PacketStream $stream)
+ {
+ parent::read($stream);
+ $this->assertPacketFlags(0);
+ $this->assertRemainingPacketLength();
+
+ $originalPosition = $stream->getPosition();
+ $this->flags = $stream->readByte();
+ $this->returnCode = $stream->readByte();
+
+ $authLength = $this->remainingPacketLength - ($stream->getPosition() - $originalPosition);
+ if ($authLength) {
+ $this->auth = $stream->readString();
+ } else {
+ $this->auth = '';
+ }
+ }
+
+ public function write(PacketStream $stream)
+ {
+ $data = new PacketStream();
+
+ $data->writeByte($this->flags);
+ $data->writeByte($this->returnCode);
+
+ if ($this->auth !== null && strlen($this->auth)) {
+ $data->writeString($this->auth);
+ }
+
+ $this->remainingPacketLength = $data->length();
+
+ parent::write($stream);
+ $stream->write($data->getData());
+ }
+
+ /**
+ * Returns the return code.
+ *
+ * @return int
+ */
+ public function getReturnCode()
+ {
+ return $this->returnCode;
+ }
+
+ /**
+ * Indicates if the connection was successful.
+ *
+ * @return bool
+ */
+ public function isSuccess()
+ {
+ return $this->returnCode === 0;
+ }
+
+ /**
+ * Indicates if the connection failed.
+ *
+ * @return bool
+ */
+ public function isError()
+ {
+ return $this->returnCode > 0;
+ }
+
+ /**
+ * Returns a string representation of the returned error code.
+ *
+ * @return int
+ */
+ public function getErrorName()
+ {
+ if (isset(self::$returnCodes[$this->returnCode])) {
+ return self::$returnCodes[$this->returnCode][0];
+ }
+
+ return 'Error '.$this->returnCode;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAuth()
+ {
+ return $this->auth;
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Lite/OutgoingConnectFlow.php b/vendor/valga/fbns-react/src/Lite/OutgoingConnectFlow.php
new file mode 100755
index 0000000..eeeffbd
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Lite/OutgoingConnectFlow.php
@@ -0,0 +1,74 @@
+connection = $connection;
+ }
+
+ public function getCode()
+ {
+ return 'connect';
+ }
+
+ public function start()
+ {
+ $packet = new ConnectRequestPacket();
+ $packet->setProtocolLevel(self::PROTOCOL_LEVEL);
+ $packet->setProtocolName(self::PROTOCOL_NAME);
+ $packet->setKeepAlive(self::KEEPALIVE);
+ $packet->setFlags(194);
+ $packet->setPayload(zlib_encode($this->connection->toThrift(), ZLIB_ENCODING_DEFLATE, 9));
+
+ return $packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $packet->getPacketType() === Packet::TYPE_CONNACK;
+ }
+
+ public function next(Packet $packet)
+ {
+ /** @var ConnectResponsePacket $packet */
+ if ($packet->isSuccess()) {
+ $this->succeed($packet);
+ } else {
+ $this->fail($packet->getErrorName());
+ }
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Lite/PacketFactory.php b/vendor/valga/fbns-react/src/Lite/PacketFactory.php
new file mode 100755
index 0000000..c824b8b
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Lite/PacketFactory.php
@@ -0,0 +1,74 @@
+ ConnectRequestPacket::class,
+ Packet::TYPE_CONNACK => ConnectResponsePacket::class,
+ Packet::TYPE_PUBLISH => PublishRequestPacket::class,
+ Packet::TYPE_PUBACK => PublishAckPacket::class,
+ Packet::TYPE_PUBREC => PublishReceivedPacket::class,
+ Packet::TYPE_PUBREL => PublishReleasePacket::class,
+ Packet::TYPE_PUBCOMP => PublishCompletePacket::class,
+ Packet::TYPE_SUBSCRIBE => SubscribeRequestPacket::class,
+ Packet::TYPE_SUBACK => SubscribeResponsePacket::class,
+ Packet::TYPE_UNSUBSCRIBE => UnsubscribeRequestPacket::class,
+ Packet::TYPE_UNSUBACK => UnsubscribeResponsePacket::class,
+ Packet::TYPE_PINGREQ => PingRequestPacket::class,
+ Packet::TYPE_PINGRESP => PingResponsePacket::class,
+ Packet::TYPE_DISCONNECT => DisconnectRequestPacket::class,
+ ];
+
+ /**
+ * Builds a packet object for the given type.
+ *
+ * @param int $type
+ *
+ * @throws UnknownPacketTypeException
+ *
+ * @return Packet
+ */
+ public function build($type)
+ {
+ if (!isset(self::$mapping[$type])) {
+ throw new UnknownPacketTypeException(sprintf('Unknown packet type %d.', $type));
+ }
+
+ $class = self::$mapping[$type];
+
+ return new $class();
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Lite/ReactFlow.php b/vendor/valga/fbns-react/src/Lite/ReactFlow.php
new file mode 100755
index 0000000..91d7095
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Lite/ReactFlow.php
@@ -0,0 +1,120 @@
+decorated = $decorated;
+ $this->deferred = $deferred;
+ $this->packet = $packet;
+ $this->isSilent = $isSilent;
+ }
+
+ public function getCode()
+ {
+ return $this->decorated->getCode();
+ }
+
+ public function start()
+ {
+ $this->packet = $this->decorated->start();
+
+ return $this->packet;
+ }
+
+ public function accept(Packet $packet)
+ {
+ return $this->decorated->accept($packet);
+ }
+
+ public function next(Packet $packet)
+ {
+ $this->packet = $this->decorated->next($packet);
+
+ return $this->packet;
+ }
+
+ public function isFinished()
+ {
+ return $this->decorated->isFinished();
+ }
+
+ public function isSuccess()
+ {
+ return $this->decorated->isSuccess();
+ }
+
+ public function getResult()
+ {
+ return $this->decorated->getResult();
+ }
+
+ public function getErrorMessage()
+ {
+ return $this->decorated->getErrorMessage();
+ }
+
+ /**
+ * Returns the associated deferred.
+ *
+ * @return Deferred
+ */
+ public function getDeferred()
+ {
+ return $this->deferred;
+ }
+
+ /**
+ * Returns the current packet.
+ *
+ * @return Packet
+ */
+ public function getPacket()
+ {
+ return $this->packet;
+ }
+
+ /**
+ * Indicates if the flow should emit events.
+ *
+ * @return bool
+ */
+ public function isSilent()
+ {
+ return $this->isSilent;
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Lite/ReactMqttClient.php b/vendor/valga/fbns-react/src/Lite/ReactMqttClient.php
new file mode 100755
index 0000000..49f8e23
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Lite/ReactMqttClient.php
@@ -0,0 +1,695 @@
+connector = $connector;
+ $this->loop = $loop;
+
+ $this->parser = $parser;
+ if ($this->parser === null) {
+ $this->parser = new StreamParser();
+ }
+
+ $this->parser->onError(function (\Exception $e) {
+ $this->emitWarning($e);
+ });
+
+ $this->identifierGenerator = $identifierGenerator;
+ if ($this->identifierGenerator === null) {
+ $this->identifierGenerator = new DefaultIdentifierGenerator();
+ }
+ }
+
+ /**
+ * Return the host.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Return the port.
+ *
+ * @return string
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Indicates if the client is connected.
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->isConnected;
+ }
+
+ /**
+ * Returns the underlying stream or null if the client is not connected.
+ *
+ * @return DuplexStreamInterface|null
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+
+ /**
+ * Connects to a broker.
+ *
+ * @param string $host
+ * @param int $port
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function connect($host, $port, Connection $connection, $timeout = 5)
+ {
+ if ($this->isConnected || $this->isConnecting) {
+ return new RejectedPromise(new \LogicException('The client is already connected.'));
+ }
+
+ $this->isConnecting = true;
+ $this->isConnected = false;
+
+ $this->host = $host;
+ $this->port = $port;
+
+ $deferred = new Deferred();
+
+ $this->establishConnection($this->host, $this->port, $timeout)
+ ->then(function (DuplexStreamInterface $stream) use ($connection, $deferred, $timeout) {
+ $this->stream = $stream;
+
+ $this->emit('open', [$connection, $this]);
+
+ $this->registerClient($connection, $timeout)
+ ->then(function (ConnectResponsePacket $responsePacket) use ($deferred, $connection) {
+ $this->isConnecting = false;
+ $this->isConnected = true;
+ $this->connection = $connection;
+
+ $this->emit('connect', [$responsePacket, $this]);
+ $deferred->resolve($responsePacket);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred, $connection) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+
+ $this->emit('close', [$connection, $this]);
+ });
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $this->isConnecting = false;
+
+ $this->emitError($e);
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Disconnects from a broker.
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function disconnect()
+ {
+ if (!$this->isConnected || $this->isDisconnecting) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $this->isDisconnecting = true;
+
+ $deferred = new Deferred();
+
+ $connection = new DefaultConnection();
+ $this->startFlow(new OutgoingDisconnectFlow($connection), true)
+ ->then(function () use ($connection, $deferred) {
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+
+ $this->emit('disconnect', [$connection, $this]);
+ $deferred->resolve($connection);
+
+ if ($this->stream !== null) {
+ $this->stream->close();
+ }
+ })
+ ->otherwise(function () use ($deferred) {
+ $this->isDisconnecting = false;
+ $deferred->reject($this->connection);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Subscribes to a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function subscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingSubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Unsubscribes from a topic filter.
+ *
+ * @param Subscription $subscription
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function unsubscribe(Subscription $subscription)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingUnsubscribeFlow([$subscription], $this->identifierGenerator));
+ }
+
+ /**
+ * Publishes a message.
+ *
+ * @param Message $message
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publish(Message $message)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ return $this->startFlow(new OutgoingPublishFlow($message, $this->identifierGenerator));
+ }
+
+ /**
+ * Calls the given generator periodically and publishes the return value.
+ *
+ * @param int $interval
+ * @param Message $message
+ * @param callable $generator
+ *
+ * @return ExtendedPromiseInterface
+ */
+ public function publishPeriodically($interval, Message $message, callable $generator)
+ {
+ if (!$this->isConnected) {
+ return new RejectedPromise(new \LogicException('The client is not connected.'));
+ }
+
+ $deferred = new Deferred();
+
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ $interval,
+ function () use ($message, $generator, $deferred) {
+ $this->publish($message->withPayload($generator($message->getTopic())))->then(
+ function ($value) use ($deferred) {
+ $deferred->notify($value);
+ },
+ function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ }
+ );
+ }
+ );
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Emits warnings.
+ *
+ * @param \Exception $e
+ */
+ private function emitWarning(\Exception $e)
+ {
+ $this->emit('warning', [$e, $this]);
+ }
+
+ /**
+ * Emits errors.
+ *
+ * @param \Exception $e
+ */
+ private function emitError(\Exception $e)
+ {
+ $this->emit('error', [$e, $this]);
+ }
+
+ /**
+ * Establishes a network connection to a server.
+ *
+ * @param string $host
+ * @param int $port
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function establishConnection($host, $port, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $timer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('Connection timed out after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->connector->connect($host.':'.$port)
+ ->always(function () use ($timer) {
+ $this->loop->cancelTimer($timer);
+ })
+ ->then(function (DuplexStreamInterface $stream) use ($deferred) {
+ $stream->on('data', function ($data) {
+ $this->handleReceive($data);
+ });
+
+ $stream->on('close', function () {
+ $this->handleClose();
+ });
+
+ $stream->on('error', function (\Exception $e) {
+ $this->handleError($e);
+ });
+
+ $deferred->resolve($stream);
+ })
+ ->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Registers a new client with the broker.
+ *
+ * @param Connection $connection
+ * @param int $timeout
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function registerClient(Connection $connection, $timeout)
+ {
+ $deferred = new Deferred();
+
+ $responseTimer = $this->loop->addTimer(
+ $timeout,
+ function () use ($deferred, $timeout) {
+ $exception = new \RuntimeException(sprintf('No response after %d seconds.', $timeout));
+ $deferred->reject($exception);
+ }
+ );
+
+ $this->startFlow(new OutgoingConnectFlow($connection), true)
+ ->always(function () use ($responseTimer) {
+ $this->loop->cancelTimer($responseTimer);
+ })->then(function (ConnectResponsePacket $responsePacket) use ($deferred) {
+ $this->timer[] = $this->loop->addPeriodicTimer(
+ OutgoingConnectFlow::KEEPALIVE - OutgoingConnectFlow::KEEPALIVE_TIMEOUT,
+ function () {
+ $this->startFlow(new OutgoingPingFlow());
+ }
+ );
+
+ $deferred->resolve($responsePacket);
+ })->otherwise(function (\Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Handles incoming data.
+ *
+ * @param string $data
+ */
+ private function handleReceive($data)
+ {
+ if (!$this->isConnected && !$this->isConnecting) {
+ return;
+ }
+
+ $flowCount = count($this->receivingFlows);
+
+ $packets = $this->parser->push($data);
+ foreach ($packets as $packet) {
+ $this->handlePacket($packet);
+ }
+
+ if ($flowCount > count($this->receivingFlows)) {
+ $this->receivingFlows = array_values($this->receivingFlows);
+ }
+
+ $this->handleSend();
+ }
+
+ /**
+ * Handles an incoming packet.
+ *
+ * @param Packet $packet
+ */
+ private function handlePacket(Packet $packet)
+ {
+ switch ($packet->getPacketType()) {
+ case Packet::TYPE_PUBLISH:
+ /* @var PublishRequestPacket $packet */
+ $message = new DefaultMessage(
+ $packet->getTopic(),
+ $packet->getPayload(),
+ $packet->getQosLevel(),
+ $packet->isRetained(),
+ $packet->isDuplicate()
+ );
+
+ $this->startFlow(new IncomingPublishFlow($message, $packet->getIdentifier()));
+ break;
+ case Packet::TYPE_CONNACK:
+ case Packet::TYPE_PINGRESP:
+ case Packet::TYPE_SUBACK:
+ case Packet::TYPE_UNSUBACK:
+ case Packet::TYPE_PUBREL:
+ case Packet::TYPE_PUBACK:
+ case Packet::TYPE_PUBREC:
+ case Packet::TYPE_PUBCOMP:
+ $flowFound = false;
+ foreach ($this->receivingFlows as $index => $flow) {
+ if ($flow->accept($packet)) {
+ $flowFound = true;
+
+ unset($this->receivingFlows[$index]);
+ $this->continueFlow($flow, $packet);
+
+ break;
+ }
+ }
+
+ if (!$flowFound) {
+ $this->emitWarning(
+ new \LogicException(sprintf('Received unexpected packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ break;
+ default:
+ $this->emitWarning(
+ new \LogicException(sprintf('Cannot handle packet of type %d.', $packet->getPacketType()))
+ );
+ }
+ }
+
+ /**
+ * Handles outgoing packets.
+ */
+ private function handleSend()
+ {
+ $flow = null;
+ if ($this->writtenFlow !== null) {
+ $flow = $this->writtenFlow;
+ $this->writtenFlow = null;
+ }
+
+ if (count($this->sendingFlows) > 0) {
+ $this->writtenFlow = array_shift($this->sendingFlows);
+ $this->stream->write($this->writtenFlow->getPacket());
+ }
+
+ if ($flow !== null) {
+ if ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ } else {
+ $this->receivingFlows[] = $flow;
+ }
+ }
+ }
+
+ /**
+ * Handles closing of the stream.
+ */
+ private function handleClose()
+ {
+ foreach ($this->timer as $timer) {
+ $this->loop->cancelTimer($timer);
+ }
+ $this->timer = [];
+
+ $this->cleanPreviousSession();
+
+ $connection = $this->connection;
+
+ $this->isConnecting = false;
+ $this->isDisconnecting = false;
+ $this->isConnected = false;
+ $this->connection = null;
+ $this->stream = null;
+
+ if ($connection !== null) {
+ $this->emit('close', [$connection, $this]);
+ }
+ }
+
+ /**
+ * Handles errors of the stream.
+ *
+ * @param \Exception $e
+ */
+ private function handleError(\Exception $e)
+ {
+ $this->emitError($e);
+ }
+
+ /**
+ * Starts the given flow.
+ *
+ * @param Flow $flow
+ * @param bool $isSilent
+ *
+ * @return ExtendedPromiseInterface
+ */
+ private function startFlow(Flow $flow, $isSilent = false)
+ {
+ try {
+ $packet = $flow->start();
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return new RejectedPromise($e);
+ }
+
+ $deferred = new Deferred();
+ $internalFlow = new ReactFlow($flow, $deferred, $packet, $isSilent);
+
+ if ($packet !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $internalFlow;
+ } else {
+ $this->stream->write($packet);
+ $this->writtenFlow = $internalFlow;
+ $this->handleSend();
+ }
+ } else {
+ $this->loop->nextTick(function () use ($internalFlow) {
+ $this->finishFlow($internalFlow);
+ });
+ }
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Continues the given flow.
+ *
+ * @param ReactFlow $flow
+ * @param Packet $packet
+ */
+ private function continueFlow(ReactFlow $flow, Packet $packet)
+ {
+ try {
+ $response = $flow->next($packet);
+ } catch (\Exception $e) {
+ $this->emitError($e);
+
+ return;
+ }
+
+ if ($response !== null) {
+ if ($this->writtenFlow !== null) {
+ $this->sendingFlows[] = $flow;
+ } else {
+ $this->stream->write($response);
+ $this->writtenFlow = $flow;
+ $this->handleSend();
+ }
+ } elseif ($flow->isFinished()) {
+ $this->loop->nextTick(function () use ($flow) {
+ $this->finishFlow($flow);
+ });
+ }
+ }
+
+ /**
+ * Finishes the given flow.
+ *
+ * @param ReactFlow $flow
+ */
+ private function finishFlow(ReactFlow $flow)
+ {
+ if ($flow->isSuccess()) {
+ if (!$flow->isSilent()) {
+ $this->emit($flow->getCode(), [$flow->getResult(), $this]);
+ }
+
+ $flow->getDeferred()->resolve($flow->getResult());
+ } else {
+ $result = new \RuntimeException($flow->getErrorMessage());
+ $this->emitWarning($result);
+
+ $flow->getDeferred()->reject($result);
+ }
+ }
+
+ /**
+ * Cleans previous session by rejecting all pending flows.
+ */
+ private function cleanPreviousSession()
+ {
+ $error = new \RuntimeException('Connection has been closed.');
+ foreach ($this->receivingFlows as $receivingFlow) {
+ $receivingFlow->getDeferred()->reject($error);
+ }
+ $this->receivingFlows = [];
+ foreach ($this->sendingFlows as $sendingFlow) {
+ $sendingFlow->getDeferred()->reject($error);
+ }
+ $this->sendingFlows = [];
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Lite/StreamParser.php b/vendor/valga/fbns-react/src/Lite/StreamParser.php
new file mode 100755
index 0000000..edfabf6
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Lite/StreamParser.php
@@ -0,0 +1,102 @@
+buffer = new PacketStream();
+ $this->factory = new PacketFactory();
+ }
+
+ /**
+ * Registers an error callback.
+ *
+ * @param callable $callback
+ */
+ public function onError($callback)
+ {
+ $this->errorCallback = $callback;
+ }
+
+ /**
+ * Appends the given data to the internal buffer and parses it.
+ *
+ * @param string $data
+ *
+ * @return Packet[]
+ */
+ public function push($data)
+ {
+ $this->buffer->write($data);
+
+ $result = [];
+ while ($this->buffer->getRemainingBytes() > 0) {
+ $type = $this->buffer->readByte() >> 4;
+ try {
+ $packet = $this->factory->build($type);
+ } catch (UnknownPacketTypeException $e) {
+ $this->handleError($e);
+ continue;
+ }
+
+ $this->buffer->seek(-1);
+ $position = $this->buffer->getPosition();
+ try {
+ $packet->read($this->buffer);
+ $result[] = $packet;
+ $this->buffer->cut();
+ } catch (EndOfStreamException $e) {
+ $this->buffer->setPosition($position);
+ break;
+ } catch (MalformedPacketException $e) {
+ $this->handleError($e);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Executes the registered error callback.
+ *
+ * @param \Throwable $exception
+ */
+ private function handleError($exception)
+ {
+ if ($this->errorCallback !== null) {
+ $callback = $this->errorCallback;
+ $callback($exception);
+ }
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Message/Push.php b/vendor/valga/fbns-react/src/Message/Push.php
new file mode 100755
index 0000000..9a04ea2
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Message/Push.php
@@ -0,0 +1,153 @@
+json = $json;
+
+ if (isset($data->token)) {
+ $this->token = (string) $data->token;
+ }
+ if (isset($data->ck)) {
+ $this->connectionKey = (string) $data->ck;
+ }
+ if (isset($data->pn)) {
+ $this->packageName = (string) $data->pn;
+ }
+ if (isset($data->cp)) {
+ $this->collapseKey = (string) $data->cp;
+ }
+ if (isset($data->fbpushnotif)) {
+ $this->payload = (string) $data->fbpushnotif;
+ }
+ if (isset($data->nid)) {
+ $this->notificationId = (string) $data->nid;
+ }
+ if (isset($data->bu)) {
+ $this->isBuffered = (string) $data->bu;
+ }
+ }
+
+ /**
+ * Message constructor.
+ *
+ * @param string $json
+ */
+ public function __construct($json)
+ {
+ $this->parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->json;
+ }
+
+ /**
+ * @return string
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+
+ /**
+ * @return string
+ */
+ public function getConnectionKey()
+ {
+ return $this->connectionKey;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPackageName()
+ {
+ return $this->packageName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCollapseKey()
+ {
+ return $this->collapseKey;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPayload()
+ {
+ return $this->payload;
+ }
+
+ /**
+ * @return string
+ */
+ public function getNotificationId()
+ {
+ return $this->notificationId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIsBuffered()
+ {
+ return $this->isBuffered;
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Message/Register.php b/vendor/valga/fbns-react/src/Message/Register.php
new file mode 100755
index 0000000..6fb9cf5
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Message/Register.php
@@ -0,0 +1,89 @@
+json = $json;
+
+ if (isset($data->pkg_name)) {
+ $this->packageName = (string) $data->pkg_name;
+ }
+ if (isset($data->token)) {
+ $this->token = (string) $data->token;
+ }
+ if (isset($data->error)) {
+ $this->error = (string) $data->error;
+ }
+ }
+
+ /**
+ * Message constructor.
+ *
+ * @param string $json
+ */
+ public function __construct($json)
+ {
+ $this->parseJson($json);
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->json;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPackageName()
+ {
+ return $this->packageName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+
+ /**
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Thrift/Compact.php b/vendor/valga/fbns-react/src/Thrift/Compact.php
new file mode 100755
index 0000000..f045449
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Thrift/Compact.php
@@ -0,0 +1,24 @@
+handler($context, $field, $value, $type);
+ });
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Thrift/Reader.php b/vendor/valga/fbns-react/src/Thrift/Reader.php
new file mode 100755
index 0000000..0b48175
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Thrift/Reader.php
@@ -0,0 +1,263 @@
+buffer = $buffer;
+ $this->position = 0;
+ $this->length = strlen($buffer);
+ $this->field = 0;
+ $this->stack = [];
+ $this->handler = $handler;
+ $this->parse();
+ }
+
+ /**
+ * Parser.
+ */
+ private function parse()
+ {
+ $context = '';
+ while ($this->position < $this->length) {
+ $type = $this->readField();
+ switch ($type) {
+ case Compact::TYPE_STRUCT:
+ array_push($this->stack, $this->field);
+ $this->field = 0;
+ $context = implode('/', $this->stack);
+ break;
+ case Compact::TYPE_STOP:
+ if (!count($this->stack)) {
+ return;
+ }
+ $this->field = array_pop($this->stack);
+ $context = implode('/', $this->stack);
+ break;
+ case Compact::TYPE_LIST:
+ $sizeAndType = $this->readUnsignedByte();
+ $size = $sizeAndType >> 4;
+ $listType = $sizeAndType & 0x0f;
+ if ($size === 0x0f) {
+ $size = $this->readVarint();
+ }
+ $this->handleField($context, $this->field, $this->readList($size, $listType), $listType);
+ break;
+ case Compact::TYPE_TRUE:
+ case Compact::TYPE_FALSE:
+ $this->handleField($context, $this->field, $type === Compact::TYPE_TRUE, $type);
+ break;
+ case Compact::TYPE_BYTE:
+ $this->handleField($context, $this->field, $this->readSignedByte(), $type);
+ break;
+ case Compact::TYPE_I16:
+ case Compact::TYPE_I32:
+ case Compact::TYPE_I64:
+ $this->handleField($context, $this->field, $this->fromZigZag($this->readVarint()), $type);
+ break;
+ case Compact::TYPE_BINARY:
+ $this->handleField($context, $this->field, $this->readString($this->readVarint()), $type);
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param int $size
+ * @param int $type
+ *
+ * @return array
+ */
+ private function readList($size, $type)
+ {
+ $result = [];
+ switch ($type) {
+ case Compact::TYPE_TRUE:
+ case Compact::TYPE_FALSE:
+ for ($i = 0; $i < $size; $i++) {
+ $result[] = $this->readSignedByte() === Compact::TYPE_TRUE;
+ }
+ break;
+ case Compact::TYPE_BYTE:
+ for ($i = 0; $i < $size; $i++) {
+ $result[] = $this->readSignedByte();
+ }
+ break;
+ case Compact::TYPE_I16:
+ case Compact::TYPE_I32:
+ case Compact::TYPE_I64:
+ for ($i = 0; $i < $size; $i++) {
+ $result[] = $this->fromZigZag($this->readVarint());
+ }
+ break;
+ case Compact::TYPE_BINARY:
+ $result[] = $this->readString($this->readVarint());
+ break;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return int
+ */
+ private function readField()
+ {
+ $typeAndDelta = ord($this->buffer[$this->position++]);
+ if ($typeAndDelta === Compact::TYPE_STOP) {
+ return Compact::TYPE_STOP;
+ }
+ $delta = $typeAndDelta >> 4;
+ if ($delta === 0) {
+ $this->field = $this->fromZigZag($this->readVarint());
+ } else {
+ $this->field += $delta;
+ }
+ $type = $typeAndDelta & 0x0f;
+
+ return $type;
+ }
+
+ /**
+ * @return int
+ */
+ private function readSignedByte()
+ {
+ $result = $this->readUnsignedByte();
+ if ($result > 0x7f) {
+ $result = 0 - (($result - 1) ^ 0xff);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return int
+ */
+ private function readUnsignedByte()
+ {
+ return ord($this->buffer[$this->position++]);
+ }
+
+ /**
+ * @return int
+ */
+ private function readVarint()
+ {
+ $shift = 0;
+ $result = 0;
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_init($result, 10);
+ }
+ while ($this->position < $this->length) {
+ $byte = ord($this->buffer[$this->position++]);
+ if (PHP_INT_SIZE === 4) {
+ $byte = gmp_init($byte, 10);
+ }
+ $result |= ($byte & 0x7f) << $shift;
+ if (PHP_INT_SIZE === 4) {
+ $byte = (int) gmp_strval($byte, 10);
+ }
+ if ($byte >> 7 === 0) {
+ break;
+ }
+ $shift += 7;
+ }
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_strval($result, 10);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $n
+ *
+ * @return int
+ */
+ private function fromZigZag($n)
+ {
+ if (PHP_INT_SIZE === 4) {
+ $n = gmp_init($n, 10);
+ }
+ $result = ($n >> 1) ^ -($n & 1);
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_strval($result, 10);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $length
+ *
+ * @return string
+ */
+ private function readString($length)
+ {
+ $result = substr($this->buffer, $this->position, $length);
+ $this->position += $length;
+
+ return $result;
+ }
+
+ /**
+ * @param string $context
+ * @param int $field
+ * @param mixed $value
+ * @param int $type
+ */
+ private function handleField($context, $field, $value, $type)
+ {
+ if (!is_callable($this->handler)) {
+ return;
+ }
+ call_user_func($this->handler, $context, $field, $value, $type);
+ }
+}
diff --git a/vendor/valga/fbns-react/src/Thrift/Writer.php b/vendor/valga/fbns-react/src/Thrift/Writer.php
new file mode 100755
index 0000000..229bdf1
--- /dev/null
+++ b/vendor/valga/fbns-react/src/Thrift/Writer.php
@@ -0,0 +1,279 @@
+> ($bits - 1));
+ if (PHP_INT_SIZE === 4) {
+ $result = gmp_strval($result, 10);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeByte($number)
+ {
+ $this->buffer .= chr($number);
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeWord($number)
+ {
+ $this->writeVarint($this->toZigZag($number, 16));
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeInt($number)
+ {
+ $this->writeVarint($this->toZigZag($number, 32));
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeLongInt($number)
+ {
+ $this->writeVarint($this->toZigZag($number, 64));
+ }
+
+ /**
+ * @param int $field
+ * @param int $type
+ */
+ private function writeField($field, $type)
+ {
+ $delta = $field - $this->field;
+ if ((0 < $delta) && ($delta <= 15)) {
+ $this->writeByte(($delta << 4) | $type);
+ } else {
+ $this->writeByte($type);
+ $this->writeWord($field);
+ }
+ $this->field = $field;
+ }
+
+ /**
+ * @param int $number
+ */
+ private function writeVarint($number)
+ {
+ if (PHP_INT_SIZE === 4) {
+ $number = gmp_init($number, 10);
+ }
+ while (true) {
+ $byte = $number & (~0x7f);
+ if (PHP_INT_SIZE === 4) {
+ $byte = (int) gmp_strval($byte, 10);
+ }
+ if ($byte === 0) {
+ if (PHP_INT_SIZE === 4) {
+ $number = (int) gmp_strval($number, 10);
+ }
+ $this->buffer .= chr($number);
+ break;
+ } else {
+ $byte = ($number & 0xff) | 0x80;
+ if (PHP_INT_SIZE === 4) {
+ $byte = (int) gmp_strval($byte, 10);
+ }
+ $this->buffer .= chr($byte);
+ $number = $number >> 7;
+ }
+ }
+ }
+
+ /**
+ * @param string $data
+ */
+ private function writeBinary($data)
+ {
+ $this->buffer .= $data;
+ }
+
+ /**
+ * @param int $field
+ * @param bool $value
+ */
+ public function writeBool($field, $value)
+ {
+ $this->writeField($field, $value ? Compact::TYPE_TRUE : Compact::TYPE_FALSE);
+ }
+
+ /**
+ * @param int $field
+ * @param string $string
+ */
+ public function writeString($field, $string)
+ {
+ $this->writeField($field, Compact::TYPE_BINARY);
+ $this->writeVarint(strlen($string));
+ $this->writeBinary($string);
+ }
+
+ public function writeStop()
+ {
+ $this->buffer .= chr(Compact::TYPE_STOP);
+ if (count($this->stack)) {
+ $this->field = array_pop($this->stack);
+ }
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt8($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_BYTE);
+ $this->writeByte($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt16($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_I16);
+ $this->writeWord($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt32($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_I32);
+ $this->writeInt($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $number
+ */
+ public function writeInt64($field, $number)
+ {
+ $this->writeField($field, Compact::TYPE_I64);
+ $this->writeLongInt($number);
+ }
+
+ /**
+ * @param int $field
+ * @param int $type
+ * @param array $list
+ */
+ public function writeList($field, $type, array $list)
+ {
+ $this->writeField($field, Compact::TYPE_LIST);
+ $size = count($list);
+ if ($size < 0x0f) {
+ $this->writeByte(($size << 4) | $type);
+ } else {
+ $this->writeByte(0xf0 | $type);
+ $this->writeVarint($size);
+ }
+
+ switch ($type) {
+ case Compact::TYPE_TRUE:
+ case Compact::TYPE_FALSE:
+ foreach ($list as $value) {
+ $this->writeByte($value ? Compact::TYPE_TRUE : Compact::TYPE_FALSE);
+ }
+ break;
+ case Compact::TYPE_BYTE:
+ foreach ($list as $number) {
+ $this->writeByte($number);
+ }
+ break;
+ case Compact::TYPE_I16:
+ foreach ($list as $number) {
+ $this->writeWord($number);
+ }
+ break;
+ case Compact::TYPE_I32:
+ foreach ($list as $number) {
+ $this->writeInt($number);
+ }
+ break;
+ case Compact::TYPE_I64:
+ foreach ($list as $number) {
+ $this->writeLongInt($number);
+ }
+ break;
+ case Compact::TYPE_BINARY:
+ foreach ($list as $string) {
+ $this->writeVarint(strlen($string));
+ $this->writeBinary($string);
+ }
+ break;
+ }
+ }
+
+ /**
+ * @param int $field
+ */
+ public function writeStruct($field)
+ {
+ $this->writeField($field, Compact::TYPE_STRUCT);
+ $this->stack[] = $this->field;
+ $this->field = 0;
+ }
+
+ public function __construct()
+ {
+ if (PHP_INT_SIZE === 4 && !extension_loaded('gmp')) {
+ throw new \RuntimeException('You need to install GMP extension to run this code with x86 PHP build.');
+ }
+ $this->buffer = '';
+ $this->field = 0;
+ $this->stack = [];
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->buffer;
+ }
+}
diff --git a/vendor/winbox/args/LICENSE b/vendor/winbox/args/LICENSE
new file mode 100755
index 0000000..1f5c051
--- /dev/null
+++ b/vendor/winbox/args/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 John Stevenson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/winbox/args/README.md b/vendor/winbox/args/README.md
new file mode 100755
index 0000000..9daab8c
--- /dev/null
+++ b/vendor/winbox/args/README.md
@@ -0,0 +1,50 @@
+Winbox-Args
+===========
+
+[](https://travis-ci.org/johnstevenson/winbox-args)
+[](https://ci.appveyor.com/project/johnstevenson/winbox-args)
+
+A PHP function to escape command-line arguments, which on Windows replaces `escapeshellarg` with a more robust method. Install from [Packagist][packagist] and use it like this:
+
+```php
+$escaped = Winbox\Args::escape($argument);
+```
+
+Alternatively, you can just [copy the code][function] into your own project (but please keep the license attribution and documentation link).
+
+### What it does
+The following transformations are made:
+
+* Double-quotes are escaped with a backslash, with any preceeding backslashes doubled up.
+* The argument is only enclosed in double-quotes if it contains whitespace or is empty.
+* Trailing backslashes are doubled up if the argument is enclosed in double-quotes.
+
+See [How Windows parses the command-line](https://github.com/johnstevenson/winbox-args/wiki/How-Windows-parses-the-command-line) if you would like to know why.
+
+By default, _cmd.exe_ meta characters are also escaped:
+
+* by caret-escaping the transformed argument (if it contains internal double-quotes or `%...%` syntax).
+* or by enclosing the argument in double-quotes.
+
+There are a couple limitations:
+
+1. If _cmd_ is started with _DelayedExpansion_ enabled, `!...!` syntax could expand environment variables.
+2. If the program name requires caret-escaping and contains whitespace, _cmd_ will not recognize it.
+
+See [How cmd.exe parses a command](https://github.com/johnstevenson/winbox-args/wiki/How-cmd.exe-parses-a-command) and [Implementing a solution](https://github.com/johnstevenson/winbox-args/wiki/Implementing-a-solution) for more information.
+
+### Is that it?
+Yup. An entire repo for a tiny function. However, it needs quite a lot of explanation because:
+
+* the command-line parsing rules in Windows are not immediately obvious.
+* PHP generally uses _cmd.exe_ to execute programs and this applies a different set of rules.
+* there is no simple solution.
+
+Full details explaining the different parsing rules, potential pitfalls and limitations can be found in the [Wiki][wiki].
+
+## License
+Winbox-Args is licensed under the MIT License - see the LICENSE file for details.
+
+[function]: https://github.com/johnstevenson/winbox-args/blob/master/src/Args.php#L15
+[wiki]:https://github.com/johnstevenson/winbox-args/wiki/Home
+[packagist]: https://packagist.org/packages/winbox/args
diff --git a/vendor/winbox/args/appveyor.yml b/vendor/winbox/args/appveyor.yml
new file mode 100755
index 0000000..3f90587
--- /dev/null
+++ b/vendor/winbox/args/appveyor.yml
@@ -0,0 +1,22 @@
+build: false
+shallow_clone: false
+platform: 'x86'
+clone_folder: C:\projects\winbox-args
+init:
+ - cinst php
+ - SET PATH=C:\tools\php\;%PATH%
+install:
+ - cd c:\tools\php
+ - copy php.ini-production php.ini
+ - echo date.timezone="UTC" >> php.ini
+ - echo extension_dir=ext >> php.ini
+ - echo extension=php_openssl.dll >> php.ini
+ - echo extension=php_intl.dll >> php.ini
+ - echo extension=php_mbstring.dll >> php.ini
+ - echo extension=php_fileinfo.dll >> php.ini
+ - cd C:\projects\winbox-args
+ - php -r "readfile('https://getcomposer.org/installer');" | php
+ - php composer.phar require phpunit/phpunit:4.* --prefer-dist --dev --no-interaction
+test_script:
+ - cd C:\projects\winbox-args
+ - vendor\bin\phpunit.bat
diff --git a/vendor/winbox/args/composer.json b/vendor/winbox/args/composer.json
new file mode 100755
index 0000000..0491911
--- /dev/null
+++ b/vendor/winbox/args/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "winbox/args",
+ "description": "Windows command-line formatter",
+ "keywords": ["windows", "escape", "command"],
+ "homepage": "http://github.com/johnstevenson/winbox-args",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "autoload": {
+ "psr-4": {
+ "Winbox\\": "src/"
+ }
+ }
+}
diff --git a/vendor/winbox/args/src/Args.php b/vendor/winbox/args/src/Args.php
new file mode 100755
index 0000000..7f3dd89
--- /dev/null
+++ b/vendor/winbox/args/src/Args.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Winbox;
+
+class Args
+{
+ /**
+ * Escapes a string to be used as a shell argument
+ *
+ * Provides a more robust method on Windows than escapeshellarg.
+ *
+ * Feel free to copy this function, but please keep the following notice:
+ * MIT Licensed (c) John Stevenson
+ * See https://github.com/johnstevenson/winbox-args for more information.
+ *
+ * @param string $arg The argument to be escaped
+ * @param bool $meta Additionally escape cmd.exe meta characters
+ *
+ * @return string The escaped argument
+ */
+ public static function escape($arg, $meta = true)
+ {
+ if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
+ return escapeshellarg($arg);
+ }
+
+ $quote = strpbrk($arg, " \t") !== false || $arg === '';
+ $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes);
+
+ if ($meta) {
+ $meta = $dquotes || preg_match('/%[^%]+%/', $arg);
+
+ if (!$meta && !$quote) {
+ $quote = strpbrk($arg, '^&|<>()') !== false;
+ }
+ }
+
+ if ($quote) {
+ $arg = preg_replace('/(\\\\*)$/', '$1$1', $arg);
+ $arg = '"'.$arg.'"';
+ }
+
+ if ($meta) {
+ $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg);
+ }
+
+ return $arg;
+ }
+}